.. _sec_googlenet: Redes com Concatenações Paralelas (GoogLeNet) ============================================= Em 2014, *GoogLeNet* venceu o ImageNet Challenge, propondo uma estrutura que combinou as forças de NiN e paradigmas de blocos repetidos :cite:`Szegedy.Liu.Jia.ea.2015`. Um dos focos do artigo foi abordar a questão dos quais tamanhos de núcleos de convolução são os melhores. Afinal, as redes populares anteriores empregavam escolhas tão pequenas quanto :math:`1 \times 1` e tão grande quanto :math:`11 \times 11`. Uma ideia neste artigo foi que às vezes pode ser vantajoso empregar uma combinação de grãos de vários tamanhos. Nesta seção, apresentaremos GoogLeNet, apresentando uma versão ligeiramente simplificada do modelo original: nós omitir alguns recursos ad-hoc que foram adicionados para estabilizar o treinamento mas são desnecessários agora com melhores algoritmos de treinamento disponíveis. Inception Blocks ---------------- O bloco convolucional básico no GoogLeNet é chamado de *bloco Inception*, provavelmente nomeado devido a uma citação do filme *Inception* (“Precisamos ir mais fundo”), que lançou um meme viral. .. _fig_inception: .. figure:: ../img/inception.svg Estrutura do bloco Inception. Conforme descrito em :numref:`fig_inception`, o bloco de iniciação consiste em quatro caminhos paralelos. Os primeiros três caminhos usam camadas convolucionais com tamanhos de janela de :math:`1\times 1`, :math:`3\times 3`, e :math:`5\times 5` para extrair informações de diferentes tamanhos espaciais. Os dois caminhos intermediários realizam uma convolução :math:`1\times 1` na entrada para reduzir o número de canais, diminuindo a complexidade do modelo. O quarto caminho usa uma camada de pooling máxima de :math:`3\times 3`, seguido por uma camada convolucional :math:`1\times 1` para alterar o número de canais. Todos os quatro caminhos usam preenchimento apropriado para dar à entrada e saída a mesma altura e largura. Finalmente, as saídas ao longo de cada caminho são concatenadas ao longo da dimensão do canal e compreendem a saída do bloco. Os hiperparâmetros comumente ajustados do bloco de início são o número de canais de saída por camada. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import np, npx from mxnet.gluon import nn from d2l import mxnet as d2l npx.set_np() class Inception(nn.Block): # `c1`--`c4` are the number of output channels for each path def __init__(self, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # Path 1 is a single 1 x 1 convolutional layer self.p1_1 = nn.Conv2D(c1, kernel_size=1, activation='relu') # Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3 # convolutional layer self.p2_1 = nn.Conv2D(c2[0], kernel_size=1, activation='relu') self.p2_2 = nn.Conv2D(c2[1], kernel_size=3, padding=1, activation='relu') # Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5 # convolutional layer self.p3_1 = nn.Conv2D(c3[0], kernel_size=1, activation='relu') self.p3_2 = nn.Conv2D(c3[1], kernel_size=5, padding=2, activation='relu') # Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1 # convolutional layer self.p4_1 = nn.MaxPool2D(pool_size=3, strides=1, padding=1) self.p4_2 = nn.Conv2D(c4, kernel_size=1, activation='relu') def forward(self, x): p1 = self.p1_1(x) p2 = self.p2_2(self.p2_1(x)) p3 = self.p3_2(self.p3_1(x)) p4 = self.p4_2(self.p4_1(x)) # Concatenate the outputs on the channel dimension return np.concatenate((p1, p2, p3, p4), axis=1) .. raw:: html
.. raw:: html
.. code:: python import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l class Inception(nn.Module): # `c1`--`c4` are the number of output channels for each path def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # Path 1 is a single 1 x 1 convolutional layer self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3 # convolutional layer self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5 # convolutional layer self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1 # convolutional layer self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # Concatenate the outputs on the channel dimension return torch.cat((p1, p2, p3, p4), dim=1) .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf from d2l import tensorflow as d2l class Inception(tf.keras.Model): # `c1`--`c4` are the number of output channels for each path def __init__(self, c1, c2, c3, c4): super().__init__() # Path 1 is a single 1 x 1 convolutional layer self.p1_1 = tf.keras.layers.Conv2D(c1, 1, activation='relu') # Path 2 is a 1 x 1 convolutional layer followed by a 3 x 3 # convolutional layer self.p2_1 = tf.keras.layers.Conv2D(c2[0], 1, activation='relu') self.p2_2 = tf.keras.layers.Conv2D(c2[1], 3, padding='same', activation='relu') # Path 3 is a 1 x 1 convolutional layer followed by a 5 x 5 # convolutional layer self.p3_1 = tf.keras.layers.Conv2D(c3[0], 1, activation='relu') self.p3_2 = tf.keras.layers.Conv2D(c3[1], 5, padding='same', activation='relu') # Path 4 is a 3 x 3 maximum pooling layer followed by a 1 x 1 # convolutional layer self.p4_1 = tf.keras.layers.MaxPool2D(3, 1, padding='same') self.p4_2 = tf.keras.layers.Conv2D(c4, 1, activation='relu') def call(self, x): p1 = self.p1_1(x) p2 = self.p2_2(self.p2_1(x)) p3 = self.p3_2(self.p3_1(x)) p4 = self.p4_2(self.p4_1(x)) # Concatenate the outputs on the channel dimension return tf.keras.layers.Concatenate()([p1, p2, p3, p4]) .. raw:: html
.. raw:: html
Para ter alguma intuição de por que essa rede funciona tão bem, considere a combinação dos filtros. Eles exploram a imagem em uma variedade de tamanhos de filtro. Isso significa que os detalhes em diferentes extensões pode ser reconhecido de forma eficiente por filtros de diferentes tamanhos. Ao mesmo tempo, podemos alocar diferentes quantidades de parâmetros para filtros diferentes. Modelo GoogLeNet ---------------- Conforme mostrado em :numref:`fig_inception_full`, GoogLeNet usa uma pilha de um total de 9 blocos iniciais e pooling médio global para gerar suas estimativas. O agrupamento máximo entre os blocos de iniciação reduz a dimensionalidade. O primeiro módulo é semelhante ao AlexNet e LeNet. A pilha de blocos é herdada de VGG e o pool de média global evita uma pilha de camadas totalmente conectadas no final. .. _fig_inception_full: .. figure:: ../img/inception-full.svg A arquitetura GoogLeNet. Agora podemos implementar o GoogLeNet peça por peça. O primeiro módulo usa uma camada convolucional :math:`7\times 7` de 64 canais. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python b1 = nn.Sequential() b1.add(nn.Conv2D(64, kernel_size=7, strides=2, padding=3, activation='relu'), nn.MaxPool2D(pool_size=3, strides=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python def b1(): return tf.keras.models.Sequential([ tf.keras.layers.Conv2D(64, 7, strides=2, padding='same', activation='relu'), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')]) .. raw:: html
.. raw:: html
O segundo módulo usa duas camadas convolucionais: primeiro, uma camada convolucional :math:`1\times 1` de 64 canais, em seguida, uma camada convolucional :math:`3\times 3` que triplica o número de canais. Isso corresponde ao segundo caminho no bloco *Inception*. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python b2 = nn.Sequential() b2.add(nn.Conv2D(64, kernel_size=1, activation='relu'), nn.Conv2D(192, kernel_size=3, padding=1, activation='relu'), nn.MaxPool2D(pool_size=3, strides=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python def b2(): return tf.keras.Sequential([ tf.keras.layers.Conv2D(64, 1, activation='relu'), tf.keras.layers.Conv2D(192, 3, padding='same', activation='relu'), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')]) .. raw:: html
.. raw:: html
O terceiro módulo conecta dois blocos de iniciação completos em série. O número de canais de saída do primeiro bloco de iniciação é :math:`64+128+32+32=256`, e a relação número de canal de saída entre os quatro caminhos está :math:`64:128:32:32=2:4:1:1`. O segundo e o terceiro caminhos reduzem primeiro o número de canais de entrada para :math:`96/192=1/2` e :math:`16/192=1/12`, respectivamente, e conecte a segunda camada convolucional. O número de canais de saída do segundo bloco de iniciação é aumentado para :math:`128+192+96+64=480`, e a proporção do número de canal de saída entre os quatro caminhos está :math:`128:192:96:64 = 4:6:3:2`. O segundo e o terceiro caminhos reduzem primeiro o número de canais de entrada a :math:`128/256=1/2` e :math:`32/256=1/8`, respectivamente. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python b3 = nn.Sequential() b3.add(Inception(64, (96, 128), (16, 32), 32), Inception(128, (128, 192), (32, 96), 64), nn.MaxPool2D(pool_size=3, strides=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python def b3(): return tf.keras.models.Sequential([ Inception(64, (96, 128), (16, 32), 32), Inception(128, (128, 192), (32, 96), 64), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')]) .. raw:: html
.. raw:: html
O quarto módulo é mais complicado. Ele conecta cinco blocos de iniciação em série, e eles têm :math:`192+208+48+64=512`, :math:`160+224+64+64=512`, :math:`128+256+64+64=512`, :math:`112+288+64+64=528`, e :math:`256+320+128+128=832` canais de saída, respectivamente. O número de canais atribuídos a esses caminhos é semelhante para aquele no terceiro módulo: o segundo caminho com a camada convolucional :math:`3\times 3` produz o maior número de canais, seguido pelo primeiro caminho com apenas a camada convolucional :math:`1\times 1`, o terceiro caminho com a camada convolucional :math:`5\times 5`, e o quarto caminho com a camada de pooling máxima :math:`3\times 3`. O segundo e terceiro caminhos irão primeiro reduzir o número de canais de acordo com a proporção. Essas proporções são ligeiramente diferentes em diferentes blocos *Inception*. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python b4 = nn.Sequential() b4.add(Inception(192, (96, 208), (16, 48), 64), Inception(160, (112, 224), (24, 64), 64), Inception(128, (128, 256), (24, 64), 64), Inception(112, (144, 288), (32, 64), 64), Inception(256, (160, 320), (32, 128), 128), nn.MaxPool2D(pool_size=3, strides=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) .. raw:: html
.. raw:: html
.. code:: python def b4(): return tf.keras.Sequential([ Inception(192, (96, 208), (16, 48), 64), Inception(160, (112, 224), (24, 64), 64), Inception(128, (128, 256), (24, 64), 64), Inception(112, (144, 288), (32, 64), 64), Inception(256, (160, 320), (32, 128), 128), tf.keras.layers.MaxPool2D(pool_size=3, strides=2, padding='same')]) .. raw:: html
.. raw:: html
O quinto módulo tem dois blocos de iniciação com :math:`256+320+128+128=832` e\ :math:`384+384+128+128=1024` canais de saída. O número de canais atribuídos a cada caminho é o mesmo que no terceiro e quarto módulos, mas difere em valores específicos. Deve-se notar que o quinto bloco é seguido pela camada de saída. Este bloco usa a camada de pooling média global para alterar a altura e largura de cada canal para 1, assim como em NiN. Por fim, transformamos a saída em uma matriz bidimensional seguido por uma camada totalmente conectada cujo número de saídas é o número de classes de rótulo. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python b5 = nn.Sequential() b5.add(Inception(256, (160, 320), (32, 128), 128), Inception(384, (192, 384), (48, 128), 128), nn.GlobalAvgPool2D()) net = nn.Sequential() net.add(b1, b2, b3, b4, b5, nn.Dense(10)) .. raw:: html
.. raw:: html
.. code:: python b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10)) .. raw:: html
.. raw:: html
.. code:: python def b5(): return tf.keras.Sequential([ Inception(256, (160, 320), (32, 128), 128), Inception(384, (192, 384), (48, 128), 128), tf.keras.layers.GlobalAvgPool2D(), tf.keras.layers.Flatten() ]) # Recall that this has to be a function that will be passed to # `d2l.train_ch6()` so that model building/compiling need to be within # `strategy.scope()` in order to utilize the CPU/GPU devices that we have def net(): return tf.keras.Sequential([b1(), b2(), b3(), b4(), b5(), tf.keras.layers.Dense(10)]) .. raw:: html
.. raw:: html
O modelo GoogLeNet é computacionalmente complexo, portanto, não é tão fácil modificar o número de canais como no VGG. Para ter um tempo de treinamento razoável no Fashion-MNIST, reduzimos a altura e largura de entrada de 224 para 96. Isso simplifica o cálculo. As mudanças na forma da saída entre os vários módulos são demonstrados abaixo. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python X = np.random.uniform(size=(1, 1, 96, 96)) net.initialize() for layer in net: X = layer(X) print(layer.name, 'output shape:\t', X.shape) .. parsed-literal:: :class: output sequential0 output shape: (1, 64, 24, 24) sequential1 output shape: (1, 192, 12, 12) sequential2 output shape: (1, 480, 6, 6) sequential3 output shape: (1, 832, 3, 3) sequential4 output shape: (1, 1024, 1, 1) dense0 output shape: (1, 10) .. raw:: html
.. raw:: html
.. code:: python X = torch.rand(size=(1, 1, 96, 96)) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape:\t', X.shape) .. parsed-literal:: :class: output Sequential output shape: torch.Size([1, 64, 24, 24]) Sequential output shape: torch.Size([1, 192, 12, 12]) Sequential output shape: torch.Size([1, 480, 6, 6]) Sequential output shape: torch.Size([1, 832, 3, 3]) Sequential output shape: torch.Size([1, 1024]) Linear output shape: torch.Size([1, 10]) .. raw:: html
.. raw:: html
.. code:: python X = tf.random.uniform(shape=(1, 96, 96, 1)) for layer in net().layers: X = layer(X) print(layer.__class__.__name__, 'output shape:\t', X.shape) .. parsed-literal:: :class: output Sequential output shape: (1, 24, 24, 64) Sequential output shape: (1, 12, 12, 192) Sequential output shape: (1, 6, 6, 480) Sequential output shape: (1, 3, 3, 832) Sequential output shape: (1, 1024) Dense output shape: (1, 10) .. raw:: html
.. raw:: html
Treinamento ----------- Como antes, treinamos nosso modelo usando o conjunto de dados Fashion-MNIST. Nós o transformamos em resolução de :math:`96 \times 96` pixels antes de invocar o procedimento de treinamento. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python lr, num_epochs, batch_size = 0.1, 10, 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.239, train acc 0.909, test acc 0.907 2236.9 examples/sec on gpu(0) .. figure:: output_googlenet_83a8b4_87_1.svg .. raw:: html
.. raw:: html
.. code:: python lr, num_epochs, batch_size = 0.1, 10, 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.246, train acc 0.905, test acc 0.899 3468.7 examples/sec on cuda:0 .. figure:: output_googlenet_83a8b4_90_1.svg .. raw:: html
.. raw:: html
.. code:: python lr, num_epochs, batch_size = 0.1, 10, 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu()) .. parsed-literal:: :class: output loss 0.229, train acc 0.914, test acc 0.906 3852.8 examples/sec on /GPU:0 .. parsed-literal:: :class: output .. figure:: output_googlenet_83a8b4_93_2.svg .. raw:: html
.. raw:: html
Sumário ------- - O bloco de iniciação é equivalente a uma sub-rede com quatro caminhos. Ele extrai informações em paralelo por meio de camadas convolucionais de diferentes formatos de janela e camadas de agrupamento máximo. As convoluções :math:`1 \times 1` reduzem a dimensionalidade do canal em um nível por pixel. O pool máximo reduz a resolução. - GoogLeNet conecta vários blocos de iniciação bem projetados com outras camadas em série. A proporção do número de canais atribuídos no bloco de iniciação é obtida por meio de um grande número de experimentos no conjunto de dados ImageNet. - GoogLeNet, assim como suas versões subsequentes, foi um dos modelos mais eficientes no ImageNet, fornecendo precisão de teste semelhante com menor complexidade computacional. Exercícios ---------- 1. Existem várias iterações do GoogLeNet. Tente implementá-los e executá-los. Alguns deles incluem o seguinte: - Adicione uma camada de normalização em lote :cite:`Ioffe.Szegedy.2015`, conforme descrito mais tarde em :numref:`sec_batch_norm`. - Faça ajustes no bloco de iniciação :cite:`Szegedy.Vanhoucke.Ioffe.ea.2016`. - Use suavização de rótulo para regularização de modelo :cite:`Szegedy.Vanhoucke.Ioffe.ea.2016`. - Incluir na conexão residual :cite:`Szegedy.Ioffe.Vanhoucke.ea.2017`, conforme descrito posteriormente em :numref:`sec_resnet`. 2. Qual é o tamanho mínimo de imagem para o GoogLeNet funcionar? 3. Compare os tamanhos dos parâmetros do modelo de AlexNet, VGG e NiN com GoogLeNet. Como as duas últimas arquiteturas de rede reduzem significativamente o tamanho do parâmetro do modelo? .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
`Discussão `__ .. raw:: html
.. raw:: html
`Discussão `__ .. raw:: html
.. raw:: html
`Discussão `__ .. raw:: html
.. raw:: html
.. raw:: html