MobileNet系列解读
文章目录
写这篇博客主要是记录自己所学知识,方便以后查阅学习。主要是对b站学习视频的记录。
mobileNetv2 pytorch代码:https://zhuanlan.zhihu.com/p/187564017
一 b站深度之眼:
b站学习资料:https://www.bilibili.com/video/BV1ry4y1y7aa?from=search&seid=13792893548931885317&spm_id_from=333.337.0.0
第一课:论文导读
1.1论文研究背景、成果及意义
1.2论文泛读
第二课 论文精读
MobileNet的网络结构很简单。我们仔细看这个表格,就会发现里面的层只有这么几种的堆叠:标准卷积、DW卷积、PW卷积、平均池化、全连接层。
需要多说的一点是,这里的每个卷积层后面都跟着BN和ReLU,这两个已经是卷积网络中的标配了。BN的目的是正则化来防止过拟合,ReLU是最主流的激活函数。
2.1 卷积块特点
2.2 降采样方式
一般降采样方式为池化,本论文采用stride.
2.3 标准卷积
普通卷积空间上是稀疏连接,通道上是密集连接的过程
实现:一个卷积核与一个通道做卷积运算,相乘并相加,若三个通道,则将三个通道相加得到一个值。输出通道等于过滤器个数。
理解卷积神经网络中的通道(channel):https://medlen.blog.csdn.net/article/details/109920512
输入通道数是3,每个通道都需要跟一个过滤器里边的一个卷积核做卷积运算,然后将结果相加得到一个特征图的输出。若有4个过滤器,因此得到4个特征图的输出,输出通道数为4。即输出通道等于过滤器个数。可能会有偏置。
2.4 深度可分离卷积
知乎:https://zhuanlan.zhihu.com/p/92134485
与普通卷积的不同之处:
普通卷积的过滤器filter有3个3x3的卷积核,即3x3x3,是有深度的。
而深度可分离卷积包括深度卷积和逐点卷积累
深度卷积的一个过滤器有1个3x3的卷积核,即3x3。没有深度。
逐点卷积是有N个1x1的卷积核,和普通卷积操作类似。
深度卷积只是在空间上进行探索,但是很多在通道间是有丰富联系的。我们需要找到通道间丰富的关联,所以需呀一个卷积操作把各种通道间的信息打通,就引入了点卷积。
1.丰富空间之间的关系
2.用1x1卷积将空间中的关系在通道方面打通联系起来。
纠正:输出应是Dk.卷积核:Dk x Dk x M x N
深度卷积在空间上是稀疏连接的,在通道上是逐个进行,相互独立。
点卷积在空间上是逐一进行,在通道上是密度连接。
常规卷积操作
卷积层共4个Filter,每个Filter包含了3个Kernel,每个Kernel的大小为3×3。因此卷积层的参数数量可以用如下公式来计算:
N_std = 4 × 3 × 3 × 3 = 108
深度卷积
其中一个Filter只包含一个大小为3×3的Kernel,卷积部分的参数个数计算如下:
N_depthwise = 3 × 3 × 3 = 27
逐点卷积
为了有效的利用不同通道在相同空间位置上的特征信息。
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要Pointwise Convolution来将这些Feature map进行组合生成新的Feature map。
Pointwise Convolution的运算与常规卷积运算非常相似,它的卷积核的尺寸为 1×1×M,M为上一层的通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。
参数对比
回顾一下,常规卷积的参数个数为:
N_std = 4 × 3 × 3 × 3 = 108
Separable Convolution的参数由两部分相加得到:
N_depthwise = 3 × 3 × 3 = 27
N_pointwise = 1 × 1 × 3 × 4 = 12
N_separable = N_depthwise + N_pointwise = 39
相同的输入,同样是得到4张Feature map,Separable Convolution的参数个数是常规卷积的约1/3。因此,在参数量相同的前提下,采用Separable Convolution的神经网络层数可以做的更深。
标准卷积和深度可分离卷积的不同
算力消耗这个公式是步长s=1,为逐层卷积和逐点卷积。
2.5 MobileNet超参数
纠正:算力消耗变为:
步长s=1时。
虽然一个MobileNet模型已经很小、速度很快了,但有时候在特定的场景下,可能需要模型更小和更快。那么为了满足更小和更快的需求,作者分别引入了一个宽度系数和一个分辨率系数。
知乎:https://zhuanlan.zhihu.com/p/371667329
宽度系数alpha:
分辨率系数: beta
第三课 MobileNet后续创新及改进
二 b站霹雳吧啦Wz:
https://www.bilibili.com/video/BV1yE411p7L7?spm_id_from=333.999.0.0
2.1 MobileNet网络详解
介绍MobileNet v1和MobileNet v2论文中一些亮点,包括Depthwide Convolution、Pointwise Convolution以及Inverted Residuals结构。并对MobileNet v2网络结构进行详解的讲解。
github链接:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing
2.1.1 MobileNet v2 2018
v2亮点:倒残差结构和线性瓶颈层
激活函数
MobileNet v1激活函数是ReLU, v2激活函数是ReLU6。
最后是线性激活函数:
V2网络结构
bottleneck指到残差结构,n是bottleneck的重复次数。一个block由一系列bottleneck组成,其中t=1时,没有第一个conv1x1。
stride=1时,特征矩阵的高和宽不会发生变化。
最后conv2d 1x1,其实功能和一个全连接层一样。
v2性能对比
注:1.4为alpha倍率因子。
难点解疑,知识深挖
输出大小:n+2p-f/s+1
激活函数:
激活函数sigmoid、tanh、ReLU、LeakyReLU、ReLU6
https://www.cnblogs.com/itmorn/p/11132494.html
激活函数优缺点:https://blog.csdn.net/kuweicai/article/details/93926393
**1.为什么使用激活函数:**再深的网络也是线性模型,只能把输入线性组合再输出。不能学习到复杂的映射关系。因此需要激活函数这个非线性函数做转换。
2.sigmod
sigmod优点:
作为最早开始使用的激活函数之一, Sigmoid 函数具有如下优点。
连续,且平滑便于求导
但是缺点也同样明显。
none-zero-centered(非零均值, Sigmoid 函数 的输出值恒大于0),会使训练出现 zig-zagging dynamics 现象,使得收敛速度变慢。
梯度消失问题。 由于Sigmoid的导数总是小于1,所以当层数多了之后,会使回传的梯度越来越小,导致梯度消失问题。而且在前向传播的过程中,通过观察Sigmoid的函数图像,当 x 的值大于2 或者小于-2时,Sigmoid函数的输出趋于平滑,会使权重和偏置更新幅度非常小,导致学习缓慢甚至停滞。
计算量大。由于采用了幂计算。
建议:基于上面Sigmoid的性质,所以不建议在中间层使用Sigmoid激活函数,因为它会让梯度消失
sigmod(x)的缺点:
①输出范围在0~1之间,均值为0.5,需要做数据偏移,不方便下一层的学习。
②当x很小或很大时,存在导数很小的情况。另外,神经网络主要的训练方法是BP算法,BP算法的基础是导数的链式法则,也就是多个导数的乘积。而sigmoid的导数最大为0.25,多个小于等于0.25的数值相乘,其运算结果很小。随着神经网络层数的加深,梯度后向传播到浅层网络时,基本无法引起参数的扰动,也就是没有将loss的信息传递到浅层网络,这样网络就无法训练学习了。这就是所谓的梯度消失。
3.tanh
tanh是双曲函数中的一个,tanh()为双曲正切。在数学中,双曲正切“tanh”是由双曲正弦和双曲余弦这两种基本双曲函数推导而来。
tanh激活函数和导函数分别为
对应的图像分别为:
对应代码为:
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure()
x = np.arange(-10, 10, 0.025)
plt.plot(x,(1-np.exp(-2*x))/(1+np.exp(-2*x)))
plt.title("y = (1-exp(-2x))/(1+exp(-2x))")
plt.show()
plt.plot(x,4*np.exp(-2*x)/(1+np.exp(-2*x))**2)
plt.title("y = 4exp(-2x)/(1+exp(-2x))^2")
plt.show()
在神经网络的应用中,tanh通常要优于sigmod的,因为tanh的输出在-1~1之间,均值为0,更方便下一层网络的学习。但有一个例外,如果做二分类,输出层可以使用sigmod,因为他可以算出属于某一类的概率
Sigmod(x)和tanh(x)都有一个缺点:在深层网络的学习中容易出现梯度消失,造成学习无法进行。
可以发现它的优点主要是解决了none-zero-centered 的问题,但是缺点依然是梯度消失,计算消耗大。但是如果和上面的 sigmoid 激活函数相比, tanh 的导数的取值范围为(0, 1), 而 sigmoid 的导数的取值范围为(0,1/4),显然sigmoid 会更容易出现梯度消失,所以 tanh 的收敛速度会比 sigmoid 快。
4.ReLU系列
4.1 ReLU
针对sigmod和tanh的缺点,提出了ReLU函数
线性整流函数(Rectified Linear Unit, ReLU),又称修正线性单元,是一种人工神经网络中常用的激活函数(activation function),通常指代以斜坡函数及其变种为代表的非线性函数。
ReLU激活函数和导函数分别为
对应的图像分别为:
对应代码为:
from matplotlib import pyplot as plt
import numpy as np
fig = plt.figure()
x = np.arange(-10, 10, 0.025)
plt.plot(x,np.clip(x,0,10e30))
plt.title("y = relu(x)=max(x,0)")
plt.show()
from matplotlib import pyplot as plt
plt.plot(x,x>0,"o")
plt.title("y = relu'(x)")
plt.show()
Relu的一个缺点是当x为负时导数等于零,但是在实践中没有问题,也可以使用leaky Relu。
总的来说Relu是神经网络中非常常用的激活函数。
其优点如下
x 大于0时,其导数恒为1,这样就不会存在梯度消失的问题
计算导数非常快,只需要判断 x 是大于0,还是小于0
收敛速度远远快于前面的 Sigmoid 和 Tanh函数
缺点
none-zero-centered
Dead ReLU Problem,指的是某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。因为当x 小于等于0时输出恒为0,如果某个神经元的输出总是满足小于等于0 的话,那么它将无法进入计算。有两个主要原因可能导致这种情况产生:
(1) 非常不幸的参数初始化,这种情况比较少见
(2) learning rate太高导致在训练过程中参数更新太大,不幸使网络进入这种状态。解决方法是可以采用 MSRA 初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。
这个激活函数应该是在实际应用中最广泛的一个。
4.2.LeakyReLU
优点:Leaky ReLU 的提出主要是为了解决前面 Relu 提到的 Dead ReLUProblem 的问题,因为当 x 小于 0 时,其输出不再是 0.
而同时 Leaky ReLU 具有 ReLU 的所有优点。听上去貌似很好用,只是在实际操作中并没有完全证明好于 ReLU 函数。
4.3 ReLU6
ReLU6 仅仅是在 ReLU 的基础上进行了 clip 操作,即限制了最大输出,比较典型的是在 mobile net v2 中有使用。
比较有意思的是,为什么是6,不是7或者其他呢?结合原文的说明,可能是作者尝试了多种,ReLU6 效果最好。
2.2 使用pytorch搭建MobileNetV2网络
b站:
https://www.bilibili.com/video/BV1qE411T7qZ?from=search&seid=11783857503387836810&spm_id_from=333.337.0.0
github链接:https://github.com/WZMIAOMIAO/deep-learning-for-image-processing
CSDN: https://blog.csdn.net/qq_37541097/article/details/103482003
代码中的疑惑解答:
1.nn.ReLU6(inplace=True)的理解:
为True,将会改变输入的数据 ,否则不会改变原输入,只会产生新的输出。省去了反复申请与释放内存的时间,直接代替原来的值。
例:测试代码:
import torch
import torch.nn as nn
relu1 = nn.ReLU6(True)
relu2 = nn.ReLU6(False)
inputs1 = torch.tensor([50,-2,6,5,3])
inputs2 = torch.tensor([50,-2,6,5,3])
x1 = relu1(inputs1)
x2 = relu2(inputs2)
打印结果:
x1 = tensor([6, 0, 6, 5, 3]),
x2 = tensor([6, 0, 6, 5, 3]),
inputs1 = tensor([6, 0, 6, 5, 3]),
inputs2 = tensor([50, -2, 6, 5, 3])