目录
前景概述
(1)mobilenetV2;
(2)mobilenetV3主要贡献简单来说,有三点,一是SE-Net优化了InvertedResidual,二是使用NAS搜索出最优的网络结构,三是提出了新的激活函数h-swish.
mobilenetV3论文
https://arxiv.org/pdf/1905.02244.pdf
mobilenetV3网络结构(Large)
网络结构逐层分析
第1层conv2d,结构是conv2d + bn + HSwish. 注意这里的激活函数是HSwish是公式是:
RE是指soft形式,即ReLU正常形式。其中HS实现如下。
class hswish(nn.Module):
def forward(self, x):
out = x * F.relu6(x + 3, inplace=True) / 6
return out
第1层实现如下。
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(16)
self.hs1 = hswish()
中间15层bneck
是mobilenetV3提出的新组件,相对于V2多了SE-Net插件。V2和V3组件对比。
SE-Net插件,简单来说就是两步,squeeze操作和excitation操作,如下所示。
(1)先squeeze操作,用白话讲就是对前面的特征图U进行avgPooling,得到1x1xC的特征向量;(2)然后excitation操作,其实就是fc全连接,得到新的特征向量;
(3)最后用这个新的特征向量逐点卷积pw,即对U每个通道分别加权。
SE-Net插件代码
class SeModule(nn.Module):
def __init__(self, in_size, reduction=4):
super(SeModule, self).__init__()
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(1), # 1x1xC
# 这里用全卷积形式
nn.Conv2d(in_size, in_size // reduction, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size // reduction),
nn.ReLU(inplace=True),
# 论文中的组件是有两个fc的
nn.Conv2d(in_size // reduction, in_size, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(in_size),
hsigmoid()
)
def forward(self, x):
return x * self.se(x)
class Block(nn.Module):
"""expand + depthwise + pointwise,
eg:
Block(3, 80, 184, 80, hswish(), None, 1),
Block(3, 80, 480, 112, hswish(), SeModule(112), 1),
"""
def __init__(self, kernel_size, in_size, expand_size, out_size, nolinear, semodule, stride):
super(Block, self).__init__()
self.stride = stride
self.se = semodule # SE-Net插件
# pw, expand通道
self.conv1 = nn.Conv2d(in_size, expand_size, kernel_size=1, stride=1, padding=0, bias=False)
self.bn1 = nn.BatchNorm2d(expand_size)
self.nolinear1 = nolinear
# dw, 使用深度卷积,对每个通道分别单独卷积。实现形式是groups=input_channel。
self.conv2 = nn.Conv2d(expand_size, expand_size, kernel_size=kernel_size, stride=stride, padding=kernel_size//2, groups=expand_size, bias=False)
self.bn2 = nn.BatchNorm2d(expand_size)
self.nolinear2 = nolinear
# pw-linear,实现形式就是在pw基础上,去掉激活函数。
self.conv3 = nn.Conv2d(expand_size, out_size, kernel_size=1, stride=1, padding=0, bias=False)
self.bn3 = nn.BatchNorm2d(out_size)
# 只有步长为1,且特征通道的情况下,才能使用跳层连接。
self.shortcut = nn.Sequential()
if stride == 1 and in_size != out_size:
self.shortcut = nn.Sequential( # 定义普通的1x1卷积。
nn.Conv2d(in_size, out_size, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_size),
)
def forward(self, x):
out = self.nolinear1(self.bn1(self.conv1(x))) # pw expand
out = self.nolinear2(self.bn2(self.conv2(out))) # dw
out = self.bn3(self.conv3(out)) # pw-linear
# se-net
if self.se != None:
out = self.se(out)
out = out + self.shortcut(x) if self.stride==1 else out
return out
mmclassification实现V3
作为backbone,去掉了bneck后面的所有4层。这里就不贴太多代码了,可参考V2讲解。
def forward(self, x):
x = self.conv1(x) # conv2d + bn + HS
outs = []
for i, layer_name in enumerate(self.layers): # bneck层,记录每一层输出。
layer = getattr(self, layer_name)
x = layer(x)
if i in self.out_indices:
outs.append(x)
if len(outs) == 1: # 支持任意单层输出。
return outs[0]
else:
return tuple(outs) # 也支持多层输出。