前言
在很长一段时间里,CNN 几乎是计算机视觉任务的标准答案。图像分类用 CNN,目标检测用 CNN,语义分割也离不开 CNN。从 AlexNet 到 VGG,再到 ResNet,卷积神经网络一步步推动了计算机视觉的发展。对于很多人来说,一提到图像任务,第一反应就是卷积网络。但是从 2020 年开始,一个新的模型逐渐成为视觉领域的核心关键词:Vision Transformer,简称 ViT。
ViT 的想法看起来非常简单:把一张图像切成很多小块,也就是 patch,然后把这些 patch 当成一个个 token,送入 Transformer 中进行建模。这就带来了一个很自然的问题:CNN 已经这么成功了,为什么视觉领域还需要 Vision Transformer?ViT 的出现并不是因为 CNN 突然失效了,而是因为视觉任务的发展需求发生了变化。随着大规模预训练、多模态学习、视觉基础模型和高效部署的发展,视觉模型不再只需要识别局部纹理和形状,还需要更强的全局建模能力、更统一的结构形式,以及更好的规模扩展能力。
这篇文章,我们就从 CNN 的优势讲起,一步步理解 ViT 为什么会出现。
[1. CNN 曾经是视觉任务的默认选择](#1. CNN 曾经是视觉任务的默认选择)
在 ViT 出现之前,CNN 长期是计算机视觉领域的主流模型。早期的 AlexNet 证明了深度卷积网络在大规模图像分类任务上的强大能力;后来的 VGG 使用更规整的网络结构加深模型;GoogLeNet 引入 Inception 结构;ResNet 则通过残差连接解决了深层网络难以训练的问题。从那以后,CNN 几乎成为视觉任务中的基础组件。例如:
图像分类:ResNet、DenseNet、EfficientNet
目标检测:Faster R-CNN、YOLO、SSD
语义分割:FCN、U-Net、DeepLab
实例分割:Mask R-CNN
这些经典方法虽然任务不同,但底层基本都离不开卷积网络。原因很简单:图像本身就是一种空间结构非常强的数据,而 CNN 天然适合处理这种空间结构。
2. CNN 为什么适合处理图像?
要理解 ViT 为什么会出现,首先要理解 CNN 为什么成功。CNN 的成功不是偶然的,它和图像数据的特点高度匹配。一张图像不是一堆毫无关系的数字,而是有明显空间结构的二维网格。图像中的边缘、角点、纹理、颜色变化,通常都发生在局部区域中。例如,一只猫的图片中:眼睛、耳朵、胡须、毛发纹理,都是局部视觉模式;猫的头部、身体、尾巴,是由局部模式逐渐组合出来的;最后,模型根据整体特征判断这是一只猫。
2.1 局部连接:先看局部区域
卷积核不会一次性看完整张图,而是只关注一个小窗口。比如一个 3×3 的卷积核,每次只看图像中的一个局部区域。它可以用来检测边缘、纹理、角点等低级视觉特征。这和人类观察图像的方式也有点类似:我们通常不是一眼就理解整张图的全部细节,而是先感知局部结构,再逐渐形成整体理解。
2.2 权重共享:同一种特征可以出现在不同位置
CNN 的另一个重要特点是权重共享。同一个卷积核会在整张图像上滑动。也就是说,如果一个卷积核学会了检测"边缘",那么它可以在图像的左上角检测边缘,也可以在右下角检测边缘。这非常适合图像任务。因为图像中的某种视觉模式可能出现在不同位置。例如:猫的耳朵可能出现在图像左侧,也可能出现在图像右侧;车轮可能在图像下方,也可能因为视角变化出现在其他位置;边缘和纹理更是可能出现在任意区域。CNN 通过权重共享减少了参数量,也提高了模型对位置变化的适应能力。
2.3 层次化特征:从边缘到语义
CNN 通常是一层一层堆叠的。浅层卷积提取低级特征,比如边缘、颜色、纹理;中间层提取局部结构,比如眼睛、轮子、叶子;高层提取语义特征,比如猫、汽车、树。可以简单理解为:浅层:边缘、颜色、纹理;中层:局部部件、形状;高层:物体类别、语义信息。这种从局部到整体、从低级到高级的特征提取方式,非常符合图像识别任务的需求。所以 CNN 能够长期主导视觉任务,并不是因为它"刚好有效",而是因为它的结构设计本身就很适合图像。
3. CNN 的问题:看局部很强,看全局不够直接
既然 CNN 这么适合图像,为什么还需要 ViT?原因在于,CNN 的优势和局限其实来自同一个地方:局部建模。CNN 很擅长处理局部区域,但如果想让图像中两个距离很远的区域发生信息交互,就没有那么直接。举个例子。假设图像中有一个人骑在马背上。人的上半身、腿、马的身体、背景可能分布在图像的不同区域。模型如果想正确理解这张图,可能需要同时考虑多个远距离区域之间的关系。对于 CNN 来说,远距离信息交互通常需要依赖多层卷积逐渐扩大感受野。也就是说,CNN 是这样理解图像的:先看局部;再通过多层网络逐渐扩大感受野;最后形成整体语义理解。这种方式当然是有效的,但它不是最直接的全局建模方式。理论上,深层 CNN 的感受野可以覆盖整张图像。但是,感受野覆盖整张图,并不意味着模型一定能高效地建模所有区域之间的关系。比如图像中的两个区域相距很远:左上角有一个目标;右下角有另一个目标;两个目标之间存在语义关系。CNN 需要通过很多层卷积才能让这两个区域的信息充分交互。而 Transformer 的 self-attention 则可以让任意两个 token 直接计算关系。这就是 ViT 出现的重要背景之一:视觉模型需要一种更直接的全局关系建模方式。
4. Transformer 给视觉领域带来了什么启发?
Transformer 最早在自然语言处理领域取得了巨大成功。在 NLP 中,一句话可以看成由多个 token 组成的序列。例如:Transformer 的核心机制是 self-attention。它允许序列中的每个 token 和其他 token 建立联系。也就是说,每个词都可以"看见"其他词,并根据相关性决定应该关注谁。例如,在一个长句子中,一个代词可能需要关联前面很远处的名词;一个动词可能需要同时考虑主语和宾语。Self-attention 可以比较直接地处理这种长距离依赖。这给视觉领域带来了一个很自然的想法:如果文本可以被看成 token 序列,那么图像能不能也被看成 token 序列?这个问题就是 ViT 的出发点。
5. ViT 的关键想法:把图像变成 token
ViT 最核心的想法其实很简单:把图像切成一个个 patch,然后把每个 patch 当成一个视觉 token。
比如一张常见的输入图像大小是:
224 × 224
如果我们把它切成:
16 × 16
大小的小块,那么横向可以切成 14 个 patch,纵向也可以切成 14 个 patch。
所以总共可以得到:
14 × 14 = 196 个 patch
也就是说,一张图像可以变成一个长度为 196 的 patch 序列。
6. ViT 和 CNN 到底有什么区别?
ViT 不是简单地把 CNN 中的卷积层换成 Transformer 层,它背后的建模思路发生了变化。
CNN 的思路可以概括为:从局部到整体,逐层提取特征。
CNN 天生适合处理局部模式。它的卷积核先关注局部区域,再通过层层堆叠获得更大的感受 野。这种设计让 CNN 在有限数据下也能表现不错,因为它本身就带有很强的图像先验。简单来说:CNN 认为图像应该从局部结构开始理解。
ViT 的思路可以概括为:把图像切成 token,让 token 之间直接进行全局交互。
ViT 中的 self-attention 允许任意两个 patch token 直接建立关系。这意味着图像中相距很远的两个区域,也可以在一层 Transformer 中发生信息交互。简单来说:ViT 认为图像可以被看成一个 token 序列,不同 token 之间的关系可以通过 attention 来学习。
CNN 有很强的归纳偏置,比如局部连接、权重共享和平移等变性。这些先验让 CNN 即使在数据量不是特别大的情况下,也能比较稳定地训练。ViT 则不太依赖这些手工设计的图像先验。它更希望通过大规模数据自己学习视觉规律。所以早期 ViT 有一个非常典型的问题:数据量小时,不一定比 CNN 好;数据量足够大时,模型潜力非常强。这也是为什么 ViT 原论文中特别强调大规模预训练。
7. ViT 的真正意义是什么?
ViT 的真正意义,不只是提出了一个新的图像分类模型。
它更重要的意义在于:ViT 改变了人们对视觉建模方式的理解。
在 CNN 时代,图像通常被看作二维特征图。模型通过卷积操作不断提取局部特征,再逐渐形成高级语义。而在 ViT 中,图像被重新组织成 token 序列。模型通过 self-attention 建模 token 之间的关系。这带来了视觉建模方式的一次重要转变:从"卷积特征图建模"
转向"视觉 token 序列建模"
这个转变非常重要。
因为一旦图像可以被看成 token 序列,那么图像、文本、视频、语音等不同模态,就有机会放在更统一的 Transformer 框架下处理。这也是后来很多视觉基础模型和多模态模型发展的基础。
8.把图像切成 patch
python
from PIL import Image
import matplotlib.pyplot as plt
# 读取图像
img_path = "demo.jpg"
img = Image.open(img_path).convert("RGB")
# 调整图像大小
img = img.resize((224, 224))
# patch 大小
patch_size = 16
# 计算 patch 数量
num_patches_per_row = 224 // patch_size
num_patches = num_patches_per_row * num_patches_per_row
print("每行 patch 数量:", num_patches_per_row)
print("总 patch 数量:", num_patches)
# 切分 patch
patches = []
for i in range(num_patches_per_row):
for j in range(num_patches_per_row):
left = j * patch_size
upper = i * patch_size
right = left + patch_size
lower = upper + patch_size
patch = img.crop((left, upper, right, lower))
patches.append(patch)
# 可视化 patch
plt.figure(figsize=(8, 8))
for idx, patch in enumerate(patches):
plt.subplot(num_patches_per_row, num_patches_per_row, idx + 1)
plt.imshow(patch)
plt.axis("off")
plt.tight_layout()
plt.show()


每一个小块,都可以看成 ViT 中的一个视觉 token。ViT 处理图像的方式和 CNN 有本质区别。CNN 是在二维图像上滑动卷积核。ViT 是先把图像变成 token 序列,再用 Transformer 建模 token 之间的关系。