赞
踩
本文针对网络部署时面临的内存和资源有限的问题,提出两种不同的Ghost模块,旨在利用成本低廉的线性运算来生成Ghost特征图。C-Ghost模块被应用于CPU等设备,并通过简单的模块堆叠实现C-GhostNet。适用于GPU等设备的G-Ghost模块利用阶段性特征冗余构建。最终实验结果表明两种模块分别实现了对应设备上精度和延迟的最佳权衡。
在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。
C-GhostNet中使用低运算密度的depth-wise卷积作为生成Ghost特征的廉价操作,对于GPU等设备来说,无法充分利用其并行计算能力。另一方面,如果能去除部分特征图并减少激活,便可以概率地减少 GPU 上的延迟。
如何实现这一目标?就得利用导读部分提及的“特征的相似性和冗余性不仅存在于一个层内,也存在于该阶段的多个层之间”。由于现有流行CNN架构中,同一阶段的不同层,其输出特征图的尺寸大小不会发生变化,因此一种跨层的廉价替代便可以通过这一特点来实现。其具体实现过程如下:
在CNN的某个阶段中,深层特征被分为Ghost特征(可通过浅层廉价操作获得)和复杂特征(不可通过浅层廉价操作获得),以图2为例:
但是,直接的特征拼接带来的影响是显而易见的。复杂特征经过逐层提取,包含更丰富的语义信息;而Ghost特征由浅层进行廉价操作所得,可能缺乏一部分深层信息。因此一种信息补偿的手段是有必要的,作者等人使用如下操作来提升廉价操作的表征能力:
如图5,复杂特征经过连续的n个卷积块生成,Ghost特征则由第一个卷积块经过廉价操作所得。其中mix模块用于提升廉价操作表征能力,即先将复杂特征分支中第2至第n层的中间特征进行拼接,再使用变换函数,变换至与廉价操作的输出同域,最后再进行特征融合(如简单的逐元素相加)。
除了大小为1的卷积核,廉价操作还可以用3×3、5×5的卷积核,或者直接的恒等映射来计算。
本文只对表2中Ghost-RegNetX-3.2GF进行复现。
代码位置: gghostregnetp.py 和 PaddleDetection/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
#解压数据集
!mkdir data/ILSVRC2012
!tar -xf ~/data/data105740/ILSVRC2012_val.tar -C ~/data/ILSVRC2012
#导入必要的库 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]) ])
#获得模型
from g_ghost_regnet_p import regnetx_032
model = regnetx_032()
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.
#加载权重
model.load_dict(paddle.load('/home/aistudio/g_ghost_regnet_p.pdparams'))
model = paddle.Model(model)
#查看模型结构
model.summary((1,3,224,224))
#验证模型效果
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)
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}
本项目还将G-GhostNet应用到目标检测中,结合PaddleDetection实现,在VOC上准确率为74.83%。
训练好的VOC权重在/home/aistudio/model_final
!unzip -d /home/aistudio/data/ /home/aistudio/data/data63105/PascalVOC07_12.zip
%cd PaddleDetection/
/home/aistudio/PaddleDetection
!pip install -r requirements.txt
!python setup.py install
!python tools/train.py -c configs/yolov3/yolov3_gghost_voc.yml --eval
#这里使用预先提供好的VOC权重进行验证
yolov3_gghost_voc.yml --eval
#这里使用预先提供好的VOC权重进行验证
!python tools/eval.py -c configs/yolov3/yolov3_gghost_voc.yml -o weights=/home/aistudio/model_final
本文利用可视化观察到的现象和大量的实验结果,提出了Ghost特征的思想,利用“特征的相似性和冗余性不仅存在于一个层内,也存在于该阶段的多个层之间”这一猜测,设计出相比C-Ghost更适用于GPU等设备的G-Ghost,并在实际延迟与性能之间取得了良好的权衡。
华为轻量级神经网络架构GhostNet再升级,GPU上大显身手的G-GhostNet(IJCV22)
此项目为搬运
原项目链接
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。