mobilenet结构详解
这里写目录标题
mobilenet简介
mobilenet网路是一种轻量级网络,专门给嵌入式设备而设计,它可以稍微降低准确率的情况下大大缩减模型的参数量。因为现在很多网络的参数量巨大,比如resnet152模型参数量就有600多兆,无法使用到嵌入式设备。
mobilenetv1
模型亮点
mobilenetv1网络是由谷歌团队在2017年提出的,这个网络有两个亮点:
1、使用了depthwise conv(dw)和pointwise conv(pw)两个卷积结构
2、使用了两个超参数(α和β)α:控制卷积核个数 β:控制输入图像分辨率
dw和pw
1.什么是dw卷积呢?
看下图 此图片是 霹雳吧啦Wz up主视频中的图片,如果不让使用请联系删除
如图所示,其采用卷积核的通道数是1,卷积核的个数=输入图片的通道数=输出特征图的通道数
2.pw卷积是什么呢?
如图就是pw卷积,它和普通的卷积操作相同,只不过其卷积核的大小是11,
dw卷积核pw卷积就组合成了我们的深度可分离卷积,下面我们看看两个操作所的参数量对比。
大家都知道,参数和输入输出特征图的大小是没有关系的,只和其特征图的深度,和卷积核的大小和深度有关,比如一个普通卷积,
到这里,基础不是很好的同学要拿出你的笔,稍微记录画一下我下面叙述的内容,这样你就便于理解了,想象力非常强可以忽略哦,比如:
输入特征图是28283
卷积核个数:4
卷积核大小:333
则在此层的参数量是3334=108
如果采用深度可分离卷积:
首先dw卷积:输入图像28283
卷积核:331
卷积核个数:3
输出特征图:28283
首先dw卷积的参数量:1333=9
然后是pw卷积:
输入特征图:28283
卷积核:113
卷积核个数:4
pw卷积的参数量1134=12
dw+pw=9+12=21
大致是3-4倍左右,我这里是简单的举了一个例子,在真实情况中比这个要大,因为我这里数值太小,影响了
α和β
第二个亮点就是设置了α和β两个参数,第一个是卷积核个数,第二个是输入图片大小。
这里给出v1网络的性能对比,
模型整体结构
这个是mobilenetv1的网络结构图了,一个conv dw和一个conv组成了一个深度可分离卷积
在研究过程中呢?很多的dw卷积的卷积核很多的参数接近于0,直接就废掉了,为了解决这个问题,我们产生了mobilenetv2版本
mobilenetv2
它是谷歌团队在2018年提出的,
模型亮点
他的亮点主要有两个:1.提出了inverted residual block(倒残差结构)和bottleneck
inverted residual block
直接上图,如图所示,第一个是残差结构,两边大中间小的一种瓶颈结构,在resnet中提出,我们这里不做详细介绍,第二个是到残差结构,两边小中间大,其先使用11的卷积核对特征图进行升维,然后用33的卷积核对特征图进行卷积提取特征,这里用到的卷积是dw卷积,我们在上边已经提到过,最后再使用1*1的卷积核进行降维,其中升维和降维的意思就是特征图的深度。
激活函数
在一般的卷积中使用的是relu激活函数,但是在mobilenetv2中提出,relu激活函数会对低维特征造成巨大的信息损失,所以本文提出了使用relu6激活函数。
bottlenect
下面这三个图显示了mobilenetv2的网络结构,下面我们进行分析
上面这个图和下面这个图是配套的,首先我们先用11的卷积进行升维,卷积得到特征图的深度是由t 这个系数进行控制,在总的结构图中也给出了t在各个层的取值,然后再进行33的dw卷积操作来提取特征, 这个图片上标的s=s,s就是步长的意思,所以其将特征图大小压缩了S,因为是dw卷积,所以其特征图的维度没有发生改变,最后一个是1*1卷积,对特征图进行降维操作,最后使用的是线性激活,不是relu激活,这三步就组成了我们mobilenet网络的基础bottlenect,这个还不是完全的,还差残差连接。
下边这个图片就是一个bottlenect,bottlenect中是有残差结构的,其实残差结构就是将卷积结果和输入直接相加,而不是堆叠,如果是直接相加的话,需要满足一定的条件才能相加,你想:如果两个特征图相加,是不是特征图的大小和维度相同才能让两个特征图中的数据对位相加,如果大小,或者维度有一个不是相等的,计算机就无法运算了,这里就是这个道理,进行卷积后必须要特征图大小和通道数相等才能相加。
网络的整体架构
下表就是mobilenetv2的网络结构图,都是由很多个bottlenect组成,
这里就详细叙述一下这个表:表中的t:就是我们刚才那个bottlenect表中的
t,就是卷积核个数(输出特征图深度)的扩增倍数,
c:就是卷积出来的特征图的通道数,或者该层卷积核的个数,
n:bottlenect的重复次数,
s:string,卷积核移动的步长,
还有需要提醒的s步长只针对第一层,其他的均为1,因为只在第一层会改变特征图的大小
在pytorch的torchvision中实现了mobilenetv2版本,有兴趣的同学可以看一下,
import torchvision.models.mobilenet
我下载的是torchvision比较老的版本了,不知道那些版本对这个为了完善,有看了的小伙伴可以跟我说一下,顺便告诉我你下载的是torchvision的版本,我也该一下,哈h
最后贴出来mobilenetv2的性能对比图,第一个是在分类中的性能,第二个是在目标检测领域中的性能。
代码
from torch import nn
import torch
from torchsummary import summary
__all__ = ['MobileNetV2', 'mobilenet_v2']
model_urls = {
'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
}
def _make_divisible(v, divisor, min_value=None):
"""
This function is taken from the original tf repo.
It ensures that all layers have a channel number that is divisible by 8
It can be seen here:
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
:param v:
:param divisor:
:param min_value:
:return:
"""
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
class ConvBNReLU(nn.Sequential):
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
padding = (kernel_size - 1) // 2
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
nn.BatchNorm2d(out_planes),
nn.ReLU6(inplace=True)
)
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio):
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2]
hidden_dim = int(round(inp * expand_ratio))
self.use_res_connect = self.stride == 1 and inp == oup
layers = []
if expand_ratio != 1:
# pw
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
layers.extend([
# dw
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
class MobileNetV2(nn.Module):
def __init__(self, num_classes=1000, width_mult=1.0, inverted_residual_setting=None, round_nearest=8):
"""
MobileNet V2 main class
Args:
num_classes (int): Number of classes
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
inverted_residual_setting: Network structure
round_nearest (int): Round the number of channels in each layer to be a multiple of this number
Set to 1 to turn off rounding
"""
super(MobileNetV2, self).__init__()
block = InvertedResidual
input_channel = 32
last_channel = 1280