神经网络学习小记录47——ShuffleNetV2模型的复现详解

article/2025/7/22 22:38:59

神经网络学习小记录47——ShuffleNetV2模型的复现详解

  • 学习前言
  • 什么是ShuffleNetV2
  • ShuffleNetV2
    • 1、所用模块
    • 2、网络整体结构
  • 网络实现代码

学习前言

据说ShuffleNetV2比Mobilenet还要厉害,我决定好好学一下。
在这里插入图片描述

什么是ShuffleNetV2

这篇是ECCV2018关于轻量级模型的文章。

目前大部分的轻量级模型在对比模型速度时用的指标是FLOPs,这个指标主要衡量的就是卷积层的乘法操作。

但是实际运用中会发现,同一个FLOPS的网络运算速度却不同,只用FLOPS去进行衡量的话并不能完全代表模型速度。

通过如下图所示对比,作者发现Elemwise/Data IO等内存读写密集型操作也会极大的影响模型运算速度。
在这里插入图片描述
结合理论与实验作者提出了4条实用的指导原则:
1、卷积层的输入和输出特征通道数相等时MAC最小,此时模型速度最快。
2、过量使用组卷积会增加MAC。
3、网络碎片化会降低并行度。
4、不能忽略元素级操作,比如ReLU和Add,虽然它们的FLOPs较小,但是却需要较大的MAC。

ShuffleNetV2

1、所用模块

在这里插入图片描述
如图所示是ShuffleNetV2所常用的两个模块:

1、当Stride==1的时候,采用左边的模块,由于残差边没有卷积,因此宽高不变,主要用于加深网络层数

2、当Stride==2的时候,采用右边的模块,由于残差边有卷积,因此宽高可变,主要用于压缩特征层的宽高,进行下采样

模块实现代码如下:

def channel_split(x, name=''):# 输入进来的通道数in_channles = x.shape.as_list()[-1]ip = in_channles // 2# 对通道数进行分割c_hat = Lambda(lambda z: z[:, :, :, 0:ip], name='%s/sp%d_slice' % (name, 0))(x)c = Lambda(lambda z: z[:, :, :, ip:], name='%s/sp%d_slice' % (name, 1))(x)return c_hat, cdef channel_shuffle(x):height, width, channels = x.shape.as_list()[1:]channels_per_split = channels // 2# 通道交换x = K.reshape(x, [-1, height, width, 2, channels_per_split])x = K.permute_dimensions(x, (0,1,2,4,3))x = K.reshape(x, [-1, height, width, channels])return xdef shuffle_unit(inputs, out_channels, bottleneck_ratio, strides=2, stage=1, block=1):bn_axis = -1prefix = 'stage{}/block{}'.format(stage, block)# [116, 232, 464]bottleneck_channels = int(out_channels * bottleneck_ratio/2)if strides < 2:c_hat, c = channel_split(inputs, '{}/spl'.format(prefix))inputs = c# [116, 232, 464]x = Conv2D(bottleneck_channels, kernel_size=(1,1), strides=1, padding='same', name='{}/1x1conv_1'.format(prefix))(inputs)x = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_1'.format(prefix))(x)x = Activation('relu', name='{}/relu_1x1conv_1'.format(prefix))(x)# 深度可分离卷积x = DepthwiseConv2D(kernel_size=3, strides=strides, padding='same', name='{}/3x3dwconv'.format(prefix))(x)x = BatchNormalization(axis=bn_axis, name='{}/bn_3x3dwconv'.format(prefix))(x)# [116, 232, 464]x = Conv2D(bottleneck_channels, kernel_size=1,strides=1,padding='same', name='{}/1x1conv_2'.format(prefix))(x)x = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_2'.format(prefix))(x)x = Activation('relu', name='{}/relu_1x1conv_2'.format(prefix))(x)# 当strides等于2的时候,残差边需要添加卷积if strides < 2:ret = Concatenate(axis=bn_axis, name='{}/concat_1'.format(prefix))([x, c_hat])else:s2 = DepthwiseConv2D(kernel_size=3, strides=2, padding='same', name='{}/3x3dwconv_2'.format(prefix))(inputs)s2 = BatchNormalization(axis=bn_axis, name='{}/bn_3x3dwconv_2'.format(prefix))(s2)s2 = Conv2D(bottleneck_channels, kernel_size=1,strides=1,padding='same', name='{}/1x1_conv_3'.format(prefix))(s2)s2 = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_3'.format(prefix))(s2)s2 = Activation('relu', name='{}/relu_1x1conv_3'.format(prefix))(s2)ret = Concatenate(axis=bn_axis, name='{}/concat_2'.format(prefix))([x, s2])ret = Lambda(channel_shuffle, name='{}/channel_shuffle'.format(prefix))(ret)return retdef block(x, channel_map, bottleneck_ratio, repeat=1, stage=1):x = shuffle_unit(x, out_channels=channel_map[stage-1],strides=2,bottleneck_ratio=bottleneck_ratio,stage=stage,block=1)for i in range(1, repeat+1):x = shuffle_unit(x, out_channels=channel_map[stage-1],strides=1,bottleneck_ratio=bottleneck_ratio,stage=stage, block=(1+i))return x

2、网络整体结构

在这里插入图片描述
网络整体结构如图所示:
1、当输入进来的图片为224,224,3的时候,会经过一次卷积压缩+一次最大池化,此时网络的shape由224,224,3->112,112,24->56,56,24。
2、经过一次右边的ShuffleNet模块后进行三次左边的ShuffleNet模块。此时网络的shape由56,56,24->28,28,116。
3、经过一次右边的ShuffleNet模块后进行七次左边的ShuffleNet模块。此时网络的shape由28,28,116->14,14,232。
4、经过一次右边的ShuffleNet模块后进行三次左边的ShuffleNet模块。此时网络的shape由14,14,232->7,7,464。
5、卷积到1024,此时网络的shape由7,7,464->7,7,1024。
6、全局池化后,进行全连接,用于预测。

网络实现代码

ShuffleNetV2一共有4个scale,分别对应不同大小的ShuffleNetV2。
在这里插入图片描述

import numpy as np
from keras.utils import plot_model
from keras.layers import Input, Conv2D, MaxPool2D
from keras.layers import Activation, Add, Concatenate, Conv2D
from keras.layers import GlobalAveragePooling2D, Dense
from keras.layers import MaxPool2D,AveragePooling2D, BatchNormalization, Lambda, DepthwiseConv2D
from keras.models import Model
import keras.backend as Kimport numpy as npdef channel_split(x, name=''):# 输入进来的通道数in_channles = x.shape.as_list()[-1]ip = in_channles // 2# 对通道数进行分割c_hat = Lambda(lambda z: z[:, :, :, 0:ip], name='%s/sp%d_slice' % (name, 0))(x)c = Lambda(lambda z: z[:, :, :, ip:], name='%s/sp%d_slice' % (name, 1))(x)return c_hat, cdef channel_shuffle(x):height, width, channels = x.shape.as_list()[1:]channels_per_split = channels // 2# 通道交换x = K.reshape(x, [-1, height, width, 2, channels_per_split])x = K.permute_dimensions(x, (0,1,2,4,3))x = K.reshape(x, [-1, height, width, channels])return xdef shuffle_unit(inputs, out_channels, bottleneck_ratio, strides=2, stage=1, block=1):bn_axis = -1prefix = 'stage{}/block{}'.format(stage, block)# [116, 232, 464]bottleneck_channels = int(out_channels * bottleneck_ratio/2)if strides < 2:c_hat, c = channel_split(inputs, '{}/spl'.format(prefix))inputs = c# [116, 232, 464]x = Conv2D(bottleneck_channels, kernel_size=(1,1), strides=1, padding='same', name='{}/1x1conv_1'.format(prefix))(inputs)x = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_1'.format(prefix))(x)x = Activation('relu', name='{}/relu_1x1conv_1'.format(prefix))(x)# 深度可分离卷积x = DepthwiseConv2D(kernel_size=3, strides=strides, padding='same', name='{}/3x3dwconv'.format(prefix))(x)x = BatchNormalization(axis=bn_axis, name='{}/bn_3x3dwconv'.format(prefix))(x)# [116, 232, 464]x = Conv2D(bottleneck_channels, kernel_size=1,strides=1,padding='same', name='{}/1x1conv_2'.format(prefix))(x)x = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_2'.format(prefix))(x)x = Activation('relu', name='{}/relu_1x1conv_2'.format(prefix))(x)# 当strides等于2的时候,残差边需要添加卷积if strides < 2:ret = Concatenate(axis=bn_axis, name='{}/concat_1'.format(prefix))([x, c_hat])else:s2 = DepthwiseConv2D(kernel_size=3, strides=2, padding='same', name='{}/3x3dwconv_2'.format(prefix))(inputs)s2 = BatchNormalization(axis=bn_axis, name='{}/bn_3x3dwconv_2'.format(prefix))(s2)s2 = Conv2D(bottleneck_channels, kernel_size=1,strides=1,padding='same', name='{}/1x1_conv_3'.format(prefix))(s2)s2 = BatchNormalization(axis=bn_axis, name='{}/bn_1x1conv_3'.format(prefix))(s2)s2 = Activation('relu', name='{}/relu_1x1conv_3'.format(prefix))(s2)ret = Concatenate(axis=bn_axis, name='{}/concat_2'.format(prefix))([x, s2])ret = Lambda(channel_shuffle, name='{}/channel_shuffle'.format(prefix))(ret)return retdef block(x, channel_map, bottleneck_ratio, repeat=1, stage=1):x = shuffle_unit(x, out_channels=channel_map[stage-1],strides=2,bottleneck_ratio=bottleneck_ratio,stage=stage,block=1)for i in range(1, repeat+1):x = shuffle_unit(x, out_channels=channel_map[stage-1],strides=1,bottleneck_ratio=bottleneck_ratio,stage=stage, block=(1+i))return xdef ShuffleNetV2(input_tensor=None,pooling='max',input_shape=(224,224,3),num_shuffle_units=[3,7,3],scale_factor=1,bottleneck_ratio=1,classes=1000):name = 'ShuffleNetV2_{}_{}_{}'.format(scale_factor, bottleneck_ratio, "".join([str(x) for x in num_shuffle_units]))out_dim_stage_two = {0.5:48, 1:116, 1.5:176, 2:244}out_channels_in_stage = np.array([1,1,2,4])out_channels_in_stage *= out_dim_stage_two[scale_factor]  #  calculate output channels for each stageout_channels_in_stage[0] = 24  # first stage has always 24 output channelsout_channels_in_stage = out_channels_in_stage.astype(int)img_input = Input(shape=input_shape)x = Conv2D(filters=out_channels_in_stage[0], kernel_size=(3, 3), padding='same', use_bias=False, strides=(2, 2),activation='relu', name='conv1')(img_input)x = MaxPool2D(pool_size=(3, 3), strides=(2, 2), padding='same', name='maxpool1')(x)for stage in range(len(num_shuffle_units)):repeat = num_shuffle_units[stage]x = block(x, out_channels_in_stage,repeat=repeat,bottleneck_ratio=bottleneck_ratio,stage=stage + 2)if scale_factor!=2:x = Conv2D(1024, kernel_size=1, padding='same', strides=1, name='1x1conv5_out', activation='relu')(x)else:x = Conv2D(2048, kernel_size=1, padding='same', strides=1, name='1x1conv5_out', activation='relu')(x)x = GlobalAveragePooling2D(name='global_avg_pool')(x)x = Dense(classes, name='fc')(x)x = Activation('softmax', name='softmax')(x)inputs = img_inputmodel = Model(inputs, x, name=name)return modelif __name__ == '__main__':import osos.environ['CUDA_VISIBLE_DEVICES'] = ''model = ShuffleNetV2(input_shape=(224, 224, 3),scale_factor=1)model.summary()

http://chatgpt.dhexx.cn/article/Qd2U3aLB.shtml

相关文章

一个简单的dos命令实现无限弹窗,卡死电脑

教大家一个简单的dos命令实现无限弹窗&#xff0c;从而实现卡死电脑。 1.新建一个文本文件 2.在该文件里面输入 : start start cmd goto start 3.保存后并把此文件的.txt后缀改为.bat 提示&#xff1a;如果你的计算机不显示后缀拓展名&#xff0c;首先应先让显示拓展名再进行操…

Pycharm制作搞怪弹窗(声音强制最大,屏幕亮度强制最亮,按钮躲避,弹窗炸弹)

Pycharm制作搞怪弹窗(声音强制最大&#xff0c;屏幕亮度强制最亮&#xff0c;按钮躲避&#xff0c;弹窗炸弹&#xff09; 闲来无聊用python制作了一个搞怪的桌面弹窗程序&#xff0c;惊喜连连哦 运行动图 实现代码&#xff1a; import tkinter as tk import tkinter.font as…

Charles抓包出现弹窗问题或者无法抓包https问题汇总

一、重要问题总结 1、https抓包需要在电脑端和移动端都装上相应的证书&#xff01;不同的电脑、手机需要的证书可能不一样&#xff0c;如果不能正常工作&#xff0c;建议重新安装。 2、iOS10及以上系统&#xff0c;需要在安装charles证书后 在设置->通用->关于本机->…

python制作恶搞_Pycharm制作搞怪弹窗的实现代码

闲来无聊用python制作了一个搞怪的桌面弹窗程序,惊喜连连哦 运行动图 实现代码: import tkinter as tk import tkinter.font as tkFont # 引入字体模块 import time import sys import pygame import random import threading import win32api import wmi from tkinter.messa…

用Python写一个假的病毒炸弹(整蛊)

病毒炸弹 现在我们用Python来写一个假的病毒炸弹 弹窗实现 import tkinter as tk import random import threading import timedef boom():window tk.Tk()width window.winfo_screenwidth()height window.winfo_screenheight()a random.randrange(0, width)b random.ra…

bat 炸弹升级

转自&#xff1a;http://digi.163.com/15/0320/06/AL4LP0QD0016192R.html 第1页&#xff1a;什么是批处理炸弹&#xff1f; 最近网上流传一个叫做《大哥别杀我》视频纷纷遭到网友模仿&#xff0c;虽然我们都知道视频里出现的人大多都是群众演员&#xff0c;但还是会被视频中各种…

xss完成浏览器视窗炸弹

无聊&#xff0c;&#xff0c;&#xff0c;发个文章。 这个就是很简单的视窗炸弹&#xff0c;放在自己的 xss平台上&#xff0c;执行就会一直打开页面。初学者可以 试一试。 function WindowBomb() { var iCounter 0 // dummy counter while (true) { window.open(“https:/…

Linux fork炸弹以及预防办法

fork炸弹是什么&#xff1f; fork炸弹以极快的速度创建大量进程&#xff08;进程数呈以2为底数的指数增长趋势&#xff09;&#xff0c;并以此消耗系统分配予进程的可用空间使进程表饱和&#xff0c;而系统在进程表饱和后就无法运行新程序&#xff0c;除非进程表中的某一进程终…

弹窗炸弹恶搞整人

1.创建一个记事本文件 2.编辑文件内容 在文件中输入以下内容并保存 :start start cmd goto start3.实现弹窗炸弹 把文件扩展名改为.bat。 双击执行&#xff08;慎用&#xff09;&#xff0c;效果如下&#xff1a; 4.解除无线弹窗的方法 方法一&#xff1a; 新建个记事本…

python弹窗炸弹

当我们看某人不爽时&#xff0c;可以用这个&#xff1a; ​ import tkinter as tk import random import threading import timedef dow():window tk.Tk()window.title(你是SB)window.geometry("200x50" "" str(random.randrange(0, window.winfo_scre…

无限弹窗(bat代码 整人恶作剧)

炸弹弹窗&#xff0c;是使用bat制作的一个小程序&#xff0c;效果就是执行程序后会一直不停地弹出窗口&#xff0c;用来恶作剧。下面我们就来看看详细的教程。 打开文件&#xff0c;输入以下代码&#xff1a; :start start cmd goto start 点击文件&#xff0c;选择另存为 把文…

bat代码雨代码流星_怎么制作无限弹窗效果? 限弹窗代码bat文件分享

炸弹弹屏,也可以叫炸弹弹窗,是可以使用txt制作的一个小程序,效果就是执行程序后会一直不停地弹出窗口,就好像炸弹轰炸一样,如图所示,用来恶作剧很好玩。该怎么制作这个无限弹窗效果呢?下面我们就来看看详细的教程。 1、首先,如图所示,我们打开电脑,在桌面鼠标右键,新…

分享森林火灾年鉴统计平台

https://www.yearbookchina.com 可以参观一下&#xff0c;里面有一部分研究人员能用得上的数据

分享统计数据搜集方法

阿关科研统计篇 将近两个月&#xff0c;在统计年鉴中水深火热。终于&#xff0c;形成一套可供后人快速工作的流程&#xff0c;以供大家参考。点名感恩提供帮助的江红蕾博士的引领和帮助。 第一&#xff1a;明确你想获取的统计数据区域 一般而言&#xff0c;历年《中国统计年…

统计年鉴在哪里查找

一、国家统计局 国家统计局>>年度统计公报 (stats.gov.cn) 二、地方统计局 以江苏为例 江苏省人民政府 江苏统计年鉴 (jiangsu.gov.cn) 三、统计年鉴分享平台 可以直接获取国家以及地方统计年鉴&#xff0c;此网站收录的很全。 上海统计年鉴2001 - 统计年鉴分享平台…

统计年鉴分享平台 - 让科研工作者和学生查找数据更方便

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也…

统计年鉴分享平台,怎么做到免费下载

上海统计年鉴2001 - 统计年鉴分享平台 (yearbookchina.com) 在各个平台分享3次&#xff0c;可以获得110个下载币。

如何将CHM文件翻译成中文

chm文件对于我们在某种需求上而言是非常有帮助的一类文件&#xff0c;可以快速的让我们了解&#xff0c;熟悉和掌握我们所需要满足的需求&#xff0c;但是英文版的chm文件对于我们来说&#xff0c;既是又爱又恨&#xff0c;特别是对于英语不好的同学来说&#xff0c;为什么写这…

win10 打开 chm 文件显示异常的解决办法

一些电子书、软件说明书使用chm格式&#xff0c;在win10下打开chm经常会遇到显示空白&#xff0c;或提示页显无法显示错误。可能的原因与解决办法如下&#xff1a; 1. HTML Help (CHM) 组件未注册 用管理员身份打开command 窗口&#xff0c;输入 regsvr32 hhctrl.ocx &#x…