読者です 読者をやめる 読者になる 読者になる

白猫のメモ帳

JavaとかJavaScriptとかHTMLとか機械学習とか。

JavaでTwitterBotをつくってみたよ

Java その他 自然言語処理

こんばんは。

最近VBAが多くて、あまりJavaを書いていない気がするわたしです。
あ、このブログのプロフィール欄をTwitterとかで使っている名前と写真に合わせました。
なんか混乱してきたので。

さてさて、
結構前にさくらVPSJavaやらMySQLやらJenkinsやらのいろいろをセッティングして、
アプリが動かせる環境を作りましたが、実際に何を作ったかについて全く触れていなかったので、
ちょっと書いておこうかと思います。

今回は仕組みついてなので、実際のコードはでてきません。

こんなことつぶやいてます


どんな発言か気になる。

twitter.com

売り込むべきではない。

twitter.com

たとえのセンスが独特。

twitter.com

危ないゾーンを踏みに行くスタイル。

twitter.com


まぁたまに変なこと言ってます。

仕様の確認


・定期Tweet型のTwitterBot
・対話機能はいまのところなし
コーパスはニュースサイト等の本文
・文章生成はマルコフ型

仕組み


①Jenkinsのスケジュール機能で、10分に一回実行可能jarを叩く
②マスタに登録したRSSフィードの情報をもとにニュースやコラムなどの本文を取得
分かち書きをして、データベースに登録
マルコフ連鎖を利用して、しりとり形式で文を生成
⑤Twitter4jライブラリを利用してTweet

簡単に書くとこんな感じです。
大きく分けると①~③の収集処理と④~⑤の文生成・投稿処理に分かれます。

もうちょっと詳しく見ていきましょう。

マルコフ連鎖


今回のBotではマルコフ連鎖という仕組みを使います。

マルコフ連鎖(マルコフれんさ)とは、確率過程の一種であるマルコフ過程のうち、
とりうる状態が離散的(有限または可算)なもの(離散状態マルコフ過程)をいう。

マルコフ過程(マルコフかてい)とは、マルコフ性をもつ確率過程のことをいう。
すなわち、未来の挙動が現在の値だけで決定され、過去の挙動と無関係であるという性質を持つ確率過程である。

ということなんですが、いまいちよくわかりません。
もうちょっとわかるようにしてみましょう。

空 は 青い
林檎 は 赤い
林檎 が 食べ たい
月 が 綺麗 です ね

4つの短文を用意しました。

たとえば3つ目の文章を2語ずつに分解すると、
「林檎」-「が」
「が」-「食べ」
「食べ」-「たい」
の3つのペアを作ることができます。
これをすべての文章に対して行います。すると、語のペアがたくさん集まりますね。

次に文章を作っていきましょう。

1つ目の語に「月」を選んだとすると、
「月」の次に来る単語の候補は「が」のみですね。

月 が


「が」の次に来る語の候補は「食べ」か「綺麗」です。
ここでは「食べ」を選んでみましょう。

月 が 食べ


「食べ」の次に来る語の候補は「たい」のみです。

月 が 食べ たい


なんだか変な文章が誕生しましたね。
このようにして、語がつながるかだけを条件にランダムに文を生成するのがマルコフ型文章生成です。
意味があるかは別として、文章のつながりがある程度それっぽくなるのがマルコフ連鎖で生成した文の特徴ですね。

また、この例では1つ前の語のみから次の語を決定しましたが、これを「単純マルコフ連鎖」といい、
2つ前の語まで見て次の語を決定するような場合を「2階マルコフ連鎖」といいます。
もちろん3階とか4階もありますが、今回のBotでは2階マルコフ連鎖を採用しています。

収集側の詳細


自然言語処理において、文章生成や学習のもとになるデータを「コーパス」といいます。
今回のコーパスRSSが配信されているニュースやコラムにしました。

ただ、ニュースをすべて引っ張ってきてしまうと物騒なことを言い出してしまうので、
Yahooニュースの「IT・科学」と「エンタメ」、あとは「ITmedia」と「ライフハッカー」にしてみました。
ITとか芸能とかに詳しいBotになるはずです。(ならない)

RSSの解析ライブラリには「ROME」、
取得したURLからDOMを解析するのには「Jsoup」、
形態素分析には「Kuromoji」を使いました。

「Kuromoji」についてはベイズフィルタの記事でちょっと触れているので、よければどうぞ。


2階マルコフ連鎖用に登録する語の組はこんな感じのテーブルに登録します。

f:id:Shiro-Neko:20161019161338p:plain

試しに1語目が「猫」で検索したらこんな風になりました。
一番上からして猫じゃないし…。

f:id:Shiro-Neko:20161019161641p:plain

「head」と「tail」はそれぞれ文頭、文末になりえるかを表しています。
「date_time」は最終更新日、「cnt」はこの3連語の出現回数ですね。
これは後で文章を作るときの優先度に関係してきます。

文章生成・投稿側の詳細


さて、文章の作成です。
まずはhead=1の語の組をとります。

ただし、なるべく話題になっていることをつぶやきたいので、
最終更新日が新しく、出現回数が多いものほどソート順が上になるようにorder byで調整します。
(この辺りはファインチューニングなのでお好みでっていう感じです)

MySQLはoffsetがあるので、(全件数)×(乱数の2乗)でoffsetして、
ソート順が上にあるほど2次関数的にHitしやすくしています。

どんどん連鎖していって、tail=1で終わります。
30コくっつけても終わらなかったらやり直しにしています。
そして、Twitterの制限で140字を超えてもやり直しです。


で、最初はこれで文章生成は終わりにしていたのですが、
括弧が開いたまま閉じないとか、閉じ括弧だけある文章が大量に生成されてしまったので、ちょっと改良しました。

①開き括弧と閉じ括弧を組にしてマスタに登録する
②開き括弧が出てきたらスタックに積む
③スタックに括弧が積んである場合、その閉じ括弧を含む語の組の優先度をMAXにし、
 今ほしい閉じ括弧以外は取得されないようにする
⑤文が終わった時点でスタックに積み残しがある場合、やり直しにする

こんな感じで大体閉じられます。
うまくいかないで何度もやり直してることもあるけど、それはまぁ…。


TweetはTwitter4jのupdateStatusというメソッドを使うだけなんですが、
TwitterAPIを利用するための登録とかは詳しく説明してくれているサイトがたくさんあるので、
ここでは特に触れないことにします。

その他


・O/Rマッパーには「Iciql」を使いました。jarひとつで使えて簡単!

・設定ファイルはYAMLにしたので、読み込みには「SnakeYAML」を使いました。XMLとかJSONより好きかもしれない。

今後


コーパス自体をTwitterから引っ張ってこようかとコードを書いたのですが、
つぶやきの内容が割と個人のそのままだったり、なんか変なこと言って誰かに怒られないかちょっと怖くて保留中。

リプライ機能も試してみたいんだけど、seq2seqとかが動くようなパフォーマンスじゃないので、
なんとか辞書型を使わずに挨拶だけでもできたりしないかなとか思っていたり。

微妙にフォローしてくれている方がいるのですが、
自動リフォロー機能とかあったほうがいいんですかね?

そんなわけで


一応断っておきますが、このBotには機械学習的な仕組みはほぼありません。
語彙が増えたり、つながりやすい言葉を少しは覚えたりすることはあるかもしれませんが。

それでもこんな風に何を考えているのかよくわからないような(実際何も考えていない)
ものを作ることができたりします。

心の広い暇な人はフォローしてみてください。
10分に1回よくわからないことをタイムラインに流してくれます。

たまーになんか意味深なこと言ったりするのがちょっと面白いですヨ。

それでは。