.. _sec_vgg:
Redes Usando Blocos (VGG)
=========================
Enquanto AlexNet ofereceu evidências empíricas de que CNNs profundas
pode alcançar bons resultados, não forneceu um modelo geral para
orientar os pesquisadores subsequentes na concepção de novas redes. Nas
seções a seguir, apresentaremos vários conceitos heurísticos comumente
usado para projetar redes profundas.
O progresso neste campo reflete aquele no design de chips onde os
engenheiros deixaram de colocar transistores para elementos lógicos para
blocos lógicos. Da mesma forma, o projeto de arquiteturas de rede neural
tornou-se progressivamente mais abstrato, com pesquisadores deixando de
pensar em termos de neurônios individuais para camadas inteiras, e agora
para blocos, repetindo padrões de camadas.
A ideia de usar blocos surgiu pela primeira vez a partir do `Grupo de
Geometria Visual `__ (VGG) na
Universidade de Oxford, em sua rede de mesmo nome *VGG*. É fácil
implementar essas estruturas repetidas no código com qualquer estrutura
moderna de aprendizado profundo usando loops e sub-rotinas.
VGG Blocks
----------
O bloco de construção básico das CNNs clássicas é uma sequência do
seguinte: (i) uma camada convolucional com preenchimento para manter a
resolução, (ii) uma não linearidade, como um ReLU, (iii) uma camada de
pooling tal como uma camada de pooling máxima. Um bloco VGG consiste em
uma sequência de camadas convolucionais, seguido por uma camada de
*pooling* máxima para *downsampling* espacial. No artigo VGG original
:cite:`Simonyan.Zisserman.2014`, Os autores convoluções empregadas com
:math:`3 \times 3` kernels com preenchimento de 1 (mantendo a altura e
largura) e :math:`2 \times 2` *pool* máximo com passo de 2 (reduzindo
pela metade a resolução após cada bloco). No código abaixo, definimos
uma função chamada ``vgg_block`` para implementar um bloco VGG.
: begin_tab: ``mxnet, tensorflow`` A função leva dois argumentos
correspondendo ao número de camadas convolucionais ``num_convs`` e o
número de canais de saída ``num_channels``. : end_tab:
: begin_tab: ``pytorch`` A função leva três argumentos correspondentes
ao número de camadas convolucionais ``num_convs``, o número de canais de
entrada ``in_channels`` e o número de canais de saída ``out_channels``.
: end_tab:
.. raw:: html
.. raw:: html
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l
npx.set_np()
def vgg_block(num_convs, num_channels):
blk = nn.Sequential()
for _ in range(num_convs):
blk.add(nn.Conv2D(num_channels, kernel_size=3,
padding=1, activation='relu'))
blk.add(nn.MaxPool2D(pool_size=2, strides=2))
return blk
.. raw:: html
.. raw:: html
.. code:: python
import torch
from torch import nn
from d2l import torch as d2l
def vgg_block(num_convs, in_channels, out_channels):
layers = []
for _ in range(num_convs):
layers.append(nn.Conv2d(in_channels, out_channels,
kernel_size=3, padding=1))
layers.append(nn.ReLU())
in_channels = out_channels
layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
return nn.Sequential(*layers)
.. raw:: html
.. raw:: html
.. code:: python
import tensorflow as tf
from d2l import tensorflow as d2l
def vgg_block(num_convs, num_channels):
blk = tf.keras.models.Sequential()
for _ in range(num_convs):
blk.add(tf.keras.layers.Conv2D(num_channels,kernel_size=3,
padding='same',activation='relu'))
blk.add(tf.keras.layers.MaxPool2D(pool_size=2, strides=2))
return blk
.. raw:: html
.. raw:: html
Camadas VGG
-----------
Como AlexNet e LeNet, a rede VGG pode ser dividida em duas partes: o
primeiro consistindo principalmente de camadas convolucionais e de
*pooling* e a segunda consistindo em camadas totalmente conectadas. Isso
é descrito em :numref:`fig_vgg`.
.. _fig_vgg:
.. figure:: ../img/vgg.svg
:width: 400px
De AlexNet a VGG que é projetado a partir de blocos de construção.
A parte convolucional da rede conecta vários blocos VGG de
:numref:`fig_vgg` (também definido na função\ ``vgg_block``) em
sucessão. A seguinte variável ``conv_arch`` consiste em uma lista de
tuplas (uma por bloco), onde cada um contém dois valores: o número de
camadas convolucionais e o número de canais de saída, quais são
precisamente os argumentos necessários para chamar a função
``vgg_block``. A parte totalmente conectada da rede VGG é idêntica à
coberta no AlexNet.
A rede VGG original tinha 5 blocos convolucionais, entre os quais os
dois primeiros têm uma camada convolucional cada e os três últimos
contêm duas camadas convolucionais cada. O primeiro bloco tem 64 canais
de saída e cada bloco subsequente dobra o número de canais de saída, até
que esse número chegue a 512. Uma vez que esta rede usa 8 camadas
convolucionais e 3 camadas totalmente conectadas, geralmente chamado de
VGG-11.
.. code:: python
conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))
O código a seguir implementa VGG-11. Esta é uma simples questão de
executar um loop for sobre ``conv_arch``.
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
net = nn.Sequential()
# A parte convolucional
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
# A parte totalmente conectada
net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
nn.Dense(10))
return net
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
conv_blks = []
in_channels = 1
# A parte convolucional
for (num_convs, out_channels) in conv_arch:
conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
in_channels = out_channels
return nn.Sequential(
*conv_blks, nn.Flatten(),
# A parte totalmente conectada
nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
nn.Linear(4096, 10))
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
def vgg(conv_arch):
net = tf.keras.models.Sequential()
# A parte convolucional
for (num_convs, num_channels) in conv_arch:
net.add(vgg_block(num_convs, num_channels))
# A parte totalmente conectada
net.add(tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(4096, activation='relu'),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Dense(10)]))
return net
net = vgg(conv_arch)
.. raw:: html
.. raw:: html
A seguir, construiremos um exemplo de dados de canal único com altura e
largura de 224 para observar a forma de saída de cada camada.
.. raw:: html
.. raw:: html
.. code:: python
net.initialize()
X = np.random.uniform(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.name, 'output shape:\t', X.shape)
.. parsed-literal::
:class: output
sequential1 output shape: (1, 64, 112, 112)
sequential2 output shape: (1, 128, 56, 56)
sequential3 output shape: (1, 256, 28, 28)
sequential4 output shape: (1, 512, 14, 14)
sequential5 output shape: (1, 512, 7, 7)
dense0 output shape: (1, 4096)
dropout0 output shape: (1, 4096)
dense1 output shape: (1, 4096)
dropout1 output shape: (1, 4096)
dense2 output shape: (1, 10)
.. raw:: html
.. raw:: html
.. code:: python
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
.. parsed-literal::
:class: output
Sequential output shape: torch.Size([1, 64, 112, 112])
Sequential output shape: torch.Size([1, 128, 56, 56])
Sequential output shape: torch.Size([1, 256, 28, 28])
Sequential output shape: torch.Size([1, 512, 14, 14])
Sequential output shape: torch.Size([1, 512, 7, 7])
Flatten output shape: torch.Size([1, 25088])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 4096])
ReLU output shape: torch.Size([1, 4096])
Dropout output shape: torch.Size([1, 4096])
Linear output shape: torch.Size([1, 10])
.. raw:: html
.. raw:: html
.. code:: python
X = tf.random.uniform((1, 224, 224, 1))
for blk in net.layers:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t', X.shape)
.. parsed-literal::
:class: output
Sequential output shape: (1, 112, 112, 64)
Sequential output shape: (1, 56, 56, 128)
Sequential output shape: (1, 28, 28, 256)
Sequential output shape: (1, 14, 14, 512)
Sequential output shape: (1, 7, 7, 512)
Sequential output shape: (1, 10)
.. raw:: html
.. raw:: html
Como você pode ver, dividimos a altura e a largura em cada bloco,
finalmente alcançando uma altura e largura de 7 antes de achatar as
representações para processamento pela parte totalmente conectada da
rede.
Treinamento
-----------
Como o VGG-11 é mais pesado em termos computacionais do que o AlexNet
construímos uma rede com um número menor de canais. Isso é mais do que
suficiente para o treinamento em Fashion-MNIST.
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
.. raw:: html
.. raw:: html
.. code:: python
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
# Lembre-se de que esta deve ser uma função que será passada para `d2l.train_ch6()`
# para que a construção/compilação do modelo precise estar dentro de `strategy.scope()`
# a fim de utilizar os dispositivos CPU/GPU que temos
net = lambda: vgg(small_conv_arch)
.. raw:: html
.. raw:: html
Além de usar uma taxa de aprendizado um pouco maior, o processo de
treinamento do modelo é semelhante ao do AlexNet em
:numref:`sec_alexnet`.
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.177, train acc 0.935, test acc 0.927
1791.5 examples/sec on gpu(0)
.. figure:: output_vgg_4a7574_53_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.169, train acc 0.938, test acc 0.923
2550.9 examples/sec on cuda:0
.. figure:: output_vgg_4a7574_56_1.svg
.. raw:: html
.. raw:: html
.. code:: python
lr, num_epochs, batch_size = 0.05, 10, 128
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=224)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
.. parsed-literal::
:class: output
loss 0.176, train acc 0.936, test acc 0.923
2931.3 examples/sec on /GPU:0
.. parsed-literal::
:class: output
.. figure:: output_vgg_4a7574_59_2.svg
.. raw:: html
.. raw:: html
Sumário
-------
- VGG-11 constrói uma rede usando blocos convolucionais reutilizáveis.
Diferentes modelos de VGG podem ser definidos pelas diferenças no
número de camadas convolucionais e canais de saída em cada bloco.
- O uso de blocos leva a representações muito compactas da definição da
rede. Ele permite um projeto eficiente de redes complexas.
- Em seu artigo VGG, Simonyan e Ziserman experimentaram várias
arquiteturas. Em particular, eles descobriram que várias camadas de
convoluções profundas e estreitas (ou seja, :math:`3 \times 3`) eram
mais eficazes do que menos camadas de convoluções mais largas.
Exercícios
----------
1. Ao imprimir as dimensões das camadas, vimos apenas 8 resultados, em
vez de 11. Para onde foram as informações das 3 camadas restantes?
2. Comparado com o AlexNet, o VGG é muito mais lento em termos de
computação e também precisa de mais memória GPU. Analise as razões
disso.
3. Tente alterar a altura e a largura das imagens no Fashion-MNIST de
224 para 96. Que influência isso tem nos experimentos?
4. Consulte a Tabela 1 no artigo VGG :cite:`Simonyan.Zisserman.2014`
para construir outros modelos comuns, como VGG-16 ou VGG-19.
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
`Discussions `__
.. raw:: html
.. raw:: html
.. raw:: html