大家好,我是AI领域的从业者,经常在CSDN上分享深度学习相关知识。今天我们来聊一个经典却又非常核心的话题——Transformer的位置编码(Positional Encoding,简称PE)。为什么说它核心?因为Transformer一出世就颠覆了序列建模,但它本身是“无序”的,没有位置信息,就相当于一堆散装单词拼不成句子。
这篇文章的目标是通俗、深入、实用,从零基础到高手都能看懂。我们会一步步拆解为什么需要位置编码、经典的正弦位置编码是怎么设计的、可学习的位置编码又有什么不同、相对位置编码(RoPE、ALiBi)的最新进展,最后还会附上大量PyTorch代码和可视化图表。
目录
- 4.1 公式详解
- 4.2 为什么用正弦/余弦函数?
- 4.3 可扩展性与相对位置感知
- 7.1 RoPE(Rotary Position Embedding)
- 7.2 ALiBi(Attention with Linear Biases)
- 7.3 T5相对偏置
1. 前言:Transformer为什么“失忆”?
想象一下,你在读这句话:“我爱吃苹果”。你能瞬间知道“我”是主语,“苹果”是宾语,顺序不能乱。但如果把单词打乱成“苹果吃爱我”,你就懵了。
RNN/LSTM 天生就有顺序信息,因为它们是逐个处理序列的,位置信息被隐含在时间步里。
Transformer 却不一样。它用自注意力机制(Self-Attention)一次性处理整个序列,计算每个词与其他词的关联时,完全不考虑顺序——这就好比一锅乱炖,香味混在一起,却分不清谁先谁后。
因此,位置编码 就成了Transformer的“救命稻草”,它给每个词贴上一个“身份证”,让模型知道“谁在前面,谁在后面”。
你猜猜看:如果不加位置编码,Transformer还能做什么任务?(欢迎评论区告诉我你的答案)


(图1-2:经典Transformer架构图,注意输入Embedding后直接加了Positional Encoding)
2. Transformer模型快速回顾
Transformer由Google在2017年《Attention is All You Need》提出,主要结构是Encoder-Decoder。
- 输入:词向量(Word Embedding) + 位置编码(Positional Encoding)
- 核心:Multi-Head Self-Attention + Feed-Forward Network
- 位置编码 通常加在词向量上: X = Embedding + Positional_Encoding
位置编码的维度必须和词向量相同(d_model),这样才能直接相加。
3. 位置编码的必要性:从RNN到Transformer的痛点
RNN的痛点大家都知道:梯度消失、并行度低。
Transformer解决了并行问题,但引入了新问题:绝对位置丢失。
举个例子:
句子A:The cat sat on the mat. 句子B:The mat sat on the cat.(语义完全不同)
如果没有位置编码,两个句子的表示几乎一样(词向量相同,只是顺序不同),模型就傻了。
位置编码的作用:
4. 经典方案:正弦/余弦位置编码
Transformer原论文用的就是这个,至今仍是许多模型的默认选择。
公式如下:
对于位置pos(从0开始),维度i(0到d_model/2-1):
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)
为什么这样设计?我们一步步拆解。
4.1 公式详解
- 周期性:用sin/cos函数,让不同位置的编码有周期性变化。
- 波长递增:低维度(小i)波长短(变化快),高维度波长长(变化慢)。
- 10000的指数:让波长从1到10000倍递增,覆盖短距离和长距离相对位置。
4.2 为什么用正弦/余弦函数?
证明相对位置感知:
假设位置pos和pos+k,PE(pos+k) 可以用PE(pos)的线性组合表示(用三角恒等式)。
这意味着注意力机制可以“感知”距离。
4.3 可扩展性与相对位置感知
正弦位置编码的最大优势:长度外推。训练时max_len=512,推理时可以直接用pos=6000,编码依然合理。



(图3-5:正弦位置编码可视化,低维变化快,高维变化慢)
5. 代码实战:PyTorch实现正弦位置编码
下面给出完整PyTorch实现,复制粘贴就能跑。
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
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)
pe = pe.unsqueeze(0) # [1, max_len, d_model]
self.register_buffer('pe', pe)
def forward(self, x):
# x: [batch, seq_len, d_model]
return x + self.pe[:, :x.size(1), :]
使用示例:
emb = nn.Embedding(10000, 512)
pe = PositionalEncoding(512)
input_ids = torch.randint(0, 10000, (2, 128)) # batch=2, seq=128
x = emb(input_ids)
x = pe(x)
print(x.shape) # torch.Size([2, 128, 512])
小实验:把max_len改成10000,试试推理时输入更长序列,看看效果如何?欢迎评论区分享你的实验结果!
6. 可学习的位置编码
BERT、GPT等模型常用可学习的位置编码。
实现方式:一个可训练的Embedding层。
class LearnedPositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=512):
super().__init__()
self.pos_emb = nn.Parameter(torch.zeros(1, max_len, d_model))
def forward(self, x):
return x + self.pos_emb[:, :x.size(1), :]
优缺点对比:
- 优点:模型可以自己学到最优位置表示。
- 缺点:不能外推(训练max_len=512,推理6000会报错或效果差)。


(图12-13:可学习位置编码的热力图,显示了训练后的相似性)
7. 相对位置编码的崛起
近年来,相对位置编码 逐渐取代绝对位置编码,尤其在长序列任务中。
7.1 RoPE(Rotary Position Embedding)
RoPE是目前最火的方案(LLaMA、PaLM、Qwen等大模型都在用)。
核心思想:不是加一个向量,而是旋转查询和键向量。
对于维度对 (q_{2i}, q_{2i+1}):
\\begin{pmatrix}
q'_{2i} \\\\
q'_{2i+1}
\\end{pmatrix}
=
\\begin{pmatrix}
\\cos \\theta & -\\sin \\theta \\\\
\\sin \\theta & \\cos \\theta
\\end{pmatrix}
\\begin{pmatrix}
q_{2i} \\\\
q_{2i+1}
\\end{pmatrix}
其中 θ = pos × 10000^{-2i/d}
优点:
- 天然支持相对位置(注意力分数只依赖距离)
- 长度外推能力极强
- 计算高效(可以预计算旋转矩阵)
代码实现(简化版):
def apply_rotary_emb(x, freqs):
# x: […, seq_len, dim]
# freqs: [seq_len, dim//2] complex
x_ = x.float().reshape(*x.shape[:–1], –1, 2)
x_out = torch.view_as_complex(x_)
x_out = x_out * freqs
return torch.view_as_real(x_out).flatten(–2).type_as(x)


(图7-8:RoPE的旋转示意图,直观理解为什么能捕捉相对位置)
7.2 ALiBi(Attention with Linear Biases)
ALiBi更简单:直接在注意力分数上加一个随距离线性衰减的偏置。
attn = QK^T / \\sqrt{d} – m \\cdot |i-j|
m是超参数。
优点:极简,无需额外参数,支持长度外推。
缺点:相对位置感知不如RoPE精细。
7.3 T5相对偏置
T5用可学习的相对位置偏置,桶化处理距离(0~32等)。
8. 不同位置编码的对比表格
| 正弦/余弦 | 绝对 | 否 | 强 | 中等 | 原Transformer | 经典、简单、外推好 |
| 可学习 | 绝对 | 是 | 弱 | 弱 | BERT, GPT-2 | 灵活,但不能外推 |
| RoPE | 相对 | 否 | 极强 | 强 | LLaMA, PaLM, Qwen | 当前最优,旋转思想优雅 |
| ALiBi | 相对 | 否 | 强 | 中等 | MPT, Falcon | 超简单,无需额外参数 |
| T5相对偏置 | 相对 | 是 | 中等 | 强 | T5, UL2 | 桶化处理,适合中短序列 |

(图10:位置编码对比可视化)
9. 可视化与实验:一图胜千言
下面是几种位置编码的热力图对比(余弦相似度):
- 正弦位置编码:对角线清晰,距离越远相似度越低。
- 可学习:训练后出现模式。
- RoPE:相对位置更明显。
建议读者自己用Matplotlib画图实验:
import matplotlib.pyplot as plt
import seaborn as sns
# 假设pe是 [seq_len, d_model]
sns.heatmap(pe.detach().cpu().numpy(), cmap='viridis')
plt.title('Positional Encoding Heatmap')
plt.show()
你会发现:低维变化剧烈,高维几乎是直线——这就是为什么能同时捕捉短距和长距关系。
10. 常见问题
Q:位置编码加在Embedding前还是后? A:后,加在词向量上。
Q:为什么不用简单的1,2,3…位置向量? A:那样学不到相对位置,且数值爆炸。
Q:RoPE怎么实现长度外推? A:因为旋转角度只依赖相对距离,不依赖绝对位置。
互动环节:你更喜欢哪种位置编码?为什么?欢迎评论区讨论!如果你有自己的实现代码,也欢迎分享~
11. 总结与展望
位置编码是Transformer成功的基石,从原论文的正弦编码,到如今的RoPE,经历了从绝对到相对、从固定到旋转的演进。
一句话总结:位置编码让无序的注意力机制“有序”起来,是序列建模的灵魂。
未来,随着序列越来越长(100k+),相对位置编码(尤其是RoPE)会继续主导。
希望这篇文章帮你彻底搞懂位置编码!如果觉得有用,欢迎点赞、收藏、转发~
12. 参考文献
如果你想看更多Transformer系列文章,欢迎关注我!下期见~





