Tensor2Tensorで雑談チャットボットを作ったら今度はうまくいった話

# はじめに
前回の失敗から手法を変えてチャットボットの作成を試みました。
今度はうまくいきましたが、ほぼ公式ドキュメント通りの内容なのであまり面白味はありません。

前回の失敗は[こちら](https://qiita.com/NJIMAMTO/items/0011fcbaad3420ace78b)から
全体のコードは[こちら](https://github.com/NJIMAMTO/transformer-chat-bot/blob/master/Chatbot_with_t2t.ipynb)から

# 作成方法
今回はGoogle Brain チームが提供しているTensor2Tensor(t2t)を使うことにしました。
t2tは既に用意されているデータセットで学習するだけならコードを書くことなく(コマンドのみ)実行できる手軽さが特徴です。
自前のデータセットを実行する際にも、ほぼ公式ドキュメントに書かれている数行のコードと形式の整ったデータセットさえあれば実行できるので非常に楽です。

今回は前回作成した名大会話コーパスから抽出したinput_corpus.txtとoutput_corpus.txtをデータセットとして学習・推論をさせてみます。実行環境はGoogle Colabです。

なお公式ドキュメント[^1]やこちら[^2]、こちら[^3]などの内容に沿って進めます。

### データセットの準備
上記参考ページの通りに進めますと必要なことは以下の二つです。

+ データセットを参照するためのコード(myproblem.py)
+ myproblem.pyをt2tに認識させるためのコード(\_\_init__.py)

作り方の詳細については参考ページにお任せします。とりあえずコードだけ載せておきます

```myproblem.py
from tensor2tensor.data_generators import problem
from tensor2tensor.data_generators import text_problems
from tensor2tensor.utils import registry
 
 
@registry.register_problem
class chat_bot(text_problems.Text2TextProblem):
    @property
    def approx_vocab_size(self):
        return 2**13
    
    @property
    def is_generate_per_split(self):
        return False
 
    @property
    def dataset_splits(self):
        return [{
            "split": problem.DatasetSplit.TRAIN,
            "shards": 9,
        }, {
            "split": problem.DatasetSplit.EVAL,
            "shards": 1,
        }]
 
    def generate_samples(self, data_dir, tmp_dir, dataset_split):
        filename_input = '/content/drive/My Drive/Colab Notebooks/input_corpus.txt'
        filename_output = '/content/drive/My Drive/Colab Notebooks/output_corpus.txt'
 
        with open(filename_input) as f_in, open(filename_output) as f_out:
            for src, tgt in zip(f_in, f_out):
                src = src.strip()
                tgt = tgt.strip()
                if not src or not tgt:
                    continue
                yield {'inputs': src, 'targets': tgt}
```

公式ドキュメントから変更した点はクラス名とgenerate_samples関数の必要箇所になります。
なおクラス名はキャメルケースで書くのが通例ですが、なぜかこのあとのことを考慮するとスネークケースで書かざるを得ませんでした。本来ならキャメルケースでも動くはずですなのが、ちょっと謎です。

```__init__.py
from . import myproblem
```
こちらについては上記の内容のみを書いてmyproblem.pyと同じディレクトリに置けばOKです。

### データの前処理
t2tはデータの前処理もほぼ自動でやってくれます。便利ですね。(下記コードはnotebook形式から.py形式に変換しています)

```ChatBot_with_t2t.py
#Tensorflowのバージョンを1,xにする
"""

# Commented out IPython magic to ensure Python compatibility.
# %tensorflow_version 1.x

"""#機械学習モデル(Transformer)をインストールする"""

!pip install tensor2tensor

"""#Google Driveのマウント"""

from google.colab import drive
drive.mount('/content/drive')

"""#作業ディレクトリの変更"""

cd /content/drive/My Drive/Colab Notebooks

"""#学習データの前処理"""

!t2t-datagen \
  --data_dir=. \
  --tmp_dir=./t2t \
  --problem=chat_bot \
  --t2t_usr_dir=./t2t

```
今回はmyproblem.pyと\_\_init__.pyをChatBot\_with\_t2t.ipynbの一階層下にあるt2tディレクトリに置きます。また、今回はinput_corpus.txtとoutput_corpus.txtは.ipynbと同じディレクトリに置きましたが、実行後に生成されるファイルがあるため、別フォルダに保存しておいたほうが良いかもしれません。

なお、コマンドラインオプションの`problem=`にはmyproblem.pyで変更したクラス名(本来ならキャメルケースからスネークケースに自動変換されるがうまくいきませんでした)を指定します。

### 学習させる

```ChatBot_with_t2t.py
"""#学習の実行"""

!t2t-trainer \
  --data_dir=/content/drive/My\ Drive/Colab\ Notebooks \
  --problem=chat_bot \
  --model=transformer \
  --hparams_set=transformer_base_single_gpu \
  --output_dir=/content/drive/My\ Drive/Colab\ Notebooks/t2t \
  --t2t_usr_dir=/content/drive/My\ Drive/Colab\ Notebooks/t2t

```

前処理は相対パスでディレクトリを指定しましたが、絶対パスでの記述ももちろん可能です。
今回はパス内に空白を含むので`\`でエスケープしました。
今回も学習モデルはtransformerで行います。今回の学習には大体3時間~4時間かかりました。
なお、90分制限をクリアするためにChorme拡張機能で自動再読み込みさせています。

ひとつ注意すべき点がありまして、学習を実行すると中間生成物が多く作られます。自環境だとGoogle Drive内のゴミ箱が一杯になってしまい、実行途中に空にする必要に迫られました。
まだ実行後に生成される学習ファイルも結構容量を食うので、必要がなくなったら都度削除する必要があるかもしれません。

### 推論させる

```ChatBot_with_t2t.py
"""#推論する"""

!t2t-decoder \
   --data_dir=./ \
   --problem=chat_bot \
   --model=transformer \
   --hparams_set=transformer_base_single_gpu \
   --output_dir=./t2t \
   --decode_hparams="beam_size=4,alpha=0.6" \
   --decode_interactive=true \
   --t2t_usr_dir=./t2t
```

上記コマンドで対話的に実行させることができます。

# 実行結果
decodeコマンドを打つとボットと会話できます。以下、入力と出力内容の抜粋です。

```
入力：こんにちは
出力：っていう意味なの？

入力：いやおはようって言ってるでしょ
出力：大丈夫。

入力：なにが大丈夫なんだよ(笑)
出力：うん。

入力：うんちゃうわ
出力：性格？

入力：そういう性格です
出力：うーん、そうなんですよ。

入力：納得したわ
出力：テレフォン、テレフォンカード、ふつう、ありがとうっていうお礼にね、あげるのはあたりまえよね。

入力：突然暴れだしたぞ
出力：＜笑い＞突き刺したら、お前。

入力：えっいきなり怖っ
出力：怖いじゃん。

入力：もうやめさしてもらうわ
出力：さしぶりってなんだよー。
```

最後に`q`と入力すれば対話モードを終了させることができます。

不自然さは拭えないうえにたまに暴走することもありますが、概ね上手くいっているのではないでしょうか
今回のデータセットは砕けた会話が中心のものなので、堅苦しい文章などは難しいかもしれません。

# まとめ
前回は大いに失敗しましたが、今回はt2tを使うことで雑談チャットボットを作ることができました。
会話というより質問に対して応答しているだけではありますが、ある程度はうまくいっていると思います。

チャットボットを簡単に作れますし、ほかの機械学習タスクにも対応しているので皆さんが作りたいものも簡単に作れるかもしれません。

# 参考文献

[^1]: [公式ドキュメント 自前データセットの作り方](https://tensorflow.github.io/tensor2tensor/new_problem.html)
[^2]: [おいでませ！Chatbot whywaitaくん！](https://twismik0.hatenablog.com/entry/2018/12/18/004434)
[^3]: [tensor2tensorで和英翻訳](https://datanerd.hateblo.jp/entry/2019/07/25/101436)