keras 實(shí)現(xiàn)輕量級網(wǎng)絡(luò)ShuffleNet教程
ShuffleNet是由曠世發(fā)表的一個計算效率極高的CNN架構(gòu),它是專門為計算能力非常有限的移動設(shè)備(例如,10-150 MFLOPs)而設(shè)計的。該結(jié)構(gòu)利用組卷積和信道混洗兩種新的運(yùn)算方法,在保證計算精度的同時,大大降低了計算成本。ImageNet分類和MS COCO對象檢測實(shí)驗(yàn)表明,在40 MFLOPs的計算預(yù)算下,ShuffleNet的性能優(yōu)于其他結(jié)構(gòu),例如,在ImageNet分類任務(wù)上,ShuffleNet的top-1 error 7.8%比最近的MobileNet低。在基于arm的移動設(shè)備上,ShuffleNet比AlexNet實(shí)際加速了13倍,同時保持了相當(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中就用過了,當(dāng)時因?yàn)镚PU的顯存不足因而利用組卷積分配到兩個GPU上訓(xùn)練。簡單來講,組卷積就是將輸入特征圖按照通道方向均分成多個大小一致的特征圖,如下圖所示左面是輸入特征圖右面是均分后的特征圖,然后對得到的每一個特征圖進(jìn)行正常的卷積操作,最后將輸出特征圖按照通道方向拼接起來就可以了。
目前很多框架都支持組卷積,但是tensorflow真的不知道在想什么,到現(xiàn)在還是不支持組卷積,只能自己寫,因此效率肯定不及其他框架原生支持的方法。組卷積層的代碼編寫思路就與上面所說的原理完全一致,代碼如下。
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),盡管組卷積大量減少了計算量和參數(shù),但是通道之間的信息交流也受到了限制因而模型精度肯定會受到影響,因此作者提出通道混洗,在不增加參數(shù)量和計算量的基礎(chǔ)上加強(qiáng)通道之間的信息交流,如下圖所示。
通道混洗層的代碼實(shí)現(xiàn)很巧妙參考了別人的實(shí)現(xiàn)方法。通過下面的代碼說明,d代表特征圖的通道序號,x是經(jīng)過通道混洗后的通道順序。
>>> 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步長時用的單元,c圖為2步長時用的單元。
ShuffleNet架構(gòu)
注意,對于第二階段(Stage2),作者沒有在第一個1×1卷積上應(yīng)用組卷積,因?yàn)檩斎胪ǖ赖臄?shù)量相對較少。
環(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)輕量級網(wǎng)絡(luò)ShuffleNet教程就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Python2中的raw_input() 與 input()
這篇文章主要介紹了Python2中的raw_input() 與 input(),本文分析了它們的內(nèi)部實(shí)現(xiàn)和不同之處,并總結(jié)了什么情況下使用哪個函數(shù),需要的朋友可以參考下2015-06-06基于Python實(shí)現(xiàn)人臉識別相似度對比功能
人臉識別技術(shù)是一種通過計算機(jī)對人臉圖像進(jìn)行分析和處理,從而實(shí)現(xiàn)自動識別和辨認(rèn)人臉的技術(shù),隨著計算機(jī)視覺和模式識別領(lǐng)域的快速發(fā)展,人臉識別技術(shù)取得了長足的進(jìn)步,本文給大家介紹了基于Python實(shí)現(xiàn)人臉識別相似度對比功能,感興趣的朋友可以參考下2024-01-01Pycharm自帶Git實(shí)現(xiàn)版本管理的方法步驟
這篇文章主要介紹了Pycharm自帶Git實(shí)現(xiàn)版本管理的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(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é)公式,探索其相對于 ReLU 的優(yōu)勢,并使用 PyTorch 演示其實(shí)現(xiàn)2023-11-11