Vision transformer

1. Vision Transformer

1.1. Vit基本信息

类别内容
论文AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE
论文地址https://arxiv.org/pdf/2010.11929
项目地址https://github.com/google-research/vision_transformer
timmhttps://github.com/rwightman/pytorch-image-models/blob/master/timm/models/vision_transformer.py
作者Alexey Dosovitskiy∗,†, Lucas Beyer∗, Alexander Kolesnikov∗, Dirk Weissenborn∗,Xiaohua Zhai∗, Thomas Unterthiner, Mostafa Dehghani, Matthias Minderer,Georg Heigold, Sylvain Gelly, Jakob Uszkoreit, Neil Houlsby∗,†∗equal technical contribution, †equal advising Google Research, Brain Team{adosovitskiy, neilhoulsby}@google.com
单位Google Research, Brain Team
摘要虽然 Transformer 架构已成为 NLP 任务的事实标准,但它在 CV 中的应用仍然有限。在视觉上,注意力要么与卷积网络结合使用,要么用于替换卷积网络的某些组件,同时保持其整体结构。我们证明了这种对 CNNs 的依赖是不必要的,直接应用于图像块序列 (sequences of image patches) 的纯 Transformer 可以很好地执行 图像分类 任务。当对大量数据进行预训练并迁移到多个中小型图像识别基准时 (ImageNet、CIFAR-100、VTAB 等),与 SOTA 的 CNN 相比,Vision Transformer (ViT) 可获得更优异的结果,同时仅需更少的训练资源。
贡献ViT最大的特色在于将Transformer中的Encoder直接应用于图像特征提取部分,实现了从自然语言处理到视觉领域的跨域迁移。这一创新使得ViT在图像分类、目标检测、语义分割等多个视觉任务上都取得了优异的表现。与传统的CNN模型相比,ViT具有更强的全局特征提取能力,可以更好地建模图像中的长距离依赖关系。
此外,ViT还具有更强的可扩展性。通过增加模型的深度和宽度,ViT可以在更大的数据集上进行预训练,从而进一步提升模型的性能。这一特性使得ViT在AIGC预训练大模型的发展中具有巨大的潜力。

在这里插入图片描述

1. 2. ViT的实现细节

ViT的实现主要包括以下几个部分:

名称解释
图像分块与嵌入ViT将输入的图像分割成固定大小的块,并将每个块展平为一维向量。然后,通过线性变换将向量映射到模型的嵌入空间中,得到每个块的嵌入表示。
位置编码由于Transformer模型本身不具有处理序列顺序的能力,因此需要通过位置编码来引入序列中的位置信息。ViT采用了与Transformer相同的位置编码方式,即为每个位置添加一个固定的位置嵌入向量。
Transformer EncoderViT在图像特征提取部分直接使用了Transformer中的Encoder。Encoder由多个自注意力机制和前馈神经网络组成,通过迭代更新每个块的嵌入表示,提取出图像的全局特征。
分类头在提取出全局特征后,ViT将特征向量送入一个分类头进行分类。分类头通常由一个全连接层和一个softmax函数组成,用于将特征向量映射到各个类别的概率分布上。

2. 论文阅读

摘要

虽然Transformer架构已成为 NLP 任务的事实标准,但它在 CV 中的应用仍然有限。在视觉上,注意力要么与卷积网络结合使用,要么用于替换卷积网络的某些组件,同时保持其整体结构。我们证明了这种对 CNNs 的依赖是不必要的,直接应用于图像块序列 (sequences of image patches) 的纯 Transformer 可以很好地执行 图像分类 任务。当对大量数据进行预训练并迁移到多个中小型图像识别基准时 (ImageNet、CIFAR-100、VTAB 等),与 SOTA 的 CNN 相比,Vision Transformer (ViT)可获得更优异的结果,同时仅需更少的训练资源。

2.1. 介绍

基于自注意力的架构,尤其是 Transformer,已成为 NLP 中的首选模型。主要方法是 在大型文本语料库上进行预训练,然后在较小而特定于任务的数据集上进行微调。 由于 Transformers 的计算效率和可扩展性,训练具有超过 100B 个参数的、前所未有的模型成为了可能。随着模型和数据集的增长,仍未表现出饱和的迹象。

然而,在 CV 中,卷积架构仍然占主导地位。受到 NLP 成功的启发,多项工作尝试将类似 CNN 的架构与自注意力相结合,有些工作完全取代了卷积。后一种模型虽然理论上有效,但由于使用了特定的注意力模式,尚未在现代硬件加速器上有效地扩展。因此,在大规模图像识别中,经典的类 ResNet 架构仍是最先进的。

受 NLP 中 Transformer 成功放缩/扩展 (scaling / scale up) 的启发,我们尝试将标准 Transformer 直接应用于图像,并尽可能减少修改。为此,我们 将图像拆分为块 (patch),并将这些图像块的线性嵌入序列作为 Transformer 的输入。图像块 (patches) 的处理方式同 NLP 的标记 (tokens) (故经过线性嵌入后又叫 patch token)。我们以有监督方式训练图像分类模型。

当在没有强正则化的中型数据集(如 ImageNet)上进行训练时,这些模型产生的准确率比同等大小的 ResNet 低几个百分点。 这种看似令人沮丧的结果可能是意料之中的:Transformers 缺乏 CNN 固有的一些归纳偏置 (inductive biases) —— 如 平移等效性 和 局部性 (translation equivariance and locality),因此在数据量不足时,训练不能很好地泛化。

但若在更大的数据集 (14M-300M 图像) 上训练,情况就会发生变化。我们发现 大规模训练 胜过 归纳偏置。Vision Transformer (ViT) 在以足够的规模进行预训练并迁移到具有较少数据点的任务时获得了出色结果。当在公共 ImageNet-21k 数据集或内部 JFT-300M 数据集上进行预训练时,ViT 在多个图像识别基准上接近或击败了最先进的技术。特别是,最佳模型在 ImageNet 上的准确率达到 88.55%,在 ImageNet-RealL 上达到 90.72%,在 CIFAR-100 上达到 94.55%,在 19 个任务的 VTAB 上达到 77.63%。

2.2. 相关工作

Transformers 是由 Vaswani 等人提出的 机器翻译 方法,并已成为许多 NLP 任务中的 SOTA 方法。基于大型 Transformers 的模型通常在大型语料库 (corpus) 上预训练,然后根据所需的下游任务 (down-stream tasks) 进行微调 (finetune)。注意,BERT 使用 去噪自监督预训练任务,而 GPT 系列使用 语言建模 (LM) 作为预训练任务。

应用于图像的简单自注意力要求 每个像素关注所有其他像素。由于像素数量的二次方成本,其无法放缩到符合实际的输入尺寸。因此,曾经有研究者尝试过几种近似方法 以便于在图像处理中应用 Transformer。Parmar 等人只在每个 query 像素的局部邻域而非全局应用自注意力,这种局部多头点积自注意力块完全可以代替卷积。在另一种工作中,稀疏 Transformer 采用可放缩的全局自注意力,以便适用于图像。衡量注意力的另一种方法是将其应用于大小不同的块中,在极端情况下仅沿单个轴。许多这种特殊的注意力架构在 CV 任务上显示出很好的效果,但是需要在硬件加速器上有效地实现复杂的工程。

与我们最相关的是 Cordonnier 等人的模型,该模型从输入图像中提取 2×2 大小的块,并在顶部应用完全的自注意力。该模型与ViT 非常相似,但我们的工作进一步证明了 大规模的预训练使普通的 Transformers 能够与 SOTA 的 CNNs 竞争 (甚至更优)。此外,Cordonnier 等人使用 2×2 像素的小块,使模型只适用于小分辨率图像,而我们也能处理中分辨率图像。

将 CNN 与自注意力的形式相结合有很多有趣点,例如增强用于图像分类的特征图,或使用自注意力进一步处理CNN 的输出,如用于目标检测、视频处理、图像分类,无监督目标发现,或统一文本视觉任务。

另一个最近的相关模型是图像 GPT (iGPT),它在降低图像分辨率和颜色空间后对图像像素应用 Transformers。该模型以无监督的方式作为生成模型进行训练,然后可以对结果表示进行微调或线性探测以提高分类性能,在 ImageNet 上达到 72% 的最大精度。

我们的工作增加了在比标准 ImageNet 数据集更大尺度上探索图像识别的论文的数量。使用额外的数据源可以在标准基准上取得 SOTA 的成果。此外,Sun 等人研究了 CNN 性能如何随数据集大小而变化,Kolesnikov、Djolonga 等人从 ImageNet-21k 和JFT-300M 等大规模数据集对 CNN 迁移学习进行了实证研究。我们也关注后两个数据集,但是是训练 Transformers 而非以前工作中使用的基于 ResNet 的模型。

2.3. 方法

在模型设计中,我们尽可能地遵循原始 Transformer (Vaswani 等, 2017)。 这种有意简单设置的优势在于,可扩展的 NLP Transformer 架构及其高效实现几乎可以开箱即用。

在这里插入图片描述

2.3.1. 图像块嵌入

该模型的概述如图 1 所示。标准 Transformer 使用 一维标记嵌入序列 (Sequence of token embeddings) 作为输入。
为处理2D图像,将图像 x ∈ R H × W × C x \in \mathbb{R}^{H \times W \times C} xRH×W×C reshape为一个展平(flatten)的2D patches序列 x p ∈ R N × ( P 2 ⋅ C ) x_p \in \mathbb{R}^{N \times (P^2 \cdot C)} xpRN×(P2C),其中 ( H , W ) (H,W) (H,W)为原始图像分辨率, C C C为原始图像通道数(RGB图像C=3), ( P , P ) (P,P) (P,P)是每个图像patch的分辨率,由此产生图像patch数 N = H W / P 2 N=HW/P^2 N=HW/P2,为Vision Transformer的有效输入序列长度。
Transformer在其所有层中使用恒定的隐向量(latent vector)大小D,将图像patches展平,并使用可训练的线性投影(FC层)将维度 P 2 C P^2C P2C映射为D维,同时保持图像patches数N不变(等式1)。

上述投影输出即图像块嵌入(Patch Embedings) (本质就是对每一个展平后的patch vector x p ∈ R N × ( P 2 ⋅ C ) x_p \in \mathbb{R}^{N \times (P^2 \cdot C)} xpRN×(P2C) 做一个线性变换全连接层 E ∈ R ( P 2 ⋅ C ) × D E \in \mathbb{R}^{(P^2 \cdot C) \times D} ER(P2C)×D,由 P 2 ⋅ C P^2 \cdot C P2C 降维至 D D D维,得到 x p E ∈ R N × D x_p E \in \mathbb{R}^{N \times D} xpERN×D), 好比于NLP中的词嵌入(Word Embedings).

import torch.nn as nn
from timm.models.layers import to_2tuple,to_3tuple


class PatchEmbed(nn.Module):
    """ Image to Patch Embedding"""

    def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
        super().__init__()
        # (H,W)
        img_size = to_2tuple(img_size)
        patch_size = to_2tuple(patch_size)
        # N = (H // P) * (w // P)
        num_patches = (img_size[1] // patch_size[1]) * (img_size[0] // patch_size[0])

        self.img_size = img_size
        self.patch_size = patch_size
        self.num_patches = num_patches

        # 可训练的线性投影-获取输入嵌入
        self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)

    def forward(self, x):
        B, C, H, W = x.shape
        # FIXME look at relaxing size constraints
        assert H == self.img_size[0] and W == self.img_size[1], f"Input image size ({H}, {W}) doesn't match model" \
                                                                f" ({self.img_size[0]}*{self.img_size[1]})"
        # (B, C, H, W) -> (B, D, (H//P), (W//P)) -> (B, D, N) -> (B, N, D)
        #   D=embed_dim=768, N=num_patches=(H//P)*(W//P)
        #   torch.flatten(input, start_dim=0, end_dim=-1)  # 形参:展平的起始维度和结束维度
        # 可见 Patch Embedding 操作 1 行代码 3 步到位
        x = self.proj(x).flatten(2).transpose(1,2)
        return x

2.3.2. 可学习的嵌入(Learnable Embedding)

  类似于BERT的类别token[class],此处为图像patch嵌入序列预设一个可学习的嵌入 z 0 0 = x c l a s s z_0^0=x_{class} z00=xclass,该嵌入在Vision Transformer编码器输出的状态/特征 z L 0 z_L^0 zL0用作图像表示 y y y(等式4)。无论是预训练还是微调,都有一个分类头(Classification Head)附加在 z L 0 z_L^0 zL0之后,从而用于图像分类。在预训练是,分类头为一个单层MLP;在微调时,分类头为单个线性层(多层感知机与线性模型类似,区别在于MLP相对于FC层数增加且引入了非线性激活函数,例如FC+GELU+FC形式的MLP)。

  更明确地,等式 1 中给长度为N的嵌入向量后追加了一个分类向量,用于训练 Transformer 时学习类别信息。假设将图像分为N个图像块 x p 1 , x p 2 , ⋯   , x p N x_p^1,x_p^2, \cdots, x_p^N xp1,xp2,,xpN,输入到Transformer编码器中就有N个向量,但该取哪一个向量用于分类预测呢?都不合适!一个合理的做法是手动添加一个可学习的嵌入向量作为用于分类的类别向量 x c l a s s x_{class} xclass,同时与其他图像块嵌入向量一起输入到Transformer编码器中,最后取追加的首个可学习的嵌入向量作为类别预测结果。所以追加的首个类别向量可以理解为其他N哥图像块寻找的类别信息。从而,最终输入Transformer的嵌入向量最长度为 N + 1 N+1 N+1。可学习嵌入在训练时随机初始化,然后通过训练得到,其具体实现为:

### 随机初始化
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))  # shape = (1, 1, D)
 
### 分类头 (Classifier head)
self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()
 
### 前馈过程 (Forward)
B = x.shape[0]  # Batch Size
 
# 通过 可学习的线性投影 获取 Input Imgaes 的 Patch Embeddings (实现在 3.1 节)
x = self.patch_embed(x)  # x.shape = (B, N, D)
 
# 可学习嵌入 - 用于分类
cls_tokens = self.cls_token.expand(B, -1, -1)  # shape = (B, 1, D)
 
# 按元素相加 附带 Position Embeddings
x = x + self.pos_embed  # shape = (B, N, D) - Python 广播机制
 
# 按通道拼接 获取 N+1 维 Embeddings
x = torch.cat((cls_tokens, x), dim=1)  # shape = (B, N+1, D)

2.3.3. 位置嵌入(Position Embeddings)

  位置嵌入 E p o s ∈ R ( N + 1 ) × D E_{pos} \in \mathbb{R}^{(N+1)\times D} EposR(N+1)×D也被加入图像块嵌入,以保留输入图像块之间的空间位置信息。不同于CNN,Transformer需要位置嵌入来编码path tokens的位置信息,这主要是由于自注意力的的 扰动不变性 (Permutation-invariant),即打乱 Sequence 中 tokens 的顺序并不会改变结果。

  相反,若不给模型提供图像块的位置信息,那么模型就需要通过图像块的语义来学习拼图,这就额外增加了学习成本。ViT 论文中对比了几种不同的位置编码方案:

  1. 无位置嵌入
  2. 1-D 位置嵌入 (1D-PE):考虑把 2-D 图像块视为 1-D 序列
  3. 2-D 位置嵌入 (2D-PE):考虑图像块的 2-D 位置 (x, y)
  4. 相对位置嵌入 (RPE):考虑图像块的相对位置

  最后发现如果 不提供位置编码效果会差,但其它各种类型的编码效果效果都接近,这主要是因为 ViT 的输入是相对较大的图像块而非像素,所以学习位置信息相对容易很多。
  Transformer 原文中默认采用 固定位置编码,ViT 则采用 标准可学习/训练的 1-D 位置编码嵌入,因为尚未观察到使用更高级的 2-D-aware 位置嵌入 (附录 D.4) 能够带来显著的性能提升 (当然,后续的很多 ViT 变体也使用了 2-D 位置嵌入)。在输入 Transformer 编码器之前直接 将图像块嵌入和位置嵌入按元素相加

# 多 +1 是为了加入上述的 class token
# embed_dim 即 patch embed_dim
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim)) 
 
# patch emded + pos_embed :图像块嵌入 + 位置嵌入
x = x + self.pos_embed

论文中也对学习到的位置编码进行了可视化,发现相近的图像块的位置编码较相似,且同行或列的位置编码也相近:

在这里插入图片描述
关于 Transformer 位置嵌入,推荐详见《【机器学习】详解 Transformer_闻韶-CSDN博客_机器学习transformer》的解读!!!

2.3.4. Transformer编码器

  Transformer 编码器 由交替的 多头自注意力层 (MSA, 附录 A) 和 多层感知机块 (MLP, 等式 2, 3) 构成。在每个块前应用 层归一化(Layer Norm) ,在每个块后应用 残差连接(Residual Connection)
在这里插入图片描述

在这里插入图片描述

class Attention(nn.Module):
    def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
        super().__init__()

        self.num_heads = num_heads
        head_dim = dim // num_heads

        self.scale = qk_scale or head_dim ** -0.5

        self. qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop)
        self.proj = nn.Linear(dim, dim)

        # 附带dropout
        self.proj_drop = nn.Dropout(proj_drop)

    def forward(self, x):
        B, N, C = x.shape
        qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[2]  # make torchscript happy (cannot use tensor as tuple)

        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        x = (attn @ v).transpose(1, 2).reshape(B, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)

        return x

  在Transformer中,MSA后跟一个FFN(Feed-forward network),其包含两个FC层,第一个FC层将特征从维度 D D D变换成 4 D 4D 4D,第二个FC将特征从维度 4 D 4D 4D恢复成 D D D,中间的非线性激活函数均采用GeLU(Gaussian Error Linear Unit,高斯误差线性单元)——这实质是一个MLP多层感知机与线性模型类似,区别在于MLP相对于FC层数增加且引入了非线性激活函数,例如FC+GeLU+FC),实现如下:

class Mlp(nn.Module):
    def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
        super().__init__()

        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)

    def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
        
        return x

  一个Transformer Encoder Block就包含一个MSA和一个FFN,二者都有跳跃连接层归一化操作构成MSA BlockMLP Block,实现如下:

# Transformer Encoder Block
class Block(nn.Module):
    def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
                 drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm):
        super().__init__()

        # 后接与MHA的layerNorm
        self.norm1 = norm_layer(dim)
        # MHA
        self.attn = Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias,
                              qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
        # NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()

        # 后接于MLP的Layer Norm
        self.norm2 = norm_layer(dim)
        # hidden layer dims
        mlp_hidden_dim = int(dim * mlp_ratio)
        # MLP
        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)

    def forward(self, x):
        # MHA + Add & Layer Norm
        x = x + self.drop_path(self.attn(self.norm1(x)))
        # MLP + Add & Layer Norm
        x = x + self.drop_path(self.mlp(self.norm2(x)))
        return x

  集合了类别向量、图像块嵌入和位置编码三者到一体的输入嵌入向量后,即可馈入Transformer Encoder。ViT类似于CNN,不断前向通过Transformer Encoder Blocks 串行堆叠构成的Transformer Encoder,最后提取可学习的类别嵌入向量——class token对应的特征用于图像分类。整体前向计算过程如下:
z 0 = [ x c l a s s ; x p 1 E ; x p 2 E ; ⋯   ; x p N E ; ] + E p o s , E ∈ R ( P 2 ⋅ C ) × D , E p o s ∈ R ( N + 1 ) × D ( 1 ) z ′ l = M S A ( L N ( z l − 1 ) ) + z l − 1 , l = 1 ⋯ L ( 2 ) z l = M L P ( L N ( z l ′ ) ) + z ′ l , l = 1 ⋯ L ( 3 ) y = L N ( z L 0 ) ( 4 ) \begin{matrix} \mathbf{z}_0 = [\mathbf{x}_{class};\mathbf{x}_p^1\mathbf{E};\mathbf{x}_p^2\mathbf{E};\cdots;\mathbf{x}_p^N\mathbf{E};]+\mathbf{E}_{pos}, &\mathbf{E} \in \mathbb{R}^{(P^2 \cdot C) \times D},\mathbf{E}_{pos} \in \mathbb{R}^{(N+1) \times D}&(1)\\ \mathbf{z'}_l = MSA(LN(\mathbf{z}_{l}-1))+\mathbf{z}_{l-1}, &l=1 \cdots L&(2)\\ \mathbf{z}_l=MLP(LN(\mathbf{z'_l}))+\mathbf{z'}_l, &l=1 \cdots L&(3) \\ \mathbf{y}=LN({\mathbf{z}_L^0})& &(4)\\ \end{matrix} z0=[xclass;xp1E;xp2E;;xpNE;]+Epos,zl=MSA(LN(zl1))+zl1,zl=MLP(LN(zl))+zl,y=LN(zL0)ER(P2C)×D,EposR(N+1)×Dl=1Ll=1L(1)(2)(3)(4)

  • 等式1:由图像块嵌入 x p i E , i ∈ 1 , 2 , ⋯   , N x_p^iE, i \in 1,2, \cdots, N xpiE,i1,2,,N、类别向量 x c l a s s x_{class} xclass和位置编码 E p o s E_{pos} Epos构成嵌入输入向量 z 0 z_0 z0
  • 等式2:由多头注意力机制、层归一化和跳跃连接(Layer Norm & Add)构成的MSA Block,可重复 L L L个,其中第 l l l个输出为 z l ′ z'_l zl
  • 等式3:由前馈网络(FFN)、层归一化和跳跃连接(Layer Norm & Add)构成的MLP Block,可重复 L L L个,其中第 l l l个输出为 z l z_l zl
  • 等式4:由层归一化(Layer Norm)和分类头(MLP or FC)输出图像表示 y \mathbf{y} y

关于Transformer编码器的结构,详见【机器学习】详解Transformer

2.3.5. ViT张量维度变化举例

在这里插入图片描述

  1. 输入图像 (input images) 的 shape = (b = b, c = 3, h = 256, w = 256)。
  2. 输入图像 (input images) 被切分 (Split / Divide) 并展平 (Flatten) 为:batch size 仍为 b,通道数 c = 3、尺寸 P = 32、个数 N = (256×256) / (32×32) = 64 的图像块 (Patch),每个图像块 (Patch) 均有 P²c = 32×32×3 = 3072 个像素。
  3. 图像块 (Patch) 馈入线性投影层 (Linear Projection),得到个数/长度 (length) 为 N = 64、像素数/大小/维度 (dimension) 为 D = (32×32×1) = 1024 的图像块嵌入 (Patch Embedding)。
  4. 每个图像块嵌入 (Patch Embedding) 按元素加 (Element-wise Summary) 入位置向量/嵌入后,尺寸仍为 N×D = 64×1024
  5. 具有位置嵌入的图像块嵌入 (Patch Embedding) 再于长度 (length) 维度 拼接 (Concat) 一个用于预测分类结果的 1×1024 可学习嵌入/向量,构成大小为 65×1024 完整嵌入 (长度 (length) N+1 = 64+1 = 65)。
  6. 完整嵌入输入编码器经过一系列前向处理后,得到尺寸仍为 N×D = 65×1024 的输出。
    当然,事实上,根据 3.1 节的代码实现,Patch Embedding 只需依次经过 Conv + Flatten + Transpose 这一行操作得到。上述图示和描述仅仅是用于更直观而细粒度地展现馈入 Encoder 的整体 Embedding 的形成过程。

2.3.6. 归纳偏置与混合架构

  归纳偏置 (Inductive bias):注意到,Vision Transformer 的图像特定归纳偏置比 CNN 少得多。在 CNN 中,局部性、二维邻域结构 和 平移等效性 存在于整个模型的每一层中而在 ViT 中,只有 MLP 层是局部和平移等变的,因为自注意力层都是全局的。二维邻域结构 的使用非常谨慎:在模型开始时通过将图像切分成块,并在微调时调整不同分辨率图像的位置嵌入 (如下所述)。此外,初始化时的位置嵌入不携带有关图像块的 2D 位置的信息,图像块之间的所有空间关系都必须从头开始学习。

  混合架构 (Hybrid Architecture)作为原始图像块的替代方案,输入序列可由 CNN 的特征图构成。在这种混合模型中,图像块嵌入投影 E E E (等式 1) 被用在 经 CNN 特征提取的块 而非 原始输入图像块。作为一种特殊情况,块的空间尺寸可以为 1 × 1 1 \times 1 1×1,这意味着输入序列是通过 简单地将特征图的空间维度展平并投影到 Transformer 维度 获得的。然后,如上所述添加了分类输入嵌入和位置嵌入,再将三者组成的整体馈入 Transformer 编码器。简单来说,就是先用 CNN 提取图像特征,然后由 CNN 提取的特征图构成图像块嵌入。由于 CNN 已经将图像降采样了,所以块尺寸可为 1 × 1 1 \times 1 1×1
关于归纳偏置,推荐详见《【机器学习】浅谈 归纳偏置 (Inductive Bias)_闻韶-CSDN博客_归纳偏置》 的解读。

2.3.7. 微调及更高分辨率

  通常,我们在大型数据集上预训练ViT,并对(更小的)下游任务进行微调。为此,我们移除了预训练的预测头部,换为一个零值初始化 D × K D \times K D×K前馈层/全连接层,其中 K K K是下游任务的类别数
  用比预训练时更高的图像分辨率进行微调通常更有益当提供更高分辨率的图像时,需保持图像patchs大小相同,此时有效图像patchs数变多,从而有效序列长度会变长。Vision Transformer可处理任意序列长度(取决于内存限制),但预训练的位置嵌入(pos_embed)可能不再有意义,因为当前的位置嵌入无法与之一一对应了。因此,根据它们在原图中的位置,对预训练的位置嵌入执行2D插值,以扩展到微调尺寸。注意,此分辨率调整和图像patches提取是将有关图像2D结构的归纳偏执手动注入Vision Transformer的唯一点。

在这里插入图片描述

import math
import torch
import torch.nn.functional as F
import logging
_logger = logging.getLogger(__name__)


def resize_pos_embed(posemb, posemb_new):
    # Rescale the grid of position embeddings when loading from state_dict. Adapted from
    # https://github.com/google-research/vision_transformer/blob/00883dd691c63a6830751563748663526e811cee/vit_jax/checkpoint.py#L224
    _logger.infor('Resized position embedding: %s to %s', posemb.shape, posemb_new.shape)
    ntok_new = posemb_new.shape[1]

    # 除去 class token 的pos_embed
    posemb_tok, posemb_grid = posemb[:, :1], posemb[0, 1:]
    ntok_new -= 1
    gs_old = int(math.sqrt(len(posemb_grid)))
    gs_new = int(math.sqrt(ntok_new))
    _logger.info('Position embedding grid-size from %s to %s', gs_old, gs_new)

    # 把pos_embed 变换到2-D 维度再进行插值
    posemb_grid = posemb_grid.reshape(1, gs_old, gs_old, -1).permute(0, 3, 1, 2)
    posemb_grid = F.interpolate(posemb_grid, size=(gs_new, gs_new), mode='bilinear')
    posemb_grid = posemb_grid.permute(0, 2, 3, 1).reshape(1, gs_new * gs_new, -1)
    posemb = torch.cat([posemb_tok, posemb_grid], dim=1)
    return posemb

  但这种情形一般会造成性能少许损失,可通过微调模型解决。另外。论文CPVT通过Implicit Conditional Position Encoding来解决问题(插入Conv来饮食编码位置信息,Zero-padding 让Conv学习到绝对位置信息)。

2.3.8. 超参数

如下的ViT主要超参数直接影响模型参数及计算量:

Layers:Encoder Block数量
Hidden Size D:隐藏层特征大小,其在各Encoder Block保持一致
MLP Size: MLP特征大小,通常设为4D
Heads:MSA中的heads数量
Patch Size: 模型输入的Patch size, ViT中共有两个设置: 14 × 14 14 \times 14 14×14 16 × 16 16 \times 16 16×16,该参数仅影响计算量

  类似BERT,ViT原文共定义了3中不同大小的模型:Base、Large、Huge,其对应的模型参数不同,如下所示。如ViT-L/16表示采用Large结构,输入Patch Size= 16 × 16 16 \times 16 16×16
在这里插入图片描述
  事实上,timm还实现了Tiny,Small,Base,Large等多种结构.

2.4. 实验

  ViT并不像CNN那样具有Inductive Bias,若直接在ImageNet上训练,同level的ViT效果不如ResNet。但若先在较大的数据集上预训练,然后再对特定的较小数据集进行微调,则效果优于ResNet。比如ViT在Google私有的300M JFT数据集上预训练后,在ImageNet上的最好的Top-1 Acc可达88。55%,这在当时已和ImageNet上的SOTA相当了(Noisy Student EfficientNet-L2效果为88。5%,Google最新的SOTA是Meta Psedu Labels,效果可达90.2%):
在这里插入图片描述
那么 ViT 至少需要多大的数据量才能比肩 CNN 呢?结果如下图所示。可见预训练的数据量须达到 100M 时才能凸显 ViT 的优势。Transformer 的一个特色其 Scalability:当模型和数据量提升时,性能持续提升。在大数据下,ViT 可能会发挥更大的优势。
在这里插入图片描述
Transformer、ResNet 与 Hybrid Transformer 三者的性能变化比较:
在这里插入图片描述
此外,论文分析了 不同 Layers 的 Mean Attention Distance,其类比于 CNN 的感受野。结果表明:前面层的 “感受野” 虽然差异很大,但总体相比后面层 “感受野” 较小而模型后半部分 “感受野” 基本覆盖全局,和 CNN 比较类似,说明 ViT 也最后学习到了类似的范式
在这里插入图片描述
当然,ViT 还可根据 Attention Map 来可视化,得知模型具体关注图像的哪个部分,从结果上看比符合实际:

在这里插入图片描述

参考:【深度学习】详解 Vision Transformer (ViT)
参考:极智AI:深度解析ViT——视觉Transformer的革命性意义与实现细节
参考:ViT:视觉Transformer backbone网络ViT论文与代码详解
参考:https://github.com/dk-liang/Awesome-Visual-Transformer
参考:搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了(二)
参考:可视化VIT中的注意力

Vision Transformer(ViT)是一种基于Transformer架构的深度学习模型,用于处理计算机视觉任务。它将图像分割成一系列的图像块,并将每个图像块作为输入序列传递给Transformer编码器。每个图像块通过一个线性投影层转换为向量表示,并与位置嵌入向量相结合,然后输入到Transformer编码器中进行处理。Transformer编码器由多个自注意力层和前馈神经网络层组成,用于学习图像中的全局和局部特征。最后,通过一个线性分类器对编码器的输出进行分类。 Vision Transformer的优点是能够在没有使用传统卷积神经网络的情况下,实现对图像的高质量特征提取和分类。它在一些计算机视觉任务上取得了与传统方法相媲美甚至更好的性能,例如图像分类、目标检测和语义分割等任务。 以下是一个使用Vision Transformer进行图像分类的示例代码[^1]: ```python import torch import torch.nn as nn from torchvision import transforms from torchvision.models import resnet50 from vit_pytorch import ViT # 加载预训练的Vision Transformer模型 model = ViT( image_size = 224, patch_size = 16, num_classes = 1000, dim = 768, depth = 12, heads = 12, mlp_dim = 3072, dropout = 0.1, emb_dropout = 0.1 ) # 加载预训练的权重 model.load_from('vit_weights.pth') # 图像预处理 transform = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) # 加载图像并进行预处理 image = Image.open('image.jpg') image = transform(image).unsqueeze(0) # 使用Vision Transformer进行图像分类 output = model(image) _, predicted_class = torch.max(output, 1) # 输出预测结果 print('Predicted class:', predicted_class.item()) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落花逐流水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值