深度学习专家级——MNIST(翻译)
TensorFlow是一个可以进行大规模数值计算的强大的库。它擅长的任务之一是实现并训练深度神经网络。在这个教程中,我们将学习TensorFlow模型的基本构建模块,构建一个深度卷积分类器
这篇教程,假定你对神经网络和MNIST数据集有所了解。如果不了解他们,先看一下对初学者的介绍.并且确保在学习之前先安装了TensorFlow。
关于这篇教程
这篇教程的第一部分解释了mnist_softmax.py中的代码,是Tensorflow模型的一些基本实现。第二部分展示了改进准确率的一些方法。
你可以从教程中拷贝粘贴代码块到你的Python环境中,或者你可以只选择通读这些代码。
在这篇教程中,我们将要完成:
创建一个基于查看图片中的每一个像素,并认出MNIST图像中数字模型的softmax回归函数。
使用Tensorflow训练模型,让模型通过”查看“上千个例子认出数字(运行我们的第一个Tensorflow的session就可以做到)
使用我们的测试数据测试模型的准确率。
构建,训练,和测试多层卷积神经网络以提高结果。
Setup
创建我们的模型之前,我先要下载MNIST数据集,启动TensorFlow会话。
下载 MNIST数据
如果你要从这篇教程中拷贝粘贴代码,从这两行代码开始,这两行代码将会自动的下载和阅读数据:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
这里,mnist是以NumPy数组的形式存储了训练,验证,和测试数据集的轻量级类。它还提供了一个遍历数据的功能,这个功能将在下面看到。
启动TensorFlow的InteractiveSession
TensorFlow依赖于高效的C++做后台计算。到这个后台的连接叫session。Tensorflow程序常见的用法是先创建一个图形,然后启动它的session。
在这里,我们使用方便的InteractiveSession类,它让TensorFlow更加灵活的让你构建你的代码。它允许你使用运行的图构建计算图这样的交错运算。当你在像IPython这样的交互环境中工作时这非常方便。如果你不使用InteractiveSession,那么你应该在启动session和启动图之前,构建整个计算图。
import tensorflow as tf
sess = tf.InteractiveSession()
计算图
为了在Python中执行有效的数值计算,我们通常使用像NumPy这样的库,它使用其他语言实现了高效的代码,在Python之外做像矩阵乘法这样的耗时操作。糟糕的是,每次操作切换回Python仍有许多开销。如果你在GPU上或分布式的运行一个计算,这种开销尤其大,在数据传输方面花费很高。
TensorFlow也在Python外做了它的重量级操作,但要避免这个开销也需要进一步的操作才行。为了不独立于Python运行一个单独耗时操作,TensorFlow让我们描述整个运行在Python之外的交互操作的图。这个方法类似于在Theano或Torch中使用的方法.
因此,Python代码的角色是构建这个额外的计算图,并且指出那一部分的计算图应该被运行。更多细节请参见计算图基本用法部分。
构建Softmax回归模型
在这个章节,我们将使用单个线性层构建一个softmax回归模型。在下一个章节,我将要扩展这个模型为使用多层卷积网络的softmax回归案例。
Placeholders
我们开始通过创建输入图像和输出目标类型的方式构建计算图。
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
这里x 和y_不是特定的值。而是占位符——当我们让TensorFlow运行计算时,才输入值。
输入图像x将由浮点型数值的2d tensor组成。这里我们将给他分配一个 [None, 784]形状的矩阵,其中784是28X28像素的MNIST图像拉平的维度,None是指第一个维度符合批量的大小,可以是任何尺寸的。目标输出类y_将由2d tensor组成,它的每一行是one-hot 10-维向量,表示MNIST图像符合数字的分类(0-9)
placeholder的shape参数是可选的,但是它允许TensorFlow自动捕捉因shape不一致产生的bug。
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的tensors。W是784x10的数组(因为我们有748个输入特征和10个输出),b是一个10-维度向量(因为我们有10个分类)。
在Variables可以在session内可以使用之前,必须先使用这个session初始化他们。这一步是初始化已经被定义的值(在这个案例中tensor全是0),把值分配给每个变量。一次性就可以初始化所有Variables的值:
sess.run(tf.global_variables_initializer())
预测分类和损失函数
现在,我们就可以实现我们的回归模型了。仅需一行!我们用权重矩阵W乘以输入的图像向量x,加上偏量b。
y = tf.matmul(x,W) + b
我们可以简单的指定一个损失函数。损失说明在这个例子中模型的预测是怎么样的糟糕;我们试着在训练所有例子中尽量减少这个值。这里,我们的损失函数是目标和softmax激活函数应用到模型的预测之间的交叉熵。在初学者教程中,我们使用了稳定的公式:
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y, y_))
注意,tf.nn.softmax_cross_entropy_with_logits本身在模型的非标准模型预测应用了softmax,并计算所有类的总和,tf.reduce_mean计算了这些和的平均值。
训练模型
现在,我们定义了我们的模型,训练了损失函数,直接使用TensorFlow进行训练。因为TensorFlow知道整个图,它可以使用自动微分法找到相对于每个变量的损失梯度。TensorFlow有多种内置的优化算法。在这个例子中,我们将使用步长为0.5的最陡梯度下降法,来降低交叉熵。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
在这单独一行,TensorFlow真正做的是,给计算图添加一个新的操作。这些操作包括计算梯度,计算更新步长的参数,把步长更新到参数中。
当运行时,把梯度下降更新到参数,返回操作train_step。因此,训练模型可以通过反复运行train_step完成。
for i in range(1000):
batch = mnist.train.next_batch(100)
train_step.run(feed_dict={x: batch[0], y_: batch[1]})
在每次训练迭代中,我们加载100个训练样例,然后,我们用训练样例在feed_dict中替换占位符tensor x和y_,运行train_step操作。注意,你可以使用feed_dict在计算图中替换任何tensor————这不仅限于占位符。
评估模型
我们的模型做的怎么样?
首先,让我们找到正确预测的标签。tf.argmax是一个很有用的函数, 它给你返回在tensor中某个维度中的最大值的索引。例如,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}))
构建多层卷积网络
92%的准确率对MNIST来说很差。这几乎不怎么样。在这节中,我们将要解决这个问题,从一个非常简单的模型,跳到中等复杂的模型:一个小的卷积神经网络。这将让我们达到99.2%的准确率——不是最好的,但也相当不错。
权重初始化
为了创建这个模型,我们需要创建许多权重和偏量。为了对称破缺,我们通常使用少量噪音初始化权重,并且防止0梯度。由于我们使用了ReLU神经元,为了避免使用“死神经元”,初始化偏量时使用一个小的正数是个不错的选择。为了避免在构建模型是重复这样做,让我们创建两个方便的函数做这些。
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,0填充,因此输出和输出的大小是一样的。我们的池化是一般老的最大池化2x2块。为了让代码更清晰些,我们把这些抽象成函数。
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')
第一个卷积层
现在,我们可以实现我们的第一层了。它由卷积组成,接下来是池化层。卷积将把每一个5x5小块计算成32个特质。他的权重tensor的形状将是[5, 5, 1, 32]。前两维的是小块的大小,下来是输入通道的数量,最后是输出图片的数量。我们也将为每一个输出通道提供一个带组件的偏量向量。
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
为了应用层,我们首先要把x改造成4d tensor,让第二和第三个维度符合图片的宽和高,最后一个维度,符合颜色通道的数量。
x_image = tf.reshape(x, [-1,28,28,1])
然后,我使用权重tensor卷积x_image,再加上偏量,应用ReLU函数。最终最大池化。max_pool_2x2 方法将把图像的尺寸降低到14x14。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
第二个卷积层
为了构建深度网络,我们堆叠了多个这种类型的层。第二层将会为每个5x5 小块,生成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)
密集的连接层
现在图片的尺寸降低到了7x7,我们增加一个用1024个神经元的全连接层,允许对整个图像进行处理。我们把tensor从池化层重塑为一个批次的向量,乘以权重矩阵,再加上偏量,然后应用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)
Dropout
为了降低过拟合,我们在读出层之前应用dropout。我们创建了占位符,在dropout期间保存神经元输出的概率。这让我们可以在训练的时候打开dropout,在测试的时候关闭dropout。TensorFlow的tf.nn.dropout操作处理除了掩盖它们外自动处理了神经元输出的缩放比例,因此dropout仅在没有任何额外的缩放比例时起作用。
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
读出层
最终,我们增加了一个层,就像softmax回归层一样。
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
训练并评估模型
这个模型做的怎么样?为了训练和评估它,我们将要使用和上面简单的单一层SoftMax网络几乎一样的代码。
不同的是:
我们将使用更复杂的ADAM优化器替换最陡梯度下降优化器。
我们将在feed_dict中加一个额外的参数keep_prob来控制dropout率。
我们将在训练过程中每100次迭代后增加日志。
尽管运行代码,但是它做了20,000迭代训练,或许会花一些时间(可能是半个小时),取决于你的处理器。
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(y_conv, y_))
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}))
运行这段代码后,最终的测试集精确度应该大约为99.2%。
我们已经学会了怎样使用TensorFlow快速并容易的构建,训练,和评估一个相当复杂的深度学习模型。
1:对于这个小的卷积网络,事实上,性能几乎与没有使用dropout一样。Dropout在降低过拟合方面往往非常有效,但是当训练非常大的神经网络时也很有用。
无戒365挑战营 47