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

1.MobileNetv3网络详解

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

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

(1)更新Block

MobileNetv2的倒残差结构:

MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
MobileNetv3的倒残差结构:
NL表示非线性激活函数
MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
与MobileNetv2相比,MobileNetv3的倒残差结构加入了 轻量级的注意力机制
MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
注意力机制的作用方式是调整每个通道的权重(对得到的特征矩阵的每一个channel进行池化处理)。

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

利用h-swish代替swish函数

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

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

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

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

(a)减少第一个卷积层的卷积核个数(32减为16)
原论文中作者说减少卷积核后准确率不变且能够减少计算量,节省2毫秒时间
(b)精简Last Stage

MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
使用NAS搜索出来的网络结构的最后一部分为 Original Last Stage,但作者在使用过程中发现这一部分比较耗时,因此对其进行精简为 Efficient Last Stage,精简后准确率不变且节省7毫秒时间。

; 2.MobileNetv3(large)网络结构

MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
第一列Input代表mobilenetV3每个特征层的shape变化;
第二列Operator代表每次特征层即将经历的block结构,在MobileNetV3中,特征提取经过了许多的bneck结构,NBN为不适用BN层;
第三、四列分别代表了bneck内倒残差结构升维后的通道数、输入到bneck时特征层的通道数。
第五列SE代表了是否在这一层引入注意力机制。
第六列NL代表了激活函数的种类,HS代表h-swish,RE代表RELU。
第七列s代表了每一次block结构所用的步长。

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

3.MobileNetv3(small)网络结构

MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练
与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):
    def __init__(self,
                 in_planes: int,
                 out_planes: int,
                 kernel_size: int = 3,
                 stride: int = 1,
                 groups: int = 1,
                 norm_layer: Optional[Callable[..., nn.Module]] = None,
                 activation_layer: Optional[Callable[..., nn.Module]] = None):
        padding = (kernel_size - 1) // 2
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        if activation_layer is None:
            activation_layer = nn.ReLU6
        super(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):
        super(SqueezeExcitation, self).__init__()
        squeeze_c = _make_divisible(input_c // squeeze_factor, 8)
        self.fc1 = nn.Conv2d(input_c, squeeze_c, 1)
        self.fc2 = nn.Conv2d(squeeze_c, input_c, 1)

    def 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

定义网络的参数配置:

class InvertedResidualConfig:
    def __init__(self,
                 input_c: int,
                 kernel: int,
                 expanded_c: int,
                 out_c: int,
                 use_se: bool,
                 activation: str,
                 stride: int,
                 width_multi: float):
        self.input_c = self.adjust_channels(input_c, width_multi)
        self.kernel = kernel
        self.expanded_c = self.adjust_channels(expanded_c, width_multi)
        self.out_c = self.adjust_channels(out_c, width_multi)
        self.use_se = use_se
        self.use_hs = activation == "HS"
        self.stride = stride

MobileNetv3的倒残差结构:

class InvertedResidual(nn.Module):
    def __init__(self,
                 cnf: InvertedResidualConfig,
                 norm_layer: Callable[..., nn.Module]):
        super(InvertedResidual, self).__init__()

        if cnf.stride not in [1, 2]:
            raise ValueError("illegal stride value.")

        self.use_res_connect = (cnf.stride == 1 and cnf.input_c == cnf.out_c)

        layers: List[nn.Module] = []
        activation_layer = nn.Hardswish if cnf.use_hs else nn.ReLU

        if cnf.expanded_c != cnf.input_c:
            layers.append(ConvBNActivation(cnf.input_c,
                                           cnf.expanded_c,
                                           kernel_size=1,
                                           norm_layer=norm_layer,
                                           activation_layer=activation_layer))

        layers.append(ConvBNActivation(cnf.expanded_c,
                                       cnf.expanded_c,
                                       kernel_size=cnf.kernel,
                                       stride=cnf.stride,
                                       groups=cnf.expanded_c,
                                       norm_layer=norm_layer,
                                       activation_layer=activation_layer))

        if cnf.use_se:
            layers.append(SqueezeExcitation(cnf.expanded_c))

        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_c
        self.is_strided = cnf.stride > 1

    def forward(self, x: Tensor) -> Tensor:
        result = self.block(x)
        if self.use_res_connect:
            result += x

        return result

定义MobileNetv3(large)网络结构:

class MobileNetV3(nn.Module):
    def __init__(self,
                 inverted_residual_setting: List[InvertedResidualConfig],
                 last_channel: int,
                 num_classes: int = 1000,
                 block: Optional[Callable[..., nn.Module]] = None,
                 norm_layer: Optional[Callable[..., nn.Module]] = None):
        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) and
                  all([isinstance(s, InvertedResidualConfig) for s in inverted_residual_setting])):
            raise TypeError("The inverted_residual_setting should be List[InvertedResidualConfig]")

        if block is None:
            block = InvertedResidual

        if norm_layer is None:
            norm_layer = partial(nn.BatchNorm2d, eps=0.001, momentum=0.01)

        layers: List[nn.Module] = []

        firstconv_output_c = inverted_residual_setting[0].input_c
        layers.append(ConvBNActivation(3,
                                       firstconv_output_c,
                                       kernel_size=3,
                                       stride=2,
                                       norm_layer=norm_layer,
                                       activation_layer=nn.Hardswish))

        for cnf in inverted_residual_setting:
            layers.append(block(cnf, norm_layer))

        lastconv_input_c = inverted_residual_setting[-1].out_c
        lastconv_output_c = 6 * lastconv_input_c
        layers.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))

        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.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 x

    def forward(self, x: Tensor) -> Tensor:
        return self._forward_impl(x)

定义MobileNetv3(large)网络参数:

MobileNetv3(small)同理

    inverted_residual_setting = [

        bneck_conf(16, 3, 16, 16, False, "RE", 1),
        bneck_conf(16, 3, 64, 24, False, "RE", 2),
        bneck_conf(24, 3, 72, 24, False, "RE", 1),
        bneck_conf(24, 5, 72, 40, True, "RE", 2),
        bneck_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),
        bneck_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),
        bneck_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)

    return 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'

不使用预训练权重


    for param in net.features.parameters():
        param.requires_grad = False

训练结果

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

; (3)predict.py

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

Original: https://blog.csdn.net/STATEABC/article/details/123781280
Author: STATEABC
Title: MobileNetv3网络详解、使用pytorch搭建模型并基于迁移学习训练

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/707837/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球