0%

MobileNet 系列阐述和Gluon实现

GalileoMoons

简介

深度学习在图像分类,目标检测和图像分割等任务表现出了巨大的优越性。

但是伴随着模型精度的提升是计算量,存储空间以及能耗方面的巨大开销,对于移动或车载应用都是难以接受的。

之前的一些模型小型化工作是将焦点放在模型的尺寸上。

因此,在小型化方面常用的手段有:

(1)卷积核分解,使用1×N和N×1的卷积核代替N×N的卷积核

(2)使用bottleneck结构,以SqueezeNet为代表

(3)以低精度浮点数保存,例如Deep Compression

(4)冗余卷积核剪枝及哈弗曼编码

MobileNet进一步深入的研究了depthwise separable convolutions使用方法后设计出MobileNet,depthwiseseparable convolutions的本质是冗余信息更少的稀疏化表达。在此基础上给出了高效模型设计的两个选择:宽度因子(width multiplier)和分辨率因子(resolutionmultiplier);通过权衡大小、延迟时间以及精度,来构建规模更小、速度更快的MobileNet。Google团队也通过了多样性的实验证明了MobileNet作为高效基础网络的有效性。

一、深度可分离卷积

标准的卷积过程可以看上图,一个2×2的卷积核在卷积时,对应图像区域中的所有通道均被同时考虑,问题在于,为什么一定要同时考虑图像区域和通道?我们为什么不能把通道和空间区域分开考虑?

深度可分离卷积提出了一种新的思路:对于不同的输入channel采取不同的卷积核进行卷积,它将普通的卷积操作分解为两个过程。

卷积过程

假设有 N x H x W x C 的输入,同时有 k3x3 的卷积。如果设置pad=1stride=1,那么普通卷积输出为 N x H x W x k

Depthwise 过程

Depthwise是指将 N x H x W x C 的输入分为 group=C 组,然后每一组做3x3 卷积。这样相当于收集了每个Channel的空间特征,即Depthwise特征。

Pointwise 过程

Pointwise是指对 N x H x W x C 的输入做 k1x1 卷积。这样相当于收集了每个点的特征,即Pointwise特征。Depthwise+Pointwise最终输出也是 N x H x W x k

深度可分离卷积结构

二、优势与创新

Depthwise+Pointwise可以近似看作一个卷积层:

  • 普通卷积:3x3 Conv+BN+ReLU
  • Mobilenet卷积:3x3 Depthwise Conv+BN+ReLU 和 1x1 Pointwise Conv+BN+ReLU

计算加速

参数量降低

假设输入通道数为3,要求输出通道数为256,两种做法:

1.直接接一个3×3×256的卷积核,参数量为:3×3×3×256 = 6,912

2.DW操作,分两步完成,参数量为:3×3×3 + 3×1×1×256 = 795(3个特征层(33的卷积核)),卷积深度参数通常取为1

乘法运算次数降低

对比一下不同卷积的乘法次数:

  • 普通卷积计算量为: HxWxCxkx3x3
  • Depthwise计算量为: HxWxCx3x3
  • Pointwise计算量为: HxWxCxk

通过Depthwise+Pointwise的拆分,相当于将普通卷积的计算量压缩为:
$$
\frac{\text {depthwise }+\text { pointwise }}{\operatorname{conv}}=\frac{H \times W \times C \times 3 \times 3+H \times W \times C \times k}{H \times W \times C \times k \times 3 \times 3}=\frac{1}{k}+\frac{1}{3 \times 3}
$$

通道区域分离

深度可分离卷积将以往普通卷积操作同时考虑通道和区域改变(卷积先只考虑区域,然后再考虑通道),实现了通道和区域的分离。

三、MobileNetV1 Gluon实现

可以自行输入查看打印的pdf的代码参数。

![](MobileNet architecture.png)

下面的Gluon的实现参考的Insightface_fmobilenet.py 。具体自行查看,注意num_filternum_group之间的关系。例如,conv2_dwnum_group 等于 conv1num_filter, conv2_dwnum_filterconv2_dwnum_group 的整数倍,依次类推。 注意分组卷积输出的是一个tuple类型,长度为1,所以可以看到下面Depthwise_Separable_conv 函数中第二个1x1的卷积的输入写的是conv_dw[0]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# coding: gbk
import sys
import os
import mxnet as mx
import symbol_utils

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from config import config


def Act(data, act_type, name):
# ignore param act_type, set it in this function
if act_type == 'prelu':
body = mx.sym.LeakyReLU(data=data, act_type='prelu', name=name)
else:
body = mx.sym.Activation(data=data, act_type=act_type, name=name)
return body


def Conv(data, num_filter=1, kernel=(1, 1), stride=(1, 1), pad=(0, 0), num_group=1, name=None, suffix=''):
conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel, num_group=num_group, stride=stride,
pad=pad, no_bias=True, name='%s%s_conv2d' % (name, suffix))
bn = mx.sym.BatchNorm(data=conv, name='%s%s_batchnorm' % (name, suffix), fix_gamma=True)
act = Act(data=bn, act_type=config.net_act, name='%s%s_relu' % (name, suffix))
return act


def Depthwise_Separable_conv(bf, data, stage_index, n, s, k=1):
# out = nn.HybridSequential() 不用传导输入数据了,一层一层加入即可,但是这个conv就是需要传导的啊
stride = (s, s)
# print(type(data), dir(data))
# print(dir(data.attr))

conv_dw = Conv(data=data, num_group=bf * k, num_filter=bf * k, kernel=(3, 3), pad=(1, 1), stride=stride,
name="conv_%s_dw" % stage_index), # 112/112
# print(type(conv_dw), conv_dw, len(conv_dw)) # 这里出来的为啥是一个tuple类型的啊,在下面的都不是
conv = Conv(data=conv_dw[0], num_filter=bf * n, kernel=(1, 1), pad=(0, 0), stride=(1, 1),
name="conv_%s" % stage_index)
# 112/112

return conv


# k,n,stride of conv_dw(type: int)
specification = [
(1, 2, 1),
(2, 4, 2),
(4, 4, 1),
(4, 8, 2),
(8, 8, 1),
(8, 16, 2),
(16, 16, 1),
(16, 16, 1),
(16, 16, 1),
(16, 16, 1),
(16, 16, 1),
(16, 32, 2),
(32, 32, 1),
]


def get_symbol_me():
num_classes = config.emb_size
bn_mom = config.bn_mom
workspace = config.workspace
data = mx.symbol.Variable(name="data") # 224
data = data - 127.5
data = data * 0.0078125
fc_type = config.net_output
bf = int(32 * config.net_multiplier) # 1.0 也就是32

stride = (2, 2) if config.net_input == 0 else (1, 1)
out = Conv(data, num_filter=bf, kernel=(3, 3), pad=(1, 1), stride=stride, name="conv_1") # 224/112
for i, temp in enumerate(specification):
k, n, stride = temp
out = Depthwise_Separable_conv(bf, out, i + 2, n, stride, k)

fc1 = symbol_utils.get_fc1(out, num_classes, fc_type)
return fc1



if __name__ == "__main__":
mobilenet = get_symbol_me()
digraph = mx.viz.plot_network(mobilenet,shape={'data':(1,3,112,112)},title='mobilenet_me',node_attrs={"shape":"oval","fixedsize":"false"})
digraph.view()

# mx.viz.print_summary(mobilenet, shape={'data': (1, 3, 112, 112)})
# get_symbol Total params: 3734848
# get_symbol_me Total params: 3734848

参考链接:

理解分组卷积和深度可分离卷积如何降低参数量

深度解读谷歌MobileNet

深度可分离卷积