模型是怎么看图像的?
一张图像会变成一串方块 token,从这之后,处理它的机器和读文本的是同一套。
transformer处理的是一个向量序列。文本通过分词变成这个序列。图像得有自己的入口。
让视觉 transformer 跑通的那个招数简单到有点让人失望:把图像切成网格状的小方块,每个方块当一个 token。
用方块当 token
拿一张 224×224 的图,切成 16×16 像素的方块。得到 14×14 = 196 个方块。
把每个方块摊平成一串数字——16 × 16 × 3 个颜色通道 = 768 个值——再通过一个学出来的线性层投影成一个 embedding。现在你有 196 个向量。
这就是一个序列,注意力可以直接在上面跑,不用改任何东西。一片天空的方块会去关注其他天空的方块;装着左眼的方块会去关注装着右眼的方块。
立刻有两个后果。
**方块数随分辨率增长。**图像边长翻倍,方块数就变四倍。由于注意力开销在序列长度上是平方级的,高分辨率图像很快就贵得离谱。这就是视觉模型拼命缩图的原因,也是截图里的小字这类精细细节常常在模型看到之前就已经丢了的原因。
位置必须显式注入,和文本里完全一样。注意力对顺序是盲的,所以没有位置编码,模型看到的是一袋没有次序的方块。视觉模型会加上二维位置信息,这样「在上方」「在左边」才是可还原的。
为什么是方块而不是像素
一个像素一个 token,一张小图就是 50,176 个 token,平方级的注意力开销让这根本跑不动。
一整张图一个 token,空间结构全丢了——你没法问任何东西在哪里。
方块落在中间:少到注意力处理得了,小到空间关系还活着。文本那边做子词分词是同一个折中,理由也一样。
卷积当年的做法有什么不同
在视觉 transformer 之前,图像模型是卷积的。一个小滤波器在图上滑过去,检测局部模式,层层堆起来,从边缘到纹理再到物体。
卷积把两个假设焊死在里面:相邻像素相关,同一个特征出现在哪里都表示同一件事。这两条对图像都成立,所以 CNN 用相对少的数据就能干得不错。
transformer 一个都不假设。从第一层起,任何方块都可以关注任何方块——没有内置的「局部」概念。这份灵活性要用数据来换:视觉 transformer 要达到同样的准确率,需要的训练图像比 CNN 多得多,因为「相邻方块相关」这件事它得自己学,没人告诉它。
规模够大的时候它们会反超 CNN,这和深度学习里到处可见的规律一样:数据一充足,内置假设更少的那一方赢。
分辨率的处理方式
固定的方块网格带来一个真问题:图像的尺寸和长宽比什么都有,而在 224×224 上训练的模型,位置 embedding 只对应 196 个方块。
目前常用的几种做法:
**缩放加填充。**最简单,代价是大图细节被毁掉,奇怪的长宽比还会被扯变形。
**分块平铺。**把大图切成几块模型吃得下的尺寸,分别处理再合并。保住细节,代价是 token 更多、全局视野丢失——通常再配一次缩小后的整图处理来补回整体布局。
**动态分辨率。**插值位置 embedding 来接受可变的方块数。更灵活,也越来越成为标配。
知道自己在跟哪一种打交道,能解释很多观察到的表现。一个读不出截图小字的模型,通常是把字缩没了,而不是「读不懂文字」。
这样做的收益
图像一旦是 token 序列,就能和文本 token 混在同一个上下文里。这就是视觉语言模型的全部基础——一个序列里同时装着两者,注意力跨越这条界线工作。
它也解释了一个值得知道的局限:模型看到的是一格格的方块,不是物体。数一堆小东西、读极小的字、判断精确的空间偏移,这些真的都很难,因为方块网格是一种粗糙的表示,而精细细节在第一层之前就已经被丢掉了。
要点回顾
- 图像变成 token 的办法是切成方块——通常是 16×16 像素——每块投影成一个 embedding。
- 方块数随分辨率平方增长,而注意力开销对方块数是平方级,所以细节很贵。
- 必须加二维位置编码,因为注意力对顺序是盲的。
- 卷积假设了局部性,因此用更少的数据就能凑合;transformer 什么都不假设,规模一大就赢。
- 精细细节常常在模型看到之前就被缩图缩没了。
下一篇: 视觉语言模型