odashi/chainer examples/chainer-1.5/mt s2s encdec.py のコードリーディング
code:py
def main():
args = parse_args()
XP.set_library(args)
if args.mode == 'train': train(args)
elif args.mode == 'test': test(args)
コマンドライン引数をparseしてtrainかtestに渡しているだけだね
trainを読もう
code:py
def train(args):
trace('making vocabularies ...')
src_vocab = Vocabulary.new(gens.word_list(args.source), args.vocab)
trg_vocab = Vocabulary.new(gens.word_list(args.target), args.vocab)
日本語辞書と英語辞書を作っている感じかな
args.source は日本語辞書ファイルかな。 ヘルプメッセージに '[in] source corpus' とあった
gens.word_list は指定されたファイル内の各行をスペースで分割して配列にしてるっぽい
args.vocab は語彙数。デフォルトは 1000 になってる。
語彙数って辞書の行数じゃないの?なんでコマンドライン引数で指定すんだろう...
Vocabularyクラスでは、インスタンス変数として __stoi と __itos を作ってる。
string_to_index と index_to_string の略だと思う
word_list 内の単語の出現頻度を集計して、頻度が高い順に、指定された語彙数までの __stoi と __itos を作ってるのか〜。なるほどね。え、そういうもんなのかなぁ。
いや、本家chainerのseq2seqを読んだら、辞書の行数を使ってた。俺もそうしよう。
examples/seq2seq/seq2seq.py:339 で len(source_ids) を使ってる
この辺からは学習させる処理
code:py
trace('making model ...')
encdec = EncoderDecoder(args.vocab, args.embed, args.hidden)
if args.use_gpu:
encdec.to_gpu()
学習に使うモデル(ネットワーク)を作ってるね。
自分なら predictor という変数を使うかなぁ。いや、この中で損失関数まで出してるなら model とかか。
そのあと optimizer.setup(model) して、optimizer を使って updater を作り、updaterを使って trainerを作る、という感じか。
EncoderDecoder クラスの中身は、後ほど。まずは全体像をつかもう。
code:py
for epoch in range(args.epoch):
trace('epoch %d/%d: ' % (epoch + 1, args.epoch))
trained = 0
gen1 = gens.word_list(args.source)
gen2 = gens.word_list(args.target)
gen3 = gens.batch(gens.sorted_parallel(gen1, gen2, 100 * args.minibatch), args.minibatch)
お、epoch数でfor文を回しているね。
この頃(chainer 1.5)はまだtrainerの概念がなかったのかな?
args.epoch はエポック数。デフォルトは 10。
gen1 と gen2 、ループの外で作った方が良くない?毎回作るもの同じだよね?
gens.sorted_parallel で参照渡しをしているなら分かるけど。(書き換わる場合は毎回のループで作り直しが必要)
そんなことなさそうだよなぁ
args.minibatch はバッチサイズ。デフォルトは 64 。
gen3 はなんだ?
gen1 と gen2 を sorted_parallel したものに対して、バッチサイズで batch している
日本語(source)と英語(target)の単語を100 * args.minibatch個ずつまとめた配列にして、その配列をargs.minibatch個作ってる、のかな?
code:py
opt = optimizers.AdaGrad(lr = 0.01)
opt.setup(encdec)
opt.add_hook(optimizer.GradientClipping(5))
お、optimizerにsetupしている
AdaGrad ってなんだろう。RedChainerにあるかなぁ...
本家chainerのseq2seqでは Adam を使っているようだ。 optimizer = chainer.optimizers.Adam()
Optimizer オブジェクトの add_hook() メソッドに更新時に全パラメータに対して行いたい処理を記述した関数を渡して使用します。
これだ!
optimizer.GradientClipping(5)を全パラメータに対して行ないたい、ということだろうね。
optimizer.GradientClipping(5)って何...?
code:py
for src_batch, trg_batch in gen3:
src_batch = fill_batch(src_batch)
trg_batch = fill_batch(trg_batch)
K = len(src_batch)
hyp_batch, loss = forward(src_batch, trg_batch, src_vocab, trg_vocab, encdec, True, 0)
loss.backward()
opt.update()
optimizerをupdateしてる
fill_batch メソッドよくわからん
バッチ内の単語の最大文字数に合わせて、</s>で埋めているように見える。何これ。
forward メソッドで hyp_batch, loss を返してる
loss.backward() で逆伝搬。updaterがないからこの辺も書いているんだなぁ。
code:py
for k in range(K):
trace('epoch %3d/%3d, sample %8d' % (epoch + 1, args.epoch, trained + k + 1))
trace(' src = ' + ' '.join([x if x != '</s>' else '*' for x in src_batchk])) trace(' trg = ' + ' '.join([x if x != '</s>' else '*' for x in trg_batchk])) trace(' hyp = ' + ' '.join([x if x != '</s>' else '*' for x in hyp_batchk])) trained += K
trace('saving model ...')
prefix = args.model + '.%03.d' % (epoch + 1)
src_vocab.save(prefix + '.srcvocab')
trg_vocab.save(prefix + '.trgvocab')
encdec.save_spec(prefix + '.spec')
serializers.save_hdf5(prefix + '.weights', encdec)
trace('finished.')
残りはログを出したりファイルを保存したりだね。はい。