AI 学习课程 / LESSON 11

第十一节:注意力机制

沿着三个 Token 逐格计算:一个位置如何给其他位置打分、把分数变成权重、按权重汇合信息;再扩展到因果掩码、多头和位置编码。

Q、K、V 手算softmax 热图因果掩码多头形状离线 HTML

01 / 从逐步读入到直接关联

当前位置可以参考哪些位置?

上一节的单向 RNN 依次读 Token,让后面位置通过状态间接获取前文。自注意力让每个位置直接给可见位置打分,再按分数汇合它们的信息。矩阵计算可以同时处理多个位置的相关性,概念上会形成一个 T×T 的位置关系表。

要参考谁

Query 与 Key 匹配,得到相关分数。

各占多少

沿每一行做 softmax,变成非负且和为 1 的权重。

取回什么

用权重对 Value 向量求加权和。

这里讲自注意力:Q、K、V 都由同一序列的向量分别投影得到;三套投影参数可以不同。解码时若要预测下一个词,还需因果掩码阻止看未来。

02 / 先把三种向量分开

Query 发出匹配请求,Key 用来比对,Value 用来汇合

把 Token “小猫 / 喜欢 / 小鱼”放在 3 个位置。下表的二维 Q、K、V 是人为指定的可手算数值,不是词的真实语义向量,也不是训练出的注意力。实际模型一般从输入 X 学三组线性投影。

Q = XW_Q,K = XW_K,V = XW_V Q/K/V 在这个小例子中 shape = (T=3, d=2) score[i,j] = Q[i] · K[j] / √d 第 i 行:谁来查询;第 j 列:被查询的 Key / Value。

点击下面矩阵中的一个格子,便同时选中了某个 Query 行与 Key 列,就能看到两个向量的点积。Key 决定匹配权重;Value 决定最后取回的内容。把 Key 与 Value 混作同一件事,就会看不懂后面的矩阵乘法。

03 / 一行一行看软选择

点积 → 除以 √d → 掩码 → softmax

本例 d=2,所以把点积除以 √2。这个缩放在维度较大时有助于避免点积分数随维度增大而过大,令 softmax 过于尖锐。再按是否启用因果掩码决定哪些列可见。

分数矩阵 S(softmax 之前)
注意力权重 A(按行 softmax)

颜色越深表示权重越大;每一行的权重之和为 1。当前选中的分数来自某行 Query 与某列 Key;权重不是单个分数直接除以行和,而是先指数化,再归一化。

A[i,j] = exp(S[i,j]) / Σₖ exp(S[i,k]) 被掩码的 S[i,j] 设为 −∞,所以 exp(−∞)=0。 A = softmax(QKᵀ / √d + mask),沿 Key 列方向算。

04 / 权重之后才使用 Value

第 i 个位置的输出是 Value 的加权和

选中某个 Query 行,下方会展示该行对三张 Value “取回”了多少。因果模式下未来位置权重为 0,它的 Value 不参与该行输出。

Y[i] = Σⱼ A[i,j] · V[j] 矩阵写法:Y = A @ V A.shape=(T,T),V.shape=(T,d_v),Y.shape=(T,d_v)。

例如因果模式下第 0 行只能看自己,权重 [1,0,0],所以输出就是 V[0]=[1,0]。第 1 行可看位置 0、1,输出是两行 Value 的加权组合。

05 / 不让未来答案进入当前输出

因果掩码是上三角的“不可见”格

行 i 表示当前要产生第 i 个位置的输出;列 j 表示候选输入位置。若 j>i,列 j 在未来,训练下一个词时必须屏蔽。

配套代码还做了两个检验:把第 3 个位置的输入大幅改动,因果模式下位置 0、1、2 的输出完全不变;不加掩码时早期输出会变。反向传播位置 1 的输出时,对未来输入位置 2、3 的梯度恰好为 0。它们说明这段实现不会通过注意力读取未来。

容易混淆的布尔语义:本节手写代码的 block=True 表示“遮住”;PyTorch scaled_dot_product_attention 的布尔 attn_mask=True 表示“允许参与”。因此传给该 API 时需要取反,或直接使用 is_causal=True。不要把两种布尔约定当成相同。官方定义。

06 / 一套注意力不够时

多头:并行使用不同的投影

真实代码用 B=2、T=4、D_model=4、H=2,每头宽度 d_head=2。Q、K、V 各经过一个可学习的线性层,按 head 拆开。每个 head 都有自己的权重表,分别聚合 Value,最后拼接并再做一次输出投影。

head 0 的真实权重
head 1 的真实权重

以上权重来自随机初始化、尚未训练的 CPU 模型,只用于验证形状与因果三角形;不要从色块推断它“理解了”句子。两头的不同权重来自不同投影,不等于把一个权重表复制两份。这个示例有 4 个 4×4 无偏置线性投影,共 64 个参数。

D_model 必须能被 H 整除;d_head = D_model / H Q,K,V: (B,T,4) → (B,H=2,T,d_head=2) scores/weights: (B,H,T,T) = (2,2,4,4) 每头输出: (B,H,T,d_head) → 拼接: (B,T,4) 输出投影后: (B,T,4)

07 / 注意力自己知道顺序吗

给不同位置加入位置信息

如果不加因果掩码,也不加位置向量,普通自注意力只根据内容计算;把输入顺序重排,输出会以同样方式重排,这叫置换等变。它没有固定的“第 0 个位置”标记。给每个位置加不同位置向量后,输入内容与位置结合,重排内容却保持位置槽不变,输出一般会不同。

示例:X_with_position[t] = X[t] + PE[t] 偶数维:sin(t / 10000^(2i/D)) 奇数维:cos(t / 10000^(2i/D)) 本页演示 D=4 的正弦/余弦位置向量。

代码用未加掩码的随机模型核对:无位置向量时,重排前后的最大差异仅是浮点误差;加位置向量后,本次示例差异明显。因果掩码本身也给出“可看到多少前文”的结构线索,因此不能笼统地说“所有不加显式位置编码的因果模型完全没有位置信息”。

08 / 自己写一遍核心计算

本地 CPU PyTorch 练习

本项目只用已安装的 CPU PyTorch。代码手写注意力函数与两头模块,拿官方 API 核对手算例子,检查形状、因果隔离、梯度和位置效果;不需要下载数据或启动服务。

uv run python lessons/lesson-11-attention.py

阅读完整代码 · 查看核对结果 JSON

  1. 手算第 1 行的 Q·K、除以 √2、掩码和 softmax;解释为何未来列权重为 0。
  2. 在页面切换“全可见”,观察第 0 行是否会参考“小鱼”。这时不适合直接用于逐词预测。
  3. 用形状表逐步证明 (B,H,T,d_head) @ (B,H,d_head,T) → (B,H,T,T)。
  4. 改成 D_model=6、H=3,预测每头维度和各张量形状,再运行核对。
  5. 解释布尔 mask 的两种约定,并用“改变未来 Token”测试自己的实现。

09 / 自测

五道题

1. 权重矩阵 A[i,j] 中,i 与 j 分别指什么?

2. 给定 Q、K 的最后一维是 d=2,分数为何除以 √2?

3. 因果掩码下,第 1 行可以读取第 2 行的 Value 吗?

4. B=2、H=2、T=4,权重矩阵形状?

5. PyTorch SDPA 的布尔 attn_mask=True 表示什么?

下一节:Transformer。打开第十二节互动讲义。 把自注意力与残差连接、LayerNorm、前馈网络组合起来,再看自回归生成。