【fishing-pan:https://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 实战》黄文坚等著;