Table of Contents
论文名:MobileNetV2: Inverted Residuals and Linear Bottlenecks
下载地址:https://arxiv.org/pdf/1801.04381.pdf
正文
MobileNet V2是为了解决MobileNet V1在训练中特征退化的问题而提出的(了解MobileNetV1请移步博客MobileNetV1网络深入解析),为了解决这个问题,论文提出了线性瓶颈的倒残差结构the inverted residual with linear bottleneck,它主要包含linear bottleneck和Inverted residuals。
1.原理解析
MobileNet V2的设计思想都包含在倒残差结构中,它主要包含两个内容linear bottleneck和Inverted residuals,后面分别对它们进行讲解.
MobileNetV1为什么会出现特征退化呢?论文作者认为这是ReLU导致的,并给出了解释.这块我没太读懂,就简单的说一下自己的理解吧.论文中给出下面这个图表达的意思是:输入数据经过conv处理得到输出数据,输出数据的维度越高,经过ReLU的处理后数据的损失才越小.那么现在有两种解决办法:要么提高输出数据的维度,要么去掉ReLU函数采用线性单元.
Linear Bottleneck
由上述分析可知,为了解决特征退化的最直接的方法就是将ReLU函数换成线性函数(其实就是去掉ReLU函数,不做处理),为了保证深度可分离卷积结构(Depthwise Separable Convolutions,简称DS conv)的非线性拟合能力,只将结构中1*1卷积(pointwise conv)后面的ReLU函数换成线性函数,由此得到了Linear Bottleneck.
Inverted Residual Blocks
特征维度越高,经过ReLU函数处理后信息丢失越少,因此在DS conv结构之前增加一个1*1卷积将feature map的维度提升,从而保证在经过ReLU处理后信息丢失的更少;同时借鉴ResNet的跨层连接shortcut的思想,提高特征的利用率。ResNet的操作是先降维再升维,而本文是先升维再将维,因此叫做倒残差模型Inverted Residual Blocks,其结构图如下所示:
bottleneck
将Linear Bottleneck和Inverted Residual Blocks结合到一起,得到MobileNet V2的结构基元bottleneck,其结构图如下所示(方块的高度即代表通道数):
当stride=2时,输入输出的维度不同,不进行shortcut连接操作。
2.网络结构
其中t表示扩增的倍数,c表示输出特征图的channel,n表示层的重复次数,s表示stride。
需要注意的是:
1) 当n>1时(即该瓶颈层重复的次数>1),只在第一个瓶颈层stride为对应的s,其他重复的瓶颈层stride均为1
2) 当n>1时,只在第一个瓶颈层特征维度为c,其他时候channel不变。
3.MobileNetV2实现
3.1 BottleNeck
class BottleNeck(nn.Module):
def __init__(self, in_channels, out_channels, stride, t):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_channels, in_channels*t, 1, bias=False),
nn.BatchNorm2d(in_channels*t),
nn.ReLU6(inplace=True),
nn.Conv2d(in_channels*t, in_channels*t, 3, stride=stride, padding=1, groups=in_channels*t, bias=False),
nn.BatchNorm2d(in_channels*t),
nn.ReLU6(inplace=True),
nn.Conv2d(in_channels*t, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels)
)
self.shortcut = nn.Sequential()
if stride == 1 and in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, 1, bias=False),
nn.BatchNorm2d(out_channels)
)
self.stride = stride
def forward(self, x):
out = self.conv(x)
if self.stride == 1:
out += self.shortcut(x)
return out
3.2 MobileNetV2整体实现
class MobileNetV2(nn.Module):
def __init__(self, class_num=settings.CLASSES_NUM):
super().__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(3, 32, 3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(32),
nn.ReLU6(inplace=True)
)
self.bottleneck1 = self.make_layer(1, 32, 16, 1, 1)
self.bottleneck2 = self.make_layer(2, 16, 24, 2, 6)
self.bottleneck3 = self.make_layer(3, 24, 32, 2, 6)
self.bottleneck4 = self.make_layer(4, 32, 64, 2, 6)
self.bottleneck5 = self.make_layer(3, 64, 96, 1, 6)
self.bottleneck6 = self.make_layer(3, 96, 160, 2, 6)
self.bottleneck7 = self.make_layer(1, 160, 320, 1, 6)
self.conv2 = nn.Sequential(
nn.Conv2d(320, 1280, 1, bias=False),
nn.BatchNorm2d(1280),
nn.ReLU6(inplace=True)
)
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.conv3 = nn.Conv2d(1280, class_num, 1, bias=False)
def make_layer(self, repeat, in_channels, out_channels, stride, t):
layers = []
layers.append(BottleNeck(in_channels, out_channels, stride, t))
while repeat-1:
layers.append(BottleNeck(out_channels, out_channels, 1, t))
repeat -= 1
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.bottleneck1(x)
x = self.bottleneck2(x)
x = self.bottleneck3(x)
x = self.bottleneck4(x)
x = self.bottleneck5(x)
x = self.bottleneck6(x)
x = self.bottleneck7(x)
x = self.conv2(x)
x = self.avgpool(x)
x = self.conv3(x)
x = x.flatten(1)
return x