页面树结构

版本比较

标识

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。

目录

 

本教程适用于新来的机器学习和TensorFlow的读者。如果你已经知道MNIST是什么,以及softmax(多项逻辑斯蒂)regression,那么你可能更喜欢这个 快速入门教程。在开始其它教程之前,请务必 安装TensorFlow

当学习如何编程时,有一个传统,你所做的第一件事是打印“Hello World”。就像编程有Hello World,机器学习有MNIST。

MNIST是一个简单的计算机视觉数据集。它由以下手写数字图像组成:

它还包括每个图像的标签,告诉我们每个图像对应哪个数字。例如,上述图像的标签是5,0,4和1。

在本教程中,我们将训练一个模型来查看图像并预测它们的数字。我们的目标不是训练一个具有最高水准的模型 - 尽管我们稍后会给你代码! - 而是介绍如何使用TensorFlow。因此,我们将从一个非常简单的模型开始,我们称它为Softmax Regression。

本教程的实际代码很短,所有有趣的东西都包含在三行代码里。然而,了解其背后的想法是非常重要的:TensorFlow如何运作和机器学习核心概念。因此,我们将非常仔细地介绍这些代码。

 

关于本教程

本教程将逐行解释 mnist_softmax.py中的代码。

您可以通过以下几种不同的方式使用本教程,其中包括:

  • 当您阅读每行的说明时,将每个代码段逐行复制并粘贴到Python环境中。

  • 在阅读说明之前或之后运行mnist_softmax.py文件,并使用本教程来了解不清楚的代码行。

我们将在本教程中完成什么:

  • 了解MNIST数据和softmax regressions

  • 基于图片中的每个像素,创建一个用于识别数字的模型函数

  • 使用TensorFlow“学习”成千上万个案例来训练模型并识别数字(运行我们的第一个TensorFlow会话)

  • 使用我们的测试数据检查模型的精度

 

MNIST数据

MNIST数据托管在 Yann LeCun的网站上。如果您复制和粘贴本教程中的代码,请从这两行代码开始,这两行代码将自动下载并读取数据:

代码块
languagepy
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) 
MNIST数据分为三个部分:训练数据(mnist.train),55000个训练数据集,10,000个测试数据集(mnist.test)和5,000个验证数据集(mnist.validation)。这种做法是非常
必要的:在机器学习中,我们必须有独立的测试数据集不拿来训练,而是拿来评估整个模型的性能,确保我们所学到的东西有很好的泛化能力!

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

每个图像是28×28像素。我们可以把它解释为大量数字构成的数组:

我们可以把这个数组变成一个28×28 = 784数字的向量。只要图像之间平铺方式保持一致,那么我们如何平铺这个数组并不重要。从这个角度来看,MNIST图像只是784维向量空间中的一个点,并且 结构非常复杂 (注意:计算密集型的可视化)。

平铺数据会丢弃有关图像2D结构的信息。那样岂不是更糟糕吗? 通常,最优的计算机视觉方法会利用这个结构,我们将在后面的教程中介绍。但是我们将在这里使用简单方法,softmax回归(下面会定义)不会利用这些结构信息。

mnist.train.images是一个具有[55000, 784]形状的张量(n维数组)。第一个维度代表图像列表中的索引,第二个维度是每个图像中每个像素的索引。对于给定图像中的某个像素,张量中的每个元素表示0-1之间的像素强度。

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

对于本教程,我们将标签称为“one-hot vectors”。一个one-hot是一个大多数维度上为0,一个维度上为1的向量。在这种情况下,第n个数字将被表示成1所在的维度。例如,3将是  [0,0,0,1,0,0,0,0,0,0]。因此,mnist.train.labels一个[55000, 10]的浮点型数组。

现在我们准备开始构建模型!

 

Softmax回归

我们知道MNIST中的每个图像都是零到九之间的手写数字。所以给定的图像只能有十个可能。我们希望给定的一个图像,给出它是哪个数字的概率。例如,我们的模型会得到一个数字可能为9的图片,它的概率为80%,但它是8的概率是5%(因为8和9上部都有一个相近的圆),而代表其它数字的概率更小。

这是一个使用softmax回归的简单模型的经典案例。softmax给不同的对象分配概率,由于softmax给出了一个0到1之间的值,这些值加起来等于1。稍后,当我们训练更加复杂的模型,最后一步将加上一层softmax。

softmax回归有两个步骤:首先我们对输入图片属于某个数字类别的依据进行叠加,然后将该依据转换成概率。

为了统计给定图像属于某个数字类中的证据,我们对像素值进行加权求和。如果图片像素很大程度上不属于某个类,那么权重是负的,如果它很有可能属于这个类,那么权重为正。

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

我们还增加一个额外的偏置量。从根本上来说,这样做是由于我们希望输入不带有一些干扰量。因此,给定输入图片 x, 它代表的是数字 i 的证据表达式为:

                                               

这里的 Wi 是权重, bi 是第i类的偏置量j 代表输入图片 x 的像素索引,用于像素求和。然后,将证据转换成我们预测的概率  y 通过使用“softmax”函数:

这里的softmax可以看做一个“激活”或者“链接”函数,将我们的线性函数的输出转化为我们想要的形式, 在本案例中,即是关于10个数字类的概率分布。您可以将其视为我们输入的图片属于哪个类中的概率吻合度。它定义为:                            

将该方程变形,得到:

通过第一种方式来表达softmax通常更有利:对其输入进行指数化,然后对它们进行归一化,取幂意味着更大的证据对应更大的假设模型里面的乘数权重值。相反,拥有更少的证据意味着在假设模型的早期权重中拥有更小的乘数系数。假设模型没有0或者负权重,然后Softmax对这些权重进行归一化,使它们加起来等于1,形成有效的概率分布。(要获得更多关于softmax函数的信息,请参阅Michael Nielsen的书中的 部分内容,并附有交互式的可视化文件。)

 

softmax回归图像如下图所示,虽然有多输入的x。对于每个输出,我们计算x的加权和,添加偏置量,然后应用到softmax函数中。

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

我们还可以“矢量化”这个过程,把它变成矩阵乘法和矢量加法。这有助于计算效率。(这也是一个有用的思路。)

更进一步,我们可以写成:

 

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

 

回归实现

为了在Python中进行有效的数值计算,我们通常使用像 NumPy这样的数据库,会把类似矩阵乘法这样的复杂运算使用其他外部语言实现。不幸的是,每次操作都需要重新切换到Python的开销很大。如果要在GPU上运行计算或以分布式方式运行计算,那么这种开销尤其糟糕,传输数据的成本很高。

TensorFlow也在Python之外做了大量的工作,但它需要进一步的工作来避免这种开销。Tensorflow不单独地运行单一的复杂计算,而是利用图(graph)描述一系列可交互的计算操作,然后整个图在Python之外运行。(类似的方法可以在不少机器学习库中看到。)

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

代码块
languagepy
import tensorflow as tf
我们通过操纵符号变量来描述这些交互操作。我们创建一个:
代码块
languagepy
x = tf.placeholder(tf.float32, [None, 784]) 
x不是一个特定的值。它是一个占位符,当TensorFlow运行一个计算时,我们将输入一个值。我们希望能够输入任意数量的MNIST图像,每个图像被平铺成784维的向量。我们将其表示为具
有形状的浮点数的2-D张量
[None, 784]。(这里None表示第一个维度可以是任意长度。)

我们的模型还需要权重和偏置量。我们可以像其他输入一样处理这些信息,但是TensorFlow有一个更好的处理方式:VariableVariable是一个位于TensorFlow的交互操作图中的可变张量。它可以被用来计算输入值甚至修改。对于机器学习应用,一般会有模型参数Variable

代码块
languagepy
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
我们创建这些Variable,通过tf.Variable初始化这些值:在本案例中,我们初始化Wb张量,使其全部置0。既然我们要学习Wb,它们的初始权值是多少无关紧要。

注意,W具有[784,10]的形状,因为我们要将784维图像向量乘以它,以产生不同类别的10维证据向量。b具有[10]的形状,所以我们可以将其添加到输出。

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

代码块
languagepy
y = tf.nn.softmax(tf.matmul(x, W) + b) 
首先,我们将x乘以W,表达式为tf.matmul(x,W)。 这是从前面我们的方程中进行翻转而来的,对应Wx,这是处理x具有多个输入2D张量的一个小技巧。 然后我们添加b,最后应用
tf.nn.softmax。

就是这样,我们只需要一行来定义我们的模型。这并不是因为TensorFlow的设计使得softmax的回归特别容易:它只是从机器学习模型到物理仿真的多种数值计算的非常灵活
的一种描述方式。一旦定义,我们的型号可以在不同的设备上运行:计算机的CPU,GPU,甚至手机!

 

训练

为了训练我们的模型,我们需要定义一个评判标准去判定模型的好坏。实际上,在机器学习中,我们通常定义一个标准来表示模型性能低劣。我们称之为成本(cost)或损失(loss),它代表了我们的模型与我们所期望的结果差距有多远。我们尝试最小化这个差距,而且误差范围越小,我们的模型就越好。

确定模型损失的一个常见又好用的函数称为“交叉熵”。交叉熵源于对信息理论中的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的非常重要的一种技术手段。定义为:

 这里y是我们预测的概率分布,  y'是真实的分布(数字标签的one-hot 矢量)。通俗的理解,交叉熵是衡量我们的预测用于描述真实标签的低效性。有关交叉熵的更多细节超出了本教程的范围,但它是非常值得 理解的。 

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

代码块
languagepy
y_ = tf.placeholder(tf.float32, [None, 10]) 
然后我们可以实现交叉熵函数,
代码块
languagepy
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1])) 
首先,tf.log计算y的每个元素的对数。接下来,我们将每个元素y_与相应的元素tf.log(y)相乘由于reduction_indices=[1]参数,tf.reduce_sum 在y的第二个维度中添加
元素 。最后,
tf.reduce_mean计算批量数据中所有示例的平均值。

请注意,在源代码中,我们不使用此公式,因为它在数值上是不稳定的。相反,我们应用tf.nn.softmax_cross_entropy_with_logits的非标准化logits(例如,我们在tf.matmul(x, W) + b上称之为softmax_cross_entropy_with_logits),因为这是一个在softmax激活中更稳定的数值计算函数。在您的代码中,请考虑使用tf.nn.softmax_cross_entropy_with_logits 。

现在我们知道我们想要模型去做什么,让TensorFlow训练它很容易做到这一点。因为TensorFlow知道您的计算单元的整个图形,它可以自动使用 反向传播算法来有效地确定变量从而影响您所要求的最小化损失。然后它可以应用您选择的优化算法来不断修改变量并减少损失。

代码块
languagepy
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) 
在这种情况下,我们要求TensorFlow cross_entropy使用学习率为0.5的梯度下降算法进行最小化成本。梯度下降是一个简单的过程,TensorFlow只需将每个变量一点点地往使成本
不断降低的方向移动。但是TensorFlow还提供了
其他优化算法:只要调整一行代码就可以使用其他的算法。

TensorFlow在这里实际上所做的是,它会在后台给你的计算图里面增加一系列新的计算操作单元用于实现反向传播算法和梯度下降算法。然后,它返回一个单一的操作,当运行时,进行梯度下降算法训练你的模型,稍微调整您的变量以减少损失。

我们现在可以在InteractiveSession中启动模型

代码块
languagepy
sess = tf.InteractiveSession() 
我们首先必须创建一个操作来初始化我们创建的变量:
代码块
languagepy
tf.global_variables_initializer().run() 
让我们开始训练吧 - 我们将运行1000次训练步骤!
代码块
languagepy
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用来检查我们的预测是否正确。

代码块
languagepy
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
以上操作返回给我们一个布尔值列表。为了确定哪个部分是正确的,我们转换为浮点数,然后取平均值。例如, [True, False, True, True]会变成[1,0,1,1]哪一个,概率即为0.75
代码块
languagepy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) 
最后,我们计算测试数据的准确性。
代码块
languagepy
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})) 
这应该是92%左右。

这个识别率好吗?其实这已经很糟糕了,这是因为我们使用的是非常简单的模型。有了一些小的变化,我们可以达到97%。最好的模型可以达到99.7%的精度!(有关更多信息,请查看此 结果列表。)

重要的是我们从这个模型中学到了什么。不过,如果您对这些结果感到有些失落,请查看 下一个教程,在那里我们做得更好,并学习如何使用TensorFlow构建更复杂的模型!