注意力机制
「它太累了」——「它」指谁?让每个词自己决定该看向哪里
「它」指谁?
先读两句几乎一样的话:
小猫没有过马路,因为它太堵了。
两个「它」,指的东西完全不同:第一句指小猫,第二句指马路。 你几乎是无意识就分辨出来了。但请注意你是怎么做到的—— 读到「它」时,你的目光回头扫了一眼前文,并且只在「小猫」和「马路」之间挑了一个, 其余的字几乎被忽略。
这个「回头看、且看得有轻有重」的动作,就是人类的注意力。 问题是:怎么让一个神经网络也学会这个动作?
旧方案的局限:一条太窄的门缝
卷三的终点站还记得吗?RNN / LSTM 处理句子的方式,是从左到右逐词读入, 把读过的一切不断压缩进一个固定大小的向量里,像滚雪球:
等网络读到「它」时,「小猫」已经是五步之前的记忆,在行李箱里被反复挤压。 句子再长一点——比如一整段话——开头的信息几乎荡然无存。 LSTM 的「阀门」缓解了遗忘,却没有解决根本问题:所有历史共用一条固定宽度的门缝。
新想法:不压缩,回头看,按分配
新方案只有三步,每一步都用的是你已经会的东西:
第一步,打分。「它」想知道每个词和自己的相关度。 卷一第 2 课说过:两个向量的点积越大,方向越一致,越「相关」。 那就让「它」的向量和每个词的向量分别做点积,得到一排分数。
第二步,变成权重。这排分数有大有小、有正有负,不好直接用。 卷二第 11 课的 Softmax 正是干这个的:把任意一排分数变成一排加起来等于 1 的比例。 比如「小猫 62%、马路 9%、累 13%、其他词分剩下的」。
第三步,按比例混合。用这些百分比对所有词的向量做加权求和, 得到一个新向量——它代表的不再是孤零零的「它」, 而是「一个主要由小猫构成、掺了一点累的它」。指代问题,就这样被算出来了。
一个问题,三个角色:Q、K、V
上一站的方案直接拿词向量互相点积,藏着一个微妙的毛病:一个词「想找什么」和「能提供什么」根本是两回事。「它」作为代词,想找的是「前文里某个具体事物」; 而「它」能提供给别的词的信息,只是「这里有个代词」。一个向量身兼两职,会互相打架。
修补的办法依然是最小的:别用原始向量直接对话, 让每个词先通过三个线性变换(卷一第 3 课的矩阵,参数可学习)生成三个分身—— 就像在图书馆里:
于是打分规则修正为:用「它」的 Q,去点积每个词的 K; 权重算好后,混合的是每个词的 V。 三个矩阵 WQ、WK、WV 都是训练中学出来的—— 也就是说,「该注意什么」这件事本身,是模型自己学会的。
从「它」一个词,到整句话
第 4 站收尾时,我们已经为「它」配齐了一整套动作:用它的 Q 去点积每个词的 K 打分、用 softmax 把分数变成权重、 再照权重混合每个词的 V。漂亮——可这套动作只照顾了「它」一个词。 真实的句子里,每个词都得这样回头看一遍:「累」要找它修饰谁,「过」要找它的宾语…… 与其一个词一个词地循环,不如把「对所有词各做一遍」用矩阵一次写完。
而当我们把它写到最紧凑,整套流程会坍缩成一个式子——这个式子不是我们生造的, 它正是 2017 年 Google 论文《Attention Is All You Need》的核心(3.2.1 节,Scaled Dot-Product Attention,缩放点积注意力)。 那篇论文提出了 Transformer 架构,甩开了统治十年的 RNN / LSTM;今天的 GPT、BERT、几乎所有大模型,骨架都从这里长出来:
别被一堆大写字母唬住——这一行里的每个零件,你在前三站都已经亲手造过了。 下面就把上面说的「用矩阵一次写完」摊开,看这一行到底是怎么拼出来的。
关键的一步,是把所有词的零件摞成矩阵。把每个词的查询单 q 当成一行,从上到下摞起来, 就得到一个矩阵 ( 个词就是 行);同理把所有 k 摞成 、所有 v 摞成 。大写的 Q、K、V,就是上一站那些小写分身「摞起来」的整捆版本——内容半点没变,只是排好了队,好让一次矩阵乘法把全句都算了。
于是上一站那套动作,原封不动地升级成四步矩阵运算:
一步一步看,每一步到底在搬动什么:
第一步 :一次把所有词对所有词的分数都打出来。上一站「它」的 q 点积每个词的 k,得到一行分数。现在 里有 行查询、 里有 行标签,两者一乘,就同时打出 张分数表: 第 行第 格,就是「第 个词该看第 个词多少」。 (那个转置 只是把 立起来,让维度对得上、能做点积,不必深究。) 上一站图 19-2 算的,正是下面这张表里「它」那一行——现在我们一口气把每一行都算了出来:
第二步 :给分数「降温」。这一步上一站没提,因为单看一行不明显,可一上规模就必须有。问题出在维度:论文里每个 q、k 是 维, 点积是 64 个乘积相加,数值很容易累积得很大——粗算下来分数常在 上下晃。 把 这种悬殊的分数喂进 softmax,结果几乎是 ——softmax「饱和」了,注意力变成非黑即白,只死盯一个词、其余一律不看,梯度也几乎为零、学不动。 解法很省事:整张表统一除以 ,分数缩回 的温和范围,softmax 重新变得「有轻有重」而不极端。
第三步 :把每一行变成一组百分比。降温后的分数还是有正有负的实数,不能直接当权重。对分数表的每一行各做一次 softmax(第 11 课那一套), 这一行就变成一组加起来正好等于 1 的比例——也就是图 19-5 里「它」那行的 62%、9%、13%…… 行各归一一次,整张表就成了一张干净的权重表。
第四步 :照权重把内容混合出来。拿权重表去乘 (每行是一个词的内容分身)。具体到「它」这一行:用 62% 取「小猫」的 V、13% 取「累」的 V……加权相加, 得到「它」的新向量——一个主要由小猫、掺了点累构成的「它」。每一行都这么混一次, 输出就是 个被全句上下文重新润色过的新词向量。 注意:前三步只是算出「该看谁」的计划,这一步才把计划真正执行成「信息搬进了词里」; 这些新向量会顶替掉原来的词向量继续往上走。下一站把它代成具体数字,你就会看清这个「搬运」到底有什么用。
把这四步接成一条流水线,写成一行,就是 2017 年那篇论文里的原式:
从里到外读: 打分 → 降温 → 归一成权重 → 乘 混合。四步,正好对应上面四行。
值得停下来欣赏一下这个设计的两个「免费赠品」: 其一,所有词的回头看是同时发生的——矩阵一乘全算完,不像 RNN 必须逐词排队; 其二,「小猫」距离「它」是 5 个词还是 500 个词,都是一次直达,不存在传着传着丢了的问题。 行李箱被彻底扔掉了。
把公式代进数字:一个 2 维的小例子
公式拼好了,可一行符号终究还是抽象。这一站我们把它彻底落地: 用一个小到能用纸笔验算的例子,把 从头到尾算一遍。 为了算得清爽,每个向量只取 2 维(真实模型是 64 维)——但每一步的动作,和真实 Transformer 里发生的一模一样,只是数字小。
取句子里最要紧的三个词:小猫、累、它。回忆第 4 站——每个词过 三个矩阵, 生成 q、k、v 三个分身。为了把注意力都集中在公式本身,这里直接把算好的 2 维分身列出来 (真实模型里,这些数字是训练学出来的):
一个小约定:这里把 v 的两个维度设计成可以「读」的——第 1 维代表「动物味」,第 2 维代表「疲惫味」。 所以小猫的 v=[1,0] 是纯动物味,累的 v=[0,1] 是纯疲惫味。等会儿看「它」最后混出的 v 落在哪, 就知道模型把「它」理解成了什么。下面我们只算「它」这一行(它该看谁、看多少),其余两行同理。
第一步 · 打分 。拿「它」的查询 ,分别和三个词的 k 做点积:
对「小猫」的分数 2.4,把另外两个 0.2 远远甩开。为什么差这么多? 因为点积衡量的是两个向量方向有多一致——而「它」的查询,几乎和「小猫」的 key 指向同一个方向。 把这四个向量画在 2 维平面上,一眼就能看明白:
第二步 · 降温 。这里维度 ,所以除以 。整行分数同除这个数:
(真实模型 、除以 8,原理完全一样,只是这里维度小、缩放也小,效果不明显——但流程一步不少。)
第三步 · 归一 。把降温后的 喂进 softmax:先取 e 的幂,再各自除以总和。
三个数加起来正好 100%——这就是「它」分配给全句的目光:七成给了小猫。
第四步 · 混合 。拿这组权重去加权混合三个词的 v,逐维相加:
还记得 v 的两维是「动物味 / 疲惫味」吗?「它」原本是个意思模糊的代词, 经过这一轮注意力,它的新向量变成了 [0.78, 0.22]——0.78 的动物味 + 0.22 的疲惫味。换句话说,模型已经算出:这个「它」≈ 一只(有点累的)小猫。 第 1 站那个「它指谁」的悬念,到这里被四步算术实实在在地解决了。
可是——费这么大劲混出这个新向量,到底有什么用?这才是整套机制的落点,值得说透。
前面三步(打分、降温、softmax)算出来的,只是一张「该看谁、看多少」的计划表: 70% 小猫、15% 累……可计划表本身并不会让模型变聪明——它只是「知道该看小猫」,还没「真的看到」。第四步 ×V,才是把计划付诸执行:照这组比例,把小猫的内容(V)真的取过来,装进「它」自己的向量里。少了这一步,前面辛苦算出的权重就只是一份没人读的报告, 「它」的向量纹丝不动,整层等于白忙。混合 V,就是注意力真正「搬运信息」的那一下。
而这个装好了料的新向量,会顶替掉原来那个空洞的「它」,继续往上走—— 送进下一层注意力去抓更多关系,最终送到输出层去预测下一个词。 关键在于:因为现在「它」的向量里真的写着「小猫」,模型后面所有的判断, 都是在对「一只小猫」做,而不再是对一个谁也不知道指什么的空代词做。
这也正好回扣第 1 站:原始的「它」是从词表里查出来的固定向量,与上下文无关—— 在哪句话里都长一个样。经过这一层注意力,它变成了随上下文而变的向量: 在「小猫累了」里偏向小猫,换成「马路堵了」就会偏向马路。 「同一个词,不同语境,得到不同向量」——这正是注意力存在的全部理由, 也是 GPT 们能「读懂上下文」的底层机制。
把这里的 2 维换回 64 维、把「只算它一行」换成「n 行一起算」, 就是真实 Transformer 每一层每时每刻在做的事——同样的四步:打分、降温、归一、混合, 只是更大、更并行。你刚刚用纸笔走完的,正是大模型内部最核心的那个动作。
亲手点一点
回到开头那对句子。下面是一个迷你注意力面板:点击任意一个词, 看它的注意力都分给了谁;再切换句子,观察同一个「它」的目光如何移动。
「它」的目光主要落在「小猫」上(60%)
百分比 = softmax 后的注意力权重(演示用的手工数据)。注意切换句子时,「它」的最大权重从「小猫」跳到「马路」——决定目光去向的,是「累 / 堵」这一个字的差别。
总结
注意力机制,就是让每个词带着自己的问题(Q)去查所有词的标签(K), 按匹配程度分配目光,再把对应的内容(V)按比例混合成自己的新含义—— 从此,理解一个词不再依赖一条会遗忘的传话链。
这一课你亲手发明了
- 注意力权重:点积打分 + softmax 归一,得到「该看谁多少」的百分比。
- Q / K / V:查询单、书脊标签、书的内容——一个词的三个分身,由三个可学习的矩阵生成。
- 缩放因子 √d_k:给高维点积「降温」,防止 softmax 饱和。
- 两个免费赠品:全句并行计算;任意距离一步直达。
学习小测验
做完这一课,来检测一下核心知识点。选出你的答案后点击「提交」,即可看到正确选项与讲解。
多头注意力
一句话里不止一种关系——指代、语法、情感各派一个「头」去看,再把所见拼成全貌。