0%

深度学习系列第三篇 — MNIST数字识别

这一节将上一节学到的深度神经网络的概念运用起来,通过 tf 来实现 MNIST 手写字识别。
首先导入 tf 库和训练数据:

1
2
3
4
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

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

定义全局初始常量,其中 INPUT_NODE 数为每一张图片 28 * 28 的像素数,OUTPUT_NODE 就是分类的个数 10; LAYER1_NODE 为隐藏层节点数,BATCH_SIZE 为每次训练数据的个数;LEARNING_RATE_BASE 为基础学习率,LEARNING_RATE_DECAY 为学习率的衰减率,REGULARIZATION_RATE 为正则化损失函数的系数,TRAINING_STEPS 为训练的次数,MOVING_AVERAGE_DECAY 为滑动平均衰减率。

1
2
3
4
5
6
7
8
9
10
11
INPUT_NODE = 784
OUTPUT_NODE = 10

LAYER1_NODE = 500
BATCH_SIZE = 100

LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99

定义一个 inference 函数用来计算神经网络的向前传播结果,并且通过 RELU 函数实现了去线性化。avg_class 参数是用来支持测试时使用滑动平均模型,当我们使用了滑动平均模型时,weights 和 biases 值都是从 avg_class 中取出的。

1
2
3
4
5
6
7
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
if avg_class is None:
layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
return tf.matmul(layer1, weights2) + biases2
else:
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)

定义输入层,生成隐藏层和输出层参数

1
2
3
4
5
6
7
8
x = tf.placeholder(tf.float32, [None, INPUT_NODE])
y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE])

weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=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]))

计算当前参数下神经网络向前传播的效果。

1
y = inference(x, None, weights1, biases1, weights2, biases2)

这里通过滑动平均衰减率和训练次数初始化这个类,用来加快训练早期变量的更新速度;global_step 为动态存储训练次数。

1
2
global_step = tf.Variable(0, trainable=False)
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

variables_averages_op 这里将所有的神经网络的上参数使用滑动平均,对于指定 trainable=False 的参数不作用。计算使用了滑动平均模型处理的向前传播结果。

1
2
variables_averages_op = variable_averages.apply(tf.trainable_variables())
average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

计算损失。交叉熵用来刻画预测值与真实值差距的损失函数,我们再通过 softmax 回归将结果变成概率分布。tf 提供了将这两个函数合并使用的函数,第一个参数是向前传播的结果,第二个参数是训练数据的答案。然后计算所有样例的交叉熵平均值。

1
2
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))
cross_entropy_mean = tf.reduce_mean(cross_entropy)

这里使用 L2 正则化损失函数,计算模型的正则化损失,计算权重的,不计算偏置。正则化损失函数用来避免过拟合。

1
2
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
regularization = regularizer(weights1) + regularizer(weights2)

最后得出的总损失等于交叉熵损失和正则化损失之和。

1
loss = cross_entropy_mean + regularization

设置指数衰减的学习率。

1
2
learnging_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE, LEARNING_RATE_DECAY)

使用优化算法优化总损失。

1
train_step = tf.train.GradientDescentOptimizer(learnging_rate).minimize(loss, global_step=global_step)

每过一次数据需要更新一下参数。

1
2
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op()

检验使用了滑动平均模型的向前传播结果是否正确。

1
correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))

计算平均准确率。

1
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

最后开始我们的训练,并验证数据的准确率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
with tf.Session() as sess:
# 初始化全部变量
tf.global_variables_initializer().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 "训练轮数:", i, ",准确率:", validate_acc * 100, "%"
# 取出一部分训练数据
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 "训练轮数:", TRAINING_STEPS, ",准确率:", test_acc * 100, "%"

开始训练的过程,首先初始化所有变量。

1
tf.global_variables_initializer().run()

MNIST 数据分为训练数据、验证数据和测试数据。我们先准备好验证数据和测试数据,因为数据量不大,可以直接将全部数据用于训练。然后开始我们的迭代训练,训练数据有很多,我们每次训练只取一部分数据进行训练,这样减小计算量,加速神经网络的训练,又不会对结果产生太大影响。

tf 的训练通过 sess.run 函数,第一个参数是最终要计算的,也就是公式的输出,第二个参数 feed 是 placeholder 的输入。

1
sess.run(train_op, feed_dict={x: xs, y_: ys})

通过一次次的训练,总损失会越来越小,模型的预测越来越准确,到达一个临界点。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

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

# MNIST数据集相关常数,其中输入节点数为每一张图片 28 * 28 的像素数,输出的节点数就是分类的个数 10; LAYER1_NODE 为隐藏层节点数,
# BATCH_SIZE 为每次训练数据的个数;LEARNING_RATE_BASE 为基础学习率,LEARNING_RATE_DECAY 为学习率的衰减率,
# REGULARIZATION_RATE 为正则化损失函数的系数,TRAINING_STEPS 为训练的次数,MOVING_AVERAGE_DECAY 为滑动平均衰减率

INPUT_NODE = 784
OUTPUT_NODE = 10

LAYER1_NODE = 500
BATCH_SIZE = 100

LEARNING_RATE_BASE = 0.8
LEARNING_RATE_DECAY = 0.99
REGULARIZATION_RATE = 0.0001
TRAINING_STEPS = 30000
MOVING_AVERAGE_DECAY = 0.99

# 这个函数用来计算神经网络的向前传播结果,并且通过 RELU 函数实现了去线性化。avg_class 参数是用来支持测试时使用滑动平均模型。
def inference(input_tensor, avg_class, weights1, biases1, weights2, biases2):
if avg_class is None:
layer1 = tf.nn.relu(tf.matmul(input_tensor, weights1) + biases1)
return tf.matmul(layer1, weights2) + biases2
else:
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)

# 输入层
x = tf.placeholder(tf.float32, [None, INPUT_NODE])
y_ = tf.placeholder(tf.float32, [None, OUTPUT_NODE])

# 生成隐藏层参数
weights1 = tf.Variable(tf.truncated_normal([INPUT_NODE, LAYER1_NODE], stddev=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]))

# 计算当前参数下神经网络向前传播的效果。
y = inference(x, None, weights1, biases1, weights2, biases2)

# 这个变量用来存储当前训练的次数。
global_step = tf.Variable(0, trainable=False)

# 这里通过滑动平均衰减率和训练次数初始化这个类,用来加快训练早期变量的更新速度。
variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)

# 这里将所有的神经网络的上参数使用滑动平均,对于指定 trainable=False 的参数不作用。
variables_averages_op = variable_averages.apply(tf.trainable_variables())

# 计算使用了滑动平均模型处理的向前传播结果。
average_y = inference(x, variable_averages, weights1, biases1, weights2, biases2)

# 计算交叉熵,用来刻画预测值与真实值差距的损失函数,第一个参数是向前传播的结果,第二个是训练数据的答案。
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))

# 计算所有样例的交叉熵平均值。
cross_entropy_mean = tf.reduce_mean(cross_entropy)

# 计算 L2 正则化损失函数
regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)
# 计算模型的正则化损失,计算权重的,不计算偏置。
regularization = regularizer(weights1) + regularizer(weights2)
# 总损失等于交叉熵损失和正则化损失之和。
loss = cross_entropy_mean + regularization
# 设置指数衰减的学习率。
learnging_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE, global_step, mnist.train.num_examples / BATCH_SIZE, LEARNING_RATE_DECAY)
# 使用优化算法优化总损失。
train_step = tf.train.GradientDescentOptimizer(learnging_rate).minimize(loss, global_step=global_step)

# 每过一次数据需要更新一下参数。
with tf.control_dependencies([train_step, variables_averages_op]):
train_op = tf.no_op()

# 检验使用了滑动平均模型的向前传播结果是否正确。
correct_prediction = tf.equal(tf.argmax(average_y, 1), tf.argmax(y_, 1))
# 计算平均准确率。
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

if __name__ == "__main__":
with tf.Session() as sess:
# 初始化全部变量
tf.global_variables_initializer().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 "训练轮数:", i, ",准确率:", validate_acc * 100, "%"
# 取出一部分训练数据
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 "训练轮数:", TRAINING_STEPS, ",准确率:", test_acc * 100, "%"
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
训练轮数: 0 ,准确率: 9.20000001788 %
训练轮数: 1000 ,准确率: 97.619998455 %
训练轮数: 2000 ,准确率: 98.0799973011 %
训练轮数: 3000 ,准确率: 98.2599973679 %
训练轮数: 4000 ,准确率: 98.1999993324 %
训练轮数: 5000 ,准确率: 98.1800019741 %
训练轮数: 6000 ,准确率: 98.2400000095 %
训练轮数: 7000 ,准确率: 98.2200026512 %
训练轮数: 8000 ,准确率: 98.1999993324 %
训练轮数: 9000 ,准确率: 98.2599973679 %
训练轮数: 10000 ,准确率: 98.2400000095 %
训练轮数: 11000 ,准确率: 98.2400000095 %
训练轮数: 12000 ,准确率: 98.1599986553 %
训练轮数: 13000 ,准确率: 98.2599973679 %
训练轮数: 14000 ,准确率: 98.299998045 %
训练轮数: 15000 ,准确率: 98.4200000763 %
训练轮数: 16000 ,准确率: 98.2800006866 %
训练轮数: 17000 ,准确率: 98.3799993992 %
训练轮数: 18000 ,准确率: 98.3600020409 %
训练轮数: 19000 ,准确率: 98.3200013638 %
训练轮数: 20000 ,准确率: 98.3399987221 %
训练轮数: 21000 ,准确率: 98.3799993992 %
训练轮数: 22000 ,准确率: 98.400002718 %
训练轮数: 23000 ,准确率: 98.400002718 %
训练轮数: 24000 ,准确率: 98.4200000763 %
训练轮数: 25000 ,准确率: 98.3200013638 %
训练轮数: 26000 ,准确率: 98.4200000763 %
训练轮数: 27000 ,准确率: 98.3799993992 %
训练轮数: 28000 ,准确率: 98.400002718 %
训练轮数: 29000 ,准确率: 98.3200013638 %
训练轮数: 30000 ,准确率: 98.3900010586 %

下一节总结 准确率、交叉熵平均值、总损失、学习率、平均绝对梯度 的变化趋势。

欢迎关注我的其它发布渠道