前言
Transformer模型首次在Google的论文《Attention is All You Need》中提出,迅速成为自然语言处理领域的里程碑。为了方便开发者使用,Google开源了基于TensorFlow的Tensor2Tensor库,社区也贡献了PyTorch版本实现。本文将通过可视化的方式,逐步拆解Transformer的训练过程,即使你没有深厚的深度学习基础,也能理解其核心原理。
整体架构概览
首先将Transformer视为一个黑盒系统。在机器翻译任务中,它接收一种语言的句子作为输入,输出对应的目标语言翻译结果:

打开这个黑盒,可以看到Transformer由两大核心模块组成:编码器(Encoders)和解码器(Decoders)。

进一步剖析,编码器模块由6个结构相同的Encoder层堆叠而成,解码器模块同样包含6个Decoder层(这是论文中的标准配置)。

每个Encoder层的内部结构相同但权重独立,主要包含两个子层:

输入首先经过Self-Attention层,该层帮助编码器在处理每个单词时关注输入序列中的其他相关单词。Self-Attention的输出随后传入前馈神经网络(Feed Forward Neural Network)。虽然每个Encoder的前馈网络结构相同,但各自独立运作。
Decoder层也具有类似的结构,但在Self-Attention和前馈网络之间增加了Encoder-Decoder Attention层,用于关注输入序列中的关键信息(类似于seq2seq模型的注意力机制)。

张量流动过程
现在通过可视化方式观察数据在网络中的流动。首先进行词嵌入(Word Embedding)操作,将每个单词转换为512维的向量表示:

词嵌入只在最底层的Encoder中发生。对于所有Encoder层,输入都是一个向量列表,每个向量维度为512(最底层是词嵌入向量,其他层是前一层的输出)。列表的长度是可配置的超参数,通常设置为训练集中最长句子的长度。

这里揭示了Transformer的一个关键特性:Self-Attention层中各单词的输入输出存在依赖关系,而前馈网络层则不存在这种依赖,因此可以并行处理,大幅提升计算效率。
编码过程详解
如前所述,每个Encoder接收512维向量列表作为输入,将其传递给Self-Attention层处理,输出同样维度的向量列表,再送入前馈神经网络,最终将输出传递给下一个Encoder。

每个位置的单词首先经过Self-Attention层,然后通过结构相同但参数独立的前馈神经网络。
Self-Attention机制原理
Self-Attention是论文《Attention is All You Need》提出的核心创新。以下面的句子为例:
"The animal didn't cross the street because it was too tired"
这句话中的"it"指代什么?是"animal"还是"street"?对人类而言这很简单,但对算法来说并非易事。Self-Attention机制正是为了解决这类问题,通过关联"it"与"animal",让模型理解指代关系。
当模型处理每个单词时,Self-Attention允许它查看输入序列中的其他位置,寻找有助于更好编码当前单词的线索。

如图所示,第5层Encoder在编码"it"时,部分注意力集中在"animal"上,并通过加权方式影响"it"的编码表示。
Self-Attention计算细节
下面详细介绍Self-Attention的向量计算方法,随后展示矩阵实现方式。
第一步:从每个Encoder的输入向量创建三个新向量——Query向量、Key向量和Value向量。这些向量通过输入向量与三个训练矩阵相乘得到。
注意这些新向量的维度(64维)小于输入嵌入向量(512维)。维度缩小并非必须,这是架构设计选择,使得Multi-Head Attention的计算更高效。

将 [X_1] 乘以权重矩阵 [W^Q] 得到查询向量 [q_1],同理可得到每个单词的Key和Value向量。
Query、Key、Value这些概念较为抽象,但随着后续内容的展开会逐渐清晰。
第二步:计算注意力分数。假设我们正在计算第一个单词"Thinking"的Self-Attention,需要对输入句子的每个单词进行评分,分数决定了在编码当前位置单词时对其他位置的关注程度。
分数通过Query向量与Key向量的点积计算。处理第一个位置时,第一个分数是 [q_1] 和 [k_1] 的点积,第二个分数是 [q_1] 和 [k_2] 的点积。

第三步和第四步:将分数除以8(Key向量维度64的平方根,这使得训练过程中梯度更稳定),然后通过Softmax归一化,使所有分数为正且总和为1。

Softmax分数决定了每个单词在当前位置的表达程度。显然当前单词本身的分数最高,但有时关注其他相关单词也很重要。
第五步:将每个Value向量乘以Softmax分数。这样做的目的是保留关注单词的信息,削弱不相关单词的影响。
第六步:对加权后的Value向量求和,得到该位置Self-Attention层的输出。

至此完成了单词级别的Self-Attention计算,得到可以传递给前馈网络的向量。实际实现中采用矩阵运算以提高处理速度。
Self-Attention的矩阵计算
第一步是计算Query、Key和Value矩阵。将输入的词嵌入打包成矩阵X,分别乘以训练好的权重矩阵 [W^Q]、[W^K]、[W^V]。

矩阵X的每一行对应输入句子中的一个单词。图中为了简化展示,X的维度显示为4(实际为512),Q/K/V向量维度显示为3(实际为64)。
由于使用矩阵运算,可以将前面的步骤2到步骤6浓缩为一个公式来计算Self-Attention层的输出:

Multi-Head Attention机制
论文通过"Multi-Head Attention"进一步提升了Self-Attention的性能,主要体现在两个方面:
-
扩展模型关注不同位置的能力。在前面的例子中,虽然"it"的编码包含了其他单词的信息,但可能主要由自身主导。当翻译"The animal didn't cross the street because it was too tired"时,了解"it"指代"animal"非常有用。
-
提供多个表示子空间。Multi-Head Attention使用多组Query/Key/Value权重矩阵(Transformer使用8组,即8个attention heads)。每组矩阵随机初始化,训练后用于将输入嵌入投影到不同的表示子空间。

使用Multi-Head Attention时,为每个head维护独立的Q/K/V权重矩阵,产生8个不同的Z矩阵。

这带来一个问题:前馈网络期望接收单个矩阵(每个位置一个向量),而不是8个矩阵。如何处理?
解决方案是将8个矩阵拼接起来,然后乘以额外的权重矩阵 [W^O]:

下图展示了Multi-Head Self-Attention的完整计算流程:

回到之前的例子,看看"it"在不同attention head下的关注点:

编码"it"时,一个注意力头主要关注"animal",另一个关注"tired"。如果将所有注意力头的信息叠加显示,理解起来会更复杂:

位置编码
目前还缺少一个重要机制:如何表示输入序列中单词的顺序信息?
为了解决这个问题,Transformer为每个输入嵌入添加位置编码向量。这些位置编码向量遵循特定模式,帮助模型确定每个单词的位置,或者序列中不同单词之间的距离。将位置信息添加到词嵌入中,使得后续与Q/K/V向量的点积运算能够捕获位置关系。

假设嵌入维度为4,实际的位置编码如下图所示:

位置编码遵循什么规则?观察下图,每一行代表一个位置的编码向量。第一行是输入序列第一个词的位置编码,每行包含512个值,每个值在-1到1之间,用颜色深浅表示:

这是一个20个单词(行)、512维(列)的位置编码示例。可以看到中心位置被分为两半,左半部分由正弦函数生成,右半部分由余弦函数生成,然后拼接形成完整的位置编码向量。
位置编码的具体公式在论文第3.5节中描述,也可以查看Tensor2Tensor中的get_timing_signal_1d()实现。这不是唯一的位置编码方法,但它的优势在于能够扩展到未见过的序列长度。
残差连接与层归一化
Encoder中每个子层(Self-Attention和前馈网络)都有残差连接,并进行层归一化(Layer Normalization)处理:

深入观察其内部计算,可以将层级结构可视化为:

Decoder的子层也采用相同的结构。如果构建包含2个Encoder和2个Decoder的Transformer,整体架构如下:

解码器工作原理
介绍完Encoder的大部分概念后,基本可以理解Decoder的工作方式。现在详细探讨Decoder的计算原理。
当输入序列进入Encoder开始处理时,顶层Encoder的输出被转换为一组注意力向量K和V。这些向量被送入每个Decoder的Encoder-Decoder Attention层,帮助Decoder聚焦于输入序列的相关位置。

完成编码阶段后,开始解码。解码阶段的每个步骤输出一个元素(在机器翻译中即目标语言的单词)。
在训练阶段,解码过程按以下步骤进行,直到输出特殊符号<end of sentence>表示翻译完成。每步的输出被送入下一时刻底层Decoder的输入,Decoder们像Encoder一样逐层向上传递解码结果。与Encoder类似,Decoder的输入也需要进行词嵌入并添加位置编码。

Decoder中的Self-Attention与Encoder略有不同:
在Decoder中,Self-Attention层只允许关注输出序列中当前位置之前的位置。这通过在Self-Attention计算的Softmax步骤之前,将未来位置的值设置为负无穷来实现(Masking机制)。
Encoder-Decoder Attention层的工作方式类似Multi-Head Self-Attention,区别在于它从下层创建Query矩阵,而从Encoder栈的输出获取Key和Value矩阵。
最终的线性层和Softmax
Decoder栈输出的是浮点数向量列表。如何将其转换为单词?这就是最终线性层和Softmax层的作用。
线性层是一个全连接神经网络,将Decoder输出的向量投影到一个更大的向量中,称为logits向量。
假设模型从训练数据中学习了10000个英语单词(输出词汇表),则logits向量有10000维,每个值代表对应单词的分数。线性层之后是Softmax层,将这些分数转换为概率(所有正值且总和为1)。选择概率最高的索引,通过该索引找到对应单词作为输出。

上图展示了从Decoder输出到最终Softmax输出的完整流程。
训练过程回顾
现在回顾Transformer的完整训练流程。
在训练期间,未训练的模型会经历上述完整流程。由于使用标注的训练数据集(机器翻译中为双语平行语料),可以将模型输出与真实答案比较,通过反向传播调整权重。
为了便于理解,假设输出词汇表只包含6个词:("a", "am", "i", "thanks", "student", "<eos>")。

在训练开始前,输出词汇表在预处理阶段创建。
定义输出词汇表后,可以使用相同宽度的向量表示词汇表中的每个单词,称为one-hot编码。例如,单词"am"可以用以下向量表示:

损失函数
假设正在训练模型将"merci"翻译成"thanks"。这意味着希望输出"thanks",但由于模型未经训练,不太可能直接输出正确结果。

模型参数(权重)随机初始化,因此未训练模型为每个单词产生的概率分布是随机的。通过与期望输出比较,利用反向传播调整模型权重,使输出更接近期望结果。
如何比较预测值与真实值?简单做减法即可。更深入的理解可以参考交叉熵和KL散度的相关内容。
实际场景中需要处理更长的句子。例如输入"je suis étudiant",期望输出"I am a student"。这意味着模型需要连续输出概率分布,其中:
- 每个概率分布是vocab_size维的向量(示例中为6,实际可能是3000或10000)
- 第一个概率分布在单词"I"对应的位置具有最高概率
- 第二个概率分布在单词"am"对应的位置具有最高概率
- 依此类推,直到第五个输出分布表示
<end of sentence>符号

在充分训练后,模型产生的概率分布应该如下图所示:

模型一次生成一个输出,可以理解为从概率分布中选择概率最高的单词。这里有两种策略:贪婪解码(greedy decoding)和束搜索(beam search)。束搜索是一种优化技术,可以进一步提升翻译质量。
进一步学习
如果想深入了解,可以尝试以下资源:
相关后续研究论文:
原文标题:The Illustrated Transformer
论文地址:Attention is All You Need