本教程将介绍如何使用机器学习的方法,对鸢(yuan一声)尾花按照种类进行分类。
教程将使用Tensorflow的eager模式来:
读者并不需要机器学习的经验,但是需要懂一些Python。
Tensorflow提供了很多的API,但建议从从以下高级TensorFlow概念开始学习:
通常情况下,TensorFlow程序会按照下面的流程编写:
教程将翻译自会使用jupyter notebook在浏览器中执行Python代码。谷歌提供了一个现成的工具Colab notebook eager模式在TensorFlow 1.7版本开始支持。
eager模式能让代码立刻运行,返回具体的结果,而不是等计算图绘制完成后再执行。一旦在代码中开启了eager模式,就不能关掉了。具体说明见eager模式指导。
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.7.0
EAGER execution: True
假设你是一个植物学家,现在要寻找一种能够对发现的鸢尾花分类的进行自动分类的方法。机器学习提供了许多算法来对花进行分类,比如,一个复杂的机器学习程序可以根据照片对花进行分类。鸢尾花问题简单一些,我们根据萼片和花瓣的长度和宽度测量值对其进行分类。
鸢尾花大约有300种,不过我们的程序只区分以下三种:
幸运的是,有人已经创建了一个有萼片和花瓣测量结果组成的120组鸢尾花数据集。这是一个对机器学习初学者的经典数据集。
使用Python下载数据集文件,并结构化数据
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))
输出Local copy of the dataset file: /home/jovyan/.keras/datasets/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
可以看到:
每个标签都会与一个字符串相关联(例如“setosa”),但是使用数字会让程序处理得更快。标签号码会映射到一个名字,比如
0
: Iris setosa1
: Iris versicolor2
: Iris virginica关于特征和标签的更多内容,请看ML Terminology section of the Machine Learning Crash Course
由于数据集是csv格式的文本,因此需要将特征和标签值解析为模型可以使用的格式。文件中的每一行都会被传给parse_csv
函数,该函数会抓取前四个特征值并将它们合并为单个tensor,然后自后一个字段会被解析为标签。最后函数返回特征tensor和标签tensor
def parse_csv(line):
example_defaults = [[0.], [0.], [0.], [0.], [0]] # sets field types
parsed_line = tf.decode_csv(line, example_defaults)
# First 4 fields are features, combine into single tensor
features = tf.reshape(parsed_line[:-1], shape=(4,))
# Last field is the label
label = tf.reshape(parsed_line[-1], shape=())
return features, label
TensorFlow的Dataset API能够处理给模型提供数据的很多常见场景。这是一个高级API,可用来读取数据并将其转换为可训练数据格式。
该程序使用tf.data..TextlineDataset来读取CSV格式的文件,然后通过parse_csv
函数解析其中的数据。tf.data.Dataset将输入流程表示为元素集合和一系列对这些元素起作用的转换。转换的方法被链接在一起或者按顺序调用--只要确保对返回的Dataset
对象保留引用即可。
如果样本是随机排列的话,训练的效果是做好的。将buffer_size
设置为大于样本数量的值,然后调用tf.data.Dataset.shuffle
打乱输入数据条目的顺序。为了加速训练的速度,将[batch size]设置为32,来每次处理32个样本。
train_dataset = tf.data.TextLineDataset(train_dataset_fp)
train_dataset = train_dataset.skip(1)
train_dataset = train_dataset.map(parse_csv)
train_dataset = train_dataset.shuffle(buffer_size=1000)
train_dataset = train_dataset.batch(32)
features, label = tfe.Iterator(train_dataset).next()
print('example features:', features[0])
print('example label:', label[0])
输出为
example features: tf.Tensor([7.7 3. 6.1 2.3], shape=(4,), dtype=float32)
example label: tf.Tensor(2, shape=(), dtype=int32)
模型是特征与标签之间的关系。对于鸢尾花分类问题来说,模型定义了萼片和花瓣测量结果与鸢尾花种类之间的关系。简单的模型可以用简单的代数来描述,但是复杂的机器学习模型有有很多难以概括的参数。
可以在不使用机器学习的情况下,确定四种特征与鸢尾花种类之间的关系吗?就是说,能否用传统的编程技术(比如大量的条件语句)来创建模型呢?如果有足够长的时间来进行研究,也许能发现这些特征值和鸢尾花物种之间的关系。不过对于更复杂的数据集来说,这样的方法会变得困难,甚至变得不可能实现。
一个好的机器学习方法能确定这个模型。如果将足够多有代表性的样本提供给正确的机器学习模型,程序就能找到特征值和B标签之间正确的关系。
已经有很多的机器学习模型存在了,需要一些经验才能为训练选择合适的模型。这里将使用神经网络来解决鸢尾花分类问题。神经网络能找出特征值和标签之间的复杂关系。它是由一个或多个隐藏层的高度结构化的计算图。每个隐藏层由一个或多个神经元组成。有好几类神经网络存在,本教程使用密集的,或者被称为完全连接的神经网络:某一层的神经元接接收来自前一层中每个神经元的输入连接。下图展示了一个由一个输入层,两个隐藏层和一个输出层组成的密集神经网络:
当训练了上图中的模型后,输入未标记的样本时,会产生三个预测,分别是该花为鸢尾属物种的可能性。这种预测被称为推断。在这个例子中,输出预测的总和是1.0。在上图中,预测结果是
0.03
: 山鸢尾0.95
: 杂色鸢尾0.02
: 维吉尼亚鸢尾也就是说,模型预测,这个没有被标记的样本时杂色鸢尾。
TensorFlow的tf.keras API时创建模型和图层的首选方式。Keras会处理将所有内容连接在一起的复杂性,这让构建模型并进行实验变得很容易。详情请见Keras文档。
tf.keras.Sequential模型是一个线性堆栈层。其初始化需要一个图层实例列表,在本教程的示例中,领个密集图层各有10个节点,一个输出图层3个代表预测标签的节点。第一层的input_shape
参数是必须的,对应于数据集中特征的数量。
model = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation='relu', input_shape=(4,)),
tf.keras.layers.Dense(10, activation='relu'),
tf.keras.layers.Dense(3)
])
激活函数(代码中的activation)决定了单个神经元到下一层的输出。这个工作模式总体上和大脑神经元的连接方式相同。有许多可用的激活函数,隐藏层通常使用修正线性单元(即代码中的relu)。
隐藏层和神经元的理想数量取决于问题和数据集。像机器学习的其他很多方面一样,神经网络的各个部分的选择需要知识和实践。作为一个经验法则,增加隐藏层和神经元的数量通常会创建一个更强大的模型,这需要更多的数据来进行有效的训练。
训练是机器学习中模型逐步优化或者说是模型学习数据集的阶段。训练的目标是充分了解训练数据集的结构,以及预测未知数据。如果通过训练对数据集了解太多,则预测仅适用于所看到的数据,而不能适用于一般的情况。这个问题被称之为过拟合--就像程序记住了答案而不是理解如何解决问题一样。
鸢尾花分类问题是监督式机器学习的一个例子,该模型从包含标签的样本中开始训练。在非监督式机器学习中,样本中不包含标签,相反,模型通常会在特征中找到模式。
训练和评估阶段都需要计算模型的损失。这可以用来衡量预测结果和期望标签之间的差距有多大,换句话说:模型的表现有多糟糕。我们想要最小化或者说优化这个差值。
我们使用tf.losses.sparsesoftmaxcross_entropy来计算损失,这个方法接受模型的预测和期望的标签作为参数。随着返回的损失值增大,预测的结果也随着变差。
def loss(model, x, y):
y_ = model(x)
return tf.losses.sparse_softmax_cross_entropy(labels=y, logits=y_)
def grad(model, inputs, targets):
with tfe.GradientTape() as tape:
loss_value = loss(model, inputs, targets)
return tape.gradient(loss_value, model.variables)
上面的代码中grad
函数调用loss
函数和tfe.GradientTape来记录用于优化模型梯度的操作。更多的例子见eager教程。
优化器将计算出的梯度应用于模型的变量以最小化loss
函数。可以把情况想象成一个曲面,通过在这个曲面上到处移动,来找到最低点
梯度指向上升速度最快的方向,所以我们将以相反的方向行进,并沿着山丘向下移动。通过迭代计算每个步骤(或学习速率)的损失和梯度,我们将在训练期间调整模型。慢慢的,模型会找到权重和偏差的最佳组合,以最大限度地减少损失。损失越低,模型的预测效果就越好。
TensorFlow有很多用于训练的优化算法。本教程中的模型使用tf.train.GradientDescentOptimizer,这个优化器实现了标准梯度下降算法(SGD)。learning_rate
为每次迭代的步长。这是一个超参数,通常通过调整该参数来获得更好的结果。
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
现在万事俱备,该模型已准备好接受训练了!训练循环将数据集样本提供给模型,以帮助它做出更好的预测。下面的代码设置了一些训练步骤:
x
)和标签(y
)。optimizer
来更新模型的变量。num_epochs
是循环访问数据集集合的次数。反过来说,长时间训练模型并不能保证模型变得更好。num_epochs
是一个可以调整的超参数,需要经验和实践才能找到正确的值。
train_loss_results = []
train_accuracy_results = []
num_epoches = 201
for epoch in range(num_epoches):
epoch_loss_avg = tfe.metrics.Mean()
epoch_accuracy = tfe.metrics.Accuracy()
for x, y in tfe.Iterator(train_dataset):
grads = grad(model, x, y)
optimizer.apply_gradients(zip(grads, model.variables), global_step=tf.train.get_or_create_global_step())
epoch_loss_avg(loss(model, x, y))
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.005, Accuracy: 50.833% Epoch 050: Loss: 0.384, Accuracy: 85.000% Epoch 100: Loss: 0.257, Accuracy: 95.833% Epoch 150: Loss: 0.183, Accuracy: 97.500% Epoch 200: Loss: 0.134, Accuracy: 97.500%
打印出训练的进度是很有用的,但是如果能更直观的看到整个过程就更好了。TensorFlow集成了一个非常好用的可视化工具TensorBoard,不过这里我会使用mathplotlib
模块创建基本的图表。
要看懂这样的图表需要一些经验,但是我们期望的是看到损失下降,准确度上升。
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)
plt.show()
现在模型已经过了训练,我们可以得到它表现的统计数据。
评估意味着确定模型预测的准确度。为了确定模型在鸢尾花分类问题上的有效性,先将一些萼片和花瓣的测量结果传递给模型,要求模型预测它们代表的鸢尾花种类,然后将预测结果与实际的标签进行比较。下表展示了一个比较准确的模型,在5次预测中正确了4次,达到了80%的准确率。
样本特征 | 标签 | 模型预测 | |||
---|---|---|---|---|---|
5.9 | 3.0 | 4.3 | 1.5 | 1 | 1 |
6.9 | 3.1 | 5.4 | 2.1 | 2 | 2 |
5.1 | 3.3 | 1.7 | 0.5 | 0 | 0 |
6.0 | 3.4 | 4.5 | 1.6 | 1 | 2 |
5.5 | 2.5 | 4.0 | 1.3 | 1 | 1 |
评估模型和训练模型是相似的,两者最大的区别是评估的样本来自单独的测试集,而不是训练集,为了公平评估模型的有效性,用于评估模型的样本必须和用于训练模型的样本不同。
设置测试数据集和设置训练数据集差不多。下载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)
test_dataset = tf.data.TextLineDataset(test_fp)
test_dataset = test_dataset.skip(1)
test_dataset = test_dataset.map(parse_csv)
test_dataset = test_dataset.shuffle(1000)
test_dataset = test_dataset.batch(32)
输出为
Downloading data from http://download.tensorflow.org/data/iris_test.csv
8192/573 [============================================================================================================================================================================================================================================================================================================================================================================================================================================] - 0s 0us/step
和训练不同,评估测试数据只需要一个周期。在下面的代码中,我们遍历测试集中的每个示例,并将模型的预测与实际的标签进行比较。这用于在整个测试集中测量模型的准确性。
test_accuracy = tfe.metrics.Accuracy()
for (x, y) in tfe.Iterator(test_dataset):
prediction = tf.argmax(model(x), axis=1, output_type=tf.int32)
test_accuracy(prediction, y)
print("Test set accuracy: {:.3%}".format(test_accuracy.result()))
输出为
Test set accuracy: 96.667%
我们已经训练了一个模型,并且“证明”了它能够对分辨鸢尾花的不同种类--尽管不是百分百准确。现在来使用训练好的模型对无标签样本做一些预测。
在实际场景中,无标签样本可能有多个来源,比如应用程序,CSV文件和feeds数据。现在,我们将手动提供三个无标签样本来预测其标签。每个种类被一个数字代表:
0
: 山鸢尾1
:杂色鸢尾2
:维吉尼亚鸢尾class_ids = ['Iris setosa', 'Iris versicolor', 'Iris virginica']
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()
name = class_ids[class_idx]
print("Example {} prediction: {}".format(i, name))
预测结果为:
Example 0 prediction: Iris setosa
Example 1 prediction: Iris versicolor
Example 2 prediction: Iris virginica
预测全部正确!
要想深入了解机器学习模型,请查看TensorFlow编程指南。