教程 | TensorFlow 1.11 教程 —— 研究和实验 —— 自定义训练:完整教程(9.20 ver.)

译自:TensorFlow 官方教程

本教程使用机器学习来按物种分类鸢尾花。它使用 TensorFlow 的 eager execution 来:

  1. 构建模型
  2. 在样本数据上训练此模型
  3. 使用模型对未知数据进行预测

TensorFlow 编程

本教程使用这些高级 TensorFlow 概念:

  • 启用 eager execution 开发环境
  • 使用 Datasets API 导入数据
  • 使用 TensorFlow 的 Keras API 构建模型和层

本教程的结构与许多 TensorFlow 程序类似:

  • 导入并解析数据集
  • 选择模型类型
  • 训练模型
  • 评估模型的有效性
  • 使用训练的模型做出预测

设置程序

配置导入和 eager execution

导入所需的 Python 模块(包括 TensorFlow),并为此程序启用 eager execution。eager execution 使 TensorFlow 立即评估操作,返回具体值,而不是创建稍后执行的 计算图。如果你习惯使用 REPL 或 python 交互控制台,这种感觉很熟悉。Eager execution 在 Tensorlow> = 1.8 版本中可用。

一旦启用了 eager execution,就无法在同一程序中禁用它。有关详细信息,请参阅 eager execution 指南

from __future__ import absolute_import, division, print_function

import os
import matplotlib.pyplot as plt

import tensorflow as tf
import tensorflow.contrib.eager as tfe

tf.enable_eager_execution()

print("TensorFlow version: {}".format(tf.VERSION))
print("Eager execution: {}".format(tf.executing_eagerly()))
TensorFlow version: 1.11.0-rc0
Eager execution: True

鸢尾花(Iris)分类问题

想象一下你是一名植物学家,正在寻找一种自动化的方法来对你发现的每一朵鸢尾花进行分类。机器学习提供了许多算法来对花进行统计分类。例如,复杂的机器学习程序可以根据照片对花进行分类。我们做得简单一点 —— 根据花萼花瓣的长度和宽度来对鸢尾花进行分类。

鸢尾花属包含约 300 种,但我们的程序仅对以下三种进行分类:

  • Iris setosa
  • Iris virginica
  • Iris versicolor

在这里插入图片描述

幸运的是,有人已经用花萼和花瓣测量数据创建了一个包含 120 个鸢尾花的 数据集。这是一机器学习初学者解决分类问题常用的经典数据集。


导入并解析训练数据集

下载数据集文件并将其转换为可供 Python 程序使用的结构。

下载数据集

使用 tf.keras.utils.get_file 函数下载训练数据集文件,函数返回下载文件的文件路径。

train_dataset_url = "http://download.tensorflow.org/data/iris_training.csv"

train_dataset_fp = tf.keras.utils.get_file(fname=os.path.basename(train_dataset_url),
                                           origin=train_dataset_url)

print("Local copy of the dataset file: {}".format(train_dataset_fp))
Downloading data from http://download.tensorflow.org/data/iris_training.csv
8192/2194 [================================================================================================================] - 0s 0us/step
Local copy of the dataset file: /root/.keras/datasets/iris_training.csv

检查数据

数据集 iris_training.csv 是一个纯文本文件,用于存储格式为逗号分隔值(CSV)的表格数据。使用 head -n5 命令查看前五个条目:

!head -n5 {train_dataset_fp}
120,4,setosa,versicolor,virginica
6.4,2.8,5.6,2.2,2
5.0,2.3,3.3,1.0,1
4.9,2.5,4.5,1.7,2
4.9,3.1,1.5,0.1,0

在数据集的视图中,请注意以下内容:

  1. 第一行是包含有关数据集的信息的标题:
    - 总共有 120 个样本。每个样本都有四个特征和三个可能的标签名称之一。
  2. 后续行是数据记录,每行一个样本,其中:
    - 前四个字段是 特征:这些是样本的特征。字段包含表示花卉测量值的浮点数。
    - 最后一列是 标签:这是我们想要预测的值。对于此数据集,它是与花名称对应的整数值 0、1 或 2。
# CSV 文件中的列顺序
column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']

feature_names = column_names[:-1]
label_name = column_names[-1]

print("Features: {}".format(feature_names))
print("Label: {}".format(label_name))
Features: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
Label: species

每个标签都与字符串名称相关联(例如,“setosa”),但机器学习通常依赖于数值,因此将标签映射到名称表示,例如:

  • 0:Iris setosa
  • 1:Iris versicolor
  • 2:Iris virginica

有关特征和标签的更多信息,请参阅 机器学习速成课程的 ML 术语部分

class_names = ['Iris setosa', 'Iris versicolor', 'Iris virginica']

创建一个 tf.data.Dataset

TensorFlow 的 Dataset API 可以处理许多将数据加载到模型中的常见情况。这是一个高级 API,用于读取数据并将其转换为用于训练的表单。有关详细信息,请参阅 Datasets 快速入门 指南。

由于数据集是 CSV 格式的文本文件,因此请使用 make_csv_dataset 函数将数据解析为合适的格式。由于此函数为训练模型生成数据,因此默认行为会打乱数据(shuffle=True, shuffle_buffer_size=10000),并一直重复数据集(num_epochs=None)。我们还设置了 batch_size 参数。

batch_size = 32

train_dataset = tf.contrib.data.make_csv_dataset(
    train_dataset_fp,
    batch_size, 
    column_names=column_names,
    label_name=label_name,
    num_epochs=1)

make_csv_dataset 函数返回 tf.data.Dataset 对象的 (features, label) 对,其中 features 是一个字典:{'feature_name': value}

启用 eager execution 后,这些 Dataset 对象是可迭代的。我们先来看看特征:

features, labels = next(iter(train_dataset))

features
OrderedDict([('sepal_length',
              <tf.Tensor: id=60, shape=(32,), dtype=float32, numpy=
              array([6.6, 6.1, 5.8, 6. , 7.7, 6.2, 7.7, 5.5, 5.1, 5.8, 5.6, 6.3, 6.4,
                     5.5, 5.8, 6.4, 4.6, 5.3, 5.1, 4.8, 4.6, 6.4, 5.5, 7.7, 5. , 6.1,
                     5.1, 5.7, 5.4, 4.9, 6.1, 5.7], dtype=float32)>),
             ('sepal_width',
              <tf.Tensor: id=61, shape=(32,), dtype=float32, numpy=
              array([3. , 3. , 4. , 3. , 3.8, 2.2, 2.8, 2.4, 3.8, 2.8, 2.9, 3.3, 2.8,
                     3.5, 2.7, 3.1, 3.4, 3.7, 3.8, 3.4, 3.1, 2.7, 2.4, 2.6, 3.6, 2.8,
                     3.7, 3.8, 3.9, 3. , 2.6, 2.8], dtype=float32)>),
             ('petal_length',
              <tf.Tensor: id=58, shape=(32,), dtype=float32, numpy=
              array([4.4, 4.9, 1.2, 4.8, 6.7, 4.5, 6.7, 3.8, 1.5, 5.1, 3.6, 4.7, 5.6,
                     1.3, 5.1, 5.5, 1.4, 1.5, 1.9, 1.6, 1.5, 5.3, 3.7, 6.9, 1.4, 4. ,
                     1.5, 1.7, 1.3, 1.4, 5.6, 4.1], dtype=float32)>),
             ('petal_width',
              <tf.Tensor: id=59, shape=(32,), dtype=float32, numpy=
              array([1.4, 1.8, 0.2, 1.8, 2.2, 1.5, 2. , 1.1, 0.3, 2.4, 1.3, 1.6, 2.2,
                     0.2, 1.9, 1.8, 0.3, 0.2, 0.4, 0.2, 0.2, 1.9, 1. , 2.3, 0.2, 1.3,
                     0.4, 0.3, 0.4, 0.2, 1.4, 1.3], dtype=float32)>)])

注意,特征被组合在一起。每个样本的行字段都附加到相应的特征数组。更改 batch_size 以设置存储在这些特征数组中的样本数。

你可以通过绘制批量中的一些特征来查看某些聚类:

plt.scatter(features['petal_length'],
            features['sepal_length'],
            c=labels,
            cmap='viridis')

plt.xlabel("Petal length")
plt.ylabel("Sepal length");

在这里插入图片描述

要简化模型构建步骤,请建立一个函数将特征字典重新打包为具有形状的单个数组:(batch_size, num_features)

该函数使用 tf.stack 方法,该方法从张量列表中获取值,并在指定维度创建张量组合。

def pack_features_vector(features, labels):
  """将特征打包为单个数组"""
  features = tf.stack(list(features.values()), axis=1)
  return features, labels

然后使用 tf.data.Dataset.map 方法将每个 (features,label) 对中的 features 包装到训练数据集中:

train_dataset = train_dataset.map(pack_features_vector)

现在 Dataset 的特征元素是具有形状 (batch_size, num_features) 的数组。我们来看前几个样本:

features, labels = next(iter(train_dataset))

print(features[:5])
tf.Tensor(
[[6.3 2.7 4.9 1.8]
 [6.7 3.3 5.7 2.1]
 [6.8 2.8 4.8 1.4]
 [5.  3.5 1.3 0.3]
 [5.1 3.8 1.9 0.4]], shape=(5, 4), dtype=float32)

选择模型类型

为什么建模

模型 是特征和标签之间的关系。对于鸢尾花分类问题,该模型定义了花萼和花瓣测量数据与预测的鸢尾花种类之间的关系。一些简单的模型可以用几行代数来描述,但是复杂的机器学习模型具有很多难以概括的参数。

你能否在不使用机器学习的情况下确定四种特征与鸢尾花物种之间的关系?也就是说,你可以使用传统的编程技术(例如,很多条件语句)来创建模型吗?如果你对数据集进行了足够长时间的分析,以确定特定物种的花瓣和花萼测量值之间的关系。但在更复杂的数据集上,这将变得很困难,也许是不可能的。良好的机器学习方法可以为你确定模型。如果你将足够多的代表性样本提供给正确的机器学习模型类型,程序将为你找出关系。

选择模型

我们需要选择训练的模型。模型有很多类型,挑选一个好的模型需要有足够的经验。本教程使用神经网络来解决鸢尾花分类问题。神经网络 可以找到特征和标签之间复杂的关系。它是一个高度结构化的图形,组织成一个或多个 隐藏层。每个隐藏层由一个或多个 神经元 组成。神经网络也有不同的类型,本程序使用密集(全连接)神经网络:一层中的神经元接收来自上一层每个神经元的输入连接。例如,下图说明了一个密集神经网络,包括一个输入层,两个隐藏层和一个输出层:
在这里插入图片描述

当对上图中的模型进行训练并输入未标记的样本时,它产生三个预测:该花是给定的鸢尾花物种的可能性。这种预测称为 推断。对于此示例,输出预测的总和为 1.0。在上图中,预测分别为:0.02 概率为山鸢尾,0.95 概率为变色鸢尾,0.03 概率为维吉尼亚鸢尾。这意味着模型以 95% 的概率预测未标记的样本花是变色鸢尾。

使用 Keras 建立模型

TensorFlow tf.keras API是建立模型和层的首选方式,它使得建立模型和实验变得容易。

tf.keras.Sequential 模型是层的线性堆叠。它的构造函数的参数为一个层实例列表,在这种情况下,两个 Dense 层各有 10 个节点,输出层的 3 个节点代表我们的标签预测。第一层的 input_shape 参数对应于数据集中的特征数,并且是必需的。

model = tf.keras.Sequential([
  tf.keras.layers.Dense(10, activation=tf.nn.relu, input_shape=(4,)),  # 需要输入形状
  tf.keras.layers.Dense(10, activation=tf.nn.relu),
  tf.keras.layers.Dense(3)
])

激活函数 决定层中的每个节点的输出形状。非线性激活很重要,没有它们模型将等同于单个层。有许多可用的 激活函数ReLU 常用与隐藏层。

隐藏层和神经元的理想数量取决于问题和数据集。像机器学习的许多方面一样,选择神经网络的最佳形状需要知识和实验相结合。根据经验,增加隐藏层和神经元的数量通常会创建一个更强大的模型,但这需要更多数据来进行有效的训练。

使用模型

让我们快速浏览一下这个模型对一批特征做了哪些事情:

predictions = model(features)
predictions[:5]
<tf.Tensor: id=207, shape=(5, 3), dtype=float32, numpy=
array([[ 0.7644639 , -2.2838936 , -0.26812318],
       [ 0.56341636, -1.7900109 , -0.2419351 ],
       [ 0.26537713, -0.6242165 , -0.40686837],
       [ 0.52195054, -1.6335115 , -0.25095394],
       [ 0.31738186, -0.8588741 , -0.40236714]], dtype=float32)>

这里,每个样本都返回对应每个类的对数。

要将这些对数转换为每个类的概率,需要使用 softmax 函数:

tf.nn.softmax(predictions[:5])
<tf.Tensor: id=213, shape=(5, 3), dtype=float32, numpy=
array([[0.71249366, 0.0337984 , 0.25370798],
       [0.6485195 , 0.06163715, 0.28984335],
       [0.52045834, 0.21381609, 0.26572564],
       [0.6339065 , 0.07343785, 0.29265574],
       [0.55700815, 0.17179878, 0.27119303]], dtype=float32)>

使用 tf.argmax 可以给出预测的类索引。但是,该模型还没有经过训练,所以这些都不是很好的预测。

print("Prediction: {}".format(tf.argmax(predictions, axis=1)))
print("    Labels: {}".format(labels))
Prediction: [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]
    Labels: [2 1 0 1 0 1 1 0 0 1 2 0 0 2 2 2 0 0 0 2 0 2 2 2 2 2 2 2 2 0 0 1]

训练模型

训练 是模型逐渐优化(学习数据集)的阶段。目标是充分了解训练数据集的结构,以便对未见过的数据进行预测。如果你对训练数据集了解得太多,那么预测仅适用于它见过的数据,并且无法泛化。这个问题被称为 过拟合 ,就像记忆答案而不是理解如何解决问题。

鸢尾花分类问题是 监督机器学习 的一个例子:模型使用包含标签的样本训练。在 无监督机器学习 中,样本不包含标签,相反,模型通常在特征中找到模式。

定义损失和梯度函数

训练和评估阶段都需要计算模型的 损失。这可以衡量模型预测与真实标签的关系,换句话说,模型的性能有多糟糕。我们希望最小化或最优化该值。

我们的模型将使用 tf.keras.losses.categorical_crossentropy 函数计算其损失,该函数参数为模型的类概率预测和真实标签,返回所有样本的平均损失。

def loss(model, x, y):
  y_ = model(x)
  return tf.losses.sparse_softmax_cross_entropy(labels=y, logits=y_)


l = loss(model, features, labels)
print("Loss test: {}".format(l))
Loss test: 1.127718210220337

使用 tf.GradientTape 上下文来计算用于优化模型的 梯度。有关此更多示例,请参阅 eager execution 指南

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return loss_value, tape.gradient(loss_value, model.trainable_variables)

创建一个优化器

优化器 应用计算的梯度至模型中的变量,以最小化 loss 函数。你可以将损失函数视为曲面,我们希望通过四处走动找到它的最低点。梯度指向最陡上升的方向,所以我们将以相反的方向行进并向下移动。通过迭代计算每批量的损失和梯度,我们将在训练期间调整模型。逐渐地,模型将找到权重和偏差的最佳组合,以最小化损失。损失越低,模型的预测越好。
在这里插入图片描述
TensorFlow 有许多可用于训练的 优化算法。本模型使用 tf.train.GradientDescentOptimizer 实现 随机梯度下降(SGD)算法。learning_rate 设置每次迭代减小的步长。这是一个超参数,通常需要调整以获得更好的结果。

让我们设置优化器和 global_step 计数器:

optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)

global_step = tf.train.get_or_create_global_step()

我们将使用它来计算单个优化步骤:

loss_value, grads = grad(model, features, labels)

print("Step: {}, Initial Loss: {}".format(global_step.numpy(),
                                          loss_value.numpy()))

optimizer.apply_gradients(zip(grads, model.variables), global_step)

print("Step: {},         Loss: {}".format(global_step.numpy(),
                                          loss(model, features, labels).numpy()))
Step: 0, Initial Loss: 1.127718210220337
Step: 1,         Loss: 1.1093873977661133

训练循环

随着所有部件到位,模型已准备好进行训练!训练循环将数据集样本提供给模型,以帮助其做出更好的预测。以下代码块设置了训练步骤:

  1. 每个周期进行迭代。遍历整个数据集一次为一个周期。
  2. 在一个周期内,在训练中迭代每个样本,Dataset 获取它的特征(x)和标签(y)。
  3. 使用样本的特征,进行预测并将其与标签进行比较。测量预测的不准确性,并使用它来计算模型的损失和梯度。
  4. 使用优化器更新模型的变量。
  5. 跟踪一些统计数据用于可视化。
  6. 重复每个周期。

num_epochs 变量是遍历数据集的次数。与直觉相反,长时间训练模型并不能保证得到更好的模型。num_epochs 是一个可以调整的超参数。选择正确的数字通常需要经验和实验。

# 保存结果用于绘制
train_loss_results = []
train_accuracy_results = []

num_epochs = 201

for epoch in range(num_epochs):
  epoch_loss_avg = tfe.metrics.Mean()
  epoch_accuracy = tfe.metrics.Accuracy()

  # 训练循环 - 使用的批量大小为 32
  for x, y in train_dataset:
    # 优化模型
    loss_value, grads = grad(model, x, y)
    optimizer.apply_gradients(zip(grads, model.variables),
                              global_step)

    # 跟踪进度
    epoch_loss_avg(loss_value)  # 添加当前批量损失
    # 比较预测和真实标签
    epoch_accuracy(tf.argmax(model(x), axis=1, output_type=tf.int32), y)

  # 结束一个周期
  train_loss_results.append(epoch_loss_avg.result())
  train_accuracy_results.append(epoch_accuracy.result())
  
  if epoch % 50 == 0:
    print("Epoch {:03d}: Loss: {:.3f}, Accuracy: {:.3%}".format(epoch,
                                                                epoch_loss_avg.result(),
                                                                epoch_accuracy.result()))
Epoch 000: Loss: 1.044, Accuracy: 30.000%
Epoch 050: Loss: 0.721, Accuracy: 70.000%
Epoch 100: Loss: 0.581, Accuracy: 82.500%
Epoch 150: Loss: 0.415, Accuracy: 95.833%
Epoch 200: Loss: 0.250, Accuracy: 97.500%

可视化损失函数

虽然打印模型的训练进度很有帮助,但看到这一进展通常会更有帮助。TensorBoard 是一个很好的可视化工具,但我们可以使用 matplotlib 模块创建基本图表。

解释这些图表需要一些经验,但你真的希望看到的是损失下降,准确率上升。

fig, axes = plt.subplots(2, sharex=True, figsize=(12, 8))
fig.suptitle('Training Metrics')

axes[0].set_ylabel("Loss", fontsize=14)
axes[0].plot(train_loss_results)

axes[1].set_ylabel("Accuracy", fontsize=14)
axes[1].set_xlabel("Epoch", fontsize=14)
axes[1].plot(train_accuracy_results)

在这里插入图片描述


评估模型有效性

现在模型已经过训练,我们可以对其性能进行一些统计。

评估意味着确定模型的预测有多有效。为了确定模型在鸢尾花分类中的有效性,将一些花萼和花瓣测量值输入模型,并要求模型预测它们代表的鸢尾花种类。然后将模型的预测与真实标签进行比较。

设置测试数据集

评估模型类似于训练模型。最大的区别是样本来自单独的 测试集 而不是训练集。为了公平地评估模型的有效性,用于评估模型的样本必须与用于训练模型的样本不同。

测试 Dataset 的设置类似于训练 Dataset 的设置。下载 CSV 文本文件并解析该值,然后打乱它:

test_url = "http://download.tensorflow.org/data/iris_test.csv"

test_fp = tf.keras.utils.get_file(fname=os.path.basename(test_url),
                                  origin=test_url)
Downloading data from http://download.tensorflow.org/data/iris_test.csv
8192/573 [============================================================================================================================================================================================================================================================================================================================================================================================================================================] - 0s 0us/step
test_dataset = tf.contrib.data.make_csv_dataset(
    test_fp,
    batch_size, 
    column_names=column_names,
    label_name='species',
    num_epochs=1,
    shuffle=False)

test_dataset = test_dataset.map(pack_features_vector)

在测试数据集上评估模型

与训练阶段不同,该模型仅评估测试数据单个周期。在下面的代码单元格中,我们迭代测试集中的每个样本,并将模型的预测与真实标签进行比较。这用于测量模型在整个测试集上的准确率。

test_accuracy = tfe.metrics.Accuracy()

for (x, y) in test_dataset:
  logits = model(x)
  prediction = tf.argmax(logits, axis=1, output_type=tf.int32)
  test_accuracy(prediction, y)

print("Test set accuracy: {:.3%}".format(test_accuracy.result()))
Test set accuracy: 93.333%

我们可以看到最后一批,模型通常是正确的:

tf.stack([y,prediction],axis=1)
<tf.Tensor: id=115139, shape=(30, 2), dtype=int32, numpy=
array([[1, 1],
       [2, 2],
       [0, 0],
       [1, 1],
       [1, 1],
       [1, 1],
       [0, 0],
       [2, 2],
       [1, 1],
       [2, 2],
       [2, 2],
       [0, 0],
       [2, 2],
       [1, 1],
       [1, 1],
       [0, 0],
       [1, 1],
       [0, 0],
       [0, 0],
       [2, 2],
       [0, 0],
       [1, 2],
       [2, 2],
       [1, 2],
       [1, 1],
       [1, 1],
       [0, 0],
       [1, 1],
       [2, 2],
       [1, 1]], dtype=int32)>

使用训练的模型进行预测

我们已经训练了一个模型并且“证明”它对鸢尾花物种进行分类的效果是不错的,但不是完美的。现在让我们使用训练的模型对 未标记的样本 做出一些预测。

在现实生活中,未标记的样本可以有许多不同的来源,包括应用程序,CSV 文件和数据源。目前,我们将手动提供三个未标记的样本来预测其标签。

predict_dataset = tf.convert_to_tensor([
    [5.1, 3.3, 1.7, 0.5,],
    [5.9, 3.0, 4.2, 1.5,],
    [6.9, 3.1, 5.4, 2.1]
])

predictions = model(predict_dataset)

for i, logits in enumerate(predictions):
  class_idx = tf.argmax(logits).numpy()
  p = tf.nn.softmax(logits)[class_idx]
  name = class_names[class_idx]
  print("Example {} prediction: {} ({:4.1f}%)".format(i, name, 100*p))
Example 0 prediction: Iris setosa (85.9%)
Example 1 prediction: Iris versicolor (68.1%)
Example 2 prediction: Iris virginica (72.2%)

猜你喜欢

转载自blog.csdn.net/qq_20084101/article/details/82804353