TensorFlow 机器学习秘籍(一)
原文:
annas-archive.org/md5/fdd2c78f646d889f873e10b2a8485875译者:飞龙
前言
由 Google 开发的 TensorFlow 2.x 是一个端到端的开源机器学习平台。它拥有一个全面且灵活的工具、库和社区资源生态系统,允许研究人员推动前沿的机器学习,并使开发人员能够轻松构建和部署机器学习驱动的应用程序。
本书中的独立食谱将教你如何使用 TensorFlow 进行复杂的数据计算,并让你能够深入挖掘数据,从而获得比以往更多的洞察力。在本书的帮助下,你将学习如何使用食谱进行模型训练、模型评估、回归分析、表格数据、图像与文本处理及预测等,除此之外还有更多内容。你将探索 RNN、CNN、GAN 和强化学习,每种方法都使用了 Google 最新的机器学习库 TensorFlow。通过实际案例,你将获得使用 TensorFlow 解决各种数据问题和技术的实践经验。一旦你熟悉并舒适地使用 TensorFlow 生态系统,本书还会展示如何将其应用到生产环境中。
本书结束时,你将熟练掌握使用 TensorFlow 2.x 的机器学习领域知识。你还将对深度学习有深入了解,能够在实际场景中实现机器学习算法。
本书适合人群
本书适合数据科学家、机器学习开发人员、深度学习研究人员以及具有基础统计背景的开发人员,他们希望使用神经网络并探索 TensorFlow 的结构及其新功能。为了最大化利用本书,读者需要具备 Python 编程语言的基本知识。
本书的内容
第一章,TensorFlow 2.x 入门,介绍了 TensorFlow 中的主要对象和概念。我们介绍了张量、变量和占位符,还展示了如何在 TensorFlow 中处理矩阵及各种数学运算。在本章的结尾,我们展示了如何访问本书其余部分所使用的数据源。
第二章,TensorFlow 方式,阐述了如何将 第一章,TensorFlow 入门 中的所有算法组件以多种方式连接成计算图,从而创建一个简单的分类器。在此过程中,我们涵盖了计算图、损失函数、反向传播和数据训练。
第三章,Keras,专注于名为 Keras 的高层次 TensorFlow API。在介绍了作为模型构建块的各个层之后,我们将讲解 Sequential、Functional 和 Sub-Classing API 来创建 Keras 模型。
第四章,线性回归,重点介绍如何使用 TensorFlow 探索各种线性回归技术,如 Lasso 和 Ridge、ElasticNet 和逻辑回归。我们通过扩展线性模型来介绍 Wide & Deep,并展示如何使用估算器实现每个模型。
第五章,提升树,讨论了 TensorFlow 对提升树的实现——这是最流行的表格数据模型之一。我们通过解决一个预测酒店预订取消的商业问题来演示其功能。
第六章,神经网络,涵盖了如何在 TensorFlow 中实现神经网络,从操作门和激活函数的概念开始。然后我们展示了一个浅层神经网络,并演示如何构建不同类型的层。最后,通过教 TensorFlow 神经网络玩井字游戏来结束本章。
第七章,使用表格数据进行预测,本章通过展示如何使用 TensorFlow 处理表格数据,扩展了上一章的内容。我们展示了如何处理缺失值、二进制、名义、顺序和日期特征的数据。我们还介绍了激活函数,如 GELU 和 SELU(特别适合深度架构),以及如何在数据不足时正确使用交叉验证来验证架构和参数。
第八章,卷积神经网络,通过展示如何使用图像和卷积层(以及其他图像层和功能)来扩展我们对神经网络的理解。我们展示了如何构建一个简化的 CNN 用于 MNIST 数字识别,并将其扩展到 CIFAR-10 任务中的彩色图像。我们还说明了如何将先前训练过的图像识别模型扩展到自定义任务。最后,通过解释和展示 TensorFlow 中的 StyleNet/神经风格和 DeepDream 算法来结束这一章。
第九章,循环神经网络,介绍了一种强大的架构类型(RNN),该类型在不同模式的序列数据上取得了最先进的成果;介绍的应用包括时间序列预测和文本情感分析。
第十章,变压器,专门介绍了变压器——一种深度学习模型的新类型,彻底改变了自然语言处理(NLP)领域。我们展示了如何利用其优势进行生成性和判别性任务。
第十一章,使用 TensorFlow 和 TF-Agents 进行强化学习,介绍了专门用于强化学习的 TensorFlow 库。这种结构化方法使我们能够处理从简单游戏到电子商务中的内容个性化等各种问题。
第十二章,将 TensorFlow 应用于生产环境,提供了关于将 TensorFlow 迁移到生产环境的技巧和示例,并展示了如何利用多种处理设备(例如,GPU)以及如何在多台机器上设置 TensorFlow 分布式系统。我们还展示了 TensorBoard 的多种用途,以及如何查看计算图指标和图表。最后,我们通过展示如何在 TensorFlow 中设置 RNN 模型并提供 API 的示例来结束这一章。
如何最大化利用本书
您需要对神经网络有一定的基础了解,但这不是强制性的,因为本书会从实践的角度讲解相关主题,并在需要时提供理论信息。
具备基本机器学习算法和技术的工作知识是一个加分项。您需要熟练掌握 Python 3,并且应当已经知道如何使用pip安装包,以及如何设置工作环境以便与 TensorFlow 一起使用。
环境设置将在第一章中讲解,《TensorFlow 2.x 入门》。
下载示例代码文件
本书的代码包托管在 GitHub 上:github.com/PacktPublishing/Machine-Learning-Using-TensorFlow-Cookbook。我们还在github.com/PacktPublishing/上提供了我们丰富书籍和视频目录中的其他代码包。快去看看吧!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。您可以在这里下载:static.packt-cdn.com/downloads/9781800208865_ColorImages.pdf。
使用的约定
本书中使用了若干文本约定。
CodeInText:表示文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入以及 Twitter 用户名。例如:“truncated_normal()函数始终在指定均值的两个标准差范围内选取正态值。”
代码块按如下方式设置:
import TensorFlow as tf
import NumPy as np
任何命令行输入或输出都按如下方式书写:
pip install tensorflow-datasets
粗体:表示新术语、重要词汇,或者您在屏幕上看到的单词,例如出现在菜单或对话框中的单词,也会像这样出现在文本中。例如:“TF-Agents 是一个用于强化学习(RL)的 TensorFlow 库。”
警告或重要说明会像这样显示。
小贴士和技巧会像这样显示。
联系我们
我们始终欢迎读者的反馈。
一般反馈:请通过电子邮件feedback@packtpub.com联系我们,并在邮件主题中提及书名。如果您有任何关于本书的疑问,请通过questions@packtpub.com发送邮件与我们联系。
勘误表:尽管我们已尽力确保内容的准确性,但错误是难以避免的。如果您在本书中发现错误,我们将不胜感激,您能向我们报告。请访问www.packtpub.com/submit-errata,选择您的书籍,点击“勘误提交表单”链接,并填写相关详情。
盗版:如果您在互联网上发现我们作品的任何非法复制品,我们将感激您提供其位置地址或网站名称。请通过copyright@packtpub.com与我们联系,并附上该材料的链接。
如果你有兴趣成为作者:如果你在某个领域拥有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com。
评价
请留下评价。阅读并使用本书后,为什么不在你购买书籍的站点上留下评价呢?潜在读者可以查看并参考你的公正意见来做出购买决定,我们 Packt 团队也能了解你对我们产品的看法,而我们的作者也能看到你对他们书籍的反馈。谢谢!
想了解更多关于 Packt 的信息,请访问 packtpub.com。
第一章:开始使用 TensorFlow 2.x
Google 的 TensorFlow 引擎有一种独特的解决问题方式,使我们能够非常高效地解决机器学习问题。如今,机器学习已应用于几乎所有的生活和工作领域,著名的应用包括计算机视觉、语音识别、语言翻译、医疗保健等。我们将在本书的后续页面中覆盖理解 TensorFlow 操作的基本步骤,并最终讲解如何构建生产代码。此时,本章中呈现的基础内容至关重要,它们将为你提供核心理解,帮助你更好地理解本书中其他部分的食谱。
在本章中,我们将从一些基本的食谱开始,帮助你理解 TensorFlow 2.x 的工作原理。你还将学习如何访问本书中示例所使用的数据,以及如何获取额外的资源。到本章结束时,你应该掌握以下知识:
-
理解 TensorFlow 2.x 的工作原理
-
声明和使用变量与张量
-
与矩阵合作
-
声明操作
-
实现激活函数
-
与数据源合作
-
寻找额外资源
不再多说,让我们从第一个食谱开始,它以简易的方式展示了 TensorFlow 如何处理数据和计算。
TensorFlow 的工作原理
TensorFlow 最初是由 Google Brain 团队的研究人员和工程师作为一个内部项目开始的,最初名为 DistBelief,并于 2015 年 11 月发布为一个开源框架,名称为 TensorFlow(张量是标量、向量、矩阵及更高维度矩阵的广义表示)。你可以在这里阅读关于该项目的原始论文:download.tensorflow.org/paper/whitepaper2015.pdf。在 2017 年发布 1.0 版本后,去年,Google 发布了 TensorFlow 2.0,它在继续发展和改进 TensorFlow 的同时,使其变得更加用户友好和易于使用。
TensorFlow 是一个面向生产的框架,能够处理不同的计算架构(CPU、GPU,现在还有 TPU),适用于需要高性能和易于分布式的各种计算。它在深度学习领域表现出色,可以创建从浅层网络(由少数层组成的神经网络)到复杂的深度网络,用于图像识别和自然语言处理。
在本书中,我们将呈现一系列食谱,帮助你以更高效的方式使用 TensorFlow 进行深度学习项目,减少复杂性,帮助你实现更广泛的应用并取得更好的结果。
一开始,TensorFlow 中的计算可能看起来不必要地复杂。但这背后是有原因的:由于 TensorFlow 处理计算的方式,当你习惯了 TensorFlow 风格时,开发更复杂的算法会变得相对容易。本方案将引导我们通过 TensorFlow 算法的伪代码。
准备开始
目前,TensorFlow 已在以下 64 位系统上测试并获得支持:Ubuntu 16.04 或更高版本、macOS 10.12.6(Sierra)或更高版本(不过不支持 GPU)、Raspbian 9.0 或更高版本,以及 Windows 7 或更高版本。本书中的代码已在 Ubuntu 系统上开发并测试,但它在其他任何系统上也应该能正常运行。本书的代码可在 GitHub 上找到,地址为 github.com/PacktPublishing/Machine-Learning-Using-TensorFlow-Cookbook,它作为本书所有代码和一些数据的代码库。
在本书中,我们将只关注 TensorFlow 的 Python 库封装,尽管 TensorFlow 的大多数核心代码是用 C++ 编写的。TensorFlow 与 Python 很好地兼容,支持 3.7 至 3.8 版本。此书将使用 Python 3.7(你可以在 www.python.org 获取该解释器)和 TensorFlow 2.2.0(你可以在 www.tensorflow.org/install 查找安装它所需的所有说明)。
虽然 TensorFlow 可以在 CPU 上运行,但大多数算法如果在 GPU 上处理,运行会更快,并且支持在具有 Nvidia 计算能力 3.5 或更高版本的显卡上运行(特别是在运行计算密集型的复杂网络时更为推荐)。
你在书中找到的所有方案都与 TensorFlow 2.2.0 兼容。如有必要,我们将指出与以前的 2.1 和 2.0 版本在语法和执行上的区别。
在工作站上运行基于 TensorFlow 的脚本时,常用的 GPU 有 Nvidia Titan RTX 和 Nvidia Quadro RTX,而在数据中心,我们通常会找到至少配备 24 GB 内存的 Nvidia Tesla 架构(例如,Google Cloud Platform 提供了 Nvidia Tesla K80、P4、T4、P100 和 V100 型号)。要在 GPU 上正常运行,你还需要下载并安装 Nvidia CUDA 工具包,版本为 5.x+(developer.nvidia.com/cuda-downloads)。
本章中的一些方案将依赖于安装当前版本的 SciPy、NumPy 和 Scikit-learn Python 包。这些附带包也包含在 Anaconda 包中(www.anaconda.com/products/in…
如何进行…
在这里,我们将介绍 TensorFlow 算法的一般流程。大多数方案将遵循这个大纲:
-
导入或生成数据集:我们所有的机器学习算法都依赖于数据集。在本书中,我们将生成数据或使用外部数据源。有时,依赖生成的数据更好,因为我们可以控制如何变化并验证预期结果。大多数情况下,我们将访问给定食谱的公共数据集。有关如何访问这些数据集的详细信息,请参见本章末尾的 附加资源 章节:
import tensorflow as tf import tensorflow_datasets as tfds import numpy as np data = tfds.load("iris", split="train") -
转换和规范化数据:通常,输入数据集的形式并不是我们实现目标所需要的精确形式。TensorFlow 期望我们将数据转换为接受的形状和数据类型。实际上,数据通常不符合我们算法所期望的正确维度或类型,我们必须在使用之前正确地转换它。大多数算法还期望规范化数据(这意味着变量的均值为零,标准差为一),我们也将在这里讨论如何实现这一点。TensorFlow 提供了内置函数,可以加载数据、将数据拆分为批次,并允许您使用简单的 NumPy 函数转换变量和规范化每个批次,包括以下内容:
for batch in data.batch(batch_size, drop_remainder=True): labels = tf.one_hot(batch['label'], 3) X = batch['features'] X = (X - np.mean(X)) / np.std(X) -
将数据集划分为训练集、测试集和验证集:我们通常希望在不同的数据集上测试我们的算法,这些数据集是我们训练过的。许多算法还需要进行超参数调整,因此我们预留了一个验证集,用于确定最佳的超参数组合。
-
设置算法参数(超参数):我们的算法通常会有一组参数,这些参数在整个过程中保持不变。例如,这可能是迭代次数、学习率或我们选择的其他固定参数。通常建议将这些参数一起初始化为全局变量,以便读者或用户能够轻松找到它们,如下所示:
epochs = 1000 batch_size = 32 input_size = 4 output_size = 3 learning_rate = 0.001 -
初始化变量:TensorFlow 依赖于知道它可以修改什么以及不能修改什么。在优化过程中,TensorFlow 将修改/调整变量(模型的权重/偏置),以最小化损失函数。为了实现这一点,我们通过输入变量输入数据。我们需要初始化变量和占位符的大小和类型,以便 TensorFlow 知道该期待什么。TensorFlow 还需要知道期望的数据类型。在本书的大部分内容中,我们将使用
float32。TensorFlow 还提供了float64和float16数据类型。请注意,使用更多字节来获得更高精度会导致算法变慢,而使用更少字节则会导致结果算法的精度降低。请参考以下代码,了解如何在 TensorFlow 中设置一个权重数组和一个偏置向量的简单示例:weights = tf.Variable(tf.random.normal(shape=(input_size, output_size), dtype=tf.float32)) biases = tf.Variable(tf.random.normal(shape=(output_size,), dtype=tf.float32)) -
定义模型结构:在获得数据并初始化变量之后,我们必须定义模型。这是通过构建计算图来完成的。这个示例中的模型将是一个逻辑回归模型(logit E(Y) = bX + a):
logits = tf.add(tf.matmul(X, weights), biases) -
声明损失函数:在定义模型之后,我们必须能够评估输出。这就是我们声明损失函数的地方。损失函数非常重要,因为它告诉我们预测值与实际值之间的偏差。不同类型的损失函数将在 第二章,TensorFlow 实践方式 中的 实现反向传播 这一章节中详细探讨。在此,我们以交叉熵为例,使用 logits 计算 softmax 交叉熵与标签之间的差异:
loss = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits(labels, logits)) -
初始化并训练模型:现在我们已经准备好了一切,需要创建图的实例,输入数据,并让 TensorFlow 调整变量,以更好地预测我们的训练数据。以下是初始化计算图的一种方法,通过多次迭代,使用 SDG 优化器收敛模型结构中的权重:
optimizer = tf.optimizers.SGD(learning_rate) with tf.GradientTape() as tape: logits = tf.add(tf.matmul(X, weights), biases) loss = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits(labels, logits)) gradients = tape.gradient(loss, [weights, biases]) optimizer.apply_gradients(zip(gradients, [weights, biases])) -
评估模型:一旦我们建立并训练了模型,我们应该通过某些指定的标准评估模型,看看它在新数据上的表现如何。我们会在训练集和测试集上进行评估,这些评估将帮助我们判断模型是否存在过拟合或欠拟合的问题。我们将在后续的内容中讨论这个问题。在这个简单的例子中,我们评估最终的损失,并将拟合值与真实的训练值进行比较:
print(f"final loss is: {loss.numpy():.3f}") preds = tf.math.argmax(tf.add(tf.matmul(X, weights), biases), axis=1) ground_truth = tf.math.argmax(labels, axis=1) for y_true, y_pred in zip(ground_truth.numpy(), preds.numpy()): print(f"real label: {y_true} fitted: {y_pred}") -
调整超参数:大多数时候,我们会希望回到之前的步骤,调整一些超参数,并根据我们的测试结果检查模型的性能。然后,我们使用不同的超参数重复之前的步骤,并在验证集上评估模型。
-
部署/预测新结果:了解如何对新数据和未见过的数据进行预测也是一个关键要求。一旦我们训练好模型,就可以通过 TensorFlow 轻松实现这一点。
它是如何工作的……
在 TensorFlow 中,我们必须先设置数据、输入变量和模型结构,然后才能告诉程序训练并调整其权重,以提高预测效果。TensorFlow 通过计算图来完成这项工作。计算图是没有递归的有向图,允许并行计算。
为此,我们需要创建一个损失函数,以便 TensorFlow 最小化它。TensorFlow 通过修改计算图中的变量来实现这一点。TensorFlow 能够修改变量,因为它跟踪模型中的计算,并自动计算变量的梯度(如何改变每个变量),以最小化损失。因此,我们可以看到,进行更改并尝试不同数据源是多么容易。
另见
-
有关 TensorFlow 的进一步介绍以及更多资源,请参考 TensorFlow 官方页面上的官方文档和教程:
www.tensorflow.org/ -
在官方页面中,一个更具百科全书性质的入门位置是官方 Python API 文档,
www.tensorflow.org/api_docs/python/,在那里您可以找到所有可能的命令列表。 -
还有教程可供学习:
www.tensorflow.org/tutorials/ -
除此之外,还可以在这里找到一个非官方的 TensorFlow 教程、项目、演示和代码库集合:
github.com/dragen1860/TensorFlow-2.x-Tutorials
声明变量和张量
张量是 TensorFlow 用于在计算图上进行操作的主要数据结构。即使在 TensorFlow 2.x 中,这一方面被隐藏了,但数据流图仍然在幕后运行。这意味着构建神经网络的逻辑在 TensorFlow 1.x 和 TensorFlow 2.x 之间并没有发生太大变化。最引人注目的变化是,您不再需要处理占位符,后者是 TensorFlow 1.x 图中数据的输入门。
现在,您只需将张量声明为变量,然后继续构建图。
张量 是一个数学术语,指的是广义的向量或矩阵。如果向量是一维的,矩阵是二维的,那么张量就是 n 维的(其中 n 可以是 1、2 或更大)。
我们可以将这些张量声明为变量,并将它们用于计算。为了做到这一点,我们首先需要学习如何创建张量。
准备工作
当我们创建一个张量并将其声明为变量时,TensorFlow 会在我们的计算图中创建多个图结构。还需要指出的是,仅仅创建一个张量并不会向计算图中添加任何内容。TensorFlow 仅在执行操作以初始化变量后才会这样做。有关更多信息,请参阅下节关于变量和占位符的内容。
如何做到这一点…
在这里,我们将介绍在 TensorFlow 中创建张量的四种主要方式。
在本食谱或其他食谱中,我们不会进行不必要的详细说明。我们倾向于仅说明不同 API 调用中的必需参数,除非您认为覆盖某些可选参数对食谱有帮助;当这种情况发生时,我们会说明其背后的理由。
-
固定大小的张量:
- 在以下代码中,我们创建了一个全为 0 的张量:
row_dim, col_dim = 3, 3 zero_tsr = tf.zeros(shape=[row_dim, col_dim], dtype=tf.float32)- 在以下代码中,我们创建了一个全为 1 的张量:
ones_tsr = tf.ones([row_dim, col_dim])- 在以下代码中,我们创建了一个常量填充的张量:
filled_tsr = tf.fill([row_dim, col_dim], 42)- 在以下代码中,我们从一个现有常量创建了一个张量:
constant_tsr = tf.constant([1,2,3])请注意,
tf.constant()函数可以用来将一个值广播到数组中,通过写tf.constant(42, [row_dim, col_dim])来模仿tf.fill()的行为。 -
相似形状的张量:我们也可以根据其他张量的形状初始化变量,如下所示:
zeros_similar = tf.zeros_like(constant_tsr) ones_similar = tf.ones_like(constant_tsr)请注意,由于这些张量依赖于先前的张量,我们必须按顺序初始化它们。尝试以随机顺序初始化张量会导致错误。
-
序列张量:在 TensorFlow 中,所有的参数都被文档化为张量。即使需要标量,API 也会将其作为零维标量提及。因此,TensorFlow 允许我们指定包含定义区间的张量也就不足为奇了。以下函数的行为与 NumPy 的
linspace()输出和range()输出非常相似(参考:docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html)。请看以下函数:linear_tsr = tf.linspace(start=0.0, stop=1.0, num=3)请注意,起始值和终止值参数应为浮点值,而
num应为整数。结果张量的序列为[0.0, 0.5, 1.0](
print(linear_tsr命令将提供必要的输出)。请注意,此函数包括指定的终止值。以下是tf.range函数的对比:integer_seq_tsr = tf.range(start=6, limit=15, delta=3)结果是序列[6, 9, 12]。请注意,此函数不包括限制值,并且可以处理起始值和限制值的整数和浮点数。
-
随机张量:以下生成的随机数来自均匀分布:
randunif_tsr = tf.random.uniform([row_dim, col_dim], minval=0, maxval=1)
请注意,这种随机均匀分布是从包含minval但不包含maxval的区间中抽取的(minval <= x < maxval)。因此,在这种情况下,输出范围是[0, 1)。如果你需要仅抽取整数而不是浮点数,只需在调用函数时添加dtype=tf.int32参数。
若要获得从正态分布中随机抽取的张量,可以运行以下代码:
randnorm_tsr = tf.random.normal([row_dim, col_dim],
mean=0.0, stddev=1.0)
还有一些情况,我们希望生成在某些范围内保证的正态分布随机值。truncated_normal()函数总是从指定均值的两个标准差内挑选正态值:
runcnorm_tsr = tf.random.truncated_normal([row_dim, col_dim],
mean=0.0, stddev=1.0)
我们可能还对随机化数组的条目感兴趣。为此,两个函数可以帮助我们:random.shuffle()和image.random_crop()。以下代码执行此操作:
shuffled_output = tf.random.shuffle(input_tensor)
cropped_output = tf.image.random_crop(input_tensor, crop_size)
在本书后续的内容中,我们将关注对大小为(高度,宽度,3)的图像进行随机裁剪,其中包含三种颜色光谱。为了在cropped_output中固定某一维度,你必须为该维度指定最大值:
height, width = (64, 64)
my_image = tf.random.uniform([height, width, 3], minval=0,
maxval=255, dtype=tf.int32)
cropped_image = tf.image.random_crop(my_image,
[height//2, width//2, 3])
这段代码将生成随机噪声图像,这些图像会被裁剪,既能减小高度和宽度的一半,但深度维度不会受影响,因为你已将其最大值固定为参数。
它是如何工作的…
一旦我们决定了如何创建张量,我们还可以通过将张量包装在Variable()函数中来创建相应的变量,如下所示:
my_var = tf.Variable(tf.zeros([row_dim, col_dim]))
在接下来的示例中会有更多内容介绍。
还有更多…
我们不局限于内置函数:我们可以将任何 NumPy 数组转换为 Python 列表,或使用 convert_to_tensor() 函数将常量转换为张量。注意,这个函数也接受张量作为输入,以便我们希望在函数内对计算进行通用化时使用。
使用急切执行
在开发深度且复杂的神经网络时,你需要不断地尝试不同的架构和数据。这在 TensorFlow 1.0 中是困难的,因为你总是需要从头到尾运行代码,以检查是否成功。TensorFlow 2.x 默认在急切执行模式下工作,这意味着你可以随着项目的进展,逐步开发并检查代码。这是个好消息;现在我们只需要理解如何在急切执行模式下进行实验,这样我们就能充分利用 TensorFlow 2.x 的这一特性。本教程将为你提供入门的基础知识。
准备开始
TensorFlow 1.x 的表现最优,因为它在编译静态计算图之后执行计算。所有计算都被分配并连接成一个图,你编译网络时,该图帮助 TensorFlow 执行计算,利用可用资源(多核 CPU 或多个 GPU)以最佳方式,并在资源之间以最及时高效的方式分配操作。这也意味着,无论如何,一旦你定义并编译了图,就不能在运行时对其进行更改,而必须从头开始实例化,这会带来额外的工作量。
在 TensorFlow 2.x 中,你仍然可以定义你的网络,编译并以最佳方式运行它,但 TensorFlow 开发团队现在默认采用了一种更为实验性的方式,允许立即评估操作,从而使调试更容易,尝试网络变种也更加方便。这就是所谓的急切执行。现在,操作返回的是具体的值,而不是指向稍后构建的计算图部分的指针。更重要的是,你现在可以在模型执行时使用主机语言的所有功能,这使得编写更复杂、更精细的深度学习解决方案变得更容易。
如何做……
你基本上不需要做任何事情;在 TensorFlow 2.x 中,**急切执行(eager execution)**是默认的操作方式。当你导入 TensorFlow 并开始使用它的功能时,你会在急切执行模式下工作,因为你可以在执行时进行检查:
tf.executing_eagerly()
True
这就是你需要做的全部。
它是如何工作的……
只需运行 TensorFlow 操作,结果将立即返回:
x = [[2.]]
m = tf.matmul(x, x)
print("the result is {}".format(m))
the result is [[4.]]
就是这么简单!
还有更多…
由于 TensorFlow 现在默认启用了即时执行模式,你不会惊讶地发现 tf.Session 已经从 TensorFlow API 中移除。你不再需要在运行计算之前构建计算图;现在你只需构建你的网络,并在过程中进行测试。这为常见的软件最佳实践打开了道路,比如文档化代码、在编写代码时使用面向对象编程,以及将代码组织成可重用的自包含模块。
与矩阵的操作
理解 TensorFlow 如何与矩阵协作在开发数据流通过计算图时非常重要。在这个方案中,我们将涵盖矩阵的创建以及可以使用 TensorFlow 执行的基本操作。
值得强调的是,矩阵在机器学习(以及数学中的一般应用)中的重要性:机器学习算法在计算上是通过矩阵运算来表示的。了解如何进行矩阵运算是使用 TensorFlow 时的一个加分项,尽管你可能不需要经常使用它;其高级模块 Keras 可以在后台处理大多数矩阵代数内容(更多关于 Keras 的内容请参见第三章,Keras)。
本书不涵盖矩阵属性和矩阵代数(线性代数)的数学背景,因此不熟悉的读者强烈建议学习足够的矩阵知识,以便能够熟练掌握矩阵代数。在另见部分,你可以找到一些资源,帮助你复习微积分技巧或从零开始学习,以便从 TensorFlow 中获得更多收益。
准备工作
许多算法依赖于矩阵运算。TensorFlow 为我们提供了易于使用的操作来执行这些矩阵计算。你只需要导入 TensorFlow,并按照本节内容进行操作;如果你不是矩阵代数专家,请首先查看本方案的另见部分,寻找帮助你充分理解下面方案的资源。
如何操作…
我们按如下步骤进行:
-
创建矩阵:我们可以从 NumPy 数组或嵌套列表创建二维矩阵,正如本章开头的声明和使用变量与张量方案中所描述的那样。我们可以使用张量创建函数,并为如
zeros()、ones()和truncated_normal()等函数指定二维形状。TensorFlow 还允许我们使用diag()函数从一维数组或列表创建对角矩阵,示例如下:identity_matrix = tf.linalg.diag([1.0, 1.0, 1.0]) A = tf.random.truncated_normal([2, 3]) B = tf.fill([2,3], 5.0) C = tf.random.uniform([3,2]) D = tf.convert_to_tensor(np.array([[1., 2., 3.], [-3., -7., -1.], [0., 5., -2.]]), dtype=tf.float32) print(identity_matrix) [[ 1\. 0\. 0.] [ 0\. 1\. 0.] [ 0\. 0\. 1.]] print(A) [[ 0.96751703 0.11397751 -0.3438891 ] [-0.10132604 -0.8432678 0.29810596]] print(B) [[ 5\. 5\. 5.] [ 5\. 5\. 5.]] print(C) [[ 0.33184157 0.08907614] [ 0.53189191 0.67605299] [ 0.95889051 0.67061249]]请注意,C 张量是随机创建的,它可能与你的会话中显示的内容有所不同。
print(D) [[ 1\. 2\. 3.] [-3\. -7\. -1.] [ 0\. 5\. -2.]] -
加法、减法和乘法:要对相同维度的矩阵进行加法、减法或乘法,TensorFlow 使用以下函数:
print(A+B) [[ 4.61596632 5.39771316 4.4325695 ] [ 3.26702736 5.14477345 4.98265553]] print(B-B) [[ 0\. 0\. 0.] [ 0\. 0\. 0.]] print(tf.matmul(B, identity_matrix)) [[ 5\. 5\. 5.] [ 5\. 5\. 5.]]需要注意的是,
matmul()函数有一些参数,用于指定是否在乘法前转置参数(布尔参数transpose_a和transpose_b),或者每个矩阵是否为稀疏矩阵(a_is_sparse和b_is_sparse)。如果你需要对两个形状和类型相同的矩阵进行逐元素乘法(这非常重要,否则会报错),只需使用
tf.multiply函数:print(tf.multiply(D, identity_matrix)) [[ 1\. 0\. 0.] [-0\. -7\. -0.] [ 0\. 0\. -2.]]请注意,矩阵除法没有明确定义。虽然许多人将矩阵除法定义为乘以逆矩阵,但它在本质上不同于实数除法。
-
转置:转置矩阵(翻转列和行),如下所示:
print(tf.transpose(C)) [[0.33184157 0.53189191 0.95889051] [0.08907614 0.67605299 0.67061249]]再次提到,重新初始化会给我们不同于之前的值。
-
行列式:要计算行列式,请使用以下代码:
print(tf.linalg.det(D)) -38.0 -
逆矩阵:要找到一个方阵的逆矩阵,请参阅以下内容:
print(tf.linalg.inv(D)) [[-0.5 -0.5 -0.5 ] [ 0.15789474 0.05263158 0.21052632] [ 0.39473684 0.13157895 0.02631579]]逆矩阵方法仅在矩阵是对称正定时,基于 Cholesky 分解。如果矩阵不是对称正定的,则基于 LU 分解。
-
分解:对于 Cholesky 分解,请使用以下代码:
print(tf.linalg.cholesky(identity_matrix)) [[ 1\. 0\. 1.] [ 0\. 1\. 0.] [ 0\. 0\. 1.]] -
特征值和特征向量:对于特征值和特征向量,请使用以下代码:
print(tf.linalg.eigh(D)) [[-10.65907521 -0.22750691 2.88658212] [ 0.21749542 0.63250104 -0.74339638] [ 0.84526515 0.2587998 0.46749277] [ -0.4880805 0.73004459 0.47834331]]
请注意,tf.linalg.eigh()函数输出两个张量:第一个张量包含特征值,第二个张量包含特征向量。在数学中,这种操作称为矩阵的特征分解。
它的工作原理……
TensorFlow 为我们提供了所有开始进行数值计算的工具,并将这些计算添加到我们的神经网络中。
另见
如果你需要快速提升微积分技能,并深入了解更多关于 TensorFlow 操作的内容,我们建议以下资源:
-
这本免费的书籍《机器学习数学》(Mathematics for Machine Learning),可以在这里找到:
mml-book.github.io/。如果你想在机器学习领域中成功操作,这本书包含了你需要知道的一切。 -
对于一个更加易于获取的资源,可以观看 Khan Academy 的关于向量和矩阵的课程(
www.khanacademy.org/math/precalculus),以便学习神经网络中最基本的数据元素。
声明操作
除了矩阵操作,TensorFlow 还有许多其他操作我们至少应该了解。这个教程将为你提供一个简要且必要的概览,帮助你掌握真正需要知道的内容。
准备好
除了标准的算术操作外,TensorFlow 还为我们提供了更多需要了解的操作。在继续之前,我们应该认识到这些操作并学习如何使用它们。再次提醒,我们只需要导入 TensorFlow:
import tensorflow as tf
现在我们准备运行接下来的代码。
如何操作……
TensorFlow 提供了张量的标准操作,即add()、subtract()、multiply()和division(),它们都位于math模块中。请注意,本节中的所有操作,除非另有说明,否则都将逐元素计算输入:
-
TensorFlow 还提供了
division()及相关函数的变种。 -
值得注意的是,
division()返回与输入相同类型的结果。这意味着如果输入是整数,它实际上返回除法的地板值(类似 Python 2)。要返回 Python 3 版本的除法,即在除法前将整数转换为浮点数并始终返回浮点数,TensorFlow 提供了truediv()函数,具体如下:print(tf.math.divide(3, 4)) 0.75 print(tf.math.truediv(3, 4)) tf.Tensor(0.75, shape=(), dtype=float64) -
如果我们有浮点数并且需要整数除法,可以使用
floordiv()函数。请注意,这仍然会返回浮点数,但它会向下舍入到最接近的整数。该函数如下:print(tf.math.floordiv(3.0,4.0)) tf.Tensor(0.0, shape=(), dtype=float32) -
另一个重要的函数是
mod()。该函数返回除法后的余数,具体如下:print(tf.math.mod(22.0, 5.0)) tf.Tensor(2.0, shape=(), dtype=float32) -
两个张量的叉积通过
cross()函数实现。请记住,叉积仅对两个三维向量定义,因此它只接受两个三维张量。以下代码展示了这一用法:print(tf.linalg.cross([1., 0., 0.], [0., 1., 0.])) tf.Tensor([0\. 0\. 1.], shape=(3,), dtype=float32) -
下面是常用数学函数的简明列表。所有这些函数均逐元素操作:
函数 操作 tf.math.abs()输入张量的绝对值 tf.math.ceil()输入张量的向上取整函数 tf.math.cos()输入张量的余弦函数 tf.math.exp()输入张量的底数 e 指数函数 tf.math.floor()输入张量的向下取整函数 tf.linalg.inv()输入张量的乘法逆(1/x) tf.math.log()输入张量的自然对数 tf.math.maximum()两个张量的逐元素最大值 tf.math.minimum()两个张量的逐元素最小值 tf.math.negative()输入张量的负值 tf.math.pow()第一个张量按逐元素方式升至第二个张量 tf.math.round()对输入张量进行四舍五入 tf.math.rsqrt()一个张量的平方根倒数 tf.math.sign()根据张量的符号返回 -1、0 或 1 tf.math.sin()输入张量的正弦函数 tf.math.sqrt()输入张量的平方根 tf.math.square()输入张量的平方 -
特殊数学函数:有一些在机器学习中经常使用的特殊数学函数值得一提,TensorFlow 为它们提供了内建函数。再次强调,除非另有说明,否则这些函数都是逐元素操作:
tf.math.digamma() | Psi 函数,即lgamma()函数的导数 |
|---|---|
tf.math.erf() | 一个张量的高斯误差函数(逐元素) |
tf.math.erfc() | 一个张量的互补误差函数 |
tf.math.igamma() | 下正则化不完全伽马函数 |
tf.math.igammac() | 上不完全伽马函数的正则化形式 |
tf.math.lbeta() | beta 函数绝对值的自然对数 |
tf.math.lgamma() | 伽马函数绝对值的自然对数 |
tf.math.squared_difference() | 计算两个张量之间差值的平方 |
它是如何工作的……
了解哪些函数对我们可用是很重要的,这样我们才能将它们添加到我们的计算图中。我们将主要关注前面提到的函数。我们也可以通过组合这些函数生成许多不同的自定义函数,如下所示:
# Tangent function (tan(pi/4)=1)
def pi_tan(x):
return tf.tan(3.1416/x)
print(pi_tan(4))
tf.Tensor(1.0000036, shape=(), dtype=float32)
组成深度神经网络的复杂层仅由前面的函数组成,因此,凭借这个教程,你已经掌握了创建任何你想要的内容所需的所有基础知识。
还有更多……
如果我们希望向图中添加其他未列出的操作,我们必须从前面的函数中创建自己的操作。下面是一个示例,这是之前未使用过的操作,我们可以将其添加到图中。我们可以使用以下代码添加一个自定义的多项式函数,3 * x² - x + 10:
def custom_polynomial(value):
return tf.math.subtract(3 * tf.math.square(value), value) + 10
print(custom_polynomial(11))
tf.Tensor(362, shape=(), dtype=int32)
现在,你可以创建无限制的自定义函数,不过我始终建议你首先查阅 TensorFlow 文档。通常,你不需要重新发明轮子;你会发现你需要的功能已经被编码实现了。
实现激活函数
激活函数是神经网络逼近非线性输出并适应非线性特征的关键。它们在神经网络中引入非线性操作。如果我们小心选择激活函数并合理放置它们,它们是非常强大的操作,可以指示 TensorFlow 进行拟合和优化。
准备就绪
当我们开始使用神经网络时,我们会经常使用激活函数,因为激活函数是任何神经网络的重要组成部分。激活函数的目标就是调整权重和偏置。在 TensorFlow 中,激活函数是对张量进行的非线性操作。它们的作用类似于之前的数学操作。激活函数有很多用途,但主要的概念是它们在图中引入非线性,同时对输出进行归一化。
如何实现……
激活函数位于 TensorFlow 的 神经网络 (nn) 库中。除了使用内置的激活函数外,我们还可以使用 TensorFlow 操作设计自己的激活函数。我们可以导入预定义的激活函数(通过 tensorflow import nn),或者在函数调用中明确写出 nn。在这里,我们选择对每个函数调用显式声明:
-
修正线性单元(ReLU)是最常见和最基本的方式,用于在神经网络中引入非线性。这个函数就叫做
max(0,x)。它是连续的,但不光滑。它的形式如下:print(tf.nn.relu([-3., 3., 10.])) tf.Tensor([ 0\. 3\. 10.], shape=(3,), dtype=float32) -
有时我们希望限制前面 ReLU 激活函数的线性增涨部分。我们可以通过将
max(0,x)函数嵌套在min()函数中来实现。TensorFlow 实现的版本被称为 ReLU6 函数,定义为min(max(0,x),6)。这是一个硬 sigmoid 函数的版本,计算速度更快,并且不容易遇到梯度消失(趋近于零)或梯度爆炸的问题。这在我们后续讨论卷积神经网络和递归神经网络时会非常有用。它的形式如下:print(tf.nn.relu6([-3., 3., 10.])) tf.Tensor([ 0\. 3\. 6.], shape=(3,), dtype=float32) -
Sigmoid 函数是最常见的连续且平滑的激活函数。它也被称为 logistic 函数,形式为 1 / (1 + exp(-x))。由于在训练过程中容易导致反向传播的梯度消失,sigmoid 函数并不常用。它的形式如下:
print(tf.nn.sigmoid([-1., 0., 1.])) tf.Tensor([0.26894143 0.5 0.7310586 ], shape=(3,), dtype=float32)我们应该意识到一些激活函数,如 sigmoid,并不是零中心的。这将要求我们在使用这些函数前对数据进行零均值化处理,特别是在大多数计算图算法中。
-
另一个平滑的激活函数是双曲正切函数。双曲正切函数与 sigmoid 函数非常相似,只不过它的范围不是 0 到 1,而是 -1 到 1。这个函数的形式是双曲正弦与双曲余弦的比值。另一种写法如下:
((exp(x) – exp(-x))/(exp(x) + exp(-x))这个激活函数如下:
print(tf.nn.tanh([-1., 0., 1.])) tf.Tensor([-0.7615942 0\. 0.7615942], shape=(3,), dtype=float32) -
softsign函数也被用作激活函数。这个函数的形式是 x/(|x| + 1)。softsign函数应该是符号函数的一个连续(但不光滑)近似。见以下代码:print(tf.nn.softsign([-1., 0., -1.])) tf.Tensor([-0.5 0\. -0.5], shape=(3,), dtype=float32) -
另一个函数是
softplus函数,它是 ReLU 函数的平滑版本。这个函数的形式是 log(exp(x) + 1)。它的形式如下:print(tf.nn.softplus([-1., 0., -1.])) tf.Tensor([0.31326166 0.6931472 0.31326166], shape=(3,), dtype=float32)softplus函数随着输入的增大趋于无穷大,而softsign函数则趋向 1。不过,随着输入变小,softplus函数接近零,而softsign函数则趋向 -1。 -
指数线性单元 (ELU) 与 softplus 函数非常相似,只不过它的下渐近线是 -1,而不是 0。其形式为 (exp(x) + 1),当 x < 0 时;否则为 x。它的形式如下:
print(tf.nn.elu([-1., 0., -1.])) tf.Tensor([-0.63212055 0\. -0.63212055], shape=(3,), dtype=float32) -
现在,通过这个公式,你应该能理解基本的关键激活函数。我们列出的现有激活函数并不全面,你可能会发现对于某些问题,你需要尝试其中一些不太常见的函数。除了这个公式中的激活函数,你还可以在 Keras 激活函数页面上找到更多激活函数:
www.tensorflow.org/api_docs/python/tf/keras/activations
它的工作原理…
这些激活函数是我们未来可以在神经网络或其他计算图中引入非线性的方法。需要注意的是,我们在网络中的哪个位置使用了激活函数。如果激活函数的值域在 0 和 1 之间(如 sigmoid),那么计算图只能输出 0 到 1 之间的值。如果激活函数位于节点之间并被隐藏,那么我们需要注意这个范围对张量的影响,特别是在通过张量时。如果我们的张量被缩放为零均值,我们将希望使用一个能够尽可能保持零附近方差的激活函数。
这意味着我们希望选择一个激活函数,比如双曲正切(tanh)或softsign。如果张量都被缩放为正数,那么我们理想中会选择一个能够保持正域方差的激活函数。
还有更多…
我们甚至可以轻松创建自定义的激活函数,如 Swish,公式为 x**sigmoid(x)(参见 Swish: a Self-Gated Activation Function, Ramachandran 等,2017,arxiv.org/abs/1710.05941),它可以作为 ReLU 激活函数在图像和表格数据问题中的一个更高效的替代品:
def swish(x):
return x * tf.nn.sigmoid(x)
print(swish([-1., 0., 1.]))
tf.Tensor([-0.26894143 0\. 0.7310586 ], shape=(3,), dtype=float32)
在尝试过 TensorFlow 提供的激活函数后,你的下一步自然是复制那些你在深度学习论文中找到的激活函数,或者你自己创建的激活函数。
处理数据源
本书的大部分内容都将依赖于使用数据集来训练机器学习算法。本节提供了如何通过 TensorFlow 和 Python 访问这些数据集的说明。
一些数据源依赖于外部网站的维护,以便你能够访问数据。如果这些网站更改或删除了数据,那么本节中的部分代码可能需要更新。你可以在本书的 GitHub 页面上找到更新后的代码:
github.com/PacktPublishing/Machine-Learning-Using-TensorFlow-Cookbook
准备就绪
在本书中,我们将使用的大多数数据集可以通过 TensorFlow 数据集(TensorFlow Datasets)访问,而一些其他的数据集则需要额外的努力,可能需要使用 Python 脚本来下载,或者通过互联网手动下载。
TensorFlow 数据集(TFDS)是一个现成可用的数据集集合(完整列表可以在此处找到:www.tensorflow.org/datasets/catalog/overview)。它自动处理数据的下载和准备,并且作为 tf.data 的封装器,构建高效且快速的数据管道。
为了安装 TFDS,只需在控制台中运行以下安装命令:
pip install tensorflow-datasets
现在,我们可以继续探索本书中你将使用的核心数据集(并非所有这些数据集都会包含在内,只有最常见的几个数据集会被介绍,其他一些非常特定的数据集将在本书的不同章节中介绍)。
如何操作…
-
鸢尾花数据集:这个数据集可以说是机器学习中经典的结构化数据集,可能也是所有统计学示例中的经典数据集。它是一个测量三种不同类型鸢尾花的萼片长度、萼片宽度、花瓣长度和花瓣宽度的数据集:Iris setosa、Iris virginica 和 Iris versicolor。总共有 150 个测量值,这意味着每个物种有 50 个测量值。要在 Python 中加载该数据集,我们将使用 TFDS 函数,代码如下:
import tensorflow_datasets as tfds iris = tfds.load('iris', split='train')当你第一次导入数据集时,下载数据集时会显示一个进度条,指示你所在的位置。如果你不想看到进度条,可以通过输入以下代码来禁用它:
tfds.disable_progress_bar() -
出生体重数据:该数据最初来自 1986 年 Baystate 医疗中心(马萨诸塞州斯普林菲尔德)。该数据集包含了出生体重和母亲的其他人口统计学及医学测量数据,以及家庭病史的记录。数据集有 189 条记录,包含 11 个变量。以下代码展示了如何将该数据作为
tf.data.dataset来访问:import tensorflow_datasets as tfds birthdata_url = 'https://raw.githubusercontent.com/PacktPublishing/TensorFlow-2-Machine-Learning-Cookbook-Third-Edition/master/birthweight.dat' path = tf.keras.utils.get_file(birthdata_url.split("/")[-1], birthdata_url) def map_line(x): return tf.strings.to_number(tf.strings.split(x)) birth_file = (tf.data .TextLineDataset(path) .skip(1) # Skip first header line .map(map_line) ) -
波士顿房价数据集:卡内基梅隆大学在其
StatLib库中维护了一系列数据集。该数据可以通过加州大学欧文分校的机器学习仓库轻松访问(archive.ics.uci.edu/ml/index.php)。该数据集包含 506 条房价观察记录,以及各种人口统计数据和房屋属性(14 个变量)。以下代码展示了如何在 TensorFlow 中访问该数据:import tensorflow_datasets as tfds housing_url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data' path = tf.keras.utils.get_file(housing_url.split("/")[-1], housing_url) def map_line(x): return tf.strings.to_number(tf.strings.split(x)) housing = (tf.data .TextLineDataset(path) .map(map_line) ) -
MNIST 手写数字数据集:美国国家标准与技术研究院(NIST)的手写数据集的子集即为MNIST数据集。MNIST 手写数字数据集托管在 Yann LeCun 的网站上(
yann.lecun.com/exdb/mnist/)。该数据库包含 70,000 张单个数字(0-9)的图像,其中大约 60,000 张用于训练集,10,000 张用于测试集。这个数据集在图像识别中使用非常频繁,以至于 TensorFlow 提供了内置函数来访问该数据。在机器学习中,提供验证数据以防止过拟合(目标泄露)也是很重要的。因此,TensorFlow 将训练集中的 5,000 张图像分配为验证集。以下代码展示了如何在 TensorFlow 中访问此数据:import tensorflow_datasets as tfds mnist = tfds.load('mnist', split=None) mnist_train = mnist['train'] mnist_test = mnist['test'] -
垃圾邮件-正常邮件文本数据。UCI 的机器学习数据集库也包含了一个垃圾邮件-正常邮件文本数据集。我们可以访问这个
.zip文件并获取垃圾邮件-正常邮件文本数据,方法如下:import tensorflow_datasets as tfds zip_url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip' path = tf.keras.utils.get_file(zip_url.split("/")[-1], zip_url, extract=True) path = path.replace("smsspamcollection.zip", "SMSSpamCollection") def split_text(x): return tf.strings.split(x, sep='\t') text_data = (tf.data .TextLineDataset(path) .map(split_text) ) -
电影评论数据:康奈尔大学的 Bo Pang 发布了一个电影评论数据集,将评论分类为好或坏。你可以在康奈尔大学网站上找到该数据:
www.cs.cornell.edu/people/pabo/movie-review-data/。要下载、解压并转换这些数据,我们可以运行以下代码:import tensorflow_datasets as tfds movie_data_url = 'http://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.tar.gz' path = tf.keras.utils.get_file(movie_data_url.split("/")[-1], movie_data_url, extract=True) path = path.replace('.tar.gz', '') with open(path+filename, 'r', encoding='utf-8', errors='ignore') as movie_file: for response, filename in enumerate(['\\rt-polarity.neg', '\\rt-polarity.pos']): with open(path+filename, 'r') as movie_file: for line in movie_file: review_file.write(str(response) + '\t' + line.encode('utf-8').decode()) def split_text(x): return tf.strings.split(x, sep='\t') movies = (tf.data .TextLineDataset('movie_reviews.txt') .map(split_text) ) -
CIFAR-10 图像数据:加拿大高级研究院发布了一个包含 8000 万标记彩色图像的图像集(每张图像的尺寸为 32 x 32 像素)。该数据集包含 10 个不同的目标类别(如飞机、汽车、鸟类等)。CIFAR-10 是一个子集,包含 60,000 张图像,其中训练集有 50,000 张图像,测试集有 10,000 张图像。由于我们将在多种方式下使用该数据集,并且它是我们较大的数据集之一,我们不会每次都运行脚本来获取它。要获取该数据集,只需执行以下代码来下载 CIFAR-10 数据集(这可能需要较长时间):
import tensorflow_datasets as tfds ds, info = tfds.load('cifar10', shuffle_files=True, with_info=True) print(info) cifar_train = ds['train'] cifar_test = ds['test'] -
莎士比亚作品文本数据:Project Gutenberg 是一个发布免费书籍电子版的项目。他们已经将莎士比亚的所有作品汇编在一起。以下代码展示了如何通过 TensorFlow 访问这个文本文件:
import tensorflow_datasets as tfds shakespeare_url = 'https://raw.githubusercontent.com/PacktPublishing/TensorFlow-2-Machine-Learning-Cookbook-Third-Edition/master/shakespeare.txt' path = tf.keras.utils.get_file(shakespeare_url.split("/")[-1], shakespeare_url) def split_text(x): return tf.strings.split(x, sep='\n') shakespeare_text = (tf.data .TextLineDataset(path) .map(split_text) ) -
英语-德语句子翻译数据:Tatoeba 项目(
tatoeba.org)收集了多种语言的句子翻译。他们的数据已根据创意共享许可证发布。从这些数据中,ManyThings.org(www.manythings.org)编译了可供下载的文本文件,包含逐句翻译。在这里,我们将使用英语-德语翻译文件,但你可以根据需要更改 URL 来使用其他语言:import os import pandas as pd from zipfile import ZipFile from urllib.request import urlopen, Request import tensorflow_datasets as tfds sentence_url = 'https://www.manythings.org/anki/deu-eng.zip' r = Request(sentence_url, headers={'User-Agent': 'Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11'}) b2 = [z for z in sentence_url.split('/') if '.zip' in z][0] #gets just the '.zip' part of the url with open(b2, "wb") as target: target.write(urlopen(r).read()) #saves to file to disk with ZipFile(b2) as z: deu = [line.split('\t')[:2] for line in z.open('deu.txt').read().decode().split('\n')] os.remove(b2) #removes the zip file # saving to disk prepared en-de sentence file with open("deu.txt", "wb") as deu_file: for line in deu: data = ",".join(line)+'\n' deu_file.write(data.encode('utf-8')) def split_text(x): return tf.strings.split(x, sep=',') text_data = (tf.data .TextLineDataset("deu.txt") .map(split_text) )
使用完这个数据集后,我们已完成对本书中您在使用配方时最常遇到的数据集的回顾。在每个配方开始时,我们会提醒您如何下载相关数据集,并解释它为何与该配方相关。
工作原理……
当涉及到在某个配方中使用这些数据集时,我们将参考本节内容,并假定数据已经按照我们刚才描述的方式加载。如果需要进一步的数据转换或预处理,相关代码将在配方中提供。
通常,当我们使用来自 TensorFlow 数据集的数据时,方法通常如下所示:
import tensorflow_datasets as tfds
dataset_name = "..."
data = tfds.load(dataset_name, split=None)
train = data['train']
test = data['test']
无论如何,根据数据的位置,可能需要下载、解压并转换它。
另见
这是我们在本书中使用的部分数据资源的附加参考资料:
-
Hosmer, D.W., Lemeshow, S., 和 Sturdivant, R. X.(2013 年) 《应用逻辑回归:第 3 版》
-
Lichman, M. (2013). UCI 机器学习库:
archive.ics.uci.edu/ml。加利福尼亚州欧文市:加利福尼亚大学信息与计算机科学学院 -
Bo Pang, Lillian Lee 和 Shivakumar Vaithyanathan,好评?使用机器学习技术进行情感分类,EMNLP 2002 会议论文:
www.cs.cornell.edu/people/pabo/movie-review-data/ -
Krizhevsky. (2009). 从小图像中学习多层特征:
www.cs.toronto.edu/~kriz/cifar.html -
Project Gutenberg. 访问于 2016 年 4 月:
www.gutenberg.org/
其他资源
在这一部分,你将找到更多的链接、文档资源和教程,这些在学习和使用 TensorFlow 时会提供很大帮助。
准备工作
在学习如何使用 TensorFlow 时,知道在哪里寻求帮助或提示是很有帮助的。本节列出了启动 TensorFlow 和解决问题的一些资源。
如何做到……
以下是 TensorFlow 资源的列表:
-
本书的代码可以在 Packt 仓库在线访问:
github.com/PacktPublishing/Machine-Learning-Using-TensorFlow-Cookbook -
TensorFlow 官方 Python API 文档位于
www.tensorflow.org/api_docs/python。在这里,你可以找到所有 TensorFlow 函数、对象和方法的文档及示例。 -
TensorFlow 的官方教程非常全面和详细,位于
www.tensorflow.org/tutorials/index.html。它们从图像识别模型开始,接着讲解 Word2Vec、RNN 模型以及序列到序列模型。它们还提供了生成分形图形和求解 PDE 系统的额外教程。请注意,他们会不断地向这个合集添加更多教程和示例。 -
TensorFlow 的官方 GitHub 仓库可以通过
github.com/tensorflow/tensorflow访问。在这里,你可以查看开源代码,甚至如果你愿意,可以分叉或克隆当前版本的代码。你也可以通过访问issues目录来查看当前已提交的问题。 -
TensorFlow 提供的一个由官方维护的公共 Docker 容器,始终保持最新版本,位于 Dockerhub:
hub.docker.com/r/tensorflow/tensorflow/。 -
Stack Overflow 是一个很好的社区帮助源。在这里有一个 TensorFlow 标签。随着 TensorFlow 越来越受欢迎,这个标签的讨论也在增长。要查看该标签的活动,可以访问
stackoverflow.com/questions/tagged/Tensorflow。 -
虽然 TensorFlow 非常灵活,能够用于许多用途,但它最常见的用途是深度学习。为了理解深度学习的基础、底层数学如何运作,并培养更多的深度学习直觉,Google 创建了一个在线课程,课程在 Udacity 上提供。要注册并参加这个视频讲座课程,请访问
www.udacity.com/course/deep-learning--ud730。 -
TensorFlow 还创建了一个网站,你可以在其中通过调整参数和数据集来直观地探索训练神经网络。访问
playground.tensorflow.org/来探索不同设置如何影响神经网络的训练。 -
Andrew Ng 讲授了一门名为《神经网络与深度学习》的在线课程:
www.coursera.org/learn/neural-networks-deep-learning -
斯坦福大学提供了一个在线大纲和详细的课程笔记,内容涉及卷积神经网络与视觉识别:
cs231n.stanford.edu/
第二章:TensorFlow 方式
在第一章,TensorFlow 2.x 入门中,我们介绍了 TensorFlow 如何创建张量并使用变量。在本章中,我们将介绍如何使用急切执行将这些对象组合在一起,从而动态地设置计算图。基于此,我们可以设置一个简单的分类器,并查看其表现如何。
另外,请记住,本书当前和更新后的代码可以在 GitHub 上在线获取,地址为github.com/PacktPublishing/Machine-Learning-Using-TensorFlow-Cookbook。
在本章中,我们将介绍 TensorFlow 操作的关键组件。然后,我们将把它们结合起来,创建一个简单的分类器并评估结果。在本章结束时,你应该已学到以下内容:
-
使用急切执行的操作
-
分层嵌套操作
-
使用多层
-
实现损失函数
-
实现反向传播
-
批量训练和随机训练
-
将所有内容结合在一起
让我们开始处理越来越复杂的配方,展示 TensorFlow 处理和解决数据问题的方法。
使用急切执行的操作
感谢第一章,TensorFlow 2.x 入门,我们已经能够创建 TensorFlow 中的对象,如变量。现在,我们将介绍针对这些对象的操作。为了做到这一点,我们将返回到急切执行,并通过一个新的基本配方展示如何操作矩阵。本配方及后续配方仍然是基础的,但在本章过程中,我们将把这些基础配方结合起来形成更复杂的配方。
准备开始
首先,我们加载 TensorFlow 和 NumPy,如下所示:
import TensorFlow as tf
import NumPy as np
这就是我们开始所需的一切;现在我们可以继续进行。
如何操作...
在这个例子中,我们将使用到目前为止学到的内容,将列表中的每个数字发送到 TensorFlow 命令进行计算并打印输出。
首先,我们声明我们的张量和变量。在所有可能的将数据传递给变量的方式中,我们将创建一个 NumPy 数组来传递给变量,然后将其用于操作:
x_vals = np.array([1., 3., 5., 7., 9.])
x_data = tf.Variable(x_vals, dtype=tf.float32)
m_const = tf.constant(3.)
operation = tf.multiply(x_data, m_const)
for result in operation:
print(result.NumPy())
上述代码的输出结果如下:
3.0
9.0
15.0
21.0
27.0
一旦你习惯了使用 TensorFlow 变量、常量和函数,就会自然而然地从 NumPy 数组数据开始,逐步构建数据结构和操作,并在过程中测试它们的结果。
它是如何工作的...
使用急切执行,TensorFlow 会立即评估操作值,而不是操作符号句柄,这些句柄指向计算图中的节点,后者将在之后编译和执行。因此,你可以直接遍历乘法操作的结果,并使用 .NumPy 方法打印出返回的 NumPy 对象,这个方法会从 TensorFlow 张量中返回一个 NumPy 对象。
分层嵌套操作
在这个示例中,我们将学习如何将多个操作结合起来工作;了解如何将操作串联在一起非常重要。这将为我们的网络设置分层操作进行执行。在这个示例中,我们将用两个矩阵乘以一个占位符,并执行加法操作。我们将输入两个以三维 NumPy 数组形式表示的矩阵。
这是另一个简单的示例,给你一些关于如何使用 TensorFlow 编写代码的灵感,利用函数或类等常见结构,提高代码的可读性和模块化性。即使最终产品是一个神经网络,我们依然在编写计算机程序,并且应该遵循编程最佳实践。
准备工作
和往常一样,我们只需要导入 TensorFlow 和 NumPy,代码如下:
import TensorFlow as tf
import NumPy as np
我们现在准备好继续进行我们的示例。
如何实现…
我们将输入两个大小为3 x 5的 NumPy 数组。我们将每个矩阵与大小为5 x 1的常量相乘,得到一个大小为3 x 1的矩阵。接着,我们将这个结果与一个1 x 1的矩阵相乘,最终得到一个3 x 1的矩阵。最后,我们在末尾加上一个3 x 1的矩阵,代码如下:
-
首先,我们创建要输入的数据和相应的占位符:
my_array = np.array([[1., 3., 5., 7., 9.], [-2., 0., 2., 4., 6.], [-6., -3., 0., 3., 6.]]) x_vals = np.array([my_array, my_array + 1]) x_data = tf.Variable(x_vals, dtype=tf.float32) -
然后,我们创建用于矩阵乘法和加法的常量:
m1 = tf.constant([[1.], [0.], [-1.], [2.], [4.]]) m2 = tf.constant([[2.]]) a1 = tf.constant([[10.]]) -
接下来,我们声明要立即执行的操作。作为好的编程实践,我们创建执行所需操作的函数:
def prod1(a, b): return tf.matmul(a, b) def prod2(a, b): return tf.matmul(a, b) def add1(a, b): return tf.add(a, b) -
最后,我们嵌套我们的函数并显示结果:
result = add1(prod2(prod1(x_data, m1), m2), a1) print(result.NumPy()) [[ 102.] [ 66.] [ 58.]] [[ 114.] [ 78.] [ 70.]]
使用函数(以及类,我们将进一步介绍)将帮助你编写更清晰的代码。这使得调试更加高效,并且便于代码的维护和重用。
它是如何工作的…
借助即时执行,我们不再需要使用那种"万事俱备"的编程风格(指的是将几乎所有内容都放在程序的全局作用域中;详情请见stackoverflow.com/questions/33779296/what-is-exact-meaning-of-kitchen-sink-in-programming)这种风格在使用 TensorFlow 1.x 时非常常见。目前,你可以选择采用函数式编程风格或面向对象编程风格,就像我们在这个简短示例中展示的那样,你可以将所有操作和计算按逻辑合理的方式排列,使其更易理解:
class Operations():
def __init__(self, a):
self.result = a
def apply(self, func, b):
self.result = func(self.result, b)
return self
operation = (Operations(a=x_data)
.apply(prod1, b=m1)
.apply(prod2, b=m2)
.apply(add1, b=a1))
print(operation.result.NumPy())
类比函数更能帮助你组织代码并提高重用性,这得益于类继承。
还有更多…
在这个示例中,我们需要在运行数据之前声明数据形状并知道操作的结果形状。并非所有情况下都如此。可能会有一维或二维我们事先不知道的情况,或者某些维度在数据处理过程中会发生变化。为此,我们将可以变化(或未知)的维度标记为None。
例如,要初始化一个具有未知行数的变量,我们将写下以下行,然后我们可以分配任意行数的值:
v = tf.Variable(initial_value=tf.random.normal(shape=(1, 5)),
shape=tf.TensorShape((None, 5)))
v.assign(tf.random.normal(shape=(10, 5)))
矩阵乘法具有灵活的行数是可以接受的,因为这不会影响我们操作的安排。当我们将数据馈送给多个大小批次时,这将在后续章节中非常方便。
虽然使用None作为维度允许我们使用可变大小的维度,但我建议在填写维度时尽可能明确。如果我们的数据大小预先已知,则应将该大小明确写入维度中。建议将None用作维度的使用限制在数据的批大小(或者我们同时计算的数据点数)。
处理多层
现在我们已经涵盖了多个操作,接下来我们将讨论如何连接各层,这些层中有数据通过它们传播。在本示例中,我们将介绍如何最佳连接各层,包括自定义层。我们将生成和使用的数据将代表小随机图像。最好通过一个简单的示例理解这种操作,并看看如何使用一些内置层执行计算。我们将探讨的第一层称为移动窗口。我们将在二维图像上执行一个小的移动窗口平均值,然后第二层将是一个自定义操作层。
移动窗口对与时间序列相关的所有内容都非常有用。尽管有专门用于序列的层,但在分析 MRI 扫描(神经影像)或声谱图时,移动窗口可能会很有用。
此外,我们将看到计算图可能变得很大,难以查看。为了解决这个问题,我们还将介绍如何命名操作并为层创建作用域。
准备工作
首先,您必须加载常用的包 - NumPy 和 TensorFlow - 使用以下内容:
import TensorFlow as tf
import NumPy as np
现在让我们继续进行配方。这次事情变得更加复杂和有趣。
如何做...
我们按以下步骤进行。
首先,我们用 NumPy 创建我们的示例二维图像。这个图像将是一个 4x4 像素的图像。我们将在四个维度上创建它;第一个和最后一个维度将具有大小为1(我们保持批维度不同,以便您可以尝试更改其大小)。请注意,某些 TensorFlow 图像函数将操作四维图像。这四个维度是图像编号、高度、宽度和通道,为了使它与一个通道的图像兼容,我们将最后一个维度明确设置为1,如下所示:
batch_size = [1]
x_shape = [4, 4, 1]
x_data = tf.random.uniform(shape=batch_size + x_shape)
要创建跨我们的 4x4 图像的移动窗口平均值,我们将使用一个内置函数,该函数将在形状为 2x2 的窗口上卷积一个常数。我们将使用的函数是conv2d();这个函数在图像处理和 TensorFlow 中经常使用。
此函数对我们指定的窗口和滤波器进行分段乘积。我们还必须在两个方向上指定移动窗口的步幅。在这里,我们将计算四个移动窗口的平均值:左上角、右上角、左下角和右下角的四个像素。我们通过创建一个 2 x 2 窗口,并且在每个方向上使用长度为 2 的步幅来实现这一点。为了取平均值,我们将 2 x 2 窗口与常数 0.25 做卷积,如下所示:
def mov_avg_layer(x):
my_filter = tf.constant(0.25, shape=[2, 2, 1, 1])
my_strides = [1, 2, 2, 1]
layer = tf.nn.conv2d(x, my_filter, my_strides,
padding='SAME', name='Moving_Avg_Window')
return layer
请注意,我们还通过函数的 name 参数将此层命名为 Moving_Avg_Window。
要计算卷积层的输出大小,我们可以使用以下公式:输出 = (W – F + 2P)/S + 1),其中 W 是输入大小,F 是滤波器大小,P 是零填充,S 是步幅。
现在,我们定义一个自定义层,该层将操作移动窗口平均值的 2 x 2 输出。自定义函数将首先将输入乘以另一个 2 x 2 矩阵张量,然后将每个条目加 1。之后,我们对每个元素取 sigmoid,并返回 2 x 2 矩阵。由于矩阵乘法仅适用于二维矩阵,我们需要去除大小为 1 的图像的额外维度。TensorFlow 可以使用内置的 squeeze() 函数来完成这一点。在这里,我们定义了新的层:
def custom_layer(input_matrix):
input_matrix_sqeezed = tf.squeeze(input_matrix)
A = tf.constant([[1., 2.], [-1., 3.]])
b = tf.constant(1., shape=[2, 2])
temp1 = tf.matmul(A, input_matrix_sqeezed)
temp = tf.add(temp1, b) # Ax + b
return tf.sigmoid(temp)
现在,我们必须安排网络中的两个层。我们将通过依次调用一个层函数来完成这一点,如下所示:
first_layer = mov_avg_layer(x_data)
second_layer = custom_layer(first_layer)
现在,我们只需将 4 x 4 图像输入函数中。最后,我们可以检查结果,如下所示:
print(second_layer)
tf.Tensor(
[[0.9385519 0.90720266]
[0.9247799 0.82272065]], shape=(2, 2), dtype=float32)
现在让我们更深入地了解它是如何工作的。
工作原理...
第一层被命名为 Moving_Avg_Window。第二层是称为 Custom_Layer 的一组操作。这两个层处理的数据首先在左侧被折叠,然后在右侧被扩展。正如示例所示,您可以将所有层封装到函数中并依次调用它们,以便后续层处理前一层的输出。
实现损失函数
对于这个示例,我们将介绍在 TensorFlow 中可以使用的一些主要损失函数。损失函数是机器学习算法的关键组成部分。它们衡量模型输出与目标(真实)值之间的距离。
为了优化我们的机器学习算法,我们需要评估结果。在 TensorFlow 中,评估结果取决于指定的损失函数。损失函数告诉 TensorFlow 模型输出与目标结果的好坏程度。在大多数情况下,我们会有一组数据和一个目标,用于训练我们的算法。损失函数比较目标和预测(它衡量模型输出与目标真实值之间的距离),并提供两者之间的数值量化。
准备工作
我们首先启动一个计算图,并加载 matplotlib,一个 Python 绘图包,如下所示:
import matplotlib.pyplot as plt
import TensorFlow as tf
既然我们已经准备好绘制图表了,让我们毫不拖延地进入配方部分。
如何实现...
首先,我们将讨论回归的损失函数,这意味着预测一个连续的因变量。首先,我们将创建一个预测序列和目标作为张量。我们将在-1和1之间的 500 个 x 值上输出结果。有关输出的图表,请参见*如何工作...*部分。使用以下代码:
x_vals = tf.linspace(-1., 1., 500)
target = tf.constant(0.)
L2 范数损失也称为欧几里得损失函数。它只是与目标的距离的平方。在这里,我们将假设目标为零来计算损失函数。L2 范数是一个很好的损失函数,因为它在接近目标时非常曲线,算法可以利用这一事实在接近零时更慢地收敛。我们可以按如下方式实现:
def l2(y_true, y_pred):
return tf.square(y_true - y_pred)
TensorFlow 有一个内置的 L2 范数形式,叫做tf.nn.l2_loss()。这个函数实际上是 L2 范数的一半。换句话说,它与之前的函数相同,只是除以了 2。
L1 范数损失也称为绝对损失函数。它不对差异进行平方处理,而是取绝对值。L1 范数比 L2 范数更适合处理离群值,因为它对较大值的陡峭度较低。需要注意的问题是,L1 范数在目标处不平滑,这可能导致算法无法很好地收敛。它的形式如下所示:
def l1(y_true, y_pred):
return tf.abs(y_true - y_pred)
伪-Huber 损失是Huber 损失函数的连续且平滑的近似。这个损失函数试图通过在接近目标时采用 L1 和 L2 范数的优点,并且对于极端值更加平缓,来结合这两者。它的形式取决于一个额外的参数delta,它决定了它的陡峭度。我们将绘制两个形式,delta1 = 0.25和delta2 = 5,以显示差异,如下所示:
def phuber1(y_true, y_pred):
delta1 = tf.constant(0.25)
return tf.multiply(tf.square(delta1), tf.sqrt(1\. +
tf.square((y_true - y_pred)/delta1)) - 1.)
def phuber2(y_true, y_pred):
delta2 = tf.constant(5.)
return tf.multiply(tf.square(delta2), tf.sqrt(1\. +
tf.square((y_true - y_pred)/delta2)) - 1.)
现在,我们将继续讨论分类问题的损失函数。分类损失函数用于评估在预测分类结果时的损失。通常,我们模型的输出是一个介于0和1之间的实数值。然后,我们选择一个阈值(通常选择 0.5),如果结果大于该阈值,则将结果分类为该类别。接下来,我们将考虑针对分类输出的各种损失函数。
首先,我们需要重新定义我们的预测值(x_vals)和target。我们将保存输出并在下一部分进行绘制。使用以下代码:
x_vals = tf.linspace(-3., 5., 500)
target = tf.fill([500,], 1.)
Hinge 损失主要用于支持向量机,但也可以用于神经网络。它用于计算两个目标类别之间的损失,1 和 -1。在以下代码中,我们使用目标值1,因此我们的预测值越接近1,损失值就越低:
def hinge(y_true, y_pred):
return tf.maximum(0., 1\. - tf.multiply(y_true, y_pred))
二分类情况下的交叉熵损失有时也被称为逻辑损失函数。当我们预测 0 或 1 两个类别时,便会使用该函数。我们希望衡量实际类别(0 或 1)与预测值之间的距离,预测值通常是介于 0 和 1 之间的实数。为了衡量这个距离,我们可以使用信息理论中的交叉熵公式,如下所示:
def xentropy(y_true, y_pred):
return (- tf.multiply(y_true, tf.math.log(y_pred)) -
tf.multiply((1\. - y_true), tf.math.log(1\. - y_pred)))
Sigmoid 交叉熵损失与之前的损失函数非常相似,不同之处在于我们在将 x 值代入交叉熵损失之前,使用 sigmoid 函数对其进行转换,如下所示:
def xentropy_sigmoid(y_true, y_pred):
return tf.nn.sigmoid_cross_entropy_with_logits(labels=y_true,
logits=y_pred)
加权交叉熵损失是 sigmoid 交叉熵损失的加权版本。我们对正类目标进行加权。作为示例,我们将正类目标的权重设为 0.5,如下所示:
def xentropy_weighted(y_true, y_pred):
weight = tf.constant(0.5)
return tf.nn.weighted_cross_entropy_with_logits(labels=y_true,
logits=y_pred,
pos_weight=weight)
Softmax 交叉熵损失适用于非归一化的输出。该函数用于在目标类别只有一个而不是多个时,计算损失。因此,该函数通过 softmax 函数将输出转换为概率分布,然后根据真实的概率分布计算损失函数,如下所示:
def softmax_xentropy(y_true, y_pred):
return tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_pred)
unscaled_logits = tf.constant([[1., -3., 10.]])
target_dist = tf.constant([[0.1, 0.02, 0.88]])
print(softmax_xentropy(y_true=target_dist, y_pred=unscaled_logits))
[ 1.16012561]
稀疏 softmax 交叉熵损失与 softmax 交叉熵损失几乎相同,区别在于目标不是概率分布,而是表示哪个类别是true的索引。我们传入的是该类别的索引,而不是一个稀疏的全零目标向量,其中有一个值是1,如下所示:
def sparse_xentropy(y_true, y_pred):
return tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=y_true,
logits=y_pred)
unscaled_logits = tf.constant([[1., -3., 10.]])
sparse_target_dist = tf.constant([2])
print(sparse_xentropy(y_true=sparse_target_dist,
y_pred=unscaled_logits))
[ 0.00012564]
现在让我们通过将这些损失函数绘制在图上,进一步理解它们是如何工作的。
它是如何工作的...
以下是如何使用matplotlib绘制回归损失函数:
x_vals = tf.linspace(-1., 1., 500)
target = tf.constant(0.)
funcs = [(l2, 'b-', 'L2 Loss'),
(l1, 'r--', 'L1 Loss'),
(phuber1, 'k-.', 'P-Huber Loss (0.25)'),
(phuber2, 'g:', 'P-Huber Loss (5.0)')]
for func, line_type, func_name in funcs:
plt.plot(x_vals, func(y_true=target, y_pred=x_vals),
line_type, label=func_name)
plt.ylim(-0.2, 0.4)
plt.legend(loc='lower right', prop={'size': 11})
plt.show()
我们从前面的代码中得到以下图示:
图 2.1:绘制不同的回归损失函数
以下是如何使用matplotlib绘制不同的分类损失函数:
x_vals = tf.linspace(-3., 5., 500)
target = tf.fill([500,], 1.)
funcs = [(hinge, 'b-', 'Hinge Loss'),
(xentropy, 'r--', 'Cross Entropy Loss'),
(xentropy_sigmoid, 'k-.', 'Cross Entropy Sigmoid Loss'),
(xentropy_weighted, 'g:', 'Weighted Cross Enropy Loss (x0.5)')]
for func, line_type, func_name in funcs:
plt.plot(x_vals, func(y_true=target, y_pred=x_vals),
line_type, label=func_name)
plt.ylim(-1.5, 3)
plt.legend(loc='lower right', prop={'size': 11})
plt.show()
我们从前面的代码中得到以下图示:
图 2.2:分类损失函数的绘图
每条损失曲线都为神经网络优化提供了不同的优势。接下来我们将进一步讨论这些内容。
还有更多...
这里有一张表格,总结了我们刚刚以图形方式描述的不同损失函数的属性和优点:
| 损失函数 | 用途 | 优势 | 缺点 |
|---|---|---|---|
| L2 | 回归 | 更稳定 | 鲁棒性差 |
| L1 | 回归 | 更具鲁棒性 | 稳定性差 |
| 伪-Huber | 回归 | 更具鲁棒性和稳定性 | 多了一个参数 |
| Hinge | 分类 | 在支持向量机(SVM)中创建最大间隔 | 损失无界,受异常值影响 |
| 交叉熵 | 分类 | 更稳定 | 损失无界,鲁棒性差 |
剩余的分类损失函数都与交叉熵损失的类型有关。交叉熵 Sigmoid 损失函数适用于未缩放的 logits,并且优先于计算 Sigmoid 损失,然后计算交叉熵损失,因为 TensorFlow 有更好的内置方法来处理数值边缘情况。Softmax 交叉熵和稀疏 softmax 交叉熵也是如此。
这里描述的大多数分类损失函数适用于两类预测。可以通过对每个预测/目标求和来将其扩展到多类。
在评估模型时,还有许多其他指标可供参考。以下是一些需要考虑的其他指标列表:
| 模型指标 | 描述 |
|---|---|
| R-squared (决定系数) | 对于线性模型,这是因变量方差中由独立数据解释的比例。对于具有较多特征的模型,请考虑使用调整后的 R-squared。 |
| 均方根误差 | 对于连续模型,这衡量了预测值与实际值之间的差异,通过平均平方误差的平方根。 |
| 混淆矩阵 | 对于分类模型,我们查看预测类别与实际类别的矩阵。完美的模型在对角线上有所有计数。 |
| 召回率 | 对于分类模型而言,这是真正例占所有预测正例的比例。 |
| 精确度 | 对于分类模型而言,这是真正例占所有实际正例的比例。 |
| F-score | 对于分类模型而言,这是精确度和召回率的调和平均值。 |
在选择正确的指标时,您必须同时评估您要解决的问题(因为每个指标的行为会有所不同,并且根据手头的问题,一些损失最小化策略对我们的问题可能比其他更好),并且对神经网络的行为进行实验。
实施反向传播
使用 TensorFlow 的好处之一是它可以跟踪操作并根据反向传播自动更新模型变量。在这个教程中,我们将介绍如何在训练机器学习模型时利用这一方面。
准备就绪
现在,我们将介绍如何以使得损失函数最小化的方式改变模型中的变量。我们已经学会了如何使用对象和操作,以及如何创建损失函数来衡量预测与目标之间的距离。现在,我们只需告诉 TensorFlow 如何通过网络反向传播错误,以便更新变量,使损失函数最小化。这通过声明一个优化函数来实现。一旦声明了优化函数,TensorFlow 将遍历计算图中的所有计算,找出反向传播项。当我们输入数据并最小化损失函数时,TensorFlow 将相应地修改网络中的变量。
对于这个食谱,我们将做一个非常简单的回归算法。我们将从一个正态分布中采样随机数,均值为 1,标准差为 0.1。然后,我们将这些数字通过一次操作,操作是将它们乘以一个权重张量,然后加上一个偏置张量。由此,损失函数将是输出和目标之间的 L2 范数。我们的目标将与输入高度相关,因此任务不会太复杂,但该食谱会非常具有示范性,并且可以轻松地用于更复杂的问题。
第二个例子是一个非常简单的二分类算法。在这里,我们将从两个正态分布中生成 100 个数字,N(-3,1) 和 N(3,1)。所有来自 N(-3, 1) 的数字将属于目标类别 0,而所有来自 N(3, 1) 的数字将属于目标类别 1。用于区分这些类别的模型(它们是完全可分的)将再次是一个线性模型,并根据 sigmoid 交叉熵损失函数进行优化,因此,首先对模型结果进行 sigmoid 转换,然后计算交叉熵损失函数。
虽然指定合适的学习率有助于算法的收敛,但我们还必须指定一种优化类型。从前面的两个例子中,我们使用的是标准的梯度下降法。这是通过 tf.optimizers.SGD TensorFlow 函数实现的。
如何操作...
我们将从回归示例开始。首先,我们加载通常伴随我们食谱的数值 Python 库,NumPy 和 TensorFlow:
import NumPy as np
import TensorFlow as tf
接下来,我们创建数据。为了使一切易于复制,我们希望将随机种子设置为特定值。我们将在我们的食谱中始终重复这一点,这样我们就能精确获得相同的结果;你可以自己检查,通过简单地更改种子数值,随机性是如何影响食谱中的结果的。
此外,为了确保目标和输入有很好的相关性,可以绘制这两个变量的散点图:
np.random.seed(0)
x_vals = np.random.normal(1, 0.1, 100).astype(np.float32)
y_vals = (x_vals * (np.random.normal(1, 0.05, 100) - 0.5)).astype(np.float32)
plt.scatter(x_vals, y_vals)
plt.show()
图 2.3:x_vals 和 y_vals 的散点图
我们将网络的结构(类型为 bX + a 的线性模型)添加为一个函数:
def my_output(X, weights, biases):
return tf.add(tf.multiply(X, weights), biases)
接下来,我们将我们的 L2 损失函数添加到网络的结果中进行应用:
def loss_func(y_true, y_pred):
return tf.reduce_mean(tf.square(y_pred - y_true))
现在,我们必须声明一种方法来优化图中的变量。我们声明一个优化算法。大多数优化算法需要知道在每次迭代中应该走多远。这种距离由学习率控制。设置正确的值对于我们处理的问题是特定的,因此我们只能通过实验来找到合适的设置。无论如何,如果我们的学习率太高,算法可能会超过最小值,但如果学习率太低,算法可能会需要太长时间才能收敛。
学习率对收敛有很大影响,我们将在本节末尾再次讨论它。虽然我们使用的是标准的梯度下降算法,但还有许多其他替代选择。例如,有些优化算法的操作方式不同,取决于问题的不同,它们可以达到更好的或更差的最优解。关于不同优化算法的全面概述,请参见 Sebastian Ruder 在 另见 部分列出的文章:
my_opt = tf.optimizers.SGD(learning_rate=0.02)
关于最优学习率的理论有很多。这是机器学习算法中最难解决的问题之一。有关学习率与特定优化算法之间关系的好文章,列在此食谱末尾的 另见 部分。
现在,我们可以初始化网络变量(weights 和 biases),并设置一个记录列表(命名为 history),以帮助我们可视化优化步骤:
tf.random.set_seed(1)
np.random.seed(0)
weights = tf.Variable(tf.random.normal(shape=[1]))
biases = tf.Variable(tf.random.normal(shape=[1]))
history = list()
最后一步是循环我们的训练算法,并告诉 TensorFlow 进行多次训练。我们将训练 100 次,并在每 25 次迭代后打印结果。为了训练,我们将选择一个随机的 x 和 y 输入,并将其输入到图中。TensorFlow 会自动计算损失,并略微调整权重和偏置,以最小化损失:
for i in range(100):
rand_index = np.random.choice(100)
rand_x = [x_vals[rand_index]]
rand_y = [y_vals[rand_index]]
with tf.GradientTape() as tape:
predictions = my_output(rand_x, weights, biases)
loss = loss_func(rand_y, predictions)
history.append(loss.NumPy())
gradients = tape.gradient(loss, [weights, biases])
my_opt.apply_gradients(zip(gradients, [weights, biases]))
if (i + 1) % 25 == 0:
print(f'Step # {i+1} Weights: {weights.NumPy()} Biases: {biases.NumPy()}')
print(f'Loss = {loss.NumPy()}')
Step # 25 Weights: [-0.58009654] Biases: [0.91217995]
Loss = 0.13842473924160004
Step # 50 Weights: [-0.5050226] Biases: [0.9813488]
Loss = 0.006441597361117601
Step # 75 Weights: [-0.4791306] Biases: [0.9942327]
Loss = 0.01728087291121483
Step # 100 Weights: [-0.4777394] Biases: [0.9807473]
Loss = 0.05371852591633797
在循环中,tf.GradientTape() 使 TensorFlow 能够追踪计算过程,并计算相对于观测变量的梯度。GradientTape() 范围内的每个变量都会被监控(请记住,常量不会被监控,除非你明确使用命令 tape.watch(constant) 来监控它)。一旦完成监控,你可以计算目标相对于一组源的梯度(使用命令 tape.gradient(target, sources)),并获得一个渐变的急切张量,可以应用于最小化过程。此操作会自动通过用新值更新源(在我们的例子中是 weights 和 biases 变量)来完成。
当训练完成后,我们可以可视化优化过程在连续梯度应用下的变化:
plt.plot(history)
plt.xlabel('iterations')
plt.ylabel('loss')
plt.show()
图 2.4:我们方法中 L2 损失随迭代的变化
到这个阶段,我们将介绍简单分类示例的代码。我们可以使用相同的 TensorFlow 脚本,做一些更新。记住,我们将尝试找到一组最优的权重和偏置,使得数据能够分成两类。
首先,我们从两个不同的正态分布 N(-3, 1) 和 N(3, 1) 拉取数据。我们还将生成目标标签,并可视化这两类如何在我们的预测变量上分布:
np.random.seed(0)
x_vals = np.concatenate((np.random.normal(-3, 1, 50),
np.random.normal(3, 1, 50))
).astype(np.float32)
y_vals = np.concatenate((np.repeat(0., 50), np.repeat(1., 50))).astype(np.float32)
plt.hist(x_vals[y_vals==1], color='b')
plt.hist(x_vals[y_vals==0], color='r')
plt.show()
图 2.5:x_vals 上的类别分布
由于这个问题的特定损失函数是 Sigmoid 交叉熵,我们更新我们的损失函数:
def loss_func(y_true, y_pred):
return tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(labels=y_true,
logits=y_pred))
接下来,我们初始化变量:
tf.random.set_seed(1)
np.random.seed(0)
weights = tf.Variable(tf.random.normal(shape=[1]))
biases = tf.Variable(tf.random.normal(shape=[1]))
history = list()
最后,我们对一个随机选择的数据点进行了数百次循环,并相应地更新了weights和biases变量。像之前一样,每进行 25 次迭代,我们会打印出变量的值和损失值:
for i in range(100):
rand_index = np.random.choice(100)
rand_x = [x_vals[rand_index]]
rand_y = [y_vals[rand_index]]
with tf.GradientTape() as tape:
predictions = my_output(rand_x, weights, biases)
loss = loss_func(rand_y, predictions)
history.append(loss.NumPy())
gradients = tape.gradient(loss, [weights, biases])
my_opt.apply_gradients(zip(gradients, [weights, biases]))
if (i + 1) % 25 == 0:
print(f'Step {i+1} Weights: {weights.NumPy()} Biases: {biases.NumPy()}')
print(f'Loss = {loss.NumPy()}')
Step # 25 Weights: [-0.01804185] Biases: [0.44081175]
Loss = 0.5967269539833069
Step # 50 Weights: [0.49321094] Biases: [0.37732077]
Loss = 0.3199256658554077
Step # 75 Weights: [0.7071932] Biases: [0.32154965]
Loss = 0.03642747551202774
Step # 100 Weights: [0.8395616] Biases: [0.30409005]
Loss = 0.028119442984461784
一个图表,也可以在这种情况下,揭示优化过程的进展:
plt.plot(history)
plt.xlabel('iterations')
plt.ylabel('loss')
plt.show()
图 2.6:我们方法中通过迭代的 Sigmoid 交叉熵损失
图表的方向性很清晰,尽管轨迹有些崎岖,因为我们一次只学习一个示例,从而使学习过程具有决定性的随机性。图表还可能指出需要稍微降低学习率的必要性。
它是如何工作的...
总结和解释一下,对于这两个示例,我们做了以下操作:
-
我们创建了数据。两个示例都需要将数据加载到计算网络所用的特定变量中。
-
我们初始化了变量。我们使用了一些随机高斯值,但初始化本身是一个独立的话题,因为最终的结果可能在很大程度上取决于我们如何初始化网络(只需在初始化前更改随机种子即可找到答案)。
-
我们创建了一个损失函数。回归使用了 L2 损失,分类使用了交叉熵损失。
-
我们定义了一个优化算法。两个算法都使用了梯度下降法。
-
我们遍历了随机数据样本,以逐步更新我们的变量。
还有更多...
正如我们之前提到的,优化算法对学习率的选择很敏感。总结这一选择的影响非常重要,可以简明扼要地表述如下:
| 学习率大小 | 优点/缺点 | 用途 |
|---|---|---|
| 较小的学习率 | 收敛较慢,但结果更准确 | 如果解不稳定,首先尝试降低学习率 |
| 较大的学习率 | 准确性较低,但收敛速度较快 | 对于某些问题,有助于防止解停滞 |
有时,标准的梯度下降算法可能会被卡住或显著变慢。这可能发生在优化停留在鞍点的平坦区域时。为了解决这个问题,解决方案是考虑动量项,它加上了上一步梯度下降值的一部分。你可以通过在tf.optimizers.SGD中设置动量和 Nesterov 参数,以及你的学习率,来实现这个解决方案(详细信息请参见 www.TensorFlow.org/api_docs/python/tf/keras/optimizers/SGD)。
另一种变体是对我们模型中每个变量的优化器步骤进行变化。理想情况下,我们希望对移动较小的变量采取较大的步长,对变化较快的变量采取较小的步长。我们不会深入探讨这种方法的数学原理,但这种思想的常见实现被称为Adagrad 算法。该算法考虑了变量梯度的整个历史。在 TensorFlow 中,这个函数被称为 AdagradOptimizer() (www.TensorFlow.org/api_docs/python/tf/keras/optimizers/Adagrad)。
有时,Adagrad 会因为考虑到整个历史而过早地将梯度推到零。解决这个问题的方法是限制我们使用的步数。这就是Adadelta 算法。我们可以通过使用 AdadeltaOptimizer() 函数来应用这一方法 (www.TensorFlow.org/api_docs/python/tf/keras/optimizers/Adadelta)。
还有一些其他不同梯度下降算法的实现。关于这些,请参阅 TensorFlow 文档 www.TensorFlow.org/api_docs/python/tf/keras/optimizers。
另见
关于优化算法和学习率的一些参考文献,请参阅以下论文和文章:
-
本章的食谱如下:
-
实现损失函数部分。
-
实现反向传播部分。
-
-
Kingma, D., Jimmy, L. Adam: 随机优化方法。ICLR 2015
arxiv.org/pdf/1412.6980.pdf -
Ruder, S. 梯度下降优化算法概述。2016
arxiv.org/pdf/1609.04747v1.pdf -
Zeiler, M. ADADelta: 一种自适应学习率方法。2012
arxiv.org/pdf/1212.5701.pdf
使用批量和随机训练
当 TensorFlow 根据反向传播更新我们的模型变量时,它可以处理从单个数据观察(如我们在之前的食谱中所做的)到一次处理大量数据的情况。仅处理一个训练示例可能导致非常不稳定的学习过程,而使用过大的批量则可能在计算上非常昂贵。选择合适的训练类型对于使我们的机器学习算法收敛到一个解非常关键。
准备工作
为了让 TensorFlow 计算反向传播所需的变量梯度,我们必须在一个或多个样本上测量损失。随机训练每次只处理一个随机抽样的数据-目标对,就像我们在前面的示例中所做的那样。另一个选择是一次处理一大部分训练样本,并对梯度计算进行损失平均。训练批次的大小可以有所不同,最大可以包括整个数据集。这里,我们将展示如何将之前使用随机训练的回归示例扩展为批量训练。
我们将首先加载 NumPy、matplotlib 和 TensorFlow,如下所示:
import matplotlib as plt
import NumPy as np
import TensorFlow as tf
现在我们只需编写脚本并在 如何做... 部分中测试我们的配方。
如何做...
我们从声明批次大小开始。这将是我们一次性通过计算图输入的观察数据量:
batch_size = 20
接下来,我们只需对之前用于回归问题的代码进行一些小的修改:
np.random.seed(0)
x_vals = np.random.normal(1, 0.1, 100).astype(np.float32)
y_vals = (x_vals * (np.random.normal(1, 0.05, 100) - 0.5)).astype(np.float32)
def loss_func(y_true, y_pred):
return tf.reduce_mean(tf.square(y_pred - y_true))
tf.random.set_seed(1)
np.random.seed(0)
weights = tf.Variable(tf.random.normal(shape=[1]))
biases = tf.Variable(tf.random.normal(shape=[1]))
history_batch = list()
for i in range(50):
rand_index = np.random.choice(100, size=batch_size)
rand_x = [x_vals[rand_index]]
rand_y = [y_vals[rand_index]]
with tf.GradientTape() as tape:
predictions = my_output(rand_x, weights, biases)
loss = loss_func(rand_y, predictions)
history_batch.append(loss.NumPy())
gradients = tape.gradient(loss, [weights, biases])
my_opt.apply_gradients(zip(gradients, [weights, biases]))
if (i + 1) % 25 == 0:
print(f'Step # {i+1} Weights: {weights.NumPy()} \
Biases: {biases.NumPy()}')
print(f'Loss = {loss.NumPy()}')
自从上一个示例以来,我们已经学会了在网络和成本函数中使用矩阵乘法。此时,我们只需要处理由更多行组成的输入,即批量而非单个样本。我们甚至可以将其与之前的方法进行比较,后者我们现在可以称之为随机优化:
tf.random.set_seed(1)
np.random.seed(0)
weights = tf.Variable(tf.random.normal(shape=[1]))
biases = tf.Variable(tf.random.normal(shape=[1]))
history_stochastic = list()
for i in range(50):
rand_index = np.random.choice(100, size=1)
rand_x = [x_vals[rand_index]]
rand_y = [y_vals[rand_index]]
with tf.GradientTape() as tape:
predictions = my_output(rand_x, weights, biases)
loss = loss_func(rand_y, predictions)
history_stochastic.append(loss.NumPy())
gradients = tape.gradient(loss, [weights, biases])
my_opt.apply_gradients(zip(gradients, [weights, biases]))
if (i + 1) % 25 == 0:
print(f'Step # {i+1} Weights: {weights.NumPy()} \
Biases: {biases.NumPy()}')
print(f'Loss = {loss.NumPy()}')
运行代码后,将使用批量数据重新训练我们的网络。此时,我们需要评估结果,获取一些关于它如何工作的直觉,并反思这些结果。让我们继续进入下一部分。
它是如何工作的...
批量训练和随机训练在优化方法和收敛性方面有所不同。找到合适的批量大小可能是一个挑战。为了观察批量训练和随机训练在收敛上的差异,建议你调整批次大小至不同的水平。
两种方法的视觉对比能更好地解释,使用批量训练如何在这个问题上得到与随机训练相同的优化结果,尽管在过程中波动较少。以下是生成同一回归问题的随机训练和批量损失图的代码。请注意,批量损失曲线更加平滑,而随机损失曲线则更加不稳定:
plt.plot(history_stochastic, 'b-', label='Stochastic Loss')
plt.plot(history_batch, 'r--', label='Batch Loss')
plt.legend(loc='upper right', prop={'size': 11})
plt.show()
图 2.7:使用随机优化和批量优化时的 L2 损失比较
现在我们的图表显示了一个更平滑的趋势线。存在的波动问题可以通过降低学习率和调整批次大小来解决。
还有更多...
| 训练类型 | 优势 | 劣势 |
|---|---|---|
| 随机训练 | 随机性有助于摆脱局部最小值 | 通常需要更多的迭代才能收敛 |
| 批量训练 | 更快地找到最小值 | 计算所需资源更多 |
将一切结合在一起
在本节中,我们将结合迄今为止的所有内容,并创建一个用于鸢尾花数据集的分类器。鸢尾花数据集在第一章《TensorFlow 入门》中的与数据源一起使用部分中有更详细的描述。我们将加载该数据并创建一个简单的二分类器,用于预测一朵花是否为鸢尾花 setosa。需要明确的是,这个数据集有三种花卉,但我们只预测一朵花是否为某种单一的品种,即鸢尾花 setosa 或非 setosa,从而使我们得到一个二分类器。
准备开始
我们将首先加载所需的库和数据,然后相应地转换目标数据。首先,我们加载所需的库。对于鸢尾花数据集,我们需要 TensorFlow Datasets 模块,这是我们在之前的例子中没有用到的。请注意,我们还在这里加载了matplotlib,因为我们接下来想要绘制结果图:
import matplotlib.pyplot as plt
import NumPy as np
import TensorFlow as tf
import TensorFlow_datasets as tfds
如何操作...
作为起点,我们首先使用全局变量声明我们的批次大小:
batch_size = 20
接下来,我们加载鸢尾花数据集。我们还需要将目标数据转换为1或0,表示该目标是否为 setosa。由于鸢尾花数据集中 setosa 的标记为0,我们将把所有目标值为0的标签改为1,其他值则改为0。我们还将只使用两个特征,花瓣长度和花瓣宽度。这两个特征分别是数据集每行中的第三和第四个条目:
iris = tfds.load('iris', split='train[:90%]', W)
iris_test = tfds.load('iris', split='train[90%:]', as_supervised=True)
def iris2d(features, label):
return features[2:], tf.cast((label == 0), dtype=tf.float32)
train_generator = (iris
.map(iris2d)
.shuffle(buffer_size=100)
.batch(batch_size)
)
test_generator = iris_test.map(iris2d).batch(1)
如前一章所示,我们使用 TensorFlow 的数据集函数加载数据并执行必要的转换,通过创建一个数据生成器动态地向网络提供数据,而不是将数据保存在内存中的 NumPy 矩阵中。第一步,我们加载数据,并指定要将其拆分(使用参数split='train[:90%]'和split='train[90%:]')。这样我们可以保留数据集的 10%作为模型评估所用,使用未参与训练阶段的数据。
我们还指定了参数as_supervised=True,该参数将允许我们在遍历数据集时,将数据作为特征和标签的元组进行访问。
现在,我们通过应用连续的转换,将数据集转换为一个可迭代的生成器。我们打乱数据,定义生成器返回的批次大小,最重要的是,我们应用一个自定义函数,过滤并同时转换数据集返回的特征和标签。
然后,我们定义线性模型。该模型将采用通常的形式 bX+a。请记住,TensorFlow 内置了带有 sigmoid 函数的损失函数,因此我们只需要在 sigmoid 函数之前定义模型的输出:
def linear_model(X, A, b):
my_output = tf.add(tf.matmul(X, A), b)
return tf.squeeze(my_output)
现在,我们使用 TensorFlow 内置的sigmoid_cross_entropy_with_logits()函数添加 sigmoid 交叉熵损失函数:
def xentropy(y_true, y_pred):
return tf.reduce_mean(
tf.nn.sigmoid_cross_entropy_with_logits(labels=y_true,
logits=y_pred))
我们还必须告诉 TensorFlow 如何通过声明优化方法来优化我们的计算图。我们将希望最小化交叉熵损失。我们还选择了0.02作为我们的学习率:
my_opt = tf.optimizers.SGD(learning_rate=0.02)
现在,我们将使用 300 次迭代来训练我们的线性模型。我们将输入所需的三个数据点:花瓣长度、花瓣宽度和目标变量。每 30 次迭代,我们会打印变量的值:
tf.random.set_seed(1)
np.random.seed(0)
A = tf.Variable(tf.random.normal(shape=[2, 1]))
b = tf.Variable(tf.random.normal(shape=[1]))
history = list()
for i in range(300):
iteration_loss = list()
for features, label in train_generator:
with tf.GradientTape() as tape:
predictions = linear_model(features, A, b)
loss = xentropy(label, predictions)
iteration_loss.append(loss.NumPy())
gradients = tape.gradient(loss, [A, b])
my_opt.apply_gradients(zip(gradients, [A, b]))
history.append(np.mean(iteration_loss))
if (i + 1) % 30 == 0:
print(f'Step # {i+1} Weights: {A.NumPy().T} \
Biases: {b.NumPy()}')
print(f'Loss = {loss.NumPy()}')
Step # 30 Weights: [[-1.1206311 1.2985772]] Biases: [1.0116111]
Loss = 0.4503694772720337
…
Step # 300 Weights: [[-1.5611029 0.11102282]] Biases: [3.6908474]
Loss = 0.10326375812292099
如果我们绘制损失与迭代次数的关系图,我们可以从损失随时间平滑减少的趋势中看出,线性模型的学习过程相对简单:
plt.plot(history)
plt.xlabel('iterations')
plt.ylabel('loss')
plt.show()
图 2.8:Iris setosa 数据的交叉熵误差
我们将通过检查在保留的测试数据上的表现来结束。本次我们仅取测试数据集中的示例。如预期的那样,得到的交叉熵值与训练时的值相似:
predictions = list()
labels = list()
for features, label in test_generator:
predictions.append(linear_model(features, A, b).NumPy())
labels.append(label.NumPy()[0])
test_loss = xentropy(np.array(labels), np.array(predictions)).NumPy()
print(f"test cross-entropy is {test_loss}")
test cross-entropy is 0.10227929800748825
下一组命令提取模型变量并在图表上绘制这条线:
coefficients = np.ravel(A.NumPy())
intercept = b.NumPy()
# Plotting batches of examples
for j, (features, label) in enumerate(train_generator):
setosa_mask = label.NumPy() == 1
setosa = features.NumPy()[setosa_mask]
non_setosa = features.NumPy()[~setosa_mask]
plt.scatter(setosa[:,0], setosa[:,1], c='red', label='setosa')
plt.scatter(non_setosa[:,0], non_setosa[:,1], c='blue', label='Non-setosa')
if j==0:
plt.legend(loc='lower right')
# Computing and plotting the decision function
a = -coefficients[0] / coefficients[1]
xx = np.linspace(plt.xlim()[0], plt.xlim()[1], num=10000)
yy = a * xx - intercept / coefficients[1]
on_the_plot = (yy > plt.ylim()[0]) & (yy < plt.ylim()[1])
plt.plot(xx[on_the_plot], yy[on_the_plot], 'k--')
plt.xlabel('Petal Length')
plt.ylabel('Petal Width')
plt.show()
结果图表位于*它是如何工作的...*一节中,我们还讨论了所获得结果的有效性和可重现性。
它是如何工作的...
我们的目标是通过仅使用花瓣宽度和花瓣长度,在 Iris setosa 点和其他两种物种之间拟合一条直线。如果我们绘制这些点,并用一条线将分类为零的区域与分类为一的区域分开,我们可以看到我们已经实现了这一目标:
图 2.9:Iris setosa 和非 setosa 的花瓣宽度与花瓣长度关系图;实线是我们在 300 次迭代后获得的线性分隔线
分隔线的定义方式取决于数据、网络架构和学习过程。不同的初始情况,甚至由于神经网络权重的随机初始化,可能会为你提供稍微不同的解决方案。
还有更多...
虽然我们成功地实现了用一条线分隔两个类别的目标,但这可能不是最适合分隔两个类别的模型。例如,在添加新观察数据后,我们可能会发现我们的解决方案不能很好地分隔这两个类别。随着我们进入下一章,我们将开始处理那些通过提供测试、随机化和专用层来解决这些问题的方案,这些方法将增强我们方案的泛化能力。
另见
-
关于 Iris 数据集的更多信息,请参见文档:
archive.ics.uci.edu/ml/datasets/iris。 -
如果你想更深入了解机器学习算法的决策边界绘制,我们强烈推荐 Navoneel Chakrabarty 在 Medium 上发表的这篇精彩文章:
towardsdatascience.com/decision-boundary-visualization-a-z-6a63ae9cca7d
第三章:Keras
本章将重点介绍名为 Keras 的高层 TensorFlow API。
本章结束时,您应该对以下内容有更好的理解:
-
Keras 顺序 API
-
Keras 功能 API
-
Keras 子类化 API
-
Keras 预处理 API
介绍
在上一章中,我们讲解了 TensorFlow 的基础知识,现在我们已经能够设置计算图。本章将介绍 Keras,一个用 Python 编写的高层神经网络 API,支持多个后端,TensorFlow 是其中之一。Keras 的创建者是法国软件工程师兼 AI 研究员 François Chollet,他最初为个人使用而开发 Keras,直到 2015 年开源。Keras 的主要目标是提供一个易于使用且易于访问的库,以便快速实验。
TensorFlow v1 存在可用性问题,特别是 API 庞大且有时令人困惑。例如,TensorFlow v1 提供了两个高层 API:
-
Estimator API(在 1.1 版本中添加)用于在本地主机或分布式环境中训练模型。
-
Keras API 在之后的版本(1.4.0 发布)中被添加,并旨在用于快速原型设计。
随着 TensorFlow v2 的发布,Keras 成为官方高层 API。Keras 可以扩展并适应不同的用户需求,从研究到应用开发,从模型训练到部署。Keras 提供四大优势:易于使用(不牺牲灵活性和性能)、模块化、可组合、可扩展。
TensorFlow Keras API 与 Keras API 相同。然而,Keras 在 TensorFlow 后端中的实现已针对 TensorFlow 进行了优化。它集成了 TensorFlow 特有的功能,如急切执行、数据管道和 Estimator。
Keras 作为独立库与 Keras 作为 TensorFlow 集成的实现之间的区别仅在于导入方式。
这是导入 Keras API 规范的命令:
import keras
这是 TensorFlow 对 Keras API 规范的实现:
import tensorflow as tf
from tensorflow import keras
现在,让我们从发现 Keras 的基本构建块开始。
理解 Keras 层
Keras 层是 Keras 模型的基本构建块。每一层接收数据作为输入,执行特定任务,并返回输出。
Keras 包括广泛的内置层:
-
**核心层:**Dense、Activation、Flatten、Input、Reshape、Permute、RepeatVector、SpatialDropOut 等。
-
**卷积层用于卷积神经网络:**Conv1D、Conv2D、SeparableConv1D、Conv3D、Cropping2D 等。
-
池化层执行下采样操作以减少特征图:MaxPooling1D、AveragePooling2D 和 GlobalAveragePooling3D。
-
**循环层用于循环神经网络处理循环或序列数据:**RNN、SimpleRNN、GRU、LSTM、ConvLSTM2D 等。
-
嵌入层,仅用作模型中的第一层,将正整数转换为固定大小的稠密向量。
-
合并层: 加法、减法、乘法、平均值、最大值、最小值等多种操作。
-
高级激活层: LeakyReLU、PReLU、Softmax、ReLU 等。
-
批量归一化层,它会在每个批次中归一化前一层的激活值。
-
噪声层: GaussianNoise、GaussianDropout 和 AlphaDropout。
-
层封装器: TimeDistributed 将层应用于输入的每个时间切片,Bidirectional 是 RNN 的双向封装器。
-
局部连接层: LocallyConnected1D 和 LocallyConnected2D。它们的工作方式类似于 Conv1D 或 Conv2D,但不共享权重。
我们还可以按照本章中 Keras 子类化 API 部分的说明编写自己的 Keras 层。
准备开始
首先,我们将回顾一些在所有 Keras 层中常见的方法。这些方法对于了解层的配置和状态非常有用。
如何操作...
-
让我们从层的权重开始。权重可能是层中最重要的概念;它决定了输入对输出的影响程度,表示了层的状态。
get_weights()函数返回层的权重,以 NumPy 数组的列表形式:layer.get_weights()set_weights()方法可以通过一组 Numpy 数组来设置层的权重:layer.set_weights(weights) -
正如我们将在 Keras 函数式 API 配方中解释的那样,有时神经网络的拓扑结构不是线性的。在这种情况下,层可以在网络中多次使用(共享层)。如果层是单一节点(无共享层),我们可以通过这个命令轻松获得层的输入和输出:
layer.input layer.output或者如果层包含多个节点,可以使用这个:
layer.get_input_at(node_index) layer.get_output_at(node_index) -
如果层是单一节点(无共享层),我们还可以通过这个命令轻松获得层的输入和输出形状:
layer.input_shape layer.output_shape或者如果层包含多个节点,可以使用这个:
layer.get_input_shape_at(node_index) layer.get_output_shape_at(node_index) -
现在,我们将讨论层的配置。由于相同的层可能会实例化多次,配置中不包括权重或连接信息。
get_config()函数返回一个字典,包含层的配置:layer.get_config()from_config()方法用于实例化层的配置:layer.from_config(config)请注意,层配置存储在关联数组(Python 字典)中,这是一种将键映射到值的数据结构。
它是如何工作的...
层是模型的构建模块。Keras 提供了广泛的构建层和有用的方法,让你更深入地了解模型的工作原理。
使用 Keras,我们可以通过三种方式构建模型:使用 Sequential、Functional 或 Subclassing API。稍后我们会看到,只有最后两种 API 允许访问层。
另见
有关 Keras 层 API 的一些参考文档,请参见以下文档:
-
Keras 层 API 文档:
keras.io/layers/about-keras-layers/ -
TensorFlow Keras 层 API 文档:
www.tensorflow.org/api_docs/python/tf/keras/layers
使用 Keras Sequential API
Keras 的主要目标是简化深度学习模型的创建。Sequential API 允许我们创建顺序模型,这是一种线性堆叠的层。通过逐层连接的模型可以解决许多问题。要创建一个顺序模型,我们需要实例化一个 Sequential 类,创建一些模型层并将它们添加进去。
我们将从创建顺序模型开始,到编译、训练和评估步骤,最后实现模型的预测。在这个配方结束时,您将拥有一个可以在生产中部署的 Keras 模型。
准备工作
本文将介绍创建顺序模型的主要方法,并使用 Keras Sequential API 组装层来构建模型。
首先,我们加载 TensorFlow 和 NumPy,如下所示:
import tensorflow as tf
from tensorflow import keras
from keras.layers import Dense
import numpy as np
我们准备好继续解释如何实现它。
如何实现...
-
首先,我们将创建一个顺序模型。Keras 提供了两种等效的方式来创建顺序模型。我们首先通过将层实例的列表作为数组传递给构造函数来开始。我们将通过输入以下代码构建一个多类分类器(10 个类别)完全连接的模型,也称为多层感知机。
model = tf.keras.Sequential([ # Add a fully connected layer with 1024 units to the model tf.keras.layers.Dense(1024, input_dim=64), # Add an activation layer with ReLU activation function tf.keras.layers.Activation('relu'), # Add a fully connected layer with 256 units to the model tf.keras.layers.Dense(256), # Add an activation layer with ReLU activation function tf.keras.layers.Activation('relu'), # Add a fully connected layer with 10 units to the model tf.keras.layers.Dense(10), # Add an activation layer with softmax activation function tf.keras.layers.Activation('softmax') ])创建顺序模型的另一种方式是实例化一个 Sequential 类,然后通过
.add()方法添加层。model = tf.keras.Sequential() # Add a fully connected layer with 1024 units to the model model.add(tf.keras.layers.Dense(1024, input_dim=64)) # Add an activation layer with ReLU activation function model.add(tf.keras.layers.Activation(relu)) # Add a fully connected layer with 256 units to the model model.add(tf.keras.layers.Dense(256)) # Add an activation layer with ReLU activation function model.add(tf.keras.layers.Activation('relu')) # Add a fully connected Layer with 10 units to the model model.add(tf.keras.layers.Dense(10)) # Add an activation layer with softmax activation function model.add(tf.keras.layers.Activation('softmax')) -
让我们仔细看看层的配置。
tf.keras.layersAPI 提供了许多内置层,并且还提供了创建我们自己的层的 API。在大多数情况下,我们可以将这些参数设置为层的构造函数:-
我们可以通过指定内置函数的名称或可调用对象来添加激活函数。该函数决定一个神经元是否应该被激活。默认情况下,层没有激活函数。以下是创建带有激活函数的层的两种方式。请注意,您不需要运行以下代码,这些层未分配给变量。
# Creation of a dense layer with a sigmoid activation function: Dense(256, activation='sigmoid') # Or: Dense(256, activation=tf.keras.activations.sigmoid) -
我们还可以通过传递内置初始化器的字符串标识符或可调用对象来指定初始权重(内核和偏置)的初始化策略。内核默认设置为“Glorot uniform”初始化器,偏置设置为零。
# A dense layer with a kernel initialized to a truncated normal distribution: Dense(256, kernel_initializer='random_normal') # A dense layer with a bias vector initialized with a constant value of 5.0: Dense(256, bias_initializer=tf.keras.initializers.Constant(value=5)) -
我们还可以为内核和偏置指定正则化器,如 L1(也称为 Lasso)或 L2(也称为 Ridge)正则化。默认情况下,不应用正则化。正则化器旨在通过惩罚具有大权重的模型来防止过拟合。这些惩罚被纳入网络优化的损失函数中。
# A dense layer with L1 regularization of factor 0.01 applied to the kernel matrix: Dense(256, kernel_regularizer=tf.keras.regularizers.l1(0.01)) # A dense layer with L2 regularization of factor 0.01 applied to the bias vector: Dense(256, bias_regularizer=tf.keras.regularizers.l2(0.01))
-
-
在 Keras 中,强烈建议为第一层设置输入形状。然而,与表面上看起来的不同,输入层不是一层,而是一个张量。它的形状必须与我们的训练数据相同。以下层将执行自动形状推断;它们的形状是根据前一层的单元计算出来的。
每种类型的层需要输入特定维度的张量,因此有不同的方法来指定输入形状,具体取决于层的种类。在这里,我们将重点介绍 Dense 层,因此我们将使用
input_dim参数。由于权重的形状取决于输入的大小,如果没有预先指定输入形状,模型将没有权重:模型没有被构建。在这种情况下,你无法调用Layer类的任何方法,例如summary、layers、weights等。在这个示例中,我们将创建包含 64 个特征的数据集,并处理每批 10 个样本。我们的输入数据的形状是(10,64),即(
batch_size,number_of_features)。默认情况下,Keras 模型定义为支持任何批次大小,因此批次大小不是强制性的。我们只需要通过input_dim参数为第一层指定特征数量。Dense(256, input_dim=(64))然而,为了效率的考虑,我们可以通过
batch_size参数强制设置批次大小。Dense(256, input_dim=(64), batch_size=10) -
在学习阶段之前,我们的模型需要进行配置。通过
compile方法来完成这一配置。我们需要指定:-
用于训练我们神经网络的优化算法。我们可以从
tf.keras.optimizers模块传递一个优化器实例。例如,我们可以使用tf.keras.optimizers.RMSprop的实例,或者使用字符串'RMSprop',它是一个实现了RMSprop算法的优化器。 -
一个损失函数,也叫做目标函数或优化得分函数,目的是最小化模型。它可以是现有损失函数的名称(例如
categorical_crossentropy或mse),一个符号化的 TensorFlow 损失函数(如tf.keras.losses.MAPE),或一个自定义的损失函数,它接收两个张量(真实张量和预测张量)并为每个数据点返回一个标量。 -
用于评估我们模型性能的度量列表,这些度量不会在模型训练过程中使用。我们可以传递字符串名称或来自
tf.keras.metrics模块的可调用函数。 -
如果你想确保模型以急切执行的方式进行训练和评估,可以将
run_eagerly参数设置为 true。
请注意,图形通过
compile方法最终确定。现在,我们将使用 Adam 优化器来编译模型,采用类别交叉熵损失并显示准确率度量。
model.compile( optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"] ) -
-
现在,我们将生成三个包含 64 个特征的玩具数据集,数据值为随机生成。其中一个用于训练模型(2,000 个样本),另一个用于验证(500 个样本),最后一个用于测试(500 个样本)。
data = np.random.random((2000, 64)) labels = np.random.random((2000, 10)) val_data = np.random.random((500, 64)) val_labels = np.random.random((500, 10)) test_data = np.random.random((500, 64)) test_labels = np.random.random((500, 10)) -
在模型配置完成后,通过调用
fit方法开始学习阶段。训练配置由以下三个参数完成:-
我们必须设置训练轮次,即遍历整个输入数据的迭代次数。
-
我们需要指定每个梯度的样本数,称为
batch_size参数。请注意,如果总样本数不能被批次大小整除,则最后一批次的样本可能较小。 -
我们可以通过设置
validation_data参数(一个包含输入和标签的元组)来指定验证数据集。这个数据集可以方便地监控模型的性能。在每个训练周期结束时,损失和度量会在推理模式下计算。
现在,我们将通过调用
fit方法在我们的玩具数据集上训练模型:model.fit(data, labels, epochs=10, batch_size=50, validation_data=(val_data, val_labels)) -
-
接下来,我们将在测试数据集上评估我们的模型。我们将调用
model.evaluate函数,它预测模型在测试模式下的损失值和度量值。计算是按批次进行的。它有三个重要参数:输入数据、目标数据和批次大小。此函数为给定输入预测输出,然后计算metrics函数(在model.compile中根据目标数据指定),并返回计算后的度量值作为输出。model.evaluate(data, labels, batch_size=50) -
我们也可以仅使用模型进行预测。
tf.keras.Model.predict方法仅接受数据作为输入并返回预测结果。以下是如何预测提供数据的最后一层推理输出,结果以 NumPy 数组形式呈现:result = model.predict(data, batch_size=50)分析这个模型的性能在这个示例中并不重要,因为我们使用的是随机生成的数据集。
现在,让我们继续分析这个示例。
它是如何工作的...
Keras 提供了 Sequential API 来创建由一系列线性堆叠的层组成的模型。我们可以将层实例的列表作为数组传递给构造函数,或者使用add方法。
Keras 提供了不同种类的层。它们大多数共享一些常见的构造参数,如activation、kernel_initializer和bias_initializer,以及kernel_regularizer和bias_regularizer。
注意延迟构建模式:如果没有在第一层指定输入形状,当第一次将模型应用于输入数据时,或者调用fit、eval、predict和summary等方法时,模型才会被构建。图形在调用compile方法时最终确定,该方法在学习阶段之前配置模型。然后,我们可以评估模型或进行预测。
另见
关于 Keras Sequential API 的一些参考资料,请访问以下网站:
-
tf.keras.Sequential 模型 API 文档:
www.tensorflow.org/api_docs/python/tf/keras/Sequential -
Keras Sequential 模型 API 文档:
keras.io/models/sequential/
使用 Keras 函数式 API
Keras Sequential API 在大多数情况下非常适合开发深度学习模型。然而,这个 API 有一些限制,例如线性拓扑结构,可以通过函数式 API 来克服。需要注意的是,许多高效的网络都基于非线性拓扑结构,如 Inception、ResNet 等。
函数式 API 允许定义具有非线性拓扑结构、多个输入、多个输出、残差连接的复杂模型,以及具有非顺序流动的共享和可重用层。
深度学习模型通常是一个有向无环图(DAG)。功能 API 是一种构建层图的方式,它比tf.keras.Sequential API 创建更灵活的模型。
准备工作
本实例将涵盖创建功能模型的主要方式,使用可调用模型、操作复杂的图拓扑、共享层,并最终引入使用 Keras 顺序 API 的“层节点”概念。
一如既往,我们只需要按如下方式导入 TensorFlow:
import tensorflow as tf
from tensorflow import keras
from keras.layers import Input, Dense, TimeDistributed
import keras.models
我们准备好继续解释如何实现这一点。
如何做到这一点...
让我们开始构建一个功能模型,用于识别 MNIST 手写数字数据集。我们将从灰度图像中预测手写数字。
创建功能模型
-
首先,我们将加载 MNIST 数据集。
mnist = tf.keras.datasets.mnist (X_mnist_train, y_mnist_train), (X_mnist_test, y_mnist_test) = mnist.load_data() -
然后,我们将创建一个 28x28 维度的输入节点。记住,在 Keras 中,输入层并不是一个层,而是一个张量,我们必须为第一个层指定输入形状。这个张量的形状必须与我们的训练数据形状一致。默认情况下,Keras 模型被定义为支持任何批次大小,因此批次大小不是必需的。
Input()用于实例化 Keras 张量。inputs = tf.keras.Input(shape=(28,28)) -
然后,我们将使用以下命令对大小为(28,28)的图像进行展平操作。这将生成一个包含 784 个像素的数组。
flatten_layer = keras.layers.Flatten() -
我们将通过在
inputs对象上调用flatten_layer来在层图中添加一个新的节点:flatten_output = flatten_layer(inputs)“层调用”操作就像是从
inputs到flatten_layer绘制一条箭头。我们正在“传递”输入到展平层,结果是它产生了输出。层实例是可调用的(在张量上)并返回一个张量。 -
然后,我们将创建一个新的层实例:
dense_layer = tf.keras.layers.Dense(50, activation='relu') -
我们将添加一个新节点:
dense_output = dense_layer(flatten_output) -
为了构建一个模型,多个层将被堆叠。在这个示例中,我们将添加另一个
dense层来进行 10 个类别之间的分类任务:predictions = tf.keras.layers.Dense(10, activation='softmax')(dense_output) -
输入张量和输出张量用于定义模型。模型是一个由一个或多个输入层和一个或多个输出层构成的函数。模型实例形式化了计算图,描述数据如何从输入流向输出。
model = keras.Model(inputs=inputs, outputs=predictions) -
现在,我们将打印模型的摘要。
model.summary() -
这将产生如下输出:
图 3.1:模型摘要
-
这样的模型可以通过与 Keras 顺序模型中相同的
compile, fit、evaluate和predict方法进行训练和评估。model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics=['accuracy']) model.fit(X_mnist_train, y_mnist_train, validation_data=(X_mnist_train, y_mnist_train), epochs=10)
在这个实例中,我们使用功能 API 构建了一个模型。
使用像层这样的可调用模型
让我们深入了解功能 API 与可调用模型的细节。
-
使用功能 API,重复使用训练好的模型变得非常容易:任何模型都可以通过在张量上调用它作为一层来处理。我们将重用前面定义的模型作为一层,以便查看其实现效果。它是一个用于 10 个类别的分类器。该模型返回 10 个概率值:每个类别一个概率值。这被称为 10 分类 softmax。因此,通过调用上述模型,模型将为每个输入预测 10 个类别中的一个。
x = Input(shape=(784,)) # y will contain the prediction for x y = model(x)请注意,通过调用一个模型,我们不仅仅是重用模型架构,我们还在重用它的权重。
-
如果我们面临一个序列问题,使用功能性 API 创建模型将变得非常简单。例如,假设我们不是处理一张图片,而是处理由多张图片组成的视频。我们可以通过使用
TimeDistributed层包装器,将图像分类模型转变为视频分类模型,仅需一行代码。这个包装器将我们的前一个模型应用于输入序列的每一个时间切片,换句话说,就是应用于视频的每一帧图像。from keras.layers import TimeDistributed # Input tensor for sequences of 50 timesteps, # Each containing a 28x28 dimensional matrix. input_sequences = tf.keras.Input(shape=(10, 28, 28)) # We will apply the previous model to each sequence so one for each timestep. # The MNIST model returns a vector with 10 probabilities (one for each digit). # The TimeDistributed output will be a sequence of 50 vectors of size 10. processed_sequences = tf.keras.layers.TimeDistributed(model)(input_sequences)
我们已经看到,模型像层一样是可以调用的。现在,我们将学习如何创建具有非线性拓扑的复杂模型。
创建一个具有多个输入和输出的模型
功能性 API 使得操作大量交织的数据流变得简单,具有多个输入输出和非线性连接拓扑。这些是顺序 API 无法处理的,顺序 API 无法创建具有非顺序连接或多个输入输出的模型。
让我们来看一个例子。我们将构建一个系统,用于预测特定房子的价格和销售前的经过时间。
该模型将有两个输入:
-
关于房子的资料,例如卧室数量、房屋大小、空调、内置厨房等。
-
房子的最新照片
这个模型将有两个输出:
-
销售前的经过时间(两个类别——慢或快)
-
预测价格
-
为了构建这个系统,我们将从构建第一个模块开始,用于处理关于房子的表格数据。
house_data_inputs = tf.keras.Input(shape=(128,), name='house_data') x = tf.keras.layers.Dense(64, activation='relu')(house_data_inputs) block_1_output = tf.keras.layers.Dense(32, activation='relu')(x) -
然后,我们将构建第二个模块来处理房子的图像数据。
house_picture_inputs = tf.keras.Input(shape=(128,128,3), name='house_picture') x = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same')(house_picture_inputs) x = tf.keras.layers.Conv2D(64, 3, activation='relu', padding='same')(x) block_2_output = tf.keras.layers.Flatten()(x) -
现在,我们将通过拼接将所有可用特征合并为一个大的向量。
x = tf.keras.layers.concatenate([block_1_output, block_2_output]) -
然后,我们将在特征上加一个用于价格预测的逻辑回归。
price_pred = tf.keras.layers.Dense(1, name='price', activation='relu')(x) -
接着,我们将在特征上加一个时间分类器。
time_elapsed_pred = tf.keras.layers.Dense(2, name='elapsed_time', activation='softmax')(x) -
现在,我们将构建模型。
model = keras.Model([house_data_inputs, house_picture_inputs], [price_pred, time_elapsed_pred], name='toy_house_pred') -
现在,我们将绘制模型图。
keras.utils.plot_model(model, 'multi_input_and_output_model.png', show_shapes=True) -
这将产生以下输出:
图 3.2:具有多个输入和输出的模型图
在这个示例中,我们使用功能性 API 创建了一个复杂模型,具有多个输入输出,用于预测特定房子的价格和销售前的经过时间。现在,我们将引入共享层的概念。
共享层
一些模型在其架构内多次重用相同的层。这些层实例学习与层图中多个路径对应的特征。共享层通常用于对来自相似空间的输入进行编码。
为了在不同的输入之间共享一个层(包括权重),我们只需实例化该层一次,并将其应用于我们需要的多个输入。
让我们考虑两种不同的文本序列。我们将对这两个具有相似词汇的序列应用相同的嵌入层。
# Variable-length sequence of integers
text_input_a = tf.keras.Input(shape=(None,), dtype='int32')
# Variable-length sequence of integers
text_input_b = tf.keras.Input(shape=(None,), dtype='int32')
# Embedding for 1000 unique words mapped to 128-dimensional vectors
shared_embedding = tf.keras.layers.Embedding(1000, 128)
# Reuse the same layer to encode both inputs
encoded_input_a = shared_embedding(text_input_a)
encoded_input_b = shared_embedding(text_input_b)
在这个例子中,我们已经学习了如何在同一个模型中多次重用一个层。现在,我们将介绍提取和重用层的概念。
在层的图中提取和重用节点
在本章的第一个例子中,我们看到一个层是一个实例,它以一个张量作为参数并返回另一个张量。一个模型是由多个层实例组成的。这些层实例是通过它们的输入和输出张量相互连接的对象。每次我们实例化一个层时,该层的输出是一个新的张量。通过向层添加一个“节点”,我们将输入和输出张量连接起来。
层的图是一个静态数据结构。通过 Keras 函数式 API,我们可以轻松访问和检查模型。
tf.keras.application 模块包含具有预训练权重的现成架构。
-
让我们去下载 ResNet 50 预训练模型。
resnet = tf.keras.applications.resnet.ResNet50() -
然后,我们将通过查询图数据结构来显示模型的中间层:
intermediate_layers = [layer.output for layer in resnet.layers] -
然后,我们将通过查询图数据结构来显示模型的前 10 个中间层:
intermediate_layers[:10] -
这将产生以下输出:
[<tf.Tensor 'input_7:0' shape=(None, 224, 224, 3) dtype=float32>, <tf.Tensor 'conv1_pad/Pad:0' shape=(None, 230, 230, 3) dtype=float32>, <tf.Tensor 'conv1_conv/BiasAdd:0' shape=(None, 112, 112, 64) dtype=float32>, <tf.Tensor 'conv1_bn/cond/Identity:0' shape=(None, 112, 112, 64) dtype=float32>, <tf.Tensor 'conv1_relu/Relu:0' shape=(None, 112, 112, 64) dtype=float32>, <tf.Tensor 'pool1_pad/Pad:0' shape=(None, 114, 114, 64) dtype=float32>, <tf.Tensor 'pool1_pool/MaxPool:0' shape=(None, 56, 56, 64) dtype=float32>, <tf.Tensor 'conv2_block1_1_conv/BiasAdd:0' shape=(None, 56, 56, 64) dtype=float32>, <tf.Tensor 'conv2_block1_1_bn/cond/Identity:0' shape=(None, 56, 56, 64) dtype=float32>, <tf.Tensor 'conv2_block1_1_relu/Relu:0' shape=(None, 56, 56, 64) dtype=float32>] -
现在,我们将选择所有特征层。我们将在卷积神经网络章节中详细讲解。
feature_layers = intermediate_layers[:-2] -
然后,我们将重用这些节点来创建我们的特征提取模型。
feat_extraction_model = keras.Model(inputs=resnet.input, outputs=feature_layers)
深度学习模型的一个有趣的好处是,它可以在类似的预测建模问题上部分或完全重复使用。这种技术称为“迁移学习”:通过减少训练时间,它显著提高了训练阶段的效果,并增强了模型在相关问题上的表现。
新的模型架构基于预训练模型中的一个或多个层。预训练模型的权重可以作为训练过程的起点。它们可以是固定的、微调的,或者在学习阶段完全适应。实现迁移学习的两种主要方法是权重初始化和特征提取。别担心,我们稍后会在本书中详细讲解。
在这个例子中,我们加载了基于 VGG19 架构的预训练模型。我们从这个模型中提取了节点,并在新模型中重用了它们。
它是如何工作的...
Keras 顺序 API 在绝大多数情况下是合适的,但仅限于创建逐层模型。函数式 API 更加灵活,允许提取和重用节点、共享层,并创建具有多个输入和多个输出的非线性模型。需要注意的是,许多高性能的网络基于非线性拓扑结构。
在这个例子中,我们已经学习了如何使用 Keras 函数式 API 构建模型。这些模型的训练和评估使用与 Keras 顺序模型相同的 compile、fit、evaluate 和 predict 方法。
我们还看了如何将训练好的模型作为层进行重用,如何共享层,以及如何提取并重用节点。最后一种方法在迁移学习技术中被使用,能够加速训练并提高性能。
还有更多……
由于我们可以访问每一层,使用 Keras 函数式 API 构建的模型具有一些特定功能,如模型绘图、整模型保存等。
使用函数式 API 构建的模型可能会很复杂,因此这里有一些提示,帮助你避免在过程中抓狂:
-
命名层:在展示模型图的摘要和图表时,这将非常有用。
-
分离子模型:将每个子模型视为一个乐高积木,最后我们将它们与其他子模型一起组合。
-
查看层摘要:使用
summary方法查看每一层的输出。 -
查看图形绘图:使用
plot方法显示并检查层之间的连接。 -
一致的变量命名:对输入和输出层使用相同的变量名,避免复制粘贴错误。
另见
关于 Keras 函数式 API 的一些参考资料,访问以下网站:
-
Keras 函数式 API 文档:
keras.io/getting-started/functional-api-guide/ -
tf.keras.ModelAPI:www.tensorflow.org/api_docs/python/tf/keras/Model -
机器学习大师:
machinelearningmastery.com/keras-functional-api-deep-learning/ -
在 TensorFlow 内部:tf.Keras 由 François Chollet 提供(第一部分)
www.youtube.com/watch?v=UYRBHFAvLSs -
在 TensorFlow 内部:tf.Keras(第二部分)
www.youtube.com/watch?v=uhzGTijaw8A
使用 Keras 子类化 API
Keras 基于面向对象设计原则。因此,我们可以继承 Model 类并创建我们的模型架构定义。
Keras 子类化 API 是 Keras 提出的第三种构建深度神经网络模型的方法。
这个 API 完全可定制,但这种灵活性也带来了复杂性!所以,请系好安全带,它比顺序 API 或函数式 API 更难使用。
但你可能会想,如果这个 API 使用起来这么难,我们为什么还需要它。某些模型架构和一些自定义层可能会非常具有挑战性。一些研究人员和开发人员希望完全控制他们的模型以及训练模型的方式。子类化 API 提供了这些功能。让我们深入了解细节。
准备工作
在这里,我们将介绍使用 Keras 子类化 API 创建自定义层和自定义模型的主要方法。
首先,我们加载 TensorFlow,如下所示:
import tensorflow as tf
from tensorflow import keras
我们已准备好继续解释如何操作。
如何操作……
我们从创建我们的层开始。
创建自定义层
正如 理解 Keras 层 部分所解释的那样,Keras 通过其层化 API 提供了各种内置的层,如全连接层、卷积层、循环层和归一化层。
所有层都是 Layer 类的子类,并实现了这些方法:
-
build方法,用于定义层的权重。 -
call方法,指定层执行的从输入到输出的转换。 -
compute_output_shape方法,如果该层修改了输入的形状。这样 Keras 就可以执行自动的形状推断。 -
get_config和from_config方法,如果该层被序列化和反序列化。
-
让我们将理论付诸实践。首先,我们将为自定义全连接层创建一个子类层:
class MyCustomDense(tf.keras.layers.Layer): # Initialize this class with the number of units def __init__(self, units): super(MyCustomDense, self).__init__() self.units = units # Define the weights and the bias def build(self, input_shape): self.w = self.add_weight(shape=(input_shape[-1], self.units), initializer='random_normal', trainable=True) self.b = self.add_weight(shape=(self.units,), initializer='random_normal', trainable=True) # Applying this layer transformation to the input tensor def call(self, inputs): return tf.matmul(inputs, self.w) + self.b # Function to retrieve the configuration def get_config(self): return {'units': self.units} -
然后,我们将使用前一步创建的
MyCustomDense层来创建模型:# Create an input layer inputs = keras.Input((12,4)) # Add an instance of MyCustomeDense layer outputs = MyCustomDense(2)(inputs) # Create a model model = keras.Model(inputs, outputs) # Get the model config config = model.get_config() -
接下来,我们将从配置文件重新加载模型:
new_model = keras.Model.from_config(config, custom_objects={'MyCustomDense': MyCustomDense})
在这个食谱中,我们已经创建了我们的 Layer 类。现在,我们将创建我们的模型。
创建自定义模型
通过子类化 tf.keras.Model 类,我们可以构建一个完全可定制的模型。
我们在 __init__ 方法中定义我们的层,并通过实现 call 方法完全控制模型的前向传播。training 布尔参数可以用来指定训练阶段和推理阶段期间的不同行为。
-
首先,我们将加载 MNIST 数据集并对灰度进行归一化:
mnist = tf.keras.datasets.mnist (X_mnist_train, y_mnist_train), (X_mnist_test, y_mnist_test) = mnist.load_data() train_mnist_features = X_mnist_train/255 test_mnist_features = X_mnist_test/255 -
让我们创建一个
Model的子类,用于识别 MNIST 数据:class MyMNISTModel(tf.keras.Model): def __init__(self, num_classes): super(MyMNISTModel, self).__init__(name='my_mnist_model') self.num_classes = num_classes self.flatten_1 = tf.keras.layers.Flatten() self.dropout = tf.keras.layers.Dropout(0.1) self.dense_1 = tf.keras.layers.Dense(50, activation='relu') self.dense_2 = tf.keras.layers.Dense(10, activation='softmax') def call(self, inputs, training=False): x = self.flatten_1(inputs) # Apply dropout only during the training phase x = self.dense_1(x) if training: x = self.dropout(x, training=training) return self.dense_2(x) -
现在,我们将实例化模型并处理训练:
my_mnist_model = MyMNISTModel(10) # Compile my_mnist_model.compile(optimizer='sgd', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # Train my_mnist_model.fit(train_features, y_train, validation_data=(test_features, y_test), epochs=10)
它是如何工作的……
子类化 API 是深度学习从业者使用面向对象的 Keras 设计原则构建层或模型的一种方式。如果你的模型无法使用 Sequential 或 Functional API 实现,我们建议使用此 API。尽管这种方式的实现可能会很复杂,但在某些情况下仍然非常有用,了解如何在 Keras 中实现层和模型对于所有开发者和研究人员来说都是非常有趣的。
另见
关于 Keras 子类化 API 的一些参考,见以下教程、论文和文章:
-
使用 Keras 编写自定义层和模型:
www.tensorflow.org/guide/keras/custom_layers_and_models -
编写你自己的 Keras 层:
keras.io/layers/writing-your-own-keras-layers/
使用 Keras 预处理 API
Keras 预处理 API 汇集了数据处理和数据增强的模块。该 API 提供了处理序列、文本和图像数据的工具。数据预处理是机器学习和深度学习中的一个重要步骤,它将原始数据转换、转换或编码为适合学习算法理解、有效且有用的格式。
准备工作
本食谱将介绍 Keras 提供的一些预处理方法,用于处理序列、文本和图像数据。
如往常一样,我们只需导入 TensorFlow 如下:
import tensorflow as tf
from tensorflow import keras
import numpy as np
from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator, pad_sequences, skipgrams, make_sampling_table
from tensorflow.keras.preprocessing.text import text_to_word_sequence, one_hot, hashing_trick, Tokenizer
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
我们准备好继续解释如何做了。
如何做…
让我们从序列数据开始。
序列预处理
序列数据是有序的数据,如文本或时间序列。因此,时间序列由按时间顺序排列的一系列数据点定义。
时间序列生成器
Keras 提供了处理序列数据(如时间序列数据)的实用工具。它接收连续的数据点,并使用时间序列参数(如步幅、历史长度等)进行转换,返回一个 TensorFlow 数据集实例。
-
让我们使用一个玩具时间序列数据集,包含 10 个整数值:
series = np.array([i for i in range(10)]) print(series) -
这导致以下输出:
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) -
我们希望根据最后五个滞后观测值预测下一个值。因此,我们将定义一个生成器,并将
length参数设置为 5。此参数指定输出序列的长度(以时间步为单位):generator = TimeseriesGenerator(data = series, targets = series, length=5, batch_size=1, shuffle=False, reverse=False) -
我们希望生成由 5 个滞后观测组成的样本,用于预测,而玩具时间序列数据集包含 10 个值。因此,生成的样本数为 5:
# number of samples print('Samples: %d' % len(generator)) -
接下来,我们将显示每个样本的输入和输出,并检查数据是否准备就绪:
for i in range(len(generator)): x, y = generator[i] print('%s => %s' % (x, y)) -
这导致以下输出:
[[0 1 2 3 4]] => [5] [[1 2 3 4 5]] => [6] [[2 3 4 5 6]] => [7] [[3 4 5 6 7]] => [8] [[4 5 6 7 8]] => [9] -
现在,我们将创建并编译一个模型:
model = Sequential() model.add(Dense(10, activation='relu', input_dim=5)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse') -
然后,我们将通过将生成器作为输入数据来训练模型:
model.fit(generator, epochs=10)
使用深度学习方法准备时间序列数据可能非常具有挑战性。但幸运的是,Keras 提供了一个生成器,可以帮助我们将单变量或多变量时间序列数据集转换为准备用于训练模型的数据结构。该生成器提供了许多选项来准备数据,如洗牌、采样率、起始和结束偏移量等。建议查阅官方 Keras API 获取更多详细信息。
现在,我们将专注于如何准备可变长度输入序列的数据。
填充序列
处理序列数据时,每个样本的长度通常不同。为了使所有序列适合所需的长度,解决方案是对它们进行填充。比定义的序列长度短的序列在每个序列的末尾(默认)或开头填充值。否则,如果序列大于所需长度,则截断序列。
-
让我们从四个句子开始:
sentences = [["What", "do", "you", "like", "?"], ["I", "like", "basket-ball", "!"], ["And", "you", "?"], ["I", "like", "coconut", "and", "apple"]] -
首先,我们将构建词汇查找表。我们将创建两个字典,一个用于从单词到整数标识符的转换,另一个反之。
text_set = set(np.concatenate(sentences)) vocab_to_int = dict(zip(text_set, range(len(text_set)))) int_to_vocab = {vocab_to_int[word]:word for word in vocab_to_int.keys()} -
然后,在构建词汇查找表之后,我们将句子编码为整数数组。
encoded_sentences = [] for sentence in sentences: encoded_sentence = [vocab_to_int[word] for word in sentence] encoded_sentences.append(encoded_sentence) encoded_sentences -
这导致以下输出:
[[8, 4, 7, 6, 0], [5, 6, 2, 3], [10, 7, 0], [5, 6, 1, 9, 11]] -
现在,我们将使用
pad_sequences函数轻松地截断和填充序列到一个公共长度。默认情况下,启用了序列前填充。pad_sequences(encoded_sentences) -
这导致以下输出:
array([[ 8, 4, 7, 6, 0], [ 0, 5, 6, 2, 3], [ 0, 0, 10, 7, 0], [ 5, 6, 1, 9, 11]], dtype=int32) -
然后,我们将激活序列后填充,并将
maxlen参数设置为所需的长度 – 这里是 7。pad_sequences(encoded_sentences, maxlen = 7) -
这导致以下输出:
array([[ 0, 0, 8, 4, 7, 6, 0], [ 0, 0, 0, 5, 6, 2, 3], [ 0, 0, 0, 0, 10, 7, 0], [ 0, 0, 5, 6, 1, 9, 11]], dtype=int32) -
序列的长度也可以裁剪为所需的长度——此处为 3。默认情况下,该函数会从每个序列的开头移除时间步长。
pad_sequences(encoded_sentences, maxlen = 3) -
这将产生以下输出:
array([[ 7, 6, 0], [ 6, 2, 3], [10, 7, 0], [ 1, 9, 11]], dtype=int32) -
将截断参数设置为
post,以便从每个序列的末尾移除时间步长。pad_sequences(encoded_sentences, maxlen = 3, truncating='post') -
这将产生以下输出:
array([[ 8, 4, 7], [ 5, 6, 2], [10, 7, 0], [ 5, 6, 1]], dtype=int32)
当我们希望列表中的所有序列具有相同长度时,填充非常有用。
在接下来的章节中,我们将介绍一种非常流行的文本预处理技术。
Skip-grams
Skip-grams 是自然语言处理中的一种无监督学习技术。它为给定的单词找到最相关的单词,并预测该单词的上下文单词。
Keras 提供了skipgrams预处理函数,它接收一个整数编码的单词序列,并返回定义窗口中每对单词的相关性。如果一对单词相关,样本为正样本,相关标签设置为 1。否则,样本被认为是负样本,标签设置为 0。
一例胜千言。让我们以这个句子为例:“I like coconut and apple,”选择第一个单词作为我们的“上下文单词”,并使用窗口大小为二。我们将上下文单词“I”与指定窗口中的单词配对。所以,我们有两个单词对(I, like)和(I, coconut),这两者的值都为 1。
让我们将理论付诸实践:
-
首先,我们将把一个句子编码为单词索引的列表:
sentence = "I like coconut and apple" encoded_sentence = [vocab_to_int[word] for word in sentence.split()] vocabulary_size = len(encoded_sentence) -
然后,我们将调用
skipgrams函数,窗口大小为 1:pairs, labels = skipgrams(encoded_sentence, vocabulary_size, window_size=1, negative_samples=0) -
现在,我们将打印结果:
for i in range(len(pairs)): print("({:s} , {:s} ) -> {:d}".format( int_to_vocab[pairs[i][0]], int_to_vocab[pairs[i][1]], labels[i])) -
这将产生以下输出:
(coconut , and ) -> 1 (apple , ! ) -> 0 (and , coconut ) -> 1 (apple , and ) -> 1 (coconut , do ) -> 0 (like , I ) -> 1 (and , apple ) -> 1 (like , coconut ) -> 1 (coconut , do ) -> 0 (I , like ) -> 1 (coconut , like ) -> 1 (and , do ) -> 0 (like , coconut ) -> 0 (I , ! ) -> 0 (like , ! ) -> 0 (and , coconut ) -> 0
请注意,非单词在词汇表中由索引 0 表示,并将被跳过。我们建议读者参考 Keras API,了解更多关于填充的细节。
现在,让我们介绍一些文本数据预处理的技巧。
文本预处理
在深度学习中,我们不能直接将原始文本输入到网络中。我们必须将文本编码为数字,并提供整数作为输入。我们的模型将生成整数作为输出。这个模块提供了文本输入预处理的工具。
将文本拆分为单词序列
Keras 提供了text_to_word_sequence方法,将序列转换为单词或标记的列表。
-
让我们使用这个句子:
sentence = "I like coconut , I like apple" -
然后,我们将调用将句子转换为单词列表的方法。默认情况下,该方法会根据空格拆分文本。
text_to_word_sequence(sentence, lower=False) -
这将产生以下输出:
['I', 'like', 'coconut', 'I', 'like', 'apple'] -
现在,我们将
lower参数设置为True,文本将被转换为小写:text_to_word_sequence(sentence, lower=True, filters=[]) -
这将产生以下输出:
['i', 'like', 'coconut', ',', 'i', 'like', 'apple']
请注意,默认情况下,filter参数会过滤掉一系列字符,如标点符号。在我们上一次的代码执行中,我们移除了所有预定义的过滤器。
让我们继续使用一种方法来编码单词或类别特征。
Tokenizer
Tokenizer类是文本分词的实用工具类。它是深度学习中准备文本的首选方法。
这个类的输入参数为:
-
要保留的最大词数。只有最常见的词会根据词频被保留。
-
用于过滤的字符列表。
-
一个布尔值,用于决定是否将文本转换为小写字母。
-
用于分词的分隔符。
-
让我们从这句话开始:
sentences = [["What", "do", "you", "like", "?"], ["I", "like", "basket-ball", "!"], ["And", "you", "?"], ["I", "like", "coconut", "and", "apple"]] -
现在,我们将创建一个
Tokenizer实例,并对前述句子进行拟合:# create the tokenizer t = Tokenizer() # fit the tokenizer on the documents t.fit_on_texts(sentences) -
分词器创建了文档的几部分信息。我们可以获得一个字典,包含每个词的计数。
print(t.word_counts) -
这会产生以下输出:
OrderedDict([('what', 1), ('do', 1), ('you', 2), ('like', 3), ('?', 2), ('i', 2), ('basket-ball', 1), ('!', 1), ('and', 2), ('coconut', 1), ('apple', 1)]) -
我们还可以获得一个字典,包含每个词出现在多少个文档中:
print(t.document_count) -
这会产生以下输出:
4 -
一个字典包含每个词的唯一整数标识符:
print(t.word_index) -
这会产生以下输出:
{'like': 1, 'you': 2, '?': 3, 'i': 4, 'and': 5, 'what': 6, 'do': 7, 'basket-ball': 8, '!': 9, 'coconut': 10, 'apple': 11} -
用于拟合
Tokenizer的独特文档数量。print(t.word_docs) -
这会产生以下输出:
defaultdict(<class 'int'>, {'do': 1, 'like': 3, 'what': 1, 'you': 2, '?': 2, '!': 1, 'basket-ball': 1, 'i': 2, 'and': 2, 'coconut': 1, 'apple': 1}) -
现在,我们已经准备好编码文档,感谢
texts_to_matrix函数。这个函数提供了四种不同的文档编码方案,用来计算每个标记的系数。让我们从二进制模式开始,它返回文档中每个标记是否存在。
t.texts_to_matrix(sentences, mode='binary') -
这会产生以下输出:
[[0\. 1\. 1\. 1\. 0\. 0\. 1\. 1\. 0\. 0\. 0\. 0.] [0\. 1\. 0\. 0\. 1\. 0\. 0\. 0\. 1\. 1\. 0\. 0.] [0\. 0\. 1\. 1\. 0\. 1\. 0\. 0\. 0\. 0\. 0\. 0.] [0\. 1\. 0\. 0\. 1\. 1\. 0\. 0\. 0\. 0\. 1\. 1.]] -
TokenizerAPI 提供了另一种基于词频的模式——它返回文档中每个词的计数:t.texts_to_matrix(sentences, mode='count') -
这会产生以下输出:
[[0\. 1\. 1\. 1\. 0\. 0\. 1\. 1\. 0\. 0\. 0\. 0.] [0\. 1\. 0\. 0\. 1\. 0\. 0\. 0\. 1\. 1\. 0\. 0.] [0\. 0\. 1\. 1\. 0\. 1\. 0\. 0\. 0\. 0\. 0\. 0.] [0\. 1\. 0\. 0\. 1\. 1\. 0\. 0\. 0\. 0\. 1\. 1.]]
请注意,我们也可以使用tfidf模式或频率模式。前者返回每个词的词频-逆文档频率分数,后者返回文档中每个词的频率,且与文档中的总词数相关。
Tokenizer API 可以拟合训练数据集并对训练、验证和测试数据集中的文本数据进行编码。
在本节中,我们介绍了一些在训练和预测之前准备文本数据的技术。
现在,让我们继续准备并增强图像。
图像预处理
数据预处理模块提供了一套实时数据增强工具,用于图像数据。
在深度学习中,神经网络的性能通常通过训练数据集中的示例数量来提高。
Keras 预处理 API 中的ImageDataGenerator类允许从训练数据集中创建新数据。它不应用于验证或测试数据集,因为其目的是通过合理的新图像扩展训练数据集中的示例数量。这种技术称为数据增强。请注意不要将数据准备与数据归一化或图像调整大小混淆,后者适用于所有与模型交互的数据。数据增强包括图像处理领域的许多变换,例如旋转、水平和垂直偏移、水平和垂直翻转、亮度调整等。
策略可能会根据任务的不同而有所不同。例如,在 MNIST 数据集中,它包含了手写数字的图像,应用水平翻转没有意义。除了数字 8 之外,这种变换是不合适的。
而在婴儿照片的情况下,应用这种变换是有意义的,因为图像可能是从左边或右边拍摄的。
-
让我们将理论付诸实践,对
CIFAR10数据集进行数据增强。我们将首先下载CIFAR数据集。# Load CIFAR10 Dataset (x_cifar10_train, y_cifar10_train), (x_cifar10_test, y_cifar10_test) = tf.keras.datasets.cifar10.load_data() -
现在,我们将创建一个图像数据生成器,应用水平翻转、0 到 15 度之间的随机旋转,以及在宽度和高度方向上平移 3 个像素。
datagen = tf.keras.preprocessing.image.ImageDataGenerator( rotation_range=15, width_shift_range=3, height_shift_range=3, horizontal_flip=True) -
创建训练数据集的迭代器。
it= datagen.flow(x_cifar10_train, y_cifar10_train, batch_size = 32) -
创建一个模型并编译它。
model = tf.keras.models.Sequential([ tf.keras.layers.Conv2D(filters=32, kernel_size=3, padding="same", activation="relu", input_shape=[32, 32, 3]), tf.keras.layers.Conv2D(filters=32, kernel_size=3, padding="same", activation="relu"), tf.keras.layers.MaxPool2D(pool_size=2), tf.keras.layers.Conv2D(filters=64, kernel_size=3, padding="same", activation="relu"), tf.keras.layers.Conv2D(filters=64, kernel_size=3, padding="same", activation="relu"), tf.keras.layers.MaxPool2D(pool_size=2), tf.keras.layers.Flatten(), tf.keras.layers.Dense(128, activation="relu"), tf.keras.layers.Dense(10, activation="softmax") ]) model.compile(loss="sparse_categorical_crossentropy", optimizer=tf.keras.optimizers.SGD(lr=0.01), metrics=["accuracy"]) -
通过调用
fit方法来处理训练。请注意设置step_per_epoch参数,该参数指定一个 epoch 包含的样本批次数。history = model.fit(it, epochs=10, steps_per_epoch=len(x_cifar10_train) / 32, validation_data=(x_cifar10_test, y_cifar10_test))
使用图像数据生成器,我们通过创建新图像扩展了原始数据集的大小。通过更多的图像,深度学习模型的训练效果可以得到提升。
它是如何工作的...
Keras 预处理 API 允许转换、编码和增强神经网络的数据。它使得处理序列、文本和图像数据变得更加容易。
首先,我们介绍了 Keras Sequence 预处理 API。我们使用时间序列生成器将单变量或多变量时间序列数据集转换为适合训练模型的数据结构。然后,我们重点介绍了可变长度输入序列的数据准备,即填充(padding)。最后,我们以 skip-gram 技术结束这一部分,skip-gram 可以为给定单词找到最相关的词,并预测该单词的上下文词。
然后,我们介绍了 Keras 文本预处理 API,它提供了一个完整的解决方案来处理自然语言。我们学习了如何将文本拆分为单词,并使用二进制、词频计数、tfidf 或频率模式对单词进行标记化。
最后,我们重点介绍了使用ImageDataGenerator的图像预处理 API,这是增加训练数据集大小并处理图像的一个实际优势。
另见
关于 Keras 预处理 API 的一些参考资料,请访问以下网站:
-
序列预处理 Keras API:
keras.io/preprocessing/sequence/ -
文本处理 Keras API:
keras.io/preprocessing/text/ -
关于句法和语义词汇相似性的更多信息:Tomas Mikolov、Kai Chen、Greg Corrado 和 Jeffrey Dean。(2013)。高效估算词向量表示
arxiv.org/pdf/1301.3781v3.pdf -
图像预处理 Keras API:
keras.io/preprocessing/image/