TensorFlow 手写数字图片识别

1. 手写数字数据集

1.1. 下载数据集

下载地址:http://yann.lecun.com/exdb/mnist/

下载这4个数据集,放到工程的 ./data/mnist/input_data/ 目录下,后面程序中要读取这些数据

1
2
3
4
train-images-idx3-ubyte.gz: 训练集图片
train-labels-idx1-ubyte.gz: 训练集标签
t10k-images-idx3-ubyte.gz: 测试集图片
t10k-labels-idx1-ubyte.gz: 测试集标签

TensorFlow也提供了相应下载并读取MNIST数据集的API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from tensorflow.examples.tutorials.mnist import input_data

"""
input_data.read_data_sets
第1个参数train_dir: 数据集所在目录
参数one_hot: 是否进行one_hot编码

input_data.read_data_sets首先看会查看train_dir目录下是否存在这4个数据集:
TRAIN_IMAGES = 'train-images-idx3-ubyte.gz'
TRAIN_LABELS = 'train-labels-idx1-ubyte.gz'
TEST_IMAGES = 't10k-images-idx3-ubyte.gz'
TEST_LABELS = 't10k-labels-idx1-ubyte.gz'
如果存在,则直接从本地读取;如果不存在,则会联网去下载。
由于网络原因,最好手动先去下载,放到train_dir目录下
"""
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)

1.2. 查看数据集

文件是二进制格式的,具体数据格式在下载的地方有说明。TensorFlow已经提供了读取数据的API,直接用即可

1
2
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)

mnist.train.images就是训练集图片。有55000张图片,即55000个样本。每张图片都是灰度图,共28x28=784个像素,每个像素点的灰度值是浮点数,范围在[0,1]之间,0代表黑色,1代表白色,介于0和1之间就是灰色。你可以将[0,1]*255==>[0,255],放大到256进制,就可以理解了。数据中,784个像素的数据都放在一行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> mnist.train.images.shape
(55000, 784)

>>> mnist.train.images
array([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]], dtype=float32)

>>> mnist.train.images[0] # 查看第1张图片的数据
array([0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
# .........
0. , 0. , 0.3803922 , 0.37647063, 0.3019608 ,
0.46274513, 0.2392157 , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. , 0. , 0. ,

mnist.train.labels是训练集标签。经过one-hot编码,每一个样本是10个元素的数组,只有一个元素的值是1,其它值都为0。值为1对应的下标就是该图片代表的数字,数字范围是[0,9]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> mnist.train.labels.shape
(55000, 10)

>>> mnist.train.labels
array([[0., 0., 0., ..., 1., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 1., 0.]])

>>> mnist.train.labels[0]
array([0., 0., 0., 0., 0., 0., 0., 1., 0., 0.]) # 代表数字7

测试集有10000个样本

1
2
3
4
>>> mnist.test.images.shape
(10000, 784)
>>> mnist.test.labels.shape
(10000, 10)

1.3. mnist.train.next_batch 批量迭代获取训练数据集

1
2
3
4
5
6
7
8
9
10
11
>>> x, y = mnist.train.next_batch(1)
>>> x.shape
(1, 784)
>>> y.shape
(1, 10)

>>> x, y = mnist.train.next_batch(50)
>>> x.shape
(50, 784)
>>> y.shape
(50, 10)

2. 单一隐藏层神经网络实现手写数字图片识别

单一隐藏层神经网络就是全连接神经网络。单一隐藏层,就是只有全连接层。

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
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

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

def full_connected():
"""
input_data.read_data_sets
"""
# # 获取真实的数据
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)

with tf.variable_scope("data"):
"""
第1步:建立数据的占位符
特征值 x [None, 784]
目标值 y_true [None, 10]
第1维代表样本数,因为不知道有多少样本,所以设置为None
"""
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.int32, [None, 10])

with tf.variable_scope("fc_model"):
"""
第2步:建立一个全连接层的神经网络
y_predict = tf.matmul(x, weight) + bias
已知 x[None, 784], y_predict[None, 10]
可以确定 w的形状是[784, 10],b的形状是[10]
"""

# 随机初始化 权重w[784, 10] 和 偏置b[10]
weight = tf.Variable(tf.random_normal([784, 10], mean=0.0, stddev=1.0), name="w")
bias = tf.Variable(tf.constant(0.0, shape=[10]))

# 预测None个样本的输出结果矩阵 [None, 784]*[784, 10]+[10]=[None, 10]
y_predict = tf.matmul(x, weight) + bias

with tf.variable_scope("soft_cross"):
"""
第3步:求出所有样本的损失,然后求平均值
tf.nn.softmax_cross_entropy_with_logits可以求交叉熵损失
labels: 标签值(真实值)
logits: 预测值
返回值:损失值列表
"""
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))

with tf.variable_scope("optimizer"):
"""
第4步:梯度下降求出损失
"""
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(loss)

with tf.variable_scope("acc"):
"""
第5步:计算准确率
tf.argmax(input,axis=None)
假设input形状是[10, 20, 30]
tf.argmax(input, 0) 表示求出10那一维度最大值下标
tf.argmax(input, 1) 表示求出20那一维度最大值下标
tf.argmax(input, 2) 表示求出30那一维度最大值下标
现在y的形状是[None, 10]
tf.argmax(y, 1) 就可以求出10那一维度最大值下标,即对应数字0-9
"""

equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))

# equal_list 有None个样本,例如值为[1, 0, 1, 0, 1, 1,..........],求平均值作为准确率
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))

# 定义一个初始化变量的op
init_op = tf.global_variables_initializer()

# 开启会话去训练
with tf.Session() as sess:
# 初始化变量
sess.run(init_op)

# 迭代步数去训练,更新参数预测
for i in range(2000):
# 取出真实存在的特征值和目标值,每次取出50个
mnist_x, mnist_y = mnist.train.next_batch(50)

# 运行train_op训练
sess.run(train_op, feed_dict={x: mnist_x, y_true: mnist_y})

print("训练第%d步,准确率为:%f" % (i + 1, sess.run(accuracy, feed_dict={x: mnist_x, y_true: mnist_y})))


if __name__ == "__main__":
full_connected()

2.1. 添加可视化

添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 要显示的标题信息
tf.summary.scalar("losses", loss)
tf.summary.scalar("acc", accuracy)
# 要显示的高纬度信息(用直方图显示)
tf.summary.histogram("weightes", weight)
tf.summary.histogram("biases", bias)
# 定义一个合并的op
merged = tf.summary.merge_all()

with tf.Session() as sess:
# 建立events文件,然后写入
filewriter = tf.summary.FileWriter("./summary/test/", graph=sess.graph)

for i in range(2000):
# 写入每步训练的值
summary = sess.run(merged, feed_dict={x: mnist_x, y_true: mnist_y})
filewriter.add_summary(summary, i)

启动tensorboard

1
tensorboard --logdir summary/test

2.2. 添加模型保存以及结果预测

添加以下内容

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
# 设置命令行参数is_train,1代表进行训练,0代表进行结果测试
tf.app.flags.DEFINE_integer("is_train", 1, "指定程序是预测还是训练")
# 获取命令行参数
FLAGS = tf.app.flags.FLAGS

def full_connected():
# 创建一个saver
saver = tf.train.Saver()

with tf.Session() as sess:
if FLAGS.is_train == 1:
for i in range(2000):
# ....
# 保存模型
saver.save(sess, "./model/fc_model")
else:
# 加载模型
saver.restore(sess, "./model/fc_model")

# 如果是0,做出预测
for i in range(100):
# 每次测试一张图片,y_test数据格式为[0,0,0,0,0,1,0,0,0,0]
x_test, y_test = mnist.test.next_batch(1)

# 求出测试标签对应的数字
digit_true = tf.argmax(y_test, 1).eval()
# 求出预测标签对应的数字
digit_test = tf.argmax(sess.run(y_predict, feed_dict={x: x_test, y_true: y_test}), 1).eval()
print("第%d张图片,手写数字图片目标是:%d, 预测结果是:%d %s" % (
i,
digit_true,
digit_test,
"" if digit_true == digit_test else "错误"
))

先训练得到模型并保存,再加载模型进行测试

1
2
python mnist.py --is_train=1   # 训练
python mnist.py --is_train=0 # 测试

2.3. 最终代码

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

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

# 设置命令行参数is_train,1代表进行训练,0代表进行结果测试
tf.app.flags.DEFINE_integer("is_train", 1, "指定程序是预测还是训练")
# 获取命令行参数
FLAGS = tf.app.flags.FLAGS


def full_connected():
"""
input_data.read_data_sets
"""
# # 获取真实的数据
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)

with tf.variable_scope("data"):
"""
第1步:建立数据的占位符
特征值 x [None, 784]
目标值 y_true [None, 10]
第1维代表样本数,因为不知道有多少样本,所以设置为None
"""
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.int32, [None, 10])

with tf.variable_scope("fc_model"):
"""
第2步:建立一个全连接层的神经网络
y_predict = tf.matmul(x, weight) + bias
已知 x[None, 784], y_predict[None, 10]
可以确定 w的形状是[784, 10],b的形状是[10]
"""

# 随机初始化 权重w[784, 10] 和 偏置b[10]
weight = tf.Variable(tf.random_normal([784, 10], mean=0.0, stddev=1.0), name="w")
bias = tf.Variable(tf.constant(0.0, shape=[10]))

# 预测None个样本的输出结果矩阵 [None, 784]*[784, 10]+[10]=[None, 10]
y_predict = tf.matmul(x, weight) + bias

with tf.variable_scope("soft_cross"):
"""
第3步:求出所有样本的损失,然后求平均值
tf.nn.softmax_cross_entropy_with_logits可以求交叉熵损失
labels: 标签值(真实值)
logits: 预测值
返回值:损失值列表
"""
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))

with tf.variable_scope("optimizer"):
"""
第4步:梯度下降求出损失
"""
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(loss)

with tf.variable_scope("acc"):
"""
第5步:计算准确率
tf.argmax(input,axis=None)
假设input形状是[10, 20, 30]
tf.argmax(input, 0) 表示求出10那一维度最大值下标
tf.argmax(input, 1) 表示求出20那一维度最大值下标
tf.argmax(input, 2) 表示求出30那一维度最大值下标
现在y的形状是[None, 10]
tf.argmax(y, 1) 就可以求出10那一维度最大值下标,即对应数字0-9
"""

equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))

# equal_list 有None个样本,例如值为[1, 0, 1, 0, 1, 1,..........],求平均值作为准确率
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))

# 要显示的标题信息
tf.summary.scalar("losses", loss)
tf.summary.scalar("acc", accuracy)
# 要显示的高纬度信息(用直方图显示)
tf.summary.histogram("weightes", weight)
tf.summary.histogram("biases", bias)
# 定义一个合并的op
merged = tf.summary.merge_all()

# 定义一个初始化变量的op
init_op = tf.global_variables_initializer()

# 创建一个saver
saver = tf.train.Saver()

# 开启会话去训练
with tf.Session() as sess:
# 初始化变量
sess.run(init_op)

# 建立events文件,然后写入
filewriter = tf.summary.FileWriter("./summary/test/", graph=sess.graph)

if FLAGS.is_train == 1:

# 迭代步数去训练,更新参数预测
for i in range(2000):
# 取出真实存在的特征值和目标值,每次取出50个
mnist_x, mnist_y = mnist.train.next_batch(50)

# 运行train_op训练
sess.run(train_op, feed_dict={x: mnist_x, y_true: mnist_y})

# 写入每步训练的值
summary = sess.run(merged, feed_dict={x: mnist_x, y_true: mnist_y})
filewriter.add_summary(summary, i)

print("训练第%d步,准确率为:%f" % (i + 1, sess.run(accuracy, feed_dict={x: mnist_x, y_true: mnist_y})))

# 保存模型
saver.save(sess, "./model/fc_model")

else:
# 加载模型
saver.restore(sess, "./model/fc_model")

# 如果是0,做出预测
for i in range(100):
# 每次测试一张图片,y_test数据格式为[0,0,0,0,0,1,0,0,0,0]
x_test, y_test = mnist.test.next_batch(1)

# 求出测试标签对应的数字
digit_true = tf.argmax(y_test, 1).eval()
# 求出预测标签对应的数字
digit_test = tf.argmax(sess.run(y_predict, feed_dict={x: x_test, y_true: y_test}), 1).eval()
print("第%d张图片,手写数字图片目标是:%d, 预测结果是:%d %s" % (
i,
digit_true,
digit_test,
"" if digit_true == digit_test else "错误"
))


if __name__ == "__main__":
full_connected()

3. 卷积神经网络实现手写数字图片识别

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

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


def weight_variables(shape):
"""
定义一个初始化权重的函数
:param shape:
:return:
"""
w = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=1.0))
return w


def bias_variables(shape):
"""
定义一个初始化偏置的函数
:param shape:
:return:
"""
b = tf.Variable(tf.constant(0.0, shape=shape))
return b


def model():
"""
自定义的卷积模型
:return:
"""

with tf.variable_scope("data"):
"""
第1步:建立数据的占位符
特征值 x [None, 784]
目标值 y_true [None, 10]
第1维代表样本数,因为不知道有多少样本,所以设置为None
"""
x = tf.placeholder(tf.float32, [None, 784])
y_true = tf.placeholder(tf.int32, [None, 10])

with tf.variable_scope("conv1"):
"""
第1个卷积层
卷积:
filter:
个数:取32个
大小:取5x5
步长:取strides=1
padding: "SAME"

卷积网络API: tf.nn.conv2d(input, filter, strides=, padding=, name=None)
input:给定的输入4阶张量,具有[batch,height,width,channel],类型为float32,64
batch: 样本数。因为是待定参数,所以这里为None
height/width: 长宽=28x28
channel: 通道数。因为图片是单通道的,所以设置为1

所以输入数据 x的形状要改变 [None, 784] ==> [None, 28, 28, 1]

filter: 指定过滤器张量,具有[filter_height,filter_width,in_channels,out_channels]
filter_height/filter_width: 过滤器长宽,这里取5x5
in_channels: 图片通道数,这里为1
out_channels: 过滤器数量,这里为32
所以weight形状为[5, 5, 1, 32]

激活: tf.nn.relu
激活函数不改变形状,只改变数值,所以还是[None, 28, 28, 32]

池化:
filter:
大小: 取2x2
步长:取strides=2
padding: "SAME"

输入形状:[None, 28, 28, 32]
输出形状:[None, 14, 14, 32]

"""

# 初始化weight(filter) 和 bias
w_conv1 = weight_variables([5, 5, 1, 32])
b_conv1 = bias_variables([32])

# tf.nn.conv2d要求输入的数据是4维张量,所以对x进行形状的改变[None, 784] ==> [None, 28, 28, 1]
x_reshape = tf.reshape(x, [-1, 28, 28, 1])

# 卷积+激活[None, 28, 28, 1]-----> [None, 28, 28, 32]
# strides=[1, stride, stride, 1],代表4个方向的步长,为了统一才要求以4维的形式给出。填充stride即可
x_relu1 = tf.nn.relu(tf.nn.conv2d(x_reshape, w_conv1, strides=[1, 1, 1, 1], padding="SAME") + b_conv1)

# 池化 2*2 ,strides2 [None, 28, 28, 32]---->[None, 14, 14, 32]
# ksize: 窗口大小
# strides: 步长
x_pool1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

with tf.variable_scope("conv2"):
"""
第2个卷积层
卷积: 5*5*32,64个filter,strides=1
激活: tf.nn.relu
池化:
filter:
个数: 64
大小: 取2x2
步长:取strides=2
padding: "SAME"

输入形状:[None, 14, 14, 64]
输出形状:[None, 7, 7, 64]
"""
# 随机初始化权重, 权重:[5, 5, 32, 64] 偏置[64]
w_conv2 = weight_variables([5, 5, 32, 64])

b_conv2 = bias_variables([64])

# 卷积,激活,池化计算
# [None, 14, 14, 32]-----> [None, 14, 14, 64]
x_relu2 = tf.nn.relu(tf.nn.conv2d(x_pool1, w_conv2, strides=[1, 1, 1, 1], padding="SAME") + b_conv2)

# 池化 2*2, strides 2, [None, 14, 14, 64]---->[None, 7, 7, 64]
x_pool2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

with tf.variable_scope("conv2"):
"""
全连接层
输入x_pool2: [None, 7, 7, 64]---降维-->[None, 7*7*64]

[None, 7*7*64] * w_fc[7*7*64, 10] + b_fc[10] = [None, 10]

"""

# 随机初始化权重和偏置
w_fc = weight_variables([7 * 7 * 64, 10])

b_fc = bias_variables([10])

# 修改形状 [None, 7, 7, 64] --->None, 7*7*64]
x_fc_reshape = tf.reshape(x_pool2, [-1, 7 * 7 * 64])

# 进行矩阵运算得出每个样本的10个结果
y_predict = tf.matmul(x_fc_reshape, w_fc) + b_fc

return x, y_true, y_predict


def conv_fc():
# 获取真实的数据
mnist = input_data.read_data_sets("./data/mnist/input_data/", one_hot=True)

# 定义模型,得出输出
x, y_true, y_predict = model()

# 进行交叉熵损失计算
# 3、求出所有样本的损失,然后求平均值
with tf.variable_scope("soft_cross"):
# 求平均交叉熵损失
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))

# 4、梯度下降求出损失
with tf.variable_scope("optimizer"):
train_op = tf.train.GradientDescentOptimizer(0.0001).minimize(loss)

# 5、计算准确率
with tf.variable_scope("acc"):
equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))

# equal_list None个样本 [1, 0, 1, 0, 1, 1,..........]
accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))

# 定义一个初始化变量的op
init_op = tf.global_variables_initializer()

# 开启回话运行
with tf.Session() as sess:
sess.run(init_op)

# 循环去训练
for i in range(1000):
# 取出真实存在的特征值和目标值
mnist_x, mnist_y = mnist.train.next_batch(50)

# 运行train_op训练
sess.run(train_op, feed_dict={x: mnist_x, y_true: mnist_y})

print("训练第%d步,准确率为:%f" % (i, sess.run(accuracy, feed_dict={x: mnist_x, y_true: mnist_y})))

if __name__ == '__main__':
conv_fc()

3.1. 步长

步长一般设置为1,这样观察得更仔细些

3.2. 激活层

为什么要激活函数?如果没有激活函数,神经网络只能解决线性问题,反之有了激活函数,就能解决非线性问题

为什么不用sigmoid,而是relu?

  • 从计算公式可以看出,sigmoid比relu计算量大
  • sigmoid容易导致梯度爆炸
panchaoxin wechat
关注我的公众号
支持一下