25. Transformer架构原理:transformer的自注意力机制原理

什么是自注意力机制?

自注意力机制(Self-Attention)就像是一场精心编排的集体舞,每个舞者(词)都需要:

  1. 观察其他所有舞者的位置和动作
  2. 判断哪些舞者与自己最相关
  3. 调整自己的动作,与相关舞者协调一致

与传统的"只关注前面舞者"不同,自注意力让每个人都能同时看到整个舞团,从而创造出更加和谐优美的舞蹈。

直观理解:生活中的自注意力

场景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架构的核心创新,它彻底改变了我们处理序列数据的方式:

核心概念

  1. Query-Key-Value框架:通过三个变换矩阵实现动态注意力分配
  2. 缩放因子:防止高维空间中的梯度消失问题
  3. 多头机制:从多个角度同时关注不同类型的关系
  4. 并行计算:整个序列可以同时处理,大幅提升效率

技术优势

  • 全局视野:一次性看到整个序列,没有距离限制
  • 动态权重:根据上下文动态调整关注重点
  • 可解释性:通过注意力权重理解模型决策过程
  • 高度并行:充分利用现代硬件的并行计算能力

数学美感

  • 置换等变性:保持输入顺序的数学性质
  • 梯度友好:残差连接确保梯度顺畅流动
  • 数值稳定:缩放因子和归一化保证计算稳定性

实际影响

自注意力机制不仅是Transformer的核心,更是现代大语言模型的基石。从BERT到GPT,从T5到PaLM,所有成功的语言模型都建立在自注意力的基础上。

理解自注意力机制不仅帮助我们掌握技术细节,更重要的是让我们领会到现代AI的核心思想:通过可学习的权重分配机制,让模型能够自主地发现数据中的重要模式和关系。

在后续章节中,我们将探索多头注意力的具体实现、位置编码的巧妙设计,以及Transformer的其他核心组件!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐