TensorFlow MNIST数字识别问题

 

MNIST数据处理

MNIST是一个非常有名的手写体数字识别问题。

MNIST数据集是NIST数据集的一个子集,包含60000张图片作为训练数据,10000张图片作为测试数据。每一张图片代表0-9中的一个数字,图片的大小为28×28,数字均在图片中间。TensorFlow提供了一个类来处理MNIST数据,这个类会自动下载并转化MNIST数据的格式,将数据从原始的数据包中解析成训练和测试神经网络时使用的格式。这个数据集只提供了训练和测试数据,但是为了验证模型效果,一般会从训练数据中划分出一部分数据作为验证(validation)数据,后面会详细介绍验证数据的作用。

使用样例:

from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/path/to/MNIST_data/", one_hot=True)

# 打印训练数据长度
print(mnist.train.num_examples)
# 打印验证数据的长度
print(mnist.validation.num_examples)
# 打印测试数据的长度
print(mnist.test.num_examples)

# 查看training数据集中某个成员的像素矩阵生成的一位数组
print(mnist.train.images[0])
# 查看training数据集中某个成员的像素矩阵属于的数字标签
print(mnist.train.labels[0])  # 第0个图片属于数字7
# [0. 0. 0. 0. 0. 0. 0. 7. 0. 0.]

通过input_data.read_data_sets函数生成的类会自动将MNIST数据集划分为train、validation、test三个数据集处理后的每一张图片是长度为784的一维数组。input_data.read_data_sets中为了方便使用梯度下降法儿提供的函数mnist.train.next_batch,它可以从所有的训练数据中读取一小部分作为一个训练batch。

神经网络模型训练及不同模型结果对比

下面是一个完整的TensorFlow程序来解决MNIST手写体数字识别问题。会用到之前的所有结构设计和优化方法。神经网络结构上,深度学习一方面使用激活函数实现神经网络模型去线性化,另一方面需要使用一个或多个隐藏层使得神经网络结构更深,以解决复杂问题,使用带指数衰减的学习率设置、使用正则化来避免过度拟合,以及滑动平均模型使得终极模型更加健壮。

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

# MNIST数据集相关常数
INPUT_NODE = 784  # 输入层的节点数。对于MNIST数据集,就是图片像素28×28
OUTPUT_NODE = 10  # 输出层节点数。这个等于类别的数目。0~9是10个数字,所以节点数为10

# 配置神经网络参数
LAYER1_NODE = 500  # 隐藏层节点数。本样例有一个隐藏层的网络,这个隐藏层有500个节点。

BATCH_SIZE = 100  # 一个batch训练的数据个数。数字越小训练过程越接近随机梯度下降;数字越大,训练越接近梯度下降

LEARNING_RATE_BASE = 0.8  # 基础学习率
LEARNING_RATE_DECAY = 0.99  # 学习率的衰减率
REGULARIZATION_RATE = 0.0001  # 描述模型复杂度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000  # 训练轮数
MOVING_AVERAGE_DECAY = 0.99  # 滑动平均衰减率


# 一个辅助函数,给定神经网络输入和所有参数,计算神经网络前向传播结果。
# 在这里定义了一个使用ReLU激活函数的三层全连接神经网络。
# 加入隐藏层实现多层网络结构,ReLU激活函数实现去线性化。
# 这个函数也支持传入用于计算参数平均值的类,方便测试时使用滑动平均模型
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 当没有提供滑动平均类时,直接使用参数当前的取值
    if avg_class is None:
        # 计算隐藏层的前向传播结果,使用ReLU激活函数
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)

        # 计算输出层的前向传播结果。
        # 在计算损失函数时会一并计算softmax函数,所以不需要加入激活函数。
        # 没有softmax不会影响预测结果。因为预测时使用的是不同类别对应节点输出值的大小。在计算整个神经网络的前向传播可以不加入最后的softmax值
        return tf.matmul(layer1, weights2) + biases2

    else:
        # 首先使用avg_class.average函数计算得出变量的滑动平局值
        # 然后计算相应神经网络前向传播结果
        layer1 = tf.nn.relu(tf.matmul(input_tensor, avg_class.average(weights1)) + avg_class.average(biases1))
        return tf.matmul(layer1, avg_class.average(weights2)) + avg_class.average(biases2)


# 训练模型的过程
def train(mnist):
    x = tf.placeholder(tf.float32, [None, INPUT_NODE], name='x-input')
    y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE], name='y-input')

    # 生成隐藏层的参数
    weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=0.1))  # 定义变量,784×500随机数矩阵,均值0.1
    biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
    # 生成输出层的参数
    weights2 = tf.Variable(tf.truncated_normal([LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))

    # 计算在当前参数下神经网络前向传播的结果。这里给出用于计算滑动平均的类为None,函数不会使用参数的滑动平均值
    y = inference(x, None, weights1, biases1, weights2, biases2)

    # 定义存储训练轮数的变量。这个变量不需要滑动平均值,所以定义为不可训练变量。
    # TensorFlow中训练轮数的变量一般是不可训练的
    global_step = tf.Variable(0, trainable=False)

    # 给定滑动平均衰减率和训练轮数的变量,初始化滑动平均类。
    # 给定训练轮数的变量可以加快训练早期变量的更新速度
    variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

    # 在所有代表神经网络参数的变量上使用滑动平均。
    # 辅助变量如global_step不需要滑动平均
    # tf.trainable_variables()可以获得所有没有指定trainable=False的参数
    variable_averages_op = variable_averages.apply(tf.trainable_variables())

    # 计算使用滑动平均之后的前向传播结果。
    # 滑动平均不会改变变量本身的取值,而是维护一个影子变量来记录其滑动平均值。需要这个滑动平均值时调用average函数
    average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

    # 计算交叉熵作为刻画预测值和真实值之间的损失函数。
    # 这里使用了sparse_softmax_cross_entropy_with_logits函数来计算交叉熵。当问题只有一个正确答案时(如本例数字只有0~9一个答案),可以用这个函数
    # 函数参数:第一个参数是神经网络不包括softmax层的前向传播结果,第二个训练数据是正确答案
    # 因为标准答案是长度为10的数组,函数需要提供一个正确答案的数字,所以用tf.argma函数来获得正确答案对应的编号
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=tf.argmax(y_, 1), logits=y)
    # 计算当前batch中样例交叉熵平均值
    cross_entropy_mean = tf.reduce_mean(cross_entropy)

    # 计算L2正则化损失函数
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 计算模型正则化损失。
    # 一般只使用神经网络边上权重的正则化损失,而不使用偏置项
    regularzation = regularizer(weights1) + regularizer(weights2)
    # 总损失等于交叉熵损失和正则化损失的和
    loss = cross_entropy_mean + regularzation
    # 设置指数衰减学习率
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATE_BASE,  # 基础学习率,随着迭代进行,逐渐更新学习率
        global_step,  # 当前迭代轮数
        mnist.train.num_examples / BATCH_SIZE,  # 过完所有训练数据需要的迭代次数
        LEARNING_RATE_DECAY)  # 学习率衰减速度

    # 使用tf.train.GradientDescentOptimizer优化算法来优化损失函数。这里的损失函数包含了交叉熵和L2正则化损失
    train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

    # 训练神经网络参数时,每过一遍数据需要反向传播来更新神经网络中的参数,又要更新每一个参数滑动平均值。
    # 为了一次完成多个操作,TensorFlow提供了tf.control_dependencies和tf.group两种机制。
    # 下面两行和train_op = tf.group(train_step, varables_averages_op)等价
    with tf.control_dependencies([train_step, variable_averages_op]):
        train_op = tf.no_op(name='train')

    # 计算使用了滑动平均模型的神经网络前向传播结果是否正确。
    # tf.argmax(average_y, 1)计算每一个样例的预测答案。其中average_y是一个batch_szie * 10的二维数组,每一行表示一个样例的前向传播结果
    # tf.argamx的第二个参数“1”表示选取最大值的操作仅在第一个维度中进行,也就是说每一行选取最大值对应的下标。
    # 于是得到的结果是一个长度为batch的一维数组,每个一维数组代表样例对应的数字识别结果
    # tf.equal判断两个张量每一维是否相等,相等返回true,否则返回false
    correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
    # 首先将一个布尔值转成实数型,然后计算平均值。平均值就一组数据上的正确率
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    # 初始化会话并开始训练
    with tf.Session() as sess:
        tf.initialize_all_variables().run()
        # 准备验证数据。一般在神经网络训练过程中会验证数据来大致判断停止条件和评判训练的效果
        validate_feed = {x: mnist.validation.images, y_: mnist.validation.labels}

        # 准备测试数据。在真实的应用中,这个数据在训练中不可见,作为模型优劣最后评价标准
        test_feed = {x: mnist.test.images, y_: mnist.test.labels}

        # 迭代训练神经网络
        for i in range(TRAINING_STEPS):
            if i % 1000 == 0:
                validate_acc = sess.run(accuracy, feed_dict=validate_feed)
                print("经过 %d 轮训练,平均正确率为 %g" % (i, validate_acc))

            # 产生这一轮使用的一个batch的训练数据,并运行训练过程
            xs, ys = mnist.train.next_batch(BATCH_SIZE)
            sess.run(train_op, feed_dict={x: xs, y_: ys})

        # 在训练结束之后,测试在测试集上的最终正确率
        test_acc = sess.run(accuracy, feed_dict=test_feed)
        print("经过 %d 轮训练,测试集上正确率为 %g" % (TRAINING_STEPS, test_acc))


# 主程序
def main(argv=None):
    # 声明处理mnist数据集的类
    mnist = input_data.read_data_sets("D:/PycharmProjects/Chapter4_DNN/to/MNIST_data", one_hot=True)
    train(mnist)

# TensorFlow提供的一个主程序入口,tf.app.run会调用上面定义的main函数
if __name__ == '__main__':
    tf.app.run()

运行结果:

经过 0 轮训练,平均正确率为 0.0802
经过 1000 轮训练,平均正确率为 0.9772
经过 2000 轮训练,平均正确率为 0.9816
经过 3000 轮训练,平均正确率为 0.9828
经过 4000 轮训练,平均正确率为 0.9828
经过 5000 轮训练,平均正确率为 0.9836
经过 6000 轮训练,平均正确率为 0.984
经过 7000 轮训练,平均正确率为 0.985
经过 8000 轮训练,平均正确率为 0.9852
经过 9000 轮训练,平均正确率为 0.985
经过 10000 轮训练,平均正确率为 0.985
经过 11000 轮训练,平均正确率为 0.9848
经过 12000 轮训练,平均正确率为 0.9852
经过 13000 轮训练,平均正确率为 0.9846
经过 14000 轮训练,平均正确率为 0.9846
经过 15000 轮训练,平均正确率为 0.985
经过 16000 轮训练,平均正确率为 0.9856
经过 17000 轮训练,平均正确率为 0.9854
经过 18000 轮训练,平均正确率为 0.9846
经过 19000 轮训练,平均正确率为 0.9846
经过 20000 轮训练,平均正确率为 0.985
经过 21000 轮训练,平均正确率为 0.9852
经过 22000 轮训练,平均正确率为 0.9854
经过 23000 轮训练,平均正确率为 0.985
经过 24000 轮训练,平均正确率为 0.9848
经过 25000 轮训练,平均正确率为 0.985
经过 26000 轮训练,平均正确率为 0.9848
经过 27000 轮训练,平均正确率为 0.9852
经过 28000 轮训练,平均正确率为 0.9858
经过 29000 轮训练,平均正确率为 0.9856
经过 30000 轮训练,测试集上正确率为 0.9845

不同模型效果比较

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值