位置编码为 Transformer 自注意力机制注入位置信息,解决其置换不变性问题。
核心问题
自注意力机制的置换不变性使其无法区分序列顺序。位置编码通过添加位置向量解决:
设计原则
| 原则 | 说明 |
|---|---|
| 唯一性 | 每个位置有独特的编码 |
| 可解释性 | 相对距离编码稳定 |
| 外推性 | 处理超出训练长度的序列 |
| 效率 | 计算不应成为瓶颈 |
经典正弦位置编码
核心性质
- 唯一性:不同频率的正弦/余弦组合形成唯一信号
- 可外推:理论上支持任意长度序列
- 相对位置: 可由 线性变换得到
位置编码演进
| 类型 | 代表模型 | 优点 | 缺点 |
|---|---|---|---|
| 可学习绝对编码 | BERT、GPT-2 | 简单有效 | 无法外推 |
| 固定绝对编码 | Transformer | 可外推、无参数 | 长序列效果下降 |
| 相对位置编码 | T5、DeBERTa | 更直观 | 实现复杂 |
| RoPE | LLaMA | 优雅融合相对位置 | 需修改注意力结构 |
PyTorch 实现
class PositionalEncoding(nn.Module):
def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 5000):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
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)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)