接上一节内容:Pytorch搭建常见分类网络模型------VGG、Googlenet、ResNet50 、MobileNetV2(3)_一只小小的土拨鼠的博客-CSDN博客
mobilenet系列:mobilenetV1、mobilenetV2、mobilenetV3是三个特征提取网络。MobileNet模型是Google针对手机等嵌入式设备提出的一种轻量级的深层神经网络,其使用的核心思想便是 depthwise separable convolution(深度可分离卷积) 。 从模型结构上主要对以下几点做了研究:
MobileNetV1是基于深度可分离卷积而构建的模型。 深度可分离卷积是将标准卷积分解成 深度卷积 和1x1 点向卷积 的卷积结构。 深度卷积对每个输入通道都使用一个单一的卷积核,点向卷积就将深度卷积的输出组合起来。 一个标准的卷积在一步内做了上面2件事情,而深度可分离卷积将它们分成两步。 这种分解显着的减小了模型的计算量和大小。其示意图如下所示。
(1)假设一卷积层,其卷积核大小为3×3,输入通道为3,输出通道为4; 常规卷积操作是将4个3×3×3的卷积核作用于3通道输入图像,根据卷积层参数量计算公式, 卷积计算+卷积参数量+卷积计量量 。得到所需参数为4*(3*3*3+1)= 112个。
(2)若先用3个、大小为3×3的卷积核(3*3*1)作用于3个通道的输入图像,得到了3个特征图,在做融合操作之前,接着用4个大小为1×1的卷积核(1*1*3)遍历上述得到的3个特征图,根据卷积层参数计算公式,所需参数为(3*3*1*3+3) + (1*1*3*4+4) = 46个。
上述即为深度可分离卷积的过程,普通卷积层的特征提取与特征组合一次完成并输出,而深度可分离卷积先用input_channels个
厚度为1的3*3的卷积核( depthwise分层卷积 ),再用output_channels个
厚度为input_channels的
1*1的卷积核( pointwise 卷积 )调整通道数,将特征提取与特征组合分开进行。
在MobileNet中,每个层后面都接上了BN,除了最后一个全连接层接的Softmax之外,其他都接了ReLU。整个网络共有28层。
一种
维度为 in_channels
的卷积核进行 特征提取 (没有进行特征组合);output_channels 种
维度为 in_channels
1*1 的卷积核进行 特征组合。 V2使用了跟V1类似的深度可分离结构,不同之处也正对应着V1中逐深度卷积的缺点改进:
Mobilenetv2提出Inverted Residuals结构:即:1、在可分离卷积的前面增加一个大小为1*1的卷积进行升维(Expansion layer);2、将输入和输出的部分进行连接(residual connection), 如下图所示。
Mobilenetv2提出Linear Bottlenck结构:由于DW、PW都是以Relu作为激活函数,且 PW会做降维 , 再对低维特征做ReLU时会丢失很多信息, 所以从高维向低维转换,使用ReLU激活函数可能会造成信息丢失或破坏(所以不使用非线性激活数函数),即在PW这一部分,不再使用ReLU激活函数而是使用线性激活函数,如下图。
上图中,Mobilenet v2中有两种深度可分离模块,步长为1时输入输出size相等,此时使用shortcut结构。步长为2时,由于input与outputsize不符,不添加shortcut结构。
from torch import nn
from torch.hub import load_state_dict_from_url
__all__ = ['MobileNetV2', 'mobilenet_v2']
model_urls = {
'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',}
#input_channel * width_mult, round_nearest -----》(32*1,8)------》(1280,8)
def _make_divisible(v, divisor, min_value=None):
if min_value is None:
min_value = divisor # min_value=8
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)#max(8,0) max(8,20)
if new_v < 0.9 * v:# if 8 < 28.8
new_v += divisor # new_v = 16 new_v = 28
return new_v
class ConvBNReLU(nn.Sequential):#卷积、正则,激活
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
padding = (kernel_size - 1) // 2
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
nn.BatchNorm2d(out_planes),
nn.ReLU6(inplace=True)
)
#倒残差结构:在可分离卷积的前面增加一个大小为1*1的卷积进行升维(Expansion layer);
#将输入和输出的部分进行连接(residual connection)
#Mobilenet v2中有两种深度可分离模块,步长为1时输入输出size相等,此时使用shortcut结构。
#步长为2时,由于input与outputsize不符,不添加shortcut结构。
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio):#
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2] # 用于判断关键字是否在字典中,存在则返回true,不存在则返回false。
hidden_dim = int(round(inp * expand_ratio)) # 输入维度*扩展,四舍五入参数
self.use_res_connect = self.stride == 1 and inp == oup#输入输出的通道相同且步距为1,use_res_connect 为真
layers = []
if expand_ratio != 1:#当扩展维度不为1时
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))#卷积核为1*1的升维层,输入维度inp,输出维度inp*expand_ratio
layers.extend([
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),#特征提取层,卷积核大小为3*3,输入和输出维度相同
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),#卷积核为1*1的降维层,输入维度hidden_dim,输出维度oup
nn.BatchNorm2d(oup),#正则化
])
self.conv = nn.Sequential(*layers)#将上述结构添加到self.conv结构中
def forward(self, x):
if self.use_res_connect:#如果输入输出的通道size相同,且步距为1,此时使用shortcut结构。
return x + self.conv(x)#将卷积层的输出与输入相加
else:
return self.conv(x)#步长为2时,由于input与outputsize不符,不添加shortcut结构。
class MobileNetV2(nn.Module):
def __init__(self, num_classes=1000, width_mult=1.0, inverted_residual_setting=None, round_nearest=8):
super(MobileNetV2, self).__init__()
block = InvertedResidual#倒残差结构
input_channel = 32
last_channel = 1280
#倘若为空,就添加一些值
if inverted_residual_setting is None:
inverted_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],
]
#如果不符合标准要求,则报错
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
raise ValueError("inverted_residual_setting should be non-empty "
"or a 4-element list, got {}".format(inverted_residual_setting))
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
features = [ConvBNReLU(3, input_channel, stride=2)]#特征提取层,输入维度3,输出维度32,卷积核大小3*3,步距为2
#有深度可分离卷积的倒残差结构层
# t扩展因子即升维倍数,c输出特征矩阵深度,n重复次数,s步距
for t, c, n, s in inverted_residual_setting:
output_channel = _make_divisible(c * width_mult, round_nearest)#输出深度的计算
for i in range(n):
stride = s if i == 0 else 1 #仅针对第一层,步距为stride = s,如果是其它层。步距为1
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
input_channel = output_channel
#在加上没有残差结构的卷积层,input_channel=320,last_channel=1280,步距为1
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
self.features = nn.Sequential(*features)
#添加分类结构,通过一个线性分类器,输出维度为num_classes
self.classifier = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(self.last_channel, num_classes),
)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.BatchNorm2d):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)
def forward(self, x):
x = self.features(x)
x = x.mean([2, 3])
x = self.classifier(x)
return x
def mobilenet_v2(pretrained=False, progress=True, num_classes=10):
model = MobileNetV2(num_classes=num_classes) #num_classes=num_classes
if pretrained:
state_dict = load_state_dict_from_url(model_urls['mobilenet_v2'], model_dir='./model_data',
progress=progress)
model.load_state_dict(state_dict)
model.classifier = nn.Sequential(
nn.Dropout(0.2),
nn.Linear(model.last_channel, num_classes),
)
return model
net = mobilenet_v2()
net.to(device)
print(net.to(device)) # 输出模型结构
test1 = torch.ones(64, 3, 120, 120) # 测试一下输出的形状大小 输入一个64,3,120,120的向量
test1 = net(test1.to(device)) # 将向量打入神经网络进行测试
print(test1.shape) # 查看输出的结果