GhostNet
论文地址:https://arxiv.org/abs/1911.11907
Pytorch实现代码:https://github.com/iamhankai/ghostnet.pytorch
灵感来源
作者发现将ResNet-50第一层残差网络的feature map可视化后,发现有大量冗余相似的feature map,这些冗余特征可以保证对输入数据有全面的理解。下图中配对的feature map就非常相近,当前情况用feature map A经过卷积后得到feature map B。
现在产生的想法是通过一系列固有特征(intrinsic feature maps),也就是feature map A。不经过卷积操作,而是使用更加廉价的线性运算得到大量的feature map B。这些大量的特征更有助于揭示固有特征的信息。

GhostModule
论文中提出新的模块,叫做GhostModule。既然普通卷积提取的feature map有大量冗余的部分。那么只需生成少数固有特征feature map A,再用feature map A通过廉价的线性变化得到大量的feature map B不就行了。
红色部分就是提取那些固有特征feature map A的过程。而绿色部分表示利用固有特征进行线性变换得到大量的feature map B,该操作针对的是单个通道,类似深度可分离卷积中的深度卷积。最后参考ResNet的skip connect,进行通道维度的叠加。Output的通道数是固有特征的通道数的s倍,故剩余其他部分是固有特征的(s-1)倍。s为一个超参数,下图为不同的s的参数量和准确率。


实现代码:黄色部分指红框中的固定特征,红色部分指Output底下的feature map。
class GhostModule(nn.Module):
def __init__(self, in_channel, out_channel, kernel_size=1, ratio=2, dw_size=3, stride=1, relu=True):
super(GhostModule, self).__init__()
self.out_channel = out_channel
mid_channel = math.ceil(out_channel / ratio) # 黄色部分通道数
rest_channels = mid_channel * (ratio - 1) # 剩余红色部分通道数
# 生成黄色部分
self.primary_conv = nn.Sequential(
nn.Conv2d(in_channel, mid_channel, kernel_size, stride, kernel_size // 2, bias=False),
nn.BatchNorm2d(mid_channel),
nn.ReLU6(inplace=True)
)
# 黄色部分提取剩余红色部分
self.cheap_operation = nn.Sequential(
nn.Conv2d(mid_channel, rest_channels, dw_size, 1, dw_size // 2, groups=mid_channel, bias=False),
nn.BatchNorm2d(rest_channels)
)
self.bn = nn.BatchNorm2d(out_channel)
if relu:
self.relu = nn.ReLU6(inplace=True)
else:
self.relu = None
def forward(self, x):
x1 = self.primary_conv(x)
x2 = self.cheap_operation(x1)
out = torch.cat([x1, x2], dim=1)
out = out[:, :self.out_channel, :, :]
out = self.bn(out)
if self.relu != None:
out = self.relu(out)
return out
参数量和计算量与标准卷积核进行比较:

其中卷积核大小KxK与dxd大小相近,即K约等于d。且比值S远小于C,那么两者的参数量对比为

Ghost bottleneck
Ghost bottleneck由GhostModule组成,并且也有残差网络的skip connect的部分。但是downsample采用的是深度可分离卷积,不是ResNet的传统卷积核。

代码部分:
class GhostBottleneck(nn.Module):
def __init__(self, in_channel, mid_channel, out_channel, kernel_size, stride, use_se):
super(GhostBottleneck, self).__init__()
assert stride in [1, 2]
self.conv = nn.Sequential()
self.conv.add_module('GhostModule1', GhostModule(in_channel, mid_channel, kernel_size=1, relu=True))
if stride == 2:
self.conv.add_module('DWconv', depthwise_conv(mid_channel, mid_channel, kernel_size, stride,
relu=False) if stride == 2 else nn.Sequential())
if use_se:
self.conv.add_module('se block', SE_block(mid_channel))
self.conv.add_module('GhostModule2', GhostModule(mid_channel, out_channel, kernel_size=1, relu=False))
if stride == 1 and in_channel == out_channel:
self.downsample = None
else:
self.downsample = nn.Sequential(
depthwise_conv(in_channel, in_channel, 3, stride, relu=True),
nn.Conv2d(in_channel, out_channel, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(out_channel),
)
def forward(self, x):
if self.downsample == None:
return self.conv(x) + x
else:
return self.conv(x) + self.downsample(x)
最终的主体网络其实就是由一个个Ghost bottleneck搭建而成

可视化feature map
作者还可视化了Ghost模块的特征图,下图展示了Ghost-VGG-16的第二层特征,左上方的图像是输入,红色框中的特征图来自初始卷积,而绿色框中的特征图是经过廉价深度变换后的特征图。尽管生成的特征图来自原始特征图,但它们之间确实存在显着差异,这意味着生成的特征足够灵活,可以满足特定任务的需求。

问题
文中提到普通卷积提取的feature map存在冗余的部分,那么如何判断ghost module中convolution操作输出的feature maps(即下图红框中卷积得到的feature map)不含有冗余的部分呢?如何确定该部分filter的channel数以保证fm不含有冗余的fm?

答案是没法保证
参考博客
论文作者王云鹤的知乎回答:https://www.zhihu.com/search?type=content&q=GhostNet
https://blog.csdn.net/weixin_44317740/article/details/104546632#comments