文章

Tensorflow - MNIST For ML Beginners

MNIST是什么

原文:

MNIST is a simple computer vision dataset. It consists of images of handwritten digits

简单的说MNIST就是一个手写阿拉伯数字(0 - 9)的图像库,每一个数字转换为灰度图像后由一个28 * 28的[0, 1]范围的浮点数矩阵组成,并标注了对应的数字。所以每一个文字都是一个784(28 * 28)的向量。

One-hot Encoding

这个tutorial里使用One-hot编码对label进行编码,关于One-hot可以看这里。在这个例子中,因为数字是0 - 9共10个数字,所以One-hot编码之后这是一个长为10的向量,譬如3就是[0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

因为MNIST的训练集共有55000个样本,因此整个训练集的标注数据就是一个Shape = (55000, 10)的Tensor。

Softmax Regression

Softmax回归可以看做是Logistic回归的泛化形式:众所周知,Logistic回归的结果是二值形式的,而Softmax在此之上推广到了K值形式,sum(P(k)) = 1。

这里只是简单说明一下Softmax的意思,详细的公式和介绍可以看这里,softmax经常用在多项逻辑回归(Logistic Regression处理多分类问题),朴素贝叶斯分类以及NN上。

在这个例子中,每一个像素点在每一个分类i上都有一个权重w(i),引入一个正规化参数b(i)之后,evidence(i) = sum(w(i, j) * x(j)) + b(i)其中j的取值范围是全体x,本例中输入的一幅图像是28 * 28 = 784,也就是j的取值范围就是这784个浮点数( [0, 783] )。

总结起来:

1
y = softmax(xW + b) // 这里与原文小小改动了一下为了让下文和公式更一致

对于一个图像:

  • W是一个Shape = (784, 10)的Tensor,每一行表示一个像素,每一列表示一个分类。
  • x是一个Shape = (1, 784 )的Tensor,每一列表示一个像素点
  • b是一个Shape = (1, 10 )的Tensor,每一行表示一个分类
  • y是一个Shape = (1, 10 )的Tensor,

把以上的公式推广到N个X也就是显而易见的了。

最终定义这个公式的代码只有一行:

1
y = tf.nn.softmax(tf.matmul(x, W) + b)

或者更简单一点

1
y = tf.nn.softmax(tf.nn.xw_plus_b(x, W, b))

这里有一点需要注意,推广到N个样本之后,x的Shape是(n, 784),y的Shape是(n, 10),但是b的Shape依然是(1, 10),或者说说实际上Rank = 1, Shape = [10]。这样的计算可以进行是因为Braodcasting,参考这里,当两个Tensor的Rank不匹配时,会自动的伸缩,伸缩的逻辑与Numpy的逻辑一致,也就是前面那个链接里所述的,这里大家可以多多试验一下,稍微有点儿绕。

Cross Entropy Loss

下一步是选择一个损失函数(loss-function),用于评估模型的效果也是优化目标,这里的说道也很多按下不表。例子中给出的是cross-entropy的损失函数,详情看这里

在tensorflow中实现也很简单:

1
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), axis=1))

原文解释的很详细,这里在说一下,首先这些操作都是element-wise的,所以不要和矩阵操作混淆,其次reduce_sumaxis参数指定了sum的维度,从0开始编号。y是一个Shape = (n, 10)的Tensor,y_一样,所以log一下不会影响Rank/Shape,所以y_ * tf.log(y)之后仍然是Shape = (n, 10)的Tensor,这个时候在第1维进行求和,得到了Shape = [n]的Tensor,相当于对于每一个样本,计算了所有10个分类上的得分之和。最后reduce_mean一下计算均值得到Shape = []的Tensor,也就是最终的损失。

Gradient Descent

有了损失函数,就要想个办法来优化参数减少损失,这里是梯度下降方法,也是按下不表,详情看这里。梯度下降法如果展开来说可以写很多篇文章,从BP一直到ADAM等各种方法,我打算另外一系列文章记录一下。

利用Tensorflow已经实现好的优化器可以很容易的训练网络:

1
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

以下是和算法无关,Tensorflow相关的操作了:

1
2
3
4
5
6
7
8
init = tf.initialize_all_variables() # 初始化所有变量
# 创建一个Session
with tf.Session() as sess:
    sess.run(init)
    # 迭代1000次,每次从训练集中拿出100个随机的样本,所以本质上这是一个SGD
    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})

Evaluation

抛开疑问,以上训练过程已经完成,接下来就是测试模型的效果了,基本原理就是用测试集的x输入模型,得到预测的y与真实的y做一下对比,计算误差。

例子中的实现方式是:

1
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))

这里使用了argmax函数,这个函数很有用(尤其是在one-hot encoding的向量),argmax会给出输入的Tensor中,某一维上最大的数值的索引,而one-hot encoding中,索引就是label。这里argmax(y, 1)计算了y Tensor在第1维上的最大值的索引,输出的Tensor Shape = [N],也就是计算了每一个输入的测试样例输出的Y中概率最大的一个的索引,这个索引就表示了是哪个数字。

equal函数比较了预测输出和标注的正确结果,得到了一个Shape = (N)的Tensor,由True/False组成。

然后计算整体的分数:

1
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

cast就是变换一下数据类型,因为bool型是不能做算数运算的,因此变成float32(True –> 1.0, False –> 0.0),最后reduce_mean计算均值,得到一个[0, 1]之间的数值作为准确率。实际上最后的评估函数还有很多,这里只是一个最简单的例子。

定义好了操作,在测试集合上执行一下:

1
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

运行输出:0.9183

OK,本文到此结束,下一章开始从Deep MNIST for Experts开始。

本文由作者按照 CC BY 4.0 进行授权