【PHP】api.aiの応答をマルコフ連鎖で文章生成してみた

マルコフ連鎖
Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInEmail this to someone

前回、api.aiをAPIとして活用する方法について触れました。今回はその逆?あと?で、api.aiからの応答にも、何らかの学習結果を反映させてみよう、という内容です。

昨今では、「RNNによる学習結果から応答内容を生成する」みたいな事もよく見かけますが、とりあえずまずは「マルコフ連鎖」という”人口無能”なしくみからチャレンジしてみたいと思います。

マルコフ連鎖とは?

先日の佐賀&福岡でのチャットボットハンズオンの中でも取り上げたのですが

「ある事象の1つ先の未来を、これまでの経験から推測するモデル」の事で、文章に当てはめる場合は、形態素解析した単語群をDBに突っ込み、次の単語を予測し続けることで文章をつくる、といった感じになります。

一見難しい話のようですが、形態素解析された単語3つを1要素として配列に入れ、要素を繋ぎ直して文章を作るというもの。

この仕組みを使ってみます。

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にも限りがあるので、こうやってパターンを無数に増やせるのは重要。やはり何かしら応答側の学習も必要ですね。

マルコフ連鎖は人工無能、と言えど、やる価値はあると思いました。

Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInEmail this to someone