这一次将会带领大家实现mnist的手写数字识别,手把手教会你如何使用TensorFlow进行基本的操作。本次课程中,将会事无巨细地深入讲解每一个操作细节,为每一位读者提供最贴心的服务。
准备工作
安装TensorFlow我就不多说了,主要是mnist数据集的获取一般都会有些问题。你可能会遇到input_data.py文件下载不下来,又或是下载input_data.py之后运行会报错,又或是使用tensorflow.examples.tutorials.mnist 里自带的input_data.py报错。
解决方法如下:
1、Input_data.py文件需要修改一下下才能正常使用,在37行的“return
numpy.frombuffer(bytestream.read(4), dtype=dt)”:末尾处添加“[0]”,变成“return
numpy.frombuffer(bytestream.read(4), dtype=dt)[0]”
2、由于网络问题或其他一些未知原因,input_data下载、解压mnist数据集时会报错,这时你只要自己手动下载好数据集(四个压缩包)放到工作空间的MNIST_data文件夹里就行,就能绕开报错问题。
流程讲解大致流程分为三步:
1、构建CNN网络结构;
2、构建loss function,配置寻优器;
3、训练、测试。
神经网络总体结构概览:
本教程中使用了两个卷积层+池化层,最后接上两个全连接层。
第一层卷积使用32个3x3x1的卷积核,步长为1,边界处理方式为“SAME”(卷积的输入和输出保持相同尺寸),激发函数为Relu,后接一个2x2的池化层,方式为最大化池化;
第二层卷积使用50个3x3x32的卷积核,步长为1,边界处理方式为“SAME”,激发函数为Relu, 后接一个2x2的池化层,方式为最大化池化;
第一层全连接层:使用1024个神经元,激发函数依然是Relu。
第二层全连接层:使用10个神经元,激发函数为softmax,用于输出结果。
代码概览: import input_data import tensorflow as tf #读取数据 mnist = input_data.read_data_sets('MNIST_data', one_hot=True) sess=tf.InteractiveSession() #构建cnn网络结构 #自定义卷积函数(后面卷积时就不用写太多) 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(tf.float32,[None,784]) y_=tf.placeholder(tf.float32,[None,10]) x_img=tf.reshape(x,[-1,28,28,1]) #设置第一个卷积层和池化层 w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1)) b_conv1=tf.Variable(tf.constant(0.1,shape=[32])) h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1) h_pool1=max_pool_2x2(h_conv1) #设置第二个卷积层和池化层 w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1)) b_conv2=tf.Variable(tf.constant(0.1,shape=[50])) h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2) h_pool2=max_pool_2x2(h_conv2) #设置第一个全连接层 w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1)) b_fc1=tf.Variable(tf.constant(0.1,shape=[1024])) h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50]) 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) #设置第二个全连接层 w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1)) b_fc2=tf.Variable(tf.constant(0.1,shape=[10])) y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2) #建立loss function,为交叉熵 loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1])) #配置Adam优化器,学习速率为1e-4 train_step=tf.train.AdamOptimizer(1e-4).minimize(loss) #建立正确率计算表达式 correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1)) accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32)) #开始喂数据,训练 tf.global_variables_initializer().run() 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}) print "step %d,train_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})
代码逐段解析:
1.
#自定义卷积函数(后面就不用写太多) 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')
卷积步长为1,如要改成步长为2则strides=[1,2,2,1],只有中间两个是有效的(对于二维图来说),使用‘SAME’的padding方法(即输出与输入保持相同尺寸,边界处少一两个像素则自动补上);池化层的设置也类似,池化尺寸为2X2。
2.
#设置占位符,尺寸为样本输入和输出的尺寸 x=tf.placeholder(tf.float32,[None,784]) y_=tf.placeholder(tf.float32,[None,10]) x_img=tf.reshape(x,[-1,28,28,1])
设置输入输出的占位符,占位符是向一个会话中喂数据的入口,因为TensorFlow的使用中,通过构建计算图来设计网络,而网络的运行计算则在会话中启动,这个过程我们无法直接介入,需要通过placeholder来对一个会话进行数据输入。
占位符设置好之后,将x变形成为28x28是矩阵形式(tf.reshape()函数)。
3.
#设置第一个卷积层和池化层 w_conv1=tf.Variable(tf.truncated_normal([3,3,1,32],stddev=0.1)) b_conv1=tf.Variable(tf.constant(0.1,shape=[32])) h_conv1=tf.nn.relu(conv2d(x_img,w_conv1)+b_conv1) h_pool1=max_pool_2x2(h_conv1) #设置第二个卷积层和池化层 w_conv2=tf.Variable(tf.truncated_normal([3,3,32,50],stddev=0.1)) b_conv2=tf.Variable(tf.constant(0.1,shape=[50])) h_conv2=tf.nn.relu(conv2d(h_pool1,w_conv2)+b_conv2) h_pool2=max_pool_2x2(h_conv2)
第一层卷积使用3x3x1的卷积核,一共有32 个卷积核,权值使用方差为0.1的截断正态分布(指最大值不超过方差两倍的分布)来初始化,偏置的初值设定为常值0.1。
第二层卷积和第一层类似,卷积核尺寸为3x3x32(32是通道数,因为上一层使用32个卷积核,所以这一层的通道数就变成了32),这一层一共使用50个卷积核,其他设置与上一层相同。
每一层卷积完之后接上一个2x2的最大化池化操作。
4.
#设置第一个全连接层 w_fc1=tf.Variable(tf.truncated_normal([7*7*50,1024],stddev=0.1)) b_fc1=tf.Variable(tf.constant(0.1,shape=[1024])) h_pool2_flat=tf.reshape(h_pool2,[-1,7*7*50]) 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) #设置第二个全连接层 w_fc2=tf.Variable(tf.truncated_normal([1024,10],stddev=0.1)) b_fc2=tf.Variable(tf.constant(0.1,shape=[10])) y_out=tf.nn.softmax(tf.matmul(h_fc1_drop,w_fc2)+b_fc2)
卷积层之后就是两个全连接层,第一个全连接层有1024个神经元,先将卷积层得到的2x2输出展开成一长条,使用Relu激活函数得到输出,输出为1024维。
Dropout:在这一层使用dropout(权值随机失活),对一些神经元突触连接进行强制的置零,这个trick可以防止神经网络过拟合。这里的dropout的保留比例是0.5,即随机地保留一半权值,删除另外一半(不要觉得可惜,为了保证在测试集上的效果,这是必须的)。Dropout比例通过placeholder来设置,因为训练过程中需要dropout,但是在最后的测试过程中,我们又希望使用全部的权值,所以dropout的比例要能够改变,所以这里使用placeholder。
第二个全连接层有10个神经元,分别对应0-9这10个数字(没错,这一层终于要得到最终的识别结果了),不过与之前的每一层不同的是,这里使用的激活函数是Softmax,关于softmax,我的个人理解是softmax是“以指数函数作为核函数的归一化操作”,softmax与一般归一化操作不同的是,指数函数能够放大一个分布内各个数值的差异,能够使各个数值的“贫富差距”变大,“两极分化”现象会更明显(对同一个分布进行一般的归一化得到的分布和softmax得到的分布,softmax得到的分布信息熵要更大)
5.
#建立loss function,为交叉熵 loss=tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_out),reduction_indices=[1])) #配置Adam优化器,学习速率为1e-4 train_step=tf.train.AdamOptimizer(1e-4).minimize(loss) #建立正确率计算表达式 correct_prediction=tf.equal(tf.argmax(y_out,1),tf.argmax(y_,1)) accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
建立loss function是很重要的一个过程,这里使用交叉熵来作为loss,交叉熵是用来衡量两个分布的相似程度的,两个分布越接近,则交叉熵越小。
使用Adam优化器来最小化loss,配置学习速率为1e-4。然后建立正确率的计算表达式(注意,仅仅是建立而已,现在还没有开始真正的计算),tf.argmax(y_,1),函数用来返回其中最大的值的下标,tf.equal()用来计算两个值是否相等。tf.cast()函数用来实现数据类型转换(这里是转换为float32),tf.reduce_mean()用来求平均(得到正确率)。
6.
#开始喂数据,训练 tf.global_variables_initializer().run() 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}) print "step %d,train_accuracy= %g"%(i,train_accuracy) train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5})#这里才开始真正进行训练计算
接下来就是喂数据了,对网络进行训练了,首先使用tf.global_variables_initializer().run()初始化所有数据,从mnist训练数据集中一次取50个样本作为一组进行训练,一共进行20000组训练,每100次就输出一次该组数据上的正确率。
进行训练计算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通过feed_dict来对会话输送训练数据(以及其他一些想在计算过程中实时调整的参数,比如dropout比例)。
这段代码中可以看到,训练时dropout的保留比例是0.5,测试时的保留比例是1。
7.
#训练之后,使用测试集进行测试,输出最终结果 Print "test_accuracy= %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1})
最后输入测试数据集进行测试验证,把代码跑起来看看结果吧。
接下来就是喂数据了,对网络进行训练了,首先使用tf.global_variables_initializer().run()初始化所有数据,从mnist训练数据集中一次取50个样本作为一组进行训练,一共进行20000组训练,每100次就输出一次该组数据上的正确率。
进行训练计算的方式是:train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}),通过feed_dict来对会话输送训练数据(以及其他一些想在计算过程中实时调整的参数,比如dropout比例)。
这段代码中可以看到,训练时dropout的保留比例是0.5,测试时的保留比例是1。
运行结果
最后的运行结果长这样(测试集上的准确率为99.21%,还不错):
Extracting MNIST_data/train-images-idx3-ubyte.gz Extracting MNIST_data/train-labels-idx1-ubyte.gz Extracting MNIST_data/t10k-images-idx3-ubyte.gz Extracting MNIST_data/t10k-labels-idx1-ubyte.gz step 0,train_accuracy= 0.1 step 100,train_accuracy= 0.76 step 200,train_accuracy= 0.9 step 300,train_accuracy= 0.84 step 400,train_accuracy= 0.98 …… …… …… step 19600,train_accuracy= 1 step 19700,train_accuracy= 0.98 step 19800,train_accuracy= 1 step 19900,train_accuracy= 1 test_accuracy= 0.9921