ichou1のブログ

主に音声認識、時々、データ分析のことを書く

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」。
f:id:ichou1:20190210115035p:plain

(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」(漢字を使うので文字種が多くなる)
f:id:ichou1:20190210115847p:plain

(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の最終的な出力。
f:id:ichou1:20190210115952p:plain

modelのサマリ

f:id:ichou1:20190205162852p:plain

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]