https://metaso.cn/s/tA0L08A

一、基本原理

  1. 核心思想
    注意力机制通过对输入数据的不同部分赋予差异化的权重,实现信息的选择性聚焦。其本质可概括为:根据查询(Query)与键(Key)的相似性计算权重,再对值(Value)进行加权求和。

  2. 计算过程

  • 步骤1:将输入数据映射为键(Key)、值(Value)和查询(Query)三个矩阵。
  • 步骤2:通过评分函数(如点积或加法模型)计算Query与Key的相似性得分。
  • 步骤3:使用Softmax对得分归一化,得到注意力权重α。
  • 步骤4:用权重α对Value加权求和,得到最终注意力输出。
    公式表示为:
    Attention ( Q , K , V ) = Softmax ( Q K T d k ) V \text{Attention}(Q,K,V) = \text{Softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=Softmax(dk QKT)V

其中 d k d_k dk 为缩放因子,用于稳定梯度。

二、主要类型

  1. 按计算范围划分
  • 全局注意力(Soft Attention) :考虑所有输入位置,权重连续可导,适用于大多数任务。
  • 局部注意力(Local Attention) :仅关注输入的一个子区域,计算效率更高。
  • 硬注意力(Hard Attention) :基于采样选择特定位置,不可导需强化学习优化。
  1. 按应用领域划分
  • 自注意力(Self-Attention) :Query、Key、Value均来自同一输入,用于捕捉序列内部的长程依赖关系,是Transformer的核心组件。
  • 通道/空间注意力
    • 通道注意力(如SENet):学习特征通道的重要性。
    • 空间注意力(如STN):聚焦空间关键区域。
    • 混合机制(如CBAM):同时结合通道与空间注意力。
  • 双向注意力:在编码器-解码器框架中同时考虑源序列和目标序列信息,提升翻译性能。
  • 图注意力(GAT) :在图结构中动态分配节点间的关系权重。

三、典型应用

  1. 自然语言处理(NLP)
  • 机器翻译:通过Seq2Seq模型中的注意力机制对齐源语言与目标语言的关键词。
  • Transformer模型:完全基于自注意力机制,取代RNN结构,成为BERT、GPT等预训练模型的基础。
  1. 计算机视觉(CV)
  • 目标检测:在YOLO系列中引入ECA、CBAM等注意力模块,提升特征表达能力。
  • 图像分类:SENet通过通道注意力优化特征选择,曾获ImageNet冠军。
  • 语义分割:空间注意力帮助模型聚焦目标区域,提高分割精度。
  1. 跨模态任务
  • 视觉问答(VQA) :通过注意力机制关联图像区域与问题关键词。
  • 语音识别:结合时间维度注意力,提升对语音序列关键帧的捕捉能力。

四、优势与挑战

  1. 优势
  • 动态权重分配:相比固定卷积核,注意力能自适应数据特性。
  • 长程依赖建模:克服RNN梯度消失问题,有效捕捉远距离关联。
  • 可解释性:权重可视化可辅助理解模型决策依据。
  1. 挑战
  • 计算复杂度:自注意力机制的 $ O(n^2) $ 复杂度限制长序列处理效率。
  • 参数敏感度:部分注意力模块需精细调参才能发挥效果。

五、学习资源推荐

  • 理论入门:中科院胡玥老师的《自然语言处理》课件提纲挈领;吴恩达、李宏毅的教程通俗易懂。
  • 代码实践:PyTorch官方教程实现注意力评分机制;YOLOv5改进系列提供即插即用模块代码。
  • 论文精读:从经典模型SENet、Transformer到前沿改进MAB模块,覆盖CVPR等顶会工作。

总结来看,注意力机制通过模拟人类认知的聚焦特性,已成为深度学习模型的标配组件。掌握其核心原理与典型变体,是理解现代神经网络(如Transformer、GNN)的关键基础。

注意力机制全解:从数学推导到PyTorch实战


一、注意力机制核心概念

注意力机制的核心思想是通过动态权重分配,让模型关注输入数据中更重要的部分。数学形式可表示为:

Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QKT)V

其中:
- Q Q Q:查询向量 (Query)
- K K K:键向量 (Key)
- V V V:值向量 (Value)
- d k d_k dk:缩放因子(防止梯度消失)


二、数学推导(标量→矩阵形式)

1. 标量形式推导
对于单个查询向量 q q q和键向量 k i k_i ki
α i = exp ⁡ ( q ⋅ k i / d k ) ∑ j exp ⁡ ( q ⋅ k j / d k ) \alpha_i = \frac{\exp(q \cdot k_i / \sqrt{d_k})}{\sum_j \exp(q \cdot k_j / \sqrt{d_k})} αi=jexp(qkj/dk )exp(qki/dk )
输出为加权和:
z = ∑ i α i v i z = \sum_i \alpha_i v_i z=iαivi

2. 矩阵形式梯度推导
假设损失函数为 L L L,计算梯度:
∂ L ∂ W Q = ∂ L ∂ Z ⋅ ( ∂ Z ∂ α ⋅ ∂ α ∂ ( Q K T ) ⋅ ∂ ( Q K T ) ∂ W Q ) \frac{\partial L}{\partial W_Q} = \frac{\partial L}{\partial Z} \cdot \left( \frac{\partial Z}{\partial \alpha} \cdot \frac{\partial \alpha}{\partial (QK^T)} \cdot \frac{\partial (QK^T)}{\partial W_Q} \right) WQL=ZL(αZ(QKT)αWQ(QKT))
其中:
- ∂ α ∂ ( Q K T ) = diag ( α ) − α α T \frac{\partial \alpha}{\partial (QK^T)} = \text{diag}(\alpha) - \alpha \alpha^T (QKT)α=diag(α)ααT(softmax导数)


三、PyTorch实战代码
import torch
import torch.nn as nn
import matplotlib.pyplot as plt

class SelfAttention(nn.Module):
    def __init__(self, embed_size):
        super(SelfAttention, self).__init__()
        self.query = nn.Linear(embed_size, embed_size)
        self.key = nn.Linear(embed_size, embed_size)
        self.value = nn.Linear(embed_size, embed_size)
        self.softmax = nn.Softmax(dim=-1)
    # 例如输入x是[猫爱吃老鼠,狗爱吃西瓜] 
    # 每个字的embed向量是16维。5个字就是seq_len=5, 两句话就是batch_size=2    
    def forward(self, x):
        Q = self.query(x)  # (batch, seq_len, embed)
        K = self.key(x)
        V = self.value(x)
        #Q*K^T其实就是计算猫和猫爱吃老鼠5个字的分别关联打分
        # 再计算爱和猫爱吃老鼠5个字的分别关联打分, .... 结果就是5*5个分数,再后softmax规一化
        scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(x.size(-1)))
        attn_weights = self.softmax(scores)
        # 最后的相乘,相当于调整了原本猫爱吃老鼠5个embed 向量在空间中的位置,16维,从0到16
        output = torch.matmul(attn_weights, V)
        return output, attn_weights

# 测试数据
batch_size = 2
seq_len = 5
embed_size = 16
x = torch.randn(batch_size, seq_len, embed_size)

# 可视化注意力权重
model = SelfAttention(embed_size)
output, attn = model(x)

plt.matshow(attn[0].detach().numpy())
plt.title("Attention Weights")
plt.colorbar()
plt.show()

四、应用场景与可视化案例

1. 典型应用场景

  • 机器翻译(Transformer)
  • 图像描述生成
  • 时间序列预测
  • 推荐系统

2. 可视化示例

(展示输入序列中每个位置的关注权重)


五、优缺点与改进方法

优点

  • 动态关注关键信息
  • 并行计算能力强
  • 可解释性较好(通过权重可视化)

缺点

  • 计算复杂度 O ( n 2 ) O(n^2) O(n2)
  • 对长序列处理效率低
  • 需要大量训练数据

改进方案

  1. 稀疏注意力 (Sparse Attention)

    • 限制每个token只能关注局部窗口
    • 数学形式: α i j = 0  if  ∣ i − j ∣ > k \alpha_{ij} = 0 \text{ if } |i-j| > k αij=0 if ij>k
  2. 线性注意力 (Linear Attention)

    • 将softmax分解为核函数:
      Attention ( Q , K , V ) = ϕ ( Q ) ( ϕ ( K ) T V ) ϕ ( Q ) ( ϕ ( K ) T 1 ) \text{Attention}(Q,K,V) = \frac{\phi(Q)(\phi(K)^T V)}{\phi(Q)(\phi(K)^T 1)} Attention(Q,K,V)=ϕ(Q)(ϕ(K)T1)ϕ(Q)(ϕ(K)TV)
  3. 多头注意力 (Multi-Head)

    class MultiHeadAttention(nn.Module):
        def __init__(self, embed_size, num_heads):
            super().__init__()
            self.heads = nn.ModuleList([
                SelfAttention(embed_size) for _ in range(num_heads)
            ])
            self.fc = nn.Linear(embed_size*num_heads, embed_size)
        
        def forward(self, x):
            heads = [h(x)[0] for h in self.heads]
            return self.fc(torch.cat(heads, dim=-1))
    

六、关键问题解决方案

问题1:梯度消失

  • 解决方案:Layer Normalization
    LayerNorm ( x ) = γ x − μ σ + β \text{LayerNorm}(x) = \gamma \frac{x - \mu}{\sigma} + \beta LayerNorm(x)=γσxμ+β

问题2:位置信息缺失

  • 解决方案:位置编码
    P E ( p o s , 2 i ) = sin ⁡ ( p o s / 1000 0 2 i / d ) PE(pos,2i) = \sin(pos/10000^{2i/d}) PE(pos,2i)=sin(pos/100002i/d)
    P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s / 1000 0 2 i / d ) PE(pos,2i+1) = \cos(pos/10000^{2i/d}) PE(pos,2i+1)=cos(pos/100002i/d)

问题3:计算效率

  • 解决方案:Flash Attention (IO感知算法)
    • 内存访问优化,速度提升3-5倍

七、扩展应用:时间序列预测
class TimeSeriesTransformer(nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim, nhead):
        super().__init__()
        self.encoder = nn.Linear(input_dim, hidden_dim)
        self.transformer = nn.Transformer(
            d_model=hidden_dim,
            nhead=nhead,
            num_encoder_layers=3
        )
        self.decoder = nn.Linear(hidden_dim, output_dim)
    
    def forward(self, src):
        src = self.encoder(src)
        output = self.transformer(src, src)
        return self.decoder(output)

可视化预测结果:


八、数学证明:梯度推导(矩阵形式)

令损失函数为 L L L,计算 ∂ L ∂ W Q \frac{\partial L}{\partial W_Q} WQL

  1. 前向传播:
    Z = softmax ( X W Q ( X W K ) T d k ) X W V Z = \text{softmax}\left(\frac{XW_Q (XW_K)^T}{\sqrt{d_k}}\right) XW_V Z=softmax(dk XWQ(XWK)T)XWV

  2. 反向传播:
    ∂ L ∂ W Q = X T ( ∂ L ∂ Z ⋅ ( V T ⊙ ∂ α ∂ S ) ) \frac{\partial L}{\partial W_Q} = X^T \left( \frac{\partial L}{\partial Z} \cdot (V^T \odot \frac{\partial \alpha}{\partial S}) \right) WQL=XT(ZL(VTSα))
    其中:
    ∂ α ∂ S = α ⊗ ( 1 − α ) \frac{\partial \alpha}{\partial S} = \alpha \otimes (1 - \alpha) Sα=α(1α) (按元素乘)


通过结合理论推导与代码实践,您可以深入掌握注意力机制的核心原理及工程实现。建议使用Jupyter Notebook逐步调试代码并可视化中间结果,以加深理解。

多头注意力

yuanbao.tencent.com多头注意力简单pythorch代码实现实例

以下是一个基于PyTorch实现的多头注意力机制的简化代码示例,结合了多个技术文档中的实现逻辑:

import torch
import torch.nn as nn
import torch.nn.functional as F

class MultiHeadAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super().__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.q_proj = nn.Linear(embed_dim, embed_dim)
        self.k_proj = nn.Linear(embed_dim, embed_dim)
        self.v_proj = nn.Linear(embed_dim, embed_dim)
        
        # 输出线性变换
        self.out_proj = nn.Linear(embed_dim, embed_dim)

    def forward(self, x):
        batch_size, seq_len, _ = x.size()
        
        # 生成Q/K/V [batch_size, seq_len, embed_dim]
        Q = self.q_proj(x)
        K = self.k_proj(x)
        V = self.v_proj(x)

        # 分割多头 [batch_size, num_heads, seq_len, head_dim]
        Q = Q.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
        K = K.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
        V = V.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)

        # 计算注意力分数
        attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
        attn_weights = F.softmax(attn_scores, dim=-1)

        # 加权求和
        attn_output = torch.matmul(attn_weights, V)
        
        # 拼接多头输出 [batch_size, seq_len, embed_dim]
        attn_output = attn_output.transpose(1, 2).contiguous()
        attn_output = attn_output.view(batch_size, seq_len, self.embed_dim)
        
        # 最终线性变换
        output = self.out_proj(attn_output)
        return output

# 测试示例
embed_dim = 512  # 输入特征维度
num_heads = 8    # 注意力头数
batch_size = 2
seq_len = 10

model = MultiHeadAttention(embed_dim, num_heads)
x = torch.randn(batch_size, seq_len, embed_dim)
output = model(x)
print(output.shape)  # 输出应为 [2, 10, 512]

代码解析

  1. 初始化参数:确保embed_dim能被num_heads整除,每个头的维度为head_dim = embed_dim // num_heads
  2. 线性变换:通过三个独立的线性层生成Q/K/V
  3. 多头分割:使用viewtranspose将特征维度拆分为多个头
  4. 注意力计算
    • 计算缩放点积注意力分数: Attention ( Q , K , V ) = softmax ( Q K T d k ) V \text{Attention}(Q, K, V) = \text{softmax}(\frac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk QKT)V
    • 应用softmax归一化注意力权重
  5. 输出拼接:将多个头的输出拼接后通过线性层映射回原始维度

该实现省略了mask机制和位置编码,但完整展示了多头注意力的核心计算流程。实际使用时可根据需求添加残差连接和层归一化操作。

embedding size会被拆分成多个更小的子空间

yuanbao.tencent.com多头注意力中把embedding size拆成多个小的?还是说加上多个Q, K, V, embedding size不变呢?

在多头注意力中,embedding size会被拆分成多个更小的子空间(每个头对应一个子空间),而不是保持embedding size不变。这是多头注意力的核心设计思想,具体实现逻辑如下:


1. 核心原理

假设原始输入维度为 embed_dim,头数为 num_heads

  • 拆分维度:将 embed_dim 拆分为 num_heads 个独立的子空间(头),每个头的维度为 head_dim = embed_dim // num_heads
  • 并行计算:每个头在自己的子空间内独立计算注意力权重,最终将结果拼接后融合。

关键点

  • 维度变化:总维度 embed_dim 保持不变,但每个头的维度 head_dim 变小。
  • 参数共享:所有头共享同一组 Q/K/V 的线性变换矩阵,但通过拆分维度实现不同子空间的学习。

2. 具体实现步骤

(1) 线性变换生成Q/K/V
  • 输入 x 的维度为 [batch_size, seq_len, embed_dim]
  • 通过线性变换生成 Q/K/V,维度仍为 [batch_size, seq_len, embed_dim]
    Q = self.q_proj(x)  # [batch_size, seq_len, embed_dim]
    K = self.k_proj(x)
    V = self.v_proj(x)
    
(2) 拆分多头
  • 维度重组:将 embed_dim 拆分为 num_headshead_dim
    # 拆分后的维度:[batch_size, num_heads, seq_len, head_dim]
    Q = Q.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
    K = K.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
    V = V.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
    
    • 例如:embed_dim=512, num_heads=8head_dim=64
(3) 计算注意力
  • 每个头在自己的子空间内独立计算注意力:
    # 注意力分数:[batch_size, num_heads, seq_len, seq_len]
    attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
    attn_weights = F.softmax(attn_scores, dim=-1)
    attn_output = torch.matmul(attn_weights, V)  # [batch_size, num_heads, seq_len, head_dim]
    
(4) 拼接多头输出
  • 将多个头的输出拼接回原始维度 embed_dim
    attn_output = attn_output.transpose(1, 2).contiguous()  # [batch_size, seq_len, num_heads, head_dim]
    attn_output = attn_output.view(batch_size, seq_len, self.embed_dim)  # [batch_size, seq_len, embed_dim]
    
(5) 最终线性变换
  • 通过 out_proj 将拼接后的结果映射到目标维度:
    output = self.out_proj(attn_output)  # [batch_size, seq_len, embed_dim]
    

3. 为什么不是保持embedding size不变?

如果保持 embed_dim 不变,同时增加多个独立的 Q/K/V 矩阵:

  1. 参数爆炸:每个头需要独立的 Q/K/V 矩阵,参数量会随头数线性增长。
  2. 冗余计算:不同头可能学习到相似的特征,导致计算资源浪费。
  3. 维度不匹配:最终拼接时维度会超过原始 embed_dim,破坏模型结构。

4. 多头注意力的优势

设计方式 参数效率 计算效率 特征多样性
拆分embedding size ✅ 高 ✅ 高 ✅ 强
保持embedding size不变 ❌ 低 ❌ 低 ❌ 弱
  • 参数效率:拆分维度后,所有头共享同一组线性变换矩阵,参数量恒定。
  • 特征多样性:每个头关注不同的子空间(例如语法、语义、位置等),增强模型表达能力。

5. 总结

  • 核心操作:将 embed_dim 拆分为 num_heads 个更小的 head_dim,每个头独立计算注意力。
  • 参数共享:所有头共享 Q/K/V 的线性变换矩阵,避免参数量爆炸。
  • 最终目标:通过多视角(多头)捕捉输入的不同特征,提升模型对复杂模式的建模能力。

这种设计在Transformer中被广泛验证,是自然语言处理任务中提升性能的关键机制。

为什么是 Q·Kᵀ(Q乘以K的转置)而非 Qᵀ·K(Q的转置乘以K)

在注意力机制中,使用 Q·Kᵀ(Q乘以K的转置)而非 Qᵀ·K(Q的转置乘以K)是出于维度匹配语义合理性的考虑。以下是详细解释:


1. 维度匹配问题

假设输入维度为:

  • Q(Query):形状为 [batch_size, seq_len_q, d_k]
  • K(Key):形状为 [batch_size, seq_len_k, d_k]
(1) 如果使用 Q·Kᵀ
  • 操作:将Q的最后一个维度(d_k)与K的最后一个维度(d_k)对齐。
  • 结果维度[batch_size, seq_len_q, seq_len_k]
  • 意义:每个位置 i 的查询(Q[i])与所有位置 j 的键(K[j])计算相似度,生成注意力分数矩阵。
(2) 如果使用 Qᵀ·K
  • 操作:将Q的第二个维度(seq_len_q)与K的第一个维度(batch_size)对齐,导致维度不匹配。
  • 结果维度:无法直接相乘(除非 seq_len_q = batch_size,但这通常不成立)。
  • 意义:无实际物理含义,且维度不兼容。

2. 语义合理性

注意力机制的目标是计算 查询序列(Q)与键序列(K)之间的相似度

  • Q·Kᵀ 的每个元素 (i, j) 表示第 i 个查询与第 j 个键的点积相似度。
  • Qᵀ·K 的每个元素 (i, j) 表示第 i 个特征维度(如词向量的某一维)与第 j 个特征维度的协方差,与序列位置无关,无法表达位置间的关联
示例

假设输入序列为句子中的单词:

  • Q·Kᵀ:计算每个单词与其他单词的关联强度(如“猫”与“老鼠”的关联)。
  • Qᵀ·K:计算不同特征维度之间的相关性(如词向量的第5维与第8维的关联),这与序列语义无关。

3. 数学视角

注意力分数的计算公式为:
Attention ( Q , K , V ) = softmax ( Q K ⊤ d k ) V \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V Attention(Q,K,V)=softmax(dk QK)V

为什么是 QKᵀ?
  • 点积相似度:两个向量 q(来自Q)和 k(来自K)的点积 q·k 衡量它们的相似度。
  • 矩阵乘法QKᵀ 批量计算所有查询与键的点积,生成注意力分数矩阵。
为什么不是 QᵀK?
  • 维度不匹配Qᵀ 的形状为 [d_k, seq_len_q]K 的形状为 [batch_size, seq_len_k, d_k],无法直接相乘。
  • 语义错误:即使维度匹配(如忽略batch维度),结果矩阵也无法表达序列位置间的关联。

4. 可视化对比

(1) Q·Kᵀ 的注意力分数矩阵
         Key₁   Key₂   Key₃
Query₁  [ 0.8    0.2    0.5 ]
Query₂  [ 0.3    0.7    0.1 ]
  • 行表示不同查询,列表示不同键,值表示相似度。
(2) Qᵀ·K 的协方差矩阵
         Feature₁  Feature₂  Feature₃
Feature₁ [ 1.2      0.5      -0.3 ]
Feature₂ [ 0.5      0.9      0.1  ]
Feature₃ [-0.3      0.1      0.8  ]
  • 行和列均表示特征维度,与序列位置无关。

5. 总结

操作 维度匹配 语义合理性 实际用途
Q·Kᵀ ✅ 是 ✅ 表达位置间关联 生成注意力权重矩阵
Qᵀ·K ❌ 否 ❌ 无意义 无法用于注意力计算
  • 核心原因Q·Kᵀ 的维度设计和语义逻辑完美契合注意力机制的需求,而 Qᵀ·K 在维度和语义上均不适用。

一种形象的解释

注意力机制本质是调整embedding在空间中的位置。
比如:
1.我爱吃苹果和香蕉,请问前面的苹果是什么。
2.我买了最新款的苹果,请问前面的苹果是什么。
注意力机制就是根据上下文调整 苹果在embedding空间中的位置.
第1句中,会把苹果调整的离水果这个词近一些。
第2句中,会把苹果调整的离手机这个词近一些。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐