VGG 经典神经网络学习笔记 (附代码)

论文地址:https://arxiv.org/abs/1409.1556

1.是什么?

VGG是一种深度卷积神经网络模型,由牛津大学的研究团队提出。它在2014年的ImageNet图像分类比赛中获得了第二名的好成绩,被广泛应用于计算机视觉领域。

VGG的特点是采用非常小的卷积核(3x3),但是网络非常深,有16-19层之多。这种设计可以增加网络的深度,提高特征提取的效果,同时减少了参数数量,降低了过拟合的风险。

VGG的网络结构非常简单,每个卷积层后面都跟着一个ReLU激活函数和一个2x2的最大池化层。最后是三个全连接层,其中前两个都有4096个神经元,最后一个则有1000个神经元,对应ImageNet数据集的1000个类别。

2.为什么?

VGG全称是Visual Geometry Group,因为是由Oxford的Visual Geometry Group提出的。AlexNet问世之后,很多学者通过改进AlexNet的网络结构来提高自己的准确率,主要有两个方向:小卷积核和多尺度。而VGG的作者们则选择了另外一个方向,即加深网络深度。

1)为什么使用2个3x3的卷积核代替一个5x5的卷积核?
首先很明显,2个3x3的卷积核包含的参数数量为18个(2x3x3),而一个5x5的卷积核包含的参数数量为25个。所以可以有效降低参数数量。

2)那么问题来了,2个3x3的卷积核可以替代一个5x5的卷积核吗?
当然可以,我们举一个最简单的栗子:
假设我们卷积核的步长为1,padding为0,输入的图形高度为n,那么我们用5x5的卷积核做一轮卷积运算,得到的图形高度为( ( n − 5 + 1 ) / 1 = n − 4 ) ((n-5+1)/1=n-4)((n−5+1)/1=n−4)
现在我们用3x3的卷积核对这个相同的输入进行卷积运算:1,第一次卷积得到的高度为( ( n − 3 + 1 ) / 1 = n − 2 ) ((n-3+1)/1=n-2)((n−3+1)/1=n−2),现在我们对这个结果再进行一次卷积运算,卷积核依然为3x3,此时得到的卷积高度为:( ( n − 2 − 3 + 1 ) / 1 = n − 4 ) ((n-2-3+1)/1=n-4)((n−2−3+1)/1=n−4) 显然,两次卷积结果是一样的。完美

3)为什么有时候会使用1x1的卷积核?
为了在输入和输出通道数不改变(不发生降维)的情况下实现线性变换。

3.怎么样

3.1网络结构图

以VGG16进行网络结构介绍,其他组类型大同小异。整个模型结构可分为两大部分:提取特征网络结构与分类网络结构

卷积层默认kernel_size=3,padding=1;池化层默认size=2,strider=2。下面进行结构分析:

输入图像尺寸为224*224*3

经过2层的64*3*3卷积核,即卷积两次,再经过ReLU激活,输出尺寸大小为224*224*64

经最大池化层(maxpooling),图像尺寸减半,输出尺寸大小为112*112*64

经过2层的128*3*3卷积核,即卷积两次,再经过ReLU激活,输出尺寸大小为112*112*128

经最大池化层(maxpooling),图像尺寸减半,输出尺寸大小为56*56*128

经过3层的256*3*3卷积核,即卷积三次,再经过ReLU激活,输出尺寸大小为56*56*256

经最大池化层(maxpooling),图像尺寸减半,输出尺寸大小为28*28*256

经过3层的512*3*3卷积核,即卷积三次,再经过ReLU激活,输出尺寸大小为28*28*512

经最大池化层(maxpooling),图像尺寸减半,输出尺寸大小为14*14*512

经过3层的512*3*3卷积核,即卷积三次,再经过ReLU激活,输出尺寸大小为14*14*512

经最大池化层(maxpooling),图像尺寸减半,输出尺寸大小为7*7*512

然后将feature map展平,输出一维尺寸为7*7*512=25088

经过2层的1*1*4096全连接层,经过ReLU激活,输出尺寸为1*1*4096

经过1层的1*1*1000全连接层(1000由最终分类数量决定,当年比赛需要分1000类)输出尺寸为1*1*1000,最后通过softmax输出1000个预测结果

结合下面这张图能更好感受

网络对应的参数两(VGG16)

 

3.2 卷积组

个人觉得卷积组是非常出彩的想法,在不增加计算量和保证感受野的情况下,获取了模型更深层次的信息,

示例:

  • input=8,3层conv3x3后,output=2,等同于1层conv7x7的结果;

  • input=8,2层conv3x3后,output=2,等同于2层conv5x5的结果。

简单来说: 

  1. 更多的激活函数、更丰富的特征,更强的辨别能力。卷积后都伴有激活函数,更多的卷积核的使用可使决策函数更加具有辨别能力,此外就卷积本身的作用而言,3x3比7x7就足以捕获特征的变化:3x3的9个格子,最中间的格子是一个感受野中心,可以捕获上下左右以及斜对角的特征变化。主要在于3个堆叠起来后,三个3x3近似一个7x7,网络深了两层且多出了两个非线性ReLU函数,(特征多样性和参数参数量的增大)使得网络容量更大(关于model capacity,AlexNet的作者认为可以用模型的深度和宽度来控制capacity),对于不同类别的区分能力更强(此外,从模型压缩角度也是要摒弃7x7,用更少的参数获得更深更宽的网络,也一定程度代表着模型容量,后人也认为更深更宽比矮胖的网络好);

  2. 卷积层的参数减少。相比5x5、7x7和11x11的大卷积核,3x3明显地减少了参数量,这点可以回过头去看上面的表格。比方input channel数和output channel数均为C,那么3层conv3x3卷积所需要的卷积层参数是:3x(Cx3x3xC)=27C^2,而一层conv7x7卷积所需要的卷积层参数是:Cx7x7xC=49C^2。conv7x7的卷积核参数比conv3x3多了(49-27)/27x100% ≈ 81%;

  3. 小卷积核代替大卷积核的正则作用带来性能提升。作者用三个conv3x3代替一个conv7x7,认为可以进一步分解(decomposition)原本用7x7大卷积核提到的特征,这里的分解是相对于同样大小的感受野来说的。

3.3 特点

  1. 卷积核变小。作者做的6组实验中,卷积核全部替换为3×3(极少用了1×1),选用更小卷积核的motivation是作者受到这两篇文章(Zeiler & Fergus, 2013; Sermanet et al., 2014)启发,使用更小的卷积核尺寸和stride得到性能提升;
  2. 层数更深更宽(11层、13层、16层、19层)。我认为作者是觉得:既然小卷积核带来性能提升,那么不妨试试深度对性能的影响,反正参数量我的gpu可以cover住。作者的实验也发现层数越深,带来的分类结果也越好,但并没有提到channel变宽这一个因素:6组实验中channel数都是逐层加宽的,如果单说深度对性能的影响而忽略宽度(这里宽度不是feature map的width而是depth),我觉得并不够convincing,应该再加入一下对宽度(channel)数分析对比的实验;
  3. 池化核变小且为偶数。AlexNet中的max-pool全是3×3的,但VGGNet中都是2×2的。作者没有说明选择这种size的考量(现在stride=2、以及2×2和3×3的pooling kernel选择的主流),我认为主要是2×2带来的信息损失相比3×3的比较小,相比3×3更容易捕获细小的特征变化起伏,此外或许是发现2×2的实验效果确实比3×3的好吧(毕竟这也是直接原因);
  4. 网络测试阶段将训练阶段的三个全连接替换为三个卷积。对于训练和测试一样的输入维度下,网络参数量没有变化,计算量也没有变化,思想来自OverFeat,1×1的卷积思想则来自NIN。优点在于全卷积网络可以接收任意尺度的输入

3.4 代码实现

# VGG16
class My_VGG16(nn.Module):
    def __init__(self,num_classes=5,init_weight=True):
        super(My_VGG16, self).__init__()
        # 特征提取层
        self.features = nn.Sequential(
            nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3,stride=1,padding=1),
            nn.Conv2d(in_channels=64,out_channels=64,kernel_size=3,stride=1,padding=1),
            nn.MaxPool2d(kernel_size=2,stride=2),
            nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(kernel_size=2,stride=2),
            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        # 分类层
        self.classifier = nn.Sequential(
            nn.Linear(in_features=7*7*512,out_features=4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(in_features=4096,out_features=4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(in_features=4096,out_features=num_classes)
        )

        # 参数初始化
        if init_weight: # 如果进行参数初始化
            for m in self.modules():  # 对于模型的每一层
                if isinstance(m, nn.Conv2d): # 如果是卷积层
                    # 使用kaiming初始化
                    nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu")
                    # 如果bias不为空,固定为0
                    if m.bias is not None:
                        nn.init.constant_(m.bias, 0)
                elif isinstance(m, nn.Linear):# 如果是线性层
                    # 正态初始化
                    nn.init.normal_(m.weight, 0, 0.01)
                    # bias则固定为0
                    nn.init.constant_(m.bias, 0)

    def forward(self,x):
        x = self.features(x)
        x = torch.flatten(x,1)
        result = self.classifier(x)
        return result

 

参考:

pytorch实战7:手把手教你基于pytorch实现VGG16

http://cs231n.stanford.edu/slides/2017/cs231n_2017_lecture9.pdf 

论文理解 - VGGNet - Very Deep Convolutional Networks for Large-Scale Image Recognition[转]

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值