位置编码解决 Transformer 的「顺序失忆症」,从绝对编码演进到相对编码,RoPE 已成主流。
为什么需要位置编码?
自注意力机制具有置换不变性:序列元素位置交换不影响注意力权重。位置编码为模型注入位置信息:
演进历程
| 类型 | 代表模型 | 特点 |
|---|---|---|
| 可学习绝对编码 | BERT, GPT-2 | 简单有效,但无法外推 |
| 固定绝对编码 | 原始 Transformer | 正弦函数,可外推 |
| 相对位置编码 | Transformer-XL, T5, DeBERTa | 建模相对距离,更有效 |
| 旋转位置编码 | LLaMA | 优雅融合位置与语义 |
正弦位置编码
核心性质:
- 唯一性:每个位置有独特编码
- 相对位置: 可由 线性变换得到
- 外推性:可处理超长序列
设计原则
- 唯一性:每个位置信号独特
- 外推性:优雅处理更长序列
- 效率:计算不成为瓶颈
- 融合方式:加法/乘法/注意力偏置
RoPE(旋转位置编码)
通过「旋转」融合位置信息:
注意力内积只与相对位置 有关:
优势:优雅编码相对位置,外推性强
PyTorch 实现
import torch
import torch.nn as nn
import math
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)总结
位置编码从「加法」演进到「乘法/旋转」,相对位置建模愈发重要。RoPE 以优雅方式融合位置与语义,已成为主流选择。