用deepseek学大模型08-看代码理解注意力机制
https://metaso.cn/s/tA0L08A
一、基本原理
-
核心思想
注意力机制通过对输入数据的不同部分赋予差异化的权重,实现信息的选择性聚焦。其本质可概括为:根据查询(Query)与键(Key)的相似性计算权重,再对值(Value)进行加权求和。 -
计算过程
- 步骤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(dkQKT)V
其中 d k d_k dk 为缩放因子,用于稳定梯度。
二、主要类型
- 按计算范围划分
- 全局注意力(Soft Attention) :考虑所有输入位置,权重连续可导,适用于大多数任务。
- 局部注意力(Local Attention) :仅关注输入的一个子区域,计算效率更高。
- 硬注意力(Hard Attention) :基于采样选择特定位置,不可导需强化学习优化。
- 按应用领域划分
- 自注意力(Self-Attention) :Query、Key、Value均来自同一输入,用于捕捉序列内部的长程依赖关系,是Transformer的核心组件。
- 通道/空间注意力:
- 通道注意力(如SENet):学习特征通道的重要性。
- 空间注意力(如STN):聚焦空间关键区域。
- 混合机制(如CBAM):同时结合通道与空间注意力。
- 双向注意力:在编码器-解码器框架中同时考虑源序列和目标序列信息,提升翻译性能。
- 图注意力(GAT) :在图结构中动态分配节点间的关系权重。
三、典型应用
- 自然语言处理(NLP)
- 机器翻译:通过Seq2Seq模型中的注意力机制对齐源语言与目标语言的关键词。
- Transformer模型:完全基于自注意力机制,取代RNN结构,成为BERT、GPT等预训练模型的基础。
- 计算机视觉(CV)
- 目标检测:在YOLO系列中引入ECA、CBAM等注意力模块,提升特征表达能力。
- 图像分类:SENet通过通道注意力优化特征选择,曾获ImageNet冠军。
- 语义分割:空间注意力帮助模型聚焦目标区域,提高分割精度。
- 跨模态任务
- 视觉问答(VQA) :通过注意力机制关联图像区域与问题关键词。
- 语音识别:结合时间维度注意力,提升对语音序列关键帧的捕捉能力。
四、优势与挑战
- 优势
- 动态权重分配:相比固定卷积核,注意力能自适应数据特性。
- 长程依赖建模:克服RNN梯度消失问题,有效捕捉远距离关联。
- 可解释性:权重可视化可辅助理解模型决策依据。
- 挑战
- 计算复杂度:自注意力机制的 $ 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(dkQKT)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(q⋅kj/dk)exp(q⋅ki/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) ∂WQ∂L=∂Z∂L⋅(∂α∂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)
- 对长序列处理效率低
- 需要大量训练数据
改进方案:
-
稀疏注意力 (Sparse Attention)
- 限制每个token只能关注局部窗口
- 数学形式: α i j = 0 if ∣ i − j ∣ > k \alpha_{ij} = 0 \text{ if } |i-j| > k αij=0 if ∣i−j∣>k
-
线性注意力 (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)
- 将softmax分解为核函数:
-
多头注意力 (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} ∂WQ∂L:
-
前向传播:
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(dkXWQ(XWK)T)XWV -
反向传播:
∂ 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) ∂WQ∂L=XT(∂Z∂L⋅(VT⊙∂S∂α))
其中:
∂ α ∂ 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]
代码解析:
- 初始化参数:确保
embed_dim
能被num_heads
整除,每个头的维度为head_dim = embed_dim // num_heads
- 线性变换:通过三个独立的线性层生成Q/K/V
- 多头分割:使用
view
和transpose
将特征维度拆分为多个头 - 注意力计算:
- 计算缩放点积注意力分数: 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(dkQKT)V
- 应用softmax归一化注意力权重
- 输出拼接:将多个头的输出拼接后通过线性层映射回原始维度
该实现省略了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_heads
个head_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=8
→head_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
矩阵:
- 参数爆炸:每个头需要独立的
Q/K/V
矩阵,参数量会随头数线性增长。 - 冗余计算:不同头可能学习到相似的特征,导致计算资源浪费。
- 维度不匹配:最终拼接时维度会超过原始
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(dkQK⊤)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句中,会把苹果调整的离手机这个词近一些。
更多推荐
所有评论(0)