TensorFlow之四 ----- 面向专家的深度MNIST

来源:互联网 发布:车间数据统计 编辑:程序博客网 时间:2024/06/02 11:39

TensorFLow是一个做大规模数字计算的强有力的库。它发展的其中一个任务就是实现和训练深度神经网络。在这个教程中我们将会学习一个TensorFlow模型的基础建立块,当构建一个深度卷积的MNIST分类器的时候。


这份指导假设已经熟悉了神经网络和MNIST数据集。如果你还没有这些背景,请阅读<初学者指导>。在开始之前请确认你已经安装了TensorFlow。


1. 关于这个教程

这个教程的第一部分解释在mnist_softmax.py代码中发生的事,这是一个TensorFlow模型的基本实现。第二部分显示了一些提升准确性的方法。


你可以复制和粘贴这个教程中的每个代码片段到一个Python环境来遵循,或者你能从mnist_deep.py里下载完全实现的深度网络。


我们将在这个教程里完成的事:

  • 创建一个softmax回归函数,它是用于识别MNIST数字的模型,基于在图片中寻找每一个像素
  • 使用TensorFlow来训练模型来识别数字,通过让它‘看’上千的图片(并且运行我们的第一个TensorFlow会话来这样做)
  • 使用我们的测试数据检查模型的准确性
  • 构建,训练和测试一个多层卷积神经网络来提高结果的准确性

2. 安装

在创建我们的模型之前,我们将会首先下载MNIST数据集,并且启动一个TensorFlow会话。

2.1 下载MNIST 数据

如果你正在从这个教程复制和粘贴代码,以下面这2行开始,它将会帮助自动下载和阅读数据:
from tensorflow.examples.tutorials.mnist import input_datamnist = input_data.read_data_sets('MNIST_data', one_hot=True)
这里mnist是一个轻量级的类,它储存训练,验证,和测试集,以Numpy数组的形式。它同样提供一个函数来迭代数据的最小批次,我们将会在下面使用到

2.2 启动TensorFlow InteractiveSession

TensorFlow依赖于高效的C++后端来做它的计算工作。连接到这个后端的连接称为一个会话session。TensorFlow程序的普遍用法是首先创建一个图标graph,然后在一个会话里启动它。

这里我们使用方便的InteractiveSession类来代替,它让TensorFlow使用起来更方便关于你如何构建你的代码。它允许你交错操作,建立一个运行图表的计算图标。当在类似IPython的交互式上下文的环境下工作时将变得格外方便。如果你没有使用InteractiveSession,那么你应该在开始一个会话和启动图表之前构建整个计算图表。
import tensorflow as tfsess = tf.InteractiveSession()

2.3 计算图表

为了在Python里做高效的数字计算,我们经常使用类似Numpy的库,来在Python外面做一些类似矩阵乘法这样昂贵的操作,在另一个语言中使用高效的代码实现。不幸的是,在切换回Python的每个操作的时候,仍然会有很多的花销。如果你想要在GPU或者在一个分布的方式(在传输数据上有一个高额的花销)运行计算,这种花销将会特别巨大。

TensorFlow在Python外做‘重物搬运’,但是它做了一些更深的步骤来避免这种花销。TensorFlow让我们描述一个完全运行在Python之外的交互操作的图表,而不是独立的在python中运行单独的昂贵操作。这个方法有点类似在Theano或Torch里使用的一样。

Python代码的角色因此就是建立这种外部的计算图表,并且命令计算图表的那个部分应该被运行。阅读<开始使用TensorFlow>的<计算图表>选节来了解更多明细。

3. 建立一个softmax回归模型

在这个选节我们将会使用一个单一线性层来建立一个softmax回归模型。在下一个选节,我们将会将这个softmax回归的例子扩展为使用一个多层卷积网络。

3.1 占位符Placeholders

我们开始通过为输入图片和目标输出类创建节点来构建计算图表。
x = tf.placeholder(tf.float32, shape=[None, 784])y_ = tf.placeholder(tf.float32, shape=[None, 10])
这里x和y_不是特定的值。当然,他们每一个都是占位符 -- 是一个当我们要求TensorFlow运行一个计算时我们将会输入的值。
输入图片x将会有一个2d的浮点数字张量组成。这里我们将它赋值为一个[None,784]的维度,其中784是一个平坦成28*28像素的MNIST图片的维度,None表示第一个维度,对应于批次的大小,可以使任意的大小。目标输出类y_同样也由一个2d张量组成,其中每一行都是一个one-hot10维的矢量,表示对应的MNIST图片属于哪一个数字类(0到9)

维度shape参数对于占位符placeholder来说是可选的,但是它允许TensorFlow来自动从不一致的张量维度中捕获bug。

3.2 变量Variables

我们现在为我们的模型定义权重W和偏好b。我们可以想象将这些当成额外的输入,但是TensorFlow有一个更好的方法来处理他们:变量Variable。一个变量Variable是一个存在于TensorFlow计算图表中的值。它能被计算使用甚至是修改。在机器学习应用里,一般都会有模型参数作为变量。
W = tf.Variable(tf.zeros([784,10]))b = tf.Variable(tf.zeros([10]))
我们调用tf.Variable来为每个参数传递初始值。在上例中,我们将W和b都初始化为了全0的张量。W是一个784*10的矩阵(因为我们有784个输入特征和10个输出)和b是一个10维的矢量(因为我们有10个类)。

在变量能在一个会话里使用之前,它们必须使用哪个会话来初始化。这个步骤提取已经被指定的初始化值(这个例子中是全0的张量)并且将他们赋值给每个变量。这可以在瞬间对所有的变量使用:
sess.run(tf.global_variables_initializer())

3.3 预测类和损失函数

我们现在可以实现我们的回归模型了。它只需要一行!我们将向量化的图片x乘以权重矩阵W,并且加上偏好b。
y = tf.matmul(x,W) + b
我们可以很容易的指定一个损失函数。损失表示在一个单独的例子上模型预测的有多糟糕;我们通过训练所有的例子尝试最小化这个损失。这里我们的损失函数是在目标和应用在模型预测上的softmax激活函数之间的交叉熵。正如在初学教程中的一样,我们使用稳固构想:
cross_entropy = tf.reduce_mean(    tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y))
注意,tf.nn.softmax_cross_entropy_with_logits在内部应用softmax到模型的非规格模型预测上并且累加所有的类,tf.reduce_mean计算这些汇总的平均值。

4. 训练模型

既然我们定义了我们的模型并且训练了损失函数,我们可以直接使用TensorFlow来训练了。因为TensorFlow知道整个计算图表,它可以根据每个变量来自动区分找到损失的梯度。TensorFlow有各种各样的内置优化算法。在这个例子中,我们将使用最陡梯度下降,步长为0.5,来降低交叉熵。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
TensorFlow在这一样真正做的事是添加新的操作到计算图表中。这些操作包括计算梯度,计算参数更新步长,并且应用更新步长到参数中。

返回的操作train_step,当运行时,将会应用梯度下降更新到参数上。训练模型将会因此通过重复运行train_step来完成。
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,用训练例子来替换x和y_占位符张量。注意,你可以使用feed_dict来替换你计算图表里的任意张量  -- 它不是严格对占位符的。

4.1 评估模型

我们的模型做的怎么样?

首先我们指出我们预测的正确的标签在哪里。tf.argmax是一个相当有用的函数,它返回给你沿着某个轴方向的最高项的索引。例如tf.argmax(y,1)是对于每个输入我们模型认为最可能的标签,而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, tf.float32))
最终,我们在测试数据上评估我们的准确性。这将会是大约92%的正确性。
print(accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

5. 建立一个多层卷积网络

在MNIST上得到92%准确性是糟糕的。这几乎是令人尴尬的糟糕。在这一节,我们将会修正它,从一个非常简单的模型跳到适当复杂的东西:一个小型的卷积神经网络。这将会让我们得到大约99.2%的准确率 -- 不是艺术的状态,而是尊敬。

这里有一个图,使用TensorBoard创建的,关于我们将会建立的模型的:

5.1 权重初始化

为了创建这个模型,我们将需要创建许多的权重和偏好。一般来说应该使用少量的噪声来初始化权重用于对称破坏,并且防止0梯度。因为我们使用ReLU神经,使用一个清凉的正初始偏好来避免“死神经”也是一个好的习惯。让我们来
创建2个就近的函数来做这个,而不是当我们构建模型的时候重复的做。
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)

5.2 卷积和池

TensorFlow同样给我们在卷积和池操作上提供了许多灵活性。我们如何来处理这些界限?我们的步长是多少?在这个例子中,我们总是选择朴素的版本。我们的卷积使用1的步长并且由0填充,所以输出和输入有着同样的大小。我们的池是超过2*2块的普通老的最大化池。为了使我们的代码更加简洁,让我们同样来将这些操作抽象成函数。
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')

5.3 第一卷积层

我们现在可以实现我们的第一层。它将由卷积组成,紧随其后的是最大化池。卷积将会计算每个5*5块上的32个特征。它的权重张量的[5,5,1,32].前面2维是块大小,接下来是输入管道的数字,最后的是输出管道的数字。对于每个输出管道我们仍然有一个偏差矢量。
W_conv1 = weight_variable([5, 5, 1, 32])b_conv1 = bias_variable([32])
为了应用这层,我们首先将x改造成一个4维张量,其中第二和第三维对应于图片的宽和高,最后一维对应于颜色管道的数字。
x_image = tf.reshape(x, [-1, 28, 28, 1])
然后我们将x_image和权重张量卷积,添加偏差,应用ReLU函数,和最后的最大化池。max_pool_2*2方法将会将图片的大小减少到14*14.
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)h_pool1 = max_pool_2x2(h_conv1)

5.4 第二卷积层

为了建立我们的深度网络,我们将这种类型的多个层堆叠在一起。第二层对于每个5*5的块将会有64个特征。
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)

5.5 紧密连接层
既然图片的大小被减少到了7*7,我们添加一个拥有1024神经元的全连接层来允许在整个图片上进行处理。我们将来自池层的张量改造成一批向量,乘以一个权重矩阵,添加一个偏差,并且应用一个ReLU。
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)

5.6 信号丢失

为了减少过度拟合,我们将在读出层之前应用dropout。我们为在dropout期间神经元的输出所保留的概率创建一个占位符。这将允许我们在训练期间开启dropout,在测试期间关闭。tensorFlow的tf.nn.dropout操作除了屏蔽外会自动处理缩放神经元的输出,所以dropout运行不带任何额外的缩放。
keep_prob = tf.placeholder(tf.float32)h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

5.7 读出层

最终,我们添加一个层,就好像上面的一层softamx回归一样。
W_fc2 = weight_variable([1024, 10])b_fc2 = bias_variable([10])y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2

5.8 训练并评估模型

我们的模型如何?为了训练并评估我们将会使用和上面的一层softmax网络几乎一致的代码。
不同点在于:
  • 我们将会使用更复杂的ADAM优化器来替换最陡梯度下降优化器。
  • 我们将会在feed_dict中包含额外的参数keep_prob来控制信号丢失率。
  • 我们将会在训练过程中在每100次迭代中添加日志。
我们同样也会使用tf.Session而不是tf.InteractiveSession.这将更好的分隔创建图表(模型指定)的过程和图表评估(模型适应)的过程。它通常生成更简洁的代码。tf.Session被创建在一个with块中,所以一旦块退出它将自动销毁。

请随意运行代码。要意识到它会做20000训练迭代并且需要花费一点时间(有可能要半个小时),取决于你的处理器。
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))with tf.Session() as sess:  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}))
运行这段代码后最后的测试准确率将接近99.2%。
我们已经学习了如何快速简便的使用TensorFlow来建立,训练,和评估一个相当复杂的深度学习模型。














原创粉丝点击