VIT讲解

本文介绍了一种新的方法,即纯Transformer架构在大规模图像识别中的应用,无需依赖卷积网络。VisionTransformer(ViT)通过将图像划分为16x16的patch并使用标准Transformer进行处理,预训练在大型数据集上表现优秀,超越了先进技术。研究还探讨了Transformer在计算机视觉任务的潜力和未来挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE

会议:Computer Vision and Pattern Recognition         

时间:2020-10-22

Attention is all you need 一经发表,Transformer架构问世,如今Transformer架构已成为自然语言处理任务的事实标准,但其在计算机视觉中的应用仍然有限。在视觉领域,注意力机制通常与卷积网络结合使用,或者用来替代卷积网络的某些组件,但保持其整体结构。在本文中我们展示了对CNN的这种依赖是不必要的,直接应用于图像块序列的纯Transformer可以在图像分类任务中表现出色。

从图中可以看到VIT模型的总体预览,以下分别讨论其重点部分:

Patch Embedding:对于图像数据而言,其数据格式为[C, H, W]是三维矩阵,Transformer想要的输入形状为[L, D]是二维的,其中L是序列长度,D为每个token的维度。所以需要先通过一个Embedding层来对数据做个变换。如上图所示,首先将一张图片按给定大小分成一堆Patches,将输入图片(224x224)按照16x16大小的Patch进行划分,划分后会得到 N=(224/16)^2=196个Patches。每一个patch视为一个token,故patches数量就相当于序列长度。接着通过线性映射将每个Patch映射到一维向量中。在代码实现中,直接通过一个卷积层来实现。直接使用一个卷积核大小为16x16,步距为16,卷积核个数为768的卷积来实现,得到无重复重叠的Patches,即[3, 224, 224] -> [768, 14, 14],然后把H以及W两个维度展平后再改变形状得到[768 , 14, 14] -> [768, 196]->[196, 768],此时变成了一个二维矩阵[N, D],正是Transformer想要的。(ViT-B/16为例)

class PatchEmbed(nn.Module):
    """
    2D Image to Patch Embedding
    """
    def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768, norm_layer=None):
        super().__init__()
        img_size = (img_size, img_size)
        patch_size = (patch_size, patch_size)
        self.img_size = img_size
        self.patch_size = patch_size
        self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
        # 将图片分割成无重叠的16X16的patches,每一张patch视为一个token,patches数量就是序列长度
        self.num_patches = self.grid_size[0] * self.grid_size[1] 

        self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)  # [3, 224, 224] -> [768, 14, 14]
        self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()

    def forward(self, x):
        B, C, H, W = x.shape
        assert H == self.img_size[0] and W == self.img_size[1], \
            f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."

        # flatten: [B, C, H, W] -> [B, C, HW]
        # transpose: [B, C, HW] -> [B, HW, C]
        x = self.proj(x).flatten(2).transpose(1, 2)   # [B, 768, 14, 14] ->[B, 768,196]-> [B, 196, 768]
        x = self.norm(x)
        return x

[class] Embedding / Position Embedding:在输入Transformer Encoder之前注意需要加上[class]token以及Position Embedding。 在原论文中,作者说参考BERT,在刚刚得到的一堆tokens中插入一个专门用于分类的[class]token,这个[class] token是一个可训练的参数,数据格式和其他token一样都是一个向量,就是一个长度为768的向量,与之前从图片中生成的patch tokens拼接在一起,Cat([1, 768], [196, 768]) -> [197, 768],最后的输出结果用来预测类别。这样一来,Transformer相当于一共处理了 N+1 个维度为 D 的token,并且只有最后一个token的输出用来预测类别。这种体系结构迫使patch token和class token之间传播信息。Position Embedding的作用是由于Transformer对输入的批量数据进行并行操作,失去了RNN固有的时间顺序特性,故需要加入位置编码来保证图片的位置信息不发生错乱。ViT 中的位置编码没有采用原版 Transformer 中的 sincos编码,而是直接设置为可学习的 Positional Encoding,采用的是一个可训练的参数(1D Pos. Emb.),是直接叠加在tokens上的(add),所以shape要一样。刚刚拼接[class]token后shape是[197, 768],那么这里的Position Embedding的shape也是[197, 768]。

Transformer Encoder:在本文中采用纯Transformer结构,这里使用了Transformer中的Encoder(编码器),其中主要有两大组成部分,即Multi-Head Attention 和MLP。这里我们重点讲解MSA。

所谓自注意力,也就是说我们有一个序列X,然后我们想要算出X对X自己的注意力,也即X中的每个时间点与其余时间点的相关性(在注意力机制中表现为相似性),从而得到一个注意力矩阵。算出注意力矩阵后再将之用在序列X上来得到各个时间点的加权和,也即将其余各个时间点的信息都融合到了各个时间点中。

注意力机制通过注意力汇聚将查询(自主性提示)和(非自主性提示)结合在一起,实现对(感官输入)的选择倾向。注意力Attention机制的最核心的公式为:

其中,Q为Query、K为Key、V为Value。Q、K、V是从哪儿来的呢?Q、K、V其实都是从同样的输入矩阵X线性变换而来的。所谓的QKV本质上是代表了三个独立的矩阵,他们都是我们原本的序列X做了不同的线性变换之后的结果,都可以作为X的代表。然后为了得到注意力矩阵,我们肯定是先需要两个X的代表矩阵,因为计算相似度至少要有两个向量才可以计算,因此Q和K就是用于这个工作的。计算相似度有很多方式,可以直接做点乘(一般采用的都是Scaled Dot-Product Attention),也可以用个MLP来计算。然后再将结果经过一个softmax来保证输出的是attention weight,也即保证和为1,否则用这个attention matrix的时候数据的scale会不断的变化。得到了attention matrix之后要做的就是将这个weight用在X上,也即我们剩下的那个vector V身上。做法就是将attention matrix和V相乘求和即可得到最终结果。

以下是将图像作为输入,MSA中各部分形状变化:

这里的C对应论文下图中的D,N(num_patches+1)对应HW,C/n对应Dh。

Transformer最开始应用在NLP方向,其要求的输入形状为[batch_size, sequence_length, embedding_dim],这里的输入的token为一张patches,故序列长度为patches数量,因为class token的存在,序列长度为patches+1。本文中先把q,k,v看成一个整体放在矩阵中,这里的qkv直接通过一个Linear层得到,输出维度为3*dim,所以其权重矩阵为U[dim , 3*dim]。后续通过改变形状得到对应的qkv[3, batch_size, num_heads, num_patches + 1, embed_dim_per_head],因为q,k,v形状相同,故通过索引分别取出每一个[batch_size, num_heads, num_patches + 1, embed_dim_per_head]即为Q,K,V。其余实现和原Transformer中的MSA没有变化。

class MultiHeadAttention(nn.Module):
    def __init__(self,
                 dim,   # 输入token的dim
                 num_heads=8,
                 qkv_bias=False,
                 qk_scale=None,
                 attn_drop_ratio=0.,
                 proj_drop_ratio=0.):
        super(Attention, self).__init__()
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk_scale or head_dim ** -0.5      # d**0.5
        self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
        self.attn_drop = nn.Dropout(attn_drop_ratio)
        self.proj = nn.Linear(dim, dim)
        self.proj_drop = nn.Dropout(proj_drop_ratio)

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

        # transpose: -> [batch_size, num_heads, embed_dim_per_head, num_patches + 1]
        # @: multiply -> [batch_size, num_heads, num_patches + 1, num_patches + 1]
        attn = (q @ k.transpose(-2, -1)) * self.scale
        attn = attn.softmax(dim=-1)
        attn = self.attn_drop(attn)

        # @: multiply -> [batch_size, num_heads, num_patches + 1, embed_dim_per_head]
        # transpose: -> [batch_size, num_patches + 1, num_heads, embed_dim_per_head]
        # reshape: -> [batch_size, num_patches + 1, total_embed_dim]
        x = (attn @ v).transpose(1, 2).reshape(B, N, C)
        x = self.proj(x)
        x = self.proj_drop(x)
        return x

实验

数据集:  the ILSVRC-2012 ImageNet dataset with 1k classes                  

                ImageNet-21k with 21k classes                  

                JFT with 18k classes

在论文的Table1中有给出三个模型(Base/ Large/ Huge)的参数。其中的Layers就是Transformer Encoder中重复堆叠Encoder Block的次数,Hidden Size就是对应通过Embedding层后每个token的dim(向量的长度),MLP size是Transformer Encoder中MLP Block第一个全连接的节点个数(是Hidden Size的四倍),Heads代表Transformer中Multi-Head Attention的heads数。

Table 2 表明在JFT-300M上预训练的较小的VIT-L/16模型在所有任务上都优于BiT-L(在相同的数据集上预先训练),同时需要更少的计算资源来训练。较大的模型 ViT-H/14 进一步提高了性能,尤其是在更具挑战性的数据集——ImageNet、CIFAR-100 和 VTAB 。

Table 6 是论文用来对比ViT,Resnet(使用的卷积层和Norm层都进行了修改)以及Hybrid模型的效果。通过对比发现,在训练epoch较少时Hybrid优于ViT,但当epoch增大后ViT优于Hybrid。

我们探索了Transformer在图像识别中的直接应用。与在计算机视觉中使用自注意力的先前工作不同,除了初始提取patch步骤之外,我们不会将特定于图像的归纳偏差引入架构中。相反,我们将图像解释为一系列patches,并通过 NLP 中使用的标准 Transformer 编码器对其进行处理。当与大型数据集的预训练相结合时,这种简单的但可扩展的策略效果很好。因此,Vision Transformer 在许多图像分类数据集上超过了最先进的技术,同时预训练成本相对较低。虽然这些初步结果令人鼓舞,但仍有许多挑战。一种是将ViT应用于其他计算机视觉任务,如检测和分割。我们的结果,加上Carion等人(2020)的结果,表明了这种方法的前景。另一个挑战是继续探索自监督的预训练方法。我们最初的实验表明自监督预训练有所提高,但自监督预训练和大规模监督预训练之间仍然存在很大差距。最后,ViT的进一步缩放可能会导致性能的提高。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值