(Tensorflow框架)实现手写数字识别CNN
- 使用Tensorflow操作MNIST数据
MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。而TensorFlow的封装让使用MNIST数据集变得更加方便。MNIST数据集是NIST数据集的一个子集,它包含了60000张图片作为训练数据,10000张图片作为测试数据。在MNIST数据集中的每一张图片都代表了0~9中的一个数字。图片的大小都为28*28,且数字都会出现在图片的正中间,如下图所示:
在上图中右侧显示了一张数字1的图片,而右侧显示了这个图片所对应的像素矩阵,MNIST数据集提供了4个下载文件,在tensorflow中可将这四个文件直接下载放于一个目录中并加载,如下代码input_data.read_data_sets所示,如果指定目录中没有数据,那么tensorflow会自动去网络上进行下载。下面代码介绍了如何使用tensorflow操作MNIST数据集。
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets('/home/workspace/python/tf/data/mnist',one_hot=True)
# 打印“Training data size: 55000”
print "Training data size: ",mnist.train.num_examples
# 打印“Validating data size: 5000”
print "Validating data size: ",mnist.validation.num_examples
# 打印“Testing data size: 10000”
print "Testing data size: ",mnist.test.num_examples
# 打印“Example training data: [0. 0. 0. ... 0.380 0.376 ... 0.]”
print "Example training data: ",mnist.train.images[0]
# 打印“Example training data label: [0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]”
print "Example training data label: ",mnist.train.labels[0]
batch_size = 100
# 从train的集合中选取batch_size个训练数据
xs, ys = mnist.train.next_batch(batch_size)
# 输出“X shape:(100,784)”
print "X shape: ", xs.shape
# 输出"Y shape:(100,10)"
print "Y shape: ", ys.shape
从上面的代码中可以看出,通过input_data.read_data_sets函数生成的类会自动将MNIST数据集划分为train, validation和test三个数据集,其中train这个集合内含有55000张图片,validation集合内含有5000张图片,这两个集合组成了MNIST本身提供的训练数据集。test集合内有10000张图片,这些图片都来自与MNIST提供的测试数据集。处理后的每一张图片是一个长度为784的一维数组,这个数组中的元素对应了图片像素矩阵中的每一个数字(28*28=784)。因为神经网络的输入是一个特征向量,所以在此把一张二维图像的像素矩阵放到一个一维数组中可以方便tensorflow将图片的像素矩阵提供给神经网络的输入层。像素矩阵中元素的取值范围为[0, 1],它代表了颜色的深浅。其中0表示白色背景,1表示黑色前景。为了方便使用随机梯度下降,input_data.read_data_sets函数生成的类还提供了mnist.train.next_batch函数,它可以从所有的训练数据中读取一小部分作为一个训练batch。
- CNN完整代码如下
import os
print(os.getcwd()) #显示当前路径
os.chdir("E:/Tensorflow/HandwrittenDigits/") #在引号内填入你想放代码和数据的路径
##1.首先载入MNIST数据集,并创建默认的Sessio
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/",one_hot = True) #读取数据
sess = tf.InteractiveSession()
##2. 定义常用函数,对于权重要制造一些随机的噪声来打破完全对称,比如截断的正态分布噪声,标准差设为0.1
## 对偏置也增加一些小的正值(0.1)来避免死亡节点(dead neurons)
## 对于conv2d函数,W是卷积核,shape=[5,5,1,32],表示卷积核大小为5x5,channel为1,共有32个
## strides代表卷积模板移动的步长,因为都是二维平面移动,所以一般第一维和第四维固定为1,中间两维表示平面两个方向的步长[1,1,1,1]
## padding表示边界的处理方式,这里SAME代表给边界加上padding让卷积的输出和输入保持同样的尺寸
## pooling中的ksize表示窗口的大小,通常第一,第四维固定为1,中间两维表示平面窗口的大小
## 权值初始化设置
def weight_variable(shape):
initial = tf.truncated_normal(shape,stddev=0.1) #参数stddev用于设置正太分布被截断前的标准差,设置了stddev=0.1 后,训练精度就达到达到99.2%以上
return tf.Variable(initial)
# 偏置bias初始化设置
def bias_variable(shape):
initial = tf.constant(0.1,shape = shape)
return tf.Variable(initial)
def conv2d(x,W): #2维卷积操作
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')
# 卷积strides=[首位默认为1,平行步长=1,竖直步长=1,尾位默认为1]
def max_pool_2x2(x): #池化
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')
# ksize=[1,2,2,1] 池化核size: 2×2
# 池化strides=[首位默认为1,平行步长=2,竖直步长=2,尾位默认为1]
##3. 定义网络结构
## 注意输入是要reshape成平面结构用于卷积网络
## 所以若输入为[None,784],要将其转换成28x28的形状用作卷积 -1表示通过原尺寸自动计算该维的值,比如说 x = [4,7] x‘ = tf.reshape(x,[-1,14]) 那么 x‘ = [2,14]
# placeholder:等待输入数据,x为占位符,接受型号为float32的数据,输入格式为矩阵[None,784]
x = tf.placeholder(tf.float32,[None,784]) #784 = 28×28 只对输入矩阵的列数有要求
y_ = tf.placeholder(tf.float32,[None,10])
x_image = tf.reshape(x,[-1,28,28,1]) # [batch=-1, height=28, width=28, in_channels=1]
#在这里是把一维的784的x,转化为4个维度的向量,
# Conv1 Layer 卷积层1
W_conv1 = weight_variable([5,5,1,32]) # [5×5卷积核, in_channels=1, out_channels=32=卷积核个数]
b_conv1 = bias_variable([32]) # [32=卷积核个数=bias个数]
##把x_image和权值向量进行卷积,再加上偏置值,然后应用于relu激活函数,ReLU实现稀疏后的模型能够更好地挖掘相关特征,拟合训练数据
h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1) # 激活函数:relu
h_pool1 = max_pool_2x2(h_conv1) # 池化方式:max pool 取最大值
# Conv2 Layer 卷积层2
W_conv2 = weight_variable([5,5,32,64]) #上一层使用32个过滤器,所以第三个参数是32,本层是64个过滤器
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1,W_conv2) + b_conv2) # 激活函数:relu
h_pool2 = max_pool_2x2(h_conv2)
## 全连接层,经过上面2层以后,图片大小变成了7*7
# 初始化权重,全连接层我们使用1024个神经元
W_fc1 = weight_variable([7*7*64,1024])
b_fc1 = bias_variable([1024])
# 铺平图像数据。因为这里不再是卷积计算了,需要把矩阵冲洗reshape成一个一维的向量
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) #tf.matmul两矩阵相乘
# keep_prob表示保留不关闭的神经元的比例。为了避免过拟合,这里在全连接层使用dropout方法,
# 就是随机地关闭掉一些神经元使模型不要与原始数据拟合得辣么准确。
keep_prob = tf.placeholder(tf.float32)
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)
#反向传播,损失函数和计算准确率
# 损失函数loss function: 交叉熵cross_entropy
# # 1.计算交叉熵损失
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv),reduction_indices=[1]))
# # 2.创建优化器(注意这里用 AdamOptimizer代替了梯度下降法)
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) # 优化方法:AdamOptimizer| 学习速率:(1e-4)| 交叉熵:最小化
# # 3. 计算准确率
## 首先我们我们的label是一个有10个数字的数组。 要么是0要么是1,而且只有一个是1.
##因为我们是多分类场景么。 这个在上一节的数据介绍里讲过。 tf.argmax的意思就是在这些label中
##找数值最大的那个的下标。然后用tf.equal比较预测的和真实的是不是相等的。
##这样就能比较出本次预测是否正确。 然后再使用tf.reduce_mean求平均值。
##这样我们就得出了正确率
correct_prediction = tf.equal(tf.argmax(y_conv,1),tf.argmax(y_,1)) # tf.equal返回布尔值 | tf.argmax(y_,1):数字1代表最大值
accuracy = tf.reduce_mean(tf.cast(correct_prediction,tf.float32))
with tf.Session() as sess:
# # 4.初始化所有变量
sess.run(tf.global_variables_initializer())
#tf.global_variables_initializer().run()
for i in range(20000):
batch = mnist.train.next_batch(50) # 喂入训练集的数据
if i % 100 == 0: # 批量梯度下降,把1000改成1:随机梯度下降
train_accuracy = accuracy.eval(feed_dict={x:batch[0],y_:batch[1],keep_prob:1.0}) # train accuracy: accuracy.eval
print("step %d, training accuracy %g"%(i,train_accuracy))
# # 执行训练模型
train_step.run(feed_dict={x:batch[0],y_:batch[1],keep_prob:0.5}) # test accuracy: accuracy.eval
print("test accuracy %g"%accuracy.eval(feed_dict={x:mnist.test.images,y_:mnist.test.labels,keep_prob:1.0}))