Mnist手写数字的识别
一.要求
使用一种深度学习框架,如tensorflow完成Mnist手写数字的识别
二.Mnist手写数字介绍
简介
MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片:
它也包含每一张图片对应的标签,告诉我们这个是数字几。比如,上面这四张图片的标签分别是5,0,4,1,我们将训练一个机器学习模型用于预测图片里面的数字。
数据集的下载
- 数据集可以在官网Yann LeCun’s website下载,
- 也可以通过tensorflow的提供了一份python源代码用于自动下载和安装这个数据集,可以下载代码,然后用下面的代码导入到你的项目里面,也可以直接复制粘贴到你的代码文件里面。代码如下(可以自己选择保存的路径,这里我选择的是E盘):
from tensorflow.examples.tutorials.mnist import input_data
data = input_data.read_data_sets(‘E:/mnist_tu_code/data_set’, one_hot=True)
数据集包含以下几个部分
文件按由上到下依次为,测试集图片 - 10000 张 图片,测试集图片对应的数字标签,训练集图排片55000 张 ,训练图片, 5000 张 验证图片,训练集图片对应的数字标签
关于数据集图片
mnist数据集里的每张图片大小为28 * 28像素,可以用28 * 28的大小的数组来表示一张图片,标签用大小为10的数组来表示,这种编码我们称之为One hot(独热编码)。
相关概念介绍
One-hot编码(独热编码)
独热编码使用N位代表N种状态,任意时候只有其中一位有效。
例如:数字0-9:
[0,0,0,0,0,0,0,0,0,1]代表9,
[0,1,0,0,0,0,0,0,0,0]代表1
独热编码的优点在于能够处理非连续型数值特征在一定程度上也扩充了特征
神经网络
神经网络的输入与输出
输入(x):输入是指传入给网络处理的向量,相当于数学函数中的变量
输出(y):输出是指网络处理后返回的结果,相当于数据函数中的函数
标签(label): 标签是指我们期望网络返回的结果。
对于识别mnist图片而言,输入是大小为784(28 * 28)的向量,输出是大小为10的概率向量(概率最大的位置,即预测的数字)
损失函数(loss function)
损失函数评估网络模型的好坏,值越大,表示模型越差,值越小,表示模型越好,因为传入大量的训练集训练的目标,就是将损失函数的值降到最小。
常见的损失函数的定义:
差的平方和 sum((y - label)^2)
[0, 0, 1] 与 [0.2, 0.2, 0.6] 的差的平方和为 0.04 + 0.04 + 0.16 = 0.24
[0, 0, 1] 与 [0.1, 0, 0.9] 的差的平方和为 0.01 + 0.01 = 0.02
交叉熵 -sum(label * log(y))
[0, 0, 1] 与 [0.2, 0.2, 0.6] 的交叉熵为 -log(0.6) = 0.51
[0, 0, 1] 与 [0.1, 0, 0.9] 的交叉熵为 -log(0.9) = 0.10
回归模型
我们可以将网络理解为一个函数,回归模型,其实是希望对这个函数进行拟合。
比如定义模型为 Y = X * w + b,对应的损失即
loss = (Y - labal)^2
= -(X * w - b - label)^2
这里损失函数用方差计算,这个函数是关于w和b的二次函数,所以神经网络训练的目的是找到w和b,使得loss最小。
可以通过不断地传入X和label的值,来修正w和b,使得最终得到的Y与label的loss最小。这个训练的过程,可以采用梯度下降的方法。通过梯度下降,找到最快的方向,调整w和b值,使得w * X + b的值越来越接近label。
softmax激活函数
计算交叉熵前的Y值是经过softmax后的,经过softmax后的Y,并不影响Y向量的每个位置的值之间的大小关系。大致有2个作用,一是放大效果,二是梯度下降时需要一个可导的函数。softmax模型可以用来给不同的对象分配概率。即使在之后,我们训练更加精细的模型时,最后一步也需要用softmax来分配概率
学习速率
梯度即一个函数的斜率,找到函数的斜率,其实就知道了w和b的值往哪个方向调整,能够让函数值(loss)降低得最快。那么方向知道了,往这个方向调整多少呢?这个数,神经网络中称之为学习速率。学习速率调得太低,训练速度会很慢,学习速率调得过高,每次迭代波动会很大。
TensorFlow模型保存和提取方法
TensorFlow通过tf.train.Saver类实现神经网络模型的保存和提取。tf.train.Saver对象saver的save方法将TensorFlow模型保存到指定路径中,saver.save(sess,“Model/model.ckpt”) ,checkpoint文件保存了一个录下多有的模型文件列表,model.ckpt.meta保存了TensorFlow计算图的结构信息,model.ckpt保存每个变量的取值,此处文件名的写入方式会因不同参数的设置而不同,但加载restore时的文件路径名是以checkpoint文件中的“model_checkpoint_path”值决定的加载模型的代码中也要定义TensorFlow计算图上的所有运算并声明一个tf.train.Saver类,不同的是加载模型时不需要进行变量的初始化,而是将变量的取值通过保存的模型加载进来。
saver()与restore()
saver()与restore()只是保存了session中的相关变量对应的值,并不涉及模型的结构。
Saver
Saver的作用是将我们训练好的模型的参数保存下来,以便下一次继续用于训练或测试,Saver类训练完后,是以checkpoints文件形式保存。提取的时候也是从checkpoints文件中恢复变量。Checkpoints文件是一个二进制文件,它把变量名映射到对应的tensor值 ,一般地,Saver会自动的管理Checkpoints文件。我们可以指定保存最近的N个Checkpoints文件,当然每一步都保存ckpt文件也是可以的,但是费存储空间。
saver()可以选择global_step参数来为ckpt文件名添加数字标记:
saver.save(sess, ‘my-model’, global_step=0) ==> filename: ‘my-model-0’
max_to_keep参数定义saver()将自动保存的最近n个ckpt文件,默认n=5,即保存最近的5个检查点ckpt文件。若n=0或者None,则保存所有的ckpt文件
Restore
Restore则是将训练好的参数提取出来。
restore(sess, save_path)
sess: 保存参数的会话。
save_path: 保存参数的路径。
四. 所需库介绍及下载
Tensorflow:
Tensorflow是广泛使用的实现机器学习以及其它涉及大量数学运算的算法库之一。Tensorflow由Google开发,是GitHub上最受欢迎的机器学习库之一。Google几乎在所有应用程序中都使用Tensorflow来实现机器学习,TensorFlow的核心组件是通过边遍历所有节点的计算图和张量。
下载tensorflow时速度会很慢,可能需要翻墙,或者借助镜像,不过,笔者是在Anaconda中的Anaconda Prompt中用conda install tensorflow命令下载的
Numpy:
Numpy 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库,NumPy 通常与 SciPy(Scientific Python)和 Matplotlib(绘图库)一起使用, 这种组合广泛用于替代 MatLab,是一个强大的科学计算环境,有助于我们通过 Python 学习数据科学或者机器学习。
下载numpy可以通过在命令行中使用 pip install numpy 进行下载
PIL:
Python平台事实上的图像处理标准库是PIL:Python Imaging Library。一群志愿者在PIL的基础上创建了兼容的版本,名字叫pillow,是Python处理图像的便准库,功能强大,使用简单。使用 pip install PIL命令含行下载,然后使用 from PIL import Image导入使用
五. 项目代码解读
此项目含有三个模块,分别是网络搭建模型的model.py模块,训练的train.py模块,以及验证的predict.py预测模块。
model.py
model.py主要用于进行网络模型的搭建
import tensorflow as tf
class Network:
def __init__(self):
self.learning_rate = 0.001
# 记录已经训练的次数
self.global_step = tf.Variable(0, trainable=False)
# 输入张量 28 * 28 = 784个像素的图片一维向量
self.x = tf.placeholder(tf.float32, [None, 784])
# 标签值,即图像对应的结果,如果对应数字是8,则对应label是 [0,0,0,0,0,0,0,0,1,0]
# 标签是一个长度为10的一维向量,值最大的下标即图片上写的数字,采用独热编码,最大的值即是标签对应的值
self.label = tf.placeholder(tf.float32, [None, 10])
#权重,全部初始化为0
self.w = tf.Variable(tf.zeros([784, 10]))
# 偏置 bias, 初始化全 0
self.b = tf.Variable(tf.zeros([10]))
# 输出 y = softmax(X * w + b)
self.y = tf.nn.softmax(tf.matmul(self.x, self.w) + self.b)
# 损失,即交叉熵,最常用的计算标签(label)与输出(y)之间差别的方法
self.loss = -tf.reduce_sum(self.label * tf.log(self.y + 1e-10))
# 反向传播,采用梯度下降的方法。调整w与b,使得损失(loss)最小
# loss越小,那么计算出来的y值与 标签(label)值越接近,准确率越高
# minimize 可传入参数 global_step, 每次训练 global_step的值会增加1
# 因此,可以通过计算self.global_step这个张量的值,知道当前训练了多少步
self.train = tf.train.GradientDescentOptimizer(self.learning_rate).minimize(
self.loss, global_step=self.global_step)
# argmax 返回最大值的下标,最大值的下标即答案
predict = tf.equal(tf.argmax(self.label, 1), tf.argmax(self.y, 1))
self.accuracy = tf.reduce_mean(tf.cast(predict, "float"))
首先我们需要自己搭建网络模型,保证保证模型可以保存和加载,即如果模型可以在上次训练停下的地方继续训练。
- global_step :记录已经训练的次数,tf.Variable(0, trainable=False)
- learn-rate : 表示我们的学习速率,然后将训练集里的初始化,
- x: 为训练数据的一维向量(代表28*28的像素点),
- label: 为训练数据的标签值
- w : 为训练时的权重,初始化为变量,使用tf的tf.Variable()方法初始化
- b : 是训练模型的偏置值 bias ,初始化为0
- y : 为输出量,使用softmax激活函数回归模型,
- loss : 表示损失,损失值越小表示模型的训练效果越好,这里是使用交叉熵表 示损失函数
训练使用随机梯度下降算法,使参数沿着 梯度的反方向,即总损失减小的方向移动,实现更新参数主要代码如下tf.train.GradientDescentOptimizer(),通过梯度下降法为最小化损失函数增加了相关的优化操作,在训练过程中,先实例化一个优化函数,比如 tf.train.GradientDescentOptimizer,并基于一定的学习率进行梯度优化训练:
train = tf.train.GradientDescentOptimizer(learning_rate)
然后,可以设置 一个用于记录全局训练步骤的单值,以及使用minimize()操作,该操作不仅可以优化更新训练的模型参数,也可以为全局步骤(global step)计数。与其他tensorflow操作类似,这些训练操作都需要在tf.session会话中进行
predict 用以对比预测值与真实标签值是否相等,使用tf.equel()方法,其中的参数分别是独热编码数组y和label标签中的最大值的下标。
tf.cast() 用于张量的数据类型转化,第一个参数是带转换的数据,第二个参数是要转换的数据类型,并用tf.reduce_mean()计算平均值
train.py
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
from model import Network
CKPT_DIR = 'ckpt'
class Train:
def __init__(self):
self.net = Network()
# 初始化 session
# Network() 只是构造了一张计算图,计算需要放到会话(session)中
self.sess = tf.Session()
# 初始化变量
self.sess.run(tf.global_variables_initializer())
# 读取训练和测试数据,这是tensorflow库自带的,不存在训练集会自动下载
# 项目目录下已经下载好,删掉后,重新运行代码会自动下载
self.data = input_data.read_data_sets('E:/mnist_tu_code/data_set', one_hot=True)
def train(self):
# batch_size 是指每次迭代训练,传入训练的图片张数。
# 总的训练次数
batch_size = 64
train_step = 30000
# 记录训练次数, 初始化为0
step = 0
# 每隔1000步保存模型
save_interval = 100
# tf.train.Saver是用来保存训练结果的。
# max_to_keep 用来设置最多保存多少个模型,默认是5
# 如果保存的模型超过这个值,最旧的模型将被删除
saver = tf.train.Saver(max_to_keep=10)
# 开始训练前,检查ckpt文件夹,看是否有checkpoint文件存在。
# 如果存在,则读取checkpoint文件指向的模型,restore到sess中。
ckpt = tf.train.get_checkpoint_state(CKPT_DIR)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(self.sess, ckpt.model_checkpoint_path)
# 读取网络中的global_step的值,即当前已经训练的次数
step = self.sess.run(self.net.global_step)
print('Continue from')
print(' -> Minibatch update : ', step)
while step < train_step:
# 从数据集中获取 输入和标签(也就是答案)
x, label = self.data.train.next_batch(batch_size)
# 每次计算train,更新整个网络
# loss只是为了看到损失的大小,方便打印
_, loss = self.sess.run([self.net.train, self.net.loss],
feed_dict={self.net.x: x, self.net.label: label})
step = self.sess.run(self.net.global_step)
if step % 1000 == 0:
print('第%5d步,当前loss:%.2f' % (step, loss))
# 模型保存在ckpt文件夹下
# 模型文件名最后会增加global_step的值,比如1000的模型文件名为 model-1000
if step % save_interval == 0:
saver.save(self.sess, CKPT_DIR + '/model', global_step=step)
def calculate_accuracy(self): #计算准确率
test_x = self.data.test.images
test_label = self.data.test.labels
accuracy = self.sess.run(self.net.accuracy,
feed_dict={self.net.x: test_x, self.net.label: test_label})
print("准确率: %.2f,共测试了%d张图片 " % (accuracy, len(test_label)))
if __name__ == "__main__":
app = Train()
app.train()
app.calculate_accuracy()
每一次训练,会进行一次梯度下降,传入的global_step的值会自增1,因此,可以通过计算global_step这个张量的值,知道当前训练了多少步。
CKPT_DIR = ‘ckpt’ 定义保存的位置,模型训练的进度都保存在ckpt文件夹中,ckpt文件夹下会生成多个个文件,第一个文件是 checkpoint文件,保存了所有的模型的路径。其中第一行代表当前的状态,即在加载模型时,使用哪一个模型是由第一行决定的。
接下来构造Train类,其中包含多个方法
net = Network()
初始化 session
Network() 只是构造了一张计算图,计算需要放到会话(session)中
sess = tf.Session()
初始化变量
sess.run(tf.global_variables_initializer())
读取训练和测试数据,这是tensorflow库自带的,不存在训练集会自动下载, 项目目录下已经下载好,删掉后,重新运行代码会自动下载
data = input_data.read_data_sets('E:/mnist_tu_code/data_set', one_hot=True)
先将我们构造好的网络模型实例化,然后初始化图,将我们的计算放入会话(session)中,之后使用tf.global_variables_initializer()方法初始话变量,将我们的训练集和测试集数据读取,至此我们的准备工作完成,接下来开始进行我们的
train()函数
batch_size = 64
train_step = 30000
batch_size 指每次迭代训练,传入训练的图片张数。,这里我们定 义每次的训练传入64张图片进行训练
train_step 定义总的训练次数
step = 0
save_interval = 1000
step 用来记录训练的次数,初始话化为0
save_interval 用来记录保存模型的间隔,这里我们可以设置成每隔1000步保存
saver = tf.train.Saver(max_to_keep=10)
tf.train.Saver是用来保存训练结果的, max_to_keep 用来设置最多保存多少个模型,默认是5,如果保存的模型超过这个值,最旧的模型将被删除
ckpt = tf.train.get_checkpoint_state(CKPT_DIR)
每次开始训练前需要检查ckpt文件夹,看是否有checkpoint文件存在,如果存在,则读取checkpoint文件指向的模型,restore到sess中,读取我们已经训练好的模型,然后打印出模型的保存到第几步位置。
if ckpt and ckpt.model_checkpoint_path:
saver.restore(self.sess, ckpt.model_checkpoint_path)
# 读取网络中的global_step的值,即当前已经训练的次数
step = self.sess.run(self.net.global_step)
print('Continue from')
print(' -> Minibatch update : ', step)
接着开始进行训练,当step即模型未训练完的时候,不断从数据集中获取输入和正确的标签,进行训练,计算train并更行到哦网络,然后打印出loss的损失值,在后面的运行中可以看出,loss的值不断变小的,每隔1000步,打印step和loss的值
while step < train_step:
# 从数据集中获取 输入和标签(也就是答案)
x, label = self.data.train.next_batch(batch_size)
# 每次计算train,更新整个网络
# loss只是为了看到损失的大小,方便打印
_, loss = self.sess.run([self.net.train, self.net.loss],
feed_dict={self.net.x: x, self.net.label: label})
step = self.sess.run(self.net.global_step)
if step % 1000 == 0:
print('第%5d步,当前loss:%.2f' % (step, loss))
# 模型保存在ckpt文件夹下
# 模型文件名最后会增加global_step的值,比如1000的模型文件名为 model-1000
if step % save_interval == 0:
saver.save(self.sess, CKPT_DIR + '/model', global_step=step)
calculate_accuracy()函数
def calculate_accuracy(self): #计算准确率
test_x = self.data.test.images
test_label = self.data.test.labels
accuracy = self.sess.run(self.net.accuracy,
feed_dict={self.net.x: test_x, self.net.label: test_label})
print("准确率: %.2f,共测试了%d张图片 " % (accuracy, len(test_label)))
calculate_accuracy()函数是计算准确率的函数,读入的图片和标签并计算准确率,最后打印准确率与总共测试的图片。
predict.py
precict.py 用于验证,手写数字模型的训练效果,需要用到PIL库
import tensorflow as tf
import numpy as np
from PIL import Image
from model import Network
'''
使用tensorflow的模型来预测手写数字
输入是28 * 28像素的图片,输出是个具体的数字
'''
CKPT_DIR = 'ckpt'
class Predict:
def __init__(self):
self.net = Network()
self.sess = tf.Session()
self.sess.run(tf.global_variables_initializer())
# 加载模型到sess中
self.restore()
def restore(self):
saver = tf.train.Saver()
ckpt = tf.train.get_checkpoint_state(CKPT_DIR)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(self.sess, ckpt.model_checkpoint_path)
else:
raise FileNotFoundError("未保存任何模型")
def predict(self, image_path):
# 读图片并转为黑白的
img = Image.open(image_path).convert('L')
flatten_img = np.reshape(img, 784)
x = np.array([1 - flatten_img])
y = self.sess.run(self.net.y, feed_dict={self.net.x: x})
# 因为x只传入了一张图片,取y[0]即可
# np.argmax()取得独热编码最大值的下标,即代表的数字
print(image_path)
print(' -> Predict digit', np.argmax(y[0]))
if __name__ == "__main__":
app = Predict()
app.predict('E:/mnist_tu_code/test_images/0.png')
app.predict('E:/mnist_tu_code/test_images/1.png')
app.predict('E:/mnist_tu_code/test_images/4.png')
首先依旧是初始化图,将我们的计算放入会话(session)中,之后使用tf.global_variables_initializer()方法初始话变量,将我们的训练集和测试集数据读取,至此我们的准备工作完成,值得注意的是我们需要加载模型到sess中,使用tf的restore()方法。
restore()函数
saver = tf.train.Saver()
实例化Saver(),之后读取模型保存的ckpt ,如果模型的保存正确实例化restore读取模型保存的训练数据进行数字识别,
predict(self, image_path):
参数为所要识别的手写数字图片的保存路径,
img = Image.open(image_path).convert(‘L’)
flatten_img = np.reshape(img, 784)
此段代码是用PIL库的Image模块读取所要识别图片的灰度图,并将其像素点转换成一个一维的向量,之后便打印出所预测数字的结果
八.效果展示
可以看出程序运行成功,每隔1000步打印一次loss值,并且loss值随着模型的不断训练减小,正确率为92左右当我们在代码中修改总共训练的次数时,训练次数由20000变为30000时,可以看到,模型再次训练时,并不会从0 开始训练,而是接着上次训练20000张图片之后的模型开始训练,我们的模型成功的保存识别一张手写数字时,模型成功识别,我们分别传入了0,1,4三张手写数字,训练的模型成功识别并输出了0,1,4
源代码链接
https://blog.csdn.net/lucklydog123/article/details/100547531