keras 實(shí)現(xiàn)輕量級(jí)網(wǎng)絡(luò)ShuffleNet教程
ShuffleNet是由曠世發(fā)表的一個(gè)計(jì)算效率極高的CNN架構(gòu),它是專門為計(jì)算能力非常有限的移動(dòng)設(shè)備(例如,10-150 MFLOPs)而設(shè)計(jì)的。該結(jié)構(gòu)利用組卷積和信道混洗兩種新的運(yùn)算方法,在保證計(jì)算精度的同時(shí),大大降低了計(jì)算成本。ImageNet分類和MS COCO對(duì)象檢測(cè)實(shí)驗(yàn)表明,在40 MFLOPs的計(jì)算預(yù)算下,ShuffleNet的性能優(yōu)于其他結(jié)構(gòu),例如,在ImageNet分類任務(wù)上,ShuffleNet的top-1 error 7.8%比最近的MobileNet低。在基于arm的移動(dòng)設(shè)備上,ShuffleNet比AlexNet實(shí)際加速了13倍,同時(shí)保持了相當(dāng)?shù)臏?zhǔn)確性。
Paper:ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile
Github:https://github.com/zjn-ai/ShuffleNet-keras
網(wǎng)絡(luò)架構(gòu)
組卷積
組卷積其實(shí)早在AlexNet中就用過(guò)了,當(dāng)時(shí)因?yàn)镚PU的顯存不足因而利用組卷積分配到兩個(gè)GPU上訓(xùn)練。簡(jiǎn)單來(lái)講,組卷積就是將輸入特征圖按照通道方向均分成多個(gè)大小一致的特征圖,如下圖所示左面是輸入特征圖右面是均分后的特征圖,然后對(duì)得到的每一個(gè)特征圖進(jìn)行正常的卷積操作,最后將輸出特征圖按照通道方向拼接起來(lái)就可以了。
目前很多框架都支持組卷積,但是tensorflow真的不知道在想什么,到現(xiàn)在還是不支持組卷積,只能自己寫,因此效率肯定不及其他框架原生支持的方法。組卷積層的代碼編寫思路就與上面所說(shuō)的原理完全一致,代碼如下。
def _group_conv(x, filters, kernel, stride, groups): """ Group convolution # Arguments x: Tensor, input tensor of with `channels_last` or 'channels_first' data format filters: Integer, number of output channels kernel: An integer or tuple/list of 2 integers, specifying the width and height of the 2D convolution window. strides: An integer or tuple/list of 2 integers, specifying the strides of the convolution along the width and height. Can be a single integer to specify the same value for all spatial dimensions. groups: Integer, number of groups per channel # Returns Output tensor """ channel_axis = 1 if K.image_data_format() == 'channels_first' else -1 in_channels = K.int_shape(x)[channel_axis] # number of input channels per group nb_ig = in_channels // groups # number of output channels per group nb_og = filters // groups gc_list = [] # Determine whether the number of filters is divisible by the number of groups assert filters % groups == 0 for i in range(groups): if channel_axis == -1: x_group = Lambda(lambda z: z[:, :, :, i * nb_ig: (i + 1) * nb_ig])(x) else: x_group = Lambda(lambda z: z[:, i * nb_ig: (i + 1) * nb_ig, :, :])(x) gc_list.append(Conv2D(filters=nb_og, kernel_size=kernel, strides=stride, padding='same', use_bias=False)(x_group)) return Concatenate(axis=channel_axis)(gc_list)
通道混洗
通道混洗是這篇paper的重點(diǎn),盡管組卷積大量減少了計(jì)算量和參數(shù),但是通道之間的信息交流也受到了限制因而模型精度肯定會(huì)受到影響,因此作者提出通道混洗,在不增加參數(shù)量和計(jì)算量的基礎(chǔ)上加強(qiáng)通道之間的信息交流,如下圖所示。
通道混洗層的代碼實(shí)現(xiàn)很巧妙參考了別人的實(shí)現(xiàn)方法。通過(guò)下面的代碼說(shuō)明,d代表特征圖的通道序號(hào),x是經(jīng)過(guò)通道混洗后的通道順序。
>>> d = np.array([0,1,2,3,4,5,6,7,8]) >>> x = np.reshape(d, (3,3)) >>> x = np.transpose(x, [1,0]) # 轉(zhuǎn)置 >>> x = np.reshape(x, (9,)) # 平鋪 '[0 1 2 3 4 5 6 7 8] --> [0 3 6 1 4 7 2 5 8]'
利用keras后端實(shí)現(xiàn)代碼:
def _channel_shuffle(x, groups): """ Channel shuffle layer # Arguments x: Tensor, input tensor of with `channels_last` or 'channels_first' data format groups: Integer, number of groups per channel # Returns Shuffled tensor """ if K.image_data_format() == 'channels_last': height, width, in_channels = K.int_shape(x)[1:] channels_per_group = in_channels // groups pre_shape = [-1, height, width, groups, channels_per_group] dim = (0, 1, 2, 4, 3) later_shape = [-1, height, width, in_channels] else: in_channels, height, width = K.int_shape(x)[1:] channels_per_group = in_channels // groups pre_shape = [-1, groups, channels_per_group, height, width] dim = (0, 2, 1, 3, 4) later_shape = [-1, in_channels, height, width] x = Lambda(lambda z: K.reshape(z, pre_shape))(x) x = Lambda(lambda z: K.permute_dimensions(z, dim))(x) x = Lambda(lambda z: K.reshape(z, later_shape))(x) return x
ShuffleNet Unit
ShuffleNet的主要構(gòu)成單元。下圖中,a圖為深度可分離卷積的基本架構(gòu),b圖為1步長(zhǎng)時(shí)用的單元,c圖為2步長(zhǎng)時(shí)用的單元。
ShuffleNet架構(gòu)
注意,對(duì)于第二階段(Stage2),作者沒(méi)有在第一個(gè)1×1卷積上應(yīng)用組卷積,因?yàn)檩斎胪ǖ赖臄?shù)量相對(duì)較少。
環(huán)境
Python 3.6
Tensorlow 1.13.1
Keras 2.2.4
實(shí)現(xiàn)
支持channel first或channel last
# -*- coding: utf-8 -*- """ Created on Thu Apr 25 18:26:41 2019 @author: zjn """ import numpy as np from keras.callbacks import LearningRateScheduler from keras.models import Model from keras.layers import Input, Conv2D, Dropout, Dense, GlobalAveragePooling2D, Concatenate, AveragePooling2D from keras.layers import Activation, BatchNormalization, add, Reshape, ReLU, DepthwiseConv2D, MaxPooling2D, Lambda from keras.utils.vis_utils import plot_model from keras import backend as K from keras.optimizers import SGD def _group_conv(x, filters, kernel, stride, groups): """ Group convolution # Arguments x: Tensor, input tensor of with `channels_last` or 'channels_first' data format filters: Integer, number of output channels kernel: An integer or tuple/list of 2 integers, specifying the width and height of the 2D convolution window. strides: An integer or tuple/list of 2 integers, specifying the strides of the convolution along the width and height. Can be a single integer to specify the same value for all spatial dimensions. groups: Integer, number of groups per channel # Returns Output tensor """ channel_axis = 1 if K.image_data_format() == 'channels_first' else -1 in_channels = K.int_shape(x)[channel_axis] # number of input channels per group nb_ig = in_channels // groups # number of output channels per group nb_og = filters // groups gc_list = [] # Determine whether the number of filters is divisible by the number of groups assert filters % groups == 0 for i in range(groups): if channel_axis == -1: x_group = Lambda(lambda z: z[:, :, :, i * nb_ig: (i + 1) * nb_ig])(x) else: x_group = Lambda(lambda z: z[:, i * nb_ig: (i + 1) * nb_ig, :, :])(x) gc_list.append(Conv2D(filters=nb_og, kernel_size=kernel, strides=stride, padding='same', use_bias=False)(x_group)) return Concatenate(axis=channel_axis)(gc_list) def _channel_shuffle(x, groups): """ Channel shuffle layer # Arguments x: Tensor, input tensor of with `channels_last` or 'channels_first' data format groups: Integer, number of groups per channel # Returns Shuffled tensor """ if K.image_data_format() == 'channels_last': height, width, in_channels = K.int_shape(x)[1:] channels_per_group = in_channels // groups pre_shape = [-1, height, width, groups, channels_per_group] dim = (0, 1, 2, 4, 3) later_shape = [-1, height, width, in_channels] else: in_channels, height, width = K.int_shape(x)[1:] channels_per_group = in_channels // groups pre_shape = [-1, groups, channels_per_group, height, width] dim = (0, 2, 1, 3, 4) later_shape = [-1, in_channels, height, width] x = Lambda(lambda z: K.reshape(z, pre_shape))(x) x = Lambda(lambda z: K.permute_dimensions(z, dim))(x) x = Lambda(lambda z: K.reshape(z, later_shape))(x) return x def _shufflenet_unit(inputs, filters, kernel, stride, groups, stage, bottleneck_ratio=0.25): """ ShuffleNet unit # Arguments inputs: Tensor, input tensor of with `channels_last` or 'channels_first' data format filters: Integer, number of output channels kernel: An integer or tuple/list of 2 integers, specifying the width and height of the 2D convolution window. strides: An integer or tuple/list of 2 integers, specifying the strides of the convolution along the width and height. Can be a single integer to specify the same value for all spatial dimensions. groups: Integer, number of groups per channel stage: Integer, stage number of ShuffleNet bottleneck_channels: Float, bottleneck ratio implies the ratio of bottleneck channels to output channels # Returns Output tensor # Note For Stage 2, we(authors of shufflenet) do not apply group convolution on the first pointwise layer because the number of input channels is relatively small. """ channel_axis = 1 if K.image_data_format() == 'channels_first' else -1 in_channels = K.int_shape(inputs)[channel_axis] bottleneck_channels = int(filters * bottleneck_ratio) if stage == 2: x = Conv2D(filters=bottleneck_channels, kernel_size=kernel, strides=1, padding='same', use_bias=False)(inputs) else: x = _group_conv(inputs, bottleneck_channels, (1, 1), 1, groups) x = BatchNormalization(axis=channel_axis)(x) x = ReLU()(x) x = _channel_shuffle(x, groups) x = DepthwiseConv2D(kernel_size=kernel, strides=stride, depth_multiplier=1, padding='same', use_bias=False)(x) x = BatchNormalization(axis=channel_axis)(x) if stride == 2: x = _group_conv(x, filters - in_channels, (1, 1), 1, groups) x = BatchNormalization(axis=channel_axis)(x) avg = AveragePooling2D(pool_size=(3, 3), strides=2, padding='same')(inputs) x = Concatenate(axis=channel_axis)([x, avg]) else: x = _group_conv(x, filters, (1, 1), 1, groups) x = BatchNormalization(axis=channel_axis)(x) x = add([x, inputs]) return x def _stage(x, filters, kernel, groups, repeat, stage): """ Stage of ShuffleNet # Arguments x: Tensor, input tensor of with `channels_last` or 'channels_first' data format filters: Integer, number of output channels kernel: An integer or tuple/list of 2 integers, specifying the width and height of the 2D convolution window. strides: An integer or tuple/list of 2 integers, specifying the strides of the convolution along the width and height. Can be a single integer to specify the same value for all spatial dimensions. groups: Integer, number of groups per channel repeat: Integer, total number of repetitions for a shuffle unit in every stage stage: Integer, stage number of ShuffleNet # Returns Output tensor """ x = _shufflenet_unit(x, filters, kernel, 2, groups, stage) for i in range(1, repeat): x = _shufflenet_unit(x, filters, kernel, 1, groups, stage) return x def ShuffleNet(input_shape, classes): """ ShuffleNet architectures # Arguments input_shape: An integer or tuple/list of 3 integers, shape of input tensor k: Integer, number of classes to predict # Returns A keras model """ inputs = Input(shape=input_shape) x = Conv2D(24, (3, 3), strides=2, padding='same', use_bias=True, activation='relu')(inputs) x = MaxPooling2D(pool_size=(3, 3), strides=2, padding='same')(x) x = _stage(x, filters=384, kernel=(3, 3), groups=8, repeat=4, stage=2) x = _stage(x, filters=768, kernel=(3, 3), groups=8, repeat=8, stage=3) x = _stage(x, filters=1536, kernel=(3, 3), groups=8, repeat=4, stage=4) x = GlobalAveragePooling2D()(x) x = Dense(classes)(x) predicts = Activation('softmax')(x) model = Model(inputs, predicts) return model if __name__ == '__main__': model = ShuffleNet((224, 224, 3), 1000) #plot_model(model, to_file='ShuffleNet.png', show_shapes=True)
以上這篇keras 實(shí)現(xiàn)輕量級(jí)網(wǎng)絡(luò)ShuffleNet教程就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python爬蟲谷歌Chrome F12抓包過(guò)程原理解析
這篇文章主要介紹了Python爬蟲谷歌Chrome F12抓包過(guò)程原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06Python2中的raw_input() 與 input()
這篇文章主要介紹了Python2中的raw_input() 與 input(),本文分析了它們的內(nèi)部實(shí)現(xiàn)和不同之處,并總結(jié)了什么情況下使用哪個(gè)函數(shù),需要的朋友可以參考下2015-06-06python條件語(yǔ)句和while循環(huán)語(yǔ)句
這篇文章主要介紹了python條件語(yǔ)句和while循環(huán)語(yǔ)句,文章基于python的相關(guān)資料展開(kāi)對(duì)其條件語(yǔ)句及while循環(huán)語(yǔ)句的詳細(xì)內(nèi)容介紹,需要的小伙伴可以參考一下2022-04-04基于Python實(shí)現(xiàn)人臉識(shí)別相似度對(duì)比功能
人臉識(shí)別技術(shù)是一種通過(guò)計(jì)算機(jī)對(duì)人臉圖像進(jìn)行分析和處理,從而實(shí)現(xiàn)自動(dòng)識(shí)別和辨認(rèn)人臉的技術(shù),隨著計(jì)算機(jī)視覺(jué)和模式識(shí)別領(lǐng)域的快速發(fā)展,人臉識(shí)別技術(shù)取得了長(zhǎng)足的進(jìn)步,本文給大家介紹了基于Python實(shí)現(xiàn)人臉識(shí)別相似度對(duì)比功能,感興趣的朋友可以參考下2024-01-01Pycharm自帶Git實(shí)現(xiàn)版本管理的方法步驟
這篇文章主要介紹了Pycharm自帶Git實(shí)現(xiàn)版本管理的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09使用Pytorch實(shí)現(xiàn)Swish激活函數(shù)的示例詳解
激活函數(shù)是人工神經(jīng)網(wǎng)絡(luò)的基本組成部分,他們將非線性引入模型,使其能夠?qū)W習(xí)數(shù)據(jù)中的復(fù)雜關(guān)系,Swish 激活函數(shù)就是此類激活函數(shù)之一,在本文中,我們將深入研究 Swish 激活函數(shù),提供數(shù)學(xué)公式,探索其相對(duì)于 ReLU 的優(yōu)勢(shì),并使用 PyTorch 演示其實(shí)現(xiàn)2023-11-11