您的位置:首页 > 其它

TensorFlow学习笔记---CNN分类CIFAR-10数据集3

2017-02-22 11:34 701 查看
文章是对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: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)

然后就是启动图像读取程序,组成队列,最后是使用数据验证正确率。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tensorflow
相关文章推荐