想象你经营一家电影院。观众入场时,每个人手里都攥着一张电影票,票面印着“3排5座”。没有这个座号,满大厅的人根本不知道坐哪儿,电影也没法开场。 Transformer就像一个不会按顺序读句子的“放映员”——它不像人读书那样一个字一个字从左往右看,而是一眼把所有词全扫进视野。如果这些词不分前后排队,模型就会把“我爱你”和“你爱我”当成同一句话。位置编码,就是给每个词发一张“座位号牌”。 但“座位号”有两种印法。一种叫正弦位置编码:电影院提前印好一张公式表,无论扩建多少排座位,管理员都能现场算出新座位的编号,公式固定、零成本。另一种叫可学习位置编码:工作人员每天开场前手工写名牌,写着写着就背下来一套座号,灵活贴合自家影厅,但座位超出名牌数量时就得抓瞎。 这篇文章就带你从“电影院座号系统”出发,看懂这两种方案各自的原理、外推能力、参数开销,以及该在什么场合选哪种。
弄懂两种位置编码的差别是Transformer架构设计的必修课。无论你在做模型复现、长文本任务还是预训练设计,选对位置编码方案都能让模型少走弯路。
可视化演示
展示正弦位置编码的多频率波形生成、可学习位置编码的嵌入矩阵训练、以及两者外推能力的关键差异。切换模式理解方案选择核心。
本节配有交互动效(进入视口后自动加载,约 3KB)。
正弦位置编码多频率波形 使用不同频率的sin/cos函数为每个位置生成固定编码,高频捕捉局部细节、低频捕捉全局位置 无需训练参数,基于连续数学函数,天然支持任意长度序列的位置计算 PE(pos,2i)=sin(pos/10000^(2i/d)) 正弦编码是Transformer原始论文的选择
为何需要位置编码
电影开场前,几十位观众同时涌进放映厅,黑灯瞎火,谁也不知道该往哪儿走。只有看了票上的座号,他们才能找到自己的位置。Transformer也一样——它的Attention机制不按顺序读句子,而是把整句话的所有词一次性摊在桌上,就像一群蒙眼的观众同时站在大厅中央。
RNN这类老模型天生是按顺序读词的:读第t个词时,它的“记忆”里已经装进了前t-1个词的信息。但Transformer全靠Self-Attention并行计算,词的先后顺序没有任何天然的载体。如果不做特殊处理,“我爱你”和“你爱我”对模型来说就只是同一袋词被倒进了不同的碗里。
解决方案是在每个词的输入向量上加一个“位置向量”。设第t个词原本的词向量是 $\mathbf{x}_t$,我们生成一个位置向量 $\mathbf{p}_t$,把它们逐位相加,得到带位置信息的向量 $\tilde{\mathbf{x}}_t$。这个向量同时携带“这个词是谁”和“这个词在第几位”的信息,Attention层计算相似度时才能区分不同语序。
那么,这张“座位号牌”到底怎么印?接下来的两种方案给出了截然不同的答案。正弦位置编码像是标准座号印刷系统——管理员手里有一张公式表,无论大厅扩建到几百排,他掏出公式几秒钟就能算出新座位的编号。可学习位置编码则像手工写名牌——开工前逐个座位写号,写多了自然记住,但新座位来了就抓瞎。
- 词向量 \mathbf{x}_t第t个词经过嵌入层得到的向量,储存“这个词是谁”的身份信息,就像观众的名字。
- 位置向量 \mathbf{p}_t为位置t生成的专属向量,储存“这个词在第几位”的顺序信息,就像票上的座位号。
- 逐位相加融合两个向量对应分量相加,身份信息和位置信息合二为一,后续每一层计算都能同时读取两者。
正弦位置编码原理
有些电影院用的是“标准座号印刷系统”——管理员手里有一张公式表,无论大厅扩建到几百排,他掏出公式几秒钟就能算出新座位的编号。这些编号不是背下来的,是算出来的。正弦位置编码就是这样一张“公式表”。
原始Transformer论文就采用了这套公式:给定位置 $pos$ 和维度下标 $i$,位置编码的第2i维用sin、第2i+1维用cos。公式中的分母 $10000^{2i/d}$ 控制了每一维的振荡频率。
细节在于这个分母设计:维度下标i越小,分母越小,对应sin/cos的周期越短、频率越高,相邻位置的编码差异就越明显;i越大,分母越大,周期越长、频率越低,编码值随位置变化得越缓慢。每个位置的d维向量因此构成一串从“快速变化”到“缓慢变化”的波。论文还证明了一个漂亮性质:对于固定偏移k,位置 $pos+k$ 的编码可以通过位置 $pos$ 编码的线性变换得到——这让Attention天然能捕捉相对位置关系。
公式表方案唯一的“缺点”是没有学习能力——它不会根据具体任务调整座号的写法。但正因为固定,它才能在所有任务里一视同仁地提供位置信息。接下来看看另一种思路:让位置编码自己学。
- pos 位置编号句子中词的绝对位置编号,决定该位置的编码取值,对应观众手里的座号数字。
- i 维度下标维度从0到d-1一共d个数字,偶数维用sin、奇数维用cos,让同一位置的相邻维度携带不同相位信息。
- 分母 10000^{2i/d}控制振荡频率的核心旋钮,i越大分母越大,波的周期越长,形成“维度越高越迟钝”的规律。
- 代入生成编码把位置值和维度值代进去,得到第pos个位置在第2i维和第2i+1维上的具体数值。
可学习位置编码原理
另一种电影院不印标准座号,而是给每个座位挂一块名牌。刚开始名牌是空白的,工作人员根据每天的客流和观众反馈,一点点改名牌上的字,改到顺手为止。可学习位置编码就是这套“手写名牌”。
在实现上,可学习位置编码是一张可训练的参数表 $\mathbf{P}$,行数等于预设最大长度 $T_{\max}$,列数等于模型维度 $d$。训练开始时 $\mathbf{P}$ 随机初始化,之后和模型其他参数一起反向传播更新,直到收敛。这个过程相当于工作人员每天写一点、改一点,最终把座号“背”了下来。
使用时直接“查表”:取第t行的向量作为位置t的编码。相比正弦,它不受任何手工公式的约束,理论上可以拟合出任务最需要的位置模式。BERT预训练就采用了这套方案,位置表大小固定为512×768,约39万个参数。缺点也很明显:表的大小直接给序列长度设了天花板——新座位没有名牌可挂。
名牌方案在训练时好用,可一旦电影院扩建,问题就来了。这两种方案在外推新位置时,表现天差地别。
- 位置参数表 P一个可训练的矩阵,行数是预设最大长度 $T_{\max}$,列数是模型维度d,相当于仓库里存好的全套名牌。
- 查表取行位置t的向量就是矩阵的第t行,像查字典一样按索引取值。
- 梯度更新训练时P和模型其他参数一起接受梯度下降更新,逐步从随机初值调整为有意义的模式。
长度外推能力对比
有一天电影院扩建了200个座位。标准座号系统的工作人员掏出公式表,几分钟就把新座位号算好了;手写名牌的工作人员却傻了眼——新座位根本没有准备名牌。这就是“外推能力”的差异。
正弦编码的公式对任意位置都成立,只要pos是正整数,就能算出它的编码,训练长度不会形成硬边界。可学习编码则不同,位置表只覆盖了训练时见过的长度范围,一旦输入超过 $T_{\max}$,就是“查无此座”。
在实际框架里,超过 $T_{\max}$ 的位置会触发索引越界报错,或者拿到一个从未训练过的向量——它和随机初始化时几乎没有区别,喂给模型只会得到无意义的输出。即便把表设得很大,超出训练覆盖的位置也从没收到过梯度,学不到任何东西。反观正弦编码,虽然位置信息不缺,但Attention在超长序列上的表现还会受到softmax饱和、计算量增长等因素影响,所以“正弦=无限外推”是一种过度简化。
既然可学习编码多了一张参数表,那它的“仓库成本”到底有多少?这是下面要算的一笔账。
- 正弦对所有位置有定义只要是正整数t,$f_{\sin}(t)$ 就能算出一个规范的位置向量,不存在“第几排没号”的问题。
- 可学习仅在训练长度内有定义$\mathbf{P}[t]$ 只在 $t \le T_{\max}$ 时能取到已训练的行。
- 超出即无有效语义当 $t > T_{\max}$ 时,索引越界或得到未训练向量,位置信息退化为噪声。
参数开销对比
手写名牌需要提前准备——仓库里得存放几万张空白名牌,写好的名牌也要占柜子。而标准座号的公式表只是墙上的一张纸,不占任何空间。参数开销的差别同样如此。
正弦编码由纯数学函数生成,模型新增参数为零。可学习编码则引入一张 $T_{\max} \times d$ 的参数表。以BERT-base为例,512×768=393,216个参数,在约1.1亿总参数里只占0.36%,看起来微不足道。
但这个比例不能一概而论。如果模型本身很小(比如几百万参数),39万的位置表就是一笔不小的开销;如果序列长度从512涨到8192,位置表的参数会线性放大16倍,超过600万。对GPT-3这种1700亿参数的庞然大物,这点参数可以忽略,但对端侧小模型、超长序列任务,位置表的存储和内存带宽都是实打实的负担。
算完这笔账,选型的天平该怎么摆?既不是“正弦永远最好”,也不是“可学习永远最强”,而是要看电影院的实际情况。
- 可学习参数规模位置表的参数个数等于最大长度乘维度,每多一个位置就多d个参数。
- 正弦零参数正弦编码只需要在运行时调用数学函数,不存储任何额外数值。
- BERT实例测算512个位置、768维,相乘得到约39.3万个参数,在1.1亿总参数中占比很小。
方案选型建议
电影院经理在开业前要做一道选择题:座位是固定不变,还是可能扩建?观影体验是标准统一,还是需要特殊定制?答案不同,座号方案就不同。选位置编码也一样,没有放之四海皆准的银弹。
如果你需要处理长度多变、可能超出训练长度的文本,正弦编码是稳妥的默认选择——它零参数、天然可外推,公式对任意位置都成立。如果你处理的是长度固定的预训练任务,数据量充足且训练充分,可学习编码往往能学到更贴合数据的模式,就像顶级的迎宾员能记住每一位常客的偏好座位。
现实中很多模型并不拘泥于二选一:BART对encoder用正弦、decoder用学习式;T5和RoPE干脆用相对位置编码,把“距离”直接建模进Attention。实践中的选型决策可以按一个简单的加权打分来评估:外推需求越强越偏向正弦,数据越充分越偏向可学习,参数越紧张越偏向正弦。三股力量共同决定最优方案。
- 外推需求评分 E任务越经常遇到超长输入,E越大,正弦编码的加分越多。
- 数据充分度评分 D训练语料越丰富、长度分布越均匀,可学习编码越能发挥拟合优势。
- 参数敏感度 C模型越小、序列越长,位置表参数带来的负担越重,作为减分项出现。
AI 应用场景
长文档问答与RAG重排序
阶段:模型架构设计 / 推理部署
解决的问题:传统RAG重排序模型多用可学习位置编码,训练时统一截断到512 token,遇到上千字的真实文档时位置信息断裂、排序质量急剧下降。改用正弦位置编码后,模型无需重训就能处理任意长度的检索片段,位置信息始终完整。
具体案例:某金融研报问答系统,用户上传2000字的研报正文,重排序模型分为多个chunk后仍需要知道每个chunk在原文中的相对位置。采用正弦编码后,模型对chunk的全局位置感知明显改善,关键段落召回率提升约12%。
生成式模型的超长续写
阶段:预训练 / 推理服务
解决的问题:生成任务输出长度不可预知,可学习位置表会锁死最大生成长度;一旦用户要求续写超过上限,模型直接“无座可坐”。正弦编码的公式可算性让生成长度不再受位置表制约。
具体案例:一款故事续写应用,训练时只见过512 token以内的故事片段,但线上用户经常要求续写800甚至1000 token。将位置编码替换为正弦方案后,无需增加训练成本,模型即可稳定输出超长剧情,且前后文的指代一致性没有明显退化。
Python 演示
import numpy as np def sinusoidal_positional_encoding(max_len, d_model): # 正弦位置编码: 任何位置都能现场算出 pos = np.arange(max_len).reshape(-1, 1) i = np.arange(d_model).reshape(1, -1) angle = pos / np.power(10000.0, 2 * (i // 2) / d_model) enc = np.zeros((max_len, d_model)) enc[:, 0::2] = np.sin(angle[:, 0::2]) enc[:, 1::2] = np.cos(angle[:, 1::2]) return enc def learned_positional_embedding(max_len, d_model, steps=500): # 模拟可学习位置编码: 随机初始化+逐步调整 rng = np.random.RandomState(42) P = rng.randn(max_len, d_model) * 0.1 target = sinusoidal_positional_encoding(max_len, d_model) * 0.5 lr = 0.05 for _ in range(steps): grad = (P - target) / max_len P = P - lr * grad return P train_max = 50d_model = 16test_len = 80 sin_enc = sinusoidal_positional_encoding(test_len, d_model)learned_enc = learned_positional_embedding(train_max, d_model) print("正弦编码表尺寸:", sin_enc.shape)print("可学习编码表尺寸:", learned_enc.shape) # 训练长度内的编码对比print("\n位置10的编码(前4维):")print(" 正弦:", np.round(sin_enc[10, :4], 4))print(" 可学习:", np.round(learned_enc[10, :4], 4)) # 外推能力对比print("\n外推能力对比:")print(" 正弦: 可计算位置60的编码 ->", np.round(sin_enc[60, :4], 4))try: print(" 可学习: 位置60的编码 ->", learned_enc[60])except IndexError: print(" 可学习: 位置60超出训练长度, 查表失败(报错)") # 验证正弦的相对位置知识for k in [1, 3, 5]: dot_k = np.dot(sin_enc[10], sin_enc[10 + k]) dot_same = np.dot(sin_enc[30], sin_enc[30 + k]) print(f" 正弦相对距离性质: dot(P10,P{10+k})={dot_k:.4f}, dot(P30,P{30+k})={dot_same:.4f}")
angle = pos / np.power(10000.0, 2 * (i // 2) / d_model)计算每个位置在每个维度上的角度值,分母的幂控制频率分层enc[:, 0::2] = np.sin(angle[:, 0::2])偶数维填正弦值enc[:, 1::2] = np.cos(angle[:, 1::2])奇数维填余弦值rng = np.random.RandomState(42)固定随机种子,保证可学习编码初始状态可复现P = P - lr * grad模拟梯度更新的核心步骤,逐步调整位置表sin_enc = sinusoidal_positional_encoding(test_len, d_model)正弦编码直接生成80长度的表,没有长度限制learned_enc = learned_positional_embedding(train_max, d_model)可学习编码只能生成50长度的表np.dot(sin_enc[10], sin_enc[10 + k])用点积验证相对位置性质:相同偏移k的两个点积近似相等
常见误区
误区可学习位置编码是训练出来的,肯定比正弦这种手工设计的要好;BERT都用可学习了,说明它就是更先进的方案。
正解模型效果是“数据+结构+训练”的综合结果,位置编码只是其中一小块拼图。正弦编码的强外推和零参数对长文本任务非常有价值;可学习编码的优点是灵活贴合训练数据,但长度受限。GPT-2也用了学习式位置编码,效果同样出色。选型看需求,不存在绝对的胜负。
为何常见:大家习惯把“可学习”和“先进”画等号,忽略了位置编码只是模型中的一个小组件。
误区正弦编码的向量是固定的,所以它完全不参与训练,模型根本学不到位置信息。
正解正弦编码本身不更新,但位置向量作为输入信号进入网络后,整个模型的Attention层、FFN层都会围绕“如何使用位置信息”来调整自己的权重。位置编码是固定的“输入特征”,模型学的是如何解读这个特征。就像电影院的座号印法是固定的,观众仍然会自己学会看座号找座位。
为何常见:“位置编码不更新”被误解为“位置编码与训练无关”。
误区把可学习位置表的长度设成10000,模型不就能处理10000长的句子了吗?
正解表设大只是“预留了位置”,但训练时模型只见过训练长度内的样本,超出部分的位置向量从未收到梯度,数值和随机初始化没有区别。把它喂给模型,模型看到的是一串从未见过的数字,不会产生有意义的语义。真正的长文本能力需要训练数据里真的包含长样本。
为何常见:把存储上限误当成了能力上限。
误区正弦公式对任意位置都能算,所以用正弦编码的模型一定能处理任意长的文本。
正解位置编码只是输入的一部分。真正决定外推能力的还有Attention的softmax分布、多层堆叠的误差累积、训练数据的长度分布等。正弦编码只保证“位置信号不缺位”,模型对超长输入的整体表现仍可能退化。公式可算是必要不充分条件。
为何常见:把位置编码的计算能力等同于整个模型的处理能力。
误区位置编码要么正弦要么可学习,论文里就是这么对比的,只能选一种。
正解很多模型其实是混合的,比如BART对encoder用正弦、decoder用学习式;RoPE和T5的相对位置编码则是完全不同的思路。你在自己的模型里完全可以把二者结合,甚至按层选择。位置编码是一个设计旋钮,不是非黑即白的阵营选择。
为何常见:论文的对比实验让读者形成了“二选一”的思维定式。
面试题
Q1. 为什么Transformer模型必须使用位置编码?
- A.补充模型词表的规模信息
- B.让模型感知词在句中的位置← 正确答案
- C.提升训练数据采样的效率
- D.避免梯度消失或者梯度爆炸
答案:B。Transformer的Self-Attention并行计算没有天然的先后顺序概念,所有词一次性进入注意力计算,所以必须用位置编码注入“词在第几位”的信息。选项A说的是词表信息,C是数据采样,D是训练稳定性问题,都与顺序无关。
Q2. 正弦位置编码中“低频维度”指的是哪类维度?
- A.维度下标i比较大的维度← 正确答案
- B.维度下标i比较小的维度
- C.全部维度上的频率一致
- D.词向量投影后得到的维度
答案:A。正弦编码的频率由分母10000^(2i/d)控制,i越大分母越大、周期越长、频率越低。所以维度下标i较大的维度是低频维度,它们随位置变化缓慢;i较小的维度是高频维度,相邻位置差异明显。
Q3. 相比可学习位置编码,正弦位置编码的主要优势是?
- A.可以外推到任意长度的序列← 正确答案
- B.训练阶段收敛的速度会更快
- C.完全不需要任何词向量参与
- D.推理计算的开销会显著更低
答案:A。正弦编码由公式生成,对任意正整数位置都能现场算出编码,天然支持长度外推;可学习编码查表只能查到训练长度以内的位置。B和D并非正弦的固有优势,C更是不成立——词向量依然是模型输入的一部分。
Q4. 可学习位置编码的可训练参数存储在哪里?
- A.独立的嵌入参数矩阵当中← 正确答案
- B.注意力权重矩阵的内部
- C.前馈网络的偏置项之中
- D.词表对应的嵌入向量里面
答案:A。可学习位置编码是一张独立的 $T_{\max} \times d$ 参数表,通过查表操作取行得到位置向量。它不属于注意力权重或前馈网络,也不在词表嵌入里,而是单独的一份Embedding参数。
Q5. 可学习位置编码超出训练长度时会出现什么?
- A.自动续算正弦公式来补齐
- B.查不到已经训练好的位置向量← 正确答案
- C.模型自行扩大位置参数的表
- D.用零向量替代并进行正常训练
答案:B。可学习编码的位置表只覆盖了训练时的最大长度,输入超过该长度就“查无此座”——可能索引越界报错,或拿到未训练过的随机向量。模型不会自动调用正弦公式,也不会自行扩容。
Q6. 哪些场景更适合优先选择正弦位置编码?
- A.推理时输入长度可能超过训练长度← 正确答案
- B.任务输入的长度变化范围非常大← 正确答案
- C.训练数据量有限而且序列长度短← 正确答案
- D.需要保证每个位置编码绝对不重复
答案:ABC。正弦编码的公式可算性让它在输入长度多变或可能超过训练长度时更稳妥;训练数据有限时,可学习编码难以学好一张大位置表,正弦的固定规则反而可靠。D不是正弦的独有优势——可学习编码同样给每个位置分配了不同的行。
Q7. 关于可学习位置编码的正确说法有哪些?
- A.位置向量与模型整体同步训练← 正确答案
- B.位置表大小等于长度乘以维度← 正确答案
- C.任何长度输入下都能稳定外推
- D.位置表在训练结束后保持固定← 正确答案
答案:ABD。可学习位置表在训练中参与梯度更新、训练后固定使用,其参数量等于最大长度乘维度。C错误:它无法稳定外推到训练长度之外,这正是其核心短板。
Q8. BERT选择可学习位置编码而非正弦的深层原因?
- A.预训练数据充足且长度有固定上限← 正确答案
- B.可学习编码计算开销远小于正弦编码
- C.正弦编码无法在GPU上做并行运算
- D.可学习编码完全不需要训练数据支持
答案:A。BERT的预训练语料极其充足,输入长度也固定截断到512,可学习编码能充分利用数据拟合出任务所需的位置模式。正弦编码同样可以并行计算(B错),也不存在GPU不兼容的问题(C错),更不可能“不需要训练数据”(D错)。
Q9. 预训练时位置表最大长度512,推理处理600长度输入,正确的是?
- A.超出的88个位置从未参与过训练← 正确答案
- B.模型用正弦公式自动补齐新的位置
- C.模型自动截断输入到512的长度
- D.系统会静默忽略多余的位置信息
答案:A。可学习位置表只训练了1到512的位置,第513到600个位置没有对应的已训练向量,拿到的是未训练过的随机值,语义上无意义。模型不会自动改用正弦公式,也不会静默忽略,更不会悄悄截断。
Q10. 关于位置编码外推能力的全面判断,正确的是?
- A.正弦编码在任意长度下公式均可计算← 正确答案
- B.超长输入时注意力计算开销会明显变大← 正确答案
- C.可学习编码外推效果一定比正弦编码差
- D.外推能力只取决于位置编码本身这一个因素
答案:AB。正弦编码的公式确实对任意正整数位置可算;超长输入时Attention的平方复杂度会让计算量显著上升,这也是外推要面对的工程问题。C过于绝对,可学习编码在某些分布内可能表现更好;D忽略了注意力机制、训练数据分布等多个因素。