為了更好的於都本篇文章,推薦大家需要準備:

  • Tensorflow 2.0 環境
  • 閱讀 Sequence to Sequence Learning 論文: 論文鏈接
  • 閱讀 Attention 相關論文: 論文鏈接

如果大家準備好了,那我們就開始吧!

1. 概述

我們的目標非常簡單:我們將使用一個非常簡單的數據集(僅有20個句子)和經典的Seq2Seq模型,應用TensorFlow2.0來訓練。

我們將通過以下步驟實現:

  • 數據準備
  • 沒有注意力機制的Seq2Seq
  • 有注意力機制的Seq2Seq

2. 數據準備

我們將使用20個英語 - 法語組(從原始數據集中提取)。 使用這麼小的數據集的原因是:

  • 更容易理解序列的標記方式
  • 訓練速度極快
  • 即使您不會說法語,也無需確認結果

首先,讓我們導入必要的包並查看數據:

import tensorflow as tf
import numpy as np
import unicodedata
import re
?
raw_data = (
(What a ridiculous concept!, Quel concept ridicule !),
(Your idea is not entirely crazy., "Votre idée nest pas complètement folle."),
("A mans worth lies in what he is.", "La valeur dun homme réside dans ce quil est."),
(What he did is very wrong., "Ce quil a fait est très mal."),
("All three of you need to do that.", "Vous avez besoin de faire cela, tous les trois."),
("Are you giving me another chance?", "Me donnez-vous une autre chance ?"),
("Both Tom and Mary work as models.", "Tom et Mary travaillent tous les deux comme mannequins."),
("Can I have a few minutes, please?", "Puis-je avoir quelques minutes, je vous prie ?"),
("Could you close the door, please?", "Pourriez-vous fermer la porte, sil vous pla?t ?"),
("Did you plant pumpkins this year?", "Cette année, avez-vous planté des citrouilles??"),
("Do you ever study in the library?", "Est-ce que vous étudiez à la bibliothèque des fois ?"),
("Dont be deceived by appearances.", "Ne vous laissez pas abuser par les apparences."),
("Excuse me. Can you speak English?", "Je vous prie de mexcuser ! Savez-vous parler anglais ?"),
("Few people know the true meaning.", "Peu de gens savent ce que cela veut réellement dire."),
("Germany produced many scientists.", "LAllemagne a produit beaucoup de scientifiques."),
("Guess whose birthday it is today.", "Devine de qui cest lanniversaire, aujourdhui !"),
("He acted like he owned the place.", "Il sest comporté comme sil possédait lendroit."),
("Honesty will pay in the long run.", "Lhonnêteté paye à la longue."),
("How do we know this isnt a trap?", "Comment savez-vous quil ne sagit pas dun piège ?"),
("I cant believe youre giving up.", "Je narrive pas à croire que vous abandonniez."),
)

如所見,數據是一個元組列表,其中每個元組包含一個英語句子和一個法語句子。

接下來,我們需要稍微清理原始數據。 這種任務通常包括規範化字元串,過濾不需要的字元,在標點符號前添加空格等。大多數時候,你需要的是兩個函數,如下所示:

def unicode_to_ascii(s):
return .join(
c for c in unicodedata.normalize(NFD, s)
if unicodedata.category(c) != Mn)

def normalize_string(s):
s = unicode_to_ascii(s)
s = re.sub(r([!.?]), r 1, s)
s = re.sub(r[^a-zA-Z.!?]+, r , s)
s = re.sub(rs+, r , s)
return s

我們現在將數據拆分為兩個單獨的列表,每個列表包含自己的句子。 然後我們將應用上面的函數並添加兩個特殊標記:<start>和<end>:

raw_data_en, raw_data_fr = list(zip(*raw_data))
raw_data_en, raw_data_fr = list(raw_data_en), list(raw_data_fr)
?
raw_data_en = [normalize_string(data) for data in raw_data_en]
raw_data_fr_in = [<start> + normalize_string(data) for data in raw_data_fr]
raw_data_fr_out = [normalize_string(data) + <end> for data in raw_data_fr]

我需要在這裡詳細說明一下。 首先,我們來看看下圖:

Seq2Seq模型由兩個網路組成:編碼器和解碼器。 編碼器位於左側,僅需要源語言的序列作為輸入。

另一方面,解碼器需要兩種版本的目標語言序列,一種用於輸入,一種用於目標(Loss計算)。 解碼器本身通常被稱為語言模型。

從實驗中,我還發現最好不要將<start>和<end>標記添加到源序列中。 這樣做會使模型,尤其是後來的注意機制混淆,因為所有序列都以相同的標記開頭。

接下來,讓我們看看如何標記數據,即將原始字元串轉換為整數序列。 我們將使用Keras的文本標記化實用程序類:

en_tokenizer = tf.keras.preprocessing.text.Tokenizer(filters=)

注意filters參數。 默認情況下,Keras的Tokenizer會刪除所有標點符號,這不是我們想要的。 由於我們已經過濾掉了標點符號(!?),我們可以在此處將過濾器設置為空白。

標記化的關鍵部分是辭彙。 Keras的Tokenizer類附帶了一些方法。 由於我們的數據包含原始字元串,因此我們將使用名為fit_on_texts的數據。

en_tokenizer.fit_on_texts(raw_data_en)

tokenizer將創建自己的辭彙表以及轉換詞典。 我們一起看一看:

print(en_tokenizer.word_index)
?

{
.: 1, you: 2, ?: 3, the: 4, a: 5, is: 6, he: 7,
what: 8, in: 9, do: 10, can: 11, t: 12, did: 13, giving: 14
...
}

我們現在可以將原始英語句子轉換為整數序列:

data_en = en_tokenizer.texts_to_sequences(raw_data_en)

最後但並非最不重要的是,我們需要填充零,以便所有序列具有相同的長度。 否則,我們以後將無法創建tf.data.Dataset對象。

data_en = tf.keras.preprocessing.sequence.pad_sequences(data_en,
padding=post)

讓我們檢查一切是否正常:

print(data_en[:3])
?

[[ 8 5 21 22 23 0 0 0 0 0]
[24 25 6 26 27 28 1 0 0 0]
[ 5 29 30 31 32 9 8 7 6 1]]

繼續用法語句子做同樣的事情:

fr_tokenizer = tf.keras.preprocessing.text.Tokenizer(filters=)
?
# ATTENTION: always finish with fit_on_texts before moving on
fr_tokenizer.fit_on_texts(raw_data_fr_in)
fr_tokenizer.fit_on_texts(raw_data_fr_out)
?
data_fr_in = fr_tokenizer.texts_to_sequences(raw_data_fr_in)
data_fr_in = tf.keras.preprocessing.sequence.pad_sequences(data_fr_in,
padding=post)
?
data_fr_out = fr_tokenizer.texts_to_sequences(raw_data_fr_out)
data_fr_out = tf.keras.preprocessing.sequence.pad_sequences(data_fr_out,
padding=

我們可以在不同的語料庫上多次調用fit_on_texts,它會自動更新辭彙表。 在使用texts_to_sequences之前,請務必先記得fit_on_texts。

最後一步很簡單,我們只需要創建一個tf.data.Dataset的實例:

dataset = tf.data.Dataset.from_tensor_slices(
(data_en, data_fr_in, data_fr_out))
dataset = dataset.shuffle(20).batch(5)

到此我們已經準備好了數據!

3. 沒有注意力機制的Seq2Seq

但現在,我們可能已經知道注意機制是機器翻譯任務的「標準配置」。 但我首先實現沒有注意力機制的Seq2Seq作為baseline。

實驗過程中,我們會感受到:

  • 使用最新的TensorFlow2.0的tf.keras非常簡單
  • 能夠回答:為什麼需要注意力機制?

我們將從編碼器開始。 在編碼器內部,存在嵌入層和RNN層(可以是簡單RNN或LSTM或GRU)實驗中我們使用的是LSTM。 在每個前向傳遞中,它接收一批序列和初始狀態並返回輸出序列以及最終狀態:

class Encoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_size, lstm_size):
super(Encoder, self).__init__()
self.lstm_size = lstm_size
self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_size)
self.lstm = tf.keras.layers.LSTM(
lstm_size, return_sequences=True, return_state=True)
?
def call(self, sequence, states):
embed = self.embedding(sequence)
output, state_h, state_c = self.lstm(embed, initial_state=states)
?
return output, state_h, state_c
?
def init_states(self, batch_size):
return (tf.zeros([batch_size, self.lstm_size]),
tf.zeros([batch_size, self.lstm_size]))

以下是數據在每一層的形狀變化。 我發現跟蹤數據的形狀非常有助於不犯低級錯誤:

我們已經完成了編碼器。 接下來,讓我們創建解碼器。 沒有注意機制的情況下,解碼器基本上與編碼器相同,只是它有一個Dense層將RNN的輸出映射到辭彙空間:

編碼器的最終狀態將充當解碼器的初始狀態。 這是Seq2Seq模型的語言模型和解碼器之間的差異。這就是我們需要創建的解碼器。 在繼續之前,讓我們檢查一下我們是否沒有犯任何錯誤:

EMBEDDING_SIZE = 32
LSTM_SIZE = 64
?
en_vocab_size = len(en_tokenizer.word_index) + 1
encoder = Encoder(en_vocab_size, EMBEDDING_SIZE, LSTM_SIZE)
?
fr_vocab_size = len(fr_tokenizer.word_index) + 1
decoder = Decoder(fr_vocab_size, EMBEDDING_SIZE, LSTM_SIZE)
?
source_input = tf.constant([[1, 3, 5, 7, 2, 0, 0, 0]])
initial_state = encoder.init_states(1)
encoder_output, en_state_h, en_state_c = encoder(source_input, initial_state)
?
target_input = tf.constant([[1, 4, 6, 9, 2, 0, 0]])
decoder_output, de_state_h, de_state_c = decoder(target_input, (en_state_h, en_state_c))
?
print(Source sequences, source_input.shape)
print(Encoder outputs, encoder_output.shape)
print(Encoder state_h, en_state_h.shape)
print(Encoder state_c, en_state_c.shape)
?
print(
Destination vocab size, fr_vocab_size)
print(Destination sequences, target_input.shape)
print(Decoder outputs, decoder_output.shape)
print(Decoder state_h, de_state_h.shape)
print(Decoder state_c, de_state_c.shape)
?

Source sequences (1, 8)
Encoder outputs (1, 8, 64)
Encoder state_h (1, 64)
Encoder state_c (1, 64)
Destination vocab size 107
Destination sequences (1, 7)
Decoder outputs (1, 7, 107)
Decoder state_h (1, 64)
Decoder state_c (1, 64)

太棒了! 一切都按預期工作。 接下來要做的是定義一個損失函數。 由於我們將零填充到序列中,因此在計算損失時不要考慮這些零:

def loss_func(targets, logits):
crossentropy = tf.keras.losses.SparseCategoricalCrossentropy(
from_logits=True)
mask = tf.math.logical_not(tf.math.equal(targets, 0))
mask = tf.cast(mask, dtype=tf.int64)
loss = crossentropy(targets, logits, sample_weight=mask)
?
return loss

我們還需要什麼? 是的,我們還沒有創建優化器!

optimizer = tf.keras.optimizers.Adam()

現在我們已經準備好創建訓練函數,我們執行前向傳遞,然後是反向傳播。 有兩件事需要記住:

  • 我們使用@tf.function裝飾器來推進靜態圖形計算(當你想調試時刪除它)
  • 計算需要放在tf.GradientTape()下以跟蹤Gradient

@tf.function
def train_step(source_seq, target_seq_in, target_seq_out, en_initial_states):
with tf.GradientTape() as tape:
en_outputs = encoder(source_seq, en_initial_states)
en_states = en_outputs[1:]
de_states = en_states
?
de_outputs = decoder(target_seq_in, de_states)
logits = de_outputs[0]
loss = loss_func(target_seq_out, logits)
?
variables = encoder.trainable_variables + decoder.trainable_variables
gradients = tape.gradient(loss, variables)
optimizer.apply_gradients(zip(gradients, variables))
?
return loss

在創建訓練循環之前,讓我們定義一個用於推理目的的方法。 它的作用基本上是前向傳遞,但是我們將輸入<start>標識,而不是目標序列。 每個下一個時間步都將最後一個時間步的輸出作為輸入,直到我們點擊<end>標記或輸出序列超過特定長度:

def predict():
test_source_text = raw_data_en[np.random.choice(len(raw_data_en))]
print(test_source_text)
test_source_seq = en_tokenizer.texts_to_sequences([test_source_text])
print(test_source_seq)
?
en_initial_states = encoder.init_states(1)
en_outputs = encoder(tf.constant(test_source_seq), en_initial_states)
?
de_input = tf.constant([[fr_tokenizer.word_index[<start>]]])
de_state_h, de_state_c = en_outputs[1:]
out_words = []
?
while True:
de_output, de_state_h, de_state_c = decoder(
de_input, (de_state_h, de_state_c))
de_input = tf.argmax(de_output, -1)
out_words.append(fr_tokenizer.index_word[de_input.numpy()[0][0]])
?
if out_words[-1] == <end> or len(out_words) >= 20:
break
?
print( .join(out_words))

最後,這裡是訓練循環。 在每個訓練批次,我們都會抓取批量數據還列印出損失值並查看模型在每個批次結束時的表現

NUM_EPOCHS = 250
BATCH_SIZE = 5
?
for e in range(NUM_EPOCHS):
en_initial_states = encoder.init_states(BATCH_SIZE)
?
for batch, (source_seq, target_seq_in, target_seq_out) in enumerate(dataset.take(-1)):
loss = train_step(source_seq, target_seq_in,
target_seq_out, en_initial_states)
?
print(Epoch {} Loss {:.4f}.format(e + 1, loss.numpy()))

try:
predict()
except Exception:
continue

讓我們監控訓練過程。使用GPU機器需要不到5分鐘的時間,以便在出現問題時立即知道。

起初,翻譯結果根本沒有任何意義。但逐漸地,該模型學會了製作更有意義的短語。最後,在第250個訓練批次,模型完全記住了所有20個句子。以下是我的結果:

...
?
Epoch 2 Loss 4.0025
Don t be deceived by appearances .
[[62, 12, 63, 64, 65, 66, 1]]
<end>
?
...
?
Epoch 82 Loss 0.6992
What a ridiculous concept !
[[8, 5, 21, 22, 23]]
valeur un reside ce qu il est . <end>
?
...
?
Epoch 164 Loss 0.2550
<start> He got up earlier than usual . <end>
[[1, 4, 40, 14, 41, 17, 42, 3, 2]]
il est leve plus tot que habitude . <end>
?
...
?
Epoch 131 Loss 0.2386
How do we know this isn t a trap ?
[[91, 10, 92, 20, 19, 93, 12, 5, 94, 3]]
comment savez vous qu il ne s agit pas d un piege ? <end>
?
...
?
Epoch 199 Loss 0.0675
Your idea is not entirely crazy .
[[24, 25, 6, 26, 27, 28, 1]]
votre idee est pas completement folle . <end>

顯然,我們可以確認模型實際上可以學習從小數據集轉換。 我還使用完整的英語 - 法語數據集訓練了相同的模型(對超參數進行了一些修改)。 結果中看出模型的翻譯是完全可以接受的。

Epoch 1 Batch 2500 Loss 0.4446
The recent shortage of coffee has given rise to a lot of problems .
[[5, 2093, 3679, 15, 344, 71, 871, 3317, 4, 7, 132, 15, 655, 1]]
c est une bonne idee . <end>
?
How are you today ?
[[45, 26, 3, 142, 6]]
le garcon a ete en retard . <end>
?
Epoch 4 Batch 2500 Loss 0.2167
I believe I was wrong .
[[2, 151, 2, 23, 216, 1]]
je pensais que j etais un peu jeune . <end>
?
How are you today ?
[[45, 26, 3, 142, 6]]
tu y vas ! <end>
?
Epoch 8 Batch 2500 Loss 0.0824
You re supposed to help your friends when they re in trouble .
[[3, 33, 411, 4, 93, 29, 197, 83, 40, 33, 17, 464, 1]]
tu es supposee aider tes amis lorsqu ils ont des ennuis . <end>
?
How are you today ?
[[45, 26, 3, 142, 6]]
aujourd hui vous avez ete bonne . <end>
?
Epoch 14 Batch 2500 Loss 0.0350
I ve seen this picture before .
[[2, 55, 251, 18, 444, 156, 1]]
cette photo compte que j ai trouve . <end>
?
How are you today ?
[[45, 26, 3, 142, 6]]
tu es en train de partir aujourd hui ? <end>
?
Epoch 15 Batch 2500 Loss 0.0318
We all make mistakes right ?
[[22, 42, 104, 659, 107, 6]]
nous faisons tout de suite des ennuis . <end>
?
How are you today ?
[[45, 26, 3, 142, 6]]
tu es en train de dejeuner aujourd hui . <end>

小夥伴們,我們完成了第一次任務! 我們已成功創建了一個沒有注意機制的功能齊全的Seq2Seq模型。

在下一節中,我們將看到只需進行一些修改,我們就可以立即升級我們當前的模型,注意力機制

4. 注意力機制的Seq2Seq

現在,我們來談談注意力機制。 它是什麼,我們為什麼需要它?

如果我們只看數學方程,很難理解。 因此,讓我們改變我們的觀點,並將機器翻譯模型視為一個學習外語的過程。

說到學習一門新語言,我個人認為下面兩個是我們都要處理的最常 的問題:

  • 難以記住和處理冗長複雜的背景
  • 與你的母語在語法結構上有所不同

機器翻譯模型也面臨同樣的問題。 我給你舉個例子。 下面我有一個英文句子:

I just want to have a sister.

這是法語版:

Je veux juste avoir une soeur.

讓我們看看它們如何適合Seq2Seq模型。 我們很就會看到問題:

首先要注意的是編碼器的狀態只傳遞給解碼器的第一個節點。 因此,來自編碼器的信息將在下一個時間步驟變得越來越不相關。

第二個問題,我們可以看到英語句子中的短語只相當於法語中的veux juste(just = juste和want = veux)。 這將給解碼器解決問題帶來困難。

那麼,我們怎麼可能解決這些問題呢? 理想情況下,我們希望解碼器中的所有時間步都可以訪問編碼器的輸出。 這樣,解碼器將能夠學會部分地關注編碼器的輸出併產生更準確的翻譯。

所以現在我們知道注意機制如何運作以及我們為什麼需要它。從技術上講,我們需要提前知道兩個術語:隊列向量和上下文向量

  • 隊列向量

隊列向量是與源序列具有相同長度的向量,並且在解碼器的每個時間步長處計算。 它的每個值都是源序列中相應單詞的分數(或概率):

隊列向量的作用是將權重放在編碼器的輸出上或直觀地,它們告訴解碼器在每個時間步驟要關注什麼。

  • 上下文向量

上下文向量是我們用來計算解碼器最終輸出的內容。 它是編碼器輸出的加權平均值。 您可以看到我們可以通過計算對齊向量和編碼器輸出的點積來獲取上下文向量:

這就是注意力機制的祕密。 接下來,讓我們看看我們如何在Python中創建一個。 讓我們看一下公式,確切地知道我們需要做什麼。 以下是我們將如何計算隊列向量:

注意機制提出了三種評分函數:dot,general和concat:

我只會顯示一般評分函數的代碼。我們需要採用名為Wa的矩陣和編碼器輸出的點積。 什麼層可以做點積? 這是Dense層:

class LuongAttention(tf.keras.Model):
def __init__(self, rnn_size):
super(LuongAttention, self).__init__()
self.wa = tf.keras.layers.Dense(rnn_size)

接下來,我們將實施前向傳遞。請注意,這次我們必須傳入編碼器的輸出。首先要做的是計算分數。它是當前解碼器輸出和Dense層輸出的點積。

def call(self, decoder_output, encoder_output):
# Dot score: h_t (dot) Wa (dot) h_s
# encoder_output shape: (batch_size, max_len, rnn_size)
# decoder_output shape: (batch_size, 1, rnn_size)
# score will have shape: (batch_size, 1, max_len)
score = tf.matmul(decoder_output, self.wa(encoder_output), transpose_b=True)

然後我們可以通過簡單地應用softmax函數來計算對齊向量:

# alignment vector a_t
alignment = tf.nn.softmax(score, axis=2)

最後我們將計算上下文向量。 它是編碼器輸出的加權平均值,這是表示對齊矢量和編碼器輸出的點積的另一種方式:

# context vector c_t is the average sum of encoder output
context = tf.matmul(alignment, encoder_output)
?
return context, alignment

我們已經完成了注意力的實施。 讓我們測試一下。

到現在為止還挺好。 接下來,我們將不得不進行一些更改,以便使用上面的注意機制。 讓我們從解碼器開始。

從上面,我們已經獲得了上下文和隊列向量。 隊列向量與解碼器無關(稍後我們將需要它用於可視化)。 好的,讓我們看看我們將如何使用上下文向量:

讓我們解釋那些公式: 在每個時間步t,我們將連接上下文向量和(RNN單元的)當前輸出以形成新的輸出向量。 然後我們繼續正常:將該向量轉換為辭彙空間以用於最終輸出。

為了應用這些更改,首先,我們需要在創建解碼器時創建一個注意對象:

class Decoder(tf.keras.Model):
def __init__(self, vocab_size, embedding_size, rnn_size):
super(Decoder, self).__init__()

# Create a LuongAttention object
self.attention = LuongAttention(rnn_size)
?
self.rnn_size = rnn_size
self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_size)
self.lstm = tf.keras.layers.LSTM(
rnn_size, return_sequences=True, return_state=True)

接下來,我們需要定義兩個Dense層,正如我們在上面的等式3中所見,有兩個矩陣分別稱為Wc和Ws。 請記住,第一個Dense圖層將使用tanh激活功能。

self.wc = tf.keras.layers.Dense(rnn_size, activation=tanh)
self.ws = tf.keras.layers.Dense(vocab_size)

我們已經完成了啟動。 接下來,讓我們將這些更改應用於正向傳遞,即調用方法。 由於我們在每個時間步都使用注意機製做一些事情,讓我們記住解碼器的輸入序列現在是一批單字序列。

我們將從計算嵌入向量開始,並從RNN單元獲得輸出。 請注意,我們確實需要將編碼器的輸出添加到參數:

ef call(self, sequence, state, encoder_output):
# Remember that the input to the decoder
# is now a batch of one-word sequences,
# which means that its shape is (batch_size, 1)
embed = self.embedding(sequence)

# Therefore, the lstm_out has shape (batch_size, 1, rnn_size)
lstm_out, state_h, state_c = self.lstm(embed, initial_state=state)

我們現在需要一些注意力機制。 讓我們使用解碼器的輸出和編碼器的輸入來獲取上下文和隊列向量:

Use self.attention to compute the context and alignment vectors
# context vectors shape: (batch_size, 1, rnn_size)
# alignment vectors shape: (batch_size, 1, source_length)
context, alignment = self.attention(lstm_out, encoder_output)

在我們得到上下文向量之後,是時候完成與上面公式中所寫的完全相同的操作了。我們將組合上下文向量和RNN輸出,然後將組合向量傳遞到兩個Dense層:

# Combine the context vector and the LSTM output
# Before combined, both have shape of (batch_size, 1, rnn_size),
# so lets squeeze the axis 1 first
# After combined, it will have shape of (batch_size, 2 * rnn_size)
lstm_out = tf.concat([tf.squeeze(context, 1), tf.squeeze(lstm_out, 1)], 1)

# lstm_out now has shape (batch_size, rnn_size)
lstm_out = self.wc(lstm_out)

# Finally, it is converted back to vocabulary space: (batch_size, vocab_size)
logits = self.ws(lstm_out)
?
return logits, state_h, state_c, alignment

我們已經完成了所有重大變化。 接下來,讓我們修改train_step函數。 由於我們在解碼器方面一次處理每個時間步,因此我們需要為此明確創建一個循環:

@tf.function
def train_step(source_seq, target_seq_in, target_seq_out, en_initial_states):
loss = 0
with tf.GradientTape() as tape:
en_outputs = encoder(source_seq, en_initial_states)
en_states = en_outputs[1:]
de_state_h, de_state_c = en_states

# We need to create a loop to iterate through the target sequences
for i in range(target_seq_out.shape[1]):
# Input to the decoder must have shape of (batch_size, length)
# so we need to expand one dimension
decoder_in = tf.expand_dims(target_seq_in[:, i], 1)
logit, de_state_h, de_state_c, _ = decoder(
decoder_in, (de_state_h, de_state_c), en_outputs[0])

# The loss is now accumulated through the whole batch
loss += loss_func(target_seq_out[:, i], logit)
?
variables = encoder.trainable_variables + decoder.trainable_variables
gradients = tape.gradient(loss, variables)
optimizer.apply_gradients(zip(gradients, variables))
?
return loss / target_seq_out.shape[1]

讓我們對預測函數做同樣的事情。 我們還需要獲取源序列,翻譯序列和隊列向量以實現可視化目的:

def predict(test_source_text=None):
if test_source_text is None:
test_source_text = raw_data_en[np.random.choice(len(raw_data_en))]
print(test_source_text)
test_source_seq = en_tokenizer.texts_to_sequences([test_source_text])
print(test_source_seq)
?
en_initial_states = encoder.init_states(1)
en_outputs = encoder(tf.constant(test_source_seq), en_initial_states)
?
de_input = tf.constant([[fr_tokenizer.word_index[<start>]]])
de_state_h, de_state_c = en_outputs[1:]
out_words = []
alignments = []
?
while True:
de_output, de_state_h, de_state_c, alignment = decoder(
de_input, (de_state_h, de_state_c), en_outputs[0])
de_input = tf.expand_dims(tf.argmax(de_output, -1), 0)
out_words.append(fr_tokenizer.index_word[de_input.numpy()[0][0]])

alignments.append(alignment.numpy())
?
if out_words[-1] == <end> or len(out_words) >= 20:
break
?
print( .join(out_words))
return np.array(alignments), test_source_text.split( ), out_words

就是這樣。 我們已經完成了attention式注意力的實施。 我們開始訓練吧!

好的,我們的模型已經完成了訓練。 是時候檢查一下了。 我們想知道在配備註意機制後它是否有所改善。

因此,為了更加可視化,我決定採用我們在開始時使用的20個示例,並比較兩個模型所做的翻譯。 結果如下(順序是源句 - >目標句 - > Seq2Seq - > Seq2Seq with attention):

<src> What a ridiculous concept!
<tar> Quel concept ridicule !
<vanilla> un point c est ce qui va lui faire .
<luong> ridicule !
?
<src> Your idea is not entirely crazy.
<tar> Votre idée nest pas complètement folle.
<vanilla> tu l etrange n est ce pas ?
<luong> votre idee n est pas completement folle .
?
<src> A mans worth lies in what he is.
<tar> La valeur dun homme réside dans ce quil est.
<vanilla> un homme la bas qui est en psychotherapie .
<luong> la valeur d un homme reside dans ce qu il est .
?
<src> What he did is very wrong.
<tar> Ce quil a fait est très mal.
<vanilla> il a bien fait un vrai nom de marie .
<luong> ce qu il a fait est tres mal .
?
<src> All three of you need to do that.
<tar> Vous avez besoin de faire cela, tous les trois.
<vanilla> vous avez decide de faire cela correctement .
<luong> de chacun d entre vous avez besoin de faire cela .
?
<src> Are you giving me another chance?
<tar> Me donnez-vous une autre chance ?
<vanilla> me donnes une autre chose qui me rappelle a qui je te parle .
<luong> vous conduis ?
?
<src> Both Tom and Mary work as models.
<tar> Tom et Mary travaillent tous les deux comme mannequins.
<vanilla> mange tom et mary le week end prochain .
<luong> tom et mary travaillent tous les deux comme mannequins .
?
<src> Can I have a few minutes, please?
<tar> Puis-je avoir quelques minutes, je vous prie ?
<vanilla> si quelques minutes je veux etre disposes a payer davantage .
<luong> quelques minutes s il vous plait ?
?
<src> Could you close the door, please?
<tar> Pourriez-vous fermer la porte, sil vous pla?t ?
<vanilla> tu pourrais a fermer la porte ?
<luong> la porte s il vous plait ?
?
<src> Did you plant pumpkins this year?
<tar> Cette année, avez-vous planté des citrouilles??
<vanilla> ce qui s est fait pendant un bon coup de feu .
<luong> cette annee d annee messieurs ?
?
<src> Do you ever study in the library?
<tar> Est-ce que vous étudiez à la bibliothèque des fois ?
<vanilla> tu etudies a l aeroport de tom ?
<luong> tu etudies a la bibliotheques des fois ?
?
<src> Dont be deceived by appearances.
<tar> Ne vous laissez pas abuser par les apparences.
<vanilla> on ne se montree a aucun arbre .
<luong> ne fais pas l apparences apparences apparences ?
?
<src> Excuse me. Can you speak English?
<tar> Je vous prie de mexcuser ! Savez-vous parler anglais ?
<vanilla> tu pourrais me parler devant toi dans tes devoirs .
<luong> vous pouvez parler anglais ?
?
<src> Few people know the true meaning.
<tar> Peu de gens savent ce que cela veut réellement dire.
<vanilla> si tous les gens qui lui apprecient est rendu compte qu elle .
<luong> leurs gens connaitre le plus grand signification .
?
<src> Germany produced many scientists.
<tar> LAllemagne a produit beaucoup de scientifiques.
<vanilla> l un de l autre pour les leurs manieres .
<luong> l allemagne a effectue beaucoup de scientifiques .
?
<src> Guess whose birthday it is today.
<tar> Devine de qui cest lanniversaire, aujourdhui !
<vanilla> l autre de dieu vraiment qu est marie .
<luong> a qui l anniversaire est aujourd hui !
?
<src> He acted like he owned the place.
<tar> Il sest comporté comme sil possédait lendroit.
<vanilla> il y a qu elle soit facile de traiter .
<luong> il est dans la resistance cardiaque pour laquelle il possedait l endroit .
?
<src> Honesty will pay in the long run.
<tar> Lhonnêteté paye à la longue.
<vanilla> l information demanda aux changements environnementaux .
<luong> la meteo possible .
?
<src> How do we know this isnt a trap?
<tar> Comment savez-vous quil ne sagit pas dun piège ?
<vanilla> que dites simplement que nous avons fait une difference culturelle options ?
<luong> ceci est un piege ?
?
<src> I cant believe youre giving up.
<tar> Je narrive pas à croire que vous abandonniez.
<vanilla> je vous prie de ne pas avoir demande a votre sujet .
<luong> ce matin je ne peux pas croire

我可以通過感覺帶注意機制的Seq2Seq模型比普通的Seq2Seq做出更好的翻譯。 但可能需要計算BLEU分數以獲得更準確的評估指標。

無論如何,使用注意機制的有趣之處在於我們可以在進行翻譯時可視化模型注意的位置。 我們來看看下面的動圖:

最後,讓我們回顧一下今天我們取得的成績:

  • 我們使用Tensorflow 2.0從頭開始實現了序列到序列模型
  • 我們也知道注意機制如何運作並實現了注意力機制

到目前為止,我們所做的工作將有助於建立一個強大的基礎,並可作為您下一個機器翻譯/聊天機器人項目的基準。保持良好的工作,積極進行新的實驗。

像往常一樣,您可以在github中找到重現上述結果的所有源代碼:代碼鏈接:

原文鏈接:

大魚AI

一個專註於 AI 的有趣公眾號


推薦閱讀:
相關文章