Kerasメモ(seq2seqで文字レベル機械翻訳)
「Sequence to sequence example in Keras (character-level)」を試してみる。
環境
tensorflowに統合されたKerasを使用、tensorflowのバージョンは「1.5.1」
今回、試したソースコード。
https://github.com/keras-team/keras/blob/master/examples/lstm_seq2seq.py
We apply it to translating short English sentences into short French sentences, character-by-character.
Note that it is fairly unusual to do character-level machine translation, as word-level models are more common in this domain.
文字レベル(character-level)での機械翻訳を行う(一般的な機械翻訳は単語レベル)
解説ページ
A ten-minute introduction to sequence-to-sequence learning in Keras
デフォルトから以下のとおり変更する。
- データは英文と和文のペア(short English sentences into short Japanese sentences)
- 「num_samples」は「10000」から「5000」に変更
データのフォーマットは以下のとおり(タブ区切り)
Go. 行け。 Go. 行きなさい。 Hi. やっほー。 Hi. こんにちは! ... She is mad at me. 彼女は私に怒っています。 She is obstinate. 彼女は強情です。 She is on a diet. 彼女はダイエットをしている。 ...
タブの前をencoder用、タブの後ろをdecoder用に読み込む。
# for encoder (Pdb) input_texts[0] u'Go.' (Pdb) len(input_texts[0]) 3 # for decoder (Pdb) target_texts[0] u'\t\u884c\u3051\u3002\n' (Pdb) print(target_texts[0]) 行け。 (Pdb) len(target_texts[0]) 5
Encoder用のデータ形式(encoder_input_data)
英文テキスト文字長は最大「18」、文字の種類は「70」。
(Pdb) encoder_input_data.shape # (len(input_texts), max_encoder_seq_length, len(input_characters)) (5000, 18, 70) (Pdb) input_characters [u' ', u'!', u'"', u'$', u"'", u',', u'-', u'.', u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'8', u'9', u':', u'?', u'A', u'B', u'C', u'D', u'E', u'F', u'G', u'H', u'I', u'J', u'K', u'L', u'M', u'N', u'O', u'P', u'Q', u'R', u'S', u'T', u'U', u'V', u'W', u'Y', u'a', u'b', u'c', u'd', u'e', u'f', u'g', u'h', u'i', u'j', u'k', u'l', u'm', u'n', u'o', u'p', u'q', u'r', u's', u't', u'u', u'v', u'w', u'x', u'y', u'z']
Decoder用のデータ形式(decoder_input_data / decoder_target_data)
日本語文テキスト文字長は最大「28」(先頭のタブと末尾の改行も込みでカウント)、文字の種類は「1123」(漢字を使うので文字種が多くなる)
(Pdb) decoder_input_data.shape (5000, 28, 1123) (Pdb) target_characters[:50] [u'\t', u'\n', u'!', u'.', u'0', u'1', u'2', u'3', u'4', u'5', u'6', u'8', u'9', u'?', u'B', u'C', u'D', u'F', u'I', u'V', u'e', u'f', u'i', u'o', u'r', u'x', u'\u2014', u'\u3000', u'\u3001', u'\u3002', u'\u3005', u'\u300c', u'\u300d', u'\u3041', u'\u3042', u'\u3044', u'\u3046', u'\u3048', u'\u3049', u'\u304a', u'\u304b', u'\u304c', u'\u304d', u'\u304e', u'\u304f', u'\u3050', u'\u3051', u'\u3052', u'\u3053', u'\u3054']
正解ラベル(decoder_target_data)については1つ後ろの文字を保持する。
(Pdb) decoder_target_data.shape (5000, 28, 1123)
if t > 0: # decoder_target_data will be ahead by one timestep # and will not include the start character. decoder_target_data[i, t - 1, target_token_index[char]] = 1.
modelの最終的な出力。
modelのサマリ
modelのweight (lstm_1)
LSTMレイヤのunit数は256、4つの重みを一つにまとめている(1024=256x4)
[<tf.Variable 'lstm_1/lstm_cell/kernel:0' shape=(70, 1024)>, <tf.Variable 'lstm_1/lstm_cell/recurrent_kernel:0' shape=(256, 1024)>, <tf.Variable 'lstm_1/lstm_cell/bias:0' shape=(1024,)>]
modelのweight (lstm_2)
[<tf.Variable 'lstm_2/lstm_cell/kernel:0' shape=(1123, 1024)>, <tf.Variable 'lstm_2/lstm_cell/recurrent_kernel:0' shape=(256, 1024)>, <tf.Variable 'lstm_2/lstm_cell/bias:0' shape=(1024,)>]
modelのトレーニングオプション
# Run training model.compile(optimizer='rmsprop', loss='categorical_crossentropy') model.fit([encoder_input_data, decoder_input_data], decoder_target_data, batch_size=batch_size, epochs=epochs, validation_split=0.2)
「epochs」を「50」に設定し、トレーニングを終えたモデルを使ったdecode結果。
Input sentence: Go. Decoded sentence: 行き。 - Input sentence: Hi. Decoded sentence: やっちー。 - ... - Input sentence: She is mad at me. Decoded sentence: 彼女はダイエットをしている。 - Input sentence: She is obstinate. Decoded sentence: 彼女はダイエットをしている。 - Input sentence: She is on a diet. Decoded sentence: 彼女はダイエットをしている。 - ...
今回、使ったデータでは、"Go."という英文に対応する日本語の文は"行け。"と"行きなさい。"の2つ。
同様に、"Hi."という英文に対応する日本語の文は"やっほー。"と"こんにちは!"の2つ。
decode結果は、2種類の日本語の文を混合したような内容になっている。
また、"She is ..."はis以降が異なるのに、decode結果が同じになるケースがあった。
トレーニングセットをどのように分割したかは未確認だが、Bidirectional LSTMに換えたらどうか試してみたい(試した結果)
最後に、encoder_modelとdecoder_modelについてメモしておく。
encoder_model
encoder_outputs, state_h_enc, state_c_enc = model.layers[2].output # lstm_1 encoder_model = Model( model.input[0], # input_1 [state_h_enc, state_c_enc])
_________________________________________________________________________________ Layer (type) Output Shape ================================================================================= input_1 (Input) (None, None, 70) _________________________________________________________________________________ lstm_1 (LSTM) [(None, 256), (None, 256), (None, 256)] =================================================================================
decoder_model
decoder_state_input_h = Input(shape=(256,), name='input_3') decoder_state_input_c = Input(shape=(256,), name='input_4') decoder_lstm = model.layers[3] # lstm_2 decoder_outputs, state_h_dec, state_c_dec = decoder_lstm( model.input[1], initial_state=[decoder_state_input_h, decoder_state_input_c]) decoder_dense = model.layers[4] decoder_outputs = decoder_dense(decoder_outputs) decoder_model = Model( [model.input[1]] + [decoder_state_input_h, decoder_state_input_c], [decoder_outputs] + [state_h_dec, state_c_dec])
_________________________________________________________________________________ Layer (type) Output Shape Connected to ================================================================================= input_2 (Input) (None, None, 1123) _________________________________________________________________________________ input_3 (Input) (None, 256) _________________________________________________________________________________ input_4 (Input) (None, 256) _________________________________________________________________________________ lstm_2 (LSTM) [(None, None, 256), (None, 256), (None, 256)] input_2[0][0] input_3[0][0] input_4[0][0] _________________________________________________________________________________ dense_1 (Dense) (None, None, 1123) lstm_2[0][0] =================================================================================
インプットとなる英文(one-hot表現へ変換済み)をencoder_modelに渡し、隠れ状態(state_h_enc)と記憶セル(state_c_enc)を得る。
# Encode the input as state vectors. # input_seq.shape --> (1, 18, 70) states_value = encoder_model.predict(input_seq) # states_value[0].shape --> (1, 256) # states_value[1].shape --> (1, 256)
encoder_modelのアウトプットである隠れ状態および記憶セルを初期値としてdecoder_modelに渡し、predictionを実行する。
target_seq = np.zeros((1, 1, 1123)) # Populate the first character of target sequence with the start character. target_seq[0, 0, target_token_index['\t']] = 1. while not stop_condition: output_tokens, h, c = decoder_model.predict([target_seq] + states_value) # Sample a token sampled_token_index = np.argmax(output_tokens[0, -1, :]) sampled_char = reverse_target_char_index[sampled_token_index] decoded_sentence += sampled_char # find stop character('\n') --> stop_condition = True # Update the target sequence (of length 1). target_seq = np.zeros((1, 1, 1123)) target_seq[0, 0, sampled_token_index] = 1. # Update states states_value = [h, c]