除注意力之外的其他几个组件。本文主要介绍位置编码
由于绝大多数深度学习论文、代码实现(PyTorch/TensorFlow)以及原始 Transformer 论文中的张量布局,默认都是“序列长度 n × 嵌入维度 d”(行优先)。这里也遵循一下,不然不好和原文对上
位置编码(Positional Encoding)#
TIP由于transformer并行处理所有词,本身并不像RNN那样天然理解顺序。为了让模型知道“猫追老鼠”和“老鼠追猫”的区别,必须显式地注入位置信息
词嵌入矩阵 X∈Rn×d,生成的位置编码矩阵 P∈Rn×d,则位置编码后的输入为:
X′=X+P
- 嵌入向量中偶数索引的维度使用正弦函数,奇数索引的维度使用余弦函数。
- 加法而不是“拼接”,如果拼接,位置编码的维度会翻倍,导致后续计算复杂度增加。
- 加法相当于把“词义(Content)”和“位置(Position)”两个信号在同一个向量空间里叠加。后续的线性层 WQ 可以通过训练自动学习如何拆解它们——把一部分维度用于语义,另一部分用于位置。
- 实际上模型最终需要的是“相对位置”——知道词与词之间的距离和方向更重要。绝对位置只是一个中间手段。
后续生成的 Q,K,V 其实都是基于 X′ 计算的。
设计位置编码的核心矛盾(为什么简单方案不行?)#
我们需要构造一个 P∈Rn×d,让它满足以下相互冲突的要求:
| 要求 | 含义 | 为什么重要 |
|---|
| 唯一性 | 每个位置 pos 的编码必须不同。 | 否则两个位置无法区分。 |
| 有界性 | 每个数值不能太大(不能爆炸)。 | 否则加到词嵌入上会破坏训练稳定性。 |
| 泛化性 | 能处理比训练时更长的序列。 | 测试时句子长度可能超过训练时。 |
| 相对位置可表示 | 位置 pos+k 的编码能由 pos 的编码通过一个简单的线性变换得到。 | 模型需要知道“距离 k”才有物理意义。 |
让我们看看简单方案为什么都失败:
-
方案一:直接用整数下标 [0,1,2,…]
- 违反“有界性”:位置1000的数值是1000,词嵌入通常只有几,加在一起语义被淹没。
- 而且1和2的距离是1,1000和1001的距离也是1,但数值跨度天差地别,模型无法统一理解“距离”。
-
方案二:用 One-hot 编码(每个位置一个 n 维独热向量)
- 维度 = 序列长度 n。如果序列长度是1000,位置编码就是1000维,远大于词嵌入维度(通常512或768),维度爆炸。
- 而且无法泛化到更长的序列(训练时 n=1000,测试时遇到 n=1200 就无码可用)。
-
方案三:用随机初始化的可学习向量
- 这其实在现代模型里常用(Learnable Positional Encoding),但有缺陷:它只能记住“绝对位置”,无法泛化到训练时未见的长度。而且它没有显式地诱导模型去捕捉“相对位置”。
正弦/余弦编码的设计出发点#
正弦/余弦编码是原始Transformer提出的解决方案。它的核心思想是:
把位置编码看作一种“高频振荡信号”,不同维度具有不同的振荡频率,从而让模型可以从编码的数值变化中“读”出位置信息。
公式(行优先,第 pos 行、第 2i 列和第 2i+1 列):
Ppos, 2iPpos, 2i+1=sin(100002i/dpos)=cos(100002i/dpos)其中:
- pos 是位置下标(0, 1, 2, …, n-1)。
- i 是维度下标(0, 1, 2, …, d/2 - 1)。
- d 是词嵌入维度。
关键变量:分母 100002i/d。
-
当 i=0 时,分母 = 100000=1,频率最高(sin(pos)),变化最剧烈。
-
当 i=d/2−1 时,分母 = 10000(d−2)/d≈10000,频率最低(sin(pos/10000)),变化极缓慢(周期 2π×10000≈62832 个位置)。
-
低位维度(小 i):编码“局部位置”——相邻几个位置的编码值差异大,能精细区分近距离的词。
-
高位维度(大 i):编码“全局位置”——远距离位置差异小,提供平滑的大尺度位置信号。
为什么选“正弦/余弦对”?(数学本质)#
因为正弦/余弦函数族具有完美的“线性平移不变性”性质。
高中数学的和差化积公式:
sin(pos+k)cos(pos+k)=sin(pos)cos(k)+cos(pos)sin(k)=cos(pos)cos(k)−sin(pos)sin(k)写成矩阵形式,对于任意固定的偏移 k,存在一个与 pos 无关的 2×2 旋转矩阵 Rk,使得:
[sin(pos+k)cos(pos+k)]=Rk[cosk−sinksinkcosk]⋅[sin(pos)cos(pos)]扩展到多维:对每一对 (sin(ωipos),cos(ωipos)) 都独立地应用这个旋转矩阵,就能得到:
Ppos+k=Rk⋅Ppos其中 Rk 是一个对角块旋转矩阵(每个频率对应一个 2×2 块)。
位置 pos 的编码 Ppos 经过一个与 pos 无关、只与偏移 k 有关的线性变换,就能变成位置 pos+k 的编码 Ppos+k。
这意味着:自注意力机制中的线性层 WQ 和 WK 只需要学会这个固定的旋转矩阵族,就能计算出任意两个位置之间的相对距离 k,而无需为每个绝对位置单独学习一套规则。
这就是为什么正弦/余弦编码能泛化到比训练时更长的序列——因为“旋转”的规律是普适的,不依赖于训练时见过的具体 pos 范围。
行优先下的具体数值示例#
取 n=3,d=4,即总共有三个词元,每个词嵌入只有4维,那么位置编码矩阵 P 的每一行是:
-
位置 pos=0:
- P0,0=sin(0/1)=0
- P0,1=cos(0/1)=1
- P0,2=sin(0/100002/4)=sin(0/100)=0
- P0,3=cos(0/100)=1
- 即 [0,1,0,1]
-
位置 pos=1:
- P1,0=sin(1)≈0.84
- P1,1=cos(1)≈0.54
- P1,2=sin(0.01)≈0.01
- P1,3=cos(0.01)≈0.99995
- 即 [0.84,0.54,0.01,0.99995]
-
位置 pos=2:
- P2,0=sin(2)≈0.91
- P2,1=cos(2)≈−0.42
- P2,2=sin(0.02)≈0.02
- P2,3=cos(0.02)≈0.9998
- 即 [0.91,−0.42,0.02,0.9998]
P=00.840.91⋮10.54−0.42⋮00.010.02⋮10.999950.9998⋮观察:
- 第0列(i=0,频率最高):0 → 0.84 → 0.91,变化剧烈,相邻位置区别明显。
- 第2列(i=1,频率较低):0 → 0.01 → 0.02,变化平缓,远距离位置区别明显。
在ViT中,输入是一串图像块(Patch),每个Patch经过线性投影变成一个行向量。这里的位置编码矩阵 P 有两种常见选择:
- 固定正弦编码(原始ViT):直接把1D正弦编码照搬,但这样会忽略图像的2D结构(横纵坐标不等价)。
- 可学习位置编码(更常用):把 P 定义为一个可训练的参数矩阵,让模型自己从数据中学。这相当于放弃了“泛化到更长序列”的能力,但换来了更强的数据集适配性(因为图像分辨率通常是固定的)。
此外,像Swin Transformer这类层次化模型,直接不用的位置编码,改用相对位置偏置(Relative Position Bias)——在注意力得分矩阵上直接加一个可学习的偏置项 Bij,表示第 i 个Patch和第 j 个Patch之间的空间偏移。
交互式可视化#
拖动滑块改变 pos,观察各维度编码值的变化规律——低位维度(小 i)振荡快,高位维度(大 i)振荡慢。
公式
sin/cos(pos / 10000^(2i/d))
pos
0
sin 偶数维
cos 奇数维
横轴 = 维度 i,纵轴 = 编码值