基于卷积神经网络LeNet-5模型的mnist手写数字识别

基于卷积神经网络LeNet-5模型的mnist手写数字识别

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

import os
'''
tensorflow中os.environ["TF_CPP_MIN_LOG_LEVEL"]的值的含义
log信息共有四个等级,按重要性递增为:
INFO(通知)<WARNING(警告)<ERROR(错误)<FATAL(致命的);
2、值的含义:不同值设置的是基础log信息(base_loging),运行时会输出base等级及其之上(更为严重)的信息。具体如下:
        base_loging                屏蔽信息                    输出信息
“0”     INFO                        无                      INFO + WARNING + ERROR + FATAL
“1”     WARNING                     INFO                    WARNING + ERROR + FATAL
“2”     ERROR                   INFO + WARNING              ERROR + FATAL
“3”     FATAL               INFO + WARNING  + ERROR         FATA
'''
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"

# 自动创建一个'MNIST_data'的目录来存储数据,将下载的文件解压,图像转化为4D向量[index, y, x, depth],
# 分别存储在'train', 'validation', 'test'的Dataset中。将标签one-hot编码后,就可以跟对连续型特征的
# 归一化方法一样,对每一维特征进行归一化。(已存在则不需下载,直接加载)
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

LEARN_RATE = 0.3    # 学习率
LEARN_DAMP = 0.98   # 学习率的衰减率
BATCH_SIZE = 100    # 每个批次大小(一次100张,下一次接着取后面100张,。。。)
N_BATCH = mnist.train.num_examples // BATCH_SIZE    # 训练完所有mnist训练数据需要的批次

# 初始化
# 随机产生一个形状为shape的服从正态分布(标准差为0.1)的tensor,根据官方API的定义为,
# 如果单次随机生成的值偏离均值2倍标准差之外,就丢弃并重新随机生成一个新的数。
def init_weight(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1))

# 初始化偏置,初始化形状为shape的偏置向量,初始值都为0.1
def init_bias(shape):
    return tf.Variable(tf.constant(0.1, shape=shape))

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

# 池化
def max_pool(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])
keep_prob = tf.placeholder(tf.float32)

# 将输入转化为卷积输入需要的格式
x_image = tf.reshape(x,[-1, 28, 28, 1])

W_conv1 = init_weight([5, 5, 1, 32])    # 5*5的采样窗口,32个卷积核从1个平面抽取特征
b_conv1 = init_bias([32])               # 每个卷积核一个偏置值
W_conv2 = init_weight([5, 5, 32, 64])
b_conv2 = init_bias([64])
W_fc1 = init_weight([7*7*64, 512])
b_fc1 = init_bias([512])
W_fc2 = init_weight([512, 10])
b_fc2 = init_bias([10])

# LeNet-5模型
def LeNet5_model(X, w1, b1, w2, b2, w3, b3, w4, b4, prob_hidden):
    # 第一层,卷积层
    L1 = tf.nn.relu(conv2d(X, w1) + b1)
    # 第二层,池化层
    L2 = max_pool(L1)
    # 第三层,卷积层
    L3 = tf.nn.relu(conv2d(L2, w2) + b2)
    # 第四层,池化层
    L4 = max_pool(L3)
    # 第五层,全连接层
    # 将第四层池化层的输出转化为第五层全连接层的输入格式。第四层输出为7x7x64的矩阵,
    # 然而第五层全连接层需要的输入格式为向量,所以需要将7x7x64的矩阵拉直成一个向量。
    # w3.get_shape().as_list()[0]将池化层输出按照全连接层的输入格式转化。
    L5 = tf.reshape(L4, [-1, w3.get_shape().as_list()[0]])  # reshape to (?, 7x7x64)
    L5 = tf.nn.relu(tf.matmul(L5, w3) + b3)
    # 加入dropout(训练时随机将部分节点的输出改为0),避免过拟合问题。
    L5 = tf.nn.dropout(L5, prob_hidden)
    # 第六层,全连接层
    L6 = tf.nn.sigmoid(tf.matmul(L5, w4) + b4)
    L6 = tf.nn.dropout(L6, prob_hidden)
    # 第七层,输出层
    L7 = tf.nn.softmax(L6)

    return L7

# 调用LeNet-5模型后的输出
prediction = LeNet5_model(x_image, W_conv1, b_conv1, W_conv2, b_conv2, W_fc1, b_fc1, W_fc2, b_fc2, keep_prob)

# 设置对数似然损失函数
# 代价函数 J =-(Σy.logaL)/n    .表示逐元素乘
loss = tf.reduce_mean(-tf.reduce_sum(y * tf.log(prediction), axis=1))

# 先选用Adam优化器,加快收敛速度
train_step = tf.train.AdamOptimizer(0.0008).minimize(loss)

# 对比模型输出结果和标签结果,逐个元素判断是否相等。
# tf.argmax(vector, 1):返回的是vector中的最大值的索引号,如果vector是一个向量,那就返回一个值,
# 如果是一个矩阵,那就返回一个向量,这个向量的每一个维度都是相对应矩阵行的最大值元素的索引号。
correct_prediction = (tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1)))

# 计算正确率(根据correct_prediction中0,1数据判断:如[1, 0, 1, 1],说明有三个经模型输出结果
# 和原标签结果相同,一个不同(识别错误),因此正确率就是(1+0+1+1)/(1+1+1+1)= 75% )
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# tf.train.Saver()默认保存所有参数
saver = tf.train.Saver()

# 初始化模型所有参数
init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    # 训练50轮
    for epoch in range(30):
        # 前25轮采用Adam优化器,加快收敛速度;后25轮采用随机梯度下降优化器,修正正确率.
        if epoch >= 10:
            LEARN_RATE = LEARN_RATE * LEARN_DAMP
            train_step = tf.train.GradientDescentOptimizer(LEARN_RATE).minimize(loss)

        for batch in range(N_BATCH):
            batch_xs, batch_ys = mnist.train.next_batch(BATCH_SIZE) # 依次往后取训练集中的一个批次数据
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.8})

        # 验证集的数字识别正确率
        validation_acc = sess.run(accuracy, feed_dict={x: mnist.validation.images, y: mnist.validation.labels,
                                                       keep_prob: 1.0})
        # 测试集的数字识别正确率
        test_acc = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})

        print('Iter' + str(epoch) + ',Validation Accuracy' + str(validation_acc) + ',Testing Accuracy' + str(test_acc))
    #保存模型
    saver.save(sess, 'NET/my_mnist_LeNet-5.ckpt')

测试结果如下所示:正确率达到99.4%左右。
基于卷积神经网络LeNet-5模型的mnist手写数字识别
接下来调用保存的模型进行简单对比测试:

import time
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import os
'''
tensorflow中os.environ["TF_CPP_MIN_LOG_LEVEL"]的值的含义
log信息共有四个等级,按重要性递增为:
INFO(通知)<WARNING(警告)<ERROR(错误)<FATAL(致命的);
2、值的含义:不同值设置的是基础log信息(base_loging),运行时会输出base等级及其之上(更为严重)的信息。具体如下:
        base_loging                屏蔽信息                    输出信息
“0”     INFO                        无                      INFO + WARNING + ERROR + FATAL
“1”     WARNING                     INFO                    WARNING + ERROR + FATAL
“2”     ERROR                   INFO + WARNING              ERROR + FATAL
“3”     FATAL               INFO + WARNING  + ERROR         FATA
'''
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"

# 自动创建一个'MNIST_data'的目录来存储数据,将下载的文件解压,图像转化为4D向量[index, y, x, depth],
# 分别存储在'train', 'validation', 'test'的Dataset中。将标签one-hot编码后,就可以跟对连续型特征的
# 归一化方法一样,对每一维特征进行归一化。(已存在则不需下载,直接加载)
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

# 初始化
# 随机产生一个形状为shape的服从正态分布(标准差为0.1)的tensor,根据官方API的定义为,
# 如果单次随机生成的值偏离均值2倍标准差之外,就丢弃并重新随机生成一个新的数。
def init_weight(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1))

# 初始化偏置,初始化形状为shape的偏置向量,初始值都为0.1
def init_bias(shape):
    return tf.Variable(tf.constant(0.1, shape=shape))

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

# 池化
def max_pool(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])
rate = tf.placeholder(tf.float32)

# 将输入转化为卷积输入需要的格式
x_image = tf.reshape(x,[-1, 28, 28, 1])

W_conv1 = init_weight([5, 5, 1, 32])    # 5*5的采样窗口,32个卷积核从1个平面抽取特征
b_conv1 = init_bias([32])               # 每个卷积核一个偏置值
W_conv2 = init_weight([5, 5, 32, 64])
b_conv2 = init_bias([64])
W_fc1 = init_weight([7*7*64, 512])
b_fc1 = init_bias([512])
W_fc2 = init_weight([512, 10])
b_fc2 = init_bias([10])

# LeNet-5模型
def LeNet5_model(X, w1, b1, w2, b2, w3, b3, w4, b4, rate):
    # 第一层,卷积层
    L1 = tf.nn.relu(conv2d(X, w1) + b1)
    # 第二层,池化层
    L2 = max_pool(L1)
    # 第三层,卷积层
    L3 = tf.nn.relu(conv2d(L2, w2) + b2)
    # 第四层,池化层
    L4 = max_pool(L3)
    # 第五层,全连接层
    # 将第四层池化层的输出转化为第五层全连接层的输入格式。第四层输出为7x7x64的矩阵,
    # 然而第五层全连接层需要的输入格式为向量,所以需要将7x7x64的矩阵拉直成一个向量。
    # w3.get_shape().as_list()[0]将池化层输出按照全连接层的输入格式转化。
    L5 = tf.reshape(L4, [-1, w3.get_shape().as_list()[0]])  # reshape to (?, 7x7x64)
    L5 = tf.nn.relu(tf.matmul(L5, w3) + b3)
    # 加入dropout(训练时随机将部分节点的输出改为0),避免过拟合问题。
    L5 = tf.nn.dropout(L5, rate=rate)
    # 第六层,全连接层
    L6 = tf.nn.sigmoid(tf.matmul(L5, w4) + b4)
    L6 = tf.nn.dropout(L6, rate=rate)
    # 第七层,输出层
    L7 = tf.nn.softmax(L6)

    return L7

# 调用LeNet-5模型后的输出
prediction = LeNet5_model(x_image, W_conv1, b_conv1, W_conv2, b_conv2, W_fc1, b_fc1, W_fc2, b_fc2, rate)

# 设置对数似然损失函数
# 代价函数 J =-(Σy.logaL)/n    .表示逐元素乘
loss = tf.reduce_mean(-tf.reduce_sum(y * tf.log(prediction), axis=1))

# 先选用Adam优化器,加快收敛速度
train_step = tf.train.AdamOptimizer(0.0008).minimize(loss)

# 对比模型输出结果和标签结果,逐个元素判断是否相等。
# tf.argmax(vector, 1):返回的是vector中的最大值的索引号,如果vector是一个向量,那就返回一个值,
# 如果是一个矩阵,那就返回一个向量,这个向量的每一个维度都是相对应矩阵行的最大值元素的索引号。
correct_prediction = (tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1)))

# 计算正确率(根据correct_prediction中0,1数据判断:如[1, 0, 1, 1],说明有三个经模型输出结果
# 和原标签结果相同,一个不同(识别错误),因此正确率就是(1+0+1+1)/(1+1+1+1)= 75% )
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

saver = tf.train.Saver()

# 初始化模型所有参数
init = tf.global_variables_initializer()
# 随便选择若干数字图片,对比测试正确率
with tf.Session() as sess:
    sess.run(init)
    time1 = time.time()
    test_acc = sess.run(accuracy,feed_dict={x:mnist.test.images[3123:6789], y:mnist.test.labels[3123:6789], rate: 0.0})
    time2 = time.time()
    print("单次训练用时:" + str(time2-time1) + 's, 训练正确率为:' + str(test_acc))

    saver.restore(sess, 'NET/my_mnist_LeNet-5.ckpt')
    time3 = time.time()
    model_acc = sess.run(accuracy,feed_dict={x: mnist.test.images[3123:6789], y: mnist.test.labels[3123:6789], rate: 0.0})
    time4 = time.time()
    print("调用模型训练用时:" + str(time4-time3) + 's, 训练正确率为:' + str(model_acc))

对比测试结果如下:
基于卷积神经网络LeNet-5模型的mnist手写数字识别