Appearance
位置编码
自注意力对词序「视而不见」——它把每个词一视同仁地全连接,所以「猫追狗」和「狗追猫」在纯注意力眼里是一样的。位置编码(Positional Encoding) 就是往每个词向量里注入「我是第几个位置」的信息,把丢失的顺序感补回来。
为什么必须有它
输入: [猫] [追] [狗]
纯自注意力看到的: 一个"无序集合" {猫, 追, 狗}
↓
"猫追狗" 与 "狗追猫" 表示相同 ❌ 语义完全反了!必须给每个位置一个「身份证」,让模型能区分先后。位置信息有两种主流注入思路:
- 绝对位置编码:直接告诉模型「你在第几位」。(原始 Transformer、BERT、GPT-2)
- 相对位置编码:只强调「你俩隔多远」。(T5、ALiBi、RoPE)
本页先讲透最经典的绝对正弦编码,再对比可学习编码,相对/RoPE 详见 位置编码演进RoPE。
正弦/余弦位置编码(Sinusoidal)
原始论文用不同频率的正弦波给每个位置生成一个 d_model 维向量:
[ PE_{(pos,,2i)} = \sin!\left(\frac{pos}{10000^{2i/d_{model}}}\right), \quad PE_{(pos,,2i+1)} = \cos!\left(\frac{pos}{10000^{2i/d_{model}}}\right) ]
pos:词在句子中的位置(0, 1, 2, ...)i:向量的维度下标(0 到 d_model/2)
直觉:像二进制计数器一样
想象用一串「指针转速不同的时钟」来给位置编号:
维度0 → 转得飞快的指针(高频) ┐
维度1 → 稍慢的指针 │ 组合起来,每个 pos 都有独一无二的
维度2 → 更慢... │ "指纹"; 相邻位置的指纹也很像,
... │ 远位置的指纹差异大
维度末尾 → 几乎不动的指针(低频) ┘- 低频维度:变化慢,捕捉「大概在文章哪个区域」(长程位置)。
- 高频维度:变化快,区分「紧挨着的两个词」(精细位置)。
- 多频率叠加,就能唯一且平滑地编码任意位置。
3 行代码看懂
python
import torch, math
def positional_encoding(seq_len, d_model):
pe = torch.zeros(seq_len, d_model)
pos = torch.arange(seq_len).unsqueeze(1).float() # [seq_len, 1]
div = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(pos * div) # 偶数维用 sin
pe[:, 1::2] = torch.cos(pos * div) # 奇数维用 cos
return pe # 形状 [seq_len, d_model]
print(positional_encoding(4, 8))它与词向量怎么「合体」
位置编码不另开一路,而是直接加到词嵌入上:
词嵌入 x [0.12, -0.34, 0.55, ...] ← 讲的是"是什么词"
+ 位置编码 PE [0.00, 0.84, 0.54, ...] ← 讲的是"在第几位"
─────────────────────────────────────────
= 输入 z [0.12, 0.50, 1.09, ...] ← 既含语义又含位置python
x = embedding(token_ids) # [B, T, d_model] 语义
x = x + pe[:T].unsqueeze(0) # 加上位置编码(逐元素相加)为什么是「加」而不是「拼」?相加不改变维度(仍是 d_model),后续模块无需改动;模型有能力从混合向量里分别解出语义与位置线索。
可学习位置编码(Learned)
BERT、GPT-2 的做法更简单粗暴:把位置也当成一张可学习的嵌入表,和词嵌入一样在训练中学出来。
python
# 位置 0..max_len-1,每个位置一个 d_model 向量,随模型一起训练
self.pos_embedding = nn.Embedding(max_len, d_model)
x = tok_embedding(ids) + self.pos_embedding(position_ids)| 方案 | 优点 | 缺点 |
|---|---|---|
| 正弦(固定) | 无需训练;理论上可外推到未见长度 | 实际外推效果一般;表达力受限 |
| 可学习 | 灵活,模型自己决定位置含义 | 超出训练最大长度就没法用;占参数 |
实践发现:只要序列长度不超出训练上限,可学习位置编码效果通常略好,所以 BERT/GPT-2 都选了它;而现代长上下文模型大多转向 RoPE(见进阶页)。
位置编码能力上限:最大长度
采用可学习编码的模型都有一个 max_position_embeddings,比如 GPT-2 是 1024:
训练时只见过位置 0..1023
→ 推理给到第 1024 个 token,没有对应的位置向量 → 直接报错/效果崩塌这正是「上下文长度」的物理来源之一,也是后续 RoPE + 位置插值要突破的限制。
小结
- 自注意力天生「无序」,必须靠位置编码补回顺序信息。
- 正弦编码:多频率三角函数拼出唯一「位置指纹」,免训练。
- 可学习编码:查表学出来,序列不超限时更灵活(BERT/GPT-2)。
- 二者都是加到词嵌入上,不改变
d_model维度。 - 想支持更长上下文、更好外推 → 现代方案转向 RoPE。
下一步
语义有了、位置有了,接下来是全场最重要的引擎:这些向量如何「互相关注」 → 注意力机制。