python神經(jīng)網(wǎng)絡(luò)ShuffleNetV2模型復(fù)現(xiàn)詳解
什么是ShuffleNetV2
據(jù)說(shuō)ShuffleNetV2比Mobilenet還要厲害,我決定好好學(xué)一下
這篇是ECCV2018關(guān)于輕量級(jí)模型的文章。
目前大部分的輕量級(jí)模型在對(duì)比模型速度時(shí)用的指標(biāo)是FLOPs,這個(gè)指標(biāo)主要衡量的就是卷積層的乘法操作。
但是實(shí)際運(yùn)用中會(huì)發(fā)現(xiàn),同一個(gè)FLOPS的網(wǎng)絡(luò)運(yùn)算速度卻不同,只用FLOPS去進(jìn)行衡量的話并不能完全代表模型速度。
通過(guò)如下圖所示對(duì)比,作者發(fā)現(xiàn)Elemwise/Data IO等內(nèi)存讀寫(xiě)密集型操作也會(huì)極大的影響模型運(yùn)算速度。

結(jié)合理論與實(shí)驗(yàn)作者提出了4條實(shí)用的指導(dǎo)原則:
1、卷積層的輸入和輸出特征通道數(shù)相等時(shí)MAC最小,此時(shí)模型速度最快。
2、過(guò)量使用組卷積會(huì)增加MAC。
3、網(wǎng)絡(luò)碎片化會(huì)降低并行度。
4、不能忽略元素級(jí)操作,比如ReLU和Add,雖然它們的FLOPs較小,但是卻需要較大的MAC。
ShuffleNetV2
1、所用模塊

如圖所示是ShuffleNetV2所常用的兩個(gè)模塊:
1、當(dāng)Stride==1的時(shí)候,采用左邊的模塊,由于殘差邊沒(méi)有卷積,因此寬高不變,主要用于加深網(wǎng)絡(luò)層數(shù)。
2、當(dāng)Stride==2的時(shí)候,采用右邊的模塊,由于殘差邊有卷積,因此寬高可變,主要用于壓縮特征層的寬高,進(jìn)行下采樣。
模塊實(shí)現(xiàn)代碼如下:
def channel_split(x, name=''):
# 輸入進(jìn)來(lái)的通道數(shù)
in_channles = x.shape.as_list()[-1]
ip = in_channles // 2
# 對(duì)通道數(shù)進(jìn)行分割
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)
# 當(dāng)strides等于2的時(shí)候,殘差邊需要添加卷積
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
2、網(wǎng)絡(luò)整體結(jié)構(gòu)

網(wǎng)絡(luò)整體結(jié)構(gòu)如圖所示:
1、當(dāng)輸入進(jìn)來(lái)的圖片為224,224,3的時(shí)候,會(huì)經(jīng)過(guò)一次卷積壓縮+一次最大池化,此時(shí)網(wǎng)絡(luò)的shape由224,224,3->112,112,24->56,56,24。
2、經(jīng)過(guò)一次右邊的ShuffleNet模塊后進(jìn)行三次左邊的ShuffleNet模塊。此時(shí)網(wǎng)絡(luò)的shape由56,56,24->28,28,116。
3、經(jīng)過(guò)一次右邊的ShuffleNet模塊后進(jìn)行七次左邊的ShuffleNet模塊。此時(shí)網(wǎng)絡(luò)的shape由28,28,116->14,14,232。
4、經(jīng)過(guò)一次右邊的ShuffleNet模塊后進(jìn)行三次左邊的ShuffleNet模塊。此時(shí)網(wǎng)絡(luò)的shape由14,14,232->7,7,464。
5、卷積到1024,此時(shí)網(wǎng)絡(luò)的shape由7,7,464->7,7,1024。
6、全局池化后,進(jìn)行全連接,用于預(yù)測(cè)。
網(wǎng)絡(luò)實(shí)現(xiàn)代碼
ShuffleNetV2一共有4個(gè)scale,分別對(duì)應(yīng)不同大小的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=''):
# 輸入進(jìn)來(lái)的通道數(shù)
in_channles = x.shape.as_list()[-1]
ip = in_channles // 2
# 對(duì)通道數(shù)進(jìn)行分割
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)
# 當(dāng)strides等于2的時(shí)候,殘差邊需要添加卷積
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()
以上就是python神經(jīng)網(wǎng)絡(luò)ShuffleNetV2模型復(fù)現(xiàn)詳解的詳細(xì)內(nèi)容,更多關(guān)于ShuffleNetV2模型復(fù)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Python機(jī)器學(xué)習(xí)利用鳶尾花數(shù)據(jù)繪制ROC和AUC曲線
- 利用Python畫(huà)ROC曲線和AUC值計(jì)算
- 一文詳解Python灰色預(yù)測(cè)模型實(shí)現(xiàn)示例
- python回歸分析邏輯斯蒂模型之多分類(lèi)任務(wù)詳解
- python深度學(xué)習(xí)tensorflow訓(xùn)練好的模型進(jìn)行圖像分類(lèi)
- Python實(shí)現(xiàn)自動(dòng)駕駛訓(xùn)練模型
- python神經(jīng)網(wǎng)絡(luò)Keras?GhostNet模型的實(shí)現(xiàn)
- python神經(jīng)網(wǎng)絡(luò)Densenet模型復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)MobileNetV3?small模型的復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)MobileNetV3?large模型的復(fù)現(xiàn)詳解
- python神經(jīng)網(wǎng)絡(luò)Inception?ResnetV2模型復(fù)現(xiàn)詳解
- python模型性能ROC和AUC分析詳解
相關(guān)文章
Python爬蟲(chóng)實(shí)例——爬取美團(tuán)美食數(shù)據(jù)
這篇文章主要介紹了Python爬蟲(chóng)如何爬取美團(tuán)美食數(shù)據(jù),文中講解非常詳細(xì),代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-07-07
python實(shí)現(xiàn)電腦操控安卓手機(jī)
網(wǎng)上雖然有很多教程,但是隨著版本的更新總有各種各樣的坑,本文以親身踏坑經(jīng)歷介紹了電腦使用python操控安卓手機(jī),從安裝到使用方法,感興趣的可以了解一下2021-05-05
numpy數(shù)組的重塑和轉(zhuǎn)置實(shí)現(xiàn)
本文主要介紹了numpy數(shù)組的重塑和轉(zhuǎn)置實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
解決Pycharm輸入法無(wú)法切換中英文問(wèn)題
這篇文章主要介紹了解決Pycharm輸入法無(wú)法切換中英文問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
python腳本調(diào)用iftop 統(tǒng)計(jì)業(yè)務(wù)應(yīng)用流量的思路詳解
這篇文章主要介紹了python腳本調(diào)用iftop 統(tǒng)計(jì)業(yè)務(wù)應(yīng)用流量的思路詳解,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
基于Python實(shí)現(xiàn)的車(chē)牌識(shí)別系統(tǒng)
本文將以基于Python的車(chē)牌識(shí)別系統(tǒng)實(shí)現(xiàn)為方向,介紹車(chē)牌識(shí)別技術(shù)的基本原理、常用算法和方法,并詳細(xì)講解如何利用Python語(yǔ)言實(shí)現(xiàn)一個(gè)完整的車(chē)牌識(shí)別系統(tǒng),需要的朋友可以參考下2023-10-10
Python數(shù)據(jù)處理篇之Sympy系列(五)---解方程
這篇文章主要介紹了Python數(shù)據(jù)處理篇之Sympy系列(五)---解方程,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10

