TensorFlow学习笔记(8)----CNN分类CIFAR-10数据集

来源:互联网 发布:mac 鼠标指针小手 编辑:程序博客网 时间:2024/04/28 01:39

该文章是对TF中文手册的卷积神经网络和英文手册Convolutional Neural Networks部分所包含程序的解读,旨在展示CNN处理规模比较大的彩色图片数据集(分类问题)的完整程序模型,训练中使用交叉熵损失的同时也使用了L2范式的稀疏化约束,例子修改后就可以训练自己的数据。这篇博客按照程序工作的顺序,从cifar10_train.py开始,依次解读途径的每个重要函数,具体细节还需要自己阅读源程序。注意:运行程序前请先减小训练次数,否则训练时间太长了!!!


首先说一下例子的相关内容。CIFAR-10的数据是这样的:有10分类,每个分类6000个32*32的彩色图片,5000个用于训练,1000个用于测试,大概样子如下:


1. 例子要点

      模型是一个多层架构,由卷积层和非线性层(nonlinearities)交替多次排列后构成。这些层最终通过全连通层对接到softmax分类器上。这一模型除了最顶部的几层外,基本跟Alex Krizhevsky提出的模型一致(Learning Multiple Layers of Features from Tiny Images)。在一个GPU上经过几个小时(注意时间很长!)的训练后,该模型达到了最高86%的精度。细节请查看下面的描述以及代码。模型中包含了1,068,298个学习参数,分类一副图像需要大概19.5M个乘加操作。代码的组织形式:


读入的图片经过了多种处理,都是TF自带的内部函数,另外一系列随机变换人为增加数据集的大小

  • 图片会被统一裁剪到24x24像素大小,裁剪中央区域用于评估或随机裁剪用于训练;
  • 图片会进行近似的白化处理,使得模型对图片的动态范围变化不敏感
  • 对图像进行随机的左右翻转;
  • 随机变换图像的亮度;
  • 随机变换图像的对比度;
这些基础的图像处理流程被分配在16个线程中处理。

CNN网络的不同层的功能:



训练方法与损失的定义:

       训练一个可进行N维分类的网络的常用方法是使用多项式逻辑回归(softmax 回归),Softmax 回归在网络的输出层上附加了一个softmax nonlinearity,并且计算归一化的预测值和label的1-hot encoding的交叉熵。在正则化过程中,对所有学习变量应用权重衰减损失(使用了L2范式,强调模型的参数的稀疏性),求交叉熵损失和所有权重衰减项的和,loss()函数的返回值就是这个值。

2. 数据的读取

读取的数据格式
images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size.
labels: Labels. 1D tensor of [batch_size] size
有16个线程一直在按照指定的batch_size读取数据,放置到队列中,训练程序需要数据的时候直接从队列中获取一个batch即可。

读取:
filename_queue= tf.train.string_input_producer(filenames)
获取文件名称队列,使用read_cifar10()这个自定义函数从二进制数据中获取一个样本的信息结构体(大小、数据、标签),然后使用tf.cast(read_input.uint8image, tf.float32)把uint8变换成float32类型。
切割:
比较底层的就是:def read_cifar10(filename_queue):,该函数从二进制数据中读取数据并规整,每条样本都是先标签后数据,CIFAR10是一个字节标签,CIFAR100是2字节,使用切片函数tf.slice(record_bytes,[0],[label_bytes]), tf.int32),从输入中0开始的地方切label_bytes个字节。然后切取对应数据:
depth_major= tf.reshape(tf.slice(record_bytes,[label_bytes],[image_bytes]),
[result.depth, result.height, result.width])
从第三个维度(深度通道数)开始规整变形。
处理原始图片:
初步获取数据后就需要变形成tensor了,1D变换成3D
tf.random_crop(reshaped_image,[height, width,3])

变换之后就是人工生成各种数据:
tf.image.random_flip_left_right(distorted_image)#从左到右随机
tf.image.random_brightness(distorted_image,max_delta=63)#随机亮度变换
tf.image.random_contrast(distorted_image,lower=0.2, upper=1.8)#随机对比度变换
float_image= tf.image.per_image_whitening(distorted_image)#最后是图像的白化:均值与方差的均衡,降低图像明暗、光照差异引起的影响

注意上述这些操作只是针对单幅图像的,至于多线程处理图片缓冲区队列,保证训练程序随时可读取batch_size的数据,是通过tf.train.shuffle_batch中设定队列大小、缓冲区大小,直接就保证整理好一个数据集合的队列了,这是TF内部自带的

3. 建立网络

首先注明的是:多个GPU需要tf.get_variable()用于分享数据,而单个GPU只需要tf.Variable()

参数设置函数:
_variable_with_weight_decay(name, shape, stddev, wd)
功能:输入名称、形状、偏差和均值就可以定义一个参数tensor,生成数据主要分为两步,一个是正常建立参数,另一个是添加L2范式强调稀疏化。

_variable_on_cpu中的tf.get_variable(name, shape, initializer=initializer, dtype=dtype)
是正常的参数建立
weight_decay= tf.mul(tf.nn.l2_loss(var), wd, name='weight_loss')
增加L2范式稀疏化,其中L2范式定义为:output = sum(t ** 2) / 2,然后乘以一个衰减系数wd做为一个训练指标:这个值应该尽量小,以保证稀疏性

这里使用了tf.add_to_collection('losses', weight_decay),把所有的系数作为以losses为标签进行收集,对应的还有下面的交叉熵。该模型通过控制wd就可以强调稀疏性在训练中的比重(wd=0就是不强调稀疏化),这个例子中只有全连接层对稀疏性有要求。


第一层是conv1,视野是5*5,每个图像从3通道(rgb)到64通道shape=[5,5,3,64],卷积滑动tf.nn.conv2d(images, kernel,[1,1,1,1], padding='SAME'),然后与偏置相加后是relu函数输出,对输出也有个summary用于查看稀疏性:tf.scalar_summary(tensor_name+'/sparsity', tf.nn.zero_fraction(x))统计0的比例反应稀疏性tf.histogram_summary(tensor_name+'/activations', x)输出数值的分布直接反应神经元的活跃性,如果全是很小的值说明不活跃

第一层后紧接着是pooling层pool1
tf.nn.max_pool(conv1, ksize=[1,3,3,1], strides=[1,2,2,1],padding='SAME', name='pool1')
模板是3*3,移动步长2*2,有重叠的pooling(pool有各种不同的,也有3D的)。

第一个pooling层之后有个局部响应归一化norm1tf.nn.local_response_normalization,简写为tf.nn.lrn),这是一篇论文里的理论(ImageNet Classification with Deep Convolutional Neural Networks):总之就是把输出归一化了一下,对训练有利。TF文档的定义是:


第一梯队之后又是个卷积层conv2,与第一个卷积层类似只是64通道到64通道,偏置初始是0.1,没有变化,但是之后就是归一化层norm2,然后才是结构一样的pooling层pool2

两个标准的卷积层后是全连接层
local3层首先是确定2次conv、pool后的每个样本展开的维度(注意:这里不需要知道是怎么展开的,因为到这里以后提取的都是很高维度的特征了,保证程序上连接的正确即可),展开方法:reshape= tf.reshape(pool2,[FLAGS.batch_size,-1]),具体获取每个样本展开的维度dim= reshape.get_shape()[1].value,然后就是常规的定义全连接层weights= _variable_with_weight_decay('weights', shape=[dim,384],stddev=0.04,wd=0.004),从dim映射到384个神经元:local3= tf.nn.relu(tf.matmul(reshape, weights)+ biases, name=scope.name)local4与local3相似只是从384全连接到192(192、384这些数字与GPU的架构有关),全连接层的wd是0.004,略微强调了一下稀疏性。

最后一个层名字是softmax_linear,但是并没有使用softmax:softmax_linear= tf.add(tf.matmul(local4, weights), biases, name=scope.name)

4. 损失函数

总函数:loss= cifar10.loss(logits, labels)
具体使用
cross_entropy= tf.nn.sparse_softmax_cross_entropy_with_logits(
logits, labels, name='cross_entropy_per_example')
计算交叉熵代价,具体在之前有解释。
然后计算一个batch运算后的平均值:
tf.reduce_mean(cross_entropy, name='cross_entropy')与上面的收集器对应,tf.add_to_collection('losses', cross_entropy_mean)同样收集进losses中,这样就已经包含了所有batch的交叉熵均值和所有系数的L2范式。最后使用了tf.add_n(tf.get_collection('losses'), name='total_loss')这是面向多GPU的,因为一个GPU就一个losses,不需要add_n总损失了。

5.训练
学习率更新:
首先是根据当前的训练步数、衰减速度、之前的学习速率确定新的学习速率:
# Decay the learning rate exponentially based on the number of steps.
lr= tf.train.exponential_decay(INITIAL_LEARNING_RATE,global_step,decay_steps,LEARNING_RATE_DECAY_FACTOR,staircase=True)
这个函数的解释:如果staircase是true,就取个整数。TF文档:


均值线(moving average):
等价于股票中常提到的“均值线”tf.train.ExponentialMovingAverage(0.9, name='avg'),这个只是观察,因为按照经验:
“Some training algorithms, such as GradientDescent and Momentum often benefit from maintaining a moving average of variables during optimization. Using the moving averages for evaluations often improve results significantly.”

计算梯度:
多显卡就是麻烦:为了保障均值线观察准确,需要制定同步点:
# Compute gradients.
with tf.control_dependencies([loss_averages_op]):
opt= tf.train.GradientDescentOptimizer(lr)
grads= opt.compute_gradients(total_loss)
函数tf.control_dependencies只是告诉计算单元梯度计算要在统计之后,

梯度更新参数:
apply_gradient_op= opt.apply_gradients(grads, global_step=global_step)
计算完了,就反向传播一次,更新被训练的参数

各种summary和句柄:
summary就不一一说明了,和之前的程序一样,句柄如下:
with tf.control_dependencies([apply_gradient_op, variables_averages_op]):
train_op= tf.no_op(name='train')
最后是个nothing操作,只是返回train_op作为控制界面的句柄。

具体的训练:
# Create a saver.
saver= tf.train.Saver(tf.all_variables())
# Build the summary operation based on the TF collection of Summaries.
summary_op= tf.merge_all_summaries()
# Build an initialization operation to run below.
init= tf.initialize_all_variables()
# Start running operations on the Graph.
sess= tf.Session(config=tf.ConfigProto(
log_device_placement=FLAGS.log_device_placement))
sess.run(init)

启动之前建立的图片规整线程:
# Start the queue runners.
tf.train.start_queue_runners(sess=sess)

显示和保存训练信息:
每隔10步输出:
print(format_str%(datetime.now(), step, loss_value, examples_per_sec, sec_per_batch))
每隔100步保存sumary一次
每隔1000步保存断点一次使用saver= tf.train.Saver(tf.all_variables())保存

6. 验证模型
传入验证函数的参数:
eval_once(saver, summary_writer, top_k_op, summary_op)
saver是用读取moving_average的
summary_writer和summary_op是保存记录的
top_k_op传入了模型和验证模型

读取检查点:
ckpt= tf.train.get_checkpoint_state(FLAGS.checkpoint_dir)
从检查点恢复图和参数:
saver.restore(sess, ckpt.model_checkpoint_path)
然后就是启动图像读取程序,组成队列,最后是使用数据验证正确率。


3 0