Transformer没有循环结构,位置编码告诉它"顺序"信息。本课深入各种位置编码方法和多头注意力的细节实现。
自注意力是置换不变的:Attention(π(x)) = π(Attention(x))
没有位置信息,"我爱你"和"你爱我"无法区分!
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
优点:可以推广到任意长度
PE(pos+k)可以表示为PE(pos)的线性函数
这使模型可以学习相对位置关系
BERT和GPT使用可学习的位置嵌入:nn.Embedding(max_len, d_model)
优点:更灵活,可能学得更好
缺点:不能推广到训练时未见过的长度
将位置信息融入注意力计算
q·k 的内积只依赖相对位置(m-n)
LLaMA等现代模型使用RoPE
import torch
import torch.nn as nn
import math
# 位置编码实现
class SinusoidalPositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1).float()
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]
class LearnedPositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=512):
super().__init__()
self.pe = nn.Parameter(torch.randn(1, max_len, d_model) * 0.02)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
# RoPE实现
class RotaryPositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=2048):
super().__init__()
inv_freq = 1.0 / (10000 ** (torch.arange(0, d_model, 2).float() / d_model))
self.register_buffer('inv_freq', inv_freq)
def forward(self, x):
seq_len = x.size(1)
t = torch.arange(seq_len, device=x.device).float()
freqs = torch.outer(t, self.inv_freq)
emb = torch.cat([freqs, freqs], dim=-1)
cos_emb = emb.cos().unsqueeze(0)
sin_emb = emb.sin().unsqueeze(0)
return x * cos_emb + self._rotate_half(x) * sin_emb
def _rotate_half(self, x):
x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
return torch.cat([-x2, x1], dim=-1)
# 对比
d_model = 64
seq_len = 20
x = torch.randn(1, seq_len, d_model)
encodings = {
"正弦PE": SinusoidalPositionalEncoding(d_model),
"可学习PE": LearnedPositionalEncoding(d_model),
"RoPE": RotaryPositionalEncoding(d_model),
}
for name, enc in encodings.items():
out = enc(x)
diff = (out - x).abs().mean().item()
params = sum(p.numel() for p in enc.parameters())
print(f"{name}: 编码后差异={diff:.6f}, 可训练参数={params}")
# 正弦PE的性质:线性关系
print("\n=== 正弦PE的线性关系 ===")
sin_pe = SinusoidalPositionalEncoding(d_model)
pe = sin_pe.pe[0, :10, :8] # 前10位置,前8维度
print("位置编码(前10位置,前8维):")
for pos in range(5):
vals = [f"{pe[pos, d]:.4f}" for d in range(8)]
print(f" pos={pos}: {vals}")
# 不同距离的PE相似度
print("\nPE点积相似度(位置0 vs 位置k):")
for k in [1, 2, 5, 10, 20]:
sim = torch.dot(pe[0], sin_pe.pe[0, k, :8]).item()
print(f" pos0·pos{k}: {sim:.4f}")
本课的核心知识点构成了序列模型学习的重要一环。让我们从更高维度审视这些知识之间的关系:
⚠️ 初学者常犯的错误:
💡 最佳实践:
深度学习是一个整体——每一课的知识都不是孤立的:
💡 继续深入的学习路径:
实现Attention with Linear Biases,分析其外推能力。
实现相对位置编码,与绝对位置编码对比。
测试不同位置编码在短序列训练、长序列推理时的表现。
⚠️ 需要避免的典型错误:
💡 提升模型性能的系统方法:
理解本课内容需要将其置于更大的知识网络中。每个核心概念都不是孤立的,它们相互关联、相互支撑:
建立正确的直觉比记住公式更重要:
1. 维度直觉:高维空间中,数据分布与低维直觉截然不同(维度诅咒)
2. 信息瓶颈:神经网络逐层压缩信息,保留任务相关的、丢弃无关的
3. 流形假设:高维数据实际分布在低维流形上,学习即发现流形结构
4. 偏差-方差权衡:简单模型欠拟合(高偏差),复杂模型过拟合(高方差)
本课作为序列模型阶段的核心内容,与前后课程紧密关联:
| 关联课程 | 关联点 | 协同效应 |
|---|---|---|
| 前序课程 | 提供了本课的基础知识 | 循序渐进的理解 |
| 后续课程 | 本课内容是后续的基础 | 逐步构建能力 |
| 平行课程 | 同一阶段的互补知识 | 多角度深入理解 |
| 实战项目 | 综合运用所有知识 | 理论与实践结合 |
💡 准备面试时,确保能回答以下问题:
掌握本课内容后,可以通过以下方式继续深入:
位置编码赋予Transformer感知顺序的能力!