[ニューラルネットワーク](18)EfficientNetV2コードの再現、ネットワーク分析、完全なTensorflowコード

みなさん、こんにちは。今日は、Tensorflowを使用してEfficientNetV2畳み込みニューラルネットワークモデルを構築する方法を紹介します。

EfficientNetV2は、EfficientNetV1に基づいて改善され、Fused-MBConvモジュールとプログレッシブ学習戦略が導入され、トレーニングが高速化されました。この記事では、ネットワークモデルの構築方法のみを紹介し、トレーニングプロセスについては紹介しません。

EfficientNetV1の記事はこちらです。興味のある方はこちらをご覧ください:https ://blog.csdn.net/dgvv4/article/details/123553351

EfficientNetでは、作成者は精度、パラメーターの数、およびFLOPにさらに注意を払い、EfficientNetV2では、作成者はモデルのトレーニング速度にさらに注意を払います。


1. EfficientNetのデメリット:

(1)トレーニング画像のサイズが大きい場合、トレーニング速度は非常に遅くなります。

この問題を考えるより良い方法は、トレーニング画像のサイズを小さくすることです。ボリュームの周りのトレーニング画像のサイズは、トレーニング速度を上げるだけでなく、より大きなbatch_sizeを使用することもできます。

(2)ネットワークの浅い層で深さ方向の畳み込み(深さ方向)を使用する速度は遅くなります。

現在のDepthwiseConvolutionは、一部の既存のアクセラレータを使用できないためです。理論的な計算量は少ないですが、実際の使用は想像したほど速くはありません。そのため、作成者はFused-MBConvモジュールを導入し、ネットワークの浅いMBConvモジュールをFused-MBConvモジュールに置き換えました。

(3)各ステージの均等な増幅は最適ではありません

EfficientNetV1では、各ステージの奥行きと幅が等しく拡大されます。ネットワークのトレーニング速度に対する各ステージの寄与は同じではないため、同じスケーリング戦略を直接使用することは合理的ではありません。したがって、作成者はモデルをスケーリングするために不均一なスケーリング戦略を採用します。


2.EfficientNetV2の革新

(1)新しいネットワークEfficientNetV2を導入します。これは、トレーニング速度とパラメーターの数の点で、以前のいくつかのネットワークよりも優れています。

(2)画像のサイズに応じて正則化手法を動的に調整し、トレーニング速度と精度を向上させる改良型プログレッシブ学習手法を提案します。

(3)実験による以前のネットワークと比較して、トレーニング速度が11倍になり、パラメータの数が1/6.8に減少します。

EfficientNetV1との違い

(1)ネットワークの浅い層でFused-MBConvモジュールを使用し、深い層でMBConvモジュールを使用します

(2)より小さなチャネル上昇倍数を使用する

(3)より小さな畳み込みカーネルサイズを使用するためのバイアス(3 * 3)

(4) EfficientNetV1のストライド1の最後のステージを削除しました


3.ネットワークコアモジュール

深さ分離可能な畳み込み反転残差構造、SE注意メカニズムはこれ以上導入されません。これは以前の記事で何度か詳細に紹介されています。疑問がある場合は、https ://blog.csdn.net/dgvv4を参照してください。 / article / details / 123553351

3.1確率的深さ

このDopoutメソッドは、ランダムな確率でニューロンを殺す以前のDropoutメソッドとは異なります。下の図に示すように、順方向伝搬プロセスでは、多くの残差構造があり、メインブランチが畳み込み演算を実行し、ショートカット残差が入力と出力を接続します。

Stochastic Depthは、メインブランチの出力を破棄する確率があり、前のレイヤーの出力をこのレイヤーの出力として直接使用します。これは、そのようなレイヤーがない場合と同等であり、ネットワークの深さは、破棄されるレイヤーの数。EfficientNetV2のドロップアウト確率は0〜0.2です。

ここでの確率的深さタイプのドロップアウトレイヤーは、ネットワークの最後の完全に接続されたレイヤーのドロップアウトレイヤーを除いて、Fused-MBConvモジュールおよびMBConvモジュールのドロップアウトレイヤーにのみ使用されます

この方法により、トレーニング速度が向上し、精度がわずかに向上します。コードは、通常のドロップアウト関数よりも1つ多くのパラメーターを追加するだけで済みます。

x = layers.Dropout(rate = dropout_rate,  # 随机丢弃输出层的概率
                   noise_shape = (None,1,1,1))  # 代表不是杀死神经元,是丢弃输出层


3.2MBConvモジュール

基本モジュール(ストライド= 1):画像入力、最初に1x1畳み込みでチャネル数を増やし、次に高緯度空間で深度畳み込みを使用し、次にSEアテンションメカニズムで特徴マップデータを最適化し、次にチャネル数を減らします1x1畳み込み(線形活性化関数を使用)を介して;入力特徴マップの形状がこの時点で出力特徴マップの形状と同じである場合、確率的深さタイプのドロップアウトレイヤーを1x1畳み込み次元の後に特徴マップに追加しますオーバーフィットを防ぐための削減;差動接​​続の入力と出力

ダウンサンプリングモジュール(stride = 2):一般的なプロセスは基本モジュールと同じですが、ドロップアウトレイヤーと残差接続を使用せず、特徴マップは1x1畳み込み次元削減の後に直接出力されます。

コード:

#(3)逆转残差模块
def MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate):
    '''
    expansion: 第一个卷积层特征图通道数上升的倍数
    kernel_size: 深度卷积层的卷积核size
    stride: 深度卷积层的步长
    out_channel: 第二个卷积层下降的通道数
    dropout_rate: Dropout层随机丢弃输出层的概率,直接将输入接到输出    
    '''
    # 残差边
    residual = x
    
    # 输入特征图的通道数
    in_channel = x.shape[-1]
    
    # ① 1*1标准卷积升维
    x = conv_block(inputs = x, 
                   filters = in_channel * expansion,  # 上升通道数为expansion倍 
                   kernel_size = (1,1), 
                   stride = 1,
                   activation = True)
    
    # ② 3*3深度卷积
    x = layers.DepthwiseConv2D(kernel_size = kernel_size,
                               strides = stride,
                               padding = 'same',
                               use_bias = False)(x)
    
    x = layers.BatchNormalization()(x)
    
    x = swish(x)
    
    # ④ SE注意力机制,输入特征图x,和MBConv模块输入图像的通道数
    x = se_block(inputs = x, in_channel = in_channel)
    
    # ⑤ 1*1标准卷积降维,使用线性激活
    x = conv_block(inputs = x,
                   filters = out_channel,  # 上升通道数
                   kernel_size = (1,1),
                   stride = 1,
                   activation = False)  # 不使用swish激活
    
    # ⑥ 只有步长=1且输入等于输出shape,才使用残差连接输入和输出
    if stride == 1 and residual.shape == x.shape:
        
        # 判断是否进行dropout操作
        if dropout_rate > 0:
            
            # 参数noise_shape一定的概率将某一层的输出丢弃
            x = layers.Dropout(rate = dropout_rate,  # 丢弃概率
                               noise_shape = (None,1,1,1))
        
        # 残差连接输入和输出
        x = layers.Add([residual, x])
        
        return x
    
    # 如果步长=2,直接输出1*1卷积降维后的结果
    return x

3.3融合-MBconvモジュール

チャネル数を増やす必要はありません(拡張== 1):画像入力、3 * 3標準畳み込みの後、出力特徴マップに 確率的深さタイプのドロップアウトレイヤー を使用します。stride = 1で、モジュールの入力画像と畳み込みの出力画像の形状が同じである場合、入力と出力は残差によって接続されます。stride = 2のダウンサンプリングステージの場合、畳み込み出力の特徴マップは次のようになります。直接出力します。

必要な昇順チャネルの数(拡張!= 1):画像入力の場合、最初に3 * 3の標準畳み込み昇順チャネルを使用し、次に1 * 1畳み込み降順チャネルを使用し、入力特徴マップは 確率的深さタイプのドロップアウトレイヤー を通過します。stride=1でモジュールの入力画像が1*1畳み込み出力画像と同じ形状の場合、残差は入力と出力を接続するために使用されます。stride = 2ダウンサンプリングステージの場合、特徴マップは畳み込みが直接出力されます。

コード:

#(4)Fused-MBConv模块
def Fused_MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate):    

    # 残差边
    residual = x
    
    # 输入特征图的通道数
    in_channel = x.shape[-1]
    
    # ① 如果通道扩展倍数expansion==1,就不需要升维
    if expansion != 1:
        # 3*3标准卷积升维
        x = conv_block(inputs = x, 
                       filters = in_channel * expansion,  # 通道数上升为原来的expansion倍 
                       kernel_size = kernel_size, 
                       stride = stride)
    
    # ② 判断卷积的类型
    # 如果expansion==1,变成3*3卷积+BN+激活;
    # 如果expansion!=1,变成1*1卷积+BN,步长为1
    x = conv_block(inputs = x, 
                   filters = out_channel, # FusedMBConv模块输出特征图通道数
                   kernel_size = (1,1) if expansion != 1 else kernel_size, 
                   stride = 1 if expansion != 1 else stride,
                   activation = False if expansion != 1 else True)
    
    # ④ 当步长=1且输入输出shape相同时残差连接
    if stride == 1 and residual.shape == x.shape:
        
        # 判断是否使用Dropout层
        if dropout_rate > 0:
            x = layers.Dropout(rate = dropout_rate,  # 随机丢弃输出层的概率
                               noise_shape = (None,1,1,1))  # 代表不是杀死神经元,是丢弃输出层

        # 残差连接输入和输出
        outputs = layers.Add([residual, x])
        
        return outputs
    
    # 若步长等于2,直接输出卷积层输出结果
    return x

4.コード表示

4.1ネットワーク構造図

EfficientNetV2のネットワーク構造図は次のとおりです。opterator列で、MBConv4は、昇順チャネルの数が元の入力チャネルの数の4倍であることを表し、channelsは各モジュールの出力チャネルの数を表し、layersは各モジュールが繰り返される回数を表します。


4.2完全なコード

ネットワークを構築するための関数メソッド、コードは次のとおりです

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import Model, layers

#(1)swish激活函数
def swish(x):
    x = x * tf.nn.sigmoid(x)
    return x


#(2)标准卷积块
def conv_block(inputs, filters, kernel_size, stride, activation=True):
    
    # 卷积+BN+激活
    x = layers.Conv2D(filters = filters, 
                      kernel_size = kernel_size, 
                      strides = stride,
                      padding = 'same',
                      use_bias = False)(inputs)
    
    x = layers.BatchNormalization()(x)
    
    if activation:  # 如果activation==True就使用激活函数
        x = swish(x)
    
    return x


#(3)SE注意力机制
def se_block(inputs, in_channel, ratio=0.25):
    '''
    inputs: 深度卷积层的输出特征图
    input_channel: MBConv模块的输入特征图的通道数
    ratio: 第一个全连接层的通道数下降为MBConv输入特征图的几倍
    ''' 
    squeeze = int(in_channel * ratio)  # 第一个FC降低通道数个数
    excitation = inputs.shape[-1]  # 第二个FC上升通道数个数
    
    # 全局平均池化 [h,w,c]==>[None,c]
    x = layers.GlobalAveragePooling2D()(inputs)
    
    # [None,c]==>[1,1,c]
    x = layers.Reshape(target_shape=(1, 1, x.shape[-1]))(x)
    
    # [1,1,c]==>[1,1,c/4]
    x = layers.Conv2D(filters = squeeze, # 通道数下降1/4
                      kernel_size = (1,1),
                      strides = 1,
                      padding = 'same')(x)
    
    x = swish(x)  # swish激活
    
    # [1,1,c/4]==>[1,1,c]
    x = layers.Conv2D(filters = excitation,  # 通道数上升至原来
                      kernel_size = (1,1),
                      strides = 1,
                      padding = 'same')(x)
    
    x = tf.nn.sigmoid(x)  # sigmoid激活,权重归一化
    
    # [h,w,c] * [1,1,c] ==> [h,w,c]
    outputs = layers.multiply([inputs, x])
    
    return outputs


#(3)逆转残差模块
def MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate):
    '''
    expansion: 第一个卷积层特征图通道数上升的倍数
    kernel_size: 深度卷积层的卷积核size
    stride: 深度卷积层的步长
    out_channel: 第二个卷积层下降的通道数
    dropout_rate: Dropout层随机丢弃输出层的概率,直接将输入接到输出    
    '''
    # 残差边
    residual = x
    
    # 输入特征图的通道数
    in_channel = x.shape[-1]
    
    # ① 1*1标准卷积升维
    x = conv_block(inputs = x, 
                   filters = in_channel * expansion,  # 上升通道数为expansion倍 
                   kernel_size = (1,1), 
                   stride = 1,
                   activation = True)
    
    # ② 3*3深度卷积
    x = layers.DepthwiseConv2D(kernel_size = kernel_size,
                               strides = stride,
                               padding = 'same',
                               use_bias = False)(x)
    
    x = layers.BatchNormalization()(x)
    
    x = swish(x)
    
    # ④ SE注意力机制,输入特征图x,和MBConv模块输入图像的通道数
    x = se_block(inputs = x, in_channel = in_channel)
    
    # ⑤ 1*1标准卷积降维,使用线性激活
    x = conv_block(inputs = x,
                   filters = out_channel,  # 上升通道数
                   kernel_size = (1,1),
                   stride = 1,
                   activation = False)  # 不使用swish激活
    
    # ⑥ 只有步长=1且输入等于输出shape,才使用残差连接输入和输出
    if stride == 1 and residual.shape == x.shape:
        
        # 判断是否进行dropout操作
        if dropout_rate > 0:
            
            # 参数noise_shape一定的概率将某一层的输出丢弃
            x = layers.Dropout(rate = dropout_rate,  # 丢弃概率
                               noise_shape = (None,1,1,1))
        
        # 残差连接输入和输出
        x = layers.Add([residual, x])
        
        return x
    
    # 如果步长=2,直接输出1*1卷积降维后的结果
    return x
    

#(4)Fused-MBConv模块
def Fused_MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate):    

    # 残差边
    residual = x
    
    # 输入特征图的通道数
    in_channel = x.shape[-1]
    
    # ① 如果通道扩展倍数expansion==1,就不需要升维
    if expansion != 1:
        # 3*3标准卷积升维
        x = conv_block(inputs = x, 
                       filters = in_channel * expansion,  # 通道数上升为原来的expansion倍 
                       kernel_size = kernel_size, 
                       stride = stride)
    
    # ② 判断卷积的类型
    # 如果expansion==1,变成3*3卷积+BN+激活;
    # 如果expansion!=1,变成1*1卷积+BN,步长为1
    x = conv_block(inputs = x, 
                   filters = out_channel, # FusedMBConv模块输出特征图通道数
                   kernel_size = (1,1) if expansion != 1 else kernel_size, 
                   stride = 1 if expansion != 1 else stride,
                   activation = False if expansion != 1 else True)
    
    # ④ 当步长=1且输入输出shape相同时残差连接
    if stride == 1 and residual.shape == x.shape:
        
        # 判断是否使用Dropout层
        if dropout_rate > 0:
            x = layers.Dropout(rate = dropout_rate,  # 随机丢弃输出层的概率
                               noise_shape = (None,1,1,1))  # 代表不是杀死神经元,是丢弃输出层

        # 残差连接输入和输出
        outputs = layers.Add([residual, x])
        
        return outputs
    
    # 若步长等于2,直接输出卷积层输出结果
    return x


#(5)每个模块重复执行num次
# Fused_MBConv模块
def Fused_stage(x, num, expansion, kernel_size, stride, out_channel, dropout_rate):
    
    for _ in range(num):
        # 传入参数,反复调用Fused_MBConv模块
        x = Fused_MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate)
        
    return x

# MBConv模块
def stage(x, num, expansion, kernel_size, stride, out_channel, dropout_rate):
    
    for _ in range(num):
        # 反复执行MBConv模块
        x = MBConv(x, expansion, kernel_size, stride, out_channel, dropout_rate)
    
    return x


#(6)主干网络
def efficientnetv2(input_shape, classes, dropout_rate):
    
    # 构造输入层
    inputs = keras.Input(shape=input_shape)
    
    # 标准卷积层[224,224,3]==>[112,112,24]
    x = conv_block(inputs, filters=24, kernel_size=(3,3), stride=2)
    
    # [112,112,24]==>[112,112,24]
    x = Fused_stage(x, num=2, expansion=1, kernel_size=(3,3), 
                    stride=1, out_channel=24, dropout_rate=dropout_rate)
    
    # [112,112,24]==>[56,56,48]
    x = Fused_stage(x, num=4, expansion=4, kernel_size=(3,3), 
                    stride=2, out_channel=48, dropout_rate=dropout_rate)

    # [56,56,48]==>[32,32,64]
    x = Fused_stage(x, num=4, expansion=4, kernel_size=(3,3), 
                    stride=2, out_channel=64, dropout_rate=dropout_rate)
    
    # [32,32,64]==>[16,16,128]
    x = stage(x, num=6, expansion=4, kernel_size=(3,3), 
              stride=2, out_channel=128, dropout_rate=dropout_rate)

    # [16,16,128]==>[16,16,160]
    x = stage(x, num=9, expansion=6, kernel_size=(3,3), 
              stride=1, out_channel=160, dropout_rate=dropout_rate)

    # [16,16,160]==>[8,8,256]
    x = stage(x, num=15, expansion=6, kernel_size=(3,3), 
              stride=2, out_channel=256, dropout_rate=dropout_rate)

    # [8,8,256]==>[8,8,1280]
    x = conv_block(x, filters=1280, kernel_size=(1,1), stride=1)
    
    # [8,8,1280]==>[None,1280]
    x = layers.GlobalAveragePooling2D()(x)
    
    # dropout层随机杀死神经元
    if dropout_rate > 0:
        x = layers.Dropout(rate=dropout_rate)    
    
    # [None,1280]==>[None,classes]
    logits = layers.Dense(classes)(x)
    
    # 构建网络
    model = Model(inputs, logits)
    
    return model


#(7)接收网络模型
if __name__ == '__main__':

    model = efficientnetv2(input_shape = [224,224,3], # 输入图像shape 
                           classes = 1000, # 分类数 
                           dropout_rate = 0)
    
    model.summary()  # 查看网络架构

4.3ネットワーク構造を表示する

model.summary()を介してネットワークアーキテクチャを表示します。約2,000万のパラメータがあります

--------------------------------
dense (Dense)                   (None, 1000)         1281000     global_average_pooling2d_30[0][0]
==================================================================================================
Total params: 21,612,360
Trainable params: 21,458,488
Non-trainable params: 153,872
__________________________________________________________________________________________________

おすすめ

転載: blog.csdn.net/dgvv4/article/details/123598847