当前位置:   article > 正文

CV顶刊!IJCV2022:G-GhostNet

g-ghostnet

GhostNet再升级,GPU上大显身手的G-GhostNet

作者设计出相比C-Ghost更适用于GPU等设备的G-Ghost,在实际延迟与性能之间取得了良好的权衡。Source Code

1、摘要

     本文针对网络部署时面临的内存和资源有限的问题,提出两种不同的Ghost模块,旨在利用成本低廉的线性运算来生成Ghost特征图。C-Ghost模块被应用于CPU等设备,并通过简单的模块堆叠实现C-GhostNet。适用于GPU等设备的G-Ghost模块利用阶段性特征冗余构建。最终实验结果表明两种模块分别实现了对应设备上精度和延迟的最佳权衡。

2、引言

     在GhostNet(CVPR 2020)出现之前,笔者对冗余特征一直保持刻板的偏见。但不管是过分的抑制冗余,还是过分的增加冗余,都会对架构性能造成不好的影响(存在即合理)。在GhostNet中,作者的等人通过细致的实验观察,提出“以有效方式接受冗余特征图”的思想,将以往的特征生成过程用更廉价的线性运算来替代,从而在保证性能的同时实现轻量化。

     如下图所示,对中间特征图进行可视化之后,可以明显看出一些特征表达近似(图中用相同颜色标出的方框),因此作者等人提出,近似的特征图可以通过一些廉价的操作来获得。

在这里插入图片描述

     据此,作者等人提出GhostNet,并在本文中称作C-GhostNet,因此笔者将解读的重点放在G-Ghost,对C-Ghost仅做回顾。

     C-GhostNet中为实现轻量化,使用了一些低运算密度的操作。低运算密度使得GPU的并行计算能力无法被充分利用,从而导致C-GhostNet在GPU等设备上糟糕的延迟,因此需要设计一种适用于GPU设备的Ghost模块。

     作者等人发现,现有大多数CNN架构中,一个阶段通常包括几个卷积层/块,同时在每个阶段中的不同层/块,特征图的尺寸大小相同,因此一种猜想是:特征的相似性和冗余性不仅存在于一个层内,也存在于该阶段的多个层之间。下图的可视化结果验证了这种想法(如右边第三行第二列和第七行第三列的特征图存在一定相似性)。

在这里插入图片描述

     作者等人利用观察到的阶段性特征冗余,设计G-Ghost模块并应用于GPU等设备,实现了一个在GPU上具有SOTA性能的轻量级CNN。

3、C-GhostNet(也就是之前的GhostNet)回顾

在这里插入图片描述

在这里插入图片描述

4、G-GhostNet

     C-GhostNet中使用低运算密度的depth-wise卷积作为生成Ghost特征的廉价操作,对于GPU等设备来说,无法充分利用其并行计算能力。另一方面,如果能去除部分特征图并减少激活,便可以概率地减少 GPU 上的延迟

     如何实现这一目标?就得利用导读部分提及的“特征的相似性和冗余性不仅存在于一个层内,也存在于该阶段的多个层之间”。由于现有流行CNN架构中,同一阶段的不同层,其输出特征图的尺寸大小不会发生变化,因此一种跨层的廉价替代便可以通过这一特点来实现。其具体实现过程如下:
在CNN的某个阶段中,深层特征被分为Ghost特征(可通过浅层廉价操作获得)和复杂特征(不可通过浅层廉价操作获得),以图2为例:
在这里插入图片描述
在这里插入图片描述

     但是,直接的特征拼接带来的影响是显而易见的。复杂特征经过逐层提取,包含更丰富的语义信息;而Ghost特征由浅层进行廉价操作所得,可能缺乏一部分深层信息。因此一种信息补偿的手段是有必要的,作者等人使用如下操作来提升廉价操作的表征能力:

在这里插入图片描述

     如图5,复杂特征经过连续的n个卷积块生成,Ghost特征则由第一个卷积块经过廉价操作所得。其中mix模块用于提升廉价操作表征能力,即先将复杂特征分支中第2至第n层的中间特征进行拼接,再使用变换函数,变换至与廉价操作的输出同域,最后再进行特征融合(如简单的逐元素相加)。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

     除了大小为1的卷积核,廉价操作还可以用3×3、5×5的卷积核,或者直接的恒等映射来计算。

5、性能对比

在这里插入图片描述

6、代码展示

本文只对表2中Ghost-RegNetX-3.2GF进行复现。

代码位置: gghostregnetp.pyPaddleDetection/ppdet/modeling/backbones/gghostregnetp.py

核心代码展示(图5就是核心块)

class Stage(nn.Layer):

   def __init__(self, block, inplanes, planes, group_width, blocks, stride=1, dilate=False, cheap_ratio=0.5):
       super(Stage, self).__init__()
       norm_layer = nn.BatchNorm2D
       downsample = None
       self.dilation = 1
       previous_dilation = self.dilation
       self.inplanes = inplanes
       if dilate:
           self.dilation *= stride
           stride = 1
       if stride != 1 or self.inplanes != planes:
           downsample = nn.Sequential(
               conv1x1(inplanes, planes, stride),
               norm_layer(planes),
           )
       #===========获取每个block=============
       self.base = block(inplanes, planes, stride, downsample, group_width,
                           previous_dilation, norm_layer)
       self.end = block(planes, planes, group_width=group_width,
                               dilation=self.dilation,
                               norm_layer=norm_layer)
       #===============================================
       
       group_width = int(group_width * 0.75)
       raw_planes = int(planes * (1 - cheap_ratio) / group_width) * group_width
       cheap_planes = planes - raw_planes
       self.cheap_planes = cheap_planes
       self.raw_planes = raw_planes
       
       #===============block融合=======================
       self.merge = nn.Sequential(
           nn.AdaptiveAvgPool2D(1),
           nn.Conv2D(planes+raw_planes*(blocks-2), cheap_planes,
                     kernel_size=1, stride=1, bias_attr=False),
           nn.BatchNorm2D(cheap_planes),
           nn.ReLU(),
           nn.Conv2D(cheap_planes, cheap_planes, kernel_size=1, bias_attr=False),
           nn.BatchNorm2D(cheap_planes),
#             nn.ReLU(inplace=True),
       )
       #==========文中提出的cheap操作=====================
       self.cheap = nn.Sequential(
           nn.Conv2D(cheap_planes, cheap_planes,
                     kernel_size=1, stride=1, bias_attr=False),
           nn.BatchNorm2D(cheap_planes),
#             nn.ReLU(inplace=True),
       )
       self.cheap_relu = nn.ReLU()
       
       layers = []
       downsample = nn.Sequential(
           LambdaLayer(lambda x: x[:, :raw_planes])
       )

       layers = []
       layers.append(block(raw_planes, raw_planes, 1, downsample, group_width,
                           self.dilation, norm_layer))
       inplanes = raw_planes
       for _ in range(2, blocks-1):
           layers.append(block(inplanes, raw_planes, group_width=group_width,
                               dilation=self.dilation,
                               norm_layer=norm_layer))

       self.layers = nn.Sequential(*layers)
       
   
   def forward(self, input):
      """
      可以对照图5进行理解前向传播过程
      """
       x0 = self.base(input)
       
       m_list = [x0]
       e = x0[:,:self.raw_planes]        
       for l in self.layers:
           e = l(e)
           m_list.append(e)
       m = paddle.concat(m_list,1)
       m = self.merge(m)
       
       c = x0[:,self.raw_planes:]
       c = self.cheap_relu(self.cheap(c)+m)
       
       x = paddle.concat((e,c),1)
       x = self.end(x)
       return x
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

7、ImageNet效果验证

#解压数据集
!mkdir data/ILSVRC2012
!tar -xf ~/data/data105740/ILSVRC2012_val.tar -C ~/data/ILSVRC2012
  • 1
  • 2
  • 3
#导入必要的库
import os
import cv2
import numpy as np
import warnings
import paddle
import paddle.vision.transforms as T
from PIL import Image
warnings.filterwarnings('ignore')

# 构建数据集
class ILSVRC2012(paddle.io.Dataset):
    def __init__(self, root, label_list, transform, backend='pil'):
        self.transform = transform
        self.root = root
        self.label_list = label_list
        self.backend = backend
        self.load_datas()

    def load_datas(self):
        self.imgs = []
        self.labels = []
        with open(self.label_list, 'r') as f:
            for line in f:
                img, label = line[:-1].split(' ')
                self.imgs.append(os.path.join(self.root, img))
                self.labels.append(int(label))

    def __getitem__(self, idx):
        label = self.labels[idx]
        image = self.imgs[idx]
        if self.backend=='cv2':
            image = cv2.imread(image)
        else:
            image = Image.open(image).convert('RGB')
        image = self.transform(image)
        return image.astype('float32'), np.array(label).astype('int64')

    def __len__(self):
        return len(self.imgs)


val_transforms = T.Compose([
    T.Resize(int(224 / 0.96), interpolation='bicubic'),
    T.CenterCrop(224),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

val_transform_384 = T.Compose([
    T.Resize(int(384 / 0.96), interpolation='bicubic'),
    T.CenterCrop(384),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
#获得模型
from g_ghost_regnet_p import regnetx_032
model = regnetx_032()
  • 1
  • 2
  • 3
W0804 23:55:57.808670   145 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0804 23:55:57.813205   145 gpu_resources.cc:91] device: 0, cuDNN Version: 7.6.
  • 1
  • 2
#加载权重
model.load_dict(paddle.load('/home/aistudio/g_ghost_regnet_p.pdparams'))
model = paddle.Model(model)
  • 1
  • 2
  • 3
#查看模型结构
model.summary((1,3,224,224))
  • 1
  • 2
#验证模型效果
model.prepare(metrics=paddle.metric.Accuracy(topk=(1, 5)))
val_dataset = ILSVRC2012('/home/aistudio/data/ILSVRC2012/ILSVRC2012_val', transform=val_transforms, label_list='/home/aistudio/data/ILSVRC2012/ILSVRC2012_val/val_list.txt', backend='pil')
acc = model.evaluate(val_dataset, batch_size=32, num_workers=4, verbose=1)
print(acc)
  • 1
  • 2
  • 3
  • 4
  • 5
Eval begin...
step 1563/1563 [==============================] - acc_top1: 0.7756 - acc_top5: 0.9382 - 227ms/step        
Eval samples: 50000
{'acc_top1': 0.77562, 'acc_top5': 0.93816}
  • 1
  • 2
  • 3
  • 4

8、VOC目标检测应用

本项目还将G-GhostNet应用到目标检测中,结合PaddleDetection实现,在VOC上准确率为74.83%。

训练好的VOC权重在/home/aistudio/model_final

!unzip -d /home/aistudio/data/ /home/aistudio/data/data63105/PascalVOC07_12.zip
  • 1
%cd PaddleDetection/
  • 1
/home/aistudio/PaddleDetection
  • 1
!pip install -r requirements.txt
  • 1
!python setup.py install
  • 1
!python tools/train.py -c configs/yolov3/yolov3_gghost_voc.yml --eval
  • 1
#这里使用预先提供好的VOC权重进行验证
yolov3_gghost_voc.yml --eval
  • 1
  • 2
#这里使用预先提供好的VOC权重进行验证
!python tools/eval.py -c configs/yolov3/yolov3_gghost_voc.yml -o weights=/home/aistudio/model_final
  • 1
  • 2

9、总结与思考

    本文利用可视化观察到的现象和大量的实验结果,提出了Ghost特征的思想,利用“特征的相似性和冗余性不仅存在于一个层内,也存在于该阶段的多个层之间”这一猜测,设计出相比C-Ghost更适用于GPU等设备的G-Ghost,并在实际延迟与性能之间取得了良好的权衡。

参考文献

华为轻量级神经网络架构GhostNet再升级,GPU上大显身手的G-GhostNet(IJCV22)

声明

此项目为搬运
原项目链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/350400
推荐阅读
相关标签
  

闽ICP备14008679号