チャットボットを作ってみる② seq2seqで足し算を求めてみる

January 01, 2020

前回に引き続いて、
詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~
を読んでみました。この本はKerasやTensorflowを使用していますので、実際に実装するときに参考になりそうです。seq2seqについて、前の本ほど詳しくはありませんが解説されています。今回はこの本のサンプルコードを参考にしてseq2seqを使った足し算の結果予測プログラムを実装してみました。例えば、「1+3」という文字列シーケンスから、「4」という文字列シーケンスを(計算するのではなく)予測する、というモノです。つまり、足し算を求める問題としてではなく、ある文字列シーケンスから別の文字列シーケンスを予測する、という問題として扱っているわけですね。この本にはtensorflowとkerasのコードが載ってますが、今回はkerasを使用しました。seq2seqモデルはレイヤーが複雑なのでモデル構築のコードも複雑になるのかと思いきや、かなりシンプルなコードになってます。

このページの最後に今回実装したコードを載せてます。

プログラムの流れとしては、

  1. 学習データを作成する
  2. モデルを構築する
  3. 学習する
  4. 予測する

といったように、機械学習のプログラムによくあるフローになってます。

学習データは入力、出力ともに固定長の文字列シーケンスで、それぞれmake_question関数、make_answer関数でサンプル数分、生成しています。そのあと、モデルに与えるために各文字をワンホットベクトルに変換しています(translate_onehot関数)。

前述した本ではseq2seqモデルをSequentialモデルとして実装しています(create_model_sequential関数)が、Functional APIを使った方法でも実装してみました(create_model_functional関数)。どちらもモデルとしての機能は(おそらく)同じになりますが、後者の方法はより柔軟な書き方ができるようでなので、あとで拡張するときに便利そうです。各modelをkerasのplot_model関数で可視化していますが、どちらも同じ構成になっていることがわかります。

本ではmodelの中身についてあまり解説されていませんでしたのでいまいちよく理解できませんでしたが、こちらの記事が大変参考になりました。デコーダのLSTMにはエンコーダの出力(最後の状態ベクトル)を各レイヤーに入力しているようです(つまり、各LSTMレイヤーは同じ入力が与えられる)。この前の記事で触れました別の文献に載っていた方法では、エンコーダーの出力をデコーダのLSTMの初期状態として与え、各レイヤには予測するシーケンス(ただし先頭は開始を表す文字を追加)を入力していたのですが、こういう方法もあるんですね。あと、状態ベクトルの次元を128にしていますが、妥当かどうかがいまいちわかっていません。次元が少ない方がパラメータ数も少なくなるのでモデルが小さくすむのですが、少なすぎるとうまく学習できなくなるようなので、実際に使用する場合はチューニングが必要になりそうです。

modelの構築が終わったら、次は普通にfitで学習して、学習の進み具合をグラフ(横軸が何回目の学習か、縦軸が正解率)として出力しています(show_acc関数)が、使用している学習データや乱数シードを一緒にしているのになぜか2つのモデルで微妙に結果が違ってしまいました。理由はよくわかっていませんが、学習する順番に依存して結果が変わっているようです。

そして、最後に学習したモデルで予測しています(predict関数)。いったん、足し算の式を文字列にした後、ワンホットベクトルに変換してモデルで予測した結果(ワンホットベクトル)を逆に文字のシーケンスに変換しています。どちらも無事、正解を予測できているみたいですね。