想象一个大案现场:门锁没有撬痕,窗户关着,但保险柜里的钻石不见了。你派出了一支侦探团队,每位侦探拿着不同颜色的放大镜——红色侦探负责时间线,蓝色侦探负责人物关系,绿色侦探负责物证痕迹,紫色侦探负责财务流水。他们各自在案件信息中搜索自己关心的线索,写下判断,最后把报告拼在一起,你才看清真相:是内部人干的。多头自注意力,就是Transformer派出的这支侦探团队。它让模型在读懂一句话时,同时从多个维度观察词与词的关系:语法、语义、指代、情感……每一种维度由一个“头”负责。本文会用这个比喻,把多头自注意力从QKV到缩放再到多头拼接讲得明明白白,不堆公式吓人。
多头自注意力是Transformer时代大模型理解语言的核心引擎。掌握它,你就掌握了BERT、GPT等主流模型的底层逻辑,是从事深度学习算法工程师、LLM应用开发等岗位的必修课。
可视化演示
展示多层神经网络前向传播过程,数据从输入层经隐藏层传递到输出层,实时显示各层神经元激活值变化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
输入层 L0 青色节点为神经元,线条粗细表示权重大小 流动粒子表示数据前向传播过程 a = σ(Σ w·x + b) 每个神经元计算加权输入后经激活函数输出
自注意力总览
侦探团队刚到案发现场,队长说:“先别下结论,每个人都把整本卷宗从头到尾翻一遍,看看谁和谁之间存在关联。”于是每位侦探拿着自己的笔记本,在卷宗里划出重点。这就是自注意力的开始——每个词不再是孤立的信息点,而是与句子里的每一个词交换情报。
在Transformer中,输入是一串词向量。自注意力让每个词向量通过计算与其他所有词向量的关联,更新自身为包含上下文信息的表示。这种关联用三个角色描述:Query(查询)、Key(键)、Value(值)。一段话里,Query问“我在找什么”,Key回答“我身上有什么标签”,Value给出“我实际的内容”。输出是Value按关联度加权汇总的结果。
就像侦探们最终会根据某个线索与本案的匹配程度来分配注意力,自注意力的输出就是每个词在全文语境下的“案情摘要”。
- 计算查询与键的匹配度将Q与K的转置相乘,得到每对词之间的原始分数,分数越高代表关系越紧密。
- 缩放分数除以$\sqrt{d_k}$,防止向量维度大时点积过大。
- 归一化成权重用Softmax将每一行的分数变成总和为1的概率。
- 加权汇总内容用权重对V做加权平均,得到该位置的上下文表示。
QKV矩阵变换
每位侦探都有特殊的笔记本:红色侦探的本子只记时间,蓝色侦探只记人物关系。他们拿着同一份案件材料,但记录的格式不同。同样地,输入词向量本身并不天然区分Q、K、V,需要通过三个可学习的权重矩阵,把同一个输入投影到三种“笔记本格式”里去。
具体来说,输入矩阵$X$的每一行是一个词的嵌入向量。分别乘以$W_Q$、$W_K$、$W_V$三个权重矩阵后,就得到了Q、K、V。这三个矩阵是模型训练中逐渐学出来的,相当于侦探们自己调整记录方式。
这就像给每位侦探分发不同颜色的笔记本——同样的证词,不同的关注点,最终让模型在后续计算中“各取所需”。
- 输入矩阵X把一句话的n个词向量堆叠成矩阵,行数是词数,列数是嵌入维度。
- 三次矩阵乘法X分别与三个可学习的投影矩阵相乘,完成线性变换。
- 得到三种角色Q、K、V在数值上不同,但形状相同,分别承载查询、标签、内容三种语义。
注意力分数计算
侦探如何判断一条线索是否重要?他会把自己关心的点(Q)与证据上贴着的标签(K)做匹配,匹配度就是分数。最常见的匹配方式就是点积:两个向量方向越一致,点积越大。但点积的数值会随着向量维度$d_k$增大而变大,就像多人同时说话时音量会失控,所以需要除以$\sqrt{d_k}$来“压音量”。
缩放之后的分数矩阵再进入Softmax,把每一行变成和为1的注意力权重。这一步让权重既能保留相对强弱,又不会因为个别极大值抢走全部注意力。
缩放因子就像音响系统的音量限制器——如果某个线索音量过大,就会掩盖其他线索,所以要把音量调回舒适区。合理缩放之后,Softmax才能给出既锐利又稳定的注意力分布。
- 点积计算位置i的查询向量$\mathbf q_i$与位置j的键向量$\mathbf k_j$做内积,得到相似度。
- 除以缩放因子用$\sqrt{d_k}$缩放,把分数拉回适合Softmax的区间。
- 得到分数矩阵所有i和j的组合构成注意力分数矩阵,矩阵中的每个元素就是对应两个位置的关联强度。
多头机制原理
一个侦探可能很擅长时间线,但让他同时关注人物关系就会漏掉重要线索。所以队长安排多位侦探,每人专攻一个方向,最后把报告拼在一起。多头注意力正是这样:不再只做一次注意力,而是把Q、K、V分别投影到h组较低维的子空间,在每组里独立计算注意力,最后把所有头的结果拼接起来,再经过一个输出投影矩阵$W_O$。
每个头都有自己独立的$W_i^Q$、$W_i^K$、$W_i^V$,因此可以学到不同的注意力模式。比如一个头关注语法依赖,另一个头关注指代关系,第三个头关注词义相似性。这正是“多头”比单头强大的根本原因:不同子空间中的模式可以互补。
不同侦探的独立报告最后拼成完整案情,Transformer也因此能同时捕捉语法、指代、语义等多维信息。这就是多头自注意力的核心魅力。
- 每个头独立投影用$W_i^Q$、$W_i^K$、$W_i^V$把原始Q、K、V映射到各自的子空间。
- 单头注意力计算每个头内部执行一次标准自注意力,得到该头关注的上下文向量。
- 拼接所有头把h个头的结果按列拼接成一个长向量,相当于把各位侦探的报告装订成册。
- 输出投影用$W_O$把拼接后的长向量映射回模型原始维度,形成最终输出。
长距离依赖优势
如果只有一个侦探,他必须从头到尾逐一念证词,念到第100句时早就忘了第1句说了什么。而多侦探团队直接把整本案卷摊开在桌上,第1句和第100句都能直接建立连线。RNN处理长文本时,信息沿时间步传递,遇到长距离就容易被遗忘;而自注意力让任意两个token一步直达,天然适合长距离依赖。
当然,这种“全摊开”的代价是计算量。每个位置都要和所有位置做注意力,所以复杂度随序列长度n呈平方增长。理解这个代价,你就明白为什么实际应用中会在长文本上做窗口化或稀疏化优化。
摊开卷宗虽然有成本,但换来的是任何线索都能一眼看到。正是这种“全局视野”,让大模型在长文中依然能抓住关键信息。
- n个查询与n个键配对数每个位置都要对其他所有位置计算注意力,所以有n×n对。
- 每对计算涉及维度d点积和加权平均都要处理d维向量。
- 总复杂度时间和空间复杂度都是$\mathcal O(n^2 d)$,序列变长时成本急剧上升。
实际应用场景
如今这支“侦探团队”已经被各大机构收编,成了处理语言的标配。从翻译到写作文,从问答到图文检索,背后都有多头自注意力在同时关注不同线索。
在机器翻译中,多头注意力让源语言和目标语言的词对齐更准确;在BERT预训练中,模型用多头注意力预测被掩码的词,同时参考左右两侧的上下文;在GPT系列模型中,多头注意力逐词预测下一个token,支撑起强大的文本生成能力;在多模态模型中,不同头还能分别对齐图像中的物体、颜色和文本中的名词、形容词。多头机制让“多视角协作”成为通用方案,任何需要关联两处信息的任务都能受益。
无论案件是翻译、写作还是看图说话,这支多头侦探团队都能靠多视角协作,拼出全局真相。这也解释了为什么Transformer能统一NLP、CV、语音等多种模态的建模方式。
AI 应用场景
机器翻译中的词对齐
阶段:模型训练与推理
解决的问题:传统统计翻译模型难以同时处理源语言和目标语言的远距离对齐,容易漏译。多头注意力的不同头分别关注主谓一致、时态、从句等不同关系,让翻译更加准确。
具体案例:Google翻译在线系统使用Transformer后,翻译“我昨天看到的那只狗,今天又出现了”时,不同注意力头能同时追踪“狗”和“那只”以及“昨天”和“今天”的时间关系,显著减少错译。
BERT预训练中的上下文理解
阶段:预训练
解决的问题:传统语言模型只能单向预测,无法充分理解上下文。BERT使用多头自注意力并行双向读入句子,在掩码词预测中同时参考左右两侧信息,相当于每个侦探都翻阅全文后才给出判断。
具体案例:BERT在预训练时随机遮住15%的词,让注意力头去预测被遮住的词。例如“今天[ ]真不错”,多头同时关注“今天”和“不错”来填出“天气”,让模型学到深层的语义关联。
多模态图文检索
阶段:多模态对齐训练
解决的问题:图像和文本不在同一个语义空间,难以直接比较。多头自注意力将图像块与文本词映射到共享空间,并让不同头分别关注物体、颜色、位置等维度,实现跨模态理解。
具体案例:CLIP模型用多头自注意力对齐图片与标题。用户搜索“红裙子女孩”时,模型不同头分别匹配“红”“裙子”“女孩”,从而在海量图中定位到正确结果。
Python 演示
import numpy as np def softmax(x, axis=-1): x = x - np.max(x, axis=axis, keepdims=True) e = np.exp(x) return e / np.sum(e, axis=axis, keepdims=True) class MultiHeadAttention: def __init__(self, d_model, h, d_k=None, d_v=None): self.h = h self.d_model = d_model self.d_k = d_k or d_model // h self.d_v = d_v or d_model // h self.W_Q = np.random.randn(d_model, h, self.d_k) * 0.01 self.W_K = np.random.randn(d_model, h, self.d_k) * 0.01 self.W_V = np.random.randn(d_model, h, self.d_v) * 0.01 self.W_O = np.random.randn(h * self.d_v, d_model) * 0.01 def forward(self, X, mask=None): n, d = X.shape Q = np.einsum('nd, dhk -> nhk', X, self.W_Q) K = np.einsum('nd, dhk -> nhk', X, self.W_K) V = np.einsum('nd, dhv -> nhv', X, self.W_V) scores = np.einsum('nhk, mhk -> nhm', Q, K) / np.sqrt(self.d_k) if mask is not None: scores = scores + mask attn = softmax(scores) ctx = np.einsum('nhm, mhv -> nhv', attn, V) ctx_flat = ctx.reshape(n, -1) out = ctx_flat @ self.W_O return out if __name__ == '__main__': np.random.seed(0) d_model, h, n = 16, 4, 6 X = np.random.randn(n, d_model) mha = MultiHeadAttention(d_model, h) out = mha.forward(X) print('输出shape:', out.shape)
self.W_Q = np.random.randn(d_model, h, self.d_k) * 0.01初始化每个头的Query投影矩阵,小随机值保证初始输出平稳。Q = np.einsum('nd, dhk -> nhk', X, self.W_Q)用爱因斯坦求和将输入X投影成h个头各自的Q。K = np.einsum('nd, dhk -> nhk', X, self.W_K)同理得到K,K只负责被查询匹配。V = np.einsum('nd, dhv -> nhv', X, self.W_V)得到V,V是最终被加权汇总的内容。scores = np.einsum('nhk, mhk -> nhm', Q, K) / np.sqrt(self.d_k)在每个头内计算所有位置对的相似度,并除以缩放因子。attn = softmax(scores)将相似度归一化为注意力权重,每行和为1。ctx = np.einsum('nhm, mhv -> nhv', attn, V)用注意力权重对V加权求和,得到每个头的上下文表示。ctx_flat = ctx.reshape(n, -1)把多个头的输出拼接成一个大向量。out = ctx_flat @ self.W_O输出投影,将拼接结果映射回模型维度。
常见误区
误区我觉得多头注意力就是头数越多效果越强,反正每个头都能带来新信息,所以干脆把头数设大一点,模型肯定更聪明。
正解头数增加会摊薄每个头的维度,投影后子空间表达能力反而下降;头数过多还会带来冗余和计算开销。实际中头数需要与模型维度、数据规模平衡。就像派100个侦探,如果任务重叠,不如5个分工明确的精锐更高效。
为何常见:“多头”听起来像“人多力量大”,初学者容易被直觉带偏,忽略了维度分配和计算成本的约束。
误区我一直以为Query、Key、Value是三个不同的输入向量,分别来自句子中不同的来源,所以它们天然不一样。
正解在自注意力中,Q、K、V都由同一个输入矩阵X乘上不同的权重矩阵生成。它们确实数值不同,但来源相同。好比侦探们用的是同一份案件材料,只是各自用了不同格式的笔记本记录重点。
为何常见:因为"Query、Key、Value"名字差异明显,再加上多头里又有多个投影矩阵,就容易误以为数据源头也不一样。
误区我以为除以根号d_k只是为了让公式好看,对模型效果影响不大,删掉它也能训练。
正解不加缩放因子时,高维向量点积的绝对值会偏大,Softmax进入饱和区,梯度极小,训练几乎无法收敛。缩放因子相当于把“证据音量”控制到合理范围,是训练稳定的关键一环。
为何常见:初学者往往先看公式形态,容易忽略数值稳定性问题,只有训练时才感受到它的必要性。
误区既然有多个头,我觉得模型必须一个一个地处理它们,就像多个人排队发言一样。
正解多个头之间没有任何依赖,每个头独立完成投影、注意力计算,可以在GPU上完全并行。这正是Transformer高效的原因之一,也是它比RNN更适合大规模训练的重要原因。
为何常见:“多头”这个名字容易让人联想到“多阶段流程”,而忽略了矩阵运算天然可以批量并行的事实。
误区我以为增加头数就能让模型看得更远,所以处理长文本时应该把头数调得特别大。
正解长距离依赖的捕捉主要来自自注意力机制本身,即任意两个词一步直接关联,而不是靠头数堆出来的。头数影响的是关注模式的多样性,与捕获距离没有线性关系。
为何常见:把“多头”和“多步传递”混为一谈,实际上自注意力并不像RNN那样按步传递信息。
面试题
Q1. 在多头自注意力中,Q、K、V分别对应检测中的三个角色,下列描述正确的是?
- A.查询、键、值← 正确答案
- B.主键、外键、索引
- C.质量、关键点、价值
- D.求商、开方、方差
答案:A。Q(Query)是查询条件,K(Key)是被查询的标签,V(Value)是实际携带的内容。想象侦探问“我找什么”,线索上贴着标签,标签后面的内容就是值。其余选项把字母生硬套到其他概念,不符合定义。
Q2. 多头注意力中每个“头”的核心作用是什么?
- A.控制模型层数
- B.在不同子空间关注不同信息← 正确答案
- C.减少模型参数量
- D.让模型每次只看一个词
答案:B。多个头相当于多位侦探,各自从不同子空间提取信息,再汇总,从而让模型同时关注语法、语义等多种关系。控制层数靠的是Transformer的层叠结构,减少参数量不是多头的主要目的,每次只看一个词则失去了注意力并行的优势。
Q3. 自注意力分数计算中的缩放因子通常使用哪个式子?
- A.d_k 开根号
- B.1 除以根号 d_k← 正确答案
- C.直接取 d_k
- D.d_model 的一半
答案:B。缩放因子是$\frac{1}{\sqrt{d_k}}$,计算时用分数除以$\sqrt{d_k}$。这样能把点积结果压回合适范围,防止Softmax饱和。取d_k会让分数持续偏大,d_model的一半更是没有依据的随机值。
Q4. 多头注意力的输出投影矩阵W_O的输入维度是?
- A.d_model 本身
- B.头数乘 d_v← 正确答案
- C.键向量维度
- D.序列长度乘头数
答案:B。多个头的输出维度是h×d_v,拼接后成为W_O的输入,W_O再把它映射回d_model。d_model是最终输出维度而非输入维度;键向量维度是单头内部的维度;序列长度与矩阵投影没有直接关系。
Q5. 为什么自注意力能够捕捉长距离依赖?
- A.使用循环结构
- B.所有词对直接计算相关性← 正确答案
- C.使用更大的卷积核
- D.进行梯度裁剪
答案:B。自注意力让任意两个词一步建立联系,不需要像RNN那样逐帧传递,所以第1个词和第100个词也能直接交互。循环结构是RNN的特点,大卷积核是CNN的做法,梯度裁剪只是训练稳定技巧,都不是长距离依赖的关键。
Q6. 缩放因子的作用包括哪些?
- A.防止梯度饱和← 正确答案
- B.控制注意力温度← 正确答案
- C.增加模型规模
- D.提升训练稳定性← 正确答案
答案:ABD。缩放因子把点积压回合理范围,等价于调整Softmax的温度,既能防止梯度饱和,也能提升训练稳定性。它不会增加模型规模,因为缩放因子本身不是可学习的额外参数。
Q7. Softmax在注意力机制中的主要作用是?
- A.增加非线性
- B.将分数归一化成概率← 正确答案
- C.降低向量维度
- D.防止模型过拟合
答案:B。Softmax将注意力分数转换成总和为1的权重,让加权求和在数值上稳定且有概率解释。增加非线性不是Softmax的核心目的,降低维度是投影矩阵的事,过拟合防护通常靠Dropout和正则化。
Q8. 自注意力的计算复杂度关于序列长度n和维度d是?
- A.O(n) 线性
- B.O(n^2) 平方
- C.O(n^2·d) 含维度← 正确答案
- D.O(n·d) 线性
答案:C。每个位置要对所有n个位置计算点积,共n^2对,每对涉及d维向量,因此复杂度为O(n²d)。O(n)是线性序列模型的复杂度,O(n²)忽略了维度,O(n·d)只适合单步操作,都不完整。
Q9. 关于多头注意力中各头的投影矩阵,下列说法正确的是?
- A.各自独立← 正确答案
- B.共享部分权重
- C.关注不同子空间← 正确答案
- D.一起更新参数← 正确答案
答案:ACD。每个头有自己的W_Q、W_K、W_V,互不共享,在训练中同时更新。不同头从不同子空间提取信息,因此关注模式可以互补。B说共享权重不符合多头设计初衷,如果共享就退化成单头。
Q10. 以下关于多头自注意力的说法,正确的有哪些?
- A.各头可并行计算← 正确答案
- B.缩放因子能防止梯度问题← 正确答案
- C.拼接后需输出投影← 正确答案
- D.头数越多越好
答案:ABC。各头是独立的,因此可以并行;缩放因子改善数值稳定性;拼接后要过W_O映射回原维度。头数太多未必更好,要平衡表达能力与计算量,D说法过于绝对。