Tensorflow 实现经典卷积神经网络AlexNet
Posted 河南骏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tensorflow 实现经典卷积神经网络AlexNet相关的知识,希望对你有一定的参考价值。
AlexNet将CNN的基本原理应用到很深很广的网络中,AlexNet主要使用到的新技术点如下:
(1)成功使用ReLU作为CNN的激活函数,并验证其效果在较深网络中超过了Sigmoid成功解决了Sigmoid在网络较深时的梯度弥散问题。
(2)训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合。在AlexNet中主要是最后几个全连接层使用了Dropout。
(3)在CNN中使用重叠的最大池化。此前CNN中普遍使用平均池化,ALexNet全部使用最大池化,避免平均池化的模糊化效果。并且AlexNet中提出让步长比池化核的尺寸小,这样池化层的输出之间会用重叠和覆盖,提升了模型的泛化能力。
(4)提出了LRN层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。
(5)使用CUDA加速深度卷积网络的训练,利用GPU强大的并行计算能力,处理神经网络训练时大量的矩阵运算。
(6)数据增强,随机地从256x256的原始图像中截取224x224大小的区域(以及水平翻转的镜像),相当于增加了(256-224)^2*2=2048倍的数据量。使用了数据增强后可以大大减轻过拟合,提升泛化能力。
整个AlexNet有8个需要训练参数的层(不包括池化层和LRN层),前5层为卷积层,后3层为全连接层。AlexNet最后一层是有1000类输出的Softmax层用作分类。LRN层出现在第一个及第二个卷积层后,而最大池化层出现在两个LRN层及最后一个卷积层后。Relu激活函数则应用再这8层每一层的后面。
我们建立一个完整的AlexNet卷积神经网络,然后对它每个batch的前馈计算和反馈计算的速度3进行测试。
首先导入几个接下来会用到的几个系统库,包括datetime,math和time,并载入Tensorflow。
from datetime import datetime
import math
import time
import tensorflow as tf
这里设置batch_size为32,num_batches为100,即总共测试100个batch的数据。
batch_size=32
num_batches=100
定义一个用来显示网络每一层结构的函数print_actications,展示每一个卷积层或池化层输出tensor的尺寸。这个函数接受一个tensor作为输入,并显示其名称和尺寸。
def print_activations(t):
print t.op.name,'',t.get_shape().as_list()
接下来设计AlexNet的网络结构。我们先定义函数inference,它接受images作为输入,返回最后一层pool5(第5个池化层)及parameters(AlexNet中所有需要训练的模型参数)。这个inference函数将会很大,包括多个卷积和池化层。下面将拆为几个小段分别讲解:
首先是第一个卷积层conv1,这里使用Tensorflow中的name_scope,通过with tf.name_scope('conv1') as scope可以将scope内生成的Variable自动命名为conv1/xxx,便于区分不同卷积层之间的组件,然后定义第一个卷积层,和之前一样使用tf.truncated_normal阶段的正态分布函数(标准差为0.1)初始化卷积核的参数为kernel。卷积核尺寸为11x11,颜色通道为3,卷积核数量为64.准备好了kernel,再使用tf.nn.conv2d对输入images完成卷积操作,我们将strides步长设置为4x4(即在图片上每4x4区域只取样一次,横向间隔是4,纵向间隔也为4,每次取样的卷积核大小都为11X11),padding模式设为SAME。将卷积层的biases全部初始化为0,再使用tf.nn.bias_add将conv和biases加起来,并使用激活函数tf.nn.relu对结果进行非线性处理。最后砽print_activations将这一层最后输出的tensor conv1的结构打印出来,并将这一层可训练的参数kernel,biases添加到parameters中。
def inference(images):
parameters=[]
with tf.name_scope('conv1') as scope:
kernel=tf.Variable(tf.truncated_normal([11,11,3,64],dtype=tf.float32,stddev=1e-1),name='weights')
conv=tf.nn.conv2d(images,kernel,[1,4,4,1],padding='SAME')
biases=tf.Variable(tf.constant(0.0,shape=[64],dtype=tf.float32),trianable=True,name='biases')
bias=tf.nn.bias_add(conv,biases)
conv1=tf.nn.relu(bias,name=scope)
print_activations(conv1)
parameters+=[kernel,biases]
在第一个卷积层后再添加LRN层和最大池化层。先使用tf.nn.lrn对前面输出的tensor conv1 进行lrn处理,这里使用的depth_radius 设为4,bias设为1,alpha为0.001/9,beta为0.75,基本都是AlexNet论文中的推荐值。不过目前除了AlexNet,其他景点的卷积神经网络模型基本都放弃了LRN(主要是效果不明显),而我们使用LRN也会让前馈、反馈的速度大大下降(整体速度下降1/3)。使用tf.nn.max_pool对前面的输出lrn1进行最大池化处理,这里的池化尺寸为3x3,即将3x3大小的像素块降为1x1的像素,取样的步长为2x2,padding模式设为VALID,即取样时不能超过边框,不像SAME模式那样可以填充边界外的点。最后将输出结果pool1的结构打印出来。
lrn1=tf.nn.lrn(conv1,4,bias=1.0,alpha=0.001/9,beta=0.75,name='lrn1')
pool1=tf.nn.max_pool(lrn1,ksize=[1,3,3,1],strides=[1,2,2,1],padding='VALID',name='pool1')
print_activations(pool1)
接下是第二个卷积层,大部分步骤和第一个卷积层相同,只有几个参数不同。主要区别在与我们的卷积和尺寸是5x5,输入通道数(即上一层输出通道数,也就是上一层卷积核数量)为64,卷积核数量为192.同时,卷积的步长也全部设为1,即扫描全图像素。
with tf.name_scope('conv2') as scope:
kernel=tf.Variable(tf.truncated_normal([5,5,64,192],dtype=tf.float32,stddev=1e-1),name='weights')
conv=tf.nn.conv2d(pool1,kernel,[1,1,1,1],padding='SAME')
biases=tf.Variable(tf.constant(0.0,shape=[192],dtype=tf.float32),trainable=True,name='biases')
bias=tf.nn.bias_add(conv,biases)
conv2=tf.nn.relu(bias,name=scope)
parameters+=[kernel,biases]
print_activations(pool2)
接下来对第2个卷积层的输出conv2进行处理,同样是先做LRN处理,再进行最大池化处理,参数和之前完全一样。
lrn2=tf.nn.lrn(conv2,4,bias=1.0,alpha=0.001/9,beta=0.75,name='lrn2')
pool2=tf.nn.max_pool(lrn2,ksize=[1,3,3,1],strides=[1,2,2,1],padding='VALID',name='pool2')
print_activations(pool2)
下面是创建第3个卷积层,基本结构和前面两个类似,也只是参数不同。这一层的卷积核尺寸为3X3,输入通道数为192,卷积核数量继续扩大为384,同时卷积的步长全部为1,其他地方和前面保持一致。
with tf.name_scope('conv3') as scope:
kernel=tf.Variable(tf.truncated_normal([3,3,192,384],dtype=tf.float32,stddev=1e-1),name='weights')
conv=tf.conv2d(pool2,kernel,[1,1,1,1],padding='SAME')
biases=tf.Variable(tf.constant(0.0,shape=[384],dtype=tf.float32),trainable=True,name='biases')
bias=tf.nn.bias_add(conv,biases)
conv3=tf.nn.relu(bias,name=scope)
parameters+=[kernel,biases]
print_activations(conv3)
第4个卷积层和之前也类似,这一层的卷积核尺寸为3x3,输入通道数为384,但是卷积核数量降为256.
with tf.name_scope('conv4') as scope:
kernel=tf.Variable(tf.truncated_normal([3,3,256,256],dtype=tf.float32,stddev=1e-1),name='weights')
conv=tf.nn.conv2d(conv3,kernel,[1,1,1,1],padding='SAME')
biases=tf.Variable(tf.constant(0.0,shape[256],dtype=tf.float32),trainable=True,name='biases')
bias=tf.nn.bias_add(conv,biases)
conv5=tf.nn.relu(bias,name=scope)
parameters+=[kernel,biases]
print_activations(conv4)
最后的第5个卷积层同样是3X3大小的卷积核,输入通道数为256,卷积核数量也为256
with tf.name_scope('conv5') as scope:
kernel=tf.Variable(tf.truncated_normal([3,3,256,256],dtype=tf.float32,stddev=1e-1),name='weights)
conv=tf.nn.conv2d(conv4,kernel,[1,1,1,1],padding='SAME')
biases=tf.Variable(tf.constant(0.0,shape=[256],dtype=tf.float32),trainable=True,name='biases')
bias=tf.nn.bias_add(conv,biases)
conv5=tf.nn.relu(bias,name=scope)
parameters+=[kernel,biases]
print_activations(conv5)
在第5个卷积层之后,还有一个最大池化层,这个池化层和前两个卷积层后的池化层一致,最后我们返回这个池化层的输出pool5.至此,inference函数就完成了,它可以创建AlexNet的卷积部分。在正式使用AlexNet来训练或预测时,还需要添加3个全连接层,隐含节点数分别为4096,4096和1000。
pool5=tf.nn.max_pool(conv5,ksize=[1,3,3,1].strides=[1,2,2,1],padding='VALID',name='pool5')
print_activations(pool5)
return pool,parameters
全连接层大致如下:
reshape=tf.reshape(pool2,[batch_size,-1])
dim=reshape.get_shape()[1].value
weight=variable_with_weight_loss(shape=[dim,384],stddev=0.04,wl=0.004)
bias=tf.Variable(tf.constant(0.1,shape=[384]))
local=tf.nn.relu(tf.matmul(reshape,weight3)+bias)
下面还有2个全连接层,与上类似,这里不再赘述。
接下来实现一个品谷AlexNet每轮计算时间的函数time_tensorflow_run。这个函数的第一个输入就是tensorflow的SESSION,第二个变量是需要评测的运算算子,第三个变量是测试的名称。先定义预热轮数num_steps_burn_in=10,它的作用是给程序热身,头几轮迭代有现存加载、cache命中等问题一次可以跳过,我们只考量10轮迭代之后的计算时间。同时,也记录总时间total_duration和平方和total_duration_squared用以计算方差。
def time_tensorflow_run(session,target,info_string):
num_steps_burn_in=10
total_duration=0.0
total_duration_squared=0.0
进行num_batches+num_steps_burn_in次迭代计算,使用time.time()记录时间,每次迭代通过session.run(target)执行。在初始热身的Num_steps_burn_in次迭代后,每次迭代显示迭代所需要的时间。同时每轮将total_duration和平方和total_duration_squared累加以便后面计算每轮耗时的均值和标准差。
for i in range(num_batches+num_steps_burn_in):
start_time=time.time()
_=session.run(target)
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和标准差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方便后面使用。我们使用tf.random_normal函数构造正态分布(标准差为0.1)的随机tensor,第一个维度是batch_size,即每轮迭代的样本数,第二个和第三个维度是图片的查询image_size=224,第四个维度是图片的颜色通道数。接下来,使用前面定义的inference函数侯建整个AlexNet网络,得到最后一个池化层的输出pool5和网络中需要训练的参数的几何parameters、接下来我们使用tf.Session()创建新的Session并通过tf.global_variable_initializer()初始化所有参数。
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))
pool5,parameters=inference(images)
init=tf.global_variables_initializer()
sess=tf.Session()
sess.run(init)
下面进行AlexNet的forward计算的评测,这里直接使用time_tensorflow_run统计运算时间,传入的target就是pool5,即卷积网络最后一个池化层的输出。然后进行backward,即训练过程的评测,这里和forward计算有些不同,我们需要给最后的pool5设置一个优化目标loss。使用tf.nn.l2_loss计算pool5的loss,再使用tf.gradients求相对于loss的所有模型参数的梯度,这样就模拟了一个训练的过程。当然,训练时还有一个根据梯度更新参数的过程,最后我们使用time_tensorflow_run统计backward的运算时间,这里的target就是求整个网络梯度gard的操作。
time_tensorflow_run(sess,pool5,'Forward")
objective=tf.nn.l2_loss(pool5)
grad=tf.gradients(objective,parameters)
time_tensorflow_run(sess,grad,"Forward-backward")
最后执行主函数
run_benchmark()
以上是关于Tensorflow 实现经典卷积神经网络AlexNet的主要内容,如果未能解决你的问题,请参考以下文章