双方向LSTMを使用したAttention付きseq2seq
「ゼロから作るDeep Learning―自然言語処理編」を読み終えました。
この本の後半に双方向LSTM(TimeBiLSTM)を使用してAttention付きseq2seqを作ってみてねとありましたので、作ってみました。
まず、AttentionBiSeq2Seq, AttentionBiEncoder, AttentionBiDecoderというクラスを
作成し、AttentionBiSeq2Seqを動かすためのtrain_bi_seq.pyファイルを作成しました。
間違っているところがあるかも知れませんが、一応動きます(汗)ので、勉強メモを兼ねて掲載します。
まず、attention_bi_seq2seq.pyです。AttentionBiSeq2Seq, AttentionBiEncoder, AttentionBiDecoderの三つのクラスを持ちます。
詳しくは元のサンプルソース attention_seq2seq.pyと比較してみて下さい。
# coding: utf-8 import sys sys.path.append('..') from common.time_layers import * from ch07.seq2seq import Encoder, Seq2seq from ch08.attention_seq2seq import * class AttentionBiSeq2seq(Seq2seq): def __init__(self, vocab_size, wordvec_size, hidden_size): args = vocab_size, wordvec_size, hidden_size self.encoder = AttentionBiEncoder(*args) self.decoder = AttentionBiDecoder(*args) self.softmax = TimeSoftmaxWithLoss() self.params = self.encoder.params + self.decoder.params self.grads = self.encoder.grads + self.decoder.grads class AttentionBiEncoder(AttentionEncoder): def __init__(self, vocab_size, wordvec_size, hidden_size): V, D, H = vocab_size, wordvec_size, hidden_size rn = np.random.randn embed_W = (rn(V, D) / 100).astype('f') # TimeBiLSTMを使うための設定。双方向だから、重みの数が普通のLSTMの二倍。 lstm_Wx1 = (rn(D, 4 * H) / np.sqrt(D)).astype('f') lstm_Wh1 = (rn(H, 4 * H) / np.sqrt(H)).astype('f') lstm_b1 = np.zeros(4 * H).astype('f') lstm_Wx2 = (rn(D, 4 * H) / np.sqrt(D)).astype('f') lstm_Wh2 = (rn(H, 4 * H) / np.sqrt(H)).astype('f') lstm_b2 = np.zeros(4 * H).astype('f') self.embed = TimeEmbedding(embed_W) self.lstm = TimeBiLSTM(lstm_Wx1, lstm_Wh1, lstm_b1, lstm_Wx2, lstm_Wh2, lstm_b2, stateful=False) self.params = self.embed.params + self.lstm.params self.grads = self.embed.grads + self.lstm.grads self.hs = None class AttentionBiDecoder(AttentionDecoder): def __init__(self, vocab_size, wordvec_size, hidden_size): V, D, H = vocab_size, wordvec_size, hidden_size rn = np.random.randn embed_W = (rn(V, D) / 100).astype('f') # Encoderからの入力が2倍になるため、重みの数も調整。 lstm_Wx = (rn(D, 2 * 4 * H) / np.sqrt(D)).astype('f') lstm_Wh = (rn(2 * H, 2 * 4 * H) / np.sqrt(2 * H)).astype('f') lstm_b = np.zeros(2 * 4 * H).astype('f') affine_W = (rn(2 * 2 * H, V) / np.sqrt(2 * 2 * H)).astype('f') affine_b = np.zeros(V).astype('f') self.embed = TimeEmbedding(embed_W) self.lstm = TimeLSTM(lstm_Wx, lstm_Wh, lstm_b, stateful=True) self.attention = TimeAttention() self.affine = TimeAffine(affine_W, affine_b) layers = [self.embed, self.lstm, self.attention, self.affine] self.params, self.grads = [], [] for layer in layers: self.params += layer.params self.grads += layer.grads
次はtrain_bi_seq.pyです。元のサンプルソースtrain_seq.pyとの違いはimportとAttentionBiSeq2seqのところだけです。
# coding: utf-8 import sys sys.path.append('..') import numpy as np import matplotlib.pyplot as plt from dataset import sequence from common.optimizer import Adam from common.trainer import Trainer from common.util import eval_seq2seq from attention_bi_seq2seq import AttentionBiSeq2seq from ch07.seq2seq import Seq2seq #from ch07.peeky_seq2seq import PeekySeq2seq # データの読み込み (x_train, t_train), (x_test, t_test) = sequence.load_data('date.txt') char_to_id, id_to_char = sequence.get_vocab() # 入力文を反転 x_train, x_test = x_train[:, ::-1], x_test[:, ::-1] # ハイパーパラメータの設定 vocab_size = len(char_to_id) wordvec_size = 16 hidden_size = 256 batch_size = 128 max_epoch = 10 max_grad = 5.0 model = AttentionBiSeq2seq(vocab_size, wordvec_size, hidden_size) # model = Seq2seq(vocab_size, wordvec_size, hidden_size) # model = PeekySeq2seq(vocab_size, wordvec_size, hidden_size) optimizer = Adam() trainer = Trainer(model, optimizer) acc_list = [] for epoch in range(max_epoch): trainer.fit(x_train, t_train, max_epoch=1, batch_size=batch_size, max_grad=max_grad) correct_num = 0 for i in range(len(x_test)): question, correct = x_test[[i]], t_test[[i]] verbose = i < 10 correct_num += eval_seq2seq(model, question, correct, id_to_char, verbose, is_reverse=True) acc = float(correct_num) / len(x_test) acc_list.append(acc) print('val acc %.3f%%' % (acc * 100)) model.save_params() # グラフの描画 x = np.arange(len(acc_list)) plt.plot(x, acc_list, marker='o') plt.xlabel('epochs') plt.ylabel('accuracy') plt.ylim(-0.05, 1.05) plt.show()
要は、本で紹介しているTimeBiLSTMは、Encoderで呼ばれて、
翻訳前の言葉に対する左方向のLSTM結果と右方向のLSTM結果を結合(concatenate)し、
Axis:0のサイズが通常Encoderの2倍のhsを返します。
Decoderではこのhsのサイズに合わせて重みのサイズを調整する必要があります。
もし、Encoderで左右LSTMの結果を結合(concatenate)せず、平均値を出力するなら、重みのサイズは変更せず、済んだでしょう。
前編の「ゼロから作るDeep Learning: Pythonで学ぶディープラーニングの理論と実装」もよかったですが、
自然言語処理編もとても分かりやすくてよかったです。
ダウンロードしたソースコードも問題なく動きました。
これまでDeep Learning関連の本(数学・統計なども含めて)を十冊以上読でいて、
少し分かる気はしますが、まだまだ足りないと思うので、今はKeras関連の本を買って勉強しているところです。