VGGnet原理分析理解

大数据实验室第11次打卡

一、简介

VGGNet由牛津大学的视觉几何组(Visual Geometry Group)和Google DeepMind公司的研究员共同提出,是ILSVRC-2014中定位任务第一名和分类任务第二名。VGG相应的论文“Very Deep Convolutional Networks for Large-Scale Image Recognition”,下载地址:https://arxiv.org/abs/1409.1556

二、特点

  • 小卷积核。作者将卷积核全部替换为3x3(极少用了1x1);
  • 小池化核。相比AlexNet的3x3的池化核,VGG全部为2x2的池化核;
  • 层数更深特征图更宽。基于前两点外,由于卷积核专注于扩大通道数、池化专注于缩小宽和高,使得模型架构上更深更宽的同时,计算量的增加放缓;
  • 全连接转卷积。网络测试阶段将训练阶段的三个全连接替换为三个卷积,测试重用训练时的参数,使得测试得到的全卷积网络因为没有全连接的限制,因而可以接收任意宽或高为的输入。

三、结构

VGGNet有A-E七种结构,从A-E网络逐步变深,但是参数量并没有增长很多,原因为:参数量主要消耗在最后3个全连接层,而前面的卷积层虽然层数多,但消耗的参数量不大。不过,卷积层的训练比较耗时,因为其计算量大。

算层数时不算maxpool层和softmax层,只算conv层和fc层。
比较出名的是VGG-16和VGG-19,最常用的是VGG-16。各种VGG的网络结构如下:
VGGnet原理分析理解_第1张图片
从上图可见,VGG-16有16个卷积层或全连接层,包括5组卷积层和3个全连接层,即:16=2+2+3+3+3+3。这个结构应牢记于心。

卷积层
有一个显著的特点:特征图的空间分辨率单调递减,特征图的通道数单调递增。图像经过一系列卷积层处理,在卷积层中使用了非常小的感受野(receptive field):3* 3,甚至有的地方使用1* 1的卷积,这种1* 1的卷积可以被看做是对输入通道(input channel)的线性变换。卷积步长(stride)设置为1个像素,3*3卷积层的填充(padding)设置为1个像素。

感受野
在卷积神经网络中,决定某一层输出结果中一个元素所对应的输入层的区域大小,被称作感受野。通俗的来说就是下一层的一个单位大小对应上一层的大小是多少。

在VGG神经网络中,我们可以使用2个33的卷积核来代替一个55的卷积核;使用3个33的卷积核来代替一个77的卷积核。使用三个33卷积而不是一个77的卷积的优势有两点:一,包含三个ReLu层而不是一个,使决策函数更有判别性;二,减少了参数。比如输入输出都是C个通道,使用33的3个卷积层需要3(33CC)=27CC,使用77的1个卷积层需要77CC=49CC。这可看为是对77卷积施加一种正则化,使它分解为3个3*3的卷积。

11卷积层主要是为了增加决策函数的非线性,而不影响卷积层的感受野。虽然11的卷积操作是线性的,但是ReLu增加了非线性。

池化层
池化层夹在连续的卷积层中间, 用于压缩数据和参数的量,减小过拟合。简而言之,如果输入是图像的话,那么池化层的最主要作用就是压缩图像。采用max-pooling,共有5层,在一部分卷积层后,max-pooling的窗口是2*2,步长是2。

全连接层
卷积取的是局部特征,全连接就是把以前的局部特征重新通过权值矩阵组装成完整的图。
因为用到了所有的局部特征,所以叫全连接。一系列卷积层之后跟着全连接层(fully-connected layers)。前两个全连接层均有4096个通道。第三个全连接层有1000个通道,用来分类。所有网络的全连接层配置相同。
在全连接层中间采用dropout层,防止过拟合,左边的图为一个完全的全连接层,右边为应用dropout后的全连接层。如下图:
VGGnet原理分析理解_第2张图片

为了方便理解,我把vgg-16的模型图画出来如下(第一张蓝色表示输入的图片,绿色是卷积层的输出,橙色是pooling层的输出,紫红色是全连接层,最后的深紫色是soft-max层的输出):

VGGnet原理分析理解_第3张图片

四、训练集

训练采用多尺度训练(Multi-scale),将原始图像缩放到不同尺寸,然后再随机裁切224x224的图片,并且对图片进行水平翻转和随机RGB色差调整,这样能增加很多数据量,可以有效防止模型过拟合。原因如下:
  一开始对原始图片进行裁剪时,原始图片的最小边不宜过小,这样的话,裁剪到224x224的时候,就相当于几乎覆盖了整个图片,这样对原始图片进行不同的随机裁剪得到的图片就基本上没差别,就失去了增加数据集的意义,但同时也不宜过大,因为裁剪到的224x224图片只含有目标的一小部分,也不是很好。

针对上述裁剪的问题,有两种解决办法:
(1) 、固定最小边的尺寸为256;
(2) 、随机从[256,512]的确定范围内进行抽样,这样原始图片尺寸不一,有利于训练,这个方法叫做尺度抖动(scale jittering),有利于训练集增强。

五、核心代码(tensorflow)

#定义卷积层函数
def conv_op(input_op,name,kh,kw,n_out,dh,dw,p): #分别表示输入张量,卷积层名字,
#卷积核高和宽,输出卷积核数量,步长以及步长的高和宽,以及参数列表p
    n_in=input_op.get_shape()[-1].value  #获取输入的通道数量,即输入的卷积核的数量或者图像通道
    with tf.name_scope(name) as scope:
        kernel=tf.get_variable(scope+"w",shape=[kh,kw,n_in,n_out],dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer_conv2d())
        conv=tf.nn.conv2d(input_op,kernel,(1,dh,dw,1),padding='SAME')
        bias_init_val=tf.constant(0.0,shape=[n_out],dtype=tf.float32)
        biases=tf.Variable(bias_init_val,trainable=True,name='b')
        z=tf.nn.bias_add(conv,biases)
        activation=tf.nn.relu(z,name=scope)
        p+=[kernel,biases]
        return activation
    
#定义全连接层函数
def fc_op(input_op,name,n_out,p):
    n_in=input_op.get_shape()[-1].value
    
    with tf.name_scope(name) as scope:
        kernel=tf.get_variable(scope+"w",shape=[n_in,n_out],dtype=tf.float32,initializer=tf.contrib.layers.xavier_initializer())
        biases=tf.Variable(tf.constant(0.1,shape=[n_out],dtype=tf.float32),name='b')
        activation=tf.nn.relu_layer(input_op,kernel,biases,name=scope)
        p+=[kernel,biases]
        return activation
    
#创建最大池化层函数
def mpool_op(input_op,name,kh,kw,dh,dw):
    return tf.nn.max_pool(input_op,ksize=[1,kh,kw,1],strides=[1,dh,dw,1],padding='SAME',name=name)
 
#开始定义VGG-16    
def inference_op(input_op,keep_prob):
    p=[]
    #第一段卷积,两个卷积层和一个池化层 
    conv1_1=conv_op(input_op,name="conv1_1",kh=3,kw=3,n_out=64,dh=1,dw=1,p=p)
    conv1_2=conv_op(conv1_1,name="conv1_2",kh=3,kw=3,n_out=64,dh=1,dw=1,p=p)
    pool1=mpool_op(conv1_2,name="pool1",kh=2,kw=2,dw=2,dh=2)
    #第二段卷积
    conv2_1=conv_op(pool1,name="conv2_1",kh=3,kw=3,n_out=128,dh=1,dw=1,p=p)
    conv2_2=conv_op(conv2_1,name="conv2_2",kh=3,kw=3,n_out=128,dh=1,dw=1,p=p)
    pool2=mpool_op(conv2_2,name="pool2",kh=2,kw=2,dw=2,dh=2)
    #第三段卷积
    conv3_1=conv_op(pool2,name="conv3_1",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
    conv3_2=conv_op(conv3_1,name="conv3_2",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
    conv3_3=conv_op(conv3_2,name="conv3_3",kh=3,kw=3,n_out=256,dh=1,dw=1,p=p)
    pool3=mpool_op(conv3_3,name="pool3",kh=2,kw=2,dw=2,dh=2)
    #第四段卷积
    conv4_1=conv_op(pool3,name="conv4_1",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    conv4_2=conv_op(conv4_1,name="conv4_2",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    conv4_3=conv_op(conv4_2,name="conv4_3",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    pool4=mpool_op(conv4_3,name="pool4",kh=2,kw=2,dw=2,dh=2)
    #第五段卷积操作
    conv5_1=conv_op(pool4,name="conv5_1",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    conv5_2=conv_op(conv5_1,name="conv5_2",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    conv5_3=conv_op(conv5_2,name="conv5_3",kh=3,kw=3,n_out=512,dh=1,dw=1,p=p)
    pool5=mpool_op(conv5_3,name="pool5",kh=2,kw=2,dw=2,dh=2)
    
    #对pool5的结果扁平化
    shp=pool5.get_shape()
    flattened_shape=shp[1].value*shp[2].value*shp[3].value
    resh1=tf.reshape(pool5,[-1,flattened_shape],name="resh1")
    
    #全连接
    fc6=fc_op(resh1,name="fc6",n_out=4096,p=p)
    fc6_drop=tf.nn.dropout(fc6,keep_prob,name="fc6_drop")
    fc7=fc_op(fc6_drop,name="fc7",n_out=4096,p=p)
    fc7_drop=tf.nn.dropout(fc7,keep_prob,name="fc7_drop")
    fc8=fc_op(fc7_drop,name="fc8",n_out=2,p=p)
    softmax_result=tf.nn.softmax(fc8)
    predictions=tf.argmax(softmax,1)
    

你可能感兴趣的:(自学,机器学习,之路)