iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
AI & Data

當自然語言處理遇上深度學習系列 第 21 篇

[神經機器翻譯理論與實作] Google Translate的神奇武器- Seq2Seq (III)

  • 分享至 

  • xImage
  •  

前言

今天繼續我們未完成的建模大業吧!

我們要建立的seq2seq模型由LSTM編碼器與解碼器串接而成:
https://ithelp.ithome.com.tw/upload/images/20211002/20140744V9JzB8Qbuj.jpg

寫一個簡單的seq2seq網絡吧-續

我們使用 Keras API 進行建模。值得注意的是,值得注意的是 keras API 已經被整合進 tensorflow 框架當中,因此不建議直接引入 Keras ,而是從 tensorflow 中引入 Keras:

from tensorflow import keras as K

建立Encoder-Decoder模型

為了建立神經網絡模型,我們引入 Keras API 中寫好的單層神經元類別:

  • Input :建立輸入層物件,可以指定資料維度是「 批次處理資料筆數 × 單筆資料張量維度 」
  • LSTM :單層 LSTM 細胞,可以指定 hidden state 與 cell state 向量之維度
  • Dense :全連接層( fully connected layer, FC ),為了預測輸出序列的每一個時間點最可能出現之單詞向量
from tensorflow.keras.layers import Input, LSTM, Dense
from tensorflow.keras.models import Model

正式建立編碼器囉!注意的是我們希望 seq2seq 神經網絡能夠一次處理多筆資料,指定了輸入層資料張量維度以 None 最為開頭,並接續單筆資料的維度(不重複 tokens 的總數)。我們也透過 LSTM 物件指定編碼器內部 hidden state 與 cell state 皆為 256維向量,透過在實體化 LSTM 物件時設定 return_state = True 讓兩個內部狀態依時間序傳遞於各個 LSTM 小單元。而編碼器的輸出值不會被接收,不需使用變數承接。
https://ithelp.ithome.com.tw/upload/images/20211002/20140744rvsGaMQyKS.jpg

latent_dim = 256
# Build an encoder LSTM
encoder_inputs = Input(shape = (None, num_encoder_tokens))
encoder_lstm = LSTM(latent_dim, return_state = True)
# Discard encoder_outputs
_, state_hidden, state_cell = encoder_lstm(encoder_inputs)
encoder_states = [state_hidden, state_cell]

現在輪到解碼器的建構了!這裡要注意解碼器的內部會回傳前一個輸出值,因此必須在實體化 LSTM 物件時加入 return_sequences = True 的設定。在解碼器輸出之前會經過激活函數( activation function ),將當下時間的輸出分類到最可能出現的 token 。由於輸出向量中每個 tokens 的欄位皆被分配機率,屬於多類分類問題( multi-class classification ),我們採用 softmax 當作激活函數。
https://ithelp.ithome.com.tw/upload/images/20211002/201407448yVBontmca.jpg

decoder_inputs = Input(shape = (None, num_decoder_tokens))
decoder_lstm = LSTM(latent_dim, return_sequences = True, return_state = True)
decoder_outputs, decoder_state_hidden, decoder_state_cell = decoder_lstm(decoder_inputs, initial_state = encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation = "softmax")
decoder_outputs = decoder_dense(decoder_outputs)

並且利用 Model 類別串聯建立好的編碼器與解碼器,注意 seq2seq 模型整體的輸入分別為編碼器的輸入連同解碼器的輸入,整體的輸出則為僅為解碼器的輸出。
https://ithelp.ithome.com.tw/upload/images/20211002/2014074440Bc4NiM30.jpg

由於翻譯是多類分類問題,指定損失函數設定為 categorical_crossentropy ,找尋損失函數的梯度下降演算法為 Adam ,並且測量模型的精確度:

# Build a seq2seq model
my_seq2seq = Model([encoder_inputs, decoder_inputs], decoder_outputs, name = "seq2seq_Eng-Span_translator")
my_seq2seq.summary()
my_seq2seq.compile(
    optimizer = "adam",
    loss = "categorical_crossentropy",
    metrics = ["accuracy"]
)

模型學習

開始訓練翻譯器模型並回顧訓練過程中損失函數及準確度的變化:

batch_size = 50
epochs = 500

# Start training with a GPU
with tf.device("/GPU:0"):
    start = time.time()
    train_hist =  my_seq2seq.fit(
                        x = [encoder_input_data, decoder_input_data],
                        y = decoder_target_data,
                        batch_size = batch_size,
                        epochs = epochs,
                        shuffle = True,
                        verbose = 1,
                        validation_split = 0.2
                        )

# Review training history
print("All history keys: {}".format(train_hist.history.keys()))
fig, axes = plt.subplots(1, 2, figsize = (13, 5))
fig.suptitle("Training History of My Seq2Seq Model")
plt.tight_layout()
axes[0].set_title("Loss")
axes[0].plot(train_hist.history["loss"], label = "train")
axes[0].plot(train_hist.history["val_loss"], label = "test")
axes[0].set_xlabel("epoch")
axes[0].set_ylabel("loss")
axes[0].legend(loc = "upper right")
axes[1].set_title("Accuracy")
axes[1].plot(train_hist.history["accuracy"], label = "train")
axes[1].plot(train_hist.history["val_accuracy"], label = "test")
axes[1].set_xlabel("epoch")
axes[1].set_ylabel("accuracy")
axes[1].legend(loc = "lower right")
plt.show()

https://ithelp.ithome.com.tw/upload/images/20210930/20140744W7F7cMVNio.jpg

測試翻譯效果-模型推論過程

def translate_sentence(test_input):
    # Encode the input as state vectors
    encoder_states_value = encoder_model.predict(test_input)
    # Set decoder states equal to encoder final states
    decoder_states_value = encoder_states_value
    # Generate empty target sequence of length 1
    # (batch size, number of tokens to start with, number of tokens in our target vocabulary)
    target_seq = np.zeros((1, 1, num_decoder_tokens))

    # Populate the first token of target sequence with the start token:
    target_seq[0, 0, target_features_dict["<SOS>"]] = 1.

    decoded_sentence = ''
    stop_condition = False
    while not stop_condition:
        # Run the decoder model to get possible
        # output tokens (with probabilities) & states
        output_tokens, new_decoder_hidden_state, new_decoder_cell_state = decoder_model.predict(
        [target_seq] + decoder_states_value)

    # Choose token with highest probability
    sampled_token_index = np.argmax(output_tokens[0, -1, :])
    sampled_token = reverse_target_features_dict[sampled_token_index]
    decoded_sentence += ' ' + sampled_token

    # Exit condition: either hit max length or find <EOS>
    if (sampled_token == "<EOS>" or len(decoded_sentence) > max_decoder_seq_length):
        stop_condition = True
    # Update the target sequence (of length 1)
    target_seq = np.zeros((1, 1, num_decoder_tokens))
    target_seq[0, 0, sampled_token_index] = 1.

    # Update states
    decoder_states_value = [new_decoder_hidden_state, new_decoder_cell_state]
    return decoded_sentence


# Inference process    
# Translate each English sentence in input_docs into Spanish   
with device("/GPU:0"):
    for seq_idx in range(10):
        test_input = encoder_input_data[seq_idx: seq_idx + 1]
        translated_sentence = translate_sentence(test_input)
        print("---------------------------")
        print("Source sentence:", input_docs[seq_idx])
        print("Translated sentence:", translated_sentence)
        print("Ground truth target sentence: ", target_docs[seq_idx].lstrip("<SOS> ").rstrip(" <EOS>"))

執行翻譯結果,比對一下各句英文的西班牙譯文(模型預測)以及語料庫中的西班牙原文(真實):
https://ithelp.ithome.com.tw/upload/images/20210930/20140744nl80zk53uv.jpg

結語

今天的例子中我們採用了極小型的雙語平行語料庫,裏頭只有意義相同的英文與西班牙文的文句各11句。在將單詞轉為向量時,我們為了方便說明,使用了 one-hot 編碼,很顯然模型的準確度並不會太高。在翻譯實際任務上,我們會改用 word2vec 的 word embedding 方式進行向量化,因其保留了語意關係,將大幅提升模型預測的準確度。
今天的文章就記錄到這裡,各位小夥伴晚安!

閱讀更多

  1. Customize what happens in Model.fit | TensorFlow
  2. Get started with TensorBoard

上一篇
[神經機器翻譯理論與實作] Google Translate的神奇武器- Seq2Seq (II)
下一篇
[神經機器翻譯理論與實作] 你只需要專注力(I): Attention Mechanism
系列文
當自然語言處理遇上深度學習 共 33 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言