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.
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.
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:
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:
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!