位置编码 (Positional Encoding):给没有顺序的矩阵注入"位置感"
📂 所属阶段:第三阶段 — Transformer 革命(核心篇)
🔗 相关章节:多头注意力 (Multi-Head Attention) · Transformer 完整架构
1. 为什么我们必须加「位置信号」?
1.1 Self-Attention 天生「失序」
Self-Attention 最大的卖点是完全并行处理序列——不管句子里的词怎么排,注意力矩阵的计算逻辑都只会考虑「词与词之间的语义相似度」,完全不管谁在前谁在后。
举个最直观的反例:
这时候的 Transformer,本质上就是个只会看词袋的“高级统计模型”——完全没法做文本摘要、对话生成、翻译这类「顺序直接决定输出正确与否」的任务。
1.2 最直接的解决方案:给词贴「位置标签」
既然词本身不带位置,那我们就人为给每个位置生成一个唯一的“位置向量标签”,然后和词的语义向量直接相加/拼接(原始 Transformer 和主流模型都选「直接相加」,因为拼接会把向量维度翻一倍,增加不必要的计算量)。
这样一来:
- 词向量负责记住「这个词是什么意思」
- 位置向量负责记住「这个词在句子的第几个位置」
- 两者相加后的「最终表示向量」,就同时具备了语义和位置双重属性。
2. 原始 Transformer 用的:正弦/余弦绝对位置编码
绝对位置编码的思路最简单粗暴——直接给「位置0」「位置1」……「位置N」分别分配固定的、完全不一样的向量。原始 Transformer 没有用随机生成的,而是用了一组数学性质非常好的正弦、余弦函数组合。
2.1 完整 PyTorch 实现
我们直接搬用 PyTorch 官方教程里优化过的版本(加上了 Dropout 防止过拟合,还把位置向量存在 buffer 里,训练时不会被反向传播修改,但会随模型一起保存):
2.2 为什么偏偏选正弦余弦?
你可能会问:「随便生成一组固定的随机向量不行吗?为什么要用这么麻烦的三角函数?」
原因有三个,这三个性质是随机固定向量完全没有的:
- 相对位置关系可以线性表示——这句话有点绕,但你可以理解成:模型能很容易地学会「位置3比位置0远3」这种相对信息。这对翻译、摘要这类需要「关注前后词位置差」的任务特别重要。
- 可以泛化到未见过的长序列——比如预定义的 max_len 是 5000,但推理时突然遇到了 6000 个词的长文本?没关系!直接用同样的正弦余弦公式算后面 1000 个位置的编码就行,不用重新训练。
- 计算高效,没有额外参数——预定义好的,不用反向传播更新,推理和训练都快。
3. 端到端学习的:可学习绝对位置编码
正弦余弦编码虽然好,但它是人工设计的固定值——不一定能完美贴合所有任务的需求。这时候就可以用「可学习的位置编码」:直接把位置向量当成模型的参数,和词向量一起训练。
3.1 极简 PyTorch 实现
用 PyTorch 自带的 nn.Embedding 就能搞定,代码比正弦余弦还短:
3.2 适用场景
可学习位置编码的效果通常比正弦余弦好一点,尤其是在短文本、特定任务(比如文本分类、命名实体识别) 上。但它有个硬伤:预定义的 max_len 就是极限——推理时遇到更长的序列,只能截断或者补 0(或者重新训练,但代价太大)。
4. 现代 LLM 标配:RoPE(旋转位置编码)
绝对位置编码不管是固定的还是可学习的,都是直接加到词向量上——对于现在的超长上下文大模型(比如 LLaMA 3.1 的 128K、Claude 3.5 Sonnet 的 200K)来说,这种方式会有「长距离位置信息衰减」的问题。
RoPE(Rotary Position Embedding,旋转位置编码)是 2021 年苏剑林团队提出的,完美解决了这个问题——它不是加到词向量上,而是直接对 Attention 的 Q(查询)和 K(键)做“旋转”操作,天然就保留了长距离的相对位置关系。
4.1 核心简化逻辑(大白话)
我们不用管复杂的数学推导,只需要记住两点:
- 它把 Q/K 的向量两两分组(比如 d_model=512,分成 256 组)
- 每组都用不同的「角度」旋转——位置越靠后,旋转的角度越大
这样一来,当我们计算 Q 和 K 的点积(Attention 权重的核心步骤)时,相对位置差的信息就自动被包含进去了,而且衰减非常慢,特别适合超长上下文。
4.2 极简 PyTorch 实现(只做核心旋转逻辑)
5. 三种主流位置编码对比小结
💡 金句总结:没有位置编码的 Transformer = 高级词袋模型,顺序颠倒输出不变;2026 年想做大模型/长文本任务,直接上 RoPE!
🔗 扩展阅读

