当AlexNet在2012年ImageNet竞赛中一举夺魁时,卷积神经网络(CNN)便开启了它在计算机视觉领域长达十年的统治。然而2020年,一篇名为《An Image is Worth 16x16 Words》的论文彻底颠覆了这一格局——Vision Transformer(ViT)首次证明,纯Transformer架构在图像识别任务上可以超越最先进的CNN。这一突破性进展的核心设计,正是将图像分割为patches这一看似简单的操作。
Transformer架构最初是为自然语言处理设计的,其核心自注意力机制要求输入是一系列token组成的序列。这与图像的二维网格结构存在根本性矛盾。想象一下,如果直接将224x224像素的图像展平为50176维的向量,自注意力层的计算复杂度将高达O(50176²),这在实际应用中是完全不可行的。
ViT的解决方案既优雅又高效:将图像划分为固定大小的patches。以ViT-B/16为例:
python复制# 图像分块示例 (PyTorch风格伪代码)
image = torch.rand(1, 3, 224, 224) # 输入图像
patch_size = 16
patches = image.unfold(2, patch_size, patch_size).unfold(3, patch_size, patch_size)
patches = patches.contiguous().view(1, -1, 3*patch_size*patch_size) # 形状变为[1, 196, 768]
这种处理带来了三个关键优势:
设计思考:patch大小需要在计算效率和语义粒度之间取得平衡。实验表明,16x16在ImageNet分类任务上实现了最佳性价比,而更小的8x8 patches在检测/分割任务中表现更好。
在ViT的架构中,图像patches与语言tokens的对应关系并非简单类比,而是蕴含了精妙的层次化设计:
| 处理阶段 | 视觉领域 | 自然语言处理 |
|---|---|---|
| 原始输入 | 3x224x224像素网格 | 单词序列 |
| 基础处理单元 | 16x16x3的image patches | 单词/子词tokens |
| 嵌入表示 | 768维线性投影 | 512/768维词嵌入 |
| 位置信息编码 | 可学习的位置嵌入 | 正弦/可学习位置编码 |
| 序列处理 | Transformer Encoder | Transformer Encoder |
这种设计使得ViT能够:
python复制# ViT的patch嵌入层典型实现
class PatchEmbed(nn.Module):
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
super().__init__()
self.proj = nn.Conv2d(in_chans, embed_dim,
kernel_size=patch_size,
stride=patch_size)
def forward(self, x):
x = self.proj(x) # [B, 768, 14, 14]
x = x.flatten(2).transpose(1, 2) # [B, 196, 768]
return x
当patches被转换为token序列后,ViT的多头自注意力机制展现出与CNN截然不同的特征学习方式:
CNN的特征学习特点:
ViT的自注意力特性:
python复制# 简化版多头注意力计算
class Attention(nn.Module):
def __init__(self, dim, num_heads=8):
super().__init__()
self.num_heads = num_heads
self.scale = (dim // num_heads) ** -0.5
self.qkv = nn.Linear(dim, dim*3)
self.proj = nn.Linear(dim, dim)
def forward(self, x):
B, N, C = x.shape
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C//self.num_heads)
q, k, v = qkv.unbind(2) # [B, N, H, C/H]
attn = (q @ k.transpose(-2,-1)) * self.scale
attn = attn.softmax(dim=-1)
x = (attn @ v).transpose(1,2).reshape(B, N, C)
return self.proj(x)
这种机制带来了几个革命性优势:
实验数据显示,ViT的浅层注意力头已经展现出有趣的模式:
ViT的patch设计不仅适用于分类任务,经过巧妙调整后,还能支持各种视觉任务:
目标检测(DETR系列):
语义分割(Segmenter):
python复制# 分割头典型实现
class SegmentationHead(nn.Module):
def __init__(self, in_dim, num_classes):
super().__init__()
self.head = nn.Conv2d(in_dim, num_classes, 1)
def forward(self, x, img_size):
# x: [B, N, C]
H, W = img_size[0]//patch_size, img_size[1]//patch_size
x = x[:, 1:].transpose(1,2).reshape(x.shape[0], -1, H, W)
return self.head(x)
视频理解(ViViT):
下表对比了不同任务中的patch设计差异:
| 任务类型 | Patch维度 | 特殊处理 | 典型分辨率 |
|---|---|---|---|
| 图像分类 | 16×16×3 | [CLS] token | 224×224 |
| 目标检测 | 16×16×3 | 多尺度特征融合 | 1024×1024 |
| 语义分割 | 8×8×3 | 重叠切片 | 512×512 |
| 视频理解 | 2×16×16×3 | 时空位置编码 | 32×224×224 |
在实际部署ViT模型时,patch设计还需要考虑以下工程因素:
内存效率优化:
python复制# 内存友好的注意力计算
class MemoryEfficientAttention(nn.Module):
def forward(self, q, k, v):
scale = q.shape[-1]**-0.5
q = q * scale
attn = torch.einsum('bhid,bhjd->bhij', q, k)
attn = attn.softmax(dim=-1)
out = torch.einsum('bhij,bhjd->bhid', attn, v)
return out
混合架构设计:
训练策略创新:
以下是一个典型ViT模型的参数量分布示例:
| 组件 | 参数量占比 | 计算量占比 |
|---|---|---|
| Patch Embedding | 5% | 8% |
| Position Embedding | 2% | 1% |
| Transformer Layers | 88% | 85% |
| Classification Head | 5% | 6% |
这种架构特点启示我们:
随着研究的深入,patch设计仍在持续进化。从最初的固定网格划分,到后来的重叠切片(Swin Transformer)、动态大小(Dynamic ViT),再到最近的视觉词表(Visual Vocab),这一基础设计空间仍充满创新可能。理解其背后的设计哲学,将帮助我们更好地驾驭这一强大的视觉建模范式。