0x00、何为MNIST?
在学习编程语言的时候,我们第一课基本都是打印“Hello World”,MNIST也一样,是一个入门的必修课。 简单点说,MNIST就是一大堆手写数字图片的集合,包含了训练集(就是正确的集合,教会电脑什么样是正确的)和测试集(像试卷一样,考验电脑是否能成功识别)像下面这张图一样。
0x01、准备
首先下载文件。在这个链接:(http://yann.lecun.com/exdb/mnist/)中下载四个文件,放到MNIST_data文件夹内即可。这个数据集包含了4个文件,通俗点说,分别是
-
训练所用图片(总共60000张)
-
训练所用标签(解释了图片的内容)
-
测试所用图片(总共10000张)
-
测试所用标签(测试图片的正确答案)
把测试数据与训练数据分开的目的是更客观的评价这个模型,从而更加容易把设计的模型推广到其他数据集上(泛化)如果不理解你可以想想,数学考试的时候老师并不喜欢用你做过的题目考你,而是用相同类型的其他题目考察你是否学习会了。 接着导入库文件:
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import gzip
import os
import tempfile
import numpy
from six.moves import urllib
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf
from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets
好了,下面就可以进行我们接下来的工作了。
0x02、数据集
首先导入训练和测试数据
mnist = read_data_sets("MNIST_data/", one_hot=True)
可以看到数据成功被解压读取。
导入的数据(训练所用的60000份数据集,记做mnist.train,和测试所用的10000份,记做mnist.test。其中所有的图片记做xs,标签记做ys。)
这里的每个图片都是28*28像素的,我们可以把图片转化为一个矩阵,如下图
接着我们把这个矩阵转化成一个向量,这里可以一行一行的转换([a11, a12... a1n, a21, a22... a2n... ann]),也可以一列一列的转换([a11, a21... an1, a12, a22... an2... ann]),甚至可以斜着展开... 具体怎么转换无所谓,反正保证所有的数据都按照相同的方式展开就行。这个向量是28*28=784维的。
当然这样展开后图片的二维结构信息就丢失了,(原来方形的矩阵变成了“一条”),这样并不好,但是作为初级教程我们先这么展开。后续的教程中会弥补。
在训练集中,mnist.train.images(训练图片集)有60000个,每个图片是784维的向量,所以mnist.train.images是一个形状为 [60000, 784] 的张量。第一个维度数字为图片的索引(理解为下标,第几张图片),第二个维度是图片的内容,即像素点。值在0,1之间。如下图
在标签集中,mnist.train.labels(训练标签集)是一个形状为[60000, 10]的张量,第一个依然是索引,第二个表示标签表示的第几个数字。例如 0表示为[1,0,0,0,0,0,0,0,0,0]
好了,接下来我们可以构建模型了
0x03、Softmax回归
听着这个名字挺高大上,忽略了就好了。。。我们只要学会它的原理就好。 我们知道了MNIST里面是0-9的手写数字,有工整的,也有歪歪扭扭的。我们希望这个模型怎么工作才能识别它们呢?
我们希望它这么工作:首先传入一个图片,我们希望的得到10个数字,分别是0、1一直到9的概率值。打个比方
例如我们传入的手写数字为8,我可能会得到“是8的概率为80%,是9的概率为10%,是0的概率为1.5%.......”,是8的概率最高,所以我们得到了结果:这个数字很有可能是8。
这是一个使用softmax回归(softmax regression)模型的经典案例。softmax模型可以用来给不同的对象分配概率
上面我们判断手写图片写的数字是8,凭什么是8呢?
这里的这个“凭什么”,我们称之为证据(evidence)。要想知道证据是什么,我们要做的首先是对图片的每一个像素点的像素值(黑度)进行加权求和。
如果某个像素点能够说明图片写的是8,我们给它赋予一个正数的权重值。如果不能说明图片写的是8,我们给它一个负数的权重值。
如果没听懂上面的话,请认真读下面的例子。首先请仔细对比数字“3”和数字“8”的区别:
为了方便举例,这里我们假设“3”就是把“8”的左半部分削掉,保留右半部分
3 8
比如图片上写的是一个工整的宋体8,那对于8,图片上所有黑色的部分都可以说明这个数字是8,整个图片权重值是正的。
但是对于3,图片上只有右侧的部分可以说明这个数字是3,左侧部分并不像3,所以右侧的权重是正的,左边是负的,求和就成了0。小于8的权重。
这样就说明了数字是8。
下面的图片很好的显现的模型对图片处理时权重的大小。其中蓝色代表正,红色代表负。
但是我们不能保证图片是书写工整的,可能会有歪歪扭扭的,甚至可能会有不小心点的点,或者溅上去墨迹。
因此我们要定义一个偏置量(bias)。不懂什么是偏置量你可以理解为它能中和掉干扰量(不小心点上去的点)
这样我们的证据的值,也就是那个“凭什么是8”的计算方法就是对图片所有像素点加权求和,然后加上偏置量。
用公式表示就是
这里evidence表示证据值,Wi表示权重,bi是数字i类的偏置量,j代表给定图片x的像素索引用于像素求和,接着用
softmax函数即可将证据值转化为概率。
这里的softmax函数是如何实现的我们暂时不考虑。TensorFlow会自动帮我们完成它。
0x04、TensorFlow实现模型
这里我们为了进行高性能的数值计算,我们使用了numpy库。不懂的可以点击这里。
我们先创建一个可交互的操作单元
x = tf.placeholder("float", [None, 784])
这里的x是一个占位符(placeholder),占位符没有数值上的意义。仅表示了向量的形状。
我们用2维的浮点数张量来表示这些图,这个张量的形状是[None,784 ]。(这里的None表示此张量的第一个维度可以是任何长度的。)
接着我们定义权重值和偏置量
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
这里的 Variable 是变量的意思,也就是TensorFlow中的变量。可以被修改。
我们赋予 tf.Variable 不同的初值来创建不同的 Variable:在这里,我们都用全为零的张量(tf.zeros)来初始化W和b。
下面的操作中我们需要让电脑去学习 W 和 b 的值,所以这里的初始值可以任意定义。
W 的维度是[784,10],因为我们想要用784维的图片向量乘以它以得到一个10维的证据值向量,每一位对应不同数字类。b 的形状是[10],所以我们可以直接把它加到输出上面。
下面搭建 softmax 模型:
y = tf.nn.softmax(tf.matmul(x,W) + b)
tf.matmul(X,W) 表示 x 乘以 W,对应上面那个式子中的 Wx。
b是偏置量,对应式子中的bi。
好了,模型全部搭建好了,接下来是训练~~~
0x05、TensorFlow训练
我们定义一个指标,能评估模型的好坏,这个指标通常叫做“成本(cost)”或“损失(loss)”。
看着这个名字我们也知道,一定要降低成本或者损失,这样才符合常理嘛~
这里介绍一个非常常见的成本函数,叫做“交叉熵(这个自读作‘伤’)”(cross-entropy)
它的计算表达式为:
y 是我们预测的概率分布, y' 是实际的分布。
为了计算交叉熵,我们定义一个新的占位符。
y_ = tf.placeholder("float", [None,10])
接着计算交叉熵
cross_entropy = -tf.reduce_sum(y_*tf.log(y))
这里 tf.log 可以计算对数,再把 _y(对应式子中的y')的每个元素与对数相乘。tf.reduce_sum用来求和。
是不是觉得这里的reduce_sum没有太理解?下面这个图会帮你更好的理解:
求和的原因在于交叉熵不是求某一个的交叉熵,而是求总和,来评估模型的整体性能。
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
这段代码利用了梯度下降算法,以0.01的学习速率最小化交叉熵。大致了解下就行。
设置好了,接着初始化创建的变量
init = tf.initialize_all_variables()
然后在一个session(会话)中启动我们的模型,初始化变量
sess = tf.Session()
sess.run(init)
好了,下面训练开始!
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
这里mnist.train.next_batch(100)表示返回随机100个训练数据,返回的格式为
array( array(表示图片像素的784维向量, 表示图片对应的数字的10维向量), array(像素的784维向量, 数字的10维向量) ...... )
表示像素的向量给了batch_xs,表示对应数字的向量给了batch_ys。
下面的这一行用来把这100个测试点作为参数替换掉之前的占位符来运行train_step
这样使用部分随机数据进行训练叫做随机训练。
0x06、评估
我们学习的怎么样了呢,让我们来测试一下吧! 首先让我们找出那些预测正确的标签。首先说一下 tf.argmax 函数,它能给出tensor对象在某一维上最大的数据所在的索引值。
例如[0, 0, 5, 0, 1, 0],argmax函数会返回 2,因为索引是 2 的数值为5,最大。
由于标签向量(就是那个表示这个图片上写的什么数字的向量)由0, 1组成,因此最大值1所在的索引位置就是类别标签。
接着还有一个函数,叫做 tf.equal,用来比较是否匹配相等
比如tf.argmax(y,1)返回的是模型对于任一输入x预测到的标签值,而 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, "float"))
最后,我们计算所学习到的模型在测试数据集上面的正确率。
print sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels})
0x07、总结
所有的代码整理一下大致是这样的:
# -*- coding:utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import gzip
import os
import tempfile
import numpy
from six.moves import urllib
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf
from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets
mnist = read_data_sets("MNIST_data/", one_hot=True)#读取mnist数据
################# 搭建模型 #################
x = tf.placeholder("float", [None, 784]) #用来表示图像形状的占位符
W = tf.Variable(tf.zeros([784,10])) #权重值
b = tf.Variable(tf.zeros([10])) #偏置率
evidence = tf.matmul(x,W) + b #证据=累加(W*x+b)
y = tf.nn.softmax(evidence) #y表示概率。matmul表示相乘。x与W相乘,加上偏置率
################# 训练模型 #################
y_ = tf.placeholder("float", [None,10]) #用来表示交叉熵的占位符
cross_entropy = -tf.reduce_sum(y_*tf.log(y))#交叉熵=-累加(y_*log(y))
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
#0.01的学习速率最小化交叉熵
init = tf.initialize_all_variables() #初始化所有变量
sess = tf.Session()
sess.run(init) #启动会话
for i in range(1000): #学习1000次
batch_xs, batch_ys = mnist.train.next_batch(100)
#batch_xs为图像像素向量,batch_ys为标签向量
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
#运行会话。按照train_step的方式,将像素和标签传入,进行学习
################# 评估好坏 #################
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
#判断是否相等(是否正确)
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
#reduce_mean用来求平均值。cast用来布尔转浮点(true=>1)
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))
#用所有测试图片和标签进行测试。
运行后准确率大致是91%。