MobileNet_v2:《Inverted Residuals and Linear Bottlenecks: Mobile Networks for Classification, Detection and Segmentation》
下载地址:https://arxiv.org/abs/1801.04381
目录
论文要解决什么问题?
用什么方法解决的(创新点)?
Inverted Residuals
Linear Bottlenecks
实验效果怎么样?
代码部分
参考博客
在MobileNet_v2论文中指出,实验中发现DW卷积中有大量的卷积核为0,即有许多卷积核未参与实际运算。最后发现是ReLU非线性激活函数惹的祸。
具体请参考下面的Linear Bottlenecks。
MobileNetV2 中的创新点:
下图的a为残差网络中标准的Residual block,b为本文的一创新点---Inverted residual block。
Residual block先通过1×1的Conv进行降维,再用3×3的Conv保持通道不变,最后再用一层1×1的Conv进行升维(升维后的维度和初始进入该block的维度保持一致),执行addition操作,每个卷积后面都使用了ReLU激活函数。
Inverted residual block先用1×1的Conv进行升维,激活函数使用ReLU6。再进行3×3的DW卷积(分步长为1和2),维度不变,激活函数还是使用ReLU6。最后再使用1×1的Conv进行降维,激活函数使用线性激活,不再使用ReLU,因为论文表述从高维向低维转换,使用ReLU非线性激活函数可能会造成信息丢失或破坏。
举个例子,假设输入到Inverted Residual的输入为56×56×24,则先经过1×1的Conv进行升维,扩展因子 为6,维度变为144。然后经过3×3的DW卷积,维度不变。最后再使用1×1的Conv进行降维,维度变为24.
Inverted Residual block详细构造图如下( 才有shortcut分支):
下表是论文中给出的倒残差基本结构, 为维度扩展因子。在3×3 的DW卷积中,控制步长,通过 = 2来实现下采样。 为输出通道数,即论文中的c。
另外,需注意的一个点是当 且输入特征矩阵的channels和输出特征矩阵的channels相等才会有shortccut分支。
论文中称网络层中的激活特征为兴趣流形(mainfold of interest),如果当前激活空间内兴趣流形完整度较高,经过ReLU,可能会让激活空间坍塌,不可避免的会丢失信息。
作者的思路是对一个n维空间的一个特征信息做ReLU运算,然后对比ReLU之后的结果与Input的结果相差有多大。
如上图所示,Input是一个2维数据,其中兴趣流形是其中的蓝色螺旋线。本例使用矩阵 将数据嵌入到n维空间中,后接ReLU,再使用 将其投影回2D平面。可以看到设置n=2,3时信息丢失严重,中心点坍塌掉了。当n=15~30之间,恢复的信息明显多了。这也说明了channels少的feature map后面不应该接ReLU,否则会破坏feature map,信息丢失严重 ,而channels多(>15)的feature map可以保留近乎完整的信息。
针对这个问题,既然是ReLU导致的信息损耗,就将ReLU替换成线性激活函数。
Input(输入尺寸) | 操作 | 深度扩张倍数 t | 输出通道数 c | 倒残差重复次数 n | 第一层bottleneck的步长 s |
---|---|---|---|---|---|
224×224×3 | conv2d | - | 32 | 1 | 2 |
112×112×32 | bottleneck | 1 | 16 | 1 | 1 |
112×112×16 | bottleneck | 6 | 24 | 2 | 2 |
56×56×24 | bottleneck | 6 | 32 | 3 | 2 |
28×28×32 | bottleneck | 6 | 64 | 4 | 2 |
14×14×64 | bottleneck | 6 | 96 | 3 | 1 |
14×14×96 | bottleneck | 6 | 160 | 3 | 2 |
7×7×160 | bottleneck | 6 | 320 | 1 | 1 |
7×7×320 | conv2d 1×1 | - | 1280 | 1 | 1 |
7×7×1280 | avgpool 7×7 | - | - | 1 | - |
1×1×1280 | conv2d 1×1 | - | k | - |
在ImageNet数据集对比了MobileNetV1、ShuffleNet、MobileNetV2 三个模型的Top1精度、Params和CPU(Google Pixel 1 phone)执行时间。MobileNetV2 运行时间149ms,参数6.9M,Top1精度74.7。 在ImageNet数据集,依 top-1而论,比ResNet-34,VGG19精度高,比ResNet-50精度低。
论文以MobileNetV2为基本分类网络,实现MNet V2 + SSDLite,耗时200ms,mAP 22.1,参数只有4.3M,相比之下YOLOv2 mAP 21.6,参数50.7M。模型的精度比SSD300和SSD512略低。
Mobilenet v2那个时候,Semantic Segmentation性能最高的架构是DeepLabv3,论文在MobileNetV2基础上实现DeepLabv3,同时与基于ResNet-101的架构做对比,实验效果显示MNet V2 mIOU 75.32,参数2.11M,而ResNet-101 mIOU80.49,参数58.16M,明显MNet V2 在实时性方面具有优势,具体细节还请参考论文。
定义卷积块:
def conv_bn(inp, oup, s):
# 传统的3*3卷积 CBR
return nn.Sequential(
nn.Conv2d(inp, oup, kernel_size=3, stride=s, padding=1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
def conv_1x1_bn(inp, oup):
# 传统的1*1卷积 CBR
return nn.Sequential(
nn.Conv2d(inp, oup, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
定义倒残差结构:
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, s, expand_ratio):
'''
:param inp: 输入通道
:param oup: 输出通道
:param s: 步长
:param expand_ratio: 进入3*3dw卷积之前,对输入通道进行扩展的倍数,就是下面的t
'''
super(InvertedResidual, self).__init__()
self.stride = s
assert s in [1, 2]
# round(x):返回浮点数x的四舍五入值
hidden_dim = round(inp * expand_ratio)
# shortcut分支存在条件:s=1 且 输入特征矩阵和输出特征矩阵的channels相等
self.use_res_connect = self.stride == 1 and inp == oup
if expand_ratio == 1:
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, stride=s, padding=1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# pw
nn.Conv2d(hidden_dim, oup, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(oup),
)
else:
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, s, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.ReLU6(inplace=True),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
定义Mobilenet_v2网络结构:
class MobileNetV2(nn.Module):
def __init__(self, num_classes=1000, input_size=224, width_mult=1., dropout_ratio=0.2):
super(MobileNetV2, self).__init__()
input_channel = 32
last_channel = 1280
'''
t:第一层1*1 conv2d的卷积核深度的扩展倍数,如h*w*k --> h*w*tk
c:输出特征矩阵的深度
n:倒残差结构重复次数
s:第一层bottleneck的步长,其他层为1
'''
interverted_residual_setting = [
# t, c, n, s
[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],
]
# building first layer
assert input_size % 32 == 0
input_channel = int(input_channel * width_mult)
self.last_channel = int(last_channel * width_mult) if width_mult > 1.0 else last_channel
# 第一层卷积,普通conv2d卷积,(3, 224, 224)-->(32, 112, 112)
self.features = [conv_bn(3, input_channel, 2)]
# 循环构造7个大bottleneck块,每个大bottleneck块中的倒残差堆叠数不相等
for t, c, n, s in interverted_residual_setting:
output_channel = int(c * width_mult)
# 重复构造n层倒残差结构
for i in range(n):
if i == 0:
# 每次起始第一层倒残差步长为列表中的s
self.features.append(InvertedResidual(input_channel, output_channel, s,
expand_ratio=t))
else:
# 除了第一层,其他层的倒残差步长全为1
self.features.append(InvertedResidual(input_channel, output_channel, 1,
expand_ratio=t))
# 将本层输出通道赋给下层的输入通道
input_channel = output_channel
# 上面的输出为7*7*320,再进行普通的1*1 Conv2d, 输出通道数为last_channel:1280
self.features.append(conv_1x1_bn(input_channel, self.last_channel))
# 在__init__中 self.features = nn.Sequential(…), 在forward()中只需要使用self.features(x)就可
# Sequential的输入也可以是list,然后输入的时候用*来引用
self.features = nn.Sequential(*self.features)
#
self.classifier = nn.Sequential(
nn.Dropout(dropout_ratio),
nn.Linear(self.last_channel, num_classes),
)
self._initialize_weights()
def forward(self, x):
x = self.features(x)
x = x.mean(3).mean(2)
x = self.classifier(x)
return x
# 初始化参数
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
n = m.weight.size(1)
m.weight.data.normal_(0, 0.01)
m.bias.data.zero_()
打印模型结构:
if __name__ == '__main__':
model = MobileNetV2()
model.cuda()
summary(model, (3, 224, 224))
https://zhuanlan.zhihu.com/p/67872001
图像分类中的网路结构(6)MobileNet v2