3、自注意力机制入门:从Seq2Seq到Transformer,Query、Key、Value的概念
聊到自注意力机制,很多同学第一反应就是「Q、K、V 这三个东西到底在干嘛?」。说实话,我当年刚接触 Transformer 时,也被这三个字母绕晕过。今天咱们就把它彻底讲明白。
3.1 从 Seq2Seq 到 Transformer:为什么需要自注意力?
先回顾一下老路子。在 Transformer 出现之前,做序列到序列的任务(比如机器翻译),主流方案是 RNN 加注意力。RNN 的问题很明显——它是个串行结构,你输入一个词,它处理一步,再输入下一个词,再处理一步。说白了,就像你读一句话必须一个字一个字地读,不能一眼扫完一整句。
我早期做文本分类项目时,用 LSTM 处理长文本,那叫一个痛苦。句子稍微长一点,比如超过 50 个词,前面的信息基本就「忘」得差不多了。这就是 RNN 的固有问题——长距离依赖搞不定。
后来 Google 在 2017 年提出了 Transformer,核心思想就是:别一步一步来了,咱们一次性把整句话看完,然后让每个词都去「关注」其他所有词。这就是自注意力的精髓。
核心区别一句话总结:
- RNN:串行处理,逐步传递信息
- Transformer:并行处理,全局关注
3.2 Query、Key、Value 到底是什么?
好,现在进入正题。Q、K、V 这三个概念,其实是从信息检索系统里借来的。你想想看,你在百度搜东西时,流程是这样的:
- 你输入一个查询(Query),比如「自注意力机制」
- 搜索引擎拿你的 Query 去和数据库里所有文档的键(Key)做匹配
- 匹配度高的文档,就把它的值(Value)返回给你
自注意力机制完全复用了这个逻辑。只不过这里的 Query、Key、Value 都是从输入序列本身计算出来的。
具体来说,对于输入序列中的每个词,我们都会做三件事:
- 把它映射成一个 Query 向量
- 把它映射成一个 Key 向量
- 把它映射成一个 Value 向量
然后,每个词用自己的 Query 去「问」所有其他词的 Key,算出注意力分数。分数越高,说明这两个词越「相关」。最后用这些分数去加权求和对应的 Value,得到这个词的最终表示。
我个人习惯这样理解:
Query 就像你在问「谁跟我有关系?」
Key 就像每个词在回答「我跟你有没有关系?」
Value 就像「如果我有关系,我把我的信息传递给你」
3.3 数学上是怎么算的?
咱们来看公式。假设输入序列有 n 个词,每个词的维度是 d。那么:
# 假设输入 X 的形状是 (n, d)
# 三个权重矩阵 W_Q, W_K, W_V 的形状都是 (d, d_k)
Q = X @ W_Q # 形状: (n, d_k)
K = X @ W_K # 形状: (n, d_k)
V = X @ W_V # 形状: (n, d_k)
# 注意力分数矩阵
scores = Q @ K.T # 形状: (n, n)
# 缩放,防止内积过大
scores = scores / sqrt(d_k)
# Softmax 归一化
attention_weights = softmax(scores, dim=-1)
# 加权求和
output = attention_weights @ V # 形状: (n, d_k)
这里有个细节要注意:为什么要除以 sqrt(d_k)?
嗯,原因很简单。当 d_k 比较大时,Q 和 K 的内积可能会变得很大,导致 softmax 后的梯度非常小,模型就学不动了。除以 sqrt(d_k) 相当于把方差拉回到 1 左右,让梯度更稳定。
我曾经踩过的坑:
刚开始实现时,我忘了做缩放,结果训练 loss 死活降不下去。查了两天代码才发现是这个问题。所以大家写代码时一定要记得加这一行。
3.4 自注意力机制的整体流程
下面这张图展示了自注意力机制的完整流程,从输入到输出:
3.5 多头注意力:让模型从不同角度「看」
刚才讲的是单头注意力。但实际 Transformer 用的是多头注意力(Multi-Head Attention)。为什么?
你想想看,一个词在不同语境下,关注的点是不一样的。比如「苹果」这个词,在「苹果很好吃」和「苹果发布了新手机」这两句话里,它关注的东西完全不同。单头注意力只能学到一种关注模式,不够用。
多头注意力的做法是:把 Q、K、V 分别投影到 h 个不同的子空间,在每个子空间里独立做注意力计算,最后把结果拼起来。这样模型就能同时从多个角度去「看」输入序列。
多头注意力的好处:
- 每个头可以关注不同的位置关系(比如语法关系、语义关系)
- 增加模型的表达能力,又不显著增加计算量
- 训练更稳定,收敛更快
3.6 实际应用中的注意事项
最后分享几个我在项目中积累的经验:
| 问题 | 表现 | 解决方案 |
|---|---|---|
| 序列太长 | 显存爆炸 | 使用窗口注意力或稀疏注意力 |
| 训练不稳定 | Loss 震荡 | 检查是否忘了缩放因子 |
| 过拟合 | 验证集效果差 | 增加 Dropout,减少头数 |
| 收敛慢 | Loss 下降缓慢 | 尝试更大的学习率 + warmup |
一个小技巧:
如果你刚开始实现自注意力,建议先用小规模数据(比如序列长度 32、维度 64)跑通流程,再逐步放大。我当年直接上 512 长度的序列,debug 起来那叫一个酸爽。
好了,关于 Q、K、V 的概念就讲到这里。说白了,自注意力就是让每个词都能「看到」序列里的所有其他词,然后根据相关性来聚合信息。这个思想贯穿了整个 Transformer 架构,也是 MobileViT 能在移动端高效运行的核心原因之一。