Tensorflow 实现VGGNet

 下面是开始实现VGGNet-16。首先,我们载入几个系统库和Tensorflow。

from datetime import datetime
import math
import time
import tensorflow as tf
VGGNet-16包含很多层卷积,我们先写一个函数conv_op,用来创建卷积层并把本层的参数存入参数列表。

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_c
onv2d())
                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
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()做参数初始化。

使用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添加进参数列表,并将卷积层的输出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
上面的函数为全连接层的创建函数。一样是先获取输入input_op的通道数,然后使用tf.get_variable创建全连接层的参数,只不过参数的维度只有两个,第一个维度为输入的通道数n_in,第二个维度为输出的通道数n_out。同样,参数初始化方法也是用xavier_initializer.这里biases不再初始化为0,而是赋予一个较小的值为0.1,以避免dead neuron。然后使用tf.nn.relu_layer对输入变量input_op与kernel做矩阵乘法并加上biases,再做relu非线性变换得到activation,最后将这个全连接层用到参数kernel,biases添加到参数列表P,并将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,sw,1],padding='SAME',name=name)
上面是定义最大池化层的创建函数。输入即为input_op,池化尺寸为khxkw,步长为dhxdw,padding模式设为SAME。

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)

        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=1000,p=p)
        softmax=tf.nn.softmax(fc8)
        predictions=tf.argmax(softmax,1)
        return predictions,softmax,fc8,p
上面是创建VGGNet-16的网络结构。VGGNet-16主要分为6个部分,前5段为卷积网络,最后一段是全连接网络。输入有input_op和keep_prob,这里keep_prob是控制dropout比率的一个placeholder。第一步先初始化参数列表p,然后创建第一段卷积网络。由两个卷积层和一个最大池化层构成。我们使用前面写好的函数conv_op,mpool_op来创建他们。这两个卷积层的卷积核的大小都是3x3,同时卷积核数量(输出通道数)均为64,步长为1x1,全像素扫描。第一个卷积层的输入input_op的尺寸为224x224x3,输出尺寸为224x224x64,而第二个卷积层的输入输出尺寸均为224x224x64.卷积层后的最大池化层则是一个标准的2x2最大池化,将结果尺寸变为112x112x64

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

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

第四段卷机网络也是3个卷积层加1个最大池化层。VGGNet-16的每一段卷积网络都会将图像的边长缩小一半,但是卷积输出通道数翻倍,这一层卷积输出通道数增加到512,但是通过最大之处将图片缩小为14x14。

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

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

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

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

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

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 not i%10:
                                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)
评测函数time_tensorflow_run()和前面AlexNet中的非常相似,只有一点区别:我们在session.run()方法中引入了feed_dict,方便后面传入keep_prob来控制Dropout层的保留比率。

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(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")
评测主函数run_benchmark,目标依然是仅评测forward和backward的运算性能,并不进行实质的训练和预测。首先我们生成尺寸为224x224的随机图片,方法与AlexNet中一样,通过tf.random_normal函数生成标准差为0.1的正太分布的随机数。

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

然后创建Sessionbiang初始化全局参数.

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

batch_size=32
num_batches=100
run_benchmark()
设置batch_size为32,因为VGGNet-16的模型体积比较大,如果使用较大的batch_size,GPU显存会不够用。最后执行评测的主函数run_benchmark(),测试VGGNet-16在tensorflow上的forward和backward耗时。



  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

河南骏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值