【Tensorflow】深度学习实战04——Tensorflow实现VGGNet

版权声明:【fishing-pan:https://blog.csdn.net/u013921430转载请注明出处】 https://blog.csdn.net/u013921430/article/details/80339337

 

fishing-panhttps://blog.csdn.net/u013921430转载请注明出处】

前言

       现在已经到了Tensorflow实现卷积神经网络的第四讲了,既然是学习、实践,我一直坚持每一次试验都需要有新的知识点,每一次都要有新的收获才行,所以今天实现的VGGNet主要讲他的思想和创新。VGGNet探索了卷积网络的深度与其性能之间的关系,通过反复堆叠3x3的卷积核与2x2的最大池化层,VGGNet构建了16-19层深的网络。具体细节在下面讲述;

VGGNet

      VGGNet中全部使用3x3的卷积核与2x2的最大池化层,并且整个网络都未使用LRN层。与之前的AlexNet不同的是,VGGNet没有像AlexNet那样一个卷积对应一个池化。而是将连续两到三个卷积层连接成为一个卷积段,在卷积段后面添加最大值池化。具体不同级别的网络结构图如下所示;

                                                           图1  VGGNet各级别网络结构图(图片来自网络)

分析&讨论

1x1卷积核

       其中C比B多了几个1x1的卷积层。这是很有意思的,因为很少有人会用1x1的卷积核。但是当添加了1x1的卷积核后,相当于给前面的计算结果乘上一个系数,虽然卷积是线性变换,但是激活函数是非线性的运算,这个1x1的卷积核其实是增加了激活函数的结果的非线性效果。

      上面六种网络结构的参数量很相近,原因在之前的博文中有提到过,在卷积神经网络中,计算量主要来自卷积层的卷积运算,但是参数量主要取决于全连接层。

                                        图2  VGGNet各级别网络的参数量(单位为百万)(图片来自网络)

 

多个卷积层堆叠的意义

      上面的网络结构经常出现多个完全一样的3x3的卷积核堆叠的情况,这样设计大致有两个目的,其一是这样做可以减少参数量,两个3x3的卷积核串联起来的效果相当于一个5x5的卷积核,而三个3x3的卷积核串联起来与一个7x7的卷积核的效果相当。在效果相近的情况下,前者只有3x3x3=27个参数,而后者参数多达49个,减少了将近一半的参数,同时运算量也减少了。其二是为了验证网络深度与性能之间的关系,因为卷积核的大小也是影响网络性能的一个因素,所以都是用一样大小的卷积核,保证不同网络结构中变量只有网络的深度。而且每个卷积层都有一个ReLU函数,多个激活函数可以增加网络中非线性的部分,使网络更具判别性。

                                                  图 3 两个串联的3x3的卷积核的效用类似于一个5x5的卷积核

       VGGNet在训练时有一个小技巧就是先训练级别A的网络,然后将A网络的权重用于初始化后面几个复杂的模型,这样收敛更快。其实这一步在图像处理中经常用到,特别是在处理图像尺度比较大、图像复杂的情况。这种情况下往往首先对图像进行降采样等处理,然后对降采样的结果进行计算,然后用这些计算结果用作原始分辨图像的初始计算,这样可以大大节省迭代次数和计算时间。

       此外,VGGNet还将图像缩放到不同的尺度然后随机剪切图像,从而实现数据增广,增加数据量,还可以防止模型过拟合。

代码

       由于训练一个VGGNet需要非常多的时间,我这里不会用ImageNet的数据去训练它,与上一篇博文中的AlexNet一样,这里只构造网络,并且测试前馈计算(forward)和反馈计算(backward)的速度。

       因为代码中没有太多要讲的,所以我就不逐行讲解了,只在代码中进行简单的标注。下面提供所有代码,需要说明一下,代码与书中的代码有细微的不同,但是功能是一样的。

# -*- coding: utf-8 -*-
"""
Created on Thu May 10 14:49:48 2018

@author: most_pan
"""


from datetime import datetime
import math
import time
import tensorflow as tf

#定义卷积层,输入参数依次为输入图像,这一层的名字,卷积核的高kh,卷积层的宽kw,卷积核输出通道数,卷积核步长dh、dk,以及参数列表p;
def conv_op(input_op,name,kh,kw,n_out,dh,dw,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())
        
        bias_init_val=tf.constant(0.0,shape=[n_out],dtype=tf.float32)
        biases=tf.Variable(bias_init_val,trainable=True,name='b')
        
        activation=tf.nn.relu(tf.nn.bias_add(tf.nn.conv2d(input_op,kernel,(1,dh,dw,1),padding='SAME'),biases),name=scope)
        
        p+=[kernel,biases]
        
        return activation

#定义全连接层,输入参数依次为输入图像,这一层的名字,输出通道数,以及参数列表p;
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_conv2d())
        
        biases=tf.Variable(tf.constant(0.1,shape=[n_out],dtype=tf.float32),trainable=True,name='b')
        
        activation=tf.nn.relu_layer(input_op,kernel,biases,name=scope)
        
        
        p+=[kernel,biases]
        
        return activation

#定义最大值池化,输入参数依次为输入图像,这一层的名字,卷积核的高kh,卷积层的宽kw,卷积核步长dh、dk;
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)
 
#构建网络    
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,dh=2,dw=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,dh=2,dw=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,dh=2,dw=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,dh=2,dw=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,dh=2,dw=2)
    
    
    shp=pool5.get_shape() 
    
    #shp[0]是batch_size
    flattened_shape=shp[1].value*shp[2].value*shp[3].value
    resh1=tf.reshape(pool5,shape=[-1,flattened_shape],name="resh1")
    
    fc6=fc_op(resh1,"fc6",4096,p=p)
    fc6_drop=tf.nn.dropout(fc6,keep_prob,name="fc6_drop")
    
    fc7=fc_op(fc6_drop,"fc7",4096,p=p)
    fc7_drop=tf.nn.dropout(fc7,keep_prob,name="fc7_drop")
    
    fc8=fc_op(fc7_drop,"fc7",1000,p=p)
    softmax=tf.nn.softmax(fc8)  
    predictions=tf.argmax(softmax,1)

#返回是概率最大的分类    
    return predictions,softmax,fc8,p
    
def time_tensorflow_run(session,target,feed,info_string):
    num_steps_burn_in=10
    total_duration=0.0
    total_duration_squared=0.0
    
    for i in range(num_batches+num_steps_burn_in):
        start_time=time.time()
        _=session.run(target,feed_dict=feed)
        duration=time.time()-start_time
        
        if i>=num_steps_burn_in:
            if i%10==0:
                print('%s: step %d,duration= %.3f' % (datetime.now(),i-num_steps_burn_in,duration))
                
            total_duration+=duration
            total_duration_squared+=duration*duration
            
    mn=total_duration/num_batches
    vr=total_duration_squared/num_batches-mn*mn
    sd=math.sqrt(vr)
    
    print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %(datetime.now(), info_string, num_batches, mn, sd))
    
  
def run_benchmark():  
    with tf.Graph().as_default():  
        #创建输入图像  
        image_size = 224  
        images = tf.Variable(tf.random_normal([batch_size, image_size, image_size, 3], dtype = tf.float32, stddev = 1e-1))  
        
        keep_prob=tf.placeholder(tf.float32)
        predictions,softmax,fc8,p=inference_op(images,keep_prob)
          
        #初始化网络中的所有变量  
        init = tf.global_variables_initializer()  
        sess = tf.Session()  
        sess.run(init)  
          
        #调用time_tensorflow_run函数,前向计算时候,dropout为1
        time_tensorflow_run(sess, predictions,{keep_prob:1.0}, "Forward")  
          
        #获得backward计算时间 ,反向计算(训练网络时),dropout为0.5; 
        objective = tf.nn.l2_loss(fc8)  
        grad = tf.gradients(objective, p)  
        time_tensorflow_run(sess, grad,{keep_prob:0.5}, "Forward-backward")  
    
batch_size=32
num_batches=100
run_benchmark()

运行结果

       从结果中也可以看出但是很明显前馈计算要比反馈计算快上近7倍。所以,在训练网络时,大量时间是花费在了反馈计算中,因为这些计算涉及了大量的求导和除法。

 

已完。。

 

 

参考书籍

      《Tensorflow 实战》黄文坚等著;

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页