Backbone-VGG

Backbone-VGG

1.介绍

VGG于2014年由牛津大学科学工程系Visual Geometry Group组提出的,作者在《Very Deep Convolutional Networks for Large-Scale Image Recognition》的abstract中提到,相比于2012年的AlexNet,VGG采用了多个小的卷积(3*3)核来代替AlexNet中的大卷积核(11*11和5*5)。

对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,这个很难用来理论解释,感性认识是,小卷积核加上激活函数属于非线性变换,多层非线性变换会比一层变换得到的特征更具有表达性,而且代价还比较小(参数更少)。简单来说,在VGG中,使用了3个3x3卷积核来代替7x7卷积核,使用了2个3x3卷积核来代替5*5卷积核,这样做的主要目的是在保证具有相同感知野的条件下,提升了网络的深度,在一定程度上提升了神经网络的效果。

比如,3个步长为1的3x3卷积核的一层层叠加作用可看成一个大小为7的感受野(其实就表示3个3x3连续卷积相当于一个7x7卷积),其参数总量为 3x(9xC^2) ,如果直接使用7x7卷积核,其参数总量为 49xC^2 ,这里 C 指的是输入和输出的通道数。很明显,27xC^2小于49xC^2,即减少了参数;而且3x3卷积核有利于更好地保持图像性质。

这里解释一下为什么当stride为1时,使用2个3x3卷积核可以来代替5*5卷积核。代替的意思是两者的感受野相同,下图很形象的解释了这个过程。绿色框卷积之后会形成相邻的三个像素,这三个像素会被一个紫色卷积核全部感受到。

在这里插入图片描述

2.网络结构

论文的第三页给出了网络的结构图。其中D列代表VGG16,E列代表VGG19,相比16多了三个卷积层。conv3-64代表着卷积核大小为3*3,共有64个卷积核。
在这里插入图片描述

下面是VGG16的结构图(网络上的图),很容易与上图D列相对应,这里就不过多阐述了。原先这里有个问题挺疑惑,上图中没有写maxpool的尺寸和各个层的尺寸,下面的的图中各层尺寸是如何得知的呢?再看论文发现,The convolution stride is fixed to 1 pixel; the padding is 1 pixel for 3 × 3 conv ;Max-pooling is performed over a 2 × 2 pixel window, with stride 2 . 这样就对了,卷积核3*3且padding为2时,输出尺寸不变;max-pooling输出长宽缩小为输入的1/2,没有问题。此外,论文中写到All hidden layers are equipped with the rectification (ReLU (Krizhevsky et al., 2012)) non-linearity ,即所有隐藏层都加Relu。

在这里插入图片描述

  1. 输入224x224x3的图片,经64个3x3的卷积核作两次卷积+ReLU,卷积后的尺寸变为224x224x64
  2. 作max pooling(最大化池化),池化单元尺寸为2x2(效果为图像尺寸减半),池化后的尺寸变为112x112x64
  3. 经128个3x3的卷积核作两次卷积+ReLU,尺寸变为112x112x128
  4. 作2x2的max pooling池化,尺寸变为56x56x128
  5. 经256个3x3的卷积核作三次卷积+ReLU,尺寸变为56x56x256
  6. 作2x2的max pooling池化,尺寸变为28x28x256
  7. 经512个3x3的卷积核作三次卷积+ReLU,尺寸变为28x28x512
  8. 作2x2的max pooling池化,尺寸变为14x14x512
  9. 经512个3x3的卷积核作三次卷积+ReLU,尺寸变为14x14x512
  10. 作2x2的max pooling池化,尺寸变为7x7x512
  11. 与两层1x1x4096,一层1x1x1000进行全连接+ReLU(共三层)
  12. 通过softmax输出1000个预测结果

3.代码实现

import torch.nn as nn
import torch

class VGG(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),

            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),

            nn.Linear(4096, num_classes),
            nn.ReLU(inplace=True),
            nn.Softmax(dim=1),
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), 512 * 7 * 7)
        x = self.classifier(x)
        return x


if __name__ == '__main__':
    # Example
    net = VGG()
    x = torch.rand(1, 3, 224, 224)
    out = net.forward(x)
    print(out.size())

4.心得

  1. 关于nn.SoftMax中dim的理解
  2. 发现一种很简介的网络结构定义方法
  3. python的一些特性
def foo(a, b, c):
    return a+b+c

if __name__ == '__main__':
    a = [1,2,3]
    print(foo(*[1,2,3]))	# 这两种表达是一样的
    print(foo(1,2,3))
  1. Python中的*args和**kwargs

    这个用法在2中用到了,文中在创造类的实例时用的。**kwargs可能是为了重写num_classes和init_weights用的吧。

    model = VGG(make_layers(cfg['A']), **kwargs)

    class VGG(nn.Module):
        def __init__(self, features, num_classes=1000, init_weights=True):
            super(VGG, self).__init__()
            self.features = features
            ......
    
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值