【TensorFlow实例】MNIST数字识别——2. 神经网络模型训练

MNIST 手写体数字识别完整 TensorFlow 程序

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

# 1.设置输入和输出节点的个数,配置神经网络的参数
INPUT_NODE = 784    # 输入层的节点数。对于 MNIST 数据集,这个就等于图片的像素
OUTPUT_NODE = 10    # 输出层的节点数。等于类别的数目。

# 配置神经网络的参数
LAYER1_NODE = 500   # 隐藏层节点数。这里仅使用一个隐藏层,500 个节点。
BATCH_SIZE = 100    # 一个训练 batch 中的训练数据个数。
                    # 数字越小时,训练过程越接近随机梯度下降;数字越大时,训练越接近梯度下降。
  
# 配置模型相关参数
LEARNING_RATW_BASE = 0.8        # 基础的学习率
LEARNING_RATE_DECAY = 0.99      # 学习率的衰减率
REGULARIZATION_RATE = 0.0001    # 描述模型负责度的正则化项在损失函数中的系数
TRAINING_STEPS = 30000          # 训练轮数
MOVING_AVERAGE_DECAY = 0.99     # 滑动平均衰减率

# 2. 定义辅助函数来计算前向传播结果,使用ReLU做为激活函数。

# 给定神经网络的输入和所有参数,计算神经网络的前向传播结果。
# 这里定义了一个使用 ReLU 激活函数的三层全连接神经网络。
# 通过加入隐藏层实现了多层网络结构,通过 ReLU 激活函数实现了去线性化。
# 这个函数也支持传入用于计算参数平均值的类,这样方便在测试时使用滑动平均模型。

def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
    # 当没有提供滑动平均类时,直接使用参数当前的取值
    if avg_class == None:
        # 计算隐藏层的前向传播结果,这里使用了 ReLU 激活函数
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)

        # 计算输出层的前向传播结果。
        # 因为在计算损失函数时会一并计算 softmax 函数,所以这里不需要加入激活函数。
        # 而且不加入 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)

# 3. 定义训练模型的过程
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')

    # 3.1 生成隐藏层的参数。
    weights1 = tf.Variable(tf.truncated_normal(
        [INPUT_NODE, LAYER1_NODE], stddev=0.1))
    biases1 = tf.Variable(tf.constant(0.1, shape=[LAYER1_NODE]))
    
    # 3.2 生成输出层的参数。
    weights2 = tf.Variable(tf.truncated_normal(
        [LAYER1_NODE, OUTPUT_NODE], stddev=0.1))
    biases2 = tf.Variable(tf.constant(0.1, shape=[OUTPUT_NODE]))

    # 3.3 计算不含滑动平均类的前向传播结果
    y = inference(x, None, weights1, biases1, weights2, biases2)

    # 3.4 定义训练轮数及相关的滑动平均类
    
    # 定义存储训练轮数。
    # 这个变量不需要计算滑动平均值,所以这里指定这个变量为不可训练的变量(trainable=False)。
    # 在使用 TensorFlow 训练神经网络时,一般会将代表训练轮数的变量指定为不可训练的参数。
    global_step = tf.Variable(0, trainable=False)

    # 定义滑动平均衰减率和训练轮数的变量,初始化滑动平均类。
    variable_averages = tf.train.ExponentialMovingAverage(
        MOVING_AVERAGE_DECAY, global_step)

    # tf.trainable_variables 返回的就是图上集合 GraphKeys.TRAINABLE_VARIABLES
    # 中的元素。这个集合的元素就是所有没有指定 trainable=False 的参数。
    variables_averages_op = variable_averages.apply(tf.trainable_variables())

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

    
    # 3.5 计算交叉熵及平均值
    
    # 计算交叉熵作为刻画预测值和真实值之间差距的损失函数。
    cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
        logits=y, labels=tf.argmax(y_, 1))
    # 计算在当前 batch 中所有样例的交叉熵平均值。
    cross_entropy_mean = tf.reduce_mean(cross_entropy)

    # 3.6 损失函数的计算
    
    # 计算 L2 正则化损失函数。
    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
    # 计算模型的正则化损失。
    regularization = regularizer(weights1) + regularizer(weights2)
    
    # 总损失等于交叉熵损失和正则化损失的和。
    loss = cross_entropy_mean + regularization
    
    # 3.7 设置指数衰减的学习率。
    learning_rate = tf.train.exponential_decay(
        LEARNING_RATW_BASE,                     # 基础的学习率,随着迭代的进行,更新变量时使用
                                                # 的学习率在这个基础上递减。
        global_step,                            # 当前迭代的轮数
        mnist.train.num_examples / BATCH_SIZE,   # 过完所有的训练数据需要的迭代次数。
        LEARNING_RATE_DECAY                     # 学习率衰减速度
    )


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

    # 3.9 反向传播更新参数和更新每一个参数的滑动平均值
    # 下面两行程序和 train_op = tf.group(train_step, variables_averages_op) 是等价的。
    with tf.control_dependencies([train_step, variables_averages_op]):
        train_op = tf.no_op(name='train')

    # 3.10 计算正确率
    
    # 检验使用了滑动平均模型的神经网络前向传播结果是否正确。tf.argmax(average_y, 1)
    # 计算每一个样例的预测答案。其中 average_y 是一个 batch_size * 10 的二维数组,每一行
    # 表示一个样例的前向传播结果。tf.argmax 的第二个参数 “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))

    # 3.11 初始化会话并开始训练过程。
    with tf.Session() as sess:
        #tf.global_variables_initializer().run
        sess.run(tf.global_variables_initializer())
        # 准备验证数据。一般在神经网络的训练过程中会通过验证数据来大致判断停止的条件
        # 和评判训练的效果。
        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):
            # 每 1000 轮输出一次在验证数据集上的测试结果。
            if i % 1000 == 0:
                # 计算滑动平均模型在验证数据上的结果。因为 MNIST 数据集比较小,所以一
                # 次可以处理所有的验证数据。为了计算方便,本样例程序没有将验证数据划分
                # 为更小的 batch。当神经网络模型比较复杂或者验证数据比较大时,太大的
                # batch 会导致计算时间过长甚至发生内存溢出的错误。
                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("./data",one_hot=True)
    train(mnist)

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

运行数据封装程序示例

使用 Google 云端硬盘中 colaboratory 跑程序。
运行结果:

Extracting ./data/train-images-idx3-ubyte.gz
Extracting ./data/train-labels-idx1-ubyte.gz
Extracting ./data/t10k-images-idx3-ubyte.gz
Extracting ./data/t10k-labels-idx1-ubyte.gz
0 次训练后,使用模型进行验证的准确度为 0.0954
1000 次训练后,使用模型进行验证的准确度为 0.9784
2000 次训练后,使用模型进行验证的准确度为 0.9812
3000 次训练后,使用模型进行验证的准确度为 0.983
4000 次训练后,使用模型进行验证的准确度为 0.9844
5000 次训练后,使用模型进行验证的准确度为 0.9862
6000 次训练后,使用模型进行验证的准确度为 0.9852
7000 次训练后,使用模型进行验证的准确度为 0.986
8000 次训练后,使用模型进行验证的准确度为 0.9856
9000 次训练后,使用模型进行验证的准确度为 0.9868
10000 次训练后,使用模型进行验证的准确度为 0.9862
11000 次训练后,使用模型进行验证的准确度为 0.987
12000 次训练后,使用模型进行验证的准确度为 0.9868
13000 次训练后,使用模型进行验证的准确度为 0.987
14000 次训练后,使用模型进行验证的准确度为 0.9872
15000 次训练后,使用模型进行验证的准确度为 0.9872
16000 次训练后,使用模型进行验证的准确度为 0.9868
17000 次训练后,使用模型进行验证的准确度为 0.986
18000 次训练后,使用模型进行验证的准确度为 0.9864
19000 次训练后,使用模型进行验证的准确度为 0.986
20000 次训练后,使用模型进行验证的准确度为 0.9866
21000 次训练后,使用模型进行验证的准确度为 0.9866
22000 次训练后,使用模型进行验证的准确度为 0.9866
23000 次训练后,使用模型进行验证的准确度为 0.9864
24000 次训练后,使用模型进行验证的准确度为 0.9866
25000 次训练后,使用模型进行验证的准确度为 0.9868
26000 次训练后,使用模型进行验证的准确度为 0.9868
27000 次训练后,使用模型进行验证的准确度为 0.9868
28000 次训练后,使用模型进行验证的准确度为 0.9876
29000 次训练后,使用模型进行验证的准确度为 0.9864
30000 次训练后,使用模型进行测试的准确度为 0.9842

验证数据集的作用

上面给出了使用神经网络解决 MNIST 问题的完整程序。
这个程序的开始设置了初始学习率学习率衰减率隐藏层节点数量迭代轮数等 7 种不同的参数。

为什么不能使用模型在测试数据上的效果来选择参数?

  • 配置神经网络的参数需要通过实验来调整。
  • 一个神经网络模型的效果最终是通过测试数据来评判,但不能直接通过模型在测试数据上的效果来选择参数,否则会导致神经网络模型过度拟合测试数据从而失去对未知数据的预判能力。
  • 评判神经网络模型在不同参数下的效果,一般会从训练数据中抽取一部分作为验证数据。

以下代码打印了每 1000 轮同时得到同一个模型在验证数据和测试数据上的正确率:

validate_acc = sess.run(accuracy, feed_dict=validate_feed)
test_acc = sess.run(accuracy, feed_dict=test_feed)
print("%d 次训练后,使用滑动平均模型进行验证的准确度为 %g,使用滑动平均模型进行测试的准确度为 %g" % (i, validate_acc, test_acc))

通过上面代码得到每 1000 轮滑动平均模型在不同数据集上的正确率曲线。其中灰色的曲线表示随着迭代轮数增加,模型在验证数据上的正确率,而黑色的曲线表示了再测试数据上的正确率。
在这里插入图片描述
由图上可以看出,虽然这两条曲线不会完全重合,但两条曲线的趋势基本一样,而且他们的相关系数(correlation coefficient)大于 0.9999。因此可以通过模型在验证数据上的表现来判断一个模型的优劣。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值