twitter botを作った話
製作記なので、時系列で振り返って取っ散らかってる記事です。現在進行形ですが、公開しないままだとこのまま腐る可能性があるので、とりあえず公開しちゃいます。
某論発表会のときにフレンズ構文が流行り始めていて、この構文は汎用性(主に煽り性能)高いぞ…!と思い内輪向けにtwitterのbotを作ることを思い立ちました。
基本コンセプトは
「異様にねむかったので昼寝したら土曜が溶けたんだけど・・・」
みたいなツイートに対して、
「おもしろーい!キミは異様にねむかったので昼寝したら土曜が溶けたフレンズなんだね!」
みたいなリプライを送るbotです。
初日
Tweepy
そもそもtwitterのbotの作り方を知らなかったのでQiitaとか読みながら作りました。自然言語、機械学習のライブラリとの連携に強いPythonで書きたかったので、TwitterライブラリとしてTweepyを選びました。
kivantiumさんは強いなあと改めて思いました(こなみ)
JUMAN++
また、自然言語処理部分のためには構文解析器JUMAN++を用いました。RNNLMを用いているため、日本語の構文解析器としてのポテンシャルは他のものよりも高いです。
JUMAN++ - KUROHASHI-KAWAHARA LAB
JUMAN++のPythonバインディングとして、PyKNPを使っています。MeCabのPythonバインディングより機能がそろっている気がします。
この日の夕方にはプロトタイプが出来て、仲間内やその周辺でクソリプを送り続けるbotを見て友人たちと盛り上がりました。確か晩御飯食べて友人の研究室に戻るときに1度目のアプリの凍結(アカウントの凍結ではない)が来ました。まあフォロワーもすごい少ない中で一方的にリプを送り続けるbotがいたら凍結されますよねといった感じです。
この時Pythonのコード行数は50行程度でした。50行あれば自然言語処理ツール読んで簡単な処理をしてリプライを飛ばすtwitter botができるのに驚きました。
この時点でそこそこフォロワーが増えたので、ツイートへの反応頻度を下げました。フォロー後最初のツイートを補足してから10分間はほぼリプライを飛ばし続けた後は、ツイートに逐一反応するのではなく、最後に反応した日時からの経過で確率的に反応するようにしました。いろいろ考えましたが最終的にシグモイド関数を採用しています。
2~4日目
Tweepy
最初TweepyのStreaming APIのコールバック関数でツイートの処理を行っていたのですが、ツイート先がずれるバグに悩まされました。原因は恐らくTweepyにあったので、tweepyのコールバック関数ではキューにツイートを突っ込むだけにして、別プロセスでツイートの処理を行うことにしました。
JUMAN++
JUMAN++が全角文字にしかまともに対応していない上に半角の空白や特殊記号を食わせると死ぬ現象に悩まされました。半角文字を全て除去するか、あるいは無視すればいいっちゃいいんですが、なるべくリプを飛ばしたかったのでtimeout_decoratorというライブラリでJUMAN++呼び出しを包んでtry~exceptで無理やり何とかしました。MeCabを使うことも考えたのですが、MeCabだと話し言葉の動詞の検出が死ぬほど出来なかったので諦めました。
また、この辺でJUMAN++自体の性能にも不満が出てきます。遅いのはもう仕方ないですが、感動詞の「わーい」を動詞の「わく」として構文解析するのが許せず、辞書に突っ込むことにしました。MeCabだと有名なユーザー辞書がいくつか知られていますし、ネットにも広く作り方が載っていますが、JUMAN++には公式ドキュメントしかないため、自分で読みながら作る必要があります。
(感動詞 ((読み わーい)(見出し語 わーい))))
(名詞 (固有名詞 ((読み じゃぱりぱーく)(見出し語 じゃぱりぱーく))))
他の辞書データ見れば難しくはないですが、こんな感じで各1行にまとめながらLISPっぽく登録していきます。最初は他の方のブログとか調べながらやってましたが、JUMAN++の情報ではなくJUMANの情報が多いので注意が必要です。
Botとしてはフォローバック機能、ふぁぼ機能を加えました。今回のbotはフォローのツイートのみにリプライを飛ばすので、フォローバック機能は自分にとって画期的でした(初日、2日目は全部手動でフォロバしてた…)。フォロワーが増えたので最初の10分はほぼ必ず反応するようにしていたのを最初の5分間なるべく反応するように変えました。
フォロワーがどんどん増えていくのに驚きました。この頃になるとぼちぼちニュースやまとめサイトなどでも、けものフレンズが話題になり始めます。これは完全にビッグウェーブに乗れたと思いました。あとここでも規制に引っかかり過ぎて凍結(2回目)されました。規制中にAPIを叩き過ぎると怒られるぞい
5~6日目
この辺になってくると技術的には特に面白いことは無くなってきます。フォロワーが1000人超えたのでツイート後36秒間は停止するようにしました。ツイートへの反応確率が今までは最後に反応した日時からの経過のみでしたが、ここは気に入らないので将来的にはもっとスマートなアルゴリズムにしたいと思いました。
7日目
朝起きたらフォロワーが2000超えてました。でもここでもまた凍結が来ます(3回目)。厳しくないですか…もうこの時点ではAPI制限にもほとんど引っかからなくなっていたので油断してました。そろそろアカウントごと消されそうな気持ちになってきたので、どこかのタイミングで何とかしないといけないと思いました。
この日の変更は
- このタイミングで積極的に話しかける方針を諦めて、2日に1回くらい話しかける方針に変更しました。
- 以前はなるべくたくさん呟くように、1分間呟かなかったらリプライを飛ばす確率を上げていたのですが、これも廃止しました。
- リプライの代わりにツイートする確率を上げました。3時間おきに1回程度エアリプを送るようにしました。
とにかくtwitter社に目を付けられないように行動頻度を下げています。コードはこの時ちょうど350行です。もう凍結されたらしばらく休もうかなという気持ちになってきました。
(以下2/27追記)
2週間目
アカウント自体の凍結を食らいました。最初またアプリの凍結かと思いましたが、アカウントの凍結によりアプリも凍結されてました。管理用アカウントで別垢を作っていましたがそちらと同時に凍結されました。論文の締切が近かったので、この辺はTwitterサポートに嘆願メール送るくらいしかしてません。
3週間目
凍結理由は大量のアカウント作成が理由だったようです。同時期に別の方のbot系も凍結されていたので、同一人物が管理していると思われたのかもしれません。凍結解除をもらえたので、早速いくつか変更を加えました。
- フォローからのリプライが飛んでくるまでは自発的にリプライを送らない。
- フォロバは手動で行う。
近年の改定で自動フォロバも禁止されていたようです。全く生きづらい世の中です。
とりあえず現在は安定稼働しているので、このまま様子を見ようと思います。
最近の半教師付き学習(semi-supervised learning)について
(この記事はDeep Learning Advent Calendar 2016 22日目の記事ですが、ほとんどDeep learning関係ありません)
最近分類問題におけるsemi-supervised learningの論文を読んだりとか手法を学んでいて聞いたり思ったりした話をまとめました。
半教師付き学習とは何かについては、
yamaguchiyuto.hatenablog.com上の記事に比較的わかりやすいと思うので、上の記事を読んで思ったことでも書きます。並べながら書いてるので並べながら読んでいって下さい。できればDeep Learning的なのにも触れたい。
Self-training
古典的なsemi-supervised learningの手法だと思います。強くはないが何にでも使えるんじゃないかなあ。
Generative Model
データが何らかのパラメトリックなモデルから生成されるという仮定を置く手法です。古典的にはGMM(混合ガウス分布)などがありますが、分布に対する仮定が強く、一生toy dataで遊んでろみたいな気持ちがあります。最近では半教師付き学習のauto-encoderの論文(Kingma, Diederik P., et al. "Semi-supervised learning with deep generative models." Advances in Neural Information Processing Systems. 2014.)がめっちゃ有名だと思います。実験が面白いですし、DC-GANの流行のきっかけにもなった気がするすごい論文です。次のスライドで分かりやすく解説されています。
Co-training
GANにおけるGeneratorとDescriminatorみたいなものなんですかね(よく知らない)
Ladder NetworkもEncoderとDecoderを用いてるらしいのですが、こういう双方向のtraining methodの呼び名を知りません。
Graph-based SSL (Label propagation)
データにグラフを用いた強い仮定を使って半教師あり学習を行います。two-moonの結果とかが有名。各クラスタ間が十分に離れていれば上手く分類することができます。
Manifold Assumption
データがある多様体上にほぼ存在していると仮定し半教師あり学習を行います。顔認識等で上手くいっているようですが、あまりよく知りません。
Low Density Separation (Purturbation?)
S3VMの発展形としてS4VMがあるらしいが、実装と学習が面倒らしいです。Deep Learning的な手法としては最近はVAT (Miyato, Takeru, et al. "Distributional smoothing by virtual adversarial examples." arXiv preprint arXiv:1507.00677 (2015).)っていうのがあるらしいです。この手法では、各データ点を分類結果がなるべく間違う方向にちょっとずつ動かして学習を繰り返すらしいです。entropy regularizationに近い気がします。ただ、データ点のpurturbation自体はcruster assumptionに基づいたものなので、結果としてlow dentiry separationとしての働きをすると考えるのがよさそうです。
今年のNIPS(すごい国際会議)のPurtabation系の論文としてはDropout正則化とでもいうべき手法(Sajjadi, Mehdi, Mehran Javanmardi, and Tolga Tasdizen. "Regularization With Stochastic Transformations and Perturbations for Deep Semi-Supervised Learning." Advances in Neural Information Processing Systems. 2016.)があります。MNIST100サンプルの半教師付き学習で誤分類率0.55%とかいうすごい性能が出ています。しかし、論文中での実験設定が一部不明瞭な点があったり、fractional-poolingの性質や複数回Dropoutさせる必要があるため学習のコストが大きいことから、再現するのは大変そうです。
One-shot Learning
人間は1つのサンプルを見たら他の無数のサンプルに対して良い分類性能を得ることができるし、機械学習でも同じことができるんじゃね?っていう考え方。かなり強い仮定を必要とし、まだまだ上手くいっているわけではないですが、考え方自体はとても面白いと思います。文字に関するOne-shot learningは次のスライドに載ってます。
Zero-shot Learning
人間は実物を見なくても話に聞いた特徴から推定することができるから、機械学習でも同じことができるんじゃね?っていう考え方。もっと強い仮定を必要とします。word embeddingやattributeと呼ばれる人手で作った特徴量を知識として用いて、そのベクトル表現への回帰問題を考えるイメージだと理解しています。word embeddingの場合、露骨に性能がword embeddingの良さに影響し、attributeの場合既知のクラスと未知のクラスが混ざっていると動かなくなるようです。
おわりに
とっちらかった上に、結局一部ニューラルネットワークの手法が登場しただけでDeep learningではなかった感じがありますが、最近の半教師付き学習としてはこんなところでしょうか。実世界ではデータのラベル付けというのはとてもコストが高く、ラベル付きデータが少なくても上手く動く機械学習アルゴリズムは今後も重要なトピックになると思うので許してください。
本当はpixivのいい感じの画像を自動でスクレイピングするためのミニバッチ学習とかやりたかった....
pycparserを使ってみた/libclang+sealangでPythonでC++をパースする
いろいろあってC言語のASTを扱う必要が出てきて最終的に行き着いたのがこれ。某uccがPythonで書かれてたらucc使ったんだけどね。
インストールはpipで終了。以下自分用のメモ。書いてて気づいたけどc_ast参照すればいいことに気付いた
FuncDef:関数宣言
decl:宣言関連の情報
param_decls:パラメータの情報
body:関数本体
coord:ソースコード中での位置?
Compound:いろいろ
block_items:リストでいろいろ入ってる
例
[<pycparser.c_ast.Decl object at 0x7f77d48456d8>, <pycparser.c_ast.Decl object at 0x7f77d4845748>, <pycparser.c_ast.FuncCall object at 0x7f77d3ee8168>, <pycparser.c_ast.Decl object at 0x7f77d48457b8>, <pycparser.c_ast.Decl object at 0x7f77d4845828>, <pycparser.c_ast.Decl object at 0x7f77d4845898>, <pycparser.c_ast.For object at 0x7f77d79ad518>, <pycparser.c_ast.If object at 0x7f77d3eee0e8>, <pycparser.c_ast.FuncCall object at 0x7f77d3ee8e58>, <pycparser.c_ast.Return object at 0x7f77d3eea908>]
coord:略
Decl:変数宣言?
name:変数名
quals=spec['qual'],:謎
storage=spec['storage']:謎
funcspec=spec['function']:謎
type:型。c_ast.TypeDeclが入ってる
init=decl.get('init'):謎
bitsize=decl.get('bitsize'):取得できなかった
coord:位置
FuncCall:関数呼び出し
name:呼ばれる関数名。c_ast.IDが入ってる
args: 引数リスト。c_ast.ExprListが入ってる
続きを読む
pycparserを使ってみる
ランプ損失サポートベクタ―マシン
ICML2006のTrading convexity for scalabilityを元にざっくりとした解説。
SVMは凸関数の最適化問題であることによって、理論的にその性能が保証されているのが売りだけど、非凸関数も最適化したい!っていうのがモチベっぽい。
分類器
の最適化において、パラメタを
として、
を最適化するのが通常のSVM。
この論文ではランプ損失を
とおいて、
となる式をCCCP法を用いて最適化する。
式書くのだるくなったので論文からコピペ引用
で、pythonで実装してみた。
で、前回とおんなじ感じで適当に作ったサンプルを分類してみた。
matplotlib使ったせいか色がきつくてあれだけど、綺麗に分類てきてるっぽいので上手くいってるかも。
ただ、論文読んでもbの決定方法がよく分からなくてサポートベクトル?それぞれについて求まるbの平均を取ってしまったんだけど、詳しい人いたら教えてください。
2次計画問題のソルバを作ってC-SVMを作る
大学の課題で機械学習をやることになったので、2次計画問題の解法のひとつである主双対内点法をパス追跡法で実装して、それを用いて非線形ソフトマージンサポートベクタ―マシンを実装した。
以下のサイトを参考にした。
二次計画用の内点法(Interior Point Method)をC++で書きました。 - ニートがプログラミングするブログ
Quadratic programming problems - a review on algorithms and applications
二次計画法のアルゴリズム - MATLAB & Simulink - MathWorks 日本
後で調べたら大規模密行列ではIPMは高速らしいが、大規模疎行列ではActive Set Method(有効制約法)の方が有利らしい。SVMで使うために疎行列を扱いたかったので完全に失敗したけど、今度ASMで書き直したい。
C++あまりかけないのでコードが汚いけど仕方ない。
ガウスカーネルを使って適当に作ったデータを分類したら以下の画像のようになった。一応成功してるみたい。
本当はSparseLUを用いて逆行列を求めたかったのだけど、謎のエラーが出て結局あきらめてSparseQRを用いた。エラーメッセージでググってもヘッダファイルしか出てこないのがいけなかった。