这是用户在 2024-4-15 24:13 为 http://localhost:8888/notebooks/redownload/C4W1_Assignment/Files/tf/C4W1_Assignment.ipynb 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Assignment 1: Neural Machine Translation


欢迎来到课程 4 的第一个作业。在这里,您将使用长短期记忆 (LSTM) 网络并注意构建英语到葡萄牙语的神经机器翻译 (NMT) 模型。机器翻译是自然语言处理中的一项重要任务,不仅可用于将一种语言翻译成另一种语言,还可用于词义消歧(例如确定“银行”一词是指金融银行还是指河边的土地) 。仅使用带有 LSTM 的循环神经网络 (RNN) 来实现这一点可以适用于短到中等长度的句子,但可能会导致非常长的序列梯度消失。为了解决这个问题,您将添加一个注意机制,以允许解码器访问输入句子的所有相关部分,无论其长度如何。通过完成此作业,您将:


  • 专注地实现编码器-解码器系统

  • 使用 Tensorflow 从头开始​​构建 NMT 模型

  • 使用贪婪和最小贝叶斯风险 (MBR) 解码生成翻译

 目录 ¶

In [1]:
In [2]:

1. Data Preparation


文本预处理位已经处理完毕(如果您对此感兴趣,请务必检查 utils.py 文件)。执行的步骤可以总结为:


  • 从文本文件中读取原始数据

  • 清理数据(使用小写字母、在标点符号周围添加空格、修剪空格等)

  • 将其分为训练集和验证集

  • 为每个句子添加句首和句尾标记
  • Tokenizing the sentences

  • 从标记化句子中创建 Tensorflow 数据集


花点时间检查一下原始句子:

In [3]:
English (to translate) sentence:

No matter how much you try to convince people that chocolate is vanilla, it'll still be chocolate, even though you may manage to convince yourself and a few others that it's vanilla.

Portuguese (translation) sentence:

Não importa o quanto você tenta convencer os outros de que chocolate é baunilha, ele ainda será chocolate, mesmo que você possa convencer a si mesmo e poucos outros de que é baunilha.
In [4]:


原始句子没有多大用处,因此删除它们以节省内存:

In [5]:


请注意,您从 utils.py 导入了 english_vectorizerportuguese_vectorizer 。这些是使用 tf.keras.layers.TextVectorization 创建的,它们提供了有趣的功能,例如可视化词汇表以及将文本转换为标记化 id 的方法,反之亦然。事实上,您可以检查两种语言词汇表的前十个单词:

In [6]:
First 10 words of the english vocabulary:

['', '[UNK]', '[SOS]', '[EOS]', '.', 'tom', 'i', 'to', 'you', 'the']

First 10 words of the portuguese vocabulary:

['', '[UNK]', '[SOS]', '[EOS]', '.', 'tom', 'que', 'o', 'nao', 'eu']


请注意,前 4 个字是为特殊字保留的。按顺序,这些是:

  •  空字符串

  • 表示未知单词的特殊标记

  • 表示句子开头的特殊标记

  • 表示句子结尾的特殊标记


您可以使用 vocabulary_size 方法查看词汇表中有多少个单词:

In [7]:
Portuguese vocabulary is made up of 12000 words
English vocabulary is made up of 12000 words


您可以定义 tf.keras.layers.StringLookup 对象来帮助您从单词映射到 id,反之亦然。对葡萄牙语词汇执行此操作,因为稍后当您从模型中解码预测时这将很有用:

In [8]:


尝试使用特殊标记和随机单词:

In [9]:
The id for the [UNK] token is 1
The id for the [SOS] token is 2
The id for the [EOS] token is 3
The id for baunilha (vanilla) is 7079


最后看一下将输入神经网络的数据是什么样子的。 train_dataval_data 都是 tf.data.Dataset 类型,并且已按 64 个示例的批次进行排列。要从 tf 数据集中获取第一批,您可以使用 take 方法。要从批次中获取第一个示例,您可以对张量进行切片并使用 numpy 方法进行更好的打印:

In [10]:
Tokenized english sentence:
[   2  210    9  146  123   38    9 1672    4    3    0    0    0    0]


Tokenized portuguese sentence (shifted to the right):
[   2 1085    7  128   11  389   37 2038    4    0    0    0    0    0
    0]


Tokenized portuguese sentence:
[1085    7  128   11  389   37 2038    4    3    0    0    0    0    0
    0]


In [11]:


有几个重要的细节需要注意。


  • 填充已应用于张量,且使用的值为 0

  • 每个示例由 3 个不同的张量组成:

    • 要翻译的句子

    • 右移翻译
    • The translation


前两个可以视为特征,第三个可以视为目标。通过这样做,您的模型可以执行教师强制,正如您在讲座中看到的那样。


现在是时候开始编码了!


2. 带注意力的 NMT 模型 ¶


您将构建的模型使用编码器-解码器架构。该循环神经网络 (RNN) 在其编码器中接收句子的标记化版本,然后将其传递到解码器进行翻译。正如讲座中提到的,仅使用 LSTM 的常规序列到序列模型对于中短句子会有效,但对于较长的句子就会开始退化。您可以将其想象如下图所示,其中输入句子的所有上下文都被压缩为传递到解码器块的一个向量。您可以看到这对于很长的句子(例如 100 个标记或更多)来说是一个问题,因为输入的第一部分的上下文对传递给解码器的最终向量影响很小。


向该模型添加注意力层可以让解码器访问输入句子的所有部分,从而避免此问题。为了说明这一点,我们只使用 4 个单词的输入句子,如下所示。请记住,编码器的每个时间步都会产生隐藏状态(由橙色矩形表示)。这些都被传递到注意力层,并且根据解码器的当前激活(即隐藏状态),每个层都会获得一个分数。例如,让我们考虑下图,其中已经做出了第一个预测“como”。为了产生下一个预测,注意力层将首先接收所有编码器隐藏状态(即橙色矩形)以及生成单词“como”时的解码器隐藏状态(即第一个绿色矩形)。有了这些信息,它将对每个编码器隐藏状态进行评分,以了解解码器应该关注哪个隐藏状态来生成下一个单词。作为训练的结果,模型可能已经了解到它应该与第二个编码器隐藏状态对齐,并随后为单词“você”分配高概率。如果我们使用贪婪解码,我们将输出所述单词作为下一个符号,然后重新启动过程以产生下一个单词,直到我们达到句尾预测。


实现注意力的方法有多种,我们将用于此任务的方法是缩放点积注意力,其形式如下:

Attention(Q,K,V)=softmax(QKTdk)V


您将在下周更深入地研究这个方程,但现在,您可以将其视为使用查询 (Q) 和键 (K) 计算分数,然后乘以值 (V) 以获取上下文向量解码器的特定时间步长。该上下文向量被馈送到解码器 RNN,以获得下一个预测单词的一组概率。除以键维度的平方根 ( dk ) 是为了提高模型性能,下周您还将了解更多相关信息。对于我们的机器翻译应用程序,编码器激活(即编码器隐藏状态)将是键和值,而解码器激活(即解码器隐藏状态)将是查询。


您将在接下来的部分中看到,只需几行代码即可实现这种复杂的架构和机制。


首先,您将定义两个重要的全局变量:


  • 词汇量的大小

  • LSTM 层中的单元数量(所有 LSTM 层将使用相同的数量)


在此作业中,英语和葡萄牙语的词汇量相同。因此,我们在整个笔记本中使用单个常量 VOCAB_SIZE。虽然在其他设置中,词汇量可能会有所不同,但我们的作业中并非如此。

In [12]:

Exercise 1 - Encoder


您的第一个练习是对神经网络的编码器部分进行编码。为此,请完成下面的 Encoder 类。请注意,在构造函数( __init__ 方法)中,您需要定义编码器的所有子层,然后在前向传递期间使用这些子层( call 方法)。


编码器由以下层组成:


  • 嵌入。对于该层,您需要定义适当的 input_dimoutput_dim 并让它知道您正在使用“0”作为填充,这可以通过使用 < 的适当值来完成b2> 参数。

  • 双向 LSTM。在 TF 中,您可以为类似 RNN 的层实现双向行为。这部分已经处理完毕,但您需要指定适当的图层类型及其参数。特别是,您需要设置适当的单元数,并确保 LSTM 返回完整序列而不仅仅是最后的输出,这可以通过使用 return_sequences 参数的适当值来完成。


您需要使用 TF 函数式 API 的语法来定义前向传播。这意味着您可以将函数调用链接在一起来定义您的网络,如下所示:

encoder_input = keras.Input(shape=(28, 28, 1), name="original_img")
x = layers.Conv2D(16, 3, activation="relu")(encoder_input)
x = layers.MaxPooling2D(3)(x)
x = layers.Conv2D(16, 3, activation="relu")(x)
encoder_output = layers.GlobalMaxPooling2D()(x)
In [13]:
In [14]:
Tensor of sentences in english has shape: (64, 14)

Encoder output has shape: (64, 14, 256)
 预期输出 ¶
Tensor of sentences in english has shape: (64, 14)

Encoder output has shape: (64, 14, 256)
In [15]:
 All tests passed!


练习 2 - 交叉注意力 ¶


您的下一个练习是编写将在原始句子和翻译之间执行交叉关注的层。为此,请完成下面的 CrossAttention 类。请注意,在构造函数( __init__ 方法)中,您需要定义所有子层,然后在前向传递期间使用这些子层( call 方法)。对于这种特殊情况,其中一些位已经得到处理。


交叉注意力由以下几层组成:


  • 多头注意力。对于这一层,您需要定义适当的 key_dim ,它是键和查询张量的大小。您还需要将头数设置为 1,因为您没有实现多头注意力,而是实现两个张量之间的注意力。该层优于 Attention 的原因是它在前向传递过程中允许更简单的代码。


有几点需要注意:


  • 您需要一种方法来传递注意力的输出以及向右移动的翻译(因为这种交叉注意力发生在解码器端)。为此,您将使用“添加”图层,以便保留原始尺寸,如果您使用“连接”图层之类的图层,则不会发生这种情况。

  • 还通过使用 LayerNormalization 层执行层归一化,以提高网络的稳定性。

  • 您无需担心最后的步骤,因为这些步骤已经解决。
In [16]:
In [17]:
Tensor of contexts has shape: (64, 14, 256)
Tensor of translations has shape: (64, 15, 256)
Tensor of attention scores has shape: (64, 15, 256)
 预期输出 ¶
Tensor of contexts has shape: (64, 14, 256)
Tensor of translations has shape: (64, 15, 256)
Tensor of attention scores has shape: (64, 15, 256)
In [18]:
 All tests passed!


练习 3 - 解码器 ¶


现在,您将通过完成下面的 Decoder 类来实现神经网络的解码器部分。请注意,在构造函数( __init__ 方法)中,您需要定义解码器的所有子层,然后在前向传递期间使用这些子层( call 方法)。


解码器由以下层组成:


  • 嵌入。对于该层,您需要定义适当的 input_dimoutput_dim 并让它知道您正在使用“0”作为填充,这可以通过使用 < 的适当值来完成b2> 参数。

  • 预注意力 LSTM。与在编码器中使用双向 LSTM 不同,这里您将使用普通 LSTM。不要忘记设置适当的单元数,并确保 LSTM 返回完整序列而不仅仅是最后的输出,这可以通过使用 return_sequences 参数的适当值来完成。该层返回状态非常重要,因为推理需要这,因此请确保相应地设置 return_state 参数。请注意,LSTM 层以名为 memory_statecarry_state 的两个张量的元组形式返回状态,但是这些名称已更改,以更好地反映您在 hidden_state

  • 注意力层在要翻译的句子和右移翻译之间执行交叉注意力。这里您需要使用您在上一个练习中定义的 CrossAttention 图层。

  • 后注意力 LSTM。另一个 LSTM 层。对于这个,你不需要它来返回状态。

  • 最后是密集层。该单元的单位数量应与词汇表的大小相同,因为您希望它计算词汇表中每个可能单词的逻辑。确保为此使用 logsoftmax 激活函数,您可以通过 tf.nn.log_softmax 获得该函数。
In [19]:
In [20]:
Tensor of contexts has shape: (64, 14, 256)
Tensor of right-shifted translations has shape: (64, 15)
Tensor of logits has shape: (64, 15, 12000)
 预期输出 ¶
Tensor of contexts has shape: (64, 14, 256)
Tensor of right-shifted translations has shape: (64, 15)
Tensor of logits has shape: (64, 15, 12000)
In [21]:
 All tests passed!

Exercise 4 - Translator


现在,您必须将之前编码的所有层组合到一个实际模型中。为此,请完成下面的 Translator 类。请注意,与从 tf.keras.layers.Layer 继承的 Encoder 和 Decoder 类不同,Translator 类从 tf.keras.Model 继承。


请记住, train_data 将生成一个元组,其中包含要翻译的句子和右移的翻译,这是模型的“特征”。这意味着网络的输入将是包含上下文和目标的元组。

In [22]: