TensorFlow之三 ----- 机器学习初学者的MNIST

这个教程旨在哪些对机器学习和TensorFlow都是新手的读者。如果你已经知道MNIST是什么,并且了解softmax(多项逻辑)回归,你更应该阅读<快速教程>。无论你开始那个教程,请确保你已经安装了TensorFlow。

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

MNIST是一个简单的计算机视觉数据集。它包含像这样的手写数字图片:

                                                                                            

它同样包含每个图片的标签,用于告诉我们它是哪个数字。例如,上述图片的标签是5,0,4,1.

在这个教程中,我们准备训练一个模型来查看这个图片并且预测它们是哪个数字。我们的目的不是去训练一个真正详尽的模型来达到最高的性能 -- 尽管我们稍后将会提供那种代码! -- 而只是浅谈使用TensorFlow。同样的,我们将会从一个非常简单的模型开始,这个模型叫做Softmax回归。


这个教程的实际代码非常的短小,并且所有有意思的材料只有3行。然而,对于理解它背后的想法是非常重要的:既可以了解TensorFlow是如何工作的,也可以了解核心的机器学习概念。因此,我们准备非常详尽的讨论这个代码。

1. 关于这个教程

这个教程是一个说明,逐行的,关于发生在mnist_softmax.py代码。
你可以以几种不同的方式来使用这个教程,包括:
  • 复制和粘贴每一个代码片段,逐行的,到一个python环境中,当你阅读了每一行的注释。
  • 在阅读了注释之前或之后运行整个mnist_softmax.py python文件,并且使用这个教程来理解对你来说并不是特别清晰的行。
我们在这份教程中将会完成:
  • 学习关于MNIST数据和softmax回归。
  • 创建一个模型函数用于识别数字,基于阅读图片里的每一个像素。
  • 使用TensorFlow来训练模型去识别数字,通过让它“看”上千的例子(并且运行我们的第一个TensorFlow会话来这样做)
  • 使用我们的测试数据来检查数据的正确性。

2. MNIST数据

MNIST数据可以在Yann LeCun的网站(http://yann.lecun.com/exdb/mnist/)上找到。如果你正在从这个教程里面复制和粘贴代码,从这里的2行代码开始将会自动的下载和阅读数据:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
MNIST数据被分成了3部分:55000数据点的训练数据(mnist.train),10000点的测试数据(mnist.test),和5000点的验证数据(mnist.validation)。这个分离是非常重要的:再机器学习中这是非常重要的,我们分离那些我们不需要学习的数据,以便我们能确保我们学习的是真正的概念!

正如之前提及的,每一个MNIST数据点包含2部分:手写数字的图片和一个对应的标签。我们将图片称为‘x’,标签称为‘y’。训练集和测试集都包含图片和它们对应的标签;例如,训练图片是mnist.train.images,训练标签是mnist.train.labels。

每个图片都是28*28像素。我们能将这个翻译称为一个大的数字数组:
                                                                  
我们能平坦这个数组为一个28*28=784数字的矢量。我们如何平坦这个数组并没有关系,只要我们在图片中保持一致。从这个角度看,MNIST图片只不过是一群在784维矢量控件的点,带有一个非常丰富的结构(提醒:计算密集型可视化)。

平坦数据将会丢弃图片的2D结构的信息。那会很糟糕吗?额,最好的计算机视觉方法确实开发出了这种结构,并且我们将会在稍后的教程中接触。但是我们将在这里使用简单点的方法,一个softmax回归(在下面定义)。
结果是mnist.train.images是一个张量(一个n维数组),它的维度shape是[55000,784]。第一个维度是图片列表的索引,第二个维度是每个图片里每个像素的索引。在这个张量中的每个实体是一个介于0和1之间的像素强度,在特殊图片中将会是一个特殊的像素。

MNIST里的每个图片都有一个对应的标签,介于0到9之间的数字,代表图片上画的数字。
对于这片教程,我们想要我们的标签作为‘one-hot 矢量’。one-hot矢量是一个在某单个维度上是1,其他大多数维度是0的矢量。在这种情况下,n将代表矢量在n这个维度是1.例如,3将会是[0,0,0,1,0,0,0,0,0,0]。因此,mnist.train.labels是一个[55000,10]的浮点数组。
                                                              
我们现在准备好了来实际完成我们的模型!

3. Softmax回归

我们知道再MNIST里的每个图片都是介于0和9之间的手写数字。所以一个给定的图片只有10种可能。我们想要能够阅读图片并且给出它是每个数字的概率。例如,我们的模型看一个内容为9的图片并且确认它80%的几率是9,但是5%的几率是8(因为上面的圆)和所有其他数字的一点点几率因为它不是100%确定。

这是一个经典的情况,在里面softmax回归是一个自然的,简单的模型。如果你想给一个可能是几个不同东西中一个的对象设置概率,softmax就是你要的东西,因为softmax提供了我们一个值介于0和1,总计达1的列表。甚至后面,当我们训练更多复杂的模型时,最后一步将会是softmax的一层。

一个softmax回归有2个步骤:首先我们累加再某个类里的我们输入的证据,然后我们将那个证据转换成概率。

为了得到图片在一个特殊类里的证据,我们对像素强度进行一个加权和。如果拥有高强度的像素是不利于图片在那个类中的证据,权重将是负值,如果是有利的证据,将会是正值。

下面的图片显示了一个模型对这些类的每一个学习后的权重。红色代表负值权重,蓝色代表正值权重。

我们同样添加一些称为偏爱的额外的证据。主要的,我们想要能说一些事更加可能是输入独立。结果是对于给定输入x的类i证据是:
                                                               
其中是权重,是对于类i的偏爱,j在我们输入图片x里像素求和的索引。我们然后将证据记录转换成我们的预测概率y,通过使用softmax函数:
                                                                    y = softmax(evidence)
这里的softmax作为一个‘激活’或‘链接’函数服务,塑造我们线性函数的输出为我们想要的格式 -- 在这种情况下,概率分布为10种可能。你可以把它看做是将证据记录转换为我们在每个类中的输入的概率。它被定义为:
softmax(evidence) = normalize(exp(evidence))
如果你将那个方程式展开,你将会得到:

但通常将softmax认为是第一种方式将会更有帮助:取幂它的输入然后标准化它们。指数意味着增加一个证据单元将会增加给定的任意相乘的权重。并且相反的,减少一个证据单元意味着一个得到一个它之前权重的部分的假设。没有假设曾会有0或者负值权重。softmax然后标准化这些权重,以便他们累加到一个,形成一个有效的概率分布。(了解关于更多softmax函数,阅读Michael Nielsen的书中的选节,完成交互式可视化。)

你能将我们的softmax回归想象成下列的东西,尽管有很多的x。对于每个输出,我们计算x的权重汇总添加一个偏好,然后应用softmax。

如果我们将那写成方程式,我们得到:

我们能‘矢量化’这个过程,将它转换成一个矩阵乘法和一个矢量加法。这对于计算效率是有帮助的。(同样有助于思考。)

更简洁的,我们可以写成:
y = softmax(Wx + b)
现在让我们将它转换成TensorFlow能使用的东西。

4. 实现回归

为了在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维的矢量。我们将它作为一个2-D浮点数字张量展示,它的维度是[None,784].(这里None意味着维度可以是任意长度。)

我们同样需要我们模型的权重和偏好。我们可以想象将这些作为额外的输入,但是TnesorFlow有一个更好的方式来处理它:变量Variable。一个变量Variable是一个在TensorFlow交互操作图表中的可修改的张量。它可以被计算使用甚至修改。对于机器学习应用,一般来说模型参数是变量。
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
我们通过给tf.Variable 变量Variable的初始值来创建这些变量Variable:在这种情况下,我们将W和b初始化为全0的张量。因为我们准备学习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,作为一个小的技巧来处理作为一个带有多个输入的2D张量的x。然后我们加上b,最终应用tf.nn.softmax。

就是这样。只需要花费我们一行来定义我们的模型,在几行简短的设置之后。那并不是因为TnesorFLow被设计成让softmax回归特别简单:它就是一种非常灵活的方式来描述关于数字计算的许多种类,从机器学习模型到物理模拟。并且一旦定义了,我们的模型能被运行在不同的设备上:你电脑的CPU,GPU,甚至是电话上。

5. 训练

为了训练我们的模型,我们需要定义对于模型来说什么使它变得更优的东西。额,事实上,在机器学习中我们通常定义对于一个模型来说更糟糕的东西。我们称这为花费,或者损失,并且它表示我们的模型离我们期望的输出有多远。我们尝试最小化那个错误,并且错误余下越少,我们模型就越好。

一个非常普遍,非常好的定义一个模型损失的函数称为'交叉熵cross-entropy'。交叉熵起因于在信息理论中关于信息压缩编码,但是却发展成了许多区域的一个重要的想法,从赌场到机器学习。它被定义为:

其中y使我们预测的概率分布,y’是真实分布(带有数字标签的one-hot矢量)。概括的说,交叉熵测量对于描述事实我们的预测是如何无效的。更深入讨论交叉熵不在本教程范畴,但它非常值得理解。

为了实现交叉熵我们需要首先添加一个新的占位符来输入正确的答案:
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中每个元素的对数。然后我们用tf.log(y)中对应的元素乘以y_中的每一个元素。然后tf.reduce_sum添加元素到y的第二个维度里,这取决于reduction_indices=[1]参数。最后,tf.reduce_mean计算批次中所有例子的平均值。

注意,在源代码找那个,我们不需要使用这个公式,因为它是数值不未定的。相反,我们应用tf.nn.softmax_cross_entropy_with_logits在非规范分对数(例如,我们在tf.matmul(x,W) + b调用softmax_corss_entropy_with_logits)上,因为这个更数值稳定的函数在内部计算softmax激活。在你的代码中,考虑使用tf.nn.softmax_cross_entropy_with_logits来代替。

现在我们知道我们想要我们的模型来做什么了,使用TensorFlow训练它来做这些将会非常简单。因为TensorFlow知道你的计算的整个图表,它能自动使用逆传算法backpropagation algorithm来高效的决定你的变量如何影响你要求它最小化的损失。然后它能应用你的最佳算法的选择来修改变量并且减少损失。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

在这种情况下,我们要求TensorFlow使用带有一个0.5的学习率的梯度下降算法gradient descent algorithm来最小化交叉熵cross_entropy。梯度下降是一个简单的过程,其中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})
每次循环的步骤,我们从我们的训练集中得到100个随机数据点的‘批次’。我们运行train_step传入批次数据来代替占位符。

使用随机数据的小批次称为随机训练 -- 在这种情况下,随机梯度下降。理想的,我们想要是对训练的每一步使用我们所有的数据因为那给我们一种我们应该这样做的更好的感觉,但那花销太大。所以,替代的,我们每次使用不同的子集。这样做花销小并且有很多的好处。

6. 评估我们的模型

我们模型执行的如何?

额,首先让我们指出我们在哪里预测正确的标签。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,Flase,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%的精准率!



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值