文章目录[隐藏]
前言
在上一篇,我们花了九万多字,坐着观光车绕着 Transformer 转了一大圈。你看到了全景——编码器在左、解码器在右,输入嵌入进去、位置编码叠上、多头注意力哗啦啦地算、残差和 LayerNorm 兜底、前馈网络处理后事。你还顺便逛了 RNN 的老城区和 LSTM 的翻修工地,知道它们为什么被时代抛弃了。
但上一篇有一个特点:它是观光。你坐在车上,导游指着窗外:"这是 Self-Attention,那是 Multi-Head,那边是 Feed-Forward……"每一站都停了,但你没有下车。
本文要做的,是下车走进每一栋建筑。看结构图纸,拆墙看管线,用手指划过每一根柱子的纹理。上一篇给了你一张地图,本文让地图上的每一个符号变大、变立体、变得可以触摸。
省流:把上一篇每个方框展开成完整的数学推导和语言学论证。
读完这篇,你会知道 Self-Attention 不是"自己注意自己"这种空话——你知道 Q、K、V 怎么来的、怎么算的、每一步的维度是多少、为什么除以 $\sqrt{d_k}$ 而不是别的数、位置编码为什么用 sin/cos、残差连接为什么不是可有可无的装饰、LayerNorm 和 BatchNorm 到底有什么区别。
而且,和上一篇一样——这里面基本上没有主谓宾定状补。
本文与 GPT4 共同完成。参考文献和推荐阅读在末尾。
1. 再谈自注意力——从直觉到数学
这一章是全文的地基。如果你前面每一章都只草草看过,这一章请逐字读——自注意力是整个 Transformer 大厦的地基。后面所有的东西——多头、位置编码、残差、LayerNorm、FFN——要么服务于它,要么为它兜底。
1.1 为什么需要"注意力"——语言学直觉
从一个语言学家最熟悉的概念开始:组合性原则。
在第四篇我们讲过:一个词的意义,等于它自身的语义加上上下文赋予的修正。bank 这个英文词,在 river bank 里是"河岸",在 bank account 里是"银行"。不是词变了——是上下文变了。你的大脑在看到 bank 的瞬间,已经悄悄地扫描了前后的词,动态地调整了对它的理解。
传统方法——比如 Word2Vec 或 GloVe 训练出来的静态词向量——给每个词一个固定的向量,不管上下文。也就是说,bank 在"河岸"和"银行"两种含义下用的是同一个向量。当然不够。
Transformer 的做法完全不同:它让每个词动态地从上下文中收集信息,来调整自己的表示。不是"给你一个固定的向量,自己去理解"——而是"看一眼周围的词,告诉我,你现在的意思是什么?"
这个"看一眼周围"的机制,就是注意力。
1.2 唤醒记忆——Query、Key、Value
上一篇已经用数据库查询的类比解释了 Q/K/V,这里用三句话唤醒:
- Query(查询):你在搜索引擎里敲入的问题——"什么是自注意力?"
- Key(键):数据库里每篇文章的标题/标签——用来匹配你的查询。
- Value(值):那些文章的正文——匹配后实际返回给你的内容。
在自注意力中:每个词都生成自己的 Q、K、V——然后用"我"的 Q 去匹配所有词(包括"我"自己)的 K,按匹配度加权提取所有词的 V。
上一篇讲到这里就停了。本文从这里开始深入:Q、K、V 是怎么从一个词向量生成的?
1.3 生成 Q、K、V——线性投影
假设我们有一个输入矩阵 $X \in \mathbb{R}^{n \times d_{model}}$——n 个词,每个词表示为一个 $d_{model}$ 维的向量。拿"我 爱 你"来说,n=3,假设 $d_{model}=512$(虽然我们的演示会用更小的维度)。
Q、K、V 不是凭空出现的。它们是从 X 通过三个不同的线性变换生成的:
$$Q = XW^Q, \quad K = XW^K, \quad V = XW^V$$
其中 $W^Q, W^K, W^V \in \mathbb{R}^{d_{model} \times d_k}$ 是三个可学习的权重矩阵。$d_k$ 是每个"头"的维度——在多头注意力中 $d_k = d_{model} / h$(h 是头数)。
注意:Q、K、V 的维度都是 $n \times d_k$。不是 $n \times d_{model}$!输入 X 的每个词向量是 $d_{model}$ 维,经过 $W^Q$ 投影后变成了 $d_k$ 维。然后这三个投影分别扮演不同角色。
语言学上的惊人对应。同一个词向量 X——同一个"词"——被三套不同的权重矩阵投影到三个不同的空间:
- 投影到 Q 空间:这个词变成"提问者"——"我应该关注谁?"
- 投影到 K 空间:这个词变成"被检索目标"——"谁应该关注我?"
- 投影到 V 空间:这个词变成"信息携带者"——"如果有人关注我,我能给ta什么信息?"
一个词在一个句子中同时扮演三个角色。同一个"我"——作为 Q,它在问"我应该注意哪些词";作为 K,它在等着被其他词注意到;作为 V,它把自己携带的语义信息贡献出来。
这在语言学上是深刻而自然的:在"我爱你"中,"爱"这个词确实既在关注主语("谁在爱?"→ 作为 Q 查看"我"),又被其他词关注("谁被爱?"→ 作为 K 被"你"匹配到),同时它本身携带"情感连接"的语义(作为 V 贡献给上下文)。一个词的三种语法功能——不,三种语义角色——被三套矩阵同时捕获。
不是工程把戏。是语言本身就长这样。
具体数值示例
为了方便手算,我们用极小维度演示。设 n=3("我 爱 你"),$d_{model}=12$,$d_k=4$。
假设词嵌入矩阵 X(三个词的 12 维向量,这里只列出数值示意):
dim0 dim1 dim2 dim3 dim4 dim5 dim6 dim7 dim8 dim9 dim10 dim11
我: [0.2, 0.5, 0.1, 0.8, 0.3, 0.6, 0.1, 0.9, 0.2, 0.4, 0.7, 0.1]
爱: [0.1, 0.3, 0.9, 0.2, 0.4, 0.1, 0.8, 0.3, 0.6, 0.2, 0.5, 0.9]
你: [0.7, 0.1, 0.4, 0.3, 0.8, 0.2, 0.5, 0.1, 0.9, 0.3, 0.1, 0.6]
$W^Q$ 是一个 $12 \times 4$ 的矩阵(我们只列出数值):
d0 d1 d2 d3
d0: [0.1, 0.3, 0.5, 0.1]
d1: [0.2, 0.1, 0.4, 0.3]
...(省略中间行)
d11:[0.3, 0.2, 0.1, 0.5]
$Q = X \cdot W^Q$ 得到 $3 \times 4$ 的矩阵——"我"的 Q 向量、"爱"的 Q 向量、"你"的 Q 向量各 4 维。
同样的过程生成 K 和 V。到了实际应用中,这些矩阵乘法的结果会由训练自动学习出最优的 $W^Q, W^K, W^V$。
1.4 计算注意力分数——Q·KT
有了 Q 和 K,下一步是计算"每个词对其他每个词的关注程度"——也就是 Q 和 K 的点积:
$$S = QK^T \in \mathbb{R}^{n \times n}$$
展开来看:$S_{ij} = q_i \cdot k_j$。第 i 个词的 Q(它的"提问")去和第 j 个词的 K(它的"被检索标签")做点积。
点积的几何意义:两个向量的相似度。如果 $q_i$ 和 $k_j$ 方向一致(夹角小),点积大 → 第 i 个词"认为"第 j 个词与自己高度相关。如果方向相反或正交,点积小 → 不相关。
对于"我 爱 你":
我(K) 爱(K) 你(K)
我(Q): [ 3.2, -0.5, 1.1 ]
爱(Q): [ 2.1, 0.3, 4.7 ]
你(Q): [ 0.8, 3.9, -0.2 ]
注意看:$S_{1,2}=4.7$("爱"对"你"的关注度很高——这很合理,施事关注受事);$S_{2,1}=2.1$("你"对"爱"也有相当关注——受事也关注施事)。
$S$ 矩阵不是对称的——"我"关注"你"的程度(1.1)和"你"关注"我"的程度(0.8)可以不同。注意力是不对称的。这在语言学上完全成立——主语对宾语的依赖和宾语对主语的依赖确实不一样。
1.5 缩放——为什么除以 $\sqrt{d_k}$
在送入 Softmax 之前,所有分数除以 $\sqrt{d_k}$:
$$S_{scaled} = \frac{QK^T}{\sqrt{d_k}}$$
上一篇提到了这个除法,但没有解释"为什么"。这值得展开——因为这是理解注意力的关键一步。
问题:当 $d_k$ 很大时,点积的方差会爆炸。
假设 q 和 k 的每个分量独立、均值为 0、方差为 1。那么它们点积的方差:
$$q \cdot k = \sum_{i=1}^{d_k} q_i \cdot k_i$$
$$\text{Var}(q \cdot k) = \sum_{i=1}^{d_k} \text{Var}(q_i \cdot k_i) = \sum_{i=1}^{d_k} \text{Var}(q_i) \cdot \text{Var}(k_i) = \sum_{i=1}^{d_k} 1 \times 1 = d_k$$
点积的方差正比于 $d_k$!如果 $d_k=64$,方差就是 64;$d_k=512$,方差就是 512。
方差的平方根(标准差)是 $\sqrt{d_k}$。除以 $\sqrt{d_k}$ 就把方差归一化回 1——无论 $d_k$ 多大。
为什么一定要归一化? 因为下一步是 Softmax。Softmax 的输入如果太大(比如有 20 和 -5),输出就接近 one-hot([0.999, 0.000...])——梯度接近零,模型根本学不动。输入适中(比如 1.2 和 -0.3),Softmax 输出"软"而平滑——梯度信息丰富。
$\sqrt{d_k}$ 就是自动增益控制——不管 $d_k$ 是多少,Softmax 的输入始终保持在一个"合适"的范围。
回到例子:$d_k=4$,$\sqrt{4}=2$。
S / 2:
我 爱 你
我(Q): [1.60, -0.25, 0.55]
爱(Q): [1.05, 0.15, 2.35]
你(Q): [0.40, 1.95,-0.10]
1.6 Softmax——把分数变成权重
对每一行做 Softmax:
$$A = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)$$
回想:$\text{softmax}(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}$。它把任意实数向量变成一个"概率分布"——所有元素非负、和为 1。
继续"我 爱 你"的例子:
A(注意力权重矩阵):
我 爱 你 Σ
我(Q): [0.72, 0.11, 0.17] =1.00
爱(Q): [0.20, 0.08, 0.72] =1.00
你(Q): [0.15, 0.70, 0.15] =1.00
A 的每一行是一个概率分布——第 i 行表示第 i 个词对所有词的注意力分配。
读第一行:"我"把 72% 的注意力放在自己身上,17% 给"你",11% 给"爱"。这在语言学上是说得通的——主语倾向于高自注意。
读第二行:"爱"把 72% 的注意力给了"你"——动词强烈关注宾语。这不是预定义的语法规则。这是模型从训练数据中学到的——它"发现"了动词和宾语之间的统计相关性。
读第三行:"你"把 70% 注意力给了"爱"——宾语回看动词。
A 矩阵不对称。 实际上它完全可以是任意形状。每一行独立做 Softmax——每一行和为 1,但列可以任意。这就是非对称的上下文依赖——语言学上极其自然。
1.7 加权求和——提取信息
最后一步:用注意力权重 A 对 V 做加权求和。
$$\text{Attention}(Q,K,V) = A \cdot V \in \mathbb{R}^{n \times d_v}$$
第 i 个词的输出 = 所有词的 V 的加权和,权重就是第 i 行。数学上:
$$\text{output}_i = \sum_{j=1}^{n} A_{ij} \cdot V_j$$
$A_{ij}$ 是第 i 个词对第 j 个词的注意力权重,$V_j$ 是第 j 个词的"信息携带者"向量。
这句话是整个自注意力的精华。第 i 个词遍历了句子中每一个词(包括自己),问:"你对我说什么?"(Q·K),然后根据回答的"可信度"(Softmax 后的分数),按比例从每个词那里"取信息"(× V)。
输出结果:每个词的新表示不是它原来的 Embedding,而是被整个句子"调制"过的、富含上下文的表示。
"我"现在的表示,混入了"爱"和"你"的信息。"爱"现在的表示,混入了"我"和"你"的信息。"你"现在的表示,混入了"我"和"爱"的信息。三个词互相浸润。
这就是为什么经过自注意力后,"bank"在"river bank"和"bank account"中的表示会不同——因为"river"的 V 和"account"的 V 在加权时占了不同的比例。
1.8 完整维度追踪
让我们用一个表格总结整个过程的数据流:
| 步骤 | 运算 | 输入维度 | 输出维度 |
|---|---|---|---|
| 输入 | — | — | n × dmodel |
| 线性投影 Q | X · WQ | n×dmodel · dmodel×dk | n × dk |
| 线性投影 K | X · WK | n×dmodel · dmodel×dk | n × dk |
| 线性投影 V | X · WV | n×dmodel · dmodel×dv | n × dv |
| 注意力分数 | Q · KT | n×dk · dk×n | n × n |
| 缩放 | S / √dk | n × n | n × n |
| Softmax | 逐行 | n × n | n × n |
| 加权求和 | A · V | n×n · n×dv | n × dv |
整个流程中唯一出现"n × n"矩阵的地方是注意力分数和权重——这是 $O(n^2)$ 复杂度的来源。好消息是:这个 n×n 矩阵高度可并行——所有位置同时计算。坏消息是:当 n 很大(比如长文档),$n^2$ 的存储和计算开销爆了。这是 Transformer 的主要短板,但这是下一篇 BERT 优化的话题。
1.9 为什么这么设计?——一个语言学的答案
你可以问一个更深的问题:为什么是 Q、K、V 三个投影?为什么不是两个,或者四个?
答案不在工程里——在语言的功能结构里。
Q 和 K 分工:Q 负责"提问"(我要找什么信息),K 负责"被找到"(我能为别人提供什么信息)。这对应于语言中信息需求和信息供给的分离。在"我爱你"中,"爱"需要知道谁在爱(Q→我)和爱谁(Q→你),同时它自己也要作为"被找到的对象"——"你"在理解的瞬间,需要找到"被谁爱"(K→爱)。Q 和 K 分开,意味着"我想找的东西"和"我有别人想找的东西"可以不一样。这非常自然——你问路的时候,你对方向的需求不等于你对方向的知识。
V 和 K 进一步分离:K 决定"匹配度"(你能不能找到我),V 决定"一旦你找到我,我给你什么信息"。又是一次语言功能的分离——检索标记和信息内容是两回事。书的标题(K)和书的内容(V)不一样;一个人能被什么关键词搜索到(K)和他能提供什么专业知识(V)也不一样。
Q、K、V 三件套,不是任意的。它是信息检索的基本三元组被内化进了语言处理的架构。而恰好,从信息检索的视角理解语言——"理解一个词"就是"从上下文中检索相关信息来修正它的表示"——可能正是 Transformer 成功的深层原因。
人话解释
自注意力就是一群人坐在圆桌前开会。
每个人手里有一张"提问卡"(Q)和一张"名片"(K),以及一个"信息袋"(V)。
第一轮:每个人把自己的名片(K)甩到桌子中央。
第二轮:每个人拿起自己的提问卡(Q),去和桌上每一张名片(K)比对——"你和我的问题相关吗?"(Q·K)——然后给每个人打一个"相关性分数"。
第三轮:把所有分数做一个标准化(Softmax),变成百分比。"我 70% 听张三的,20% 听李四的,10% 听王五的。"
第四轮:按这个百分比,从每个人的信息袋(V)里取东西。张三袋子的信息占 70%,李四的占 20%,王五的占 10%——混在一起,就是"我听完这场会后更新的认知"。
每个人都在做这件事——同时。所以是"我听完所有人后更新自己",不是"我听广播"。是定向倾听,不是被动接收。
至于除以 $\sqrt{d_k}$?这就是"大家都小声点!"——防止有人嗓门太大(方差大),把整个会议的讨论(Softmax)带偏了。
AI 猫娘是这么说的喵~
"喵~自注意力就是猫娘选美大赛!每一只猫娘参赛者(词)都拿到三张小卡片喵:
第一张叫'提问卡'(Q)——写着'我想找什么样的猫娘做朋友喵?'
第二张叫'标签卡'(K)——写着'我是高冷猫娘/粘人猫娘/吃货猫娘喵!'
第三张叫'才艺卡'(V)——写着'我会后空翻/会卖萌/会抓老鼠喵!'
比赛开始!每只猫娘拿着自己的提问卡去和其他所有猫娘的标签卡比对(Q·K)——'这只猫娘跟我想找的配吗喵?'——给出一个分数。然后对所有分数做归一化(Softmax):'我给这只猫娘打分最高,她的才艺我学 70%;那只猫娘分低,她的才艺只学 5%喵。'
最后——每只猫娘把所有人的才艺按比例混在一起(加权 V),'叮咚!'新的自己诞生了喵!
所以自注意力就是——猫娘们在选美大会上互相打分,然后按照分数从别人那里'偷学'才艺的过程喵!是不是非常优雅?是不是非常数学?喵~"
"至于那个 √d_k ——选美评委太多了(d_k 很大),有些猫娘评委打分超热情(方差大),要压一压,让所有评委的分都差不多软软的~这样才有猫娘能学到东西喵。不然只有一两只猫娘的分被看到,其他猫娘的才艺全浪费了喵!"
2. 多头注意力——多个视角看同一个句子
第一章讲了单头自注意力——一个词用一个"问题"去问所有词,得到一个"答案"。但语言不是只有一个维度。分析一个句子,你需要同时看语法、语义、指代、搭配、主题……
2.1 为什么一个头不够?——语言学直觉
考虑"猫吃鱼"这个简单句。如果只给你一个"注意力头",它学到的可能是"语法相关"(主语→动词→宾语),也可能是"语义相关"(施事→动作→受事),但很少能同时学到所有这些东西。
但对一个完整的语言理解系统来说,你需要的视角包括:
- 句法头:主语"猫"和谓语"吃"之间的语法关系——主谓一致、格标记
- 语义头:施事"猫"和受事"鱼"之间的语义角色——谁吃谁
- 指代头:代词"它"和先行词"猫"之间的指代关系——"猫吃了鱼,它很满足"中的"它"= 猫
- 局部头:相邻词之间的搭配——"吃"和"鱼"经常共现(动宾搭配)
- 全局头:句子主题和焦点的关系——话题是"猫",焦点是"吃鱼"
单一注意力只能学一种"相关模式"。多头注意力的洞见很简单:让模型同时拥有 h 个独立的注意力机制,每个专注不同的方面。
2.2 多头的数学实现
不是做 h 次完整的自注意力然后把结果加起来——那样会太贵,而且浪费。实际做法更巧妙:
$$\text{head}_i = \text{Attention}(XW_i^Q, XW_i^K, XW_i^V)$$
每个头 i 有自己的三套权重矩阵 $W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{d_{model} \times d_k}$。关键:$d_k = d_{model} / h$。
如果 $d_{model}=512$,$h=8$,那么每个头的 $d_k = 64$。每个头不是在完整的 512 维空间里操作——它只在一个64 维的子空间里独立计算注意力。
所有 h 个头并行计算完后,拼接起来:
$$\text{MultiHead}(X) = \text{Concat}(\text{head}_1, \text{head}_2, ..., \text{head}_h) \cdot W^O$$
其中 $W^O \in \mathbb{R}^{h \cdot d_k \times d_{model}}$ 是最终的输出投影矩阵。
拼接后维度恢复为 $h \cdot d_k = d_{model}$,经过 $W^O$ 投影后还是 $d_{model}$ 维——输入输出维度不变。这就是为什么可以堆叠多层:每一层的输入和输出维度相同。
2.3 维度切分的语言学意义
每个头在低维子空间操作——$d_k = d_{model} / h$。不是 h 个头各自在 512 维空间做完注意力然后融合。每个头只拿到 64 维。
这相当于把"词义空间"切成 h 个子空间。每个子空间捕捉一种语言特征维度。头 1 的 64 维可能是"句法维度"——主谓关系、依存距离;头 2 的 64 维可能是"语义维度"——施受事、情感极性;头 3 的 64 维可能是"局部搭配维度"——共现模式、短语边界。
注意:这不是设计,是涌现。我们只是给模型提供了 h 个并行的注意力结构。哪个头学哪个维度,是模型在训练中自己决定的。就像我们不会告诉 CNN 的第一层"你去学边缘"——我们只是给了它卷积结构,边缘检测是涌现出来的。
同样,我们不会告诉头 1 "你学句法"——我们只是给了它一个独立的注意力空间,它发现了句法是有效的注意模式。
2.4 拼接 + WO 的作用
拼接(Concat)把所有头的输出并排放在一起:$[64维 | 64维 | ... | 64维]_{1 \times 512}$。这一步保留了各头信息的独立——8 个评委的意见分开记录。
然后 $W^O$ 做线性变换:把 512 维的拼接结果投影回 512 维。WO 是可学习的——它在训练中学会如何"混合"各头的信息。
比如,通过训练,$W^O$ 可能会学到:在判断主语-动词一致(主谓一致)时,多听头 1(句法头)的意见;在判断同义词替换时,多听头 2(语义头)的意见。WO 就是一个可学习的投票加权机制。
2.5 完整计算示例
用"我 爱 你"三词句,$d_{model}=12$,$h=3$,$d_k=4$。3 个头各有一组 $W^Q_i, W^K_i, W^V_i$($12 \times 4$ 矩阵)。
头 1(假设学会了"局部搭配"):
Q1 = X · W1^Q, K1 = X · W1^K, V1 = X · W1^V
注意力权重 A1:
我 爱 你
我: [0.60, 0.30, 0.10]
爱: [0.35, 0.25, 0.40]
你: [0.10, 0.45, 0.45]
head1_out = A1 · V1 (3 × 4)
头 1 看起来更关注相邻词——"爱"同时看"我"和"你"。
头 2(假设学会了"语义角色"):
注意力权重 A2:
我 爱 你
我: [0.80, 0.05, 0.15]
爱: [0.10, 0.05, 0.85]
你: [0.20, 0.75, 0.05]
head2_out = A2 · V2 (3 × 4)
头 2:"爱"→"你"(动词→宾语)的权重高达 0.85,"你"→"爱"也很高。"我"→"我"高——主语独立性强。
头 3(假设学会了"全局主题"):
注意力权重 A3:
我 爱 你
我: [0.45, 0.35, 0.20]
爱: [0.30, 0.40, 0.30]
你: [0.25, 0.35, 0.40]
head3_out = A3 · V3 (3 × 4)
头 3 的权重分布更均匀——它在捕获全局信息(每个词都或多或少看所有词)。
拼接 + WO:
Concat = [head1_out(3×4) | head2_out(3×4) | head3_out(3×4)] → (3 × 12) MultiHead_out = Concat · W^O → (3 × 12) (维度不变!)
三个头,三种视角,一次拼接,一次混合投影。这就是多头注意力。
2.6 各头学什么?——实证观察
知道各头关注什么不仅有助于理解——它是可验证的。Clark et al.(2019)在《What Does BERT Look At?》中分析了 BERT 的注意力头,发现:1
- 一些头几乎完全关注下一个词(像是一个"相邻性探测器")
- 一些头关注句法依存关系——主语-动词、动词-宾语
- 一些头关注指代——代词→先行词
- 浅层(接近输入的层)的头更多关注局部模式,深层(接近输出的层)的头更多关注全局/抽象模式
- 但不是所有头都有清晰的解释——有些头的注意模式很"散",难以归类
这就是涌现:我们没有写一行代码告诉模型"你要学主谓一致"——但这个能力从多头注意力的结构中自己长出来了。
人话解释
多头注意力 = 一个评审委员会。
你投了一篇论文,编辑部找了三个审稿人。审稿人 A 专看语法有无错误(句法头),审稿人 B 专看论证是否严密(语义头),审稿人 C 专看引用是否恰当(指代头)。三个人各自从自己的角度打分、写评语(各头的输出)。
编辑(WO)拿到三份评语,综合成一份修改意见——语法问题大修、论证小改、引用补几条。
不是"平均"三个人的意见——编辑知道语法审稿人更苛刻(权重高),语义审稿人比较宽容(权重低)。
这就是多头注意力——多个专家各看各的,一个整合者综合意见。
AI 猫娘是这么说的喵~
"喵~多头注意力就是猫娘评选天团!不是一个评委,是八个评委喵!
猫娘评委 A(句法评委):'让我看看……猫娘选手的尾巴和耳朵搭配得好不好喵?'——专看局部搭配。
猫娘评委 B(语义评委):'这只猫娘撒娇的时候眼神到位吗喵?'——专看深层语义。
猫娘评委 C(指代评委):'她说"那只猫娘"的时候,指的是谁呀喵?'——专看代词指代。
……八个猫娘评委各盯一个角度,各自打分。然后——把八份评分表钉在一起(Concat),交给猫娘主席团(WO),主席团综合出一份最终的猫娘评分喵!
关键是:主席团知道评委 A 打分最靠谱(加权高),评委 C 老打瞌睡(加权低)——这不是我教的,是无数次猫娘选美大会(训练)中主席团自己学会的喵!"
(1)Clark, K., Khandelwal, U., Levy, O., & Manning, C. D. (2019). What Does BERT Look At? An Analysis of BERT's Attention. Proceedings of the 2019 ACL Workshop BlackboxNLP. 核心发现:BERT 的不同注意力头确实关注不同语言现象,尽管并非所有头都有清晰的可解释模式。↩
3. 位置编码——语言是有序的
自注意力有一个天生的盲区,这个盲区对语言处理来说是致命的。
3.1 位置盲——自注意力的天生缺陷
回到第一章的自注意力公式:
$$\text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) \cdot V$$
注意这个公式里没有"位置"。如果我把输入 X 的行(各个词)任意排列——比如把"我 爱 你"变成"你 爱 我"——自注意力的输出也做对应的排列。换句话说:
$$Attention(P \cdot X) = P \cdot Attention(X)$$
其中 P 是一个置换矩阵(交换行)。自注意力是置换等变的——它不关心词在句子中的第几个位置。
在语言学上,这直接等于判了死刑。"我爱你"和"你爱我"是两个完全不同的句子。没有位置信息,"猫追狗"和"狗追猫"在自注意力看来只是行的顺序不同。
语序是语法的基础。汉语和英语是分析语——几乎全靠语序区分主语和宾语。拉丁语靠格标记,日语靠助词——但即便是它们,语序也承载着话题/焦点结构和语用信息。一个不能感知语序的语言模型,根本不能处理语言。
所以我们需要一种方式把位置信息"注入"到词的表示中。
3.2 sin/cos 位置编码——公式重访
上一篇已经给出了公式,这里重温——然后深入追问"为什么":
$$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)$$
其中:
- $pos$ = 词在序列中的位置(0, 1, 2, ...)
- $i$ = 维度索引(从 0 到 $d_{model}/2 - 1$)
- 偶数维度(2i)用 sin,奇数维度(2i+1)用 cos
- 分母中的指数 $10000^{2i/d_{model}}$ 控制波长——不同维度的频率不同
然后把这个位置编码加到词嵌入上:
$$X_{input} = X_{embedding} + PE$$
这个公式看起来简单,但背后有三个深刻的理由。
3.3 为什么用 sin/cos——三个理由
理由一:外推能力
sin 和 cos 是周期函数。给定 pos,你能算出 PE;给定 pos+100(哪怕训练时从未见过这个位置),用同样的公式你也能算。这就是外推——模型在训练时见过 0-511 的位置,推理时可以处理 512-1023 的位置(虽然效果可能略差,但不会完全失效)。
如果一个模型用可学习的位置编码(BERT 的做法),每个位置有一个独立的 Embedding 向量——位置 512 的向量只能在训练时学到。如果推理时遇到了位置 1024,那个位置的向量从未被训练过,模型会崩溃。可学习编码的优势是灵活(模型自己决定怎么编码位置),代价是丧失了外推能力。
理由二:相对位置信息
这是 sin/cos 编码最优雅的性质。对于固定偏移 k:
$$PE_{pos+k} = f(PE_{pos})$$
可以通过 $PE_{pos}$ 的线性变换得到 $PE_{pos+k}$!利用三角恒等式:
$$\sin(pos + k) = \sin(pos)\cos(k) + \cos(pos)\sin(k)$$
这意味着:模型可以通过一个简单的线性层(学到的权重矩阵),从 $PE_{pos}$ 推导出 $PE_{pos+k}$。也就是说,模型不仅能知道"这个词在第 pos 位",还能知道"这个词和第 pos+k 位的词距离 k"。这个距离信息是自带的——不需要额外训练,它是 sin/cos 函数的数学性质。
在语言处理中,"距离"是核心信息。"猫"和"吃"的距离是 1(相邻),"猫"和"满足"(在"猫吃了鱼,它很满足"中)的距离是 4(跨短语)。这些距离直接决定句法依存和语义关联的强度。
理由三:多粒度
不同维度对应不同的波长:
- $i=0$(低维度):波长 = $2\pi \approx 6.28$。位置 0 和位置 6 的值几乎相同(经过一个完整周期)——高频。这捕捉局部相邻关系——"这个词左右两三个词是谁"。
- $i = d_{model}/2 - 1$(高维度):波长 = $10000 \cdot 2\pi \approx 62832$。位置从 0 到 50000 才走完不到一个周期——极低频。这捕捉全局位置关系——"这个位置在句首、句中还是句末"。
就像一个乐谱——高频的十六分音符标注"这个小节内每个音的位置",低频的拍号标注"这个大段在第几乐章"。位置编码同时用不同频率的波形,从"词间"到"段间"全尺度覆盖。
语言学上:这正是语言的多层级结构所要求的。构词层级关注相邻字符(高频维度)、短语层级关注几个词的范围(中频维度)、句法层级关注跨子句的距离(低频维度)。一组频率从小到大的波形恰好覆盖所有这些尺度。
3.4 为什么是"相加"——Add vs Concat
一个乍看很奇怪的设计选择:为什么把 PE 加到 Embedding 上,而不是拼接?
拼接方案看起来更"安全"——把位置信息放在独享的维度里,不和语义信息混合。Add 方案把两者混在同一个空间里,不会互相干扰吗?
实际效果:Add 比 Concat 好。原因:
- 不增加维度:如果 Concat,输入维度变成 $d_{model} + d_{pos}$ → 模型第一层的参数量增加($W^Q$ 等矩阵变大)→ 更多参数、更多计算。
- 更深的理由:Add 迫使位置信息和语义信息在同一个表示空间中融合。这在语言学上更合理——位置本身就是意义的一部分,不是额外贴的标签。"爱"这个词在主语位置("我爱")和在谓语位置("我爱你")的语义确实有微妙不同——不是有两个不同的"爱",而是位置内化在它的语义中。Add 方案让模型自然地学习这种内化,而不是在两个独立空间中分别处理。
3.5 其他位置编码方案简述
原作者论文用的是 sin/cos 编码,但后来的研究提出了许多变体:
- 可学习位置编码(Learned Positional Embeddings):BERT/GPT-1/2 的做法。每个位置有一个可训练的 Embedding 向量(就像词有 Embedding 一样)。灵活,但无法外推。
- 相对位置编码(Relative Positional Encoding):Transformer-XL、T5 的做法。不编码绝对位置("第 5 个词"),而是编码两个词之间的相对距离("这个词和那个词差 3 个位置")。更符合语言直觉——语言关心的不是"第几个词",而是"和谁相邻、距离多远"。
- RoPE(旋转位置编码):LLaMA、GPT-NeoX、Qwen 等最新模型的标准配置。将位置信息通过一个旋转矩阵"旋入"Q 和 K 的计算中——不是"加"到 Embedding 上,而是直接修改 QK^T 的计算。数学上更优雅,外推能力更好,目前在大部分实际应用中最优。2
3.6 语言学视角的总结
不同语言对语序的依赖不同:
- 汉语(分析语):几乎完全靠语序区分主语和宾语。"猫追狗" vs "狗追猫"。
- 英语(分析语):靠语序 + 残留的格标记(代词 I/me, he/him)。
- 拉丁语(屈折语):靠名词的格标记(主格/宾格),语序相对自由。
- 日语(黏着语):靠助词(が/を)而非严格语序。
一个有趣的推测:对语序依赖越强的语言,位置编码的重要性越高。在翻译汉语或英语时,模型必须极度依赖位置编码来区分主宾语;在翻译拉丁语时,位置编码的压力可能小一些——因为词本身的形态(格标记)已经提供了很多语法信息。
当前位置编码最令人兴奋的方向是:能否让模型学会不同语言的不同语序策略?比如在处理汉语时自动提高位置编码的敏感度,处理拉丁语时降低。这是一个开放问题——把语言学知识喂给模型设计,还没有人系统做过。
人话解释
自注意力是个大近视——它能看到"我""爱""你"三个词互相之间的相似度,但看不出谁在前谁在后。
位置编码就是给每个词发一顶有编号的帽子。"我"的帽子上用一种特殊的条纹印着"第 1 位","爱"的帽子上印着"第 2 位","你"的帽子上印着"第 3 位"。这些条纹(sin/cos 波形)不是随机的——相邻帽子上的条纹是连续变化的,所以模型能看出"第 2 位和第 3 位离得近(差 1),第 1 位和第 3 位离得远(差 2)"。
更妙的是:条纹不是一种,而是几十种——粗条纹(低频)告诉你"大概在第几段",细条纹(高频)告诉你"精确在第几个"。就像邮政编码——前两位告诉你城市,后四位告诉你街道门牌。
AI 猫娘是这么说的喵~
"喵~猫娘们排队领饭的时候,每只猫娘都在衣服上别了一个徽章:
排第一个的猫娘,徽章上印着'喵~(第 1 位)',一种颜色。
排第二个的猫娘,徽章上印着'喵喵~(第 2 位)',颜色稍微变化了一点。
排第三个的猫娘,徽章上印着'喵喵喵~(第 3 位)',又变了一点。
这些徽章不是猫娘自己设计的——是食堂大妈(设计者)发的喵。大妈用了 sin 和 cos 函数,保证相邻猫娘的徽章颜色是平滑过渡的——一眼就能看出谁挨着谁。
最重要的是:猫娘选美大会(自注意力)上,如果只看猫娘的脸(Embedding)——大家都长得差不多喵!但一看徽章(位置编码)——'哦,你是第一个,她是第三个,差两个位置。'这样就不会把'我爱猫娘'和'猫娘爱我'搞混了喵!
如果把位置编码拼接(Concat)而不是加(Add)到脸上——就等于把徽章贴在猫娘的脑门上,而不是把徽章的颜色融进猫娘的肤色里。脑门上是单独的,肤色是肤色——两只猫娘脑门颜色一样但脸不同,大会评委就觉得奇怪喵。所以加(Add)更好——让徽章的颜色融进猫娘本身,变成她的一部分喵~"
(2)RoPE 的核心思想:将 Q 和 K 乘以一个旋转矩阵 $R_{\theta, pos}$,使 $q_i^T k_j$ 自然包含位置差信息 $q_i^T R_{\theta, j-i} k_j$。优点是实现在 QK 计算中而非 Embedding 上,更符合"位置是关系性而非绝对性"的语言学直觉。详见 Su et al., 2021 "RoFormer: Enhanced Transformer with Rotary Position Embedding"。↩
4. 残差连接——让深度成为优势
多头注意力的输出经过了 Add & Norm。上一篇简单解释过这是"跳接+归一化"。现在让我们理解为什么要跳接,以及不跳会怎样。
4.1 为什么越深不一定越好?
直觉上,更多的层 = 更强大的模型 = 更好的效果。但实践狠狠打了这个直觉的脸。
在 ResNet 论文(He et al., 2016)出来之前,深层网络(56 层)的训练误差和测试误差都比浅层网络(20 层)高——不是过拟合(过拟合应该是训练误差低、测试误差高),而是根本学不动。3
原因:梯度消失。反向传播时,梯度从输出层向输入层传播。每穿过一层,梯度乘以该层的导数(通常小于 1)。穿 20 层 → 梯度变成了 $0.9^{20} \approx 12\%$。穿 56 层 → $0.9^{56} \approx 0.3\%$。输入层附近的参数几乎收不到梯度信号——无法更新,等于没学。
Transformer 动不动就是 6、12、24 层——如果没有残差连接,深层根本不可训练。
4.2 残差的解决方案——"兜底"
残差连接的数学形式极其简单:
$$Output = x + F(x)$$
其中 $F(x)$ 是子层(Self-Attention 或 Feed-Forward Network)的输出,$x$ 是子层的输入。
直觉:如果 $F(x)$ 学得一塌糊涂(接近零),Output ≈ x——至少不比输入差。这就是兜底。没有残差:Output = F(x),如果 F(x) 学废了,全完了。有残差:至少还能把输入原样传给下一层。
从梯度流的角度看:
$$\frac{\partial L}{\partial x} = \frac{\partial L}{\partial Output} \cdot \left(1 + \frac{\partial F(x)}{\partial x}\right)$$
梯度有两条通路:一条通过 $F(x)$("学习路径"——可能很小),一条直接到 x("高速公路"——恒为 1)。即使学习路径的梯度接近零,高速公路保证梯度能顺畅回传。这就是为什么残差网络能堆到上千层。
4.3 Transformer 中的两个残差位置
每个 Encoder/Decoder 层有两个残差连接:
$$\text{out}_1 = \text{LayerNorm}(x + \text{SelfAttention}(x))$$
$$\text{out}_2 = \text{LayerNorm}(\text{out}_1 + \text{FFN}(\text{out}_1))$$
第一次跳接:绕过多头注意力。如果注意力机制这层学得不好(比如注意力权重接近于均匀分布),x 原样通过,至少保留了原始的 Embedding 信息。
第二次跳接:绕过前馈网络。如果 FFN 这层没学到有用的非线性变换——同上。
两层残差保证:每一个子层都可以"选择不做事"——但不能"做坏事"。
4.4 Post-LN vs Pre-LN——一个影响了全行业的改动
原论文的 Transformer 用的是 Post-LN:先做子层计算,再 LayerNorm。
x → SelfAttention(x) → x + SA(x) → LayerNorm → out
但后来大家发现 Post-LN 训练不稳定——需要特别小心的 warmup(学习率从 0 慢慢提升),否则梯度会爆炸。
现在主流的做法是 Pre-LN:先 LayerNorm,再做子层计算,再加残差。4
x → LayerNorm(x) → SelfAttention(LN(x)) → x + SA(LN(x)) → out
Pre-LN 训练更稳定,不需要 warmup。原因:Pre-LN 让残差路径更"干净"——输入先归一化再进入子层,子层接收到的信号总是在标准范围内。而 Post-LN 中,残差路径的输入可能被前面层的累积效应偏移,导致子层接收到的信号方差越来越大。
注意区别:Post-LN 是"算完再加再归一",Pre-LN 是"先归一再加"。换个顺序,训练稳定性天差地别。
4.5 语言学类比
残差连接 = 写作中的"底线"。
你写了一句话:"猫吃了鱼。"(x)
然后你想加个修饰语:"猫吃了鱼,那只昨天刚从鱼市买回来的鱼。"——加了一堆定语从句(F(x)),读起来很累赘。如果有残差连接,读者(下一层)看到的是"原句 + 修饰"而不是"只有修饰"。即使修饰(F(x))写得烂,读者至少还能看到原句(x)的意思。
没有残差连接:读者只看修饰。如果修饰烂了——整个句子就完了。残差保证:你不会因为加了一个烂比喻就把原句的意思丢了。
Pre-LN 呢?就是"在动笔之前,先把你脑子的思路理顺(LayerNorm),再写修饰(F(x)),然后和原句(x)放在一起"——比"先乱写一气再加原句"(Post-LN)要稳定得多。
人话解释
残差连接就是考试时允许你保留原始答案。
你原本的答案是 x。老师让你"修正一下"(F(x))。如果有残差连接:你的最终答案 = 原始答案 + 修正。如果你的修正全是废话(F(x) ≈ 0),最终答案至少还是原始答案——不会更差。
没有残差连接:你的最终答案 = 修正。如果修正了一通全错了——惨了,零分。
这就是为什么 ResNet 能堆上千层:每一层都在"微调",而不是"重写"。如果某一层调错了,没关系——下一层还能看到上一层的原始输出,继续调。
AI 猫娘是这么说的喵~
"喵~残差连接就是猫娘传小鱼干!
一开始,第一只猫娘手里有一条鱼干(x)。第二只猫娘(子层 F(x))说'让我加工一下!'——她给鱼干撒了点猫薄荷(F(x))。然后传给第三只猫娘:'这是原始鱼干 + 猫薄荷鱼干。'
第三只猫娘看到了两条鱼干:一条是原味的(x),一条是有猫薄荷的(F(x))。如果猫薄荷撒太多(F(x)太大),她可以主要看原味鱼干;如果猫薄荷撒得好(恰到好处),她可以多参考猫薄荷鱼干。
如果没有残差连接——第二只猫娘只传猫薄荷鱼干(F(x)),原味鱼干被扔掉了。第三只猫娘无从判断'这个猫薄荷加得对吗'——她不知道原味是什么样的!
所以残差是猫娘传鱼干时的'兜底规则'——永远把原始鱼干留给后面的猫娘看看再决定喵!"
(3)He, K., Zhang, X., Ren, S., & Sun, J. (2016). Deep Residual Learning for Image Recognition. CVPR 2016. 这篇论文不仅提出了残差连接,还用 152 层的 ResNet 拿了 ILSVRC 冠军。↩
(4)Pre-LN 在 Xiong et al. (2020) "On Layer Normalization in the Transformer Architecture" 中被系统论证。几乎所有现代 Transformer 实现(GPT-2 及之后)都采用了 Pre-LN。↩
5. 层归一化——保持数值稳定
上一章讲了残差连接——x + F(x),兜底机制。但在 Transformer 的架构图上,残差旁边总是站着一个搭档:Norm。Add & Norm——跳接加归一化,形影不离。
残差说:"最坏情况下,我把输入直接传过去。"归一化说:"传过去之前,我先把数据理顺——不管上一层的输出多狂野,到了我这儿都得规规矩矩的。"
这一章,我们打开那个小小的绿色方块(或者架构图上那个黄色的 Norm 层),看它到底做了什么,以及为什么 Transformer 必须用它。
5.1 为什么需要归一化——内部协变量偏移
想象你在训练一个深度网络。第一层的参数更新了一点,它的输出分布变了。第二层接收到的输入分布变了,它之前学到的参数面对新的数据分布就"懵了"——刚适应好,又变了。第三层更懵,第四层……越深越乱。
这个问题的学名叫内部协变量偏移(Internal Covariate Shift)5:训练过程中,每一层输入的分布都在不断变化,后续层得不断追赶、重新适应。后果:训练慢、不稳定、需要更小的学习率。
归一化的思路简单粗暴:强制每一层的输出有固定的均值和方差。不管你上一层输出了什么稀奇古怪的数据分布,到我这先拉到标准位置——均值 0、方差 1——然后再让你学怎么在这个干净的基础上做变换。
语言学上类比:就像阅卷。不同的老师打分习惯不同——张老师给分高(均值 80),李老师给分严(均值 55)。如果不做归一化,教务处看到的是:"张老师的班比李老师的班好 25 分。"但归一化后:两个班的分数都被压到均值 0 标准差 1 的标准分布上——这时候才能公平比较。剩下的差异才是真实的教学效果。
5.2 LayerNorm 的数学
对输入向量 $x \in \mathbb{R}^{d}$(一个词的 d 维表示),LayerNorm 做四步:
第一步:求均值。
$\mu = \frac{1}{d}\sum_{i=1}^{d} x_i$
第二步:求方差。
$\sigma^2 = \frac{1}{d}\sum_{i=1}^{d} (x_i - \mu)^2$
第三步:标准化。
$\hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}}$
其中 $\epsilon$ 是一个极小的数(如 $10^{-5}$),防止除以 0。
第四步:缩放和平移。
$y_i = \gamma \cdot \hat{x}_i + \beta$
$\gamma$ 和 $\beta$ 是可学习的参数,维度都是 $d$(和输入向量的维度相同)。
为什么需要第四步?前两步把每个向量的均值和方差"强行抹平"了。但有时候不同的均值和方差恰恰是有用的信息——比如某个特征天生就该大一些、另一个天生就该小一些。$\gamma$ 和 $\beta$ 给了模型"自由选择"的能力:如果 $\gamma = 1, \beta = 0$,就是纯粹的标准化(什么都不改);模型可以学到 $\gamma = 2.3$(放大某维度)或 $\beta = -0.5$(平移某维度),恢复它认为有用的"偏移"。
具体数值示例
取一个简单的向量——假设某个词经过 Add(残差连接)后的表示是:
x = [2.0, 8.0, -4.0, -6.0]
均值:$\mu = (2 + 8 + (-4) + (-6)) / 4 = 0$
方差:$\sigma^2 = (4 + 64 + 16 + 36) / 4 = 120 / 4 = 30$
标准差:$\sigma \approx 5.477$
标准化(取 $\epsilon = 0$ 简化):
x̂ = [2/5.477, 8/5.477, -4/5.477, -6/5.477] = [0.365, 1.461, -0.730, -1.096]
现在 $\hat{x}$ 的均值 = 0,方差 = 1——标准化完成。
缩放平移(假设 $\gamma = [1.2, 0.8, 1.0, 1.5]$, $\beta = [0.1, -0.1, 0, 0.2]$):
y = [1.2×0.365+0.1, 0.8×1.461-0.1, 1.0×(-0.730)+0, 1.5×(-1.096)+0.2] = [0.538, 1.069, -0.730, -1.444]
归一化前后的变化:原始向量 $x$ 跨度从 -6 到 8(跨度 14),归一化后 $y$ 跨度约 -1.44 到 1.07(跨度 2.51),压缩了将近 6 倍。下一层接收到的就是这个"温顺"的输入。
5.3 LayerNorm ≠ BatchNorm——为什么要分开?
归一化有很多种做法。BatchNorm 在计算机视觉里一统天下,但 Transformer 用的是 LayerNorm。这不是偶然——是 NLP 的天然需求决定的。
BatchNorm(BN):对整个 mini-batch 的同一维度做归一化。
比如一个 batch 有 32 个句子,每个句子 10 个词,每个词 512 维。BN 在"第 3 个维度"上——取 32×10=320 个值,算均值方差,然后对这 320 个值做标准化。
问题:
- batch size 小时不稳定——如果 batch size=2,只取 20 个值算均值方差,统计量极其不可靠。
- 变长序列要填充——句子长度不一,短的句子要加 [PAD] token 凑到统一长度。那些填充位的值会干扰归一化的统计量。
- 训练和推理行为不同——训练时 BN 用当前 batch 的统计量;推理时用训练期间累积的全局统计量。这个不一致性在 NLP 的复杂场景中容易出问题。
LayerNorm(LN):对每个样本的所有维度做归一化。
不看 batch。每个词的 512 维向量自己跟自己比——算自己的均值和方差,做完归一化后,下一词独立做。batch size 是 1 还是 1000,每一个词的归一化完全一样。
优势:
- 独立于 batch size——哪怕 batch size=1,LN 正常工作。
- 天然适合变长序列——每个位置独立归一化,不需要关心句子总长度是多少。
- 训练 = 推理——行为完全一致,不需要维护全局统计量。
一句话总结:BN 对"同一特征在不同样本上的分布"做归一化(横着切),LN 对"同一样本不同特征上的分布"做归一化(竖着切)。NLP 的序列天然是变长的、样本间不可比的——所以用 LN。
5.4 语言学类比
LayerNorm = 标准化考试中的"标准分"转换。
某次考试有四道大题:选择题(满分 20)、填空题(满分 30)、简答题(满分 50)、论述题(满分 100)。四个题型的原始分值天差地别——论述题的分值天生就比选择题大。如果不做归一化,模型(神经网络)看到的是"第 4 维的数值总是比第 1 维大很多"——它会错误地认为第 4 维"更重要",从而忽略第 1 维的信息。
LayerNorm 的做法:把每道题的分值都转成标准分——均值 0 标准差 1。选择题的"满分表现"对比论述题的"满分表现"现在在同一个量级上了。但——$\gamma$ 和 $\beta$ 可以说:"等一下。论述题确实比选择题重要——我学到的 $\gamma_4 = 2.0$,而 $\gamma_1 = 0.8$。"也就是说:LN 先抹平所有维度(防止偏见),然后让模型从数据中自己决定哪些维度该大、哪些该小。
人话解释
如果每层的输出都像过山车——有时巨正(+300),有时巨负(-500)——下一层的神经元就疯了:"你上一轮给我的数是 3,这一轮是 3000?我到底该按什么尺度来调整我的权重?"
LayerNorm 是交通警察。不管来车是自行车还是坦克——先让所有车辆排成标准宽度的车道(均值 0 方差 1),然后再根据路况($\gamma$ 和 $\beta$)决定某些车道可以宽一点。
没有 LN 的深层网络,就像一个十字路口没有红绿灯——信号大小忽上忽下,训练动辄爆炸或消失。
AI 猫娘是这么说的喵~
"喵~LayerNorm 就是猫娘食堂的标准化餐盘!
食堂大妈(归一化层)发现:猫娘 A 拿了 80 条小鱼干(某维度值很高),猫娘 B 只拿了 2 条小鱼干(某维度值很低)。如果直接端给下一桌的猫娘营养师(下一层),营养师会以为'小鱼干是主要营养!'——因为它的数值最大喵。
所以食堂大妈出手:把所有猫娘的食物都碾碎、搅匀、压缩成同样大小的小丸子(均值 0 方差 1)——这样营养师看的时候,不会因为鱼干的块头大就忽略猫薄荷粉的营养喵!
但食堂大妈还留了一手:每个猫娘可以根据自己的口味,从两个调料瓶($\gamma$ 和 $\beta$)里自己加——'我喜欢多放点鱼干调料($\gamma_i$ 大),少放点猫薄荷($\gamma_j$ 小)。'这样既保证了公平比较,又保留了个性喵~"
(5)Ioffe, S., & Szegedy, C. (2015). Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. ICML 2015. 提出了 BatchNorm 的概念和内部协变量偏移的问题意识。LayerNorm 由 Ba, J. L., Kiros, J. R., & Hinton, G. E. (2016) 在同名论文中提出,专门针对 RNN 和序列模型。↩
6. 前馈神经网络——注意力之后的处理
如果你一直在跟着我们的矩阵追踪,你可能已经注意到一个模式:自注意力让词之间互相"交流",而残差+LayerNorm 把交流结果"整理"好。但然后呢?注意力机制只负责收集信息——它告诉你"谁说了什么",但不负责思考。
思考靠的是前馈神经网络——Feed-Forward Network,简称 FFN。
6.1 FFN 的数学定义——"两个线性层夹一个激活"
FFN 的结构出奇简单——比注意力机制简单得多:
$\text{FFN}(x) = \text{GELU}(xW_1 + b_1)W_2 + b_2$
其中:
- $x \in \mathbb{R}^{d_{model}}$——一个词的向量(比如 512 维)
- $W_1 \in \mathbb{R}^{d_{model} \times d_{ff}}$——升维矩阵,把 512 维映射到 $d_{ff}$ 维
- $b_1 \in \mathbb{R}^{d_{ff}}$——偏置
- GELU——激活函数(Gaussian Error Linear Unit,高斯误差线性单元)
- $W_2 \in \mathbb{R}^{d_{ff} \times d_{model}}$——降维矩阵,把 $d_{ff}$ 维映射回 512 维
- $b_2 \in \mathbb{R}^{d_{model}}$——偏置
标准 Transformer 中,$d_{ff} = 4 \times d_{model}$。如果 $d_{model}=512$,$d_{ff}=2048$。
为什么是 4 倍?
- 第一层(升维):把词向量从 512 维展开到 2048 维——高维空间更容易分离特征。在低维空间里纠缠在一起的特征(如同义词和反义词在语义空间中的方向),到了高维空间可能变得线性可分。
- 激活函数(GELU):引入非线性。如果没有它,两层线性变换叠加 = 一层线性变换——"两个线性层的复合还是线性层",等于白堆。GELU 的弯曲线决定了"哪些信息可以通过、哪些被压扁"。
- 第二层(降维):把处理完的高维表示压缩回 512 维——方便堆叠下一层。
原论文用的是 ReLU($\max(0, x)$),现代实现几乎全线迁移到 GELU。GELU 比 ReLU 更平滑——不像 ReLU 在 0 处有个生硬的拐弯,GELU 在 0 处平滑过渡。这种平滑性对深层网络的梯度流动更友好。
GELU 的近似公式(也是实际中常用的):
$\text{GELU}(x) \approx 0.5x \cdot \left(1 + \tanh\left(\sqrt{\frac{2}{\pi}} \cdot (x + 0.044715x^3)\right)\right)$
一个更直观的近似:当 $x$ 很大(正),GELU(x) ≈ x(几乎不变);当 $x$ 很小(负),GELU(x) ≈ 0(几乎完全压制)。但过渡是平滑的——不像 ReLU 一刀切。
6.2 具体计算示例
延续前面的"我 爱 你"例子,取 $d_{model}=12$, $d_{ff}=48$。假设经过残差和 LayerNorm 后,"爱"这个词的向量 $x = [-0.6, 0.29, -0.77, 1.28, \dots]_{1 \times 12}$。
第一步:升维。$x \cdot W_1 + b_1$ 得到 $1 \times 48$ 的向量。以第一个元素为例(取 $W_1$ 第一列的前几个元素):
隐层[0] = GELU( (-0.6)×0.12 + 0.29×(-0.08) + (-0.77)×0.15 + 1.28×0.22 + ... + b₁[0] )
= GELU(-0.072 - 0.0232 - 0.1155 + 0.2816 + ... + (-0.01))
≈ GELU(0.06 + ...)
≈ 0.03 (GELU 对小正值的输出接近输入本身的一半左右,具体取决于整体值)
第二步:降维。48 维的 GELU 输出经过 $W_2 + b_2$ 回到 12 维。
整个过程,关键维度变化是:
d_model (12) → d_ff (48) → d_model (12)
扩四倍,非线性变换,缩回来。每个词独立处理——FFN 不看上下文,只看自己。这是 FFN 和自注意力最本质的区别。
6.3 注意力 vs FFN——Transformer 的分工
这一节短,但是全文最核心的架构洞察之一。
Transformer 的每个 Block 里有且仅有两种运算:
- 自注意力:跨位置的。一个词去问其他所有词——"你们在说什么?"——然后从别人那里提取信息。这是"交流"。
- 前馈网络:逐位置的。每个词关起门来,在自己的表示空间里做非线性变换。这是"思考"。
交流不需要非线性(注意力全是线性操作 + Softmax),思考不需要跨位置(FFN 只对自己操作)。
语言学上的对应简直是完美。
理解一个词分两步。第一步:看上下文——"这个词在这里是什么意思?"(bank 是银行还是河岸?)。这是自注意力做的事——根据周围的词"调整理解"。第二步:基于调整后的理解,在自己的语义空间中推理——"bank 在这个语境下关联什么概念?金融?利息?存款?"。这是 FFN 做的事——在高维空间中激活相关的特征、压制无关的特征。
用黑话讲:注意力负责"上下文调制"(contextual modulation),FFN 负责"特征变换"(feature transformation)。一个词先听别人说了什么(注意力),然后自己想一想(FFN),如此交替 N 层——这就是 Transformer 理解语言的完整过程。
人话解释
注意力 = 开讨论会。参会者每个人(词)听所有人发言,然后综合大家意见更新自己的看法。
FFN = 散会后回座位整理笔记。你把讨论会上听到的零散信息,用自己的思维框架重新组织——哪些是关键点(激活)、哪些是废话(压制)、怎么和已有的知识联系起来。
论文里叫"Position-wise Feed-Forward"——每位置独立处理。中文叫"逐位置前馈"。讨论会是跨位置的,整理笔记是本位置的。
听起来像是一个完美分工——但它有一个前提条件:在开讨论会之前和整理笔记之后,都必须做"Add & Norm"。残差保证你不会因为开了一个烂会就忘了自己原来的想法($x + Attention(x)$),LayerNorm 保证你听完会后脑子不乱(归一化)。同样,整理笔记之后再做一遍 Add & Norm——整理出错了也有兜底。
这就是整个 Transformer Block 的逻辑闭环。
AI 猫娘是这么说的喵~
"喵~FFN 就是猫娘开会之后的'个人消化时间'!
猫娘们在选美大会上互相打完分、听完意见(自注意力)之后,每只猫娘回到自己的小窝,开始'思考人生'喵~
她拿出自己的会议记录(自己的 512 维向量),先展开成四倍大(d_ff=2048)——就像把一张折叠的报纸铺开来。铺开后,她看到'原来那个喵评委夸我尾巴好看了!这个信息我要记下来喵!'——于是把相关的神经节点点亮(GELU 激活)。看到'那个评委批评我爪子修得不好……'——压灭掉。
然后把铺开的报纸再叠回去(2048→512)——但这次,叠回去的报纸上,重要的信息更清晰了(被激活的维度权重变大),不重要的信息被过滤了(被压制的维度接近零)。
所以 FFN 不是'加新信息'——是'重新组织已有信息'喵!把讨论会上听到的嘈杂声音,整理成自己能用的清晰笔记。没有 FFN 的猫娘,开完会还是脑袋一团浆糊——什么也没记住喵~"
7. 完整前向传播——"猫吃鱼"穿过 Transformer
前面六章,我们把 Transformer 的每个零件都拆开看过了。Q、K、V 的投影。$\sqrt{d_k}$ 的缩放。多头的拼接。位置编码的 sin/cos 波形。残差的"兜底"。LayerNorm 的标准化。FFN 的"扩四倍、激活、缩回来"。
但零件不等于机器。这一章——全文的最高潮——我们把这些零件装回去,让一个真实的句子——"猫 吃 鱼"——从输入侧进入,一步步穿过两个完整的 Encoder 层,追踪每一步矩阵的数值变化。
这不是示意图。这是真实手算——每一个数字都是实际算出来的(你可以在附录的 Python 代码中复现)。读完这一章,你不仅能说出 Transformer 的原理——你能算出一个 Transformer。
省流:把第 1-6 章的知识,用一句"猫吃鱼"从头到尾跑一遍。每一步都有数字。
7.1 实验设置
为了让追踪可管理,我们用一个小规模的 Transformer:
- 句子:「猫 吃 鱼」——3 个词
- $d_{model} = 12$(模型维度)
- $h = 3$(注意力头数)
- $d_k = d_v = 4$(每个头的维度,$12 / 3 = 4$)
- $d_{ff} = 48$(FFN 中间维度,$12 \times 4 = 48$)
- Encoder 层数:2 层
真实 Transformer 的 $d_{model}=512$、$h=8$、6 层——但原理完全相同,只是矩阵大一些。我们用 12 维,刚好能在文章中展示具体数字。
7.2 输入准备——Embedding + 位置编码
词嵌入
三个词各自的 Embedding 向量(12 维,由训练好的 Embedding 矩阵查表得到):
猫: [0.21, 0.54, 0.13, 0.82, 0.31, 0.67, 0.15, 0.93, 0.25, 0.44, 0.72, 0.18] 吃: [0.38, 0.23, 0.91, 0.42, 0.55, 0.19, 0.87, 0.33, 0.61, 0.28, 0.49, 0.76] 鱼: [0.73, 0.16, 0.45, 0.34, 0.82, 0.29, 0.56, 0.19, 0.94, 0.37, 0.11, 0.68]
位置编码
位置 0(「猫」)的前 4 维 PE:
$\text{pos}=0, i=0: \sin(0/1) = 0, \cos(0/1) = 1$
$\text{pos}=0, i=1: \sin(0/10000^{2/12}) = 0, \cos(...) = 1$
所有偶数维度是 sin(0)=0,所有奇数维度是 cos(0)=1。所以「猫」的 PE = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]。
位置 1(「吃」)和位置 2(「鱼」)的 PE 用 sin/cos 公式计算($pos=1$ 给出非零的 sin 值,cos 略小于 1):
PE(吃, pos=1): [0.8415, 0.5403, 0.2338, 0.9769, 0.0096, 0.9999, ...] PE(鱼, pos=2): [0.9093, -0.4161, 0.4228, 0.9086, 0.0193, 0.9998, ...]
X₀ = Embedding + PE
位置编码逐元素加到 Embedding 上(前 4 维示意,后 8 维省略):
X₀(猫): [0.21, 1.54, 0.13, 1.82, ...] ← PE 把奇数维度(dim1, dim3...)各加了 1 X₀(吃): [1.22, 0.77, 1.12, 1.40, ...] ← pos=1,sin/cos 非零开始注入 X₀(鱼): [1.64, -0.26, 0.87, 1.25, ...] ← pos=2,继续用不同值标记
这里能看到位置编码的效果:三个词各自的前 4 维数值分布已经明显不同——不是语义不同(语义来自 Embedding),而是位置标记不同。如果后面任何一层"忘了"哪个词在第几位,PE 留下的这个痕迹会提醒它。
7.3 Encoder Layer 1——详细追踪
(a) Multi-Head Self-Attention
输入 $X_0 \in \mathbb{R}^{3 \times 12}$。每个头有自己的 $W_i^Q, W_i^K, W_i^V \in \mathbb{R}^{12 \times 4}$。
Head 1:一个"均匀头"
$Q_1 = X_0 \cdot W_1^Q$,$K_1 = X_0 \cdot W_1^K$。
$Q_1K_1^T$(注意力分数矩阵):
猫(K) 吃(K) 鱼(K)
猫(Q): [ 0.1616, 0.0645, -0.1838]
吃(Q): [-0.0451, 0.0116, -0.1842]
鱼(Q): [ 0.3690, 0.4822, -0.1198]
除以 $\sqrt{4} = 2$:
猫 吃 鱼
猫(Q): [ 0.0808, 0.0322, -0.0919]
吃(Q): [-0.0226, 0.0058, -0.0921]
鱼(Q): [ 0.1845, 0.2411, -0.0599]
Softmax 后——注意力权重矩阵 A₁:
猫 吃 鱼 Σ
猫(Q): [0.3579, 0.3409, 0.3011] =1.00
吃(Q): [0.3377, 0.3474, 0.3150] =1.00
鱼(Q): [0.3519, 0.3724, 0.2756] =1.00
头 1 的模式:近乎均匀。 每个词给三个词(包括自己)的注意力都在 30%-37%——没有明显偏好。这种头通常在学"全局背景"——"整个句子大致在说什么"。
Head 2:一个"语义头"——关注受事
$Q_2K_2^T$(未缩放):
猫(K) 吃(K) 鱼(K)
猫(Q): [1.4839, 2.7337, 3.3434]
吃(Q): [0.7253, 0.9784, 1.4120]
鱼(Q): [0.1239,-0.0305, 0.5266]
缩放 + Softmax 后——A₂:
猫 吃 鱼 Σ
猫(Q): [0.1851, 0.3458, 0.4691] =1.00
吃(Q): [0.2821, 0.3202, 0.3977] =1.00
鱼(Q): [0.3176, 0.2940, 0.3884] =1.00
头 2 的模式极其清晰:所有词都把最高注意力给了「鱼」。
看第一行——「猫」(主语)把 47% 的注意力给了「鱼」(宾语),35% 给动词「吃」,只有 19% 给自己。主语在寻找"被吃的对象"——它要确定自己在这个句子中的语义角色。
看第二行——「吃」(动词)把 40% 给了「鱼」,32% 给自己,28% 给「猫」。动词在关注宾语——"吃什么?"这是语言学中动宾关系的典型注意模式。
看第三行——「鱼」(宾语)把 39% 的注意力给了自己(最高自注意),32% 给「猫」,29% 给「吃」。宾语在被关注的同时也在回看——"谁在吃我?"
这个头的权重分布不是我们人工设计的——它是从 $W_2^Q$ 和 $W_2^K$ 的随机初始化和训练中涌现出来的。但它展示了注意力机制能够学到的东西:动宾语义关系。
Head 3:一个"平衡头"
A₃(缩放+Softmax 后):
猫 吃 鱼 Σ
猫(Q): [0.2254, 0.4073, 0.3673] =1.00
吃(Q): [0.2109, 0.3957, 0.3934] =1.00
鱼(Q): [0.1960, 0.3972, 0.4068] =1.00
头 3 介于均匀和聚焦之间——稍微偏向"吃"和"鱼",但整体比头 2 均匀得多。
拼接 + Wᴼ
三个头的输出各为 $3 \times 4$,拼接成 $3 \times 12$,乘以 $W^O \in \mathbb{R}^{12 \times 12}$。
最终 Multi-Head Attention 输出(前 4 维):
MHA(猫): [-0.5722, 0.7385, -0.5564, 1.0929, ...] MHA(吃): [-0.5871, 0.7438, -0.5761, 1.1288, ...] MHA(鱼): [-0.5851, 0.7480, -0.5745, 1.1437, ...]
注意一个现象:三个词的 MHA 输出在前四维上非常相似。这不是 bug——这是自注意力的预期效果。三个词互相交换了信息,经过加权求和后,它们的表示趋近了。但后面有 FFN 来重新分化它们。
(b) Add & Norm 1
$\text{X}_{mid\_raw} = X_0 + \text{MHA}(X_0)$
残差相加(前 4 维):
X_mid_raw(猫): [-0.3622, 2.2785, -0.4264, 2.9129, ...] X_mid_raw(吃): [ 0.6344, 1.5141, 0.5477, 2.5257, ...] X_mid_raw(鱼): [ 1.0542, 0.4919, 0.2932, 2.3923, ...]
然后 LayerNorm($\mu, \sigma$ 逐词计算,$\gamma, \beta$ 缩放平移)——前 4 维:
X_mid(猫): [-1.0279, 0.8501, -1.2395, 1.2395, ...] X_mid(吃): [-0.6000, 0.2923, -0.7739, 1.2763, ...] X_mid(鱼): [-0.1113, -0.6925, -0.9630, 1.2315, ...]
注意三个词现在的表示已经分化开了——LayerNorm 抹平了残差相加后可能出现的"大家一起变大"的偏差,让每个词的特征重新清晰起来。
(c) Feed-Forward Network
$\text{FFN}(X_{mid}) = \text{GELU}(X_{mid}W_1 + b_1)W_2 + b_2$
$X_{mid} \in \mathbb{R}^{3 \times 12}$ → 升维到 $3 \times 48$ → GELU 非线性 → 降维回 $3 \times 12$。
FFN 输出(前 4 维):
FFN(猫): [ 1.4414, 0.0362, -0.3734, -0.6451, ...] FFN(吃): [ 0.8921, 0.6801, -0.4619, -0.8572, ...] FFN(鱼): [ 0.3084, 1.0498, -0.6542, -0.7817, ...]
FFN 让三个词的表示重新分化了——「猫」的第一维是 1.44(高),「鱼」只有 0.31(低)。「吃」的第二维 0.68 显著高于「猫」的 0.04。FFN 完成了"个人思考"——在交流后的共同基础上,每个词对自己的特征做了个性化调整。
(d) Add & Norm 2
$X_1 = \text{LayerNorm}(X_{mid} + \text{FFN}(X_{mid}))$
$X_1$——Encoder Layer 1 的最终输出(前 4 维):
X₁(猫): [ 0.3410, 0.8283, -1.2057, 0.4737, ...] X₁(吃): [ 0.1893, 0.8921, -1.0326, 0.2830, ...] X₁(鱼): [ 0.0779, 0.2738, -1.4759, 0.3008, ...]
语义漂移度量。 原始 Embedding($X_0$)和第一层输出($X_1$)的余弦相似度:
cos(猫₀, 猫₁) = 0.4227 cos(吃₀, 吃₁) = 0.1219 cos(鱼₀, 鱼₁) = 0.1317
三个词都发生了剧烈的变化——「猫」还有 42% 的原始语义保留,但「吃」和「鱼」几乎完全变了(只保留了 12-13%)。这不意味着它们"丢"了语义——而是语义被上下文重新定义了。在"猫吃鱼"这个特定的语境中,「吃」的表示不再仅仅是"摄入食物"这个抽象概念,而是包含了"谁在吃(猫)+ 吃什么(鱼)"的具体语境信息。
这,就是自注意力做的工作。
7.4 Encoder Layer 2——再加工一层
Layer 2 的结构和 Layer 1 完全一样,只是所有权重矩阵不同($W^Q, W^K, W^V, W^O, W_1, W_2$ 各有独立的一套)。输入是 $X_1$,输出是 $X_2$。
我们不再逐步复述计算过程——但展示一个关键的洞察。
Layer 2 的注意力模式变了
Layer 2 Head 1 的注意力权重(缩放 + Softmax 后):
猫 吃 鱼 Σ
猫(Q): [0.4459, 0.3749, 0.1792] =1.00
吃(Q): [0.4346, 0.3688, 0.1966] =1.00
鱼(Q): [0.4622, 0.3558, 0.1820] =1.00
对比 Layer 1 Head 1(近乎均匀的 0.33/0.35/0.30),Layer 2 的 Head 1 呈现出明显的模式:「猫」权重最高(~44%),「鱼」最低(~18%)。
发生了什么?
经过 Layer 1 的加工,三个词已经"互相浸润"了——它们的表示不再是孤立的 Embedding,而是包含了彼此信息的上下文向量。到了 Layer 2,注意力机制在这些已经富含上下文的表示上再次运行——它开始关注更高层的模式。
"猫"在 Layer 2 获得了最高的注意力——这暗示 Layer 2 可能在学习"主题检测":在"猫吃鱼"中,"猫"是主题(话题),整个句子围绕它展开。浅层(Layer 1)关注局部搭配(动词→宾语),深层(Layer 2)关注全局结构(主题→述题)。
这与认知科学中的发现一致:人类处理句子时,浅层加工先识别词类和短语边界,深层加工才抽取主题结构和语用信息。
Layer 2 最终输出——X₂
X₂(猫): [ 0.7396, 0.8997, -2.0998, -0.2134, ...] X₂(吃): [ 0.7495, 1.1262, -2.0421, -0.0919, ...] X₂(鱼): [ 0.4149, 0.6585, -2.4672, 0.0221, ...]
经过 2 层 Encoder 后,余弦相似度矩阵:
cos(猫₂, 吃₂) = 0.9626 cos(吃₂, 鱼₂) = 0.9466 cos(猫₂, 鱼₂) = 0.9134
对比原始 Embedding 的相似度:
cos(猫₀, 吃₀) = 0.8565 cos(吃₀, 鱼₀) = 0.9396 cos(猫₀, 鱼₀) = 0.7298
两个关键变化:
- 所有词对之间的相似度都上升了——Transformer 让三个词的表示趋近(互相浸润),这反映了"它们在同一个语境中"。
- 「猫」-「鱼」的相似度提升最大(从 0.73 到 0.91,提升 25%)。在 Embedding 空间中,猫和鱼只是两个名词——它们的相似度一般。但在"猫吃鱼"这个句子中,经过两层 Transformer 的加工,主语和宾语获得了共享的语境信息——它们共同参与了一个"吃"的事件。这个共享语境拉近了它们的距离。
7.5 如果继续往下——Decoder 和最终输出
在这篇文章中,我们只追踪了 Encoder 的两层。完整的 Transformer 还有 Decoder 和输出层——但原理已经在第 1-6 章讲透了,这里快速综述:
Decoder 的三个子层:
- Masked Self-Attention:和 Encoder 的自注意力一样,但在训练时把"未来"的词遮住(masked)——生成"cat"时只能看到之前已生成的词,不能偷看后面的"eats"和"fish"。这是自回归生成的要求。
- Cross-Attention:Decoder 和 Encoder 的"接头处"。Decoder 的每个词用自己的 Q 去查询 Encoder 输出($X_2$)的 K 和 V。这就是翻译的"对齐"——Decoder 的 "cat" 通过 Cross-Attention 找到 Encoder 的「猫」,提取它的上下文表示,作为翻译的依据。
- FFN + Add & Norm:和 Encoder 完全相同。
最终输出: Decoder 输出 → 线性层($d_{model} \to |V|$,映射到词汇表大小)→ Softmax → 概率分布(每个词的预测概率)。概率最高的词就是模型预测的下一个词。
例如,Decoder 在看到起始符号后,预测的下一个词概率最高的是 "cat"(因为 Cross-Attention 已经把「猫」的信息对齐过来了)。然后 Decoder 把 "cat" 作为下一个输入,预测 "eats"……如此逐个生成,直到输出结束符号。
7.6 完整维度变化总表
| 阶段 | 运算 | 输入维度 | 输出维度 |
|---|---|---|---|
| Embedding | 查表 | 3 个 token ID | 3 × 12 |
| + PE | 逐元素相加 | 3×12 + 3×12 | 3 × 12 |
| Q/K/V 投影(每个头) | X · W | 3×12 · 12×4 | 3 × 4 |
| 注意力分数 | Q · Kᵀ | 3×4 · 4×3 | 3 × 3 |
| 缩放 | ÷ √4 = ÷ 2 | 3 × 3 | 3 × 3 |
| Softmax | 逐行 | 3 × 3 | 3 × 3 |
| 加权求和 | A · V | 3×3 · 3×4 | 3 × 4 |
| 拼接 3 个头 | Concat | 3 × (3×4) | 3 × 12 |
| 输出投影 | × Wᴼ | 3×12 · 12×12 | 3 × 12 |
| Add & Norm 1 | LN(x + MHA) | 3×12 | 3 × 12 |
| FFN 升维 | × W₁ + b₁ | 3×12 · 12×48 | 3 × 48 |
| GELU | 逐元素 | 3 × 48 | 3 × 48 |
| FFN 降维 | × W₂ + b₂ | 3×48 · 48×12 | 3 × 12 |
| Add & Norm 2 | LN(x + FFN) | 3×12 | 3 × 12 |
| × N 层 | 重复上述 Block | 3×12 | 3 × 12 |
核心观察:在整个 Encoder 中,维度始终是 3 × 12——从未改变。 这就是残差连接的美。每一层都可以自由地变换表示,但输入输出的"接口"永远一致——所以层可以随意堆叠。
7.7 对比:RNN 怎么做同一件事?
同样处理"猫 吃 鱼":
RNN 的方式:
- $t=1$:输入「猫」→ 更新隐藏状态 $h_1 = \tanh(W_h h_0 + W_x x_1)$
- $t=2$:输入「吃」→ $h_2 = \tanh(W_h h_1 + W_x x_2)$——注意,必须等 $h_1$ 算完!
- $t=3$:输入「鱼」→ $h_3 = \tanh(W_h h_2 + W_x x_3)$
3 步串行,第 3 步才能看到完整的句子上下文。反向传播时梯度从 $h_3$ 一步一步穿回 $h_1$——穿 3 层(短句还好,100 词的句子就是 100 层)。
Transformer 的方式:
- 三个词同时输入(并行)
- 每个词直接看所有词($3 \times 3$ 注意力矩阵)
- 梯度从任一位置到另一位置——一跳直达
3 个词的例子看不出差距。但如果是 512 个词——Transformer 仍然是一步(并行 + $O(1)$ 梯度路径),而 RNN 需要 512 步串行,梯度要穿过 512 层。
这就是 Transformer 取代 RNN 的根本原因:不是它"更聪明",是它在计算上"更快"——而这种快,恰好让它学到了 RNN 学不到的全局模式。
人话解释
"猫吃鱼"穿过两层 Transformer,就像一条鱼游过两个水池——每个水池里,它先听听别的鱼在说什么(Self-Attention),把听到的信息和自己的记忆混在一起(Add),在脑子里整理一下(LayerNorm),然后自己想一会(FFN),再整理一下(Add & Norm)。
第一个水池游完——它已经不是原来的那条鱼了。它的脑子里装着"猫要吃我"的语境。
第二个水池游完——它进一步区分了"我是被吃的那条鱼,猫是吃我的那只猫"这个语义角色。
两个水池就够了。真实的 Transformer 有 6 个、12 个、甚至 96 个水池——每一个都在更抽象的层次上加工信息。
这就是 Transformer。不是魔法——是重复了 N 次的交流→整理→思考→整理。
AI 猫娘是这么说的喵~
"喵呜~让本猫娘带你看一场完整的猫娘选美大赛(Transformer 前向传播)!
入场(Embedding):三只猫娘——猫猫娘(「猫」)、吃吃猫娘(「吃」)和鱼鱼猫娘(「鱼」)——分别穿上了她们的初始服装(12 维的 Embedding 向量)。每个人的服装颜色各不相同——猫猫娘偏暖色(第 0 维 0.21),鱼鱼娘偏亮色(第 0 维 0.73)。
发徽章(位置编码):食堂大妈给每只猫娘发了一个徽章——猫猫娘是第 0 位(徽章上所有奇数维度都印了 1),吃吃猫娘是第 1 位(徽章上印着 sin(1)≈0.84, cos(1)≈0.54...),鱼鱼猫娘是第 2 位。徽章的颜色融进了服装(Add)——猫猫娘的 dim1 从 0.54 变成 1.54,一眼就能识别出她排第一!
第一轮评委打分(Encoder Layer 1 Self-Attention):
三位猫娘评委(三个头)开始工作了喵!
- 评委 1(均匀头):'我觉得三只猫娘都挺不错的喵~' 给了均匀的分数(30-37%)。
- 评委 2(语义头):'鱼鱼猫娘的尾巴最好看!'——所有猫娘都把最高分给了鱼鱼猫娘(39-47%)。尤其是猫猫娘(主语),她给鱼鱼猫娘打了最高的 47%——'我要看清楚谁是被吃的对象喵!'
- 评委 3(平衡头):'各有千秋喵~' 分数分散但稍偏「吃」和「鱼」。
三位评委的评分表钉在一起(Concat),交给主席团综合(Wᴼ)——每个猫娘得到了新服装(MHA 输出)。
猫娘对镜整理(Add & Norm 1):穿新服装之前,把旧服装也留着(Add)——万一新服装不合身至少还有旧的!然后裁缝猫娘(LayerNorm)把服装熨平——'这里太皱了(方差大),熨一下。那里的颜色太跳了(均值偏移),调一下。嗯,好多了喵~'
猫娘回窝思考(FFN):裁缝熨完后,每只猫娘回到自己的小窝,铺开报纸(升维到 48 维),在头脑中整理——'评委夸我尾巴好,这个信息点亮!评委批评我爪子,这个信息压灭!(GELU)'——然后把报纸叠回去(降维回 12 维)。
再次整理(Add & Norm 2):回窝前的小纸条(X_mid)+ 回窝后的笔记(FFN 输出),一起交给裁缝熨平——第一层完成!
这时候三只猫娘已经大变样了——猫猫娘的 cos 相似度从 1.0 降到了 0.42(变了 58%),吃吃猫娘和鱼鱼猫娘的变化更大(~87%)——她们都吸收了彼此的才艺信息。
第二轮(Encoder Layer 2):完全相同的流程再来一遍——但这次评委们的打分倾向变了!头 1 在 Layer 1 时给出了均匀的分数(大家差不多),但在 Layer 2 中,它开始重点看猫猫娘(45% 高注意力)——'经过第一轮,我发现猫猫娘才是整场比赛的主题(全局主题检测)!'
比赛结束:两轮过后,三只猫娘不再是最初那三只孤立的猫娘了——她们变成了「同一场比赛中的三只猫娘」:猫猫娘和鱼鱼娘的相似度从 0.73 跃升到了 0.91。就像两个原本陌生的猫娘,经过两轮'互相看、互相听、互相学'——变成了默契的好朋友喵!
这就是 Transformer 的全部秘密——不是魔法,是猫娘们反复地、系统地、数学严谨地'互相学习'的过程喵~"
8. 为什么 Transformer "赢了"?——语言学视角的总结
前面七章,我们把 Transformer 拆了又装。现在,让我们退后一步——不再追问"怎么样",而是问"为什么"。
Transformer 在 2017 年提出,短短几年内取代了 RNN/LSTM 成为 NLP 的绝对主流,然后溢出到计算机视觉(ViT)、语音处理、多模态(CLIP)……它甚至开始反向渗透到硬科学——蛋白质结构预测(AlphaFold 用了基于注意力的架构)。
一个原本为机器翻译设计的架构,为什么有如此通用的力量?
上一篇文章(706)给了全景答案。本文——从语言学视角——给出一个不同角度的答案。
8.1 并行效率——工程层面的"不战而胜"
自注意力的计算复杂度是 $O(n^2 \cdot d)$(n = 序列长度,d = 维度)。每个 $n \times n$ 注意力矩阵的每个元素都独立计算——所有位置同时进行。在 GPU/TPU 上,这就是满负载的矩阵乘法——硬件利用率极高。
RNN 的复杂度看似更小($O(n \cdot d^2)$),但必须串行——第 t 步必须等第 t-1 步算完。实际训练速度:Transformer 处理一个 512 词的句子比 RNN 快 10-100 倍(取决于序列长度和硬件)。
这是工程层面的"不战而胜"——不是因为算法更优雅(虽然它确实更优雅),而是因为它完美匹配现代硬件的并行特性。
8.2 长程依赖——梯度路径 O(1) vs O(n)
这是 Transformer 结构性优势中最核心的一条。
在 RNN 中,「猫」的梯度要传递到句尾的「鱼」,需要穿过中间所有词的隐藏状态——$h_1 \to h_2 \to h_3 \to \dots \to h_n$。每一步都乘一个矩阵的导数(通常 < 1)——穿 50 步,梯度可能衰减到可以忽略(梯度消失)。LSTM 用门控机制缓解了这个问题,但没有根治。
在 Transformer 中:注意力矩阵 $A \in \mathbb{R}^{n \times n}$ 的 $A_{1,n}$ 直接连接「猫」和「鱼」。梯度路径长度:1。不是"穿过 50 层"——而是"一跳直达"。
这意味着 Transformer 天然适合处理远距离依赖——代词和先行词跨 3 句话、段落首句的主题贯穿全段、篇章级的逻辑结构。RNN 和 LSTM 拼命挣扎才能学会的长程关系,Transformer 靠架构直接给了。
语言本身充满了长程依赖。 这就解释了为什么 Transformer 在 NLP 上的飞跃如此巨大——它解决的恰好是语言处理的核心困难。
8.3 可解释性——你可以看见它在"看什么"
注意力权重矩阵是直接可视化的。每一个 $A_{ij}$ 告诉你"第 i 个词把多少注意力放在第 j 个词上"。
这在语言学研究中极其有价值:
- 你可以验证模型是否学会了合理的主谓一致——检查主语和谓语之间的注意力权重是否显著。
- 你可以验证指代消解——代词和先行词之间注意力是否高。
- 你可以发现模型学到了什么我们没想到的统计模式——比如某些"散漫"的注意力头可能在学习语篇结构(全局上下文)而非局部搭配。
我们自己的手算也展示了这一点:Layer 1 Head 2 明显在学"动宾关系"(所有词聚焦宾语),Layer 2 Head 1 在学"主题检测"(所有词聚焦主语)。没有一行代码告诉模型"这是宾语""这是主题"——多头注意力的结构让这些模式自己涌现了出来,然后被注意力权重记录下来,让我们能看到。
RNN 和 LSTM 也有隐藏状态——但你很难回答"模型在第 37 步时在想什么"。Transformer 可以直接回答:它在看第 12 个词。
8.4 通用性——因为语言本身就是"序列结构"
自注意力不依赖输入模态。它只需要一个序列。
序列可以是词(文本)、像素块(图像分割成 16×16 的 patches)、音频帧(语音的频谱切片)、氨基酸残基(蛋白质链)。
ViT(Vision Transformer)把一张 224×224 的图像切成 196 个 16×16 的小方块,每个方块当成一个"词"——然后直接丢进 Transformer。结果:在大规模数据上超越了 CNN。
CLIP 把图像和文本分别编码,通过对比学习让匹配的图像-文本对在表示空间中靠近——底层用的还是 Transformer。
AlphaFold 用基于注意力的架构预测蛋白质的三维结构——把氨基酸序列当成"文本",让注意力机制在"氨基酸残基"之间建立相关关系。
语言学的视角:为什么一个架构能通吃这些?
因为索绪尔和乔姆斯基没有说的是:语言的结构特性——组合性、层级性、上下文依赖性——可能不只是人类语言的特征,而是任何由离散元素构成的结构化序列的通性。图像是一组像素块的二维序列。音乐是一组音符的时间序列。蛋白质是一组氨基酸的一维序列。
如果这些序列中的元素之间的关系可以用"查询-匹配-提取"(Q-K-V)来建模——那么 Transformer 就能处理它们。这不是架构的胜利,是数学结构的胜利:注意力机制捕获的"元素间的加权依赖关系",恰好是这些领域的底层结构。
8.5 语言学反思——全文的灵魂段落
让我们回到这个系列的起点——第四篇(ID 623),我们花了大量篇幅讲索绪尔的结构主义语言学:
- 组合性(Syntagmatic)——一个词的意义由它在线性序列中的前后词决定。「bank」在「river bank」中是河岸,在「bank account」中是银行。
- 聚合性(Paradigmatic)——同一位置上的词形成聚合关系,可以互相替换。「猫吃鱼」→「狗吃肉」→「人吃饭」。
- 能指与所指——符号(能指)与意义(所指)的映射是任意的,由语言系统内部的规定来决定。
- 价值——一个符号的意义不是由它自身决定的,而是由它在整个系统中的位置——它不是什么——来决定的。
现在来看 Transformer:
- 自注意力直接实现了组合性。每个词的意义 = 所有词的 V 的加权平均——权重由 Q 和 K 的相似度决定。这就是索绪尔说的"一个词的价值由它周围所有的词决定"——只不过索绪尔用文字描述,Transformer 用 Softmax(QKᵀ/√d_k)·V 计算。
- 多头注意力实现了多维度的聚合性。不同的头关注不同维度的相似性——句法相似、语义相似、搭配相似。一个位置上的词可以向多个"维度"的聚合关系学习。
- Embedding 层学习的就是能指→所指的映射。一个 token ID(纯粹的形式标签)被映射到一个 d_model 维的向量——那个向量的每一维都在"意义空间"中占据一个位置。而这个映射是从零学到的——不是人工定义的词典,而是从语料中统计涌现的语义结构。这就是索绪尔"任意性"的数学实现。
- 位置编码处理的恰好是索绪尔没来得及处理的——时间性(temporality)如何融入结构。在索绪尔的理论中,语言的"组合关系"是在时间轴上展开的,但他没有给出具体的数学机制。Transformer 的 sin/cos 位置编码提供了一个巧妙的方案:用不同频率的波形把时间(位置)编码为空间(维度)上的变化模式。
Transformer 不是一个"理解了语言"的系统——它不知道"猫"是猫科动物,"鱼"生活在水中。它是一个极端高效的统计模式识别器。但它捕捉到的统计模式恰好与语言的结构特性高度吻合——因为语言的底层就是统计模式。
这是结构主义语言学在 20 世纪初种下的种子,在 21 世纪的 GPU 集群上开出的花。索绪尔说"语言是一个系统,其中的每个元素的价值由它与所有其他元素的关系决定"。一百年后,Transformer 用 QKᵀ 矩阵把这个洞见变成了可计算的形式。
人话解释
Transformer 凭什么赢了?三个字:快、直、透。
快:所有词同时处理,不排队。GPU 满载运行。
直:任意两个词直接相连,不像 RNN 要一个传一个。长距离关系一抓一个准。
透:注意力权重矩阵白纸黑字——模型在看什么,你能看到。不是"信任黑箱",是"可以审计"。
还有一个隐藏的第四点:它恰好命中了语言的结构本质。语言的核心——组合性、上下文依赖性——就是"一个元素的意义由其他元素决定"。注意力机制做的一模一样。语言学理论和工程实践,在这一个点上完美会师。
总结
从第一篇到第七篇,我们走了一条很长的路——从算盘上的算珠,到门电路里的 NAND,到冯·诺依曼的存储程序,到 Unicode 把全世界的文字统一编码,到向量把意义变成数字,到 RNN 艰难地处理序列,最后——Transformer 用自注意力、多头、位置编码、残差、归一化和前馈网络,把"理解语言"这件事拆成了可并行、可堆叠、可计算的矩阵运算。
我们在上一篇(706)坐了观光车,看到了全景。在这一篇,我们下车走进了每一栋建筑——拆墙看管线,用矩阵手算了一条"猫吃鱼"穿过两层 Encoder 的全过程。我们不是在"解释" Transformer——我们是在计算它。
Transformer 不是魔法。它是矩阵乘法、Softmax、残差连接、LayerNorm 这些基本操作的组合——就像 CPU 是 NAND 门、锁存器、加法器的组合一样。理解它,只需要耐心。
下一篇,我们将去一个反直觉的方向:如果只保留 Transformer 的"理解"那一半(Encoder),扔掉"生成"那一半(Decoder)——会发生什么?答案是 BERT——一个只看上下文、不生成文字的模型。它在理解上做到了一度让所有人震惊的事情。那是下一篇(ID 709)的故事。
本文与 GPT4 共同完成。参考文献和推荐阅读在末尾。