写在前面的话
其实有人组织了翻译TensorFlow文档,在github上的项目,但是我还是比较喜欢看原始的文档,边看边写,纯粹自娱自乐,也是看看自己是否真的认真看了并且有些理解了。
正文
主要内容来自TensorFlow的官方教程,就是变读边学,顺便记录下来,水平有限。
这个教程为了刚开始接触机器学习和TensorFlow的读者准备的,如果你已经知道了什么是MNIST,什么是softmax(多项对数)回归,你可以查看Deep MINST for Experts。请确认了在开始该教程之前你已经安装了TensorFlow。
当一个程序员开始学习编程的时候,传统的第一件事情就是输出“Hello World”。机器学习中的Hello World程序就是MNIST。
MNIST是一个简单的计算机视觉数据库。其包含了手写的数字如:
其也包含每张图像的标签,告诉我们这个图像是什么。例如,上述图像的标签就是5,0,4和1。
在这个教程中,我们将会训练一个模型来查看图像并且预测图像上是什么数字。我们的目标并不是训练一个真正精细的模型,实现最先进(state-of-the-art)的性能——虽然我们将会在之后给出代码来实现这个目标。我们目的是接触和学习使用TensorFlow。因此,我们将会从一个非常简单的模型开始,叫做Softmax回归。
这个教程实际的代码很短,所有有意思的事情发生在仅仅三行之中。然而学习在代码之后的想法是很重要的。包括TensorFlow如何起作用,和其核心的机器学习概念。因此,我们将会十分仔细地完成代码。
关于这个教程
这个教程一行行地解释在minist_softmax.py代码中发生的事情。
你可以用不同的方式使用这个教程,包括
- 随着你阅读每行的注释,逐行复制和黏贴代码段到Python环境中。
- 在通读注释之前或者之后运行整个mnist_softmax.py Python文件。
我们将会如何完成这个教程:
- 了解MNIST数据和softmax回归
- 创建一个模型根据查看图像中的每一个像素识别数字
- 使用Tensorflow通过看数以千计的例子来训练模型来识别图像(运行Tensorflow会话来完成)。
- 使用我们的测试数据来检验模型的准确率
MNIST Data
MNIST数据在Yann LeCun的网站。如果你为了这个教程在代码中拷贝和黏贴数据,以如下两行代码开始将会自动下载和读取数据。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
MNIST数据分为三个部分,55000训练数据(mnist.train),10000测试数据(mnist.test),5000 验证数据。这个划分十分重要。在机器学习中我们有着并不用来学习的独立数据,这样我们能够确保我们学习的模型确实是有效。
正如之前提到的,每个MNIST数据有两个部分,一张手写的数字的图像,一个对应的标签。我们称其为图像x和标签y。训练集和测试集都包含图像和它们对应的标签。在例子中训练图像为mnist.train.images,它们的训练标签为mnist.train.labels
每个图像为28像素*28像素,我们将其解释为一个大的数字数组。
我们将这个数组拍平为一个28*28=784数字的向量。我们怎么拍平这个队列并不重要,只要我们能够在图像中间保持一致就行。从这个角度来看,MNIST图像只是784维向量中的一堆点,拥有着十分丰富的结构(警告:可视化计算密集)。
拍平数据抛弃了关于图像的2D结构的信息。这是否不妥当呢?当然,最好的计算机视觉的方法会利用这个结构,我们在之后的教程中也会利用到。但是这里使用的是简单的方法,一个softmax回归,丢弃结构数据并不会不妥当。
结果就是mnist.train.images是一个张量(一个n维的数组),大小为55000*784。第一位是一个到图像序列的索引。第二维是到图像的每个像素的索引。在张量中的每一个入口都是一个为0或者为1的像素,代表了在一个指定的图像中的指定像素。
在MNIST中的每一张图像有一个对应的标签,一个在0到9之间的数字代表了画在图像上的数字。
为了达到这个教程的目的,我们将会希望我们的标签为”one-hot vectors”。一个ont-hot向量为一个向量,其中在大多数的维度中值为0,在一个单独的维度上为1.在这个例子中,第n个数字将会被一个1在第n维的向量代表,例如,3将会是[0,0,0,1,0,0,0,0,0,0]。相似的,mnist.train.labels 是一个[50000, 10]大小的浮点数数组。
现在我们准备创建我们的模型了。
Softmax回归
我们知道在MNIST中间的每张图像是一个在0到9之间的手写数字。所以每张图像只会给出10个可能的结果。我们希望能够查看一张图像并给出其可能为某个数字的概率。例如,我们的模型可能看一张画着9的图像认为其80%的概率为9,对于认为其5%的概率为8(因为顶上的圆)以及一些概率为其它的数字.因为并不能100%确定为某一个数字。
这是一个softmax回归为一个自然,简单模型的经典例子。如果你想要给一个可能为几个之一的东西之一的对象赋予概率,softmax是一个选择,因为softmax给我们一系列在0与1之间的值并且相加为1。即使在以后,我们训练更加复杂的模型,最后的一步可能就是一层softmax。
一个softmax回归有两步:第一步我们添加我们的输入在一个确定的类中证据,然后我们转换这些证据为概率。
为了统计一个给定的图像在一个特定的类中的证据(evidence),我们把这些像素的强度值做一个加权和。如果一个像素有着高强度值为图像并不处于该类中的证据,那么权重就是负的,如果有着高强度值为该图像处于该类中的证据,那么权重就是正的。
如下的图像展示了一个模型中每个类学习到的权重,红色代表负值,蓝色代表正值。
我们同时也提供了额外的证据称之为偏差( We also add some extra evidence called a bias)。至少我们希望一些东西更像独立于输入。就过就是在输入为x时候类i的证据为:
其中Wi是权重,bi为类i的偏差,j是输入图像x的像素的索引。然后我们适用softmax函数转换证据提示为预测概率y:
这里softmax作为一个“激发”或者“链接”函数。将我们线性函数的输出重塑为我们希望的类型–在这个例子中是在十个选项中的概率分布。你可以理解为转换相应的证据为我们的输入在每个选项中的概率,定义为:
如果你展开这个方程,可以得到:
但是通常更加有帮助的是按照第一种方式理解softmax:对输入取幂然后归一化。取幂指数意味着一个增加权重的证据单元为在之前的权重上乘法增加,一个减少权重的证据单元在之前的权重上衰减。没有任何一个假设有着0或者负数的权重。Softmax然后归一化这些权重,这样它们加起来为1,形成一个合法的概率分布(要是想获得softmax函数的更多指导,阅读Michael Nielsen的书上的章节)
你可以认为softmax回归看起来像如下的东西,虽然实际上有着更多的x。对于每个输入,我们计算这些x的加权和,添加一个偏差,然后使用softmax。
如果我们将其写为等式,我们得到:
我们可以向量化这个过程,将其转换为一个矩阵相乘和向量相加的形式,这对于计算效率很有帮助(这也是一个很有效的思维方式)。
我们可以简写为:
现在我们将其转换为一些TensorFlow可以使用的东西。
实现回归
为了在Python中进行有效的数字计算,我们通常适用类似于NumPy这样使用一些其他高效语言来实现复杂计算的库。不幸的是,这样仍然在每个切换回Python的操作中有着巨大开销。如果在GPU上或者进行分布式计算这样有着大量传输数据损耗的时候,这种开销更为严重。
TensorFlow也是使用非Python语言来做这些重活,但是其更进一步来避免了这些开销。TensorFlow让我们描述一个相互交互操作的图,然后将其这个运行在Python之外(类似做法在一些机器学习库中也能看到),避免了每步运行时候和Python切换的开销。
为了使用TensorFlow,我们首先需要import:
import tensorflow as tf
我们通过操作符号变量来描述这些交互操作。让我们创建:
x = tf.placeholder(tf.float32, [None, 784])
x并不是一个指定的值,而是一个placeholder,一个当我们要TensorFlow运行计算时候的输入数据。我们希望输入任何数量的MNIST图像,每一个都拍扁为一个784维的向量。我们用一个二维的浮点数张量来表示,形式为[None, 784]。(这里None指的是一个维度可以是任意长度)
我们的模型同时也需要权重和偏差。我们可以设想这些为额外的输入,但是TensorFlow有着更好的方式来处理:Variable。一个Variable是一个在TensorFlow相互交互图中可修改的张量。其可以在计算中被使用和修改。对于机器学习应用,通常模型参数为Variables:
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
我们通过给定tf.Variable初始化的值来创建Variable:在这个例子中我们初始化了W和b为全是0的张量。因为我们要学习W和b,他们的初始值是多少并不重要(对这里存疑,在机器学习课程中好像说过权重初始值最好不要一样,不知道是不是记混了)。
我们现在开始实现我们的模型,我们仅仅使用一行来定义:
y = tf.nn.softmax(tf.matmul(x, W) + b)
首先我们通过tf.matmul(x, W)表达式将x和W相乘。然后加上b,最后应用tf.nn.softmax。
我们仅仅花了一行来定义我们的模型,在通过几行的设置之后。这并不是因为TensorFlow被设计为让一个softmax回归及其简单。这只是一个十分平滑的方式来描述许多种的数字计算,从机器学习模型到物理模拟。一旦定义,我们的模型就能够被运行在不同的设备上:你电脑的CPU,GPU,甚至手机!
训练
为了训练我们的模型,我们需要定义什么是好的模型。实际上,在机器学习中我们通常定义什么是坏的模型。我们称之为代价或者损失。其代表了我们的模型和我们需要的结果之间的距离。我们尝试去最小化这个误差,误差越小,模型越好。
一个十分常见和不错的定义模型损失的函数称为“交叉熵(cross entropy)”。交叉熵来自于信息理论的信息压缩编码,但是随后成为从赌博到机器学习等很多领域的一个重要概念。其定义为:
其中y是我们预测的概率分布,y’是真正的分布(例子中就是数字标签的one-hot向量)。在某种粗略的角度来说,交叉熵测量了我们的预测对于事实的无效程度。深入了解交叉熵的更多细节背离了我们教程的目的,但是值得去理解。
为了实现交叉熵,我们需要首先添加一个新的placeholder来输入这些正确的答案:
y_ = tf.placeholder(tf.float32, [None, 10])
然后我们可以实现交叉熵函数:
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))
首先,tf.log计算了y的每个元素的对数,接下来我们将y_中的每个元素乘以tf.log(y)中的每个对应的元素,然后tf.reduce_sum将y的元素的第二维数据相加,因为reducetion_indices=[1]参数。最后,tf.reduce_mean批处理计算这些例子的均值。
(注意在源代码中,我们并不使用这个公式,因为它在数据上不稳定。我们在没有归一化的对数上使用tf.nn.softmax_cross_entropy_with_logits(例如我们调用softmax_cross_entropy_with_logits在tf.matmul(x,W)+b上),因为这个函数在内部计算softmax激活上数值更加稳定。在你的代码中,考虑使用tf.nn.(sparse_)softmax_across_entropy_woth_logit替代。)
现在我们知道我们希望模型做什么,就很容易让TensorFlow训练模型来做这个。因为TensorFlow知道你计算的整个图,所以能够自动使用[反向传播算法](http://colah.github.io/posts/2015-08-Backprop/)来有效决定你的变量如何影响你需要去最小化的损失。然后能够使用你选择的优化算法来修改变量和降低损失。
train_step=tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
在这个例子中,我们让TensorFlow去最小化cross_entropy使用梯度下降算法,学习速率为0,5.梯度下降是一个简单的过程,其中TensorFlow简单地慢慢修改值使得损失不断下降。但是TensorFlow也提供了许多其他的优化算法。只需要简单地调整一行就可使使用其中一个算法。
TensorFlow这里实际上做的就是添加一个新的实现反向传播算法和梯度下降操作到你的图中。然后能够在运行的时候,每次完成一次梯度下降训练,调整你的变量来降低损失。
现在我们设置了一个训练的模型,在启动之前最后一件事情就是创建一个操作来初始化我们创建的变量。注意这个定义了操作但是并没有实际运行。
init = tf.initialize_all_variables()
我们能够在Session中间启动模型,现在我们运行初始化变量的操作:
sess = tf.Session()
sess.run(init)
让我们开始训练,我们运行这个训练步骤1000次:
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
在循环的每一步,我们从我们的训练集中得到一个“批”的一百个随机数据点。我们运行train_step反馈批量数据来替代placeholders。
使用一个小批次的随机数据被称为随机训练,在这个例子中是随机梯度下降。理论上我们希望在训练的每一步使用我们的所有数据因为这会给我们更好地了解我们该做什么,但是这个消耗大量计算。我们在每个步骤中使用不同的子集。这么做消耗的计算量小而且能够和使用全部数据的收益接近。
评价我们的模型
我们的模型表现得如何呢?
首先让我们指出我们预测对了的标签。tf.argmax是一个很有用的函数,给出了一个张量沿着某些轴的最高值的索引。例如,tf.argmax(y,1)是我们模型对于每个输入给出的最可能的情况。同事tf.argmax(y_, 1)是正确的标签。我们使用tf.equal来检查我们的预测是否匹配了事实。
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
这会给我们一个布尔列表。为了获取多少比例是正确的,我们转换其为浮点数然后获取均值。例如[True, False, True, True]将会成为[1,0,1,1],因此均值将会是0.75。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
最后我们检验我们测试数据上的正确性:
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_:mnist.test.labels})
结果大约为92%。
这是不是很棒呢?当然,并不是,实际上,这个并不好。这个是因为我们使用了一个十分简单的模型。随着一些小的变化,我们可以得到97%。最好的模型甚至能够达到超过99.7%的正确率!(获取更多的信息,可以看结果列表)。
真正重要的是我们从这个模型中学习到了什么。不过如果你仍然对这些结果感到有点失落,请查看下一个教程,我们会做得更好并且学习如何使用TensorFlow构建更复杂的模型!