论文阅读笔记:[AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE]

状态: 🟢 已精读


1. 元数据 (Metadata)

条目内容
年份2021
论文链接https://arxiv.org/abs/2010.11929
代码链接https://github.com/wbzuo/paper-impl/tree/main/papers/vit
我的标签计算机视觉

2. 摘要与核心问题 (Abstract & Core Problem)

虽然Transformer架构已经成为自然语言处理任务的标准,但它在计算机视觉中的应用仍然有限。
在视觉中,注意力要么与卷积网络一起应用,要么用于替换卷积网络的某些组件,同时保持其整体结构不变。本文表明,这种对CNN的依赖是不必要的,并且直接应用于图像Patch(块序列)的仅有的Transformer可以很好地执行图像分类任务。当对大量数据进行预训练并传输到多个中型或小型图像识别基准(ImageNet、CIFAR-100、VTAB等)时,与最先进的卷积网络相比,ViT获得了优异的结果,同时训练所需的计算资源少得多。

  • 论文要解决什么问题?
    • 摆脱对卷积神经网络(CNN)的依赖,探索一个“纯粹”的Transformer架构是否能够成为视觉任务的主流骨干网络。
    • 解决Transformer在视觉中的直接应用难题:将原本为序列设计的Transformer直接应用于像素会面临计算复杂度高(与像素数的平方成正比)和缺乏视觉归纳偏置(如局部性、平移不变性)两大挑战。本文需要提出一种方法,将2D图像有效地转化为Transformer能够处理的1D序列,并克服数据效率低下的问题。
  • 核心主张或贡献是什么?
    • 提出Vision Transformer架构:
      将图像分割成固定大小的图像块,将每个图像块线性嵌入成一个向量(视为一个“词”),并加上位置编码,形成一个序列。这个序列可以直接输入标准Transformer编码器进行处理。

    • 证明“大规模预训练”胜过“归纳偏置”:
      在中小型数据集(如ImageNet)上,由于缺乏CNN固有的归纳偏置,ViT的性能会略低于同等规模的ResNet。当在超大规模数据集上进行预训练时,ViT的性能产生质的飞跃。模型能够直接从数据中学习到所需的视觉结构,从而在迁移到多个下游任务时,达到或超越了当时的SOTA性能。这证明了数据的规模可以弥补甚至超越精心设计的结构先验。


好的,这是根据论文内容整理的“背景知识”部分。

3. 背景知识 (Background)

前置知识/术语

  • Transformer: 完全基于自注意力机制,摒弃了循环和卷积结构。它已成为自然语言处理领域的标准模型。其核心组件包括多头自注意力 和前馈神经网络,通常使用编码器架构。

  • 自注意力 / 多头自注意力:

    • 自注意力:一种机制,允许序列中的每个元素(如一个词)与序列中的所有其他元素进行交互,通过计算“注意力权重”来决定在处理当前元素时应该“关注”其他元素的多少信息。
    • 多头自注意力:将自注意力机制并行执行多次(即多个“头”),每个头可以学习关注序列中不同方面的信息,最后将各头的输出拼接并线性变换,从而增强模型的表示能力。
  • 归纳偏置: 指模型为学习问题而内置的假设或先验知识。对于CNN,其归纳偏置包括局部性(相邻像素关联性强)和平移等变性(物体在图像中移动,其表征也应相应移动)。ViT则缺乏这种强图像归纳偏置。

  • 位置编码: 由于Transformer本身不包含序列顺序信息,需要额外向输入中添加表示其位置信息的编码。在ViT中,这是通过可学习的一维位置嵌入实现的,被加到每个图像块嵌入上,以让模型理解图像块的空间关系。

相关工作简要回顾

论文中提到的相关研究工作主要分为以下几类:

  • CNN与注意力的结合:

    • 方法:在CNN架构中引入自注意力模块,例如通过注意力机制来增强特征图,或使用注意力对CNN的输出进行后处理。应用于图像分类、目标检测、视频处理等。
    • 代表工作:Non-local Neural Networks, Attention Augmented Convolutional Networks, DETR。
  • 用自注意力完全替代卷积:

    • 方法:尝试用局部自注意力或稀疏全局注意力等机制来构建不包含卷积的“纯”注意力模型。
    • 代表工作:Stand-alone Self-Attention, Local Relation Networks, Sparse Transformers。
    • 局限性:这些模型虽然理论计算效率高,但由于使用了特殊的注意力模式,在当时难以在现代硬件加速器上进行有效扩展,导致在大型图像识别任务中,经典的类ResNet架构仍然是主流。
  • 基于Transformer的生成式图像模型:

    • 方法:将Transformer直接应用于图像像素,但通常需要降低图像分辨率或颜色空间,并以无监督方式(如自回归建模)进行训练。
    • 代表工作:Image GPT。
    • 局限性:在ImageNet上的最高准确率仅为72%,性能未能超越有监督的CNN。
  • 大规模监督学习:

    • 方法:研究CNN性能如何随数据集规模扩大而提升,并探索从超大数据集进行迁移学习。
    • 代表工作:Big Transfer。
    • 局限性:这些工作都局限于使用CNN作为核心架构。

局限性总结与本文动机

综上所述,在ViT之前,相关工作的局限性在于:

  1. 对CNN的依赖:主流方法要么是CNN的变体,要么与CNN强耦合。
  2. “纯”注意力模型难以有效扩展:已有的完全基于注意力的模型,或因硬件支持问题,或因性能问题,未能撼动CNN的地位。
  3. 缺乏对标准Transformer在视觉中潜力的探索:未能系统性地证明,一个尽可能不做修改的标准Transformer,在足够大规模数据的支持下,能够在视觉任务上达到顶尖水平。

因此,本文的动机是: 直接、最小修改地将标准Transformer架构应用于图像序列,并通过大规模预训练来验证其能否克服归纳偏置的不足,最终在性能和效率上超越传统的CNN。


4. 方法/模型 (Methodology/Model)

这里我将以ViT分为不同模块来讲解,下图为总体架构
在这里插入图片描述

4.1 Patch Embedding + Position Embedding + CLS

如下图所示,Vision Transformer 的核心创新在于将 图像处理重新定义为序列建模问题。这一过程包含四个关键步骤:

4.1.1 图像分块
  • 输入:一张 224 × 224 × 3 的 RGB 图像
  • 分块策略:将图像均匀分割成 16 × 16 像素的小块
  • 数学关系:
    • 每行块数:224 ÷ 16 = 14
    • 每列块数:224 ÷ 16 = 14
    • 总块数:14 × 14 = 196 个 patches
  • 每个 Patch:包含 16 × 16 × 3 = 768 个像素值
4.1.2 块嵌入
  • 每个 16×16×3 的图像块被展平为长度为 768 的向量
  • 通过一个线性投影层(在代码中常实现为卷积层)映射到固定的嵌入维度(如 768 维)
  • 此时图像被转换为:196 个嵌入向量,每个向量维度为 768 (形状:[batch_size, 196, 768])

注:这里有一个重要的细节。投影后的维度(例如768)是独立选择的,并不必须等于展平后的像素数(16x16x3=768这只是一种巧合)。我们可以选择投影到512维、768维或1024维。

4.1.3 添加特殊标记
  • [CLS] Token:在序列开头添加一个特殊的、可学习的分类标记
  • 这个标记在整个 Transformer 处理过程中会聚合全局信息
  • 序列变为:[CLS] + 196 个图像块 = 197 个嵌入向量 (形状:[batch_size, 197, 768])
4.1.4 位置编码
  • 由于 Transformer 本身不包含位置信息,需要显式添加位置编码
  • 为序列中的每个位置(包括 [CLS])学习一个独特的可学习的位置向量
  • 位置向量与嵌入向量逐元素相加,为模型提供空间关系信息
  • 最终输出:一个包含了图像内容信息和空间位置信息的序列,可以直接输入 Transformer 编码器。

NLP 与 CV 的完美类比

组件NLP (文本处理)CV (图像处理)功能说明
基本单元一句话/段落一张图片处理的完整对象
处理单元词元/单词图像块模型处理的基本元素
序列长度词元数量图像块数量决定计算复杂度
嵌入层词嵌入块嵌入将离散单元映射到连续向量空间
位置信息位置编码位置编码提供序列中的顺序/空间关系
分类标记[CLS] Token[CLS] Token聚合全局信息用于分类

最终结果

通过这一系列变换:

  • 输入:224 × 224 × 3 的二维图像
  • 输出:50 × 768 的一维序列

这个序列可以直接输入标准 Transformer 编码器进行处理,使得原本为 NLP 设计的 Transformer 架构能够"理解"图像内容,实现了计算机视觉任务的序列化处理范式。

在这里插入图片描述

class PatchEmbedding(nn.Module):
    def __init__(self, in_channels: int = 3, patch_size: int = 16, emb_size: int = 768, img_size: int = 224):
        super().__init__()
        self.patch_size = patch_size
        # 224 = 14 * 16
        # 做法1.使用卷积层(b, 14 * 14, 3 * 16 * 16) 主要做法
        
        self.projection = nn.Sequential(
            # 使用一个卷积层而不是一个线性层 -> 性能增加
            # b c h w -> b e  h // p  w // p
            nn.Conv2d(in_channels, emb_size, kernel_size=patch_size, stride=patch_size),
            
            # 将卷积操作后的patch铺平
            # b e   h // p  w // p -> 
            Rearrange('b e h w -> b (h w) e'),
        )
        
        # 做法2.使用线性层实现
        # self.projection = nn.Sequential(
        #     # 将原始图像切分为16*16的patch并把它们拉平
        #     Rearrange('b c (h s1) (w s2) -> b (h w) (s1 s2 c)', s1=patch_size, s2=patch_size),
        #     # 注意这里的隐层大小设置的也是768,可以配置
        #     nn.Linear(patch_size * patch_size * in_channels, emb_size)
        # )
        
        
        # 生成cls_token的emb_size 添加在序列最前面的
        self.cls_token = nn.Parameter(torch.randn(1, 1, emb_size))
        
        num_patches  = (img_size // patch_size) ** 2
        self.positions = nn.Parameter(torch.randn(num_patches+ 1, emb_size))
        # 添加dropout
        self.dropout = nn.Dropout(0.2)
         
    def forward(self, x):
        b, _, _, _ = x.shape

        # patch操作
        x = self.projection(x)
        
        # cls_token拓展b次 (b, 1, emb_size)
        cls_tokens = repeat(self.cls_token, '() n e -> b n e', b = b)
        
        # cls_tokens和patch进行拼接
        x = torch.cat([cls_tokens, x], dim = 1)
        # 添加位置编码
        x += self.positions

        return x

4.2 Transformer Encoder

Transformer Encoder 是 ViT 的核心组成部分,负责对序列化的图像块进行深度特征提取和关系建模。

4.2.1 整体架构

输入: [batch_size, num_patches + 1, hidden_dim]
    ↓
Transformer Encoder (L个相同的层)
    ↓
输出: [batch_size, num_patches + 1, hidden_dim]

4.2.2 单个Encoder层详细结构

class TransformerEncoderLayer(nn.Module):
    """
    Single layer of Transformer encoder
    Input: (B, N, D)
    Output: (B, N, D)
    """
    def __init__(self, embed_dim=256, num_heads=8, hidden_dim=1024, dropout=0.1):
        super().__init__()
        # Layer normalization before MHSA
        self.ln1 = nn.LayerNorm(embed_dim)
        # Multi-Head Self-Attention
        self.mha = MultiHeadAttention(embed_dim, num_heads, dropout)
        # Layer normalization before MLP
        self.ln2 = nn.LayerNorm(embed_dim)
        # MLP block
        self.mlp = MLP(embed_dim, hidden_dim, dropout)
        
    def forward(self, x):
        # Residual connection + MHSA
        x = x + self.mha(self.ln1(x))  # (B, 65, 256)
        # Residual connection + MLP
        x = x + self.mlp(self.ln2(x))  # (B, 65, 256)
        return x
输入预处理
输入 → LayerNorm → 进入Multi-Head Attention
Multi-Head Attention Block
  • 功能: 建立全局依赖关系,让每个位置都能关注所有其他位置
  • 流程:
    1. 生成Q、K、V矩阵(每个头独立)
    2. 计算注意力权重:Softmax(Q·K^T/√d_k)
    3. 加权求和:Attention = Weights · V
    4. 多头输出拼接并线性投影
class MultiHeadAttention(nn.Module):
    def __init__(self, emb_size = 512, num_heads = 8, dropout = 0):
        super().__init__()
        self.emb_size = emb_size
        self.num_heads = num_heads
        self.keys = nn.Linear(emb_size, emb_size)
        self.querys = nn.Linear(emb_size, emb_size)
        self.values = nn.Linear(emb_size, emb_size)
        self.att_drop = nn.Dropout(dropout)
        self.projection = nn.Linear(emb_size, emb_size)
            
        
    def forward(self, x, mask = None):
        queries = rearrange(self.queries(x), "b n (h d) -> b h n d", h=self.num_heads)
        keys = rearrange(self.keys(x), "b n (h d) -> b h n d", h=self.num_heads)
        values  = rearrange(self.values(x), "b n (h d) -> b h n d", h=self.num_heads)
        energy = torch.einsum('bhqd, bhkd -> bhqk', queries, keys) # batch, num_heads, query_len, key_len
        if mask is not None:
            fill_value = torch.finfo(torch.float32).min
            energy.mask_fill(~mask, fill_value)
            
        scaling = self.emb_size ** (1/2)
        att = F.softmax(energy, dim=-1) / scaling
        att = self.att_drop(att)
        # sum up over the third axis
        out = torch.einsum('bhal, bhlv -> bhav ', att, values)
        out = rearrange(out, "b h n d -> b n (h d)")
        out = self.projection(out)
        return out

残差连接: Attention_Output = Attention(LayerNorm(Input)) + Input

MLP Block
  • 结构: 两层的全连接网络
    • 扩展层: hidden_dim → 4×hidden_dim
    • 压缩层: 4×hidden_dim → hidden_dim
  • 激活函数: GELU(高斯误差线性单元)
  • 作用: 对每个位置的特征进行非线性变换和增强
class MLP(nn.Module):
    """
    Multi-Layer Perceptron for Transformer encoder
    Input: (B, N, D)
    Output: (B, N, D)
    """
    expansion = 4

    def __init__(self, embed_dim=256, hidden_dim=1024, dropout=0.1):
        super().__init__()
        self.fc1 = nn.Linear(embed_dim, hidden_dim)  # Expand to 4x dimension
        self.fc2 = nn.Linear(hidden_dim, embed_dim)  # Project back
        self.dropout = nn.Dropout(dropout)
        self.gelu = nn.GELU()  # Activation function
        
    def forward(self, x):
        x = self.fc1(x)  # (B, 65, 256) -> (B, 65, 1024)
        x = self.gelu(x)
        x = self.dropout(x)
        x = self.fc2(x)  # (B, 65, 1024) -> (B, 65, 256)
        x = self.dropout(x)
        return x

残差连接: MLP_Output = MLP(LayerNorm(Attention_Output)) + Attention_Output

在ViT中的具体配置

模型变体参数

模型层数(L)隐藏维度MLP大小头数参数量
ViT-Base1276830721286M
ViT-Large241024409616307M
ViT-Huge321280512016632M

[CLS] Token的最终状态

  • 经过所有L层Encoder后,[CLS] token包含了整个图像的压缩表示
  • 用于最终的分类任务:y = Classifier(Encoder_Output[:, 0])

Patch Embeddings的输出

  • 其他位置的输出可用于密集预测任务(如分割、检测)
  • 保留了空间结构和局部信息

这种设计使得Transformer Encoder能够:

  1. 捕捉长距离依赖:不受卷积核大小的限制
  2. 保持平移不变性:通过位置编码而非卷积
  3. 高效并行计算:自注意力的矩阵运算高度并行化
  4. 可扩展性强:通过增加层数和维度提升性能
    在这里插入图片描述

5. 结论 (Conclusion)

主要贡献:

  1. 提出了第一个完全基于标准Transformer架构的纯视觉模型(ViT),摆脱了对CNN的依赖,将图像处理重新定义为序列建模问题。

  2. 仅通过将图像分割成块、线性投影、添加位置编码,即可直接应用NLP中的标准Transformer,无需复杂的视觉专用修改。

  3. 首次系统证明大规模预训练可以超越归纳偏置——当数据量足够大时(JFT-300M),ViT的性能显著优于基于强归纳偏置的CNN模型。

局限性:

  1. 数据依赖性:在中小型数据集(如ImageNet)上,ViT的性能不如同等规模的ResNet,表明其需要大量数据来弥补缺乏的视觉归纳偏置。

  2. 计算复杂度:自注意力机制在序列长度上的二次复杂度,限制了其处理超高分辨率图像的能力。


6. 个人总结

ViT的核心贡献是完成了视觉领域的范式转变:它用“视觉即序列”的新思路打破卷积垄断,证明了大尺度数据能够超越人工设计的归纳偏置;虽然依赖大数据,但其开创性架构催生了DeiT、Swin Transformer等后续工作,重塑了现代计算机视觉的发展路径。
这篇论文开辟了视觉Transformer这一全新研究方向,启发了后续大量工作,堪称视觉领域的"Attention Is All You Need"时刻。

更多推荐