ShuffleNetV2模型复现的相关知识有什么
Admin 2022-09-03 群英技术资讯 1416 次浏览
这篇文章主要介绍“ShuffleNetV2模型复现的相关知识有什么”,有一些人在ShuffleNetV2模型复现的相关知识有什么的问题上存在疑惑,接下来小编就给大家来介绍一下相关的内容,希望对大家解答有帮助,有这个方面学习需要的朋友就继续往下看吧。据说ShuffleNetV2比Mobilenet还要厉害,我决定好好学一下
这篇是ECCV2018关于轻量级模型的文章。
目前大部分的轻量级模型在对比模型速度时用的指标是FLOPs,这个指标主要衡量的就是卷积层的乘法操作。
但是实际运用中会发现,同一个FLOPS的网络运算速度却不同,只用FLOPS去进行衡量的话并不能完全代表模型速度。
通过如下图所示对比,作者发现Elemwise/Data IO等内存读写密集型操作也会极大的影响模型运算速度。

结合理论与实验作者提出了4条实用的指导原则:
1、卷积层的输入和输出特征通道数相等时MAC最小,此时模型速度最快。
2、过量使用组卷积会增加MAC。
3、网络碎片化会降低并行度。
4、不能忽略元素级操作,比如ReLU和Add,虽然它们的FLOPs较小,但是却需要较大的MAC。

如图所示是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, c
def 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 x
def shuffle_unit(inputs, out_channels, bottleneck_ratio, strides=2, stage=1, block=1):
bn_axis = -1
prefix = '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 ret
def 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

网络整体结构如图所示:
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 K
import numpy as np
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, c
def 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 x
def shuffle_unit(inputs, out_channels, bottleneck_ratio, strides=2, stage=1, block=1):
bn_axis = -1
prefix = '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 ret
def 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
def 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 stage
out_channels_in_stage[0] = 24 # first stage has always 24 output channels
out_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_input
model = Model(inputs, x, name=name)
return model
if __name__ == '__main__':
import os
os.environ['CUDA_VISIBLE_DEVICES'] = ''
model = ShuffleNetV2(input_shape=(224, 224, 3),scale_factor=1)
model.summary()
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
内存映射通常可以提高I/O的性能,因为使用内存映射时,不需要对每个访问都建立一个单独的系统调用,也不需要在缓冲区之间复制数据,内核和用户都能很方便的直接访问内存。
这篇文章主要介绍了Python高级文件操作之shutil库详解,文中有非常详细的代码示例,对正在学习python的小伙伴们有很大的帮助,需要的朋友可以参考下
内容介绍1视频帧采样2将图片转为字符画2.1创建像素-字符索引2.2将图片逐像素转换为字符3将字符图像合成视频4完整代码5参考前言:放鞭炮贺新春,在我国有两千多年历史。关于鞭炮的起源,有
这篇文章主要介绍了Django如何创作一个简单的最小程序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
sched模块内容很简单,只定义了一个类。它用来最为一个通用的事件调度模块,接下来通过本文给大家介绍python 包之 sched 事件调度器教程,需要的朋友可以参考下
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008