美文网首页
Classification基础实验系列三——MobileNet

Classification基础实验系列三——MobileNet

作者: 吃远 | 来源:发表于2019-02-01 13:20 被阅读0次

一、概述

  本文记录MobileNet论文的阅读笔记以及代码实现。其实本来是想直接看MobileNet v2的,结果v2的论文太难了看不大懂...所以先来看看v1吧🐶

  首先根据知乎大佬的说法,MobileNet并非2017年的创作,而是Google此前两年就有的工作,只是文章一直没发表而已。这也解释了为什么MobileNet看起来是一种非常复古的“直筒结构”,性价比不高。关于“性价比”,大佬是这么解释的:


  “后续一系列的ResNet, DenseNet等结构已经证明通过复用图像特征, 使用concat/eltwise+ 等操作进行融合, 能极大提升网络的性价比”


  其次MobileNet中的Depthwise Conv在训练过程中也有一些问题。这个等到写MobileNet v2笔记时再进一步分析。

1.1 论文主要内容

  本文主要分为三个方面的内容:

  1. 介绍Depthwise Separable Convolution(深度可分离卷积)以及其在计算量方面相对于传统卷积的优势;
  2. 提出了两个超参数Width Multiplier αResolution Multiplier ρ,用于进一步压缩模型的复杂度,减小参数数量。
  3. 大量的实验结果用于对比Depthwise和普通卷积,以及比较不同Multiplier下模型的复杂度和准确率。

二、论文阅读

2.1 Depthwise Separable Convolution

  主要思想是对传统卷积做了一个分解——将传统卷积分成depthwise和pointwise两步来完成。其中depthwise就是对输入特征的每个通道单独使用一个filter,然后对得到的若干个输出通道再使用1x1conv进行组合,得到新特征,即pointwise Conv。第二步也体现了1x1 Conv跨通道整流的特点。

  论文中对此的解释是,标准卷积中,滤波和滤波之后特征的线性组合可以看做两个步骤,深度可分离卷积就是将这两个步骤分开进行。

  值得注意的一点是,这种卷积背后的假设是跨channel相关性和跨spatial相关性的解耦。而这种假设目前还无法证明,但是实际实验结果表明深度可分离卷积确实可以减小参数数量。参考上面知乎帖子大佬的回答:


近两年是深度可分离卷积(Depth-wise Separable Convolution)大红大紫的两年,甚至有跟ResNet的Skip-connection一样成为网络基本组件的趋势。Xception论文指出,这种卷积背后的假设是跨channel相关性和跨spatial相关性的解耦。应用深度可分离卷积的另一个优势即是参数量的节省(这一点其实也是解耦的结果,参数描述上享受了正交性的乘法增益)。然而,这一假设的成立与否还是存疑的,目前我们也没有足够的工具去描述和证明这一假设。


  下面的草图展示了depthwise和pointwise两个层及其计算量。


Fig. 1. 深度可分离卷积示意图

  由于标准卷积的计算量为D_k·D_k·M·N·D_F·D_F,用上图的式子除以该式,结果为 1/N + 1/D_k^2(忽略D_G和D_F之间的差距)由于网络中用的卷积核大小D_k均为3x3,使用深度可分离卷积的参数量约为标准卷积的1/9-1/8

Fig. 2. Depthwise和Pointwise视为两层,后面各接一组BN和ReLU

  除此之外,论文中还提到了一个实现细节,就是深度可分离卷积中大部分运算量都来自1x1 Conv,而1x1 Convs "do not require this reordering in memory and can be implemented directly with GEMM which is one of the most optimized numerical linear algebra algorithms.",这里的reordering就是经典的im2col,即caffe中实现卷积的方法。就是说如果空间维度大于1的卷积,为了使用高级优化方法GEMM,需要先用im2col进行矩阵重组。而1x1 Conv节省了这一步,故文章中说1x1 Conv是线性代数数值计算中的最优算法之一。详细了解im2col可以见这篇博客。注意caffe中是按行优先,而Matlab是按列优先。

Fig. 3. im2col与reordering

2.2 Width Multiplier与Resolution Multiplier

  • Width Multiplier
    对各层使用同一个缩放因子α来减少滤波器个数,使输入通道数变为αM,输出通道数变为αNα通常在1, 0.75, 0.5, 0.25中选择。1为标准MobileNet,其他三个为精简的MobileNet。节省的计算量约为 α^2

  • Resolution Multiplier
    对网络输入尺寸使用一个缩放因子ρ,进而使得每一层feature map尺寸都变成原来的ρ倍。这个缩放因子实际上就是选择不同的输入尺寸,没有像Width Multiplier那样显式设置。该因子节省的计算量也约为ρ^2

2.3 实验分析

2.3.1 深度可分离卷积带来的性能下降可以接收
Fig. 4. 将MobileNet中的卷积全部换成普通卷积,对比结果
2.3.2 Shallower or thinner?

  在对原始MobileNet进行进一步压缩时,一个问题是为什么我们要使用Width Multiplier,而不是简单地减少模型的层数?文章通过实验证明,使用α缩减网络比减小网络层数性能要好。

Fig. 5. Narrow v.s. Shallow MobileNet
2.3.3 Width Multiplier可以设多大?

  这个应该是大家最关心的实验了。本实验证明在α从1降到0.5的过程中,模型性能是平滑下降的;当α从0.5降到0.25时,性能大幅下降。可见α对模型性能影响较小,通常是一个值得尝试的选择。

Fig. 6. α的影响
2.3.4 输入分辨率的影响?

  随着输入分辨率的降低,模型性能的下降趋势较平滑:


Fig. 7. ρ的影响
2.3.5 其他实验

  本文还比较了MobileNet与其他网络的性能。比如1.0MobileNet-224要强于GoogLeNet,稍逊于VGG16,但是相比VGG16,模型大小缩小了32倍,计算量少了27倍!另外,0.5 MobileNet-160的性能就超过SqueezeNet和AlexNet了。

三、代码实现

3.1 Gluon中Conv2D

  Gluon中nn.Conv2D提供了便利的接口,即通过设置groups参数,可以指定卷积进行的方式。见下方的参数说明


Fig. 8. Conv2D参数
  • 这个参数介绍信息量还是很大的。值得注意的有几个地方:
  1. 通常一种比较省事的做法是使用Conv2D时不指定input_channels,不过这样定义完网络之后,模型的初始化会被推迟进行,即当模型第一次call forward函数时才会根据输入图像尺寸推断input_channels进而初始化网络参数。这样可能会影响model.summary()等函数的使用。
  2. 看起来Conv2D也提供了扩张卷积的接口
  3. groups参数:控制输入特征图和输出特征图的连接方式。默认groups=1,即卷积核的深度和输入通道深度相同。如果取2,则效果相当于将输入特征按通道那一维分成两半,每一半与一个group进行卷积,输出的特征图concat得到一个特征图。因此,如果将groups参数设置为同输入通道数目一致,就是Depthwise Convolution。

  首先我做了一个小实验来对比普通卷积和groups>1的卷积,来验证该参数是否可用于简便地实现depthwise conv。实验虽然选取了较小的输入feature map和卷积核,但是输出结果还是比较冗长。有兴趣的可以直接看代码,后面MobileNet的搭建以及训练实验也在里面。这里直接放上结论:

  • 可以使用Gluon提供的groups接口实现Depthwise Conv。 只需要设置num_in_channels(in_channels)==num_out_channels(channels)==groups,当然,同普通卷积,in_channels参数也可以不显式地指定。不过这样模型初始化会延后到第一次model.forward之后进行。

3.2 实现MobileNet

  

from mxnet import nd
import numpy as np
from mxnet.gluon import nn
import mxnet as mx
from mxnet import gluon

class ConvBlock(nn.HybridBlock):
    def __init__(self, in_channels, channels, strides, padding, num_sync_bn_devices=-1, multiplier=1.0):
        super(ConvBlock, self).__init__()
        self.conv_block = nn.HybridSequential()
        with self.conv_block.name_scope():
            self.conv_block.add(nn.Conv2D(int(channels*multiplier), 3, strides, padding, 
                                          in_channels=in_channels, use_bias=False))
            if num_sync_bn_devices == -1:
                self.conv_block.add(nn.BatchNorm())
            else:
                self.conv_block.add(gluon.contrib.nn.SyncBatchNorm(num_devices=num_sync_bn_devices))
            self.conv_block.add(nn.Activation('relu'))
    def hybrid_forward(self, F, x):
        return self.conv_block(x)

class DepthwiseSeperable(nn.HybridBlock):
    def __init__(self, in_channels, channels, strides, num_sync_bn_devices=-1, multiplier=1.0, **kwags):
        # Weidth Multiplier
        in_channels = int(in_channels * multiplier)
        channels = int(channels * multiplier)
        super(DepthwiseSeperable, self).__init__(**kwags)
        self.depthwise = nn.HybridSequential()
        with self.depthwise.name_scope():
            self.depthwise.add(nn.Conv2D(in_channels, 3, strides, padding=1,groups=in_channels, 
                                         in_channels=in_channels, use_bias=False))
            if num_sync_bn_devices == -1:
                self.depthwise.add(nn.BatchNorm())
            else:
                self.depthwise.add(gluon.contrib.nn.SyncBatchNorm(num_devices=num_sync_bn_devices))
            self.depthwise.add(nn.Activation('relu'))
            
        self.pointwise = nn.HybridSequential()
        with self.pointwise.name_scope():
            self.pointwise.add(nn.Conv2D(channels, 1, in_channels=in_channels, use_bias=False))
            if num_sync_bn_devices == -1:
                self.pointwise.add(nn.BatchNorm())
            else:
                self.pointwise.add(gluon.contrib.nn.SyncBatchNorm(num_devices=num_sync_bn_devices))
            self.pointwise.add(nn.Activation('relu'))
       
    def hybrid_forward(self, F, x):
        return(self.pointwise(self.depthwise(x)))

class MobileNet(nn.HybridBlock):
    def __init__(self, num_classes, n_devices=2, multiplier=1.0, **kwags):
        super(MobileNet, self).__init__(**kwags)
        self.net = nn.HybridSequential()
        self.net.add(ConvBlock(3, 32, 2, 1, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(32, 64, 1, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(64, 128, 2, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(128, 128, 1, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(128, 256, 2, n_devices, multiplier))

        self.net.add(DepthwiseSeperable(256, 256, 1, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(256, 512, 2, n_devices, multiplier))
        for _ in range(5):
            self.net.add(DepthwiseSeperable(512, 512, 1, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(512, 1024, 2, n_devices, multiplier))
        self.net.add(DepthwiseSeperable(1024, 1024, 1, n_devices, multiplier))

        self.net.add(nn.GlobalAvgPool2D())
        self.net.add(nn.Dense(num_classes))

    def hybrid_forward(self, F, x):
        return self.net(x)
  • 可以用下面这段小代码检查下上面定义的模型:
net = MobileNet(num_classes=1000, n_devices=2, multiplier=1.0)
net.initialize(mx.init.Xavier())
test_input = nd.random_normal(shape=(1, 3, 224, 224))
net.summary(test_input)
  • 也可以使用mxnet.viz.plot_networks打印出模型的graph,不过需要先将模型转换为symbol:
net.hybridize()
output = net.forward(test_input)
# export之后同时生成了一个.json文件和.params文件
net.export('Gluon-MobileNet')
symnet = mx.symbol.load('Gluon-MobileNet-symbol.json')
mx.viz.plot_network(symnet, title='mobilnet_viz', shape={'data':(1, 3, 224, 224)})

图片太长这里就不放了。可以到notebook中去看。

3.3 Experiment on CIFAR10

超参数设置:

OPTIMIZER: 'nag'
BATCH_SIZE = 64  # per gpu
EPOCHS = 200
LR = 1e-1
WD = 5e-4
MOMENTUM = 0.9
lr_decay_dict = {40:0.1, 80:0.1, 120:0.1}

数据增强:

  • 训练数据:随机左右翻转;归一化到0-1;减去数据集均值,除以标准差;
  • 测试数据:归一化到0-1;减去数据集均值,除以标准差;

结果:


Fig. 9. Training Curve
Fig. 10. Classification Report
  • 一个奇怪的现象是模型在第二次decay 0.1时的第二或第三个epoch开始(图中大概第80个epoch处)会迅速开始过拟合。看起来模型的训练受学习率衰减的影响非常大。

相关文章

网友评论

      本文标题:Classification基础实验系列三——MobileNet

      本文链接:https://www.haomeiwen.com/subject/xdirsqtx.html