VGG块

VGG块在连续使用数个多个填充为1,窗口形状为VGG - 图1的卷积层后接上一个步幅,形状为2的池化层。输入在经过每个卷积层时形状都不变,因为之前提到过,要使输出形状不变的话填充行数应为VGG - 图2。所以形状为3的卷积核填充行数为2,那么也就是上下各增添一行,填充为1。

对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核优于采用大的卷积核,因为可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。例如,在VGG中,使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5*5卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果。

  1. import torch
  2. from torch import nn
  3. def vgg_block(num_convs, in_channels, out_channels):
  4. blk = []
  5. for i in range(num_convs):
  6. if i == 0:
  7. blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
  8. else:
  9. blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
  10. blk.append(nn.ReLU())
  11. blk.append(nn.MaxPool2d(kernel_size=2))
  12. return nn.Sequential(*blk)
  13. if __name__ == '__main__':
  14. print(vgg_block(5, 3, 5))
  15. 结果:
  16. Sequential(
  17. (0): Conv2d(3, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  18. (1): ReLU()
  19. (2): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  20. (3): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  21. (4): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  22. (5): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  23. (6): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  24. )

VGG网络

与AlexNet和LeNet一样,VGG网络由卷积层模块后接全连接层模块构成。卷积层模块串联数个vgg_block,其超参数由变量conv_arch定义。该变量指定了每个VGG块里卷积层个数和输入输出通道数。全连接模块则跟AlexNet中的一样。

  1. def vgg(conv_arch, fc_features, fc_hidden_units):
  2. net = []
  3. for _, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
  4. net.append(vgg_block(num_convs, in_channels, out_channels))
  5. net.append(nn.Sequential(FlattenLayer(),
  6. nn.Linear(fc_features, fc_hidden_units),
  7. nn.ReLU(),
  8. nn.Dropout(0.5),
  9. nn.Linear(fc_hidden_units, fc_hidden_units),
  10. nn.ReLU(),
  11. nn.Dropout(0.5),
  12. nn.Linear(fc_hidden_units, 10)))
  13. return nn.Sequential(*net)
  14. if __name__ == '__main__':
  15. conv_arch = ((1, 1, 64), (1, 64, 128), (2, 128, 256), (2, 256, 512), (2, 512, 512))
  16. # 经过5个vgg_block, 宽高会减半5次, 变成 224/32 = 7
  17. fc_features = 512 * 7 * 7 # c * w * h
  18. fc_hidden_units = 4096 # 任意
  19. net = vgg(conv_arch, fc_features, fc_hidden_units)
  20. print(net)
  21. X = torch.rand(1, 1, 224, 224)
  22. print(net(X))
  23. 结果:
  24. Sequential(
  25. (0): Sequential(
  26. (0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  27. (1): ReLU()
  28. (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  29. )
  30. (1): Sequential(
  31. (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  32. (1): ReLU()
  33. (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  34. )
  35. (2): Sequential(
  36. (0): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  37. (1): ReLU()
  38. (2): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  39. (3): ReLU()
  40. (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  41. )
  42. (3): Sequential(
  43. (0): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  44. (1): ReLU()
  45. (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  46. (3): ReLU()
  47. (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  48. )
  49. (4): Sequential(
  50. (0): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  51. (1): ReLU()
  52. (2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  53. (3): ReLU()
  54. (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  55. )
  56. (5): Sequential(
  57. (0): FlattenLayer()
  58. (1): Linear(in_features=25088, out_features=4096, bias=True)
  59. (2): ReLU()
  60. (3): Dropout(p=0.5, inplace=False)
  61. (4): Linear(in_features=4096, out_features=4096, bias=True)
  62. (5): ReLU()
  63. (6): Dropout(p=0.5, inplace=False)
  64. (7): Linear(in_features=4096, out_features=10, bias=True)
  65. )
  66. )
  67. tensor([[ 0.0160, -0.0046, 0.0058, -0.0016, 0.0075, -0.0157, -0.0055, -0.0055,
  68. 0.0034, 0.0153]], grad_fn=<AddmmBackward>)

VGG这种高和宽减半以及通道翻倍的设计使得多数卷积层都有相同的模型参数尺寸和计算复杂度。

训练模型

由于MNIST过于基础,可以适当减小网络规模:

  1. ratio = 8
  2. conv_arch = ((1, 1, 64//ratio), (1, 64//ratio, 128//ratio), (2, 128//ratio, 256//ratio), (2, 256//ratio, 512//ratio), (2, 512//ratio, 512//ratio))
  3. fc_features = fc_features//ratio
  4. fc_hidden_units = fc_hidden_units//ratio
  5. net = vgg(conv_arch, fc_features, fc_hidden_units)
  6. batch_size = 64
  7. train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
  8. lr, num_epochs = 0.001, 5
  9. optimizer = torch.optim.Adam(net.parameters(), lr=lr)
  10. train_ch5(net, train_iter, test_iter, batch_size, optimizer, device="cuda", num_epochs=num_epochs)

训练效果:

  1. training on cuda
  2. epoch 1, loss 0.6016, train acc 0.773, test acc 0.871, time 67.3 sec
  3. epoch 2, loss 0.3298, train acc 0.880, test acc 0.894, time 65.5 sec
  4. epoch 3, loss 0.2827, train acc 0.896, test acc 0.905, time 66.3 sec
  5. epoch 4, loss 0.2485, train acc 0.909, test acc 0.913, time 66.0 sec
  6. epoch 5, loss 0.2289, train acc 0.916, test acc 0.914, time 64.8 sec