7.2. Redes Usando Blocos (VGG)¶ Open the notebook in SageMaker Studio Lab
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.
7.2.1. 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
[Simonyan & Zisserman, 2014], Os autores convoluções empregadas com
\(3 \times 3\) kernels com preenchimento de 1 (mantendo a altura e
largura) e \(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:
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
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)
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
7.2.2. 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 Fig. 7.2.1.
Fig. 7.2.1 De AlexNet a VGG que é projetado a partir de blocos de construção.¶
A parte convolucional da rede conecta vários blocos VGG de
Fig. 7.2.1 (também definido na funçãovgg_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.
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
.
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)
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)
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)
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.
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)
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)
X = torch.randn(size=(1, 1, 224, 224))
for blk in net:
X = blk(X)
print(blk.__class__.__name__,'output shape:\t',X.shape)
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])
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)
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)
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.
7.2.3. 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.
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
ratio = 4
small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
net = vgg(small_conv_arch)
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)
Além de usar uma taxa de aprendizado um pouco maior, o processo de treinamento do modelo é semelhante ao do AlexNet em Section 7.1.
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())
loss 0.177, train acc 0.935, test acc 0.927
1791.5 examples/sec on gpu(0)
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())
loss 0.169, train acc 0.938, test acc 0.923
2550.9 examples/sec on cuda:0
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())
loss 0.176, train acc 0.936, test acc 0.923
2931.3 examples/sec on /GPU:0
<tensorflow.python.keras.engine.sequential.Sequential at 0x7f8405602460>
7.2.4. 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, \(3 \times 3\)) eram mais eficazes do que menos camadas de convoluções mais largas.
7.2.5. Exercícios¶
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?
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.
Tente alterar a altura e a largura das imagens no Fashion-MNIST de 224 para 96. Que influência isso tem nos experimentos?
Consulte a Tabela 1 no artigo VGG [Simonyan & Zisserman, 2014] para construir outros modelos comuns, como VGG-16 ou VGG-19.