这是我的 Transformer 系列文章中的第二篇。在第一篇文章中,我们了解了 Transformer 的功能、使用方法、高级架构及其优势。
在本文中,我们可以深入了解并详细研究它们的工作原理。我们将看到数据如何通过其实际的矩阵表示和形状在系统中流动,并了解每个阶段执行的计算。
架构概述
正如我们在第 1 部分中看到的,该架构的主要组件包括:

编码器和解码器的数据输入包含:
- 嵌入层
- 位置编码层
编码器堆栈包含多个编码器,每个编码器包含:
- 多头自注意力层
- 前馈层
解码器堆栈包含多个解码器,每个解码器包含:
- 两个多头注意力层
- 前馈层
输出(右上)—— 生成最终输出,包含:
- 线性层
- Softmax 层
为了理解每个组件的作用,让我们在训练 Transformer 解决翻译问题时,了解一下它的工作原理。我们将使用一个训练数据样本,该样本由一个输入序列(英文中的“You are welcome”)和一个目标序列(西班牙语中的“De nada”)组成。
嵌入和位置编码
与任何 NLP 模型一样,Transformer 需要了解每个单词的两个信息——单词的含义和它在序列中的位置。
- Embedding 层对单词的含义进行编码
- 位置编码层表示单词的位置
Transformer 通过添加这两种编码来将它们结合在一起。
嵌入
Transformer 有两个 Embedding 层。输入序列被送到第一个 Embedding 层,称为输入 Embedding。

将目标向右移动一个位置并在第一个位置插入起始标记后,目标序列被送到第二个嵌入层。请注意,在推理过程中,没有目标序列,我们将输出序列循环送到第二层,就像我们在第 1 部分中学到的那样。这就是为什么它被称为输出嵌入。
使用我们的词汇表将文本序列映射到数字单词 ID。然后,嵌入层将每个输入单词映射到嵌入向量中,这是该单词含义的更丰富的表示。

位置编码
由于 RNN 实现了按顺序输入每个单词的循环,因此它隐式地知道每个单词的位置。
然而,Transformers 不使用 RNN,序列中的所有单词都是并行输入的。这是它相对于 RNN 架构的主要优势,但这意味着位置信息会丢失,必须单独添加回来。
就像两个嵌入层一样,有两个位置编码层。位置编码独立于输入序列计算。这些是固定值,仅取决于序列的最大长度。例如,
- 第一项是常量代码,表示第一个位置
- 第二项是指示第二个位置的常量代码
- 等等。
这些常数使用以下公式计算:

- pos是单词在序列中的位置
- d_model是编码向量的长度(与嵌入向量相同)
- i是该向量的索引值。

换句话说,它将正弦曲线和余弦曲线交错,所有偶数索引都有正弦值,所有奇数索引都有余弦值。例如,如果我们对 40 个单词的序列进行编码,我们可以在下面看到一些(单词位置、encoding_index)组合的编码值。

蓝色曲线显示所有 40 个字位置的第 0 个索引的编码,橙色曲线显示所有 40 个字位置的第 1 个索引的编码。其余索引值将有类似的曲线。
矩阵维度
我们知道,深度学习模型一次处理一批训练样本。嵌入层和位置编码层对表示一批序列样本的矩阵进行操作。嵌入层采用 (样本,序列长度) 形状的单词 ID 矩阵。它将每个单词 ID 编码为一个长度为嵌入大小的单词向量,从而产生一个 (样本,序列长度,嵌入大小) 形状的输出矩阵。位置编码使用与嵌入大小相等的编码大小。因此,它会生成一个形状相似的矩阵,可以将其添加到嵌入矩阵中。

当数据流经编码器和解码器堆栈直到被最终的输出层重塑时,嵌入和位置编码层生成的(样本、序列长度、嵌入大小)形状会在整个 Transformer 中保留。
这让我们大致了解了 Transformer 中的 3D 矩阵维度。不过,为了简化可视化,从这里开始我们将删除第一个维度(用于样本),并使用 2D 表示单个样本。

输入嵌入将其输出发送到编码器。同样,输出嵌入将其输入到解码器。
编码器
编码器和解码器堆栈分别由多个(通常为六个)编码器和解码器组成,按顺序连接。

堆栈中的第一个编码器从嵌入和位置编码接收输入。堆栈中的其他编码器从前一个编码器接收输入。
编码器将其输入传递到多头自注意力层。自注意力输出被传递到前馈层,然后前馈层将其输出向上发送到下一个编码器。

自注意力层和前馈子层周围都有一个残差跳过连接,后面跟着一个层规范化。
最后一个编码器的输出被输入到解码器堆栈中的每个解码器中,如下所述。
解码器
解码器的结构与编码器非常相似,但有几个不同之处。
与编码器一样,堆栈中的第一个解码器从输出嵌入和位置编码接收输入。堆栈中的其他解码器从前一个解码器接收输入。
解码器将其输入传递到多头自注意力层,其操作方式与编码器略有不同,它只能关注序列中前面的位置,这是通过屏蔽未来位置来实现的。

与编码器不同,解码器有第二个多头注意力层,称为编码器-解码器注意力层。编码器-解码器注意力层的工作原理与自注意力层类似,不同之处在于它结合了两个输入源——其下方的自注意力层以及编码器堆栈的输出。
自注意力输出被传递到前馈层,然后将其输出向上发送到下一个解码器。
这些子层、自注意力、编码器-解码器注意力和前馈中的每一个,周围都有一个残差跳过连接,后面跟着一个层规范化。
注意力
在第 1 部分中,我们讨论了为什么 Attention 在处理序列时如此重要。在 Transformer 中,Attention 用于三个地方:
- 编码器中的自注意力——输入序列会关注自身
- 解码器中的自注意力——目标序列会关注自身
- 解码器中的编码器-解码器注意力——目标序列会关注输入序列
注意力层以三种参数的形式获取输入,即查询 (Query)、键 (Key) 和值 (Value)。
- 编码器中的自注意力中,编码器的输入会被传递给Query、Key、Value三个参数。

- 在解码器中的自注意力中,解码器的输入会被传递给Query、Key、Value三个参数。
- 在解码器中的编码器-解码器注意力中,堆栈中最后一个解码器的输出被传递给 Value 和 Key 参数。其下方的 Self-attention(和 Layer Norm)模块的输出被传递给 Query 参数。

多头注意力机制
Transformer 将每个 Attention 处理器称为 Attention Head,并并行重复多次,这被称为多头注意力。它通过组合多个类似的 Attention 计算,使其 Attention 具有更大的辨别能力。

查询、键和值分别通过单独的线性层,每个层都有自己的权重,产生三个结果,分别称为 Q、K 和 V。然后使用如下所示的注意力公式将它们组合在一起,以产生注意力分数。

这里需要注意的是,Q、K 和 V 值带有序列中每个单词的编码表示。然后,注意力计算将每个单词与序列中的每个其他单词组合在一起,这样注意力分数就会为序列中的每个单词编码一个分数。
注意力掩码
在计算注意力分数时,注意力模块会执行一个掩蔽步骤。掩蔽有两个目的:
在编码器自注意力和编码器-解码器注意力中:掩蔽用于将输入句子中有填充的部分归零,以确保填充不会对自注意力产生影响。(注意:由于输入序列的长度可能不同,因此它们会像在大多数 NLP 应用程序中一样使用填充标记进行扩展,以便可以将固定长度的向量输入到 Transformer。)

对于编码器-解码器注意力机制来说,情况也类似。

在解码器自注意力机制中:掩蔽的作用是防止解码器在预测下一个单词时“偷看”目标句子的其余部分。
解码器处理源序列中的单词,并使用它们来预测目标序列中的单词。在训练期间,这是通过 Teacher Forcing 完成的,其中完整的目标序列作为解码器的输入。因此,在预测某个位置的单词时,解码器可以使用该单词之前的目标单词以及该单词之后的目标单词。这允许解码器通过使用未来“时间步骤”中的目标单词来“作弊”。
例如,在预测“单词 3”时,解码器应该仅参考目标的前三个输入词,而不是第四个单词“ Ketan”。

因此,解码器会屏蔽掉序列中稍后出现的输入词。

计算注意力得分时(请参阅前面显示计算结果的图片),在 Softmax 之前对分子应用掩码。被掩码掉的元素(白色方块)设置为负无穷大,这样 Softmax 就会将这些值变为零。
生成输出
堆栈中的最后一个解码器将其输出传递给输出组件,输出组件将其转换为最终的输出句子。
线性层将解码器向量投影到单词分数中,目标词汇表中每个唯一单词在句子的每个位置都有一个分值。例如,如果我们的最终输出句子有 7 个单词,而目标西班牙语词汇表中有 10000 个唯一单词,我们会为这 7 个单词中的每一个生成 10000 个分值。分值表示词汇表中每个单词在句子的该位置出现的可能性。
Softmax 层随后将这些分数转换为概率(总和为 1.0)。在每个位置,我们找到概率最高的单词的索引,然后将该索引映射到词汇表中的相应单词。然后,这些单词形成 Transformer 的输出序列。

训练和损失函数
在训练过程中,我们使用损失函数(例如交叉熵损失)将生成的输出概率分布与目标序列进行比较。概率分布给出了每个单词出现在该位置的概率。

假设我们的目标词汇表只包含四个单词。我们的目标是生成一个与预期目标序列“De nada END”相匹配的概率分布。
这意味着第一个单词位置的概率分布中“De”的概率应该为 1,而词汇表中所有其他单词的概率为 0。类似地,“nada”和“END”在第二个和第三个单词位置的概率分别应该为 1。
与往常一样,损失用于计算梯度,以通过反向传播训练 Transformer。
结论
希望这能让您了解 Transformer 在训练过程中的内部情况。正如我们在上一篇文章中所讨论的,它在推理过程中循环运行,但大多数处理保持不变。
多头注意力模块是 Transformer 的强大之处。在下一篇文章中,我们将继续我们的旅程,更深入地了解注意力计算的细节。