精通-Azure-机器学习第二版-全-
精通 Azure 机器学习第二版(全)
原文:
annas-archive.org/md5/4810ad92d2f87002f854999a7a373fce译者:飞龙
前言
在过去十年中,机器学习(ML)已经从科学界的一个小众概念发展成为一个企业级工具集,可以用来改进业务流程和构建智能产品和服务。主要原因是全球生成数据的量持续增加,需要分布式系统、强大的算法和可扩展的云基础设施来计算洞察。这本书将帮助你提高你对 ML 概念的了解,找到适合你用例的正确模型,并为你提供运行机器学习模型和在 Azure 云中构建端到端 ML 管道的技能集。
本书从概述端到端 ML 项目的每一步骤以及如何选择适合不同 ML 任务的正确 Azure 服务开始。从那时起,它专注于 Azure Machine Learning 服务,并带你了解数据准备和特征工程的重要流程。随后,本书专注于满足不同需求的 ML 建模技术,包括使用自然语言处理(NLP)的高级特征提取技术、经典 ML 技术如集成学习,以及使用深度学习方法的优秀推荐引擎和高效计算机视觉模型的秘诀。此外,本书探讨了如何使用 Azure 自动化机器学习和 HyperDrive 训练、优化和调整模型,以及在 Azure 上的分布式训练集群上执行模型训练。最后,本书涵盖了将 ML 模型部署到不同的目标计算环境,如 Azure Machine Learning 集群、Azure Kubernetes 服务以及现场可编程门阵列(FPGA),以及使用 Azure DevOps 设置 MLOps 管道。
在本书结束时,你将具备从开始到结束运行一个精心设计的 ML 项目的坚实基础,并将掌握 Azure 中用于训练、部署和操作 ML 模型和管道的工具。
这本书面向的对象
本书是为那些希望使用 Microsoft Azure 云来管理他们的数据集和机器学习实验,并使用 MLOps 构建企业级 ML 架构的机器学习工程师、数据科学家和机器学习开发者所写。任何对 ML 主题感兴趣的读者都将学习到 ML 过程的重要步骤以及如何使用 Azure Machine Learning 来支持他们。本书将支持任何构建强大的 ML 云应用程序的人。建议读者具备基本的 Python 理解和 ML 知识。
这本书涵盖的内容
第一章,理解端到端机器学习过程,涵盖了机器学习的历史、应用机器学习的场景、必要的统计知识,以及运行自定义端到端机器学习项目所需的步骤和组件。其目的是让每位读者达到相同的基础水平。因此,对于非常了解机器学习的读者来说,某些部分可能是一些回顾,但仍然可能包含一些有用的实用技巧和指南。它还旨在成为本书的指南,其中机器学习过程中的每一步都将指向详细涵盖它们的章节。
第二章,在 Azure 中选择合适的机器学习服务,帮助我们了解和分类可用的 Azure 机器学习服务。我们将定义使用某些服务的场景,并得出结论,对于构建自定义机器学习模型,Azure 机器学习是最好的选择。从本章开始,我们将使用 Azure 机器学习服务中可用的工具来执行所有即将到来的机器学习过程中的任务。
第三章,准备 Azure 机器学习工作区,涵盖了 Azure 机器学习服务的设置以及使用该服务进行的一些初步机器学习培训。我们将学习如何在 Azure 机器学习中跟踪实验、绘制指标和创建机器学习运行的快照,同时进行机器学习训练实验。
第四章,数据摄取和管理数据集,涵盖了存储我们底层数据的可用 Azure 服务以及如何在 Azure 中设置它们。此外,我们将了解如何通过提取、转换和加载(ETL)过程手动或自动地将所需数据带到这些服务中,以及如何将其他 Azure 数据服务与 Azure 机器学习集成。最后,我们将介绍 Azure 机器学习中的数据存储和数据集的概念,以及如何在实验运行中使用它们。
第五章,执行数据分析与可视化,涵盖了探索和预处理机器学习数据集所需的步骤。我们将了解表格数据集和文件数据集之间的区别,并学习如何清理我们的数据集、关联特征,以及使用统计特性和领域知识来深入了解我们的数据集。利用我们所学的知识,我们将亲自动手处理一个真实数据集来应用我们的知识。最后,我们将简要了解一些流行的嵌入技术,如 PCA、LDA、t-SNE 和 UMAP。
第六章, 特征工程和标注,涵盖了在数据集中创建或调整特征以及为监督机器学习训练创建标签的重要过程。我们将了解改变特征的原因,并简要浏览各种可用的方法来创建、转换、提取和选择数据集中的特征,然后我们将使用这些方法在我们的实际数据集上。此外,我们将探讨为不同类型的数据集标注技术,并在 Azure 机器学习中的数据标注工具上进行实际操作。
第七章, 使用 NLP 进行高级特征提取,进一步探讨了从文本和分类数据中提取特征的问题——这是用户在训练机器学习模型时经常遇到的问题。本章将描述自然语言处理(NLP)特征提取的基础。这将帮助我们使用包括 n-gram、词袋模型、TF-IDF、Word2Vec 等技术在分类和文本数据中创建语义嵌入。
第八章, Azure 机器学习管道,介绍了如何使用 Azure 机器学习管道将所学知识应用于自动化的预处理和训练管道。我们将学习如何将代码拆分为模块化管道步骤,以及如何通过端点和调度参数化和触发管道。最后,我们将构建几个训练管道,并学习如何将它们集成到其他 Azure 服务中。
第九章, 使用 Azure 机器学习构建 ML 模型,教你如何使用集成技术构建 Azure 中的传统机器学习模型。本章重点介绍基于决策树的集成学习方法,使用 Azure 机器学习中的 LightGBM 实现流行的最先进提升和袋装技术。这将帮助你将袋装和提升的概念应用于机器学习模型。
第十章, 在 Azure 上训练深度神经网络,介绍了如何使用深度学习来训练更复杂的参数化模型,以在大数据集上实现更好的泛化。我们将简要概述哪些情况下深度学习可以很好地应用,以及它与更传统的机器学习方法的区别。之后,我们将讨论合理的实用指南,最终在 Azure 机器学习上使用 Keras 训练卷积神经网络(CNN)。
第十一章, 超参数调整和自动化机器学习,涵盖了机器学习训练过程的优化以及如何自动化它以避免人为错误。这些调整技巧将帮助你更快、更有效地训练模型。因此,我们将探讨超参数调整(在 Azure 机器学习中也称为HyperDrive),这是一种优化机器学习模型所有外部参数的标准技术。通过评估不同的超参数调整采样技术,如随机采样、网格采样和贝叶斯优化,你将学习如何有效地管理运行时间和模型性能之间的权衡。然后,我们将从超参数优化推广到使用Azure 自动化机器学习自动化完整的端到端机器学习训练过程。
第十二章, Azure 上的分布式机器学习,探讨了用于在 GPU 上并行高效训练机器学习模型的分布式和并行计算算法和框架。本章的目标是在 Azure 中构建一个环境,你可以通过向你的训练环境添加更多机器来加速经典机器学习和深度学习模型的训练过程,从而扩展集群。
第十三章, 在 Azure 中构建推荐引擎,深入探讨了传统和现代推荐引擎,这些推荐引擎通常结合了前面章节中介绍的技术和技巧。我们将快速查看不同类型的推荐引擎,每种类型需要哪些数据,以及可以使用这些不同方法推荐什么,例如基于内容的推荐和基于评分的推荐引擎。我们将结合这两种技术构建一个单一的混合推荐器,并了解现代推荐引擎的最新技术。
第十四章, 模型部署、端点和操作,最后介绍了如何将我们的机器学习模型部署到生产环境中,无论是部署到批处理集群进行离线评分,还是作为端点进行在线评分。为了实现这一点,我们将打包模型和执行运行时,在模型注册表中注册它们,并将它们部署到执行环境中。我们只需几行代码就可以将 Azure 机器学习中的模型自动部署到Azure Kubernetes 服务。最后,你将学习如何使用现成的自定义指标来监控你的目标环境。
第十五章,模型互操作性、硬件优化和集成,介绍了使用开放神经网络交换(ONNX)标准化部署模型格式的方法,解释了现场可编程门阵列(FPGA)是什么,以及如何在 Azure 中将它们用作部署目标。此外,我们将学习如何将 Azure 人工智能与其他 Microsoft 服务(如Azure IoT Edge和Power BI)集成。在这里,我们将了解 FPGA 和 GPU 在性能、成本和效率方面的基本差异,并在 Power BI 中进行实际操作,以集成我们之前部署的端点之一。
第十六章,使用 MLOps 将模型投入生产,最终介绍了如何将数据摄取、数据准备、我们的机器学习训练和部署管道以及任何所需的脚本整合为一个端到端操作。这包括创建环境;启动、停止和扩展集群;提交实验;执行参数优化;以及在 Kubernetes 上部署完整的评分服务。我们将重用之前应用的所有概念,以构建一个版本控制的、可重复的、自动化的机器学习训练和部署过程,作为 Azure DevOps 中的持续集成/持续部署(CI/CD)管道。
第十七章,为成功的人工智能之旅做准备,通过总结我们在整本书中学到的重大概念,并强调在执行人工智能时真正重要的事情来结束全书。我们重申了干净的基础设施、监控和自动化的重要性,并讨论了人工智能和基于云的服务不断变化的特点。最后,我们涵盖了本书中多次提及的一个重要话题,即数据处理中的伦理问题。我们将讨论您拥有公平且可解释的人工智能模型的责任,以及 Azure 人工智能和开源工具如何帮助您实现这一目标。
为了充分利用这本书
本书需要使用 Azure 服务,因此需要一个 Azure 订阅。您可以通过注册页面azure.microsoft.com/en-us/free/免费创建 Azure 账户,并在 30 天内获得 200 美元的信用额度。
要运行作者代码,您可以使用 Azure Machine Learning 工作区中的计算实例(通常是Standard_DS3_v2虚拟机),这为您提供了访问 Jupyter 环境和所有预安装的基本库,或者您可以在自己的本地机器上运行它。要这样做,您需要一个安装了 Jupyter 包的 Python 运行时和一些额外的库,这些库将在每一章的技术要求中提到。我们在写作时使用 Python 版本 3.8 和 Azure ML Python SDK 版本 1.34.0 测试了所有代码。如果您想使用不同的设置,请确保检查 Azure ML Python SDK 支持的 Python 版本(pypi.org/project/azureml-sdk/)。
如果您正在使用这本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。
最后,为了最大限度地利用本书,您应该有使用 Python 进行编程的经验,并对流行的 ML 和数据操作库(如 TensorFlow、Keras、scikit-learn 和 pandas)有基本的了解。
下载示例代码文件
您可以从 GitHub(github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition)下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
下载彩色图像
我们还提供了一份包含本书中使用的截图和图表的彩色图像的 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781803232416_ColorImages.pdf。
使用的约定
在本书中使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“score.py脚本是一个部署文件,需要包含init()和run(batch)方法。”
代码块设置如下:
# increase display of all columns of rows for pandas datasets
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
# create pandas dataframe
raw_df = tabdf.to_pandas_dataframe()
raw_df.head()
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
df = df.drop(['Postcode'],axis=1)
df.head()
任何命令行输入或输出都按以下方式编写:
$ pip install azure-cognitiveservices-personalizer
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“我们可以看到数据集作为名为titanic的输入传递到预处理步骤。”
小贴士或重要注意事项
看起来是这样的。
联系我们
我们欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将非常感谢。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,我们将非常感谢。请通过 copyright@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《精通 Azure 机器学习》,我们很乐意听到您的想法!请点击此处直接访问亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都非常重要,并将帮助我们确保我们提供高质量的内容。
第一部分:Azure 机器学习简介
在本节中,我们将了解机器学习(ML)的历史、应用 ML 的场景、所需的统计知识,以及运行自定义端到端 ML 项目所需的步骤和组件。我们将探讨可用的 Azure ML 服务,并了解它们最适合的场景。最后,我们将介绍 Azure 机器学习,这是我们将在本书的其余部分使用的主要服务。我们将了解如何部署此服务,并使用它来在云中运行我们的第一个 ML 实验。
本节包括以下章节:
-
第一章, 理解端到端机器学习过程
-
第二章, 在 Azure 中选择合适的机器学习服务
-
第三章, 准备 Azure 机器学习工作区
第一章:第一章:理解端到端机器学习流程
欢迎来到《精通 Azure 机器学习》的第二版。在本章的第一章,我们希望让你了解哪些类型的问题需要使用机器学习(ML),完整的机器学习过程是如何展开的,以及在这个广阔领域导航所需的哪些知识。你可以将其视为机器学习的介绍和本书的概述,其中对于大多数主题,我们将提供对后续章节的参考,以便你能够轻松地在书中找到自己的位置。
在第一部分,我们将问自己什么是机器学习,我们应该在何时使用它,以及它从何而来。此外,我们将反思机器学习只是编程的另一种形式。
在第二部分,我们将奠定你处理数据所需的数学基础,并且我们将理解你工作的数据可能无法完全信赖。此外,我们将探讨不同类别的机器学习算法,它们的定义,以及我们如何定义训练模型的性能。
最后,在第三部分,我们将探讨机器学习项目的端到端流程。我们将了解从哪里获取数据,如何预处理数据,如何选择合适的模型,以及如何将此模型部署到生产环境中。这也会让我们接触到机器学习操作的话题,通常称为MLOps。
在本章中,我们将涵盖以下主题:
-
理解机器学习背后的理念
-
理解统计分析和机器学习建模的数学基础
-
发现端到端机器学习流程
理解机器学习背后的理念
术语人工智能(AI)和部分地机器学习(ML)在当今世界无处不在。然而,在AI这个术语下找到的很多东西往往不过是一个容器化的机器学习解决方案,而且更糟糕的是,机器学习有时被不必要地用于解决极其简单的问题。
因此,在本节的第一部分,让我们了解机器学习试图解决的问题类别,在哪些场景下使用机器学习,以及在何时不应使用它。
需要机器学习的问题和场景
如果你寻找机器学习的定义,你通常会找到一个这样的描述:它是使用数据研究自我改进的机器算法的学科。机器学习基本上被描述为我们试图进化的算法,这反过来又可以被视为一个复杂的数学函数。
今天的任何计算机过程都遵循简单的输入-处理-输出(IPO)模型结构。我们定义允许的输入,我们定义一个处理这些输入的过程,并通过该过程将显示的结果类型定义输出。一个简单的例子是一个文字处理应用程序,其中每个按键都会在屏幕上显示为输出字母。可能并行运行的一个完全不同的过程可能有一个基于时间的触发器,定期将文本文件存储到硬盘上。
所有这些过程或算法有一个共同点——它们都是某人使用高级编程语言手动编写的。当有人在文字处理应用程序中按下一个字母时,需要执行哪些操作是显而易见的。因此,我们可以轻松构建一个过程,其中我们实现哪些输入值应该创建哪些输出值。
现在,让我们看看一个更复杂的问题。想象一下,我们有一张狗的图片,并希望一个应用程序能够说:这是一只狗。这听起来很简单,因为我们知道输入狗的图片和输出值狗。不幸的是,我们的大脑(我们自己的机器)比我们构建的机器优越得多,尤其是在模式识别方面。对于计算机来说,一张图片只是一个由
像素组成的正方形,每个像素包含由 8 位或 10 位值定义的三个颜色通道。因此,对于计算机来说,图像只是一堆由向量组成的像素,本质上是一堆数字。
我们可以手动开始编写一个算法,可能聚类像素组,寻找边缘和感兴趣点,最终,经过大量努力,我们可能成功拥有一个在图片中找到狗的算法。但那时我们得到了一张猫的图片。
到现在为止,你应该已经清楚我们可能会遇到问题。因此,让我们定义一个机器学习解决的问题,如下:
以编程方式构建所需解决方案的期望算法要么非常耗时,要么完全不切实际,或者根本不可能。
根据这个描述,我们可以确定使用机器学习的良好场景,无论是寻找图像和视频中的对象,还是理解声音并从音频文件中提取其意图。我们将在本章(以及本书的其余部分)中进一步了解构建机器学习解决方案涉及的内容,但简单来说,让我们承认构建机器学习模型也是一个耗时的事情。
在这种情况下,如果我们有机会避免使用机器学习,那么这应该是最重要的。这或许是一个显而易见的陈述,但正如我们(作者)可以证明的那样,对很多人来说并非如此。我们见过一些使用机器学习实现的项目,如果给定一些输入向量,输出可以通过简单的if语句组合来定义。在这种情况下,可以通过几百行代码获得解决方案。相反,对机器学习算法进行数月的训练和测试,耗费了大量时间和资源。
例如,一家公司可能想要预测其零售店员工犯下的欺诈(被盗资金)。你可能听说过预测欺诈是机器学习的典型场景。在这里,使用机器学习并不是必要的,因为公司已经知道影响因素(收银员开放的时间长度、退货收据上的错误代码等),因此希望在发生某些因素组合时得到警报。既然他们已经知道这些因素,他们可以直接编写代码并完成。但这个场景告诉我们关于机器学习什么?
到目前为止,我们将机器学习视为解决一个本质上难以编码的问题的解决方案。从前面提到的场景来看,你可能理解了机器学习可以解决的另一个方面或另一类问题。因此,让我们添加第二个问题描述,如下:
构建一个所需解决方案的期望算法是不切实际的,因为影响所需输出结果的因素只有部分已知或完全未知。
看到这个问题,你现在可能已经理解为什么机器学习如此依赖于统计学领域,因为通过应用统计学,我们可以了解数据点是如何相互影响的,因此我们可能能够解决这样的问题。同时,我们可以构建一个能够找到并预测所需结果的算法。
在之前提到的检测欺诈的场景中,仍然使用机器学习可能是谨慎的,因为它可能能够找到没有人考虑过的影响因素的组合。但如果这不是你的目标——正如在这个案例中那样——你不应该将机器学习用于那些可以轻易用代码编写的事情。
既然我们已经讨论了一些机器学习解决的问题,并查看了一些机器学习的场景,让我们来看看机器学习是如何产生的。
机器学习的历史
要全面理解机器学习,我们首先必须了解它的来源。因此,让我们深入了解机器学习的历史。与历史上的所有事件一样,不同的潮流同时发生,为整个画面增添了碎片。现在,我们将查看几个重要的支柱,这些支柱孕育了我们今天所知道的机器学习的理念。
神经科学的学习
一位名叫唐纳德·O·赫布(Donald O. Hebb)的神经心理学家于 1949 年出版了一本名为《行为组织》(The Organization of Behavior)的书。在这本书中,他描述了他关于我们大脑中的神经元(神经细胞)如何工作以及它们如何贡献于我们所理解的学习的理论。这个理论被称为赫布学习(Hebbian learning),并提出了以下命题:
当细胞 A 的轴突足够接近以兴奋细胞 B,并且反复或持续地参与其放电时,一个或两个细胞中会发生一些生长过程或代谢变化,使得 A 作为放电 B 的细胞之一,其效率得到提高。
这基本上描述了一个过程,其中一个细胞反复激发另一个细胞(启动细胞),甚至接收细胞可能通过一个隐藏的过程被改变。这个过程就是我们所说的学习。
为了更直观地理解这一点,让我们看一下神经元的生物结构,如下所示:

图 1.1 – 生物神经网络中的神经元
这里可视化的是什么?首先,在左边,我们看到细胞的主体及其细胞核。主体通过与其它神经元相连的树突接收输入信号。此外,还有一个从主体伸出的大轴突,它通过一系列施万细胞将主体与所谓的轴突末端连接起来,而轴突末端反过来又连接到其他神经元。
用一些创意来看这个结构,它确实类似于一个函数或算法。我们有来自外部神经元的输入信号,我们有这些信号的一些隐藏过程,我们有一个以轴突末端形式存在的输出,它将结果连接到其他神经元,从而再次连接到其他过程。
又过了十年,才有人意识到这个联系。
计算机科学的学习经验
在计算机科学背景下谈论机器学习的历史,如果不提及现代机器之父之一艾伦·图灵,那就很难。在 1950 年发表的一篇名为《计算机与智能》的论文中,图灵定义了一个被称为模仿游戏(后来称为图灵测试)的测试,用以评估机器是否表现出与人类无法区分的行为。这个测试有多个迭代和变体,但本质上,其想法是,在对话中,一个人在任何时候都不会感觉到他们不是在与人类交谈。
当然,这个测试是有缺陷的,因为有一些方法可以在不真正智能的情况下给出相对智能的答案。如果你想了解更多关于这个的信息,可以看看约瑟夫·魏森鲍姆建造的ELIZA,它通过了图灵测试。
尽管如此,这篇论文引发了关于人工智能可能是什么以及机器学习意味着什么的第一次讨论。
在这些激动人心的时代生活,亚瑟·塞缪尔,当时在国际商业机器公司(IBM)工作的研究人员,开始开发一个能够在国际象棋游戏中做出正确决策的计算机程序。在每一步中,他让程序评估一个评分函数,试图衡量每个可用步骤的获胜机会。由于当时可用的资源有限,计算所有可能的移动组合直到游戏结束是不切实际的。
这第一步导致了所谓的最小-最大算法及其伴随的搜索树的定义,这通常可以用于任何两人对抗游戏。后来,添加了alpha-beta 剪枝算法,以自动从那些没有比已评估的结果更好的决策中剪枝。
我们在谈论 Arthur Samuel,因为正是他提出了“机器学习”这个名称,并将其定义为如下:
研究领域赋予计算机在不被明确编程的情况下学习的能力。
结合为训练机器构建评估函数的第一个想法和 Donald O. Hebb 在神经科学领域的研究,Cornell Aeronautical Laboratory 的研究员 Frank Rosenblatt 发明了一种新的线性分类器,他称之为感知器。尽管他在将这个感知器构建成硬件方面的进展相对短暂,并且没有达到其潜力,但它的原始定义如今是人工神经网络(ANN)中每个神经元的基石。
因此,现在让我们更深入地了解 ANN 是如何工作的,以及我们可以从它们中推断出关于 ML 算法内部工作原理的什么。
通过 ANN 的例子理解 ML 的内部工作原理
我们今天所知的 ANN 由以下两个主要组件定义,其中一个我们已经了解过:
-
神经网络:系统的基本结构。感知器基本上是一个只有一个神经元的 NN。到目前为止,这种结构已经以多种形式出现,通常涉及数百个神经元的隐藏层,在深度神经网络(DNNs)的情况下。
-
反向传播函数:系统学习和进化的规则。20 世纪 70 年代的一个想法,通过 1986 年由 D. Rumelhart、Geoffrey E. Hinton 和 Ronald J. Williams 发表的名为《通过反向传播错误学习表示》的论文而受到重视。
要理解这两个组件以及它们如何协同工作,让我们更深入地了解它们。
神经网络
首先,让我们了解单个神经元是如何工作的,这非常接近 Rosenblatt 定义的感知器理念。以下图表显示了这种人工神经元的内部工作原理:

图 1.2 – ANN 中的神经元
我们可以清楚地看到它与真实神经元的相似之处。我们从称为
的连接神经元那里获得输入。每个输入都对应一个权重
,然后在神经元本身中,它们都被加起来,包括一个偏差
。这通常被称为净输入函数。
作为最终操作,一个所谓的激活函数
应用于这个网络输入,决定神经元的输出信号应该如何看起来。这个函数必须是连续且可微分的,并且通常应该在[0:1]或[-1:1]的范围内创建结果,以保持结果缩放。此外,这个函数可以是线性的或非线性的,尽管使用线性激活函数有其缺点,如以下所述:
-
您无法通过线性函数系统学习数据中呈现的非线性关系。
-
由只有线性激活函数的节点组成的分层网络可以被简化为只有一个线性激活函数的一层节点,从而使网络变得过时。
-
您不能使用线性激活函数进行反向传播,因为这需要计算该函数的导数,我们将在下一节讨论。
常用的激活函数包括sigmoid、双曲正切(tanh)、整流线性单元(ReLU)和softmax。牢记这一点,让我们来看看我们如何连接神经元以实现人工神经网络(ANN)。一个整个网络通常由三种类型的层定义,如下所述:
-
输入层:由接受网络单个输入信号的神经元组成(不是加权求和)。它们的权重可能根据应用是常数或随机化的。
-
隐藏层:由我们之前描述的神经元类型组成。它们由一个激活函数定义,并给输入信号的加权求和分配权重。在深度神经网络(DNNs)中,这些层通常代表特定的转换步骤。
-
输出层:由执行数据最终转换的神经元组成。它们可以像隐藏层中的神经元一样行为,但不必如此。
这些共同构成了典型的 ANN,如下面的图所示:

图 1.3 – 具有一个隐藏层的人工神经网络
因此,我们构建了一个通用的结构,它可以接收一些输入,通过不同层的权重和激活函数实现某种形式的数学函数,最终,希望显示正确的输出。这个过程通常被称为前向传播。当然,这只能显示通过网络的输入所发生的事情。以下问题仍然存在:它最初是如何学习所需函数的?下一节将回答这个问题。
反向传播函数
到现在为止,你应该已经想到一个问题:我们如何定义正确的输出?为了有一种方式来改变网络的行为,这主要归结为改变系统中权重的值,我们不需要一种量化系统所犯错误的方法吗?
因此,我们需要一个描述错误或损失的函数,称为 损失函数 或 误差函数。你可能甚至听说过另一个名字——代价函数。让我们接下来定义它们。
损失函数与代价函数
损失函数(误差函数)计算单个训练示例的误差。另一方面,代价函数平均整个训练数据集的所有损失函数结果。
这是这些术语的正确定义,但它们通常可以互换使用。只需记住,我们正在使用某种形式的度量来衡量我们犯的错误或与正确结果的距离。
在经典的反向传播和其他机器学习场景中,正确
和计算得到的
之间的 均方误差 (MSE) 用于定义操作的错误或损失。显然的目标是现在最小化这个错误。因此,实际要执行的任务是在 n-维空间中找到这个函数的总最小值。
要做到这一点,我们使用通常被称为 优化器 的东西,定义如下。
优化器(目标函数)
优化器是一个实现达到最小化代价函数目标特定方式的函数。
其中一种优化器是一个称为 梯度下降 的迭代过程。其思想在以下屏幕截图中进行可视化:

图 1.4 – 仅受一个输入影响的梯度下降损失函数(左:寻找全局最小值,右:陷入局部最小值)
在梯度下降中,我们通过采取合理足够大的步长(通常由 学习率 定义),试图导航 n-维损失函数,目标是找到全局最小值,同时避免陷入局部最小值。
记住这一点,不深入细节,让我们通过回顾反向传播算法在神经网络中执行的步骤来完成这个想法。这些步骤如下:
-
将一对
通过网络(前向传播)。 -
计算预期
和计算得到的
之间的损失。 -
使用数学链式法则计算所有函数和权重在所有层中的所有导数。
-
从网络的后面开始更新所有权重到前面,使用优化器定义的略微改变的权重。
-
重复执行,直到达到收敛(权重不再接收任何有意义的更新)。
简而言之,这就是人工神经网络(ANN)是如何学习的。请注意,在第一步中不断改变对是至关重要的,否则,你可能会使网络过度记住你不断展示给它的这些几个对。我们将在本章后面讨论过拟合和欠拟合的现象。
作为本节的最后一步,现在让我们将到目前为止关于机器学习所学到的东西以及这对未来构建软件解决方案意味着什么结合起来。
机器学习和软件 2.0
到目前为止,我们所学的似乎表明机器学习是由一个具有各种旋钮和杠杆(设置和值)的基础结构定义的,这些旋钮和杠杆可以改变。在人工神经网络(ANN)的情况下,这将是网络本身的结构以及我们可以设置的一些权重、偏置和激活函数。
与这个基础结构相伴的是某种规则或函数,用于在学习过程中如何将这些旋钮和杠杆转换。在人工神经网络(ANN)的情况下,这是通过反向传播函数定义的,该函数结合了损失函数、优化器和一些数学。
2017 年,特斯拉人工智能部门的首席技术官(CTO)安德烈·卡帕西(Andrej Karpathy)提出,上述想法可能是编程的另一种方式,他称之为软件 2.0(karpathy.medium.com/software-2-0-a64152b37c35)。
到目前为止,编写软件是关于通过定义它必须遵循的特定命令,向机器精确地解释它必须做什么以及它必须产生什么结果。在这种经典的软件开发范例中,我们通过代码定义算法,让数据通过它运行,通常是用一种相对可读的语言编写的。
而不是这样做,另一个想法可能是定义一个由基础结构、进化这种结构的方式以及它必须处理的数据类型组成的程序。在这种情况下,我们得到的是对人类非常不友好的东西(例如具有权重的 ANN),但对于机器来说可能更容易理解。
因此,我们在本节的结尾留下安德烈想要传达的思想。也许机器学习只是编程机器的另一种形式。
在记住所有这些的同时,现在让我们谈谈数学。
理解统计分析和机器学习建模的数学基础
看看我们到目前为止学到的东西,很明显,机器学习需要充分理解数学。我们已经遇到了多个我们必须处理的数学函数。想想神经元的激活函数和训练的优化器以及损失函数。除此之外,我们还没有谈到我们新编程范式的第二个方面——数据!
为了选择正确的机器学习算法并为损失函数推导出良好的指标,我们必须分析我们所处理的数据点。此外,我们还需要将数据点与我们所工作的领域联系起来。因此,在定义数据科学家的角色时,你经常会看到这样的视觉元素:

图 1.5 – 数据科学家所需条件
在本节中,我们将专注于图 1.5中提到的统计研究。我们将了解为什么我们需要统计学,以及我们可以从给定数据集中推导出哪些基本信息,学习什么是偏差以及如何避免它,从数学上对可能的机器学习算法进行分类,最后讨论我们如何选择有用的指标来定义我们训练模型的性能。
机器学习中的统计学案例
正如我们所看到的,我们需要统计学来清理和分析我们的给定数据。因此,让我们先问一下:我们从“统计学”这个术语中理解了什么?
统计学是收集和分析大量数值数据构成的代表性样本的科学,目的是推断底层人群的统计分布。
这样的一个典型例子就是在竞选期间或投票站关闭后不久预测选举结果。在那个时间点,我们不知道整个人口的精确结果,但我们可以获取一个样本,有时也称为观察值。我们通过让人们填写问卷来获取这些信息。然后,基于这个子集,我们通过应用统计方法对整个群体做出合理的预测。
我们了解到,在机器学习中,我们试图让机器找出适合我们问题的数学函数,例如这个:

回想一下我们的人工神经网络(ANN),
将是一个输入向量,而
将是产生的输出向量。在机器学习的术语中,它们有另一个名称,如下所示。
特征和标签
输入向量 x 的一个元素被称为特征;完整的输出向量被称为标签。通常,我们只处理一个一维标签。
现在,为了将这一点结合起来,在训练机器学习模型时,我们通常只有给定世界的样本,并且正如你在处理现实世界中的任何其他样本或子集时一样,你希望选择高度代表性的特征和底层人群的样本。
那么,这究竟意味着什么?让我们来想一个例子。想象一下,你想要训练一辆小型机器人汽车能够自动通过隧道。首先,我们需要考虑在这个场景中我们的特征和标签是什么。作为特征,我们可能需要一些能够测量汽车每个方向上与隧道边缘距离的东西,因为我们可能不希望汽车撞到隧道的侧面。假设我们在车辆的前方、侧面和后方都安装了一些红外传感器。那么,我们程序的输出可能将控制车辆的转向和速度,这些就是我们的标签。
既然如此,作为下一步,我们应该考虑车辆可能遇到的各种场景。这可能是一个简单的场景,车辆直直地停在隧道中,或者它可能是一个糟糕的场景,车辆几乎卡在角落里,从那个点开始,隧道向左或向右延伸。在所有这些情况下,我们读取红外传感器的值,然后进行更复杂的任务,即做出有根据的猜测,关于转向应该如何改变,以及电机应该如何运行。最终,我们得到一系列示例情况和相应的行动,这些将成为我们的训练数据集。然后,我们可以使用这个数据集来训练一个人工神经网络(ANN),这样小汽车就可以学会如何跟随隧道行驶。
如果你有机会,尝试进行这项训练。如果你挑选了非常好的例子,你将理解机器学习的全部力量,因为你很可能会看到一些令人兴奋的东西,我可以证实这一点。在我的设置中,尽管我们从未有过一个样本,我们会指示车辆倒车,但机器训练出的最优函数的值表明车辆学会了这样做。
在这样的例子中,我们会从头开始做所有的事情,并希望我们自己能够抽取有代表性的样本。在大多数情况下,你将遇到的,数据集已经存在,你需要弄清楚它是否具有代表性,或者我们需要引入额外的数据来实现最佳的训练结果。
因此,让我们看看一些你应该熟悉的统计特性。
统计学基础
现在我们明白,我们需要能够分析单个特征的统计特性,推导出它们的分布,并分析它们与数据集中其他特征和标签的关系。
让我们从单个特征的性质及其分布开始。以下所有操作都需要数值数据。这意味着如果你处理的是分类数据或类似媒体文件这样的东西,你需要将它们转换成某种数值表示形式,才能得到这样的结果。
以下截图显示了您所追求的主要统计特性、它们的重要性以及如何计算它们:

图 1.6 – 主要统计性质列表
从现在开始,我们可以合理地假设基础随机过程遵循正态分布。请注意,这并不一定成立,因此你应该熟悉其他分布(参见www.itl.nist.gov/div898/handbook/eda/section3/eda36.htm)。
下面的截图展示了一个标准正态分布的直观表示:

图 1.7 – 标准正态分布及其性质
现在,这种正态分布的强度在于,基于均值
和标准差
,我们可以对样本落在某个范围内的概率做出假设。如图 1.7 所示,一个值距离均值的距离为 1
的概率约为 68.27%,距离为
的概率为 95.45%,距离为
的概率为 99.73%。基于此,我们可以提出如下问题:
距离平均值 5
有多大的可能性找到这样的值?
通过这样的问题,我们可以开始评估我们数据中看到的是分布的统计异常、一个简单的错误值,还是我们的怀疑分布是不正确的。这是通过称为假设检验的过程来完成的,定义如下。
假设检验(定义)
这是一种测试所谓的零假设
是否为假的方法,通常指的是当前怀疑的分布。这意味着我们遇到的罕见观察结果是纯粹的偶然。如果概率低于预定义的显著性水平(通常高于
/低于 5%),则拒绝该假设,转而采用备择假设
。因此,备择假设假定我们所观察到的结果是由于一个在初始分布中没有考虑到的真实效应。
我们不会深入讨论如何正确执行这项测试的细节,但我们敦促你彻底熟悉这个过程。
我们将要讨论的是在这个过程中可能犯的错误类型,如下面的截图所示:

图 1.8 – 一类错误和二类错误
我们将 图 1.8 中所见的错误定义为如下:
-
一类错误:这表示我们拒绝了假设
和基础分布,尽管它是正确的。这也被称为假阳性结果或α 错误。 -
第二类错误:这表示我们没有拒绝假设
和其背后的分布,尽管
是正确的。这种错误也被称为假阴性结果或贝塔 错误。
你可能之前听说过假阳性这个术语。它通常出现在你进行医学检查时。假阳性表示你在测试中得到了阳性结果,尽管你并没有你正在测试的疾病。由于医学检查也是一个随机过程,就像我们世界中的几乎所有其他事物一样,这个术语在这个场景中被正确使用。
在本节的最后,当我们谈到机器学习模型训练中的错误和度量时,我们将回到这些定义。作为最后一步,让我们讨论特征之间的关系以及特征和标签之间的关系。这种关系被称为相关性。
有多种方法可以计算两个向量
和
之间的相关性,但它们共同的特点是,它们的结果将落在 [-1,1] 的范围内。这个操作的结果可以大致分为以下三个类别:
-
负相关:结果偏向于 -1。当向量
的值上升时,向量
的值下降,反之亦然。 -
不相关:结果偏向于 0。向量
和
之间没有真正的交互作用。 -
正相关:结果偏向于 1。当向量
的值上升时,向量
的值也上升,反之亦然。
通过这种方式,我们可以了解数据点之间的关系,但请注意因果和相关性之间的区别,如下所述。
因果关系与相关性
即使两个向量相互关联,这并不意味着其中一个向量是另一个的原因——它仅仅意味着其中一个影响了另一个。这不是因果关系,因为我们可能看不到完整的图景和每一个影响因素。
我们迄今为止讨论的数学理论应该为你提供一个良好的基础来构建。在下一节中,我们将快速查看在采样过程中可能犯的错误类型,通常被称为数据的偏差。
理解偏差
在任何采样阶段和数据处理过程中,很容易引入所谓的偏差。通常,这会影响采样质量,因此对我们想要拟合数据的任何机器学习模型都有重大影响。
一个例子就是我们刚才讨论的因果关系与相关性。在没有因果关系的地方看到因果关系,可能会对你的数据处理方式产生后果。接下来展示的是影响数据的其他显著偏差:
-
选择偏差:这种偏差发生在样本不是真实数据分布的代表性样本时。这是在随机化没有正确进行或只选择某个特定子群体进行研究时的情况——例如,当关于城市规划的调查问卷只发放给城市一半地区的居民时。
-
资助偏差:这种偏差应该非常为人所知,发生在一项研究或数据项目由赞助商资助,因此结果将倾向于资助方的利益。
-
报告偏差:这种偏差发生在由于人们倾向于低估某些结果,导致数据集中只代表了一部分结果。这里给出了一些例子:当你报告恶劣天气事件但不报告晴天时;当你为产品写负面评论但不写正面评论时;当你只了解用你自己的语言或来自你自己的地区的结果,但不了解其他地区的结果时。
-
观察者偏差/确认偏差:这种偏差发生在某人倾向于支持或证实他们自己的信念和价值观的结果。通常,这会导致忽视相反的信息,不遵循已同意的指南,或使用支持现有先入为主的观点的模糊研究。这里危险的部分是,这可能是无意识的。
-
排除偏差:这种偏差发生在预处理过程中,你移除了你认为无关但实际并非无关的数据点。这包括移除空值、异常值或其他特殊数据点。这种移除可能会导致关于潜在现实分布的准确度下降。
-
自动化偏差:这种偏差发生在你更倾向于来自自动化系统的结果,而不是来自人类的信息,即使它们是正确的。
-
过度泛化偏差:这种偏差发生在你将数据集的一个属性投射到整个群体上。一个例子是,你会假设所有猫都有灰色毛发,因为在你的大型数据集中,这是真的。
-
群体归因偏差:这种偏差发生在由于该群体中少数个体的行为,而将刻板印象作为属性添加到整个群体中。
-
幸存者偏差:这种偏差发生在你专注于成功的例子,而完全忽略失败的情况。一个例子是,你在研究你公司的竞争时,却忽略了所有失败、合并或破产的公司。
这个列表应该能让你对在收集和处理数据时可能出现的各种问题有一个很好的理解。我们只能敦促你进一步阅读这个主题,同时遵循以下指南。
处理数据偏差的指导
当使用现有数据集时,找出它们被获取的情境,以便能够判断它们的质量。在单独或团队处理数据时,明确界定如何定义数据和如何处理特定情况,并始终反思你是否是在根据自己的先入之见做出假设。
为了巩固你对事物——大多数时候——并不像它们看起来那样的理解,看看被称为辛普森悖论及其相应的加州大学伯克利分校(UC)案例(corysimon.github.io/articles/simpsons-paradox/)。
现在我们已经很好地理解了在处理数据时需要注意的事项,让我们回到机器学习的基本概念。
机器学习算法的分类
在本章的第一节中,我们简要了解了人工神经网络(ANNs)。它们在以下方面很特别,即它们可以用于所谓的监督或无监督训练设置。为了理解这里的含义,让我们定义当前三种主要的机器学习算法类型,如下所示:
-
监督学习:在监督学习中,模型是用所谓的标记数据集训练的。这意味着除了知道所需算法的输入外,我们还知道所需的输出。这种学习分为两个问题组——即分类问题和回归问题。分类使用离散结果,输出是一个类别或组,而回归使用连续结果,输出将是某个特定值。分类的例子包括在货币交易中识别欺诈或对图像进行目标检测。回归的例子包括预测房价或股市或预测人口增长。重要的是要理解这种学习需要标签,这通常会导致标记整个数据集的繁琐任务。
-
无监督学习:在无监督学习中,模型是在未标记的数据上训练的。这基本上是一种自我组织的学习,用于在数据中寻找模式,被称为聚类。这种方法的例子包括在收件箱中过滤垃圾邮件或推荐某人可能喜欢观看或购买的电影或服装。通常,学习算法在需要直接处理数据的实时场景中使用。这种类型学习的优点是我们不需要标记数据集。
-
强化学习:在强化学习中,算法通过对自己所处环境的反应来学习。这种想法来源于我们作为人类在成长过程中学习的方式。我们采取了一定的行动,该行动的结果要么是好的,要么是坏的,要么介于两者之间。然后我们可能会收到某种奖励,也可能不会。另一个类似的例子是训练狗的行为方式。技术上,这是通过所谓的 代理 实现的,该代理由 策略图 引导,决定在特定状态下采取行动的概率。对于环境本身,我们定义一个所谓的 状态值函数,它返回特定状态的价值。这种类型学习的良好例子包括训练机器人的导航控制或游戏的 AI 对手。
以下图表概述了所讨论的机器学习类型及其在这些领域中使用的相应算法:

图 1.9 – 机器学习算法类型
许多显著的机器学习算法的详细概述可以在 scikit-learn 网页上找到 (scikit-learn.org/stable/),这是主要的 Python 机器学习库之一。
现在我们已经了解了我们可以执行的训练类型,让我们简要地看看从训练运行中获得的结果类型以及如何解释它们。
分析模型训练中的错误和结果质量
正如我们在本章第一节的讨论中提到的,我们需要一个损失函数,我们可以通过最小化它来优化我们的训练结果。通常,这通过数学中所谓的度量来定义。在此处,我们需要区分用于定义损失函数并因此用于优化器训练模型的度量,以及可以计算出来以提供关于训练模型性能的额外提示的度量。在本节中,我们将探讨这两种类型。
正如我们在查看机器学习算法类型时看到的,我们可能处理由连续数据表示的输出(回归),或者我们可能处理由离散数据表示的输出(分类)。
在回归中,最突出的损失函数是 均方误差 (MSE) 和 均方根误差 (RMSE)。想象一下,你试图为线性回归中的一组样本确定拟合线。该线与二维空间中样本点之间的距离是你的误差。为了计算所有数据点的 RMSE,你需要取期望值
和预测值
,并计算以下内容:

对于分类问题,这会变得稍微复杂一些。在大多数情况下,模型可以预测正确的类别或者不能,这导致结果是一个二元的结果。更进一步,我们可能有一个二元分类问题(1 或 0——是或否),或者一个多类别问题(猫、狗、马等等)。
对于分类问题,有一个突出的损失函数被称为交叉熵损失。为了解决二元结果的问题,这个损失函数需要一个模型输出一个介于 0 和 1 之间的概率 ![img/B17928_Formula_1.37.png],对于给定的数据点 ![img/B17928_Formula_1.38.png] 和一个建议的预测 ![img/B17928_Formula_1.39.png]。对于二元分类模型,它的计算如下:
![img/B17928_Formula_1.40.png]
对于多类别分类,我们将这个错误对所有类别进行求和 ![img/B17928_Formula_1.41.png],如下所示:
![img/B17928_Formula_1.42.png]
如果你想进一步了解这个话题,可以考虑其他有用的回归损失函数,例如绝对误差损失和Huber 损失函数(用于支持向量机,或SVMs),有用的二元分类损失函数,例如hinge 损失函数,以及有用的多类别分类损失函数,例如Kullback-Leibler 散度(KL 散度)函数。最后一个也可以在强化学习(RL)中作为一个指标来监控训练过程中的策略函数。
我们到目前为止讨论的所有内容都需要能够放入数学公式中的东西。想象一下,我们使用文本文件来构建一个用于自然语言处理(NLP)的模型。在这种情况下,我们除了像Unicode这样的东西之外,没有有用的数学表示形式。我们将在第七章,高级特征提取与 NLP中学习如何以有用、向量化的方式表示它。有了向量,我们可以使用不同类型的指标来计算向量之间的相似度,称为余弦相似度指标,我们将在第六章,特征工程与标注中讨论。
到目前为止,我们已经讨论了如何计算几种场景下的损失函数,但我们是怎样定义我们模型的整体性能的呢?
对于回归模型,我们的损失函数是在整个训练集语料库上定义的。单个观察或预测的错误将是 ![img/B17928_Formula_1.43.png]。因此,RMSE 已经是一个成本函数,可以被优化器用来提高模型性能,所以我们可以用它来判断模型性能。
对于分类模型,这会变得更有趣一些。交叉熵可以与优化器一起用来训练模型,也可以用来判断模型,但除此之外,我们还可以定义一个额外的指标来关注。
一个显而易见的方法就是所谓的模型的准确率,计算方法如下:

现在,这看起来是正确的。我们只是说我们模型的品质是我们猜对次数的百分比,而现实中很多人同意这个说法。记得我们定义假阳性和假阴性的时候吗?这些现在开始发挥作用了。让我们看看一个例子。
想象一个检测传染性病毒的测试。图 1.10显示了 100 人接受这种病毒检测的结果,包括结果的正确性:

图 1.10 – 100 人组的测试结果
现在,根据这些结果,这个测试的准确率会是多少呢?让我们再次定义它,使用以下值:真正例(
)、假正例(
)、假负例(
)和真负例(
),并计算我们示例的结果,如下所示:

这个听起来像是一个很好的测试。它在 92%的情况下给出了准确的结果,但你可能在这里看到了问题。准确率对一切同等看待。我们的测试将患有病毒的人错误地分类为无病毒的人八次,这可能会产生严重的后果。这意味着可能需要具有更多强调假阳性或假阴性结果性能指标的指标。因此,让我们定义两个额外的指标来计算。
我们首先称之为精确度,这是一个定义有多少个阳性识别是正确的值。公式如下所示:

在我们的例子中,当我们宣布某人感染时,只有三分之二的情况下我们是正确的。具有 1 精确度值的模型将没有假阳性结果。
我们称之为召回率的第二个值,这是一个定义我们正确识别多少个阳性结果的价值。公式如下所示:

这意味着在我们的例子中,我们正确地识别了所有感染患者的 20%,这是一个不好的结果。具有 1 召回值的模型将没有假阴性结果。
为了正确评估我们的测试或分类,我们需要评估准确度、精确度和召回率。请注意,正如我们在讨论假设检验时提到的,精确度和召回率可能会相互对立。因此,你通常必须决定你更喜欢在说“你有病毒”时保持精确,还是更喜欢找到所有有病毒的人。你现在可能理解为什么这样的测试通常设计为以召回率为目标。
通过这一点,我们总结了提高构建机器学习模型和数据处理能力所需的数学基础部分。基于我们迄今为止所学的内容,你应该带着下一个要点继续前进。
重要提示
永远不要只使用机器学习库中的方法进行数据分析建模;要理解它们的数学原理。
在下一节中,我们将引导您了解端到端机器学习过程和本书的结构。
发现端到端机器学习过程
我们终于到达了本章的主题。在回顾过去并了解机器学习的目的以及它是如何根植于数学数据分析之后,现在让我们清楚地了解需要采取哪些步骤来创建高质量的机器学习模型。
以下图表展示了从数据到模型再到部署模型的(有时是递归的)步骤概述:

图 1.11 – 端到端机器学习过程
观察这个流程,我们可以定义以下不同的步骤:
-
挖掘数据和来源
-
准备和清理数据
-
定义标签和特征工程
-
训练模型
-
部署模型
这些展示了运行单个机器学习项目的步骤。当你处理大量项目和数据时,采用某种形式的自动化和运营变得越来越重要,这通常被称为 MLOps。
在本节中,我们将概述这些步骤的每个部分,包括 MLOps 及其重要性,并解释我们将深入探讨相应主题的章节。在我们开始逐步了解这些步骤之前,请思考以下问题:
你将为每个步骤分配多少百分比的时间?
完成后,请查看以下截图,它显示了完成这些任务所需的典型时间投入:

图 1.12 – 机器学习时间投入
你的猜测合理吗?你可能惊讶地发现,只有 20%的时间,你将从事与实际训练和部署机器学习模型有关的工作。因此,你应该牢记下一点。
重要提示
在机器学习项目中,你应该花大部分时间拆解你的数据集并寻找其他有用的数据来源。
如果不这样做,将对模型的质量和性能产生负面影响。现在,既然已经说了这一点,让我们一步一步地过一遍,从数据来源开始。
挖掘数据和来源
当你开始一个机器学习项目时,你可能已经有一个预期的结果,并且通常,你有一些形式的现有数据集,你或你的公司希望从中开始。这就是你开始熟悉给定数据的时候,通过分析了解你有什么以及缺少什么,我们将在以下步骤中回顾这一点。
在某个时候,你可能会意识到你缺少一些额外的——但至关重要的——数据点,以增加你结果的质量。这高度取决于你所缺少的是什么——是你可以或你的公司可以获得的,还是你需要从其他地方找到的。为了给你一些想法,让我们看看以下获取额外数据的方法以及你应该注意的事项:
-
内部数据源:如果你在公司内部或与公司合作进行此项目,首先需要考虑的是内部资源。这种选择的优点是免费,通常标准化,你应该能够找到了解这些数据及其获取方式的人。根据项目情况,这也可能是你获取所需数据的唯一途径。这种选择的缺点是可能找不到你想要的东西,数据可能记录不佳,而且由于数据中的偏差,质量可能存在问题。
-
公开数据源:另一个选择是使用免费可用的数据集。这些数据集的优点通常是规模巨大(千兆字节(TB)的数据),覆盖不同的时间段,并且通常结构良好且文档齐全。缺点是某些数据字段可能难以理解(且创建者不可用),由于数据中的偏差,质量也可能参差不齐,并且在使用时,通常需要你发布你的结果。此类示例包括国家海洋和大气管理局(NOAA)(
www.ncei.noaa.gov/weather-climate-links)和欧盟(EU)开放数据门户(data.europa.eu/en),以及其他许多数据源。 -
数据卖家(数据即服务,或 DaaS):最后一个选择是从数据卖家那里购买数据,无论是购买现有的数据集还是请求创建一个。这种选择的优点是节省时间,可以让你访问个性化的数据集,甚至可能获得预处理数据的访问权限。缺点是这很昂贵,你仍然需要完成所有其他后续步骤来使这些数据变得有用,并且可能存在有关隐私和伦理的问题。
现在我们已经对最初或额外获取数据的地方有了很好的了解,让我们看看下一步:准备和清理数据。
准备和清理数据
如前所述,描述性数据探索无疑是机器学习项目中最重要的步骤之一。如果你想清理数据、构建派生特征或选择机器学习算法来预测数据集中的目标变量,那么首先你需要了解你的数据。你的数据将定义许多必要的清理和预处理步骤。它将定义你可以选择哪些算法,并最终定义你的预测模型的性能。
探索应该作为一个结构化的分析过程来完成,而不是一系列实验任务。因此,我们将通过一系列数据探索任务清单,这些任务可以作为每个机器学习项目开始时的初步步骤,在开始任何数据清理、预处理、特征工程或模型选择之前执行。通过应用这些步骤,你将能够理解数据并获得有关所需预处理任务的知识。
此外,它还将为您提供对预测任务可能遇到的困难类型的良好估计,这对于判断所需的算法和验证策略至关重要。您还将深入了解哪些可能的特征工程方法适用于您的数据集,并更好地理解如何选择一个好的损失函数。
让我们看看所需的步骤。
存储和准备数据
您的数据可能以各种不同的格式存在。您可能需要处理存储在逗号分隔值(CSV)文件中的表格数据;您可能拥有以联合图像专家组(JPEG)或可移植网络图形(PNG)文件格式存储的图像,文本存储在JavaScript 对象表示法(JSON)文件中,或者音频文件以MP3或M4V格式。CSV 可以是一个好的格式,因为它可读性强,且可以高效解析。您可以使用任何文本编辑器打开和浏览它。
如果您独立工作,您可能只需将原始数据存储在系统中的一个文件夹中,但当你与云基础设施或一般的公司基础设施一起工作时,您可能需要某种形式的云存储。当然,您可以手动上传原始数据到这种存储,但通常,您处理的数据来自实时系统,需要从中提取。这意味着查看所谓的提取-转换-加载(ETL)工具可能是有价值的,这些工具可以自动化此过程并将所需的原始数据带入云存储。
在所有预处理步骤完成后,您存储中的数据将呈现某种形式的多层结构,从原始数据到清洗数据,再到标记数据,最后到处理后的数据集。
我们将在第四章“数据摄取与管理数据集”中更深入地探讨这个主题。现在,只需了解我们将自动化使数据可用于处理的过程。
清洗数据
在这一步,我们检查数据本身中的不一致性和结构错误。这一步骤通常适用于表格数据,有时也适用于文本文件,但不太适用于图像或音频文件。对于后者,我们可能能够裁剪图像并改变它们的亮度或对比度,但可能需要回到源数据以创建更好的样本。同样适用于音频文件。
对于表格数据集,我们有很多处理选项。以下是我们需要关注的事项:
-
重复项:由于数据复制错误或不同数据源的组合,您可能会发现重复的样本。通常,副本可以被删除。只需确保这些不是看起来相同但实际不同的样本。
-
无关信息:在大多数情况下,你将拥有包含许多不同特征的数据库集,其中一些对于你的项目来说可能完全不必要。你应该在开始时直接删除明显的那些;其他的一些你可以在进一步分析数据后删除。
-
(
US和United States) 或简单的拼写错误。这些应该被标准化或清理。一个好的方法是可视化一个特征的可用值。 -
异常值(离群值):这指的是非常不可能的值,你需要决定它们是错误还是实际上是真的。这通常是在分析数据后进行的,当你知道一个特征的分布时。
-
NA或NaN。除了删除整个样本之外,还有不同的方法可以纠正这个问题。在分析数据并可能看到更好的替换方法之前等待,也是谨慎的做法。
在这一步之后,我们可以开始进一步分析清洗后的数据集。
分析数据
在这一步中,我们将应用我们对统计学的理解,以获取对我们特征和标签的洞察。这包括为每个特征计算统计属性,可视化它们,找到相关特征,并测量称为特征重要性的东西,它计算特征对标签的影响,也称为目标变量。
通过这些方法,我们获得了关于特征之间以及特征与目标之间关系的想法,这可以帮助我们做出决定。在这个决策过程中,我们也开始添加一些至关重要的东西——我们的领域知识。如果你不知道数据代表什么,你将很难对其进行修剪,并选择训练的最佳特征和样本。
在这一步中,还有许多可以应用的技术,包括称为降维的东西。如果你有成千上万的特征(例如图像的数值表示),对于人类甚至对于机器学习过程来说,理解关系会变得非常复杂。在这种情况下,将这个高维样本映射到二维或三维的向量表示可能是有用的。通过这种方式,我们可以轻松地找到不同样本之间的相似性。
我们将在第五章 执行数据分析与可视化中更深入地探讨清理和分析数据的话题。
完成所有这些步骤后,我们将对手头的数据有一个良好的理解,并且可能已经知道我们缺少什么。作为数据预处理中的最后一步,我们将查看创建和转换特征的过程,通常称为特征工程,以及当缺少标签时创建标签。
定义标签和工程特征
在数据预处理的第二部分,我们将讨论数据的标注以及我们可以对特征执行的操作。要执行这些步骤,我们需要通过我们之前讨论的探索步骤获得的知识。让我们首先看看标注数据。
标注
让我们从一件令人沮丧的事情开始:这个过程非常繁琐。标注,也称为注释,是机器学习项目中最不令人兴奋的部分,但也是整个过程中最重要的任务之一。目标是向机器学习算法提供高质量的训练数据。
虽然适当的标签极大地有助于提高预测性能,但标注过程也将帮助您更详细地研究数据集。让我澄清一下,标注数据需要深入洞察和理解数据集的上下文以及预测过程,您应该已经在这个阶段获得了这些。例如,如果我们旨在使用计算机断层扫描(CT)来预测乳腺癌,我们还需要了解如何在 CT 图像中检测乳腺癌以标注数据。
错误标注训练数据有几个后果,例如标签噪声,您希望避免它,因为它会影响机器学习管道中每个下游过程的性能。在某些情况下,您的标注方法取决于为预测问题选择的机器学习方法。一个很好的例子是目标检测和分割之间的差异,它们都需要完全不同标注的数据。
有一些技术和工具可以利用我们不仅可以使用机器学习算法来完成所需项目,还可以学习如何标注我们的数据这一事实来加速标注过程。这些模型会在您手动标注数据集时开始提出标签。
特征工程
简而言之,在这一步中,我们将开始转换特征或添加新特征。显然,我们并不是一时兴起地执行这些操作,而是基于我们在前一步骤中收集到的知识。例如,我们可能已经了解到完整的日期和时间过于精确,我们只需要星期几或月份。无论是什么,我们都会尝试塑造和提取我们所需要的。
通常,我们将执行以下操作之一:
-
特征创建:从一组给定的特征或从额外的信息源中创建新特征。
-
特征转换:将单个特征转换为对所使用的机器学习算法有用的和稳定的。
-
特征提取:从原始数据中创建派生特征。
-
特征选择:选择最突出和预测性强的特征。
我们将在第六章“特征工程和标记”中更深入地探讨标记和应用于我们的特征的各种方法。此外,我们还将详细研究一个更复杂的特征工程示例,当在自然语言处理(NLP)项目中处理文本数据时。您将在第七章“使用 NLP 的高级特征提取”中找到它。
我们通过重申整个预处理数据步骤的重要性以及它们对下一步的影响来结束这一步骤,下一步我们将讨论模型训练。此外,我们记得,如果我们的模型表现不佳,我们可能需要在模型训练后返回到此步骤。
训练模型
我们最终达到了可以应用机器学习算法的点。与数据实验和预处理一样,训练机器学习模型是一个分析性的、逐步的过程。每个步骤都涉及一个思考过程,根据实验阶段的结果评估每个算法的优缺点。正如其他任何科学过程一样,建议您首先提出一个假设,然后验证这个假设是否正确。
让我们看看定义训练机器学习模型过程的步骤,如下所示:
-
定义您的机器学习任务:首先,我们需要定义我们面临的机器学习任务,这通常由您用例背后的业务决策所定义。根据标记数据的数量,您可以选择无监督学习和监督学习方法,以及许多其他子类别。
-
选择一个合适的模型:为所选的机器学习任务选择一个合适的模型。这可能是一个逻辑回归、梯度提升集成树或深度神经网络(DNN),仅举几个流行的机器学习模型选择。选择主要取决于训练(或生产)基础设施(如 Python、R、Julia、C 等)以及数据的形状和类型。
-
选择或实现损失函数和优化器:在数据实验阶段,您应该已经制定了一个测试模型性能的策略。因此,您应该已经选择了数据分割、损失函数和优化器。如果您还没有这样做,您应该在此点评估您想要衡量和优化的内容。
-
选择数据集分割:将您的数据分割成不同的集合——即训练集、验证集和测试集——可以帮助您深入了解训练和优化过程的表现,并帮助您避免模型对训练数据的过度拟合。
-
使用交叉验证训练简单模型:在做出所有前面的选择之后,你可以继续训练你的机器学习模型。理想情况下,这是在训练集和验证集上进行交叉验证,而不将训练数据泄露到验证中完成的。在训练基线模型后,是时候解释验证运行中的错误度量了。它有意义吗?它是否如预期的那样高或低?它是(希望)比随机预测更好,并且比总是预测最流行的目标更好吗?
-
调整模型:最后,你可以通过调整模型的超参数、进行模型堆叠或其他高级方法来调整模型的输出,或者你可能需要回到初始数据并在重新训练模型之前对其进行处理。
这些是我们训练模型时执行的基线步骤。在下一节中,我们将更深入地探讨上述步骤,从如何选择模型开始。
选择模型
当涉及到为你的数据选择一个好的模型时,建议你在转向更复杂选项之前优先考虑简单的传统模型。例如,当训练数据有限时,可以采用集成模型,如梯度提升树集成。这些模型在广泛的输入值(有序、名义和数值)上表现良好,并且训练效率高,易于理解。
基于树的集成模型将许多弱学习器结合成一个基于决策树的单一预测器。这大大减少了单个决策树的过拟合和不稳定问题。使用默认参数进行几次迭代后,输出通常为许多不同应用提供很好的基线结果。
在第九章“使用 Azure 机器学习构建 ML 模型”中,我们专门用一节来介绍如何使用LightGBM(来自微软的一个流行的树集成库)训练梯度提升树集成分类器。
为了捕捉大量复杂训练数据的含义,我们需要大型参数模型。然而,由于梯度爆炸和消失、通过如此复杂的模型传播损失、数值不稳定性和归一化等问题,用数亿个参数训练参数模型并非易事。近年来,这类高参数模型通过许多复杂任务实现了极其好的结果——即深度学习(DL)。
深度学习(DL)基本上是一个多层人工神经网络(ANN),其中每一层都被视为模型数据处理管道中的某个步骤。
在第十章“在 Azure 上训练深度神经网络”和第十二章“Azure 上的分布式机器学习”中,我们将更深入地探讨如何在单机和分布式 GPU 集群上训练大型和复杂的深度学习模型。
最后,你可能会处理完全不同形式的数据,例如音频或文本数据。在这种情况下,有专门的方法来预处理和评分这些数据。这些领域之一是推荐引擎,我们将在第十三章中详细讨论,在 Azure 中构建推荐引擎。
选择损失函数和优化器
正如我们在上一节中讨论的,根据你想要使用的训练类型和模型,有许多指标可以选择。在查看特征和目标维度的关系以及数据的可分性之后,你应该继续评估你将使用哪个损失函数和优化器来训练你的模型。
许多机器学习从业者并没有高度重视适当错误指标的重要性,只是使用简单易行的指标,例如准确率和 RMSE。这个选择至关重要。此外,了解基线性能和模型对噪声的鲁棒性是有用的。第一种可以通过仅使用出现频率最高的目标变量作为预测来计算错误指标来实现。这将是你基线性能。第二种可以通过修改你的机器学习模型的随机种子并观察错误指标的变化来实现。这将显示你可以信任错误指标的哪个小数位。
请记住,在训练运行后,评估所选的错误指标以及任何其他您希望评估的指标是谨慎的,并尝试实验其他指标是否可能更有益。
至于优化器,它高度依赖于你选择的模型,这决定了你在这方面有哪些选项。只需记住,优化器是我们达到目标的方式,而目标由损失函数定义。
数据集划分
一旦你选择了机器学习模型、损失函数和优化器,你需要考虑将你的数据集划分为训练集。理想情况下,数据应该分为三个互斥的集合:训练集、验证集和测试集。我们使用多个集合来确保模型在未见过的数据上具有良好的泛化能力,并且报告的错误指标是可以信赖的。因此,你可以看到将数据划分为代表性集合是一个应该作为分析过程执行的任务。这些集合如下定义:
-
训练数据集:用于拟合/训练模型的子数据集。
-
验证数据集:用于在训练过程中提供评估以调整超参数的子数据集。算法在训练过程中看到这些数据,但从未从中学习。因此,它对模型有间接影响。
-
测试数据集:用于在训练后对训练好的模型进行无偏评估的子数据集。
如果训练数据泄露到验证集或测试集中,你可能会使模型过拟合,并扭曲验证和测试结果。除了欠拟合模型外,过拟合也是一个你必须处理的问题。两者如下定义:
欠拟合与过拟合
一个欠拟合的模型仅对数据进行操作。这种情况的原因通常是因为模型过于简单,无法理解特征与目标变量之间的关系,或者是因为你的初始数据缺乏有用的特征。一个过拟合的模型在训练数据集上表现完美,在任意其他数据上也表现完美。这种情况的原因是它基本上记住了训练数据,无法进行泛化。
关于这些分割的大小有不同的讨论,以及许多不同的进一步技术来为每个类别选择样本,例如分层分割(基于类别分布的采样)、时间分割和基于组的分割。我们将在第九章中更深入地探讨这些内容,使用 Azure 机器学习构建机器学习模型。
运行模型训练
在大多数情况下,你不会从头开始构建人工神经网络(ANN)结构和优化器。你将使用现成的机器学习库,如scikit-learn、TensorFlow或PyTorch。这些框架和库大多是用 Python 编写的,因此 Python 应该是你的机器学习项目的首选语言。
在编写模型训练的代码时,将所需的代码逻辑上分为两个文件是一个好主意,如下所示:
-
脚本编写(脚本环境):定义机器学习训练将进行的(库、训练位置等)环境以及触发执行脚本的脚本
-
执行脚本(执行环境):仅包含实际机器学习训练的脚本
通过以这种方式拆分你的代码,你可以在目标环境发生变化时避免更新实际的训练脚本。这将使代码版本控制和 MLOps 变得更加干净。
为了理解在机器学习库中我们可能会遇到哪些类型的分类方法,让我们来看看 TensorFlow 中的一个简短代码片段:
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),…])
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)
通过查看这段代码,我们看到我们正在使用一个名为Sequential的模型,这是一个由一系列层定义的基本人工神经网络(ANN),这些层有一个输入和一个输出。在模型创建步骤中,我们看到定义了层,并省略了一些其他设置。此外,在compile()方法中,我们定义了一个优化器、一个损失函数以及一些我们感兴趣的额外指标。最后,我们看到一个名为fit()的方法正在训练数据集上运行,一个名为evaluate()的方法正在测试数据集上运行。现在,这些方法究竟做了什么?在我们深入探讨之前,让我们首先定义一些东西。
模型的超参数与参数
在模型训练过程中调整的有两种类型的设置。在人工神经网络(ANN)中,权重和偏差等设置被称为参数。它们在训练阶段被改变。其他设置——如 ANN 中的激活函数和层数、数据分割、学习率或选择的优化器——被称为超参数。这些是我们训练运行之前调整的元设置。
在这方面没有问题之后,让我们定义你将遇到的典型方法,如下所示:
-
(
Sequential类),在特殊函数如compile()中,或者它们是我们接下来讨论的训练方法的一部分。 -
fit()或train(),这是基于训练数据集、损失函数和优化器训练模型参数的主要方法。这些方法不返回任何类型的值——它们只是更新模型对象及其参数。 -
evaluate()、transform()、score()或predict()。在大多数情况下,这些函数会返回某种形式的结果,因为它们通常是在将测试数据集与训练模型进行对比时运行的。
这是你在机器学习库中遇到的一个模型的典型方法结构。现在我们已经对如何设置我们的编码环境和使用可用的机器学习库有了很好的了解,让我们看看如何在初始训练后调整模型。
调整模型
在我们训练了一个简单的集成模型,该模型的表现比基线模型好得多,并且根据数据准备期间估计的预期性能达到了可接受的水平之后,我们可以进行优化。这是一个我们真的想强调的点。强烈建议不要在简单的集成技术无法提供有用结果时开始模型优化和堆叠。如果这种情况发生,退一步深入数据分析和特征工程会更好。
常见的机器学习优化技术——如超参数优化、模型堆叠,甚至自动化机器学习(AutoML)——可以帮助你从模型中获得最后的 10%性能提升。
超参数优化专注于改变模型训练的初始设置以提高其最终性能。同样,模型堆叠是一种非常常见的用于通过将多种不同模型类型组合成一个单一堆叠模型来提高预测性能的技术。因此,每个模型的输出都输入到一个元模型中,该元模型本身通过交叉验证和超参数调整进行训练。通过将显著不同的模型组合成一个单一堆叠模型,你总是可以超越单个模型。
如果你决定使用这些优化技术中的任何一种,建议你在分布式集群上并行和完全自动化地执行它们。在看到太多机器学习从业者手动参数化、调整和堆叠模型之后,我们想提出这个重要信息:优化机器学习模型是无聊的。
这很少需要手动完成,因为作为端到端优化过程自动执行要快得多。你大部分的时间和精力应该投入到实验、数据准备和特征工程中——也就是说,所有这些都不能通过原始计算能力轻松自动化和优化的事情。我们将在第十一章“超参数调整和自动化机器学习”中更深入地探讨模型调整的主题。
这就结束了关于模型训练的所有重要主题。接下来,我们将探讨机器学习模型部署的选项。
模型部署
一旦训练和优化了机器学习模型,它就准备好部署了。这一步通常被称为推理或评分模型。实际上,许多数据科学团队在这里停止,并将模型作为 Docker 镜像部署到生产环境中,通常嵌入到表示状态转移(REST)API中,使用 Flask 或类似框架。然而,正如你可以想象的那样,这并不总是最佳解决方案,这取决于你的需求。机器学习或数据工程师的责任并不止于此。
在生产环境中对模型进行实时数据测试时,最能体现机器学习管道的部署和操作。进行测试是为了收集见解和数据,以持续改进模型。因此,随着时间的推移收集模型性能是一个保证和提升模型性能的重要步骤。
通常,我们将机器学习评分管道分为两种主要架构,如下所示:
-
批处理评分使用管道:这是一个离线过程,其中你将机器学习模型与一批数据评估。这种评分技术的结果通常不是时间敏感的,要评分的数据通常比模型大。
-
基于容器化 Web 服务端点的实时评分:这指的是一种评分单个数据输入的技术。这在流处理中非常常见,其中单个事件会实时评分。显然,这项任务对时间非常敏感,执行会一直阻塞,直到计算出的评分结果出来。
我们将在第十四章“模型部署、端点和操作”中更详细地讨论这两种架构。在那里,我们还将研究收集运行时间、延迟和其他操作指标以及模型性能的有效方法。
我们创建的模型文件和之前提到的选项通常由标准硬件架构定义。正如之前提到的,我们可能创建一个 Docker 镜像,将其部署到虚拟机(VM)或 Web 服务中。如果我们想将模型部署到高度专业化的硬件环境中,比如 GPU 或现场可编程门阵列(FPGA)呢?
为了进一步探讨这个问题,我们将深入探讨第十五章中提到的替代部署目标和方法的替代方案,模型互操作性、硬件优化和集成。在那里,我们将研究一个名为Open Neural Network eXchange(ONNX)的框架,它允许我们将我们的模型转换为标准化的模型格式,以便部署到几乎任何环境中。此外,我们还将探讨 FPGA 及其为何可能成为机器学习的良好部署目标,最后,我们将探索其他 Azure 服务,如Azure IoT Edge和Power BI,用于集成。
此步骤封装了单个机器学习模型的端到端过程。接下来,我们将简要概述如何使用 MLOps 在企业级环境中使此类机器学习项目投入运行。
开发和运营企业级机器学习解决方案
为了使机器学习项目投入运行,需要使用自动化管道和开发运维(DevOps)方法,如持续集成(CI)和持续交付/持续部署(CD)。这些通常统称为 MLOps。
当查看我们在机器学习项目中执行的步骤时,我们可以看到通常有两个主要操作发生——模型的训练和模型的部署。由于这些操作可以独立进行,因此定义两个不同的自动化管道是值得的,如下所示:
-
训练管道:这包括加载数据集(可能包括 ETL 管道)、转换、模型训练和注册最终模型。此管道可以由数据集的变化或已部署模型中检测到的可能数据漂移触发。
-
部署管道:这包括从注册表中加载模型、创建和部署 Docker 镜像、创建和部署操作脚本,以及将模型最终部署到目标位置。此管道可以由机器学习模型的新版本触发。
我们将在第八章中深入探讨使用 Azure Machine Learning 的机器学习管道,Azure Machine Learning Pipelines。
在拥有这些管道之后,我们还可以关注Azure DevOps以及其他工具。有了这些,我们可以为我们的机器学习项目构建以下部分定义的生命周期:
-
创建或重新训练模型:在此,我们使用训练管道创建或重新训练我们的模型,同时控制管道和代码的版本。
-
部署模型并创建评分文件和依赖项:在此,我们使用部署管道部署特定的模型版本,同时控制管道和代码的版本。
-
创建审计跟踪:通过 CI/CD 管道和版本控制,我们为所有资产创建审计跟踪,确保完整性和合规性。
-
在生产中监控模型:我们监控性能和可能的数据漂移,这可能会自动触发模型的重新训练。
我们将在第十六章“使用 MLOps 将模型投入生产”中更详细地讨论这些主题和其他内容。
这就结束了我们对端到端机器学习过程和本章的讨论。如果你之前还没有,你现在应该对机器学习以及本书剩余部分的内容有一个很好的理解。
摘要
在本章中,我们学习了在哪些情况下我们应该使用机器学习以及它的来源,我们了解了统计学的基本概念以及我们进行机器学习所需的数学知识,我们还发现了创建一个表现良好的机器学习模型所需的步骤。此外,我们还对将机器学习项目投入运营所需的内容有了初步的了解。这应该为我们提供一个关于机器学习是什么以及我们将在这本书中深入研究什么的基本概念。
由于本书不仅涵盖了机器学习(ML),还包括了云平台 Azure,在接下来的两章中,我们将深入探讨我们之前未曾涉及的主题——我们将讨论机器学习的工具。因此,在下一章中,我们将了解 Azure 为机器学习提供的工具和服务,而在第三章中,我们将使用最有用的工具在我们的 Azure 上进行第一次实际的机器学习实验。
第二章:第二章: 在 Azure 中选择正确的机器学习服务
在上一章中,我们学习了端到端的机器学习过程以及所有必需的步骤,从数据探索到数据预处理、训练、优化、部署和运营。了解整个过程将更好地帮助我们选择构建基于云的机器学习服务的正确服务。
在本章中,我们将帮助您了解不同的 Azure 人工智能服务,并展示如何为您的机器学习任务选择正确的服务。首先,我们将根据服务抽象和应用领域对不同的服务进行分类,然后探讨不同服务的不同权衡和益处。
在下一节中,我们将重点关注托管服务,并直接进入 Azure 认知服务,这是针对一般任务和领域的多个预训练机器学习服务。然后,我们将介绍定制认知服务,这是一种针对特定任务或领域微调认知服务的方法,并在本节结束时探讨应用人工智能服务。
在接下来的章节中,我们将讨论 Azure 中的定制机器学习服务,例如 Azure 自动机器学习、Azure 机器学习设计器和 Azure 机器学习服务——本书中将使用该服务。
在上一节中,我们将探讨定制计算服务,例如 Azure Databricks、Azure Batch 和数据科学虚拟机,用于构建定制的机器学习解决方案。
在本章结束时,您将了解如何导航 Azure 人工智能领域,并理解为什么 Azure 机器学习是构建定制机器学习解决方案的首选服务。
本章将涵盖以下主题:
-
选择 Azure 机器学习服务
-
管理机器学习服务
-
定制机器学习服务
-
定制计算服务
选择 Azure 机器学习服务
Azure 提供了超过 200 个服务,其中超过 30 个服务针对构建人工智能和机器学习解决方案。如此众多的服务往往使得 Azure 新手难以选择针对特定任务的正确服务。在 Azure 中开始机器学习时,为您的机器学习任务选择正确的服务是您必须做出的最重要的决定。在本节中,我们将提供关于如何在 Azure 中选择正确的机器学习和计算服务的明确指导。
选择正确的服务以及适当的抽象层可以为您节省数月甚至数年的时间来推广基于机器学习的产品或功能。它可以帮助您避免诸如通过迁移学习、重新训练、管理、重新部署机器学习模型,或监控、扩展和操作推理服务和端点等繁琐且耗时的任务。
选择错误的服务可能会导致你快速开始产生结果,但可能变得无法提高特定领域的模型性能或扩展模型以用于其他任务。因此,对不同的 Azure AI 和 ML 服务有一个基本的了解将帮助你做出正确的权衡,并为你的用例选择正确的服务。在下一节中,我们将帮助你导航众多的 Azure 服务和 Azure AI 景观,以确定适合你用例的正确 ML 服务。
导航 Azure AI 景观
对于许多基于云的服务,如计算、存储、数据库或分析,最重要的选择是服务级别抽象——基础设施即服务(IaaS)、平台即服务(PaaS)或软件即服务(SaaS)。图 2.1显示了云服务中自管理部分和管理部分之间的区别:

图 2.1 – 云服务的 IaaS 与 PaaS 与 SaaS 比较
让我们比较前图中展示的不同类型的抽象和责任。应用堆栈是从左到右构建的,从包含硬件(计算机、磁盘、网络卡、交换机等)的数据中心(建筑、冷却、电力等)开始。每台机器由一个操作系统(Linux 或 Windows)供电,并运行特定的服务(Web 服务器、数据库、缓存等)和应用程序(例如,WordPress),这些应用程序存储和提供你的数据(例如,你的自定义网站):
-
在本地计算中,你拥有并管理一切——从建筑、冷却、电力、物理服务器、网络连接、交换机,到操作系统、服务、应用程序和数据。如果硬盘、网络接口或电源连接出现故障,你需要更换它们。
-
在IaaS服务中,你从你的云提供商那里消费基础设施,例如虚拟机(VM)。你选择 CPU 数量、内存、磁盘、网络接口等,所有这些都将为你管理,但你需要自己管理操作系统以及所有服务、应用程序和数据。如果有一个重要的内核安全更新,你需要安装它。IaaS 服务是所有其他服务的基本构建块。
-
PaaS服务让你可以纯粹关注你的应用程序。一个典型的例子是所谓的无服务器计算,如 Azure Functions。在这里,你可以选择你的 JVM 版本来部署基于 Java 的应用程序,但你不需要担心修补你的操作系统、你的服务运行时或底层硬件。PaaS 服务通常在所有权、定制和成本之间提供良好的权衡。大多数云服务都属于这一类别。
-
最后,SaaS服务是由云服务提供商设计、实施和管理的完整应用程序。您通常通过网站或 API 端点与这些服务交互,甚至不知道使用了什么操作系统或服务运行时,也不知道应用程序代码或数据模型的样子。SaaS 服务可以与我们在日常生活中使用的流行网络服务相提并论,例如 Facebook、Netflix、Spotify 或 YouTube。云服务提供商通常为特定的用例构建这些服务,例如物联网、基因组学、计算机视觉等。
总之,所有 Azure 服务都可以根据服务抽象级别在 IaaS、PaaS 和 SaaS 规模上定位。我们可以使用这个规模将所有 Azure AI 服务分为三类:
-
管理型机器学习服务(SaaS)
-
定制 ML 服务(PaaS)
-
机器学习定制计算服务(IaaS)
因此,在 Azure 中选择 ML 服务的第一步是确定适合您用例的正确服务级别抽象——通过选择正确的灵活性、所有权、技能、时间和成本之间的权衡。
然而,选择 ML 服务比仅仅区分托管和定制服务要复杂得多。特别是对于托管 ML 服务,我们还需要比较不同的应用领域和定制及专业化的水平。
Azure 提供了许多预训练的特定领域模型和服务,例如目标检测、情感分析、推荐引擎和文档解析。根据您的应用领域,您可以选择包含预训练模型的 ML 服务。例如,如果您需要一个通用的面部识别模型,您可以从 Azure 中作为托管服务来使用它。这意味着您根本不需要任何训练数据来构建这样的功能。使用预训练模型的决定对您的项目时间表有巨大影响,因为获取、清理和标记训练数据是 ML 过程中最繁琐且耗时的工作之一。
然而,许多 ML 应用是为高度专业化的领域构建的,例如医学数据分析、法医分析和法律职业。如果您正在为这样的领域构建基于 ML 的应用或功能,没有针对应用领域进行定制的预训练模型可能并不合适。在这种情况下,您可以选择提供定制功能的托管 ML 服务——一种使用训练数据来微调预训练模型以适应定制领域的方法。这个过程也被称为迁移学习,并得到一些托管 Azure 机器学习服务的支持。
一些领域或基于 ML 的应用不适合这个类别,并且不能轻易地针对不同的应用领域进行微调。例如,在别人的评分上预训练推荐引擎、将文本到语音功能转移到古典音乐的生成模型,或者用三维图像数据微调二维模型并不实用。在这些情况下,你除了使用自己的训练数据创建自己的模型外别无选择。
使用前面的例子,我们可以根据所需训练数据量和应用领域将托管和自定义 ML 服务细分为以下几组:
-
不需要训练数据
-
需要一些训练数据用于定制
-
需要训练数据
因此,选择托管或自定义 ML 服务的第二个选项取决于你的应用领域以及对训练数据和模型专业化的需求。类似于服务抽象,权衡的是灵活性(定制)、所有权、技能、时间和成本。
让我们比较这些需求,并查看图 2.2中专门针对基于云的 ML 服务的 IaaS、PaaS 和 SaaS 比较:

图 2.2 – IaaS 与 PaaS 与 SaaS 在 ML 服务中的比较
如前图所示,你可以根据与其他云服务类似的维度评估你 ML 服务的首选服务抽象——取决于你想要自己管理堆栈的哪一部分。该表包含了一些针对 ML 应用的特定调整,例如用库(ML 框架、工具和运行时)代替服务,用模型代替应用。ML 的 SaaS 服务可以允许定制,这意味着你可以自带数据,或者不允许定制,这意味着你根本不需要提供任何训练数据。
带着关于服务抽象(IaaS 与 PaaS 与 SaaS)以及应用领域和所需训练数据(无训练数据与通过迁移学习进行定制的训练数据)的知识,我们可以开始剖析 Azure 机器学习领域。
消耗托管 AI 服务
通过 API 消耗托管 AI 服务是构建基于 ML 的功能或应用最快、最简单的方式。这是因为你不需要清理训练数据或训练模型,你不需要管理用于训练或推理的计算集群,也不需要监控和扩展你的模型部署以进行批量预测。
对于 Azure 中的许多托管 AI 服务,您只需要使用您的 API 密钥和数据调用一个网络服务,API 将响应相应的预测,这通常是多个模型分数的组合。例如,Azure 认知服务 API 用于理解图像,将返回对象检测、图像标记、成人内容分类、血腥和色情分类、人脸检测、性别和年龄检测、图像描述等预测,所有这些都在单个 API 调用中完成。
如果您处理的是一个一般机器学习问题和一般领域——例如图像标记、文本提取、语音转文本和翻译——那么您很幸运,能够为您的应用程序选择这样的托管 AI 服务。对于通用图像领域(如照片)的图像分析、文本分析、文本到语音和语音到文本、语言和翻译服务是常见的机器学习问题,可以利用现成的机器学习解决方案。我们将在Azure 认知服务部分稍后探讨不同的 API 和服务,用于托管预训练 AI 服务。
托管 AI 服务的一个缺点是,它们都配备了预训练的黑盒模型,我们无法看到、解释、分析或优化。这使得在高度特定的领域使用这些 API 变得不可行。如果您使用 MRI 图像进行癌症检测,您会发现 Azure 的一般对象检测算法不太有用。
对于这些特定情况——具有自定义应用域的一般机器学习问题——Azure 提供了可定制的托管 AI 服务。一个这样的例子是 Azure 自定义视觉服务,它允许您微调一个预训练模型以用于常见的图像识别任务。这些服务与众不同的地方在于,您可以提供自己的训练数据来微调一个模型以用于自定义应用域,同时享受使用托管服务的优势。
另一个这样的例子是Azure 表单识别器,这是一个允许您从结构化文档中提取打印和手写文本的工具。它可以微调以检测您应用程序领域使用的自定义文本格式。我们将在自定义认知服务和Azure 应用服务部分稍后查看所有这些可定制的托管服务。
然而,如果您需要选择一个不支持作为服务的特定模型或算法(例如,图像分割)的灵活性,那么您别无选择,只能实现自己的模型并构建自己的 AI 解决方案。我们将在下一节更深入地探讨这个话题。
让我们以开发基于云和 ML 的功能或应用的重要建议结束本节——如果可能的话,选择具有预训练模型的托管服务,而不是构建自定义 ML 解决方案。通过 API 消耗预训练模型通常比训练、部署和运营自己的 ML 服务容易、快、成本低得多。许多实际应用可以利用通用的预训练模型或微调的定制模型,并且提供的模型、服务和领域列表不断增长。
在整本书中,我们将帮助您掌握在 Azure 中构建自定义 ML 应用的能力,涵盖所有无法使用托管 AI 服务的情况。
构建自定义 AI 服务
如果您无法使用托管 AI 服务,要么是因为没有适用于您的用例的模型或服务,要么是因为微调能力不足以满足您的应用领域,您别无选择,只能构建自定义 AI 解决方案。
您可以选择在 Azure 中构建自定义 AI 解决方案的 PaaS 或 IaaS 服务。这两种类型的服务都将为您提供类似的选择灵活性,例如选择您偏好的编程语言和库来实现和训练 ML 模型,选择您自己的数据源和格式作为训练数据,以及选择特定的部署策略,例如针对批量预测的优化或低延迟的设备端推理。
然而,这种灵活性是有代价的,通常比消耗预训练或定制 AI 服务高得多。更高的成本是成功构建和运营 ML 服务所需的额外任务、技能和投资的结果。与消耗 AI 服务相比,构建自定义 AI 解决方案最重要的差异如下:
-
收集、预处理和标注训练数据
-
构建用于训练和推理的基础设施和自动化
-
ML 模型的建模、训练和优化
-
在生产中运营 ML 服务
很容易看出,额外的复杂性不仅来自于训练自定义模型,还来自于端到端 ML 过程中的许多其他任务。足够的训练数据可用性、数据质量和为数据标注人员可用性是构建高性能自定义 AI 解决方案的主要障碍。因此,您需要在项目开始之前确保训练数据可用,或者可以在项目期间获取。
第二个最重要的额外成本和资源与基础设施相关。建模、训练和优化是 ML 服务生命周期中持续迭代的流程。部署后,我们通常会收集更多训练数据,记录模型指标,测量模型漂移,并反复重复整个过程。因此,即使是较小的 ML 项目,对基础设施的投资也是显著的,但对于项目的长期成功至关重要。
大型公司甚至将这些责任分割成不同的团队,以满足这两个领域不同技能集的需求——一个用于构建和维护 ML 基础设施,另一个用于 ML 建模、训练和优化。这清楚地表明,基础设施和建模对于开发成功的 ML 项目同等重要。
在灵活性和所有权方面,构建基于云的定制 AI 服务的最佳权衡是选择基于 PaaS 的 ML 平台。因此,一个优秀的定制 ML 平台会支持您进行所有这些基础设施设置和操作,简化您的建模和优化任务,提供抽象来封装重复性工作负载,并在项目生命周期中提供自动化以最小化人工努力。此外,定制 ML 服务还为您提供灵活性,选择任何 ML 框架、任何建模技术和训练算法、任何数据源和格式来构建一个完全定制的 AI 解决方案。
Azure 机器学习是构建定制 ML 解决方案和优化 ML 项目全生命周期的一种基于 PaaS 的服务的好例子。我们将在“定制 ML 服务”部分更详细地探讨 Azure 机器学习,并将其功能与其他定制 ML 服务进行比较。
在这本书中,我们将为您提供从零开始构建自己的定制 ML 服务所需的所有技能,使用 Azure 机器学习作为您首选的托管 ML 服务。
然而,值得注意的是,为了构建定制 AI 服务,您不一定需要一个平台来注册您的模型、定义您的数据集或跟踪您的训练分数。您只需选择您最喜欢的计算服务(例如,Azure Kubernetes 服务),您最喜欢的存储服务(例如,Azure Data Lake Storage),以及您最喜欢的数据库服务(例如,Azure Cosmos DB),然后构建自己的定制解决方案。实际上,您可以使用任何计算服务在 Azure 中构建基于 IaaS 的定制 ML 应用程序。
选择 IaaS 服务来构建自己的机器学习应用程序,在 ML 过程中选择任何基础设施组件时,你将拥有最大的灵活性。另一方面,这也意味着你需要手动设置、配置和集成这些服务,以及设置身份、认证和访问控制,这导致前期投资增加、基础设施开发成本上升,以及需要特定的技能集。
Azure 提供了优秀的 IaaS 计算服务来构建自定义机器学习解决方案。你可以从简单的虚拟机、预装 ML 镜像的虚拟机、批量计算服务以及可扩展的分布式计算服务中进行选择。我们将在“自定义计算服务”部分看到一些服务示例。
什么是 Azure 机器学习服务?
在我们开始探讨具体的托管和自定义机器学习服务之前,我们想要澄清一些关于Azure Machine Learning术语的混淆,这个术语不仅出现在本书的封面上,而且在 Azure 中也是一个流行的机器学习服务,是其他机器学习服务的空间,也是互联网、博客和书籍中的热门关键词。
首先,术语Azure Machine Learning代表一个流行的 Azure 服务(docs.microsoft.com/en-us/azure/machine-learning/overview-what-is-azure-machine-learning),它提供了构建自定义机器学习解决方案的能力。该服务包含不同的组件来管理资源(如计算集群和数据存储)和资产(如数据集、实验、模型、管道、Docker 环境以及端点),以及访问这些资源和资产,所有这些都在同一个工作空间内。
这是我们将在本书中使用的服务,用于构建训练、部署和操作自定义机器学习模型的端到端管道。你将在下一章中开始创建你的第一个 Azure 机器学习工作空间。
为了构建自定义机器学习模型,你将创建训练集群,跟踪实验,将数据注册为数据集,存储训练好的模型,管理用于训练和推理的 Docker 镜像,以及配置端点,所有这些操作都在 Azure 机器学习内完成。
在本书中,我们将主要使用 Python API(docs.microsoft.com/en-us/python/api/overview/azure/ml/?view=azure-ml-py)与 Azure 机器学习进行交互。然而,你也可以使用 UI 门户来访问和管理资源及资产,创建实验,提交训练作业,可视化训练结果,创建 Docker 环境,以及部署推理集群。
与 Azure 机器学习交互的 UI 称为Azure 机器学习工作室(docs.microsoft.com/en-us/azure/machine-learning/overview-what-is-machine-learning-studio)。这个名字不要与一个较老的 Azure 服务混淆,即 Azure 机器学习工作室——一个基于 GUI 的服务,通过基于块的拖放界面创建和部署机器学习服务,现在称为Azure 机器学习工作室(经典)(studio.azureml.net/)。
Azure 机器学习服务还提供了通过机器学习工作区访问其他共享相同资源和资产的其他机器学习服务。这包括 Azure 自动化机器学习、Azure 机器学习设计器——Azure 机器学习的新基于 GUI 的体验、一个数据标注工具,以及 Azure 机器学习的集成笔记本服务器(不要与已停用的https://notebooks.azure.com/experience混淆),所有这些都可以在 Azure 机器学习的工作区中创建。因此,Azure 机器学习有时也被称为 Azure 机器学习服务或 Azure 机器学习工作区(docs.microsoft.com/en-us/azure/machine-learning/concept-workspace)。
了解这些关于 Azure 机器学习不同术语和服务的微妙差异后,你就可以准备学习更多关于 Azure 中不同托管和自定义机器学习服务的内容了。
托管机器学习服务
如果你正在处理文本、图像、视频、语言或文档领域的明确定义的一般用途机器学习问题,那么 Azure 很可能已经为这个问题提供了一个托管机器学习服务。
托管机器学习服务非常易于使用,快速嵌入到应用程序中,通常不需要任何运营开销。这使得它们非常适合创建不需要收集训练数据、训练模型和在生产中部署模型部署的基于 AI 的应用程序或功能。最重要的是,托管机器学习服务不需要任何机器学习专业知识来构建基于机器学习的应用程序。
一些明确定义的机器学习问题的例子包括图像分类、图像标记、目标检测、人脸检测、手写识别、语音转文本和文本转语音转换、说话人识别、翻译、拼写检查、关键词和实体提取、情感分析、成人内容过滤和文档解析。
托管机器学习服务通常与预训练模型一起使用,这些模型有时可以针对特定应用程序领域进行训练或微调。在托管机器学习服务中使用定制模型结合了托管服务的优势与自定义应用程序领域的灵活性。
在本节中,我们将探讨 Azure 认知服务、可定制的 AI 服务以及 Azure 应用 AI 服务。
Azure 认知服务
让我们从 Azure 最受欢迎的托管 AI 能力服务——Azure 认知服务开始。Azure 认知服务是一组 API,包含多个预训练的 ML 模型,用于解决以下类别中定义良好的常见问题——视觉、语言、语音和决策。
Azure 认知服务模型非常易于使用,可以通过任何编程语言中的单个 REST API 调用进行集成。这使得认知服务成为向现有应用程序添加机器学习能力的流行选择。以下是一些流行的认知服务示例:
-
视觉:计算机视觉和面部 API
-
语言:文本分析和翻译服务
-
语音:文本分析、语音转文本、文本转语音和语音翻译
-
决策:异常检测和内容审核
大多数认知服务 API 的工作方式非常相似。您首先在 Azure 中部署特定的认知服务(例如,计算机视觉和文本分析)或认知服务多服务帐户。一旦服务部署完成,您可以从服务中检索 API 端点和访问密钥,并使用您的数据和 API 密钥调用认知服务 API。这就是您需要做的所有事情,以丰富现有应用程序的 AI 能力。
为了让您了解这些服务是如何使用的,我们将通过计算机视觉认知服务的示例来引导您。我们将将其功能嵌入到一个简单的 Python 应用程序中。以下代码是调用计算机视觉认知服务 API 的示例。我们将使用免费 F0 层级的Analyze Image API从样本图像中提取类别、标签和描述。让我们从一些设置代码开始,以便我们稍后可以使用requests库并从认知服务 API 获取预测:
import requests
region='eastus2'
language='en'
version='v3.1'
key = '<insert access key>'
url = f"https://{region}.api.cognitive.microsoft.com" \
+ f"/vision/{version}/analyze"
在前面的代码片段中,我们定义了区域、语言、API 版本和认知服务 API 的访问密钥。您可以在 Azure 门户的服务概述或属性选项卡中找到这些详细信息。我们将使用这些组件来构建服务端点。接下来,让我们定义 API 调用的参数,包括指向埃菲尔铁塔图片的 URL:
params = {
'visualFeatures': 'Categories,Tags,Description',
'language': language
}
headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': key
}
payload = {
'url': 'https://../Eiffel_Tower.jpg'
}
剩下的唯一事情就是用所有参数和图片 URL 调用请求。我们得到一个包含多个模型得分的 JSON 响应:
response = requests.post(url,
json=payload,
params=params,
headers=headers)
result = response.json()
print(result)
如前述代码示例所示,使用认知服务归结为发送一个 HTTP 请求。在 Python 中,这很简单,使用requests库。响应体包含标准 JSON 并编码了认知服务 API 的结果。API 的 JSON 输出将具有以下结构:
{
"categories": [...],
"tags": [...],
"description": {...},
"requestId": "...",
"metadata": {
"width": 288,
"height": 480,
"format": "Jpeg"
}
}
categories键包含对象类别及其派生分类,例如地标检测结果,包括置信度分数。在埃菲尔铁塔图像的例子中,认知服务检测到一个得分为 95%的建筑,并几乎以 100%的置信度将其识别为地标:
"categories": [
{
"name": "building_",
"score": 0.9453125,
"detail": {
"landmarks": [
{
"name": "Eiffel Tower",
"confidence": 0.99992179870605469
}
]
}
}
]
tags键显示了与整个图像相关的多个标签。此外,每个标签都附带一个置信度分数。正如我们在 API 的响应中可以看到的,模型对图片是在户外拍摄的非常有信心:
"tags": [
{
"name": "outdoor",
"confidence": 0.99838995933532715
},
{
"name": "tower",
"confidence": 0.63238395233132431
}, ...
]
最后,description标签为你提供了更多标签和自动生成的图像标题。这很酷,不是吗?想象一下,仅通过使用 Azure 认知服务提取图像标签并对每个图像 URL 的标签进行索引,你可以多快地实现基于标签的图像搜索:
"description": {
"tags": [
"outdoor", "building", "tower", ...
],
"captions": [
{
"text": "a large clock tower in the background with Eiffel Tower in the background",
"confidence": 0.74846089195278742
}
]
}
认知服务计算机视觉 API 的结果只是这个服务如何被使用的例子之一。我们从 API 请求了类别、标签和描述的图像特征,这些特征作为 JSON 对象的键返回。每个类别和标签预测都返回结合置信度值的前几项结果。某些类别可能会触发其他检测模型,例如人脸、手写文本识别和 OCR。
重要提示
你可以通过访问相应服务网站来探索和测试许多其他 Azure 认知服务 API。以下是一些示例:
azure.microsoft.com/en-us/services/cognitive-services/computer-vision/
azure.microsoft.com/en-us/services/cognitive-services/language-service/
azure.microsoft.com/en-us/services/cognitive-services/speech-to-text/
使用前面的示例,通过requests调用 Azure 认知服务,你可以实现一个方法,通过将前面的代码片段包装在analyze()方法中并应用于数据集中的所有图像,自动为零售应用程序中的产品图像添加图像标题:
for url in product_image_urls:
res = analyze(url, key, features=['Description'])
caption = res['description']['captions'][0]['text']
print(caption)
你可以看到,这是将可扩展的基于深度学习的图像分析服务(例如为图像创建标题)集成到自定义应用程序中的最快方式。如果你对此感兴趣,那么是时候也尝试其他认知服务 API 了。
所有 Azure 认知服务都有一个共同点——它们使用预训练的黑色盒机器学习模型来执行单个机器学习任务的预测。当我们处理人脸或照片时,这是可以的,但当处理特定应用领域,如医学图像时,可能会出现问题。在这种情况下,您会很高兴地听到,您可以通过提供定制训练数据来微调一些认知服务以适应您的定制应用领域。让我们在下一节中更详细地探讨这些可定制服务。
定制认知服务
认知服务的一个主要缺点是您只能使用 API 提供的功能。这意味着您不能自定义图像分类 API 中的标签或标记,或者例如使用模型来分类不同类型的材料。要这样做,您需要在认知服务 API 中自定义模型——这正是某些定制认知服务允许您做的。
这里是一个流行的可定制认知服务 API 列表,您可以使用自己的训练数据将其微调到特定的应用领域:
-
愿景:Azure Custom Vision
-
语言:语言理解和 QnA Maker
-
语音:定制语音到文本
-
语音:定制文本到语音
-
语音:说话人识别
-
决策:Azure Personalizer
前述的每个服务都提供了一个接口,用于使用您自己的领域特定训练数据来训练或定制内置的机器学习模型。在这本书中,我们不会详细介绍这些服务中的每一个,而是将关注这些可定制认知服务的两个示例——Azure Personalizer 和 Custom Vision。Azure Personalizer 是一个有趣的服务,它允许您通过强化学习来优化在线推荐引擎。我们将在第十三章“在 Azure 中构建推荐引擎”中更详细地探讨 Azure Personalizer,并将其与其他最先进的推荐系统进行比较。
在本章中,让我们以 Azure Custom Vision 服务为例,探讨 Azure 中可定制的托管 AI 服务。Azure Custom Vision 允许您使用自己的训练数据微调预训练的机器学习模型。这个过程被称为迁移学习,通常在机器学习中用于将先前学习的特征提取能力转移到新的目标或领域。
Azure Custom Vision 提供了一个用户界面来上传和分类您的图像(或标记您的对象),然后通过按按钮使用最先进的计算机视觉模型来训练模型。图 2.3显示了 Azure Custom Vision 服务中对象检测模型的完成训练:

图 2.3 – Azure Custom Vision 训练结果
您可以在前面的图中看到,训练过程就像点击右上角的训练按钮一样简单,此时已启用快速测试选项,或者使用高级选项自定义训练过程。您无需编写任何代码或选择要优化的错误度量标准;所有这些都将由系统为您管理。在屏幕截图中,您可以查看训练结果,其中包括在验证集上自动计算的三个指标。通过移动左上角的分类概率阈值,您甚至可以调整权重,以实现更高的精确度或更高的召回率,具体取决于您是想避免误报还是最大化真阳性。
这为您提供了预训练托管认知服务的功能,同时具有自定义应用领域的灵活性。一旦模型训练并发布,就可以像我们使用认知服务一样使用 REST API。点击requests库:
import requests
def score(img_url, key, project_id, iteration_name):
endpoint = 'https://%s.api.cognitive.microsoft.com' \
+ '/customvision/v3.0/Prediction/%s' \
+ '/detect/iterations/%s/url' \
% (region, project_id, iteration_name)
headers = {
'Content-Type': 'application/json',
'Prediction-Key': key
}
payload = { 'url': img_url }
r = requests.post(url, json=payload, headers=headers)
return r.json()
在前面的代码中,我们实现了一个与我们在认知服务中使用过的函数非常相似的功能。实际上,只有端点和requests参数发生了变化。我们现在可以像以前一样调用该函数:
url = 'https://../Material_Experiment_1.jpg'
key = '<insert api key>'
project_id = '<insert project key>'
iteration_name = 'Iteration2'
res = score(url, key, project_id, iteration_name)
print(res)
响应也是一个 JSON 对象,现在看起来如下所示:
{
"Id":"7796df8e-acbc-45fc-90b4-1b0c81b73639",
"Project":"00ae2d88-a767-4ff6-ba5f-33cdf4817c44",
"Iteration":"59ec199d-f3fb-443a-b708-4bca79e1b7f7",
"Created":"2019-03-20T16:47:31.322Z",
"Predictions":[
{
"TagId":"d9cb3fa5-1ff3-4e98-8d47-2ef42d7fb373",
"TagName":"defect",
"Probability":1.0
},
{
"TagId":"9a8d63fb-b6ed-4462-bcff-77ff72084d99",
"TagName":"defect",
"Probability":0.1087869
}
]
}
前面的响应现在包含一个Predictions键,其中包含来自自定义视觉的所有预测类别和置信度值。如您所见,示例看起来与认知服务示例非常相似。然而,我们需要传递参数来指定训练模型的项目和发布迭代。使用这个内置的托管 API,我们节省了大量实现和运营部署基础设施的努力。如果我们想在其他地方使用训练好的模型(例如,在 iPhone 或 Android 应用程序中,或在 Kubernetes 集群中),我们可以以许多不同的格式导出模型,例如 TensorFlow、TensorFlow.js、Core ML 和 ONNX。
自定义认知服务是高效测试或展示针对特定应用领域的机器学习模型的一个绝佳方式,尤其是在处理定义明确的机器学习问题时。您可以使用 GUI 或 API 与这些服务交互,并通过托管 API 消费模型或将它们导出到任何设备平台。另一个好处是,您不需要深厚的机器学习专业知识来应用迁移学习算法,只需简单地使用预定义的模型和错误度量标准即可。
Azure 应用人工智能服务
在前面的章节中,我们展示了 Azure 认知服务的示例,包括完全预训练的模型和可定制的模型。在本节中,我们将扩展可定制的托管人工智能服务的列表,包括所有归入Azure 应用人工智能服务名称下的服务。这些应用人工智能服务——就像自定义认知服务一样——是预训练的可定制人工智能服务,它们以一个共同名称松散地分组,以构建专业服务。
这些应用人工智能服务都是由于大型企业客户对这些特定服务有强烈需求,由微软在认知服务之上开发的服务。以下服务目前是应用人工智能服务的一部分,但与认知服务不同,它们并没有很好地归类。以下是可以用来为特定应用构建自定义模型的适用人工智能服务列表:
-
对话:Azure 机器人服务
-
文档:Azure 表单识别器
-
搜索:Azure 认知搜索
-
监控:Azure 度量顾问
-
视频:Azure 视频分析器
-
无障碍:Azure 沉浸式阅读器
我们不会详细介绍这个列表中的每个服务,但如果其中一些服务让你感到好奇,我们鼓励你深入了解。你可以在 Azure 文档(docs.microsoft.com/en-us/azure/applied-ai-services/)或 Azure 应用人工智能服务产品页(azure.microsoft.com/en-us/product-categories/applied-ai-services)中找到详细信息和示例。Azure 表单识别器和 Azure 认知搜索都使用认知服务图像 API 从文档中提取文本和手写笔记。前者帮助你从结构化文档中解析这些数据,而后者在所有提取的数据上创建搜索索引,并提供了对非结构化文档的全文搜索,包括手写文档。
如你所见,如果你有这些相同的问题,那么使用这些应用人工智能服务并将其集成到你的应用中很容易。虽然应用领域有限,但你可以在处理这些用例的任何项目中大大加速项目。
如果你需要完全自定义算法、模型和错误度量,你需要自己实现模型和机器学习管道。在接下来的章节中,我们将讨论如何在 Azure 中使用自定义机器学习服务来实现这一点。
自定义 ML 服务
Azure 为不同的专业领域提供了许多 PaaS 服务。平台服务建立在 IaaS 服务之上,并实现了相关领域常用的一些有用的抽象和功能。ML 就是一个这样的领域,在那里你可以找到用于构建自定义 ML 模型的各种服务。在本节中,我们将查看最受欢迎的自定义 ML PaaS 服务。
我们将首先介绍基于 GUI 的解决方案 Azure 机器学习工作室(经典)和 Azure 机器学习设计器,然后切换到基于 GUI 和 API 的 Azure 自动化机器学习。最后,我们将查看 Azure 机器学习,这是为前两种服务提供资源和资产工作空间的服务。
Azure 机器学习将帮助我们创建用于编写的笔记本实例,训练用于训练的集群,上传和注册数据集,跟踪实验和训练好的模型,以及跟踪我们的 Conda/PIP 环境和 Docker 镜像。
Azure 机器学习工作室(经典版)
Azure 机器学习工作室(经典版)是 Azure 中广泛采用的一个工具,用于使用 GUI 和拖放、基于块的编程模型构建、训练、优化和部署 ML 模型。它是 Azure 中最早的托管云服务之一,通过 R 和 Python 支持提供强大的功能和大量特性、算法和扩展。该服务提供了用于聚类、回归、分类、异常检测和推荐的内置构建块,以及数据、统计和文本分析。你还可以通过使用 Python 或 R 的自定义代码块来扩展 Azure 机器学习工作室的功能。
重要提示
Azure 机器学习工作室(经典版)将于 2024 年 8 月 31 日退役,客户将不得不过渡到 Azure 机器学习。因此,我们强烈建议在 Azure 机器学习中开始任何新的项目。
图 2.4展示了 Azure 机器学习工作室(经典版)主要拖放 GUI 的概述:

图 2.4 – Azure 机器学习工作室(经典版)
可以从左侧的目录中选择功能块,将其拖放到右侧的画布上,并连接起来形成一个复杂的计算图。每个块都可以定义输入和输出数据,这些数据通过其他块的连接传递。
Azure 机器学习工作室(经典版)允许你从许多不同的来源导入数据,例如来自 Azure Blob 存储的 CSV 文件或直接从 SQL Server、Azure Cosmos DB 或 Apache Hive 导入。它还提供了许多内置块用于转换常见的数据格式和数据类型、归一化和清理。
Azure 机器学习工作室(经典版)之所以非常受欢迎,其中一个原因在于其部署能力。如果你已经创建了一个数据管道并训练了一个模型,你可以在机器学习工作室(经典版)中保存训练好的模型。现在,只需几点击,你就可以使用训练好的模型创建一个 Web 服务来部署评分服务。用户输入是通过用于训练数据的相同数据导入块定义的。它可以连接到将用户输入传递到管道或返回模型预测到 Web 服务的管道。再点击一下,你就可以使用 Web 服务计划将管道部署到生产环境中。
虽然 Azure Machine Learning Studio 是一个基于 GUI 的、用于构建 ML 管道和简单基于 Web 的 ML 应用的非常受欢迎的工具,但它并不是编写自定义 ML 应用的首选工具。工作区可能会很快变得复杂,这将使得跟踪数据流通过管道变得困难。另一个缺点是,对于更大的管道,块内自定义代码的组织变得困难,并且与其他 Azure 服务的集成有限。最后,经过多年的服务,Azure Machine Learning(经典版)将在 2024 年停止使用。
如果你正在寻找类似类型的基于块的编程,具有更好的代码组织和管道支持,以及更好的 Azure 集成,那么你应该考虑 Azure Machine Learning 设计师。
Azure Machine Learning 设计师
虽然 Azure Machine Learning Studio(经典版)非常受欢迎且功能丰富,但其与其他 Azure 服务的集成一直有限。从不同的数据源中摄取和预处理数据并不容易,管理访问和共享数据集很困难,而且定制仅限于 Azure Machine Learning Studio(经典版)。然而,随着 Azure Machine Learning 的创建,微软也对旧的 Studio 进行了翻新,并在 Azure Machine Learning 内部创建了一个名为设计师的新版本。
Azure Machine Learning 设计师 与 Azure Machine Learning 完全集成,因此可以访问并共享工作区内的所有资源和资产。它允许基于 GUI 创建 ML 管道,同时与其他数据工程师和数据科学家在同一工作区中进行协作。他们都可以共享相同的计算资源,这些资源会根据开发者的需求自动扩展和缩减。
图 2.5 展示了设计师的用户界面,它与 Azure Machine Learning Studio(经典版)相同的基于块、拖放的用户界面:

图 2.5 – Azure Machine Learning 设计师用户界面
如前图所示,通过图形数据流创建 ML 过程仍然具有之前讨论过的相同缺点。然而,我们至少可以与其他工作区用户共享数据摄取、预处理、清洗和特征提取阶段,并专注于设计师中的 ML 任务。
用于创建基于块的机器学习训练管道的图形用户界面并不适合所有人。然而,如果你更喜欢基于块的拖放环境,那么 Azure 机器学习设计器就是你的正确选择。更重要的是,你所有的作品都存储在 Azure 机器学习工作区中,这意味着你可以轻松地将基于 GUI 的管道的部分扩展或迁移到基于代码的版本,反之亦然。总的来说,使用设计器在 Azure 机器学习中开始你的机器学习项目是一个不错的选择。然而,如果你想构建一个可扩展的机器学习项目,允许多个团队协作,建议使用非 GUI 服务,如我们将在整本书中使用的 Azure 机器学习工作区。
Azure 自动化机器学习
每个用户都应有机会创建预测模型并将符合条件的数据集转换为机器学习模型。这是人工智能的民主化,任何能够使用电子表格应用程序的用户都有可能从电子表格中的数据创建机器学习模型,而无需任何机器学习专业知识。
这就是Azure 自动化机器学习发挥作用的地方!Azure 自动化机器学习是一个无代码工具,允许你指定数据集、目标列和机器学习任务,从电子表格中训练机器学习模型。对于只想将训练数据拟合到目标变量而无需了解特征提取、建模、训练和优化的用户来说,这是一个很好的抽象。类似于 Azure 机器学习设计器,自动化机器学习是一个可以从 Azure 机器学习工作区创建的服务,因此可以访问工作区中定义的所有资源和资产。
值得注意的是,典型的电子表格用户并不是唯一的目标群体,他们使用自动化机器学习来自动训练、优化和堆叠机器学习模型。自动化机器学习是超参数调整的自然扩展,其中模型架构和预处理本身也成为超参数。我们将在第十一章“超参数调整和自动化机器学习”中更详细地探讨这个应用领域及其 Python API。
图 2.6显示了自动化机器学习界面的最后一步,用户需要为指定数据选择要解决的机器学习任务:

图 2.6 – 自动化机器学习
如前图所示,自动化机器学习目前支持分类、回归和时间序列预测任务。结合每个任务的详细说明,这是我们可以交给 Excel 用户并帮助机器学习工程师快速构建和部署优秀基线模型的东西。
此外,自动机器学习还为您提供了访问所有训练运行、所有训练模型及其训练分数,以及有用的内置指标、可视化和洞察的机会。在 图 2.7 中,我们可以看到 ROC 曲线作为训练运行许多内置可视化中的一个示例:

图 2.7 – 自动机器学习结果的接收者操作特征 (ROC) 曲线
重要提示
您还可以通过 Azure 机器学习 SDK 直接从您的创作环境以编程方式访问自动机器学习。您可以在 Microsoft 文档中找到有关自动机器学习功能的更多信息:docs.microsoft.com/en-us/python/api/azureml-automl-core/azureml.automl.core?view=azure-ml-py。
自动机器学习是一项出色的服务,提供了一个真正的机器学习即服务平台,为非经验丰富的和技能高超的用户提供了合理的抽象。这项服务使每位开发者都能利用机器学习,并将为未来产品的 AI 功能提供动力。
Azure 机器学习工作区
Azure 机器学习 是 Azure 的旗舰机器学习服务,用于实现和自动化构建定制机器学习应用程序的端到端机器学习过程的全部步骤。它最初是为了将所有其他机器学习服务组合在一个工作区下,并促进资源、资产和权限的共享——因此,也常被称为 Azure 机器学习工作区。
目前,Azure 机器学习提供了、组合并抽象了许多重要的机器学习基础设施服务和功能,例如跟踪实验运行和训练作业、基于 conda/pip 和 Docker 的模型注册、环境和容器注册、数据集注册、管道以及计算和存储基础设施。它还实现了一套通用的身份和权限,以方便从 Azure 工作区内部访问这些单个组件。
除了所有基础设施服务之外,它还将在单个工作区中集成 Azure 自动机器学习、Azure 机器学习设计器(新的 Azure 机器学习工作室(经典))以及一个数据标注服务。工作区中的所有服务都可以访问和共享资源。Azure 机器学习提供了许多有用的抽象和功能,以开发定制的机器学习应用程序,并在灵活性、易用性和价格之间取得了很好的平衡。因此,它也是我们在 Azure 中构建定制机器学习解决方案的首选服务,我们将在整本书中使用它。
图 2.8 展示了 Azure 机器学习工作室,这是 Azure 机器学习的用户界面。如前所述,这个名字不要与 Azure 机器学习工作室(经典)混淆,后者是旧的基于 GUI 和块的 ML 服务。

图 2.8 – Azure 机器学习工作室
正如前一个图所示,我们可以在 Azure 机器学习工作区中管理不同的资源和资产。所有这些资源不仅可以通过 UI 访问,还可以通过 SDK 和 Azure 机器学习 CLI 访问。在这本书中,我们将主要使用 Azure 机器学习的 Python SDK。您可以在 Microsoft 文档中找到有关 Azure 机器学习 Python SDK 的更多信息:docs.microsoft.com/en-us/python/api/overview/azure/ml/?view=azure-ml-py。
在这本书中,我们将使用三种类型的计算资源来处理 ML 流程中的不同步骤。我们可以使用几行代码和 Azure 机器学习 SDK 直接在 Azure 机器学习中创建这些资源:
-
用于编写运行时和 Jupyter 的计算实例:这是一个预安装和预配置了 ML 库以及针对编写和实验优化的 Azure 机器学习 SDK 的计算实例。
-
训练期间用于 ML 执行运行时的训练集群:这是一个自动可扩展的计算集群,预安装和预配置了 ML 库以及针对大规模训练和优化的 Azure 机器学习 SDK。
-
评分期间用于执行运行时的推理集群:这是一个使用 Azure Kubernetes 服务管理的托管 Kubernetes 集群。
除了计算资源,我们还将使用 Azure 机器学习创建存储资源,这些资源作为编写和应用程序代码、作业日志和输出、可视化、训练模型、数据集快照等的存储。我们可以使用 ML SDK 来管理 ML 工作区中的 Azure Blob 存储容器,并将作业的输出和资产直接写入存储。
除了管理基础设施,Azure 机器学习还能为我们做更多的事情。最重要的是,它可以跟踪我们的实验运行并收集输出文件、图表、工件、日志和自定义指标,例如训练损失。这到目前为止也是进入 Azure 机器学习平台最强大的门户。
通过简单地注释你的现有机器学习项目,你可以跟踪所有你的模型得分,流式传输你的日志输出,收集所有你的输出图像,并存储每个迭代或运行的最好模型。你所需要的只是一些简单的代码行,以防止再次丢失特定训练运行的模型,或者跟踪你的训练得分、图表和工件。所有这些都可以在不改变你的机器学习设置的情况下完成;你的实验可以在本地机器上运行,你的训练运行可以安排在 AWS 上。
除了跟踪作业工件外,你还可以使用几行代码在 Azure 机器学习中跟踪数据集版本、环境和模型。这让你能够保持工作区中变化的可预测历史记录。通过这样做,你可以创建可重复的实验,这些实验总是为训练运行读取相同的数据快照,使用相同的指定 Conda 或 PIP 环境,并在模型历史和工件存储中更新训练模型。这使你朝着持续集成/持续部署(CI/CD)方法前进。我们将在第十六章中更详细地讨论这种方法,使用 MLOps 将模型投入生产。
说到管道,Azure 机器学习让你可以将你的编写代码抽象成管道。一个管道可以并行触发或运行数据准备作业,创建并启动训练集群,在集群上执行训练脚本,或者启动和执行蓝/绿部署。你可以看到所有这些是如何引导你走向一个可重复、版本化的端到端管道,用于你的训练过程。然而,最棒的部分是,你不必全力以赴就能从 Azure 机器学习中受益。
相反,你可以逐步开始,逐渐将更多有用的功能添加到现有的训练过程中,然后将现有或新的机器学习项目逐步迁移到 Azure 机器学习工作区。你将在下一章中了解如何入门,如何与现有的机器学习项目集成,以及如何为新项目设置编写和训练环境。
Azure 机器学习是 Azure 中构建自定义机器学习应用的最好 PaaS 服务。然而,如果你更喜欢在虚拟机上动手,调试分布式作业执行,并为分布式训练作业设置 MPI,你应该仔细阅读下一节,我们将了解更多关于常用作机器学习的自定义计算服务。
机器学习自定义计算服务
到目前为止,我们已经查看了一些提供带或不带一定程度的定制化管理的预训练机器学习模型的服务,以及包括 Azure 机器学习在内的定制机器学习服务。由于在灵活性、功能性和舒适性之间取得了很好的平衡,Azure 机器学习成为了我们开发定制机器学习应用的首选服务。
然而,我们理解这些权衡可能并不适用于每个人,有些人可能希望仅使用 IaaS 服务来构建定制机器学习应用时拥有最高的灵活性。这些服务正是构建 Azure 中任何其他 PaaS 服务(包括 Azure 机器学习)基础的服务。因此,作为最后一步,我们将深入了解您可以使用 Azure 中的自定义计算服务来构建灵活机器学习解决方案的选项。
Azure Databricks
Azure Databricks 是 Azure 上的一个托管服务,提供 Databricks 平台作为一个完全集成的解决方案。因此,Azure Databricks 在 Azure 中被称为一等公民。这意味着,与其他第三方解决方案相比,用户可以从 Azure Marketplace 部署,并且与 Azure Active Directory 完全集成,允许 Azure 管理员以与其他平台上的任何其他 Microsoft 托管服务相同的方式处理此服务。
Databricks 平台本身是一个利用 Apache Spark 的大数据分析平台。该平台的背后公司也称为 Databricks(databricks.com/),由 Spark 的原始创造者创立,旨在将这种不断变化的开源技术作为现成的产品提供给客户。
要了解如何在 Azure Databricks 中执行机器学习,我们首先将查看所有计算和处理的底层分布式计算技术——Apache Spark。
使用 Apache Spark 进行分布式计算
Apache Spark 是一个分布式内存分析引擎,其根源来自 Apache Hadoop 框架。其背后的主要思想是将计算图分布到集群的工作节点。将这些节点想象成不同的独立服务器,甚至可能位于不同的物理位置,它们共同完成同一项工作,或者更准确地说,完成各自的工作部分。它们反过来又由一个主节点控制和管理,该节点负责监控调度、资源可用性和数据流的连接。
图 2.9展示了 Apache Spark 最重要的组件。在中间,我们可以看到主计算引擎,称为 Spark Core。Spark Core 负责作业调度和监控、与底层存储系统的交互、节点上的内存管理以及整个集群的通用容错。对于调度,它要么使用自己的调度器,即Spark 调度器,要么可以在其他调度选项上运行,即Apache YARN或Apache Mesos。当在 Azure Databricks 中使用 Apache Spark 时,作业调度引擎是托管服务的一部分,由 Databricks 管理:

图 2.9 – Apache Spark 框架
作为存储系统,它支持众多选项,从标准的本地存储和Hadoop 分布式文件系统(HDFS)到 Azure Data Lake 和 Amazon S3 存储,甚至可以直接访问关系型数据库管理系统(RDBMS)和 NoSQL 系统的文档。
最后,为了定义和调度作业,最终用户可以利用不同的编程语言,如 Scala、Python 和 R,来定义将通过 Apache Spark 执行的计算图。除了所有可用的库和框架之外,Apache Spark 还提供了一些内置库,以方便通过 Spark SQL 进行数据访问和处理,以及通过 Spark Streaming、MLlib 和 GraphX 进行分布式计算。
Azure Databricks 的机器学习库
要在 Spark 和随后在 Azure Databricks 上训练机器学习模型,我们需要库,这些库一方面实现了相关的机器学习算法和数值函数,另一方面理解 Spark 框架以利用分布式计算原语。
Apache Spark 自带一个名为 MLlib 的内置机器学习库。这个库旨在实现传统的机器学习算法,例如不同的聚类和嵌入技术、逻辑回归、随机森林、梯度提升以及用于推荐的交替最小二乘法(ALS)矩阵分解,同时利用 Apache Spark 的分布式计算能力。
由于支持的语言,您还可以在 Azure Databricks 上使用 Apache Spark 中的所有其他流行机器学习库,如 TensorFlow、XGBoost、scikit-learn、PyTorch、Horovod 以及许多其他知名库(见databricks.com/product/machine-learning-runtime)。
Azure Databricks 还支持 MLflow,这是一个开源框架,用于自动化端到端的机器学习过程,我们将在第十六章中看到其实际应用,使用 MLOps 将模型投入生产,以及他们自己的 AutoML 版本和笔记本服务器。
然而,大规模分布式计算引擎通常不会没有缺点,Apache Spark 和 Databricks 也是如此。虽然 Databricks 在隐藏大部分复杂性并使 Spark 易于启动和运行方面做得很好,但复杂性并未消失。没有深入了解 Spark,监控作业和利用集群资源、调试和优化作业,以及阅读和理解日志变得非常复杂。
简而言之,除了理解机器学习过程和算法之外,用户还必须了解 Spark 的内部结构和其分布式作业调度和执行模型。这为运行、调试和优化机器学习作业增加了另一层复杂性,使得整个过程变得更加困难。
此外,并非所有机器学习库和算法都易于将工作负载分配到不同的节点,这通常会导致集群资源利用率不佳。为什么要在单个工作节点上执行底层算法时,还要使用复杂的分布式计算框架并支付主要编排节点的额外费用?
当将本地 Spark 服务迁移到 Azure 或构建大数据分析、转换或推荐服务时,Azure Databricks 是一个不错的选择。然而,其复杂性和高昂的价格使其通常不是机器学习项目的最佳选择。
Azure Batch
Azure Batch 是一个非常成熟且灵活的批处理和调度框架,用于在 Azure 中运行大规模并行工作负载。它允许您定义可以在虚拟机池上调度和执行的自定义应用程序和作业。它处理存储在 Azure 存储中的数据,并可以为您动态扩展计算资源,最多可达数万台虚拟机。Azure Batch是 Azure 机器学习训练集群的基础,因此如果您想构建自己的定制机器学习服务,这是一个很好的解决方案。
Azure Batch 通常用于尴尬的并行工作负载,即可以轻松地在多台机器上并行化,而无需任何编排的工作。这使得 Azure Batch 比提供分布式协调原语的 Azure Databricks 更不灵活,但因此对最终用户来说也更简单。典型应用包括计算 3D 渲染、视频和图像处理、计算密集型模拟或一般批处理计算,例如计算推荐结果或批评分机器学习模型。
批处理作业将在计算池或自定义虚拟机上执行,这意味着 Azure Batch 支持许多异构计算实例,包括高性能计算实例、内存优化和 GPU 启用虚拟机,仅举几例。它还支持使用消息传递接口(MPI)和远程直接内存访问(RDMA)的多实例工作负载。
如果你正在构建你的定制 ML 解决方案并希望避免 Azure 机器学习的舒适性和灵活性,那么 Azure 批处理是你一个很好的选择。它为你提供了所有选择自定义实例、框架、库和数据格式的灵活性。然而,Azure 机器学习在几乎每个方面都是一个更好、更简单、更集成的解决方案,特别是用于构建 ML 应用。
数据科学虚拟机
不需要单独的章节来解释,你可以在 Azure 中使用传统的虚拟机(VM)来在 IaaS 服务之上构建定制的基于云的机器学习(ML)服务。这将是云服务中最低级别的,在这里你可以完全控制每个网络接口、磁盘配置以及 VM 上的用户权限。你可以使用你所在区域提供的任何实例类型,以满足你的内存、计算或图形需求。
然而,如果你正在寻找一个作为你的云上 ML 工作站的 VM——例如,为了利用灵活的云计算、运行你的 ML 实验或执行按需 GPU 加速训练——使用标准 VM 之外还有更好的选择,那就是数据科学虚拟机(DSVM)。
DSVM 是一种预先构建和预配置的 VM,针对数据科学和 ML 应用进行了优化。它预装了许多流行的 ML 库,并支持 Windows 和 Linux。预装的库和服务包括 CUDA 和 cuDNN、NVIDIA 驱动程序和系统管理接口(nvidia-smi)、CRAN-R、Julia、Python、Jupyter、TensorFlow、PyTorch、Horovod、XGBoost、LightGBM、OpenCV 和 ONNX。你可以在许多不同的实例类型上启动 DSVM,包括 GPU 加速实例。
当你需要一个预装和预配置了流行 ML 工具的无忧 VM 时,DSVM 是你的首选服务。然而,值得注意的是,当你在一个 Azure 机器学习工作区工作时,你可能不需要 DSVM,因为你可以创建计算实例和训练集群来运行你的 ML 实验和训练。尽管如此,它仍然是一个出色的 ML 实验环境替代方案。
摘要
在本章中,你学习了如何导航 Azure AI 景观并选择适合你的应用程序和领域的正确 ML 服务。虽然 IaaS 服务为你提供了很大的灵活性,但 PaaS 服务通常提供了有用的抽象,并为你管理复杂的集成。如果它们是为你的应用程序领域设计的或可以定制,SaaS 应用是非常好的。
我们调查了 Azure 服务,用于构建每个先前类别中的 ML 应用,例如 Azure 认知服务(SaaS)、Azure 机器学习(PaaS)和 Azure 批处理(IaaS)。Azure 机器学习不仅是 Azure 中最全面和集成的 ML 服务,而且提供了灵活性、功能性和舒适性的良好平衡。因此,我们将在这本书中使用 Azure 机器学习来开发端到端的定制 ML 解决方案。
如果你真的想从头开始构建自己的机器学习基础设施,而不依赖任何托管机器学习服务,你应该考虑那些针对大型计算工作负载进行优化的自定义计算服务,例如 Azure Databricks 或 Azure Batch。如果你只需要一个用于机器学习实验的 VM,且不需要任何预构建的服务集成或模型和实验跟踪,你可以选择 DSVM。
在下一章中,我们将继续我们的旅程,通过设置 Azure 机器学习工作区来展开。为了做到这一点,我们首先将学习如何以编程方式在 Azure 中部署资源;然后我们将深入探讨机器学习工作区本身,了解我们如何使用笔记本并集成计算节点进行模型训练,最后,我们将运行我们的第一个小实验。
第三章:第三章:准备 Azure Machine Learning 工作区
在上一章中,我们学习了如何导航不同的 Azure 服务以在云中实现 ML 解决方案。我们意识到,对于程序化训练自定义 ML 模型并自动化基础设施和部署来说,最佳服务是 Azure Machine Learning 服务。在本章中,我们将设置并探索 Azure Machine Learning 工作区,创建云训练集群,并在本地和云计算上执行数据实验,同时收集 Azure Machine Learning 中 ML 运行的全部工件。
在第一部分,我们将学习如何使用不同的工具管理 Azure 资源,例如 Azure 命令行界面 (CLI)、Azure SDKs 和Azure 资源管理器 (ARM)模板。我们将设置并探索 Azure CLI 以及 Azure Machine Learning 扩展,然后部署 Azure Machine Learning 工作区。
然后,我们将通过探索作为 Azure Machine Learning 一部分部署的资源来深入了解 Azure Machine Learning,例如存储账户、Azure Key Vault、Azure Application Insights 和 Azure Container Registry。随后,我们将深入研究 Azure Machine Learning 并探索工作区,以更好地理解各个组件。
最后,在最后一部分,我们将把所有这些知识付诸实践,并使用 Azure Machine Learning 运行我们的第一个实验。在设置好我们的环境后,我们将增强一个简单的 ML Keras 训练脚本,将其指标、日志、模型和代码快照记录到 Azure Machine Learning。然后,我们将继续在本地机器以及 Azure 的训练集群上安排训练运行。
到本章结束时,您将在 Azure Machine Learning 工作区中看到所有成功的训练运行、指标和跟踪的模型,并且您将对 Azure Machine Learning 有一个良好的理解,从而开始您的 ML 之旅。
本章将涵盖以下主题:
-
部署 Azure Machine Learning 工作区
-
探索 Azure Machine Learning 服务
-
使用 Azure Machine Learning 运行 ML 实验
技术要求
在本章中,我们将使用以下 Python 库和版本在 Azure Machine Learning 上执行和管理实验运行:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
azureml-widgets 1.34.0 -
tensorflow 2.6.0
您可以使用本地 Python 解释器或 Azure Machine Learning 中托管的笔记本环境运行此代码。然而,某些脚本需要计划在 Azure 中执行。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter03。
部署 Azure Machine Learning 工作区
在我们能够深入探讨 Azure 上的机器学习之前,我们需要了解如何部署 Azure 机器学习工作区或 Azure 服务,支持哪些工具,以及我们将在整本书中使用哪一个来工作。
作为第一步,我们需要一个 Azure 订阅。
如果您在组织中工作并想使用您的工账户,您可以访问 portal.azure.com 并使用您的工账户登录。如果登录成功,您将进入门户本身,您的工账户将显示在右上角。这意味着您的公司已经设置了Azure Active Directory(AAD)实例。在这种情况下,如果您还没有,请与您的 Azure 全球管理员联系,讨论您应该使用哪个 Azure 订阅来满足您的需求。
如果您是 Azure 的新用户并想使用您的私人账户,请访问 azure.com 并点击免费账户创建一个带有免费试用订阅的 AAD。这个试用订阅在 30 天内为您提供了在 Azure 服务上花费的一定金额。
在任何情况下,最终,您应该能够使用您的身份登录到 Azure 门户,并且您应该知道您想要将机器学习服务部署到哪个 Azure 订阅(名称和/或订阅 ID)。
完成所有这些后,我们现在将看看如何部署和管理我们的 Azure 环境,以及有哪些选项和工具可供选择。
理解 Azure 部署的可用工具
在 Azure 中,任何部署或更改 Azure 服务的操作都通过所谓的 ARM(Azure 资源管理器)进行。如图图 3.1所示,ARM 接受来自Azure 门户、Azure PowerShell(PowerShell 扩展)、Azure CLI或Azure REST API的请求:

图 3.1 – Azure 资源管理器
在 Azure 门户中,您可以选择“机器学习”,结果集将显示一个来自微软的名为机器学习的服务。点击这张卡片然后创建将打开此服务的部署向导。这将让您了解部署此服务所需的内容。
但我们不会在门户本身上进一步讨论,因为我们想在这本书中促进一种更程序化的方法。使用这种方法将极大地提高在 Azure 中执行的所有任务的重复性和自动化。因此,我们将专注于后者的解决方案——让我们来看看它们:
-
Azure CLI:这是一个完整的命令行环境,您可以在每个主要操作系统上安装。最新版本可以从
docs.microsoft.com/en-us/cli/azure/install-azure-cli下载。 -
Azure Power Shell:正如其名所示,这是一个 PowerShell 模块库,可以添加到 PowerShell 环境中。以前,PowerShell 只在 Windows 上可用,但新的 PowerShell Core 7.x 现在正式支持主要的 Linux 版本和 macOS。以下描述展示了如何在您的系统上安装它:https://docs.microsoft.com/en-us/powershell/azure/install-az-ps。
-
curl或流行的 Pythonrequests库。以下文章描述了给定的语法:https://docs.microsoft.com/en-us/rest/api/resources/。
所有这些选项都允许使用所谓的 ARM 模板(https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview),这是 Azure 的 基础设施即代码(IaC)版本。它使您能够将基础设施定义保存到文件中并进行版本控制。在处理复杂的架构部署时,这种方式非常推荐,但我们不会深入探讨这个话题。这里要补充的唯一一点是,市场上还有其他用于 IaC 管理的工具。最突出的工具称为 Terraform(https://www.terraform.io/),它允许管理任何云供应商或本地环境的架构,包括 Azure。为了实现这一点,Terraform 在底层使用 Azure CLI。
总结来说,您可以选择上述任何一种选项来完成手头的任务,尤其是如果您对其中之一有强烈的偏好。
由于我们不会管理复杂的架构并希望避免任何不必要的额外复杂性层级,我们将在本书的其余部分使用 Azure CLI。此外,新的 ML CLI 扩展为 Azure 机器学习提供了一些实用的功能,我们将在本章中逐一发现:

图 3.2 – Azure CLI
如果您还没有做,请随意下载并安装或更新 CLI 到最新版本。准备好后,打开您喜欢的命令行或终端,并在控制台中输入 az。您应该会看到如图 3.2 所示的屏幕。
部署工作区
在对 ARM 进行了简短的介绍之后,让我们部署我们的第一个 ML 工作区。我们将使用 Azure CLI 来部署工作区。如果您想通过 Azure 门户部署它,可以遵循这个教程:https://docs.microsoft.com/en-us/azure/machine-learning/quickstart-create-resources。
如果您简要地浏览了 CLI 命令列表,可能会注意到似乎没有命令引用 ML。让我们纠正这一点,并按照以下步骤通过 CLI 设置我们的第一个 Azure 机器学习工作区:
-
通过 CLI 登录到您的 Azure 环境:
$ az login
此命令将打开一个带有 AAD 登录界面的网站。完成此操作后,请返回控制台。现在屏幕将显示有关您的 AAD 租户(homeTenantId)、您的订阅(id、name)以及您的用户的一些信息。
-
如果显示给您多个订阅,并且您需要检查哪个订阅是活动的,请使用以下命令:
$ az account show --output table
在输出中,检查IsDefault列是否显示为True以表示您首选的订阅。如果不是,请使用以下命令将其设置为所选订阅,输入其名称 – <yoursub> – 然后再次检查:
$ az account set --subscription "<yoursub>"
-
现在我们正在将部署到正确的租户中的正确订阅,让我们检查已安装扩展的情况。在您的终端中输入以下命令:
$ az extension list
如果列表中既没有显示azure-cli-ml也没有显示ml,则您缺少用于通过 CLI 使用 Azure 机器学习的扩展。第一个表示Azure ML CLI 1.0,第二个表示Azure ML CLI 2.0。ML CLI 的 2.0 版本在 2021 年 Microsoft Build 上宣布(techcommunity.microsoft.com/t5/azure-ai/announcing-the-new-cli-and-arm-rest-apis-for-azure-machine/ba-p/2393447),提供了对 ML 工作区的精细控制。因此,我们将使用新的 CLI 扩展版本。
重要提示
Azure ML CLI 2.0 提供了从命令行直接控制 ML 工作区的作业、集群和管道的新功能。它还提供了对 YAML 配置文件的支持,这对于 MLOps 至关重要。
-
如果您正在运行旧版本,应删除该版本,但请注意,由于一些命令略有不同,您可能会破坏您正在使用的脚本。要清理命名空间并删除旧版本,可以使用以下命令:
$ az extension remove -n azure-cli-ml $ az extension remove -n ml -
让我们使用以下命令安装 ML 扩展:
$ az extension add -n ml
之后,您可以随意再次检查已安装的扩展。
-
现在,我们将能够使用它。首先,我们将查看扩展的帮助页面:
$ az ml -h
这将显示以下子组:
code: Manage Azure ML code assets.
compute: Manage Azure ML compute resources.
data: Manage Azure ML data assets.
datastore: Manage Azure ML datastores.
endpoint: Manage Azure ML endpoints.
environment: Manage Azure ML environments.
job: Manage Azure ML jobs.
model: Manage Azure ML models.
workspace: Manage Azure ML workspaces.
如您所见,我们有大量的选项可以从 CLI 控制我们的工作区。我们将在本书的后面回到许多这些选项。现在,我们感兴趣的是管理我们的工作区。
-
如果您输入以下命令,我们将查看是否仍缺少创建 ML 工作区所需的要求:
$ az ml workspace create -h
在查看参数时,您会看到需要一个资源组。Azure 中的资源组是一个逻辑结构,资源需要部署到其中。它是Azure 管理层次结构的一个重要部分。有关进一步阅读,请参阅 Azure 中的访问管理:https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-setup-guide/organize-resources。
此外,如果您在控制台输出中向下滚动到示例,您还会看到新的 CLI 版本有一个整洁的特性,允许我们从 另一种标记语言 (YAML) 文件中部署工作区。我们现在不会这样做,但这是需要记住的事情。
重要提示
Azure 机器学习服务可以完全使用 Azure ML CLI 2.0 扩展、YAML 配置文件和训练或推理脚本来操作。
-
Azure 中的资源组也需要一个位置。因此,让我们运行以下命令来查看 Azure 云可用的数据中心位置:
$ az account list-locations -o table
查看您首选区域的名称,并在以下命令中使用它来创建资源组。我们在此的示例将在西 US 2 创建一个名为 mldemo 的资源组:
$ az group create -n mldemo -l westus2
重要提示
尽管我们定义资源组位于西 US 2,但资源组内的资源可以位于不同的区域。这只是将组定义在特定区域并让该组内的资源位于同一区域的最佳实践。
-
现在,我们可以使用以下命令创建工作区本身:
$ az ml workspace create -w mldemows -g mldemo -l westus2
这将在 mldemo 资源组中创建一个名为 mldemows 的工作区。如果我们删除位置设置,它将采用资源组的位置。
此命令可能需要一些时间。完成后,您将看到如下输出:
AppInsights Done (7s)
StorageAccount ... Done (31s)
KeyVault Done (23s)
Workspace ................ Done (1m 49s)
Total time : 2m 26s
{
"application_insights": "/subscriptions/... ",
"description": "mldemows",
"discovery_url":"https://westus2.api.azureml.ms/discovery",
"friendly_name": "mldemows",
"hbi_workspace": false,
"key_vault": "/subscriptions/... ",
"location": "westus2",
"mlflow_tracking_uri": "azureml://westus2.api.azureml.ms/mlflow/v1.0/subscriptions/... ",
"name": "mldemows",
"storage_account": "/subscriptions/... ",
"tags": {}
}
如您所见,前面的命令创建了多个资源,包括 Azure 机器学习工作区,这些资源对于运行 ML 实验是必需的。我们将在下一节中回到这些原因。
-
最后,要查看任何时间点的部署,您可以运行以下命令:
$ az ml workspace show -g mldemo -w mldemows
我们已创建了第一个 Azure 机器学习工作区。做得好!在下一节中,我们将探讨这包含哪些内容。
探索 Azure 机器学习服务
在我们继续设置自己的开发环境并进行一些 ML 之前,我们将查看除了主要工作区之外已部署的内容,了解服务中所有可用的功能的基础知识,这些功能我们将贯穿整本书,并首次简要了解 Azure 机器学习 工作室。
分析已部署的服务
我们将首先再次导航到 Azure 门户。在那里,在顶部搜索栏中键入工作区的名称为 mldemows。您应该会看到类似于 图 3.3 中所示的结果:

图 3.3 – Azure 门户中搜索 ML 工作区
如您所见,除了主要的 mldemows 工作区外,还部署了三个其他服务,即存储账户、密钥保管库和应用程序洞察。由于它们大多数需要唯一的名称,您将在每个名称的末尾看到一个随机的字母数字代码。对于这些附加服务中的每一个,我们可以在部署工作区时提供我们自己的已存在服务。
此外,在稍后的阶段将需要Azure 容器注册库,但在工作区初始部署期间不需要它。
现在我们知道了部署了哪些附加服务,让我们来讨论它们为什么存在。
机器学习工作区的存储账户
存储账户,通常被称为默认存储账户,是工作区的主要数据存储。这种存储对于服务的运行至关重要。它存储了实验运行、模型、快照,甚至源文件,如 Jupyter 笔记本。我们将在第四章中更深入地探讨默认工作区存储、Azure 中的许多其他数据存储以及它们如何集成,数据摄取和管理数据集。
重要提示
请注意,如果你想在部署工作区时使用自己的存储账户作为默认存储,它不能有分层命名空间(Azure 数据湖)并且不能是高级存储(高性能 SSD)。
机器学习工作区的 Azure 密钥保管库
密钥保管库是一种云托管服务,可以存储诸如密码、API 密钥、证书和加密密钥等密钥。服务中的密钥要么存储在软件保管库中,要么存储在托管的硬件安全模块(HSM)中。对于机器学习工作区以及任何其他服务,将访问密钥存储在安全环境中至关重要。
到目前为止,我们只处理了相对不重要的信息,例如订阅 ID,但如果我们想从外部存储中拉取数据,我们可能需要一个访问它的密钥或调用另一个服务中的函数,该服务安全地存储了这些信息。你可以判断哪种选择更好。
机器学习工作区的开发者选择了后者。因此,需要一个 Azure 密钥保管库来存储工作区的内部密钥,并为你提供存储任何必要密钥的可能性,以便读取数据集、在计算目标上进行机器学习训练以及将最终模型部署到内部或外部目标。
现在,可能会出现如何安全访问密钥保管库本身的问题。这是通过所谓的管理身份来完成的,它为工作区(应用程序)本身提供了一个身份来分配权限。
Azure 上的管理身份
管理身份是赋予应用程序的身份,其行为方式与用户身份相同。
与其他服务一样,在部署过程中,你可以没有任何限制地将已经存在的密钥保管库链接到应用洞察。
机器学习工作区的应用洞察
应用程序洞察是Azure Monitor的一个模块,而Azure Monitor是 Azure 中用于监控基础设施和应用程序的套件,它存储和显示基础设施指标,如 CPU 使用率和应用程序的日志文件。
Azure 机器学习工作区使用 Application Insights 存储计算基础设施日志、ML 脚本日志以及 ML 模型运行的定义度量标准,因此对于工作区的操作是必需的。
Azure 容器注册表用于 ML 工作区
Azure 容器注册表(ACR)是基于 Docker 注册表的服务。它用于存储和管理 Docker 容器镜像和工件。对于工作区,当我们在本地机器之外的计算机上开始运行训练或部署模型时,需要该注册表。在这个过程中,容器被打包并注册到 ACR,然后可以在 ML 脚本或部署管道中跟踪和使用。
重要提示
请注意,默认情况下,ML 服务在基本服务层中部署 ACR。为了减少构建和部署镜像到计算目标的时间,您可能希望将容器注册表服务级别更改为标准或高级。
理解工作区内部结构
现在我们已经了解了额外部署的服务,我们将查看工作区本身的内部结构。图 3.4 展示了 Azure 机器学习工作区的几乎所有重要方面:

图 3.4 – Azure 机器学习工作区的结构视图
让我们了解这些方面的每一个,除了 关联的 Azure 资源,因为我们已经在 分析已部署的服务 部分讨论过这一点。
用户角色
与 Azure 中的任何其他服务一样,用户身份验证和授权是通过 AAD 以及所谓的 Azure 基于角色的访问控制(Azure RBAC)来执行的。
Azure 上的基于角色的访问控制
Azure RBAC 用于将 AAD 中的身份(用户、服务主体或托管身份)分配到资源上的特定角色,该角色定义了对资源的访问级别和可以执行的具体细粒度操作。
在 ML 工作区的情况下,我们可以分配 Azure 预定义的基本角色(所有者、贡献者或读者)以及两个自定义角色,名为 AzureML 数据科学家 和 AzureML Metrics Writer。以下是它们的详细信息:
-
读者:此角色允许查看所有内容,但不能更改任何数据或执行任何会改变资源状态的操作(例如,部署计算或更改网络配置)。
-
贡献者:此角色允许查看和更改所有内容,但不允许更改资源上的用户角色和权限。
-
所有者:此角色允许在特定资源上执行任何操作。
-
AzureML 数据科学家:此角色在工作区中不允许执行任何操作,除了创建或删除计算资源或修改工作区设置。
-
AzureML Metrics Writer:此角色仅允许将度量标准写入工作区。
除了这些,机器学习工作区不提供额外的自定义角色。
为了在此方面给您提供更精细的控制,基于角色的访问控制(RBAC)允许您构建自己的自定义角色,因为许多用户在机器学习工作区中可以执行的操作在 RBAC 中定义为所谓的操作。Azure 机器学习服务所有可用的操作都可以在这个资源提供者列表中找到,docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations,在名为Microsoft.MachineLearningServices的操作组下。
为了获得不同角色的灵感,请查看 Microsoft 建议的常见场景和自定义角色:docs.microsoft.com/en-us/azure/machine-learning/how-to-assign-roles#common-scenarios。我们将在下一节中查看,您可以在那里定义和分配它们。
实验
机器学习的目标——简而言之——是找到一个数学函数,这个函数在算法上很难找到,当给定特定的输入时,尽可能多地产生预期的输出。这个函数通常被称为机器学习模型。我们训练的模型可能是一个将声音文件中的声音分配给特定说话者的函数,或者基于类似购买者的购买行为向网络商店中的客户推荐产品的函数(参见第十三章,“在 Azure 中构建推荐引擎”)。
要实现这一点,我们需要利用现有的机器学习算法来训练机器学习模型,目标是降低该模型所谓的损失函数的输出。这需要调整我们模型的设置,从数学上讲,在最佳情况下,找到所有可能函数的n维空间中损失函数的全局最小值。根据我们模型的复杂性,这可能需要大量的迭代。
因此,为了跟踪我们模型训练的迭代过程,我们将它们定义为运行,并将它们与一个称为实验的结构对齐,该结构收集了我们想要训练的特定模型的全部信息。为此,我们将我们执行的任何训练脚本运行与一个特定的实验关联起来。
数据集和数据存储
任何机器学习模型都需要数据来操作,无论是用于训练还是测试。我们可以在脚本中直接链接数据源和不同的数据文件,而不是引用数据集,我们可以在工作区内部定义这些数据集。反过来,数据集会从数据存储中整理数据,我们可以定义并在工作区中附加这些数据存储。我们将在第四章“导入数据和管理数据集”中更详细地介绍如何处理数据、数据集和数据存储。
计算目标
为了运行实验,并在以后托管用于推理的模型,我们需要一个计算目标。ML 服务在此领域提供了两个选项,如下所示:
-
计算实例:一个单独的虚拟机,通常用于开发、作为笔记本服务器或作为训练和推理的目标
-
计算集群:一个多节点集群,通常用于复杂训练和推理生产环境
您可以在此处找到支持的计算目标(虚拟机)列表:docs.microsoft.com/en-us/azure/machine-learning/concept-compute-target#supported-vm-series-and-sizes。有关它们定价的更多详细信息,请参阅以下概述:azure.microsoft.com/en-us/pricing/details/virtual-machines/linux/。
除了这两个选项之外,工作区还提供了一系列其他可能的训练和推理目标。流行的计算选项包括您自己的本地计算机、任何类型的 Spark 引擎(Apache Spark、Azure Databricks或Synapse)用于训练,以及Azure Kubernetes Service(AKS)用于推理。有关选项的完整更新列表,请参阅docs.microsoft.com/en-us/azure/machine-learning/concept-compute-target。
环境
当你编写一个简单的 Python 脚本并在 Python 解释器中运行它时,你就是在所谓的numpy环境中运行它,当然还有你正在运行的操作系统。对于任何我们运行的 ML 脚本也是如此。
对于我们的目的,我们在一个需要特定 Python 版本和某些库的环境中操作,例如 Azure Machine Learning Python SDK 以及包含 ML 算法和工具的库,如TensorFlow。对于我们的本地机器,尤其是如果我们想在工作区中的更快计算集群上运行我们的脚本,我们需要一种良好的方法来定义计算目标的 环境。
为了便于操作,工作区为我们提供了定义和注册 ML 环境的能力。这些通常是包含操作系统和每个运行时、库以及依赖项的Docker 容器。对于在容器内定义 Python 的库和依赖项,大多数情况下在幕后使用包管理器Conda([conda.io/](https://conda.io/))。说到这一点,让我们来分类我们可以工作或创建的环境类型:
-
精选环境使用包含典型运行时和 ML 框架的预定义环境。
-
系统管理的环境(使用默认行为)从基础镜像开始构建环境,通过 Conda 进行依赖管理。
-
用户管理的环境通过从基础镜像开始但允许您通过 Docker 步骤自行处理所有库和依赖项,或者通过创建一个完整的自定义 Docker 镜像来构建环境。
当我们在本章末尾开始我们的第一次实验时,我们将看到如何在我们的机器学习运行中使用环境。
Azure Machine Learning 环境
Azure Machine Learning 环境中的环境是一个包含操作系统以及任何运行时、库和额外依赖项的 Docker 容器。
我们可以得出结论,我们需要在工作区中的计算集群上运行实验时定义一个环境。另一方面,对于我们的本地计算机,我们可以在机器上精心制作的环境上运行,并忽略机器学习工作区环境。但是,如果我们要在我们的机器学习脚本中使用 Azure Machine Learning Python SDK 的环境方法,运行将需要某种类型定义的环境。这可以是机器存在的给定环境、本地 Docker 运行时或由 Conda 环境定义支持的运行时。
运行
运行是在计算目标上实际执行模型训练的过程。在执行运行之前,它需要(在大多数情况下)所谓的运行配置。此配置由以下内容组成:
-
训练脚本:执行实际机器学习训练的脚本(基本上是将包含所有源文件的源文件夹压缩,并将其发送到计算目标)
-
环境:之前描述的机器学习环境
-
计算目标:运行将在其中执行的目标计算实例或集群
在本章稍后当我们进行第一次实验时,我们将看到 Azure Machine Learning Python 库中有一个名为RunConfiguration的类,需要使用它来执行运行。
Azure Machine Learning 实验运行
运行是在指定计算目标上给定环境中执行训练脚本的过程。
此外,在运行执行期间和之后,它跟踪和收集以下信息:
-
日志文件:包括执行过程中生成的日志文件以及我们添加到日志中的任何语句
-
指标:包括标准运行指标以及我们希望在运行期间特别跟踪的任何类型的对象(值、图像和表格)
-
快照:包括包含我们的训练脚本的源目录的副本(使用我们已为运行配置所需的 ZIP 文件)
-
输出文件:包括算法(模型)生成的文件以及我们希望附加到运行中的任何其他文件
我们将在稍后看到,我们可以利用 Azure Machine Learning Python 库中的Run类来影响跟踪的内容。
已注册的模型
如前所述,我们实验输出的结果是机器学习模型。这个模型基本上是一个数学函数,或者更准确地说,是一个实现函数的代码片段。根据我们使用的机器学习框架,函数以二进制格式存储在一个或多个同名的输出文件中。流行的序列化机器学习模型格式有pickle(Python)、H5(Keras)、Protobuf(TensorFlow 和 Caffe)以及其他自定义格式。
由于所有不同运行的结果都会仅仅存储在运行本身的输出文件中,工作区提供了将模型注册到模型注册表的能力。在注册表中,模型以名称和版本存储。每次添加具有相同名称的模型时,注册表都会添加一个新版本的新模型,并带有新的版本号。此外,您还可以使用元信息标记每个模型,例如所使用的框架。
Azure Machine Learning 模型注册表
Azure Machine Learning 中的模型注册表存储已注册模型的名称和版本,以进行跟踪和部署。
最后,模型注册表帮助您跟踪通过训练获得的不同结果,并允许您将不同版本的模型部署到生产、开发和测试环境。
部署和部署端点
一旦模型经过训练并注册,就可以将其打包为一个服务——通过定义入口脚本和环境——并部署到计算目标。入口脚本的任务是在初始化期间加载模型,以及解析用户输入、评估模型并返回用户请求的结果。这个过程在 Azure Machine Learning 中被称为部署。部署的计算目标可以是管理服务,如Azure 容器实例(ACI)或Azure Kubernetes 服务(AKS),或者一个完全由用户管理的 AKS 集群。每个部署通常只服务于单个模型。
如果您想将多个模型部署抽象为一个共同的端点,您可以定义一个端点服务。这是推出多个模型版本、执行蓝绿部署或A/B 测试的常见需求。端点是 Azure Machine Learning 中的一个独立服务,为多个模型部署提供了一个共同的域名,执行安全套接字层(SSL)/传输层安全性(TLS)终止,并允许在部署之间分配流量。端点也可以部署到多个计算目标,包括 ACI 和 AKS。
Azure Machine Learning 端点
Azure Machine Learning 中的部署端点是一个提供访问和测试多个模型版本共同域名的服务。
对于部署和端点,我们区分了在线评分和批量评分:
-
在线评分:对单个输入记录(或小批量的输入记录)进行同步评估,其中输入数据和评分结果直接在请求和响应中传递。
-
批量评分:用户通常将输入数据的位置传递给请求,而不是在请求中发送输入数据。在这种情况下,模型异步评估并提供输出位置的结果。
我们将在第十四章“模型部署、端点和操作”中更详细地讨论模型的部署和端点。
管道
最后要提到的是ML 管道。到目前为止,我们所讨论的内容可能已经足够我们自己进行一些数据准备、模型训练、模型部署和推理。但即使这样,我们也可以通过一些脚本使用 Azure CLI 自动化大多数步骤,并对我们的设置感到非常满意。
现在,假设我们想要与团队合作,并在有新的训练数据时自动重新训练和部署我们的模型。我们可能需要再次运行类似的步骤,例如预处理、训练和优化——只是这次使用新的训练数据。这个过程通常在训练数据和推理数据之间存在重大数据漂移时重复。这就是我们需要考虑引入来自 DevOps 的想法和解决方案的时候,因为最终,我们也将编写代码并将基础设施部署到生产环境。
因此,我们使用管道来简化工作流程,并将自动化引入 ML 链的每个步骤;我们将在第八章“Azure 机器学习管道”中更详细地探讨它们。管道也是 MLOps 的组成部分之一,我们将在第十六章“使用 MLOps 将模型投入生产”中看到它们的应用。
检查 Azure 机器学习工作室
既然我们已经很好地了解了工作空间的功能,那么让我们继续之前中断的地方,看看 Azure 门户和Azure 机器学习工作室,这是操作 ML 流程每个方面的网络服务。这次,再次搜索我们的工作空间名称,并点击mldemows,ML 工作空间。您将看到左侧典型的 Azure 资源菜单结构和右侧服务的概述页面,如图 3.5 所示:

图 3.5 – Azure 资源视图
从基础设施的角度来看,这是管理视图。您需要记住的主要关注点是以下内容:
-
概述:显示工作空间名称和附加服务的面板以及启动 ML 工作室的按钮。
-
访问控制(IAM):设置用户对工作区每个方面的访问权限的面板,如上一节所述。
-
网络:通过激活工作区的私有端点,将服务集成到私有虚拟网络中的面板。
-
身份:显示工作区已创建的管理身份的面板,可用于通过 RBAC 授予工作区访问外部 Azure 服务(如存储帐户)的权限。
-
使用量 + 配额:访问订阅中可用配额的面板,该配额定义了用户在订阅内可以部署多少个类型的虚拟机核心。
通过在概述页上点击启动工作室按钮,实际的 Azure 机器学习工作室将在新标签页中打开,并以图 3.6中显示的视图欢迎您。

图 3.6 – Azure 机器学习工作室主页
您可以通过这个网络应用完成本书中将要做的所有事情,但在某些领域,这可能会很麻烦。我们将在下一节中详细讨论我们如何设置和操作我们的开发环境,但了解这个网络服务是一个好主意,因为我们将贯穿全书回到它。
看看左侧的菜单,有三个主要类别,即作者、资产和管理。让我们将我们对工作区的了解与我们在网络服务中看到的内容进行匹配。
作者
菜单的第一部分显示了您编写机器学习实验的选项。具体如下:
-
笔记本:通过云中的笔记本虚拟机(VM)(计算实例)创建和编写 Jupyter 笔记本。
-
自动化机器学习:通过向导创建机器学习模型,根据您提供的数据集和要解决的问题提供见解和建议。
-
设计器:通过 GUI 界面使用逻辑构建块构建机器学习模型。
我们已经在第二章中讨论了为什么我们更喜欢在 Azure 中选择合适的机器学习服务,即选择 Azure 中的正确机器学习服务。我们将在本书的第十一章中回到自动化机器学习,即超参数调整和自动化机器学习。
目前,我们可以通过两种方式来创建我们的笔记本:要么在云服务环境中工作并利用云中的计算实例上的 Jupyter 服务器,要么使用本地计算机上的本地 Jupyter 服务器进行工作。
重要提示
我们将在本书的大部分内容中保持在我们自己的本地环境中,但请注意,在一个更大的团队中,可能有必要在云中有一个笔记本服务器。
资产
菜单的第二部分显示了您在脚本中可以使用的资产。具体如下:
-
数据集:在工作区中查看和创建数据集,并配置数据集监控以了解训练数据和部署模型的推理数据之间的数据漂移(想象一下在生产中放置位置不同的传感器,或者在收集测试数据时突然损坏)。
-
实验:查看所有实验和所有已跟踪的运行,包括它们的详细运行统计信息(指标、快照、日志和输出)以及计算目标的监控日志。
-
管道:创建管道、查看管道运行并定义管道端点。
-
模型:注册模型并查看它们的属性,包括它们的版本、它们使用的数据集、它们由哪些工件组成以及它们正在积极部署到的端点。
-
端点:查看和创建 Web 服务端点。
通过这些页面,我们可以看到我们已经讨论过的许多工作区项目,从数据集到模型训练,通过实验及其运行,注册模型,以及为我们的部署公开服务端点,直到通过 ML 管道管理所有这些。
您可能已经看到了一些其他附加功能,例如 数据集监控,我们将在 第四章 数据摄取和管理数据集 中再次讨论。
当我们在 Azure Machine Learning Studio 中展示了实验和运行时,我们将在本章末尾更详细地查看实验和运行统计信息。
管理
菜单的最后部分显示了我们可以管理的工作区中的机器和服务。它们如下:
-
计算:创建、查看和管理计算实例、计算集群、推理集群以及其他附加的计算(例如,外部虚拟机或 Databricks 集群),包括已执行的运行、节点上的运行分布(如果存在)以及基础设施本身的监控(例如,CPU 使用率)。
-
环境:查看可用的精选环境,并从 Python 虚拟环境、Conda YAML 配置、存储在容器注册表的 Docker 镜像或您的 Docker 文件中创建自己的自定义环境。
-
workspacefilestore和workspaceblobstore)、全局 Azure Machine Learning 数据集存储库 (azureml_globaldatasets)、以及任何已附加的外部存储或附加新的存储,包括 Azure Data Lake、Azure Blob 存储空间、Azure 文件共享和 Azure SQL、MySQL 以及 PostgreSQL 数据库。 -
数据标注:为图像分类和目标检测创建标注项目。
-
链接服务:将 Azure Synapse Spark 池链接到工作区。
在这些视图中,我们发现了一些缺失的最终组件,包括工作区中的计算目标、环境以及我们可用的数据存储,我们从这些数据存储中获取数据集用于建模。此外,我们还发现了一个服务,可以帮助我们对源文件(通常是图像)进行数据标注,以及将 Azure Synapse 链接到我们的工作区的可能性。
我们将在下一章中详细介绍数据存储,并在第六章“特征工程和标注”中详细介绍数据标注。我们不会在本书中详细讨论 Azure Synapse 集成。
现在我们已经对 Azure 机器学习服务的功能和工具有了良好的概述,我们可以回到我们的本地机器,并开始使用 Azure 机器学习进行我们的第一次实验。
使用 Azure 机器学习运行 ML 实验
到目前为止,我们已经在本地上安装了 Azure CLI,将我们的 ML 工作区部署到我们的 Azure 订阅中,并查看了 Azure 机器学习工作区的功能和功能。
在本章的最后部分,我们将设置我们的本地环境,包括 Python、Azure 机器学习 Python SDK,以及可选的 Visual Studio Code,并在本地以及云中的计算目标上进行我们的第一次实验。
设置本地环境
在一开始,我们简要讨论了通过 Azure 资源管理器部署 Azure 资源的工具。同样,让我们看看从我们的本地环境编写和编排工作区的选项。选项如下:
-
使用 Python 3、Azure 机器学习 Python SDK、Jupyter Python 扩展和 Azure ML CLI(1.0/2.0)扩展(以及选择的编辑器)
-
使用 Python3、Azure 机器学习 Python SDK、Azure ML CLI(1.0/2.0)扩展、Visual Studio Code (VS Code)以及 VS Code 扩展(Azure、Azure 机器学习、Jupyter 等)
-
使用 Python3、Azure ML CLI 2.0 扩展、YAML 和 VS Code(或选择的编辑器)
-
使用 R、Azure ML CLI 2.0 扩展、YAML 和 VS Code(或选择的编辑器)
在撰写本文时,前两种选项是事实上的标准,我们将主要关注本书中的这两种选项。我们将使用 Azure 机器学习 Python SDK 和 Python 3,如果您更喜欢主要从控制台使用源文件和可选的编辑器,或者如果您想使用集成开发环境(IDE),如 VS Code,它自带功能丰富的编辑器和针对 Azure、Azure 机器学习和 Jupyter 的有用扩展,那么请由您决定。
在这两种情况下,我们将编写一个 Jupyter 笔记本来编排我们在工作区上的 ML 实验,以及一个或多个 Python 源文件来实现训练过程。
后两个选项是在更广泛的 Azure ML CLI 2.0 中引入的。我们不再将工作区的编排(运行配置、环境、部署和端点)与训练和推理源代码分离,而是通过 YAML 配置文件来实现。一个 ML 实验运行的示例如下:
$schema: https://.../commandJob.schema.json
code:
local_path: <path-to-python-scripts>
command: python <script-name> --data {inputs.trainingData1}
environment:
docker:
image: docker.io/python
compute:
target: azureml:goazurego
inputs:
trainingData1:
mode: mount
data:
local_path: <path-to-training-data>
如您所见,这种 YAML 结构引用了要执行的实际代码(code)、要使用的运行时(command),并以描述性的方式定义了训练运行所需的每个部分(environment、compute 和 data)。
YAML 配置
YAML 配置文件是一种描述性的方法来运行实验、创建计算服务和端点,并在 Azure Machine Learning 中部署模型。
这是一种更结构化的思考任务的方式,当我们在 第十六章 “使用 MLOps 将模型投入生产”中讨论生产系统和 MLOps 时会很有用。最后,这是唯一允许以 R 语言(数据科学的领域特定语言)编写源文件的选项,并且通过 Azure Machine Learning VS Code 扩展在 VS Code 中得到了高度支持。
设置 Python 环境
现在我们对可以与之一起工作的可能本地开发环境有了很好的了解,让我们设置我们的 Python 环境:
重要提示
如果您在自己的本地机器上运行实验,而不是在 Azure Machine Learning Studio 编写环境中使用笔记本计算实例或 Azure 中的 数据科学虚拟机(DSVM),则必须执行以下操作。
-
首先,通过运行以下命令检查您的系统上是否已安装 Python 版本:
$ python --version -
接下来,请检查 https://pypi.org/project/azureml-sdk/ 上的 Azure Machine Learning Python 扩展的元数据。有时,扩展可能落后于最新的 Python 版本。如果您已经在系统上安装了不受支持的 Python 版本,请卸载该版本或查阅如何在同一台机器上操作多个 Python 环境的说明。
-
在您验证了支持的 Python 版本后,您可以选择访问
www.python.org/并查找 Windows 和 macOS 的支持版本,或者使用 Linux 发行版的终端和apt-get命令。以 Python 3.8 为例,它看起来可能如下所示:$ sudo apt-get install python3.8 -
如果您是第一次安装 Python 或重新安装了它,请检查 Python 是否已正确集成到路径环境变量中,方法是检查 Python 版本(见 步骤 1)。如果一切正常,我们可以继续运行以下命令来安装 SDK:
$ python -m pip install azureml-sdk
如果此命令正在尝试解析大量依赖项,您可能仍在使用不受支持的 Python 版本或包安装程序 PIP。
-
如果您想使用 VS Code,现在可以跳到下一段。如果您主要使用命令行,请安装本地 JupyterLab 或本地 Jupyter 笔记本服务器 (
jupyter.org/index.html),可以使用以下命令之一:$ python -m pip install jupyterlab $ python -m pip install notebook
之后,您可以从命令行启动任一环境,如下所示:
$ jupyter-lab
$ jupyter notebook
使用此版本的设置,您现在可以继续到 使用 Azure 机器学习运行简单实验 部分。
设置 Visual Studio Code
VS Code 是一个轻量级但功能强大的集成开发环境。它与 Azure、Azure 机器学习和 Git 高度集成,拥有一个非常好的编辑器、一个集成终端以及一个长长的可选扩展列表。
让我们来看看它:
-
您可以从
code.visualstudio.com/或通过 Azure 市场下载此工具并安装。 -
打开它后,您将看到如图 3.7 所示的视图(可能主题较暗):

图 3.7 – VS Code 界面
- 如果您点击顶部的
>主题并查找>首选项:颜色主题。
点击它将为您提供快速设置 UI 主题的方法。
-
现在,要打开终端,您可以再次点击顶部的
az菜单,以查看如图 3.7 所示的内容。 -
查看左侧菜单,您将找到一个 资源管理器 选项卡,您可以在此添加源文件夹和文件,一个 源代码管理 选项卡以连接到 Git,一个 运行和调试 选项卡,它允许您处理代码的调试,以及一个 扩展 选项卡,您可以在此搜索 VS Code 扩展。
切换到 扩展 选项卡,搜索并安装以下扩展(如果尚未安装):Azure Tools、Azure Machine Learning、Python、Pylance、YAML 和 Jupyter。
- 安装完成后,您将在左侧菜单中找到一个名为
sign in azure的新选项卡,您将找到登录的方法。
在您完成 Azure 登录后,Azure 选项卡将填充您的订阅名称、资源组以及您可能拥有的任何资源。如果您查看 机器学习 标题下,您也会找到之前部署的工作区,如图 3.8 所示:

图 3.8 – VS Code Azure 机器学习扩展
- 在下一节中,下载本章所需的文件以进行操作。只需通过 文件 | 打开文件夹… 打开文件夹,它们将被添加到 资源管理器 选项卡中,从这里您可以开始旅程。
VS Code 有更多功能,但我们将主要集中理解机器学习(ML)和 Azure 机器学习工作区,而不是操作此编辑器的各个方面。如果您需要更多关于使用 VS Code 的帮助,请随时访问 code.visualstudio.com/docs/introvideos/basics 或任何其他可以帮助您的资源。
增强简单实验
Azure 机器学习的伟大用例之一是将高级日志记录、跟踪和监控功能添加到您现有的机器学习脚本和管道中。想象一下,您有一个中央位置来跟踪所有数据科学家的所有机器学习实验,监控训练和验证指标,上传您的训练模型和其他输出文件,并在每次执行新的训练运行时保存当前环境的快照。您可以通过在训练脚本中添加几行代码来实现这一点。
我们将首先向一个 Keras (keras.io) 机器学习训练脚本添加 Azure 机器学习工作区功能。Keras 是我们可以选择的许多机器学习库之一,具体取决于我们需要的机器学习算法。
工作目录和准备
在我们开始之前,请从存储库下载本章的代码文件,并将它们提取到您首选的工作目录中。之后,您可以在控制台中切换到该目录,或者在 VS Code 中将其打开为文件夹。
在任何情况下,您都会在目录中找到以下文件:
-
.azureml/config.json: Azure 机器学习工作区配置文件 -
.azureml/requirements.txt: Python PIP 环境需求 -
00_setup_env.sh: 一个 shell 脚本,用于从头开始设置 Azure CLI 和 Python 环境(就像我们之前做的那样) -
01_setup_azure_ml_ws.sh: 一个 shell 脚本,用于设置 Azure 机器学习工作区(就像我们之前做的那样) -
0x_run_experiment_*.ipynb: 为即将进行的实验提供的多个 Jupyter 笔记本 -
04_setup_azure_ml_compute.sh: 一个 shell 脚本,用于从 YAML 配置创建工作区计算实例 -
compute.yml: 工作区计算实例的 YAML 配置文件 -
code/*.py: 包含我们将使用的 Python 模型训练脚本的文件夹 -
.amlignore: 一个文件,表示运行快照应该忽略的所有内容
让我们从我们的第一个实验开始:
-
首先,我们需要安装我们将需要的缺失 Python 包,以便进行以下实验。运行以下命令,该命令将安装 PIP 需求文件中定义的包:
$ python -m pip install -r .azureml/requirements.txt
PIP 将指出 Azure 机器学习 SDK 已经安装。
-
接下来,打开
config.json文件,在subscription_id键之后输入您的订阅 ID。这是必要的,因为我们将在所有笔记本中使用以下代码加载此配置:from azureml.core import Workspace ws = Workspace.from_config()
from_config()方法会在当前工作目录或名为.azureml的目录中寻找一个名为config.json的文件。我们将选择将其添加到文件夹中,因为它包含在.amlignore文件中。
- 打开
02_run_experiment_keras_base.ipynb笔记本。
在以下内容中,我们将查看笔记本,以了解实际的模型训练脚本,如何将快照、输出和日志添加到 Azure 机器学习实验中,以及如何将最佳模型编目到模型注册表中。
Keras 的训练脚本
导航到笔记本中的第二个块。想象这部分是你的原始机器学习训练文件(加上你将在最后一段找到的model.fit()函数)。
让我们了解实际的训练代码。
首先,我们从tensorflow库(Keras 是 TensorFlow 的一部分)导入所需的类:
import tensorflow
from tensorflow.keras.datasets import cifar10
…
然后,我们继续从 CIFAR-10 数据集中获取我们的训练和测试数据,并将其转换为有用的格式。cifar10.load_data()函数将训练集填充 50,000 个数据点,测试集填充 10,000 个数据点:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
…
y_train = tensorflow.keras.utils.to_categorical
(y_train, num_classes)
…
测试和训练数据集
训练数据集由我们训练模型的数据点组成;测试数据集由我们在模型训练后用于评估模型的数据点组成。这些数据点应该完全不同。
之后,我们开始定义我们的模型——在这种情况下,一个Sequential模型([keras.io/guides/sequential_model/](https://keras.io/guides/sequential_model/))——并设置模型的名称和输出位置。我们将使用之前提到的HDF5文件格式(或简称 H5):
model = Sequential()
…
model_name = 'keras_cifar10_trained_model.h5'
model_output_dir = os.path.join(os.getcwd(), 'outputs')
之后,我们定义了一个优化器(在这种情况下是RMSProp),一个检查点loss函数,optimizer,以及训练运行期间要跟踪的额外metrics:
opt = RMSprop(learning_rate=0.0001, decay=1e-6)
…
checkpoint_cb = ModelCheckpoint(model_path,
monitor='val_loss',
save_best_only=True)
…
model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])
原脚本中本应完成的部分位于笔记本的最后一段,我们稍后会讨论:
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
validation_data=(x_test, y_test),
shuffle=True,
callbacks=[azureml_cb, checkpoint_cb])
如您所见,这是笔记本中的大部分代码。其余的代码是你需要添加到脚本中以启用实验运行跟踪的部分,我们将在下面分析。
跟踪快照、输出和日志
现在,我们将查看我们之前忽略的代码。首先,回到我们之前跳过的笔记本的第一个块:
from azureml.core import Workspace, Experiment
ws = Workspace.from_config()
exp = Experiment(workspace=ws, name="cifar10_cnn_local")
在此片段中,我们使用配置文件定义了一个名为ws的工作区对象,作为第二步,我们定义了一个名为exp的实验对象,以便在指定的名称下跟踪定义的工作区。如您所见,我们将其命名为cifar10_cnn_local,因为我们将利用 CIFAR-10 数据集(www.kaggle.com/c/cifar-10),我们将运行一个卷积神经网络(CNN),并且我们将在本地机器上运行。如果已存在具有相同名称的实验,则此调用将返回现有实验的句柄;否则,将创建一个新的实验。通过给定的名称,现在所有此实验中的运行都分组在一起,可以在单个仪表板上显示和分析。
重要提示
运行此代码块可能会打开一个网站以登录您的 Azure 账户。这被称为交互式身份验证。请执行此操作以授予您的当前执行环境访问 Azure 机器学习工作区的权限。如果您运行的是非交互式 Python 脚本而不是笔记本环境,您可以通过此处描述的其他方式提供 Azure CLI 凭据:https://docs.microsoft.com/en-us/azure/machine-learning/how-to-setup-authentication#use-interactive-authentication。
一旦您已成功将工作区链接到ws对象,您就可以继续为您的 ML 实验添加跟踪功能。我们将使用此对象创建实验、运行、记录指标,并在我们的 Azure 机器学习工作区中注册模型。
现在,让我们跳到最后一个代码块,我们将在此处运行实验。如前所述,运行是您实验(您的训练脚本)的单次执行,具有不同的设置、模型、代码和数据,但具有相同的可比较指标。您使用运行来测试给定实验的多个假设,并在同一实验中跟踪所有结果。
通常,我们可以创建一个run对象,并通过调用以下函数来开始在此处记录这个运行:
# Create and start an interactive run
run = exp.start_logging(snapshot_directory='.')
上述代码不仅创建并初始化了一个新的运行,还通过snapshot_directory参数定义的当前环境快照,并将其上传到 Azure 机器学习工作区。要禁用此功能,您需要明确将snapshot_directory=None传递给start_logging()函数。
在这种情况下,快照将获取当前目录中存在的所有文件和文件夹。为了限制这一点,我们可以使用.amlignore文件指定要忽略的文件和文件夹。
在最后一个笔记本代码块中查看代码本身,您可以看到这并不是之前显示的相同行代码。
这是因为将训练代码包裹在try和except块中是一种良好的实践,以便在 Azure 中传播运行状态。如果训练运行失败,那么该运行将在 Azure 中报告为失败的运行。您可以通过以下代码片段实现这一点:
run = exp.start_logging(snapshot_directory='.')
try:
# train your model here
run.complete()
except:
run.cancel()
raise
我们添加了 raise 语句,以便在脚本发生错误时失败。这通常不会发生,因为所有异常都被捕获。你可以通过在 Python 中使用 with 语句来简化前面的代码。这将产生相同的结果,并且更容易阅读:
with exp.start_logging(snapshot_directory='.') as run:
# train your model here
pass
通过仅使用这一行代码,你可以自动跟踪每次实验运行执行的快照,因此永远不会丢失代码或配置,并且始终可以回到用于你的 ML 运行之一的特定代码、参数或模型。这目前可能并不那么令人印象深刻,但我们只是刚开始使用 Azure Machine Learning 的功能。
现在,执行这个笔记本中的每个代码块,并等待完成。
执行后,回到 Azure Machine Learning Studio,导航到 cifar10_cnn_local。当你点击它时,你会在图表中看到一些指标以及与实验关联的运行列表。点击最近的运行,然后点击 .azureml)。
图 3.9 显示了实验中运行的已上传快照文件:

图 3.9 – 实验运行快照视图
除了在运行开始之前上传的 snapshot 目录之外,我们还会在运行结束后由 ML 脚本创建两个额外的目录,即 outputs 和 logs。
一旦使用 run.complete() 完成一次运行,outputs 目录中的所有内容将自动上传到 Azure Machine Learning 工作区。在我们的简单示例中,使用 Keras,我们可以使用检查点回调来仅将所有 epoch 中的 最佳模型 存储到 outputs 目录,然后与我们的运行一起跟踪。看看这个示例代码:
import os
from keras.calbacks import ModelCheckpoint
model_output_dir = os.path.join(os.getcwd(), 'outputs')
model_name = 'keras_cifar10_trained_model.h5'
model_path = os.path.join(model_output_dir, model_name)
# define a checkpoint callback
checkpoint_cb = ModelCheckpoint(model_path,
monitor='val_loss',
save_best_only=True)
# train the model
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
validation_split=0.2,
shuffle=True,
callbacks=[checkpoint_cb])
在前面的代码中,我们训练了一个 Keras 模型五个 epoch。这个过程将 20% (validation_split) 的训练数据作为所谓的验证集分开。
验证数据集
验证集是第三组数据点,模型在模型训练期间将针对这些数据点进行评估。它既不应是训练数据的子集,也不应是测试数据。
之后,该函数会遍历每个 epoch,使用打乱顺序的 (shuffle=True) 训练数据集。在每个 epoch 中,如果该 epoch 的模型在验证集上表现更好,它将覆盖定义的 output 文件夹中的模型文件,我们通过具有更低的验证损失 (monitor='val_loss') 来定义验证集。因此,我们最终只会在 output 文件夹中存储最佳模型。因此,每次我们使用之前的实验跟踪运行训练时,一旦运行完成,模型就会自动上传。
如果你回到笔记本中的第二个代码块,你会看到我们已经在代码中添加了检查点回调。那么让我们看看我们得到了什么。
在 Azure Machine Learning Studio 中,导航到 keras_cifar10_trained_model.h5,已上传到 Azure Machine Learning 工作区。
这也非常方便,因为你不会再失去对训练好的模型的跟踪。除此之外,你在这里看到的所有工件都存储在工作区的 Blob 存储中,它具有高度可扩展性和低成本。
图 3.10 展示了实验运行中产生的额外输出和日志信息:

图 3.10 – 实验运行输出和日志
logs 目录包含来自 Keras 的日志输出,你也在执行最后一个块时的 Jupyter 笔记本中看到了。在当前运行中,这是在运行完成后上传的,包括 output 文件夹和模型。
Azure Machine Learning 日志流式传输
Azure Machine Learning 日志流式传输允许你在运行执行时在 Azure Machine Learning Studio 中查看日志。
我们稍后会看到,如果通过 ScriptRunConfig 而不是直接执行来调用训练脚本,日志将会 流式传输 到工作区(也请参阅 启用日志流式传输 按钮)。这将允许你在运行进行时在这里查看日志。
将模型编目到模型注册库
作为最后一步,我们希望将存储在 output 文件夹中的最佳模型注册到 Azure Machine Learning 工作空间中的模型注册库。
如果我们再次导航到笔记本的最后一个块,我们可以看到最后几行是这样的:
# Upload the best model
run.upload_file(model_name, model_path)
# Register the best model
run.register_model(model_name, model_path=model_name,
model_framework='TfKeras')
在这里,我们首先强制上传模型。这是必要的,因为所有输出资源只有在运行完成后才会上传,而不是立即上传。因此,在模型上传后,我们可以通过调用 run.register_model() 方法简单地将其注册到模型注册库中。
如果你从 cifar10_cnn_local 实验导航到 Azure Machine Learning Studio 中的 keras_cifar10_trained_model.h5。如果你点击它,你将在 详细信息 下找到关于模型的详细信息,包括版本号,你将在 工件 下找到我们创建的实际模型文件。
图 3.11 展示了注册模型的详细信息:

图 3.11 – Azure Machine Learning 模型注册库中的注册模型
该模型可以从 Azure Machine Learning 服务进行自动部署。我们将在 第十四章,模型部署、端点和操作 和 第十一章,超参数调整和自动化机器学习 中更详细地探讨这一点。
现在我们已经知道了如何运行一个简单的实验,接下来让我们学习如何在下一节中记录指标和跟踪结果。
记录指标和跟踪结果
我们已经在我们的 Azure Machine Learning 工作区中看到了三个用于跟踪快照代码、上传输出工件和注册训练模型文件的有用功能。正如我们所见,这些功能可以通过几行代码添加到任何现有的实验和训练 Python 脚本或笔记本中。以类似的方式,我们也可以扩展实验脚本以跟踪所有类型的变量,例如每个 epoch 的训练准确率和验证损失,以及最佳模型的测试集准确率。
使用run.log()方法,你可以在训练和实验过程中跟踪任何参数。你只需提供一个名称和值,Azure 就会为你完成剩余的工作。后端会自动检测你是否发送了一个值列表——因此当你多次记录相同值时,会有多个具有相同键的值——或者每个运行一个单独的值,例如测试性能。在 Azure Machine Learning Studio 中,这些值将自动用于可视化你的整体训练性能。
到目前为止,我们的 Keras 模型默认通过模型编译跟踪损失作为指标,并通过我们的模型编译跟踪模型的准确率。我们只是没有将它们记录到工作区中。
我们之前讨论了脚本中使用的不同数据集,即训练数据集、验证数据集和测试数据集。请记住,验证数据集在每个 epoch 结束时进行评估,这也意味着我们可以在每个 epoch 结束时获得验证损失和验证准确率。进一步地,在我们找到所有 epoch 的最佳模型之后,我们希望评估这个模型与测试数据,而这我们还没有做。这导致了模型的测试损失和测试准确率。
在以下内容中,我们将首先将测试指标添加到我们的运行中,然后是验证指标,然后在 Azure Machine Learning Studio 中查看它们。最后,我们将增强代码,以便只有当模型比之前运行中的所有模型都好时才注册模型。请随意打开02_run_experiment_keras_enhanced.ipynb笔记本以跟随操作。
最佳模型的评估
目标是评估所有 epoch 的最佳训练模型与测试数据集以获得整体测试指标。为了做到这一点,我们需要将其重新加载到我们的模型对象中。幸运的是,我们已经在之前定义的 checkpoint 回调中,只将整个运行的最好模型存储到了我们的output文件夹中。让我们看看代码:
# load the overall best model into the model object
model = load_model(model_path)
# evaluate the best model against the test dataset
scores = model.evaluate(x_test, y_test, verbose=1)
print('Test loss of best model:', scores[0])
run.log('Test loss', scores[0])
print('Test accuracy of best model:', scores[1])
run.log('Test accuracy', scores[1])
如你所见,我们得到了最佳模型,然后对其进行评估,提取损失(scores[0])和准确率(scores[1])。完成这部分后,让我们看看验证指标。
Keras 的验证指标回调
目标是在每个 epoch 中评估创建的模型与验证数据集,以获取每个 epoch 的验证指标。我们已使用现有的回调在每个 epoch 中检查最佳模型,所以自己编写一个来跟踪每个 epoch 的指标可能是个好主意。
在code目录下打开keras_azure_ml_cb.py文件。你会看到以下内容:
from keras.callbacks import Callback
import numpy as np
class AzureMlKerasCallback(Callback):
def __init__(self, run):
super(AzureMlKerasCallback, self).__init__()
self.run = run
def on_epoch_end(self, epoch, logs=None):
# logs is filled by Keras at the end of an epoch
logs = logs or {}
for metric_name, metric_val in logs.items():
if isinstance(metric_val, (np.ndarray, np.generic)):
self.run.log_list(metric_name, metric_val.tolist())
else:
self.run.log(metric_name, metric_val)
之前的代码实现了一个简单的 Keras 回调函数。当回调执行时,Keras 会将当前 epoch 以及所有训练和验证指标作为字典(logs)传递。
然后,对于所有字典条目,我们提取名称和值,使用run.log(metric_name,metric_val)函数将它们记录到实验运行中。我们只需检查值是单个值还是数组类型,因为 Azure Machine Learning SDK 有一个名为run.log_list()的不同函数,用于多值条目。
我们现在可以使用这个回调在我们的模型训练中,就像我们之前使用回调一样,通过将其添加到model.fit()函数中:
# create an Azure Machine Learning monitor callback
azureml_cb = AzureMlKerasCallback(run)
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
validation_data=(x_test, y_test),
callbacks=[azureml_cb, checkpoint_cb])
这通过使用回调函数在 Azure Machine Learning 服务中跟踪训练和验证损失以及准确度,自然地扩展了 Keras。现在,模型本身上定义的任何指标都将自动在实验运行中跟踪。
在 Azure Machine Learning Studio 中运行指标可视化
在我们向实验运行添加了一堆指标之后,让我们按原样运行笔记本,并在 Azure Machine Learning Studio 中查看运行统计信息。
当你打开运行时,指标列表的类型,就像验证指标一样,都会自动转换为折线图并绘制出来,如图图 3.12所示:

图 3.12 – 实验运行的指标视图
我们可以看到测试指标和验证指标都已考虑在内。此外,我们还可以看到测试损失和测试准确率作为指标,这些指标也是 Keras 为每个 epoch 提供的,作为模型对训练数据集的评估。
另一个很酷的功能是,ML 工作区实验为你提供了所有运行的概览。它自动使用每个运行的标量值和训练以及验证指标,并在仪表板上显示它们。你可以修改显示的值和用于聚合这些值的聚合方法。
图 3.13显示了所有实验运行的准确率和验证准确率:

图 3.13 – 所有实验运行的可视化指标
这是跟踪运行值并显示相应实验的最简单方法。在现有的机器学习训练脚本中添加几行代码——无论你使用哪个框架——可以自动跟踪你的模型分数并在仪表板上显示所有实验。
增强模型的注册
现在我们有了可以读取和处理的指标,作为最后一步,我们可以增强将最佳模型保存到模型注册的方式。
到目前为止,我们总是在新模型可用时立即用新版本更新模型。然而,这并不意味着新模型的实际性能比我们在工作区中注册的最后一个模型更好。因为我们希望新版本的模型实际上比上一个版本更好,所以我们需要检查这一点。
因此,一个常见的做法是,只有当指定的指标比实验中之前存储的最高指标更好时,才注册新的模型。让我们实现这个功能。
我们可以定义一个函数,它从一个实验中返回指标的生成器,如下所示:
from azureml.core import Run
def get_metrics_from_exp(exp, metric, status='Completed'):
for run in Run.list(exp, status=status):
yield run.get_metrics().get(metric)
前面的生成器函数为每个完成的运行产生指定的跟踪指标。我们可以使用这个函数来返回所有先前实验运行中的最佳指标,以比较当前模型的评估分数并决定是否应该注册新版本的模型。我们只有在当前模型的表现优于之前记录的模型时才应该这样做。为此,我们需要比较一个指标。使用测试准确率是一个好主意,因为它是对未知数据进行测试的模型:
# get the highest test accuracy
best_test_acc = max(get_metrics_from_exp(
exp,'Test accuracy')
default = 0)
# upload the model
run.upload_file(model_name, model_path)
if scores[1] > best_test_acc:
# register the best model as a new version
run.register_model(model_name, model_path=model_name)
如您所见,我们得到了在此实验中跟踪的所有先前运行的测试准确率指标的结果,并选择了最大的一个。然后,只有当新模型的测试准确率高于之前存储的最佳分数时,我们才注册该模型。尽管如此,我们仍然将模型二进制文件与实验运行一起上传和跟踪。
现在,我们有了笔记本的增强版本,包括指标跟踪和更好的模型注册版本。
调度脚本执行
在上一节中,我们看到了如何通过几行代码来注释现有的机器学习实验和训练代码,以便跟踪相关指标并在工作区中运行工件。在本节中,我们将从直接调用训练脚本转变为在本地机器上调度训练脚本。你可能会问为什么这一额外步骤是有用的,因为直接调用训练脚本和调度本地运行训练脚本之间并没有太多区别。
这个练习的主要动机是,在后续步骤中,我们可以将执行目标更改为远程计算目标,并在云中的计算集群上运行训练代码,而不是在本地机器上。这将是一个巨大的好处,因为我们现在可以轻松地在本地测试代码,然后将其部署到云中高度可扩展的计算环境中。
另一点需要注意的是,当安排训练脚本而不是调用它时,标准输出和错误流以及日志目录中的所有文件都将直接流式传输到 Azure Machine Learning 工作区运行。这有一个好处,即您可以在您的 ML 工作区实时跟踪脚本输出,即使您的代码正在远程计算集群上运行。
让我们在所谓的编写脚本中实现这一点。当我们说脚本或环境的任务是安排另一个训练或实验脚本时,我们称之为编写脚本(或编写环境)。此外,我们现在将运行和执行训练的脚本称为执行脚本(或执行环境)。
在编写脚本中,我们需要定义两件事——我们将运行的环境和运行配置,我们将执行脚本、环境和可能的计算目标传递给它。
打开 03_run_experiment_local.ipynb 笔记本文件。与我们的前一个笔记本相比,你可以看到这是一个非常短的文件,因为实际的 Keras 训练现在正在执行脚本中进行,你可以找到 code 文件夹中的 cifar10_cnn_remote.py 文件。
首先,我们需要定义一个环境。由于我们仍在本地运行,我们使用 user-managed-env 创建一个环境,这将直接从我们的本地机器获取我们的环境:
from azureml.core.environment import Environment
myenv = Environment(name = "user-managed-env")
myenv.python.user_managed_dependencies = True
在下一个块中,我们定义了我们想要在本地运行的执行脚本的地址和名称:
import os
script = 'cifar10_cnn_remote.py'
script_folder = os.path.join(os.getcwd(), 'code')
最后,我们使用ScriptRunConfig对象定义一个运行配置,并将其源目录、脚本名称以及我们之前定义的本地环境附加到它:
from azureml.core import ScriptRunConfig
runconfig = ScriptRunConfig(source_directory=script_folder,
script=script,
environment = myenv)
run = exp.submit(runconfig)
run.wait_for_completion(show_output=True)
现在,执行整个笔记本,同时导航到 Azure Machine Learning Studio,查找名为 cifar10_cnn_remote 的当前实验运行。当它可见时,转到 azureml-logs 和 logs/azureml 文件夹,现在将填充运行期间的日志输出。
图 3.14 展示了摄入的流式日志的一个示例:

图 3.14 – Azure Machine Learning 实验运行的流式日志
这非常方便,因为现在我们实际上并不需要知道代码最终在哪里执行。我们唯一关心的是看到输出,运行进度,同时跟踪所有指标,生成的模型以及所有其他工件。可以通过调用 print(run.get_portal_url()) 方法检索当前运行的链接。
然而,我们不必每次运行训练脚本时都导航到 Azure 门户,我们可以在笔记本环境中嵌入一个小部件,以提供相同(甚至更多)的功能,直接在 Jupyter、JupyterLab 或 VS Code 中。为此,我们需要将run.wait_for_completion()行替换为以下代码片段:
from azureml.widgets import RunDetails
RunDetails(run).show()
请注意,您需要将Azure Widgets Python 扩展添加到您的环境中。请参阅此安装指南了解扩展:docs.microsoft.com/en-us/python/api/azureml-widgets/azureml.widgets.rundetails?view=azure-ml-py。
最后,让我们看看我们正在使用的执行脚本。在code目录中打开名为cifar10_cnn_remote.py的文件。扫描这个文件,你应该会找到我们添加到原始模型训练代码中的两个附加部分。
第一个部分是我们将调试日志写入logs文件夹的部分:
# log output of the script
logging.basicConfig(filename='logs/debug.log',
filemode='w',
level=logging.DEBUG)
logger_cb = CSVLogger('logs/training.log')
第二部分看起来是这样的:
from azureml.core import Run
# load the current run
run = Run.get_context()
进行此调用的原因是,当我们想要迁移到远程执行环境时,我们需要推断运行上下文。因此,我们需要从当前执行上下文中加载run对象,而不是像之前章节中所示的那样创建一个新的运行,当时我们使用了exp.start_logging()调用。
当通过作者脚本进行调度时,run 对象将自动与实验关联。这对于远程执行来说很方便,因为我们不再需要在执行脚本中显式指定run对象。使用这个推断的run对象,我们可以记录值、上传文件和文件夹,以及注册模型,就像在之前的章节中做的那样。
在云计算上运行实验
在我们已经在本地机器上运行了实验之后,现在让我们在本章的最后一个步骤中,在 ML 工作空间中的计算目标上运行相同的 ML 模型。
在 Azure 中训练 ML 模型推荐的计算目标是受管理的 Azure Machine Learning 计算集群,这是一个直接在您的 Azure 订阅内管理的自动扩展计算集群。如果您已经使用 Azure 进行批量工作负载,您会发现它与 Azure Batch 和 Azure Batch AI 类似,配置更少,并且紧密集成在 Azure Machine Learning 服务中。
有三种方式可以部署集群,要么通过 Azure CLI 和 YAML,要么通过 Python SDK,要么通过 Azure Machine Learning Studio。在以下步骤中,我们将使用第一种方式,因为它们越来越普遍,尤其是在 MLOps 中。之后,我们将看到如何使用 Python 代码实现第二种方式。
在工作目录中打开compute.yml文件。您将看到以下内容:
compute.yml
$schema: https://azuremlschemas.azureedge.net/latest/compute.schema.json
name: mldemocompute
type: amlcompute
size: STANDARD_D2_V2
location: westus2
min_instances: 0
max_instances: 2
idle_time_before_scale_down: 900
这描述了一个名为 mldemocompute 的计算集群,我们希望部署。此配置在 ML 工作区中定义了一个计算类型(amlcompute),具有 0-2 个节点,VM 大小为 Standard D2v2(2 个 CPU,7 GB 的 RAM 和 100 GB 的 HDD),位于美国西部 2 的 Azure 区域。此外,我们定义了集群缩放(关闭)前的空闲时间为 15 分钟(等于 900 秒)。
计算集群有许多其他设置,包括各种网络和负载均衡设置。你也可以通过简单地更改配置来定义带有 GPU 的 VM 类型作为你的工作节点 – 例如,Standard_NC6(6 个 CPU,56 GB 的 RAM,340 GB 的 SSD,1 个 GPU 和 12 GB 的 GPU 内存)。
与其他托管集群(如 Azure Databricks)相比,你不需要为头节点或主节点付费,只需为工作节点付费。我们将在 第十章 在 Azure 上训练深度神经网络 和 第十二章 在 Azure 上进行分布式机器学习 中详细介绍深度学习的 VM 类型,并运行 GPU 集群的分布式训练。
如果你正在使用 VS Code,可以在左侧的 Azure 选项卡中找到 Azure ML 扩展(可显示 YAML 模板)。只需转到你的 ML 工作区,然后在 mldemows | Compute | Compute clusters 下,点击右侧的 + 号。它将生成一个模板文件,看起来像是前面版本的裸版本。此外,如果你已安装 YAML 扩展,它将理解文件中的模式链接并自动完成你的输入:
-
打开控制台并运行以下 CLI 命令,从 YAML 文件创建计算实例:
$ az ml compute create -f compute.yml -g mldemo -w mldemows
你还可以调用工作目录中名为 04_setup_azure_ml_compute.sh 的 shell 脚本。
稍等片刻,它将显示创建的计算集群的属性。
- 打开名为
05_run_experiment_remote.ipynb的笔记本。
那个笔记本中的第二个块展示了以下代码:
from azureml.core.compute import ComputeTarget, AmlCompute
from azureml.core.compute_target import ComputeTargetException
cluster_name = "mldemocompute"
min_nodes = 0
max_nodes = 2
vm_size = "STANDARD_D2_V2"
try:
aml_cluster = ComputeTarget
(workspace=ws, name=cluster_name)
except ComputeTargetException:
print('Cluster not '%s' not found, creating one now.'
% cluster_name)
config = AmlCompute.provisioning_configuration
(vm_size=vm_size,
min_nodes=min_nodes,
max_nodes=max_nodes)
aml_cluster = ComputeTarget.create
(workspace=ws,
name=cluster_name,
provisioning_configuration=config)
aml_cluster.wait_for_completion(show_output=True)
try 构造的 except 子句展示了你可以通过 Python SDK 创建计算集群的方式。由于集群的名称与我们之前通过 CLI 部署的名称相同,当执行此块时,它将通过 try 子句将我们的计算链接到 aml_cluster 对象。
无论哪种方式,这个 try..except 子句都非常方便,因为它要么返回已经存在的集群,要么为我们创建一个新的集群。如果计算目标尚未存在,则代码的最后一行是必要的,因为我们需要等待计算目标在下一步准备好接收运行配置。
如果我们现在查看环境定义和运行配置,我们将看到从 03_run_experiment_local.ipynb 笔记本中代码的一些细微变化。我们现在的环境定义如下所示:
myenv = Environment.from_pip_requirements
(name = "remote_env", file_path = pipreq_path)
如您所见,我们附上了我们在本地工作的 PIP 配置文件。在后台,SDK 将将其转换为Conda 属性文件,并从 Docker 基础镜像创建一个容器。如果您运行到这一步,您将看到 Azure 机器学习基于此输入构建的基础镜像和配置。这里展示的是其中一小部分:
"docker": {
"baseImage": "mcr.microsoft.com/azureml/openmpi3.1.2-ubuntu18.04:20210714.v1",
"platform": {
"architecture": "amd64",
"os": "Linux"
}
}
在笔记本的最后一个块中查看,我们可以看到唯一的区别是,我们现在在运行配置中将计算目标定义为我们的aml_cluster,并传递新的环境。
最后,我们现在运行整个笔记本。
训练脚本现在在 Azure 的远程计算目标上执行。在 Azure 机器学习工作室中的实验运行中,快照、输出和日志看起来与本地运行非常相似。然而,我们现在还可以看到计算目标的 Docker 环境构建过程的日志,如图 3.15 所示:

图 3.15 – 远程实验运行的 Docker 构建阶段
作为最后的练习,让我们了解当我们提交这次运行到 Azure 机器学习工作区时执行的步骤:
-
如果不存在,Azure 机器学习服务将根据定义的环境构建 Docker 容器。
-
Azure 机器学习服务在私有容器注册表中注册您的环境,以便它可以用于其他脚本和部署。
-
Azure 机器学习服务排队执行您的脚本。
-
Azure 机器学习计算服务使用定义的容器初始化和扩展计算节点。
-
Azure 机器学习计算服务执行脚本。
-
Azure 机器学习计算服务捕获日志、工件和指标,并将它们流式传输到 Azure 机器学习服务,并通过小部件在 Jupyter 笔记本中内联日志。
-
Azure 机器学习服务将所有工件存储在工作区存储中,并将您的指标存储在 Application Insights 中。
-
Azure 机器学习服务通过 Azure 机器学习工作室或 Python SDK 为您提供有关运行的全部信息。
-
Azure 机器学习计算服务在 15 分钟(在我们的情况下)的无操作后自动缩小自身。
恭喜您跟随着这个练习。鉴于我们可能花费了 5 分钟来设置 Azure 机器学习工作区,我们得到了一个完整的批量计算调度和执行环境,用于我们所有的机器学习工作负载。这个环境中的许多部分都可以根据我们的喜好进行调整和配置,最好的是,一切都可以通过 Azure CLI 或 Azure Python SDK 自动化。在整个书中,我们将使用这些工具来配置、启动、扩展和删除用于训练和评分的集群。
摘要
这本书的第一部分到此结束。到目前为止,你应该对机器学习(ML)的一般概念有了很好的了解,Azure 中可用的服务和选项,以及如何利用 Azure 机器学习服务进行 ML 实验并增强你现有的 ML 建模脚本。
在本书的下一部分,我们将专注于机器学习经常被忽视的一个方面,即数据本身。正确处理这一点至关重要。你可能之前听说过“垃圾输入,垃圾输出”这个短语,这是真的。因此,我们将通过运行自动数据摄取、数据清理和准备、特征提取以及执行标注来尽可能多地消除陷阱。最后,我们将把我们的知识汇总起来,讨论如何设置数据摄取和训练 ML 管道。
作为这个过程的第一步,我们需要了解不同的数据源和格式,并将我们的数据带到 Azure 机器学习工作区,我们将在下一章中讨论这一点。
第二部分:数据摄取、准备、特征工程和管道化
在本节中,我们将学习如何在 Azure 中加载数据和存储数据,以及如何从 Azure 机器学习工作区管理这些数据。然后,我们将研究预处理和可视化数据的技术,以及如何从高维数据集中获得洞察。从那时起,我们将专注于如何通过创建和转换特征以及为监督建模创建标签来优化我们的数据集。我们将使用复杂语义词嵌入来执行自然语言处理的高级特征提取。最后,我们将利用 Azure 机器学习管道将所学知识整合到自动化的预处理和训练管道中。
本节包括以下章节:
-
第四章, 摄取数据和管理数据集
-
第五章, 执行数据分析与可视化
-
第六章, 特征工程和标签化
-
第七章, 使用 NLP 的高级特征提取
-
第八章, Azure 机器学习管道
第四章:第四章:导入数据和管理数据集
在上一章中,我们设置了 Azure 机器学习工作区,进行了数据实验,并安排了在 Azure 机器学习中远程计算目标上运行的脚本。在本章中,我们将学习如何连接数据存储,创建、探索、访问和跟踪 Azure 机器学习中的数据。
首先,我们将通过了解数据存储和数据集的概念来探讨 Azure 机器学习中数据是如何管理的。我们将查看不同类型的数据存储,并学习在 Azure 中组织和管理数据以用于机器学习(ML)的最佳实践。
接下来,我们将创建一个Azure Blob 存储账户,并将其作为数据存储连接到 Azure 机器学习。我们将介绍使用流行的 CLI 工具以及Azure Data Factory和Azure Synapse Spark服务将数据导入 Azure 的最佳实践。
在下一节中,我们将学习如何从 Azure 中的数据创建数据集,访问和探索这些数据集,并将数据有效地传递到 Azure 机器学习工作区中的计算环境中。最后,我们将讨论如何通过第三方数据源访问 Azure 开放数据集,以改善模型性能。
本章将涵盖以下主题:
-
选择 Azure 机器学习的数据存储解决方案
-
创建数据存储和导入数据
-
在 Azure 机器学习中使用数据集
技术要求
在本章中,我们将使用以下 Python 库和版本来创建和管理数据存储和数据集:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0
与前几章类似,您可以使用本地 Python 解释器或 Azure 机器学习中的笔记本环境运行此代码。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter04。
选择 Azure 机器学习的数据存储解决方案
当在本地开发机器上运行 ML 实验或训练脚本时,您通常不会考虑管理您的数据集。您可能将训练数据存储在本地硬盘、外部存储设备或文件共享中。在这种情况下,访问实验或训练数据不会成为问题,您也不必担心数据位置、访问权限、最大吞吐量、并行访问、存储和出口成本、数据版本等问题。
然而,一旦你开始在远程计算目标上训练机器学习模型,例如云中的 VM 或 Azure 机器学习内部,你必须确保所有可执行文件都能有效地访问训练数据。如果你与其他人合作,他们也需要从多个环境和多台机器并行访问数据以进行实验、标记和训练,那么这一点尤为重要。如果你部署了一个需要访问这些数据的模型——例如,查找分类结果的标签、根据用户的评分历史进行评分推荐等——那么这个环境也需要访问数据。
在本节中,我们将学习如何在 Azure 中管理不同用例的数据。我们将首先了解 Azure 机器学习提供的抽象,以方便数据访问进行机器学习实验、训练和部署。
在 Azure 机器学习中组织数据
在 Azure 机器学习中,数据以数据集的形式进行管理,数据存储以数据存储的形式进行管理。这种抽象隐藏了数据集和数据存储对象背后的位置、数据格式、数据传输协议和访问权限的细节,因此让 Azure 机器学习用户能够专注于探索、转换和管理数据,而无需担心底层存储系统。
数据存储是物理数据存储系统的抽象,用于将现有的存储系统连接到 Azure 机器学习工作区。为了通过创建数据存储将现有存储连接到工作区,你需要提供存储系统的连接和身份验证详情。一旦创建,数据存储就可以通过数据存储对象被用户访问,该对象将自动使用数据存储定义中提供的凭据。这使得向在 Azure 机器学习工作区中协作的开发人员、数据工程师和科学家提供数据存储访问变得容易。目前,以下服务可以作为数据存储连接到工作区:
-
Azure Blob 容器
-
Azure 文件共享
-
Azure Data Lake
-
Azure Data Lake Gen2
-
Azure SQL 数据库
-
Azure Database for PostgreSQL
-
Databricks 文件系统
-
Azure Database for MySQL
虽然数据存储是数据存储系统的抽象,但数据集是数据的通用抽象——例如,以远程服务器上的文件形式存在的数据,这些文件可以通过公共 URL 访问,或者是在数据存储中的文件和表。Azure 机器学习支持两种数据格式抽象,即表格数据集和文件数据集。前者用于定义表格数据——例如,来自逗号或分隔符分隔的文件、Parquet 和 JSON 文件,或来自 SQL 查询——而后者用于指定来自文件和文件夹的任何二进制数据,例如图像、音频和视频数据。
可以直接从公开可用的 URL 定义和使用表格数据集,这被称为 pandas 和 requests。表格数据集和文件数据集都可以在您的工作区中注册。我们将把这些数据集称为 注册数据集。注册数据集将在您的 Azure Machine Learning Studio 下的 数据集 中显示。
理解 Azure 机器学习的默认存储账户
在 Azure Machine Learning 中存在一个特殊的数据存储,用于在执行实验运行时内部存储所有快照、日志、图表、模型等。这被称为 默认数据存储,是一个 Azure Blob 存储账户,并在您设置初始工作区时自动创建。在创建工作区期间,您可以选择自己的 Blob 存储作为默认数据存储,或者在 Azure Machine Learning Studio 中连接您的存储账户并将其标记为默认。
图 4.1 展示了 Azure Machine Learning Studio 中的数据存储列表。默认数据存储被标记为 默认,并在设置 Azure Machine Learning 工作区时自动生成。要访问此视图,只需在 Azure Machine Learning Studio 左侧菜单的 管理 类别下点击 数据存储。要查看现有数据集,请在 资产 类别下点击 数据集:

图 4.1 – Azure 机器学习中的默认数据存储
当没有定义其他数据存储时,默认数据存储由 Azure Machine Learning 内部使用来存储所有资产和工件。您可以通过创建数据存储引用,以与自定义数据存储相同的方式访问和使用默认数据存储。以下代码片段显示了如何获取默认数据存储的引用:
from azureml.core import Datastore
default_datastore = Datastore.get_default(ws)
默认数据存储由 Azure Machine Learning 内部使用,用于在机器学习生命周期中存储所有资产和工件。使用前面的代码片段,您可以访问默认数据存储以存储自定义数据集和文件。
一旦我们访问了默认的数据存储并连接了自定义数据存储,我们就需要考虑一个策略来高效地存储不同机器学习用例的数据。让我们在下一节中解决这个问题。
探索在 Azure 中存储训练数据的选项
Azure 支持多种不同的数据存储解决方案和技术,以在云中存储数据——正如我们在上一节中看到的,其中许多都是 Azure Machine Learning 支持的数据存储。在本节中,我们将探讨一些这些服务和技术,以了解哪些可以用于机器学习用例。
数据库系统可以根据数据类型和访问数据的类型进行广泛分类,分为以下两类:
-
关系数据库管理系统(RDBMSs)通常用于存储使用基于 B 树的有序索引的规范化事务数据。典型的查询通过连接多个表中的多行来过滤、分组和聚合结果。Azure 支持不同的 RDBMS,例如 Azure SQL 数据库、Azure Database for PostgreSQL 和 MySQL。
-
NoSQL:基于键值存储的系统通常用于存储使用基于哈希或有序索引的非规范化数据。典型的查询通过基于分区键分布的集合访问单个记录。Azure 支持不同的基于 NoSQL 的服务,如 Azure Cosmos DB 和 Azure 表存储。
如您所见,根据您的用例,您可以使用这两种数据库技术来存储机器学习数据。虽然 RDBMS 是存储机器学习训练数据的优秀技术,但 NoSQL 系统非常适合存储查找数据,例如训练标签,或机器学习结果,如推荐、预测或特征向量。
除了选择数据库服务之外,机器学习的另一种流行选择是使用数据存储系统。在磁盘上,大多数数据库服务以数据页的形式持久化在 文件 或 blob 存储系统 上。由于它们的可扩展性、性能、吞吐量和成本,blob 存储系统是存储各种数据和资产以供机器学习使用的非常流行的选择。Azure 机器学习广泛使用 blob 存储系统,特别是用于存储所有操作资产和日志。
流行的 Azure Blob 存储服务包括 Azure Blob 存储和 Azure Data Lake Storage,它们通过不同的数据格式选择提供了极大的灵活性,以实现高效的数据存储和访问解决方案。虽然 Azure Blob 存储支持大多数基于 blob 的文件系统操作,但 Azure Data Lake Storage 实现了高效的目录服务,这使得它成为横向可扩展文件系统的流行通用存储解决方案。它是存储大型机器学习训练数据集的流行选择。
虽然表格数据可以在 RDBMS 系统中有效地存储,但在 blob 存储系统上存储数据时,通过选择正确的数据格式和嵌入的聚类索引也可以实现类似的功能。选择正确的数据格式将允许您的文件系统有效地存储、读取、解析和聚合信息。
常见的数据格式选择可以分为文本格式(CSV、JSON 等)以及二进制格式(图像、音频、视频等)。用于存储表格数据的二进制格式通常分为行压缩格式(Protobuf、Avro、SequenceFiles 等)或列压缩格式(Parquet、ORC 等)。另一种流行的选择是使用 Gzip、Snappy 或其他压缩算法压缩整个文件。
大多数数据存储系统都共同拥有一种结构,即分层路径或目录结构来组织数据块。对于存储机器学习训练数据的一个流行选择是实施数据分区策略。这意味着数据被组织在多个目录中,每个目录包含特定键的所有数据,也称为分区键。
云服务提供商提供各种不同的存储解决方案,可以通过选择不同的索引、分区、格式和压缩技术进一步定制。对于存储机器学习表格训练数据的一个常见选择是使用列压缩的二进制格式,如 Parquet,按摄取日期分区,存储在 Azure 数据湖存储上,以实现高效的管理操作和可扩展的访问。
创建数据存储和导入数据
在查看 Azure 中用于 ML 处理的数据存储选项之后,我们现在将创建一个存储账户,我们将在整个书中使用它来存储原始数据和 ML 数据集。此外,我们还将探讨如何手动将一些数据传输到我们的存储账户,以及如何通过利用 Azure 中可用的集成引擎自动执行此任务。
创建 Blob 存储并将其与 Azure 机器学习工作区连接
首先,让我们创建一个存储账户。任何存储账户都将附带一个文件共享、一个队列以及表格存储,以便您在其他场景中利用。除了这三个之外,根据您在创建时提供的设置,您最终可能会得到 Blob 存储或数据湖。默认情况下,将创建 Blob 存储账户。如果我们想创建数据湖账户,我们必须将enable-hierarchical-namespace设置设置为True,因为数据湖提供了一个实际的分层文件夹结构,而不是一个扁平的命名空间。
创建 Blob 存储
记住这一点,让我们创建一个 Blob 存储账户:
-
导航到您选择的终端,登录 Azure,并确认您正在正确的订阅中工作,正如我们在第三章,“准备 Azure 机器学习工作区”中学到的。
-
由于我们要创建一个存储账户,让我们通过运行以下命令来查看创建账户的选项和所需设置:
$ az storage account create -h
查看结果,您将看到一个非常长的可能参数列表,但唯一必需的是name和resource-group。尽管如此,我们仍然应该进一步查看,因为许多其他设置仍然设置为某些默认值,这些值可能不适合我们的情况。
在查看列表时,您会发现许多关于网络或安全设置的选项。大多数选项的默认设置是至少允许从任何地方访问。在这个时候,我们不太关心虚拟网络集成或处理 Azure Key Vault 中的自己的管理密钥。
除了所有这些选项之外,还有一些定义了我们设置的存储账户类型的选项,即enable-hierarchical-namespace、kind、location和sku。
我们已经讨论了第一个选项,并且默认值为False,因此我们可以忽略它。
查看kind,您会看到一个存储类型的列表。您可能会认为我们需要选择BlobStorage,但不幸的是,这是一个遗留设置,留在了仍在运行第一版(V1)的任何存储账户中。对于我们的场景,默认的(StorageV2)是最佳选项。
查看location,我们看到我们可以为所有部署设置一个默认位置,因此它没有被标记为必需。由于我们迄今为止还没有这样做,我们将在部署存储账户时提供它。
最后,查看sku,我们看到这是一个关于所使用的磁盘技术类型(Standard/Premium)的选项组合,其中Standard表示 HDD 存储,Premium表示 SSD 存储,以及一个定义数据冗余方案(LRS/ZRS/GRS/RAGRS/GZRS)的选项。如果您想了解更多关于冗余选项的信息,请点击此链接:docs.microsoft.com/en-us/azure/storage/common/storage-redundancy。由于两者都会增加成本,您可以保留默认值(Standard_RAGRS)或选择本地冗余(Standard_LRS)。
-
让我们创建我们的存储账户。请注意,您选择的名称必须是全局唯一的,因此您不能选择在以下命令中将要读取的名称:
az storage account create \ --name mldemoblob8765 \ --resource-group mldemo \ --location westus \ --sku Standard_LRS \ --kind StorageV2
该命令生成的输出将显示创建的存储账户的详细设置。
-
作为最后一步,让我们在我们的新 Blob 存储中创建一个容器。为此,请使用适当的账户名称运行以下命令:
az storage container create \ --name mlfiles \ --account-name mldemoblob8765
结果将在最后显示True,但在之前会给出一些警告,类似于以下内容:
There are no credentials provided in your command and environment, we will query for account key for your storage account. It is recommended to provide --connection-string, --account-key or --sas-token in your command as credentials.
命令成功执行,因为它通过我们的会话自动拉取了存储账户的密钥。通常,要访问存储账户,我们需要一个 AD 身份、访问整个账户的密钥(account-key)或共享访问密钥(sas-token)以访问特定子目录或容器。当从 ML 工作区连接时,我们将会回到这一点。
要检查结果,请运行以下命令:
az storage container list \
--account-name mldemoblob8765 \
--auth-mode login
现在我们已经有了存储账户,让我们将其连接到我们的 Azure 机器学习工作区。
在 Azure 机器学习中创建数据存储
为了在处理我们的 ML 脚本时不再烦恼存储账户本身,我们现在将创建一个永久连接到存储账户中的容器,并将其定义为 Azure 机器学习工作区中的一个数据存储。
以下步骤将指导您完成此过程:
-
首先,让我们通过运行以下命令来了解创建数据存储所需的内容:
az ml datastore create -h
通过查看输出,我们了解到需要资源组的名称、ML 工作区的名称和一个 YAML 文件。我们有两个这样的东西。因此,让我们了解 YAML 文件应该是什么样子。
-
导航至
docs.microsoft.com/en-us/azure/machine-learning/reference-yaml-datastore-blob,在那里你可以找到我们文件的所需架构和一些示例。通过查看示例,你会发现它们主要在认证存储账户的方式上有所不同。其中最安全的是通过 SAS 令牌进行限制访问,因此我们将选择这条路径。 -
请从 GitHub 仓库中下载第四章,“数据摄取和管理数据集”的
blobdatastore.yml文件,或者创建一个具有相同名称和以下内容的文件:$schema: https://azuremlschemas.azureedge.net/latest/azureBlob.schema.json name: mldemoblob type: azure_blob description: main ML blob storage account_name: mldemoblob8765 container_name: mlfiles credentials: sas_token: <your_token>
请输入适合你情况的适当账户名称。现在唯一缺少的是 SAS 令牌,我们需要为我们的mlfiles容器创建它。
-
运行以下命令为我们的容器创建一个 SAS 令牌:
az storage container generate-sas \ --account-name mldemoblob8765 \ --name mlfiles \ --expiry 2023-01-01 \ --permissions acdlrw
此命令生成一个 SAS 令牌,有效期为 2023 年 1 月 1 日,并具有对mlfiles容器的权限。选择一个足够远的未来日期,以便你可以使用这本书。在正常情况下,你会选择一个更短的过期日期,并相应地旋转此密钥。
结果应该是这种格式:
xx=XXXX-XX-XX&xx=xxxx&xxx=xxx&xx=xxxxxxxxxxx&xx=XXXX-XX-XXXXX:XX:XXX&xx=XXXX-XX-XXXXX:XX:XXX&xxx=xxxxx&xxx=XXxXXXxxxxxXXXXXXXxXxxxXXXXXxxXXXXXxXXXXxXXXxXXxXX
将此结果(不带引号)输入 YAML 文件中的sas_token字段。
-
导航到 YAML 文件所在的目录,这样我们就可以通过运行以下命令在 Azure Machine Learning 工作区中最终创建数据存储:
az ml datastore create \ --workspace-name mldemows \ --resource-group mldemo \ --file ./blobdatastore.yml
结果应该看起来像以下这样:
"account_name": "mldemoblob8765",
"container_name": "mlfiles",
"credentials": {},
"description": "main ML blob storage",
"endpoint": "core.windows.net",
"id": <yourid>,
"name": "mldemoblob",
"protocol": "https",
"resourceGroup": "mldemo",
"tags": {},
"type": "azure_blob"
通过这些步骤,我们已经使用 SAS 令牌注册了一个连接到我们的 blob 存储的数据存储。
重要提示
当连接到数据湖存储时,你可以遵循相同的步骤,但请注意,要访问数据湖,你需要创建一个服务主体。有关此内容的详细描述,请参阅此处:docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal。
如前所述,我们可以在 Azure 门户中的向导中创建一个 blob 存储,为该容器创建一个 SAS 令牌,并在 Azure Machine Learning Studio 的数据存储创建向导中输入它。我们使用了 Azure CLI,这样你可以熟悉这个过程,因为这在将来自动化此类步骤时是必需的,尤其是在我们谈论基础设施即代码和 DevOps 环境时。
在任何情况下,请随意导航到 Azure Machine Learning Studio 中的数据存储选项卡。图 4.2显示了我们的新创建的工作区:

图 4.2 – 创建的数据存储
保持此标签页打开,以便我们可以在下一节中通过 mlfiles 容器进行验证。
将数据导入 Azure
我们创建了一个 Azure Blob 存储帐户,并学习了如何为常见的机器学习用例组织和格式化文件和表格数据。然而,一个常被忽视的步骤是如何将这些数据存储或 Azure 中的数据有效地导入。针对不同的数据集和用例有不同的解决方案,从临时、自动化、并行化解决方案等。在本节中,我们将探讨将数据手动或自动上传到关系数据库(SQL、MySQL 或 PostgreSQL)或 Azure 中的存储帐户的方法。最后,我们将上传一个数据集文件到之前创建的 blob 存储。
理解手动导入数据的相关工具
如果您处理少量数据集和文件,并且不需要从其他现有源传输数据,手动上传数据是首选选项。
以下列表显示了将数据带入您的数据存储或直接带入您的机器学习管道的可能选项:
-
Azure 存储资源管理器:存储资源管理器是一个交互式应用程序,允许您上传数据到并控制数据存储,例如存储帐户和管理磁盘。这是管理存储帐户最容易使用的工具,您可以在以下位置找到它:
azure.microsoft.com/en-us/features/storage-explorer/#overview。 -
Azure CLI:正如我们之前看到的,我们基本上可以使用 CLI 做任何事情,包括在存储帐户中创建和上传 blob。您可以在以下存储扩展中找到上传 blob 的适当命令:
docs.microsoft.com/en-us/cli/azure/storage/blob。 -
AzCopy:这是另一个专门设计用于将 blob 或文件复制到存储帐户的命令行工具。您使用 Azure CLI 软件包还是 AzCopy 取决于个人喜好,因为这两种选项之间没有明显的性能差异。您可以在以下位置找到下载链接和描述:
docs.microsoft.com/en-us/azure/storage/common/storage-use-azcopy-v10。 -
Azure 门户:对于任何服务,您都会在 Azure 门户中直接找到一个用于上传或更改数据的网络界面。如果您导航到存储帐户,您可以使用内置的存储浏览器通过网络界面直接上传 blob 和文件。对于任何数据库技术也是如此。
-
RDBMS 管理工具:您可以使用任何典型的管理工具来配置、创建和更改关系数据库中的表和模式。对于 SQL 数据库和 Synapse,这将使用SQL Server Management Studio (
docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15);对于 PostgreSQL,这将使用pgAdmin (www.pgadmin.org/);对于 MySQL,这将使用MySQL Workbench (docs.microsoft.com/en-us/azure/mysql/connect-workbench)。 -
Azure Data Studio:Data Studio 允许您连接到任何 Microsoft SQL 数据库、Synapse、Azure 中的 PostgreSQL 数据库以及 Azure 数据探索器。这是一个多平台工具,与最后一点中提到的典型管理工具非常相似,但仅在一个平台上。您可以从这里下载此工具:
docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio?view=sql-server-ver15。 -
Azure Machine Learning designer(导入数据):如果您不想使用 Azure 机器学习数据存储,您可以使用机器学习设计器中的导入数据组件将数据临时添加到您的管道中。这不是最干净的操作方式,但仍然是一个选择。您可以在以下位置找到有关此方法的所有信息:
docs.microsoft.com/en-us/azure/machine-learning/component-reference/import-data。
在我们测试这些选项之前,让我们看看在 Azure 中创建自动化数据流和转换数据的选择。
理解自动化数据摄取和转换的工具
对于小型测试来说,手动复制数据是完全可行的,甚至可能是我们将在本书中执行的大多数任务,但在现实世界的场景中,我们不仅需要与许多不同的源进行集成,还需要一个不涉及人员手动将数据从 A 移动到 B 的过程。
因此,我们现在将查看允许我们以自动化方式转换和移动数据的服务,并且这些服务与 Azure 机器学习中的管道和 MLOps 集成得非常好。
Azure Data Factory
Azure Data Factory 是 Azure 中用于移动和转换数据的企业级解决方案。它提供了连接到数百个不同源的能力,并能够创建管道以转换集成数据,同时调用 Azure 中的多个其他服务。
运行以下命令以创建数据工厂:
az datafactory create \
--location "West US 2" \
--name "mldemoDF8765" \
--resource-group "mldemo"
请注意,名称必须再次是全球唯一的。此外,在部署之前,CLI 将要求您安装datafactory扩展。
完成后,导航到 Azure 门户中的资源,在 概览 选项卡上点击 打开 Azure 数据工厂工作室,这将带您到您的数据工厂实例的工作台。您应该看到一个如 图 4.3 所示的视图:

图 4.3 – 数据工厂资源视图
从此视图,您可以创建管道、数据集、数据流和 Power Query。让我们简要讨论一下它们是什么:
-
管道:管道是 Azure 数据工厂的主要明星。您可以通过创建复杂的管道,调用多个服务从源拉取数据,对其进行转换,并将其存储在目标中。
-
数据集:数据集在管道中用作源或目标。因此,在构建管道之前,您可以定义一个连接到您最终想要从中读取或写入特定数据的数据存储的连接。
-
数据流:数据流允许您在数据工厂内部本身进行实际的数据处理或转换,而不是调用不同的服务来完成繁重的工作。
-
Power Query:Power Query 允许您在数据工厂内部使用 DAX 进行数据探索,这在其他情况下通常只有使用 Power BI 或 Excel 才能实现。
如果您点击 Pipeline 旁边的三个点,可以创建一个新的,这将导致如 图 4.4 所示的以下视图:

图 4.4 – 创建数据工厂管道
查看可能的活动,您会发现一种从 A 到 B 复制数据(Copy Data)、在 Azure Functions 中执行脚本(Azure Function)、在 SQL 数据库中调用存储过程(Stored Procedure)、在 Databricks 中执行笔记本(Notebook)以及执行 ML 管道(Machine Learning Execute Pipeline)等方法。通过这些活动和您在 通用 和 迭代与条件 中找到的控制工具,您可以构建非常复杂的数据管道来移动和转换您的数据。
如您可能已经注意到的,Azure Synapse 在活动列表中缺失。原因是 Synapse 在平台中集成了自己的数据工厂版本。因此,如果您在 Synapse 中使用 SQL 池或 Spark 池,您可以使用 Synapse 的集成工具,这将为您提供在 Synapse Spark 池中运行笔记本或在 SQL 池上调用存储过程的访问权限。
如果您正在寻找 Azure 数据工厂的深入概述,请查看 Catherine Wilhelmsen 的 Azure 数据工厂入门指南:www.cathrinewilhelmsen.net/series/beginners-guide-azure-data-factory/。
现在,我们需要理解的是,有两种方法可以将此数据工厂管道集成到 Azure Machine Learning 中:
-
从存储帐户读取结果:我们可以在数据工厂中运行转换管道,转换我们的数据,然后将结果存储在存储帐户中。然后,我们可以通过我们学习的方式访问数据,即通过一个 ML 数据存储。在这种情况下,我们在 Azure 机器学习中的任何管道都与数据工厂中的转换管道断开连接,这可能不是 MLOps 的最佳方式。
-
从数据工厂调用 Azure 机器学习:我们可以创建一个转换管道,并将实际的 Azure 机器学习管道作为数据工厂管道的一部分来调用。如果我们开始构建端到端的 MLOps 工作流程,这是首选方式。
关于这方面的更多信息,请阅读以下文章:docs.microsoft.com/en-us/azure/machine-learning/how-to-data-ingest-adf。
Azure Synapse Spark 池
如我们在第二章中讨论的,在 Azure 中选择合适的机器学习服务,Azure Databricks 和 Azure Synapse 提供了在 Spark 池中运行 Spark 作业的选项。Apache Spark 可以通过利用节点池的分布式特性来帮助您转换和预处理极其庞大的数据集。因此,这个工具在开始实际的机器学习过程之前,可以帮助我们分解和过滤数据集。
我们已经看到,我们可以从 Azure Data Factory 或 Azure Synapse 的集成引擎中运行笔记本,因此已经可以访问这些服务。除此之外,我们还有选项将 Synapse Spark 池作为所谓的链接服务添加到 Azure 机器学习工作区中(请参阅 Azure 机器学习工作室中的链接服务选项卡)。执行此步骤后,我们不仅可以访问 ML 计算目标,还可以通过 Azure 机器学习 SDK 将 Spark 池作为计算目标。
您可以通过 Azure 机器学习工作室或 Azure 机器学习 Python SDK 创建此链接,这两者都在以下文章中进行了描述:docs.microsoft.com/en-us/azure/machine-learning/how-to-link-synapse-ml-workspaces。
通过这种直接集成,我们可以在我们的 ML 管道中通过 Spark 集群运行转换步骤,因此又得到了构建干净端到端 MLOps 工作流程的另一个好选择。
将数据复制到 Blob 存储
现在,我们已经对大多数移动和转换数据的选项有了很好的理解,让我们将数据集上传到我们的存储帐户。
在第五章中,执行数据分析与可视化,我们将开始分析和预处理数据。为此,让我们上传本章中将使用的数据集。
我们将使用由 Anthony Pino 创建的墨尔本住房数据集,您可以在以下链接找到:www.kaggle.com/anthonypino/melbourne-housing-market。选择这个数据集的原因是它覆盖的领域,因为每个人都理解住房,以及数据的合理清洁度。如果你继续通过处理数据来推进你的旅程,你会发现有很多数据集,但只有少数是干净且具有教育意义的。
此外,为了使我们在下一章分析数据集时生活更加便利,我们将实际上只使用这个数据集的一个子集。
按照以下步骤操作,以便我们可以将此文件放入我们的mldemoblob数据存储中:
-
从
www.kaggle.com/dansbecker/melbourne-housing-snapshot下载melb_data.csv文件,并将其存储在你的设备上的一个合适的文件夹中。 -
导航到该文件夹,并在 CLI 中运行以下命令,将存储账户名称替换为您自己的名称:
az storage blob upload \ --account-name mldemoblob8765 \ --file ./melb_data.csv \ --container-name mlfiles \ --name melb_data.csv -
为了验证这一点,让我们看看另一种移动此文件的方法。安装 Azure 存储资源管理器,并在该应用程序中登录到您的 Azure 账户。导航到您的存储账户并打开
mlfiles容器。它应该显示如图 4.5 所示的视图:

图 4.5 – Azure 存储资源管理器
如您所见,我们的文件就在它应该的位置。我们也可以直接将文件拖放到这里,自动创建一个 blob 文件。从现在开始,请随意使用您觉得更舒适的方法。
- 为了完成这个步骤,请查看应用程序本身。例如,如果你在容器上右键点击,你可以选择一个名为获取共享访问签名的选项,这将打开一个向导,允许你直接在这里创建 SAS 令牌,而不是像我们通过命令行那样做。
通过之前的步骤,我们已经将原始数据集文件放入我们的存储账户中,因此也放入了我们的机器学习数据存储中。在下一节中,我们将探讨如何从这些原始文件创建 Azure 机器学习数据集,以及它们提供了哪些功能来支持我们当前的机器学习之旅。
在 Azure 机器学习中使用数据集
在本章的前几节中,我们讨论了如何将数据放入云端,将数据存储在数据存储中,以及如何通过数据存储和数据集将数据连接到 Azure 机器学习工作区。我们集中管理数据和数据访问,以便在所有计算环境中使用数据,无论是用于实验、训练还是推理。在本节中,我们将重点介绍如何在训练期间创建、探索和访问这些数据集。
一旦数据被管理为数据集,我们就可以在 Azure Machine Learning 中跟踪每个实验或训练运行所使用的数据。这将使我们能够了解特定训练运行和训练模型所使用的数据,这是创建可重复端到端机器学习工作流程的关键步骤。
将您的数据组织到数据集中还有另一个好处,那就是您可以通过直接访问、下载或挂载轻松地将管理数据集传递到您的实验或训练脚本中。直接访问方法适用于公开可用的数据源,下载方法适用于小型数据集,挂载方法适用于大型数据集。在 Azure Machine Learning 训练集群中,这完全透明,数据将自动提供。然而,我们可以使用相同的技巧通过访问数据集对象来访问任何其他 Python 环境中的数据。
在本节的最后部分,我们将探讨 Azure Open Datasets——一组您可以直接从 Azure Machine Learning 工作区中消费的精选 Azure Machine Learning 数据集。
创建新的数据集
创建新的数据集有多种方法,但大多数方法会区分表格数据和文件数据集。您需要根据您想要创建的数据集类型使用不同的构造函数:
-
Dataset.Tabular.from_*用于表格数据集 -
Dataset.File.from_*用于基于文件的数据集(例如,图像、音频等)
对于表格数据集,我们也会区分从原始位置通过公开 URL 访问的数据——称为直接数据集——或存储在默认或自定义数据存储中。
Dataset对象可以通过其对象引用在当前环境中访问或传递。然而,数据集也可以注册(并版本化),因此可以通过数据集名称(和版本)访问——这被称为注册数据集。
让我们看看一个简单的直接数据集示例,它被定义为表格数据集,并包含一个公开可用的 URL,该 URL 包含包含数据的分隔符文件:
from azureml.core import Dataset
path = 'https://...windows.net/demo/Titanic.csv'
ds = Dataset.Tabular.from_delimited_files(path)
如您在代码中所见,我们可以通过传递公开可访问的分隔符文件 URL 来创建一个直接数据集。当内部传递此数据集时,每个消费者都会尝试从其 URL 获取数据集。

图 4.6 – 直接数据集
一旦我们有一个数据存储的引用,我们就可以访问其中的数据。在以下示例中,我们从mldata数据存储的目录中创建一个文件数据集:
from azureml.core import Dataset, Datastore
datastore_name = "mldata"
datastore = Datastore.get(ws, datastore_name)
ds = Dataset.File.from_files((datastore, "cifar10/"))
如示例所示,我们可以将数据存储中的数据注册为数据集。在这个例子中,我们将文件夹中的所有文件定义为文件数据集,但我们也可以将 Blob 存储中的分隔符文件定义为表格数据集。

图 4.7 – 文件数据集
在下一步中,我们使用以下代码片段在工作空间中注册此数据集以创建一个 已注册的数据集:
ds = ds.register(ws, name="titanic",
create_new_version=True)
之前的代码将直接数据集注册到你的工作空间,并返回一个已注册的数据集。已注册的数据集列在 Azure Machine Learning Studio 中,可以通过数据集名称而不是 Dataset Python 对象访问。
create_new_version 参数控制我们是否想要创建现有数据集的新版本。一旦创建了一个新的数据集版本,就可以通过数据集名称访问数据集 - 这将隐式访问最新版本 - 或者通过其名称和特定版本。数据集版本对于管理工作空间内数据集的不同迭代非常有用。
探索数据集中的数据
在 Azure Machine Learning 中探索已注册数据集有多种选择。对于表格数据集,最方便的方法是在 Azure Machine Learning 工作空间中以编程方式加载和分析数据集。为此,你可以简单地通过其名称和版本引用数据集,如下面的代码片段所示:
from azureml.core import Dataset
ds = Dataset.get_by_name(ws, name="titanic", version=1)
一旦你有了数据集的引用,你可以将数据集引用转换为实际的内存中 pandas DataFrame 或懒加载的 Spark 或 Dask DataFrame。为此,你可以调用以下方法之一:
-
to_pandas_dataframe()用于创建内存中的 pandas DataFrame -
to_spark_dataframe()用于创建一个懒加载的 Spark DataFrame -
to_dask_dataframe()用于创建懒加载的 Dask DataFrame
让我们看看三个命令的实际操作,从内存中的 pandas DataFrame 开始。以下代码片段将所有数据加载到 pandas DataFrame 中,然后返回 DataFrame 的前五行:
panads_df = ds.to_pandas_dataframe()
pandas_df.head()
在加载 DataFrame 之后,你可以运行你喜欢的 pandas 方法来探索数据集。例如,开始时使用 info() 命令查看列和数据类型,以及使用 describe() 命令查看 DataFrame 中数值的统计信息。
懒加载数据集是仅在需要时才将部分数据加载到内存中的数据集,例如,当需要计算结果时。非懒加载数据集将所有数据加载到内存中,因此受可用内存的限制。
如果你更熟悉 PySpark,你也可以使用以下代码片段将数据集转换为 Spark DataFrame。与上一个示例相比,此代码实际上不会将所有数据加载到内存中,而只会获取执行 show() 命令所需的数据 - 这使其成为分析大型数据集的绝佳选择:
spark_df = ds.to_spark_dataframe()
spark_df.show()
另一种选择是返回数据集的 Dask DataFrame。Dask 是一个支持懒加载数据集的 Python 库,具有类似 pandas 和 NumPy 的 API。因此,你可以运行以下代码以懒加载方式返回 DataFrame 的前五行:
dask_df = ds.to_dask_dataframe()
dask_df.head()
一旦你获得了访问你喜欢的数值或统计库中数据的编程权限,你就可以根据需要对你的数据集进行切片和切块。虽然编程访问对于可重复性和定制化来说很棒,但用户通常只想了解数据是如何结构的,并查看一些示例记录。Azure Machine Learning 也提供了在 Data Studio UI 中探索数据集的可能性。
要进入此视图,请转到 数据集,选择一个数据集,然后点击 探索 选项卡。第一页显示了你的数据预览,包括前 n 行以及一些关于数据的基本信息——例如行数和列数。以下截图是一个示例:

图 4.8 – 带有数据预览的数据集
如果你点击第二个选项卡,你可以生成并查看数据概要。此概要类似于在 pandas DataFrame 上调用 describe()——对数据集中每一列的统计分析,但支持分类数据和一些更有用的信息。如图 4.9 所示,它还显示了每个列的数据分布图:

图 4.9 – 带有数据概要的数据集
如前图所示,这是数据集的一个非常有用的总结。从这个视图中获得的见解对于所有使用此数据集的人来说都很重要。
在本节中,我们看到了多种访问和分析 Azure Machine Learning 数据集中存储的数据的方法——通过 Python 和你喜欢的数值库进行编程访问,或者通过 UI。
Azure Machine Learning 中的数据集跟踪
对最终生产模型中所有资产的端到端跟踪对于可重复性、可解释性、审计和跟踪都是必不可少的。机器学习模型是一个通过迭代和从你的训练数据中采样实验来最小化损失函数的函数。因此,训练数据本身应被视为模型的一部分,因此应通过端到端的机器学习过程进行管理、版本控制和跟踪。
我们想利用数据集来为我们的实验添加数据跟踪。了解数据跟踪能力差异的一个好方法是通过两个示例:首先,从 URL 加载 CSV 数据集,然后通过 Azure Machine Learning 中的数据集抽象从同一 URL 加载数据。然而,我们不仅想加载数据,还想将其从编写脚本传递到训练脚本作为参数。
我们将首先使用 pandas 直接从 URL 加载 CSV 文件,并将其作为 URL 传递给训练脚本。在下一步中,我们将通过使用直接的数据集来增强此方法,这样我们就可以方便地将数据集配置传递给训练脚本,并跟踪 Azure Machine Learning 中实验运行的数据集。
将外部数据作为 URL 传递
我们以从远程 URL 可用的 CSV 文件数据开始我们的示例,这是分发公共数据集的常见方式。在第一个没有 Azure Machine Learning 数据集跟踪的示例中,我们将使用 pandas 库来获取和解析 CSV 文件:
-
让我们从使用 pandas 的
read_csv()方法作为示例的第一个代码片段开始,通过公共 URL 从远程服务器获取数据。然而,这只是一个示例——你可以用任何其他方法从远程位置获取数据:import pandas as pd path ='https://...windows.net/demo/Titanic.csv' df = pd.read_csv(path) print(df.head())
我们的目的是将数据从编写脚本传递到训练脚本,以便将来可以轻松跟踪和更新。为了实现这一点,我们不能直接传递 DataFrame,而必须传递 CSV 文件的 URL 并在训练脚本中使用相同的方法获取数据。让我们编写一个小型训练脚本,其唯一任务是解析命令行参数并从 URL 获取数据:
code/access_data_from_path.py
import argparse
import pandas as pd
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str)
args = parser.parse_args()
df = pd.read_csv(args.input)
print(df.head())
如前述代码所示,我们通过命令行的 --input 参数传递数据路径,然后使用 pandas 的 read_csv() 从位置加载数据。
- 接下来,我们创建一个
ScriptRunConfig构造函数,将实验运行提交给 Azure Machine Learning,以执行从 步骤 1 开始的训练脚本。现在,我们不做任何训练,只想了解编写和执行运行时之间传递的数据:
Access_data_from_path.ipynb
src = ScriptRunConfig(
source_directory="code",
script='access_data_from_path.py',
arguments=['--input', path],
environment=get_current_env())
- 让我们执行运行配置以运行实验并跟踪 Azure Machine Learning 中的运行详情。一旦实验运行完成,我们导航到 Azure Machine Learning 并检查这次运行的详细信息。正如我们在 图 4.10 中可以看到的,Azure Machine Learning 将按预期跟踪
script参数,但不能将参数关联到数据集:

图 4.10 – 实验运行详情
让我们总结一下这种方法的缺点:
-
我们不能将 pandas DataFrame 或 DataFrame 标识符传递给训练脚本;我们必须通过 URL 将数据传递到其位置。如果文件路径发生变化,我们必须更新训练脚本的参数。
-
训练脚本不知道输入路径指的是训练脚本的输入数据,它只是训练脚本的一个字符串参数。虽然我们可以在 Azure Machine Learning 中跟踪参数,但我们不能自动跟踪数据。
将外部数据作为直接数据集传递
如承诺,我们现在将使用 Azure Machine Learning 中的数据集增强先前的示例。这将允许我们将数据集作为命名配置传递——抽象 URL 和数据的物理位置访问。它还自动为实验启用数据集跟踪:
-
我们从编写脚本开始,从路径加载数据 - 只不过这次,我们使用 Azure Machine Learning 的
TabularDataset,通过from_delimited_files()工厂方法创建:from azureml.core import Dataset path ='https://...windows.net/demo/Titanic.csv' ds = Dataset.Tabular.from_delimited_files(path) print(ds.to_pandas_dataframe().head())
这将在 pandas 中输出与上一个示例相同的行集 - 所以除了使用不同的方法创建 DataFrame 之外,几乎没有区别。然而,现在我们已经创建了一个 直接数据集,我们可以轻松地将数据集传递给训练脚本作为命名数据集配置 - 这将在底层使用数据集 ID。
- 与 pandas 示例类似,我们编写了一个简化的训练脚本,该脚本将访问数据集并通过解析命令行参数中的输入数据集来打印前几条记录。在训练脚本中,我们可以使用
Dataset.get_by_id()方法通过其 ID 从工作区获取数据集:
code/access_data_from_dataset.py
import argparse
from azureml.core import Dataset, Run
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str)
args = parser.parse_args()
run = Run.get_context()
ws = run.experiment.workspace
ds = Dataset.get_by_id(ws, id=args.input)
print(ds.to_pandas_dataframe().head())
如前述代码所示,我们稍微修改了之前的代码,并添加了代码来检索当前的运行上下文、实验和工作区。这使得我们可以通过将数据集 ID 传递给 Dataset.get_by_id() 方法从工作区访问直接数据集。
- 接下来,我们编写一个运行配置,将前面的代码作为实验提交给 Azure Machine Learning。首先,我们需要将数据集转换为命令行参数,并将其传递给训练脚本,以便在执行运行时自动检索。我们可以通过在数据集实例上使用
as_named_input(name)方法来实现这一点,这将数据集转换为命名的DatasetConsumptionConfig参数,允许数据集传递到其他环境。
在这种情况下,数据集将以直接模式传递,并在运行时环境中作为 name 环境变量提供,或在命令行参数中作为数据集 ID。数据集也将作为训练脚本的输入参数在 Azure Machine Learning 中进行跟踪。
然而,如前述代码片段所示,我们在训练脚本中使用 Dataset.get_by_id() 方法从数据集 ID 获取数据集。我们不需要手动创建或访问数据集 ID,因为当 Azure Machine Learning 使用直接数据集调用训练脚本时,DatasetConsumptionConfig 参数将自动展开为数据集 ID。
Access_data_from_dataset.ipynb
src = ScriptRunConfig(
source_directory="code",
script='access_data_from_dataset.py',
arguments=['--input', ds.as_named_input('titanic')],
environment=get_current_env())
如前述代码所示,数据集被转换为可以通过 as_named_input(name) 方法简单传递给训练脚本的配置。如果我们提交实验并检查运行日志,我们可以看到 Azure Machine Learning 将数据集 ID 传递给了训练脚本:
70_driver_log.txt
...
After variable expansion, calling script [access_data_from_dataset.py] with arguments:['--input', '04f8ad60-5a51-4319-92fe-cdfa7f6c9adc']
该实验的运行详情如图 图 4.11 所示。如果您查看输入参数,您可以看到我们传递了 DatasetConsumptionConfig 对象给脚本,该对象随后自动转换为数据集 ID。不仅输入参数传递时没有关于底层数据位置的任何信息,输入数据集也被识别为训练数据的输入:

图 4.11 – 实验运行详情
通过将数据集传递给训练脚本,Azure Machine Learning 会自动跟踪实验运行中的数据集。如图 图 4.11 所示,数据集 ID 是追踪数据集的链接。当在 Azure Machine Learning 中点击数据集 ID 时,它将打开一个页面,显示追踪数据集的详细信息,例如描述、URL、大小和数据集类型,如图 图 4.12 所示。与已注册的数据集一样,您也可以探索原始数据,查看数据集列统计信息——称为概要——或查看从这些数据派生的任何已注册模型。通过点击 注册 操作或从代码中,可以轻松地将追踪数据集注册——从而进行版本控制和管理工作:

图 4.12 – Azure Machine Learning 中追踪的直接数据集
正如本节所示,将输入数据作为数据集参数传递给训练脚本有许多重要好处。这将自动跟踪您工作区中的数据集,并将数据集与实验运行连接起来。
在本节的代码片段中,我们以 直接数据集 的形式传递了数据,这意味着训练脚本必须再次从外部 URL 获取数据。这并不总是最优的,尤其是在处理大量数据或数据应在 Azure Machine Learning 中管理时。在下一节中,我们将探讨将数据传递给训练脚本的不同方法。
训练过程中的数据访问
在前一节中,我们隐式地将原始数据集的 URL 传递给训练脚本。虽然这对于小型公共数据集来说是一个实用且快速的方法,但对于私有或更大的数据集来说,通常不是首选的方法。想象一下,您的数据存储在 SQL 服务器、Blob 存储或文件共享上,并且受密码保护。想象一下,您的数据集包含许多千兆字节的文件。在本节中,我们将看到适用于这两种情况的技术。
当通过 URL 可达的外部公共数据以 直接数据集 的形式创建并传递时,所有其他数据集都可以通过 下载 或 挂载 的方式访问。对于大数据数据集,Azure Machine Learning 还提供了一个将数据集挂载为 Hadoop 分布式文件系统(HDFS)的选项。
在本节中,我们将看到创作脚本,这些脚本将以下载和挂载的方式传递数据集。让我们首先在创作脚本中创建对 cifar10 数据集的引用,该数据集我们在上一节中注册过。以下代码片段从 Azure Machine Learning 工作区通过名称检索数据集:
from azureml.core import Dataset
dataset = Dataset.get_by_name(ws, "cifar10")
接下来,我们希望将数据集传递给训练脚本,以便我们可以从脚本中访问训练数据。使用数据集的好处不仅在于跟踪,还在于我们可以简单地选择适合每个数据集的适当数据消费配置。它还将帮助我们分离训练脚本和训练数据,使得将新数据、更新数据或增强数据传递给相同的训练脚本变得容易,而无需更新训练脚本。
不论是哪种消费方式,训练脚本都可以始终从目录路径加载数据,该路径是数据下载或挂载的位置。在底层,Azure Machine Learning 会检查 ScriptRunConfig 的命令行参数,检测数据集引用,将数据传递到计算环境,并用本地文件系统中数据集的路径替换参数。
Azure Machine Learning 使用参数扩展将数据集引用替换为磁盘上实际数据的路径。为了使这一点更加明显,我们将编写一个单独的训练文件,该文件将简单地列出传递给它的所有训练文件。以下代码片段实现了这个训练脚本:
code/access_dataset.py
import os
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str)
args = parser.parse_args()
print("Dataset path: {}".format(args.input))
print(os.listdir(args.input))
在前面的脚本中,我们定义了一个单一的 --input 参数,我们将使用它来传递训练数据。然后我们将输出此参数并列出目录中的所有文件。我们将使用此脚本通过不同的挂载技术传递数据,并将看到数据始终可用在文件夹中。
在拥有数据集引用和简单的训练脚本之后,我们现在可以查看不同的 ScriptRunConfig,使用不同的数据消费配置传递 cifar10 数据集。虽然代码在调用训练脚本之前由 Azure Machine Learning 下载或挂载,但我们将探索底层发生了什么——这样我们就可以将相同的技巧应用于在 Azure Machine Learning 管理的计算环境之外加载训练数据。
以下载方式访问数据
我们首先将查看将数据下载到训练实例的过程。为此,我们将在创作环境中创建一个 ScriptRunConfig 构造函数,并将数据传递给 as_download()。我们将安排一个代码片段,该片段将访问并输出传递给脚本的文件:
Access_dataset_as_download.ipynb
from azureml.core import ScriptRunConfig
src = ScriptRunConfig(
source_directory="code",
script='access_dataset.py',
arguments=['--input',
dataset.as_named_input('cifar10').as_download()],
environment=get_current_env())
Azure 会将 input 参数传递的数据集进行插值,并用磁盘上数据集的位置替换它。如果数据集是通过 Dataset.as_download() 方法传递的,数据将自动下载到训练环境中。
如果你运行此脚本配置,access_dataset.py脚本将输出数据集的临时位置,该数据集已自动下载到磁盘。你可以在你的创作环境中复制 Azure Machine Learning 在内部所执行的确切过程。为此,你可以简单地调用以下代码:
folder = '/tmp/cifar10-data'
paths = dataset.download(folder)
以下载方式传递数据对于小型数据集或使用大量消费者且对数据有高吞吐量需求的情况来说很方便。然而,如果你处理的是大型数据集,你也可以将它们作为挂载传递。
以挂载方式访问数据
在本例中,我们将数据挂载到训练环境中。为此,我们将在创作环境中再次创建一个ScriptRunConfig构造函数,这次我们将调用as_mount()。我们将安排一个代码片段,该片段将访问并输出传递给脚本的文件:
Access_dataset_as_mount.ipynb
from azureml.core import ScriptRunConfig
src = ScriptRunConfig(
source_directory="code",
script='access_dataset.py',
arguments=['--input',
dataset.as_named_input('cifar10').as_mount()],
environment=get_current_env())
如你所见,前面的示例与之前将数据下载到磁盘的示例非常相似。事实上,我们正在重用完全相同的计划脚本access_dataset.py,该脚本将输出数据在磁盘上的位置。然而,在这个示例中,数据并没有下载到这个位置,而是挂载到文件路径。
Azure Machine Learning 会将通过输入参数传递的数据集与磁盘上的挂载路径进行插值。类似于上一个示例,你可以在 Azure Machine Learning 内部复制所发生的事情,并在你的创作环境中挂载数据:
import os
folder = '/tmp/cifar10-data'
# Or you can also use the start and stop methods
mount_context = dataset.mount(folder)
try:
mount_context.start()
print(os.listdir(folder))
finally:
mount_context.stop()
如你从前面的代码片段中看到的,数据集是通过挂载上下文的start和stop方法挂载和释放的。你还可以使用 Python 的with语句简化代码片段,自动挂载和卸载数据,如下面的代码片段所示:
with dataset.mount() as mount_context:
print(os.listdir(mount_context.mount_point))
因此,根据用例的不同,我们有不同的选项将数据集引用传递给计划中的脚本。独立于数据传输,Azure Machine Learning 将在内部实现正确的方法,并插值输入参数,这样训练脚本就不需要知道数据集是如何配置的。对于执行的脚本,数据通过文件系统中的路径简单地提供。
使用外部数据集与公开数据集
提高任何机器学习模型预测性能的最有效方法之一是向你的训练数据中添加额外的信息。实现这一目标的一种常见方式是将外部数据集与训练数据合并。一个很好的迹象是,在你的数据集中存在流行的合并键,例如日期、位置、国家等。
当您处理包含日期的交易数据时,您可以轻松地将外部数据连接起来,为训练数据集创建额外的特征,从而提高预测性能。常见的日期衍生特征包括工作日、周末、到或自周末的时间、假日、到或自假日的时间、体育赛事、音乐会等。当处理国家信息时,您通常可以连接额外的特定国家数据,例如人口数据、经济数据、社会学数据、健康数据、劳动数据等。当处理地理位置时,您可以连接到兴趣点的距离、天气数据、交通数据等。每个额外的数据集都为您提供了额外的见解,因此可以显著提升模型性能。
开放数据集是一个提供访问精选数据集的服务,这些数据集可用于交通、健康和基因组学、劳动和经济、人口以及安全等类别和常用数据集,您可以使用这些数据集来提升模型性能。让我们看看三个例子。
重要提示
在使用特定数据集为商业服务之前,请确保您的应用程序受许可证覆盖。如有疑问,请联系微软的 aod@microsoft.com。
在第一个例子中,我们将研究全球公共假日的数据集。数据涵盖了 1970 年至 2099 年近 40 个国家和地区或地区的假日。这些数据来自维基百科和holidays Python 包。您可以将它们导入到您的环境中,并使用以下示例中的opendatasets库访问这些假日:
from azureml.opendatasets import PublicHolidays
from dateutil import parser
end_date = parser.parse("Jan 10 2000 12:00AM")
start_date = parser.parse("Jan 10 2010 12:00AM")
ds = PublicHolidays(start_date=start_date,
end_date=end_date)
df = ds.to_pandas_dataframe()
正如我们在代码中看到的,我们可以从azureml-opendatasets包中访问数据集,并将其用作 Azure Machine Learning 数据集。这意味着我们可以返回 pandas 或 Spark DataFrame 以进行进一步处理。
另一个流行的数据集是按县划分的2000 年和 2010 年美国人口。它按性别和种族细分,并来源于美国人口普查局:
from azureml.opendatasets import UsPopulationZip
population = UsPopulationZip()
population_df = population.to_pandas_dataframe()
另一个公开数据集的例子是美国劳工统计局(BLS)发布的当前就业统计。它包含了美国工资单上工人的就业、小时数和收入估计:
from azureml.opendatasets import UsLaborEHENational
ds = UsLaborEHENational()
df = ds.to_pandas_dataframe()
正如您在本节中看到的,Azure Open Datasets 为您提供了一个方便的选项,可以直接在您的 Azure Machine Learning 工作区中访问以 Azure Machine Learning 数据集形式提供的精选数据集。虽然可用的数据集数量仍然可控,但您可以预期可用的数据集数量会随着时间的推移而增长。
摘要
在本章中,我们学习了如何使用数据存储和数据集在 Azure Machine Learning 中管理数据。我们看到了如何配置负责在 Azure Machine Learning 中存储所有资产、日志、模型等默认数据存储,以及可用于不同类型数据的其他服务。
在创建 Azure Blob 存储账户并将其配置为 Azure Machine Learning 中的数据存储后,我们看到了将数据导入 Azure 的不同工具,例如 Azure Storage Explorer、Azure CLI 和 AzCopy,以及针对数据导入和转换优化的服务,如 Azure Data Factory 和 Azure Synapse Spark。
在随后的部分,我们实际操作了数据集。我们创建了文件和表格数据集,并了解了直接和注册数据集。数据集可以作为下载或挂载传递给执行脚本,这将自动跟踪 Azure Machine Learning 中的数据集。
最后,我们学习了如何通过将 Azure Open Datasets 的第三方数据集加入我们的机器学习流程中来提高预测性能。在下一章中,我们将学习如何通过执行数据分析与可视化来探索数据。
第五章:第五章:执行数据分析和可视化
在上一章中,我们学习了如何将我们的数据集带到云端,在 Azure 机器学习工作区中定义数据存储以访问它们,并在 Azure 机器学习数据集注册表中注册数据集,从而为从数据预处理开始打下良好的基础。在本章中,我们将学习如何探索这些原始数据。
首先,您将了解可以帮助您探索表格和文件数据集的技术。我们还将讨论如何处理缺失值,如何交叉关联特征以了解它们之间的统计关系,以及如何将领域知识应用到这个过程中,以改善我们对上下文和数据清洗质量的了解。此外,我们将学习如何使用机器学习算法,不是为了训练,而是为了探索我们的数据集。
之后,我们将将这些方法应用于实际数据集,同时学习如何使用 pandas DataFrame 以及如何可视化数据集的特性。
最后,我们将探讨可以将高维数据映射到低维平面的方法,这将帮助我们看到数据点之间的相似性和关系。此外,这些方法还可以给我们提供清晰的线索,了解我们的数据有多干净,以及所选机器学习算法在数据集上的效果如何。
在本章中,我们将涵盖以下主题:
-
理解数据探索技术
-
在表格数据集上执行数据分析
-
理解降维技术
技术要求
在本章中,我们将使用以下 Python 库和版本来执行数据预处理和高维可视化:
-
azureml-sdk 1.34.0 -
azureml-widgets 1.34.0 -
azureml-dataprep 2.20.0 -
pandas 1.3.2 -
numpy 1.19.5 -
scikit-learn 0.24.2 -
seaborn 0.11.2 -
plotly 5.3.1 -
umap_learn 0.5.1 -
statsmodels 0.13.0 -
missingno 0.5.0
与前几章类似,您可以使用本地 Python 解释器或 Azure 机器学习中的笔记本环境执行此代码。
本章中的所有代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter05。
理解数据探索技术
描述性数据探索无疑是机器学习项目中最重要的步骤之一。如果您想清理数据、构建派生特征或选择机器学习算法来预测数据集中的目标变量,那么您首先需要了解您的数据。您的数据将定义许多必要的清理和预处理步骤;它将定义您可以选择哪些算法,并最终定义您预测模型的性能。
因此,数据探索应该被视为理解数据是否足够信息以首先构建机器学习模型的重要分析步骤。通过分析步骤,我们是指探索应该作为一个结构化的分析过程来完成,而不是一系列实验任务。因此,我们将通过一系列数据探索任务清单,这些任务可以作为每个机器学习项目的初始步骤来执行——在你开始任何数据清洗、预处理、特征工程或模型选择之前。
我们可以执行的可能任务与我们正在处理的数据集类型相关。许多数据集将以表格数据的形式出现,这意味着我们为数据集的每个实例定义了连续或分类特征。这些数据集可以表示为表格,我们可以在它们上执行基本的和复杂的数学运算。我们可能遇到的另一种一般类型的数据集将以媒体文件的形式出现。这包括图像、视频、声音文件、文档以及任何其他不能放入表格结构中的数据点。
为了表示这些不同类型的数据集,Azure 机器学习提供了将我们的数据保存为以下对象之一的选项:
-
TabularDataset: 这个类提供了对表格数据进行基本转换的方法,并将它们转换为已知格式,例如 pandas (
docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.tabulardataset)。 -
FileDataset: 这个类主要提供对文件元数据的过滤方法 (
docs.microsoft.com/en-us/python/api/azureml-core/azureml.data.filedataset)。
这两种类型的数据集对象都可以注册到 Azure 机器学习数据集注册表中,以便在预处理后进一步使用。
仅从那些两个类别中可用的方法来判断,很明显,我们可以在表格数据集和文件数据集之间执行的可能任务和操作有很大差异。在接下来的几节中,我们将探讨这两种类型以及我们如何准备它们以影响我们机器学习模型的结果。
探索和分析表格数据集
表格数据集允许我们利用数学和统计函数的全谱来分析和转换我们的数据集,但在大多数情况下,我们没有时间或资源来随机将每个数据集通过我们工具箱中所有可能的技术。
选择正确的方法不仅需要分析大量不同数据集的经验,还需要我们所在领域的专业知识。有些领域每个人都有一些一般的专长(例如,房价的影响因素),但还有很多领域需要专业知识来理解手头的数据。想象一下,你想要提高炼钢高炉的产量。在这种情况下,为了理解数据,你需要对高炉中的化学过程有深入了解,或者你需要一个领域专家来支持你。在探索和分析的每一步中,我们需要应用领域知识来解释我们看到的结果和关系。
除了理解领域之外,我们还需要理解数据集中的特征及其目标或标签。想象一下,有一个由某个城市房屋特征组成的数据集,但没有它们的市场价格。为了预测房价,我们需要每个房屋的价格标签或目标值。另一方面,如果我们想要预测一封电子邮件是否为垃圾邮件,而我们有一个包含大量元数据的电子邮件数据集,这可能足以通过无监督学习训练模型。
因此,为了对数据集有一个良好的理解,我们需要彻底探索其内容,尽可能多地从特征和可能的靶标中获取洞察力,以便做出明智的决策。
重要提示
请记住,不仅特征维度需要预处理和分析,目标变量也需要。
为了实现这一点,我们将首先查看数据集中每个特征和目标向量的以下方面:
-
datetime、string、int、object?我们需要进行数据类型转换吗? -
缺失数据:是否存在任何缺失条目?我们如何处理它们?
-
不一致的数据:日期和时间是以不同的方式存储的吗?相同的类别是否以不同的方式书写?在给定上下文中,是否存在具有相同意义的不同类别?
-
唯一值:对于一个分类特征,存在多少唯一值?是否太多?我们应该创建它们的子集吗?
-
统计特性:一个特征的均值、中位数和方差是什么?是否存在任何异常值?最小值和最大值是什么?最常见的值(众数)是什么?
-
统计分布:值是如何分布的?是否存在数据偏斜?标准化或缩放是否有用?
-
相关性:不同的特征之间是如何相互关联的?是否存在包含相似信息可以被省略的特征?我的特征与目标的相关性有多大?
分析具有超过 100 个特征维度的数据集的每个维度是一项极其耗时的工作。然而,您可以通过按特征重要性排序的维度来分析,而不是随机探索特征维度,从而显著减少您处理数据的时间。像计算机科学的许多其他领域一样,在初始数据探索中使用 80/20 原则是好的,这意味着只使用 20%的特征来实现 80%的性能。这将为您提供一个良好的起点,您可以在需要时随时返回添加更多维度。
因此,了解特征对建模的重要性是明智的。我们可以通过查看特征与目标变量之间的关系来实现这一点。有许多方法可以做到这一点,以下是一些方法:
-
回归系数:用于回归
-
特征重要性:用于分类
-
分类值的错误率较高:用于二元分类
通过应用这些步骤,您可以了解数据,并获取有关数据、特征和目标变量的预处理任务的知识。此外,它将为您提供对预测任务中可能遇到的困难的良好估计,这对于判断所需的算法和验证策略至关重要。您还将深入了解可能应用于数据集的特征工程方法,并更好地理解如何选择一个好的误差度量。
重要提示
您可以使用数据的代表性子集,并将您的假设和见解外推到整个数据集。
一旦数据已上传到 Azure 的存储服务中,我们就可以启动笔记本环境并开始探索数据。目标是通过对数据的深入分析过程,了解数据每个维度的分布。我们将在“在表格数据集上执行数据分析”部分执行这些步骤中的一些。
但首先,我们将回顾我们讨论过的某些技术,并快速查看文件数据集。
处理缺失值和异常值
在新的数据集中,首先要寻找的是每个特征和目标维度的缺失值。这将帮助您更深入地了解数据集以及可以采取哪些措施来解决这些问题。在项目开始时删除缺失值或用零填充它们并不罕见——然而,这种方法存在风险,即最初未能正确分析缺失值,并丢失大量数据点。
重要提示
缺失值可能被伪装成有效的数值或分类值。典型的例子是最小值或最大值,-1,0,或 NaN。因此,如果你在一个整数数据列中多次发现 32,767(= 215-1)或 65,535(= 216-1)这样的值,它们可能正是伪装成最大有符号或无符号 16 位整数表示的缺失值。始终假设你的数据包含不同形状和表示的缺失值和异常值。你的任务是揭示、找到并清理它们。
关于数据或领域的任何先验知识都会在你处理数据时给你带来竞争优势。这是因为你将能够理解数据和相关领域的缺失值、异常值和极端值,这将帮助你更好地进行插补、清理或转换。作为下一步,你应该在你的数据中寻找这些异常值,特别是以下方面的绝对数量或百分比:
-
空值(查找
Null,"Null","",NaN等) -
最小值和最大值
-
最常见的值(
MODE) -
0值 -
任何唯一值
一旦你确定了这些值,你可以使用不同的预处理技术来填补缺失值和归一化或排除维度。
处理缺失值的典型选项如下:
-
删除:从数据集中删除整个行或列。这可能导致偏差或训练数据不足。
-
对于分类特征,查找
Missing。 -
列平均值:根据与其他特征的关系,填充整个数据列或列子集的均值、中位数或众数。
-
插值:根据列的数据填充一个插值值。
-
热补丁插补:从数据列的排序记录中填充逻辑前一个值(在时间序列数据集中很有用)。
处理异常值的典型选项如下:
-
错误观测值:如果值是错误的,可以删除整个列,或者用列的均值替换异常值。
-
保留原样:如果它包含重要信息,并且模型不会因此受到扭曲。
-
上限或下限:将值限制在均值最大偏差内(例如,三个标准差)。
为了在选择处理缺失值和异常值的方法时获得更多上下文,统计分析列分布和相关性是有用的。我们将在以下章节中这样做。
计算统计属性和可视化数据分布
现在你已经知道了异常值,你可以开始探索你的数据集特征的值分布。这将帮助你了解在数据准备过程中应该应用哪些转换和归一化技术。在连续变量中寻找的一些常见分布统计量如下:
-
均值或中位数
-
最小值和最大值
-
方差和标准差
-
第 25、50(中位数)和第 75 百分位数
-
数据倾斜
可视化这些分布的常用技术包括使用箱线图、密度图或直方图。以下截图显示了针对多类识别数据集的每个目标类别的不同可视化技术。每种方法都有其优缺点——箱线图显示了所有相关指标,但阅读起来稍微有点困难,密度图显示了非常平滑的形状,但隐藏了一些异常值,而直方图则不让你容易地找到中位数和百分位数,同时给你一个数据倾斜的良好估计:

图 5.1 – 箱线图(左),密度图(中),直方图(右)
在这里,我们可以看到,对于分类数据(无论是名义的还是序数的),只有直方图工作得很好。然而,你可以查看每个类别的值数量。你可以在本书的 GitHub 仓库中的 01_data_distribution.ipynb 文件中找到创建这些图表的代码。
在二元分类任务中,另一种显示值分布与目标率关系的好方法是。以下图表显示了来自 Microsoft 恶意软件检测数据集(www.kaggle.com/c/microsoft-malware-prediction/data)的 Windows Defender 的 版本号 与恶意软件 检测率(针对非触摸设备):

图 5.2 – Windows Defender 版本号与检测率的关系
许多统计机器学习算法需要数据呈正态分布,因此需要归一化或标准化。了解数据分布有助于你决定在数据准备期间需要应用哪些转换。在实践中,数据通常需要转换、缩放或归一化。
寻找相关维度
数据探索中的另一个常见任务是寻找数据集中的相关性。这将帮助你排除高度相关的特征维度,从而可能影响你的机器学习模型。例如,在线性回归模型中,两个高度相关的独立变量将导致具有相反符号的大系数,最终相互抵消。通过移除一个相关维度,可以找到一个更稳定的回归模型。因此,不仅需要查看特征和目标之间的相关性,还需要查看特征之间的相关性。
-1(强负相关)到1(强正相关)。0表示两个变量之间没有线性关系。
以下图表显示了加利福尼亚住房数据集(www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html)的相关矩阵示例,该数据集仅包含连续变量。相关系数的范围从-1到1,并相应着色,其中红色表示负相关,蓝色表示正相关。最后一行显示了每个特征维度与目标变量(MedHouseVal)之间的线性相关性。我们可以立即看出,Longitude与Latitude之间存在相关性,MedHouseVal与MedInc之间存在相关性,以及AveRooms与AveBedrms之间存在相关性。所有这些关系都是相对不出意外的:

图 5.3 – 加利福尼亚住房数据集的相关矩阵
你可以在本书 GitHub 仓库中的02_correlation.ipynb文件中找到创建此相关矩阵的代码。
值得注意的是,许多相关系数只能存在于数值之间。有序变量可以通过整数编码进行编码,也可以计算出一个有意义的相关系数。对于名义数据,你需要回退到不同的方法,例如Cramér's V来计算相关性。值得注意的是,在计算相关系数之前,输入数据不需要进行归一化(线性缩放)。
测量回归中的特征和目标变量依赖性
一旦我们分析了缺失值、数据分布和相关性,我们就可以开始分析特征与目标变量之间的关系。这将为我们提供预测问题难度的良好指示,从而确定预期的基线性能,这对于优先考虑特征工程努力和选择合适的机器学习模型至关重要。测量这种依赖性的另一个巨大好处是按特征维度对目标变量的影响进行排名,你可以将其用作数据探索和预处理优先级列表。
在回归任务中,目标变量是数值或有序的。因此,我们可以计算单个特征与目标变量之间的相关系数,以计算特征与目标变量之间的线性依赖性。高相关性,即高绝对相关系数,表明存在强烈的线性关系。这为我们进一步探索提供了一个很好的起点。然而,在许多实际问题中,很少看到特征与目标变量之间存在高(线性)相关性。
您还可以使用散点图或回归图来可视化特征和目标变量之间的依赖关系。以下图表显示了来自波士顿住房数据集的平均每户住宅房间数(RM)与业主自住房屋的中位数价值(MEDV)之间的回归图。如果回归线在 45 度角,则表示存在完美的线性相关性:

图 5.4 – 特征与目标之间的散点图,带有回归线
确定这种依赖关系的另一种有效方法是,将线性或逻辑回归模型拟合到训练数据中。得到的模型系数应该能为你提供对关系的良好解释——系数越高,对目标变量的线性(对于线性回归)或边际(对于逻辑回归)依赖性就越大。因此,按系数排序将得到一个按重要性排序的特征列表。根据回归类型,输入数据应进行归一化或标准化。
以下截图显示了拟合的普通最小二乘法(OLS)回归模型的关联系数(第一列):

图 5.5 – OLS 回归模型的关联系数
您可以在本书 GitHub 仓库中的03_regression.ipynb文件中找到创建图表和系数的代码。
虽然得到的R 平方指标(未显示)可能不足以作为基线模型,但系数的排序可以帮助我们优先考虑进一步的数据探索、预处理和特征工程。
可视化特征和标签之间的分类依赖关系
在具有多类名义目标变量的分类任务中,我们不能在不进一步预处理数据的情况下使用回归系数。另一种流行的、开箱即用的有效方法是,将简单的基于树的分类器拟合到训练数据中。根据训练数据的大小,我们可以使用决策树或基于树的集成分类器,如随机森林或梯度提升树。这样做将根据选择的分割标准对特征维度进行特征重要性排序。在以熵分割的情况下,特征将按信息增益排序,这将表明哪些变量携带关于目标变量最多的信息。
以下图表显示了使用来自UCI 葡萄酒识别数据集(archive.ics.uci.edu/ml/datasets/wine)的熵标准由基于树的集成分类器拟合的特征重要性:

图 5.6 – 基于树的集成分类器的特征重要性
这些线条代表单个树之间特征信息增益的变化。这个输出是进一步数据分析和按特征重要性顺序探索的绝佳第一步。您可以在本书 GitHub 仓库中的04_feature_importance.ipynb文件中找到计算特征重要性并可视化的代码。
这里是另一种发现数据集可分性的流行方法。以下截图显示了一个包含三个类别的数据集,其中一个是线性可分的,另一个不是:

图 5.7 – 线性可分数据集(左)与非线性可分数据集(右)的比较
您可以在本书 GitHub 仓库中的05_separability.ipynb文件中找到创建这些可分性图的代码。
通过观察三个簇以及这些簇之间的重叠,您可以发现,如果簇是分离的,那么训练好的分类模型将在这个数据集上表现非常好。另一方面,当我们知道数据不是线性可分的时候,我们知道这项任务将需要高级的特征工程和建模来产生良好的结果。
探索和分析文件数据集
由媒体文件组成的数据集完全是另一回事。如果我们以图像为例,我们可以将每个像素视为信息向量,并将其视为图像的一个特征。但在探索和数据处理方面我们能做什么呢?可能对单个特征来说并不多。大多数时候,我们需要关注的是大量像素或整个图像本身。从广义上讲,我们可以考虑以下方面:
-
一致性:数据集中的所有图像都应该具有相同的大小。如果不是,它们需要被重新缩放,这可能涉及到每个通道的像素值居中,可能还伴随着某种形式的归一化。
-
增强:这涉及到在不获取新数据(新图像)的情况下使数据集多样化。如果我们有一个小的数据集,这通常涉及到水平翻转、裁剪和旋转等变换。
观察这些选项,很明显我们正在尝试修复图像数据集中可能在我们最初拍摄图像时已经基本解决的问题。因此,现实情况是,当我们处理大多数类型的媒体文件时,将更高的注意力集中在为数据集采集好的训练样本上,比在预处理阶段拼命修复它们至关重要。
让我们想象一下,我们是一家制造商,想要拍摄他们生产的在传送带上通过的产品照片,以找出有缺陷的产品并丢弃它们。假设我们在全球各地都有生产基地。你将如何确保照片尽可能均匀地拍摄,同时覆盖许多不同的场景?以下是一些需要考虑的方面:
-
相机类型:我们可能需要全球各地都使用相同类型的相机以相同的格式拍照。
-
环境条件:所有地方的光照是否相似?温度和湿度是否在所有地方都相似?这可能会影响相机的电子设备。
-
定位:是否使用了相同的拍摄角度?我们能否从非常不同的角度拍照以增加多样性?
这些只是你在拍照时需要考虑的一些点。
现在,让我们看看文件数据的一种另一种形式——声音文件。假设我们想要构建一个语音转文本模型,将我们说的话转换成书面文本。这类模型例如在语音助手中被用来将请求映射到一系列要执行的操作。
在这个背景下,我们可以使用傅里叶变换等方法来分解我们的声音文件。然而,我们可能需要考虑我们想要训练的样本或训练数据,以及如何在考虑以下方面的同时提高它们的质量:
-
录音硬件:如果我们家里有语音助手,可能每个人都在使用同一个麦克风。但如果我们为手机构建语音助手呢?那么,我们就有了非常不同的麦克风。
-
环境:我们可能需要在不同环境中录制声音。当我们站在有轨电车上时,与我们在录音棚中时,声音频谱肯定不同。
-
发音:你大脑中的机器学习算法可能难以解析不同的发音——尤其是方言。一个实际的机器学习模型如何处理这个问题?
这些只是处理声音文件时需要考虑的一些点。关于发音,如果你查看Azure 语音服务,你很快就会意识到后台运行着两个模型——一个用于声学,一个用于语言。在构建自定义模型时查看样本的要求(docs.microsoft.com/en-us/azure/cognitive-services/speech-service/how-to-custom-speech-test-and-train),因为这可以给你一个很好的想法,当你从头开始构建这样的模型时需要什么。
总结来说,对于文件数据集,我们没有太多选项来从统计上消除问题,因此我们应该专注于采集好的、干净的样本,这些样本模拟了模型在生产环境中运行时可能遇到的现实环境。
现在我们已经熟悉了探索和分析不同类型数据集的方法,让我们尝试在一个实际的表格数据集上应用这些方法。
在表格数据集上执行数据分析
如果您没有遵循第四章“数据摄取与管理数据集”中的步骤,从melb_data.csv下载墨尔本住房数据集的快照,在您的存储账户的mlfiles容器中,并将其连接到 Azure Machine Learning 工作区中的数据存储mldemoblob。
在接下来的章节中,我们将探索数据集,进行一些基本的统计分析,查找缺失值和异常值,找出特征之间的相关性,并在使用随机森林模型的同时,对特征的重要性进行初步测量,正如我们在本章的“可视化特征和标签依赖性以进行分类”部分所看到的。您可以选择创建一个新的 Jupyter 笔记本并跟随本书进行,或者打开 GitHub 仓库中本章的06_ dataprep_melbhousing.ipynb文件。
注意,我们现在要执行的步骤并不全面。如数据集网页所示,我们有 21 个特征可以工作。因此,为了彻底分析,你必须分析每一个。
本节应该能让你对可以执行的任务类型有一个很好的理解,但我们将会留下很多问题供你寻找答案。如果你需要一些灵感,可以查看 Kaggle 网站上的这个数据集。你将找到许多用户尝试分析这个数据集的笔记本。
最后,我们在此处不会完全转换实际数据,因为我们将在第六章“特征工程与标注”中回到这个问题,在那里我们将学习如何根据通过即将到来的过程获得的分析和知识来选择特征和创建新的特征。
初始探索和清洗墨尔本住房数据集
在本节中,我们将从在 Azure Machine Learning 中注册的数据存储中加载数据,并查看其内容。之后,我们将开始进行一些关于原始数据的基本清洗:
-
通过 Python PIP 单独下载以下包或使用本书 GitHub 仓库中可找到的需求文件:
pandas、seaborn、plotly、scikit-learn、numpy、missingno、umap-learn和statsmodels。 -
创建一个新的 Jupyter 笔记本或跟随之前提到的笔记本进行。
-
通过配置文件连接到您的 ML 工作区,正如我们之前所学的。
-
使用以下代码将数据集拉取到您的本地计算机:
from azureml.core import Datastore, Dataset import pandas as pd import seaborn as sns import numpy as np import plotly.express as px import matplotlib.pyplot as plt # retrieve an existing datastore in the workspace by name datastore_name = 'mldemoblob' datastore = Datastore.get(ws, datastore_name) # create a TabularDataset from the file path in datastore datastore_path = [(datastore, 'melb_data.csv')] tabdf = Dataset.Tabular.from_delimited_files (path=datastore_path)
在这里,我们正在从您定义的 ML 数据存储yourname中检索数据,并将数据集加载到一个表格数据集对象中。根据您数据存储中的文件夹结构,调整第二行最后面的文件路径和名称。
-
在表格数据集对象上可用的方法不如在 pandas DataFrame 上那么多。所以,让我们将其转换为 pandas DataFrame,并首次查看数据:
# increase display of all columns of rows for pandas datasets pd.set_option('display.max_columns', None) pd.set_option('display.max_rows', None) # create pandas dataframe raw_df = tabdf.to_pandas_dataframe() raw_df.head()
pd.set_option()方法让你可以访问 pandas 操作的通用设置。在这种情况下,我们希望所有列和行都在可视化中显示,而不是被截断。你可以将其设置为对你有用的任何值。
head()函数将让你先看看数据集的前五行。看看它们。
你将看到很多有意义的特征,比如Suburb、Address和Bathroom。但有些其他特征可能不太清楚,比如Type、Method或Distance。
通常,与任何数据集一样,对于随附的字段,都有某种形式的数据定义。查看数据集的网站以找到它们。
-
现在我们已经查看了定义,让我们看看数据集的所谓“形状”,这将显示数据集包含多少列(特征和标签)以及多少行(样本):
raw_df.shape
前面的命令显示了一个包含 13,580 个样本和 21 个特征/标签的数据集。
-
最后,运行以下代码,以便我们可以查看每个特征的唯一值数量、缺失值数量和数据类型:
stats = [] for cl in raw_df.columns: stats.append((cl, raw_df[cl].nunique(), raw_df[cl].isnull().sum(), raw_df[cl].isnull().sum() * 100 / raw_df.shape[0], raw_df[cl].value_counts( normalize=True, dropna=False).values[0] * 100, raw_df[cl].dtype)) # create new dataframe from stats stats_df = pd.DataFrame(stats, columns=[ 'Feature', 'Unique Values', 'Missing Values', 'Missing Values [%]', 'Values in the biggest category [%]', 'Datatype']) stats_df.sort_values('Missing Values [%]', ascending=False)
运行前面的代码后,你应该会看到以下类似的内容:

图 5.8 – 墨尔本住房数据集特征概述
看这张表格,我们可以得出以下观察结果:
-
有四个特征似乎有缺失值(BuildingArea、YearBuilt、CouncilArea和Car)。
-
许多数值(如
float64类型。这并不一定是问题,但既然每个值可能都适合int8、int16或int32,所以这是一种空间上的浪费。 -
有七个
object类型的特征,它们很可能都是字符串值。我们很快会详细查看它们。 -
有一个名为Price的特征,这可能是监督学习(如分类)的一个很好的标签/目标。
-
有一个名为Postcode的特征和一个名为Suburb的特征。我们可能不需要两者都保留。从唯一值的数量来看,Suburb似乎更细粒度。
-
有一个名为Address的特征和一个名为SellerG的特征。尽管物业的卖家可能对价格有一定的影响,但我们现在可以为了简单起见先删除它们。同样,地址也是极其精确的。几乎每个样本都有一个唯一的地址。
通过查看object类型的七个特征,我们可以看到以下情况:
-
类型:这有3个不同的值;我们的数据定义显示6。我们需要检查这个差异。
-
方法:这有5个不同的值;我们的数据定义显示11。我们也需要检查这一点。
-
SellerG:这有268个不同的卖家名称。
-
Address:这有 13378 个不同的值,但我们有 13580 个样本,所以似乎有多个地址相同的地方。尽管如此,我们在这里有极端的多样性,这使得这个特征相当不重要。
-
Regionname:这有 8 个不同的值——即墨尔本的区域。
-
Suburb:这有 314 个不同的值——即墨尔本的郊区。
-
CouncilArea:这有 33 个不同的值,并且是唯一具有缺失值的类别特征。
到目前为止,我们已经找到了一些有趣的信息和一些线索,表明我们下一步需要查看的地方。现在,让我们深入到特征的内容并进行一些初步的数据集清理。
-
让我们从删除一些不太重要的特征开始:
df = raw_df.drop(['Address', 'SellerG'],axis=1)
如您所见,我们保留了原始的 DataFrame,称为 raw_df,并创建了一个新的 DataFrame,称为 df。通过这样做,我们可以在任何时间添加已删除的特征。DataFrame 中的每一行都有一个索引,因此即使我们过滤掉行,我们仍然可以匹配原始值。
-
接下来,我们将重命名一些列以增加我们对它们的理解:
df = df.rename(columns={'Bedroom2': 'Bedrooms', 'Bathroom': 'Bathrooms', 'Regionname': 'Region', 'Car': 'Parking', 'Propertycount': 'SuburbPropCount'}) df.head() -
在这一点上,寻找重复项可能是个好主意。让我们运行以下代码片段来查找重复项:
s = df.duplicated(keep = False) s = s[s == True] s
将 keep 设置为 False 将显示每个具有重复项的行。在这里,我们可以看到有两行是相同的。我们可以通过以下命令查看它们:
df.loc[[7769,7770]]
如您所见,这些表示相同的条目。所以,让我们使用以下命令删除其中一个:
df.drop([7769], inplace=True)
由于这只是一个示例,我们可以通过其行索引来删除它。通常,这类操作会返回一个新的 DataFrame,但在许多操作中,我们可以使用一个名为 inplace 的属性来直接覆盖当前的 DataFrame。
-
现在,让我们看看似乎有缺失类别的分类特征,从
Method开始:df['Method'].unique()
我们数据集中的类别是 S、SP、PI、VB 和 SA。从数据定义中的列表来看,我们可以看到数据集中只指定了房产的销售地点以及我们知道的销售价格。有人已经为我们清理了这些信息。
通过查看 Type,我们可以看到单卧室、开发用地和其他住宅区域也被删除了,留下了房屋、单元和联排别墅:
df['Type'].unique()
为了使这些条目更清晰,让我们将单个字母替换为全名:
df = df.replace({'Type':
{'h':'house','u':'unit','t':'townhouse'}})
df = df.replace({'Method': {'S':'Property Sold',
'SP':'Property Sold Prior',
'PI':'Property Passed In',
'VB':'Vendor Bid',
'SA':'Sold After Auction'}})
df.head()
-
现在,让我们专注于包含大量条目的分类特征。以下代码显示了该列中唯一值的列表:
df['CouncilArea'].unique()
我们将得到以下结果集:
array(['Yarra', 'Moonee Valley', 'Port Phillip', 'Darebin', 'Hobsons Bay', 'Stonnington', 'Boroondara', 'Monash', 'Glen Eira', 'Whitehorse', 'Maribyrnong', 'Bayside', 'Moreland', 'Manningham', 'Banyule', 'Melbourne', 'Kingston', 'Brimbank', 'Hume', None, 'Knox', 'Maroondah', 'Casey', 'Melton', 'Greater Dandenong', 'Nillumbik', 'Whittlesea', 'Frankston', 'Macedon Ranges', 'Yarra Ranges', 'Wyndham', 'Cardinia', 'Unavailable', 'Moorabool'], dtype=object)
在这里,我们可以看到有一个名为 None 的类别,其中包含我们的缺失值,还有一个名为 Unavailable 的类别。否则,似乎每个其他条目都定义得很好,似乎没有具有相同意义的重复条目;它们只是由于打字错误或空格而有所不同。这类错误通常被称为 结构错误。
通过对 Suburb 特征运行相同的命令,我们得到一个更大的结果集。在这个阶段,要看到结构错误变得非常复杂,因此我们需要采取程序化的方法来检查这个类别。这里可以使用诸如模式匹配或模糊匹配之类的技术,但我们现在先不考虑这一点。您可以自由地查找有关模糊匹配和Levenshtein 距离的主题,这些可以在结果集中找到相似词组的组。
-
最后,我们剩下最后一个问题,即关于邮编和郊区之间的关系,以及我们是否可以去掉其中一个。那么,让我们看看有多少邮编针对多个郊区:
postcodes_df = df.groupby( 'Postcode', as_index=False).Suburb.nunique() postcodes_df.columns = ['Postcode', '#Assigned Suburbs'] postcodes_df.loc[postcodes_df['#Assigned Suburbs'] > 1]
在这里,我们创建了一个新的 DataFrame,显示了邮编和分配的郊区数量。通过搜索那些被映射到多个郊区的邮编,我们可以找到相应的列表。让我们来计数:
postcodes_df.loc[postcodes_df['#Assigned Suburbs'] > 1].count()
在这里,我们可以看到 198 个邮编中有 73 个指向多个郊区。尽管如此,每个郊区都有一个邮编,所以让我们保留郊区,并将邮编从 DataFrame 中删除:
df = df.drop(['Postcode'],axis=1)
df.head()
这已经看起来相当好了。作为最后一步,我们可以将数据类型从 float64 改为整数类型之一(int8、int16、int32 或 int64),但我们还不足以了解数据点的分布情况,并且我们无法对有缺失值的列进行此操作。我们稍后再来处理这个问题。
到目前为止,我们已经对数据集进行了一些基本的探索和基础剪枝。现在,让我们更多地了解统计特性。
对数据集进行统计分析
是时候查看我们数值特征的统计特性了。为了做到这一点,运行以下代码片段:
dist_df = df.describe().T.apply(lambda s: s.apply(lambda x: format(x, 'g')))
dist_df
在这里,describe() 方法将为您提供数据集数值特征的典型统计特性表。T 将表进行转置,而 apply() 和 lambda() 方法将帮助将数据点格式化为常规数值表示。您可以自由地移除 apply 方法并查看差异。
结果将显示一些信息,但我们还想添加一些额外的统计值,包括偏度、众数以及等于众数、最大值和最小值的特征中的值的数量。通过以下代码,我们可以实现这一点:
from pandas.api.types import is_numeric_dtype
max_count=[]
min_count=[]
mode_count=[]
mode=[]
skew=[]
for cl in df.columns:
if (is_numeric_dtype(df[cl])):
max_count.append(df[cl].value_counts(
dropna=False).loc[df[cl].max()])
min_count.append(df[cl].value_counts(
dropna=False).loc[df[cl].min()])
mode_count.append(df[cl].value_counts(
dropna=False).loc[df[cl].mode()[0]])
skew.append(df[cl].skew())
mode.append(int(df[cl].mode()[0]))
dist_df['mode'] = mode
dist_df['skew'] = skew
dist_df['#values(min)'] = min_count
dist_df['#values(max)'] = max_count
dist_df['#values(mode)'] = mode_count
dist_df
在这里,我们创建了一系列列表,并将我们基础 DataFrame 中每个列的计算值追加到每个列表中。我们还为每个我们计算过的属性列表添加了一个新列到我们的分布 DataFrame,dist_df。为了便于您理解代码,我们在这里使用了 Python 列对象。您可以通过使用另一个 pandas DataFrame 来缩短这段代码,这留作您的练习。
运行前面的代码后,你应该看到以下类似的输出:

图 5.9 – 墨尔本住房数据集的统计特性
让我们通过查看这个表格来了解每个特征的推断:
-
价格: 这个值向右偏斜。在这里,我们可能会看到一些高价,这并不奇怪。最高的房价是 900 万。
-
距离: 这个值向右偏斜,可能是由于一个样本距离墨尔本 CBD 有 48.1 公里。有趣的是,有6个样本的距离为0。有时,0 是一个虚拟值,所以我们应该检查这些样本。根据11这个众数被设置了739次的事实,距离可能不是城市中心的精确距离,而是郊区到城市中心的平均距离。我们也应该弄清楚这一点。
-
卧室: 由于某些地方的卧室很多,这个值向右偏斜。奇怪的是,有16个样本的卧室数为0,需要核实。
-
浴室: 这与卧室特征的分布相似,有34个样本没有浴室,这又很奇怪。
-
停车位: 这与卧室特征的分布相似。有1026个样本没有停车位,这听起来是合理的。
-
土地面积: 这个值极度向右偏斜(95.24)。最大值是433014。如果我们假设这里使用的是平方米,那么大约有 43 公顷的土地。这并不是不可能的,但显然这是一个异常值,可能会扭曲我们的模型。
-
建筑面积: 由于最大值为44515平方米,这个值极度向右偏斜。这听起来相当不可能,所以我们可能想要去掉这个值。此外,还有17个样本的面积为0平方米,需要检查。
-
建造年份: 由于有一个建筑是在1196年建造的,这个值向左偏斜。我们可能想要丢弃这个值。
-
经度/纬度: 这些值似乎分布得相当合理,但奇怪的是,17和21的值分别相同,具体是-37和144。这让我们有了一些想法,即坐标可能没有我们想象的那么精确。
-
郊区房产数量: 这个值略微向右偏斜。我们必须分析这个值有多有帮助。
现在,让我们考虑我们期望的关系,并查看这些特征之间的关系:
-
房间与浴室/卧室: 如果你看一下这些值的分布,就会变得清楚,我们并不完全清楚房间的含义。房间的最大值是10,而卧室的最大值是20。查看数据定义,我们可以看到卧室是从多个不同来源获取的,所以我们可能在那些数据点之间有差异。
-
建筑面积与房间/浴室/卧室: 我们预计会有某种正相关性,但仅凭现有数据我们无法判断。
如我们所见,仅从这张表格中我们就可以获得一些非常有价值的见解,并对下一步要查看的内容有一个很好的了解。我们现在将检查价格和BuildingArea特征,但在现实中,我们可能需要遵循所有这些途径。请随意自己尝试,并查看提供的笔记本以获取更多想法。
首先,让我们看看seaborn或plotly库。了解它们是如何工作的以及它们之间的区别。为了简单起见,我们现在将使用plotly。使用以下代码来绘制一个箱线图,并在旁边显示数据点的分布:
fig = px.box(df, x="Price",points="all")
fig.show()
你应该看到以下图表:

图 5.10 – 价格目标的箱线图
悬停在箱线图上,你可以看到价格向量的log值,然后再看一眼。
要做到这一点,让我们在我们的 DataFrame 中添加一个新的列,包含价格的log值,并再次运行可视化:
df["Price_log"] = np.log(df['Price'])
fig = px.box(df, x="Price_log",points="all")
fig.show()
这将得到以下图表:

图 5.11 – log (价格) 目标的箱线图
这样做似乎是个好主意,因为它的分布更好。请随意检查这个分布的偏斜。
现在,让我们看看BuildingArea特征。再次,让我们使用以下代码创建一个箱线图:
fig = px.box(df, y="BuildingArea",points="all")
fig.show()
这将得到以下图表:

图 5.12 – BuildingArea 特征的箱线图
我们看到一个非常扭曲的箱线图。悬停在它上面,我们可以看到上界在295平方米,而最大值在44515平方米。有一个主要异常值和一些小异常值。
让我们使用以下代码查看有多少样本高于295:
df.loc[raw_df['BuildingArea'] > 295]['BuildingArea'].count()
结果仍然显示有353个样本高于此阈值。从箱线图来看,这可能很快就会减少到 2,000 平方米。因此,让我们使用以下代码检查超过 2,000 平方米的结果集:
df.loc[raw_df['BuildingArea'] > 2000]
这将给出以下输出:

图 5.13 – 按 BuildingArea 大小排名前四的样本
如我们所见,最大的房产距离市中心 48.1 公里,因此在这个范围内的Landsize和BuildingArea是可行的。然而,如果我们想了解墨尔本的房子价格,这可能并不那么重要。它位于北维多利亚地区,而不是大都市地区。我们可以进一步探讨这些特定房屋与正常情况外的其他特征之间的联系,但我们将就此搁置。
让我们使用以下代码从我们的数据集中删除主要异常值:
df.drop([13245], inplace=True)
由于它只包含一个样本,我们可以通过行 ID 将其删除。
在这一点上,我们可以继续用其他特征进行此类分析,但我们将把它留给你作为一个练习,以便更深入地了解其他特征及其统计依赖性。现在,让我们继续看看之后我们会做什么。
在我们继续之前,让我们使用以下函数将我们的数据集保存到 Azure 机器学习:
Dataset.Tabular.register_pandas_dataframe(
dataframe = df,
target = datastore,
name ='Melbourne Housing Dataset',
description = 'Data Cleansing 1 - removed address,
postcode, duplicates and outliers')
在这个练习中,我们将继续这样做,以便以后可以拥有不同的版本。
寻找和处理缺失值
我们接下来的任务是处理数据集中的缺失值。我们可以使用一个非常好的扩展missingno来获取一些关于缺失值的有趣可视化。
但在那之前,让我们运行以下代码,看看如果我们删除所有具有缺失值的样本会发生什么:
df.dropna(how='any').shape
如我们所见,结果 DataFrame 将包含6196个样本,这将少于数据集的一半。所以,处理缺失值可能是一个好主意。
现在,运行以下代码:
import missingno as msno
msno.matrix(df);
这将产生以下输出:

图 5.14 – DataFrame 及其缺失值的结构可视化
如我们所见,CouncilArea特征在 DataFrame 的后部样本中只有缺失值,Parking在后部样本中只有非常小的一部分缺失,而BuildingArea和YearBuilt在整个 DataFrame 中都有缺失。
正如我们已经学到的,我们可以通过为缺失的类别数据发明一个新的类别或用缺失的连续数据的平均值来替换它们来进行替换。
让我们从Unavailable开始,看看具有此类别的样本,通过选择具有该特性的任何样本:
df.loc[df.CouncilArea.isin(['Unavailable'])]
如我们所见,只有一个条目属于这个类别。这似乎是一个有效的条目;它只是缺少议会区域的名字。所以,让我们使用以下代码用一个新的类别Missing替换这个条目和缺失值:
df['CouncilArea'].fillna(value = "Missing", inplace = True)
df['CouncilArea'].replace(to_replace="Unavailable", value="Missing", inplace=True)
在特征之后检查唯一值显示,None或Unavailable类别中不再有任何值:
df['CouncilArea'].unique()
这是替换特征的最简单方法。由于这些是墨尔本的议会区域,每所房子都应该分配到一个区域,所以更好的想法是找到另一个匹配郊区或地址到议会区域的数据库,并进行交叉引用。您可以自由搜索并执行此操作。
继续使用三个连续特征,我们可以使用以下代码将任何缺失值替换为该列的平均值,并在之后检查是否还有剩余的缺失值:
BA_mean = df['BuildingArea'].mean()
df['BuildingArea'].replace(to_replace=np.nan, value=BA_mean, inplace=True)
df['BuildingArea'].isnull().sum()
最终命令的结果显示我们填充的平均值是145.749。将此代码修改为对YearBuilt和Parking执行相同的操作。然而,您可能希望对这些使用中位数而不是平均值。
目前,这解决了缺失值的问题,从统计学的角度来看,这是一种合理的方法。然而,正如我们讨论的那样,这是做这件事的最简单方法之一。更好的方法是在特征之间找到关系,并使用它们来填充缺失值。我们不仅可以使用整个数据集的平均值,还可以集中寻找与缺失值样本具有相似特征的数据子集。例如,我们可以找到一侧停车位数量与房屋内房间数量或房屋大小另一侧之间的依赖关系。然后,我们可以定义一个函数,根据这些其他特征给出 Parking 的值。
因此,为了更好地处理缺失值,我们需要找出关系,我们将在下一节中查看这些关系。
但在那之前,让我们再次用这个描述注册这个数据集:数据清洗 2 - 替换缺失值。
计算相关性和特征重要性
到目前为止,我们已经研究了单个特征、它们的内容以及它们的分布。现在,让我们看看它们之间的关系。
使用以下代码生成我们特征和目标之间的相关矩阵:
# compute the correlation matrix
corr = df.corr()
# define and create seaborn plot
mask = np.triu(np.ones_like(corr, dtype=np.bool))
f, ax = plt.subplots(figsize=(11, 9))
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3,
center=0, square=True, linewidths=.5,
cbar_kws={"shrink": .5})
plt.show()
生成的矩阵将显示我们 13 个特征的相关性,但不是全部。如果你检查可见的,你会看到我们缺少所有 object 或 datetime 类型的数据。
因此,在我们分析矩阵之前,让我们通过从我们的 DataFrame 中开始挖掘剩余的 object 类型列来添加缺失的特征:
obj_df = df.select_dtypes(include=['object']).copy()
obj_df.head()
在这里,我们可以看到剩余的列是 category,我们现在将我们的列转换为:
for cl in obj_df.columns:
obj_df[cl] = obj_df[cl].astype('category')
obj_df.dtypes
因此,我们创建了一个名为 obj_df 的 DataFrame,其中包含五个 category 类型的特征。现在,让我们为每个类别分配一个数值。为此,我们将使用 cat.codes 方法,并在我们的 DataFrame 中创建五个新列,列名扩展为 _cat:
for cl in obj_df.columns:
obj_df[cl+"_cat"] = obj_df[cl].cat.codes
obj_df.head()
完美!我们已经创建了一个包含编码类别的 DataFrame。我们将将这些新特征与我们的原始 DataFrame,df,合并到一个新的 DataFrame 中,称为 cont_df:
column_replacement = {'Type':'Type_cat','Suburb':'Suburb_cat','Method':'Method_cat','CouncilArea':'CouncilArea_cat','Region':'Region_cat'}
cont_df = df.copy()
for key in column_replacement:
cont_df[key] = obj_df[column_replacement[key]]
cont_df.dtypes
上述代码的输出显示了新数据集中所有列的数据类型。我们仍然可以看到 datetime 类型和一些应该为 int 类型的原始列。在再次创建相关矩阵之前,让我们纠正这个问题。
首先,让我们创建一个名为 Date_Epoch 的新列,该列包含一个表示自纪元(docs.python.org/3/library/time.html)以来的秒数的整数,并删除原始的 Date 列:
cont_df['Date_Epoch'] = cont_df['Date'].apply(lambda x: x.timestamp())
cont_df.drop(['Date'], axis=1, inplace=True)
cont_df.dtypes
我们还可以将 Date 分解为 Month 列和 Year 列,因为它们可能产生影响。请随意添加它们。
现在,让我们将所有 float64 列转换为整数,除了那些浮点数是正确的情况:
for cl in cont_df.columns:
if (cont_df[cl].dtype == np.float64 and cl not in
['Lattitude', 'Longtitude',
'Price_log', 'Distance']):
cont_df[cl] = cont_df[cl].astype('int')
cont_df.dtypes
上述代码显示,我们的 DataFrame 现在由最优化大小和格式的数值数据类型组成(一些特征每个值只占用 8 位内存)。
现在,是时候再次运行相关性矩阵了。使用我们之前使用的相同代码 – 只需将 df 替换为我们的新 cont_df。结果应该如下所示:

图 5.15 – 所有特征及其目标的相关矩阵
强烈的红色表示正相关,而强烈的蓝色表示负相关。基于此,我们可以得出以下结论:
-
房间数量 与 价格、价格对数、距离、卧室数量、浴室数量、停车位 和 建筑面积 强烈相关。
-
类型 与 价格、价格对数、卧室数量、建造年份 和 房间数量 强烈相关。
-
价格 与 房间数量、类型、卧室数量、浴室数量、停车位 和 建筑面积 强烈相关。
-
郊区、方法、土地面积和郊区房产数量似乎在当前状态下对其他特征或目标没有太大的影响。
观察这些结果,它们并不令人惊讶。郊区有太多的类别,无法精确地用于任何事物,方法也不应该有太大的影响,土地面积可能也不是最大的因素,而郊区房产数量可能也有太多的变化。可能的转换包括要么删除郊区和郊区房产数量,要么将它们映射到一个变化较少的类别。
在我们继续之前,让我们将 cont_df 注册为具有描述:“数据清洗 3 - 所有特征转换为数值类型”的数据集版本。
作为最后的任务,让我们使用 06_dataprep_melbhousing.ipynb 文件来双重检查我们到目前为止所了解的内容。在那里,你会看到我们计算了 价格 和 价格对数 目标特征的重要性。两者的结果都显示在这里:

图 5.16 – 价格(左侧)和价格对数(右侧)的特征重要性
如我们所见,房产类型明显影响其价格。这种影响可能看起来并不那么巨大,但请注意,我们正在查看对数形式的房价。
我们到目前为止所了解的内容与这些结果相符。通过观察图表之间的差异,我们可以看到,将对数缩放添加到我们的目标变量中,增强了最有影响力的特征。类型特征似乎对我们的目标有很强的影响。
让我们通过以下代码来结束这个练习,看看这种关系:
fig = px.box(df, y="Price_log",x='Type', color = 'Type',
category_orders={"Type": ["house",
"townhouse", "unit"]})
fig.show()
这些结果如下:

图 5.17 – 类型与价格对数之间的相关性
这样,我们就完成了这个练习。我们能够清理我们的数据集,发现一些非常好的初步见解,并发现我们的目标变量与一个特征之间有非常强的相关性。
还有很多开放性问题,我们仍然处于完全理解这个数据集的初期。例如,除了价格目标之外,我们没有查看特征缩放或归一化,这是某些算法的另一个可能要求。
我们将在第六章,特征工程和标记中继续使用这个数据集。在此之前,请随意深入挖掘这个数据集的秘密,或者尝试将您新获得的知识应用于不同的数据集。
在 Azure 机器学习中跟踪探索中的图表
在我们的数据探索过程中,我们创建了大量的不同图表和可视化。让我们学习如何使用 Azure 机器学习跟踪它们,以便它们不仅仅存在于我们的 Jupyter 笔记本中。
在第三章,准备 Azure 机器学习工作区中,我们学习了如何使用 Azure 机器学习跟踪 ML 实验的指标和文件。您数据转换和 ML 脚本的其他重要输出包括可视化、数据分布的图表、关于模型的见解以及结果。因此,Azure 机器学习提供了一种类似的方式来跟踪图像、图表和matplotlib引用的指标。
让我们想象一下,我们使用以下代码创建了一个流行的Iris 花卉数据集(archive.ics.uci.edu/ml/datasets/iris)的pairplot:
import seaborn as sns
sns.set(style="ticks")
df = sns.load_dataset("iris")
sns.pairplot(df, hue="species")
通过几行代码,我们可以跟踪所有的matplotlib图表并将它们附加到我们的实验运行中。为此,我们只需将matplotlib引用传递给run.log_image()方法,并给它一个合适的名称。以下代码显示了在实验中这会是什么样子:
with exp.start_logging() as run:
fig = sns.pairplot(df, hue="species")
run.log_image("pairplot", plot=fig)
现在,这是最令人惊奇的部分。通过调用带有matplotlib引用的函数,Azure 机器学习将渲染图表,保存它,并将其附加到实验运行中。以下截图显示了 Azure 机器学习工作室,其中包含了我们刚刚创建并注册的pairplot图像:

图 5.18 – 在 Azure 机器学习工作室中跟踪并显示的 Pairplot
这似乎是一个微不足道的功能,但在现实世界的实验中非常有用。习惯于自动生成数据、模型和结果图表,并将它们附加到您的运行中。当您稍后回顾实验时,您将已经将所有可视化附加到您的运行、指标和配置中。
当您在训练回归模型时,考虑存储回归图;当训练分类模型时,存储混淆矩阵和 ROC 曲线。当您在训练基于树的集成和神经网络的激活时,存储特征重要性。您可以一次性实现这一点,并将大量有用的信息添加到您的数据和机器学习管道中。
重要提示
当您使用 AutoML 和 HyperDrive 优化参数、预处理、特征工程和模型选择时,您将获得大量生成的可视化,以帮助您理解数据、模型和结果。
现在我们已经知道了如何在 Azure 机器学习工作区中存储可视化,让我们学习如何创建表示高维数据的可视化。
理解降维技术
在前面的章节中,我们探讨了多种可视化数据的方法,但高维数据在二维中难以准确可视化。为了实现这一点,我们需要某种类型的投影或嵌入技术来将特征空间嵌入到二维中。您可以使用许多线性和非线性嵌入技术来生成数据的二维投影。以下是最常见的几种:
-
主成分分析 (PCA)
-
线性判别分析 (LDA)
-
t 分布随机邻域嵌入 (t-SNE)
-
均匀流形近似和投影 (UMAP)
下图显示了 13 维 UCI 葡萄酒识别数据集(archive.ics.uci.edu/ml/datasets/wine)的 LDA 和 t-SNE 嵌入。在 LDA 嵌入中,我们可以看到所有类别应该是线性可分的。这就是我们在开始模型选择或训练过程之前,仅用两行代码绘制嵌入所学到的东西:

图 5.19 – 监督 LDA(左)与无监督 t-SNE(右)
LDA 和 t-SNE 嵌入对于判断单个类别的可分性和因此分类任务的难度非常有帮助。在开始选择和训练特定算法之前,始终评估特定模型在您的数据上的表现总是好的。
通过可视化数据来快速获得洞察力和对数据的良好理解是一个很好的方法。这也有助于您识别数据中的聚类、不规则性和异常情况——所有这些都是在所有进一步的数据处理中都需要考虑的因素。但是,您如何可视化具有 10、100 或 1,000 个特征维度的数据集?您应该在哪里保存分析?
在本节中,我们将回答所有这些问题。首先,我们将探讨线性嵌入技术——PCA,一种无监督技术,以及LDA,一种监督技术。然后,我们将比较这两种技术与两种流行的无监督非线性嵌入技术,t-SNE和UMAP,后者是 t-SNE 的通用和更快版本。拥有这四种技术将有助于你理解数据集并创建有意义的可视化。我们将对越来越复杂的数据集运行所有这些技术,具体如下:
-
Iris 花卉数据集:这个数据集包含三个类别和四个特征维度。
-
UCI 葡萄酒识别数据集:这个数据集包含三个类别和十三个特征维度。
-
MNIST 手写数字数据集:这个数据集包含 10 个类别和 784 个特征维度(28 x 28 像素图像)。
为了简洁起见,本节中省略了生成嵌入的代码,但可以在本书 GitHub 仓库中的07_dimensionality_reduction.ipynb文件中找到。
使用 PCA 进行无监督降维
最流行的线性降维技术是 PCA。这是因为,由于它是一种无监督方法,它不需要任何训练标签。PCA 将数据集线性转换,使得结果投影是不相关的。这个投影的轴被称为主成分,并且以这种方式计算,使得每个成分都有下一个最高的方差。
主成分是数据中最高方差的方向。这意味着主成分或特征向量描述了数据集的最强方向,下一个维度显示了与前一个方向的正交差异。在 NLP 中,主成分对应于高级概念——在推荐引擎中,它们对应于用户或项目特征。
PCA 可以通过协方差矩阵或相关矩阵的特征值分解来计算,或者通过使用 SVD 在非方阵上计算。PCA 和特征值分解通常用作数据实验步骤以进行可视化,而 SVD 通常用于稀疏数据集的降维;例如,NLP 中的词袋模型。我们将在第七章,“使用 NLP 的高级特征提取”中看到 SVD 在实际中的应用。
嵌入技术可以通过仅保留前x个成分来作为一种降维形式,因为这些第一个——也是最大的——成分解释了数据集一定比例的方差。因此,我们必须移除方差较低的数据,以获得低维数据集。
在二维(或执行任何嵌入技术后)执行 PCA 后的数据可视化,就是可视化变换数据集的前两个成分——两个最大的主成分。结果数据沿着轴——主成分——缩放,并居中于零。以下图表显示了前两个数据集的 PCA 结果。如您所见,所有可视化都有最高方差投影在x轴上,第二高方差在y轴上,依此类推:

图 5.20 – 鸢尾花数据集(左侧)和 UCI 葡萄酒识别数据集(右侧)的 PCA
在这里,我们应该承认,我们能够仅用两个维度展示这三个数据集是一个很好的第一步,并且可以立即识别簇。
通过将数据投影到前两个主成分上,并在左侧查看鸢尾花数据集,我们可以看到所有簇看起来都是线性可分的(在二维空间中)。然而,当我们查看右侧的 UCI 葡萄酒识别数据集时,我们可以已经看出簇不再那么明显了。现在,13 个特征维度与前两个主成分一起投影,其中最高方差沿着x轴,第二高方差沿着y轴。在 PCA 中,簇的形状通常与x轴对齐,因为算法就是这样工作的。
现在,让我们对最复杂的数据集——MNIST 手写数字数据集——运行 PCA。这样做的结果可以在以下图表中看到:

图 5.21 – MNIST 手写数字数据集的 PCA 结果
当我们查看更复杂的 MNIST 手写数字数据集的嵌入时,除了可能位于顶部的0簇外,我们看不到很多簇。数据围绕零中心并缩放到-30到30的范围内。因此,我们已能看出 PCA 的缺点——它不考虑任何目标标签,这意味着它不针对可分类别进行优化。
在下一节中,我们将探讨一种考虑目标标签的技术。
使用 LDA 进行监督降维
在 LDA 中,我们线性变换输入数据——类似于 PCA——并优化变换,使得结果方向具有最高的簇间方差和最低的簇内方差。这意味着优化尝试使同一簇的样本靠近簇的平均值,同时尝试使簇的平均值尽可能远。
在 LDA 中,我们还收到一个作为结果变换的线性加权方向集。数据以 0 为中心,方向按其最高簇间方差排序。因此,从这个意义上说,LDA 类似于 PCA,因为它考虑了目标标签。LDA 和 PCA 都没有真正的调整旋钮,除了我们希望在投影中保留的组件数量,可能还有一个随机初始化种子。
下面的图表显示了我们对前两个数据集执行 LDA 的结果:

图 5.22 – 红花数据集(左)和 UCI 葡萄酒识别数据集(右)的 LDA 结果
在这里,我们可以看到数据被转换成二维,使得聚类均值在x轴上彼此之间距离最远。对于红花和 UCI 葡萄酒识别数据集,我们都可以看到同样的效果。在两个嵌入中,我们还可以观察到另一个有趣的事实,即数据也变得线性可分。我们几乎可以在两个可视化中画两条直线来将聚类分开。
对于这两个数据集,LDA 嵌入在数据按类别分离方面看起来相当不错。据此,我们可以有信心认为这两个数据集的线性分类器应该会取得很好的性能——例如,超过 95%的准确率。虽然这可能只是一个粗略的估计,但我们已经知道从线性分类器中可以期待什么,即使是最小化的分析和数据预处理。
不幸的是,大多数现实世界的嵌入看起来都更像下面所示图表中的那种,我们在这个最终数据集上使用了 LDA。这是因为大多数现实世界的数据集通常具有超过 10 个甚至 100 个特征维度:

图 5.23 – MNIST 手写数字数据集的 LDA 结果
在这里,我们还可以看到包含底部0数字的簇与左侧的四和六的两个簇之间有很好的分离。所有其他簇都重叠在一起,看起来并不线性可分。
因此,我们可以判断线性分类器不会表现良好,可能只有大约 30%的准确率——这仍然比我们随机做要好得多。然而,我们无法预测复杂非线性模型(甚至基于决策树集成分类器的非参数模型)的性能。
如我们所见,LDA 在考虑类别标签方面比 PCA 表现得更好。因此,在优化结果时考虑数据标注是值得考虑的。我们将在第六章,“特征工程和标注”中学习如何进行高效的标注。
LDA 是一种非常适合线性可分数据集的嵌入技术,这些数据集具有不到 100 个维度和分类目标变量。LDA 的一个扩展是二次判别分析(QDA),它使用两个变量的组合进行非线性投影。如果你处理的是连续目标变量,你可以使用一个非常类似的技术,称为方差分析(ANOVA),来建模簇之间的方差。ANOVA 变换的结果表明,数据集中的方差是否归因于不同组件方差的组合。
正如我们所见,无论是 PCA 还是 LDA 在分离高维数据,如图像数据时表现都不佳。在 Handwritten Digits 数据集中,我们处理的是来自 28 x 28 像素图像的仅有 784 个特征维度。想象一下,如果你的数据集由 1,024 x 1,024 像素的图像组成——你的数据集将会有超过一百万个维度。因此,我们需要一种更好的嵌入技术来处理非常高维的数据集。
使用 t-SNE 进行非线性降维
几年前,将高维数据集投影到二维或三维空间非常困难且繁琐。如果你想在二维图表上可视化图像数据,可以使用之前讨论过的任何技术——如果它们能计算出结果——或者尝试使用自组织映射等异国情调的嵌入。
尽管 t-SNE 在 2008 年由 Laurence van der Maaten 和 Geoffrey Hinton 发表在论文中(lvdmaaten.github.io/publications/papers/JMLR_2008.pdf),但直到 2012 年才有人将其应用于主要数据集。它在 Merck Viz Kaggle 竞赛中排名第一的团队中被使用——这是一种非常不寻常的方式,首次将一个伟大的嵌入算法应用于实践。然而,自从那次竞赛结束以来,t-SNE 已经在其他 Kaggle 竞赛和大型公司中定期用于嵌入高维数据集,并取得了巨大的成功。
t-SNE 将高维特征投影到二维或三维空间,同时最小化高维和低维空间中相似点的差异。因此,彼此靠近的高维特征向量在二维嵌入中也可能彼此靠近。
下图显示了 t-SNE 应用于爱丽丝花和 UCI 葡萄酒识别数据集。正如我们所见,复杂的非线性嵌入并没有比简单的 PCA 或 LDA 技术表现得更好。然而,它的真正力量在包含高达 3000 万个特征维度的非常大型和高维数据集中得到了体现:

图 5.24 – 爱丽丝花数据集(左)和 UCI 葡萄酒识别数据集(右)的 t-SNE 结果
在下面的图中,你可以看到 t-SNE 在 MNIST 数据集上的表现:

图 5.25 – MNIST 手写数字数据集的 t-SNE 结果
如我们所见,t-SNE 在 MNIST 数据集上表现非常好,轻松地分离了 10 个手写数字的簇。这表明可能达到 99% 的准确率。
这种可视化类型的美妙之处不仅在于我们可以看到数据是可分离的,而且我们可以通过查看前面的可视化来想象当分类器在数据上训练时,混淆矩阵将是什么样子。以下是我们仅从查看嵌入中可以得出的关于数据的观察:
将此项目符号列表替换为以下列表:
-
有三个包含数字 1 样本的簇,其中一个簇离平均值更远。
-
有三个包含数字 9 样本的簇,其中在少数情况下,这些样本非常接近数字 1 和数字 7 样本的簇。
-
中间有一个包含数字 3 样本的簇,这些样本靠近数字 8 样本的簇。
-
有一个包含数字 2 样本的微小簇,这些样本靠近数字 8 样本的簇。
-
包含数字 3 和 9 样本的簇彼此非常接近,所以它们可能看起来很相似。
-
包含数字 0、4 和 6 样本的簇与其他簇的距离非常远,这表明它们相当可分离。
这些是卓越的见解,因为当你手动探索样本时,你知道应该期待什么,以及应该寻找什么。这也帮助你调整特征工程,例如,尝试区分数字 1、7 和 9 的图像,因为它们将在建模后期导致最多的误分类。
使用 UMAP 推广 t-SNE
UMAP 是一种用于通用流形学习和降维的算法。它是基于黎曼几何和代数拓扑的 t-SNE 的一般化。
通常,UMAP 以拓扑方法提供与 t-SNE 相似的结果,具有更好的特征维度的可扩展性,以及运行时的更快计算。由于它更快,在拓扑结构方面表现略好,因此它迅速获得了人气。
如果我们再次查看 Iris 花朵和 UCI 酒类识别数据集的嵌入,我们会看到与 t-SNE 相似的效果。结果展示在下面的图中:

图 5.26 – Iris 花朵数据集(左)和 UCI 酒类识别数据集(右)的 UMAP 结果
结果嵌入看起来合理,但它们并不优于 LDA 的线性可分结果。然而,我们不能仅仅通过比较结果来衡量计算性能,这正是 UMAP 的亮点所在。
当涉及到更高维度的数据,如 MNIST 手写数字数据集时,UMAP 作为二维嵌入技术在表现上非常出色。以下图表显示了 MNIST 手写数字数据集上的 UMAP 结果:

图 5.26 – MNIST 手写数字数据集的 UMAP 结果
如我们所见,UMAP 将簇减少为在嵌入中完全可分实体,簇之间的重叠最小,簇本身的距离很大。做出与之前类似的观察,例如,关于数字1和9的簇,仍然是可能的,但簇看起来要明显可分得多。
从这些数据实验和可视化技术中,我们希望你能记住以下关键点:
-
执行 PCA 以尝试分析特征向量
-
执行 LDA 或 ANOVA 以了解你数据的方差
-
如果你有复杂的高维数据,请执行 t-SNE 或 UMAP 嵌入
带着这些知识,我们可以直接进入特征工程,因为我们知道哪些数据样本容易处理,哪些样本会在生产中导致高误分类率。
摘要
在本章的前两部分,你学习了有哪些技术可以用来探索和统计分析原始数据集,以及如何在真实数据集上实际应用它们。
之后,你学习了可以使用哪些降维技术来可视化高维数据集。在那里,你了解了对你理解数据、其主成分、判别方向和可分性非常有用的技术。
此外,你在这章中学到的所有内容都可以在你的 Azure 机器学习工作区中的计算集群上执行,通过它可以跟踪生成的所有图表和输出。
在下一章中,利用你迄今为止所获得的所有知识,你将深入探讨特征工程这一主题,在那里你将学习如何选择和转换数据集中的特征,以便为 ML 训练做准备。此外,你将更深入地了解标记以及 Azure 机器学习如何帮助完成这项繁琐的任务。
第六章:第六章:特征工程和标注
在上一章中,我们学习了如何清理我们的数据并进行基本统计分析。在本章中,我们将深入探讨在开始我们的机器学习训练之前必须执行的两种更多类型的操作。这两个步骤是所有步骤中最重要的,除了高效地清理数据集之外,而且要擅长它们,你需要有大量的经验。本章将为你提供一个基础来构建。
在第一部分,我们将学习特征工程。我们将了解这个过程,如何从我们的数据集中选择预测特征,以及将我们的数据集中的特征转换为可用于我们的机器学习算法的方法。
在第二部分,我们将探讨数据标注。大多数机器学习算法属于监督学习类别,这意味着它们需要标注的训练数据。我们将探讨一些需要标签的典型场景,并学习 Azure 机器学习如何帮助完成这项繁琐的任务。
在本章中,我们将涵盖以下主题:
-
理解和应用特征工程
-
处理数据标注
技术要求
在本章中,我们将使用以下 Python 库和版本来对不同的数据集进行特征工程。
-
azureml-sdk 1.34.0 -
azureml-widgets 1.34.0 -
azureml-dataprep 2.20.0 -
pandas 1.3.2 -
numpy 1.19.5 -
scikit-learn 0.24.2 -
seaborn 0.11.2 -
plotly 5.3.1 -
umap_learn 0.5.1 -
statsmodels 0.13.0 -
missingno 0.5.0
与前几章类似,你可以使用本地 Python 解释器或 Azure 机器学习中的笔记本环境执行此代码。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter06。
理解和应用特征工程
特征工程是一个通用术语,描述了将我们数据集中的现有特征进行转换、创建缺失特征,并最终从我们的数据集中选择最具有预测性的特征以使用给定的机器学习算法开始机器学习训练过程的过程。这些不能仅仅被视为我们必须应用于我们的数据的某些数学函数。这是一种艺术形式,做得好可以区分一个平庸和高度表现的预测模型。如果你想知道你应该在哪里投入时间,特征工程是你可以对最终机器学习模型的质量产生最大影响的步骤。为了产生这种影响并提高效率,我们必须考虑以下因素:
-
机器学习算法要求:特征是否需要特定的格式或范围?我如何最好地避免模型过拟合和欠拟合?
-
领域知识:给定的特征是否足够用于我们的模型?我们能否创建包含更多预测信息的附加特征或派生特征?
在本节中,我们将定义不同的特征工程技术类别,然后探讨一些应用于不同类型数据集的最显著方法。
重要提示
请记住,特定特征工程方法的有用性取决于所使用的特征类型(分类、连续、文本、图像、音频)以及所选的机器学习算法。
特征工程技术分类
广义而言,特征工程方法可以归纳为以下类别:
-
特征创建:从给定的特征集或额外的信息源中创建新的特征。
-
特征转换:转换单个特征,使其对所使用的机器学习算法有用且稳定。
-
特征提取:从原始数据中创建派生特征。
-
特征选择:选择最突出和最具预测性的特征。
让我们看看这些类别及其包含的内容。
特征创建
特征工程的第一步是找到模型中应包含的所有特征。要擅长这一点,你必须对相关领域有深入了解,或者知道该领域的领域专家(SME)。最后,我们想要确保我们考虑了任何具有预测性且在合理时间内可以获取的数据点。
反过来,我们必须理解所有可以帮助我们在数据集中创建新特征的方法,无论是来自额外来源还是初始数据集。通常,这些方法可以按以下方式分类:
-
添加缺失的预测特征:我们添加外部缺失信息,以实现更具有预测性的模型。
-
结合可用特征:我们通过结合数据集中已有的特征来创建新的特征。
为什么我们必须更改数据集中已经存在的特征?
原因在于,我们理解的许多特征与标签之间的联系可能对所使用的机器学习算法来说并不明显。因此,考虑哪些特征或可用特征的表示我们认为对于使机器学习算法更容易把握内在联系是很有帮助的。
让我们看看一些例子,以便更好地理解这一点。
想象一下,你有一个用于预测房价的数据集,就像我们在第五章“执行数据分析与可视化”中考察的那样。此外,想象一下我们拥有的特征是房屋或公寓的长度和宽度。在这种情况下,将这两个特征结合起来创建一个名为面积的新特征可能是有用的。此外,如果缺少建筑类型(房屋、公寓、联排别墅等),我们可能需要从其他来源添加这个信息,因为我们知道类型会影响房产的价格。
重要提示
如果你从现有特征中创建新特征,通常明智的做法是只保留新创建的特征,从数据集中删除那些初始特征。
现在,想象一下一个人在其一生中花费的金额。年轻时,这可能会非常少。随着年龄的增长,他们可能会有抵押贷款和子女,最终,当他们的子女搬出家时,他们的支出可能会下降,他们接近退休。由于这会在年龄和生活成本之间形成某种抛物线关系,因此,对于机器学习算法来说,可能不容易掌握这一点。因此,一个可能的选择是将生活成本特征的值平方,以强调更高的成本,并降低较低的成本的重要性。
在前两个例子中,我们使用了我们的领域知识来创建新的特征。但如果我们没有这种知识怎么办?
有一种方法可以通过所谓的多项式扩展在数学上创建新特征。这个想法是通过将一个特征的值提升到一定的幂,并乘以一个或多个其他特征来创建新特征。在这里,我们定义度为单个特征可以提升到的最大幂,我们定义顺序为我们允许相互乘积的特征的数量。以下图表显示了左侧阶数为 2,顺序为 2 的所有可能组合,以及右侧阶数为 3,顺序为 3 的所有可能组合:

图 6.1 – 多项式扩展的可能组合(左侧为阶数=2,顺序=2;右侧为阶数=3,顺序=3)
你应该只考虑最大阶数为 3,因为,如图所示,即使阶数为 2,这个操作也已经产生了太多的组合。然而,这个自动过程可能比原始的特征产生更好的预测特征。
要尝试这种方法,你可以使用sklearn库中的PolynomialFeatures类(scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html)。
在考虑了所有这些方法之后,我们可以在我们的数据集中创建新的特征,这些特征可能更容易被我们的机器学习算法处理,并且包含更精确、更具预测性的信息。
接下来,让我们看看一些让我们可以通过转换其值或其表示来改变单个特征的方法。
特征转换
特征转换是关于操纵特征以改变其值或创建相同特征的新表示。以下列表涵盖了我们可以对单个特征执行的转换类型:
-
离散化:将特征值划分为不同的组或区间以降低复杂性。这可以在数值或分类特征上完成。
-
拆分:将特征拆分为多个元素。这通常是在日期时间和字符串值上进行的。
-
分类编码:通过创建新的数值特征并遵循特定方法来数值化分类特征。
-
缩放:将连续特征转换为一个适合特定值范围的值。
-
标准化:将连续特征转换为一个具有均值为 0 和标准差为 1 的正态分布。
-
归一化:将多个连续特征的向量(行)分别转换为一个所谓的单位范数(单位大小)。
-
square、square root、exp、log等。
在第五章,“执行数据分析与可视化”中,我们使用了log函数来计算所有房价值的对数。我们这样做是为了减少少数异常值对我们机器学习训练的影响。因此,转换特征的主要原因是使特征适应给定机器学习算法的可能数学要求。通常,你可能会遇到以下机器学习算法的要求:
-
数值格式:算法要求所有特征都是数值的。
-
相同尺度:算法要求所有预测特征都在相同的尺度上,甚至可能具有均值为 0 和标准差为 1。
-
数学理论:域本身可能需要根据数学理论进行某些转换。例如,对于涉及经济理论的预测,价格特征几乎总是需要用自然对数进行转换。
-
[-1,1]. -
复杂性:大多数算法都需要非常精确的特征。因此,降低特征可能取值的复杂性通常是有价值的。
例如,离散化特征。其中一种方法称为分箱,它将数值连续值转换为少量离散值。我们将在第七章,“使用 NLP 的高级特征提取”中看到这一方法的应用。
另一个例子是将日期时间特征分割。想象一下,我们想要预测一天中特定时间某条道路上的交通量。假设我们得到了一个表示我们记录的日期和时间以及在那个点追踪的汽车数量的特征。为了做出更好的预测,一个想法是创建三个新的特征,表示是否是工作日、周末或假日。与工作日上午 7 点相比,星期天上午 7 点的交通量会更少。
让我们学习如何执行这种转换。以下截图显示了我们的初始小型数据库和添加星期几的第一个转换:

图 6.2 – 包含新工作日特征的数据库
在下一步中,我们必须通过添加一个名为daytype的新分类特征来丰富数据,该特征表示一天是工作日、周末还是假日:

图 6.3 – 数据库丰富化
理论上,我们已经完成了。但我们的机器学习算法可能在这里有不同的看法。我们的机器学习模型可能会为我们的分类数据创建一个不存在的自然顺序,或者它简单地无法处理分类数据。在这种情况下,明智的做法是将我们的分类数据用数值进行编码。一种这样的方法称为独热编码,它通过为每个现有类别创建一个具有两个有效值(0 或 1)的新特征,将分类特征转换为多个数值特征。以下截图显示了我们对示例的这种编码:

图 6.4 – 对新特征进行独热编码
在这里,我们创建了三个新的特征,分别命名为holiday、weekday和weekend,每个特征代表我们的初始类别。如果一个样本具有这个初始类别,那么该特征的值设置为1;否则,设置为0。
在这个例子中,我们做了什么?我们通过分割特征,添加外部知识通过特征创建,并在创建的特征上执行分类编码,将一个非常不直观的日期时间特征转换成具有更多预测力的特征。
现在我们已经很好地掌握了特征转换,让我们看看什么是特征提取的范畴。
特征提取
通过特征提取,我们将所有不通过简单手段操纵特征但能从高维数据集中提取有用信息的方法分组在一起。这通常是通过使用复杂的数学算法或机器学习算法来完成的。
当底层数据集过于复杂而难以处理时,通常需要提取,同时保持其预测价值,将其转化为简化的形式。
以下是一些不同场景下的典型提取类型:
-
高维降维:基于 n 维数据集创建代表性特征。
-
特征检测:在图像数据集中的每张图像中找到感兴趣点。
-
词嵌入:为文本数据集中的单词创建数值编码。
-
信号处理:从音频数据集中提取声音波的特征。
我们在第五章“执行数据分析和可视化”中讨论了高维降维方法,当时我们探讨了可视化高维数据集。在主成分分析(PCA)这样的过程中,数据集通过创建主成分向量被投影到二维或三维空间。我们不仅可以使用这种方法进行可视化,还可以使用这些计算向量作为派生和更简单的特征,这些特征代表我们的数据集。
重要提示
高维降维技术可用于特征提取,但请注意,我们失去了对特征的内禀理解。我们最终得到的不是称为郊区或房间的特征,而是称为主成分 1 和主成分 2 的特征。
观察其他场景,似乎提取通常发生在我们处理由文本、图像或音频数据组成的复杂数据集时。在这些所有情况下,当我们从原始数据中提取信息时,都有特定的方法需要考虑。
在图像数据集的情况下,我们可能对关键区域或感兴趣点感兴趣,包括寻找边缘和对象。在第十章“在 Azure 上训练深度神经网络”中,你会看到这样的图像提取步骤是由深度神经网络自动完成的,从而消除了在许多情况下对图像进行手动特征提取的需要。
在文本数据的情况下,我们可以使用诸如词袋模型和TF-IDF之类的提取方法,这两种方法都有助于创建文本的数值表示,捕捉意义和语义关系。我们将在第七章“使用 NLP 的高级特征提取”中深入探讨这些方法。
在音频数据的情况下,我们可以使用信号处理从源数据中提取信息和新的特征。在这种情况下,也存在两个领域——时域和频域——我们可以从中提取信息。从时域来看,我们通常会提取诸如幅度包络这样的内容,它是每帧信号的峰值幅度,均方根能量,它暗示了信号的响度,以及过零率,即波穿越水平时间轴的次数。如果你必须处理来自这个领域的数据,请让自己熟悉这样的处理技术。
重要提示
许多特征提取和特征转换技术已经嵌入到常见的机器学习框架和算法中,无需您手动触摸特征。通过理解算法本身做什么以及您在预处理时需要手动做什么,来获得良好的理解。
到目前为止,我们已经学习了如何创建新特征、转换特征以及从我们的数据集中提取特征。现在,让我们看看一些可以帮助我们从特征集中选择最具预测性的特征的方法。
特征选择
通过特征选择,我们定义了所有帮助我们理解特征对目标有价值性和预测性的方法,以便我们可以选择有用的特征变量子集进行训练。减少复杂性的原因有两个。一方面,我们希望简单性使模型可解释;另一方面,我们希望避免模型过拟合。当输入信息过多时,我们最终会得到一个模型,在大多数情况下,这个模型会完美地拟合我们的训练数据,但除了这些之外,它在未见过的数据上的表现会很差。
通常,有三种不同类型的特征选择方法,如下所示:
-
基于过滤的方法:这些方法定义了一个派生指标,即不是目标错误率,来衡量特征子集的质量。
-
基于包装的方法:这些方法使用贪婪搜索算法在不同的特征子集组合上运行预测模型。
-
嵌入式方法:这些是已经嵌入到我们最终机器学习模型中的特定选择方法。
基于过滤的方法在计算资源方面可以非常高效,但仅与一个更简单的过滤方法进行评估。通常,这些方法中使用统计指标,如相关性、互信息和熵作为度量标准。
另一方面,基于包装的方法计算密集。同时,它们可以找到性能极佳的特征集,因为用于特征选择的错误函数或指标与实际模型训练中使用的相同。这种方法的不利之处在于,如果没有独立的指标,选定的子集仅对所选的机器学习训练算法有用。通常,这是通过执行以下过程之一来完成的:
-
逐步前进特征选择:根据每个特征的训练结果逐个添加特征,直到模型不再提高其性能。
-
逐步后退特征选择:使用完整特征集评估模型。然后,这些特征被逐一移除,直到达到预定义的特征数量。这种移除是循环进行的。
-
穷举特征选择:评估所有特征子集,这是最昂贵的方法。
最后,当选择步骤是模型学习算法本身的一部分时,选择方法被称为嵌入式方法。嵌入式方法通常通过学习算法利用其选择过程,同时进行选择和训练,从而结合过滤器和包装方法的特性。嵌入式方法的典型例子是集成模型、Lasso和Ridge。
你可能现在已经意识到了,我们在第五章,执行数据分析与可视化中使用了这样的方法。我们用于生成相关矩阵的皮尔逊相关系数是一个派生指标,因此它属于基于过滤器的选择方法。此外,我们还使用了一个集成决策树模型来计算数据集的特征重要性。这有助于我们清楚地了解哪些特征可能比其他特征对目标有更大的影响。这种集成方法利用了随机森林方法。随机森林不仅实现了所谓的袋装技术,随机选择样本子集进行训练,而且还随机选择特征,而不是使用所有特征来生长每一棵树。因此,对于特征选择,随机森林属于嵌入式类别。
我们将在第九章,使用 Azure 机器学习构建 ML 模型中更详细地查看基于树的集成分类器,以及袋装和提升。
除了所有这些特征选择的数学方法之外,有时,更手动的方法可能更优越。例如,当我们从第五章,执行数据分析与可视化中的墨尔本住房数据集中删除邮政编码时,我们这样做是因为我们理解邮政编码和郊区包含相同的信息,这使得它们是冗余的。我们这样做是因为我们具有领域知识,并了解邮政编码和郊区之间的关系。请注意,这种额外的知识减轻了模型自己学习这些联系的压力。
重要提示
对于特征工程,对数据或领域了解的更多外部知识,可以使许多预处理步骤变得更加简单,或者完全避免。
我们将在本书中反复阐述这一概念,因为它需要融入你做的每一件事,以便你更高效、更擅长处理数据。
我们现在对可以执行的一般特征工程类型有了总体了解。在下一节中,我们将概述最显著的方法,并深入探讨其中的一些方法。
发现特征转换和提取方法
现在我们已经很好地掌握了我们可以应用于特征的特征工程动作类型,让我们来看看一些最突出的特征工程技术和它们的名称。以下表格提供了我们所学不同类别中大多数已知方法的良好概述:

图 6.5 – 不同特征工程方法的概述
请记住,这个列表远非详尽无遗,正如我们之前提到的,其中一些方法已经作为特定机器学习算法的一部分得到实现。
在接下来的章节中,我们将探讨其中的一些。您可以自由下载 GitHub 仓库中该章节的01_feateng_examples.ipynb文件,其中包含即将到来的示例的代码。如果您想了解更多关于我们将要介绍的一些特征提取方法,我们将在接下来的章节中回到它们。对于我们将不介绍的方法,请自由研究它们。
缩放、标准化和归一化
由于所有缩放和归一化方法彼此之间非常相似,我们在这里将详细讨论它们。
让我们从所谓的StandardScaler开始。这种缩放将我们的特征值转换,使得结果值分布的均值(µ)为 0,标准差(s)为 1。应用于每个值的公式看起来如下:

在这里,µ是给定分布的均值,s 是给定分布的标准差。有了这个,我们可以将每个值,
,转换成一个新的缩放值,
。
下图展示了该缩放器如何改变多个分布的形状:

图 6.6 – StandardScaler 分布(左:缩放前,右:缩放后)
只有当底层分布是正态分布时,才应使用此缩放器,因为这符合要求。
接下来,我们将探讨MinMaxScaler。这种缩放方法与标准化非常相似,只是我们不是在处理值分布的均值或标准差;相反,我们将值缩放到[0,1]或[-1,1](如果存在负值)的范围内。以这种方式缩放特征通常会提高机器学习算法的性能,因为它们通常更擅长处理小规模值。
从数学上讲,这种缩放定义为以下:

在这里,
定义了初始分布的最小值,而
定义了初始分布的最大值。
如果最小值和最大值定义良好,MinMaxScaler 是一个不错的选择 – 想想 RGB 图片中的颜色强度。此外,我们可以改变公式以影响结果的值范围。
重要提示
StandardScaler 和 MinMaxScaler 都对分布中的异常值非常敏感,这反过来又可能扭曲某些机器学习算法。
许多机器学习算法更关注大值,因此它们存在异常值的问题。为了解决这个问题,定义了一个名为RobustScaler的缩放器。这个缩放器使用四分位距(IQR)而不是标准差作为离散度的度量,并使用分布的中位数而不是平均值作为集中趋势的度量。四分位距表示分布中间的 50%,这意味着它是第 75 百分位数和第 25 百分位数之间的差值。
因此,数学缩放函数看起来是这样的:

在这里,
表示分布的中位数,
表示第一四分位数开始的位置,
表示第三四分位数开始的位置。
为什么这个缩放器对异常值更有效?
在前面的公式中,最大的异常值仍然会落在预定义的区间内,因为最大的异常值会是
。因此,异常值离数据点群越远,中心值缩向 0 的程度就越大。另一方面,使用 RobustScaler,中间 50%的所有数据点都会缩放到单位距离,而高于或低于这个值的数据点会被缩放到主要区间之外适当的值,同时保持分布中间值之间的相对距离不变。
简而言之,中位数和四分位距受异常值的影响不大,因此这个缩放器受异常值的影响也不大。
让我们看看这些缩放器在一个样本分布上的表现。为此,我们将取Price列的Price列和应用我们讨论的每种缩放方法得到的分布:

图 6.7 – 使用多种缩放方法缩放的分布
如我们所见,StandardScaler创建了一个均值为 0、标准差为 1 的分布,MinMaxScaler将值缩放到 0 到 1 之间,而RobustScaler将均值设置为 0。查看图 6.8和图 6.9中的箱线图,我们可以看到它们分布的差异。请注意y轴的刻度:

图 6.8 – StandardScaler 和 RobustScaler 的箱线图
将下面的箱线图与图 6.8进行比较,我们可以看到它们分布的差异:

图 6.9 – MinMaxScaler 的箱线图
现在我们已经对如何缩放一个特征有了些了解,让我们来谈谈归一化。
归一化是将特征值向量(行)缩放到单位模长的过程,通常是为了简化如余弦相似度这样的数学过程。
让我们先了解一个可以从中受益的归一化步骤。余弦相似度描述了两个不同向量之间的相似程度。在一个 n 维空间中,它们是否指向同一方向,是否相互垂直,或者是否面向相反方向?
例如,这样的计算可以帮助我们理解文本文档之间的相似性,通过取词频向量或类似信息并比较它们来实现。
因此,为了理解文档相似性,我们必须使用以下公式计算向量之间的余弦值:

如您所见,为了进行这个计算,我们必须计算每个向量的模——例如,
。这个模定义为以下内容:

这个单独的向量模长计算相当昂贵。现在,假设我们有一个包含数十万个文档的数据集。我们每次都必须为数据集中每个向量的组合(样本)计算这个值。如果所有这些向量模长都等于 1,不是会更容易吗?这将极大地简化余弦的计算。
因此,我们的想法是通过适当缩放所有样本,将数据集中的所有样本归一化到单位模长,如下所示:

在这个方程中,
表示我们的初始向量,
表示初始向量的模,
表示我们缩放到单位模长的缩放向量。
这种归一化称为L2 范数,是三种典型归一化方法之一。让我们看看在这个以及其他所有度量中如何计算向量的模:
-
L1 范数:这个计算将向量的模定义为向量各分量绝对值的和。
-
L2 范数:这个计算的是传统的向量模长(如上所述)。
-
最大范数:这个计算的是向量的元素绝对值的模。
L1 范数和最大范数不能用于余弦相似度,因为它们没有计算数学上定义的向量模。所以,让我们看看这两个是如何计算的。
L1 范数在数学上定义为以下内容:

L1 范数常用于在拟合机器学习算法时正则化数据集中的值。它保持系数较小,这使得模型训练过程更简单。
最大范数在数学上定义为以下内容:

最大范数也用于正则化,通常在神经网络中用于保持神经元之间连接的权重低,这也有助于执行更少的极端反向传播运行以稳定机器学习算法的学习。
到目前为止,你应该已经很好地掌握了缩放和归一化的有用性。接下来,我们将探讨一些可以将分类值转换为数值表示的方法。
分类编码
当我们将特征转换作为一个概念来考虑时,我们查看了一个应用了独热编码的例子。这种方法为初始分类特征中的每个可用类别创建具有两个可能值(0,1)的新特征。这可能很有帮助,但高基数分类特征会极大地膨胀特征空间。因此,在使用这种方法时,我们必须弄清楚每个类别是否具有预测性。
在我们之前的例子中,我们不是使用一周中每天(周一至周六)的类别,而是选择了只有三个类别,即工作日、周末和假日。在这种情况下,独热编码非常有帮助。
除了这种方法之外,还有其他方法可以编码分类特征。其中最基本的方法是标签编码。在标签编码中,我们将每个类别替换为一个数值标签(0,..,n),从而使其成为一个数值特征。通过这种方式,我们没有向这个特征添加任何额外的信息。
接下来的想法是将整个数据集的一些内在信息添加到我们必须编码的值中,并使其融入其中。这个想法的一些选项如下:
-
计数编码:将每个类别替换为整个数据集中该类别观察值的绝对数量。
-
频率编码:将每个类别替换为整个数据集中该类别观察值的相对数量(百分比)。
-
目标编码:将每个类别替换为从整个数据集中该类别的每个条目计算出的目标平均值。
为了理解这些方法,让我们假设我们有一个包含 25 个人的最爱零食项作为特征之一,以及他们购买公司生产的新零食产品的可能性的数据集。以下表格显示了原始值和我们所讨论的所有三种编码:

图 6.10 – 计数、频率和目标编码示例
使用这些方法,我们可以将额外的信息融入特征中,使机器学习算法更容易理解关系。
最后,让我们谈谈Rare,因此将它们归为一类。这有助于降低整体复杂性,特别是如果Rare类别仍然只是整体类别分布的一小部分时,更应该这样做。你可以将这比作在选举图中将小党派归入其他标签,而主要展示大党派。
到目前为止,你应该对不同的编码技术有了很好的理解。在下一节中,我们将讨论我们如何在真实数据集上尝试这些技术。
在表格数据集上测试特征工程技术
在第五章《执行数据分析与可视化》中,我们对墨尔本住房数据集进行了一些清理和统计分析。在上一节查看了一系列可能的特征工程方法之后,你可能已经意识到我们在处理数据集时使用了其中的一些方法。
作为练习,思考我们之前停在了哪里,并考虑到特征工程选项,我们现在可以做什么来创建新的有用特征,转换给定的特征,并最终在我们的数据集中选择最突出和最具预测性的特征。
为了获得灵感,请查看 GitHub 仓库中本章的02_fe_melbhousing.ipynb文件。
在本章的最后部分,我们将放下特征空间,专注于我们的机器学习训练的目标或标签——更准确地说,是那些缺少标签的情况。
处理数据标注
在本节中,我们将探讨在为机器学习训练预处理数据集时最耗时且最重要的任务之一:数据标注。正如我们在第一章《理解端到端机器学习流程》中学习到的那样,对于大多数场景,将标签附加到我们的样本上至关重要。正如我们在第五章《执行数据分析与可视化》中查看高维降维和其他机器学习技术时讨论的,在大多数情况下,我们希望使用监督模型,这意味着我们需要标签。
在接下来的几节中,我们将讨论哪些场景需要我们进行手动标注,以及 Azure 机器学习如何帮助我们尽可能高效地完成这项单调的任务。
分析需要标签的场景
我们将首先查看我们迄今为止讨论过的数据集类型,以及我们需要在哪些场景下进行手动标注。
数值数据和分类数据
正如我们在处理墨尔本住房数据集时所见,对于表格数据集,我们可能经常有一个可以用作标签的列。在我们的案例中,我们可以用作标签的是价格列,因为我们的机器学习目标是根据特定的特征输入预测房价。
即使这个列缺失了,我们也可以纳入其他数据集,例如显示墨尔本不同郊区的房屋平均价格的那些数据集,来为我们的数据集样本中的每一个计算一个合理的价值。
因此,与其他我们将讨论的任何场景相比,主要优势在于,在由具有明确意义(不是图像的像素值)的数值和分类特征组成的数据集中,我们可以使用逻辑和数学函数来创建数值标签,或者我们可以自动将样本分类到分类标签。这意味着我们不必手动查看每个样本来定义其标签。
自然语言处理
让我们先看看文本数据。你可能认为分类条目在某种程度上也是文本,但通常,分类数据也可以用数学值交换,而不会损失太多。
另一方面,文本数据表示单词块,例如这本书中的那些,因此它们要复杂得多。看看以下两个句子或话语:
我想预订 2020 年 12 月 23 日从迪拜到巴黎的机票。
房间没有打扫,暖气也不工作。
我们将如何标记这些话语?这非常取决于我们的训练目标。也许我们只想将这些话语分组,例如订单、问候或陈述。在这种情况下,每个话语都会收到一个标签。另一方面,我们可能想要深入挖掘句子中单词的意义。对于我们的第一个话语,我们可能想要理解订单的意义,通过展示可能的航班选项来提供答案。对于第二个话语,我们可能想要理解情感,因为它是对酒店房间质量的陈述。
因此,我们需要在话语本身开始标记单个单词或短语,同时寻找其语义意义。
我们将在第七章中回到这个话题,使用 NLP 的高级特征提取。
计算机视觉
当我们谈论图像的机器学习建模时,我们通常试图理解和学习以下之一:
-
图像分类:将图像分类到一类或多类。典型用例包括图像搜索、图书馆管理和对人的情感分析。
-
目标检测:在图像中定位特定对象。典型用例包括行人检测、交通流量分析和对象计数。
-
图像分割:将图像的每个像素分配到特定的区域。典型用例包括自动驾驶汽车的精确环境分析和 X 射线或 MRI 图像中的像素级异常检测。
以下图示展示了这三种类型的示例:

图 6.11 – 不同的图像处理方法
对于这些方法,随着我们向下查看列表,标注的过程变得更加复杂。对于分类,我们只需在图像上放置一个或多个标签。对于目标检测,我们在图像上开始绘制所谓的边界框或多边形。最后,图像分割变得非常复杂,因为我们必须为图像的每个像素分配标签。为此,需要高度专业的工具。
如我们很快将看到的,我们可以使用 Azure Machine Learning Studio 中的数据标注工具来进行分类、目标检测,并在一定程度上进行图像标注任务的分割。
音频标注
最后,让我们来谈谈音频数据的标注。当涉及到音频数据的机器学习建模时,以下场景是可能的:
-
语音转文本:运行实时转录、语音助手、发音评估和类似解决方案。
-
语音翻译:将语音翻译为触发应用程序或设备中的操作。
-
说话人识别:通过声音特征验证和识别说话人。
因此,标注音频数据意味着我们必须从音频文件中提取片段,并相应地标注这些片段。以下图示展示了这个过程的简单示例:

图 6.12 – 音频标注过程
如您所想,这个标注任务也不是非常直接,需要专门的工具。
我们已经看到了很多标注至关重要的场景。现在,让我们尝试自己标注一些图像。
使用 Azure Machine Learning 标注服务进行图像分类的数据标注
在本节中,我们将使用 Azure Machine Learning Studio 中的数据标注服务来标注一些资产。正如我们在第三章,“准备 Azure Machine Learning 工作区”中学习的,导航到 Azure Machine Learning Studio 并在菜单底部点击数据标注,如图下截图所示:

图 6.13 – Azure Machine Learning Studio
在下一个屏幕上,点击添加项目,这将带您到以下视图:

图 6.14 – 标注项目创建向导
在我们开始练习之前,让我们看看我们可以使用这个服务执行哪些类型的标注任务。如图中所示,我们可以使用图像和文本数据作为数据源。在屏幕上的图像和文本选项之间切换,我们有以下选择:
-
图像分类多类别:给每张图像附加一个标签。
-
图像分类多标签:给每张图像附加多个标签。
-
目标检测(边界框):在图像上的一个对象周围绘制一个或多个框。
-
实例分割(多边形):在图像上的一个对象周围绘制复杂的多边形。
-
文本分类多类别:给一段文本附加一个标签。
-
文本分类多标签:给一段文本附加一个或多个标签。
如我们所见,在图像数据方面有很多有用的选项。我们可以通过使用边界框或多边形来突出显示和标记图像中的非常具体的部分。使用多边形,您在技术上能够进行完整的图像分割,但使用这个工具将每个像素分配到类别中相当困难。
然而,对于文本数据,有一些限制。我们没有选择在一段文本中标注特定单词或短语,正如我们在上一节中讨论的那样。在撰写本文时,唯一的选择是对文本块进行单标签或多标签。
因此,我们将使用图像。为了不让第一次使用这个工具变得过于复杂,我们将从给图像数据集中的图像附加单个标签开始。在接下来的步骤中,我们将创建一个图像数据集和一个相应的标注项目:
-
在通过向导之前,让我们寻找一个合适的图像数据集来使用。我们将使用STL-10 数据集(
cs.stanford.edu/~acoates/stl10/)。这个数据集包含大量的小型 96x96 图像,可以分成 10 个类别(飞机、鸟、汽车、猫、鹿、狗、马、猴子、船和卡车)。这 10 个类别将成为我们的标签。由于原始页面只提供给我们二进制格式的图像,我们需要找到不同的来源。在Kaggle上,您经常可以找到这些类型的数据集以不同的格式准备。 -
访问
www.kaggle.com/jessicali9530/stl10并下载test_images,这是一个包含 8,000 个png格式文件的集合。通常,我们会使用unlabeled_images集合,但由于有 10 万个,我们暂时将其保留。 -
如果您还没有这样做,请将本章的文件下载到您的设备上,并在
chapter06文件夹下创建一个名为images的新文件夹。 -
将所有 8,000 张图片提取到
images文件夹中。之后,打开03_reg_unlabeled_data.ipynb文件。在这个文件中,你会发现我们迄今为止用来连接到我们的工作空间和数据存储的代码。请将datastore_name替换为你 ML 工作空间中给出的名称。第一个单元格的最后一段代码如下:file_ds = Dataset.File.upload_directory( src_dir='./images', target=DataPath (datastore, 'mldata/STL10_unlabelled'), show_progress=True)
upload_directory方法将一次性上传images文件夹中的所有文件到你在目标中定义的数据存储位置,并创建一个名为file_ds的文件数据集对象。一旦上传完成,我们可以使用以下代码注册我们的新数据集:
file_ds = file_ds.register(workspace=ws,
name='STL10_unlabeled',
description='8000 unlabeled
STL-10 images')
如果你导航到 Azure Machine Learning Studio 中的数据集选项卡,你会看到我们新注册的数据集。在探索选项卡下,你会看到图像的子集,包括图像元数据和图像预览。
-
现在我们已经注册了我们的数据集,我们可以设置我们的标注项目。回到向导,如图 6.14 所示,将项目名称输入为
STL10_Labeling,并选择多类图像分类作为类型。点击下一步。 -
在下一屏,Microsoft 将提供从Azure Marketplace雇佣劳动力来完成你的标注工作的选项。这将是一个有用的工具,因为你很快就会了解到这项任务有多么繁琐。现在,我们不需要额外的帮助。点击下一步。
-
现在,我们可以选择要工作的数据集。选择我们新创建的数据集,命名为
STL10_unlabeled,然后点击下一步。 -
我们将看到一个名为增量刷新的选项。此功能如果底层数据集中添加了新图像,则每天更新一次项目。我们目前不打算这样做,所以保持原样并点击下一步。
-
下一屏要求我们定义我们的标签。标签为
飞机、鸟、汽车、猫、鹿、狗、马、猴子、船和卡车。然后,点击下一步。 -
倒数第二屏允许我们输入标注说明。如果我们不是单独在这个项目上工作,或者我们已经订购了劳动力来完成这项工作,这些说明将很有用。在这里,我们可以给他们下指令。对我们来说,因为我们单独工作,这就不必要了。所以,点击下一步。
-
最后,我们有选择使用ML 辅助标注的选项。如果我们不激活此选项,我们就必须自己标注所有 8,000 张图片,而不需要帮助。请注意,激活此选项需要运行 GPU 计算集群,每次辅助 ML 模型重新训练时都会运行几分钟。我们将选择使用默认选项,这将为我们创建一个合适的集群。点击创建项目。这将带我们回到概览页。当集群创建完成后,点击项目的名称以获取概览页面。
你将看到一个类似于以下仪表板的界面:

图 6.15 – 标注项目的仪表板
仪表板分为以下视图:
-
进度:这显示了正在标注的资产数量。在我们的案例中,我们正在处理 8,000 张图像。它还显示了每个资产的状态(完成、跳过、需要审查和不完整)。
-
标签类别分布:此视图将显示一个条形图,显示哪些标签被使用以及分类图像的次数。
-
标注员性能:此视图显示每个标注员处理了多少资产。在我们的案例中,只会显示我们的名字。
-
任务队列:此视图显示管道中的任务。目前,我们需要在下一个训练阶段或下一次检查之前手动标注 150 张图像。
-
机器学习辅助标注实验:此视图显示了辅助机器学习模型的运行或已运行的训练实验。
如果你切换到数据标签页,你会看到一些图像预览,你可以查看已经标注的图像。当你在一个团队中工作时,这很有帮助,因为一些人正在标注图像,而另一些人正在审查他们的标注工作。
最后,如果你查看DefLabelNC6。
以下截图显示了此集群的概览页面:

图 6.16 – 标注集群仪表板
如你所见,用于节点的机器具有 6 个核心,56 GB 的 RAM 和一个 Tesla K80 GPU。在 Azure 上创建任何类型的计算实例时,请始终检查定价页面(azure.microsoft.com/en-us/pricing/details/virtual-machines/ml-server-ubuntu/)。如该页面所示,我们使用的节点称为NC6,每小时大约花费 3 美元。集群节点显示集群是空闲的,因此没有费用。稍后,你可以检查运行标签页以了解训练运行的持续时间,从而了解定价影响。目前,一个合理的估计是,我们将在我们的标注项目中需要 2 到 4 小时的机器学习辅助支持。
因此,在我们开始标注图像之前,让我们了解机器学习辅助标注能做什么。当你切换回我们的标注项目仪表板时,你会在任务队列下看到三个选项,如下所示:
-
手动:这表示在任何时候都必须处理的资产,没有任何支持。
-
集群:这表示在已经标注的资产上使用了聚类模型。当你处理这些资产时,它们将以模型认为属于同一类的图像组的形式显示给你。
-
预标注:这表示在已经标注的资产上训练了分类模型的资产。在这种情况下,它为未标注的资产预测了标签。当你处理这些图像时,你会看到建议的标签并需要检查模型是否正确。
现在,让我们开始标记。当您点击标记数据时,您将看到以下视图:

图 6.17 – 标记任务视图
从这个视图,您可以看到中间的资产。通过顶部的控件,您可以放大并更改图像的亮度和对比度属性。如果您对这些选项不确定,您可以暂时选择跳过。在右侧,您可以选择适当的标签。如果您对您的选择满意,您可以点击提交。
对几幅图像进行标记,以便掌握情况。之后,查看右上角的控件。在这里,我们可以更改同时显示给我们多少资产(1、4、6 或 9)。我建议同时显示 6 个资产。此外,为了标记图片,您可以多选它们,并使用键盘上的数字 1 到 9(如前一张截图所示)来更快地进行标记。
现在,为了看到机器学习辅助标记的触发,您需要手动标记大约 400 到 600 张图像。您可以决定这是否是您时间的良好利用,但这是一个很好的练习,因为它让您了解了这项任务的繁琐程度。
最终,训练将被触发,如下面的截图所示:

图 6.18 – 触发的标记训练运行
在第一次标记训练触发之前,我不得不手动标记 616 个资产。正如我们所见,该工具显示了在标记过程中遇到的标签类别的分布。与其他任何训练一样,这创建了一个带有运行的实验。您可以在 ML 工作区的“实验”下找到这些,如下面的截图所示:

图 6.19 – 使用机器学习辅助标记的实验运行
在这一点上,只需继续标记资产。最终,您将看到由页面顶部的聚类任务定义的聚类图像(参见图 6.20):

图 6.20 – 显示聚类图像的数据标记
或者,您将看到预先标记的图像,这些图像由页面顶部的预标记任务定义(参见图 6.21):

图 6.21 – 显示预标记图像的数据标记
通过以上内容,您已经了解了如何利用机器学习建模来标记您的资产,以及 Azure 机器学习工作室如何使这一过程更加简便。正如您现在应该理解的那样,这是一项耗时的工作,但如果您希望在未来的机器学习训练中取得更好的结果,这项工作必须完成。
摘要
在本章中,我们探讨了如何通过特征工程来准备我们的特征,以及如何通过标记来准备我们的标签。
在第一部分,我们了解到特征工程包括创建新的和缺失的特征、转换现有特征、从高维数据集中提取特征,以及使用方法来选择对机器学习训练最有预测性的特征。
在第二部分,我们了解到标记是必不可少的且繁琐的。因此,像 Azure Machine Learning 数据标记这样的工具可以是一种祝福,可以减轻这项耗时的工作。
本章的关键要点是,创建、转换和选择预测性特征对机器学习模型的质量影响最大。在机器学习管道中的其他任何步骤都不会对其结果产生更大的影响。
要完成高质量的特征工程,你必须对领域有深入了解(或者你必须认识一个有这种知识的人),并且清楚地掌握所选机器学习算法的内部工作方式。这包括理解数学理论、算法期望作为输入的数据结构,以及当你拟合模型时自动应用的特征工程方法。
在下一章中,我们将看到特征工程的实际应用。我们将探讨如何对文本数据进行特征提取,以用于自然语言处理。
第七章:第七章:使用 NLP 的高级特征提取
在前面的章节中,我们学习了在 Azure 机器学习服务中许多标准的转换和预处理方法,以及使用 Azure 机器学习数据标注服务进行典型标注技术的应用。在本章中,我们希望更进一步,从文本和分类数据中提取语义特征——这是用户在训练机器学习模型时经常遇到的问题。本章将描述使用自然语言处理(NLP)进行特征提取的基础。这将帮助您在实际的机器学习管道中实现使用 NLP 的语义嵌入。
首先,我们将探讨文本、分类、名义和有序数据之间的差异。这种分类将帮助您根据特征类型决定最佳的特征提取和转换技术。稍后,我们将查看分类值最常见的转换方法,即标签编码和独热编码。这两种技术将被比较和测试,以了解这两种技术的不同用例和应用。
接下来,我们将处理文本数据的数值嵌入。为了实现这一点,我们将构建一个简单的词袋模型,使用计数向量器。为了净化输入,我们将构建一个包含分词器、停用词去除、词干提取和词形还原的 NLP 管道。我们将逐步学习这些不同的技术如何影响样本数据集。
此后,我们将用一种更好的词频加权方法——词频-逆文档频率(TF-IDF)算法来替换词计数方法。这将帮助您在给定整个文档集合的情况下,通过加权一个文档中术语的出现频率相对于文档集合中的频率来计算单词的重要性。此外,我们将探讨奇异值分解(SVD)以减少术语字典的大小。作为下一步,我们将通过利用词义来提高术语嵌入的质量,并深入了解语义嵌入,如全局向量(GloVe)和Word2Vec。
在最后一节,我们将探讨基于序列到序列深度神经网络且超过一亿参数的当前最先进的语言模型。我们将使用长短期记忆(LSTM)训练一个小的端到端模型,使用双向编码器表示从 Transformer(BERT)进行词嵌入和情感分析,并将这两种自定义解决方案与 Azure 认知服务中的文本分析能力进行比较。
本章将涵盖以下主题:
-
理解分类数据
-
构建简单的词袋模型
-
利用术语重要性和语义
-
实现端到端语言模型
技术要求
在本章中,我们将使用以下 Python 库和版本来创建分类编码、创建语义嵌入、训练端到端模型以及执行经典的 NLP 预处理步骤:
-
azureml-sdk 1.34.0 -
azureml-widgets 1.34.0 -
tensorflow 2.6.0 -
numpy 1.19.5 -
pandas 1.3.2 -
scikit-learn 0.24.2 -
nltk 3.6.2 -
gensim 3.8.3
与前几章类似,您可以使用本地 Python 解释器或 Azure Machine Learning 中托管的笔记本环境来执行此代码。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter07。
理解分类数据
分类数据以多种形式、形状和意义存在。了解你正在处理的数据类型至关重要——它是一个字符串、文本还是伪装成分类值的数值?这些信息对于数据预处理、特征提取和模型选择至关重要。
在本节中,首先,我们将查看不同类型的分类数据——即顺序、名义和文本。根据类型,你可以使用不同的方法从中提取信息或其他有价值的数据。请记住,分类数据无处不在,无论是 ID 列、名义类别、顺序类别还是自由文本字段。值得一提的是,你对数据的了解越多,预处理就越容易。
接下来,我们将通过将其转换为数值来实际预处理顺序和名义分类数据。当你想要使用不能解释分类数据的机器学习算法时,这是一个必要的步骤,这对于大多数算法都是真实的,例如基于决策树的算法。大多数其他算法只能对数值值进行操作(例如,计算损失函数),因此需要进行转换。
比较文本、分类和顺序数据
许多机器学习算法,如支持向量机、神经网络、线性回归等,只能应用于数值数据。然而,在现实世界的数据集中,我们经常发现非数值列,例如包含文本数据的列。本章的目标是将文本数据转换为数值数据,作为高级特征提取步骤,这样我们就可以将处理后的数据插入到任何机器学习算法中。
当处理现实世界数据时,你将面临许多不同类型的文本和/或分类数据。为了优化机器学习算法,你需要了解这些差异,以便对不同的类型应用不同的预处理技术。但首先,让我们定义三种不同的文本数据类型:
-
文本数据:自由文本
-
分类名义数据:不可排序的类别
-
有序类别数据:可排序的类别
文本数据和类别数据之间的区别在于,在文本数据中,我们想要捕捉语义相似性(即词语的意义相似性),而在类别数据中,我们想要区分少数几个变量。
有序类别数据和有序类别数据之间的区别在于,名义数据不能排序(所有类别具有相同的权重),而有序类别可以在有序尺度上逻辑排序。
图 7.1 展示了一个新闻文章评论的示例数据集,其中第一列,命名为 statement,是一个文本字段,名为 topic 的列是一个名义类别,而 rating 是一个有序类别:

图 7.1 – 比较不同的文本数据类型
理解这些数据表示之间的差异对于之后找到适当的嵌入技术至关重要。用有序数值尺度替换有序类别似乎很自然,将名义类别嵌入到正交空间中。相反,将文本数据嵌入到保留语义的数值空间中并不明显——这将在本章后面的部分中介绍,这部分内容涉及 NLP。
请注意,除了类别值之外,你还会看到表示类别信息的连续数值变量,例如来自维度或查找表的 ID。尽管这些是数值,但如果可能的话,你应该考虑将它们作为类别名义值处理。以下是一个示例数据集:

图 7.2 – 比较数值类别值
在这个例子中,我们可以看到 sensorId 值是一个数值,应该将其解释为类别名义值,而不是默认的数值,因为它没有数值意义。当你从 sensorId 1 减去 sensorId 2 时,你得到什么?sensorId 10 是 sensorId 1 的 10 倍大吗?这些问题是发现和编码这些类别值的典型问题。我们将在 第九章,使用 Azure 机器学习构建 ML 模型 中发现,通过指定这些值是类别数据,梯度提升树模型可以优化这些特征,而不是将它们作为连续变量处理。
将类别转换为数值
让我们先从将分类变量(序数和名义)转换为数值开始。在本节中,我们将探讨两种常见的分类编码技术:标签编码和独热编码(也称为虚拟编码)。虽然标签编码用一个数值特征列替换分类特征列,独热编码则使用多个列(列的数量等于唯一值的数量)来编码一个单一特征。
这两种技术以相同的方式进行应用。在训练迭代过程中,这些技术会找到特征列中的所有唯一值,并给它们分配一个特定的数值(对于独热编码,是一个多维数值)。结果,一个定义这种替换的查找字典存储在编码器中。当应用编码器时,应用列中的值会使用查找字典进行转换(替换)。如果事先知道可能的值列表,大多数实现允许编码器直接从已知值的列表初始化查找字典,而不是在训练集中找到唯一值。这有利于指定字典中值的顺序,从而对编码值进行排序。
重要提示
请注意,通常可能存在某些分类特征值在测试集中没有出现在训练集中,因此没有存储在查找字典中。因此,你应该在你的编码器中添加一个默认类别,该类别也可以将未见过的值转换为数值。
现在,我们将使用两个不同的分类数据列,一个是序数类别,另一个是名义类别,来展示不同的编码。图 7.3 显示了一个名义特征topic,它可能代表一个新闻机构的文章列表:

图 7.3 – 名义分类数据
图 7.4 包含了rating的序数类别;它可能代表一个网站购买文章的反馈表单:

图 7.4 – 序列分类数据
为了保留类别的含义,我们需要为不同的分类数据类型采用不同的预处理技术。首先,我们来看一下标签编码器。标签编码器为特征列中的每个唯一分类值分配一个递增的值。因此,它将类别转换为介于0和N-1之间的数值,其中N代表唯一值的数量。
让我们在第一个表中的topic列中测试标签编码器。我们在数据上训练编码器,并用数值主题 ID 替换topic列。以下是一个训练标签编码器并转换数据集的示例片段:
from sklearn import preprocessing
data = load_articles()
enc = preprocessing.LabelEncoder()
enc.fit(data)
enc.transform(data)
图 7.5 显示了先前转换的结果。每个主题都被编码为一个数值增量,topicId:

图 7.5 – 标签编码的主题
topicId生成的查找表如图7.6所示。这个查找字典是在fit()方法期间由编码器学习到的,可以使用transform()方法应用于分类数据:

图 7.6 – 主题的查找字典
如前几个截图所示,使用标签对名义数据进行编码既简单又直接。然而,生成的数值数据具有与不同的名义类别不同的数学属性。因此,让我们找出这种方法对有序数据是如何工作的。
在下一个例子中,我们天真地将标签编码器应用于评分数据集。编码器通过迭代训练数据来训练,以创建查找字典:
from sklearn import preprocessing
data = load_ratings()
enc = preprocessing.LabelEncoder()
enc.fit(data)
enc.transform(data)
图 7.7显示了编码后的评分结果作为ratingId,这与前面的例子非常相似。然而,在评分的情况下,评分数据的数值属性与分类评分的有序属性相似:

图 7.7 – 标签编码的评分
此外,让我们看看编码器从输入数据中学习到的查找字典,如图7.8所示:

图 7.8 – 评分的查找字典
你在自动生成的查找字典中看到什么奇怪的地方了吗?由于训练数据中分类值的顺序,我们按照以下顺序创建了一个数字列表:
good < very good < bad < average
这可能不是我们在将标签编码器应用于有序分类值时所预期的结果。我们希望寻找的顺序类似于以下内容:
very bad < bad < average < good < very good
为了创建具有正确顺序的标签编码器,我们可以将分类值的有序列表传递给编码器。这将创建一个更有意义的编码,如图7.9所示:

图 7.9 – 带有自定义顺序的标签编码的评分
要在 Python 中实现这一点,我们必须使用 pandas 的分类顺序变量,这是一种特殊的标签编码器,它需要一个有序分类列表作为输入:
import pandas as pd
data = load_ratings()
categories = [
'very bad', 'bad', 'average', 'good', 'very good']
data = pd.Categorical(data,
categories=categories,
ordered=True)
print(data.codes)
在幕后,我们通过直接将类别传递给编码器来隐式地创建了以下查找字典:

图 7.10 – 带有自定义顺序的评分的查找字典
如前例所示,标签编码器可以迅速应用于任何分类数据,无需过多思考。标签编码器的结果是单个数值特征和分类查找表。此外,我们还可以看到,在主题和评分的示例中,标签编码更适合有序数据。
重要提示
主要的收获是标签编码器非常适合编码有序分类数据。你也了解到元素的顺序很重要,因此将类别按正确顺序手动传递给编码器是一个好的实践。
使用独热编码的正交嵌入
在本节的第二部分,我们将探讨N的含义,其中N代表唯一值的数量。这个向量除了包含一个列值为1的列,代表这个特定值所在的列外,其余列都包含0。以下是一个代码片段,展示了如何将独热编码器应用于articles数据集:
from sklearn import preprocessing
data = [load_articles()]
enc = preprocessing.OneHotEncoder()
enc.fit(data)
enc.transform(data)
前面代码的输出显示在图 7.11中:

图 7.11 – 独热编码的文章
独热编码的查找字典有N+1列,其中N是编码列中唯一值的数量。正如我们在图 7.12中的查找字典中可以看到的那样,字典中的所有 N 维向量都是正交的,长度相等,为1:

图 7.12 – 文章的查找字典
现在,让我们将这种技术与有序数据进行比较,并将独热编码应用于评分表。结果显示在图 7.13中:

图 7.13 – 独热编码的评分
在前面的图中,我们可以看到,即使原始的类别值是有序的,编码后的值也无法排序,因此,在数值编码后,这个属性就丢失了。因此,我们可以得出结论,独热编码非常适合唯一值数量较少的名称分类值。
到目前为止,我们已经学习了如何通过使用查找字典和一维或 N 维数值嵌入将名称和有序分类值嵌入到数值中。然而,我们发现它在许多方面都有一定的局限性,例如唯一类别的数量和嵌入自由文本的能力。在接下来的几节中,我们将学习如何使用简单的 NLP 管道提取单词。
语义和文本值
值得花时间去理解的是,分类值和文本值并不相同。尽管它们可能都存储为字符串,并且在你的数据集中可能有相同的数据类型,但通常,分类值代表一组有限的类别,而文本值可以包含任何文本信息。
那么,这种区分为什么很重要呢?一旦你预处理了分类数据并将其嵌入到数值空间中,名称类别通常会被实现为正交向量。你将无法自动计算类别 A 到类别 B 的距离或创建类别之间的语义意义。
然而,对于文本数据,通常您会采用不同的方法来开始特征提取,该方法假设您将在数据集样本的相同文本特征中找到相似术语。您可以使用这些信息来计算两个文本列之间的有意义相似度得分;例如,测量共同单词的数量。
因此,我们建议您彻底检查您有哪些类型的分类值以及您打算如何预处理它们。此外,一个很好的练习是计算两行之间的相似度,看看它是否与您的预测相符。让我们看看使用基于字典的词袋嵌入的简单文本预处理方法。
构建简单的词袋模型
在本节中,我们将探讨一个惊人的简单概念,即使用称为词袋的技术来解决标签编码在文本数据中的不足,这将为一个简单的 NLP 管道打下基础。当您阅读这些技术时,如果它们看起来太简单,请不要担心;我们将通过调整、优化和改进逐步构建现代 NLP 管道。
使用计数构建的简单词袋模型
在本节中,我们将构建的主要概念是词袋模型。这是一个非常简单的概念;也就是说,它涉及将任何文档建模为包含在给定文档中的单词集合,每个单词的频率。因此,我们丢弃句子结构、单词顺序、标点符号等,并将文档简化为单词的原始计数。在此基础上,我们可以将这个单词计数向量化为一个数值向量表示,然后可以用于机器学习、分析、文档比较等等。虽然这个单词计数模型听起来非常简单,但在路上我们将会遇到很多语言特定的障碍,我们需要解决。
让我们开始并定义一个示例文档,我们将在这个部分对其进行转换:
Almost before we knew it, we had left the ground. The unknown holds its grounds.
将简单的单词计数应用于文档为我们提供了我们的第一个(过于简单)词袋模型:

图 7.14 - 一个简单的词袋模型
然而,像前面那样简单的方法有很多问题。我们混合了不同的标点符号、符号、名词、动词、副词和形容词的不同变形、屈折、时态和格。因此,我们必须构建一个管道来使用 NLP 清理和标准化数据。在本节中,在将数据输入到计数向量器之前,我们将构建以下清理步骤的管道,该向量器最终会计算单词出现次数并将它们收集到特征向量中。
分词 - 将字符串转换为单词列表
构建管道的第一步是将语料库分为文档,将文档分为单词。这个过程被称为nltk:
from nltk.tokenize import word_tokenize
nltk.download('punkt')
tokens = word_tokenize(document)
print(tokens)
上一段代码将输出一个包含单词和标点符号的标记列表:
['Almost', 'before', 'we', 'knew', 'it', ',', 'we', 'had', 'left', 'the', 'ground', '.', 'The', 'unknown', 'holds', 'its', 'grounds', '.']
当你执行前面的代码片段时,nltk将下载预训练的标点模型以运行分词器。分词器的输出是单词和标点符号。
在下一步中,我们将移除标点符号,因为它们对于随后的词形还原过程不相关。然而,我们将在本节稍后将其恢复:
words = [word.lower() for word in tokens if word.isalnum()]
print(words)
结果将只包含原始文档中的单词,没有任何标点符号:
['almost', 'before', 'we', 'knew', 'it', 'we', 'had', 'left', 'the', 'ground', 'the', 'unknown', 'holds', 'its', 'grounds']
在前面的代码中,我们使用了word.isalnum()函数来仅提取字母数字标记并将它们全部转换为小写。前面的单词列表已经比最初的原始模型好得多。然而,它仍然包含许多不必要的词,如the、we、had等,这些词不传达任何信息。
为了过滤掉特定语言的噪声,有道理移除那些经常出现在文本中且不增加任何语义意义的词。在 Python 中,移除这些词是常见的做法,使用nltk库:
from nltk.corpus import stopwords
stopword_set = set(stopwords.words('english'))
words = [word for word in words if word not in stopword_set]
print(words)
现在得到的列表只包含不是停用词的单词:
['almost', 'knew', 'left', 'ground', 'unknown', 'holds', 'grounds']
上述代码为我们提供了一个很好的管道,我们最终只得到具有语义意义的词。我们可以将这个词表带到下一步,并对每个词应用更复杂的转换/归一化。如果我们在这个阶段应用计数向量器,我们最终会得到如图 7.15 所示的简单词袋模型:

图 7.15 – 一个简单的词袋模型
如前图所示,词袋模型中包含的术语列表已经比原始示例干净得多。这是因为它不包含任何标点符号或停用词。
你可能会问,除了在文本中相对频繁出现之外,什么使一个词成为停用词?嗯,这是一个非常好的问题!我们可以使用TF-IDF方法来衡量每个词在当前上下文中的重要性,与它在整个文本中的出现频率进行比较,这将在使用 TF-IDF 衡量词的重要性部分进行讨论。
词干提取 – 基于规则的词缀移除
在下一步中,我们想要归一化词缀——单词的结尾以创建复数和动词变位。你可以看到,随着每一步的进行,我们都在更深入地探讨单一语言的概念——在这个案例中,是英语。然而,当将这些步骤应用于不同的语言时,可能需要使用完全不同的转换。这就是为什么 NLP 是一个如此困难的领域。
移除单词的词缀以获得词根也称为词干提取。词干提取是指将每个单词的出现转换为它的词根的基于规则(启发式)方法。以下是一些预期的转换示例:
cars -> car
saying -> say
flies -> fli
如前例所示,这种针对词根的启发式方法必须为每种语言专门构建。这对于所有其他 NLP 算法也是普遍适用的。为了简洁起见,在这本书中,我们只将讨论英语示例。
英语中一个流行的词根化算法是 Porter 算法,它定义了五个连续的缩减规则,例如从单词末尾移除ed、ing、ate、tion、ence、ance等。nltk库包含 Porter 词根化算法的实现:
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
words = [stemmer.stem(word) for word in words]
print(words)
词根化后的单词列表看起来像这样:
['almost', 'knew', 'left', 'ground', 'unknown', 'hold', 'ground']
在前面的代码中,我们只是简单地将stemmer应用于分词文档中的每个单词。经过这一步骤后的词袋模型如图7.16所示:

图 7.16 – 词根化后的词袋模型
虽然这个算法与词缀配合得很好,但它无法避免对动词的变形和时态进行规范化。这是我们接下来要解决的问题,我们将使用词形还原来解决。
词形还原 – 基于词典的词规范化
当查看词根化示例时,我们已能看出该方法的局限性。例如,对于像are、am或is这样的不规则动词变形,它们都应该被规范化为同一个词be,会发生什么?这正是词形还原试图通过使用预训练的词汇集和转换规则(称为词元)来解决的问题。词元存储在查找字典中,类似于以下转换:
are -> be
is -> be
taught -> teach
better -> good
讨论词形还原时,有一个非常重要的观点需要提出。每个词元都需要应用于正确的词型,因此名词、动词、形容词等都有词元。这样做的原因是,一个词可以是名词或动词的过去时。在我们的例子中,ground可能来自名词ground或动词grind;left可能是形容词或leave的过去时。因此,我们还需要从句子中的单词中提取词型——这个过程称为nltk库再次为我们提供了支持。为了估计正确的 POS 标签,我们还需要提供标点符号:
import nltk
nltk.download('averaged_perceptron_tagger')
tags = nltk.pos_tag(tokens)
print(tags)
这里是生成的 POS 标签:
[('Almost', 'RB'), ('before', 'IN'), ('we', 'PRP'), ('knew', 'VBD'), ('it', 'PRP'), (',', ','), ('we', 'PRP'), ('had', 'VBD'), ('left', 'VBN'), ('the', 'DT'), ('ground', 'NN'), ('.', '.'), ('The', 'DT'), ('unknown', 'JJ'), ('holds', 'VBZ'), ('its', 'PRP$'), ('grounds', 'NNS'), ('.', '.')]
POS 标签描述了文档中每个标记的词型。您可以使用nltk.help.upenn_tagset()命令找到完整的标签列表。以下是从命令行执行此操作的示例:
import nltk
nltk.download('tagsets')
nltk.help.upenn_tagset()
前面的命令将打印出 POS 标签列表:
CC: conjunction, coordinating
& 'n and both but either et for less minus neither nor or
plus so therefore times v. versus vs. whether yet
CD: numeral, cardinal
mid-1890 nine-thirty forty-two one-tenth ten million 0.5
one forty- seven 1987 twenty '79 zero two 78-degrees
eighty-four IX '60s .025 fifteen 271,124 dozen quintillion
DM2,000 ...
DT: determiner
all an another any both del each either every half la many
much nary neither no some such that the them these this
those
EX: existential there
there
FW: foreign word
gemeinschaft hund ich jeux habeas Haementeria Herr K'ang-si
vous lutihaw alai je jour objets salutaris fille quibusdam
pas
...
POS 标签还包括动词和其他非常有用的时态信息。然而,在本节的词形还原中,我们只需要知道单词类型——名词、动词、形容词或副词。一个可能的词形还原器选择是nltk中的 WordNet 词形还原器。WordNet 是一个英语词汇数据库,它将单词分组到概念和词型组中。
要将词形还原器应用于词干分析的结果,我们需要通过标点符号和停用词过滤 POS 标签,类似于之前的预处理步骤。然后,我们可以使用结果单词的词标签。让我们使用nltk应用词形还原器:
from nltk.corpus import wordnet
from nltk.stem import WordNetLemmatizer
nltk.download('wordnet')
lemmatizer = WordNetLemmatizer()
tag_dict = {
"J": wordnet.ADJ,
"N": wordnet.NOUN,
"V": wordnet.VERB,
"R": wordnet.ADV
}
pos = [tag_dict.get(t[0].upper(), wordnet.NOUN) \
for t in zip(*tags)[1]]
words = [lemmatizer.lemmatize(w, pos=p) \
for w, p in zip(words, pos)]
print(words)
代码输出了词形还原后的单词:
['almost', 'know', 'leave', 'ground', 'unknown', 'hold', 'ground']
上述单词列表看起来比我们在之前的模型中找到的干净得多。这是因为我们对动词的时态进行了归一化,并将它们转换成不定式形式。得到的词袋模型在图 7.17中显示:

图 7.17 – 词形还原后的词袋模型
这种技术对于清理数据集中单词的不规则形式非常有帮助。然而,它基于规则——称为词元——因此,它只能用于有此类词元的语言和单词。
scikit-learn 中的词袋模型
最后,我们可以将我们之前的所有步骤结合起来,创建一个最先进的自然语言处理预处理流程,以归一化输入文档,并通过计数向量器运行它们,以便我们可以将它们转换成数值特征向量。对多个文档这样做,我们可以轻松地在数值空间中比较文档的语义。我们可以计算文档特征向量之间的余弦相似度来计算它们的相似度,将它们插入到监督分类方法中,或者对生成的文档概念进行聚类。
回顾一下,让我们看看简单词袋模型的最终流程。我想强调的是,这个模型只是我们使用自然语言处理进行特征提取旅程的开始。我们进行了以下步骤进行归一化:
-
分词
-
删除标点符号
-
删除停用词
-
词干提取
-
基于 POS 标签的词形还原
在最后一步中,我们在 scikit-learn 中应用了CountVectorizer。这将计算每个词的出现次数,创建一个全局单词语料库,并输出一个包含单词频率的稀疏特征向量。以下是将预处理数据从nltk传递到CountVectorizer的示例代码:
from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer()
data = [" ".join(words)]
X_train_counts = count_vect.fit_transform(data)
print(X_train_counts)
转换后的词袋模型包含坐标和计数:
(0, 0) 1
(0, 3) 1
(0, 4) 1
(0, 1) 2
(0, 5) 1
(0, 2) 1
坐标指的是(文档 ID,术语 ID)对,而计数指的是术语频率。为了更好地理解这个输出,我们还可以查看模型的内部词汇表。vocabulary_参数包含术语 ID 的查找字典:
print(count_vect.vocabulary_)
代码输出了模型的单词字典:
{'almost': 0, 'know': 3, 'leave': 4, 'ground': 1, 'unknown': 5, 'hold': 2}
在前面的例子中,我们在将其传递到CountVectorizer之前将预处理文档转换回字符串。这样做的原因是CountVectorizer自带一些可配置的预处理技术,例如分词、停用词删除等。对于这个演示,我们想将其应用于预处理数据。转换的输出是一个包含术语频率的稀疏特征向量。
让我们来找出如何将多个术语与语义概念相结合。
利用术语重要性和语义
我们到目前为止所做的一切都相对简单,并且基于词干或所谓的标记。词袋模型只不过是一个标记字典,它按字段统计标记的出现次数。在本节中,我们将探讨一种常见的技巧,通过术语的 n-gram 和 skip-gram 组合来进一步改进文档之间的匹配。
以多种方式组合术语将使你的词典爆炸。如果你有一个大语料库,比如一千万个单词,这就会变成一个问题。因此,我们将探讨一种常见的预处理技术,通过 SVD 来降低大型词典的维度。
虽然,现在,这种方法要复杂得多,但它仍然基于一个在大语料库上已经工作得很好的词袋模型。然而,当然,我们可以做得更好,并尝试理解词语的重要性。因此,我们将探讨 NLP 中另一种流行的技术来计算术语的重要性。
使用 n-gram 和 skip-gram 进行词语泛化
在之前的管道中,我们考虑了每个单词本身,没有任何上下文。然而,众所周知,上下文在语言中非常重要。有时,词语在一起才有意义,而不是单独存在。为了将这种上下文引入同类型的算法,我们将引入n-gram和skip-gram。这两种技术都在 NLP 中广泛用于预处理数据集和从文本数据中提取相关特征。
让我们从 n-gram 开始。一个输入数据集的N个连续实体(即字符、单词或标记)。以下是一些在字符列表中计算 n-gram 的示例:
A, B, C, D -> 1-Gram: A, B, C, D
A, B, C, D -> 2-Gram: AB, BC, CD
A, B, C, D -> 3-Gram: ABC, BCD
这里是一个示例,使用 scikit-learn 的CountVectorizer中的内置ngram_range参数来为输入数据生成多个 n-gram:
from sklearn.feature_extraction.text import CountVectorizer
count_vect = CountVectorizer(ngram_range=(1,2))
X_train_counts = count_vect.fit_transform(data)
print(count_vect.vocabulary_)
如您所见,词汇现在包含每个术语的 1-gram 和 2-gram 表示:
{'almost': 0, 'before': 2, 'we': 24, 'knew': 15, 'it': 11, 'had': 7, 'left': 17, 'the': 19, 'ground': 4, 'unknown': 22, 'holds': 9, 'its': 13, 'grounds': 6, 'almost before': 1, 'before we': 3, 'we knew': 26, 'knew it': 16, 'it we': 12, 'we had': 25, 'had left': 8, 'left the': 18, 'the ground': 20, 'ground the': 5, 'the unknown': 21, 'unknown holds': 23, 'holds its': 10, 'its grounds': 14}
在前面的代码中,我们可以看到,我们现在在训练词汇中拥有两个连续词语的组合,而不是原始词语。
我们可以将 n-gram 的概念扩展到允许模型跳过词语。如果我们想要执行一个 2-gram,但其中一个样本中两个词语之间有一个形容词,而在另一个样本中这些词语是直接相邻的,这是一个很好的选项。为了实现这一点,我们需要一种方法来定义我们允许跳过多少个词语来找到匹配的词语。以下是一个使用之前相同字符的示例:
A, B, C, D -> 2-Gram (1 skip): AB, AC, BC, BD, CD
A, B, C, D -> 2-Gram (2 skip): AB, AC, AD, BC, BD, CD
幸运的是,我们在nltk中找到了 n-gram 的通用版本,即nltk.skipgrams方法。将跳过距离设置为0会导致传统的 n-gram 算法。我们可以将其应用于我们的原始数据集:
terms = list(nltk.skipgrams(document.split(' '), 2, 1))
print(terms)
与 2-gram 示例类似,该方法产生了一组成对术语的组合列表。然而,在这种情况下,我们在这些对之间允许存在一个跳过的单词:
[('Almost', 'before'), ('Almost', 'we'), ('before', 'we'), ('before', 'knew'), ('we', 'knew'), ('we', 'it,'), ('knew', 'it,'), ('knew', 'we'), ('it,', 'we'), ('it,', 'had'), ('we', 'had'), ('we', 'left'), ('had', 'left'), ('had', 'the'), ('left', 'the'), ('left', 'ground.'), ('the', 'ground.'), ('the', 'The'), ('ground.', 'The'), ('ground.', 'unknown'), ('The', 'unknown'), ('The', 'holds'), ('unknown', 'holds'), ('unknown', 'its'), ('holds', 'its'), ('holds', 'grounds.'), ('its', 'grounds.')]
在前面的代码中,我们可以观察到 skip-grams 可以为 NLP 模型生成大量的额外有用特征维度。在现实场景中,这两种技术通常都会使用,因为单个单词的顺序在语义中起着重要作用。
然而,如果输入文档是来自网络的所有网站或大型文档,新特征维度的爆炸可能会造成灾难。因此,我们还需要一种方法来避免维度爆炸,同时捕获输入数据中的所有语义。我们将在下一节中解决这个挑战。
使用 SVD 减小词字典大小
NLP 的一个常见问题是语料库中的单词数量庞大,因此字典大小会爆炸。在先前的例子中,我们看到字典的大小定义了正交项向量的大小。因此,20,000 个术语的字典大小将导致 20,000 维的特征向量。即使没有任何 n-gram 丰富,这个特征向量维度也太大,无法在标准 PC 上处理。
因此,我们需要一个算法来减小生成的CountVectorizer的维度,同时保留现有信息。理想情况下,我们只会从输入数据中移除冗余信息,并将其投影到低维空间,同时保留所有原始信息。
PCA 变换非常适合我们的解决方案,并帮助我们将输入数据转换成更低维度的线性无关维度。然而,计算特征值需要一个对称矩阵(行数和列数相同),在我们的情况下,我们没有这样的矩阵。因此,我们可以使用 SVD 算法,它将特征向量计算推广到非对称矩阵。由于其数值稳定性,它通常用于 NLP 和信息检索系统中。
SVD 在 NLP 应用中的使用也被称为潜在语义分析(LSA),因为主成分可以解释为潜在特征空间中的概念。SVD 嵌入将高维特征向量转换成低维概念空间。概念空间中的每个维度都是由术语向量的线性组合构成的。通过丢弃方差最小的概念,我们也减小了结果概念空间的维度,使其变得小得多,更容易处理。典型的概念空间有 10 到 100 个维度,而单词字典通常有超过 100,000 个。
让我们通过 sklearn 的 TruncatedSVD 实现来查看一个示例。SVD 被实现为一个转换器类,因此我们需要调用 fit_transform() 来拟合一个字典并使用相同的步骤进行转换。SVD 使用 n_components 参数配置为仅保留方差最高的成分:
from sklearn.decomposition import TruncatedSVD
svd = TruncatedSVD(n_components=5)
X_lsa = svd.fit_transform(X_train_counts)
在前面的代码中,我们使用 SVD 对 X_train_counts 数据和 CountVectorizer 的输出进行 LSA。我们配置 SVD 只保留方差最高的前五个成分。
通过降低数据集的维度,你会丢失信息。幸运的是,我们可以使用训练好的 SVD 对象来计算剩余数据集中方差的数量,如下例所示:
Print(svd.explained_variance_ratio_.sum())
前面的命令将方差输出为一个介于 0 和 1 之间的数字,其中 1 表示 SVD 变换是原始数据到潜在空间的精确无损映射:
0.19693920498587408
在这种情况下,仅使用五个成分,SVD 保留了原始数据集 20% 的方差。
重要提示
根据任务的不同,我们通常的目标是在潜在变换后保留超过 80-90% 的原始方差。
在前面的代码示例中,我们计算了转换后保留的数据的方差。因此,我们现在可以增加或减少成分的数量,以保持转换数据中特定百分比的信息。这是一个非常有用的操作,并在许多实际的 NLP 应用中得到了使用。
注意,我们仍在使用词袋模型的原始单词字典。这个模型的一个特定缺点是,一个术语出现的频率越高,它的计数(以及因此的权重)就越高。这是一个问题,因为现在,任何不是停用词且在文本中频繁出现的术语都将获得高权重——无论该术语在特定文档中的重要性如何。因此,我们引入了另一个极其流行的预处理技术——TF-IDF。
使用 TF-IDF 测量单词的重要性
词袋方法的一个特定缺点是,我们仅仅计算一个上下文中单词的绝对数量,而不检查该单词是否在所有文档中普遍出现。一个在所有文档中都出现的术语可能对我们模型来说并不相关,因为它包含的信息较少,并且更频繁地出现在其他文档中。因此,在文本挖掘中,计算给定上下文中某个单词的重要性是一项重要的技术。
因此,我们希望计算一个上下文中术语的相对数量,而不是上下文中术语的绝对计数。通过这样做,我们将给只出现在特定上下文中的术语赋予更高的权重,并减少给出现在许多不同文档中的术语的权重。这正是 TF-IDF 算法所做的事情。根据以下方程式,很容易计算文档中术语 (t) 的权重 (w):

虽然词频 (ft) 计算了文档中的所有术语,但逆文档频率是通过将总文档数 (N) 除以所有文档中术语的计数 (fd) 来计算的。IDF* 术语通常进行对数变换,因为所有文档中术语的总数可能相当大。
在下面的示例中,我们不会直接使用 TF-IDF 函数。相反,我们将使用 TfidfVectorizer,它在一步中完成计数并将 TF-IDF 函数应用于结果。再次强调,该函数作为 sklearn 转换器实现,因此我们调用 fit_transform() 来训练和转换数据集:
from sklearn.feature_extraction.text import TfidfVectorizer
vect = TfidfVectorizer()
data = [" ".join(words)]
X_train_counts = vect.fit_transform(data)
print(X_train_counts)
结果的格式与前面的示例类似,包含 (document id, term id) 对及其 TF-IDF 值:
(0, 2) 0.3333333333333333
(0, 5) 0.3333333333333333
(0, 1) 0.6666666666666666
(0, 4) 0.3333333333333333
(0, 3) 0.3333333333333333
(0, 0) 0.3333333333333333
在前面的代码中,我们直接应用 TfidfVectorizer,它返回与使用 CountVectorizer 和 TfidfTransformer 结合相同的结果。我们转换包含词袋模型中单词的数据集,并返回 TF-IDF 值。我们还可以为每个 TF-IDF 值返回术语:
print(vect.get_feature_names())
上述代码返回模型的词汇表:
['almost', 'ground', 'hold', 'know', 'leave', 'unknown']
在这个示例中,我们可以看到 ground 获得了 TF-IDF 值为 0.667,而所有其他术语的值均为 0.333。当向语料库中添加更多文档时,这个计数将相对缩放——因此,如果单词 hold 再次出现,TF-IDF 值将降低。
在任何实际的管道中,我们都会始终使用本章中介绍的所有技术——分词、停用词去除、词干提取、词形还原、n-gram/skip-gram、TF-IDF 和 SVD——结合在一个单一的管道中。结果将是一个由重要性加权的 n-gram/skip-gram 的标记的数值表示,并转换到潜在语义空间。使用这些技术进行你的第一个 NLP 管道将让你走得很远,因为你现在可以从你的文本数据中捕获大量信息。
到目前为止,我们已经学习了如何使用一维或 N 维标签、计数和加权词干和字符组合来数值化许多种类的分类和文本值。虽然许多这些方法在需要简单数值嵌入的许多情况下都表现良好,但它们都有一个严重的限制——它们不编码语义。让我们看看我们如何在同一个管道中提取文本的语义意义。
使用词嵌入提取语义
当计算新闻的相似性时,你会想象到像网球、一级方程式或足球这样的主题在语义上比像政治、经济或科学这样的主题更相似。然而,在之前讨论的技术中,所有编码的分类都被视为在语义上是相同的。在本节中,我们将讨论一种简单的语义嵌入方法,这也可以称为词嵌入。
之前讨论的管道使用 LSA 将多个文档转换为术语,然后将这些术语转换为可以与其他文档比较的语义概念。然而,语义意义基于术语出现和重要性——没有对单个术语之间的语义进行测量。
因此,我们寻找的是将术语嵌入到数值多维空间中的嵌入,这样每个单词就代表这个空间中的一个点。这使我们能够计算这个空间中多个单词之间的数值距离,以比较两个单词的语义意义。词嵌入最有趣的好处是,在词嵌入上的代数运算不仅数值上是可能的,而且是有意义的。考虑以下示例:
King – Man + Woman = Queen
我们可以通过将单词语料库映射到 N 维数值空间,并根据单词语义优化数值距离(例如,基于语料库中单词之间的距离)来创建这样的嵌入。结果优化输出语料库中单词及其 N 维数值表示的字典。在这个数值空间中,单词具有与语义空间中相同或至少相似的属性。一个巨大的好处是,这些嵌入可以无监督地训练,因此不需要标记的训练数据。
最早的嵌入之一被称为Word2Vec,它基于连续的词袋模型或连续的跳字模型来计数和测量窗口中的单词。让我们尝试这个功能,并使用 Word2Vec 进行语义词嵌入:
-
最好的 Python 词嵌入实现是Gensim,我们也将在这里使用它。我们需要将我们的标记输入到模型中以便训练它:
from gensim.models import Word2Vec model = Word2Vec(words, size=100, window=5) vector = model.wv['ground']
在前面的代码中,我们加载了Word2Vec模型,并用之前章节中存储在words变量中的标记列表初始化它。size属性定义了结果向量的维度,window参数决定了我们应该考虑多少个单词作为每个窗口。一旦模型被训练,我们就可以简单地在该模型的字典中查找词嵌入。
代码将自动在我们提供的标记集上训练嵌入。结果模型将单词到向量的映射存储在wv属性中。理想情况下,我们还使用一个大型语料库或预训练模型,该模型由gensim或另一个 NLP 库(如NLTK)提供,以训练嵌入并使用较小的数据集进行微调。
-
接下来,我们可以使用训练好的模型通过 Word2Vec 嵌入将我们文档中的所有术语嵌入。然而,这将导致多个向量,因为每个单词都返回其自己的嵌入。因此,你需要使用所有嵌入的数学平均值将所有向量组合成一个单一的向量。这个过程与用于生成 LSA 中概念的类似过程非常相似。此外,还有其他可能的缩减技术;例如,使用 TF-IDF 值对单个嵌入向量进行加权:
dim = len(model.wv.vectors[0]) X = np.mean([model.wv[w] for w in words if w in model.wv] \ or [np.zeros(dim)], axis=0)
在前面的函数中,我们计算所有术语的词嵌入向量的平均值——这被称为平均嵌入,它代表了文档在嵌入空间中的概念。如果一个单词在嵌入中未找到,我们需要在计算中将它替换为零。
您可以通过下载预训练嵌入,例如在维基百科语料库上,来使用此类语义嵌入为您的应用程序。然后,您可以遍历您的清洗过的输入标记,并在数字嵌入的字典中查找单词。
GloVe 是另一种流行的将单词编码为数值向量的技术,由斯坦福大学开发。与基于连续窗口的方法相比,它使用全局单词到单词共现统计来确定单词之间的线性关系:
-
让我们看看在维基百科和 Gigaword 新闻档案上训练的预训练 6B 标记嵌入:
# download pre-trained dictionary from # http://nlp.stanford.edu/data/glove.6B.zip glove = {} with open('glove.6B.100d.txt') as f: for line in f: word, coefs = line.split(maxsplit=1) coefs = np.fromstring(coefs, 'f', sep=' ') glove[word] = coefs
在前面的代码中,我们只打开并解析预训练的词嵌入,以便将单词和向量存储在查找字典中。
-
然后,我们使用这个字典在我们的训练数据中查找标记,并通过计算所有 GloVe 向量的平均值来合并它们:
X = np.mean([glove[w] for w in words if w in glove] \ or [np.zeros(dim)], axis=0)
前面的代码与之前非常相似,每个单词返回一个向量,最后通过取平均值进行聚合。再次强调,这与使用训练数据中所有标记的语义概念相对应。
Gensim 提供了其他流行的语义嵌入模型,如doc2word、fastText和GloVe。gensim Python 库是利用这些预训练嵌入或训练您自己的模型的绝佳场所。现在您可以用单词向量的平均嵌入替换您的词袋模型,以捕获词义。然而,您的管道仍然由许多可调组件构建。
在下一节中,我们将探讨构建端到端最先进的语言模型以及重用 Azure 认知服务中的一些语言特征。
实现端到端语言模型
在前面的章节中,我们训练和连接了多个部分以实现一个最终算法,其中大多数单个步骤也需要进行训练。词形还原包含一个转换规则字典。停用词存储在字典中。词干提取需要为每种语言和每个需要嵌入训练的单词制定规则——TF-IDF 和 SVD 仅在训练数据上计算,但彼此独立。
这是一个类似于传统计算机视觉方法的问题,我们将在第十章“在 Azure 上训练深度神经网络”中更深入地讨论,其中许多经典算法被组合成一个特征提取器和分类器的管道。类似于计算机视觉中通过梯度下降和反向传播训练的端到端模型的突破,深度神经网络——尤其是序列到序列模型——已经取代了手动执行每个转换和训练步骤的经典方法。
在本节中,首先,我们将查看如何通过自定义嵌入和 LSTM 实现来改进我们之前的模型,以对标记序列进行建模。这将帮助你更好地理解我们是如何从基于单个预处理器管道的个体方法过渡到使用深度学习的完整端到端方法的。
序列到序列模型是基于在可变输入集上训练的编码器和解码器模型。这种编码器/解码器架构用于各种任务,如机器翻译、图像标题和摘要。这些模型的优点之一是,你可以重用这个网络中的编码器部分,将一组输入转换为编码器的固定集数值表示。
接下来,我们将探讨最先进的语言表示模型,并讨论它们如何用于特征工程和文本数据的预处理。我们将使用 BERT 进行情感分析和数值嵌入。
最后,我们还将探讨如何重用 Azure 认知服务 API 进行文本分析,以执行高级建模和特征提取,例如文本或句子情感、关键词或实体识别。这是一个很好的方法,因为你可以利用微软的知识和大量训练数据,通过简单的 HTTP 请求来执行复杂的文本分析。
标记序列的端到端学习
我们不想将不同的算法片段连接成一个单一的管道,而是想要构建和训练一个端到端模型,该模型可以训练词嵌入、预形式潜在语义转换,并在单个模型中捕获文本中的顺序信息。
这种模型的优点是,每个处理步骤都可以在单个联合优化过程中针对用户的预测任务进行微调:
-
管道的第一部分将看起来与前几节非常相似。我们将构建一个标记化器,将文档转换为标记序列,然后将其转换为基于标记序列的数值模型。然后,我们将使用
pad_sequences将所有文档对齐到相同的长度:from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import \ pad_sequences num_words = 1000 tokenizer = Tokenizer(num_words=num_words) tokenizer.fit_on_texts(X_words) X = tokenizer.texts_to_sequences(X_words) X = pad_sequences(X, maxlen=2000) -
在下一步中,我们将使用 Keras 构建一个简单的模型,包括一个嵌入层和一个 LSTM 层来捕获标记序列。嵌入层将执行类似于 GloVe 的操作,将单词嵌入到语义空间中。LSTM 单元将确保我们比较的是单词序列而不是单个单词。然后,我们将使用一个带有softmax激活函数的密集层来实现分类头:
from tensorflow.keras.layers import Embedding, LSTM, Dense from tensorflow.keras.models import Sequential embed_dim = 128 lstm_out = 196 model = Sequential() model.add(Embedding( num_words, embed_dim, input_length=X.shape[1])) model.add(LSTM( lstm_out, recurrent_dropout=0.2, dropout=0.2)) model.add(Dense( len(labels), activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['categorical_crossentropy'])
如前所述的函数所示,我们使用三个层(即Embedding、LSTM和Dense)和一个softmax激活函数构建了一个简单的神经网络,用于分类。这意味着为了训练此模型,我们还需要同时解决一个分类问题。因此,我们需要标记的训练数据来使用这种方法进行分析。在下一节中,我们将探讨序列到序列模型是如何在输入输出文本序列中用于学习隐式文本表示的。
最前沿的序列到序列模型
在近年来,另一种类型的模型已经取代了传统的 NLP 管道——基于 transformer 的模型。这些类型的模型是完全端到端的,并使用序列到序列映射、位置编码和多头注意力层。这使得模型能够在文本中向前和向后查看,关注特定模式,并完全端到端地学习任务。正如你可能已经猜到的,这些模型具有复杂的架构,通常有超过一亿或超过十亿的参数。
序列到序列模型现在在许多复杂的端到端自然语言处理(NLP)问题中处于最前沿,例如分类(例如,情感或文本分析)、语言理解(例如,实体识别)、翻译、文本生成、摘要等等。
一种流行的序列到序列模型是 BERT,今天,它存在许多不同的变体和配置。基于 BERT 架构的模型似乎表现特别出色,但已经被更新的架构、调整的参数或具有更多训练数据的模型所超越。
使用这些新的 NLP 模型的最简单方法是通过Hugging Face的transformers库,该库提供了端到端模型(或管道)以及预训练的标记器和模型。transformers库实现了TensorFlow和PyTorch的所有模型架构。这些模型可以轻松地在应用程序中使用,从头开始训练,或者使用特定领域的自定义训练数据进行微调。
以下示例展示了如何使用默认的 sentiment-analysis 流程实现情感分析,该流程在撰写本文时使用 TFDistilBertForSequenceClassification 模型:
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
result = classifier("Azure ML is quite good.")[0]
print("Label: %s, with score: %.2f" %
(result['label'], result['score']))
如前例所示,使用预训练模型进行端到端预测任务非常简单。这三行代码可以轻松集成到你的特征提取流程中,以丰富你的训练数据中的情感。
除了端到端模型之外,NLP 的另一个流行应用是在预处理文本数据时提供语义嵌入。这也可以使用 transformers 库和许多支持的模型之一来实现。
要做到这一点,首先,我们初始化一个预训练的 BERT 分词器。这将帮助我们将输入数据分割成适合 BERT 模型的正确格式:
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
inputs = tokenizer("Azure ML is quite good.",
return_tensors="tf")
一旦我们将输入转换为标记序列,我们就可以评估 BERT 模型。要检索数值嵌入,我们需要理解编码器的潜在状态,我们可以使用 last_hidden_state 属性来检索:
from transformers import TFBertModel
model = TFBertModel.from_pretrained('bert-base-uncased')
outputs = model(**inputs)
print(outputs.last_hidden_state)
最后的隐藏层包含模型的潜在表示,我们现在可以用作模型中的语义数值表示:
<tf.Tensor: shape=(1, 10, 768), dtype=float32, numpy=
array([[[-0.30760652, 0.19552925, 0.1440584 , ..., 0.08283961,
0.16151786, 0.23049755],…
这些模型的关键启示是它们使用基于编码器/解码器的架构,这使我们能够简单地借用编码器将文本嵌入到语义数值特征空间中。因此,一个常见的方法是下载预训练模型,并通过网络的编码器部分进行正向传递。现在,固定大小的数值输出可以用作任何其他模型的特征向量。这是一个常见的预处理步骤,也是使用最先进的语言模型进行数值嵌入的良好权衡。
使用 Azure 认知服务的文本分析
在许多工程学科中,一个好的方法是不重复造轮子,因为许多其他公司已经比你更好地解决了相同的问题。对于微软开发、实施和训练的基本文本分析和文本理解任务,现在作为服务提供的情况可能也是如此。
如果我告诉你,当使用 Azure 时,文本理解功能,如情感分析、关键词提取、语言检测、命名实体识别以及个人身份信息(PII)的提取,只需一个请求即可?Azure 提供的 Text Analytics API 作为认知服务的一部分,将为您解决所有这些问题。
这并不能解决将文本转换为数值的需求,但它会使从文本中提取语义变得更加容易。一个例子就是使用认知服务作为额外的特征工程步骤,执行关键词提取或情感分析,而不是实现自己的 NLP 流程。
让我们实现一个函数,使用认知服务的文本分析 API 返回给定文档的情感。当你想在文本中添加额外的属性,如整体情感时,这非常棒。让我们首先设置所有需要调用认知服务 API 的参数:
import requests
region='westeurope'
language='en'
version='v3.1'
key = '<insert access key>'
url = "https://{region}.api.cognitive.microsoft.com" + \
+ "/text/analytics/{version}/sentiment".format(
region=region, version=version)
接下来,我们定义请求的内容和元数据。我们创建一个包含单个文档和要分析的文本的payload对象:
params = {
'showStats': False
}
headers = {
'Content-Type': 'application/json',
'Ocp-Apim-Subscription-Key': key
}
payload = {
'documents': [{
'id': '1',
'text': 'This is some input text that I love.',
'language': language
}]
}
最后,我们需要将 payload、头部和参数发送到认知服务 API:
response = requests.post(url,
json=payload,
params=params,
headers=headers)
result = response.json()
print(result)
之前的代码看起来与我们在第二章中看到的计算机视觉示例非常相似,即在 Azure 中选择合适的机器学习服务。事实上,它使用的是相同的 API,但只是为文本分析和在此情况下情感分析功能使用了不同的端点。让我们运行这段代码并查看输出,输出看起来与以下片段非常相似:
{
'documents': [{
'id': '1',
'sentiment': 'positive',
'confidenceScores': {
'positive': 1.0,
'neutral': 0.0,
'negative': 0.0},
...}],
...
}
我们可以观察到,JSON 响应包含每个文档的情感分类(积极、中性和消极)以及每个类别的数字置信度分数。此外,你可以看到,生成的文档存储在一个数组中,并标记了一个id值。因此,你可以使用 ID 来标识每个文档,向此 API 发送多个文档。
使用自定义预训练的语言模型很棒,但对于标准化的文本分析,我们可以简单地重用认知服务。微软在研究和生产这些语言模型上投入了大量的资源,你可以用相对较少的费用为自己的数据管道使用这些模型。因此,如果你更喜欢使用托管服务而不是运行自己的客户 transformer 模型,你应该尝试这个文本分析 API。
摘要
在本章中,你学习了如何使用最先进的 NLP 技术对文本和分类的定名和有序数据进行预处理。
现在,你可以使用停用词去除、词形还原和词干提取、n-gram和计数词项出现来构建一个经典的 NLP 管道,使用词袋模型。我们使用SVD来降低结果特征向量的维度,并生成低维度的主题编码。对基于计数的词袋模型的一个重要调整是比较文档中术语的相对频率。你学习了TF-IDF函数,并可以使用它来计算一个词在文档中的重要性,与语料库相比。
在下一节中,我们探讨了Word2Vec和GloVe,它们是预训练的数字词嵌入字典。现在,你可以轻松地重用预训练的词嵌入,在商业 NLP 应用中实现显著的改进和准确性,这得益于词的语义嵌入。
最后,我们通过研究一种最先进的方法来结束这一章节,该方法使用端到端语言表示,例如 BERT 和基于 BERT 的架构,这些架构被训练为序列到序列模型。这些模型的好处是你可以重用编码器将一系列文本转换为数值表示,这在特征提取过程中是一个非常常见的任务。
在下一章中,我们将探讨如何使用 Azure Machine Learning 训练一个机器学习模型,应用我们迄今为止所学的一切。
第八章:第八章:Azure 机器学习管道
在上一章中,我们学习了高级预处理技术,如类别嵌入和 NLP,从文本特征中提取语义意义。在本章中,你将学习如何使用这些预处理和转换技术来构建可重用的机器学习管道。
首先,你将了解将你的代码分解成单个步骤并将它们包装成管道的好处。不仅可以通过模块化和参数化使你的代码块可重用,而且还可以控制单个步骤的计算目标。这有助于优化你的计算,节省成本,并同时提高性能。最后,你可以通过 HTTP 端点或通过定期或反应式调度来参数化和触发你的管道。
然后,我们将分几个步骤构建一个复杂的 Azure 机器学习管道。我们将从一个简单的管道开始,添加数据输入、输出以及步骤之间的连接,并将管道作为 Web 服务部署。你还将了解基于频率和变化数据的先进调度,以及如何并行化管道步骤以处理大量数据。
在最后一部分,你将学习如何将 Azure 机器学习管道集成到其他 Azure 服务中,例如 Azure 机器学习设计器、Azure 数据工厂和 Azure DevOps。这将帮助你了解不同管道和工作流程服务之间的共性和差异,以及你如何触发机器学习管道。
在本章中,我们将涵盖以下主题:
-
在机器学习工作流程中使用管道
-
构建和发布机器学习管道
-
将管道与其他 Azure 服务集成
技术要求
在本章中,我们将使用以下 Python 库和版本来创建管道和管道步骤:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0
与前面的章节类似,你可以使用本地 Python 解释器或托管在 Azure 机器学习中的笔记本环境运行此代码。然而,所有脚本都需要在 Azure 中安排执行。
本章中所有的代码示例都可以在本书的 GitHub 存储库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter08。
在机器学习工作流程中使用管道
将你的工作流程分解成可重用和可配置的步骤,并将这些步骤组合成一个端到端管道,为实施端到端机器学习过程提供了许多好处。多个团队可以拥有并迭代单个步骤以改进管道,同时其他人可以轻松地将管道的每个版本集成到他们的当前设置中。
管道本身不仅将代码与执行分开,还将执行与编排分开。因此,你可以配置单个计算目标,用于优化你的执行并提供并行执行,而你无需接触 ML 代码。
我们将快速浏览 Azure 机器学习管道,并探讨为什么它们是实现 Azure 中 ML 工作流程的首选工具。在下一节“构建和发布 ML 管道”中,我们将深入探讨通过构建这样一个管道来探索其单个功能。
为什么构建管道?
对于一个主要进行实验、同时处理数据、基础设施和建模的单个开发者来说,管道并没有给开发者的工作流程带来很多好处。然而,一旦你在多个团队中进行企业级开发,这些团队在不同的 ML 系统部分进行迭代,那么你将极大地从将代码拆分为单个执行步骤的管道中受益。
这种模块化将为你带来极大的灵活性,多个团队能够高效协作。当你在迭代和构建管道的新版本时,团队能够集成你的模型和管道。通过使用版本化管道和管道参数,你可以控制如何调用你的数据或模型服务管道,并确保审计和可重复性。
使用工作流而不是在单个文件中运行所有内容的另一个重要好处是执行速度和成本的改进。你可以在不同的计算目标上单独运行和扩展步骤,而不是在同一个计算实例上运行单个脚本。这让你对潜在的成本节约有更大的控制,并能够更好地优化性能,你只需重试管道中失败的部分,而无需重试整个管道。
通过调度管道,你可以确保所有管道运行都无需手动干预即可执行。你只需定义触发器,例如新训练数据的存在,以执行你的管道。将代码执行与触发执行解耦为你带来了许多好处,例如轻松集成到许多其他服务中。
最后,你代码的模块化特性使得代码具有很高的可重用性。通过将脚本拆分为功能步骤,如清理、预处理、特征工程、训练和超参数调整,你可以为其他项目版本化和重用这些步骤。
因此,一旦你想从这些优势中获益,你就可以开始组织你的代码到管道中,这些管道可以部署、调度、版本化、扩展和重用。让我们看看如何在 Azure 机器学习中实现这一点。
Azure 机器学习管道是什么?
Azure 机器学习流水线是 Azure 机器学习中的可执行步骤工作流程,它构成了完整的机器学习工作流程。因此,您可以将数据导入、数据转换、特征工程、模型训练、优化以及部署作为流水线步骤。
流水线是您 Azure 机器学习工作空间中的资源,您可以创建、管理、版本控制、触发和部署。它们与所有其他 Azure 机器学习工作空间资源集成,例如用于加载数据的数据集和数据存储、计算实例、模型和端点。每个流水线运行都在您的 Azure 机器学习工作空间中作为一个实验执行,并为您提供我们在上一章中介绍过的相同好处,例如在灵活的计算集群上运行时跟踪文件、日志、模型、工件和图像。
在实现灵活和可重用的机器学习工作流程时,Azure 机器学习流水线应该是您的首选。通过使用流水线,您可以模块化代码为功能块和版本,并将这些块与其他项目共享。这使得与其他团队协作进行复杂的端到端机器学习工作流程变得容易。
Azure 机器学习流水线的另一个伟大集成是与工作空间中的端点和触发器的集成。通过一行代码,您可以将流水线发布为 Web 服务或 Web 服务端点,并使用此端点从任何地方配置和触发流水线。这为将 Azure 机器学习流水线与其他许多 Azure 和第三方服务集成打开了大门。
然而,如果您需要一个更复杂的触发器,例如基于源数据变化的持续调度或响应式触发,您也可以轻松地配置这些功能。使用流水线的额外好处是,所有编排功能都与您的训练代码完全解耦。
正如您所看到的,使用 Azure 机器学习流水线为您的机器学习工作流程带来了许多好处。然而,值得注意的是,这项功能确实带来了一些额外的开销,即在每个计算步骤中包装流水线步骤,添加流水线触发器,为每个步骤配置环境和计算目标,以及将参数作为流水线选项公开。让我们从构建我们的第一个流水线开始。
构建和发布机器学习流水线
让我们继续使用之前章节中学到的所有知识,构建一个数据处理流水线。我们将使用 Azure 机器学习 SDK for Python 定义所有流水线步骤为 Python 代码,以便它可以轻松管理、审查和作为编写脚本存入版本控制。
我们将定义一个管道为一系列管道步骤的线性序列。每个步骤都将有一个输入和输出,分别定义为管道数据汇和源。每个步骤都将关联到一个计算目标,该目标定义了执行环境以及执行所需的计算资源。我们将设置一个执行环境,作为一个包含所有必需 Python 库的 Docker 容器,并在 Azure Machine Learning 的训练集群上运行管道步骤。
管道作为你的 Azure Machine Learning 工作空间中的一个实验运行。我们可以将管道作为创作脚本的一部分提交,将其部署为 Web 服务并通过 webhook 触发,将其作为已发布的管道进行安排,类似于 cron 作业,或者从第三方服务(如 Logic Apps)触发。
在许多情况下,运行线性顺序的管道已经足够好。然而,当数据量增加且管道步骤变得越来越慢时,我们需要找到一种方法来加速这些大型计算。加快数据转换、模型训练和评分的常见解决方案是通过并行化。因此,我们将向我们的数据转换管道添加一个并行执行步骤。
正如我们在本章的第一节中学到的,将 ML 工作流程解耦到管道中的主要原因是模块化和可重用性。通过将工作流程拆分为单个步骤,我们为常见 ML 任务的可重用计算块奠定了基础,无论是通过可视化和特征重要性进行数据分析,还是通过 NLP 和第三方数据进行特征工程,或者简单地评分常见的 ML 任务,如通过目标检测进行自动图像标记。
在 Azure Machine Learning 管道中,我们可以使用模块从管道创建可重用的计算步骤。模块是在管道步骤之上的一个管理层,它允许你轻松地对管道步骤进行版本控制、部署、加载和重用。这个概念与对 ML 项目中的源代码或数据集进行版本控制非常相似。
对于任何企业级 ML 工作流程,管道的使用是必不可少的。它不仅帮助你解耦、扩展、触发和重用单个计算步骤,而且还为你的端到端工作流程提供了可审计性和可监控性。此外,将计算块拆分为管道步骤将为你成功过渡到 MLOps——ML 项目的持续集成和持续部署(CI/CD)过程打下基础。
让我们开始并实现我们的第一个 Azure Machine Learning 管道。
创建一个简单的管道
Azure 机器学习管道是一系列可以并行或顺序执行的独立计算步骤。Azure 机器学习在管道之上提供了额外的功能,例如计算图的可视化、步骤之间的数据传输以及将管道作为端点或已发布管道发布。在本节中,我们将创建一个简单的管道步骤并执行管道以探索 Azure 机器学习管道的功能。
根据计算类型,您可以在不同的计算目标上安排作业,例如 Azure 机器学习、Azure Batch、Databricks、Azure Synapse 等,或者运行自动机器学习或HyperDrive实验。根据执行类型,您需要为每个步骤提供额外的配置。
让我们从只包含一个步骤的简单管道开始。我们将在后续章节中逐步添加更多功能和步骤。首先,我们需要定义我们的管道步骤的执行类型。虽然PipelineStep是管道中可以运行的任何执行的基类,但我们需要选择一个步骤实现。以下是在编写时可用的一些步骤:
-
AutoMLStep: 运行一个自动机器学习实验 -
AzureBatchStep: 在 Azure Batch 上运行一个脚本 -
DatabricksStep: 运行一个 Databricks 笔记本 -
DataTransferStep: 在 Azure 存储账户之间传输数据 -
HyperDriveStep: 运行一个 HyperDrive 实验 -
ModuleStep: 运行一个模块 -
MpiStep: 运行一个消息传递接口 (MPI)作业 -
ParallelRunStep: 并行运行一个脚本 -
PythonScriptStep: 运行一个 Python 脚本 -
RScriptStep: 运行一个 R 脚本 -
SynapseSparkStep: 在 Synapse 上运行一个 Spark 脚本 -
CommandStep: 运行一个脚本或命令 -
KustoStep: 在 Azure 数据探索器上运行一个 Kusto 查询
在我们的简单示例中,我们想在管道中运行一个单独的 Python 数据预处理脚本,因此我们将从前面列表中选择PythonScriptStep。我们正在构建与我们在前面的章节中看到的相同示例和代码示例。在这个第一个管道中,我们将执行一个步骤,该步骤将直接从脚本中加载数据——因此不需要将任何输入或输出传递给管道步骤。我们将在以下步骤中单独添加这些:
-
管道步骤都附加到 Azure 机器学习工作区。因此,我们首先加载工作区配置:
from azureml.core import Workspace ws = Workspace.from_config() -
接下来,我们需要一个计算目标,我们可以在其上执行我们的管道步骤。让我们创建一个自动扩展的 Azure 机器学习训练集群作为计算目标,类似于我们在前面的章节中创建的:
# Create or get training cluster aml_cluster = get_aml_cluster( ws, cluster_name="cpu-cluster") aml_cluster.wait_for_completion(show_output=True) -
此外,我们还需要一个运行配置,它定义了我们的训练环境和 Python 库:
run_conf = get_run_config(['numpy', 'pandas', 'scikit-learn', 'tensorflow']) -
现在,我们可以定义
PythonScriptStep,它为目标的机器学习训练脚本提供了所有必需的配置和入口点:from azureml.pipeline.steps import PythonScriptStep step = PythonScriptStep(name='Preprocessing', script_name="preprocess.py", source_directory="code", runconfig=run_conf, compute_target=aml_cluster)
如您在前面代码中所见,我们正在配置script_name和包含预处理脚本的source_directory参数。我们还传递了runconfig运行时配置和compute_target计算目标到PythonScriptStep。
-
如果您还记得前面的章节,我们之前将
ScriptRunConfig对象作为实验提交到 Azure 机器学习工作区。在管道的情况下,我们首先需要将管道步骤包装在Pipeline中,然后将管道作为实验提交。虽然一开始这似乎有些反直觉,但我们将看到我们如何参数化管道并添加更多的计算步骤。在下一个代码片段中,我们定义了管道:from azureml.pipeline.core import Pipeline pipeline = Pipeline(ws, steps=[step])
如您所见,管道通过一系列管道步骤简单地定义,并与工作区相关联。在我们的例子中,我们只定义了一个执行步骤。让我们也检查一下,我们是否在配置管道时没有犯任何错误,通过内置的管道验证:
pipeline.validate()
-
一旦管道成功验证,我们就可以准备执行了。可以通过将管道作为实验提交到 Azure 机器学习工作区来执行管道:
from azureml.core import Experiment exp = Experiment(ws, "azureml-pipeline") run = exp.submit(pipeline)
恭喜!您刚刚运行了第一个非常简单的 Azure 机器学习管道。
重要提示
您可以在官方 Azure 存储库中找到许多使用 Azure 机器学习管道的完整和最新示例:github.com/Azure/MachineLearningNotebooks/blob/master/how-to-use-azureml/machine-learning-pipelines。
一旦提交了管道,它将在管道部分以及实验部分中显示,如图 8.1 所示。管道被视为一个实验,其中每个管道运行就像一个实验运行。管道的每个步骤,以及其日志、图表和指标,都可以作为实验的子运行来访问:

图 8.1 – Azure 机器学习中的管道运行作为实验
虽然这个简单的管道在直接将脚本作为实验提交时并没有带来很多好处,但现在我们可以向管道中添加额外的步骤并配置数据输入和输出。让我们看看吧!
在步骤之间连接数据输入和输出
管道步骤是计算块,而管道定义了步骤执行的顺序。为了控制数据流,我们需要为管道定义输入和输出,并为单个步骤连接数据输入和输出。计算块之间的数据流最终将定义块的执行顺序,从而将一系列步骤转换为一个有向无环执行图。这正是我们将在本节中探讨的内容。
在大多数情况下,管道需要外部输入、各个块之间的连接以及持久化输出。在 Azure Machine Learning 管道中,我们将使用以下构建块来配置此数据流:
-
预持久化管道输入:
Dataset -
管道步骤之间的数据:
PipelineData -
持续管道输出:
PipelineData.as_dataset()
在本节中,我们将查看所有三种类型的数据输入和输出。首先,我们将查看如何将数据作为输入传递到管道中。
管道步骤的输入数据
让我们从向管道的第一步添加数据输入开始。为此 – 或者将任何预持久化数据传递给管道步骤 – 我们将使用数据集,这在第四章,数据摄取和管理数据集中我们已经看到。在 Azure Machine Learning 中,数据集是在指定路径上存储的指定编码数据的抽象引用。存储系统本身被抽象为数据存储对象,它是物理系统的引用,包含有关位置、协议和访问权限的信息。
如果您还记得前面的章节,我们可以通过简单地按名称引用来访问之前在 Azure Machine Learning 工作区中注册的数据集:
from azureml.core.dataset import Dataset
dataset = Dataset.get_by_name(ws, 'titanic')
当数据最初被组织并注册为数据集时,前面的代码非常方便。作为管道开发者,我们不需要知道底层的数据格式(例如,CSV、ZIP、Parquet 和 JSON),以及数据存储在哪个 Azure Blob 存储或 Azure SQL 数据库上。管道开发者可以消费指定的数据,并专注于预处理、特征工程和模型训练。
然而,当将新数据传递到 Azure Machine Learning 管道时,我们通常没有将数据注册为数据集。在这些情况下,我们可以创建一个新的数据集引用。以下是如何从公开数据创建Dataset的示例:
path ='https://...windows.net/demo/Titanic.csv'
dataset = Dataset.Tabular.from_delimited_files(path)
将文件和表格数据转换为Dataset有多种方式。虽然这看起来像是额外的复杂工作,而不是直接将绝对路径传递给管道,但遵循此约定将带来许多好处。最重要的是,Azure Machine Learning 工作区中的所有计算实例都将能够访问、读取和解析数据,而无需任何额外配置。此外,Azure Machine Learning 将引用和跟踪每个实验运行所使用的数据集。
一旦我们获得了Dataset的引用,我们就可以将数据集作为输入传递给管道步骤。当将数据集传递给计算步骤时,我们可以配置以下附加配置:
-
在脚本中对数据集引用的名称 –
as_named_input() -
FileDataset的访问类型 –as_download()或as_mount()
首先,我们将表格数据集配置为命名输入:
from azureml.core.dataset import Dataset
dataset = Dataset.get_by_name(ws, 'titanic')
data_in = dataset.as_named_input('titanic')
接下来,我们将使用 PythonScriptStep,这将允许我们将参数传递给管道步骤。我们需要将数据集传递给两个参数——作为管道脚本的参数以及作为步骤的输入依赖。前者将允许我们将数据集传递给 Python 脚本,而后者将跟踪数据集作为此管道步骤的依赖项:
from azureml.pipeline.steps import PythonScriptStep
step = PythonScriptStep(name='Preprocessing',
script_name="preprocess_input.py",
source_directory="code",
arguments=["--input", data_in],
inputs=[data_in],
runconfig=run_conf,
compute_target=aml_cluster)
如前述示例所示,我们可以将一个(或多个)数据集作为inputs参数传递给管道步骤,以及作为脚本的参数。为这个数据集指定一个特定的名称将帮助我们区分管道中的多个输入。我们将更新预处理脚本以从命令行参数解析数据集,如下面的代码片段所示:
preprocess_input.py
import argparse
from azureml.core import Run, Dataset
run = Run.get_context()
ws = run.experiment.workspace
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str)
args = parser.parse_args()
dataset = Dataset.get_by_id(ws, id=args.input)
df = dataset.to_pandas_dataframe()
如前述代码所示,数据集作为数据集名称传递给 Python 脚本。我们可以使用 Dataset API 在运行时检索数据。
一旦我们提交管道以执行,我们可以在 Azure Machine Learning Studio 界面中看到管道的可视化,如图 8.2 所示。我们可以看到数据集作为titanic命名的输入传递给预处理步骤:

图 8.2 – 数据集作为管道步骤输入
这是一种将功能块与其输入解耦的绝佳方式。我们将在下一节,通过模块化重用管道步骤中看到,如何将这些可重用块转换为共享模块。
重要提示
除了将数据集作为输入参数传递给管道步骤外,我们还可以使用运行上下文对象上的以下属性从运行上下文中访问命名输入——Run.get_context().input_datasets['titanic']。然而,将数据集设置为输入和输出参数可以更容易地在管道和其他实验中重用管道步骤和代码片段。
接下来,让我们了解如何设置单个管道步骤之间的数据流。
步骤间传递数据
当我们为管道步骤定义输入时,我们通常希望配置计算输出的输出。通过传递输入和输出定义,我们将管道步骤与预定义的数据存储分离,并避免在计算步骤中移动数据。
虽然预先持久化的输入被定义为Dataset对象,但管道步骤之间的数据连接(输入和输出)是通过PipelineData对象定义的。让我们看看一个PipelineData对象作为某个管道步骤的输出和另一个步骤的输入的示例:
from azureml.core import Datastore
from azureml.pipeline.core import PipelineData
datastore = Datastore.get(ws, datastore_name="mldata")
data_train = PipelineData('train', datastore=datastore)
data_test = PipelineData('test', datastore=datastore)
与前一个示例类似,我们将数据集作为参数传递,并作为outputs引用它们。前者将允许我们在脚本中检索数据集,而后者定义了步骤依赖项:
from azureml.pipeline.steps import PythonScriptStep
step_1 = PythonScriptStep(name='Preprocessing',
script_name= \
"preprocess_output.py",
source_directory="code",
arguments=[
"--input", data_in,
"--out-train", data_train,
"--out-test", data_test],
inputs=[data_in],
outputs=[data_train, data_test],
runconfig=run_conf,
compute_target=aml_cluster)
一旦我们将预期的输出路径传递给评分文件,我们需要解析命令行参数以检索路径。评分文件看起来如下,以便读取输出路径并将 pandas DataFrame 输出到期望的位置。我们首先需要在训练脚本中解析命令行参数:
preprocess_output.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=str)
parser.add_argument("--out-train", type=str)
parser.add_argument("--out-test", type=str)
args = parser.parse_args()
PipelineData参数在运行时被解释,并用挂载的数据集目录的本地路径替换。因此,我们可以简单地写入这个本地目录,数据将被自动注册到数据集中:
preprocess_output.py
import os
out_train = args.out_train
os.makedirs(os.path.dirname(out_train), exist_ok=True)
out_test = args.out_test
os.makedirs(os.path.dirname(out_test), exist_ok=True)
df_train, df_test = preprocess(...)
df_train.to_csv(out_train)
df_test.to_csv(out_test)
一旦我们将数据输出到PipelineData数据集,我们就可以将这些数据集传递给下一个管道步骤。传递数据集的方式与我们之前看到的完全相同 – 我们将它们作为参数传递并注册为inputs:
from azureml.pipeline.steps import PythonScriptStep
step_2 = PythonScriptStep(name='Training',
script_name="train.py",
source_directory="code",
arguments=[
"--in-train", data_train,
"--in-test", data_test],
inputs=[data_train, data_test],
runconfig=run_conf,
compute_target=aml_cluster)
现在,我们可以在训练脚本中加载数据。如果你还记得前一个步骤,PipelineData在本地执行环境中被解释为路径。因此,我们可以从命令行参数中解释的路径读取数据:
train.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--in-train", type=str)
parser.add_argument("--in-test", type=str)
args = parser.parse_args()
...
df_train = pd.read_csv(args.in_train)
df_test = pd.read_csv(args.in_test)
最后,我们可以通过使用steps关键字传递步骤来将这两个步骤包装成一个Pipeline对象。这个pipeline对象可以被传递给 Azure Machine Learning 作为一个实验:
from azureml.pipeline.core import Pipeline
pipeline = Pipeline(ws, steps=[step_1, step_2])
如前一个示例所示,我们可以从命令行参数中读取输出路径,并在 Python 脚本中将其用作标准文件路径。因此,我们需要确保文件路径存在,并将一些表格数据输出到该位置。接下来,我们定义第二个验证步骤的输入,该步骤读取新创建的数据:

图 8.3 – 在管道步骤间传递数据
最后,我们将探讨如何将管道步骤的输出持久化以供管道外使用。
持久化数据输出
在本节的最后,我们将学习如何持久化管道的输出数据。对于管道来说,一个常见的任务是构建数据转换 – 因此我们通常期望管道输出数据。
在前一节中,我们学习了如何使用PipelineData从管道步骤创建输出,主要是为了将这些输出连接到后续步骤的输入。我们可以使用相同的方法来定义管道的最终持久化输出。
一旦你理解了如何创建、持久化和版本化数据集,这样做就非常简单了。原因是我们可以使用as_dataset()方法将PipelineData对象转换为数据集。一旦我们有了Dataset对象的引用,我们就可以继续将其导出到特定的数据存储或在工作区中将其注册为数据集。
这里是一个如何将作为管道步骤输出的PipelineData对象转换为数据集并在 Azure Machine Learning 工作区中注册的示例:
from azureml.data import OutputFileDatasetConfig
data_out = OutputFileDatasetConfig(name="predictions",
destination=(datastore, 'titanic/predictions'))
通过调用前面的作者代码,您将能够将结果预测作为数据集访问任何与您的工作空间连接的计算实例:

图 8.4 – 作为管道步骤输出的数据集
接下来,我们将探讨触发管道执行的不同方法。
发布、触发和安排管道
在您创建了第一个简单的管道之后,您有多种运行管道的方式。我们已经看到的一个例子是将管道作为实验提交给 Azure Machine Learning。这将简单地从配置管道的同一作者脚本中执行管道。虽然这最初执行管道是一个好的开始,但还有其他触发、参数化和执行管道的方法。
执行管道的常见方法如下:
-
将管道发布为 Web 服务。
-
使用 webhook 触发已发布的管道。
-
安排管道定期运行。
在本节中,我们将探讨所有三种方法,以帮助您轻松触发和执行您的管道。让我们首先从将管道作为 Web 服务发布和版本化开始。
将管道发布为 Web 服务
将机器学习工作流程拆分成可重用管道的常见原因是你可以根据需要对其进行参数化和触发,以执行各种任务。好的例子包括常见的预处理任务、特征工程步骤和批量评分。
因此,将管道转变为可参数化的 Web 服务,我们可以从任何其他应用程序中触发它,这是一种很好的部署我们的机器学习工作流程的方式。让我们开始,并将之前构建的管道作为 Web 服务进行打包和部署。
由于我们希望我们的已发布管道可以通过 HTTP 参数进行配置,我们需要首先创建这些参数引用。让我们创建一个参数来控制训练管道的学习率:
from azureml.pipeline.core.graph import PipelineParameter
lr_param = PipelineParameter(name="lr_arg",
default_value=0.01)
接下来,我们将通过将参数作为训练脚本的参数传递来将管道参数与管道步骤链接起来。我们扩展了上一节中的步骤:
data = mnist_dataset.as_named_input('mnist').as_mount()
args = ["--in-train", data, "--learning-rate", lr_param]
step = PythonScriptStep(name='Training',
script_name="train.py",
source_directory="code",
arguments=args,
inputs=[data_train],
runconfig=run_conf,
compute_target=aml_cluster)
arguments=args ,
estimator=estimator,
compute_target=cpu_cluster)
在前面的示例中,我们将学习率作为一个参数添加到了命令行参数列表中。在训练脚本中,我们可以解析命令行参数并读取参数:
score.py
parser = argparse.ArgumentParser()
parser.add_argument('--learning-rate', type=float,
dest='lr')
args = parser.parse_args()
# print learning rate
print(args.lr)
现在,唯一剩下的步骤就是发布管道。为此,我们创建一个管道并调用publish()方法。我们需要为管道传递一个名称和版本,这样它现在将是一个版本化的已发布管道:
pipeline = Pipeline(ws, steps=[step])
service = pipeline.publish(name="CNN_Train_Service",
version="1.0")
service_id = service.id
service_endpoint = service.endpoint
这就是您需要公开的代码,以将管道作为具有身份验证的参数化 Web 服务。如果您想将您的已发布管道从特定的端点抽象出来——例如,在迭代管道的开发过程的同时,让其他团队将 Web 服务集成到他们的应用程序中——您还可以部署管道 webhooks 作为端点。
让我们看看一个示例,其中我们使用先前创建的管道服务并通过单独的端点公开它:
from azureml.pipeline.core import PipelineEndpoint
application = PipelineEndpoint.publish(ws,
pipeline=service,
name="CNN_Train_Endpoint")
service_id = application.id
service_endpoint = application.endpoint
我们已经部署并解耦了管道和管道端点。我们最终可以通过服务端点调用和触发端点。让我们在下一节中看看这个例子。
使用 webhook 触发已发布的管道
已发布的管道 web 服务需要认证。因此,在我们调用 web 服务之前,让我们首先检索一个 Azure Active Directory 令牌:
from azureml.core.authentication import AzureCliAuthentication
cli_auth = AzureCliAuthentication()
aad_token = cli_auth.get_authentication_header()
使用认证令牌,我们现在可以通过调用服务端点来触发和参数化管道。让我们通过使用requests库来查看一个示例。我们可以通过在上一节中定义的lr_arg参数配置学习率,并通过发送自定义 JSON 体来设置实验名称。如果你还记得,管道仍然会在你的 Azure 机器学习工作区中以实验的形式运行:
import requests
response = requests.post(service_endpoint,
headers=aad_token,
json={"ExperimentName": "mnist-train",
"ParameterAssignments": {"lr_arg": 0.05}})
在前面的代码片段中,我们可以看到我们使用POST请求调用管道 webhook,并通过发送自定义 JSON 体来配置管道运行。对于认证,我们还需要通过 HTTP 头传递认证信息。
在这个例子中,我们使用 Python 脚本来触发 web 服务端点。然而,你现在可以通过 webhook 使用任何其他 Azure 服务来触发此管道,例如 Azure Logic Apps、Azure DevOps 中的 CI/CD 管道或任何其他自定义应用程序。如果你希望你的管道定期运行而不是手动触发,你可以设置管道计划。让我们在下一节中看看这个例子。
安排已发布的管道
在构建管道时,为工作流设置连续触发器是一个常见用例。这些触发器可以在每周或每天运行管道并重新训练模型,如果可用新数据。Azure 机器学习管道支持两种类型的调度技术——通过预定义频率的连续调度,以及通过轮询间隔的响应式调度和数据变更检测。在本节中,我们将探讨这两种方法。
在我们开始安排管道之前,我们将首先探索一种列出工作区中所有先前定义的管道的方法。为此,我们可以使用PublishedPipeline.list()方法,类似于我们的 Azure 机器学习工作区资源中的list()方法。让我们打印工作区中每个已发布管道的名称和 ID:
from azureml.pipeline.core import PublishedPipeline
for pipeline in PublishedPipeline.list(ws):
print("name: %s, id: %s" % (pipeline.name, pipeline.id))
要为已发布的管道设置计划,我们需要将管道 ID 作为参数传递。我们可以从前面的代码片段中检索所需的管道 ID 并将其插入到计划声明中。
首先,我们将探讨连续计划,这些计划会以预定义的频率重新触发管道,类似于 cron 作业。为了定义计划频率,我们需要创建一个ScheduleRecurrence对象。以下是一个创建重复计划的示例片段:
from azureml.pipeline.core.schedule import \
ScheduleRecurrence, Schedule
recurrence = ScheduleRecurrence(frequency="Minute",
interval=15)
schedule = Schedule.create(ws,
name="CNN_Train_Schedule",
pipeline_id=pipeline_id,
experiment_name="mnist-train",
recurrence=recurrence,
pipeline_parameters={})
上述代码就是设置一个持续触发你的管道的重复调度的全部所需。管道将在你的 Azure Machine Learning 工作区中定义的实验中运行。使用pipeline_parameters参数,你可以将额外的参数传递给管道运行。
Azure Machine Learning 管道还支持另一种类型的重复调度,即轮询数据存储库中的更改。这种类型的调度被称为反应式调度,需要连接到数据存储库。它将在你的数据存储库中的数据更改时触发你的管道。以下是一个设置反应式调度的示例:
from azureml.core.datastore import Datastore
# use default datastore 'ws.get_default_datastore()'
# or load a custom registered datastore
datastore = Datastore.get(workspace, 'mldemodatastore')
# 5 min polling interval
polling_interval = 5
schedule = Schedule.create(
ws, name="CNN_Train_OnChange",
pipeline_id=pipeline_id,
experiment_name="mnist-train",
datastore=datastore,
data_path_parameter_name="mnist"
polling_interval=polling_interval,
pipeline_parameters={})
如此例所示,我们使用数据存储库引用和分钟级的轮询间隔来设置反应式调度。因此,调度将检查每个轮询间隔,以查看是否有任何 blob 已更改,并使用这些更改来触发管道。blob 名称将通过data_path_parameter_name参数传递给管道。类似于之前的调度,你也可以使用pipeline_parameters参数将额外的参数发送到管道。
最后,让我们看看如何一旦启用就程序性地停止一个调度。为此,我们需要一个调度对象的引用。我们可以通过获取特定工作区的调度来获取这个引用,类似于 Azure Machine Learning 中的任何其他资源:
for schedule in Schedule.list(ws):
print(schedule.id)
我们可以使用调度对象上所有可用的属性来过滤这个列表。一旦我们找到了所需的调度,我们只需简单地禁用它:
schedule.disable(wait_for_provisioning=True)
使用额外的wait_for_provisioning参数,我们确保在调度真正禁用之前阻塞代码执行。你可以使用Schedule.enable方法轻松重新启用调度。现在,你可以创建重复和反应式调度,持续运行你的 Azure Machine Learning 管道,并在不再需要时禁用它们。接下来,我们将看看如何并行化执行步骤。
并行化步骤以加快大型管道
在许多情况下,随着时间的推移,管道不可避免地会处理越来越多的数据。为了并行化管道,你可以并行或顺序运行管道步骤,或者通过使用ParallelRunConfig和ParallelRunStep来并行化单个管道步骤的计算。
在我们深入讨论单个步骤执行的并行化之前,让我们首先讨论简单管道的控制流。我们将从一个使用多个步骤构建的简单管道开始,如下面的示例所示:
pipeline = Pipeline(ws, steps=[step1, step2, step3, step4])
当我们提交此管道时,这四个步骤将如何执行——是顺序执行、并行执行,还是甚至是无定义的顺序?为了回答这个问题,我们需要查看各个步骤的定义。如果所有步骤都是独立的,并且每个步骤的计算目标足够大,则所有步骤都会并行执行。然而,如果我们定义PipelineData为step1的输出并将其输入到其他步骤中,则这些步骤只有在step1完成后才会执行:

图 8.5 – 带有并行步骤的管道
管道步骤之间的数据连接隐式定义了步骤的执行顺序。如果没有步骤之间存在依赖关系,则所有步骤都会并行安排。
上述说法有一个例外,即在没有专用数据对象作为依赖项的情况下强制执行管道步骤的特定执行顺序。为了做到这一点,你可以手动定义这些依赖项,如下面的代码片段所示:
step3.run_after(step2)
step4.run_after(step3)
上述配置将首先并行执行step1和step2,然后再安排step3,这要归功于你明确配置的依赖项。这在当你访问 Azure 机器学习工作区外的资源中的状态或数据时非常有用;因此,管道不能隐式创建依赖项:

图 8.6 – 带有自定义步骤顺序的管道
一旦我们解决了步骤执行顺序的问题,我们想要了解如何并行执行单个步骤,而不是多个步骤。这个用例非常适合批量评分大量数据。你不想将输入数据分割成多个步骤的输入,而是希望将数据作为单个步骤的输入。然而,为了加快评分过程,你希望对单个步骤的评分进行并行执行。
在 Azure 机器学习管道中,你可以使用ParallelRunStep步骤来配置单个步骤的并行执行。为了配置数据分区和计算的并行化,你需要创建一个ParallelRunConfig对象。并行运行步骤是一个很好的选择,适用于任何类型的并行化计算,帮助我们将输入数据分割成更小的数据分区(也称为批量或小批量)。让我们通过一个示例来了解如何为单个管道步骤设置并行执行。我们将配置批量大小作为管道参数,该参数可以在调用管道步骤时设置:
from azureml.pipeline.core import PipelineParameter
from azureml.pipeline.steps import ParallelRunConfig
parallel_run_config = ParallelRunConfig(
entry_script='score.py',
mini_batch_size=PipelineParameter(
name="batch_size",
default_value="10"),
output_action="append_row",
append_row_file_name="parallel_run_step.txt",
environment=batch_env,
compute_target=cpu_cluster,
process_count_per_node=2,
node_count=2)
上述代码片段定义了通过将输入分割成小批量来并行化计算的运行配置。我们将批量大小配置为管道参数batch_size。我们还通过node_count和process_count_per_node参数配置计算目标和并行性。使用这些设置,我们可以并行评分四个小批量。
score.py 脚本是一个部署文件,需要包含一个 init() 和 run(batch) 方法。batch 参数包含一个文件名列表,这些文件将从步骤配置的输入参数中提取出来。我们将在 第十一章,超参数调整和自动化机器学习 中了解更多关于此文件结构的信息。
score.py 脚本中的 run 方法应返回评分结果或将数据写入外部数据存储。根据这一点,output_action 参数需要设置为 append_row,这意味着所有值都将收集到一个结果文件中,或者设置为 summary_only,这意味着用户将负责存储结果。您可以使用 append_row_file_name 参数定义所有行都将附加到的结果文件。
如您所见,设置并行批量执行的运行配置并不简单,需要一些调整。然而,一旦设置并正确配置,它就可以用来扩展计算步骤并并行运行多个任务。因此,我们现在可以定义具有所有必需输入和输出的 ParallelRunStep:
from azureml.pipeline.steps import ParallelRunStep
from azureml.core.dataset import Dataset
parallelrun_step = ParallelRunStep(
name="ScoreParallel",
parallel_run_config=parallel_run_config,
inputs=[Dataset.get_by_name(ws, 'mnist')],
output=PipelineData('mnist_results',
datastore=datastore),
allow_reuse=True)
如您所见,我们从引用数据存储中所有文件的输入数据集中读取。我们将结果写入我们自定义数据存储中的 mnist_results 文件夹。最后,我们可以开始运行并查看结果。为此,我们将管道作为实验运行提交到 Azure Machine Learning:
from azureml.pipeline.core import Pipeline
pipeline = Pipeline(workspace=ws, steps=[parallelrun_step])
run = exp.submit(pipeline)
将步骤执行拆分为多个分区将有助于您加快大量数据的计算速度。一旦计算时间显著长于在计算目标上调度步骤执行的开销,这种方法就会带来回报。因此,ParallelRunStep 是加快您管道的好选择,只需对您的管道配置进行少量更改。接下来,我们将探讨更好的模块化和管道步骤的可重用性。
通过模块化重用管道步骤
通过将您的流程拆分为管道步骤,您正在为可重用的 ML 和数据处理构建块奠定基础。然而,而不是将您的管道、管道步骤和代码复制粘贴到其他项目中,您可能希望将您的功能抽象为功能性的高级模块。
让我们来看一个例子。假设您正在构建一个管道步骤,该步骤接受用户和项目评分的数据集,并为每个用户输出前五个项目的推荐。然而,当您正在微调推荐引擎时,您希望允许您的同事将此功能集成到他们的管道中。一种很好的方法是将代码的实现和使用分离,定义输入和输出数据格式,并对其进行模块化和版本控制。这正是模块在 Azure Machine Learning 管道步骤中的作用。
让我们创建一个模块,这个容器将包含对计算步骤的引用:
from azureml.pipeline.core.module import Module
module = Module.create(ws,
name="TopItemRecommender",
description="Recommend top 5 items")
接下来,我们使用InputPortDef和OutputPortDef绑定来定义模块的输入和输出。这些是输入和输出引用,稍后需要将它们绑定到数据引用。我们使用这些绑定来抽象化所有的输入和输出:
from azureml.pipeline.core.graph import \
InputPortDef, OutputPortDef
in1 = InputPortDef(name="in1",
default_datastore_mode="mount",
default_data_reference_name = \
datastore.name,
label="Ratings")
out1 = OutputPortDef(name="out1",
default_datastore_mode="mount",
default_datastore_name=datastore.name,
label="Recommendation")
最后,我们可以通过发布此模块的 Python 脚本来定义模块功能:
module.publish_python_script("train.py",
source_directory="./rec",
params={"numTraits": 5},
inputs=[in1],
outputs=[out1],
version="1",
is_default=True)
这就是您需要做的所有事情,以便其他人可以在他们的 Azure 机器学习管道中重用您的推荐块。通过使用版本控制和默认版本,您可以确保用户拉取的确切代码。正如我们所看到的,您可以为每个模块定义多个输入和输出,并为该模块定义可配置参数。除了以 Python 代码发布功能外,我们还可以发布 Azure Data Lake Analytics 或 Azure 批处理步骤。
接下来,我们将探讨如何将模块集成到 Azure 机器学习管道中,并与自定义步骤一起执行。为此,我们将首先使用以下命令加载之前创建的模块:
from azureml.pipeline.core.module import Module
module = Module.get(ws, name="TopItemRecommender")
现在的伟大之处在于,前面的代码将在任何具有访问您的 Azure 机器学习工作区权限的 Python 解释器或执行引擎中工作。这是一个巨大的进步——无需复制代码,无需检查依赖项,也无需为您的应用程序定义任何额外的访问权限——一切都与工作区集成在一起。
首先,我们需要编写这个管道步骤的输入和输出。让我们将管道的输入直接传递到推荐模块,并将所有输出传递到管道输出:
from azureml.pipeline.core import PipelineData
in1 = PipelineData("in1",
datastore=datastore,
output_mode="mount",
is_directory=False)
out1 = PipelineData("out1",
datastore=datastore,
output_mode="mount",
is_directory=False)
input_wiring = {"in1": in1}
output_wiring = {"out1": out1}
现在,我们使用管道参数来参数化模块。这使得我们可以在管道中配置一个参数,并将其传递到推荐模块。此外,我们还可以为在管道中使用时定义该参数的默认值:
from azureml.pipeline.core import PipelineParameter
num_traits = PipelineParameter(name="numTraits",
default_value=5)
我们已经定义了此管道的输入和输出,以及管道步骤的输入参数。我们唯一缺少的是将所有这些整合起来并定义一个管道步骤。类似于前一个章节,我们可以定义一个将执行模块化推荐块的管道步骤。为此,我们不再使用PythonScriptStep,而是现在使用ModuleStep:
from azureml.core import RunConfiguration
from azureml.pipeline.steps import ModuleStep
step = ModuleStep(module= module,
version="1",
runconfig=RunConfiguration(),
compute_target=aml_compute,
inputs_map=input_wiring,
outputs_map=output_wiring,
arguments=[
"--output_sum", first_sum,
"--output_product", first_prod,
"--num-traits", num_traits])
最后,我们可以通过将管道作为实验提交到我们的 Azure 机器学习工作区来执行管道。此代码与之前章节中看到的内容非常相似:
from azureml.core import Experiment
from azureml.pipeline.core import Pipeline
pipeline = Pipeline(ws, steps=[step])
exp = Experiment(ws, "item-recommendation")
run = exp.submit(pipeline)
前一个步骤在您的 Azure 机器学习工作区中以实验的形式执行模块化管道。然而,您也可以选择之前章节中讨论的任何其他发布方法,例如作为 Web 服务发布或安排管道。
当与多个团队在同一 ML 项目上工作时,将管道步骤拆分为可重用模块非常有帮助。所有团队都可以并行工作,并且结果可以轻松地集成到单个 Azure 机器学习工作区中。让我们看看 Azure 机器学习管道如何与其他 Azure 服务集成。
将管道与其他 Azure 服务集成
用户仅使用单个服务来管理云中的数据流、实验、训练、部署和 CI/CD 是很少见的。其他服务提供特定的功能,使它们更适合特定任务,例如,Azure Data Factory 用于将数据加载到 Azure 中,Azure Pipelines 用于在 Azure DevOps 中运行自动化任务。
选择云提供商的最有力的论据是其个别服务的强大集成。在本节中,我们将看到 Azure 机器学习管道如何与其他 Azure 服务集成。如果我们要涵盖所有可能的集成服务,这个列表将会很长。正如我们在本章中学到的,您可以通过调用 REST 端点并使用标准 Python 代码提交管道来触发发布的管道。这意味着您可以在任何可以调用 HTTP 端点或运行 Python 代码的地方集成管道。
我们首先将探讨与 Azure 机器学习设计师的集成。设计师允许您通过拖放界面构建管道,这些管道、发布的管道和管道运行将像我们在本章中构建的任何其他管道一样显示在工作区中。因此,快速查看共同点和差异是实用的。
接下来,我们将快速查看 Azure 机器学习管道与 Azure Data Factory 的集成,这可能是最常用的集成之一。将 ML 管道与 ETL 管道一起包含,用于在 ETL 过程中对数据进行评分、丰富或增强,这是一种非常自然的本能。
最后,我们将比较 Azure 机器学习管道与 Azure DevOps 中的 Azure Pipelines 用于 CI/CD。虽然 Azure DevOps 主要用于应用程序代码和应用程序编排,但它现在正在过渡到提供完整的端到端 MLOps 工作流程。让我们从设计师开始,直接进入正题。
使用 Azure 机器学习设计师构建管道
Azure 机器学习设计师是一个图形界面,通过拖放界面创建复杂的 ML 管道。您可以选择表示为块的函数来导入数据,这些块将使用底层的存储库和数据集。
下图显示了一个简单的管道,用于训练和评分一个提升决策树回归模型。如您所见,基于块的编程风格需要较少的关于单个块的知识,并且它允许您在不编写任何代码的情况下构建复杂的管道:

图 8.7 – Azure Machine Learning 设计器管道
一些操作,例如将一个计算的结果连接到下一个计算的输入,在视觉 UI 中创建可能比使用代码更方便。通过可视化管道也更容易理解数据流。其他操作,例如创建大型数据批次的并行执行,在代码中处理和维护可能更容易一些。然而,由于我们首先使用代码的方法来保证可重复性、可测试性和版本控制,我们通常更倾向于使用代码进行编写和执行。
值得注意的是,设计器中的管道和代码中使用的管道的功能并不相同。虽然你有一系列预先配置的抽象功能块,例如之前图 8.7中的Boosted Decision Tree Regression块,但你无法在代码中访问这些功能。然而,你可以使用 scikit-learn、PyTorch、TensorFlow 等来重用现有功能或在代码中构建自己的功能。
由于设计器与工作空间的一级集成,你可以在设计器内部访问工作空间中的所有文件、模型和数据集。一个重要的启示是,在工作空间中创建的所有资源,如管道、发布的管道、实时端点、模型、数据集等,都存储在公共系统中——无论它们是在哪里创建的。
Azure Data Factory 中的 Azure Machine Learning 管道
在移动数据、ETL 以及触发各种 Azure 服务中的计算时,你很可能会遇到Azure Data Factory。这是一个非常流行的服务,可以将大量数据移动到 Azure,执行处理和转换,构建工作流,并触发许多其他 Azure 或第三方服务。
Azure Machine Learning 管道与 Azure Data Factory 集成得非常好,你可以轻松配置并通过 Data Factory 触发已发布的管道的执行。为此,你需要将ML Execute Pipeline活动拖放到你的 Data Factory 画布中,并指定已发布管道的管道 ID。此外,你还可以指定管道参数以及管道运行的实验名称。
下图显示了如何在 Azure Data Factory 中配置ML Execute Pipeline步骤。它使用链接服务连接到你的 Azure Machine Learning 工作空间,这允许你从下拉框中选择所需的管道:

图 8.6 – Azure Data Factory 与 Azure Machine Learning 活动
如果你正在使用 JSON 配置计算步骤,你可以使用以下片段创建一个与 Azure Machine Learning 作为链接服务的ML Execute Pipeline活动。同样,你必须指定管道 ID,并可以传递实验名称以及管道参数:
{
"name": "Machine Learning Execute Pipeline",
"type": "AzureMLExecutePipeline",
"linkedServiceName": {
"referenceName": "AzureMLService",
"type": "LinkedServiceReference"
},
"typeProperties": {
"mlPipelineId": "<insert pipeline id>",
"experimentName": "data-factory-pipeline",
"mlPipelineParameters": {
"batch_size": "10"
}
}
}
最后,您可以通过添加触发器或输出到ML 执行管道活动来触发步骤。这将最终触发您已发布的 Azure Machine Learning 管道,并在工作区中开始执行。这是一个很好的补充,使得其他团队在经典的 ETL 和数据转换过程中重用您的 ML 管道变得容易。
Azure Pipelines for CI/CD
Azure Pipelines 是 Azure DevOps 的一个功能,允许您作为一个持续集成(CI)和持续部署(CD)过程来运行、构建、测试和部署代码。因此,它们是具有许多高级功能(如审批队列和门控阶段)的灵活的代码和应用程序编排管道。
通过允许您运行多个代码块,将 Azure Machine Learning 集成到 Azure DevOps 的最佳方式是使用 Python 脚本块。如果您已经按照这本书的内容,并使用以代码优先的方法来编写您的实验和管道,那么这种集成非常简单。让我们来看一个小例子。
首先,让我们编写一个实用函数,该函数根据工作区和管道 ID 参数返回一个已发布的管道。在这个例子中,我们需要这个函数:
def get_pipeline(workspace, pipeline_id):
for pipeline in PublishedPipeline.list(workspace):
if pipeline.id == pipeline_id:
return pipeline
return None
接下来,我们可以继续实现一个非常简单的 Python 脚本,允许我们在 Azure 中配置和触发管道运行。我们将初始化工作区,检索已发布的管道,并将管道作为实验提交到 Azure Machine Learning 工作区。这一切都是可配置的,而且只需要几行代码:
ws = Workspace.get(
name=os.environ.get("WORKSPACE_NAME"),
subscription_id=os.environ.get("SUBSCRIPTION_ID"),
resource_group=os.environ.get("RESOURCE_GROUP"))
pipeline = get_pipeline(args.pipeline_id)
pipeline_parameters = args.pipeline_parameters
exp = Experiment(ws, name=args.experiment_name)
run = exp.submit(pipeline,
pipeline_parameters=pipeline_parameters)
print("Pipeline run initiated %s" % run.id)
上述代码展示了我们如何将管道触发器集成到 Azure 管道中进行 CI/CD。我们可以看到,一旦工作区初始化完成,代码将遵循与从本地开发环境提交已发布的管道完全相同的模式。此外,我们还可以通过环境变量和命令行参数来配置管道运行。我们将在第十六章中看到这一功能的应用,使用 MLOps 将模型投入生产。
摘要
在本章中,您学习了如何使用和配置 Azure Machine Learning 管道,通过管道和管道步骤将 ML 工作流程拆分为多个步骤,用于估计量、Python 执行和并行执行。您使用 Dataset 和 PipelineData 配置了管道输入和输出,并成功控制了管道的执行流程。
作为另一个里程碑,您将管道作为 PublishedPipeline 部署到了 HTTP 端点。这允许您通过简单的 HTTP 调用来配置和触发管道执行。接下来,您实现了基于时间频率的自动调度,以及基于底层数据集变化的反应式调度。现在,当输入数据发生变化时,管道可以自动重新运行工作流程,无需任何手动交互。
最后,我们还模块化和版本化了管道步骤,以便在其他项目中重用。我们使用了InputPortDef和OutputPortDef来为数据源和汇点创建虚拟绑定。在最后一步,我们探讨了将管道集成到其他 Azure 服务中,例如 Azure 机器学习设计器、Azure 数据工厂和 Azure DevOps。
在下一章中,我们将探讨在 Azure 中使用基于决策树集成模型构建机器学习模型。
第三部分:机器学习模型的训练和优化
在本节中,我们将学习如何在 Azure 上训练和优化传统的机器学习(ML)模型以及深度学习模型。首先,我们将探讨传统集成技术的优缺点以及它们与基于新神经网络的模型之间的差异。然后,我们将利用 Azure 机器学习服务的功能在 Azure 上实现和训练卷积神经网络(CNNs)。随后,我们将探讨通过超参数调整和自动化机器学习来优化模型训练的方法。此外,我们还将探讨如何在分布式集群上而不是单个计算实例上运行 ML 训练。获得这些知识后,我们将通过在云中构建推荐引擎来结束本节。
本节包括以下章节:
-
第九章, 使用 Azure 机器学习构建 ML 模型
-
第十章, 在 Azure 上训练深度神经网络
-
第十一章, 超参数调整和自动化机器学习
-
第十二章, Azure 上的分布式机器学习
-
第十三章, 在 Azure 中构建推荐引擎
第九章:第九章:使用 Azure Machine Learning 构建 ML 模型
在前面的章节中,我们学习了 Azure Machine Learning 中的数据集、预处理、特征提取和管道。在本章中,我们将利用我们迄今为止所获得的知识来创建和训练一个强大的基于树的集成分类器。
首先,我们将深入了解流行的集成分类器,如随机森林、XGBoost和LightGBM的幕后场景。这些分类器在实际的现实中表现极为出色,并且它们在底层都是基于决策树。通过了解它们的主要优势,你将能够轻松发现可以用集成决策树分类器解决的问题。
我们还将学习梯度提升和随机森林之间的区别,以及是什么使得这些树集成对实际应用有用。这两种技术都有助于克服决策树的主要弱点,并且可以应用于许多不同的分类和回归问题。
最后,我们将使用我们迄今为止所学的所有技术在一个样本数据集上训练一个 LightGBM 分类器。我们将编写一个训练脚本,该脚本可以自动记录所有参数、评估指标和图形,并且可以通过命令行参数进行配置。我们将计划在 Azure Machine Learning 训练集群上运行训练脚本。
在本章中,我们将涵盖以下主题:
-
使用基于树的集成分类器
-
使用 LightGBM 训练集成分类器模型
技术要求
在本章中,我们将使用以下 Python 库和版本来创建基于决策树的集成分类器:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
lightgbm 3.2.1 -
numpy 1.19.5 -
pandas 1.3.2 -
scikit-learn 0.24.2 -
seaborn 0.11.2 -
matplotlib 3.4.3
与前面的章节类似,你可以使用本地 Python 解释器或 Azure Machine Learning 托管的工作簿环境来执行此代码。
本章中的所有代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter09。
使用基于树的集成分类器
监督的基于树的集成分类和回归技术在近年来在许多实际应用中证明非常成功。因此,它们今天在包括欺诈检测、推荐引擎、标签引擎等多个应用中得到了广泛的使用。你所有的移动和桌面操作系统、Office 程序以及音频或视频流服务每天都在大量使用它们。
因此,在本节中,我们将深入了解它们受欢迎和性能的主要原因,包括训练和评分。如果您是传统机器学习算法的专家,并且了解提升和袋装之间的区别,您可以直接跳到下一节,使用 LightGBM 训练集成分类器模型,在那里我们将理论付诸实践。
我们首先将探讨决策树,这是一种非常简单的技术,已有数十年历史。我们鼓励您即使跟随简单的步骤,因为它们构成了今天最先进经典监督机器学习方法的基石。我们还将详细探讨基于树的分类器的优势,以帮助您理解经典方法与基于深度学习的机器学习模型之间的差异。
单个决策树也存在许多缺点,因此它仅用于集成模型,而从不作为单独的模型使用。我们将在本节稍后部分更详细地探讨单个决策树的缺点。之后,我们将发现将多个弱单个树组合成一个强大的集成分类器的方法,这种分类器建立在基于树的方法的优势之上,并将它们转化为今天所拥有的——几乎集成到每个现成机器学习平台中的强大多用途监督机器学习模型。
理解简单的决策树
让我们先讨论一下if/else语句。这个函数可以是连续回归函数或决策边界函数。因此,像许多其他机器学习方法一样,决策树可以用于学习回归和分类问题。
从前面的描述中,我们可以立即发现决策树的一些重要优势:
-
一种是灵活性,可以在不同的数据分布、数据类型(例如,数值和分类数据)以及机器学习问题(如分类或回归)上工作。
-
另一个优势,也是它们与更复杂模型竞争的原因之一,是它们的可解释性。基于树的模型和集成可以可视化,甚至可以打印在纸上以解释预测的决策(输出)。
-
第三大优势在于它们在训练性能、模型大小和有效性方面的实际应用。将预训练的决策树集成到桌面、Web 或移动应用程序中,比深度学习方法要简单得多,也快得多。
重要提示
请注意,我们并不打算将基于树的方法作为解决每个机器学习问题的解决方案,并贬低深度学习方法的重要性。我们更希望让您在本章中意识到传统方法的优点,以便您可以为您的特定问题评估正确的方案。
下图展示了一个用于判断一个人是否健康的决策树示例:

图 9.1 – 一个简单的决策树
图 9.1是一个训练好的决策树的例子,我们可以通过简单地遍历每个节点并到达树的叶节点来对模型进行评分。
决策树的优点
基于决策树的机器学习模型因其处理现实世界应用中的数据时的优势而极为流行,这些数据形式多样、形状各异,且杂乱、有偏见和不完整。以下是决策树的关键优点:
-
它们支持广泛的应用。
-
它们需要很少的数据准备。
-
它们使模型的可解释性成为可能。
-
它们提供快速训练和快速评分。
首先,让我们关注决策树的灵活性,这是它们与其他许多经典/统计机器学习方法的重大优势之一。尽管其通用框架非常灵活,支持分类和回归,以及多输出问题,但它之所以受到广泛欢迎,是因为它能够直接处理数值和分类数据。多亏了嵌套的if-else树,它还可以处理名义类别以及数据中的 NULL 或缺失值。决策树之所以受欢迎,是因为它们不需要在事先进行大量的预处理和数据清洗。
尽管数据准备和清洗是每个机器学习管道中的重要步骤,但有一个自然支持分类输入数据的框架仍然很令人愉快。一些集成树形分类器是建立在这一优势之上的,例如,CatBoost——来自 Yandex Research 的梯度提升树实现,具有对分类数据的原生支持。
树形模型的一个重要优点,特别是从商业角度来看,是模型的可解释性。与其他机器学习方法不同,决策树分类器模型的输出不是一个巨大的参数化决策边界函数。训练好的深度学习模型通常会产生包含超过 1 亿个参数的模型,因此表现得像一个黑盒——特别是对于商业决策者来说。虽然从深度学习模型中获取见解和推理关于激活是可能的,但通常很难推理输入参数对输出变量的影响。
可解释性是树形方法的优势所在。与许多其他传统机器学习方法(如 SVM、逻辑回归或深度学习)相比,决策树是一个非参数模型,因此不使用参数来描述要学习的函数。它使用可以绘制、可视化和打印在纸上的嵌套决策树。这使得决策者能够理解基于树的分类模型的每一个决策(输出)——可能需要很多纸张,但总是可能的。
当谈到可解释性时,我们需要提到决策树的另一个重要方面:决策树模型在训练过程中隐式地发展了特征重要性的概念。这是训练好的决策树模型的一个非常有用的输出,我们可以用它来对特征进行预处理排序,而无需首先清理数据。
重要提示
虽然特征重要性也可以用其他机器学习(ML)方法来衡量,例如线性回归,但它们通常需要输入一个清洗和归一化的数据集。许多其他机器学习方法,如支持向量机(SVM)或深度学习,不会为单个输入维度开发特征重要性的度量。
基于决策树的方法在这方面表现出色,因为它们内部根据重要性标准创建每个单个分割(决策)。这导致了对最终模型中哪些特征维度重要以及如何重要的内在理解。
让我们来看看决策树另一个巨大的优势。与源自非参数方法的传统统计模型相比,决策树具有许多实际优势。基于树的模型通常在各种输入分布上都能产生良好的结果,甚至在模型假设被违反时也能很好地工作。除此之外,与深度学习方法相比,训练好的树的大小较小,推理/评分速度快。
决策树的缺点
正如生活中的一切都有利有弊一样,决策树也是如此。与单个决策树相关的一些严重缺点应该让你在机器学习(ML)管道中避免使用单个决策树分类器。单个决策树的主要弱点是树是在所有训练样本上拟合的,因此很可能发生过拟合。这是因为模型本身倾向于构建复杂的if-else树来模拟连续函数。
另一个重要点是,即使对于简单概念,找到最优决策树也是一个NP 难题(也称为非确定性多项式时间难题)。因此,它通过启发式方法来解决,并且得到的单个决策通常不是最优的。
过拟合很糟糕——非常糟糕——并且会导致机器学习中的严重问题。一旦模型过拟合,它就不太能泛化,因此在未见过的数据上的性能非常差。因此,对新输入的预测结果将比训练期间测量的结果更差。另一个相关问题是,训练数据或训练样本顺序的微小变化可能导致非常不同的嵌套树,因此训练收敛性不稳定。单个决策树极其容易过拟合。此外,单个决策树很可能偏向于训练数据中样本数量最多的类别。
通过 Bagging 和 Boosting 将多个决策树组合成一个集成模型,可以克服单棵树的缺点,如过拟合、不稳定和非最优树。还有许多基于树的优化方法,包括树剪枝,以提高泛化能力。使用这些技术的流行模型包括随机森林和梯度提升树,它们克服了单棵决策树的大部分问题,同时保持了大部分优点。我们将在下一节中探讨这两种方法。
重要提示
有时,即使在基于树的集成方法中,也会出现一些更基本的缺点,这些缺点值得提及。由于决策树的本质,基于树的模型在学习复杂函数,如 XOR 问题时存在困难。对于这些问题,最好使用非线性参数模型,如神经网络和深度学习方法。
将分类器与 Bagging 结合
单个决策树的一个关键缺点是对训练数据的过拟合,因此,从训练数据的小变化中,会导致泛化性能差和稳定性差。一个Bagging(也称为自助聚合)分类器通过将多个独立的模型组合成一个在训练数据子集上训练的集成模型来克服这个确切问题。这些子集是通过从训练数据集中随机抽取带有替换的样本来构建的。单个模型的输出要么通过分类中的多数投票选择,要么通过回归问题中的均值聚合。
通过结合独立的模型,我们可以降低组合模型的方差,而不会增加偏差,从而大大提高泛化能力。然而,训练多个单独模型还有一个好处:并行化。由于每个单独的模型都使用训练数据的一个随机子集,因此训练过程可以轻松并行化,并在多个计算节点上训练。因此,当在大数据集上训练大量基于树的分类器时,Bagging 是一种流行的技术。
下面的图 9.2展示了每个分类器如何独立地在相同的训练数据上训练——每个模型使用带有替换的随机子集。所有单个模型的组合构成了集成模型。

图 9.2 – Bagging
Bagging 可以与任何机器学习模型结合使用;然而,它通常与基于树的分类器一起使用,因为它们最容易过拟合。随机森林的理念建立在 bagging 方法之上,并结合了每个分割(决策)的随机特征子集。当随机选择一个特征时,计算分割的最佳阈值,以优化某个信息准则(通常是GINI或信息增益)。因此,随机森林使用训练数据的随机子集、随机特征选择和分割的最佳阈值。
随机森林因其简单的基于决策树的模型以及更好的泛化能力和易于并行化而得到广泛应用。采用特征随机子集的另一个好处是,这种技术也适用于非常高维度的输入。因此,在处理经典机器学习方法时,随机森林通常用于大规模的树集成。
另一种流行的基于树的 bagging 技术是extra-trees(即extremely randomized trees)算法,它在维度分割上增加了一个额外的随机化步骤。对于每个分割,随机抽取阈值,并选择最佳阈值进行决策。因此,除了随机特征外,extra-trees 算法还使用随机分割阈值来进一步提高泛化能力。
下面的图 9.3展示了所有树集成技术在推理中的应用。每棵树计算一个单独的分数,而每棵树的结果被汇总以产生最终结果:

图 9.3 – 多数投票
你可以在许多流行的机器学习库中找到基于树的 bagging 集成,例如 scikit-learn、Spark MLlib、ML.NET 等。
使用 boosting 轮优化分类器
在计算机科学的许多问题中,我们可以用一个更复杂但更优的方法来替换随机贪婪方法。对于树集成也是如此,并为其奠定了boosted 树集成的基础。
增强背后的基本思想如下:
-
我们开始在整个训练数据集上训练单个模型。
-
然后我们在训练数据集上计算模型的预测,并开始提高产生错误结果的训练样本的权重。
-
接下来,我们使用加权训练集训练另一棵决策树。然后我们将这两棵决策树组合成一个集成,并预测加权训练集的输出类别。然后我们在下一轮 boosting 中进一步增加组合模型中错误分类的训练样本的权重。
-
我们继续执行此算法,直到达到停止标准。
下面的图 9.4展示了使用 boosting 优化训练误差如何随着每次迭代(boosting 轮)的增加而降低:

图 9.4 – Boosting
第一个提升算法是AdaBoost,它通过在加权训练集上拟合,将多个弱模型组合成一个集成,并通过学习率适应每一轮迭代。这种方法的概念是添加单个树,这些树专注于预测前一个树无法预测的东西。
提升的一个特别成功的技巧是梯度提升树(或梯度提升)。在梯度提升中,你将梯度下降优化技术与提升相结合,以便将提升推广到任意损失函数。现在,我们不再使用权重调整数据集样本,而是在每一轮迭代中计算损失函数的梯度,并选择最优权重——那些最小化损失函数的权重。多亏了优化技术的使用,这种方法产生了非常好的结果,增加了决策树现有的优势。
基于梯度提升树的集成在许多流行的机器学习库中都有包括,例如 scikit-learn、Spark MLlib 等。然而,一些个别实现,如 XGBoost 和 LightGBM,已经获得了相当多的流行度,并且可以作为独立库以及作为 scikit-learn 和 Spark 的插件使用。
使用 LightGBM 训练集成分类器模型
由于决策树的简单性和结合多个分类器的优势,随机森林和梯度提升树都是强大的机器学习技术。在这个例子中,我们将使用来自微软的流行 LightGBM 库来实现这两种技术在测试数据集上的实现。LightGBM 是一个梯度提升框架,它结合了多个基于树的机器学习算法。
对于本节,我们将遵循典型的最佳实践方法,使用 Azure 机器学习,并执行以下步骤:
-
在 Azure 中注册数据集。
-
创建一个远程计算集群。
-
实现一个可配置的训练脚本。
-
在计算集群上运行训练脚本。
-
记录和收集数据集、参数和性能。
-
注册训练好的模型。
在我们开始这个激动人心的方法之前,我们将快速看一下为什么我们选择 LightGBM 作为训练装袋和提升树集成工具。
LightGBM 概述
LightGBM 使用了许多经典基于树的集成技术的优化,以在分类和连续特征上提供出色的性能。后者使用基于直方图的方法进行配置文件分析,并将其转换为最优分割的离散箱,这减少了内存消耗并加快了训练速度。这使得 LightGBM 比使用预排序算法计算分割的其他提升库更快、更节省内存,因此是大型数据集的一个很好的选择。
LightGBM 的另一个优化是,树是垂直生长的,从叶子到叶子,而其他类似的库是水平生长的,一层层。在叶节点算法中,新添加的叶子节点总是有最大的损失减少。这意味着这些算法与分层算法相比,往往能实现更小的损失。然而,更大的深度也会导致过拟合,因此你必须仔细调整每个树的最大深度。总的来说,LightGBM 在大量应用上使用默认参数就能产生非常好的结果。
在第七章,“使用 NLP 的高级特征提取”中,我们了解了很多关于分类特征嵌入和从文本特征中提取语义意义的内容。我们探讨了嵌入名义分类变量的常见技术,如标签编码和独热编码,以及其他方法。然而,为了优化基于树的分类变量的分割标准,有更好的编码可以产生最优的分割。因此,在本节中,我们根本不对分类变量进行编码,而只是简单地告诉 LightGBM 哪些使用的变量是分类变量。
最后要提到的一点是,LightGBM 可以利用 GPU 加速,并且可以在数据并行或模型并行的方式下进行训练。我们将在第十二章,“Azure 上的分布式机器学习”中了解更多关于分布式训练的内容。
重要提示
LightGBM 是一个基于树的集成模型的绝佳选择,特别是对于非常大的数据集。
在本书中,我们将使用带有lgbm命名空间的 LightGBM。然后我们可以通过输入四个字符来直接调用命名空间中的不同方法——这是 Python 中数据科学家的一种最佳实践方法。让我们看一个简单的例子:
import lightgbm as lgbm
# Construct a LGBM dataset
lgbm.Dataset(..)
# Train a LGBM predictor
clf = lgbm.train(..)
值得注意的是,所有算法都是通过lgbm.train()方法进行训练的,我们使用不同的参数来指定算法、应用类型、损失函数,以及每个算法的额外超参数。LightGBM 支持多种基于决策树的集成模型,用于袋装和提升。以下是您可以选择的算法选项,以及它们的名称,以便在提升参数中识别它们:
-
gbdt:传统的梯度提升决策树 -
rf:随机森林 -
dart:Dropouts meet multiple additive regression trees -
goss:基于梯度的单侧采样
前两个选项,即梯度提升决策树(gbdt),这是 LightGBM 的默认选择,以及随机森林(rf),是提升和袋装技术的经典实现,在本章的第一节中解释,并具有 LightGBM 特定的优化。其他两种技术,Dropouts Meet Multiple Additive Regression Trees(dart)和Gradient-Based One-Side Sampling(goss),是 LightGBM 特有的,并为更好的结果提供了更多优化,以牺牲训练速度为代价。
目标参数——这是最重要的参数之一——指定了模型的适用应用程序类型,因此是您试图解决的机器学习问题。在 LightGBM 中,您有以下标准选项,这些选项与其他大多数基于决策树的集成算法类似:
-
regression: 用于预测连续目标变量 -
binary: 用于二分类任务 -
multiclass: 用于多分类问题
除了标准选择之外,您还可以选择以下更具体的目标:regression_l1、huber、fair、poisson、quantile、mape、gamma、cross_entropy以及许多其他选项。
与模型的目标参数直接相关的是选择损失函数来衡量和优化训练性能。在这里,LightGBM 也为我们提供了默认选项,这些选项也大多数其他提升库中可用,我们可以通过 metric 参数来指定:
-
mae: 均值绝对误差 -
mse: 均方误差 -
binary_logloss: 二分类的损失 -
multi_logloss: 多分类的损失
除了这些损失度量之外,还支持其他度量,例如rmse、quantile、mape、huber、fair、poisson以及许多其他度量。在我们的分类场景中,我们将选择具有binary目标函数和binary_logloss度量的dart算法。
重要提示
您还可以将 LightGBM 用作 scikit-learn 估计器。为此,从lightgbm命名空间调用LGBMModel、LGBMClassifier或LGBMRegressor模型。然而,最新功能通常仅通过 LightGBM 接口可用。
现在,了解了如何使用 LightGBM,我们可以开始实现数据准备和编写脚本。
准备数据
在本节中,我们将读取和准备数据,并将清洗后的数据注册为新的数据集在 Azure Machine Learning 中。这将使我们能够从与工作区连接的任何计算目标访问数据,而无需手动复制数据、挂载磁盘或设置与数据存储的连接。这在第四章中进行了详细讨论,数据摄取和管理数据集。所有设置、调度和操作都将从作者环境——Jupyter 笔记本中完成。
对于分类示例,我们将使用泰坦尼克号数据集,这是一个流行的机器学习实践者数据集,用于预测泰坦尼克号上每位乘客的二元生存概率(生存或未生存)。该数据集的特征描述了乘客,并包含以下属性:乘客 ID、等级、姓名、性别、年龄、船上兄弟姐妹或配偶的数量、船上子女或父母数量、票号、票价、船舱号和登船港口。
重要提示
关于这个数据集的详细信息以及完整的预处理流程,可以在本书附带源代码中找到。
在不知道更多细节的情况下,我们将卷起袖子设置工作区并开始实验:
-
我们从
azureml.core导入Workspace和Experiment,并指定实验名称为titanic-lgbm:from azureml.core import Workspace, Experiment ws = Workspace.from_config() exp = Experiment(workspace=ws, name="titanic-lgbm") -
接下来,我们使用 pandas 加载数据集,并开始清理和预处理数据:
import pandas as pd # Read the data df = pd.read_csv('data/titanic.csv') # Prepare the data df.drop(['PassengerId'], axis=1, inplace=True) df.loc[df['Sex'] == 'female', 'Sex'] = 0 df.loc[df['Sex'] == 'male', 'Sex'] = 1 df['Sex'] = df['Sex'].astype('int8') embarked_encoder = LabelEncoder() embarked_encoder.fit(df['Embarked'].fillna('Null')) df['Embarked'].fillna('Null', inplace=True) df['Embarked'] = embarked_encoder.transform( df['Embarked']) df.drop(['Name', 'Ticket', 'Cabin'], axis=1, inplace=True)
在前面的示例中,我们从 CSV 文件加载数据,删除未使用的列,将Sex特征的值替换为标签0和1,并将Embarked特征的分类值编码为标签。
-
接下来,我们编写一个小的实用函数
df_to_dataset(),它将帮助我们存储 pandas DataFrame 并将其注册为 Azure 数据集,以便在 Azure 机器学习环境中轻松重用:def df_to_dataset(ws, df, name): datastore = ws.get_default_datastore() dataset = Dataset.Tabular.register_pandas_dataframe( df, datastore, name) return dataset
首先,我们检索到我们机器学习工作区的默认数据存储的引用——这是我们首次设置工作区时创建的 Azure Blob 存储。然后,我们使用一个辅助函数将数据集上传到这个默认数据存储,并将其作为表格数据集引用。
-
接下来,我们使用新创建的辅助函数将 pandas DataFrame 注册为名为
titanic_cleaned的数据集:# Register the data df_to_dataset(ws, df, 'titanic_cleaned') -
一旦数据集在 Azure 中注册,就可以在任何 Azure 机器学习工作区中访问。如果我们现在进入 UI 并点击
titanic_cleaned数据集。在 UI 中,我们还可以轻松检查和预览数据,如下面的截图所示:

图 9.5 – 泰坦尼克号数据集
值得注意的是,我们首先将分类变量编码为整数,使用标签编码,但稍后告诉 LightGBM 哪些变量包含数值列中的分类信息。这将帮助 LightGBM 在计算直方图和最佳参数分割时对这些列进行不同的处理。
数据集注册的好处是,我们现在可以简单地将数据传递给训练脚本或从 Azure 机器学习中的任何 Python 解释器访问它。让我们继续训练示例,为 LightGBM 创建训练和执行环境。
设置计算集群和执行环境
在我们开始训练 LightGBM 分类器之前,我们需要设置我们的训练集群和一个包含所有必需 Python 库的训练环境。对于本章,我们选择了一个最多有四个节点的 STANDARD_D2_V2 类型的 CPU 集群:
-
让我们编写一个小的辅助函数,使我们能够检索或创建一个具有指定名称和配置的训练集群。我们利用
ComputeTargetException,如果未找到指定名称的集群,则会抛出此异常:def get_aml_cluster(ws, cluster_name, vm_size='STANDARD_D2_V2', max_nodes=4): try: cluster = ComputeTarget( workspace=ws, name=cluster_name) except ComputeTargetException: config = AmlCompute.provisioning_configuration( vm_size=vm_size, max_nodes=max_nodes) cluster = ComputeTarget.create( ws, cluster_name, config) return cluster
我们已经在之前的章节中看到了这个脚本的组成部分,其中我们调用 AmlCompute.provisioning_configuration() 来配置一个新的集群。你可以在你的创作环境中定义所有基础设施这一点非常有帮助。
-
让我们检索或创建一个新的训练集群:
# Create or get training cluster aml_cluster = get_aml_cluster(ws, cluster_name="cpu-cluster") aml_cluster.wait_for_completion(show_output=True) -
接下来,我们想要为我们的训练环境和 Python 配置做同样的事情。我们实现了一个小的
get_run_config()函数,用于返回具有 Python 配置的远程执行环境。这将用于配置训练脚本所需的全部 Python 包:def get_run_config(target, packages=None): packages = packages or [] packages += ['azureml-defaults'] config = RunConfiguration() config.target = target config.environment.python.conda_dependencies = \ CondaDependencies.create(pip_packages=packages) return config
在前面的脚本中,我们使用 RunConfiguration 定义了 Azure Machine Learning 所需的包,如 azureml-defaults 和自定义 Python 包。
-
接下来,我们使用此函数配置一个包含所有必需
pip包的 Python 镜像,包括lightgbm:# Create a remote run configuration lgbm_config = get_run_config(aml_cluster, [ 'numpy', 'pandas', 'matplotlib', 'seaborn', 'scikit-learn', 'joblib', 'lightgbm' ])
在前面的代码片段中使用的两个函数非常有用。你使用 Azure Machine Learning 的时间越长,你将构建更多的抽象来轻松地与 Azure Machine Learning 服务交互。
使用自定义运行配置和自定义 Python 包,Azure Machine Learning 将在调度使用此运行配置的作业时立即设置 Docker 镜像并将其自动注册到 容器注册表 中。让我们首先构建训练脚本,然后在集群上调度它。
构建 LightGBM 分类器
现在我们有了数据集,并且我们已经为 LightGBM 分类模型的训练设置了环境和集群,我们可以设置训练脚本。上一节中的代码是在 Jupyter 笔记本中编写的。本节中的以下代码现在将编写并存储在一个名为 train_lgbm.py 的 Python 文件中。我们将按照以下步骤开始构建分类器:
-
首先,我们配置运行并从运行中提取工作区配置。这应该已经很熟悉了,因为我们已经为到目前为止在 Azure Machine Learning 上调度的几乎所有脚本都做过这件事:
from azureml.core import Dataset, Run run = Run.get_context() ws = run.experiment.workspace -
接下来,我们设置一个参数解析器,将命令行参数解析为 LightGBM 参数。我们开始时只有一些参数,但可以轻松地添加所有可用参数和默认值:
parser.add_argument('--data', type=str) parser.add_argument('--boosting', type=str) parser.add_argument('--learning-rate', type=float) parser.add_argument('--drop-rate', type=float) args = parser.parse_args()重要提示
我们建议使您的训练脚本可配置。使用
argparse定义数据集、输入参数和默认值。如果您坚持这个约定,所有模型参数都将自动跟踪在您的 Azure Machine Learning 实验中。另一个好处是,您以后可以调整超参数,而无需在训练脚本中更改一行代码。 -
然后,我们可以引用从输入参数中清理过的数据集,并使用
to_pandas_dataframe()方法将其加载到内存中:# Get a dataset by id dataset = Dataset.get_by_id(ws, id=args.data) # Load a TabularDataset into pandas DataFrame df = dataset.to_pandas_dataframe() -
在将数据集作为 pandas DataFrame 加载之后,我们现在可以开始将训练数据分割成训练集和验证集。我们还将把目标变量
Survived从训练数据集中分割成其自己的变量:y = df.pop('Survived') # Split into training and testing set X_train, X_test, y_train, y_test = train_test_split( df, y, test_size=0.2, random_state=42) -
接下来,我们向 LightGBM 介绍分类特征,这些特征已经被转换成数值变量,但需要特殊处理来计算最优分割值:
categories = ['Alone', 'Sex', 'Pclass', 'Embarked'] -
接下来,我们创建实际的 LightGBM 训练集和测试集,从 pandas DataFrame 中:
# Create training set train_data = lgbm.Dataset(data=X_train, label=y_train, categorical_feature=categories, free_raw_data=False) # Create testing set test_data = lgbm.Dataset(data=X_test, label=y_test, categorical_feature=categories, free_raw_data=False)
与 scikit-learn 不同,我们无法直接在 LightGBM 中使用 pandas DataFrame,但需要使用包装类 lgbm.Dataset。这将使我们能够访问所有必需的优化和功能,例如分布式训练、稀疏数据优化以及关于分类特征的元信息。
-
在解析完命令行参数后,我们将它们传递到一个参数字典中,然后将其传递给 LightGBM 训练方法:
lgbm_params = { 'application': 'binary', 'metric': 'binary_logloss', 'learning_rate': args.learning_rate, 'boosting': args.boosting, 'drop_rate': args.drop_rate, } -
所有通过命令行参数传递的参数都会自动记录在 Azure Machine Learning 中。然而,如果您想以编程方式访问模型参数或在 Azure Machine Learning 的实验概览中显示它们,我们可以在实验中记录它们。这将把所有参数附加到每个运行实例上,并在 Azure Machine Learning 中作为参数值提供。这意味着我们可以在以后根据模型参数对实验运行进行排序和筛选:
for k, v in params.items(): run.log(k, v)
梯度提升是一种具有可变迭代次数和可选提前停止标准的迭代优化方法。因此,我们还想记录训练脚本的每个迭代的全部指标。在这本书的整个过程中,我们将使用所有 ML 框架的类似技术——即使用一个回调函数,将所有可用的指标记录到您的 Azure Machine Learning 工作区。让我们使用 LightGBM 的自定义回调规范来编写这样一个函数。
-
在这里,我们创建一个回调对象,它遍历所有评估结果并将它们记录在运行中:
def azure_ml_callback(run): def callback(env): if env.evaluation_result_list: for data_name, eval_name, result, _ in \ env.evaluation_result_list: run.log("%s (%s)" % (eval_name, data_name), result) callback.order = 10 return callback -
在我们为 LightGBM 预测器设置好参数之后,我们可以使用
lgbm.train()方法来配置训练和验证过程。我们需要提供所有参数、参数和回调函数:clf = lgbm.train(train_set=train_data, params=lgbm_params, valid_sets=[train_data, test_data], valid_names=['train', 'val'], num_boost_round=args.num_boost_round, callbacks = [azure_ml_callback(run)])
上述代码的亮点是,通过提供通用的回调函数,所有训练和验证分数将自动记录到 Azure。因此,我们可以实时跟踪训练迭代,无论是在 UI 中还是在 API 中——例如,在一个自动收集所有运行信息的 Jupyter 小部件中。
-
为了评估最终的训练分数,我们使用训练好的分类器来预测几个默认的分类分数,例如
accuracy(准确率)、precision(精确率)和recall(召回率),以及组合的f1分数:y_pred = clf.predict(X_test) run.log("accuracy (test)", accuracy_score(y_test, y_pred)) run.log("precision (test)", precision_score(y_test, y_pred)) run.log("recall (test)", recall_score(y_test, y_pred)) run.log("f1 (test)", f1_score(y_test, y_pred))
我们已经运行了脚本并看到了所有指标以及模型在 Azure 中的性能。但这只是开始——我们想要更多!
-
让我们在 Azure Machine Learning 中计算特征重要性并跟踪其图表,并运行它。我们可以用几行代码来完成这个任务:
fig = plt.figure() ax = plt.subplot(111) lgbm.plot_importance(clf, ax=ax) run.log_image("feature importance", plot=fig)
一旦将此片段添加到训练脚本中,每次训练运行也将存储一个特征重要性图表。这有助于了解不同的指标如何影响特征重要性。
-
我们还想添加一个额外的步骤。每当训练脚本运行时,我们希望将训练好的模型上传并注册到模型注册表中。通过这样做,我们可以在以后手动或自动将模型部署到容器服务中。然而,这只能通过保存每次运行的训练工件来完成:
import joblib joblib.dump(clf, 'outputs/lgbm.pkl') run.upload_file('lgbm.pkl', 'outputs/lgbm.pkl') run.register_model(model_name='lgbm_titanic', model_path='lgbm.pkl')
在前面的片段中,我们使用了 joblib 包,该包最初是 scikit-learn 的一部分,用于将分类器保存到磁盘。然后我们将导出的模型注册为 Azure Machine Learning 中的 LightGBM 模型。
就这样——我们已经写完了整个训练脚本。它并不特别长,也不特别复杂。最棘手的部分是理解如何选择 LightGBM 的一些参数以及一般性地理解梯度提升——这就是为什么我们将章节的前半部分专门用于这个主题。现在让我们启动集群并提交训练脚本。
在 Azure Machine Learning 集群上安排训练脚本
我们逻辑上回到了作者环境——Jupyter 笔记本。上一节中的代码已存储为 train_lgbm.py 文件,我们现在将准备将其提交到集群。一件好事是我们使训练脚本可以通过命令行参数进行配置,因此我们可以使用 CLI 参数调整 LightGBM 模型的基参数。在以下步骤中,我们将配置作者脚本以执行训练过程:
-
让我们定义此模型的参数——我们将使用
dart,标准学习率为0.01,dropout 率为0.15。我们还通过命名参数将数据集传递给训练脚本:script_params = [ '--data', ds.as_named_input('titanic'), '--boosting', 'dart', '--learning-rate', '0.01', '--drop-rate', '0.15', ]
我们指定了提升方法,dart。正如我们在上一节中学到的,这项技术表现非常好,但并不特别高效,比其他选项——gbdt、rf 和 goss——慢一些。
重要提示
这也是 Azure 机器学习中的超参数调整工具HyperOpt将超参数传递给训练脚本的方式。我们将在第十一章中了解更多关于超参数调整和自动机器学习的内容。
-
接下来,我们可以将参数传递给
ScriptRunConfig并启动训练脚本:from azureml.core import ScriptRunConfig src = ScriptRunConfig( source_directory=os.getcwd(), script='train_lightgbm.py', run_config= lgbm_config arguments=script_params)
在前面的代码中,我们指定了我们的分类器文件,该文件存储在当前编写脚本的相关位置。Azure 机器学习会将训练脚本上传到默认的数据存储库,并在运行脚本的集群的所有节点上使其可用。
-
最后,让我们提交运行配置并执行训练脚本:
from azureml.widgets import RunDetails run = exp.submit(src) RunDetails(run).show()
RunDetails方法为我们提供了一个带有实时日志的交互式小部件,这些日志来自远程计算服务。我们可以看到集群正在初始化和扩展,Docker 镜像正在构建和注册,最终,还包括训练脚本的日志。
提示
如果你更喜欢其他方法而不是交互式的 Jupyter 小部件,你也可以使用run.wait_for_completion(show_output=True)或print(run.get_portal_url())来跟踪日志,以获取在 Azure 中运行的实验的 URL。
- 现在,让我们切换到 Azure 机器学习 UI,并在实验中查找运行。一旦我们点击它,我们就可以导航到指标部分,并找到所有已记录指标的概览。你可以在以下图 9.6中看到,具有相同名称的多次记录的指标被转换为向量,并以折线图的形式显示:

图 9.6 – 验证损失
然后,点击图像部分。当我们这样做时,我们会看到我们在训练脚本中创建的特征重要性图。以下图 9.7展示了它在 Azure 机器学习 UI 中的样子:

图 9.7 – 特征重要性
我们看到了如何在 Azure 机器学习中训练一个 LightGBM 分类器,利用了自动扩展的 Azure 机器学习计算集群。记录指标、图表和参数将所有关于训练运行的详细信息保持在同一个地方。与保存训练脚本的快照、输出、日志和训练好的模型一起,这对任何专业的大型机器学习项目来说都是无价的。
你应该从本章记住的是,梯度提升树是一种非常高效且可扩展的经典机器学习方法,拥有许多优秀的库,并支持分布式学习和 GPU 加速。LightGBM 是微软提供的一种替代方案,它在微软和开源生态系统中都得到了很好的整合。如果你在寻找一个经典、快速且易于理解的机器学习模型,我们的建议是选择 LightGBM。
摘要
在本章中,你学习了如何在 Azure 机器学习中构建一个经典机器学习模型。
您了解了决策树,这是一种在多种分类和回归问题中流行的技术。决策树的主要优势是它们需要很少的数据准备,因为它们在分类数据和不同的数据分布上表现良好。另一个重要的好处是它们的可解释性,这对于商业决策和用户来说尤为重要。这有助于您了解何时使用基于决策树的集成预测器是合适的。
然而,我们也了解到了一些弱点,特别是在过拟合和泛化能力差方面。幸运的是,基于树的集成技术,如 bagging(自助聚合)和 boosting(提升),有助于克服这些问题。虽然 bagging 有像随机森林这样的流行方法,它能够很好地并行化,但 boosting,尤其是梯度 boosting,有高效的实现,包括 XGBoost 和 LightGBM。
您在 Azure Machine Learning 中使用 LightGBM 库实现了并训练了一个基于决策树的分类器。LightGBM 是由微软开发的,通过一些优化提供了出色的性能和训练时间。这些优化帮助 LightGBM 即使在处理大型数据集时也能保持较小的内存占用,并且通过更少的迭代次数产生更好的损失。您不仅使用 Azure Machine Learning 来执行您的训练脚本,还用它来跟踪您的模型训练性能和最终的分类器。
在下一章中,我们将探讨一些流行的深度学习技术,以及如何使用 Azure Machine Learning 来训练它们。
第十章:第十章:在 Azure 上训练深度神经网络
在上一章中,我们学习了如何使用非参数的基于树的集成方法来训练和评分经典机器学习模型。虽然这些方法在包含分类变量的许多小型和中型数据集上表现良好,但它们在大数据集上的泛化能力不佳。
在本章中,我们将使用深度学习(DL)来训练复杂的参数模型,以实现与非常大的数据集的更好泛化。这将帮助您了解深度神经网络(DNNs),如何训练和使用它们,以及它们何时比传统模型表现更好。
首先,我们将简要概述为什么以及何时深度学习(DL)效果良好,并着重于理解一般原则和理由,而不是理论方法。这将帮助您评估哪些用例和数据集需要深度学习,以及它的一般工作原理。
然后,我们将探讨深度学习的一个流行应用领域——计算机视觉。我们将使用 Azure 机器学习服务和额外的 Azure 基础设施来训练一个简单的卷积神经网络(CNN)模型进行图像分类。我们将将其性能与在预训练的残差神经网络(ResNet)模型上微调过的模型进行比较。这将为您从头开始训练模型、针对您的应用领域微调现有模型以及克服训练数据不足的情况做好准备。
在本章中,我们将涵盖以下主题:
-
深度学习简介
-
训练 CNN 进行图像分类
技术要求
在本章中,我们将使用以下 Python 库和版本来创建基于决策树的集成分类器:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
numpy 1.19.5 -
pandas 1.3.2 -
scikit-learn 0.24.2
与前几章类似,您可以使用本地 Python 解释器或 Azure 机器学习托管的工作簿环境执行此代码。
本章中所有的代码示例都可以在这个书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter10。
深度学习简介
深度学习最近彻底改变了机器学习领域,并在各种任务中(如图像分类、目标检测、分割、语音转录、文本翻译、文本理解、销售预测等)不断优于经典统计方法,甚至优于人类。与经典模型相比,深度学习模型使用数百万个参数、参数共享、优化技术和隐式特征提取,当训练足够的数据时,可以优于所有之前手工制作的特征检测器和机器学习模型。
在本节中,我们将帮助你了解神经网络的基本知识以及使用更多参数、更好泛化和更好性能来训练更深模型的路径。这将帮助你理解基于深度学习的各种方法是如何工作的,以及为什么和何时它们对某些领域和数据集是有意义的。如果你已经是深度学习的专家,请随意跳过本节,直接进入训练用于图像分类的 CNN部分的实际示例。
为什么是深度学习?
许多传统的优化、分类和预测过程在过去几十年中已经使用经典的机器学习方法(如 k 最近邻、线性回归和逻辑回归、朴素贝叶斯、支持向量机(SVMs)、基于树的集成模型等)工作得很好。它们在小到中等规模的数据集上对各种类型的数据(交易、时间序列、运营等)和数据类型(二进制、数值和分类)都表现良好。
然而,在某些领域,数据生成已经爆炸式增长,即使训练数据量不断增加,经典机器学习模型也无法实现更好的性能。这尤其影响了 2010 年底左右的计算机视觉和自然语言处理(NLP)领域。那时,研究人员在神经网络——也称为多层感知器(MLPs)——这一技术上取得了突破,这是一种在 20 世纪 80 年代使用的技术,通过使用多层嵌套层来捕捉大型图像数据集中的大量特征。
以下图表很好地捕捉了这个想法。虽然传统的机器学习(ML)方法在小型和中等规模的数据集上工作得非常好,但它们的性能通常不会随着更多训练数据的增加而提高。然而,深度学习(DL)模型是大规模参数模型,可以从训练数据中捕捉大量细节。因此,我们可以看到,随着数据量的增加,它们的预测性能也在提高:

图 10.1 – 深度学习与传统机器学习的有效性
传统模型通常使用预构建的特征,并针对各种数据类型和范围的数据集进行优化。在上一章中,我们看到了梯度提升树在分类数据上的表现极为出色。然而,在包含高度结构化数据或可变长度数据的领域中,许多传统模型已经达到了它们的极限。这尤其适用于二维和三维图像以及视频中的像素信息,以及音频数据中的波形以及自由文本数据中的字符和字符序列。以前,机器学习模型使用复杂的、手动调整的特征提取器来处理此类数据,例如方向梯度直方图(HoG)过滤器、尺度不变特征变换(SIFT)特征或局部二值模式(LBPs)——仅举计算机视觉领域中的几个过滤器为例。
使这些数据如此复杂的原因是,输入数据(例如,单个像素)和输出之间不存在明显的线性关系——在大多数情况下,看到一个图像中的单个像素并不能帮助确定该图像中的汽车品牌。因此,训练更大、更强大的参数模型的需求不断增加,这些模型使用原始、未处理的数据作为输入,以从输入像素捕获这些关系并做出最终预测。
重要的是要理解,对具有更多参数的深度模型的必要性源于特定领域(如视觉、音频和语言)中高度结构化训练数据的巨大增加。这些新模型通常具有数百万个参数来捕捉大量的原始和增强训练数据,以及开发训练数据的内部泛化概念表示。在选择适用于您的用例的机器学习方法时,请记住这一点。
快速查看您的训练数据通常有助于确定基于深度学习的模型是否适合该任务——鉴于深度学习模型有数百万个参数需要训练。如果您的数据存储在 SQL 数据库、CSV 或 Excel 文件中,那么您可能需要考虑经典机器学习(ML)方法,例如参数统计(线性回归、支持向量机等)或非参数方法(基于决策树的集成)。如果您的数据量如此之大以至于无法放入内存,或者存储在Hadoop 分布式文件系统(HDFS)、blob 存储或文件存储服务器中,那么您可以使用基于深度学习的方法。
从神经网络到深度学习
神经网络的基础以及今天基于深度学习(DL)的方法——感知器——是一个超过半个世纪的概念,它是在 20 世纪 50 年代发展起来的。在本节中,我们将探讨基础知识,并逐步回顾到 20 世纪 80 年代的多层感知器(MLPs)——也称为人工神经网络(ANNs)——以及卷积神经网络(CNNs),然后是最近十年的深度神经网络(DNNs)和深度学习(DL)。这将帮助您理解神经网络和深度学习的基础概念,以及模型架构和训练技术在过去一个世纪中是如何演变成我们今天使用的最先进技术的。
感知器——20 世纪 50 年代的分类器
感知器是今天神经网络的基石,它们模仿人类大脑中的细胞(所谓的神经元)。它们由两个简单非线性函数组成:所有输入的加权和以及一个激活函数,如果输出大于指定的阈值,则激活。虽然这种神经元的类比是模拟大脑工作方式的一个很好的方法,但它并不是理解输入信号如何转换为输出的一个很好的模型。
我们更倾向于使用一种简单得多、非生物的方法来解释感知器、MLPs 和 CNNs,即简单的几何方法。当简化后,这种方法只需要你理解二维直线方程。一旦你理解了两维的基本概念,这个概念可以扩展到多维度,其中直线在更高维的特征空间中变成一个平面或超平面。
如果我们看一个单独的感知器,它描述了其输入的加权求和加上一个常量偏置和一个激活函数。让我们分解感知器的两个组成部分。你知道什么也被描述为输入的加权求和加上偏置吗?对,就是直线方程:

在前面的方程中,x 是输入,k 是权重,b 是偏置项。你可能在你的一些数学课程中看到过这个方程。这个方程的一个特性是,当你将一个点的 x 和 y 坐标插入到直线方程中时,对于所有位于直线上的点,它会产生 0 = 0。我们可以利用这个信息推导出直线方程的向量形式,如下所示:

因此,当点位于直线上时,
为 0。如果我们插入一个不在直线上的点的坐标会发生什么?一个很好的猜测是结果将是正的或负的,但肯定不是 0. 向量直线方程的一个特性是,这个结果的正负号描述了点位于直线的哪一侧。因此,当
为正或负但不是零时,点位于直线的左侧或右侧。
要确定直线的哪一侧,我们可以将符号函数应用于
。符号函数通常也被称为阶跃函数,因为它的输出是 1 或 -1,因此是正的或负的。这里的符号或阶跃函数是我们的激活函数,因此是感知器的第二个组成部分。感知器的输出
可以写成以下形式:

在以下图表中,我们可以看到两个点、一条直线以及它们到直线的最短距离。两个点都不在直线上,因此直线将它们彼此分开。如果我们把两个点的坐标插入到向量直线方程中,那么一个点会产生一个正值
,而另一个点会产生一个负值
:

图 10.2 – 一个简单的二元分类器
结果会告诉我们点位于线的哪一侧。这条线是感知器的几何描述,它是一个非常简单的分类器。训练好的感知器通过线方程(或多个维度中的超平面)定义,将空间分为左右两部分。这条线是分类的决策边界,而一个点是一个观察。通过将一个点插入线方程并应用步函数,我们返回观察结果的类别,即左或右,-1 或+1,或类别 A 或 B。这描述了一个二元分类器。
我们如何找到决策边界?为了找到最优的决策边界,我们可以在使用标记的训练样本的同时遵循一个迭代训练过程。首先,我们必须初始化一个随机的决策边界,然后计算每个样本到决策边界的距离,并将决策边界移动到最小化总距离和的方向。移动决策边界的最优向量是如果我们沿着负梯度移动它,使得点与线之间的距离达到最小。通过使用学习率因子,我们迭代这个过程几次,最终得到一个完美对齐的决策边界,如果训练样本是线性可分的。这个过程被称为梯度下降,其中我们迭代地修改分类器的权重(在这个例子中是决策边界)以找到具有最小误差的最优边界。
多层感知器
感知器描述了一个简单的分类器,其决策边界是通过加权输入定义的线(或超平面)。然而,我们不是使用单个分类器,而是简单地增加神经元的数量,这将导致多个决策边界,如下面的图表所示:

图 10.3 – 多个感知器的组合
每个神经元描述一个决策边界,因此将具有独立的权重和输出 – 决策边界的左侧或右侧。通过在层中堆叠多个神经元,我们可以创建输入是前一个输出的分类器。这允许我们将多个决策边界的输出组合成一个单一的输出 – 例如,找到所有被三个神经元的决策边界包围的样本,如前述图表所示。
当单层感知器描述输入和输出的线性组合时,研究人员开始将这些感知器堆叠成多个连续层,每一层后面都跟着一个激活函数。这被称为 MLP,或人工神经网络。使用几何模型作为类比,你可以在复杂的几何对象上简单地堆叠多个决策边界,以创建更复杂的决策边界。
重要提示
另一个类比是,分类器的决策边界始终是一个直线超平面,但输入样本通过决策边界被转换成线性分离。
同样的几何类比帮助我们理解深度学习模型中的层。虽然网络的第一层描述了非常低级的几何特征,例如直线和线条,但更高层描述了这些低级特征的复杂嵌套组合;例如,四条线构成一个正方形,五个正方形构成一个更复杂的形状,而这些形状的组合看起来像人脸。我们就是用三层神经网络构建了一个人脸检测器。
Google DeepDream 实验是这个类比的一个绝佳例子。在下面的图中,我们可以可视化一个预训练的深度神经网络(DNN)中不同深度的三层如何表示多云天空图像中的特征。这些层是从 DNN 的开始、中间和末端提取出来的,并将输入图像转换为最小化每层的损失。在这里,我们可以看到早期层主要关注线条和边缘(左),中间层看到抽象形状(中间),而最后一层在图像的非常具体的高级特征上激活(右):

图 10.4 – DeepDream – 最小化 DNN 层的损失
接下来,让我们看看 CNNs。
CNNs
使用多个高维超平面方程,其中每个输出馈送到下一层的每个输入,需要非常多的参数。虽然需要大量的参数来模拟大量的复杂训练数据,但所谓的全连接神经网络并不是描述这些连接的最佳方式。那么,问题是什么?
在全连接网络中,每个输出都被作为输入馈送到下一层的每个神经元。在每个神经元中,我们都需要为每个输入设置一个权重,因此我们需要与输入维度一样多的权重。当我们开始堆叠多个感知器层时,这个数字会迅速增加。另一个问题是,网络无法泛化,因为它为每个维度分别学习所有单个权重。
在 20 世纪 80 年代,卷积神经网络(CNNs)被发明来解决这些问题。它们的目的是将单层上的连接和参数数量减少到一个固定的参数集,这个参数集与输入维度的数量无关。现在,一个层的参数在所有输入之间是共享的。这种方法的灵感来源于信号处理,其中滤波器通过卷积操作应用于信号。卷积意味着将单一权重集,如窗口函数,应用于输入的多个区域,然后对每个位置的滤波器信号响应进行求和。
这也是卷积神经网络中卷积层相同的概念。通过使用与输入卷积的固定大小滤波器,我们可以大大减少每一层的参数数量,并在网络中添加更多嵌套层。通过使用所谓的池化层,我们还可以减少图像大小,并将滤波器应用于输入的降尺度版本。让我们看看用于构建卷积神经网络的流行层:
-
全连接(FC):FC 层是一个全连接神经元层,如前一小节关于感知器的描述中所述——它将前一层的每个输出与一个神经元连接起来。在深度神经网络中,FC 层通常用于网络的末端,以结合前一卷积层的所有空间分布的激活。FC 层在模型中也有最多的参数(通常约为 90%)。
-
卷积:卷积层由沿空间维度(通常是二维)卷积的空间(滤波器)组成,并在输入的深度维度上求和。由于权重共享,它们比全连接层更高效,并且参数更少。
-
池化:卷积层通常后面跟着一个池化层来减少下一滤波器的体积的空间维度——这相当于一个子采样操作。池化操作本身没有可学习的参数。大多数情况下,由于它们简单的梯度计算,在深度学习模型中会使用最大池化层。另一个流行的选择是平均池化,它通常用作网络末端的分类器。
-
归一化:在现代深度神经网络中,归一化层通常用于在整个网络中稳定梯度。由于某些激活函数的无界行为,滤波器响应必须归一化。常用的归一化技术是批归一化。
现在我们已经了解了卷积神经网络的主要组成部分,我们可以看看这些模型是如何堆叠得更深,以提高泛化能力,从而提高预测性能。
从卷积神经网络到深度学习
20 世纪 50 年代的感知器,以及 80 年代的 ANN 和 CNN,为今天使用的所有深度学习模型奠定了基础。通过在训练过程中稳定梯度,研究人员克服了梯度爆炸和消失的问题,构建了更深的模型。这是通过使用额外的归一化层、修正线性激活、辅助损失和残差连接来实现的。
深度模型有更多的可学习参数——通常超过一亿个参数——因此它们可以找到更高层次的模式,学习更复杂的变换。然而,为了训练更深的模型,你还必须使用更多的训练数据。因此,公司和研究人员建立了大量的标记数据集(如 ImageNet),为这些模型提供训练数据。
这种发展过程得益于廉价并行计算资源(如 GPU 和云计算)的可用性。训练这些深度模型的速度在短短几年内从数月缩短到数天再到数小时。如今,我们可以在高度并行的计算基础设施下,在一小时内训练一个典型的深度神经网络(DNN)。
许多研究也投入到了新的层堆叠技术中,从具有跳转连接的非常深的网络(如 ResNet152)到具有并行层组的网络(如 GoogLeNet)。这两种层类型的组合导致了极其高效的网络架构,如 SqueezeNet 和 Inception。新的层类型如 LSTM、GRU 和注意力机制显著提高了预测性能,而 GAN 和变换模型创造了全新的训练和优化模型的方法。
所有这些进步都帮助深度学习成为今天无处不在的机器学习技术——在提供足够训练数据的情况下,它可以在大多数预测任务中超越传统的机器学习模型,甚至优于人类。如今,深度学习被应用于几乎任何有足够数据可用的领域。
深度学习与传统机器学习
让我们来看看经典机器学习方法和基于深度学习方法的区别,并了解深度学习模型如何利用更多的参数以及它们从中获得的好处。
如果我们回顾 2012 年之前的图像或音频处理领域,我们会发现机器学习模型通常不是在原始数据本身上训练的。相反,原始数据经过人工设计的特征提取器转换成低维特征空间。当处理 256 x 256 x 3 维度的图像(RGB,对应 196,608 维特征空间)并将其转换为例如 2,048 维特征嵌入作为机器学习模型的输入时,我们大大降低了这些模型的计算需求。图像和音频特征的特征提取器通常使用卷积算子和特定的滤波器(如边缘检测器、块检测器、尖峰/谷检测器等)。然而,滤波器通常是手动构建的。
在过去 50 多年中开发的经典机器学习模型仍然是今天我们成功使用的模型。其中包括基于树的集成技术、线性回归和逻辑回归、支持向量机(SVMs)和多层感知器(MLPs)。MLP 模型也被称为具有隐藏层的全连接神经网络,并且在一些早期的深度学习架构中仍作为分类或回归头使用。
下图展示了计算机视觉领域中经典机器学习方法的典型流程:

图 10.5 – 传统机器学习分类器
首先,使用手工制作的图像滤波器(SIFT、SURF、HoG、LBPs、Haar 滤波器等)将原始数据转换为低维特征嵌入。然后,使用特征嵌入来训练机器学习模型;例如,一个多层全连接神经网络或决策树分类器,如前图所示。
当人类难以用简单规则表达输入图像和输出标签之间的关系时,那么对于经典计算机视觉和机器学习方法来说,找到这样的规则也可能很困难。基于深度学习的方法在这些情况下表现更好。原因在于深度学习模型是在原始输入数据上而不是在手动提取的特征上训练的。由于卷积层与随机和训练的图像滤波器相同,这些用于特征提取的滤波器被网络隐式地学习。
下图显示了图像分类的深度学习方法,这与经典机器学习方法的前图类似:

图 10.6 – 基于深度学习的分类器
如我们所见,原始输入图像数据直接输入到网络中,输出最终的图像标签。这就是我们通常将深度学习模型称为端到端模型的原因——因为它在输入数据(字面上,原始像素值)和模型输出之间创建了一个端到端转换。
如前图所示,基于深度学习的模型是一个端到端模型,它在一个模型中学习特征提取器和分类器。然而,我们通常指的是最后一个全连接层。
重要提示
在选择机器学习模型之前,看看你的数据类型。如果你处理的是图像、视频、音频、时间序列、语言或文本,你可能希望使用深度学习模型或特征提取器进行嵌入、聚类、分类或回归。如果你处理的是运营或业务数据,那么经典机器学习方法可能更适合。
使用基于深度学习的特征提取器进行传统机器学习
在许多情况下,尤其是当你拥有小数据集、计算资源不足或缺乏训练端到端深度学习模型的知识时,你也可以重用预训练的深度学习模型作为特征提取器。这可以通过加载预训练模型并执行正向传播直到分类/回归头部来实现。它返回一个多维嵌入(所谓的潜在空间表示),你可以直接将其插入到经典机器学习模型中。
这里是一个这种混合方法的例子。我们使用在imagenet数据上预训练的IncpetionV3模型作为特征提取器。深度学习模型仅用于将原始输入图像数据转换为低维特征表示。然后,在图像特征之上训练一个 SVM 模型。让我们看看这个例子的源代码:
import numpy as np
from tensorflow.keras.applications import InceptionV3
def extract_features(img_data, IMG_SIZE):
IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3)
model = InceptionV3(input_shape=IMG_SHAPE,
include_top=False,
weights='imagenet',
pooling='avg')
predictions = model.predict(img_data)
return np.squeeze(predictions)
labels = [] # loaded previously
features = extract_features(image_data)
X_train, X_test, y_train, y_test = train_test_split(
features, labels)
from sklearn.svm import SVC
clf = SVC(kernel='linear', C=1)
clf.fit(X_train, y_train)
在前面的代码中,我们使用 TensorFlow 加载了基于 ImageNet 权重的InceptionV3模型,但没有任何分类或回归头。这是通过将include_top属性设置为False来实现的。然后,我们将预测的输出——图像的潜在表示——压缩成一个单一的向量。最后,我们使用 scikit-learn 和默认的 train/test 拆分在图像特征上训练了一个支持向量机(SVM)。
我们从经典方法开始,其中特征提取和 ML 被分为两个步骤。然而,在经典方法中,过滤器是手工制作的,并直接应用于原始输入数据。在深度学习方法中,我们隐式地学习特征提取。
训练用于图像分类的卷积神经网络(CNN)
既然我们已经很好地理解了为什么以及何时使用深度学习模型,我们就可以开始实施一个模型并使用 Azure Machine Learning 来运行它。我们将从一个深度学习在过去几年中表现非常出色的任务开始——计算机视觉,或者更准确地说,是图像分类。如果你觉得这对你来说太简单了,你可以用任何其他计算机视觉技术替换实际的训练脚本,并按照本节中的步骤进行操作:
-
首先,我们将启动一个 Azure Machine Learning 计算实例,它将作为我们的 Jupyter Notebook 创作环境。首先,我们将编写一个训练脚本并在创作环境中执行它,以验证其是否正常工作,检查点保存模型,并记录训练和验证指标。我们将训练模型几个周期以验证设置、代码和生成的模型。
-
接下来,我们将尝试通过在训练脚本中添加数据增强来改进算法。虽然这似乎是一个简单的任务,但我想要重申,这对于任何基于深度学习的 ML 方法来说都是必要的,并且强烈推荐。图像数据可以很容易地增强以提高泛化能力,从而提高模型评分性能。然而,通过这种技术,模型的训练将比之前更长,因为每个周期使用了更多的训练数据。
-
现在,我们必须将训练脚本从创作环境迁移到 GPU 集群——一个远程计算环境。我们将在这个环境中完成所有这些工作——上传数据、生成训练脚本、创建集群、在集群上执行训练脚本,以及检索训练好的模型。如果你已经在自己的服务器上自行训练 ML 模型,那么本节将展示如何将你的训练脚本迁移到远程执行环境,以及如何从动态可扩展的计算(垂直和水平扩展,因此是更大和更多的机器)、自动扩展、低成本数据存储等众多好处中受益。
-
一旦你成功从头开始训练了一个 CNN,你将想要在模型性能和复杂性方面进入下一个层次。一个良好且推荐的方法是微调预训练的深度学习(DL)模型,而不是从头开始训练。采用这种方法,我们通常还可以使用来自特定任务的预训练模型,从模型中移除分类头(通常是最后的一个或两个层),并通过在它之上训练我们的分类头来重用特征提取器进行另一个任务。这被称为迁移学习,并且被广泛用于训练各种领域的最先进模型。
现在,让我们打开一个 Jupyter 笔记本,开始训练一个 CNN 图像分类器。
在笔记本中从头开始训练 CNN
让我们在 Azure Machine Learning 服务上的 Jupyter 中训练一个 CNN。首先,我们只想在当前创作环境中简单地训练一个模型,这意味着我们必须使用计算实例(CPU 和内存)。这是一个标准的 Python/Jupyter 环境,所以它与在本地机器上训练 ML 模型没有区别。因此,让我们在我们的 Azure Machine Learning 服务工作区中创建一个新的计算实例,然后打开 Jupyter 环境:
-
在我们开始创建我们的卷积神经网络(CNN)模型之前,我们需要一些训练数据。由于我们在创作计算机上训练机器学习(ML)模型,数据需要位于同一台机器上。在这个例子中,我们将使用 MNIST 图像数据集:
import os import urllib os.makedirs('./data/mnist', exist_ok=True) BASE_URL = 'http://yann.lecun.com/exdb/mnist/' urllib.request.urlretrieve( BASE_URL + 'train-images-idx3-ubyte.gz', filename='./data/mnist/train-images.gz') urllib.request.urlretrieve( BASE_URL + 'train-labels-idx1-ubyte.gz', filename='./data/mnist/train-labels.gz') urllib.request.urlretrieve( BASE_URL + 't10k-images-idx3-ubyte.gz', filename='./data/mnist/test-images.gz') urllib.request.urlretrieve( BASE_URL + t10k-labels-idx1-ubyte.gz', filename='./data/mnist/test-labels.gz')
在前面的代码中,我们加载了训练和测试数据,并将其放在当前环境中代码执行的data目录中。在下一节中,我们将学习如何使数据在 ML 工作区中的任何计算实例上可用。
-
接下来,我们必须加载数据,解析它,并将其存储在多维 NumPy 数组中。我们将使用一个辅助函数
load,该函数定义在本章的配套源代码中。之后,我们必须通过将像素值归一化到0到1之间来预处理训练数据:DIR = './data/mnist/' X_train = load(DIR + 'train-images.gz', False) / 255.0 X_test = load(DIR + 'test-images.gz', False) / 255.0 y_train = load(DIR + 'train-labels.gz', True) \ .reshape(-1) y_test = load(DIR + 'test-labels.gz', True) \ .reshape(-1)
使用reshape方法,我们检查了训练和测试标签是一维向量,每个训练和测试样本都有一个标签。
一旦我们有了训练数据,就是时候决定使用哪个 Python 框架来训练神经网络模型了。虽然你在 Azure Machine Learning 中不受任何特定框架的限制,但我们建议你使用 TensorFlow(带 Keras)或 PyTorch 来训练神经网络和深度学习模型。当你训练和部署标准生产模型时,TensorFlow 和 Keras 是不错的选择。
重要提示
PyTorch 是探索异构模型和自定义层以及调试定制模型的一个很好的选择。在我看来,PyTorch 更容易上手,而 TensorFlow 则更复杂、更成熟,并且拥有更大的生态系统。在本章中,我们将使用 TensorFlow,因为它拥有庞大的生态系统、Keras 集成、优秀的文档以及在 Azure 机器学习服务中的良好支持。
-
选择了一个机器学习框架后,我们可以开始构建一个简单的 CNN。让我们使用
keras构建一个序列模型:from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Conv2D, \ MaxPooling2D, Flatten, Dense model = Sequential() model.add(Conv2D(filters=16, kernel_size=3, padding='same', activation='relu', input_shape=(28,28,1))) model.add(MaxPooling2D(pool_size=2)) model.add(Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')) model.add(MaxPooling2D(pool_size=2)) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(Dense(10, activation='softmax'))
在前面的代码中,我们利用了keras.Sequential模型 API 构建了一个简单的 CNN 模型。我们采用了默认的权重初始化,并在这里仅指定了模型结构。您还可以看到典型的特征提取器组合,直到Flatten层,以及使用softmax激活函数在末尾输出 10 个概率的 MLP 分类头。
让我们快速看一下模型,该模型总共有409034个参数,如下面的图所示。请注意,我们特别构建了一个简单的 CNN,其输入图像尺寸为28x28的灰度图像。下面的图显示了模型定义的紧凑结构。在这里,我们可以观察到最大的参数数量是在特征提取器之后的全连接层,它包含了总模型参数的 98%:

图 10.7 – 深度学习模型架构
-
在定义了模型结构之后,我们需要定义我们试图优化的
loss指标,并指定一个优化器。优化器负责在每次训练迭代中计算所有权重的变化,给定总损失和反向传播的损失。使用 Keras 和 TensorFlow,我们可以轻松选择一个最先进的优化器,并为分类使用默认的指标:model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
在前面的代码中,我们定义了一个categorical_crossentropy损失和adam优化器来训练 CNN。我们还跟踪了除了损失之外的另一个指标——accuracy。这使得在训练过程中更容易估计和衡量 CNN 的性能。
-
在开始训练之前,我们必须定义一个模型检查点。这很重要,因为它允许我们在每个 epoch 之后在任何给定时间暂停和恢复训练。使用 Keras,实现这一点相当简单,如下所示:
from tensorflow.keras.callbacks import ModelCheckpoint checkpoint_path = "./mnist_cnn.bin" checkpoint_cb = ModelCheckpoint(checkpoint_path) -
最后,我们可以通过在 Keras 模型上调用
fit方法来在本地开始训练。我们必须提供训练数据以及训练的批大小和 epoch(迭代)数。我们还必须传递之前创建的callback模型检查点,这样我们就可以在每个 epoch 后保存模型:model.fit(X_train, y_train, batch_size=16, epochs=10, callbacks=[checkpoint_cb]) -
最后,我们可以使用最后一个 epoch 训练好的模型在测试集上计算最终得分:
from tensorflow.keras.models import load_model model = load_model(checkpoint_path) scores = model.evaluate(X_test, y_test, verbose=1) print('Test loss:', scores[0]) print('Test accuracy:', scores[1])
在前面的代码中,我们可以看到在 Azure Machine Learning 的计算实例上训练 CNN 是直接且与在本地机器上训练模型类似的。唯一的区别是我们必须确保所有必需的库(及其所需版本)都已安装,并且数据是可用的。
使用增强生成更多输入数据
DL 模型通常有数百万个参数来表示训练集分布的模型。因此,在处理 DL 时,无论是使用认知服务进行自定义视觉、Azure Machine Learning Studio 或 ML 服务工作区中的自定义模型,都应该始终实现数据增强。
数据增强是一种通过稍微修改现有数据并提供修改后的数据给 ML 算法来创建更多训练数据的方法。根据用例的不同,这可能包括镜像、平移、缩放或倾斜图像,以及改变图像的亮度、亮度和颜色信息。这些修改可以极大地提高模型的泛化能力,例如,使模型能够更好地实现尺度、平移、旋转和变换的不变性。
使用 TensorFlow 和 Keras 的好处是数据增强是一个内置功能。首先,我们可以创建一个 ImageDataGenerator 对象,该对象存储了所有我们的修改,并且可以通过增强数据集生成迭代器。此生成器的数据增强技术可以在初始化生成器时进行配置。然而,我们希望使用生成器简单地遍历训练图像而不进行增强,并在连接好所有组件后再添加增强。让我们看一下:
-
让我们在 Keras 中使用
ImageDataGenerator对象实现一个图像数据生成器:from tensorflow.keras.preprocessing.image import \ ImageDataGenerator datagen = ImageDataGenerator() -
现在,我们可以通过将原始训练图像数据和标签传递给生成器来从图像数据生成器返回一个数据迭代器。在我们从生成器中采样图像之前,我们需要计算训练集统计信息,这些统计信息将用于进一步的增强。类似于 scikit-learn 的
BaseTransformer接口,我们需要在生成器上调用fit方法:datagen.fit(x_train) -
接下来,我们必须使用
flow方法创建一个迭代器:it = datagen.flow(X_train, y_train, batch_size=16) -
如果我们不想事先将图像加载到 NumPy 数组中,而是想从文件夹中读取单个图像,我们可以使用不同的生成器函数来完成,如下面的代码片段所示:
it = datagen.flow_from_directory( directory='./data/mnist', target_size=(28, 28), batch_size=16, class_mode='categorical')
然而,在我们的例子中,训练图像已经被合并到一个单独的文件中,因此我们不需要自己加载图像数据。
-
现在,我们可以使用迭代器来遍历数据生成器,并在每次迭代中产生新的训练样本。为此,我们需要用
fit_generator函数替换fit函数,该函数期望一个迭代器而不是训练数据集:model.fit_generator(it, steps_per_epoch=256, epochs=10, callbacks=[checkpoint_cb])
如我们所见,我们可以将相同的epoch和callback参数传递给fit_generator函数,就像我们传递给fit函数一样。唯一的区别是现在,我们需要在每个 epoch 中固定几个步骤,以便迭代器产生新的图像。一旦我们将增强方法添加到生成器中,理论上我们可以在每个 epoch 中为每个训练图像生成无限多的修改。因此,使用此参数,我们可以定义我们希望每个 epoch 训练多少批数据,这应该大致对应于训练样本数除以批大小。
最后,我们可以配置数据增强技术。默认的图像数据生成器通过不同的参数支持各种增强:
-
翻译或平移
-
水平或垂直翻转
-
旋转
-
亮度
-
缩放
让我们回到图像数据生成器并激活数据增强技术。以下是一个常用于图像处理数据增强的示例生成器:
datagen = ImageDataGenerator(
featurewise_center=True,
featurewise_std_normalization=True,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)
通过使用此数据生成器,我们可以使用增强图像数据来训练模型,并进一步提高 CNN 的性能。正如我们之前所看到的,这是任何深度学习(DL)训练流程中的关键步骤,并且强烈推荐。
让我们将迄今为止开发的全部代码移动到一个名为scripts/train.py的文件中。我们将在下一节中使用此文件在 GPU 集群上安排和运行它。
使用 Azure 机器学习在 GPU 集群上进行训练
现在我们已经准备好训练脚本,验证了脚本的工作,并添加了数据增强,我们可以将此训练脚本移动到更高效的执行环境中。在深度学习中,许多操作,如卷积、池化和通用张量运算符,可以从并行执行中受益。因此,我们将训练脚本在 GPU 集群上执行,并在创作环境中跟踪其状态。
使用 Azure 机器学习的优点之一是我们可以从创作环境中设置和运行所有内容——即运行在 Azure 机器学习计算实例上的 Jupyter 笔记本:
-
首先,我们必须配置我们的 Azure 机器学习工作区,这是一个在计算实例上不带参数的单个语句:
from azureml.core.workspace import Workspace ws = Workspace.from_config() -
接下来,我们必须为训练过程加载或创建一个具有自动扩展功能的 GPU 集群:
from azureml.core.compute import ComputeTarget, \ AmlCompute from azureml.core.compute_target import \ ComputeTargetException cluster_name = "gpu-cluster" vm_size = "STANDARD_NC6" max_nodes = 3 try: compute_target = ComputeTarget(ws, name=cluster_name) print('Found existing compute target.') except ComputeTargetException: print('Creating a new compute target...') compute_config = \ AmlCompute.provisioning_configuration( vm_size=vm_size, max_nodes=max_nodes) # create the cluster and wait for completion compute_target = ComputeTarget.create(ws, cluster_name, compute_config) compute_target.wait_for_completion(show_output=True)
如前述代码片段所示,使用 Azure 机器学习创建具有自动扩展功能的 GPU 集群只需要在 Jupyter 中编写几行代码。但我们是如何选择虚拟机大小和 GPU 集群节点数量的呢?
通常,您可以从 Azure 的 N 系列虚拟机中的 NC、ND 和 NV 类型中进行选择。较晚的版本号(例如,v2 或 v3)通常意味着更新的硬件,因此有更新的 CPU 和 GPU,以及更好的内存。您可以将不同的 N 系列版本视为应用类型(NC,其中 C 代表计算;ND,其中 D 代表深度学习;NV,其中 V 代表视频)。以下表格将帮助您比较不同的 N 系列虚拟机类型及其 GPU 配置。大多数机器可以扩展到每个虚拟机四个 GPU。
下表显示了 Azure VM N 系列的比较:

图 10.8 – Azure VM N 系列成本
上表中的价格代表 2021 年 12 月美国西部 2 区域 Linux VM 的按量付费价格。请注意,这些价格在你阅读此内容时可能已经发生变化,但它应该能给你一个不同选项和配置的选择指示。
为了更好地了解成本和性能,我们可以查看在 ImageNet 数据集上训练 ResNet50 模型的典型工作负载。以下由 Nvidia 提供的表格显示,选择最新的 GPU 模型是有意义的,因为它们的性能提升更好,成本也更有效率,比旧款 GPU 模型更优。

图 10.9 – GPU 成本
如前表所示,对于相同任务的较短的训练时间所显示的性能提升是有回报的,并且导致整体任务的成本大大降低。
因此,从定价角度来看,STANDARD_NC6 模型是开始在 Azure 上进行 GPU、CNN 和 DNN 实验的一个很好的起点。我们唯一需要确保的是我们的模型可以适应 VM 可用的 GPU 内存。计算这个的一个常见方法是计算模型的参数数量,乘以 2 以存储梯度(仅进行推理时乘以 1),乘以批处理大小,再乘以 4 以字节为单位表示的单精度大小(或乘以 2 以表示半精度)。
在我们的例子中,CNN 架构需要 1.6 MB 来存储可训练参数(权重和偏差)。为了存储批处理大小为 16 的反向传播损失,我们需要大约 51.2 MB(1.6 MB x 16 x 2)的 GPU 内存来在单个 GPU 上执行整个端到端训练。这也很容易适应我们最小的 NC 实例中的 12 GB GPU 内存。
重要提示
虽然这些数字对于我们的测试案例来说似乎很小,但你经常会处理更大的模型(参数数量高达 1 亿)和更大的图像尺寸。为了更直观地说明这一点,ResNet152 在 224 x 224 x 3 的图像尺寸上训练时,大约有 6000 万个参数和 240 MB 的大小。根据我们的公式,在 STANDARD_NC6 实例上,我们最多可以在批处理大小为 24 的情况下进行训练。
通过向集群添加更多的 GPU 或节点,我们必须引入一个不同的框架来利用分布式设置。我们将在第十二章,“Azure 上的分布式机器学习”中更详细地讨论这个问题。然而,我们可以通过自动扩展添加更多的节点到集群,这样多个人可以同时提交多个作业。最大节点数可以计算为每个节点的并发模型数乘以同时要训练的峰值模型数。在我们的测试场景中,我们将选择3个节点的集群大小,这样我们就可以同时安排几个模型。
-
既然我们已经决定了虚拟机的大小和 GPU 配置,我们就可以继续进行训练过程。接下来,我们需要确保集群可以访问训练数据。为此,我们将使用 Azure 机器学习工作区上的默认数据存储库:
ds = ws.get_default_datastore() ds.upload(src_dir='./data/mnist', target_path='mnist', show_progress=True)
在前面的代码中,我们将训练数据从本地机器复制到了默认的数据存储库——blob 存储账户。正如我们在第四章,“数据摄取与管理数据集”中讨论的那样,还有其他方法可以将您的数据上传到 blob 存储或其他存储系统。
将 blob 存储挂载到机器上,甚至是一个集群,通常不是一个简单的过程。是的,您可以在集群的每个节点上挂载一个 NAS 作为网络驱动器,但这设置和扩展起来都很繁琐。使用 Azure 机器学习数据存储 API,我们可以简单地请求一个数据存储库的引用,这个引用可以用来在每个需要访问数据的机器上挂载正确的文件夹:
ds_data = ds.as_mount()
前面的命令返回一个Datastore Mount对象,看起来并不特别强大。然而,如果我们把这个引用作为参数传递给训练脚本,它可以在 Azure 机器学习中的每个训练计算上自动挂载数据存储库并读取内容。如果您曾经玩过挂载点或fstab,您会理解这一行代码可以加快您的日常工作流程。
-
现在,我们可以创建一个 Azure 机器学习配置。让我们创建
ScriptRunConfiguration,这样我们就可以在集群上安排训练脚本:from azureml.core import ScriptRunConfig script_params={ '--data-dir': ds_data } src = src = ScriptRunConfig( source_directory='./scripts', script='train.py', compute_target=compute_target, environment=tf_env) -
要从指定的默认数据存储库读取数据,我们需要解析
train.py脚本中的参数。让我们回到脚本,用以下代码块替换文件加载代码:import argparse parser = argparse.ArgumentParser() parser.add_argument('--data-dir', type=str) args = parser.parse_args() DIR = args.data_dir X_train = load(DIR + 'train-images.gz', False) / 255.0 X_test = load(DIR + 'test-images.gz', False) / 255.0 y_train = load(DIR + 'train-labels.gz', True) \ .reshape(-1) y_test = load(DIR + 'test-labels.gz', True) \ .reshape(-1) -
这就剩下在 GPU 集群上安排和运行脚本了。然而,在这样做之前,我们想要确保所有运行都在 Azure 机器学习服务中被跟踪。因此,我们还需要在
train.py文件中添加Run,并重用来自第三章,“准备 Azure 机器学习工作区”的 Keras 回调。以下是训练脚本的样子:from azureml.core import Run # Get the run configuration run = Run.get_context() # Create an Azure Machine Learning monitor callback azureml_cb = AzureMlKerasCallback(run) callbacks = [azureml_cb, checkpoint_cb] model.fit_generator(it, steps_per_epoch=256, epochs=10, callbacks=callbacks) # Load the best model model = load_model(checkpoint_path) # Score trained model scores = model.evaluate(X_test, y_test, verbose=1) print('Test loss:', scores[0]) run.log('Test loss', scores[0]) print('Test accuracy:', scores[1]) run.log('Test accuracy', scores[1])
正如我们所见,我们添加了Run配置和 Keras 回调来跟踪整个训练过程中的所有指标。我们还收集了最终的测试集指标,并将其报告给 Azure 机器学习服务。您可以在本书提供的代码中找到完整的可运行示例。
通过转移学习提高您的性能
在许多情况下,您可能没有包含数亿个标记训练样本的数据集,这是完全可以理解的。那么,您如何还能从所有之前的工作和基准测试中受益呢?难道在识别动物的特征提取器训练后,在识别面部时表现不佳吗?分类器当然会不同,但从图像中提取的视觉特征应该是相似的。
这就是faces数据集、CoCo数据集等背后的想法,并在模型的末端附加一个自定义分类器。转移学习意味着我们可以将一个模型从一项任务的特征转移到另一项任务:例如,从分类到目标检测。一开始可能会有些困惑,是否希望为不同的任务重用特征。然而,如果一个模型已经被训练来识别图像中的地理形状模式,这个相同的特征提取器当然可以用于同一领域中的任何与图像相关的任务。
转移学习的一个有用特性是,初始学习任务不一定需要是一个监督式机器学习任务,因此不需要有标注的训练数据来训练特征提取器。一种流行的无监督机器学习技术称为自编码器,其中机器学习模型试图使用特征提取器和上采样网络,根据输入生成类似的外观输出。通过最小化生成的输出与输入之间的误差,特征提取器学会在潜在空间中高效地表示输入数据。自编码器在预训练网络架构之前,使用实际机器学习任务的预训练权重之前很受欢迎。
我们需要确保预训练模型是在同一领域的数据集上训练的。生物细胞图像与面部看起来非常不同,云与建筑物看起来也非常不同。一般来说,ImageNet 数据集涵盖了广泛的照片风格图像,用于许多标准视觉特征,如建筑物、汽车、动物等。因此,对于许多计算机视觉任务,使用预训练模型是一个很好的选择。
转移学习不仅与计算机视觉中的图像数据和建模数据相关。转移学习在数据集足够相似的所有领域都已被证明是有价值的,例如人类声音或书面文本。因此,无论何时您在实现深度学习模型时,都要研究可用于转移学习的数据集,以及最终提高模型性能。
让我们将理论应用于实践,并深入研究一些示例。我们在本章前面看到了一个类似的例子,其中我们将特征提取器的输出管道化到一个 SVM。在本节中,我们想要实现类似的效果,但结果将是一个单一端到端模型。因此,在这个例子中,我们将构建一个由预训练的特征提取器和新的分类器头部组成的新模型网络架构:
-
首先,我们必须定义输出类的数量、输入形状,并从 Keras 加载基本模型:
from tensorflow.keras.applications.resnet50 \ import ResNet50 num_classes = 10 input_shape = (224, 224, 3) # create the base pre-trained model base_model = ResNet50(input_shape=input_shape, weights='imagenet', include_top=False, pooling='avg')
在前面的代码中,预训练的大部分魔法都归功于 Keras。首先,我们使用weights参数指定了将用于训练此模型的图像数据集,这将自动使用预训练的imagenet权重初始化模型权重。通过第三个参数include_top=False,我们告诉 Keras 只加载模型的特征提取部分。使用pooling参数,我们还指定了最后一个池化操作应该如何执行。在这种情况下,我们选择了平均池化。
-
接下来,我们必须通过将它们的
trainable属性设置为False来冻结模型的层。为此,我们可以简单地遍历模型中的所有层:for layer in base_model.layers: layer.trainable = False -
最后,我们可以将任何网络架构附加到我们想要的模型上。在这种情况下,我们将附加与上一节中 CNN 网络中使用的相同分类器头部。最后,我们必须使用新的架构和输出作为分类器输出层来构建最终的模型类:
from tensorflow.keras.models import Model from tensorflow.keras.layers import Flatten, Dense clf = base_model.output clf = Dense(256, activation='relu')(clf) clf = Dense(10, activation='softmax')(clf) model = Model(base_model.input, clf)
就这样!你已经成功构建了一个新的端到端模型,该模型结合了在 ImageNet 上预训练的 ResNet50 特征提取器以及你的自定义分类器。现在你可以使用这个 Keras 模型,将其插入你偏好的优化器中,并发送到 GPU 集群。训练过程的输出将是一个可以像任何其他自定义模型一样管理和部署的单个模型。
重要提示
你不必总是冻结原始网络的所有层。一个常见的方法是同时解冻网络中的后续层,将学习率至少降低 10 倍,并继续训练。通过重复此过程,我们甚至可以以逐步降低学习率的方式重新训练(或微调)网络的所有层。
不论你的选择和使用案例如何,你应该将迁移学习添加到你的标准训练深度学习模型的方法库中。将其视为其他流行的预处理和训练技术,例如数据增强,这些技术应该在训练深度学习模型时始终使用。
摘要
在本章中,我们学习了何时以及如何使用深度学习(DL)在 Azure 上训练机器学习(ML)模型。我们使用了 Azure 机器学习服务中的计算实例和 GPU 集群来使用 Keras 和 TensorFlow 训练模型。
首先,我们发现深度学习在具有非明显关系的结构化数据上工作得非常好,这些关系是从原始输入数据到最终预测结果。好的例子包括图像分类、语音转文本和翻译。我们还看到,深度学习模型是具有大量参数的参数模型,因此我们通常需要大量的标记或增强的输入数据。与传统机器学习方法相比,额外的参数用于训练一个完全端到端的模型,这还包括从原始输入数据中提取特征。
使用 Azure 机器学习服务训练 CNN 并不困难。我们看到了许多方法,从在 Jupyter 中进行原型设计到增强训练数据,再到在具有自动扩展功能的 GPU 集群上运行训练。在深度学习中,困难的部分在于准备和提供足够的高质量训练数据,找到一个描述性的错误度量标准,以及在成本和性能之间进行优化。我们概述了如何为您的任务选择最佳的虚拟机和 GPU 大小及配置,我建议您在开始您的第一个 GPU 集群之前先做这件事。
在下一章中,我们将进一步探讨超参数调整和自动机器学习,这是 Azure 机器学习服务中的一个功能,允许您自动训练和优化堆叠模型。
第十一章:第十一章:超参数调整和自动机器学习
在前一章中,我们学习了如何训练卷积神经网络和复杂的深度神经网络。在训练这些模型时,我们经常面临各种参数的艰难选择,例如层数、滤波器维度、层的类型和顺序、正则化、批量大小、学习率、训练轮数等。这不仅适用于 DNN,当我们需要选择正确的预处理步骤、特征、模型和模型参数时,统计机器学习方法也会出现同样的挑战。
在本章中,我们将探讨优化训练过程以移除机器学习(ML)中的一些非最佳人类选择。这将帮助您更快、更高效地训练更好的模型,而无需人工干预。首先,我们将探讨超参数优化(在 Azure 机器学习中也称为HyperDrive),这是一种优化 ML 过程中参数的标准技术。通过评估不同的超参数采样技术,例如随机采样、网格采样和贝叶斯优化,您将学习如何有效地在模型运行时间和模型性能之间进行权衡。
在本章的后半部分,我们将探讨通过使用自动机器学习自动化完整的端到端 ML 训练过程来进行模型优化。这个过程也常被称为AutoML。使用自动机器学习,我们可以在一个抽象的优化管道中优化预处理、特征工程、模型选择、超参数调整和模型堆叠。
Azure 机器学习的一个好处是,参数优化(HyperDrive)和模型优化(自动机器学习)都支持相同的通用方式。这意味着我们可以将它们部署到自动扩展的训练集群中,将最佳模型或参数组合存储在磁盘上,然后在不离开笔记本环境的情况下将最佳模型部署到生产环境中。
本章将涵盖以下主题:
-
使用 HyperDrive 找到最佳模型参数
-
使用自动机器学习找到最佳模型
技术要求
在本章中,我们将使用以下 Python 库和版本来创建基于决策树的集成分类器:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0
与前几章类似,您可以使用本地 Python 解释器或 Azure 机器学习中的笔记本环境运行此代码。然而,所有脚本都需要在 Azure 机器学习训练集群中安排。
本章中的所有代码示例都可以在这个书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter11。
使用 HyperDrive 寻找最佳模型参数
在机器学习中,我们通常处理参数模型或非参数模型。模型代表训练数据的分布,以对同一分布中的未见数据进行预测。虽然参数模型(如线性回归、逻辑回归和神经网络)通过使用学习到的参数集来表示训练数据分布,但非参数模型通过其他特征来描述训练数据分布,如决策树(所有基于树的分类器)、训练样本(k-最近邻)或加权的训练样本(支持向量机)。
参数模型,如线性或逻辑回归,通常由一个与训练数据无关的常数参数数量定义。这些模型对训练数据做出了强烈的假设,因此通常需要较少的训练样本。因此,训练和推理通常都非常快。
相比之下,对于非参数模型,如决策树或 k-最近邻,特征的数量通常随着训练样本数量的增加而增加。虽然这些模型不对训练数据的分布做任何假设,但通常需要许多训练样本。这往往会导致训练速度慢和干扰性能慢。
术语超参数指的是用于配置和调整参数模型或非参数模型训练过程的全部参数。以下是一个神经网络中一些典型超参数的列表:
-
隐藏层的数量
-
每层的单元数量
-
批处理大小
-
过滤维度
-
学习率
-
正则化项
-
Dropout
-
损失度量
训练一个简单的机器学习模型所需的超参数数量和参数值是惊人的。你是否曾经发现自己手动调整训练过程中的参数,比如决策分类器中的分割数量或神经网络分类器中的单元数量?如果是这样,你并不孤单!然而,接受手动调整参数需要对该特定模型或模型配置有深厚的专业知识是非常重要的。然而,我们不可能成为每种统计建模、机器学习和优化的专家,以便手动调整所有可能的参数。鉴于参数选择数量巨大,尝试所有可能的组合是不切实际的,因此我们需要找到一种更好的方法来优化它们。
我们不仅不可能手动尝试所有不同的参数组合,而且在许多情况下,即使有专家知识,我们也无法预测超参数调整的结果。在这种情况下,我们可以开始寻找自动找到最佳参数集的方法。这个过程被称为超参数调整或超参数搜索。
超参数调整涉及自动测试模型针对不同超参数组合集的性能,并最终选择最佳的超参数组合。最佳性能的定义取决于选择的指标和验证方法。例如,使用 f1 分数指标的分层交叉验证将产生与 k 折交叉验证的准确度指标不同的最佳参数集。
我们在这本书中讨论超参数调整(以及稍后讨论的自动机器学习)的一个原因是,我们使用弹性云计算基础设施具有竞争优势。虽然在你笔记本电脑上按顺序训练数百个模型是困难的,但在云中使用廉价的自动扩展计算并行训练数千个模型则很容易。使用廉价的云存储,我们还可以持久化所有潜在的好模型以供后续分析。许多最近的机器学习论文表明,通过使用更多的计算能力或更好的参数选择,我们通常可以实现更好的结果。
在我们开始调整超参数之前,我们想提醒您基线模型的重要性。对于许多实际的机器学习任务,您应该能够使用单个基于树的集成分类器或具有默认参数的预训练神经网络实现良好的性能。如果情况不是这样,超参数调整不会神奇地输出顶级最佳模型的参数。在这种情况下,最好是先回到数据预处理和特征工程,构建一个更好的基线模型,然后再调整批量大小、隐藏单元数量或树的数量。
在超参数调整中需要避免的另一个问题是过拟合以及关注错误的表现指标或验证方法。与任何其他优化技术一样,超参数调整将产生给定损失函数或指标的最佳参数组合。因此,在开始超参数调整之前验证你的损失函数是至关重要的。
与机器学习中的大多数其他技术一样,有多种方法可以找到模型的最佳超参数。最流行的技术是网格搜索、随机搜索和贝叶斯优化。在本章中,我们将研究这三种方法,讨论它们的优缺点,并通过实际示例进行实验。
使用网格搜索对所有可能的参数组合进行采样
网格搜索(或网格采样)是一种通过测试多维参数网格的每个可能的参数组合来从参数网格中找到最佳超参数的流行技术。对于每个参数(连续或分类),我们需要定义所有应该测试的值或值范围。流行的机器学习库提供工具来有效地创建这些参数网格。
两个特性将网格搜索与其他超参数采样方法区分开来:
-
假设所有参数组合都是相互独立的,这意味着它们可以并行测试。因此,给定 100 种可能的参数组合,我们可以启动 100 个模型来并行测试所有组合。
-
通过测试所有可能的参数组合,我们可以确保我们寻找的是全局最优解,而不是局部最优解。
网格搜索对于只有少数超参数的小型机器学习模型工作得很好,但随着每个额外参数的增加,它会呈指数增长,因为它为参数网格添加了一个新的维度。
让我们看看如何使用 Azure 机器学习实现网格搜索。在 Azure 机器学习中,超参数调整功能位于hyperdrive包中。以下是我们要做的事情:
-
创建一个网格采样配置
-
定义一个主要指标来定义调整目标
-
创建
hyperdrive配置 -
将
hyperdrive配置作为实验提交到 Azure 机器学习
让我们更详细地看看这些步骤:
-
首先,我们必须通过定义网格采样的参数选择和范围来创建网格采样配置,如下面的代码块所示:
from azureml.train.hyperdrive import \ GridParameterSampling from azureml.train.hyperdrive.parameter_expressions \ import * grid_sampling = GridParameterSampling({ "--first-layer-neurons": choice(16, 32, 64, 128), "--second-layer-neurons": choice(16, 32, 64, 128), "--batch-size": choice(16, 32) })
在前面的代码中,我们使用离散的参数选择定义了一个参数网格,沿着三个参数维度——第一层的神经元数量、第二层的神经元数量和训练批次大小。
-
参数名称格式化为命令行参数,因为它们将被作为参数传递给训练脚本。因此,我们需要确保训练脚本可以通过命令行参数配置参数。以下代码显示了在您的训练示例中这可能是什么样子:
import argparse parser = argparse.ArgumentParser() parser.add_argument('--batch-size', type=int, default=50) parser.add_argument('--epochs', type=int, default=30) parser.add_argument('--first-layer-neurons', type=int, dest='n_hidden_1', default=100) parser.add_argument('--second-layer-neurons', type=int, dest='n_hidden_2', default=100) parser.add_argument('--learning-rate', type=float, default=0.1) parser.add_argument('--momentum', type=float, default=0.9) args = parser.parse_args()
使用网格采样,我们可以测试这些参数的所有可能组合。这将导致总共 32 次运行(4 x 4 x 2),理论上我们可以并行运行,因为训练运行和参数配置是相互独立的。在这种情况下,所需的总训练运行次数很明显,因为我们只使用离散的参数范围。稍后,我们将看到这并不适用于随机采样和贝叶斯优化。对于这些其他方法,我们从连续分布中进行采样,因此训练运行的次数不会有限制。我们还将看到,当参数选择不是独立时,并行运行的次数会影响优化过程。因此,让我们欣赏网格采样解决方案在少量离散参数上的简单性。
-
接下来,我们需要定义一个度量标准,用于衡量每个参数组合的性能。这个度量标准可以是训练脚本记录的任何数值。请注意,这个度量标准不需要与损失函数相同——它可以是你想要用来比较不同参数对度的任何测量。看看下面的例子。在这里,我们决定最大化
accuracy度量标准,并定义了以下参数:from azureml.train.hyperdrive import PrimaryMetricGoal primary_metric_name = "accuracy" primary_metric_goal = PrimaryMetricGoal.MAXIMIZE
在前面的代码中,我们选择了accuracy度量标准,这是我们想要最大化的。在这里,你可以看到我们只是简单地指定了任何度量名称作为字符串。为了使用此度量标准来评估超参数优化运行,训练脚本需要记录具有此确切名称的度量。我们已经在之前的章节中看到了这一点,其中我们为 Azure Machine Learning 运行发出了度量。
-
我们必须使用相同的
primary_metric_name度量名称来定义和记录一个可以被hyperdrive在训练脚本中评估的度量:from azureml.core.run import Run run = Run.get_context() run.log("accuracy", float(val_accuracy)) -
在我们继续之前,回想一下之前章节中的脚本运行配置。类似于之前的章节,我们必须配置一个基于 CPU 的 Azure Machine Learning 训练集群,定义为
aml_cluster,以及一个包含运行 TensorFlow 所需的所有相关包的环境tf_env:src = ScriptRunConfig(source_directory="train", script="train.py", compute_target=aml_cluster, environment=tf_env) -
现在,我们可以初始化
hyperdrive配置,它由估计器、采样网格、优化度量标准以及运行数和并发运行数组成:from azureml.train.hyperdrive import HyperDriveConfig hyperdrive_run_config = HyperDriveConfig( run_config=src, hyperparameter_sampling=grid_sampling, primary_metric_name=primary_metric_name, primary_metric_goal=primary_metric_goal, max_total_runs=32, max_concurrent_runs=4)
在网格采样中,运行的次数应该与可能的参数组合数相对应。由于这是一个必需的属性,我们需要计算这个值并将其传递到这里。网格采样的最大并发运行数仅受你的 Azure Machine Learning 集群中节点数的限制。我们使用了一个四节点集群,因此我们将数字设置为4以最大化并发性。
-
最后,我们可以将
hyperdrive配置提交给一个实验,该实验将在指定的计算目标上执行所有并发子运行:from azureml.core.experiment import Experiment experiment = Experiment(workspace, experiment_name) hyperdrive_run = experiment.submit(hyperdrive_run_config) print(hyperdrive_run.get_portal_url())
前面的代码片段将启动训练过程,如果需要,将构建和注册新的 Docker 镜像,初始化并扩展集群中的节点,并最终在集群上运行训练脚本。每个脚本将使用采样网格中唯一的参数组合进行参数化。下面的截图显示了生成的实验运行。我们可以通过点击前面代码片段返回的链接来访问这个页面:

图 11.1 – 网格采样概述
在这里,我们可以看到采样策略的名称,它是GRID,以及配置的参数空间。这些参数将被作为命令行参数应用到训练脚本中。
如您可能已经猜到的,当你必须从一个多维网格中采样所有可能的参数组合时,并非一切都很完美。随着超参数数量的增加,网格的维度也会增加。并且每个参数维度都会增加一个数量级的参数配置,需要对其进行测试。别忘了,测试参数配置通常意味着在你的模型上执行训练、交叉验证和测试集预测,这可能需要大量的资源。
假设你想为五个参数寻找最佳参数组合,每个参数有 10 个不同的值。让我们假设以下情况:
-
我们正在测试 10⁵ (10 x 10 x 10 x 10 x 10) 个参数组合。
-
一次训练运行只需 2 分钟。
-
我们正在进行四折交叉验证。
在这里,我们最终会有 555 天(2min x 4 x 10⁵ = 800,000min)的累计训练时间。虽然你可以通过并行运行参数组合来减少总运行时间,但还有其他更适合大量参数的方法,例如随机抽样。让我们看看如何通过随机抽样参数配置来限制参数优化搜索所需的运行时间。
使用随机搜索测试随机组合
随机搜索是另一种流行的超参数抽样方法,类似于网格搜索。主要区别在于,它不是测试所有可能的参数组合,而是随机选择并测试几个组合。主要思想是,网格搜索通常采样对模型性能影响不大的参数配置。因此,我们浪费了大量时间追逐类似的不良解决方案,而我们本可以用这些时间尝试更多样化和可能更成功的参数配置。
当你处理大量的超参数(例如,超过 5 个)时,随机搜索会比网格搜索更快地找到一组好的超参数 – 然而,它可能不是最佳结果。即便如此,在许多情况下,使用随机搜索而不是网格搜索来提高预测性能,通过超参数调整进行权衡将是合理的。
在随机搜索中,参数通常是从连续分布中抽取的,而不是使用离散的参数选择。这导致定义参数网格的方式略有不同。我们不是为不同的值提供选择,而是可以为每个参数定义一个分布函数,以从连续范围内抽取随机值。
与网格搜索类似,如果参数组合是带替换抽取的,则所有参数组合都是独立的,这意味着它们可以被完全并行化。如果提供了一个包含 10,000 个不同参数配置的参数网格,我们就可以并行运行并测试所有模型。
让我们来看看 Azure Machine Learning 中的随机搜索:
-
与所有其他超参数优化方法一样,我们在
hyperdrive包中找到了随机采样方法。正如我们之前讨论的,我们现在可以为每个参数定义概率分布函数,例如normal和uniform,而不是只选择离散参数:from azureml.train.hyperdrive import \ RandomParameterSampling from azureml.train.hyperdrive.parameter_expressions \ import * random_sampling = RandomParameterSampling({ "--learning-rate": normal(10, 3), "--momentum": uniform(0.5, 1.0), "--batch-size": choice(16, 32, 64) })
使用连续参数范围是随机采样中的唯一不同之处。由于可以从连续范围中采样无限数量的参数配置,我们需要一种方法来指定搜索的持续时间。我们可以使用max_total_runs和max_duration_minutes参数来定义预期的运行时间(以分钟为单位)或限制采样参数配置的数量。
-
让我们测试 25 种不同的配置,并将超参数调整过程运行最长 60 分钟。我们必须设置以下参数:
max_total_runs = 25 max_duration_minutes = 60 -
我们将重用之前定义的相同指标,即准确率。
hyperdrive配置如下所示:from azureml.train.hyperdrive import HyperDriveConfig hyperdrive_run_config = HyperDriveConfig( run_config=src, hyperparameter_sampling=random_sampling, primary_metric_name=primary_metric_name, primary_metric_goal=primary_metric_goal, max_total_runs=max_total_runs, max_duration_minutes=max_duration_minutes) -
与之前的示例类似,我们必须从作者运行时提交
hyperdrive配置到 Azure Machine Learning,这将安排所有优化运行在计算目标上:from azureml.core.experiment import Experiment experiment = Experiment(workspace, experiment_name) hyperdrive_run = experiment.submit(hyperdrive_run_config) print(hyperdrive_run.get_portal_url())
随机采样是测试大量可调超参数或从连续范围中采样值的绝佳选择。然而,我们不是逐步优化参数配置,而是简单地随机尝试所有这些配置,并比较它们的性能。
在下一节中,我们将学习如何通过提前停止训练运行来更快地找到好的参数组合。在随后的章节使用贝叶斯优化优化参数选择中,我们将探讨在超参数调整中通过优化导航参数空间的一种更优雅的方法。
使用提前终止更快地收敛
网格和随机采样技术都会测试模型对参数选择的不足,因此会浪费宝贵的计算资源来拟合参数较差的模型到您的训练数据中。提前终止是一种在中间结果看起来比其他运行更差时提前停止训练的技术。这是加快昂贵超参数优化技术的绝佳解决方案。
通常,您应该始终尝试在使用网格或随机采样时使用提前终止。如果结果比一些现有运行差得多,那么训练所有参数组合就没有任何好处。
一旦我们理解了取消表现不佳的运行的想法,我们需要找到一种方法来指定何时应该取消运行的阈值——我们将这个阈值称为终止策略。Azure Machine Learning 提供了最流行的终止策略,即探索者、中位数停止和截断选择。让我们来看看它们,并了解它们之间的区别。
在我们深入了解细节之前,让我们学习如何配置早期终止。在 Azure Machine Learning 中,我们可以使用两个全局属性对不同的早期终止策略进行参数化,即evaluation_interval和delay_evaluation。这些参数控制早期终止策略被测试的频率。以下是如何使用这些参数的示例:
evaluation_interval = 1
delay_evaluation = 10
这两个参数的单位都是间隔。例如,当你训练一个神经网络时,一个间隔等于一个训练 epoch。delay_evaluation参数控制我们在第一次测试早期终止策略之前需要等待多少个间隔。在先前的例子中,我们将其配置为10,这意味着我们等待 10 个 epoch 之后才测试早期终止策略。
然后,每个其他策略评估都使用evaluation_interval参数进行配置。它描述了需要经过多少次迭代才能进行下一次测试。在先前的例子中,我们将evaluation_interval设置为1,这也是默认值。这意味着我们在delay_evaluation间隔之后每隔一个间隔测试早期终止策略——在这里,每 1 次迭代。让我们更详细地看看三种终止策略。
中值终止策略
让我们从最简单的终止策略开始——中值终止策略。它不需要除了两个默认参数之外的其他参数,这两个参数控制策略何时以及多久应该被测试。中值终止策略会跟踪所有实验运行中主要指标的平均值。每当评估中值策略时,它都会测试当前指标是否高于所有运行实验的中位数,并停止那些低于中位数的运行。以下代码展示了如何为任何超参数调整脚本创建一个中值终止的早期终止策略:
from azureml.train.hyperdrive import MedianStoppingPolicy
early_termination_policy = MedianStoppingPolicy(
evaluation_interval=evaluation_interval,
delay_evaluation=delay_evaluation)
如我们所见,构建中值终止策略非常简单,因为它只由两个默认参数配置。由于其简单性,它是一种非常有效的方法,可以减少你的超参数优化脚本的运行时间。然后,使用policy参数将早期终止策略应用于hyperdrive配置文件。现在,让我们看看截断选择策略。
截断选择策略
与中值终止策略不同,truncation_percentage参数:
truncation_percentage = 10
evaluation_interval = 5
delay_evaluation = 10
在先前的例子中,我们将truncation_percentage值设置为10。这意味着每当早期终止策略执行时,它将终止表现最差的 10%的运行。我们还必须将evaluation_interval值增加到5,因为我们不想像以下示例中那样在每个 epoch 结束时终止运行:
from azureml.train.hyperdrive import TruncationSelectionPolicy
early_termination_policy = TruncationSelectionPolicy(
truncation_percentage=truncation_percentage,
evaluation_interval=evaluation_interval,
delay_evaluation=delay_evaluation)
这种早期终止策略在只有很少的训练资源可用,并且我们希望在每次评估早期终止策略时积极修剪运行数量时是有意义的。让我们看看最终的策略——bandit 策略。
Bandit 策略
slack_factor或slack_amount参数。slack_factor参数描述了相对于最佳指标的相对偏差,而slack_amount参数描述了相对于最佳主要指标的绝对偏差。
让我们来看一个例子。在这里,我们将通过配置slack_factor参数为0.2并测试一个准确度值(越大越好)来配置hyperdrive。像之前一样,我们将evaluation_interval值设置为5,将evaluation_delay值设置为10个间隔:
slack_factor = 0.2
evaluation_interval = 5
delay_evaluation = 10
from azureml.train.hyperdrive import BanditPolicy
early_termination_policy = BanditPolicy(
slack_factor = slack_factor,
evaluation_interval=evaluation_interval,
delay_evaluation=delay_evaluation)
假设性能最佳的运行在 10 个 epoch 后产生了 0.8 的准确度,这是早期终止策略第一次被应用的时候。现在,所有性能比最佳指标差 20%以上的运行都将被终止。我们可以通过以下函数计算从 0.8 准确度出发的相对偏差:
0.8/(1 + 0.2) = 0.67
因此,所有性能低于 0.67 的运行都将被早期终止策略取消。
带有终止策略的 HyperDrive 配置
要创建一个hyperdrive配置,我们需要使用policy参数传递早期终止策略。以下是一个使用网格搜索采样和之前定义的 bandit 策略的示例:
from azureml.train.hyperdrive import HyperDriveConfig
hyperdrive_run_config = HyperDriveConfig(
run_config=src,
hyperparameter_sampling=grid_sampling,
policy=early_termination_policy,
primary_metric_name="accuracy",
primary_metric_goal=PrimaryMetricGoal.MAXIMIZE)
Bandit 策略是中值停止和截断选择策略之间的一种良好权衡,后者在许多情况下都表现良好。你可以放心,只有所有超参数配置中表现良好的子集将在多个间隔内运行和评估。
让我们将这个 HyperDrive 配置作为一个实验提交到 Azure 机器学习。我们可以使用之前章节中看到的RunDetails方法来输出关于超参数调整实验的附加信息,例如调度和参数信息、训练性能的可视化,以及显示参数维度的并行坐标图:
from azureml.widgets import RunDetails
hyperdrive_run = exp.submit(hyperdrive_run_config)
RunDetails(hyperdrive_run).show()
如果你运行前面的代码,它将运行配置策略的超参数搜索。一旦实验开始运行,你将看到作为小部件中图表的指定指标,对于单个参数组合和迭代:

图 11.2 – HyperDrive – 运行的性能
除了查看定义的指标外,你还可以选择其他可视化,显示采样参数,例如在并行坐标图上,或作为二维和三维散点图。在这里,你可以看到哪些参数组合产生了高模型准确度:

图 11.3 – HyperDrive – 结果的可视化
在本节中,你了解到将早期终止策略应用于你的超参数优化脚本是一种简单但极其有效的方法,可以减少表现不佳的训练运行次数。只需几行代码,我们就可以将训练运行的次数减少到最小,并且只完成那些产生有希望结果的任务。
重要提示
当你使用随机或网格采样进行超参数优化时,始终使用早期终止策略。
使用贝叶斯优化优化参数选择
在前面的例子中,我们评估了从网格或随机采样的不同参数配置,而没有进行任何优化或战略性的参数选择。这有一个好处,即所有配置都是独立的,并且可以并行评估。然而,想象一下使用 ML 模型帮助我们找到大型多维参数空间中最佳参数组合的情况。这正是贝叶斯优化在超参数调整领域所做的事情。
优化方法的工作是找到预定义目标函数的最优值(即最小值或最大值)。在超参数调整中,我们面临一个非常类似的问题:我们想要找到产生最佳预定义评估指标的参数配置。
那么,超参数搜索的优化是如何工作的呢?首先,我们必须定义一个超平面——一个多维网格,我们可以从中采样参数配置。在下面的图中,我们可以看到沿着x和y轴的两个参数的这样一个平面。z轴表示使用该特定位置的参数测试的模型的性能:

图 11.4 – Rastrigin 函数
上述图显示了多维 Rastrigin 函数,作为一个极其难以优化的例子。在超参数调整中,我们经常面临类似的问题,即找到最优解是困难的——就像在 Rastrigin 函数中找到全局最小值一样。
然后,我们必须从这个平面上采样点并测试第一个(几个)参数配置。我们假设参数不是独立的,并且当使用相似的邻近参数时,模型将具有相似的性能。然而,每次评估只能得到真实模型性能的噪声值。利用这些假设,我们可以使用高斯过程将模型评估组合成一个多元连续高斯分布。接下来,我们可以计算在这个高斯上预期改进最高的点。这些点将产生新的样本,以便用我们的模型进行测试。
幸运的是,我们不必自己实现算法,许多机器学习库都提供了开箱即用的超参数优化算法。在 Azure 机器学习中,我们可以使用贝叶斯采样方法,这有助于我们选择好的参数配置来优化预定义的指标。
参数网格的定义与随机采样技术类似——即通过使用连续或离散的参数空间来定义所有参数值,如下面的代码块所示:
from azureml.train.hyperdrive import BayesianParameterSampling
from azureml.train.hyperdrive.parameter_expressions import *
bayesian_sampling = BayesianParameterSampling({
"--learning-rate": normal(10, 3),
"--momentum": uniform(0.5, 1.0),
"--batch-size": choice(16, 32, 64)
})
在我们继续之前,我们需要记住一件事。贝叶斯采样技术试图根据先前测试的参数结果来预测性能良好的参数配置。这意味着参数选择和运行不再独立。我们不能同时并行运行所有实验,因为我们需要某些实验的结果来采样新的参数。因此,我们需要设置一个额外的参数来控制应该同时运行多少个训练运行。
我们可以使用max_concurrent_runs参数来实现这一点。为了使贝叶斯优化技术收敛,建议将此值设置为较小的值,例如,在 2-10 的范围内。让我们将此实验的值设置为 4,并将总运行次数设置为 100。这意味着我们正在使用 25 次迭代来应用贝叶斯优化方法,其中我们一次探索四个参数配置:
max_concurrent_runs = 4
max_total_runs = 100
让我们用贝叶斯采样来启动实验:
from azureml.train.hyperdrive import HyperDriveConfig
from azureml.core.experiment import Experiment
hyperdrive_run_config = HyperDriveConfig(
estimator=estimator,
hyperparameter_sampling=bayesian_sampling,
primary_metric_name=primary_metric_name,
primary_metric_goal=primary_metric_goal,
max_total_runs=max_total_runs,
max_concurrent_runs=max_concurrent_runs)
experiment = Experiment(workspace, experiment_name)
hyperdrive_run = experiment.submit(hyperdrive_run_config)
print(hyperdrive_run.get_portal_url())
不幸的是,由于所有参数选择都依赖于前一次迭代的输出,这种技术无法进一步并行化以更快地完成。然而,由于优化步骤,它通常在相对较短的时间内产生良好的结果。
贝叶斯优化或超参数调优的另一个缺点是,优化需要计算每个运行中定义的参数配置的结果,以确定新的参数选择。因此,我们不能与贝叶斯采样一起使用早期终止,因为训练将提前停止,这意味着无法计算准确的指标。
重要提示
对于贝叶斯优化等优化技术,早期终止不起作用,因为它需要计算最终测试分数来计算参数梯度。
一旦你尝试使用机器学习来优化机器学习模型,你可能已经考虑将这一步更进一步:为什么我们应该止步于优化超参数,为什么我们不应该优化模型选择、网络结构或模型堆叠?
这是一个完全合理的想法。没有人能够测试所有不同机器学习模型、不同参数配置和不同嵌套模型的变体。在下一节中,我们将做这件事,不仅优化参数,还将使用自动机器学习优化模型架构和预处理步骤。
使用自动化机器学习寻找最佳模型
自动化机器学习是一个令人兴奋的新趋势,许多(如果不是所有)云服务提供商都在追随。目标是向用户提供一种服务,该服务可以自动预处理您的数据,选择机器学习模型,并训练和优化模型以适应您的训练数据,从而优化指定的误差指标。这将创建和训练一个完全自动化的端到端机器学习管道,只需您的标记训练数据和目标指标作为输入。以下是自动化机器学习为您优化的步骤列表:
-
数据预处理
-
特征工程
-
模型选择
-
超参数调整
-
模型集成
虽然大多数经验丰富的机器学习工程师或数据科学家可能会对这种自动化方法的有效性非常谨慎,但它仍然有很多好处,将在本节中解释。如果您喜欢超参数调整的想法,那么您会发现自动化机器学习很有价值。
考虑自动化机器学习的一个好方法是,它在完整的端到端机器学习管道上执行超参数搜索,类似于贝叶斯优化,但参数空间要大得多。现在,这些参数是端到端机器学习管道中的单独步骤,应该实现自动化。自动化机器学习的优点在于,它不会像愚蠢地采样所有可能的参数选择那样,而是在实际训练模型之前,预测某些预处理步骤和模型在数据集上的表现。这个过程被称为元学习,并将帮助优化过程产生对管道的候选解决方案,而无需花费时间进行评估。
自动化机器学习的优势
让我们评估自动化机器学习的优势。如果我们看看我们之前提到的自动化步骤列表,每个步骤都需要经验丰富的数据科学家花费数天时间来探索、评估和微调。即使是选择正确的模型,例如用于基于梯度的树集成分类的 LightGBM 或 XGBoost,也是非平凡的,因为它们需要对这些工具的经验和知识。此外,我们都知道这两个只是所有可能的分类模型选项的一个非常小的子集。如果我们看看超参数调整和模型堆叠,我们可以立即看出构建一个优秀的集成模型所需的工作量是非平凡的。
这不仅是一个知识或专业知识的问题,而且也非常耗时。自动化机器学习的目标是用自动化的最佳实践来替代手动步骤,应用持续改进的规则,并对每个可能的人类选择进行大量优化。它与超参数调整非常相似,但针对的是完整的端到端过程。通过使用优化而不是手动选择,机器将比人类更快、更准确地找到最佳参数。
我们还可以从另一个角度看待自动化机器学习,即作为 机器学习即服务(MLaaS) 产品:输入数据,输出模型(或预测端点)。到目前为止,你应该已经意识到,构建端到端机器学习管道的每一步都是一个彻底、复杂且耗时的任务。即使你可以使用贝叶斯优化选择正确的模型和调整参数,构建和运营此基础设施的成本也是显著的。在这种情况下,选择 MLaaS 将为你提供通常成本的一小部分机器学习基础设施。
自动化机器学习的想法之所以非常有趣,还有另一个原因。它将机器学习部分与你的数据拟合问题分开,让你专注于你最擅长的数据。类似于在云中使用托管服务(例如,托管数据库),这让你可以专注于实现业务逻辑而不是操作基础设施,自动化机器学习将允许你使用基于最佳实践和数据优化而不是特定机器学习算法的托管机器学习管道。
这也是为什么自动化机器学习仍然非常适合许多(成熟)公司的原因——它将预测问题简化为最重要的任务:
-
数据获取
-
数据清洗
-
数据标注
-
选择错误度量标准
我们不想评判任何人,但机器学习从业者往往喜欢跳过这些话题,直接进入有趣的环节,即特征工程、模型选择、参数化、堆叠和调整。因此,每个机器学习项目的良好开端是从自动化机器学习基线模型开始,因为它将迫使你只关注数据方面。在取得良好的初始分数后,你总是可以继续进行进一步的特征工程,并在需要时构建模型。
现在我们已经讨论了自动化机器学习趋势的合理性以及你可以在某种程度上从中受益,让我们深入探讨一些示例和代码。我们将查看 Azure 自动化机器学习(Azure 机器学习的一个产品)的不同功能,它应用于标准的端到端机器学习管道。
在我们深入代码之前,让我们先看看 Azure 自动化机器学习可以解决哪些问题。一般来说,在自动化机器学习中,我们可以选择 分类、回归 和 时间序列预测。正如我们从前几章所知,时间序列预测只是回归的一种变体,其中所有预测值都在未来。
因此,选择正确的机器学习任务之后的最重要的任务是选择应该优化的适当错误度量标准。以下列表显示了所有受支持的错误度量标准:
-
accuracy、AUC_weighted、average_precision_score_weighted、norm_macro_recall和precision_score_weighted -
spearman_correlation,normalized_root_mean_squared_error,r2_score, 和normalized_mean_absolute_error
您应该熟悉这些大多数指标,因为它们是最受欢迎的错误度量分类和回归的变体。
在支持的模型中,包括 LogisticRegression, SGD, MultinomialNaiveBayes, SVM, KNN, Random Forest, ExtremeRandomTrees, LigthtGBM, GradientBoosting, DNN, Lasso, Arima, Prophet 等。在云中托管服务的优点在于,这个列表很可能会在未来增长,并添加最新的最先进模型。然而,这个列表应该被视为仅为您提供的附加信息,因为自动机器学习的理念是模型会自动为您选择。然而,根据用户的偏好,可以为自动机器学习允许或拒绝列表中的单个模型。
考虑到所有这些,让我们看看一个使用自动机器学习的分类示例。
自动机器学习的一个分类示例
当您使用新技术时,总是好的退一步思考这项技术可能具备的能力。让我们使用相同的方法来了解自动预处理如何帮助我们在一个典型的机器学习项目中,以及其局限性在哪里。
自动机器学习非常适合将最佳实践转换应用于您的数据集:应用日期/时间转换,以及在应用线性回归时对数据进行归一化和标准化,处理缺失数据或删除低方差特征等。微软提供了一系列功能,预计未来会增长。
让我们回顾一下在第七章中学习的内容,使用 NLP 的高级特征提取。虽然自动机器学习可以检测自由文本并将其转换为数值特征向量,但它无法理解您业务领域中的数据语义。因此,它能够转换您的文本数据,但如果您需要语义编码文本或分类数据,您必须自行实现。
另一点需要记住的是,自动机器学习不会尝试推断训练数据中不同特征维度之间的任何相关性。因此,如果您想将两个分类列合并为一个组合特征列(例如,使用 one-hot-encoding,mean embedding 等),那么您将必须自行实现这一点。
在自动机器学习中,有两个不同的预处理集 – 指定了preprocess参数。如果您之前使用过 scikit-learn,那么以下大多数预处理技术应该相当熟悉:
-
StandardScaler: 归一化 – 减去平均值并将特征缩放到单位方差。 -
MinMaxScaler: 归一化 – 通过最小值和最大值缩放特征。 -
MaxAbsScaler:归一化 – 通过最大绝对值缩放特征。 -
RobustScaler:归一化 – 将特征缩放到分位数范围内。 -
PCA:基于 PCA 的线性降维。 -
TruncatedSVD:基于线性降维的截断奇异值分解(SVD)。与 PCA 不同,此估计量在事先不对数据进行中心化。 -
SparseNormalizer:归一化 – 独立归一化每个样本。
复杂的预处理被称为特征化。这些预处理步骤更为复杂,并在自动机器学习优化过程中执行各种任务。作为 Azure 自动机器学习的用户,您可以期待这个列表不断增长,并包括随着可用性而出现的新最先进转换。以下列表显示了各种特征化步骤:
-
删除高基数或无方差特征:删除高基数特征(例如,散列、ID 或 GUID)或无方差特征(例如,所有值缺失或所有行具有相同值)。
-
填充缺失值:对数值特征(均值填充)和分类特征(众数填充)进行缺失值填充。
-
生成额外特征:生成基于日期/时间的额外特征(例如,年、月、日、星期几、年中的日、季度、年中的周、小时、分钟和秒)和文本特征(基于 n-gram 的词频)。
-
转换和编码:使用单热编码(低基数)和单热哈希编码(高基数)对分类特征进行编码。将具有少量唯一值的数值特征转换为分类特征。
-
词嵌入:使用预训练的嵌入模型将文本转换为使用平均嵌入的聚合特征向量。
-
目标编码:对分类特征执行目标编码。
-
文本目标编码:使用词袋模型对文本特征执行目标编码。
-
证据权重:通过证据权重计算分类列与目标列的相关性,并为每个列和每个类别输出一个新特征。
-
聚类距离:在所有数值列上训练 k-means 聚类模型,并在输出每个列和每个簇的新特征之前计算每个特征到其质心的距离。
让我们从使用预处理的一个简单的自动机器学习分类任务开始。
我们将首先定义一个包含自动机器学习配置的字典。为了启用标准预处理,如缩放、归一化和 PCA/SVD,我们需要将preprocess属性设置为true。对于高级预处理和特征工程,我们需要将featurization属性设置为auto。以下代码块显示了所有这些设置:
automl_settings = {
"experiment_timeout_minutes": 15,
"n_cross_validations": 3,
"primary_metric": 'accuracy',
"featurization": 'auto',
"preprocess": True,
"verbosity": logging.INFO,
}
使用此配置,我们现在可以使用pandas加载数据集。如下面的代码片段所示,我们正在加载titanic数据集,并将目标列指定为字符串。这个列在稍后配置自动机器学习时是必需的:
import pandas as pd
df = pd.read_csv("train.csv")
target_column = "survival"
重要提示
当你使用自动机器学习并且处于本地执行上下文时,你可以使用 pandas DataFrame 作为输入源。然而,当你在一个远程集群上执行训练过程时,你需要将数据包装在 Azure 机器学习数据集中。
每当我们使用黑盒分类器时,我们也应该保留一个测试集来验证模型的测试性能,以验证泛化能力。因此,我们必须将数据分成训练集和测试集:
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df, test_size=0.2)
最后,我们可以将所有必需的参数提供给自动机器学习配置构造函数。在这个例子中,我们使用本地执行目标来训练自动机器学习实验。然而,我们也可以提供一个 Azure 机器学习数据集并将实验提交到我们的训练集群:
from azureml.train.automl import AutoMLConfig
automl_config = AutoMLConfig(
task='classification',
debug_log='debug.log',
compute_target=aml_cluster,
training_data=df_train,
label_column_name=target_column,
**automl_settings)
让我们将自动机器学习配置作为实验提交到定义的计算目标,并等待完成。我们可以输出运行详情:
from azureml.widgets import RunDetails
automl_run = experiment.submit(automl_config,
show_output=False)
RunDetails(automl_run).show()
与HyperDriveConfig类似,我们可以看到自动机器学习的RunDetails显示了关于你当前实验的大量有用信息。你不仅可以查看所有已安排和正在运行的模式,还可以获得训练模型及其训练性能的精美可视化。以下截图显示了自动机器学习实验的前 14 次运行的准确度:

图 11.5 – 自动机器学习 – 结果的可视化
最后,经过 15 分钟后,我们可以从自动机器学习运行中检索最佳 ML 管道。从现在起,我们将简单地称这个管道为模型,因为所有预处理步骤都打包在这个模型中,它本身就是一个操作管道。我们可以使用以下代码来检索管道:
best_run, best_model = remote_run.get_output()
得到的拟合管道(称为best_model)现在可以像 scikit-learn 估计器一样使用。我们可以将其存储在磁盘上,将其注册到模型存储中,部署到容器实例,或者简单地评估测试集上的结果。我们将在第十四章中更详细地了解,模型部署、端点和操作。最后,我们想要评估最佳模型。为此,我们将使用之前从数据集中分离出的测试集,并在拟合模型上预测输出:
from sklearn.metrics import accuracy_score
y_test = df_test[target_column]
X_test = df_test.drop(target_column, axis=1)
y_pred = fitted_model.predict(X_test)
accuracy_score(y_test, y_pred)
在前面的代码中,我们使用了 scikit-learn 中的accuracy_score函数来计算最终模型的准确度。这些步骤就是你在使用自动预处理数据和拟合模型对数据集进行分类时需要执行的所有步骤。
摘要
在本章中,我们介绍了通过HyperDrive进行超参数优化和通过自动机器学习进行模型优化。这两种技术都可以帮助你高效地检索到适合你的机器学习任务的最佳模型。
网格采样与经典机器学习模型配合得很好,当可调整参数的数量固定时也是如此。离散参数网格上的所有值都会被评估。在随机采样中,我们可以为参数空间应用连续分布,并选择尽可能多的参数选择,以适应配置的训练时长。随机采样在大量参数上表现更好。这两种采样技术都可以/应该使用早期停止标准进行调整。
与随机和网格采样不同,贝叶斯优化通过探测模型性能来优化以下参数选择。这意味着每一组参数选择和由此产生的模型性能都会用来计算下一组最佳参数选择。因此,贝叶斯优化使用机器学习来优化你的机器学习模型的参数选择。由于底层高斯过程需要模型性能的结果,早期停止在贝叶斯优化中不起作用。
我们还了解到,自动机器学习是对完整端到端机器学习管道中贝叶斯优化的泛化。我们不仅选择超参数,还选择预处理、特征工程、模型选择和模型堆叠方法,并将它们一起优化。自动机器学习通过预测哪些模型会在你的数据上表现良好,而不是盲目地尝试所有可能的组合,从而加快了这个过程。这两种技术对于优秀的机器学习项目都是必不可少的;自动机器学习让你首先关注数据和标注,而超参数调整则让你优化特定模型。
在下一章中,我们将探讨训练深度神经网络(DNNs)的情况,其中数据或模型参数不再适合单个机器的内存,因此需要分布式学习。
第十二章:第十二章:Azure 上的分布式机器学习
在上一章中,我们学习了通过搜索和优化进行超参数调整,使用 HyperDrive 以及自动机器学习作为超参数优化的特例,涉及特征工程、模型选择和模型堆叠。自动机器学习是机器学习即服务(MLaaS),其中唯一的输入是您的数据、一个机器学习任务和一个错误度量。很难想象在单个机器或单个 CPU/GPU 上运行所有自动机器学习的实验和参数组合,我们正在寻找通过并行化和分布式计算加速训练过程的方法。
在本章中,我们将探讨用于在并行中高效训练机器学习模型的分布式和并行计算算法和框架。本章的目标是在 Azure 中构建一个环境,通过向您的训练环境添加更多机器,从而扩展集群,加速经典机器学习和深度学习模型的训练过程。
首先,我们将探讨分布式机器学习的不同方法和基本构建块。您将了解在 HyperDrive 和自动机器学习中并行训练独立模型的方法,以及通过划分训练数据并行训练单个模型集成在大型数据集上的方法。然后,我们将探讨针对单个模型的分布式机器学习,并发现数据分布式和模型分布式训练方法。这两种方法通常在现实场景中用于加速或实现大型深度神经网络的训练。
之后,我们将了解分布式机器学习中最流行的框架以及如何在 Azure 中使用它们,以及如何与 Azure 机器学习计算结合使用。分布式机器学习库之间的执行引擎、通信库和功能之间的过渡是平滑的,但往往难以理解。然而,在阅读本章后,您将了解在 Databricks 中使用 MLlib 运行 Apache Spark 和使用 Horovod、Gloo、PyTorch 和 TensorFlow 参数服务器之间的区别。
在最后一节中,我们将探讨如何在 Azure 中实现我们将要介绍的功能,并将其与 Azure 机器学习计算集成。
本章涵盖了以下主题:
-
探索分布式机器学习的方法
-
在 Azure 中使用分布式机器学习
技术要求
在本章中,我们将使用以下 Python 库和版本来创建基于决策树的集成分类器:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
horovod 0.23.0 -
tensorflow 2.6.0 -
pyspark 3.2.0 -
numpy 1.19.5 -
pandas 1.3.2 -
scikit-learn 0.24.2
与前几章类似,您可以使用本地 Python 解释器或托管在 Azure 机器学习中的笔记本环境执行此代码。
本章中所有代码示例都可以在本书的 GitHub 仓库中找到,该仓库位于github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter12。
探索分布式机器学习的方法
对于许多用户来说,实现机器学习流水线的旅程非常相似,通常与前面章节中描述的步骤相似。当用户开始从实验转向现实世界数据或从小示例转向更大模型时,他们经常会遇到类似的问题:在大量数据上训练大型参数模型——特别是深度学习(DL)模型——需要非常长的时间。有时,一个 epoch 需要数小时,训练需要数天才能收敛。
等待数小时甚至数天以等待模型收敛,对于许多工程师来说意味着宝贵时间的浪费,因为它使得交互式调整训练过程变得更加困难。因此,许多机器学习工程师需要通过利用各种分布式计算技术来加速他们的训练过程。分布式机器学习的理念就像通过添加更多计算资源来加速训练过程一样简单。在最佳情况下,通过向训练集群添加更多机器(扩展),训练性能可以线性提高。在本节中,我们将探讨分布式机器学习最常见的模式,并尝试理解和推理它们。在本章的下一节中,我们还将将这些模式应用于一些实际案例。
大多数现代机器学习(ML)流水线在数据或模型变得更大时,会使用本章讨论的一些技术来加速训练过程。这类似于大数据平台(如 Spark、Hive 等)在数据变得庞大时进行数据预处理的需求。因此,尽管本章看起来过于复杂,但我们建议在等待模型收敛或希望更快地产生更好结果时重新审视它。
如此,利用分布式计算进行机器学习通常有三种模式,如下所示:
-
并行地在小数据上训练独立的模型
-
并行地在数据的不同子集上训练模型的副本
-
并行训练同一模型的各个部分
让我们逐一查看这些方法。
并行地在小数据上训练独立的模型
我们首先来看一个最简单的例子:在(小)数据集上训练(小)独立模型。这种并行训练的典型用例是进行超参数搜索或经典机器学习模型或小型神经网络的优化。这与我们在第十一章中讨论的内容非常相似,即超参数调整和自动机器学习。即使是自动机器学习——其中多个独立的模型被训练和比较——在底层也使用这种方法。在并行训练中,我们的目标是通过并行训练这些模型来加速具有不同参数的多个独立模型的训练。
下面的图示显示了这种情况,其中我们不是在单台机器上按顺序训练单个模型,而是在并行训练它们:

图 12.1 – 并行处理
您可以看到,在单个模型的训练过程中不需要通信或同步。这意味着我们可以在同一台机器上的多个 CPU/GPU 上训练,或者在不同的机器上训练。
当使用 Azure Machine Learning 进行超参数调整时,通过配置具有多个节点的 Azure Machine Learning 计算目标并选择 HyperDrive 配置的max_concurrent_runs参数中的并发运行数量,可以轻松实现这种并行化。在 Azure Machine Learning HyperDrive 中,只需指定一个估计器和param_sampling,然后将 HyperDrive 配置作为实验提交,就可以并行运行单个任务,如下所示:
from azureml.train.hyperdrive import HyperDriveConfig
hyperdrive_run_config = HyperDriveConfig(
estimator=estimator,
hyperparameter_sampling=param_sampling,
primary_metric_name="accuracy",
primary_metric_goal=PrimaryMetricGoal.MAXIMIZE,
max_total_runs=100,
max_concurrent_runs=4)
from azureml.core.experiment import Experiment
experiment = Experiment(workspace, experiment_name)
hyperdrive_run = experiment.submit(hyperdrive_run_config)
这里有一些公式来计算 HyperDrive 或其他任何分布式计算设置中max_concurrent_runs的值:
-
对于基于 CPU 的训练,最大并发训练运行次数受可用 CPU 和计算节点数量的限制。可用的物理内存也是一个限制因素,但通过交换到虚拟内存,我们可以消耗比物理可用更多的内存。
-
对于基于 GPU 的训练,最大并发训练运行次数受可用 GPU 和计算节点数量以及可用 GPU 内存量的限制。通常,一个训练运行被固定到一个物理 GPU 上,但通过 GPU 虚拟化,如果足够的 GPU 内存可用,我们也可以在单个物理 GPU 上训练多个模型。
这里是如何估计单个模型将消耗多少内存的指南:
单个参数的大小:
-
半精度浮点数:16 位(2 字节)。
-
单精度浮点数:32 位(4 字节)——这通常是默认值。
-
双精度浮点数:64 位(8 字节)。
模型所需的参数数量:
-
参数模型:所有参数的总和
-
非参数模型:表示数量(例如,决策树)* 表示的参数数量
然后,您需要乘以额外的因素,如下所示:
-
使用反向传播的模型:总体内存 * 2
-
使用批次的模型:总体内存 * 批次大小
-
使用(递归)状态的模型:每个状态的记忆 * 递归步骤数
虽然这个用例看起来非常相似,但让我们继续到下一个用例,其中我们得到一个无法复制到每台机器上的大型数据集。
并行在大数据集上训练模型集成
我们接下来要讨论的是机器学习中的一个非常常见的优化,尤其是在在大数据集上训练模型时。为了训练模型,我们通常需要一个大量数据,这些数据很少全部适合单台机器的内存。因此,通常需要将数据分割成块,并在不同的块上训练多个单个模型。
以下截图显示了两种将数据分割成更小块的方法——通过水平分割行(左)或通过垂直分割列(右):

图 12.2 – 数据分割:水平(行方向)与垂直(列方向)
你也可以混合这两种技术从你的训练数据中提取子集。无论何时你使用大数据领域中的工具——如 MapReduce、Hive 或 Spark——分割你的数据将有助于你加快训练过程或最初就允许在大量数据上训练。
执行数据分布式训练的一个好例子是训练一个由完全独立的决策树模型组成的巨大树集成,也称为随机森林。通过将数据分割成成千上万的随机块,你可以为每个数据块训练一个决策树,并将所有训练好的树组合成一个单一集成模型。Apache Hivemall 是一个基于 Hive 和 Spark 的库,在两个执行引擎中的任何一个上都可以做到这一点。以下是一个使用Hive 查询语言(HiveQL)和 Apache Hivemall 在 Hive 上训练多个 XGBoost 多类集成模型的示例:
-- explicitly use 3 reducers
-- set mapred.reduce.tasks=3;
create table xgb_softmax_model as
select
train_xgboost(features, label,
'-objective multi:softmax -num_class 10 -num_round 10')
as (model_id, model)
from (
select features, (label - 1) as label
from data_train
cluster by rand(43) -- shuffle data to reducers
) data;
在前面的函数中,我们使用cluster关键字随机移动数据行到 reducer。这将水平分割数据并在每个 reducer 上为每个分区训练一个 XGBoost 模型。通过定义 reducer 的数量,我们也定义了并行训练的模型数量。生成的模型存储在一个表中,其中每一行定义了一个模型的参数。在预测中,我们只需简单地将所有单个模型组合起来,并执行平均投票标准以检索最终结果。
这种方法的另一个例子是一个标准的 Spark 管道,它在垂直和水平数据分区上训练多个独立模型。当我们完成单个模型的训练后,我们可以在推理期间使用平均投票标准来找到预测任务的优化结果。以下是一个使用 Python、PySpark 和 scikit-learn 并行在水平分割数据上训练多个模型的示例脚本:
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.appName("Distributed Training") \
.master("local") \
.getOrCreate()
# read the input data
df = spark.read.parquet("data/")
# define your training function
from sklearn.ensemble import RandomForestClassifier
def train_model(data):
clf = RandomForestClassifier(n_estimators=10)
return clf.fit(data['train_x'], data['train_y'])
# split your data into partitions and train models
num_models = 100
models = df.rdd.repartition(num_models) \
.mapPartitions(train_model) \
.collect()
在前面的函数中,我们现在可以加载几乎任何数量的数据,并重新分区,使得每个分区都能适应单个节点的本地内存。如果我们有 1 个collect()方法来将所有训练好的模型返回给头节点。
我们也可以选择将每个单独工作节点的模型存储在磁盘上或在分布式文件系统中,但将结果合并到单个节点上可能更好。在这个例子中,我们看到我们有选择这两种方法之一的自由,因为所有模型都是相互独立的。但这并不适用于模型突然相互依赖的情况——例如,当最小化全局梯度或在一个模型上分割多个机器时,这两种情况都是训练 DNN 时的常见用例。在这种情况下,我们需要一些新的操作符来引导数据和梯度的控制流。让我们在下一节中探讨这些操作符。
分布式机器学习的基本构建块
如前一个示例所示,我们在分布式系统中管理数据流需要一些基本的构建块或操作符。我们把这些操作符称为集体算法。这些算法实现了分布式计算中的常见同步和通信模式,并且在训练机器学习模型时是必需的。在我们深入探讨深度神经网络(DNN)的分布式训练方法之前,我们将快速浏览这些模式以了解其基础。
分布式系统中最常见的通信模式如下:
-
一对一
-
一对多(也称为broadcast或scatter模式)
-
多对一(也称为gather或reduce模式)
-
多对多(也称为all-gather或all-reduce模式)
下面的截图提供了这些模式的一个很好的概述,并展示了数据如何在系统的各个个体之间流动:

图 12.3 – 分布式系统中的通信模式
我们可以立即回想起贝叶斯优化技术中的超参数优化技巧。首先,我们需要将训练数据从主节点广播到所有工作节点。然后,我们可以在主节点的参数空间中选择参数组合,并将这些广播到工作节点。最后,我们在工作节点上执行训练,然后在主节点上收集所有模型验证分数。通过比较分数并应用贝叶斯定理,我们可以预测下一个可能的参数组合,并将它们重复广播到工作节点。
你注意到了前面算法中的某个地方吗?我们如何知道所有工作节点已经完成了训练过程,并从所有工作节点收集所有分数?为了做到这一点,我们将使用另一个构建块,称为同步,或屏障同步。使用屏障同步,我们可以安排任务的执行,使其需要等待所有其他分布式任务完成。以下截图展示了多处理器中同步模式的良好概述:

图 12.4 – 同步机制
如你所见,我们已经在上一章中隐式地使用了这些算法,当时它们被隐藏在优化这个术语背后。现在,我们将通过更改优化器来显式地使用它们,以便在多台机器上训练单个模型。
如你所想,这些模式并不新鲜,并且你的操作系统每秒会多次使用它们。然而,在这种情况下,我们可以利用这些模式并将它们应用于分布式训练过程的执行图,并通过专用硬件(例如,通过使用InfiniBand(IB)连接两个 GPU)。
为了使用这个集体算法与不同级别的硬件支持(GPU 支持和向量化),你需要选择一个通信后端。这些后端是通常作为单独进程运行的库,并实现通信和同步模式。用于集体算法的流行库包括 Gloo、消息传递接口(MPI)和 NVIDIA 集体通信库(NCCL)。
大多数深度学习框架,如 PyTorch 或 TensorFlow,在这些通信后端之一上提供了自己的高级抽象——例如,PyTorch 远程过程调用(RPC)和 TensorFlow 参数服务器(PS)。你不必使用不同的执行和通信框架,也可以选择一个通用的分布式计算框架,如 Spark。
重要提示
PyTorch 文档提供了一个关于何时使用哪个集体通信库的最新指南:https://pytorch.org/docs/stable/distributed.html#which-backend-to-use.
如你所见,可能的选择列表是无限的,并且有多种组合可能。我们甚至还没有谈到 Horovod,这是一个框架,用于通过分布式优化器将分布式训练添加到其他深度学习框架中。好事是,大多数这些框架和库都包含在所有 Azure Machine Learning 运行时中,并且通过 Azure ML SDK 得到支持。这意味着你通常只需要指定所需的后端,将你的模型提供给任何特定框架,然后让 Azure Machine Learning 处理这些工具的设置、初始化和管理。我们将在本章的后半部分看到这一点。
使用数据并行训练加速深度学习
在深度学习(DL)中,分布式数据并行训练的另一种变体非常常见。为了加快大型模型的训练速度,我们可以在同一模型的分布式副本上运行多个不同数据块的训练迭代。这在每次训练迭代需要显著时间(例如,数秒)的情况下尤为重要,这对于我们想要利用多 GPU 环境的训练大型深度神经网络(DNNs)是一个典型场景。
深度学习中的数据分布式训练基于使用分布式梯度下降(DGD)算法的思想,如下所示:
-
将模型的一个副本分发到每个节点。
-
将数据块分发给每个节点。
-
在每个节点上运行整个网络,并计算梯度。
-
在单个节点上收集所有梯度并计算平均梯度。
-
将平均梯度发送到所有节点。
-
使用平均梯度更新所有模型。
以下图表显示了多个模型在行动中的情况,它们分别运行正向/反向传递,并将梯度发送回参数服务器:

图 12.5 – 数据并行训练
如此看来,服务器计算平均梯度,并将其发送回所有其他节点。我们可以立即看到,突然之间,工作节点和主节点(让我们称其为参数服务器)之间需要通信,并且在等待所有模型完成梯度计算时也需要同步。
这种用例的一个很好的例子是通过并行化反向传播步骤并将每个节点的梯度组合到整体梯度中来加速深度学习模型的训练过程。TensorFlow 目前使用所谓的参数服务器支持这种分布模式。Uber 开发的Horovod框架为分布式优化器提供了一个方便的抽象,并可以插入许多可用的机器学习框架或分布式执行引擎,如 TensorFlow、PyTorch 和 Apache Spark。我们将在Horovod – 分布式深度学习训练框架部分中查看使用 Horovod 和 Azure 机器学习的实际示例。
使用模型并行训练训练大型模型
最后,深度学习中的一个常见用例是训练比单个 GPU 提供的 GPU 内存更大的模型。这种方法有点复杂,因为它需要将模型执行图分割到不同的 GPU 或甚至不同的机器上。虽然这在基于 CPU 的执行中不是大问题,并且通常在 Spark、Hive 或 TensorFlow 中完成,但我们还需要在多个 GPU 内存之间传输中间结果。为了有效地做到这一点,需要额外的硬件和驱动程序,例如Infiniband(GPU 到 GPU 通信)和GPUDirect(高效的 GPU 内存访问)。
下图显示了并行计算多个梯度(在左侧)和计算分布式模型的单个前向传播(在右侧)之间的差异:

图 12.6 – 模型并行训练
后者要复杂得多,因为数据需要在多个 GPU 和/或多个节点之间的正向和反向传播过程中进行交换。
通常,我们在两种场景之间进行选择:单机多 GPU 训练和多机多 GPU 训练。正如你所预期的那样,后者要复杂得多,因为它需要在网络中多个机器之间进行通信和同步。
在以下脚本中,我们使用 PyTorch 创建了一个在两个 GPU 上运行的简单模型。在整个模型中使用.to('cuda:*')方法,我们定义了操作应该在哪个 GPU 上执行。此外,我们还需要将这些相同的注释添加到这些计算的数据输入中:
import torch
import torch.nn as nn
import torch.optim as optim
class ParallelModel(nn.Module):
def __init__(self):
super(ParallelModel, self).__init__()
self.net1 = torch.nn.Linear(10, 10).to('cuda:0')
self.relu = torch.nn.ReLU()
self.net2 = torch.nn.Linear(10, 5).to('cuda:1')
def forward(self, x):
x = self.relu(self.net1(x.to('cuda:0')))
return self.net2(x.to('cuda:1'))
如前述代码所示,我们配置网络在 GPU 0 上计算第一个全连接层,而第二个全连接层则在 GPU 1 上计算。在配置前向步骤时,我们还需要相应地配置两个层的输入。
使用内置优化器和损失函数训练模型与非分布式模型并没有太大区别。唯一的区别是我们还必须定义训练标签的目标 GPU,以便计算损失,如下所示:
model = ParallelModel()
loss_fn = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)
optimizer.zero_grad()
outputs = model(torch.randn(20, 10))
labels = torch.randn(20, 5).to('cuda:1')
loss_fn(outputs, labels).backward()
optimizer.step()
如你所见,我们将单个层分割以在多个 GPU 上运行,而这些层之间的数据需要在正向和反向传播过程中进行传输。我们必须对模型本身进行代码更改,以指定模型的哪些部分应该在哪个 GPU 上运行。
重要提示
请注意,我们也可以使这种分割动态化,将模型分割成x个连续的子图,这些子图在x个 GPU 上执行。
值得注意的是,本章讨论的许多技术可以结合起来使用。例如,我们可以在每台机器上训练一个多 GPU 模型,同时将数据分成块,并在多台机器上计算多个梯度的多个部分——因此采用数据分布式模型并行方法。
在下一节中,我们将学习如何将这些概念付诸实践。
在 Azure 中使用分布式机器学习
“探索分布式机器学习的方法”部分包含了大量的不同并行化场景、用于集体算法的各种通信后端以及使用不同机器学习框架甚至执行引擎的代码示例。在机器学习框架的选择上,可供选择的空间相当大,做出明智的决定并不容易。当一些框架在 Azure 机器学习上直接支持时,而其他框架则需要用户安装、配置和管理,这种选择变得更加复杂。
在本节中,我们将探讨最常见的场景,学习如何选择正确的框架组合,并在 Azure 中实现分布式 ML 管道。
通常,在 Azure 中运行分布式机器学习有三种选择,如下所示:
-
第一个明显的选择是使用 Azure 机器学习,笔记本环境,Azure 机器学习 SDK 和 Azure 机器学习计算集群。这将是对许多复杂用例来说最简单的解决方案。大量数据可以存储在 Azure Blob 存储中,模型可以训练为数据并行和/或模型并行模型,使用不同的通信后端。所有这些都将通过将您的训练脚本包装在估计器抽象中来由您管理。
-
第二种选择是使用不同的代码编写和执行引擎,而不是 Azure 机器学习笔记本和 Azure 机器学习计算集群。一个流行的选项是集成了交互式笔记本和 Apache Spark 作为分布式执行引擎的 Azure Databricks。使用 Databricks,您可以使用预构建的 ML 镜像和自动扩展集群,这为运行分布式 ML 训练提供了一个极佳的环境。
-
第三种选择是构建并推出您自己的定制解决方案。为此,您需要构建一个包含虚拟机或 Kubernetes 的独立集群,并编排基础设施和代码的设置、安装和管理。虽然这是最灵活的解决方案,但它也是迄今为止设置最复杂、耗时最长的。
对于本书,我们将在深入研究 Azure 机器学习之前,首先了解 Horovod 优化器、Azure Databricks 和 Apache Spark。
Horovod – 一个分布式深度学习训练框架
Horovod是一个用于启用分布式深度学习的框架,最初由 Uber 开发和开源。它为以下支持的框架提供了统一的方式来支持现有深度学习训练代码的分布式训练——TensorFlow、Keras、PyTorch 和 Apache MXNet。设计目标是使任何现有项目从单节点训练到数据并行训练的过渡变得极其简单,从而使得这些模型能够在分布式环境中更快地使用多个 GPU 进行训练。
Horovod 是作为任何支持框架中优化器的即插即用替代品的绝佳选择,用于数据并行训练。它通过初始化和更新步骤或更新钩子与支持的框架很好地集成,通过简单地从深度学习代码中抽象 GPU。从用户的角度来看,只需对代码进行最小更改即可支持模型的数据并行训练。让我们通过使用 Keras 的示例来查看以下步骤:
-
初始化 Horovod。
-
配置 Keras 从 Horovod 读取 GPU 信息。
-
加载一个模型并分割训练数据。
-
将 Keras 优化器包装为 Horovod 分布式优化器。
-
实现模型训练。
-
使用
horovodrun执行脚本。
详细步骤如下:
-
对于任何使用 Horovod 的脚本,第一步都是相同的——我们首先需要从正确的包中加载
horovod并初始化它,如下所示:import horovod.keras as hvd hvd.init() -
接下来,我们需要执行一个自定义设置步骤,这个步骤取决于所使用的框架。这一步将为框架设置 GPU 配置,并确保它可以通过 Horovod 调用抽象版本。以下代码片段展示了这一点:
from tensorflow.keras import backend as K import tensorflow as tf # pin GPU to be used to process local rank. # one GPU per process config = tf.ConfigProto() config.gpu_options.allow_growth = True config.gpu_options.visible_device_list = str(hvd.local_rank()) K.set_session(tf.Session(config=config)) -
现在,我们可以简单地使用我们的单节点、单 GPU Keras 模型,并定义所有参数以及训练和验证数据。在这个步骤中不需要任何特殊要求,正如我们在这里可以看到的:
# standard model and data batch_size = 10 epochs = 100 model = load_model(...) x_train, y_train = load_train_data(...) x_test, y_test = load_test_data(...) -
最后,我们来到了神奇的部分,我们将框架优化器(在这种情况下,是 Keras 的 Adadelta)包装成 Horovod 分布式优化器。对于所有后续代码,我们将简单地使用分布式优化器而不是默认的优化器。我们还需要调整学习率到使用的 GPU 数量,因为最终的梯度将是来自各个单独变化的平均值。这可以通过以下代码完成:
from tensorflow.keras.optimizers import Adadelta # adjust learning rate based on number of GPUs opt = Adadelta(1.0 * hvd.size()) # add Horovod Distributed Optimizer opt = hvd.DistributedOptimizer(opt) -
剩余的部分看起来相当简单。它包括编译模型、拟合模型和评估模型,就像单节点版本一样。值得注意的是,我们需要在训练过程中添加一个回调来初始化所有梯度。以下代码片段展示了这一点:
model.compile(loss=keras.losses.categorical_crossentropy, optimizer=opt, metrics=['accuracy']) callbacks = [ hvd.callbacks.BroadcastGlobalVariablesCallback(0) ] model.fit(x_train, y_train, batch_size=batch_size, callbacks=callbacks, epochs=epochs, verbose=1 if hvd.rank() == 0 else 0, validation_data=(x_test, y_test)) score = model.evaluate(x_test, y_test) print('Test loss:', score[0]) print('Test accuracy:', score[1])
当查看前面的代码时,可以说 Horovod 并没有过分承诺使代码扩展到使用数据并行方法和分布式梯度计算进行分布式执行变得容易。如果你已经调查了原生的 TensorFlow 或 PyTorch 版本,你会看到这需要更少的代码更改,并且比参数服务器或 RPC 框架更易于阅读和移植。
-
Horovod 框架在底层使用基于 MPI 的通信来处理集体算法,通常每个节点每个 GPU 需要一个运行进程。然而,它也可以通过配置选项在 Gloo 后端或自定义 MPI 后端上运行。以下是如何使用
horovodrun命令在两台机器server1和server2上启动训练过程的示例片段,每台机器使用四个独立的 GPU:horovodrun -np 8 -H server1:4,server2:4 python train.py
当你只想通过扩展你的集群来加速训练进度时,在自己的集群上运行和调试 Horovod 仍然可能很痛苦。因此,Azure Machine Learning 计算提供了一个包装器,为你完成所有繁重的工作,只需要一个带有 Horovod 注释的训练脚本。我们将在“在 Azure Machine Learning 上使用 Horovod 训练模型”部分看到这一点。
模型并行训练可以通过使用底层框架的模型并行功能,并且每个机器只使用一个 Horovod 进程而不是每个 GPU 来实现与 Horovod 的结合。然而,这是一个自定义配置,目前在 Azure Machine Learning 中尚不支持。
为 Spark 作业实现 HorovodRunner API
在许多公司中,机器学习是现有数据管道之上的一个附加数据处理步骤。因此,如果您有大量数据,并且您已经在管理 Spark 集群或使用 Azure Databricks 处理这些数据,那么添加分布式训练功能也很容易。
正如我们在本章的“探索分布式机器学习的方法”部分所看到的,我们可以简单地通过并行化或分割训练数据来训练多个模型。然而,我们也可以训练深度学习模型,并从分布式机器学习技术中受益,以加快训练过程。
当使用 Databricks ML 运行时,您可以使用 Horovod 为 Spark 分发您的训练过程。此功能通过HorovodRunner API 提供,并由 Spark 的 barrier-mode 执行引擎提供稳定的通信后端,为长时间运行的工作提供支持。在头节点上使用HorovodRunner,它将训练函数发送到工作节点,并使用 MPI 后端启动该函数。所有这些都在 Spark 进程的幕后发生。
再次强调,这正是 Horovod 易于使用的原因之一,因为它实际上只是您当前优化器的直接替换。想象一下,您通常在 Azure Databricks 上使用 PySpark 引擎运行您的 Keras 模型;然而,您希望添加 Horovod 以利用集群中的其他机器并分割梯度下降到多台机器上,以加快训练过程。为此,您只需在上一节示例中添加两行代码即可,如下所示:
hr = HorovodRunner(np=2)
def train():
# Perform your training here..
import horovod.keras as hvd
hvd.init()
...
hr.run(train)
在前面的代码片段中,我们可以观察到,我们只需要用工作节点数初始化HorovodRunner()。调用run()方法并传入训练函数将自动启动新的工作节点和 MPI 通信后端,并将训练代码发送到工作节点,并行执行训练。因此,您现在可以将数据并行训练添加到您的长时间运行的 Spark ML 作业中。
在 Azure Machine Learning 上使用 Horovod 训练模型
转向云服务的一个好处是,您可以以服务的形式消费功能,而不是自己管理基础设施。好的例子包括托管数据库、lambda 函数、托管 Kubernetes 或容器实例,选择托管服务意味着您可以专注于您的应用程序代码,而基础设施则由云为您管理。
Azure Machine Learning 服务位于一个类似的位置,你可以通过 SDK(例如模型管理、优化、训练和部署)使用许多不同的功能,这样你就不必维护 ML 集群基础设施。当涉及到通过分布式 ML 加速 DNN 时,这带来了巨大的好处。如果你一直坚持使用 Azure Machine Learning 计算服务,那么迁移到数据并行训练就像在你的训练配置中添加单个参数一样简单——对于本章讨论的任何各种选择。
让我们思考一下在分布式环境中使用 Horovod 优化器以数据并行模式运行 Keras 训练脚本。你需要确保所有正确的工具版本都已设置好(从 Compute Unified Device Architecture (CUDA)到 CUDA Deep Neural Network (cuDNN),GPUDirect,MPI,Horovod,TensorFlow 和 Keras),并且与你的当前操作系统和硬件良好地协同工作。然后,你需要将训练代码分发到所有机器上,启动 MPI 进程,然后使用 Horovod 和集群中每台机器的相关命令行参数调用脚本。而且我们还没有讨论认证、数据访问或自动扩展。
使用 Azure Machine Learning,你将获得一个即用型 ML 环境,它将为你保持最新。让我们看看之前的 Horovod 和 Keras 训练脚本,我们将其存储在 train.py 文件中。现在,类似于之前的章节,我们创建一个估计器来封装 Azure Machine Learning SDK 的训练调用。要使用 Horovod 和 MPI 后端启用多 GPU 数据并行训练,我们只需添加相关参数。生成的脚本看起来像这样:
from azureml.core import ScriptRunConfig
from azureml.core.runconfig import MpiConfiguration
run_config = get_run_config(aml_cluster, [
'numpy', 'pandas', 'scikit-learn', 'joblib',
'tensorflow', 'horovod'])
distr_config = MpiConfiguration(process_count_per_node=1,
node_count=2)
src = ScriptRunConfig(source_directory=script_folder,
script='train.py',
run_config=run_config,
arguments=script_params
distributed_job_config=distr_config)
使用 use_gpu 标志,我们可以启用具有预编译二进制的 GPU 特定机器及其对应图像,用于我们的 Azure Machine Learning 计算集群。使用 node_count 和 process_count_per_node,我们指定数据并行训练的并发级别,其中 process_count_per_node 应与每个节点可用的 GPU 数量相对应。最后,我们将 distributed_backend 参数设置为 mpi 以启用此估计器的 MPI 通信后端。另一个可能的选项是使用 ps 来启用 TensorFlow 的 ParameterServer 后端。
最后,为了启动作业,我们只需提交实验,它将自动在每个节点上设置 MPI 会话,并使用相关参数调用训练脚本。我不知道你对此有何感想,但对我来说,这真的是从之前的手动示例中迈出的巨大一步。以下代码行显示了如何提交实验:
run = experiment.submit(src)
将您的训练作为 Azure Machine Learning 估计器的一部分,您可以享受到为多个环境微调您的训练脚本配置的好处,无论是用于分布式梯度下降训练的多 GPU 数据并行模型,还是用于快速推理的单节点实例。通过结合分布式深度学习与 Azure Machine Learning 计算自动缩放集群,您可以通过使用预构建的托管服务而不是手动调整基础设施和配置,从云中获得最大收益。
摘要
分布式机器学习是一种很好的方法,可以扩展您的训练基础设施,以在训练过程中获得速度。它在许多实际场景中得到应用,并且与 Horovod 和 Azure Machine Learning 非常容易使用。
并行执行类似于超参数搜索,而分布式执行类似于我们在上一章中详细讨论的贝叶斯优化。分布式执行需要方法来高效地执行通信(如一对一、一对多、多对一和多对多)和同步(如屏障同步)。这些所谓的集体算法由通信后端(MPI、Gloo 和 NCCL)提供,并允许高效的 GPU 到 GPU 通信。
深度学习框架在通信后端之上构建了高级抽象,以执行模型并行和数据并行训练。在数据并行训练中,我们将输入数据分区,在不同的机器上计算模型的多个独立部分,并在后续步骤中汇总结果。深度学习中的一个常见技术是分布式梯度下降,其中每个节点在输入批次的分区上执行梯度下降,而主节点收集所有单独的梯度来计算组合模型的总体平均梯度。在模型并行训练中,您将单个模型分布到多个机器上。当模型不适合单个 GPU 的 GPU 内存时,这种情况通常发生。
Horovod 是在 TensorFlow、Keras、PyTorch 和 Apache MXNet 等其他机器学习框架的现有优化器之上的一种抽象。它提供了一个易于使用的接口,可以在不进行许多代码更改的情况下向现有模型添加数据分布式训练。虽然您可以在独立集群上运行 Horovod,但 Azure Machine Learning 服务通过将其功能封装为估计器对象提供了良好的集成。您学习了如何在 Azure Machine Learning 计算集群上运行 Horovod,通过几行 Horovod 初始化和当前优化器的包装来加速您的训练过程。
在下一章中,我们将使用前几章的所有知识,在 Azure 上训练推荐引擎。推荐引擎通常建立在其他 NLP 特征提取或分类模型之上,因此结合了我们迄今为止学到的许多技术。
第十三章:第十三章:在 Azure 中构建推荐引擎
在前一章中,我们讨论了机器学习模型的分布式训练方法,并学习了如何在 Azure 中高效地训练分布式机器学习模型。在本章中,我们将深入探讨传统和现代推荐引擎,这些推荐引擎通常结合了前几章中介绍的技术和技巧。
首先,我们将快速浏览不同类型的推荐引擎,每种类型需要哪些数据,以及可以使用这些不同方法推荐什么。这将帮助您了解何时选择非个性化、基于内容或基于评分的推荐器。
之后,我们将深入探讨基于内容的推荐,即基于特征向量和相似度的项目-项目和用户-用户推荐器。您将学习如何使用余弦距离来衡量特征向量之间的相似性,以及如何通过特征工程技术避免在构建基于内容的推荐引擎时常见的陷阱。
随后,我们将讨论在收集到足够的用户-项目交互数据后可以使用的基于评分的推荐。您将了解隐式评分和显式评分之间的区别,开发自己的隐式度量函数,并思考用户评分的时效性。
在接下来的章节中,我们将结合基于内容和基于评分的推荐器,构建一个单一的混合推荐器,并了解现代推荐引擎的最新技术。您将使用 Azure 机器学习实现两个推荐器,一个使用 Python,另一个使用 Azure 机器学习设计器——Azure 机器学习的图形用户界面。
在最后一节中,我们将探讨使用强化学习作为服务的在线推荐系统——Azure Personalizer。在理解了基于内容和基于评分的方法之后,您将学习如何使用适应函数和在线学习实时改进您的推荐。
本章将涵盖以下主题:
-
推荐引擎简介
-
基于内容的推荐系统
-
协同过滤——基于评分的推荐系统
-
在混合推荐引擎中结合内容和评分
-
通过强化学习实现自动优化
技术要求
在本章中,我们将使用以下 Python 库和版本来创建基于内容和基于评分的推荐引擎,以及混合和在线推荐器:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
numpy 1.19.5 -
scipy 1.7.1 -
pandas 1.3.2 -
scikit-learn 0.24.2 -
lightgbm 3.2.1 -
pyspark 3.2.0 -
azure-cognitiveservices-personalizer 0.1.0
与前几章类似,您可以使用本地 Python 解释器或 Azure 机器学习托管的工作区中的笔记本环境运行此代码。
对于 Matchbox 推荐引擎的例子,你需要在 Azure Machine Learning 工作区中使用 Azure Machine Learning designer。对于 Azure Personalizer,你需要在 Azure 门户中设置一个 Azure Personalizer 资源。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter13。
推荐引擎简介
在今天的数字世界中,推荐引擎在许多行业中无处不在。许多在线业务,如流媒体、购物、新闻和社交媒体,在其核心都依赖于向用户推荐最相关的文章、新闻和项目。你有多少次点击了 YouTube 上推荐的视频,浏览了你的 Facebook 动态,在 Spotify 上听了个性化的播放列表,或者在 Amazon 上点击了推荐的商品?
如果你问自己,对于不同的服务和行业,“相关”这个术语意味着什么,你就在正确的道路上。为了向用户推荐相关内容,我们首先需要定义一个相关性指标,以及描述和比较不同项目及其相似性的方法。这两个属性是理解不同推荐引擎的关键。我们将在本章接下来的部分中了解更多关于这方面的内容。
虽然大多数人对于推荐引擎的目的都很清楚,但不同的方法通常并不明显。因此,为了更好地理解这一点,在本章中,我们将比较不同类型的推荐系统,并给出一些你可能已经在日常生活中见过的例子。也值得提到的是,许多服务实施了这些方法中的多个,以产生出色的推荐。
最简单推荐引擎和方法是非个性化推荐。它们通常用于显示全球兴趣(例如,Twitter 全球趋势、流行的 Netflix 节目和新闻网站首页)或没有可用用户数据的趋势。一个很好的例子是在你第一次注册并登录服务时,任何流媒体服务出现的推荐。
一旦你登录到一个网络服务并开始适度地使用它,你通常会面临基于内容的推荐。基于内容的推荐者会根据项目和使用者配置文件的特征寻找相似的项目或具有相似用户特征的项目。用户配置文件项目可以包含许多基于个性或社会人口统计特征的属性,包括以下内容:
-
年龄
-
性别
-
国籍
-
居住国
-
母语
想象一下在没有在 Amazon 上购买任何东西的情况下登录 Amazon。大多数推荐的商品将与你刚刚查看的商品相似,或者与你的人口统计和位置相匹配。
一旦有足够的交互数据可用,你将开始看到基于评分的推荐,这种方法也被称为协同过滤。在基于评分的推荐器中,用户与物品的交互被转换成显式或隐式的评分。基于这些评分,推荐是基于其他用户给出的相似推荐来进行的。在 Netflix 上评分一部电影是显式评分,而在 YouTube 上观看完整的 20 分钟纪录片是隐式评分。因此,用户将看到其他也喜欢你所评分电影的用户的电影。同样,YouTube 将展示其他用户观看过而你刚刚看过的视频。
重要提示
微软在其 GitHub 仓库github.com/Microsoft/Recommenders/中提供了许多流行的推荐引擎的不同实现。这使得开始使用、选择正确的算法以及实现、训练和部署 Azure 上的推荐引擎变得容易。
下一个自然的步骤是将基于内容和基于评分的推荐器结合成一个单一的混合推荐引擎,它可以处理用户评分和没有评分的新用户。这种方法的好处是两个推荐系统一起优化,并创建一个综合推荐。Azure 机器学习工作室(经典)和 Azure 机器学习设计器提供了训练和部署 Matchbox 推荐器的构建块,这是一个由微软研究院构建的在线贝叶斯混合推荐引擎。
过去一年中另一个令人兴奋的新发展是基于强化学习的混合在线推荐优化。通过为用户评分提供一个适应函数,算法可以持续学习以优化此函数。在本章的最后部分,我们将探讨 Azure Personalizer,这是一个基于强化学习的推荐引擎作为服务。
让我们直接进入讨论的方法,并为 Azure 的可扩展推荐引擎开发一些示例解决方案。
基于内容的推荐系统
我们首先从基于内容的推荐开始,因为它们与我们在这本书中之前讨论的内容最为相似。术语内容指的是仅使用物品或用户的(数值)特征向量形式的内容信息。从物品(网店中的文章)或用户(网络服务中的浏览器会话)到达特征向量的方式是通过数据挖掘、数据预处理和特征工程——这些技能你在前面的章节中学过。
使用用户和物品的特征向量,我们可以将基于内容的推荐分为大致两种方法:
-
物品-物品相似度
-
用户-用户相似度
因此,推荐是基于项目之间的相似性或用户之间的相似性。这两种方法在用户和项目之间几乎没有交互数据的情况下效果很好(例如,在亚马逊上没有购买历史记录的用户,在 YouTube 上没有搜索历史记录,或者在 Netflix 上还没有观看过电影——所谓的冷启动问题)。
当你决定推出推荐或新用户开始使用你的服务时,你将始终需要处理冷启动问题。在这两种情况下,你都没有足够的用户-项目交互(所谓的评分)数据,需要仅基于内容推荐项目。
对于第一种方法,我们设计了一个系统,该系统推荐与用户当前交互的项目相似的项目。当用户查看一个项目时,推荐器返回最相似的项目。项目相似性基于项目特征向量的相似性——我们将在下一节中看到如何计算这种相似性。当没有或几乎没有用户交互数据时,可以使用这种方法。图 13.1展示了基于内容特征和单个用户交互推荐相似项目的这种方法:

图 13.1 – 使用基于内容的推荐查找相似产品
在 Spotify 上创建播放列表会在底部显示推荐歌曲的框,如图图 13.2所示。我们可以看到,推荐的歌曲是基于播放列表中的歌曲;因此,它是相似的内容:

图 13.2 – Spotify 的推荐歌曲
我们可以看到列出的歌曲与播放列表中的歌曲相似——在流派、风格、艺术家等多个方面相似。
点击亚马逊上的一个产品,页面底部会显示相关产品列表,如图图 13.3所示。再次强调,相似产品意味着这是基于内容的推荐:

图 13.3 – 亚马逊的推荐产品
这种推荐与你的先前购物经验无关,即使没有找到用户购买历史记录,也可以显示。
在第二种方法中,系统根据用户配置文件推荐相似用户。然后,我们可以从这些相似用户中选择最喜欢的项目,并将它们作为推荐展示。请注意,在数字系统中,用户配置文件可以通过位置(例如,通过 IP 地址)、语言、人口统计和设备指纹隐式定义。当可以从其他用户那里获得用户-项目交互数据,但不能为当前用户获得时,可以使用这种技术。图 13.4展示了基于内容特征推荐的相似用户的购买:

图 13.4 – 使用基于内容的推荐寻找相似用户
从用户的角度来看,通常很难区分这种推荐与非个性化推荐(例如,你所在地区的顶级产品,针对你的人口统计或你的语言——所有这些都可以从你的浏览器指纹中提取出来的属性)。
测量项目之间的相似度
训练基于内容的推荐引擎的关键部分是指定一个可以测量和排名两个项目之间相似度的度量标准。一个流行的选择是使用项目特征向量之间的余弦相似度或余弦距离来测量两个项目之间的相似度。余弦相似度是通过计算两个向量之间角度的余弦值来计算的,其中向量是数据集中的观察值。余弦距离是通过 1 减去余弦相似度来计算的。图 13.5显示了两个数值特征向量和特征向量之间的余弦距离:

图 13.5 – 余弦距离
我们可以从图中看到,如果两个向量相同,它们之间的余弦距离为 0。另一方面,当两个向量指向同一方向时,余弦相似度产生 1,当两个向量相互垂直时,余弦相似度为 0;因此,观察值之间没有相似度。
如果你不确定,你可以始终使用以下代码计算两个特征向量之间的余弦距离或相似度(确保你的 DataFrame (df) 没有额外的 id 列,并且所有列都是数值型):
from scipy import spatial
f1 = df.iloc[0, :]
f2 = df.iloc[1, :]
# compute the cosine distance between the first 2 rows
cosine_distance = spatial.distance.cosine(f1, f2)
print(cosine_distance)
# compute the cosine similarity between the first 2 rows
cosine_similarity = 1 - spatial.distance.cosine(f1, f2)
print(cosine_similarity)
观察前面的片段,我建议你从你的数据集中选择几行,估计它们的相似度(如果它们相同则为 1,如果它们完全不同则为 0),然后使用上述方法计算余弦相似度。如果你的猜测和计算的方法差异很大,并且你不理解原因,你最好回到数据预处理和特征工程。在下一节中,你将了解推荐系统中特征工程最常见的错误。
基于内容的推荐器的特征工程
训练基于内容的推荐引擎与训练经典机器学习模型非常相似。对于端到端的机器学习管道,所有步骤,如数据准备、训练、验证、优化和部署,都是相同的,并且使用与任何传统嵌入、聚类、回归或分类技术非常相似甚至相同的工具和库。
对于大多数其他机器学习算法,出色的特征工程是推荐引擎获得良好结果的关键。对于基于聚类的推荐器来说,困难在于大多数嵌入和相似度度量标准仅在数值空间中工作。虽然其他技术,如基于树的分类器,在输入数据的结构上给你更多的自由度,但许多聚类技术需要数值特征。
训练基于内容的推荐器的一个重要因素是分类特征的语义意义。因此,你很可能想使用高级自然语言处理方法将分类特征嵌入到数值空间中,以捕获这种语义意义并将其提供给推荐引擎。分类特征在推荐系统中的影响是基于相似度测量的方式。
如我们在上一节所讨论的,相似性通常被表示/测量为余弦相似度,因此计算两个特征向量之间的余弦值。因此,即使两个分类值之间只有一个不同的字符,使用独热编码,这些分类值也会产生相似度为 0 的结果——尽管它们在语义上非常相似。使用简单的标签编码,结果甚至更不明显。使用标签编码,得到的相似度现在不仅是 0,而且是一个与 0 不同的不可解释的值。
因此,我们建议对名义/文本变量进行语义嵌入,以在数值空间中捕获它们的语义意义并避免常见的陷阱,因为分类嵌入会泄露到相似度指标中。
通常,有两种可能的实现基于内容的推荐器的方法。如果你在寻找纯相似度,你可以使用任何无监督的嵌入和聚类技术来寻找相似的项目或用户。第二种可能性是将推荐器实现为回归或分类技术。这样,你可以预测所有项目的相关性的离散或连续值,只考虑项目特征或项目与用户特征的组合。在下一节中,我们将查看一个示例方法。
基于内容的推荐使用梯度提升树
对于我们的基于内容的模型,我们将使用Criteo 数据集来预测每篇文章的点击通过率(CTR),基于文章特征。我们将使用预测的 CTR 来推荐预测 CTR 最高的文章。正如你所看到的,将基于内容的推荐引擎表述为一个标准的分类或回归问题非常简单。
对于这个例子,我们将使用 LightGBM 中的梯度提升树回归器。预测点击通过率(CTR)的模型与本书中之前训练的任何回归模型非常相似。让我们开始吧:
-
首先,我们定义 LightGBM 模型的参数:
params = { 'task': 'train', 'boosting_type': 'gbdt', 'num_class': 1, 'objective': "binary", 'metric': "auc", 'num_leaves': 64, 'min_data': 20, 'boost_from_average': True, 'feature_fraction': 0.8, 'learning_rate': 0.15, } -
接下来,我们将训练集和测试集定义为 LightGBM 数据集:
lgb_train = lgb.Dataset(x_train, y_train.reshape(-1), params=params) lgb_test = lgb.Dataset(x_test, y_test.reshape(-1), reference=lgb_train) -
使用这些信息,我们现在可以训练模型:
lgb_model = lgb.train(params, lgb_train, num_boost_round=100) -
最后,我们可以通过预测 CTR 并计算 ROC 曲线下的面积作为错误指标来评估模型性能:
y_pred = lgb_model.predict(x_test) auc = roc_auc_score(np.asarray(y_test.reshape(-1)), np.asarray(y_pred))
太好了!你已经学会了如何根据项目相似性创建推荐。然而,这些推荐缺乏多样性,只会推荐相似的项目。因此,当没有用户-商品交互数据可用时,它们可以发挥作用,但一旦用户开始使用你的服务,它们的性能就会很差。一个更好的推荐引擎会推荐各种不同的项目,以帮助用户探索和发现他们可能喜欢的新和无关的项目。这正是我们在下一节中将要使用协同过滤来做的。
协同过滤 – 一种基于评分的推荐系统
通过只推荐相似的项目或来自相似用户的商品,你的用户可能会因为缺乏多样性和变化而厌倦提供的推荐。一旦用户开始与一个服务(例如,在 YouTube 上观看视频,在 Facebook 上阅读和点赞帖子,或在 Netflix 上评分电影)互动,我们希望为他们提供出色的个性化推荐和相关的内容,以保持他们的快乐和参与度。这样做的一个好方法是提供相似内容和新内容的好混合,以供探索和发现。
协同过滤是一种流行的推荐方法,通过比较用户-商品交互,找到与其他人互动相似项目的用户,并推荐那些用户也互动过的项目。这几乎就像你构建了许多定制的刻板印象,并推荐其他由相同刻板印象消费的项目。图 13.6 展示了这个例子:

图 13.6 – 使用协同过滤查找相似的用户评分
当左边的人购买与右边的人相似的商品时,我们可以向左边的人推荐右边的人购买的新商品。在这种情况下,用户-商品交互是一个人购买产品。然而,在推荐系统中,我们谈论评分作为一个术语,它总结了用户和商品之间所有可能的交互。让我们看看如何构建这样的评分函数(也称为反馈函数)。
基于评分的推荐的一个很好的例子是 Spotify 中的个性化推荐播放列表,如图 图 13.7 所示。与之前在播放列表底部的 Spotify 推荐相比,这些推荐是基于我的互动历史和反馈进行个性化的:

图 13.7 – Spotify 的基于评分的歌曲推荐
这些播放列表包含与我听过的歌曲相似的歌曲,并且也是与我品味相似的其他人听过的歌曲。另一个巧妙的扩展是,歌曲推荐根据流派分类到这六个播放列表中。
评分是什么?显式反馈与隐式反馈
反馈函数(或评分)量化了用户与项目之间的互动。我们区分两种类型的反馈——明确评分(或不可观察的反馈)和隐式评分(或直接可观察的反馈)。明确评分可能是对亚马逊上的产品留下五星评价,而隐式评分则是购买该产品。虽然前者是用户的主观决定,但后者可以客观地观察到并评估。
最明显的评分形式是明确要求用户进行反馈——例如,对某部电影、歌曲、文章或支持文档的有用性进行评分。这是人们在首次实施推荐引擎时首先想到的方法。在明确评分的情况下,我们无法直接观察到用户的情感,但必须依赖用户使用评分来量化他们的情感,例如在从一到五的顺序尺度上对电影进行评分。
明确评分存在许多问题——尤其是在顺序尺度上(例如,从一到五的星级)——我们在构建反馈函数时应加以考虑。大多数人在对顺序尺度上的项目进行评分时都会存在偏见——例如,一些用户如果对电影不满意可能会评 3/5,如果喜欢则评 5/5,而其他用户可能会对糟糕的电影评 1/5,对好的电影评 3/5,而只有极少数情况下会评 5/5。
因此,顺序尺度要么需要在用户之间进行标准化,要么你需要使用二进制尺度(如点赞/踩不点赞)来收集二进制反馈。二进制反馈通常更容易处理,因为我们可以从反馈函数中去除用户偏见,简化误差指标,从而提供更好的推荐。如今,许多流行的流媒体服务收集二进制(点赞/踩不点赞、星标/取消星标等)反馈。
这里有一个小片段可以帮助标准化用户评分。它对每个用户评分组应用标准化:
import numpy as np
def normalize_ratings(df,
rating_col="rating",
user_col="user"):
groups = df.groupby(user_col)[rating_col]
# computes group-wise mean/std
mean = groups.transform(np.mean)
std = groups.transform(np.std)
return (df[rating_col] - mean) / std
df["rating_normalized"] = normalize_ratings(df)
训练推荐系统的另一种流行方法是建立一个基于直接观察隐式用户评分的隐式反馈函数。这有一个好处,即用户反馈是无偏见的。常见的隐式评分包括用户将项目添加到购物车、用户购买项目、用户滚动到文章的末尾以及用户观看完整视频到结束。
另一个需要考虑的问题是,用户与项目互动的方式会随时间而变化。这可能是由于用户在服务上消费越来越多的项目而形成的习惯,或者是因为用户偏好的改变。向一个曾经喜欢童年视频的用户推荐视频可能对另一个成年人没有帮助。与这种用户漂移相似,项目的流行度也会随时间变化。今天向用户推荐歌曲 Somebody That I Used to Know 可能不会像 2011 年那样带来相同的点击率。因此,我们也必须在项目评分和反馈函数中建模时间和考虑时间漂移。
可以使用指数时间衰减在数值评分上对显式或隐式评分的时间漂移进行建模。根据业务规则,例如,我们可以使用具有二进制刻度 [1, -1] 的显式评分,并以 1 年的半衰期对这些评分进行指数衰减。因此,1 年后,评分为 1 的评分变为 0.5;2 年后,变为 0.25,依此类推。以下是一个指数衰减评分的示例:
import numpy as np
def cumsum_days(s, duration='D'):
diff = s.diff().astype('timedelta64[%s]' % duration)
return diff.fillna(0).cumsum().values
def decay_ratings(df,
decay=1,
rating_col="rating",
time_col="t"):
weight = np.exp(-cumsum_days(df[time_col]) * decay)
return df[rating_col] * weight
half_life_t = 1
decay = np.log(2) / half_life_t
df["rating_decayed"] = decay_ratings(df, decay=decay)
我们了解到,选择合适的反馈函数非常重要,并且对于设计基于评分的推荐引擎来说,与内容推荐器中的特征工程一样重要。
预测缺失评分以做出推荐
通过收集用户-项目评分,我们生成一个类似于 图 13.8 的稀疏用户-项目-评分矩阵。然而,为了做出推荐,我们首先需要填写图中显示为红色的未知评分。协同过滤是根据预测用例来填充用户-项目-评分矩阵中的空白行或列:

图 13.8 – 用户-项目-评分矩阵
为了向 Alice 推荐最佳电影,我们只需要计算评分矩阵的第一行,而为了计算终结者的最佳候选人,我们只需要计算矩阵的最后一列。重要的是要知道,我们不必每次都计算整个矩阵,这有助于显著提高推荐性能。
你可能也已经猜到,随着用户和/或项目数量的增加,这个矩阵会变得非常大。因此,我们需要一个高效的并行算法来计算空白评分,以便做出推荐。解决这个问题的最流行的方法是使用矩阵分解,因此将矩阵分解为两个低维矩阵的乘积。这两个矩阵及其维度可以解释为用户特征矩阵和项目特征矩阵;通过类比,维度指的是不同独特特征的数目——所谓的潜在表示。
一旦知道了潜在表示,我们可以通过乘以潜在特质矩阵中的正确行和列来填充缺失的评分。然后,可以通过使用计算出的最高* n* 个评分来做出推荐。但理论就到这里了——让我们看看使用PySpark的例子。除了方法之外,管道中的其他所有内容都与标准 ML 管道相同。
与所有之前的管道类似,我们也计算了用于验证模型性能的训练集和测试集,使用分组选择算法(例如,LeavePGroupsOut和GroupShuffleSplit),执行训练、优化超参数、验证模型测试性能,最后,将多个模型堆叠在一起。正如许多其他方法一样,大多数模型都是使用梯度下降进行训练的。我们还可以使用标准的回归损失函数,如RMSE,来计算我们的推荐在测试集上的拟合度。让我们深入到例子中。
使用 ALS 分解进行可扩展的推荐
要使用矩阵分解训练一个大型协同过滤模型,我们需要一个易于分布的算法。Spark MLlib包中的 ALS 算法是一个很好的选择——然而,还有许多其他用于矩阵分解的算法,例如贝叶斯个性化排名、FastAI 的嵌入点偏差或神经协同过滤。
重要提示
前面方法的示例应用总结可以在微软的 GitHub 仓库github.com/Microsoft/Recommenders中找到。
通过使用 Spark,或者更确切地说,PySpark——Spark 及其库的 Python 绑定——我们可以利用 Spark 的分布式计算框架。虽然可以在本地单节点、单核进程中运行 Spark,但它可以轻松地扩展到拥有数百甚至数千个节点的集群。因此,它是一个很好的选择,因为如果你的输入数据规模扩大并超过了单个节点的内存限制,你的代码会自动变得可扩展:
-
让我们首先在 PySpark 中使用
MLlib(Spark 的标准 ML 库)创建和参数化一个 ALS 估计器。我们将在MLlib的推荐包中找到ALS:import pyspark from pyspark.ml.recommendation import ALS sc = pyspark.SparkContext('local[*]') n_iter = 10 rank = 10 l2_reg = 1 als = ALS() \ .setMaxIter(n_iter) \ .setRank(rank) \ .setRegParam(l2_reg)
在前面的代码中,我们初始化了ALS估计器,并定义了梯度下降优化的迭代次数、潜在特质矩阵的秩和 L2 正则化常数。
-
接下来,我们使用这个估计器来拟合模型:
model = als.fit(train_data) -
这就是我们必须要做的。一旦模型成功训练,我们就可以通过在训练模型上调用
transform方法来预测测试集的评分:y_test = model.transform(test_data) -
为了计算推荐的性能,我们使用回归评估器和
rmse指标作为评分函数:from pyspark.ml.evaluation import RegressionEvaluator scoring = RegressionEvaluator(metricName="rmse", labelCol="rating", predictionCol="y") -
为了计算
rmse评分,我们只需在scoring对象上调用evaluate方法:rmse = scoring.evaluate(y_test)
恭喜!您已成功实现了一个基于评分的推荐引擎,该引擎通过分解用户-项目评分矩阵采用协同过滤方法。您是否意识到这种方法类似于寻找矩阵的特征向量,并且它们可以被解释为用户原型(或用户口味、特质等)?虽然这种方法非常适合创建多样化的推荐,但它需要(许多)用户-项目评分的可用性。因此,它非常适合用户交互频繁的服务,而对于完全新用户(冷启动问题)则效果不佳。
在混合推荐引擎中结合内容和评分
与将基于评分的推荐器视为基于内容的推荐器的继任者不同,你应该在积累了足够用户-项目交互数据以提供仅评分推荐之后,将它们视为一种不同的推荐器。在大多数实际情况下,推荐引擎将同时存在这两种方法——要么是两种不同的算法,要么是一个单一的混合模型。在本节中,我们将探讨如何训练这样的混合模型。
要使用Matchbox 推荐器构建最先进的推荐器,请打开 Azure 机器学习设计器,并将 Matchbox 推荐器的构建块添加到画布上,如图下所示。正如我们所看到的,推荐器现在可以接受评分和用户及项目特征作为输入,以创建混合推荐模型:

图 13.9 – Azure 机器学习设计器中的 Matchbox 推荐器
为了配置 Matchbox 推荐器,我们需要配置特质的数量,从而确定潜在空间矩阵的维度。我们将此值设置为 10。类似于基于内容的推荐器,我们不应将原始未经处理的特征向量直接输入到推荐器中,而应该预处理数据,并使用高级 NLP 技术对分类变量进行编码。
一旦您在 Azure 机器学习设计器中构建了推荐引擎,只需简单地按运行来训练模型。您还可以将输入和输出块拖放到画布上,以将此模型作为 Web 服务部署。
目前,Matchbox 推荐器仅通过图形界面提供。但是,您可以使用其他混合模型,如极端深度因子分解机和宽深度,从 Python 中训练混合推荐器。
混合推荐器非常强大,因为它们有助于避免冷启动问题,但一旦用户提供了项目评分,就会根据评分来细化推荐。然而,额外的评分仅用于细化预测,并且类似于所有之前的技巧,混合推荐器在部署之前必须进行训练。
在下一节中,我们将探讨一种无需用户评分即可部署,并在用户与物品互动时在线训练的推荐器——基于强化学习的推荐器。
通过强化学习进行自动优化
您可以通过提供在线训练技术来改进您的推荐,这些技术会在每次用户与物品互动后重新训练您的推荐系统。通过用奖励函数替换反馈函数并添加强化学习模型,我们现在可以做出推荐、做出决策,并优化选择以优化奖励函数。
这是一种训练推荐模型的新颖方法。Azure Personalizer 服务正好提供了这种功能——通过向用户提供上下文特征和奖励函数来做出和优化决策和选择。Azure Personalizer 使用上下文赌博机,这是一种围绕在给定上下文中做出或选择离散动作的强化学习方法。
重要提示
在内部,Azure Personalizer 使用微软研究机构的 Vowpal Wabbit (github.com/VowpalWabbit/vowpal_wabbit/wiki) 学习系统,为推荐系统提供高吞吐量和低延迟的优化。
从开发者的角度来看,Azure Personalizer 非常易于使用。基本的推荐器 API 由两个主要请求组成,即排名请求和奖励请求。在排名请求期间,我们向 API 发送当前用户的用户特征,以及所有可能的物品特征,API 返回这些物品的排名和响应中的事件 ID。
使用此响应,我们可以向用户展示物品,然后用户将与这些物品互动。每当用户创建隐式反馈(例如,点击物品或滚动到物品的末尾),我们就会向服务发出第二次调用,这次是奖励 API。在这个请求中,我们只向服务发送事件 ID 和奖励(一个数值)。这将触发使用新奖励和之前提交的用户和物品特征的另一个训练迭代。因此,随着每次迭代和每次服务调用,我们优化推荐引擎的性能。
Azure Personalizer SDK 适用于多种不同的语言,主要是官方 REST API 的包装器。为了安装 Python SDK,请在您的 shell 中运行以下命令:
pip install azure-cognitiveservices-personalizer
现在,前往 Azure 门户,从门户部署 Azure Personalizer 的一个实例,并配置奖励和探索设置,如以下段落所述。
重要提示
您可以在官方文档中找到有关 Azure Personalizer 配置的更多信息,请参阅docs.microsoft.com/en-us/azure/cognitive-services/personalizer/how-to-settings。
首先,您需要配置算法应该等待多长时间来收集特定事件的奖励,如图 图 13.10 所示。在此时间内,奖励由奖励聚合函数收集和汇总。您还可以定义模型更新频率,当需要快速变化的用户行为的推荐时,这允许您频繁地训练您的模型。将奖励时间和模型更新频率设置为相同的值是有意义的——例如,10 分钟:

图 13.10 – 奖励设置
在前面的图中,我们还可以选择在奖励等待时间内收集的同一事件的奖励聚合函数。可能的选项是最早和总和——因此,只使用奖励周期内的第一个奖励或所有奖励的总和。
探索设置使算法随着时间的推移探索替代模式,这在通过探索发现一系列多样化的项目时非常有帮助。这可以通过探索所使用的排名调用百分比来设置,如图 图 13.11 所示:

图 13.11 – 探索设置
因此,在 20%的调用中,模型不会返回最高排名的项目,而是会随机探索新的项目和它们的奖励。探索的值应该大于 0%以让强化算法尝试随时间变化的物品变体,并设置低于 100%以避免使算法完全随机。
让我们在您的应用程序中使用 Python 嵌入一个推荐引擎:
-
让我们获取您的资源密钥,打开一个 Python 环境,并开始实现排名和奖励调用。首先,我们定义这两个调用的 API URL:
personalization_base_url = "https://<name>.cognitiveservices.azure.com/" resource_key = "<your-resource-key>" rank_url = personalization_base_url \ + "personalizer/v1.0/rank" reward_url = personalization_base_url \ + "personalizer/v1.0/events/" -
接下来,我们创建一个独特的
eventid函数和一个包含当前用户特征和所有可能操作的项目特征的对象。一旦请求构建完成,我们就可以将其发送到排名 API:eventid = uuid.uuid4().hex data = { "eventid": eventid, "contextFeatures": user_features, "actions": item_features } response = requests.post(rank_url, headers=headers, json=data) -
响应包含可能的物品/操作的排名以及一个概率值,以及位于
rewardActionId属性下的获胜项目:{ "result": { "ranking": [ { "id": "ai-for-earth", "probability": 0.664000034 }, ... ], "eventId": "482d82bc-2ff8-4721-8e92-607310a0a415", "rewardActionId": "ai-for-earth" } } -
让我们从
response中解析rewardActionId——这包含获胜的项目,因此是用户的推荐操作:action_id = response.json()["rewardActionId"] prediction = json.dumps(action_id).replace('"','') -
使用这个排名,我们可以根据
rewardActionId将获胜的项目返回给用户。我们现在给用户一些时间来与项目互动。最后,我们使用这个 ID 将跟踪的隐式反馈作为奖励值返回给奖励 API:reward_url = reward_url + eventid + "/reward" response = requests.post(reward_url, headers=headers, json = {"value": reward})
这就是您需要使用 Python 和 Azure Personalizer 在您的应用程序中嵌入一个完全在线的自训练推荐引擎的全部内容。就这么简单。如前所述,还有许多其他语言的 SDK 可以包装 API 调用。
重要提示
可以在personalizationdemo.azurewebsites.net/找到 Personalizer 的演示,用于测试奖励函数以及服务的请求和响应。
在 GitHub 上提供了其他语言的详细最新示例,网址为github.com/Azure-Samples/cognitive-services-personalizer-samples。
摘要
在本章中,我们讨论了不同类型推荐引擎的需求,从非个性化推荐引擎到基于评分和内容的推荐引擎,以及混合模型。
我们了解到基于内容的推荐引擎使用特征向量和余弦相似度来仅基于内容计算相似的项目和用户。这使我们能够通过k-means 聚类或基于树的回归模型进行推荐。一个重要的考虑因素是分类数据的嵌入,如果可能的话,应使用语义嵌入来避免基于 one-hot 或标签编码的相似性混淆。
基于评分的推荐或协同过滤方法依赖于用户-项目交互,即所谓的评分或反馈。虽然显式反馈是通过序数或二进制尺度收集用户评分的最明显可能性,但我们需要确保这些评分得到适当的归一化。
另一种可能性是直接通过隐式评分观察反馈——例如,用户购买了一个产品,点击了一篇文章,滚动到页面底部,或者观看了一个完整的视频直到结束。然而,这些评分也会受到用户偏好随时间漂移以及项目随时间流行度的影响。为了避免这种情况,可以使用指数时间衰减来随时间降低评分。
基于评分的方法非常适合提供多样化的推荐,但需要大量的现有评分才能有良好的性能。因此,它们通常与基于内容的推荐相结合,以解决冷启动问题。因此,流行的最先进推荐模型通常在单个混合模型中结合这两种方法,其中Matchbox 推荐器就是一个例子。
最后,你了解了使用强化学习实时优化推荐器反馈函数的可能性。Azure Personalizer是一个可以用来创建混合在线推荐器的服务。
在下一章中,我们将探讨如何将我们的训练模型作为批量或实时评分系统直接从 Azure 机器学习服务部署。
第四部分:机器学习模型部署和操作
在本节的最后部分,我们将通过将模型部署到集群进行批量评分或到端点进行在线评分,将我们的模型投入生产,并学习如何监控这些部署。此外,我们还将讨论专门的部署目标和与其他 Azure 服务的可用集成。将我们所学的一切结合起来,我们将学习如何使用 MLOps 概念和 Azure DevOps 操作企业级端到端机器学习(ML)项目。最后,我们将以总结我们所学的内容结束本书,看看什么会改变,以及我们构建 ML 模型和与数据合作时的责任。
本节包括以下章节:
-
第十四章, 模型部署、端点和操作
-
第十五章, 模型互操作性、硬件优化和集成
-
第十六章, 使用 MLOps 将模型投入生产
-
第十七章, 为成功的人工智能之旅做准备
第十四章:第十四章:模型部署、端点和操作
在上一章中,我们学习了如何通过特征工程、自然语言处理和分布式算法构建高效且可扩展的推荐引擎。
在本章中,我们将解决在训练推荐引擎或任何机器学习模型之后的下一步;我们将部署和操作机器学习模型。这需要我们打包和注册模型,构建执行运行时,构建网络服务,并将所有组件部署到执行目标。
首先,我们将查看部署机器学习模型到生产环境所需的所有准备工作。你将学习典型部署过程中所需的步骤,如何打包和注册训练好的模型,如何定义和构建推理环境,以及如何选择部署目标来运行模型。
在下一节中,我们将学习如何构建一个用于实时评分服务的网络服务,类似于 Azure 认知服务,但使用自定义模型和自定义代码。我们将探讨模型端点、受控发布和端点模式,以便模型可以在不停机的情况下部署,并集成到其他服务中。最后,我们还将构建一个批处理评分解决方案,可以通过网络服务或管道进行调度或触发。
在最后一节中,我们将关注如何监控和操作你的机器学习评分服务。为了优化性能和成本,你需要跟踪系统级指标,以及遥测数据和评分结果,以检测模型或数据漂移。到本节结束时,你将能够自信地在 Azure 中部署、调整和优化你的评分基础设施。
在本章中,你将涵盖以下主题:
-
模型部署准备
-
在 Azure 中部署机器学习模型
-
Azure 中的机器学习操作
技术要求
在本章中,我们将使用以下 Python 库和版本来创建模型部署和端点:
-
azureml-core 1.34.0 -
azureml-sdk 1.34.0 -
scikit-learn 0.24.2 -
joblib 1.0.1 -
numpy 1.19.5 -
tensorflow 2.6.0 -
pandas 1.3.3 -
requests 2.25.1 -
nvidia-smi 0.1.3
与前几章类似,你可以使用本地 Python 解释器或 Azure 机器学习托管的工作簿环境运行此代码。然而,所有脚本都需要在 Azure 中安排执行。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter14。
模型部署准备
在本书中,我们已经学习了如何通过 Azure 的工具对各种机器学习模型进行实验、训练和优化,以执行分类、回归、异常检测、图像识别、文本理解和推荐。在成功训练我们的机器学习模型后,我们现在希望使用 Azure 的工具将此模型打包并部署到生产环境中。
在本节中,我们将学习使用 Azure Machine Learning 将训练好的模型部署到生产环境所需的最重要准备步骤。我们将讨论标准化部署中的不同组件、定制部署、自动部署以及如何选择合适的部署目标。让我们深入探讨。
理解机器学习模型的组件
不论是哪种用例,将机器学习模型投入生产都需要类似的准备步骤。首先,需要将训练好的模型注册到模型注册表中。这将使我们能够跟踪模型版本和二进制文件,并在部署中获取特定版本的模型。其次,我们需要指定部署资产(例如,环境、库、资产和评分文件)。这些资产定义了模型如何加载和初始化,用户输入如何解析,模型如何执行,以及输出如何返回给用户。最后,我们需要选择一个计算目标来运行模型。
当使用 Azure Machine Learning 进行部署时,你需要明确指定一系列事项,以便将机器学习模型作为 Web 服务部署和运行。这个列表包括以下组件:
-
训练好的模型:模型定义和参数
-
推理环境:描述环境的配置,例如 Docker 文件
-
评分文件:用于解析用户输入和输出并调用模型的 Web 服务代码
-
运行时:评分文件的运行时,例如 Python 或 PySpark
-
计算目标:运行 Web 服务的计算环境,例如 Azure Kubernetes 服务(AKS)或 Azure 容器实例(ACI)
让我们更详细地探讨这五个组件:
-
首先,我们需要一个训练好的模型。一个模型(根据所使用的框架、库和算法)可能由一个或多个存储模型参数和结构的文件组成。在 scikit-learn 中,这可能是一个序列化的估计器;在 LightGBM 中,这可能是一系列决策树的序列化列表;而在 Keras 中,这可能是一个模型定义和一个存储模型权重的二进制 blob。我们称之为“模型”,并将其存储和版本化在 Blob 存储中。在评分服务的启动时间,模型将被加载到评分运行时中。
-
除了模型之外,我们还需要一个执行环境,这可以通过
InferenceConfig来定义。在 Azure 机器学习部署中,环境将被构建成一个Docker镜像,并存储在您的私有 Docker 注册库中。在部署过程中,Azure 机器学习将自动从提供的环境配置中构建 Docker 镜像,并将其加载到您工作区中的私有注册库中。
在 Azure 机器学习部署中,您可以选择预定义的 ML 环境或配置自己的环境和 Docker 基础镜像。在基础镜像之上,您可以定义一个 Python Pip 或 Conda 依赖项列表,启用 GPU 支持,或配置自定义 Docker 步骤。环境,包括所有必需的包,将在运行时自动提供,并在 Docker 镜像上设置。在此基础上,环境还可以由 Azure 机器学习服务注册和版本化。这使得跟踪、重用和组织您的部署环境变得容易。
-
接下来,我们需要一个所谓的评分文件。这个文件通常加载模型,并提供一个函数,当给定一些数据作为输入时,对模型进行评分。根据部署的类型,您需要为(实时)同步评分服务或异步批量评分服务提供评分文件。评分文件应在您的版本控制系统中进行跟踪,并将被挂载到 Docker 镜像中。
-
要完成
InferenceConfig,我们缺少最后但非常重要的一步:用于运行您的评分文件的 Python 运行时。目前,Python 和 PySpark 是唯一支持的运行时。 -
最后,我们需要一个执行目标,它定义了 Docker 镜像应该在其上执行的计算基础设施。在 Azure 中,这被称为计算目标,并通过部署配置来定义。计算目标可以是管理的 Kubernetes 集群(如 AKS)、容器实例(如 ACI)、Azure 机器学习计算(AmlCompute)或许多其他 Azure 计算服务之一。
重要提示
前面的组件仅适用于 Azure 机器学习内的托管部署。没有任何东西阻止您在其他环境中检索模型二进制文件或在您的本地计算目标上运行推理环境(Docker 镜像)。
如果您只想部署标准模型文件,例如 scikit-learn、ONNX或 TensorFlow 模型,您也可以使用 Azure Machine Learning 中内置的自动部署功能。自动部署不需要提供所有前面的组件,只需提供所使用框架的名称和版本以及资源配置,例如执行所需的 CPU 数量和 RAM 量。Azure Machine Learning 将完成其余工作;它将提供所有必需的配置并将模型部署到 ACI。这使得使用不超过一行代码即可轻松部署标准模型——非常适合开发、调试和测试。
现在我们已经了解了 Azure Machine Learning 中的基本部署组件,我们可以继续并查看一个注册模型以准备部署的示例。
在模型注册表中注册您的模型
部署过程的第一步应该在训练和优化过程期间或之后发生,即在每个运行中注册 Azure Machine Learning 模型注册表中的最佳模型。无论您的训练脚本生成单个模型、模型集成还是与多个文件结合的模型,您都应该始终存储训练工件并在 Azure Machine Learning 工作区中注册每个运行的最佳模型。
在您的训练脚本中,只需额外一行代码即可将模型存储在 Azure Machine Learning 中并注册,因此您永远不会丢失训练的工件和模型。Blob 存储和模型注册直接集成到您的工作区中,因此该过程紧密集成到训练过程中。一旦模型注册,Azure Machine Learning 提供了一个方便的界面,可以从注册表中加载模型。
让我们快速了解一下这对您的训练脚本意味着什么:
-
让我们定义运行上下文并训练
sklearn分类器:Run = Run.get_context() exp = run.experiment # train your model clf, test_acc = train_sklearn_mnist() -
接下来,我们编写一个小的辅助函数,该函数从所有之前的运行中返回最佳测试准确度指标。我们将使用此指标来检查新模型是否优于所有之前的运行:
Def get_metrics(exp, metric): for run in Run.list(exp, status='Completed'): yield run.get_metrics().get(metric) m_name = 'Test accuracy' best_acc = max(get_metrics(exp, m_name), default=0) -
接下来,我们检查模型是否比所有之前的运行表现更好,并将其作为新版本注册到模型工厂中:
Import joblib # serialize the model and write it to disk joblib.dump(clf, 'outputs/model.pkl') if test_acc > best_acc: model = run.register_model( model_name='sklearn_mnist', model_path='outputs/model.pkl') print(model.name, model.id, model.version, sep='\t')
在前面的代码块中,我们首先使用joblib.dump()函数将训练好的分类器序列化并存储到磁盘上。然后我们调用run.model_register()函数将训练好的模型上传到默认数据存储并将模型注册到磁盘上。这将自动通过名称跟踪和版本控制模型,并将其链接到当前的训练运行。
-
一旦您的模型存储在 Azure Machine Learning 工作区的模型注册表中,您就可以用于部署,并在任何调试、测试或实验步骤中通过名称检索它。您只需通过在本地机器上运行以下代码片段即可简单地请求最新的模型:
import joblib from azureml.core.model import Model model_path = Model.get_model_path('sklearn_mnist') model = joblib.load(model_path)
在前面的代码中,我们只是运行Model.get_model_path()来通过名称检索模型的最新版本。我们也可以指定版本号来从注册库中加载特定模型。
内置模型注册库是 Azure Machine Learning 工作区的一项功能,它让您上瘾,并使您在未来的模型注册、实验运行和指标跟踪中永远不会错过。它在使用不同环境中的模型工件以及在不同实验期间工作时提供了极大的灵活性和透明度。
在前面的示例中,我们没有提供关于训练模型的任何元数据,因此 Azure Machine Learning 无法从模型工件中推断出任何信息。然而,如果我们提供有关模型的额外信息,Azure Machine Learning 可以自动为您生成一些所需的部署配置,以便您启用自动部署。让我们在下一节中看看这一点。
已注册模型的自动部署
如果您坚持使用 scikit-learn、TensorFlow 或 ONNX 提供的标准功能,您也可以在 Azure Machine Learning 中利用自动部署。这将允许您将注册的模型部署到测试、实验或生产环境,而无需定义任何所需的部署配置、资产和服务端点。
重要提示
Azure Machine Learning 模型的自动部署将自动将您的模型作为 Web 服务提供。如果您在训练期间提供了模型元数据,您可以使用单个命令Model.deploy()来调用自动部署。
让我们看看如何修改前面的示例以利用自动部署:
-
首先,我们定义模型的资源配置,如下面的代码块所示:
From azureml.core.resource_configuration import \ ResourceConfiguration resource_config = ResourceConfiguration( cpu=1, memory_in_gb=2.0, gpu=0) -
接下来,我们需要在注册模型时定义框架和框架版本。为此,我们需要通过扩展
Model.register()参数来向模型添加此附加信息,如下面的代码片段所示:From azureml.core import Model model = run.register_model( model_name='sklearn_mnist', model_path='outputs/model.pkl', model_framework=Model.Framework.SCIKITLEARN, model_framework_version='0.24.2', resource_configuration= resource_config)
在前面的代码中,我们将框架和框架版本添加到模型注册库中,以及为此特定模型配置的资源。模型本身以标准格式存储在支持的框架之一(scikit-learn、ONNX 或 TensorFlow)中。这些元数据被添加到模型注册库中的模型中。这是自动部署此模型作为实时 Web 服务所需的全部配置,只需一行代码即可完成。
-
最后,我们调用
Model.deploy()函数来启动部署过程。这将构建部署运行时作为 Docker 镜像,将其注册到您的容器注册库中,并以托管容器实例的形式启动镜像,包括评分文件、REST 服务抽象和遥测收集:Service_name = 'my-sklearn-service' service = Model.deploy(ws, service_name, [model]) -
部署完成后,要检索评分服务的 URL,我们运行以下代码:
service.wait_for_deployment(show_output=True) print(service.state) print("Scoring URL: " + service.scoring_uri)
如果您想要对执行环境、端点配置和计算目标有更细粒度的控制,您可以使用高级推理、部署和服务配置来定制您的部署。现在让我们看看定制部署。
自定义部署环境
如您在前几章中看到的,使用 ML 模型转换数据所需的库、框架和自定义步骤数量巨大。Azure 机器学习为我们提供了足够的灵活性来配置 ML 评分服务,以反映这些自定义设置。在本节中,我们将学习如何自定义部署以包括库和框架。让我们更深入地探讨这些单独的部署步骤。
在 Azure 机器学习服务中,您使用执行环境来指定基础 Docker 镜像、Python 运行时以及评分模型所需的所有依赖包。与模型一样,环境也可以在 Azure 中注册和版本控制,因此 Docker 工件和元数据都存储、版本控制和跟踪在您的工作区中。这使得跟踪环境更改变得简单,可以找出特定运行所使用的环境,在多个环境版本之间来回切换,以及为多个项目共享环境。
执行以下步骤以在 Docker 中构建和打包您的部署:
-
让我们从编写一个辅助函数开始,该函数可以动态创建环境。当根据包列表程序化创建环境时,此代码片段非常有用。我们还将自动将
azureml-defaults包添加到每个环境中:From azureml.core import Environment from azureml.core.conda_dependencies import \ CondaDependencies def get_env(name="my-env", packages=None): packages = packages or [] packages += ['azureml-defaults'] conda_deps = CondaDependencies.create( pip_packages=packages) env = Environment(name=name) env.python.conda_dependencies = conda_deps return env
正如您在前面的代码块中所看到的,我们首先初始化一个Environment实例,然后添加多个conda包。我们通过覆盖env.python.conda_dependencies属性来分配conda依赖项,该属性使用conda_deps依赖项。使用相同的方法,我们还可以分别使用env.docker和env.spark覆盖 Docker、Spark 以及任何额外的 Python 设置。
-
接下来,我们可以定义一个用于实验、训练或部署的自定义环境:
myenv = get_env(name="PythonEnv", packages=["numpy", "scikit-learn", "tensorflow"]) -
在下一步中,您现在可以使用描述性名称注册环境。这将添加当前环境配置的新版本到具有相同名称的环境:
myenv.register(ws, name="PythonEnv") -
您还可以使用以下代码从注册表中检索环境。当您已注册一个可以重复使用和扩展到多个实验的基础环境时,这也很有用:
myenv = Environment.get(ws, name="PythonEnv") -
与
model注册表一样,您还可以使用指定的版本作为附加参数来加载环境。一旦您配置了执行环境,您就可以将其与评分文件组合成一个InferenceConfig对象。评分文件实现了从注册表中加载模型并针对一些输入数据进行评估的所有功能。配置可以定义如下:from azureml.core.model import InferenceConfig inference_config = InferenceConfig( entry_script="score.py", environment=myenv)
在前面的示例中,我们可以看到我们只是指定了在本地创作环境中评分脚本的相对路径。因此,您首先必须创建此评分文件;在接下来的几节中,我们将通过批处理和实时评分的示例进行说明。
-
要构建环境,我们可以简单地触发 Docker 镜像的构建:
from azureml.core import Image build = myenv.build(ws) build.wait_for_completion(show_output=True) -
环境将被打包并注册为您的私有容器注册库中的 Docker 镜像,其中包含 Docker 基础镜像和所有指定的库。如果您想打包模型和评分文件,您可以只打包模型。这可以在部署模型时自动完成,也可以通过使用
Model.package函数强制执行。让我们从上一节加载模型并打包和注册镜像:model_path = Model.get_model('sklearn_mnist') package = Model.package(ws, [model], inference_config) package.wait_for_creation(show_output=True)重要提示
Azure ML SDK 文档包含一个可能的配置选项的详细列表,您可以在
docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.environment(class)找到。
上述代码将构建并打包您的部署为 Docker 镜像。在下一节中,我们将了解如何选择最佳的计算目标来执行您的 ML 部署。
在 Azure 中选择部署目标
Azure 机器学习服务的一个巨大优势是它们与许多其他 Azure 服务紧密集成。这对于我们希望在 Azure 托管服务中运行 ML 服务的 Docker 镜像的部署非常有帮助。这些计算目标可以配置并利用 Azure 机器学习进行自动部署。
如果您的任务是生产化 ML 训练和部署管道,您可能不一定是一位 Kubernetes 专家。如果是这样,您可能会喜欢 Azure 机器学习 SDK 中 Azure 计算服务管理的紧密集成。类似于创建训练环境,您可以在创作环境中(例如,Jupyter 笔记本编排您的 ML 工作流程)创建 GPU 集群、托管 Kubernetes 集群或简单的容器实例。
我们可以遵循选择特定服务的一般建议,类似于选择计算服务进行常规应用程序部署;因此,我们在可以轻松从 Docker 镜像启动 Web 服务的计算服务之间权衡简单性、成本、可扩展性、灵活性和运营成本。
这里是关于何时使用每个 Azure 计算服务的建议:
-
对于快速实验和本地测试,请使用 Docker 和 Azure 机器学习中的本地部署目标。
-
对于测试和实验,使用 ACI。它易于设置和配置,并且是为了运行容器镜像而设计的。
-
对于需要 GPU 支持的可扩展实时 Web 服务的部署,请使用 AKS。这个托管的 Kubernetes 集群更加灵活和可扩展,但操作起来也更困难。
-
对于批量部署,使用 Azure Machine Learning 集群,这是我们之前已经用于训练的计算集群环境。
对于快速实验,您可以使用 LocalWebservice 作为部署目标在本地部署您的服务。为此,您可以在本地机器上运行以下代码片段,提供推理配置中的评分文件和环境:
From azureml.core.webservice import LocalWebservice
deployment_config = LocalWebservice.deploy_configuration(
port=8890)
service = Model.deploy(ws,
name=service_name,
models=[model],
inference_config=inference_config,
deployment_config=deployment_config)
service.wait_for_deployment(show_output=True)
print(service.state)
如您所见,一旦您的模型注册成功,您可以根据您的用例将其部署到多个计算目标。虽然我们已经涵盖了几个不同的配置选项,但我们还没有讨论多个部署选项和评分文件。我们将在下一节中这样做。
在 Azure 中部署 ML 模型
从广义上讲,部署 ML 模型有两种常见的方法,即作为同步实时 Web 服务和异步批量评分服务部署。请注意,同一个模型可以作为两个不同的服务部署,服务于不同的用例。部署类型在很大程度上取决于模型评分模式的批量大小和响应时间。小批量大小和快速响应需要水平可扩展的实时 Web 服务,而大批量大小和慢速响应时间则需要水平和垂直可扩展的批量服务。
文本理解模型(例如,实体识别模型或情感分析模型)的部署可能包括一个实时 Web 服务,该服务在应用中发布新评论时评估模型,以及另一个 ML 管道中的批量评分器,用于从训练数据中提取相关特征。对于前者,我们希望尽可能快地处理每个请求,因此我们将同步评估小批量。对于后者,我们正在评估大量数据,因此我们将异步评估大批量。我们的目标是,一旦模型打包并注册,我们就可以将其用于任何任务或用例。
在本节中,我们将查看这些部署方法,并构建一个用于实时评分的服务和一个用于批量评分的服务。我们还将评估不同的选项来管理和执行评分服务的部署。
构建实时评分服务
在本节中,我们将构建一个 Azure Machine Learning 中的实时评分服务。我们将探讨支持该 Web 服务的所需评分文件,以及启动 AKS 集群上服务的配置。
在这个例子中,我们将训练一个 NLP Hugging Face transformer 模型,用于对用户输入进行情感分析。我们的目标是构建我们自己的 Cognitive Services Text Analytics API,该 API 使用在自定义数据集上训练或微调的自定义模型。
为了做到这一点,我们将训练一个情感分析管道,将其保存,并在 Azure Machine Learning 中将其注册为模型,如下面的代码片段所示:
clf = train(name="sentiment-analysis")
clf.save_pretrained("outputs/sentiment-analysis")
model = Model.register(ws,
model_name='sentiment-analysis',
model_path='outputs/sentiment-analysis')
一旦我们有了模型,我们就通过查看评分文件开始构建 Web 服务。评分文件将在 Web 服务启动时加载,并且对于每个对 ML 服务的请求都会被调用。因此,我们使用评分文件来加载 ML 模型,解析请求中的用户数据,调用 ML 模型,并返回 ML 模型的预测结果。为此,你需要在评分文件中提供init()和run()函数,其中run()函数在服务启动时运行一次,并且对于每个请求,都会使用用户输入调用run方法。以下是一个简单的评分文件示例:
scoring_file_example.py
def init():
print("Initializing service")
def run(data):
print("Received a new request with data: ", data)
现在我们有了训练好的模型,并且我们知道评分文件的结构,我们可以继续构建我们的自定义 Web 服务:
- 让我们从服务的初始化开始。我们首先定义一个全局模型变量,然后从
AZUREML_MODEL_DIR环境变量中获取模型路径。这个变量包含模型在本地磁盘上的位置。接下来,我们使用 Hugging Face 的AutoModel转换器加载模型:
Scoring_file.py
from transformers import AutoModel
from azureml.core import Model
def init():
global model
model_path = os.getenv("AZUREML_MODEL_DIR")
model = AutoModel.from_pretrained(model_path,
from_tf=True)
- 接下来,我们处理 Web 服务的实际推理部分。为此,我们需要解析传入的请求,调用 NLP 模型,并将预测结果返回给调用者:
Scoring_file.py
import json
def run(request):
try:
data = json.loads(request)
text = data['query']
sentiment = model(text)
result = {'sentiment': sentiment}
return result
except Exception as e:
return str(e)
在run()函数中,我们提供了一个request对象。该对象包含发送到服务的请求正文。由于我们期望 JSON 输入,我们将请求正文解析为 JSON 对象,并通过query属性访问输入字符串。我们期望客户端发送一个包含此架构的有效请求。最后,我们返回一个预测,它将被自动序列化为 JSON 并返回给调用者。
-
为了测试目的,让我们将服务部署到 ACI 计算目标。为此,我们需要更新部署配置以包含 ACI 资源配置:
from azureml.core.webservice import AciWebservice deploy_config = AciWebservice.deploy_configuration( cpu_cores=1, memory_gb=1)重要提示
你可以在官方文档中找到有关 Azure Container Instance 的更多信息:
docs.microsoft.com/en-us/azure/container-instances/container-instances-overview。 -
接下来,我们将环境和评分文件传递给推理配置:
from azureml.core.model import InferenceConfig env = get_env(name="sentiment-analysis", package=["tensorflow", "transformers"]) inference_config = InferenceConfig( environment=env, source_directory="code", entry_script="scoring_file.py", ) -
在拥有所有必需组件后,我们最终可以将模型、推理配置和部署配置传递给
Model.deploy方法并开始部署:service_name = "sentiment-analysis" service = Model.deploy(ws, name=service_name, models=[model], inference_config=inference_config, deployment_config=deploy_config) service.wait_for_deployment(show_output=True) print(service.state) -
一旦服务启动并运行,我们可以尝试向服务发送测试请求,以确保一切正常工作。默认情况下,Azure Machine Learning 服务使用基于密钥的(主和辅助)身份验证。让我们从服务中检索密钥,并发送一些测试数据到部署的服务:
import requests import json from azureml.core import Webservice service = Webservice(ws, name="sentiment-analysis") scoring_uri = service.scoring_uri # If the service is authenticated key, _ = service.get_keys() # Set the appropriate headers headers = {"Content-Type": "application/json"} headers["Authorization"] = f"Bearer {key}" data = {"query": "AzureML is quite good."} resp = requests.post(scoring_uri, data=json.dumps(data), headers=headers) print(resp.text)
前面的代码片段获取服务 URL 和访问密钥,并将 JSON 编码的数据作为 POST 请求发送到 ML 模型部署。
就这样!您已成功部署了情感分析模型,并从 Python 中进行了测试。然而,使用服务端点和令牌,您还可以从任何其他编程语言或 HTTP 客户端向您的服务发送请求。
部署到 Azure Kubernetes 服务
我们已成功将情感分析模型部署到 ACI。然而,作为下一步,我们希望将其部署到 AKS。虽然 ACI 对于快速部署 Docker 容器非常出色,但 AKS 是一个用于复杂基于容器的生产工作负载的服务。AKS 支持认证、自动扩展、GPU 支持、副本以及高级指标和日志等功能。
重要提示
您可以在官方文档中找到有关 Azure Kubernetes 服务(AKS)的更多信息:docs.microsoft.com/en-us/azure/aks/intro-kubernetes。
现在我们将此服务部署到 AKS 集群,以便我们可以利用 GPU 加速和自动扩展:
-
首先,我们需要定义我们所需的基础设施:
from azureml.core.compute import AksCompute, \ ComputeTarget # Configure AKS cluster with NVIDIA Tesla P40 GPU prov_config = AksCompute.provisioning_configuration( vm_size="Standard_ND6s") aks_name = 'aks-ml-prod' # Create the cluster aks_target = ComputeTarget.create(ws, name=aks_name, provisioning_configuration=prov_config) # Wait for the create process to complete aks_target.wait_for_completion(show_output=True)
在前面的代码中,我们创建了一个 AKS 配置和一个新的 AKS 集群,作为 Azure Machine Learning 计算目标。所有这些都在您的创作环境中完成。
-
如果您已经有一个正在运行的 AKS 集群,您可以直接使用此集群进行 Azure Machine Learning。为此,您必须将资源组和集群名称传递给
AksCompute.attach_configuration()方法。然后,设置包含 AKS 集群的资源组和集群名称:resource_group = 'my-rg' cluster_name = 'aks-ml-prod' attach_config = AksCompute.attach_configuration( resource_group = resource_group, cluster_name=cluster_name) aks_target = ComputeTarget.attach(ws, cluster_name, attach_config) -
一旦我们有了集群的引用,我们就可以将 ML 模型部署到集群。这一步骤与上一个步骤类似:
deploy_config = AksWebservice.deploy_configuration( cpu_cores=1, memory_gb=1, gpu_cores=1) service = Model.deploy(ws, service_name, [model], inference_config, deploy_config, aks_target) service.wait_for_deployment(show_output=True) print(service.state) print(service.get_logs())
如前例所示,除了将 AKS 集群作为目标附加到 Azure Machine Learning 外,模型部署与使用 ACI 的示例相同。
定义评分端点的模式
在前面的示例中,我们从 JSON 解析用户输入并期望它包含一个 query 参数。为了帮助使用您的服务端点的用户和服务,告诉用户服务期望哪些参数将是有用的。这在构建 Web 服务 API 时是一个常见问题。
为了解决这个问题,Azure Machine Learning 提供了一种创新的方法来自动生成 OpenAPI 规范(OAS),之前称为 Swagger 规范。此规范可以通过模式端点被 API 的消费者访问。这提供了一种自动化的标准化方式来指定和消费服务的数据格式,并且可以用于自动生成客户端。一个例子是 Swagger Codegen,它可以用来为您的新 ML 服务生成 Java 和 C# 客户端。
您可以通过在 Python 中添加注释来为您的服务启用 pandas、NumPy、PySpark 和标准 Python 对象的自动模式生成。首先,您需要将azureml-defaults和inference-schema作为 PIP 包包含到您的环境中。然后,您可以通过为端点提供样本输入和输出数据来自动生成模式,如下面的示例所示:
scoring_file.py
import numpy as np
input_sample = np.array([[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]])
output_sample = np.array([3726.995])
@input_schema('data', NumpyParameterType(input_sample))
@output_schema(NumpyParameterType(output_sample))
def run(data):
# data is a np.array
pass
在前面的示例中,我们通过run()方法中的样本数据和注释定义了基于 NumPy 的模型的模式。
我们还可以选择情感分析模型,并允许它接收多个输入查询。为此,我们可以将用户输入反序列化为 pandas DataFrame 对象,并返回一个预测数组作为结果,如下面的示例所示。请注意,这基本上为我们实时 Web 服务添加了批量预测功能:
scoring_file.py
import numpy as np
import pandas as pd
input_sample = pd.DataFrame(data=[
{'query": "AzureML is quite good."}])
output_sample = np.array([np.array(["POSITIVE", 0.95])])
@input_schema('data', PandasParameterType(input_sample))
@output_schema(NumpyParameterType(output_sample))
def run(data):
# data is a pd.DataFrame
pass
定义示例输入和输出是自动生成 API 规范所需的一切,您的客户端可以使用该规范来验证端点和参数,或自动生成客户端。这同样是创建可以自动集成到 Power BI 的 ML 服务的格式,如第十五章中所示,模型互操作性、硬件优化和集成。
管理模型端点
每个模型部署都包含一个用于发送请求到模型的 URL;在线评分服务提供用于处理在线预测的 URL,批量评分服务提供用于触发批量预测的 URL。虽然这使得启动和查询服务变得容易,但在部署过程中仍然存在一个重大问题,即服务 URL 会随着每次部署而改变。这导致了一个问题,即我们无法控制用户请求将击中哪个服务。
为了解决这个问题,我们需要在固定的服务 URL 后面隐藏模型部署 URL,并提供一种机制将用户请求解析到特定的服务。在 Azure Machine Learning 中,实现这一功能的组件被称为端点,它可以在固定的端点 URL 下暴露多个部署。
下图展示了端点和部署的概念。客户向端点发送请求,我们配置端点将请求路由到服务之一。在部署期间,我们会在相同的评分端点后面添加新的模型版本,并逐步从新的(绿色)版本而不是之前的(蓝色)版本开始服务请求:

图 14.1 – Azure Machine Learning 端点和部署
这种类型的部署也称为蓝绿部署。首先,您将所有流量从旧服务中取出,并启动新服务。一旦新服务启动并运行,并且健康检查成功完成,服务将在端点下注册,并开始处理请求。最后,如果旧服务上没有剩余的活跃请求,您可以将其关闭。
这个过程是一种非常安全的更新无状态应用程序服务的方法,零停机时间或最小停机时间。它还帮助您在新服务部署不成功时回滚到旧服务。
Azure 机器学习提供多种类型的端点,具体取决于模型部署机制:
-
在线端点:用于实时在线部署:
-
管理在线端点:用于管理的 Azure 机器学习部署
-
Kubernetes 在线端点:用于管理的 AKS 部署
-
-
批量端点:用于批量评分部署
在顶级上,我们区分在线和批量端点。在线端点用于基于 Web 服务部署的同步评分,而批量端点用于基于管道部署的异步评分。
对于在线端点,我们根据部署目标区分管理型基于 Kubernetes 的在线端点。这与在线评分的不同计算目标和功能相对应。
让我们看看如何配置 AKS 的端点:
-
首先,我们配置端点详情,如下面的代码片段所示:
from azureml.core.webservice import AksEndpoint endpoint_config = AksEndpoint.deploy_configuration( version_name="version-1", tag'={'modelVersion':'1'}, namespace="nlp", traffic_percentile=100)
端点配置充当 AKS 计算目标的部署配置。
-
接下来,我们将端点配置和计算目标都提供给
Model.deploy方法:endpoint_name"= "sentiment-analysis" endpoint = Model.deploy(ws, endpoint_name, [model], inference_config, endpoint_config, aks_target) endpoint.wait_for_deployment(show_output=True) print(endpoint.state)
部署将返回一个端点,现在可以用来连接到服务并添加额外的配置。在下一节中,我们将探讨端点的更多用例,并了解如何向 AKS 端点添加额外的部署。
控制式发布和 A/B 测试
端点的另一个好处是执行受控发布和新模型版本的增量测试。机器学习模型部署类似于应用程序开发中新功能的部署。我们可能不想一次性将这个新功能推出给所有用户,而是首先测试新功能是否提高了我们一小部分用户的业务指标。
新的机器学习模型部署绝不应该是不受控制的或基于个人感受或偏好;部署应始终基于硬性指标和真实证据。测试和向用户推出更改的最佳和最系统的方法是定义一个关键指标,将新模型推出给用户的一部分(组 B),并将旧模型服务于剩余的用户部分(组 A)。一旦组 B 中用户的指标在定义的期间内超过组 A 的指标,您就可以自信地将该功能推出给所有用户。
这个概念被称为A/B 测试,并被许多科技公司用于推广新服务和功能。正如你可以在下面的图中看到的那样,你将流量分成控制组和挑战者组,其中只有后者被提供新模型:

图 14.2 – 使用端点进行 A/B 测试
A/B 测试和蓝绿部署可以很好地结合使用,因为它们实际上是相似的方法。两者都需要部署一个完全功能的服务,通过路由策略使你的部分用户可以访问。如果你使用 Azure Machine Learning 进行部署和推广策略,你将得到很好的保障。首先,所有通过 Azure Machine Learning 到 ACI 或 AKS 的部署都是蓝绿部署,这使得你很容易回滚到模型的前一个版本。
Azure Machine Learning 在 AKS 上的部署支持在同一端点后面最多六个模型版本,以实现蓝绿部署或 A/B 测试策略。然后你可以定义策略来在这些端点之间分割流量;例如,你可以按百分比分割流量。以下是一个小代码示例,说明如何在 AKS 端点上创建另一个版本,该版本应服务于 50%的用户:
-
让我们先更新原始部署,作为控制版本,服务于 50%的流量:
endpoint.update_version( version_name="version-1", traffic_percentile=50, is_default=True, is_control_version_type=True) -
接下来,我们添加挑战者版本,这是
test_model的部署。正如你可以在下面的代码片段中看到的那样,你也可以为新部署提供不同的推理配置:endpoint.create_version( version_name="version-2", inference_config=inference_config, models=[test_model], tags={'modelVersion':'2'}, description="my second version", traffic_percentile=50) -
最后,我们开始部署更新后的端点:
endpoint.wait_for_deployment(show_output=True) print(endpoint.state)
在前面的代码中,我们展示了 Azure Machine Learning 和 AKS 受控发布的预览功能。我们使用不同的模型和推理配置组合,在同一个端点下部署一个独立的服务。现在,流量分割通过 Kubernetes 的路由自动进行。然而,为了与本章的前一部分保持一致,我们可以预期随着许多客户在推广 ML 模型时使用这项功能,其功能将在未来得到改进。
实现批评分流程
运行批评分服务与之前讨论的在线评分方法非常相似;你需要提供一个环境、计算目标和评分脚本。然而,在你的评分文件中,你更愿意传递一个指向 Blob 存储位置的新数据批次的路径,而不是数据本身。然后你可以使用你的评分函数异步处理数据,并将预测输出到不同的存储位置,回到 Blob 存储,或者异步将数据推送到调用服务。
你如何实现评分文件取决于你,因为它只是一个你控制的 Python 脚本。在部署过程中唯一的区别是批评分脚本将作为 Azure 机器学习集群上的计算部署,通过管道定期调度或通过 REST 服务触发。因此,确保你的评分脚本可以通过命令行参数进行配置非常重要。记住,批评分的不同之处在于我们不向评分脚本发送数据,而是发送数据的路径和写入输出的路径异步发送。
批评分脚本通常封装在管道步骤中,作为管道部署,并通过 REST 服务或批评分端点触发。管道可以配置为使用 Azure 机器学习集群进行执行。在本节中,我们将重用我们之前在第八章,Azure 机器学习管道中看到的所有概念,并将它们应用到批评分管道步骤中。让我们构建一个使用 Inception v3 DNN模型评分图像的批评分管道:
-
首先,我们定义一个可配置的批量大小。在管道配置和评分文件中,你可以利用在 Azure 机器学习集群中并行化你的工作:
from azureml.pipeline.core.graph import \ PipelineParameter batch_size_param = PipelineParameter( name="param_batch_size", default_value=20) -
接下来,我们定义一个将调用批评分脚本的管道步骤:
from azureml.pipeline.steps import PythonScriptStep batch_score_step = PythonScriptStep( name="batch_scoring", script_name="batch_scoring.py", arguments=[ "--dataset_path", input_images, "--model_name", "inception", "--label_dir", label_dir, "--output_dir", output_dir, "--batch_size", batch_size_param], compute_target=compute_target, inputs=[input_images, label_dir], outputs=[output_dir], runconfig=amlcompute_run_config) -
最后,我们将管道步骤封装在管道中。为了测试批处理步骤,我们将管道作为实验提交到 Azure 机器学习工作区:
from azureml.core import Experiment from azureml.pipeline.core import Pipeline pipeline = Pipeline(ws, steps=[batch_score_step]) exp = Experiment(ws, 'batch_scoring') pipeline_run = exp.submit(pipeline, pipeline_params={"param_batch_size": 20}) -
使用此管道配置,我们使用相关参数调用我们的评分脚本。管道作为 Azure 机器学习中的一个实验提交,这使我们能够访问 Azure 中运行和实验的所有功能。一个功能就是,当实验运行完成后,我们可以简单地下载输出:
pipeline_run.wait_for_completion(show_output=True) step_run = list(pipeline_run.get_children())[0] step_run.download_file("./outputs/result-labels.txt") -
如果批评分文件生成了一个包含名称和预测的漂亮的 CSV 输出,我们现在可以使用以下 pandas 功能来显示结果:
import pandas as pd df = pd.read_csv( "./outputs/result-labels.txt", delimiter=":", header=None) df.columns = ["Filename", "Prediction"] df.head() -
让我们继续将管道作为 REST 服务发布:
published_pipeline = pipeline_run.publish_pipeline( name="Inception_v3_scoring", description="Batch scoring using Inception v3", version="1.0") published_id = published_pipeline.id rest_endpoint = published_pipeline.endpoint -
要通过 HTTP 将发布的管道作为服务运行,我们现在需要使用基于令牌的认证:
from azureml.core.authentication import \ AzureCliAuthentication cli_auth = AzureCliAuthentication() aad_token = cli_auth.get_authentication_header() -
获取了认证令牌后,我们现在可以运行发布的管道:
import requests # Specify batch size when running the pipeline response = requests.post( rest_endpoint, headers=aad_token, json={ "ExperimentName": "batch_scoring", "ParameterAssignments": { "param_batch_size": 50 } }) run_id = response.json()["Id"]
就这样!你现在可以使用 REST 端点来触发你的批评分管道。数据将被处理,结果将以一个可以编程方式消费或管道传输到下一个处理步骤的文件提供。
在 Azure Machine Learning 服务上运行批处理评分流水线与运行同步评分服务略有不同。虽然实时评分服务使用 Azure Machine Learning 部署和 AKS 或 ACI 作为流行的计算目标,但批处理评分模型通常作为发布流水线部署在 AmlCompute 之上。发布流水线的优点是它可以作为一个 REST 服务使用,可以触发和参数化流水线。
Azure 中的 ML 操作
在上一节中,您成功注册了一个训练好的模型、一个环境、一个评分文件和一个推理配置。您已经优化了模型以进行评分,并将其部署到了一个管理的 Kubernetes 集群中。您为您的 ML 服务自动生成了客户端 SDK。所以,您终于可以放松一下,享受您辛勤工作的成果了吗?嗯,还不行!首先,我们需要确保我们已经设置了所有监控,这样我们就可以观察并应对部署中发生的任何情况。
首先,让我们看看优点:使用 Azure Machine Learning 部署和管理计算目标,您将获得许多内置功能,无论是使用 Azure、Azure Machine Learning 还是您的服务作为计算目标。例如,Azure 门户上的Azure 仪表板、Azure Monitor和Azure 日志分析等工具使得集中日志和调试信息变得容易。一旦您的数据通过日志分析可用,就可以使用 Azure Automation 进行查询、分析、可视化、警报和/或自动化。
在操作任何应用程序时,首先应该想到的是测量软件和硬件指标。了解您的应用程序的内存消耗、CPU 使用率、I/O 延迟和网络带宽是至关重要的。特别是对于 ML 服务,您应该始终关注性能瓶颈和资源利用率,以实现成本优化。对于大型 GPU 加速的 DNN,了解您的系统对于高效扩展至关重要。这些指标允许您垂直扩展您的基础设施,并在需要时移动到更大的或更小的节点。
对于一般的应用程序部署,另一个监控目标应该是您的用户遥测数据(他们如何使用您的服务,他们使用频率,以及他们使用服务的哪些部分)。这将帮助您水平扩展,并在需要时添加更多节点或删除节点。
如果可能的话,从你的评分服务中测量的最后一个重要部分是用户随时间输入的数据和评分结果。为了实现最佳的预测性能,了解用户发送到你的服务的数据类型以及这些数据与训练数据相似性如何至关重要。相对确定的是,你的模型在某个时刻将需要重新训练,监控输入数据将帮助你定义何时需要重新训练(例如,通过数据漂移指标)。
让我们看看如何监控 Azure 机器学习部署并跟踪 Azure 中的所有这些指标。
为最佳资源配置分析模型
Azure 机器学习提供了一个方便的工具,帮助你通过模型分析评估 ML 模型部署所需资源。这将帮助你估计在特定吞吐量下操作评分服务所需的 CPU 数量和内存量。
让我们看看在实时评分示例中训练的模型的模型分析:
-
首先,你需要以与你的 ML 服务 JSON 请求相同的格式定义
test_data;因此,请将test_data嵌入到data根属性下的 JSON 对象中。请注意,如果你在评分文件中定义了不同的格式,那么你需要使用你自己的自定义格式:import json test_data = json.dump'({'data': [ [1,2,3,4,5,6,7,8,9,10] ]}) -
然后,你可以使用
Model.profile()方法来分析模型并评估服务的 CPU 和内存消耗。这将启动你的模型,向其发送test_data请求,并同时测量资源利用率:profile = Model.profile(ws, service_name, [model], inference_config, test_data) profile.wait_for_profiling(True) print(profile.get_results()) -
输出包含资源列表,以及针对分析模型的推荐值,如下面的代码片段所示:
{'cpu': 1.0, 'memoryInGB': 0.5}
在进行生产部署之前运行模型分析工具是个好主意,这有助于你为资源配置设置有意义的默认值。为了进一步优化并决定是否需要向上或向下、垂直或水平扩展,你需要测量、跟踪和观察各种其他指标。我们将在本章的最后部分更详细地讨论监控和扩展。
收集日志和基础设施指标
如果你刚开始接触云服务,或者具体来说是 Azure,那么日志和指标收集一开始可能会有些令人不知所措。日志和指标在你的应用程序的不同层级生成,可以是基于基础设施或应用程序的,并且可以自动或手动收集。然后,还有自动生成但需要手动启用的诊断指标。在本节中,我们将简要讨论如何在 Azure 机器学习服务中的三个主要托管计算目标(ACI、AKS 和 AmlCompute)上收集这些指标。
默认情况下,您将通过 Azure Monitor 获取对基础设施指标和日志的访问权限。它将自动收集 Azure 资源和虚拟机操作系统指标和日志,并基于日志分析提供日志的指标和查询接口。Azure Monitor 应用于跟踪资源利用率(例如,CPU、RAM、磁盘空间、磁盘 I/O 和网络带宽),然后可以将其固定到仪表板或发出警报。您甚至可以根据这些指标设置自动扩展。
指标通常以时间分布的形式收集,并在特定的时间间隔内进行报告。因此,您不需要每秒看到数千个值,而是需要为每个指标选择一个汇总值,例如每个区间的平均值。在大多数监控场景中,我建议您查看 95%分位数(或最大汇总值,对于越低越好的指标)以避免在汇总过程中平滑任何峰值。在 AKS 中,您可以通过 Azure Monitor 获得您指标的四个不同视图:集群、节点、控制器和容器。
通过启用诊断设置并提供单独的日志分析实例,您可以访问 Azure 机器学习部署的更详细的资源、虚拟机和虚拟化主机日志。这将自动将日志数据加载到您的日志分析工作区中,在那里您可以高效地查询所有日志、分析它们并创建可视化或警报。
强烈建议利用诊断设置,因为它们为您提供了对 Azure 基础设施的洞察。这在您需要调试 ML 服务中的问题(例如,失败的容器、无法启动的服务、崩溃、应用程序冻结和缓慢的响应时间)时特别有帮助。日志分析的另一个很好的用例是收集、存储和分析您的应用程序日志。在 AKS 中,您可以发送 Kubernetes 主节点日志、kubelet日志和 API 服务器日志到日志分析。
对于机器学习训练集群和部署来说,有一个非常重要的指标需要跟踪,但遗憾的是它并没有自动跟踪,那就是 GPU 资源利用率。由于这个问题,GPU 资源利用率必须在应用程序级别进行监控和收集。
解决 AKS 部署问题的最有效方法是运行一个作为应用程序副车的 GPU 日志记录服务,该服务收集资源统计信息并将它们发送到应用洞察(App Insights),这是一个收集应用程序指标的服务。App Insights 和日志分析在底层使用相同的数据存储技术:Azure 数据探索器。然而,App Insights 的默认集成主要提供应用程序指标,如访问日志,而日志分析提供系统日志。
在 AmlCompute 中,我们需要从您的应用程序代码中启动一个单独的监控线程来监控 GPU 利用率。然后,对于 Nvidia GPU,我们使用nvidia-smi监控工具的包装器,例如nvidia-ml-py3 Python 包。要将数据发送到 App Insights,我们只需使用 Azure SDK for App Insights。以下是一个简短的代码示例,展示您如何实现这一点:
from applicationinsights import TelemetryClient
import nvidia_smi
nvidia_smi.nvmlInit()
# Get handle for card id 0
dev_handle = nvidia_smi.nvmlDeviceGetHandleByIndex(0)
res = nvidia_smi.nvmlDeviceGetUtilizationRates(dev_handle)
# Submit GPU metrics to AppInsights
tc = TelemetryClient("<insert appinsights key")
tc.track_metric("gpu", res.gpu)
tc.track_metric("gpu-gpu-mem", res.memory)
在前面的代码中,我们首先在nvidia-smi之上使用了nvidia-ml-py3包装器来返回当前 GPU 的句柄。请注意,当您有多个 GPU 时,您也可以遍历它们并报告多个指标。然后,我们使用 App Insights 的TelemetryClient API 将这些指标报告回一个中心位置,然后我们可以可视化、分析和对这些值发出警报。
跟踪遥测和应用指标
在上一节中,我们简要介绍了 Azure App Insights。它是一个自动收集您的服务应用指标的优秀服务,例如 Azure 机器学习部署。它还提供了一个 SDK 来收集您想要跟踪的任何用户定义的应用指标。
要自动跟踪用户指标,我们需要使用 Azure Machine Learning 部署将模型部署到 AKS 或 ACI。这不仅会收集 Web 服务元数据,还会收集模型的预测。为此,您需要启用 App Insights 的诊断以及数据模型收集,或者通过 Python API 启用 App Insights:
from azureml.core.webservice import Webservice
aks_service= Webservice(ws, "aks-deployment")
aks_service.update(enable_app_insights=True)
在前面的代码片段中,我们可以直接从 Python 编写环境激活 App Insights 的指标。虽然这在服务类中只是一个简单的参数,但它为您提供了关于部署的惊人洞察。
需要测量的两个重要指标是训练数据和模型预测的数据漂移系数。我们将在下一节中了解更多关于这方面的内容。
检测数据漂移
机器学习中的一个重要问题是何时重新训练模型。当有新的训练数据可用时,您是否应该始终重新训练,例如每天、每周、每月或每年?我们是否需要重新训练,或者训练数据是否仍然相关?测量数据漂移将有助于回答这些问题。
通过自动跟踪用户输入和模型预测,您可以比较训练数据与用户输入在每个特征维度上的统计差异,以及训练标签与模型预测的差异。训练数据和实际数据之间的差异被称为数据漂移,应该定期跟踪和监控。数据漂移会导致模型性能随时间退化,因此需要监控。最佳情况是设置监控和警报,以便了解您的部署模型与训练数据差异过大时需要重新训练。
Azure 机器学习提供了有用的抽象,可以根据注册的数据集实现数据漂移监控器和警报,并且可以自动在 Application Insights 中公开数据漂移指标。计算数据漂移需要两个数据集:一个是基线数据集,通常是训练数据集;另一个是目标数据集,通常是来自评分服务输入构建的数据集:
-
首先,我们定义目标和基线数据集。这些数据集必须包含一个表示每个观测日期和时间的列:
from azureml.core import Workspace, Dataset from datetime import datetime ws = Workspace.from_config() ds_target = Dataset.get_by_name(ws, 'housing-data') ds_baseline = ds_target.time_before( datetime(2022, 1, 1)) -
接下来,我们可以为监控器设置电子邮件警报。这可以通过许多不同的方式完成,但为了本例的目的,我们直接在数据漂移监控器上设置电子邮件警报:
from azureml.datadrift import AlertConfiguration alert_config = AlertConfiguration( email_addresses=['<insert email address>']) -
现在,我们可以设置数据漂移监控器,提供所有之前的详细信息。我们为三个特定的特征
['a', 'b', 'c']配置监控器,以每月的节奏进行漂移测量,延迟 24 小时。当目标数据集相对于基线数据漂移超过 25%时,将创建一个警报:from azureml.datadrift import DataDriftDetector monitor = DataDriftDetector.create_from_datasets(ws, "data-drift-monitor", ds_baseline, ds_target, compute_target=compute_target, frequency='Month', feature_list=['a', 'b', 'c'], alert_config=alert_config, drift_threshold=0.25, latency=24) -
最后,我们可以启用监控器定期运行的计划:
monitor.enable_schedule()
数据漂移是操作机器学习部署时需要关注的重要运营指标。设置监控器和警报将帮助您在数据分布与训练数据偏差过大时及早得到警报,因此需要重新训练模型。
摘要
在本章中,我们学习了如何将训练好的模型通过几行简单的代码部署为 Azure 中的托管服务。为此,我们学习了如何为部署准备模型,并探讨了 Azure 机器学习的自动部署和自定义部署。
然后,我们使用 NLP 情感分析模型并将其部署为实时评分服务到 ACI 和 AKS。我们还学习了如何定义服务架构,以及如何使用端点和蓝绿部署有效地推出新版本。最后,我们学习了如何将模型集成到管道中进行异步批量评分。
在最后一节中,我们学习了如何使用 Azure 机器学习服务监控和操作模型。我们提出了监控 CPU、内存和 GPU 指标以及遥测数据。我们还学习了如何通过收集用户输入和模型输出随时间的变化来衡量服务的数据漂移。检测数据漂移是一个重要的指标,它允许您知道何时需要重新训练模型。
在下一章中,我们将应用所学知识,探讨模型互操作性、硬件优化以及集成到其他 Azure 服务中。
第十五章:第十五章:模型互操作性、硬件优化和集成
在上一章中,我们发现了如何将我们的机器学习评分部署为批量或实时评分器,什么是端点以及我们如何部署它们,最后,我们查看了一下如何监控我们部署的解决方案。在本章中,我们将更深入地探讨 ML 推理的额外部署场景,我们可以利用的其他可能的硬件基础设施,以及我们如何将我们的模型和端点与其他 Azure 服务集成。
在第一部分,我们将探讨如何通过将 ML 模型转换为标准化的模型格式和推理优化的评分框架来提供模型互操作性。开放神经网络交换(ONNX)是一种标准化的格式,用于有效地序列化和存储 ML 模型以及循环计算图和操作。我们将学习 ONNX 框架是什么,如何将 ML 模型从流行的 ML 框架转换为 ONNX,以及如何使用 ONNX Runtime 在多个平台上评分 ONNX 模型。
随后,我们将探讨替代硬件目标,例如现场可编程门阵列(FPGA)。我们将了解它们内部是如何工作的,以及它们如何与标准硬件甚至 GPU 相比实现更高的性能和更好的效率。
最后,我们将探讨如何将 ML 模型和端点集成到其他服务中。我们将更深入地了解将 ML 部署到边缘设备的过程,并将我们之前设置的一个端点与 Power BI 集成。
在本章中,我们将涵盖以下主题:
-
与 ONNX 的模型互操作性
-
使用 FPGA 进行硬件优化
-
将 ML 模型和端点与 Azure 服务集成
技术要求
在本章中,您需要访问一个 Microsoft Power BI 账户。您可以通过您的工作场所或在此处创建一个试用账户来获取一个账户:app.powerbi.com/signupredirect?pbi_source=web。
本章中的所有代码示例都可以在本书的 GitHub 存储库中找到:github.com/PacktPublishing/Masthttps://github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter15。
与 ONNX 的模型互操作性
在上一章中,我们学习了如何将 ML 模型作为 Web 服务部署以进行在线和批量评分。然而,许多实际用例要求您将训练好的 ML 模型直接嵌入到应用程序中,而不使用单独的评分服务。目标服务可能使用与训练 ML 模型的语言不同的语言编写。一个常见的例子是,使用 scikit-learn 在 Python 中训练的简单模型需要嵌入到 Java 应用程序中。
模型互操作性为您提供了灵活性,您可以使用您选择的语言和框架来训练模型,将其导出为通用格式,然后使用共享格式在不同的语言和平台上对其进行评分。在某些情况下,使用针对目标环境优化的本地运行时甚至可以实现比运行原始模型更好的评分性能。
首先,我们将了解 ONNX 创举,包括规范、运行时和生态系统,以及它是如何帮助实现跨大量支持语言、框架、操作和目标平台的模型互操作性的。
然后,我们将探讨如何将机器学习模型从流行的框架转换为 ONNX(称为 ONNX 前端),并使用 ONNX Runtime(ONNX 的多个后端之一)在本地推理运行时中执行 ONNX 模型。让我们深入了解。
模型互操作性是什么?ONNX 如何帮助?
随着信息技术组织的成长,工具、开发和部署平台以及选择也会增加。在机器学习中,这个问题更为突出,因为存在多个机器学习框架以及模型序列化格式。因此,一旦组织规模扩大,就几乎不可能让每个科学家和工程师都同意使用相同的工具、框架和模型格式,这些格式还需要支持所有目标环境。您的 XGBoost 模型能在 iOS 上运行吗?您的 PyTorch 模型能在 Java 中工作吗?您的 scikit-learn 模型能在基于浏览器的 JavaScript 应用程序中加载吗?解决模型互操作性问题的一种方法确保训练好的机器学习模型可以被移植到可以在所有目标平台上本地执行的标准化格式。这正是 ONNX 的目的。
ONNX 是由微软、Facebook、亚马逊、ARM 和英特尔等主要 IT 公司共同发起的一项联合倡议,旨在促进机器学习模型的互操作性。它允许组织为机器学习训练选择不同的语言、框架和环境,以及为推理选择不同的语言、环境和设备。例如,ONNX 允许组织使用 PyTorch 和 TensorFlow 训练深度学习模型,使用 LightGBM 和 XGBoost 训练传统机器学习模型,并将这些模型部署到基于 Java 的 Web 服务、基于 Objective-C 的 iOS 应用程序和基于浏览器的 JavaScript 应用程序。这种互操作性是通过三个关键要素实现的:
-
ONNX 规范:一种使用协议缓冲区(Protobuf)进行模型定义和模型权重高效序列化和反序列化的数据格式。为了表示广泛的机器学习模型,ONNX 规范由可扩展的计算图模型的定义以及标准数据类型和内置算子的定义组成。使用 ONNX 规范,许多由各种支持的架构、构建块、操作和数据类型组成的机器学习模型可以高效地表示在单个文件中,我们称之为ONNX 模型。
-
ONNX Runtime: 一个高效的原生推理引擎,支持与多种高级语言绑定,例如 C#、Python、JavaScript、Java/Kotlin(Android)和 Objective-C(iOS)。这意味着,通过这些语言之一的 ONNX Runtime 绑定,我们可以加载、评分,甚至训练 ONNX 模型。它还提供了内置的 GPU 加速,使用 DirectML、TensorRT、深度神经网络库(DNNL)、nGraph、CUDA 以及微软线性代数子程序(MLAS)库,并支持权重量化和图优化,以便在各种计算目标上高效运行,例如云计算、Jupyter 内核、手机和网页浏览器。
-
ONNX 生态系统:一组库集合,便于在 ONNX 之间进行转换。ONNX 库可以广泛分为 ONNX 前端(转换为 ONNX)和 ONNX 后端(从 ONNX)。虽然ONNX 前端库帮助将任意计算转换为 ONNX 模型(遵循 ONNX 规范的模型),但ONNX 后端库提供执行 ONNX 模型或将其转换为特定框架运行时的支持。ONNX 在微软以及其他大型公司中广泛使用,因此支持广泛的框架和语言。许多流行的库是官方支持的前端,例如传统的机器学习算法、scikit-learn、LightGBM、XGBoost 和 CatBoost,以及现代深度学习框架,如 TensorFlow、Keras、PyTorch、Caffe 2 和 CoreML。
ONNX 是提供模型互操作性以允许组织解耦模型训练、模型序列化和模型推理的绝佳选择。让我们在下一节中了解 ONNX 的流行前端和后端在实际中的应用。
使用 ONNX 前端将模型转换为 ONNX 格式
ONNX 前端是包、工具或库,可以将现有的机器学习模型或数值计算转换为 ONNX 模型。虽然流行的机器学习框架通常自带 ONNX 导出功能(类似于 PyTorch 的torch.onnx模块),但如今大多数框架都通过单独的转换库支持 ONNX。在撰写本文时,最受欢迎的 ONNX 前端如下:
-
skl2onnx:将 scikit-learn 模型转换为 ONNX -
tf2onnx:将 TensorFlow 模型转换为 ONNX -
onnxmltools:将 XGBoost、LightGBM、CatBoost、H2O、libsvm 和 CoreML 模型转换为 ONNX -
torch.onnx:将 PyTorch 模型转换为 ONNX
一旦安装了 ONNX 前端库,将模型转换为 ONNX 规范通常只需运行一个命令。让我们以 TensorFlow 为例看看这个操作:
- 首先,我们将使用 TensorFlow 的
SaveModel格式保存一个 Keras 模型。我们可以通过调用model.save()并提供将SaveModel模型序列化到磁盘的路径来实现这一点:
train.py
model = create_model()
model.fit(X_train, y_train)
model.save('tf_model')
- 然后,我们可以使用
tf2onnx库将SaveModel模型转换为 ONNX 模型,如下面的代码片段所示:
convert.sh
python -m tf2onnx.convert \
--saved-model tf_model \
--output model.onnx
如前例所示,我们只需要一个命令就可以将 TensorFlow 模型转换为 ONNX 模型。一旦我们有了 ONNX 模型,我们可以使用 ONNX 后端对其进行评分,如下节所述。
使用 ONNX 后端对 ONNX 模型进行原生评分
一旦模型被导出为 ONNX 模型,我们可以使用 ONNX 兼容的后端加载它。ONNX 后端的参考实现被称为ONNX Runtime,它是一个具有许多高级语言绑定的本地实现。
首先,我们可以使用onnx库加载、分析和检查一个 ONNX 模型,如下面的示例所示:
import onnx
model = onnx.load("model.onnx")
onnx.checker.check_model(model)
然而,如果我们想对模型进行评分,我们需要使用onnxruntime后端库。首先,我们需要为推理会话加载模型;这意味着我们可以加载优化后的模型,而无需分配任何用于存储梯度的缓冲区。在下一步中,我们可以通过执行run(output_names, input_feed, run_options=None)来对模型进行评分。output_names参数指向我们希望从模型中返回的命名输出层,而input_feed表示我们想要传递给模型的数据。评分属性,如日志级别,可以通过run_options参数进行配置。以下示例展示了如何对模型进行评分并从 ONNX 模型中返回最后一层的输出:
import onnxruntime as rt
session = rt.InferenceSession("model.onnx")
outputs = session.run(None, {'input': X.values})
在前面的代码中,我们加载了针对推理优化的 ONNX 模型,将数据传递给模型的input参数,并使用 ONNX Runtime Python API 返回最后一层的输出。您可以使用辅助方法session.get_modelmeta()访问层信息以及输入和输出的名称。
在本节中,我们了解了 ONNX,学习了如何使用 ONNX 前端从训练好的机器学习模型创建 ONNX 模型,以及如何使用 ONNX Runtime(ONNX 后端的参考实现)对 ONNX 模型进行评分。虽然我们只看了 ONNX Runtime 的 Python API,但还有许多其他高级绑定可用。
使用 FPGA 进行硬件优化
在前一节中,我们将模型导出为 ONNX 以利用推理优化和硬件加速的运行时来提高评分性能。在本节中,我们将进一步采取这种方法,在甚至更快的推理硬件上部署:FPGA。
但是,在我们讨论如何将模型部署到 FPGA 之前,让我们首先了解 FPGA 是什么,以及为什么我们会选择它作为深度学习推理的目标而不是 GPU。
理解 FPGA
大多数人通常遇到一种特定的集成电路(IC),称为专用集成电路(ASIC)。ASIC 是专门设计的集成电路,例如笔记本电脑中的处理器、显卡上的 GPU 核心或洗衣机中的微控制器。这些芯片共享的事实是,它们具有固定的硬件足迹,优化以支持特定任务。通常,像任何通用处理器一样,它们使用特定的指令集运行,允许运行某些命令。当你用高级语言,如 Java、C++或 Python 编程时,编译器或解释器将这种高级代码转换为机器代码,这是处理器理解并能运行的命令集。
ASIC 的强大之处在于,其底层芯片架构可以根据特定的工作负载进行优化,从而在所需的面积方面实现最优化设计。ASIC 的弱点在于,它仅适用于执行其设计时指定的特定任务,并且其设计是固定的,因为底层硬件无法更改。
尽管我们可以在标准处理器上运行任何任务,但对于非常具体的事情,例如神经网络中数千个节点的计算和回溯,它们可能不是最优的。因此,现在许多这些计算都是在 GPU 上运行的,因为其芯片架构更倾向于并行运行相同的计算,这更接近神经网络算法的固有结构,而不是标准 CPU。
FPGA 的定义与它们的 ASIC 对应物不同。FPGA 在最优设计方面进行交易,尤其是在芯片上使用的面积方面,以换取可重编程性的自由。这一主要特性允许用户购买 FPGA,然后自己构建自己的处理器、硬件交换机、网络路由器或其他任何东西,并且可以在任何时候更改底层硬件设计。
由于硬件最终是由某种形式的二进制逻辑门、寄存器和电线组成的物理实体,因此 FPGA 的这种能力听起来可能像是魔法。然而,我们每天都在使用可以存储和擦除数据的闪存驱动器。例如,现代NAND 闪存驱动器通过称为场电子发射的过程进行擦除,这允许电荷通过一层薄绝缘层来重置位或,更准确地说,位块。
记住这一点,让我们看看 FPGA 的基本构建块,称为逻辑元素。图 15.1显示了这些构建块的一般概念。不同的制造商调整这些构建块的不同方面,但基本概念保持不变:

图 15.1 – FPGA 中逻辑元件的结构
逻辑元件通常由以下组件组成:
-
输入/输出(I/O):表示与其他逻辑元件或外部 I/O(例如,考虑以太网和 USB)的互连。
-
查找表(LUT):存储在此逻辑元件中执行的主要逻辑功能。数字电路中的任何逻辑都可以分解为一个布尔函数,该函数将一定数量的二进制输入映射到一定数量的二进制输出。
-
D 触发器(寄存器):存储当前时钟周期的输入值,以便在下一个时钟周期使用,其长度是运行电路频率的倒数。存储东西以供下一轮使用是所有数字硬件的基本原则,也是能够进行硬件流水线操作所必需的。电路中任何相邻寄存器之间的最大处理时间定义了电路可以运行的最大频率。
-
多路复用器(MUX):选择其输入中哪一个被显示为输出。在这种情况下,它要么显示布尔函数的当前结果,要么显示前一个时钟周期的结果。
通过查找表,任何布尔函数(以及通过寄存器,任何多层硬件逻辑)都可以实现。此外,查找表可以被擦除和重置,这使得 FPGA 具有可编程性。
FPGA 的完整示意图结构在图 15.2中显示。只需理解一个正常大小的 FPGA 将拥有超过 500,000 个逻辑元件:

图 15.2 – FPGA 的示意图结构
除了逻辑元件外,图 15.2还显示了开关矩阵和I/O 块。开关矩阵是最后一部分拼图,允许在逻辑元件之间以及它们与 I/O 块之间设置和重置所需的连接。借助它们,可以完全重新编程 FPGA 上的电路结构。
最后,为了便于 FPGA 的编程,使用了一种所谓的硬件描述语言(HDL)。用于硬件设计的两种主要语言是SystemVerilog和VHDL。当你看到用这些语言编写的代码时,它可能看起来像高级编程语言,但实际上,你并没有在编程任何东西;你是在描述所需的硬件架构。从某种意义上说,你以代码的形式给机器提供了一张电路的图片,它试图将此映射到 FPGA 上的给定元素。这一步称为综合。在此步骤之后,将一个二进制文件发送到 FPGA,该文件用正确的布尔函数填充所需的逻辑元件,并相应地设置所有互连。
除了这种逻辑结构之外,你还会在现代 FPGA 中发现许多其他集成系统,结合了 ASIC 和 FPGA 的优势。你甚至可能会在 IC 上找到像ARM Cortex这样的处理器。其理念是让任何在 FPGA 上从头开始构建将非常耗时的事物都在处理器上运行,同时使用 FPGA 来托管你的定制硬件设计。例如,在 FPGA 上构建以太网协议的低层将花费很多时间,因为 TCP 需要一个高度复杂的硬件电路。因此,将这部分外包给处理器可以极大地加快开发时间。
现在我们已经对 FPGA 是什么以及它是如何工作的有一个大致的了解,让我们讨论为什么它们可能比 GPU 更适合深度学习。
比较 GPU 和 FPGA 在深度神经网络中的应用
正如我们在上一节中讨论的那样,GPU 的底层硬件结构支持深度神经网络的训练和推理。原因在于它们的设计考虑了 3D 图像渲染,因此,在板上有很多逻辑来促进矩阵乘法,这是一个在 CPU 上耗时极多的任务,对于 DNNs 至关重要。通过 GPU,处理时间通常可以从几天降低到仅仅几小时。对于 FPGA 也是如此,因为我们基本上可以构建我们需要的任何专用电路来优化我们想要执行的任务的速度和功耗。
因此,对于 DNNs 来说,这两种选择都比通用 CPU 优越得多。但是,我们应该选择哪一个,为什么?现在让我们通过一个考虑方面的列表来探讨,以及这两个选项在这两种情况下各自的表现如何:
-
实施复杂性:GPU 通常提供一种软件级语言(例如,CUDA),以使程序员与底层硬件分离。对于 FPGA,程序员必须理解硬件领域以及如何为其设计。因此,为 FPGA 构建正确的电路比在高级编程语言中使用另一个库要复杂得多。但是,正在通过专门的工具和转换器尽可能多地抽象这一层。
-
功耗:GPU 产生大量热量,需要大量冷却和电力。这是因为为了促进软件可编程性而增加的硬件设计复杂性,从而支持 RAM、CPU 和 GPU 的基础硬件堆栈。另一方面,FPGA 不需要这个堆栈来运行,因此,在大多数情况下,具有低到中等的功耗,这使得它们比 GPU 节能 4 到 10 倍。
-
硬件堆栈: GPU 依赖于整个标准硬件堆栈的内存管理(CPU 缓存、RAM 和 GPU 内存),并需要一个外部系统来控制它们。这导致 GPU 的硬件设计既低效又必需,以促进连接层到标准硬件堆栈,这使其性能降低。另一方面,FPGA 在其 IC 上拥有所有必需的元素(如高速内存),因此可以完全自主运行,无需从系统内存或其他任何地方拉取数据。
-
延迟和互连性: 虽然 GPU 连接到标准的硬件堆栈,并且在其后面只有几个实际的硬件端口(HDMI 和 DisplayPort),这些端口通常仅作为输出,但 FPGA 可以连接到任何设备。这意味着它可以同时支持大量不同的输入和输出标准,使其极其灵活且能够适应任何特定情况。此外,它能够以非常低的延迟处理数据,因为不需要通过系统内存、CPU 或 SW 层传输数据,这使得它在实时视频处理等应用中远优于 GPU。
-
灵活性: 尽管 GPU 具有并行硬件架构,但你可能无法有效地使用它。特定的 DNN 算法必须映射到底层硬件,这可能既不完美也不可行。它属于与在 CPU 核心之间分配进程相同的问题类别。此外,GPU 设计用于处理 32 位或 64 位标准数据类型。如果你使用的是非常专业的数据类型或自定义数据类型,你可能根本无法在 GPU 上运行它。另一方面,FPGA 允许你定义你想要工作的任何数据大小或数据类型,并且在此基础上,甚至在运行时允许所谓的部分重构,它使用此功能在运行时重新编程逻辑的一部分。
-
行业适应性: 在典型的工业场景中,无论是国防、制造业、智能城市还是其他任何领域,部署的硬件必须紧凑,必须具有较长的使用寿命,应该具有低功耗,应该能够适应其所在的环境(灰尘、高温、湿度),在某些情况下,还需要具备功能性安全,这意味着它必须遵循某些合规标准和协议。GPU 对于这些情况中的任何一种都不是一个好的选择,因为它非常耗电,使用寿命为 2 到 5 年,需要大量的冷却,无法在恶劣环境中生存,并且没有功能性安全。FPGA 的设计初衷就是针对工业环境,因此通常是为了长期使用(10 到 30 年)和安全而构建的,同时功耗和所需空间的影响很小。
-
成本: 如果你曾经为你的 PC 购买过 GPU,你可能对这种扩展卡的代价有所了解。另一方面,FPGA 可能成本较高,但对于相当的需求配置来说,通常更便宜。
考虑到所有这些因素,FPGAs 在大多数方面在技术上更优越,而且通常更便宜,但它们的主要问题是需要开发者理解硬件设计。这个问题导致了帮助弥合硬件和机器学习开发之间差距的工具包的创建,以下是一些例子:
-
Vitis AI for Xilinx FPGAs:这是一个用于机器学习推理的预设计深度学习处理器单元(DLUs)的开发套件。更多信息请参阅
www.xilinx.com/products/design-tools/vitis/vitis-ai.html。此外,您还可以在此处找到有关如何使用 NP VM 系列在 Azure 中的一些信息:github.com/Xilinx/Vitis-AI/tree/master/docs/azure。 -
OpenVINO for Intel FPGAs:这是一个用于深度学习和机器学习推理的开发套件。更多信息请参阅
www.intel.com/content/www/us/en/artificial-intelligence/programmable/solutions.html。 -
Microsoft Project Brainwave:这是一个用于计算机视觉和 NLP 的深度学习和机器学习推理的开发平台。更多信息请参阅
www.microsoft.com/en-us/research/project/project-brainwave。
这些只是支持通过 FPGA 部署和加速机器学习模型的一些选项。
重要提示
FPGAs 是一种非常卓越的技术,但它们需要充分理解硬件设计才能在任何项目中高效且成功地使用,或者需要一个非常复杂的工具集来抽象硬件层。
既然我们知道了为什么我们可能更愿意选择 FPGA 用于 DNN,那么让我们简要地看看如何使用 Azure 机器学习来利用 FPGA 在这方面。
在 Azure 上使用 Intel FPGAs 运行 DNN 推理
如前文所述,为 FPGA 构建硬件设计并非易事。您当然可以从头开始,利用 Azure VM 中配备 FPGA 的选项(docs.microsoft.com/en-us/azure/virtual-machines/np-series),或者使用您自己的 FPGA 开发套件。另一个选择是使用 Azure 机器学习 Python SDK 中可用的硬件加速 Python 包。此包通过支持模型子集和选项的通用硬件设计提供抽象层,特别是用于 DNN 推理的选项。通过这种方式,您可以访问Azure PBS VM 系列,该系列配备有 Intel FPGA,并且仅通过 Azure 机器学习提供。这种机器类型可在东 US、东南亚、西欧和西 US 2 进行部署。
通用方法与 ONNX 非常相似;您将一个训练好的模型转换为可以在 FPGA 上执行的具体格式。在这种情况下,您的模型必须是 ResNet、DenseNet、VGG 或 SSD-VGG,并且必须使用 TensorFlow 编写,以便适应底层硬件设计。此外,我们将使用量化 16 位浮点模型权重转换为 ONNX 模型,这些模型将在 FPGA 上运行。对于这些模型,FPGA 在云中提供了最佳推理性能。
要通过 FPGA 实现硬件加速,与 ONNX 示例相比,我们需要执行一些额外的步骤。以下列表显示了需要执行哪些步骤:
-
选择一个受支持的模型特征提取器。
-
使用自定义分类器训练受支持的模型。
-
将模型特征提取器的权重量化为 16 位精度。
-
将模型转换为 ONNX 格式。
-
(可选) 注册模型。
-
创建一个计算目标(最好是 Azure Kubernetes 服务)带有 PBS 节点。
-
部署模型。
重要提示
由于代码杂乱且难以理解,我们将跳过本节中的代码示例。然而,您可以在 Azure 的 GitHub 仓库中找到有关 FPGA 模型训练、转换和部署的详细示例,链接为
github.com/Azure/MachineLearningNotebooks/tree/master/how-to-use-azureml/deployment/accelerated-models。
让我们更详细地讨论这些步骤。
从我们讨论的第十章中,在 Azure 上训练深度神经网络,只有特征提取器层(azureml.accel.models包 (docs.microsoft.com/en-us/python/api/azureml-accel-models/azureml.accel.models)。您可以使用 TensorFlow 或 Keras 在顶部附加任何分类或回归头(或两者),但它们将不会进行硬件加速,类似于仅在 GPU 上运行某些操作。设计者在这里选择仅将最耗时的部分部署到 FPGA 上。
在下一步中,您可以使用自己的数据和权重,或者通过微调,例如使用提供的 ImageNet 权重,来训练一个由预定义的特征提取器和自定义分类头组成的模型。这应该在 32 位精度下进行,因为训练期间收敛会更快。
一旦训练完成,您需要使用azureml.accel.models包中提供的量化模型,将特征提取器的权重量化为半精度浮点数。这一步骤需要执行,因为设计者在这里选择了 16 位固定数据大小,以便使硬件设计尽可能通用和可重用。
在下一步中,你将使用来自同一 Azure 包的 AccelOnnxConverter 方法将整个模型转换为 ONNX 模型。此外,AccelContainerImage 类帮助你为基于 FPGA 的计算目标定义 InferenceConfig。
最后,你可以使用 Azure Machine Learning 模型注册表注册你的模型,并可以使用 Standard_PB6s 节点创建一个 AKS 集群。一旦集群启动并运行,你就可以使用你的 Webservice.deploy_from_image 方法部署网络服务。
重要提示
你可以在 Azure Machine Learning 文档中找到部署步骤的详细示例:docs.microsoft.com/en-us/azure/machine-learning/how-to-deploy-fpga-web-service。
通过 Azure Machine Learning 将模型部署到基于 FPGA 的计算目标的工作流程与简单地部署 ONNX 模型略有不同,因为你从一开始就必须考虑有限支持的模型选择。另一个区别是,当你选择一个预定义的受支持模型进行 FPGA 部署时,你只能加速模型的特征提取部分。这意味着你必须附加一个额外的分类或回归头——这是一个不太明显的一步。一旦你理解了这一点,你就会觉得在训练后只对特征提取进行半精度浮点量化更有意义。
虽然这个过程看起来有点困难且定制化,但在处理图像数据的预测时,性能和延迟的增益是巨大的。但是,只有当你准备好将你的训练过程和管道适应到这个特定环境时,你才应该利用这种优化,正如本节中所示。
现在我们已经很好地理解了 FPGA 是什么以及我们如何通过 Azure Machine Learning 利用它们,让我们在下一节中看看我们可以与我们的模型集成的其他 Azure 服务。
将 ML 模型和端点与 Azure 服务集成
依靠 Azure Machine Learning 服务进行实验、执行端到端训练或简单地注册你的训练模型和环境,都能为你带来巨大的价值。在第十四章,“模型部署、端点和操作”中,我们介绍了两个主要场景,一个是通过自动化部署的实时评分网络服务,另一个是通过部署的管道进行批量评分。虽然这两个用例在需求和部署类型上相当不同,但它们展示了当你有一个存储在 Azure Machine Learning 中的训练模型和打包环境时,你可以做到什么。
在许多场景中,将您的批量评分流程从实际数据处理流程中抽象出来,以分离关注点和责任,是非常有意义的。然而,有时您的评分应该在数据处理或查询时间直接进行,并在同一系统中进行。一旦您的 ML 模型在 Azure Machine Learning 中注册并版本化,您就可以使用 Azure ML SDK 在任何地方提取模型的特定版本,无论是 Python、C#、命令行还是任何可以调用 REST 服务的其他语言。
这使得从桌面应用程序中提取训练和转换后的 ONNX 模型成为可能,无论是在构建时间还是运行时。例如,您可以在 Azure Databricks 或 Azure Synapse 上运行 Spark 作业时加载模型。通过这种方式,您可以避免将 TB 级的数据传输到单独的评分服务。
其他服务,如 Azure Data Explorer,允许您通过 Python 扩展直接从服务中调用模型(docs.microsoft.com/en-us/azure/data-explorer/kusto/query/pythonplugin)。Azure Data Explorer 是一个用于高效存储和查询大量遥测数据的托管服务。它被 Azure 内部使用,为 Azure Log Analytics、Azure Application Insights 和 Time Series Insights 提供动力。它拥有强大的 Python 运行时,提供了许多流行的包,因此提供了执行基于自定义模型的异常检测或时间序列分析的理想服务。此外,它还允许您通过名为Kqlmagic的 Python 扩展在 ML 建模期间访问其时间序列数据(docs.microsoft.com/en-us/azure/data-explorer/kqlmagic)。
重要提示
当使用 Azure Machine Learning 进行模型部署时,您可以利用所有 Azure 生态系统的好处,并可以期待随着时间的推移,模型或端点与越来越多的 Azure 服务进行集成。
在本章结束时,我们将深入探讨即将到来的部分中的两个其他集成选项。我们将查看如何通过Azure IoT Edge将 ML 模型部署到现场网关或设备,以及如何利用 ML 端点在Power BI中进行数据增强。
与 Azure IoT Edge 集成
到目前为止,我们已经讨论了不同的方法来让我们的模型在云中的系统上运行,无论是 CPU、GPU 还是 FPGA 机器,无论是作为批量评分过程还是作为实时端点。现在,让我们讨论另一个有趣的部署场景,将实时评分器部署到现场的一个到数十万个设备上。对这些设备和收集的遥测数据和事件的控制属于所谓的物联网(IoT)的范畴,它使我们能够几乎实时地对任何环境中的变化和关键问题做出反应。
在这些场景中,机器学习的集成使我们能够同时将模型分发到多个系统和设备,使这些所谓的边缘设备能够在本地运行时执行模型,以便根据机器处理的结果做出相应反应。这可能是一个执行机器学习图像处理以应对入侵者并发送警报的本地摄像头系统,或者任何你可能想象到的其他场景。
要了解如何利用 Azure 平台实现这一目标的基础知识,让我们首先看看 IoT 场景是如何通过Azure IoT Hub和其他服务的帮助实现的,然后讨论如何将其与 Azure 机器学习和我们的训练模型集成。
理解 Azure 上的 IoT 解决方案
Azure 上任何 IoT 架构的基础是 Azure IoT Hub。它作为云网关与现场中的设备和其他网关进行通信,并能够在一定程度上控制它们。一方面,它运行 Azure Event Hubs 以能够通过分布式结构处理大量传入遥测,这与 Apache Kafka 并无太大不同。另一方面,它作为控制工具,提供以下功能:
-
设备目录:记录所有注册到 Azure IoT Hub 的设备。任何连接的设备都会获得自己的设备名称和连接配置,定义了设备与中心之间的直接连接如何安全,这可以通过旋转密钥或设备证书来实现。
-
设备配置:一种允许设备自动注册到 IoT Hub 以获取带有密钥的连接字符串或证书的服务。如果需要注册的设备数量较多,则非常有用。
-
设备孪生:一个配置文件,定义了设备的重要属性,可以设置或请求。在遥测流之间,设备被要求偶尔发送此文件,更新云网关中设备的状态。因此,设备孪生始终持有设备的最新状态。当在设备上使用Azure IoT 设备 SDK时,此功能会自动实现。
-
命令与控制:这是通过Azure IoT 服务 SDK实现的。来自控制台或外部应用程序的命令可以用来向单个设备发送新的期望属性,为设备组定义配置,或发送设备需要理解和实施的预定义命令。这可能是一个请求重新启动设备或更新其固件的请求。
-
监控和诊断:对来自和发送到 IoT Hub 的任何传入和传出消息的诊断视图。它可以用来了解传入遥测的吞吐量,了解交换的任何控制平面信息,并在设备不可达且出现故障时发出警告。
除了这个云网关之外,Azure 还提供了一种边缘设备运行时,称为 Azure IoT Edge,它可以安装在设备或网关上。它由 Moby Docker 运行时(mobyproject.org/)提供支持,允许用户将 Docker 容器部署到现场设备。在此运行时中运行的任何解决方案的设置都由一个部署清单定义,该清单通过 IoT Hub 中的设备孪生配置文件为边缘设备设置。此清单定义以下组件:
-
IoT Edge 代理:验证和实例化模块,在运行时检查它们的状态,并利用设备孪生配置文件报告任何配置或运行时问题。它是运行时的主要模块,是必需的。
-
IoT Edge 网关:使 IoT Edge 运行时能够模拟 IoT Hub,以便连接到该本地边缘设备的额外设备。这允许任何形式的复杂层次结构,同时设备可以使用与 IoT Hub 相同的协议与 IoT Edge 设备通信。此模块是必需的。
-
容器模块:定义要复制到边缘运行时的容器镜像。这是通过定义一个链接到存储在 Azure 容器注册表中的源文件来完成的。除了任何可以通过这种方式部署的用户定义容器外,还有许多 Azure 服务的容器化版本可以发送到运行时。这个列表包括 Blob 存储、Azure Function 应用、某些认知服务,甚至还有一个称为SQL Edge的小型、优化的 SQL 服务器版本。
-
通过路由进行本地通信:通过设置先前定义的各种模块的输入和输出之间的直接连接,定义将模块连接在一起的第一种选项。
-
通过 MQTT 代理进行本地通信:定义将模块连接在一起的第二种选项。而不是设置直接连接,使用一个代理,模块可以订阅。此代理还提供连接到理解如何与 MQTT 代理通信的外部设备。
定义部署清单时,需要考虑的主要组件和选项。
重要提示
Azure IoT Edge 带来的最大优势是能够在云中定义、管理和版本控制容器,并将它们部署到成千上万的设备上。借助设备配置,我们可以分组设备,并且只为特定组进行新的测试更新,从而在物联网环境中实现 DevOps 的最佳实践。
现在,让我们简要地看一下一个示例。图 15.3显示了通过 Azure IoT Edge 对传入遥测数据进行容器化 ML 模型评分的简单设置及其与 Azure IoT Hub 的连接:

图 15.3 – Azure IoT Hub 连接到边缘运行时
图 15.3中的连接显示了容器之间的内部路由,包括本地执行的操作,而来自机器学习评分的任何见解和任何初始遥测数据都额外发送到云端进行进一步分析。这是任何在边缘运行的机器学习模型的典型场景。
在了解这些知识的基础上,我们现在来看看如何将 Azure 机器学习集成到这样的物联网架构中。
集成 Azure 机器学习
在第三章,“准备 Azure 机器学习工作区”,我们了解到每个 Azure 机器学习工作区都自带一个 Azure 容器注册表。我们现在可以使用这个注册表来实现我们的目标。图 15.4展示了边缘机器学习端到端解决方案的示例:

图 15.4 – Azure IoT Edge 端到端机器学习场景
它描述了以下步骤:
-
在存储账户中收集遥测数据,无论是通过从 IoT Hub 路由单个消息,还是通过从边缘的 Blob 存储批量上传到云端的存储账户
-
在捕获的数据上训练 ML 模型,正如我们之前所学的
-
在 Azure 机器学习工作区的现有 Azure 容器注册表中注册包含训练模型和依赖项的容器
-
创建一个定义从 Azure 容器注册表源 ML 模块的 IoT Edge 部署清单
-
通过 Azure IoT Hub 将创建的配置部署到边缘设备
通过这个设置,我们现在能够部署和控制边缘的 ML 模型,从而在外部设备上运行低延迟的 ML 解决方案。
重要提示
如果你想尝试一下,请随意遵循设置 Azure IoT Edge 上的示例机器学习模型的教程,教程链接如下:docs.microsoft.com/en-us/azure/iot-edge/tutorial-machine-learning-edge-01-intro。
最后,如果你对边缘机器学习解决方案的更多选项感兴趣,可以查看 Azure 物联网空间中最新的添加之一,名为Azure Percept (azure.microsoft.com/en-us/services/azure-percept/)。它提供了一个现成的硬件开发套件,用于视频和音频推理,可与 Azure IoT Hub 和 Azure 机器学习协同工作。
现在我们已经对物联网世界和边缘机器学习场景有了初步了解,让我们来看看如何利用 Power BI 实时机器学习端点。
集成 Power BI
从企业角度来看,最有趣的集成之一是 Azure 机器学习与 Power BI 的集成。它允许我们利用我们的 ML 端点,在内置的Power Query 编辑器中应用我们的模型到数据列。想想看,将 ML 模型推广给数据分析师在他们的 BI 工具中使用是多么强大的概念。
让我们通过利用在第十四章“模型部署、端点和操作”中创建的sentiment-analysis-pbi端点来尝试一下,按照以下步骤操作:
-
如果您还没有这样做,请将 Power BI 桌面应用程序([
powerbi.microsoft.com/en-gb/desktop/](https://powerbi.microsoft.com/en-gb/desktop/))下载到您的计算机上,运行它,并登录。 -
从章节存储库下载
sentiment_examples.csv文件,然后选择获取数据 | 文本/CSV,将此本地文件的内容加载到 Power BI 的内存数据集中。 -
Power Query 编辑器将打开,并显示带有名称和大小的文件图标。右键单击该图标,然后选择文本。
-
您应该会看到一个只有一个列的表格。将列重命名为
Phrases,如图 15.5 所示:

图 15.5 – 情感分析样本短语
-
编辑器为您提供了许多将转换应用于这些数据的方法。查看菜单,您应该在最右侧看到一个名为Azure 机器学习的按钮。点击它。
-
如果您登录正确,您应该看到您有权访问的所有 Azure 机器学习工作区中的所有可用端点。选择我们之前创建的端点
AzureML.sentiment-analysis-pbi。在Phrases列中。这将是我们的 ML 端点的输入。图 15.6显示了它应该看起来是什么样子:

图 15.6 – 在 Power BI 中选择正确的 ML 端点
-
点击确定。Power BI 现在将开始向端点发送请求。请注意,您可能会在 Power BI 窗口中收到有关数据隐私的警告,因为我们正在将可能涉及隐私的数据发送到另一个服务。请通过选择第一个复选框接受此警告,以便执行此操作。
-
结果,您现在应该看到一个名为
AzureML.sentiment-analysis-pbi的新列,其中包含许多标记为Record的字段。由于我们的端点发送了多个输出,我们收到了一个记录。您现在可以单独点击每个记录,或者您可以点击列标题旁边显示两个箭头的按钮。这允许您将此Record列展开成多个列。选择所有列名并按确定。图 15.7显示了您应该看到的结果:

图 15.7 – Power BI 情感分析结果
如我们所见,该模型为每个句子给出一个标签(NEGATIVE或POSITIVE)和一个置信度值分数,表示 ML 模型对给定标签的确定性。结果相当准确,也许第四个短语除外。
- 现在,您可以点击左上角的关闭并应用,这将导致 Power BI 创建一个增强型 ML 数据集,您现在可以构建报告中的可视化,并最终将报告发布到云中的 Power BI 服务。
如您自己所见,与 Power BI 集成是一种快速简单的方法,使每个人都能利用他们的业务数据使用您部署的 ML 端点,同时不必深入了解 ML 服务的内部工作原理。
随意添加一些你自己的短语来尝试一下。
摘要
在本章中,我们学习了如何使用 ONNX 将 ML 模型转换为便携和可执行格式,什么是 FPGA,以及我们如何通过 Azure Machine Learning 将 DNN 特征提取器部署到 FPGA VM。此外,我们还学习了如何将我们的 ML 模型集成到各种 Azure 服务中,例如 Azure IoT Edge 和 Power BI。
这就结束了我们通过前两章对部署 ML 模型进行批量或实时推理的各种选项的讨论。
在下一章中,我们将把迄今为止所学的一切整合起来,理解和构建一个端到端的 MLOps 流水线,使我们能够为任何需要添加 ML 的过程创建一个企业级和自动化的环境。
第十六章:第十六章:使用 MLOps 将模型投入生产
在上一章中,我们探讨了使用 ONNX 进行模型互操作性、使用 FPGA 进行硬件优化以及将训练模型集成到其他服务和平台中的方法。到目前为止,你已经学习了如何在一个端到端机器学习管道中实现每个步骤,包括数据清洗、预处理、标记、实验、模型训练、优化和部署。在本章中,我们将把之前所有章节中的片段连接起来,在构建和发布管道中集成和自动化它们。我们将重用所有这些概念,在 Azure 中构建一个版本控制的、可重复的、自动化的 ML 训练和部署过程,作为一个 持续集成和持续部署(CI/CD)管道。类似于软件开发中的 DevOps 方法论,我们将把这个主题称为 MLOps。
首先,我们将看看如何为 ML 项目生成可重复的构建、环境和部署。我们将涵盖代码的版本控制,以及数据和构建工件版本化/快照。
接下来,我们将学习如何自动测试我们的代码,并重点关注 ML 项目来验证代码质量。为此,我们将了解如何将单元测试、集成测试和端到端测试适应以确保训练数据和 ML 模型的良好质量。
最后,你将构建自己的 MLOps 管道。首先,你将学习如何设置 Azure DevOps 作为 MLOps 的编排和协调层,然后你将实现构建(CI)和发布(CD)管道。
在本章中,我们将涵盖以下主题:
-
确保可重复构建和部署
-
验证代码、数据和模型
-
构建端到端 MLOps 管道
技术要求
在本章中,我们将使用以下 Python 库和版本在 Azure DevOps 中创建 MLOps 管道:
-
azureml-core 1.34.0 -
azureml-sdk1.34.0 -
pandas 1.3.3 -
tensorflow 2.6.0 -
pytest 7.1.1 -
pytest-cov 3.0.0 -
mock 4.0.3 -
tox 3.24.5
本章讨论的大多数脚本和管道需要在 Azure DevOps 中进行调度执行。
本章中所有的代码示例都可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Mastering-Azure-Machine-Learning-Second-Edition/tree/main/chapter16.
确保可重复构建和部署
DevOps 有许多不同的含义,但通常是指在源代码更改时实现快速和高品质的部署。实现高质量操作代码的一种方法是通过保证可重复和可预测的构建。虽然对于只有少量配置更改的应用程序开发来说,编译的二进制文件看起来和行为相似似乎是显而易见的,但对于 ML 管道的开发来说,情况并非如此。
机器学习工程师和数据科学家面临着许多问题,这使得构建可重复部署变得非常困难:
-
开发过程通常在笔记本中执行,因此它不总是线性的。
-
重构笔记本代码经常会破坏旧的笔记本。
-
存在库版本和驱动程序不匹配的问题。
-
源数据可能会被更改或修改。
-
非确定性优化技术可能导致完全不同的输出。
本书的前几章我们讨论了交互式笔记本(例如 Jupyter、Databricks、Zeppelin 和 Azure 笔记本),你可能在实现机器学习模型和数据管道时已经见过它们。虽然交互式笔记本有执行单元格以迭代验证模型块块的优势,但它们也常常鼓励用户以非线性顺序运行单元格。当尝试将管道投入生产或自动化时,使用笔记本环境的主要好处变成了痛点。
机器学习中的第二个常见问题是确保安装了正确的驱动程序、库和运行时。虽然使用 Python 2 运行基于 scikit-learn 的小型线性模型很容易,但如果部署的 CUDA、cuDNN、libgpu、Open MPI、Horovod、TensorFlow、PyTorch 和类似库与开发版本匹配,对深度学习模型来说就大不相同了。通过 Docker 或类似技术进行容器化有助于构建可重复的环境,但在实验、训练、优化和部署过程中使用它们并不简单。
数据科学家面临的另一个挑战是数据通常会随时间变化。在开发过程中,可能添加了新的数据批次,或者数据被清理、写回存储,并作为其他实验的输入重新使用。由于数据在格式、规模和质量上的可变性,它可能是生产可重复模型时遇到的最大问题之一。与代码版本控制类似,对数据进行版本控制对于可重复构建和审计目的都是必不可少的。
另一个使可重复的机器学习构建变得困难的挑战是,它们通常包含一个优化步骤,如第 第十一章 中讨论的,超参数调整和自动化机器学习。虽然优化是机器学习(例如,用于模型选择、训练、超参数调整或堆叠)的必要步骤,但它可能会给训练过程添加非确定性行为。让我们一步一步地找出如何解决这些问题。
代码版本控制
版本控制源代码是一种最佳实践,不仅适用于软件开发,也适用于数据工程、数据科学和机器学习。作为一个组织,您可以选择建立自己的内部源代码仓库或使用外部服务。GitHub、GitLab、Bitbucket和Azure DevOps是管理源代码仓库的流行服务。这些服务的优势在于它们中的一些提供了额外的功能,例如对 CI 工作流的支持。在本章的后面,我们将使用 Azure DevOps 的 CI 运行器集成。
使用版本控制对您的代码来说比您使用的版本控制系统更重要。是的,Git工作得相当好,但Mercurial和Subversion(SVN)也是如此。对于我们的示例 MLOps 管道,我们将使用 Git,因为它是最广泛使用和支持的。您必须熟悉您选择的版本控制系统的基本工作流程。您应该能够创建提交和分支,提交pull requests(PRs),对请求进行评论和审查,以及合并更改。
版本控制源代码的力量在于记录更改。在每个这样的更改上,我们希望触发一个自动的管道来测试您的更改,验证代码质量,并在成功合并后训练模型并自动将其部署到预发布或生产环境。您的提交和 PR 历史记录不仅将成为记录更改的来源,还将触发、运行和记录这些更改是否经过测试并准备好投入生产。
为了有效地使用版本控制,您必须尽快将业务逻辑从您的交互式笔记本中移除。笔记本以自定义数据格式存储每个单元格的代码和输出,例如序列化为 JSON 文件。这使得在序列化的笔记本中审查更改变得非常困难。一种好的折衷方法是采用混合方法,首先在笔记本中测试您的代码实验,然后逐渐将逻辑移动到导入到每个文件中的模块。使用自动重新加载插件,您可以确保在更改逻辑时,这些模块会自动重新加载,而无需重启内核。
将代码从笔记本移动到模块不仅会使您的代码对所有其他实验可重用(无需从笔记本复制实用函数),而且会使您的提交更加易于阅读。当多个人在一个庞大的 JSON 文件(这是您的笔记本环境存储每个单元格的代码和输出的方式)中更改几行代码时,对文件的更改将几乎无法审查和合并。然而,如果这些更改是在模块(仅包含可执行代码的单独文件)中进行的,那么这些更改将更容易阅读、审查、推理和合并。
在我们继续查看训练数据的版本化之前,这是一个复习你的 Git 技能、创建(私有)存储库并实验版本控制功能的好机会。
注册数据快照
你的机器学习模型是训练代码和训练数据的输出。如果我们对训练源代码进行版本控制以创建可重复构建,我们也需要对训练数据进行版本控制。虽然将小型、文本、非二进制和非压缩文件与源代码一起检查到版本控制系统中听起来是合理的,但对于大型二进制或压缩数据源来说,这听起来并不合理。在本节中,我们将讨论如何处理后者的解决方案。
让我们再次强调可重复构建的概念:无论何时执行训练——它可能是今天,也可能是一年后——输出应该是相同的。这意味着对训练数据的任何修改都应该创建数据集的新版本,并且训练应该使用数据集的特定版本。我们区分操作事务数据和历史数据。前者通常是状态性和可变的,而后者通常是不可变的。有时,我们也会看到两者的混合,例如,可变的历史事件数据。
当处理可变数据(例如,存储客户信息的操作数据库)时,我们需要在拉取数据用于训练之前创建快照。对于机器学习,使用完整快照比增量快照更容易,因为每个快照都包含完整的数据集。虽然增量快照通常创建来节省成本,但使用列压缩数据格式和可扩展的 blob 存储系统(如 Azure Blob 存储)也可以高效地存储完整快照,即使你有多个 TB 的数据。
当处理历史数据或不可变数据时,我们通常不需要创建完整快照,因为数据是分区的——也就是说,组织在目录中,目录对应于分区键的值。历史数据通常按处理日期或时间分区,例如数据摄取执行的时间。日期或时间分区使得将训练管道指向特定范围的分区而不是直接指向一组文件变得更容易。
有多种方法可以创建训练数据的快照。然而,当使用 Azure 机器学习工作区时,建议将数据包装在 Azure 机器学习数据集中,如第四章“导入数据和管理数据集”中所述。这使得创建数据快照或版本化数据变得容易。在 Azure 机器学习中处理和修改数据时,你应该养成增加数据集版本的惯例。此外,在训练脚本中获取数据时,你应该传递数据集的特定版本。
每次你向你的训练脚本传递参数时,使用确定性占位符参数化管道是有帮助的。如日期和时间戳之类的参数应在管道调度步骤中创建,而不是在代码本身中创建。这确保了你总是可以用历史参数重新运行失败的管道,并且会创建相同的输出。
因此,确保你的输入数据已注册并版本控制,你的输出数据也已注册并参数化。这需要一点时间来正确设置,但整个项目生命周期都是值得的。
跟踪你的模型元数据和工件
将你的代码移到模块中,将其检查到版本控制中,并对你的数据进行版本控制将有助于创建可重复的模型。如果你正在为企业构建 ML 模型,或者你正在为你的初创公司构建模型,了解哪个模型版本被部署以及它使用的数据集进行训练是至关重要的。这对于审计、调试或解决客户对你服务预测的询问是相关的。
我们在前几章中看到,几个简单的步骤就可以让你在模型注册表中跟踪模型工件和模型版本。对模型工件进行版本化是持续部署的必要步骤。模型由工件组成,这些工件是在训练过程中生成的文件和元数据。模型资产包含模型架构、参数和权重的定义,而模型元数据包含数据集、提交哈希、实验和运行 ID 以及更多训练运行信息。
另一个重要的考虑因素是指定和版本控制你的随机数生成器的种子。在大多数训练和优化步骤中,算法将使用基于随机种子的伪随机数来洗牌数据和参数选择。因此,为了在多次运行代码后产生相同的模型,你需要确保为每个使用随机行为的操作设置一个固定的随机种子。
一旦你了解了源代码版本控制对你应用程序代码和版本化数据集的好处,你就会明白这对你的训练模型来说也是非常有意义的。然而,现在你存储的是每个模型的模型工件(包含模型权重和架构的二进制文件)和元数据,而不是可读的代码。
编写你的环境和部署脚本
自动化你在训练和部署过程中执行的每个操作将增加开发、测试和部署的初始时间,但最终在再次执行这些步骤时将节省大量时间。云服务,如 Azure 机器学习和 Azure DevOps,的好处是它们为你提供了自动化开发部署过程中每一步所需的所有工具。
如果你还没有这样做,你应该开始组织你的 Python 代码到虚拟环境中。流行的选项包括 requirements、pyenv、Pipenv 或 conda 文件,这些文件可以帮助你跟踪开发和测试依赖项。这有助于你将依赖项作为虚拟环境的一部分进行指定,而不是依赖于全局包或开发机器的全局状态。
Azure DevOps 和其他 CI 运行器将帮助你定义依赖关系,因为运行集成测试将在测试过程中自动安装所有定义的依赖项。这通常是 CI 流水线中的第一步。然后,无论何时你将新代码或测试检入到你的版本控制系统,CI 流水线都会执行并自动测试你的环境安装。因此,将集成测试添加到所有模块中是一个好习惯,这样你就不会错过环境中任何包的定义。如果你遗漏了依赖项的声明,CI 构建将失败。
接下来,你还需要编写脚本、配置和自动化所有基础设施。如果你已经阅读了本书的前几章,你现在可能已经明白为什么我们通过 Python 编写环境自动化和部署。如果你之前已经编写了这些步骤的脚本,你可以在 CI 流水线中简单地运行和参数化这些脚本。
如果你运行一个生成模型的 CI 流水线,你很可能希望为这项工作启动一个新的 Azure Machine Learning 集群,这样你就不会干扰到其他发布、构建流水线或实验。虽然这种自动化程度在本地基础设施上非常难以实现,但在云中你可以轻松做到。许多服务,如 Azure Machine Learning 中的 YAML 文件、Azure 中的 ARM 模板或 HashiCorp 的 Terraform,都提供了对你基础设施和配置的完全控制。
最后的部分是在 Azure Machine Learning 中自动化部署。通过代码执行部署并不比通过 UI 花费更多时间,但它提供了可重复和可再现的部署脚本的优点。你经常会面临以多种方式执行相同操作的情况;例如,通过 Azure Machine Learning CLI、Python SDK、YAML、Studio 或 Azure DevOps 中的插件部署 ML 模型。建议选择对你来说最有效的方法,坚持一种做事方式,并以相同的方式进行所有自动化和部署。话虽如此,使用 Python 作为部署的脚本语言并在版本控制中检查你的部署代码是一个好主意,也是流行的选择。
可重复构建和 CI 管道的关键是从一开始就自动化基础设施和环境。在云中,特别是在 Azure 中,这应该非常容易,因为大多数工具和服务都可以通过 SDK 进行自动化。Azure 机器学习团队在 SDK 上投入了大量工作,以便你可以在 Python 内部自动化从摄入到部署的每个步骤。
接下来,让我们来看看代码和资产验证,以确保代码和训练好的模型按预期工作。
验证代码、数据和模型
当实现 CI/CD 管道时,你需要确保你已经设置了所有必要的测试,以便轻松自信地部署你新创建的代码。一旦你运行了 CI 或 CI/CD 管道,自动化测试的力量将立即显现。它不仅可以帮助你检测代码中的故障,还可以帮助你检测整个机器学习过程中的未来问题,包括环境设置、构建依赖项、数据需求、模型初始化、优化、资源需求和部署。
当实现我们机器学习过程的验证管道时,我们可以从传统的软件开发原则中汲取灵感(例如,单元测试、集成测试和端到端测试)。我们可以将这些技术直接转换为机器学习过程中的步骤,例如输入数据、模型和评分服务的应用程序代码。让我们了解如何将这些测试技术适应机器学习项目。
使用单元测试测试数据质量
单元测试对于编写高质量的代码至关重要。单元测试旨在独立于所有其他代码测试代码的最小单元(一个函数)。每个测试应该一次只测试一件事情,并且应该快速运行和完成。许多应用程序开发人员在他们更改代码时运行单元测试,或者至少在将新提交提交到版本控制时运行单元测试。
下面是一个使用 Python 3 标准库中提供的unittest模块编写的单元测试的简单示例:
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(), 'FOO')
如代码片段所示,我们运行一个函数并测试结果是否与预定义的变量匹配。我们可以将更多测试作为额外的方法添加到测试类中。
在 Python 和许多其他语言中,我们区分测试框架和库,这些框架和库帮助我们编写和组织测试,以及执行测试和创建报告的库。pytest和tox是执行测试的出色库;unittest和mock帮助你以类为单位编写和组织测试,并模拟对其他函数的依赖。
当你为你的机器学习模型编写代码时,你也会发现一些代码单元,这些代码单元可以在每次提交时进行单元测试,并且可能应该进行单元测试。然而,机器学习工程师、数据工程师和数据科学家现在在他们的开发周期中还要处理另一个错误来源:数据。因此,重新思考单元测试在数据质量方面的意义是一个好主意。
一旦你掌握了这个技巧,你将很快理解使用单元测试来衡量数据质量的力量。你可以将输入数据的特征维度视为一个可测试的单个单元,并编写测试来确保每个单元都满足定义的要求。这在随着时间的推移收集新的训练数据并计划未来重新训练模型时尤为重要。在这种情况下,我们总是希望在开始训练过程之前确保数据是干净的并且符合我们的假设。
这里有一些示例,说明你的单元测试可以在训练数据中测试的内容:
-
唯一/不同值的数量
-
特征维度的相关性
-
偏度
-
最小值和最大值
-
最常见值
-
包含零或未定义值的值
让我们将其付诸实践,编写一个单元测试来确保数据集的最小值为 0。这个简单的测试将确保如果数据集中包含意外的值,你的 CI/CD 管道将失败:
import unittest
import pandas as pd
class TestDataFrameStats(unittest.TestCase):
def setUp(self):
# initialize and load df
self.df = pd.DataFrame(data={'data': [0,1,2,3]})
def test_min(self):
self.assertEqual(self.df.min().values[0], 0)
在前面的代码中,我们使用 unittest 在同一个类中的多个函数内组织测试。每个类对应一个特定的数据源,在每一个类中,我们可以测试所有特征维度。一旦设置好,我们就可以安装 pytest 并简单地从命令行执行它来运行测试。
在 Azure DevOps 中,我们可以在构建管道中设置 pytest 或 tox 作为简单步骤。对于构建管道步骤,我们只需将以下块添加到 azure-pipelines.yml 文件中:
- displayName: 'Testing data quality'
script: |
pip install pytest pytest-cov
pytest tests --doctest-modules
在前面的代码中,我们首先安装了 pytest 和 pytest-cov 来创建 pytest 覆盖率报告。在下一行,我们执行了测试,现在它将使用数据集并计算所有统计要求。如果测试未满足要求,测试将失败,我们将在构建的 UI 中看到这些错误。这为你的 ML 管道增加了保护,因为你现在可以确保没有未预见的问题的训练数据在没有你注意到的情况下进入发布。
单元测试对于软件开发至关重要,对于数据也是如此。与一般的测试一样,实施它需要一些初始的努力,这不会立即转化为价值。然而,你很快就会看到,有了这些测试,在部署新模型时你会感到更加安心,因为它会在构建时捕捉到训练数据中的错误,而不是当模型已经部署时。
机器学习集成测试
在软件开发中,集成测试验证所谓的单个组件,这些组件通常由多个较小的单元组成。你通常使用测试驱动程序来运行测试套件,并在测试中模拟或存根你不想测试的其他组件。在图形应用程序中,你可以测试一个简单的视觉组件,同时模仿该组件交互的模块。在后端代码中,你测试你的业务逻辑模块,同时模拟所有依赖的持久化、配置和 UI 组件。
因此,集成测试有助于你在组合多个单元时检测关键错误,而无需构建整个应用程序基础设施。它们位于单元测试和端到端测试之间,通常在 CI 运行时按提交、分支或 PR 运行。
在机器学习(ML)中,我们可以使用集成测试的概念来测试机器学习管道的训练过程。这可以帮助你的训练运行在构建阶段发现潜在的错误和错误。集成测试允许你测试你的模型、预训练权重、测试数据片段和优化器是否能够产生成功的输出。然而,不同的算法需要不同的集成测试来测试训练过程中是否存在问题。
当训练一个深度神经网络(DNN)模型时,你可以通过集成测试验证模型的许多方面。以下是一个非详尽的步骤列表,用于验证:
-
权重初始化
-
默认损失函数
-
零输入
-
单批次拟合
-
默认激活函数
-
默认梯度
使用类似的列表,你可以轻松地识别和捕捉到所有激活函数在正向传播中都被限制在最大值,或者在反向传播中所有梯度都是0的情况。理论上,你可以在 CI 运行时连续运行任何实验、测试或检查,就像在处理新的数据集和模型之前手动执行的那样。因此,每次你的模型被重新训练或微调时,这些检查都会在后台自动运行。
一个更一般的假设是,在训练回归模型时,默认的均值应该接近预测值的均值。在训练分类器时,你可以测试输出类别的分布。在这两种情况下,你可以在开始昂贵的训练和优化过程之前,检测到由于建模、数据或初始化错误引起的问题。
在运行者和框架方面,你可以选择与单元测试相同的库,因为在这种情况下,集成测试仅在测试的组件及其组合方式上有所不同。因此,选择unittest、mock和pytest来构建你的集成测试管道是一种流行的选择。
集成测试对于应用程序开发和运行端到端机器学习(ML)管道至关重要。如果你能够自动检测和避免这些问题,这将节省你大量时间并降低运营成本。
使用 Azure Machine Learning 进行端到端测试
在端到端测试中,我们希望验证所有参与请求已部署和完全功能服务的组件。为此,我们需要一起部署完整的服务。端到端测试对于捕获仅在所有组件组合在一起并在一个没有模拟其他组件的预发布或测试环境中运行服务时触发的错误至关重要。
在机器学习部署中,有许多步骤,如果不进行适当的测试,可能会出现很多问题。让我们先排除那些需要确保环境正确安装和配置的简单问题。在 Azure 机器学习中的部署中,一个更关键的部分是应用程序逻辑本身的代码:评分文件。没有简单的方法可以在没有适当端到端测试的情况下测试评分文件、请求格式和输出。
如你所想,端到端测试通常构建和操作成本很高。首先,你需要编写代码并部署应用程序来测试代码,这需要额外的工作、努力和成本。然而,这是在类似生产环境的端到端环境中真正测试评分端点的唯一方法。
好消息是,通过使用 Azure 机器学习部署,端到端测试变得如此简单,以至于它应该成为每个人的管道的一部分。如果模型允许,我们甚至可以进行无代码部署,我们不需要指定部署目标。如果这不可能,我们可以指定 Azure 容器镜像作为计算目标,并独立部署模型。这意味着将前一章的代码放入 Python 脚本中,并将其作为构建过程中的一个步骤。
端到端测试通常很复杂且成本高昂。然而,使用 Azure 机器学习和自动化部署,模型部署和样本请求可以仅仅是构建管道的一部分。
持续监控你的模型
模型监控是实验和训练阶段的重要步骤。这将帮助你了解当模型作为评分服务使用时所需的资源。这是设计和选择适当规模的推理环境的关键信息。
无论训练和优化过程是否持续进行,模型需求和配置都会随着时间的推移而演变。如果你使用优化进行模型堆叠或自动化机器学习,你的模型可能会变得更大以适应新的数据。因此,密切关注你的模型需求,以考虑与初始资源选择偏差是很重要的。
幸运的是,Azure 机器学习提供了一个模型监控接口,你可以用模型、评分函数和测试数据来填充它。它将为你实例化一个推理环境,启动评分服务,将测试数据通过服务运行,并跟踪资源利用率。让我们将所有这些部分组合起来,并设置一个端到端 MLOps 管道。
构建端到端 MLOps 管道
在本节中,我们希望设置一个端到端的 MLOps 管道。所有必需的训练代码都应该被检查到版本控制中,数据集和模型也将进行版本控制。我们希望在代码或训练数据发生变化时触发 CI 管道以构建代码和重新训练模型。通过单元和集成测试,我们将确保训练和推理代码在隔离状态下工作,并且数据和模型满足所有要求,不偏离我们的初始假设。因此,CI 管道将负责自动的持续代码构建、训练和测试。
接下来,每当一个新的模型版本准备好时,我们将触发 CD 管道。这将部署模型和推理配置到预发布环境,并运行端到端测试。测试成功完成后,我们希望自动将模型部署到生产环境。因此,CD 管道将负责自动部署。
将管道分为 CI 和 CD 部分使得将构建资产的过程与部署资产的过程解耦变得容易。然而,您也可以将这两部分合并为一个单一的 CI/CD 管道,从而使用一个管道进行构建、训练、优化和部署。如何对您的管道的 CI 和 CD 组件进行建模,以及如何设置任何触发器和(手动)批准,取决于您和您的组织。您可以选择将每个提交部署到生产环境,或者在每个工作日或每周在手动批准后部署一定数量的提交。
在本节中,我们将使用 Azure DevOps 来编写和执行 CI/CD 管道,因此设置触发器、运行构建、训练和测试步骤,并处理训练模型的部署。Azure DevOps 具有内置的功能来自动化端到端的 CI/CD 流程。通常,它允许您在您定义的计算基础设施上运行管道中的功能块,称为任务。您可以通过版本控制系统中新提交的提交自动触发管道,或者通过构建工件的新版本或按钮等触发它们,例如,用于半自动部署。前者称为代码管道,指的是 CI,而后者称为发布管道,指的是 CD。
让我们开始设置一个 Azure DevOps 项目。
设置 Azure DevOps
Azure DevOps 将作为编写、配置、触发和执行所有我们的 CI/CD 管道的容器。它提供了与版本控制资源(如代码仓库和与 Azure 及 Azure 机器学习工作区的连接)一起工作的有用抽象,并允许您协作访问运行器、管道和构建工件。
重要提示
Azure DevOps指的是通过dev.azure.com/可访问的托管 Azure DevOps 服务。还存在一个名为Azure DevOps Server(以前称为 Visual Studio Team Foundation Server(TFS))的本地提供方案,它提供了类似的 CI/CD 集成功能。
作为第一步,我们将设置 Azure DevOps 工作区,以便我们可以编写和执行 Azure MLOps 管道。让我们从设置组织和项目开始。
组织和项目
首先,您需要设置您的组织。组织是一个用于管理类似项目并与一组人协作的工作区。您可以通过使用 Microsoft 账户、GitHub 账户或连接到Azure Active Directory(AAD)来创建组织。要创建组织,您需要登录到 Azure DevOps(dev.azure.com/),提供您组织的 slug 名称,并选择一个区域来托管您组织资产。
以下图显示了创建新的 Azure DevOps 组织的屏幕:

图 16.1 – 创建新的 Azure DevOps 组织
接下来,您可以在组织中设置项目;我们将从一个包含运行 MLOps 管道的配置和代码的项目开始。项目是一个用于逻辑分组特定 ML 项目所有资产的地方。您将能够在 Azure DevOps 项目中管理您的代码仓库、冲刺板、问题、PR、构建工件、测试计划和 CI/CD 管道。
以下图显示了创建新的 Azure DevOps 项目的流程。这将作为我们的管道、测试和部署配置的容器:

图 16.2 – 创建新的 Azure DevOps 项目
一旦我们设置了组织和项目,我们需要通过安装适当的 Azure DevOps 扩展将 Azure 机器学习功能添加到 Azure DevOps 中。
Azure 机器学习扩展
接下来,建议为您的 Azure DevOps 组织安装 Azure 机器学习扩展。这将紧密集成您的 Azure 机器学习工作区到 Azure DevOps 中,以便您可以在 Azure DevOps 内执行以下操作:
-
通过 Azure 资源管理器自动分配访问您的 Azure 机器学习工作区资源的自动权限。
-
触发新模型修订的发布管道。
-
将 Azure 机器学习管道作为任务运行。
-
设置模型部署和模型分析预配置任务。
公平地说,所有上述内容也可以通过使用自定义凭据和 Azure ML Python SDK 手动设置,但紧密集成使得设置变得更加容易。
重要提示
你可以从 marketplace.visualstudio.com/items?itemName=ms-air-aiagility.vss-services-azureml 安装 Azure Machine Learning 扩展到 Azure DevOps。
接下来,我们将使用扩展来设置服务连接和你的 Azure 及 Azure Machine Learning 工作区账户的访问权限。
服务连接
你可能还记得从之前的代码示例中,与 Azure 和 Azure Machine Learning 资源交互需要配置适当的权限、租户和订阅。访问这些服务和资源的权限通常通过 服务主体 定义。在 Azure DevOps 中,我们可以设置我们的 Azure DevOps 管道访问 Azure 和 Azure Machine Learning 资源、创建计算资源以及通过 服务连接 提交 ML 实验的权限。
在你的 Azure DevOps 项目中,转到 设置 | 服务连接 并配置一个新的 Azure 服务连接,使用服务主体身份验证你的 Azure Machine Learning 工作区。以下图显示了如何在 Azure DevOps 中设置此连接:

图 16.3 – 创建 Azure DevOps 服务连接
同样,你也可以允许 Azure DevOps 管道以编程方式管理 Azure 资源组中的资源。建议你通过服务主体创建这两种权限,并注意新创建的连接的名称。
密钥
在下一步中,我们希望将所有变量和凭证存储和管理在实际的 CI/CD 管道之外。我们不希望将凭证或配置参数(如订阅 ID、工作区名称和租户 ID)嵌入到管道中,而是将它们作为参数传递给正在运行的管道。
在 Azure DevOps 中,你可以通过使用 变量组 和 安全文件 来实现这一点。你甚至可以将变量组连接到 Azure Key Vault 实例来为你管理密钥。
建议你导航到 管道 | 库 来设置一个包含你的订阅 ID、租户 ID、服务连接名称等作为变量的变量组,以便它们可以在管道中重复使用。如果你需要,你总是可以稍后回来添加更多变量。以下图显示了可以包含在你的管道中的示例变量组定义:

图 16.4 – 创建 Azure DevOps 变量组
接下来,我们将设置一个仓库并编写代码管道。
代理和代理池
你的 CI 和 CD 任务最终将检出项目,构建它,训练模型,运行测试,并部署它。为了完成所有这些(以及更多),你需要一个计算基础设施来运行 CI/CD 作业。在 Azure DevOps 中,这些计算资源被称为 代理。
Azure DevOps 服务提供 Microsoft 托管代理,这些代理将在 VM 或 Docker 镜像中执行你的管道作业。这两种计算资源都是临时的,并在每个管道作业后拆除。
当使用 Azure DevOps 与公共项目时,Azure Pipelines 是免费的,并为你的 CI/CD 管道作业提供 Microsoft 托管代理。这允许你运行最多 10 个并行作业,每个作业最多运行 6 小时。对于私有项目,你每月最多只能运行一个并行作业,每个作业最多运行 1 小时。
重要提示
为了防止滥用,所有免费管道资源都需要通过此表单请求组织:aka.ms/azpipelines-parallelism-request。
如果需要更多容量,我们可以通过 Azure DevOps Server 和/或 Azure VM 规模集代理运行自托管代理,或者通过 Azure DevOps 服务购买额外的 Microsoft 托管代理。为了本书的目的,你应该能够利用私有仓库上的免费容量舒适地进行实验。
持续集成 – 使用管道构建代码
现在,我们可以开始使用 Azure DevOps 管道设置我们的 ML 模型的自动构建、测试和训练管道。从概念上讲,我们将在 Azure DevOps 中创建或导入一个 Git 仓库,作为我们的 ML 项目的容器,并将包含 CI 管道定义。按照惯例,我们将管道存储在 .pipeline/ 目录中。
下图展示了如何在 Azure DevOps 中设置或导入一个仓库:

图 16.5 – 克隆或导入仓库
接下来,我们打开 Visual Studio Code 并开始编写我们的管道。我们不会从小部件和插件中构建 CI 管道,而是选择 YAML 来编写管道代码。这与 GitHub CI 或 Jenkins 工作流程的编写方式非常相似。
管道包含一系列线性任务,用于构建、测试和训练 ML 模型,这些任务可以通过仓库中的条件触发。在 Azure DevOps 管道中,任务按以下层次结构组织:
-
阶段 A:
-
作业 1:
-
步骤 1.1
-
步骤 1.2
-
-
作业 2:
- 步骤 2.1
-
因此,一个管道由阶段组成,其中每个阶段包含多个作业。每个作业可以包含多个称为步骤的任务。除了阶段和作业之外,管道还可以包含以下部分:
-
管道定义:
name:管道的名称
-
管道触发器:
-
schedules:基于计划的管道触发配置 -
trigger:基于代码的管道触发配置 -
pr:基于 PR 的管道触发配置
-
-
管道计算资源:
-
resources: 容器和存储库配置 -
pool: 管道计算资源的代理池配置
-
-
管道定制:
-
variables: 管道变量 -
parameters: 管道参数
-
-
管道作业定义:
-
stages: 管道作业的分组,如果管道只有一个阶段,则可以跳过 -
jobs: 要执行的管道作业
-
如前所述的列表所示,Azure DevOps 管道 YAML 方案允许您自定义管道触发器、计算资源、变量和配置,并允许您定义管道中要运行的作业。Azure DevOps 管道还理解模板的概念。您可以使用 template 指令为阶段、管道、作业、步骤、参数和变量引用模板中的文件。
重要提示
您可以在微软文档中找到管道 YAML 方案的文档,网址为 docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/.
让我们使用这些步骤定义来构建一个简单的管道,以测试模型代码并开始模型训练:
ci-pipeline.yaml
trigger:
- main
pool:
vmImage: ubuntu-latest
stages:
- stage: CI
jobs:
- job: Build
steps:
- script: pytest tests --doctest-modules
- stage: Train
jobs:
- job: Train
steps:
- script: python train.py
在前面的管道中,我们定义了触发器,当 main 分支上有新提交时启动管道。对于执行,我们使用 Ubuntu VM 在微软托管的免费代理池上运行每个作业。然后,我们将任务分为两个阶段:CI 和 Train。前者将构建和测试代码及数据集,而后者将训练机器学习模型并在模型注册表中创建模型的新版本。
现在,我们可以在存储库中添加一个提交并将其合并到 main 分支,CI 管道将被触发并训练一个新的模型版本。您可以使用前面的管道定义作为起点,添加额外的步骤、测试、配置和触发器,以完全定制您的 CI 管道。
重要提示
您可以在微软 GitHub 存储库中找到最新的 MLOps 管道示例,网址为 github.com/microsoft/MLOpsPython。
您可以在 Azure MLOps 存储库中找到更多 MLOps 起始点的示例 github.com/Azure/mlops-v2
接下来,我们将查看一个 CD 管道,以将训练好的模型部署到生产环境中。
持续部署 - 使用发布管道部署模型
在模型注册库(例如 Azure 机器学习)中跟踪模型工件(例如,新机器学习模型或版本)的另一个好处是,当工件发生变化时,您可以在 Azure DevOps 中自动触发发布管道。任何工件,如新的机器学习模型或版本,都可以配置为在 Azure DevOps 中触发发布。因此,代码更改触发 CI 构建管道,而工件更改触发 CD 发布管道。在本节中,我们将为我们的模型创建一个 CD 管道,并自动将模型推出到预发布和生产环境。
尽管发布管道的触发机制与构建管道不同,但大多数关于管道执行的概念都非常相似。发布管道也有管道阶段,而每个阶段可以有多个任务。发布管道的一个额外功能是,由于它们处理工件部署,因此每个阶段可以具有额外的触发器,以及部署前和部署后条件,例如手动批准和门控。
触发器将允许您在指定的计划期间继续管道执行。手动批准将使管道暂停,直到被定义的用户或用户组批准,而门控将使管道在执行程序性检查之前暂停预定义的时间。通常将多个阶段、触发器以及部署前和部署后的条件结合起来,以安全地将工件部署到不同的环境。
如果您已安装 Azure Machine Learning 插件,您可以选择专门针对 Azure Machine Learning 的触发器和部署任务,例如基于 ML 模型版本的工件和 Azure Machine Learning 模型部署和性能分析任务。在本节中,我们将选择 ML 模型工件触发器和 ML 模型部署任务。
重要提示
您可以在 Microsoft 文档中找到可用的 Azure DevOps 任务,网址为docs.microsoft.com/en-us/azure/devops/pipelines/tasks/。
下图显示了 Azure DevOps 发布管道,其中我们选择一个机器学习模型作为发布管道触发的工件。我们配置了两个阶段,一个是部署到预发布环境,另一个是部署到生产环境。此外,我们还添加了一个手动批准作为预发布部署的部署后条件:

图 16.6 – 定义 Azure DevOps 发布管道
默认情况下,发布管道将要求用户通过在右上角点击创建发布按钮来创建发布。这种模式旨在仅在操作员决定触发部署时创建发布,并有助于我们在配置发布管道时避免任何自动化部署。然而,一旦操作员确信管道和发布过程按预期工作,我们可以通过切换发布管道中资产上的闪光图标来启用自动化部署。这将启用 CD 触发器,因此,每当资产发生变化时,都会触发发布和部署。在本章的最后一个任务中,您可以继续激活 CD 触发器,以完全自动化您的 CD 管道。
摘要
在本章中,我们介绍了 MLOps,这是一种类似于 DevOps 的工作流程,用于开发、部署和操作机器学习服务。DevOps 代表了一种快速且高质量地更改代码并将这些更改部署到生产环境的方法。
我们首先了解到 Azure DevOps 提供了所有功能来运行强大的 CI/CD 流水线。我们可以运行构建流水线,其中步骤用 YAML 编码,或者发布流水线,它们在 UI 中进行配置。发布流水线可以有手动或多个自动触发器(例如,版本控制仓库中的提交或如果模型注册表的工件已更新)并为发布或部署创建输出工件。
版本控制您的代码是必要的,但仅此不足以运行适当的 CI/CD 流水线。为了创建可重复构建,我们需要确保数据集也进行了版本控制,并且伪随机生成器使用指定的参数进行初始化。环境和基础设施也应自动化,部署可以从创作环境进行。
为了保持代码质量高,您需要将测试添加到机器学习流水线中。在应用程序开发中,我们区分单元测试、集成测试和端到端测试,它们测试代码的不同部分,要么独立进行,要么与其他服务一起进行。对于数据流水线,如果数据发生变化或增加,单元测试应测试数据质量以及应用程序中的代码单元。集成测试非常适合独立于其他组件加载模型或执行正向或反向传递。使用 Azure Machine Learning,编写端到端测试变得非常愉快,因为它们可以非常低效和低成本地完全自动化。
现在,您已经学会了如何设置连续流水线,这些流水线可以重新训练和优化您的模型,然后自动构建和重新部署模型到生产环境中。在最后一章中,我们将探讨您、您的公司以及您在 Azure 中的 ML 服务下一步将是什么。
第十七章:第十七章:为成功的 ML 之旅做好准备
恭喜你,你已经成功了——你经历了多么不可思议的旅程!到现在,你应该已经学会了如何在云中预处理数据,实验ML模型,在自动扩展集群上训练深度学习模型和推荐引擎,优化模型,并将它们部署到你想要的地方。你应该知道如何通过MLOps将这些步骤操作化,为蛋糕增添一抹亮色。
在最后一章,我们将回顾我们在这次旅程中学到的一些重要启示。很容易在技术和算法选择中迷失或感到不知所措。你可能会深入研究建模、基础设施或监控,但可能离拥有一个好的预测模型更远。
在第一部分,我们将提醒你,ML 主要关于数据。人工智能可能应该被称为数据清洗和标注,但当然,这听起来没有 AI 那么好。你会明白你的数据是出色性能的关键,所以你最应该关心的是这个。你的数据就是一切!
在接下来的部分,我们将向你展示如何开始你的 ML 项目。我们将通过提供一些指导并强调干净的基础设施和深思熟虑的监控的重要性来实现这一点。
之后,我们将重申自动化的重要性以及新技术将如何带我们进一步进入机器学习即服务(MLaaS)的世界。了解技术的发展方向总是很好的,在机器学习的情况下,它是元学习和系统,它们已经自动建议合适的模型并将它们堆叠起来以实现良好的预测性能。当建模完全自动化时,剩下的是什么?正是——你的数据!
在此之后,我们将讨论云服务的持续变化和演变,同时关注 PaaS 服务。我们将探讨为什么 PaaS 解决方案被构建以及它们的基石是什么。这将帮助你了解如何最好地准备应对变化,以及为什么尽管服务不断变化,你仍然在正确的基石上押注。
最后,我们将讨论在这本书中我们主要忽略的一个主题。我们将讨论在开始任何 ML 项目之前你应该思考的一些问题:你应该这样做吗?你模型的成果会对人们的生活产生严重影响吗?你可能已经猜到了:我们将从数据处理的角度讨论伦理。在一个越来越互联的世界中,你不应该滥用他人的个人信息,你不应该构建对某些群体极端有偏见的模型,你不应该通过你的部署解决方案负面地影响人们的生活。
本章将涵盖以下主题:
-
记住数据的重要性
-
从一个深思熟虑的基础设施开始
-
自动化重复性任务
-
预期持续变化
-
思考你的责任
记住数据的重要性
许多用于预测和模型拟合的算法问题难以使用经典优化算法或复杂启发式方法进行建模、计算和优化。监督式机器学习提供了一种利用优化和大量标记训练数据解决最复杂问题的强大新方法。
有些人可能认为你只需将大量数据扔给模型。想象一下,你有成千上万张同一只鸟从每个可能角度的照片。基于这些照片训练的模型可能对分类不同的鸟类家族的预测并不具有很高的预测性。
为你的模型选择合适的数据样本
当模型使用高度独特的数据样本和对你模型应预测的上下文有用的数据样本时,其质量会提高。
因此,当你使用机器学习算法工作时,你需要记住,模型是由你提供给它们的训练数据和训练标签驱动的。好的数据是良好性能的关键。
了解这一点,让我们再次强调在处理数据和训练机器学习模型时的关键要点:
-
大部分时间用于处理数据:正如我们在本书开头讨论的那样,在大多数机器学习项目中,你将花费大约 80%的时间进行数据分析、预处理和特征工程。彻底理解你的数据对于开发成功的预测模型至关重要。这样想:使你与众不同的唯一东西就是你的数据。很可能,你的竞争对手可以访问与你类似的算法、优化和计算基础设施。他们唯一没有的是你的数据和你的技能来分析这些数据(希望如此)。因此,这就是你成功的关键所在:在解释、清理、建模和准备你的数据以进行高质量预测。
-
强调特征工程:你获得的最大机会是提高任何模型的基础预测性能,这可以通过改进你的基础数据集,通过更好的特征工程或添加更多预测性特征来实现。不要迷失在尝试调整和堆叠模型的过程中。相反,你应该把大部分的时间和资源投入到数据预处理和特征工程中。特征工程是你可以发光和赢得预测游戏的地方。你正在处理日期吗?引入其他数据源,例如当地和全球的节假日以及附近的事件;添加相对日期,例如节假日前的天数,周末前的天数等等。你正在处理位置、城市或国家吗?在这里,你应该引入人口统计数据、政治数据或地理数据。你明白这个意思。
-
不要被模型调优分散注意力:你的模型能做的事情是有限的。是的,你可以堆叠多个模型,调整和优化它们,针对不同的指标进行优化,等等。然而,你的最大优势是你的数据。任何机器学习模型的好计划都是从一个非常简单的基础模型开始。你是在处理分类数据吗?如果是这样,选择梯度提升树集成,并坚持默认参数。你是在预测连续值吗?如果是这样,选择逻辑回归模型。从小处着手,确保你在开始调整模型之前,你的数据是正确的。
-
始终从基础模型开始:使用基础模型,并围绕它构建所有自动化、基础设施和指标。值得注意的是,基础模型应该比随机方法表现更好。一旦管道完成,你就可以深入数据,添加新数据,进行更好的特征工程,再次部署,测试,并重新迭代。将你的模型简化为原始的基础模型是一个困难的步骤,但它将帮助你成功管理项目第一阶段的工作重点。为什么基础模型方法如此重要?因为它为迭代项目设定了你的心态,在这个项目中,你不断地测量、添加数据、重新训练并改进你的模型。你的模型将需要重新训练,你需要测量何时需要这样做。为了重新训练,你需要新的训练数据。
-
持续收集新的、相关的数据样本:在一个完美的设置中,你会安装一个持续的数据收集管道,直接从你的当前产品中收集新的训练数据和训练标签。你的模型预测搜索相关性吗?收集搜索查询和点击结果。你的模型预测欺诈吗?收集新的数据和手动验证的欺诈案例的结果。你的模型预测标签吗?跟踪预测,并让用户在它们不准确时更改它们。在这些所有例子中,我们持续跟踪相关的训练数据,我们可以用这些数据不断重新训练和微调。拥有这种持续的训练数据流可能是你业务的竞争优势,让你为成功做好准备。因此,当你监督一个机器学习项目时,考虑你将如何在未来重新训练模型。
除了遵循这些技术规则来处理机器学习项目外,了解你公司的业务方面至关重要。这样的项目通常需要一个跨学科团队才能成功。因此,获得公司数据策略的 C 级支持至关重要。数据是你的燃料,它通常以大量数据孤岛的形式分散在公司的各个部门。你可能需要访问大量这些来源来实现和改进机器学习模型,因此,拥有访问和使用这些数据的权限至关重要。
这通常需要大多数公司进行思维上的转变,因为来自不同部门的数据需要结合和分析,以便用于预测。因此,数据质量很重要,数据来源很重要,这样你才能了解它来自哪里,及时性很重要,正确性是必不可少的。所以,确保数据在你的公司中得到应有的支持、关爱和照顾。
现在我们已经重申了关于数据处理的重要事实,让我们谈谈你正在工作的环境。
从一个深思熟虑的基础设施开始
成功应用的机器学习项目依赖于迭代的方法来处理数据收集、数据清洗、特征工程和建模。在成功部署和推广后,你应该回到起点,关注你的指标,并收集更多数据。现在应该很清楚,你将在机器学习项目的生命周期中重复一些开发和部署步骤。
从一开始就正确设置你的机器学习项目的基础设施和环境将为你节省很多麻烦。成功基础设施的一个关键在于自动化和版本控制,正如我们在上一章中讨论的那样。因此,我们建议你花几天时间来设置你的基础设施和自动化,并在 Azure 机器学习中注册你的数据集、模型和环境。
这同样适用于监控。为了做出明智的决定,比如你的模型是否按预期工作,训练数据是否仍然准确,或者资源利用率是否足够高,你需要准确的指标。在部署后添加指标相当棘手。因此,你应该事先了解你想要衡量什么,以及你想要提前被提醒什么。在你的项目开始时,花些额外的时间思考你将要跟踪的指标。
最后,在处理数据和模型的同时优先考虑基础设施是困难的。如果你能承担将它们分成单独的团队进行机器学习基础设施、建模和数据工作的奢侈,那么这可能不是你首要考虑的事情。然而,这种情况通常并不存在。为了避免这种优先级问题,我们建议从简单的基线模型开始,并基于这个简单的模型定义你的基础设施自动化。
让我们看看当你开始你的机器学习项目时应该执行哪些步骤:
-
选择一个基线模型:为你的用例选择具有默认参数的最简单模型,一小部分训练数据以及最重要的工程特征。
-
构建一个简单的流水线:将这些模型训练步骤放入一个流水线中,自动构建你的模型并将其部署到预发布环境中。这种方法的优点在于你自动优先考虑基础设施,并且始终输出一个已部署的评分服务。这将为你成功奠定基础。
-
深入数据:确保你理解数据及其质量,如何填充缺失值,以及如何预处理特征。你可以添加额外的数据,并从事特征工程,将你的原始输入数据转换为可解释的数据。如果你选择了一个好的基线模型,这项工作应该会极大地提高基线模型的性能,并给你的同事提供一个评分服务 API,以便与新的服务一起使用。
-
实验更复杂的模型:一旦你确信你已经建立了一个稳固的数据管道,你就可以着手建模,包括模型选择、训练、验证、优化和堆叠。再次强调,你应该能够看到可以衡量并持续部署到任何 QA 环境中的渐进式改进。一旦你的性能足够好,就可以将服务推广给你的客户,并开始收集指标和更多训练数据。
-
监控云使用:当你使用云中的计算基础设施进行开发时,很容易迅速花费几千美元用于一些未使用或利用率低的虚拟机。我们建议你定期检查机器的数量及其利用率。如果某些东西不再被使用,就进行扩展或关闭。请记住,云的最大好处是可扩展的基础设施。所以,请充分利用它。
遵循这些指导原则将帮助你建立一个干净且可监控的基础设施,你可以在这个过程中不断演进。
现在我们已经讨论了你应该设置的基础设施,让我们再次谈谈自动化。
自动化重复性任务
训练机器学习模型是一个复杂且迭代的流程,包括数据准备、特征工程、模型选择、优化和部署。最重要的是,一个企业级的端到端机器学习管道需要是可重复的、可解释的、安全的和自动化的,这对大多数公司来说在知识、成本和基础设施要求方面都提出了额外的挑战。
在前面的章节中,我们学习了这个过程的方方面面,因此我们可以确认它没有什么是简单或容易的。调整特征工程方法会影响模型训练;数据清洗过程中的缺失值策略将影响优化过程。
首先,你的模型所捕获的信息很少是恒定的,因此大多数机器学习模型都需要频繁的重训练和部署。这导致了对 MLOps 的新要求:一个用于机器学习的 DevOps 管道,以确保数据的持续集成和持续部署。
自动化机器学习通过自动化许多这些挑战来简化这个复杂且迭代的流程。而不是手动调整输入数据,然后手动选择、优化和部署机器学习模型,自动化服务只需要输入数据,以及一些与业务相关的配置,例如要训练的预测类型。
因此,使用 Azure DevOps 和 Azure 机器学习管道等工具可以大大减少错误和系统停机时间,并使用户从执行大量手动任务中解放出来。此外,Azure 自动机器学习等服务允许用户优化机器学习训练,甚至堆叠多个模型以提高预测性能。最大的好处是用户可以专注于机器学习过程最重要的部分:理解、获取和清理数据。
在许多情况下,自动机器学习服务将优于手动训练的模型,同时显著降低训练和运营成本。原因在于许多任务,如选择正确的分类嵌入、处理不平衡数据、选择最佳模型、找到最佳参数以及结合多个模型以提高性能,可以系统地优化,而不是手动选择。
每个主要的云服务提供商都提供成熟的服务,以便您可以在云中执行自动机器学习,并方便地部署这些模型。自动机器学习是一种节省时间和成本的同时,为现有员工提供训练复杂端到端机器学习管道所需工具的绝佳方式。这使得自动机器学习成为一种真正的服务——机器学习即服务(MLaaS)。
谈到工具,让我们谈谈当您使用现代云系统时需要跟上的一些变化。
期待持续变化
一切都处于持续变化的状态。15 年前,只有少数人听说过神经网络和机器学习。今天,您可以访问大量的机器学习库、程序和云服务。每天,都在取得新的进展,以自动化机器学习任务并改进机器学习建模。只需想想您可能使用的语音助手以及自动驾驶汽车正在发生的事情。
由于这个原因,您将面临对机器学习库及其工具进行的大量持续变化。这在云环境中尤其如此,与许可软件相比,更新可以快速推送到用户群体。正如我们之前所学的,查看大型云服务提供商,他们的服务通常可以分为以下几类:
-
基础设施即服务(IaaS):IaaS 服务是所有基础设施抽象,如虚拟机(计算)、磁盘(存储)和网络。
-
平台即服务(PaaS):PaaS 服务是在这些组件之上构建的平台,具有额外的功能,可以暴露服务同时隐藏底层基础设施和操作系统。
-
软件即服务(SaaS):与 PaaS 服务相反,SaaS 服务通过用户界面暴露,不提供对底层软件和硬件堆栈的任何访问。
Azure 机器学习是一个很好的 PaaS 服务的例子,因为它结合了不同的基础设施服务、UI 和 SDK,为你提供了全新的功能,并提供了对底层服务的完全访问,例如 blob 存储、训练集群和容器注册表,而在大多数情况下,操作系统则被置于幕后。在你的每月 Azure 账单上,你会发现当你使用 PaaS 解决方案时,你大部分的钱都花在了基础设施服务上。
虽然底层基础设施为所有云服务奠定了基础,但它们在接下来的几年内不太可能发生剧烈变化。新的改进将进入市场,通常集中在吞吐量水平和网络安全上。尽管如此,你不应该期望对现有 API 进行重大更改。此外,这些服务不太可能被终止,因为它们是许多服务的基石。
对于 PaaS 服务来说,情况并非如此。它们被设计用来回答客户关于抽象解决方案的需求,这样他们就可以免于编写大量的样板代码和处理解决方案的低级基础设施细节。你有多少次看到 Azure 机器学习的一个功能,心想,“嘿,我完全可以自己实现这个功能”?这当然是对的,但你可能希望有人帮你解决这个简单的问题,这样你就可以专注于你试图解决的复杂问题。这就是 PaaS 最初存在的原因。
然而,客户驱动的需求带来的不利之处在于,这些需求和用法模式始终在不断发展。新的用例(如 MLOps)不断出现,需要支持新的服务或对现有服务的扩展。因此,你应该始终期待 PaaS 会随着时间的推移而变化。
如果你查看这本书的第一版,你会发现其中近一半的代码和功能要么已经弃用,要么被新的东西所取代,或者与 Azure 机器学习服务的其他部分合并。根据你阅读这本书的时间,你可能已经发现我们在这里描述的功能或 API 与 Azure 当前 API 和功能之间存在差异。
如果你感到困惑是可以理解的,并问自己这本书怎么会已经过时了,我们想向你保证,我们展示的是正确的技术,值得下注。PaaS 服务总体上,以及 MLaaS 服务具体来说,总是在经历巨大的变化和改进。期待变化!
让我们看看你可能随着时间的推移会遇到的一些可能的变更:
-
预期名称会发生变化:这可能是最常见的变更。公司通常在命名产品方面做得不好,Azure 和其他所有云服务提供商也不例外。这可能看起来像是一个很大的变化或不便,但实际上只是更改服务或组件的名称,或者将其隐藏在云平台的其他地方。在过去的几年里,针对 Azure 的机器学习(ML)进行了许多变更。曾经有一个名为Azure Machine Learning Studio (classic)的服务,它主要作为 Azure 机器学习中的Designer存在。曾经有,现在仍然有名为Azure Batch、Azure BatchAI和AML Compute的服务,它们提供了与您现在在 Azure 机器学习中找到的批推理计算集群大致相同的功能。简单来说,不要让自己被这些变化分散注意力。预期会出现一些有趣的新名称,用于您所熟悉和喜爱的功能。
-
预期 UI 会发生变化:这是最明显的变化,在最近的云服务中相当常见。许多服务都得到了全新的 UI,一些被整合到 Azure UI 中,还有一些被放置在单独的应用程序中。预期某些功能可能只在一个 UI 中暴露,而在另一个 UI 中则不暴露。然而,通常情况下,新的 UI 意味着相同或类似的功能可以通过新的界面访问。这也是我们为什么训练你更多地使用 Python API 或 Azure CLI 而不是图形界面工作的原因之一。
-
预期 SDK 中的类和包会发生变化:大多数云服务提供商的机器学习解决方案的 API 都在不断演变。Azure 在它的机器学习服务上投入了大量的资金,因此变化是不可避免的。为应对这种变化,一个很好的做法是将代码抽象成特定的实现,这样就可以轻松地用新功能替换。另一个好的做法是对库更新保持谨慎,但也不要落后于最新版本太久。
你是否同意,在这些情况下,变化是唯一的不变因素?请记住,所有 PaaS 解决方案最终都是建立在底层基础设施之上的,这为你的计算、存储和网络提供了坚实的基础。
因此,请记住:尽管变化不断,你是在正确的基石上构建!
在讨论了在使用云平台进行机器学习时应考虑的大部分内容之后,让我们谈谈一个更加重要的话题:数据伦理。
考虑到你的责任
在本书的最后一节,我们想要从模型、部署和优化中退一步,来谈谈一个更加重要的主题:处理数据时的伦理,或者今天所知的负责任的 AI/ML。
在第一章《理解端到端机器学习过程》中,我们讨论了数据中的偏差,它如何有意或无意地被引入数据集中,以及你需要注意什么。这只是反映你如何收集数据以及你的训练模型如何对他人生活产生负面影响的一个小小拼图。
想象一下,你正在训练一个机器学习模型,建议银行柜员允许面前的客户获得贷款,以及客户可以获得的贷款利率。使用自动化系统做出这个决定可能是一种祝福或诅咒。如果公司的大多数银行柜员存在固有的偏差,而你构建了一个公平的模型,那么这可能会是一种祝福。然而,如果你的模型基于那些银行柜员的先前决策,你必须密切关注你的数据中的大量偏差。如果不这样做,你可能会创造一个更加不公平的世界,因为现在,你的机器学习系统负责。一个公平的柜员发放贷款,即使他们可能了解你的机器学习系统中存在偏差,现在可能也不允许他们推翻它。
虽然有比这更糟糕的例子,但这应该能给你一个很好的概念,了解我们想要讨论的内容。
一般而言,我们可以将你的责任分为以下几类:
-
可解释性:你如何解释你的模型及其生成的结果?
-
公平性:你如何通过消除数据中的偏差来确保公平性?
-
隐私:在你的基础数据和模型中,个人的可识别信息(PII)得到了多好的保护?谁可以访问它?
-
合规性:你所使用和可以访问的每一件事物都有多好的文档记录?你是如何追踪谁在使用你的数据或模型的?
让我们更详细地看看你需要注意的事项,以及 Azure 机器学习提供的哪些工具可以帮助你在进行这项工作时得到支持。
解释模型
任何部署的机器学习模型都是一个黑盒。我们通过模型发送输入并接收以预测或分类形式呈现的输出。因此,利益相关者很难理解为什么系统会做出某些决策以及为什么不会。为了缓解这种情况,你可以应用新的工具来解释你的模型。
但在我们讨论解释机器学习模型的工具和方法之前,让我们将模型分为两类:
-
黑盒模型:计算如此复杂,以至于我们不知道决策是如何形成的。
-
玻璃箱模型:结果可以相对容易地解释和计算的模型。例如,考虑线性回归模型。
玻璃盒模型通常更简单,所以权衡似乎是在可解释性和复杂性(因此,可能是准确性)之间。但如果您的模型处理大量个人信息,您将想知道模型是如何得出结论的。
因此,出现了对黑盒模型进行解释的需求,这种解释器被称为黑盒解释器。以下是最为知名的两种解释器:
-
Shapley 增量解释(SHAP):这是一种将博弈论应用于机器学习模型的方法,主要用于可解释性。这个方法族假设模型中的每个特征都是一个游戏中的玩家。基于这个假设,您可以使用所谓的Shapley 值来计算特征值对预测的平均贡献。简单来说,这是通过向联盟中添加和删除特征来完成的,在博弈论中,联盟是合作的一组玩家。SHAP 可以用于任何类型的模型,但它对线性回归、树、集成树以及使用 TensorFlow 或 Keras 的深度学习有很好的定义。此外,它还可以解释单个预测,而不仅仅是全局层面的解释。您可以在其开源版本中了解更多关于 SHAP 的信息(
github.com/slundberg/shap)。 -
局部可解释模型无关解释(LIME):这是一种创建所谓的代理玻璃盒模型的方法,基于任何黑盒分类器模型。代理模型试图模仿底层模型的行为,同时降低其复杂性。这是通过在特定实例附近训练一个线性模型来实现的。用户可以查看这个新创建的玻璃盒模型,以了解黑盒模型对这个邻域或预测子集的输出。因此,LIME 可以解释黑盒模型的单个预测。您可以在其开源版本中了解更多关于 LIME 的信息(
github.com/marcotcr/lime)。
这些是您可以用来解释黑盒模型的技术。为了稍微缓解玻璃盒模型的情况,微软研究院正在开发一个名为可解释提升机(EBM)的机器学习模型,它在准确度上与梯度提升相当,同时仍然完全可解释。他们的原始论文可以在arxiv.org/abs/2106.09680找到。
要尝试这些解释器,您可以直接在项目中使用这些包,或者您可以使用来自 Azure ML SDK 的azureml-interpret包(docs.microsoft.com/en-us/python/api/azureml-interpret)。这个包为您提供了访问Interpret Community SDK(github.com/interpretml/interpret-community)的权限。您可以阅读该包上可用的解释器。
如果你想尝试一下,可以查看以下指南:docs.microsoft.com/en-us/azure/machine-learning/how-to-machine-learning-interpretability-aml。当你在这本书的所有动手练习中查看 Azure Machine Learning 工作室页面时,你可能已经注意到训练运行和模型中有一个名为解释的标签。当你使用这个包时,你可以将解释器的结果添加到训练运行中,并在之后在线查看视觉效果。
想要进一步阅读,可以查看InterpretML项目(interpret.ml/docs/intro.html),该项目提供了不同类型解释器的概述。
现在我们已经了解了如何解释我们模型的结果,让我们来看看公平性。
模型训练中的公平性
分析模型公平性的主要工具之一称为Fairlearn(fairlearn.org/)。为了定义模型是否公平,Fairlearn 包中的算法和指标寻找两种可能造成的损害,如下所示:
-
分配损害:一个保留机会、资源或信息的模型或系统。这符合我们之前的例子,其中我们讨论了一个 ML 系统向个人发放贷款。
-
服务质量损害:一个不保留任何东西但对待不同群体行为不同的模型或系统。
为了评估给定模型的公平性,使用了两种结构,评估指标和缓解算法。这些可以按以下方式分类:
-
评估指标:可以通过比较多个模型为单个模型计算指标,也可以为通过缓解算法创建的模型计算指标。它们包括从计算模型召回率等简单指标到添加分组信息以分析模型结果。更多信息请参阅
fairlearn.org/main/user_guide/assessment.html。 -
减少算法:这些算法在评估后从重新加权的训练数据集中构建一个新的标准黑盒模型。用户可以通过不同的模型运行来调整,以找到准确性和公平性之间的最佳权衡。更多信息请参阅
fairlearn.org/main/user_guide/mitigation.html#reductions。 -
后处理算法:这些算法将原始模型和敏感特征结合起来计算应用于模型预测的转换。通过这个过程,我们避免了重新训练原始模型。
请注意,像 Fairlearn 这样的包仍在开发中。由于决定公平性不是一个简单的话题,不要仅依赖于这样的工具。当你思考可以引入的类型偏见时,要反思你所做的事情,并使用这些工具来获得更多见解。Fairlearn 的开发者指出了以下几点:
“公平性本质上是一个社会技术挑战。许多关于公平性的方面,如正义和正当程序,都无法通过定量公平指标来捕捉。此外,还有许多定量公平指标无法同时满足。我们的目标是使人类能够评估不同的缓解策略,并根据他们的场景做出适当的权衡。”
有关如何使用 Fairlearn 包与 Azure 机器学习结合使用以及如何上传您的结果的指南,请访问docs.microsoft.com/en-us/azure/machine-learning/how-to-machine-learning-fairness-aml。
最后,让我们学习如何使用 Azure 机器学习处理隐私和合规性。
处理 PII 数据和合规要求
随着欧洲的通用数据保护条例(GDPR)和加州的加州消费者隐私法案(CCPA)等立法的出台,企业现在处于困境。除了有明确的指导说明如何利用 PII 数据外,他们通常还要求存储涉及此数据的任何行动的审计跟踪,从用户到访问此数据的公司员工。
因此,拥有支持这一努力的工具非常重要。大多数 Azure 服务都设有安全措施来应对外部入侵者并构建多租户应用程序,帮助客户避免看到他人的 PII 数据。然而,在大多数组织中,系统管理员通常可以访问这些明文数据。对于构建机器学习模型的人来说也是如此。此外,Azure 上的数据库通常可以记录任何访问并建立审计跟踪以供审查。但机器学习建模管道或部署管道又如何呢?谁能在什么形式和什么时间点看到数据?
所有这些问题都需要得到解答。让我们看看一些可用的工具和在这个领域正在进行的研究:
-
差分隐私: 该机制用于向数据添加噪声或随机性,以使个人的数据无法识别。这样做,我们仍然可以在略微改变的数据集上构建一个准确模型。请注意,这并不是指明显的 PII 数据,例如你的姓名或电子邮件地址。为了让你思考一下:你很可能会直接通过你使用的浏览器版本和安装的浏览器插件被识别。这种方法在名为 SmartNoise (
github.com/opendp/smartnoise-core) 的软件包中实现,你可以在你的机器学习项目中使用它。有关此主题的更多信息,请参阅docs.microsoft.com/en-us/azure/machine-learning/concept-differential-privacy。 -
同态加密: 这允许在加密数据上执行计算,而不允许访问解密密钥。只需要用秘密密钥解密计算结果。到目前为止,即使使用加密数据并使用密钥解密,也相当麻烦,因为对 TB 级数据运行加密是耗时的。现在,这项由微软研究的技术,通过 Microsoft SEAL 项目 (
www.microsoft.com/en-us/research/project/microsoft-seal/) 提供使用。此外,你可以通过遵循docs.microsoft.com/en-us/azure/machine-learning/how-to-homomorphic-encryption-seal上的指南来学习如何使用这个方法与推理网络服务一起使用。 -
模型数据表: 这提供了记录机器学习资产及其生命周期的指南。为了符合法规并且工作得更加整洁,可以采用名为 关于机器学习 的指南 ABOUT ML (
partnershiponai.org/paper/about-ml-reference-document/)。在 Azure 机器学习环境中如何采用此指南的示例可以在此找到:github.com/microsoft/MLOps/blob/master/pytorch_with_datasheet/model_with_datasheet.ipynb。
请密切关注这些主题的发展,因为未能遵守这些法规可能会产生严重的后果。
正如你所见,我们在这个章节中讨论的所有包都还处于 alpha 或 beta 阶段,因为可解释性、公平性和隐私性在机器学习背景下相对较新。在过去十年中,机器学习更多的是一个研究课题,而不是实际的生产环境。如今,基于机器学习的解决方案已经融入了我们的日常生活。因此,我们需要退一步,开始思考我们是否可以不质疑其有效性就让机器为我们做决定。
因此,当你运行下一个注定要投入生产的 ML 项目时,将这些话题带入讨论,因为它们需要从一开始就得到处理。
摘要
在这一章中,我们通过涵盖数据、基础设施、监控、自动化、变更管理和伦理等方面,从更高的层次审视了一些内容。我们希望你在阅读这本书后,对这些话题的理解是合理的。
重要的是要理解,你的数据将控制和影响一切,因此,将数据作为你公司的一等公民是第一步重要的举措。雇佣一个数据副总裁并定义数据质量、血缘和可发现性的标准只是你可以采取的一些措施。
在自动化方面,我们看到自动化机器学习将在几年内统治世界。这个想法很简单:一个训练好的元模型在提出、训练、优化和堆叠模型以实现更高的预测性能方面将始终优于人类。这完全说得通。这只是另一个参数优化步骤,也包括模型架构。另一个有趣的思考是,自动化机器学习将为不熟悉机器学习的人提供真正的 MLaaS。也许 Excel 中会提供一个预测列,或者在 Power BI 中有一个机器学习转换步骤,这意味着普通的 Office 用户可以通过电子表格应用程序突然利用机器学习的力量。
我们还提到,在云中使用 PaaS 时,变化是不可避免的。这是因为 PaaS 解决方案旨在实施典型的客户解决方案,并推动你消费更多的基础设施服务。随着客户需求的变化,这些 PaaS 提供的产品也会随之变化。因此,一个很好的经验法则是不要过于依赖产品名称、UI 或 SDK 包。
最后,我们理解了在数据处理中伦理的重要性。我们讨论了构建可解释的模型、评估我们模型公平性以及如何保护个人数据不受我们自己和其他人侵害的话题。
我们希望你喜欢这本书,并学会了如何掌握机器学习和 Azure 机器学习。然而,这个兔子洞比这本书深得多。所以,继续学习吧,我们也会这样做。在社交媒体上联系我们,告诉我们你学到了什么,你喜欢什么,以及这本书中可以改进的地方。我们非常乐意听到你的反馈。
在那时之前,祝大家机器学习愉快!


通过网络(前向传播)。
和计算得到的
之间的损失。
和基础分布,尽管它是正确的。这也被称为假阳性结果或α 错误。
和其背后的分布,尽管
是正确的。这种错误也被称为假阴性结果或贝塔 错误。
的值上升时,向量
的值下降,反之亦然。
和
之间没有真正的交互作用。
的值上升时,向量
的值也上升,反之亦然。
浙公网安备 33010602011771号