こんばんは。
最近VBAが多くて、あまりJavaを書いていない気がするわたしです。
あ、このブログのプロフィール欄をTwitterとかで使っている名前と写真に合わせました。
なんか混乱してきたので。
さてさて、
結構前にさくらVPSにJavaやらMySQLやらJenkinsやらのいろいろをセッティングして、
アプリが動かせる環境を作りましたが、実際に何を作ったかについて全く触れていなかったので、
ちょっと書いておこうかと思います。
今回は仕組みついてなので、実際のコードはでてきません。
こんなことつぶやいてます
どんな発言か気になる。
twitter.com要注目だが男気溢れる発言。
— ねこぺんぎん (@nekopenguin1) 2016年10月19日
売り込むべきではない。
twitter.comソフトウェア、ハードウェアへの甘えを全力で売り込んだ。
— ねこぺんぎん (@nekopenguin1) 2016年10月19日
たとえのセンスが独特。
twitter.com実際はおじいちゃんと孫くらいの軽い胃腸炎みたい。
— ねこぺんぎん (@nekopenguin1) 2016年7月14日
危ないゾーンを踏みに行くスタイル。
twitter.com痛恨の極み乙女。
— ねこぺんぎん (@nekopenguin1) 2016年7月2日
まぁたまに変なこと言ってます。
仕組み
①Jenkinsのスケジュール機能で、10分に一回実行可能jarを叩く
②マスタに登録したRSSフィードの情報をもとにニュースやコラムなどの本文を取得
③分かち書きをして、データベースに登録
④マルコフ連鎖を利用して、しりとり形式で文を生成
⑤Twitter4jライブラリを利用してTweet
簡単に書くとこんな感じです。
大きく分けると①~③の収集処理と④~⑤の文生成・投稿処理に分かれます。
もうちょっと詳しく見ていきましょう。
マルコフ連鎖
マルコフ連鎖(マルコフれんさ)とは、確率過程の一種であるマルコフ過程のうち、
とりうる状態が離散的(有限または可算)なもの(離散状態マルコフ過程)をいう。マルコフ過程(マルコフかてい)とは、マルコフ性をもつ確率過程のことをいう。
すなわち、未来の挙動が現在の値だけで決定され、過去の挙動と無関係であるという性質を持つ確率過程である。
ということなんですが、いまいちよくわかりません。
もうちょっとわかるようにしてみましょう。
空 は 青い 林檎 は 赤い 林檎 が 食べ たい 月 が 綺麗 です ね
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階マルコフ連鎖用に登録する語の組はこんな感じのテーブルに登録します。
試しに1語目が「猫」で検索したらこんな風になりました。
一番上からして猫じゃないし…。
「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より好きかもしれない。