Chainerで学習した対話用のボットをSlackで使用+Twitterから学習データを取得してファインチューニング

#ChainerでSlack用のbot作成！！

注意：chainer 1.5.1以降のバージョンで動作を試みると`Segmentation fault`のバグが出ます

```
pip install chainer=="1.5.1"
```

で1.5.1をインストールしてください。

突然ですが、あなたはこんな衝動にかられたことはないでしょうか？
Slack上で面白い返事やつぶやきをしてくれるbotやいろんな質問に答えてくれるボットがいれば、会話が盛り上がるし、聞きづらい質問にも答えてくれるのに・・・

私も思ったことがあります。そこで実装してみました。
最終的にこんな感じの応答を返してくれるようになります。

![Screen Shot 2016-01-03 at 21.51.41.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/26329015-4bdd-2c52-ee3b-a158caac8e8e.jpeg)


##対象読者

１：中身は分らなくも良いから賢くなるbotを作ってみたい！！
やるべきアクション：下記リポジトリをgit cloneして環境構築して、実行して下さい！！

https://github.com/SnowMasaya/Chainer-Slack-Twitter-Dialogue

２：中身も知って賢くなるbotを作ってみたい！！
やるべきアクション：この記事を読んで仕組みを理解してからgit cloneして環境構築して、実行して下さい！！

https://github.com/SnowMasaya/Chainer-Slack-Twitter-Dialogue

３：ガチの人
やるべきアクション：アドバイス下さい。お願い致します。

#全体構成

![スライド1.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/de9b43fb-de89-4435-bf5e-02d4d1dce19f.jpeg)


#Chainerによる対話モデルの学習

<img src="https://qiita-image-store.s3.amazonaws.com/0/10496/599bd4d3-42aa-5e19-1328-b3606103e5f6.png" alt="chainer_icon_red.png" width="200" height="200">


まずはこの仕組みの根幹となる部分のChainerによる対話モデルから説明していきます。
参考にしているのは下記の論文です。

http://arxiv.org/pdf/1507.04808.pdf


ではここで対話モデルを学習するまでの構成図を下記に示します。


![スライド2.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/9fd8850c-970c-0e94-abdb-80525856193b.jpeg)


１：Word2Vecで良い初期値を得るための下準備

使用データは下記のWikipediaのデータからタイトルデータ5000行をランダムに抽出

http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html

コードは下記を参考に実装

https://github.com/pfnet/chainer/tree/master/examples/word2vec

語彙数：5000
隠れ層のユニット数：300

上記の数字は対話モデルの語彙から潜在層に写像する層のユニット数に合わして下さい。
＊注意事項
対話学習の際に使用するコーパス内での未知語がない方が良いので抽出されたデータと対話破綻コーパスデータの両者をマッチング。対話破綻で検出された未知語を追加という形にしておく方が良いです。
具体的な流れは下記になります。

１：抽出したデータでWord2Vecを学習
２：学習したデータと対話破綻コーパスデータでマッチングしないデータがないか確認
３：マッチングしないデータが検出されたら、Word2Vecの学習データに追加
４：収束するまで学習


２：得られた初期値を使用して、End to Endのニューラル対話モデルを構成

使用データは下記の対話破綻コーパスを使用（データがないため）
[mecab](http://mecab.googlecode.com/svn/trunk/mecab/doc/index.html?sess=3f6a4f9896295ef2480fa2482de521f6 "mecab") で分かち書きをしてから使用してください。

https://sites.google.com/site/dialoguebreakdowndetection/

コードは下記のchainer1.5バージョンの`mt_s2s_encdec.py`を参考に実装

https://github.com/odashi/chainer_examples

３：Twitterから取得したデータを用いて、再学習

この手法の良い点は良い初期値を得るための工夫をいくつも加えることで、Twitterで取得したデータを用いても適切に学習が進むことです。
また非常に強力な点はテキスト情報だけでなく、画像情報、音声情報も入力として入れることが可能という点です。

論文では、提案されているが実装出来ていない機能（本記事では実装できていないので今後、実装したいと思います。）

１：Bi-Directional-RNNを用いる事で最初から最後、最後から最初の両方向からの対話の流れを考慮
２：対話のデータセットもユーザーアクション→応答→ユーザーアクションのトリプルセットで学習することで単純な質問応答ではなく前回の質問も考慮した返答を行なう
３：トリプルセットを事前学習に使用
４：トリプルセットのユーザーアクション、応答を利用して最終的なユーザーアクションをMAP推定により推定

モデルの構成自体は翻訳と同様なので、下記の記事が翻訳モデルを実際に実装された方の記事なので参考にされると良いと思います。

http://qiita.com/odashi_t/items/a1be7c4964fbea6a116e

細かい説明は下記のipython notebookで確認できます。

https://github.com/SnowMasaya/Chainer_Machine_Translation_ipython_notebook.git

変更した点はWord2Vecによる事前学習の部分です。

下記の記事を参考にしました。

http://qiita.com/tabe2314/items/6c0c1b769e12ab1e2614

```py
            if dec_flag:
                if dst["weight_jy"] and child.name == "weight_xi" and self.word2vecFlag:
                    for a, b in zip(child.namedparams(), dst["weight_jy"].namedparams()):
                        b[1].data = a[1].data
                    print('Copy weight_jy')
```

変更した部分が事前学習にユーザー入力部だけでなくシステム応答部分の初期値もWord2vecで最適化した値を使用したかったため、上記のようなコードで指定していますが、明らかなハードコーディングなので、後で修正しておきます。

#Slackによるbotの応答


<img src="https://qiita-image-store.s3.amazonaws.com/0/10496/fab3a9cd-8bf1-7cc3-4c74-6fe4c6277ce1.png" alt="slack.png" width="400" height="200">

下記のリポジトリを使用しました。

https://github.com/slackhq/python-slackclient

![スライド3.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/a65a021f-3036-ab6a-b3b7-f2ee8f7dc573.jpeg)

##Slackの連携に必要な情報の取得

下記のサイトが非常に参考になりました。キーの取得だけでなく、今回はユーザーリストとチャネルの指定方法なども参考になった点が良かったです。

https://medium.com/@julianmartinez/how-to-write-a-slack-bot-with-python-code-examples-4ed354407b98#.ngtlokoy8

Slackで情報を取得する際のapiサイト

https://api.slack.com/

Slackのapiトークンを取得するサイト

https://api.slack.com/web

Slackでユーザー情報を取得するときに使用

https://api.slack.com/methods/users.list

Slackでチャネル情報を取得するときに使用

https://api.slack.com/methods/channels.list

使用例：ユーザー情報を取得したい場合は下記を入力すると取得可能

https://slack.com/api/users.list?token=apiトークンを指定

##機能概要

１：Web SocketによりSlack上の反応を監視
２：特定のキーワードを観測したら、反応
３：反応は今回は応答と学習に分けている

応答の場合のキーワード： chainer:呼びかけの言葉
学習の場合のキーワード： chainer_train

学習はファインチューニングを採用して、事前に学習したモデルに対してさらに学習を進めることでより高度なモデルに変更しています。

```py
    def __input_sentence(self):
        #1
        text = self.__mecab_method(self.data[0]["text"].replace("chainer:", ""))
        #2
        data = [text]
        src_batch = [x + ["
"] * (self.generation_limit - len(x) + 1) for x in data]
        return src_batch

    def __predict_sentence(self, src_batch):
        #1
        dialogue = EncoderDecoderModelForwardSlack(self.parameter)
        #2
        src_vocab = Vocabulary.load(self.model_name + '.srcvocab')
        trg_vocab = Vocabulary.load(self.model_name + '.trgvocab')
        #3
        model = EncoderDecoder.load_spec(self.model_name + '.spec')
        serializers.load_hdf5(dialogue.model + '.weights', model)
        #4
        hyp_batch = dialogue.forward(src_batch, None, src_vocab, trg_vocab, model, False, self.generation_limit)
        return hyp_batch
```

上記は応答時のコードの一部を抜粋しています。上記の`__input_sentence`で入力されたユーザーの文章をchainerで処理できる形に変更して`__predict_sentence`で対応する文章を予測して返しています。

インプットされた文章に関しての処理は

１：Mecabで分割処理
２：確率的勾配法で処理しているので単一の文章でも同様の処理が出来るデータ形式に変更

予測に関しての処理は

１：予測用のモデルを作成
２：入力単語と応答単語の語彙を設定
３：モデルの詠み込み
４：応答仮説の予測です。

```py

                #1
                self.__setting_parameter()
                #2
                model = EncoderDecoder.load_spec(self.model_name + '.spec')
                dialogue = EncoderDecoderModelForwardSlack(self.parameter)
                serializers.load_hdf5(dialogue.model + '.weights', model)
                #3
                dialogue.encdec = model
                dialogue.word2vecFlag = False
                dialogue.train()
```

上記はファインチューニング時のコードの一部を抜粋しています。

１：ファインチューニング時に使用する学習データなどの指定を行なう
２：予め予備学習したモデルの詠み込み
３：モデルを設定して、学習したモデルのパラメータを使用する設定をオンにする。

#Twitter

<img src="https://qiita-image-store.s3.amazonaws.com/0/10496/ea6c965a-7e8c-582c-accf-ec4c622a0225.jpeg" alt="twitter.jpg" width="200" height="200">

Twitter関連の処理はTwitterからデータを取得して、SQLiteに保存。SQLiteから学習用のデータを取得するようにしています。

![スライド4.jpg](https://qiita-image-store.s3.amazonaws.com/0/10496/4518d2f9-686b-88f6-a93c-c263aa6bdd54.jpeg)


１：Twitterから会話データを取得
２：SQLiteに保存
３：SQLiteから、ユーザーアクションとシステム応答に振り分けておく

直接貯めずにSQLiteに保存することでデータを必要な時に必要な分だけ学習できる仕組みになっており、ファインチューニングに重要な速度性を担保することを考慮しています。

Twitterで会話データの取得方法

１：ユーザー指定でつぶやきを取得。その際に`in_reply_to_status_id`と`in_reply_to_screen_name`を取得
２：取得した`in_reply_to_screen_name`でつぶやきを取得し`in_reply_to_status_id`が一致するつぶやきを取得
３：つぶやきと応答の辞書を作成してSQLiteに保存

#総括

今回は冬の長期休暇を利用して、Chainer + Slack + Twitter連携を行なってみました。
Chainerは1.5から少ないコードで強力な機能も簡単に使えるようになってきており、他のニューラルネットのツールも出て来ていますが、個人的には好きなので今後も使っていこうと思っています。

#参考

https://forger.typo3.org/slack/

https://play.google.com/store/apps/details?id=com.twitter.android&hl=ja

http://chainer.org/