ChainerでLSTM言語モデルとミニバッチ学習の実装
概要
- LSTMに日本語の文章を学習させた
- LSTMでミニバッチ学習をするための実装の紹介
はじめに
ChainerでLSTMを実装するサンプルはいくつかありますが、英語データが対象だったり情報が古かったりしてそのままでは使えないケースがあります。
そこでChainer 1.7でLSTMを実装してみました。
コードはGitHubにあります。
使い方
適当なフォルダを作成し、そこに訓練用データを入れてください。
データはtxtファイルに文章を改行区切りで入力します。
Twitterなどから取得したツイートのテキストデータをそのまま使えます。
ファイル名やファイル数に制限はありません。フォルダ内のすべてのファイルが読み込まれます。
学習にはtrain.py
を使い、文章生成にはvalidate.py
を使います。
Windows 7 + GTX 970Mで動作確認済みです。
日本語を扱う関係で他の環境では問題が生じるかもしれません。
モデルを保存する時、ファイルサイズは100MBを超えることもあるため速いディスクかSSD上で動かすことをおすすめします。
ミニバッチ学習
RNN系のニューラルネットの学習では、入力や出力の系列の長さがデータごとに異なっているため、そのままではミニバッチ学習を行えません。
そこで、バッチ中で最も長い系列のデータに合わせて、他の短い系列の末尾を-1で埋めます。
参考:可変長データのミニバッチをchainerのwhereでやる
たとえば以下の2つのデータからなるミニバッチを考えます。(私の実装では0は終端記号になります)
A: [11, 12, 13, 0]
B: [23, 24, 25, 26, 27, 0]
各データは長さが異なるので、一番長いBに合わせてAの末尾を-1で埋めて拡張します。
A: [11, 12, 13, 0, -1, -1]
B: [23, 24, 25, 26, 27, 0]
これらを合わせたミニバッチCは以下のようになります。
C: [[11, 12, 13, -1, -1], [23, 24, 25, 26, 27]]
これは転置すると以下のようになり、インデックス0から系列ごとに取り出せるようになります。
C.T: [[11, 23], [12, 24], [13, 25], [0, 26], [-1, 27], [-1, 0]]
ただし、Chainer 1.7の時点で、EmbedIDに-1を入力するとnanが返ってきておかしなことになるため、コードに工夫をする必要があります。
以下のように前状態$c0$の-1の要素をすべて終端記号の0に置き換えます。(次状態$c1$は-1のままにしておきます)
for c0, c1 in zip(seq_batch[:-1], seq_batch[1:]):
c0[c0 == -1] = 0
c0 = Variable(xp.asarray(c0, dtype=np.int32))
c1 = Variable(xp.asarray(c1, dtype=np.int32))
なぜ-1で埋めるかというと、softmax_cross_entropyの正解ラベルに-1を指定すると、対応する入力は無視され、誤差も$0$になってくれるからです。
終端記号から-1への遷移を学習する必要はありませんので。
loss = F.softmax_cross_entropy(output, c1)
追記
Chainer 1.8に対応させました。
1.8からはEmbedIDにignore_label=-1を指定することで-1を無視できるようになっています。
ただし1.7含む旧バージョンでは動作しませんのでご注意ください。