经典卷积神经网络-VGGNet
一、背景介绍
VGG是Oxford的Visual Geometry Group的组提出的。该网络是在ILSVRC 2014上的相关工作,主要工作是证明了增加网络的深度能够在一定程度上影响网络最终的性能。VGG有两种结构,分别是VGG16和VGG19,两者并没有本质上的区别,只是网络深度不一样。
二、VGG-16网络结构
其中VGG系列具体的网络结构如下表所示:
如图所示,这是论文中所有VGG网络的详细信息,D列对应的为VGG-16网络。16指的是在这个网络中包含16个卷积层和全连接层(不算池化层和Softmax)。
-
VGG-16的卷积层没有那么多的超参数,在整个网络模型中,所有卷积核的大小都是 3 × 3的,并且padding为same,stride为1。所有池化层的池化核大小都是 2 × 2 的,并且步长为2。在几次卷积之后紧跟着池化,整个网络结构很规整。
-
总共包含约1.38亿个参数,但其结构并不复杂,结构很规整,都是几个卷积层后面跟着可以压缩图像大小的池化层,同时,卷积层的卷积核数量的变化也存在一定的规律,都是池化之后图像高度宽度减半,但在下一个卷积层中通道数翻倍,这正是这种简单网络结构的一个规则。
-
VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5)。对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。在VGG中,使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5×5卷积核,这样做的主要目的是在保证具有相同感受野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果。
-
它的主要缺点就是需要训练的特征数量非常大。有些文章介绍了VGG-19,但通过研究发现VGG-19和VGG-16的性能表现几乎不分高下,所以很多人还是使用VGG-16,这也说明了单纯的增加网络深度,其性能不会有太大的提升。
-
论文中还介绍了权重初始化方法,即预训练低层模型参数为深层模型参数初始化赋值。原文:网络权重初始化是非常重要的,坏的初始化会使得深度网络的梯度的不稳定导致无法学习。为了解决这个问题,我们首先在网络A中使用随机初始化进行训练。然后到训练更深的结构时,我们将第一层卷积层和最后三层全连接层的参数用网络A中的参数初始化(中间层的参数随机初始化)。
-
论文中揭示了,随着网络深度的增加,图像的高度和宽度都以一定规律不断缩小,每次池化之后刚好缩小一半,而通道数量在不断增加,而且刚好也是在每组卷积操作后增加一倍。也就是说,图像缩小和通道增加的比例是有规律的,从这个角度看,这篇论文很吸引人。
三、VGG-16的Pytorch实现
我们可以根据:https://dgschwend.github.io/netscope/#/preset/vgg-16,来搭建VGG-16。
后面要将VGG-16Net应用到CIFAR10数据集上,所以对网络做了一些修改,具体代码如下:
from torch import nn
class Vgg16_Net(nn.Module):
def __init__(self):
super(Vgg16_Net, self).__init__()
self.layer1 = nn.Sequential(
# input_size = (3, 32, 32)
nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
# input_size = (64, 32, 32)
nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
# input_size = (64, 32, 32)
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.layer2 = nn.Sequential(
# input_size = (64, 16, 16)
nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
# input_size = (128, 16, 16)
nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
# input_size = (128, 16, 16)
nn.MaxPool2d(2, 2)
)
self.layer3 = nn.Sequential(
# input_size = (128, 8, 8)
nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2d