Mirando hacia atrás a ResNet: un paso clave en la historia del aprendizaje profundo

Cuando entré en contacto por primera vez con el aprendizaje profundo en 2020, aprendí la arquitectura de ResNet. Pero en ese momento, no le presté mucha atención a ResNet. No fue hasta más tarde, cuando realmente comencé a ponerme en contacto con los proyectos de investigación de CV, NLP y sincronización, incluido Graph, que me di cuenta del profundo impacto de ResNet. en todo el campo del aprendizaje profundo.

¿Qué estaba mal con las redes neuronales antes de ResNet?

  • Cuando el número de capas de la red neuronal es grande y la profundidad es muy profunda, es propensa a problemas fatales de desaparición de gradiente o explosión de gradiente, lo que hace que el proceso de entrenamiento del modelo no pueda continuar.
  • Cuando el número de capas de la red neuronal es grande y la profundidad es muy profunda, es posible que el modelo no pueda aprender la información real y efectiva, lo que resulta en un ajuste cada vez peor del objetivo, y la distancia desde el objetivo es cada vez mayor. más y más lejos. Como se muestra en la figura anterior, puede
    inserte la descripción de la imagen aquí
    ver: La imagen de la izquierda es el modelo de aprendizaje profundo anterior (como VGG). A medida que aumenta el número de capas del modelo, el modelo se ha desviado gradualmente del objetivo de ajuste. , por lo que el efecto de entrenamiento es cada vez peor, incluso peor que el obtenido con un pequeño número de capas. El efecto es bueno; la imagen de la derecha es el objetivo que ResNet espera alcanzar. A medida que aumenta el número de capas del modelo, el modelo se acerca gradualmente al objetivo que necesita ser ajustado.Aunque el efecto de ajuste tardío es lento, no hay problema de aumentar la desviación de ajuste.

Diseño estructural de ResNet
H ( x ) = F ( x ) + x H(x)=F(x)+xalto ( x )=F ( x )+X

donde H ( x ) H(x)H ( x ) es la salida observada de cada capa,F ( x ) F(x)F ( x ) es la capa de la red neuronal,xxx es la entrada a cada capa (llamada identidad). Esta conexión residual se convierte en una conexión saltada (skip connection) o un cortocircuito (shortcut).

inserte la descripción de la imagen aquí

Explicar la efectividad de ResNet desde un punto de vista funcional
puede explicar intuitivamente la efectividad de ResNet: incluso después de pasar F ( x ) F(x)La capa F ( x ) no aprende nada (ni siquiera las cosas afectadas negativamente), y el modelo también puede heredar la entradaxxinformación x . El lado derecho de la primera imagen se puede explicar intuitivamente, el modelo de cada capa puede garantizar que contiene completamente la información aprendida por el modelo de la capa anterior. Por tanto, a medida que se profundice el modelo, el efecto no se desviará del objetivo, al menos siempre aprenderá en base al anterior.

Explicación de la eficacia de ResNet desde la perspectiva de los residuos
Es muy intuitivo explicar la eficacia de ResNet desde la perspectiva de la función, pero en el artículo de Kaiming He no se explica de esta manera. Porque H ( x ) = F ( x ) + x H(x)=F(x)+xalto ( x )=F ( x )+x,故F ( x ) = H ( x ) − x F(x)=H(x)-xF ( x )=alto ( x )x , dondeF ( x ) F(x)F ( x ) es la diferencia entre la salida observada y la entrada de esta capa, a la que llamamos "Residual".
Luego, el objetivo de entrenamiento se convierte del objetivo de ajuste original al residuo de ajuste. Los residuos ajustados son beneficiosos aunqueF ( x ) F(x)F ( x ) no puede aprender cosas efectivas (o incluso aprender cosas con efectos negativos), y no se alejará gradualmente del objetivo debido a esto. También se puede decir que la desviación del modelo no será cada vez más grande. Al mismo tiempo, esta conexión de salto también evita el problema de formación de la desaparición de gradientes o la explosión de gradientes.
(Esta idea es muy similar a Impulsar en el aprendizaje integrado, como el árbol ascendente de gradiente de GBDT. Ambos son esencialmente residuos de ajuste, pero también hay diferencias: GBDT es la etiqueta de etiqueta adecuada y ResNet se ajusta a la función de mapa de características)

Diseño de arquitectura de ResNet La
siguiente figura muestra la diferencia entre la red neuronal convolucional tradicional y ResNet:
inserte la descripción de la imagen aquí
De acuerdo con el método de convolución, se divide principalmente en los siguientes dos tipos de ResNet:

  1. Conecte múltiples bloques ResNet con altura y ancho constantes (a la izquierda en la figura)
  2. Luego se conecta el bloque ResNet con la mitad de alto y ancho (stride=2), y se aumenta el número de canales a 2 veces, luego se introduce Conv1x1 para transformar el número de canales para que finalmente se puedan sumar (justo en la figura)
    inserte la descripción de la imagen aquí

Código clave de ResNet
Diseñe una red aquí: Al principio, es una convolución de conv7x7, sin cambiar la cantidad de canales. Cada módulo de acceso directo contiene dos capas de conv, y cada bloque de resnet contiene dos módulos de acceso directo anteriores. Excepto por el bloque conv7x7, el primer bloque resnet no transforma la cantidad de canales. En bloques posteriores de resnet, solo el primer módulo de acceso directo necesita duplicar la cantidad de canales. En el módulo que duplica el número de canales, shorcut necesita transformar el número de canales a través de una convolución conv1x1.

import torch.nn as nn
from torch.nn import functional as F
import torch


# 定义shortcut模块
class Residual(nn.Module):
    def __init__(self, input_channels, num_channel, use_conv1x1=False, stride=1):
        super().__init__()
        self.conv1 = nn.Conv2d(
            in_channels=input_channels, out_channels=num_channel, kernel_size=3, stride=stride, padding=1
        )
        self.conv2 = nn.Conv2d(
            in_channels=num_channel, out_channels=num_channel, kernel_size=3, stride=1, padding=1
        )
        if use_conv1x1:
            self.conv3 = nn.Conv2d(
                in_channels=input_channels, out_channels=num_channel, kernel_size=1, stride=stride
            )
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm2d(num_features=num_channel)
        self.bn2 = nn.BatchNorm2d(num_features=num_channel)
        # batch_normalization有自己的参数,所以不能像relu一样只定义一个
        self.relu = nn.ReLU(inplace=True)  # 不需要重新开内存去存变量,更节省内存

    def forward(self, x):
        y = self.conv1(x)
        y = self.bn1(y)
        y = self.relu(y)
        y = self.conv2(y)
        y = self.bn2(y)
        if self.conv3:
            x = self.conv3(x)
        y += x
        return F.relu(y)


## residual test
resblk1 = Residual(3, 3, use_conv1x1=False, stride=1)
x = torch.rand(4, 3, 6, 6)
y = resblk1(x)
print(y.shape)

# 通常feature map长宽减半,通道数翻倍
resblk2 = Residual(3, 6, use_conv1x1=True, stride=2)
x = torch.rand(4, 3, 6, 6)
y = resblk2(x)
print(y.shape)
## residual test


# 定义resnet网络块
def resnet_block(input_channels, num_channels, num_residuals, first_block=False):
    blks = []
    for i in range(num_residuals):
   		# 是renet块中的第一个(要改变通道数),同时它不是第一个块
        if i == 0 and not first_block:
            blks.append(
                Residual(input_channels=input_channels, num_channel=num_channels, use_conv1x1=True, stride=2)
            )
        else:
            blks.append(Residual(input_channels=num_channels, num_channel=num_channels, use_conv1x1=False, stride=1))
    return blks


b1 = nn.Sequential(
    nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=1),
    nn.BatchNorm2d(64),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
b3 = nn.Sequential(*resnet_block(64, 128, 2, first_block=False))
b4 = nn.Sequential(*resnet_block(128, 256, 2, first_block=False))
b5 = nn.Sequential(*resnet_block(256, 512, 2, first_block=False))
# 这里的*是指把list展开

net = nn.Sequential(
    b1, b2, b3, b4, b5,
    nn.AdaptiveAvgPool2d((1, 1)),
    nn.Flatten(),
    nn.Linear(512, 10)
)

x = torch.rand((1, 1, 224, 224))
for i, layer in enumerate(net):
    x = layer(x)
    print('layer:', i, layer.__class__.__name__, 'output shape:', x.shape)
torch.Size([4, 3, 6, 6])
torch.Size([4, 6, 3, 3])
layer: 0 Sequential output shape: torch.Size([1, 64, 55, 55])
layer: 1 Sequential output shape: torch.Size([1, 64, 55, 55])
layer: 2 Sequential output shape: torch.Size([1, 128, 28, 28])
layer: 3 Sequential output shape: torch.Size([1, 256, 14, 14])
layer: 4 Sequential output shape: torch.Size([1, 512, 7, 7])
layer: 5 AdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])
layer: 6 Flatten output shape: torch.Size([1, 512])
layer: 7 Linear output shape: torch.Size([1, 10])

Además de los comentarios en el código, tenga en cuenta lo siguiente al programar:

  • F.relu() es una llamada de función, generalmente utilizada en la salida final de la función de reenvío, nn.ReLU() es una llamada de módulo, generalmente utilizada al definir una red.
  • Porque la tasa de aprendizaje es mejor que la tasa de aprendizaje fija
  • ¿Será mayor la precisión del conjunto de prueba que la del conjunto de entrenamiento? De hecho, es posible, si se realiza una gran cantidad de aumento de datos en el conjunto de entrenamiento, entonces el conjunto de prueba puede ser más preciso y el conjunto de entrenamiento contiene ruido.

Supongo que te gusta

Origin blog.csdn.net/qq_16763983/article/details/126127458
Recomendado
Clasificación