Date: 2020/02/07
Author: CW
Foreword:
轻量化网络是深度学习领域的研究热点之一,其目标是在网络的参数量少、训练和预测速度快的情况下又能够保持一定的准确率,以下是一些常用的减少网络计算量的方法:
i). 网络模型设计:使用组卷积(Group Conv)、1x1 卷积(Ponit-Wise Conv)等在达到相同(或近似)效果的同时减少计算量,类似的案例如 Inception、Xception、ShuffleNet 以及本文的主角MobileNet等;
ii). 剪枝: 通过剪去网络中的冗余部分来减少网络计算量;
iii). 知识蒸馏:利用大模型(Teacher-Model)来辅助小模型(Student-Model)学习,从而在小模型上达到目标效果。
MobileNet是Google的作品,能够将模型部署应用于移动端,同时又保证良好的效果,目前Google Pixel 系列的手机就是用的MobileNet。MobileNet目前更新到了v3版本,CW参考和收集了网上的资料及博文,在本文中会从入门到放弃...哦不,是会从v1到v3都进行解读,供广大帅哥靓女们参考学习。如有不当之处,尽管反馈,大家一起学习与进步,欢迎交流,谢谢!
Outline
1、MobileNetv1
i). 对标准卷积层进行改进
ii). 使用2个超参数构建更小的模型
2、MobileNetv2
i). 扩展层:Expansion Layer
ii). 线性瓶颈:Linear Bottleneck
iii). “倒”残差结构:Inverted Residual
3、MobileNetv3
i). 新的非线性激活函数:h-swish
ii). 基于Squeeze & Excitation(SE)的轻量级注意力结构
iii). 修改了部分v2的结构:头部卷积核的通道数及最后的尾部结构
iv). 网络结构搜索:资源受限的NAS(Platform-Aware NAS)与 NetAdapt
4、MobileNet为何这么快?
MobileNetv1
v1的主要工作是将标准的卷积过程拆为两个步骤 —— Depth-Wise Conv 和 Point-Wise Conv,并且引入2个超参,减少了计算量并且模型也更小,从而可以轻松地匹配移动和嵌入式视觉应用的设计要求。
i). 对标准卷积层的改进 —— Depth-Wise Conv 和 Point-Wise Conv
Depth-Wise Conv(简称dw),也称深度可分离卷积,它将卷积核的每个通道分别应用于输入特征图的每个通道(这样,输入和输出的通道数维持一致),而非如标准卷积般将输入特征图的所有通道加权组合在一起。
Point-Wise Conv(简称pw), 也称逐点卷积,它使用1x1大小的卷积核,用于组合深度可分离卷积的输出结果(将深度可分离卷积的输出通道数映射到目标通道数)。
标准卷积将提取空间特征和通道之间的相关性组合在一起,而MobileNet将其分解为Depth-Wise + Point-Wise,这样的效果是先基于各个通道提取空间特征(空间相关性),之后再组合通道之间的相关性,这种因式分解操作具有大幅度减少计算和模型大小的效果。
为何说这样能够减少计算量?根据上图,举个例子动手计算下才有说服力(下文均使用 channel first 惯例,所谓的计算量指乘法次数):
(a) 表示标准卷积的过程:有N个卷积核,每个卷积核维度是M xx,输入特征图的通道数是M,输出特征图维度为N x x ,计算量为;
(b) 表示dw的过程:用M个维度为1 x x 的卷积核去卷积对应输入特征图的M个通道,得到M个输出结果,注意这M个结果不会进行相加(相比标准卷积是卷积输入特征图的所有通道,并累加这M个结果),输出结果的维度是,这时计算量为 x ;
(c) 表示pw的过程:用N个维度为的卷积核卷积上述(b)的输出结果,最终得到维度为的 feature map。这个过程和普通卷积无异,只是卷积核大小为1 x 1,此时计算量是。
(b) + (c) 总共的计算量是+,我们拿它除以标准卷积 (a)的计算量,来看看缩减了多少:
=
也就是说,如果使用将我们最常使用的3x3标准卷积分解为dw+pw,那么计算量将缩减为原来的有多!
根据paper原文的意思,有些点还需要提下:
MobileNet在每一层均使用BN和ReLU两种非线性变换;
与标准卷积相比,dw非常有效。然而,它只会滤除输入通道,不会将它们组合起来以创建新功能。因此,需要通过pw来计算深度卷积输出的线性组合的附加层来生成这些新特征。
ii). 使用2个超参数构建更小的模型
(1) 宽度乘数(mulitiplier)
记宽度乘数为α,其作用是在每层均匀地减薄网络。对于给定的层和宽度乘数α,输入通道M的数量变为αM,输出通道数量N变为αN。
再拿上述例子来说,使用了宽度乘数后,dw+pw的计算量为:,通常设置为0.75。
(2) 分辨率乘数
这个超参通常隐式设置,应用于输入图像,以降低分辨率,记其为。
同样地,拿上述例子来说,在使用了宽度乘数的基础上,进一步使用分辨率乘数后,dw+pw的计算量为:
最后来看下MobileNetv1的整体架构,总共28个卷积层(conv+bn+relu),第1个和最后1个都是标准卷积层,中间13个是dw+pw卷积层(dw+pw算作1个卷积层)
MobileNetv2
回顾下MobileNetv1的骚操作——首先利用3×3的深度可分离卷积提取空间特征,然后利用1×1的逐点卷积来组合通道特征,同时映射到目标通道数。这样既减少了参数量、计算量,同时又提高了网络运算速度,还能得到一个接近于标准卷积的不错的结果,看起来十分美好。
然而,现实是残酷的!
不少炼丹者在实际使用过程中, 发现深度卷积部分的卷积核被废掉了——训练完之后发现深度卷积核有不少是空的!
于是,作者赶紧再发一篇paper(心想:本来想坑你们一把,没想到这么快就被识破了,不好玩 !),把锅甩给了ReLU(ReLU内心独白:我太难了!),同时,MobileNetv2也诞生了(此刻脑补众炼丹者们内心:也不知道是不是新挖的坑...)。
在MobileNetv2的paper中,作者举出一个充分必要条件将锅“合乎所以然”地甩给了ReLU——将低维流形映射到高维空间后使用ReLU变换再复原的例子:
如上图,在2维空间有一组由m个点组成的螺旋线Xm数据(上图input),利用随机矩阵T映射到n维空间上并进行ReLU运算:
这时,再利用随机矩阵T的逆矩阵,将y映射回2维空间中:
最后,根据映射到不同维度空间的情况,对比下逆映射后的结果与原始输入:
可以看到,当映射到2、3维空间(n = 2, 3)时,与原始输入input相比有很大一部分的信息已经丢失了;而映射到15、30维空间(n = 15, 30)时,还有许多信息能被保留下来。
这就是说,在低维度空间下与ReLU拥抱(做ReLU运算),很容易造成信息丢失;而在高维度的情况下,信息丢失则相对没那么严重。
这就解释了为什么dw的卷积核有不少是空,因为dw本身不会改变通道数,如果输入通道本来就少的话,经过ReLU后信息丢失就会比较严重。
咦...说了那么久,怎么好像都没有提到MobileNetv2呀?是不是跑题了?矣?对耶!
哦,不不不,以上铺垫的这个问题很重要,因为针对这个问题的解决手段正是MobileNetv2最重要的骚操作,好了,接下来进入正题。
i). Expansion Layer
对于深度可分离卷积,其本身没有改变通道的能力,输入通道多少输出就是多少。有上述可知,这样的话,若输入通道很少,经过深度可分离卷积后再接ReLU,效果便不是很好,那怎么办呢?
我们可以在深度可分离卷积前先扩展通道数,也称作Expansion Layer(扩展层)。OK,那具体如何扩展呢?
既然在深度可分离卷积后我们使用了逐点卷积来将feature map的通道数映射到目标通道数,那么同样地,我们也可以在深度可分离卷积之前先使用逐点卷积进行升维(paper原文是升6倍)。
ii). Linear Bottleneck
相对于上述方式,这是一个比较“暴力”的手段,干脆把ReLU换掉,使用线性激活层替代。不过,我们可不能把所有的激活函数都换成线性的,这样整张网络就相当于一个线性操作了(别忘记使用激活函数的一个原因之一就是引入非线性变换),那么应该替换掉哪里的ReLU呢?
仔细看上图,第1个pw已将通道数扩展,中间的pw不会改变通道数,那么这两层后面使用ReLU通常较为“安全”,而最后1个pw通常是压缩通道,如果后面接ReLU,效果可能就相对没那么好了,因此可以将最后1个pw后的ReLU替换为线性激活函数,这样的结构也称作Linear Bottleneck。
iii). Inverted Residual
还记得ResNet那货不?哦,这肯定是废话了,对于众炼丹者来说,它也算得上是个全明星了。
残差结构(通常也称作shortcut)能够其起到特征复用和避免梯度消失的作用,于是在MobileNetv2里也引入这一骚操作。
根据上图,对比下可以发现,两者都采用了 1×1 -> 3 ×3 -> 1 × 1 的卷积模式以及shortcut,不同点在于ResNet先使用pw降维,后使用pw升维,而MobileNetV2“反其道而行之”——先使用pw升维,后使用pw降维。
这样一来,MobileNetv2的block形状看起来就刚好与ResNet的block相反,因此作者给了它一个网红名字——Inverted-Residual。
综合以上,我们来看下MobileNetv2的block:
再来对比下v1和v2的block:
上图左边是v1的block,没有shortcut并且最后的pw后面接了ReLU6;
右边的是v2的block,加入了Expansion Layer(使用1×1 pw 升维),引入shortcut,同时去掉了最后pw后的ReLU,改为线性激活函数。注意,v2的block有2种,当dw的步长(stride)为1时,使用了shortcut,形成残差结构;而dw的步长(stride)为2时,由于input与output的尺寸不同,因此没有使用shortcut结构。
最后,附上MobileNetv2的网络结构:
MobileNetv3
本以为v2能成为网红,没想到一下子被v3干掉了...好吧,我们先来回顾下v1和v2都做了哪些骚操作。
v1将标准卷积分解为dw+pw,同时使用2个超参进一步构建小模型和降低计算量;
v2在v1基础上,在dw前加入 Expansion Layer 进行通道数的扩展,将dw后的pw接的ReLU6替换为线性激活层,并且引入了shortcut,最终形成 inverted-residual(“倒残差”) 这样的结构。
简单精炼,OK!那么v3在以上的基础上又有哪些骚操作呢?
i). 新的非线性激活函数 —— h-swish
在介绍h-swish先来认识一个家伙——swish,它一次是在谷歌大脑2017的论文 Searching for Activation functions 种亮相,其具备无上界有下界、平滑以及非单调的特性,并且在深层模型上的效果优于ReLU,并且作者通过实验进行了验证。
()
这好东西v3当然也尝试使用过,但是作者又认为这家伙虽然提高了精度,但在嵌入式环境中,还是有不少计算成本,原因是在移动设备上计算sigmoid 函数成本不小,因此把sigmoid这部分替换掉,改为ReLU6,并且将这整个激活函数命名为h-swish:
为啥叫h-siwsh?h代表什么呢?h其实是单词hard的第一个字母,h-swish的意思就是这个近似的函数可以逼近swish,让swish硬(hard)起来(别想歪呀...)。那为啥使用ReLU6呢?作者认为几乎所有的软硬件框架上都可以方便计算ReLU6,同时它能在特定模式下消除由于近似sigmoid的不同实现而带来的潜在数值精度损失。
下图展示了使用h-swish对于时间以及精度的影响,可以发现,使用h-swish@16可以提高大约0.2%的精度,但是持剑延长了大约20%。因此,总的来说,在更深的层中使用h-swish好处更大,于是建议通常在模型的后半部分使用。
ii). 基于Squeeze & Excitation(SE)的轻量级注意力结构
在bottleneck中加入了SE结构,并且放在了dw之后。另外,由于SE部分的计算会消耗一定的时间,因此在SE结构的第1个FC层中,压缩了通道数(paper原文是变为原来的1/4),在最后的FC层再复原,通过实验证明,这样在减少时耗的同时也提高了精度,这是属于MobileNet的轻量级SE结构。
关于这里的SE,有几点提醒下:
(1). 这里的Pool指的是GAP(Global Average Pooling),即全局平均池化,经过它的“沐浴”后,feature map的大小变为1 x 1,通道数维持不变;
(2). 第1个FC后的激活函数是ReLU,最后1个FC后的激活函数是h-sigmoid(注意不是 h-swish,相比 h-swish 少乘了1个 x,即)
iii). 修改了部分v2的结构 —— 头部卷积核的通道数 和 尾部结构
(1). 修改头部卷积核的通道数
v2中的头部卷积核是 32 x 3 x 3,v3中改成了 16 x 3 x 3,作者发现这样在保证了精度的前提下还降低了3ms的时耗。
(2). 修改尾部结构
v2中,在最后的 Avg-Pooling 之前,使用了1个pw来升维,有利于结构的预测,但这同时引入了一定的计算量,于是作者在v3中作了修改:
先去掉这个 pw 前的 bottleneck 中的 dw 和后面的 pw,然后将原本位于 Avg-Pooling 前的 pw 放置于 Avg-Pooling 后,这样就变为先用 Avg-Pooling 将feature map大小由 7 x 7 降到了 1 x 1(Avg-Pooling 的 kernel size 是 7 x 7),然后再使用 pw 升维度,减少了计算量和时耗,同时发现精度并没有得到损失,最终变为如下所示的结构:
相比于v2:
iv). 网络结构搜索 —— 资源受限的NAS(Platform-Aware NAS)与 NetAdapt
通过上述,可以发现v3相比于v2并没有做太多创新,其实重头之戏在于这部分。但是CW对这部分没有深入了解,参考了一些博文,在这里简单介绍一下。
总体过程就是先用资源受限的NAS,在计算和参数量受限的前提下搜索网络来优化各个块(block),称之为模块级搜索(Block-wise Search)。然后再使用 NetAdapt 算法对各个模块确定每一层的卷积核数量,称之为层级搜索(Layer-wise Search)。
MobileNet为何这么快?
看到这里,你有没有认真想过其实为何MobileNet会这么快呢?它是闪电侠吗,sorry跑题...
或许你会说因为它将标准卷积分解为dw+pw、使用了宽度乘数和分辨率乘数缩减计算量、使用了h-swish...blablabla等等,嗯,也没错,但是这里可以尝试从另一个视角去理解。
以v1为例,来看看计算资源分布情况:
可以看到,大部分计算都分布在了pw也就是1 x 1卷积上,这么看来,快就快在1 × 1卷积了。咦,难道1 x 1卷积才是闪电侠?(⊙o⊙)…又跑题,sorry...
我们知道,卷积实质是矩阵之间的加乘运算,而在计算机进行操作时,需要将其先存入内存再操作,按照 “行先序”:
再来一张图,你会发现这是有点“辛苦”的:
于是,通常都会使用到一个叫im2col的骚操作,其实质是通过牺牲空间的手段(约扩增K×K倍),将特征图转换成更大的矩阵来进行卷积计算,具体为:
把在特征图中每一次循环所需的数据排列成列向量,然后逐一堆叠起来形成矩阵(按通道顺序在列方向上拼接),比如输入特征图维度为 Ci × Wi × Hi ,卷积核尺寸为 K × K ,输出维度为Co×Wo×Ho,那么卷积核转换为 Co x ( K x K) 的矩阵,同时输入特征图转换为(K x K) × (Ci x Wo x Ho)的矩阵,如下所示。
然后调用诸如 GEMM(矩阵乘矩阵)这样的库加速两矩阵的相乘计算。由于这过程按照计算需求排布了数据顺序,使得计算机每次计算过程中能够依次访问到特征图中所需的数据,因此极大地提高了运算速度。
OK,现在回来看看MobileNet的宝贝1×1卷积,根据上述,1×1卷积的“行先序”储存结构以及使用im2col后的结构分别如下图所示:
咦,这是...一样哒!也就是说,1x1卷积根本不需要im2col的过程,“即插即用”!大大节省了数据重排列的时间和空间,因此在底层计算中相对更快。
#最后
若你认真看完了本文,那么CW真心感谢你!与此同时,如果你觉得本文给予了你帮助或者让你有了新的认知,那我就更开心了,CW期待与大家共同成长!
参考:
https://cloud.tencent.com/developer/article/1467101
https://blog.csdn.net/u010712012/article/details/95922901
https://blog.csdn.net/qq_38807688/article/details/84590717
https://blog.csdn.net/Chunfengyanyulove/article/details/91358187