【卷积神经网络结构专题】经典网络结构之VGG(附代码实现)

2020-06-04 14:46:19 浏览数 (1)

【导读】本文是卷积神经网络结构系列专题第三篇文章,前面我们先后介绍了LeNet和AlexNet。本篇博文将介绍一下在ImageNet 2014 年斩获目标定位竞赛的第一名,图像分类竞赛的第二名的网络结构---VGG。VGG 是 Visual Geometry Group 的缩写,是这个网络创建者的队名,作者来自牛津大学。

实验室地址:http://www.robots.ox.ac.uk/~vgg/

VGG论文

VGG 最大的特点就是它在之前的网络模型上,通过比较彻底地采用 3x3 尺寸的卷积核来堆叠神经网络,从而加深整个神经网络的层级。并且VGG论文给出了一个非常振奋人心的结论:卷积神经网络的深度增加和小卷积核的使用对网络的最终分类识别效果有很大的作用。

VGG网络结构

VGG 选择的是在 AlexNet 的基础上加深它的层数,但是它有个很显著的特征就是持续性的添加 3x3 的卷积核。而AlexNet 有 5 层卷积层,从上面的网络结构图我们可以看出来,VGG 就是针对这 5 层卷积层进行改造,共进行了 6 种配置。

下面是这六种配置的效果展示图:

从上面的效果图中,我们发现VGG19是最好的。但是,VGG-19 的参数比 VGG-16 的参数多了好多。由于VGG-19需要消耗更大的资源,因此实际中VGG-16使用得更多。而且VGG-16网络结构十分简单,并且很适合迁移学习,因此至今VGG-16仍在广泛使用,下面我们主要来讨论一下VGG16的网络结构:

VGG16的网络结构

从上图可以看到网络的第一个卷积层的通道数为,然后每一层Max Pooling之后卷积层的通道数都成倍的增加,最后界三个全连接层完成分类任务。总的来说VGGNet的贡献可以概括如下两点:

  • 所有隐藏层都使用了ReLU激活函数,而不是LRN(Local Response Normalization),因为LRN浪费了更多的内存和时间并且性能没有太大提升。
  • 使用更小的卷积核和更小的滑动步长。和AlexNet相比,VGG的卷积核大小只有和两种。卷积核的感受野很小,因此可以把网络加深,同时使用多个小卷积核使得网络总参数量也减少了。

VGG的创新点

1. 使用3x3卷积核替代7x7卷积核,这样做的好处是:

  • 3x3 卷积核是能够感受到上下、左右、重点的最小的感受野尺寸。并且,2 个 3x3 的卷积核叠加,它们的感受野等同于 1 个 5x5 的卷积核,3 个叠加后,它们的感受野等同于 1 个 7x7 的效果。
  • 由于感受野相同,3个3x3的卷积,使用了3个非线性激活函数,增加了非线性表达能力,使得分割平面更具有可分性。
  • 使用3x3卷积核可以减少参数,假设现在有 3 层 3x3 卷积核堆叠的卷积层,卷积核的通道是 C 个,那么它的参数总数是 3x(3Cx3C) = 27C^2 。同样和它感受野大小一样的一个卷积层,卷积核是 7x7 的尺寸,通道也是 C 个,那么它的参数总数就是 49C^2。而且通过上述方法网络层数还加深了。三层3x3的卷积核堆叠参数量比一层7x7的卷积核参数链还要少。
  • 总的来说,使用3x3卷积核堆叠的形式,既增加了网络层数又减少了参数量。

最初应用 1x1 卷积核的神经网络是 Network In Network。使用1x1卷积核的主要好处:

  1. 增加网络的深度,使用多个1x1卷积核,在保持feature map 尺寸不变(即不损失分辨率)的前提下,可以大幅增加非线性表达能力,把网络做得很deep。
  2. 进行卷积核通道数的降维和升维。
  3. 实现跨通道的交互和信息整合。

总结就是:1x1 卷积核的好处是不改变感受野的情况下,进行升维和降维,同时也加深了网络的深度。

详细的介绍请见:【基础积累】1x1卷积到底有哪些用处?

基于Pytorch的VGG代码实现

代码语言:javascript复制
class VGG16(torch.nn.Module):

    def __init__(self, num_classes):
        super(VGG16, self).__init__()
        
        # calculate same padding:
        # (w - k   2*p)/s   1 = o
        # => p = (s(o-1) - w   k)/2
        
        self.block_1 = nn.Sequential(
                nn.Conv2d(in_channels=3,
                          out_channels=64,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          # (1(32-1)- 32   3)/2 = 1
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=64,
                          out_channels=64,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=(2, 2),
                             stride=(2, 2))
        )
        
        self.block_2 = nn.Sequential(
                nn.Conv2d(in_channels=64,
                          out_channels=128,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=128,
                          out_channels=128,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=(2, 2),
                             stride=(2, 2))
        )
        
        self.block_3 = nn.Sequential(
                nn.Conv2d(in_channels=128,
                          out_channels=256,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=256,
                          out_channels=256,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=256,
                          out_channels=256,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=256,
                          out_channels=256,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=(2, 2),
                             stride=(2, 2))
        )
        
          
        self.block_4 = nn.Sequential(
                nn.Conv2d(in_channels=256,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=(2, 2),
                             stride=(2, 2))
        )
        
        self.block_5 = nn.Sequential(
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.Conv2d(in_channels=512,
                          out_channels=512,
                          kernel_size=(3, 3),
                          stride=(1, 1),
                          padding=1),
                nn.ReLU(),
                nn.MaxPool2d(kernel_size=(2, 2),
                             stride=(2, 2))
        )
        
        self.classifier = nn.Sequential(
                nn.Linear(512*2*2, 4096),
                nn.ReLU(),
                nn.Linear(4096, 4096),
                nn.ReLU(),
                nn.Linear(4096, num_classes)
        )
            
        
        for m in self.modules():
            if isinstance(m, torch.nn.Conv2d):
                #n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
                #m.weight.data.normal_(0, np.sqrt(2. / n))
                m.weight.detach().normal_(0, 0.05)
                if m.bias is not None:
                    m.bias.detach().zero_()
            elif isinstance(m, torch.nn.Linear):
                m.weight.detach().normal_(0, 0.05)
                m.bias.detach().detach().zero_()
        
        
    def forward(self, x):

        x = self.block_1(x)
        x = self.block_2(x)
        x = self.block_3(x)
        x = self.block_4(x)
        x = self.block_5(x)

        logits = self.classifier(x.view(-1, 512*2*2))
        probas = F.softmax(logits, dim=1)

        return logits, probas

参考链接:

1. https://blog.csdn.net/briblue/article/details/83792394

2. https://blog.csdn.net/briblue/article/details/83151475

3. https://blog.csdn.net/whz1861/article/details/78111606

0 人点赞