目录
图像分类
图像分类实质上就是从给定的类别集合中为图像分配对应标签的任务。也就是说我们的任务是分析一个输入图像并返回一个该图像类别的标签。
假定类别集为categories = {dog, cat, panda},之后我们提供一张图片给分类模型,如下图所示:
分类模型给图像分配多个标签,每个标签的概率值不同,如dog:95%,cat:4%,panda:1%,根据概率值的大小将该图片分类为dog,那就完成了图像分类的任务。
其数据集一般是图片,和对应的标签类别。
Lenet
1.简介
广为流传的LeNet诞生于1998年,网络结构比较完整,包括卷积层、pooling层、全连接层,这些都是现代CNN网络的基本组件,其被认为是CNN的开端。主要用于识别10个手写邮政编码数字,5*5卷积核,stride=1,应用到了最大池化。
2.网络结构
通过图1我们可以发现,Lenet网络的主要结构是由:输入层、卷积层、池化层、全连接层、输出层这五个经典部分,网络的主要流程是:卷积->池化->卷积->池化。(现在的网络基本流程是会在池化层之后加一个激活层,如Relu、LeakRelu;有的的网络通过1x1的卷积替换了全连接层,实现了全卷积的操作,具体见后面链接)。1x1的卷积为什么能够替代全连接层
Alexnet
1.简介
2012年Geoffrey和他学生Alex在ImageNet的竞赛中,刷新了image classification的记录,一举奠定了deep learning 在计算机视觉中的地位。这次竞赛中Alex所用的结构就被称为作为AlexNet。
2.网络结构
上图 Alexnet的整个网络结构是由5个卷积层和3个全连接层组成的,深度总共8层。AlexNet针对的是1000类的分类问题,输入图片规定是256×256的三通道彩色图片,为了增强模型的泛化能力,避免过拟合,作者使用了随机裁剪的思路对原来256×256的图像进行随机裁剪,得到尺寸为3×224×224的图像,输入到网络训练。
注意:输入Input的图像规格: 224X224X3(RGB图像),实际上会经过预处理变为227X227X3。因为 (224-11)/4+1 ≠ 55,所以这里是做了padding再做卷积的,即先padiing图像至227×227,再做卷积(227-11)/4+1 = 55
AlexNet将CNN用到了更深更宽的网络中,其效果分类的精度更高相比于以前的LeNet 用到的trick:
1.数据增广技巧来增加模型泛化能力 256×256×3 -->随机裁剪224×224×3 -->进入网络。
2.非线性激活函数ReLU:AlexNet使用ReLU代替了Sigmoid,其能更快的训练,同时解决sigmoid在训练较深的网络中出现的梯度消失(网络越深,梯度值越小)。
3.防止过拟合的方法:Dropout,Data augmentation等。Dropout随机失活,随机忽略一些神经元,以避免过拟合。
4.以前的CNN中普遍使用平均池化层,AlexNet全部使用最大池化层,避免了平均池化层的模糊化的效果(平均池化会使图像变得平滑,使边缘信息或者高频信息模糊),并且步长比池化的核的尺寸小,这样池化层的输出之间有重叠(池化层移动的时候,会出现重复的区域,这种重复会产生联系。使得特征更丰富),提升了特征的丰富性。
5.提出了LRN层,局部响应归一化增加了泛化能力,做了平滑处理,提高了1%~2%的识别率, LRN 对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大(使得重要位置更突出),并抑制其他反馈较小的神经元。
6.使用多个GPU,LRN归一化层。其主要的优势有:网络扩大(5个卷积层+3个全连接层+1个softmax层);解决过拟合问题(dropout,data augmentation,LRN);多GPU加速计算。
VGG
1.简介
VGG-Net来自 Andrew Zisserman 教授的组 (Oxford),在2014年的 ILSVRC localization and classification 两个问题上分别取得了第一名和第二名,其不同于AlexNet的地方是:VGG-Net使用更多的层,通常有16/19层,而AlexNet只有8层。同时,VGG-Net的所有 convolutional layer 使用同样大小的 convolutional filter,大小为 3 x 3。总体来说,其由5层卷积层、3层全连接层、softmax输出层构成。用了3*3的卷积核(c模型用了1*1的卷积核,但一般都只用d、e模型,1*1不必管它),步长stride=1,padding=1,max池化,pooling窗口为2*2,pooling步长为2。
2.网络结构
VGG16 是在 AlexNet 网络上每一层进行了改造,5个 stage 对应 AlexNet 中的5层卷积,3层全连接仍然不变,图片输入的大小还是沿用了 224x224x3。结构上的变化:
1.共16层(不包括Max pooling层和softmax层)
2.所有的卷积核都使用3*3的大小,max pooling池化核都使用大小为2*2,采用步长stride=2,padding=0的Max pooling
卷积核个数依次为64 -> 128 -> 256 -> 512 ->512
3.VGG改进点总结
1.使用了更小的3*3卷积核,和更深的网络(更大的感受野)。两个3*3卷积核的堆叠相对于5*5卷积核的视野,三个3*3卷积核的堆叠相当于7*7卷积核的视野。 (好处:有更少的参数 3个堆叠的3*3结构只有7*7结构参数数量的(3*3*3)/(7*7)=55% ) 另一方面拥有更多的非线性变换,增加了CNN对特征的学习能力。对于感受野的知识,可以看这篇博客:一次读懂感受野
2.VGG采用的是一种Pre-training的方式,先训练级别简单(层数较浅)的VGGNet的A级网络,然后使用A网络的权重来初始化后面的复杂模型,加快训练的收敛速度。采用了Multi-Scale的方法来训练和预测。可以增加训练的数据量,防止模型过拟合,提升预测准确率
GoogLeNet
1.网络简介
神经网络除了纵向的扩展,是否能进行横向的拓展(横向拓展:更多的支路)? 提升网络性能最直接的办法就是增加网络深度和宽度,深度指网络层次数量、宽度指神经元数量。
GoogLeNet在2014的ImageNet分类任务上击败了VGG-Nets夺得冠军,GoogLeNet跟AlexNet,VGG-Nets这种单纯依靠加深网络结构进而改进网络性能的思路不一样,它另辟幽径,在加深网络的同时(22层),也在网络结构上做了创新,引入Inception结构代替了单纯的卷积+激活的传统操作(这思路最早由Network in Network提出)
2.inception的结构
一分四,然后做一些不同大小的卷积,之后再堆叠feature map。残差网络做了相加的操作,而inception做了串联的操作。(前者是add,后者是concat)
对上图做以下说明:
1.采用不同大小的卷积核意味着不同大小的感受野,最后拼接意味着不同尺度特征的融合;
2.之所以卷积核大小采用1、3和5,主要是为了方便对齐。设定卷积步长stride=1之后,只要分别设定pad=0、1、2,那么卷积之后便可以得到相同维度的特征,方便最后拼接在一起(最后拼接在一起,也就是concat操作,需要让四条支路的特征的维度相同,这样才能融合)。
3.文章说很多地方都表明pooling挺有效,所以Inception里面也嵌入了。
4.网络越到后面,特征越抽象,而且每个特征所涉及的感受野也更大了,因此随着层数的增加,3x3和5x5卷积的比例也要增加,以便能尽量保留信息。
但是,使用5x5的卷积核仍然会带来巨大的计算量。 文章借鉴NIN2,采用1x1卷积核来进行降维。
5.中间层的辅助LOSS单元
这一点的设计我觉得很有意思:网络结构中,有三个分类器(前两个为辅助分类器),两个辅助分类器单独计算,最后的分类器通过一个权重相加(最后分类的概率是取平均值):loss = loss0 + loss1 * 0.3 + loss2 * 0.3,这个式子表明两点:①最后的分类器受三个分类器的影响,他们可以起到相互制约 ②最后一层的分类器最重要,其次是倒数第二个(这主要与卷积的深度有关,网络越深,分类越准确,自然权重越大)
3. 1x1卷积核的主要作用
1x1卷积核并不改变特征图的二维维度,但是可以重新定义特征图的深度(说白了,1x1的卷积可以起到不改变特征图的大小的前提下,融合特征图),融合之后,再放入5x5的卷积)。
1.降维
2.使网络更深
3.增加RELU等非线性(图中每一个a*a卷积都是conv+BN+relu的操作)。
例如:上一层的输出为100x100x128,经过具有256个输出的5x5卷积层之后(stride=1,pad=2),输出数据为100x100x256。其中,卷积层的参数为128x5x5x256。假如上一层输出先经过具有32个输出的1x1卷积层(先通过1x1的卷积来降维),再经过具有256个输出的5x5卷积层,那么最终的输出数据仍为为100x100x256,但卷积参数量已经减少为128x1x1x32 + 32x5x5x256,大约减少了4倍。
Googlenet的核心思想是inception,通过不垂直堆砌层的方法得到更宽的网络(我的理解是变宽且视野范围种类多,vgg及resnet让网络变深,inception让网络变宽,在同一层整合不同感受野的信息,并让模型自己选择卷积核的大小)
4. 几点说明
1.GoogLeNet采用了模块化的结构(Inception结构),方便增添和修改(可以直接修改该模块的参数,来实现分支卷积核的大小)
2.网络最后采用了average pooling(平均池化)来代替全连接层,该想法来自NIN(Network in Network)事实证明这样可以将准确率提高0.6%。但是,实际在最后还是加了一个全连接层,主要是为了方便对输出进行灵活调整;
3.虽然移除了全连接,但是网络中依然使用了Dropout ;
4.为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(辅助分类器)。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有裨益。而在实际测试的时候,这两个额外的softmax会被去掉。(去掉之后,和最后一层的输出相加取平均值在softmax)
Resnet
1.简介
2015年何恺明推出的ResNet在ISLVRC和COCO上横扫所有选手,获得冠军。ResNet在网络结构上做了大创新,而不再是简单的堆积层数,ResNet在卷积神经网络的新思路,绝对是深度学习发展历程上里程碑式的事件。
ResNet提出了一种减轻网络训练负担的残差学习框架,这种网络比以前使用过的网络本质上层次更深。其明确地将这层作为输入层相关的学习残差函数,而不是学习未知的函数。在ImageNet数据集用152 层(据说层数已经超过1000==)——比VGG网络深8倍的深度来评估残差网络,但它仍具有较低的复杂度。在2015年大规模视觉识别挑战赛分类任务中赢得了第一。
2.网络结构
设计了“bottleneck”形式的block(有跨越几层的直连)用全局平均池化GAP代替全连层FC,解决全连接层参数冗余的问题,但FC的优势在于在迁移学习中可改善微调的效果在(原始网络做的是1000的分类任务,我现在要做一个4分类的任务,前面的卷积层或者叫特征提取层的权重参数可以保留,我们可以冻结它的权重,然后修改全连接层的输出,这样就可以实现微调,可以使得网路快速收敛)。
随着网络深度增加,网络的准确度应该同步增加,当然要注意过拟合问题。但是网络深度增加的一个问题在于这些增加的层是参数更新的信号,因为梯度是从后向前传播的,增加网络深度后,比较靠前的层梯度会很小。这意味着这些层基本上学习停滞了,这就是梯度消失问题。(梯度反向传播,越传播到前一层,梯度越小,我们知道,有梯度,网络才能训练,没有梯度,网络就会停滞,当传播到前面层的梯度很小很小或者为0的时候,前面的特征提取层基本不会更新参数,这就会停滞),进一步理解梯度消失和梯度爆炸,可以参考这个博客:残差网络为何可以解决梯度消失
深度网络的第二个问题在于训练,当网络更深时意味着参数空间更大,优化问题变得更难,因此简单地去增加网络深度反而出现更高的训练误差,深层网络虽然收敛了,但网络却开始退化了(退化就是:简单的堆叠网络层,会使得后一层的网络并不能完全学习到前一层网络的特征,可能会学习偏,只要又一层学习偏了,越往后的层就会学习得更偏),即增加网络层数却导致更大的误差,比如下图,一个56层的网络的性能却不如20层的性能好,这不是因为过拟合(训练集训练误差依然很高),这就是烦人的退化问题。残差网络ResNet设计一种残差模块让我们可以训练更深的网络。
从上图可以看出,数据经过了两条路线,一条是常规路线,另一条则是捷径(shortcut),直接实现单位映射的直接连接的路线,这有点类似与电路中的“短路”。通过实验,这种带有shortcut的结构确实可以很好地应对退化问题(加了shortcut结构,在反向传播的公式中,很难得到很小的梯度,也就是产生小梯度的条件比较苛刻)。我们把网络中的一个模块的输入和输出关系看作是y=H(x),那么直接通过梯度方法求H(x)就会遇到上面提到的退化问题,如果使用了这种带shortcut的结构,那么可变参数部分的优化目标就不再是H(x),若用F(x)来代表需要优化的部分的话,则H(x)=F(x)+x,也就是F(x)=H(x)-x 因为在单位映射的假设中y=x就相当于观测值,所以F(x)就对应着残差,因而叫
残差网络。
为啥要这样做,因为作者认为学习残差F(X)比直接学习H(X)简单!设想下,现在我们只需要去学习输出和输入的差值(H(x)-x 即残差), 不再是学习一个完整的输出H(x),优化起来简单很多。
考虑到x的维度与F(X)维度可能不匹配情况,需进行维度匹配。这里论文中采用两种方法解决这一问题
1.zero_padding:对恒等层进行0填充的方式将维度补充完整。这种方法不会增加额外的参数
2.projection:在恒等层采用1x1的卷积核来增加维度。这种方法会增加额外的参数
3.模型搭建
import torch.nn as nn
import torch
class BasicBlock(nn.Module):
expansion = 1 #残差结构中,主分支的结构有没有发生变化
def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
super(BasicBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
kernel_size=3, stride=stride, padding=1, bias=False) #stride设置为参数,是左右两边一开始的stride不同
self.bn1 = nn.BatchNorm2d(out_channel)
self.relu = nn.ReLU()
self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
kernel_size=3, stride=1, padding=1, bias=False) #一开始是:3 1 1 的结构,不会改变特征图的大小,所以不需要残差结构
#2我们在使用BN的时候,不需要卷积有偏置
self.bn2 = nn.BatchNorm2d(out_channel)
self.downsample = downsample #下采样层:区别这是左边的结构还是右边的结构(默认为None)
def forward(self, x):
identity = x
if self.downsample is not None:
identity = self.downsample(x)
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out += identity
out = self.relu(out)
return out
#50,101,152层的结构,expension不是1,主分支的结构是变化的
class Bottleneck(nn.Module):
"""
注意:原论文中,在虚线残差结构的主分支上,第一个1x1卷积层的步距是2,第二个3x3卷积层步距是1。
但在pytorch官方实现过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,
这么做的好处是能够在top1上提升大概0.5%的准确率。
可参考Resnet v1.5 https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch
"""
expansion = 4
def __init__(self, in_channel, out_channel, stride=1, downsample=None,
groups=1, width_per_group=64):
super(Bottleneck, self).__init__()
width = int(out_channel * (width_per_group / 64.)) * groups
self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,
kernel_size=1, stride=1, bias=False) # squeeze channels
self.bn1 = nn.BatchNorm2d(width)
# -----------------------------------------
self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,
kernel_size=3, stride=stride, bias=False, padding=1)
self.bn2 = nn.BatchNorm2d(width)
# -----------------------------------------
self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,
kernel_size=1, stride=1, bias=False) # unsqueeze channels
self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
def forward(self, x):
identity = x
if self.downsample is not None:
identity = self.downsample(x)
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
out += identity
out = self.relu(out)
return out
class ResNet(nn.Module):
'''
block = BasicBlock、Bottleneck
blocks_num:堆叠的次数
'''
def __init__(self,
block,
blocks_num,
num_classes=1000,
include_top=True,
groups=1,
width_per_group=64):
super(ResNet, self).__init__()
self.include_top = include_top
self.in_channel = 64 #maxipool之后的深度
self.groups = groups
self.width_per_group = width_per_group
'''
卷积-》BN-》Relu
'''
self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,
padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(self.in_channel)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
'''
layer1-4为残差层
'''
self.layer1 = self._make_layer(block, 64, blocks_num[0])#conv2_x的stride都是1(默认);con3_x->conv5_xstride都是2
self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)
self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)
self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)
if self.include_top:
self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # output size = (1, 1)
self.fc = nn.Linear(512 * block.expansion, num_classes)
for m in self.modules(): #参数初始化
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
def _make_layer(self, block, channel, block_num, stride=1):
#channel是输出通道
downsample = None
'''
这里有两种情况:
在18、34层的时候,expension=1,self.in_channel = channel * block.expansion,后几层的stride=2,这时候就需要上采样,增加通道
在50、101、152层的时候,self.in_channel != channel * block.expansion,但是stride != 1,这时候也需要上采样(变换通道来和输出通道进行叠加)
'''
if stride != 1 or self.in_channel != channel * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(channel * block.expansion))
layers = []
#block已经是堆叠好的一层了
layers.append(block(self.in_channel,
channel,
downsample=downsample,
stride=stride,
groups=self.groups,
width_per_group=self.width_per_group))
self.in_channel = channel * block.expansion
for _ in range(1, block_num):
layers.append(block(self.in_channel,
channel,
groups=self.groups,
width_per_group=self.width_per_group))
return nn.Sequential(*layers)
def forward(self, x):
#1.一开始的特征提取
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
#2.堆叠残差层(4个层)
x = self.layer1(x) #3
x = self.layer2(x) #4
x = self.layer3(x) #6
x = self.layer4(x) #3
if self.include_top:
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
def resnet34(num_classes=1000, include_top=True):
# https://download.pytorch.org/models/resnet34-333f7ec4.pth
return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)
def resnet50(num_classes=1000, include_top=True):
# https://download.pytorch.org/models/resnet50-19c8e357.pth
return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)
def resnet101(num_classes=1000, include_top=True):
# https://download.pytorch.org/models/resnet101-5d3b4d8f.pth
return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)
def resnext50_32x4d(num_classes=1000, include_top=True):
# https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth
groups = 32
width_per_group = 4
return ResNet(Bottleneck, [3, 4, 6, 3],
num_classes=num_classes,
include_top=include_top,
groups=groups,
width_per_group=width_per_group)
def resnext101_32x8d(num_classes=1000, include_top=True):
# https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth
groups = 32
width_per_group = 8
return ResNet(Bottleneck, [3, 4, 23, 3],
num_classes=num_classes,
include_top=include_top,
groups=groups,
width_per_group=width_per_group)
if __name__ == '__main__':
x = torch.randn(1,3,256,256)
model = resnet34()
res = model(x)
print(res.size())
DenseNet
1.简介
2017最佳论文DenseNet,主要还是和ResNet及Inception网络做对比,思想上有借鉴,但却是全新的结构,网络结构并不复杂,却非常有效,在CIFAR指标上全面超越ResNet。可以说DenseNet吸收了ResNet最精华的部分,并在此上做了更加创新的工作,使得网络性能进一步提升。
2.网络结构
DenseNet是借鉴了ResNet,是ResNet的升级版,从上述ResNet可以看到,一般每个Block会有一个skip connect,而DenseNet会在每层conv间有一个skip connect。
densenet就同时做了两件事情,一是将网络中的每一层都直接与其前面层相连,提高特征的利用率;二是把网络的每一层设计得很窄,也就是卷积的输出通道数通常很小,只有几十,该层学习非常少的特征图并与输入concat使用。密集连接:缓解梯度消失问题,加强特征传播,极大的减少了参数量。
MobileNetV1
一、模型复杂度与硬件性能的衡量
1.模型复杂度的衡量
参数数量(Params):指模型含有多少参数,直接决定模型的大小,也影响推断时对内存的占用量
a.单位通常为 M,通常参数用 float32 表示,所以模型大小是参数数量的 4 倍左右
b.参数数量与模型大小转换示例:10M float32 bit = 10M × 4 Byte = 40MB
理论计算量(FLOPs):指模型推断时需要多少计算次数
是 floating point operations 的缩写(注意 s 小写),可以用来衡量算法/模型的复杂度,这关系到算法速度,大模型的单位通常为 G(GFLOPs:10亿次浮点运算),小模型单位通常为 M
通常只考虑乘加操作(Multi-Adds)的数量,而且只考虑 CONV 和 FC 等参数层的计算量,忽略 BN 和 PReLU 等等。一般情况,CONV 和 FC 层也会 忽略仅纯加操作 的计算量,如 bias 偏置加和 shotcut 残差加等,目前有 BN 的卷积层可以不加 bias
PS:也有用 MAC(Memory Access Cost) 表示的。
2.硬件性能的衡量
算力:
计算平台倾尽全力每秒钟所能完成的浮点运算数(计算速度,fp32),单位一般为 TFLOPS(floating point of per second
计算公式一般为 处理器核心时钟频率 × 处理器核心数量 × 特定数据类型的指令吞吐量 × 2 ,后面乘以 2 是因为乘加视作两次浮点运算
eg:NVIDIA A100 GPU 频率为 1.41GHZ,处理器( Streaming Multiprocessors, SMs)数为 108,每个处理器 FP32 的 CUDA Cores 数量为 64,那么 PeakFLOPS = 1.41*108*64*2 = 19.49TFLOPS
带宽:
计算平台倾尽全力每秒所能完成的内存(CPU 内存 or GPU 显存)交换量,单位一般为 GB/s(GByte/second)
计算公式一般为 (内存核心时钟频率 × 2) × (内存位宽 / 8) × 通道数,内存频率乘以 2 是因为一个时钟周期传输 2 bit 数据(上升沿和下降沿各传送一次);内存位宽的单位为 bit,除以 8 转换为 Byte;现在的内存条一般通道数为 2
eg:某相机芯片带宽:(2000MHZ × 2) × (32 / 8) × 2 = 32GB/s ,NVIDIA A100 带宽:(1215MHZ × 2) × (5120 / 8) × 1 = 1555.2GB/s
3.模型复杂度的计算公式
二、背景
随着深度学习的火热,计算机视觉领域内的卷积神经网络模型也层出不穷。从1998年的LeNet,到2012年引爆深度学习热潮的AlexNet,再到后来2014年的VGG,2015年的ResNet,深度学习网络模型在图像处理中应用的效果越来越好。神经网络体积越来越大,结构越来越复杂,预测和训练需要的硬件资源也逐步增多,往往只能在高算力的服务器中运行深度学习神经网络模型。移动设备因硬件资源和算力的限制,很难运行复杂的深度学习网络模型。
深度学习领域内也在努力促使神经网络向小型化发展。在保证模型准确率的同时体积更小,速度更快。到了2016年直至现在,业内提出了SqueezeNet、ShuffleNet、NasNet、MnasNet以及MobileNet等轻量级网络模型。这些模型使移动终端、嵌入式设备运行神经网络模型成为可能。而MobileNet在轻量级神经网络中较具代表性。
谷歌在2019年5月份推出了最新的MobileNetV3。新版MobileNet使用了更多新特性,使得MobileNet非常具有研究和分析意义,本文将对MobileNet进行详细解析。
MobileNet网络拥有更小的体积,更少的计算量,更高的精度。在轻量级神经网络中拥有极大的优势。
1.更小的体积
2.更少的计算量
3.更高的准确率
4.更快的速度
三、网络结构
1、能够减少参数数量和计算量的原理
a.深度可分离卷积的使用
深度卷积(Depthwise convolution, DW)不同于常规卷积操作,深度卷积中一个卷积核只有一维,负责一个通道,一个通道只被一个卷积核卷积;常规卷积每个卷积核的维度与输入维度相同,每个通道单独做卷积运算后相加。
以一张5x5x3(长和宽为5,RGB3通道)的彩色图片举例。每层深度卷积卷积核的数量与上一层的通道数相同(通道和卷积核一一对应)。设padding=1,stride=1,一个三通道的图像经过运算后生成了3个特征图,如下图所示:
深度卷积完成后的输出特征图通道数与输入层的通道数相同,无法扩展通道数。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的特征信息。因此需要逐点卷积来将生成的特征图进行组合生成新的特征图。
注意:深度可分离卷积在Xinception中也有应用
b.逐点卷积
逐点卷积(Pointwise Convolution, PW)的运算与标准卷积运算非常相似。
逐点卷积卷积核大小为1×1xM(M为输入数据的维度),每次卷积一个像素的区域。逐点卷积运算会将上一层的特征图在深度(通道)方向上进行加权组合,生成新的特征图,新的特征图的大小与输入数据大小一致,这种卷积方式以较少的计算量进行降维或升维操作(改变输出数据的维度)。这种卷积被用来“混合”通道之间的信息。
以一张5x5x3(长和宽为5,RGB3通道)的彩色图片举例,使用4个1x1x3的逐点卷积核进行卷积,逐点卷积运算后生成了4个特征图。这个例子是使用逐点卷积进行升维的操作,特征图从5x5x3 升维到5x5x4。如下图所示:
四、三代MobileNet网络的比较
1.V1
2.V2
3.V3
4.三代比较
EfficientNet
1.背景
在之前的一些手工设计网络中(AlexNet,VGG,ResNet等等)经常有人问,为什么输入图像分辨率要固定为224,为什么卷积的个数要设置为这个值,为什么网络的深度设为这么深?这些问题你要问设计作者的话,估计回复就四个字——工程经验。而这篇论文主要是用NAS(Neural Architecture Search)技术来搜索网络的图像输入分辨率 r ,网络的深度depth以及channel的宽度三个参数的合理化配置。在之前的一些论文中,基本都是通过改变上述3个参数中的一个来提升网络的性能,而这篇论文就是同时来探索这三个参数的影响。在论文中提到,本文提出的EfficientNet-B7在Imagenet top-1上达到了当年最高准确率84.3%,与之前准确率最高的GPipe相比,参数数量(Params)仅为其1/8.4,推理速度提升了6.1倍(看上去又快又轻量,但个人实际使用起来发现很吃显存)。下图是EfficientNet与其他网络的对比(注意,参数数量少并不意味推理速度就快)。
在之前的一些论文中,有的会通过增加网络的width即增加卷积核的个数(增加特征矩阵的channels)来提升网络的性能如图(b)所示,有的会通过增加网络的深度即使用更多的层结构来提升网络的性能如图(c)所示,有的会通过增加输入网络的分辨率来提升网络的性能如图(d)所示。而在本篇论文中会同时增加网络的width、网络的深度以及输入网络的分辨率来提升网络的性能如图(e)所示:
2.网络结构
如图所示,MBConv结构主要由一个1x1的普通卷积(升维作用,包含BN和Swish),一个kxk的Depthwise Conv卷积(包含BN和Swish)k的具体值可看EfficientNet-B0的网络框架主要有3x3和5x5两种情况,一个SE模块,一个1x1的普通卷积(降维作用,包含BN),一个Droupout层构成。
ShufflentV1
ShuffleNet是Face++的一篇关于降低深度网络计算量的论文,号称是可以在移动设备上运行的深度网络。这篇文章可以和MobileNet、Xception和ResNeXt结合来看,因为有类似的思想。卷积的group操作从AlexNet就已经有了,当时主要是解决模型在双GPU上的训练。ResNeXt借鉴了这种group操作改进了原本的ResNet。MobileNet则是采用了depthwise separable convolution代替传统的卷积操作,在几乎不影响准确率的前提下大大降低计算量,具体可以参考MobileNets-深度学习模型的加速。Xception主要也是采用depthwise separable convolution改进Inception v3的结构。
该文章主要采用channel shuffle(可以实现信息之间的交互)、pointwise group convolutions和depthwise separable convolution来修改原来的ResNet单元。
左边第一个为深度可分离卷积(PW->DW->PW);右边为shuffleNet(GConv-<channel->DW->GConv),因为作者发现,深度可分离卷积大部分的计算都耗在1x1的卷积上,所以作者改良了一下,把1x1的卷积变成组卷积。然后,组卷积的交互性太差了,作者又设计了Channel Shuffle,来实现分组后的融合。
我们可以看一下这三个网络的Floats