Tensorflow实现VGGNet代码实现

源码地址https://github.com/mcttn1/DeepLearning

下面就开始实现VGGNet-16,也就是上面的版本D,其他版本读者可以仿照本节的代码自行修改并实现,难度不大。首先,我们载入几个系统库和Tensorflow。本节代码主要来自tensorflow-vgg的开源实现。https://github.com/machrisaa/tensorflow-vgg

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

VGGNet-16包含很多层的卷积,因此我们先写一个函数conv_op,用来创建卷积层并把本层的参数存入参数列表。先来看conv_op函数的输入,input_op是输入的tensor,name是这一层的名称,kh是kernel height即卷积核的高,kw是kernel width卷积核的宽,n_out是卷积核数量即通道数,dh是步长的高,dw是步长的宽,p是参数列表。下面使用get_shape()[-1].value获得输入input_op的通道数,比如输入图片的尺寸224x224x3中最后那个3。然后使用tf.name_scope(name)设置scope。我们的kernel(即卷积核参数)使用tf.get_variable创建,其中shape就是[kh,kw,n_in,n_out]即[卷积核的高,卷积核的宽,输入通道数,输出通道数],同时使用tf.contrib.layers.xavier_initializer_conv2d()做参数初始化。

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())

接着使用tf.nn.conv2d对input_op进行卷积处理,卷积核即为kernel,步长是dhxdw,padding模式设为SAME。biases使用tf.constant赋值为0,再使用tf.Variable将其转成可训练的参数。我们使用tf.nn.bias_add将卷积结果conv与bias相加,再使用tf.nn.relu对其进行非线性处理得到activation。最后将创建卷积层时用到的参数kernel和biases添加进参数列表p,并将卷积层的输出activation作为函数结果返回。

        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

下面定义全连接卷积层的创建函数fc_op。一样是先获取输入input_op的通道数,然后使用tf.get_variable创建全连接层的参数,只不过参数的维度只有两个,第一个维度为输入的通道数n_in,第二个维度为输出的通道数。同样,参数初始化方法也使用xavier_initializer。这里biases不再初始化为0,而是赋予一个较小的值0.1以避免dead neuron。然后使用tf.nn.relu_layer对输入变量input_op与kernel做矩阵乘法并加上biases,再做ReLU非线性变换得到activation。最后将这个全连接层用到参数kernel、biases添加到参数列表p,并将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

再定义最大池化层的创建函数mpool_op。这里直接使用tf.nn.max_pool,输入即为input_op,池化尺寸为khxkw,步长是dhxdw,padding模式设为SAME。

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)

完成了卷积层、全连接层和最大池化层的创建函数,接下来就开始创建VGGNet-16的网络结构。VGGNet-16主要分为6个部分,前5段为卷积网络,最后一段是全连接网络。我们定义创建VGGNet-16网络结构的函数inference_op,输入有input_op和keep_prob,这里的keep_prob是控制比率的一个placeholder。第一步先初始化参数列表p。然后创建第一段卷及网络,这一段如图1中的网络结构,由两个卷积层和一个最大池化层构成。我们使用前面写好的函数conv_op、mpool_op来创建他们。这两个卷积层的卷积核大小都是3x3,同时卷积核数量(输出通道数)均为64,步长为1x1,全像素扫描。第一个卷积层的输入input_opde的尺寸为224x224x3,输出尺寸为224x224x64;而第二个卷积层的输入输出尺寸均为224x224x64。卷积层后的最大池化层是一个标准的2x2的最大池化,将输出结果尺寸变为了112x112x64。

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)

第二段卷及网络和第一段非常类似,同样是两个卷积层加一个最大池化层,两个卷积层的卷积核尺寸也是3x3,步长1x1,但是输出通道数变为128,是以前的两倍。最大池化层和前面保持一致,因此这一段卷及网络的输出尺寸变为56x56x128。

    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)

接下来是第三段卷及网络,这里有3个卷积层和1个最大池化层。3个卷积层的卷积核大小依然是3x3, 步长为1x1,但是输出通道数增长为256,而最大池化层保持不变,因此这一段卷积网络的输出尺寸是28x28x256。

    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_2",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)

第四段卷积网络也是3个卷积层加1个最大池化层。读者可能已经发现规律了,到目前为止,VGGNet-16的每一段卷积都会将图像缩小一半,但是卷积通道数翻倍。这样图像面积缩小到1/4,输出通道数变为2倍,因此输出tensor的总尺寸每次缩小一半。这一层就是将卷积输出通道数增加到512,但是通过最大池化将图片缩小为14x14。

    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=1,dh=1,dw=1,p=p)
    conv4_3=conv_op(conv4_2,name="conv4_3",kh=3,kw=3,n_out=512=1,dh=1,dw=1,p=p)
    pool4=mpool_op(conv4_3,name="pool4",kh=2,kw=2,dh=2,dw=2)

最后一段卷积网络有所变化,这里卷积输出的通道数不再增加,继续维持在512。最后一段卷积网络同样是3个卷积层加一个最大池化层,卷积核尺寸为3x3,步长为1x1,池化层尺寸为2x2,步长为2x2。因此到这里输出的尺寸变为7x7x512。

    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=1,dh=1,dw=1,p=p)
    conv5_3=conv_op(conv5_2,name="conv5_3",kh=3,kw=3,n_out=512=1,dh=1,dw=1,p=p)
    pool5=mpool_op(conv5_3,name="pool5",kh=2,kw=2,dh=2,dw=2)

我们将第5段卷积网络的输出结果进行扁平化,使用tf.reshape函数将每个样本化为长度为7x7x512=25088的一维向量。

    shp=pool5.get_shape()
    flattened_shape=shp[1].value*shp[2].value*shp[3].value
    resh1=tf.reshape(pool5,[-1,flattened_shape],name='resh1')

然后连接一个隐含节点数为4096的全连接层,激活函数为ReLU。然后连接一个Dropout层,在训练时节点保留率为0.5,预测时为1.0。

    fc6=fc_op(resh1,name="fc6",n_out=4096,p=p)
    fc6_drop=tf.nn.dropout(fc6,keep_prob,name="fc6_drop")#keep_prob是每个元素被保留的概率,那么 keep_prob=1就是所有元素全部保留的意思

接下来是一个和前面一样的全连接层,之后同样连接一个Dropout层。

    fc7=fc_op(fc6_drop,name="fc7",n_out=4096,p=p)
    fc7_drop=tf.nn.dropout(fc7,keep_prob,name="fc7_drop")

最后连接一个有1000个输出节点的全连接层,并使用Softmax进行处理得到分类输出率。这里使用tf.argmax求输出概率最大的类别。最后将fc8、softmax、predictions和参数列表p一起返回。到此为止,VGGNet-16的网络结构就全部构建完成了。

    fc8=fc_op(fc7_drop,name="fc8",n_out=1000,p=p)
    softmax=tf.nn.softmax(fc8)
    predictions=tf.argmax(softmax,1)
    return predictions,softmax,fc8,p

测评函数time_tensorflow_run(),评估VGGNet16每轮计算时间,这个函数的第一个输入时Tensorflow的Session,第二个变量是要测评的运算算子,第四个变量是测试的名称。在session.run()方法中引入了feed_dict,方便后面传入keep_prob来控制Dropout层的保留比率。先定义预热轮数num_step_burn_in=10,它的作用是给程序热身,头几轮迭代有显存加载、cache命中等问题因此可以跳过,我们只考量10轮迭代之后的计算时间。同时,也记录总时间total_duration和平方和total_duration_squared用以计算方差。

def time_tensorflow_run(session,traget,feed,info_string):
    num_step_burn_in=10
    total_duration=0.0
    total_duration_squared=0.0

 我们进行num_batches+num_step_burn_in此迭代计算,使用time.time()记录时间,每次迭代通过session.run(target)执行。在初始热身的num_step_burn_in次迭代后,每10轮迭代显示当前迭代所需要的时间。同时每轮将total_duration和total_duration_squared累加,以便后面计算每轮耗时的均值和标准差。

    for i in range(num_batches+num_step_burn_in):
        start_time=time.time()
        _=session.run(target)
        duration=time.time()-start_time
        if i>=num_step_burn_in:
            if not i%10:#判断是否为None
            print('%s:step %d,duration=%.3f'%(datetime.now(),i-num_steps_burn_in,duration))
            total_duration+=duration
            total_duration_squared+=duration*duration

在循环结束后,计算每轮迭代的平均耗时mn和标准差sd,最后将结果显示出来。这样就完成了计算每轮迭代耗时的评测函数time_tensorflow_run。

    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))

接下来是主函数run_benchmark。首先使用with tf.Graph().as_default()定义默认的Graph方便后面使用。如前面所说,我们并不使用ImageNet数据集来训练,只使用随机图像数据测试前馈和反馈计算的耗时。我们使用tf.random_normal函数构造正态分布(标准差为0.1)的随机tensor,第一个维度是batch_size,即每轮迭代的样本数,第二个和第三个维度是图片的尺寸image_size=224,第四个维度是图片的颜色通道数。

def run_benchmark():
    with tf.Graph().as_default():
        image_size=224
        images=tf.Variable(tf.randrom_normal([batch_size,image_size,image_size,3],
        dtype=tf.float32,stddev=1e-1))
 

接下来创建keep_prob的placeholder,并调用Inference_op函数构建VGGNet-16的网络结构,获得predictions、softmax、fc8和参数 列表p

        keep_prob=tf.placeholder(tf.float32)
        predictions,softmax,fc8,p=inference_op(images,keep_prob)

然后创建Session并初始化全局参数

        init=tf.global_variables_initializer()
        sess=tf.Session()
        sess.run(init)

我们通过将keep_prob设为1.0来执行预测,并使用time_tensorflow_run测评forward运算时间。再计算VGGNet-16最后的全连接层的输出fc8的l2loss,并使用tf.gradients求相对于这个loss的所有模型参数的梯度。最后使用time_tensorflow_run评测backward运算时间,这里target为求解梯度操作grad,keep_prob为0.5。 

        time_tensorflow_run(sess,predictions,{keep_prob:1.0},"Forward")
        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,因为VGGNet-16的模型体积比较大,如果使用较大的batch_size,GPU显存会不够用。最后执行评测的主函数 run_benchmark(),测试VGGNet-16在tensorflow上forward和backward耗时。

batch_size=32
num_batches=100
run_benchmark()

forward计算时每个batch的平均耗时:

2019-01-08 15:39:14.617169: step 0,duration=43.854
2019-01-08 15:45:16.466930: step 10,duration=25.438
2019-01-08 15:51:34.463691: step 20,duration=49.127
2019-01-08 15:56:12.268783: step 30,duration=24.447
2019-01-08 16:01:05.908951: step 40,duration=30.076
2019-01-08 16:02:25.168161: step 50,duration=4.213
2019-01-08 16:03:24.630595: step 60,duration=4.032
2019-01-08 16:04:08.969369: step 70,duration=3.496
2019-01-08 16:04:48.592290: step 80,duration=6.589
2019-01-08 16:05:35.395297: step 90,duration=4.061
2019-01-08 16:06:14.046486:Forward across 100 steps, 16.633 +/- 14.782 sec /batch

backward求解梯度时,每个batch的平均耗时:

2019-01-08 16:10:01.807624: step 0,duration=19.103
2019-01-08 16:13:10.404442: step 10,duration=20.527
2019-01-08 16:16:05.957063: step 20,duration=17.272
2019-01-08 16:19:06.447500: step 30,duration=18.080
2019-01-08 16:22:06.283585: step 40,duration=20.063
2019-01-08 16:24:52.184556: step 50,duration=21.301
2019-01-08 16:27:24.695263: step 60,duration=14.913
2019-01-08 16:30:00.931430: step 70,duration=17.591
2019-01-08 16:33:01.062166: step 80,duration=14.908
2019-01-08 16:36:28.285703: step 90,duration=22.373
2019-01-08 16:40:23.472396:Forward-backward across 100 steps, 18.408 +/- 3.821 sec /batch

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值