多头注意力机制(Multi-Head Attention)是自注意力机制的一种扩展,旨在解决自注意力机制在处理复杂任务时可能存在的局限性,尤其是当模型在编码当前位置信息时,容易过度关注自身位置而忽略其他重要信息的问题。通过引入多个“注意力头“(Attention Head),多头注意力机制能够从不同子空间中捕捉输入序列的多种依赖关系,从而提高模型的表达能力和鲁棒性。 背景问题:自注意力机制虽然能够捕捉输入序列内部的依赖关系,但在实际应用中,它可能会存在以下问题: 1. 单一视角的局限性:自注意力机制通常只使用一组Q、K和V 来计算注意力得分,会过度的将注意力集中于自身的位置,有效信息抓取能力就差一些,这可能导致模型无法充分挖掘输入数据中的多样化特征。 为了解决这些问题,多头注意力机制被提出,其核心思想是通过并行地使用多个独立的注意力头,分别学习不同的子空间表示,最终将这些子空间的信息融合起来,以获得更全面的表示。 ![image.png|1000](https://imagehosting4picgo.oss-cn-beijing.aliyuncs.com/imagehosting/fix-dir%2Fpicgo%2Fpicgo-clipboard-images%2F2025%2F07%2F19%2F17-30-48-b23a92deba824614f3a676bef64d14be-202507191730342-afb6d0.png) 其实就是能力更强的 CNN ![CleanShot 2025-07-13 at [email protected]|1000](https://imagehosting4picgo.oss-cn-beijing.aliyuncs.com/imagehosting/fix-dir%2Fmedia%2Fmedia_iSQUV8ITYG%2F2025%2F07%2F13%2F19-41-15-a593cd29588d679b136a4e3613606808-CleanShot%202025-07-13%20at%2019.40.46-2x-8e9c86.png) 好的,我用具体的数字来演示整个多头注意力的计算过程。 # 初始设置 ```python # 配置 d_model = 6, num_heads = 2, d_head = 3 seq_len = 4 # "我爱北京" # 输入 X(经过embedding后) X = [ [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], # "我" [0.2, 0.3, 0.4, 0.5, 0.6, 0.7], # "爱" [0.3, 0.4, 0.5, 0.6, 0.7, 0.8], # "北" [0.4, 0.5, 0.6, 0.7, 0.8, 0.9], # "京" ] ``` # Step 1: 线性投影(简化的权重矩阵) ```python # 为了便于计算,使用简化的权重 W_q = [[1, 0, 0, 0, 0, 0], # 简化为单位矩阵的变体 [0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 1]] # 实际计算 Q = X @ W_q(这里简化后 Q = X) Q = [ [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], # "我"的query [0.2, 0.3, 0.4, 0.5, 0.6, 0.7], # "爱"的query [0.3, 0.4, 0.5, 0.6, 0.7, 0.8], # "北"的query [0.4, 0.5, 0.6, 0.7, 0.8, 0.9], # "京"的query ] # K 和 V 使用略微不同的值 K = [[0.1, 0.2, 0.3, 0.3, 0.4, 0.5], [0.2, 0.3, 0.4, 0.4, 0.5, 0.6], [0.3, 0.4, 0.5, 0.5, 0.6, 0.7], [0.4, 0.5, 0.6, 0.6, 0.7, 0.8]] V = [[1.0, 1.0, 1.0, 2.0, 2.0, 2.0], # 简化V便于观察 [2.0, 2.0, 2.0, 3.0, 3.0, 3.0], [3.0, 3.0, 3.0, 4.0, 4.0, 4.0], [4.0, 4.0, 4.0, 5.0, 5.0, 5.0]] ``` # Step 2: 分割成多个头 重排前(合并状态): ```Java Token 0: [head0|head1|head2|head3] ← 64维的长向量 Token 1: [head0|head1|head2|head3] Token 2: [head0|head1|head2|head3] ``` 重排后(分离状态): ```Java Head 0: [token0_vec|token1_vec|token2_vec|...] ← 每个16维 Head 1: [token0_vec|token1_vec|token2_vec|...]   Head 2: [token0_vec|token1_vec|token2_vec|...] Head 3: [token0_vec|token1_vec|token2_vec|...] ``` 举例 ```python Q = [ [0.1, 0.2, 0.3, 0.4, 0.5, 0.6], # "我"的query [0.2, 0.3, 0.4, 0.5, 0.6, 0.7], # "爱"的query [0.3, 0.4, 0.5, 0.6, 0.7, 0.8], # "北"的query [0.4, 0.5, 0.6, 0.7, 0.8, 0.9], # "京"的query ] # Head 0:取前3维 Q_head_0 = [[0.1, 0.2, 0.3], # "我" [0.2, 0.3, 0.4], # "爱" [0.3, 0.4, 0.5], # "北" [0.4, 0.5, 0.6]] # "京" K_head_0 = [[0.1, 0.2, 0.3], [0.2, 0.3, 0.4], [0.3, 0.4, 0.5], [0.4, 0.5, 0.6]] V_head_0 = [[1.0, 1.0, 1.0], [2.0, 2.0, 2.0], [3.0, 3.0, 3.0], [4.0, 4.0, 4.0]] # Head 1:取后3维 Q_head_1 = [[0.4, 0.5, 0.6], # "我" [0.5, 0.6, 0.7], # "爱" [0.6, 0.7, 0.8], # "北" [0.7, 0.8, 0.9]] # "京" K_head_1 = [[0.3, 0.4, 0.5], [0.4, 0.5, 0.6], [0.5, 0.6, 0.7], [0.6, 0.7, 0.8]] V_head_1 = [[2.0, 2.0, 2.0], [3.0, 3.0, 3.0], [4.0, 4.0, 4.0], [5.0, 5.0, 5.0]] ``` # Step 3: Head 0 的注意力计算 ## 3.1 计算注意力分数 ```python # scores_0 = Q_head_0 @ K_head_0.T / sqrt(3) # 计算"我"对所有词的注意力分数 "我"对"我": (0.1×0.1 + 0.2×0.2 + 0.3×0.3) / 1.732 = 0.14 / 1.732 = 0.081 "我"对"爱": (0.1×0.2 + 0.2×0.3 + 0.3×0.4) / 1.732 = 0.20 / 1.732 = 0.115 "我"对"北": (0.1×0.3 + 0.2×0.4 + 0.3×0.5) / 1.732 = 0.26 / 1.732 = 0.150 "我"对"京": (0.1×0.4 + 0.2×0.5 + 0.3×0.6) / 1.732 = 0.32 / 1.732 = 0.185 scores_0 = [[0.081, 0.115, 0.150, 0.185], # "我"的注意力分数 [0.115, 0.173, 0.231, 0.289], # "爱"的注意力分数 [0.150, 0.231, 0.312, 0.393], # "北"的注意力分数 [0.185, 0.289, 0.393, 0.496]] # "京"的注意力分数 ``` ## 3.2 应用softmax ```python # 对每一行应用softmax # 以"我"这一行为例: exp_scores = [e^0.081, e^0.115, e^0.150, e^0.185] = [1.084, 1.122, 1.162, 1.203] sum_exp = 4.571 attn_weights_0[0] = [0.237, 0.245, 0.254, 0.263] # 归一化后 # 完整的注意力权重矩阵 attn_weights_0 = [[0.237, 0.245, 0.254, 0.263], # "我"的注意力分布 [0.220, 0.240, 0.260, 0.280], # "爱"的注意力分布 [0.200, 0.233, 0.267, 0.300], # "北"的注意力分布 [0.180, 0.220, 0.270, 0.330]] # "京"的注意力分布 ``` ## 3.3 加权求和V ```python # output_0 = attn_weights_0 @ V_head_0 # 计算"我"的输出: "我"_out = 0.237×[1,1,1] + 0.245×[2,2,2] + 0.254×[3,3,3] + 0.263×[4,4,4] = [0.237,0.237,0.237] + [0.490,0.490,0.490] + [0.762,0.762,0.762] + [1.052,1.052,1.052] = [2.541, 2.541, 2.541] output_0 = [[2.541, 2.541, 2.541], # "我" [2.600, 2.600, 2.600], # "爱" [2.700, 2.700, 2.700], # "北" [2.800, 2.800, 2.800]] # "京" ``` # Step 4: Head 1 的计算(类似过程) ```python # 假设经过计算后 output_1 = [[3.200, 3.200, 3.200], # "我" [3.400, 3.400, 3.400], # "爱" [3.600, 3.600, 3.600], # "北" [3.800, 3.800, 3.800]] # "京" ``` # Step 5: 拼接输出 ```python # 将两个头的输出拼接 multi_head_output = [ [2.541, 2.541, 2.541, 3.200, 3.200, 3.200], # "我" [2.600, 2.600, 2.600, 3.400, 3.400, 3.400], # "爱" [2.700, 2.700, 2.700, 3.600, 3.600, 3.600], # "北" [2.800, 2.800, 2.800, 3.800, 3.800, 3.800] # "京" ] # 形状: [4, 6] ``` # Step 6: 输出投影 ```python # 简化的输出投影矩阵 W_output = [[0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1], [0.1, 0.1, 0.1, 0.1, 0.1, 0.1]] # final_output = multi_head_output @ W_output # "我"的最终输出 = [2.541+2.541+2.541+3.200+3.200+3.200] × 0.1 = 1.122(每一维) final_output = [[1.122, 1.122, 1.122, 1.122, 1.122, 1.122], # "我" [1.200, 1.200, 1.200, 1.200, 1.200, 1.200], # "爱" [1.300, 1.300, 1.300, 1.300, 1.300, 1.300], # "北" [1.400, 1.400, 1.400, 1.400, 1.400, 1.400]] # "京" ``` # 关键观察 1. **Head 0** 的注意力权重显示:"我"略微更关注后面的词(0.263 > 0.237) 2. **Head 1** 处理的是输入的不同维度(后3维),可能捕获不同的语义信息 3. 最终输出融合了两个头的信息,每个位置都得到了丰富的上下文表示 这就是多头注意力如何并行处理不同的注意力模式,然后融合成最终表示的完整过程! 好的,我们来用一个更生活化的比喻来理解“多头注意力”(Multi-Head Attention)。 **想象一下:你要理解一个复杂的句子** 比如这句:“**那只快速奔跑的棕色小狐狸跳过了懒惰的大狗。**” **单一注意力(只有一个“头”)就像只有一个专家在分析:** - 这个专家可能特别擅长**语法结构**。他会告诉你,“狐狸”是主语,“跳过”是谓语,“狗”是宾语。他关注的是词语扮演的角色。这很有用,但可能忽略了其他信息。 **多头注意力(比如有 8 个“头”)就像请来了一个专家委员会:** 你把同一个句子交给 8 位不同的专家,他们同时进行分析,但**各有专长(关注点不同)**: 1. **专家1(语法专家)**:同上,分析主谓宾结构。他发现“狐狸”执行了“跳过”的动作。 2. **专家2(形容词专家)**:他特别关注描述性词语。他会注意到“快速奔跑的”和“棕色”是形容“狐狸”的,“懒惰的”和“大”是形容“狗”的。 3. **专家3(邻近关系专家)**:他可能更关注紧挨着的词。“快速”后面是“奔跑的”,“懒惰的”后面是“大”。 4. **专家4(远距离依赖专家)**:他擅长看长距离关系,比如发现主语“狐狸”和很后面的谓语“跳过”之间的联系。 5. **专家5(同类关系专家)**:他可能注意到“狐狸”和“狗”都是动物。 6. **专家6(动作关联专家)**:他关注动作“跳过”和动作的发出者“狐狸”以及承受者“狗”。 7. **专家7(颜色专家)**:他可能特别对颜色敏感,只挑出“棕色”。 8. **专家8(也许是个“新手”专家)**:他可能还没学到什么特别的专长,只是进行一些基础的关联。 **过程是这样的:** 1. **分派任务**:虽然所有专家都看同一个句子(原始的词嵌入向量),但在内部,每个专家会用**自己独特的方式**稍微“预处理”一下信息(通过不同的线性变换 Wq, Wk, Wv),得到自己专属的查询(Q)、键(K)、值(V)。这就好比每个专家都戴上了自己特制的“眼镜”来看待这句话。 2. **独立分析**:每个专家根据自己的“眼镜”和关注点,独立地计算句子中每个词对其他词的注意力得分,并得出自己的分析结果(一个输出向量)。 3. **汇总报告**:最后,有一个**总协调人**(最后的那个线性层),把这 8 位专家的独立分析结果(8个输出向量)**拼接**在一起,然后进行**综合整理**,形成一个最终的、更全面、更丰富的对句子中每个词的理解(最终的输出向量)。 **为什么这么做?** - **捕捉多种信息**:语言很复杂,词语间的关系多种多样。只用一个注意力机制(一个专家)可能只能抓住某一方面。多头让模型能**同时关注语法、语义、位置、指代等多种不同类型的信息**。 - **效果更好**:实践证明,多头注意力通常比单头注意力效果更好,能让模型学到更强大的表示。 **简单来说,多头注意力就是:** **让模型同时从多个不同的“视角”(头)去分析词语之间的关系,然后把这些不同视角得到的信息整合起来,得到一个更全面、更鲁棒的理解。** 就像请一群各有专长的专家一起分析问题,通常比只请一位专家要强。