LLM-25Transformer自注意力机制原理
·
25. Transformer架构原理:transformer的自注意力机制原理
什么是自注意力机制?
自注意力机制(Self-Attention)就像是一场精心编排的集体舞,每个舞者(词)都需要:
- 观察其他所有舞者的位置和动作
- 判断哪些舞者与自己最相关
- 调整自己的动作,与相关舞者协调一致
与传统的"只关注前面舞者"不同,自注意力让每个人都能同时看到整个舞团,从而创造出更加和谐优美的舞蹈。
直观理解:生活中的自注意力
场景1:阅读时的注意力分配
当你读到这句话时:
“那只猫坐在垫子上,它看起来很舒服。”
你的大脑会自动:
- 将"它"与"猫"关联起来
- 将"舒服"与"坐在垫子上"关联起来
- 理解"垫子"是"猫"坐的地方
这就是自注意力在起作用!
场景2:会议中的注意力管理
想象你在参加一个会议:
- 当老板说话时,你会特别关注
- 当同事补充意见时,你会适度关注
- 当无关人员插话时,你会降低关注度
自注意力机制让模型能够动态地分配注意力资源,就像人类一样聪明。
自注意力的核心思想
三个关键操作:Query、Key、Value
自注意力通过三个变换矩阵来实现:
自注意力核心概念 = '''
输入序列: X = [x₁, x₂, x₃, ..., xₙ]
变换过程:
1. Query (查询): "我应该关注什么?"
Q = X × W_Q
2. Key (键): "我能提供什么信息?"
K = X × W_K
3. Value (值): "我的实际内容是什么?"
V = X × W_V
注意力分数: Score = Q × K^T
注意力权重: Weight = Softmax(Score)
注意力输出: Output = Weight × V
'''
图书馆类比
想象你在图书馆找资料:
图书馆类比 = '''
图书管理员 (Query): "我需要找什么书?"
↓
书籍索引 (Key): "这本书是关于什么的?"
↓
书籍内容 (Value): "这本书实际写了什么?"
↓
匹配过程: 管理员根据需求找到相关书籍
↓
结果: 拿到最相关的书籍内容
'''
数学原理详解
基础自注意力计算
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
def self_attention_basic(X, W_Q, W_K, W_V):
"""
基础自注意力计算
Args:
X: 输入序列 [seq_len, embed_dim]
W_Q, W_K, W_V: 权重矩阵 [embed_dim, embed_dim]
Returns:
output: 注意力输出 [seq_len, embed_dim]
attention_weights: 注意力权重 [seq_len, seq_len]
"""
# 1. 计算Q, K, V
Q = torch.matmul(X, W_Q) # [seq_len, embed_dim]
K = torch.matmul(X, W_K) # [seq_len, embed_dim]
V = torch.matmul(X, W_V) # [seq_len, embed_dim]
# 2. 计算注意力分数
scores = torch.matmul(Q, K.T) # [seq_len, seq_len]
# 3. 应用softmax获得注意力权重
attention_weights = torch.softmax(scores, dim=-1) # [seq_len, seq_len]
# 4. 计算注意力输出
output = torch.matmul(attention_weights, V) # [seq_len, embed_dim]
return output, attention_weights
# 示例演示
print("=== 基础自注意力计算演示 ===")
# 创建输入数据(模拟4个词的序列)
seq_len = 4
embed_dim = 8
X = torch.randn(seq_len, embed_dim)
# 创建权重矩阵(随机初始化)
W_Q = torch.randn(embed_dim, embed_dim)
W_K = torch.randn(embed_dim, embed_dim)
W_V = torch.randn(embed_dim, embed_dim)
# 计算自注意力
输出, 注意力权重 = self_attention_basic(X, W_Q, W_K, W_V)
print(f"输入形状: {X.shape}")
print(f"输出形状: {输出.shape}")
print(f"注意力权重形状: {注意力_weights.shape}")
# 可视化注意力权重
plt.figure(figsize=(8, 6))
sns.heatmap(注意力_weights.numpy(),
annot=True,
fmt='.3f',
cmap='Blues',
xticklabels=[f'位置{i}' for i in range(seq_len)],
yticklabels=[f'位置{i}' for i in range(seq_len)])
plt.title('自注意力权重矩阵')
plt.xlabel('Key位置')
plt.ylabel('Query位置')
plt.savefig('自注意力权重可视化.png', dpi=300, bbox_inches='tight')
plt.show()
缩放因子(Scale Factor)
def scaled_dot_product_attention(Q, K, V, mask=None, dropout=None):
"""
缩放点积注意力(Scaled Dot-Product Attention)
Args:
Q: Query矩阵 [batch_size, num_heads, seq_len, head_dim]
K: Key矩阵 [batch_size, num_heads, seq_len, head_dim]
V: Value矩阵 [batch_size, num_heads, seq_len, head_dim]
mask: 可选的掩码
dropout: 可选的dropout层
Returns:
output: 注意力输出
attention_weights: 注意力权重
"""
# 获取维度信息
d_k = Q.size(-1)
# 1. 计算注意力分数
scores = torch.matmul(Q, K.transpose(-2, -1)) / np.sqrt(d_k)
# 2. 应用掩码(如果有)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
# 3. 应用softmax
attention_weights = torch.softmax(scores, dim=-1)
# 4. 应用dropout(如果有)
if dropout is not None:
attention_weights = dropout(attention_weights)
# 5. 计算输出
output = torch.matmul(attention_weights, V)
return output, attention_weights
# 缩放因子演示
print("\n=== 缩放因子作用演示 ===")
# 创建不同维度的输入
维度列表 = [32, 64, 128, 256]
缩放效果 = []
for d_k in 维度列表:
# 创建随机Q, K矩阵
Q = torch.randn(1, 1, 10, d_k)
K = torch.randn(1, 1, 10, d_k)
# 无缩放的注意力分数
scores_no_scale = torch.matmul(Q, K.transpose(-2, -1))
# 有缩放的注意力分数
scores_with_scale = torch.matmul(Q, K.transpose(-2, -1)) / np.sqrt(d_k)
# 记录统计信息
缩放效果.append({
'维度': d_k,
'无缩放均值': scores_no_scale.mean().item(),
'无缩放标准差': scores_no_scale.std().item(),
'有缩放均值': scores_with_scale.mean().item(),
'有缩放标准差': scores_with_scale.std().item()
})
# 可视化缩放效果
缩放数据 = pd.DataFrame(缩放效果)
print("\n缩放因子效果统计:")
print(缩放数据)
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.plot(缩放数据['维度'], 缩放数据['无缩放标准差'], 'o-', label='无缩放', linewidth=2)
plt.plot(缩放数据['维度'], 缩放数据['有缩放标准差'], 's-', label='有缩放', linewidth=2)
plt.xlabel('维度 (d_k)')
plt.ylabel('标准差')
plt.title('缩放因子对标准差的影响')
plt.legend()
plt.grid(True, alpha=0.3)
plt.subplot(1, 2, 2)
plt.plot(缩放数据['维度'], 缩放数据['无缩放均值'], 'o-', label='无缩放', linewidth=2)
plt.plot(缩放数据['维度'], 缩放数据['有缩放均值'], 's-', label='有缩放', linewidth=2)
plt.xlabel('维度 (d_k)')
plt.ylabel('均值')
plt.title('缩放因子对均值的影响')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('缩放因子效果.png', dpi=300, bbox_inches='tight')
plt.show()
多头自注意力机制(Multi-Head Self-Attention)
为什么需要多头?
多头注意力优势 = '''
单头注意力: 只能关注一种类型的关系
↓
多头注意力: 同时关注多种类型的关系
↓
比喻: 多个专家从不同角度分析问题
↓
结果: 更丰富、更全面的表示
'''
# 多头注意力可视化
def visualize_multihead_attention():
"""
可视化多头注意力的不同模式
"""
# 模拟8个注意力头的权重
num_heads = 8
seq_len = 10
# 创建不同的注意力模式
注意力模式 = []
for head in range(num_heads):
# 为每个头创建不同的注意力模式
if head == 0: # 局部注意力
pattern = np.eye(seq_len) + np.eye(seq_len, k=1) + np.eye(seq_len, k=-1)
elif head == 1: # 全局注意力
pattern = np.ones((seq_len, seq_len)) / seq_len
elif head == 2: # 前向注意力
pattern = np.triu(np.ones((seq_len, seq_len)))
elif head == 3: # 后向注意力
pattern = np.tril(np.ones((seq_len, seq_len)))
else: # 随机模式
pattern = np.random.rand(seq_len, seq_len)
pattern = pattern / pattern.sum(axis=1, keepdims=True)
注意力模式.append(pattern)
# 可视化
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
axes = axes.flatten()
for i, (head, pattern) in enumerate(zip(range(num_heads), 注意力模式)):
sns.heatmap(pattern,
ax=axes[i],
cmap='Blues',
annot=False,
cbar=False)
axes[i].set_title(f'注意力头 {head+1}')
axes[i].set_xlabel('Key位置')
axes[i].set_ylabel('Query位置')
plt.suptitle('多头注意力模式可视化', fontsize=16)
plt.tight_layout()
plt.savefig('多头注意力模式.png', dpi=300, bbox_inches='tight')
plt.show()
# 运行可视化
visualize_multihead_attention()
多头注意力实现
class MultiHeadSelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads, dropout=0.1):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
assert self.head_dim * num_heads == embed_dim, "embed_dim必须能被num_heads整除"
# 线性变换层
self.q_linear = nn.Linear(embed_dim, embed_dim)
self.k_linear = nn.Linear(embed_dim, embed_dim)
self.v_linear = nn.Linear(embed_dim, embed_dim)
self.dropout = nn.Dropout(dropout)
self.out_linear = nn.Linear(embed_dim, embed_dim)
def forward(self, x, mask=None):
"""
多头自注意力前向传播
Args:
x: 输入张量 [batch_size, seq_len, embed_dim]
mask: 可选的注意力掩码
Returns:
output: 输出张量 [batch_size, seq_len, embed_dim]
attention_weights: 注意力权重 [batch_size, num_heads, seq_len, seq_len]
"""
batch_size, seq_len, embed_dim = x.size()
# 1. 线性变换得到Q, K, V
Q = self.q_linear(x) # [batch_size, seq_len, embed_dim]
K = self.k_linear(x)
V = self.v_linear(x)
# 2. 重塑为多头形式
# [batch_size, seq_len, num_heads, head_dim]
Q = Q.view(batch_size, seq_len, self.num_heads, self.head_dim)
K = K.view(batch_size, seq_len, self.num_heads, self.head_dim)
V = V.view(batch_size, seq_len, self.num_heads, self.head_dim)
# 3. 转置以得到正确的维度
# [batch_size, num_heads, seq_len, head_dim]
Q = Q.transpose(1, 2)
K = K.transpose(1, 2)
V = V.transpose(1, 2)
# 4. 应用缩放点积注意力
attn_output, attn_weights = scaled_dot_product_attention(Q, K, V, mask, self.dropout)
# 5. 合并多头输出
# [batch_size, seq_len, num_heads, head_dim]
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, seq_len, self.embed_dim)
# 6. 最终线性变换
output = self.out_linear(attn_output)
return output, attn_weights
# 多头注意力演示
print("\n=== 多头自注意力演示 ===")
# 创建输入
batch_size = 2
seq_len = 10
embed_dim = 512
num_heads = 8
输入 = torch.randn(batch_size, seq_len, embed_dim)
# 创建多头注意力层
多头注意力 = MultiHeadSelfAttention(embed_dim, num_heads)
# 前向传播
输出, 注意力权重 = 多头注意力(输入)
print(f"输入形状: {输入.shape}")
print(f"输出形状: {输出.shape}")
print(f"注意力权重形状: {注意力_weights.shape}")
# 可视化某个头的注意力权重
plt.figure(figsize=(10, 8))
头索引 = 0 # 可视化第一个头
sns.heatmap(注意力_weights[0, 头索引].numpy(),
cmap='Blues',
annot=False)
plt.title(f'多头注意力 - 头{头索引+1} (批次0)')
plt.xlabel('Key位置')
plt.ylabel('Query位置')
plt.savefig('多头注意力权重.png', dpi=300, bbox_inches='tight')
plt.show()
自注意力的数学性质
1. 置换等变性(Permutation Equivariance)
def test_permutation_equivariance():
"""
测试自注意力的置换等变性
"""
# 创建输入
x = torch.randn(1, 5, 8) # [batch=1, seq_len=5, embed_dim=8]
# 创建注意力层
attention = MultiHeadSelfAttention(8, 2)
# 原始输出
with torch.no_grad():
output_original, _ = attention(x)
# 置换输入
置换索引 = [2, 0, 4, 1, 3] # 随机置换
x_permuted = x[:, 置换索引, :]
# 置换后的输出
with torch.no_grad():
output_permuted, _ = attention(x_permuted)
# 检查等变性:f(P(x)) = P(f(x))
output_original_permuted = output_original[:, 置换索引, :]
差异 = torch.abs(output_permuted - output_original_permuted).max().item()
print(f"置换等变性测试:")
print(f"最大差异: {差异:.6f}")
print(f"等变性是否成立: {差异 < 1e-5}")
return 差异 < 1e-5
# 运行测试
等变性成立 = test_permutation_equivariance()
2. 梯度流动分析
def gradient_flow_analysis():
"""
分析自注意力中的梯度流动
"""
# 创建简单的自注意力模块
class SimpleSelfAttention(nn.Module):
def __init__(self, embed_dim):
super().__init__()
self.W_Q = nn.Parameter(torch.randn(embed_dim, embed_dim))
self.W_K = nn.Parameter(torch.randn(embed_dim, embed_dim))
self.W_V = nn.Parameter(torch.randn(embed_dim, embed_dim))
def forward(self, x):
Q = torch.matmul(x, self.W_Q)
K = torch.matmul(x, self.W_K)
V = torch.matmul(x, self.W_V)
scores = torch.matmul(Q, K.T) / np.sqrt(x.size(-1))
attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights
# 创建模型和数据
模型 = SimpleSelfAttention(16)
输入 = torch.randn(4, 16, requires_grad=True)
目标 = torch.randn(4, 16)
# 前向传播
输出, 注意力权重 = 模型(输入)
损失 = nn.MSELoss()(输出, 目标)
# 反向传播
损失.backward()
# 分析梯度
梯度信息 = {
'W_Q梯度范数': 模型.W_Q.grad.norm().item(),
'W_K梯度范数': 模型.W_K.grad.norm().item(),
'W_V梯度范数': 模型.W_V.grad.norm().item(),
'输入梯度范数': 输入.grad.norm().item() if 输入.grad is not None else 0
}
print("\n梯度流动分析:")
for 名称, 值 in 梯度信息.items():
print(f"{名称}: {值:.4f}")
return 梯度信息
# 运行梯度分析
梯度分析结果 = gradient_flow_analysis()
自注意力的变体和改进
1. 稀疏注意力(Sparse Attention)
稀疏注意力类型 = '''
局部注意力: 只关注附近的token
↓
跨步注意力: 关注固定间隔的token
↓
随机注意力: 随机选择关注的token
↓
块注意力: 将序列分块,块内全连接
↓
组合注意力: 多种模式的组合
'''
# 局部注意力实现
class LocalSelfAttention(nn.Module):
def __init__(self, embed_dim, num_heads, window_size=16):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.window_size = window_size
self.head_dim = embed_dim // num_heads
self.q_linear = nn.Linear(embed_dim, embed_dim)
self.k_linear = nn.Linear(embed_dim, embed_dim)
self.v_linear = nn.Linear(embed_dim, embed_dim)
self.out_linear = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, embed_dim = x.shape
# 创建局部掩码
mask = torch.zeros(seq_len, seq_len)
for i in range(seq_len):
start = max(0, i - self.window_size // 2)
end = min(seq_len, i + self.window_size // 2 + 1)
mask[i, start:end] = 1
# 应用标准的多头注意力,但使用局部掩码
# ...(类似多头注意力的实现,但添加掩码)
return x, mask
2. 线性注意力(Linear Attention)
线性注意力原理 = '''
标准注意力: O(n²) 复杂度
Attention(Q,K,V) = softmax(QK^T)V
线性注意力: O(n) 复杂度
通过核技巧近似softmax
改变计算顺序: Q(K^T V) instead of (QK^T)V
'''
# 线性注意力简化实现
class LinearAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
# 特征映射函数(简化版)
self.feature_map = nn ELU() # 使用ELU作为特征映射
self.q_linear = nn.Linear(embed_dim, embed_dim)
self.k_linear = nn.Linear(embed_dim, embed_dim)
self.v_linear = nn.Linear(embed_dim, embed_dim)
self.out_linear = nn.Linear(embed_dim, embed_dim)
def forward(self, x):
batch_size, seq_len, embed_dim = x.shape
# 线性变换
Q = self.feature_map(self.q_linear(x))
K = self.feature_map(self.k_linear(x))
V = self.v_linear(x)
# 改变计算顺序: Q(K^T V) instead of (QK^T)V
# 先计算 K^T V
KV = torch.matmul(K.transpose(-2, -1), V) # [batch, head_dim, head_dim]
# 再计算 Q(K^T V)
output = torch.matmul(Q, KV) # [batch, seq_len, head_dim]
# 归一化
Z = 1 / (torch.matmul(Q, K.sum(dim=-2, keepdim=True).transpose(-2, -1)) + 1e-8)
output = output * Z
return self.out_linear(output)
实际应用中的注意事项
1. 内存优化策略
内存优化技巧 = '''
1. 梯度检查点: 重新计算而非存储
2. 注意力稀疏化: 减少计算量
3. 混合精度: FP16 + FP32
4. 模型分片: 分布式存储
5. 动态批处理: 根据长度调整
'''
# 内存效率注意力
class MemoryEfficientAttention(nn.Module):
def __init__(self, embed_dim, num_heads, chunk_size=64):
super().__init__()
self.chunk_size = chunk_size
# ... 其他初始化
def forward(self, x):
# 分块处理,减少内存峰值
batch_size, seq_len, embed_dim = x.shape
outputs = []
for i in range(0, seq_len, self.chunk_size):
end_idx = min(i + self.chunk_size, seq_len)
chunk = x[:, i:end_idx, :]
# 处理当前块
chunk_output = self._process_chunk(chunk, i, end_idx)
outputs.append(chunk_output)
return torch.cat(outputs, dim=1)
2. 数值稳定性
数值稳定性措施 = '''
1. 缩放因子: 防止softmax溢出
2. 梯度裁剪: 防止梯度爆炸
3. 层归一化: 稳定激活分布
4. 残差连接: 保证梯度流动
5. 权重初始化: 合适的初始值
'''
# 数值稳定的注意力实现
class StableAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super().__init__()
# ... 初始化
def forward(self, x):
# 使用log-space计算提高数值稳定性
Q, K, V = self._get_qkv(x)
# 在log-space计算注意力
scores = torch.matmul(Q, K.transpose(-2, -1))
# 减去最大值提高数值稳定性
scores_max = scores.max(dim=-1, keepdim=True)[0]
scores_stable = scores - scores_max
# 应用softmax
attention_weights = torch.softmax(scores_stable, dim=-1)
output = torch.matmul(attention_weights, V)
return output
小结
自注意力机制是Transformer架构的核心创新,它彻底改变了我们处理序列数据的方式:
核心概念
- Query-Key-Value框架:通过三个变换矩阵实现动态注意力分配
- 缩放因子:防止高维空间中的梯度消失问题
- 多头机制:从多个角度同时关注不同类型的关系
- 并行计算:整个序列可以同时处理,大幅提升效率
技术优势
- 全局视野:一次性看到整个序列,没有距离限制
- 动态权重:根据上下文动态调整关注重点
- 可解释性:通过注意力权重理解模型决策过程
- 高度并行:充分利用现代硬件的并行计算能力
数学美感
- 置换等变性:保持输入顺序的数学性质
- 梯度友好:残差连接确保梯度顺畅流动
- 数值稳定:缩放因子和归一化保证计算稳定性
实际影响
自注意力机制不仅是Transformer的核心,更是现代大语言模型的基石。从BERT到GPT,从T5到PaLM,所有成功的语言模型都建立在自注意力的基础上。
理解自注意力机制不仅帮助我们掌握技术细节,更重要的是让我们领会到现代AI的核心思想:通过可学习的权重分配机制,让模型能够自主地发现数据中的重要模式和关系。
在后续章节中,我们将探索多头注意力的具体实现、位置编码的巧妙设计,以及Transformer的其他核心组件!
更多推荐

所有评论(0)