Vision transformer学习笔记

1. transformer基本架构

在了解cv中的transformer之前,我们先来复习一下transformer的基本架构

1.1 编码器与解码器

我们可以将网络分成两个部分,编码器与解码器。

        编码器:将输入编程成中间表达形式(抽取特征)

        解码器:将中间表示(特征)解码成任务需要的输出

从编解码的角度来理解神经网络,就是说我先通过编码器将原始数据编码成机器学习比较好理解的结构,再通过解码器解码成你需要的任务输出。

对应到CNN,就是说前面使用卷积层进行特征抽取称为编码器,后面将抽取的特征变为所需的输出称为解码器。

Vision transformer学习笔记_第1张图片

对应到RNN中对文本进行处理,同样可以分为编码器和解码器

Vision transformer学习笔记_第2张图片

我们将上述结构抽象成一个更一般的结构

一个模型被分成两块:

        解码器处理输入

        解码器生成输出

Vision transformer学习笔记_第3张图片

1.2 transformer块

2. vision transformer

        Vision Transformer(ViT)是视觉Transformer的开山之作,其开创性的工作是:纯粹地使用了标准的transformer结构(简单、扩展性很好),实现了能够媲美CNN中SOTA模型的效果(在这之前也有一些工作与它想法类似,但效果很差,VIT论文中给出的解释是transformer 没有像CNN那样包含图片的归纳偏置,因此,需要给它喂很大量的数据做训练,让它自己学到充分的特征,效果才能好)。

Vision transformer学习笔记_第4张图片

        回忆一下,在transformer里面计算自注意力时,我们是将一个可变长的序列传进去,每个元素都要跟每个元素去做互动,算得一个注意力得分,也就是一个自注意力图,然后用这个自注意力图去做加权平均,最后得到输出,因为每个元素之间都要做互动,所以这个计算复杂度是跟序列长度n成平方关系的,目前硬件能支持的序列长度,一般也就是几百或者上千,如在bert模型中,序列长度也就是512。

        换到视觉领域,如果想使用transformer的话,需要解决的第一个问题就是,如何把二维的图片变成一维的序列。

        最直观的方式就是把图片按像素点拉直成一维的,但这样计算复杂度太高,比如我们做图片分类的一般大小是224x224,拉直以后序列长度就是224x224=50176,已经接近bert的100倍,显然是不可行的。

那么怎么去降低序列长度呢?

        vision transformer之前在视觉领域里使用注意力机制

                1. 将注意力机制加到cnn里面,比如SE模块、PAM模块、BEAM模块,他是使用特征图作为输入,如resnet50最后一层特征图大小7x7,拉皮以后长度也只有49,就降低了这个序列长度

                2. 孤立自注意力:类似于卷积,计算注意力的时候,不对整个图片一起计算,而是选择一个小窗口,基于这个局部窗口去计算,这样复杂度也降低了

                3. 轴自注意力:先在高度这个维度上做一次自注意力,再在宽度这个维度上做一次自注意力

        对于第一种方法,本质上主体还是卷积神经网络,只是加了一些插件,对于后两种方法,虽然理论计算复杂度很低,但由于尚未针对性在gpu硬件上做一些计算优化,因此很难能用这两种思路训练出一个大模型

Vision transformer学习笔记_第5张图片

        vision transformer的想法就是把224x224的图片打成一个个16x16大小patch,然后每个patch作为一个基本元素,这样W=H=224/16=14,拉直以后的序列长度就只有14 x 14 = 196了,这样就直接可以将原图传进transformer进行训练了

        224x224大小的RGB图片,拆分成16x16的patch,每个patch的维度就是16x16x3=768,总序列维度就是196x768,实现起来也很简单,就是对图片做一个 kernel_size = stride = 要拆分的patch大小,out_channle=768的卷积操作就可以了

代码实现

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])
        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)
        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)
        x = self.norm(x)
        return x

Vision transformer学习笔记_第6张图片

  1. 先将batchsize x 224x244x3的图片打成16x16大小的patch,得到b x 196x768(b是batch_size的维度)的向量

  2. 再经过一个全连接层(768x768)做线性投影,还是一个bx196x768的向量

  3. 参照NLP中的bert模型,加上一个extra learnable class embedding作为class token(1x768),向量变为bx197x768,在给每个位置都加上一个位置编码P,向量变为bx197x768,传进transformer块

  4. 先经过一个LN,向量维度仍为bx197x768,再经过一个多头自注意力和残差连接 ,向量维度仍为bx197x768,再经过LN后,经过第一个全连接层(768x3072,扩大四倍),向量维度变为bx197x3072,再经过第二个全连接层(3072x768),向量维度变回bx197x768,最后再跟一个残差连接,向量维度仍为bx197x768。

  5. 经过L次上述的transformer块后,只取第0位class token位置对应的向量作为特征(bx1x768),像CNN做分类任务一样,接一个全连接层后接softmax最后得到分类

根据参数的不同VIT有小、中、大三个版本

Vision transformer学习笔记_第7张图片

一些实验和讨论:

  1. class token:使用class token是为了和nlp中的方式保持一致,也可以像cnn那样,对所有的特征做一个全局平均池化(将197x768池化成1x768),再传进全连接层进行分类,可以看到,在调好参的情况下,区别不大

Vision transformer学习笔记_第8张图片

        2. 位置编码使用一维、二维或相对位置编码区别不大

Vision transformer学习笔记_第9张图片

        3. transformer没有内含任何的归纳偏置,位置编码也是随机初始化的(只保证了每个位置的值不一样),所有的信息都得从头学,因此在中小数据集上的表现不如CNN,但注意力机制关注的是全局信息,它具有强大学习能力和扩展性,所以在更大数据集上表现更好

Vision transformer学习笔记_第10张图片

        4. 混合模型:前面是将图片打成patch缩小尺寸,混合模型直接利用CNN来缩小尺寸,即将CNN的特征图作为输入传入transformer。

在计算量相同的情况下,对于更小的模型,混合模型表现好于VIT,但对于更大的模型,区别不大

Vision transformer学习笔记_第11张图片

        5. CNN在微调的时候如果使用更大的图片尺寸,通常效果会更好一些,但VIT难以改变输入尺寸的大小。虽然transformer可以处理任意长输入,但位置编码矩阵具有明确的信息,如果图片变大了,patch_size不变,那么你可能得到更多的patch,这时需要对位置进行插值。(如果图片大小差距过大,这样直接简单插值,会导致模型效果变差)

        6. 做自监督预训练,简单来说就是对图片的一些patch随机打上马赛克,去预测这些patch,使用这种方式去做预训练,但在VIT这篇论文中实验效果一般(一年以后,也就是去年,MAE模型在这上面取得了突破)

        7. 在更大的数据集上(JFT或imagenet21K)做预训练,再在别的数据集上做微调,效果更好,训练起来更便宜(相对来说,使用一个TPUv3训练需要2500天),证明了transformer在CV上同样具有和在nlp领域中一样的强大的学习能力

Vision transformer学习笔记_第12张图片

你可能感兴趣的:(深度学习基础)