TensorsFlow学习笔记4----面向机器学习专家的深度MNIST教程(Deep MNIST for Experts)

来源:互联网 发布:seo要学多久 编辑:程序博客网 时间:2024/06/05 10:21

原文教程:tensorflow官方教程
翻译教程:极客学院

记录关键内容与学习感受。未完待续。。


面向机器专家的深度MNIST教程(Deep MNIST for Experts)

—–tensorflow是一个很强大的库,可以做大型数值运算。其中它最擅长的任务之一就是实现并训练深度神经网络。在这个教程中,我们将学习构建tensorflow模型的基本步骤,然后构建一个深度卷积MNIST分类器。

—–这个教程假设你已经对神经网络和MNIST数据集很熟悉。如果你不了解这些背景,可以查看针对初学者的教程。在开始前请确定已经安装tensorflow。

1、关于教程

—–教程的第一部分解释了mnist_softmax.py代码发生了什么,这是一个tensorflow模型的基本实现。第二部分展示了几种方法来提高准确度。

—–你可以复制粘贴本教程的每一行代码到一个Python环境中,或者你可以选择阅读这份代码。

—–在本教程中我们将完成:

  • 创建一个softmax regression函数,基于观察图像的每个像素点,这个模型可以识别手写数字。
  • 使用tensorflow通过“看”上千个例子来训练模型以识别数字(运行我们第一个tensorflow session)。
  • 用测试数据检查模型的正确度。
  • 建立、训练并且测试一个多层卷积神经网络来提高结果。

2、安装

—–在我们创建模型之前,我们必须首先加载MNIST数据集,并且开始一个tensorflow session。

2.1 加载MNIST数据

—–如果你复制粘贴了教程中的代码,这里这两行代码将自动下载和读取数据。

from tensorflow.examples.tutorials.mnist import input_datamnist = input_data.read_data_sets("MNIST_data/",one_hot=True)

—–mnist是一个轻量级的类,它以Numpy数组的形式存储着训练、验证和测试数据集,它也提供一个函数,用来在迭代中获得minibatches,后面我们会用到。

2.2 运行tensorflow的InteractiveSession

—–tensorflow依赖于一个高效的C++后端来执行计算。与后端的这个连接就称之为session。tensorflow程序的通常用法是创建一个图,然后在session中启动图。

—–这里,我们使用更加方便的InteractiveSession类,它使得你可以更加灵活的构建你的代码。它能让你在运行图的时候插入一些计算图,这些计算图是由某些操作构成的。这在可交互式环境,如IPython中,非常方便。如果你不使用InteractiveSession类,你需要在启动session,运行图之前就建立好整个计算图。

import tensorflow as tfsess = tf.InteractiveSession()

2.2.1 计算图

—–为了在Python更有效率的做数值运算,我们通常使用函数库,比如Numpy,来做一些复杂的操作,如在Python之外进行矩阵乘法,使用非Python语言用更有效的代码来实现计算。不幸的是,从外部运算切换回Python仍然是一个巨大的开销。如果你想用GPU计算,开销会更大,如果使用分布式计算,也会花费大量资源传输数据。

—–tensorflow把复杂的计算放在Python之外来完成,为了避免前面所说的开销也做了进一步的改善。并不是在Python之外单独运行单一复杂的操作,tensorflow先让我们用图来描述一系列可交互的操作,然后最后全部一起在Python之外运行。(这与Theano和Torch的方法类似)

——因此Python代码的目的就是建立一个外部计算图,以及安排计算图中的哪一部分被运行,可以从Computation Graph和Basic Usage查看更多细节。

3、建立softmax regression模型

——在这一节我们要建立一个拥有一个线性层的softmax regression模型,下一节我们将把它扩展为多层卷积网络的softmax regression模型。

3.1 占位符Placeholders

——我们通过对输入图像和目标输出类别创建节点来建立计算图。

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

——这里的x和y_并不是特定的值,相反,它们只是一个占位符,当我们要求tensorflow运行计算的时候,根据占位符可以输入具体的值。

—–输入图像是由2维的浮点数tensor组成的,这里我们分配给它的形状是[None,784],其中784表示一个28x28像素点的MNIST图像单一展开的维度,None表示第一个维度,与batch的大小有关,它可以是任意大小,即输入图像数量不唯一。目标输出类别y_也是一个2维tensor,每一行都是一个10大小的one-hot向量,这表示了MNIST图像所属于的数字类别(0到9)。

——虽然placeholder的参数shape是可选的,但是有了它,tensorflow可以自动捕捉因为数据维度不一致导致的错误。

3.2 变量Variables

——现在定义模型的权重W和偏置值b。我们可以把它想象成额外的输入,但是tensorflow有更好的办法来处理它们:Variables。一个Variable是一个值,存在于tensorflow的计算图中。在计算中可以被使用甚至被修改。在机器学习应用中,模型参数一般用Variables来表示。

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

——我们在调用tf.Variable的时候为每一个参数传入初始值。在这里,我们初始化W和b为全是0的tensor。W是一个784x10的矩阵(因为我们有784个输入特征和10个输出类别),b是一个10大小的向量(因为我们有10个类别)。

——在session中,在Variables被使用之前,他们必须被初始化。这个初始化的步骤是,为初始值指定具体的值(本例中全为0),并将其分配给每个Variable。可以一次性为所有的Variable完成初始化这一操作。

sess.run(tf.global_variables_initializer())

3.3 类别预测和损失函数

——现在我们可以实现模型,这只需用一行代码。我们把向量化后的输入图像x和权重矩阵W相乘,再加上偏置值b。

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

——可以很容易指定loss函数,loss表示模型的预测在一个单一例子上的糟糕程度,在训练过程中,我们试图最小化所有例子的loss。这里我们在目标和应用于预测的softmax激励函数之间使用cross-entropy函数。在初学者的教程中,我们使用了下面的方程式:

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y, lables=y_))

—–注意tf.nn.softmax_cross_entropy_with_logits是采用了模型内部的非规格化模型预测并将所有类别加起来,tf.reduce_mean是把这个和做了一个平均值。

4、训练模型

——现在我们开始定义模型和训练loss函数,直接用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个训练例子。我们运行train_step操作,使用feed_dict,用训练数据代替占位符的张量(tensor)x和y_。记住你可以使用feed_dict来替代计算图中的任何tensor,并不仅仅限制于占位符。

4.1 验证模型

——我们的模型做的怎么样?

——首先我们要找出我们预测正确的标签。tf.argmax是一个非常有用的函数,它可以给出某个tensor对象在某一维度上最大值所在的下标即它的索引值。举例来说,tf.argmax(y,1)是模型对任一输入图像x的预测标签,tf.argmax(y_,1)是真实的标签,我们可以使用tf.equal检查预测标签和真实标签是否相等(y和y_都由0,1构成的10大小的向量,且最大值就是1,有且仅一个,1所在的索引值就是标签,即预测的数字)。

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))

5、建立多层卷积网络

——MNIST得到的正确率只有92%,这很不好。在本节中,我们将做一些调整,从简单模型过渡到一个相对复杂的模型:一个小的卷积神经网络。它可以达到99.2%的准确率,虽然不是最好,但是还算令人满意。

5.1 权重初始化

——为了创建这个模型,我们需要创建大量的权重和偏置值。这个模型的权重在初始化的时候应该加入一些噪声来打破对称性,防止0梯度。由于我们使用的是RelU神经元,因此一个比较好的做法就是使用一个比较小的正确来初始化偏置值以避免“dead neurons”(神经节点输出恒为0)。为了不在建立模型时反复做初始化这一操作,我们定义了两个函数来初始化。

# 返回正态分布的数据作为W的初始值,后面那个参数是什么意思?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在卷积和池化操作上有很强的灵活性。我们如何处理边界?步长是多大?在这个例子中,我们将一直使用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')

5.2.1 对tf.nn.conv2d函数的介绍

—–参考于博客:参考博客1
—–对tf.nn.conv2d函数的介绍。函数的形式如下:

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)

——第一个参数input:做卷积操作的输入图像x,在下文中会提到,它是一个4维的tensor,形状为[batch, in_height, in_width, in_channels],具体含义是[训练时一个batch的图片数量, 图像高度, 图像宽度, 颜色通道数]。

——第二个参数filter:做卷积操作的另一个参数,相当于卷积神经网络中的卷积核,实际模型中的权重W,它是一个4维的Tensor,形状为[filter_height, filter_width, in_channels, out_channels],具体含义是[卷积核的高度,卷积核的宽度,输入图像通道数,输出通道数(卷积核个数)]。

——第三个参数strides:在做卷积时,在图像每一维的步长,这是一个一维的向量,长度是4。

——第四个参数padding:string类型,只能是”SAME”,”VALID”其中之一,这个值决定了不同的卷积方式,是否保留原图像的大小。

——第五个参数use_cudnn_on_gpu=None:bool类型,是否使用cudnn加速,默认为true。

——这个函数返回结果是一个tensor,就是我们常说的feature map。

5.2.2 对tf.nn.max_pool函数的介绍

——参考于博客:参考博客2
—–对tf.nn.max_pool函数的介绍。函数的形式如下:

tf.nn.max_pool(value, ksize, strides, padding, name=None)

——第一个参数value:池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map(卷积层的输出),形状为[batch, height, width, channels]。

——第二个参数ksize:池化窗口的大小,是一个4维向量,一般是[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1,中间两个维度是实际池化窗口的大小。

——第三个参数strides:和卷积类似,窗口在每一个维度上滑动的步长,一般也是[1, stride,stride, 1]。

——第四个参数padding:和卷积类似,可以取’VALID’ 或者’SAME’
返回一个Tensor,类型不变,形状是[batch, height, width, channels]。

5.3 第一层卷积

—–现在可以实现第一层,它是由卷积接一个池化组成。卷积在每个5x5的patch中计算出32个特征。权重tensor的形状就是[5, 5, 1, 32],前两个维度是patch的大小,第三个维度是输入的通道数,最后一个维度是输出的通道数,而每一个输出通道都有一个偏置值向量。

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

—–为了用这一层,我们首先把x转换为4维的tensor,第二维和第三维对应于图像的宽和高,最后一维是颜色通道数(灰度图颜色通道数为1,RGB彩色图为3)。

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

—–我们把x_image和权重tensor进行卷积再加上偏置值,应用到RelU函数上,最后最大池化。max_pool_2x2方法将图像大小减为14x14。

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

5.4 第二层卷积

—–为了建立一个深度网络,我们把几个类似的层堆叠起来,第二层中,每个5x5的patch会得到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 密集连接层

—–现在图像大小已经被减成7x7的了,我们添加一个拥有1024个神经元的全连接层,用于处理整个图像。我们把池化层的输出tensor,reshape成一些向量,然后乘以权重矩阵,加上偏置值,输入到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.5.1 Dropout

—–为了减少过拟合,我们在输出层之前采用dropout。我们使用一个placeholder来代表一个神经元的输出在dropout中保持不变的概率。这允许我们可以在训练时启动dropout,在测试时关闭dropout。tensorflow的tf.nn.dropout操作可以屏蔽神经元的输出,还可以自动处理神经元输出值的范围(scale),所以用dropout的时候可以不用考虑scale。

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

5.6 输出层

—–最后,我们添加一个层,就想前面的单层softmax regression一样。

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

5.7 训练和验证模型

——我们的模型做的怎么样?为了训练和验证它,我们使用和之前单层softmax 网络几乎一样的代码。但是不同的是:

  • 我们使用更加复杂的ADAM优化器来做最快梯度下降。
  • 我们在feed_dict中加入额外的参数keep_prob来控制dropout的比例。
  • 在训练时,每100次迭代后添加一份日志。

——直接运行这段代码看起来简单,但它做了20000次训练迭代,而且需要运行一段时间(可能需要半个小时),这取决于你的处理器。

cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y_conv, labels=y_))train_step = tf.train.AdamOptimizer(le-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快速、简单的建立、训练和验证一个略微复杂的深度学习模型。

——注:对于小的卷积网络,有无dropout的性能可能完全相同。Dropout是一个非常有效的减少过拟合的方法,但是在训练比较大的神经网络时很有用。


6、实际运行

——以上,最终实际运行代码和结果如下:

——代码:

# step1 获取数据集from tensorflow.examples.tutorials.mnist import input_datamnist = input_data.read_data_sets("MNIST_data/", one_hot = True)# step2 导入tensorflow模块,并且创建会话import tensorflow as tfsess = tf.InteractiveSession()# step3 设置输入数据占位符、权重标量、偏置值标量x = tf.placeholder(tf.float32, shape=[None, 784])y_ = tf.placeholder(tf.float32, shape=[None,10])# 修改后# step3 定义初始化权重W和偏置值b的函数 # 用正态分布的数据作为权重W的初始值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)# step4 定义卷积函数和池化函数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')# step5 第一卷积、激励、池化层W_conv1 = weight_variable([5, 5, 1, 32])b_conv1 = bias_variable([32])# 修改向量化的输入图像的形状x_image = tf.reshape(x, [-1, 28, 28, 1])h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)h_pool1 = max_pool_2x2(h_conv1)# step6 第二卷积、激励、池化层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)# step7 全连接层W_fc1 = weight_variable([7 * 7 * 64, 1024])b_fc1 = bias_variable([1024])# reshape第二池化层后的tensor变成向量形式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减少过拟合keep_prob = tf.placeholder(tf.float32)h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)# step8 输出层W_fc2 = weight_variable([1024, 10])b_fc2 = bias_variable([10])y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2# step9 定义损失函数和优化器cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y_conv, labels=y_))train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)# step10 定义验证变量并初始化所有tensorcorrect_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())# step11 迭代20000次,每次取50个数据,每100次迭代输出一次日志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})# step12 打印测试结果print("test accuracy %g" % accuracy.eval(feed_dict={x:mnist.test.images, y_:mnist.test.labels, keep_prob:1.0}))

——运行结果截图—训练中间的打印日志:

这里写图片描述

——运行结果截图—最终验证测试数据的正确率,这里出了一个小问题,不过不是程序本身的原因,如下:

——问题描述:terminate called after throwing an instance of ‘std::bad_alloc’ what(): std::bad_alloc

这里写图片描述

——问题解决方法:硬件不行,内存太小了,测试10000张图像时跑不动,建议改成1000张,代码如下

test_batch = mnist.test.next_batch(1000)print("test accuracy %g, all use time %g s" % (accuracy.eval(feed_dict={x:test_batch[0], y_:test_batch[1], keep_prob:1.0}), t_all-t1))

——修改位置:

这里写图片描述

——最终测试结果如下,准确率98.9%,总共跑了一个100分钟左右,图中的时间time.clock()函数的计时,是实际处理器处理的时间,单位是s,但是这里忘记减去初始时间,所以并非真正运行时间。

这里写图片描述


——以上,第一个卷积神经网络训练MNIST已经完成。

0 0
原创粉丝点击