mnist.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import math
import tensorflow as tf
# The MNIST dataset has 10 classes, representing the digits 0 through 9.
NUM_CLASSES = 10
# The MNIST images are always 28x28 pixels.
IMAGE_SIZE = 28
IMAGE_PIXELS = IMAGE_SIZE * IMAGE_SIZE
def inference(images, hidden1_units, hidden2_units):
"""尽可能地构建好图表,满足促使神经网络向前反馈并做出预测的要求.
Args:
images: Images placeholder, from inputs().
hidden1_units: Size of the first hidden layer.
hidden2_units: Size of the second hidden layer.
Returns:
softmax_linear: Output tensor with the computed logits.
"""
# 每一层都创建于一个唯一的tf.name_scope之下
# 创建于该作用域之下的所有元素都将带有其前缀
# 当这些层是在hidden1作用域下生成时
# 赋予权重变量的独特名称将会是"hidden1/weights"
with tf.name_scope('hidden1'):
weights = tf.Variable(
tf.truncated_normal([IMAGE_PIXELS, hidden1_units], # 通过tf.truncated_normal函数初始化权重变量
stddev=1.0 / math.sqrt(float(IMAGE_PIXELS))),
name='weights') # 将根据所得到的均值和标准差,生成一个随机分布
biases = tf.Variable(tf.zeros([hidden1_units]), # 偏差的起始值都是0,而它们的shape则是其在该层中所接到的(connect to)单元数量
name='biases')
hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases) # 嵌入了隐藏层所需的tf.matmul
# Hidden 2
with tf.name_scope('hidden2'):
weights = tf.Variable(
tf.truncated_normal([hidden1_units, hidden2_units],
stddev=1.0 / math.sqrt(float(hidden1_units))),
name='weights')
biases = tf.Variable(tf.zeros([hidden2_units]),
name='biases')
hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
# Linear
with tf.name_scope('softmax_linear'):
weights = tf.Variable(
tf.truncated_normal([hidden2_units, NUM_CLASSES],
stddev=1.0 / math.sqrt(float(hidden2_units))),
name='weights')
biases = tf.Variable(tf.zeros([NUM_CLASSES]),
name='biases')
logits = tf.matmul(hidden2, weights) + biases
return logits
def loss(logits, labels):
"""从logits和labels计算损失函数
往inference图表中添加生成损失(loss)所需要的操作(ops)
Args:
logits: Logits tensor, float - [batch_size, NUM_CLASSES].
labels: labels_placeholer中的值,将被编码为一个含有1-hot values的Tensor
如果类标识符为“3”,那么该值就会被转换为: [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
Returns:
loss: Loss tensor of type float.
"""
labels = tf.to_int64(labels)
return tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
def training(loss, learning_rate):
"""往损失图表中添加计算并应用梯度(gradients)所需的操作.
从loss()函数中获取损失Tensor,将其交给tf.scalar_summary.
Creates an optimizer and applies the gradients to all trainable variables.
The Op returned by this function is what must be passed to the
`sess.run()` call to cause the model to train.
Args:
loss: Loss tensor, from loss().
learning_rate: The learning rate to use for gradient descent.
Returns:
train_op: The Op for training.
"""
# 可以向事件文件(events file)中生成汇总值(summary values).
tf.summary.scalar('loss', loss)
# 按照所要求的学习效率(learning rate)应用梯度下降法(gradients).
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
# 一个变量用于保存全局训练步骤(global training step)的数值
global_step = tf.Variable(0, name='global_step', trainable=False)
# 使用minimize()函数更新系统中的三角权重(triangle weights)、增加全局步骤的操作
# TensorFlow会话(session)诱发一个完整训练步骤所必须运行的操作
train_op = optimizer.minimize(loss, global_step=global_step)
return train_op
def evaluation(logits, labels):
"""在预测标签时评估logits的质量
Args:
logits: Logits tensor, float - [batch_size, NUM_CLASSES].
labels: Labels tensor, int32 - [batch_size], with values in the
range [0, NUM_CLASSES).
Returns:
标量int32张量,具有正确预测的示例数(从batch_size开始)
"""
# 如果在K个最有可能的预测中可以发现真的标签,
# 那么这个操作就会将模型输出标记为正确。
# 把K的值设置为1,也就是只有在预测是真的标签时,才判定它是正确的
correct = tf.nn.in_top_k(logits, labels, 1)
# 返回真实标签的数量
return tf.reduce_sum(tf.cast(correct, tf.int32))
fully_connected_feed.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# pylint: disable=missing-docstring
import argparse
import os
import sys
import time
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.examples.tutorials.mnist import mnist
# 基本模型参数作为外部标志.
FLAGS = None
def placeholder_inputs(batch_size):
""" placeholder
生成两个 placeholder 表示输入的图表.
定义传入图表中的shape参数,shape参数中包括batch_size值,后续还会将实际的训练用例传入图表
# 请注意,占位符的形状与完整图像和标签张量的形状相匹配
# 但第一个维度现在是batch_size
# 而不是训练或测试数据集的完整大小
# 传入的整个图像和标签数据集会被切片
# 以符合每一个操作所设置的batch_size值
# 占位符操作将会填补以符合这个batch_size值
Args:
batch_size: 传入的整个图像和标签数据集会被切片,以符合每一个操作所设置的batch_size值
Returns:
images_placeholder: 图像 placeholder.
labels_placeholder: 标签 placeholder.
"""
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size,
mnist.IMAGE_PIXELS))
labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
return images_placeholder, labels_placeholder
def fill_feed_dict(data_set, images_pl, labels_pl):
"""向图表提供反馈.
执行每一步时,我们的代码会生成一个反馈字典(feed dictionary),
其中包含对应步骤中训练所要使用的例子,
这些例子的哈希键就是其所代表的占位符操作。
Args:
data_set: fill_feed_dict函数会查询给定的DataSet
images_pl: 索要下一批次batch_size的图像
labels_pl: 索要下一批次batch_size的标签
Returns:
feed_dict: 这个字典随后作为feed_dict参数,传入sess.run()函数中,为这一步的训练提供输入样例
"""
# 与占位符相匹配的Tensor则会包含下一批次的图像和标签.
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size,
FLAGS.fake_data)
# 以占位符为哈希键,创建一个Python字典对象,键值则是其代表的反馈Tensor
feed_dict = {
images_pl: images_feed,
labels_pl: labels_feed,
}
return feed_dict
def do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_set):
"""Runs one evaluation against the full epoch of data.
Args:
sess: 经过模型训练后的会话
eval_correct: Tensor返回正确预测的数量
images_placeholder: 图像 placeholder.
labels_placeholder: 标签 placeholder.
data_set: 要评估的图像和标签集,来自input_data.read_data_sets()
"""
# 并运行一个预测的评估.
true_count = 0 # 计算正确预测的数量.
steps_per_epoch = data_set.num_examples // FLAGS.batch_size
num_examples = steps_per_epoch * FLAGS.batch_size
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) # 在调用sess.run()函数时传入eval_correct操作,目的就是用给定的数据集评估模型
precision = float(true_count) / num_examples # true_count变量会累加所有in_top_k操作判定为正确的预测之和
print('Num examples: %d Num correct: %d Precision @ 1: %0.04f' %
(num_examples, true_count, precision))
def run_training():
"""循环地迭代式训练和评估"""
# 得到一系列的图和标签为了训练, 验证, 和
# 测试 on MNIST.
data_sets = input_data.read_data_sets(FLAGS.input_data_dir, FLAGS.fake_data)
# 一个Python语言中的with命令
# 这个命令表明所有已经构建的操作都要与默认的tf.Graph全局实例关联起来
# tf.Graph实例是一系列可以作为整体执行的操作
with tf.Graph().as_default():
# 生成标签和图像的占位符.
images_placeholder, labels_placeholder = placeholder_inputs(
FLAGS.batch_size)
# 构建好图表,满足促使神经网络向前反馈并做出预测的要求
logits = mnist.inference(images_placeholder,
FLAGS.hidden1,
FLAGS.hidden2)
# 往inference图表中添加生成损失(loss)所需要的操作(ops)
loss = mnist.loss(logits, labels_placeholder)
# 往损失图表中添加计算并应用梯度(gradients)所需的操作
train_op = mnist.training(loss, FLAGS.learning_rate)
# 传入的logits和标签参数要与loss函数的一致。这样做事为了先构建Eval操作
eval_correct = mnist.evaluation(logits, labels_placeholder)
# 为了释放TensorBoard所使用的事件文件(events file)
# 所有的即时数据(在这里只有一个)都要在图表构建阶段合并至一个操作(op)中
summary = tf.summary.merge_all()
# 添加变量初始化操作Op.
init = tf.global_variables_initializer()
# 为了得到可以用来后续恢复模型以进一步训练或评估的检查点文件(checkpoint file)
saver = tf.train.Saver()
# 创建一个tf.Session,用于运行图表.
sess = tf.Session()
# 实例化一个tf.train.SummaryWriter
# 用于写入包含了图表本身和即时数据具体值的事件文件
summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)
# 通过调用各自初始化操作中的sess.run()函数进行初始化
# 在初次调用时,init操作只包含了变量初始化程序tf.group。
# 图表的其他部分不会在这里,而是在下面的训练循环运行
sess.run(init)
# 训练的每一步都是通过用户代码控制,而能实现有效训练的最简单循环就是:.
for step in xrange(FLAGS.max_steps):
start_time = time.time()
# 使用此特定训练步骤的实际图像集和标签填充反馈字典
feed_dict = fill_feed_dict(data_sets.train,
images_placeholder,
labels_placeholder)
# 运行模型的一个步骤。 返回值是来自`train_op`(被丢弃)和`loss` Op的激活。
# 要检查Ops或变量的值,可以将它们包含在传递给sess.run()的列表中,
# 并且将在调用的元组中返回值tensors。
_, loss_value = sess.run([train_op, loss],
feed_dict=feed_dict)
duration = time.time() - start_time
# 打印一行简单的状态文本,告知用户当前的训练状态
if step % 100 == 0:
# 打印状态.
print('Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration))
# 每次运行summary_op时,都会往事件文件中写入最新的即时数据
# 函数的输出会传入事件文件读写器(writer)的add_summary()函数
summary_str = sess.run(summary, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)
summary_writer.flush() # 事件文件写入完毕之后,可以就训练文件夹打开一个TensorBoard,查看即时数据的情况
# 在训练循环中,将定期调用saver.save()方法
# 向训练文件夹中写入包含了当前所有可训练变量值得检查点文件
if (step + 1) % 1000 == 0 or (step + 1) == FLAGS.max_steps:
checkpoint_file = os.path.join(FLAGS.log_dir, 'model.ckpt')
saver.save(sess, checkpoint_file, global_step=step)
# 每隔一千个训练步骤,我们的代码会尝试使用训练数据集与测试数据集,对模型进行评估
# 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)
def main(_):
if tf.gfile.Exists(FLAGS.log_dir):
tf.gfile.DeleteRecursively(FLAGS.log_dir)
tf.gfile.MakeDirs(FLAGS.log_dir)
run_training()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--learning_rate',
type=float,
default=0.01,
help='Initial learning rate.'
)
parser.add_argument(
'--max_steps',
type=int,
default=2000,
help='Number of steps to run trainer.'
)
parser.add_argument(
'--hidden1',
type=int,
default=128,
help='Number of units in hidden layer 1.'
)
parser.add_argument(
'--hidden2',
type=int,
default=32,
help='Number of units in hidden layer 2.'
)
parser.add_argument(
'--batch_size',
type=int,
default=100,
help='Batch size. Must divide evenly into the dataset sizes.'
)
parser.add_argument(
'--input_data_dir',
type=str,
default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
'tensorflow/mnist/input_data'),
help='Directory to put the input data.'
)
parser.add_argument(
'--log_dir',
type=str,
default=os.path.join(os.getenv('TEST_TMPDIR', '/tmp'),
'tensorflow/mnist/logs/fully_connected_feed'),
help='Directory to put the log data.'
)
parser.add_argument(
'--fake_data',
default=False,
help='If true, uses fake data for unit testing.',
action='store_true'
)
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
测试结果:
Training Data Eval:
Num examples: 55000 Num correct: 49370 Precision @ 1: 0.8976
Validation Data Eval:
Num examples: 5000 Num correct: 4515 Precision @ 1: 0.9030
Test Data Eval:
Num examples: 10000 Num correct: 9036 Precision @ 1: 0.9036