TensorFlow2 口袋参考(一)
译者:飞龙
前言
TensorFlow 生态系统已经发展成许多不同的框架,以服务各种角色和功能。这种灵活性是其被广泛采用的原因之一,但也增加了数据科学家、机器学习(ML)工程师和其他技术利益相关者的学习曲线。有很多方法可以管理 TensorFlow 模型用于常见任务,比如数据和特征工程、数据摄取、模型选择、训练模式、交叉验证防止过拟合以及部署策略,选择可能会让人感到不知所措。
这本便携参考书将帮助您在 TensorFlow 中做出选择,包括如何使用 Python 中的 TensorFlow 2.0 设计模式设置常见的数据科学和 ML 工作流程。示例描述和演示了 TensorFlow 编码模式和您在 ML 项目工作中可能经常遇到的其他任务。您可以将其用作操作指南和参考书。
本书适用于当前和潜在的 ML 工程师、数据科学家和企业 ML 解决方案架构师,他们希望在 TensorFlow 建模中的可重用模式和最佳实践方面提升知识和经验。也许您已经阅读过一本介绍性的 TensorFlow 书籍,并且对数据科学领域保持了解。本书假定您具有使用 Python(可能还有 NumPy、pandas 和 JSON 库)进行数据工程、特征工程例程和构建 TensorFlow 模型的实际经验。熟悉常见数据结构,如列表、字典和 NumPy 数组,也将非常有帮助。
与许多其他 TensorFlow 书籍不同,本书围绕您可能需要执行的任务进行结构化,比如:
-
何时以及为什么应该将训练数据作为 NumPy 数组或流式数据集传递?(第 2 和 5 章)
-
如何利用预训练模型进行迁移学习?(第 3 和 4 章)
-
您应该使用通用的 fit 函数进行训练,还是编写自定义训练循环?(第六章)
-
您应该如何管理和利用模型检查点?(第七章)
-
如何使用 TensorBoard 审查训练过程?(第七章)
-
如果你的数据无法全部放入运行时的内存中,你如何使用多个加速器(如 GPU)进行分布式训练?(第八章)
-
在推断期间如何将数据传递给模型以及如何处理输出?(第九章)
-
您的模型是否公平?(第十章)
如果您正在处理这类问题,本书将对您有所帮助。
本书使用的约定
本书使用以下排版约定:
斜体
指示新术语、URL、电子邮件地址、文件名和文件扩展名。
等宽字体
用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
等宽粗体
显示用户应该按照字面意思输入的命令或其他文本。
等宽斜体
显示应该用用户提供的值或上下文确定的值替换的文本。
提示
这个元素表示提示或建议。
使用代码示例
补充材料(代码示例、练习等)可在https://github.com/shinchan75034/tensorflow-pocket-ref下载。
如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
这本书旨在帮助您完成工作。一般来说,如果本书提供示例代码,您可以在程序和文档中使用它。除非您复制了代码的大部分内容,否则无需联系我们以获得许可。例如,编写一个程序使用本书中的几个代码块不需要许可。销售或分发 O'Reilly 图书中的示例需要许可。通过引用本书回答问题并引用示例代码不需要许可。将本书中大量示例代码整合到产品文档中需要许可。
我们感谢,但通常不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“TensorFlow 2 Pocket Reference by KC Jung (O’Reilly). Copyright 2021 Favola Vera, LLC, 978-1-492-08918-6.”
如果您认为您对代码示例的使用超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。
第一章:TensorFlow 2 简介
TensorFlow 长期以来一直是最受欢迎的开源 Python 机器学习(ML)库。它是由 Google Brain 团队作为内部工具开发的,但在 2015 年以 Apache 许可证发布。从那时起,它已经发展成一个充满重要资产的生态系统,用于模型开发和部署。今天,它支持各种特定设计用于处理数据摄入和转换、特征工程、模型构建和服务等任务的 API 和模块。
TensorFlow 变得越来越复杂。本书的目的是帮助简化数据科学家或 ML 工程师在端到端模型开发过程中需要执行的常见任务。本书不关注数据科学和算法;相反,这里的示例使用预构建的模型作为教授相关概念的工具。
本书适用于具有构建 ML 模型的基本经验和知识的读者。强烈建议具备一定的 Python 编程能力。如果您从头到尾阅读本书,您将获得关于端到端模型开发过程和涉及的主要任务的大量知识,包括数据工程、摄入和准备;模型训练;以及服务模型。
本书中示例的源代码是在 Google Colaboratory(简称 Colab)和运行 macOS Big Sur,版本 11.2.3 的 MacBook Pro 上开发和测试的。使用的 TensorFlow 版本是 2.4.1,Python 版本是 3.7。
TensorFlow 2 的改进
随着 TensorFlow 的发展,它的复杂性也在增加。新用户学习 TensorFlow 的曲线是陡峭的,因为需要记住许多不同的方面。*我如何准备数据进行摄入和训练?如何处理不同的数据类型?对于不同的处理方法需要考虑什么?*这些只是您在 ML 旅程初期可能遇到的一些基本问题。
一个特别难以适应的概念是惰性执行,这意味着 TensorFlow 实际上不会处理您的数据,直到您明确告诉它执行整个代码。这个想法是为了加快性能。您可以将 ML 模型看作一组节点和边(换句话说,一个图)。当您在路径中运行计算并通过节点转换数据时,结果只有数据路径中的计算被执行。换句话说,您不必计算每个计算,只需计算数据通过图从输入到输出的路径中直接位于的计算。如果数据的形状和格式在一个节点和下一个节点之间没有正确匹配,当您编译模型时将会出现错误。在传递数据结构或张量形状时,很难调查您在哪里犯了错误以进行调试。
通过 TensorFlow 1.x,惰性执行是构建和训练 ML 模型的方式。然而,从 TensorFlow 2 开始,急切执行是构建和训练模型的默认方式。这种改变使得调试代码和尝试不同的模型架构变得更加容易。急切执行还使得学习 TensorFlow 变得更加容易,因为您将立即在执行每行代码时看到任何错误。您不再需要在调试和测试输入数据是否具有正确形状之前构建整个模型图。这是使 TensorFlow 2 比以前版本更易于使用的几个主要功能和改进之一。
Keras API
Keras 是由 AI 研究员 François Chollet 创建的开源、高级、深度学习 API 或框架。它与多个 ML 库兼容。
高级意味着在更低级别上有另一个实际执行计算的框架,事实上确实如此。这些低级框架包括 TensorFlow、Theano 和 Microsoft 认知工具包(CNTK)。Keras 的目的是为那些想要利用低级框架构建深度学习模型的用户提供更简单的语法和编码风格。
2015 年,Chollet 加入 Google 后,Keras 逐渐成为 TensorFlow 采用的基石。2019 年,随着 TensorFlow 团队推出 2.0 版本,正式采用了 Keras 作为 TensorFlow 的一流 API,即tf.keras,用于所有未来版本。从那时起,TensorFlow 已经将tf.keras与许多其他重要模块集成在一起。例如,它与tf.io API 无缝配合,用于读取分布式训练数据。它还与tf.data.Dataset类一起工作,用于流式传输训练数据,这些数据太大,无法容纳在一台计算机中。本书在所有章节中都使用这些模块。
今天,TensorFlow 用户主要依赖tf.keras API 来快速轻松地构建深度模型。快速获得训练例程的便利性使得更多时间可以用来尝试不同的模型架构和调整模型和训练例程中的参数。
TensorFlow 中的可重用模型
学术研究人员已经构建和测试了许多 ML 模型,所有这些模型在其架构上都很复杂。用户学习如何构建这些模型并不现实。这就引入了迁移学习的概念,其中为一个任务开发的模型被重用来解决另一个任务,即用户定义的任务。这基本上归结为将用户数据转换为适当的数据结构,以便模型输入和输出。
自然地,这些模型及其潜在用途引起了极大的兴趣。因此,应大众需求,许多模型已经在开源生态系统中可用。TensorFlow 创建了一个仓库,TensorFlow Hub,向公众提供这些复杂模型的免费访问。如果您感兴趣,您可以尝试这些模型,而无需自己构建它们。在第四章中,您将学习如何从 TensorFlow Hub 下载和使用模型。一旦您这样做了,您只需要了解模型在输入时期望的数据结构,并添加一个适合您预测目标的最终输出层。TensorFlow Hub 中的每个模型都包含简洁的文档,为您提供构建输入数据所需的信息。
另一个获取预构建模型的地方是tf.keras.applications模块,它是 TensorFlow 分发的一部分。在第四章中,您将学习如何使用此模块来利用预构建模型处理您自己的数据。
使常用操作变得简单
TensorFlow 2 中的所有这些改进使得许多重要操作更容易实现。即便如此,从头到尾构建和训练一个 ML 模型并不是一项简单的任务。本书将向您展示如何处理 TensorFlow 2 模型训练过程的每个方面,从一开始。以下是其中一些操作。
开源数据
集成到 TensorFlow 2 中的一个方便的包是TensorFlow 数据集库。这是一个由精心策划的开源数据集组成的集合,可供使用。该库包含图像、文本、音频、视频等多种格式的数据集。有些是 NumPy 数组,而其他的是数据集结构。该库还提供了如何使用 TensorFlow 加载这些数据集的文档。通过在其产品中分发各种开源数据集,TensorFlow 团队真正为用户节省了搜索、集成和重塑训练数据以适应 TensorFlow 工作负载的麻烦。本书中将使用的一些开源数据集是用于结构化数据分类的泰坦尼克数据集和用于图像分类的CIFAR-10 数据集。
处理分布式数据集
首先,您必须处理如何处理训练数据的问题。许多教学示例使用 TensorFlow 中的预构建训练数据,以其原生格式,例如小型 pandas DataFrame 或 NumPy 数组,这些数据可以很好地适应计算机的内存。然而,在更现实的情况下,您可能需要处理比计算机内存更多的训练数据。从 SQL 数据库读取的表的大小很容易达到几十亿字节。即使您有足够的内存将其加载到 pandas DataFrame 或 NumPy 数组中,您的 Python 运行时在计算过程中可能会耗尽内存并崩溃。
通常将大量数据保存为多个文件,常见格式为 CSV(逗号分隔值)或文本。因此,您不应尝试在 Python 运行时加载每个文件。处理分布式数据集的正确方法是创建一个引用,指向所有文件的位置。第二章将向您展示如何使用tf.io API,该 API 提供一个包含文件路径和名称列表的对象。无论数据大小和文件数量如何,这都是处理训练数据的首选方式。
数据流式传输
您打算如何将数据传递给模型进行训练?这是一个重要的技能,但许多流行的教学示例通过将整个 NumPy 数组传递到模型训练例程中来处理。就像加载大型训练数据一样,如果尝试将大型 NumPy 数组传递给模型进行训练,您将遇到内存问题。
更好的处理方式是通过数据流。不是一次性传递整个训练数据,而是为模型训练提供一个子集或批量数据进行流式传输。在 TensorFlow 中,这被称为您的数据集。在第二章中,您还将学习如何从tf.io对象创建数据集。数据集对象可以从各种本地数据结构创建。在第三章中,您将看到如何从 CSV 文件和图像创建tf.data.Dataset对象。
通过tf.io和tf.data.Dataset的组合,您将为模型训练设置一个数据处理工作流程,而无需在 Python 运行时内存中读取或打开任何数据文件。
数据工程
为了使模型能够学习模式,您需要对训练数据应用数据或特征工程任务。根据数据类型,有不同的方法可以做到这一点。
如果您正在处理表格数据,可能在不同列中有不同的值或数据类型。在第三章中,您将看到如何使用 TensorFlow 的feature_column API 对训练数据进行标准化。它可以帮助您正确标记哪些列是数值列,哪些是分类列。
对于图像数据,您将有不同的任务。例如,数据集中的所有图像必须具有相同的尺寸。此外,像素值通常被归一化或缩放到[0, 1]范围。对于这些任务,tf.keras提供了ImageDataGenerator类,用于标准化图像尺寸并为您归一化像素值。
迁移学习
TensorFlow Hub 为所有人提供了预构建的开源模型。在第四章中,您将学习如何使用 Keras 层 API 访问 TensorFlow Hub。此外,tf.keras附带了这些预构建模型的清单,可以使用tf.keras.applications模块调用。在第四章中,您将学习如何使用此模块进行迁移学习。
模型风格
使用tf.keras可以以多种方式实现模型。这是因为一些深度学习模型架构或模式比其他更复杂。对于常见用途,符号 API风格,按顺序设置模型架构,可能足够了。另一种风格是命令式 API,其中您将模型声明为一个类,因此每次调用模型对象时,都会创建该类的一个实例。这要求您了解类继承的工作原理(我将在第六章中讨论)。如果您的编程背景源自面向对象的编程语言,如 C++或 Java,那么这个 API 可能对您更自然。使用命令式 API 方法的另一个原因是将模型架构代码与其余工作流程分开。在第六章中,您将学习如何设置和使用这两种 API 风格。
监控训练过程
监控您的模型在每个epoch(即一次通过训练集)中是如何训练和验证的是模型训练的一个重要方面。在每个 epoch 结束时进行验证步骤是您可以采取的最简单的措施,以防止模型过拟合,这是一种现象,模型开始记忆训练数据模式而不是按预期学习特征。在第七章中,您将学习如何使用各种回调来保存每个 epoch 的模型权重和偏差。我还将指导您如何设置和使用 TensorBoard 来可视化训练过程。
分布式训练
即使您知道如何处理分布式数据和文件并将其流式传输到模型训练例程中,但如果发现训练需要不切实际的时间呢?这就是分布式训练可以帮助的地方。它需要一组硬件加速器,例如图形处理单元(GPU)或张量处理单元(TPU)。这些加速器可以通过许多公共云提供商获得。您还可以在 Google Colab 中免费使用一个 GPU 或 TPU(而不是集群);您将学习如何使用tf.distribute.MirroredStrategy类,简化和减少设置分布式训练的繁重工作,在第八章的第一部分示例中进行操作。
在tf.distribute.MirroredStrategy之前发布的 Horovod API 来自 Uber 工程团队,是一个相对复杂的替代方案。它专门用于在计算集群上运行训练例程。要学习如何使用 Horovod,您需要使用基于云的计算平台 Databricks,在第八章的第二部分示例中进行操作。这将帮助您学习如何重构您的代码以分发和分片数据以供 Horovod API 使用。
为您的 TensorFlow 模型提供服务
一旦您构建了模型并成功训练了它,现在是时候将模型持久化或存储起来,以便可以提供给处理用户输入。您将看到使用tf.saved_model API 保存您的模型是多么容易。
通常,模型由 Web 服务托管。这就是 TensorFlow Serving 出现的地方:它是一个框架,包装您的模型并通过 HTTP 公开为 Web 服务调用。在第九章中,您将学习如何使用 TensorFlow Serving Docker 镜像来托管您的模型。
改进培训体验
最后,第十章讨论了评估和改进模型训练过程的一些重要方面。您将学习如何使用 TensorFlow 模型分析模块来查看模型偏差的问题。该模块提供了一个交互式仪表板,称为公平性指标,旨在揭示模型偏差。使用 Jupyter Notebook 环境和您在第三章中训练的泰坦尼克号数据集上的模型,您将看到公平性指标是如何工作的。
tf.keras API 带来的另一个改进是使超参数调整更加方便。超参数是与模型训练例程或模型架构相关的属性。调整它们通常是一个繁琐的过程,因为它涉及彻底搜索参数空间。在第十章中,您将看到如何使用 Keras Tuner 库和一个称为 Hyperband 的高级搜索算法来进行超参数调整工作。
总结
TensorFlow 2 是对以前版本的重大改进。其最重要的改进是将tf.keras API 指定为使用 TensorFlow 的推荐方式。这个 API 与tf.io和tf.data.Dataset无缝配合,用于端到端的模型训练过程。这些改进加快了模型构建和调试的速度,因此您可以尝试模型训练的其他方面,比如尝试不同的架构或进行更有效的超参数搜索。所以,让我们开始吧。
第二章:数据存储和摄入
要想设想如何设置一个 ML 模型来解决问题,您必须开始考虑数据结构模式。在本章中,我们将看一些存储、数据格式和数据摄入中的一般模式。通常,一旦您理解了您的业务问题并将其设置为数据科学问题,您必须考虑如何将数据转换为模型训练过程可以使用的格式或结构。在训练过程中进行数据摄入基本上是一个数据转换管道。没有这种转换,您将无法在企业驱动或用例驱动的环境中交付和提供模型;它将仍然只是一个探索工具,无法扩展以处理大量数据。
本章将向您展示如何为两种常见的数据结构(表格和图像)设计数据摄入管道。您将学习如何使用 TensorFlow 的 API 使管道可扩展。
数据流式处理是模型在训练过程中以小批量摄入数据的方式。在 Python 中进行数据流式处理并不是一个新概念。然而,理解它对于理解 TensorFlow 中更高级 API 的工作方式是基本的。因此,本章将从 Python 生成器开始。然后我们将看一下如何存储表格数据,包括如何指示和跟踪特征和标签。然后我们将转向设计您的数据结构,并讨论如何将数据摄入到模型进行训练以及如何流式传输表格数据。本章的其余部分涵盖了如何为图像分类组织图像数据以及如何流式传输图像数据。
使用 Python 生成器进行数据流式处理
有时候 Python 运行时的内存不足以处理整个数据集的加载。当发生这种情况时,建议的做法是将数据分批加载。因此,在训练过程中,数据被流式传输到模型中。
以小批量发送数据还有许多其他优点。其中之一是对每个批次应用梯度下降算法来计算误差(即模型输出与实际值之间的差异),并逐渐更新模型的权重和偏差以使这个误差尽可能小。这让我们可以并行计算梯度,因为一个批次的误差计算(也称为损失计算)不依赖于其他批次。这被称为小批量梯度下降。在每个时代结束时,当整个训练数据集经过模型时,所有批次的梯度被求和并更新权重。然后,使用新更新的权重和偏差重新开始训练下一个时代,并计算误差。这个过程根据用户定义的参数重复进行,这个参数被称为训练时代数。
Python 生成器是返回可迭代对象的迭代器。以下是它的工作示例。让我们从一个 NumPy 库开始,进行这个简单的 Python 生成器演示。我创建了一个名为my_generator的函数,它接受一个 NumPy 数组,并以数组中的两条记录为一组进行迭代:
import numpy as np
def my_generator(my_array):
i = 0
while True:
yield my_array[i:i+2, :] # output two elements at a time
i += 1
这是我创建的测试数组,将被传递给my_generator:
test_array = np.array([[10.0, 2.0],
[15, 6.0],
[3.2, -1.5],
[-3, -2]], np.float32)
这个 NumPy 数组有四条记录,每条记录由两个浮点值组成。然后我将这个数组传递给my_generator:
output = my_generator(test_array)
要获得输出,请使用:
next(output)
输出应该是:
array([[10., 2.],
[15., 6.]], dtype=float32)
如果您再次运行next(output)命令,输出将不同:
array([[15\. , 6\. ],
[ 3.2, -1.5]], dtype=float32)
如果您再次运行它,输出将再次不同:
array([[ 3.2, -1.5],
[-3\. , -2\. ]], dtype=float32)
如果您第四次运行它,输出现在是:
array([[-3., -2.]], dtype=float32)
现在最后一条记录已经显示,您已经完成了对这些数据的流式处理。如果您再次运行它,它将返回一个空数组:
array([], shape=(0, 2), dtype=float32)
正如您所看到的,my_generator 函数每次运行时都会以 NumPy 数组的形式流式传输两条记录。生成器函数的独特之处在于使用 yield 语句而不是 return 语句。与 return 不同,yield 会生成一个值序列,而不会将整个序列存储在 Python 运行时内存中。yield 在我们调用 next 函数时每次生成一个序列,直到到达数组的末尾。
此示例演示了如何通过生成器函数生成数据子集。但是,在此示例中,NumPy 数组是即时创建的,因此保存在 Python 运行时内存中。让我们看看如何迭代存储为文件的数据集。
使用生成器流式传输文件内容
要理解如何流式传输存储中的文件,您可能会发现使用 CSV 文件作为示例更容易。我在这里使用的文件是 Pima 印第安人糖尿病数据集,这是一个可供下载的开源数据集。下载并将其存储在本地机器上。
这个文件没有包含标题,因此您还需要下载此数据集的列名和描述。
简而言之,该文件中的列是:
['Pregnancies', 'Glucose', 'BloodPressure',
'SkinThickness', 'Insulin', 'BMI',
'DiabetesPedigree', 'Age', 'Outcome']
让我们用以下代码行查看这个文件:
import csv
import pandas as pd
file_path = 'working_data/'
file_name = 'pima-indians-diabetes.data.csv'
col_name = ['Pregnancies', 'Glucose', 'BloodPressure',
'SkinThickness', 'Insulin', 'BMI',
'DiabetesPedigree', 'Age', 'Outcome']
pd.read_csv(file_path + file_name, names = col_name)
文件的前几行显示在 图 2-1 中。
图 2-1. Pima 印第安人糖尿病数据集
由于我们想要流式处理这个数据集,更方便的方法是将其作为 CSV 文件读取,并使用生成器输出行,就像我们在前一节中对 NumPy 数组所做的那样。实现这一点的方法是通过以下代码:
import csv
file_path = 'working_data/'
file_name = 'pima-indians-diabetes.data.csv'
with open(file_path + file_name, newline='\n') as csvfile:
f = csv.reader(csvfile, delimiter=',')
for row in f:
print(','.join(row))
让我们仔细看看这段代码。我们使用 with open 命令创建一个文件句柄对象 csvfile,该对象知道文件存储在哪里。下一步是将其传递给 CSV 库中的 reader 函数:
f = csv.reader(csvfile, delimiter=',')
f 是 Python 运行时内存中的整个文件。要检查文件,执行这段简短的 for 循环代码:
for row in f:
print(','.join(row))
前几行的输出看起来像 图 2-2。
图 2-2. Pima 印第安人糖尿病数据集 CSV 输出
现在您已经了解了如何使用文件句柄,让我们重构前面的代码,以便可以在函数中使用 yield,有效地创建一个生成器来流式传输文件的内容:
def stream_file(file_handle):
holder = []
for row in file_handle:
holder.append(row.rstrip("\n"))
yield holder
holder = []
with open(file_path + file_name, newline = '\n') as handle:
for part in stream_file(handle):
print(part)
回想一下,Python 生成器是一个使用 yield 来迭代通过 iterable 对象的函数。您可以像往常一样使用 with open 获取文件句柄。然后我们将 handle 传递给一个包含 for 循环的生成器函数 stream_file,该循环逐行遍历 handle 中的文件,删除换行符 \n,然后填充一个 holder。每行通过 yield 从生成器传回到主线程的 print 函数。输出显示在 图 2-3 中。
图 2-3. 由 Python 生成器输出的 Pima 印第安人糖尿病数据集
现在您已经清楚了数据集如何进行流式处理,让我们看看如何在 TensorFlow 中应用这一点。事实证明,TensorFlow 利用这种方法构建了一个用于数据摄入的框架。流式处理通常是摄入大量数据(例如一个表中的数十万行,或分布在多个表中)的最佳方式。
JSON 数据结构
表格数据是用于对 ML 模型训练的特征和标签进行编码的常见和便捷格式,CSV 可能是最常见的表格数据格式。您可以将逗号分隔的每个字段视为一列。每列都定义了一个数据类型,例如数字(整数或浮点数)或字符串。
表格数据不是唯一的结构化数据格式,我指的是每个记录遵循相同约定,每个记录中字段的顺序相同。另一个常见的数据结构是 JSON。JSON(JavaScript 对象表示)是一个由嵌套、分层键值对构建的结构。您可以将键视为列名,将值视为该样本中数据的实际值。JSON 可以转换为 CSV,反之亦然。有时原始数据是以 JSON 格式存在,需要将其转换为 CSV,这样更容易显示和检查。
这里是一个示例 JSON 记录,显示了键值对:
{
"id": 1,
"name": {
"first": "Dan",
"last": "Jones"
},
"rating": [
8,
7,
9
]
},
请注意,“rating”键与数组[8, 7, 9]的值相关联。
有很多例子使用 CSV 文件或表作为训练数据,并将其纳入 TensorFlow 模型训练过程。通常,数据被读入一个 pandas DataFrame。然而,这种策略只有在所有数据都能适应 Python 运行时内存时才有效。您可以使用流处理数据,而不受 Python 运行时限制内存分配。由于您在前一节学习了 Python 生成器的工作原理,现在您可以看一下 TensorFlow 的 API,它遵循与 Python 生成器相同的原理,并学习如何使用 TensorFlow 采用 Python 生成器框架。
设置文件名的模式
在处理一组文件时,您会遇到文件命名约定中的模式。为了模拟一个不断生成和存储新数据的企业环境,我们将使用一个开源 CSV 文件,按行数拆分成多个部分,然后使用固定前缀重命名每个部分。这种方法类似于 Hadoop 分布式文件系统(HDFS)如何命名文件的部分。
如果您有一个方便的 CSV 文件,可以随时使用您自己的 CSV 文件。如果没有,您可以为本示例下载建议的CSV 文件(一个 COVID-19 数据集)。(如果您愿意,您可以克隆这个存储库。)
现在,你只需要owid-covid-data.csv。一旦下载完成,检查文件并确定行数:
wc -l owid-covid-data.csv
输出表明有超过 32,000 行:
32788 owid-covid-data.csv
接下来,检查 CSV 文件的前三行,看看是否有标题:
head -3 owid-covid-data.csv
iso_code,continent,location,date,total_cases,new_cases,
total_deaths,new_deaths,total_cases_per_million,
new_cases_per_million,total_deaths_per_million,
new_deaths_per_million,new_tests,total_tests,
total_tests_per_thousand,new_tests_per_thousand,
new_tests_smoothed,new_tests_smoothed_per_thousand,tests_units,
stringency_index,population,population_density,median_age,
aged_65_older,aged_70_older,gdp_per_capita,extreme_poverty,
cardiovasc_death_rate,diabetes_prevalence,female_smokers,
male_smokers,handwashing_facilities,hospital_beds_per_thousand,
life_expectancy
AFG,Asia,Afghanistan,2019-12-31,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,,,,,,,,,38928341.0,
54.422,18.6,2.581,1.337,1803.987,,597.029,9.59,,,37.746,0.5,64.8
由于这个文件包含一个标题,你会在每个部分文件中看到标题。你也可以查看一些数据行,看看它们实际上是什么样子的。
将单个 CSV 文件拆分为多个 CSV 文件
现在让我们将这个文件分割成多个 CSV 文件,每个文件有 330 行。你应该最终得到 100 个 CSV 文件,每个文件都有标题。如果你使用 Linux 或 macOS,请使用以下命令:
cat owid-covid-data.csv| parallel --header : --pipe -N330
'cat >owid-covid-data-
part00{#}.csv'
对于 macOS,您可能需要先安装parallel命令:
brew install parallel
这里是一些已创建的文件:
-rw-r--r-- 1 mbp16 staff 54026 Jul 26 16:45
owid-covid-data-part0096.csv
-rw-r--r-- 1 mbp16 staff 54246 Jul 26 16:45
owid-covid-data-part0097.csv
-rw-r--r-- 1 mbp16 staff 51278 Jul 26 16:45
owid-covid-data-part0098.csv
-rw-r--r-- 1 mbp16 staff 62622 Jul 26 16:45
owid-covid-data-part0099.csv
-rw-r--r-- 1 mbp16 staff 15320 Jul 26 16:45
owid-covid-data-part00100.csv
这个模式代表了多个 CSV 格式的标准存储安排。命名约定有一个明显的模式:要么所有文件都有相同的标题,要么没有一个文件有标题。
保持文件命名模式是个好主意,无论您有几十个还是几百个文件都会很方便。当您的命名模式可以很容易地用通配符表示时,更容易创建一个指向存储中所有数据的引用或文件模式对象。
在下一节中,我们将看看如何使用 TensorFlow API 创建一个文件模式对象,我们将使用它来为这个数据集创建一个流对象。
使用 tf.io 创建文件模式对象
TensorFlow 的tf.io API 用于引用包含具有共同命名模式的文件的分布式数据集。这并不意味着您要读取分布式数据集:您想要的是一个包含您想要读取的所有数据集文件的文件路径和名称列表。这并不是一个新的想法。例如,在 Python 中,glob 库是检索类似列表的常用选择。tf.io API 简单地利用 glob 库生成符合模式对象的文件名列表:
import tensorflow as tf
base_pattern = 'dataset'
file_pattern = 'owid-covid-data-part*'
files = tf.io.gfile.glob(base_pattern + '/' + file_pattern)
files是一个包含所有原始 CSV 文件名的列表,没有特定顺序:
['dataset/owid-covid-data-part0091.csv',
'dataset/owid-covid-data-part0085.csv',
'dataset/owid-covid-data-part0052.csv',
'dataset/owid-covid-data-part0046.csv',
'dataset/owid-covid-data-part0047.csv',
…]
这个列表将成为下一步的输入,即基于 Python 生成器创建一个流数据集对象。
创建一个流数据集对象
现在您已经准备好文件列表,您可以将其用作输入来创建一个流数据集对象。请注意,此代码仅旨在演示如何将 CSV 文件列表转换为 TensorFlow 数据集对象。如果您真的要使用这些数据来训练一个监督式 ML 模型,您还将执行数据清洗、归一化和聚合等操作,所有这些我们将在第八章中介绍。在本例中,“new_deaths”被选为目标列:
csv_dataset = tf.data.experimental.make_csv_dataset(files,
header = True,
batch_size = 5,
label_name = 'new_deaths',
num_epochs = 1,
ignore_errors = True)
前面的代码指定files中的每个文件都包含一个标题。为了方便起见,我们将批量大小设置为 5。我们还使用label_name指定一个目标列,就好像我们要使用这些数据来训练一个监督式 ML 模型。num_epochs用于指定您希望对整个数据集进行多少次流式传输。
要查看实际数据,您需要使用csv_dataset对象迭代数据:
for features, target in csv_dataset.take(1):
print("'Target': {}".format(target))
print("'Features:'")
for k, v in features.items():
print(" {!r:20s}: {}".format(k, v))
这段代码使用数据集的第一个批次(take(1)),其中包含五个样本。
由于您指定了label_name作为目标列,其他列都被视为特征。在数据集中,内容被格式化为键值对。前面代码的输出将类似于这样:
'Target': [ 0\. 0\. 16\. 0\. 0.]
'Features:'
'iso_code' : [b'SWZ' b'ESP' b'ECU' b'ISL' b'FRO']
'continent' :
[b'Africa' b'Europe' b'South America' b'Europe' b'Europe']
'location' :
[b'Swaziland' b'Spain' b'Ecuador' b'Iceland' b'Faeroe Islands']
'date' :
[b'2020-04-04' b'2020-02-07' b'2020-07-13' b'2020-04-01'
b'2020-06-11']
'total_cases' : [9.000e+00 1.000e+00 6.787e+04
1.135e+03 1.870e+02]
'new_cases' : [ 0\. 0\. 661\. 49\. 0.]
'total_deaths' : [0.000e+00 0.000e+00 5.047e+03
2.000e+00 0.000e+00]
'total_cases_per_million':
[7.758000e+00 2.100000e-02 3.846838e+03
3.326007e+03 3.826870e+03]
'new_cases_per_million': [ 0\. 0\. 37.465
143.59 0\. ]
'total_deaths_per_million': [ 0\. 0\. 286.061
5.861 0\. ]
'new_deaths_per_million':
[0\. 0\. 0.907 0\. 0\. ]
'new_tests' :
[b'' b'' b'1331.0' b'1414.0' b'']
'total_tests' :
[b'' b'' b'140602.0' b'20889.0' b'']
'total_tests_per_thousand':
[b'' b'' b'7.969' b'61.213' b'']
'new_tests_per_thousand':
[b'' b'' b'0.075' b'4.144' b'']
'new_tests_smoothed':
[b'' b'' b'1986.0' b'1188.0' b'']
'new_tests_smoothed_per_thousand':
[b'' b'' b'0.113' b'3.481' b'']
'tests_units' :
[b'' b'' b'units unclear' b'tests performed' b'']
'stringency_index' :
[89.81 11.11 82.41 53.7 0\. ]
'population' :
[ 1160164\. 46754784\. 17643060\. 341250\. 48865.]
'population_density':
[79.492 93.105 66.939 3.404 35.308]
'median_age' :
[21.5 45.5 28.1 37.3 0\. ]
'aged_65_older' :
[ 3.163 19.436 7.104 14.431 0\. ]
'aged_70_older' :
[ 1.845 13.799 4.458 9.207 0\. ]
'gdp_per_capita' :
[ 7738.975 34272.36 10581.936 46482.957 0\. ]
'extreme_poverty' : [b'' b'1.0' b'3.6' b'0.2' b'']
'cardiovasc_death_rate':
[333.436 99.403 140.448 117.992 0\. ]
'diabetes_prevalence': [3.94 7.17 5.55 5.31 0\. ]
'female_smokers' :
[b'1.7' b'27.4' b'2.0' b'14.3' b'']
'male_smokers' :
[b'16.5' b'31.4' b'12.3' b'15.2' b'']
'handwashing_facilities':
[24.097 0\. 80.635 0\. 0\. ]
'hospital_beds_per_thousand':
[2.1 2.97 1.5 2.91 0\. ]
'life_expectancy' :
[60.19 83.56 77.01 82.99 80.67]
此数据在运行时检索(延迟执行)。根据批量大小,每列包含五条记录。接下来,让我们讨论如何流式传输这个数据集。
流式传输 CSV 数据集
现在已经创建了一个 CSV 数据集对象,您可以使用这行代码轻松地按批次迭代它,该代码使用iter函数从 CSV 数据集创建一个迭代器,并使用next函数返回迭代器中的下一个项目:
features, label = next(iter(csv_dataset))
请记住,在这个数据集中有两种类型的元素:features和label。这些元素作为元组返回(类似于对象列表,不同之处在于对象的顺序和值不能被更改或重新分配)。您可以通过将元组元素分配给变量来解压元组。
如果您检查标签,您将看到第一个批次的内容:
<tf.Tensor: shape=(5,), dtype=float32,
numpy=array([ 0., 0., 1., 33., 29.], dtype=float32)>
如果再次执行相同的命令,您将看到第二个批次:
features, label = next(iter(csv_dataset))
让我们看一下label:
<tf.Tensor: shape=(5,), dtype=float32,
numpy=array([ 7., 15., 1., 0., 6.], dtype=float32)>
确实,这是第二批观察结果;它包含与第一批不同的值。这就是在数据摄入管道中生成流式传输 CSV 数据集的方式。当每个批次被发送到模型进行训练时,模型通过前向传递计算预测,该计算通过将输入值与神经网络中每个节点的当前权重和偏差相乘来计算输出。然后,它将预测与标签进行比较并计算损失函数。接下来是反向传递,模型计算与预期输出的变化,并向网络中的每个节点后退以更新权重和偏差。然后模型重新计算并更新梯度。将新的数据批次发送到模型进行训练,过程重复。
接下来我们将看看如何为存储组织图像数据,并像我们流式传输结构化数据一样流式传输它。
组织图像数据
图像分类任务需要以特定方式组织图像,因为与 CSV 或表格数据不同,将标签附加到图像需要特殊技术。组织图像文件的一种直接和常见模式是使用以下目录结构:
<PROJECT_NAME>
train
class_1
<FILENAME>.jpg
<FILENAME>.jpg
…
class_n
<FILENAME>.jpg
<FILENAME>.jpg
…
validation
class_1
<FILENAME>.jpg
<FILENAME>.jpg
…
class_n
<FILENAME>.jpg
<FILENAME>.jpg
test
class_1
<FILENAME>.jpg
<FILENAME>.jpg
…
class_n
<FILENAME>.jpg
<FILENAME>.jpg
…
*<PROJECT_NAME>*是基本目录。它的下一级包含训练、验证和测试目录。在每个目录中,都有以图像标签命名的子目录(class_1、class_2等,在下面的示例中是花卉类型),每个子目录包含原始图像文件。如图 2-4 所示。
这种结构很常见,因为它可以方便地跟踪标签及其相应的图像,但这绝不是组织图像数据的唯一方式。让我们看看另一种组织图像的结构。这与之前的结构非常相似,只是训练、测试和验证都是分开的。在*<PROJECT_NAME>*目录的正下方是不同图像类别的目录,如图 2-5 所示。
图 2-4. 用于图像分类和训练工作的文件组织
图 2-5. 基于标签的图像文件组织
使用 TensorFlow 图像生成器
现在让我们看看如何处理图像。除了文件组织的细微差别外,处理图像还需要一些步骤来标准化和归一化图像文件。模型架构需要所有图像具有固定形状(固定尺寸)。在像素级别,值通常被归一化为[0, 1]的范围(将像素值除以 255)。
在这个例子中,您将使用一个包含五种不同类型的花朵的开源图像集(或者随意使用您自己的图像集)。假设图像应该是 224×224 像素,其中尺寸对应高度和宽度。如果您想要使用预训练的残差神经网络(ResNet)作为图像分类器,这些是输入图像的预期尺寸。
首先让我们下载这些图像。以下代码下载五种不同尺寸的花朵,并将它们放入稍后在图 2-6 中显示的文件结构中:
import tensorflow as tf
data_dir = tf.keras.utils.get_file(
'flower_photos',
'https://storage.googleapis.com/download.tensorflow.org/
example_images/flower_photos.tgz', untar=True)
我们将data_dir称为基本目录。它应该类似于:
'/Users/XXXXX/.keras/datasets/flower_photos'
如果列出基本目录中的内容,您将看到:
-rw-r----- 1 mbp16 staff 418049 Feb 8 2016 LICENSE.txt
drwx------ 801 mbp16 staff 25632 Feb 10 2016 tulips
drwx------ 701 mbp16 staff 22432 Feb 10 2016 sunflowers
drwx------ 643 mbp16 staff 20576 Feb 10 2016 roses
drwx------ 900 mbp16 staff 28800 Feb 10 2016 dandelion
drwx------ 635 mbp16 staff 20320 Feb 10 2016 daisy
流式传输图像有三个步骤。让我们更仔细地看一下:
-
创建一个
ImageDataGenerator对象并指定归一化参数。使用rescale参数指示归一化比例,使用validation_split参数指定将 20%的数据留出用于交叉验证:train_datagen = tf.keras.preprocessing.image. ImageDataGenerator( rescale = 1./255, validation_split = 0.20)可选地,您可以将
rescale和validation_split包装为一个包含键值对的字典:datagen_kwargs = dict(rescale=1./255, validation_split=0.20) train_datagen = tf.keras.preprocessing.image. ImageDataGenerator(**datagen_kwargs)这是一种方便的方式,可以重复使用相同的参数并将多个输入参数包装在一起。 (将字典数据结构传递给函数是一种称为字典解包的 Python 技术。)
-
将
ImageDataGenerator对象连接到数据源,并指定参数将图像调整为固定尺寸:IMAGE_SIZE = (224, 224) # Image height and width BATCH_SIZE = 32 dataflow_kwargs = dict(target_size=IMAGE_SIZE, batch_size=BATCH_SIZE, interpolation="bilinear") train_generator = train_datagen.flow_from_directory( data_dir, subset="training", shuffle=True, **dataflow_kwargs) -
为索引标签准备一个映射。在这一步中,您检索生成器为每个标签分配的索引,并创建一个将其映射到实际标签名称的字典。TensorFlow 生成器内部会跟踪
data_dir下目录名称的标签。它们可以通过train_generator.class_indices检索,返回标签和索引的键值对。您可以利用这一点并将其反转以部署模型进行评分。模型将输出索引。要实现这种反向查找,只需反转由train_generator.class_indices生成的标签字典:labels_idx = (train_generator.class_indices) idx_labels = dict((v,k) for k,v in labels_idx.items())这些是
idx_labels:{0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers', 4: 'tulips'}现在您可以检查
train_generator生成的项目的形状:for image_batch, labels_batch in train_generator: print(image_batch.shape) print(labels_batch.shape) break预计通过迭代基本目录生成器产生的第一批数据将会看到以下内容:
(32, 224, 224, 3) (32, 5)第一个元组表示 32 张图片的批量大小,每张图片的尺寸为 224×224×3(高度×宽度×深度,深度代表三个颜色通道 RGB)。第二个元组表示 32 个标签,每个标签对应五种花的其中一种。它是根据
idx_labels进行独热编码的。
流式交叉验证图片
回想一下,在创建用于流式训练数据的生成器时,您使用了validation_split参数,值为 0.2。如果不这样做,validation_split默认为 0。如果validation_split设置为非零小数,则在调用flow_from_directory方法时,还必须指定subset为training或validation。在前面的示例中,它是subset="training"。
您可能想知道如何知道哪些图片属于我们之前创建的训练生成器的training子集。好消息是,如果您重新分配和重复使用训练生成器,您就不需要知道这一点:
valid_datagen = train_datagen
valid_generator = valid_datagen.flow_from_directory(
data_dir, subset="validation", shuffle=False,
**dataflow_kwargs)
正如您所看到的,TensorFlow 生成器知道并跟踪training和validation子集,因此您可以重复使用相同的生成器来流式传输不同的子集。dataflow_kwargs字典也被重用。这是 TensorFlow 生成器提供的一个便利功能。
因为您重复使用train_datagen,所以可以确保图像重新缩放的方式与图像训练的方式相同。在valid_datagen.flow_from_directory方法中,您将传入相同的dataflow_kwargs字典,以设置交叉验证的图像大小与训练图像相同。
如果您希望自行组织图片到训练、验证和测试中,之前学到的内容仍然适用,但有两个例外。首先,您的data_dir位于训练、验证或测试目录的级别。其次,在ImageDataGenerator和flow_from_directory中不需要指定validation_split和subset。
检查调整大小的图片
现在让我们检查生成器生成的调整大小的图片。以下是通过生成器流式传输的数据批次进行迭代的代码片段:
import matplotlib.pyplot as plt
import numpy as np
image_batch, label_batch = next(iter(train_generator))
fig, axes = plt.subplots(8, 4, figsize=(10, 20))
axes = axes.flatten()
for img, lbl, ax in zip(image_batch, label_batch, axes):
ax.imshow(img)
label_ = np.argmax(lbl)
label = idx_labels[label_]
ax.set_title(label)
ax.axis('off')
plt.show()
这段代码将从生成器中产生的第一批数据中生成 32 张图片(参见图 2-6)。
图 2-6. 一批调整大小的图片
让我们来检查代码:
image_batch, label_batch = next(iter(train_generator))
这将通过生成器迭代基本目录。它将iter函数应用于生成器,并利用next函数将图像批次和标签批次输出为 NumPy 数组:
fig, axes = plt.subplots(8, 4, figsize=(10, 20))
这行代码设置了您期望的子图数量,即 32,即批量大小:
axes = axes.flatten()
for img, lbl, ax in zip(image_batch, label_batch, axes):
ax.imshow(img)
label_ = np.argmax(lbl)
label = idx_labels[label_]
ax.set_title(label)
ax.axis('off')
plt.show()
然后您设置图形轴,使用for循环将 NumPy 数组显示为图片和标签。如图 2-6 所示,所有图片都被调整为 224×224 像素的正方形。尽管子图容器是一个尺寸为(10, 20)的矩形,您可以看到所有图片都是正方形的。这意味着您在生成器工作流中调整大小和归一化图片的代码按预期工作。
总结
在本章中,你学会了使用 Python 处理流数据的基础知识。这是在处理大型分布式数据集时的一种重要技术。你还看到了一些常见的表格和图像数据的文件组织模式。
在表格数据部分,你学会了如何选择一个良好的文件命名约定,可以更容易地构建对所有文件的引用,无论有多少个文件。这意味着你现在知道如何构建一个可扩展的流水线,可以将所需的数据输入到 Python 运行时中,用于任何用途(在本例中,用于 TensorFlow 创建数据集)。
你还学会了图像文件通常如何在文件存储中组织,以及如何将图像与标签关联起来。在下一章中,你将利用你在这里学到的关于数据组织和流式处理的知识,将其与模型训练过程整合起来。
第三章:数据预处理
在本章中,您将学习如何为训练准备和设置数据。机器学习工作中最常见的数据格式是表格、图像和文本。与每种数据格式相关的常见实践技术,尽管如何设置数据工程流水线当然取决于您的问题陈述是什么以及您试图预测什么。
我将详细查看所有三种格式,使用具体示例来引导您了解这些技术。所有数据都可以直接读入 Python 运行时内存;然而,这并不是最有效的使用计算资源的方式。当讨论文本数据时,我将特别关注标记和字典。通过本章结束时,您将学会如何准备表格、图像和文本数据进行训练。
为训练准备表格数据
在表格数据集中,重要的是要确定哪些列被视为分类列,因为您必须将它们的值编码为类或类的二进制表示(独热编码),而不是数值值。表格数据集的另一个方面是多个特征之间的相互作用的潜力。本节还将查看 TensorFlow 提供的 API,以便更容易地建模列之间的交互。
通常会遇到作为 CSV 文件的表格数据集,或者仅仅作为数据库查询结果的结构化输出。在这个例子中,我们将从已经在 pandas DataFrame 中的数据集开始,并学习如何转换它并为模型训练设置它。我们将使用泰坦尼克数据集,这是一个开源的表格数据集,通常用于教学,因为其可管理的大小和可用性。该数据集包含每位乘客的属性,如年龄、性别、舱位等级,以及他们是否幸存。我们将尝试根据他们的属性或特征来预测每位乘客的生存概率。请注意,这是一个用于教学和学习目的的小数据集。实际上,您的数据集可能会更大。您可能会对一些输入参数的默认值做出不同的决定,并选择不同的默认值,所以不要对这个例子过于字面理解。
让我们从加载所有必要的库开始:
import functools
import numpy as np
import tensorflow as tf
import pandas as pd
from tensorflow import feature_column
from tensorflow.keras import layers
from sklearn.model_selection import train_test_split
从谷歌的公共存储中加载数据:
TRAIN_DATA_URL = "https://storage.googleapis.com/
tf-datasets/titanic/train.csv"
TEST_DATA_URL = "https://storage.googleapis.com/
tf-datasets/titanic/eval.csv"
train_file_path = tf.keras.utils.get_file("train.csv",
TRAIN_DATA_URL)
test_file_path = tf.keras.utils.get_file("eval.csv", TEST_DATA_URL)
现在看看train_file_path:
print(train_file_path)
/root/.keras/datasets/train.csv
这个文件路径指向一个 CSV 文件,我们将其读取为一个 pandas DataFrame:
titanic_df = pd.read_csv(train_file_path, header='infer')
图 3-1 展示了titanic_df作为 pandas DataFrame 的样子。
图 3-1。泰坦尼克数据集作为 pandas DataFrame
标记列
如您在图 3-1 中所见,这些数据中既有数值列,也有分类列。目标列,或者用于预测的列,是“survived”列。您需要将其标记为目标,并将其余列标记为特征。
提示
在 TensorFlow 中的最佳实践是将您的表格转换为流式数据集。这种做法确保数据的大小不会影响内存消耗。
为了做到这一点,TensorFlow 提供了函数tf.data.experimental.make_csv_dataset:
LABEL_COLUMN = 'survived'
LABELS = [0, 1]
train_ds = tf.data.experimental.make_csv_dataset(
train_file_path,
batch_size=3,
label_name=LABEL_COLUMN,
na_value="?",
num_epochs=1,
ignore_errors=True)
test_ds = tf.data.experimental.make_csv_dataset(
test_file_path,
batch_size=3,
label_name=LABEL_COLUMN,
na_value="?",
num_epochs=1,
ignore_errors=True)
在前面的函数签名中,您指定要生成数据集对象的文件路径。batch_size被任意设置为一个较小的值(在本例中为 3),以便方便检查数据。我们还将label_name设置为“survived”列。对于数据质量,如果任何单元格中指定了问号(?),您希望将其解释为“NA”(不适用)。对于训练,将num_epochs设置为对数据集进行一次迭代。您可以忽略任何解析错误或空行。
接下来,检查数据:
for batch, label in train_ds.take(1):
print(label)
for key, value in batch.items():
print("{}: {}".format(key,value.numpy()))
它看起来类似于图 3-2。
图 3-2. 泰坦尼克数据集的一个批次
以下是训练范式消耗训练数据集的主要步骤:
-
按特征类型指定列。
-
决定是否嵌入或交叉列。
-
选择感兴趣的列,可能作为一个实验。
-
为训练范式创建一个“特征层”以供使用。
现在您已经将数据设置为数据集,可以根据其特征类型指定每列,例如数字或分类,如果需要的话可以进行分桶。如果唯一类别太多且降维会有帮助,还可以嵌入列。
让我们继续进行第 1 步。有四个数字列:age、n_siblings_spouses、parch和fare。五列是分类的:sex、class、deck、embark_town和alone。完成后,您将创建一个feature_columns列表来保存所有特征列。
以下是如何根据实际数字值严格指定数字列的方法,而不进行任何转换:
feature_columns = []
# numeric cols
for header in ['age', 'n_siblings_spouses', 'parch', 'fare']:
feature_columns.append(feature_column.numeric_column(header))
请注意,除了使用age本身,您还可以将age分箱,例如按年龄分布的四分位数。但是分箱边界(四分位数)是什么?您可以检查 pandas DataFrame 中数字列的一般统计信息:
titanic_df.describe()
图 3-3 显示了输出。
图 3-3。泰坦尼克号数据集中的数字列统计
让我们尝试为年龄设置三个分箱边界:23、28 和 35。这意味着乘客年龄将被分组为第一四分位数、第二四分位数和第三四分位数(如图 3-3 所示):
age = feature_column.numeric_column('age')
age_buckets = feature_column.
bucketized_column(age, boundaries=[23, 28, 35])
因此,除了“age”之外,您还生成了另一个列“age_bucket”。
为了了解每个分类列的性质,了解其中的不同值将是有帮助的。您需要使用每列中的唯一条目对词汇表进行编码。对于分类列,这意味着您需要确定哪些条目是唯一的:
h = {}
for col in titanic_df:
if col in ['sex', 'class', 'deck', 'embark_town', 'alone']:
print(col, ':', titanic_df[col].unique())
h[col] = titanic_df[col].unique()
结果显示在图 3-4 中。
图 3-4。数据集中每个分类列的唯一值
您需要以字典格式跟踪这些唯一值,以便模型进行映射和查找。因此,您将对“sex”列中的唯一分类值进行编码:
sex_type = feature_column.categorical_column_with_vocabulary_list(
'Type', ['male' 'female'])
sex_type_one_hot = feature_column.indicator_column(sex_type)
然而,如果列表很长,逐个写出会变得不方便。因此,当您遍历分类列时,可以将每列的唯一值保存在 Python 字典数据结构h中以供将来查找。然后,您可以将唯一值作为列表传递到这些词汇表中:
sex_type = feature_column.
categorical_column_with_vocabulary_list(
'Type', h.get('sex').tolist())
sex_type_one_hot = feature_column.
indicator_column(sex_type)
class_type = feature_column.
categorical_column_with_vocabulary_list(
'Type', h.get('class').tolist())
class_type_one_hot = feature_column.
indicator_column(class_type)
deck_type = feature_column.
categorical_column_with_vocabulary_list(
'Type', h.get('deck').tolist())
deck_type_one_hot = feature_column.
indicator_column(deck_type)
embark_town_type = feature_column.
categorical_column_with_vocabulary_list(
'Type', h.get('embark_town').tolist())
embark_town_type_one_hot = feature_column.
indicator_column(embark_town_type)
alone_type = feature_column.
categorical_column_with_vocabulary_list(
'Type', h.get('alone').tolist())
alone_one_hot = feature_column.
indicator_column(alone_type)
您还可以嵌入“deck”列,因为有八个唯一值,比任何其他分类列都多。将其维度减少到 3:
deck = feature_column.
categorical_column_with_vocabulary_list(
'deck', titanic_df.deck.unique())
deck_embedding = feature_column.
embedding_column(deck, dimension=3)
减少分类列维度的另一种方法是使用哈希特征列。该方法根据输入数据计算哈希值,然后为数据指定一个哈希桶。以下代码将“class”列的维度减少到 4:
class_hashed = feature_column.categorical_column_with_hash_bucket(
'class', hash_bucket_size=4)
将列交互编码为可能的特征
现在来到最有趣的部分:您将找到不同特征之间的交互(这被称为交叉列),并将这些交互编码为可能的特征。这也是您的直觉和领域知识可以有益于您的特征工程努力的地方。例如,基于泰坦尼克号灾难的历史背景,一个问题是:一等舱的女性是否比二等或三等舱的女性更有可能生存?为了将这个问题重新表述为一个数据科学问题,您需要考虑乘客的性别和舱位等级之间的交互。然后,您需要选择一个起始维度大小来表示数据的变化性。假设您任意决定将变化性分成五个维度(hash_bucket_size):
cross_type_feature = feature_column.
crossed_column(['sex', 'class'], hash_bucket_size=5)
现在您已经创建了所有特征,需要将它们组合在一起,并可能进行实验以决定在训练过程中包含哪些特征。为此,您首先要创建一个列表来保存您想要使用的所有特征:
feature_columns = []
然后,您将每个感兴趣的特征附加到列表中:
# append numeric columns
for header in ['age', 'n_siblings_spouses', 'parch', 'fare']:
feature_columns.append(feature_column.numeric_column(header))
# append bucketized columns
age = feature_column.numeric_column('age')
age_buckets = feature_column.
bucketized_column(age, boundaries=[23, 28, 35])
feature_columns.append(age_buckets)
# append categorical columns
indicator_column_names =
['sex', 'class', 'deck', 'embark_town', 'alone']
for col_name in indicator_column_names:
categorical_column = feature_column.
categorical_column_with_vocabulary_list(
col_name, titanic_df[col_name].unique())
indicator_column = feature_column.
indicator_column(categorical_column)
feature_columns.append(indicator_column)
# append embedding columns
deck = feature_column.categorical_column_with_vocabulary_list(
'deck', titanic_df.deck.unique())
deck_embedding = feature_column.
embedding_column(deck, dimension=3)
feature_columns.append(deck_embedding)
# append crossed columns
feature_columns.
append(feature_column.indicator_column(cross_type_feature))
现在创建一个特征层:
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
这一层将作为您即将构建和训练的模型的第一(输入)层。这是您为模型的训练过程提供所有特征工程框架的方式。
创建一个交叉验证数据集
在开始训练之前,您需要为交叉验证目的创建一个小数据集。由于一开始只有两个分区(训练和测试),生成一个交叉验证数据集的一种方法是简单地将其中一个分区细分:
val_df, test_df = train_test_split(test_df, test_size=0.4)
在这里,原始test_df分区的 40%被随机保留为test_df,剩下的 60%现在是val_df。通常,测试数据集是三个数据集中最小的,因为它们仅用于最终评估,而不用于模型训练。
现在您已经处理了特征工程和数据分区,还有最后一件事要做:使用数据集将数据流入训练过程。您将把三个 DataFrame(训练、验证和测试)分别转换为自己的数据集:
batch_size = 32
labels = train_df.pop('survived')
working_ds = tf.data.Dataset.
from_tensor_slices((dict(train_df), labels))
working_ds = working_ds.shuffle(buffer_size=len(train_df))
train_ds = working_ds.batch(batch_size)
如前面的代码所示,首先您将任意决定要包含在一个批次中的样本数量(batch_size)。然后,您需要设置一个标签指定(survived)。tf.data.Dataset.from_tensor_slices方法接受一个元组作为参数。在这个元组中,有两个元素:特征列和标签列。
第一个元素是dict(train_df)。这个dict操作实质上将 DataFrame 转换为键值对,其中每个键代表一个列名,相应的值是该列中的值数组。另一个元素是labels。
最后,我们对数据集进行洗牌和分批处理。由于这种转换将应用于所有三个数据集,将这些步骤合并到一个辅助函数中以减少重复会很方便:
def pandas_to_dataset(dataframe, shuffle=True, batch_size=32):
dataframe = dataframe.copy()
labels = dataframe.pop('survived')
ds = tf.data.Dataset.
from_tensor_slices((dict(dataframe), labels))
if shuffle:
ds = ds.shuffle(buffer_size=len(dataframe))
ds = ds.batch(batch_size)
return ds
现在您可以将此函数应用于验证和测试数据:
val_ds = pandas_to_dataset(val_df, shuffle=False,
batch_size=batch_size)
test_ds = pandas_to_dataset(test_df, shuffle=False,
batch_size=batch_size)
开始模型训练过程
现在,您已经准备好开始模型训练过程。从技术上讲,这并不是预处理的一部分,但通过这个简短的部分,您可以看到您所做的工作如何融入到模型训练过程中。
您将从构建模型架构开始:
model = tf.keras.Sequential([
feature_layer,
layers.Dense(128, activation='relu'),
layers.Dense(128, activation='relu'),
layers.Dropout(.1),
layers.Dense(1)
])
为了演示目的,您将构建一个简单的两层深度学习感知器模型,这是一个前馈神经网络的基本配置。请注意,由于这是一个多层感知器模型,您将使用顺序 API。在这个 API 中,第一层是feature_layer,它代表所有特征工程逻辑和派生特征,例如年龄分段和交叉,用于建模特征交互。
编译模型并为二元分类设置损失函数:
model.compile(optimizer='adam',
loss=tf.keras.losses.BinaryCrossentropy(
from_logits=True),
metrics=['accuracy'])
然后您可以开始训练。您只会训练 10 个 epochs:
model.fit(train_ds,
validation_data=val_ds,
epochs=10)
您可以期望得到与图 3-5 中所示类似的结果。
图 3-5. 泰坦尼克数据集中生存预测的示例训练结果
总结
在本节中,您看到了如何处理由多种数据类型组成的表格数据。您还看到了 TensorFlow 提供的feature_columnAPI,它可以正确转换数据类型、处理分类数据,并为潜在交互提供特征交叉。这个 API 在简化数据和特征工程任务方面非常有帮助。
为处理图像数据做准备
对于图像,您需要将所有图像重塑或重新采样为相同的像素计数;这被称为标准化。您还需要确保所有像素值在相同的颜色范围内,以便它们落在每个像素的 RGB 值的有限范围内。
图像数据具有不同的文件扩展名,例如*.jpg*、.tiff和*.bmp*。这些并不是真正的问题,因为 Python 和 TensorFlow 中有可以读取和解析任何文件扩展名的图像的 API。关于图像数据的棘手部分在于捕获其维度——高度、宽度和深度——由像素计数来衡量。(如果是用 RGB 编码的彩色图像,这些会显示为三个独立的通道。)
如果您的数据集中的所有图像(包括训练、验证以及测试或部署时的所有图像)都预期具有相同的尺寸并且您将构建自己的模型,那么处理图像数据并不是太大的问题。然而,如果您希望利用预构建的模型如 ResNet 或 Inception,那么您必须符合它们的图像要求。例如,ResNet 要求每个输入图像为 224 × 224 × 3 像素,并呈现为 NumPy 多维数组。这意味着在预处理过程中,您必须重新采样您的图像以符合这些尺寸。
另一个需要重新采样的情况是当您无法合理地期望所有图像,特别是在部署时,具有相同的尺寸。在这种情况下,您需要在构建模型时考虑适当的图像尺寸,然后设置预处理程序以确保重新采样正确进行。
在本节中,您将使用 TensorFlow 提供的花卉数据集。它包含五种类型的花卉和不同的图像尺寸。这是一个方便的数据集,因为所有图像都已经是 JPEG 格式。您将处理这些图像数据,训练一个模型来解析每个图像并将其分类为五类花卉之一。
像往常一样,导入所有必要的库:
import tensorflow as tf
import numpy as np
import matplotlib.pylab as plt
import pathlib
现在从以下网址下载花卉数据集:
data_dir = tf.keras.utils.get_file(
'flower_photos',
'https://storage.googleapis.com/download.tensorflow.org/
example_images/flower_photos.tgz',
untar=True)
这个文件是一个压缩的 TAR 存档文件。因此,您需要设置untar=True。
使用tf.keras.utils.get_file时,默认情况下会在~/.keras/datasets目录中找到下载的数据。
在 Mac 或 Linux 系统的 Jupyter Notebook 单元格中执行:
!ls -lrt ~/.keras/datasets/flower_photos
您将找到如图 3-6 所示的花卉数据集。
图 3-6. 花卉数据集文件夹
现在让我们看看其中一种花卉:
!ls -lrt ~/.keras/datasets/flower_photos/roses | head -10
您应该看到前九个图像,如图 3-7 所示。
图 3-7. 玫瑰目录中的九个示例图像文件
这些图像都是不同的尺寸。您可以通过检查几张图像来验证这一点。以下是一个您可以利用的辅助函数,用于显示原始尺寸的图像:¹
def display_image_in_actual_size(im_path):
dpi = 100
im_data = plt.imread(im_path)
height, width, depth = im_data.shape
# What size does the figure need to be in inches to fit
# the image?
figsize = width / float(dpi), height / float(dpi)
# Create a figure of the right size with one axis that
# takes up the full figure
fig = plt.figure(figsize=figsize)
ax = fig.add_axes([0, 0, 1, 1])
# Hide spines, ticks, etc.
ax.axis('off')
# Display the image.
ax.imshow(im_data, cmap='gray')
plt.show()
让我们用它来显示一张图像(如图 3-8 所示):
IMAGE_PATH = "/root/.keras/datasets/flower_photos/roses/
7409458444_0bfc9a0682_n.jpg"
display_image_in_actual_size(IMAGE_PATH)
图 3-8。玫瑰图像示例 1
现在尝试不同的图像(如图 3-9 所示):
IMAGE_PATH = "/root/.keras/datasets/flower_photos/roses/
5736328472_8f25e6f6e7.jpg"
display_image_in_actual_size(IMAGE_PATH)
图 3-9。玫瑰图像示例 2
显然,这些图像的尺寸和长宽比是不同的。
将图像转换为固定规格
现在您已经准备好将这些图像转换为固定规格。在这个特定的示例中,您将使用 ResNet 输入图像规格,即 224×224,带有三个颜色通道(RGB)。此外,尽可能使用数据流。因此,您的目标是将这些彩色图像转换为 224×224 像素的形状,并从中构建一个数据集,以便流式传输到训练范式中。
为了实现这一点,您将使用ImageDataGenerator类和flow_from_directory方法。
ImageDataGenerator负责创建一个生成器对象,该对象从由flow_from_directory指定的目录中生成流式数据。
一般来说,编码模式是:
my_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
**datagen_kwargs)
my_generator = my_datagen.flow_from_directory(
data_dir, **dataflow_kwargs)
在这两种情况下,关键字参数选项或kwargs为您的代码提供了很大的灵活性。(关键字参数在 Python 中经常见到。)这些参数使您能够将可选参数传递给函数。事实证明,在ImageDataGenerator中,有两个与您需求相关的参数:rescale和validation_split。rescale参数用于将像素值归一化为有限范围,validation_split允许您将数据的一个分区细分,例如用于交叉验证。
在flow_from_directory中,有三个对于本示例有用的参数:target_size、batch_size和interpolation。target_size参数帮助您指定每个图像的期望尺寸,batch_size用于指定批量图像中的样本数。至于interpolation,请记住您需要对每个图像进行插值或重新采样,以达到用target_size指定的规定尺寸?插值的支持方法有nearest、bilinear和bicubic。对于本示例,首先尝试bilinear。
您可以将这些关键字参数定义如下。稍后将把它们传递给它们的函数调用:
pixels =224
BATCH_SIZE = 32
IMAGE_SIZE = (pixels, pixels)
datagen_kwargs = dict(rescale=1./255, validation_split=.20)
dataflow_kwargs = dict(target_size=IMAGE_SIZE,
batch_size=BATCH_SIZE,
interpolation="bilinear")
创建一个生成器对象:
valid_datagen = tf.keras.preprocessing.image.ImageDataGenerator(
**datagen_kwargs)
现在您可以指定此生成器将从中流式传输数据的源目录。此生成器将仅流式传输 20%的数据,并将其指定为验证数据集:
valid_generator = valid_datagen.flow_from_directory(
data_dir, subset="validation", shuffle=False,
**dataflow_kwargs)
您可以使用相同的生成器对象进行训练数据:
train_datagen = valid_datagen
train_generator = train_datagen.flow_from_directory(
data_dir, subset="training", shuffle=True, **dataflow_kwargs)
检查生成器的输出:
for image_batch, labels_batch in train_generator:
print(image_batch.shape)
print(labels_batch.shape)
break
(32, 224, 224, 3)
(32, 5)
输出表示为 NumPy 数组。对于一批图像,样本大小为 32,高度和宽度为 224 像素,三个通道表示 RGB 颜色空间。对于标签批次,同样有 32 个样本。每行都是独热编码,表示属于五类中的哪一类。
另一个重要的事情是检索标签的查找字典。在推断期间,模型将输出每个五类中的概率。唯一的解码方式是使用标签的预测查找字典来确定哪个类具有最高的概率:
labels_idx = (train_generator.class_indices)
idx_labels = dict((v,k) for k,v in labels_idx.items())
print(idx_labels)
{0: 'daisy', 1: 'dandelion', 2: 'roses', 3: 'sunflowers',
4: 'tulips'}
我们分类模型的典型输出将类似于以下的 NumPy 数组:
(0.7, 0.1, 0.1, 0.05, 0.05)
具有最高概率值的位置是第一个元素。将此索引映射到idx_labels中的第一个键 - 在本例中为daisy。这是您捕获预测结果的方法。保存idx_labels字典:
import pickle
with open('prediction_lookup.pickle', 'wb') as handle:
pickle.dump(idx_labels, handle,
protocol=pickle.HIGHEST_PROTOCOL)
这是如何加载它的方法:
with open('prediction_lookup.pickle', 'rb') as handle:
lookup = pickle.load(handle)
训练模型
最后,对于训练,您将使用从预训练的 ResNet 特征向量构建的模型。这种技术称为迁移学习。TensorFlow Hub 免费提供许多预训练模型。这是在模型构建过程中访问它的方法:
import tensorflow_hub as hub
NUM_CLASSES = 5
mdl = tf.keras.Sequential([
tf.keras.layers.InputLayer(input_shape=IMAGE_SIZE + (3,)),
hub.KerasLayer("https://tfhub.dev/google/imagenet/
resnet_v1_101/feature_vector/4", trainable=False),
tf.keras.layers.Dense(NUM_CLASSES, activation='softmax',
name = 'custom_class')
])
mdl.build([None, 224, 224, 3])
第一层是InputLayer。请记住,预期输入为 224×224×3 像素。您将使用元组添加技巧将额外的维度附加到IMAGE_SIZE:
IMAGE_SIZE + (3,)
现在您有了(224, 224, 3),这是一个表示图像维度的 NumPy 数组的元组。
下一层是由指向 TensorFlow Hub 的预训练 ResNet 特征向量引用的层。让我们直接使用它,这样我们就不必重新训练它。
接下来是具有五个输出节点的Dense层。每个输出是图像属于该类的概率。然后,您将构建模型骨架,第一个维度为None。这意味着第一个维度,代表批处理的样本大小,在运行时尚未确定。这是如何处理批输入的方法。
检查模型摘要以确保它符合您的预期:
mdl.summary()
输出显示在图 3-10 中。
图 3-10. 图像分类模型摘要
使用optimizers和相应的losses函数编译模型:
mdl.compile(
optimizer=tf.keras.optimizers.SGD(lr=0.005, momentum=0.9),
loss=tf.keras.losses.CategoricalCrossentropy(
from_logits=True,
label_smoothing=0.1),
metrics=['accuracy'])
然后对其进行训练:
steps_per_epoch = train_generator.samples //
train_generator.batch_size
validation_steps = valid_generator.samples //
valid_generator.batch_size
mdl.fit(
train_generator,
epochs=5, steps_per_epoch=steps_per_epoch,
validation_data=valid_generator,
validation_steps=validation_steps)
您可能会看到类似于图 3-11 的输出。
图 3-11. 训练图像分类模型的输出
摘要
在本节中,您学习了如何处理图像文件。具体来说,在设计模型之前,有必要确保您已经设置了一个预定的图像大小要求。一旦这个标准被接受,下一步就是将图像重新采样到该大小,并将像素的值归一化为更小的动态范围。这些例程几乎是通用的。此外,将图像流式传输到训练工作流程是最有效的方法和最佳实践,特别是在您的工作样本大小接近 Python 运行时内存的情况下。
为处理文本数据做准备
对于文本数据,每个单词或字符都需要表示为一个数字整数。这个过程被称为标记化。此外,如果目标是分类,那么目标需要被编码为类别。如果目标是更复杂的,比如翻译,那么训练数据中的目标语言(比如英语到法语翻译中的法语)也需要自己的标记化过程。这是因为目标本质上是一个长字符串的文本,就像输入文本一样。同样,您还需要考虑是在单词级别还是字符级别对目标进行标记化。
文本数据可以以许多不同的格式呈现。从内容组织的角度来看,它可以被存储和组织为一个表格,其中一列包含文本的主体或字符串,另一列包含标签,例如二进制情感指示器。它可能是一个自由格式的文件,每行长度不同,每行末尾有一个换行符。它可能是一份手稿,其中文本块由段落或部分定义。
有许多方法可以确定要使用的处理技术和逻辑,当您设置自然语言处理(NLP)机器学习问题时;本节将涵盖一些最常用的技术。
这个例子将使用威廉·莎士比亚的悲剧《科里奥兰纳斯》中的文本,这是一个简单的公共领域示例,托管在谷歌上。您将构建一个文本生成模型,该模型将学习如何以莎士比亚的风格写作。
对文本进行标记化
文本由字符字符串表示。这些字符需要转换为整数以进行建模任务。这个例子是科里奥兰纳斯的原始文本字符串。
让我们导入必要的库并下载文本文件:
import tensorflow as tf
import numpy as np
import os
import time
FILE_URL = 'https://storage.googleapis.com/download.tensorflow.org/
data/shakespeare.txt'
FILE_NAME = 'shakespeare.txt'
path_to_file = tf.keras.utils.get_file('shakespeare.txt', FILE_URL)
打开它并输出几行示例文本:
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')
print ('Length of text: {} characters'.format(len(text)))
通过打印前 400 个字符来检查这段文本:
print(text[:400])
输出显示在图 3-12 中。
为了对该文件中的每个字符进行标记化,简单的set操作就足够了。这个操作将创建在文本字符串中找到的字符的一个唯一集合:
vocabulary = sorted(set(text))
print ('There are {} unique characters'.format(len(vocabulary)))
There are 65 unique characters
图 3-13 展示了vocabulary列表的一瞥。
图 3-12. 威廉·莎士比亚《科里奥兰纳斯》的示例
图 3-13.《科里奥兰纳斯》词汇列表的一部分
这些标记包括标点符号,以及大写和小写字符。不一定需要同时包含大写和小写字符;如果不想要,可以在执行set操作之前将每个字符转换为小写。由于您对标记列表进行了排序,您可以看到特殊字符也被标记化了。在某些情况下,这是不必要的;这些标记可以手动删除。以下代码将所有字符转换为小写,然后执行set操作:
vocabulary = sorted(set(text.lower()))
print ('There are {} unique characters'.format(len(vocabulary)))
There are 39 unique characters
您可能会想知道是否将文本标记化为单词级而不是字符级是否合理。毕竟,单词是对文本字符串的语义理解的基本单位。尽管这种推理是合理的并且有一定的逻辑性,但实际上它会增加更多的工作和问题,而并没有真正为训练过程增加价值或为模型的准确性增加价值。为了说明这一点,让我们尝试按单词对文本字符串进行标记化。首先要认识到的是单词是由空格分隔的。因此,您需要在空格上拆分文本字符串:
vocabulary_word = sorted(set(text.lower().split(' ')))
print ('There are {} unique words'.format(len(vocabulary_word)))
There are 41623 unique words
检查vocabulary_word列表,如图 3-14 所示。
由于每个单词标记中嵌入了特殊字符和换行符,这个列表几乎无法使用。需要通过正则表达式或更复杂的逻辑来清理它。在某些情况下,标点符号附加在单词上。此外,单词标记列表比字符级标记列表要大得多。这使得模型更难学习文本中的模式。出于这些原因和缺乏已证明的好处,将文本标记化为单词级并不是一种常见做法。如果您希望使用单词级标记化,则通常会执行单词嵌入操作以减少话语表示的变异性和维度。
图 3-14. 标记化单词的示例
创建字典和反向字典
一旦您有包含所选字符的标记列表,您将需要将每个标记映射到一个整数。这被称为字典。同样,您需要创建一个反向字典,将整数映射回标记。
使用enumerate函数很容易生成一个整数。这个函数以列表作为输入,并返回与列表中每个唯一元素对应的整数。在这种情况下,列表包含标记:
for i, u in enumerate(vocabulary):
print(i, u)
您可以在图 3-15 中看到这个结果的示例。
图 3-15. 标记列表的示例枚举输出
接下来,您需要将其制作成一个字典。字典实际上是一组键值对,用作查找表:当您给出一个键时,它会返回与该键对应的值。构建字典的表示法,键是标记,值是整数:
char_to_index = {u:i for i, u in enumerate(vocabulary)}
输出将类似于图 3-16。
这个字典用于将文本转换为整数。在推理时,模型输出也是整数格式。因此,如果希望输出为文本,则需要一个反向字典将整数映射回字符。要做到这一点,只需颠倒i和u的顺序:
index_to_char = {i:u for i, u in enumerate(vocabulary)}
图 3-16. 字符到索引字典的示例
标记化是大多数自然语言处理问题中最基本和必要的步骤。文本生成模型不会生成纯文本作为输出;它会生成一系列整数作为输出。为了使这一系列索引映射到字母(标记),您需要一个查找表。index_to_char就是专门为此目的构建的。使用index_to_char,您可以通过键查找每个字符(标记),其中键是模型输出的索引。没有index_to_char,您将无法将模型输出映射回可读的纯文本格式。
总结
在本章中,您学习了如何处理一些最常见的数据结构:表格、图像和文本。表格数据集(结构化的、类似 CSV 的数据)非常常见,通常从数据库查询返回,并经常用作训练数据。您学会了如何处理这些结构中不同数据类型的列,以及如何通过交叉感兴趣的列来建模特征交互。
对于图像数据,您学会了在使用整个图像集训练模型之前需要标准化图像大小和像素值,以及需要跟踪图像标签。
文本数据在格式和用途方面是最多样化的数据类型。然而,无论数据是用于文本分类、翻译还是问答模型,标记化和字典构建过程都非常常见。本章描述的方法和方法并不是详尽或全面的;相反,它们代表了处理这些数据类型时的“基本要求”。
¹ 用户 Joe Kington 在StackOverflow上的回答,2016 年 1 月 13 日,2020 年 10 月 23 日访问。