mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
10036 字
27 分钟
自注意力机制(Self-Attention)

引言:Self-Attention要解决什么问题?#

在深入Self-Attention机制之前,我们需要先明确:它是用来处理什么样的任务的?

序列处理的三种典型场景#

在自然语言处理和序列建模中,我们经常遇到以下三种输入输出模式:

1. Sequence to Sequence(序列到序列)#

  • 输入:一排向量(如一个句子的词向量序列)
  • 输出:另一排向量(如翻译后的句子)
  • 典型任务:机器翻译、文本摘要
  • 例子:“我爱机器学习” → “I love machine learning”

2. Sequence Labeling(序列标注)#

  • 输入:一排向量

  • 输出:同样长度的一排标签

  • 典型任务:词性标注、命名实体识别、语音识别

  • 例子

    输入: ["我", "爱", "机器", "学习"]
    输出: [代词, 动词, 名词, 名词]

3. Sequence to One(序列到单值)#

  • 输入:一排向量
  • 输出:一个值或类别
  • 典型任务:情感分类、文本分类
  • 例子:“这部电影太棒了!” → 正面情感

Self-Attention的核心优势#

对于Sequence Labeling这类任务,Self-Attention相比RNN有一个关键优势:

  • RNN:处理第ii个词时,只能看到前面11i1i-1个词的信息(单向RNN)
  • Self-Attention:处理第ii个词时,可以同时看到整个序列的所有词,包括前面的、后面的、甚至自己

举例:在词性标注任务中

  • 句子:“我在银行工作”
  • 判断”银行”的词性时,Self-Attention可以同时看到”工作”这个词,从而判断这里的”银行”是金融机构而不是河岸

这种全局视野使得Self-Attention能够更好地利用上下文信息,给每个Token打上更准确的标签。


一、从RNN到Self-Attention的动机#

RNN的局限性:为什么需要Self-Attention?#

想象你在读一篇文章,理解当前这个词的意思时,你需要回忆前面所有相关的词。RNN就像一个人从头到尾依次阅读,每读一个词都要记住之前的内容。这种方式有几个问题:

1. 顺序处理的困境#

  • 必须按顺序读:RNN像排队一样,必须处理完第1个词,才能处理第2个词,再处理第3个词…
  • 无法并行:即使有多个GPU核心,也只能干等着,因为后面的词依赖前面的结果
  • 训练慢:处理长文本时,训练时间会非常长

2. 长距离依赖的困难#

考虑这个句子:

“那只在公园里追逐蝴蝶的、毛色金黄的、看起来很开心的正在奔跑。”

  • RNN处理到”奔跑”时,需要记住很久之前的”狗”
  • 尽管LSTM/GRU有记忆机制,但信息经过多次传递后仍会衰减
  • 就像传话游戏,传的人越多,信息失真越严重

3. 计算效率低#

  • 现代GPU擅长并行计算,但RNN的顺序特性无法充分利用
  • 就像有100个工人,却只能排队干活,而不能同时开工

Self-Attention的革命性突破#

Self-Attention提出了一个全新的思路:让序列中的每个位置直接”看到”所有其他位置

核心思想:“天涯若比邻”#

  • 并行处理:所有位置可以同时计算,不需要等待
  • 直接连接:任意两个位置之间的距离都是O(1),无论它们相隔多远
  • 灵活关注:每个位置可以自主决定关注哪些位置,权重由相关性决定

形象比喻#

  • RNN:像接力赛,信息一棒一棒传递,后面的人要等前面的人跑完
  • Self-Attention:像群聊,所有人同时看到所有消息,可以直接回复任何人

加权求和的本质#

每个位置的输出 = 对整个序列的加权求和

  • 权重由该位置与其他位置的相关性决定
  • 相关性高的位置权重大,贡献更多信息
  • 相关性低的位置权重小,几乎不贡献信息

二、Self-Attention的基本原理#

Query、Key、Value机制#

Self-Attention的核心是Q-K-V三元组机制。这三个概念可能一开始听起来很抽象,但其实它们来自我们日常生活中的检索行为。

直观理解:图书馆找书的过程#

想象你在图书馆找书:

  1. Query(查询):你脑海中想要查找的主题
    • 比如你想了解”深度学习”
    • 这是你的需求,你想要什么信息
  2. Key(键):每本书的标签/索引/目录
    • 每本书的封面、标题、关键词
    • 这是书籍的索引信息,用来判断是否匹配你的需求
  3. Value(值):书的实际内容
    • 书里面真正的知识和信息
    • 这是你最终要获取的实际内容
  4. 注意力权重:Query与Key的匹配度
    • 如果一本书的标签与你的查询高度匹配,你会花更多时间读它(权重大)
    • 如果不匹配,你可能只是扫一眼就跳过(权重小)

在Self-Attention中的对应#

在处理序列”我 爱 机器 学习”时:

  • 每个词都有三个角色
    • 作为Query:当处理这个词时,它想要从其他词那里获取什么信息?
    • 作为Key:当其他词查询时,这个词提供什么索引信息?
    • 作为Value:这个词实际包含什么内容?
  • 举例:处理”学习”这个词
    • Query(“学习”):我想知道学习的是什么?
    • 与Key(“机器”)匹配度高 → 权重大
    • 与Key(“我”)、Key(“爱”)匹配度低 → 权重小
    • 最终输出 = 主要来自Value(“机器”)的信息 + 少量其他词的信息

关键洞察#

  • Q、K、V都来自同一个输入,只是通过不同的权重矩阵投影得到
  • Query决定”我要什么”,Key决定”我有什么”,Value决定”我给什么”
  • 注意力权重 = Query和Key的相似度,决定了Value的贡献比例

Self-Attention的运作流程#

示例:处理句子”机器 学习”#

Self-Attention的Q-K-V机制示意图:输入通过不同权重矩阵生成Query、Key、Value

假设我们要计算”学习”这个词的Self-Attention输出。

第一步:生成Q、K、V向量#

对于输入序列中的每个词,通过三个不同的权重矩阵生成Q、K、V:

输入: ["机器", "学习"]
通过权重矩阵投影:
- "机器" → Q₁, K₁, V₁
- "学习" → Q₂, K₂, V₂

关键点:每个词都有自己的Q、K、V三个向量,它们是通过学习得到的权重矩阵从原始词向量转换而来。

第二步:计算注意力分数#

现在我们要计算”学习”对所有词(包括自己)的注意力分数:

Query("学习") 与所有Key的点积:
- score("学习", "机器") = Q₂ · K₁ = 0.9 (高相关性)
- score("学习", "学习") = Q₂ · K₂ = 0.3 (自己对自己)

直观理解

  • 点积越大,说明Query和Key越相似,相关性越高
  • “学习”的Query与”机器”的Key相关性很高(0.9),说明”学习”这个词需要关注”机器”
  • 除以√d_k是为了防止点积值过大,导致Softmax饱和

第三步:归一化得到注意力权重#

使用Softmax将分数转换为概率分布(权重和为1):

Softmax归一化:
- α("学习", "机器") = exp(0.9) / (exp(0.9) + exp(0.3)) = 0.73
- α("学习", "学习") = exp(0.3) / (exp(0.9) + exp(0.3)) = 0.27

含义

  • “学习”这个词应该73%关注”机器”,27%关注自己
  • 权重和为1,确保输出的尺度稳定

第四步:加权求和得到输出#

用注意力权重对所有Value进行加权求和:

输出("学习") = 0.73 × V₁ + 0.27 × V₂
= 0.73 × Value("机器") + 0.27 × Value("学习")

最终结果

  • “学习”的输出向量主要包含”机器”的信息(73%)
  • 也保留了一些自己的信息(27%)
  • 这个输出向量融合了上下文信息,比原始的”学习”向量更丰富

核心洞察#

  1. 每个词都会执行这个过程:不仅”学习”,“机器”也会计算自己的注意力输出
  2. 并行计算:所有词的注意力可以同时计算,不需要等待
  3. 动态权重:注意力权重是根据内容动态计算的,不是固定的
  4. 信息融合:输出向量融合了整个序列的相关信息

数学公式#

1. 计算注意力分数#

对于位置ii的Query和位置jj的Key:

score(i,j)=qikjdk\text{score}(i, j) = \frac{\pmb{q}_i \cdot \pmb{k}_j}{\sqrt{d_k}}
  • qi\pmb{q}_i:位置ii的Query向量
  • kj\pmb{k}_j:位置jj的Key向量
  • dkd_k:Key向量的维度
  • dk\sqrt{d_k}:缩放因子,防止点积过大

2. 计算注意力权重#

αij=exp(score(i,j))k=1nexp(score(i,k))\alpha_{ij} = \frac{\exp(\text{score}(i, j))}{\sum_{k=1}^{n} \exp(\text{score}(i, k))}

使用Softmax归一化,确保权重和为1

3. 计算输出#

oi=j=1nαijvj\pmb{o}_i = \sum_{j=1}^{n} \alpha_{ij} \pmb{v}_j

输出是所有Value的加权和

矩阵形式#

Attention(Q,K,V)=softmax(QKdk)V\text{Attention}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{softmax}\left(\frac{\pmb{Q}\pmb{K}^\top}{\sqrt{d_k}}\right)\pmb{V}

为什么需要缩放?#

  • dkd_k很大时,点积qk\pmb{q} \cdot \pmb{k}的值可能很大
  • 导致Softmax函数进入饱和区,梯度很小
  • 除以dk\sqrt{d_k}可以将点积的方差稳定在1左右

矩阵运算视角:并行计算的关键#

前面我们通过单个词的视角(For loop逻辑)解释了Self-Attention的计算过程。但在实际实现中,我们不需要对每个词写循环,而是一次性通过矩阵乘法算出整个句子的注意力分布。这就是GPU加速的物理基础。

从向量到矩阵#

假设输入序列有nn个词,每个词的嵌入维度是dd

输入矩阵

IRn×d\pmb{I} \in \mathbb{R}^{n \times d}
  • 每一行是一个词的向量
  • 例如:n=4n=4(4个词),d=512d=512(每个词512维)

第一步:生成Q、K、V矩阵#

通过三个权重矩阵,一次性将所有词转换为Q、K、V:

Self-Attention完整计算流程:从输入到Q-K-V生成、注意力分数计算、Softmax归一化、加权求和输出

Q=IWQRn×dkK=IWKRn×dkV=IWVRn×dv\begin{aligned} \pmb{Q} &= \pmb{I} \pmb{W}^Q \in \mathbb{R}^{n \times d_k} \\ \pmb{K} &= \pmb{I} \pmb{W}^K \in \mathbb{R}^{n \times d_k} \\ \pmb{V} &= \pmb{I} \pmb{W}^V \in \mathbb{R}^{n \times d_v} \end{aligned}
  • WQ,WKRd×dk\pmb{W}^Q, \pmb{W}^K \in \mathbb{R}^{d \times d_k}:Query和Key的投影矩阵
  • WVRd×dv\pmb{W}^V \in \mathbb{R}^{d \times d_v}:Value的投影矩阵
  • 通常 dk=dv=dd_k = d_v = d

关键点:一次矩阵乘法,所有词的Q、K、V同时计算完成!

第二步:计算注意力分数矩阵#

Query与Key的点积计算:每个Query向量与所有Key向量计算相似度得到注意力分数

注意力分数矩阵的计算:Q·K^T一次性得到所有位置之间的相似度矩阵

A=QKdkRn×n\pmb{A} = \frac{\pmb{Q} \pmb{K}^\top}{\sqrt{d_k}} \in \mathbb{R}^{n \times n}

这一步的物理意义

  • QK\pmb{Q} \pmb{K}^\top:所有Query和所有Key的点积,一次性算出!
  • 矩阵A\pmb{A}的第(i,j)(i, j)元素 = 第ii个词的Query与第jj个词的Key的相似度
  • 例如:A2,3\pmb{A}_{2,3}表示第2个词对第3个词的注意力分数

可视化

K₁ K₂ K₃ K₄
Q₁ [ a₁₁ a₁₂ a₁₃ a₁₄ ]
Q₂ [ a₂₁ a₂₂ a₂₃ a₂₄ ]
Q₃ [ a₃₁ a₃₂ a₃₃ a₃₄ ]
Q₄ [ a₄₁ a₄₂ a₄₃ a₄₄ ]
  • 第2行表示:词2对所有词的注意力分数
  • 第3列表示:所有词对词3的注意力分数

第三步:Softmax归一化#

Softmax归一化:将注意力分数转换为概率分布,确保权重和为1

对矩阵A\pmb{A}每一行进行Softmax:

A=softmax(A)Rn×n\pmb{A}' = \text{softmax}(\pmb{A}) \in \mathbb{R}^{n \times n}
  • 每一行的权重和为1
  • ii行表示:词ii对所有词的注意力权重分布

第四步:加权求和得到输出#

加权求和输出:用注意力权重对所有Value向量进行加权求和,得到融合上下文信息的输出

O=AVRn×dv\pmb{O} = \pmb{A}' \pmb{V} \in \mathbb{R}^{n \times d_v}

物理意义

  • O\pmb{O}的第ii行 = 第ii个词的输出向量
  • 它是所有Value向量的加权和,权重由A\pmb{A}'的第ii行决定

完整的矩阵形式#

将上述步骤合并:

Attention(Q,K,V)=softmax(QKdk)V\text{Attention}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{softmax}\left(\frac{\pmb{Q}\pmb{K}^\top}{\sqrt{d_k}}\right)\pmb{V}

Self-Attention的矩阵运算全景图:从输入矩阵I到Q-K-V矩阵生成、注意力矩阵A计算、最终输出O的完整流程

GPU加速的本质#

为什么矩阵运算这么重要?

  1. 并行计算
    • 矩阵乘法可以在GPU上高度并行
    • 所有词的注意力同时计算,不需要循环
  2. 硬件优化
    • 现代GPU针对矩阵乘法做了专门优化(如NVIDIA的Tensor Core)
    • 一次矩阵乘法比nn次向量运算快得多
  3. 计算复杂度
    • 时间复杂度:O(n2d)O(n^2 \cdot d)
    • 空间复杂度:O(n2)O(n^2)(存储注意力矩阵)
    • 虽然是O(n2)O(n^2),但可以充分利用GPU并行能力

我们不需要对每个词写循环,而是一次性通过矩阵乘法算出整个句子的注意力分布,这就是GPU加速的物理基础。

三、多头注意力(Multi-Head Attention)#

动机:为什么需要多个”头”?#

单个Self-Attention就像一个人从一个角度看问题,可能会遗漏重要信息。多头注意力就像多个专家从不同角度同时分析

单头的局限性#

假设处理句子”那只在公园里奔跑的狗很可爱”:

  • 单个注意力头可能只关注语法关系:“狗”和”奔跑”
  • 但可能忽略修饰关系:“狗”和”可爱”
  • 也可能忽略位置信息:“狗”和”公园”

多头的优势#

多头注意力让模型同时从多个角度理解:

  • 头1:关注语法关系(主谓宾)
  • 头2:关注修饰关系(形容词-名词)
  • 头3:关注位置关系(地点-动作)
  • 头4:关注语义相似性

形象比喻:就像一个团队讨论问题,每个人负责不同的视角,最后综合所有人的意见做出决策。

深层原因:相关性的定义不止一种#

核心洞察:什么叫”相关”?这个问题没有唯一答案!

例子1:天涯若比邻#

考虑句子:“虽然我们相隔万里,但心灵相通”

  • 位置上的相关性:“我们”和”万里”(物理距离)
    • 如果只有一个头,可能会认为”我们”和”万里”不相关(距离太远)
  • 语义上的相关性:“我们”和”心灵相通”(情感联系)
    • 另一个头可能会发现”我们”和”心灵相通”高度相关(语义紧密)

矛盾:同样是”我们”这个词,在不同的相关性定义下,应该关注的对象完全不同!

例子2:一词多义#

考虑句子:“我在银行工作,银行就在河岸边”

  • 第一个”银行”
    • 头1(职业相关):关注”工作” → 金融机构
    • 头2(位置相关):关注”河岸” → 可能产生混淆
  • 第二个”银行”
    • 头1(职业相关):关注”工作” → 可能产生混淆
    • 头2(位置相关):关注”河岸” → 地理位置

解决方案:多个头可以同时捕捉不同类型的相关性,避免单一视角的局限。

子空间投影的数学本质#

为什么多头能够学习不同的相关性?关键在于子空间投影

单头的问题#

假设模型维度是512维:

  • 所有词都在同一个512维空间中
  • 计算相似度时,所有维度混在一起
  • 无法区分”语法相关”和”语义相关”

多头的解决方案#

使用8个头,每个头64维:

  • 头1:将512维投影到第1个64维子空间
    • 这个子空间专门学习”语法关系”
    • W1Q,W1K\pmb{W}_1^Q, \pmb{W}_1^K将输入映射到”语法子空间”
  • 头2:将512维投影到第2个64维子空间
    • 这个子空间专门学习”语义关系”
    • W2Q,W2K\pmb{W}_2^Q, \pmb{W}_2^K将输入映射到”语义子空间”
  • 头3-8:其他子空间,学习其他类型的相关性

关键点

  • 不同的投影矩阵WiQ,WiK\pmb{W}_i^Q, \pmb{W}_i^K将输入映射到不同的子空间
  • 在不同子空间中,“相关性”的定义不同
  • 这样可以避免不同类型的相关性互相干扰

形象比喻:就像把一个复杂的问题分解成多个简单的子问题,每个专家负责一个子问题,最后综合所有专家的答案。

数学定义#

1. 多个注意力头#

每个头使用不同的权重矩阵,学习不同的表示:

headi=Attention(QWiQ,KWiK,VWiV)\text{head}_i = \text{Attention}(\pmb{Q}\pmb{W}_i^Q, \pmb{K}\pmb{W}_i^K, \pmb{V}\pmb{W}_i^V)
  • WiQ,WiK,WiV\pmb{W}_i^Q, \pmb{W}_i^K, \pmb{W}_i^V:第ii个头的投影矩阵(每个头都不同)
  • 每个头独立计算注意力,关注不同的模式

2. 拼接与投影#

将所有头的输出拼接起来,再通过一个线性变换:

MultiHead(Q,K,V)=Concat(head1,...,headh)WO\text{MultiHead}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{Concat}(\text{head}_1, ..., \text{head}_h)\pmb{W}^O
  • hh:注意力头的数量(通常为8或16)
  • WO\pmb{W}^O:输出投影矩阵,将拼接后的向量映射回原始维度

3. 维度分配#

假设模型维度为512,使用8个头:

  • 每个头的维度 = 512 / 8 = 64
  • 8个头并行计算,每个头处理64维的子空间
  • 拼接后恢复到512维

4. 分割与拼接的深层原理#

总维度守恒定律:不是增加,而是分散#

常见误解:多头注意力是不是增加了8倍的计算量?

实际情况:不是!多头机制的设计遵循**“总维度守恒”**原则。

单头 vs 多头的参数量对比

假设模型维度 dmodel=512d_{model} = 512

单头方案

  • WQ,WK,WV\pmb{W}^Q, \pmb{W}^K, \pmb{W}^V:每个都是 512×512512 \times 512
  • 总参数量:3×512×512=786,4323 \times 512 \times 512 = 786,432

8头方案

  • 每个头的维度:dk=512/8=64d_k = 512 / 8 = 64
  • 每个头的 WiQ,WiK,WiV\pmb{W}_i^Q, \pmb{W}_i^K, \pmb{W}_i^V512×64512 \times 64
  • 8个头的总参数量:8×3×512×64=786,4328 \times 3 \times 512 \times 64 = 786,432

结论:参数量完全相同!

核心思想

  • 多头机制不是为了增加计算量(那样会变慢)
  • 而是为了强制模型把计算能力”分散”到不同的子空间
  • 就像”不要把鸡蛋放在同一个篮子里”

形象比喻

  • 单头:一个全能专家,什么都看,但可能什么都看不深
  • 多头:8个专业专家,每人负责一个领域,分工明确

合并后的”信息搅拌”:W^O的关键作用#

拼接不够,还需要混合!

在多头注意力的公式中,有一个容易被忽略但极其重要的矩阵:WO\pmb{W}^O(输出投影矩阵)。

MultiHead(Q,K,V)=Concat(head1,...,headh)WO\text{MultiHead}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{Concat}(\text{head}_1, ..., \text{head}_h)\pmb{W}^O

为什么拼接后还需要W^O?

假设我们只做拼接(Concat),不做W^O:

Head 1 输出: [第1-64维] → 只包含"语法关系"信息
Head 2 输出: [第65-128维] → 只包含"位置关系"信息
Head 3 输出: [第129-192维] → 只包含"语义关系"信息
...
拼接结果: [第1-512维]

问题

  • 第1-64维永远只包含Head 1的信息
  • 第65-128维永远只包含Head 2的信息
  • 不同头的发现无法交互

W^O的作用:信息搅拌

WOR512×512\pmb{W}^O \in \mathbb{R}^{512 \times 512} 是一个巨大的线性变换矩阵:

output=Concat(heads)WO\pmb{output} = \text{Concat}(\text{heads}) \cdot \pmb{W}^O

效果

  • 输出的每一维都是所有头的加权组合
  • Head 1的”语法发现”和Head 2的”位置发现”可以交互
  • 就像把8个专家的报告融合成一份综合报告

形象比喻

  • 只拼接:8个专家各写各的报告,装订成一本书,但内容互不相关
  • 拼接+W^O:8个专家开会讨论,综合所有观点,写出一份融合报告

数学直觉

  • 拼接:[h1,h2,...,h8][h_1, h_2, ..., h_8] → 简单的”并排放置”
  • W^O:矩阵乘法 → 每个输出维度都是所有输入维度的线性组合
  • 结果:信息充分混合,不同视角的发现可以相互增强

为什么”浓缩”有效?降维打击的智慧#

核心问题:为什么把512维压缩到64维反而更有效?

直观理解:高维的诅咒

高维空间太空旷

  • 在512维空间中,数据点之间的距离都很大
  • 就像在一个巨大的仓库里找东西,到处都是空地
  • 很难发现数据的规律和模式

低维空间抓特征

  • 把向量投影到64维的”语法子空间”
    • 只保留与语法相关的信息
    • 主谓宾关系变得清晰可见
  • 把向量投影到64维的”位置子空间”
    • 只保留与位置相关的信息
    • 相邻关系一目了然

形象比喻

高维空间(512维)

  • 就像一个巨大的图书馆,书籍按512种标准分类
  • 你想找”语法相关”的书,但书架上混杂着各种主题
  • 信号被噪声淹没

低维子空间(64维)

  • 就像一个专题书架,只放”语法”相关的书
  • 去掉了无关的维度(颜色、大小、价格…)
  • 只保留了关键维度(主语、谓语、宾语…)
  • 规律清晰可见

数学直觉

投影到子空间的过程:

qi=xWiQR64\pmb{q}_i = \pmb{x} \pmb{W}_i^Q \in \mathbb{R}^{64}
  • WiQR512×64\pmb{W}_i^Q \in \mathbb{R}^{512 \times 64}:投影矩阵
  • 作用:从512维的”全空间”投影到64维的”语法子空间”
  • 效果:过滤掉无关信息,突出关键特征

关键洞察

  • 不是维度越高越好(高维会导致数据稀疏)
  • 不是维度越低越好(低维会丢失信息)
  • 最优策略:把高维空间分解成多个低维子空间,每个子空间专注一个方面

类比机器学习中的降维

  • PCA:找到数据的主成分(低维表示)
  • 多头注意力:找到多个主成分(多个低维子空间)
  • 区别:PCA是无监督的,多头注意力是通过训练学习的

完整的计算流程总结#

结合以上理解,多头注意力的完整流程是:

  1. 分割(投影到子空间)headi=Attention(QWiQ,KWiK,VWiV)\text{head}_i = \text{Attention}(\pmb{Q}\pmb{W}_i^Q, \pmb{K}\pmb{W}_i^K, \pmb{V}\pmb{W}_i^V)
    • 每个头独立工作在自己的64维子空间
    • 总参数量保持不变(守恒定律)
  2. 并行计算
    • 8个头同时计算注意力
    • 每个头关注不同的模式
  3. 拼接Concat(head1,...,head8)R512\text{Concat}(\text{head}_1, ..., \text{head}_8) \in \mathbb{R}^{512}
    • 简单地把8个64维向量排成一个512维向量
  4. 信息搅拌(W^O)MultiHead(Q,K,V)=Concat(heads)WO\text{MultiHead}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{Concat}(\text{heads})\pmb{W}^O
    • 让不同头的发现交互融合
    • 生成最终的512维输出

金句:多头注意力不是”做更多”,而是”做得更专注”——通过分工合作,让每个头在自己的子空间里成为专家。

不同头学到的模式#

研究发现,不同的头会自动学习关注不同的语言现象:

头1:局部依赖#

  • 关注相邻词之间的关系
  • 例如:“机器 学习” → “机器”主要关注”学习”

头2:长距离依赖#

  • 关注句子开头和结尾的呼应
  • 例如:“如果…那么…” 结构

头3:语法关系#

  • 关注主谓宾等语法结构
  • 例如:“狗”关注”奔跑”(主语-谓语)

头4:语义相似性#

  • 关注语义相关的词
  • 例如:“深度”关注”学习”(构成专业术语)

关键洞察:这些模式不是人为设计的,而是模型在训练过程中自动学习出来的!

多头注意力 vs 多层注意力#

在Transformer架构中,有两个容易混淆的概念:多头注意力(Multi-Head Attention)多层注意力(Multi-Layer Attention)。它们分别从横向和纵向两个维度扩展模型的能力。

核心区别#

对比维度多头注意力 (Multi-Head)多层注意力 (Multi-Layer)
维度横向 (Width)纵向 (Depth)
核心逻辑并行 (Parallel):同时看不同的方面串行 (Sequential):一步步深入处理
数学操作向量切分 → 独立计算 → 拼接向量输入 → 计算 → 输出 → 非线性变换(FFN) → 下一层
解决痛点解决信息单一性:避免只关注一种关系解决表达能力不足:让模型学会复杂推理
生物学类比视觉皮层同时处理颜色、形状、运动视网膜 → V1区 → V2区 → … → 大脑皮层决策

详细说明#

多头注意力(横向扩展)#

工作方式

  • 同一层内,将注意力机制复制多份
  • 每个头独立计算,关注不同的模式
  • 最后将所有头的输出拼接起来

形象比喻

  • 就像一个团队同时从不同角度分析同一个问题
  • 所有人在同一时间工作,最后汇总结果

数学表示

MultiHead(Q,K,V)=Concat(head1,...,headh)WO\text{MultiHead}(\pmb{Q}, \pmb{K}, \pmb{V}) = \text{Concat}(\text{head}_1, ..., \text{head}_h)\pmb{W}^O

多层注意力(纵向扩展)#

工作方式

  • 将多个Transformer Block堆叠起来
  • 每一层的输出作为下一层的输入
  • 逐层提取更抽象的特征

形象比喻

  • 就像流水线,每个工人负责一道工序
  • 前一个工人完成后,后一个工人才能开始

数学表示

h(1)=TransformerBlock1(x)h(2)=TransformerBlock2(h(1))h(L)=TransformerBlockL(h(L1))\begin{aligned} \pmb{h}^{(1)} &= \text{TransformerBlock}_1(\pmb{x}) \\ \pmb{h}^{(2)} &= \text{TransformerBlock}_2(\pmb{h}^{(1)}) \\ &\vdots \\ \pmb{h}^{(L)} &= \text{TransformerBlock}_L(\pmb{h}^{(L-1)}) \end{aligned}

为什么两者都需要?#

多头注意力

  • 提供多样性:从不同角度理解输入
  • 类似于”横向思维”:同时考虑多个可能性

多层注意力

  • 提供深度:逐层抽象,学习复杂模式
  • 类似于”纵向思维”:从具体到抽象,层层递进

结合效果

  • 横向:每一层都有多个头,提供丰富的视角
  • 纵向:多层堆叠,逐步提取高级特征
  • 就像一个多层多头的金字塔,既宽又深

实际应用#

BERT(12层,12头)

  • 12个Transformer Block(纵向)
  • 每个Block有12个注意力头(横向)
  • 总共144个注意力计算单元

GPT-3(96层,96头)

  • 96个Transformer Block(纵向)
  • 每个Block有96个注意力头(横向)
  • 总共9216个注意力计算单元

关键洞察:多头和多层是正交的设计,分别从宽度和深度两个维度增强模型的表达能力。

四、位置编码(Positional Encoding)#

为什么需要位置编码?#

Self-Attention有一个严重的问题:它不知道词的顺序

位置无关性的问题#

考虑以下两个句子:

  • “狗 咬 人”
  • “人 咬 狗”

对于Self-Attention来说:

  • 两个句子包含相同的词:{狗, 咬, 人}
  • 计算注意力时,只看词与词之间的相关性
  • 无论顺序如何,计算结果完全相同

但显然,这两个句子的意思完全不同:

  • “狗咬人”:狗是施动者
  • “人咬狗”:人是施动者(这是新闻!)

为什么Self-Attention是位置无关的?#

回顾Self-Attention的计算过程:

  1. 计算Q·K的相似度
  2. 用Softmax归一化
  3. 对V加权求和

关键问题:这个过程中,没有任何地方使用了位置信息!

  • “狗”在第1个位置还是第3个位置,它的Q、K、V向量都一样
  • 就像一个袋子里装着词,不管怎么摇晃,结果都不变(Bag of Words

解决方案:位置编码#

既然Self-Attention不知道位置,我们就人为地告诉它

核心思想:给每个位置添加一个独特的”位置标记”

  • 位置1的词 = 原始词向量 + 位置1的编码
  • 位置2的词 = 原始词向量 + 位置2的编码
  • 位置3的词 = 原始词向量 + 位置3的编码

这样,即使是相同的词,在不同位置也会有不同的表示。

位置编码方案#

1. 正弦位置编码(Sinusoidal)#

Transformer原论文使用的方案,使用正弦和余弦函数:

PE(pos,2i)=sin(pos100002i/d)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right)PE(pos,2i+1)=cos(pos100002i/d)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)

参数说明

  • pospos:位置索引(0, 1, 2, …)
  • ii:维度索引(0, 1, 2, …)
  • dd:模型维度(如512)

为什么用正弦/余弦?

  • 不同位置产生不同的编码向量
  • 可以处理任意长度的序列(不需要预先设定最大长度)
  • 相对位置关系可以通过三角函数性质表示

2. 可学习位置编码#

另一种方案是将位置编码作为可训练参数:

  • 为每个位置学习一个独特的向量
  • 优点:更灵活,可以学习到最适合任务的位置表示
  • 缺点:需要预先设定最大序列长度

位置编码的使用#

将位置编码到输入嵌入上:

xi=xi+PEi\pmb{x}_i' = \pmb{x}_i + \pmb{PE}_i

示例

原始输入: ["狗", "咬", "人"]
词嵌入: [v_狗, v_咬, v_人]
加上位置编码:
位置0: v_狗 + PE_0
位置1: v_咬 + PE_1
位置2: v_人 + PE_2

现在,即使”狗”和”人”交换位置,它们的表示也会不同,Self-Attention就能区分”狗咬人”和”人咬狗”了!

关键洞察#

  • 位置编码是必需的:没有它,Self-Attention就是一个”词袋模型”
  • 加法而非拼接:位置编码通过加法融入词向量,而不是拼接
  • 一次性添加:位置编码只在输入层添加一次,后续层会自动传播这个信息

深度思考:为什么是相加而不是拼接?#

这是一个非常常见的疑问:为什么位置编码要到词向量上,而不是拼接在一起?

直觉上的困惑#

拼接方案看起来更自然:

词向量: [x₁, x₂, ..., x_d] (d维)
位置编码: [p₁, p₂, ..., p_d] (d维)
拼接结果: [x₁, x₂, ..., x_d, p₁, p₂, ..., p_d] (2d维)

相加方案看起来很奇怪:

词向量: [x₁, x₂, ..., x_d] (d维)
位置编码: [p₁, p₂, ..., p_d] (d维)
相加结果: [x₁+p₁, x₂+p₂, ..., x_d+p_d] (d维)

疑问:相加会不会让词向量和位置编码混在一起,无法区分?

数学上的等价性#

关键洞察:在经过线性变换后,相加和拼接在数学上是等价的!

拼接方案#

假设拼接后的向量是 [x,p]R2d[\pmb{x}, \pmb{p}] \in \mathbb{R}^{2d},经过权重矩阵 WR2d×dk\pmb{W} \in \mathbb{R}^{2d \times d_k}

q=[x,p]W\pmb{q} = [\pmb{x}, \pmb{p}] \pmb{W}

我们可以把 W\pmb{W} 分成两块:

W=[WxWp]\pmb{W} = \begin{bmatrix} \pmb{W}_x \\ \pmb{W}_p \end{bmatrix}

其中 WxRd×dk\pmb{W}_x \in \mathbb{R}^{d \times d_k} 对应词向量部分,WpRd×dk\pmb{W}_p \in \mathbb{R}^{d \times d_k} 对应位置编码部分。

那么:

q=[x,p][WxWp]=xWx+pWp\pmb{q} = [\pmb{x}, \pmb{p}] \begin{bmatrix} \pmb{W}_x \\ \pmb{W}_p \end{bmatrix} = \pmb{x} \pmb{W}_x + \pmb{p} \pmb{W}_p

相加方案#

假设相加后的向量是 x+pRd\pmb{x} + \pmb{p} \in \mathbb{R}^d,经过权重矩阵 WRd×dk\pmb{W}' \in \mathbb{R}^{d \times d_k}

q=(x+p)W=xW+pW\pmb{q}' = (\pmb{x} + \pmb{p}) \pmb{W}' = \pmb{x} \pmb{W}' + \pmb{p} \pmb{W}'

对比#

  • 拼接方案q=xWx+pWp\pmb{q} = \pmb{x} \pmb{W}_x + \pmb{p} \pmb{W}_p
  • 相加方案q=xW+pW\pmb{q}' = \pmb{x} \pmb{W}' + \pmb{p} \pmb{W}'

结论:两种方案的形式完全一样!只是拼接方案用了两个独立的权重矩阵 Wx\pmb{W}_xWp\pmb{W}_p,而相加方案用了同一个权重矩阵 W\pmb{W}'

相加方案的优势#

既然数学上等价,为什么选择相加?

1. 参数效率#

  • 拼接:输入维度变成 2d2d,所有权重矩阵的参数量翻倍
  • 相加:输入维度保持 dd,参数量不变

2. 维度一致性#

  • 拼接:输入维度 2d2d,后续层的输入输出维度不一致,需要额外处理
  • 相加:所有层的维度保持一致,架构更简洁

3. 信息融合#

  • 拼接:词向量和位置编码在物理上分离,需要通过权重矩阵学习如何融合
  • 相加:词向量和位置编码在输入时就已经融合,模型可以直接使用

直觉理解#

高维空间的魔法

  • 在高维空间(如512维)中,两个向量相加后,原始信息并不会丢失
  • 通过不同的投影矩阵(WQ,WK,WV\pmb{W}^Q, \pmb{W}^K, \pmb{W}^V),模型可以提取出词向量和位置编码的不同组合
  • 就像把两种颜料混合,虽然看起来是一种颜色,但通过光谱分析仍然可以分离出原始成分

形象比喻

  • 拼接:把两本书并排放在一起,左边是内容,右边是页码
  • 相加:把页码直接印在每一页的内容上,内容和页码融为一体

虽然”相加”看起来会混淆信息,但在高维空间中,通过线性变换可以灵活地提取和组合信息,因此不会造成信息损失。

五、Self-Attention与其他模型的对比#

vs RNN:从顺序到并行#

自注意力与循环神经网络对比

特性RNN/LSTMSelf-Attention
并行化不可并行(顺序处理)完全并行
长距离依赖O(n)步传播O(1)直接连接
计算复杂度O(n·d²)O(n²·d)
内存需求O(n·d)O(n²)
位置信息隐式编码需要显式编码

适用场景#

  • 短序列:Self-Attention更优(并行化优势)
  • 长序列:需要考虑O(n²)的复杂度,可能需要稀疏注意力
  • 实时处理:RNN可以流式处理,Self-Attention需要完整序列

vs CNN:从局部到全局#

卷积神经网络的函数集与自注意力的函数集的关系

CNN是受限的Self-Attention#

核心观点:CNN可以看作是一种受限的Self-Attention,它只关注固定的局部窗口。

感受野的差异#

CNN的局部感受野

  • 卷积核大小固定(如3×3)
  • 每个位置只能看到固定窗口内的邻居
  • 需要堆叠多层才能扩大感受野
  • 例如:3层3×3卷积才能看到7×7的范围

Self-Attention的全局感受野

  • 每个位置可以看到整个序列
  • 第一层就能建立任意距离的连接
  • 感受野是动态的,由注意力权重决定

灵活性的差异#

CNN的固定模式

卷积核: [w₁, w₂, w₃]
输出 = w₁·x_{i-1} + w₂·x_i + w₃·x_{i+1}
  • 权重是固定的,对所有位置使用相同的卷积核
  • 无法根据内容调整

Self-Attention的动态模式

注意力权重: [α₁, α₂, ..., αₙ]
输出 = α₁·v₁ + α₂·v₂ + ... + αₙ·vₙ
  • 权重是动态计算的,根据内容自适应调整
  • 可以看作”可学习的卷积核”

形象比喻#

  • CNN:戴着固定焦距的眼镜,只能看清固定范围内的东西
  • Self-Attention:拥有可变焦的镜头,可以自由选择关注远处还是近处

归纳偏置 vs 数据需求#

CNN的优势(归纳偏置强)

  • 内置了局部性平移不变性的先验知识
  • 参数量少,训练效率高
  • 数据量少时表现更好

Self-Attention的优势(上限高)

  • 没有强制的归纳偏置,更灵活
  • 可以学习到任意复杂的模式
  • 数据量大时表现更好

实验结论(Vision Transformer)#

自注意力与卷积神经网络对比 根据Vision Transformer(ViT)的研究:

  • 小数据集(ImageNet):CNN(如ResNet)表现更好
  • 大数据集(JFT-300M):Transformer超越CNN
  • 关键因素:数据量是否足够让模型学习到有效的归纳偏置

金句:CNN是人类设计的归纳偏置,Self-Attention是数据驱动的归纳偏置。

vs GNN:从稀疏图到全连接图#

alt text

Self-Attention就是全连接的GNN#

核心观点:Self-Attention可以看作是一个全连接图神经网络(Fully Connected GNN)。

图的视角#

将序列看作图

  • 每个Token是一个节点
  • 注意力权重是边的权重
  • Self-Attention的输出是节点特征的加权聚合

数学形式

hi(l+1)=j=1nαijhj(l)\pmb{h}_i^{(l+1)} = \sum_{j=1}^{n} \alpha_{ij} \pmb{h}_j^{(l)}
  • hi(l)\pmb{h}_i^{(l)}:第ll层节点ii的特征
  • αij\alpha_{ij}:节点ii到节点jj的边权重(注意力权重)
  • 这正是GNN的消息传递机制!

图结构的差异#

GNN的稀疏图

  • 只在有边的节点之间传递信息
  • 图结构是预定义的(如社交网络、分子结构)
  • 边的数量通常是O(n)O(n)(稀疏图)

Self-Attention的全连接图

  • 所有节点之间都有边(全连接)
  • 图结构是动态的,由注意力权重决定
  • 边的数量是O(n2)O(n^2)(稠密图)

适用场景#

GNN适合

  • 图结构已知且稀疏(如社交网络、知识图谱)
  • 需要利用图的拓扑结构

Self-Attention适合

  • 图结构未知,需要模型自己学习
  • 序列数据(可以看作全连接图)

统一视角#

三者的统一

  • CNN:局部连接的图(每个节点只连接邻居)
  • GNN:稀疏连接的图(根据预定义结构连接)
  • Self-Attention:全连接的图(所有节点互相连接)

灵活性递增:CNN < GNN < Self-Attention 计算复杂度递增:CNN < GNN < Self-Attention 数据需求递增:CNN < GNN < Self-Attention

六、PyTorch实现示例#

基础Self-Attention实现#

import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttention(nn.Module):
def __init__(self, embed_dim):
super(SelfAttention, self).__init__()
self.embed_dim = embed_dim
# Query, Key, Value投影矩阵
self.query = nn.Linear(embed_dim, embed_dim)
self.key = nn.Linear(embed_dim, embed_dim)
self.value = nn.Linear(embed_dim, embed_dim)
self.scale = embed_dim ** 0.5
def forward(self, x):
# x shape: (batch_size, seq_len, embed_dim)
batch_size, seq_len, embed_dim = x.size()
# 计算Q, K, V
Q = self.query(x) # (batch_size, seq_len, embed_dim)
K = self.key(x) # (batch_size, seq_len, embed_dim)
V = self.value(x) # (batch_size, seq_len, embed_dim)
# 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) / self.scale
# scores shape: (batch_size, seq_len, seq_len)
# 计算注意力权重
attention_weights = F.softmax(scores, dim=-1)
# 计算输出
output = torch.matmul(attention_weights, V)
# output shape: (batch_size, seq_len, embed_dim)
return output, attention_weights
# 使用示例
embed_dim = 128
seq_len = 10
batch_size = 32
model = SelfAttention(embed_dim)
x = torch.randn(batch_size, seq_len, embed_dim)
output, attention_weights = model(x)
print(f"输出形状: {output.shape}")
print(f"注意力权重形状: {attention_weights.shape}")

多头注意力实现#

class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(MultiHeadAttention, self).__init__()
assert embed_dim % num_heads == 0, "embed_dim必须能被num_heads整除"
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
# Q, K, V投影
self.qkv = nn.Linear(embed_dim, embed_dim * 3)
# 输出投影
self.out_proj = nn.Linear(embed_dim, embed_dim)
self.scale = self.head_dim ** 0.5
def forward(self, x):
batch_size, seq_len, embed_dim = x.size()
# 计算Q, K, V并分割成多个头
qkv = self.qkv(x) # (batch_size, seq_len, embed_dim * 3)
qkv = qkv.reshape(batch_size, seq_len, 3, self.num_heads, self.head_dim)
qkv = qkv.permute(2, 0, 3, 1, 4) # (3, batch_size, num_heads, seq_len, head_dim)
Q, K, V = qkv[0], qkv[1], qkv[2]
# 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) / self.scale
attention_weights = F.softmax(scores, dim=-1)
# 计算输出
output = torch.matmul(attention_weights, V)
# output shape: (batch_size, num_heads, seq_len, head_dim)
# 合并多个头
output = output.transpose(1, 2).contiguous()
output = output.reshape(batch_size, seq_len, embed_dim)
# 输出投影
output = self.out_proj(output)
return output, attention_weights
# 使用示例
embed_dim = 128
num_heads = 8
seq_len = 10
batch_size = 32
model = MultiHeadAttention(embed_dim, num_heads)
x = torch.randn(batch_size, seq_len, embed_dim)
output, attention_weights = model(x)
print(f"输出形状: {output.shape}")
print(f"注意力权重形状: {attention_weights.shape}")

实际应用中的增强:残差连接与层归一化#

上面的代码实现了Self-Attention的核心机制,但在实际应用(如Transformer)中,Self-Attention输出后通常会接残差连接(Residual Connection)层归一化(Layer Normalization),以帮助深层网络训练。

为什么需要这些增强?#

1. 残差连接(Residual Connection)#

问题:深层网络容易出现梯度消失/爆炸,导致训练困难。

解决方案:将输入直接加到输出上

output=SelfAttention(x)+x\pmb{output} = \text{SelfAttention}(\pmb{x}) + \pmb{x}

作用

  • 提供梯度的”高速公路”,让梯度可以直接传播
  • 即使Self-Attention学不到有用的信息,至少可以保留输入
  • 使得堆叠多层Self-Attention成为可能

2. 层归一化(Layer Normalization)#

问题:不同层的激活值分布可能差异很大,导致训练不稳定。

解决方案:对每个样本的特征进行归一化

LayerNorm(x)=γxμσ+β\text{LayerNorm}(\pmb{x}) = \gamma \frac{\pmb{x} - \mu}{\sigma} + \beta

作用

  • 稳定训练过程
  • 加速收敛
  • 减少对学习率的敏感性

Transformer Block的标准结构#

在Transformer中,一个标准的Self-Attention Block包含:

class TransformerBlock(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim, dropout=0.1):
super(TransformerBlock, self).__init__()
# Multi-Head Self-Attention
self.attention = MultiHeadAttention(embed_dim, num_heads)
# Layer Normalization
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
# Feed-Forward Network
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, embed_dim)
)
# Dropout
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# Self-Attention + 残差连接 + LayerNorm
attn_output, _ = self.attention(x)
x = self.norm1(x + self.dropout(attn_output))
# Feed-Forward + 残差连接 + LayerNorm
ffn_output = self.ffn(x)
x = self.norm2(x + self.dropout(ffn_output))
return x

关键点

  • Add & Norm:每个子层(Self-Attention、FFN)后都有残差连接和LayerNorm
  • 顺序:有两种常见顺序
    • Post-LN:LayerNorm(x + Sublayer(x))(原始Transformer)
    • Pre-LN:x + Sublayer(LayerNorm(x))(更稳定,现代实现常用)

为什么这些不属于Self-Attention本身?#

  • Self-Attention:核心的注意力机制,负责信息聚合
  • 残差连接和LayerNorm:训练技巧,帮助深层网络优化
  • 它们是正交的:Self-Attention可以单独使用,但在深层网络中需要这些技巧

类比:Self-Attention是引擎,残差连接和LayerNorm是润滑油和冷却系统,让引擎在高负荷下稳定运行。

七、注意力可视化与解释#

注意力权重的含义#

  • 注意力权重矩阵是n×nn \times n的,其中nn是序列长度
  • ii行表示位置ii对其他所有位置的注意力分布
  • 权重越大,表示该位置对当前位置的影响越大

常见的注意力模式#

  1. 局部注意力:主要关注相邻位置
  2. 全局注意力:关注整个序列
  3. 语法注意力:关注有语法关系的词(如主语-谓语)

八、Self-Attention的变体#

1. Masked Self-Attention#

  • 用于解码器,防止看到未来信息
  • 通过掩码矩阵将未来位置的注意力分数设为-\infty

2. Cross-Attention#

  • Query来自一个序列,Key和Value来自另一个序列
  • 用于Seq2Seq模型的编码器-解码器连接

3. 稀疏注意力#

  • 减少O(n²)的复杂度
  • 只计算部分位置之间的注意力

九、实践建议#

超参数选择#

  • 注意力头数:通常为8或16
  • 头维度:embed_dim / num_heads,通常为64
  • Dropout:在注意力权重上应用,防止过拟合

训练技巧#

  1. 学习率预热:开始时使用较小的学习率
  2. 层归一化:在每个子层之后应用
  3. 残差连接:帮助梯度传播

常见问题#

  • 内存不足:减少batch size或序列长度
  • 训练不稳定:检查学习率和初始化
  • 注意力退化:所有位置的权重都接近均匀分布

十、总结#

Self-Attention机制是深度学习领域的重要突破,它:

  • 解决了RNN的顺序处理限制
  • 提供了更直接的长距离依赖建模
  • 成为Transformer架构的核心组件

虽然Self-Attention有O(n²)的复杂度限制,但其并行化能力和建模能力使其在NLP、CV等领域取得了巨大成功。


致谢#

本笔记在学习李宏毅老师的深度学习课程(LeeDL)时完成。感谢李宏毅老师对Self-Attention机制的精彩讲解,特别是对Query、Key、Value的直观解释和注意力可视化的展示。

相关资源:

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

自注意力机制(Self-Attention)
https://castorice.xin/posts/自注意力机制/
作者
castorice
发布于
2025-09-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时