Skip to content

位置编码

Transformer 没有循环结构,所以不知道词的顺序。位置编码告诉模型每个词在句子中的位置。

“我 爱 你” 和 “你 爱 我” 的词完全相同,但意思完全不同。没有位置编码,Self-Attention 无法区分它们。

原始 Transformer 使用正弦函数:

PE(pos,2i)=sin(pos100002i/d)PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right) PE(pos,2i+1)=cos(pos100002i/d)PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right)

其中 pospos 是位置,ii 是维度索引,dd 是总维度。

import numpy as np
import 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 (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)
方法优点缺点使用者
正弦编码可外推(训练长度外也能用)性能一般原始 Transformer
可学习编码简单不可外推BERT、GPT-1
RoPE相对位置、可外推实现稍复杂Llama、Qwen