这篇文章的主题是:通俗易懂的把 Transformer 内部原理理清
这是我 通俗易懂的 Transformer 入门文章 系列的第二篇文章。在第一篇文章中,我们了解了 Transformer 的功能、使用方式、整体架构及其优势。通俗易懂的 Transformer 入门文章(第一部分):功能概述
在本文中,我们将深入底层,详细研究它的具体工作原理。我们会看到数据如何通过实际的矩阵表示与形状在系统中流动,并理解每个阶段所执行的计算。
架构概览
正如我们在第一部分中所见,该架构的主要组件包括:

编码器和解码器的数据输入部分包含:
-
嵌入层(Embedding layer)
-
位置编码层(Position Encoding layer)
编码器栈由多个编码器组成。每个编码器包含:
-
多头注意力层(Multi-Head Attention layer)
-
前馈层(Feed-forward layer)
解码器栈由多个解码器组成。每个解码器包含:
-
两个多头注意力层
-
前馈层
输出层(右上角)------ 生成最终输出,包含:
-
线性层(Linear layer)
-
Softmax 层
为了理解每个组件的作用,我们以机器翻译任务 为例,走一遍 Transformer 在训练阶段的工作流程。我们使用一组训练样本:
-
输入序列:英文句子 You are welcome
-
目标序列:西班牙语句子 De nada
嵌入与位置编码
和所有 NLP 模型一样,Transformer 需要为每个单词获取两类信息:
-
单词的含义
-
单词在序列中的位置
-
嵌入层负责编码单词的含义。
-
位置编码层负责表示单词的位置。
- Transformer 通过逐元素相加的方式将这两种编码融合。
嵌入(Embedding)
Transformer 拥有两个嵌入层 。输入序列被送入第一个嵌入层,该层称为输入嵌入层(Input Embedding)。

目标序列会先向右偏移一位 ,并在第一个位置插入起始标记(Start token),然后再送入第二个嵌入层。注意,在推理阶段我们没有目标序列,正如第一部分所学,我们会通过循环将输出序列送入这第二层。这也是它被称为 输出嵌入层(Output Embedding) 的原因。
文本序列会通过词表映射为数字形式的单词 ID 。随后,嵌入层将每个输入单词映射为嵌入向量,这是对单词语义更丰富的表示。

位置编码
由于 RNN 采用循环结构、按顺序逐个输入单词 ,它能隐式地感知每个单词的位置。
但 Transformer 不使用 RNN,序列中的所有单词都是并行输入 的。这是它相比 RNN 架构的主要优势,但也意味着位置信息会丢失 ,必须额外重新加入。
和两个嵌入层一样,Transformer 也有两个位置编码层 。位置编码的计算与输入序列无关 ,是一组只取决于序列最大长度的固定值。
例如:
-
第一个位置对应一个常量编码,表示第 1 位
-
第二个位置对应一个常量编码,表示第 2 位
-
依此类推
这些常量通过下面的公式计算,其中

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

换句话说,它交替使用正弦曲线和余弦曲线 :所有偶数索引 使用正弦值,所有奇数索引使用余弦值。
举个例子,如果我们对一个包含 40 个单词的序列进行编码,下图可以看到若干(单词位置,编码索引)组合对应的编码值。

蓝色曲线表示全部 40 个单词位置 在第 0 号索引 上的编码值,橙色曲线表示全部 40 个单词位置 在第 1 号索引上的编码值。其余索引值也会有类似的曲线。
矩阵维度
我们知道,深度学习模型会一次性处理一批训练样本 。嵌入层与位置编码层作用于代表一批序列样本的矩阵之上。
嵌入层接收形状为 (样本数,序列长度) 的单词 ID 矩阵,将每个单词 ID 编码为长度等于嵌入维度 的词向量,最终输出形状为 (样本数,序列长度,嵌入维度) 的矩阵。
位置编码使用的编码维度 与嵌入维度 相等,因此它会生成形状相同的矩阵,可以与嵌入矩阵直接相加。

由嵌入层和位置编码层生成的形状为 (样本数,序列长度,嵌入维度) 的矩阵,在数据流经编码器栈和解码器栈的整个过程中都会保持不变,直到最终被输出层重塑形状。
这让我们对 Transformer 中的三维矩阵维度有了直观认识。不过,为了简化可视化展示,从现在起我们将去掉第一个维度(样本数),只使用二维表示来描述单个样本。

输入嵌入层(Input Embedding)将输出送入编码器。同样,输出嵌入层(Output Embedding)将输出送入解码器。
编码器
编码器栈和解码器栈分别由若干个(通常是 6 个)编码器和解码器依次串联组成。

栈中的第一个编码器 接收来自嵌入层与位置编码 的输入。栈中的其他编码器 接收来自上一个编码器的输出作为输入。
编码器将其输入传入多头自注意力层 。自注意力的输出被传入前馈网络层 ,然后前馈层将其输出向上传递给下一个编码器。

自注意力层和前馈层这两个子层,外部都带有残差跳跃连接 ,之后再进行层归一化(Layer Normalization)。
最后一个编码器的输出,会被送入解码器栈中的每一个解码器,具体过程如下所述。
解码器
解码器的结构与编码器非常相似,但存在几处不同。
与编码器一样,栈中的第一个解码器 接收来自输出嵌入层和位置编码 的输入。栈中的其他解码器 接收来自上一个解码器的输入。
解码器先将输入传入多头自注意力层 。这一层的工作方式与编码器中的略有不同:它只能关注序列中已经出现的位置 。这是通过屏蔽未来位置实现的,我们稍后会详细讲解。

与编码器不同,解码器拥有第二个多头注意力层 ,称为编码器‑解码器注意力层 。编码器‑解码器注意力层的工作方式与自注意力类似,唯一的区别是它会融合两路输入:下方自注意力层的输出,以及编码器栈的输出。
自注意力的输出会被传入前馈网络层,然后前馈层再将输出向上传递给下一个解码器。
自注意力、编码器‑解码器注意力、前馈网络,这三个子层都带有残差跳跃连接,之后都会经过层归一化。
注意力机制
在第一部分中,我们讲过为什么注意力机制在处理序列时如此重要。在 Transformer 中,注意力主要用在三个地方:
-
编码器中的自注意力 ------ 输入序列关注自身
-
解码器中的自注意力 ------ 目标序列关注自身
-
解码器中的编码器‑解码器注意力 ------ 目标序列关注输入序列
注意力层接收三个形式为参数的输入,分别称为查询(Query)、键(Key)和值(Value)。
在编码器的自注意力中,编码器的输入会同时被送入Query、Key、Value这三个参数。

在解码器的自注意力层中,解码器的输入会被同时传入查询(Query)、键(Key)和值(Value) 这三个参数。
在解码器的编码器‑解码器注意力层中:
-
编码器栈中最后一个编码器 的输出被传入 值(Value)和键(Key)参数。
-
其下方自注意力(及层归一化)模块的输出被传入 查询(Query)参数。

多头注意力
Transformer 将每个注意力计算单元称为一个注意力头(Attention Head) ,并将多个注意力头并行重复 执行。这一结构被称为多头注意力(Multi-head Attention) 。它通过组合多个相似的注意力计算,让模型的分辨与建模能力更强。

查询(Query)、键(Key)和值(Value)分别通过独立的线性层 ,每层都有各自的权重,生成三个结果,分别称为 Q、K 和 V 。然后使用下面所示的注意力公式 将它们组合在一起,得到注意力分数(Attention Score)。

这里需要重点理解的是:Q、K、V 中都携带了序列里每个单词的编码表示 。注意力计算会将每个单词与序列中所有其他单词进行融合 ,因此最终的注意力分数会为序列中的每个单词编码出一个权重分值。
刚才在讲解解码器时,我们简单提到过掩码(Masking)。掩码也显示在上面的注意力结构图中。下面我们来看它的工作原理。
注意力掩码(Attention Masks)
在计算注意力分数时,注意力模块会执行一个掩码步骤。掩码主要有两个作用:
- 在编码器自注意力 和编码器‑解码器注意力 中:掩码用于将输入句子中填充(padding)位置 的注意力输出置为 0,确保填充部分不会对自注意力产生影响。(注:由于输入序列长度可能不同,和大多数 NLP 任务一样,会用填充标记补齐长度,以便向 Transformer 输入固定长度的向量。)

编码器‑解码器(Encoder-Decoder)注意力的原理也是如此。

在解码器自注意力中:掩码的作用是防止解码器在预测下一个单词时,提前 "偷看" 目标句子的后续内容。
解码器处理源语言序列,并利用它来预测目标语言序列的单词。在训练过程中,这是通过教师强制(Teacher Forcing)实现的,即把完整的目标序列作为解码器的输入。因此,在预测某个位置的单词时,解码器既可以看到该位置之前 的目标单词,也能看到该位置之后的目标单词。这会让解码器利用未来 "时间步" 的目标单词进行 "作弊"。
例如,在预测第 3 个单词时,解码器应该只能参考目标序列中的前 3 个输入单词,而不能看到第 4 个单词 "Ketan"。

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

在计算注意力分数时(参考之前展示计算过程的图),掩码会在 Softmax 之前直接作用于分子部分 。被屏蔽的元素(白色方块)会被设置为负无穷大,这样经过 Softmax 后,这些值就会变为 0。
生成输出
解码器栈中的最后一个解码器 将其输出传递给输出组件,由输出组件将其转换为最终的输出句子。
线性层(Linear layer) 将解码器向量投影为单词分数 :对句子中的每个位置,都为目标词表中的每个唯一单词分配一个分数值。例如,如果最终输出句子有 7 个单词,而西班牙语目标词表有 10000 个唯一单词,我们就会为这 7 个位置中的每一个位置生成 10000 个分数值。这些分数表示词表中每个单词出现在句子该位置的概率大小。
然后 Softmax 层 将这些分数转换为概率 (所有概率之和为 1.0)。在每个位置上,我们找到概率最高 的单词对应的索引,再将该索引映射回词表中对应的单词。这些单词就构成了 Transformer 的输出序列。

训练与损失函数
在训练过程中,我们使用交叉熵损失等损失函数,将模型生成的输出概率分布与目标序列进行对比。该概率分布表示每个单词出现在对应位置的概率。

假设我们的目标词表只包含四个单词。我们的目标是生成一个概率分布 ,使其与我们期望的目标序列 "De nada END" 相匹配。
这意味着:
-
第一个单词位置的概率分布中,"De" 的概率应为 1,词表中所有其他单词的概率为 0。
-
第二个和第三个单词位置的概率分布中,"nada" 和 "END" 的概率应分别为 1。
与往常一样,我们使用损失函数计算梯度 ,并通过反向传播训练 Transformer 模型。
总结
希望这篇内容能让你对 Transformer 在训练阶段 的内部运行过程有一个直观认识。正如我们在上一篇文章中讨论的,它在推理阶段会以循环方式运行,但大部分处理流程保持不变。
多头注意力模块是 Transformer 的核心能力所在。在下一篇文章中,我们将继续深入,真正拆解注意力机制的具体计算细节。
最后,如果你喜欢这篇文章,你可能也会喜欢我其他关于深度学习 的系列教程。用直白语言讲透 Transformer