在这篇文档里面依旧是使用前馈神经网络(feed-forward neural network)来构建MNIST识别的神经网络,只不过神经网络的编写更加规范了。主要是对mnist.py 和fully_connected_feed.py 这两个文件代码的一个解释。这两个文件在tensorflow的官方github中都可以找到tensorflow/tensorflow/examples/tutorials/mnist/。在mnist.py里面主要编写的就是构建全连接神经网络-mnist的模型,在fully_connected_feed.py里面主要编写利用mnist数据集的训练代码。运行fully_connected_feed.py就可以开始训练我们的模型。
mnist.py
在开始之前我们需要导入一些模块:
import math
import tensorflow as tf
在mnist.py里面我们经过三阶段的模式函数操作:inference()
, loss()
,和training(),
就能够把图表构建完成了。
1.inference()
—— 尽可能地构建好图表,满足促使神经网络向前反馈并做出预测的要求。
2.loss()
—— 往inference图表中添加生成损失(loss)所需要的操作(ops)。
3.training()
—— 往损失图表中添加计算并应用梯度(gradients)所需的操作。
inference():
inferfence()函数会尽可能地构建图表,返回包含了预测结果(out prediction)的Tensor。它以图片的占位符(image placeholder)作为输入,借助ReLu(Rectified Linear Units)激活函数,构建一对全连接层。
每一层都创建于一个唯一的tf.name_scope之下,创建于该作用域之下的所有元素都将带有其前缀。
with tf.name_scope('hidden1'):
接下来我们需要构建第一层的神经元了,我们知道第一层的神经元以图像作为输入,因此我们首先需要去给出图像的大小的参数:
IMAGE_SIZE = 28
IMAGE_PIXELS = IMAGE_SIZE * IMAGE_SIZE
我们有了这个参数之后,我们就可以构建输入层,也就是第一层的神经元的权重(weight)和偏置(bias)了,第一层的输出为隐藏层第一层的输入:
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')
因此权重W的维度是:[IMAGE_PIXELS, hidden1_units],tf.truncated_normal
初始函数将根据所得到的均值和标准差,生成一个随机分布。
定义好了权重和偏置之后,接下来就需要将其和输入图像连接起来并经过激活函数:
hidden1 = tf.nn.relu(tf.matmul(images, weights) + biases)
这样的话第一层神经元就构建好了,接下来我们以同样的方式构建第二层神经元:
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)
最后我们希望输出是10个类别,因此我们需要定义一个参数去确定我们的输出是多少维度的:
NUM_CLASSES = 10
之后构建输出层(至于这里为啥没有经过激活函数我也不是很清楚,如果有知道的同学的话还希望可以留言告诉我一下,嘻嘻):
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
整个inference函数的构建如下所示:
def inference(images, hidden1_units, hidden2_units):
with tf.name_scope('hidden1'):
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 = tf.nn.relu(tf.matmul(images, weights) + biases)
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)
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
loss():
loss()函数通过添加所需的op来构建图:
首先,这个来自lables_placeholder是被编码成一个1-hot的向量。例如:如果一个标签是被定义为数字“3”,那么它的表示形式如下所示:
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
loss()函数的编写如下:
def loss(logits, labels):
labels = tf.to_int64(labels)
return tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
tf.to_int64将张量转换为 int64 类型;
使用tf.nn.sparse_softmax_cross_entropy_with_logits的交叉熵(cross-entropy)loss。
- labels:形状为[d_0, d_1, ..., d_{r-1}]的Tensor(其中,r是labels和结果的秩),并且有dtype int32或int64。labels中的每个条目必须是[0, num_classes)中的索引。当此操作在CPU上运行时,其他值将引发异常,并返回NaNGPU上相应的loss和梯度行。
- logits:形状为[d_0, d_1, ..., d_{r-1}, num_classes],并且是dtype float32或float64的未缩放的日志概率。
返回与logits具有相同类型的加权损失Tensor。
training():
training的代码如下图所示:
def training(loss, learning_rate):
tf.summary.scalar('loss', loss)
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)
return train_op
对于tf.summary.scalar()函数的理解:TensorBoard可以将训练过程中的各种绘制数据展示出来,包括标量(scallars),图片(images),音频(Audio),计算图(graph)分布,直方图(histograms)和嵌入式向量。
如果我们需要使用TensorBoard展示数据的话,我们需要在执行Tensorflow就计算图的过程中,将各种类型的数据汇总并记录到日志文件中。然后使用TensorBoard读取这些日志文件,解析数据并产生数据可视化的Web页面,让我们可以在浏览器中观察各种汇总数据。summary_op包括了summary.scalar、summary.histogram、summary.image等操作,这些操作的输出是各种summary protobuf,最后通过summary.writer写入到event文件中。
Tensorflow API中包含系列生成summary数据的API接口,这些函数将汇总信息存放在protobuf中,以字符串形式表达。具体的更加细致的用法可以参考博客。接下来的操作就跟之前的一样,使用梯度下降算法优化迭代更新,不同的是我们生成一个变量用于保存全局训练步骤(global training step)的数值。
之后还多了一个函数,如下所示:
def evaluation(logits, labels):
correct = tf.nn.in_top_k(logits, labels, 1)
return tf.reduce_sum(tf.cast(correct, tf.int32))
tf.nn.in_top_k组要是用于计算预测的结果和实际结果的是否相等,返回一个bool类型的张量,tf.nn.in_top_k(prediction, target, K):prediction就是表示你预测的结果,大小就是预测样本的数量乘以输出的维度,类型是tf.float32等。target就是实际样本类别的标签(是mnist标签对应的下标值,一个10维的标签代表一个值(这个十维的标签的最大值的下标),具体的解析参考博客),大小就是样本数量的个数。K表示每个样本的预测结果的前K个最大的数里面是否含有target中的值。一般都是取1。
tf.cast表示将数据转化为tf.int32格式。tf.reduce_sum表示为压缩求和。
fully_connected_feed.py
在开始训练之前我们需要先用tf.placeholder占位符帮手写体数据集占一个位置,这里我们的标签的维度是[batch_size, 1]:
def placeholder_inputs(batch_size):
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
run_training():
在开始训练之前,我们需要去读取数据:
data_sets = input_data.read_data_sets(FLAGS.input_data_dir, FLAGS.fake_data)
read_data_sets函数是input_data.py源码中的函数,主要用于读取数据,它需要三个参数:
- train_dir——文件夹的文件夹的位置
- fake_data——是否使用假数据,默认为False
- one_hot——是否把标签转为一维向量,默认为False,由于这里没有采用one-hot编码,那么这里的返回值就是图片数字的下标,也就是图片数字到底是几。是一个单纯的数字,而不是一个十维的向量([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])。
在run_training()
这个函数的一开始,是一个Python语言中的with
命令,这个命令表明所有已经构建的操作都要与默认的tf.Graph
全局实例关联起来。
with tf.Graph().as_default():
tf.Graph
实例是一系列可以作为整体执行的操作。TensorFlow的大部分场景只需要依赖默认图表一个实例即可。利用多个图表的更加复杂的使用场景也是可能的,但是超出了本教程的范围。
之后的话,我们需要调用之前写好的神经网络来对其进行训练,在训练之前需要调用之前写好的占位符函数placeholder_input()来给输入数据占个位置:
images_placeholder, labels_placeholder = placeholder_inputs(FLAGS.batch_size)
这样的话我们就假装我们有了输入的图片和其对应的标签(其实是没有的,我们需要之后再将其传进来,嘻嘻),接下来我们构建图的输出公式:
logits = mnist.inference(images_placeholder,FLAGS.hidden1,FLAGS.hidden2)
选择损失和训练参数:
loss = mnist.loss(logits, labels_placeholder)
train_op = mnist.training(loss, FLAGS.learning_rate)
评估模型结果指标:
eval_correct = mnist.evaluation(logits, labels_placeholder)
接下来我们把图运行过程中发生的事情(产生的数据记录下来):
summary = tf.summary.merge_all()
初始化变量:
init = tf.global_variables_initializer()
建立一个保存训练中间数据的存档点:
saver = tf.train.Saver()
建立会话:
sess = tf.Session()
创建一个记事本写入器(这里我也想吐槽一下,为什么不之前的创建记事本放在一起):
summary_writer = tf.summary.FileWriter(FLAGS.log_dir, sess.graph)
之后再初始化变量:
sess.run(init)
做完这些基本的操作之后我们就可以对其进行迭代训练了,但是在迭代训练之前我们得处理一下我们的输入数据,之前我们一直都是用tf.placeholder来假装我们有输入数据,现在我们需要将真实数据传入进来:
def fill_feed_dict(data_set, images_pl, labels_pl):
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size,FLAGS.fake_data)
feed_dict = {images_pl: images_feed,labels_pl: labels_feed}
return feed_dict
fill_feed_dict函数输入是数据,图像placeholder的变量名称,和标签placeholder的变量名称,返回一个字典,即需要传入到会话图中的数据。再接着就可以对其进行迭代训练:
for step in xrange(FLAGS.max_steps):
start_time = time.time()
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数组,而这些数组中包含了当前这步训练中对应Tensor的值。由于train_op
并不会产生输出,其在返回的元祖中的对应元素就是None
,所以会被抛弃。但是,如果模型在训练中出现偏差,loss
Tensor的值可能会变成NaN,所以我们要获取它的值,并记录下来。我们也希望记录一下程序运行的时间:
duration = time.time() - start_time
如果进行顺利的话,我们每隔100步,打印一下步数,损失函数值,和程序运行的时间。
if step % 100 == 0:
print('Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration))
summary_str = sess.run(summary, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)
summary_writer.flush()
在每次运行summary时,都会往事件文件中写入最新的即时数据,函数的输出会传入事件文件读写器(writer)的add_summary()函数。
我们也希望能够保存我们的训练模型:
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)
接下来我们定义一个评估模型的函数:
def do_eval(sess,
eval_correct,
images_placeholder,
labels_placeholder,
data_set):
true_count = 0 # Counts the number of correct predictions.
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)
precision = float(true_count) / num_examples
print('Num examples: %d Num correct: %d Precision @ 1: %0.04f' %
(num_examples, true_count, precision))
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)
每隔一千个训练步骤,我们的代码会尝试使用训练数据集与测试数据集,对模型进行评估。do_eval
函数会被调用三次,分别使用训练数据集、验证数据集合测试数据集。
总的代码文件目录如下:
总的代码文档链接:链接:https://pan.baidu.com/s/1TqOJSWeHbIV6kMJBwT1mYA 提取码:2h6h
https://github.com/18279406017/code-of-csdn/tree/master/Tensorflow/six
参考
http://www.tensorfly.cn/tfdoc/tutorials/mnist_tf.html
https://tensorflow.googlesource.com/tensorflow/+/master/tensorflow/g3doc/tutorials/mnist/tf/index.md
https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/mnist/mnist.py