经典轻量级神经网络(1)MobileNet V1及其在Fashion-MNIST数据集上的应用
1 MobileNet V1的简述
自从2017年由谷歌公司提出,MobileNet可谓是轻量级网络中的Inception,经历了一代又一代的更新。
MobileNet
应用了Depthwise
深度可分离卷积来代替常规卷积,从而降低计算量,减少模型参数。MobileNet
不仅产生了小型网络,还重点优化了预测延迟。与之相比,有一些小型网络虽然网络参数较少,但是预测延迟较大。- 论文下载地址: https://arxiv.org/pdf/1704.04861.pdf
1.1 深度可分离卷积
对于传统的卷积层,单个输出feature
这样产生:
-
首先由一组滤波器对输入的各通道执行滤波,生成滤波
feature
。这一步仅仅考虑空间相关性。 -
然后计算各通道的滤波
feature
的加权和,得到单个feature
。这里不同位置处的通道加权和的权重不同,这意味着在空间相关性的基础上,叠加了通道相关性。
Depthwise
深度可分离卷积打破了空间相关性和通道相关性的混合:
- 首先由一组滤波器对输入的各通道执行滤波,生成滤波
feature
。这一步仅仅考虑空间相关性。 - 然后执行
1x1
卷积来组合不同滤波feature
。这里不同位置处的通道加权和的权重都相同,这意味着这一步仅仅考虑通道相关性。 depthwise
卷积的参数数量和计算代价都是常规卷积的 1/8 到 1/9。
传统卷积和深度可分离卷积的区别可参考下面博客:
Pytorch常用的函数(三)深度学习中常见的卷积操作详细总结_undo_try的博客-CSDN博客
1.2 网络结构
1.2.1 V1卷积层
上图左边是标准卷积层,右边是V1的卷积层。V1的卷积层,首先使用3×3的深度卷积提取特征,接着是一个BN层,随后是一个ReLU层,在之后就会逐点卷积,最后就是BN和ReLU了。这也很符合深度可分离卷积,将左边的标准卷积拆分成右边的一个深度卷积和一个逐点卷积。
注意:
深度可分离卷积里面的ReLU,是ReLU6。
上图左边是普通的ReLU,对于大于0的值不进行处理,右边是ReLU6,当输入的值大于6的时候,返回6,relu6“具有一个边界”。作者认为ReLU6作为非线性激活函数,在低精度计算下具有更强的鲁棒性。
标准卷积核深度可分离卷积层到底对结果有什么样的影响?
从上图可以看到使用深度可分离卷积与标准卷积,参数和计算量能下降为后者的九分之一到八分之一左右。但是准确率只有下降极小的1%。
1.2.2 网络结构
MobileNeet
网络结构如下表所示。其中:
Conv
表示标准卷积,Conv dw
表示深度可分离卷积。- 所有层之后都跟随
BN
和ReLU
(除了最后的全连接层,该层的输出直接送入到softmax
层进行分类)。 - 先是一个3x3的标准卷积,s2进行下采样。然后就是堆积深度可分离卷积,并且其中的部分深度卷积会利用s2进行下采样。然后采用平均池化层将feature变成1x1,根据预测类别大小加上全连接层,最后是一个softmax层。整个网络有28层,其中深度卷积层有13层。
- 与训练大模型相反,训练
MobileNet
时较少的采用正则化和数据集增强技术,因为MobileNet
是小模型,而小模型不容易过拟合。
整个计算量基本集中在1x1卷积上。对于参数也主要集中在1x1卷积,除此之外还有就是全连接层占了一部分参数。
Conv 1x1
包含了所有的1x1
卷积层,包括可分离卷积中的1x1
卷积。Conv DW 3x3
仅包括可分离卷积中的3x3
卷积。
1.3 宽度乘子、分辨率乘子
尽管基本的MobileNet
架构已经很小,延迟很低,但特定应用需要更快的模型。为此MobileNet
引入了两个超参数:宽度乘子、分辨率乘子。
宽度乘子width multiplier
,记做a 。实际上是减小每层网络的输入、输出 feature map
的通道数量。
-
宽度乘子应用于第一层(是一个全卷积层)的输出通道数上。这也影响了后续所有
Depthwise
可分离卷积层的输入feature map
通道数、输出feature map
通道数。这可以通过直接调整第一层的输出通道数来实现。
-
它大概以 a^2的比例减少了参数数量,降低了计算量。
-
通常将其设置为:0.25、0.5、0.75、1.0 四档。
分辨率乘子resolution multiplier
,记做p 。其作用是:降低输出的feature map
的尺寸。
-
分辨率乘子应用于输入图片上,改变了输入图片的尺寸。这也影响了后续所有
Depthwise
可分离卷积层的输入feature map
尺寸、输出feature map
尺寸。这可以通过直接调整网络的输入尺寸来实现。
-
它不会改变模型的参数数量,但是大概以p^2 的比例降低计算量。
如果模型同时实施了宽度乘子和分辨率乘子,则模型大概以 a^2 的比例减少了参数数量,大概以 a^2p^2 的比例降低了计算量。
假设输入feature map
尺寸为14x14
,通道数为 512
;卷积尺寸为3x3
;输出feature map
尺寸为14x14
,通道数为512
。
层类型 | 乘-加操作(百万) | 参数数量(百万) |
---|---|---|
常规卷积 | 462 | 2.36 |
深度可分离卷积 | 52.3 | 0.27 |
a=0.75 的深度可分离卷积 | 29.6 | 0.15 |
a=0.75,p=0.714 的深度可分离卷积 | 15.1 | 0.15 |
1.4 模型的性能
1.4.1 更瘦的模型和更浅的模型的比较
在计算量和参数数量相差无几的情况下,采用更瘦的MobileNet
比采用更浅的MobileNet
更好。
- 更瘦的模型:采用a=0.75 宽度乘子(
瘦
表示模型的通道数更小)。 - 更浅的模型:删除了
MobileNet
中5x Conv dw/s
部分(即:5层feature size=14x14@512
的深度可分离卷积)。
模型 | ImageNet Accuracy | 乘-加操作(百万) | 参数数量(百万) |
---|---|---|---|
更瘦的MobileNet | 68.4% | 325 | 2.6 |
更浅的MobileNet | 65.3% | 307 | 2.9 |
1.4.2 不同宽度乘子及分辨率乘子的比较
随着a降低,模型的准确率一直下降(a=1 表示基准 MobileNet
)。
with multiplier | ImageNet Accuracy | 乘-加 操作(百万) | 参数数量(百万) |
---|---|---|---|
1.0 | 70.6% | 569 | 4.2 |
0.75 | 68.4% | 325 | 2.6 |
0.5 | 63.7% | 149 | 1.3 |
0.25 | 50.6% | 41 | 0.5 |
同样,随着 p的降低,模型的准确率一直下降( p=1表示基准MobileNet
)。
resolution | ImageNet Accuracy | 乘-加 操作(百万) | 参数数量(百万) |
---|---|---|---|
224x224 | 70.6% | 569 | 4.2 |
192x192 | 69.1% | 418 | 4.2 |
160x160 | 67.2% | 290 | 4.2 |
128x128 | 64.4% | 186 | 4.2 |
1.4.3 MobileNet和其它模型的比较
作者将V1与大型网络GoogleNet和VGG16进行了比较。
可以发现,作为轻量级网络的V1在计算量小于GoogleNet,参数量差不多是在一个数量级的基础上,在分类效果上比GoogleNet还要好,这就是要得益于深度可分离卷积了。VGG16的计算量参数量比V1大了30倍,但是结果也仅仅只高了1%不到。
瘦身的MobileNet
(宽度乘子a=0.75 ,分辨率乘子p=0.714 )和 Squeezenet
模型大小差不多,但是准确率更高,计算量小了 22 倍。
2 MobileNet V1在Fashion-MNIST数据集上的应用示例
2.1 创建MobileNet V1网络模型
我们实现一个简化版本的模型。
import torch.nn as nn
import torch
class MobileNetV1(nn.Module):
def __init__(self):
super(MobileNetV1, self).__init__()
def conv_bn(inp, oup, stride):
"""
标准卷积块
"""
return nn.Sequential(
nn.Conv2d(in_channels=inp, out_channels=oup, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True)
)
def conv_dw(inp, oup, stride):
"""
深度可分离卷积
"""
return nn.Sequential(
# 深度卷积
nn.Conv2d(
in_channels=inp,
out_channels=inp, # out_channels=in_channels
kernel_size=3,
stride=stride,
padding=1,
groups=inp, # groups=in_channels
bias=False
),
nn.BatchNorm2d(inp),
nn.ReLU(inplace=True),
# 逐点卷积
nn.Conv2d(
in_channels=inp,
out_channels=oup,
kernel_size=1,
stride=1,
padding=0,
bias=False
),
nn.BatchNorm2d(oup),
nn.ReLU(inplace=True),
)
self.model = nn.Sequential(
# conv_bn(3, 32, 2),
conv_bn(1, 32, 2),
conv_dw(32, 64, 1), # 深度卷积层有13层
conv_dw(64, 128, 2),
conv_dw(128, 128, 1),
conv_dw(128, 256, 2),
conv_dw(256, 256, 1),
conv_dw(256, 512, 2),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 512, 1),
conv_dw(512, 1024, 2),
conv_dw(1024, 1024, 1),
nn.AvgPool2d(7),
)
# self.fc = nn.Linear(1024, 1000)
self.fc = nn.Linear(1024, 10)
def forward(self, x):
x = self.model(x)
x = x.view(-1, 1024)
x = self.fc(x)
return x
if __name__ == '__main__':
net = MobileNetV1()
X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
for layer in net.model:
X = layer(X)
print(layer.__class__.__name__, 'output shape:', X.shape)
equential output shape: torch.Size([1, 32, 112, 112])
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 1024, 7, 7])
Sequential output shape: torch.Size([1, 1024, 7, 7])
AvgPool2d output shape: torch.Size([1, 1024, 1, 1])
2.2 读取Fashion-MNIST数据集
# 我们将图片大小设置224×224
# 训练机器内存有限,将批量大小设置为64
batch_size = 64
train_iter,test_iter = get_mnist_data(batch_size,resize=224)
2.3 在GPU上进行模型训练
from _07_MobileNetV1 import MobileNetV1
# 初始化模型
net = MobileNetV1()
lr, num_epochs = 0.1, 10
train_ch(net, train_iter, test_iter, num_epochs, lr, try_gpu())