视觉Transformer的困境:ViT为何需要位置编码?
说实话,我第一次把Transformer搬到视觉任务上时,心里还挺美。心想:NLP那边都验证过了,自注意力机制这么强,直接拿过来用不就行了?结果一跑实验,效果惨不忍睹。模型根本学不到东西,训练loss死活降不下去。
我当时就纳闷了:同样的架构,怎么换个领域就不灵了?
后来我才意识到——Transformer本身是“位置盲”的。它不像CNN,天生就带着空间结构的信息。你想想看,CNN的卷积核在图像上滑动时,每个位置天然就对应着图像的一块区域。但Transformer呢?它把图像切成patch,然后一股脑全扔进自注意力里。模型根本不知道哪个patch在左上角,哪个在右下角。
嗯,这就是视觉Transformer的第一个大坑。
自注意力的“排列不变性”
咱们先看一个核心问题。自注意力机制的计算公式是这样的:
Attention(Q, K, V) = softmax(QK^T / √d) V
这里有个关键点:Q、K、V都是输入序列的函数。如果你把输入序列的顺序打乱,Q和K的对应关系也会跟着变。但问题在于,softmax之后的结果,对序列顺序是“对称”的。
我举个例子你就明白了。假设输入是 [patch_A, patch_B],自注意力计算时,A和B会互相看。但如果输入变成 [patch_B, patch_A],结果完全一样。模型根本区分不了“A在左边,B在右边”和“A在右边,B在左边”。
这在NLP里还好说,因为语言本身就有顺序。但在视觉里,空间位置就是一切。一个猫头在左上角还是右下角,对识别任务来说天差地别。
核心结论:没有位置编码的ViT,等价于把图像当成“一袋patch”来处理。模型只能看到“有什么”,看不到“在哪里”。
绝对位置编码的“老办法”
最早期的ViT论文(就是Dosovitskiy那篇)用的是绝对位置编码。做法很简单:给每个位置分配一个可学习的向量,然后加到patch embedding上。
代码大概长这样:
class ViT(nn.Module):
def __init__(self, ...):
super().__init__()
# 可学习的位置编码
self.pos_embed = nn.Parameter(
torch.randn(1, num_patches + 1, embed_dim)
)
def forward(self, x):
# x: [batch, num_patches, embed_dim]
x = x + self.pos_embed # 直接相加
# 然后过Transformer
...
看着挺简单对吧?但我在实际项目里踩过坑。这个做法有几个硬伤:
- 固定长度限制:训练时用了196个patch(14x14),推理时就不能改成256个。位置编码是学出来的,没见过的位置就瞎猜。
- 平移不变性丢失:CNN天然对平移鲁棒,但ViT加上绝对位置编码后,同一个物体出现在不同位置,模型会学到不同的特征。这其实是不合理的。
- 空间关系建模弱:绝对位置编码只告诉模型“我在第i个位置”,但没告诉它“我离第j个位置有多远”。说白了,它是个“死记硬背”的方案。
我曾经踩过的坑:有一次做目标检测,输入分辨率从224x224换到384x384。结果模型直接崩了,因为patch数量变了,位置编码的维度对不上。后来我不得不做插值,但效果还是差一截。从那以后,我就开始研究更灵活的位置编码方案了。
视觉任务的特殊性
咱们再往深了想。视觉和语言有个本质区别:视觉信息是二维的,语言是一维的。
NLP里的位置编码,只需要处理一个方向(从左到右)。但图像是二维的,有行有列。一个patch的位置,需要同时用行坐标和列坐标来描述。
我画个图帮你理解:
你看,一维情况只需要一个数字就能描述位置。但二维情况,你得同时知道行和列。绝对位置编码的做法,是把二维坐标展平成一维,然后分配一个编号。这其实丢失了空间结构信息。
举个例子:patch(0,0)和patch(0,1)在图像上是邻居,但展平后它们的索引可能差得很远。模型学到的位置编码,可能根本反映不出这种近邻关系。
绝对位置编码的“视觉病”
我总结一下,绝对位置编码在视觉任务里主要有三个毛病:
| 问题 | 具体表现 | 影响 |
|---|---|---|
| 长度固定 | 训练时patch数固定,推理时无法改变 | 无法处理多尺度输入 |
| 平移敏感 | 同一物体在不同位置特征不同 | 降低泛化能力 |
| 二维建模弱 | 展平后丢失空间结构 | 难以捕捉局部关系 |
说白了,绝对位置编码是个“偷懒”的方案。它把位置信息硬塞给模型,但没考虑视觉任务的特殊性。我刚开始做ViT时也用过这个方案,后来发现它在小数据集上还行,一上大规模数据就露馅了。
一个小技巧:如果你实在要用绝对位置编码,可以试试“2D可学习位置编码”。就是给行和列分别学一套编码,然后组合起来。这样至少保留了二维结构,比展平强一些。
为什么不能直接照搬NLP的方案?
你可能想问:NLP里用的Sinusoidal位置编码(正弦余弦那个)不是挺好的吗?为什么不用在视觉里?
我试过。效果一般。
原因在于:Sinusoidal编码假设位置关系是线性的。在语言里,词与词之间的距离确实可以用线性关系近似。但在图像里,两个patch之间的关系是二维的、非线性的。一个patch和它上下左右四个邻居的关系,远比和远处patch的关系重要。Sinusoidal编码没法很好地表达这种“局部性”。
另外,Sinusoidal编码是手工设计的,没有可学习参数。它无法根据任务自适应调整。你想想看,不同数据集对位置信息的敏感度是不一样的。比如医学图像可能更看重绝对位置(器官总是在固定区域),而自然图像更看重相对位置(物体可以出现在任何地方)。手工设计的编码没法兼顾这些差异。
嗯,说到这里,你应该能理解为什么我们需要更好的位置编码方案了。绝对位置编码只是权宜之计,真正要解决视觉Transformer的位置问题,还得靠更聪明的办法。
我个人觉得,一个好的位置编码应该满足三个条件:灵活、可学习、能建模二维关系。这也是后来RoPE在视觉里大放异彩的原因——它恰好解决了这些痛点。
不过,那是下一节的内容了。咱们先把问题搞清楚,再谈解决方案。