MobileNetv_3网络结构+代码

前言 

MobileNet系列由Google提出,2019年Google正式提出MobileNetv_3,论文题目为《Searching for MobileNetV3》

论文地址:https://arxiv.org/pdf/1905.02244.pdf

代码地址:https://hub.fastgit.org/xiaolai-sqlai/mobilenetv3hub.fastgit.org为Github的镜像网站,可加速进入)

图1   MobileNetV3 论文标题

MobileNetv_3是MnasNet的改进版,研究人员公布了MobileNetv_3的两个版本,MobileNetv_3-small和MobileNetv_3-Large,网络的亮点为:

  • 更新了bneck块
  • 使用NAS(Neural Architecture Search)搜索参数
  • 重新设计了耗时层结构

我们直接手撕代码来分析论文核心点。

 

重新设计激活函数

作者发现使用非线性函数swish时,能够显著提高神经网络的精度。swish函数定义如下:

图2   非线性swish函数表达式

其中,\delta (x) =\frac{1}{1+e^{-x}},即在分类中经常见到的sigmoid function。论文中提到“虽然swish这种非线性提高了精度,但它在嵌入式环境中需要成本,因为它在移动端设备上计算sigmoid function或对其求导代价高昂。作者想到了一个函数h-sigmoid(x)=\frac{ReLU6(x+3)}{6},可以解决sigmoid function带来的计算复杂和求导复杂。文中采用的激活函数为

图3   h-swish(x)函数表达式

激活函数图像如下:

图4   四种激活函数图像对比

代码如下:

class hswish(nn.Module):
    def forward(self, x):
        out = x * F.relu6(x+3, inplace=True) / 6
        return out

class hsigmoid(nn.Module):
    def forward(self, x):
        out = F.relu6(x + 3, inplace=True) / 6
        return out

 

加入SE模块

SE通道注意力机制,它是最后一届ImageNet冠军模型,原论文为《Squeeze-and-Excitation Networks》,论文链接:https://arxiv.org/pdf/1709.01507.pdf

一个SEblock可以分为 Squeeze(压缩) 和 Excitation(激发) 两个步骤:

  • Squeeze(压缩) :对 C×H×W 进行 global average pooling,得到 1×1×C 大小的特征图,这个特征图可以理解为具有全局感受野。
  • Excitation(激发) :通过两层全连接的bottleneck结构得到Feature Map中每个通道的权值,并将加权后的Feature Map作为下一层网络的输入。
图5   SE结构块

SE和Inception、ResNet结合后的模型图如下:

图6   SE同Inception和ResNet结合模型

SE的代码实现:

# 定义SE块
class SE_Module(nn.Module):
    def __init__(self, in_size, reduction=4):
        super(SE_Module, self).__init__()

        self.se = nn.Sequential(
            nn.AdaptiveAvgPool2d(1),
            nn.Conv2d(in_size, in_size // reduction, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(in_size // reduction),
            nn.ReLU(inplace=True),

            nn.Conv2d(in_size // reduction, in_size, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(in_size),
            nn.ReLU(inplace=True),

            hsigmoid(),
        )

    def forward(self, x):
        return x * self.se(x)
  • 代码中reduction为通道减小倍数,在MobileNetv_3中,假设进入SE模块时feature的通道为C,经过自适应平均池化后,feature map通道未发生改变,仍为C,在经过第一层FC后(其实就是做1×1卷积),通道变为C/reduction,论文中reduction=4。然后feature map再次经过一层FC(1×1卷积),通道数又变为C,即把特征通道数升回到原来的维度。再通入h-sigmoid激活函数中,得到C个取值在(0, 1)的权重值。最后通过scale操作将每个权重值加权到每个通道的特征上(就是把每个权重值分别乘以对应特征矩阵上的每个元素得到一个新的feature map)

 

  • nn.AvgPool2d()  VS nn.AdaptiveAvgPool2d() 
    • 第一个原型为 class torch.nn.AvgPool2d(kernel_size, stride=None, padding=0, ceil_mode=False, count_include_pad=True),意思是对信号的输入通道,提供2维的平均池化.

      • 参数介绍:

        • kernel_size(int or tuple) - 池化窗口大小
        • stride(int or tupleoptional) - max pooling的窗口移动的步长。默认值是kernel_size
        • padding(int or tupleoptional) - 输入的每一条边补充0的层数
        • dilation(int or tupleoptional) – 一个控制窗口中元素步幅的参数
        • ceil_mode - 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作
        • count_include_pad - 如果等于True,计算平均池化时,将包括padding填充的0
    • 第二个原型为 class torch.nn.AdaptiveAvgPool2d(output_size),对输入信号,提供2维的自适应平均池化操作 对于任何输入大小的输入,可以将输出尺寸指定为H*W,但是输入和输出特征的数目不会变化。
      • 参数介绍:
        • output_size: 输出信号的尺寸,可以用(H,W)表示H*W的输出,也可以使用耽搁数字H表示H*H大小的输出

 

 

Block设计

Mobilenet V3 block结构如下图所示:

图7   Mobilenet V3 block块结构

解释图7中一个block的执行流程:

输入特征首先经过1×1卷积进行升维,上升的维度为图10结构表中exp size参数,所使用的非线性激活函数也请参考图10中的NL列。接着将输出特征通入3×3的dw卷积,使用非线性激活函数NL,通道数exp size不发生改变。然后将输出特征通入SE块中,先对每个通道进行全局平均池化,得到1*1*exp size的向量,再将该向量通入第一层FC,激活函数为ReLU,得到输出为1*1*(exp size/4)。接着将输出通入第二层FC,激活函数为h-sigmoid,得到1*1*exp size的向量,其中向量内元素取值为(0,1),类似于概率值。将该向量中的每个元素乘以“输入到SE层的feature map”的对应通道特征矩阵,得到一个和feature map一样大小的新feature。最后进行1×1卷积降维处理,输出维度请参考图10中out列。

注意:只有当步长s=1且输入通道==输出通道时,才有图中的shortcut连接。

Mobilenet V2 block结构如下图所示:

图8   Mobilenet V2 倒残差结构

V3对比V2,可以发现V3的block块第一个1×1卷积后的激活函数为NL,即非线性激活函数,针对不同的层使用不同的激活函数。另外,V3在Dwise卷积之后引入了SE模块,具体网络实现细节还得参考网络结构。

block设计的代码如下:

class Bneck(nn.Module):
    '''expand + depthwise + pointwise'''
    def __init__(self, kernel_size, in_size, expand_size, out_size, nolinear, semodule, s):
        super(Bneck, self).__init__()

        self.stride = s
        self.se = semodule

        self.conv1 = nn.Conv2d(in_size, expand_size, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn1 = nn.BatchNorm2d(expand_size)
        self.nolinear1 = nolinear
        self.conv2 = nn.Conv2d(expand_size, expand_size, kernel_size=kernel_size, stride=s,
                               padding=kernel_size // 2, groups=expand_size, bias=False)
        self.bn2 = nn.BatchNorm2d(expand_size)
        self.nolinear2 = nolinear
        self.conv3 = nn.Conv2d(expand_size, out_size, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn3 = nn.BatchNorm2d(out_size)

        self.shortcut = nn.Sequential()
        if s == 1 and in_size != out_size:
            self.shortcut = nn.Sequential(
                nn.Conv2d(in_size, out_size, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(out_size),
            )

    def forward(self, x):
        out = self.nolinear1(self.bn1(self.conv1(x)))
        out = self.nolinear2(self.bn2(self.conv2(out)))
        out = self.bn3(self.conv3(out))
        if self.se != None:
            out = self.se(out)
        out += self.shortcut(x) if self.stride == 1 else out
        return out

 

重新设计耗时层结构

  • 减少第一个卷积层卷积核的个数(32 --> 16)
    • 作者发现使用16个卷积核代替之前的32个卷积核(V1和V2版本都是使用的32个卷积核),得到的精确度是一样的,那当然就选16个卷积核啊,因为可以减小参数量呀。作者在实验发现通过这一步优化后,能节省2毫秒的时间和1000万次MAdds运算。具体请参考网络结构表的第一行。
  • 简化Last Stage
    • 作者发现之前的Last Stage比较耗时,就把Original Last Stage改造成了Efficient Last Stage,改造后发现时间能减少7毫秒,这7毫秒占总运行时间的11%,而且MAdds运算次数也减少了许多,但精度几乎没有什么损失。
图9   Last Stage改进图

 

网络结构表

图10   MobileNetV3-Large结构表

上表为MobileNetV3-Large的结构表,对表解释如下:

Operator表示该层采用的操作,conv2d为标准卷积,bneck为上面介绍的V3的block块,后面的n×n表示使用的卷积核大小;

exp_size表示每一个bneck块中第一层1×1 conv2d的卷积核的个数,即对输入特征进行1×1卷积升维,上升的维度就是exp_size;

NBN表示该层卷积不使用Batch Normalization;

out表示输出特征通道数;

SE表示在哪几个bneck使用SE模块,图中打“√”的为使用SE;

NL表示每层所使用的非线性激活函数,其中HS表示h-swish函数,RE表示使用ReLu激活函数。

 

下表为MobileNetV3-Small的网络结构。相比MobileNetV3-Large的15层bneck块,MobileNetV3-Small比其少了4层,在参数量和计算量上也减少了许多。

图11   MobileNetV3-Small结构表

MobileNetV3-Large代码如下:

class MobileNetV3_Large(nn.Module):
    def __init__(self, num_classes=1000):
        super(MobileNetV3_Large, self).__init__()
        self.init_params()

        # 224*224**3 --> 112*112*16
        self.top = nn.Sequential(
            nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(16),
            hswish()
        )

        self.bneck = nn.Sequential(
            Bneck(kernel_size=3, in_size=16, expand_size=16, out_size=16, nolinear=nn.ReLU(True), semodule=None, s=1),
            Bneck(kernel_size=3, in_size=16, expand_size=64, out_size=24, nolinear=nn.ReLU(True), semodule=None, s=2),
            Bneck(kernel_size=3, in_size=24, expand_size=72, out_size=24, nolinear=nn.ReLU(True), semodule=None, s=1),

            Bneck(5, 24, 72, 40, nn.ReLU(True), SE_Module(40), 2),
            Bneck(5, 40, 120, 40, nn.ReLU(True), SE_Module(40), 1),
            Bneck(5, 40, 120, 40, nn.ReLU(True), SE_Module(40), 1),

            Bneck(3, 40, 240, 80, hswish(), None, 2),
            Bneck(3, 80, 200, 80, hswish(), None, 1),
            Bneck(3, 80, 184, 80, hswish(), None, 1),
            Bneck(3, 80, 184, 80, hswish(), None, 1),
            Bneck(3, 80, 480, 112, hswish(), SE_Module(112), 1),
            Bneck(3, 112, 672, 112, hswish(), SE_Module(112), 1),

            Bneck(5, 112, 672, 160, hswish(), SE_Module(160), 1),
            Bneck(5, 160, 672, 160, hswish(), SE_Module(160), 2),
            Bneck(5, 160, 960, 160, hswish(), SE_Module(160), 1),
        )

        self.bottom = nn.Sequential(
            nn.Conv2d(160, 960, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(960),
            hswish()
        )

        self.last = nn.Sequential(
            nn.Linear(960, 1280),
            nn.BatchNorm1d(1280),
            hswish()
        )

        self.linear = nn.Linear(1280, num_classes)

    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                init.constant_(m.weight, 1)
                init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                init.normal_(m.weight, std=0.001)
                if m.bias is not None:
                    init.constant_(m.bias, 0)

    def forward(self, x):
        out = self.top(x)
        out = self.bneck(out)
        out = self.bottom(out)

        out = F.avg_pool2d(out, 7)
        out = out.view(out.size(0), -1)

        out = self.last(out)
        out = self.linear(out)
        return out

 

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MobileNetV3是谷歌提出的一种轻量级卷积神经网络,主要用于移动端和嵌入式设备上的图像分类和目标检测任务。它是MobileNet系列的第三代,相比于MobileNetV2,MobileNetV3在准确率和计算效率上都有了更好的表现。 MobileNetV3共有三个版本:Small、Large和EdgeTPU。其中Small版本主要用于移动设备上的轻量级图像分类任务,Large版本则适用于高精度的图像分类和目标检测任务,EdgeTPU版本则是专门为Google的Edge TPU加速器设计的。 下面以MobileNetV3-Small为例介绍其网络结构MobileNetV3-Small网络结构主要包括三个部分:基础网络、中间层和顶层分类器。其中基础网络采用了轻量级卷积模块(Lightweight Convolutional Module,简称LCM)和倒置残差模块(Inverted Residual Block)的组合,可以有效降低模型的参数量和计算量。中间层采用了线性瓶颈(Linear Bottleneck)和注意力机制(Attention Mechanism)来进一步提升特征表示能力。顶层分类器则采用了自适应平均池化(Adaptive Average Pooling)和SE模块(Squeeze-and-Excitation Module)来增强分类器的泛化能力。 MobileNetV3-Small的网络结构如下图所示: ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12759958/1622613475426-0d8c1d0b-7a12-4d1a-8e0c-2b7d3c3b2f8a.png) 其中,每个L表示一个轻量级卷积模块(LCM),每个MB表示一个倒置残差模块(Inverted Residual Block),每个IBN表示一个线性瓶颈(Linear Bottleneck),每个SE表示一个SE模块(Squeeze-and-Excitation Module)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值