在学习完简单的线性回归问题之后,但是线性回归只能解决一些房价预测等求解具体数值的问题,但是在现实生活中有很多的分类问题,例如:对于图片的分类、判断是否等问题。
今天想研究的问题就是这一类的分类问题:MNIST手写数字识别,就是利用我们训练的模型对一张手写数字图片进行识别,判断这个数字是0~9中的哪一个?
1、数据下载与解释
对于数据集的获取,我们可以直接通过mnist数据集来直接下载获取,导入相应的包之后,直接运行下列代码,会自动下载到我们当前的文件夹,当然也可以从其他途径下载好放到当前项目文件夹中。
关于这个数据集,其中一共包括3个部分:训练集(train)、验证集(validation)和测试集(test)。通过下面打印出来的结果,我们可以看到:有55000个训练集、5000个验证集和10000个测试集,这样的数据分配就可以很好的解决我们前面讲到的问题,不存在“模型记忆”问题。
首先使用训练集对我们的模型进行训练,当正确率达到我们的预期之后,然后使用验证集进行验证训练的效果,在通过了验证集的验证之后,就可以使用测试集再次检查评估结果。(降低过拟合的发生几率)
得到的新的工作流程为:
# 下载并
import tensorflow as tf
import tensorflow.examples.tutorials.mnist.input_data as input_data
import matplotlib.pyplot as plt
import numpy as np
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
print("训练集 train 数量:", mnist.train.num_examples,
",验证集 validation 数量:", mnist.validation.num_examples,
",测试集 test 数量:", mnist.test.num_examples)
print("训练集图片的shape:", mnist.train.images.shape,
"标签的shape:", mnist.train.labels.shape)
# 图像变形(变成图像)
mnist.train.images[0].reshape(28, 28)
运行结果:
从打印出来的训练集图片的shape我们可以看到,是一个55000 * 784的二维矩阵,我们已经知道训练集有55000张图片,所以每一行就代表一张图片,每一张图片的规模是28*28=784个像素的图片,所以在下面我们对图像进行了“变形”。
这里主要说一下标签数据,从打印出来的结果可以看出有10列,每一张图片的标签是10个数字,那么怎么通过这10个数字来表示0-9这10个数字呢?这就涉及到了一种编码模式:独热编码(one hot编码),简单讲就是:想分为多少类就需要多少个数,然后相应类别的那一位数为1,其余位数都为0.具体的解释可以看下面的解释:
2、知识点补充
在下载好数据之后,接下来应该进行模型的构建。我们先来补充一些零碎的知识点,然后再进行模型的构建。
(1)显示手写数字图片
可以定义一个函数,然后调用matplotlib包中的imshow()来显示图片。
# 定义图像显示的函数
def plot_image(image):
plt.imshow(image.reshape(28, 28), cmap='binary')
plt.show()
# 显示任意手写数字
plot_image(mnist.train.images[1])
图片显示结果:
(2)关于reshape的详解
关于reshape函数我们前面已经接触过了,是用来“重塑”形状的,那么规则是什么样的呢?其实一句话就是行优先,逐行排列,通过下面的例子我们可以看出。
# 进一步了解reshape()
# 行优先,逐行排列
plot_image(mnist.train.images[5])
plt.imshow(mnist.train.images[5].reshape(14, 56), cmap='binary')
plt.show()
重塑之前的形状:28 * 28
重塑之后的形状:14 * 56
(3)argmax函数
返回数组中最大值的索引值,但是对于二维数组和多维数组的返回值相对比较麻烦,可以看一下这篇博客。
https://blog.csdn.net/weixin_38145317/article/details/79650188
对于二维数组,有一个参数axis,当这个参数为0时,按照列找到最大值找下标;当参数为1时,按照行的最大值找下标。
# 返回数组中最大值的索引值
print(np.argmax(mnist.train.labels[1]))
(4)数据批量读取
可以每一次读入10组数据或者更多组数据。
# 数据的批量读取
# next_batch方法读取数据包括两部分(图像和标签)
batch_images_xs, batch_labels_ys = mnist.train.next_batch(batch_size=10)
print(batch_labels_ys)
3、模型构建
前面定义的变量和占位都是一样的,主要区别就是后面定义前向运算和对于结果的分类。
前向运算也就相当于前面定义的模型:
主要的就是结果分类,由于前面研究的是线性回归的问题,最终只需要输出一个结果值即可。但是现在研究的问题是分类问题,也就是我们要把最终的输出结果变成相应的有多少类就输出多少个数,而且这些数代表每一类的概率,所有类相加起来应该等于1。
这里我们可以简单的理解为:softmax函数就是用来做这个工作的,它将前向计算得到的结果变成相应类别的概率,哪个类别所分配到的概率最大,那么最终分类结果就是这一类。
# 模型构建
# mnist 中每张图片有28*28=784个像素点
x = tf.placeholder(tf.float32, [None, 784], name="X")
# 0-9 一共10个数字(10个类别)
y = tf.placeholder(tf.float32, [None, 10], name="Y")
# 定义模型变量
w = tf.Variable(tf.random_normal([784, 10]), name="w")
b = tf.Variable(tf.zeros([10]), name="b")
# 定义前向计算
forward = tf.matmul(x, w) + b
# 结果分类
# Softmax 将逻辑回归延伸到多类别领域,会为每个类别分配一个用小数表示的概率(相加起来等于1)
pred = tf.nn.softmax(forward)
# 逻辑回归(确保输出值在[0, 1])
# Sigmoid 函数(二分类)
4、模型训练
定义好模型之后就是进行模型的训练了,和原来一样设置轮数、学习率等,不过这里我们又设置了一个变量batch_size,就是单次训练样本数,由于我们前面都是每次训练一个样本,这样效率比较慢,所以这里我们每次训练的数据是100个。这里训练数据有55000个,如果按照原来一次一个,那么得循环55000次,而现在每次100个,那么就只需要循环550次即可。
然后就是定义损失函数和优化器,损失函数这里使用的是交叉熵函数,优化器还是利用梯度下降法。
我们还想知道的一个值就是每轮训练之后的准确率,判断独热编码最大值和预测后的概率最大值是否有一样的索引,如果一样,那么就是预测正确,如果不一样,那么就预测错误。total_batch 个批次训练完后,使用验证数据集计算准确率。
# 定义训练参数
train_epochs = 50 # 训练的轮数
batch_size = 100 # 单次训练样本数
total_batch = int(mnist.train.num_examples/batch_size)
display_step = 1 # 显示粒度
learning_rate = 0.01 # 学习率
# 定义损失函数(交叉熵损失函数)
loss_function = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1))
# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
# 定义准确率(新特性)
# 检测预测类别 tf.argmax(pred, 1)与实际类别 tf.argmax(y, 1)的匹配情况
# 关于argmax函数的使用
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
# 准确率,将布尔值转化为浮点数,并计算平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 定义会话
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
# 开始训练
for epoch in range(train_epochs):
for batch in range(total_batch):
xs, ys = mnist.train.next_batch(batch_size)
sess.run(optimizer, feed_dict={x: xs, y: ys})
# total_batch 个批次训练完后,使用验证数据计算准确率
loss, acc = sess.run([loss_function, accuracy], feed_dict={x: mnist.validation.images, y: mnist.validation.labels})
# 打印训练过程中的详细信息
if (epoch+1) % display_step == 0:
print("训练轮次:", epoch+1, "损失值:", format(loss), "准确率:", format(acc))
print("训练完成!")
训练结果:
5、模型评估
最后利用训练集、验证集、测试集上评估模型。
# 评估模型
# 完成训练之后,在测试集上评估模型准确率
accu_test = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})
print("测试集正确率:", accu_test)
# 完成训练之后,在验证集上评估模型准确率
accu_validation = sess.run(accuracy, feed_dict={x: mnist.validation.images, y: mnist.validation.labels})
print("验证集正确率:", accu_validation)
# 完成训练之后,在训练集上评估模型准确率
accu_train = sess.run(accuracy, feed_dict={x: mnist.train.images, y: mnist.train.labels})
print("训练集正确率:", accu_train)
附:完整代码
#-*- coding = utf-8 -*-
#@Time : 2021-03-06 15:00
#@Author : 穆永恒
#@File : Mnist.py
#@Software: PyCharm
# 下载数据
import tensorflow as tf
import tensorflow.examples.tutorials.mnist.input_data as input_data
import matplotlib.pyplot as plt
import numpy as np
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
# print("训练集 train 数量:", mnist.train.num_examples,
# ",验证集 validation 数量:", mnist.validation.num_examples,
# ",测试集 test 数量:", mnist.test.num_examples)
#
# print("训练集图片的shape:", mnist.train.images.shape,
# "标签的shape:", mnist.train.labels.shape)
# 图像变形(变成图像)
mnist.train.images[0].reshape(28, 28)
# 定义图像显示的函数
def plot_image(image):
plt.imshow(image.reshape(28, 28), cmap='binary')
plt.show()
# 显示任意手写数字
# plot_image(mnist.train.images[1])
# 进一步了解reshape()
# 行优先,逐行排列
# plot_image(mnist.train.images[5])
# plt.imshow(mnist.train.images[5].reshape(14, 56), cmap='binary')
# plt.show()
# 标签数据与独热编码(相应的位置为1,其余位置都为0)
# print(mnist.train.labels[1])
# 返回数组中最大值的索引值
# print(np.argmax(mnist.train.labels[1]))
# 数据集的划分(训练集、验证集、测试集)
# 数据的批量读取
# print(mnist.train.labels[0:10])
# next_batch方法读取数据包括两部分(图像和标签)
# 会实现内部打乱(shuffle)
batch_images_xs, batch_labels_ys = mnist.train.next_batch(batch_size=10)
# print(batch_labels_ys)
# 模型构建
# mnist 中每张图片有28*28=784个像素点
x = tf.placeholder(tf.float32, [None, 784], name="X")
# 0-9 一共10个数字(10个类别)
y = tf.placeholder(tf.float32, [None, 10], name="Y")
# 定义模型变量
w = tf.Variable(tf.random_normal([784, 10]), name="w")
b = tf.Variable(tf.zeros([10]), name="b")
# 定义前向计算
forward = tf.matmul(x, w) + b
# 结果分类
# Softmax 将逻辑回归延伸到多类别领域,会为每个类别分配一个用小数表示的概率(相加起来等于1)
pred = tf.nn.softmax(forward)
# 逻辑回归(确保输出值在[0, 1])
# Sigmoid 函数
# 定义训练参数
train_epochs = 50 # 训练的轮数
batch_size = 100 # 单次训练样本数
total_batch = int(mnist.train.num_examples/batch_size)
display_step = 1 # 显示粒度
learning_rate = 0.01 # 学习率
# 定义损失函数(交叉熵损失函数)
loss_function = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1))
# 定义优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
# 定义准确率(新特性)
# 检测预测类别 tf.argmax(pred, 1)与实际类别 tf.argmax(y, 1)的匹配情况
# 关于argmax函数的使用
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
# 准确率,将布尔值转化为浮点数,并计算平均值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
# 定义会话
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
# 开始训练
for epoch in range(train_epochs):
for batch in range(total_batch):
xs, ys = mnist.train.next_batch(batch_size)
sess.run(optimizer, feed_dict={x: xs, y: ys})
# total_batch 个批次训练完后,使用验证数据计算准确率
loss, acc = sess.run([loss_function, accuracy], feed_dict={x: mnist.validation.images, y: mnist.validation.labels})
# 打印训练过程中的详细信息
if (epoch+1) % display_step == 0:
print("训练轮次:", epoch+1, "损失值:", format(loss), "准确率:", format(acc))
print("训练完成!")
# 评估模型
# 完成训练之后,在测试集上评估模型准确率
accu_test = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels})
print("测试集正确率:", accu_test)
# 完成训练之后,在验证集上评估模型准确率
accu_validation = sess.run(accuracy, feed_dict={x: mnist.validation.images, y: mnist.validation.labels})
print("验证集正确率:", accu_validation)
# 完成训练之后,在训练集上评估模型准确率
accu_train = sess.run(accuracy, feed_dict={x: mnist.train.images, y: mnist.train.labels})
print("训练集正确率:", accu_train)