← 课程首页 | M0 理论地基 论文深度拆解
ICLR 2021 · Google Research · 视觉领域的范式转折

一张图片
值 16×16 个词

An Image is Worth 16×16 Words

上一篇讲的 Transformer 只吃"词序列"。这篇论文用一个近乎粗暴的办法,让它直接吃图像——把图切成小方块,每块当一个词。就这么简单,却掀翻了 CNN 统治视觉的十年。

图像块 16×16 序列长度 196+1 ViT-Base d=768 层数 12
00 / 困境

Transformer 想吃图像,但一口就噎死

上一篇你已经知道:Transformer 处理的是一串 token。文字天然是一串词,切开就能用。那图像呢?

最直觉的想法 · 每个像素当一个 token

听起来很自然——文字的最小单位是词,图像的最小单位是像素。但算一下就知道这条路死得多惨:

# 一张标准的 224×224 输入图像
序列长度 n = 224 × 224 = 50,176 个 token

# 注意力复杂度是 O(n²)——每个 token 要和所有 token 算一次
注意力矩阵大小 = 50176 × 50176 = 2,517,630,976

# 25 亿个数,光存这一个矩阵(fp32)就要:
2.5e9 × 4 bytes ≈ 10 GB   # 而这只是一层、一个头、一张图

还记得上一篇第 0 节那张对比表最后一行吗——Transformer 的复杂度是序列长度的平方。在文字里句子几十上百个词,平方一下还能接受;在图像里像素数直接把它送进了不可能的领域。

◆ 这就是 2017-2020 年的僵局

Transformer 在 NLP 摧枯拉朽,视觉界眼馋了三年却进不来。期间有很多妥协方案——只在局部窗口做注意力、用 CNN 先降维再上注意力、把注意力当作 CNN 的插件……都是打补丁,没人敢真的把 CNN 整个扔掉。

ViT 这篇论文的贡献,不在于发明了什么新机制——它几乎原封不动地用了标准 Transformer 编码器。它的贡献是提出了一个简单到让人拍大腿的预处理方式,把"图像"变成了"合法的 token 序列"。

01 / 核心思想

既然像素太多,那就切成方块

解法一句话说完:把图像切成 16×16 像素的小方块(patch),每一块当作一个"词"。

# 同样的 224×224 图像
每边切成 224 ÷ 16 = 14 块
总共 14 × 14 = 196 个 patch

# 序列长度从 50176 降到 196,缩小了 256 倍
注意力矩阵 = 196 × 196 = 38,416   # 完全跑得动

这就是标题的由来:一张图片值 16×16 个词——图像被翻译成了一门"视觉语言",每个 patch 是一个"视觉单词"。

原图 224×224 切成 14×14 = 196 块 (图中简化为 4×4) 展平 按行展开成一维序列 1 2 3 4 5 ··· 196 每块 16×16×3 = 768 个数值 线性投影 变成 token 序列 token 1 · 768 维向量 token 2 · 768 维向量 token 3 · 768 维向量 ··· token 196 · 768 维向量 到这一步,它和一句 196 个词的话 在 Transformer 眼里完全一样 整篇论文的核心创新,就是从「原图」到「token 序列」这两步预处理 之后送进的,是原封不动的标准 Transformer 编码器
图 1 · ViT 的核心流程。切块 → 展平 → 线性投影 → 得到和文字 token 完全同构的序列。
● 为什么是 16?

这是一个精度与算力的取舍旋钮。patch 越小,切出的块越多、序列越长、细节保留越好,但计算量按平方涨;patch 越大则相反。

论文对比过 16×16 和 32×32:更小的 patch 效果更好但更贵。16 是实践中的甜点。后来的模型也常用 14 或 8——这是个可调超参,不是什么神圣数字。

02 / Patch Embedding

一块 16×16 的图,怎么变成一个向量

这一步是整篇论文唯一需要认真算的地方。跟着形状走一遍:

STEP 1 · 切块

输入图像 3 × 224 × 224(RGB 三通道),切成 14×14 个块,每块 3 × 16 × 16。

STEP 2 · 展平成一维

每块把三个通道的所有像素拉直排成一列:16 × 16 × 3 = 768 个数值。

于是一块变成 768 维向量,196 块就是 196 × 768。

STEP 3 · 线性投影

乘一个可学习矩阵 E(768 × D),把原始像素值投影到模型的隐藏维度 D。

ViT-Base 的 D 也是 768,所以这一步形状不变——但数值含义完全变了:从"原始像素亮度"变成了"可学习的语义表示"。

● 巧合值得澄清

ViT-Base 里 16×16×3 = 768 恰好等于 d_model = 768,很多人误以为这两个数必须相等。它们完全无关。前者由 patch 尺寸决定,后者是模型宽度超参。ViT-Large 的 d_model 是 1024,patch 仍是 16×16(展平还是 768),投影矩阵就是 768×1024。

这正是你上一篇学的 d_model 概念——模型内部所有 token 统一的向量宽度。和 BEVFormer 的 256、Qwen 的 hidden_size 是同一个东西。

工程上的小聪明:用一个卷积就搞定了

"切块 + 展平 + 线性投影"这三步,实现时不用真的写循环切图——一个卷积层就等价完成:

# 核大小=16、步长=16 的卷积 —— 卷积核不重叠地滑过整张图
# 每滑一次正好覆盖一个 patch,输出一个 D 维向量
patch_embed = nn.Conv2d(
    in_channels=3,
    out_channels=768,      # D
    kernel_size=16,        # patch 尺寸
    stride=16             # 步长=核大小 → 不重叠
)

x = patch_embed(img)          # [B,3,224,224] → [B,768,14,14]
x = x.flatten(2).transpose(1,2)  # → [B,196,768]  搞定

卷积核不重叠地滑动,每一次覆盖恰好是一个 patch,输出的就是该 patch 的投影结果。两行代码完成整个 patch embedding——这也是为什么说 ViT 的实现极其简洁。

◆ 一个有意思的观察

ViT 号称"抛弃 CNN",结果第一层用的却是一个卷积。但这个卷积和 CNN 里的卷积作用完全不同:CNN 用小核(3×3)大量重叠滑动来提取局部特征、层层堆叠扩大感受野;ViT 这里的卷积只是一个高效的切块工具,核大小等于步长、完全不重叠、只有一层。它不承担"逐层提特征"的职责——那个活全交给后面的注意力了。

03 / 动手

亲手切一张图,看它怎么变成 token

下面是一张模拟的驾驶场景图,按 ViT 的方式切成了网格。点击任意一个方块,右边会实时显示这一块经历的完整流水线:它在网格的哪个位置、序号是几、展平成多少个数、最后落到 token 序列的哪一格。

模拟输入图像 · 点击任意 patch
该 patch 的处理流水线
① 网格位置
— 点击左图 —
图像被切成 14×14 网格
② 序列编号
—
按行优先展开,从 1 到 196
③ 展平
—
16×16 像素 × 3 通道 拉成一列
④ 线性投影 × E
—
投影到模型隐藏维度 D=768
进入 Transformer 的完整 token 序列(197 个)
● 点的时候留意

无论你点图像的哪个位置——天空、路面、车辆——处理流程完全相同。ViT 对每个 patch 一视同仁,不因为"这块看起来重要"就特殊对待。

哪块重要是后面的注意力层决定的,不是在切块阶段。这个"预处理阶段绝对公平、重要性交给注意力学"的设计,正是上一篇讲的 Transformer 哲学在视觉上的复刻。

04 / 两个补丁

CLS token 和位置编码:两个必要的外挂

CLS token —— 一个"不代表任何图像块"的 token

分类任务需要一个代表整张图的向量。196 个 patch token 各代表一小块,谁来代表全局?

ViT 的做法(借自 BERT):凭空造一个可学习的 token,插在序列最前面。它不对应图像的任何位置,初始值是随机的可学习参数。

在 12 层注意力中,这个 CLS token 和所有 patch token 反复交互,不断从各个图像块吸收信息。到最后一层,它就成了整张图的浓缩表示,接一个分类头就能输出类别。

◆ 为什么不用"所有 token 求平均"?

其实也可以——论文试过全局平均池化,效果差不多。CLS token 的优势是它可以主动学习"该关注谁"(通过注意力加权),而平均池化是无差别地一视同仁。ViT 选 CLS 主要是为了和 BERT 保持一致、便于对照实验。后来很多视觉模型反而回归了平均池化。

这是个有意思的细节:论文里的很多设计不是"最优解",而是"为了可比性的选择"。读论文时要能分辨这两者。

位置编码 —— 又是这个老问题

上一篇讲过:注意力机制本身没有顺序概念。对图像来说这个问题更严重——如果不加位置信息,把 196 个 patch 随机打乱,ViT 看到的是完全一样的东西。一张脸和把五官打散重排的图,在它眼里没有区别。

所以要给每个 patch 加上"我在图像的哪个位置"的信息。但这里 ViT 做了个和原始 Transformer 不同的选择:

原始 TransformerViT
编码方式正弦/余弦函数,固定不变可学习的参数,训练中优化
维度1D(第几个词)1D(第几个 patch)
为什么这样选能外推到更长句子图像尺寸通常固定,不需要外推
● 论文里一个漂亮的发现

ViT 用的是一维位置编码(只知道"我是第 37 个"),论文作者也试过精心设计的二维编码(显式告诉它"我在第 3 行第 9 列")——结果几乎没有提升。

原因在可视化里揭晓了:模型自己从一维编号里学出了二维空间结构。把学到的位置编码两两算相似度会发现,相邻行、相邻列的 patch 位置编码彼此相似——它自己搞明白了"第 15 个和第 1 个是上下相邻的"这种二维关系(因为 14 个一行)。

这又是那句话的印证:不需要人告诉它结构,任务会逼它学出来。

加上这两个补丁后,送进 Transformer 的完整序列是:

z₀ = [ x_cls ; x¹E ; x²E ; ... ; x¹⁹⁶E ] + E_pos

#   ↑CLS      ↑196 个 patch 投影后的 token      ↑位置编码(直接相加)
# 形状: [197, 768]
05 / 主体

剩下的部分,全是上一篇的老朋友

这是 ViT 最"偷懒"也最有说服力的地方——准备好 token 序列之后,它用的是一字未改的标准 Transformer 编码器。

197 个 token + 位置编码 Transformer 编码器 × 12 层 多头自注意力 每个图像块「看」遍全图所有块 +残差+LN 前馈网络 MLP 768 → 3072 → 768 +残差+LN 取 CLS token → 分类头 → 类别 和上一篇对比 结构完全一致, 只是 token 从「词」 换成了「图像块」。 注意:只用编码器
图 2 · ViT 主体架构。除了输入端的 patch 处理,其余部分和上一篇讲的 Transformer 编码器一模一样。
◆ 这种"偷懒"恰恰是论文最强的论证

如果 ViT 为视觉大改了架构,那结论会很模糊——到底是"Transformer 适合视觉"还是"改造后的新架构适合视觉"?

一字不改地照搬,然后证明它能打败 CNN,这个论证力度是压倒性的:说明 Transformer 是一个真正通用的架构,图像和文字的差异可以完全在"如何 token 化"这一步吸收掉。这个洞察后来直接催生了多模态大模型——只要能变成 token,什么都能塞进同一个 Transformer。你学的 VLA 正是这个思路的产物。

06 / 代价

ViT 的阿喀琉斯之踵:它什么先验都没有

这一节是整篇论文最有价值的部分,也是最容易被跳过的部分。ViT 不是无条件优于 CNN——它有个很硬的前提条件。

CNN 自带两个"出厂设置"

卷积神经网络的结构里,天然内置了两条关于图像的假设(学术上叫"归纳偏置"):

这两条假设正确且免费——不需要从数据里学,CNN 一出生就懂。所以 CNN 在小数据集上也能训得不错。

而 ViT 几乎什么都不假设

ViT 的自注意力从第一层起就是全局的——每个 patch 平等地看所有 patch,它不知道相邻的块更相关,也不知道平移不变性。除了 patch 切分保留了一点点局部性,其余全靠自己从数据里学。

结果就是那个著名的结论:

训练数据规模谁赢为什么
ImageNet(约 130 万张)CNN 明显更好数据不够,ViT 学不出那些"免费的先验"
ImageNet-21k(约 1400 万张)打平ViT 开始追上来
JFT-300M(约 3 亿张)ViT 反超数据足够多时,学出来的先验比人手写的更好
◆ 这是全文最重要的一句话

论文原文的意思是:Transformer 缺少 CNN 固有的归纳偏置(如平移等变性和局部性),因此在数据量不足时泛化能力不好。

换个角度理解:归纳偏置是一种"带偏见的捷径"。数据少时,好偏见能救命;数据多时,偏见反而成了天花板——因为真实世界的规律可能比人写的假设更复杂。ViT 赌的是"数据管够",而这个赌注在 2020 年之后被证明是对的。

这个"先验 vs 数据"的取舍,是理解现代 AI 的一把钥匙,也直接解释了自动驾驶的路线之争:

✓ 对应到智驾路线

手写规则的模块化系统 = 最强的归纳偏置。少量数据就能跑,但天花板低、长尾场景写不完。

端到端大模型(FSD v14、小鹏 VLA) = 最弱的归纳偏置。需要海量真实驾驶数据(小鹏 VLA 2.0 的训练数据是 10 亿公里级别),但上限高得多。

车企拼命囤积驾驶数据、建数据闭环,本质上就是在下 ViT 这一篇论证过的同一个赌注——用数据规模换掉人工先验。你 Project 3 里的数据闭环章节,价值根源就在这里。

07 / 内部观察

ViT 到底看到了什么

论文做了几个漂亮的可视化,揭示了 ViT 内部真实的运作方式。这些发现比性能数字更有启发性。

发现一 · 浅层就有全局注意力,但也保留了局部头

论文统计了每一层各个注意力头的"平均注意力距离"(一个头关注的 patch 离自己有多远):

网络层数(浅 → 深) 注意力距离 远 近 浅层:两极分化 中层:逐渐收拢 深层:全部全局 浅层这些低点 = 像 CNN 一样只看邻域
图 3 · 注意力距离分布(示意)。浅层的头分成两拨:一部分只看邻近 patch(类 CNN 行为),一部分一上来就看全图。越深越全局。

这个结果很有意思:ViT 自己"重新发明"了 CNN 的局部性——浅层有些头自发学会了只关注邻域。但同时它还保留了另一批全局头,这是 CNN 做不到的(CNN 要堆很多层才能获得大感受野)。

所以 ViT 的优势不是"全局比局部好",而是:它能同时拥有两者,并且由数据决定各自的比例。

发现二 · 位置编码自己学出了二维结构

前面第 4 节提过:可视化位置编码的两两相似度会发现,同行、同列的 patch 位置编码明显更相似。模型从"第几个"这个一维编号里,自己还原出了"第几行第几列"的二维布局。

发现三 · 第一层投影学出了类似 Gabor 滤波器的模式

把 patch embedding 的投影矩阵可视化,会看到边缘检测、颜色渐变、纹理方向这类基础视觉模式——和 CNN 第一层卷积核学到的东西高度相似。

● 三个发现指向同一个结论

ViT 没有被告知任何关于图像的知识,但它自发地重新发现了:局部性、二维空间结构、边缘与纹理特征。

这些正是几十年来计算机视觉研究者手工设计进 CNN 里的东西。当数据足够多时,学习可以复现设计,并且超越它。这是本篇论文留给整个领域最深的一句话。

08 / 回到智驾

ViT 在你的自动驾驶栈里在哪

这一篇不像 BEVFormer 那样直接就是智驾模型,但它是整个视觉侧的地基。串一下:

ViT 的概念在智驾模型里的位置
Patch tokenBEVFormer 里 6 路相机图像特征的 token;VLA 里的"视觉 token"
Patch embedding骨干网络输出的图像特征图(ResNet 或 ViT 都可以充当)
纯注意力骨干BEVFormer 可用 ViT 系骨干替代 ResNet;后续 BEV 模型大量采用
CLS tokenUniAD 的 Ego Query、VAD 的场景全局向量——都是"一个代表全局的可学习 token"
可学习位置编码BEV 网格的空间位置编码
"万物皆可 token"VLA 把视觉 token + 导航 token + 动作 token 拼成一个序列
✓ 最关键的一条连接

你之前问过"导航指令怎么和摄像头画面一起进模型"——答案的根就在 ViT。

ViT 证明了图像可以被 token 化,并且和文字 token 在 Transformer 眼里没有区别。一旦这件事成立,把导航信息也编码成 token 塞进同一个序列,就变成了顺理成章的事。没有 ViT 打通"图像→token"这一步,就没有后来的多模态大模型,也就没有 VLA。

和上一篇的关系,一句话总结

上一篇(Transformer)建立了处理 token 序列的通用机器;这一篇(ViT)证明了图像也能变成合法的 token 序列。两篇合起来,才让"用同一个架构处理所有模态"成为可能——这正是 BEVFormer、UniAD、VLA 全部成立的前提。

读完的自测

自测一 · 说清为什么不能用像素当 token

50176 个 token → O(n²) 注意力矩阵 25 亿。这个数字要能随口算出来。

自测二 · 手推 patch embedding 的形状变化

[3,224,224] → [196, 768] → [197, 768],每一步为什么。

自测三 · 讲清归纳偏置的取舍

CNN 有什么先验、ViT 没有什么、为什么这导致了对数据量的不同需求。这是面试高频深度题。

自测四 · 用两行代码实现 patch embedding

那个 Conv2d(3, 768, kernel_size=16, stride=16) 的技巧,按你的"必须手敲"原则,这个要能默写。

下一篇建议读 LSS(Lift, Splat, Shoot)——它和 BEVFormer 是 BEV 的两大流派,你目前只学了 BEVFormer 这一派。读完 LSS,你的 BEV 认知才算完整。