模型架构 / 整体架构 原理笔记入门 27 分钟阅读 查看源文件 ↗

CNN 与 ViT:图像表示与架构比较#

本页内容10 个部分
查看概览图
FOUNDATIONS · 02

把归纳偏置换成数据,
把图像换成序列

CNN 用局部性和平移等变性省数据;ViT 放弃这些先验,换来 scaling 上限和跨模态的统一接口。

IMAGE H × W × 3 像素网格
PATCHIFY 16 × 16 切块 + 线性投影
SEQUENCE N tokens + 位置编码
ENCODER Transformer 第一层就全局交互
ViT 唯一保留的归纳偏置只剩 patch 内的局部性
维度CNNViT
归纳偏置强(局部性 + 平移等变性)几乎没有(只有 patch 内的局部性)
注意力范围局部 → 逐层扩大感受野全局,第一层就能看到所有 patch
参数共享卷积核在所有位置共享注意力权重每个位置不同
小数据表现好(先验帮忙)差(自由度太高,容易过拟合)
大数据表现有上限(先验限制)更强(没有先验限制,scaling 好)
长距离依赖差(需要堆很多层)好(一层就能全局交互)
计算复杂度O(n),n 是像素数O(n²),n 是 patch 数
代表模型ResNet · EfficientNet · ConvNeXtViT · DeiT · Swin Transformer
◫统一多模态的机制

只要能变成 token 序列,就能进同一个 Transformer

图像切成 patch
  • 文本 → 每个词 / subword 一个 tokenGPT · BERT · LLaMA
  • 图像 → 切 patchViT · CLIP(视觉侧)
  • 视频 → spacetime patchSora · CogVideoX
  • 音频 → 分帧Whisper · AudioLM
  • 3D 点云 → 分组Point-BERT
同一套骨架 token 化
是唯一前提
⇄两种融合方式

交互时机决定交互深度

  • 拼接(Early Fusion)两个模态的 token 直接混在一起 · 每一层都交互
    深
  • Cross-Attention(Late Fusion)各自编码后再交互 · 只在特定层交互
    浅

Flamingo / LLaVA 走前者;Stable Diffusion / BLIP-2 走后者。

BOTTLENECK 的演进
从「必须先压缩才能看全局」到「压根不需要压缩」

U-Net(2015)靠最低分辨率获得最大感受野;DDPM U-Net(2020)在 bottleneck 加 Self-Attention;DiT(2023)完全去掉 hourglass——VAE 提前压缩替代编码器,Transformer 的全局 Attention 天然不需要先压缩。

patch = 视觉的 token小数据吃亏,大数据翻盘同一骨架吃下五种模态

组件阅读路线 · 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 风格)

2_CNN_ViT 图1

以 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 架构图

2_CNN_ViT 图2

空间分辨率沿左侧向下递减(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 的复杂度是 O(n2)O(n^2),50176² ≈ 25 亿个 attention score(每个 score 还要做 head 维度上的点积),算不动。

ViT 的方法:Patch Embedding——不把每个像素当 token,而是把图像切成一块一块的 patch,每个 patch 当成一个 token。

3.2 Patch Embedding 的具体过程#

以一张 224×224×3 的 RGB 图像为例,patch 大小 P=16P = 16:

第一步:切 patch#

把 224×224 的图像切成 16×1616 \times 16 大小的 patch:

22416=14 行×14 列=196 个 patch\frac{224}{16} = 14 \text{ 行} \times 14 \text{ 列} = 196 \text{ 个 patch}

每个 patch 是 16×16×3=76816 \times 16 \times 3 = 768 个像素值。

相比把每个像素当 token(50176 个),patch 方式只有 196 个 token。O(n2)O(n^2) 从 25 亿变成了 38416,减少了 6 万多倍。

第二步:线性投影#

每个 patch 被展平成一个 768 维的向量(16×16×3=76816 \times 16 \times 3 = 768),然后通过一个线性层投影到 dmodeld_{\text{model}} 维:

patch_embed=Linear(768,dmodel)\text{patch\_embed} = \text{Linear}(768, d_{\text{model}})

如果 dmodel=768d_{\text{model}} = 768,投影矩阵是 768×768768 \times 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)

**为什么可以用卷积等价?**一个 16×1616 \times 16、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 在大规模预训练中表现出良好扩展性
长距离依赖 差(需要堆很多层) 好(一层就能全局交互)
计算复杂度 O(n)O(n),n 是像素数(卷积是线性的) O(n2)O(n^2),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 看到的都是 (n,d)(n, d) 的矩阵——nn 个 token,每个 dd 维。它不知道也不关心这些 token 原来是像素还是文字还是音频。

6.2 为什么 CNN 做不到统一?#

CNN 的卷积操作假设了数据的空间维度——2D 卷积用于图像、1D 卷积用于序列、3D 卷积用于视频。不同维度的卷积是不同的操作,参数不能共享,架构不能复用。你不能把一个图像 CNN 直接拿来处理文本。

Transformer 没有这个问题——Self-Attention 对 (n,d)(n, d) 矩阵操作,不关心 nn 是什么。n=196n = 196(ViT 的 patch 数)和 n=512n = 512(文本的 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:和教师模型的输出分布对齐,学"教师怎么看这张图"

训练时两个损失同时优化:

L=0.5×CrossEntropy([CLS], ytrue)+0.5×KL([DIST], pteacher)\mathcal{L} = 0.5 \times \text{CrossEntropy}(\text{[CLS]},\ y_{\text{true}}) + 0.5 \times \text{KL}(\text{[DIST]},\ p_{\text{teacher}})

推理时:将 [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 的全局注意力复杂度是 O(n2)O(n^2),处理高分辨率图像(如 1024×1024)时 patch 数量巨大,算不动。而且 ViT 只输出单一分辨率的特征,不适合需要多尺度特征的任务(检测、分割)。

窗口注意力(Window Attention)#

用一个具体例子。假设输入是 8×8 = 64 个 patch,窗口大小 4×4:

标准 ViT:每个 patch 和所有 64 个 patch 做注意力。计算量 642=409664^2 = 4096。

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 互相做注意力,计算量 162=25616^2 = 256。4 个窗口总计 4×256=10244 \times 256 = 1024。比全局注意力的 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 做扩散需要条件——至少需要时间步 tt(告诉模型"现在噪声多大"),还可能需要类别标签或文本。DiT 试了几种条件注入方式(Cross-Attention、adaLN、adaLN-Zero),发现 adaLN-Zero 效果最好。这个细节 Day 6 会详细讲。

9. 理解检查#

面试题 01 CNN 的归纳偏置是什么?

结论:两个——局部性(卷积核只看局部区域)和平移等变性(同一个卷积核在所有位置共享参数)。

为什么好:图像中相邻像素确实更相关(局部性成立),同一个特征确实在不同位置长一样(平移等变性成立)。这些偏置让 CNN 用很少的参数和数据就能学好。

为什么也有限制:局部性让 CNN 不擅长长距离依赖,平移等变性让 CNN 对位置信息不敏感。而且当数据足够多时,归纳偏置从帮助变成限制——限制了模型的表达上限。

面试题 02 ViT 怎么把图像变成 token?

结论:把图像切成固定大小的 patch(如 16×16),每个 patch 展平后线性投影到 dmodeld_{\text{model}} 维,变成一个 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 看到的都是统一的 (n,d)(n, d) 矩阵。

对比 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 全局注意力 O(n2)O(n^2) 在高分辨率图像上算不动,Swin 用窗口注意力变成 O(n)O(n);(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。

延伸阅读#

相关阅读路线

理解 Transformer 与架构变体 ↗