经典轻量级神经网络(2)MobileNetV2及其在Fashion-MNIST数据集上的应用

经典轻量级神经网络(2)MobileNet V2及其在Fashion-MNIST数据集上的应用

1 MobileNet V2的简述

  1. MobileNet V2 创新性的提出了具有线性bottleneckInverted 残差块。
  2. 这种块特别适用于移动设备和嵌入式设备,因为它用到的张量都较小,因此减少了推断期间的内存需求。
  3. MobileNetV2 提供了一个非常高效的面向移动设备的模型,可以用作许多视觉识别任务的基础。
  4. 论文下载地址: https://arxiv.org/abs/1801.04381

1.1 回顾一下MobileNet V1

MobileNet V1核心思想是采用 深度可分离卷积 操作。在相同的权值参数数量的情况下,相较标准卷积操作,可以减少数倍的计算量,从而达到提升网络运算速度的目的。

在这里插入图片描述

  • 如上图,MobileNet V1首先利用3×3的深度可分离卷积提取特征,然后利用1×1的卷积来扩张通道。用这样的block堆叠起来的MobileNetV1既能较少不小的参数量、计算量,提高网络运算速度,又能的得到一个接近于标准卷积的还不错的结果,看起来是很美好的。

  • 但是,在实际使用的时候, 发现深度卷积部分的卷积核比较容易训废掉:训完之后发现深度卷积训出来的卷积核有不少是空的。

  • 作者认为,这是ReLU激活函数造成的,ReLU 的非线性导致信息的不可逆丢失。一直以来,人们认为与任务相关的信息是嵌入到feature map 中的一个低维子空间。因此feature map 事实上有一定的信息冗余。如果缩减feature map 的通道数量(相当于降维),则可以降低计算量。MobileNet V1 就是采用宽度乘子,从而在计算复杂度和准确率之间平衡。由于ReLU 非线性激活的存在,缩减输入 feature map 的通道数量可能会产生不良的影响。

在这里插入图片描述

1.2 MobileNet V2主要的创新点

1.2.1 ReLU 的非线性导致信息的不可逆丢失

下图就是对一个n维空间中的一个“东西”做ReLU运算,然后(利用T的逆矩阵T-1恢复)对比ReLU之后的结果与Input的结果相差有多大

  • 可以发现当n = 2、3时,与Input相比有很大一部分的信息已经丢失了。而当n = 15到30,还是有相当多的地方被保留了下来。

    • 也就是说,对低维度做ReLU运算,很容易造成信息的丢失。而在高维度进行ReLU运算的话,信息的丢失则会很少。
  • 这就解释了为什么深度卷积的卷积核有不少是空

  • 针对这个问题,可以这样解决:既然是ReLU导致的信息损耗,将ReLU替换成线性激活函数

在这里插入图片描述

1.2.2 bottleneck block

  • 虽然引入非线性会提升模型的表达能力,但是引入非线性会破坏太多信息,会引起准确率的下降。因此bootleneck 中使用线性是非常重要的。

  • 我们当然不能把所有的激活层都换成线性的,所以我们就把最后的那个ReLU6换成Linear。

  • 作者将这个部分称之为linear bottleneck

在这里插入图片描述

现在还有个问题是,深度卷积本身没有改变通道的能力,来的是多少通道输出就是多少通道。如果来的通道很少的话,DW深度卷积只能在低维度上工作,这样效果并不会很好,所以我们要【扩张】通道。既然我们已经知道PW逐点卷积也就是1×1卷积可以用来升维和降维,那就可以在DW深度卷积之前使用PW卷积进行升维(升维倍数为t,t=6),再在一个更高维的空间中进行卷积操作来提取特征。

在这里插入图片描述

也就是说,不管输入通道数是多少,经过第一个PW逐点卷积升维之后,深度卷积都是在相对的更高6倍维度上进行工作。

在这里插入图片描述

bottleneck block:输入feature map 首先经过线性 bottleneck 来扩张通道数,然后经过深度可分离卷积,最后通过线性bottleneck 来缩小通道数。

输入bootleneck 输出通道数与输入通道数的比例称作膨胀比。

  • 通常较小的网络使用略小的膨胀比效果更好,较大的网络使用略大的膨胀比效果更好。
  • 如果膨胀比小于 1 ,这就是一个典型的 resnet 残差块。

1.2.3 Inverted residuals

MobileNet V1很像是一个直筒型的VGG网络。我们想像Resnet一样复用我们的特征,所以我们引入了shortcut结构,这样V2的block就是如下图形式:

在这里插入图片描述

现在,我们再来比较一下ResNet和MobileNetV2:
在这里插入图片描述

可以发现,都采用了 1×1 -> 3 ×3 -> 1 × 1 的模式,以及都使用Shortcut结构。但是不同点呢:

  • ResNet 先降维 (0.25倍)、卷积、再升维。
  • MobileNetV2 则是 先升维 (6倍)、卷积、再降维。

刚好V2的block刚好与Resnet的block相反,作者将其命名为Inverted residuals。就是论文名中的Inverted residuals

在这里插入图片描述

1.3 两个版本block的对比及网络结构

1.3.1 两个版本block的对比

  • 左边是v1的block,没有Shortcut并且带最后的ReLU6。

  • 右边是v2的加入了1×1升维,引入Shortcut并且去掉了最后的ReLU,改为Linear。步长为1时,先进行1×1卷积升维,再进行深度卷积提取特征,再通过Linear的逐点卷积降维。将input与output相加,形成残差结构。步长为2时,因为input与output的尺寸不符,因此不添加shortcut结构,其余均一致。

  • 事实上旁路连接有两个插入的位置:在两个1x1 卷积的前后,或者在Dwise 卷积的前后。通过实验证明:在两个1x1 卷积的前后使用旁路连接的效果最好。

  • bottleneck block 可以看作是对信息的两阶段处理过程:

    • 阶段一:对输入feature map 进行降维,这一部分代表了信息的容量。
    • 阶段二:对信息进行非线性处理,这一部分代表了信息的表达。

    MobileNet v2 中这二者是独立的,而传统网络中这二者是相互纠缠的。

在这里插入图片描述

1.3.2 网络结构

MobileNet V2 的设计基于 MobileNet v1 ,其结构如下:

  • 卷积块+不断堆叠的倒置残差结构+卷积块+平均池化+卷积
  • 每一行代表一个或者一组相同结构的层,层的数量由 n 给定。
  • 相同结构指的是:
    • 同一行内的层的类型相同,由Operator 指定。其中bottleneck 指的是bottleneck block
    • 同一行内的层的膨胀比相同,由 t 指定。
    • 同一行内的层的输出通道数相同,由c 指定。
    • 同一行内的层:第一层采用步幅s,其它层采用步幅1
  • 采用ReLU6 激活函数,因为它在低精度浮点运算的环境下表现较好。
  • 训练过程中采用dropoutBN
  • MobileNet V1 类似,MobileNet V2 也可以引入宽度乘子、分辨率乘子这两个超参数。

在这里插入图片描述

网络在ImageNet 测试集上的表现: 最后一列给出了预测单张图片的推断时间。

网络 Top 1 Params(百万) 乘-加 数量(百万) CPU
MobileNet V1 70.6 4.2 575 113ms
ShuffleNet (1.5) 71.5 3.4 292 -
ShuffleNet (x2) 73.7 5.4 524 -
NasNet-A 74.0 5.3 564 183ms
MobileNet V2 72.0 3.4 300 75ms
MobileNet V2(1.4) 74.7 6.9 585 143ms

2 MobileNet V2在Fashion-MNIST数据集上的应用示例

2.1 创建MobileNet V2网络模型

import torch
import torch.nn as nn

'''
  定义卷积块,conv+bn+relu6
'''
def conv_block(in_channel, out_channel, kernel_size=3, stride=1, groups=1):

    # 1x1卷积,padding=0
    # 3x3卷积,padding=1
    padding = 0 if kernel_size == 1 else 1
    return nn.Sequential(
        # conv
        nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding=padding, groups=groups, bias=False),
        # bn
        nn.BatchNorm2d(out_channel),
        # relu6
        nn.ReLU6(inplace=True)
    )


'''
  定义倒置残差结构,Inverted Residual
'''
class InvertedResidual(nn.Module):

    def __init__(self, in_channel, out_channel, stride, t=6):
        super(InvertedResidual, self).__init__()

        # 输入通道数
        self.in_channel = in_channel
        # 输出通道数
        self.out_channel = out_channel
        # 步长
        self.stride = stride
        # 中间层通道扩大倍数,对应原文expansion ratio
        self.t = t
        # 计算中间层通道数
        self.hidden_channel = in_channel * t

        # 存放模型结构
        layers = []

        # 如果expansion ratio不为1,就利用1x1卷积进行升维
        if self.t != 1:
            # 添加conv+bn+relu6
            layers += [conv_block(self.in_channel, self.hidden_channel, kernel_size=1)]


        layers += [
            # 添加conv+bn+relu6,此处使用组数等于输入通道数的 分组卷积实现 【depthwise conv】
            conv_block(self.hidden_channel, self.hidden_channel, stride=self.stride, groups=self.hidden_channel),
            # 添加1x1conv+bn,此处不再进行relu6
            conv_block(self.hidden_channel, self.out_channel, kernel_size=1)[:-1]
        ]

        # 倒置残差结构块
        self.residul_block = nn.Sequential(*layers)

    def forward(self, x):
        # 如果卷积步长为1且前后通道数一致,则连接残差边
        if self.stride == 1 and self.in_channel == self.out_channel:
            # x + F(x)
            return x + self.residul_block(x)
        # 否则不进行残差连接
        else:
            # F(x)
            return self.residul_block(x)


'''
  定义MobileNet v2网络
'''
class MobileNetV2(nn.Module):
    def __init__(self, num_classes):
        super(MobileNetV2, self).__init__()

        # 类别数量
        self.num_classes = num_classes

        # 特征提取部分
        self.feature = nn.Sequential(
            # conv_block(3, 32, strid=2),             # conv+bn+relu6,(n,3,224,224)-->(n,32,112,112)
            conv_block(1, 32, stride=2),              # conv+bn+relu6,(n,1,224,224)-->(n,32,112,112)
            InvertedResidual(32, 16, stride=1, t=1),  # inverted residual block,(n,32,112,112)-->(n,16,112,112)
            InvertedResidual(16, 24, stride=2),    # inverted residual block,(n,16,112,112)-->(n,24,56,56)
            InvertedResidual(24, 24, stride=1),    # inverted residual block,(n,24,56,56)-->(n,24,56,56)
            InvertedResidual(24, 32, stride=2),    # inverted residual block,(n,24,56,56)-->(n,32,28,28)
            InvertedResidual(32, 32, stride=1),    # inverted residual block,(n,32,28,28)-->(n,32,28,28)
            InvertedResidual(32, 32, stride=1),    # inverted residual block,(n,32,28,28)-->(n,32,28,28)
            InvertedResidual(32, 64, stride=2),    # inverted residual block,(n,32,28,28)-->(n,64,14,14)
            InvertedResidual(64, 64, stride=1),    # inverted residual block,(n,64,14,14)-->(n,64,14,14)
            InvertedResidual(64, 64, stride=1),    # inverted residual block,(n,64,14,14)-->(n,64,14,14)
            InvertedResidual(64, 64, stride=1),    # inverted residual block,(n,64,14,14)-->(n,64,14,14)
            InvertedResidual(64, 96, stride=1),    # inverted residual block,(n,64,14,14)-->(n,96,14,14)
            InvertedResidual(96, 96, stride=1),    # inverted residual block,(n,96,14,14)-->(n,96,14,14)
            InvertedResidual(96, 96, stride=1),    # inverted residual block,(n,96,14,14)-->(n,96,14,14)
            InvertedResidual(96, 160, stride=2),   # inverted residual block,(n,96,14,14)-->(n,160,7,7)
            InvertedResidual(160, 160, stride=1),  # inverted residual block,(n,160,7,7)-->(n,160,7,7)
            InvertedResidual(160, 160, stride=1),  # inverted residual block,(n,160,7,7)-->(n,160,7,7)
            InvertedResidual(160, 320, stride=1),  # inverted residual block,(n,160,7,7)-->(n,320,7,7)
            conv_block(320, 1280, kernel_size=1)   # conv+bn+relu6,(n,320,7,7)-->(n,1280,7,7)
        )

        # 分类部分
        self.classifier = nn.Sequential(
            # avgpool,(n,1280,7,7)-->(n,1280,1,1)
            nn.AdaptiveAvgPool2d(1),
            # 1x1conv,(n,1280,1,1)-->(n,num_classes,1,1),等同于linear
            nn.Conv2d(1280, self.num_classes, 1, 1, 0)
        )

    def forward(self, x):
        # 提取特征
        x = self.feature(x)
        # 分类
        x = self.classifier(x)
        return x.view(-1, self.num_classes)  # 压缩不需要的维度,返回分类结果,(n,num_classes,1,1)-->(n,num_classes)


if __name__ == '__main__':
    net = MobileNetV2(num_classes=10)
    X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    for layer in net.feature:
        X = layer(X)
        print(layer.__class__.__name__, 'output shape:', X.shape)
    print()
    for layer in net.classifier:
        X = layer(X)
        print(layer.__class__.__name__, 'output shape:', X.shape)

2.2 读取Fashion-MNIST数据集

其他所有的函数,与经典神经网络(1)LeNet及其在Fashion-MNIST数据集上的应用完全一致。

# 我们将图片大小设置224×224
# 训练机器内存有限,将批量大小设置为64
batch_size = 64

train_iter,test_iter = get_mnist_data(batch_size,resize=224)

2.3 在GPU上进行模型训练

from _09_MobileNetV2 import MobileNetV2

# 初始化模型,并设置为10分类
net = MobileNetV2(num_classes=10)

lr, num_epochs = 0.1, 10
train_ch(net, train_iter, test_iter, num_epochs, lr, try_gpu())

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44665283/article/details/131573505