CNN 与 ViT:图像表示与架构比较#
本页内容10 个部分
查看概览图
把归纳偏置换成数据,
把图像换成序列
CNN 用局部性和平移等变性省数据;ViT 放弃这些先验,换来 scaling 上限和跨模态的统一接口。
只要能变成 token 序列,就能进同一个 Transformer
-
文本 → 每个词 / subword 一个 tokenGPT · BERT · LLaMA -
图像 → 切 patchViT · CLIP(视觉侧) -
视频 → spacetime patchSora · CogVideoX -
音频 → 分帧Whisper · AudioLM -
3D 点云 → 分组Point-BERT
是唯一前提
交互时机决定交互深度
-
深
拼接(Early Fusion)两个模态的 token 直接混在一起 · 每一层都交互 -
浅
Cross-Attention(Late Fusion)各自编码后再交互 · 只在特定层交互
Flamingo / LLaVA 走前者;Stable Diffusion / BLIP-2 走后者。
U-Net(2015)靠最低分辨率获得最大感受野;DDPM U-Net(2020)在 bottleneck 加 Self-Attention;DiT(2023)完全去掉 hourglass——VAE 提前压缩替代编码器,Transformer 的全局 Attention 天然不需要先压缩。
组件阅读路线 · Transformer 如何进入视觉领域
1. 历史脉络:视觉领域怎么走到 Transformer 的#
在 NLP 领域,Transformer 在 2017 年出现后迅速取代了 RNN/LSTM。但在 CV 领域,CNN 统治了将近十年(2012-2020),直到 ViT 出现才开始动摇。
**为什么 CV 领域 Transformer 来得这么晚?**因为 CNN 在图像上确实非常好用——它的两个归纳偏置(后面会详细讲)天然适合图像数据,不需要太多数据就能学得不错。而 Transformer 没有这些偏置,需要大量数据才能弥补,2020 年之前数据和算力都不够。
2012
AlexNet 在 ImageNet 上碾压传统方法,CNN 时代开始。此后 VGG、GoogLeNet、ResNet 等不断刷新记录。
2015
ResNet 引入残差连接,网络可以做到 152 层。CNN 架构基本成熟。
2017
Transformer 在 NLP 发表。CV 领域有人开始尝试把注意力加入 CNN(如 Non-local Neural Networks、SENet),但都是把注意力当 CNN 的辅助,不是替代。
2020
ViT(Vision Transformer)发表。核心贡献:证明了纯 Transformer 架构(不用任何卷积)可以在图像分类上达到 SOTA。Transformer 正式进入 CV。
2021-至今
ViT 的思想被迅速扩展:DeiT(数据高效训练)、Swin Transformer(层级结构+窗口注意力)、CLIP(图文对齐)、MAE(自监督预训练)、DiT(扩散模型 backbone)。
2. CNN 的归纳偏置(Inductive Bias)#
经典 CNN 架构图(LeNet-5 风格)
以 LeNet-5 为原型。空间分辨率(高度)逐层缩小,通道数(3D 深度)逐层增加;Flatten 后进入全连接分类头。
2.1 什么叫归纳偏置?#
归纳偏置就是模型在设计上预先假设的数据结构。还没看到任何数据之前,模型的结构本身就对"数据应该长什么样"有一些假设。这些假设如果和实际数据吻合,模型就能用更少的数据学得更好;如果不吻合,反而会限制模型。
打个比方:你去一个陌生城市找餐厅。如果有人告诉你"餐厅通常在商业街上"(归纳偏置),你就直奔商业街,很快找到。但如果这个城市的餐厅都藏在住宅区里,这个"偏置"反而让你找错方向。
2.2 CNN 的两个核心归纳偏置#
偏置一:局部性(Locality)#
CNN 假设:相邻的像素之间关系最密切。一个 3×3 的卷积核每次只看 3×3 = 9 个相邻像素,从中提取局部特征(边缘、纹理、角点等)。
**感受野(Receptive Field):**某一层的一个神经元能"看到"原始输入的区域大小。第一层的 3×3 卷积感受野是 3×3。两层叠加后是 5×5。三层是 7×7。要覆盖整张 224×224 的图,需要非常多层。这就是 CNN 处理长距离依赖的方式——逐层扩大感受野,但信息要经过很多层中转,类似于 RNN 的问题。
偏置二:平移等变性 / 平移不变性(Translation Equivariance / Invariance)#
平移等变性:同一个卷积核在图像的每个位置都用同一组参数。一个能检测"竖直边缘"的卷积核,不管这个边缘在图像左上角还是右下角,都能检测到。特征图会随着物体平移而平移——物体往右移 5 个像素,特征图也往右移 5 个像素。
平移不变性:配合池化(Pooling)层之后,模型对物体位置的微小变化不敏感。猫在图像正中间和偏左一点,最终分类结果应该一样。
💡 参数共享 = 数据效率高
一个 3×3×3 的卷积核只有 27 个参数,但它在整张 224×224 的图上滑动了约 50000 次。每次滑动都相当于用这 27 个参数处理了一个不同的输入。这就是 CNN 数据效率高的核心原因——参数少,但每个参数被复用了几万次。相比之下,全连接层对每个位置都有独立的参数,224×224×3 的输入接一个 1000 维的输出就需要 1.5 亿个参数。
2.3 归纳偏置的代价#
硬币的另一面:归纳偏置限制了模型的上限。
局部性假设让 CNN 天然不擅长长距离依赖。就算堆到 152 层(ResNet-152),远处像素之间的信息传递还是要经过很多层的中转,效率不高。
平移等变性假设让 CNN 对所有位置一视同仁。但有时候位置信息是重要的——比如在人脸识别中,眼睛在上面、嘴巴在下面,这个空间关系很关键。CNN 需要额外的手段(比如更深的网络、空间注意力模块)来学习位置相关的特征。
更根本的问题是:**当数据量足够大的时候,归纳偏置从"帮助"变成了"限制"。**数据少的时候,偏置帮模型"抄近路",快速学到合理的特征。数据多的时候,模型有足够的数据自己学出最优的特征提取方式,人为的偏置反而限制了它。这就是 ViT 在大数据下超过 CNN 的根本原因,后面会详细讲。
2.4 CNN 的重要变体:U-Net 与 Bottleneck#
普通 CNN(如 LeNet、ResNet)的目标是分类——把图像压缩成一个向量,输出"这是猫还是狗"。但图像分割、图像生成等任务需要像素级输出,输出图和输入图一样大。这催生了 U-Net。
U-Net = CNN Encoder + CNN Decoder + Skip Connection#
U-Net 的所有积木仍然是卷积层,只是架构上做了三件事:
① 编码器(下采样路径)#
和普通 CNN 一样,反复 Conv+Pool,空间分辨率不断缩小,通道数不断增加。感受野逐层扩大。
② 解码器(上采样路径)#
用转置卷积或双线性插值逐步恢复空间分辨率,最终输出与输入同尺寸的特征图。
整体形状像字母 U ——左侧编码器向下压缩,底部 Bottleneck 语义最丰富,右侧解码器向上恢复,中间灰色虚线是 Skip Connection。
U-Net 架构图
空间分辨率沿左侧向下递减(256²→16²),沿右侧向上恢复;Skip Connection 将编码器各层的精确位置细节拼接给对应的解码器层。
下采样路径与 Bottleneck#
CNN 的感受野随层数增加,但增长很慢(每层 +2 像素)。对 224×224 的图像,需要约 111 层 3×3 卷积才能让每个输出位置"看完"整张图——代价极高。池化解决了这个问题:每次 2×2 池化,空间减半但感受野覆盖翻倍。经过 5 次池化后,224×224 压缩到 7×7,此时每个位置的感受野已覆盖原图绝大部分区域。这是整条编码器(所有 Down Block)的累积效果,不是某一层单独完成的。
Bottleneck 是编码器最底部的那一层,操作上和 Down Block 几乎一样(Conv×2),唯一区别是它后面不再接池化,而是转向上采样——它只是 U 形结构的转折点,不是一种特殊模块。之所以叫"bottleneck",是因为信息在这里被挤到了最窄的空间维度(通道多、分辨率最小),就像瓶颈一样。
**到达 Bottleneck 时,特征图存储的是高层语义("这是一只猫,在左侧"),但丢失了精确的空间位置("猫耳朵在第 127 行第 43 列")。**这个矛盾正是 Skip Connection 要解决的。
Skip Connection:把细节"借回来"#
解码器上采样时,靠 Bottleneck 的语义知道"在哪里画什么",靠 Skip Connection 从编码器直接拿回未丢失的高分辨率细节,知道"边界精确在哪条线上"。两者拼接(concat)后,像素级的精确重建才成为可能。
💡 Skip Connection vs 残差连接
ResNet 的残差连接是同分辨率的 shortcut(同一层内 x + F(x)),目的是解决梯度消失。U-Net 的 Skip Connection 是跨分辨率的桥梁(把编码器第 k 层接到解码器对称位置),目的是恢复空间细节。两者形式相似但动机完全不同。
Bottleneck 的演化:从 CNN 到 Attention 再到 DiT#
| 时代 | 模型 | Bottleneck 的处理 | 原因 |
|---|---|---|---|
| CNN 时代 | U-Net(2015) | 纯卷积,和 Down Block 结构相同 | 仅靠位置特殊(最低分辨率)获得最大感受野 |
| 扩散模型时代 | DDPM U-Net(2020) | Bottleneck 处加 Self-Attention | 分辨率小(8×8),Attention 代价低,且能建立长距离依赖 |
| Transformer 时代 | DiT(2023) | 完全去掉 hourglass 结构 | VAE 提前压缩替代编码器;Transformer 全局 Attention 天然不需要先压缩才能看全局 |
**一句话总结:**U-Net 的 hourglass 结构(编码器反复下采样 + 解码器反复上采样)是 CNN 感受野有限这一先天缺陷的工程补丁。Bottleneck 本身只是这条路径的最低点——在原版 U-Net 中它和 Down Block 结构相同,到了 DDPM 才因为加入 Self-Attention 而变得功能独特。Transformer 的全局 Attention 从根本上消除了"先压缩才能看全局"的需求,DiT 因此可以抛弃整个 hourglass 结构。
3. ViT:把图像变成序列送进 Transformer#
3.1 核心问题:图像怎么变成序列?#
Transformer 接收的输入是一个 token 序列。NLP 中每个词就是一个 token,天然就是序列。但图像是一个二维的像素网格,不是序列。怎么办?
最暴力的方法:把每个像素当成一个 token。224×224 的图就是 50176 个 token。但 Self-Attention 的复杂度是 ,50176² ≈ 25 亿个 attention score(每个 score 还要做 head 维度上的点积),算不动。
ViT 的方法:Patch Embedding——不把每个像素当 token,而是把图像切成一块一块的 patch,每个 patch 当成一个 token。
3.2 Patch Embedding 的具体过程#
以一张 224×224×3 的 RGB 图像为例,patch 大小 :
第一步:切 patch#
把 224×224 的图像切成 大小的 patch:
每个 patch 是 个像素值。
相比把每个像素当 token(50176 个),patch 方式只有 196 个 token。 从 25 亿变成了 38416,减少了 6 万多倍。
第二步:线性投影#
每个 patch 被展平成一个 768 维的向量(),然后通过一个线性层投影到 维:
如果 ,投影矩阵是 。输入 768 维,输出 768 维(碰巧一样大)
实际实现中,这个"切 patch + 展平 + 线性投影"可以用一个卷积来等价实现:
# 等价于:切成 16×16 的 patch,然后线性投影到 768 维
self.patch_embed = nn.Conv2d(
in_channels=3, # RGB 三通道
out_channels=768, # 投影到 768 维
kernel_size=16, # 卷积核大小 = patch 大小
stride=16 # 步长 = patch 大小(不重叠)
)
# 输入: (B, 3, 224, 224)
# 输出: (B, 768, 14, 14) → reshape → (B, 196, 768)
**为什么可以用卷积等价?**一个 、stride=16 的卷积,刚好在每个 patch 的位置提取一次特征,不重叠。每个输出通道对应投影后的一个维度。数学上和"切 patch → 展平 → 线性投影"完全一样,但用卷积实现更快(GPU 对卷积有硬件优化)。
第三步:加上 [CLS] token + 位置编码#
ViT 在 196 个 patch token 前面加一个特殊的 [CLS] token(和 BERT 一样),最终序列变成 197 个 token。
[CLS] token 的作用:它不对应图像的任何区域,而是作为"整张图的代表"。经过多层 Transformer 之后,[CLS] token 通过注意力从所有 patch 那里聚合了全局信息。最终分类时只取 [CLS] token 的输出接一个分类头。
[CLS] 放在最前面还是最后面?#
放在最前面,位置 0:
[CLS], patch_1, patch_2, patch_3, ..., patch_196
0 1 2 3 196
其实放哪里都能工作——Self-Attention 是全局的,[CLS] 不管在哪个位置都能看到所有 patch。放在最前面是沿用 BERT 的惯例。实现通常给 [CLS] 单独保留一个可学习位置向量,只对 patch 的位置编码做分辨率插值。放在首位是约定,并没有由此推出“学习最稳定”的结论;放在末位也可以单独管理它的位置编码。
BERT 为什么要加 [CLS]?#
BERT 需要一个代表"整句话"的向量来做句子级任务(分类、句对关系等)。用哪个词的输出来代表整句话?用"我"的输出?它偏向"我"这个词的语义,不够中立。用所有词的平均?没有区分重要性。
所以加一个不对应任何真实词的 [CLS] token——它是一张"白纸",没有自己的语义偏向。经过多层 Self-Attention 后,它从所有真实 token 那里聚合信息,变成整句话的客观"摘要"。打个比方:开会时每个人(真实 token)都有自己的立场。你想要客观的会议纪要,与其让某个与会者写,不如派一个旁听者([CLS])——他没有立场,只负责听所有人发言然后总结。
**为什么不直接对所有 patch 取平均?**其实可以,后来的研究(DeiT 等)发现取平均(global average pooling)效果和 [CLS] 差不多。[CLS] 是 ViT 沿用 BERT 的设计,不是必须的。DiT 里也没有 [CLS] token——因为 DiT 不需要一个"代表整张图"的 token,它需要每个 patch 的输出来重建整张图。
然后给 197 个 token 加上 Learnable 位置编码(ViT 用的是可学习位置编码,不是 RoPE——因为 ViT 处理的是固定大小的图像,长度固定,Learnable 的缺点"长度不能变"在这里不是问题)。
3.3 完整流程总结#
输入图像 (B, 3, 224, 224)
→ 切成 16×16 的 patch,得到 196 个 patch
→ 每个 patch 展平为 768 维,线性投影到 d_model
→ 加上 [CLS] token,变成 197 个 token
→ 加上 Learnable 位置编码
→ 送入标准 Transformer Encoder(多层 Self-Attention + FFN)
→ 取 [CLS] token 的输出,接分类头
→ 输出分类结果
关键认知:ViT 的 Transformer 部分和 NLP 的 Transformer 完全一样——标准的 Multi-Head Self-Attention + FFN + LayerNorm + 残差连接。ViT 的创新不在 Transformer 本身,而在于怎么把图像变成 token 序列。一旦变成了序列,后面就是标准的 Transformer 流水线。
3.4 扩展到视频:3D Patch Embedding#
图像用 Conv2d 做 Patch Embedding,视频自然就用 Conv3d——多了一个时间维度:
# 图像:2D
nn.Conv2d(3, d_model, kernel_size=16, stride=16)
# 视频:3D
nn.Conv3d(3, d_model, kernel_size=(2, 16, 16), stride=(2, 16, 16))
# 在 T, H, W 三个方向上切 2×16×16 的 spacetime patch
为什么时间维度的步长通常比空间小?#
空间上 stride=16 没问题——相邻像素高度冗余,压缩一下信息损失不大。但时间上相邻帧之间的变化比相邻像素大得多——前一帧猫在左边,后一帧猫可能移到了右边。如果时间步长也设成 16(每 16 帧才取一个 token),运动信息就丢失严重了。所以常见做法是时间步长设得比空间小:
| 模型 | kernel | stride | 说明 |
|---|---|---|---|
| CogVideoX | (2, 2, 2) | (1, 2, 2) | 时间步长 1,不跳帧 |
| HunyuanVideo | (1, 2, 2) | (1, 2, 2) | 时间步长 1 |
VAE 压缩 vs Patch Embedding:两个阶段#
注意一个重要区别:ViT 的时代(2020)没有 VAE,Patch Embedding 是唯一的压缩步骤——直接把原始像素切 patch。
但到了 DiT / 视频生成的时代(2023+),压缩分成了两个阶段:
ViT 时代:
原始图像 (224×224) → Patch Embedding → 196 个 token → Transformer
DiT / 视频生成时代:
原始图像/视频 → VAE Encoder 压缩 → latent → Patch Embedding → token → Transformer
VAE 做第一次压缩(主要压缩):把像素空间压到隐空间。比如空间压缩 8 倍(512×512 → 64×64),时间压缩 4 倍(16 帧 → 4 帧)。
Patch Embedding 做第二次压缩(轻量格式转换):在 latent 上再切 patch,变成 Transformer 能吃的 token 序列。
所以 ViT 时代 Patch Embedding 承担了全部压缩。到了生成模型时代,VAE 承担了主要压缩任务(特别是时间维度),Patch Embedding 变成了相对轻量的格式转换步骤。
4. CNN vs ViT:为什么两者效果不同#
4.1 核心区别:有没有归纳偏置#
CNN 有两个强归纳偏置(局部性 + 平移等变性)。ViT 几乎没有——它只假设输入是一个序列,不假设相邻 token 更相关,也不假设同一个特征在不同位置长一样。
ViT 唯一保留的微弱偏置:切 patch 这个操作本身引入了一点点局部性——同一个 patch 内的像素被一起处理了。但 patch 之间的关系完全由注意力自由学习,没有"相邻 patch 更相关"的假设。
4.2 小数据量下:CNN 赢#
ViT 原论文有一个很重要的实验结果:在 ImageNet-1K(128 万张图)上从头训练,ViT 不如同等大小的 CNN(ResNet)。
**为什么?**因为没有归纳偏置的 ViT 需要从数据中自己学出"相邻像素更相关""边缘检测器在不同位置通用"这些规律。128 万张图听起来很多,但对 ViT 来说不够——它的参数空间太大,自由度太高,数据不够就容易过拟合。
而 CNN 不需要从数据中学这些规律,它的结构本身就包含了这些先验知识。所以在相同数据量下,CNN 学得更快更稳。
打个比方:CNN 像一个有经验的画家,拿起笔就知道"先画轮廓再填色"(归纳偏置)。ViT 像一个天才白纸,什么都不预设,从零开始摸索怎么画画。给他们同样少的时间(少量数据),有经验的画家肯定画得更好。
4.3 大数据量下:ViT 赢#
但 ViT 原论文还有另一个关键实验:当用 JFT-300M(3 亿张图)预训练后,ViT 大幅超过了 CNN。
为什么数据多了 ViT 就反超了?
**第一,归纳偏置从"帮助"变成了"限制"。**CNN 的局部性假设意味着它每一层只能看到局部区域。即使堆了很多层,远距离的信息交互仍然是间接的、低效的。有些全局关系(比如"图片左上角的太阳和右下角的影子")CNN 很难捕获。ViT 的每一层都是全局注意力,任意两个 patch 之间一步直达。当数据足够多时,ViT 能学到 CNN 结构上就不容易表达的全局特征。
**第二,ViT 的 Scaling 行为更好。**和 LLM 领域一样,Transformer 有很好的 Scaling Law——模型越大、数据越多、效果越好,而且提升是可预测的。CNN 也能通过加宽加深来增大模型,但效率不如 Transformer。同样增加一倍参数,ViT 的效果提升比 CNN 更大。这和 NLP 里 Decoder-only Transformer 比 LSTM scale 更好的逻辑是一样的。
**第三,ViT 的注意力模式是数据驱动的,不是人为设计的。**CNN 的特征提取方式是卷积核大小、步长、层数等超参数决定的——这些都是人设计的。ViT 的注意力模式完全由数据学出来——哪些 patch 之间要交互、怎么交互,模型自己从 3 亿张图中总结。当数据足够多时,数据驱动的方法能发现人想不到的模式。
一句话总结:CNN 靠先验知识(归纳偏置)在少数据时占优势。ViT 靠自由度和 scaling 能力在大数据时占优势。这不是谁设计得更巧妙的问题,而是"人为先验"和"数据驱动"两种哲学的根本差异。当数据不够时,先验知识更可靠;当数据足够时,让数据说话更强大。
4.4 具体对比表#
| 维度 | CNN | ViT |
|---|---|---|
| 归纳偏置 | 强(局部性 + 平移等变性) | 几乎没有(只有 patch 内的局部性) |
| 注意力范围 | 局部 → 逐层扩大感受野 | 全局,第一层就能看到所有 patch |
| 参数共享 | 卷积核在所有位置共享 | 注意力权重每个位置不同 |
| 小数据表现 | 好(先验帮忙) | 差(自由度太高,容易过拟合) |
| 大数据表现 | 可随数据和模型规模扩展,依赖架构与训练配方 | ViT 在大规模预训练中表现出良好扩展性 |
| 长距离依赖 | 差(需要堆很多层) | 好(一层就能全局交互) |
| 计算复杂度 | ,n 是像素数(卷积是线性的) | ,n 是 patch 数(注意力是二次的) |
| 代表模型 | ResNet, EfficientNet, ConvNeXt | ViT, DeiT, Swin Transformer |
5. ViT 为什么适合做大模型 / 基座模型#
从 JD 的角度看,"视觉基座""大规模预训练""统一表征"这些词反复出现。为什么大家选 ViT 做基座,不选 CNN?
5.1 原因一:Scaling Law#
和 LLM 一样,ViT 有非常好的 scaling 行为。Google 的实验(ViT 原论文 + 后续 Scaling Vision Transformers 论文)表明:
- ViT 从 ViT-Small(22M 参数)到 ViT-Huge(632M 参数),在 JFT-300M 上的性能持续稳定提升,没有饱和迹象。
- 同等参数量下,ViT 的性能提升比 CNN 更大。加同样多的参数,ViT 多学到的东西比 CNN 多。
这意味着:想要更好的效果?加参数加数据就行,而且效果提升是可预测的。这对工业界非常重要——你能预估投入多少算力能得到多少收益。
5.2 原因二:架构统一性#
ViT 用的就是标准 Transformer——和 GPT、BERT 一模一样的架构。这意味着:
- 工程复用:NLP 领域几年积累的 Transformer 优化技术(Flash Attention、混合精度训练、分布式策略、KV Cache 等)可以直接搬过来用。不需要为视觉模型单独开发一套优化。
- 多模态扩展:图像是 patch token,文本是 word token,都是 token 序列。可以很自然地拼在一起送进同一个 Transformer 处理。这就是 CLIP、LLaVA 等多模态模型的基础。
5.3 原因三:自监督预训练#
CNN 时代的预训练主要靠有标签的 ImageNet(分类任务)。但标注数据贵且有限。
ViT 的序列化表示天然适合自监督预训练——**MAE(Masked Autoencoder)**就是最好的例子:随机遮住 75% 的 patch,让模型预测被遮住的 patch。和 BERT 的 MLM 思路一模一样,只是从"遮词"变成了"遮图块"。这让 ViT 可以利用几乎无限的无标注图像数据做预训练。
CNN 也可以做自监督(如 SimCLR、MoCo),但这些方法依赖精心设计的数据增强策略。MAE 更简洁——遮住一部分让模型补全,不需要设计增强。
6. 为什么 Transformer 能统一多模态#
这是面试中很可能被问到的"大问题"。答案在我们第一天就讲过,现在从 ViT 的角度再理解一遍。
6.1 核心原因:Transformer 不对输入数据做任何结构假设#
RNN 假设数据有时序性——前一步的输出传给后一步。CNN 假设数据有空间局部性——相邻位置更相关。这些假设把它们"绑"在了特定的数据类型上。
Transformer 只假设输入是一个 token 序列。它不关心 token 从哪来、代表什么——只要是一个向量序列就行。这让它可以处理任何模态:
| 模态 | 怎么变成 token 序列 | 代表模型 |
|---|---|---|
| 文本 | 每个词/subword 是一个 token | GPT, BERT, LLaMA |
| 图像 | 切成 patch,每个 patch 是一个 token | ViT, CLIP (视觉侧) |
| 视频 | 切成 spacetime patch,每个是一个 token | Sora, CogVideoX |
| 音频 | 分帧,每帧是一个 token | Whisper, AudioLM |
| 3D 点云 | 分组,每组点是一个 token | Point-BERT |
**只要能变成序列,Transformer 就能处理。**不同模态之间的差异被"tokenization"这一步吸收了。Transformer 看到的都是 的矩阵—— 个 token,每个 维。它不知道也不关心这些 token 原来是像素还是文字还是音频。
6.2 为什么 CNN 做不到统一?#
CNN 的卷积操作假设了数据的空间维度——2D 卷积用于图像、1D 卷积用于序列、3D 卷积用于视频。不同维度的卷积是不同的操作,参数不能共享,架构不能复用。你不能把一个图像 CNN 直接拿来处理文本。
Transformer 没有这个问题——Self-Attention 对 矩阵操作,不关心 是什么。(ViT 的 patch 数)和 (文本的 token 数)对 Transformer 来说没有本质区别。
6.3 多模态融合的两种方式#
有了统一的 token 化表示,多模态融合就变得自然了:
方式一:拼接(Early Fusion)#
把不同模态的 token 拼成一个长序列,送进同一个 Transformer:
图像 token: [patch_1, patch_2, ..., patch_196]
文本 token: [word_1, word_2, ..., word_50]
拼接后: [patch_1, ..., patch_196, word_1, ..., word_50]
送进同一个 Transformer → Self-Attention 让图像 token 和文本 token 互相交互
图像 token 可以"看到"文本 token,文本 token 也可以"看到"图像 token。交互是自动的,由注意力权重决定。
方式二:Cross-Attention(Late Fusion)#
两个模态各自有自己的编码器,通过 Cross-Attention 交互:
图像 → 视觉编码器(ViT)→ 图像特征
文本 → 文本编码器(CLIP text encoder)→ 文本特征
Cross-Attention: Q 来自图像特征,K/V 来自文本特征
→ 图像特征"查询"文本信息
这就是 Stable Diffusion 里文本控制图像生成的方式。
两种方式的区别#
| 方式 | 交互时机 | 交互深度 | 代表模型 |
|---|---|---|---|
| 拼接(Early Fusion) | 最早,两个模态的 token 直接混在一起 | 深(每一层都交互) | Flamingo, LLaVA (某种程度上) |
| Cross-Attention(Late Fusion) | 较晚,各自编码后再交互 | 浅(只在特定层交互) | Stable Diffusion, BLIP-2 |
7. ViT 之后的演进:关键变体#
ViT 证明了"纯 Transformer 能做视觉",但它也有一些问题。后续工作在不断改进。面试时不需要深入每个变体的细节,但要知道它们在解决什么问题。
7.1 DeiT(2021,Facebook)—— 数据效率#
解决的问题:ViT 需要 JFT-300M(3 亿张)才能训练好,普通实验室没有这么多数据。
核心思路:不改架构,改训练方法——三板斧:基于 token 的知识蒸馏 + 更强的数据增强 + 更好的正则化。结果:只用 ImageNet-1K(128 万张)、8 张 GPU 训练 3 天,就能训练出比肩 ViT-JFT 的效果。
技术一:基于 Distillation Token 的知识蒸馏(最核心创新)#
传统知识蒸馏是让学生模型的输出 logits 去对齐教师模型的软标签(KL 散度)。DeiT 做了一个专门针对 ViT 的改进——引入第三种 token:
原始 ViT: [CLS], patch_1, patch_2, ..., patch_196
DeiT: [CLS], [DIST], patch_1, patch_2, ..., patch_196
↑ ↑
分类头 蒸馏头(对齐教师输出)
[CLS]token:和真实标签(ImageNet 类别)对齐,学"正确答案"[DIST]token:和教师模型的输出分布对齐,学"教师怎么看这张图"
训练时两个损失同时优化:
推理时:将 [CLS] 和 [DIST] 两个 token 的 logits 取平均作为最终分类结果。
⚠️ 关键发现:CNN 当教师比 Transformer 当教师效果更好
DeiT 实验发现用 CNN(RegNet / EfficientNet)当教师,比用另一个 Transformer 当教师效果更好。原因:CNN 带有局部性和平移等变性的归纳偏置,这些偏置在 ImageNet-1K 这样的中等规模数据集上是有效的先验。用 CNN 当教师,相当于把 CNN 的归纳偏置"软性地"传递给了 ViT,弥补了 ViT 自身归纳偏置不足的短板。
💡 追问:经过 CNN 蒸馏,ViT 会变成"像 CNN 一样的模型"吗?
不会。[DIST] token 学到的是 CNN 的输出分布,而不是 CNN 的内部表示结构。ViT 内部依然是全局注意力,只是学到了"在 ImageNet 上 CNN 认为重要的视觉模式"。在需要全局理解的任务上,DeiT 依然保持 ViT 的优势。
技术二:更强的数据增强#
没有 JFT-300M,就用数据增强来"人工扩充"数据多样性。DeiT 把当时最强的增强全部叠加:
| 增强方法 | 做什么 | 为什么有效 |
|---|---|---|
| RandAugment | 随机选 2 种变换(旋转、颜色抖动等) | 让模型见到更多样的图像 |
| Mixup | 两张图按比例 λ 混合,标签也混合 | 防止模型过于自信,正则化效果强 |
| CutMix | 把一张图的矩形区域替换为另一张图的对应区域 | 比 Mixup 更强,迫使模型关注局部区域 |
| Random Erasing | 随机遮住图像的一个矩形区域(随机噪声填充) | 模拟遮挡,增强鲁棒性 |
| Repeated Augmentation | 同一张图在一个 mini-batch 里出现多次(每次增强不同) | 让批次内的样本多样性更高 |
**为什么 ViT 比 CNN 更依赖数据增强?**CNN 的卷积核参数共享,本身就限制了模型容量,不容易过拟合。ViT 的自注意力每个位置独立计算权重,参数多、自由度高,在小数据集上极易过拟合。强数据增强相当于给 ViT 做了隐式正则化。
技术三:更好的正则化#
- Stochastic Depth(随机深度 / Drop Path):训练时随机跳过某些 Transformer 层——将该层的残差分支直接短路,输出等于输入。类似 Dropout,但作用在整层级别,防止模型过度依赖某几层。
- Label Smoothing(标签平滑):把 one-hot 标签变成软标签(ε = 0.1),防止模型对答案过于自信。
效果对比#
| 模型 | 预训练数据 | ImageNet Top-1 | 成本 |
|---|---|---|---|
| ViT-L(原论文) | JFT-300M(3 亿张) | 87.8% | 极高(Google 内部) |
| ResNet-50 | ImageNet-1K | 76.5% | 低 |
| DeiT-B | ImageNet-1K 仅此 | 81.8% | 8 张 GPU,3 天 |
| DeiT-B(蒸馏版) | ImageNet-1K 仅此 | 83.4% | 8 张 GPU,3 天 |
意义:证明了 ViT 的数据效率问题可以通过训练技巧解决,不一定需要改架构。DeiT 让 ViT 走出了 Google 的实验室,成为普通研究者可以使用的视觉基座。
一句话总结 DeiT 的哲学:ViT 缺少归纳偏置 → 数据少时学习效率低 → 用 CNN 当老师传授先验知识(蒸馏),用强数据增强和正则化弥补数据量不足。本质是用训练技巧模拟出更多的数据和先验。
7.2 Swin Transformer(2021,Microsoft)—— 层级结构 + 效率#
解决的问题:ViT 的全局注意力复杂度是 ,处理高分辨率图像(如 1024×1024)时 patch 数量巨大,算不动。而且 ViT 只输出单一分辨率的特征,不适合需要多尺度特征的任务(检测、分割)。
窗口注意力(Window Attention)#
用一个具体例子。假设输入是 8×8 = 64 个 patch,窗口大小 4×4:
标准 ViT:每个 patch 和所有 64 个 patch 做注意力。计算量 。
Swin:把 64 个 patch 分成 4 个窗口,每个窗口 4×4 = 16 个 patch,窗口内部单独做注意力:
原始 8×8 的 patch 网格:
┌───────────┬───────────┐
│ │ │
│ 窗口 A │ 窗口 B │
│ 4×4=16 │ 4×4=16 │
│ │ │
├───────────┼───────────┤
│ │ │
│ 窗口 C │ 窗口 D │
│ 4×4=16 │ 4×4=16 │
│ │ │
└───────────┴───────────┘
窗口 A 内 16 个 patch 互相做注意力,计算量 。4 个窗口总计 。比全局注意力的 4096 少了 4 倍。
问题:窗口 A 右边缘的 patch 和窗口 B 左边缘的 patch 明明紧挨着,但在不同窗口里互相看不到。信息被窗口边界切断了。
Shifted Window(移动窗口)#
下一层把窗口往右下移动半个窗口大小(2 个 patch):
第 1 层的窗口划分: 第 2 层的窗口划分(移动了 2 格):
┌───────┬───────┐ ┌──┬────────┬──┐
│ │ │ │ │ │ │
│ A │ B │ │ │ E │ │
│ │ │ │ │ │ │
├───────┼───────┤ ├──┼────────┼──┤
│ │ │ │ │ │ │
│ C │ D │ │ │ F │ │
│ │ │ │ │ │ │
└───────┴───────┘ └──┴────────┴──┘
第 1 层里 A 和 B 边界处的 patch,在第 2 层被划进了同一个窗口 E 里。信息就通了——第 1 层在窗口内交流,第 2 层通过错开窗口让跨窗口的 patch 交流。两层交替,信息逐渐传遍全图。
本质:一层看一种"井"字格划分,下一层看错开的"井"字格划分,两层配合覆盖所有相邻 patch 之间的交互。
实现细节:循环移位(Cyclic Shift)#
移动窗口之后,图像边缘会出现不足一个窗口大小的"零碎区域"。Swin 的做法是循环移位:把溢出边缘的 patch 从对面绕回来补满,保证每个窗口始终是完整的固定大小。对于那些绕回来的 patch(它们在原图中并不相邻),在计算注意力时用 Attention Mask 屏蔽掉,让它们互相看不见——目的是高效实现同一个 shifted-window 分区;等价性要求窗口成员、mask 和位置偏置都匹配,不能笼统说与任意补零方案相同。
💡 为什么选循环移位而不是直接补零?
朴素移动分区会增加边界小窗口的数量。循环移位配合区域 mask,可维持规则窗口批次;输入尺寸不整除窗口大小时,仍可能需要 padding。torch.roll 只完成移位,不能替代区域 mask、padding mask 和反向移位。
层级结构(Hierarchical Structure)#
ViT 从头到尾都是 196 个 token,分辨率不变。Swin 像 CNN 一样逐阶段降低分辨率:
Stage 1:patch 大小 4×4
输入 224×224 → 56×56 = 3136 个 token,通道数 96
做几层窗口注意力
↓ Patch Merging(把相邻 2×2 的 patch 合并成一个)
Stage 2:分辨率减半
56×56 → 28×28 = 784 个 token,通道数 192
做几层窗口注意力
↓ Patch Merging
Stage 3:
28×28 → 14×14 = 196 个 token,通道数 384
做几层窗口注意力
↓ Patch Merging
Stage 4:
14×14 → 7×7 = 49 个 token,通道数 768
做几层窗口注意力
Patch Merging 怎么做:把相邻 2×2 的 4 个 patch 的特征向量拼起来(通道数变成 4 倍),然后过一个线性层降回 2 倍通道。效果等价于 CNN 里 stride=2 的卷积——空间分辨率减半,通道数翻倍。
为什么要层级结构:目标检测和语义分割需要多尺度特征——大物体用低分辨率特征检测,小物体用高分辨率特征检测。ViT 只有一种分辨率的输出,做不了这事。Swin 的四个 Stage 输出四种分辨率的特征,可以直接接 FPN(Feature Pyramid Network)做检测和分割。
意义:把 CNN 的一些好的设计思想(多尺度、局部窗口)引回 Transformer,在效率和效果之间取得更好的平衡。Swin Transformer 在检测和分割任务上全面超过 CNN。
💡 有趣的趋势:CNN 和 Transformer 在互相学习
Swin Transformer 从 CNN 那里借了层级结构和局部窗口。反过来,ConvNeXt(2022,Facebook)把 Transformer 的设计思想(更大的卷积核、LayerNorm、更少的激活函数)搬回 CNN,结果纯 CNN 架构也能达到 Swin 的水平。这说明 CNN 和 Transformer 的差距不只是架构差异,更多是训练策略和设计细节的差异。
7.3 MAE(2022,Facebook)—— 自监督预训练#
解决的问题:ViT 需要大量有标签数据预训练。能不能用无标签数据?
MAE 和 ViT 的关系#
ViT 是模型架构,MAE 是训练方法。MAE 的目的是让 ViT 在没有标签的数据上也能学到好的视觉特征。MAE 的名字里虽然有 Autoencoder,但它不是 VAE 那种学隐空间分布的生成模型。叫 Autoencoder 只是因为"输入一张图,输出还是这张图"(自编码),仅此而已。
MAE 的具体流程#
原始图像 196 个 patch
→ 随机遮住 75%,只留 49 个 patch
→ 49 个 patch 送入 ViT Encoder(标准 ViT,这就是我们要训练的模型)
→ Encoder 输出 49 个 token
→ 补上 147 个 mask token(可学习的占位符),拼成 196 个 token
→ 送入轻量 Decoder(一个小 Transformer)
→ 预测被遮住的 147 个 patch 的像素值
→ 和原始像素算 MSE loss
训练完之后:Decoder 扔掉不要,只保留 Encoder(就是那个 ViT)。这个 ViT 通过"补全图像"的任务学到了好的视觉特征表示。然后拿它去做下游任务(分类、检测等)。
一个关键的工程创新:被遮住的 patch 不送进 Encoder#
BERT 的 MLM 中,被遮住的词还是要送进模型(用 [MASK] token 替代),计算量不变。但 MAE 被遮住的 75% patch 根本不送进 Encoder——Encoder 只处理 25% 的 patch。这让训练速度提升了 3-4 倍。只有在 Decoder 阶段才把 mask token 补上。
MAE 和 BERT MLM 的对比#
| 维度 | BERT MLM(2018,NLP) | MAE(2022,CV) |
|---|---|---|
| 遮蔽比例 | 15% | 75%(图像冗余度高,遮少了太简单) |
| 预测目标 | 离散的词(从词表选一个,分类问题) | 连续的像素值(回归问题) |
| 被遮住的部分 | 用 [MASK] 替代,仍然送入模型 | 直接不送进 Encoder,大幅省计算 |
| 哲学 | 遮住一部分,让模型补全 | 同样的哲学,但针对图像特点做了大量调整 |
**为什么遮 75% 这么多?**文本遮掉 15% 就已经很难了——"我 __ 猫"需要理解语义才能填。但图像遮掉 15% 太简单了——旁边的 patch 几乎一样,直接插值就能补全,模型学不到有意义的东西。必须遮到 75% 才够难,迫使模型理解图像的语义结构而不只是做局部插值。
意义:让 ViT 可以在海量无标注图像上预训练,不再依赖有标注的 ImageNet。这是 ViT 成为视觉基座模型的关键一步。
8. ViT 和你的方向(DiT / 视频生成)的关系#
回到我们在 Day 0 就理清的认知:ViT 不是做生成的,它是做视觉理解的(分类、检测等)。但它的核心贡献——证明 Transformer 可以处理图像——为后来的 DiT 提供了思想基础。
8.1 从 ViT 到 DiT 的逻辑链#
2020 ViT
证明 Transformer 可以处理图像(切 patch → token 序列 → Self-Attention)
2022 Stable Diffusion
扩散模型用 U-Net 做 backbone,在隐空间生成图像。效果很好,但 U-Net 不好 scale。
2023 DiT
自然的想法:既然 ViT 证明了 Transformer 能处理图像 patch,那扩散模型的 backbone 也可以从 U-Net 换成 Transformer。DiT 就是这么做的——把 latent 切成 patch,当成 token,用 Transformer 做去噪。
8.2 ViT 和 DiT 的具体区别#
| 维度 | ViT | DiT |
|---|---|---|
| 任务 | 视觉理解(分类) | 视觉生成(扩散模型去噪) |
| 输入 | 原始图像的 patch | VAE 隐空间的 latent patch |
| 注意力类型 | 双向 Self-Attention | 双向 Self-Attention(不是因果的) |
| 条件注入 | 无(或者只有 [CLS] token) | adaLN-Zero(通过 LayerNorm 参数注入时间步和条件) |
| 输出 | [CLS] token → 分类概率 | 所有 patch token → 预测的噪声 |
| 位置编码 | Learnable | adaLN-Zero 隐式包含(或 Sinusoidal) |
**最重要的区别是条件注入方式。**ViT 做分类不需要外部条件。DiT 做扩散需要条件——至少需要时间步 (告诉模型"现在噪声多大"),还可能需要类别标签或文本。DiT 试了几种条件注入方式(Cross-Attention、adaLN、adaLN-Zero),发现 adaLN-Zero 效果最好。这个细节 Day 6 会详细讲。
9. 理解检查#
面试题 01 CNN 的归纳偏置是什么?
结论:两个——局部性(卷积核只看局部区域)和平移等变性(同一个卷积核在所有位置共享参数)。
为什么好:图像中相邻像素确实更相关(局部性成立),同一个特征确实在不同位置长一样(平移等变性成立)。这些偏置让 CNN 用很少的参数和数据就能学好。
为什么也有限制:局部性让 CNN 不擅长长距离依赖,平移等变性让 CNN 对位置信息不敏感。而且当数据足够多时,归纳偏置从帮助变成限制——限制了模型的表达上限。
面试题 02 ViT 怎么把图像变成 token?
结论:把图像切成固定大小的 patch(如 16×16),每个 patch 展平后线性投影到 维,变成一个 token。224×224 的图切成 196 个 token。
实现细节:可以用一个 kernel=16, stride=16 的卷积等价实现,更快。然后加上 [CLS] token 和 Learnable 位置编码,送入标准 Transformer Encoder。
面试题 03 CNN 和 ViT 的核心区别?
归纳偏置:CNN 有强归纳偏置(局部性+平移等变性),ViT 几乎没有。
注意力范围:CNN 是局部的(每层只看卷积核大小的区域),ViT 是全局的(每层看所有 patch)。
数据效率:CNN 小数据好(先验帮忙),ViT 大数据好(没有先验限制,scaling 更好)。
面试题 04 为什么 ViT 在大数据下更强?
三个原因:(1) 归纳偏置在大数据下从帮助变成限制,CNN 的表达上限比 ViT 低;(2) Transformer 有更好的 Scaling Law,加参数的回报更大;(3) ViT 的特征提取方式完全数据驱动,能发现人为设计(CNN)想不到的模式。
类比:CNN 像有经验的画家,数据少时优势明显。ViT 像天才白纸,数据少时不如有经验的人,但数据多了之后,没有先入为主的限制,反而能画出更好的作品。
面试题 05 为什么 Transformer 能统一多模态?
核心原因:Transformer 不对输入数据做任何结构假设,只要求输入是 token 序列。文本天然是序列,图像切成 patch 是序列,视频切成 spacetime patch 还是序列。不同模态的差异被 tokenization 这一步吸收了,Transformer 看到的都是统一的 矩阵。
对比 CNN:CNN 的卷积假设了数据的空间维度(2D 图像、1D 序列、3D 视频是不同的卷积),不同模态不能复用架构。
面试题 06 ViT 和 DiT 的关系?
**ViT 是 DiT 的思想前提,不是技术前身。**ViT 证明了 Transformer 能处理图像 patch,DiT 把这个思想用在了扩散模型上——latent 切成 patch 当 token,用 Transformer 替代 U-Net 做去噪 backbone。两者的区别:ViT 做理解(分类),DiT 做生成(去噪);ViT 不需要条件注入,DiT 通过 adaLN-Zero 注入时间步和条件。
面试题 07 Swin Transformer 解决了 ViT 的什么问题?
两个问题:(1) ViT 全局注意力 在高分辨率图像上算不动,Swin 用窗口注意力变成 ;(2) ViT 输出单一分辨率特征,不适合检测/分割,Swin 用层级结构输出多尺度特征。
窗口注意力的问题:窗口边界切断了相邻 patch 之间的信息。解决方法:Shifted Window——相邻层的窗口错开半个窗口大小,让跨窗口的 patch 在下一层可以交互。两层交替就能覆盖所有相邻 patch 之间的通信。
层级结构:4 个 Stage,每个 Stage 之间做 Patch Merging(空间分辨率减半、通道数翻倍),输出多尺度特征,可以直接接 FPN 做检测和分割。
面试题 08 MAE 是什么?和 ViT 的关系?
**MAE 是训练方法,ViT 是模型架构。**MAE 用"遮住 75% 的 patch 让模型补全"这个自监督任务来训练 ViT,让 ViT 不需要有标签数据就能学到好的视觉特征。
不是 VAE 那种 Autoencoder:叫 Autoencoder 只是因为输入输出是同一张图(自编码),不学隐空间分布,不做生成。训练完后 Decoder 扔掉,只保留 Encoder(ViT)。
和 BERT MLM 的关系:同一个哲学——"遮住一部分让模型补全"。但遮蔽比例不同(BERT 15% vs MAE 75%,图像更冗余需要遮更多)、预测目标不同(BERT 预测离散词 vs MAE 预测连续像素)、效率不同(BERT 被遮的词还是送进模型,MAE 被遮的 patch 不送进 Encoder,省 75% 计算)。
面试题 09 [CLS] token 是什么?为什么需要?
来源:BERT 引入的设计,ViT 沿用。一个不对应任何真实数据的特殊 token,放在序列最前面(位置 0)。
作用:作为"白纸",经过多层注意力后从所有真实 token 聚合全局信息,最终作为整个输入的"摘要"用于分类等任务。
是否必须:不是。后来研究发现 global average pooling(对所有 token 取平均)效果差不多。DiT 里就没有 [CLS]——因为生成任务需要每个 patch 的输出,不需要一个全局代表。
CNN → ViT → 视觉 Transformer
多模态算法面试准备系列
原始资料:ViT、Swin Transformer。
延伸阅读#
- DiT 架构与条件调制:Transformer 怎样成为扩散模型中的去噪网络?