请点击此处查看本环境基本用法.

Please click here for more detailed instructions.

超越Swin v2、PvT v2等模型,ViT-Adaptiver实现ADE20K冠军60.5mIoU

Vision Transformer Adapter for Dense Predictions. Source code

1、摘要

     与最近将视觉特定的归纳偏差引入Vision Transformer架构不同,ViT由于缺乏图像的先验信息,在密集预测任务上的性能较差。为了解决这个问题,本文提出了一种Vision Transformer适配器(ViT-Adapter),ViT-Adapter可以通过额外的架构引入归纳偏差来弥补ViT的缺陷并实现与视觉特定模型相当的性能。

     具体来说,ViT-Adapter中的Backbone是一个普通的Transformer,可以用多模态数据进行预训练。在对下游任务进行微调时,使用特定于模态的适配器将数据和任务的先验信息引入模型,使其适用于这些任务。

在这里插入图片描述

     作者验证了ViT-Adapter在多个下游任务上的有效性,包括目标检测、实例分割和语义分割。尤其,使用HTC++时,ViT-Adapter-L得到了60.1 和52.1 ,在COCO test-dev上,超过 Swin-L 1.4 和1.0 。对于语义分割,ViT-Adapter-L在ADE20K val上建立了一个新的mIoU 60.5%,比SwinV2-G高0.6%。

2、本文方法

    如图 1 所示,与之前对大规模图像数据集(例如ImageNet)进行预训练和对不同任务进行微调的范式相比,本文的范式更加灵活。在ViT-Adapter框架中,Backbone网络是一个通用模型(例如,ViT),可以使用多模态数据和任务进行预训练。当将其应用于下游任务时,视觉专用适配器将输入数据和任务的先验信息引入到通用Backbone网络之中,使模型适用于下游任务。通过这种方式,使用ViT作为Backbone,ViT-Adapter框架实现了与专为密集预测任务设计的Transformer Backbone(如Swin Transformer)相当甚至更好的性能。

在这里插入图片描述

    如图3所示,ViT-Adapter模型可以分为2部分。(1)第1部分是Backbone(即 ViT):它由1个Patch Embedding和L个Transformer Encoder层组成(见图3(a))。(2)第2部分是提出的ViT-Adapter:如图3(b)所示,它包含1个Spatial prior module,用于从输入图像中捕获空间特征,1个Spatial Feature injector,用于将空间先验注入到ViT中,以及1个多尺度特征提取器,用于从ViT中提取分层特征。

在这里插入图片描述

    对于ViT,首先将输入图像输入Patch Embedding,将图像分成16×16个不重叠的Patch。在此之后,这些Patch被Flatten并投影到d维Embedding中。这里的特征分辨率降低到原始图像的1/16。最后,嵌入的Patch被和位置嵌入通过ViT的L编码器层。

     对于ViT-Adapter,首先将输入图像输入到Spatial prior module中。将收集3种目标分辨率(即1/8、1/16和1/32)的d维空间特征。然后,这些特征映射被Flatten并连接起来,作为特征交互的输入。

     具体来说,给定交互时间N,将ViT的Transforer编码器均匀地分割成N个Blocks,每个Block包含L/N编码器层。对于第i个Block,首先通过Spatial Feature injector将空间先验注Fsp到Block中,然后通过多尺度特征提取器从Block的输出中提取层次特征。经过N个特征交互后,获得了高质量的多尺度特征,然后将特征分割并reshape为3个目标分辨率1/8、1/16和1/32。最后,通过2×2的转置卷积对1/8尺度的特征图进行上采样,得到了1/4尺度的特征图。

     通过这种方法,得到了一个与ResNet分辨率相似的特征金字塔,它可以用于各种密集的预测任务。

Spatial Prior Module

     最近的工作表明具有重叠滑动窗口的卷积可以帮助Transforer更好地捕捉输入图像的局部连续性。受此启发,作者在ViT中引入了一个基于卷积的Spatial prior module,它通过一个stem和3个卷积将H×W输入图像下采样到不同的尺度。该模块旨在模拟与Patch Embedding平行的图像的局部空间上下文,以免改变ViT的原始架构。

在这里插入图片描述

     如图3©所示,采用了1个借鉴于ResNet的标准卷积stem,它由3个卷积层和一个最大池化层组成。接下来,使用一个步长为2的3×3卷积堆栈构成了该模块的其余部分,它使通道数量增加了一倍并减小了特征图的大小。

在这里插入图片描述

Feature Interaction

    由于柱状结构,ViT中的特征图是单尺度和低分辨率的,与金字塔结构的Transformer相比,ViT对于密集预测任务的性能是次优的。为了缓解这个问题,作者提出了2个特征交互模块,在适配器和ViT之间传递特征映射。具体来说,这2个模块分别是基于Cross-Attention的Spatial Feature Injector和Multi-Scale Feature Extractor。如前面所述,将基于ViT的Transformer编码器划分为N个相等的Blocks,并分别在每个Block之前和之后应用所提出的2个算子。

Spatial Feature Injector

在这里插入图片描述

在这里插入图片描述

Multi-Scale Feature Extractor

在这里插入图片描述

在这里插入图片描述

模型架构配置

    本文为4种不同的ViT变体构建了ViT-Adapter,包括ViT-T、ViT-S、ViT-B和ViT-L。对于这些模型,ViT-Adapter的参数数分别为2.5M、5.8M、14.0M和23.7M。每种配置的细节如表1所示。

在这里插入图片描述

3、实验结果(ADE20K)

在这里插入图片描述

4、代码展示

代码位置:PaddleSeg/paddleseg/models/backbones/beit.py

配置文件位置: PaddleSeg/configs/upernet/upernetvitadapterbeit512x512.yml

核心代码

BEiTAdapter核心组成两个部分:Beit和adapter,结构如上面图3所示

 class BEiTAdapter(BEiT):
    def __init__(self, pretrain_size=224, conv_inplane=64, n_points=4, deform_num_heads=6,
                 init_values=0., cffn_ratio=0.25, deform_ratio=1.0, with_cffn=True,
                 interaction_indexes=None, add_vit_feature=True, with_cp=False, *args, **kwargs):

        super().__init__(init_values=init_values, with_cp=with_cp, *args, **kwargs)

        # self.num_classes = 80
        # self.cls_token = None
        self.num_block = len(self.blocks)
        self.pretrain_size = (pretrain_size, pretrain_size)
        self.flags = [i for i in range(-1, self.num_block, self.num_block // 4)][1:]
        self.interaction_indexes = interaction_indexes
        self.add_vit_feature = add_vit_feature
        embed_dim = self.embed_dim

        self.level_embed = add_parameter(self,paddle.zeros((3, embed_dim)))
        self.spm = SpatialPriorModule(inplanes=conv_inplane, embed_dim=embed_dim, with_cp=False)
        self.interactions = nn.Sequential(*[
            InteractionBlockWithCls(dim=embed_dim, num_heads=deform_num_heads, n_points=n_points,
                             init_values=init_values, drop_path=self.drop_path_rate,
                             norm_layer=self.norm_layer, with_cffn=with_cffn,
                             cffn_ratio=cffn_ratio, deform_ratio=deform_ratio,
                             extra_extractor=True if i == len(interaction_indexes) - 1 else False,
                             with_cp=with_cp)
            for i in range(len(interaction_indexes))
        ])

        self.up = nn.Conv2DTranspose(embed_dim, embed_dim, 2, 2)
        self.norm1 = nn.SyncBatchNorm(embed_dim)
        self.norm2 = nn.SyncBatchNorm(embed_dim)
        self.norm3 = nn.SyncBatchNorm(embed_dim)
        self.norm4 = nn.SyncBatchNorm(embed_dim)

        self.feat_channels = [1024, 1024, 1024, 1024]

        self.up.apply(self._init_weights)
        self.spm.apply(self._init_weights)
        self.interactions.apply(self._init_weights)
        self.apply(self._init_deform_weights)
        normal_(self.level_embed)

    def _init_weights(self, m):
        if isinstance(m, nn.Linear):
            trunc_normal_ = nn.initializer.TruncatedNormal(std=.02)
            trunc_normal_(m.weight)
            if isinstance(m, nn.Linear) and m.bias is not None:
                zeros_(m.bias)
        elif isinstance(m, nn.LayerNorm) or isinstance(m, nn.BatchNorm2D):
            zeros_(m.bias)
            ones_(m.weight)
        elif isinstance(m, nn.Conv2D) or isinstance(m, nn.Conv2DTranspose):
            fan_out = m._kernel_size[0] * m._kernel_size[1] * m._out_channels
            fan_out //= m._groups
            norm = nn.initializer.Normal(0,math.sqrt(2.0 / fan_out))
            norm(m.weight)
            if m.bias is not None:
                zeros_(m.bias)

    def _get_pos_embed(self, pos_embed, H, W):
        pos_embed = pos_embed.reshape((
            1, self.pretrain_size[0] // 16, self.pretrain_size[1] // 16, -1)).transpose((0, 3, 1, 2))
        pos_embed = F.interpolate(pos_embed, size=(H, W), mode='bicubic', align_corners=False).\
            reshape((1, -1, H * W)).transpose((0, 2, 1))
        return pos_embed

    def _init_deform_weights(self, m):
        if isinstance(m, MSDeformAttn):
            m._reset_parameters()

    def _add_level_embed(self, c2, c3, c4):
        c2 = c2 + self.level_embed[0]
        c3 = c3 + self.level_embed[1]
        c4 = c4 + self.level_embed[2]
        return c2, c3, c4

    def forward(self, x):
        """
        前向传播的过程分解为BeIT和Adapter两个部分
        """
        deform_inputs1, deform_inputs2 = deform_inputs(x)

        # SPM forward
        c1, c2, c3, c4 = self.spm(x)
        c2, c3, c4 = self._add_level_embed(c2, c3, c4)
        c = paddle.concat([c2, c3, c4], axis=1)

        # Patch Embedding forward
        x, H, W = self.patch_embed(x)
        bs, n, dim = x.shape
        cls = self.cls_token.expand((bs, -1, -1))  # stole cls_tokens impl from Phil Wang, thanks

        if self.pos_embed is not None:
            pos_embed = self._get_pos_embed(self.pos_embed, H, W)
            x = x + pos_embed
        x = self.pos_drop(x)

        # Interaction
        outs = list()
        for i, layer in enumerate(self.interactions):
            indexes = self.interaction_indexes[i]
            x, c, cls = layer(x, c, cls, self.blocks[indexes[0]:indexes[-1] + 1],
                              deform_inputs1, deform_inputs2, H, W)
            outs.append(x.transpose([0, 2, 1]).reshape((bs, dim, H, W)))

        # Split & Reshape
        c2 = c[:, 0:c2.shape[1], :]
        c3 = c[:, c2.shape[1]:c2.shape[1] + c3.shape[1], :]
        c4 = c[:, c2.shape[1] + c3.shape[1]:, :]

        c2 = c2.transpose([0, 2, 1]).reshape([bs, dim, H * 2, W * 2])
        c3 = c3.transpose([0, 2, 1]).reshape([bs, dim, H, W])
        c4 = c4.transpose([0, 2, 1]).reshape([bs, dim, H // 2, W // 2])
        c1 = self.up(c2) + c1

        if self.add_vit_feature:
            x1, x2, x3, x4 = outs
            x1 = F.interpolate(x1, scale_factor=4, mode='bilinear', align_corners=False)
            x2 = F.interpolate(x2, scale_factor=2, mode='bilinear', align_corners=False)
            x4 = F.interpolate(x4, scale_factor=0.5, mode='bilinear', align_corners=False)
            c1, c2, c3, c4 = c1 + x1, c2 + x2, c3 + x3, c4 + x4

        # Final Norm
        f1 = self.norm1(c1)
        f2 = self.norm2(c2)
        f3 = self.norm3(c3)
        f4 = self.norm4(c4)
        return [f1, f2, f3, f4]

5、ADE20K 效果验证

由于Paddleseg里面未提供Mask2Former代码,本项目只复现UperNet的Beit模型

#cd 到data目录
%cd /home/aistudio/data/
/home/aistudio/data
#解压数据集
!unzip -d ./ data26423/ade20k.zip
#cd 到PaddleSeg目录
%cd /home/aistudio/PaddleSeg/
/home/aistudio/PaddleSeg
#安装PaddleSeg
!pip install -r requirements.txt
!python setup.py install
#先来验证一下效果吧
#声明一下哈,一次验证需要1个小时15分钟,看结果的耐心等待
#验证的时候可以将batchsize调成大于等于2的,等于1太慢
!python val.py --config configs/upernet/upernet_vit_adapter_beit_640x640.yml \
               --model_path /home/aistudio/data/data164140/vit_adapter_beit_l.pdparams \
               --num_workers 4 \
               --is_slide \
               --crop_size 640 640 \
               --stride 426 426 \

由于在单卡V100上验证需要一个多小时,为了节省时间这里向大家公开我在个人服务器四卡T4(16GB memory)上验证结果为: miou: 0.574,accuracy=0.86 。感兴趣的可以跑一下看看

#开启训练
#训练时候需要将batchsize调成1,不然会有bug
!python train.py --config configs/upernet/upernet_vit_adapter_beit_640x640.yml \
                 --do_eval \
                 --use_vdl \
                 --save_interval 1000 \
ave_interval 1000 \
                 --save_dir output                 
2022-08-15 23:31:14 [INFO]	
------------Environment Information-------------
platform: Linux-4.15.0-140-generic-x86_64-with-debian-stretch-sid
Python: 3.7.4 (default, Aug 13 2019, 20:35:49) [GCC 7.3.0]
Paddle compiled with cuda: True
NVCC: Cuda compilation tools, release 10.1, V10.1.243
cudnn: 7.6
GPUs used: 1
CUDA_VISIBLE_DEVICES: None
GPU: ['GPU 0: Tesla V100-SXM2-32GB']
GCC: gcc (Ubuntu 7.5.0-3ubuntu1~16.04) 7.5.0
PaddleSeg: 2.6.0
PaddlePaddle: 2.3.1
OpenCV: 4.1.1
------------------------------------------------
2022-08-15 23:31:14 [INFO]	
---------------Config Information---------------
batch_size: 1
iters: 40000
loss:
  coef:
  - 1
  - 0.4
  types:
  - ignore_index: 255
    type: CrossEntropyLoss
lr_scheduler:
  end_lr: 0.0
  learning_rate: 2.0e-05
  power: 0.9
  type: PolynomialDecay
  warmup_iters: 1500
  warmup_start_lr: 1.0e-06
model:
  backbone:
    cffn_ratio: 0.25
    conv_inplane: 64
    deform_num_heads: 16
    deform_ratio: 0.5
    depth: 24
    drop_path_rate: 0.3
    embed_dim: 1024
    img_size: 640
    init_values: 1.0e-06
    interaction_indexes:
    - - 0
      - 5
    - - 6
      - 11
    - - 12
      - 17
    - - 18
      - 23
    mlp_ratio: 4
    n_points: 4
    num_heads: 16
    patch_size: 16
    pretrained: /home/aistudio/data/data164140/vit_adapter_beit_l.pdparams
    qkv_bias: true
    type: beit_adapter
    use_abs_pos_emb: false
    use_rel_pos_bias: true
    with_cp: false
  backbone_indices:
  - 0
  - 1
  - 2
  - 3
  channels: 1024
  dropout_prob: 0.1
  enable_auxiliary_loss: true
  num_classes: 150
  type: UPerNet
optimizer:
  beta1: 0.9
  beta2: 0.999
  type: AdamW
  weight_decay: 0.05
train_dataset:
  dataset_root: /home/aistudio/data/ADEChallengeData2016/
  mode: train
  transforms:
  - max_scale_factor: 2.0
    min_scale_factor: 0.5
    scale_step_size: 0.25
    type: ResizeStepScaling
  - crop_size:
    - 640
    - 640
    type: RandomPaddingCrop
  - type: RandomHorizontalFlip
  - brightness_range: 0.4
    contrast_range: 0.4
    saturation_range: 0.4
    type: RandomDistort
  - type: Normalize
  type: ADE20K
val_dataset:
  dataset_root: /home/aistudio/data/ADEChallengeData2016/
  mode: val
  transforms:
  - keep_ratio: true
    target_size:
    - 2048
    - 640
    type: Resize
  - size_divisor: 32
    type: ResizeToMultiple
  - type: RandomHorizontalFlip
  - type: Normalize
  type: ADE20K
------------------------------------------------
W0815 23:31:14.885622   293 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0815 23:31:14.885679   293 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/fluid/dygraph/math_op_patch.py:278: UserWarning: The dtype of left and right variables are not the same, left dtype is paddle.float32, but right dtype is paddle.int64, the right dtype will convert to paddle.float32
  format(lhs_dtype, rhs_dtype, lhs_dtype))
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:654: UserWarning: When training, we now always track global mean and variance.
  "When training, we now always track global mean and variance.")
2022-08-15 23:31:32 [INFO]	[TRAIN] epoch: 1, iter: 10/40000, loss: 7.6269, lr: 0.000001, batch_cost: 1.4596, reader_cost: 0.04923, ips: 0.6851 samples/sec | ETA 16:12:48
2022-08-15 23:31:44 [INFO]	[TRAIN] epoch: 1, iter: 20/40000, loss: 8.3714, lr: 0.000001, batch_cost: 1.2583, reader_cost: 0.00017, ips: 0.7947 samples/sec | ETA 13:58:25
2022-08-15 23:31:57 [INFO]	[TRAIN] epoch: 1, iter: 30/40000, loss: 7.7384, lr: 0.000001, batch_cost: 1.2583, reader_cost: 0.00015, ips: 0.7947 samples/sec | ETA 13:58:13
2022-08-15 23:32:09 [INFO]	[TRAIN] epoch: 1, iter: 40/40000, loss: 7.3995, lr: 0.000001, batch_cost: 1.2545, reader_cost: 0.00016, ips: 0.7971 samples/sec | ETA 13:55:30
^C

6、权重转换

如果你想快速获得VIT-adapter权重,本项目提供pytorch转Paddle代码,在 /home/aistudio/conver_weight.py

具体用法如下:

  1. 首先 cd PaddleSeg

  2. 接着 git clone https://github.com/czczup/ViT-Adapter

  3. 将VIT-adapter改名为Adapter(因为原文件中有“-”,python文件无法识别)

  4. 运行pip install -r requirements.txt和python setup.py install 安装PaddleSeg

  5. cd Adapter/segmentation/

  6. 按照VIT-adapter中准备环境的代码准备环境

  7. cp /home/aistudio/conver_weight.py ./

  8. 下载pytorch权重并运行python conver_weight.py

7、模型效果

可以看到single scale的验证,VIT-adapter可以达到58.0的miou,目前AIstudio上在ADE20K上最高miou

在这里插入图片描述

8、总结

1、本文从图像先验信息的角度,设计一个新的Adapter模块,将图像的先验知识引入VIT,实现在ADE20K最先进的新能。

2、缺点:代码验证时候太耗时,也就是推理速度慢,这是VIT通病,希望未来可以改进。

参考文献

分割冠军 | 超越Swin v2、PvT v2等模型,ViT-Adaptiver实现ADE20K冠军60.5mIoU

此文章为搬运
原项目链接

Logo

学大模型,用大模型上飞桨星河社区!每天8点V100G算力免费领!免费领取ERNIE 4.0 100w Token >>>

更多推荐