简介
深度学习的发展伴随着模型参数的暴涨,导致对运行模型的设备有很大的限制,普通的卷积神经网络模型难以运用到移动或嵌入式设备中,主要是这些设备的内存有限,其次这些设备的算力不能满足足够的响应速度,即实时性差,因此开发出一种能够在这些设备上运行的轻量级CNN模型至关重要,目前对此类的研究主要有两种实现方式:
-
压缩训练好的复杂模型
-
直接设计小模型
2017年,基于上述第二点,谷歌提出在移动设备上运行的轻量级CNN—MobileNetV1 -
优点:占用内存小(参数量少),速度快(低延迟),精度高,易调试,这些优点正是普通CNN在移动设备上的短板。
-
创新点:
1、使用了depthwise separable convolution(深度可分离CNN)
2、使用Relu6激活函数,提高模型精度和泛化能力
3、引入缩减因子(Width Mutiplier、resolution multiplier),人为控制降低模型参数量
深度可分离卷积
深度可分离卷积分为两步,首先是深度卷积(depthwise convolution)也叫逐通道卷积,然后是逐点卷积(pointwise convolution)。与普通卷积相比,深度可分离极大降低了模型的参数量和计算量。
假设输入shape为
(
B
,
H
,
W
,
C
i
n
)
\left( B,H,W,C_{in\!} \right)
(B,H,W,Cin),卷积核shape为
(
F
,
K
1
,
K
2
)
\left( F,K_1,K_2 \right)
(F,K1,K2)。
对于普通卷积,参数量为:
C
i
n
×
K
1
×
K
2
×
F
C_{in}\times K_1\times K_2\times F
Cin×K1×K2×F
,计算量为:
C
i
n
×
K
1
×
K
2
×
F
×
H
×
W
C_{in}\times K_1\times K_2\times F\times H\times W
Cin×K1×K2×F×H×W
对于深度可分离卷积,参数量为:
C
i
n
×
K
1
×
K
2
+
C
i
n
×
F
C_{in}\times K_1\times K_2+C_{in}\times F
Cin×K1×K2+Cin×F
,计算量为:
(
C
i
n
×
K
1
×
K
2
+
C
i
n
×
F
)
×
H
×
W
\left( C_{in}\times K_1\times K_2+C_{in}\times F \right) \times H\times W
(Cin×K1×K2+Cin×F)×H×W
两者计算量相比为
C
i
n
×
K
1
×
K
2
×
F
×
H
×
W
(
C
i
n
×
K
1
×
K
2
+
C
i
n
×
F
)
×
H
×
W
=
F
+
K
1
×
K
2
\frac{C_{in}\times K_1\times K_2\times F\times H\times W}{\left( C_{in}\times K_1\times K_2+C_{in}\times F \right) \times H\times W}=F+K1\times K_2
(Cin×K1×K2+Cin×F)×H×WCin×K1×K2×F×H×W=F+K1×K2
由上式可得到普通卷积计算量比深度可分离卷积的计算量大很多。
Relu6 激活函数
下式分别为Relu6函数和它的导数:
R
e
l
u
6
(
x
)
=
min
(
max
(
x
,
0
)
,
6
)
∈
[
0
,
6
)
R
e
l
u
6
′
(
x
)
=
{
1
,
0
<
x
<
6
0
,
其他
∈
{
0
,
1
}
\mathrm{Re}lu6\left( x \right) =\min \left( \max \left( x,0 \right) ,6 \right) \in \left[ 0,6 \right) \\ \mathrm{Re}lu6^{'}\left( x \right) =\begin{cases} 1,0<x<6\\ 0,\text{其他}\\ \end{cases}\in \left\{ 0,1 \right\}
Relu6(x)=min(max(x,0),6)∈[0,6)Relu6′(x)={1,0<x<60,其他∈{0,1}
上图看到,Relu函数的输出范围为0到无穷大,而Relu6的输出范围为0到6.
Relu6激活函数专为嵌入式设备设计,普通的激活函数输出范围为0到无穷大,而嵌入式设备采用的低精度float16无法描述如此大的范围,导致产生精度损失,所以RELU6应运而生,它在低精度计算下有更强的鲁棒性。
引入缩减因子(Width Mutiplier、resolution multiplier)
通过两个超参数对MobileNet基本模型进行轻量化,降低参数量和计算量。
Width Mutiplier主要用来按比例减少通道数,取值范围(0,1],使模型参数量按比例减少;
resolution multiplier主要用来按比例降低特征图尺寸,取值范围(0,1],使模型计算量按比例减少。
模型网络结构
最后,上代码
##激活函数用的Relu
import tensorflow as tf
from tensorflow.keras.layers import *
from tensorflow.keras import Model
import numpy as np
class Depth_Separable_Conv(Model):
def __init__(self,s1=1,filters=64,s2=1,width_multiplier=1):
super().__init__()
self.layers_list=[]
self.layers_list.append(DepthwiseConv2D(kernel_size=(3,3),strides=s1,padding='same'))
self.layers_list.append(BatchNormalization())
self.layers_list.append(Activation('relu'))
self.layers_list.append(Conv2D(filters=round(filters * width_multiplier),kernel_size=1,strides=s2,padding='same'))
self.layers_list.append(BatchNormalization())
self.layers_list.append(Activation('relu'))
def call(self,x):
for layer in self.layers_list:
x=layer(x)
return x
class MobileNet(Model):
def __init__(self,width_multiplier=1,resolution_multiplier=1):
""" width_multiplier :通道缩减比例
resolution multiplier:尺寸缩减比例
"""
super().__init__()
self.resolution_multiplier=resolution_multiplier
self.layers_list=[]
self.layers_list.append(Conv2D(filters=32,kernel_size=3,strides=2,padding='same'))
self.layers_list.append(Depth_Separable_Conv(1,64,1,width_multiplier))
self.layers_list.append(Depth_Separable_Conv(2,128,1,width_multiplier))
self.layers_list.append(Depth_Separable_Conv(1,128,1,width_multiplier))
self.layers_list.append(Depth_Separable_Conv(2,256,1,width_multiplier))
self.layers_list.append(Depth_Separable_Conv(1,256,1,width_multiplier))
self.layers_list.append(Depth_Separable_Conv(2,512,1,width_multiplier))
for i in range(5):
self.layers_list.append(Depth_Separable_Conv(1,512,1,1))
self.layers_list.append(Depth_Separable_Conv(2,1024,1,1))
self.layers_list.append(Depth_Separable_Conv(2,1024,1,1))
self.layers_list.append(AveragePooling2D(pool_size=(7,7)))
self.layers_list.append(Dense(1000,activation='softmax'))
def call(self,x):
re_size=round(x.shape[1] * self.resolution_multiplier)
x=tf.image.resize(images=x, size=[re_size,re_size])
for layer in self.layers_list:
x=layer(x)
return x
model=MobileNet()
##用随机image验证模型的正确性,能输出预期的结果
x=range(112*112)
x=np.array(x).reshape(1,112,112,1)
x.shape
model(x)
##<tf.Tensor: shape=(1, 0, 0, 1000), dtype=float32, numpy=array([], shape=(1, 0, 0, 1000), dtype=float32)>
参考
CNN模型之MobileNet
MobileNet教程:用TensorFlow搭建在手机上运行的图像分类器
卷积神经网络学习笔记——轻量化网络MobileNet系列(V1,V2,V3)
MobileNet系列(万文长字详细讲解,一篇足以)
轻量级神经网络“巡礼”(二)—— MobileNet,从V1到V3