通俗易懂理解GhostNetV2轻量级神经网络模型

一、参考资料

原始论文:[1]

NeurIPS22 Spotlight | 已开源 | 华为GhostNetV2:端侧小模型性能新SOTA

二、术语解析

廉价的线性变换/线性运算cheap linear operations

线性变换的线性内核linear kernels

深度可分离卷积Depthwise Separable ConvolutionDSConv

逐深度卷积Depthwise ConvolutionDWConv

逐点卷积Pointwise ConvolutionPWConv

Ghost特征图Ghost feature maps

注意力图attention maps

解耦全连接注意力decoupled fully connected attentionDFC attention

表征能力expressiveness

性能capacity

三、GhostNetV2相关介绍

1. 引言

关于 GhostNetV1 网络模型的详细介绍,请参考另一篇博客:通俗易懂理解GhostNetV1轻量级神经网络模型

智能手机等边缘设备计算资源有限,在设计模型时,不仅需要考虑模型的性能,更要考虑其实际的推理速度。最近计算机视觉领域爆火的Transformer模型在多个任务取得了很高精度,但在端侧设备上运行速度较慢,难以满足实时性的要求。经典的自注意力(self-attention)模块的计算复杂度较高,计算量随着输入分辨率的增加以二次方的速度增长。尽管目前主流的局部注意力模块(将图像切分为多个区域,在每个区域内分别部署注意力模块)降低了理论复杂度,但图像切分需要大量变换张量形状的操作(比如reshape、transpose等),在端侧设备上会产生很高的时延。比如,将局部注意力模块和轻量级模型GhostNet结合,理论复杂度只增加了20%,但是实际的推理时延却翻了2倍。因此,为轻量化小模型专门设计硬件友好的注意力机制非常有必要。

2. DFC attention

2.1 移动端CNN的注意力模块

一个适用于移动端CNN的注意力模块应当满足3个条件:

  • 对长距离空间信息的建模能力强。相比CNN,Transformer性能强大的一个重要原因是它能够建模全局空间信息,因此新的注意力模块也应当能捕捉空间长距离信息。
  • 部署高效。注意力模块应该硬件友好,计算高效,以免拖慢推理速度,特别是不应包含硬件不友好的操作。
  • 概念简单。为了保证注意力模块的泛化能力,这个模块的设计应当越简单越好。

2.2 DFC attention 原理

虽然自注意力操作可以很好地建模长距离依赖,但是部署效率低。相比自注意力机制,具有固定权重的FC层更简单,更容易实现,也可以用于生成具有全局感受野的 attention maps

给定特征图 Z ∈ R H × W × C \begin{array}{ccc}Z&\in&\mathbb{R}^{H\times W\times C}\end{array} ZRH×W×C,它可以看作 h w hw hwtokens,记作 z i ∈ R C z_i\in\mathbb{R}^C ziRC,也就是 Z = { z 11 , z 12 , ⋯   , z H W } Z=\{z_{11},z_{12},\cdots,z_{HW}\} Z={z11,z12,,zHW}。FC层生成 attention map 的公式表达如下:
a h w = ∑ h ′ , w ′ F h w , h ′ w ′ ⊙ z h ′ w ′ , ( 3 ) \boldsymbol{a}_{hw}=\sum_{h^{\prime},w^{\prime}}F_{hw,h^{\prime}w^{\prime}}\odot\boldsymbol{z}_{h^{\prime}w^{\prime}}, \quad (3) ahw=h,wFhw,hwzhw,(3)
其中, ⊙ \odot 表示 element-wise multiplication F F F 是FC层中可学习的权重, A = { a 11 , a 12 , ⋯   , a H W } A=\{a_{11},a_{12},\cdots,\boldsymbol{a}_{HW}\} A={a11,a12,,aHW}

根据上述公式,将所有 tokens 与可学习的权重聚合在一起以提取全局信息,该过程比经典的自注意力简单的多。然而,该过程的计算复杂度仍然是二次方,特征图的大小为 O ( H 2 W 2 ) ) 2 \mathcal{O}({H^{2}W^{2}}))^{2} O(H2W2))2,这在实际情况下是不可接受的,特别是当输入的图像是高分辨率时。例如,对于4层的GhostNet网络的特征图具有 3136 ( 56 × 56 ) 3136 (56 \times 56) 3136(56×56)tokens,这使得计算变得 attention maps 异常复杂。实际上,CNN中的特征图通常是低秩的,不需要将不同空间位置的所有输入和输出的 tokens 密集地连接起来。特征的2D尺寸很自然地提供一个视角,以减少FC层的计算量,也就是根据上述公式分解为两个FC层,分别沿水平方向和垂直方向聚合特征,其公式表达如下:
a h w ′ = ∑ h ′ = 1 H F h , h ′ w H ⊙ z h ′ w , h = 1 , 2 , ⋯   , H , w = 1 , 2 , ⋯   , W , ( 4 ) \boldsymbol{a}'_{hw}=\sum\limits_{h'=1}^{H}F_{h,h'w}^{H}\odot\boldsymbol{z}_{h'w},h=1,2,\cdots,H,w=1,2,\cdots,W, \quad (4) ahw=h=1HFh,hwHzhw,h=1,2,,H,w=1,2,,W,(4)

a h w = ∑ w ′ = 1 W F w , h w ′ W ⊙ a h w ′ ′ , h = 1 , 2 , ⋯   , H , w = 1 , 2 , ⋯   , W , ( 5 ) \boldsymbol{a}_{hw}=\sum_{w'=1}^{W}F_{w,hw'}^{W}\odot\boldsymbol{a}_{hw'}^{\prime},h=1,2,\cdots,H,w=1,2,\cdots,W, \quad (5) ahw=w=1WFw,hwWahw,h=1,2,,H,w=1,2,,W,(5)

其中, F H F^H FH F W F^W FW是变换的权重。输入原始特征 Z Z Z,并依次应用公式(4)和公式(5),分别提取沿两个方向的长距离依赖关系。 作者将此操作称为解耦全连接注意力(decoupled fully connected attentionDFC attention),其信息流如下图所示:

在这里插入图片描述

由于水平和垂直方向变换的解耦,注意力模块的计算复杂度可以降低到 O ( H 2 W + H W 2 ) \mathcal{O}(H^{2}W+HW^{2}) O(H2W+HW2)。对于 full attention (公式3),正方形区域内的所有 patches 直接参与被聚合 patch 的计算。在 DFC attention 中,一个 patch 直接由其垂直方向和水平方向的 patch 进行聚合,而其他 patch 参与垂直线/水平线上的 patch 的生成,与被聚合的 token 有间接关系。因此,一个 patch 的计算也涉及到正方形区域的所有 patchs

公式(4)和公式(5)是 DFC attention 的一般表示,分别沿着水平和垂直方向聚合像素。通过共享部分变换权重,可以方便地使用卷积操作实现,省去了影响实际推理速度的耗时张量的reshape操作和transpose操作。为了处理不同分辨率的输入图像,卷积核的大小可以与特征图的大小进行解耦,也就是在输入特征上依次进行两个大小为 1 × K H 1 \times K_H 1×KH K W × 1 K_W \times 1 KW×1DWConv操作。当用卷积操作时,DFC attention 理论上的计算复杂度为 O ( K H H W + K W H W ) \mathcal{O}(K_{H}HW+K_{W}HW) O(KHHW+KWHW)。这种策略得到了TFLite和ONNX等工具的良好支持,可以在移动设备上进行快速推理。

2.3 DFC attention的性能

DFC attention 捕获了不同空间位置像素之间的长距离依赖关系,增强了模型的表征能力。

不同注意模块的 MobileNetV2 的实验结果如表4所示。SE[2]和CBAM[3]是两种广泛使用的注意力模块,CA[4]是最近提出的一种SOTA方法。本文提出的DFC attention比现有方法具有更高的性能。例如,所提出的 DFC attentionMobileNetV2 的top-1精度提高了2.4%,而超过了CA(1.5%)。

在这里插入图片描述

3. GhostNetV2

DFC attention 插入到轻量化网络GhostNetV1中可以提升表征能力,从而构建出新型视觉骨干网络 GhostNetV2

3.1 Enhancing Ghost Module

Ghost Module 中只有m个特征与其他像素交互,这影响了Ghost Module 提取空间信息(spatial information)的能力。因此,作者使用 DFC attention 来增强 Ghost Module 的输出特征 Y Y Y,从而来捕获不同空间像素之间的长距离依赖关系。

输入特征 X ∈ R h × w × c X\in\mathbb{R}^{h\times w \times c} XRh×w×c 被送入两个分支,一个是 Ghost Module 分支,用于输出特征 Y Y Y,另一个是 DFC attention Module 分支,用于生成 attention map,记作 A A A(公式(4)和公式(5)。 回想一下,在经典的自注意力中,线性变换层将输入特征图转换为计算 attention mapsquerykey。类似的,作者实现一个 1 × 1 1 \times 1 1×1 的卷积操作,将 Ghost Module 分支的输入 X X X 转换为 DFC module 分支的输入 Z Z Z。两个分支输出的乘积,即为最终输出 O ∈ R H × W × C O\in\mathbb{R}^{{H}\times W\times C} ORH×W×C,可以表示为:
O = Sigmoid ⁡ ( A ) ⊙ V ( X ) , ( 6 ) O=\operatorname{Sigmoid}(A)\odot\mathcal{V}(X), \quad (6) O=Sigmoid(A)V(X),(6)
其中, ⊙ \odot 表示element-wise multiplication A A Aattention map Sigmoid ⁡ \operatorname {Sigmoid} Sigmoid 是归一化函数以缩放到 ( 0 , 1 ) (0, 1) (0,1) 范围。 V ( ) \mathcal{V}() V() 表示 Ghost Module X X X 为输入特征。则信息聚合过程如下图所示:

在这里插入图片描述

使用相同的输入特征,Ghost ModuleDFC attention 是两个从不同角度提取信息的并行分支。输出特征是它们逐元素的信息,其中包含来自 Ghost Module 的特性和 DFC attention 的信息。每个 attention value 涉及到大范围的 patches,以便输出的特征可以包含这些 patches的信息。

3.2 GhostNetV1 bottleneck

the former enhances expanded features (expressiveness) while the latter improves the block’s capacity.

GhostNetV1 bottleneck是反向残差bottlenet(inverted residual bottleneck)结构,这种结构自然地解耦模型的表征能力(expressiveness)和容量(capacity)。对应的 GhostNetV1 bottleneck 包含两个Ghost Module ,第一个 Ghost Module 用于升维以增强扩展特征(表征能力),输出通道数增加;第二个 Ghost Module 用于降维以提高 GhostNetV1 bottleneck 的容量,输出通道数减少。简单理解,expressiveness对应第一个 Ghost Modulecapacity对应第二个 Ghost ModuleGhostNetV1 bottleneck 的结构如下图所示:

在这里插入图片描述

3.3 GhostNetV2 bottleneck

作者比较了将 DFC atttention 放到不同 Ghost Module中的精度差异,实验结果如下图所示:

在这里插入图片描述

从实验结果发现,虽然两个 Ghost Module 中都放置 DFC atttention 对模型的 Top1-Acc 都有影响,但计算量也随之增大,且将 DFC atttention 放到第一个Ghost Module中用来增强 expressiveness 更有效。因此默认设置下,只在第一个 Ghost Module 中加入DFC attentionGhostNetV2 bottleneck 的结构如下图所示:

在这里插入图片描述

DFC attention 分支与第一个Ghost Module并行,以增强扩展特征,然后将增强的特征传递到第二个Ghost Module

4. 实验结果

GhostNetV2也可以作为骨干模型,用于目标检测、语义分割等下游任务。

本文在ImageNet图像分类、COCO目标检测、ADE语义分割等数据集上进行了实验,相比其他架构,GhostNetV2取得了更快的推理速度和更高的模型精度

在这里插入图片描述

4.1 Image Classification on ImageNet

在ImageNet数据集中进行图像分类任务,实验结果如下:

在这里插入图片描述

4.2 Object Detection on MS COCO

在MS COCO数据集中进行目标检测任务,实验结果如下:

在这里插入图片描述

4.3 Semantic Segmentation on ADE20K

在ADE20K数据集中进行语义分割任务,实验结果如下:

在这里插入图片描述

5. 代码实现

PyTorch代码:ghostnetv2_pytorch

MindSpore 代码:ghostnetv2

GhostNet v2(NeurIPS 2022 Spotlight)原理与代码解析

以下仅介绍核心代码。

GhostModuleV2

class GhostModuleV2(nn.Module):
    def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True,mode=None,args=None):
        super(GhostModuleV2, self).__init__()
        self.mode=mode
        self.gate_fn=nn.Sigmoid()

        if self.mode in ['original']:
            self.oup = oup
            init_channels = math.ceil(oup / ratio) 
            new_channels = init_channels*(ratio-1)
            self.primary_conv = nn.Sequential(  
                nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),
                nn.BatchNorm2d(init_channels),
                nn.ReLU(inplace=True) if relu else nn.Sequential(),
            )
            self.cheap_operation = nn.Sequential(
                nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
                nn.BatchNorm2d(new_channels),
                nn.ReLU(inplace=True) if relu else nn.Sequential(),
            )
        elif self.mode in ['attn']: 
            self.oup = oup
            init_channels = math.ceil(oup / ratio) 
            new_channels = init_channels*(ratio-1)
            self.primary_conv = nn.Sequential(  
                nn.Conv2d(inp, init_channels, kernel_size, stride, kernel_size//2, bias=False),
                nn.BatchNorm2d(init_channels),
                nn.ReLU(inplace=True) if relu else nn.Sequential(),
            )
            self.cheap_operation = nn.Sequential(
                nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groups=init_channels, bias=False),
                nn.BatchNorm2d(new_channels),
                nn.ReLU(inplace=True) if relu else nn.Sequential(),
            ) 
            self.short_conv = nn.Sequential( 
                nn.Conv2d(inp, oup, kernel_size, stride, kernel_size//2, bias=False),
                nn.BatchNorm2d(oup),
                nn.Conv2d(oup, oup, kernel_size=(1,5), stride=1, padding=(0,2), groups=oup,bias=False),
                nn.BatchNorm2d(oup),
                nn.Conv2d(oup, oup, kernel_size=(5,1), stride=1, padding=(2,0), groups=oup,bias=False),
                nn.BatchNorm2d(oup),
            ) 
      
    def forward(self, x):
        if self.mode in ['original']:
            x1 = self.primary_conv(x)
            x2 = self.cheap_operation(x1)
            out = torch.cat([x1,x2], dim=1)
            return out[:,:self.oup,:,:]         
        elif self.mode in ['attn']:  
            res=self.short_conv(F.avg_pool2d(x,kernel_size=2,stride=2))  
            x1 = self.primary_conv(x)
            x2 = self.cheap_operation(x1)
            out = torch.cat([x1,x2], dim=1)
            return out[:,:self.oup,:,:]*F.interpolate(self.gate_fn(res),size=(out.shape[-2],out.shape[-1]),mode='nearest')
DFC attention 分支

self.short_conv 就是 DFC attention 分支,DFC attention 的代码实现过程如下:

  1. 首先,采用 Average Pooling 进行 down-sampling
  2. 然后,经过 1x1卷积,扩充通道数;
  3. 接着,用卷积替代 horizontal FCvertical FC,卷积核大小分别为(1, 5)、(5, 1);
  4. 最后,经过sigmoid得到 DFC attention 分支的输出。
  5. 对于 DFC attention 分支的输出,采用bilinear插值进行 up-sampling ,得到原始输入大小。然后与原始 GHost Module 的输出相乘,得到最终输出。
self.gate_fn=nn.Sigmoid()

self.short_conv = nn.Sequential( 
                nn.Conv2d(inp, oup, kernel_size, stride, kernel_size//2, bias=False),
                nn.BatchNorm2d(oup),
                nn.Conv2d(oup, oup, kernel_size=(1,5), stride=1, padding=(0,2), groups=oup,bias=False),
                nn.BatchNorm2d(oup),
                nn.Conv2d(oup, oup, kernel_size=(5,1), stride=1, padding=(2,0), groups=oup,bias=False),
                nn.BatchNorm2d(oup),
            ) 

res=self.short_conv(F.avg_pool2d(x,kernel_size=2,stride=2))  

F.interpolate(self.gate_fn(res),size=(out.shape[-2],out.shape[-1]),mode='nearest') 

请注意,论文与代码不一致的地方

  • 论文中,通过消融实验发现 Max Pooling 效率高,默认的下采样方法为 Max Pooling,而代码中使用 Average Pooling
  • 论文中,通过消融实现发现 bilinear interpolation 速度快,默认的上采样方法为 bilinear interpolation ,而代码中使用 nearest

GhostBottleneckV2

GhostBottleneckV2 由两个 GhostModuleV2 构成。

class GhostBottleneckV2(nn.Module): 

    def __init__(self, in_chs, mid_chs, out_chs, dw_kernel_size=3,
                 stride=1, act_layer=nn.ReLU, se_ratio=0.,layer_id=None,args=None):
        # in_chs 表示输入特征图的通道数
		# mid_chs 表示第一个 `Ghost Module` 进行升维得到的输出通道数
		# out_chs 表示第二个 `Ghost Module` 进行降维得到的输出通道数
        super(GhostBottleneckV2, self).__init__()
        has_se = se_ratio is not None and se_ratio > 0.
        self.stride = stride

        # Point-wise expansion
        if layer_id<=1:
            self.ghost1 = GhostModuleV2(in_chs, mid_chs, relu=True,mode='original',args=args)
        else:
            self.ghost1 = GhostModuleV2(in_chs, mid_chs, relu=True,mode='attn',args=args) 

        # Depth-wise convolution
        if self.stride > 1:
            self.conv_dw = nn.Conv2d(mid_chs, mid_chs, dw_kernel_size, stride=stride,
                             padding=(dw_kernel_size-1)//2,groups=mid_chs, bias=False)
            self.bn_dw = nn.BatchNorm2d(mid_chs)

        # Squeeze-and-excitation
        if has_se:
            self.se = SqueezeExcite(mid_chs, se_ratio=se_ratio)
        else:
            self.se = None
            
        self.ghost2 = GhostModuleV2(mid_chs, out_chs, relu=False,mode='original',args=args)
        
        # shortcut
        if (in_chs == out_chs and self.stride == 1):
            self.shortcut = nn.Sequential()
        else:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_chs, in_chs, dw_kernel_size, stride=stride,
                       padding=(dw_kernel_size-1)//2, groups=in_chs, bias=False),
                nn.BatchNorm2d(in_chs),
                nn.Conv2d(in_chs, out_chs, 1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(out_chs),
            )
    def forward(self, x):
        residual = x
        x = self.ghost1(x)
        if self.stride > 1:
            x = self.conv_dw(x)
            x = self.bn_dw(x)
        if self.se is not None:
            x = self.se(x)
        x = self.ghost2(x)
        x += self.shortcut(residual)
        return x

四、参考文献

[1] Tang Y, Han K, Guo J, et al. GhostNetv2: enhance cheap operation with long-range attention[J]. Advances in Neural Information Processing Systems, 2022, 35: 9969-9982.

[2] Hu J, Shen L, Sun G. Squeeze-and-excitation networks[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2018: 7132-7141.

[3] Woo S, Park J, Lee J Y, et al. Cbam: Convolutional block attention module[C]//Proceedings of the European conference on computer vision (ECCV). 2018: 3-19.

[4] Hou Q, Zhou D, Feng J. Coordinate attention for efficient mobile network design[C]//Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2021: 13713-13722.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花花少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值