原文教程:tensorflow官方教程 翻译教程:极客学院
记录关键内容与学习感受。未完待续。。
代码:tensorflow/examples/tutorials/mnist/
——本教程的目标是展示如何使用tensorflow,利用MNIST数据集,训练和评估一个可以识别手写数字的简单的前馈神经网络。因此受众是对机器学习有经验并且对tensorflow感兴趣的用户。
——因此本教程并不是为了教授机器学习。
——请确定你已经看了安装教程。
——-本教程参考一下文件:
文件目的mnist.py这份代码建立了一个全连接模型fully_connected_feed.py训练和建立MNIST模型的主要代码,以数据反馈字典的形式作为输入模型.
——只要直接运行fully_connected_feed.py文件,就可以开始训练。
python fully_connected_feed.py——在机器学习中,MNIST是一个分类问题,这个问题是通过查看28x28像素点的手写数字的灰度图来决定这个数字图像所代表的是0到9中的哪个数字。
——更多信息获取点击Yann LeCun’s MNIST page和Chris Olah’s visualizations of MNIST
——在run_training()方法的一开始,input_data.read_data_sets()函数将确保正确的数据下载到你的本地训练文件夹下,并且解压数据返回一个含有DataSet实例的字典。
data_sets = input_data.read_data_sets(FLAGS.train_dir,FLAGS.fake_data)——如图fully_connected_feed.py文件中所示:
——注意:fake_data的标签是用来单元测试的,读者可以完全忽略。
数据集目的data_sets.train55000份图像和标签,用于主要训练data_sets.validation5000份图像和标签,用于迭代验证训练的准确性data_sets.test10000份图像和标签,用于最终测试训练的准确性——placeholder_inputs()函数生成了两个tf.placeholder操作来定义传入图的shape参数,shape参数中包含batch_size,后面还会将实际的训练用例传入到图中。
images_placeholder = tf.placeholder(tf.float32,shape=(bath_size, mnist.IMAGE_PIXELS)) labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))——如图fully_connected_feed.py文件中所示:
——在训练循环的后续步骤中,整个图像和标签数据集被分片,以符合每一步操作设定的batch_size,占位符操作会填补以符合batch_size值,然后使用feed_dict参数,将数据传入到 sess.run()函数中。
——在创建数据的占位符之后,在mnist.py文件中,根据三阶段模式:inference(),loss()和training(),建立图graph。
inference():尽可能的构建好图,以满足运行网络前向反馈来获取预测。loss():向inference图中添加生成损失所需的操作。training():向loss图中添加计算和应用梯度所需的操作。——如图mnist.py文件中所示:
——inference()函数尽可能的构建图,以做到返回包含输出预测的tensor。
——它将图像的占位符作为输入,并且在此之上借助Relu激励函数构建了一对全连接层,以及一个有着10个节点,指明了输出logits的线性层。
——每一层都在一个唯一的tf.name_scope下面创建,创建于该作用域下的所有元素都带有其前缀。
with tf.name_scope('hidden1'):——在该定义的作用域下,每一层都要使用的权重和偏置值由tf.Variable实例来生成,并且带有他们想要的大小。
with tf.name_scope('hidden1'): weights = tf.Variable( tf.truncated_normal([IMAGE_PIXELS, hiddenl_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初始化权重,并且给了2维tensor的形状,其中第一个维度代表每层中权重连接的单元数量(单元指向权重?),第二个维度表示每层中权重所连接到的单元数量(权重指向单元?)。对于第一层,命名为hidden1,维度是[IMAGE_PIXELS, hidden1_units],因为权重将图像输入连接到了hidden1层。tf.truncated_normal初始函数根据得到的均值和标准差,产生一个随机分布。
——然后,偏置值由tf.zeros函数初始化,以确保它们初始都是0值,形状是该层偏置值所连接的单元数量。
——图的三个主要操作,分别是两个tf.nn.relu操作,它们中嵌入了隐藏层所需的tf.matmul,一个logits所需的另一个tf.matmul操作。三者依次创建,各自的tf.Variable则与每一个输入占位符或者是前一层的输出tensor相连。
hidden1 = tf.nn.rulu(tf.matmul(images, weights) + biases) hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases) logits = tf.matmul(hidden2, weights) + biases——最终,程序会返回包含了输出结果的logits tensor。
——loss函数通过添加所需要的损失操作,进一步构建图。
——首先,labels_placeholder中的值被转换成64位的整数,然后tf.nn.sparse_softmax_cross_entropy_with_logits操作被添加进来,用来从labels_placeholder中自动生成1-hot标签,最后,将inference()函数产生的输出logits和这些1-hot标签比较。
labels = tf.to_int64(labels) cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits, labels, name='xentropy')——接着,用tf.reduce_mean函数计算batch维度(第一维度)下交叉熵的平均值,作为总损失。
loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')——最后程序会返回包含了loss的tensor。
——注意:交叉熵是信息论中的一个观点,它允许我们去描述,基于已有的事实,相信神经网络的预测会有多么差。更多信息请阅读博客《可视化信息理论》。
——training()函数添加了通过梯度下降最小化loss所需的操作。
——首先,取出loss()函数中的loss tensor,并用tf.scalar_summary处理,tf.scalar_summary这个操作可以与SummaryWriter(见下文)配合使用,可以向事件文件(events file)中生成汇总值(summary values)。在本教程中,每次写入汇总值时,它都会释放loss tensor的当前值。
tf.scalar_summary(loss.op.name, loss)——接下来,我们实例化一个tf.train.GradientDescentOptimizer,负责按照要求的学习速率应用梯度下降法。
optimizer = tf.train.GradientDescentOptimizer(learning_rate)——之后,我们生成一个单一变量,用来保存全局训练步骤的次数,并用minimize()操作更新系统中的三角权重(trainable weights),增加全局步骤的操作。根据惯例,这个操作称之为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语言中的with命令,它表明,所有已经构建的操作,与默认的tf.Graph全局实例是关联起来的。
with tf.Graph().as_default():——tf.Graph实例是一系列可以作为整体运行的操作。tensorflow大部分场景只需要依赖于单一默认的图即可。
——利用多个图的更加复杂的场景也是可能的,但是超出了本教程的范围。
——-一旦已经完成所有的构建准备,生成所有需要的操作,为了运行图,需要创建tf.Session。
sess = tf.Session()——另外,也可以用with模块生成session,限制作用域。
with tf.Session() as sess:——session中没有参数表明,这份代码将依附于(如果没有创建会话,就先创建会话)默认的本地会话。
——在生成session之后,所有的tf.Variable实例都要都要通过各自初始化操作中的sess.run()函数来初始化。
init = tf.global_variable_initializer() sess.run(init)——sess.run()方法将运行图中,与作为参数传入的操作相对应的完整子集。在第一调用时,它只包含了变量初始化程序tf.group,图的其他部分不会在这里运行,它们将发生在下面的训练循环中。
——在会话中初始化所有变量后,训练就开始了。
——训练的每一步都是有用户代码控制的,最简单的循环可以做有用的训练:
for step in xrange(FLAGS.max_steps): sess.run(train_op)—–然而,本教程在这方面会稍微复杂一点,它必须把输入的数据根据每一步的情况进行切分,以匹配前面生成的占位符。
——对每一步,代码都会生成一个反馈字典,包含了对应步骤中训练所使用的例子集合,这些例子的哈希键就是其所代表的占位符操作。
——在fill_feed_dict()函数中,为了下一批次图像和标签的batch_size集合,会查询给定的DataSet,并且与占位符匹配的tensor会包含下一批次的图像和标签。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size, FLAGS.fake_data)——以占位符作为哈希键,创建一个Python字典对象,键值则是其代表的反馈tensor。
feed_dict = { images_placeholder:images_feed, labels_placeholder:labels_feed, }——这传给sess.run()函数的feed_dict参数,为这一步的训练提供输入例子。
——在运行sess.run()函数时,要在代码中明确其获取的两个值,[train_op, loss]。
for step in xrange(FLAGS.max_steps): feed_dict = fill_feed_dict( data_sets.train, images_placeholder, labels_placeloder ) _, loss_value = sess.run([train_op, loss], feed_dict=feed_dict)——因为要获取这两个值,sess.run()会返回一个有两个元素的元组。列表中的每一个tensor对象,都对应了返回元组中的numpy数组,而这些数组中包含了当前这步训练中对应的tensor的值。由于train_op操作没有输出值,在返回元组中对应的元素就是None,所以会抛弃。然而,如果在训练过程中模型出现偏差,loss tensor的值可能会变成NaN,所以我们要获取它的值,并记录下来。
——假设训练没有出现NaNs,训练循环将每100次训练后,打印一个简单的状态文本 ,让用户知道现在的训练状态。
if step % 100 == 0: print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)——-为了释放TensorBoard所使用的事件文件,所有的即时数据(在这里只有一个)在图建立期间都要合并在一个tensor中。
summary = tf.merge_all_summaries()—–然后在会话创建后,可以实例化一个tf.train.SummaryWriter,用于写入包含了图本身和即时数据的具体值的事件文件。
summary_writer = tf.train.SummaryWriter(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,查看即时数据的情况。
——注意:更多关于建立和运行Tensorboard的信息,请查看相关教程Tensorboard: 训练过程可视化
——为了得到可以用来后续恢复模型以进一步训练和评估的检查点文件,我们实例化一个tf.train.Saver。
saver = tf.train.Saver()——在训练循环中,saver.save()方法被定期调用,用来向训练文件夹写入包含了当前所有可训练变量值的检查点文件。
saver.save(sess, FLAGS.train_dir, global_step=step)——这样,以后我们就可以使用saver.restore()方法,重载模型的参数,继续训练。
saver.restore(sess, FLAGS.train_dir)
——在每1000步后,代码将会用训练数据集和测试数据集评估模型,do_eval()函数将会被调用3次,分别使用训练数据集、验证数据集和测试数据集。
print 'Training Data Eval:' do_eval( sess, images_placeholer, 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( eval_correct, images_placeholder, labels_placeholder, data_sets.test )——注意:越复杂的使用场景,通常越要隔绝data_sets.test测试数据集,只有在大量的超参数优化调整(hyperparameter tuning)之后才检查。但是由于MNIST问题比较简单,我们可以一次性评估所有的数据。
——在进入训练循环之前,评估操作可以通过调用mnist.py文件的evaluation()函数来建立,传入和loss()函数相同的logits/labels参数。
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_k操作判定为正确的预测之和,然后只需要将正确测试的总数,除以例子总数,就可以得到正确率了。
precision = true_count / num_examples print(' Num examples: %d Num correct: %d Precision @ 1: %0.04f' % (num_examples, true_count, precision))——官网下载源代码,如下:
——第一次出现一点问题,File “fully_connected_feed.py”, line 155, in run_training summary_writer = tf.train.SummaryWriter(FLAGS.log_dir, sess.graph) AttributeError: ‘module’ object has no attribute ‘SummaryWriter’ ——截图如下:
——原因是最新版的tensorflow修改了很多API(更多内容查看官网),这里的也做了修改:
——修改代码,将第一行替换为第二行
summary_writer = tf.SummaryWriter(FLAGS.log_dir, sess.graph) summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)——再次运行结果如下:
——以上,tensorflow的基本操作已经完成。