简介

深度学习的发展伴随着模型参数的暴涨,导致对运行模型的设备有很大的限制,普通的卷积神经网络模型难以运用到移动或嵌入式设备中,主要是这些设备的内存有限,其次这些设备的算力不能满足足够的响应速度,即实时性差,因此开发出一种能够在这些设备上运行的轻量级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}
tensorflow2 MobileNet-LMLPHP
上图看到,Relu函数的输出范围为0到无穷大,而Relu6的输出范围为0到6.
Relu6激活函数专为嵌入式设备设计,普通的激活函数输出范围为0到无穷大,而嵌入式设备采用的低精度float16无法描述如此大的范围,导致产生精度损失,所以RELU6应运而生,它在低精度计算下有更强的鲁棒性。

引入缩减因子(Width Mutiplier、resolution multiplier)

通过两个超参数对MobileNet基本模型进行轻量化,降低参数量和计算量。
Width Mutiplier主要用来按比例减少通道数,取值范围(0,1],使模型参数量按比例减少;
resolution multiplier主要用来按比例降低特征图尺寸,取值范围(0,1],使模型计算量按比例减少。

模型网络结构

tensorflow2 MobileNet-LMLPHP

最后,上代码

##激活函数用的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

11-25 12:59