🐸 青蛙编程网
首页 / AI / Transformer 原理 / 3. 注意力机制

3. 注意力机制:Q、K、V 与缩放点积

这是什么:注意力(Attention) = 给句子里每个词装一个"聚光灯",用三个矩阵把词向量改造成 Q(查询)、K(键)、V(值) 三种角色,再互相打分、按分取值。

为什么用:第 2 章把话变成了向量,但这些向量各管各的,"它"不知道自己指的是"猫"还是"鱼"。注意力让每个词吸收整个句子的语境,这是 Transformer 的灵魂。

如何用:本章用 NumPy 手搓一遍"打分 → softmax → 加权求和"三步曲,亲眼看到每个词是如何分配关注度的。

三种角色:查询、键、值

一个记忆检索的类比最直观:

  • Q(Query,查询):我这个词想找什么?——"它"想找"单数名词"
  • K(Key,键):我这个词贴的什么标签?——"猫"贴着"单数名词"的标签
  • V(Value,值):对上号之后,我实际能提供的内容

三者都由词向量乘各自权重矩阵变出来。真实模型里 W_Q / W_K / W_V 是训练出来的,我们这里拿随机的顶替,重点看数据怎么流:

第一步:打分——缩放点积

让每个词的 Q 和所有词的 K 各算一次点积,就得到一张"谁该看谁几分"的分数表。分数除以 √d(d 是向量维度)再进 softmax,这一步叫缩放点积注意力(Scaled Dot-Product Attention):

import numpy as np

rng = np.random.default_rng(42)       # 固定随机种子:保证你跑出的数字和我一样
T, d = 3, 4                           # 句子 3 个词,每个词的向量 4 维
X = rng.normal(size=(T, d))           # 3×4 词向量矩阵(随机模拟)

W_Q = rng.normal(size=(d, d))         # 示意权重:真实模型是训练出来的
W_K = rng.normal(size=(d, d))
W_V = rng.normal(size=(d, d))
Q, K, V = X @ W_Q, X @ W_K, X @ W_V

scores = Q @ K.T / np.sqrt(d)         # Q 逐个与所有 K 点积打分,再除 √d 缩放
weights = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True)   # softmax 变百分比

print(np.round(weights, 3))           # 每行是词对句中每个词的关注度,长相加为 1
print(weights.sum(axis=1))

你会看到:

[[0.419 0.217 0.365]
 [0.01  0.955 0.034]
 [0.396 0.21  0.394]]
[1. 1. 1.]
代码 作用
rng = np.random.default_rng(42) 固定种子:换一个数字,整张关注度表就换一种长相
X @ W_Q 矩阵乘法:把"词向量"改造成"查询向量",@ 是 NumPy 的矩阵乘法
Q @ K.T / np.sqrt(d) 3×3 分数表;除 √d 防止大维度时点积偏大、softmax "偏科"到个别词上
np.exp(scores) / ...sum(axis=1, keepdims=True) softmax:把一行分数变成加起来等于 1 的百分比
np.round(weights, 3) 关注度表:第 2 行 0.955 表示第 2 个词几乎只盯着第 2 个词看

第二步:加权求和——按待遇领内容

拿到关注度表后,每个词的输出 = 关注度 × 各词的 V 做加权平均。对方关注度越高,它的 V 内容就吸进来越多:

out = weights @ V       # 3×3 关注度表 × 3×4 值矩阵 = 每个词吸收语境后的新向量
print(out.shape)

你会看到:

(3, 4)

记忆口诀:Q 生疑问,K 贴标签;点积打分除根号,softmax 摊百分,按分领 V 领内容。

动手改

  1. 把 default_rng(42) 换成其他种子,看关注度表怎么"换性格"
  2. 把 / np.sqrt(d) 删掉再跑,观察 softmax 输出是不是变得极化(更接近非 0 即 1)
  3. 构造一个 Q 全等于 K 的极端例子,验证每个词最关注的正是它自己

下一章:多头注意力与残差,给一层装上完整配件 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。