论文链接:GhostNet: More Features from Cheap Operations
开源地址:https://github.com/huawei-noah/ghostnet
1 Ghost
Ghost模块是一种实现轻量级神经网络的方法,使得深度神经网络可以在保证算法表现能力的基础上将网络移植到一些计算能力相对较弱的移动设备上(如智能手机等)。整体方向是减少网络模型参数数量及浮点数运算次数(FLOPs)。

上图是普通的卷积层,将Input经过卷积运算得到Output。
下图是将一张小猫图片输入到ResNet50
网络中经过第一个Resdual block
后的特征图可视化结果。可以看出其中的有些特征图非常相似,如下图中的红、绿、蓝框,圈出来的这些框就是论文中提到的(the redundancy in feature maps,特征图冗余)。这些相似的特征图可以通过廉价的线性运算互相转换,故下图中的小扳手就是Ghost模块。

Output的通道数是由参与卷积操作的filter
的数量来决定的,这样一来,我们可以减少filter的数量,通过廉价的线性转换同样可以生成类似的结果,从而减少了网络模型参数以及FLOPs,以至于实现模型的轻量化。
2 Ghost module

上图中intrinsic feature maps
是通过普通卷积操作得到的,Ghost模块用于将 intrinsic feature maps 通过一系列廉价的线性转换生成更多的ghost feature maps
,生成的这些ghost feature maps可以极大地展示 intrinsic feature maps 中潜在包含的特征信息。
上图中的这些Φ
表示之前所说的廉价的线性转换操作,那么它具体是什么呢?
Φ其实是 3×3
、5×5
大小的内核卷积,因为普通的filter在进行卷积时,filter的通道数与输入的通道数必须保持一致,故相比于普通的filter进行卷积操作,仅仅在一个通道上进行卷积运算,其计算成本极大降低。
从 intrinsic feature maps 生成 ghost feature maps的公式如下:

其中 yi’表示 intrinsic feature maps 中的第i个特征图,i
表示m
个intrinsic feature maps中的序列号,j
表示对于每个intrinsic feature maps中的特征图进行的第 j
次线性转换。从上图公式中可以看出,对于 intrinsic feature maps 中的每一个特征图,都可以进行 s-1
次的线性转换(还有一次是上图中的Identity
操作),从而Ghost模块将生成(s-1)×m+m=s×m=n
个ghost feature maps(假设普通卷积操作的结果是生成 n 个特征图)
class GhostModule(nn.Module):
def __init__(self, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):
super(GhostModule, self).__init__()
self.oup = oup
init_channels = math.ceil(oup / ratio) # ratio = oup / intrinsic
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, padding=dw_size//2, groups=init_channels, bias=False), # groups 分组卷积
nn.BatchNorm2d(new_channels),
nn.ReLU(inplace=True) if relu else nn.Sequential(),
)
def forward(self, x):
x1 = self.primary_conv(x)
x2 = self.cheap_operation(x1)
out = torch.cat([x1,x2], dim=1)
return out[:,:self.oup,:,:]
量化衡量引入Ghost的加速比:

其中d×d
表示线性转换的内核卷积的平均大小,公式中d×d
与k×k
有相同的数量级,故可约去;s<<c
,故结果可简化为s
;
量化衡量引入Ghost的压缩比:

可以看到引入Ghost后的加速比和压缩比均为s
。
3 Ghost Bottlenecks:
还记得Resdua Block
是长这个样子的:

而Ghost Bottlenecks
是长这个样子的:

可以看出仅仅把Resdual Block
中的卷积层改为Ghost module
即可。上图中左图是用于Stride = 1
的情况;右图是用于Stride = 2
的情况,其中加入了Depthwise convolution(stride=2)
来减半特征图大小,且shortcut
也需要经过一次下采样层来将输入的特征图大小减半。(注意在第二个Ghost Module之后没有使用ReLU)
4 GhostNet
GhostNet参考了MobileNetV3
的结构,将MobileNetV3中的bottlenect block
改为Ghost Bottlenecks
,并且使用了SE
结构(文后解释)。下图中#exp
为Ghost Bottleneck中第一个Ghost module的输出特征图数量。

5 ghostnet中用到的SEblock结构
Sequeeze-and-Excitation(SE) block
并不是一个完整的网络结构,而是一个结构快,可以嵌入到其他神经网络模型中。SEblock
的核心思想在于通过网络根据loss
去学习特征权重,使得有效的feature map
权重大,无效或效果小的feature map权重小的方式训练模型达到更好的结果。

SEblock的工作就是将上图中左边的X
特征图经过一系列运算得到上图中右边彩色的x̅
,可将整个运算过程分为四步,接下来结合论文中的公式对这四个步骤做一个总结:
- Ftr:Ftr这一步是转换操作,实际上它并不属于SEblock,在原网络中这一步仅仅是一个标准的卷积操作而已;且后面可以看到在定义SqueezeExcite函数时没有这一步操作。
- Fsq:
x
经过 Ftr得到U
,U
为C个大小为H×W
的特征层,而uc表示U
中第c个二维矩阵特征层,下标c表示channel。接下来是Squeeze操作,也就是简单的一个global average pooling
:

所以 Fsq 操作将H×W×C
的输入转换成1×1×C
的输出,这一步相当于获取C个特征层的数值分布情况,或者叫全局信息。
-
Fex:这一步操作即是
Excitation
操作,计算过程将括号从里向外看。首先将第2步的结果Z乘以一个系数矩阵W1
,这是一个简单的全连接层操作,W1的维度是C/r × C
,这个r是一个缩放参数,目的是为了减少channel个数从而降低计算量z的维度是
1×1×C
,所以W1×z
的结果就是1×1×C/r
;然后再经过一个ReLU
层,输出维度不变;然后再与W2相乘,这也是一个全连接层操作。W2的维度是C×C/r
,因此输出的维度是1×1×C
;最后再经过sigmoid
函数得到结果s。这两个全连接层的作用就是融合各通道的feature map信息。

-
Fscale:第三步得到的结果s的维度是
1×1×C
。这个s其实是SEblock的核心,用它来刻画特征层U中的C个feature map的权重,而且这个权重是通过前面这些全连接层和非线性层学习得到的,因此可以end-to-end训练。在得到s之后,就可以对原来的特征层U进行操作,即
channel-wise multiplication
。uc是一个二维矩阵,sc是一个数,也就是权重,因此相当于把uc矩阵中的每个值都乘以sc,故特征层U中的C个特征图都被一个权重系数相乘。

经过以上四步后,将原始特征层X
的每个通道分别乘以对应的权重系数,使得有效的特征图占比更大,无效或效果较小的特征图占比降低。整体来说仅仅是将特征图内的数值进行了调整,其大小及通道数均不发生改变。
欢迎关注【OAOA】