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

该文章是对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层之后有个局部响应归一化 norm1 tf.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: s oftmax_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 )
然后就是启动图像读取程序,组成队列,最后是使用数据验证正确率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值