零件齐了:词向量(第 3 课)、矩阵乘法(第 4 课)、位置指纹(第 5 课)。 现在把它们装成第一个真正的机制——自注意力。 要解决的问题只有一句话:一句话里的每个字,凭什么知道该多看谁一眼?这一课我们造出 Q、K、V 三个角色,用第 4 课那把尺子给全班打分, 把分数换成加起来 100% 的百分比,再按百分比把大家的内容收集回来。 全程 8 维、6 个字,每一个数都能手算核对。
先别急着看公式。下面这个动画把这一课要讲的东西整个演了一遍: 点「自动播放」,看这一排字是怎么互相「看一眼」的。 数字看不过来就点「数字:藏起来」,只看线的粗细 —— 线越粗,说明那个字被看得越多。
如果「注意力」这个名字让你发懵,就换成图书馆:你带着一个检索词走进去,从一排书里挑几本借走。三样东西一一对应:
「自」注意力就是:这排书互相去借——每个字既是借书的人, 也是被别人借的书。下一站开始,我们把这三样东西造出来。
第 4 课我们算过一张 20 × 20 的相似度表:第 (i, j) 格是「第 i 个字」 和「第 j 个字」的点积。那张表回答的是「谁像谁」。
可注意力要回答的是另一个问题:「我该看谁一眼」。 这一课的场景很具体——一句话「三加五等于?」进来, 「?」那个座位上的字,得知道该从「三」「加」「五」里搬走什么信息,才能猜出答案。 它凭什么决定?
最偷懒的答案是「谁跟我像,我就看谁」。这个答案会立刻撞上三个麻烦。 先看第一个,也是最要命的那个:相似度表是对称的。
「对称」听起来是个中性词,放在这里却是致命的:它意味着「谁在问」和「谁在答」是同一个角色。可语言里这两件事从来不一样——「等」需要从「加」那里知道 「该做加法了」,而「加」并不需要从「等」那里知道这个。 左边那张表给不出这种区别:它算出来的两个数是同一个。
第二个麻烦:左边那张表的第 i 行,只由第 i 个字自己决定, 跟这句话里有哪些字毫无关系。「等」在「三加五等于?」里的那一行, 和在「十减四等于?」里的那一行,是一模一样的 6 个数。 可它在这两句里该看的东西完全不同。
第三个麻烦:每个字只会找「跟我像的字」,不会找「我需要的信息」。「?」这个座位上的字,它自己长得跟谁都不像——它需要的不是「像」,而是「答案该从哪几个字里来」。
现在把「准备三串数」这件事落到最实在的做法上。 每个字手上只有一份东西——第 3 课学到的词向量 x。要长出三串不同的数,只有一个办法:乘三张不同的矩阵。
第 4 课你已经很熟了:一行 x 乘一张矩阵,一次算出一整行数。 这里就是把它做三遍,只不过三张矩阵各管一件事:
我在找什么我挂出的名牌我要交出去的内容举个可以按计算器核对的小例子。第 2 站里「三」的词向量第一维是 0.67, 而它的 Q 的第一个数是 0.09—— 这个数就是 x 的 8 个数和 Wq 第 0 列的 8 个数逐项相乘再相加的结果。 三张矩阵共 3 张、每张 8 × 8 = 64 个数, 加起来 192 个可训练参数——只是演示规模。
注意 Wk、Wv 为什么比 Wq 窄:28 个头共用同样几组 K、V(这叫 GQA), 所以它们只要 512 宽就够。省下来的正是「长文本最贵的那部分」。
有了 Q 和 K,打分这件事就回到了第 4 课那条老规则上:点积。「等」拿着自己的 Q,去和 6 个字的 K 各做一次点积, 得到 6 个分数——这就是它在决定「该看谁」。
形状也是第 4 课算过的:Q 是 [6, 8]、K 也是 [6, 8], 要让「行点列」,就得把 K 躺下来(转置): [6, 8] · [8, 6] → [6, 6]。 一次乘法,36 个分数全部到位。
拿两个具体的格子看看方向性是怎么出来的: 「等」问「加」得到 4.05,而「加」问「等」只有 2.08。同一对字,两个方向的分数差了一截—— 因为一个用的是「等 的问题 × 加 的名牌」, 另一个是反过来的组合。左边那张相似度表永远给不出这种差别。
softmax 做的事一句话讲完:把一行任意大小的分数, 变成一行加起来正好 100% 的百分比,而且不改变谁大谁小。 分数最高的那个字,拿到的百分比也一定最高。
两步,没有别的:先把每个分数取指数(ex,一个永远为正、且越大的数涨得越猛的函数), 再除以它们的总和。
为什么先取指数?因为它把「负分」变成「很小的正数」,把「高分」变成「很大的正数」, 于是百分比永远在 0 和 1 之间、且分数越高份额越大。减掉最大值只是为了数值稳定(ex−max 不会溢出), 结果一模一样。
那 ÷√d 是干什么的?它是 softmax 前面的一步除法, 防的是「分数太大」。而分数为什么会太大——答案在维度上:点积是 8 个数相乘再相加,维度越高,加进来的项越多,分数就越大(随机向量的点积典型值正比于 √d)。下面这台机器把这件事直接摆出来:
饱和为什么是灾难?因为 softmax 一旦输出「一个 100%、其余全 0%」, 它的梯度就接近 0——反向传播时,这几个字收不到「你分错了」的信号, 参数就不再更新。这不是「效果差一点」,是「学不动」。 除以 √d 的全部作用,就是把分数拉回一个 softmax 还能分辨轻重的区间。
√d,d 是每个头的宽度。 真实模型一个头 128 维 → √128 ≈ 11.313708; 演示里 8 维 → √8 ≈ 2.828427。现在每个字手上都有一张分配方案了:6 个百分比,加起来 100%。 接下来这一步是整个机制里最朴素的一步——按百分比,把大家的 V 搬回来。
做法就是加权平均:给「等」分到 52.4%, 就把它的 V 乘 0.524 收进来;分到 1.4%,就乘 0.014。 全部 6 份加起来,就是这个座位的新向量。
具体到「等」这个座位:它把 52.4% 的注意力给了「加」, 于是收上来的输出最像「加」的 V(余弦 0.9855), 输出是 [0.52, -0.42, 0.13, -0.17, 0.96, -0.40, -0.05, -0.17]。但它不等于 V(加)—— 因为另外 5 个字也各分到了一点百分比,它们的内容同样被搬了进来。
四步凑齐了,把它们连起来就是自注意力。 下面这台机器把「三加五等于?」整句跑一遍,四张表依次摆开:① 三个分身 → ② 打分 → ③ 变百分比 → ④ 取货。
值得停下来看一眼最后一行数字:输入是 6 行 8 个数,输出还是 6 行 8 个数。自注意力不改形状、不做非线性,它只做一件事—— 把每个字的内容,按「关注度」重新搅拌一遍。 这一条性质非常要紧:正因为输入输出形状一样, 它才能被一层层叠起来,也才能和「残差连接」那条高速公路对接(第 8 课)。
就这五行。真实框架里的 nn.MultiheadAttention、F.scaled_dot_product_attention 干的就是这五行 (再加上第 7 课的掩码和多头)。这一课你亲手把这五行算了一遍,每一个数都对得上。
第 4 课那张相似度表不够用:它对称、只认内容,量不出「谁在问、谁在答」。 补的办法是让每个字准备三个分身——Q(我在找什么)、K(我挂出的名牌)、 V(我要交出去的内容),都由同一个词向量乘三张各自要训练的矩阵得到。 打分就是第 4 课那条点积:Q 乘 K 的转置,得到一张 [T, T] 的分数表; 先除以 √d(因为点积的量级正比于 √d,不缩放 softmax 会饱和、梯度会消失), 再 softmax 成加起来 100% 的百分比; 最后按百分比把 V 加权平均回来。 输入 [6,8]、输出 [6,8],形状不变,但每个字的内容已经被 「按关注度」重新搅拌过一遍 —— 这就是自注意力。