当前位置: 首页>>技术教程>>正文


Tensorflow技术101

代码:tensorflow/examples/tutorials/mnist/

本教程的目的:展示如何使用TensorFlow来训练和评估简单的feed-forward神经网络,这个网络使用(经典)MNIST数据集的做手写数字分类。本教程的目标用户是有兴趣使用TensorFlow并且有经验的机器学习用户。

这个教程不适用于一般的机器学习概念教学。

请确保您已按照说明安装了TensorFlow

教程文件

本教程引用了以下文件:

文件 目的
mnist.py 构建全连接MNIST模型的代码。
fully_connected_feed.py 主要代码使用Feed字典训练建立模型,使用下载的MNIST数据集。

简单地运行fully_connected_feed.py文件即可直接开始训练:

python fully_connected_feed.py

准备数据

MNIST是机器学习中的经典问题,即查看手写数字的28×28像素灰度图像,并确定图像代表哪个数字:从0到9的所有数字。

MNIST Digits

有关更多信息,请参阅Yann LeCun的MNIST页面或者克里斯·奥拉的MNIST的可视化

下载

run_training()方法之上,input_data.read_data_sets()函数将确保正确的数据已被下载到您的本地训练文件夹,然后解压缩该数据返回一个字典DataSet实例。

data_sets = input_data.read_data_sets(FLAGS.train_dir, FLAGS.fake_data)

注意fake_data标志用于unit-testing目的,读者可以安全地忽略它。

数据集 目的
data_sets.train 55000图像和标签,用于初级训练。
data_sets.validation 5000个图像和标签,用于迭代验证训练精度。
data_sets.test 10000个图像和标签,用于最终测试的训练准确性评估。

输入和占位符

placeholder_inputs()函数创建两个tf.placeholderops,用于定义输入的形状,包括batch_size

images_placeholder = tf.placeholder(tf.float32, shape=(batch_size,
                                                       mnist.IMAGE_PIXELS))
labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))

再往下,在训练循环的每一步中,完整的图像和标签数据集被切割以适应batch_size,与这些占位符操作相匹配,然后使用feed_dict参数传入sess.run()函数。

构建图(计算图)

在为数据创建占位符之后,从mnist.py文件可以看到,计算图的构建按照3阶段模式:inference()loss(),和training()

  1. inference() – 根据需要构建图,以便向前运行网络进行预测。
  2. loss() – 向推理图添加生成损失所需的操作。
  3. training() – 向损失图添加计算和应用梯度所需的操作。

推断(inference)

inference()函数根据需要构建图形,以返回包含输出预测的张量。

它将图像占位符作为输入,并在其上建立一对完全连接的图层(使用RELU激活),之后是一个指定输出logits的十节点线性层。

每个图层都在唯一的tf.name_scope下方创建,作为在该范围内创建的项目的前缀。

with tf.name_scope('hidden1'):

在定义的范围内,每个层所使用的权重和偏置被生成tf.Variable实例:

weights = tf.Variable(
    tf.truncated_normal([IMAGE_PIXELS, hidden1_units],
                        stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))),
    name='weights')
biases = tf.Variable(tf.zeros([hidden1_units]),
                     name='biases')

例如,在hidden1范围,赋予权重变量的唯一名称将是“hidden1/weights”。

权重用tf.truncated_normal初始化并给出它们的二维张量的形状,其中第一个dim表示权重连接层输入单元的数量,第二个dim表示权重连接层输出单元的数量。对于第一层,命名为hidden1,尺寸是[IMAGE_PIXELS, hidden1_units],因为权重将图像输入连接到hidden1图层。tf.truncated_normal初始值生成器生成一个给定均值和标准差的随机分布。

然后,这个偏差被初始化tf.zeros以确保它们以零值开始,它们的形状只是它们所连接的图层中的单元数。

该图有三个主要操作 – 其中两个tf.nn.reluops分别为隐藏层封装了tf.matmul,以及为logits封装了tf.matmul

hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)
hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
logits = tf.matmul(hidden2, weights) + biases

最后,logits将包含返回的输出张量。

损失(loss)

loss()函数通过添加所需的损失操作来进一步构建图表。

首先,从labels_placeholder被转换为64位整数。然后,tf.nn.sparse_softmax_cross_entropy_with_logitsop被添加用来自动生成1-hot标签labels_placeholder,并与inference()的logits输出做比较。

labels = tf.to_int64(labels)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
    labels=labels, logits=logits, name='xentropy')

接下来使用tf.reduce_mean将批量维度(第一维度)上交叉熵的值平均化为总损失。

loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')

最后将包含损失值的张量返回。

训练(training)

training()函数添加所需的操作并通过梯度下降最大限度地减少损失。

首先,从loss()函数中获得损耗张量,并把它交给一个tf.summary.scalar(这个操作用于在与tf.summary.FileWriter一起使用时将生成的摘要值写入文件(见下文))。在这种情况下,每次写出摘要时都会生成损失的快照值。

tf.summary.scalar('loss', loss)

接下来,我们实例化一个tf.train.GradientDescentOptimizer负责按要求的学习率应用梯度。

optimizer = tf.train.GradientDescentOptimizer(learning_rate)

然后,我们生成一个单一的变量,以包含全局训练步骤和计数器tf.train.Optimizer.minimizeop用于更新系统中的可训练权重并增加全局步长。按照惯例,这个操作就是所谓的train_op而且是TensorFlow会话必须执行的步骤,以便引导一整步训练(见下文)。

global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)

训练模型

一旦图被构建,就可以在由用户代码控制的循环中迭代地训练和评估fully_connected_feed.py

图(Graph)

run_training()函数之上是一个pythonwith命令,指示所有内置操作将与默认的全局tf.Graph实例关联。

with tf.Graph().as_default():

一个tf.Graph是可以作为一个整体一起执行的操作集合。大多数TensorFlow使用只需要依靠单个默认图。

更复杂的使用多个图是可能的,但超出了这个简单的教程的范围。

会话(session)

一旦所有的构建准备工作已经完成,并且产生了所有必要的操作,就可以创建tf.Session运行图了。

sess = tf.Session()

或者,为了限制范围,可以使用with创建Session

with tf.Session() as sess:

会话的空参数表示此代码将附加到(或创建,如果尚未创建)默认本地会话。

创建会话后,立即可以通过调用tf.Session.run将所有的tf.Variable实例进行初始化。

init = tf.global_variables_initializer()
sess.run(init)

tf.Session.run方法将运行作为参数传递的op相对应的图的完整子集。在第一个调用中,initop是一个tf.group,它只包含变量的初始值。

Training循环

用会话初始化变量后,就可以开始训练了。

用户代码可以控制每一步的训练,最简单的训练循环是:

for step in xrange(FLAGS.max_steps):
    sess.run(train_op)

但是,本教程稍微复杂一些,因为必须为每个步骤分割输入数据以匹配先前生成的占位符。

给图填充数据

对于每一步,代码将生成一个feed字典,该feed字典将包含该步骤所训练的一组示例,由它们所代表的占位符操作。

fill_feed_dict()函数中,给定的DataSet用于查询下一个batch_size图像和标签,同时与占位符匹配的张量被填充。

images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size,
                                               FLAGS.fake_data)

然后用占位符作为key和相应的feed张量作为value生成一个python字典对象。

feed_dict = {
    images_placeholder: images_feed,
    labels_placeholder: labels_feed,
}

通过feed_dict参数传入sess.run()函数来提供这一步训练所需的输入样本。

检查状态

下面的代码指定了调用run时要用到的两个值:[train_op, loss]

for step in xrange(FLAGS.max_steps):
    feed_dict = fill_feed_dict(data_sets.train,
                               images_placeholder,
                               labels_placeholder)
    _, loss_value = sess.run([train_op, loss],
                             feed_dict=feed_dict)

由于有两个取值,sess.run()返回一个包含两个元素的元组。每Tensor在要获取的值的列表中,对应于返回元组中的一个numpy数组,在训练步骤中会填充该张量的值。因为train_op是一个没有输出值的操作,返回的元组中的对应元素是None,因此被忽略。但是,如果训练期间模型没有收敛,loss张量的值可能变成NaN,所以我们捕获这个值用于记录。

假设训练运行良好,没有NaNs,训练循环还会每100步打印一个简单的状态文本,让用户知道训练的状态。

if step % 100 == 0:
    print('Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration))

可视化状态

为了生成TensorBoard使用的时间文件,所有的摘要(在这种情况下,只有一个)在图构建阶段收集到一个张量中。

summary = tf.summary.merge_all()

然后,会话创建后,一个tf.summary.FileWriter可以被实例化以写入事件文件,它包含图本身和摘要的值。

summary_writer = tf.summary.FileWriter(FLAGS.train_dir, sess.graph)

最后,每当摘要值summary重新评估时时间文件即被更新,输出传递给writer的add_summary()函数。

summary_str = sess.run(summary, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)

当事件文件被写入时,TensorBoard可以运行在训练文件夹中以显示摘要中的值。

MNIST TensorBoard

注意:有关如何构建和运行Tensorboard的更多信息,请参阅随附的教程Tensorboard:可视化学习

保存一个检查点

为了生成一个检查点文件,可以用来稍后恢复一个模型进行进一步的训练或评估,我们实例化一个tf.train.Saver

saver = tf.train.Saver()

在训练循环中,可以周期调用tf.train.Saver.save方法将所有可训练变量的当前值写入训练目录中的检查点文件。

saver.save(sess, FLAGS.train_dir, global_step=step)

在将来的某个时候,可以使用tf.train.Saver.restore方法重新加载模型参数。

saver.restore(sess, FLAGS.train_dir)

评估模型

每一千步,代码将尝试评估模型在训练集和测试集上的效果。do_eval()函数被调用三次,分别用于训练集,验证集和测试集。

print('Training Data Eval:')
do_eval(sess,
        eval_correct,
        images_placeholder,
        labels_placeholder,
        data_sets.train)
print('Validation Data Eval:')
do_eval(sess,
        eval_correct,
        images_placeholder,
        labels_placeholder,
        data_sets.validation)
print('Test Data Eval:')
do_eval(sess,
        eval_correct,
        images_placeholder,
        labels_placeholder,
        data_sets.test)

请注意,更复杂的用法通常会隔离data_sets.test,只有在大量超参数调整后才会检查。然而,在这个简单的MNIST问题上,我们评估所有的数据。

构建评估图

进入训练循环之前,通过调用evaluation()函数先建立Eval操作。

eval_correct = mnist.evaluation(logits, labels_placeholder)

evaluation()函数只是生成一个tf.nn.in_top_k操作,如果可以在K个最可能的预测中找到真正的标签,则可以自动将每个模型输出评分为正确的。在这种情况下,我们将K的值设置为1,只考虑真实标签的预测正确性。

eval_correct = tf.nn.in_top_k(logits, labels, 1)

评估输出

然后可以创建一个循环来填充一个feed_dict并调用sess.run()以及eval_correct运算来评估给定数据集上的模型。

for step in xrange(steps_per_epoch):
    feed_dict = fill_feed_dict(data_set,
                               images_placeholder,
                               labels_placeholder)
    true_count += sess.run(eval_correct, feed_dict=feed_dict)

true_count变量只是累积了所有的预测in_top_kop已经确定是正确的。精确度可以通过简单除以示例的总数来计算。

precision = true_count / num_examples
print('  Num examples: %d  Num correct: %d  Precision @ 1: %0.04f' %
      (num_examples, true_count, precision))

参考资料

本文由《纯净天空》出品。文章地址: https://vimsky.com/article/3635.html,未经允许,请勿转载。