生产就绪的应用深度学习指南-一-

181 阅读1小时+

生产就绪的应用深度学习指南(一)

原文:zh.annas-archive.org/md5/e14f18bbb2088c66db1babf46e8b6eee

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

随着对人工智能AI)日益增长的兴趣,有数以百万计的资源介绍了各种深度学习DL)技术,解决了广泛的问题。它们可能足以让您获得许多朋友梦寐以求的数据科学家职位。然而,您很快就会发现,DL 项目的真正困难不仅在于为给定问题选择正确的算法,还包括以正确的格式高效预处理必要的数据并提供稳定的服务。

本书将引导您完成深度学习项目的每一个步骤。我们从在笔记本中编写的概念验证模型开始,将该模型转变为服务或应用程序,旨在在部署后最大化用户满意度。然后,我们使用亚马逊网络服务AWS)有效地提供稳定的服务。此外,我们还将看看如何在部署后监控运行深度学习模型的系统,从而完全闭环。

在整本书中,我们重点介绍了工程师们在技术前沿日常使用的各种技术,以满足严格的服务规范要求。

完成本书阅读后,您将更广泛地了解大规模部署深度学习应用的真实困难,并能够以最高效、最有效的方式克服这些挑战。

本书适合对象

机器学习工程师、深度学习专家和数据科学家会发现本书在通过详细示例缩小理论与应用之间的差距方面很有帮助。具备机器学习或软件工程的初学者级知识将有助于您轻松掌握本书涵盖的概念。

本书内容涵盖

第一章, 深度学习驱动项目的有效规划,全面介绍了如何准备深度学习项目。我们介绍了项目规划中使用的各种术语和技术,并描述了如何构建项目手册,总结计划。

第二章, 深度学习项目的数据准备,描述了深度学习项目的第一步,即数据收集和数据准备。本章介绍了如何为项目准备笔记本设置,收集必要的数据,并有效地为训练深度学习模型进行处理。

第三章, 开发强大的深度学习模型,解释了深度学习的理论及如何使用最流行的框架 PyTorch 和 TensorFlow 开发模型。

第四章, 实验跟踪、模型管理和数据集版本控制,介绍了一系列有用的工具,用于实验跟踪、模型管理和数据集版本控制,从而有效管理深度学习项目。

第五章云中的数据准备,专注于使用 AWS 扩展数据处理流水线。具体来说,我们看看如何以成本效益的方式设置和调度提取、转换和加载ETL)作业。

第六章高效模型训练,首先描述了如何配置 TensorFlow 和 PyTorch 的训练逻辑,以利用不同机器上的多个 CPU 和 GPU 设备。然后,我们看看为分布式训练开发的工具:SageMaker、Horovod、Ray 和 Kubeflow。

第七章揭示深度学习模型的秘密,介绍了超参数调整,这是找到正确训练配置的最标准过程。我们还涵盖了可解释 AI,一套用于了解 DL 模型在幕后工作的过程和方法。

第八章简化深度学习模型部署,描述了如何利用开放神经网络交换ONNX),这是用于机器学习模型的标准文件格式,将模型转换为各种框架,有助于将模型开发与模型部署分离。

第九章扩展深度学习流水线,介绍了两个最流行的 AWS 特性,旨在将 DL 模型部署为推理端点:弹性 Kubernetes 服务EKS)和 SageMaker。

第十章提高推理效率,介绍了在部署过程中如何通过网络量化、权重共享、网络修剪、知识蒸馏和网络架构搜索等技术来提高推理延迟,同时尽可能保持原始性能。

第十一章移动设备上的深度学习,描述了如何使用 TensorFlow Lite 和 PyTorch Mobile 在移动设备上部署 TensorFlow 和 PyTorch 模型。

第十二章监控生产中的深度学习端点,解释了用于监控运行中 DL 模型系统的现有解决方案。具体来说,我们讨论了如何将 CloudWatch 集成到在 SageMaker 和 EKS 集群上运行的端点中。

第十三章审查完成的深度学习项目,涵盖了 DL 项目的最后阶段,审查过程。我们描述了如何有效评估项目并为下一个项目做准备。

要充分利用本书

尽管我们在旅程中将与许多工具互动,所有安装说明均包含在书籍和 GitHub 存储库中。在阅读本书之前,您唯一需要准备的是 AWS 账户。AWS 提供免费层级(aws.amazon.com/free),应该足以让您开始。

书中涉及的软件/硬件操作系统要求
TensorFlowWindows、macOS 或 Linux
PyTorch
Docker
Weights & Biases、MLflow 和 DVC
ELI5 and SHAP
Ray and Horovod
AWS SageMaker
AWS EKS

如果您想尝试运行本书中的示例,请使用我们仓库或官方文档页面上的完整版本,因为书中的版本可能会缺少某些组件,以增强内容的传递效果。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件,链接为github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning。如果代码有更新,将在 GitHub 仓库中更新。

我们还有其他代码包,来自我们丰富的书籍和视频目录,可在github.com/PacktPublishing/查看!

下载彩色图像

我们还提供一个 PDF 文件,其中包含本书中使用的截图和图表的彩色图像。您可以在此处下载:packt.link/fUhAv

使用的约定

本书中使用了许多文本约定。

文本中的代码: 表示文本中的代码词汇,数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 句柄。例如:“将下载的WebStorm-10*.dmg磁盘映像文件挂载为系统中的另一磁盘。”

代码块设置如下:

html, body, #map {
 height: 100%; 
 margin: 0;
 padding: 0
}

当我们希望引起您对代码块特定部分的注意时,相关行或条目将设置为粗体:

[default]
exten => s,1,Dial(Zap/1|30)
exten => s,2,Voicemail(u100)
exten => s,102,Voicemail(b100)
exten => i,1,Voicemail(s0)

任何命令行输入或输出均以以下格式编写:

$ mkdir css
$ cd css

粗体: 表示一个新术语、重要词或您在屏幕上看到的词。例如,菜单或对话框中的词以粗体显示。例如:“从管理面板中选择系统信息。”

提示或重要注意事项

显示如下。

联系我们

我们的读者的反馈总是受欢迎的。

一般反馈: 如果您对本书的任何方面有疑问,请通过电子邮件联系我们,邮件地址为 customercare@packtpub.com,并在主题中提到书名。

勘误: 尽管我们已竭尽全力确保内容的准确性,但错误确实偶尔会发生。如果您发现本书中的错误,请向我们报告。请访问www.packtpub.com/support/err…并填写表单。

盗版: 如果您在互联网上发现我们作品的任何非法副本,请向我们提供位置地址或网站名称。请通过链接至版权@packt.com 与我们联系。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且有意参与书籍的撰写或贡献,请访问 authors.packtpub.com

分享您的想法

一旦您阅读完《生产就绪的应用深度学习》,我们很乐意听取您的想法!请点击此处前往亚马逊书评页面并分享您的反馈。

您的评论对我们和技术社区非常重要,将帮助我们确保提供优质的内容。

第一部分:建立最小可行产品

AI 项目始于规划和理解给定问题的难度。一旦项目范围明确定义,下一步是创建最小可行产品MVP)。对于基于深度学习的项目,此过程涉及准备数据集并探索各种模型架构,以找到解决问题的可行方案。在本书的第一部分中,我们解释如何通过利用各种资源高效地完成上述步骤。

本部分包括以下章节:

  • 第一章, 深度学习驱动项目的有效规划

  • 第二章, 深度学习项目的数据准备

  • 第三章, 开发强大的深度学习模型

  • 第四章, 实验跟踪、模型管理和数据集版本控制

第一章:有效规划深度学习驱动项目

在书的第一章中,我们将介绍什么是深度学习DL),以及 DL 项目通常如何进行。本章以对 DL 的介绍开始,提供一些关于它如何影响我们日常生活的见解。然后,我们将把重点转移到 DL 项目上,描述它们的结构。在整个章节中,我们将特别强调第一阶段,即项目规划;您将学习到关键概念,如理解业务目标、如何定义适当的评估指标、识别利益相关者、资源规划,以及最小可行产品MVP)和完全特性产品FFP)之间的区别。到本章末,您应该能够制定一个 DL 项目手册,清楚地描述项目的目标、里程碑、任务、资源分配及其时间表。

在本章中,我们将讨论以下主要主题:

  • DL 是什么?

  • 理解 DL 在我们日常生活中的作用

  • DL 项目概述

  • 规划 DL 项目

技术要求

您可以从以下 GitHub 链接下载本章的补充材料:

github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_1

DL 是什么?

自 DL 出现以来仅过去了十年,但它已经迅速开始在我们的日常生活中扮演重要角色。在人工智能AI)领域内,存在一套被归类为机器学习ML)的流行方法。通过从历史数据中提取有意义的模式,ML 的目标是构建一个能为新收集的数据做出合理预测和决策的模型。DL 是一种 ML 技术,利用人工神经网络ANNs)来捕捉给定模式。图 1.1展示了自 20 世纪 50 年代开始的 AI 演变的关键组成部分,包括艾伦·图灵等 AI 领域的先驱讨论智能机器。虽然自 AI 问世以来引入了各种 ML 算法,但实际上,这个领域花了几十年才真正开花结果。同样地,自 DL 成为主流也只有大约十年,因为这个领域的许多算法需要大量的计算能力。

图 1.1 – AI 的历史

图 1.1 – AI 的历史

图 1.2所示,DL 的关键优势来自于人工神经网络(ANNs),它们能够自动选择必要的特征。类似于人类大脑的结构,ANNs 也由称为神经元的组件组成。一组神经元形成一个,多个层链接在一起形成一个网络。这种架构可以理解为多个嵌套指令的多步骤。随着输入数据通过网络传递,每个神经元提取不同的信息,模型被训练为选择给定任务最相关的特征。考虑到神经元作为模式识别的构建块,更深的网络通常导致更好的性能,因为它们更好地学习细节。

图 1.2 - 机器学习与深度学习之间的区别

图 1.2 - 机器学习与深度学习之间的区别

虽然典型的机器学习技术需要手动选择特征,但 DL 学会了自动选择重要的特征。因此,它可能适用于更广泛的问题。然而,这种优势并非免费。为了正确训练 DL 模型,训练数据集需要足够大且多样化,这导致训练时间增加。有趣的是,图形处理单元GPU)在缩短训练时间方面发挥了重要作用。虽然中央处理单元CPU)通过其广泛的指令集在执行复杂计算方面表现有效,但 GPU 更适合通过其大规模并行性处理更简单但更大的计算。通过利用 DL 模型严重依赖的矩阵乘法中的这种优势,GPU 已成为 DL 中的关键组成部分。

由于我们仍处于人工智能时代的早期阶段,芯片技术正在不断发展,目前尚不清楚这些技术将如何在未来发生变化。值得一提的是,新设计不仅来自初创企业,还来自大型科技公司。这场持续的竞争清楚地显示出,基于人工智能的产品和服务将不断推出。考虑到市场增长和工作机会的增加,我们认为现在是学习 DL 的绝佳时机。

要记住的事情

a. DL 是一种利用人工神经网络进行模式识别的机器学习技术。

b. DL 非常灵活,因为它在整个训练过程中自动选择给定任务的最相关特征。

c. GPU 可以通过其大规模并行性加速 DL 模型的训练。

现在我们高层次地理解了 DL 是什么,接下来我们将在下一节中描述它如何塑造我们的日常生活。

理解 DL 在我们日常生活中的角色

通过利用 DL 的灵活性,研究人员在传统 ML 技术性能有限的领域取得了显著进展(见图 1.3)。首先在计算机视觉CV)领域为数字识别和目标检测任务打下了基础。然后,DL 被采用于自然语言处理NLP),在翻译和语音识别任务中显示出显著进展。它还展示了在强化学习RL)以及生成建模中的有效性。

本章的进一步阅读部分列出了与 DL 相关的论文的流行用例。

下图显示了 DL 的各种应用:

图 1.3 – DL 的应用

图 1.3 – DL 的应用

然而,将 DL 集成到现有技术基础设施中并非易事;困难可能来自各个方面,包括但不限于预算、时间以及人才。因此,对 DL 的深入理解已成为那些管理 DL 项目的人必备的技能:项目经理、技术负责人以及高管。此外,对这一快速增长领域的了解将使他们能够在特定垂直领域及整个组织中找到未来的机会,因为从事 AI 项目的人积极收集新知识以获得创新和竞争优势。总体而言,深入了解 DL 管道并开发可投入生产的输出使管理者能够通过有效避免已知的常见问题更好地执行项目。

不幸的是,DL 项目尚未达到即插即用的状态。在许多情况下,它们涉及广泛的研究和调整阶段,这可能迅速耗尽可用资源。最重要的是,我们注意到许多公司在从概念验证转向生产时遇到困难,因为关键决策是由少数只对项目需求和 DL 管道有有限理解的人做出的。话虽如此,我们的书旨在全面介绍 DL 项目的全貌;我们将从项目规划开始,然后讨论如何开发 MVP 和 FFP,如何利用云服务进行扩展,最后讨论如何将产品交付给目标用户。

需要记住的事情

a. DL 已应用于各个领域的许多问题,包括但不限于 CV、NLP、RL 和生成建模。

b. 对 DL 的深入理解对于领导 DL 项目的人员至关重要,无论其职位或背景如何。

c. 本书通过描述 DL 项目从项目规划到部署的过程,提供了对 DL 项目的全面理解。

接下来,我们将仔细研究 DL 项目的结构。

DL 项目概述

尽管深度学习和其他软件工程项目有很多共同点,但深度学习项目强调规划,主要来自模型复杂性和涉及的大量数据的资源需求。一般而言,深度学习项目可以分为以下阶段:

  1. 项目规划

  2. 构建 MVP

  3. 构建 FFP

  4. 部署和维护

  5. 项目评估

在本节中,我们提供了这些阶段的高级概述。接下来的部分将详细介绍每个阶段。

项目规划

作为第一步,项目负责人必须明确定义项目需要实现的目标,并了解可能影响或被项目影响的群体。必须定义并达成一致的评估指标,因为它们将在项目评估期间重新审视。然后,团队成员集结在一起,讨论个人责任,并利用现有资源实现业务目标。这个过程自然而然地导致了一个时间表,即项目需要多长时间完成的估计。总体而言,项目规划应该产生一个称为 playbook 的文件,其中包括项目如何进行和评估的详细描述。

构建最小可行产品

一旦方向对每个人都清晰,下一步是构建 MVP,这是目标交付物的简化版本,展示项目的价值。此阶段的另一个重要方面是了解项目的困难,并通过“快速失败,频繁失败”的哲学拒绝风险更大或前景不明确的路径。因此,数据科学家和工程师通常在开发环境中使用部分采样的数据集,并忽略不重要的优化。

构建完整功能产品

一旦 MVP 项目的可行性得到确认,必须将其打包成 FFP。此阶段旨在优化 MVP,构建一个可投入生产的交付成果,并进行各种优化。对于深度学习项目,引入额外的数据准备技术以提高输入数据的质量,或者稍微增强模型管道以获得更好的模型性能。此外,数据准备管道和模型训练管道可能会迁移到云端,利用各种网络服务提高吞吐量和质量。在这种情况下,整个管道通常会使用不同的工具和服务重新实现。本书专注于处理大量数据和昂贵计算的最流行网络服务亚马逊网络服务(AWS)

部署和维护

在许多情况下,部署设置与开发设置不同。因此,在将 FFP 移至生产环境时通常涉及不同的工具集。此外,部署过程可能会引入在开发过程中看不到的问题,这主要是由于计算资源有限而引起的。因此,许多工程师和科学家在此阶段花费额外时间来改善用户体验。大多数人认为部署是最后一步。然而,还有一个步骤:维护。需要持续监控数据质量和模型性能,以向目标用户提供稳定的服务。

项目评估

在最后阶段,项目评估阶段,团队应重新审视项目规划期间所做的讨论,评估项目是否成功进行。此外,需要记录项目的详细信息,并讨论潜在的改进措施,以便更有效地完成下一个项目。

需记住的事情

a. DL 项目内的阶段包括项目规划、构建 MVP、构建 FFP、部署和维护以及项目评估。

b. 在项目规划阶段,确定项目目标和评估指标,团队讨论个人责任、可用资源和项目的时间表。

c. 建立 MVP 的目的是了解项目的困难之处,并拒绝存在更大风险或提供较少前景的路径。

d. FFP 是一个生产就绪的可交付成果,是 MVP 的优化版本。数据准备管道和模型训练管道可以迁移到云端,利用各种网络服务以获得更高的吞吐量和质量。

e. 部署设置通常提供有限的计算资源。在这种情况下,系统需要进行调优,以向目标用户提供稳定的服务。

f. 项目完成后,团队需要重新审视时间表、分配的责任和业务需求,评估项目的成功与否。

在接下来的部分中,我们将详细介绍如何正确规划一个 DL 项目。

规划一个 DL 项目

每个项目都始于规划。在规划过程中,需要清晰定义项目的目的,并且关键成员应该对可以分配给项目的可用资源有深入了解。一旦确定了团队成员和利益相关者,下一步是讨论每个人的责任,并为项目制定时间表。

这个阶段应该产生一个详细记录的项目手册,精确定义业务目标以及项目评估方式。典型的手册包括主要交付内容概述、利益相关者列表、甘特图定义的步骤和瓶颈、责任定义、时间表和评估标准。在高度复杂的项目中,强烈建议遵循项目管理知识体系指南PMBOK®)(www.pmi.org/pmbok-guide-standards/foundational/pmbok),并考虑每个知识领域(例如,整合管理、项目范围管理和时间管理)。当然,还有其他的项目管理框架,如PRINCE2www.prince2.com/usa/what-is-prince2),它可以作为一个很好的起点。一旦手册编制完成,每个利益相关者都必须审查和修订,直到所有人都同意内容。

在现实生活中,很多人低估了规划的重要性。特别是在初创公司中,工程师们渴望投入到 MVP 开发中,并尽量少花时间在规划上。然而,在 DL 项目中这样做是非常危险的,因为训练过程可能会迅速消耗所有可用资源。

定义目标和评估指标

规划的第一步是理解项目的目的。目标可能是开发新产品、改善现有服务的性能或节省运营成本。项目的动机自然有助于定义评估指标。

在 DL 项目中,有两种评估指标:与业务相关的指标和基于模型的指标。一些业务相关的指标的例子如下:转化率点击率CTR),用户生命周期价值用户参与度度量运营成本节约投资回报率ROI)和收入。这些在广告、营销和产品推荐领域中广泛使用。

另一方面,基于模型的指标包括准确率精确率召回率F1 分数排名准确度指标平均绝对误差MAE),均方误差MSE),均方根误差RMSE)和标准化平均绝对误差NMAE)。通常,可以在这些指标之间进行权衡。例如,如果满足延迟要求对项目更为关键,那么稍微降低准确率可能是可以接受的。

除了因项目而异的目标评估指标外,大多数项目中还包括其他常见的指标,如截止日期和资源使用情况。必须利用现有资源,在特定日期达到目标状态。

目标及相应的评估指标需要公平。如果目标过于难以实现,项目成员可能会失去动力。如果评估指标不正确,理解项目的影响也变得困难。因此,建议将选择的评估指标与他人分享,并确保公平对待每个人。

图 1.4 – 一个填写了项目描述部分的示例 playbook

图 1.4 – 一个填写了项目描述部分的示例 playbook

图 1.4所示,playbook 的第一部分从一般描述开始,估计技术方面的复杂性,并列出所需的工具和框架。接下来,它清晰地描述了项目的目标及其评估方式。

利益相关者识别

就像“利益相关者”这个术语在业务中使用一样,项目的利益相关者指的是可以影响或受到项目影响的人或群体。利益相关者可以分为两类,即内部和外部。内部利益相关者直接参与项目执行,而外部利益相关者可能在外围支持项目的执行。

每个利益相关者在项目中扮演不同的角色。首先,我们来看内部利益相关者。内部利益相关者是项目的主要推动者。因此,他们密切合作处理和分析数据,开发模型并建立可交付成果。表 1.1列出了在 DL 项目中常见的内部利益相关者:

表 1.1 – DL 项目的常见内部利益相关者

表 1.1 – DL 项目的常见内部利益相关者

另一方面,外部利益相关者通常扮演支持角色,例如收集项目所需的数据或对可交付成果提供反馈。在表 1.2中,我们描述了一些 DL 项目的常见外部利益相关者:

表 1.2 – DL 项目的常见外部利益相关者

表 1.2 – DL 项目的常见外部利益相关者

Playbook 的第二部分描述了利益相关者。如图 1.4所示,playbook 必须列出项目中的利益相关者及其责任。

任务组织

里程碑指的是项目中发生重大事件的时间点。因此,有一系列要求导致里程碑。一旦满足了这些要求,就可以宣称已经达到了里程碑。项目规划中最重要的步骤之一是定义里程碑及其相关任务。通向目标的任务排序称为关键路径。值得一提的是,任务并不总是需要按顺序处理。理解关键路径很重要,因为它允许团队适当地优先考虑任务,以确保项目的成功。

在这一步骤中,还需要识别工作量评估LOE)活动和支持活动,这些活动对项目的执行至关重要。在软件开发项目中,工作量评估活动包括辅助任务,如设置 Git 仓库或审查他人的合并请求。下图(图 1.5)描述了深度学习项目的典型关键路径。如果底层项目包含不同的任务、需求、技术和所需的详细级别,则其结构将有所不同:

图 1.5 – 深度学习项目的典型关键路径

图 1.5 – 深度学习项目的典型关键路径

资源分配

对于深度学习项目,需要明确分配两大主要资源:人力资源和计算资源。人力资源指的是将积极参与个人任务的员工。通常,他们担任数据工程、数据科学、DevOps 或软件工程等职位。在谈论人力资源时,人们往往只考虑人数。然而,个人所掌握的知识和技能也是其他重要因素。人力资源与项目能够快速进行的相关性密切。

计算资源指的是分配给项目的硬件和软件资源。与典型的软件工程项目(如移动应用程序开发或网页开发)不同,深度学习项目需要大量的计算和大量的数据。加速开发过程的常见技术包括并行处理或使用计算能力更强的机器。在某些情况下,需要在它们之间进行权衡,因为一台高计算能力的单机可能比多台低计算能力的机器成本更高。

总体上说,新颖的深度学习流水线利用灵活和无状态的资源,例如具有容错代码的 AWS Spot 实例。除了硬件资源外,还有可能提供必要功能的框架和服务。如果所需服务需要付费,了解它如何改变项目执行以及如果团队决定自行处理它时对人力资源的需求是非常重要的。

在这一步中,需要将可用资源分配给每个任务。图 1.6列出了前一节中描述的任务,并描述了分配的资源,以及操作成本的估计。每个任务都有自己的风险水平指示器。它适用于一个由三人组成的小团队,在几个 AWS 弹性计算云EC2)实例上进行简单的 DL 项目工作,预计为 4 至 6 个月。请注意,人力资源的成本估算未包含在示例中,因为它根据地理位置和团队资历有很大差异:

图 1.6 – 深度学习项目示例资源分配部分

图 1.6 – 深度学习项目示例资源分配部分

在我们继续下一步之前,我们想提到,将一部分资源作为备份非常重要,以防里程碑所需资源超过分配的资源。

定义时间表

现在我们知道了可用资源,我们应该能够为项目制定时间表。在这一步中,团队需要讨论每个步骤需要多长时间完成。值得一提的是,事情并不总是按计划进行。在项目过程中会遇到许多困难,这些困难可能会延迟最终产品的交付。

因此,在时间表中包含缓冲区是许多组织的常见做法。每位利益相关者都同意时间表是非常重要的。如果有人认为这不合理,需要立即进行调整。图 1.7是根据图 1.6所示信息的最有可能估计时间表的样本甘特图:

图 1.7 – 描述时间表的样本甘特图

图 1.7 – 描述时间表的样本甘特图

值得一提的是,该图表还可用于监视每个任务及整体项目的进度。在这种情况下,可以附加每个指示条的附加注释或可视化,总结进展情况。

管理项目

在项目规划阶段讨论的深度学习项目的另一个重要方面是团队将如何更新其他团队成员并确保项目按时交付的流程。在各种项目管理方法中,敏捷方法尤为适用,因为它有助于将工作分解为较小的部分,并快速迭代开发周期,直至 FFP 出现。由于深度学习项目通常被认为是高度复杂的,因此在研究、开发和优化阶段内工作在短周期内更为便利。在每个周期结束时,利益相关者审查结果并调整其长期目标。敏捷方法论特别适合经验丰富的小团队。在典型情况下,发现 2 周冲刺最为有效,特别是在清晰定义短期目标时。

在冲刺会议期间,团队回顾了上个冲刺的目标,并为即将到来的冲刺定义了目标。还建议每天进行短暂的日常会议,回顾前一天的工作并计划下一天的工作,因为这个过程有助于团队快速识别瓶颈并根据需要调整优先级。用于此过程的常用工具包括JiraAsanaQuickbase。前述工具中大多数还支持预算管理、时间线审查、想法管理和资源跟踪。

需要记住的事情

a. 项目规划应该产生一个清晰描述项目用途及团队如何共同前进达到特定目标状态的操作手册。

b. 项目规划的第一步是定义目标及其相应的评估指标。在深度学习项目的情况下,有两种类型的评估指标:与业务相关的指标和基于模型的指标。

c. 利益相关者是指可以影响或被项目影响的个人或群体。利益相关者可以分为两类:内部和外部。

d. 项目规划的下一个阶段是任务组织。团队需要确定里程碑,识别任务以及 LOE 活动,并理解关键路径。

e. 对于深度学习项目,有两个主要资源需要明确分配:人力资源和计算资源。在资源分配期间,将资源的一部分作为备用非常重要。

f. 在估计项目时间表时,必须与团队共享,并且每个利益相关者必须同意时间表。

g. 敏捷方法论非常适合管理深度学习项目,因为它有助于将工作分解为较小的部分,并快速迭代开发周期。

总结

这一章是我们旅程的介绍。在前两节中,我们描述了深度学习在人工智能大局中的定位,以及它如何不断地塑造我们的日常生活。关键要点是深度学习由于其独特的模型架构而具有高度的灵活性,并且已经积极应用于传统机器学习技术未能显示显著成就的领域。

接下来,我们提供了深度学习项目的高层视角。一般来说,深度学习项目可以分为以下阶段:项目规划、建立最小可行产品(MVP)、建立最终可行产品(FFP)、开发与维护以及项目评估。

本章的主要内容涵盖了深度学习项目中最重要的步骤:项目规划。在这个阶段,项目的目的需要明确定义,评估指标需要明确,每个人必须对利益相关者及其各自的角色有深入了解,最后,参与者需要就任务、里程碑和时间表达成共识。这个阶段的结果将会是一个名为“playbook”的格式良好的文档。在下一章中,我们将学习如何为深度学习项目准备数据。

进一步阅读

这里是一些参考资料,可以帮助您更深入地了解与本章相关的主题。以下研究论文总结了深度学习的流行用例:

  • 计算机视觉

    • 基于梯度的学习在文档识别中的应用 by LeCun et al.

    • ImageNet:大规模分层图像数据库 by Deng et al.

  • 自然语言处理

    • 神经概率语言模型 by Bengio et al.

    • 深度递归神经网络语音识别 by Grave et al.

  • 强化学习

    • 深度强化学习简介 by François-Lavet et al.
  • 生成建模

    • 生成对抗网络 by Goodfellow et al.

第二章:准备深度学习项目的数据

机器学习ML)项目的第一步是数据收集和数据准备。作为机器学习的一个子集,深度学习DL)涉及相同的数据处理过程。我们将从使用 Anaconda 设置标准的 DL Python 笔记本环境开始这一章节。然后,我们将提供在各种格式(JSON、CSV、HTML 和 XML)中收集数据的具体示例。在许多情况下,收集到的数据会进行清理和预处理,因为它包含不必要的信息或无效的格式。

本章将介绍该领域中的流行技术:填补缺失值、删除不必要的条目和归一化值。接下来,您将学习常见的特征提取技术:词袋模型、词频-逆文档频率、独热编码和降维。此外,我们将介绍最流行的数据可视化库 matplotlibseaborn。最后,我们将介绍 Docker 镜像,这些镜像是工作环境的快照,通过将应用程序及其依赖项捆绑在一起,最大限度地减少潜在的兼容性问题。

在本章中,我们将涵盖以下主要主题:

  • 设置笔记本环境

  • 数据收集、数据清洗和数据预处理

  • 从数据中提取特征

  • 执行数据可视化

  • Docker 简介

技术要求

本章的补充材料可以从 GitHub 上下载,网址为 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_2

设置笔记本环境

Python 是一种最流行的编程语言之一,广泛用于数据分析。其优势在于动态类型和无需编译。凭借其灵活性,它已成为数据科学家最常用的语言。在本节中,我们将介绍如何使用 AnacondaPreferred Installer ProgramPIP)为 DL 项目设置 Python 环境。这些工具允许您为每个项目创建独立的环境,同时简化包管理。Anaconda 提供了一个名为 Anaconda Navigator 的带有 GUI 的桌面应用程序。我们将指导您如何设置 Python 环境,并安装用于 DL 项目的流行 Python 库,如 TensorFlowPyTorchNumPypandasscikit-learnMatplotlibSeaborn

设置 Python 环境

Python 可以从 www.python.org/downloads 安装。但是,Python 版本通常可以通过操作系统提供的包管理器(例如 Linux 上的 高级包管理工具 (APT) 和 macOS 上的 Homebrew)获取。建立 Python 环境的第一步是使用 PIP 安装必要的软件包,PIP 是一个允许您安装和管理各种 Python 软件包的软件包管理系统。

安装 Anaconda

当在计算机上设置了多个 Python 项目时,将环境分开可能是理想的,因为每个项目可能依赖于这些软件包的不同版本。Anaconda 可以帮助您管理环境,因为它既设计用于 Python 软件包管理,又设计用于环境管理。它允许您创建虚拟环境,其中安装的软件包绑定到当前活动的每个环境。此外,Anaconda 超越了 Python 的界限,允许用户安装非 Python 库依赖项。

首先,可以从其官方网站 www.anaconda.com 安装 Anaconda。为了完整起见,我们已经在 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/anaconda/anaconda_graphical_installer.md 上用图片描述了安装过程。

也可以直接从终端安装。Anaconda 为每个操作系统提供安装脚本(repo.anaconda.com/archive)。您可以简单地下载适合您系统的脚本的正确版本,并运行它来在您的计算机上安装 Anaconda。例如,我们将描述如何从这些脚本之一为 macOS 安装 Anaconda:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/anaconda/anaconda_zsh.md

使用 Anaconda 设置 DL 项目

在此时,您应该已经准备好使用 Anaconda 环境。现在,我们将创建我们的第一个虚拟环境,并安装 DL 项目所需的必要软件包:

conda create --name bookenv python=3.8

您可以使用以下命令列出可用的 conda 环境:

conda info --envs

您应该看到我们之前创建的 bookenv 环境。要激活此环境,您可以使用以下命令:

conda activate bookenv

同样地,通过以下命令可以实现去激活:

conda deactivate

通过 pip 命令可以安装 Python 包。可以通过 conda install <package name>pip install <package name> 安装 Python 包。在下面的代码片段中,首先我们通过 pip 命令下载了 NumPy,这是科学计算的基础包。然后,我们将通过 conda 命令安装 PyTorch。在安装 PyTorch 时,你必须为 CUDA 提供版本,CUDA 是一种用于通用计算的并行计算平台和编程模型。CUDA 可以通过允许 GPU 并行处理来加速 DL 模型训练:

pip install numpy
conda install pytorch torchvision torchaudio \
cudatoolkit=<cuda version> -c pytorch -c nvidia

TensorFlow 是另一个用于深度学习项目的流行包。与 PyTorch 类似,TensorFlow 为每个 CUDA 版本提供不同的包。完整列表可以在这里找到:www.tensorflow.org/install/source#gpu。为了使所有与深度学习相关的库能够无缝工作,必须保证 Python 版本、TensorFlow 版本、GCC 编译器版本、CUDA 版本和 Bazel 构建工具版本之间的兼容性,如下图所示:

图 2.1 – TensorFlow、Python、GCC、Bazel、cuDNN 和 CUDA 版本的兼容矩阵

图 2.1 – TensorFlow、Python、GCC、Bazel、cuDNN 和 CUDA 版本的兼容矩阵

回到 pip 命令,你可以通过生成一个文本文件来安装所有必需的包,并在单个命令中安装它们,而不是重复输入 install 命令。为了实现这一点,可以将 --requirement (-r) 选项与 pip install 命令一起使用,如下所示:

pip install -r requirements.txt

CPU-only 环境中列出的常见所需包在示例 requirements.txt 文件中列出:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/anaconda/requirements.txt。列表中的主要包括 TensorFlow 和 PyTorch。

现在,让我们看一些有用的 Anaconda 命令。就像 pip install 可以与 requirements.txt 文件一起使用一样,你也可以使用 YAML 文件创建一个包含一组包的环境。在以下示例中,我们使用了一个 env.yml 文件来保存现有环境中的库列表。稍后,可以使用 env.yml 创建一个具有相同包的新环境,如下代码片段所示:

conda create -n env_1
conda activate env_1
# save environment to a file
conda env export > env.yml
# clone existing environment 
conda create -n env_2 --clone env_1
# delete existing environment (env_1)
conda remove -n env_1 --all
# create environment (env_1) from the yaml file
conda env create -f env.yml
# using conda to install the libraries from requirements.txt
conda install --force-reinstall -y -q --name py37 -c conda-forge --file requirements.txt

下面的代码片段描述了从 conda env export 生成的样本 YAML 文件:

# env.yml
name: env_1
channels:
  - defaults
dependencies:
  - appnope=0.1.2=py39hecd8cb5_1001
  - ipykernel=6.4.1=py39hecd8cb5_1
  - ipython=7.29.0=py39h01d92e1_0
prefix: /Users/userA/opt/anaconda3/envs/new_env

此 YAML 文件的主要组件是环境的名称 (name)、库的来源 (channels) 和库的列表 (dependencies)。

需要记住的事情

a. Python 是由于其简单的语法而成为数据分析的标准语言

b. Python 不需要显式编译

c. PIP 用于安装 Python 包

d. Anaconda 可同时处理 Python 包管理和环境管理

在接下来的部分中,我们将解释如何从各种来源收集数据。然后,我们将对收集到的数据进行清理和预处理,以供后续处理使用。

数据收集、数据清理和数据预处理

在本节中,我们将向您介绍数据收集过程中涉及的各种任务。我们将描述如何从多个来源收集数据,并将其转换为数据科学家可以使用的通用形式,而不论底层任务是什么。这个过程可以分解为几个部分:数据收集、数据清理和数据预处理。值得一提的是,任务特定的转换被认为是特征提取,这将在下一节中讨论。

收集数据

首先,我们将介绍用于构建初始数据集的不同数据收集方法。根据原始数据的格式,需要使用不同的技术。大多数数据集要么作为 HFML 文件在线可用,要么作为 JSON 对象存储。一些数据以 逗号分隔值CSV)格式存储,可以通过流行的数据分析和操作工具 pandas 轻松加载。因此,我们将主要专注于在本节中收集 HTML 和 JSON 数据,并将其保存为 CSV 格式。此外,我们还将介绍一些流行的数据集库。

爬取网页

作为 Web 的基本组成部分,超文本标记语言HTML)数据易于访问,包含多样化的信息。因此,通过爬取网页可以帮助您收集大量有趣的数据。在本节中,我们将使用 BeautifulSoup,这是一个基于 Python 的网络爬虫库(www.crummy.com/software/BeautifulSoup/)。作为示例,我们将演示如何爬取 Google 学术页面,并将爬取的数据保存为 CSV 文件。

在这个示例中,将使用 BeautifulSoup 的多个函数来提取作者的名字、姓氏、电子邮件、研究兴趣、引用次数、h 指数(高指数)、共同作者和论文标题。下表显示了我们希望在此示例中收集的数据:

表 2.1 – 可从 Google 学术页面收集的数据

爬取网页是一个两步过程:

  1. 利用请求库获取 HTML 数据,并存储在 response 对象中。

  2. 构建一个 BeautifulSoup 对象,用于解析 response 对象中的 HTML 标签。

这两个步骤可以用以下代码片段总结:

# url points to the target google scholar page 
response = requests.get(url) 
html_soup = BeautifulSoup(response.text, 'html.parser')

下一步是从 BeautifulSoup 对象获取感兴趣的内容。 表 2.2 总结了常见的 BeautifulSoup 函数,允许您从解析的 HTML 数据中提取感兴趣的内容。 由于我们在这个例子中的目标是将收集的数据存储为 CSV 文件,我们将简单地生成页面的逗号分隔字符串表示,并将其写入文件。 完整的实现可以在 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/google_scholar/google_scholar.py 找到。

以下表格提供了从 Google Scholar 页面处理原始数据所需的方法列表:

表 2.2 – 可能的特征提取技术

接下来,我们将学习另一种流行的原始数据格式 JSON。

收集 JSON 数据

JSON 是一种与语言无关的格式,将数据存储为键-值和/或键-数组字段。 由于大多数编程语言支持键-值数据结构(例如 Python 中的 Dictionary 或 Java 中的 HashMap),因此 JSON 被认为是可互换的(独立于程序)。 以下代码片段显示了一些示例 JSON 数据:

{
    "first_name": "Ryan",
    "last_name": "Smith",
    "phone": [{"type": "home",
               "number": "111 222-3456"}],
    "pets": ["ceasor", "rocky"],
    "job_location": null
}

查看 Awesome JSON Datasets GitHub 仓库 (github.com/jdorfman/awesome-json-datasets),其中包含一些有用的 JSON 数据源的列表。 此外,Public API’s GitHub 仓库 (github.com/public-apis/public-apis) 包含一些可以检索各种 JSON 数据的 Web 服务器端点的列表。 此外,我们提供一个脚本,从端点收集 JSON 数据,并将必要的字段存储为 CSV 文件: github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/rest/get_rest_api_data.py。 此示例使用位于 www.reddit.com/r/all.json 的 Reddit 数据集。

接下来,我们将介绍数据科学领域中流行的公共数据集。

流行的数据集存储库

除了网页和 JSON 数据之外,许多公共数据集可以用于各种目的。例如,您可以从流行的数据集中心如Kaggle (www.kaggle.com/datasets)或MIT Data Hub (datahub.csail.mit.edu/browse/public)获取数据集。这些公共数据集经常被许多研究机构和企业用于广泛的活动。从各种领域如医疗保健、政府、生物学和计算机科学的数据在研究过程中收集,并捐赠给仓库以造福更多人。就像这些组织如何管理和提供多样化的数据集一样,社区也在努力管理各种公共数据集:github.com/awesomedata/awesome-public-datasets

另一个常见的数据集来源是数据分析库,如sklearnKerasTensorFlow。每个库提供的数据集列表可以在以下链接找到:scikit-learn.org/stable/datasetskeras.io/api/datasets/www.tensorflow.org/datasets

最后,政府机构也向公众提供许多数据集。例如,您可以在由 AWS 托管的数据湖中找到与 COVID 相关的有趣且经过精心策划的数据集:dj2taa9i652rf.cloudfront.net。从这些数据集列表中,您可以轻松下载关于 Moderna 疫苗在不同州分布的 CSV 格式数据,方法是导航至cdc-moderna-vaccine-distribution页面。

现在您已经收集了初始数据集,接下来的步骤是清理它。

清理数据

数据清理是将原始数据精磨以保持条目一致的过程。常见的操作包括使用默认值填充空字段,移除非字母数字字符如?!,去除停用词,以及删除 HTML 标记如<p></p>。数据清理还侧重于保留从收集的数据中提取的相关信息。例如,用户个人资料页面可能包含广泛的信息,如传记、名字、电子邮件和隶属关系。在数据收集过程中,目标信息被按原样提取,以便保留在原始 HTML 或 JSON 标记中。换句话说,已收集的传记信息可能仍然带有换行的 HTML 标签(<br>)或加粗(<b></b>),这些对后续分析任务并不增加多少价值。在整个数据清理过程中,这些不必要的组件应该被丢弃。

在讨论单个数据清理操作之前,了解一下 DataFrames 将会很有帮助,DataFrames 是由 pandas 库提供的类似表格的数据结构(pandas.pydata.org/)。它们拥有行和列,就像 SQL 表或 Excel 表格一样。它们的一个基本功能是pandas.read_csv,它允许你将 CSV 文件加载到一个 DataFrame 中,就像下面的代码片段所演示的那样。在终端上显示内容时,tabulate库是一个不错的选择,因为 DataFrame 可以将数据结构化成表格格式。

下面的代码片段显示了如何读取 CSV 文件并使用tabulate库打印数据(在上面的例子中,tabulate将模仿 Postgres psql CLI 的格式,因为我们使用了tablefmt="psql"选项):

import pandas as pd
from tabulate import tabulate
in_file = "../csv_data/data/cdc-moderna-covid-19-vaccine-distribution-by-state.csv"
# read the CSV file and store the returned dataframe to a variable "df_vacc"
df_vacc = pd.read_csv(in_file)
print(tabulate(df_vacc.head(5), headers="keys", tablefmt="psql"))

下面的截图显示了前述代码片段中 DataFrame 在终端上使用tabulate库显示的内容(你可以通过使用df_vacc.head(5)而不使用tabulate库来查看类似的输出)。以下截图显示了每个司法管辖区疫苗剂量的分配情况:

图 2.2 – 使用 pandas 加载 CSV 文件并使用 tabulate 显示内容

图 2.2 – 使用 pandas 加载 CSV 文件并使用 tabulate 显示内容

我们将讨论的第一个数据清理操作是使用默认值填充缺失字段。

使用默认值填充空字段

我们将使用本章早些时候抓取的 Google Scholar 数据来演示如何使用默认值填充空字段。在数据检查后,您会发现一些作者没有填写他们的机构,因为这些信息没有明确指定:

图 2.3 – 机构列包含缺失值(nan)

图 2.3 – 机构列包含缺失值(nan)

每个字段的默认值根据上下文和数据类型而异。例如,将 9 到 6 作为操作时间的典型默认值,将空字符串作为缺少中间名的良好选择。短语“不适用(N/A)”经常用于明确指出字段为空。在我们的示例中,我们将填写包含na的空字段,以指示这些值在原始网页中缺失,而不是在整个收集过程中由于错误而丢失。我们将在这个例子中演示的技术涉及pandas库;DataFrame 有一个fillna方法,它填充指定值中的空值。fillna方法接受一个参数True,用于在原地更新对象而不创建副本。

下面的代码片段解释了如何使用fillna方法填充 DataFrame 中的缺失值:

df = pd.read_csv(in_file)
# Fill out the empty "affiliation" with "na"
df.affiliation.fillna(value="na", inplace=True)

在上述代码片段中,我们将 CSV 文件加载到 DataFrame 中,并将缺失的关联条目设置为na。此操作将在原地执行,而不会创建额外的副本。

在下一节中,我们将描述如何移除停用词。

移除停用词

停用词是从信息检索角度来看没有多大价值的词语。常见的英文停用词包括itsandtheforthat。例如,在 Google Scholar 数据的研究兴趣字段中,我们看到security and privacy preservation for wireless networks。像andfor这样的词在解释这段文本的含义时并不有用。因此,在自然语言处理NLP)任务中建议移除这些词。自然语言工具包NLTK)提供了一个流行的停用词移除功能,它是一套用于符号和统计 NLP 的库和程序。以下是 NLTK 库认为是停用词标记的一些单词:

['doesn', "doesn't", 'hadn', "hadn't", 'hasn', "hasn't", 'haven', "haven't", 'isn', "isn't", 'ma', …]

单词标记化是将句子分解为单词标记(单词向量)的过程。通常在停用词移除之前应用。下面的代码片段演示了如何标记化 Google Scholar 数据的research_interest字段并移除停用词:

import pandas as pd
import nltk
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize
import traceback
from nltk.corpus import stopwords
# download nltk corpuses
nltk.download('punkt')
nltk.download('stopwords')
# create a set of stop words
stop_words = set(stopwords.words('english'))
# read each line in dataframe (i.e., each line of input file)
for index, row in df.iterrows():
   curr_research_interest = str(row['research_interest'])\
       .replace("##", " ")\
       .replace("_", " ")
   # tokenize text data.
   curr_res_int_tok = tokenize(curr_research_interest)
   # remove stop words from the word tokens
   curr_filtered_research = [w for w in curr_res_int_tok\
                             if not w.lower() in stop_words]

正如您所见,我们首先使用stopwords.words('english')下载 NLTK 的停用词语料库,并移除不在语料库中的单词标记。完整版本位于github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/blob/main/Chapter_2/data_preproessing/bag_of_words_tf_idf.py

与停用词类似,非字母数字文本在信息检索角度上也没有多大价值。因此,我们将在下一节中说明如何移除它们。

移除非字母数字文本

字母数字字符是既不是英文字母字符也不是数字的字符。例如,在文本“Hi, How are you?”中,有两个非字母数字字符:,?。与停用词类似,它们可以被去除,因为它们对上下文的信息贡献不大。一旦这些字符被移除,文本将变成Hi How are you

要删除一组特定字符,我们可以使用正则表达式regex)。正则表达式是一系列字符,表示搜索模式。下面的表 2.3显示了一些重要的正则表达式搜索模式,并解释了每个模式的含义:

表 2.3 – 关键正则表达式搜索模式

您可以在docs.python.org/3/library/re.html找到其他有用的模式。

Python 提供了一个内置的regex库,支持查找和删除匹配给定正则表达式的一组文本。以下代码片段展示了如何删除非字母数字字符。\W模式匹配任何非单词字符。模式后面的+表示我们想保留前面的元素一次或多次。将它们组合在一起,我们将在以下代码片段中找到一个或多个字母数字字符:

def clean_text(in_str):
   clean_txt = re.sub(r'\W+', ' ', in_str)
   return clean_txt
# remove non alpha-numeric characters for feature "text"
text = clean_text(text)

作为最后的数据清理操作,我们将介绍如何高效地删除换行符。

移除换行符

最后,收集的文本数据可能包含不必要的换行符。在许多情况下,即使是结尾的换行符也可以毫无损害地删除,而不管接下来的任务是什么。可以使用 Python 的内置replace功能将这些字符轻松地替换为空字符串。

以下代码片段展示了如何在文本中删除换行符:

# replace the new line in the given text with empty string. 
text = input_text.replace("\n", "")

在上述代码片段中,"abc\n"将会变成"abc"

清理后的数据通常会进一步处理,以更好地表示底层数据。这个过程称为数据预处理。我们将在下一节深入研究这个过程。

数据预处理

数据预处理的目标是将清洗后的数据转换为适合各种数据分析任务的通用形式。数据清理和数据预处理之间没有明确的区别。因此,像替换一组文本或填充缺失值这样的任务既可以归类为数据清理,也可以归类为数据预处理。在本节中,我们将重点介绍前一节未涵盖的技术:标准化、将文本转换为小写、将文本转换为词袋模型,并对单词应用词干提取。

完整的示例实现可以在github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_2/data_preproessing找到。

标准化

有时候,字段的值可能以不同的方式表示,尽管它们表示相同的含义。在谷歌学术数据的情况下,研究兴趣的条目可能用不同的词汇,尽管它们指的是类似的领域。例如,数据科学,ML 和人工智能AI)在更大的上下文中指的是 AI 的相同领域。在数据预处理阶段,我们通常通过将 ML 和数据科学转换为 AI 来标准化它们,这样可以更好地表示底层信息。这有助于数据科学算法利用特征来完成目标任务。

如在示例存储库中的normalize.py脚本中所示,可以通过保持一个映射预期值到标准化值的字典来轻松实现前述案例的标准化。在以下代码片段中,artificial_intelligence将是research_interestsdata_sciencemachine_learning特征的标准化值:

# dictionary mapping the values are commonly used for normalization
dict_norm = {"data_science": "artificial_intelligence",
    "machine_learning": "artificial_intelligence"}
# normalize.py
if curr in dict_norm:
   return dict_norm[curr]
else:
   return curr

字段的数值也需要标准化。对于数值,标准化将是将每个值重新缩放到特定范围内的过程。在以下示例中,我们将每个州的每周疫苗分配的平均计数缩放到 0 和 1 之间。首先,我们计算每个州的平均计数。然后,通过将平均计数除以最大平均计数来计算标准化的平均计数:

# Step 1: calculate state-wise mean number for weekly corora vaccine distribution
df = df_in.groupby("jurisdiction")["_1st_dose_allocations"]\
   .mean().to_frame("mean_vaccine_count").reset_index()
# Step 2: calculate normalized mean vaccine count
df["norm_vaccine_count"] = df["mean_vaccine_count"] / df["mean_vaccine_count"].max()

标准化的结果可以在以下屏幕截图中看到。此屏幕截图的表格包含两列 - 标准化之前和之后的平均疫苗计数:

图 2.4 - 各州标准化平均疫苗分配量

图 2.4 - 各州标准化平均疫苗分配量

接下来介绍的下一个数据预处理是文本数据的大小写转换。

大小写转换

在许多情况下,文本数据会被转换为小写或大写作为标准化的一种方式。这在涉及比较的任务时带来了一定的一致性。在停用词移除示例中,curr_res_int_tok变量中的单词标记将在 NLTK 库的标准英文停用词中搜索。为了成功比较,大小写应保持一致。在以下代码片段中,在停用词搜索之前,标记会转换为小写:

# word tokenize
curr_resh_int_tok = word_tokenize(curr_research_interest)
# remove stop words from the word tokens
curr_filtered_research = [w for w in curr_res_int_tok\
                         if not w.lower() in stop_words]

另一个示例可以在get_rest_api_data.py中找到,我们从 Reddit 收集和处理数据。在从脚本中提取的以下代码片段中,每个文本字段在收集时都转换为小写:

def convert_lowercase(in_str):
   return str(in_str).lower()
# convert string to lowercase
text = convert_lowercase(text)

接下来,您将了解如何通过词干化来改善数据质量。

词干化

词干化是将一个词转换为其根词的过程。词干化的好处在于,如果它们的基本含义相同,可以保持单词的一致性。例如,“信息”,“通知”和“通知”有相同的词根 - “通知”。以下示例展示了如何利用 NLTK 库进行词干化。NLTK 库提供了基于*波特词干算法(Porter, Martin F. “An algorithm for suffix stripping.” Program (1980))*的词干化功能:

from nltk.stem import PorterStemmer
# porter stemmer for stemming word tokens
ps = PorterStemmer()
word = "information"
stemmed_word = ps.stem(word) // "inform"

在上述代码片段中,我们从nltk.stem库实例化了PosterStemmer,并将文本传递给stem函数。

需要记住的事项

a. 数据以不同格式出现,如 JSON,CSV,HTML 和 XML。每种类型的数据都有许多数据收集工具可用。

b. 数据清洗是将原始数据整理成一致性条目的过程。常见操作包括用默认值填充空特征、删除非字母数字字符、删除停用词和不必要的标记。

c. 数据预处理的目标是应用通用数据增强技术,将清洗后的数据转换为适用于任何数据分析任务的形式。

d. 数据清洗和数据预处理的领域有重叠,这意味着某些操作可用于两个过程之一。

到目前为止,我们已讨论了数据准备的通用过程。接下来,我们将讨论最后一个过程:特征提取。与我们讨论过的其他过程不同,特征提取涉及特定于任务的操作。让我们更详细地看一下。

从数据中提取特征

特征提取特征工程)是将数据转换为表达特定任务底层信息的特征的过程。数据预处理应用通用技术,通常对大多数数据分析任务都是必需的。但是,特征提取要求利用领域知识,因为它是特定于任务的。在本节中,我们将介绍流行的特征提取技术,包括文本数据的词袋模型、词频-逆文档频率、将彩色图像转换为灰度图像、序数编码、独热编码、降维和用于比较两个字符串的模糊匹配。

这些示例的完整实现可以在github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_2/data_preproessing在线找到。

首先,我们将从词袋模型技术开始。

使用词袋模型转换文本

Sklearn 的 CountVectorizer 类可帮助从文本创建词袋模型。以下代码演示了如何使用 Sklearn 的功能进行词袋模型:

import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
document_1 = "This is a great place to do holiday shopping"
document_2 = "This is a good place to eat food"
document_3 = "One of the best place to relax is home"
# 1-gram (i.e., single word token used for BoW creation)
count_vector = CountVectorizer(ngram_range=(1, 1), stop_words='english')
# transform the sentences
count_fit = count_vector.fit_transform([document_1, document_2, document_3])
# create dataframe
df = pd.DataFrame(count_fit.toarray(), columns=count_vector.get_feature_names_out())
print(tabulate(df, headers="keys", tablefmt="psql"))

下面的屏幕截图总结了词袋模型的输出,采用表格格式:

图 2.5 – 词袋模型在三个样本文档上的输出

图 2.5 – 词袋模型在三个样本文档上的输出

接下来,我们将介绍词频-逆文档频率TF-IDF)用于文本数据。

应用词频-逆文档频率(TF-IDF)转换

使用词频的问题在于具有更高频率的文档将主导模型或分析。因此,更好的方法是根据单词在所有文档中出现的频率重新调整频率。这种缩放有助于惩罚那些高频单词(如thehave),使文本的数值表示更好地表达上下文。

在介绍 TF-IDF 的公式之前,我们必须定义一些符号。让n表示文档的总数,t表示一个单词(术语)。df(t)指的是单词t的文档频率(包含单词t的文档数),而tf(t, d)指的是单词t在文档d中的频率(单词t在文档d中出现的次数)。有了这些定义,我们可以定义idf(t),即逆文档频率,为log [ n / df(t) ] + 1

总体而言,tf-idf(t, d),即单词t在文档d中的 tf-idf 值,可以表示为tf(t, d) * idf(t)

在样本代码脚本bag_of_words_tf_idf.py中,我们使用 Google Scholar 数据的研究兴趣领域来计算 TF-IDF。在这里,我们利用 Sklearn 的TfidfVectorizer函数。fit_transform函数接受一组文档并生成 TF-IDF 加权的文档-术语矩阵。从这个矩阵中,我们可以打印出前N个研究兴趣:

tfidf_vectorizer = TfidfVectorizer(use_idf=True)
# use the tf-idf instance to fit list of research_interest 
tfidf = tfidf_vectorizer.fit_transform(research_interest_list)
# tfidf[0].T.todense() provides the tf-idf dense vector 
# calculated for the research_interest
df = pd.DataFrame(tfidf[0].T.todense(), index=tfidf_vectorizer.get_feature_names_out(), columns=["tf-idf"])
# sort the tf-idf calculated using 'sort_values' of dataframe.
df = df.sort_values('tf-idf', ascending=False)
# top 3 words with highest tf-idf
print(df.head(3))

在前面的示例中,我们创建了一个TfidfVectorizer实例,并使用研究兴趣文本列表(research_interest_list)触发了fit_transform函数。然后,我们在输出上调用todense方法,以获取结果矩阵的稠密表示。该矩阵被转换为 DataFrame 并排序以显示前几条条目。下面的屏幕截图显示了df.head(3)的输出 – 研究兴趣领域中具有最高 TF-IDF 值的三个单词:

图 2.6 – 研究兴趣领域中具有最高 TF-IDF 值的三个单词

图 2.6 – 研究兴趣领域中具有最高 TF-IDF 值的三个单词

接下来,您将学习如何使用独热编码处理分类数据。

创建独热编码(one-of-k)

独热编码(one-hot 编码)是将离散值转换为一系列二进制值的过程。让我们从一个简单的例子开始,其中一个字段可以具有"猫"或"狗"的分类值。独热编码将被表示为两位,其中一位指代"猫",另一位指代"狗"。编码中数值为 1 的位表示该字段具有相应的值。因此,1 0 表示猫,而 0 1 表示狗:

品种宠物类型
猎犬10
缅因库恩01
德国牧羊犬10

表 2.4 – 将宠物类型(pet_type)中的分类值转换为独热编码(狗和猫)

one_hot_encoding.py中展示了独热编码的演示。在以下代码片段中,我们专注于核心操作,涉及 Sklearn 中的OneHotEncoder

from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
encoded_data = labelencoder.fit_transform(df_research ['is_artifical_intelligent'])

在前述代码片段中使用的is_artificial_intelligence列包含两个不同的值:"yes"和"no"。以下屏幕截图总结了独热编码的结果:

图 2.7 – is_artificial_intelligence字段的独热编码

图 2.7 – is_artificial_intelligence字段的独热编码

在接下来的部分中,我们将介绍另一种称为序数编码的编码类型。

创建序数编码

序数编码是将分类值转换为数值的过程。在表 2.5中,有两种宠物类型,狗和猫。狗分配值为 1,猫分配值为 2:

品种宠物类型序数编码
猎犬1
缅因库恩猫2
德国牧羊犬1

表 2.5 – pet_type 字段中的分类值在 ordinal_encoding 中被编码为序数

在下面的代码片段中,我们使用 Sklearn 中的LabelEncoder类将研究兴趣领域转换为数值。有关序数编码的完整示例可以在ordinal_encoding.py中找到:

from sklearn.preprocessing import LabelEncoder
labelencoder = LabelEncoder()
encoded_data = labelencoder.fit_transform(df_research ['research_interest'])

前面的代码片段几乎是自解释的 - 我们只需构造一个LabelEncoder实例,并将目标列传递给fit_transform方法。下图显示了生成的 DataFrame 的前三行:

图 2.8 – 研究兴趣的序数编码结果

图 2.8 – 研究兴趣的序数编码结果

接下来,我们将解释一种图像技术:将彩色图像转换为灰度图像。

将彩色图像转换为灰度图像

在计算机视觉任务中,最常见的技术之一是将彩色图像转换为灰度图像。OpenCV是图像处理的标准库(opencv.org/)。在以下示例中,我们只是导入 OpenCV 库(import cv2)并使用cvtColor函数将加载的图像转换为灰度图:

image = cv2.imread('./images/tiger.jpg')
# filter to convert color tiger image to gray one
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# write the gray image to a file
cv2.imwrite('./images/tiger_gray.jpg', gray_image)

在处理包含多个字段的大量数据时,通常需要减少维度。在下一节中,我们将探讨此过程的可用选项。

执行降维

在许多情况下,任务需要的特征多于实际需要的特征;并非所有特征都包含有用信息。在这种情况下,可以使用降维技术,如主成分分析PCA)、奇异值分解SVD)、线性判别分析LDA)、t-SNEUMAPISOMAP等。另一种选择是使用 DL。您可以构建用于降维的自定义模型,或者使用预定义的网络结构,如AutoEncoder。在本节中,我们将详细描述 PCA,因为它是我们提到的技术中最流行的一种。

给定一组特征,PCA 识别特征之间的关系,并生成一组新的变量,以最有效的方式捕捉样本之间的差异。这些新变量称为主成分,并按重要性排名;在构建第一个主成分时,它压缩了不重要的变量,并将它们留给第二个主成分。因此,第一个主成分与其余变量不相关。这个过程重复进行,以构建后续顺序的主成分。

如果我们更正式地描述 PCA 过程,我们可以说该过程有两个步骤:

  1. 构建代表每对特征的相关性的协方差矩阵。

  2. 通过计算协方差矩阵的特征值,生成能够捕获不同信息量的新功能集。

新功能集是主要组件。通过按从最高到最低排序相应的特征值,您可以在顶部获得最有用的新功能。

为了理解细节,我们将查看 Iris 数据集(archive.ics.uci.edu/ml/datasets/iris)。该数据集包括三类鸢尾植物(山鸢尾、变色鸢尾和维吉尼亚鸢尾),以及四个特征(花萼宽度、花萼长度、花瓣宽度和花瓣长度)。在下图中,我们使用从 PCA 构建的两个新特征绘制每个条目。根据这个图表,我们可以轻松地得出结论,我们只需要前两个主成分来区分这三个类别:

图 2.9 - Iris 数据集上 PCA 的结果

图 2.9 - Iris 数据集上 PCA 的结果

在下面的示例中,我们将使用来自 Kaggle 的人力资源数据来演示 PCA。初始数据集包括多个字段,如工资、过去五年内是否晋升以及员工是否离开公司。一旦构建了主成分,就可以使用 matplotlib 绘制它们:

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler, 
# read the HR data in csv format
df_features = pd.read_csv("./HR.csv")
# Step 1: Standardize features by removing the mean and scaling to unit variance
scaler = StandardScaler()
# train = scaler.fit(X)
X_std = scaler.fit_transform(X)
# Step 2: Instantiate PCA & choose minimum number of 
# components such that it covers 95% variance
pca = PCA(0.95).fit(X_std)

在前面的代码片段中,首先使用 pandas 库的read_csv函数加载数据,使用 Sklearn 的StandardScaler对条目进行标准化,并使用 Sklearn 应用 PCA。完整示例可以在pca.py中找到。

作为特征提取的最后一种技术,我们将解释如何有效地计算两个序列之间的距离度量。

应用模糊匹配来处理字符串之间的相似性

模糊匹配 (pypi.org/project/fuzzywuzzy/) 使用距离度量来衡量两个序列的差异,并且如果它们被认为是相似的,则对待它们相同。在这一部分中,我们将演示如何使用莱文斯坦距离(Levenshtein, Vladimir I. (February 1966). "Binary codes capable of correcting deletions, insertions, and reversals". Soviet Physics Doklady. 10 (8): 707–710. Bibcode: 1966SPhD...10..707L)来应用模糊匹配。

模糊字符串匹配最流行的库是 fuzzywuzzyratio 函数将提供莱文斯坦距离分数,我们可以用它来决定是否要在接下来的过程中将两个字符串视为相同。下面的代码片段描述了 ratio 函数的使用方式:

from fuzzywuzzy import fuzz
# compare strings using ratio method
fuzz.ratio("this is a test", "this is a test!") // 91
fuzz.ratio("this is a test!", "this is a test!") // 100

如前例所示,ratio 函数将在两个文本更相似时输出较高的分数。

记住的事情

a. 特征提取 是将数据转换为能更好地表达目标任务下潜在信息的特征的过程。

b. BoW 是基于单词出现情况的文档表示。TF-IDF 可以通过惩罚高频词语更好地表达文档的上下文。

c. 使用 OpenCV 库,可以将彩色图像更新为灰度图像。

d. 分类特征可以使用序数编码或独热编码进行数值表示。

e. 当数据集具有过多特征时,降维可以减少具有最多信息的特征数量。PCA 构建新特征同时保留大部分信息。

f. 在评估两个文本之间的相似性时,可以应用模糊匹配方法。

一旦数据被转换为合理的格式,通常需要可视化数据以理解其特性。在下一节中,我们将介绍用于数据可视化的流行库。

执行数据可视化

当应用机器学习技术分析数据集时,第一步必须是理解可用数据,因为每个算法的优势与底层数据密切相关。数据科学家需要理解的数据关键方面包括数据格式、分布和特征之间的关系。当数据量较小时,可以通过手动分析每个条目来收集必要信息。然而,随着数据量的增长,可视化在理解数据中扮演关键角色。

Python 中有许多用于数据可视化的工具。MatplotlibSeaborn 是最流行的用于统计数据可视化的库。我们将在本节逐一介绍这两个库。

使用 Matplotlib 执行基本可视化

在以下示例中,我们将演示如何使用 Matplotlib 生成条形图和饼图。我们使用的数据代表 COVID 疫苗的周分布。要使用 matplotlib 功能,必须首先导入该包 (import matplotlib.pyplot as plt)。plt.bar 函数接受前 10 个州名称的列表和其平均分布列表,以生成条形图。类似地,plt.pie 函数用于从字典生成饼图。plt.figure 函数调整绘图大小,并允许用户在同一画布上绘制多个图表。完整的实现可以在 visualize_matplotlib.py 中找到:

# PIE CHART PLOTTING
# colors for pie chart
colors = ['orange', 'green', 'cyan', 'skyblue', 'yellow', 'red', 'blue', 'white', 'black', 'pink']
# pie chart plot
plt.pie(list(dict_top10.values()), labels=dict_top10.keys(), colors=colors, autopct='%2.1f%%', shadow=True, startangle=90)
# show the actual plot
plt.show()
# BAR CHART PLOTTING
x_states = dict_top10.keys()
y_vaccine_dist_1 = dict_top10.values()
fig = plt.figure(figsize=(12, 6))  # figure chart with size
ax = fig.add_subplot(111)
# bar values filling with x-axis/y-axis values
ax.bar(np.arange(len(x_states)), y_vaccine_dist_1, log=1)
plt.show()

前述代码的结果如下:

图 2.10 – 使用 Matplotlib 生成的条形图和饼图

图 2.10 – 使用 Matplotlib 生成的条形图和饼图

在接下来的部分中,我们将介绍 Seaborn,另一个流行的数据可视化库。

使用 Seaborn 绘制统计图

Seaborn 是建立在 Matplotlib 之上的库,提供了绘制统计图形的高级接口,这是 Matplotlib 不支持的。在本节中,我们将学习如何使用 Seaborn 为相同的数据集生成折线图和直方图。首先,我们需要导入 Seaborn 库以及 Matplotlib (import seaborn as sns)。sns.line_plot 函数设计用于接受 DataFrame 和列名。因此,我们必须提供包含分布最高的前 10 个州的平均值的 df_mean_sorted_top10,以及两个列名 state_namescount_vaccine 作为 XY 轴。要绘制直方图,可以使用 sns.dist_plot 函数,该函数接受具有 Y 轴列值的 DataFrame。如果我们要使用相同的平均值,可以这样做 sns.displot(df_mean_sorted_top10['count_vaccine'], kde=False)

import seaborn as sns 
# top 10 stats by largest mean
df_mean_sorted_top10 = ... # top 10 stats by largest mean
# LINE CHART PLOT
sns.lineplot(data=df_mean_sorted_top10, x="state_names", y="count_vaccine")
# show the actual plot
plt.show()
# HISTOGRAM CHART PLOTTING
# plot histogram bars with top 10 states mean distribution count of vaccine
sns.displot(df_mean_sorted_top10['count_vaccine'], kde=False)
plt.show()

下图显示了生成的图形:

图 2.11 – 使用 Seaborn 生成的折线图和直方图

图 2.11 – 使用 Seaborn 生成的折线图和直方图

完整的实现可以在本书的 GitHub 仓库中找到 (visualize_seaborn.py)。

许多库可用于增强可视化:pyROOT,这是来自 CERN 的数据分析框架,通常用于研究项目 (root.cern/manual/python),Streamlit,用于轻松创建 Web 应用 (streamlit.io),Plotly,一个免费开源的绘图库 (plotly.com),和Bokeh,用于交互式 Web 可视化 (docs.bokeh.org/en/latest)。

记住的事情

a. 可视化数据有助于分析和理解对选择正确的机器学习算法至关重要的数据。

b. Matplotlib 和 Seaborn 是最流行的数据可视化工具。其他工具包括 pyRoot、Streamlit、Plotly 和 Bokeh。

本章的最后一节将描述 Docker,它允许你为项目实现操作系统OS)级别的虚拟化。

Docker 简介

在上一节 设置笔记本环境 中,你学习了如何使用condapip命令设置带有各种包的虚拟环境用于深度学习。此外,你还知道如何将环境保存到 YAML 文件并重新创建相同的环境。然而,基于虚拟环境的项目在需要在多台机器上复制环境时可能不够,因为可能会出现来自非明显的操作系统级别依赖项的问题。在这种情况下,Docker 将是一个很好的解决方案。使用 Docker,你可以创建一个包含操作系统版本的工作环境快照。总之,Docker 允许你将应用程序与基础设施分开,以便快速交付软件。安装 Docker 可以按照 www.docker.com/get-started 上的说明进行。在本书中,我们将使用版本 3.5.2。

在本节中,我们将介绍 Docker 镜像,即 Docker 上下文中虚拟环境的表示,并解释如何为目标 Docker 镜像创建 Dockerfile。

Dockerfile 简介

Docker 镜像由所谓的 Dockerfile 创建。每个 Docker 镜像都有一个基础(或父)镜像。对于深度学习环境,作为基础镜像的一个很好选择是为 Linux Ubuntu 操作系统开发的镜像之一:ubuntu:18.04releases.ubuntu.com/18.04)或 ubuntu:20.04releases.ubuntu.com/20.04)。除了基础操作系统的镜像,还有安装了特定包的镜像。例如,建立基于 TensorFlow 的环境的最简单方法是下载已安装 TensorFlow 的镜像。TensorFlow 开发人员已经创建了一个基础镜像,并可以通过使用 docker pull tensorflow/serving 命令轻松下载(hub.docker.com/r/tensorflow/serving)。还提供了一个带有 PyTorch 的环境:github.com/pytorch/serve/blob/master/docker/README.md

接下来,你将学习如何使用自定义 Docker 镜像进行构建。

构建自定义 Docker 镜像

创建自定义镜像也很简单。但是,它涉及许多命令,我们将详细信息委托给 github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_2/dockerfiles。一旦构建了 Docker 镜像,您可以使用称为 Docker 容器的东西来实例化它。Docker 容器是一个独立的可执行软件包,包括运行目标应用程序所需的所有内容。通过按照 README.md 文件中的说明操作,您可以创建一个 Docker 镜像,该镜像将在本章中描述的标准库中运行一个容器化的 Jupyter 笔记本服务。

要记住的事情

a. Docker 创建了您工作环境的快照,包括底层操作系统。创建的镜像可用于在不同的机器上重新创建相同的环境。

b. Docker 帮助您将环境与基础设施分离。这使得您可以将应用程序轻松移动到不同的云服务提供商(如 AWS 或 Google Cloud)。

此时,您应该能够为您的深度学习项目创建一个 Docker 镜像。通过实例化 Docker 镜像,您应该能够在本地机器或各种云服务提供商上收集所需的数据并进行处理。

总结

在本章中,我们描述了如何为数据分析任务准备数据集。第一个关键点是如何使用 Anaconda 和 Docker 实现环境虚拟化,并使用 pip 进行 Python 包管理。

数据准备过程可以分解为四个步骤:数据收集、数据清理、数据预处理和特征提取。首先,我们介绍了支持不同数据类型的数据收集工具。一旦数据被收集,它就会被清理和预处理,以便能够转换为通用形式。根据目标任务,我们经常应用各种特定于任务的特征提取技术。此外,我们介绍了许多数据可视化工具,可以帮助您了解准备好的数据的特性。

现在我们已经学习了如何为分析任务准备数据,下一章中,我们将解释深度学习模型开发。我们将介绍基本概念以及如何使用两个最流行的深度学习框架:TensorFlow 和 PyTorch。

第三章:开发一个强大的深度学习模型

在本章中,我们将描述如何设计和训练深度学习(DL)模型。在上一章节中描述的笔记本上下文中,数据科学家们调查各种网络设计和模型训练设置,以生成适合给定任务的工作模型。本章的主要内容包括 DL 的理论以及如何使用最流行的 DL 框架PyTorchTensorFlowTF)训练模型。在本章末尾,我们将解构StyleGAN的实现,这是一个用于图像生成的流行 DL 模型,以解释如何使用我们在本章介绍的组件构建复杂模型。

在本章中,我们将涵盖以下主要内容:

  • 学习深度学习基础理论

  • 理解 DL 框架的组成部分

  • 在 PyTorch 中实现和训练模型

  • 在 TF 中实现和训练模型

  • 解构复杂的最新模型实现

技术要求

您可以从以下 GitHub 链接下载本章的补充材料:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_3

本章中的示例可以在安装了必要包的任何 Python 环境中执行。您可以使用上一章介绍的示例环境:github.com/PacktPublishing/Production-Ready-Applied-Deep-Learning/tree/main/Chapter_2/dockerfiles

学习深度学习基础理论

第一章中简要描述的,深度学习驱动项目的有效规划,DL 是基于人工神经网络(ANNs)的机器学习(ML)技术。本节的目标是在不深入数学的情况下解释 ANNs 的工作原理。

DL 是如何工作的?

人工神经网络(ANN)基本上是一组相连的神经元。如图 3.1所示,ANN 中的神经元和我们大脑中的神经元表现出类似的行为。在 ANN 中,每个连接都有一个可调参数称为权重。当神经元 A 到神经元 B 有连接时,神经元 A 的输出乘以连接的权重,得到的加权值成为神经元 B 的输入。偏置是神经元内的另一个可调参数;神经元将所有输入求和并加上偏置。最后的操作是激活函数,将计算得到的值映射到不同的范围。新范围内的值是神经元的输出,根据连接传递给其他神经元。

在研究中发现,神经元组根据其组织方式捕捉不同的模式。一些强大的组织形式被标准化为,已成为 ANN 的主要构建模块,为复杂的神经元间互动提供了一层抽象。

图 3.1 – 生物神经元与 ANN 神经元数学模型的比较

图 3.1 – 生物神经元与 ANN 神经元数学模型的比较

正如前面的图表所述,DL 中的操作基于数值。因此,网络的输入数据必须转换为数值。例如,红、绿、蓝RGB)颜色代码是用数值表示图像的标准方式。对于文本数据,通常使用词嵌入。类似地,网络的输出将是一组数值。这些值的解释可以根据任务和定义而异。

DL 模型训练

总体而言,训练 ANN 是一个寻找一组权重、偏差和激活函数的过程,使得网络能够从数据中提取有意义的模式。接下来的问题是:我们如何找到合适的参数集? 许多研究人员尝试使用各种技术解决这个问题。在所有尝试中,发现的最有效算法是一种称为梯度下降的优化算法,这是一个迭代过程,用于找到局部或全局最小值。

在训练 DL 模型时,我们需要定义一个函数,将预测值与地面实况标签之间的差异量化为一个称为损失的数值。一旦损失函数被明确定义,我们就会迭代生成中间预测,计算损失值,并朝向最小损失方向更新模型参数。

鉴于优化的目标是找到最小损失,模型参数需要根据训练集样本在梯度的反方向上进行更新(见图 3.2)。为了计算梯度,网络在预测过程中跟踪计算中间值(前向传播)。然后,从最后一层开始,利用链式法则计算每个参数的梯度(反向传播)。有趣的是,模型的性能和训练时间可能会根据每次迭代中参数更新的方式有很大的差异。不同的参数更新规则包含在优化器的概念中。DL 的一个主要任务之一是选择能够产生最佳性能模型的优化器类型。

图 3.2 – 利用梯度下降,模型参数将在每次迭代中朝着梯度的相反方向更新

图 3.2 – 使用梯度下降,模型参数将在每次迭代中朝着梯度的反方向更新

然而,这个过程有一个需要注意的地方。如果模型被训练来在训练集上获得最佳性能,那么在未见数据上的性能可能会下降。这被称为过拟合;模型专门针对它以前见过的数据进行训练,无法正确预测新数据。另一方面,训练不足会导致欠拟合,即模型未能捕获训练集的潜在模式。为了避免这些问题,训练集的一部分被保留用于在训练过程中评估训练好的模型:验证集。总体而言,DL 的训练涉及根据训练集更新模型参数的过程,但选择在验证集上表现最佳的模型。最后一种数据集,测试集,代表了模型部署后可能与之交互的数据。测试集在模型训练时可能可用,也可能不可用。测试集的目的是了解训练好的模型在生产环境中的表现。为了进一步理解整体的训练逻辑,我们可以看一下图 3.3

Figure 3.3 – 训练 DL 模型的步骤

图 3.3 – 训练 DL 模型的步骤

这张图清晰地描述了迭代过程中的步骤,以及每种类型数据集在场景中的角色。

需要记住的事情

a. 训练人工神经网络(ANN)是一个寻找一组权重、偏置和激活函数的过程,使网络能够从数据中提取有意义的模式。

b. 在训练流程中有三种类型的数据集。使用训练集更新模型参数,并选择在验证集上表现最佳的模型。测试集反映了训练好的模型在部署时将与之交互的数据分布。

接下来,我们将看看旨在帮助我们进行模型训练的 DL 框架。

DL 框架的组成部分

由于模型训练的配置遵循相同的流程,无论底层任务如何,许多工程师和研究人员已经将常见的构建模块整合到框架中。大多数框架通过将数据加载逻辑和模型定义与训练逻辑分离,简化了 DL 模型的开发。

数据加载逻辑

数据加载逻辑包括从内存加载原始数据到为每个样本准备训练和评估所需的所有步骤。在许多情况下,训练集、验证集和测试集的数据存储在不同的位置,因此每个集合都需要不同的加载和准备逻辑。标准框架将这些逻辑与其他构建模块分开,以便可以以动态方式使用不同的数据集进行模型训练,并在模型方面进行最小更改。此外,这些框架已经标准化了这些逻辑的定义方式,以提高可重用性和可读性。

模型定义

另一个基础模块,模型定义,指的是人工神经网络的架构本身及其对应的前向和反向传播逻辑。尽管使用算术运算建立模型是一种选择,但标准框架提供了常见的层定义,用户可以组合这些定义以构建复杂的模型。因此,用户需负责实例化必要的网络组件,连接这些组件,并定义模型在训练和推断时的行为方式。

在接下来的两个部分中,在 PyTorch 中实现和训练模型在 TF 中实现和训练模型,我们将介绍如何在 PyTorch 和 TF 中实例化流行的层:稠密(线性)、池化、归一化、dropout、卷积和循环层。

模型训练逻辑

最后,我们需要结合这两个组件并定义训练逻辑的详细信息。这个包装组件必须清晰描述模型训练的关键部分,例如损失函数、学习率、优化器、epochs、迭代次数和批处理大小。

损失函数可以根据学习任务的类型分为两大类:分类损失回归损失。这两类之间的主要区别来自输出格式;分类任务的输出是分类的,而回归任务的输出是连续值。在不同的损失中,我们将主要讨论用于回归损失的均方误差MSE)和平均绝对误差MAE)损失,以及用于分类损失的交叉熵CE)和二元交叉熵BCE)损失。

学习率LR)定义了梯度下降在局部最小值方向上迈出的步伐大小。选择适当的学习率可以帮助过程更快地收敛,但如果学习率过高或过低,收敛性将无法保证(见图 3.4):

图 3.4 – 学习率在梯度下降中的影响

图 3.4 – 学习率在梯度下降中的影响

谈到优化器,我们关注两个主要的优化器:随机梯度下降SGD),一个具有固定学习率的基本优化器,以及自适应矩估计Adam),一种基于自适应学习率的优化器,在大多数情况下表现最佳。如果你有兴趣了解不同优化器及其背后的数学原理,我们推荐阅读 Choi 等人的调查论文(arxiv.org/pdf/1910.05446.pdf)。

一个epoch表示训练集中的每个样本都已经通过网络进行了前向和反向传播,并且网络参数已经更新。在许多情况下,训练集中的样本数量太大,无法一次通过,因此会将其分成mini-batchesbatch size指的是单个 mini-batch 中的样本数量。给定一组 mini-batches 组成整个数据集,迭代次数指的是模型需要与每个样本进行梯度更新事件(更确切地说是 mini-batch 的数量)。例如,如果一个 mini-batch 有 100 个样本,总共有 1,000 个样本,那么完成一个 epoch 需要 10 次迭代。选择合适的 epoch 数量并不容易。这取决于其他训练参数,如学习率和 batch size。因此,通常需要通过试验和错误的过程来确定,同时注意欠拟合和过拟合的问题。

需要记住的事情

a. 模型训练的组成部分可以分为数据加载逻辑、模型定义和模型训练逻辑。

b. 数据加载逻辑包括从内存加载原始数据到为训练和评估准备每个样本的所有步骤。

c. 模型定义指的是网络架构及其正向和反向传播逻辑的定义。

d. 模型训练逻辑通过将数据加载逻辑和模型定义结合起来处理实际的训练过程。

在众多可用的框架中,我们将在本书中讨论两个最流行的框架:TFPyTorch。在今天,运行在 TF 上的 Keras 已经变得非常流行,而 PyTorch 以其出色的灵活性和简易性在研究中被广泛使用。

在 PyTorch 中实现和训练模型

PyTorch 是一个用于 Lua 的 ML 包的 Python 库。 PyTorch 的主要特性包括图形处理单元GPU)加速的矩阵计算和用于构建和训练神经网络的自动微分。在代码执行过程中动态创建计算图,PyTorch 因其灵活性、易用性以及在模型训练中的高效性而受到青睐。

基于 PyTorch,PyTorch LightningPL)提供了另一层抽象,隐藏了许多样板代码。新框架更多关注研究人员,通过将 PyTorch 的研究相关组件与工程相关组件解耦。PL 的代码通常比 PyTorch 代码更可扩展和易读。尽管本书中的代码片段更加强调 PL,但 PyTorch 和 PL 共享许多功能,因此大多数组件是可互换的。如果您愿意深入了解细节,我们推荐访问官方网站,pytorch.org

市场上还有其他扩展 PyTorch 的选择:

我们不会在本书中涵盖这些库,但如果您对这个领域是新手,可能会发现它们很有帮助。

现在,让我们深入了解 PyTorch 和 PL。

PyTorch 数据加载逻辑

为了可读性和模块化,PyTorch 和 PL 利用名为Dataset的类进行数据管理,并利用另一个名为DataLoader的类来迭代访问样本。

虽然Dataset类处理获取单个样本,但模型训练会批量输入数据,并需要重排以减少模型过拟合。DataLoader通过提供简单的 API 来为用户抽象这种复杂性。此外,它在后台利用 Python 的多进程功能加速数据检索。

Dataset子类必须实现的两个核心函数是__len____getitem__。如下课程大纲所述,__len__应返回总样本数,__getitem__应返回给定索引的样本:

from torch.utils.data import Dataset
class SampleDataset(Dataset):
   def __len__(self):
      """return number of samples"""
   def __getitem__(self, index):
      """loads and returns a sample from the dataset at the given index"""

PL 的LightningDataModule封装了处理数据所需的所有步骤。关键组件包括下载和清理数据,预处理每个样本,并将每种数据集包装在DataLoader中。以下代码片段描述了如何创建LightningDataModule类。该类具有prepare_data函数用于下载和预处理数据,以及三个函数用于实例化每种数据集的DataLoader,分别是train_dataloaderval_dataloadertest_dataloader

from torch.utils.data import DataLoader
from pytorch_lightning.core.lightning import LightningDataModule
class SampleDataModule(LightningDataModule):
   def prepare_data(self):
       """download and preprocess the data; triggered only on single GPU"""
       ...
   def setup(self):
       """define necessary components for data loading on each GPU"""
       ...
   def train_dataloader(self):
       """define train data loader"""
       return data.DataLoader(
         self.train_dataset, 
           batch_size=self.batch_size, 
           shuffle=True)
   def val_dataloader(self):
       """define validation data loader"""
       return data.DataLoader(
          self.validation_dataset, 
          batch_size=self.batch_size, 
          shuffle=False)             
   def test_dataloader(self):
       """define test data loader"""
       return data.DataLoader(
          self.test_dataset, 
          batch_size=self.batch_size, 
          shuffle=False)

LightningDataModule 的官方文档可以在 pytorch-lightning.readthedocs.io/en/stable/extensions/datamodules.html 找到。

PyTorch 模型定义

PL 的关键优势来自 LightningModule,它将复杂的 PyTorch 代码简化为六个部分:

  • 计算(__init__

  • 训练循环(training_step

  • 验证循环(validation_step

  • 测试循环(test_step

  • 预测循环(predict_step

  • 优化器和学习率调度器(configure_optimizers

模型架构是计算部分的一部分。必要的层在 __init__ 方法中实例化,计算逻辑在 forward 方法中定义。在以下代码片段中,三个线性层在 __init__ 方法内注册到 LightningModule 模块,并在 forward 方法内定义它们之间的关系:

from pytorch_lightning import LightningModule
from torch import nn
class SampleModel(LightningModule):
   def __init__(self):
      """instantiate necessary layers"""
       self.individual_layer_1 = nn.Linear(..., ...)
       self.individual_layer_2 = nn.Linear(..., ...)
       self.individual_layer_3 = nn.Linear(..., ...)
   def forward(self, input):
       """define forward propagation logic"""
       output_1 = self.individual_layer_1(input)
       output_2 = self.individual_layer_2(output_1)
       final_output = self.individual_layer_3(output_2)
       return final_output

另一种定义网络的方法是使用 torch.nn.Sequential,如下所示。使用此模块,一组层可以被组合在一起,并且自动实现输出链式化:

class SampleModel(LightningModule):
   def __init__(self):
       """instantiate necessary layers"""
       self.multiple_layers = nn.Sequential(
       nn.Linear(    ,    ),
       nn.Linear(    ,    ),
       nn.Linear(    ,    ))
   def forward(self, input):
       """define forward propagation logic"""
       final_output = self.multiple_layers(input)
       return final_output

在前面的代码中,三个线性层被组合在一起并存储为单个实例变量 self.multiple_layers。在 forward 方法中,我们只需触发 self.multiple_layers 与输入张量以逐个通过每个层传递张量。

以下部分旨在介绍常见的层实现。

PyTorch DL 层

DL 框架的一个主要优势来自于各种层定义:梯度计算逻辑已经成为层定义的一部分,因此您可以专注于找到适合任务的最佳模型架构。在本节中,我们将了解跨项目常用的层。如果您感兴趣的层在本节中未涵盖,请参阅官方文档(pytorch.org/docs/stable/nn.html)。

PyTorch 密集(线性)层

第一种类型的层是 torch.nn.Linear。顾名思义,它对输入张量应用线性变换。函数的两个主要参数是 in_featuresout_features,分别定义输入和输出张量的维度:

linear_layer = torch.nn.Linear(
              in_features,   # Size of each input sample
              out_features,  # Size of each output sample)
# N = batch size
# * = any number of additional dimensions
input_tensor = torch.rand(N, *, in_features)
output_tensor = linear_layer(input_tensor) # (N, *, out_features)

来自 torch.nn 模块的层实现已经定义了 forward 函数,因此您可以将层变量像函数一样使用,以触发前向传播。

PyTorch 池化层

池化层通常用于对张量进行下采样。最流行的两种类型是最大池化和平均池化。这些层的关键参数是 kernel_sizestride,分别定义窗口的大小以及每个池化操作的移动方式。

最大池化层通过选择每个窗口中的最大值来对输入张量进行下采样:

# 2D max pooling
max_pool_layer = torch.nn.MaxPool2d(
   kernel_size,         # the size of the window to take a max over
   stride=None,         # the stride of the window. Default value is kernel_size
   padding=0,           # implicit zero padding to be added on both sides
   dilation=1,          # a parameter that controls the stride of elements in the window)
# N = batch size
# C = number of channels
# H = height of input planes in pixels
# W = width of input planes in pixels
input_tensor = torch.rand(N, C, H, W)
output_tensor = max_pool_layer(input_tensor) # (N, C, H_out, W_out) 

另一方面,平均池化层通过计算每个窗口的平均值来降低输入张量的分辨率:

# 2D average pooling
avg_pool_layer = torch.nn.AvgPool2d(
   kernel_size,         # the size of the window to take a max over
   stride=None,         # the stride of the window. Default value is kernel_size
   padding=0,           # implicit zero padding to be added on both sides)
# N = batch size
# C = number of channels
# H = height of input planes in pixels
# W = width of input planes in pixels
input_tensor = torch.rand(N, C, H, W)
output_tensor = avg_pool_layer(input_tensor) # (N, C, H_out, W_out)

你可以在 pytorch.org/docs/stable/nn.html#pooling-layers 找到其他类型的池化层。

PyTorch 归一化层

在数据处理中常用的归一化的目的是将数值数据缩放到一个公共尺度,而不改变其分布。在深度学习中,归一化层用于以更大的数值稳定性训练网络 (pytorch.org/docs/stable/nn.html#normalization-layers)。

最常用的归一化层是批量归一化层,它对一个小批量的值进行缩放。在下面的代码片段中,我们介绍了 torch.nn.BatchNorm2d,一个为带有额外通道维度的二维张量小批量设计的批量归一化层:

batch_norm_layer = torch.nn.BatchNorm2d(
   num_features,      # Number of channels in the input image
   eps=1e-05,         # A value added to the denominator for numerical stability
   momentum=0.1,      # The value used for the running_mean and running_var computation
   affine=True,       # a boolean value that when set to True, this module has learnable affine parameters)
# N = batch size
# C = number of channels
# H = height of input planes in pixels
# W = width of input planes in pixels
input_tensor = torch.rand(N, C, H, W)
output_tensor = batch_norm_layer(input_tensor) # same shape as input (N, C, H, W)

在各种参数中,你应该了解的主要参数是 num_features,它表示通道数。层的输入是一个四维张量,其中每个索引表示批量大小 (N),通道数 (C),图像高度 (H) 和图像宽度 (W)。

PyTorch 丢弃层

丢弃层帮助模型通过随机将一组值设为零来提取通用特征。这种操作可以防止模型过度拟合训练集。话虽如此,PyTorch 的丢弃层主要通过一个参数 p 控制一个元素被设为零的概率:

drop_out_layer = torch.nn.Dropout2d(
   p=0.5,  # probability of an element to be zeroed )
# N = batch size
# C = number of channels
# H = height of input planes in pixels
# W = width of input planes in pixels
input_tensor = torch.rand(N, C, H, W)
output_tensor = drop_out_layer(input_tensor) # same shape as input (N, C, H, W)

在这个例子中,我们将元素的 50%设为零 (p=0.5)。与批量归一化层类似,torch.nn.Dropout2d 的输入张量大小为 N, C, H, W

PyTorch 卷积层

针对图像处理专门设计的卷积层,使用滑动窗口技术在输入张量上应用卷积操作。在图像处理中,中间数据以 N, C, H, W 大小的四维张量表示,torch.nn.Conv2d 是标准选择:

conv_layer = torch.nn.Conv2d(
   in_channels,         # Number of channels in the input image
   out_channels,        # Number of channels produced by the convolution
   kernel_size,         # Size of the convolving kernel
   stride=1,            # Stride of the convolution
   padding=0,           # Padding added to all four sides of the input.
   dilation=1,          # Spacing between kernel elements)
# N = batch size
# C = number of channels
# H = height of input planes in pixels
# W = width of input planes in pixels
input_tensor = torch.rand(N, C_in, H, W)
output_tensor = conv_layer(input_tensor) # (N, C_out, H_out, W_out)

torch.nn.Conv2d 类的第一个参数 in_channels 表示输入张量中的通道数。第二个参数 out_channels 表示输出张量中的通道数,即滤波器的数量。其他参数 kernel_sizestridepadding 决定了如何对该层进行卷积操作。

PyTorch 循环层

递归层设计用于序列数据。在各种类型的递归层中,本节将介绍torch.nn.RNN,它将多层 Elman递归神经网络RNN)应用于给定的序列(onlinelibrary.wiley.com/doi/abs/10.1207/s15516709cog1402_1)。如果您想尝试不同的递归层,可以参考官方文档:pytorch.org/docs/stable/nn.html#recurrent-layers

# multi-layer Elman RNN with tanh or ReLU non-linearity to an input sequence.
rnn = torch.nn.RNN(
   input_size,                     # The number of expected features in the input x
   hidden_size,                    # The number of features in the hidden state h
   num_layers = 1,                 # Number of recurrent layers
   nonlinearity="tanh",            # The non-linearity to use. Can be either 'tanh' or 'relu'
   bias=True,                      # If False, then the layer does not use bias weights
   batch_first=False,              # If True, then the input and output tensors are provided
                                                                                                 # as (batch, seq, feature) instead of (seq, batch, feature)
             dropout=0,                                                # If non-zero, introduces a Dropout layer on the outputs of each RNN layer
                                                                                                 # except the last layer, with dropout probability equal to dropout
   bidirectional=False,             # If True, becomes a bidirectional RNN)
# N = batch size
# L = sequence length
# D = 2 if bidirectionally, otherwise 1
# H_in = input_size
# H_out = hidden_size
rnn = nn.RNN(H_in, H_out, num_layers)
input_tensor = torch.randn(L, N, H_in)
# H_0 = tensor containing the initial hidden state for each element in the batch
h0 = torch.randn(D * num_layers, N, H_out)
# output_tensor (L, N, D * H_out)
# hn (D * num_layers, N, H_out) 
output_tensor, hn = rnn(input_tensor, h0)

torch.nn.RNN的三个关键参数是input_sizehidden_sizenum_layers。它们分别指的是输入张量中预期的特征数、隐藏状态中的特征数以及要使用的递归层数。为了触发前向传播,您需要传递两个东西,一个输入张量和一个包含初始隐藏状态的张量。

PyTorch 模型训练

在本节中,我们描述了 PL 的模型训练组件。如下面的代码块所示,LightningModule是您必须继承的基类。它的configure_optimizers函数用于定义训练的优化器。然后,实际的训练逻辑在training_step函数中定义:

class SampleModel(LightningModule):
   def configure_optimizers(self):
      """Define optimizer to use"""
      return torch.optim.Adam(self.parameters(), lr=0.02)
   def training_step(self, batch, batch_idx):
      """Define single training iteration"""
      x, y = batch
      y_hat = self(x)
      loss = F.cross_entropy(y_hat, y)
      return loss

验证、预测和测试循环具有类似的函数定义;一个批次被馈送到网络中以计算必要的预测和损失值。收集的数据也可以使用 PL 的内置日志记录系统进行存储和显示。有关详细信息,请参阅官方文档(pytorch-lightning.readthedocs.io/en/latest/common/lightning_module.html):

   def validation_step(self, batch, batch_idx):
      """Define single validation iteration"""
      loss, acc = self._shared_eval_step(batch, batch_idx)
      metrics = {"val_acc": acc, "val_loss": loss}
      self.log_dict(metrics)
      return metrics
   def test_step(self, batch, batch_idx):
      """Define single test iteration"""
      loss, acc = self._shared_eval_step(batch, batch_idx)
      metrics = {"test_acc": acc, "test_loss": loss}
      self.log_dict(metrics)
      return metrics
   def _shared_eval_step(self, batch, batch_idx):
      x, y = batch
      outputs = self(x)
      loss = self.criterion(outputs, targets)
      acc = accuracy(outputs.round(), targets.int())
      return loss, acc
   def predict_step(self, batch, batch_idx, dataloader_idx=0):
      """Compute prediction for the given batch of data"""
      x, y = batch
      y_hat = self(x)
      return y_hat

在幕后,LightningModule执行以下一系列简化的 PyTorch 代码:

model.train()
torch.set_grad_enabled(True)
outs = []
for batch_idx, batch in enumerate(train_dataloader):
   loss = training_step(batch, batch_idx)
   outs.append(loss.detach())
   # clear gradients
   optimizer.zero_grad()
   # backward
   loss.backward()
   # update parameters
   optimizer.step()
   if validate_at_some_point
      model.eval()
      for val_batch_idx, val_batch in enumerate(val_dataloader):
         val_out = model.validation_step(val_batch, val_batch_idx)
         model.train()

LightningDataModuleLightningModule结合在一起,可以简单地实现对测试集的训练和推理如下:

from pytorch_lightning import Trainer
data_module = SampleDataModule()
trainer = Trainer(max_epochs=num_epochs)
model = SampleModel()
trainer.fit(model, data_module)
result = trainer.test()

到目前为止,您应该已经学会了如何使用 PyTorch 设置模型训练。接下来的两节专门介绍了损失函数和优化器,这两个模型训练的主要组成部分。

PyTorch 损失函数

首先,我们将查看 PL 中提供的不同损失函数。本节中的损失函数可以在torch.nn模块中找到。

PyTorch MSE / L2 损失函数

可以使用torch.nn.MSELoss创建 MSE 损失函数。但是,这仅计算平方误差分量,并利用reduction参数提供变体。当reductionNone时,返回计算值。另一方面,当设置为sum时,输出将被累加。为了获得精确的 MSE 损失,必须将 reduction 设置为mean,如下面的代码片段所示:

loss = nn.MSELoss(reduction='mean')
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)

接下来,让我们看看 MAE 损失。

PyTorch MAE / L1 损失函数

MAE(Mean Absolute Error,平均绝对误差)损失函数可以通过torch.nn.L1Loss来实例化。与 MSE 损失函数类似,此函数根据reduction参数计算不同的值:

Loss = nn.L1Loss(reduction='mean')
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = loss(input, target)

现在我们可以转向 CE 损失,它在多类分类任务中使用。

PyTorch 交叉熵损失函数

torch.nn.CrossEntropyLoss在训练多类别分类问题的模型时非常有用。正如下面的代码片段所示,此类别还具有reduction参数以计算不同的变体。您还可以使用weightignore_index参数来进一步改变损失的行为,分别对每个类别进行加权并忽略特定索引:

loss = nn.CrossEntropyLoss(reduction="mean")
input = torch.randn(3, 5, requires_grad=True)
target = torch.empty(3, dtype=torch.long).random_(5)
output = loss(input, target)

类似地,我们可以定义 BCE 损失。

PyTorch BCE 损失函数

类似于 CE 损失,PyTorch 将 BCE 损失定义为torch.nn.BCELoss,具有相同的参数集。然而,利用torch.nn.BCELoss与 sigmoid 操作之间的密切关系,PyTorch 提供了torch.nn.BCEWithLogitsLoss,通过在一个类中组合softmax操作和 BCE 损失计算,实现了更高的数值稳定性。其使用方法如下面的代码片段所示:

loss = torch.nn.BCEWithLogitsLoss(reduction="mean")
input = torch.randn(3, requires_grad=True)
target = torch.empty(3).random_(2)
output = loss(input, target)

最后,让我们看一下如何构建 PyTorch 中的自定义损失。

PyTorch 自定义损失函数

定义自定义损失函数非常简单。任何使用 PyTorch 操作定义的函数都可以用作损失函数。

下面是使用mean操作符实现的torch.nn.MSELoss的样本实现:

def custom_mse_loss(output, target):
   loss = torch.mean((output - target)**2)
   return loss
input = torch.randn(3, 5, requires_grad=True)
target = torch.randn(3, 5)
output = custom_mse_loss(input, target)

现在,我们将转到 PyTorch 优化器的概述。

PyTorch 优化器

PyTorch 模型训练部分所述,LightningModuleconfigure_optimizers函数指定了训练的优化器。在 PyTorch 中,可以从torch.optim模块中找到预定义的优化器。优化器实例化需要模型参数,可以通过在模型上调用parameters函数来获取,如以下部分所示。

PyTorch SGD 优化器

下面的代码片段实例化了一个 LR 为0.1的 SGD 优化器,并展示了如何实现模型参数更新的单步操作。

torch.optim.SGD内置支持动量和加速,进一步提高了训练性能。可以使用momentumnesterov参数进行配置:

optimizer = torch.optim.SGD(model.parameters(), lr=0.1 momentum=0.9, nesterov=True)
PyTorch Adam 优化器

同样地,可以使用torch.optim.Adam来实例化 Adam 优化器,如下面的代码行所示:

optimizer = torch.optim.Adam(model.parameters(), lr=0.1) 

如果您对 PyTorch 中优化器的工作原理感兴趣,我们建议阅读官方文档:pytorch.org/docs/stable/optim.html

要记住的事情

a. PyTorch 是一种流行的深度学习框架,提供了 GPU 加速的矩阵计算和自动微分功能。PyTorch 因其灵活性、易用性以及在模型训练中的效率而受到欢迎。

b. 为了提高可读性和模块化性,PyTorch 利用名为Dataset的类来进行数据管理,并使用另一个名为DataLoader的类来迭代访问样本。

c. PL 的关键优势来自于LightningModule,它简化了复杂的 PyTorch 代码结构组织为六个部分:计算、训练循环、验证循环、测试循环、预测循环,以及优化器和学习率调度器。

d. PyTorch 和 PL 共享torch.nn模块,用于各种层和损失函数。预定义的优化器可以在torch.optim模块中找到。

在接下来的部分中,我们将看一看另一个深度学习框架,TF。使用 TF 进行的训练设置与使用 PyTorch 的设置非常相似。

在 TF 中实现和训练模型

虽然 PyTorch 偏向于研究项目,TF 更加注重行业应用案例。尽管 PyTorch 的部署功能,如 Torch Serve 和 Torch Mobile 仍处于实验阶段,TF 的部署功能,如 TF Serve 和 TF Lite 已经稳定并且在积极使用中。TF 的第一个版本由 Google Brain 团队于 2011 年推出,并且他们持续更新 TF 以使其更加灵活、用户友好和高效。TF 和 PyTorch 的关键区别最初相差较大,因为 TF 的第一个版本使用静态图。然而,随着版本 2 的推出,情况已经改变,它引入了急切执行,模仿了 PyTorch 中的动态图。TF 版本 2 经常与Keras一起使用,这是一个用于人工神经网络的接口(keras.io)。Keras 允许用户快速开发深度学习模型并运行实验。在接下来的部分中,我们将介绍 TF 的关键组件。

TF 数据加载逻辑

TF 模型可以以多种方式加载数据。你应该了解的一个关键数据操作模块是tf.data,它帮助你构建高效的输入管道。tf.data提供了tf.data.Datasettf.data.TFRecordDataset类,专门设计用于加载不同数据格式的数据集。此外,还有tensorflow_datasetstfds)模块(www.tensorflow.org/datasets/api_docs/python/tfds)和tensorflow_addons模块(www.tensorflow.org/addons),它们进一步简化了许多情况下的数据加载过程。还值得一提的是 TF I/O 包(www.tensorflow.org/io/overview),它扩展了标准 TF 文件系统交互的功能。

无论你将使用哪个包,你都应该考虑创建一个DataLoader类。在这个类中,你将清晰地定义目标数据如何加载以及在训练之前如何预处理。以下代码片段展示了一个加载逻辑的示例实现:

import tensorflow_datasets as tfds
class DataLoader: 
   """ DataLoader class"""
   @staticmethod 
   def load_data(config): 
      return tfds.load(config.data_url)

在前面的例子中,我们使用tfds从外部 URL(config.data_url)加载数据。关于tfds.load的更多信息可以在线找到:www.tensorflow.org/datasets/api_docs/python/tfds/load

数据以各种格式可用。因此,重要的是将其预处理为 TF 模型可以使用的格式,使用tf.data模块提供的功能。因此,让我们看一下如何使用这个包来读取常见格式的数据:

  • 首先,可以按如下方式读取以tfrecord格式存储的数据序列:

    import tensorflow as tf 
    dataset = tf.data.TFRecordDataset(list_of_files)
    
  • 我们可以使用tf.data.Dataset.from_tensor_slices函数从 NumPy 数组创建数据集对象,如下所示:

    dataset = tf.data.Dataset.from_tensor_slices(numpy_array)
    
  • Pandas DataFrames 也可以使用相同的tf.data.Dataset.from_tensor_slices函数加载为数据集:

    dataset = tf.data.Dataset.from_tensor_slices((df_features.values, df_target.values))
    
  • 另一种选项是使用 Python 生成器。以下是一个简单的示例,演示如何使用生成器来提供配对的图像和标签:

    def data_generator(images, labels):
       def fetch_examples(): 
           i = 0 
           while True: 
              example = (images[i], labels[i]) 
              i += 1 
              i %= len(labels) 
              yield example 
           return fetch_examples
    training_dataset = tf.data.Dataset.from_generator(
       data_generator(images, labels),
       output_types=(tf.float32, tf.int32), 
       output_shapes=(tf.TensorShape(features_shape), tf.TensorShape(labels_shape)))
    

如上一段代码片段所示,tf.data.Dataset 提供了内置的数据加载功能,例如批处理、重复和洗牌。这些选项都是不言自明的:批处理创建特定大小的小批量数据,重复允许我们多次迭代数据集,而洗牌则在每个 epoch 中混淆数据条目。

在结束本节之前,我们想提到,使用 Keras 实现的模型可以直接消耗 NumPy 数组和 Pandas DataFrames。

TF 模型定义

类似于 PyTorch 和 PL 如何处理模型定义,TF 提供了多种定义网络架构的方法。首先,我们将看看Keras.Sequential,它链式连接一组层以构建网络。这个类为您处理了层之间的链接,因此您不需要显式地定义它们的连接:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
input_shape = 50
model = keras.Sequential(
   [
      keras.Input(shape=input_shape),
      layers.Dense(128, activation="relu", name="layer1"),
      layers.Dense(64, activation="relu", name="layer2"),
      layers.Dense(1, activation="sigmoid", name="layer3"),
   ])

在前面的例子中,我们创建了一个模型,包括一个输入层、两个密集层和一个生成单个神经元输出的输出层。这是一个简单的模型,可以用于二元分类。

如果模型定义更复杂,无法按顺序构建,另一种选择是使用keras.Model类,如下面的代码片段所示:

num_classes = 5 
input_1 = layers.Input(50)
input_2 = layers.Input(10)
x_1 = layers.Dense(128, activation="relu", name="layer1x")(input_1)
x_1 = layers.Dense(64, activation="relu", name="layer1_2x")(x_1)
x_2 = layers.Dense(128, activation="relu", name="layer2x")(input_2)
x_2 = layers.Dense(64, activation="relu", name="layer2_1x")(x_2)
x = layers.concatenate([x_1, x_2], name="concatenate")
out = layers.Dense(num_classes, activation="softmax", name="output")(x)
model = keras.Model((input_1,input_2), out)

在这个例子中,我们有两个输入,并进行了不同的计算。这两条路径在最后的串联层中合并,将串联的张量传输到最终的具有五个神经元的密集层中。考虑到最后一层使用了softmax激活函数,这个模型可以用于多类分类。

第三个选项如下,是创建一个继承了keras.Model的类。这个选项给了你最大的灵活性,允许你自定义模型的每个部分和训练过程:

class SimpleANN(keras.Model):
   def __init__(self):
      super().__init__()
      self.dense_1 = layers.Dense(128, activation="relu", name="layer1")
      self.dense_2 = layers.Dense(64, activation="relu", name="layer2")
      self.out = layers.Dense(1, activation="sigmoid", name="output")
   def call(self, inputs):
      x = self.dense_1(inputs)
      x = self.dense_3(x)
      return self.out(x)
model = SimpleANN()

SimpleANN,来自前面的代码,继承自 Keras.Model。在 __init__ 函数中,我们需要使用 tf.keras.layers 模块或基本的 TF 操作来定义网络架构。前向传播逻辑在 call 方法内部定义,就像 PyTorch 中有 forward 方法一样。

当模型被定义为一个独立的类时,您可以将额外的功能链接到该类。在下面的示例中,添加了 build_graph 方法以返回一个 keras.Model 实例,因此您可以例如使用 summary 函数来可视化网络架构作为更简单的表示:

class SimpleANN(keras.Model):
   def __init__(self):
   ...
   def call(self, inputs):
   ...
   def build_graph(self, raw_shape):
      x = tf.keras.layers.Input(shape=raw_shape)
      return keras.Model(inputs=[x], outputs=self.call(x))

现在,让我们看看 TF 如何通过 Keras 提供一组层实现。

TF 深度学习层

如前一节所述,tf.keras.layers 模块提供了一组层实现,您可以用来构建 TF 模型。在本节中,我们将涵盖与我们在 PyTorch 中实现和训练模型 部分描述的相同一组层。此模块中可用的层的完整列表可以在 www.tensorflow.org/api_docs/python/tf/keras/layers 找到。

TF 密集(线性)层

第一个是 tf.keras.layers.Dense,执行线性转换:

tf.keras.layers.Dense(units, activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None, **kwargs)

units 参数定义了密集层中神经元的数量(输出的维度)。如果未定义 activation 参数,则层的输出将原样返回。如下面的代码所示,我们也可以在层定义之外应用 Activation 操作:

X = layers.Dense(128, name="layer2")(input)
x = tf.keras.layers.Activation('relu')(x)

在某些情况下,您需要构建一个自定义层。下面的示例演示了如何使用基本的 TF 操作创建一个密集层,通过继承 tensorflow.keras.layers.Layer 类:

import tensorflow as tf
from tensorflow.keras.layers import Layer
class CustomDenseLayer(Layer):
   def __init__(self, units=32):
      super(SimpleDense, self).__init__()
      self.units = units
   def build(self, input_shape):
      w_init = tf.random_normal_initializer()
      self.w = tf.Variable(name="kernel", initial_value=w_init(shape=(input_shape[-1], self.units),
      dtype='float32'),trainable=True)
      b_init = tf.zeros_initializer()
      self.b = tf.Variable(name="bias",initial_value=b_init(shape=(self.units,), dtype='float32'),trainable=True)
   def call(self, inputs):
      return tf.matmul(inputs, self.w) + self.b

CustomDenseLayer 类的 __init__ 函数中,我们定义输出的维度(units)。然后,在 build 方法中实例化层的状态;我们为层创建并初始化权重和偏置。最后的 call 方法定义了计算本身。对于密集层,它包括将输入与权重相乘并添加偏置。

TF 池化层

tf.keras.layers 提供不同类型的池化层:平均池化、最大池化、全局平均池化和全局最大池化层,用于一维时间数据、二维或三维空间数据。在本节中,我们将展示二维最大池化和平均池化层:

tf.keras.layers.MaxPool2D(
   pool_size=(2, 2), strides=None, padding='valid', data_format=None,
   kwargs)
tf.keras.layers.AveragePooling2D(
   pool_size=(2, 2), strides=None, padding='valid', data_format=None,
   kwargs)

这两个层都使用 pool_size 参数,定义窗口的大小。strides 参数用于定义窗口在池化操作中如何移动。

TF 标准化层

在下面的示例中,我们演示了一个批量标准化层,tf.keras.layers.BatchNormalization

tf.keras.layers.BatchNormalization(
   axis=-1, momentum=0.99, epsilon=0.001, center=True, scale=True,
   beta_initializer='zeros', gamma_initializer='ones',
   moving_mean_initializer='zeros',
   moving_variance_initializer='ones', beta_regularizer=None,
   gamma_regularizer=None, beta_constraint=None, gamma_constraint=None, **kwargs)

该层的输出将具有接近 0 的均值和接近 1 的标准差。有关每个参数的详细信息,请查看 www.tensorflow.org/api_docs/python/tf/keras/layers/BatchNormalization

TF dropout 层

Tf.keras.layers.Dropout 层应用了 dropout,这是一种将随机选择的数值设为零的正则化方法:

tf.keras.layers.Dropout(rate, noise_shape=None, seed=None, **kwargs)

在上述层的实例化中,rate 参数是一个介于 01 之间的浮点值,确定将被丢弃的输入单元的分数。

TF 卷积层

tf.keras.layers 提供了各种卷积层的实现,包括 tf.keras.layers.Conv1Dtf.keras.layers.Conv2Dtf.keras.layers.Conv3D,以及相应的转置卷积层(反卷积层) tf.keras.layers.Conv1DTransposetf.keras.layers.Conv2DTransposetf.keras.layers.Conv3DTranspose

以下代码片段描述了二维卷积层的实例化:

tf.keras.layers.Conv2D(
   filters, kernel_size, strides=(1, 1), padding='valid',
   data_format=None, dilation_rate=(1, 1), groups=1,
   activation=None, use_bias=True,
   kernel_initializer='glorot_uniform',
   bias_initializer='zeros', kernel_regularizer=None,
   bias_regularizer=None, activity_regularizer=None,
   kernel_constraint=None, bias_constraint=None, **kwargs)

在上述层定义中的主要参数是 filterskernel_sizefilters 参数定义了输出的维度,kernel_size 参数定义了二维卷积窗口的大小。有关其他参数,请查看 www.tensorflow.org/api_docs/python/tf/keras/layers/Conv2D

TF 循环层

在 Keras 中实现了以下一系列的循环层:LSTM 层,GRU 层,SimpleRNN 层,TimeDistributed 层,Bidirectional 层,ConvLSTM2D 层和 Base RNN 层。

在下面的代码片段中,我们展示了如何实例化 BidirectionalLSTM 层:

model = Sequential()
model.add(Bidirectional(LSTM(10, return_sequences=True), input_shape=(5, 10)))
model.add(Bidirectional(LSTM(10)))
model.add(Dense(5))
model.add(Activation('softmax'))

在上面的例子中,LSTM 层通过 Bidirectional 封装器进行了修改,以向两个隐藏层的两个副本提供初始序列和反向序列。这两层的输出被合并以得到最终的输出。默认情况下,输出是连接的,但是 merge_mode 参数允许我们选择不同的合并选项。输出空间的维度由第一个参数定义。要在每个时间步访问每个输入的隐藏状态,可以启用 return_sequences。更多细节,请查看 www.tensorflow.org/api_docs/python/tf/keras/layers/LSTM

TF 模型训练

对于 Keras 模型,只需在调用带有优化器和损失函数的 compile 函数后,通过在模型上调用 fit 函数即可完成模型训练。fit 函数使用提供的数据集进行指定轮数的训练。

以下代码片段描述了 fit 函数的参数:

model.fit(
   x=None, y=None, batch_size=None, epochs=1,
   verbose='auto', callbacks=None, validation_split=0.0,
   validation_data=None, shuffle=True,
   class_weight=None, sample_weight=None, 
   initial_epoch=0, steps_per_epoch=None,
   validation_steps=None, validation_batch_size=None,
   validation_freq=1, max_queue_size=10, workers=1,
   use_multiprocessing=False)

xy表示输入张量和标签。它们可以以多种格式提供:NumPy 数组、TF 张量、TF 数据集、生成器或tf.keras.utils.experimental.DatasetCreator。除了fit,Keras 模型还具有train_on_batch函数,仅在单个数据批次上执行梯度更新。

在 TF 版本 1 中,训练循环需要计算图编译,而 TF 版本 2 允许我们在不进行任何编译的情况下定义训练逻辑,就像 PyTorch 一样。典型的训练循环如下所示:

Optimizer = tf.keras.optimizers.Adam()
loss_fn = tf.keras.losses.CategoricalCrossentropy()
train_acc_metric = tf.keras.metrics.CategoricalAccuracy()
for epoch in range(epochs):
   for step, (x_batch_train, y_batch_train) in enumerate(train_dataset):
       with tf.GradientTape() as tape:
          logits = model(x_batch_train, training=True)
          loss_value = loss_fn(y_batch_train, logits)
       grads = tape.gradient(loss_value, model.trainable_weights)
       optimizer.apply_gradients(zip(grads, model.trainable_weights))
       train_acc_metric.update_state(y, logits)

在上述代码片段中,外部循环遍历各个 epoch,内部循环遍历训练集。前向传播和损失计算在GradientTape的范围内,该范围记录每个批次的自动微分操作。在范围外,优化器使用计算出的梯度更新权重。在上述示例中,TF 函数立即执行操作,而不是像急切执行中那样将操作添加到计算图中。我们想提到的是,如果您使用的是 TF 版本 1,那么需要使用@tf.function装饰器,因为那里需要显式构建计算图。

接下来,我们将研究 TF 中的损失函数。

TF 损失函数

在 TF 中,当模型编译时需要指定损失函数。虽然您可以从头开始构建自定义损失函数,但您可以通过tf.keras.losses模块提供的预定义损失函数来使用 Keras 提供的损失函数。以下示例演示了如何使用 Keras 的损失函数来编译模型:

model.compile(loss=tf.keras.losses.BinaryFocalCrossentropy(gamma=2.0, from_logits=True), ...)

此外,您可以将字符串别名传递给损失参数,如以下代码片段所示:

model.compile(loss='sparse_categorical_crossentropy', ...)

在本节中,我们将解释如何在 TF 中实例化PyTorch 损失函数部分中描述的损失函数。

TF MSE / L2 损失函数

MSE / L2 损失函数可以定义如下(www.tensorflow.org/api_docs/python/tf/keras/losses/MeanSquaredError):

mse = tf.keras.losses.MeanSquaredError()

这是回归中最常用的损失函数,它计算标签和预测之间平方差的平均值。默认设置将计算 MSE。然而,类似于 PyTorch 的实现,我们可以提供一个reduction参数来改变这种行为。例如,如果您希望应用sum操作而不是平均操作,您可以在损失函数中添加reduction=tf.keras.losses.Reduction.SUM。鉴于 PyTorch 中的torch.nn.MSELoss返回原始的平方差,您可以通过将reduction=tf.keras.losses.Reduction.NONE传递给构造函数,在 TF 中获得相同的损失。

接下来,我们将研究 MAE 损失。

TF MAE / L1 损失函数

tf.keras.losses.MeanAbsoluteError是 Keras 中用于 MAE 损失的函数(www.tensorflow.org/api_docs/python/tf/keras/losses/MeanAbsoluteError):

mae = tf.keras.losses.MeanAbsoluteError()

正如其名称所示,此损失计算真实值和预测值之间的绝对差的平均值。它还有一个reduction参数,可以像tf.keras.losses.MeanSquaredError中描述的那样使用。

现在,让我们来看看分类损失,交叉熵损失。

TF 交叉熵损失函数

交叉熵损失计算两个概率分布之间的差异。Keras 提供了tf.keras.losses.CategoricalCrossentropy类,专门用于多类分类(www.tensorflow.org/api_docs/python/tf/keras/losses/CategoricalCrossentropy)。以下代码片段展示了其实例化:

cce = tf.keras.losses.CategoricalCrossentropy()

在 Keras 的情况下,标签需要格式化为独热向量。例如,当目标类是五类中的第一类时,它会是[1, 0, 0, 0, 0]

用于二元分类的交叉熵损失,BCE 损失,也存在。

TF 二元交叉熵损失函数

在二元分类的情况下,标签是01。专门为二元分类设计的损失函数,BCE 损失,可以定义如下(www.tensorflow.org/api_docs/python/tf/keras/losses/BinaryFocalCrossentropy):

loss = tf.keras.losses.BinaryFocalCrossentropy(from_logits=True)

此损失的关键参数是from_logits。当此标志设置为False时,我们需要提供概率,即介于01之间的连续值。当设置为True时,我们需要提供 logits,即介于-无穷大+无穷大之间的值。

最后,让我们看看如何在 TF 中定义自定义损失。

TF 自定义损失函数

要构建一个自定义损失函数,我们需要创建一个接受预测值和标签作为参数并执行所需计算的函数。虽然 TF 的语法只期望这两个参数,但我们可以通过将函数包装到另一个返回损失的函数中来添加一些额外的参数。以下示例展示了如何创建 Huber Loss 作为自定义损失函数:

def custom_huber_loss(threshold=1.0):
   def huber_fn(y_true, y_pred):
       error = y_true - y_pred
       is_small_error = tf.abs(error) < threshold
       squared_loss = tf.square(error) / 2
       linear_loss = threshold * tf.abs(error) - threshold**2 / 2
       return tf.where(is_small_error, squared_loss, linear_loss)
   return huber_fn
model.compile(loss=custom_huber_loss (2.0), optimizer="adam"

另一种选择是创建一个继承tf.keras.losses.Loss类的类。在这种情况下,我们需要实现__init__call方法,如下所示:

class CustomLoss(tf.keras.losses.Loss):
   def __init__(self, threshold=1.0):
      super().__init__()
      self.threshold = threshold
   def call(self, y_true, y_pred):
      error = y_true - y_pred 
      is_small_error = tf.abs(error) < threshold
      squared_loss = tf.square(error) / 2 
      linear_loss = threshold*tf.abs(error) - threshold**2 / 2 
      return tf.where(is_small_error, squared_loss, linear_loss)
model.compile(optimizer="adam", loss=CustomLoss(),

要使用这个损失类,您必须实例化它,并通过一个loss参数将其传递给compile函数,就像本节开头所描述的那样。

TF 优化器

在本节中,我们将描述如何在 TF 中设置不同的优化器来进行模型训练。与前一节中的损失函数类似,Keras 通过tf.keras.optimizers提供了一组优化器。在各种优化器中,我们将在接下来的章节中看到两个主要的优化器,SGD 和 Adam 优化器。

TF SGD 优化器

设计为固定学习率的 SGD 优化器是许多模型中最典型的优化器。以下代码片段描述了如何在 TF 中实例化 SGD 优化器:

tf.keras.optimizers.SGD(
   learning_rate=0.01,
   momentum=0.0,
   nesterov=False,
   name='SGD',
   kwargs)

类似于 PyTorch 的实现,tf.keras.optimizers.SGD还支持使用momentumnesterov参数的增强型 SGD 优化器。

TF Adam 优化器

如“模型训练逻辑”部分所述,Adam 优化器采用自适应学习率。在 TF 中,可以按以下方式实例化:

tf.keras.optimizers.Adam(
   learning_rate=0.001, beta_1=0.9, beta_2=0.999,
   epsilon=1e-07, amsgrad=False, name='Adam', **kwargs)

对于这两种优化器,虽然learning_rate在定义初始学习率时起着最重要的作用,但我们建议您查阅官方文档,以熟悉其他参数:www.tensorflow.org/api_docs/python/tf/keras/optimizers

TF 回调函数

在本节中,我们想简要描述回调函数。这些是在训练的各个阶段执行特定操作的对象。最常用的回调函数是EarlyStoppingModelCheckpointTensorBoard,它们分别在满足特定条件时停止训练、在每个 epoch 后保存模型并可视化训练状态。

这里是一个监控验证损失并在监控的损失停止减少时停止训练的EarlyStopping回调的示例:

tf.keras.callbacks.EarlyStopping(
   monitor='val_loss', min_delta=0.1, patience=2, 
   verbose=0, mode='min', baseline=None, 
   restore_best_weights=False)

min_delta参数定义了被监控数量的最小变化量,以便将变化视为改进,而patience参数定义了在训练停止之前经过的没有改进的 epoch 数量。

通过继承keras.callbacks.Callback可以构建自定义回调函数。通过覆盖其方法可以定义特定事件的逻辑,清晰地描述其绑定的事件:

  • on_train_begin

  • on_train_end

  • on_epoch_begin

  • on_epoch_end

  • on_test_begin

  • on_test_end

  • on_predict_begin

  • on_predict_end

  • on_train_batch_begin

  • on_train_batch_end

  • on_predict_batch_begin

  • on_predict_batch_end

  • on_test_batch_begin

  • 或者on_test_batch_end

对于完整的详细信息,建议您查看www.tensorflow.org/api_docs/python/tf/keras/callbacks/Callback

要记住的事项

a. tf.data允许您构建高效的数据加载逻辑。诸如tfdstensorflow addons或 TF I/O 等包可以用于读取不同格式的数据。

b. TF 通过 Keras 支持三种不同的模型构建方法:顺序模型、函数式模型和子类化模型。

c. 为了简化使用 TF 进行模型开发,tf.keras.layers模块提供了各种层的实现,tf.keras.losses模块包含不同的损失函数,tf.keras.optimizers模块提供了一组标准优化器。

d. Callbacks可以用于在训练的各个阶段执行特定操作。常用的回调包括EarlyStoppingModelCheckpoint

到目前为止,我们已经学习了如何使用最流行的深度学习框架 PyTorch 和 TF 设置 DL 模型训练。在接下来的部分中,我们将探讨我们在本节描述的组件在实际中如何使用。

分解复杂的最新模型实现

即使您已掌握了 TF 和 PyTorch 的基础知识,从头开始设置模型训练可能会让人感到不知所措。幸运的是,这两个框架都有详细的文档和易于跟随的教程:

在这一部分中,我们将介绍一个更复杂的模型,StyleGAN。我们的主要目标是解释如何将前面描述的组件组合起来用于复杂的深度学习项目。关于模型架构和性能的完整描述,我们建议参阅由 NVIDIA 发布的文章,网址为ieeexplore.ieee.org/document/8953766

StyleGAN

StyleGAN 作为生成对抗网络GAN)的一个变体,旨在从潜在代码(随机噪声向量)生成新的图像。其架构可以分解为三个元素:映射网络、生成器和鉴别器。在高层次上,映射网络和生成器共同作用,从一组随机值生成图像。鉴别器在训练过程中发挥了指导生成器生成逼真图像的关键作用。让我们更详细地看看每个组件。

映射网络和生成器

在传统的 GAN 中,生成器设计为直接处理潜在代码,而在 StyleGAN 中,潜在代码首先被馈送到映射网络,如 图 3.5 所示。映射网络的输出然后被馈送到生成器的每个步骤,改变生成图像的风格和细节。生成器从较低分辨率开始,以 4 x 4 或 8 x 8 的张量尺寸构建图像的轮廓。随着生成器处理更大的张量,图像的细节被填充。在最后几层,生成器与 64 x 64 和 1024 x 1024 尺寸的张量交互,构建高分辨率特征:

图 3.5 – StyleGAN 的映射网络(左)和生成器(右)

图 3.5 – StyleGAN 的映射网络(左)和生成器(右)

在上述图中,接受潜在向量 z 并生成 w 的网络是映射网络。右侧的网络是生成器 g,它接受一组噪声向量以及 w。与生成器相比,鉴别器相对简单。图层显示在 图 3.6 中:

图 3.6 – StyleGAN 在 FFHQ 数据集上的 1024 × 1024 分辨率的鉴别器架构

图 3.6 – StyleGAN 在 FFHQ 数据集上的 1024 × 1024 分辨率的鉴别器架构

如前所示的图像所示,鉴别器由多个卷积层块和下采样操作组成。它接收大小为 1024 x 1024 的图像并生成介于 01 之间的数值,描述图像的真实性。

训练 StyleGAN

训练 StyleGAN 需要大量计算,因此需要多个 GPU 才能达到合理的训练时间。估算结果总结在 图 3.7 中:

图 3.7 – 使用 Tesla V100 GPU 训练 StyleGAN 的训练时间

图 3.7 – 使用 Tesla V100 GPU 训练 StyleGAN 的训练时间

因此,如果您想尝试使用 StyleGAN,我们建议按照官方 GitHub 存储库中提供的预训练模型的说明进行操作:github.com/NVlabs/stylegan

在 PyTorch 中的实现

不幸的是,NVIDIA 尚未分享 StyleGAN 在 PyTorch 中的公共实现。相反,他们发布了 StyleGAN2,它与大多数相同组件共享。因此,我们将使用 StyleGAN2 实现作为我们的 PyTorch 示例:github.com/NVlabs/stylegan2-ada-pytorch

所有网络组件都位于 training/network.py 下。三个组件的命名如前所述:MappingNetworkGeneratorDiscriminator

PyTorch 中的映射网络

MappingNetwork 的实现是不言自明的。以下代码片段包含映射网络的核心逻辑:

class MappingNetwork(torch.nn.Module):
   def __init__(self, ...):
       ...
       for idx in range(num_layers):
          in_features = features_list[idx]
          out_features = features_list[idx + 1]
          layer = FullyConnectedLayer(in_features, out_features, activation=activation, lr_multiplier= lr_multiplier) setattr(self, f'fc{idx}', layer)

   def forward(self, z, ...):
       # Embed, normalize, and concat inputs.
       x = normalize_2nd_moment(z.to(torch.float32))

       # Main layers
       for idx in range(self.num_layers):
          layer = getattr(self, f'fc{idx}')
          x = layer(x)
       return x

在这个网络定义中,MappingNetwork也继承了torch.nn.Module。在__init__函数中初始化了必要的FullyConnectedLayer实例。forward方法将潜在向量z传递给每一层。

PyTorch 中的生成器

以下代码片段描述了生成器的实现方式。它包括MappingNetworkSynthesisNetwork,如图 3.5所示:

class Generator(torch.nn.Module):
   def __init__(self, …):
       self.z_dim = z_dim
       self.c_dim = c_dim
       self.w_dim = w_dim
       self.img_resolution = img_resolution
       self.img_channels = img_channels
       self.synthesis = SynthesisNetwork(
          w_dim=w_dim, 
          img_resolution=img_resolution,
          img_channels=img_channels,
          synthesis_kwargs)
       self.num_ws = self.synthesis.num_ws
       self.mapping = MappingNetwork(
          z_dim=z_dim, c_dim=c_dim, w_dim=w_dim,
          num_ws=self.num_ws, **mapping_kwargs)
   def forward(self, z, c, truncation_psi=1, truncation_cutoff=None, **synthesis_kwargs):
       ws = self.mapping(z, c, 
       truncation_psi=truncation_psi, 
       truncation_cutoff=truncation_cutoff)
       img = self.synthesis(ws, **synthesis_kwargs)
       return img

生成器网络Generator也继承了torch.nn.ModuleSynthesisNetworkMappingNetwork__init__函数中被实例化,并在forward函数中按顺序触发。SynthesisNetwork的实现总结如下代码片段:

class SynthesisNetwork(torch.nn.Module):
   def __init__(self, ...):
       for res in self.block_resolutions:
          block = SynthesisBlock(
             in_channels, out_channels, w_dim=w_dim,
             resolution=res, img_channels=img_channels,
             is_last=is_last, use_fp16=use_fp16,
             block_kwargs)
          setattr(self, f'b{res}', block)
       ...
   def forward(self, ws, **block_kwargs):
       ...
       x = img = None
       for res, cur_ws in zip(self.block_resolutions, block_ws):
          block = getattr(self, f'b{res}')
          x, img = block(x, img, cur_ws, **block_kwargs)
       return img

SynthesisNetwork包含多个SynthesisBlockSynthesisBlock接收噪声向量和MappingNetwork的输出,生成最终成为输出图像的张量。

PyTorch 中的判别器

以下代码片段总结了Discriminator的 PyTorch 实现。网络架构遵循图 3.6中描述的结构:

class Discriminator(torch.nn.Module):
   def __init__(self, ...):
       self.block_resolutions = [2 ** i for i in range(self.img_resolution_log2, 2, -1)]
       for res in self.block_resolutions:
          block = DiscriminatorBlock(
              in_channels, tmp_channels, out_channels,
              resolution=res,
              first_layer_idx = cur_layer_idx,
              use_fp16=use_fp16, **block_kwargs, 
              common_kwargs)
          setattr(self, f'b{res}', block)
   def forward(self, img, c, **block_kwargs):
       x = None
       for res in self.block_resolutions:
          block = getattr(self, f'b{res}')
          x, img = block(x, img, **block_kwargs)
       return x

类似于SynthesisNetworkDiscriminator利用DiscriminatorBlock类动态创建一组不同尺寸的卷积层。它们在__init__函数中定义,并且张量在forward函数中按顺序传递给每个块。

PyTorch 中的模型训练逻辑

训练逻辑在training/train_loop.pytraining_loop函数中定义。原始实现包含许多细节。在以下代码片段中,我们将查看与PyTorch 模型训练部分所学内容相符的主要组件:

def training_loop(...):
   ...
training_set_iterator = iter(torch.utils.data.DataLoader(dataset=training_set, sampler=training_set_sampler, batch_size=batch_size//num_gpus, **data_loader_kwargs))
   loss = dnnlib.util.construct_class_by_name(device=device, **ddp_modules, **loss_kwargs) # subclass of training.loss.Loss
   while True:
      # Fetch training data.
      with torch.autograd.profiler.record_function('data_fetch'):
         phase_real_img, phase_real_c = next(training_set_iterator)
      # Execute training phases.
      for phase, phase_gen_z, phase_gen_c in zip(phases, all_gen_z, all_gen_c):
         # Accumulate gradients over multiple rounds.
      for round_idx, (real_img, real_c, gen_z, gen_c) in enumerate(zip(phase_real_img, phase_real_c, phase_gen_z, phase_gen_c)):
         loss.accumulate_gradients(phase=phase.name, real_img=real_img, real_c=real_c, gen_z=gen_z, gen_c=gen_c, sync=sync, gain=gain)
      # Update weights.
      phase.module.requires_grad_(False)
      with torch.autograd.profiler.record_function(phase.name + '_opt'):
         phase.opt.step()

该函数接收各种训练组件的配置,并训练GeneratorDiscriminator。外部循环迭代训练样本,内部循环处理梯度计算和模型参数更新。训练设置由单独的脚本main/train.py配置。

这总结了 PyTorch 实现的结构。尽管存储库由于大量文件而显得庞大,我们已经指导您如何将实现分解为在 PyTorch 中实现和训练模型部分所描述的组件。在接下来的部分中,我们将查看 TF 中的实现。

TF 中的实现

即使官方实现是基于 TF 的(github.com/NVlabs/stylegan),我们将看一个不同的实现,该实现出现在Hands-On Image Generation with TensorFlow: A Practical Guide to Generating Images and Videos Using Deep Learning by Soon Yau Cheong。这个版本基于 TF 版本 2,更符合我们在本书中描述的内容。该实现可以在以下链接找到:github.com/PacktPublishing/Hands-On-Image-Generation-with-TensorFlow-2.0/blob/master/Chapter07/ch7_faster_stylegan.ipynb

类似于前一节中描述的 PyTorch 实现,原始的 TF 实现包括 G_mapping 作为映射网络,G_style 作为生成器,以及 D_basic 作为判别器。

TF 中的映射网络

让我们看一下在以下链接定义的映射网络:github.com/NVlabs/stylegan/blob/1e0d5c781384ef12b50ef20a62fee5d78b38e88f/training/networks_stylegan.py#L384,以及其 TF 版本 2 的实现如下所示:

def Mapping(num_stages, input_shape=512):
   z = Input(shape=(input_shape))
   w = PixelNorm()(z)
   for i in range(8):
      w = DenseBlock(512, lrmul=0.01)(w)
      w = LeakyReLU(0.2)(w)
      w = tf.tile(tf.expand_dims(w, 1), (1,num_stages,1))
   return Model(z, w, name='mapping') 

MappingNetwork 的实现几乎是不言自明的。我们可以看到映射网络从潜在向量 z 构建出向量 w,使用了一个 PixelNorm 自定义层。该自定义层定义如下:

class PixelNorm(Layer):
   def __init__(self, epsilon=1e-8):
      super(PixelNorm, self).__init__()
      self.epsilon = epsilon                
   def call(self, input_tensor):
      return input_tensor / tf.math.sqrt(tf.reduce_mean(input_tensor**2, axis=-1, keepdims=True) + self.epsilon)

正如TF dense (linear) layers部分所述,PixelNorm 继承了 tensorflow.keras.layers.Layer 类,并在 call 函数中定义计算。

Mapping 的其余组件是一组具有 LeakyReLU 激活函数的稠密层。

接下来,我们将看一下生成器网络。

TF 中的生成器

原始代码中的生成器 G_style 由两个网络组成:G_mappingG_synthesis。参见以下链接:github.com/NVlabs/stylegan/blob/1e0d5c781384ef12b50ef20a62fee5d78b38e88f/training/networks_stylegan.py#L299

从仓库中获取的完整实现一开始可能看起来非常复杂。然而,你很快会发现 G_style 只是依次调用 G_mappingG_synthesis

SynthesisNetwork 的实现总结在以下代码片段中:github.com/NVlabs/stylegan/blob/1e0d5c781384ef12b50ef20a62fee5d78b38e88f/training/networks_stylegan.py#L440

在 TF 版本 2 中,生成器的实现如下:

def GenBlock(filter_num, res, input_shape, is_base):
   input_tensor = Input(shape=input_shape, name=f'g_{res}')
   noise = Input(shape=(res, res, 1), name=f'noise_{res}')
   w = Input(shape=512)
   x = input_tensor
   if not is_base:
      x = UpSampling2D((2,2))(x)
      x = ConvBlock(filter_num, 3)(x)
   x = AddNoise()([x, noise])
   x = LeakyReLU(0.2)(x)
   x = InstanceNormalization()(x)
   x = AdaIN()([x, w])
   # Adding noise
   x = ConvBlock(filter_num, 3)(x)
   x = AddNoise()([x, noise])
   x = LeakyReLU(0.2)(x)
   x = InstanceNormalization()(x)                    
   x = AdaIN()([x, w])
   return Model([input_tensor, w, noise], x, name=f'genblock_{res}x{res}')

这个网络遵循了图 3.5中描述的架构;SynthesisNetwork由一组自定义层 AdaInConvBlock 构成。

让我们继续看鉴别器网络。

TF 中的鉴别器

函数 D_basic 实现了 图 3.6 中描述的鉴别器。(github.com/NVlabs/stylegan/blob/1e0d5c781384ef12b50ef20a62fee5d78b38e88f/training/networks_stylegan.py#L562)。由于鉴别器由一组卷积层块组成,D_basic 有一个专用函数 block,根据输入张量大小构建块。函数的核心组件如下:

def block(x, res): # res = 2 … resolution_log2
   with tf.variable_scope('%dx%d' % (2**res, 2**res)):
       x = act(apply_bias(conv2d(x, fmaps=nf(res-1), kernel=3, gain=gain, use_wscale=use_wscale)))
       x = act(apply_bias(conv2d_downscale2d(blur(x), fmaps=nf(res-2), kernel=3, gain=gain, use_wscale=use_wscale, fused_scale=fused_scale)))
   return x

在上述代码中,block 函数处理通过结合卷积和下采样层创建鉴别器中的每个块。D_basic 的剩余逻辑很简单,因为它只是通过将一个块的输出作为下一个块的输入来链式连接一组卷积层块。

TF 中的模型训练逻辑

TF 实现的训练逻辑可以在 train_step 函数中找到。理解实现细节不应该是个难题,因为它们遵循了我们在 TF 模型训练 部分的描述。

总的来说,我们学习了如何在 TF 版本 2 中使用我们在本章中描述的 TF 构建块实现 StyleGAN。

要记住的事情

a. 无论实现的复杂性如何,任何 DL 模型训练实现都可以分解为三个组件(数据加载逻辑、模型定义和模型训练逻辑)。

在这个阶段,您应该了解 StyleGAN 仓库在每个框架中的结构。我们强烈建议您玩弄预训练模型以生成有趣的图像。如果掌握了 StyleGAN,那么跟随 StyleGAN2 (arxiv.org/abs/1912.04958)、StyleGAN3 (arxiv.org/abs/2106.12423) 和 HyperStyle (arxiv.org/abs/2111.15666) 的实现就会很容易。

总结

在本章中,我们探讨了 DL 的灵活性来自何处。DL 使用数学神经元网络来学习数据集中的隐藏模式。训练网络涉及根据训练集更新模型参数的迭代过程,并选择在验证集上表现最佳的模型,以期在测试集上实现最佳性能。

在模型训练中实现重复过程时,许多工程师和研究人员将常见的构建块整合到框架中。我们描述了两个最流行的框架:PyTorch 和 TF。这两个框架的结构方式相似,允许用户使用三个构建块设置模型训练:数据加载逻辑、模型定义和模型训练逻辑。作为本章的最后一个主题,我们分解了 StyleGAN,这是最流行的 GAN 实现之一,以了解这些构建块在实际中的使用方式。

由于深度学习需要大量数据进行成功训练,高效管理数据、模型实现以及各种训练结果对于任何项目的成功至关重要。在接下来的章节中,我们将介绍用于深度学习实验监控的有用工具。