[Red neuronal] (18) Reproducción de código EfficientNetV2, análisis de red, con código Tensorflow completo

Hola a todos, hoy compartiré con ustedes cómo usar Tensorflow para construir un modelo de red neuronal convolucional EfficientNetV2.

EfficientNetV2 se mejoró sobre la base de EfficientNetV1, introdujo el módulo Fused-MBConv y la estrategia de aprendizaje progresivo, y la capacitación es más rápida . Este artículo solo presenta cómo construir un modelo de red y no presenta el proceso de capacitación.

El artículo de EfficientNetV1 está aquí, puedes echarle un vistazo si te interesa: https://blog.csdn.net/dgvv4/article/details/123553351

En EfficientNet, el autor presta más atención a la precisión, la cantidad de parámetros y FLOP, y en EfficientNetV2, el autor presta más atención a la velocidad de entrenamiento del modelo.


1. Desventajas en EfficientNet:

(1) Cuando el tamaño de las imágenes de entrenamiento es grande, la velocidad de entrenamiento es muy lenta.

Una mejor manera de pensar en este problema es reducir el tamaño de las imágenes de entrenamiento. El tamaño de las imágenes de entrenamiento alrededor del volumen no solo puede acelerar la velocidad de entrenamiento, sino también usar un tamaño de lote más grande.

(2) La velocidad de uso de la convolución en profundidad (Depthwise) en las capas superficiales de la red será lenta.

Debido a que la convolución profunda actual no puede usar algunos aceleradores existentes. Aunque la cantidad de cálculo teórico es pequeña, el uso real no es tan rápido como se imagina. Por lo tanto, el autor introdujo el módulo Fused-MBConv y reemplazó el módulo MBConv poco profundo de la red con el módulo Fused-MBConv.

(3) La amplificación igual de cada etapa es subóptima

En EfficientNetV1, la profundidad y el ancho de cada etapa se amplían por igual. La contribución de cada etapa a la velocidad de entrenamiento de la red no es la misma, por lo que no es razonable utilizar directamente la misma estrategia de escalado. Por lo tanto , el autor adopta una estrategia de escalado no uniforme para escalar el modelo.


2. Innovaciones de EfficientNetV2

(1) Introducir una nueva red EfficientNetV2, que es superior a algunas redes anteriores en términos de velocidad de entrenamiento y número de parámetros.

(2) Se propone un método de aprendizaje progresivo mejorado, que ajusta dinámicamente el método de regularización según el tamaño de la imagen para mejorar la velocidad y la precisión del entrenamiento.

(3) En comparación con algunas redes anteriores a través de experimentos, la velocidad de entrenamiento aumenta 11 veces y la cantidad de parámetros se reduce en 1/6.8

Diferencia de EfficientNetV1

(1) Use el módulo Fused-MBConv en la capa superficial de la red y el módulo MBConv en la capa profunda

(2) Use un múltiplo de elevación de canal más pequeño

(3) Sesgo para usar un tamaño de núcleo de convolución más pequeño (3*3)

(4) Eliminada la última etapa con zancada 1 en EfficientNetV1


3. Módulo de núcleo de red

Convolución separable en profundidad , estructura residual inversa, mecanismo de atención SE ya no se presentará. Se ha presentado en detalle varias veces en artículos anteriores. Si tiene alguna duda, puede leer: https://blog.csdn.net/dgvv4 /artículo/detalles/123553351

3.1 Profundidad estocástica

Este método Dopout es diferente del método Dropout anterior que mata neuronas con probabilidad aleatoria . Como se muestra en la figura a continuación, en el proceso de propagación hacia adelante, hay muchas estructuras residuales, la rama principal realiza operaciones de convolución y el atajo residual conecta la entrada y la salida.

La profundidad estocástica tiene una cierta probabilidad de descartar la salida de la rama principal, y usa directamente la salida de la capa anterior como la salida de esta capa, lo que equivale a no tener tal capa, y la profundidad de la red se vuelve aleatoria , dependiendo de cuantas capas se desechan. La probabilidad de abandono en EfficientNetV2 es 0-0.2

La capa Dropout del tipo de profundidad estocástica aquí solo se usa para la capa Dropout en el módulo Fused-MBConv y el módulo MBConv, excluyendo la capa Dropout de la última capa completamente conectada de la red .

Este método mejora la velocidad de entrenamiento y mejora ligeramente la precisión. El código solo necesita agregar un parámetro más que la función de abandono normal.

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


Módulo de conversión de 3,2 MB

Módulo básico (stride = 1): entrada de imagen, primero aumente el número de canales a través de la convolución 1x1 ; luego use la convolución de profundidad en el espacio de alta latitud ; luego optimice los datos del mapa de características a través del mecanismo de atención SE ; y luego disminuya la cantidad de canales a través de la convolución 1x1 (usando una función de activación lineal) ; si la forma del mapa de características de entrada es la misma que la forma del mapa de características de salida en este momento, agregue una capa de exclusión del tipo de profundidad estocástica al mapa de características después de la dimensión de convolución 1x1 reducción para evitar el sobreajuste; entrada y salida de conexión diferencial

Módulo de reducción de resolución (stride=2): el proceso general es el mismo que el del módulo básico, sin el uso de la capa de abandono y la conexión residual, y el mapa de características se genera directamente después de la reducción de la dimensión convolucional 1x1.

Código:

#(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 Módulo MBconv fusionado

No es necesario aumentar la cantidad de canales (expansión == 1): entrada de imagen, después de una convolución estándar de 3*3, use la capa de abandono de tipo de profundidad estocástica para el mapa de características de salida  . Cuando zancada = 1 y la forma de la imagen de entrada del módulo y la imagen de salida de la convolución son iguales, la entrada y la salida están conectadas por residuos; cuando la etapa de reducción de resolución de zancada = 2 , el mapa de características de la salida de convolución es Salida directa. 

Número de canales ascendentes requeridos (expansión != 1): Para la entrada de imagen, primero use 3*3 canales ascendentes de convolución estándar , luego use 1*1 canales descendentes de convolución , y el mapa de características de entrada pasa por la  capa de deserción del tipo de profundidad estocástica  . Cuando la zancada = 1 y la imagen de entrada del módulo tiene la misma forma que la imagen de salida de convolución 1*1, el residuo se utiliza para conectar la entrada y la salida; cuando la etapa de reducción de la resolución de zancada = 2 , el mapa de características generado por el la convolución se emite directamente.

Código:

#(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. Visualización de código

4.1 Diagrama de estructura de red

El diagrama de estructura de la red EfficientNetV2 es el siguiente. En la columna de operadores, MBConv4 representa que el número de canales ascendentes es 4 veces el número de canales de entrada originales, los canales representan el número de canales de salida de cada módulo y las capas representan cuántas veces se repite cada módulo.


4.2 Código completo

Método de función para construir la red, el código es el siguiente

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 Ver la estructura de la red

Vea la arquitectura de la red a través de model.summary(), hay alrededor de 20 millones de parámetros

--------------------------------
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
__________________________________________________________________________________________________

Supongo que te gusta

Origin blog.csdn.net/dgvv4/article/details/123598847
Recomendado
Clasificación