深度学习系列用PaddlePaddle和Tensorflow实现经典CNN网络Vgg

Posted Charlotte数据挖掘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度学习系列用PaddlePaddle和Tensorflow实现经典CNN网络Vgg相关的知识,希望对你有一定的参考价值。

    本文同步发布于博客园:http://www.cnblogs.com/charlotte77/p/8028651.html    

    上周我们讲了经典CNN网络AlexNet对图像分类的效果,2014年,在AlexNet出来的两年后,牛津大学提出了Vgg网络,并在ILSVRC 2014中的classification项目的比赛中取得了第2名的成绩(第一名是GoogLeNet,也是同年提出的)。在论文《Very Deep Convolutional Networks for Large-Scale Image Recognition》中,作者提出通过缩小卷积核大小来构建更深的网络。

 


Vgg网络结构

   VGGnet是Oxford的Visual Geometry Group的team,在ILSVRC 2014上的主要工作是证明了增加网络的深度能够在一定程度上影响网络最终的性能,如下图,文章通过逐步增加网络深度来提高性能,虽然看起来有一点小暴力,没有特别多取巧的,但是确实有效,很多pretrained的方法就是使用VGG的model(主要是16和19),VGG相对其他的方法,参数空间很大,所以train一个vgg模型通常要花费更长的时间,不过公开的pretrained model让我们很方便的使用,paper中的几种模型如下:

     图1 vgg网络结构

   图中D和E分别为VGG-16和VGG-19,参数分别是138m和144m,是文中两个效果最好的网络结构,VGG网络结构可以看做是AlexNet的加深版,VGG在图像检测中效果很好(如:Faster-RCNN),这种传统结构相对较好的保存了图片的局部位置信息(不像GoogLeNet中引入Inception可能导致位置信息的错乱)。 

  我们来仔细看一下vgg16的网络结构:

图2 vgg16网络结构                         

  从图中可以看到,每个卷积层都使用更小的3×3卷积核对图像进行卷积,并把这些小的卷积核排列起来作为一个卷积序列。通俗点来讲就是对原始图像进行3×3卷积,然后再进行3×3卷积,连续使用小的卷积核对图像进行多次卷积。

  在alexnet里我们一开始的时候是用11*11的大卷积核网络,为什么在这里要用3*3的小卷积核来对图像进行卷积呢?并且还是使用连续的小卷积核?VGG一开始提出的时候刚好与LeNet的设计原则相违背,因为LeNet相信大的卷积核能够捕获图像当中相似的特征(权值共享)。AlexNet在浅层网络开始的时候也是使用9×9、11×11卷积核,并且尽量在浅层网络的时候避免使用1×1的卷积核。但是VGG的神奇之处就是在于使用多个3×3卷积核可以模仿较大卷积核那样对图像进行局部感知。后来多个小的卷积核串联这一思想被GoogleNet和ResNet等吸收。

  从图1的实验结果也可以看到,VGG使用多个3x3卷积来对高维特征进行提取。因为如果使用较大的卷积核,参数就会大量地增加、运算时间也会成倍的提升。例如3x3的卷积核只有9个权值参数,使用7*7的卷积核权值参数就会增加到49个。因为缺乏一个模型去对大量的参数进行归一化、约减,或者说是限制大规模的参数出现,因此训练核数更大的卷积网络就变得非常困难了。

  VGG相信如果使用大的卷积核将会造成很大的时间浪费,减少的卷积核能够减少参数,节省运算开销。虽然训练的时间变长了,但是总体来说预测的时间和参数都是减少的了。



  Vgg的优势

   与AlexNet相比:

  • 相同点

    • 整体结构分五层;

    • 除softmax层外,最后几层为全连接层;

    • 五层之间通过max pooling连接。

  • 不同点

    • 使用3×3的小卷积核代替7×7大卷积核,网络构建的比较深;

    • 由于LRN太耗费计算资源,性价比不高,所以被去掉;

    • 采用了更多的feature map,能够提取更多的特征,从而能够做更多特征的组合。  



用PaddlePaddle实现Vgg

  1.网络结构 vggnet.py(https://github.com/huxiaoman7/PaddlePaddle_code/blob/master/3.image_classification/vggnet.py)

#coding:utf-8

'''

Created by huxiaoman 2017.12.12

vggnet.py:用vgg网络实现cifar-10分类

'''

import paddle.v2 as paddle

def vgg(input):

    def conv_block(ipt, num_filter, groups, dropouts, num_channels=None):

        return paddle.networks.img_conv_group(

            input=ipt,

            num_channels=num_channels,

            pool_size=2,

            pool_stride=2,

            conv_num_filter=[num_filter] * groups,

            conv_filter_size=3,

            conv_act=paddle.activation.Relu(),

            conv_with_batchnorm=True,

            conv_batchnorm_drop_rate=dropouts,

            pool_type=paddle.pooling.Max())

    conv1 = conv_block(input, 64, 2, [0.3, 0], 3)

    conv2 = conv_block(conv1, 128, 2, [0.4, 0])

    conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])

    conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])

    conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])

    drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5)

    fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear())

    bn = paddle.layer.batch_norm(

        input=fc1,

        act=paddle.activation.Relu(),

        layer_attr=paddle.attr.Extra(drop_rate=0.5))

    fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear())

    return fc2


        2.训练模型 trian_vgg.py(https://github.com/huxiaoman7/PaddlePaddle_code/blob/master/3.image_classification/train_vgg.py)

        具体可见github代码,不详细展开了

    

    从训练结果来看,开了7个线程,8个Tesla K80,迭代200次,耗时16h21min,相比于之前训练的lenet和alexnet的几个小时来说,时间消耗很高,但是结果很好,准确率是89.11%,在同设备和迭代次数情况下,比lenet的和alexnet的精度都要高。

 


用Tensorflow实现vgg

  1.网络结构 

def inference_op(input_op, keep_prob):

    p = []

    # 第一块 conv1_1-conv1_2-pool1

    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-conv2_2-pool2

    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-conv3_2-conv3_3-pool3

    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-conv4_2-conv4_3-pool4

    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-conv5_2-conv5_3-pool5

    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 ( [7, 7, 512] )  拉成向量

    shp  = pool5.get_shape()

    flattened_shape = shp[1].value * shp[2].value * shp[3].value

    resh1 = tf.reshape(pool5, [-1, flattened_shape], name = 'resh1')

    # 全连接层1 添加了 Droput来防止过拟合    

    fc1 = fc_op(resh1, name = 'fc1', n_out = 2048, p = p)

    fc1_drop = tf.nn.dropout(fc1, keep_prob, name = 'fc1_drop')

    # 全连接层2 添加了 Droput来防止过拟合    

    fc2 = fc_op(fc1_drop, name = 'fc2', n_out = 2048, p = p)

    fc2_drop = tf.nn.dropout(fc2, keep_prob, name = 'fc2_drop')

    # 全连接层3 加一个softmax求给类别的概率

    fc3 = fc_op(fc2_drop, name = 'fc3', n_out = 1000, p = p)

    softmax = tf.nn.softmax(fc3)

    predictions = tf.argmax(softmax, 1)

    return predictions, softmax, fc3, p


    2.训练模型 vgg_tf.py

    具体见github:https://github.com/huxiaoman7/PaddlePaddle_code/blob/master/3.image_classification/cifar_tf/vgg_tf.py

          

    对比训练结果,在同等设备和环境下,迭代200tensorflow的训练结果是89.18%,耗时18h12min,对比paddlepaddle的效果,精度差不多,时间慢一点。其实可以对数据进行处理后再进行训练,转换成tfrecord多线程输入在训练,时间应该会快很多。

 


总结

  通过论文的分析和实验的结果,我总结了几点:

   1.LRN层太耗费计算资源,作用不大,可以舍去。

    2.大卷积核可以学习更大的空间特征,但是需要的参数空间也更多,小卷积核虽然学习的空间特征有限,但所需参数空间更小,多层叠加训练可能效果更好。

    3.越深的网络效果越好,但是要避免梯度消失的问题,选取relu的激活函数、batch_normalization等都可以从一定程度上避免。

  4.小卷积核+深层网络的效果,在迭代相同次数时,比大卷积核+浅层网络效果更好,对于我们自己设计网络时可以有借鉴作用。但是前者的训练时间可能更长,不过可能比后者收敛速度更快,精确度更好。



以上是关于深度学习系列用PaddlePaddle和Tensorflow实现经典CNN网络Vgg的主要内容,如果未能解决你的问题,请参考以下文章

深度学习系列用PaddlePaddle和Tensorflow实现经典CNN网络Vgg

深度学习系列用PaddlePaddle和Tensorflow实现经典CNN网络Vgg

深度学习系列PaddlePaddle之手写数字识别

深度学习系列关于PaddlePaddle的一些避“坑”技巧

深度学习系列PaddlePaddle之数据预处理

深度学习系列关于PaddlePaddle的一些避“坑”技巧