MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练

本文主要是介绍MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.MobileNetv3网络详解

提出了MobileNetv3-LargeMobileNetv3-Small两种不同大小的网络结构,主要的区别是通道数的变化与bneck的次数

网络的创新点
(1)更新Block(bneck)
(2)使用NAS搜索参数(Neural Architecture Search)
(3)重新设计耗时层结构

(1)更新Block

MobileNetv2的倒残差结构:
在这里插入图片描述
MobileNetv3的倒残差结构:
NL表示非线性激活函数
在这里插入图片描述
与MobileNetv2相比,MobileNetv3的倒残差结构加入了轻量级的注意力机制
在这里插入图片描述
注意力机制的作用方式是调整每个通道的权重(对得到的特征矩阵的每一个channel进行池化处理)。

调整方式:特征矩阵的channel等于多少,得到的一维向量就有多少个元素;然后通过两个全连接层得到输出的向量。第一个全连接层,其节点个数为特征矩阵的channel的1/4;。第二个全连接层,其节点个数等于特征矩阵的channel;输出的向量可以理解为对特征矩阵的每一个channel分析出的一个权重关系,对于重要的channel就赋予一个比较大的权重。

利用h-swish代替swish函数
在这里插入图片描述

在这里插入图片描述

(2)使用NAS搜索参数(Neural Architecture Search)

(3)重新设计耗时层结构

(a)减少第一个卷积层的卷积核个数(32减为16)
原论文中作者说减少卷积核后准确率不变且能够减少计算量,节省2毫秒时间
(b)精简Last Stage
在这里插入图片描述
使用NAS搜索出来的网络结构的最后一部分为Original Last Stage,但作者在使用过程中发现这一部分比较耗时,因此对其进行精简为Efficient Last Stage,精简后准确率不变且节省7毫秒时间。

2.MobileNetv3(large)网络结构

在这里插入图片描述
第一列Input代表mobilenetV3每个特征层的shape变化;
第二列Operator代表每次特征层即将经历的block结构,在MobileNetV3中,特征提取经过了许多的bneck结构,NBN为不适用BN层;
第三、四列分别代表了bneck内倒残差结构升维后的通道数、输入到bneck时特征层的通道数。
第五列SE代表了是否在这一层引入注意力机制。
第六列NL代表了激活函数的种类,HS代表h-swish,RE代表RELU。
第七列s代表了每一次block结构所用的步长。

:112x112x16这一层,输入特征矩阵的channel等于exp size,所以在这一层的倒残差结构里不需要升维,没有1x1的卷积层

3.MobileNetv3(small)网络结构

在这里插入图片描述
与MobileNetV3(large)相比,bneck的次数与通道数都有一定的下降。

4.使用Pytorch搭建MobileNetv3网络

文件结构:

MobileNetv2├── model_v2.py:           MobileNetv2模型搭建├── model_v3.py:           MobileNetv3模型搭建├── train.py:              训练脚本└── predict.py:            图像预测脚本

(1)model_v3.py

定义卷积结构:

class ConvBNActivation(nn.Sequential):         #定义结构:Conv+BN+激活函数def __init__(self,in_planes: int,               #输入特征矩阵channelout_planes: int,              #输出特征矩阵channelkernel_size: int = 3,stride: int = 1,groups: int = 1,norm_layer: Optional[Callable[..., nn.Module]] = None,           #卷积后接的BN层activation_layer: Optional[Callable[..., nn.Module]] = None):    #激活函数padding = (kernel_size - 1) // 2if norm_layer is None:                  #如果没有传入norm_layer就默认设置为BNnorm_layer = nn.BatchNorm2dif activation_layer is None:            #如果没有传入激活函数寄默认使用ReLU6activation_layer = nn.ReLU6super(ConvBNActivation, self).__init__(nn.Conv2d(in_channels=in_planes,out_channels=out_planes,kernel_size=kernel_size,stride=stride,padding=padding,groups=groups,bias=False),norm_layer(out_planes),activation_layer(inplace=True))

定义注意力机制模块:

class SqueezeExcitation(nn.Module):                                      #定义注意力机制模块,此处注意对比上文讲到的注意力机制def __init__(self, input_c: int, squeeze_factor: int = 4):           #squeeze_factor为第一个全连接层的节点个数为输入特征矩阵channel的1/4,所以int=4super(SqueezeExcitation, self).__init__()squeeze_c = _make_divisible(input_c // squeeze_factor, 8)        #计算第一个全连接层的节点个数:输出特征矩阵channel/4,计算完成后调整到8最近的整数倍self.fc1 = nn.Conv2d(input_c, squeeze_c, 1)                      #全连接层1,输入输出的channelself.fc2 = nn.Conv2d(squeeze_c, input_c, 1)                      #全连接层2def forward(self, x: Tensor) -> Tensor:                              #定义正向传播scale = F.adaptive_avg_pool2d(x, output_size=(1, 1))             #对输入的特征矩阵的每一个维度进行池化操作scale = self.fc1(scale)scale = F.relu(scale, inplace=True)scale = self.fc2(scale)scale = F.hardsigmoid(scale, inplace=True)return scale * x                                                 #利用得到的权重scale与对应的channel维度的输入相乘

定义网络的参数配置:

class InvertedResidualConfig:                                            #对应MobileNetv3中的每一个bneck结构的参数配置def __init__(self,input_c: int,                                           #对应网络结构中的inputkernel: int,                                            #对应网络结构中的Operatorexpanded_c: int,                                        #对应网络结构中的exp sizeout_c: int,                                             #对应网络结构中的#outuse_se: bool,                                           #对应网络结构中的SEactivation: str,                                        #对应网络结构中的NLstride: int,                                            #对应网络结构中的swidth_multi: float):                                    #对应mobilenetv2中提到的α参数,调节每一个卷积层所使用channel的倍率银子self.input_c = self.adjust_channels(input_c, width_multi)        #调节每一个卷积层所使用channelself.kernel = kernelself.expanded_c = self.adjust_channels(expanded_c, width_multi)self.out_c = self.adjust_channels(out_c, width_multi)self.use_se = use_seself.use_hs = activation == "HS"                                 self.stride = stride

MobileNetv3的倒残差结构:

class InvertedResidual(nn.Module):                                             #mobilenetv3的倒残差结构def __init__(self,cnf: InvertedResidualConfig,norm_layer: Callable[..., nn.Module]):super(InvertedResidual, self).__init__()if cnf.stride not in [1, 2]:                                           #判断对应某一层的步长是否为1或2(网络结构中步长只有1和2)raise ValueError("illegal stride value.")self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c)  #判断是否使用捷径分支(当s=1且input_c=output_c时使用)layers: List[nn.Module] = []                                           #定义layers空列表activation_layer = nn.Hardswish if cnf.use_hs else nn.ReLU             #判断使用哪个激活函数# expand                                                               #倒残差结构中第一个1x1卷积层,用来升维处理if cnf.expanded_c != cnf.input_c:                                      #判断exp size是否等于输入特征矩阵channellayers.append(ConvBNActivation(cnf.input_c,                        #两者不相等才有1x1卷积层cnf.expanded_c,kernel_size=1,norm_layer=norm_layer,activation_layer=activation_layer))# depthwise                                                           #dw卷积layers.append(ConvBNActivation(cnf.expanded_c,                        #上一层输出的特征矩阵channelcnf.expanded_c,                        #dw卷积的输入输出特征矩阵channel一直kernel_size=cnf.kernel,stride=cnf.stride,groups=cnf.expanded_c,                 #dw卷积对每一个channel都单独使用chennel为1卷积核来进行卷积处理,其group数等于channelnorm_layer=norm_layer,activation_layer=activation_layer))if cnf.use_se:                                                        #判断当前层结构是否使用SE模块layers.append(SqueezeExcitation(cnf.expanded_c))                  #如果使用传入SE模块中的expanded_c# project                                                             #最后一个卷积层,降维layers.append(ConvBNActivation(cnf.expanded_c,cnf.out_c,kernel_size=1,norm_layer=norm_layer,activation_layer=nn.Identity))self.block = nn.Sequential(*layers)self.out_channels = cnf.out_cself.is_strided = cnf.stride > 1def forward(self, x: Tensor) -> Tensor:             #定义正向传播过程result = self.block(x)if self.use_res_connect:result += xreturn result

定义MobileNetv3(large)网络结构:

class MobileNetV3(nn.Module):                                                   #定义MobileNetv3网络结构def __init__(self,inverted_residual_setting: List[InvertedResidualConfig],       #对应bneck结构的参数列表last_channel: int,                                             #对应倒数第二个全连接层num_classes: int = 1000,                                       #类别个数,默认1000block: Optional[Callable[..., nn.Module]] = None,              #定义的mobilenetv3的倒残差结构norm_layer: Optional[Callable[..., nn.Module]] = None):        #BN层super(MobileNetV3, self).__init__()if not inverted_residual_setting:            #数据格式检查raise ValueError("The inverted_residual_setting should not be empty.")elif not (isinstance(inverted_residual_setting, List) andall([isinstance(s, InvertedResidualConfig) for s in inverted_residual_setting])):raise TypeError("The inverted_residual_setting should be List[InvertedResidualConfig]")if block is None:block = InvertedResidualif norm_layer is None:norm_layer = partial(nn.BatchNorm2d, eps=0.001, momentum=0.01)layers: List[nn.Module] = []                                        #构建网络# building first layer                                              #第一个卷积层,conv2dfirstconv_output_c = inverted_residual_setting[0].input_c           #获取第一个bneck结构的input-channel,对应第一个卷积层的输出channellayers.append(ConvBNActivation(3,firstconv_output_c,kernel_size=3,stride=2,norm_layer=norm_layer,activation_layer=nn.Hardswish))# building inverted residual blocksfor cnf in inverted_residual_setting:                              #遍历每一个bneck结构layers.append(block(cnf, norm_layer))                          #将每一层的配置文件和norm_layer传给block,然后将创建好的block(即InvertedResidual模块)添加到layers中# building last several layerslastconv_input_c = inverted_residual_setting[-1].out_c             #构建bneck之后的一层网络lastconv_output_c = 6 * lastconv_input_clayers.append(ConvBNActivation(lastconv_input_c,lastconv_output_c,kernel_size=1,norm_layer=norm_layer,activation_layer=nn.Hardswish))self.features = nn.Sequential(*layers)                             #特征提取主干部分self.avgpool = nn.AdaptiveAvgPool2d(1)                             #平均池化self.classifier = nn.Sequential(nn.Linear(lastconv_output_c, last_channel),   #全连接层,分类部分nn.Hardswish(inplace=True),nn.Dropout(p=0.2, inplace=True),nn.Linear(last_channel, num_classes))# initial weightsfor 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.GroupNorm)):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_impl(self, x: Tensor) -> Tensor:x = self.features(x)x = self.avgpool(x)x = torch.flatten(x, 1)x = self.classifier(x)return xdef forward(self, x: Tensor) -> Tensor:return self._forward_impl(x)

定义MobileNetv3(large)网络参数:

MobileNetv3(small)同理

    inverted_residual_setting = [# input_c, kernel, expanded_c, out_c, use_se, activation, stridebneck_conf(16, 3, 16, 16, False, "RE", 1),bneck_conf(16, 3, 64, 24, False, "RE", 2),  # C1bneck_conf(24, 3, 72, 24, False, "RE", 1),bneck_conf(24, 5, 72, 40, True, "RE", 2),  # C2bneck_conf(40, 5, 120, 40, True, "RE", 1),bneck_conf(40, 5, 120, 40, True, "RE", 1),bneck_conf(40, 3, 240, 80, False, "HS", 2),  # C3bneck_conf(80, 3, 200, 80, False, "HS", 1),bneck_conf(80, 3, 184, 80, False, "HS", 1),bneck_conf(80, 3, 184, 80, False, "HS", 1),bneck_conf(80, 3, 480, 112, True, "HS", 1),bneck_conf(112, 3, 672, 112, True, "HS", 1),bneck_conf(112, 5, 672, 160 // reduce_divider, True, "HS", 2),  # C4bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),bneck_conf(160 // reduce_divider, 5, 960 // reduce_divider, 160 // reduce_divider, True, "HS", 1),]last_channel = adjust_channels(1280 // reduce_divider)  # C5return MobileNetV3(inverted_residual_setting=inverted_residual_setting,last_channel=last_channel,num_classes=num_classes)

MobileNetV3预训练权重下载

https://download.pytorch.org/models/mobilenet_v3_large-8738ca79.pth

下载完成后将其名称改为mobilenet_v3_large.pth,存放在当前文件夹

数据集

数据集采用花分类数据集:使用pytorch搭建AlexNet并训练花分类数据集

(2)train.py

如果要使用v3模型,要做如下更改:
导入模块的更改

from model_v2 import MobileNetV2
改为:from model_v3 import mobilenet_v3_large

实例化网络的更改

    model_weight_path = "./mobilenet_v3_large.pth"          #预训练模型路径

保存路径更改:

    save_path = './MobileNetV3.pth'

不使用预训练权重

    # freeze features weights                        #冻结特征提取部分的所有权重,只训练最后两个全连接层权重for param in net.features.parameters():          #如果训练整个网络的权重就将这两句注释掉param.requires_grad = False

训练结果

在这里插入图片描述

(3)predict.py

同样更改导入模块、实例化模块和载入权重部分代码

这篇关于MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/671755

相关文章

解决Maven项目idea找不到本地仓库jar包问题以及使用mvn install:install-file

《解决Maven项目idea找不到本地仓库jar包问题以及使用mvninstall:install-file》:本文主要介绍解决Maven项目idea找不到本地仓库jar包问题以及使用mvnin... 目录Maven项目idea找不到本地仓库jar包以及使用mvn install:install-file基

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

Java中的@SneakyThrows注解用法详解

《Java中的@SneakyThrows注解用法详解》:本文主要介绍Java中的@SneakyThrows注解用法的相关资料,Lombok的@SneakyThrows注解简化了Java方法中的异常... 目录前言一、@SneakyThrows 简介1.1 什么是 Lombok?二、@SneakyThrows

Java中字符串转时间与时间转字符串的操作详解

《Java中字符串转时间与时间转字符串的操作详解》Java的java.time包提供了强大的日期和时间处理功能,通过DateTimeFormatter可以轻松地在日期时间对象和字符串之间进行转换,下面... 目录一、字符串转时间(一)使用预定义格式(二)自定义格式二、时间转字符串(一)使用预定义格式(二)自

Redis Pipeline(管道) 详解

《RedisPipeline(管道)详解》Pipeline管道是Redis提供的一种批量执行命令的机制,通过将多个命令一次性发送到服务器并统一接收响应,减少网络往返次数(RTT),显著提升执行效率... 目录Redis Pipeline 详解1. Pipeline 的核心概念2. 工作原理与性能提升3. 核

Python正则表达式语法及re模块中的常用函数详解

《Python正则表达式语法及re模块中的常用函数详解》这篇文章主要给大家介绍了关于Python正则表达式语法及re模块中常用函数的相关资料,正则表达式是一种强大的字符串处理工具,可以用于匹配、切分、... 目录概念、作用和步骤语法re模块中的常用函数总结 概念、作用和步骤概念: 本身也是一个字符串,其中

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

C 语言中enum枚举的定义和使用小结

《C语言中enum枚举的定义和使用小结》在C语言里,enum(枚举)是一种用户自定义的数据类型,它能够让你创建一组具名的整数常量,下面我会从定义、使用、特性等方面详细介绍enum,感兴趣的朋友一起看... 目录1、引言2、基本定义3、定义枚举变量4、自定义枚举常量的值5、枚举与switch语句结合使用6、枚

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

使用Python从PPT文档中提取图片和图片信息(如坐标、宽度和高度等)

《使用Python从PPT文档中提取图片和图片信息(如坐标、宽度和高度等)》PPT是一种高效的信息展示工具,广泛应用于教育、商务和设计等多个领域,PPT文档中常常包含丰富的图片内容,这些图片不仅提升了... 目录一、引言二、环境与工具三、python 提取PPT背景图片3.1 提取幻灯片背景图片3.2 提取