语言·计算&LLM——7. 一个真正的Transformer

文章目录[隐藏]

前言

上一篇,我们花了九万多字,坐着观光车绕着 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
线性投影 QX · WQn×dmodel · dmodel×dkn × dk
线性投影 KX · WKn×dmodel · dmodel×dkn × dk
线性投影 VX · WVn×dmodel · dmodel×dvn × dv
注意力分数Q · KTn×dk · dk×nn × n
缩放S / √dkn × nn × n
Softmax逐行n × nn × n
加权求和A · Vn×n · n×dvn × 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 好。原因:

  1. 不增加维度:如果 Concat,输入维度变成 $d_{model} + d_{pos}$ → 模型第一层的参数量增加($W^Q$ 等矩阵变大)→ 更多参数、更多计算。
  2. 更深的理由: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 里有且仅有两种运算

  1. 自注意力:跨位置的。一个词去问其他所有词——"你们在说什么?"——然后从别人那里提取信息。这是"交流"。
  2. 前馈网络:逐位置的。每个词关起门来,在自己的表示空间里做非线性变换。这是"思考"。

交流不需要非线性(注意力全是线性操作 + 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

两个关键变化:

  1. 所有词对之间的相似度都上升了——Transformer 让三个词的表示趋近(互相浸润),这反映了"它们在同一个语境中"。
  2. 「猫」-「鱼」的相似度提升最大(从 0.73 到 0.91,提升 25%)。在 Embedding 空间中,猫和鱼只是两个名词——它们的相似度一般。但在"猫吃鱼"这个句子中,经过两层 Transformer 的加工,主语和宾语获得了共享的语境信息——它们共同参与了一个"吃"的事件。这个共享语境拉近了它们的距离。

7.5 如果继续往下——Decoder 和最终输出

在这篇文章中,我们只追踪了 Encoder 的两层。完整的 Transformer 还有 Decoder 和输出层——但原理已经在第 1-6 章讲透了,这里快速综述:

Decoder 的三个子层:

  1. Masked Self-Attention:和 Encoder 的自注意力一样,但在训练时把"未来"的词遮住(masked)——生成"cat"时只能看到之前已生成的词,不能偷看后面的"eats"和"fish"。这是自回归生成的要求。
  2. Cross-Attention:Decoder 和 Encoder 的"接头处"。Decoder 的每个词用自己的 Q 去查询 Encoder 输出($X_2$)的 K 和 V。这就是翻译的"对齐"——Decoder 的 "cat" 通过 Cross-Attention 找到 Encoder 的「猫」,提取它的上下文表示,作为翻译的依据。
  3. FFN + Add & Norm:和 Encoder 完全相同。

最终输出: Decoder 输出 → 线性层($d_{model} \to |V|$,映射到词汇表大小)→ Softmax → 概率分布(每个词的预测概率)。概率最高的词就是模型预测的下一个词。

例如,Decoder 在看到起始符号后,预测的下一个词概率最高的是 "cat"(因为 Cross-Attention 已经把「猫」的信息对齐过来了)。然后 Decoder 把 "cat" 作为下一个输入,预测 "eats"……如此逐个生成,直到输出结束符号。

7.6 完整维度变化总表

阶段运算输入维度输出维度
Embedding查表3 个 token ID3 × 12
+ PE逐元素相加3×12 + 3×123 × 12
Q/K/V 投影(每个头)X · W3×12 · 12×43 × 4
注意力分数Q · Kᵀ3×4 · 4×33 × 3
缩放÷ √4 = ÷ 23 × 33 × 3
Softmax逐行3 × 33 × 3
加权求和A · V3×3 · 3×43 × 4
拼接 3 个头Concat3 × (3×4)3 × 12
输出投影× Wᴼ3×12 · 12×123 × 12
Add & Norm 1LN(x + MHA)3×123 × 12
FFN 升维× W₁ + b₁3×12 · 12×483 × 48
GELU逐元素3 × 483 × 48
FFN 降维× W₂ + b₂3×48 · 48×123 × 12
Add & Norm 2LN(x + FFN)3×123 × 12
× N 层重复上述 Block3×123 × 12

核心观察:在整个 Encoder 中,维度始终是 3 × 12——从未改变。 这就是残差连接的美。每一层都可以自由地变换表示,但输入输出的"接口"永远一致——所以层可以随意堆叠。

7.7 对比:RNN 怎么做同一件事?

同样处理"猫 吃 鱼":

RNN 的方式:

  1. $t=1$:输入「猫」→ 更新隐藏状态 $h_1 = \tanh(W_h h_0 + W_x x_1)$
  2. $t=2$:输入「吃」→ $h_2 = \tanh(W_h h_1 + W_x x_2)$——注意,必须等 $h_1$ 算完!
  3. $t=3$:输入「鱼」→ $h_3 = \tanh(W_h h_2 + W_x x_3)$

3 步串行,第 3 步才能看到完整的句子上下文。反向传播时梯度从 $h_3$ 一步一步穿回 $h_1$——穿 3 层(短句还好,100 词的句子就是 100 层)。

Transformer 的方式:

  1. 三个词同时输入(并行)
  2. 每个词直接看所有词($3 \times 3$ 注意力矩阵)
  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 共同完成。参考文献和推荐阅读在末尾。

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇