从整体结构到位置编码与残差连接 · znzryb
> 这组笔记主要参考 Jay Alammar 的 [The Illustrated Transformer](https://jalammar.github.io/illustrated-transformer/),结合自己的截图与手写批注重新组织。讲解对象是原始的 Encoder–Decoder Transformer,采用原论文的 Post-LN 结构。图中的 6 层、512 维、8 个头是原始 base 配置的例子,不是 Transformer 必须遵守的常数。
>
> 原文及其图示采用 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/);本系列对原文的中文改编与批注沿用该许可。保留了截图原貌,正文另行说明需要澄清的简化说法。
## 先认路:一句话怎样穿过整台模型
先把完整结构放在眼前。左边把源句子变成一组向量;右边结合这组向量和已知的目标前缀,逐步生成译文。下面会把这张总图拆开看,第二篇再手算注意力。

*图 1:带残差连接和归一化的 Encoder–Decoder 总图。原始截图与手写批注。*
阅读时先盯三件事:向量在哪些位置之间流动,哪些模块只加工当前位置,解码器在什么地方读到了源句子。暂时不必记住所有矩阵。

*图 2:把机器翻译看成法语输入、英语输出的黑盒。原始截图与手写批注。*
黑盒里分成两个大部分。Encoder 负责把输入变成带有上下文的信息表示;Decoder 负责在这些信息的帮助下预测下一个输出 token。图里把 token 画成单词,实际分词时,一个词也可能被切成多个 token。

*图 3:打开黑盒,分成编码器和解码器。原始截图与手写批注。*
编码器的结果不是一句压缩好的英文,也不是只有一个句向量。输入有多少个位置,通常就留下多少行表示,每一行包含该位置及其上下文的信息。

*图 4:编码器与解码器各自堆叠多层。原始截图与手写批注。*
这里的“堆叠”是深度:上一层的整组输出送进下一层。不同层结构相似,但参数通常各自独立。图中从顶层编码器拉到每层解码器的连线,表示每层交叉注意力都可以读取最终编码结果;不是第一个编码层只对应第一个解码层。
## 每个位置先变成一行数字
模型不能直接乘一个英文单词,要先查词嵌入表,把 token ID 换成向量。假设句子有 $n$ 个 token,每个向量有 $d_{model}$ 个数,排起来就是 $n\times d_{model}$ 的矩阵。第一层的输入还会加入位置编码;更高层接收上一层已经加工过的表示。

*图 5:把词变成嵌入向量,送入底层编码器。原始截图与手写批注。*
截图把向量画成几格只是为了看清流程,不代表真的只有几维。讨论“512 维”时,说的是每个位置带着 512 个数;讨论“句长”时,说的是这样的向量有多少行。这两条轴要分开。
## 一个编码层里,谁负责交流,谁负责加工
自注意力让当前位置根据其他位置的信息更新自己。随后,前馈网络 FFN 对每个位置的向量做同一种非线性加工。

*图 6:单个编码层中的自注意力与 FFN。原始截图与手写批注。*
可以先把自注意力理解成“先决定该参考谁,再把参考内容按比例汇总”。FFN 则继续加工汇总结果。原始配置中的 FFN 是两次线性变换,中间加 ReLU;它不在这个子层里直接读取其他位置。

*图 7:不同位置经过相同的前馈网络。原始截图与手写批注。*
这里特别容易看反:图中每个位置都有一个 FFN 框,表示可以分别计算,**同一层各位置使用的是同一套 FFN 参数**。但不同位置的输入不同,输出当然可以不同;不同编码层的 FFN 参数也通常不同。

*图 8:把按位置计算的路径单独展开。原始截图与手写批注。*
注意力中的跨位置混合,要等当前层的输入表示都准备好后一起计算;并不要求像 RNN 那样,位置 2 必须等待位置 1 在这一层先算完。因此可以把多个位置装进矩阵,一次并行处理。并行的是同层位置,模型深度上的层与层仍有先后依赖。
## 为什么还要告诉它词的顺序
只按内容做自注意力,交换输入行会相应交换输出行,模型没有由这一操作自动获得固定的先后标记。为了区分位置,原始 Transformer 在词向量上加位置编码。

*图 9:位置编码与词嵌入逐元素相加。原始截图与手写批注。*
相加要求两个向量长度相同。位置不是被拼到向量末尾的额外一列,也不是给每个词分配一个整数后直接当语义。每个位置获得一整条有规律的数值信号。

*图 10:位置为行、编码维度为列的位置编码热力图。原始截图与手写批注。*
看图时,沿一行读,是一个位置拿到的整条位置向量;沿一列读,是某个维度随位置变化的规律。不同维度使用不同频率,使位置以多种尺度体现出来。

*图 11:原论文正弦、余弦信号交错排列的版本。原始截图与手写批注。*
这两张热力图的排法不同。原博客说明:前一张对应 Tensor2Tensor 中正弦与余弦分块拼接的实现;后一张对应论文把它们放在偶数、奇数维的交错排法。别把两种列顺序当成两个互相矛盾的原理。
用公式记录交错版本:$pos$ 是位置,$i$ 是频率编号,偶数维用正弦,紧邻的奇数维用余弦。
$$
PE(pos,2i)=\sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)
$$
$$
PE(pos,2i+1)=\cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)
$$
这种公式可以为训练范围外的位置继续算出编码;但“能算出编码”不等于模型一定能可靠处理任意长的句子。
## Add & Norm 把原信息也留下来
一个子层处理完以后,不必把原输入直接丢掉。残差连接把原输入与子层输出相加,再由 LayerNorm 调整每个位置向量的数值尺度。下面两张图分别突出绕行路径和相加的位置。

*图 12:自注意力与 FFN 外侧的残差连接。原始截图与手写批注。*
原输入 $x$ 可以沿旁路保留下来,子层学习的是在此基础上需要补充或调整的内容。残差也给深层网络提供更直接的梯度路径。

*图 13:残差相加后进行 LayerNorm。原始截图与手写批注。*
在本文讲的原始结构里,顺序是“子层计算、与输入相加、再归一化”。省略 dropout 后可以写成:
$$
y=\operatorname{LayerNorm}(x+\operatorname{Sublayer}(x))
$$
LayerNorm 通常对每个 token 的特征维进行归一化,并带可学习的缩放与偏移。它不是对不同 token 的注意力分数做 softmax。后来的 Pre-LN 等变体会改变归一化位置,阅读别的模型时要看清具体结构。
## 解码器为什么多一个注意力模块
解码器先在目标前缀内部做带因果遮罩的自注意力,再通过交叉注意力读取源句子的编码结果,最后经过 FFN。

*图 14:解码层额外包含 Encoder–Decoder Attention。原始截图与手写批注。*
三块各自回答不同问题:目标前缀里哪些信息有关?源句子里哪些位置值得参考?把得到的表示怎样进一步加工?自注意力与交叉注意力使用同一种计算骨架,Q/K/V 的来源不同。

*图 15:位置编码、编码器输出与逐步解码放在同一张图里。原始截图与手写批注。*
到这里,整台模型已经有了轮廓:输入逐位置表示,编码层交换并加工信息,解码层结合源句与目标前缀继续预测。下一篇把“决定参考谁”这件事拆到每一次乘法,弄清 Q、K、V 到底各做什么。
主要参考:[Jay Alammar — The Illustrated Transformer](https://jalammar.github.io/illustrated-transformer/)。原始结构与符号可对照 [Attention Is All You Need](https://arxiv.org/abs/1706.03762) 的第 3 节。