MNIST机器学习入门

这是一个简单的示例,表示如何通过 tensorflow 来对数据集 MNIST 进行机器学习

MNIST 是什么

MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片,开源给人进行数字的图像识别

本例的数学模型

本例的模型比较简单,叫做 Softmax Regression

下面,会使用 tensorflow 提供的代码下载 MNIST 的数据集,如果由于网络问题,也可以自己翻墙下载 MNIST ,将以下4个文件复制到你的数据目录中

train-images-idx3-ubyte.gz:  training set images (9912422 bytes) 
train-labels-idx1-ubyte.gz:  training set labels (28881 bytes) 
t10k-images-idx3-ubyte.gz:   test set images (1648877 bytes) 
t10k-labels-idx1-ubyte.gz:   test set labels (4542 bytes)
In [1]:
# input_data.read_data_sets 会报较多的警告,这里忽略输出
import tensorflow as tf
old_v = tf.logging.get_verbosity()
tf.logging.set_verbosity(tf.logging.ERROR)

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("../../data/mnist/", one_hot=True)

tf.logging.set_verbosity(old_v)
Extracting ../../data/mnist/train-images-idx3-ubyte.gz
Extracting ../../data/mnist/train-labels-idx1-ubyte.gz
Extracting ../../data/mnist/t10k-images-idx3-ubyte.gz
Extracting ../../data/mnist/t10k-labels-idx1-ubyte.gz

上述代码会对数据集的文件进行处理,返回一个 mnist 对象,它的属性如下:

  • mnist.train 60000行的训练数据集

    • mnist.train.images 训练数据集的图片
    • mnist.train.labels 训练数据集的标签
  • mnist.test 10000行的测试数据集

train 与 test 只是数据量的不同,这样切分的目的在于,在机器学习模型设计时必须有一个单独的测试数据集不用于训练而是用来评估这个模型的性能,从而更加容易把设计的模型推广到其他数据集上(泛化)。

这里,mnist是一个轻量级的类。它以Numpy数组的形式存储着训练、校验和测试数据集。同时提供了一个函数,用于在迭代中获得minibatch,后面我们将会用到

每一张图片包含28像素X28像素。我们可以用一个数字数组来表示这张图片:

在本例子中,我们把这个数组展开成一个向量,长度是 28x28 = 784。如何展开这个数组(数字间的顺序)不重要,只要保持各个图片采用相同的方式展开。从这个角度来看,MNIST数据集的图片就是在784维向量空间里面的点, 并且拥有比较复杂的结构 (tips: 此类数据的可视化是计算密集型的)。

展平图片的数字数组会丢失图片的二维结构信息。这显然是不理想的,最优秀的计算机视觉方法会挖掘并利用这些结构信息,我们会在后续教程中介绍。但是在这个教程中我们忽略这些结构,所介绍的简单数学模型,softmax回归(softmax regression),不会利用这些结构信息。

数据集中的标签,是介于0到9的数字,本例子中,标签数据是"one-hot vectors",比如,标签0将表示成[1,0,0,0,0,0,0,0,0,0,0]。

什么是 Softmax回归

softmax回归(softmax regression)分两步:第一步

为了得到一张给定图片属于某个特定数字类的证据(evidence),我们对图片像素值进行加权求和。如果这个像素具有很强的证据说明这张图片不属于该类,那么相应的权值为负数,相反如果这个像素拥有有利的证据支持这张图片属于这个类,那么权值是正数。

下面的图片显示了一个模型学习到的图片上每个像素对于特定数字类的权值。红色代表负数权值,蓝色代表正数权值。

因为输入往往会带有一些无关的干扰量,我们也需要加入一个额外的偏置量(bias)。那么对于给定的输入图片 x 它代表的是数字 i 的证据可以表示为

解释下

  • Wi 权重
  • bi 代表数字i类的偏置量
  • 求和符号(Σ,sigma)
  • j 代表给定图片x的像素索引

接下来,使用 softmax 函数把这些证据转换为概率 y :

这里的softmax可以看成是一个激励(activation)函数或者链接(link)函数,把我们定义的线性函数的输出转换成我们想要的格式。

简单来说,就是给定一张图片,它对于每一个数字的吻合度可以被softmax函数转换成为一个概率值,即区间[0,1]的浮点数。

softmax函数可以定义为:

展开等式右边的子式,可以得到:

简单的网络模型

到这里就需要讲一下网络模型,避免画图复杂,这里假如输入x是一个[1,3]的vector,假如输出y有3个可能的结果,那么计算图如下

对于输入的xs加权求和,再分别加上一个偏置量,最后再输入到softmax函数中:

如果把它写成一个等式,我们可以得到:

我们也可以用向量表示这个计算过程:用矩阵乘法和向量相加。这有助于提高计算效率。(也是一种更有效的思考方式)

更进一步,可以写成更加紧凑的方式:

开始使用 tensorflow

基本操作可以参考 tensorflow example 代码的前两节

代码执行过程的技术与效率问题:

为了用python实现高效的数值计算,我们通常会使用函数库,比如NumPy,会把类似矩阵乘法这样的复杂运算使用其他外部语言实现。不幸的是,从外部计算切换回Python的每一个操作,仍然是一个很大的开销。如果你用GPU来进行外部计算,这样的开销会更大。用分布式的计算方式,也会花费更多的资源用来传输数据。

TensorFlow也把复杂的计算放在python之外完成,但是为了避免前面说的那些开销,它做了进一步完善。Tensorflow不单独地运行单一的复杂计算,而是让我们可以先用图描述一系列可交互的计算操作,然后全部一起在Python之外运行。(这样类似的运行方式,可以在不少的机器学习库中看到。)

下面,先定义占位符、变量、以及网络模型的计算过程

In [2]:
# ps: y 是 one-hot vectors,如 [1,0,0,0,0,0,0,0,0,0,0]

# x 定义占位符, None 表示此张量的第一个维度可以是任何长度的,也就是该维度由机器来判断
x = tf.placeholder("float", [None, 784])

# w 定义模型参数之一 —— 权重,第一个维度与 x 的第二个维度相等,第二个维度与 y 离散的数量相等
W = tf.Variable(tf.zeros([784,10]))

# b 定义模型参数之一 —— 偏置量,数量与 y 离散的数量相等
b = tf.Variable(tf.zeros([10]))

# 上面计算图的运算过程
y = tf.nn.softmax(tf.matmul(x,W) + b)

损失函数

为了训练我们的模型,我们首先需要定义一个指标来评估这个模型是好的。其实,在机器学习,我们通常定义指标来表示一个模型是坏的,这个指标称为成本(cost)或损失(loss),然后尽量最小化这个指标。但是,这两种方式是相同的。

一个非常常见的,非常漂亮的成本函数是“交叉熵”(cross-entropy)。交叉熵产生于信息论里面的信息压缩编码技术,但是它后来演变成为从博弈论到机器学习等其他领域里的重要技术手段。它的定义如下:

y 是我们预测的概率分布, y' 是实际的分布(我们输入的one-hot vector)

In [3]:
# 添加一个新的占位符用于输入正确值
y_ = tf.placeholder("float", [None,10])
# 计算交叉熵
cross_entropy = -tf.reduce_sum(y_*tf.log(y))

首先,用 tf.log 计算 y 的每个元素的对数。接下来,我们把 y 的每一个元素和 tf.log(y) 的对应元素相乘。最后,用 tf.reduce_sum 计算张量的所有元素的总和。

注意,这里的交叉熵不仅仅用来衡量单一的一对预测和真实值,而是所有100幅图片的交叉熵的总和。对于100个数据点的预测表现比单一数据点的表现能更好地描述我们的模型的性能。

In [4]:
# 我们要求TensorFlow用梯度下降算法(gradient descent algorithm)以0.01的学习速率最小化交叉熵
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

现在我们知道我们需要我们的模型做什么啦,用TensorFlow来训练它是非常容易的。因为TensorFlow拥有一张描述你各个计算单元的图,它可以自动地使用反向传播算法(backpropagation algorithm)来有效地确定你的变量是如何影响你想要最小化的那个成本值的。然后,TensorFlow会用你选择的优化算法来不断地修改变量以降低成本。

梯度下降算法(gradient descent algorithm)是一个简单的学习过程,TensorFlow只需将每个变量一点点地往使成本不断降低的方向移动。当然TensorFlow也提供了其他许多优化算法:只要简单地调整一行代码就可以使用其他的算法。

In [5]:
# 初始化我们创建的变量
init = tf.global_variables_initializer()

# 现在我们可以在一个Session里面启动我们的模型,并且初始化变量
sess = tf.Session()
sess.run(init)

# 开始训练模型,这里我们让模型循环训练1000次
for i in range(1000):
    
  # 该循环的每个步骤中,我们都会随机抓取训练数据中的 100 个批处理数据点
  batch_xs, batch_ys = mnist.train.next_batch(100)
    
  # 用这些数据点作为参数替换之前的占位符来运行 train_step,在该过程中,tensorflow 会修改变量,权重与偏置量
  sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
    
# 循环结束后,权重与偏置量调到合适的值
print("W:", sess.run(W))
print("b:", sess.run(b))
W: [[0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]
 [0. 0. 0. ... 0. 0. 0.]]
b: [-5.9057999e-01  4.2961848e-01  1.9686964e-01 -3.7805343e-01
 -1.0193298e-03  1.8464813e+00 -1.6955788e-01  9.5418853e-01
 -1.9506774e+00 -3.3726507e-01]

评估我们的模型

如何评估我们的模型的性能

  • 首先让我们找出那些预测正确的标签。tf.argmax 是一个非常有用的函数,它能给出某个tensor对象在某一维上的其数据最大值所在的索引值。
  • 由于标签向量是由0,1组成,因此最大值1所在的索引位置就是类别标签,比如tf.argmax(y,1)返回的是模型对于任一输入x预测到的标签值,而 tf.argmax(y_,1) 代表正确的标签,我们可以用 tf.equal 来检测我们的预测是否真实标签匹配,索引位置一样表示匹配。
In [6]:
# tf.equal() 得到布尔值
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

# 将布尔值转换为浮点数,累加后取平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

# 最后,我们计算所学习到的模型在测试数据集上面的正确率。
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
0.9184

保存本次训练的模型

In [8]:
saver = tf.train.Saver()

# 第二个参数设定保存的路径和名字,第三个参数将训练的次数作为后缀加入到模型名字中
saver.save(sess,"../../MyModel/mnist_linear/", global_step=1000)
Out[8]:
'../../MyModel/mnist_linear/-1000'