机器学习实战:MNIST手写数据识别:分类应用入门
MNIST分类入门实现代码
1. 如何引入MNIST手写数据并确认读入成功。
实现了数据的读入,并且对读入的数据进行观察的实现的代码如下: 读入MNIST数据并观察
- 其实这个点是会存在很多问题的,因为TF版本的原因,然后视频引例中使用的TF的版本又偏比较老旧,所以很多比较新的版本的TF的自带的数据的包,其实是放到了其他地方去了的。这里主要要区分的处理方式分为两种类型。
1.1 针对版本是TF1但是属于1中比较高的版本的,比如1.15.0版本。
- 首先出现的问题是这个样子的:
- 由于我当前使用的版本是tensorflow1.15.0 的版本,而课程中讲解很多用的是TF1比较初等版本的代码,所以很多地方是存在代码不对称的问题的。
- 直接使用TF1比较初等版本的语句,虽然也能达到效果,数据资料可以被引用进来,运行上面的代码,会自动下载数据集并将文件解压到当前代码所在同级自录下的MNIST_data 文件夹下。
- 原因是
mnist = input_data.read_data_sets("MNIST_data/", one_hot = True) 这个语句提供了资料MNIST_data 的下载的相对路径,所以才会被下载在当前项目的文件内。 - 但是难受的是,这种方法,代码本身的报错并不会解除,尽管数据有被引用进来,也能够被使用。
解决方案:
- 在1.15.0的版本中,那些文件其实被放在了另一个地方:
- 把引用的位置改为
tensorflow_core 就能找到这些文件了。 - 简单来说,如果用的版本依然是TF1只不过版本等级比较高的话,那可以知道,大部分的文件都还在,只不过是换了一个地方而已.
- 把tensorflow换成tensorflow_core就行。
1.2 针对版本是TF2版本的处理办法。
TF2.0解决MNIST手写数据问题的博客
import tensorflow as tf
from tensorflow_core.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot = True)
mnist_not_one_hot = input_data.read_data_sets("MNIST_data/", one_hot = False)
read_data_sets() 函数是MNIST中自带的用来读取数据的函数,这里面可以研究的东西很多,暂时是从功能角度记住它能从MNIST数据中读取数据就行。"MNIST_data/" 这个是用来存放下载的数据的相对路径,下载好后的这个文件会被放到工程文件的根目录下。one_hot 这个参数用来选着标签数据是否用独热编码来表示。 one-hot
1.3 对读入的数据的测试:
1.3.1 查看读入的数据的形状:
- 由于对分类问题涉及到需要有训练集,验证集和测试集,所以MNIST数据本身也自带了这三个集合,并做好了分类。
print('train images shape:', mnist.train.images.shape,
',labels shape', mnist.train.labels.shape)
print('validation images shape:', mnist.validation.images.shape,
',labels shape', mnist.validation.labels.shape)
print('test images shape:', mnist.test.images.shape,
',labels shape', mnist.test.labels.shape)
1.3.2 像列表一样取单个数据:
print(len(mnist.train.images[0]))
print(mnist.train.images[0].shape)
print(mnist.train.labels[0])
1.3.3 单幅图片的可视化的实现
print(mnist.train.images[0].reshape(28, 28))
# 可视化
def plot_image(image):
plt.imshow(image.reshape(14, 56), cmap='binary')
plt.show()
plot_image(mnist.train.images[20000])
reshape()函数 这个函数会以行优先,先把一行的数据排满后然后再往下接数据去排。在它需要输入的两个参数中,左边的那个参数代表行数,右边的那个参数代表列数。plt.imshow()函数 这个函数用来实现图片的可视化,它的功能很强大,需要载入的参数有图形矩阵(可以控制形状),以及色彩的搭配方案。
1.3.4 对独热编码进行取值
print(np.argmax(mnist.train.labels[0]))
np.argmax()函数 这个函数可以取当前列表中最大项的索引,对当前数字的10分类问题来说,可以实现把独热码转换成它的实际意义的数字。
1.3.5 一次性读取多条数据
- 方法一,采用列表的切片的读取的方法
print(mnist.train.labels[0:10])
- 方法二,采用mnist自带的next_batch方法
batch_images_xs, batch_labels_ys = \
mnist.train.next_batch(batch_size=10)
print(batch_images_xs)
print(batch_labels_ys)
next_batch() 会返回两个值,一个是x维度的n维数组,另一个是y维度的n维数组;并且它有一个参数batch_size 用来控制每一批次读取的数据的数量。- 在同一个数据集中调用
next_batch() 的话,在全部调用完毕之前,下一次调用会在紧接在上一次调用的地方往下继续调用。
1.3.6 tf.random_normal()用于参数自动赋初值。
norm = tf.random_normal([100])
with tf.Session() as sess:
norm_data = norm.eval()
print(norm_data[:10])
plt.hist(norm_data)
plt.show()
tf.random_normal() 它需要的参数是输入n维数组的形状,它会自动返回一个填充满随机数的对应形状的n维数组。plt.hist() 它实现的是将一组数据用柱状图的形式可视化出来。
2. 模型的构建。
- 该模型的构建采用的方法是使用了单个神经元的方法来实现的,内部的数学核心是使用了线性代数中的矩阵运算。
# 占位符的定义
x = tf.placeholder(tf.float32, [None, 784], name="images")
y = tf.placeholder(tf.float32, [None, 10], name="labels")
# 定义变量
w = tf.Variable(tf.random_normal([784,10]),name="weight")
b = tf.Variable(tf.zeros([10]),name="b")
# 此处参数带的10,可以让y的结果是一个shape为[1,10]的行向量,正好对应10分类。
# 定义前向计算
forward = tf.matmul(x, w) + b # 前向计算
# 进行结果分类
pred = tf.nn.softmax(forward) # 使用S型函数进行Softmax分类
# 损失函数的选择
loss_fuction = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred),
reduction_indices=1)) # 交叉熵 / 对数损失函数
1.占位符的确定
- 依然需要的是两个占位符,一个是特征向量也就是x(image),另一个是标签向量,也就是y(label),[None, 784]其中的None表示不确定每次输入的行的数目,这样可以方便后续参数的改动,而784代表的是一幅图形所需要的空间大小(28*28)。
2.变量的确定
- 关于变量,它类似于线性代数的样式,只不过每一个参数变量都变成了矩阵,所以也依然需要
w矩阵 和b矩阵 ,其中,先说w矩阵 这个矩阵最后需要和输入的每一个x向量相乘,而输入的x向量的形状是[1,784],而要满足矩阵的叉乘运算的规则,这就要求w矩阵的行数也为784,又因为本次问题是10分类的问题,最后我们表示标签的方法是用独热码的形式来表示,所以需要最后得到的结果是一个形状为[1,10]的行向量,所以w矩阵的形状需要被确定为[784,10],这里使用的是tf.random_normal() 对矩阵进行数据的初始化。 - 最后b矩阵本质上是偏振值,出于矩阵之间加法的要求,所以b矩阵也得是一个[1,10]的行向量。其中
tf.zeros([10]) 这个函数实现的是对给定形状的n维数组赋0的初值。
3.前向计算和结果分类(Softmax分类)
- 前向计算:计算出预测值的原始向量。
- 由于在逻辑回归问题当中,我们更倾向于使用比例值(处在0~1中间的值)来表示具体的量,所以这里就要用到一种对结果进行分类的方法,那就是Softmax分类。
- 通过softmax分类,我们能得到一个值总和为1的向量。
4.损失函数的选择。
- 先直接看使用MSE的均方差损失函数的方法,由于我们做Softmax分类的时候,使用了sigmod函数,所以当把s函数代入MSE函数式中,得到的函数特性是,该函数是非凸函数,存在多个极小值的点,而对于这种函数,如果使用梯度下降法的话,会容易导致陷入局部最优解当中,而这显然不是我们想要达到的结果,所以显然均方差的损失函数,在这个问题上就不适用了,因为后续优化的方案我们依然选择的是梯度下降法,争取最小化损失函数的模型。
对数损失函数。
- 函数模型实现的效果就是,当标签值为0的时候,预测值越接近0的损失值就越小;当标签值为1的时候,预测值越接近1的损失值就越小。
交叉熵损失函数。
交叉熵损失和对数损失之间的关系
-
交叉熵中未知真实分布\large p(x)相当于对数损失中的真实标记\large y,寻找的近似分布\large q(x)相当于我们的预测值。如果把所有样本取均值就把交叉熵转化成了对数损失函数。 -
在该模型中我们本质上使用的是对数损失函数,而之所以提出交叉熵损失函数的原因是对数损失函数是交叉熵损失函数把所有样本取均值所得到的结果,所以用交叉熵的函数式来写,最后再取所有样本的均值就可以实现对数损失函数。这样来构造函数会比较方便。 -
loss_fuction = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1)) # 交叉熵 / 对数损失函数 reduction_indices=1 表示按行来计算,简单说就是求总和是取一整行的数据求一次总和。
reduction_indices的用法
3. 模型的训练。
# 超参数的定义
learning_rate = 0.01 # 学习率
train_epoches = 50 # 训练的轮数
batch_size = 100 # 单次训练的样本数(批次大小)
display_step = 1 # 显示的粒度
total_batch = int(mnist.train.images.shape[0]/batch_size)
# 优化器的选择,此处依然使用梯度下降优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_fuction)
# 定义准确率(用于验证集验证情况的反馈)
# 检查预测的类别与实际类别的匹配情况
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)
all_loss = []
all_accuracy = []
# 训练模型
for epoch in range(train_epoches):
for batch in range(total_batch):
xs, ys = mnist.train.next_batch(batch_size) # 读取批次的数据
sess.run(optimizer,feed_dict={x: xs, y: ys}) # 执行批次训练
# 所有的批次完成之后,使用验证数据集来计算误差和准确率; 验证集没有进行分批而是一次型传入
loss, acc = sess.run([loss_fuction, accuracy],
feed_dict={x: mnist.validation.images,y: mnist.validation.labels})
all_loss.append(loss)
all_accuracy.append(acc)
# 打印训练过程中的详细信息(损失值和准确率)
if (epoch+1) % display_step == 0:
print("Train Epoch:", "%02d" % (epoch+1), "Loss=", "{:.9f}".format(loss),\
"Accuracy=", "{:.4f}".format(acc))
print("Train Finished")
# 损失值和准确值的可视化
plt.plot(all_loss, label="loss")
plt.plot(all_accuracy, label="accuracy")
plt.legend(loc=2)
超参数的定义
- 机器学习的模型必备的参数:
- 训练的轮迭代次(train_epoches)
- 学习率(learn_rate) 一般取 [0.01,0.1]
- 训练过程的监视所需的显示粒度(dispaly_step)
- 这里由于样本的容量偏大,所以,不可能一次性把训练集中的所有数据都进行训练,所以需要分成小批次多次执行。因此有:(应为整型int)
- 每一批次的训练量(batch_size)
- 总共需要的训练批次(total_batch)
优化器的选择
依然选择梯度下降优化器。
准确率的设计(用来监控训练效果的另一个参数)
tf.equal() 实现的是逐个比较(一一对应)两个输入源的值,如果相同的话,就返回True,如果不相同的话,就返回False。
tf.equal()
- 对bool值的浮点数的转化,True转化成1,False转化成0。而对准确率的计算方法,则是求所有的匹配值数组的和后再求均值,即准确值就是在所有的验证集合中,预测成功的个数占总个数的比例。
模型训练的主体:
- 数据的读取使用的是MNIST自带的
next_batch()函数
4. 模型评估,模型应用与可视化。
# 损失值和准确值的可视化
plt.plot(all_loss, label="loss")
plt.plot(all_accuracy, label="accuracy")
plt.legend(loc=2)
# 评估模型
# 测试集
accu_test = sess.run(accuracy,
feed_dict={x: mnist.test.images, y: mnist.test.labels})
print("Test Accuracy: ", accu_test)
# 验证集
accu_validation = sess.run(accuracy,
feed_dict={x: mnist.validation.images, y: mnist.validation.labels})
print("Validation Accuracy: ", accu_validation)
# 训练集
accu_train = sess.run(accuracy,
feed_dict={x: mnist.train.images, y: mnist.train.labels})
print("Train Accuracy: ", accu_train)
plt.show()
# 在建立模型并进行训练后,若认为准确率可以接受,则可以使用此模型进行预测
# 模型应用与可视化
# 由于pred的预测结果是one-hot编码格式,所以需要转换为0~9数字
prediction_result = sess.run(tf.argmax(pred, 1),
feed_dict={x: mnist.test.images})
print(prediction_result[0:10])
# 定义可视化函数
def plot_images_labels_prediction(images, # 图像的列表
labels, # 标签的列表
prediction, # 预测值的列表
index, # 从第index个下标开始显示
num=10): # 默认一次显示10幅
# 获取图表,并设置尺寸。
fig = plt.gcf() # 获取当前的图表,Get Current Figure
fig.set_size_inches(10, 12) # 1英寸等于2.54cm
# 控制展示的峰值
if num > 25:
num = 25
# 处理每一个要展示的子图
for i in range(0, num):
ax = plt.subplot(5, 5, i+1) # 获取当前要处理的子图
ax.imshow(np.reshape(images[index], (28, 28)), # 显示第index个图像
cmap="binary")
title = "label=" + str(np.argmax(labels[index])) # 构建图上要显示的title信息
if len(prediction) > 0: # 如果有预测信息的话
title += ",predict=" + str(prediction[index])
ax.set_title(title,fontsize=10) # 显示图上的title信息
ax.set_xticks([])
ax.set_yticks([])
index += 1
plt.show()
print("_________________________________分割线_____________________________________")
plot_images_labels_prediction(
mnist.test.images,
mnist.test.labels,
prediction_result, 10, 10)
print("_________________________________分割线_____________________________________")
plot_images_labels_prediction(
mnist.test.images,
mnist.test.labels,
prediction_result, 10, 25)
关于损失值和准确值的可视化
这一部分是我自己加上去的内容,关键是想看看损失值和准确值的变化趋势。
模型的评估
- 模型的评估的本质是待训练结束后,把最后的一个集合,也就是测试集的数据代入求取准确度,用准确度来衡量模型的训练结果。
- 从对比的角度来说,也可以分别直接代入三个集合的数据来查看。
- 本质上是用会话端口运行求准确值的节点,然后需要输入 (x,y) 两个占位符的具体数据。
详细解析模型应用的可视化函数
- 此处本质上是使用了matplotlib的可视化功能,把原始标签、预测结果、实际图形,以一种合理的形式展示出来。
参数的考虑
- 由于需要展示实际图形,所以需要传入images的列表。
- 由于需要展示原始标签,所以需要传入labels的列表。
- 由于考虑到需要展示预测标签,所以需要传入预测结果的列表。
- 考虑到输出需要控制输出的图形的个数,所以可以定义一个可选参数用来控制输出的图像的个数。
- 考虑到用户不一定想从头看,可能想任意指定具体的位置来查看,所以可以定义一个可以指定具体的展示的开始下标的参数。
陌生函数的介绍
plt.gcf()
- 它的作用就是获得一张画布,我们称之为figure。
- 可以把figure理解为pyplot的一种数据结构,而该语句就是定义了这样的一个类似画布的数据结构,有了画布才能在上面画图形。
- 对于图片中的两句代码,如果没有上一句定义的画布的话,直接进行展示是不会触发图像的产生的。
fig.set_size_inches()
- 这是pyplot中自带的一个函数,用于设置这块定义好的画布的尺寸大小,只不过单位是inches(1英寸等于2.54cm)。
- 用面向对象的角度来说的话,如果把figure当成一个类来看的话,fig就是这个类的一个对象,然后set_size_inches()就是这个类的一个方法,它的作用是设置这个类尺寸的状态,可以这样来理解。
ax = plt.subplot(5, 5, i+1)
使用subplot绘制子图的一些特点
- 当subplot函数带第3个参数的时候,就代表当前创建的只是一个子图而已,第三个变量表示的是子图组中子图的序号,从1开始。
- 当只有两个参数的时候,这两个参数的乘积就表示一共创建的子图的数量,第一个参数是总行数,第二个参数是总列数。建子图的时候,逐行创建,一行建完后,再接下一行。
- 可以通过赋值方式把具体的一个子图赋值给一个子图变量,采用是的对子图组取行,列的索引来赋值。
- 子图兼容多类图形的创建函数,如直线图(plot),如散点图(scatter)等,直接用子图索引创建的话,创建的图形会直接出现在子图对应的索引区域。
- 用plt来直接创建的图形,会默认绘制在执行plt代码执行时处在的最新的一个子图上。
ax.set_title(title,fontsize=10)
- 对于每一个子图,我们都可以给它加上一条字符串标题。
fontsize 控制字体大小的参数。
ax.set_xticks([]) / ax.set_yticks([])
- 这两个函数都是对坐标轴的控制函数,只不过一个针对x轴一个针对y轴而已,本质是一样的。
- 参数给定一个空列表可以实现省略坐标轴的功能。
- 它的本质是,用空列表中的多个空数据来填充坐标轴的每一个间隔,于是最后得到的效果自然也就是空的坐标轴啦。
set_xticks() python_matplotlib改变横坐标和纵坐标上的刻度(ticks)
预测数据的可视化展示
取10个: 取25个:
主要参考资料: 深度学习应用开发-TensorFlow实践----吴明晖 、李卓蓉 、金苍宏
|