O que é ResNet? Construir ResNet do zero com Python

Conteúdo

Este artigo foi publicado como parte do Data Science Blogathon

Introdução

Houve uma série de avanços no campo do aprendizado profundo e visão computacional. Especialmente com a introdução de redes neurais convolucionais muito profundas, esses modelos ajudaram a obter resultados de ponta em problemas como reconhecimento e classificação de imagens.

Então, ao passar dos anos, as arquiteturas de aprendizagem profunda ficaram cada vez mais profundas (adicionando mais camadas) para resolver tarefas cada vez mais complexas, o que também ajudou a melhorar o desempenho das tarefas de classificação e reconhecimento e também a torná-las robustas.

Mas quando continuamos adicionando mais camadas à rede neural, torna-se muito mais difícil de treinar e a precisão do modelo começa a saturar e depois também degrada. Aí vem a ResNet para nos resgatar daquele cenário e ajudar a resolver esse problema.

O que é ResNet?

Rede Residual (ResNet) é um dos famosos modelos de aprendizagem profunda introduzidos por Shaoqing Ren, Kaiming He, Jian Sun e Xiangyu Zhang em seu artigo. O documento foi nomeado “Aprendizagem residual profunda para reconhecimento de imagem”. [1] sobre 2015. O modelo ResNet é um dos modelos de aprendizado profundo mais populares e bem-sucedidos até agora.

Blocos residuais

O problema de treinar redes muito profundas foi amenizado com a introdução desses blocos residuais e o modelo ResNet é composto por esses blocos.

63071n1-8299125
Fonte: ‘Aprendizagem residual profunda para reconhecimento de imagem‘ papel

O problema de treinar redes muito profundas foi amenizado com a introdução desses blocos residuais e o modelo ResNet é composto por esses blocos.

Na figura acima, a primeira coisa que podemos notar é que há uma conexão direta que omite algumas camadas do modelo. Esta conexão é chamada “conexão de salto” e é o coração dos blocos residuais. A saída não é a mesma devido a esta conexão de salto. Sem a conexão de salto, a entrada 'X é multiplicada pelos pesos da camada, seguido pela adição de um termo de inclinação.

Em seguida, vem a função de ativação, f () e obtemos a saída como H (x).

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

Agora, com a introdução de uma nova técnica de conexão de salto, a saída é H (x) muda para

H (x) = f (x) + x

Mas a dimensão da entrada pode variar daquela da saída, o que poderia acontecer a uma camada convolucional ou camadas agrupadas. Portanto, este problema pode ser tratado com essas duas abordagens:

· Zero é preenchido com a conexão de salto para aumentar suas dimensões.

· Camadas convolucionais são adicionadas 1 × 1 na entrada para combinar com as dimensões. Em tal caso, a saída é:

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

Aqui, um parâmetro extra w1 é adicionado enquanto nenhum parâmetro extra é adicionado ao usar a primeira abordagem.

Essa técnica de salto de conexão no ResNet resolve o problema do desaparecimento do gradiente em CNNs profundos, permitindo um caminho de atalho alternativo para o fluxo do gradiente.. O que mais, a conexão de bypass ajuda se alguma camada prejudicar o desempenho da arquitetura, então ele será ignorado pela regularização.

Arquitectura de ResNet

Existe uma rede simples de 34 camadas na arquitetura que é inspirada pelo VGG-19 em que conexão de acesso direto ou conexões de salto são adicionadas. Essas conexões de salto ou blocos residuais, então, convertem a arquitetura para a rede residual, conforme mostrado na figura abaixo.

28984n2-1814961

Fonte: ‘Aprendizagem residual profunda para reconhecimento de imagem‘ papel

Usando ResNet com Keras:

Keras é uma biblioteca de aprendizado profundo de código aberto capaz de funcionar no TensorFlow. Os aplicativos Keras fornecem as seguintes versões do ResNet.

– ResNet50

– ResNet50V2

– ResNet101

– ResNet101V2

– ResNet152

– ResNet152V2

Vamos construir o ResNet do zero:

78567n3-1485933

Fonte: ‘Aprendizagem residual profunda para reconhecimento de imagem‘ papel

Vamos manter a imagem acima para referência e começar a construir a rede..

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

import torch
import torch.nn as nn
bloco de classe(nn.Module):
    def __init__(
        auto, in_channels, intermediate_channels, identity_downsample=Nenhum, passo=1
    ):
        super(bloquear, auto).__iniciar__()
        auto.expansão = 4
        self.conv1 = nn. Conv2d(
            in_channels, intermediate_channels, kernel_size=1, passo=1, preenchimento = 0, viés=Falso
        )
        auto.bn1 = nn. BatchNorm2d(intermediate_channels)
        self.conv2 = nn. Conv2d(
            intermediate_channels,
            intermediate_channels,
            kernel_size = 3,
            passo=passo,
            preenchimento=1,
            viés=Falso
        )
        auto.bn2 = nn. BatchNorm2d(intermediate_channels)
        self.conv3 = nn. Conv2d(
            intermediate_channels,
            intermediate_channels * auto.expansão,
            kernel_size=1,
            passo=1,
            preenchimento = 0,
            viés=Falso
        )
        auto.bn3 = nn. BatchNorm2d(intermediate_channels * auto.expansão)
        self.relu = nn. ReLU()
        self.identity_downsample = identity_downsample
        self.stride = stride

    def forward(auto, x):
        identidade = 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)

        se self.identity_downsample não é Nenhum:
            identidade = self.identity_downsample(identidade)

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

Mais tarde, cree una clase ResNet que tome la entrada de varios bloques, capas, canais de imagem y la cantidad de clases.

No seguinte código, la función '_make_layer’
crea las capas ResNet, que toma la entrada de bloques, el número de resíduos
bloques, canal de salida y zancadas.

classe ResNet(nn.Module):
    def __init__(auto, bloquear, camadas, image_channels, num_classes):
        super(ResNet, auto).__iniciar__()
        self.in_channels = 64
        self.conv1 = nn. Conv2d(image_channels, 64, kernel_size=7, passo=2, preenchimento=3, viés=Falso)
        auto.bn1 = nn. BatchNorm2d(64)
        self.relu = nn. ReLU()
        self.maxpool = nn. MaxPool2d(kernel_size = 3, passo=2, preenchimento=1)
# Essencialmente, toda a arquitetura ResNet está nestas 4 lines below
self.layer1 = self._make_layer(
bloquear, camadas[0], intermediate_channels=64, passo=1
)
auto.layer2 = self._make_layer(
bloquear, camadas[1], intermediate_channels=128, passo=2
)
auto.layer3 = self._make_layer(
bloquear, camadas[2], intermediate_channels=256, passo=2
)
auto.layer4 = self._make_layer(
bloquear, camadas[3], intermediate_channels=512, passo=2
)

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

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

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

return x

def _make_layer(auto, bloquear, num_residual_blocks, intermediate_channels, passo):
identity_downsample = None
layers = []

# Ou se metade do espaço de entrada para ex, 56x56 -> 28x28 (passo=2), ou mudanças de canais
# precisamos adaptar a Identidade (pular conexão) por isso será capaz de ser adicionado
# to the layer that's ahead


if stride != 1 ou self.in_channels != intermediate_channels * 4:
identity_downsample = nn. Sequencial(
nn. Conv2d(
self.in_channels,
intermediate_channels * 4,
kernel_size=1,
passo=passo,
viés=Falso
),
nn. BatchNorm2d(intermediate_channels * 4),
)

camadas.append(
bloquear(self.in_channels, intermediate_channels, identity_downsample, passo)
)

# O tamanho da expansão é sempre 4 para ResNet 50,101,152
self.in_channels = intermediate_channels * 4

# Por exemplo, para a primeira camada de resnet: 256 será mapeado para 64 como camada intermediária,
# em seguida, finalmente de volta para 256. Portanto, não é necessário diminuir a identidade, desde o passo = 1,
# e também a mesma quantidade de canais.
para eu no alcance(num_residual_blocks - 1):
camadas.append(bloquear(self.in_channels, intermediate_channels))

retorno nn. Sequencial (* capas)

Luego defina diferentes versões de ResNet

Para ResNet50, a sequência de camadas é [3, 4, 6, 3].

Para ResNet101, a sequência de camadas é [3, 4, 23, 3].

Para ResNet152, a sequência de camadas é [3, 8, 36, 3]. (Pedir ao Aprendizagem residual profunda para reconhecimento de imagem‘ papel)

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


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

Mais tarde, escreva um pequeno código de teste para verificar se o modelo está funcionando bem.

teste de def():
    net = ResNet101(img_channel = 3, num_classes = 1000)
    dispositivo = "milagres" se torch.cuda.is_available() outro "CPU"
    y = net(torch.randn(4, 3, 224, 224)).para(dispositivo)
    imprimir(y.size())
teste()

Para o caso de teste acima, a saída deve ser:

76052n4-1368070

O código completo pode ser acessado aqui:

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: Aprendizagem profunda residual para reconhecimento de imagem, Dezembro de 2015, DOI: https://arxiv.org/abs/1512.03385

Obrigado.

Suas sugestões e dúvidas são bem-vindas aqui na seção de comentários. Obrigado por ler o meu artigo!

Assine a nossa newsletter

Nós não enviaremos SPAM para você. Nós odiamos isso tanto quanto você.