|从零搭建网络| MobileNet系列网络详解及搭建

|从零搭建网络| MobileNet系列网络详解及搭建

前言

由于前段时间刚刚结束了pytorch的学习,现阶段准备使用pytorch对一些经典的1D-CNN模型进行复现,通读了各种论文以及资料后,对于MobileNet系列网络有很多想法,决定从网络概述、网络优点、网络结构、网络搭建以及最后模拟数据集训练各个方面一并打包成一个详细的博客,如果有见解不是很周到的地方,还是很欢迎指出一并改正。(本篇博客主要介绍一维MobileNet系列网络,后续会持续更新二维的MobileNet系列网络)

MobileNetV1

MobileNetV1网络简介

MobileNetV1论文下载网址:https://arxiv.org/pdf/1704.04861.pdf

首先是MobileNetV1网络的介绍。

MobileNet网络最早是由google团队在2017年提出,他是一个专注于移动端或者嵌入式设备中的轻量级CNN网络。

在这里插入图片描述

在其原论文中第二部分的Prior Work中就有提到他们的设计初衷就是要设计出一个小而高效的神经网络,使其能够在移动设备或者嵌入式设备中就可以得到很好地运行。并且在简化压缩网络的同时,作者也很专注于优化延迟、选择合适的资源限制,在充分简化网络的基础上,也专注于网络的速度。

在这里插入图片描述

并且相较于传统的卷积神经网络,MobileNet网络在准确率小幅度降低的前提下大大减少了模型参数与运算量,大量提升了模型学习速度。

MobileNetV1网络创新点

深度可分离卷积(Depthwise Separable Convolution)

首先是MobileNet网络最大的一个创新点,就是提出了深度可分离卷积。

在这里插入图片描述

在原论文中提到,MobileNet网络模型基于深度可分离卷积,这是一种分解卷积的形式,它将标准卷积分解为深度卷积和成为点卷积的1×1卷积。这种分解对于模型具有显著减少计算和模型大小的效果。

下面我们来看一下深度可分离卷积的进行过程和对于模型及计算量的简化效果。

在这里插入图片描述

首先上图是一个传统卷积的过程,在传统卷积的过程中,卷积核的深度必须和输入特征矩阵的深度是相同的,并且输出特征矩阵的深度是由卷积核的个数来决定。例如上图中输出特征矩阵的深度为4,所以就需要4个深度为3的卷积核来进行卷积。即:

卷积核 c h a n n e l = 输入特征矩阵 c h a n n e l 卷积核channel = 输入特征矩阵channel 卷积核channel=输入特征矩阵channel

输出特征矩阵 c h a n n e l = 卷积核个数 输出特征矩阵channel = 卷积核个数 输出特征矩阵channel=卷积核个数

然而深度可分离卷积摒弃了传统的卷积方式,它是由DW卷积(Depthwise Convolution) 以及 PW卷积(Pointwise Convolution) 所构成,下图是一个DW卷积的过程。

在这里插入图片描述

通过上图我们可以发现,DW卷积每个卷积核的深度都是唯一的,并且深度为1,每个卷积核都只和其中一个通道的输入矩阵进行卷积运算,运算得到的结果也只是输出特征矩阵的一个通道。由此我们可以对DW卷积运算进行总结:

​ $ 卷积核channel = 1$

输入特征矩阵 c h a n n e l = 卷积核个数 = 输出特征矩阵 c h a n n e l 输入特征矩阵channel = 卷积核个数 = 输出特征矩阵channel 输入特征矩阵channel=卷积核个数=输出特征矩阵channel

由于DW卷积具有以上特性,所以相较于传统卷积相比大大减少了运算量,初步降低MobileNet网络的速度。

下面介绍一下PW卷积,下图是PW卷积的过程图:

在这里插入图片描述

从上图中我们可以看到,PW卷积就是我们所说的传统卷积,只是对于卷积核的大小限制为1×1。

1×1的卷积核在传统神经网络中有着极其重要的作用,其中一个比较重要的作用就是降低特征图的深度以大幅度减少网络的参数数量和计算量,在降低计算成本的同时,还可以保留有用的信息。这种技术在GoogLeNet的Inception模块中得到了广泛的应用。

下面我们将深度可分离卷积和传统卷积做一下对比,来感受一下相较于传统卷积而言,深度可分离卷积到底能够降低多大的计算量。首先下面图例是传统的卷积操作。

在这里插入图片描述

图示中我们输入是一个M个通道且高和宽均为DF的特征矩阵,使用N个M通道大小为DK×DK的卷积核,最后得到N个单通道的输出特征矩阵,那么我们可以由此算得这个过程的计算量为:

D k ∗ D k ∗ M ∗ N ∗ D F ∗ D F Dk * Dk * M * N * DF * DF DkDkMNDFDF

下图分别是一个DW卷积和一个PW卷积,同样使用相同规格的输入去得到一个相同规格的输出。

在这里插入图片描述

上图DW卷积使用的输入为一个M通道且高和宽均为DF的特征矩阵,通过M个1通道大小为DF×DF卷积核,生成了一个M通道大小为Df×DF的输出特征矩阵。此过程计算量为:

D k ∗ D K ∗ M ∗ D F ∗ D F Dk * DK * M * DF * DF DkDKMDFDF

在这里插入图片描述

使用DW卷积的输出来进行PW卷积,即经过N个M通道的1×1卷积核,最后得到一个N通道大小为DF×DF的输出特征矩阵。此过程计算量为:

M ∗ N ∗ D F ∗ D F M*N*DF*DF MNDFDF

此时我们来对比传统卷积和深度可分离卷积运算量的差距:

深度可分离卷积 / 传统卷积 = D k ∗ D k ∗ M ∗ N ∗ D f ∗ D f / D k ∗ D k ∗ M ∗ N ∗ D f ∗ D f + M ∗ N ∗ D F ∗ D F = 1 / N + 1 / 9 深度可分离卷积 / 传统卷积 = Dk * Dk * M * N * Df * Df/Dk * Dk * M * N * Df * Df+M*N*DF*DF = 1/N + 1/9 深度可分离卷积/传统卷积=DkDkMNDfDf/DkDkMNDfDf+MNDFDF=1/N+1/9

由此我们可以得到理论上来说,传统卷积的计算量是深度可分离卷积的8到9倍。

Width Multiplier and Resolution Multiplier

(这小节标题使用英文完全是因为没翻译出来一个听上去不那么尴尬的版本。。。)

另外一个MobileNetV1中比较创新的点是引入了两个超参数α(Width Multiplier)和β(Resolution Multiplier),首先是介绍一下α。

在这里插入图片描述

在这里插入图片描述

文中对于α的作用做了一个大致的介绍,就是他可以作为一个宽度乘法器改变输入输出的通道数以及卷积核的个数,这样就可以在每一层均匀地降低网络厚度,以便于可以构建一个更小、成本更低的模型,大概从计算量上可以减少α^2。下表为α的不同取值对于模型正确率、计算量、以及模型参数数量的影响。

在这里插入图片描述

对于超参数β(Resolution Multiplier)文中的描写如下图。

在这里插入图片描述

在这里插入图片描述

文中将超参数β称为分辨率乘法器(Resolution Multiplier),它的作用是通过设置β的大小来控制输入图像的大小,后文中也是用数据来体现,β对于控制参数量以及计算量有着巨大的作用。下表时不同取值的β对于模型各个指标的影响。

在这里插入图片描述

表中1.0MobileNet-224中的‘-224’表示的是224×224分辨率的RGB图像。

MobileNetV1网络的搭建及代码实现

下面我将一步步地介绍MobileNetV1各个模块及网络的搭建以及所需的代码,真正的从零搭建起一个神经网络。(以下代码均使用pytorch实现)

深度可分离卷积的搭建

想要搭建MobileNetV1网络首先需要对网络的灵魂部分,也就是深度可分离卷积(Depthwise Separable Convolution)模块进行搭建,首先我们从图中看一下搭建深度可分离卷积使用的相关参数及网络层。

在这里插入图片描述

就和上文所叙述的那样,深度可分离卷积使用DW卷积和PW卷积的结合,原论文中给的搭建方式为3×3的DW卷积+BatchNrom(批量归一化)+ReLu激活函数+PW卷积+BatchNorm+ReLu激活函数。

首先我们根据各参数来定义DW卷积层、PW卷积层两个BatchNorm层和ReLu激活函数层。

实现代码如下:

import torch

class DepthwiseSeparableConv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,stride):
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        # self.stride = stride
        super(DepthwiseSeparableConv,self).__init__()
        self.depthwise = torch.nn.Conv1d(input_channels,input_channels,3,
                                         stride,1,groups=input_channels)
        self.pointwise = torch.nn.Conv1d(input_channels,output_channels,1,1,0)
        self.relu = torch.nn.ReLU(inplace=True)#直接在输入数据上修改替换,减少内存
        self.BN = torch.nn.BatchNorm1d(input_channels)
        self.BN2 = torch.nn.BatchNorm1d(output_channels)

原论文中没有在DW卷积层中没有提到具体的填充大小,但是根据后续层的数据大小我们可以推断出填充大小为‘1’。这里由于两个BatchNorm需要初始化的大小不同,所以需要根据上一层的输出初始化不同的BatchNorm层。

初始化结束后,后续就可以根据上文提到的网络一个一个搭建前馈网络。

    def forward(self,x):
        x = self.depthwise(x)
        x = self.BN(x)
        x = self.pointwise(x)
        x = self.BN2(x)
        x = self.relu(x)
        return x

搭建前馈网络时,这里只需要使用一个’x’进行接收每一层的输出即可。至此深度可分离模块搭建完成,下面开始根据论文搭建MobileNetV1网络。

MobileNetV1搭建

首先还是从文中找到MobileNetV1的各个层以及参数大小,如下表所示。

在这里插入图片描述

表中第一列代表的是,各个层以及其步长;第二列是卷积核的各个参数,第三列则是输入数据的形状大小,例如第一行代表的则是:步长为2的卷积层;使用32个3通道大小为3×3的卷积核;输入数据为3通道像素为224×224的RGB图像。

我们在搭建网络的过程中,由于需要搭建一个泛用大部分同维度数据集的网络,因此不需要考虑表中输入数据的像素信息,只需要根据其通道数量、步长等信息来搭建即可。

首先我们还是先开始初始化部分,初始化部分可以分为两端,分别是特征提取分类,从上图中我们可以看到从第一层到倒数第三层的平均池化层均为神经网络不断使用卷积学习特征的部分,最后两层全连接层以及Softmax则为分类层。

初始化特征提取部分实现代码如下:

class MobileNetV1(torch.nn.Module):
    def __init__(self,inputchannel,classes):
        super(MobileNetV1,self).__init__()
        # self.input_samples = input_samples
        # self.classes = classes
        # self.inputchannel = inputchannel

        self.con_dw = torch.nn.Sequential(
            torch.nn.Conv1d(inputchannel, 32, 3, 2, 1),
            torch.nn.ReLU(inplace=True),
            DepthwiseSeparableConv(32,64,1),
            DepthwiseSeparableConv(64,128,2),
            DepthwiseSeparableConv(128,128,1),
            DepthwiseSeparableConv(128,256,2),
            DepthwiseSeparableConv(256,256,1),
            DepthwiseSeparableConv(256,512,2),
            DepthwiseSeparableConv(512,512,1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512,1024,2),
            DepthwiseSeparableConv(1024,1024,1),
            torch.nn.AdaptiveAvgPool1d(1)
        )

在特征提取部分,我们将平均池化层使用自适应平均池化层并且设置输出特征值为‘1’。其余均按照原文中给出的参数一步步搭建,其中DepthwiseSeparableConv为上部分创建的深度可分离卷积模块。初始化中的inputchannel表示输入数据的通道数,后续使用时可以根据自己的数据集通道大小自行输入,classes则表示需要分成的类别数量。

初始化分类部分代码实现如下:

self.classify = torch.nn.Linear(1024,classes)

初始化部分至此结束,下面根据原文结构搭建即可,实现代码如下:

    def forward(self,x):
        x = self.con_dw(x)
        x = x.view(x.size(0),-1)
        x = self.classify(x)
        return x

由于数据集在提取完特征后需要进入全连接层进行线性运算,所以使用x = x.view(x.size(0),-1)将数据进行展开成一维向量,或者也可以在提取特征部分的最后加一个torch.nn.Flatten()也是一样的效果。

整体代码实现:

import torch

class DepthwiseSeparableConv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,stride):
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        # self.stride = stride
        super(DepthwiseSeparableConv,self).__init__()
        self.depthwise = torch.nn.Conv1d(input_channels,input_channels,3,
                                         stride,1,groups=input_channels)
        self.pointwise = torch.nn.Conv1d(input_channels,output_channels,1,1,0)
        self.relu = torch.nn.ReLU(inplace=True)#直接在输入数据上修改替换,减少内存
        self.BN = torch.nn.BatchNorm1d(input_channels)
        self.BN2 = torch.nn.BatchNorm1d(output_channels)

    def forward(self,x):
        x = self.depthwise(x)
        x = self.BN(x)
        x = self.pointwise(x)
        x = self.BN2(x)
        x = self.relu(x)
        return x

class MobileNetV1(torch.nn.Module):
    def __init__(self,inputchannel,classes):
        super(MobileNetV1,self).__init__()
        # self.input_samples = input_samples
        # self.classes = classes
        # self.inputchannel = inputchannel

        self.con_dw = torch.nn.Sequential(
            torch.nn.Conv1d(inputchannel, 32, 3, 2, 1),
            torch.nn.ReLU(inplace=True),
            DepthwiseSeparableConv(32,64,1),
            DepthwiseSeparableConv(64,128,2),
            DepthwiseSeparableConv(128,128,1),
            DepthwiseSeparableConv(128,256,2),
            DepthwiseSeparableConv(256,256,1),
            DepthwiseSeparableConv(256,512,2),
            DepthwiseSeparableConv(512,512,1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512,1024,2),
            DepthwiseSeparableConv(1024,1024,1),
            torch.nn.AdaptiveAvgPool1d(1)
        )
        self.classify = torch.nn.Linear(1024,classes)

    def forward(self,x):
        x = self.con_dw(x)
        x = x.view(x.size(0),-1)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    model = MobileNetV1(3,10)
    x = torch.randn(1,3,224)
    y  =model(x)
    print(y.shape)

代码最后部分是随机生成一组(1,3,224)的数据经过实例化后的MobileNetV1网络看下效果,至少在搭建上没有问题。

至于MobileNetV1与其他网络在各个领域的性能比较上面论文里已经进行了很细致的描述,这里就不过多赘述,感兴趣的可以下载论文自行查看。论文连接在本章开头。

二维MobileNetV1的搭建

下面开始介绍一下二维MobileNetV1的模型,在这个模型中相较于一维的模型并没有特别多改动的地方,下面我们直接上完整代码。

import torch

class DepthwiseSeparableConv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,stride):
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        # self.stride = stride
        super(DepthwiseSeparableConv,self).__init__()
        self.depthwise = torch.nn.Conv2d(input_channels,input_channels,3,
                                         stride,1,groups=input_channels)
        self.pointwise = torch.nn.Conv2d(input_channels,output_channels,1,1,0)
        self.relu = torch.nn.ReLU(inplace=True)#直接在输入数据上修改替换,减少内存
        self.BN = torch.nn.BatchNorm2d(input_channels)
        self.BN2 = torch.nn.BatchNorm2d(output_channels)

    def forward(self,x):
        x = self.depthwise(x)
        x = self.BN(x)
        x = self.pointwise(x)
        x = self.BN2(x)
        x = self.relu(x)
        return x

class MobileNetV1(torch.nn.Module):
    def __init__(self,inputchannel,classes):
        super(MobileNetV1,self).__init__()
        # self.input_samples = input_samples
        # self.classes = classes
        # self.inputchannel = inputchannel

        self.con_dw = torch.nn.Sequential(
            torch.nn.Conv2d(inputchannel, 32, 3, 2, 1),
            torch.nn.ReLU(inplace=True),
            DepthwiseSeparableConv(32,64,1),
            DepthwiseSeparableConv(64,128,2),
            DepthwiseSeparableConv(128,128,1),
            DepthwiseSeparableConv(128,256,2),
            DepthwiseSeparableConv(256,256,1),
            DepthwiseSeparableConv(256,512,2),
            DepthwiseSeparableConv(512,512,1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512, 512, 1),
            DepthwiseSeparableConv(512,1024,2),
            DepthwiseSeparableConv(1024,1024,1),
            torch.nn.AdaptiveAvgPool2d(1)
        )
        self.classify = torch.nn.Linear(1024,classes)

    def forward(self,x):
        x = self.con_dw(x)
        x = x.view(x.size(0),-1)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    model = MobileNetV1(3,10)
    x = torch.randn(1,3,224,224)
    y  =model(x)
    print(y.shape)

说一下改动的地方,这里主要就是把各种网络层从1d改为了2d,在输入信号的地方,把形状改为(批次,通道,高,宽)即可。

MobileNetV2

介绍完MobileNetV1之后我们开始介绍MobileNetV2网络,还是从网络概述、网络优点、网络结构、网络搭建以及最后模拟数据集训练等各个方面进行介绍。

MobileNetV2网络简介

MobileNetV2论文下载地址:https://arxiv.org/pdf/1801.04381.pdf

MobileNetV2是google团队在2018年提出的,仅使用一年的时间,将MobileNet系列网络优化得准确率更高,模型更小。

在这里插入图片描述

论文名称叫做MobileNetV2:Inverted Residual and Linear Bottlenecks。

MobileNetV2网络创新点

MobileNet网络的创新点就在文章的题目中,分别是倒残差结构(Inverted Residual)和Linear Bottlenecks(线性瓶颈残差块)。再次大大减少了网络计算量以及参数量。

倒残差结构(Inverted Residual)

在MobileNetV2中提到了一个全新的结构叫做倒残差结构,与ResNet网络中的残差结构有着大量的不同。

在这里插入图片描述

其中ResNet网络中提出的残差结构首先经过一个1×1的卷积核进行压缩,从深度进行降低,中间经过一个3×3的卷积核进行卷积处理,最后经过一个1×1的卷积核重新对信号进行升维,并且每个卷积层后面都会跟着批量归一化和ReLu激活函数。最后变成一个两边大中间小的瓶颈性结构。

而MobileNetV2中的倒残差结构则是先经过一个1×1的卷积核对信号进行升维处理,中间经过一个3×3的DW卷积进行卷积处理,最后经过一个1×1的卷积核即PW卷积对信号降维,激活函数均使用ReLu6激活函数进行非线性处理。倒残差结构在保持轻量级的同时提供高效的性能。

线性瓶颈残差块(Linear Bottleneck Residual Blocks)

MobileNetV2之所以是一个为移动和嵌入式设备而设计的高效卷积神经网络架构。 就是因为 通过特有的线性瓶颈残差块(Linear Bottleneck Residual Block)来实现轻量级和高效率。这种设计优化了模型的计算成本和模型大小,使其在资源受限的环境中仍能保持良好的性能。

线性瓶颈残差块是MobileNetV2的核心,其设计基于两个主要思想:倒残差结构(Inverted Residuals)和线性瓶颈。 模块参数及结构如下图所示。

在这里插入图片描述

第一层为扩展层,使用1×1的卷积对输入的特征图进行扩展,增加深度,其中t为扩展因子,增加输出的特征图深度,目的是提供一个更高维度的表示空间,以便捕获更复杂的特征。激活函数使用ReLu6增加非线性。

第二层为深度可分离卷积层,即每个 输入通道独立地应用一个卷积滤波器,这减少了参数数量和计算量。然后通过1x1的逐点卷积(Pointwise convolution)来组合深度卷积的输出,进一步处理特征图。 且 深度卷积不使用任何激活函数,而逐点卷积则采用ReLU6激活函数,以保持非线性。

第三层为线性瓶颈层, 通过另一个1x1的卷积来减少特征图的深度,恢复到接近输入时的深度,但这一步使用线性激活(即不使用激活函数)。这种设计的目的是为了保留重要的特征,同时去除ReLU激活可能引入的非线性失真,尤其是在特征表示已经非常紧凑的情况下。

同时在本文中提出ReLu激活函数在某些方面的不足之处,以下是原文图。

在这里插入图片描述

大概意思就是ReLu激活函数对于低维特征信息会造成大量的损失。因此在最后输出较低维度的线性瓶颈层才会使用线性激活。

MobileNetV2网络的搭建及代码实现

线性瓶颈残差块的搭建

在对于MobileNetV2网络的搭建中,同样也要先搭建他的灵魂部分:线性瓶颈残差块。下面我将根据步长的不同针对性的介绍对于线性瓶颈残差块的搭建。

在这里插入图片描述

左侧是步长为1时候的线性瓶颈残差模块,右侧则是步长为2时候的线性瓶颈残差模块。在左侧步长为1的模块中,输入会依次通过1×1卷积层,3×3的DW卷积层以及1×1的线性瓶颈层。但是值得注意的是,在步长为1的模块中存在一个捷径分支(shortcut),当步长为1且输入信号的通道数(input-channels)和(output-channels)相等时,线性激活后的输出会和输入相加后变成一个新的信号作为输出。

步长为2的线性瓶颈残差模块则是结构和之前介绍的线性平静残差模块一样的结构,这里就不在过多赘述,直接进入代码实现部分。

在定义线性瓶颈残差结构之前,首先定义了一个卷积结构,这个卷积结构可以自动根据卷积核设置填充,并且在每一个卷积层后面跟着一个批量归一化层,有助于后文线性瓶颈残差模块的定义。

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,kenal_size,
                 stride = 1,groups = 1):
        # self.stride = stride
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        super(conv,self).__init__()
        if kenal_size == 1:
            padding = 0
        else:
            padding = 1
        self.con = torch.nn.Conv1d(input_channels,output_channels,
                                   kenal_size,stride,padding,groups=groups)
        self.BN = torch.nn.BatchNorm1d(output_channels)

    def forward(self,x):
        x = self.con(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        return x

在初始化中,首先根据卷积核大小定义填充,以保证在某些情况下信号大小不变。在前向传播中if条件语句表示的是只有信号最后一个维度不为1时,进行批量归一化,以避免对单个数值的归一化。

接下来是对线性瓶颈残差块的代码实现:

class Bottleneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,stride,t):
        self.stride = stride
        self.in_channels = in_channels
        self.out_channels = out_channels
        super(Bottleneck,self).__init__()
        self.block = torch.nn.Sequential(
            conv(in_channels,in_channels * t,1),
            torch.nn.ReLU6(),
            conv(in_channels * t,in_channels * t,3,
                 stride = stride,groups = in_channels * t),
            torch.nn.ReLU6(),
            conv(in_channels * t,out_channels,1)
        )

    def forward(self,x):
        x1 = self.block(x)
        if self.stride == 1 and self.in_channels == self.out_channels:
            x1 += x
        return x1

代码的结构就和之前介绍的线性瓶颈残差块的结构相同,主要注意捷径分支的触发条件即可,其他没有什么好过多赘述的。

MobileNetV2的搭建

在原文中找到网络搭建的具体参数及结构。

在这里插入图片描述

表格中第一列为输入数据大小,第二列为网络层的类型,t为扩展因子,c为输出信号的通道,n为重复次数,s为步长。值得注意的是,当步长为2且重复次数不止一次的时候,从第二次重复开始步长要变成1.

下面是MobileNetV2部分的搭建代码:

class MobileNetV2(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super(MobileNetV2,self).__init__()

        self.feature = torch.nn.Sequential(
            conv(inputchannels,32,3,2),

            Bottleneck(32,16,1,1),

            Bottleneck(16,24,2,6),
            Bottleneck(24,24,1,6),

            Bottleneck(24,32,2,6),
            Bottleneck(32,32,1,6),
            Bottleneck(32,32,1,6),

            Bottleneck(32,64,2,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),

            Bottleneck(64,96,1,6),
            Bottleneck(96,96,1,6),
            Bottleneck(96,96,1,6),

            Bottleneck(96,160,2,6),
            Bottleneck(160,160,1,6),
            Bottleneck(160,160,1,6),

            Bottleneck(160,320,1,6),

            conv(320,1280,1,1),

            torch.nn.AdaptiveAvgPool1d(1)
        )

        self.classify = torch.nn.Sequential(
            conv(1280,classes,1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x

此处在初始化网络的时候,也是把输入的通道和最后分的类别数量作为两个变量在实例化的时候供用户传输,搭建过程与MobileNetV1不同的地方就是在进入‘classify’分类模块的时候并没有展开成一维向量,而是在1×1卷积层输出后展开成一维向量。原因是‘classify’模块虽然是作为线性激活对输出激活,但是是由1×1的卷积作为线性激活层的,所以本质上还是进入了一个卷积层,故不用展开成一维向量,后续展开成一维向量是因为便于后续的模型评估等。

整体代码实现:

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,kenal_size,
                 stride = 1,groups = 1):
        # self.stride = stride
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        super(conv,self).__init__()
        if kenal_size == 1:
            padding = 0
        else:
            padding = 1
        self.con = torch.nn.Conv1d(input_channels,output_channels,
                                   kenal_size,stride,padding,groups=groups)
        self.BN = torch.nn.BatchNorm1d(output_channels)

    def forward(self,x):
        x = self.con(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        return x

class Bottleneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,stride,t):
        self.stride = stride
        self.in_channels = in_channels
        self.out_channels = out_channels
        super(Bottleneck,self).__init__()
        self.block = torch.nn.Sequential(
            conv(in_channels,in_channels * t,1),
            torch.nn.ReLU6(),
            conv(in_channels * t,in_channels * t,3,
                 stride = stride,groups = in_channels * t),
            torch.nn.ReLU6(),
            conv(in_channels * t,out_channels,1)
        )

    def forward(self,x):
        x1 = self.block(x)
        if self.stride == 1 and self.in_channels == self.out_channels:
            x1 += x
        return x1

class MobileNetV2(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super(MobileNetV2,self).__init__()

        self.feature = torch.nn.Sequential(
            conv(inputchannels,32,3,2),

            Bottleneck(32,16,1,1),

            Bottleneck(16,24,2,6),
            Bottleneck(24,24,1,6),

            Bottleneck(24,32,2,6),
            Bottleneck(32,32,1,6),
            Bottleneck(32,32,1,6),

            Bottleneck(32,64,2,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),

            Bottleneck(64,96,1,6),
            Bottleneck(96,96,1,6),
            Bottleneck(96,96,1,6),

            Bottleneck(96,160,2,6),
            Bottleneck(160,160,1,6),
            Bottleneck(160,160,1,6),

            Bottleneck(160,320,1,6),

            conv(320,1280,1,1),

            torch.nn.AdaptiveAvgPool1d(1)
        )

        self.classify = torch.nn.Sequential(
            conv(1280,classes,1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    model = MobileNetV2(1,10)
    x = torch.randn(1,1,224)
    y = model(x)
    print(y.shape)


结尾同样是使用pytorch生成维度(1,1,224)的随机信号,经过一个实例化输入通道为1输出类别为10的MobileNetV2网络,可以得到最后分成了10个类别。

在这里插入图片描述

二维MobileNetV2 的搭建

下面是二维MobileNet的搭建,和MobileNetV1一维到二维一样,只需要对部分网络层进行更改即可。

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channels,output_channels,kenal_size,
                 stride = 1,groups = 1):
        # self.stride = stride
        # self.input_channels = input_channels
        # self.output_channels = output_channels
        super(conv,self).__init__()
        if kenal_size == 1:
            padding = 0
        else:
            padding = 1
        self.con = torch.nn.Conv2d(input_channels,output_channels,
                                   kenal_size,stride,padding,groups=groups)
        self.BN = torch.nn.BatchNorm2d(output_channels)

    def forward(self,x):
        x = self.con(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        return x

class Bottleneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,stride,t):
        self.stride = stride
        self.in_channels = in_channels
        self.out_channels = out_channels
        super(Bottleneck,self).__init__()
        self.block = torch.nn.Sequential(
            conv(in_channels,in_channels * t,1),
            torch.nn.ReLU6(),
            conv(in_channels * t,in_channels * t,3,
                 stride = stride,groups = in_channels * t),
            torch.nn.ReLU6(),
            conv(in_channels * t,out_channels,1)
        )

    def forward(self,x):
        x1 = self.block(x)
        if self.stride == 1 and self.in_channels == self.out_channels:
            x1 += x
        return x1

class MobileNetV2(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super(MobileNetV2,self).__init__()

        self.feature = torch.nn.Sequential(
            conv(inputchannels,32,3,2),

            Bottleneck(32,16,1,1),

            Bottleneck(16,24,2,6),
            Bottleneck(24,24,1,6),

            Bottleneck(24,32,2,6),
            Bottleneck(32,32,1,6),
            Bottleneck(32,32,1,6),

            Bottleneck(32,64,2,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),
            Bottleneck(64,64,1,6),

            Bottleneck(64,96,1,6),
            Bottleneck(96,96,1,6),
            Bottleneck(96,96,1,6),

            Bottleneck(96,160,2,6),
            Bottleneck(160,160,1,6),
            Bottleneck(160,160,1,6),

            Bottleneck(160,320,1,6),

            conv(320,1280,1,1),

            torch.nn.AdaptiveAvgPool2d(1)
        )

        self.classify = torch.nn.Sequential(
            conv(1280,classes,1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    model = MobileNetV2(3,10)
    x = torch.randn(1,3,224,224)
    y = model(x)
    print(y.shape)

MobileNetV3

介绍了MobileNet系列的前两个网络后,我们话不多说,直接开始MobileNetV3的介绍。

MobileNetV3网络简介

MobileNetV3原论文下载地址:https://arxiv.org/pdf/1905.02244.pdf

MobileNetV3相较于前两个网络而言,更新的东西还是比较多的,首先我们先来看一下他的摘要部分。

在这里插入图片描述

大概意思我总结了一下,首先是提出了基于一种互补式的搜索技术和更新颖的框架设计相结合的下一代MobileNet网络,在这个网络中提出了通过NAS和NetAdapt算法相结合进而调整到移动设备CPU的方式,并且通过这个过程创建了两个新的MobileNet模型,分别是MobileNetV3-Small和MobileNetV3-Large,他们分别针对消耗的资源高低用例,最后两个模型与更早的MobileNet网络系列相比,在准确率上有了较为显著的提高,并且延迟大幅度减少。

MobileNetV3网络创新点

在这个更新的MobileNet系列的网络中大概有三个创新点需要我们特别关注一下,分别是更新了Block(bneck),使用NAS搜索参数(Network Architecture Search),并且重新设计了耗时层的结构。我们一个个来看下。

使用NAS搜索参数

首先我们来看一下原论文中的相关介绍的一部分:

在这里插入图片描述

在这里插入图片描述

我刚开始看的时候也没有特别能理解相关的介绍,后来又查了很多资料和翻译,简单讲一下我的感受。

首先说一下我刚看完论文中相关介绍的第一感受,那就是感觉不像是我能用得起的东西(卑微),后来查了各种资料和翻译后发现他确实不是我能用的起的的东西(更卑微了)。

神经架构搜索是MobileNetV3的一个比较核心的配置,在网络的优化方面起到了很重要的作用,主要包括于网络的深度和宽度、图像解析度以及本文中提到的轻量级注意力机制(SE模块)和用到的两个激活函数(H-swish和H-sigmoid)也是通过NAS搜索集成到网络中的。并且文中提到搜索过程中考虑了mO型在特定硬件上的性能,既满足了高精度的要求,也保证了在移动设备的高效运行。

说了NAS的优点之后就不得不提一下他的成本了。由于NAS的目标是在一个预定义的搜索空间中自动寻找最优的网络架构,这个过程包括了成千上万种可能的网络结构的训练和评估,以确定哪一种结构在特定任务上表现最佳。 这就导致他需要一个很大规模的搜索空间、更多次数的重复训练和评估,而这样一个大的需求就导致了如果想在合理的时间内完成任务,就需要使用很高性能的GPU或TPU集群,只能说租用或者购买成本略高。但是写到这想到他那些优点之后我脑子里只有一句话,那就是贵的东西只有一个缺点,那就是贵。^^

更新Block(bneck)

MobileNetV3论文中,对于MobileNetV2的倒残差结构做了一个更新,分别是加入了SE模块和更新了激活函数。

首先我们先来看一下和MobileNetV2网络block模块的对比。

在这里插入图片描述

在MobileNetV3网络中的Block里首先是保留了前任网络的线性瓶颈残差结构的大部分要素,在新的模块中先是经过1×1卷积层进行升维,然后经过3×3的DW卷积进行处理,最后加一个1×1的卷积层重新降维,但是在DW卷积和最后一个1×1卷积之间加上了一个注意力机制(SE模块),SE模块则是由最大池化层和两个全连接层所组成,他的目标我们可以理解为就是在DW卷积后,对于每一个信号的channel做一系列处理,然后分析出来各个channel的重要性,然后赋予相应的权重。

还有一点就是benck中标注的NL,他们表示的是非线性激活函数的意思,但是由于每个层的激活函数都不一样,所以这里不过多赘述,下文搭建时候会详细讲解,但是需要关注的地方是最后一层1×1的卷积层并没有使用激活函数,或者可以说使用了线性激活,我感觉原因可能和MobileNetV2中讲的一样,就是非线性激活函数对于一些低维信号处理的时候会有损失。

重新设计耗时层结构

在这个创新点中,作者把其中一部分卷积层的卷积核个数进行精简到了一半,并且在作者把32个卷积核减少到16个之后,准确率方面和32个卷积核的准确率是一样的,但是消耗时间大幅度减少。

另外一个耗时层就是在最后一部分中,我们先来看原文中的一张图:

在这里插入图片描述

作者把最后的一些网络层进行了精简,调整之后作者发现在准确率上基本上没上面变化,但是执行时间大幅度减少,所以换成了新的Last Stage之后,网络执行时间相较于之前而言是明显的优化。

MobileNetV3网络搭建及代码实现

MobileNetV3网络搭建相较于前两个网络而言略显复杂,但好在这是这系列最后一个网络(目前而言),所以坚持就是胜利。

在MobileNetV3的网络搭建中我也会和V2的网络一样,先介绍相关模块最后是总体网络搭建。

Bneck的搭建

在搭建Bneck之前我们还是先定义一种卷积规则,但是这个和MobileNetV2不同的是我们不确定激活函数的使用,所以比之前略显复杂一点点,下面直接上代码:

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channnels,output_channels,kernal_size,
                 stride = 1,groups = 1,activition = None):
        super(conv,self).__init__()

        padding = kernal_size // 2
        self.conv1 = torch.nn.Conv1d(input_channnels,output_channels,
                                     kernal_size,stride,padding,groups = groups)
        self.BN = torch.nn.BatchNorm1d(output_channels)
        self.activate = activition
        if self.activate == 'ReLu':
            self.activate = torch.nn.ReLU6()
        elif self.activate == 'H-swish':
            self.activate = torch.nn.Hardswish()

    def forward(self,x):
        x = self.conv1(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        if self.activate != None:
            x = self.activate(x)
        return x

在这个代码中我们同样是使用卷积核来定义每个卷积的填充,并且每个卷积后面都会跟着一个BN层,BN层的执行条件和MobileNetV2一样,也是判定最后一个维度是否不为1,但是由于每层激活函数不一样,所以在激活函数的使用上也是加了个判定条件。

下面就是Bneck模块的创建:

class Bneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,kernal_size,
                 expansion,stride = 1,SE = False,activition = 'ReLu'):
        super(Bneck,self).__init__()
        self.stride = stride
        self.in_channels = in_channels
        self.out_outchannels = out_channels
        self.SE = SE
        self.activition = activition
        self.conv1 = conv(in_channels,expansion,1,stride,activition=activition)
        self.conv2 = conv(expansion,expansion,kernal_size,stride,
                          groups=expansion,activition=activition)
        if self.SE == True:
            self.attention = SE_block(expansion)
        self.conv3 = conv(expansion,out_channels,1,activition=activition)

    def forward(self,x):
        x1 = self.conv1(x)
        x1 = self.conv2(x1)
        if self.SE == True:
            x1 = self.attention(x1)
        x1 = self.conv3(x1)

        if self.stride == 1 and self.in_channels == self.out_outchannels:
            x1 += x
        return x1

Bneck模块中首先需要注意的一点是就是加入了一个SE_block,这个是在后文定义的一个注意力机制,同样模块的结构图在上节也有过讲解,这里就不在多说了,其他和MobileNetV2不同的地方就是激活函数的使用,在这里也要使用条件语句判定使用哪个激活函数。

下面是SE_block注意力机制模块代码:

class SE_block(torch.nn.Module):
    def __init__(self,inchannels,ratio = 1):
        super(SE_block,self).__init__()
        self.pool = torch.nn.AdaptiveAvgPool1d(1)
        self.conv4 = torch.nn.Sequential(
            torch.nn.Linear(inchannels,inchannels // ratio),
            torch.nn.ReLU(inplace=True),
            torch.nn.Linear(inchannels // ratio,inchannels),
            torch.nn.Hardsigmoid(inplace=True)
        )

    def forward(self,input):
        b,c,_ = input.shape
        x = self.pool(input)
        x = x.view([b,c])
        x = self.conv4(x)
        x = x.view([b,c,1])
        return x*input

SE模块的本质是一个通过对卷积网络的特征通道进行动态权重调整,从而增强网络表示能力的机制。 其中初始化中的ratio为压缩比率。

在前向传播中,首先是根据传入数据的形状获得其batch(批次大小)和channel(通道数),然后经过自适应平均池化,通过前连接层后重新调整形状,使权重的形状与输入特征图相匹配。最后就是将得到的权重与原始输入特征图相乘,实现对特征图的通道进行动态重标定,增强模型对重要特征的关注度,抑制不重要的特征,从而提升网络的性能。

MobileNetV3的搭建

首先我们来看一下原文中给的MobileNetV3的搭建图:

在这里插入图片描述

第一列还是输入数据,第二列网络层的类别,第三列是扩充后的通道大小,第四列输出通道数大小,第五列是否使用注意力机制,第六列使用什么激活函数,其中RE为ReLu6,HS为Hard-Swish,最后一列则是步长大小。最后几个分类层这是精简后的Efficient Last Stage。

下面是代码实现:

class MobileNetV3_small(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super().__init__()
        self.classes = classes
        self.feature = torch.nn.Sequential(
            conv(inputchannels,16,3,2,activition='H-swish'),
            Bneck(16,16,3,16,2,True,'ReLu'),
            Bneck(16,24,3,72,2,False,'ReLu'),
            Bneck(24,24,3,88,1,False,'ReLu'),
            Bneck(24,40,5,96,2,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,48,5,120,1,True,'H-swish'),
            Bneck(48,48,5,144,1,True,'H-swish'),
            Bneck(48,96,5,288,2,True,'H-swish'),
            Bneck(96,96,5,576,1,True,'H-swish'),
            Bneck(96, 96, 5, 576, 1, True, 'H-swish'),
            conv(96,576,1,1,activition='H-swish'),
            torch.nn.AdaptiveAvgPool1d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(576,1024,1,1,1,'H-swish'),
            conv(1024,output_channels=self.classes,
                 kernal_size=1,stride=1,groups=1,activition='H-swish'),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x

这里也是贯彻了一直以来的传统就是,将输入通道和分的类别最为一个传入供使用者传入。其他方面全都按照论文结构里给的搭建。

下面我们继续MobileNetV3-Large的搭建:

class MobileNetV3_large(torch.nn.Module):
    def __init__(self,inchannels,classes):
        super(MobileNetV3_large,self).__init__()
        self.classes = classes
        self.features = torch.nn.Sequential(
            conv(inchannels,16,3,2,1,'H-swish'),
            Bneck(16,16,3,16,1,False,'ReLu'),
            Bneck(16,24,3,64,2,False,'ReLu'),
            Bneck(24,24,3,72,1,False,'ReLu'),
            Bneck(24,40,5,72,2,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,80,3,240,2,False,'H-swish'),
            Bneck(80,80,3,200,1,False,'H-swish'),
            Bneck(80,80,3,184,1,False,'H-swish'),
            Bneck(80, 80, 3, 184, 1, False, 'H-swish'),
            Bneck(80,112,3,480,1,True,'H-swish'),
            Bneck(112,112,3,672,1,True,'H-swish'),
            Bneck(112,160,5,672,2,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            conv(160,960,1,1,1,'H-swish'),
            torch.nn.AdaptiveAvgPool1d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(960,1280,1,1,1,'H-swish'),
            conv(1280,output_channels=self.classes,kernal_size=1,stride=1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.features(x)
        x = self.classify(x)
        return x

相较于MobileNetV3-Small,这段的代码确实显得略长,但也没什么很难的地方,都是按照论文里给的图一点点搭建得来,最后看下总体代码:

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channnels,output_channels,kernal_size,
                 stride = 1,groups = 1,activition = None):
        super(conv,self).__init__()

        padding = kernal_size // 2
        self.conv1 = torch.nn.Conv1d(input_channnels,output_channels,
                                     kernal_size,stride,padding,groups = groups)
        self.BN = torch.nn.BatchNorm1d(output_channels)
        self.activate = activition
        if self.activate == 'ReLu':
            self.activate = torch.nn.ReLU6()
        elif self.activate == 'H-swish':
            self.activate = torch.nn.Hardswish()

    def forward(self,x):
        x = self.conv1(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        if self.activate != None:
            x = self.activate(x)
        return x

class Bneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,kernal_size,
                 expansion,stride = 1,SE = False,activition = 'ReLu'):
        super(Bneck,self).__init__()
        self.stride = stride
        self.in_channels = in_channels
        self.out_outchannels = out_channels
        self.SE = SE
        self.activition = activition
        self.conv1 = conv(in_channels,expansion,1,stride,activition=activition)
        self.conv2 = conv(expansion,expansion,kernal_size,stride,
                          groups=expansion,activition=activition)
        if self.SE == True:
            self.attention = SE_block(expansion)
        self.conv3 = conv(expansion,out_channels,1,activition=activition)

    def forward(self,x):
        x1 = self.conv1(x)
        x1 = self.conv2(x1)
        if self.SE == True:
            x1 = self.attention(x1)
        x1 = self.conv3(x1)

        if self.stride == 1 and self.in_channels == self.out_outchannels:
            x1 += x
        return x1

class SE_block(torch.nn.Module):
    def __init__(self,inchannels,ratio = 1):
        super(SE_block,self).__init__()
        self.pool = torch.nn.AdaptiveAvgPool1d(1)
        self.conv4 = torch.nn.Sequential(
            torch.nn.Linear(inchannels,inchannels // ratio),
            torch.nn.ReLU(inplace=True),
            torch.nn.Linear(inchannels // ratio,inchannels),
            torch.nn.Hardsigmoid(inplace=True)
        )

    def forward(self,input):
        b,c,_ = input.shape
        x = self.pool(input)
        x = x.view([b,c])
        x = self.conv4(x)
        x = x.view([b,c,1])
        return x*input

class MobileNetV3_small(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super().__init__()
        self.classes = classes
        self.feature = torch.nn.Sequential(
            conv(inputchannels,16,3,2,activition='H-swish'),
            Bneck(16,16,3,16,2,True,'ReLu'),
            Bneck(16,24,3,72,2,False,'ReLu'),
            Bneck(24,24,3,88,1,False,'ReLu'),
            Bneck(24,40,5,96,2,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,48,5,120,1,True,'H-swish'),
            Bneck(48,48,5,144,1,True,'H-swish'),
            Bneck(48,96,5,288,2,True,'H-swish'),
            Bneck(96,96,5,576,1,True,'H-swish'),
            Bneck(96, 96, 5, 576, 1, True, 'H-swish'),
            conv(96,576,1,1,activition='H-swish'),
            torch.nn.AdaptiveAvgPool1d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(576,1024,1,1,1,'H-swish'),
            conv(1024,output_channels=self.classes,
                 kernal_size=1,stride=1,groups=1,activition='H-swish'),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x


class MobileNetV3_large(torch.nn.Module):
    def __init__(self,inchannels,classes):
        super(MobileNetV3_large,self).__init__()
        self.classes = classes
        self.features = torch.nn.Sequential(
            conv(inchannels,16,3,2,1,'H-swish'),
            Bneck(16,16,3,16,1,False,'ReLu'),
            Bneck(16,24,3,64,2,False,'ReLu'),
            Bneck(24,24,3,72,1,False,'ReLu'),
            Bneck(24,40,5,72,2,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,80,3,240,2,False,'H-swish'),
            Bneck(80,80,3,200,1,False,'H-swish'),
            Bneck(80,80,3,184,1,False,'H-swish'),
            Bneck(80, 80, 3, 184, 1, False, 'H-swish'),
            Bneck(80,112,3,480,1,True,'H-swish'),
            Bneck(112,112,3,672,1,True,'H-swish'),
            Bneck(112,160,5,672,2,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            conv(160,960,1,1,1,'H-swish'),
            torch.nn.AdaptiveAvgPool1d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(960,1280,1,1,1,'H-swish'),
            conv(1280,output_channels=self.classes,kernal_size=1,stride=1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.features(x)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    x = torch.randn((1,112,224))
    model = MobileNetV3_large(112,10)
    # model = MobileNetV3_small(112,10)
    y = model(x)
    print(y.shape)

二维MobileNetV3的搭建

在二维MobileNetV3中,除了需要对各网络层进行更改外,最大的改动就是注意力机制的地方,因为涉及到采集图形形状并处理,下面是详细代码:

import torch

class conv(torch.nn.Module):
    def __init__(self,input_channnels,output_channels,kernal_size,
                 stride = 1,groups = 1,activition = None):
        super(conv,self).__init__()

        padding = kernal_size // 2
        self.conv1 = torch.nn.Conv2d(input_channnels,output_channels,
                                     kernal_size,stride,padding,groups = groups)
        self.BN = torch.nn.BatchNorm2d(output_channels)
        self.activate = activition
        if self.activate == 'ReLu':
            self.activate = torch.nn.ReLU6()
        elif self.activate == 'H-swish':
            self.activate = torch.nn.Hardswish()

    def forward(self,x):
        x = self.conv1(x)
        if x.size()[-1] != 1:
            x = self.BN(x)
        if self.activate != None:
            x = self.activate(x)
        return x

class Bneck(torch.nn.Module):
    def __init__(self,in_channels,out_channels,kernal_size,
                 expansion,stride = 1,SE = False,activition = 'ReLu'):
        super(Bneck,self).__init__()
        self.stride = stride
        self.in_channels = in_channels
        self.out_outchannels = out_channels
        self.SE = SE
        self.activition = activition
        self.conv1 = conv(in_channels,expansion,1,stride,activition=activition)
        self.conv2 = conv(expansion,expansion,kernal_size,stride,
                          groups=expansion,activition=activition)
        if self.SE == True:
            self.attention = SE_block(expansion)
        self.conv3 = conv(expansion,out_channels,1,activition=activition)

    def forward(self,x):
        x1 = self.conv1(x)
        x1 = self.conv2(x1)
        if self.SE == True:
            x1 = self.attention(x1)
        x1 = self.conv3(x1)

        if self.stride == 1 and self.in_channels == self.out_outchannels:
            x1 += x
        return x1

class SE_block(torch.nn.Module):
    def __init__(self,inchannels,ratio = 1):
        super(SE_block,self).__init__()
        self.pool = torch.nn.AdaptiveAvgPool2d(1)
        self.conv4 = torch.nn.Sequential(
            torch.nn.Linear(inchannels,inchannels // ratio),
            torch.nn.ReLU(inplace=True),
            torch.nn.Linear(inchannels // ratio,inchannels),
            torch.nn.Hardsigmoid(inplace=True)
        )

    def forward(self,input):
        b,c,_,_ = input.shape
        x = self.pool(input)
        x = x.view(b,c)
        x = self.conv4(x)
        x = x.view([b,c,1,1])
        return x*input

class MobileNetV3_small(torch.nn.Module):
    def __init__(self,inputchannels,classes):
        super().__init__()
        self.classes = classes
        self.feature = torch.nn.Sequential(
            conv(inputchannels,16,3,2,activition='H-swish'),
            Bneck(16,16,3,16,2,True,'ReLu'),
            Bneck(16,24,3,72,2,False,'ReLu'),
            Bneck(24,24,3,88,1,False,'ReLu'),
            Bneck(24,40,5,96,2,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,40,5,240,1,True,'H-swish'),
            Bneck(40,48,5,120,1,True,'H-swish'),
            Bneck(48,48,5,144,1,True,'H-swish'),
            Bneck(48,96,5,288,2,True,'H-swish'),
            Bneck(96,96,5,576,1,True,'H-swish'),
            Bneck(96, 96, 5, 576, 1, True, 'H-swish'),
            conv(96,576,1,1,activition='H-swish'),
            torch.nn.AdaptiveAvgPool2d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(576,1024,1,1,1,'H-swish'),
            conv(1024,output_channels=self.classes,
                 kernal_size=1,stride=1,groups=1,activition='H-swish'),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.feature(x)
        x = self.classify(x)
        return x


class MobileNetV3_large(torch.nn.Module):
    def __init__(self,inchannels,classes):
        super(MobileNetV3_large,self).__init__()
        self.classes = classes
        self.features = torch.nn.Sequential(
            conv(inchannels,16,3,2,1,'H-swish'),
            Bneck(16,16,3,16,1,False,'ReLu'),
            Bneck(16,24,3,64,2,False,'ReLu'),
            Bneck(24,24,3,72,1,False,'ReLu'),
            Bneck(24,40,5,72,2,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,40,5,120,1,True,'ReLu'),
            Bneck(40,80,3,240,2,False,'H-swish'),
            Bneck(80,80,3,200,1,False,'H-swish'),
            Bneck(80,80,3,184,1,False,'H-swish'),
            Bneck(80, 80, 3, 184, 1, False, 'H-swish'),
            Bneck(80,112,3,480,1,True,'H-swish'),
            Bneck(112,112,3,672,1,True,'H-swish'),
            Bneck(112,160,5,672,2,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            Bneck(160,160,5,960,1,True,'H-swish'),
            conv(160,960,1,1,1,'H-swish'),
            torch.nn.AdaptiveAvgPool2d(1)
        )
        self.classify = torch.nn.Sequential(
            conv(960,1280,1,1,1,'H-swish'),
            conv(1280,output_channels=self.classes,kernal_size=1,stride=1),
            torch.nn.Flatten()
        )

    def forward(self,x):
        x = self.features(x)
        x = self.classify(x)
        return x

if __name__ == '__main__':
    x = torch.randn((1,3,224,224))
    model = MobileNetV3_large(3,10)
    # model = MobileNetV3_small(112,10)
    y = model(x)
    print(y.shape)

总结

写的可能有点多,但是也确实把学习过程中遇到的各种点都选择重要的记录下来了,本来还是想加上生成虚拟数据集、调用模型、初始化实例化、最后评估模型的代码的,现在觉得可能有点过分多了,决定贴一部分泛用的训练模版(适用各种网络)的图,大家需要训练模版的可以到学长 @浩浩的科研笔记Pytroch 自写训练模板适合入门版 包含十五种经典的自己复现的一维模型 1D CNN自取,并且里面还包含了1D-CNN的各种经典模型复现。

在这里插入图片描述

另外就是在MobileNet系列的网络的创新点中也有很多是关于激活函数的描述,这篇博客中并没有对这方面进行过多赘述,如果想了解更多激活函数可以到学长 @浩浩的科研笔记 常用的激活函数python代码-简洁版-想画好看的激活函数图的第一步里面对于各种激活函数的描述已经很详尽了。

最后就是本博客一部分图来自B站UP主 @ 霹雳吧啦Wz (感谢)。

写完看了一眼字数加上代码也有一万多了,感觉手都要敲麻了。先就这样吧,如果有哪里不太好的地方还是很欢迎大家指出。但因为目前这个博客里全都是基于一维信号的实现,下次更新时候我会把处理二维信号的代码加上。

更新

2024.3.18 更新了MobileNet二维模型训练代码及详解,并且均使用3通道224×224的图片作为输出进行了实验。

  • 25
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
MobileNet 是一种轻量级的深度学习模型,专为移动设备和嵌入式系统设计,以减少计算资源和内存占用,同时保持较高的性能。它是 Google 在 2017 年 ICLR 大会上提出的,由 Inception 模型发展而来,但采用了深度可分离卷积(Depthwise Separable Convolution)来大幅度减少参数数量。 在 Keras 中,你可以使用 `tf.keras.applications.MobileNet` 或 `keras.applications.mobilenet_v2.MobileNetV2` 来导入预训练的 MobileNet 模型。这个模型通常包括以下几个部分: 1. **输入层**:接受图像数据作为输入。 2. **卷积层**:包括深度可分离卷积层,它们分别对空间维度和通道维度进行操作,大大减少了参数数量。 3. **瓶颈层**:使用扩张路径(Expanded Path),包含一个深度可分离卷积后接一个1x1卷积来增加通道数。 4. **全局平均池化**(Global Average Pooling):代替全连接层,减少过拟合并使网络更易于部署。 5. **分类层**:如 `tf.keras.layers.Dense`,用于输出分类结果。 如果你想要在 Keras 中使用 MobileNet,可以直接加载预训练权重,然后可以选择冻结部分层进行微调,或者从头开始训练。以下是使用 Keras 导入 MobileNet 的基本步骤: ```python from tensorflow.keras.applications import MobileNet from tensorflow.keras.models import Model from tensorflow.keras.layers import Dense, GlobalAveragePooling2D # 加载预训练模型 base_model = MobileNet(weights='imagenet', include_top=False, input_shape=(img_height, img_width, 3)) # 添加全局平均池化和全连接层进行分类任务 x = base_model.output x = GlobalAveragePooling2D()(x) predictions = Dense(num_classes, activation='softmax')(x) # 创建新的模型 model = Model(inputs=base_model.input, outputs=predictions) # 选择是否训练或冻结预训练层 if fine_tuning: # 冻结所有层 for layer in base_model.layers: layer.trainable = False # 再定义几个顶部的层进行微调 num_frozen_layers = len(base_model.layers) - num_top_layers_to_freeze for layer in model.layers[:num_frozen_layers]: layer.trainable = False else: # 训练整个模型 model.trainable = True ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值