Networks Using Blocks (VGG)

:label:sec_vgg

While AlexNet offered empirical evidence that deep CNNs can achieve good results, it did not provide a general template to guide subsequent researchers in designing new networks. In the following sections, we will introduce several heuristic concepts commonly used to design deep networks.

Progress in this field mirrors that in chip design where engineers went from placing transistors to logical elements to logic blocks. Similarly, the design of neural network architectures had grown progressively more abstract, with researchers moving from thinking in terms of individual neurons to whole layers, and now to blocks, repeating patterns of layers.

The idea of using blocks first emerged from the Visual Geometry Group (VGG) at Oxford University, in their eponymously-named VGG network. It is easy to implement these repeated structures in code with any modern deep learning framework by using loops and subroutines.

VGG Blocks

The basic building block of classic CNNs is a sequence of the following: (i) a convolutional layer with padding to maintain the resolution, (ii) a nonlinearity such as a ReLU, (iii) a pooling layer such as a max pooling layer. One VGG block consists of a sequence of convolutional layers, followed by a max pooling layer for spatial downsampling. In the original VGG paper :cite:Simonyan.Zisserman.2014, the authors employed convolutions with $3\times3$ kernels with padding of 1 (keeping height and width) and $2 \times 2$ max pooling with stride of 2 (halving the resolution after each block). In the code below, we define a function called vgg_block to implement one VGG block. The function takes two arguments corresponding to the number of convolutional layers num_convs and the number of output channels num_channels.

```{.python .input} from d2l import mxnet as d2l from mxnet import np, npx from mxnet.gluon import nn npx.set_np()

def vggblock(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

  1. ```{.python .input}
  2. #@tab pytorch
  3. from d2l import torch as d2l
  4. import torch
  5. from torch import nn
  6. def vgg_block(num_convs, in_channels, out_channels):
  7. layers=[]
  8. for _ in range(num_convs):
  9. layers.append(nn.Conv2d(in_channels, out_channels,
  10. kernel_size=3, padding=1))
  11. layers.append(nn.ReLU())
  12. in_channels = out_channels
  13. layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
  14. return nn.Sequential(*layers)

```{.python .input}

@tab tensorflow

from d2l import tensorflow as d2l import tensorflow as tf

def vggblock(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

  1. ## VGG Network
  2. Like AlexNet and LeNet,
  3. the VGG Network can be partitioned into two parts:
  4. the first consisting mostly of convolutional and pooling layers
  5. and the second consisting of fully-connected layers.
  6. This is depicted in :numref:`fig_vgg`.
  7. ![From AlexNet to VGG that is designed from building blocks.](/uploads/projects/d2l-ai-CN/img/vgg.svg)
  8. :width:`400px`
  9. :label:`fig_vgg`
  10. The convolutional part of the network connects several VGG blocks from :numref:`fig_vgg` (also defined in the `vgg_block` function)
  11. in succession.
  12. The following variable `conv_arch` consists of a list of tuples (one per block),
  13. where each contains two values: the number of convolutional layers
  14. and the number of output channels,
  15. which are precisely the arguments required to call
  16. the `vgg_block` function.
  17. The fully-connected part of the VGG network is identical to that covered in AlexNet.
  18. The original VGG network had 5 convolutional blocks,
  19. among which the first two have one convolutional layer each
  20. and the latter three contain two convolutional layers each.
  21. The first block has 64 output channels
  22. and each subsequent block doubles the number of output channels,
  23. until that number reaches 512.
  24. Since this network uses 8 convolutional layers
  25. and 3 fully-connected layers, it is often called VGG-11.
  26. ```{.python .input}
  27. #@tab all
  28. conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512))

The following code implements VGG-11. This is a simple matter of executing a for-loop over conv_arch.

```{.python .input} def vgg(conv_arch): net = nn.Sequential()

  1. # The convolutional part
  2. for (num_convs, num_channels) in conv_arch:
  3. net.add(vgg_block(num_convs, num_channels))
  4. # The fully-connected part
  5. net.add(nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
  6. nn.Dense(4096, activation='relu'), nn.Dropout(0.5),
  7. nn.Dense(10))
  8. return net

net = vgg(conv_arch)

  1. ```{.python .input}
  2. #@tab pytorch
  3. def vgg(conv_arch):
  4. # The convolutional part
  5. conv_blks=[]
  6. in_channels=1
  7. for (num_convs, out_channels) in conv_arch:
  8. conv_blks.append(vgg_block(num_convs, in_channels, out_channels))
  9. in_channels = out_channels
  10. return nn.Sequential(
  11. *conv_blks, nn.Flatten(),
  12. # The fully-connected part
  13. nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5),
  14. nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5),
  15. nn.Linear(4096, 10))
  16. net = vgg(conv_arch)

```{.python .input}

@tab tensorflow

def vgg(conv_arch): net = tf.keras.models.Sequential()

  1. # The convulational part
  2. for (num_convs, num_channels) in conv_arch:
  3. net.add(vgg_block(num_convs, num_channels))
  4. # The fully-connected part
  5. net.add(tf.keras.models.Sequential([
  6. tf.keras.layers.Flatten(),
  7. tf.keras.layers.Dense(4096, activation='relu'),
  8. tf.keras.layers.Dropout(0.5),
  9. tf.keras.layers.Dense(4096, activation='relu'),
  10. tf.keras.layers.Dropout(0.5),
  11. tf.keras.layers.Dense(10)]))
  12. return net

net = vgg(conv_arch)

  1. Next, we will construct a single-channel data example
  2. with a height and width of 224 to observe the output shape of each layer.
  3. ```{.python .input}
  4. net.initialize()
  5. X = np.random.uniform(size=(1, 1, 224, 224))
  6. for blk in net:
  7. X = blk(X)
  8. print(blk.name, 'output shape:\t', X.shape)

```{.python .input}

@tab pytorch

X = torch.randn(size=(1, 1, 224, 224)) for blk in net: X = blk(X) print(blk.class.name,’output shape:\t’,X.shape)

  1. ```{.python .input}
  2. #@tab tensorflow
  3. X = tf.random.uniform((1, 224, 224, 1))
  4. for blk in net.layers:
  5. X = blk(X)
  6. print(blk.__class__.__name__,'output shape:\t', X.shape)

As you can see, we halve height and width at each block, finally reaching a height and width of 7 before flattening the representations for processing by the fully-connected part of the network.

Training

Since VGG-11 is more computationally-heavy than AlexNet we construct a network with a smaller number of channels. This is more than sufficient for training on Fashion-MNIST.

```{.python .input}

@tab mxnet, pytorch

ratio = 4 small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch] net = vgg(small_conv_arch)

  1. ```{.python .input}
  2. #@tab tensorflow
  3. ratio = 4
  4. small_conv_arch = [(pair[0], pair[1] // ratio) for pair in conv_arch]
  5. # Recall that this has to be a function that will be passed to
  6. # `d2l.train_ch6()` so that model building/compiling need to be within
  7. # `strategy.scope()` in order to utilize the CPU/GPU devices that we have
  8. net = lambda: vgg(small_conv_arch)

Apart from using a slightly larger learning rate, the model training process is similar to that of AlexNet in :numref:sec_alexnet.

```{.python .input}

@tab all

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) ```

Summary

  • VGG-11 constructs a network using reusable convolutional blocks. Different VGG models can be defined by the differences in the number of convolutional layers and output channels in each block.
  • The use of blocks leads to very compact representations of the network definition. It allows for efficient design of complex networks.
  • In their VGG paper, Simonyan and Ziserman experimented with various architectures. In particular, they found that several layers of deep and narrow convolutions (i.e., $3 \times 3$) were more effective than fewer layers of wider convolutions.

Exercises

  1. When printing out the dimensions of the layers we only saw 8 results rather than 11. Where did the remaining 3 layer information go?
  2. Compared with AlexNet, VGG is much slower in terms of computation, and it also needs more GPU memory. Analyze the reasons for this.
  3. Try changing the height and width of the images in Fashion-MNIST from 224 to 96. What influence does this have on the experiments?
  4. Refer to Table 1 in the VGG paper :cite:Simonyan.Zisserman.2014 to construct other common models, such as VGG-16 or VGG-19.

:begin_tab:mxnet Discussions :end_tab:

:begin_tab:pytorch Discussions :end_tab:

:begin_tab:tensorflow Discussions :end_tab: