ディープラーニング(seq2seq)でtwitter chatbotを作ってみた

こんにちは。今年は冬休みをとても長くとったのですが、肉や蟹や餅や酒を連日消費しているうちに人体が終わっていき、気持ちになったので([様々な方向に感極まった状態のことを「気持ちになる」と表します](http://negineesan.hatenablog.com/entry/2014/02/07/120114))、世間で流行っているらしいディープラーニングの関連情報をつまみ食いしてチャットボットを作ってみることにしました。

入力文に対しニューラルネット(RNN)で応答文を生成して返事します。
```@neural_chatbot``` というtwitterアカウントで動かしています。

<img width="435" alt="スクリーンショット 2017-01-05 12.25.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/75058/bb40d6da-07f0-76fc-b00c-00d29f9e5089.png">

ご興味があればぜひ```@neural_chatbot```に話しかけてみてください。

## あらすじ
- ニューラルネットというものがあり、関数を近似することができ、知られています。
- Recurrent Neural Network (RNN)というものがあり、内部状態を持つことができ、自然言語を含む可変長の系列を取り扱うのに便利で、知られています。
- [Sequence to Sequence Learning](https://arxiv.org/abs/1409.3215)というものがあり、RNNを数層積んだものに文を1単語ずつ順番に食わせた後に1回しばくと料理された単語列を吐かせることができます。英語を入れてフランス語を出すこともできますが、[会話の前の文から次の文を作って返事をする](http://cs224d.stanford.edu/papers/ancm.pdf)こともでき、そのようにします。
- TensorFlowとAmazon EC2のGPUインスタンスでこれらを調理することにし、やっていきます。プラットフォームの選択に特段の理由はない。

DISCLAIMER: ニューラルネットを扱った経験はほとんどないので随所で変なことをやっている可能性が高いです。

## 学習コード

- TensorFlowのseq2seqサンプルをもとにattentionメカニズム([これ](https://arxiv.org/pdf/1412.7449v3.pdf)のsection2.1参照)とbeam decodingをいい感じにした[コード](https://github.com/pbhatia243/Neural_Conversation_Models)がgithubに落ちていたので感謝してそのまま使います。
- ほぼ変更不要ですが、Optimizerがlearning rate固定のSGDだったのでAdamとかに変えるのと、日本語でやりたいのでMeCab-ipadicをかませるくらいだけいじることにし、そのようにします。
  - マニュアルで決めたスケジュールでlearning rateを減らしながら最急降下するんだろうとしか思っていませんでしたが、実はいろんなOptimizerがあってlearning rateをある程度自動調整してくれるということは[ここ](http://postd.cc/optimizing-gradient-descent/)を見て知りました。
  - ほんとうにAdamが妥当な選択かどうかは不明。
  - IPADicはミニマルで古い辞書のため、ソーシャルメディアで使われるような現代口語日本語にはあまり適さないと思われるが、モデルサイズの都合で語彙数を極力増やしたくないのでとりあえずそれでいいことにします。サブワードとか文字ベースとかよい方法はあるのだと思いますが踏み込みません。

## ハードウェア環境

- EC2の[p2インスタンス](https://aws.amazon.com/jp/ec2/instance-types/p2/)が速いと聞いたので、おこづかいで使うには大変高いですがCPUでやっていると冬休みが終わってしまうのでp2.xlargeインスタンスを使います。(2017.01.04現在)時間90セントです。教科書1冊買ったくらいの生きた情報が得られると期待して使うことにし、学習してないときはこまめに停止します。
  - ubuntu派ですのでそのようにします。16.04
- CUDAとかcuDNNとかTensorFlowとか、[ネット情報](http://expressionflow.com/2016/10/09/installing-tensorflow-on-an-aws-ec2-p2-gpu-instance/)をもとにインストールしますが、TFの最新版(現在r0.12)ではrnn関係のライブラリの場所とか仕様がいろいろ変わっていて拾ったコードが現時点で動かないことに気づき、TFのバージョンをr0.9まで落としました。CUDAはv7.5、cuDNNはv4にする必要があります。
- TF r0.12は一度ソースからコンパイルしましたが、コンパイルに時間がかかってお金がもったいない。r0.9はapt/pipでインストールしてみて問題なく動くという気づきを得ました。
  - cuDNNは[NVIDIAのサイト](https://developer.nvidia.com/cudnn)でregisterしてからv4を落とします。

手順はざっくりこんな感じ。

```
$ sudo apt-get install cuda-7.5
$ sudo dpkg -i cudnn-7.0-linux-x64-v4.0-prod.tgz # あらかじめ落としておく
$ sudo apt-get install python-pip
$ pip install --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow-0.9.0-cp27-none-linux_x86_64.whl 
```

## 学習データ
ソーシャルメディアから得た日本語のquestion-reply対のデータが手元に50万行ほどあり、これを使います。
単純にtwitterのreply対など使えば楽なように思われますが、知り合い同士の対話には文中には現れていないコンテクストが多く、問いのテキスト情報だけから答えを推定するタスクの学習データとしては適さないので、コンテクストの影響を減らすために入手方法に工夫をしています。
青空文庫から会話文を拾ったりも考えましたが同様の理由でいまいちです。

データ例:

```
あなたは内向的?それとも外向的? -> いろいろやるので外向的かもなー 他の人とお話するのだいすき
好きなアイスのフレーバーは？ -> チョコレートクッキー、オレオ系統！
質問するほうが好き? されるほうが好き? -> される方がすきかも 募集しております
さざえさんのキャラクターになるとしたら、どのキャラクターになりたい? -> サブちゃん
```

これを学習データ90%、バリデーションデータ5%、テストデータ5%に分割して使います。

## 学習結果

おこづかいとGPUにものを言わせて回していきます。

- attentionはon/offできますがアリでいきます。
  - 過去の内部状態が思い出せるので長い文を入れたときに有利だと思われます。
- 語彙数は200,000語。
  - 最初語彙40,000語でやったら出力が```_UNK```だらけになってしまい、終わってしまいました。つまり40,000語では学習データの応答文に現れる語彙が十分にカバーできていないらしい。
  - 200,000語に増やすとこの点は改善したがモデルサイズがディスクとメモリ消費に優しくありません。ベターな解決法は今後の課題です。
- 3層GRU、単語embeddingのサイズは512、バッチサイズは64。

ステップ数に対する学習データとバリデーションデータのperplexityの推移はこんな感じ。

**Adam learning_rate=0.001**

<img width="353" alt="スクリーンショット 2017-01-05 9.19.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/75058/bed68d56-7bd6-8def-94bc-966d9506e0df.png">


フム

**SGD learning_rate=0.5**

<img width="349" alt="スクリーンショット 2017-01-05 9.18.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/75058/1e17d6a7-51d8-6fbe-bb02-92b910385cac.png">

Adamと縦軸をあわせるとこんな感じ。

<img width="351" alt="スクリーンショット 2017-01-05 9.16.56.png" src="https://qiita-image-store.s3.amazonaws.com/0/75058/58715453-f107-55c8-9698-4059d47ed956.png">

SGDのほうが収束が非常におそいことがわかります。learning rateの減衰もさせていないので損失が高止まりするのは当然ですが、最初から遅いです。

Adamのほうで24時間くらい回した。もっと回すとよくなるかもしれませんが個人の金でこれ以上やりたくない。

## 推定例

出来上がったモデルを使って、とりあえずテストデータに対する1best出力を観察します。
beam decodeしていますが、beam sizeが意外と回答の質に効く印象があります。デコード時間とのトレードオフになるが、とりあえず```beam_size=100```。
出力文のよさの定量評価はやっていません。

まあまあな例:

```
今週どんなことをしましたか? -> 仕事
ロックマンシリーズで好きなBGMを教えてください。-> 特にないですね………………
プレイするのが好きなスポーツは? -> バスケ
写真を撮る時には笑顔になりますか? -> なりません・・・・・・・・・・・・・・・・・・
あなたの目はどんな色ですか? -> 黒
乙女チックな妄想はしますか？ -> します。。
悲しい気分のとき、あなたを元気にしてくれるものはなんですか？ -> サーティーワンアイスクリーム
ベッド以外で寝るのはどこ? -> 床
歩くのと自転車、どっちが好き？ -> 圧倒的徒歩
今流行っているものはなんですか？ -> インフルエンザ
```

いまいち会話に広がりを感じないがとりま噛み合ってはいる様子。
好きなスポーツの質問にバスケ、色の質問に対して黒、といったように質問の5W1Hに合った答えができるのは興味深いです。
バスケはスポーツの1種であるといった外部知識は与えていないので、こういったちょっとした常識のようなものは50万対の学習文例のみから獲得されていることになります。

だめっぽい例:

```
あなたは思い付きで行動する? それとも計画的に行動する? その理由は? -> 計画的に計画的に計画的に計画的に計画的に計画的に計画的に計画的に
話す方が好き?それとも聴く方が好き? -> どちらも好きですかなのかなの方が好きです
人を愛してはいけないのはなぜ？ -> 知らんじゃないからじゃないんじゃないですかなん？？
恋はどれくらい続くでしょうか？ -> 3ヶ月くらいのかなんですね
いまのはなーし。\n精密トリガーじゃなくて、ベレスクで精密を上げるカスタムはどうですか？ -> ロング人体人体人体人体人体人体人体人体人体人体人体rcです
```

文生成の破綻。繰り返しが生じるのはニューラルの出力破綻の特徴らしいです。

## チャットボット

せっかく作ったのでTwitterのchatbotにしてみます。
[アプリ登録](https://apps.twitter.com/)みたいなことが必要ですが、SMSが受けられる電話番号が必要な他はそんなに面倒でもないです。

chatbotはご予算の都合でCPUホストで動かします。
CPU版であればTFのインストールも非常に楽です。

```
$ pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.9.0-cp27-none-linux_x86_64.whl
```

あとはaptでmecab, mecab-ipadic-utf8, python-mecabをインストールすれば作ったモデルのdecodeは動きます。

```
$ sudo apt-get install mecab mecab-ipadic-utf8 python-mecab
```

twitter APIとのやりとりをよい感じに解決する必要がありますが、[tweepy](http://www.tweepy.org/)というtwitter APIクライアントライブラリがpipでさっと入れられて便利そうなのでそのようにします。

```
$ pip install tweepy
```

殴り書きでbotの動作部分を作ります。動きはこんな感じ:

- mentionが来たら返事する
  - 返事は上記のNeural Conversational Modelで作り、N-best outputからランダムに選択して応答。
- mentionしてくれた人はフォローする
- フォローされたらフォローバックする
- フォローしてくれてる人のつぶやきにはたまに勝手にreplyする

## ソースコード

https://github.com/ktsujino/Neural_Conversation_Models