在本文中,我们将深入探讨在ECCV 2020上发布的对最近流行的MobileNet体系结构(即MobileNeXt)进行的改造。
尽管深度神经网络的许多最新进展都倾向于优先考虑性能而不是复杂性,但社区中仍然存在着使深度神经网络更便宜,更高效地用于较小设备的火花。高效神经网络的研究方向多种多样,从二进制神经网络和高效网络到基于组件的研究(例如PyConv和Depthwise卷积层)。经受时间考验的一项工作是MobileNet模型的集合,其中包括MobileNet,MobileNetV2和MobileNetV3。
在本文中,我们将在ECCV 2020上发布新条目MobileNeXt,以继续在MobileNet系列模型中取得进步。我们将首先回顾一下残差网络和MobileNetV2的瓶颈结构,并讨论它们之间的差异。接下来,我们将介绍在MobileNeXt中提出的经过重新设计的瓶颈结构,然后再深入研究SandGlass块的PyTorch实现。我们将通过研究本文所观察到的结果来总结本文,并附上该模型的缺点。
Bottleneck Structures
高性能神经网络体系结构的第一个主要出现无疑是残差网络(称为ResNet)。残差网络一直是现代神经网络体系结构的基础和圣经。这些网络中提出的剩余层由于其改善信息传播的能力而存在于所有新颖的体系结构中。

ResNet提出了一个基本的基本可移植结构,称为“残差块”。残差块有两种变体,如上图所示;残差块v1和残差块v2。残差块v1主要用于在CIFAR数据集上评估的较小的ResNet模型,而残差块v2是用于基于ImageNet的模型的讨论最多的块结构。从现在开始,当我们提到残差块时,我们将参考残差块v2,以使讨论与基于ImageNet的模型相关。
让我们将此“残差瓶颈”分解为它的核心组件,这些组件包括主分支(main branch)和残差连接( residual connection)(也称为“快捷连接”)。

Main Branch
如上所示,在残差块中,输入首先经过(1x1)(1 x 1)(1x1)点向卷积运算符(注意:点向卷积不影响输入张量的空间尺寸,但用于操纵通道数在张量中)。随后是保留通道的空间(3x3)(3 x 3)(3x3)卷积,这实际上减小了输入张量中特征图的空间尺寸。最后,接着是另一个逐点(1x1)(1 x 1)(1x1)卷积层,该层将通道数增加到与主分支输入相同的数量。
Residual Connection
平行于主分支,将输入张量投影到与主分支的输出相同的尺寸。对于剩余连接,唯一需要更改输入的维是空间维,这是通过使用步幅增大的(1x1)(1 x 1)(1x1)卷积算符来实现的,以将空间维与输出的维相匹配。主分支。(注意:此(1x1)(1 x 1)(1x1)卷积不是点运算符。)
然后,将残差添加到主分支输出中,最后将其传递给标准非线性激活函数(在这种情况下为ReLU)。
Inverted Residual Connection

MobileNeXt
通过继承倒置残差模块,MobileNetV2取代了其前身MobileNetV1,成为最广泛使用的轻量级体系结构之一。尽管残差块通过残差/快捷方式连接连接了较高维的张量(即,具有更多通道的那些张量),但MobileNetV2提出了通过残差连接来连接瓶颈并将较大的通道张量保持在主分支内的方法,从而对此进行了反转。这是减少所需参数和FLOP的关键重新设计因素之一。
深度卷积层的使用进一步放大了这一点,每个通道本质上都有一个卷积滤波器。通过这种结构上的改革,MobileNet在诸如ImageNet数据集上的Top-1准确性之类的性能指标上无法胜过其ResNet同类产品(这主要是由于使用了深度卷积运算符,从而减少了特征空间)考虑到与ResNet系列产品相比轻巧,快速和高效,MobileNet仍然能够获得可观的性能。
SandGlass Block

- (a) Residual Block (b) Inverted Residual Block
Sand Glass Block
在我们之前对瓶颈结构的讨论的基础上,MobileNeXt本质上提出了对瓶颈结构的又一次大修,他们将其称为SandGlass块(如上图所示)。抽象而言,SandGlass是经典残差瓶颈和倒置残差瓶颈的简单组合,融合了两个方面的优势。让我们更详细地看一下:

- (a) Inverted Residual Bottleneck (b) SandGlass Bottleneck
SandGlass块本质上是一个经典的残差块,其中主分支中的第一个和最后一个卷积层是保留通道的空间深度卷积层。为了模拟瓶颈结构,它使用两个连续的逐点卷积运算符来首先减少然后增加通道数。这些点运算符堆叠在两个深度卷积层之间。由于现在更大的通道张量由深度内核操作,因此与例如MobileNetV2相比,参数数量大大减少。
PyTorch代码
import math
import torch
from torch import nn
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, norm_layer=None):
padding = (kernel_size - 1) // 2
if norm_layer is None:
norm_layer = nn.BatchNorm2d
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
norm_layer(out_planes),
nn.ReLU6(inplace=True)
)
class SandGlass(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio, identity_tensor_multiplier=1.0, norm_layer=None, keep_3x3=False):
super(SandGlass, self).__init__()
self.stride = stride
assert stride in [1, 2]
self.use_identity = False if identity_tensor_multiplier==1.0 else True
self.identity_tensor_channels = int(round(inp*identity_tensor_multiplier))
if norm_layer is None:
norm_layer = nn.BatchNorm2d
hidden_dim = inp // expand_ratio
if hidden_dim < oup /6.:
hidden_dim = math.ceil(oup / 6.)
hidden_dim = _make_divisible(hidden_dim, 16)
self.use_res_connect = self.stride == 1 and inp == oup
layers = []
# dw
if expand_ratio == 2 or inp==oup or keep_3x3:
layers.append(ConvBNReLU(inp, inp, kernel_size=3, stride=1, groups=inp, norm_layer=norm_layer))
if expand_ratio != 1:
# pw-linear
layers.extend([
nn.Conv2d(inp, hidden_dim, kernel_size=1, stride=1, padding=0, groups=1, bias=False),
norm_layer(hidden_dim),
])
layers.extend([
# pw
ConvBNReLU(hidden_dim, oup, kernel_size=1, stride=1, groups=1, norm_layer=norm_layer),
])
if expand_ratio == 2 or inp==oup or keep_3x3 or stride==2:
layers.extend([
# dw-linear
nn.Conv2d(oup, oup, kernel_size=3, stride=stride, groups=oup, padding=1, bias=False),
norm_layer(oup),
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
out = self.conv(x)
if self.use_res_connect:
if self.use_identity:
identity_tensor= x[:,:self.identity_tensor_channels,:,:] + out[:,:self.identity_tensor_channels,:,:]
out = torch.cat([identity_tensor, out[:,self.identity_tensor_channels:,:,:]], dim=1)
# out[:,:self.identity_tensor_channels,:,:] += x[:,:self.identity_tensor_channels,:,:]
else:
out = x + out
return out
else:
return out
Results
ImageNet分类任务的结果

如上图所示,MobileNeXt在各个方面似乎都比MobileNet更好。就参数而言,它更轻巧,并且仍然获得比其同等MobileNetV2同类产品更高的精度。在随后的表格中,我们将展示MobileNeXt如何执行不同的任务,例如ImageNet数据集上的图像分类和Pascal VOC数据集上的对象检测。
PASCAL VOC上的目标检测

缺点和进一步的讨论
-
列表该论文未能提供有关物体检测任务的结论性结果。它还没有进行MS-COCO实验以充分验证其物体检测性能。
-
列表由于它与经典残差块更相关,因此它使用ResNets的训练方案,而不是MobileNets的训练方案,因此不是苹果与苹果的比较。使用MobileNetV2的训练方案在CIFAR数据集上进行测试时,MobileNeXt的性能比MobileNetV2模型差,如下图所示。

mnetv2表示MobileNet v2,而mxnet表示MobileNeXt。在CIFAR-10分类任务上显示5次平均值。
总体而言,这是一个非常简单的想法,但可以提供很好的结果,并且它可以很好地成为计算机视觉领域中的默认轻量级模型。
References
-
MobileNetV2: Inverted Residuals and Linear Bottlenecks
-
MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications Searching for MobileNetV3
-
ReActNet: Towards Precise Binary Neural Network with Generalized Activation Functions
-
EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks
-
Pyramidal Convolution: Rethinking Convolutional Neural Networks for Visual Recognition
-
Xception: Deep Learning with Depthwise Separable Convolutions
-
Rethinking Bottleneck Structure for Efficient Mobile Network Design
-
Deep Residual Learning for Image Recognition
-
Official Implementation of MobileNeXt
-
Unofficial PyTorch implementation of MobileNeXt