注意力机制学习笔记:从 QKV 到缩放点积注意力
本文记录我学习《动手学深度学习》第 10 章注意力机制时的理解过程。刚开始看注意力机制时,我觉得 Query、Key、Value、注意力评分函数、softmax 权重这些概念都比较抽象,尤其是教材中用“自主性提示”和“非自主性提示”来引入注意力机制,一开始并不好理解。
经过这一轮学习之后,我对注意力机制的理解逐渐清晰起来。现在我可以先用一句话概括:
注意力机制就是:根据当前的查询 Query,从一组 Key-Value 中找到最相关的信息,并对 Value 进行加权求和。
1. 从人类注意力理解 Query、Key、Value
Section titled “1. 从人类注意力理解 Query、Key、Value”教材首先从人类注意力机制讲起。
人类注意力可以分为两类:
- 非自主性注意力:外界刺激吸引注意力,例如亮光、响声、鲜艳颜色、突然移动的物体等。
- 自主性注意力:人根据自己的目标主动寻找信息,例如在街上主动寻找奶茶店、在文章里寻找某个关键词。
对应到注意力机制中:
| 人类注意力概念 | 注意力机制概念 | 我的理解 |
|---|---|---|
| 自主性提示 | Query | 我现在想找什么 |
| 非自主性提示 | Key | 每个信息有什么特征可以被匹配 |
| 感官输入 | Value | 真正要被取出来的信息 |
所以我可以这样理解:
Query:我想找什么
Key:每条信息的标签或特征
Value:真正的信息内容
例如,在图书馆找一本深度学习相关的书:
Query = 我想找“深度学习”的书
Key = 每本书的标签,比如机器学习、数据库、操作系统
Value = 每本书真正的内容
注意力机制会用 Query 去和每个 Key 做匹配,哪个 Key 更相关,对应的 Value 权重就更大。
2. 注意力机制和全连接层、池化层的区别
Section titled “2. 注意力机制和全连接层、池化层的区别”我一开始的问题是:
全连接层也能综合特征,池化层也能汇聚信息,为什么还要注意力机制?
后来理解到,关键区别在于:
是否有 Query,也就是是否根据当前目标动态选择信息。
平均池化是:
所有输入一视同仁,每个输入权重一样
最大池化是:
谁最大就选谁
全连接层虽然有权重,但是训练完成后,它的权重通常是固定的,并不会根据某个具体 Query 动态变化。
而注意力机制是:
给定不同 Query,会得到不同的注意力权重
因此,同一批输入信息,在不同 Query 下会有不同的关注重点。
这一点是我理解注意力机制最关键的一步:
注意力机制的核心不是简单加权,而是根据 Query 动态决定应该关注哪些信息。
3. 注意力权重的可视化
Section titled “3. 注意力权重的可视化”注意力权重通常可以用热力图显示。
热力图中:
横轴:Keys
纵轴:Queries
颜色深浅:注意力权重大小
如果某个位置颜色越深,说明这个 Query 对这个 Key 的关注程度越高。
教材中使用了单位矩阵作为例子:
attention_weights = np.eye(10).reshape((1, 1, 10, 10))单位矩阵的特点是对角线为 1,其他位置为 0。
所以热力图中会出现一条对角线,表示:
Query 0 只关注 Key 0
Query 1 只关注 Key 1
Query 2 只关注 Key 2
…
Query 9 只关注 Key 9
这个例子本身不是训练出来的真实注意力,而是为了演示如何可视化注意力权重。
4. Nadaraya-Watson 核回归:用回归问题理解注意力
Section titled “4. Nadaraya-Watson 核回归:用回归问题理解注意力”为了理解注意力汇聚,教材引入了 Nadaraya-Watson 核回归。
这里构造了一个人工数据集:
其中:
是真实函数
是随机噪声
训练数据的构建过程可以理解为:
随机生成 50 个
代入真实函数
再加上随机噪声
得到训练样本 (x_train,y_train)
代码大致如下:
n_train = 50x_train = np.sort(np.random.rand(n_train) * 5)
def f(x): return 2 * np.sin(x) + x**0.8
y_train = f(x_train) + np.random.normal(0.0, 0.5, (n_train,))测试集则是在 [0, 5) 范围内每隔 0.1 取一个点:
x_test = np.arange(0, 5, 0.1)y_truth = f(x_test)这里的对应关系是:
| 注意力机制 | 核回归例子 |
|---|---|
| Query | 当前要预测的测试点 x |
| Key | 训练样本输入 |
| Value | 训练样本输出 |
也就是说:
要预测某个 的输出,就去训练集中找哪些 和它更接近,然后更重视这些 对应的 。
5. 平均汇聚:一个最简单但效果差的基线
Section titled “5. 平均汇聚:一个最简单但效果差的基线”平均汇聚的思想非常简单:
也就是:
不管输入 是多少,都直接预测为所有
y_train的平均值
代码是:
y_hat = y_train.mean().repeat(n_test)这会导致预测曲线是一条水平线。
它的问题是:
完全没有考虑当前要预测的 在哪里
例如:
预测 时,用所有训练样本平均
预测 时,还是用所有训练样本平均
所以它不能反映真实函数随 x 变化的趋势。
这一部分让我意识到:
简单平均并不能体现注意力,因为它没有根据 Query 调整权重。
6. 非参数注意力汇聚:距离越近,权重越大
Section titled “6. 非参数注意力汇聚:距离越近,权重越大”为了改进平均汇聚,Nadaraya-Watson 核回归根据输入位置来分配权重:
其中:
表示 对第 个训练样本的注意力权重
如果使用高斯核,可以得到:
这里的核心逻辑是:
和 越近
越小
越大
softmax 后权重越大
也就是:
距离近 → 得分大 → 权重大
距离远 → 得分小 → 权重小
对应代码:
x_repeat = x_test.repeat(n_train).reshape((-1, n_train))attention_weights = npx.softmax(-(x_repeat - x_train)**2 / 2)y_hat = np.dot(attention_weights, y_train)这时,每个测试点都会根据自己和训练样本的距离,分配不同的注意力权重。
和平均汇聚相比,非参数注意力汇聚的预测曲线已经能够随着真实函数变化而变化。
7. softmax 如何把得分转化为权重
Section titled “7. softmax 如何把得分转化为权重”softmax 的公式是:
它的作用是把一组任意得分转化成一组权重,并满足:
每个权重大于 0
所有权重加起来等于 1
得分越大,权重越大
例如,给定三个得分:
[-0.281, -0.031, -0.281]
先取指数:
e^-0.281 ≈ 0.755
e^-0.031 ≈ 0.969
e^-0.281 ≈ 0.755
再除以总和:
0.755 / 2.479 ≈ 0.305
0.969 / 2.479 ≈ 0.391
0.755 / 2.479 ≈ 0.305
得到权重:
[0.305, 0.391, 0.305]
所以得分最高的第二个样本获得了最大权重。
我的理解是:
softmax 把“匹配得分”变成了“关注比例”。
8. 带参数注意力汇聚:让模型自己学习关注范围
Section titled “8. 带参数注意力汇聚:让模型自己学习关注范围”非参数注意力汇聚中的规则是固定的:
距离越近,权重越大
但是这个“近”到底应该多近,是人为设定的。
带参数注意力汇聚在公式中加入了一个可学习参数 :
这里的 控制模型对距离的敏感程度。
如果 较大:
距离差异被放大
模型更关注离当前 很近的样本
预测曲线更灵活
如果 较小:
距离差异被缩小
模型参考更大范围的样本
预测曲线更平滑
所以 可以理解为:
注意力范围的调节参数
9. w 是怎么学习的
Section titled “9. w 是怎么学习的”我一开始的问题是:
这个 w 到底是怎么学出来的?
后来理解到,训练过程就是:
- 随机初始化
- 用当前 计算注意力权重
- 用注意力权重对
y_train加权求和,得到预测值y_hat- 计算预测值和真实值之间的 loss
- 反向传播计算
- 用梯度下降更新
更新公式为:
其中 是学习率。
假设某次计算得到:
学习率
则更新后:
因为梯度是负数,所以 会变大。
这说明在当前情况下:
增大 可以让 loss 下降
因此模型会自动把 往增大的方向调整。
这让我对“学习”有了更直观的认识:
参数学习的本质,就是根据 loss 对参数的梯度,判断参数应该往哪个方向调整。
10. 用数据理解 w 的学习过程
Section titled “10. 用数据理解 w 的学习过程”假设现在要预测:
query
真实
训练集中有 3 个样本作为 Key 和 Value:
| 训练样本 | key | value | 距离 |
|---|---|---|---|
| 样本 1 | 1.0 | 0 | 1.5 |
| 样本 2 | 2.0 | 3 | 0.5 |
| 样本 3 | 4.0 | 1 | 1.5 |
很明显, 离 最近,而且它的 ,正好接近真实值。所以模型应该学会:
更关注样本 2
10.1 当 w = 0.5 时
Section titled “10.1 当 w = 0.5 时”注意力打分公式是:
代入 :
| 样本 | 距离 | 打分 | softmax 后权重 |
|---|---|---|---|
| 样本 1 | 1.5 | -0.281 | 0.305 |
| 样本 2 | 0.5 | -0.031 | 0.391 |
| 样本 3 | 1.5 | -0.281 | 0.305 |
预测值为:
= 1.477
真实值是:
损失为:
说明 w=0.5 时,模型还不够关注最近的样本 2。
10.2 当 w = 1.0 时
Section titled “10.2 当 w = 1.0 时”| 样本 | 距离 | 打分 | softmax 后权重 |
|---|---|---|---|
| 样本 1 | 1.5 | -1.125 | 0.212 |
| 样本 2 | 0.5 | -0.125 | 0.576 |
| 样本 3 | 1.5 | -1.125 | 0.212 |
预测值为:
= 1.940
损失为:
损失从 1.159 降到了 0.561,说明把 变大是有帮助的。
10.3 当 w = 2.0 时
Section titled “10.3 当 w = 2.0 时”| 样本 | 距离 | 打分 | softmax 后权重 |
|---|---|---|---|
| 样本 1 | 1.5 | -4.500 | 0.018 |
| 样本 2 | 0.5 | -0.500 | 0.965 |
| 样本 3 | 1.5 | -4.500 | 0.018 |
预测值为:
= 2.912
损失为:
这时预测已经非常接近真实值 3。
因此,这个例子说明:
变大 → 模型更关注近处样本 → 预测更接近真实值
11. ∂loss / ∂w 是怎么计算的
Section titled “11. ∂loss / ∂w 是怎么计算的”上面例子中,当 时,我们可以计算梯度。
损失函数为:
所以:
前面已经算出:
打分函数为:
对 求导:
代入 :
softmax 权重对 的导数为:
计算得到:
预测值为:
所以:
最后:
因此:
这个负号说明:
增大 会让 loss 下降
所以梯度下降会把 往增大的方向更新。
12. 为什么要用梯度调整参数
Section titled “12. 为什么要用梯度调整参数”模型训练的目标是让 loss 变小。
如果把 loss 看成参数 的函数:
那么梯度表示:
当 w 稍微变化时,loss 会怎么变化
如果:
说明 增大时 loss 会增大,所以应该减小 。
如果:
说明 增大时 loss 会减小,所以应该增大 。
因此梯度下降使用负梯度方向更新参数:
我的理解是:
梯度告诉模型“往哪里走 loss 会下降”,所以它可以指导参数更新。
这比暴力尝试每个参数要高效得多。因为神经网络里通常有大量参数,不可能手动枚举所有参数组合。
13. 注意力评分函数
Section titled “13. 注意力评分函数”前面 Nadaraya-Watson 核回归中,注意力权重是根据距离计算的。
而在更一般的注意力机制中,需要一个函数衡量 Query 和 Key 的匹配程度,这个函数叫:
注意力评分函数 attention scoring function
一般写作:
其中:
是 Query
是第 个 Key
是匹配得分
然后使用 softmax 将得分转化为注意力权重:
最后输出为:
整体流程是:
Query + Keys
↓
注意力评分函数
↓
softmax
↓
注意力权重
↓
对 Values 加权求和
↓
输出
14. 掩蔽 softmax:忽略无效位置
Section titled “14. 掩蔽 softmax:忽略无效位置”在 NLP 任务中,一个 batch 里的句子长度可能不同。
例如:
句子 1:我 爱 你
句子 2:我 喜欢 深度 学习
为了组成 batch,需要补齐:
句子 1:我 爱 你
<pad>
句子 2:我 喜欢 深度 学习
但是 <pad> 不是有效信息,不应该被注意力机制关注。
因此需要使用 masked softmax。
它的作用是:
把无效位置的权重变成 0
只在有效位置上做 softmax
实现时通常把无效位置替换成一个非常小的数,例如 -1e6:
X = npx.sequence_mask(X, valid_lens, True, value=-1e6)因为:
所以这些位置经过 softmax 后权重接近 0。
我的理解是:
masked softmax 是为了防止模型把注意力分配给 padding 等无效位置。
15. 加性注意力
Section titled “15. 加性注意力”加性注意力是一种具体的注意力评分函数。
公式为:
它的计算过程是:
Query 经过 映射
Key 经过 映射
两者相加
经过 tanh
再经过 得到一个标量得分
之所以叫“加性注意力”,是因为它通过:
来融合 Query 和 Key。
加性注意力的一个重要优点是:
Query 和 Key 的原始维度可以不同
原因是:
可以把 Query 映射到隐藏维度
可以把 Key 映射到隐藏维度
例如:
原来是 3 维
原来是 5 维
可以设置:
: 3 维 → 8 维
: 5 维 → 8 维
这样:
和 都是 8 维
于是就可以相加。
本质上,这就是全连接层或线性层在做维度变换。
16. W_q 和 W_k 如何实现维度变换
Section titled “16. W_q 和 W_k 如何实现维度变换”假设:
要把 3 维的 q 变成 8 维,可以让它乘一个 的矩阵 :
其中:
每一个输出维度都是原始特征的加权组合:
…
所以维度变换不是凭空增加信息,而是把原始特征重新组合成新的表示。
PyTorch 中可以这样写:
import torchimport torch.nn as nn
W_q = nn.Linear(3, 8)W_k = nn.Linear(5, 8)
q = torch.randn(3)k = torch.randn(5)
h_q = W_q(q)h_k = W_k(k)
print(h_q.shape) # torch.Size([8])print(h_k.shape) # torch.Size([8])这里的 nn.Linear(3, 8) 内部包含:
权重矩阵:8 × 3
偏置向量:8
这些参数会在训练过程中自动学习。
17. 加性注意力的代码理解
Section titled “17. 加性注意力的代码理解”加性注意力的模型可以大致写成:
class AdditiveAttention(nn.Block): def __init__(self, num_hiddens, dropout, **kwargs): super(AdditiveAttention, self).__init__(**kwargs) self.W_k = nn.Dense(num_hiddens, use_bias=False, flatten=False) self.W_q = nn.Dense(num_hiddens, use_bias=False, flatten=False) self.w_v = nn.Dense(1, use_bias=False, flatten=False) self.dropout = nn.Dropout(dropout)
def forward(self, queries, keys, values, valid_lens): queries, keys = self.W_q(queries), self.W_k(keys)
features = np.expand_dims(queries, axis=2) + np.expand_dims(keys, axis=1) features = np.tanh(features)
scores = np.squeeze(self.w_v(features), axis=-1) self.attention_weights = masked_softmax(scores, valid_lens)
return npx.batch_dot(self.dropout(self.attention_weights), values)其中:
:把 Query 映射到隐藏空间
:把 Key 映射到隐藏空间
:把隐藏向量变成注意力得分
假设:
queries: (2, 1, 20)
keys: (2, 10, 2)
values: (2, 10, 4)
经过 和 之后:
queries: (2, 1, 8)
keys: (2, 10, 8)
然后扩展维度:
queries → (2, 1, 1, 8)
keys → (2, 1, 10, 8)
广播相加得到:
features: (2, 1, 10, 8)
含义是:
2 个样本
每个样本 1 个 query
每个 query 和 10 个 key 分别组合
每个组合是 8 维隐藏特征
之后 w_v 会把最后的 8 维隐藏向量压成 1 个分数:
features: (2, 1, 10, 8)
scores: (2, 1, 10)
也就是说:
每个 query 对 10 个 key 都有一个匹配分数
再经过 masked softmax,就得到了注意力权重。
18. 缩放点积注意力
Section titled “18. 缩放点积注意力”缩放点积注意力是 Transformer 中常用的注意力形式。
单个 Query 和 Key 的评分函数是:
其中 是 Query 和 Key 的维度。
点积 表示 Query 和 Key 的相似程度:
越相似,点积越大
越不相似,点积越小
但是如果维度 很大,点积结果可能会变得很大。
如果分数过大,softmax 会变得非常极端,例如几乎把所有权重都分给一个位置,这会影响训练稳定性。
所以要除以 :
控制点积分数的大小
避免 softmax 过于极端
让训练更稳定
多个 Query 和 Key 的矩阵形式为:
流程是:
- 计算每个 Query 和每个 Key 的相似度
- 除以 进行缩放
- softmax 得到注意力权重
- 乘以 ,对 Value 加权求和
代码大致如下:
scores = npx.batch_dot(queries, keys, transpose_b=True) / math.sqrt(d)attention_weights = masked_softmax(scores, valid_lens)output = npx.batch_dot(attention_weights, values)19. 加性注意力和缩放点积注意力的区别
Section titled “19. 加性注意力和缩放点积注意力的区别”| 方法 | 评分函数 | 特点 |
|---|---|---|
| 加性注意力 | Query 和 Key 维度可以不同,计算较灵活 | |
| 缩放点积注意力 | 计算效率高,但 Query 和 Key 维度需要相同 |
我的理解是:
加性注意力更像是用一个小网络学习 Query 和 Key 的匹配方式;
缩放点积注意力更直接,用向量点积衡量相似度。
Transformer 中大量使用缩放点积注意力,是因为它可以高效地进行矩阵并行计算。
20. 我的阶段性理解
Section titled “20. 我的阶段性理解”经过这一部分学习,我对注意力机制的理解从一开始的抽象概念,逐渐变成了一个清晰的计算过程。
最开始我不理解:
什么是自主性提示?
Query、Key、Value 到底是什么?
为什么平均汇聚不算真正的注意力?
softmax 为什么能变成权重?
w 是怎么学习的?
为什么梯度可以调整权重?
加性注意力为什么支持 Query 和 Key 维度不同?
为什么点积注意力要除以 ?
现在我的理解是:
- Query 表示当前目标。
- Key 表示每个信息用于匹配的特征。
- Value 表示真正被汇聚的信息。
- 注意力评分函数计算 Query 和 Key 的匹配程度。
- softmax 将匹配得分转化为权重。
- 注意力输出是 Value 的加权求和。
- masked softmax 用来屏蔽 padding 等无效位置。
- 加性注意力通过线性层统一 Query 和 Key 的维度。
- 缩放点积注意力通过 高效计算相似度,并用 稳定 softmax。
用一句话总结我目前对注意力机制的理解:
注意力机制不是简单地把所有信息平均起来,而是根据当前 Query 动态判断哪些 Key 更重要,并把对应的 Value 按权重汇聚起来。
这也是 Transformer 能够建模序列中不同位置依赖关系的基础。
21. 后续学习计划
Section titled “21. 后续学习计划”这一节主要解决了注意力机制的基本计算过程。后面继续学习时,我准备重点关注以下问题:
- 多头注意力为什么要分多个头?
- 自注意力和普通注意力的区别是什么?
- Transformer 中 Q、K、V 是如何由输入序列生成的?
- 位置编码为什么是必要的?
- Transformer 编码器和解码器分别如何使用注意力机制?
通过这一章的学习,我感觉注意力机制并不是一个单独的公式,而是一种非常通用的信息选择框架。只要问题可以抽象成“根据当前目标,从一组候选信息中选择重要内容”,就可以使用注意力机制。