作者 : 陈龙, 腾讯即通产品部 Android 开发工程师, 负责 Android QQ 的开发与维护. 热衷于机器学习的研究与分享.
对于人类来说, 识别手写的数字是一件非常容易的事情. 我们甚至不用思考, 就可以看出下面的数字分别是 5,0,4,1.
但是想让机器识别这些数字, 则要困难得多.
如果让你用传统的编程语言 (如 Java) 写一个程序去识别这些形态各异的数字, 你会怎么写? 写很多方法去检测横, 竖, 圆这些基本形状, 然后计算它们的相对位置? 我想你很快就会陷入绝望之中. 即使你花很多时间写出来了程序, 精确度也一定不高. 当你在传统编程方法的小黑屋里挣扎时, 机器学习这种更高阶的方法却为我们打开了一扇窗.
为了找到识别手写数字的方法, 机器学习界的大师 Yann LeCun 利用 NIST(National Institute of Standards and Technology 美国国家标准技术研究所)的手写数字库构建了一个便于机器学习研究的子集 MNIST.MNIST 由 70000 张手写数字 (0~9) 图片 (灰度图) 组成, 由很多不同的人写成, 其中 60000 张是训练集, 另外 10000 张是测试集, 每张图片的大小是 28 x 28 像素, 数字的大小是 20 x 20, 位于图片的中心. 更详细的信息可以参考 Yann LeCun 的网站: http://yann.lecun.com/exdb/mnist/
已经有很多研究人员利用该数据集进行了手写数字识别的研究, 也提出了很多方法, 比如 KNN,SVM, 神经网络等, 精度已经达到了人类的水平.
抛开这些研究成果, 我们从头开始, 想想怎样用机器学习的方法来识别这些手写数字. 因为数字只包含 0~9, 对于任意一张图片, 我们需要确定它是 0~9 中的哪个数字, 所以这是一个分类问题. 对于原始图片, 我们可以将它看作一个 28 x 28 的矩阵, 或者更简单地将它看作一个长度为 784 的一维数组. 将图片看作一维数组将不能理解图片里的二维结构, 我们暂且先这么做, 看能够达到什么样的精度. 这样一分析, 我们很自然地就想到可以用 Softmax 回归来解决这个问题. 关于 Softmax Regression 可以参考下面的文章:
http://ufldl.stanford.edu/wiki/index.php/Softmax回归
我们的模型如下:
对于一张图片, 我们需要算出它分别属于 0~9 的概率, 哪个概率最大, 我们即认为这张图片上是那个数字. 我们给每个像素 10 个权重, 对应于 0~9, 这样我们的权重矩阵的大小就是 784 x 10. 将上图的模型用公式表示, 即为:
写成向量的形式:
softmax 函数将 n 个非负的值归一化为 0~1 之间的值, 形成一个概率分布.
模型有了, 我们的代价函数是什么呢? 怎样评估模型输出的结果和真实值之间的差距呢? 我们可以将数字表示成一个 10 维的向量, 数字是几则将第几个元素置为 1, 其它都为 0, 如下图所示:
比如 1 可表示为:[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]. 这样我们就可以用交叉熵来衡量模型输出结果和真实值之间的差距了, 我们将其定义为:
其中, y 是模型输出的概率分布, y 一撇是真实值, 也可以看作概率分布, 只不过只有一个值为 1, 其它都为 0. 将训练集里每个样本与真实值的差距累加起来, 就得到了成本函数. 这个函数可以通过梯度下降法求解其最小值.
关于交叉熵 (cross-entropy) 可以参考下面这篇文章:
http://colah.github.io/posts/2015-09-Visual-Information/
模型和成本函数都有了, 接下来我们用 TensorFlow 来实现它, 代码如下:
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- import tensorflow as tf
- from tensorflow.examples.tutorials.mnist import input_data
- # Import data
- mnist = input_data.read_data_sets('input_data/', 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
- y_ = tf.placeholder(tf.float32, [None, 10])
- # Define loss and optimizer
- # 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}))
下面我来解释一些比较重要的代码:
mnist = input_data.read_data_sets('input_data/', one_hot=True)
这行代码用来下载 (如果没有下载) 和读取 MNIST 的训练集, 测试集和验证集(验证集暂时可以先不用管).TensorFlow 的安装包里就有了 input_data 这个 module, 所以我们直接 import 进来就好了. 将数据读到内存后, 我们就可以直接通过 mnist.test.images 和 mnist.test.labels 来获得测试集的图片和对应的标签了. TensorFlow 提供的方法从训练集里取了 5000 个样本作为验证集, 所以训练集, 测试集, 验证集的大小分别为: 55000,10000,5000.
'input_data/'是你存放下载的数据集的文件夹名.
- W = tf.Variable(tf.zeros([784, 10]))
- b = tf.Variable(tf.zeros([10]))
这里简单的将参数都初始化为 0. 在复杂的模型中, 初始化参数有很多的技巧.
y = tf.matmul(x, W) + b
这一行是我们建立的模型, 很简单的一个模型. tf.matmul 表示矩阵相乘.
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
这一行等价于:
- cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(tf.nn.softmax(y)), reduction_indices=[1]))
- y_ * tf.log(tf.nn.softmax(y)
做的事情就是计算每个样本的 cross-entropy, 因为这样算数值不稳定, 所以换成了
tf.nn.softmax_cross_entropy_with_logits
这个方法.
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
这一行就是用 TensorFlow 提供的梯度下降法在成本函数最小化的过程中调整参数, 学习率设置为 0.5.
- 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})
这 3 行代码是进行训练, 因为训练集有 55000 个样本, 太大了, 所以采用了 Stochastic Gradient Descent(随机梯度下降), 这样做大大降低了计算量, 同时又能有效的训练参数, 使其收敛.
mnist.train.next_batch
方法就是从训练集里随机取 100 个样本来训练. 迭代的次数为 1000.
- 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}))
这 3 行是在模型参数训练完之后, 在测试集上检测其准确度.
运行我们的代码, 其结果如下:
可以看到, 这样一个简单的模型就可以达到 92% 的准确度. Amazing~
来源: https://cloud.tencent.com/developer/article/1004802?fromSource=waitui