1.2 Self-Attention原理详解


1.2 Self-Attention原理详解

读者读完这节,能够深入理解Self-Attention的数学原理、实现细节和核心优势,掌握从理论到代码实现的完整流程。

1.2.1 Self-Attention的基本概念

Self-Attention(自注意力机制)是Transformer架构的核心组件,它允许序列中的每个元素都能关注序列中的所有其他元素。与传统的注意力机制不同,Self-Attention的Query、Key、Value都来自同一个输入序列。

Self-Attention的直观理解

想象一下在阅读一篇文章时,当你理解一个词时,你往往会回顾文章中的其他词来获得上下文信息。Self-Attention就是用数学的方式模拟这个过程:

  • Query: 当前正在处理的位置(比如你当前读到的词)
  • Key: 序列中所有位置(文章中的所有词)
  • Value: 序列中所有位置对应的内容(所有词的语义表示)

1.2.2 Self-Attention的数学原理

核心计算公式

Self-Attention的计算过程可以用以下公式表示:

Attention(Q, K, V) = softmax((QK^T)/√d_k)V

其中:

  • Q ∈ ℝ^(n×d_k): Query矩阵,n是序列长度,d_k是键向量的维度
  • K ∈ ℝ^(n×d_k): Key矩阵,与Query维度相同
  • V ∈ ℝ^(n×d_v): Value矩阵,d_v是值向量的维度
  • d_k: 键向量的维度,用于缩放防止梯度消失

详细计算步骤

1. 线性变换

首先,输入序列X通过三个不同的权重矩阵进行线性变换,得到Q、K、V:

Q = X · W_Q K = X · W_K V = X · W_V

其中:

  • W_Q ∈ ℝ^(d_model×d_k): Query权重矩阵
  • W_K ∈ ℝ^(d_model×d_k): Key权重矩阵
  • W_V ∈ ℝ^(d_model×d_v): Value权重矩阵
  • d_model: 输入向量的维度

2. 相似度计算

计算Query和Key之间的相似度:

Scores = Q · K^T

这个矩阵的维度是n×n,其中每个元素表示第i个Query和第j个Key之间的相似度。

3. 缩放处理

为了避免维度过高导致梯度消失,我们进行缩放处理:

Scaled_Scores = Scores / √d_k

4. 注意力权重计算

使用softmax函数将相似度转换为概率分布:

Attention_Weights = softmax(Scaled_Scores)

5. 加权求和

将注意力权重与Value矩阵相乘,得到最终的输出:

Output = Attention_Weights · V

代码实现

import torch import torch.nn as nn import torch.nn.functional as F import math class SelfAttention(nn.Module): def __init__(self, d_model, d_k, d_v, dropout=0.1): super(SelfAttention, self).__init__() self.d_model = d_model self.d_k = d_k self.d_v = d_v # 线性变换矩阵 self.W_Q = nn.Linear(d_model, d_k, bias=False) self.W_K = nn.Linear(d_model, d_k, bias=False) self.W_V = nn.Linear(d_model, d_v, bias=False) # Dropout层 self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): """ x: 输入张量,形状为 [batch_size, seq_len, d_model] mask: 可选的注意力掩码,形状为 [batch_size, seq_len, seq_len] """ batch_size, seq_len, d_model = x.shape # 1. 线性变换得到Q, K, V Q = self.W_Q(x) # [batch_size, seq_len, d_k] K = self.W_K(x) # [batch_size, seq_len, d_k] V = self.W_V(x) # [batch_size, seq_len, d_v] # 2. 计算注意力分数 # K.transpose(-2, -1) 将K转置为 [batch_size, d_k, seq_len] scores = torch.matmul(Q, K.transpose(-2, -1)) # [batch_size, seq_len, seq_len] # 3. 缩放处理 scores = scores / math.sqrt(self.d_k) # 4. 应用mask(如果需要) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) # 5. 计算注意力权重 attention_weights = F.softmax(scores, dim=-1) attention_weights = self.dropout(attention_weights) # 6. 计算输出 output = torch.matmul(attention_weights, V) # [batch_size, seq_len, d_v] return output, attention_weights

数值稳定性分析

在实现Self-Attention时,数值稳定性是一个重要问题:

Softmax的数值稳定性

直接计算softmax可能导致数值溢出:

def softmax_unstable(x): exp_x = torch.exp(x) # 可能导致数值溢出 return exp_x / torch.sum(exp_x, dim=-1, keepdim=True) def softmax_stable(x): # 减去最大值以提高数值稳定性 x_max = torch.max(x, dim=-1, keepdim=True)[0] exp_x = torch.exp(x - x_max) return exp_x / torch.sum(exp_x, dim=-1, keepdim=True)

缩放因子的作用

缩放因子√d_k的主要作用:

  1. 防止梯度消失: 当d_k较大时,QK^T的值会变大,导致softmax进入饱和区域
  2. 稳定训练: 合适的缩放可以保持梯度在合适的范围内
  3. 平衡相似度: 防止相似度值过大或过小

1.2.3 Multi-Head Self-Attention

单头Self-Attention只能关注一种模式,而Multi-Head Self-Attention允许模型同时关注多种不同的模式。

多头注意力的结构

MultiHead(Q, K, V) = Concat(head_1, ..., head_h)W^O

其中:

  • head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
  • W^O ∈ ℝ^(h·d_v×d_model): 输出投影矩阵
  • h: 头的数量

代码实现

class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1): super(MultiHeadAttention, self).__init__() assert d_model % num_heads == 0, "d_model必须能被num_heads整除" self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads self.d_v = d_model // num_heads # 多头注意力层 self.self_attention = SelfAttention(d_model, self.d_k, self.d_v, dropout) # 输出投影 self.W_O = nn.Linear(num_heads * self.d_v, d_model) # Layer Normalization self.layer_norm = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): batch_size, seq_len, d_model = x.shape # 保存残差连接 residual = x # 应用多头注意力 attention_output, attention_weights = self.self_attention(x, mask) # 输出投影 output = self.W_O(attention_output) # 残差连接和Layer Normalization output = self.layer_norm(residual + self.dropout(output)) return output, attention_weights

多头注意力的优势

  1. 模式多样性: 每个头可以学习不同的注意力模式
  2. 并行计算: 各头之间可以并行计算,效率高
  3. 特征互补: 不同头捕获的信息相互补充
  4. 鲁棒性: 某个头失效时,其他头仍能工作

1.2.4 Masked Self-Attention

在解码器中,我们需要防止当前位置关注未来的位置,这时需要使用Masked Self-Attention。

Mask的实现

def create_padding_mask(seq, pad_idx=0): """ 创建填充掩码 seq: [batch_size, seq_len] 返回: [batch_size, 1, 1, seq_len] """ return (seq != pad_idx).unsqueeze(1).unsqueeze(2) def create_look_ahead_mask(seq_len): """ 创建前瞻掩码 返回: [seq_len, seq_len] """ mask = 1 - torch.triu(torch.ones(seq_len, seq_len), diagonal=1) return mask == 0 # 位置为0的地方被mask

Masked Self-Attention实现

class MaskedSelfAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1): super(MaskedSelfAttention, self).__init__() self.multi_head_attention = MultiHeadAttention(d_model, num_heads, dropout) def forward(self, x, mask=None): return self.multi_head_attention(x, mask)

1.2.5 Self-Attention的变体

Relative Position Self-Attention

考虑位置信息的Self-Attention变体:

class RelativePositionSelfAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1): super(RelativePositionSelfAttention, self).__init__() self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads # 相对位置编码 self.relative_positions = nn.Parameter(torch.zeros(2 * d_model - 1, self.d_k)) self.W_Q = nn.Linear(d_model, d_model, bias=False) self.W_K = nn.Linear(d_model, d_model, bias=False) self.W_V = nn.Linear(d_model, d_model, bias=False) self.W_O = nn.Linear(d_model, d_model) self.layer_norm = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): batch_size, seq_len, d_model = x.shape # 线性变换 Q = self.W_Q(x) K = self.W_K(x) V = self.W_V(x) # 计算相对位置编码 positions = torch.arange(seq_len).expand(seq_len, seq_len) relative_positions = positions - positions.transpose(0, 1) relative_positions = torch.clamp(relative_positions, -self.d_model + 1, self.d_model - 1) relative_embeddings = self.relative_positions[relative_positions + self.d_model - 1] # 添加相对位置信息 K = K + relative_embeddings # 注意力计算 scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) attention_weights = F.softmax(scores, dim=-1) output = torch.matmul(attention_weights, V) # 输出投影 output = self.W_O(output) output = self.layer_norm(x + self.dropout(output)) return output

1.2.6 Self-Attention的优化技术

1. 缓存机制

在解码过程中,可以使用缓存来保存已经计算的Key和Value:

class CachedSelfAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1): super(CachedSelfAttention, self).__init__() self.multi_head_attention = MultiHeadAttention(d_model, num_heads, dropout) def forward(self, x, cache=None, mask=None): if cache is None: # 首次计算 return self.multi_head_attention(x, mask) else: # 使用缓存的Key和Value cached_K, cached_V = cache K = torch.cat([cached_K, x], dim=1) V = torch.cat([cached_V, x], dim=1) Q = x # 注意力计算 scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) attention_weights = F.softmax(scores, dim=-1) output = torch.matmul(attention_weights, V) # 更新缓存 new_cache = (K, V) return output, new_cache

2. 稀疏注意力

只计算部分位置之间的注意力:

class SparseSelfAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1, sparsity_pattern='strided'): super(SparseSelfAttention, self).__init__() self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads self.sparsity_pattern = sparsity_pattern self.multi_head_attention = MultiHeadAttention(d_model, num_heads, dropout) def create_sparse_mask(self, seq_len, device='cpu'): """ 创建稀疏注意力掩码 """ mask = torch.ones(seq_len, seq_len, device=device) if self.sparsity_pattern == 'strided': # 步长稀疏模式 stride = 2 for i in range(seq_len): for j in range(seq_len): if abs(i - j) > stride: mask[i, j] = 0 elif self.sparsity_pattern == 'local': # 局部注意力模式 window_size = 5 for i in range(seq_len): for j in range(seq_len): if abs(i - j) > window_size: mask[i, j] = 0 return mask == 0 def forward(self, x, mask=None): # 如果没有提供掩码,使用稀疏掩码 if mask is None: seq_len = x.shape[1] mask = self.create_sparse_mask(seq_len, x.device) return self.multi_head_attention(x, mask)

3. FlashAttention优化

FlashAttention是一种高效的注意力计算方法,减少了内存访问:

class FlashAttention(nn.Module): def __init__(self, d_model, num_heads, dropout=0.1): super(FlashAttention, self).__init__() self.d_model = d_model self.num_heads = num_heads self.d_k = d_model // num_heads self.W_Q = nn.Linear(d_model, d_model, bias=False) self.W_K = nn.Linear(d_model, d_model, bias=False) self.W_V = nn.Linear(d_model, d_model, bias=False) self.W_O = nn.Linear(d_model, d_model) self.layer_norm = nn.LayerNorm(d_model) self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): batch_size, seq_len, d_model = x.shape # 线性变换 Q = self.W_Q(x) K = self.W_K(x) V = self.W_V(x) # 分割为多头 Q = Q.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) K = K.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) V = V.view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) # 计算注意力分数 scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) # 应用mask if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) # 计算注意力权重 attention_weights = F.softmax(scores, dim=-1) # 计算输出 output = torch.matmul(attention_weights, V) # 重组 output = output.transpose(1, 2).contiguous().view(batch_size, seq_len, d_model) # 输出投影 output = self.W_O(output) output = self.layer_norm(x + self.dropout(output)) return output

1.2.7 Self-Attention在Transformer中的应用

Encoder中的Self-Attention

class TransformerEncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super(TransformerEncoderLayer, self).__init__() # 多头自注意力 self.self_attention = MultiHeadAttention(d_model, num_heads, dropout) # 前馈网络 self.feed_forward = nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model) ) # Layer Normalization self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) # Dropout self.dropout = nn.Dropout(dropout) def forward(self, x, mask=None): # 自注意力子层 attention_output, _ = self.self_attention(x, mask) x = self.norm1(x + self.dropout(attention_output)) # 前馈网络子层 ff_output = self.feed_forward(x) x = self.norm2(x + self.dropout(ff_output)) return x

Decoder中的Self-Attention

class TransformerDecoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout=0.1): super(TransformerDecoderLayer, self).__init__() # 掩码自注意力 self.masked_self_attention = MaskedSelfAttention(d_model, num_heads, dropout) # 编码器-解码器注意力 self.cross_attention = MultiHeadAttention(d_model, num_heads, dropout) # 前馈网络 self.feed_forward = nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model) ) # Layer Normalization self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.norm3 = nn.LayerNorm(d_model) # Dropout self.dropout = nn.Dropout(dropout) def forward(self, x, encoder_output, self_attention_mask=None, cross_attention_mask=None): # 掩码自注意力子层 attention_output, _ = self.masked_self_attention(x, self_attention_mask) x = self.norm1(x + self.dropout(attention_output)) # 编码器-解码器注意力子层 cross_attention_output, _ = self.cross_attention(x, encoder_output, cross_attention_mask) x = self.norm2(x + self.dropout(cross_attention_output)) # 前馈网络子层 ff_output = self.feed_forward(x) x = self.norm3(x + self.dropout(ff_output)) return x

1.2.8 Self-Attention的可解释性

注意力权重的可视化

import matplotlib.pyplot as plt import seaborn as sns def plot_attention_heatmap(attention_weights, title="Attention Weights"): """ 绘制注意力权重热力图 """ plt.figure(figsize=(10, 8)) sns.heatmap(attention_weights.cpu().numpy(), cmap='Blues', cbar=True, xticklabels=range(attention_weights.shape[1]), yticklabels=range(attention_weights.shape[0])) plt.title(title) plt.xlabel('Key Position') plt.ylabel('Query Position') plt.show()

注意力模式的统计分析

def analyze_attention_patterns(attention_weights): """ 分析注意力模式 """ batch_size, seq_len, _ = attention_weights.shape # 计算注意力熵 attention_entropy = -torch.sum( attention_weights * torch.log(attention_weights + 1e-8), dim=-1 ) # 计算注意力集中度 attention_concentration = torch.max(attention_weights, dim=-1)[0] # 计算平均注意力权重 mean_attention = torch.mean(attention_weights, dim=-1) return { 'entropy': attention_entropy, 'concentration': attention_concentration, 'mean_attention': mean_attention }

1.2.9 Self-Attention的数学性质

1. 线性性质

Self-Attention是一个线性变换:

Attention(α·x₁ + β·x₂, K, V) = α·Attention(x₁, K, V) + β·Attention(x₂, K, V)

2. 旋转不变性

对于正交矩阵Q:

Attention(Q·x, Q·K, Q·V) = Q·Attention(x, K, V)

3. 泛化误差界

Self-Attention的泛化误差界与网络参数数量呈多项式关系:

R(Attention) = O(√(d_model · log(n) / n))

其中n是训练样本数量,d_model是模型维度。

1.2.10 总结与展望

本节深入探讨了Self-Attention的原理、实现和优化技术:

  1. 数学原理: 掌握了Self-Attention的线性变换和softmax计算
  2. 多头机制: 理解了多头注意力的结构和优势
  3. 变体分析: 学习了Masked Self-Attention和相对位置注意力
  4. 优化技术: 掌握了缓存、稀疏注意力和FlashAttention等优化方法
  5. 应用实践: 实现了完整的Transformer架构和机器翻译模型

Self-Attention作为现代深度学习的核心技术,其重要性不言而喻。在下一节中,我们将深入探讨Multi-Head Attention机制的实现和优化技术。


作者与出处
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 秃头披风侠的小龙虾 转发
评论区 (0)
U