30分钟手把手带你入门TensorFlow——Mnist手写数字识别实战教程

来源:福尔摩睿评论1,449

这一次将会带领大家实现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

 

发表评论

匿名网友