MNIST For ML Beginners
这个教程是给机器学习和TensorFlow的新手准备的,如果你已经知道MNIST是什么,softmax(多项式回归)是什么,可以跳过。
当你开始学习编程时,可能第一件事就是打印'hello world',机器学习与此类似的是MNIST。
MNIST是一个简单的计算机视觉数据集,它由手写的数字图像组成

他也包含了每个图像的标签,告诉我们是什么数字,比如,上面4个图像的标签就是5,0,4,1.
在这个教程,我们将训练一个根据图形预测数字的模型。我们不是想训练一个完美精确的模型,而是给TensorFlow做个初步教程。照此,我们用softmax回归开始做一个简单的模型。
这个教程的代码非常短,而且真正值得注意的只有三行。但是,对于理解其背后的思想非常重要:TensorFlow怎样工作的,机器学习的核心思想。
About this tutorial
这里的教程是逐行解释mnist_softmax.py的代码
你可以使用这里的代码:
- 边读每一行的代码解释边逐行复制粘贴到python环境中
- 运行整段代码然后不清楚的地方再看教程
教程目的:
- 学习MNIST数据和softmax回归
- 基于图片像素的认知数字的函数创建
- 基于千计的样本用TensorFlow训练模型认知数字
- 用测试数据检查模型的准确性
The MNIST Data
The MNIST Data存在于Yann LeCun's website,如果你复制粘贴这两行代码,它会自动下载读取数据
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.validation).这样的分割是非常重要的:这样我们可以确保模型的正确。
数据集中的每个样本有两部分:手写数字的图像和伴随的标签。我们称图像为x,标签为y。训练集和测试集都以此划分,比如,训练集的图像是mnist.train.images,测试集的标签是mnist.train.labels.
每个图像是28*28的像素,我们可以理解为一个大的数字矩阵:

我们可以把这个矩阵平铺成28*28=784个数字的向量。怎样平铺矩阵不重要,只要图像之间保持一致就行了。按照这一观点,MNIST图像就是一个784维的向量空间(warning: computationally intensive visualizations).
平铺2D结构的图像会造成信息的丢弃。这是不好的吗?最好的计算机视觉方法能够利用这种2D结构,我们也将在之后的教程中介绍。但这里我们使用的softmax仍是使用的平铺数据的方法。
mnist.train.images是形状为[55000, 784]的张量。第一维是图像列表的下标,第二维是每个图像每个像素的下标。张量的每个元素是0到1之间的像素。

MNIST的每个图像都有一个伴随的标签,一个0到9之间的数字。
我们把我们的标签当做'one-hot vectors'(独热编码)。即大多维度是0,只有一个维度上是1,在这里,第n个数字的向量将在第n个维度上是1,3可以看成是[0,0,0,1,0,0,0,0,0,0].由此,mnist.train.labels即[55000,10]的浮点数矩阵。

Softmax Regressions
我们知道mnist的每个图像都是0到9的手写数字。因此一个给出的图像仅有10种可能。我们想要由一幅图像给出它每个数字的概率,比如,看一张9的图片,我们80%的概率确定是9,5%是8,其他数字概率更小。
softmax回归是一种自然简单的模型,如果你想要给一个多种可能的物体分配概率,softmax就是你需要的,因为softmax给我们一组0到1之间的序列,并且和为1.甚至之后,我们将训练更复杂的模型,最后一步就是softmax层。
一个softmax回归分为两步:得到由输入变成某一种类的证据,然后将证据转换成概率。
为了得到给定图片属于某个特定数字的证据,我们对像素值加权求和,权重为负表示像素值有很强的证据不属于该类,如果为正就支持证据。
下面的图片显示了一个模型学习到的图片上每个像素对于特定数字类的权值。红色代表负数权值,蓝色代表正数权值。

我们增加偏差作为额外的证据,因为输入往往带一些无关的干扰量,因此对于给定的输入图片 x 它代表的是数字 i 的证据可以表示为
其中W代表权重, b代表数字i类的偏置量,j代表给定图片x的像素索引。然后用softmax函数可以把这些证据转换成概率y
这里的softmax可以看做是activation(激励)或link(链接),把我们定义的线性函数的输出转换成我们想要的格式,也就是关于10个数字类的概率分布。因此,给定一张图片,它对于每一个数字的吻合度可以被softmax函数转换成为一个概率值。softmax函数可以定义为:
展开等式右边的子式,可以得到:
但是更多的时候把softmax模型函数定义为前一种形式:把输入值当成幂指数求值,再(normalize)归一化这些结果值。这个幂运算表示,更大的证据对应更大的假设模型(hypothesis)里面的乘数权重值。反之,拥有更少的证据意味着在假设模型里面拥有更小的乘数系数。假设模型里的权值不可以是0值或者负值。Softmax然后会归一化这些权重值,使它们的总和等于1,以此构造一个有效的概率分布。
你可以把softmax回归看作下面的部分,对于每个输出,我们对x加权求和,再加上一个偏置,再运用softmax。

如果写成一个等式,得到:

我们可以向量化这一结果,转化成矩阵和向量的运算。有助于提高计算效率。

更进一步,可以写成
现在我们把它转化为TensorFlow可以使用的。
Implementing the Regression
为了用python实现高效的数值计算,我们通常会使用函数库,比如NumPy,会把类似矩阵乘法这样的复杂运算使用其他外部语言实现。不幸的是,从外部计算切换回Python的每一个操作,仍然是一个很大的开销。如果你用GPU来进行外部计算,这样的开销会更大。用分布式的计算方式,也会花费更多的资源用来传输数据。
TensorFlow也把复杂的计算放在python之外完成,但是为了避免前面说的那些开销,它做了进一步完善。Tensorflow不单独地运行单一的复杂计算,而是让我们可以先用图描述一系列可交互的计算操作,然后全部一起在Python之外运行。(这样类似的运行方式,可以在不少的机器学习库中看到。)
使用TensorFlow之前,首先要导入它.import tensorflow as tf
我们通过操作符号化的变量来描述这些可交互的节点x = tf.placeholder(tf.float32, [None, 784])
x不是一个特定值,而是一个placeholder(占位符)。我们在TensorFlow运行计算时输入这个值。我们希望能够输入任意数量的MNIST图像,每一张图展平成784维的向量。我们用2维的浮点数张量来表示这些图,这个张量的形状是[None,784 ]。(这里的None表示此张量的第一个维度可以是任何长度的。)
我们的模型也需要权重值和偏置量,当然我们可以把它们当做是额外的输入,但TensorFlow有一个更好的方法来表示它们:Variable 。 一个Variable代表在计算图交互节点中可修改的张量。它们可以在计算中使用和修改。对于机器学习,通常把模型参数视为变量。
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
我们用tf.Variable来得到变量的初始值来创建变量。在这里,我们把w和b初始化为0,既然要训练w和b,就不用太在意初始值。
注意w的shape是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量,每一位对应不同数字类。b的形状是[10],所以我们可以直接把它加到输出上面。
现在,我们可以实现我们的模型啦。只需要一行代码!
y = tf.nn.softmax(tf.matmul(x, W) + b)
首先,我们用tf.matmul(X,W)表示x乘以W,对应之前等式里面的,这里x是一个2维张量拥有多个输入。然后再加上b,把和输入到tf.nn.softmax函数里面。
至此,我们先用了几行简短的代码来设置变量,然后只用了一行代码来定义我们的模型。TensorFlow不仅仅可以使softmax回归模型计算变得特别简单,它也用这种非常灵活的方式来描述其他各种数值计算,从机器学习模型对物理学模拟仿真模型。一旦被定义好之后,我们的模型就可以在不同的设备上运行:计算机的CPU,GPU,甚至是手机!
训练模型。
Training
为了训练我们的模型,我们首先需要定义模型的好坏。其实,在机器学习,我们通常定义指标来表示一个模型是坏的,这个指标称为成本(cost)或损失(loss),然后尽量最小化这个指标。也即是表征模型是好的。
一个常见的成本函数是“交叉熵”(cross-entropy)。交叉熵产生于信息论里面的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的重要技术手段。被定义为:
y 是我们预测的概率分布, y' 是实际的分布(我们输入的one-hot vector)。比较粗糙的理解是,交叉熵是描述预测的低效性来描述真相。更详细的关于交叉熵的解释超出本教程的范畴,见understanding。
为了计算交叉熵,我们首先需要添加一个新的占位符用于输入正确值:
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 计算张量的所有元素(第二个维度)的总和。最后,计算batch中所有样本的均值。
在源代码中,我们并没有用这种格式,因为它常常数值不稳定。我们用tf.nn.softmax_cross_entropy_with_logits在未规范化的对数上(比如我们用softmax_cross_entropy_with_logits在tf.matmul(x, W) + b上),这个函数计算softmax更加稳定。
现在我们知道我们需要我们的模型做什么啦,用TensorFlow来训练它是非常容易的。因为TensorFlow得到了整个计算图,它可以自动地使用反向传播算法(backpropagation algorithm)来有效地确定你的变量是如何影响你想要最小化的那个成本值的。然后,TensorFlow会用你选择的优化算法来不断地修改变量以降低代价。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
这里,我们用0.5学习率的gradient descent algorithm(梯度下降算法)来最小化交叉熵。梯度下降算法是一个简单的学习过程,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的数据点组成batch。赋值placeholder运行train_step。
使用随机数据的batch来进行训练被称为随机训练(stochastic training)- 在这里更确切的说是随机梯度下降训练。在理想情况下,我们希望用我们所有的数据来进行每一步的训练,因为这能给我们更好的训练结果,但显然这需要很大的计算开销。所以,每一次训练我们可以使用不同的数据子集,这样做既可以减少计算开销,又可以最大化地学习到数据集的总体特性。
Evaluating Our Model
那么我们的模型性能呢?
首先让我们找出那些预测正确的标签。tf.argmax 是一个非常有用的函数,它能给出某个tensor对象在某一维上的其数据最大值所在的索引值。由于标签向量是由0,1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1)返回的是模型对于任一输入x预测到的标签值,而 tf.argmax(y_,1) 代表正确的标签,我们可以用 tf.equal 来检测我们的预测是否真实标签匹配(索引位置一样表示匹配)。
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
最后在测试集上验证准确性
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
大概是92%。
这是好的吗?当然不那么好,事实上是相当差的。因为我们用的一个非常简单的模型。做出一些改变,我们可以得到97%。最好的模型的可以达到99.7%(更多信息,可以看list of results)
重要的是我们从模型中学到的,如果你不满足这个结果,可以看下一章节学习TensorFlow更多的模型。
附上mnist_softmax.py源代码:
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""A very simple MNIST classifier.
See extensive documentation at
https://www.tensorflow.org/get_started/mnist/beginners
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import sys
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
FLAGS = None
def main(_):
# Import data
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
# Create the model
x = tf.placeholder(tf.float32, [None, 784])
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
y = tf.matmul(x, W) + b
# Define loss and optimizer
y_ = tf.placeholder(tf.float32, [None, 10])
# The raw formulation of cross-entropy,
#
# tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.nn.softmax(y)),
# reduction_indices=[1]))
#
# can be numerically unstable.
#
# So here we use tf.nn.softmax_cross_entropy_with_logits on the raw
# outputs of 'y', and then average across the batch.
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
# Train
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})
# Test trained model
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images,
y_: mnist.test.labels}))
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--data_dir', type=str, default='/tmp/tensorflow/mnist/input_data',
help='Directory for storing input data')
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)