为了更好的于都本篇文章,推荐大家需要准备:

  • 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 的有趣公众号


推荐阅读:
相关文章