TensorFlow运作方式入门

代码:tensorflow/examples/tutorials/mnist/

本篇教程的目的,是向大家展示如何利用TensorFlow使用(经典)MNIST数据集训练并评估一个用于识别手写数字的简易前馈神经网络。 本教程的目标读者是经验丰富的机器学习用户并有兴趣使用TensorFlow。

这些教程并不是为了教大家机器学习的基础知识。

请确保你已经按照说明安装TensorFlow

教程文件

本教程引用了以下文件:

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

只需直接运行fully_connected_feed.py文件就可以开始训练:

python fully_connected_feed.py

准备数据

MNIST是机器学习中的一个经典问题。 这个问题是查看28x28像素的手写数字灰度图像,并确定图像表示0到9中的哪一个数字。

MNIST Digits

MNIST数字

有关更多信息,请参阅Yann LeCun的MNIST页面Chris Olah对MNIST的可视化

下载

run_training()方法的顶部,input_data.read_data_sets()函数将确保正确的数据已下载到你的本地训练文件夹,然后解包该数据以返回DataSet实例的字典。

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

fake_data标志用于单元测试,读者可以完全忽略。

数据集 目的
data_sets.train 55000张图像和标签,主要作为训练集。
data_sets.validation 5000张图像和标签,用于迭代验证训练准确度。
data_sets.test 10000张图像和标签,用于最终测试训练的准确度。

Inputs和Placeholders

placeholder_inputs()函数创建两个tf.placeholder操作,它们定义图的输入的形状,包括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,与这些placeholder操作匹配,然后使用feed_dict参数传入给sess.run()函数。

构建图

在为数据创建placeholder之后,下面的图是根据三段模式从mnist.py文件构建:inference()loss()training()

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

Inference

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

它将图像placeholder作为输入,并在其上构建具有ReLU激活的一对完全连接的层,之后是指定输出逻辑的十个节点线性层。

每一层都创建于一个唯一的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作用域内创建时,将赋予weights变量唯一的名称“hidden1/weights”。

每个变量都被赋予initializer操作,并作为它们构建的一部分。

在这种最常见的情况下,使用tf.truncated_normal初始化权重,并给出它们的二维张量的形状,其中第一个dim表示权重连接的图层中的单位数,第二个dim表示权重连接的图层中的单元数量。 对于名为hidden1的第一个图层,尺寸为[IMAGE_PIXELS,hidden1_units],因为权重会将图像输入连接到hidden1图层。 tf.truncated_normal初始值设定项生成一个具有给定均值和标准差的随机分布。

然后用tf.zeros初始化偏置,以确保它们以所有零值开始,它们的形状仅仅是它们连接的图层中的单元数。

The graph's three primary ops -- two tf.nn.relu ops wrapping tf.matmul for the hidden layers and one extra tf.matmul for the logits -- are then created, each in turn, with separate tf.Variable instances connected to each of the input placeholders or the output tensors of the previous layer.

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()函数通过添加所需的损失操作来进一步构建图表。

首先,将labels_placeholder中的值转换为64位整数。 然后,添加一个tf.nn.sparse_softmax_cross_entropy_with_logits op,以自动从labels_placeholder产生1个热标签,并比较来自推理() t3>功能与这些单热标签。

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')

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

注意:交叉熵是信息理论中的一个概念,它允许我们描述相信神经网络的预测有多糟糕,因为实际上是真的。 欲了解更多信息,请阅读博客文章视觉信息理论(http://colah.github.io/posts/2015-09-Visual-Information/)

训练

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.minimize op用于更新系统中的可训练权重并增加全局步长。 按照惯例,这个操作符被称为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中的用户代码控制的循环中对其进行迭代训练和评估。

run_training()函数的顶部是一个带有命令的python ,它指示所有构建的操作将与默认全局tf.Graph

with tf.Graph().as_default():

一个tf.Graph是可以作为一个组一起执行的ops的集合。 大多数TensorFlow使用只需要依赖单个默认图形。

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

会话

一旦完成了所有构建准备并生成了所有必需的操作,就会创建tf.Session来运行该图。

sess = tf.Session()

或者,可以将会话生成为带有块的用于作用域:

with tf.Session() as sess:

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

在创建会话之后,立即通过调用tf.Session.run初始化所有tf.Variable实例的初始化操作。

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

tf.Session.run方法将运行与作为参数传递的op相对应的图的完整子集。 In this first call, the init op is a tf.group that contains only the initializers for the variables. 没有其他的图表在这里运行;发生在下面的训练循环中。

训练循环

通过会话初始化变量后,可开始培训。

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

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

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

Feed the Graph

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

fill_feed_dict()函数中,为给定的DataSet查询其下一个batch_size图像和标签集,并且匹配占位符的张量被填充包含下面的图像和标签。

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

然后生成一个python字典对象,其占位符作​​为关键字并将代表性的张量作为值输入。

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

它被传递给sess.run()函数的feed_dict参数以提供这一步训练的输入示例。

检查状态

该代码指定了两个要在其运行调用中获取的值:[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)

Because there are two values to fetch, sess.run() returns a tuple with two items. 要获取的值列表中的每个Tensor对应于返回元组中的一个numpy数组,在此步训练过程中填充该张量的值。 由于train_op是一个没有输出值的Operation,所以返回的元组中的对应元素是None,因此被丢弃。 However, the value of the loss tensor may become NaN if the model diverges during training, so we capture this value for logging.

假设训练在没有NaN的情况下运行良好,训练循环还会每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

MNIST TensorBoard

NOTE: For more info about how to build and run Tensorboard, please see the accompanying tutorial Tensorboard: Visualizing Learning.

保存检查点

为了发出一个检查点文件,这个文件可以用来为以后的训练或评估恢复模型,我们实例化一个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问题,我们对所有数据进行评估。

构建Eval图

在进入训练循环之前,Eval op应该是通过使用与相同的logits / labels参数调用mnist.py中的evaluation() loss()函数。

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)

Eval Output

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

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_k op确定为正确的所有预测值。 从那里,精确度可以通过简单除以示例的总数来计算。

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