python神經(jīng)網(wǎng)絡Keras?GhostNet模型的實現(xiàn)
什么是GhostNet模型
GhostNet是華為諾亞方舟實驗室提出來的一個非常有趣的網(wǎng)絡,我們一起來學習一下。
2020年,華為新出了一個輕量級網(wǎng)絡,命名為GhostNet。
在優(yōu)秀CNN模型中,特征圖存在冗余是非常重要的。如圖所示,這個是對ResNet-50第一個殘差塊特征圖進行可視化的結(jié)果,當我們給一個神經(jīng)網(wǎng)絡輸入一張圖片時,我們可以獲得特別多的特征圖。
利用小扳手連接起來的兩幅特征圖,它們的相似性就特別高,這個就是神經(jīng)網(wǎng)絡中存在的特征圖冗雜的情況。
作者將相似的特征圖認為是彼此的Ghost,所以這個網(wǎng)絡就叫做GhostNet(誤)。
在GhostNet這篇論文里面,作者認為可以使用一些計算量更低(Cheap Operations)的操作去生成這些冗余的特征圖,這樣就可以在保證良好檢測效果的情況下,減少模型的參數(shù)量與提高模型的執(zhí)行速度。
GhostNet模型的實現(xiàn)思路
1、Ghost Module
通過上述的介紹,我們了解到了,GhostNet的核心思想就是使用一些計算量更低(Cheap Operations)的操作去生成這些冗余的特征圖。
在論文中,作者設計了一個名為Ghost Module的模塊,他的功能是代替普通卷積。
Ghost Module將普通卷積分為兩部分,首先進行一個普通的1x1卷積,這是一個少量卷積,比如正常使用32通道的卷積,這里就用16通道的卷積,這個1x1卷積的作用類似于特征整合,生成輸入特征層的特征濃縮。
然后我們再進行深度可分離卷積,這個深度可分離卷積是逐層卷積,它也就是我們上面提到的Cheap Operations。它利用上一步獲得的特征濃縮生成Ghost特征圖。
因此,如果我們從整體上去看這個Ghost Module,它其實就是兩步簡單思想的匯總:
1、利用1x1卷積獲得輸入特征的必要特征濃縮。
2、利用深度可分離卷積獲得特征濃縮的相似特征圖(Ghost)。
Ghost Module的實現(xiàn)代碼如下:
def _ghost_module(inputs, exp, kernel, dw_kernel, ratio, strides=1, padding='same',use_bias=False, relu=True): output_channels = math.ceil(exp * 1.0 / ratio) x = Conv2D(output_channels, kernel, strides=strides, padding=padding, use_bias=use_bias)(inputs) x = BatchNormalization()(x) if relu: x = Activation('relu')(x) dw = DepthwiseConv2D(dw_kernel, strides, padding=padding, depth_multiplier=ratio-1, use_bias=use_bias)(x) dw = BatchNormalization()(dw) if relu: dw = Activation('relu')(dw) x = Concatenate(axis=-1)([x,dw]) x = Lambda(slices, arguments={'n':exp})(x) return x
2、Ghost Bottlenecks
Ghost Bottlenecks是由Ghost Module組成的瓶頸結(jié)構(gòu),就像這樣。
其實本質(zhì)上就是用Ghost Module,來代替瓶頸結(jié)構(gòu)里面的普通卷積。
Ghost Bottlenecks可以分為兩個部分,分別是主干部分和殘差邊部分,包含Ghost Module的,我們稱它為主干部分。
Ghost Bottlenecks有兩個種類,如下圖所示,當我們需要對特征層的寬高進行壓縮的時候,我們會設置這個Ghost Bottlenecks的Stride=2,即步長為2。
此時我們會Bottlenecks里面多添加一些卷積層,在主干部分里,我們會在兩個Ghost Module中添加一個步長為2x2的深度可分離卷積進行特征層的寬高壓縮。在殘差邊部分,我們也會添加上一個步長為2x2的深度可分離卷積和1x1的普通卷積。
Ghost Bottlenecks的實現(xiàn)代碼如下:
def _ghost_bottleneck(inputs, output_channel, hidden_channel, kernel, ghost_kernel, strides, ratio, squeeze): input_shape = K.int_shape(inputs) # 獲取輸入張量的尺寸 x = _ghost_module(inputs, hidden_channel, [1,1], ghost_kernel, ratio) if strides > 1: x = DepthwiseConv2D(kernel, strides, padding='same', depth_multiplier=1, use_bias=False)(x) x = BatchNormalization()(x) if squeeze: x = _squeeze(x, hidden_channel, 4) x = _ghost_module(x, output_channel, [1,1], ghost_kernel, ratio, relu=False) if strides == 1 and input_shape[-1] == output_channel: res = inputs else: res = DepthwiseConv2D(kernel, strides=strides, padding='same', depth_multiplier=1, use_bias=False)(inputs) res = BatchNormalization()(res) res = Conv2D(output_channel, (1, 1), padding='same', strides=(1, 1), use_bias=False)(res) res = BatchNormalization()(res) x = Add()([res, x]) return x
3、Ghostnet的構(gòu)建
整個Ghostnet的構(gòu)建方式如列表所示:
可以看到,整個Ghostnet都是由Ghost Bottlenecks進行組成的。
當一張圖片輸入到Ghostnet當中時,我們首先進行一個16通道的普通1x1卷積塊(卷積+標準化+激活函數(shù))。
之后我們就開始Ghost Bottlenecks的堆疊了,利用Ghost Bottlenecks,我們最終獲得了一個7x7x160的特征層(當輸入是224x224x3的時候)。
然后我們會利用一個1x1的卷積塊進行通道數(shù)的調(diào)整,此時我們可以獲得一個7x7x960的特征層。
之后我們進行一次全局平均池化,然后再利用一個1x1的卷積塊進行通道數(shù)的調(diào)整,獲得一個1x1x1280的特征層。
然后平鋪后進行全連接就可以進行分類了。
GhostNet的代碼構(gòu)建
1、模型代碼的構(gòu)建
GhostNet的實現(xiàn)代碼如下,該代碼是Ghostnet在YoloV4上的應用,可以參考一下:
import math import warnings import numpy as np import tensorflow as tf from keras import backend as K from keras.applications.imagenet_utils import decode_predictions from keras.initializers import random_normal from keras.layers import (Activation, Add, BatchNormalization, Concatenate, Conv2D, DepthwiseConv2D, GlobalAveragePooling2D, Lambda, Multiply, Reshape) def slices(dw, n): return dw[:,:,:,:n] def _make_divisible(v, divisor, min_value=None): if min_value is None: min_value = divisor new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) if new_v < 0.9 * v: new_v += divisor return new_v def _squeeze(inputs, hidden_channel, ratio, block_id, sub_block_id): x = GlobalAveragePooling2D()(inputs) x = Reshape((1,1,-1))(x) x = Conv2D(_make_divisible(hidden_channel/ratio, 4), (1,1), strides=(1,1), padding='same', kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".se.conv_reduce")(x) x = Activation('relu')(x) x = Conv2D(hidden_channel, (1,1),strides=(1,1), padding='same', kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".se.conv_expand")(x) x = Activation('hard_sigmoid')(x) x = Multiply()([inputs, x]) # inputs和x逐元素相乘 return x def _ghost_module(inputs, exp, ratio, block_id, sub_block_id, part, kernel_size=1, dw_size=3, stride=1, relu=True): output_channels = math.ceil(exp * 1.0 / ratio) x = Conv2D(output_channels, kernel_size, strides=stride, padding="same", use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".primary_conv.0")(inputs) x = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".primary_conv.1")(x) if relu: x = Activation('relu')(x) dw = DepthwiseConv2D(dw_size, 1, padding="same", depth_multiplier=ratio-1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".cheap_operation.0")(x) dw = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".ghost"+str(part)+".cheap_operation.1")(dw) if relu: dw = Activation('relu')(dw) x = Concatenate(axis=-1)([x,dw]) x = Lambda(slices, arguments={'n':exp})(x) return x def _ghost_bottleneck(inputs, output_channel, hidden_channel, kernel, strides, ratio, squeeze, block_id, sub_block_id): input_shape = K.int_shape(inputs) x = _ghost_module(inputs, hidden_channel, ratio, block_id, sub_block_id, 1) if strides > 1: x = DepthwiseConv2D(kernel, strides, padding='same', depth_multiplier=1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".conv_dw")(x) x = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".bn_dw")(x) if squeeze: x = _squeeze(x, hidden_channel, 4, block_id, sub_block_id) x = _ghost_module(x, output_channel, ratio, block_id, sub_block_id, 2, relu=False) if strides == 1 and input_shape[-1] == output_channel: res = inputs else: res = DepthwiseConv2D(kernel, strides=strides, padding='same', depth_multiplier=1, use_bias=False, depthwise_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.0")(inputs) res = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.1")(res) res = Conv2D(output_channel, (1, 1), padding='same', strides=(1, 1), use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.2")(res) res = BatchNormalization(name="blocks."+str(block_id)+"."+str(sub_block_id)+".shortcut.3")(res) x = Add()([res, x]) return x def Ghostnet(inputs): x = Conv2D(16, (3, 3), padding="same", strides=(2, 2), use_bias=False, kernel_initializer=random_normal(stddev=0.02), name="conv_stem")(inputs) x = BatchNormalization(name="bn1")(x) x = Activation('relu')(x) x = _ghost_bottleneck(x, 16, 16, (3, 3), strides=1, ratio=2, squeeze=False, block_id=0, sub_block_id=0) x = _ghost_bottleneck(x, 24, 48, (3, 3), strides=2, ratio=2, squeeze=False, block_id=1, sub_block_id=0) x = _ghost_bottleneck(x, 24, 72, (3, 3), strides=1, ratio=2, squeeze=False, block_id=2, sub_block_id=0) x = _ghost_bottleneck(x, 40, 72, (5, 5), strides=2, ratio=2, squeeze=True, block_id=3, sub_block_id=0) x = _ghost_bottleneck(x, 40, 120, (5, 5), strides=1, ratio=2, squeeze=True, block_id=4, sub_block_id=0) feat1 = x x = _ghost_bottleneck(x, 80, 240, (3, 3), strides=2, ratio=2, squeeze=False, block_id=5, sub_block_id=0) x = _ghost_bottleneck(x, 80, 200, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=0) x = _ghost_bottleneck(x, 80, 184, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=1) x = _ghost_bottleneck(x, 80, 184, (3, 3), strides=1, ratio=2, squeeze=False, block_id=6, sub_block_id=2) x = _ghost_bottleneck(x, 112, 480, (3, 3), strides=1, ratio=2, squeeze=True, block_id=6, sub_block_id=3) x = _ghost_bottleneck(x, 112, 672, (3, 3), strides=1, ratio=2, squeeze=True, block_id=6, sub_block_id=4) feat2 = x x = _ghost_bottleneck(x, 160, 672, (5, 5), strides=2, ratio=2, squeeze=True, block_id=7, sub_block_id=0) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=False, block_id=8, sub_block_id=0) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=True, block_id=8, sub_block_id=1) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=False, block_id=8, sub_block_id=2) x = _ghost_bottleneck(x, 160, 960, (5, 5), strides=1, ratio=2, squeeze=True, block_id=8, sub_block_id=3) feat3 = x return feat1,feat2,feat3
2、Yolov4上的應用
作為一個輕量級網(wǎng)絡,我把Ghostnet和Mobilenet放在一起,作為Yolov4的主干網(wǎng)絡進行特征提取。
對于yolov4來講,我們需要利用主干特征提取網(wǎng)絡獲得的三個有效特征進行加強特征金字塔的構(gòu)建。
我們通過上述代碼可以取出三個有效特征層,我們可以利用這三個有效特征層替換原來yolov4主干網(wǎng)絡CSPdarknet53的有效特征層。
為了進一步減少參數(shù)量,我們可以使用深度可分離卷積代替yoloV3中用到的普通卷積。
最終Ghostnet-Yolov4的構(gòu)建代碼如下:
from functools import wraps from keras import backend as K from keras.initializers import random_normal from keras.layers import (Activation, BatchNormalization, Concatenate, Conv2D, DepthwiseConv2D, Input, Lambda, MaxPooling2D, UpSampling2D) from keras.layers.normalization import BatchNormalization from keras.models import Model from keras.regularizers import l2 from utils.utils import compose from nets.ghostnet import Ghostnet from nets.mobilenet_v1 import MobileNetV1 from nets.mobilenet_v2 import MobileNetV2 from nets.mobilenet_v3 import MobileNetV3 from nets.yolo_training import yolo_loss def relu6(x): return K.relu(x, max_value=6) #------------------------------------------------------# # 單次卷積DarknetConv2D # 如果步長為2則自己設定padding方式。 #------------------------------------------------------# @wraps(Conv2D) def DarknetConv2D(*args, **kwargs): darknet_conv_kwargs = {'kernel_initializer' : random_normal(stddev=0.02), 'kernel_regularizer': l2(5e-4)} darknet_conv_kwargs['padding'] = 'valid' if kwargs.get('strides')==(2,2) else 'same' darknet_conv_kwargs.update(kwargs) return Conv2D(*args, **darknet_conv_kwargs) #---------------------------------------------------# # 卷積塊 -> 卷積 + 標準化 + 激活函數(shù) # DarknetConv2D + BatchNormalization + Relu6 #---------------------------------------------------# def DarknetConv2D_BN_Leaky(*args, **kwargs): no_bias_kwargs = {'use_bias': False} no_bias_kwargs.update(kwargs) return compose( DarknetConv2D(*args, **no_bias_kwargs), BatchNormalization(), Activation(relu6)) #---------------------------------------------------# # 深度可分離卷積塊 # DepthwiseConv2D + BatchNormalization + Relu6 #---------------------------------------------------# def _depthwise_conv_block(inputs, pointwise_conv_filters, alpha = 1, depth_multiplier=1, strides=(1, 1)): pointwise_conv_filters = int(pointwise_conv_filters * alpha) x = DepthwiseConv2D((3, 3), depthwise_initializer=random_normal(stddev=0.02), padding='same', depth_multiplier=depth_multiplier, strides=strides, use_bias=False)(inputs) x = BatchNormalization()(x) x = Activation(relu6)(x) x = DarknetConv2D(pointwise_conv_filters, (1, 1), padding='same', use_bias=False, strides=(1, 1))(x) x = BatchNormalization()(x) return Activation(relu6)(x) #---------------------------------------------------# # 進行五次卷積 #---------------------------------------------------# def make_five_convs(x, num_filters): # 五次卷積 x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) x = _depthwise_conv_block(x, num_filters*2,alpha=1) x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) x = _depthwise_conv_block(x, num_filters*2,alpha=1) x = DarknetConv2D_BN_Leaky(num_filters, (1,1))(x) return x #---------------------------------------------------# # Panet網(wǎng)絡的構(gòu)建,并且獲得預測結(jié)果 #---------------------------------------------------# def yolo_body(input_shape, anchors_mask, num_classes, backbone="mobilenetv1", alpha=1): inputs = Input(input_shape) #---------------------------------------------------# # 生成mobilnet的主干模型,獲得三個有效特征層。 #---------------------------------------------------# if backbone=="mobilenetv1": #---------------------------------------------------# # 52,52,256;26,26,512;13,13,1024 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV1(inputs, alpha=alpha) elif backbone=="mobilenetv2": #---------------------------------------------------# # 52,52,32;26,26,92;13,13,320 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV2(inputs, alpha=alpha) elif backbone=="mobilenetv3": #---------------------------------------------------# # 52,52,40;26,26,112;13,13,160 #---------------------------------------------------# feat1,feat2,feat3 = MobileNetV3(inputs, alpha=alpha) elif backbone=="ghostnet": #---------------------------------------------------# # 52,52,40;26,26,112;13,13,160 #---------------------------------------------------# feat1,feat2,feat3 = Ghostnet(inputs) else: raise ValueError('Unsupported backbone - `{}`, Use mobilenetv1, mobilenetv2, mobilenetv3, ghostnet.'.format(backbone)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(feat3) P5 = _depthwise_conv_block(P5, int(1024* alpha)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) maxpool1 = MaxPooling2D(pool_size=(13,13), strides=(1,1), padding='same')(P5) maxpool2 = MaxPooling2D(pool_size=(9,9), strides=(1,1), padding='same')(P5) maxpool3 = MaxPooling2D(pool_size=(5,5), strides=(1,1), padding='same')(P5) P5 = Concatenate()([maxpool1, maxpool2, maxpool3, P5]) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) P5 = _depthwise_conv_block(P5, int(1024* alpha)) P5 = DarknetConv2D_BN_Leaky(int(512* alpha), (1,1))(P5) P5_upsample = compose(DarknetConv2D_BN_Leaky(int(256* alpha), (1,1)), UpSampling2D(2))(P5) P4 = DarknetConv2D_BN_Leaky(int(256* alpha), (1,1))(feat2) P4 = Concatenate()([P4, P5_upsample]) P4 = make_five_convs(P4,int(256* alpha)) P4_upsample = compose(DarknetConv2D_BN_Leaky(int(128* alpha), (1,1)), UpSampling2D(2))(P4) P3 = DarknetConv2D_BN_Leaky(int(128* alpha), (1,1))(feat1) P3 = Concatenate()([P3, P4_upsample]) P3 = make_five_convs(P3,int(128* alpha)) #---------------------------------------------------# # 第三個特征層 # y3=(batch_size,52,52,3,85) #---------------------------------------------------# P3_output = _depthwise_conv_block(P3, int(256* alpha)) P3_output = DarknetConv2D(len(anchors_mask[0])*(num_classes+5), (1,1))(P3_output) P3_downsample = _depthwise_conv_block(P3, int(256* alpha), strides=(2,2)) P4 = Concatenate()([P3_downsample, P4]) P4 = make_five_convs(P4,int(256* alpha)) #---------------------------------------------------# # 第二個特征層 # y2=(batch_size,26,26,3,85) #---------------------------------------------------# P4_output = _depthwise_conv_block(P4, int(512* alpha)) P4_output = DarknetConv2D(len(anchors_mask[1])*(num_classes+5), (1,1))(P4_output) P4_downsample = _depthwise_conv_block(P4, int(512* alpha), strides=(2,2)) P5 = Concatenate()([P4_downsample, P5]) P5 = make_five_convs(P5,int(512* alpha)) #---------------------------------------------------# # 第一個特征層 # y1=(batch_size,13,13,3,85) #---------------------------------------------------# P5_output = _depthwise_conv_block(P5, int(1024* alpha)) P5_output = DarknetConv2D(len(anchors_mask[2])*(num_classes+5), (1,1))(P5_output) return Model(inputs, [P5_output, P4_output, P3_output])
以上就是python神經(jīng)網(wǎng)絡Keras GhostNet模型的復現(xiàn)詳解的詳細內(nèi)容,更多關(guān)于Keras GhostNet模型復現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!
- Python機器學習利用鳶尾花數(shù)據(jù)繪制ROC和AUC曲線
- 利用Python畫ROC曲線和AUC值計算
- 一文詳解Python灰色預測模型實現(xiàn)示例
- python回歸分析邏輯斯蒂模型之多分類任務詳解
- python深度學習tensorflow訓練好的模型進行圖像分類
- Python實現(xiàn)自動駕駛訓練模型
- python神經(jīng)網(wǎng)絡ShuffleNetV2模型復現(xiàn)詳解
- python神經(jīng)網(wǎng)絡Densenet模型復現(xiàn)詳解
- python神經(jīng)網(wǎng)絡MobileNetV3?small模型的復現(xiàn)詳解
- python神經(jīng)網(wǎng)絡MobileNetV3?large模型的復現(xiàn)詳解
- python神經(jīng)網(wǎng)絡Inception?ResnetV2模型復現(xiàn)詳解
- python模型性能ROC和AUC分析詳解
相關(guān)文章
python 實現(xiàn)圖片旋轉(zhuǎn) 上下左右 180度旋轉(zhuǎn)的示例
今天小編就為大家分享一篇python 實現(xiàn)圖片旋轉(zhuǎn) 上下左右 180度旋轉(zhuǎn)的示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-01-01Python 函數(shù)用法簡單示例【定義、參數(shù)、返回值、函數(shù)嵌套】
這篇文章主要介紹了Python 函數(shù)用法,結(jié)合實例形式分析了Python函數(shù)定義、參數(shù)、返回值及函數(shù)嵌套相關(guān)使用技巧,需要的朋友可以參考下2019-09-09Django 創(chuàng)建新App及其常用命令的實現(xiàn)方法
這篇文章主要介紹了Django 創(chuàng)建新App及其常用命令的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08Webots下載安裝?+?Pycharm聯(lián)調(diào)使用教程
Webots是一個開源的三維移動機器人模擬器,它最初是作為研究移動機器人中各種控制算法的研究工具開發(fā)的,自2018年12月起,Webots作為開源軟件發(fā)布,并獲得Apache 2.0許可證,這篇文章主要介紹了Webots下載安裝?+?Pycharm聯(lián)調(diào)?,需要的朋友可以參考下2023-02-02