想象你是一位经验丰富的主厨,正带着学徒做一桌宴席。面前的长桌上摆着 50 道菜所需的食材,而学徒既不会读整本菜谱,也不能同时盯着所有食材。他唯一想到的办法,是把 50 种食材全部倒进一个大汤桶,煮成一锅浓汤——做每一道菜时,都从这锅里舀一勺。这就是传统 Seq2Seq 模型的处境:无论原文多长,都要先被“压缩”成一个固定的上下文向量,解码器再从这个向量里慢慢挤出翻译。句子短还行,句子一长,细节早就烂在汤里了。于是,研究者们给模型装上了“注意力”:允许解码器在生成每一个词时,直接回头去看原文的各个位置,并学会把更多“眼神”分给与当前输出最相关的部分。注意力机制,本质上就是给 Seq2Seq 装上了一双会分轻重缓急的眼睛——它不改变翻译的流程,却改变了信息的流向,让长句翻译、文本摘要这些任务第一次真正变得可用。本篇文章,我们将从“为什么需要注意力”讲到“注意力权重怎么算”,再到“软硬注意力的区别”,带你把这口厨房里的学问一步步拆开。
掌握注意力机制,是理解 Transformer 与一切现代大模型的前提。对你而言,它不仅是面试必考知识点,更是阅读论文、调优模型时的核心思维工具。
目录
可视化演示
展示注意力机制如何让解码器动态聚焦编码器不同位置,交互切换软/硬注意力模式观察权重分布差异。
本节配有交互动效(进入视口后自动加载,约 3KB)。
注意力机制:动态聚焦关键信息 绿色为高权重编码位置(注意力聚焦点) 青色为解码器当前步(生成输出) 橙色为上下文向量(加权求和) att(Q,K,V)=softmax(Q*K^T)*V 软注意力对所有位置分配连续权重,硬注意力仅关注峰值位置,注意力机制解决了固定上下文向量的信息瓶颈
注意力动机——信息瓶颈之痛
有个学徒第一次进后厨,主厨递给他一张写满 50 行配料的菜单,让他准备一整桌宴席。学徒想了半天,把所有食材都丢进一个大锅里,熬成一桶“万能浓汤”。之后每做一道菜,他都从桶里舀一勺“精华”。前五道菜还算有模有样,到了十几道菜之后,汤里的味道早已搅成一团——鱼味和葱味混在一起,谁都说不清底料是什么。
在深度学习中,这个“学徒”就是传统的 Seq2Seq 模型。编码器读完整个输入序列,把全部信息压缩进一个固定长度的向量,也就是上下文向量;解码器在生成每一个输出词时,都只能从这一个向量里“舀汤”。问题很明显:句子越长、信息越多,这个向量的容量越不够用,远处位置的信息被不断稀释、覆盖——这就是信息瓶颈,也是注意力机制出现前,机器翻译在长句上表现糟糕的根本原因。
注意力机制的解法,是不再要求“一锅汤装下所有菜”。Bahdanau 等人在 2015 年的机器翻译论文中提出:编码器把每个输入位置的状态都留下来,解码器每走一步,自行决定去看哪些状态、每种状态各看几眼。如果把传统方案写成公式,就是:
注意力机制的第一步,就是把那口碍事的“大锅”扔掉,把所有食材放回各自的小碗里。想明白“为什么要扔锅”,你就理解了注意力的全部动机:不是故弄玄虚,而是信息容量不够用。
- 编码器状态集合\(\{h_1, h_2, \ldots, h_{T_x}\}\) 是编码器在每个输入位置留下的“备菜记录”,每个记录都对应某个时刻的输入信息。
- 聚合压缩函数\(q(\cdot)\) 表示把整串状态揉成一个向量的硬性操作,简单实现往往就是取编码器最后一个时刻的状态。
- 固定上下文向量\(c\) 是解码器唯一能看到的“大锅汤”,不论生成哪个词,它都保持不变。
- 信息瓶颈来源由于 \(c\) 的维度有限,当 \(T_x\) 很大时,汤里的细节必然互相挤压、丢失。
编码器状态——备菜台上的小碗
扔了锅,就要摆碗。主厨让学徒把每样食材单独处理好,装进各自的小碗并编号:1 号碗放葱、2 号碗放姜、3 号碗放鱼……碗与碗互不掺和,但每一碗都保留了食材原本的形状和位置感。
在 Seq2Seq 中,这些小碗就是编码器的隐藏状态 \(h_j\)。它们本来就在编码过程中逐位置产生,只是传统方案只用最后一个状态。注意力机制要求把它们全部“端上桌”,作为解码器随时可查的原料池。
为了让每个“碗”都含有前后语境的完整信息,工程上常用双向 RNN / GRU:一个方向从左往右读,另一个方向从右往左读,然后把同一位置两个方向的状态拼起来,得到最终的输入状态:
每个小碗里的食材,都被“正反各看一遍”后再装好。这样主厨在做菜时随便拿起一碗,都能知道它在整桌宴席中的来龙去脉——这正是注意力查询的原料基础。
- 正向隐状态\(\overrightarrow{h_j}\) 是正向 RNN 读到第 \(j\) 个词时输出的状态,携带了从开头到 \(j\) 的信息。
- 反向隐状态\(\overleftarrow{h_j}\) 是反向 RNN 从末尾倒着读到第 \(j\) 个词时的状态,携带了从尾部到 \(j\) 的信息。
- 向量拼接\([\cdot;\cdot]\) 把两个向量首尾相接,形成维度更高的新向量。拼接后 \(h_j\) 同时带有上文与下文信息。
对齐模型——嗅觉判断匹配
主厨站在出菜口,手里刚摆好上一道菜。他要在几十个碗中快速判断:当前这道菜最缺什么?他靠的是嗅觉与经验的匹配——闻到鱼肉味,就知道该往鱼碗那边看。
模型同样需要一个“嗅觉”,也就是打分函数。解码器用一个状态 \(s_{i-1}\) 代表“我已经做到哪一步了”,编码器每个位置有状态 \(h_j\)。把二者交给一个对齐模型,就能算出当前解码位置与输入位置之间的匹配分数 \(e_{ij}\)。
最经典的加性对齐模型(Bahdanau, 2015)是:把 \(s_{i-1}\) 与 \(h_j\) 拼接起来,过一次线性变换,再用 \(\tanh\) 激活,最后乘上打分向量 \(v_a\),得到一个标量:
分数越高,说明“这道菜与那只碗越搭”。但主厨不会只凭一股味道就定全部心思——他还得把几十个碗的“气味浓度”放在一起比较,按比例分配眼神。这就引出了归一化。
- 拼接向量\([s_{i-1}; h_j]\) 将解码器上一时刻状态与编码器第 \(j\) 位置的输入状态拼接成一个长向量。
- 线性变换\(W_a [s_{i-1}; h_j]\) 通过可学习的矩阵 \(W_a\) 将它们映射到新的隐含空间。
- 非线性激活\(\tanh(\cdot)\) 对变换结果做非线性压缩,把数值稳定在 \([-1,1]\) 附近,让网络能表达更复杂的匹配关系。
- 标量分数\(v_a^\top \cdot (\cdot)\) 把激活后的向量与打分向量 \(v_a\) 做内积,得到标量 \(e_{ij}\)。
权重归一化——把眼神化为份额
主厨闻了一圈,心里有了排名:鱼味最冲,姜次之,葱再次之。但他看每个碗的时间总和必须恰好是一餐饭的时长——不可能因为鱼味重就永远只看鱼。于是他给自己定了一张“眼神份额表”:每只碗分到一定的“眼神百分比”。
模型的“眼神百分比”就是注意力权重。要做到这一点,需要把上一步算出的 \(e_{ij}\) 转成正数,且总和等于 1。Softmax 是最标准的工具:它把任意一组实数分数平滑地映射成一个概率分布。对于位置 \(j\),它得到的权重为:
有了份额表,主厨的动作变得有章可循:看鱼碗 60%,看姜碗 25%,看葱碗 10%…… 最后还剩下 5% 扫一眼蒜碗。这些份额下一步就会被用来“取料”。
- 指数化\(\exp(e_{ij})\) 把任意实数分数变成正数,并拉大分数差距,分数略高一点,指数后的优势就明显得多。
- 分母求和\(\sum_{k=1}^{T_x} \exp(e_{ik})\) 遍历所有输入位置,把它们的指数分数加起来作为“总眼神池”。
- 归一化权重\(\alpha_{ij}\) 是位置 \(j\) 在生成第 \(i\) 个输出时获得的注意力权重,满足 \(\sum_{j=1}^{T_x} \alpha_{ij} = 1\)。
上下文向量——按需取料
份额表定好了,主厨开始取料:从鱼碗里舀 60% 的鱼肉量,从姜碗取 25% 的量,从葱碗取 10%……最后拌成一碗“应景混合精华”。这道菜需要什么,这碗精华就是什么;下一道菜变了,比例与精华也会整体改变。
对应到模型,这碗“混合精华”就是上下文向量 \(c_i\)。它不再是编码器最后那个固定状态,而是当前解码步根据注意力权重,对所有编码器状态做加权求和得到的动态向量。每生成一个词,都会重新计算一次:
把这份“应景精华”交给解码器,它就能自信地落笔生成当前词——因为它既知道自己已经写了什么,也清楚原文里此刻哪一部分最该被用到。
- 权重系数\(\alpha_{ij}\) 是第 \(i\) 步解码分配给输入位置 \(j\) 的注意力权重,来自 softmax 归一化。
- 编码器状态\(h_j\) 是输入位置 \(j\) 的上下文表示,也就是“小碗中装好的食材”。
- 加权求和\(\sum_{j=1}^{T_x} \alpha_{ij} h_j\) 对全部输入位置的表示做加权求和,结果是当前步专属的动态上下文。
软硬注意力——两种派菜方式
主厨有两种派菜哲学:一种是“博采众长”,把五个碗按比例各取一些,混成一道风味复杂的新菜;另一种是“孤注一掷”,只认准最对味的那一个碗,整碗端走。菜单上的每道菜,都可以由其中一种策略来派。
这两种策略对应注意力的两个变体。软注意力对所有编码器状态做加权平均,每一步都用到全部输入位置的信息;硬注意力则先选出一个最相关的输入位置,再直接取用该位置的状态,相当于做了一次离散选择。
软注意力:
硬注意力:
在真实落地时,绝大多数系统选择软注意力,因为它可以被反向传播直接训练;而硬注意力由于离散决策带来的训练难度,通常需要配合采样或强化学习,往往得不偿失,其效果也未必超过软注意力。
- 软注意力取料\(c_i = \sum_{j=1}^{T_x} \alpha_{ij} h_j\) 对所有位置做加权平均,每个位置都有贡献。
- 硬注意力选择\(\hat{j} = \arg\max_j \alpha_{ij}\) 找出权重最大的那个位置下标。
- 直接取用\(c_i = h_{\hat{j}}\) 把选中的那一个编码器状态作为上下文向量,其他位置一概不用。
应用实战——翻译、摘要与可视化
开饭馆的日子久了,主厨发现自己的“眼神分配法”不只可以用于做菜,还可以用在采购、摆盘、库存管理上——只要任务需要“在一堆选项里挑重点”,这套方法就成立。注意力机制也是这样:只要任务本质是“从输入序列到输出序列的生成”,注意力就能大幅提升效果。
在机器翻译中,编码器读入输入句,解码器逐词生成译文,注意力让每个译文词都能返回原文找到对应区域。在文本摘要中,输入是长文档,输出是简短的摘要,注意力在摘要生成的每一步决定“这段该看正文的哪几句话”。在视觉语言任务中,把图片的区域特征当成编码器状态,注意力就能在画面上移动“眼神”,完成看图说话或视觉问答。下图是注意力参与解码输出预测的常用方式——把解码器状态与动态上下文一起拼起来,映射到词表空间:
更让工程师兴奋的是,注意力权重 \(\alpha_{ij}\) 可以被直接可视化成“对齐热力图”:哪一格最亮,就代表哪两个语言片段在翻译时相互对应。这就像主厨在每个菜品旁贴一张便签——“这道菜的关键食材是鱼”,让决策过程既高效又透明。
- 解码器状态\(s_i\) 是当前解码步的隐状态,编码了“已经生成了什么”。
- 动态上下文\(c_i\) 是这一步通过注意力从原文取来的关键信息。
- 拼接映射\([s_i; c_i]\) 把两者拼起来,经过 \(W_o\) 线性映射到词表大小,再用 softmax 得到词汇分布。
AI 应用场景
神经机器翻译的逐词对齐
阶段:模型架构设计 / 训练优化
解决的问题:传统机器翻译按短语块局部拼接,长距离词序重整经常出错;加入注意力后,解码器每个词生成时都能直接关注原句中对应区域,长句翻译质量大幅改善,错位与漏译明显减少。
具体案例:在英语到中文的新闻翻译中,翻译 “The boy who is wearing a red hat is my brother” 时,注意力会让“戴着红帽子的男孩”这半句在生成时把目光锁定在原句的 who is wearing a red hat 上,而不是机械地按短语块顺序硬翻。
长文档自动摘要
阶段:输入表示与解码生成
解决的问题:文档动辄几百上千词,即使 LSTM 也会逐渐遗忘开头;注意力让每步摘要生成从原文各句动态取样,解决长文信息丢失与重复摘要的问题。
具体案例:新闻聚合产品用 Seq2Seq + attention 对 800 字的体育报道生成 80 字摘要;热力图显示生成“比分”一词时,模型确实把注意力集中到包含数字的句子,而不是开头套话。
图像描述中的区域关注
阶段:跨模态编码与生成
解决的问题:生成“一只猫坐在沙发上看窗外”时,模型若只看全局图像向量,会漏掉局部细节;注意力可以把图像的 R-CNN 区域特征当作“小碗”,每一步按需取区域。
具体案例:在图像描述评测中,引入注意力后模型生成“小狗”一词时,注意力图会聚焦在图片右下角的小狗区域,而生成“沙发”时焦点转移到左侧,显著提升描述准确率。
Python 演示
import numpy as np def softmax(x): """把打分向量变成概率分布, 总和为 1.""" x = x - np.max(x) e = np.exp(x) return e / e.sum() def attention(query, keys, values): """完整注意力层: 打分 -> softmax -> 加权求和.""" scores = np.dot(keys, query) weights = softmax(scores) context = np.dot(weights, values) return context, weights if __name__ == "__main__": # 模拟句子"猫 喜欢 鱼"的三个编码器状态 keys = np.array([ [0.8, 0.1, 0.0], [0.2, 0.9, 0.1], [0.1, 0.2, 0.8], ]) values = keys.copy() # 解码器第一步状态偏向"猫" query = np.array([0.9, 0.2, 0.0]) context, weights = attention(query, keys, values) print("注意力权重:", np.round(weights, 3)) print("权重之和:", round(float(weights.sum()), 6)) print("上下文向量:", np.round(context, 3)) print("最关注位置:", int(np.argmax(weights)) + 1)
x = x - np.max(x)先减去最大值,防止指数运算数值溢出,结果不变。scores = np.dot(keys, query)点积对齐模型:衡量每个编码器状态与当前解码器状态的匹配程度。weights = softmax(scores)把分数归一化成总和为 1 的注意力分布。context = np.dot(weights, values)按权重对 values 加权求和,得到当前步的上下文向量。values = keys.copy()在标准注意力中,values 通常就是编码器状态本身。query = np.array([0.9, 0.2, 0.0])模拟解码器当前状态:它更“想看”猫,因此权重应偏向第一行。print("权重之和:", ...)验证 softmax 输出满足概率分布性质。
常见误区
误区我觉得注意力机制就是让解码器每次只看最相关的那一个词,其它词完全不看,像聚光灯照在舞台上。
正解软注意力其实是对所有位置做加权平均。权重分布在所有位置上,只是重要的词分到更大比例,其他位置依然有微小贡献。“只看一个词”是对硬注意力的理解,而大多数实际模型用的是软注意力,每个位置都会影响最后的上下文向量。
为何常见:因为“注意力”这个词容易让人联想到聚光灯,聚焦意味着光只照一个点,于是把人的视觉直觉直接套到了模型上。
误区我以为训练完成后 alpha 就固定了,它和神经网络里的权重一样,是一组可学习的参数。
正解alpha 不是参数,而是由查询向量(解码器状态)与键(编码器状态)在每一步动态计算出来的瞬时值。模型真正学习的是打分函数里的 \(W_a\)、\(v_a\) 等参数;alpha 每次前向传播都会随输入和解码进度变化。
为何常见:中文里“注意力权重”和“网络权重”都叫权重,初学者很容易把二者混为一谈。
误区我认为注意力只是给原来的那个固定上下文向量加了个权重,所以整个解码过程还是只有一个上下文向量。
正解固定上下文向量是编码器最后那个状态,在整个解码过程中不变;引入注意力后,每个解码时间步都有一个自己专属的上下文向量 \(c_i\),它根据当前解码状态实时变化,每生成一个词都会重新计算。
为何常见:因为看论文里注意力连接画得密密麻麻,容易忽略每个时间步的 \(c_i\) 并不相同。
误区硬注意力只挑一个位置,计算量小,训练应该更简单、更省资源。
正解硬注意力涉及离散选择或抽样,梯度无法直接回传,需要蒙特卡洛采样或强化学习来估计梯度;而软注意力所有权重对输入可导,可以端到端用反向传播训练。因此大多数任务都用软注意力,硬注意力更多是研究或特殊场景才用。
为何常见:人肉眼看东西确实是“聚焦一个点”,就把这种直觉误当作工程上更简单。
面试题
Q1. 传统 Seq2Seq 的“信息瓶颈”指的是?
- A.编码器算力不足
- B.所有输入信息被压缩进固定上下文向量← 正确答案
- C.解码器不能并行
- D.词表太大
答案:B。传统 Seq2Seq 把整个输入序列压缩成固定长度向量,再让解码器依赖它生成输出,序列增长时信息丢失严重,这就是信息瓶颈。A 是硬件问题,C 是并行问题,D 是输出层问题,都与信息压缩无关。
Q2. 在注意力打分公式 \(e_{ij} = v_a^\top \tanh(W_a [s_{i-1}; h_j])\) 中,\(v_a^\top\) 的作用是?
- A.对拼接向量做归一化
- B.把隐层向量压成标量分数← 正确答案
- C.把两个向量拼接起来
- D.增加记忆容量
答案:B。\(v_a^\top\) 是打分向量,与 \(\tanh\) 的输出做内积后得到一个标量 \(e_{ij}\),代表对齐程度。A 是 softmax 做的事,C 是拼接操作,D 与注意力打分无关。
Q3. Softmax 归一化后,所有注意力权重的总和满足?
- A.等于 1← 正确答案
- B.等于输入长度
- C.小于 1
- D.等于解码步数
答案:A。Softmax 的分母是所有权重的指数和,分子为单个指数,因此输出总和恒为 1,可解释为概率分布。
Q4. 关于硬注意力,正确的说法有?
- A.每次都选权重最大的输入位置← 正确答案
- B.上下文向量是 one-hot 选取的结果← 正确答案
- C.梯度可以直接反向传播到编码器
- D.训练通常需要采样或强化学习← 正确答案
答案:ABD。硬注意力通过 argmax 或采样选位置,上下文退化为单个状态,离散操作不可导,不能直接把梯度传回编码器,通常用强化学习等技巧训练。C 恰恰说反了。
Q5. 上下文向量 \(c_i\) 的核心作用是?
- A.替代解码器状态 \(s_i\)
- B.把原文按注意力权重动态汇总成当前步要用的信息← 正确答案
- C.把全部输入压缩成一个向量
- D.计算 loss
答案:B。\(c_i = \sum_j \alpha_{ij} h_j\) 是编码器各状态的加权和,随解码步骤变化,给当前输出提供需要的原文信息。A 错在它不会替代状态,C 是旧方案,D 与 loss 无关。
Q6. 注意力机制最初被引入 Seq2Seq 的主要动机是什么?
- A.解决梯度消失
- B.解决固定上下文向量的信息瓶颈← 正确答案
- C.让模型可以并行训练
- D.降低词表大小
答案:B。Bahdanau 等人引入注意力正是为了摆脱固定上下文向量带来的信息瓶颈,让解码器随时可以直接关注原文的任意位置。梯度消失是 LSTM/GRU 从结构层面解决的问题,并行训练是后来 Transformer 进一步改善的方向。
Q7. 下列关于软注意力的说法正确的有?
- A.对全部编码器状态做加权平均← 正确答案
- B.每个解码步生成的上下文向量都不同← 正确答案
- C.所有权重之和恒为 1← 正确答案
- D.不可微,不能用梯度下降训练
答案:ABC。软注意力通过 softmax 得到权重,对所有编码器状态加权求和;每步上下文向量不同,权重和为 1,并且对输入完全可导,可以端到端训练。D 说反了。
Q8. 在机器翻译的可视化中,注意力权重矩阵的行和列通常分别代表?
- A.行是编码器位置,列是词表
- B.行是解码器输出位置,列是编码器输入位置← 正确答案
- C.行是嵌入维度,列是注意力头
- D.行是训练轮次,列是 batch 大小
答案:B。注意力矩阵 \(A_{ij}\) 第 \(i\) 行对应解码器生成的第 \(i\) 个词,第 \(j\) 列对应编码器读到的第 \(j\) 个输入词,单元格颜色深浅表示权重大小。
Q9. 关于加性注意力与点积注意力的比较,下列说法不正确的是?
- A.加性注意力包含可学习的 \(W_a\) 和 \(v_a\)
- B.点积注意力没有额外的可学习参数
- C.在高维场景下点积注意力通常直接用 tanh 归一化← 正确答案
- D.加性注意力用 tanh 提供非线性
答案:C。点积注意力直接用内积打分,不经过 tanh;高维场景下内积方差变大,常用“缩放点积”除以 \(\sqrt{d}\) 来稳定。C 的后半句错误。
Q10. 以下哪些关于“注意力机制带来的额外好处”是正确的?
- A.提供了可解释的对齐关系← 正确答案
- B.为梯度从解码器直接流向编码器提供捷径← 正确答案
- C.减少每个 token 所需的计算量
- D.缓解 RNN 处理长序列时的远期信息遗忘← 正确答案
答案:ABD。注意力可以可视化为对齐矩阵,带来可解释性;注意力连接让解码器直接访问编码器各位置,梯度路径更短,缓解长距离遗忘;但注意力本身会增加计算量(解码器每步都要对所有位置打分),并非减少,所以 C 错。