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

news2024/12/26 10:59:37

经典轻量级神经网络(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 1Params(百万)乘-加 数量(百万)CPU
MobileNet V170.64.2575113ms
ShuffleNet (1.5)71.53.4292-
ShuffleNet (x2)73.75.4524-
NasNet-A74.05.3564183ms
MobileNet V272.03.430075ms
MobileNet V2(1.4)74.76.9585143ms

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())

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/725959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis 入门指南

Redis 入门指南 目录 Redis 入门指南 Redis 基本概念 Redis 常用命令 字符串String操作命令: 哈希Hash操作命令: 列表list操作命令 集合set操作命令: 有序集合sorted set 操作命令 通用命令 在Java中操作 Redis 1.Jedis 2.Spring …

windows后台窗口启动jar包

启动命令 chcp 65001 //切换utf-8 echo off start javaw -jar test-1.0-SNAPSHOT.jar --spring.profiles.activeprod echo 启动成功,按任意键结束 pause exit查看后台进程命令 wmic process where caption"javaw.exe" get processid,caption,commandli…

手机里的视频怎么转换成MP4格式?简单的转换方法分享

MP4格式是一种广泛使用的视频格式,几乎所有设备和操作系统都支持MP4格式的视频播放。无论是使用 iPhone、iPad、安卓手机、电视等各种设备,都可以播放 MP4 格式的视频。这种广泛的兼容性使得 MP4 成为一种非常方便的视频格式,我们可以随时随地…

IDEA自动生成实体类

操作流程 第一次需要配置Generate POJOs.groovy 新建该文件 Generate POJOs.groovy import com.intellij.database.model.DasTable import com.intellij.database.model.ObjectKind import com.intellij.database.util.Case import com.intellij.database.util.DasUtil import…

嵌入式面试刷题(day1)

文章目录 前言一、由for( ; ;)引出的一系列问题二、sizeof和strlen的误区三、字符串转义字符带来的问题四、结构体概念模糊五、!和~概念分不清六、switch case误区七、短路运算八、复合赋值运算符带来的问题九、什么是左值什么是右值十、struct和union总结 前言 最…

cuda sample-asyncAPI(01)

C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.0\0_Simple\asyncAPI 这个例子说明了CUDA事件在GPU计时和CPU与GPU重叠执行方面的应用。 事件被插入到CUDA的调用流中。 由于CUDA流调用是异步的,CPU可以在GPU执行时进行计算(包括主机和设备之间的DM…

【前端系列】前端如何使用websocket发送消息

序言 今天来学习一下前端如何使用websocket发送消息 1 基础介绍 1.1 什么是WebSocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它可以让客户端和服务器之间进行实时的双向通信。与传统的 HTTP 请求不同,WebSocket 使用了一个长连接&…

Django实现简单的音乐播放器 1

使用django框架开发一个简单的音乐播放器。 效果: 目录 环境准备 安装django 创建项目 创建应用 注册应用 配置数据库 设置数据库配置 设置pymysql库引用 创建数据库 创建数据表 生成表迁移文件 执行表迁移 配置时区 配置语言 配置子应用路由 在pla…

外部中断实验(stm32)

目录 EXIT的相关代码exit.cexit.h LED的相关代码KEY的相关代码BEEP的相关代码main.cGPIO 跟中断线的映射关系图 说明:以下内容参考正点原子资料 EXIT的相关代码 exit.c void EXTIX_Init(void) { EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitS…

el-dialog中,el-form中表单信息未收集齐全时禁用提交按钮,动态控制按钮是否禁用

需求 在el-dialog中放置了一个表单&#xff0c;打开el-dialog时&#xff0c;表单没有收集内容&#xff0c;各项为空&#xff0c;此时表单的提交按钮被禁用&#xff0c;只有每个表单项都收集到内容时&#xff0c;才会将提交按钮设置为可用 预期效果 解决方案 <el-button c…

多线程进阶学习(高并发、线程池、使用场景)

文章目录 1、线程基础知识1.1、线程和进程线程和进程的区别&#xff1f; 1.2、并行与并发并行与并发有什么区别&#xff1f; 1.3、线程的创建方式创建线程的方式有哪些&#xff1f;刚才你说过&#xff0c;使用runnable和callable都可以创建线程&#xff0c;它们有什么区别呢&am…

OpenCV读取一张8位无符号三通道图像并显示

#include <iostream> #include <opencv2/imgcodecs.hpp> #include <opencv2/opencv.hpp> #include

Kafka生产者概述

【Kafka】Kafka生产者概述 文章目录 【Kafka】Kafka生产者概述1. 生产者1.1 生产者消息发送流程1.1.1 发送原理1.1.2 生产者重要参数列表 1.2 异步发送 API1.2.1 普通异步发送1.2.2 带回调函数的异步发送 1. 生产者 1.1 生产者消息发送流程 1.1.1 发送原理 在消息发送过程中…

企业数字化转型成功的标准是什么?

​近几年来&#xff0c;数字化转型一直是企业管理者的热议话题&#xff0c;那么&#xff0c;到底该以什么标准来衡量转型成效&#xff0c;又如何向管理者交出一份满意的答卷呢&#xff1f; 为了确保转型效果与目标的一致性&#xff0c;在规划数字化转型之初&#xff0c;选择正…

JVM — JDK11垃圾回收器 ZGC

1. ZGC介绍 ZGC&#xff08;The Z Garbage Collector&#xff09;是 JDK 11 中推出的一款低延迟垃圾回收器&#xff0c;为实现以下几个目标而诞生的垃圾回收器&#xff0c;停顿时间不超过 10ms&#xff0c;停顿时间不会因堆变大而变长&#xff0c;支持 8MB~4TB 级别的堆&#…

【专题速递】MD-VQA、AB实验、音视频质量建设以及在手机上的应用

// 怎样才能更好地进行QoE优化&#xff1f;音视频技术在用户侧的挑战又是什么&#xff1f;7月29日LiveVideoStackCon上海站QoE与数据驱动专场&#xff0c;为您解答。 QoE与数据驱动 在音视频应用里&#xff0c;获得了大量的用户上报数据&#xff0c;包括但不限于音视频质量数…

ASM汇编语言环境安装

以前是学习过8位单片机的&#xff0c;忘记的差不多了。现在需要使用64位的汇编语言&#xff0c;准备重新学习。 64位的编程环境使用ebe&#xff0c;sf上有的下载&#xff1a; 这个软件不错&#xff0c;可以调试64位的汇编语言&#xff0c;寄存器也可以实时查看。 32位的编程开…

pcl经典算法60例——(1)打开并显示点云,窗口PCLVisualizer嵌入MFC的picture control

一、搭建MFC框架 1、环境说明 本教程为vs2022&#xff0c;pcl1.12.1版本&#xff0c;其他版本自己进行适当修改&#xff0c;仅供参考。 2、方法步骤 (1)新建项目&#xff0c;选择“基于对话框”&#xff0c;然后点击“下一步” 二、配置pcl环境 关于配置环境&#xff0c;网…

centos安装常见软件

安装git # 方式一&#xff1a;yum install git -y# 方式二&#xff1a;&#xff08;开发会用的软件&#xff09;yum -y groupinstall "Development tools"# 执行下面这条yum install openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel…

点云实战及Python路径实验

点云实战 文章目录 点云实战python 有关路径实验下一级目录上一级目录 学习PointNet论文&#xff08;https://arxiv.org/abs/1612.00593&#xff09;并实践 python .\show_seg.py --dataset ../data/shapenet --model .\seg\seg_model_Chair_2.pth python 有关路径实验 下一级…