MobileNetV2原理说明及实践落地

news2024/12/24 9:36:25

本文参考:

轻量级网络——MobileNetV2_Clichong的博客-CSDN博客_mobilenetv2

1、MobileNetV2介绍

MobileNetV1主要是提出了可分离卷积的概念,大大减少了模型的参数个数,从而缩小了计算量。但是在CenterNet算法中作为BackBone效果并不佳,模型收敛效果不好导致目标检测的准确率不高。

MobileNetV2在MobileNetV1的DW和PW的基础上进行了优化,使得准确率更高,作为CenterNet算法的BackBone效果也可以。它的两个亮点是:

  • Inverted Residuals:倒残差结构
  • Linear Bottlenecks:结构的最后一层采用线性层

2、MobileNetV2的结构

(1)倒残差结构

ResNet网络:残差结构是先用1*1卷积降维 再升维 的操作,所以两头大中间小。

MobileNetV2中,残差结构是先用1*1卷积升维 再降维 的操作,所以两头小中间大。

在MobileNetV2中采用了新的激活函数:ReLU6,它的定义如下:

(2)线性Bottlenecks

针对倒残差结构最后一层的卷积层,采用了线性的激活函数(f(x)=x,可以认为没有激活函数),而不是使用ReLU6激活函数。

原因解释:

 ReLU激活函数对于低维的信息可能会造成比较大的损失,而对于高维的特征信息造成的损失很小。而且由于倒残差结构是两头小中间大,所以输出的是一个低维的特征信息。所以使用一个线性的激活函数避免特征损失。

低维解释:低维针对的是channel,低维意味着[batch, channel, height, width]中的height和width还较大。

(3)整体结构

当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut链接。

 3、MobileNetV2的pytorch实现

import torch
import torch.nn as nn
import torchvision

# 分类个数
num_class = 5

# DW卷积
def Conv3x3BNReLU(in_channels, out_channels, stride, groups):
    return nn.Sequential(
        # stride=2,wh减半; stride=1,wh不变
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1, groups=groups),
        nn.BatchNorm2d(out_channels),
        nn.ReLU6(inplace=True)
    )

# PW卷积
def Conv1x1BNReLU(in_channels, out_channels):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
        nn.BatchNorm2d(out_channels),
        nn.ReLU6(inplace=True)
    )

# PW卷积(Linear)没有使用激活函数
def Conv1x1BN(in_channels, out_channels):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
        nn.BatchNorm2d(out_channels)
    )


class InvertedResidual(nn.Module):
    # t为扩展因子
    def __init__(self, in_channels, out_channels, expansion_factor, stride):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        self.in_channels = in_channels
        self.out_channels = out_channels
        mid_channels = (in_channels * expansion_factor)

        # 先1*1卷积升维,再1*1卷积降维
        self.bottleneck = nn.Sequential(
            # 升维操作
            Conv1x1BNReLU(in_channels, mid_channels),
            # DW卷积,降低参数量
            Conv3x3BNReLU(mid_channels, mid_channels, stride, groups=mid_channels),
            # 降维操作
            Conv1x1BN(mid_channels, out_channels)
        )

        # stride=1才有shortcut,此方法让原本不相同的channels相同
        if self.stride == 1:
            self.shortcut = Conv1x1BN(in_channels, out_channels)

    def forward(self, x):
        out = self.bottleneck(x)
        out = (out + self.shortcut(x)) if self.stride == 1 else out
        return out

class MobileNetV2(nn.Module):
    def make_layer(self, in_channels, out_channels, stride, factor, block_num):
        layers = []
        layers.append(InvertedResidual(in_channels, out_channels, factor, stride))
        for i in range(block_num):
            layers.append(InvertedResidual(out_channels, out_channels, factor, 1))
        return nn.Sequential(*layers)

    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear) or isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def __init__(self, num_classes=num_class, t=6):
        super(MobileNetV2, self).__init__()

        self.first_conv = Conv3x3BNReLU(3, 32, 2, groups=1)
        # 32 -> 16 stride=1 wh不变
        self.layer1 = self.make_layer(in_channels=32, out_channels=16, stride=1, factor=1, block_num=1)
        # 16 -> 24 stride=2 wh减半
        self.layer2 = self.make_layer(in_channels=16, out_channels=24, stride=2, factor=t, block_num=2)
        # 24 -> 32 stride=2 wh减半
        self.layer3 = self.make_layer(in_channels=24, out_channels=32, stride=2, factor=t, block_num=3)
        # 32 -> 64 stride=2 wh减半
        self.layer4 = self.make_layer(in_channels=32, out_channels=64, stride=2, factor=t, block_num=4)
        # 64 -> 96 stride=1 wh不变
        self.layer5 = self.make_layer(in_channels=64, out_channels=96, stride=1, factor=t, block_num=3)
        # 96 -> 160 stride=2 wh减半
        self.layer6 = self.make_layer(in_channels=96, out_channels=160, stride=2, factor=t, block_num=3)
        # 160 -> 320 stride=1 wh不变
        self.layer7 = self.make_layer(in_channels=160, out_channels=320, stride=1, factor=t, block_num=1)
        # 320 -> 1280 单纯的升维操作
        self.last_conv = Conv1x1BNReLU(320, 1280)

        self.avgpool = nn.AvgPool2d(kernel_size=7, stride=1)
        self.dropout = nn.Dropout2d(p=0.2)
        self.linear = nn.Linear(in_features=1280, out_features=num_classes)
        self.init_params()

    def forward(self, x):
        x = self.first_conv(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.layer5(x)
        x = self.layer6(x)
        x = self.layer7(x)
        x = self.last_conv(x)
        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.dropout(x)
        x = self.linear(x)
        return x

if __name__ == '__main__':
    model = MobileNetV2()

    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)

4、MobileNetV2作为CenterNet的BackBone

import torch
import torch.nn as nn

# DW卷积
def Conv3x3BNReLU(in_channels, out_channels, stride, groups):
    return nn.Sequential(
        # stride=2,wh减半; stride=1,wh不变
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1, groups=groups),
        nn.BatchNorm2d(out_channels),
        nn.ReLU6(inplace=True)
    )

# PW卷积
def Conv1x1BNReLU(in_channels, out_channels):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
        nn.BatchNorm2d(out_channels),
        nn.ReLU6(inplace=True)
    )

# PW卷积(Linear)没有使用激活函数
def Conv1x1BN(in_channels, out_channels):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
        nn.BatchNorm2d(out_channels)
    )


class InvertedResidual(nn.Module):
    # t为扩展因子
    def __init__(self, in_channels, out_channels, expansion_factor, stride):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        self.in_channels = in_channels
        self.out_channels = out_channels
        mid_channels = (in_channels * expansion_factor)

        # 先1*1卷积升维,再1*1卷积降维
        self.bottleneck = nn.Sequential(
            # 升维操作
            Conv1x1BNReLU(in_channels, mid_channels),
            # DW卷积,降低参数量
            Conv3x3BNReLU(mid_channels, mid_channels, stride, groups=mid_channels),
            # 降维操作
            Conv1x1BN(mid_channels, out_channels)
        )

        # stride=1才有shortcut,此方法让原本不相同的channels相同
        if self.stride == 1:
            self.shortcut = Conv1x1BN(in_channels, out_channels)

    def forward(self, x):
        out = self.bottleneck(x)
        out = (out + self.shortcut(x)) if self.stride == 1 else out
        return out

class MobileNetV2(nn.Module):
    def make_layer(self, in_channels, out_channels, stride, factor, block_num):
        layers = []
        layers.append(InvertedResidual(in_channels, out_channels, factor, stride))
        for i in range(block_num):
            layers.append(InvertedResidual(out_channels, out_channels, factor, 1))
        return nn.Sequential(*layers)

    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear) or isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def __init__(self, heads, t=6):
        super(MobileNetV2, self).__init__()

        num_classes = heads['hm']

        self.first_conv = Conv3x3BNReLU(3, 32, 2, groups=1)
        # 32 -> 16 stride=1 wh不变
        self.layer1 = self.make_layer(in_channels=32, out_channels=16, stride=1, factor=1, block_num=1)
        # 16 -> 24 stride=2 wh减半
        self.layer2 = self.make_layer(in_channels=16, out_channels=24, stride=2, factor=t, block_num=2)
        # 24 -> 32 stride=2 wh减半
        self.layer3 = self.make_layer(in_channels=24, out_channels=32, stride=2, factor=t, block_num=3)
        # 32 -> 64 stride=2 wh减半
        self.layer4 = self.make_layer(in_channels=32, out_channels=64, stride=2, factor=t, block_num=4)
        # 64 -> 96 stride=1 wh不变
        self.layer5 = self.make_layer(in_channels=64, out_channels=96, stride=1, factor=t, block_num=3)
        # 96 -> 160 stride=2 wh减半
        self.layer6 = self.make_layer(in_channels=96, out_channels=160, stride=2, factor=t, block_num=3)
        # 160 -> 320 stride=1 wh不变
        self.layer7 = self.make_layer(in_channels=160, out_channels=320, stride=1, factor=t, block_num=1)
        # 320 -> 1280 单纯的升维操作
        self.last_conv = Conv1x1BNReLU(320, 1280)
        self.init_params()

        self.hm = nn.Conv2d(20, num_classes, kernel_size=1)
        self.wh = nn.Conv2d(20, 2, kernel_size=1)
        self.reg = nn.Conv2d(20, 2, kernel_size=1)


    def forward(self, x):
        x = self.first_conv(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.layer5(x)
        x = self.layer6(x)
        x = self.layer7(x)
        x = self.last_conv(x)

        y = x.view(x.shape[0], -1, 128, 128)
        z = {}
        z['hm'] = self.hm(y)
        z['wh'] = self.wh(y)
        z['reg'] = self.reg(y)
        return [z]

if __name__ == '__main__':
    heads = {'hm': 10, 'wh': 2, 'reg': 2}
    model = MobileNetV2(heads)

    input = torch.randn(1, 3, 512, 512)
    out = model(input)
    print(out.shape)

5、MobileNetV2在CenterNet目标检测落地情况

(1)训练情况

训练loss,mobilenetV1在batch_size=16时最少达到4.0左右,而mobileNetV2在batch_size=16时最少达到0.5以下。与DLASeg的效果基本接近。

(2)目标检测效果

检测效果也与DLASeg基本接近

(3)模型参数量

DLASeg为2000W个左右

MobileNetV1为320W个左右

MobileNetV2为430W个左右,总模型大小为17M

(4)CPU运行时间

DLASeg为1.2s

MobileNetV1为250ms

MobileNetV2为600ms

 

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

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

相关文章

【MATLAB教程案例59】使用matlab实现基于LSTM网络的数据分类预测功能与仿真分析

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 目录 1.软件版本 2.LSTM网络理论概述

荧光点击试剂ICG-N3, ICG-azide,根据具体的需求进行定制, 避免频繁的溶解和冻干,取用时注意干燥

【英文名称】 ICG-azide,ICG-N3 【结 构 式】 【CAS】N/A 【分子式】C48H56N6O4S 【分子量】813.07 【基团】叠氮基基团 【纯度】95% 【规格】1mg,5mg,10mg 【是否接受定制】根据具体的需求进行定制 【外观】 绿色固体(具…

最小二乘问题,,而不是方法

最小二乘是一大类问题,而不是一个简单的方法 适用于:线性(非线性)方程组问题,如果观测带有噪声,我们需要建立最小二乘模型。如果噪声符合高斯分布,即最小二乘问题的解对应于原问题的最大似然解…

千万不要做“舔狗式”营销

不知道在网上做生意的你们是否经常陷入我下面说的这几种尴尬境地:每天都在推广引流,每天都在发广告,但转化率却低得可怜。粉丝质量普遍不行,不精准,好不容易来几个粉,不是白嫖党就是垃圾粉。两句话不对头&a…

C++实现红外Fir谱图文件转BMP图片文件

1、红外图谱文件 红外图谱文件由文件头和温度数据两部分组成,其中文件头 64 个字节,其余字节为温度数据。 如下如: 每个像素用两个字节表示温度(16 位有符号短整数),低字节在前,高字节在后,温度数据单位为 0.1℃,温度数据共 w h 2 字节。文件头定义 如下: 从文件…

Android Studio 开发环境搭建 配置

前言 上一次做 Android 开发还是在大三的 Android 课设项目上「 IPOD - 本地音乐播放器」 开发环境:JDK开发语言:Java开发工具:Android Studio 现在由于工作需要 「面向业务编程」,需要重拾 Android 开发,由于电脑已换…

mysql基础学习(2)-regexp正则表达式的学习

表示例 user 学习: ^匹配以^后面字符开头的所有数据,示例:^str,则匹配str开头的所以数据$匹配以$前面面字符结尾的所有数据,示例:str$,则匹配str结尾的所以数据. 匹配任何单个字符&…

嵌入式分享合集121

一、Matter协议 不是广告啊就是看见了就搬来了 也没用过啊~ 早在2019年底,中国就已成为全球最大的智能家居消费国,占全球智能家居消费市场份额的50%-60%;2021年,中国智能家居市场规模约为5880亿元,同比增长12.7%&…

使用github的pages配合action自动部署vue项目

如果你需要某个 action,不必自己写复杂的脚本,直接引用他人写好的 action 即可,整个持续集成过程,就变成了一个 actions 的组合。这就是 GitHub Actions 最特别的地方。 GitHub 做了一个官方市场,可以搜索到他人提交的…

安全研究 # 课题:二进制成分分析(Binary SCA)

本文参考多篇文章写作而成,出处在文末注明(本文在课题开展过程中长期保持更新)。 二进制成分分析 SCA(Software Composition Analysis)软件成分分析,通俗的理解就是通过分析软件包含的一些信息和特征来实现对该软件的识别、管理、…

奶茶果茶饮品店数字化转型| 奶茶店小程序 | 餐饮外卖系统

奶茶/果茶/饮品店里总是容易聚集大量年轻消费者,尤其品牌开新店或搞促销,往往会排很长的队伍,而茶饮店也会根据季节推出相应的新品或冷热饮品,以保证消费者在任何时候都能喝到应季的饮品。 年轻人是奶茶饮品店的主要消费者&#x…

第九章 webpack5高级优化——提升开发体验

之前我们所配置的webpack打包出来的文件在浏览器里面进行调试,是非常困难的。 查看打包出来的js文件: /** ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").* This devtool is neither m…

SpringCloud全系列知识(6)——RabbitMQ(消息队列)

消息队列(MQ)—RabbitMQ 一 初识MQ 1.同步通信与异步通信 1.同步通信的问题 同步调用的优点在于时效性高,可以立即得到结果 微服务之间基于Feign的调用属于同步方式,存在一些问题 耦合性:业务较多时,扩展…

分布式数据库与集中式数据库的差异

第一章:分布式数据库与集中式数据库的差异 1. 数据库是核心的IT基础设施 • 互联网业务增长,带动核心系统升级 • 核心系统引入数据库分布式与云化改造,支撑横向平滑扩展 • 5G规模推广,带动IT系统升级 • 5G具备大带宽和超低延时…

基于java+springboot+mybatis+vue+mysql的企业客户信息反馈平台

项目介绍 企业客户信息反馈平台能够通过互联网得到广泛的、全面的宣传,让尽可能多的用户了解和熟知企业客户信息反馈平台的便捷高效,不仅为客户提供了服务,而且也推广了自己,让更多的客户了解自己。对于企业客户信息反馈而言&…

小蓝本 第一本 《因式分解技巧》 第六章 二元二次式的分解 笔记(第六天)

小蓝本 第一本 《因式分解技巧》 第六章 二元二次式的分解 笔记(第六天)前言二元二次式的分解研究对象类型普通二元二次式基本形式分解方法总体总结——长十字相乘注意三元齐次式基本形式分解方法总体总结——长十字相乘注意提示习题6题目题解前言 今天…

【数据库】MySQL和Navicate安装和使用

MySQL和Navicate安装使用MySQLNavicate使用数据库MySQL 1、下载 可以考虑在官网下载或者在其它途径获取MySQL https://www.mysql.com/ download-》选择免费版或者其他版本-》选择系统和版本号-》根据需要下载 MySQL的Windows安装版只提供 32 位了 2、运行安装文件 可以选择…

基于51单片机的交通信号灯系统设计

功能: 十字路口交通灯控制程序: 正常时,EW方向计时60s,SN方向计时40s 若按时间加按键(Add_Button)按钮,EW、SN方向各加5s,不可大于99s,79s 若按时间减按键(R…

全网最新的Fiddler(3):fiddler界面工具栏介绍

fiddler界面工具栏介绍 (1)WinConfig:windows 使用了一种叫做“AppContainer”的隔离技术,使得一些流量无法正常捕获,在 fiddler中点击 WinConfig 按钮可以解除这个诅咒,这个与菜单栏 Tools→Win8 Loopback…

【Flutter 组件】003-基础组件:按钮

【Flutter 组件】003-基础组件:按钮 文章目录【Flutter 组件】003-基础组件:按钮一、ElevatedButton 悬浮按钮1、概述2、构造方法3、示例代码示例运行结果二、TextButton 文本按钮1、概述2、构造方法3、示例代码示例运行结果三、OutlinedButton 边框按钮…