TensorFlow从1到2(二)续讲从锅炉工到AI专家

2022-02-25

图像样本可视化

在原文第四部分,我们介绍了官方的入门案例MNIST,作用是识别手写数字0-9。这是一个非常基础的应用程序,相当于“Hello World!”在正常的语言学习中。

2.0中的MNIST代码解释我们先不赘述,因为2.0在Keras的帮助下抽象程度很高,代码也很简单。但它也隐藏了很多工作,很难真正理解上下文。特别是使用的样本数据也有所不同,这对于学习者来说是非常重要的部分。模型可以阅读论文、在线查找成熟结果、收集和处理数据,但没有人会提供帮助。

在原文中,我们首先介绍了MNIST的数据结构,并用一个小程序将样本中的数组数据转换成JPG图片,帮助读者了解原始数据的组织结构。

这里我们也升级了小程序,直接在屏幕上显示图片,不再保存JPG文件。这样图片看起来更快更直观。

在1.x中,程序.py用于下载和管理MNIST样本数据集。此代码已在当前官方仓库的分支中取消。为了不通过存储库,您可以在此处下载并将其放置在您的工作目录中。

在2.0中,会有一个keras。类来管理演示和模型中使用的大部分数据集,我们稍后会谈到。

MNIST 的样本数据来自 Yann LeCun 的项目网站。如果网速比较慢,可以先用下载工具下载,然后放到自己设置的data目录下,比如工作目录下的data文件夹。如果检测到现有数据,则不会重复下载。

以下是我们升级后的训练样本集的源码,代码的解释保留在评论中。如有疑问,建议去原文查看样本集数据结构的图形部分:

#!/usr/bin/env python3
# 引入mnist数据预读准备库
# 2.0之后建议直接使用官方的keras.datasets.mnist.load_data
# 此处为了同以前的讲解对比,沿用之前的引用文件
import input_data
# tensorflow 2.0库
import tensorflow as tf
# 引入绘图库
import matplotlib.pyplot as plt
# 这里使用mnist数据预读准备库检查给定路径是已经有样本数据,
# 没有的话去网上下载,并保存在指定目录
# 已经下载了数据的话,将数据读入内存,保存到mnist对象中
mnist = input_data.read_data_sets("data/", one_hot=True)
# 样本集的结构如下:
# mnist.train 训练数据集
# mnist.validation 验证数据集
# mnist.test 测试数据集
# len(mnist.train.images)=55000
# len(mnist.train.images[0])=784
# len(mnist.train.labels[0])=10
def plot_image(i, imgs, labels):
    # 将1维的0-1的数据转换为标准的0-255的整数数据,2维28x28的图片
    image = tf.floor(256.0 * tf.reshape(imgs[i], [28, 28]))
    # 原数据为float,转换为uint8字节数据
    image = tf.cast(image, dtype=tf.uint8)
    # 标签样本为10个字节的数组,为1的元素下标就是样本的标签值
    # 这里使用argmax方法直接转换为0-9的整数
    label = tf.argmax(labels[i])
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])
    # 绘制样本图
    plt.imshow(image)
    # 显示标签值
    plt.xlabel("{}".format(label))
def show_images(num_rows, num_cols, images, labels):
    num_images = num_rows*num_cols
    plt.figure('Train Samples', figsize=(2*num_cols, 2*num_rows))
    # 循环显示前num_rows*num_cols副样本图片
    for i in range(num_images):
        plt.subplot(num_rows, num_cols, i+1)
        plot_image(i, images, labels)
    plt.show()
# 显示前4*6=24副训练集样本
show_images(4, 6, mnist.train.images, mnist.train.labels)

请注意,此代码仅用于转换样本集可视化。 2.0 新功能,这里只体现了取消和.run()。目的只是继续原来的解释,让图片直接显示而不是保存为图片文件,升级到2.0的执行环境。

样本集显示如下:

2.0 模型构建

在原文第四篇中,我们使用了一个不切实际的线性回归模型进行手写数字识别。这样做可以简化中间层,从而能够使用视觉手段来解释机器视觉的数学基础。因为我们在本系列的第一部分讲过线性回归模型,这里就略过,直接说神经网络是用来解决MNIST问题的。

神经网络模型的构建是1.0中最繁琐的工作。为了解释vgg-19神经网络的使用,我们首先编写了一个复杂的辅助类,通过字符串数组的遍历自动构建复杂的神经网络模型。

在2.0中,通过高度抽象的keras构建神经网络模型非常容易。

为了帮助理解,我们先贴出1.0中使用神经网络解决MNIST问题的原代码如下:

#!/usr/bin/env python
# -*- coding=UTF-8 -*-
import input_data
mnist = input_data.read_data_sets('data/', one_hot=True)
import tensorflow as tf
sess = tf.InteractiveSession()
#对W/b做初始化有利于防止算法陷入局部最优解,
#文档上讲是为了打破对称性和防止0梯度及神经元节点恒为0等问题,数学原理是类似问题
#这两个初始化单独定义成子程序是因为多层神经网络会有多次调用
def weight_variable(shape):
    #填充“权重”矩阵,其中的元素符合截断正态分布
    #可以有参数mean表示指定均值及stddev指定标准差
  initial = tf.truncated_normal(shape, stddev=0.1)
  return tf.Variable(initial)
def bias_variable(shape):
    #用0.1常量填充“偏移量”矩阵
  initial = tf.constant(0.1, shape=shape)
  return tf.Variable(initial)
#定义占位符,相当于tensorFlow的运行参数,
#x是输入的图片矩阵,y_是给定的标注标签,有标注一定是监督学习
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
#定义输入层神经网络,有784个节点,1024个输出,
#输出的数量是自己定义的,要跟第二层节点的数量吻合
W1 = weight_variable([784, 1024])
b1 = bias_variable([1024])
#使用relu算法的激活函数,后面的公式跟前一个例子相同
h1 = tf.nn.relu(tf.matmul(x, W1) + b1)
#定义第二层(隐藏层)网络,1024输入,512输出
W2 = weight_variable([1024, 512])
b2 = bias_variable([512])
h2 = tf.nn.relu(tf.matmul(h1, W2) + b2)
#定义第三层(输出层),512输入,10输出,10也是我们希望的分类数量
W3 = weight_variable([512, 10])
b3 = bias_variable([10])
#最后一层的输出同样用softmax分类(也算是激活函数吧)
y3=tf.nn.softmax(tf.matmul(h2, W3) + b3)
#交叉熵代价函数
cross_entropy = -tf.reduce_sum(y_*tf.log(y3))
#这里使用了更加复杂的ADAM优化器来做"梯度最速下降",
#前一个例子中我们使用的是:GradientDescentOptimizer
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#计算正确率以评估效果
correct_prediction = tf.equal(tf.argmax(y3,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
#tf初始化及所有变量初始化
sess.run(tf.global_variables_initializer())
#进行20000步的训练
for i in range(20000):
    #每批数据50组
  batch = mnist.train.next_batch(50)
  #每100步进行一次正确率计算并显示中间结果
  if i0 == 0:
    train_accuracy = accuracy.eval(feed_dict={
        x:batch[0], y_: batch[1]})
    print "step %d, training accuracy %g"%(i, train_accuracy)
    #使用数据集进行训练
  train_step.run(feed_dict={x: batch[0], y_: batch[1]})
#完成模型训练给出最终的评估结果
print "test accuracy %g"%accuracy.eval(feed_dict={
    x: mnist.test.images, y_: mnist.test.labels})

总结以上1.x版本的MNIST代码中的工作:

如果你理解我总结的几点,请继续阅读2.0的实现:

p>

#!/usr/bin/env python3
# 引入mnist数据预读准备库
# 2.0之后建议直接使用官方的keras.datasets.mnist.load_data
# 此处为了同以前的讲解对比,沿用之前的引用文件
import input_data
# tensorflow库
import tensorflow as tf
# tensorflow 已经内置了keras
from tensorflow import keras
# 引入绘图库
import matplotlib.pyplot as plt
# 这里使用mnist数据预读准备库检查给定路径是已经有样本数据,
# 没有的话去网上下载,并保存在指定目录
# 已经下载了数据的话,将数据读入内存,保存到mnist对象中
mnist = input_data.read_data_sets("data/", one_hot=True)
# 样本集的结构如下:
# mnist.train 训练数据集
# mnist.validation 验证数据集
# mnist.test 测试数据集
# len(mnist.train.images)=55000
# len(mnist.train.images[0])=784
# len(mnist.train.labels[0])=10
def plot_image(i, imgs, labels, predictions):
    # 将1维的0-1的数据转换为标准的0-255的整数数据,2维28x28的图片
    image = tf.floor(256.0 * tf.reshape(imgs[i], [28, 28]))
    # 原数据为float,转换为uint8字节数据
    image = tf.cast(image, dtype=tf.uint8)
    # 标签样本为10个字节的数组,为1的元素下标就是样本的标签值
    # 这里使用argmax方法直接转换为0-9的整数
    label = tf.argmax(labels[i])
    prediction = tf.argmax(predictions[i])
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])
    # 绘制样本图
    plt.imshow(image)
    # 显示标签值,对比显示预测值和实际标签值
    plt.xlabel("predict:{} label:{}".format(prediction, label))
def show_images(num_rows, num_cols, images, labels, predictions):
    num_images = num_rows*num_cols
    plt.figure('Predict Samples', figsize=(2*num_cols, 2*num_rows))
    # 循环显示前num_rows*num_cols副样本图片
    for i in range(num_images):
        plt.subplot(num_rows, num_cols, i+1)
        plot_image(i, images, labels, predictions)
    plt.show()
# 原文中已经说明了,当前是10个元素数组表示一个数字,
# 值为1的那一元素的索引就是代表的数字,这是分类算法决定的
# 下面是直接转换为0-9的正整数,用作训练的标签
train_labels = tf.argmax(mnist.train.labels, 1)
# 定义神经网络模型
model = keras.Sequential([
    # 输入层为28x28共784个元素的数组,节点1024个
    keras.layers.Dense(1024, activation='relu', input_shape=(784,)),
    keras.layers.Dense(512, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])
# 编译模型
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
# 使用训练集数据训练模型
model.fit(mnist.train.images, train_labels, epochs=3)
# 测试集的标签同样转成0-9数字
test_labels = tf.argmax(mnist.test.labels, 1)
# 使用测试集样本验证识别准确率
test_loss, test_acc = model.evaluate(mnist.test.images, test_labels)
print('nTest accuracy:', test_acc)
# 完整预测测试集样本
predictions = model.predict(mnist.test.images)
# 图示结果的前4*6个样本
show_images(4, 6, mnist.test.images, mnist.test.labels, predictions)

代码说明

通常我直接在注释中解释程序,这次是个例外,因为我们需要从大图中看代码的结构。

这些代码行定义了神经网络模型:

# 定义神经网络模型
model = keras.Sequential([
    # 输入层为28x28共784个元素的数组,节点1024个
    keras.layers.Dense(1024, activation='relu', input_shape=(784,)),
    keras.layers.Dense(512, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])

每条线实际上代表一个神经网络的一个节点。第一行指定了输入数据的形式,即可以有未知数量的样本,每个样本为784字节(28×28)。其实这个输入样本不能指定shape,在没有指定的情况下,Keras会自动识别训练数据集的形状,并自动将模型输入与训练集的形状进行匹配,只是这个习惯不一定好,除了效率问题,样本集错误时,模型定义无济于事,开发者提前发现问题,因此建议产品模型在模型中指定输入数据类型。

除第一层外,后续各层无需指定输入样本形状。 Keras 会自动匹配两个相邻层的数据。这为开发人员节省了大量的手动计算,并且不易出错。

最后,激活函数的选择变成了一个参数。整个代码看起来非常简洁。

然后在编译模型的代码中,直接指定Keras中预定义的“”损失函数和“adam”优化算法。一个函数通过几个参数选择来完成这部分工作:

# 编译模型
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

对于原来复杂的训练循环,2.0是最彻底的优化,只有一行代码:

# 使用训练集数据训练模型
model.fit(mnist.train.images, train_labels, epochs=3)

@ >

使用测试集数据对模型进行评估也只需要一行代码,这里就不提了,可以看上面的完整代码。

可以想象,2.0正式发布后,模型构建、训练、评估的工作量将大大减少,实验模型创新中会涌现出许多新算法。机器学习领域将再次掀起普及浪潮。

在这个版本的代码中,我们还稍微修改了样本可视化部分的程序,将原来训练集样本的显示改为测试集样本的显示。主要目的是为图像识别结果添加一个参数。图像识别结果与数据相同。集合的注释显示在图片下方以供比较。

程序运行时,控制台输出如下:

$ python3 mnist-show-predict-pic-v1.py 
Extracting data/train-images-idx3-ubyte.gz
Extracting data/train-labels-idx1-ubyte.gz
Extracting data/t10k-images-idx3-ubyte.gz
Extracting data/t10k-labels-idx1-ubyte.gz
Epoch 1/3
55000/55000 [==============================] - 17s 307us/sample - loss: 0.1869 - accuracy: 0.9420
Epoch 2/3
55000/55000 [==============================] - 17s 304us/sample - loss: 0.0816 - accuracy: 0.9740
Epoch 3/3
55000/55000 [==============================] - 16s 298us/sample - loss: 0.0557 - accuracy: 0.9821
10000/10000 [==============================] - 1s 98us/sample - loss: 0.0890 - accuracy: 0.9743
Test accuracy: 0.9743

最终结果表明,模型经过3次训练迭代后,使用测试集数据进行验证,手写数字识别正确率为97.43%。

程序最终会显示前24个测试集数据的图片、预测结果和标注信息对比:

(待续……)

分类:

技术要点:

相关文章:

© 版权声明
THE END
喜欢就支持一下吧
点赞12赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容