2026-05-19 · 架构
32
架构 · 2026-05-19

Positional Encoding 怎么解决词序问题

Transformer 刚出来时,很多人把注意力都放在 self-attention 上。那当然没错,因为它确实重新定义了模型怎么看上下文。但如果只盯着 attention,你会漏掉另一个同样关键的问题:模型怎么知道词序?

这是我看完这期视频后最强烈的感受。Transformer 的突破,不只是让每个词都能看见别的词,还要解决“看见之后,怎么分清谁在前谁在后”。本文整理自 Visual AI 的视频 Why Transformers Need Positional Encoding | Sin & Cos Explained Visually。它从最简单的 Dog bites manMan bites dog 出发,一路讲到位置盲、learned positional embedding、正弦余弦公式、10,000 这个底数为什么刚刚好。原视频:https://www.youtube.com/watch?v=42y3XeOnH78

Transformer 如果没有位置信息,根本分不清词序

视频开头选的例子很好:Dog bites manMan bites dog。人一眼就知道两句话完全不是一回事。一个说狗咬人,一个说人咬狗,主客体一换,语义立刻翻过去。

问题在于,一个没有位置信息的 vanilla Transformer,其实看不出这种差别。因为它是整句并行处理的。每个词都会同时和别的词做 attention,算 query 和 key 的点积,看谁和谁更相关。但这一整套计算,只关心词本身是什么,不关心词站在第几个位置。

视频把这种状态形容成“把单词丢进袋子里再晃一晃”。这个比喻很到位。没有顺序信息,句子就不再像一句语言,而更像一个无序的词集合。于是 dogbitesman 这三个词虽然都还在,但“谁咬谁”这件事已经不见了。

这其实说明了一件很重要的事:self-attention 负责建关系,但语言里的很多关系,本来就依赖顺序。你如果只保留“词与词之间的相关性”,却不保留“谁先出现、谁后出现”,模型会丢掉一层非常基础的语法结构。

RNN 虽然旧,但它天然知道顺序

视频这里特地回头看了 RNN,我觉得这个对比非常必要。很多人谈 Transformer 时容易把 RNN 当成“被淘汰的老东西”,但在“顺序感”这件事上,RNN 其实有天然优势。

RNN 一次读一个词。先读 dog,再读 bites,最后读 man。每一步都会把前面的历史状态带到下一步,像一张纸条从前一刻传到后一刻。这样做虽然慢,也难并行,而且长距离依赖会出问题,但顺序信息是直接写进计算路径里的。

换句话说,RNN 不需要额外告诉模型“哪个词先来”。因为它本来就是按时间往前走的。

Transformer 则反过来。它为了并行化,把这种时间顺序完全扔掉了。速度和扩展性是上来了,但顺序感也一起被扔没了。视频在这里点得很准:Transformer 的问题不是“忘了顺序”,而是它从架构上就没有顺序这个概念,除非你额外加进去。

permutation problem 本质上是 attention 只认内容,不认位置

视频后面把问题落回到 self-attention 的公式上,重点看的是 QK^T 这一项,也就是 pairwise dot products。

对句子 dog bites man 来说,每个词都拿自己的 query 去和别的词的 key 做匹配,于是得到一张 attention score 矩阵。接着把词序换成 man bites dog,你会以为这张矩阵应该明显不同,结果不是。分数基本一样,只是顺序被平移了。

原因非常直接:词没变,只是位置变了。而 self-attention 在没有位置编码时,只看 token 的内容向量,不看 token 在句子里的坐标。

所以,dogman 的交互分数,在两个句子里几乎是一回事。数学上它们看起来像同一批词,只是摆放方式不同;但 attention 本身对“摆放方式”没有感觉。这就是 permutation problem,也就是排列不变性带来的麻烦。

这个地方很值得记住,因为它解释了 positional encoding 的真正角色。它不是给模型“多加一点辅助信息”,而是在修一个根本缺陷:如果不加,Transformer 连最基本的顺序差异都看不见。

positional encoding 做的事,是把“位置”烤进每个词的向量里

一旦加上 positional encoding,情况就变了。视频强调了一个关键点:位置编码不是在 attention 外面单独处理顺序,而是直接混进词向量里。

比如在 dog bites man 里,dog 在 position 0;到了 man bites dog,position 0 上站的变成了 man。于是虽然词表里的 dogman 还是同一个词,但它们拿去做 attention 的底层向量已经不一样了,因为里面混进了不同的位置指纹。

这样一来,再去算 attention,dogman 的交互分数就真的变了。模型终于能看到“同样的词,不同的站位,会形成不同关系”。

我觉得这可以理解成给每个词补了一张地址卡。没有位置编码时,模型只知道“你是谁”;加上以后,模型同时知道“你是谁,你站在哪”。语言理解才算重新完整起来。

最直觉的方案是直接加序号,但这条路很快就坏掉了

视频接着做了一个很好的工程推演:如果只是想让模型知道顺序,最简单的办法是什么?直接给每个词一个位置编号。第一个词加 1,第二个词加 2,第三个词加 3。

乍看完全合理。最终向量 = 词向量 + 位置序号。

但问题很快就出现了。词向量通常是一些细小的实数,比如 0.1、0.3、-0.2 这种量级。你突然把一个 100 丢进去,语义信息会被压扁。视频把这种感觉比作“在喷气发动机旁边听耳语”,很形象。原本 embedding 里那些细微的语义结构,会被巨大的整数位置直接淹没。

于是 raw index 这条路行不通。它不是在补充位置信息,而是在拿位置粗暴覆盖词义。

把位置归一化,看起来优雅,其实也不稳

那能不能把位置缩到 0 到 1 之间?视频接着试了这个方案。比如把所有位置都除以序列长度减一,让第一词是 0,中间词是 0.5,最后一个词是 1。这样数值范围就不会爆炸了。

这个版本确实解决了 scale 问题,但又冒出一个更隐蔽的麻烦:同一个 index,在不同长度的句子里,编码会变。

视频举了一个简单例子。某个词在三词句子里 index=2,归一化后可能是 1.0,因为它是最后一个词;但在七词句子里,同样 index=2,归一化以后只变成 0.33。也就是说,同样站在第三个位置上的词,会因为句长不同而带着完全不同的位置表示。

这样模型就没法跨句稳定比较“这个词到底在前、中、后哪里”。顺序信息不再是绝对坐标,反而变成了依赖句长的浮动值。

所以,raw integer 会把 embedding 压坏,normalized index 又会让位置定义飘来飘去。视频这段推导很有价值,因为它说明 positional encoding 的设计并不是拍脑袋选个公式,而是被一连串失败方案逼出来的。

learned positional embedding 好用,但天花板写死在表长里

走到这里,一个自然想法是:别手写公式了,让模型自己学。视频也讲了 learned positional embeddings 的思路。

做法很像一个查表矩阵。每个位置对应一行向量,初始是随机数,训练时不断更新,最后模型自己学出一套位置表示。推理时,位置 1 就查第 1 行,位置 2 查第 2 行,再把查到的向量加进 token embedding。

这套方法实际效果很好。视频也提到 BERT 和 GPT-2 都用过它。说明它不是理论玩具,而是工业界真正跑通过的方案。

但问题也很清楚:它的上限写死在表长度里。你训练时最多支持 512 个位置,来了第 513 个 token,就没有对应的行可查。模型不是“效果变差一点”,而是直接撞墙。

这一点很像很多工程系统里的硬编码上限。平时够用,但一旦超出分配范围,泛化就会立刻变差。对于想处理更长序列的模型来说,这就是个大问题。

理想的位置编码,至少要满足四个条件

视频到这里没有急着上公式,而是先像工程师一样列需求。我很喜欢这种讲法,因为它让后面的正弦余弦解法显得不是“数学炫技”,而是“需求驱动的设计”。

它希望 positional encoding 同时满足四个性质:

这四条几乎把前面几种失败方案的问题全说完了。raw index 不有界,normalized index 不 length agnostic,lookup table 泛化差。于是你会发现,真正难的不是“告诉模型位置”,而是“告诉位置的同时,不破坏语义、不限制长度、还能保留距离结构”。

正弦和余弦的妙处,在于它们同时满足了工程和数学要求

视频接着抛出了那个关键问题:什么函数天然有界、平滑、周期性强,而且可以无限延伸?答案就是 sine 和 cosine。

于是,Transformer 论文里那套经典做法出现了:对每个位置,用不同频率的正弦和余弦波拼出一串向量。position 0 有自己的组合,position 1 有另一组,position 2 再换一组。每个位置都会得到一张独一无二的波形指纹。

视频这里的表达很直观:每个词都像带着一个数学 GPS tag。这个说法我觉得非常准。它不是某个单一标号,而是一串由多种频率混合出来的地址信息。

更重要的是,这套方案一下子解决了前面的核心矛盾:

也就是说,它不只是“能用”,而是和这个任务非常贴。

公式本身不难,难的是理解为什么要交替频率

视频后半段开始真正展开公式。偶数维用 sine,奇数维用 cosine;同一对维度共享一个角度,只是一个取正弦,一个取余弦。随着维度变深,分母会越来越大,于是对应波形越来越慢。

它用一个六维的小例子,把 dog bites man 三个 token 的 positional encoding 一格一格算出来。第一组维度变化快,后面几组变化越来越慢。最后每一行都变成一个完整的 fingerprint。

这里真正重要的不是手算过程本身,而是它表达出的结构:浅层维度负责快速振荡,区分附近位置;深层维度负责慢速变化,保留更长范围的位置信息。

所以 positional encoding 不是“一个波”,而是一组快慢不同的波一起工作。快波看局部,慢波看整体。叠在一起后,模型既能分清相邻词,也不会丢掉整句的大尺度位置信息。

10,000 不是神秘常数,它是在避免两头都坏

很多教程提 positional encoding 时,都会把公式里的 10,000 一带而过。视频专门拿这件事做了详细解释,这点很值。

这个常数控制的是不同维度的频率跨度。最前面的维度分母小,频率高;最深的维度分母大,频率低。如果底数太小,比如 10,那么最慢的波也还是转得太快,远处位置很容易撞出近似编码。视频把这叫 catastrophic collision,确实很贴切。

反过来,如果底数太大,比如 1,000,000,那么最慢的波几乎不动,深层维度提供不了什么有效距离信息,等于白占维度。

所以 10,000 的好处就在于它卡在中间:既不会让慢波转得太快,也不会慢到几乎不动。视频把它叫 Goldilocks situation,也就是“不太小,也不太大,刚刚好”。

这类设计很像工程里的超参数选择。不是说 10,000 有某种神圣正确性,而是它在经验上把频率分布铺到了一个很实用的范围,让模型大致能覆盖到 10,000 token 级别的序列。

positional encoding 不只给绝对位置,还顺手保留了相对距离

我觉得视频最漂亮的部分,是它没有停在“每个位置都有唯一编码”,而是继续往下讲:为什么这套编码还能让模型感到两个词相隔多远。

它用三词句子继续说明。每个位置都有一行自己的 encoding 向量,看上去像绝对地址。但通过三角函数的加法关系,你可以把“后一个位置的向量”写成“前一个位置的向量经过某种变换”。

落到 attention 里,这意味着不同位置编码做 dot product 时,相同距离的词对会得到相似的结果。视频举了个很清楚的例子:dogbites 的距离是 1,bitesman 的距离也是 1,所以它们的 positional similarity 一样;而 dogman 的距离是 2,dot product 就会降下来。

这很关键。因为模型不只是知道“你在第 3 个位置”,还隐约知道“你和另一个词之间隔了多远”。这就是 relative position 的感觉。

也就是说,正弦余弦位置编码其实一箭双雕:既给了绝对地址,也给了相对距离线索。这个设计确实很优雅。

这套方案真正厉害的地方,是把数学性质直接变成了工程能力

视频最后把 sinusoidal positional encoding 的优点总结成四个词:unique、bounded、relative、generalizable。我觉得这四个词可以直接当作理解它的总纲。

从工程视角看,这四点几乎对应了 Transformer 真正需要的位置机制全部要求。它不靠额外状态,也不靠递归路径或固定表长,而是用一个很简单的解析函数,把顺序问题重新嵌回并行架构里。

我的补充:positional encoding 说明 Transformer 不是“只靠大力出奇迹”

很多人今天谈大模型,会把一切归结为参数、算力和数据规模。但 positional encoding 这种设计会提醒你,Transformer 能成功,不只是因为它大,也因为它在一些看似细小的问题上处理得很精确。

如果没有 positional encoding,attention 再强,模型也只是在一堆词之间做内容匹配。它会知道哪些词彼此相关,却未必知道这些词是怎么按顺序组成语言的。真正让 Transformer 成为语言模型的,不只是“词之间能互相看见”,还包括“每个词带着自己的位置进入这张关系网”。

我看完这期视频后的判断是:self-attention 解决了“看谁”,positional encoding 解决了“站哪儿”。少了前者,模型抓不到全局关系;少了后者,模型抓不到句法秩序。Transformer 的威力,是这两层一起成立之后才真正出来的。

目录 最新
← 左侧翻上一屏 · 右侧翻下一屏 · 中间唤出菜单