TensorFlow是进行large-scale数值计算的强大库。它擅长的任务之一是实施和训练深度神经网络。在本教程中,我们将学习TensorFlow模型的基本构建模块,同时构建深度卷积MNIST分类器。
这个介绍假定熟悉神经网络和MNIST数据集。如果你没有他们的背景,请参考MNIST初学者介绍。在开始之前务必安装TensorFlow。
关于本教程
本教程的第一部分解释了这个代码文件,这是一个Tensorflow模型的基本实现。第二部分展示了一些提高准确性的方法。
您可以将本教程中的每个代码片段复制并粘贴到Python环境中,或者您可以下载完整实现的深度网络。
我们将在本教程中完成的任务:
-
根据查看图像中的每个像素,创建一个softmax回归函数,该函数是识别MNIST数字的模型
-
使用Tensorflow通过数千个示例来训练模型以识别数字(并运行我们的第一个Tensorflow会话来执行此操作)
-
用我们的测试数据检查模型的准确性
-
构建,训练和测试多层卷积神经网络以改善结果
开始
在我们创建模型之前,我们将首先加载MNIST数据集,然后启动一个TensorFlow会话。
加载MNIST数据
如果您正在复制和粘贴本教程中的代码,请从这里开始这两行代码,它们将自动下载并读取数据:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
这里mnist
是一个轻量级类,它将训练,验证和测试集存储为NumPy数组。它还提供了一个函数来迭代数据minibatches,我们将在下面使用。
启动TensorFlow InteractiveSession
TensorFlow依靠高效的C++后端来完成它的计算。与此后端的连接称为会话。 TensorFlow程序的常见用法是首先创建一个图,然后在会话中启动它。
在这里,我们改为使用方便的InteractiveSession
类,这使得TensorFlow在你的代码结构上更加灵活。它允许你交互操作构建一个计算图并运行图。在IPython等交互式环境中工作时,这特别方便。如果你不使用一个InteractiveSession
,那么你应该在开始一个会话之前建立整个计算图启动图表。
import tensorflow as tf
sess = tf.InteractiveSession()
计算图
为了在Python中进行高效的数值计算,我们通常使用NumPy这样的库,在Python之外执行昂贵的操作,例如矩阵乘法,使用以另一种语言实现的高效代码。
TensorFlow也在Python之外进行繁重的工作,但是为了避免这种开销,还需要进一步的工作。 TensorFlow不是独立于Python运行一个昂贵的操作,而是让我们描述一个完全在Python之外运行的交互操作图。这种方法类似于Theano或Torch中使用的方法。
因此,Python代码的作用是构建这个外部计算图,并指定运行图的哪个部分。见计算图部分TensorFlow入门了解更多细节。
建立一个Softmax回归模型
在本节中,我们将建立一个单线性层的softmax回归模型。在下一节中,我们将扩展到具有多层卷积网络的softmax回归的情况。
占位符
我们通过为输入图像和目标输出类创建节点来开始构建计算图。
x = tf.placeholder(tf.float32, shape=[None, 784])
y_ = tf.placeholder(tf.float32, shape=[None, 10])
这里x
和y_
不是具体的值。相反,他们都是placeholder
– 我们要求TensorFlow运行计算时才会输入值。
输入图像x
将由一个二维张量的浮点数组成。在这里,我们给它分配shape
为[None, 784]
,其中784
是28×28像素MNIST图像的单一平面的维度,None
表示与批次大小相对应的第一个维度可以是任何大小。目标输出类y_
也将由2d张量组成,其中每行是one-hot 10维向量,指示对应的MNIST图像属于哪个数字类(0到9)。
placeholder
的shape
参数是可选的,但它允许TensorFlow自动捕捉源自不一致张量形状的错误。
变量
我们现在为我们的模型定义权重W
和偏置b
。我们可以想象把这些看作是额外的投入,但是TensorFlow有更好的方法来处理它们:Variable
。一个Variable
是在TensorFlow计算图中的一个值。它可以被使用,甚至被计算修改。在机器学习应用中,模型参数通常用Variable
表示。
W = tf.Variable(tf.zeros([784,10]))
b = tf.Variable(tf.zeros([10]))
我们将调用中每个参数的初始值传递给tf.Variable
。在这种情况下,我们初始化W
和b
作为充满零的张量。W
是一个784×10矩阵(因为我们有784个输入功能和10个输出),而b
是一个10维向量(因为我们有10个类)。
Variable
s可以在会话中使用之前,必须使用会话进行初始化。这一步取得已经指定的初始值(在这种情况下张量是零),并分配给每个Variable
:
sess.run(tf.global_variables_initializer())
预测类和损失函数
我们现在可以实现我们的回归模型。只需要一行!我们将向量化的输入图像x
和权重矩阵W
相乘,加上偏置b
。
y = tf.matmul(x,W) + b
我们可以很容易地指定一个损失函数。损失表明模型的预测在一个例子上有多糟糕;在所有的例子训练,我们尽量减小损失函数值。在这里,我们的损失函数是应用于模型预测的目标和softmax激活函数之间的cross-entropy。正如在初学者教程中,我们使用稳定的公式:
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
取这些数目的平均值。
训练模型
现在我们已经定义了我们的模型和训练损失函数,然后使用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
取代placeholder
张量x
和y_
。请注意,您可以使用feed_dict
替换计算图中的任何张量 – 它不仅限于placeholder
。
评估模型
我们的模型有多好?
首先我们要弄清楚我们在哪里预测了正确的标签。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}))
构建一个多层卷积网络
在MNIST上获得92%的准确性还不够好。在这一节中,我们将解决这个问题,从一个非常简单的模型跳到一些中等复杂的问题:一个小的卷积神经网络。这将使我们达到约99.2%的准确性 – 不是最先进的,但已经很不错了。
下面是一个用TensorBoard创建的关于我们将要构建的模型的图表:
权重初始化
要创建这个模型,我们需要创建很多权重和偏置。一般应该用少量的噪声初始化权重,以防止对称性破坏,并防止0梯度。因为我们正在使用RELU神经元,为了避免”dead neurons”,用比较小的正浮点数初始化它们也是一个很好的实践。为了方便起见,创建两个辅助函数。
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)
卷积和pooling(池化)
TensorFlow也为卷积和pooling操作提供了很大的灵活性。我们如何处理边界?我们的步幅是多少?在这个例子中,我们总是选择普通版本。我们的卷积使用步长为1,并填充零,以便输出与输入大小相同。我们的池化采用2×2块的普通最大pooling。为了保持我们的代码更清晰,我们也将这些操作抽象为函数。
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×5块计算32个特征。它的权重张量形状为[5, 5, 1, 32]
。前两个维度是色块大小,下一个是输入通道的数量,最后一个是输出通道的数量。每个输出通道还会有一个带有分量的偏向量。
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
为了应用图层,我们首先重塑x
到4d张量,第二维度和第三维度对应于图像宽度和高度,并且最后一个维度对应于色彩通道的数量。
x_image = tf.reshape(x, [-1, 28, 28, 1])
然后我们用权重张量对x_image
做卷积,加上偏差,应用ReLU函数,最后是最大池化。该max_pool_2x2
方法将图像大小减少到14×14。
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
第二卷积层
为了建立一个深层网络,我们堆叠了这种类型的几个层。第二层将为每个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)
密集连接层
现在图像尺寸已经减小到7×7,我们添加一个带有1024个神经元的全连接(FC)图层,以允许在整个图像上进行处理。我们将池化层的张量重塑为一批向量,乘以权重矩阵,添加一个偏差,并应用一个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。创建一个placeholder
在dropout保持神经元输出的概率。这可以让我们在训练过程中开启dropout,并在测试过程中将其关闭。 TensorFlow的tf.nn.dropout
op除了掩蔽它们之外,还自动处理缩放神经元输出,所以dropout只是在没有任何额外缩放的情况下工作。1
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优化器替代最陡的梯度下降优化器。
-
我们将包含附加参数
keep_prob
在feed_dict
控制dropout率。 -
我们将在训练过程中每100次迭代输出一次日志。
我们也将使用tf.Session而不是tf.InteractiveSession。这更好地分离了创建图(模型说明)的过程和评估图(模型拟合)的过程。这样做通常使代码更清晰。 tf.Session是在with
块中创建,所以一旦块被退出,它就会被自动销毁。
随意运行这个代码。请注意,它会进行20,000次训练迭代,可能需要一段时间(可能长达半小时),具体取决于您的处理器。
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快速,轻松地构建,训练和评估相当复杂的深度学习模型。
1:对于这个小卷积网络,没有dropout, 性能实际上几乎是相同的。dropout对于减少过度拟合通常是非常有效的,但是在训练非常大的神经网络时它是最有用的。↩