MNIST For ML Beginners(ML的初学者MNIST 教程)

MNIST For ML Beginners

本教程适用于机器学习和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会话来完成此操作)

  • 用我们的测试数据检查模型的准确性

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个训练数据点(mnist.train),10,000点测试数据(mnist.test)和5,000点验证数据(mnist.validation)。这种分裂是非常重要的:机器学习中必不可少的是我们有单独的数据,我们没有从中学习,这样我们就可以确保我们所学到的知识实际上是泛化的!

如前所述,每个MNIST数据点都有两部分:手写数字图像和相应标签。我们将调用图像“x”和标签“y”。训练集和测试集都包含图像及其相应的标签; 例如训练图像mnist.train.images和训练标签mnist.train.labels

每个图像是28像素×28像素。我们可以把它解释为一大堆数字:

我们可以将这个数组变成28x28 = 784数字的向量。只要我们在图像之间保持一致,无论如何将阵列弄平。从这个角度来看,MNIST图像只是784维向量空间中的一束点,具有非常丰富的结构(警告:计算密集型可视化)。

展平数据将丢弃有关图像二维结构的信息。那不好吗?那么,最好的计算机视觉方法会利用这种结构,我们将在后面的教程中讨论。但是我们将在这里使用的简单方法,softmax回归(下面定义)不会。

The result is that mnist.train.images is a tensor (an n-dimensional array) with a shape of [55000, 784]. The first dimension is an index into the list of images and the second dimension is the index for each pixel in each image. Each entry in the tensor is a pixel intensity between 0 and 1, for a particular pixel in a particular image.

MNIST中的每个图像都有相应的标签,0到9之间的数字代表图像中绘制的数字。

对于本教程的目的,我们将要我们的标签作为“一个热点向量”。单热矢量是一个矢量,它在大多数维度上是0,而在一维中是1。在这种情况下,第(n)位将表示为在第(n)维中为1的向量。例如,3将是(0,0,0,1,0,0,0,0,0,0)。因此,mnist.train.labels是一[55000, 10]组浮标。

我们现在准备好实际制作我们的模型!

Softmax回归

我们知道MNIST中的每个图像都是一个介于零和九之间的手写数字。所以一个给定的图像可能只有十种可能的东西。我们希望能够查看图像并给出每个数字的概率。例如,我们的模型可能会查看一张九张图片,并且有80%确定这是一张九张图片,但给它一个5%的概率是因为它是一个八(因为顶部循环),并且对所有其他的概率有一些概率,因为它不是100%确定的。

这是一个经典案例,其中softmax回归是一个自然而简单的模型。如果你想把一个对象的概率分配给几个不同的东西,softmax是要做的事情,因为softmax给了我们一个介于0和1之间的值的列表,加起来为1。甚至在后面的时候,当我们训练更复杂型号,最后一步将是一层softmax。

softmax回归有两个步骤:首先,我们将输入的证据加在某些类别中,然后将这些证据转换为概率。

为了收集给定图像在特定类别中的证据,我们进行像素强度的加权总和。如果具有高强度的像素是针对该类中的图像的证据,则重量为负;如果证据有利,则重量为负。

下图显示了一个模型为每个类学习的权重。红色代表负向权重,蓝色代表正向权重。

我们还添加了一些额外的证据,称为偏见。基本上,我们希望能够说有些事情更可能独立于输入。结果是给定输入(x)的类(i)的证据是:

$$\text{evidence}_i = \sum_j W_{i,~ j} x_j + b_i$$

其中(W_i)是权重,(b_i)是类别(i)的偏差,(j)是对我们输入图像(x)中的像素进行求和的指数。然后,我们使用“softmax”函数将证据符号转换为我们的预测概率(y):

$$y = \text{softmax}(\text{evidence})$$

这里softmax是作为一个“激活”或“链接”功能,将我们的线性函数的输出形成我们想要的形式 - 在这种情况下,概率分布超过10个案例。你可以把它看作是将证据转换为我们在每个班级中输入的概率。它被定义为:

$$\text{softmax}(evidence) = \text{normalize}(\exp(evidence))$$

如果你扩展这个等式,你会得到:

$$\text{softmax}(evidence)_i = \frac{\exp(evidence_i)}{\sum_j \exp(evidence_j)}$$

但是,首先想到softmax会更有帮助:指数化输入,然后使其正常化。指数运算意味着多一个单位的证据可以乘法地增加任何假设的权重。相反,拥有一个较少的证据单位意味着一个假设获得了早期权重的一小部分。没有假设曾经有零或负重量。Softmax然后归一化这些权重,以便它们加起来为1,形成有效的概率分布。(为了获得关于softmax函数的更多直觉,请查看Michael Nielsen书中的关于它的部分,其中包含交互式可视化。)

您可以将我们的softmax回归看成如下所示,尽管有更多(x)s。对于每个输出,我们计算(x)s的加权和,添加一个偏差,然后应用softmax。

如果我们把它写成等式,我们得到:

我们可以将这个过程“矢量化”,将其转化为矩阵乘法和矢量加法。这对计算效率很有帮助。(这也是一种有用的思考方式。)

更简洁,我们可以写出:

$$y = \text{softmax}(Wx + b)$$

现在让我们把它变成TensorFlow可以使用的东西。

实施回归

为了在Python中进行高效的数值计算,我们通常使用像NumPy这样的库来执行昂贵的操作,例如使用Python之外的矩阵乘法,使用以另一种语言实现的高效代码。不幸的是,每次操作都会返回Python,但仍然会有很多开销。如果您想要在GPU上运行计算或以分布式方式运行计算,则这种开销尤其糟糕,因为传输数据的成本很高。

TensorFlow也在Python之外进行繁重的工作,但为了避免这种开销,它需要更进一步。TensorFlow不是独立于Python运行一个昂贵的操作,而是让我们描述一个完全在Python之外运行的交互操作图。(像这样的方法可以在几个机器学习库中看到。)

要使用TensorFlow,首先我们需要导入它。

import tensorflow as tf

我们通过操纵符号变量来描述这些交互操作。我们来创建一个:

x = tf.placeholder(tf.float32, [None, 784])

x不是一个具体的价值。这是placeholder我们要求TensorFlow运行计算时输入的值。我们希望能够输入任意数量的MNIST图像,每个图像都被拼合成784维向量。我们将其表示为具有形状的浮点数的二维张量[None, 784]。(这None意味着维度可以是任意长度。)

我们还需要我们模型的权重和偏差。我们可以想象将这些视为额外的输入,但TensorFlow有更好的方式来处理它:Variable。A Variable是生活在TensorFlow的交互操作图中的可修改张量。它可以被使用,甚至被计算修改。对于机器学习应用程序,通常模型参数为Variables。

W = tf.Variable(tf.zeros([784, 10])) b = tf.Variable(tf.zeros([10]))

我们Variable通过给出tf.Variable以下初始值来创建这些Variable:在这种情况下,我们初始化这两者W并将其b作为充满零的张量。既然我们要学习Wb,他们最初是并没有那么麻烦。

请注意,W它的形状为784,10,因为我们想用它乘以784维图像矢量,以生成差分类的证据的10维向量。b具有10的形状,所以我们可以将它添加到输出中。

我们现在可以实现我们的模型。它只需要一行来定义它!

y = tf.nn.softmax(tf.matmul(x, W) + b)

首先,我们用Wx与表达为tf.matmul(x, W)。当我们在我们的方程中将它们相乘时(这里我们有(Wx)),这是一个小技巧,用于处理x具有多个输入的2D张量。然后我们添加b并最终应用tf.nn.softmax

而已。经过几次简单的设置之后,我们只用一条线来定义我们的模型。这并不是因为TensorFlow的设计使得softmax回归特别容易:它只是一种非常灵活的方式来描述从机器学习模型到物理模拟的各种数值计算。一旦定义,我们的模型可以在不同的设备上运行:计算机的CPU,GPU甚至是手机!

训练

为了训练我们的模型,我们需要定义模型的好处。呃,实际上,在机器学习中,我们通常定义模型的坏意味着什么。我们称之为成本或损失,它表示我们的模型与我们期望的结果有多远。我们尽量减少误差,误差越小,我们的模型越好。

确定模型损失的一个非常常见的非常好的函数被称为“交叉熵”。跨熵来源于信息理论中对信息压缩代码的思考,但它在从赌博到机器学习等许多领域都是一个重要思想。它被定义为:

$$H_{y'}(y) = -\sum_i y'_i \log(y_i)$$

其中(y)是我们的预测概率分布,并且(y')是真实分布(具有数字标签的单热矢量)。在一些粗略的意义上,交叉熵正在测量我们的预测对于描述事实的效率有多低。关于交叉熵的更多细节超出了本教程的范围,但它非常值得理解

为了实现交叉熵,我们需要首先添加一个新的占位符来输入正确的答案:

y_ = tf.placeholder(tf.float32, [None, 10])

然后我们可以实现交叉熵函数, (-\sum 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根据reduction_indices=[1]参数添加y的第二维中的元素。最后,tf.reduce_mean计算批处理中所有示例的平均值。

更新时间:-tf.nn.softmax_cross_entropy_with_logits的非标准化logits(例如,我们称之为softmax_cross_entropy_with_logitstf.matmul(x, W) + b),因为这个数值上更稳定的内部函数计算SOFTMAX激活。在你的代码中,请考虑使用tf.nn.softmax_cross_entropy_with_logits去替换。

现在我们知道我们希望我们的模型能做什么了,TensorFlow很容易培训它做到这一点。由于TensorFlow知道整个计算图,因此它可以自动使用反向传播算法来有效地确定变量如何影响您要求它最小化的损失。然后它可以应用您选择的优化算法来修改变量并减少损失。

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

在这种情况下,我们要求TensorFlow最小化,cross_entropy使用梯度下降算法,学习率为0.5。梯度下降是一个简单的过程,其中TensorFlow只是简单地将每个变量向降低成本的方向移动一点点。但TensorFlow还提供了许多其他优化算法:使用一个就像调整一条线一样简单。

TensorFlow实际上在幕后做了什么,是为图形添加新的操作,实现向后传播和渐变下降。然后它会给你一个单一的操作,在运行时进行梯度下降训练,略微调整你的变量以减少损失。

我们现在可以通过以下方式启动模型InteractiveSession

sess = tf.InteractiveSession()

我们首先必须创建一个操作来初始化我们创建的变量:

tf.global_variables_initializer().run()

让我们训练 - 我们将运行1000次训练步骤!

for _ 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构建更复杂的模型!