## Abstract Transformer结构已经成为受限。在CV里面,要么将attention结构结合CNN,要么区替代一些CNN组件保留大体结构。文章展示出对CNN结构的依赖不是必须的,一个纯接受图片patch序列的transormer结构也可以在图像分类任务中获得一个不错的效果。当在大规模数据集进行预训练,然后迁移到小规模的数据集上面的时候,VIT也获得一个SOTA水平的效果。 ## 1 Introduction self-attention的结构,尤其是transformer结构,成为NLP任务里面主流选择。主流方法是先在大规模语料库进行预训练,然后在特定的任务数据集上fine-tune.由于Transformer结构计算的高效性,因此模型参数规模可以做得很大。随着模型和数据集增长,并没有出现明显的性能饱和。 在计算机视觉领域CNN仍然占据主导地位,受到NLP成功的激励,许多工作尝试将CNN结构和self-attention结构结合起来,还有些完全替代CNN结构。但是后者尽管理论上面可行,但是由于特殊的attention范式,在现代GPU上面还没有进行有效的拓展。因此在大规模的图像分类任务中,CNN像resnet结构仍然占据主导地位SOTA. 受到Transformer在NLP任务成功的启发,我们直接在图片上应用标准的transformer结构,尽可能少的修改。我们将图片分成一个个的patch,并且提供这些patch的位置embedding,将其输入到transformer结构里面。图片patch和NLP任务中的word(token)相同的对待。训练这个图片分类采用监督学习的方式。 当在中等规模的数据集(如ImageNet)上进行训练时,没有强正则化,这些模型的精度比同等规模的ResNets低几个百分点。这个看似令人沮丧的结果可能是意料之中的,Transformer缺少平移等方差和局部性,因此在数据量不足的情况下进行训练时不能很好地推广。然而,如果在更大的数据集(14M-300M图像)上训练模型,图像会发生变化。我们发现大规模的训练战胜了归纳偏见。我们的视觉转换器(ViT)在充分规模的预训练和转移到具有较少数据点的任务时获得了优异的结果。 ## 2 Related Work Transformer在2017年提出用于机器翻译,并且成为NLP领域SOTA方法。基于Transformer的方法通常在大型语料库进行与训练,然后根据手头任务进行微调。BERT采用子监督的方式,GPT通过语言建模作为预训练。 图像进行self-attention的朴素应用将要求每个像素对每个其他像素进行处理, 像素数量的二次代价,这不能按实际的输入大小缩放。因此,为了在图像处理的背景下应用变形,过去已经尝试了几种近似方法: - 只应用局部邻域的自我关注,而不是全局关注, 局部采用多头注意力代替卷积操作 - 将注意力机制应用在不同大小的feature map - 采用2*2的patch,但是该方式只能适用于分辨率较低的图片 ## 3 Method  **模型总览图** ------------ 模型将图像分割成固定大小的小块,对每个小块进行线性嵌入,添加位置嵌入,并将得到的矢量序列输入标准Transformer编码器,最后通过正常的全连接层对图片进行分类。为了执行分类,我们使用标准方法,即在序列中添加一个额外的可学习的“分类标记”。 ViT主要包括以下几个部分:ViT主要包括以下几个部分:**数据处理部分,Patch编码,位置编码,分类等模块**。 ### 3.1 输入数据 设H, W是原始图像的高和宽,C是原始图像的通道数,P是分成patch的长和宽. 图片分块patch的数目N如下: $$N=H \times W / (P \times P)$$ 将每个patch看成NLP中的token维度,则patch长度为: $$P \times P \times C$$ 一共N个patch,那么总输入维度是: $$N \times P \times P \times C$$ 将图片输入reshape成以上维度即可。 对于标准的Transformer模块,要求输入的是token向量,是一个二维矩阵[num_tokens, token_dim]. **数据形状演示** 在代码实现中,通过一个卷积层实现kerner_size=16,stride=16,卷积核数量768 [3, 224, 224] -> [768, 14, 14] 然后采用flatten摊平, transpose转职即可。 [768, 14, 14] -> [768, 196] -> [196, 768] ### 3.2 Patch编码 接着对每个向量都做一个线性变换(即全连接层),压缩维度为D,这里我们称其为 Patch Embedding。在代码里是初始化一个全连接层,输出维度为dim,然后将分块后的数据输入 ```python self.patch_to_embedding = nn.Linear(patch_dim, dim) # forward前向代码 x = rearrange(img, 'b c (h p1) (w p2) -> b (h w) (p1 p2 c)', p1=p, p2=p) x = self.patch_to_embedding(x) ``` ### 3.3 位置编码 vit在patch嵌入位置编码以保留位置信息。使用标准的可学习1D位置嵌入,因为没有观察到使用更高级的2d感知位置嵌入有显著的性能增益。(The difference in how to encode spatial information is less important)所得到的嵌入向量序列作为编码器的输入 ```python self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim)) ``` 假设我们按照论文切成了9块,但是在输入的时候+1变成了10个向量。这是人为增加的一个向量。因为传统的Transformer采取的是类似seq2seq编解码的结构。而ViT只用到了Encoder编码器结构,缺少了解码的过程,假设你9个向量经过编码器之后,你该选择哪一个向量进入到最后的分类头呢?因此这里作者给了额外的一个用于分类的向量,与输入进行拼接。同样这是一个可学习的变量。 ### 3.4 分类头 ```python self.mlp_head = nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, mlp_dim), nn.GELU(), nn.Dropout(dropout), nn.Linear(mlp_dim, num_classes) ) ``` ### 3.5 模型整体架构 ```python class ViT(nn.Module): def __init__(self, *, image_size, patch_size, num_classes, dim, depth, heads, mlp_dim, channels=3, dropout=0., emb_dropout=0.): super().__init__() assert image_size % patch_size == 0, 'Image dimensions must be divisible by the patch size.' num_patches = (image_size // patch_size) ** 2 patch_dim = channels * patch_size ** 2 assert num_patches > MIN_NUM_PATCHES, f'your number of patches ({num_patches}) is way too small for attention to be effective (at least 16). Try decreasing your patch size' self.patch_size = patch_size self.pos_embedding = nn.Parameter(torch.randn(1, num_patches + 1, dim)) self.patch_to_embedding = nn.Linear(patch_dim, dim) self.cls_token = nn.Parameter(torch.randn(1, 1, dim)) self.dropout = nn.Dropout(emb_dropout) self.transformer = Transformer(dim, depth, heads, mlp_dim, dropout) self.to_cls_token = nn.Identity() self.mlp_head = nn.Sequential( nn.LayerNorm(dim), nn.Linear(dim, mlp_dim), nn.GELU(), nn.Dropout(dropout), nn.Linear(mlp_dim, num_classes) ) def forward(self, img, mask=None): p = self.patch_size x = self.patch_to_embedding(x) b, n, _ = x.shape cls_tokens = repeat(self.cls_token, '() n d -> b n d', b=b) x = torch.cat((cls_tokens, x), dim=1) x += self.pos_embedding[:, :(n + 1)] x = self.dropout(x) x = self.transformer(x, mask) x = self.to_cls_token(x[:, 0]) return self.mlp_head(x) ``` ### 3.6 数据流总览 1. 一个图片224x224,分成了49个32x32的patch; 2. 对这么多的patch做embedding,成49个128向量; 3. 再拼接一个cls_tokens,变成50个128向量; 4. 再加上pos_embedding,还是50个128向量; 5. 这些向量输入到transformer中进行自注意力的特征提取; 6. 输出的是50个128向量,然后对这个50个求均值,变成一个128向量; 7. 然后线性层把128维变成2维从而完成二分类任务的transformer模型 ### 4 CNN vs Transformer 在机器学习中,很多学习算法经常会对学习的问题做一些假设,这些假设就称为**归纳偏置**(Inductive Bias)。归纳偏置这个译名可能不能很好地帮助理解,不妨拆解开来看:归纳(Induction)是自然科学中常用的两大方法之一(归纳与演绎, induction and deduction),指的是从一些例子中寻找共性、泛化,形成一个比较通用的规则的过程;偏置(Bias)是指我们对模型的偏好。在CNN的inductive bias应该是locality和spatial invariance,即空间相近的grid elements有联系而远的没有,和空间不变性(kernel权重共享)。 CNN 强大的归纳偏置使得即使使用非常少的数据也能实现高性能,但当存在大量数据时,这些归纳偏置就可能会限制模型。相比之下,Transformer 具有最小的归纳偏置,这说明在小数据设置下是存在限制的,但同时这种灵活性让 Transformer 在大数据上性能优于 CNN。在ViT中,只有MLP层是局部的、平移等变的,而自注意层是全局的。二维邻域结构的使用非常少:在模型的开始通过将图像分割成小块,在微调时间调整不同分辨率图像的位置嵌入(如下所述)。除此之外,初始化时的位置嵌入不携带关于patch二维位置的信息,并且patch之间的所有空间关系都需要从头学习。 可以说cnn的kernel是一种特殊的attention,attention理论上学习成任何形状的kernel。因为规定了kernel的形状,因此cnn训练成本较低,可以在较少数据集达到理想效果,attention需要更多数据进行训练,但是其拥有更好的性能,更不容易出现性能饱和的现象。 ### 5 总结及未来发展 与之前在计算机视觉中使用self-attention的工作不同,除了最初的patch提取步骤外,没有在架构中引入特定图像的归纳偏差。相反将图像解释为一系列patch,并使用NLP中使用的标准Transformer编码器对其进行处理。这种简单的、可扩展的策略在与大型数据集的预训练相结合时效果惊人地好。因此,Vision Transformer在许多图像分类数据集上都达到或超过了目前的技术水平,而且预训练成本相对较低。 虽然这些初步结果令人鼓舞,但仍存在许多挑战。一是将ViT应用于其他计算机视觉任务,如检测和分割。我们的研究结果,加上Carion等人(2020)的研究结果,表明了这种方法的前景。另一个挑战是继续探索自我监督的训练前方法。初步实验表明,自监督前训练的效果有所改善,但与大规模监督前训练相比仍存在较大差距。作者还对自监督的掩蔽patch预测进行了初步的探索,模拟了BERT中使用的掩蔽语言建模任务。该方式比监督训练表现低4%,但是比从头训练高2%。 最后编辑:2024年04月23日 ©著作权归作者所有 赞 0 分享
最新回复