视觉 Transformer(ViT)


文档摘要

视觉 Transformer(ViT) 本节摘要:一张图是 patch 的网格,一句话是 token 的网格——同一个 Transformer 两者通吃。2020 年之前,计算机视觉等于卷积,ImageNet、COCO、检测基准的每个 SOTA 都用 CNN 骨干,Transformer 是给语言用的。Dosovitskiy 等(2020)的《An Image is Worth 16x16 Words》证明可以彻底丢掉卷积:把图切成固定大小的 patch,每个 patch 线性投影成嵌入,把这条序列喂进一个原版 Transformer 编码器。规模够大(ImageNet-21k 预训练或更大)时,ViT 追平甚至击败 ResNet。

视觉 Transformer(ViT)

本节摘要:一张图是 patch 的网格,一句话是 token 的网格——同一个 Transformer 两者通吃。2020 年之前,计算机视觉等于卷积,ImageNet、COCO、检测基准的每个 SOTA 都用 CNN 骨干,Transformer 是给语言用的。Dosovitskiy 等(2020)的《An Image is Worth 16x16 Words》证明可以彻底丢掉卷积:把图切成固定大小的 patch,每个 patch 线性投影成嵌入,把这条序列喂进一个原版 Transformer 编码器。规模够大(ImageNet-21k 预训练或更大)时,ViT 追平甚至击败 ResNet。这是 2026 年更大模式的起点:一种架构,多种模态——Whisper 把音频切成 token,ViT 把图像切成 patch,机器人用 action token,视频用 pixel token。Transformer 不在乎,喂它一条序列它就学。到 2026 年,ViT 及其后裔(DeiT、Swin、DINOv2、ViT-22B、SAM 3)占据大半视觉;CNN 只在边缘设备和延迟敏感任务上仍占优。本节将带你纯标准库实现 patchify + 线性嵌入,看清「图像→token」只需两步,然后讲清为什么 ViT 比 CNN 更吃数据(它丢掉了平移不变性和局部性这两个归纳偏置)。

学习目标

阅读完本节,你应当能够:

  1. 说清 ViT 的五步流水线:patchify、线性嵌入、加 [CLS] 与位置编码、标准 Transformer 编码器、分类头。
  2. 纯标准库实现 patchify:把 H×W×C 图切成 N×(P·P·C) 的扁平 patch 序列(行优先)。
  3. 解释 patch 线性嵌入等价于 kernel 大小和步长都是 P 的卷积,并能用 nn.Conv2d 两行实现。
  4. 说明为什么 ViT 比 CNN 更吃数据(无平移不变性、无局部性这两个归纳偏置),以及 DeiT(蒸馏)、DINOv2(自监督)如何弥补。
  5. 列出 2026 年主流的 ViT 变体(DeiT、Swin、DINOv2、SigLIP、SAM 3)及其关键改动。

一、问题与直觉

Dosovitskiy 等的论文标题就是论点:一张图值 16×16 个词。把图切成 patch、当 token 喂进原版 Transformer 编码器,规模够大就能赢。这是 2026 年「一种架构、多种模态」模式的起点。

Step 1——patchify

H×W×C 图切成 N×(P·P·C) 的扁平 patch 序列。典型设置:224×224 图、16×16 patch → 196 个 patch,每个 768 个值。

image (224, 224, 3) → 14×14 网格的 16x16x3 patch → 196 个长度 768 的向量

patch 大小是杠杆:更小 = 更多 token、更好分辨率、O(N²) 注意力代价更大;更大 = 更粗、更便宜。

Step 2——线性嵌入

一个学习矩阵把每个扁平 patch 投影到 d_model等价于 kernel 大小 P、步长 P 的卷积。PyTorch 里就是 nn.Conv2d(C, d_model, kernel_size=P, stride=P)——两行实现。

Step 3——加 [CLS],加位置编码

  • 前置一个可学习的 [CLS] token,其最终隐状态是用于分类的图像表示(同 BERT)。
  • 加可学习位置嵌入(ViT 原版)或 2D 正弦(后续变体)。2024+ 的 RoPE 扩展到 2D,有时干脆不要显式位置嵌入。

Step 4——标准 Transformer 编码器

堆 L 个 LayerNorm → 自注意力 → + → LayerNorm → MLP → +,与 BERT 完全相同,无任何视觉专用层。这是论文的教学性结论:视觉不需要特殊架构,只需要把图变成 token。

Step 5——头

分类取 [CLS] 隐状态 → 线性 → softmax。DINOv2 或 SAM 丢弃 [CLS],直接用 patch 嵌入。

变体谱系

模型 年份 关键改动
ViT 2020 原版,固定 patch,全全局注意力
DeiT 2021 蒸馏;只在 ImageNet-1k 上可训
Swin 2021 层次化 + 滑动窗口,亚二次代价
DINOv2 2023 自监督(无标签),最佳通用视觉特征
ViT-22B 2023 220 亿参数,缩放定律适用
SigLIP 2023 ViT + 语言配对,sigmoid 对比损失
SAM 3 2025 分割万物,ViT-Large + 可提示掩码解码器

为什么花了一段时间

ViT 需要海量数据才能匹敌 CNN,因为它没有任何 CNN 的归纳偏置(平移不变性、局部性)。没有 >1 亿标注图或强自监督预训练,等算力下 CNN 仍赢。DeiT 在 2021 年用蒸馏技巧修复,DINOv2 在 2023 年用自监督永久修复。

💡 归纳偏置的代价:CNN 把「局部性」和「平移不变性」硬编码进架构(卷积核 + 权重共享),所以小数据上赢;ViT 不假设任何东西,所以更吃数据,但一旦数据够了就 scale 得更远——和第 01 节 Transformer vs RNN 的逻辑完全一致。

二、从零实现

完整代码见原课程 phases/07-transformers-deep-dive/09-vision-transformers/code/main.py。纯标准库 patchify + 线性嵌入 + sanity check。无训练——任何现实规模的 ViT 都需要 PyTorch 和数小时 GPU。

Step 1:假图像

24×24 RGB 图,表示成 (R,G,B) 元组的行的列表。用 6×6 patch → 16 个 patch,每个 108 维嵌入。

Step 2:patchify

def patchify(image, P): H, W = len(image), len(image[0]) patches = [] for i in range(0, H, P): for j in range(0, W, P): patch = [] for di in range(P): for dj in range(P): patch.extend(image[i + di][j + dj]) patches.append(patch) return patches

行优先:按行主序遍历网格。每个 ViT 都用这个顺序。

Step 3:线性嵌入

每个扁平 patch 乘一个随机 (patch_flat_size, d_model) 矩阵,前置 [CLS] 后验证输出形状是 (N_patches + 1, d_model)

Step 4:数真实 ViT 的参数

打印 ViT-Base 的参数量:12 层、12 头、d=768、patch=16。对比 ResNet-50(约 2500 万):ViT-Base 约 8600 万,ViT-Large 约 3.07 亿,ViT-Huge 约 6.32 亿。

三、框架对比:HuggingFace 的 ViT

from transformers import ViTImageProcessor, ViTModel from PIL import Image processor = ViTImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k") model = ViTModel.from_pretrained("google/vit-base-patch16-224-in21k") img = Image.open("cat.jpg") inputs = processor(img, return_tensors="pt") out = model(**inputs).last_hidden_state # (1, 197, 768): [CLS] + 196 patch cls_emb = out[:, 0] # 图像表示
  • DINOv2 嵌入是 2026 年图像特征的默认。 冻结骨干,训一个小头,适用于分类、检索、检测、字幕。Meta 的 DINOv2 检查点在每个非文本视觉任务上超过 CLIP。
  • patch 大小选择。 小模型用 16×16(ViT-B/16);稠密预测(分割)用 8×8 或 14×14(SAM、DINOv2);超大模型用 14×14。

四、可复用产物

原课程产出 outputs/skill-vit-configurator.md:一个配置器 Skill,给定数据集大小、分辨率、算力预算,为新视觉任务选 ViT 变体和 patch 大小。

五、练习

  1. (Easy)code/main.py,验证 patch 数等于 (H/P)*(W/P)、扁平 patch 维度等于 P*P*C
  2. (Medium) 实现 2D 正弦位置嵌入——每个 patch 的 rowcol 各一组独立正弦码,拼接。喂进微型 PyTorch ViT,在 CIFAR-10 上对比可学习位置嵌入的精度。
  3. (Hard) 用 PyTorch 搭一个 3 层 ViT,在 1000 张 MNIST 图上(4×4 patch)训练,测测试精度。再加上简化的 DINOv2 自监督预训练(只训编码器从掩 patch 预测 patch 嵌入),精度提升吗?

本节要点回顾

  1. 一张图 = patch 网格:切成 P×P patch、扁平化、线性投影成嵌入——图变成了 token 序列。
  2. patchify 行优先:每个 ViT 都按行主序遍历网格,patch 数 (H/P)·(W/P)
  3. 线性嵌入等价于卷积:kernel 和步长都是 Pnn.Conv2d,两行实现。
  4. 编码器与 BERT 完全相同:无视觉专用层——这是论文的教学性结论,视觉只需把图变 token。
  5. [CLS] 当图像摘要:前置可学习 token,其最终隐状态用于分类。
  6. ViT 更吃数据:无平移不变性和局部性这两个 CNN 归纳偏置;DeiT(蒸馏)、DINOv2(自监督)弥补了数据缺口。
  7. 变体谱系:DeiT(蒸馏)、Swin(滑动窗口亚二次)、DINOv2(自监督最佳特征)、SigLIP(sigmoid 对比)、SAM 3(分割万物)。
  8. 2026 默认:DINOv2 嵌入冻结骨干 + 小头,patch 16×16(分类)/8×8 或 14×14(稠密)。

下一节,我们把 Transformer 扩展到音频——看 Whisper 如何用 encoder-decoder 把音频频谱图变成文本,以及音频 patch 如何像图像 patch 一样被 token 化。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U