[关闭]
@Team 2019-01-27T08:25:07.000000Z 字数 5638 阅读 1601

《Computer vision》笔记-MobileNet(7)

石文华


1、前言

移动端和其他嵌入式系统通常是内存空间小,能耗要求低的,也就是计算资源有限。一般的模型,比如ImageNet数据集上训练的VGG,googlenet,resnet等,需要巨大的计算资源,所以很难用在嵌入式系统上。MobileNet是一种高效的模型,用于移动和嵌入式视觉应用。

2、深度可分离卷积(depthwise separable convolution)

MobileNet使用技术之一是深度可分离卷积(depthwise separable convolution)代替传统的3D卷积操作,这样可以减少参数数量以及卷积过程中的计算量,但是也会导致特征丢失,使得精度下降。MobileNet其实就是Xception思想的应用。区别就是Xception文章重点在提高精度,而MobileNet重点在压缩模型。
假设输入特征图有M个,大小为DF,输出的特征图是N个,卷积核尺寸是Dk*Dk,那么传统的3D卷积的卷积核是立体的,也就是每一个卷积核都是Dk*Dk*M,总共有N个Dk*Dk*M的卷积核,如下图所示:

image.png-20.4kB
所以总的参数个数为Dk*Dk*M*N,假设输出使用的padding参数是same,则输出特征图大小也是DF,那么总的计算量为Dk*Dk*M*N*DF*DF。
而MobileNet将普通卷积操作分为两部分,第一部分是逐通道卷积,使用M个通道数为1,大小为Dk*Dk的卷积核,每一个卷积核负责其中的一个通道。如下图所示:

image.png-10.6kB
逐通道卷积之后的参数个数为:Dk*Dk*M,同样假设padding为same,则计算量为:Dk*Dk*M*DF*DF
第二部分是点卷积,也就是采用3D的1*1卷积改变通道数,对深度方向上的特征信息进行组合,最终将输出通道数变为指定的数。如下图所示:

image.png-20.5kB

这部分的参数个数为:M*N,padding为same时的计算量为M*N*DF*DF.
因此这种分离之后的总的计算量为::Dk*Dk*M*DF*DF+M*N*DF*DF。
深度可分离卷积跟传统3D卷积计算量的比例为:

image.png-19.9kB
如下图,左边是3D卷积常见结构,右边是深度可分离卷积的使用方式:

image.png-17.4kB

3、宽度因子和分辨率因子

MobileNet有两个简单的全局超参数,分别是宽度因子和分辨率因子,可有效的在延迟和准确率之间做折中。允许我们依据约束条件选择合适大小、低延迟、易满足嵌入式设备要求的模型。
(1)宽度因子
上述的逐通道卷积的卷积核个数通常是M,也就是Dk*Dk*1的卷积核个数等于输入通道数,宽度因子是一个参数为(0,1]之间的参数,作用于通道数,可以理解为按照比例缩减输入通道数。同理,输出的通道数也可以通过这个参数进行按比例缩减。用α表示这个参数,则计算量为:

image.png-12.7kB

不同的α取值在ImageNet上的准确率,下图为准确率,参数数量和计算量之间的权衡情况:

image.png-52kB

(2)分辨率因子
上述的输入特征图大小为DF*DF,分辨率因子取值范围在(0,1]之间,可以理解为对特征图进行下采样,也就是按比例降低特征图的大小,使得输入数据以及由此在每一个模块产生的特征图都变小,用β表示这个参数,结合宽度因子α,则计算量为:

image.png-16.4kB

不同的β系数作用于标准MobileNet时,对精度和计算量以的影响(α固定):

image.png-51.9kB

4、改进(MobileNet V2):

在 MobileNet-V1 基础上结合当下流行的残差思想而设计,V2 主要引入了两个改动:Linear Bottleneck 和 Inverted Residual Blocks。
V1与V2的结构对比:

image.png-17.4kB

两者相同的地方在于都采用 Depth-wise (DW) 卷积搭配 Point-wise (PW) 卷积的方式来提特征。
(1)改进一:
V2在 DW 卷积之前新加了一个 PW 卷积,主要目的是为了提升维度数。相比V1直接在每个通道上单独提取特征,V2的这种做法能够先组合不同深度上的特征,并升维,使得特征更加丰富。比直接DW的话,特征提取的效果更好。
(2)改进二:
V2 去掉了第二个 PW 的激活函数。论文作者称其为 Linear Bottleneck。这么做的原因,是因为作者认为激活函数在高维空间能够有效的增加非线性,而在低维空间时则会破坏特征,由Relu的性质,Relu对于负的输入,输出全为零,所以relu会使得一部分特征失效。由于第二个 PW 的主要功能就是降维,再经过Relu的话,又要“损失”一部分特征,因此按照上面的理论,降维之后就不宜再使用 Relu了。如下图所示,一个原始螺旋形被利用随机矩阵T经过ReLU后嵌入到一个n维空间中,然后使用T−1投影到2维空间中。例子中,n=2,3导致信息损失,可以看到流形的中心点之间互相坍塌。同时n=15,30时信息变成高度非凸。

image.png-37.7kB

(3)使用短路连接: 倒残差(Inverted Residual)
典的残差块是:1x1(压缩)->3x3(卷积)->1x1(升维),而inverted residual顾名思义是颠倒的残差:1x1(升维)->3x3(dw conv+relu)->1x1(降维+线性变换),skip-connection(跳过连接)是在低维的瓶颈层间发生的(如下图),这对于移动端有限的宽带是有益的。连接情况如下图所示(shortcut只在stride==1时才使用):

image.png-159.2kB
(4)网络结构如下:

image.png-367.8kB

由于笔者水平有限,可参考如下github上的代码:https://github.com/tonylins/pytorch-mobilenet-v2/blob/master/MobileNetV2.py

  1. import torch.nn as nn
  2. import math
  3. #传统的3D卷积
  4. def conv_bn(inp, oup, stride):
  5. return nn.Sequential(
  6. nn.Conv2d(inp, oup, 3, stride, 1, bias=False),#卷积
  7. nn.BatchNorm2d(oup), #bn
  8. nn.ReLU6(inplace=True)#relu6
  9. )
  10. #1x1的点卷积
  11. def conv_1x1_bn(inp, oup):
  12. return nn.Sequential(
  13. nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
  14. nn.BatchNorm2d(oup),
  15. nn.ReLU6(inplace=True)
  16. )
  17. #倒残差
  18. class InvertedResidual(nn.Module):
  19. def __init__(self, inp, oup, stride, expand_ratio):#参数分别是输入特征图数,输出特征图数,步长,扩展比例
  20. super(InvertedResidual, self).__init__()
  21. self.stride = stride #步长
  22. assert stride in [1, 2]
  23. hidden_dim = round(inp * expand_ratio)#隐藏层层数,expand_ratio为拓展倍数
  24. self.use_res_connect = (self.stride == 1 and inp == oup)#是否进跳跃链接
  25. if expand_ratio == 1: #不进行扩展
  26. self.conv = nn.Sequential(
  27. # dw
  28. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),#可分离卷积
  29. nn.BatchNorm2d(hidden_dim),
  30. nn.ReLU6(inplace=True),
  31. # pw-linear
  32. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
  33. nn.BatchNorm2d(oup),
  34. )
  35. else:
  36. self.conv = nn.Sequential(
  37. # pw
  38. nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
  39. nn.BatchNorm2d(hidden_dim),
  40. nn.ReLU6(inplace=True),
  41. # dw
  42. nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
  43. nn.BatchNorm2d(hidden_dim),
  44. nn.ReLU6(inplace=True),
  45. # pw-linear
  46. nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
  47. nn.BatchNorm2d(oup),
  48. )
  49. def forward(self, x):
  50. if self.use_res_connect:
  51. return x + self.conv(x)
  52. else:
  53. return self.conv(x)
  54. class MobileNetV2(nn.Module):
  55. def __init__(self, n_class=1000, input_size=224, width_mult=1.):
  56. super(MobileNetV2, self).__init__()
  57. block = InvertedResidual#创建一个倒残差对象
  58. input_channel = 32
  59. last_channel = 1280
  60. '''
  61. t :是输入通道的倍增系数(即中间部分的通道数是输入通道数的多少倍)
  62. n :是该模块重复次数
  63. c :是输出通道数
  64. s :是该模块第一次重复时的 stride(后面重复都是 stride 1)
  65. '''
  66. interverted_residual_setting = [
  67. # t, c, n, s
  68. [1, 16, 1, 1],
  69. [6, 24, 2, 2],
  70. [6, 32, 3, 2],
  71. [6, 64, 4, 2],
  72. [6, 96, 3, 1],
  73. [6, 160, 3, 2],
  74. [6, 320, 1, 1],
  75. ]
  76. # building first layer
  77. assert input_size % 32 == 0
  78. input_channel = int(input_channel * width_mult)
  79. self.last_channel = int(last_channel * width_mult) if width_mult > 1.0 else last_channel
  80. self.features = [conv_bn(3, input_channel, 2)]
  81. # building inverted residual blocks
  82. for t, c, n, s in interverted_residual_setting:
  83. output_channel = int(c * width_mult)
  84. for i in range(n):
  85. if i == 0:
  86. self.features.append(block(input_channel, output_channel, s, expand_ratio=t))
  87. else:
  88. self.features.append(block(input_channel, output_channel, 1, expand_ratio=t))
  89. input_channel = output_channel
  90. # building last several layers
  91. self.features.append(conv_1x1_bn(input_channel, self.last_channel))
  92. # make it nn.Sequential
  93. self.features = nn.Sequential(*self.features)
  94. # building classifier
  95. self.classifier = nn.Sequential(
  96. nn.Dropout(0.2),
  97. nn.Linear(self.last_channel, n_class),
  98. )
  99. self._initialize_weights()
  100. def forward(self, x):
  101. x = self.features(x)
  102. x = x.mean(3).mean(2)
  103. x = self.classifier(x)
  104. return x
  105. def _initialize_weights(self):
  106. for m in self.modules():
  107. if isinstance(m, nn.Conv2d):
  108. n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
  109. m.weight.data.normal_(0, math.sqrt(2. / n))
  110. if m.bias is not None:
  111. m.bias.data.zero_()
  112. elif isinstance(m, nn.BatchNorm2d):
  113. m.weight.data.fill_(1)
  114. m.bias.data.zero_()
  115. elif isinstance(m, nn.Linear):
  116. n = m.weight.size(1)
  117. m.weight.data.normal_(0, 0.01)
  118. m.bias.data.zero_()
  119. model= MobileNetV2()
  120. print(model)

参考文献:
https://blog.csdn.net/t800ghb/article/details/78879612
https://www.cnblogs.com/CodingML-1122/p/9043078.html
https://blog.csdn.net/u011995719/article/details/79135818
https://zhuanlan.zhihu.com/p/33075914
https://zhuanlan.zhihu.com/p/39386719
https://mp.weixin.qq.com/s/T6S1_cFXPEuhRAkJo2m8Ig
https://blog.csdn.net/qq_31531635/article/details/80550412
https://github.com/tonylins/pytorch-mobilenet-v2/blob/master/MobileNetV2.py

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注