睿智的seq2seq模型4——往英文到法文的翻譯里加上注意力機制

學習前言

既然學了注意力機制,也學了文獻翻譯,那不如,聯通一下吧?
在這裏插入圖片描述

什麼是注意力機制

假設我們要翻譯一句話:
打電腦遊戲。
也就是play computer game。
在這裏插入圖片描述
如果不引入注意力機制,那麼我們從Encoder獲得語義編碼c之後,這個語義編碼在Decoder中傳遞,其內容就和Encoder無關了。

但是事實上我們希望在翻譯打電腦遊戲中的的時候,我們更注意打->play的轉換,此時我們希望Decoder更加註意Encoder從中提取出來的特徵。

這就是注意力機制的概念,它的本意是讓神經網絡模型在做特定的事的時候可以注意到它需要注意的地方。

由於神經網絡是一堆數字的傳遞,每個事物的特徵也是由一堆數字組成的,比如字的特徵也是一堆數字,電腦的特徵也是一堆數字,遊戲的特徵也是一堆數字,語義編碼c就是這麼多特徵的組合。

那麼如何使得神經網絡模型對某個內容進行注意呢?其實就是將改變不同內容的權重,當我們需要神經網絡注意到的時候,我們只需要提高字的特徵的權重就可以了。

假設函數 f 可以用於提取特徵,函數 g 可以實現解碼。那麼如果我們要神經網絡注意到,可以通過如下方式進行。
Cplay=g(0.8f(),0.1f(),0.1f()) C_{play} = g(0.8*f(打),0.1*f(電腦),0.1*f(遊戲))

如何將注意力機制應用到翻譯中

如上述所示,想要將注意力機制應用到翻譯中,其核心重點就是將輸入的字符和輸出的字符建立映射。

還是以打電腦遊戲爲例,此時我們的輸入可以分爲三個部分,分別是打、電腦、遊戲

輸出是三個部分,分別是play、computer、game

我們需要分別在翻譯這三個部分的時候, 打、電腦、遊戲對翻譯的貢獻。

用圖片來演示就是這樣。
在這裏插入圖片描述
利用LSTM可以獲得打、電腦、遊戲的特徵,分別命名爲特徵1、特徵2、特徵3。

特徵1代表了打的特徵、特徵2代表了電腦的特徵、特徵3代表了遊戲的特徵。

當我們需要翻譯出play的時候,我們需要使得特徵1的權重較大,特徵2、特徵3的權重較小。

當我們需要翻譯出computer的時候,我們需要使得特徵2的權重較大,特徵1、特徵3的權重較小。

當我們需要翻譯出game的時候,我們需要使得特徵3的權重較大,特徵1、特徵2的權重較小。

在這裏插入圖片描述
實際上就是通過調整上述圖像中,特徵1、2、3到STEP1、2、3的權值,從而實現注意力機制。

英文翻譯到法文的思路

1、對英文進行特徵提取

將英文按順序輸入到LSTM中,LSTM會對英文進行特徵提取。

我們知道LSTM每一個STEP都會有一個輸出,因此我們將每一個STEP的輸出作爲當前輸入的特徵值。

每一個STEP的輸入都有一個對應的特徵值。
在這裏插入圖片描述
實現代碼爲(這裏我將LSTM函數換成了CuDNNLSTM函數,因爲我裝了TensorflowGPU,會運算的更快,二者除了運算速度的差別,沒有什麼其它的差別。):

encoder_inputs = Input(shape=(None, num_encoder_tokens))

x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)

2、將提取到的特徵傳入到decoder

通過第一步,我們可以獲得每個STEP的特徵,按照順序傳入到decoder中。
在這裏插入圖片描述

3、將"\t"作爲起始符預測第一個字母

當我們將所有特徵傳入到decoder中,我們還需要計算每個特徵對不同的輸出的貢獻權重。

當我們把將"\t"作爲起始符預測第一個字母時,我們需要計算每個特徵對這個輸出的貢獻權重。

我們採用點乘的方式進行貢獻權重的計算。

然後利用特徵和貢獻權重計算出進入到decoder中的特徵,並與decoder利用自身輸入得到的特徵進行結合,實現預測。

在本例子中,求出了四個特徵對這個輸出的貢獻權重,從而計算出進入到decoder中的特徵,然後將"\t"傳入到LSTM中獲得自身輸入得到的特徵,將兩個特徵結合後,進入到新的一個LSTM中,再經過全連接層進行預測。
在這裏插入圖片描述

4、逐個字母向後傳遞進行預測

接下來就是繼續求取四個特徵對這個輸出的貢獻權重,從而計算出進入到decoder中的特徵

然後將上一輪求出的字符傳入到LSTM中獲得自身輸入得到的特徵,將兩個特徵結合後,進入到新的一個LSTM中,再經過全連接層進行預測。

在這裏插入圖片描述
以此類推。
在這裏插入圖片描述以此類推,當出現"\n"的時候,表示結尾了!
在這裏插入圖片描述
神經網絡部分實現代碼如下:

encoder_inputs = Input(shape=(None, num_encoder_tokens))

x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)

decoder_inputs = Input(shape=(None, num_decoder_tokens))

x_decoder = CuDNNLSTM(latent_dim,return_sequences=True)(decoder_inputs)
x_decoder = Dropout(0.5)(x_decoder)
# Attention
attention = Dot(axes=[2, 2])([x_decoder, x_encoder])
attention = Activation('softmax')(attention)

context = Dot(axes=[2, 1])([attention, x_encoder])
decoder_combined_context = Concatenate(axis=-1)([context, x_decoder])
x_decoder = CuDNNLSTM(int(latent_dim/2),return_sequences=True)(decoder_combined_context)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(x_decoder)

model = Model([encoder_inputs, decoder_inputs], decoder_outputs)

全部代碼實現

訓練集在這裏下載:
http://www.manythings.org/anki/fra-eng.zip
英文到法文的翻譯可通過如下的代碼實現。


from __future__ import print_function

from keras.models import Model
from keras.layers import Input, CuDNNLSTM, Dense,TimeDistributed
from keras.layers import Convolution1D, Dot, Activation, Concatenate, Dropout
from keras.models import Model
import numpy as np
import keras.backend as K
def get_dataset(data_path, num_samples):
    input_texts = []
    target_texts = []

    input_characters = set()
    target_characters = set()
    with open(data_path, 'r', encoding='utf-8') as f:
        lines = f.read().split('\n')
    for line in lines[: min(num_samples, len(lines) - 1)]:
        input_text, target_text, _ = line.split('\t')
        # 用tab作用序列的開始,用\n作爲序列的結束
        target_text = '\t' + target_text + '\n'

        input_texts.append(input_text)
        target_texts.append(target_text)
        
        for char in input_text:
            if char not in input_characters:
                input_characters.add(char)
        for char in target_text:
            if char not in target_characters:
                target_characters.add(char)
    return input_texts,target_texts,input_characters,target_characters


#------------------------------------------#
#   init初始化部分
#------------------------------------------#
# 每一次輸入64個batch
batch_size = 64
# 訓練一百個世代
epochs = 100
# 256維特徵向量
latent_dim = 128
# 一共10000個樣本
num_samples = 10000

# 讀取數據集
data_path = 'fra.txt'

# 獲取數據集
# 其中input_texts爲輸入的英文字符串
# target_texts爲對應的法文字符串

# input_characters用到的所有輸入字符,如a,b,c,d,e,……,.,!等
# target_characters用到的所有輸出字符
input_texts,target_texts,input_characters,target_characters = get_dataset(data_path, num_samples)

# 對字符進行排序
input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
# 計算共用到了什麼字符
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
# 計算出最長的序列是多長
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])

print('一共有多少訓練樣本:', len(input_texts))
print('多少個英文字母:', num_encoder_tokens)
print('多少個法文字母:', num_decoder_tokens)
print('最大英文序列:', max_encoder_seq_length)
print('最大法文序列:', max_decoder_seq_length)

# 建立字母到數字的映射
input_token_index = dict(
    [(char, i) for i, char in enumerate(input_characters)])
target_token_index = dict(
    [(char, i) for i, char in enumerate(target_characters)])

#---------------------------------------------------------------------------#

#--------------------------------------#
#   改變數據集的格式
#--------------------------------------#
encoder_input_data = np.zeros(
    (len(input_texts), max_encoder_seq_length, num_encoder_tokens),
    dtype='float32')
decoder_input_data = np.zeros(
    (len(input_texts), max_decoder_seq_length, num_decoder_tokens),
    dtype='float32')
decoder_target_data = np.zeros(
    (len(input_texts), max_decoder_seq_length, num_decoder_tokens),
    dtype='float32')


for i, (input_text, target_text) in enumerate(zip(input_texts, target_texts)):
    # 爲末尾加上" "空格
    for t, char in enumerate(input_text):
        encoder_input_data[i, t, input_token_index[char]] = 1.
    encoder_input_data[i, t + 1:, input_token_index[' ']] = 1.
    # 相當於前一個內容的識別結果,作爲輸入,傳入到解碼網絡中
    for t, char in enumerate(target_text):
        decoder_input_data[i, t, target_token_index[char]] = 1.
        if t > 0:
            # decoder_target_data不包括第一個tab
            decoder_target_data[i, t - 1, target_token_index[char]] = 1.
    decoder_input_data[i, t + 1:, target_token_index[' ']] = 1.
    decoder_target_data[i, t:, target_token_index[' ']] = 1.
#---------------------------------------------------------------------------#


encoder_inputs = Input(shape=(None, num_encoder_tokens))

x_encoder, _, _ = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)
x_encoder, state_h, state_c = CuDNNLSTM(latent_dim,return_sequences=True, return_state=True)(encoder_inputs)
x_encoder = Dropout(0.5)(x_encoder)

decoder_inputs = Input(shape=(None, num_decoder_tokens))

x_decoder = CuDNNLSTM(latent_dim,return_sequences=True)(decoder_inputs)
x_decoder = Dropout(0.5)(x_decoder)
# Attention
attention = Dot(axes=[2, 2])([x_decoder, x_encoder])
attention = Activation('softmax')(attention)

context = Dot(axes=[2, 1])([attention, x_encoder])
decoder_combined_context = Concatenate(axis=-1)([context, x_decoder])
x_decoder = CuDNNLSTM(int(latent_dim/2),return_sequences=True)(decoder_combined_context)
x_decoder = Dropout(0.5)(x_decoder)
# Output
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(x_decoder)

model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
model.summary()
# Run training
model.compile(optimizer='adam', loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit([encoder_input_data, decoder_input_data], decoder_target_data,
          batch_size=batch_size,
          epochs=epochs,
          validation_split=0.2)
# Save model
model.save('cnn_s2s.h5')
# 求字符到序號的映射
reverse_input_char_index = dict(
    (i, char) for char, i in input_token_index.items())
reverse_target_char_index = dict(
    (i, char) for char, i in target_token_index.items())


index = np.random.randint(0,9999,100)
in_encoder = encoder_input_data[index]
in_decoder = np.zeros(
    (len(in_encoder), max_decoder_seq_length, num_decoder_tokens),
    dtype='float32')
in_decoder[:, 0, target_token_index["\t"]] = 1

for i in range(max_decoder_seq_length - 1):
    predict = model.predict([in_encoder, in_decoder])
    predict = predict.argmax(axis=-1)
    predict_ = predict[:, i].ravel().tolist()
    for j, x in enumerate(predict_):
        in_decoder[j, i + 1, x] = 1

for seq_index in range(100):
    output_seq = predict[seq_index, :].ravel().tolist()
    decoded_sentence = ""
    for x in output_seq:
        if reverse_target_char_index[x] == "\n":
            break
        else:
            decoded_sentence+=reverse_target_char_index[x]
    print('-')
    print('Input sentence:', input_texts[index[seq_index]])
    print('Decoded sentence:', decoded_sentence)

實現效果:

Input sentence: It looks good.
Decoded sentence: Ça a m'esprisé.
-
Input sentence: You're young.
Decoded sentence: Vous êtes malins.
-
Input sentence: How weird!
Decoded sentence: Comment allez !
-
Input sentence: I want to live.
Decoded sentence: Je veux un la.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章