位置编码
Transformer 没有循环结构,所以不知道词的顺序。位置编码告诉模型每个词在句子中的位置。
为什么需要?
Section titled “为什么需要?”“我 爱 你” 和 “你 爱 我” 的词完全相同,但意思完全不同。没有位置编码,Self-Attention 无法区分它们。
正弦位置编码
Section titled “正弦位置编码”原始 Transformer 使用正弦函数:
其中 是位置, 是维度索引, 是总维度。
import numpy as npimport matplotlib.pyplot as plt
def sinusoidal_encoding(seq_len, d_model): pe = np.zeros((seq_len, d_model)) for pos in range(seq_len): for i in range(0, d_model, 2): angle = pos / (10000 ** (i / d_model)) pe[pos, i] = np.sin(angle) pe[pos, i + 1] = np.cos(angle) return pe
pe = sinusoidal_encoding(50, 128)plt.imshow(pe.T, aspect='auto', cmap='RdBu')plt.colorbar(label='编码值')plt.xlabel('位置'); plt.ylabel('维度')plt.show()RoPE:旋转位置编码
Section titled “RoPE:旋转位置编码”RoPE (Rotary Position Embedding) 是 Llama、GPT-NeoX 等现代模型使用的方法。核心思想:通过旋转向量来编码位置。
import torch
def apply_rope(x, pos, theta=10000.0): """对输入 x 应用旋转位置编码""" d = x.shape[-1] # 生成旋转角度 freqs = 1.0 / (theta ** (torch.arange(0, d, 2).float() / d)) angles = pos * freqs # (seq_len, d/2)
# 旋转:将每对维度视为复数并旋转 cos = torch.cos(angles).unsqueeze(0).unsqueeze(0) # (1, 1, seq, d/2) sin = torch.sin(angles).unsqueeze(0).unsqueeze(0)
x_even = x[..., 0::2] # 偶数维度 x_odd = x[..., 1::2] # 奇数维度
x_rotated_even = x_even * cos - x_odd * sin x_rotated_odd = x_even * sin + x_odd * cos
# 交错拼接 result = torch.stack([x_rotated_even, x_rotated_odd], dim=-1) return result.flatten(-2)三种编码对比
Section titled “三种编码对比”| 方法 | 优点 | 缺点 | 使用者 |
|---|---|---|---|
| 正弦编码 | 可外推(训练长度外也能用) | 性能一般 | 原始 Transformer |
| 可学习编码 | 简单 | 不可外推 | BERT、GPT-1 |
| RoPE | 相对位置、可外推 | 实现稍复杂 | Llama、Qwen |
- Attention — 位置编码输入到 Attention
- Transformer 教程 — 完整架构