自注意力(Self-Attention)到底在注意什么?一个句子里的每个词怎么互相看

你正在读这句话:"我把苹果放进了冰箱,它很好吃。" 那个'它'指的是什么?苹果还是冰箱?你大概半秒就懂了,但让机器理解这个,NLP研究者在很多年里掉了不少头发。直到2017年,一篇论文提出了自注意力(Self-Attention),事情终于有了转机。它让一个句子里的每个词都能直接和所有其他词"互相看",从而捕捉到像指代、修饰这样的关系。今天我想跟你聊聊,这个机制到底是怎么工作的,以及为什么它如此重要。

AI technology illustration

它根本不是“看”,而是在做三件事

我最早接触自注意力时,脑子里蹦出的画面是:每个词伸出一根触手,去碰其他词,然后带回消息。但很快我就发现这个比喻错得离谱——自注意力里没有“触手”,有的只是三个神奇的向量:Query(查询)、Key(键)、Value(值)。说人话就是:每个词都在同时扮演三个角色——它想知道自己应该关注谁(Query),它也有一个标签让别人找到自己(Key),最后它还携带着实际要传递的信息(Value)。

这个设计其实很像数据库的检索逻辑:你拿着一个查询(Q),去匹配所有键(K),匹配度越高,就从对应的值(V)里提取越多的内容。只不过在自注意力里,这些Q、K、V全是句子里的词自己通过线性变换学出来的,没有提前写好的规则。所以,“注意”本质上是每个词根据当前上下文,动态计算出一组加权系数,然后对所有词的值向量做加权平均

一个句子里的社交网络:注意力权重矩阵的诞生

想象一个只有四个词的句子:“我 喜欢 吃 苹果”。当模型处理“吃”这个词时,它需要知道宾语是什么,于是“吃”的Query向量会去和所有词的Key向量计算点积。点积越大,说明两个向量越匹配,也就意味着“吃”应该多关注那个词。这些点积值经过一个softmax函数,就变成了概率分布——也就是注意力权重。最后,用这些权重去混合所有词的Value向量,得到“吃”这一位置的输出表示。

这个过程可以拆成六个步骤,每一步都对应一个矩阵运算:

  1. 将句子中的每个词嵌入向量分别乘以三个权重矩阵WQ、WK、WV,得到Q、K、V。
  2. 计算每个词的Q与所有词的K的点积,得到注意力得分矩阵。
  3. 将得分除以√dk(Key向量的维度开根号),防止点积过大导致softmax梯度消失。
  4. 对每一行进行softmax,得到注意力权重矩阵。
  5. 将权重矩阵与V矩阵相乘,得到每个位置的输出向量。
  6. (可选)把多个头的输出拼起来再投影,得到最终的多头注意力结果。

我最早动手实现时,用PyTorch写了个demo,打印出注意力权重矩阵,发现大多数值都在0.1左右,以为是代码bug。后来才想通,一个短句子里每个词本来就应该和其他词有差不多的关注度,加权平均后自然就平滑了。只有在长句或者特定语境下,某些词才会被强烈关注——比如“苹果”和“吃”之间的权重会远高于“苹果”和“我”。

import torch.nn.functional as F
# 假设已经得到Q, K, V,形状为(batch, seq_len, d_k)
scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_k ** 0.5)
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)

这段代码就是自注意力的全部核心,剩下所有花活都是围绕它打的补丁。

为什么要有“多头”?因为一个头根本不够用

如果你只用一组Q、K、V,那么模型只能学到一种单一的关注模式。比如对于“把苹果放冰箱,它很好吃”,一个头可能只学会了关注名词,却搞不清“它”到底指代谁。2017年的论文里就给出了一个聪明的解法:并行做多次自注意力,每次用不同的投影矩阵,让不同的“头”去捕捉不同的关系。一个头可能专门学主谓关系,另一个头学指代消解,第三个头学语法性别……最后把它们拼起来,喂给一个线性层,综合所有信息。

这就像你同时派好几个侦探去调查一个案子,每个侦探擅长不同的领域,回来之后开会汇总。多头注意力的公式看起来吓人,但其实只是把上面的单头代码包了一层循环,然后拼接起来。

“Instead of performing a single attention function, we found it beneficial to linearly project the queries, keys and values h times with different, learned linear projections.” — Attention Is All You Need

在实际的Transformer里,比如BERT-base,头数h=12,每个头的维度是64,总输出维度是768。这种设计让模型既能保有足够的表示能力,又不至于让计算量爆炸。

一个要命的缺陷:没有位置感

自注意力机制有一个天生的盲区:它完全不知道词的顺序。对你来说,“我打你”和“你打我”天差地别,但对自注意力而已,两句话的Q、K、V计算完全一样,只是词的位置不同,但注意力权重里没有顺序信息。为了让模型区分词序,Transformer在输入上叠加了位置编码(Positional Encoding)。原始论文用的是正弦余弦函数生成的位置编码,后续工作也有用可学习位置嵌入的。不管哪种,本质上都是在给词向量“印上”一个独一无二的位置戳。

这个细节很容易被忽略,但如果没有它,Transformer连简单的词序任务都做不了。我曾经试着去掉位置编码训练一个小模型,结果在翻译任务上BLEU值直接掉到个位数——模型输出的句子就像一堆打乱的词卡。

这张表说清自注意力、RNN和CNN的区别

比较维度 自注意力 RNN/LSTM CNN
长距离依赖 任意两个位置直接交互,路径长度O(1) 需要逐步传递,路径长度O(n),容易遗忘 通过堆叠扩大感受野,浅层只能看到局部
并行计算 每个位置可同时计算,高效 必须顺序计算,无法并行 可并行,但感受野受限
计算复杂度 O(n²·d) O(n·d²) O(k·n·d²) k为卷积核大小
位置信息 需要额外注入位置编码 天然具备顺序感知 局部天然具备,全局需堆叠

一句话总结:自注意力用 O(n²) 的复杂度,换来了全局交互和并行能力。 当序列长度n不太大时(比如n<512),这是非常划算的买卖;但当n达到几千甚至几万时,O(n²)会让计算资源吃不消,这也是后来出现Longformer、BigBird等稀疏注意力方案的原因。

常见误解,一次说清

注意力权重就是每个词的重要性?

不完全对。权重矩阵的高值只表示在当前这个头的视角下,某个词对当前词的信息聚合贡献大。但不同的头可能关注完全不同的关系,一个头里权重高的词,换个头可能就变低了。而且,权重还受Value向量的影响——即使权重高,如果Value向量很小,实际贡献也有限。

自注意力能自动学会语法?

它能学会很多语法现象,但并不是通过显式规则。大量实验表明,某些注意力头确实会聚焦于主谓关系、介词宾语等,但这种模式是从数据中涌现出来的,并没有人告诉它什么是主语。所以它也可能学到一些奇怪的统计关联,比如“not”和后面某个词的高权重,但并不是真正的句法树。

多头不会互相覆盖吗?

不会,因为每个头有自己独立的WQ、WK、WV矩阵,它们学到的投影方向不同。从训练动力学看,多头会自发地分化出不同的功能,就像团队分工一样。当然,如果头数太多,也可能出现冗余,但实践中8~16个头通常够用。

我为什么觉得自注意力是一个“优雅且粗暴”的发明

优雅在于,它用一套统一的计算范式(点积缩放+softmax+加权求和)解决了序列建模中几乎所有的依赖问题,而且可以用矩阵乘法高效实现。粗暴在于,它直接假设所有词对之间都需要两两交互,这带来了不可忽视的平方级复杂度,而对于长文本,这种“全连接”的方式其实很浪费——大部分词对之间的交互信息量极低。

所以,当你在用ChatGPT生成一段代码时,它背后的自注意力机制正以O(n²)的代价,让每个token和你之前的所有对话历史“打招呼”。当前上下文窗口已经突破128k甚至1M,但那是靠着KV缓存、记忆压缩、稀疏注意力等一大堆工程技巧才撑住的。

自注意力是当下的基石,但未来未必是终局。 更好的长程依赖建模、更低的计算开销,可能是下一代架构要解决的核心问题。但至少在2024年,你要理解任何一个大模型,都得先过自注意力这一关。希望这篇文章能帮你少走一些弯路。

原创文章,作者:guanweilu,如若转载,请注明出处:https://guanweilu.cn/article/164.html

(0)
上一篇 2026年8月18日 上午12:09
下一篇 2026年8月18日 上午12:22

相关推荐