Chainerで学習した対話用のボットをSlackで使用+Twitterから学習データを取得してファインチューニング – Qiita を試そうとしたんですが、所詮フロントエンドエンジニア止まりでにわかプログラマーの私にとっては大変苦労したので、試行錯誤したメモを残します。
結果的にはなんとかSlackでbotが動くまでできました。
これから機械学習を行いたい方、上記のサンプルを動かそうとされて断念さた方のの一助になれば幸いです。
一応、筆者のプログラムレベルを書いときます。
- python歴 2週間(基本、コードはコピペで済みます)
- phpやJavaScriptをたまにかく
- データベースは管理画面で見る程度
- ターミナルはたまに使う程度(cd,ls,pwd,chmod,Ctrl+cのコマンド程度)
基本方針
- Chainerで学習した対話用のボットをSlackで使用+Twitterから学習データを取得してファインチューニング – Qiita
- GitHub – SnowMasaya/Chainer-Slack-Twitter-Dialogue: Chainer-Slack-Twitter-Dialogue
上記ページを見ながら環境を構築し、動かしていきます。
環境構築
Chainer-Slack-Twitter-Dialogue をgithubからダウンロードして展開。
以下、展開したフォルダ内{myproject}での作業となります。
ライブラリのインストール
$ brew install virtualenv
brewが使えなかったので pipを使いました。
権限ないとエラーで怒られたので sudo -H で実行
$ sudo -H pip install virtualenv
たぶん、スペルミスっぽい
$ pip install -r requirement.txt ↓ $ pip install -r requirements.txt
これで大丈夫でした
Prepare the Data の準備
PreTrain
>[Wikipedia for Japanese]https://dumps.wikimedia.org/jawiki/latest/
Train
>[Dialogue Data]https://sites.google.com/site/dialoguebreakdowndetection/
上記は何かのコマンドと思って実行したら、、、全然違いました(汗)
それぞれのURLからデータを取って来いとのことでした。
Qiita記事中で丁寧なコメントのやり取りされてて助かりました(感謝)
https://dumps.wikimedia.org/jawiki/latest/
ここからはWikipediaのデータからタイトルデータで、僕はこの時点で
jawiki-latest-all-titles-in-ns0.gz (10MB)をダウンロード
解答したデータ jawiki-latest-all-titles-in-ns0 は {Chainer-Slack-Twitter-Dialogue}/word2vec/ 内に保存
https://sites.google.com/site/dialoguebreakdowndetection/
対話破綻検出チャレンジさんからは左メニューの 雑談対話コーパス の「4.ダウンロード」から projectnextnlp-chat-dialogue-corpus.zip をダウンロード。
ちなみにNTTドコモの雑談対話APIに使われてるんだとか。
動作確認用に wikipediaのデータを前処理
とにかくテスト用で動かすためにと、ご丁寧にランダムで5000件取得するスクリプトを用意してくれてました。
sh random_choice.sh {wikipediaタイトルデータ名} > {wikipediaタイトルデータ名からランダムに5000件取得されたデータ}
私は以下のコマンドを実行しました。
$ cd {myproject}/word2vec $ sh random_choice.sh jawiki-latest-all-titles-in-ns0 > jawiki-latest-all-titles-in-ns0-rnd5000
word2vec/jawiki-latest-all-titles-in-ns0-rnd5000 がテスト用のデータとして生成されます
Word2Vecのモデル作成
前のテスト用データを指定して実行
$ python word2vec_execute.py --data jawiki-latest-all-titles-in-ns0-rnd5000
こんなエラーが。
FileNotFoundError: [Errno 2] No such file or directory: 'test.txt'
–dataで指定したのに通ってない??
word2vec内のファイルword2vec_c.pyx、word2vec_load.py内で ‘test.txt’ がハードコーディングされていた。
以下のように対応。
jawiki-latest-all-titles-in-ns0-rnd5000 → test.txt にリネーム
パーミッションに書込権限を与えておく
RuntimeError: h5py is not installed on your environment. Please install h5py to activate hdf5 serializers.
hdf5ってライブラリがないから怒られたのでインストール
$ sudo -H pip install h5py
もう一度、実行
$ python word2vec_execute.py --data jawiki-latest-all-titles-in-ns0-rnd5000
フォルダword2vec内に以下のファイルが生成された
word2vec_chainer.model
word2vec.model
どのファイルも10MB以上になっていた。
word2vec.model をテキストエディタで開くと数字の羅列。
1行目が「5000 300」となっていればたぶん成功。
(word2vec_chainer.model は開けなかった)
対話破綻コーパスからjson生成
ここも公式サイトの通りなんですが、日曜プログラマなの私にとってはプログラマ界隈の常識や当たり前のルールがわからず、かなり苦戦しました。
似たような人の手助けになれば幸いです。
1:data というフォルダを作成していただきます。
これはそのままで、作業フォルダ直下にフォルダ[data]を新規作成
{myproject}/data
2:対話破綻コーパスからjson形式のデータを取得して、下記のような形式で各対話のデータを保存したlistファイルを作成します。
この意味が全然わからずに苦労しました。
「listファイル」は拡張子なしのテキストファイルです。
僕がやった方法を順を追って書いていきます。
2-1:jsonを格納するdevフォルダを新規作成
{myproject}/dev/
2-1: 対話破綻コーパスをダウンロードし展開したらjsonファイルを探す
projectnextnlp-chat-dialogue-corpus/json/
├init100
├1407287164.log.json
└1407219916.log.json
…
└rest1046
├1404365812.log.json
└1407143708.log.json
…
2-3:jsonファイル群を {myproject}/dev/ に複製or異動
ディレクトリは不要で、jsonファイル群を全部devフォルダ直下に入れます。
2-4: jsonのパスを全て書いたlist(テキストファイル)を生成
{myproject}/list
現時点ではこのようなフォルダ構成になるはず
{myproject}
├ dev/ ←jsonファイル群
├ data/ ←空
├ list ←テキストファイル
├ word2vec/
3:下記のスクリプトでplayer_1とplayer_2に分けられます。
https://github.com/SnowMasaya/Chainer-Slack-Twitter-Dialogue/blob/master/data_load.py
3-1:上記pythonを実行
$ cd {myproject} $ python data_load.py
▼実行後
{myproject}
├ player_1 ← 生成
├ player_2 ← 生成
jsonから単語が抽出されたテキストファイルが生成されます。
このテキストに、次の”分ち書き”させる前に、好きな言葉を入れておくことで個性を出せそうです。
4:分けたデータは分ち書きが出来ていないのでmecab-ipadic-neologdを使用したmecabで分ち書きをして
各データの名前を下記のようにしてdataフォルダの直下に保存します。
player_1_wakati
player_2_wakati
“分ち書き”というのが最初わかりませんでした。
一言で言うと、文章から単語の区切りや分析を辞書から行うもので、環境の準備が必要でした。
4-1 : 分ち書きのための環境準備
参考サイト)スタバのTwitterデータをpythonで大量に取得し、データ分析を試みる その3 – Qiita
- mecab-ipadic-NEologd インストール
- mecab-python インストール
- MeCab downloads → mecab-python-0.996.tar.gz をダウンロード
インストールの動作テスト : python
以下のコードをsample.pyとして保存
#!/usr/bin/env python #-*- coding:utf-8 -*- import MeCab as mc t = mc.Tagger('-Ochasen -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd/') sentence = u'今日は良い天気ですが、雨ですね。キャラメルマキアートがうまいです。' text = sentence.encode('utf-8') node = t.parseToNode(text) # 先頭行はヘッダのためスキップ while(node): if node.surface != "": print node.surface +"\t"+ node.feature node = node.next if node is None: break
sample.py を実行
$ python sample.py 今日 名詞,副詞可能,*,*,*,*,今日,キョウ,キョー は 助詞,係助詞,*,*,*,*,は,ハ,ワ 良い 形容詞,自立,*,*,形容詞・アウオ段,基本形,良い,ヨイ,ヨイ 天気 名詞,一般,*,*,*,*,天気,テンキ,テンキ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス が 助詞,接続助詞,*,*,*,*,が,ガ,ガ 、 記号,読点,*,*,*,*,、,、,、 雨 名詞,一般,*,*,*,*,雨,アメ,アメ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス ね 助詞,終助詞,*,*,*,*,ね,ネ,ネ 。 記号,句点,*,*,*,*,。,。,。 キャラメルマキアート 名詞,固有名詞,一般,*,*,*,キャラメル・マキアート,キャラメルマキアート,キャラメルマキアート が 助詞,格助詞,一般,*,*,*,が,ガ,ガ うまい 形容詞,自立,*,*,形容詞・アウオ段,基本形,うまい,ウマイ,ウマイ です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス 。 記号,句点,*,*,*,*,。,。,。
成功!
4-2: player_1、player_2を分ち書き
{myproject}
├ player_1
├ player_2
実行前にmecabのパスを確認
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"; /usr/local/lib/mecab/dic/mecab-ipadic-neologd
2ファイルを分ち書き実行
$ mecab -Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd player_1 > data/player_1_wakati $ mecab -Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd player_2 > data/player_2_wakati
data/player_1_wakati をテキストエディタで開くと 単語間に半角スペースの区切りが入ったのがわかると思います。
英語だと必ず単語ではスペースが空きますが日本語では空かないからこの処理が必要なんですね。
しかし、web系のへたらプログラマとして、生成するファイルには拡張子を付けないのが気持ち悪いのは僕だけでしょうか…
Twitter,Slackの設定
Prepare enviroment.yml : twitterとSlackで設定用のファイルを新規作成する
{myproject}/twitter/enviroment.yml
{myproject}/slack/enviroment.yml
を作成して、それぞれにTwitter,Slackの必要なAPI情報とかmecabの辞書のパスを指定する
内容は参考サイトのそのままです、APIの取得リンクも参考になりました。
わからなかったのは、「mecab: your mecab dictionary」
結果として、mecabをインストールした時に指定した –dicdir のことだった。 mecab: /usr/local/lib/mecab/dic/mecab-ipadic-neologd
slack の botは事前に登録が必要
Slack で slackbot じゃない bot を作る | Covelline Developer Blog を参考にbotを作成。
作成したbotはテスト用のチャンネルに招待しておきます。
参考までに{myproject}/slack/enviroment.yml の中身
slack: api_token: xoxb-********-********************** user: bot-sample1 #作成したbotの名前 channel: bot-test #slackでテストするチャンネル名 mecab: /usr/local/lib/mecab/dic/mecab-ipadic-neologd #mecabの辞書フォルダ
※ “channel” の先頭に “#” は入りません!コメント扱いされますw
b'{"ok":false,"error":"channel_not_found"}'
slack/app.python でslackからデータ学習コメント受けた時にこんなエラーが出た人は見直してください。
Slackと連携
$ cd slack $ python app.py
実行した後に、slack側でチャットすると、このターミナルにテキストが表示される(感動
応答の場合のキーワード: chainer:呼びかけの言葉
学習の場合のキーワード: chainer_train
slackから「chainer:テスト」と打つと以下のエラー
Traceback (most recent call last): File "app.py", line 95, in slack.call_method() File "app.py", line 40, in call_method self.__judge_print() File "app.py", line 52, in __judge_print hyp_batch = self.__predict_sentence(src_batch) File "app.py", line 73, in __predict_sentence src_vocab = Vocabulary.load(self.model_name + '.srcvocab') File "../util/vocabulary.py", line 50, in load with open(filename) as fp: FileNotFoundError: [Errno 2] No such file or directory: '../ChainerDialogue.srcvocab'
ChainerDialogue.srcvocab ってファイルがないとあるけど…どこで作るんだ??(後述で紹介)
ほんと全然わかってないのに突っ走るのって怖い。
EncoderDecoderModelForward.py の作成
ソースとQiitaの元記事とGitHubのIntrodcutionのどこを見ても、手順がどこにも見当たらない。
GitHubのIntrodcution の
2.対話のクラス
3.各値を設定
…これは自分で準備しろってこと??(ハンズオン??)
というわけで
{myproject}/EncoderDecoderModelForward.py を新規作成
中身は、GitHubのIntrodcutionのサンプル?なソースコードを順番にコピペ
「1.各種ライブラリ導入」のサンプルコード
#表示用に使用しています。
from util.functions import trace
〜
「2.対話のクラス」のサンプルコード
class EncoderDecoderModelForward(EncoderDecoderModel):
〜
EncoderDecoderModelForward.py の実行
$ python EncoderDecoderModelForward.py # unit: 300 Window : 5 Minibatch-size: 100 # epoch: 10 Traing model: skipgram Output type: original Traceback (most recent call last): File "EncoderDecoderModelForward.py", line 17, in serializers.load_hdf5("word2vec/word2vec_chainer.model", w2v_model) 〜 TypeError: Incompatible object (Dataset) already exists
とエラーが。
“word2vec/word2vec_chainer.model” が開けなかった??
$ python word2vec_execute.py --data jawiki-latest-all-titles-in-ns0-rnd5000
を実行して word2vec_chainer.model を再生成。
この時、word2vec.model に書き込み権限がないと何故かエラーになってので書き込み権限(666)を付与したらOKだった。
$ python EncoderDecoderModelForward.py TypeError: Can't broadcast (0, 300) -> (5000, 300)
このエラーが出たら、word2vec_chainer.model が生成失敗してる可能性大。
ちゃんとしていたら5000件のテストデータからでも10MB程度のデータになってる。
再生成して word2vec.model をテキストエディタで開くと1行目が「5000 300」となってるはず。
$ python EncoderDecoderModelForward.py # unit: 300 Window : 5 Minibatch-size: 100 # epoch: 10 Traing model: skipgram Output type: original
特にエラーが出なかったので成功??
(というかこのファイルだけでは何も起きない)
「4.実行」のために train.py を新規作成
これでいいのかわかりませんが、僕はこれで動きました。
- train.py を空で新規作成
- 「1.各種ライブラリ導入」「3.各値を設定」「4.実行」のサンプルコードを順番にコピペ
- 新規に作成した対話クラスを読み込ませるため、9行目辺りに追記
… from EncoderDecoderModel import EncoderDecoderModel from EncoderDecoderModelForward import EncoderDecoderModelForward #←追記 import subprocess …
train.pyを実行すると処理が始まります(MacBook Airのファンが回り出します)
$ python train.py 2016-05-03 19:44:59.655303 ... initializing ... 2016-05-03 19:44:59.656028 ... making vocabularies ... 2016-05-03 19:44:59.738731 ... making model ... 2016-05-03 19:45:00.145924 ... epoch 1/20: start copy Copy weight_xi start copy Copy weight_jy 2016-05-03 19:45:00.887415 ... epoch 1/ 20, sample 43 2016-05-03 19:45:00.888114 ... src = なるほど * * * * * * * * * * * * * * * 2016-05-03 19:45:00.888706 ... trg = おう * 2016-05-03 19:45:00.889577 ... hyp = デラウェア 歳 2016-05-03 19:47:59.694884 ... epoch 2/20: 2016-05-03 19:48:00.389251 ... epoch 2/ 20, sample 50 2016-05-03 19:48:00.389859 ... src = えっ * * * * * * * * * * * * * * * 2016-05-03 19:48:00.390355 ... trg = えええっ * 2016-05-03 19:48:00.390839 ... hyp = スイカ は
1時間ぐらい待つと作業フォルダ直下に以下のファイルが生成されてました。
ChainerDialogue.weights
ChainerDialogue.spec
ChainerDialogue.srcvocab
ChainerDialogue.trgvocab
やった!Slackのエラー時に出てた ChainerDialogue.srcvocab ができてる!
slack実行前にテスト。
動作テスト:bot同士の会話
train.py を複製して train-test.py
最後の方をテストコードに書き換えて保存
--------- trace('initializing ...') encoderDecoderModel = EncoderDecoderModelForward(parameter_dict) encoderDecoderModel.train() --------- ↓ --------- model_name = "ChainerDialogue.021" trace('initializing ...') encoderDecoderModel = EncoderDecoderModelForward(parameter_dict) encoderDecoderModel.test() ---------
実行してみる
$ python train-test.py # unit: 300 Window : 5 Minibatch-size: 100 # epoch: 10 Traing model: skipgram Output type: original 2016-05-03 20:51:30.686674 ... initializing ... 2016-05-03 20:51:30.686760 ... loading model ... 2016-05-03 20:51:31.265107 ... generating translation ... 2016-05-03 20:51:31.265597 ... sample 1 - 64 ... src : こんにちは hyp : こんにちは src : 分からない hyp : そっか src : 昼ごはんは何を食べましたか hyp : ごはんはあったかいです src : へー hyp : なに src : へー hyp : なに src : それで夕食は何を食べるのですか hyp : 夕食がいってないかもですよねえ src : 日本語として変です hyp : 日本語は沖縄で行きますよねー
おおお!!!会話してる!!
放置すると永遠に会話するのでCtrl+Cで止めますw
1万回ぐらい会話してると同じ文章が出てきますが、これはサンプルデータが少ないからでしょうね。
それでも十分会話として成立してて面白い。
Slackでbotとやり取り
$ slack/app.py
slack側からbotに 「chainer:テスト」とチャット送信するとbotが返信!!
slack/app.py のターミナルでslackのbotが動いてるのがわかります。
slack側からbotに 「chainer_train:テスト」とチャット送信するとエラー…
FileNotFoundError: [Errno 2] No such file or directory: '../twitter/source_twitter_data.txt' FileNotFoundError: [Errno 2] No such file or directory: '../twitter/replay_twitter_data.txt'
え、どこにこんなファイル作って書いてあるの??
とマニュアル見返しても見つからず…
{myproject}/twitter/内に
source_twitter_data.txt
replay_twitter_data.txt
を空ファイルで新規作成。
一応書き込み権限(666)を与えておく。
再びslack botを起動してchainer_trainで学習させてみる
$ python app.py chainer_train: ナストマくん 2016-05-03 21:22:01.773029 ... making vocabularies ... 2016-05-03 21:22:01.773409 ... making model ... 2016-05-03 21:22:02.394966 ... epoch 1/5: 2016-05-03 21:22:02.413685 ... epoch 2/5: 2016-05-03 21:22:02.429900 ... epoch 3/5: 2016-05-03 21:22:02.450281 ... epoch 4/5: 2016-05-03 21:22:02.473980 ... epoch 5/5: 2016-05-03 21:22:02.495622 ... saving model ... 2016-05-03 21:22:04.914517 ... finished.
おお!学習してくれたっぽい!
学習したどうかテストしてみたいので、chainerで呼びかけてみる。
chainer:ナストマくん b'{"ok":false,"error":"no_text"}'
あれ??
Slack側から何かエラーが出てる?
どうしたのかと思ってFinderを確認すると、以下のファイルのタイムスタンプが変わってて、容量が少なくなってた。
ChainerDialogue.spec
ChainerDialogue.srcvocab
ChainerDialogue.trgvocab
5000行ぐらい全部空行になってた。
ChainerDialogue.weights はタイムスタンプ変更されてたけど、30MBぐらいあった。
学習の追加に失敗したのかな??
ともかくSlackの応答botまで動かせたのでここまで。
player_1,player_2のテキストデータを上手く事前に準備していれば個性を保たせたbotを作れそうです。
苦労しましたがその分、動かせた時は達成感ありました。
改めてこの素晴らしいサンプルデータを公開してくれた GushiSnowさんに感謝です。