简单的记录一下对ghostnet的学习。作者大大自己写的文章CVPR 2020华为GhostNet超越谷歌MobileNet已开源 - 知乎 (zhihu.com)1、要解决的问题作者提出在一个训练好的神经网络中会存在很多的冗余特征如下图图出自论文而这些冗余的特征可以通过一些廉价的操作从别的特征图变换中得到。因此作者的想法是通过卷积得到一部分的特征然后通过一些廉价的操作从这些特征图中得到冗余的特征。2、ghost卷积卷积过程1先通过卷积生成一部分的特征图。2对每个通道通过廉价的线性变换其实就是对每个通道进行一次卷积等价于深度卷积。3将第一步生成的特征图与第二步生成的特征图拼接到一起。3、ghost bottleneck:1stride 1的情况当stride1的时候不进行下采样特征图的宽高保持不变。这一部分思想类似mobilenetV2先通过一个Ghost module用作扩展层增加特征的通道数扩展出冗余的特征这是为了防止低维度的特征在ReLU激活的时候造成信息丢失。经过ReLU激活之后使用一个Ghost module降低特征维度减少计算量。1stride 2的情况当stride2的时候对特征进行下采样。两个Ghost module作用和stride1的时候是相同的除此之外在两个Ghost module之间加入一个stride2的DWConv实现下采样的功能。代码如下 Creates a GhostNet Model as defined in: GhostNet: More Features from Cheap Operations By Kai Han, Yunhe Wang, Qi Tian, Jianyuan Guo, Chunjing Xu, Chang Xu. https://arxiv.org/abs/1911.11907 Modified from https://github.com/d-li14/mobilenetv3.pytorch import torch import torch.nn as nn import math __all__ [ghost_net] def _make_divisible(v, divisor, min_valueNone): This function is taken from the original tf repo. It ensures that all layers have a channel number that is divisible by 8 It can be seen here: https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py if min_value is None: min_value divisor new_v max(min_value, int(v divisor / 2) // divisor * divisor) # Make sure that round down does not go down by more than 10%. if new_v 0.9 * v: new_v divisor return new_v class SELayer(nn.Module): def __init__(self, channel, reduction4): super(SELayer, self).__init__() self.avg_pool nn.AdaptiveAvgPool2d(1) self.fc nn.Sequential( nn.Linear(channel, channel // reduction), nn.ReLU(inplaceTrue), nn.Linear(channel // reduction, channel), ) def forward(self, x): b, c, _, _ x.size() y self.avg_pool(x).view(b, c) y self.fc(y).view(b, c, 1, 1) y torch.clamp(y, 0, 1) return x * y def depthwise_conv(inp, oup, kernel_size3, stride1, reluFalse): return nn.Sequential( nn.Conv2d(inp, oup, kernel_size, stride, kernel_size//2, groupsinp, biasFalse), nn.BatchNorm2d(oup), nn.ReLU(inplaceTrue) if relu else nn.Sequential(), ) class GhostModule(nn.Module): def __init__(self, inp, oup, kernel_size1, ratio2, dw_size3, stride1, reluTrue): super(GhostModule, self).__init__() 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, biasFalse), nn.BatchNorm2d(init_channels), nn.ReLU(inplaceTrue) if relu else nn.Sequential(), ) self.cheap_operation nn.Sequential( nn.Conv2d(init_channels, new_channels, dw_size, 1, dw_size//2, groupsinit_channels, biasFalse), nn.BatchNorm2d(new_channels), nn.ReLU(inplaceTrue) if relu else nn.Sequential(), ) def forward(self, x): x1 self.primary_conv(x) x2 self.cheap_operation(x1) out torch.cat([x1,x2], dim1) return out[:,:self.oup,:,:] class GhostBottleneck(nn.Module): def __init__(self, inp, hidden_dim, oup, kernel_size, stride, use_se): super(GhostBottleneck, self).__init__() assert stride in [1, 2] self.conv nn.Sequential( # pw GhostModule(inp, hidden_dim, kernel_size1, reluTrue), # dw depthwise_conv(hidden_dim, hidden_dim, kernel_size, stride, reluFalse) if stride2 else nn.Sequential(), # Squeeze-and-Excite SELayer(hidden_dim) if use_se else nn.Sequential(), # pw-linear GhostModule(hidden_dim, oup, kernel_size1, reluFalse), ) if stride 1 and inp oup: self.shortcut nn.Sequential() else: self.shortcut nn.Sequential( depthwise_conv(inp, inp, kernel_size, stride, reluFalse), nn.Conv2d(inp, oup, 1, 1, 0, biasFalse), nn.BatchNorm2d(oup), ) def forward(self, x): return self.conv(x) self.shortcut(x) class GhostNet(nn.Module): def __init__(self, cfgs, num_classes1000, width_mult1.): super(GhostNet, self).__init__() # setting of inverted residual blocks self.cfgs cfgs # building first layer output_channel _make_divisible(16 * width_mult, 4) layers [nn.Sequential( nn.Conv2d(3, output_channel, 3, 2, 1, biasFalse), nn.BatchNorm2d(output_channel), nn.ReLU(inplaceTrue) )] input_channel output_channel # building inverted residual blocks block GhostBottleneck for k, exp_size, c, use_se, s in self.cfgs: output_channel _make_divisible(c * width_mult, 4) hidden_channel _make_divisible(exp_size * width_mult, 4) layers.append(block(input_channel, hidden_channel, output_channel, k, s, use_se)) input_channel output_channel self.features nn.Sequential(*layers) # building last several layers output_channel _make_divisible(exp_size * width_mult, 4) self.squeeze nn.Sequential( nn.Conv2d(input_channel, output_channel, 1, 1, 0, biasFalse), nn.BatchNorm2d(output_channel), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d((1, 1)), ) input_channel output_channel output_channel 1280 self.classifier nn.Sequential( nn.Linear(input_channel, output_channel, biasFalse), nn.BatchNorm1d(output_channel), nn.ReLU(inplaceTrue), nn.Dropout(0.2), nn.Linear(output_channel, num_classes), ) self._initialize_weights() def forward(self, x): x self.features(x) x self.squeeze(x) x x.view(x.size(0), -1) x self.classifier(x) return x def _initialize_weights(self): for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, modefan_out, nonlinearityrelu) elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() def ghost_net(num_classes1000): Constructs a GhostNet model cfgs [ # k, t, c, SE, s [3, 16, 16, 0, 1], [3, 48, 24, 0, 2], [3, 72, 24, 0, 1], [5, 72, 40, 1, 2], [5, 120, 40, 1, 1], [3, 240, 80, 0, 2], [3, 200, 80, 0, 1], [3, 184, 80, 0, 1], [3, 184, 80, 0, 1], [3, 480, 112, 1, 1], [3, 672, 112, 1, 1], [5, 672, 160, 1, 2], [5, 960, 160, 0, 1], [5, 960, 160, 1, 1], [5, 960, 160, 0, 1], [5, 960, 160, 1, 1] ] model GhostNet(cfgs, num_classesnum_classes) return model if __name____main__: model ghost_net(num_classes10) input torch.randn(1,3,224,224) out model(input) print(out.shape)