注意力机制:让模型学会抓重点,从查询、键、值到Transformer
前面我们已经认识了卷积神经网络、循环神经网络,也大致知道模型是怎么“看图”“读句子”的。到了这一章,我们要学一种更像人脑的能力:不是把所有信息一视同仁,而是学会把力气花在重点上。
这就是注意力机制。
你可以把它理解成这样一件事:
- 你在图书馆找书,不会把每一本书都翻完,而是先看目录、标签和书名。
- 你在做阅读理解,也不会把每个字都同等对待,而是会先盯住题目相关的句子。
- 你在跟人聊天,耳边有很多杂音,但真正重要的那句话,你会下意识地抓住。
模型也一样。面对大量输入时,它不应该平均用力,而应该学会:哪些信息值得多看一眼,哪些信息可以少看一点。
这一篇我们用最通俗的话,把注意力机制从头讲清楚,内容会覆盖:
- 注意力提示、查询、键和值
- Nadaraya-Watson 核回归
- 注意力评分函数:掩蔽 softmax、加性注意力、缩放点积注意力
- Bahdanau 注意力
- 多头注意力
- 自注意力和位置编码
- Transformer 的整体结构
一、为什么模型需要注意力?
1. 人类本来就会“挑重点”
想象你在一个热闹的集市里找朋友。周围有很多人、很多声音、很多招牌,但你不会把每个细节都完整记住。你会先盯住对方的衣服颜色、站姿、招手动作,快速缩小范围。
这就是注意力。
注意力机制的核心,不是“记住全部”,而是“根据当前任务,决定该关注什么”。
2. 传统模型的问题:总想平均分配注意力
很多早期模型遇到一段输入时,容易把所有位置看成差不多重要。这样做有两个问题:
- 信息太多时,会被噪声淹没。
- 距离太远的关键信息,模型不一定能及时抓住。
比如翻译一句话时,英文单词和中文词语之间经常不是一一对应的。如果模型只按固定顺序往前读,遇到长句就容易“前面忘了,后面又来不及看”。
注意力机制就是为了解决这个问题而生的:让模型在需要的时候,动态回头看更相关的内容。
3. 一个很直白的比喻:查字典
你要查“卷积”这个词时,会先在目录里找“卷”或“convolution”附近的条目。
- 你的目的,就是查找现在真正想要的内容,这叫 查询(Query)。
- 每个词条都有自己的名字、标签、索引,这些像 键(Key)。
- 真正的词条解释内容,像 值(Value)。
模型也是同样的思路:
- Query:我现在想解决什么问题
- Key:每个候选信息的“标签”
- Value:被取出来的真实内容
注意力机制做的事,就是先比较 Query 和每个 Key 的相关程度,再把对应的 Value 按重要性加权汇总。
二、注意力提示:模型该往哪看?
1. 生物学里的注意力提示
在现实世界里,注意力不是随机发生的,而是会被一些“提示”触发。
比如:
- 你听到自己的名字,会立刻抬头。
- 你看到红色警示牌,会比普通文字更敏感。
- 你在做数学题时,会更在意数字、符号和条件,而不是题干里的背景故事。
这些能引导注意力的线索,就叫 注意力提示。
2. 查询、键和值:注意力机制的三件套
这是注意力机制最重要的一组概念。
| 名称 | 通俗理解 | 作用 |
|---|---|---|
| Query | 我现在想找什么 | 发出需求 |
| Key | 每条信息的标签 | 用来比较相关性 |
| Value | 这条信息真正的内容 | 被加权汇总 |
你可以把它想成“在商场里找商品”:
- Query 是你心里想买的东西。
- Key 是每个货架贴的商品标签。
- Value 是货架上真正的商品。
模型先看 Query 和 Key 像不像,再决定从哪些 Value 多拿一点。
3. 注意力其实就是加权平均
如果只说一句话,注意力机制可以概括成:
先算每个位置的重要性,再做加权平均。
数学上可以写成:
output=sumialphaivioutput = sum_i alpha_i v_ioutput=sumialphaivi
其中:
- alphaialpha_ialphai 表示第 iii 个位置的权重
- viv_ivi 表示第 iii 个位置的值
权重越大,这个位置对结果影响越大。
4. 一个生活化例子
假设你在读一句话:
小明把苹果放进了冰箱,因为它很容易坏。
这里“它”指的是“苹果”,而不是“冰箱”。
如果模型想判断“它”指谁,就不能平均看前文所有词,而应该更关注“苹果”这个词。注意力机制做的,就是这种“把目光投向更相关词语”的工作。
5. 注意力的可视化
注意力不是神秘黑箱。我们通常可以把权重画成热力图,看看模型到底在关注哪里。
比如在机器翻译里:
- 译“我喜欢这本书”时,模型可能会重点关注“喜欢”和“书”。
- 译长句时,某个中文词可能会对应英文里的几个词。
可视化的价值很大,因为它能帮我们回答一个很实际的问题:
模型是“真的看懂了”,还是只是碰巧猜对了?
三、注意力汇聚:Nadaraya-Watson 核回归
注意力机制并不是先有深度学习才出现的。其实早在 1964 年,人们就在统计学习里用过一个非常接近注意力思想的方法:Nadaraya-Watson 核回归。
它的思想特别适合入门理解,因为它几乎就是“注意力机制的雏形”。
1. 先看最简单的平均汇聚
如果你想预测一个点的值,最偷懒的方法就是平均周围样本。
比如周围 5 个点分别是:2、4、6、8、10。
直接平均就是 6。
这就是平均汇聚:不管远近,不管相似不相似,大家权重都一样。
这个方法简单,但太粗糙了。因为离你更近、更像你的样本,往往更值得参考。
2. 非参数注意力汇聚:相似的多看,不相似的少看
Nadaraya-Watson 的核心做法是:
- 先看当前输入和每个样本有多像。
- 越像的样本,权重越大。
- 最后把所有样本的值加权平均。
这就像你在问同学题目答案时:
- 跟你学得差不多的人,建议更可信。
- 完全不在一个水平的人,参考价值就低一些。
所以它不是简单平均,而是根据相似度分配权重。
3. 一个最小示例
下面是一个非常小的示意代码,帮助你理解“权重越大,越像被重点关注”。
import torch
def attention_pooling(query, keys, values):
# query: 当前想预测的位置(一个标量或向量)
# keys: 所有候选样本的位置(与 query 同类型的张量)
# values: 候选样本对应的真实值(将被加权汇总)
# 下面计算每个 key 与 query 的相似性得分:这里用负平方距离,距离越小得分越大
scores = -((keys - query) ** 2)
# 把得分转换为概率权重,softmax 会把所有分数规范化为正数并使其和为 1
weights = torch.softmax(scores, dim=0)
# 用权重对 values 做加权求和,得到注意力汇聚结果;同时返回权重便于可视化或调试
return (weights * values).sum(), weights
# --- 示范用法 ---
# 构造一个示例查询(query)和值/键(keys, values)
query = torch.tensor(2.5) # 我们想预测的位置是 2.5
keys = torch.tensor([1.0, 2.0, 3.0, 4.0]) # 四个候选位置
values = torch.tensor([1.0, 2.0, 3.0, 4.0]) # 对应的真实值(这里简单用位置本身作为示例)
# 调用注意力汇聚函数得到输出和每个候选的权重
output, weights = attention_pooling(query, keys, values)
# 打印结果:output 是汇聚后的预测值,weights 是每个候选被关注的程度
print('output =', output)
print('weights =', weights)
这里的重点不是代码多复杂,而是思路:
- 先算 query 和每个 key 的相关性。
- 再把相关性变成权重。
- 最后对 values 做加权求和。
这和现代注意力机制的主干完全一致。
4. 带参数的注意力汇聚
前面的方法里,相关性是“手工定义”的,比如距离越近权重越大。
但真实问题里,我们希望模型自己学会什么叫“相似”。于是就会引入参数,让模型自动学习权重怎么分配。
这样一来,注意力不再是固定公式,而是一个可以训练的模块。
这就是从“统计里的核回归”走向“深度学习里的注意力层”的关键一步。
5. 小结
Nadaraya-Watson 核回归告诉我们一件很重要的事:
模型不一定要平均看待所有输入,它可以根据相关程度,给不同输入不同权重。
这就是注意力思想的原点。
四、注意力评分函数:先打分,再汇总
注意力机制的核心流程可以拆成三步:
- 计算 Query 和 Key 的相关性。
- 把相关性变成权重。
- 对 Value 做加权求和。
第一步最关键,因为“怎么打分”,会直接影响注意力怎么分配。
1. 掩蔽 softmax:有些位置不能看
在很多任务里,并不是所有位置都能看。
比如:
- 句子补齐后的空白部分,不能被当成真实词语。
- 在生成文本时,模型不能偷看未来词,只能看已经生成的过去词。
这时候就要用 掩蔽 softmax。
它的作用很简单:
- 不允许看的位置,分数直接设成一个极小值。
- 这样经过 softmax 后,这些位置的权重就接近 0。
可以把它理解成“黑名单机制”。
2. 加性注意力:用一个小网络来打分
加性注意力的思路是:
- 把 Query 和 Key 拼在一起。
- 送进一个小神经网络。
- 输出一个相关性分数。
通俗理解:
不只是看“像不像”,还让模型自己学会“怎么判断像不像”。
这类方法通常比较灵活,适合处理 Query 和 Key 维度不同的情况。
3. 缩放点积注意力:更直接、更高效
另一种非常常见的方法是 缩放点积注意力。
它的打分方式很直接:
score=(q⋅k)/sqrt(d)score = (q \cdot k) / sqrt(d)score=(q⋅k)/sqrt(d)
其中:
- qqq 是 Query
- kkk 是 Key
- ddd 是向量维度
为什么要除以 sqrt(d)sqrt(d)sqrt(d)?
因为维度越大,点积数值越容易变得很大,softmax 就会变得特别“偏激”,容易只盯住某一个位置。缩放以后,训练会更稳定。
4. 什么时候用哪种?
你可以这样记:
- 加性注意力:更灵活,适合不一样的 Query 和 Key。
- 缩放点积注意力:更简单、更快,Transformer 里最常见。
5. 小结
注意力评分函数就像“打分器”。
先给每个候选信息打分,再把分数变成权重,最后汇总值。
这件事看起来普通,但它是所有注意力模型的共同骨架。
五、Bahdanau 注意力:让翻译模型学会“回头看”
如果说前面的内容是在讲“注意力是什么”,那 Bahdanau 注意力就是注意力机制真正进入深度学习主流的代表作之一。
它最早在机器翻译任务里大放异彩。
1. 传统 Seq2Seq 的问题
早期的编码器-解码器模型,通常会把整句输入压缩成一个固定长度的向量,再交给解码器生成翻译。
这个做法的问题很明显:
- 短句还行。
- 长句容易丢信息。
就像你把一整本书压缩成一张便签,再指望它帮你复述全文,难度太大了。
2. Bahdanau 注意力的思路
Bahdanau 注意力的关键改进是:
解码器在生成每一个词时,都可以回头看编码器输出的全部位置,并决定当前最该关注哪里。
也就是说,翻译不是一次性看完整句就结束,而是每一步都重新分配注意力。
3. 直白理解:翻译时边写边查
你可以把它想成写作文时查资料:
- 写到这一句时,你会回头翻前面的笔记。
- 下一句又可能参考另外一页。
- 每一步关注的位置都可能不一样。
Bahdanau 注意力做的,就是这种“边生成,边回看”的事情。
4. 结构上发生了什么?
最简单地说:
- 编码器把输入句子变成一串隐藏状态。
- 解码器当前的状态作为 Query。
- 编码器每个时间步的隐藏状态作为 Key 和 Value。
- 计算注意力权重后,得到一个上下文向量。
- 解码器用这个上下文向量去生成下一个词。
5. 一个小示意图
这个循环会一直重复,直到整句翻译完成。
6. 训练时要注意什么?
Bahdanau 注意力本身是可微分的,所以可以直接用反向传播训练。
训练时通常会配合 teacher forcing,也就是:
- 训练阶段,把上一时刻的真实词语喂给解码器。
- 推理阶段,模型自己根据上一步的预测继续往下生成。
7. 小结
Bahdanau 注意力最重要的价值,不只是“翻译效果变好了”,而是它证明了:
模型可以在生成过程中动态对齐输入和输出。
这一步非常关键,因为它让注意力真正成为了序列建模的核心工具。
六、多头注意力:一个人看不够,就让很多人一起看
1. 为什么要多头?
单个注意力头,通常只能从一个角度看问题。
但真实语言往往很复杂:
- 有的词关系是主谓关系。
- 有的词关系是修饰关系。
- 有的词关系隔得很远。
如果只用一种关注方式,模型很可能看漏。
所以就有了 多头注意力。
2. 多头注意力的直观理解
你可以把它想成开会:
- 一个同学负责看主语和谓语。
- 一个同学负责看修饰词。
- 一个同学负责看长距离依赖。
- 最后大家把结论汇总起来。
这比一个人从头看到尾更稳。
3. 多头到底做了什么?
多头注意力通常包含以下步骤:
- 把输入投影到多个子空间。
- 每个子空间单独做一次注意力。
- 把多个头的结果拼接起来。
- 再做一次线性变换。
这样模型就能同时从多个角度“读同一句话”。
4. 一个简单的比喻
如果单头注意力像一副望远镜,那么多头注意力就像:
- 一组不同焦距的望远镜
- 一起看同一个目标
有的看细节,有的看全局。
5. 多头的好处
- 表达能力更强。
- 能学到不同类型的关系。
- 更适合复杂序列任务。
6. 小结
多头注意力的核心思想其实很朴素:
让多个注意力模块并行工作,每个模块负责看不同的“重点”。
这也是 Transformer 之所以强大的关键组件之一。
七、自注意力和位置编码:同一句话里,词语彼此交流
1. 什么是自注意力?
前面我们讲的注意力,常常是“一个输入去关注另一个输入”。
而 自注意力 更直接:
同一个序列里的每个位置,都会去关注同一序列里的其他位置。
也就是说,句子里的每个词,都可以“和自己队伍里的其他词开会”。
比如在句子“我把书放在桌子上”里:
- “放”可能会关注“书”和“桌子”。
- “桌子”可能会关注“上”。
- “书”可能会关注“放”。
于是,每个词都能拿到一个融合了上下文的新表示。
2. 自注意力为什么强?
它有三个很明显的优点:
- 可以并行计算,不像 RNN 那样必须一步一步读。
- 能直接建立远距离依赖,不需要层层传递。
- 表达灵活,能根据任务动态决定关注谁。
3. 和 CNN、RNN 比一比
| 模型 | 主要特点 | 优点 | 局限 |
|---|---|---|---|
| CNN | 局部感受野,擅长看邻近区域 | 计算高效,局部特征强 | 远距离依赖需要多层堆叠 |
| RNN | 按顺序读取序列 | 自然适合时间序列 | 难并行,长距离信息容易衰减 |
| 自注意力 | 序列内部任意位置都能互相看 | 全局建模强,并行能力好 | 没有位置编码时,不知道顺序 |
如果只看这一张表,你就已经抓住了自注意力的主线:
它强在“全局看、并行算、动态选重点”。
4. 但它也有一个天然问题:不知道顺序
注意力机制本身只会算“谁和谁相关”,但它并不知道谁在前、谁在后。
如果你把“我爱你”和“你爱我”的词都打乱,单靠注意力很难判断顺序差异。
所以我们还需要 位置编码。
5. 位置编码:给每个位置发一个“座位号”
位置编码就是给序列里的每个位置加上一个表示“我在第几个”的信息。
这样模型就不只知道“有哪些词”,还知道“这些词的先后顺序”。
经典的正弦位置编码可以理解成:
- 低维度变化快,容易区分相邻位置。
- 高维度变化慢,帮助模型感知更远的距离。
6. 一个简化版代码示意
import torch
def positional_encoding(num_positions, num_hiddens):
# 初始化一个全零张量,形状为 (1, 序列长度, 隐藏维度)
X = torch.zeros((1, num_positions, num_hiddens))
# positions 是每个位置的索引,形状为 (num_positions, 1)
positions = torch.arange(0, num_positions).reshape(-1, 1)
# div_term 用于控制不同维度正弦/余弦函数的频率
# 这里按照论文做法,在偶数和奇数维上使用不同的频率比例
div_term = torch.exp(
torch.arange(0, num_hiddens, 2) * (-torch.log(torch.tensor(10000.0)) / num_hiddens)
)
# 偶数维用 sin,步长为 2(0::2),把 positions 与频率相乘后取 sin
X[0, :, 0::2] = torch.sin(positions * div_term)
# 奇数维用 cos,步长为 2(1::2)
X[0, :, 1::2] = torch.cos(positions * div_term)
# 返回位置编码张量,通常会加到输入的词嵌入上
return X
# --- 示范用法 ---
pe = positional_encoding(6, 8) # 生成长度为 6、隐藏维度为 8 的位置编码
print('positional encoding shape =', pe.shape) # 打印形状以验证输出:应为 (1, 6, 8)
你不用死记这段代码,只要记住一句话:
位置编码是在告诉模型:这些词虽然同时出现,但它们的顺序不能乱。
7. 小结
自注意力负责“看内容关系”,位置编码负责“看顺序信息”。
两者合在一起,模型才真正知道一句话是什么、先后顺序又是什么。
八、Transformer:只靠注意力,也能搭起强大系统
1. Transformer 为什么重要?
2017 年,Transformer 一出现,就把很多序列任务的规则改写了。
它最大的特点是:
不靠循环,主要靠注意力。
这意味着它既能并行处理,又能建模长距离关系,还特别适合大规模训练。
2. Transformer 的整体结构
最经典的 Transformer 可以分成两部分:
- 编码器(Encoder):把输入句子编码成更有信息的表示。
- 解码器(Decoder):根据编码结果逐步生成输出。
你可以把它想成“读题 + 写答案”:
- 编码器负责读懂题目。
- 解码器负责一步步写答案。
3. 一个 Transformer 块里有什么?
每个基本模块通常包括:
- 多头注意力
- 基于位置的前馈网络
- 残差连接
- 层规范化
这几样东西组合起来,让模型既能学关系,又能保持训练稳定。
4. 基于位置的前馈网络:给每个位置再加工一次
注意力负责“把信息聚合起来”,前馈网络负责“把每个位置再加工一遍”。
你可以把它理解成:
- 注意力像开会,把有用信息汇总出来。
- 前馈网络像个人复盘,把自己的理解再提炼一次。
5. 残差连接和层规范化:训练稳定的关键
深层网络最怕什么?
怕信号传不过去,怕训练不稳定。
所以 Transformer 里要加:
- 残差连接:让信息可以“绕一下路”直接传过去。
- 层规范化:让每层输出别太极端,训练更稳。
它们就像给模型装上减震器和旁路通道。
6. 解码器为什么要“看未来”被禁止?
在生成任务里,模型不能提前偷看后面的词。
比如你在写句子时,不能先看到答案再写过程。为了保证训练和生成逻辑一致,解码器里的自注意力会用掩蔽机制屏蔽未来位置。
这就是为什么 Transformer 解码器里有 masked self-attention。
7. 训练时发生什么?
训练 Transformer 时,通常还是用交叉熵损失。
步骤可以非常直白地理解成:
- 编码器读入源句子。
- 解码器根据已知前缀预测下一个词。
- 和真实答案比对,算损失。
- 反向传播更新参数。
8. Transformer 的优势和代价
优势:
- 并行能力强。
- 长距离建模好。
- 结构统一,容易扩展。
代价:
- 自注意力计算量随序列长度增长很快。
- 序列很长时,显存和算力压力会变大。
也就是说,它很强,但并不是“没有代价的万能钥匙”。
9. 一张总览图
如果你把这个图看懂了,Transformer 的主线就基本抓住了。
10. 小结
Transformer 的真正厉害之处,不是某一个小技巧,而是它把以下几件事整合到了一起:
- 注意力
- 多头并行
- 位置编码
- 残差连接
- 层规范化
最后形成了一个既能表达复杂关系、又能高效训练的通用架构。
九、把整章串起来:注意力机制到底在解决什么?
如果用一句话总结整章,那就是:
注意力机制让模型学会根据当前任务,动态决定“看哪里、看多少、怎么看”。
它不是替代所有模型,而是给模型增加了一种非常重要的能力:有选择地处理信息。
你可以把整章内容串成一条线:
- 注意力提示告诉我们,模型可以像人一样先抓重点。
- Nadaraya-Watson 说明了“按相关性加权”的原始思想。
- 评分函数把“相关性”变成可计算、可训练的权重。
- Bahdanau 注意力让序列模型能边生成边回看。
- 多头注意力让模型从多个角度同时看问题。
- 自注意力让序列内部的信息彼此交流。
- 位置编码补上顺序信息。
- Transformer 把这些部分组合成一个强大的通用架构。
这就是从“会关注”到“善于关注”的完整演化。
十、给新手的记忆法
如果你刚学这一章,不用一口气背完所有公式。先记住下面这几句就够了:
- Query 是我现在想找什么。
- Key 是每条信息的标签。
- Value 是真正要取走的内容。
- 注意力就是先打分,再加权平均。
- 多头注意力就是让多个“关注视角”一起工作。
- 自注意力是序列内部自己和自己交流。
- 位置编码是给模型补上顺序感。
- Transformer 是只靠注意力搭起来的强大架构。
只要这几句话在脑子里站稳了,后面看论文、看代码、看实现都会顺很多。
十一、练习一下
你可以先不写代码,直接尝试回答这几个问题:
- 为什么注意力机制比“平均看所有输入”更聪明?
- Query、Key、Value 三者分别像什么?
- 为什么翻译长句时,Bahdanau 注意力比早期 Seq2Seq 更有优势?
- 多头注意力为什么不是“重复计算”,而是“多角度建模”?
- 为什么自注意力需要位置编码?
- Transformer 里的残差连接和层规范化,分别在帮什么忙?
如果你能用自己的话把这些问题讲出来,说明你已经真正入门了。
写在最后
注意力机制最迷人的地方,不是公式有多漂亮,而是它的思路非常符合人类直觉:世界很大,不可能处处用力,真正聪明的做法,是把资源花在最相关的地方。
从查询、键和值,到 Bahdanau 注意力,再到多头注意力和 Transformer,你会发现整条技术路线其实是在做同一件事:
让模型学会像人一样,先看重点,再做判断。
这也是现代深度学习里最重要的能力之一。
如果你接下来想继续学,可以把这篇当成“注意力机制总览图”。先抓主线,再回头看细节,学习效率会高很多。
注:本文参考《动手学深度学习》注意力机制:https://zh.d2l.ai/chapter_attention-mechanisms/index.html
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)