深度学习入门笔记之VggNet网络

VGGNet是由牛津大学的视觉几何组(Visual Geometry Group)和谷歌旗下DeepMind团队的研究员共同研发提出的,获得了ILSVRC 2014( 2014年ImageNet图像分类竞赛) 的第二名,将 Top-5错误率降到7.3%, 在Top-5中取得了92.3%的正确率,同年的冠军是googlenet。
目前使用比较多的网络结构主要有ResNet(152-1000层),GooleNet(22层),VGGNet(19层),大多数模型都是基于这几个模型上改进,采用新的优化算法,多模型融合等。到目前为止,VGGNet 依然经常被用来提取图像特征。
一、简介
论文名为《Very Deep Convolutional Networks for Large-Scale Image Recognition》,论文地址:https://arxiv.org/pdf/1409.1556.pdf。 VGGNet论文给出了一个非常振奋人心的结论:卷积神经网络的深度增加和小卷积核的使用对网络的最终分类识别效果有很大的作用。
VGGNet探索了CNN的深度及其性能之间的关系,通过反复堆叠33的小型卷积核和22的最大池化层,VGGNet成功的构筑了16-19层深的CNN。
二、网络结构
网络结构如下图所示,其中D和E即为常用的VGG-16和VGG-19,前者拥有13个核大小均为3×3的卷积层、5个最大池化层和3个全连接层,后者拥有16个核大小均为3×3的卷积层、5个最大池化层和3个全连接层。本文主要针对VGG16进行解读,可以看出VGG19只是多了3个卷积层而已,其它的和VGG16没啥区别。
深度学习入门笔记之VggNet网络_第1张图片
表中的卷积层(conv3-kernels,其中kernels代表卷积核的个数)全部都是大小为3x3,步距为1,padding为1的卷积操作(经过卷积后不会改变特征矩阵的高和宽)。最大池化下采样层全部都是池化核大小为2,步距为2的池化操作,每次通过最大池化下采样后特征矩阵的高和宽都会缩减为原来的一半。
VGG-16的结构图如下图:
深度学习入门笔记之VggNet网络_第2张图片
VGG-16架构:13个卷积层+3个全连接层,前5段卷积网络(标号1-5),最后一段全连接网络(标号6-8),网络总共参数数量大约138M左右。。

(1) 输入层(Input):图像大小为224×224×3

(2) 卷积层1+ReLU:conv3 - 64(卷积核的数量):kernel size:3 stride:1 pad:1
像素:(224-3+2×1)/1+1=224 输出为224×224×64 64个feature maps
参数: (3×3×3)×64+64=1792

(3) 卷积层2+ReLU:conv3 - 64:kernel size:3 stride:1 pad:1
像素: (224-3+1×2)/1+1=224 输出为224×224×64 64个feature maps
参数: (3×3×64×64)+64=36928

(4) 最大池化层: pool2: kernel size:2 stride:2 pad:0
像素: (224-2)/2 = 112 输出为112×112×64,64个feature maps
参数: 0

(5) 卷积层3+ReLU:.conv3-128:kernel size:3 stride:1 pad:1
像素: (112-3+2×1)/1+1 = 112 输出为112×112×128,128个feature maps
参数: (3×3×64×128)+128=73856

(6) 卷积层4+ReLU:conv3-128:kernel size:3 stride:1 pad:1
像素: (112-3+2×1)/1+1 = 112 输出为112×112×128,128个feature maps
参数: (3×3×128×128)+128=147584

(7) 最大池化层:pool2: kernel size:2 stride:2 pad:0
像素: (112-2)/2+1=56 输出为56×56×128,128个feature maps。
参数: 0

(8) 卷积层5+ReLU:conv3-256: kernel size:3 stride:1 pad:1
像素: (56-3+2×1)/1+1=56 输出为56×56×256,256个feature maps
参数: (3×3×128×256)+256=295168

(9) 卷积层6+ReLU:conv3-256: kernel size:3 stride:1 pad:1
像素: (56-3+2×1)/1+1=56 输出为56×56×256,256个feature maps,
参数: (3×3×256×256)+256=590080

(10) 卷积层7+ReLU:conv3-256: kernel size:3 stride:1 pad:1
像素: (56-3+2×1)/1+1=56 输出为56×56×256,256个feature maps
参数: (3×3×256×256)+256=590080

(11) 最大池化层:pool2: kernel size:2 stride:2 pad:0
像素:(56 - 2)/2+1=28 输出为28×28×256,256个feature maps
参数: 0

(12) 卷积层8+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(28-3+2×1)/1+1=28 输出为28×28×512,512个feature maps
参数: (3×3×256×512)+512=1180160

(13) 卷积层9+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(28-3+2×1)/1+1=28 输出为28×28×512,512个feature maps
参数: (3×3×512×512)+512=2359808

(14) 卷积层10+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(28-3+2×1)/1+1=28 输出为28×28×512,512个feature maps
参数: (3×3×512×512)+512=2359808

(15) 最大池化层:pool2: kernel size:2 stride:2 pad:0,输出为14×14×512,512个feature maps。
像素:(28-2)/2+1=14 输出为14×14×512
参数: 0

(16) 卷积层11+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(14-3+2×1)/1+1=14 输出为14×14×512,512个feature maps
参数: (3×3×512×512)+512=2359808

(17) 卷积层12+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(14-3+2×1)/1+1=14 输出为14×14×512,512个feature maps
参数: (3×3×512×512)+512=2359808

(18) 卷积层13+ReLU:conv3-512:kernel size:3 stride:1 pad:1
像素:(14-3+2×1)/1+1=14 输出为14×14×512,512个feature maps,
参数: (3×3×512×512)+512=2359808

(19) 最大池化层:pool2:kernel size:2 stride:2 pad:0
像素:(14-2)/2+1=7 输出为7×7×512,512个feature maps
参数: 0

(20) 全连接层1+ReLU+Dropout:有4096个神经元或4096个feature maps
像素:1×1×4096
参数:7×7×512×4096 = 102760448

(21) 全连接层2+ReLU+Dropout:有4096个神经元或4096个feature maps
像素:1×1×4096
参数:4096×4096 = 16777216

(22) 全连接层3:有1000个神经元或1000个feature maps
像素:1×1×1000
参数:4096×1000=4096000

(23) 输出层(Softmax):输出识别结果,看它究竟是1000个可能类别中的哪一个。

train和predict的可视化结果如下图所示:
深度学习入门笔记之VggNet网络_第3张图片
三、VGGNet的特点
VGG网络的特点是利用小的尺寸核代替大的卷积核,然后把网络做深。
1. 结构简洁 卷积层+ReLU、最大池化层、全连接层、Softmax输出层。
VGGNet的结构十分简洁,由5个卷积层、3个全连接层和1个softmax层构成,**层与层之间使用最大池化连接,隐藏层之间使用的激活函数全都是ReLU。**并且网络的参数也是整齐划一的,赏心悦目。
2. 使用小卷积核
VGGNet使用含有多个小型的3×3卷积核的卷积层来代替AlexNet中的卷积核较大的卷积层。**2个3×3的卷积核堆叠的感受野相当于一个5×5的卷积核的感受野,而3个3×3的卷积核堆叠的感受野则相当于一个7×7的卷积核的感受野。**因此,采用多个小型卷积核,既能减少参数的数量,又能增强网络的非线性映射从而提升网络的表达能力。
深度学习入门笔记之VggNet网络_第4张图片

为什么可以增加网络的非线性?我们知道激活函数的作用就是给神经网络增加非线性因素,使其可以拟合任意的函数,每个卷积操作后都会通过ReLU激活,ReLU函数就是一个非线性函数。下图展示了为什么使用2个3x3的卷积核可以代替5×5卷积核。
深度学习入门笔记之VggNet网络_第5张图片
总结一下,使用多个3×3卷积堆叠的作用有两个:一是在不影响感受野的前提下减少了参数;二是增加了网络的非线性。
3. 使用小滤波器
与AlexNet相比,VGGNet在池化层全部采用的是2×2的小滤波器,stride为2。。
4. 通道数较多
VGGNet的第一层有64个通道,后面的每一层都对通道进行了翻倍,最多达到了512个通道( 64-128-256-512-512)。由于每个通道都代表着一个feature map,这样就使更多的信息可以被提取出来。
5 图像预处理
训练采用多尺度训练(Multi-scale),将原始图像缩放到不同尺寸 S,然后再随机裁切224x224的图片,并且对图片进行水平翻转和随机RGB色差调整,这样能增加很多数据量,对于防止模型过拟合有很不错的效果。
  初始对原始图片进行裁剪时,原始图片的最小边不宜过小,这样的话,裁剪到224x224的时候,就相当于几乎覆盖了整个图片,这样对原始图片进行不同的随机裁剪得到的图片就基本上没差别,就失去了增加数据集的意义,但同时也不宜过大,这样的话,裁剪到的图片只含有目标的一小部分,也不是很好。
针对上述裁剪的问题,提出的训练图片预处理过程:
(1)训练图片归一化,图像等轴重调(最短边为S)
等轴重调剪裁时的两种解决办法:
方法一:固定最小边的尺寸为256
方法二:随机从[256,512]的确定范围内进行抽样,这样原始图片尺寸不一,有利于训练,这个方法叫做尺度抖动,有利于训练集增强。 训练时运用大量的裁剪图片有利于提升识别精确率。
(2)随机剪裁(每SGD一次)
(3)随机水平翻转
(4)RGB颜色偏移
6. 将全连接层转换为卷积层
这个特征是体现在VGGNet的测试阶段。**在进行网络测试时,将训练阶段的3个全连接层替换为3个卷积层,使测试得到的网络没有全连接的限制,能够接收任意宽和高的输入。**如果后面3个层都是全连接层,那么在测试阶段就只能将测试的图像全部缩放到固定尺寸,这样就不便于多尺度测试工作的开展。
深度学习入门笔记之VggNet网络_第6张图片
为什么这样替换之后就可以处理任意尺寸的输入图像了呢?因为1×1卷积一个很重要的作用就是调整通道数。如果下一层输入的特征图需要控制通道数为N,那么设置N个1×1卷积核就可以完成通道数的调整。比如最后需要1000个神经元用于分出1000个类别,那就在最后一层的前面使用1000个1×1的卷积核,这样的到的结果就是(1, 1, 1000)正好可以匹配。
深度学习入门笔记之VggNet网络_第7张图片
7 参数和内存占用分析(来源[斯坦福大学CS231课程]课件截图):
深度学习入门笔记之VggNet网络_第8张图片
由上图分析可以看出:
前面部分的卷积层占用大量内存
后面的三层全连接层占用了大量的参数
四、代码(Pytorch)
在VGG网络中,有两部分,提取特征网络结构和分类网络结构。
VGG网络非常大,训练起来大概要几个小时,如果训练集样本很少比如三千多张,是没办法充分的训练VGG网络的。如果需要使用VGG网络,建议使用迁移学习方法训练自己的样本集。
model.py:

import torch.nn as nn
import torch

# official pretrain weights
model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
}


class VGG(nn.Module):
    #
    #num_classes=1000需要分类的类别个数
    #初始化函数__init__传进来features,也就是我们刚刚通过make_features(cfg: list)函数生成的提取特征网络结构
    #init_weights=False是否对网络进行权重初始化
    def __init__(self, features, num_classes=1000, init_weights=False):
        super(VGG, self).__init__()
        self.features = features
        #生成分类网络结构
        self.classifier = nn.Sequential(
            #输入节点个数,是展平处理之后所得到的1维向量的元素个数512*7*7
            nn.Linear(512*7*7, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, num_classes)
        )
        if init_weights:#是否要对网络进行参数的初始化
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.features(x)
        # N x 512 x 7 x 7
        # 图像经过提取特征网络结构之后,得到一个7*7*512的特征矩阵,如果...
        # 要和全连接层进行全连接,要进行一个展平处理。展平之后,才能和全连接层进行全连接
        x = torch.flatten(x, start_dim=1)#展平,从第一个维度展平
        # N x 512*7*7
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():#遍历网络中的每一层
            if isinstance(m, nn.Conv2d):
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.xavier_uniform_(m.weight)#初始化权重
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)

#参数是配置变量,是list类型,传入对应配置的列表就可以了
#make_features(cfg: list)这个函数用来生成提取网络结构
def make_features(cfg: list):
    layers = []#定义一个空列表,用来存放所创建的每一层结构
    in_channels = 3#输入的图片是RGB彩色图片
    for v in cfg:#通过一个for循环来遍历配置列表,能得到一个由卷积操作和池化操作组成的一个列表
        if v == "M":#如果当前的配置元素是一个M字符,说明该层是最大池化层
            # 创建一个最大池化下采样层,在VGG中,所有的最大池化下采样层的核大小都是2,stride都是2
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:#否则是卷积层
            #in_channels表示输入的特征矩阵的深度,v是输出的特征矩阵的深度,也就是卷积核的个数
            #在VGG中,所有的卷积层padding为1,stride为1
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)#创建一个卷积层
            # 将卷积层和ReLU拼接好后,放到layers层中
            layers += [conv2d, nn.ReLU(True)]
            in_channels = v
    #将列表通过非关键字参数的形式,传入进去。layers前面的*表示我们是通过非关键字参数传入进去的
    return nn.Sequential(*layers)

#定义cfgs一个字典文件,每一个key代表一个模型的配置文件,比如vgg11代表A配置,也就是11层的一个网络
#数字比如64代表卷基层的卷积核的个数,'M'代表池化层的结构
#有了这个配置,如何生成提取特征网络结构呢?定义了一个函数make_features(cfg: list)
cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

#实例化给定的配置模型
def vgg(model_name="vgg16", **kwargs):
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    cfg = cfgs[model_name]
    #实例化VGG网络
    #**kwargs表示可变长度的字典变量,是在调用VGG函数时传入的一个字典变量
    #这个字典变量包含了分类的个数,以及是否初始化权重的布尔变量
    model = VGG(make_features(cfg), **kwargs)
    return model

#在nn.Sequential(*layers)设置一个断点,可以实例化一个类来看一下
#vgg_model=vgg(model_name='vgg13')

五、注意事项和遇到的问题
(1) 我们通常看到别人在搭建VGG网络时,图像预处理的第一步会将图像的RGB分量分别减去[123.68, 116.78, 103.94]这三个参数。这三个参数是对应着ImageNet分类数据集中所有图像的R、G、B三个通到的均值分量。如果你要使用别人在ImageNet数据集上训练好的模型参数进行fine-trian操作(也就是迁移学习)那么你需要在在图像预处理过程中减去这[123.68, 116.78, 103.94]三个分量,如果你是从头训练一个数据集(不使用在ImageNet上的预训练模型)那么就可以忽略这一步。
(2)假如输入图像大小为nn,过滤器(filter)为ff,padding为p,步长(stride)为s,则输出大小为:如果商不是整数,向下取整,即floor函数。
在这里插入图片描述
(3)在特定的层使用了预训练得到的数据进行参数的初始化。对于较浅的网络,如网络A,可以直接使用随机数进行随机初始化,而对于比较深的网络,则使用前面已经训练好的较浅的网络中的参数值对其前几层的卷积层和最后的全连接层进行初始化。
(4)11的卷积层常被用来提炼特征,即多通道的特征组合在一起,凝练成较大通道或者较小通道的输出,而每张图片的大小不变。有时11的卷积神经网络还可以用来替代全连接层。
(5)模型训练方法:使用具有动量的小批量梯度下降优化多项式逻辑回归目标函数。
对于选择softmax分类器还是k个logistics分类器,取决于所有类别之间是否互斥。所有类别之间明显互斥用softmax;所有类别之间不互斥有交叉的情况下最好用k个logistics分类器。
(6)通过逐步增加网络深度来提高性能,虽然看起来有一点小暴力,没有特别多取巧的,但是确实有效,很多pretrained的方法就是使用VGG的model(主要是16和19),VGG相对其他的方法,参数空间很大,最终的model有500多m,alnext只有200m,googlenet更少,所以train一个vgg模型通常要花费更长的时间,所幸有公开的pretrained model让我们很方便的使用。
(7)虽然每一级网络逐渐变深,但是网络的参数量并没有增长很多(相对而言),这是因为参数量主要都消耗在最后3个全连接层。前面的卷积部分虽然很深,但是消耗的参数量不大,不过训练比较耗时的部分依然是卷积,因其计算量比较大。

你可能感兴趣的:(深度学习,深度学习,网络,cnn)