2511 字
13 分钟
Trasformer-编码器的其他部分:位置编码
2026-06-27
无标签

除注意力之外的其他几个组件。本文主要介绍位置编码

由于绝大多数深度学习论文、代码实现(PyTorch/TensorFlow)以及原始 Transformer 论文中的张量布局,默认都是“序列长度 nn × 嵌入维度 dd”(行优先)。这里也遵循一下,不然不好和原文对上

位置编码(Positional Encoding)#

TIP

由于transformer并行处理所有词,本身并不像RNN那样天然理解顺序。为了让模型知道“猫追老鼠”和“老鼠追猫”的区别,必须显式地注入位置信息

词嵌入矩阵 XRn×dX\in \mathbb{R}^{n \times d},生成的位置编码矩阵 PRn×dP\in \mathbb{R}^{n \times d},则位置编码后的输入为:

X=X+PX' = X + P
  • 嵌入向量中偶数索引的维度使用正弦函数,奇数索引的维度使用余弦函数。
  • 加法而不是“拼接”,如果拼接,位置编码的维度会翻倍,导致后续计算复杂度增加。
  • 加法相当于把“词义(Content)”和“位置(Position)”两个信号在同一个向量空间里叠加。后续的线性层 WQW_Q 可以通过训练自动学习如何拆解它们——把一部分维度用于语义,另一部分用于位置。
  • 实际上模型最终需要的是“相对位置”——知道词与词之间的距离和方向更重要。绝对位置只是一个中间手段。

后续生成的 Q,K,VQ,K,V 其实都是基于 XX' 计算的。

设计位置编码的核心矛盾(为什么简单方案不行?)#

我们需要构造一个 PRn×dP \in \mathbb{R}^{n \times d},让它满足以下相互冲突的要求:

要求含义为什么重要
唯一性每个位置 pospos 的编码必须不同。否则两个位置无法区分。
有界性每个数值不能太大(不能爆炸)。否则加到词嵌入上会破坏训练稳定性。
泛化性能处理比训练时更长的序列。测试时句子长度可能超过训练时。
相对位置可表示位置 pos+kpos+k 的编码能由 pospos 的编码通过一个简单的线性变换得到。模型需要知道“距离 kk”才有物理意义。

让我们看看简单方案为什么都失败

  • 方案一:直接用整数下标 [0,1,2,][0, 1, 2, \dots]

    • 违反“有界性”:位置1000的数值是1000,词嵌入通常只有几,加在一起语义被淹没。
    • 而且1和2的距离是1,1000和1001的距离也是1,但数值跨度天差地别,模型无法统一理解“距离”。
  • 方案二:用 One-hot 编码(每个位置一个 nn 维独热向量)

    • 维度 = 序列长度 nn。如果序列长度是1000,位置编码就是1000维,远大于词嵌入维度(通常512或768),维度爆炸。
    • 而且无法泛化到更长的序列(训练时 n=1000n=1000,测试时遇到 n=1200n=1200 就无码可用)。
  • 方案三:用随机初始化的可学习向量

    • 这其实在现代模型里常用(Learnable Positional Encoding),但有缺陷:它只能记住“绝对位置”,无法泛化到训练时未见的长度。而且它没有显式地诱导模型去捕捉“相对位置”。

正弦/余弦编码的设计出发点#

正弦/余弦编码是原始Transformer提出的解决方案。它的核心思想是:

把位置编码看作一种“高频振荡信号”,不同维度具有不同的振荡频率,从而让模型可以从编码的数值变化中“读”出位置信息。

公式(行优先,第 pospos 行、第 2i2i 列和第 2i+12i+1 列):

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

其中:

  • pospos 是位置下标(0, 1, 2, …, n-1)。
  • ii 是维度下标(0, 1, 2, …, d/2 - 1)。
  • dd 是词嵌入维度。

关键变量:分母 100002i/d10000^{2i / d}

  • i=0i = 0 时,分母 = 100000=110000^{0} = 1,频率最高(sin(pos)\sin(pos)),变化最剧烈。

  • i=d/21i = d/2 - 1 时,分母 = 10000(d2)/d1000010000^{(d-2)/d} \approx 10000,频率最低(sin(pos/10000)\sin(pos/10000)),变化极缓慢(周期 2π×10000628322\pi \times 10000 \approx 62832 个位置)。

  • 低位维度(小 ii):编码“局部位置”——相邻几个位置的编码值差异大,能精细区分近距离的词。

  • 高位维度(大 ii):编码“全局位置”——远距离位置差异小,提供平滑的大尺度位置信号。

为什么选“正弦/余弦对”?(数学本质)#

因为正弦/余弦函数族具有完美的“线性平移不变性”性质。

高中数学的和差化积公式:

sin(pos+k)=sin(pos)cos(k)+cos(pos)sin(k)cos(pos+k)=cos(pos)cos(k)sin(pos)sin(k)\begin{aligned} \sin(pos + k) &= \sin(pos)\cos(k) + \cos(pos)\sin(k) \\ \cos(pos + k) &= \cos(pos)\cos(k) - \sin(pos)\sin(k) \end{aligned}

写成矩阵形式,对于任意固定的偏移 kk,存在一个pospos 无关2×22 \times 2 旋转矩阵 Rk\mathbf{R}_k,使得:

[sin(pos+k)cos(pos+k)]=[cosksinksinkcosk]Rk[sin(pos)cos(pos)]\begin{bmatrix} \sin(pos + k) \\ \cos(pos + k) \end{bmatrix} = \underbrace{ \begin{bmatrix} \cos k & \sin k \\ -\sin k & \cos k \end{bmatrix} }_{\mathbf{R}_k} \cdot \begin{bmatrix} \sin(pos) \\ \cos(pos) \end{bmatrix}

扩展到多维:对每一对 (sin(ωipos),cos(ωipos))(\sin(\omega_i pos), \cos(\omega_i pos)) 都独立地应用这个旋转矩阵,就能得到:

Ppos+k=RkPposP_{pos+k} = \mathbf{R}_k \cdot P_{pos}

其中 Rk\mathbf{R}_k 是一个对角块旋转矩阵(每个频率对应一个 2×22 \times 2 块)。

位置 pospos 的编码 PposP_{pos} 经过一个pospos 无关、只与偏移 kk 有关的线性变换,就能变成位置 pos+kpos+k 的编码 Ppos+kP_{pos+k}

这意味着:自注意力机制中的线性层 WQ\mathbf{W}_QWK\mathbf{W}_K 只需要学会这个固定的旋转矩阵族,就能计算出任意两个位置之间的相对距离 kk,而无需为每个绝对位置单独学习一套规则。

这就是为什么正弦/余弦编码能泛化到比训练时更长的序列——因为“旋转”的规律是普适的,不依赖于训练时见过的具体 pospos 范围。

行优先下的具体数值示例#

n=3,d=4n = 3, d = 4,即总共有三个词元,每个词嵌入只有4维,那么位置编码矩阵 PP 的每一行是:

  • 位置 pos=0pos= 0

    • P0,0=sin(0/1)=0P_{0,0} = \sin(0/1) = 0
    • P0,1=cos(0/1)=1P_{0,1} = \cos(0/1) = 1
    • P0,2=sin(0/100002/4)=sin(0/100)=0P_{0,2} = \sin(0/10000^{2/4}) = \sin(0/100) = 0
    • P0,3=cos(0/100)=1P_{0,3} = \cos(0/100) = 1
    • [0,1,0,1][0, 1, 0, 1]
  • 位置 pos=1pos= 1

    • P1,0=sin(1)0.84P_{1,0} = \sin(1) \approx 0.84
    • P1,1=cos(1)0.54P_{1,1} = \cos(1) \approx 0.54
    • P1,2=sin(0.01)0.01P_{1,2} = \sin(0.01) \approx 0.01
    • P1,3=cos(0.01)0.99995P_{1,3} = \cos(0.01) \approx 0.99995
    • [0.84,0.54,0.01,0.99995][0.84, 0.54, 0.01, 0.99995]
  • 位置 pos=2pos= 2

    • P2,0=sin(2)0.91P_{2,0} = \sin(2) \approx 0.91
    • P2,1=cos(2)0.42P_{2,1} = \cos(2) \approx -0.42
    • P2,2=sin(0.02)0.02P_{2,2} = \sin(0.02) \approx 0.02
    • P2,3=cos(0.02)0.9998P_{2,3} = \cos(0.02) \approx 0.9998
    • [0.91,0.42,0.02,0.9998][0.91, -0.42, 0.02, 0.9998]
P=[01010.840.540.010.999950.910.420.020.9998]P=\begin{bmatrix} 0 & 1 & 0 & 1 \\ 0.84 & 0.54 & 0.01 & 0.99995 \\ 0.91 & -0.42 & 0.02 & 0.9998 \\ \vdots & \vdots & \vdots & \vdots \end{bmatrix}

观察:

  • 第0列(i=0i=0,频率最高):0 → 0.84 → 0.91,变化剧烈,相邻位置区别明显。
  • 第2列(i=1i=1,频率较低):0 → 0.01 → 0.02,变化平缓,远距离位置区别明显。

视觉Transformer(ViT)中的位置编码#

在ViT中,输入是一串图像块(Patch),每个Patch经过线性投影变成一个行向量。这里的位置编码矩阵 PP 有两种常见选择:

  • 固定正弦编码(原始ViT):直接把1D正弦编码照搬,但这样会忽略图像的2D结构(横纵坐标不等价)。
  • 可学习位置编码(更常用):把 PP 定义为一个可训练的参数矩阵,让模型自己从数据中学。这相当于放弃了“泛化到更长序列”的能力,但换来了更强的数据集适配性(因为图像分辨率通常是固定的)。

此外,像Swin Transformer这类层次化模型,直接不用的位置编码,改用相对位置偏置(Relative Position Bias)——在注意力得分矩阵上直接加一个可学习的偏置项 Bij\mathbf{B}_{ij},表示第 ii 个Patch和第 jj 个Patch之间的空间偏移。


交互式可视化#

拖动滑块改变 pospos,观察各维度编码值的变化规律——低位维度(小 ii)振荡快,高位维度(大 ii)振荡慢。

位置 pos
0
d_model
512
公式
sin/cos(pos / 10000^(2i/d))
pos 0

sin 偶数维 cos 奇数维  横轴 = 维度 i,纵轴 = 编码值
Trasformer-编码器的其他部分:位置编码
https://biscuit0613.github.io/posts/ml/trasformer-otherpartsinencoder/
作者
Biscuit
发布于
2026-06-27
许可协议
CC BY-NC-SA 4.0
Trasformer-编码器核心:self-attention自注意力机制
视觉先验-在神经网络结构中的体现