理解 Transformer 中的位置编码 Positional Encoding

在学习 Transformer 的自注意力机制时,一个很容易让人困惑的问题是:

Self-Attention 明明可以让每个 token 关注序列中的其他 token,为什么还需要位置编码?

原因在于:Self-Attention 本身并不知道 token 的顺序

位置编码的作用,就是给每个 token 加入顺序信息,让模型不仅知道“这个 token 是什么”,还知道“这个 token 在哪里”。


1. 为什么 Self-Attention 需要位置编码?

在 RNN 中,序列是按时间步一个一个输入的:

\(x_1 \rightarrow x_2 \rightarrow x_3 \rightarrow x_4\)

因此 RNN 天然知道顺序。第一个 token 先进来,第二个 token 后进来,隐状态也是按时间一步步更新的。

但 Self-Attention 不同。Self-Attention 会让序列中的所有 token 同时参与注意力计算。它关注的是 token 之间的关系,而不是按时间顺序依次处理。

这就带来一个问题。

比如:

“我爱你”

和:

“你爱我”

这两个句子包含的 token 是一样的,但顺序不同,含义也不同。

如果模型只看到 token embedding,而没有任何位置信息,那么它很难区分这两个句子的差异。

因此,Transformer 需要额外给每个 token 加入位置信息。这就是位置编码。


2. Transformer 的输入是什么?

每个 token 原本会被映射成一个 token embedding:\(E(x_t)\),它表示“这个 token 是什么”。

位置编码则为每个位置生成一个向量:\(P_t\),它表示“这个 token 在第几个位置”。

Transformer 的实际输入是二者相加:\(X_t = E(x_t) + P_t\),也就是说,模型看到的不是单纯的 token embedding,而是:

token embedding + position encoding
词义信息 + 位置信息

例如:

\(E(\text{我}) + P_0\)

表示“第 0 个位置上的‘我’”。

\(E(\text{爱}) + P_1\)

表示“第 1 个位置上的‘爱’”。

\(E(\text{你}) + P_2\)

表示“第 2 个位置上的‘你’”。

这样,模型就不仅知道 token 是什么,也知道 token 出现在序列中的哪个位置。


3. 为什么 token embedding 和 position encoding 可以相加?

因为它们的维度相同。

如果 token embedding 是 \(d\) 维:

\(E(x_t) \in \mathbb{R}^d\)

那么 position encoding 也是 \(d\) 维:

\(P_t \in \mathbb{R}^d\)

因此二者可以逐维相加:

\(X_t = E(x_t) + P_t\)

假设:

\(E = [e_1, e_2, e_3, e_4]\)

\(P = [p_1, p_2, p_3, p_4]\)

那么:

\(X = [e_1+p_1, e_2+p_2, e_3+p_3, e_4+p_4]\)

这个操作确实改变了原始 token embedding 的数值。

但这并不意味着它破坏了语义。更准确地说,它把原来的表示从“这个 token 是什么”,变成了“这个 token 是什么,并且它在什么位置”。

也就是说,相加不是抹掉语义而是给 token 的表示叠加了位置信息


4. 相加之后,模型怎么利用词义和位置信息?

相加之后,模型并不会显式地把向量拆成两部分:一部分是 token embedding,另一部分是 position encoding。

模型看到的只是一个混合后的向量。

但这并不妨碍模型使用其中的信息。原因在于:位置编码是固定且有规律的,token embedding 是可学习的。模型在训练过程中会逐渐学会如何利用这种混合表示。

例如,Self-Attention 中会计算 Query:

\(Q = XW_Q\)

而:

\(X = E + P\)

所以:

\(Q = (E + P)W_Q\)

根据线性运算的分配律:

\(Q = EW_Q + PW_Q\)

这说明,虽然 token embedding 和 position encoding 已经相加在一起,但它们仍然会以不同方式影响后续计算。

模型不一定需要把二者显式分离出来,它只需要学会从这个“带位置的 token 表示”中提取对任务有用的信息。


5. 正弦/余弦位置编码公式

Transformer 原论文中使用的是正弦/余弦位置编码:

\[\begin{aligned} PE(pos, 2i) &= \sin\left( \frac{pos}{10000^{2i/d}} \right) \\ PE(pos, 2i+1) &= \cos\left( \frac{pos}{10000^{2i/d}} \right) \end{aligned} \]

其中:

符号 含义
\(pos\) token 在序列中的位置
\(i\) 维度对的编号,取值范围 \([0, (d/2)-1]\)
\(d\) embedding 的总维度
\(2i\) 偶数维度
\(2i+1\) 奇数维度

这两个公式表示:

  • 偶数维使用 \(\sin\)
  • 奇数维使用 \(\cos\)
  • 不同维度使用不同频率的位置编码

6. pos 是什么?

\(pos\) 表示 token 在序列中的位置。

如果一个序列长度是 \(T\),那么位置编号通常是:

\(pos = 0, 1, 2, \dots, T-1\)

比如句子:

“我 爱 你”

有 3 个 token,那么:

token pos
0
1
2

每个位置都会生成一个 \(d\) 维的位置编码向量。


7. i 是什么?

\(i\) 表示位置编码中的“维度对编号”。

因为公式中有:

\(PE(pos, 2i)\)

和:

\(PE(pos, 2i+1)\)

所以每个 \(i\) 对应两个维度。

如果 embedding 维度 \(d\) 是偶数,那么:

\(i = 0, 1, 2, \dots, \frac{d}{2}-1\)

例如,当 \(d=8\) 时:

\(i\) 对应维度
0 第 0 维、第 1 维
1 第 2 维、第 3 维
2 第 4 维、第 5 维
3 第 6 维、第 7 维

其中:

  • \(2i\) 维使用 \(\sin\)
  • \(2i+1\) 维使用 \(\cos\)

8. 公式中的 \(10000^{2i/d}\) 是干什么的?

这一部分控制不同维度的位置编码变化速度。

位置编码不是让所有维度都用同样的 \(\sin(pos)\)\(\cos(pos)\),而是让不同维度使用不同频率。

公式是:

\(\sin\left(\frac{pos}{10000^{2i/d}}\right)\)

\(\cos\left(\frac{pos}{10000^{2i/d}}\right)\)

随着 \(i\) 增大:

\(10000^{2i/d}\)

会变大。

因此:

\(\frac{pos}{10000^{2i/d}}\)

会变小。

这意味着,维度编号越靠后,\(\sin\)\(\cos\) 里面的自变量变化越慢。

可以简单理解为:

维度 变化特点
低维度 变化快,适合区分近距离位置
高维度 变化慢,适合表达远距离位置

也就是说,不同维度使用了不同频率的波形。多个频率组合在一起,就可以为每个位置生成一个独特的位置“指纹”。


9. 一个 \(d=8\) 的例子

假设 embedding 维度是:

\(d = 8\)

那么:

\(i = 0, 1, 2, 3\)

位置编码可以展开为:

\(PE(pos) = [\sin(pos), \cos(pos), \sin(pos/10), \cos(pos/10), \sin(pos/100), \cos(pos/100), \sin(pos/1000), \cos(pos/1000)]\)

对于位置 \(pos=1\)

\(PE(1) = [\sin(1), \cos(1), \sin(0.1), \cos(0.1), \sin(0.01), \cos(0.01), \sin(0.001), \cos(0.001)]\)

对于位置 \(pos=2\)

\(PE(2) = [\sin(2), \cos(2), \sin(0.2), \cos(0.2), \sin(0.02), \cos(0.02), \sin(0.002), \cos(0.002)]\)

可以看到,不同位置会得到不同的位置编码向量。


10. 高维度的位置编码数值会更小吗?

不一定。

因为 \(\sin\)\(\cos\) 的输出范围始终是:

\([-1, 1]\)

所以不能说高维度的位置编码数值一定更小。

更准确的说法是:

随着维度编号增大,\(\sin\)\(\cos\) 里面的自变量变小,位置编码随 \(pos\) 的变化更慢。

也就是说,高维度不是“数值更小”,而是“变化频率更低”。


11. 位置编码可以理解成什么?

可以把位置编码理解成每个位置的“指纹”。

第 0 个位置有一组 sin/cos 数值,第 1 个位置有另一组 sin/cos 数值,第 2 个位置又有另一组 sin/cos 数值。

每个位置都有自己的编码模式。

同时,由于这些编码是由多种频率的 sin/cos 组合而成的,不同位置之间的相对关系也具有一定规律。

这对 Transformer 很重要,因为模型不仅需要知道某个 token 在第几个位置,还需要知道两个 token 之间相隔多远。


12. 为什么不用简单的位置编号?

可能有人会问:既然只是表示位置,为什么不直接给第一个 token 加 1,第二个 token 加 2,第三个 token 加 3?

原因是,简单的整数编号不适合直接作为高维向量输入模型。

位置编码需要满足几个要求:

第一,它要和 token embedding 维度一致,方便相加。

第二,不同位置应该有不同表示。

第三,相近位置之间应该有某种连续关系。

第四,模型应该容易从中推断相对位置关系。

正弦/余弦位置编码正好满足这些特点。它既能给每个位置生成不同向量,又具有规律性,方便模型学习位置之间的关系。


13. 总结

位置编码的核心作用是:

给 Self-Attention 加入顺序信息。

Self-Attention 本身不按时间步处理序列,因此它天然缺少顺序感。

所以 Transformer 输入的不是单纯的 token embedding,而是:

\(token\ embedding + position\ encoding\)

也就是:

这个 token 是什么 + 它在什么位置

正弦/余弦位置编码公式为:

\[\begin{aligned} PE(pos, 2i) &= \sin\left( \frac{pos}{10000^{2i/d}} \right) \\ PE(pos, 2i+1) &= \cos\left( \frac{pos}{10000^{2i/d}} \right) \end{aligned} \]

可以将其理解为:

  • 为每个位置生成一个 \(d\) 维向量
  • 偶数维用 \(\sin\)
  • 奇数维用 \(\cos\)
  • 不同维度使用不同频率
  • 低维变化快,高维变化慢
  • 最终为每个 token 注入位置信息

一句话概括:

位置编码就是给每个 token embedding 加上一组有规律的位置向量,让 Transformer 能够感知序列顺序。



posted @ 2026-06-11 15:37  icuic  阅读(63)  评论(0)    收藏  举报