编程与 AI13 分钟阅读更新于 2026-08-03

位置编码是什么:让 Transformer 知道词语的先后顺序

从句子顺序的变化出发,理解自注意力为什么需要额外的位置线索,并进一步认识正弦余弦位置编码、位置向量与词嵌入的相加方式。

相关工具

注意力看得到内容,却不会自动知道顺序

多头注意力可以让每个位置读取整段序列中的信息,但它接收到的首先是一组向量。对于注意力计算来说,向量之间的相似程度很重要,向量来自第几个位置却不是天然携带的属性。也就是说,注意力擅长判断“哪些内容相关”,却不会凭空知道“谁在前面、谁在后面”。

可以看两个词序不同的句子:“我喜欢机器学习”和“机器学习喜欢我”。它们包含相同的词,但顺序变化后,通常表达的关系也变了。如果模型只看到几个词的内容,缺少位置线索,就很难稳定地区分这两种排列。

RNN 把顺序写进了逐步更新的隐藏状态,卷积结构则通过局部窗口间接获得位置信息。Transformer 为了保持并行计算,需要用另一种方式把位置送进模型,这就是位置编码。

自注意力缺少顺序信息以及位置编码补充位置的示意图
为什么 Transformer 需要位置编码

相同词语换了顺序后,词嵌入仍然相同;把位置编码加入表示后,模型才有机会区分不同排列。

位置编码到底要告诉模型什么

位置编码不是把一个简单的数字编号直接贴在词后面。模型需要的是一种可以和词嵌入一起参与矩阵运算的向量表示。第 1 个位置、第 2 个位置和第 20 个位置,应该在这个向量空间里呈现出可区分、又有规律的差异。

理想的位置表示至少要满足两个要求。第一,不同位置要能被区分;第二,位置之间的相对关系不能完全丢失。比如相邻位置应当有相近的变化规律,而相隔较远的位置也应当保留足够明显的差异。

位置编码最终要和词嵌入处在同一个维度空间里。这样它们才能逐元素相加,形成一个同时包含“这个词是什么”和“这个词在哪里”的新表示。

词嵌入和位置编码为什么可以直接相加

设词嵌入为 E,位置编码为 P。对序列中的第 i 个位置,Transformer 通常使用 H_i=E_i+P_i 作为后续编码器的输入。E_i 负责携带词语内容,P_i 负责提供位置线索,两者的维度相同,因此可以逐元素相加。

相加并不意味着模型把两类信息混成了无法区分的一团。不同参数层可以学习如何从合成向量中读取内容和位置。更重要的是,相加不会额外增加序列长度,也不会把表示维度扩大一倍,适合接入后面的多头注意力和前馈网络。

这一步可以用一张表来理解:同一个词出现在不同位置时,词嵌入部分保持不变,位置编码部分发生变化,所以相加后的结果也不同;不同词出现在同一个位置时,位置编码相近,词嵌入部分则不同。

Transformer 编码器中词嵌入与位置编码相加的流程图
位置编码进入 Transformer 的流程

词元序列生成词嵌入,位置索引生成位置编码,两者逐元素相加后再送入多头注意力。

正弦余弦位置编码的基本形式

Transformer 论文中使用了正弦和余弦函数构造位置编码。对于位置 pos 和维度索引 i,偶数维使用正弦函数,奇数维使用余弦函数:PE(pos,2i)=sin(pos/10000^(2i/d_model)),PE(pos,2i+1)=cos(pos/10000^(2i/d_model))。

公式看起来复杂,直觉却不难。位置 pos 沿着序列向前移动时,每个维度都会产生周期性变化,但不同维度使用的周期不同。低维变化得更快,高维变化得更慢,许多不同频率的波叠加在一起,就形成了每个位置的向量指纹。

正弦和余弦成对出现,也让相邻位置之间的变化具有规律。模型不只可以记住某个位置的绝对编号,还可能通过向量之间的关系推断位置差异。

不同维度正弦余弦位置编码随位置变化的曲线图
正弦余弦位置编码

不同维度使用不同频率的正弦和余弦函数,位置沿序列变化时,各维度形成不同周期。

为什么要使用不同的频率

如果所有维度都用同一个频率,位置编码在一段周期之后就会重复,模型很难区分相隔一个完整周期的位置。不同频率可以让一些维度记录短距离变化,另一些维度记录更长距离变化。

这有点像同时使用秒针、分针和时针看时间。秒针变化快,适合描述短时间差;时针变化慢,适合描述更长的时间范围。多个尺度放在同一个向量里,模型就能同时获得近距离和远距离的位置线索。

这也是正弦余弦方案的重要特点:它不是为每一个位置单独记一个没有规律的编号,而是用一组连续变化的函数表示位置。这样的结构更容易被矩阵运算利用,也为处理比训练序列更长的输入提供了一定的外推可能。

绝对位置和相对位置有什么区别

“第 5 个位置”是绝对位置,“两个词相隔 3 个位置”是相对位置。经典正弦余弦位置编码首先为每个位置生成绝对向量,但由于正弦函数具有规律,模型也有机会从两个位置编码的组合中学习相对距离。

这两种信息都很有用。绝对位置可以帮助模型区分句首、句中和句尾;相对位置则适合表达邻近、间隔和方向等关系。注意力计算本身会比较不同位置的向量,位置编码提供的规律越清楚,模型越容易学习这些关系。

需要注意的是,位置编码并不等于专门的相对位置注意力。后者会把位置差异直接写进注意力分数或偏置中,是另一类设计。学习基础 Transformer 时,先区分“给输入加位置向量”和“在注意力分数里加入位置关系”,不容易混淆。

位置编码放在什么时候加入

在经典编码器结构中,词元先经过嵌入层得到向量,再把位置编码加到词嵌入上,之后才进入第一层多头注意力。这样,注意力从第一次计算开始就能同时看到内容和位置。

如果把位置编码加入得太晚,前面的注意力层已经在缺少顺序信息的情况下完成了信息混合,后面再补位置就可能错过一些关系。位置线索通常要在序列交互开始前就进入表示。

解码器也需要位置编码。无论是读取已经生成的内容,还是和编码器输出进行交互,当前位置和前后顺序都影响解释。解码器的因果掩码负责禁止偷看未来,位置编码则负责告诉模型当前词处在什么位置。两者解决的是不同问题。

位置编码不是注意力掩码

这两个概念经常被放在一起,但作用完全不同。位置编码是加到输入表示中的数值向量,让模型获得顺序信息;注意力掩码则作用于注意力分数,决定某些位置能不能被看见。

例如,解码器的因果掩码会把未来位置屏蔽掉,使当前位置不能读取尚未生成的词。即使没有这个掩码,位置编码仍然可以告诉模型每个词在哪里,但它无法阻止模型访问未来信息。

反过来,只有掩码而没有位置编码,也不能让模型充分理解两个词谁在前谁在后。一个负责“提供坐标”,一个负责“限制可见范围”,不要把它们当成同一种机制。

学习位置编码时要检查哪些形状

假设批次大小为 B,序列长度为 L,模型维度为 d_model,词嵌入的形状通常是 B×L×d_model。位置编码可以保存为 L×d_model,再沿批次维度广播到 B×L×d_model,最后逐元素相加。

如果位置编码的序列长度小于实际输入长度,就无法为所有位置提供向量;如果最后一维不是 d_model,就不能直接和词嵌入相加。很多实现问题都可以先从这两个形状检查开始排查。

还要注意位置索引从 0 开始还是从 1 开始。两种约定都可以,但整个模型和词元处理流程必须保持一致。特殊词元是否占用位置、填充位置是否参与注意力,也应当在数据处理阶段明确下来。

位置编码的四个检查点
步骤 1
覆盖长度

位置编码至少覆盖当前输入的序列长度 L。

步骤 2
维度一致

位置编码最后一维应与词嵌入的 d_model 相同。

步骤 3
逐元素相加

词嵌入和位置编码按相同位置、相同维度相加。

步骤 4
约定统一

位置从 0 还是从 1 开始,特殊词元如何占位,需要前后一致。

从固定编码到可学习位置表示

固定的正弦余弦位置编码不需要额外训练参数,位置规律由公式直接生成。另一种常见方式是把每个位置对应的向量也作为参数,让模型在训练过程中学习。可学习位置表示更灵活,但通常受训练时最大长度影响更明显。

两者并不是简单的谁取代谁。固定编码带有明确的周期结构,可学习编码可以适应数据中的具体模式。不同模型会根据训练数据、上下文长度和架构设计选择不同方案,也有模型把位置偏置直接放进注意力计算。

无论采用哪一种设计,核心问题都没有变:注意力需要内容关系,也需要顺序线索。位置编码是 Transformer 把“无序的向量集合”重新放回序列语境的一种方式。

把位置编码放回整条 Transformer 链路

现在可以把前几篇内容连起来看:词元先变成词嵌入,位置编码把顺序信息加入其中,多头注意力让不同位置交换上下文,前馈网络进一步变换每个位置的表示,残差连接和层归一化帮助这些模块堆叠起来。

位置编码看起来只是一次相加,却决定了后面的注意力是否有机会理解顺序。没有它,多头可以计算出很漂亮的关系矩阵,但这些关系缺少可靠的空间坐标;加入它之后,模型才有可能区分同样内容在不同位置的作用。

下一篇可以继续观察编码器层中的前馈网络:注意力负责把信息带到当前位置,前馈网络则在当前位置内部加工这些信息。两个模块轮流工作,才形成 Transformer 一层完整的表示变换。

常见问题

位置编码是不是给每个词加一个位置编号?

不是简单地加一个整数编号,而是为每个位置生成一个与词嵌入同维度的向量,再与词嵌入逐元素相加。

为什么位置编码要和词嵌入相加,而不是拼接?

相加可以保持模型主维度不变,不会额外扩大序列表示的宽度;后续层可以从合成向量中学习内容和位置的组合关系。

位置编码和因果掩码有什么区别?

位置编码提供顺序信息,因果掩码限制当前步骤能看到哪些位置。前者回答“在哪里”,后者回答“能不能看”。