¿Qué es ResNet? Construya ResNet desde cero con Python

Contenidos

Este artículo fue publicado como parte del Blogatón de ciencia de datos

Introducción

Ha habido una serie de avances en el campo del aprendizaje profundo y la visión por computadora. Especialmente con la introducción de redes neuronales convolucionales muy profundas, estos modelos ayudaron a lograr resultados de vanguardia en problemas como el reconocimiento de imágenes y la clasificación de imágenes.

Entonces, a lo largo de los años, las arquitecturas de aprendizaje profundo se hicieron cada vez más profundas (agregando más capas) para resolver tareas cada vez más complejas, lo que también ayudó a mejorar el rendimiento de las tareas de clasificación y reconocimiento y también a hacerlas robustas.

Pero cuando seguimos agregando más capas a la red neuronal, se vuelve mucho más difícil de entrenar y la precisión del modelo comienza a saturarse y luego también se degrada. Aquí viene la ResNet para rescatarnos de ese escenario y ayuda a resolver este problema.

¿Qué es ResNet?

Residual Network (ResNet) es uno de los famosos modelos de aprendizaje profundo que introdujeron Shaoqing Ren, Kaiming He, Jian Sun y Xiangyu Zhang en su artículo. El documento se denominó «Aprendizaje residual profundo para el reconocimiento de imágenes». [1] en 2015. El modelo ResNet es uno de los modelos de aprendizaje profundo más populares y exitosos hasta ahora.

Bloques residuales

El problema de entrenar redes muy profundas se ha aliviado con la introducción de estos bloques residuales y el modelo ResNet se compone de estos bloques.

63071n1-8299125
Fuente: ‘Aprendizaje residual profundo para el reconocimiento de imágenes‘ papel

El problema de entrenar redes muy profundas se ha aliviado con la introducción de estos bloques residuales y el modelo ResNet se compone de estos bloques.

En la figura anterior, lo primero que podemos notar es que hay una conexión directa que omite algunas capas del modelo. Esta conexión se llama «conexión de salto» y es el corazón de los bloques residuales. La salida no es la misma debido a esta conexión de salto. Sin la conexión de salto, la entrada ‘X se multiplica por los pesos de la capa seguida de agregar un término de sesgo.

Luego viene la función de activación, f () y obtenemos la salida como H (x).

H (x) = f (wx + b) o H (x) = f (x)

Ahora, con la introducción de una nueva técnica de conexión de salto, la salida es H (x) se cambia a

H (x) = f (x) + x

Pero la dimensión de la entrada puede variar de la de la salida, lo que podría suceder con una capa convolucional o capas agrupadas. Por lo tanto, este problema se puede manejar con estos dos enfoques:

· Zero está acolchado con la conexión de salto para aumentar sus dimensiones.

· Se agregan capas convolucionales 1 × 1 a la entrada para que coincidan con las dimensiones. En tal caso, la salida es:

H (x) = f (x) + w1.x

Aquí se agrega un parámetro adicional w1 mientras que no se agrega ningún parámetro adicional cuando se usa el primer enfoque.

Esta técnica de omisión de conexiones en ResNet resuelve el problema de la desaparición del gradiente en las CNN profundas al permitir una ruta de acceso directo alternativa para que fluya el gradiente. Además, la conexión de omisión ayuda si alguna capa daña el rendimiento de la arquitectura, entonces se omitirá mediante la regularización.

Arquitectura de ResNet

Hay una red simple de 34 capas en la arquitectura que está inspirada en VGG-19 en la que se agregan la conexión de acceso directo o las conexiones de salto. Estas conexiones de salto o los bloques residuales luego convierten la arquitectura en la red residual como se muestra en la figura siguiente.

28984n2-1814961

Fuente: ‘Aprendizaje residual profundo para el reconocimiento de imágenes‘ papel

Usando ResNet con Keras:

Keras es una biblioteca de aprendizaje profundo de código abierto capaz de ejecutarse sobre TensorFlow. Keras Applications proporciona las siguientes versiones de ResNet.

– ResNet50

– ResNet50V2

– ResNet101

– ResNet101V2

– ResNet152

– ResNet152V2

Construyamos ResNet desde cero:

78567n3-1485933

Fuente: ‘Aprendizaje residual profundo para el reconocimiento de imágenes‘ papel

Conservemos la imagen de arriba como referencia y comencemos a construir la red.

La arquitectura de ResNet usa los bloques CNN varias veces, así que creemos una clase para el bloque CNN, que toma canales de entrada y canales de salida. Hay un batchnorm2d después de cada capa de conv.

import torch
import torch.nn as nn
class block(nn.Module):
    def __init__(
        self, in_channels, intermediate_channels, identity_downsample=None, stride=1
    ):
        super(block, self).__init__()
        self.expansion = 4
        self.conv1 = nn.Conv2d(
            in_channels, intermediate_channels, kernel_size=1, stride=1, padding=0, bias=False
        )
        self.bn1 = nn.BatchNorm2d(intermediate_channels)
        self.conv2 = nn.Conv2d(
            intermediate_channels,
            intermediate_channels,
            kernel_size=3,
            stride=stride,
            padding=1,
            bias=False
        )
        self.bn2 = nn.BatchNorm2d(intermediate_channels)
        self.conv3 = nn.Conv2d(
            intermediate_channels,
            intermediate_channels * self.expansion,
            kernel_size=1,
            stride=1,
            padding=0,
            bias=False
        )
        self.bn3 = nn.BatchNorm2d(intermediate_channels * self.expansion)
        self.relu = nn.ReLU()
        self.identity_downsample = identity_downsample
        self.stride = stride

    def forward(self, x):
        identity = x.clone()

        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.conv2(x)
        x = self.bn2(x)
        x = self.relu(x)
        x = self.conv3(x)
        x = self.bn3(x)

        if self.identity_downsample is not None:
            identity = self.identity_downsample(identity)

        x += identity
        x = self.relu(x)
        return x

Luego, cree una clase ResNet que tome la entrada de varios bloques, capas, canales de imagen y la cantidad de clases.

En el siguiente código, la función ‘_make_layer’
crea las capas ResNet, que toma la entrada de bloques, el número de residuales
bloques, canal de salida y zancadas.

class ResNet(nn.Module):
    def __init__(self, block, layers, image_channels, num_classes):
        super(ResNet, self).__init__()
        self.in_channels = 64
        self.conv1 = nn.Conv2d(image_channels, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
# Essentially the entire ResNet architecture are in these 4 lines below
self.layer1 = self._make_layer(
block, layers[0], intermediate_channels=64, stride=1
)
self.layer2 = self._make_layer(
block, layers[1], intermediate_channels=128, stride=2
)
self.layer3 = self._make_layer(
block, layers[2], intermediate_channels=256, stride=2
)
self.layer4 = self._make_layer(
block, layers[3], intermediate_channels=512, stride=2
)

self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(512 * 4, num_classes)

def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x = self.relu(x)
x = self.maxpool(x)
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)

x = self.avgpool(x)
x = x.reshape(x.shape[0], -1)
x = self.fc(x)

return x

def _make_layer(self, block, num_residual_blocks, intermediate_channels, stride):
identity_downsample = None
layers = []

# Either if we half the input space for ex, 56x56 -> 28x28 (stride=2), or channels changes
# we need to adapt the Identity (skip connection) so it will be able to be added
# to the layer that's ahead


if stride != 1 or self.in_channels != intermediate_channels * 4:
identity_downsample = nn.Sequential(
nn.Conv2d(
self.in_channels,
intermediate_channels * 4,
kernel_size=1,
stride=stride,
bias=False
),
nn.BatchNorm2d(intermediate_channels * 4),
)

layers.append(
block(self.in_channels, intermediate_channels, identity_downsample, stride)
)

# The expansion size is always 4 for ResNet 50,101,152
self.in_channels = intermediate_channels * 4

# For example for first resnet layer: 256 will be mapped to 64 as intermediate layer,
# then finally back to 256. Hence no identity downsample is needed, since stride = 1,
# and also same amount of channels.
for i in range(num_residual_blocks - 1):
layers.append(block(self.in_channels, intermediate_channels))

return nn.Sequential (* capas)

Luego defina diferentes versiones de ResNet

Para ResNet50, la secuencia de capas es [3, 4, 6, 3].

Para ResNet101, la secuencia de capas es [3, 4, 23, 3].

Para ResNet152, la secuencia de capas es [3, 8, 36, 3]. (consulte el Aprendizaje residual profundo para el reconocimiento de imágenes‘ papel)

def ResNet50(img_channel=3, num_classes=1000):
    return ResNet(block, [3, 4, 6, 3], img_channel, num_classes)
def ResNet101(img_channel=3, num_classes=1000):
return ResNet(block, [3, 4, 23, 3], img_channel, num_classes)


def ResNet152(img_channel=3, num_classes=1000):
return ResNet(block, [3, 8, 36, 3], img_channel, num_classes)

Luego, escriba un pequeño código de prueba para verificar si el modelo está funcionando bien.

def test():
    net = ResNet101(img_channel=3, num_classes=1000)
    device = "cuda" if torch.cuda.is_available() else "cpu"
    y = net(torch.randn(4, 3, 224, 224)).to(device)
    print(y.size())
test()

Para el caso de prueba anterior, la salida debe ser:

76052n4-1368070

Se puede acceder al código completo aquí:

https://github.com/BakingBrains/Deep_Learning_models_implementation_from-scratch_using_pytorch_/blob/main/ResNet_.py

[1]. Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun: Aprendizaje profundo residual para el reconocimiento de imágenes, diciembre de 2015, DOI: https://arxiv.org/abs/1512.03385

Gracias.

Tus sugerencias y dudas son bienvenidas aquí en la sección de comentarios. ¡Gracias por leer mi artículo!

Suscribite a nuestro Newsletter

No te enviaremos correo SPAM. Lo odiamos tanto como tú.