共计 3306 个字符,预计需要花费 9 分钟才能阅读完成。
从 RNN 到 Transformer 的进化
Transformer 模型彻底改变了自然语言处理领域的游戏规则。在机器翻译任务中,基于 Transformer 的模型比传统 RNN 提升了超过 60% 的 BLEU 分数。这种架构优势后来被成功迁移到计算机视觉领域,Vision Transformer 在 ImageNet 分类任务上达到了与 CNN 相当甚至更好的性能。更令人惊讶的是,统一使用 Transformer 架构的多模态模型(如 CLIP)展现了强大的跨模态理解能力。

传统 RNN 和 LSTM 存在两个主要缺陷:
- 顺序计算的特性导致训练无法并行化,处理长序列时效率低下
- 随着序列长度增加,早期输入的信息会逐渐衰减(长期依赖问题)
Self-Attention 机制通过三个关键设计解决了这些问题:
- 并行计算 :所有位置的注意力权重可以同时计算
- 全局依赖 :任意两个 token 之间都有直接的连接路径
- 可解释性 :注意力权重提供了模型决策过程的直观解释
核心机制解析
多头注意力数学原理
标准的 Scaled Dot-Product Attention 计算公式为:
$$Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V$$
其中:
– $Q \in \mathbb{R}^{n\times d_k}$ (Query 矩阵)
– $K \in \mathbb{R}^{m\times d_k}$ (Key 矩阵)
– $V \in \mathbb{R}^{m\times d_v}$ (Value 矩阵)
– $d_k$ 是 key 向量的维度
多头注意力将这个过程重复 h 次:
$$MultiHead(Q,K,V) = Concat(head_1,…,head_h)W^O$$
$$head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)$$
位置编码实现
由于 Transformer 不包含递归和卷积操作,需要显式注入位置信息:
$$PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}})$$
$$PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}})$$
这种正弦曲线设计使模型能够学习到相对位置关系,且可以处理比训练时更长的序列。
PyTorch 实现详解
import torch
import torch.nn as nn
import math
class TransformerEncoderLayer(nn.Module):
def __init__(self, d_model=512, nhead=8, dim_feedforward=2048):
super().__init__()
# 多头注意力层 (输入输出维度保持 d_model 不变)
self.self_attn = nn.MultiheadAttention(d_model, nhead)
# 前馈网络 (维度变化: d_model -> dim_feedforward -> d_model)
self.linear1 = nn.Linear(d_model, dim_feedforward)
self.linear2 = nn.Linear(dim_feedforward, d_model)
# 层归一化
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
def forward(self, src):
# src 形状: (seq_len, batch_size, d_model)
# 残差连接 1
src2 = self.self_attn(src, src, src)[0] # 注意力输出
src = src + self.norm1(src2) # Add & Norm
# 残差连接 2
src2 = self.linear2(torch.relu(self.linear1(src)))
src = src + self.norm2(src2)
return src # 输出维度保持不变
文本分类实战
数据准备
from torch.utils.data import Dataset, DataLoader
class TextDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_len):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = str(self.texts[idx])
label = self.labels[idx]
# Tokenize (假设使用预训练的 tokenizer)
encoding = self.tokenizer(
text,
max_length=self.max_len,
padding='max_length',
truncation=True,
return_tensors='pt'
)
return {'input_ids': encoding['input_ids'].flatten(),
'attention_mask': encoding['attention_mask'].flatten(),
'label': torch.tensor(label, dtype=torch.long)
}
# 创建 DataLoader 示例
batch_size = 32
train_loader = DataLoader(
dataset=train_dataset,
batch_size=batch_size,
shuffle=True
)
性能分析
with torch.profiler.profile(activities=[torch.profiler.ProfilerActivity.CUDA],
schedule=torch.profiler.schedule(wait=1, warmup=1, active=3),
on_trace_ready=torch.profiler.tensorboard_trace_handler('./log')
) as prof:
for step, batch in enumerate(train_loader):
if step >= 5:
break
inputs = batch['input_ids'].to(device)
outputs = model(inputs)
prof.step()
常见问题与优化
梯度消失对策
- 使用学习率 warmup:前 1000 步从 0 线性增长到目标学习率
- 采用 Adam 优化器时,beta2 参数设置为 0.98(默认 0.999 可能过大)
- 梯度裁剪(norm=1.0)
长序列处理
KV 缓存技术可以显著减少推理时的计算量:
# 推理时缓存过去的 K 和 V
past_key_values = None
for i in range(max_length):
outputs = model(input_ids, past_key_values=past_key_values)
past_key_values = outputs.past_key_values # 更新缓存
混合精度训练
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
开放性问题
- 如何设计稀疏注意力模式来降低 O(n²) 的计算复杂度?
- 在跨模态任务中,如何优化不同模态之间的注意力交互?
- 能否用更高效的位置编码方案替代三角函数式编码?
Transformer 架构虽然强大,但仍有广阔的优化空间等待探索。建议读者从修改注意力头数、调整层深度等简单实验开始,逐步深入理解各组件的作用机制。在实践中,持续关注 CUDA 内存使用情况和计算图可视化,这是优化模型性能的两个重要视角。
