tensorflow(4)---mnist问题的深度卷积神经网络(基于官网文档的实现)

来源:互联网 发布:数据库数据存储方式 编辑:程序博客网 时间:2024/05/29 09:57

导语

那么,上次我们已经使用最基本的梯度下降法实现了mnist问题,并且还原了手写数字的原始模样,那么本文就开始讲述如何调用tensorflow中的深度学习中的卷积神经网络解决mnist问题

讲解开始

首先我们还是给出源代码,然后再分析

import input_datamnist = input_data.read_data_sets('MNIST_data', one_hot=True)import tensorflow as tfsess = tf.InteractiveSession()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)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')x = tf.placeholder("float", shape=[None, 784])y_ = tf.placeholder("float", shape=[None, 10])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)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)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)keep_prob = tf.placeholder("float")h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)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)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.run(tf.initialize_all_variables())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})

虽然说代码比较长···emmm开始分析吧~

分析:

import input_datamnist = input_data.read_data_sets('MNIST_data', one_hot=True)import tensorflow as tfsess = tf.InteractiveSession()

上面所示的这段代码大意是导入数据和定义sess,应该看了(3)的同学都大致知道他们的含义,这里就不再重新说一遍了。

接下来源代码一堆自己的函数,为保持思路的连贯,我们暂且不管这些函数的含义,继续我们看到了:

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

定义了两个placeholder,就是说我们将要输入x和y_来帮助这个程序进行训练,即这两个时我们等会儿要输入的placeholder。

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

上面这两句代码涉及到了卷积的定义,要理解他们,我们先来看看什么是卷积~

现在假设有一张图像可以用f(x,y)来表示,f(x,y)表示在(x,y)这一点的灰度值。然后,我们现在对这个图像矩阵做一个运算:

f(x,y)w(x,y)

然后这里的不是矩阵乘法,它的计算方法有固定的卷积公式,计算的具体详情可以参见知乎

然后,我们重点关注它时如何生成这个权重矩阵的。这里使用到了一个weight_bariable()的方法,也就是它刚才定义的啦~,这个方法的具体:

def weight_variable(shape):  initial = tf.truncated_normal(shape, stddev=0.1)  return tf.Variable(initial)

而其中tf.truncated_normal()这个方法,我们可以参考博客,大概就是输入一个规格,然后输出与之相应的正太分布的矩阵吧。(反正只是一种初始化的方式而已),具体是生成什么样子我们暂且不说。现在W_conv1是第一层卷积权重, 那么b_conv1是什么呢?我们可以看看b_conv1的定义是使用了bias_variable([32])这个方法,而关于这个方法的定义为:

def bias_variable(shape):  initial = tf.constant(0.1, shape=shape)  return tf.Variable(initial)

是使用了tf.constant()这个方法,这里所生成的这个b_conv1主要是在接下来要将的relu函数里面会用到。
接下来:

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

这里是把原来我们的原始数据(100行,784列的那个数据x)每一张图片转化成28×28的矩阵,最后一维表示图片的颜色通道数,因为是灰度图所以通道数为1,如果是彩色图,则为3。

接下来就要对我们已经处理好的图片和卷积矩阵和偏置项代入RELU函数之中进行卷积啦。并且进行一个叫做max pooling的操作

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

第一层卷积的实现方式就是通过一个relu卷积加上一个max pooling完成。

接下来,官网说要构造一个更深的网络,也就是进行第二层卷积,代码与我们之前所看到的类似:

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,这是再将图片进行一次卷积,这个步骤被称为密集连接层:

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的操作,详情这里暂且不具体介绍。(因为不算是重点把)

然后再从我们最后密集连接层所得到的参数中添加softmax层。

以上我们完成了tensorflow这幅图的大部分工作,包括如何一步一步卷积,到最后将密集连接层代入softmax中得到y_conv(也就是最后预测的标签),那么还需要定义我们最后的训练器和评估模型以及模型的运行,这段代码和(3)的代码差不多,只不过时把梯度下降法改成了ADAM优化器来做,所以就不再一句一句解释了,代码如下:

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.run(tf.initialize_all_variables())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})

以上便是使用卷积网络的基本流程,总结一下:

  1. 导入数据
  2. 定义x,y_等placeholder
  3. 生成权重矩阵和偏项的初值
  4. 将原数据改变形状为28×28的矩阵形式
  5. 将图片矩阵和权重矩阵和偏项代入RELU中,并将得到的结果使用max_pool方法,得到的结果作为下一层的图片矩阵
  6. 将max_pool所输出作为图片矩阵继续进行一次卷积,并max_pool,将所输出的变量修改形状后作为下一层的图片矩阵。代入RELU函数中,将输出记为h_fc1
  7. 使用dropout方法防止过拟合
  8. 将最后的W,b和h_fc1输入softmax函数中,使用交叉熵作为目标函数,定义train,和预测准确次数,精度。初始化参数,从mnist获取训练集,开始训练,从mnist中获取测试机,进行测试,输出精度。

细节

以上所述只是讲述大概,接下来将对其中某些方法进行进一步的讲解和分析~

权重矩阵和偏项到底是怎么赋的值

本部分旨在说明白在本文中所使用的函数的输入输出,对于其背后的原理及数学思想先暂时不聊。

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)W_conv1 = weight_variable([5, 5, 1, 32])b_conv1 = bias_variable([32])

首先我们注意到W_conv系列肯定都是Variable,所以是不能使用sess(W_conv)直接输出的,所以我们使用一下代码来实现weight_variable中initial的输出:

import tensorflow as tfsess = tf.Session()shape = [5, 5, 1, 32]initial = tf.truncated_normal(shape, stddev=0.1)a = sess.run(initial)print a 

然后我们对a进行一些简单的命令来了解initial的样子:
这里写图片描述
现在就明白[5,5,1,32]的用处了~也就是生成一个5行5列的数组,然后其中的每个元素是1行32列的形式存在的。

那么,这句话到底是什么意思?

initial = tf.truncated_normal(shape, stddev=0.1)

这是说,在initial这个数组中的每一个元素都是服从标准差为0.1,均值为0的正态分布。

以同样的方式我们来看偏项的初始化的定义方式:
这里写图片描述

conv2d函数有什么用?

回看conv2d第一次在我们代码中出现的样子:

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

emmm,这句话中同时出现了relu和conv2d,我们先来看内层函数conv2d的定义方式:

def conv2d(x, W):  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

是使用tf.nn.conv2d方法来定义的一个方法,关于这个方法:

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

先看input的作用:

input这个位置就是用来放图像的,正如官网demo中传入的时x_image一样,这个参数要求x_image是一个张量,并且诶具有

[图片数量,图片高度,图片宽度,图片通道数]

这样的shape。还记得我们的x_image是如何定义的吗?

x_image = tf.reshape(x, [-1,28,28,1])```![这里写图片描述](http://img.blog.csdn.net/20171111151657004?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)所谓图片通道数就是,如果通道数是1,那就表明时黑白图像,如果是3,就表明时彩色图像。至于这里的图片数量,为什么取-1呢,我们来看看取-1和取1有什么区别?取-1的时候:![这里写图片描述](http://img.blog.csdn.net/20171111151453289?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)取1的时候则出现了错误:![这里写图片描述](http://img.blog.csdn.net/20171111151541552?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)取100的时候(因为我们知道batch_xs是从训练集中随机抽取100个图片出来训练):![这里写图片描述](http://img.blog.csdn.net/20171111151726207?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)那么,现在我们想知道取-1的时候和取100的时候的输出结果是否相等?经过本人用sum(a-b)验证,结果是相等的。所以现在我们就了解,当batch=-1的时候的意思就是自适应任何数量的图片输入,并且在官网的demo中,reshape将100个784的数据转化成了100个28×28的矩阵![这里写图片描述](http://img.blog.csdn.net/20171111152049117?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)还记得我们扯这么多,都是为了确认我们输入的x_image是否与input所要求的具有[图片数量,图片高度,图片宽度,图片通道数]这样的shape相吻合,我们输出x_image:![这里写图片描述](http://img.blog.csdn.net/20171111152554603?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)看到了吧,果然是具有这种形式的shape,那么input这个参数的输入我们就大致理解了~<div class="se-preview-section-delimiter"></div>###filter的作用这个地方的输入就是卷积核了,并且要求卷积核具有如[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数]这样的shape,源代码我们这里给的是:<div class="se-preview-section-delimiter"></div>

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

还记得我们W_conv1的定义:<div class="se-preview-section-delimiter"></div>

W_conv1 = weight_variable([5, 5, 1, 32])
“`
这里的[5,5,1,32]所对应的就是卷积核的高,宽,图像通道数和卷积核个数~。且不要忘记我们之前有输出过W_conv1是一个(5×5)的矩阵,其中每个元素是(1×13)的矩阵。

strides的作用

这是表明卷积在图像每一维的步长(但是在官方的源代码中没有定义这个参数)[[[待修改!!!其实是有的]]]

padding

这个只能是”SAME”或者“”

strides的作用

这是表明卷积在图像每一维的步长,原demo的写法:

def conv2d(x, W):  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

这个地方以后或许还会再详细进行补充

padding

这个只能是”SAME”或者“VALID”,一种填充方式而已。在本demo中的选择是”SAME”


接着将从conv2d中返回的东西+偏项b之后代入relu函数中:

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

输出的东西即为h_conv1…(讲实话我现在已经被各种变量搞晕了)

如何进行预测?

观察源代码,不难发现在正确率判断那里所使用的还是:

correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))

用y_conv和y_进行对比,那么我们是如何得到预测值y_conv的呢?

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

不难看出还是通过我们的softmax这个方法,还记得softmax是怎么用的吗?

x = tf.placeholder(tf.float32, [None, 784])W = tf.Variable(tf.zeros([784,10]))b = tf.Variable(tf.zeros([10]))y = tf.nn.softmax(tf.matmul(x,W) + b)

就是直接把代表图像的x,权重W和b进行输入,softmax即可输出预测,那么类比来看我们这里的h_fc1_drop的本质也是一张图像,只不过这一张图像经过cnn,max_pool和drop等一系列处理之后,可能特征变得更加明显吧~总之将经过处理之后得到的图像代入就可以获得更高的准确率(当然怎么样处理可以使得图像的识别度最高,也是需要tensorflow自己来搞定的)。

处理前和处理后的图像有什么区别?

emmm那么既然了解(4)的内容相比于(3)来说只是将图像重新进行处理之后再代入softmax()中,那么现在有一个我比较感兴趣的问题,即cnn等一系列操作处理之后得到的h_fc1_drop的图像跟它的原始图像的区别在哪里?

为解决这个问题,我试图将x_image和与之对应的h_fc1_drop进行输出。

补充:在对h_fc1_drop这个变量进行输出的的时候,出现了如下错误:
输入

print sess.run(h_conv1)

输出

InvalidArgumentError: You must feed a value for placeholder tensor 'Placeholder' with dtype float     [[Node: Placeholder = Placeholder[dtype=DT_FLOAT, shape=[], _device="/job:localhost/replica:0/task:0/cpu:0"]()]]Caused by op u'Placeholder'

大致的意思就是需要提供一些placeholder,故修改成:
“`
sess.run(h_fc1_drop,feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

即可。首先我们看原始图像长什么样子:![这里写图片描述](http://img.blog.csdn.net/20171114003042839?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)我看到的时候也想说···这是什么鬼,难道是9吗?然后查了这张图对应的label,结果是8。。。(目测时在录入的时候没有录完吧···哎)![这里写图片描述](http://img.blog.csdn.net/20171114003152839?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXE1cTEzNjM4/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

然后我们再看看经过cnn一堆处理之后,最终传给softmax函数的h_fc1_drop是什么样子:

forp2 = h_fc1_dropa = sess.run(h_fc1_drop,feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})b = tf.reshape(a[0], [-1,32,32,1])b = sess.run(b)[0]np.savetxt('new2.csv',b,delimiter=',')

然后打开new2.csv:
这里写图片描述

表示,根本找不到任何规律,谁能看出来这是什么数字啊。。。


总结

所以,可能cnn以及其配套的一系列操作,是把肉眼可以识别的数字,变成容易被计算机识别的数字吧~对于其内部的原理也是很感兴趣,该篇再说

阅读全文
0 0