Tensorflow官方文档中文版——第二章(瞎奖杯写)
包含如下几个部分:
1.面向机器学习初学者的 MNIST 初级教程
2.面向机器学习专家的 MNIST 高级教程
3.TensorFlow 使用指南
4.卷积神经网络
5.单词的向量表示(word embedding)
6.循环神经网络 (Recurrent Neural Network, 简称 RNN)
7.序列到序列模型 (Sequence-to-Sequence Model)
8.Mandelbrot 集合
9.偏微分方程
10.MNIST 数据下载
MNIST机器学习入门
当我们开始学习编程的时候,第一件事往往是学习打印"Hello World"。就好比编程入门有Hello World,机器学
习入门有MNIST。
MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片:
它也包含每一张图片对应的标签,告诉我们这个是数字几。比如,上面这四张图片的标签分别是5,0,4,1。
在此教程中,我们将训练一个机器学习模型用于预测图片里面的数字。我们的目的不是要设计一个世界一流的复
杂模型 -- 尽管我们会在之后给你源代码去实现一流的预测模型 -- 而是要介绍下如何使用TensorFlow。所
以,我们这里会从一个很简单的数学模型开始,它叫做Softmax Regression。
MNIST数据集
MNIST数据集的官网是Yann LeCun's website。在这里,我们提供了一份python源代码用于自动下载和安装这个数据集。下载下来的数据集被分成两部分:60000行的训练数据集( mnist.train )和10000行的测试数据集( mnist.test )。这样的切分很重要,在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能,从而更加容易把设计的模型推广到其他数据集上(泛化)。
正如前面提到的一样,每一个MNIST数据单元有两部分组成:一张包含手写数字的图片和一个对应的标签。我们把这些图片设为“xs”,把这些标签设为“ys”。训练数据集和测试数据集都包含xs和ys,比如训练数据集的图片是mnist.train.images ,训练数据集的标签是mnist.train.labels 。每一张图片包含28X28个像素点。我们可以用一个数字数组来表示这张图片:
我们把这个数组展开成一个向量,长度是 28x28 = 784。如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开。从这个角度来看,MNIST数据集的图片就是在784维向量空间里面的点, 并且拥有比较复杂的结构(提醒: 此类数据的可视化是计算密集型的)。
展平图片的数字数组会丢失图片的二维结构信息。这显然是不理想的,最优秀的计算机视觉方法会挖掘并利用这些结构信息,我们会在后续教程中介绍。但是在这个教程中我们忽略这些结构,所介绍的简单数学模型,softmax回归(softmax regression),不会利用这些结构信息。
因此,在MNIST训练数据集中, mnist.train.images 是一个形状为[60000, 784] 的张量,第一个维度数字用来索引图片,第二个维度数字用来索引每张图片中的像素点。在此张量里的每一个元素,都表示某张图片里的某个像素的强度值,值介于0和1之间。
相对应的MNIST数据集的标签是介于0到9的数字,用来描述给定图片里表示的数字。为了用于这个教程,我们使标签数据是"one-hot vectors"。 一个one-hot向量除了某一位的数字是1以外其余各维度数字都是0。所以在此教程中,数字n将表示成一个只有在第n维度(从0开始)数字为1的10维向量。比如,标签0将表示成([1,0,0,0,0,0,0,0,0,0,0])。因此, mnist.train.labels 是一个[60000, 10] 的数字矩阵。
Softmax回归介绍
softmax回归(softmax regression)分两步:
第一步
为了得到一张给定图片属于某个特定数字类的证据(evidence),我们对图片像素值进行加权求和。如果这个像素具有很强的证据说明这张图片不属于该类,那么相应的权值为负数,相反如果这个像素拥有有利的证据支持这张图片属于这个类,那么权值是正数。下面的图片显示了一个模型学习到的图片上每个像素对于特定数字类的权值。红色代表负数权值,蓝色代表正数权值。
我们也需要加入一个额外的偏置量(bias),因为输入往往会带有一些无关的干扰量。因此对于给定的输入图片x 它代表的是数字i 的证据可以表示为
其中bi代表数字i 类的偏置量,j 代表给定图片x 的像素索引用于像素求和。然后用softmax函数可以把这些证据转换成概率y:
这里的softmax可以看成是一个激励(activation)函数或者链接(link)函数,把我们定义的线性函数的输出转换成我们想要的格式,也就是关于10个数字类的概率分布。因此,给定一张图片,它对于每一个数字的吻合度可以被softmax函数转换成为一个概率值。softmax函数可以定义为:
但是更多的时候把softmax模型函数定义为前一种形式:把输入值当成幂指数求值,再正则化这些结果值。这个幂运算表示,更大的证据对应更大的假设模型(hypothesis)里面的乘数权重值。反之,拥有更少的证据意味着在假设模型里面拥有更小的乘数系数。假设模型里的权值不可以是0值或者负值。Softmax然后会正则化这些权重值,使它们的总和等于1,以此构造一个有效的概率分布。
对于softmax回归模型可以用下面的图解释,对于输入的xs 加权求和,再分别加上一个偏置量,最后再输入到softmax函数中:
如果把它写成一个等式,我们可以得到:
我们也可以用向量表示这个计算过程:用矩阵乘法和向量相加。这有助于提高计算效率。(也是一种更有效的思考方式)
更进一步,可以写成更加紧凑的方式:
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) #载入mnist数据 x = tf.placeholder("float", [None, 784])#x 不是一个特定的值,而是一个占位符placeholder ,我们在TensorFlow运行计算时输入这个值 W = tf.Variable(tf.zeros([784,10]))#W 的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量,每一位对应不同数字类 b = tf.Variable(tf.zeros([10])) y = tf.nn.softmax(tf.matmul(x,W) + b) y_ = tf.placeholder("float", [None,10]) cross_entropy = -tf.reduce_sum(y_*tf.log(y)) train_step=tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)#学习过程 init = tf.initialize_all_variables()#初始化所有变量 sess = tf.Session() sess.run(init) for i in range(1000):#该循环的每个步骤中,我们都会随机抓取训练数据中的100个批处理数据点,然后我们用这些数据点作为参数替换之前的占位符来运行train_step 。 [batch_xs,batch_ys]=mnist.train.next_batch(100)#从训练集中取出100个数据以及其标签 sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})#将其带入之前的占位符中 correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))#这行代码会给我们一组布尔值 accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))#正确率 print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
评估我们的模型
首先让我们找出那些预测正确的标签。tf.argmax 是一个非常有用的函数,它能给出某个tensor对象在某一维上的其数据最大值所在的索引值。由于标签向量是由0,1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1) 返回的是模型对于任一输入x预测到的标签值,而tf.argmax(y_,1) 代表正确的标签,我们可以用tf.equal 来检测我们的预测是否真实标签匹配(索引位置一样表示匹配)。
深入MNIST
运行TensorFlow的InteractiveSession
Tensorflow依赖于一个高效的C++后端来进行计算。与后端的这个连接叫做session。一般而言,使用TensorFlow程序的流程是先创建一个图,然后在session中启动它。这里,我们使用更加方便的InteractiveSession 类。通过它,你可以更加灵活地构建你的代码。它能让你在运行图的时候,插入一些计算图,这些计算图是由某些操作(operations)构成的。这对于工作在交互式环境中的人们来说非常便利,比如使用IPython。如果你没有使用InteractiveSession ,那么你需要在启动session之前构建整个计算图,然后启动该计算图。
计算图
为了在Python中进行高效的数值计算,我们通常会使用像NumPy一类的库,将一些诸如矩阵乘法的耗时操作在Python环境的外部来计算,这些计算通常会通过其它语言并用更为高效的代码来实现。但遗憾的是,每一个操作切换回Python环境时仍需要不小的开销。如果你想在GPU或者分布式环境中计算时,这一开销更加可怖,这一开销主要可能是用来进行数据迁移。
TensorFlow也是在Python外部完成其主要工作,但是进行了改进以避免这种开销。其并没有采用在Python外部独立运行某个耗时操作的方式,而是先让我们描述一个交互操作图,然后完全将其运行在Python外部。这与Theano或Torch的做法类似。
因此Python代码的目的是用来构建这个可以在外部运行的计算图,以及安排计算图的哪一部分应该被运行。详情请查看基本用法中的计算图表一节。
构建Softmax 回归模型
在这一节中我们将建立一个拥有一个线性层的softmax回归模型。在下一节,我们会将其扩展为一个拥有多层卷积网络的softmax回归模型。
占位符
我们通过为输入图像和目标输出类别创建节点,来开始构建计算图。
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
这里的x 和y 并不是特定的值,相反,他们都只是一个占位符,可以在TensorFlow运行某一计算时根据该占位符输入具体的值。
输入图片x 是一个2维的浮点数张量。这里,分配给它的shape 为[None, 784] ,其中784 是一张展平的MNIST图片的维度。None 表示其值大小不定,在这里作为第一个维度值,用以指代batch的大小,意即x 的数量不定。输出类别值y_ 也是一个2维张量,其中每一行为一个10维的one-hot向量,用于代表对应某一MNIST图片的类别。虽然placeholder 的shape 参数是可选的,但有了它,TensorFlow能够自动捕捉因数据维度不一致导致的错误。
变量
我们现在为模型定义权重W 和偏置b 。可以将它们当作额外的输入量,但是TensorFlow有一个更好的处理方式: 变量。一个变量代表着TensorFlow计算图中的一个值,能够在计算过程中使用,甚至进行修改。在机器学习的应用过程中,模型参数一般用Variable 来表示。
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
我们在调用tf.Variable 的时候传入初始值。在这个例子里,我们把W 和b 都初始化为零向量。W 是一个784x10的矩阵(因为我们有784个特征和10个输出值)。b 是一个10维的向量(因为我们有10个分类)。
变量需要通过seesion初始化后,才能在session中使用。这一初始化步骤为,为初始值指定具体值(本例当中是全为零),并将其分配给每个变量,可以一次性为所有变量完成此操作。
sess.run(tf.initialize_all_variables())
类别预测与损失函数
现在我们可以实现我们的回归模型了。这只需要一行!我们把向量化后的图片x 和权重矩阵W 相乘,加上偏置b ,然后计算每个分类的softmax概率值。
y = tf.nn.softmax(tf.matmul(x,W) + b)
可以很容易的为训练过程指定最小化误差用的损失函数,我们的损失函数是目标类别和预测类别之间的交叉熵。
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
注意, tf.reduce_sum 把minibatch里的每张图片的交叉熵值都加起来了。我们计算的交叉熵是指整个minibatch的。
构建一个多层卷积网络
在MNIST上只有91%正确率,实在太糟糕。在这个小节里,我们用一个稍微复杂的模型:卷积神经网络来改善效果。这会达到大概99.2%的准确率。虽然不是最高,但是还是比较让人满意。
权重初始化
为了创建这个模型,我们需要创建大量的权重和偏置项。这个模型中的权重在初始化时应该加入少量的噪声来打破对称性以及避免0梯度。由于我们使用的是ReLU神经元,因此比较好的做法是用一个较小的正数来初始化偏置项,以避免神经元节点输出恒为0的问题(dead neurons)。为了不在建立模型的时候反复做初始化操作,我们定义两个函数用于初始化。
卷积和池化
TensorFlow在卷积和池化上有很强的灵活性。我们怎么处理边界?步长应该设多大?在这个实例里,我们会一直使用vanilla版本。我们的卷积使用1步长(stride size),0边距(padding size)的模板,保证输出和输入是同一个大小。我们的池化用简单传统的2x2大小的模板做max pooling。为了代码更简洁,我们把这部分抽象成一个函数。
第一层卷积
现在我们可以开始实现第一层了。它由一个卷积接一个max pooling完成。卷积在每个5x5的patch中算出32个特征。卷积的权重张量形状是[5, 5, 1, 32] ,前两个维度是patch的大小,接着是输入的通道数目,最后是输出的通道数目。 而对于每一个输出通道都有一个对应的偏置量。
第二层卷积
为了构建一个更深的网络,我们会把几个类似的层堆叠起来。第二层中,每个5x5的patch会得到64个特征。
密集连接层
现在,图片尺寸减小到7x7,我们加入一个有1024个神经元的全连接层,用于处理整个图片。我们把池化层输出的张量reshape成一些向量,乘上权重矩阵,加上偏置,然后对其使用ReLU。
Dropout
为了减少过拟合,我们在输出层之前加入dropout。我们用一个placeholder 来代表一个神经元的输出在dropout中保持不变的概率。这样我们可以在训练过程中启用dropout,在测试过程中关闭dropout。 TensorFlow的tf.nn.dropout 操作除了可以屏蔽神经元的输出外,还会自动处理神经元输出值的scale。所以用dropout的时候可以不用考虑scale。
输出层
最后,我们添加一个softmax层,就像前面的单层softmax regression一样。
训练和评估模型
这个模型的效果如何呢?
为了进行训练和评估,我们使用与之前简单的单层SoftMax神经网络模型几乎相同的一套代码,只是我们会用更加复杂的ADAM优化器来做梯度最速下降,在feed_dict 中加入额外的参数keep_prob 来控制dropout比例。然后每100次迭代输出一次日志。
import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data #为了不在建立模型的时候反复做初始化操作,我们定义两个函数用于初始化。 def weight_variable(shape): initial=tf.truncated_normal(shape,stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial=tf.constant(0.1,shape=shape) return tf.Variable(initial) def conv2d(x,W): return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME') #我们的卷积使用1步长(stride size),0边距(padding size)的模板,保证输出和输入是同一个大小。 def max_pool_2x2(x): return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME') #我们的池化用简单传统的2x2大小的模板做max pooling if __name__=='__main__': mnist=input_data.read_data_sets("MNIST_data/",one_hot=True) #构图阶段 x = tf.placeholder("float", [None, 784])#x 不是一个特定的值,而是一个占位符placeholder ,我们在TensorFlow运行计算时输入这个值 y_ = tf.placeholder("float", [None,10]) #第一层 W_conv1=weight_variable([5,5,1,32]) b_conv1=bias_variable([32]) x_image=tf.reshape(x,[-1,28,28,1])#我们把x 变成一个4d向量,其第2、第3维对应图片的宽、高,最后一维代表图片的颜色通道数(因为是灰度图所以这里的通道数为1,如果是rgb彩色图,则为3)。 h_conv1=tf.nn.relu(conv2d(x_image,W_conv1)+b_conv1) h_pool1=max_pool_2x2(h_conv1) #第二层 W_conv2=weight_variable([5,5,32,64]) b_conv2=bias_variable([64]) h_conv2=tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2) h_pool2=max_pool_2x2(h_conv2) #密集连接层 W_fc1=weight_variable([7*7*64,1024]) b_fc1=bias_variable([1024]) h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*64]) h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1) #dropout keep_prob=tf.placeholder('float') h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob) #输出层 W_fc2=weight_variable([1024,10]) b_fc2=bias_variable([10]) y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2) #训练和评估 cross_entropy=-tf.reduce_sum(y_*tf.log(y_conv)) train_step=tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) correct_prediction=tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1))#返回布尔值 accuracy=tf.reduce_mean(tf.cast(correct_prediction,'float'))#计算正确率 sess=tf.Session() init = tf.initialize_all_variables() sess.run(init) for i in range(2000): batch=mnist.train.next_batch(50) if i%100==0: train_accuracy=accuracy.eval(session=sess,feed_dict={ x:batch[0],y_:batch[1],keep_prob:1.0}) print("step %d,training accuracy %g"%(i,train_accuracy)) train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}) print("test accuracy %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))
TensorFlow运作方式入门
输入与占位符(Inputs and Placeholders)
placeholder_inputs() 函数将生成两个tf.placeholder 操作,定义传入图表中的shape参数,shape参数中包括batch_size 值,后续还会将实际的训练用例传入图表。
images_placeholder = tf.placeholder(tf.float32, shape=(batch_size,IMAGE_PIXELS))
labels_placeholder = tf.placeholder(tf.int32, shape=(batch_size))
在训练循环(training loop)的后续步骤中,传入的整个图像和标签数据集会被切片,以符合每一个操作所设置的batch_size 值,占位符操作将会填补以符合这个batch_size 值。然后使用feed_dict 参数,将数据传入sess.run() 函数。
构建图表 (Build the Graph)
在为数据创建占位符之后,就可以运行mnist.py 文件,经过三阶段的模式函数操作: inference() , loss() ,和training() 。图表就构建完成了。
1. inference() —— 尽可能地构建好图表,满足促使神经网络向前反馈并做出预测的要求。
2. loss() —— 往inference图表中添加生成损失(loss)所需要的操作(ops)。
3. training() —— 往损失图表中添加计算并应用梯度(gradients)所需的操作。
推理(Inference)
inference() 函数会尽可能地构建图表,做到返回包含了预测结果(output prediction)的Tensor。
它接受图像占位符为输入,在此基础上借助ReLu(Rectified Linear Units)激活函数,构建一对完全连接层(layers),以及一个有着十个节点(node)、指明了输出logtis模型的线性层。每一层都创建于一个唯一的tf.name_scope 之下,创建于该作用域之下的所有元素都将带有其前缀。
with tf.name_scope('hidden1') as scope:
在定义的作用域中,每一层所使用的权重和偏差都在tf.Variable 实例中生成,并且包含了各自期望的shape。
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 "。
每个变量在构建时,都会获得初始化操作(initializer ops)。在这种最常见的情况下,通过tf.truncated_normal 函数初始化权重变量,给赋予的shape则是一个二维tensor,其中第一个维度代表该层中权重变量所连接(connect from)的单元数量,第二个维度代表该层中权重变量连接到的(connect to)单元数量。对于名叫hidden1 的第一层,相应的维度则是[IMAGE_PIXELS, hidden1_units] ,因为权重变量将图像输入连接到了hidden1 层。tf.truncated_normal 初始函数将根据所得到的均值和标准差,生成一个随机分布。
然后,通过tf.zeros 函数初始化偏差变量(biases),确保所有偏差的起始值都是0,而它们的shape则是其在该层中所接到的(connect to)单元数量。
图表的三个主要操作,分别是两个tf.nn.relu 操作,它们中嵌入了隐藏层所需的tf.matmul ;以及logits模型所需的另外一个tf.matmul 。三者依次生成,各自的tf.Variable 实例则与输入占位符或下一层的输出tensor所连接。
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 Tensor。
损失(Loss)
loss() 函数通过添加所需的损失操作,进一步构建图表。
首先, labels_placeholer 中的值,将被编码为一个含有1-hot values的Tensor。例如,如果类标识符为“3”,那么该值就会被转换为:
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
batch_size = tf.size(labels)
labels = tf.expand_dims(labels, 1)
indices = tf.expand_dims(tf.range(0, batch_size, 1), 1)
concated = tf.concat(1, [indices, labels])
onehot_labels = tf.sparse_to_dense(concated, tf.pack([batch_size, NUM_CLASSES]), 1.0, 0.0)
之后,又添加一个tf.nn.softmax_cross_entropy_with_logits 操作,用来比较inference() 函数与1-hot标签所输
出的logits Tensor。
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits,onehot_labels,name='xentropy')
然后,使用tf.reduce_mean 函数,计算batch维度(第一维度)下交叉熵(cross entropy)的平均值,将将该值
作为总损失。loss = tf.reduce_mean(cross_entropy, name='xentropy_mean')最后,程序会返回包含了损失值的Tensor。
训练
training() 函数添加了通过梯度下降(gradient descent)将损失最小化所需的操作。
首先,该函数从loss() 函数中获取损失Tensor,将其交给tf.scalar_summary ,后者在与SummaryWriter (见下文)配合使用时,可以向事件文件(events file)中生成汇总值(summary values)。在本篇教程中,每次写入汇总值时,它都会释放损失Tensor的当前值(snapshot value)。
tf.scalar_summary(loss.op.name, loss)
接下来,我们实例化一个tf.train.GradientDescentOptimizer ,负责按照所要求的学习效率(learning rate)应用梯度下降法(gradients)。
optimizer = tf.train.GradientDescentOptimizer(FLAGS.learning_rate)之后,我们生成一个变量用于保存全局训练步骤(global training step)的数值,并使用minimize() 函数更新系统中的三角权重(triangle weights)、增加全局步骤的操作。根据惯例,这个操作被称为train_op ,是TensorFlow会话(session)诱发一个完整训练步骤所必须运行的操作(见下文)。
global_step = tf.Variable(0, name='global_step', trainable=False)
train_op = optimizer.minimize(loss, global_step=global_step)
最后,程序返回包含了训练操作(training op)输出结果的Tensor。
训练模型
一旦图表构建完毕,就通过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 函数中没有传入参数,表明该代码将会依附于(如果还没有创建会话,则会创建新的会话)默认的本地会话。
生成会话之后,所有tf.Variable 实例都会立即通过调用各自初始化操作中的sess.run() 函数进行初始化。
init = tf.initialize_all_variables()
sess.run(init)
sess.run() 方法将会运行图表中与作为参数传入的操作相对应的完整子集。在初次调用时, init 操作只包含了变量初始化程序tf.group 。图表的其他部分不会在这里,而是在下面的训练循环运行。
训练循环
完成会话中变量的初始化之后,就可以开始训练了。
训练的每一步都是通过用户代码控制,而能实现有效训练的最简单循环就是:
for step in xrange(max_steps):
sess.run(train_op)
但是,本教程中的例子要更为复杂一点,原因是我们必须把输入的数据根据每一步的情况进行切分,以匹配之前生成的占位符。
向图表提供反馈
执行每一步时,我们的代码会生成一个反馈字典(feed dictionary),其中包含对应步骤中训练所要使用的例子,这些例子的哈希键就是其所代表的占位符操作。
fill_feed_dict 函数会查询给定的DataSet ,索要下一批次batch_size 的图像和标签,与占位符相匹配的Tensor则会包含下一批次的图像和标签。
images_feed, labels_feed = data_set.next_batch(FLAGS.batch_size)
然后,以占位符为哈希键,创建一个Python字典对象,键值则是其代表的反馈Tensor。
feed_dict = {
images_placeholder: images_feed,
labels_placeholder: labels_feed,
}
这个字典随后作为feed_dict 参数,传入sess.run() 函数中,为这一步的训练提供输入样例。
检查状态
在运行sess.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数组,而这些数组中包含了当前这步训练中对应Tensor的值。由于train_op 并不会产生输出,其在返回的元祖中的对应元素就是None ,所以会被抛弃。但是,如果模型在训练中出现偏差, loss Tensor的值可能会变成NaN,所以我们要获取它的值,并记录下来。假设训练一切正常,没有出现NaN,训练循环会每隔100个训练步骤,就打印一行简单的状态文本,告知用户当前
的训练状态。
if step % 100 == 0:
print 'Step %d: loss = %.2f (%.3f sec)' % (step, loss_value, duration)
状态可视化
为了释放TensorBoard所使用的事件文件(events file),所有的即时数据(在这里只有一个)都要在图表构建阶段合并至一个操作(op)中。
summary_op = tf.merge_all_summaries()
在创建好会话(session)之后,可以实例化一个tf.train.SummaryWriter ,用于写入包含了图表本身和即时数据具体值的事件文件。
summary_writer = tf.train.SummaryWriter(FLAGS.train_dir,graph_def=sess.graph_def)
最后,每次运行summary_op 时,都会往事件文件中写入最新的即时数据,函数的输出会传入事件文件读写器(writer)的add_summary() 函数。。
summary_str = sess.run(summary_op, feed_dict=feed_dict)
summary_writer.add_summary(summary_str, step)
事件文件写入完毕之后,可以就训练文件夹打开一个TensorBoard,查看即时数据的情况。
保存检查点(checkpoint)
为了得到可以用来后续恢复模型以进一步训练或评估的检查点文件(checkpoint file),我们实例化一个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)
评估模型
每隔一千个训练步骤,我们的代码会尝试使用训练数据集与测试数据集,对模型进行评估。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 测试数据集,只有在大量的超参数优化调整(hyperparameter tuning)之后才进行检查。但是,由于MNIST问题比较简单,我们在这里一次性评估所有的数据。
构建评估图表(Eval Graph)
在打开默认图表(Graph)之前,我们应该先调用get_data(train=False) 函数,抓取测试数据集。
test_all_images, test_all_labels = get_data(train=False)
在进入训练循环之前,我们应该先调用mnist.py 文件中的evaluation 函数,传入的logits和标签参数要与loss函数的一致。这样做事为了先构建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)
评估图表的输出(Eval Output)
之后,我们可以创建一个循环,往其中添加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 = float(true_count) / float(num_examples)
print ' Num examples: %d Num correct: %d Precision @ 1: %0.02f' % (
num_examples, true_count, precision)