※api.aiは、2017年10月にDialogflowへと名称変更されました。
前回、api.aiをAPIとして活用する方法について触れました。今回はその逆?あと?で、api.aiからの応答にも、何らかの学習結果を反映させてみよう、という内容です。
昨今では、「RNNによる学習結果から応答内容を生成する」みたいな事もよく見かけますが、とりあえずまずは「マルコフ連鎖」という”人口無能”なしくみからチャレンジしてみたいと思います。
マルコフ連鎖とは?
先日の佐賀&福岡でのチャットボットハンズオンの中でも取り上げたのですが
「ある事象の1つ先の未来を、これまでの経験から推測するモデル」の事で、文章に当てはめる場合は、形態素解析した単語群をDBに突っ込み、次の単語を予測し続けることで文章をつくる、といった感じになります。
一見難しい話のようですが、形態素解析された単語3つを1要素として配列に入れ、要素を繋ぎ直して文章を作るというもの。
https://takuti.me/note/twitter-bot/
この仕組みを使ってみます。
api.aiのResponse Text
応答文生成に必要な学習用データは、どのようにでも調達可能なのですが、今回はapi.aiをそのまま使ってみます。
api.aiは、Response Textに複数回答を要ししておけば、そのいずれかがランダムに帰ってくる仕様になっていますが、結局は固定の文章になってしまいます。これをある程度は文章生成させてみようかと。
対話例として、「あんたバカァ?」と聞いたら、アスカ風なマルコフ連鎖用文章データを返します。

文頭で「私」と言っているのは、それをキーといて文章を作るからですね。このResronse Textを、前回のようにAPIでJSONから抽出した後、マルコフ連鎖にかけます。
マルコフ連鎖のPHPコード
こちらを参考にしました。
精度を高めるため文字列ではなく配列を使い、精度が高そうだったパターン1を採用。EOFまで文用生成を繰り返すととてつもなく長くなることがあるため、「。」「!」「?」といった文章の切れ目までの生成としたのが、次のsummarize関数。
function summarize($nodesArray) { if (is_array($nodesArray)) $words = $nodesArray; else return false; $table = array(); for ( $i = 0; $i < count($words) - 2; $i++ ) { $table[] = array( 'head' => $words[$i],'middle' => $words[$i + 1],'end' => $words[$i + 2] ); } $t1 = $table[0]['head']; $t2 = $table[0]['middle']; $summary = $t1 . $t2; while (true) { $a = array(); foreach ($table as $h) {if ($h['head'] === $t1 && $h['middle'] == $t2) $a[] = $h; } if (count($a) === 0) break; $num = array_rand($a); $summary .= $a[$num]['end']; if ($a[$num]['end'] === "。" | $a[$num]['end'] === "?" | $a[$num]['end'] === "!") break ; $t1 = $a[$num]['middle']; $t2 = $a[$num]['end']; } return preg_replace('/EOS$/', '', $summary);}
これにより得られた応答が、以下。
文章はそれなりにそのままだったりかぶったりもしますが、それなりに生成もされていたりしてちょっと楽しいし、話していて飽きない。
api.aiの場合、自分で登録できるResponse textにも限りがあるので、こうやってパターンを無数に増やせるのは重要。やはり何かしら応答側の学習も必要ですね。
マルコフ連鎖は人工無能、と言えど、やる価値はあると思いました。