视觉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的位置,需要同时用行坐标和列坐标来描述。

我画个图帮你理解:

一维 vs 二维位置编码 一维(NLP) 位置0 位置1 位置2 位置3 位置4 二维(视觉) (0,0) (1,0) (2,0) (0,1) (1,1) (2,1) 每个位置只需要 一个标量索引 每个位置需要 (行, 列) 两个索引 绝对位置编码难以 建模这种二维关系

你看,一维情况只需要一个数字就能描述位置。但二维情况,你得同时知道行和列。绝对位置编码的做法,是把二维坐标展平成一维,然后分配一个编号。这其实丢失了空间结构信息。

举个例子:patch(0,0)和patch(0,1)在图像上是邻居,但展平后它们的索引可能差得很远。模型学到的位置编码,可能根本反映不出这种近邻关系。

绝对位置编码的“视觉病”

我总结一下,绝对位置编码在视觉任务里主要有三个毛病:

问题 具体表现 影响
长度固定 训练时patch数固定,推理时无法改变 无法处理多尺度输入
平移敏感 同一物体在不同位置特征不同 降低泛化能力
二维建模弱 展平后丢失空间结构 难以捕捉局部关系

说白了,绝对位置编码是个“偷懒”的方案。它把位置信息硬塞给模型,但没考虑视觉任务的特殊性。我刚开始做ViT时也用过这个方案,后来发现它在小数据集上还行,一上大规模数据就露馅了。

一个小技巧:如果你实在要用绝对位置编码,可以试试“2D可学习位置编码”。就是给行和列分别学一套编码,然后组合起来。这样至少保留了二维结构,比展平强一些。

为什么不能直接照搬NLP的方案?

你可能想问:NLP里用的Sinusoidal位置编码(正弦余弦那个)不是挺好的吗?为什么不用在视觉里?

我试过。效果一般。

原因在于:Sinusoidal编码假设位置关系是线性的。在语言里,词与词之间的距离确实可以用线性关系近似。但在图像里,两个patch之间的关系是二维的、非线性的。一个patch和它上下左右四个邻居的关系,远比和远处patch的关系重要。Sinusoidal编码没法很好地表达这种“局部性”。

另外,Sinusoidal编码是手工设计的,没有可学习参数。它无法根据任务自适应调整。你想想看,不同数据集对位置信息的敏感度是不一样的。比如医学图像可能更看重绝对位置(器官总是在固定区域),而自然图像更看重相对位置(物体可以出现在任何地方)。手工设计的编码没法兼顾这些差异。

嗯,说到这里,你应该能理解为什么我们需要更好的位置编码方案了。绝对位置编码只是权宜之计,真正要解决视觉Transformer的位置问题,还得靠更聪明的办法。

我个人觉得,一个好的位置编码应该满足三个条件:灵活、可学习、能建模二维关系。这也是后来RoPE在视觉里大放异彩的原因——它恰好解决了这些痛点。

不过,那是下一节的内容了。咱们先把问题搞清楚,再谈解决方案。