jlearning.cn

TensorFlow入门——MNIST手写字符识别

内容翻译自Deep MNIST for Experts

本文可能插入TensorFlow官网上的图片或者链接,需翻墙可以加载或访问。

TenforFlow是一个强大的大规模数字计算的软件包。它擅长的功能之一是实现并且训练深度神经网络。在这个教程中,我们将会学习构建一个基本的深度卷积MNIST分类模型,

关于这个教程

这个教程的第一部分解释在mnist_softmax.py代码中实现了什么,这是TensorFlow模型实现的基础。第二部分展示一些提高精确度的方法。

在这个教程中我们将会完成以下几点:

  • 建立一个softmax回归函数作为识别MNIST数字的模型,基于观察图片中的每一个像素。
  • 使用TensorFlow训练这个模型去识别图片,通过观察成千的样本。
  • 使用测试数据验证模型的精确度。
  • 建立、训练、测试一个多层卷积神经网络,来提升结果。

准备

开始之前,要先加载MNIST数据集,然后开始一个TensorFlow session

加载MNIST数据

复制粘贴下面的代码,程序会自动下载读取数据:

1
2
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

这里mnist是一个轻量的类储存训练、验证、测试数据,以NumPy数组的方式。其也提供了一个迭代小批的函数,我们下面会用到。

开始TensorFlow InterativeSession

TensorFlow在后端依靠高效的C++来执行计算。与后端的连接叫做sessioin。TensorFlow程序常见的用法是创建一个graph,然后再一个session中启动它。

这里我们选择使用方便的InteractiveSession类,它会让TensorFlow中你组织你的代码更灵活。它循序你交替运行建立一个计算图和运行这个图的操作。( It allows you tointerleave operations which build a computation graph with ones that run the graph. )这在例如IPython的交互式的环境中特别方便。如果你不使用InteractiveSesion,你应该建立一个完整的图在你启动session和运行这个图之前。

1
2
import tensorflow as tf
sess = tf.InteractiveSession()

计算图

为了在Python中高效的进行数值计算,我们使用像NumPy的包来在Python以外进行重量级运算,例如矩阵乘法。不幸的是,每个操作仍然有很多切换回Python的开销。如果你想运行这些计算在GPU上或者以分布式的形式这些在转换数据时有大量消耗的方式,这些开销变的相当昂贵。

TensorFlow也是把这些重量操作放在Python外面,但是它做了一些设定去避免这些开销。TensorFlow允许我们描述一个图在Python外面运行的交互操作,而不是使用Python运行一个单独的重量操作。这个方法和在Theano和Torch中的方法很像。

Python代码的作用是建立一个外部计算图,然后命令运算图的哪一部分应该被执行。查看Computation GraphGetting Started With TensorFlow获得更多细节。

建立一个Softmax回归模型

在这一部分,我们建立一个单层softmax回归模型,下一节我们将模型扩展为多层卷积网络。

占位符

我们通过创建输入图像的节点和输出类别的节点来建立计算图:

1
2
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])

输入的图像x由二维浮点数tensor组成。我们分配一个[None,784]shape。784是28*28图像,None代表第一位由每一批的数量决定。目标输出类别y_由二维tensor组成,每行是一个十维的矢量表示类别(0或者1)。

placeholdershape参数是可选择的,但是它允许TensorFlow在shape不一致的时候自动的捕获bug。

变量

我们定义权重W和偏置量b,我们可以设想通过额外的输入训练这些量。但是TensorFlow有一种更好的方式管理他们:variable。一个varable是TensorFlow的计算图中的一个值。他可以根据计算修改。

1
2
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))

我们在调用tf.Variable的时候传入一个初始值。在这个例子中,我们将w和b都初始为0.

Varialbe在session中调用之前,他们必须使用这个session初始化。这一步让已经指定的初始值赋值给每个变量。可以为所有变量一次性全部初始化。

1
sess.run(tf.global_variables_initializer())

预测类别以及损失函数

我们现在可以实现我们的回归模型:

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

我们可以和这一样简单的指定损失函数。这里,我们的损失函数是目标和这个预测模型softmax激活函数的交叉熵。作为初级教程,我们使用这个保险的形式:

1
2
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))

注意这里tf.nn.softmax_corss_entropy_with_logitsinternally(内部地) applies the softmax on the model’s unnormalized model prediction and sums across all classes。tf.reduce_mean求这些和的平均数。

训练这个模型

现在我们已经定义了我们的模型和损失函数,这让使用TensorFlow训练很简单。因为TensorFlow知道了所有的计算图,他可以使用自动的计算损失函数对于每一个变量的导数。TensorFlow有很多不同的自带最优化算法。例如我们使用梯度下降法,步长0.5去减少交叉熵:

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

这里在计算图上增加了一个新的操作,这个操作包含计算梯度、计算参数更新量、然后更新参数。

运行时被返回的操作train_step会应用到梯度下降中更新参数。因此,训练这个模型可以不停的运行train_step

1
2
3
for _ in range(1000):
batch = mnist.train.next_batch(100)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})

我们在每次训练迭代的时候加载100条训练样本。当我们运行train_step操作,使用feed_dict去取代placeholdertensorsxy_。注意,你可以使用feed_dict替代任何tensor在你的计算图中,不仅仅是placeholder.

评价这个模型

首先我们计算出我们预测正确标签的位置。tf.argmax是一个特别有用的函数,可以给你tensor中最高标量的索引。例如tf.argmax(y,1)是我们模型认为最有可能的结果,当tf.argmax(y_,1)是一个正确的标签。我们使用tf.equal去检查我们的预测是否正确:

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

返回我们一个布尔值。为了确定正确率,我们转换为浮点数,然后计算他们的平均数:

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

最终我们评价在测试数据上的精确度:

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

构建一个多层卷积网络

权重初始化

为了建立这个模型,我们将需要很多权重和偏置量。应该用一个很小的量初始化这些权重来打破对称,和预防0梯度。所以,我们使用ReLU)神经细胞。用一些小的正值初始化偏置量可以避免”死神经细胞“。为了防止在建立模型时重复这一过程,我们建立两个方便的函数去做这些事情:

1
2
3
4
5
6
7
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)

卷积和池化

TensorFlow也给予我们在在卷积和池化中很多灵活性。我们要怎样处理边界?步长多少?在这个例子中,我们将选择vanilla版本。我们的卷积使用步长1,pad 0.所以,输出和输入的规格是一样的。我们2*2格子中选择最大池化。为了保持代码整洁,仍然建立两个函数:

1
2
3
4
5
6
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1], padding='SAME')

padding :步长不能整除图片大小时边距的处理方法,“SAME”表示超过边界用0填充,使得输出保持和输入相同的大小,“VALID”表示不超过边界,通常会丢失一些信息。

第一个卷积层

我们可以实现我们的第一层。卷积将会计算5*5patch的32个特征。权重tensor的shape是[5,5,1,32]。前两个维度是patch大小。下一个数字是输入频道数,最后一个是输出频道数。而且我们将为每个输出加上一个偏置量。

1
2
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

我们先将x改造成4维的tensor。第二第三维对应图像的宽和长,最后一维对应彩色通道(channel)。

1
x_image = tf.reshape(x, [-1,28,28,1])

使用权重卷积,加上偏置量,应用ReLU函数,最后最大池化。池化将会把图片大小改变为14*14.

1
2
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

第二个卷积层

第二层有对于每个5*5的patch,有64个特征。

1
2
3
4
5
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

全连接层

1
2
3
4
5
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

Dropout

为了减少过拟合,我们在输出之前应用dropout。我们创建一个神经元输出概率的placeholder。在训练的时候弃用,在测试的时候关闭。TensorFlow的tf.nn.dropout自动的控制神经输出时mask他们。所以dropout不需要额外的值。

1
2
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

输出层

1
2
3
4
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

训练并且评价这个模型

和softmax神经网络的区别:

  • 使用复杂的ADAM optimizer取代梯度下降法。
  • feed_dict 中使用keep_prob控制dropout率。
  • 训练过程中x每100次迭代输出一次日志。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cross_entropy = tf.reduce_mean(
tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
sess.run(tf.global_variables_initializer())
for i in range(20000):
batch = mnist.train.next_batch(50)
if i%100 == 0:
train_accuracy = accuracy.eval(feed_dict={
x:batch[0], y_: batch[1], keep_prob: 1.0})
print("step %d, training accuracy %g"%(i, train_accuracy))
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
print("test accuracy %g"%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))