深入理解tensorflow人工智能面面观tensorflow

[译]与TensorFlow的第一次接触(五)之多层神经网络

2016-08-21  本文已影响11329人  cn_Fly

     后面会在微信公众号中推送后续的翻译章节,与TensorFlow的第一次接触系列已整理成pdf,关注公众号后回复:tensorflow即可下载~~公众号:源码之心

源码之心

本章中,我们继续使用之前章节中的MNIST数字识别问题,与读者一起编码实现一个简单的深度学习神经网络。

      如我们所了解的,一个深度学习神经网络由相互叠加的多层组成。特别的,本章中我们会建立一个卷积神经网络---典型的深度学习样例。卷积神经网络由Yann LeCunn及其它人一起在1998年发明并流行起来。这些卷积网络在最近的图像识别中引领了最高性能表现;例如,在这个数字识别的例子中,它就达到了高于99%的精度。

      本章的其余部分,我会通过示例代码来讲解神经网络中最重要的两个概念:卷积和池化,关于它们参数的细节超出了本书的讨论范围。但是,读者可以运行本章中的所有代码,也希望读者能从中理解卷积网络背后的整体认识。

卷积神经网络

      卷积神经网络(CNN)是深度学习一个特殊示例,它在计算机视觉有非常重要的影响。

      CNN的一个典型特性就是它们的输入基本全是图片,这可以有很高效的实现并减少需要的参数。让我们回顾一下MNIST数字识别样例:读入MNIST数据并定义placeholders后:

      我们可以对输入照片数据的shape进行重建,代码如下:

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

      在这我们把输入shape转换成了4D tensor,第二与第三维度对应的是照片的宽度与高度,最后一个维度是颜色通道数,本例子中是1。

      我们可把神经网络的输入看作2维空间中大小为28*28的神经元:

      定义卷积神经网络有两个基本概念:filters与characteristic maps。这些概念可被表述为一组特殊的神经元,稍后我们就会看到。最后要的是我们先简明介绍这两个概念对CNN的重要性。

      直观地来说,卷积层的主要目的就是检测图像中的特征或可见特性,如边缘,线,块,颜色等等。这些是由隐藏层负责的,它们会与输入层相连接。在CNN中,输入数据并不是与隐藏层中的神经元全连接;而只是很小的包含图片像素值的局部空间相连。如下图所示:

     更精确地来说,在本例子中,隐藏层的每一个神经元是与一个输入层中5*5小区域(25个神经元)相连接。

      我们可看成一个5*5大小的窗口在包含整个照片的输入层(28*28)上滑行。窗口滑过整层的所有神经元。对于每一位置的窗口,隐藏层中都有一个神经元来处理这部分信息。

      可理解为窗口从图片的左上角开始;这部分信息输入给隐藏层中的第一个神经元。然后窗口向右滑动一个像素;该部分的信息与隐藏层中的第二个神经元相连接。一直持续这个动作直到整体平面从上到下,从左到右被窗口全部转换:

      分析这个具体例子,可以发现输入照片大小为28*28,窗口为5*5,会产生第一层隐藏层中24*24排列的神经元,这是因为我们需要从上到下移动窗口23次,从左到右移动窗口23次来覆盖整个输入图片。在这里是假设窗口每次只移动一个像素,所以新窗口与上次的窗口会有重叠。

      在卷积层中每次移动超过一个像素是可能的,这个参数叫作步长。另外一个扩展是对边缘填充0(或其它值),所以窗口可滑过图像的边缘,这可能会产生更好的结果。控制这个特性的参数是padding,你可以用它来决定padding的大小。探讨这两个参数的实现细节同样超出了本书的讨论范围。

      基于我们研究的样例,遵循之前章节的形式,我们需要一个bias b与一个5*5的矩阵W来连接输入层与隐藏层的神经元。CNN的一个关键特性是这个权重矩阵W与bias b是隐藏层中所有神经元间共享的。本例子中是24*24(576)个神经元。读者将会发现与全连接的神经网络相比,这会大大减少权重参数的数量。具体来说,由于共享了权重矩阵W,参数数量会从14000(5*5*24*24)降到25(5*5)。

      这个共享的矩阵W与bias b在CNN中经常被叫作kernel或filter。这些filters与图像处理程序中润色图片的那些是类似的,此处是用来找出有鉴别性的feature。建议读者查阅GIMP手册中的例子来了解卷积是如何工作的。

      一个矩阵与bias定义了一个kernel。一个kernel仅仅只检测图像中一个特定相关的feature,所以建议使用多个kernels,每个想检测的特征一个kernel。这就意味着CNN中一个完整的卷积层包含了很多kernels。描述这些kernels的一个常用方法如下:

      隐藏层的第一层包含了多个kernels。此处,我们使用了32个kernels,每一个定义了5*5的权重矩阵W和bias b,这两个参数也是隐藏层间共享的。

      为了简化代码,定义了如下两个函数来表示权重W与bias b:

      为了不深入到细节,可通过自定义用一些随机值来初始化权重与一些正值来初始化化bias。

      对于我们描述的卷积层,经常在卷积层后面加一个池化层。池化层简单的浓缩卷积层的输出结果并创建一个压缩版本的信息并输出。本例子中,我们使用2*2区域的卷积层,通过池化将其压缩成一个点:

      有多种不同的实现方式来池化浓缩信息;本例子中,我们使用的方法叫max-pooling。这种方法中通过只保留2*2区域中的最大值来压缩信息。

      如上面所提到的,卷积层中包含了很多kernels,我们会对每一个应用max-pooling。一般来说,会有很多池化层跟卷积层:

      通过池化层将24*24的卷积结果转变成了12*12排列,该排列中的每一元素都是来源于2*2区域。不像是卷积层,这里的数据是平的,并不是由一个滑动窗口创建。

      直观地来说,池化就是找出某一特征是否在图片中出现,该特征的确切位置不如其它特征的相关位置重要。

实现模型

      本节中,会分析如何基于之前的例子(Deep MNIST)来实现CNN,该例子可在TensorFlow官网上找到。如之前所说,参数的实现细节与理论概念要远比本书中说明的复杂。因此只分析代码的整体,并不会深入到TensorFlow参数的很多细节中。

      我们需要定义几个参数来表示卷积与池化层。在每个维度中我们使用步长为1(滑动窗口步长),并用0来padding的模型。使用的池化层是在2*2区域上。为了简单计算,建议使用如下两个函数来实现卷积跟池化:

      现在可以实现第一个卷积层与池化层。此处我们使用了32个filters,每一个都有一个大小为5*5的窗口。我们必须定义一个tensor来保存shape为[5,5,1,32]权重矩阵W:前两个参数是窗口的大小,第三个参数是channel的数量。最后一个定义了我们想使用多少个特征。更进一步,还需要为每一个权重矩阵定义bias。使用之前定义的函数来创建变量:

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

b_conv1=bias_variable([32])

      ReLU(Rectified Linear unit)激活函数最近变成了神经网络中隐藏层的默认激活函数。这个简单的函数包含了返回max(0,x),所以对于负值,它会返回0,其它返回x。在我们的例子中,我们会在卷积层后的隐藏层中使用这个激活函数。

      代码中首先对输入图像x_image计算卷积,计算得到的结果保存在2D tensor W_conv1中,然后与bias求和,接下来应用ReLU激活函数。最后一步,对输出应用max-pooling:

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

h_pool1=max_pool_2x2(h_conv1)

      当构建一个深度神经网络时,可以相互叠加很多层。为演示如何做到这样,我会创建64个filters且窗口大小为5*5的第二层卷积层。此时我们会需传递32个channels,因为这是前一层的输出结果:

      当我们对12*12排列应用5*5的窗口,步长为1时,卷积层的输出结果是7*7维度。下一步是对输出的7*7增加一个全连接层,最终结果输入给softmax层。

     我们使用包含1024个神经元的一层来处理整个图片。权重与bias的tensor如下:

W_fc1=weight_variable([7*7*64,1024])

b_fc1=bias_variable([1024])

      tensor的第一维度表示第二层卷积层的输出,大小为7*7带有64个filters,第二个参数是层中的神经元数量,我们可自由设置。

      接下来,我们将tensor打平到vector中。我们在之前章节中已经看到softmax需要将图片打平成一个vector任为输入。通过打平后的vector乘以权重矩阵W_fc1,再加上bias b_fc1,最后应用ReLU激活函数后就能实现:

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,它会在softmax层前先调用tf.nn.dropout函数。我们还需要创建一个placholder来保存神经元被保留的概率:

keep_prob=tf.placeholder("float")

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

        最后,在模型中添加softmax层。Softmax函数返回输入属于每一个分类(本例子中是数字)的概率且概率总和为1。Softmax层如下:

W_fc2=weight_variable([1024,10])

b_fc2=bias_variable([10])

y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)

训练与评估模型

      现在我们已经准备好训练模型,模型会在卷积层中调整所有参数,一个全连接层来获得预测照片的结果。如果我们想知道模型表现如何,就需要之前的测试集。

      接下来的实现代码与上一章节中的十分类似,但有一个例外:将原来的梯度下降优化算法替换为ADAM优化算法,因为这个算法根据文献中的说明,它实现一种更有效的优化算法。

      同样还需要在feed_dict参数中提供keep_prob,用来控制dropout层保留神经元的概率。

cross_entropy=-tf.reduce_sum(y_*tf.log(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,"float"))

sess=tf.Session()

sess.run(tf.initialize_all_variables())

foriinrange(20000):

batch=mnist.train.next_batch(50)

ifi%100==0:

train_accuracy=sess.run(accuracy,feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0})

print("step %d, training accuracy %g"%(i,train_accuracy))

sess.run(train_step,feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})

print("test accuracy %g"%sess.run(accuracy,feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))

      本模型的所有实现代码同样可以在github上获得,我们可以发现该模型的精度达到了99.2%。

      现在简要介绍如何使用TensorFlow来建立,训练,评估深度神经网络作为结尾。如果读者已经可以运行本代码,会发现训练这个神经网络的时间要远远长于上一章节中的;可以设想神经网络层数越多,训练时间也会越久。建议读者阅读下一章节,讲述如何使用GPU训练模型,这会大大缩短训练时间。

      本章中的完代码可在github上的CNN.py下载到,下图中的代码供研究的需要:

上一篇下一篇

猜你喜欢

热点阅读