1. 位置编码:为什么Transformer需要“空间感”
大家好,我是老张,在AI和视觉领域摸爬滚打了十几年。今天咱们来聊一个Transformer模型里看似不起眼、实则至关重要的组件——位置编码。如果你玩过ViT(Vision Transformer),肯定知道它会把一张图片切成一个个小方块(patch),然后把这些方块像处理句子里的单词一样送进模型。但这里有个大问题:Transformer的核心注意力机制,天生是“位置盲”的。
想象一下,你把一句话的单词顺序完全打乱,比如“我爱北京天安门”变成“天安门爱我北京”,一个标准的Transformer(如果不做特殊处理)计算出的注意力权重可能是一样的,因为它只关心词与词之间的内容关联,不关心谁在前谁在后。这显然不对!对于图像更是如此,把一张猫脸的鼻子和眼睛位置对调,模型如果分辨不出来,那生成的“猫”可能就成克苏鲁了。
所以,我们必须给每个输入的小方块(或单词)注入位置信息,告诉模型:“嘿,你现在处理的是第几个位置的数据。”这就是位置编码的使命。在原始的ViT论文里,用的是绝对位置编码,简单粗暴,每个位置学一个固定的向量,直接加到输入特征上。这种方法实现起来简单,但有个硬伤:缺乏外推性。比如模型在训练时只见过最多14x14(196个)的patch位置,你突然给它一张更精细的、有20x20(400个)patch的图,它就没见过第197到第400号位置该长啥样,性能就可能暴跌。
为了解决这个问题,研究者们提出了各种相对位置编码方法,而今天的主角——旋转位置编码,就是其中一颗耀眼的明星。它不仅在自然语言处理的大模型(如LLaMA、GLM)里大放异彩,在视觉领域也展现出独特的魅力。它用一种非常“几何”的方式,将位置信息巧妙地编织进注意力计算中,既保留了绝对位置的确定性,又蕴含了相对位置的灵活性。
2. 从绝对到相对:位置编码的演进之路
2.1 经典绝对位置编码:简单但局限
我们先回顾一下最经典的Sinusoidal绝对位置编码,这也是原始Transformer论文和早期ViT常用的方法。它的核心思想是为序列中的每个位置(比如第0个、第1个patch)生成一个独一无二的、固定不变的向量。这个向量通常通过正弦和余弦函数生成,公式长这样:
对于位置 pos 和维度索引 i:
- 如果
i是偶数:PE(pos, i) = sin(pos / 10000^(2i/d_model)) - 如果
i是奇数:PE(pos, i) = cos(pos / 10000^(2i/d_model))
这里的 d_model 是特征维度。这个设计很巧妙,因为对于固定的偏移量 k,位置 pos + k 的编码可以被表示为位置 pos 编码的线性函数,这隐含了一定的相对位置关系。在代码里,我们通常会预计算一个位置编码矩阵:
import torch
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe) # 不参与训练
def forward(self, x):
# x: [batch_size, seq_len, d_model]
return x + self.pe[:x.size(1), :]
优势一目了然:实现简单,可以预先算好,不增加训练参数,计算速度快。 劣势也很明显:最大的问题就是前面说的外推性差。模型在训练长度之外的位置编码是未定义的。虽然可以通过在微调时对位置编码进行插值来缓解,比如把训练时512长度的编码“拉伸”到1024去用,但这并非原生支持,效果不一定稳定。
2.2 相对位置编码的兴起:关注关系
正因为绝对位置编码的局限,大家开始转向相对位置编码。它的核心思想不是告诉模型“你在第几个位置”,而是告诉模型“你和另一个位置相隔多远”。在注意力计算中,这通常体现为在查询向量 q 和键向量 k 计算内积时,加上一个与它们相对位置 (m-n) 相关的偏置项。
相对位置编码让模型能更好地理解序列内部的结构关系,比如在图像中,一个像素和它相邻像素的关系,远比和遥远角落像素的关系更紧密。但是,很多相对位置编码的实现需要修改注意力计算结构,引入额外的可学习参数或计算步骤,增加了模型的复杂度和计算开销。
2.3 RoPE的巧妙融合:用绝对实现相对
那么,有没有一种方法能兼具两者的优点呢?旋转位置编码 的提出正是为了回答这个问题。RoPE的核心洞见非常优雅:通过对查询和键向量施加一个依赖于其绝对位置的旋转变换,使得它们在进行内积计算时,结果自然地只依赖于两者的相对位置。
听起来有点绕?我们来打个比方。想象一下,每个位置都有一个特定的“角度”。对于位置 m 的查询向量 q_m,我们把它在特征空间的某个二维子平面上旋转 m * θ 角度;对于位置 n 的键向量 k_n,我们把它旋转 n * θ 角度。那么,旋转后的 q_m 和旋转后的 k_n 做内积,根据几何知识,结果会等于原始 q_m 和 k_n 的内积再乘以一个与 (m-n) * θ 相关的因子。看,绝对位置 m 和 n 消失了,只剩下相对位置 m-n 出现在最终的内积中!
这种方法的美妙之处在于:
- 形式上是绝对的:我们确实为每个绝对位置计算了一个变换(旋转矩阵)。
- 效果上是相对的:注意力分数最终只依赖于相对位置。
- 结构上是简洁的:它不需要改变标准的注意力计算流程,只需在计算
q和k的内积前,对它们进行旋转操作即可。 - 外推性是优秀的:
9990




被折叠的 条评论
为什么被折叠?



