简化自注意力
用无参数版本建立加权求和直觉,理解为何 Q=K=V 共用一个向量会限制表达力
核心要点:
- Attention 起源是解决 RNN encoder-decoder 的信息瓶颈
- 最简版:输入向量自己做 query / key / value,无任何可学习参数
- 每个输出 = 用 softmax 权重加权求和所有 token (含自己)
- 任意两 token 距离 = 1 跳,序列维全并行
- 缺陷:表达力受限,通向下一步的 Q/K/V 投影
名词定义
本篇共享名词在 4.1 总览 已定义 (Attention score / Attention weight / 简化自注意力)。本篇仅一个新引入语境:
| 名词 | 定义 |
|---|---|
| Context vector (上下文向量) | 简化自注意力中每个位置的输出 $\mathbf{z}_i$,由序列里所有 token 的 value 加权求和而成;"上下文" 这个名字来自 Bahdanau 2015 的 encoder-decoder attention |
@tbl-simple-attn-glossary 本篇引入名词
Self-attention 想解什么 RNN 解不了?
核心问题:Attention 不是 2017 年突然蹦出来的。它 2015 年起源时解决的是什么具体痛点?为什么这个痛点导致后来 Transformer 把 attention 推到中心?
RNN 在 encoder-decoder 翻译任务里把整个源句子压成一个固定向量再解码,长句子信息损失严重——attention 让 decoder 每步重新看全句,直接取信息。
Bahdanau 2015: attention 的起源
2014 年的 seq2seq 模型 (Sutskever 2014) 用 RNN encoder 把源句压成最后一个 hidden state $h_T$,再用 RNN decoder 从 $h_T$ 解码。问题:整句信息全压在固定维向量里,长句子翻不好。
Bahdanau et al. 2015[1] 提出:decoder 第 $t$ 步不只看 $h_T$,而是对 encoder 所有时刻的 hidden state $h_1, \ldots, h_T$ 做加权求和,权重根据 decoder 当前状态动态算。这就是 attention 的起源——它最早是一个"动态看回去" 的机制,解决信息瓶颈。
Transformer 把 attention 从配菜变主菜
Vaswani 2017 的 transformative 创意是:既然 attention 比 RNN 递推更直接,干脆完全用 attention 代替 RNN。Encoder 内部 token 之间互相 attend (self-attention), decoder 内部也是,encoder-decoder 之间还是 attention (cross-attention)。论文标题 "Attention Is All You Need" 直白说明:attention 不只是 RNN 的补丁,它自己就是骨架。
Self-attention 相对 RNN 的两个根本变化 (见 02-大模型是什么 已铺垫):
- 任意两 token 距离 1 跳:不依赖中间,无梯度沿时间衰减
- 序列维全并行:不存在 $h_t$ 必须等 $h_{t-1}$ 的数据依赖,训练吞吐随算力线性扩展
下面拆开看 self-attention 最朴素的形式怎么做到这两点。
最简化的 self-attention 长什么样?
核心问题:课程图书里 "$\mathrm{softmax}(QK^\top / \sqrt{d})V$" 一上来就有 4 个符号 (Q/K/V/√d),看不清每个的作用。先去掉所有可学参数,用最朴素形式建立直觉。
最简化版本:直接用输入向量 $\mathbf{x}_i$ 自己当作 query, key, value,不引入任何可学习投影。这是 Raschka 2024 Ch.3.3 的教学起点[2]。
三步走的算法
给定输入序列 $\mathbf{x}_1, \mathbf{x}_2, \ldots, \mathbf{x}_s$ (每个是 $h$ 维向量,来自 03-token-embedding + 位置编码),简化自注意力对第 $i$ 个位置算它的输出 $\mathbf{z}_i$:
Step 1:算 attention score (内积相似度),第 $i$ 个 token 对第 $j$ 个 token 的关注度:
$$\begin{equation} \omega_{ij} = \mathbf{x}_i \cdot \mathbf{x}_j \label{eq:simple-attn-score} \end{equation}$$Step 2:沿 $j$ 维做 softmax 归一化,让所有 $j$ 的权重和为 1:
$$\begin{equation} \alpha_{ij} = \mathrm{softmax}_j(\omega_{ij}) = \frac{\exp(\omega_{ij})}{\sum_{k=1}^{s} \exp(\omega_{ik})} \label{eq:simple-attn-weight} \end{equation}$$Step 3:用 $\alpha_{ij}$ 作为权重对所有 token 的 $\mathbf{x}_j$ 加权求和:
$$\begin{equation} \mathbf{z}_i = \sum_{j=1}^{s} \alpha_{ij} \mathbf{x}_j \label{eq:simple-attn-output} \end{equation}$$$\mathbf{z}_i$ 就是位置 $i$ 的新表示:它是"自己 ($j = i$) 加上其他位置 ($j \neq i$) 的加权融合",权重由内积相似度决定——内积越大的位置 (语义/向量上越相似) 权重越大。
矩阵形式:一次算完整个序列
按位置逐个算 $\mathbf{z}_i$ 没意思,工程上一次性算完整个序列。把所有 $\mathbf{x}_i$ 按行堆成矩阵 $\mathbf{X} \in \mathbb{R}^{s \times h}$:
$$\begin{equation} \mathbf{Z} = \mathrm{softmax}\left(\mathbf{X} \mathbf{X}^\top\right) \mathbf{X} \label{eq:simple-attn-matrix} \end{equation}$$- $\mathbf{X} \mathbf{X}^\top \in \mathbb{R}^{s \times s}$:所有 token 两两内积矩阵,第 $(i, j)$ 项就是 $\omega_{ij}$
- $\mathrm{softmax}(\cdot)$ 沿最后一维 ($j$) 归一化,得到 attention 权重矩阵
- 乘 $\mathbf{X}$ 得到所有 $\mathbf{z}_i$ 堆成的输出矩阵 $\mathbf{Z} \in \mathbb{R}^{s \times h}$
这就是 "self-attention 是序列维全并行" 的具体落地:所有位置的输出在一次矩阵乘里同时算完,没有 RNN 那种"必须等前一步" 的数据依赖。
直觉:Q = K = V = 自己
最简化版本里 query / key / value 三个角色全是输入向量 $\mathbf{x}_i$ 自己:
- 作为 query:位置 $i$ 在"找信息" 时用 $\mathbf{x}_i$
- 作为 key:位置 $j$ 提供"被查的索引" 也是 $\mathbf{x}_j$
- 作为 value:位置 $j$ 提供"实际内容" 还是 $\mathbf{x}_j$
全部混在一个向量里 —— 这是简化版的核心简化,也是它的根本限制 (后文展开)。
一个 3-token 例子从头到尾
核心问题:公式看一遍还不够,用具体数字走一遍最容易抓直觉。
用 Raschka Ch.3 的经典示例 "Your journey starts" 走完整三步。
输入
3 个 token,每个 token 用 4 维向量 (实际 $h$ 是几千,这里简化):
| Token | $\mathbf{x}$ 向量 |
|---|---|
| Your | $(0.43, 0.15, 0.89, 0.55)$ |
| journey | $(0.55, 0.87, 0.66, 0.58)$ |
| starts | $(0.57, 0.85, 0.64, 0.80)$ |
@tbl-simple-attn-example 简化版示例的 3 个输入向量
计算 "journey" (位置 2) 的输出
Step 1:算 attention score — "journey" 与所有 token 的内积:
| 对 | 内积 $\omega_{2j}$ |
|---|---|
| journey · Your | $0.55 \cdot 0.43 + 0.87 \cdot 0.15 + 0.66 \cdot 0.89 + 0.58 \cdot 0.55 \approx 1.27$ |
| journey · journey | $0.55^2 + 0.87^2 + 0.66^2 + 0.58^2 \approx 1.83$ |
| journey · starts | $0.55 \cdot 0.57 + 0.87 \cdot 0.85 + 0.66 \cdot 0.64 + 0.58 \cdot 0.80 \approx 1.94$ |
Step 2: softmax 归一化:
$$\begin{align*} \alpha_{2, \text{Your}} &= \exp(1.27) / Z \approx 0.21 \\ \alpha_{2, \text{journey}} &= \exp(1.83) / Z \approx 0.36 \\ \alpha_{2, \text{starts}} &= \exp(1.94) / Z \approx 0.43 \\ \text{(其中 } Z &= \exp(1.27) + \exp(1.83) + \exp(1.94) \approx 17.0) \end{align*}$$权重和 = 1,给 "starts" 最多注意,其次是 "journey" 自己。
Step 3:加权求和:
$$\begin{equation*} \mathbf{z}_{\text{journey}} = 0.21 \cdot \mathbf{x}_{\text{Your}} + 0.36 \cdot \mathbf{x}_{\text{journey}} + 0.43 \cdot \mathbf{x}_{\text{starts}} \end{equation*}$$$\mathbf{z}_{\text{journey}}$ 是一个新向量,主要由 "starts" 和 "journey" 自己贡献——直觉上 attention 完成了"用上下文重新表示自己" 这件事。
同时算所有位置
用矩阵形式公式 $\eqref{eq:simple-attn-matrix}$,一次性算完 $\mathbf{Z}$ 三行 (对应 3 个位置)。代码视角:
import torch
X = torch.tensor([[0.43, 0.15, 0.89, 0.55],
[0.55, 0.87, 0.66, 0.58],
[0.57, 0.85, 0.64, 0.80]])
omega = X @ X.T # [3, 3] attention score 矩阵
alpha = omega.softmax(dim=-1) # [3, 3] attention weight 矩阵
Z = alpha @ X # [3, 4] 输出
这 4 行代码就是简化自注意力的全部。Karpathy 教学视频里也用这个量级的代码量介绍 attention[3]。
这个版本的局限在哪?
核心问题:简化自注意力跑得通,但训练不出来强的语言模型。具体卡在哪?怎么自然过渡到 Q/K/V 投影?
最简化版的根本限制是无任何可学习参数,attention 权重完全由输入向量的内积决定,模型没有调节"看什么 / 怎么看" 的自由。
三个表达力受限的具体表现
- 相似 = 关注:$\omega_{ij}$ 就是 $\mathbf{x}_i$ 和 $\mathbf{x}_j$ 的余弦相似度方向 (没缩放的内积),语义相似的 token 必然互相关注。但语言里很多时候反义词需要互相关注 ("up" 要看 "down"),或者句法相关但语义不同 ("the" 要看后面的名词)——简化版只能学相似,学不出这些
- 无方向性:$\omega_{ij} = \omega_{ji}$, "我看你" 和"你看我" 权重完全对称。但语言里"主语看动词" 和 "动词看主语" 是不对称关系
- 内容与查询混淆:$\mathbf{x}_i$ 同时是 query (我在找什么)、key (我可以被怎么查到)、value (我提供什么内容)。理论上这三件事应该有独立的表示,简化版强制三者绑定,让模型不能分别学习"找什么" 和 "提供什么"
为什么必须引入 Q/K/V 投影
下一步 (03-自注意力 Q/K/V) 引入三个可学习投影矩阵 $W_Q, W_K, W_V$,把 $\mathbf{x}$ 投影到三个不同空间:
$$\begin{align*} \mathbf{q}_i &= \mathbf{x}_i W_Q \\ \mathbf{k}_i &= \mathbf{x}_i W_K \\ \mathbf{v}_i &= \mathbf{x}_i W_V \end{align*}$$这一步同时解掉简化版的三个限制:投影是可学习的,所以模型能学习"哪些维度用于查询 / 哪些用于被查 / 哪些是实际内容"; query 空间和 key 空间不同,让 $\omega_{ij} \neq \omega_{ji}$ (因为现在是 $\mathbf{q}_i \cdot \mathbf{k}_j$ 而不是 $\mathbf{q}_j \cdot \mathbf{k}_i$);内容 / 查询 / 索引解耦,让模型有了独立表达每个角色的自由度。
下一篇详细展开为什么三个角色必须分开,以及 $\sqrt{d}$ 缩放在 softmax 上起什么作用。
Takeaway
| 知识点 | 核心结论 |
|---|---|
| Attention 起源 | Bahdanau 2015 在 encoder-decoder 翻译里解决信息瓶颈 |
| Transformer 创意 | "Attention Is All You Need": attention 不只是补丁,自己就是骨架 |
| 简化版三步 | 内积得 score → softmax 得 weight → 加权求和得 output |
| 矩阵形式 | $\mathbf{Z} = \mathrm{softmax}(\mathbf{X} \mathbf{X}^\top) \mathbf{X}$,序列维一次算完 |
| 直觉 | Q = K = V = 输入向量自己,"用上下文重新表示自己" |
| 与 RNN 的根本差异 | 任意两 token 距离 1 跳 + 序列维全并行 |
| 局限 1 | 相似才关注,无法学反义 / 句法关系 |
| 局限 2 | $\omega_{ij} = \omega_{ji}$,无方向性 |
| 局限 3 | 内容 / 查询 / 索引混在一个向量,无法分别表达 |
| 通向下一步 | 引入 $W_Q, W_K, W_V$ 三个投影矩阵,同时解掉三个限制 |
开放问题
- 简化自注意力是否在某些任务上有用:无参版本计算便宜,在某些低算力场景 (移动端 / 边缘) 是否仍有价值?业界基本没探索过,因为表达力差距太大。
- "相似才关注" 的限制是否被高估:训练后的 Q/K/V 投影是否真的让 attention 学反义关系,还是仍以相似为主?mechanistic interpretability 工作有部分研究但未定论。
延伸阅读
- 下一步:Q/K/V 投影 → 4.3 自注意力 Q/K/V
- 位置编码 (上一步:token 进 attention 前怎么注入位置) → 03-文本如何变成数字/04-位置编码
- 完整数据流全景 → 02-大模型是什么
- Karpathy 完整 GPT 实现视频 → https://www.youtube.com/watch?v=kCc8FmEb1nY
参考资料
- Bahdanau, Cho, Bengio. Neural Machine Translation by Jointly Learning to Align and Translate. ICLR 2015. https://arxiv.org/abs/1409.0473
- Sebastian Raschka. Build a Large Language Model (From Scratch). Manning, 2024. Chapter 3.3 "Attending to different parts of the input with self-attention".
- Andrej Karpathy. Let's build GPT: from scratch, in code, spelled out. https://www.youtube.com/watch?v=kCc8FmEb1nY