这篇教程是面向刚开始接触机器学习TensorFlow的读者。如果你已经知道了什么是MNIST,并且知道softmax(多分类)回归是什么,你也许会喜欢这种快节奏的教程。在开始学习这个教程之前一定要安装TensorFlow。
有一个传统,当一个人学习如何编程时,第一件事情是先要打印“Hello World”。就像编程世界有“Hello World”一样,机器学习有MNIST。
MNIST是一个简单的计算机可视数据集。它由像这样的手写数字组成:
它也包含了每个图片的标签,它告诉我们图片上是那一个数字。例如,对于上面图片,标签是5,0,4,和1.
在这篇教程中,我将训练一个看图片,预测图片上的数字是什么的模型。我们的目标不是训练一个真正达到最高性能的复杂模型--尽管我们随后会会给你代码--而是尝试使用TensorFlow。因此,我们从一个被叫做Softmax回归的非常简单的模型开始。
这篇教程的实际代码非常短,核心代码只有三行。但是,理解这背后TensorFlow怎么工作和机器学习概念的核心的思想是非常重要的。由于这个原因,我们将非常仔细的研究这些代码。
关于这篇教程
这篇教程一行一行的解释了在mnist_softmax.py中的代码。
你可以用几种不同的方式使用这篇教程,包括:
在阅读每行代码的解释时,逐行的把每一个代码片段复制并粘贴到你的Python环境中。
在通读注释之前或之后运行整个mnist_softmax.py Python文件,并使用这个教程去理解你不清楚的代码。
我们将要在本教程中完成什么:
学习关于MNIST数据和softmax回归。
创建一个函数,这个函数是一个基于查看图片中的每一个像素来识别数字的模型。
使用TensorFlow训练模型,让它“看”上千个例子来识别数字(运行我们的第一个TensorFlow session来做这点)
使用我们的测试数据检查模型的准确率。
MNIST数据
MNIST数据被托管在Yann LeCun站点上. 如果你要从这个教程中复制粘贴代码,从这里开始,这两行代码会自动地下载并读取数据:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
MNIST数据被分割成了三部分:55,000条训练数据(nist.train), 10,000条测试数据(mnist.test),和5,000条验证数据(mnist.validation)。这个分割很重要:在机器学习中,我们从不学习的数据中分离数据很关键,这样我们就可以确保确实涵盖了我们学习的数据。
正如前面提到的,每一个MNIST数据有两部分:一个是手写数字的图片,和相应的标签。我们将声明图片为“x”,标签为“y”。训练数据集和测试数据集,都包含了图片和他们相应的标签;例如训练图片是mnist.train.images,训练标签是mnist.train.labels。
每个图片都是28X28的。我们可以把它转换成一个数字的大数组:
我们可以把这个数组展开成28x28 = 784的向量,不管我们如何展开数组,只要我们与图片保持一致就行。从这个角度看,MNIST图片在784维向量空间中只是一堆具有丰富结构的数据点(警告:高强度的可视化技术)。
扁平化数据丢掉了图片的二维结构信息。这不坏吗?好吧,最好的计算机视觉方法利用这种结构,我们将在后面的教程中用到。但是我们将在这里使用最简单的方法,softmax回归(定义见下文)。
结果是,mnist.train.images是一个[55000, 784]的tensor(一个n-维数组)。第一个维度是图片列表的索引,第二个维度是每一个图片中每个像素的索引。在tensor中的每一项都是特定图片之中特定像素的0到1之间的像素灰度。
在MNIST中的每一个图片都有一个对应的标签,标签上的一个0到9之间的数字表示图片中画的数字。
为了达到这篇教程的目的,我们希望我们的标签是“one-hot向量”。一个one-hot 向量是一个多数维度是0,一个维度是1的向量。在这案例中,第n个数字将被表示为在第n个维度是1的向量。例如,3将会是[0,0,0,1,0,0,0,0,0,0]。所以,mnist.train.labels是一个[55000, 10] 浮点型数组.
我们现在准备好实现我们的模型了!
softmax回归
我们知道在MNIST中的每一个图片是一个0到9的数字,因此给的图片只有10中可能。我们要通过看一个图片并给出它是每一个数字的概率。例如,我的模型看到一张9的图片,并80%确认他是9,但是给出5%的机会是8(由于顶部的圈),并且由于不是100%的确定,所以还有所有其他数字的一点可能。
这是一个经典的案例,其中softmax回归是一个自然简单的模型。如果你想把概率分配几个不同事物中的一个,softmax就是做这个的,因为softmax给我们一个值是0到1的列表,并且这些值加起来是1。即使在以后,我们训练更复杂的模型,最后一步也将是softmax层。
softmax回归有两个步骤:首先,我们把我们输入的证据累积都某个分类中,然后我们把这些证据转换成概率。
为了计算一个给定图片是在一个特定分类内的证据,我们计算了像素强度的加权和。如果具有高强度的像素对于图片属于那个分类的证据是不利的,那么权重就是负的,如果是有利的,那么权重就是正的。
下面的图表显示了,我们为这些分类中的每一个分类学习了模型的权重。红的表示负权重,而蓝的表示正权重。
我们也增加了一些额外的证据,叫做偏量。主要是,我们希望能够得到更加独立于输入的东西。结果是,给定一个输入x是分类i的证据是:
其中,Wi是分类i的权重,bi是分类i的偏量,j是输入图片x中所有像素的索引。然后我们使用
“softmax”函数把证据转换成与我们的预测概率吻合,“softmax”函数:
y=softmax(evidence)
这里softmax是做为一个“激活”或“连接”函数,把我们的线性函数输出调整成我们想要的形式——在这个案例中,概率分布要覆盖到10个案例。你可以把它看做是把相应的证据转换为我们输入的成为每个分类的可能性,它被定义为:
softmax(x)=normalize(exp(x))
如果你展开这个表达式,你会得到:
但是把softmax看做是第一种方式更有用:对输入求幂,然后再把他们规范化。求幂是指给一或多个证据单元增加任何假设乘法的权重。并且,相反地,至少有一个证据单元意味着一个假设得到了早期权重的一小部分。任何假设都不是零或负的权重。然后softmax规范化这些权重,让他们加起来等于1,形成有效的概率分布。(要得到一个更直观的softmax函数,看一看Michael Nielsen的书中完成了可视化交互的章节。)
尽管有很多的xs,但你可以想象一下我们的softmax回归像下面这个样子。对于每一个输出,我们计算了xs的加权和,加了偏量,然后使用了softmax
[图片上传失败...(image-287673-1516326657579)]
如果我们把它写成方程式,我们得到:
[图片上传失败...(image-5e6432-1516326657579)]
我们可以“矢量化”这个过程,把它转换成矩阵乘法和向量加法,这样有助于提高计算效率。(这也是一种有用的思考方式)
[图片上传失败...(image-21b70-1516326657580)]
更简洁些,我们可以写成:
y=softmax(Wx+b)
现在,让我们把这些转成Tensorflow 可以使用的。
实现回归
为了在Python中进行有效的数值计算,我们通常使用如Numpy这样的库,它在Python之外,使用其他语言实现更高效的代码,做一些像的矩阵乘法这样耗时的操作。不幸的是,每次操作切换回Python仍然是一个很大的开销,如果你想在GPU或分布式上运行计算,这种开销尤其糟糕,数据的传输成本更高。
在Python之外,TensorFlow也做了些重量级的操作,但是为了避免这个还有更多的工作要做。为了不运行独立于Python的耗时操作,TensorFlow让我们描述一个整个运行在Python之外的交互操作的图形。(类似的方法可以在一些机器学习库中看到。)
为了使用TensorFlow,首先我们需要导入它。
import tensorflow as tf
我通过控制符号变量描述了这些交互操作,让我们创建一个:
x = tf.placeholder(tf.float32, [None, 784])
x不是一个特定的值,它是一个占位符,当我们让TensorFlow运行计算时,才输入值。我们想把MNIST图像的每一个拉平成784-维度的向量的任何数值都可以输入。我们把它表示成[None, 784]二维浮点数值的tensor。(这里None意味着这个维度可以是任意长度)
对于我们的模型,我们也需要权重和偏量。我可以想象一下如何对待这些额外的输入,但是TensorFlow有一个更好的方式处理它:变量。变量A是一个可修改的tensor,它处在TensorFlow的交互图中。它可以被使用,甚至通过计算修改。对于机器学习应用来说,通常有一个模型参数是变量。
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
我们通过给定的tf.Variable创建变量,初始化变量的值:在这个案例中,我们把W和b都初始化为都是0的tensor。由于我们要去学习W和b,所以不需要在意他的初始值是什么。
注意,W的形状是 [784, 10],是因为我们想用他乘以784-维度的图像向量产生10-维度不同类别的证据向量。b的形状是[10],因此我们可以把它加给输出。
现在我们可以实现我们的模型了,仅用一行就可以定义它。
y = tf.nn.softmax(tf.matmul(x, W) + b)
首先,我们使用表达式 tf.matmul(x, W)让W乘以x。当我们在我们的方程式中相乘这些时有一个翻转,这里我们有Wx,这是处理多个输入的x成为2D tensor的小技巧。然后我们再加上b,最后应用tf.nn.softmax。
是的,经过几行简短的设置后,仅仅使用一行就定义了我们的模型。这并不是因为TensorFlow被设计的目的是让softmax回归变得极其简单,而是它只是一个用非常灵活的方式,描述从机器学习模型到物理模型的各种类型的计算。并且一旦定义,我们的模型可以运行在不同的设备上:你的电脑的CPU,GPU,甚至是手机上!
训练
为了训练我们的模型,我们需要定义什么样的模型是好的。实际上,在机器学习中,我通常定义什么模型是坏的。我们称之为成本或者损失,它表示了我们的模型里我们的期望有多远。我们尽量让误差最小,误差越小,我们的模型越好。
一个非常常见,非常好的觉得模型的损失的功能叫“交叉熵”。交叉熵来源于信息论中的信息压缩编码的思想,但它在从赌博到机器学习的许多领域中已成为一个重要的概念。他被定义为:
这里y是我们预测的概率分布,y′是真实的分布( 数字的one-hot向量)。在某种粗略的意义上,交叉熵是衡量我们的预测对描述事实的效率有多低。关于交叉熵的更多细节超出了本教程的范围,但它很值得理解。
为了实现交叉熵,首先,我们需要增加一个新的占位符来输入正确的答案:
y_ = tf.placeholder(tf.float32, [None, 10])
然后我们可以实现交叉熵函数, −∑y′log(y):
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的第二个维度中把每个元素相加。最终, tf.reduce_mean计算了batch内的所有例子的平均值。
(注意,在源码中,我们没有使用公式,因为它在数值上不稳定。 因此我们在非标准化数值上采用了tf.nn.softmax_cross_entropy_with_logits (例如, 在 tf.matmul(x, W) + b)上调用 softmax_cross_entropy_with_logits ,因为这个较为数值稳定的功能内部调用了softmax激活。 在你的代码中,考虑使用 tf.nn.(sparse_)softmax_cross_entropy_with_logits 代替).
现在,我们知道了我们想要我们的模型做什么,很容易让TensorFlow训练它去做这些。因为TensorFlow知道你计算的整个图,它可以自动的使用反向传播算法,来有效的决定你的变量怎样影响你要的最小化损失。然后你可以应用你选择的最优算法来修改变量,减少损失。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
在这个案例中,我们让TensorFlow使用学习率为0.5的梯度下降算法来最小化交叉熵。梯度下降是一个简单的算法,在这里TensorFlow一点点的转变每一个变量的方向以降低成本。但是TensorFlow也提供了其他的优化算法:换一个就跟调整一行代码一样简单。
TensorFlow在这里真正做的是什么,在后台,是在你实现的反向传播和梯度下降的图中添加新的操作。然后,他给你返回一个单独的操作,当运行时,进行一个梯度下降训练,稍微调整你的变量,以减少损失。
现在,我们要去训练我们的模型。训练前的最后一件事情是,我们要创建一个操作来初始化我们创建的变量。注意这时定义了操作,但还没有运行:
init = tf.global_variables_initializer()
我们现在可以在一个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})
循环的每一步,我们从我们的训练集中得到100个随机的数据点“batch”。我们运行train_step 把每个batch中数据放进占位符中。
使用小批量随机数据被称为随机训练。———在这个案例中,叫随机梯度下降。理想情况下,我们希望在训练的每一步使用我们所有的数据,因为这样可以让我们更好地了解我们应该做什么,但这很耗时。因此,我们每次使用不同的子数据集。这样做很方便,也有很多好处。
评估我们的模型
我们的模型做的怎么样?
首先,让我们找到正确预测的标签。tf.argmax是一个很有用的函数, 它给你返回在tensor中某个维度中的最高值的索引。例如,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构建更复杂的下一个教程里
无戒365挑战营 46