MobilenetV2介绍
网络设计是基于MobileNetV1。它保持了简单性,同时显著提高了精度,在移动应用的多图像分类和检测任务上达到了最新的水平。主要贡献是一个新的层模块:具有线性瓶颈的倒置残差。该模块将输入的低维压缩表示首先扩展到高维并用轻量级深度卷积进行过滤。随后用线性卷积将特征投影回低维表示。
MobilenetV2网络结构
在介绍MobilenetV2网络结构之前需要先了解一下网络内部的细节。
1. Depthwise Separable Convolutions
深度可分卷积 这是一种分解卷积的形式,它将一个标准卷积分解为深度卷积和一个1×1卷积,1x1卷积又叫称为点卷积,下图是MobilenetV1中论文的图。 标准的卷积是输入一个
D
F
D_{F}
DF? x
D
F
D_{F}
DF? x
M
M
M 的特征图,输出一个
D
F
D_{F}
DF? x
D
F
D_{F}
DF? x
N
N
N 的特征图。
标准的卷积层的参数是
D
K
D_{K}
DK? x
D
K
D_{K}
DK? x
M
M
M x
N
N
N ,其中
D
K
D_{K}
DK?是卷积核的小大,
M
M
M是输入通道数,
N
N
N是输出通道数。
所以标准的卷积计算量如下:
D
K
D_{K}
DK? x
D
K
D_{K}
DK? x
M
M
M x
N
N
N x
D
F
D_{F}
DF? x
D
F
D_{F}
DF?
深度可分离卷积由两层组成:深度卷积和点卷积。
我们使用深度卷积来为每个输入通道(输入深度)应用一个过滤器。然后使用点态卷积(一个简单的1×1卷积)创建深度层输出的线性组合。
其计算量为:
D
K
D_{K}
DK? x
D
K
D_{K}
DK? x
M
M
M x
D
F
D_{F}
DF? x
D
F
D_{F}
DF?
相对于标准卷积,深度卷积是非常有效的。然而,它只过滤输入通道,并没有将它们组合起来创建新的功能。因此,为了生成这些新特征,需要一个额外的层,通过1 × 1的卷积计算深度卷积输出的线性组合。
其计算量为:
M
M
M x
N
N
N x
D
F
D_{F}
DF? x
D
F
D_{F}
DF?
例子: 标准卷积: 假设有一个56 x 56 x 16的特征图,其卷积核是3 x 3大小,输出通道为32,计算量大小就是56 x56 x16 x 32 x 3 x 3 = 14450688 深度可分离卷积: 假设有一个56 x 56 x 16的特征图,输出通道为32,先用16个3 x 3大小的卷积核进行卷积,接着用32个1 x 1大小的卷积核分别对这用16个3 x 3大小的卷积核进行卷积之后的特征图进行卷积,计算量为3 x 3 x 16 x 56 x 56 + 16 x 32 x 56 x 56 = 2057216
从而可见计算量少了许多。
2. Linear Bottlenecks
在论文中,实验者们发现,使用线性层是至关重要的,因为它可以防止非线性破坏太多的信息,在瓶颈中使用非线性层确实会使性能降低几个百分点,如下图。线性瓶颈 模型的严格来说比非线性模型要弱一些,因为激活总是可以在线性状态下进行,并对偏差和缩放进行适当的修改。然而,我们在图a中展示的实验表明,线性瓶颈改善了性能,为非线性破坏低维空间中的信息提供了支持。
3. Inverted residuals
倒残差瓶颈块 与残差块类似,其中每个块包含一个输入,然后是几个瓶颈,然后是扩展,下图是论文中给大家展示的残差块与倒残差瓶颈块的区别。
主要区别就是残差块先进行降维再升维,而倒残差瓶颈块是先进行升维再降维,结构如下: 倒残差瓶颈块从
k
k
k转换为
k
′
k′
k′个通道,步长为
s
s
s,扩展系数为
t
t
t。
4. Model Architecture
网络使用使用ReLU6作为非线性,因为用于低精度计算时它的鲁棒性。
其中:
-
n
n
n代表重复次数。
-
c
c
c代表有相同的通道数。
-
s
s
s代表步长。
-
t
t
t代表扩展系数。
网络结构代码演示
参考:https://github.com/PaddlePaddle/PaddleClas/blob/release/2.3/ppcls/arch/backbone/model_zoo/mobilenet_v2.py
import numpy as np
import paddle
from paddle import ParamAttr
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.nn import Conv2D, BatchNorm, Linear
from paddle.nn import AdaptiveAvgPool2D
class ConvBNLayer(nn.Layer):
def __init__(self,
num_channels,
filter_size,
num_filters,
stride,
padding,
channels=None,
num_groups=1,
name=None,
use_cudnn=True):
super(ConvBNLayer, self).__init__()
self._conv = Conv2D(
in_channels=num_channels,
out_channels=num_filters,
kernel_size=filter_size,
stride=stride,
padding=padding,
groups=num_groups,
weight_attr=ParamAttr(name=name + "_weights"),
bias_attr=False)
self._batch_norm = BatchNorm(
num_filters,
param_attr=ParamAttr(name=name + "_bn_scale"),
bias_attr=ParamAttr(name=name + "_bn_offset"),
moving_mean_name=name + "_bn_mean",
moving_variance_name=name + "_bn_variance")
def forward(self, inputs, if_act=True):
y = self._conv(inputs)
y = self._batch_norm(y)
if if_act:
y = F.relu6(y)
return y
class InvertedResidualUnit(nn.Layer):
def __init__(self, num_channels, num_in_filter, num_filters, stride,
filter_size, padding, expansion_factor, name):
super(InvertedResidualUnit, self).__init__()
num_expfilter = int(round(num_in_filter * expansion_factor))
self._expand_conv = ConvBNLayer(
num_channels=num_channels,
num_filters=num_expfilter,
filter_size=1,
stride=1,
padding=0,
num_groups=1,
name=name + "_expand")
self._bottleneck_conv = ConvBNLayer(
num_channels=num_expfilter,
num_filters=num_expfilter,
filter_size=filter_size,
stride=stride,
padding=padding,
num_groups=num_expfilter,
use_cudnn=False,
name=name + "_dwise")
self._linear_conv = ConvBNLayer(
num_channels=num_expfilter,
num_filters=num_filters,
filter_size=1,
stride=1,
padding=0,
num_groups=1,
name=name + "_linear")
def forward(self, inputs, ifshortcut):
y = self._expand_conv(inputs, if_act=True)
y = self._bottleneck_conv(y, if_act=True)
y = self._linear_conv(y, if_act=False)
if ifshortcut:
y = paddle.add(inputs, y)
return y
class InvresiBlocks(nn.Layer):
def __init__(self, in_c, t, c, n, s, name):
super(InvresiBlocks, self).__init__()
self._first_block = InvertedResidualUnit(
num_channels=in_c,
num_in_filter=in_c,
num_filters=c,
stride=s,
filter_size=3,
padding=1,
expansion_factor=t,
name=name + "_1")
self._block_list = []
for i in range(1, n):
block = self.add_sublayer(
name + "_" + str(i + 1),
sublayer=InvertedResidualUnit(
num_channels=c,
num_in_filter=c,
num_filters=c,
stride=1,
filter_size=3,
padding=1,
expansion_factor=t,
name=name + "_" + str(i + 1)))
self._block_list.append(block)
def forward(self, inputs):
y = self._first_block(inputs, ifshortcut=False)
for block in self._block_list:
y = block(y, ifshortcut=True)
return y
class MobileNetv2(nn.Layer):
def __init__(self, class_num=1000, scale=0.5, prefix_name=""):
super(MobileNetv2, self).__init__()
self.scale = scale
self.class_num = class_num
bottleneck_params_list = [
(1, 16, 1, 1),
(6, 24, 2, 2),
(6, 32, 3, 2),
(6, 64, 4, 2),
(6, 96, 3, 1),
(6, 160, 3, 2),
(6, 320, 1, 1),
]
self.conv1 = ConvBNLayer(
num_channels=3,
num_filters=int(32 * scale),
filter_size=3,
stride=2,
padding=1,
name=prefix_name + "conv1_1")
self.block_list = []
i = 1
in_c = int(32 * scale)
for layer_setting in bottleneck_params_list:
t, c, n, s = layer_setting
i += 1
block = self.add_sublayer(
prefix_name + "conv" + str(i),
sublayer=InvresiBlocks(
in_c=in_c,
t=t,
c=int(c * scale),
n=n,
s=s,
name=prefix_name + "conv" + str(i)))
self.block_list.append(block)
in_c = int(c * scale)
self.out_c = int(1280 * scale) if scale > 1.0 else 1280
self.conv9 = ConvBNLayer(
num_channels=in_c,
num_filters=self.out_c,
filter_size=1,
stride=1,
padding=0,
name=prefix_name + "conv9")
self.pool2d_avg = AdaptiveAvgPool2D(1)
self.out = Linear(
self.out_c,
class_num,
weight_attr=ParamAttr(name=prefix_name + "fc10_weights"),
bias_attr=ParamAttr(name=prefix_name + "fc10_offset"))
def forward(self, inputs):
y = self.conv1(inputs, if_act=True)
for block in self.block_list:
y = block(y)
y = self.conv9(y, if_act=True)
y = self.pool2d_avg(y)
y = paddle.flatten(y, start_axis=1, stop_axis=-1)
y = self.out(y)
return y
代码仓库
数据集链接:https://pan.baidu.com/s/1zs9U76OmGAIwbYr91KQxgg 提取码:bhjx
代码地址: gitee:https://gitee.com/Hao_gg/classification-pp github:https://github.com/hao-ux/classification-pp
|