图像分类篇——使用pytorch搭建MobileNet网络


本文为学习记录和备忘录,对代码进行了详细注释,以供学习。
内容来源:
★github: https://github.com/WZMIAOMIAO/deep-learning-for-image-processing

★b站:https://space.bilibili.com/18161609/channel/index

★CSDN:https://blog.csdn.net/qq_37541097


1. MobileNet网络详解

1.1 MobileNet(v1)

1.1.1 MoblieNet(v1)网络概述

MobileNet网络是由google团队在2017年提出的,专注于移动端或者嵌入式设备中的轻量级CNN网络。相比传统卷积神经网络,在准确率小幅降低的前提下大大减少模型参数与运算量。(相比VGG16准确率减少了0.9%,但模型参数只有VGG的1/32)

研究动机:传统卷积神经网络, 内存需求大、 运算量大,导致无法在移动设备以及嵌入式设备上运行

论文全称:MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

论文链接:MobileNet(v1):MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications

网络中的亮点:

  • Depthwise Convolution(简称DW卷积,大大减少运算量和参数数量)
  • 增加超参数α、β

1.1.2 DW卷积(Depthwise Convolution)

传统卷积:
传统卷积其中,卷积核channel=输入特征矩阵channel,输出特征矩阵channel = # filters。如上图,输入特征矩阵channel=3,则卷积核channel=3。共有4个filters,则输出特征矩阵channel=4。
DW卷积(Depthwise Convolution):
DW卷积
DW卷积中,每个卷积核的channel都为1,每个卷积核只负责与输入特征矩阵中的1个channel进行卷积运算,然后再得到相应的输出特征矩阵中的1个channel。则,所有卷积核的channel都等于1,且输入特征矩阵channel=# filters(即卷积核个数)=输出特征矩阵channel。
PW卷积(Pointwise Conv):
PW卷积
PW卷积和普通卷积一样,特殊在于卷积核大小为1。
深度可分卷积(Depthwise Separable Conv):
深度可分卷积
由两部分组成:DW和PW。理论上普通卷积计算量是DW+PW的8到9倍。

1.1.3 MobileNet(v1)网络详细参数

MobileNet(v1)网络详细参数
上表第1行中Conv/s2表示普通卷积且步距为2,filter shape为3×3×3×32表示卷积核height=3,width=3,channel=3(rgb图片),#filters=32。
第2行Conv dw/s1表示采用DW卷积操作,且步距为1。由于DW卷积的卷积核深度为1,则filter shape为3×3×32 dw表示卷积核height=3,width=3,#filters=32。其中channel=1.
注:MoblieNet相比于GoogLeNet、VGG准确率只降低一点点,但是模型参数大概只有VGG网络的1/32。超参数α指卷积核个数的倍率,控制卷积过程中所采用的卷积核的个数。β指输入图像尺寸。


1.2 MobileNet(v2)

1.2.1 MoblieNet(v2)网络概述

MobileNet v2网络是由google团队在2018年提出的,相比MobileNet V1网络,准确率更高,模型更小。

论文全称:MobileNetV2: Inverted Residuals and Linear Bottlenecks

论文链接:MobileNet(v2):MobileNetV2: Inverted Residuals and Linear Bottlenecks

网络中的亮点:

  • Inverted Residuals(倒残差结构)
  • Linear Bottlenecks

1.2.2 Inverted Residuals(倒残差结构)

(1)残差结构与倒残差结构对比如下:残差结构与倒残差结构对比
原始的残差结构先通过1×1卷积降维,然后经过3×3卷积,最后再经过1×1卷积升维
而倒残差结构先通过1×1卷积升维,然后经过3×3卷积,最后再经过1×1卷积降维。与原始残差结构正好相反。
另外,普通的残差结构中采用的激活函数是relu激活函数,而在倒残差结构中采用的激活函数是relu6激活函数
y = ReLU6 (x) = min (max (x, 0) , 6)
relu6
(2)Linear Bottlenecks
倒残差结构中最后一个1×1的卷积层,它使用了线性的激活函数而不是relu激活函数。因为relu函数对低维特征信息会产生大量损失。而在倒残差结构中最后经过1×1卷积降维,是一个低维特征向量,因此要用线性激活函数代替relu激活函数来避免信息的损失。
(3)原论文中倒残差结构的结构图为:
倒残差结构的结构图
倒残差结构中的层信息为:
倒残差结构中的层信息
由上图,倒残差结构第1层为普通的卷积层,卷积核大小为1×1,激活函数为ReLU6,(1×1卷积升维),所采用的卷积核个数(# filters)为tk(t为倍率因子,用来扩大深度)。
第2层为DW卷积,卷积核大小为3,步距s为传入参数,使用ReLU6激活函数,输出特征矩阵深度与输入特征矩阵深度相同,为tk。但是高和宽缩减为1/s倍
第3层为普通1×1卷积层,这里需要注意的是,激活函数使用的是线性激活函数卷积核个数为k’(人为指定)。
需要注意的是:当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut连接。(和图中表示的稍有不同)

1.2.3MobileNet(v2)网络详细参数

MobileNetV2网络详细参数

  • 要点1:t是扩展因子,对应表格中第1层1×1卷积层的扩展倍率h×w×(tk);
    c是输出特征矩阵深度channel,对应表格中的k’;
    n是bottleneck的重复次数;
    s是步距(针对第一层,其他为1),s只代表每一个block的第1层的bottleneck的步距,一个block由一系列bottleneck组成。如第3行n=2,s=2,bottleneck重复2次,第1层的bottleneck的步距为2,而第2层的bottleneck的步距仍为1。
  • 要点2
    上面说过,当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut连接。
    以这个例子为例:
    shortcut举例
    有3层bottleneck,对于第1层bottleneck,步距s=1,但是输入特征矩阵深度为64,输出特征矩阵深度为96,两者shape不相等,故在第1层bottleneck中不存在short cut分支。而对于第2层、第3层bottleneck而言,输入特征矩阵和输出特征矩阵深度都等于96,且满足步距s=1的条件,因此在后2层bottleneck中存在short cut分支。
  • 要点3:最后一层为卷积层,但其输入特征矩阵为1×1×1280,相当于是一维向量,因此卷积的效果和全连接层相同。这里的输出矩阵深度为k,代表的就是分类的类别个数。

1.3 MobileNet(v3)

1.3.1 MoblieNet(v3)网络概述

在很多轻量级的网络中,MobileNet(v3)经常被使用到。MobileNet(v3)是Google在继MobileNet(v2)后提出的v3版本。

论文全称:Searching for MobileNetV3

论文链接:MobileNet(v3):Searching for MobileNetV3

网络中的亮点:

  • 更新Block(bneck)
  • 使用NAS搜索参数(Neural Architecture Search)
  • 重新设计耗时层结构

1.3.2 更新Block(bneck)

MobileNet(v2)中倒残差结构如下所示:
v2残差结构
其中,需要注意的是:当stride=1且 输入特征矩阵与输出特征矩阵shape 相同时才有shortcut连接。

在MobileNet(v3)中,更新了Block,其中主要体现在1.加入了SE模块(注意力机制)。2.更新了激活函数
其结构如下:
v3block结构

  • 要点1SE模块,即注意力机制(上图中红色框部分)。对得到的特征矩阵,对其每一个channel进行池化处理,那么特征矩阵的channel为多少, 得到的一维向量就有多少个元素。接下来通过两个全连接层得到一个输出向量。
  • 要点2:对于第1个全连接层,它的节点个数等于特征矩阵channel数的1/4,第2个全连接层的节点个数与特征矩阵的channel数相同。则经过两个全连接层的输出向量可以理解为对特征矩阵的每1个channel分析出了一个权重关系(比较重要的channel赋予一个大权重,不太重要的channel赋予小权重)。则得到的输出向量中每一个元素即为针对每一个channel的权重,将每一个channel中的数据与相应权重相乘,即可得到新的特征矩阵。(输出特征矩阵channel与输入特征矩阵channel相同)
  • 要点3:第1个全连接层的激活函数是Relu,第2个全连接层的激活函数是Hard-sigmoid
  • 要点4:图中NL指非线性激活函数,因为每一个层中使用的激活函数类型不同,这里统一以NL指代。
  • 要点5:最后1×1卷积降维层没有使用激活函数。(也可以说使用了线性激活y=x)
    SE注意力机制过程可由下例展示:
    注意力机制举例

1.3.3 重新设计耗时层结构

主要改变如下:
1.减少第一个卷积层的卷积核个数 (32->16)
在MobileNet v1,v2中,第一个卷积层的卷积核个数(即#filters or c)都是32,论文作者研究发现,将卷积核个数变为16个后,准确率和32个差不多,但是可以节省2ms的时间。
2.精简Last Stage
精简last stage

1.3.4 重新设计激活函数

在MobileNet(v2)中,常用relu6激活函数。 ReLU6 (x) = min (max (x, 0) , 6)
现在介绍一种新的激活函数:swish (x) = x × σ(x),其中σ(x)=1/(1+e(-x)),但是这种激活函数计算、求导复杂,对量化过程不友好。因此作者提出了h-swish激活函数
在这之前介绍一下h-sigmoid激活函数h-sigmoid=ReLu(x+3)/6
定义h-swish函数为:
h-swish[x]=x×h-sigmoid=xReLu(x+3)/6.
作者在文中提到,将sigmoid激活函数替换为h-sigmoid激活函数,将swish激活函数替换为h-swish激活函数,对网络的推理过程有帮助,且对量化过程友好。

1.3.5 MobileNet(v3)网络结构及详细参数

(1)MobileNet(v3)-Large
v3large
input表示当前层输入特征矩阵的shape,比如表中使用RGB彩色图片,它的高和宽都是244;
Operator表示相应的操作,其中①bneck表示V3中更新后的block,②其后紧跟的3×3表示DW卷积的卷积核大小③最后两层NBN表示不使用BN层;
exp size表示bneck结构中,第1个1×1升维卷积层要将输入特征矩阵升到的维度,即exp size给定多少,就将输入特征矩阵升到多少维;
#out表示输出特征矩阵的channel,上文强调过,为了减少耗时,第1层卷积层中使用的卷积核个数为16;
SE表示是否使用了SE注意力机制;
NL表示非线性激活函数,其中HS表示h-swish激活函数,RE表示使用relu激活函数;
s表示DW卷积的步距。
以下几点需注意:

  • 第1个1×1升维卷积层根据exp size给定值的大小将输入特征矩阵升维至指定channel,然后DW卷积层不会改变channel大小SE操作同样不改变channel大小,最后根据#out的给定值,通过1×1降维卷积层输出指定channel的特征矩阵。
  • 在第1个bneck结构中,即详细参数的第2行。其输入特征矩阵channel为16,升维维度也为16,则在第1个bneck结构中,没有进行1×1升维卷积层操作,同时这层也没有SE结构。则直接对输入特征矩阵进行DW操作,然后直接通过1×1卷积降温处理得到输出特征矩阵。
  • 与MoblieNetv2相似,当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut连接

(2)MobileNet(v3)-Small
MobileNet(v3)-Small与MobileNet(v3)-Large类似,详细参数如下:
v3small


2. Pytorch搭建

2.1 MobileNet(v2)

2.1.1 model.py

首先定义Conv+BN+ReLU这样的组合层,在MobileNet中所有的卷积层,包括DW卷积操作,基本上都是有卷积conv+BN+ReLU6激活函数共同组成,唯一不同的是在倒残差结构的第3层,使用1×1的普通卷积,将其进行降维处理时,使用的是线性激活函数

  • 要点1:始化函数传入参数groups:groups如果设置为1,则为普通卷积。如果groups设置为in_channel,则为DW卷积(pytroch中DW卷积也调用nn.Conv2d来实现)。

接下来定义倒残差结构,def InvertedResidual(nn.Module):
倒残差结构层信息

  • 要点1:由上图,倒残差结构第1层为普通的卷积层,卷积核大小为1×1,激活函数为ReLU6,(1×1卷积升维),所采用的卷积核个数(# filters)为tk(t为倍率因子,用来扩大深度)。
    第2层为DW卷积,卷积核大小为3,步距s为传入参数,使用ReLU6激活函数,输出特征矩阵深度与输入特征矩阵深度相同,为tk。但是高和宽缩减为1/s倍。
    第3层为普通1×1卷积层,这里需要注意的是,激活函数使用的是线性激活函数,卷积核个数为k’(人为指定)。
    且当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut分支。
  • 要点2:当倍率因子t=1时(对应详细参数表第2行),那么倒残差结构第1层1×1升维卷积层输出特征矩阵channel等于输入特征矩阵channel,即第1层1×1卷积层没有起作用,此时,舍去第1层1×1卷积层。当倍率因子t != 1时,不存在上述情况。

最后定义MobileNet(v2)网络结构,初始化函数中传入参数num_classes,即分类的类别个数。α是超参数,在v1中提到,控制卷积层所使用卷积核个数的倍率,round_nearest为基数,在定义的_make_divisible函数中起作用,_make_divisible的作用是将输入值调整为最接近基数值整数倍的数值。

input_channel = _make_divisible(32 * alpha, round_nearest)  # _make_divisible将输入的卷积核个数调整为round_nearest的整数倍

input_channel = _make_divisible(32 * alpha, round_nearest)
即将32×alpha调整为最接近8的整数倍的数值(这里round_nearest值为8)。
模型部分全部代码如下:

from torch import nn
import torch


def _make_divisible(ch, divisor=8, min_ch=None):  # ch指输入特征深度,divisor指基数
    # 此函数的作用时讲ch调整为指定divisor这个数的整数倍,将ch调整为离8最近的整数倍的数值
    """
    This function is taken from the original tf repo.
    It ensures that all layers have a channel number that is divisible by 8
    It can be seen here:
    https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
    """
    if min_ch is None:
        min_ch = divisor
    new_ch = max(min_ch, int(ch + divisor / 2) // divisor * divisor)
    # Make sure that round down does not go down by more than 10%.
    if new_ch < 0.9 * ch:
        new_ch += divisor
    return new_ch


# 首先定义一个Conv+BN+ReLU这样的组合层,在MobileNet中所有的卷积层,包括DW卷积操作,基本上都是有卷积+BN+ReLU6激活函数共同组成。
class ConvBNReLU(nn.Sequential):  # 继承来自于nn.Sequential,而不是nn.Module。与pytorch官方样例保持一致。
    def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
        # groups如果设置为1,则为普通卷积。如果groups设置为in_channel,则为DW卷积(pytroch中DW卷积也调用nn.Conv2d来实现)
        padding = (kernel_size - 1) // 2  # padding根据kernel_size来计算
        super(ConvBNReLU, self).__init__(  # 在super.__init__()中传入这3个层结构
            nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
            # kernel_size默认3,stride默认1,padding计算得到,groups默认等于1,bias不使用(因为下面有BN层)
            nn.BatchNorm2d(out_channel),  # BN层输入特征矩阵深度为out_channel
            nn.ReLU6(inplace=True)
        )


class InvertedResidual(nn.Module):
    def __init__(self, in_channel, out_channel, stride, expand_ratio):
        # expand_ratio为倍率因子,用来扩大深度
        super(InvertedResidual, self).__init__()
        hidden_channel = in_channel * expand_ratio  # hidden_channel为第1层卷积层卷积核个数,即tk
        self.use_shortcut = stride == 1 and in_channel == out_channel  # 定义1个布尔变量判断是否使用short cut分支

        layers = []
        if expand_ratio != 1:  # 如果倍率因子=1,只有参数表第2行情况,这时不需要残差结构中第1层1×1卷积层
            # 1x1 pointwise conv
            layers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size=1))  # 第1层:1×1卷积
        layers.extend([  # 通过extend函数添加一系列层结构,与append功能相同,但extend能一次性批量插入很多元素
            # 3x3 depthwise conv
            # 第2层:DW卷积。输入c与输出c相同,都是hidden_channel.groups=hidden_channel控制着DW卷积区别于普通卷积。
            ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel),
            # 1x1 pointwise conv(linear)
            # 注意这里是线性激活函数,就不可以用刚才定义的ConvBNReLU()函数,这里用Conv2d。
            nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channel),
            # 线性激活函数y=x,也就是不做处理。则不添加激活函数就相当于是线性激活函数。
        ])

        self.conv = nn.Sequential(*layers)

    def forward(self, x):
        if self.use_shortcut:  # 判断是否满足short cut分支连接条件
            return x + self.conv(x)  # 如果满足shortcut条件,返回shortcut分支结果与主分支结果的和
        else:
            return self.conv(x)  # 如果不满足shortcut条件,只返回主分支结果


class MobileNetV2(nn.Module):
    def __init__(self, num_classes=1000, alpha=1.0, round_nearest=8):
        # num_classes分类的类别个数.α超参数,控制卷积层所使用卷积核个数的倍率,round_nearest为基数,在下面_make_divisible函数中
        super(MobileNetV2, self).__init__()
        block = InvertedResidual  # 将上面定义的InvertedResidual类传给block
        input_channel = _make_divisible(32 * alpha, round_nearest)  # _make_divisible将输入的卷积核个数调整为round_nearest的整数倍
        # input_channel表示表格中第1行Conv2d卷积层所使用的卷积核的个数,也等于下一层输入特征矩阵的深度
        last_channel = _make_divisible(1280 * alpha, round_nearest)
        # last_channel表示表格中倒数第3行1×1卷积层的卷积核个数

        # 创建1个list列表,list列表中每一个元素就是表格中bottleneck对应每一行的参数t,c,n,s
        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],
        ]

        features = []
        # conv1 layer
        features.
  • 13
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
使用PyTorch提供的MobileNet模型实现图像分类的步骤如下: 1. 导入必要的库 ```python import torch import torchvision from torchvision import transforms ``` 2. 加载MobileNet模型 ```python model = torchvision.models.mobilenet_v2(pretrained=True) ``` 这里使用PyTorch提供的预训练的MobileNet_v2模型,可以根据需要选择其他的预训练模型。 3. 对输入图像进行预处理 ```python preprocess = transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) ``` 这里使用了一系列的transforms来对输入的图像进行预处理,包括将图像缩放到256x256大小、中心裁剪为224x224大小、将图像转换为Tensor格式、以及使用ImageNet数据集的均值和标准差对图像进行归一化。 4. 加载输入图像 ```python img = Image.open('test.jpg') ``` 这里使用了PIL库的Image模块来加载输入图像,可以根据实际情况选择其他的图像加载方式。 5. 对输入图像进行预处理 ```python img_tensor = preprocess(img) ``` 将输入图像转换为Tensor格式,并进行预处理。 6. 将输入图像送入模型中进行预测 ```python with torch.no_grad(): output = model(img_tensor.unsqueeze(0)) pred = output.argmax(dim=1) ``` 将Tensor格式的输入图像送入模型中进行预测,得到输出结果。这里使用了torch.no_grad()上下文管理器来关闭梯度计算,以减少内存占用。 7. 打印预测结果 ```python print('Predicted class:', pred.item()) ``` 打印出预测结果,即输入图像所属的类别。 以上是使用PyTorch提供的MobileNet模型实现图像分类的基本步骤。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值