在本文中,我会按照 NLP 发展的脉络,一步一步拆解 Transformer 机制
在 2017 年 Ashish Vaswani 等人在 NeurIPS 发表了 Attention is all you need 。这篇文章首次提出注意力机制进行序列建模,尽管 attention 机制在当初为了解决序列串行训练中的低效问题,实现了在大规模 GPU 上的并行训练,并在多年后成为了现代 LLM 的基础。
但说实话,这篇论文的文笔其实并不怎么样,文辞和图示也比较晦涩,笔者其实也吐槽很久了,它并不符合一般读者的习惯,更不适合新手入门。
在本文中,我会按照 NLP 发展的脉络,一步一步拆解 Transformer 机制中“为什么需要这么设计”,以及“每个公式都是怎么得出来的”。尽管如此,你仍然需要具备基本的线性代数、机器学习常识。
语义与含义
在机器学习中的流形假设中,人的思维空间是一个高维欧式空间,词汇可以通过向量 表示。而一个词的语义往往是丰富的,即一个词可能含有多种语义。我们可以认为,一个词的含义是多种语义的组合。
在思维空间里,我们把含义与词汇的概念等同。可以理解为,维度是语义的单元。也是组成含义的最小单元。
为了简化说明,我们考虑一个最简单的情况:描述一个颜色可以使用三原色(R,G,B),此时语义可以划分成三种相互正交的维度:红色度、绿色度、蓝色度。
我们可以建立一个三维空间 ,一个描述颜色的词汇,就包含三种最简单的语义:R、G、B。可以得出一些简单的颜色词汇的向量表示:
纯红 (255,0,0) 纯绿 (0,255,0)
粉色 (255,192,203) 天蓝 (135,206,235)
R\G\B 的三种维度的语义组成了一个颜色的含义。
向量中各个维度中的数字的组合构成了含义,也就能表达一个词汇,一个词汇表达 种语义。词汇是离散的,所以含义是「分布式表示」的。
在此基础上,提出两种假设:
多个含义的加权组合可以合成一个含义。
一个含义可以分解成多个含义的加权组合。
对于第两种假设很好理解,例如皇后(Queen),含义为「国王的妻子」。
从含义分解视角,可以形式化表示为:
或者反过来,得到合成视角:
由此观之,一种含义既可以由多个其他含义进行加权组合,也可以和其他含义加权组合成新的含义。
现在,我们把视角从词汇推广到一个句子。句子和词汇也是一样,也可以看做成「一个含义的表达」。
一个句子由多种词汇的有序序列 组成。为了简化说明,我们先暂时忽略掉它们的顺序。我们有:
一个句子也可以表达一种含义。设权重矩阵为 ,那么此时,句子的含义可以写作各个含义向量的加权组合的向量 :
同理,以上过程还可以推广到一个段落、一篇文章、一本文集。
我们也可以知道,如果我们能把一个含义分解的语义颗粒度越细,那么这个词汇能表达的含义也就越细腻精确,即维度 越高,词汇的含义表达越强。
上下文关联
在上面的演示中,我们假设的是一个词汇对应着一个唯一的向量,表达的是唯一的含义,所以我们忽略了句子 里的词向量顺序。
但是实际上,一个句子中,同一个词,在不同的位置,也可能具有不同的含义。所以,词汇的含义和其所在的序列顺序也是有关的。举个例子:
I went to the bank to sit by the bank of the river.
第一个 bank 指的是银行(金融机构),第二个 bank 指的是河岸(一处地点)。同一个词 bank 却有两种截然不同的含义,而造就含义不同的原因,就是因为它们所在的句子中词序位置不同。这就是 上下文关联,指的是一个词的含义会随着序列的位置变化而变化。位置变了,含义也就变了。
此时,“一个词汇对应着一个唯一的向量,表达的是唯一的含义”这个假设就完全不适用了。此时就变成了“一个句子中,词的含义不仅和本身有关,还和其位置有关”。
我们必须做点什么,把位置信息也加入到词汇向量里面。
我们再细想一下,其实「词序位置」本身也是一种含义,同样也可以像词向量那样,用 维向量来表达,也可以与词向量进行加权组合,形成新的含义。
假如没有位置编码,那么一个词就只能有一种含义,就无法处理一个句子中“一词多义”的情况。
我们为词汇位置也定义一种含义,可以用向量 表示。所以,一个词的含义加上位置的含义,就组成了一个新的含义:
同理,在一个句子中,我们为每个位置 的词汇 ,其位置上的含义为 ,加权组合后,我们就可以为这个句子里每一个个词汇都建立起上下文关联。
在原文中,使用的是直接相加的组合。
在原文中, 用的是三角函数 PE 编码,定义为
其中每一个维度的分量 为
注意力机制
注意力机制是模仿人类在阅读文本的过程中,对每个词汇的理解过程。
事实上,一个人在阅读文本的过程中并不是逐词扫描一遍过的。 而是分为两步。
第一步,逐词扫描,将一段句子种的所有词汇载入到大脑的短期记忆中。这里,对应的是上面所提的,建立上下文关联、将每个词的含义糅合位置含义,得到新含义的过程。
第二步,再进行扫描,在理解每个词汇时,同时关注其他所有剩余词汇,结合剩余的词汇来分析它的含义。
这么说可能有点抽象,但是换一种说法就明确了,它可以被表述为:
一句话中每一个词的含义,可以视为剩余所有词汇含义的加权组合。
以形式化的方法说明,例如在句子 中,对于 ,可以写作
这里的问题是:如何确定权重 ?也就是说,如何衡量剩余词汇对目标词含义的贡献比例?
我们可以提出一个假设:
如果一个词 与剩余某个词 的含义越相似,那么该词对 含义的贡献就越大。所以,权重可以写作两个词向量的内积相似度。
以一句话举个例子,现在我们分析「Cat sat seat」这一句子。
我们先对「Cat」这一个词进行分析,计算它和剩余词汇的相似度,我们假设,它与「sat」和「seat」的相似度分别为 。那么,我们就可以认为
含义是:「Cat」这个词的含义,是由「sat」的 10% 和「seat」的 5% 组成的。对于剩下的词「sat」和「seat」也是同理。
上面的过程,就是分别计算 “一个词的含义,分别可以由剩下的词各自以多少比例组成”的过程,就是注意力机制的雏形。
然而,上面的权重 的总和不是 ,为了保证在数值上有一致性,我们需要把权重值进行 Softmax 正则化一下:
这样使得 ,计算得到
在注意力机制中,我们把上面的 作为查询向量 ,剩余词汇 作为键向量 ,而加权组合 则为值向量 .
值得注意的是,上面的示例均为简化版示例,不涉及线性变换。
点积注意力公式
上面的示例中已经演示了简单的注意力机制。但是,我们仍然感觉,似乎还是缺了点什么。
我以前上英语的时候,读到过一个笑话:
有一个大学生小明出国读研,在选课的时候发现了这样一门课,名字叫做:《Options,Futures and Other Derivatives》。
于是小明心里边翻译边想:嗯,这门课好啊,讲的是选择,未来,和衍生,一看就是那种人生的哲学,教人们怎么在生活中选择的那种课,就它了,选了!
结果呢,到了上课那天,他傻眼了:老师在前面讲些什么呢?我怎么一句话都听不懂?怎么还有那么多的数学公式?人生选择难不成也要用数学吗?
第二天他才明白,噢,原来这个课的实际名字叫:《期权,期货和其他衍生品》。
这个笑话已经说明了一个问题:在实际的语料库中,一个词可能往往有多个子含义。例如这里,「options」的子含义有两个:「期权」和「选择」。但是有时候,我们需要偏重其中一个子含义,例如在这里,「期权」的含义成分更多,而不是往另一个子含义「选择」上面去理解。
我们在前面的第一节「语义和含义」的一节,已经知道,一个词向量(含义)可以写作多个词向量(子含义)的加权组合。我们假设,一个词的完整含义可以拆分成 个子含义,每个子含义对应一个向量,并通过权重加权得到最终含义。
就比如,「Options」这个词,它的完整含义,本质上可以拆分成是「选择」和「期权」两个「子含义」的加权组合。
就像上面的例子,如果放在金融语境下,它实际上需要以更大的权重往「期权」的子含义上去理解。对于「选择」方向上的子含义权重要小一些,以避免干扰。
之前,我们的一个句子是有序词元素,这个序列每一个元素,是一个词含义与位置含义的加权组合成的“新含义”。
一个“含义”既然可以拆分成多个“子含义”的加权组合,假设拆成 个子含义,那么我们就有
其中 . 那么,如果我们有一个长度为 的句子序列,把每个词拆成子含义后,就可以把整个序列写成矩阵形式:
其中 .
如果我们把这一层也考虑进注意力机制,那么在上面的过程中, 的操作对象,实际上是每个词之间的子含义。那么此时,我们有
我们得到注意力表达式为:
然而,为了避免 在数值上爆炸,我们需要手动调整数值规模,将矩阵元素除 .我们得到最终的注意力公式为:
换句话说,注意力机制实际上是在词的子含义层面上计算匹配分数,不同子含义之间的相似度决定了信息传递的权重。
上面的矩阵 均为可学习矩阵,在训练阶段进行随机初始化,后期通过反向传播和梯度下降进行矩阵元素更新。这就是单个注意力头。
Encoder 和多头注意力
均为可学习矩阵,在训练阶段进行随机初始化,也就意味着,每一次训练,得到的模型参数都不一定是最优的。在不同的语料上训练时,模型参数也有所差异。
所以,我们需要把矩阵 复制多份,以不同的种子进行随机初始化,同时去训练多个注意力头。如果有 个头,每个头的注意力计算为
然后将 个头的输出拼接:
然而此时 Multi Head 输出维度是非常大的,其形状为 。Transformer 中后续还引入了残差链接和前馈神经网络,要求输出维度特征必须与输入 一致。
因此,需要引入一个可学习的线性映射矩阵 ,将拼接后的多头输出,映射回原来的模型维度 。这个矩阵也是通过反向传播进行更新。
因此,多头拼接再经过线性变换,有
*高阶注意力
在上面的多头输出后,我们得到输出 ,与输入 维度特征一致,那么,我们可以将输出 作为新的输入 ,再来一轮多头注意力。以此类推,经过多轮多头注意力,连续经过 轮,得到最终输出:
最终输出 就是模型对序列的高阶表示,它融合了多轮注意力捕捉到的复杂语义和依赖关系。第一层多头注意力:捕捉局部和短距离的依赖。第二层多头注意力:在上一层表示基础上,捕捉更高级的组合模式。直到第 层:得到全局、高阶语义的向量表示。
在工程上,通常每一层通常都有 残差连接 + LayerNorm,即
保证梯度稳定和信息传递。高阶注意力就是将多头注意力层按序堆叠,让模型能够逐层捕捉更复杂、更抽象的语义和依赖关系。
有最近的研究表明,阶数越低,捕捉到的特征成分主要是语法、句法和词法结构。阶数越高,捕捉到的特征成分主要是语义信息。在文章 BERT Rediscovers the Classical NLP Pipeline 中提到,发现 Transformer 层次表示呈现 由语法到语义的递进规律。
Decoder 和自回归生成
在前面的章节中,我们详细讨论了 Encoder 部分——它的作用是把输入的序列(比如一句英文)编码成一串富含上下文信息的高阶表示向量。那么,问题来了:如果我们想要生成一个新的序列(比如把英文翻译成中文,或者根据上文续写下文),该怎么办?
Encoder 的目标是“理解”,Decoder 的目标是“生成”。在 Transformer 原始架构中(用于机器翻译),Decoder 和 Encoder 长得非常像,但有三个关键的区别:
- Decoder 是自回归的:它一个词一个词地往外蹦,一次只预测下一个词,每次生成下一个词时,会把之前生成的所有词都当作输入。
- Decoder 包含 Masked Attention(掩码注意力):在生成第 个词时,它不能“偷看”未来的词。
- Decoder 包含 Cross-Attention(交叉注意力):它不仅要关注自己已经生成的内容,还要时刻关注 Encoder 输出的源语言信息。
假设我们正在进行中文 - 英文翻译任务,输入是 我 爱 机 器 学 习,期望输出是 I love machine learning。
在训练阶段,我们可以并行计算(因为知道标准答案)。但在推理阶段(即模型真正使用时),模型必须自己生成答案。这个过程就是自回归(Autoregressive)。
自回归假设:当前时刻的输出,依赖于之前所有时刻的输出。
用形式化表示就是:
其中 是 Encoder 的输出(源语言信息), 是当前生成的词。
举个例子:
- 输入
<START>(起始标记),模型预测第一个词:。 - 输入
<START> I,模型预测第二个词:。 - 输入
<START> I love,模型预测第三个词:。 - ……一直持续到模型预测出
<END>(结束标记)。
注意:Decoder 在生成第 个词时,是看不到第 个词的。这很合理——就像考试做填空默写题,你只能看到题目和你自己已经写下来的内容,你不能去翻后面的答案。
Decoder 训练与掩码注意力
这就引出了一个问题:在训练 Decoder 的时候,我们是希望并行计算的(否则 GPU 利用率太低)。但如果我们像 Encoder 那样直接把整个目标序列 一次性输入给 Decoder,那么它在计算 的时候就能看到 ,这就叫做“信息泄露”(cheating)。
解决方案是 Masked Attention(掩码注意力),也叫 Causal Attention(因果注意力)。
它的原理非常简单:在计算注意力分数矩阵 时,我们把“未来”位置的分数全部设为 (或者一个非常大的负数,比如 )。
矩阵形状是这样的(假设序列长度为 4),是一个上三角全被遮盖的矩阵:
解释:
- 第一行( 的位置):只能看到 自己,不能看到 。
- 第二行( 的位置):只能看到 ,不能看到 。
以此类推。
当 Softmax 处理这些分数时, 会被映射成 0(因为 )。这样一来,Decoder 在移动到 ,计算其含义表示时, 的含义贡献就是 0。当 Decoder 移动到 时,其含义表示为自身含义与 含义的加权组合, 的含义贡献就是 0。
掩码注意力的公式可以表示为:
其中 就是掩码矩阵( 如果 ,否则 )。
Decoder 输出与交叉注意力
在推理阶段,Decoder 不能只靠自言自语来生成翻译——它必须参考编码器的原始输入句子。比如翻译 我爱机器学习 时,Decoder 需要知道 Encoder 为 machine learning 生成的表示,对应的是 机器学习 这部分。
这就引入了 Cross-Attention(交叉注意力)层。
在 Cross-Attention 中,Q、K、V 的来源和 Encoder 不同:
- Q(Query):来自 Decoder 当前的词汇表示。这个词汇表示为该词含义与之前所有词汇含义的加权组合。
- K(Key):来自 Encoder 的输出(即“源语言里有哪些信息可供我查询”)。
- V(Value):同样来自 Encoder 的输出(即“这些信息的实际内容是什么”)。
公式和之前的类似:
- Decoder 当前想生成“love”这个词,它的 Q 矩阵中包含了“我正在翻译「爱」”这个意图。
- 这个 Q 去和 Encoder 的 K 做相似度计算——发现
love和源语言中的爱这个含义高度相似。 - 于是,注意力权重集中在
爱的那个位置上,把那个位置的 V(即爱的语义向量)加权提取出来,融合进 Decoder 的当前表示中。
这样一来,Decoder 既知道“我已经生成了哪些词”(通过 Masked Self-Attention),又知道“源语言在说什么”(通过 Cross-Attention),就能很好地完成翻译任务了。
可视化翻译步骤
假设 Encoder 输入 我 爱 AI,训练完 Encoder,得到输出 。
Decoder 正在生成第 3 个词(已经生成了 I love,现在要生成 AI):
- Masked Self-Attention:
I和love互相“看”对方,计算出它们之间的关联关系。I的含义会包含love的含义,love的含义也会有I的含义。但它们都不能看到未来的第 3 个位置(因为那个位置还没生成)。 - Cross-Attention:Decoder 把当前的词汇表示(也就是
love,它包含了自身以及I的含义的加权表示)作为 ,去和 Encoder 的三个位置 计算相似度。如果模型学得好,它应该发现“love”和“爱”()最相关,于是注意力权重集中在 上,把 的特征提取出来。 - FFN:根据向量 ,发现 这个向量的含义中,包含词汇
AI的含义最高,于是可以输出AI。
上面就是 Encoder-Decoder 联合进行翻译的全过程。