本系列前十篇打底了向量、矩阵、softmax、嵌入与 RNN 的局限,本篇正式进入第二部分「注意力机制原理」。
这一篇刻意不写一行严谨推导,只做一件事:把「注意力」这个词从人类的视觉行为和翻译任务里拆下来,让你在第一次看到
softmax(QK^T/√d) V时,脑子里先有一个真切的画面,再有一组数学符号。
读完这一篇你会带走什么:
- 知道「注意力」最早不是 Transformer 的概念,而是认知心理学和神经科学几十年的研究主题;
- 理解人在阅读一段长文字时,眼睛是怎么动的,为什么大脑必须有一个「下一步看哪里」的策略;
- 看清机器翻译里「对齐(alignment)」这个老问题怎么逼出了软对齐;
- 理解为什么注意力权重一定要满足两条性质:非负、加和为一,并把这两条性质和 softmax 直接对上;
- 明白「注意力 = 加权平均」这一句口号背后真正解决的问题是什么;
- 从工程角度看清「为什么不直接 argmax,而要 softmax」的根本原因——可微、可端到端训练。
读完之后,下一篇我们会回到 2014 年的 Bahdanau,看他怎么把这个直觉写成第一份具体的实现。
一、先承认一件事:人类的注意力不是个隐喻
工程师拿到一个名词,第一反应往往是把它当成隐喻:「哦,注意力就是模型学会看哪里嘛,好懂。」
这种说法不算错,但太轻。
轻到让你在看 softmax(QK^T/√d_k) V
时,仍然无法回答「为什么是这三个矩阵」「为什么是 softmax
而不是 argmax」「为什么要除一个
√d_k」。本系列把第二部分的第一篇花在直觉上,就是想避免这种轻。
1.1 William James 的那句被反复引用的话
注意力(attention)作为一个词,在认知科学里有一百多年的研究史。1890 年 William James 在《心理学原理》(The Principles of Psychology)里写过一句几乎所有 attention 综述都会引用的话:
“Everyone knows what attention is.”
这句话之所以被反复引用,恰恰是因为它几乎完全错了。每个人都觉得自己知道注意力是什么,可一旦被要求定义,就会发现自己说不清。
James 自己接下来给的定义是:mind 在多个同时可得的对象或思想中,以清晰、生动的方式占据其中之一。
这句定义在今天看仍然很准。注意力的核心不是「看到什么」,也不是「选了什么」,而是「在多个候选里挑选一个或几个,并以不同强度去处理它们」。
把 James 这句翻译到机器学习的语言:注意力是「在 n 个候选上构造一个非均匀分布」。这个分布告诉系统每个候选要被处理多少。一旦你接受这个抽象,整个机制后面的数学几乎是必然的。
1.2 视觉系统:为什么必须有「下一步看哪里」
把这件事再往物理一层放,就成了视觉系统的工作机制。
人类视网膜中央有一小块叫中央凹(fovea)的区域,视锥细胞密度极高,能看清物体的细节。
离中央凹越远,分辨率就越低,到边缘几乎只能感知到形状和运动。
换句话说,眼睛不是一台均匀清晰的摄像机,而是一台「中间一小块清楚、四周模糊」的镜头。
要看清整张报纸,你不可能一下子全看清,必须一次又一次把中央凹移到不同位置——这个动作叫扫视(saccade)。
两次扫视之间的短暂稳定阶段叫注视(fixation),通常持续约 200 到 300 毫秒。
一次正常的阅读,每秒大概发生 3 到 4 次扫视。
这给我们的第一个启发是:注意力不是因为「我们想专注」才出现,而是因为「带宽不够」才出现。
如果视网膜每个点都和中央凹一样清晰,如果大脑可以同时处理整个视野的信息,那就不需要选择。
正是因为信息处理通道有限,必须挑选——挑哪个、挑多大权重、何时切换——才有了注意力这件事。
1.3 模型也是一台「带宽不够」的机器
模型也一样。
当我们让一个 decoder 在每一步都能「同时充分」看完编码器给出的所有 hidden state 时,问题就从「能不能看到」变成了「以多大的强度看哪一部分」。
这正是注意力的位置。
具体一点说,假设 encoder 给出 50 个位置的 hidden state,每个 256 维,decoder 要在每个生成步从这 50 × 256 个数字里挑出对当前最有用的那一部分。
如果让 decoder 一视同仁地看完所有 50 个位置——比如直接取平均——那它会被无关信息淹没。
如果让 decoder 只看一个位置——硬选一个——那它丢掉了所有可能的辅助信号。
中间这条路:按 query 内容分配一个 50 维的概率分布,按这个分布加权 50 个位置——就是注意力。
1.4 阅读心理学的更精细图景
人在阅读时并不是按字逐字读,而是有所谓「中心 - 外围」的并行:注视点上的字最清楚,但视野中相邻几个字也能被弱感知,构成感知跨度(perceptual span)。
读到一个不熟悉的术语,注视会停得久;读到「the」「of」这样的功能词,常常会被跳过。
读到歧义结构(比如 garden-path 句),眼睛甚至会回跳到前面再读一遍——这种回跳叫 regression。
这种「按内容动态分配注视时间和注视位置」的机制,对应到模型里就是「按 query 内容动态分配权重」。
换句话说,注意力本来就不是均匀的,注意力是「可调节的非均匀」。
1.5 软:人类的注意力本来就不是 0/1
第三个启发也许最关键:人类的注意力是「软」的。
当你阅读「The cat sat on the mat」时,你的视觉系统并不会真的把「cat」之外的所有词清零。
它只是把更多处理资源分给「cat」,同时保留对「the」「sat」的弱响应。
这种「主要看 A、但也看一点 B、几乎不看 C」的分布,正是软对齐(soft alignment)这个名词的物理来源。
注意力机制不是把一个 token 选出来,而是给每个 token 分一个 0 到 1 之间的权重,总和为 1。
把这三件事合在一起:注意力来自带宽限制、注意力是按内容动态分配的、注意力是软的(连续的)。
这三句话决定了我们后面看到的所有具体设计——softmax 的非负与归一性、权重的可微性、动态依赖 query 的机制——都不是任意选择,而是把上面这三句话写成数学。
二、机器翻译里那个老问题:对齐
把注意力从人类拉到机器,最自然的入口是机器翻译。
机器翻译这件事在统计 NLP 时代有一个困扰所有人的概念:对齐(alignment)。
2.1 一对例子:英→法→日
给定一个英语句子和它的法语译文,要把 source 端的词对应到 target 端的词,对应关系既不是一对一,也不是顺序保持的。
看一个最朴素的例子:
英语:The cat sat on the mat
法语:Le chat est assis sur le tapis
把这两句对一对:「The → Le」「cat → chat」「mat → tapis」「on → sur」这四对很顺。
但「sat → est assis」就有问题——一个英语动词要对应两个法语词。
同样的词组,西班牙语会写成「El gato se sentó en la alfombra」,又出现了反身代词「se」这种英语里完全没有对应的成分。
再换成日语「猫がマットの上に座った」,词序整体翻转,连冠词都没了。
到德语会出现复杂的格变化,到阿拉伯语方向甚至要从右往左排,到中文「猫坐在垫子上」连「the」都没法对应。
2.2 SMT 时代的硬对齐
统计机器翻译(Statistical Machine Translation, SMT)几十年的核心研究,绝大部分都在和「怎么把不对称的对齐学出来」斗争。
经典的 IBM Models(Brown 等 1993)把对齐当作隐变量,学一个对齐分布 a:要做的是「给定 source word,target word 来自哪一个 source 位置」。
Model 1 假设对齐均匀,Model 2 加位置先验,Model 3 引入 fertility(一个源词能生成几个目标词),Model 4 加 distortion(位移),Model 5 修补 deficiency。
这一条线技术非常深,但它有一个根本约束:对齐被建模成离散的、硬的。
每个 target 词必须对应到一个具体的 source 位置,或者一个具体的 source 子集。
这种硬对齐在训练时要用 EM 算法,要用复杂的剪枝,工程实现极其难调,并且很难端到端嵌进一个深度网络。
2.3 NMT 早期的固定 context vector 瓶颈
时间到 2013–2014 年,神经机器翻译(Neural Machine Translation, NMT)开始登场。
Cho 等的 RNN Encoder-Decoder(2014)和 Sutskever 等的 Sequence to Sequence(2014)几乎同时提出:用一个 RNN 把 source 句压成一个固定长度的向量 c,再用另一个 RNN 从 c 出发生成 target 句。
这个范式优雅、可端到端、漂亮,但有一个所有人都立刻看到的问题。
当 source 句很长时,把所有信息压进一个固定维度的向量 c 里,就像把一本书装进一个手提箱——总会丢东西。
Sutskever 自己在论文里也观察到长句翻译质量明显下降。
他甚至用了「把 source 句反过来输入」这种工程 trick 来缓解最早一段信息容易被遗忘的问题。
这种 trick 不是优雅的解,是工程上的应急。
2.4 把固定 c 换成动态 c
换个角度看这件事,它其实和人类阅读的带宽问题一模一样。
RNN 的 hidden state 维度就是带宽,一个 256 维的 hidden state 就那么多容量。
你不能指望它在一个时间步里同时编码「主语 + 谓语 + 宾语 + 修饰语 + 时态 + 语气」。
SMT 时代的应对是显式的硬对齐;NMT 时代的最初应对是「用更大的 hidden state、用更深的 RNN、用 LSTM/GRU」。
这些都是治标。
真正的解药是承认一件事:decoder 在生成第 t 个 target 词时,不应该只看一个固定的 c,而应该有权动态选择 source 端的某一部分作为这一刻的 context。
这就把硬对齐换成了软对齐,把固定上下文换成了动态上下文,把 SMT 时代的隐变量换成了一组可微的权重。
2.5 软对齐解决了什么
但有个细节常被忽略:从硬对齐到软对齐的转换不仅仅是「把离散变成连续」这么简单。
它解决了三件事。
第一,softmax 输出非负归一的权重,可以解读为概率,便于和概率框架接上。
第二,权重连续可微,意味着可以用反向传播一路把误差传回去优化对齐策略,不再需要 EM。
第三,权重依赖 query(也就是当前 decoder 状态),而不是固定的全局对齐表,意味着同一对源句和目标句在不同位置可以有不同对齐。
这三件事合起来,让翻译质量在长句上立刻翻盘。
后面我们会看到,这一组性质几乎被原样搬到了 Transformer 里。
2.6 为什么我把对齐讲这么长
我特意把「对齐」这个老问题讲长一点,是因为很多教材直接从 Bahdanau 的公式开始,跳过了它要解决的问题。
结果学生看完公式只记住了
score = v_a^T tanh(W_a [s; h])
这一行,但说不清这个 score 究竟在算什么。
它在算的就是「decoder 当前状态 s 和 encoder 第 i 个位置的 h_i 之间的对齐强度」。
这个强度就是软化版的对齐变量 α。
一旦你把对齐这个直觉装回脑子里,后面的所有公式都只是这个直觉的不同写法而已。
三、加权平均:注意力的内核就这么一句话
把直觉从认知科学和翻译里拉回机器学习,注意力的内核可以浓缩成一句话:
给定一个 query,从 n 个候选里按相似度算权重,取一个加权平均。
这一句话已经包含了我们要讨论的所有数学。
我们一项一项拆。
3.1 query 是「现在我想知道什么」
「query」是什么?
query 是「现在我想知道什么」的表示。
在 Bahdanau 的翻译模型里,query 是 decoder 当前 hidden state s_t——它编码了「我现在准备生成的是 target 句的第几个词、我刚刚说了什么、我打算说什么」。
在自注意力里,query 是序列中某一个 token 的当前表示——它编码了「这个 token 现在需要哪种信息来更新自己」。
在图像描述里,query 可能是 caption decoder 的状态;在 image classification 的某些注意力变种里,query 可能是一个学得的全局向量。
把 query 想成「问题」就行:每一次注意力调用,都是一次「问候选库一个问题」的过程。
3.2 候选:key 和 value 的雏形
「候选」是什么?
候选是被查的对象集合。
它们最终会以「value」的身份贡献信息,但在计算权重时通常用一个「key」来和 query 对相似度。
在第十三篇我们会专门拆 Q/K/V 三件套,这里只需要建立一个粗粒度直觉:候选库里的每条记录都有「拿来比对的部分(key)」和「拿来取出的部分(value)」。
在最简单的设定下,key 就等于 value——比如 Bahdanau 把 encoder 的 hidden state 同时当作 key 和 value 用。
到了 Transformer,K 和 V 被显式分开成两份不同的投影,这是后话。
3.3 相似度函数:打分的几种经典选择
「相似度」是什么?
相似度可以有很多种——点积、cos 距离、欧式距离反过来、可学的 MLP。
它们的共同目的是:给每个候选打一个分,分数越高说明 query 和这个候选越「契合」。
点积是最常见的选择,因为高维空间里点积和向量内积的几何意义清楚(投影长度),而且矩阵化之后非常适合 GPU 并行。
Bahdanau 用的是 additive attention(一个小 MLP),Luong 用的是 multiplicative attention(点积变种),Transformer 选了 scaled dot-product。
这三种我们后面都会讲。
但关键是它们都先打了一个分。
3.4 加权平均与凸组合
「加权平均」是什么?
把上面的分变成权重 α,要求 α_i ≥ 0 且 Σ α_i = 1,然后输出 = Σ α_i · v_i。
这就是注意力的输出——一个新向量,它是候选 value 的凸组合(convex combination)。
凸组合的几何意义清楚:输出一定落在所有候选 value 的凸包内部,不会跑到外面去。
这件事在数值稳定上是天然的好性质:模型不会因为「权重把某个 value 放大十倍」而爆掉,所有输出都被候选自己 bound 住了。
3.5 为什么权重必须非负且和为一
为什么权重必须非负且加和为一?
我们一条一条想。
如果权重可以是负数,那「加权平均」就不再是凸组合,输出可以走到候选凸包外,而且训练时正负权重容易彼此抵消,梯度方向不稳定。
如果权重可以大于 1,输出量级会随权重直接爆涨,没有自然 bound。
如果不归一化(不强制和为 1),那权重的绝对大小没有「意义」,只能比较相对大小,跨样本、跨层的权重分布会失去可比性。
把这两条写在一起:权重应该是 n 个候选上的概率分布。
一旦同意这件事,softmax 就是把任意 n 个分数变成概率分布的最自然映射——它把分数指数化保持单调、归一化保证和为一、可微保证可训练、连续保证小扰动不会让权重跳变。
3.6 softmax 不是审美选择
我们暂时还不展开 softmax 的更细节(第七篇已经讲过基础,第十五篇还会回来讨论 √d_k 的缩放问题),这里只把它当作「分数 → 概率」的标准件用。
但要强调一句:softmax 在这里不是审美选择,是约束推出来的工程结果。
任何把实数映射到概率单纯形(probability simplex)上的连续可微算子,都满足上面那三条要求。
softmax 是这一类映射里最朴素、最便宜、最稳定的一个。
后来人们试过 sparsemax(Martins & Astudillo 2016)、entmax、Talking-Heads 之类的变种,它们也都满足非负归一与可微,只是在「稀疏性」「头之间共享」这些次级目标上做了取舍。
但内核没变。
3.7 三步流水线:打分 → 归一 → 加权求和
把这一节合起来:注意力 = 给候选打分 → softmax 归一化成概率 → 用概率加权候选 value → 输出新向量。
三步,仅此而已。
后面所有花样——multi-head、scaled、causal、cross、self——都是在「query 是谁」「候选是谁」「打分函数是什么」「mask 怎么加」这四件事上做手脚。
直觉一旦立住,剩下的就是工程组合。
四、为什么不是 argmax:可微优先
写到这里你可能已经想到一个问题:既然要「挑」,为什么不直接挑分数最高的那个?
干脆 argmax,把权重设成 one-hot,然后直接取那个候选的 value。
这样不更干净吗?
4.1 argmax 的致命缺陷:不可微
这个问题不傻。
事实上 SMT 时代的硬对齐就是这种思路,IBM Models 训练时甚至要用 EM 反复推断这个离散选择。
但放到神经网络里,argmax 有一个致命缺陷:它不可微。
argmax 的输出是一个离散索引,输入做小扰动时输出要么不变要么直接跳到另一个候选,没有梯度。
没有梯度意味着反向传播传不下去。
意味着上层的 loss 没法用来更新「打分函数」自身的参数。
整个端到端训练就断了。
4.2 为什么 Gumbel-softmax 等替代方案不够
有人会说,那不能用 Gumbel-softmax 这种 trick 让 argmax 可微吗?
可以。
Gumbel-softmax(Jang et al. 2017)的思路是:在分数上加一个 Gumbel 噪声,然后做温度可控的 softmax,温度趋于 0 时近似一个 one-hot 采样。
它有几个代价。
第一,前向引入了随机性,所以同一输入两次前向输出不同,对 batch normalization、对推理一致性都不友好。
第二,反向用了 straight-through 或重参数近似,方差大、训练不稳。
第三,引入了一个温度超参,需要 anneal schedule。
工程复杂度大增,并且方差通常不友好。
Bahdanau 那一年的工业界还没有这些工具,即使有,也仍然要回答一个更根本的问题:
离散选择和连续平均,哪个更适合表达「decoder 在第 t 步要的信息」?
4.3 软选择更符合任务本质
答案在大多数任务上都是连续平均更合理。
看翻译的例子,当 decoder 生成法语「assis」时,最相关的源词显然是英语「sat」。
但「on」也提供了一些位置和介词搭配的信息,「the cat」提供了主语一致性。
如果硬选一个最相关,剩下的辅助信息就被全部丢掉。
软选择允许 decoder 同时摄取主信息和侧信息,权重的分布本身就携带了「这个时刻信息怎么混合」的策略。
这个混合策略是任务相关的、是数据相关的、是上下文相关的——把它学出来比手工硬选要强。
4.4 softmax 能逼近 argmax,但反过来不行
换个角度想,softmax 还有一个常被低估的好处:它可以「逼近」argmax。
当分数差距很大时,softmax 的输出会非常接近 one-hot;当分数接近时,softmax 给出更平滑的分布。
这意味着模型在训练初期可以保持「软」以获得稳定梯度,训练后期当某些选择确实变得清晰时,可以自动收敛到接近硬选择的形态。
这种「能软能硬」的弹性是 argmax 给不了的。
反过来,scaled dot-product 的 √d_k 缩放就是为了控制 softmax 不要过早进入「过硬」的饱和区——这件事我们会在第十五篇详细讨论。
4.5 可微是硬约束,不是锦上添花
我特别想强调一句:在深度学习里,「可微」不是一种锦上添花的性质,它是设计选择的硬约束。
无数巧妙的算法之所以最后没有进入主流神经网络,就是因为它们不可微或者只能近似可微而代价太高。
注意力之所以能成为基础组件,正是因为它在保持「选择」语义的同时,把整个机制塞进了可微框架里。
这件事说起来轻巧,做起来花了 NLP 整个领域将近 30 年。
五、一个更广义的视角:注意力是寻址
到这里我们已经有了三层直觉:人类视觉的带宽问题、机器翻译的对齐问题、加权平均的可微选择。
把它们抽象一层,注意力其实在做一件经典计算机科学问题:寻址(addressing)。
5.1 硬寻址 vs 内容寻址
一台传统计算机用「索引」做寻址:你给一个地址 i,从内存数组里取出 array[i]。
这是硬寻址,和 argmax 完全一致——输入是离散索引,输出是该位置的精确值,没有梯度。
一台神经网络要做的是「内容寻址(content-based addressing)」:你给一个 query 向量,从「内存」里取出和 query 最匹配的内容。
匹配是用相似度衡量的,输出是「按匹配度加权的所有内容」。
这是软寻址。
5.2 神经图灵机的伏笔
神经图灵机(Neural Turing Machine, Graves 2014)和可微神经计算机(Differentiable Neural Computer, Graves 2016)就是这个思路的早期完整尝试。
他们直接借用了「memory」这个名字。
在那一线工作里,注意力被明确叫作 read head 的 addressing 机制。
NTM 还引入了 location-based addressing(按位置寻址)和 content-based addressing(按内容寻址)的二分。
Transformer 后来基本只保留了 content-based 这条线,把 location 信息显式编码到位置编码里——这是另一种取舍。
5.3 cross / self 都是寻址的特例
把注意力理解成寻址,有一个非常实际的好处:你立刻就能解释为什么有 cross-attention 和 self-attention 两种用法。
cross-attention 是「我用 query 去访问另一个序列的内容」——典型的查询-数据库分离场景,比如 decoder 查 encoder。
self-attention 是「我用 query 去访问自己所在序列的内容」——序列内部的交叉寻址,每个 token 既当 query、又当 key+value,互相检索。
这两种用法在数学上完全相同,区别只在于 query 和候选库的来源。
5.4 KV Cache:把寻址工程化
寻址视角还能解释 KV Cache 这种工程优化。
在自回归推理中,每生成一个新 token,就要把它插入到「可被未来 token 查询的内存」里——这就是 KV cache 的物理意义。
它本质上是一个「不断增长的 key-value 数据库」,新 token 把自己的 K、V 写入数据库,未来 token 用自己的 Q 来读这个数据库。
这件事在第二十二、四十八、五十篇会深入展开。
5.5 内存本身也是学出来的
但有个细节常被忽略:注意力作为寻址机制,和真正的内存系统有一个本质差别——注意力的「内存」本身不是固定的,它是和模型一起学出来的。
Q、K、V 都是从输入投影出来的,投影矩阵 W_Q、W_K、W_V 在训练中被优化。
换句话说,模型不只是学怎么查,还在学「内存里应该按什么角度组织信息以便被查」。
这一点在 Bahdanau 那一代还不明显,因为 K=V=encoder hidden state,没有显式的投影。
到了 Vaswani 2017 引入 W_Q/W_K/W_V 之后,「投影成检索友好的表示」就成了模型容量的一部分。
这件事要等到第十三篇我们才会展开。
六、把直觉写成代码:一个最小例子
直觉讲了五节,我们把它落成最小的代码。
先不引入 Q/K/V 三件套,只用最朴素的版本:query 一个向量,候选若干个向量(key=value),用点积打分,softmax 归一,加权求和。
6.1 二十行实现
import numpy as np
def naive_attention(query, candidates):
"""
query: shape (d,)
candidates: shape (n, d),每行是一个候选向量
返回 (output, weights)
"""
scores = candidates @ query # (n,) 点积打分
weights = np.exp(scores - scores.max()) # 数值稳定的 softmax
weights = weights / weights.sum() # 归一化为概率分布
output = weights @ candidates # (d,) 加权平均
return output, weights这段代码只有四行核心逻辑,但已经是注意力机制的全部内核。
后面的 Q/K/V 投影、scaled、multi-head、causal mask、kv cache,都是围绕这四行做的工程化扩展。
6.2 一个玩具例子
query = np.array([1.0, 0.0])
candidates = np.array([
[1.0, 0.1], # 与 query 几乎共线,分数最高
[0.5, 0.5], # 中等
[0.0, 1.0], # 与 query 正交,分数最低
[-0.8, 0.2], # 与 query 反向
])
out, w = naive_attention(query, candidates)
print("权重:", w.round(3))
print("输出:", out.round(3))跑一下,权重大约是
[0.451, 0.273, 0.165, 0.111],输出是这四个候选的凸组合。
第一项权重最大,因为它和 query 最贴近。
第四项权重不为零,因为 softmax 不会把任何候选权重压到精确的 0——这是软选择和硬选择最直观的区别。
6.3 实验一:放大某个候选会发生什么
我们再做一个小实验:把第一个候选缩放放大十倍,看看权重会变成什么样:
candidates_scaled = candidates.copy()
candidates_scaled[0] *= 10
out2, w2 = naive_attention(query, candidates_scaled)
print("权重:", w2.round(4))你会发现权重几乎全部集中在第一项(接近 one-hot)——因为它的点积分数比其他项大了一个数量级。
softmax 此时进入了饱和区,分布从「软」变成了「硬」。
这个现象在维度变高时也会自然出现:即使各候选量级正常,d 越大,点积期望方差也越大,softmax 也越容易进入饱和。
这就是 √d_k 缩放要解决的问题——后面那一篇我们再回来。
6.4 实验二:用 argmax 代替 softmax
最后一个小实验:如果我们用 argmax 代替 softmax,看看会发生什么:
def argmax_attention(query, candidates):
scores = candidates @ query
idx = np.argmax(scores)
return candidates[idx], idx这个版本只能返回一个候选向量,输出是离散的。
如果稍微扰动 query,输出要么完全不变,要么直接换到另一个候选——梯度信息全部丢失。
这就是为什么深度学习里所有「选择」机制几乎一律用 softmax 的原因。
6.5 实验三:小批量、可训练版本
把上面这段写成可训练形式,再加一个 W_q 的投影:
import torch
import torch.nn as nn
class TinyAttention(nn.Module):
def __init__(self, d):
super().__init__()
self.W_q = nn.Linear(d, d, bias=False)
def forward(self, query, candidates):
# query: (B, d)
# candidates: (B, n, d)
q = self.W_q(query) # (B, d)
scores = torch.einsum("bd,bnd->bn", q, candidates)
weights = torch.softmax(scores, dim=-1) # (B, n)
output = torch.einsum("bn,bnd->bd", weights, candidates)
return output, weights这段代码已经具备了「学一个 query 投影」的能力——这是从「直觉版 attention」走向「真正可训练 attention」的第一步。
如果再加一个 W_k 让 key 和 value 走不同投影、再加一个 W_v 让 value 也独立投影,就到了第十三篇要讲的 Q/K/V 三件套。
6.6 这段代码教给我们什么
这段代码至少教给我们三件事。
第一,attention 的内核非常薄。四行 Python 就够。
第二,attention 的计算图天然 batch 友好——所有运算都是矩阵乘和 softmax,没有任何递归依赖。这就是它能比 RNN 快几十倍的根因。
第三,attention 的所有「能力」最终都体现在 W_q、W_k、W_v 这些可学投影上。机制本身只是一个固定算子,模型的容量来自投影。
后面 Transformer 的所有设计——multi-head、深度堆叠、残差、LayerNorm——都可以理解成「给这套机制加更多投影 + 让它能堆深」。
七、几对常被混的概念
直觉建立到这里基本就够了,但有几对概念在中文资料里经常被混着用,值得在进入 Bahdanau 之前先说清楚。
7.1 一张澄清表
| 概念 | 含义 | 容易和谁混 | 真正区别 |
|---|---|---|---|
| 注意力(attention) | 一个机制:从候选里加权取信息 | 自注意力 | self-attention 是 attention 的特例,区别在 query 和候选是否来自同一序列 |
| 软对齐 | 学到的连续权重分布 | 硬对齐 | 硬对齐是离散索引,软对齐是 n 个 softmax 概率 |
| score | 未归一化的相似度分数 | 权重 | score 是任意实数,权重是 0 到 1 的概率 |
| 加权平均 | 输出 = Σ αᵢ vᵢ | 加权和 | 加权平均特指权重和为 1 的加权和;attention 的输出严格是加权平均 |
| 注意力权重 | softmax 后的 α | 注意力分数 | 不少教材把 score 和 weight 写成一个符号,导致混乱 |
| query / key / value | Bahdanau 之后被固化的术语 | 「输入」 | 同一份输入被投影成三种角色,不能混称 |
7.2 score 和 weight:写代码时务必区分
我把这张表放在这里,是因为后面所有篇章我们都会按这套术语严格区分。
中文里「权重」「分数」「打分」「评分」很容易混着用。
写代码的时候 scores 和 weights
是两个不同的张量,前者是点积结果,后者是 softmax
结果,量级和性质都不一样。
这个区分一旦混淆,公式就读不准。
读论文也是同理:有些论文里 α 是 scores,有些里 α 是 weights,如果不区分会让推导步骤完全错位。
7.3 注意力 ≠ 解释
还有一个常见误解需要现在拆掉:注意力不是「看哪里」的解释。
这是非常顽固的一个误解,连不少综述都在用。
注意力权重高的位置在生成当前输出时贡献了更多 value,这只能说明它在数学上贡献了更多信号;但它不一定就是模型「真正依赖的因果证据」。
Jain 和 Wallace 2019 那篇有名的「Attention is not Explanation」做过一组实验:把 RNN 加注意力的模型注意力权重整个换成另一个随机但等效的分布,模型预测几乎不变。
Wiegreffe & Pinter 2019 反驳了一部分论点,但也承认权重和决策之间的关系比直觉更弱。
这件事我们会在第五十二篇专门展开,这里只是先打个预防针:注意力是一个加权机制,不是一个解释机制。
它本质上和卷积里的「特征图响应强度」是同一档的东西——可以辅助分析,不能直接当作模型给出的因果说明。
八、注意力不是 Transformer 发明的
很多人误以为「注意力 = Transformer」,事实并非如此。
注意力机制在 Transformer 之前已经在多个领域走了很长的路,把这条路看清楚,对理解后面的设计选择非常关键。
8.1 NMT 这条主线
最早把「学一个权重分布去加权候选」这件事系统化做出来的,是 NMT 领域 2014 年 Bahdanau 等人的论文「Neural Machine Translation by Jointly Learning to Align and Translate」。
这篇论文的标题里就埋了关键词「Jointly Learning to Align」——把对齐这个 SMT 时代的隐变量端到端学出来。
它是 attention 在主流 NLP 任务上的首次大规模应用,也是这个机制开始被叫作 attention 的起点。
差不多同时,Luong 等 2015 又给出了一些改进:global vs local attention、dot vs general vs concat 三种打分函数。
这两篇我们会在下一篇详细讲。
8.2 视觉这条线
差不多同时,图像描述(image captioning)领域 Xu 等 2015 的「Show, Attend and Tell」用 attention 让 caption 生成模型能在生成每个单词时聚焦图像不同区域。
它分了 soft attention 和 hard attention 两种实现:soft 用 softmax 加权,hard 用 REINFORCE 训练 categorical 采样。
soft 比 hard 训得更稳,最后基本所有后续工作都默认用 soft 的版本。
视觉问答(VQA)后续也大量用 attention,包括 Yang 等的 Stacked Attention Networks(2016)。
8.3 神经图灵机这条线
再早一点,Graves 2013 的「Generating Sequences With Recurrent Neural Networks」其实已经在用一种基于位置的高斯混合形式做语音合成对齐——那是 attention 的另一种形式。
神经图灵机 NTM(Graves 2014)把 content-based 和 location-based addressing 显式拆开做存储读写,是更通用的形式。
DNC(Differentiable Neural Computer, 2016)在 NTM 基础上加了链表式的时间寻址。
这一条线影响了后来一些显式记忆网络的工作(Memory Networks, End-to-End Memory Networks),但在 NLP 主流里没成为主干件——主干件最终被 Transformer 占了。
8.4 更早的伏笔:MoE 与 saliency
在更早的年代,注意力的雏形甚至可以追溯到对齐模型与混合专家(Mixture of Experts, MoE)这两条线。
MoE(Jacobs 1991)让一个 gating network 输出一组概率分布去选专家,这本质上就是 attention——只不过 candidates 换成了「专家网络」。
一些计算机视觉里的 saliency map 工作(早至 90 年代)也是在算「图像哪里更重要」。
把这些线索串起来你会发现:注意力不是一个新发明,它是一种在多个子领域不约而同被重新发现的通用机制。
8.5 Transformer 的真正贡献
Vaswani 等 2017 的贡献不是发明注意力,而是证明了「只用注意力就够了」,把一个广泛存在的机制从 RNN/CNN 的辅助件升级成主干件。
「Attention Is All You Need」这句标题本身就是一种宣言:把 RNN 全部去掉,把卷积也去掉,只留注意力 + FFN + 残差 + LayerNorm,照样能做 SOTA 翻译。
后面的事情大家都知道了。
理解这一点对你看后续工作的眼光很重要。
当我们看 FlashAttention、Sparse Attention、Linear Attention 这些名字时,它们绝大多数都不在改注意力的「加权求和」内核,而是在改「以什么样的方式存候选、以什么样的复杂度查候选、以什么样的精度算 softmax」——也就是寻址的工程实现。
注意力本身的核心定义已经稳了十年。
九、几个常被忽略的工程踩坑
讲完直觉和历史,最后说几个在工程里反复看到的小坑,提前知道能省掉很多调试时间。
9.1 softmax 数值稳定
第一个坑是 softmax 数值稳定性。
朴素写法 exp(x) / sum(exp(x)) 在 x
较大时会溢出,较小时会下溢。
工业标准做法是先减去最大值:exp(x - max(x)) / sum(exp(x - max(x)))。
减去最大值不改变 softmax 输出(因为分子分母同时除以 exp(max)),但保证最大的指数是 0,避免溢出。
PyTorch 的 F.softmax
内部自动做这个稳定化,所以业务代码一般不用关心。
但当你自己实现 attention(比如学习目的、或者写自定义 kernel)时,必须自己减去最大值,否则随便一个长序列就会数值爆掉。
FlashAttention 这种工程优化甚至要在分块累积时维护 running max 和 running sum,难度更大——第四十二篇会展开。
9.2 attention mask 与 padding
第二个坑是 padding mask。
实际训练里 batch 里每个样本长度不同,要 pad 到一样长。
pad 出来的位置不应该参与 attention——它们是无意义的零向量,让它们参与会污染 softmax 分布。
标准做法是构造一个 mask,把 pad 位置的 score 设成 -∞(实际是 -1e9 或 -1e4,具体看 fp16/fp32),然后再做 softmax。
exp(-∞) = 0,所以这些位置 softmax 后权重为
0,不参与加权。
这个 trick 在第十七篇 causal mask 时还会再用一次,是 attention 实现里的标准件。
9.3 对齐 != 因果
第三个坑是混淆「对齐」和「因果」。
attention 学到的对齐反映的是「在数据集里这两个位置统计上相关」,不是「这两个位置在语义上必须配对」。
短语翻译里这二者大多重合;但在长文本生成里,模型可能学到一些奇怪的对齐——比如 token 大量看自己(self-loop),或者集中看某个标点(“attention sink”,Xiao et al. 2023)。
这些奇怪现象不是 bug,是模型在用 attention 的自由度做工程上意想不到的事。
9.4 注意力的「平均偏置」
第四个坑是 attention 输出的「平均偏置」。
注意力的输出是凸组合,所以它天然有「向均值收缩」的倾向。
如果候选 value 量级接近、又没有特别突出的 query 匹配,输出就接近所有 value 的平均,看起来什么也没学到。
这种情况在训练初期非常常见。
工程上对应的诊断是:看 attention 权重的熵。
如果熵很高(接近 log n),说明模型还没把权重分化出来;训练正常进行后熵会逐渐下降,但不应该下降到 0(那意味着完全 one-hot,又过分集中)。
健康的训练曲线下,attention entropy 通常会稳定在某个中间值。
9.5 不要把 batch dim 和 sequence dim 搞混
第五个坑是张量维度。
attention 的张量通常是
(batch, heads, seq, dim) 或
(batch, seq, dim),softmax 应该沿 seq
维做,加权求和也应该沿 seq 维做。
新手最常见的 bug 是 softmax 沿了错误的维度——结果权重并没有归一到「序列上的概率分布」,模型表现莫名其妙地差但不报错。
写 attention 时建议每一步都打一遍 shape 注释,确保每个算子作用在正确的维度上。
9.6 一个真实的调试故事
我自己在做一个翻译模型时踩过一次很典型的坑。
batch 里短句被 pad 到长句长度,但我忘了给 pad 位置加 mask。
训练 loss 看起来在下降,但翻译输出出现大量「the the the」「,,,」这种重复,质量明显异常。
调了三天才发现原因:pad 位置参与了 softmax 归一,pad 的零向量在某些层会拿到非零权重,导致这些位置的 value 被错误地混入输出。
加上 mask 之后,模型一夜之间从「重复 token 怪兽」变成正常翻译。
这种坑写一遍永远记得。
attention 的设计干净,但「干净」不等于「不容易出错」——任何一个维度、一个 mask、一个 softmax 轴搞错,模型行为都会以一种「不报错但结果离谱」的方式失败。
这也是为什么我们花了一整篇先讲直觉:直觉立稳,调试时才有锚点。
9.7 attention 不一定要全连接
最后一个常被忽略的事实:attention 在原始定义里要求「每个 query 看所有候选」,但工程上完全可以裁剪。
local attention 只让 query 看附近 k 个候选;sparse attention 给定一个稀疏模式(比如 strided、block-sparse);linear attention 用 kernel trick 把 softmax 换成可分解的相似度函数从而避免 n×n 矩阵。
这些都不是把 attention 推翻,而是在「打分函数」「候选范围」「softmax 形式」这三个维度上做不同取舍。
理解了直觉版本,再看这些变种就只是组合学问题。
十、把直觉串到下一篇:硬对齐到软对齐的瞬间
让我们把这一篇的几条线收拢,把视角准备到下一篇 Bahdanau Attention 上。
10.1 三条线的合流
第一条线:人类阅读时的眼动告诉我们,注意力是带宽限制下的「按内容动态分配」机制。
这条直觉到了机器,就是「decoder 在第 t 步动态选择要看 encoder 的哪些 hidden state」。
第二条线:机器翻译里几十年的硬对齐研究,意味着我们已经知道「source 和 target 之间应该有一种位置 - 内容映射」。
这条线到了 NMT,就是把硬对齐换成 softmax 上的概率分布——同样的语义,但变成连续可微版。
第三条线:加权平均的内核——非负、和为一、按 query 算权重、可微——决定了我们必然会用 softmax 而不是 argmax,决定了 attention 必然是凸组合,也决定了它必然能用反向传播一路训练下去。
10.2 机制的必然性
把这三条放到一起,你会发现一个非常清晰的预测:
只要有人把「decoder 当前状态」当作 query、把「encoder 各位置的 hidden state」当作候选库、把「点积或一个小 MLP」当作打分函数、把「softmax + 加权求和」当作输出,这个机制就一定会出现。
Bahdanau 那一篇的工程贡献不是想到了什么前所未有的东西,而是把这件「该来的事」第一次以可训练的形式做出来。
它的价值在于把整套机制塞进了一个端到端可训的 NMT 框架,而不是发明了一个新概念。
10.3 下一篇预告
下一篇我们就把 Bahdanau 的具体公式逐项拆开,看一看
score(s, h) = v_a^T tanh(W_a [s; h])
每一个符号在做什么、为什么这么写、和后来 Luong 2015 的
multiplicative attention 有什么不同。
再下一篇,我们会进一步把 Bahdanau 的写法抽象成 Q/K/V 三件套——这一抽象彻底打开了通向 Transformer 的大门。
到第十五篇我们会详细讲 √d_k 那个看似奇怪的常数;到第十六篇讲 multi-head 为什么要分头;到第十七篇讲 causal mask 怎么让模型只看过去。
这一段六篇连起来,是整个系列最核心的「机制原理」段落。
十一、关键概念回顾
回到开头那句话:本篇不写公式推导,只把直觉立稳。
回看一遍,注意力的内核就是「query 决定权重、加权平均候选 value」。
我们用人类视觉的中央凹和扫视讲了为什么注意力天然存在——带宽不够,必须挑——也用机器翻译里的硬对齐告诉你,这件事在 NLP 里有几十年的伏笔,所谓 NMT 的 attention 不过是把硬对齐软化成了一个 softmax 概率分布。
我们花了一节专门讨论「为什么不是 argmax」,结论是可微优先:神经网络的所有设计都必须是端到端可训的,离散选择因为没有梯度而不能直接用,而 softmax 既能保留软选择语义、又是一个连续可微算子,自然成了第一选择。
我们也用最小代码把整个机制写了出来:点积打分、softmax 归一、加权求和。十几行代码已经包括了 attention 的全部数学内核,后面所有花样都是这十几行的扩展和优化。
我们顺手指出了几个常被混的概念:score 和 weight 不是一回事;硬对齐和软对齐量纲完全不同;attention 不是 Transformer 的专利,它在 NMT、image captioning、神经图灵机里都早已存在;attention 不等同于解释,权重高的地方不一定是因果证据。
这些细节在后续读论文、读源码、调模型时都会反复出现,提前看清能少踩很多坑。
最重要的是:注意力是寻址。
把它放到这个抽象层级上,cross-attention、self-attention、kv cache、sparse attention、linear attention 这些后来的名字才有一个统一的解释——它们都是「query 一个内存库」的不同变种,区别只在内存库怎么构造、怎么压缩、怎么查询。
这个视角会在后面 47 篇里反复用到。
十二、常见误解
12.1 注意力 ≠ 替代 RNN 的全部
第一个误解:注意力可以替代 RNN,所以注意力比 RNN 强。
这种说法跳过了关键。
注意力本身只是「按权重取信息」的机制,它没有规定信息怎么编码、怎么累积、怎么前进。
Transformer 之所以能替代 RNN,是因为它把注意力 + 残差 + LayerNorm + FFN + 位置编码组合成了一个完整架构,每一件都不可缺。
光有注意力,连「序列顺序」这件事都搞不定——self-attention 是排列等变的,必须靠位置编码补上。
这件事在第十四、第二十一篇会专门讲。
12.2 注意力 ≠ 权重
第二个误解:softmax 之后的权重就是注意力。
不准确。
注意力是整个机制:query 给候选打分、softmax 归一、加权求和、输出新向量。
softmax 之后的权重 α 只是这个流水线的一个中间产物。
把 α 当作 attention 的全部,会让你忽略 V 的存在——而 V 的设计(包括 W_V 投影矩阵)才是模型从候选里取出什么的关键。
12.3 注意力越集中越好?不一定
第三个误解:注意力权重越集中越好。
也不一定。
集中的权重意味着模型「确认」了从某个候选取信息,但同时也意味着它放弃了其他候选的辅助信号。
在很多翻译、问答、长文本任务上,权重适度分散反而能保留更多上下文。
研究上对「注意力的熵」和模型性能之间的关系也有大量讨论,结论是没有简单的「越集中越好」或「越分散越好」。
这件事的细节会在第五十二篇讨论可解释性时再回来。
12.4 注意力 ≠ Transformer 发明的
第四个误解:注意力是 Transformer 发明的。
前面已经讲过了,Bahdanau 2014、Xu 2015、Graves 的 NTM 2014 都比 Transformer 早。
Transformer 的真正贡献是「Attention Is All You Need」这句话——只用注意力,不要 RNN,照样能行。
这是一次架构选择上的极简化,不是机制本身的发明。
12.5 注意力 ≠ 解释
第五个误解:注意力机制天然能解释模型行为。
这条最顽固也最危险。
Jain & Wallace 2019、Serrano & Smith 2019、Wiegreffe & Pinter 2019 一系列论文已经反复证明:attention 权重和模型决策之间没有可靠的因果关系。
看注意力图可以做诊断、可以做 ablation 的提示,但不能直接当作「模型为什么这么决定」的解释。
第五十二篇会详细展开。
12.6 自注意力 ≠ 自反注意力
第六个误解:「self-attention」里的「self」很多人理解成「自己看自己」,认为每个 token 主要看自己那一行。
事实上 self-attention 里 query/key/value 都来自同一个序列的不同投影,但每个 token 在计算 attention 时主要看的是其他 token——尤其是和它语义相关的 token。
「self」指的是「同一序列内部」,不是「自反」。
很多论文里 self-attention 权重图会显示明显的对角线,这只是因为某个 token 自己投影出的 q 和 k 内积常常较高,但绝不是设计目标。
健康的 self-attention 应该有跨位置的强连接,不然就退化回 RNN 没有信息流的问题。
12.7 注意力的复杂度不可避免吗
第七个误解:「attention 的 O(n²) 是无法避免的代价」。
并非如此。
Linear Attention(Katharopoulos 2020)、Performer(Choromanski 2020)、Mamba(Gu 2023)等线性时间复杂度的替代方案已经存在多年。
但它们各有取舍——通常在长程依赖、训练稳定性、表达能力上有所妥协。
第十八篇会专门讨论复杂度,第五十五到五十八篇会讨论替代架构。
至少当下,O(n²) 仍然是质量最高的实现。
十三、下一步
下一篇 12|Bahdanau Attention 会回到 2014 年,把 Bahdanau, Cho, Bengio 那篇「Neural Machine Translation by Jointly Learning to Align and Translate」逐段拆开。
我们会看:固定长度 context vector 的瓶颈是什么、怎么把
attention 嵌入
encoder-decoder、v_a^T tanh(W_a [s; h])
每一项的语义、为什么这是 additive attention、和 Luong 2015
的 multiplicative attention 比起来各自的取舍是什么。
这一篇是从直觉到工程的桥梁。
再往后,13|Q/K/V 三件套 会把 Bahdanau 的写法抽象成 query/key/value 三个矩阵,这一步抽象是后面所有 Transformer 工作的起点。
读完这两篇,你应该能对着
softmax(QK^T/√d_k) V
这一行公式,把每一项的语义、几何意义、工程动机都立刻说清楚。
十四、参考文献
- James, W. The Principles of Psychology. Henry Holt and Company, 1890.(注意力的早期定义来源)
- Rayner, K. “Eye movements in reading and information processing: 20 years of research.” Psychological Bulletin, 124(3): 372–422, 1998.
- Bahdanau, D., Cho, K., Bengio, Y. “Neural Machine Translation by Jointly Learning to Align and Translate.” ICLR 2015 (arXiv:1409.0473, 2014).
- Sutskever, I., Vinyals, O., Le, Q. V. “Sequence to Sequence Learning with Neural Networks.” NeurIPS 2014.
- Cho, K. et al. “Learning Phrase Representations using RNN Encoder-Decoder for Statistical Machine Translation.” EMNLP 2014.
- Brown, P. F. et al. “The Mathematics of Statistical Machine Translation: Parameter Estimation.” Computational Linguistics, 19(2): 263–311, 1993.
- Xu, K. et al. “Show, Attend and Tell: Neural Image Caption Generation with Visual Attention.” ICML 2015.
- Graves, A. “Generating Sequences With Recurrent Neural Networks.” arXiv:1308.0850, 2013.
- Graves, A., Wayne, G., Danihelka, I. “Neural Turing Machines.” arXiv:1410.5401, 2014.
- Luong, M.-T., Pham, H., Manning, C. D. “Effective Approaches to Attention-based Neural Machine Translation.” EMNLP 2015.
- Vaswani, A. et al. “Attention Is All You Need.” NeurIPS 2017.
- Jain, S., Wallace, B. “Attention is not Explanation.” NAACL 2019.
- Wiegreffe, S., Pinter, Y. “Attention is not not Explanation.” EMNLP 2019.
- Jang, E., Gu, S., Poole, B. “Categorical Reparameterization with Gumbel-Softmax.” ICLR 2017.
- Martins, A., Astudillo, R. “From Softmax to Sparsemax: A Sparse Model of Attention and Multi-Label Classification.” ICML 2016.
- Yang, Z. et al. “Stacked Attention Networks for Image Question Answering.” CVPR 2016.
- Serrano, S., Smith, N. A. “Is Attention Interpretable?” ACL 2019.
- Xiao, G. et al. “Efficient Streaming Language Models with Attention Sinks.” arXiv:2309.17453, 2023.
- Jacobs, R. A. et al. “Adaptive Mixtures of Local Experts.” Neural Computation, 3(1): 79–87, 1991.
- Graves, A., Wayne, G., et al. “Hybrid computing using a neural network with dynamic external memory.” Nature, 538(7626): 471–476, 2016.
← 上一篇:10|RNN 的根本局限 | 下一篇:12|Bahdanau Attention →
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
15|Scaled Dot-Product:那个 √d_k 是怎么来的
很多人第一次读 Vaswani 2017 的公式时,都会卡在那一个 √dk 上。
【Transformer 与注意力机制】07 Softmax 与概率分布:从分数到选择的桥
Softmax 不是一个孤立的归一化函数,而是把任意实数分数变成概率分布的一座桥。本文从'为什么需要它'出发,讲清楚公式、几何、温度、稳定性、与交叉熵的配合,以及它在 Transformer 注意力里扮演的关键角色。
【Transformer 与注意力机制】03 矩阵乘法的两种视角
把矩阵乘法掰开成两种等价但风格不同的视角——『行 × 列』的点积视角和『列的线性组合』视角,最终落到 QK^T 的形状分析。
【Transformer 与注意力机制】01|为什么要从这里开始
这是【Transformer 与注意力机制】系列的第一篇,承担两件事:一是把这套五十多篇文章为谁写、解决什么问题、彼此之间是什么关系交代清楚;二是为完全没基础的读者画出一条从向量、点积、矩阵乘法走到自注意力、再走到大语言模型的爬升路径,让你在投入时间之前先知道终点在哪、路上要经过哪些坎、读完之后你会、还不会做什么事。