1. 为什么 Transformer 需要位置编码? Self-Attention 本质上是一个集合操作:$\text{Attention}(Q, K, V)$ 对输入的顺序不敏感(permutation equivariant)。如果打乱输入顺序,输出只是相应打乱,不包含任何顺序信息。但语言有明确的顺序语义(“猫追狗” ≠ “狗追猫”),必须注入位置信息。
2. 绝对位置编码 2.1 Sinusoidal (原版 Transformer) Vaswani 等人提出用正弦/余弦函数生成位置编码:
$$ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
$$ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) $$
性质:
不同维度对应不同频率的正弦波,从 $2\pi$ 到 $10000 \times 2\pi$ 对于任意偏移 $k$,$PE_{pos+k}$ 可以表示为 $PE_{pos}$ 的线性变换:$\sin(pos + k) = \sin(pos)\cos(k) + \cos(pos)\sin(k)$ 理论上可以外推到比训练时更长的序列,但实际效果不佳 import torch import math def sinusoidal_pos_encoding(max_len, d_model): 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) return pe 2.2 Learned Positional Embedding BERT、GPT-2 使用可学习的位置 Embedding:
self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size) # 前向传播 position_ids = torch.arange(seq_len).unsqueeze(0) embeddings = token_embeddings + self.position_embeddings(position_ids) 优点:灵活,模型可以学习最优的位置表示。缺点:硬编码了最大长度,无法外推。
...