一、先把欠的三笔账列出来

这一讲开始之前,先把前几讲埋下、还没有兑现的账摆在桌面上:

  • 第 2 讲留了一张对照表,说 Transformer 针对性地解决了 RNN 的三个瓶颈(固定大小隐藏状态、梯度消失、无法并行),但没有说清楚具体怎么做到。
  • 第 3 讲指出静态向量"一词一个固定位置"装不下一词多义(“苹果公司"和"吃苹果"里的"苹果"不该是同一个向量),说这个空隙要靠自注意力填。
  • 第 8 讲断言"自注意力天生分不清顺序,把一句话当成一个无序集合来处理”,但没有证明。

这一讲要把这三笔账一次性还清。**自注意力(self-attention)**是 Transformer 里唯一的新机制,剩下的部分(矩阵乘法、非线性、softmax)你在第 3、5 讲已经全部见过。

二、打个比方:图书馆检索

想象你带着一个问题走进图书馆,想知道"最近有哪些关于气候变化的书"。这个问题就是你的查询(Query)。

书架上每一本书都贴着一张索引标签,写着这本书大致在讲什么——这张标签就是这本书的键(Key)。你把手里的查询,和书架上每一本书的标签逐一对比,标签和查询匹配得越好的书,你越会重点参考。

但你真正带走的知识,不是标签本身,是书的内容——这本书的值(Value)。你最终收获的,是根据"匹配程度"加权参考过的所有书的内容,匹配度高的书贡献得多,匹配度低的书贡献得少(但不是完全不看)。

⭐ 这就是自注意力的全部直觉:每个词都带着一个"我想找什么"的查询,去和句子里每一个词(包括它自己)“贴的标签"做匹配,再按匹配程度,把所有词的"实际内容"加权混合起来,得到自己新的表示。

三、把直觉钉成数学

三个投影:Query、Key、Value

第 8 讲结束时,每个 token 是一个向量(词嵌入 + 位置编码)。自注意力第一步,是把这个向量分别投影成三个不同的向量:

Q = X·Wq     (这个词在找什么)
K = X·Wk     (这个词能提供什么,写在自己的"标签"上)
V = X·Wv     (这个词真正的"内容",被参考时实际拿走的东西)

Wq、Wk、Wv 是三个不同的、可训练的矩阵(第 3 讲的"变换”,第 5、6 讲讲过参数怎么训练)——同一个输入向量 X,被同一套矩阵乘法投影成三个用途完全不同的向量。为什么要分成三个而不是共用一个?因为"我在找什么"(Query)、“我能提供什么线索让别人找到我”(Key)、“我真正的内容是什么”(Value),本质上是三件不同的事,分开投影,模型才有空间把这三件事分别学好。

打分:用点积衡量匹配程度

回忆第 3 讲:点积衡量两个向量方向有多接近。自注意力用点积衡量"某个词的查询"和"另一个词的标签"匹配得有多好:

score(i, j) = Qᵢ · Kⱼ

对句子里每一对位置 (i, j) 都算一次这样的点积,会得到一张 n × n 的打分表(n 是序列长度)——这正是第 2、7 讲反复提到的那个"平方级代价"的直接来源:n 个词,两两配对打分,天然就是 n² 次计算,第四节会正式讲这笔代价。

缩放:一个工程修正

在送进 softmax 之前,标准做法是把每个分数除以 √dₖ(dₖ 是 Key 向量的维度):

score(i, j) = (Qᵢ · Kⱼ) / √dₖ

原因和第 6 讲的梯度有关:点积是 dₖ 项数字相乘再相加,维度越高,数值本身的波动范围会越大;数值差距一旦太悬殊,softmax 会把绝大部分权重都推给分数最高的那一项,其余项的权重被压得接近 0——这种"过分尖锐"的分布,在反向传播时梯度会变得极小,不利于训练(第 6 讲讲过这类问题的通用形态)。除以 √dₖ 是把数值范围拉回一个更适合训练的区间。

softmax:把打分变成合法权重

对每个位置 i 算出的一整行分数 [score(i,1), score(i,2), ..., score(i,n)],套第 5 讲讲过的 softmax,变成一组非负、加起来等于 1 的权重(第 4 讲对概率分布的要求)——这组权重就是"位置 i 该在多大程度上参考每一个其他位置"。

加权求和:算出新的表示

最后一步,用这组权重,对所有位置的 Value 向量做加权求和,得到位置 i 的新表示:

输出ᵢ = Σⱼ softmax(score(i,·))ⱼ × Vⱼ

把全部步骤写成一个公式,这就是自注意力的完整定义:

Attention(Q, K, V) = softmax(QKᵀ / √dₖ) · V

自注意力(self-attention):给定一个序列,把每个位置的向量分别投影成 Query、Key、Value,用 Query 和所有位置的 Key 做点积打分并 softmax 归一化,再用这组权重对所有位置的 Value 加权求和,得到每个位置的新表示——新表示由整个序列的内容动态决定,不是一个固定不变的变换。

⭐ 这里是自注意力和第 5 讲讲的"一层"最本质的区别:第 5 讲的 y=σ(Wx+b) 里,W 训练完之后是固定的,同一个 x 永远得到同一个变换结果。自注意力里,真正决定"怎么混合"的那组权重(softmax 的输出)不是训练完就固定下来的参数,是每次输入不同的句子,临时现算出来的——这就是为什么"苹果"在不同句子里能得到不同的表示:不是词嵌入变了(它还是第 8 讲那张查表矩阵里固定的一行),是这一层根据当下这句话的具体内容,现算出了一套不同的混合方式。

一个可以完全手算的例子

用两个极简的 2 维向量走一遍全过程。设两个 token 的输入向量(词嵌入+位置编码之后)是:

x₁ = [1, 0]
x₂ = [0, 1]

为了让手算干净,取三个投影矩阵为最简单的形式:Wq = Wk = 单位矩阵(Q、K 直接等于输入本身),Wv = 2×单位矩阵(V 是输入的 2 倍):

Q₁=[1,0]  K₁=[1,0]  V₁=[2,0]
Q₂=[0,1]  K₂=[0,1]  V₂=[0,2]

算位置 1 的输出(查询是 Q₁=[1,0]):

score(1,1) = Q₁·K₁ = 1×1+0×0 = 1
score(1,2) = Q₁·K₂ = 1×0+0×1 = 0

缩放(dₖ=2,√2≈1.414):
scaled(1,1) = 1/1.414 ≈ 0.707
scaled(1,2) = 0/1.414 = 0

softmax([0.707, 0]):
exp(0.707)≈2.028, exp(0)=1,总和≈3.028
权重(1,1) ≈ 2.028/3.028 ≈ 0.670
权重(1,2) ≈ 1/3.028 ≈ 0.330

输出₁ = 0.670×[2,0] + 0.330×[0,2] = [1.340, 0.660]

用完全相同的步骤算位置 2(查询是 Q₂=[0,1]),由对称性直接得到:

权重(2,1) ≈ 0.330,权重(2,2) ≈ 0.670
输出₂ = 0.330×[2,0] + 0.670×[0,2] = [0.660, 1.340]

⭐ 两个输出都不等于任何一个原始的 V,是两个 V 按不同比例混合出来的新向量——位置 1 更多参考了自己(权重 0.670),也拿了一小部分位置 2 的内容(权重 0.330)。这正是"新的表示由整个序列动态决定"这句话的具体样子。

定性验证:一词多义问题解决了吗

回到第 3 讲的例子——“苹果公司发布了新款"和"他咬了一口苹果”。“苹果"这个词查出来的初始向量(第 8 讲的词嵌入)是同一个,但自注意力这一层会发生什么:

在第一句里,“苹果"的 Query 会去和"公司"“发布"这些词的 Key 做点积,这些词的 Key(如果模型训练得当)在"商业、科技"这个方向上会和"苹果"的 Query 匹配得比较好,于是"苹果"的新表示,会混入较多"公司"“发布"的 Value,把它的向量往"品牌、科技产品"的方向拉。

在第二句里,“苹果"的 Query 去和"咬"“一口"的 Key 匹配,这些词的 Key 在"食物、动作"这个方向上匹配度更高,“苹果"的新表示会被拉向"水果、食物"的方向。

⭐ 两句话里"苹果"查表查出来的初始向量完全相同,但自注意力算出来的最终表示完全不同——因为决定"怎么混合"的权重,是由当下这句话里实际出现的其他词动态算出来的。第 3 讲留下的空隙,到这里被正式填上。

形式化验证:没有位置编码,自注意力真的是一个集合操作

把手算例子里的两个 token 顺序对调:x₁'=x₂=[0,1],x₂'=x₁=[1,0]。重新算一遍会发现——新的位置 1(原来的位置 2)算出的输出,恰好等于原来位置 2 算出的输出;新的位置 2 也恰好等于原来位置 1 的输出。整个计算结果跟着输入顺序一起换了位置,但每一个具体的数值都没有变。

⭐ 这就是第 8 讲说的"自注意力是在集合上计算"的精确含义:打分公式 Qᵢ·Kⱼ 只依赖于 xᵢ 和 xⱼ 这两个向量的内容本身,不包含任何”i 排第几、j 排第几"的信息——调换输入顺序,只是调换了"谁是位置 1、谁是位置 2"这个标签,每个词各自算出来的内容毫无变化。这也正是第 8 讲要在向量里额外注入位置编码的根本原因:如果不做这件事,自注意力从数学结构上就无法区分"狗咬人"和"人咬狗”。

四、这一讲机制自带的代价

⭐ 现在可以正式兑现第 2 讲那张对照表了:

RNN 的瓶颈(第 2 讲) 自注意力具体怎么解决的
固定大小隐藏状态压缩历史 每个位置直接拿到全部其他位置的 Value 加权,没有被压缩进一个固定容量的容器
梯度消失,长距离依赖学不到 任意两个位置之间只隔"一次点积”,路径长度恒为 1,不随距离增长而衰减
顺序依赖,无法并行 n×n 的打分表可以一次性用矩阵乘法批量算出(第 3 讲),不需要等前一个位置算完

但第 2 讲也提醒过,天下没有免费的午餐:

⚠️ n×n 的打分表,本身就是这一讲机制自带的代价:序列长度翻倍,需要计算和存储的分数数量变成 4 倍——这是第 2 讲留下的悬念的正式答案,也是第 23 讲"上下文长度是工程难题"最核心的数学来源,第 7 讲"token 数量不等于字数"这条提醒,到这里终于有了完整的因果链条:分词切得越碎 → token 数量越多 → 自注意力要处理的序列越长 → 计算和存储代价平方级增长。

⚠️ 另一个容易被忽视的局限:这一讲讲的自注意力本身,对"该往哪看"没有任何预先设定的偏好——不像某些视觉模型天然假设"临近的像素更可能相关”,自注意力对序列里任意两个位置一视同仁,具体该重点关注谁,完全靠训练从数据里学出来。这既是优点(足够通用,不预设结构),也是代价(需要足够多的数据和算力,模型才能学会"该往哪看"这件事本身)——这条线索会在第 14、15 讲数据规模和 Scaling Laws 那里重新出现。

⚠️ 最后,一层自注意力只做了一次"信息汇聚”——它能让"苹果"混入"公司"的信息,但更复杂的推理(比如需要综合三四个相隔很远的线索才能得出的结论)往往需要好几层自注意力叠加、反复汇聚才能做到。单独一层的能力是有限的,第 10 讲堆叠多层、多头的设计,正是在弥补这个局限。

五、和后面课程的关系

  • 第 10 讲要做两件事:一是同时算好几组不同的 Q/K/V(“多头”,相当于同时从好几个不同的角度去检索),二是在自注意力之后接一个第 5 讲讲过的"一层+非线性”(前馈层),把这一讲纯粹靠加权平均得到的表示,再做一次非线性的加工。
  • 第 22 讲的 KV Cache,直接源于这一讲算出来的 K、V:生成文字时,之前位置的 K、V 一旦算过就不会变,可以缓存下来避免重复计算——这个工程优化的对象,就是这一讲刚刚定义的两个向量。
  • 第 23 讲会把这一讲第四节的 n² 代价,正式展开成"为什么长上下文是工程难题,不是调个参数就能解决的"这个完整论证。
  • 第 31 讲讲可解释性时会回头审视一个常见的误解:softmax 权重经常被直接当成"模型在关注什么"的证据来解读,但这个解读本身是有争议的,不能想当然地把注意力权重等同于"模型的推理过程”。

六、本讲小结

  • 自注意力把每个位置的向量投影成三个角色:Query(在找什么)、Key(能提供什么线索)、Value(真正的内容)——图书馆检索的类比:拿着查询去匹配标签,加权带走内容。
  • 完整公式:Attention(Q,K,V) = softmax(QKᵀ/√dₖ)·V——点积打分(第 3 讲)、除以 √dₖ 防止 softmax 过度尖锐(呼应第 6 讲的梯度问题)、softmax 归一化成合法权重(第 4、5 讲)、加权求和 Value。
  • ⭐ 和第 5 讲的"一层"最本质的区别:自注意力的"混合方式"是根据当下输入内容动态算出来的,不是训练完就固定不变的参数——这正是第 3 讲"一词多义"问题被解决的机制来源。
  • ⭐ 没有位置编码时,自注意力是一个集合操作:调换输入顺序只会调换输出的顺序标签,每个位置各自算出的内容完全不变——第 8 讲要求额外注入位置编码的原因,在这一讲被正式证明。
  • ⭐ 第 2 讲的三个瓶颈在这一讲被逐一兑现:固定容量瓶颈(每个位置直接访问全部内容)、梯度消失(任意两位置路径恒为 1)、无法并行(打分表可批量矩阵乘法算出)。
  • ⚠️ 代价是 n² 的计算和存储——这是第 23 讲上下文长度瓶颈的核心来源;此外单层注意力汇聚能力有限、对"该关注哪里"没有预设偏好,都需要靠后面的层数堆叠和大规模训练来弥补。

思考题

  1. 用第三节手算例子的思路,把 Wv 换成 3×单位矩阵(其他不变),重新算一遍位置 1 的输出。权重会变吗?输出向量的数值会怎么变?这说明 Q、K 决定的是什么,V 决定的是什么?
  2. 为什么说"点积的数值会随维度 dₖ 增大而变大"?如果 dₖ 从 2 变成 200,两个随机方向差不多的向量,点积的数值级别大概会怎么变化(不要求精确计算,说出直觉即可)?
  3. 本讲说"自注意力是集合操作",请用第三节的手算例子亲自验证一遍:交换 x₁ 和 x₂ 的顺序重新算一次,确认新的位置 1 输出是不是恰好等于原来的位置 2 输出。
  4. 如果一句话只有一个词(n=1),自注意力这一讲讲的全部计算(打分、softmax、加权求和)会发生什么退化?这时候输出和输入是什么关系?
  5. 第四节说"序列长度翻倍,计算和存储代价变成 4 倍"——如果一段文本从 1000 个 token 增加到 4000 个 token(4 倍),打分表的计算量大概会变成原来的多少倍?结合第 7 讲,这对处理长文档、多语言混合文本分别意味着什么?