MLFlow-大规模深度学习实践指南-全-
MLFlow 大规模深度学习实践指南(全)
原文:
annas-archive.org/md5/0801c480d77054d281bf58617eeac06d译者:飞龙
前言
从 2012 年 AlexNet 赢得大规模 ImageNet 竞赛,到 2018 年 BERT 预训练语言模型在多个自然语言处理(NLP)排行榜上名列前茅,现代深度学习(DL)在广泛的人工智能(AI)和机器学习(ML)社区中的革命持续进行。然而,将这些 DL 模型从离线实验转移到生产环境的挑战依然存在。这主要是因为缺乏一个统一的开源框架来支持 DL 完整生命周期的开发。本书将帮助你理解 DL 完整生命周期开发的全貌,并实现可从本地离线实验扩展到分布式环境和在线生产云的 DL 管道,重点是通过实践项目式学习,使用流行的开源 MLflow 框架支持 DL 过程的端到端实现。
本书从 DL 完整生命周期的概述和新兴的机器学习运维(MLOps)领域开始,提供了 DL 四大支柱(数据、模型、代码和可解释性)以及 MLflow 在这些领域中的作用的清晰图景。在第一章中,基于转移学习的基本 NLP 情感分析模型使用 PyTorch Lightning Flash 构建,并在接下来的章节中进一步开发、调优并部署到生产环境。从此开始,本书将一步步引导你理解 MLflow 实验的概念和使用模式,使用 MLflow 作为统一的框架来跟踪 DL 数据、代码与管道、模型、参数和指标的规模化管理。我们将在分布式执行环境中运行 DL 管道,确保可重现性和溯源追踪,并通过 Ray Tune、Optuna 和 HyperBand 对 DL 模型进行超参数优化(HPO)。我们还将构建一个多步骤 DL 推理管道,包括预处理和后处理步骤,使用 Ray Serve 和 AWS SageMaker 部署 DL 推理管道到生产环境,最后,提供一个使用SHapley 加法解释(SHAP)和 MLflow 集成的 DL 解释服务。
在本书的结尾,你将拥有从初始离线实验到最终部署和生产的深度学习(DL)管道构建的基础和实践经验,所有内容都在一个可重现和开源的框架中完成。在此过程中,你还将学习到与 DL 管道相关的独特挑战,以及我们如何通过实际且可扩展的解决方案克服这些挑战,例如使用多核 CPU、图形处理单元(GPU)、分布式和并行计算框架,以及云计算。
本书适合谁阅读
本书是为数据科学家、机器学习工程师和人工智能从业者编写的,旨在帮助他们掌握深度学习从构想到生产的完整生命周期,使用开源 MLflow 框架及相关工具,如 Ray Tune、SHAP 和 Ray Serve。本书中展示的可扩展、可复现且关注溯源的实现,确保您能够成功构建企业级深度学习管道。本书将为构建强大深度学习云应用程序的人员提供支持。
本书内容概述
第一章,深度学习生命周期与 MLOps 挑战,涵盖了深度学习完整生命周期的五个阶段,并使用迁移学习方法进行文本情感分类的第一个深度学习模型。它还定义了 MLOps 的概念,并介绍了其三个基础层次和四大支柱,以及 MLflow 在这些领域的作用。此外,还概述了深度学习在数据、模型、代码和可解释性方面面临的挑战。本章旨在将每个人带入相同的基础水平,并为本书其余部分的范围提供清晰的指导。
第二章,使用 MLflow 开始深度学习之旅,作为 MLflow 的入门教程和首个实践模块,快速设置基于本地文件系统的 MLflow 追踪服务器,或在 Databricks 上与远程管理的 MLflow 追踪服务器互动,并使用 MLflow 自动日志记录进行首个深度学习实验。本章还通过具体示例讲解了一些基础的 MLflow 概念,如实验、运行、实验和运行之间的元数据及其关系、代码追踪、模型日志记录和模型类型。特别地,我们强调实验应作为一等公民实体,能够弥合深度学习模型的离线与在线生产生命周期之间的差距。本章为 MLflow 的基础知识奠定了基础。
第三章,跟踪模型、参数和指标,涵盖了使用完整本地 MLflow 跟踪服务器的第一次深入学习模块。它从设置在 Docker Desktop 中运行的本地完整 MLflow 跟踪服务器开始,后端存储使用 MySQL,工件存储使用 MinIO。在实施跟踪之前,本章提供了基于开放源追踪模型词汇规范的开放源追踪框架,并提出了六种可以通过使用 MLflow 实现的溯源问题。接着,它提供了如何使用 MLflow 模型日志 API 和注册 API 来跟踪模型溯源、模型指标和参数的实践示例,无论是否启用自动日志。与其他典型的 MLflow API 教程不同,本章不仅仅提供使用 API 的指导,而是专注于我们如何成功地使用 MLflow 来回答溯源问题。在本章结束时,我们可以回答六个溯源问题中的四个,剩下的两个问题只能在拥有多步骤管道或部署到生产环境时回答,这些内容将在后续章节中涵盖。
第四章,跟踪代码和数据版本管理,涵盖了关于 MLflow 跟踪的第二个深入学习模块。它分析了在 ML/DL 项目中使用笔记本和管道的现有实践。它推荐使用 VS Code 笔记本,并展示了一个具体的深度学习笔记本示例,该示例可以在启用 MLflow 跟踪的情况下交互式或非交互式运行。它还建议使用 MLflow 的MLproject,通过 MLflow 的入口点和管道链实现多步骤的深度学习管道。为深度学习模型的训练和注册创建了一个三步深度学习管道。此外,它还展示了通过 MLflow 中的父子嵌套运行进行的管道级跟踪和单个步骤的跟踪。最后,它展示了如何使用 MLflow 跟踪公共和私有构建的 Python 库以及在Delta Lake中进行数据版本管理。
第五章,在不同环境中运行深度学习管道,涵盖了如何在不同环境中运行深度学习管道。首先介绍了在不同环境中执行深度学习管道的场景和要求。接着展示了如何使用 MLflow 的命令行界面(CLI)在四种场景中提交运行:本地运行本地代码、在 GitHub 上运行本地代码、在云端远程运行本地代码、以及在云端远程运行 GitHub 上的代码。MLflow 所支持的灵活性和可重现性在执行深度学习管道时,也为需要时的持续集成/持续部署(CI/CD)自动化提供了构建块。
第六章,大规模超参数调优运行,介绍了如何使用 MLflow 支持大规模的超参数优化(HPO),并利用最先进的 HPO 框架如 Ray Tune。首先回顾了深度学习流水线超参数的类型和挑战。然后,比对了三个 HPO 框架:Ray Tune、Optuna 和 HyperOpt,并对它们与 MLflow 的集成成熟度及优缺点进行了详细分析。接着,推荐并展示了如何使用 Ray Tune 与 MLflow 结合,对本书中迄今为止所讨论的深度学习模型进行超参数调优。此外,还介绍了如何切换到其他 HPO 搜索和调度算法,如 Optuna 和 HyperBand。这使得我们能够以一种具有成本效益且可扩展的方式,生产符合业务需求的高性能深度学习模型。
第七章,多步骤深度学习推理流水线,介绍了使用 MLflow 的自定义 Python 模型方法创建多步骤推理流水线的过程。首先概述了生产环境中四种推理工作流模式,在这些模式下,单一的训练模型通常不足以满足业务应用的需求,需要额外的预处理和后处理步骤。接着,提供了一个逐步指南,讲解如何实现一个多步骤推理流水线,该流水线将先前微调过的深度学习情感模型与语言检测、缓存以及额外的模型元数据结合起来。该推理流水线随后会作为一个通用的 MLflow PyFunc 模型进行日志记录,可以通过通用的 MLflow PyFunc 加载 API 加载。将推理流水线包装成 MLflow 模型为自动化和在同一 MLflow 框架内一致地管理模型流水线开辟了新天地。
第八章,大规模部署深度学习推理流水线,介绍了如何将深度学习推理流水线部署到不同的主机环境中以进行生产使用。首先概述了部署和托管环境的全景,包括大规模的批量推理和流式推理。接着,描述了不同的部署机制,如 MLflow 内置的模型服务工具、自定义部署插件以及像 Ray Serve 这样的通用模型服务框架。示例展示了如何使用 MLflow 的 Spark mlflow-ray-serve 部署批量推理流水线。接下来,提供了一个完整的逐步指南,讲解如何将深度学习推理流水线部署到托管的 AWS SageMaker 实例中用于生产环境。
第九章,深度学习可解释性基础,介绍了可解释性的基础概念,并探索了使用两个流行的可解释性工具。本章从概述可解释性的八个维度和可解释的人工智能(XAI)开始,然后提供了具体的学习示例,探索如何使用 SHAP 和 Transformers-interpret 工具箱进行 NLP 情感分析管道的应用。它强调,在开发深度学习应用时,可解释性应该被提升为一类重要的工件,因为在各类商业应用和领域中,对模型和数据解释的需求和期望正在不断增加。
第十章,使用 MLflow 实现深度学习可解释性,介绍了如何使用 MLflow 实现深度学习可解释性,并提供解释即服务(EaaS)。本章从概述 MLflow 当前支持的解释器和解释功能开始。具体来说,MLflow API 与 SHAP 的现有集成不支持大规模的深度学习可解释性。因此,本章提供了使用 MLflow 的工件日志记录 API 和PyFunc API 来实现的两种通用方法。文中提供了实现 SHAP 解释的示例,该解释将 SHAP 值以条形图的形式记录到 MLflow 跟踪服务器的工件存储中。SHAP 解释器可以作为 MLflow Python 模型进行日志记录,然后作为 Spark UDF 批处理解释或作为 Web 服务进行在线 EaaS 加载。这为在统一的 MLflow 框架内实现可解释性提供了最大的灵活性。
如何充分利用本书
本书中的大多数代码可以使用开源的 MLflow 工具进行实现和执行,少数情况需要 14 天的完整 Databricks 试用(可以在databricks.com/try-databricks注册)和一个 AWS 免费账户(可以在aws.amazon.com/free/注册)。以下列出了本书中涵盖的一些主要软件包:
- 
MLflow 1.20.2 及以上版本
 - 
Python 3.8.10
 - 
Lightning-flash 0.5.0
 - 
Transformers 4.9.2
 - 
SHAP 0.40.0
 - 
PySpark 3.2.1
 - 
Ray[tune] 1.9.2
 - 
Optuna 2.10.0
 
本书中每一章的完整包依赖列在requirements.txt文件或本书 GitHub 仓库中的conda.yaml文件中。所有代码已在 macOS 或 Linux 环境下成功测试。如果你是 Microsoft Windows 用户,建议安装WSL2以运行本书中提供的 bash 脚本:www.windowscentral.com/how-install-wsl2-windows-10。已知问题是 MLflow CLI 在 Microsoft Windows 命令行中无法正常工作。
从本书的 第三章**, 模型、参数和指标的追踪 开始,你还需要安装 Docker Desktop(www.docker.com/products/docker-desktop/)来设置一个完整的本地 MLflow 跟踪服务器,以便执行本书中的代码。第八章, 在大规模上部署深度学习推理管道 需要 AWS SageMaker 进行云部署示例。本书中使用的是 VS Code 版本 1.60 或更高版本 (code.visualstudio.com/updates/v1_60),作为 集成开发环境 (IDE)。本书中的虚拟环境创建和激活使用的是 Miniconda 版本 4.10.3 或更高版本 (docs.conda.io/en/latest/miniconda.html)。
如果你使用的是本书的电子版本,建议你自己输入代码或从本书的 GitHub 仓库中获取代码(下一个部分会提供链接)。这样可以帮助你避免因复制粘贴代码而可能出现的错误。
最后,为了充分利用本书的内容,你应该具有 Python 编程经验,并且对常用的机器学习和数据处理库(如 pandas 和 PySpark)有基本了解。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件,链接为 github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow。如果代码有更新,它将在 GitHub 仓库中同步更新。
我们还提供了来自我们丰富图书和视频目录的其他代码包,详情请访问 github.com/PacktPublishing/。赶紧去看看吧!
下载彩色图像
我们还提供了 PDF 文件,包含本书中使用的屏幕截图和图表的彩色图像。你可以在这里下载:static.packt-cdn.com/downloads/9781803241333_ColorImages.pdf。
使用的约定
本书中使用了许多文本约定。
Code in text:表示文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号。示例如下:“为了学习目的,我们在 GitHub 仓库的 chapter08 文件夹下提供了两个示例 mlruns 工件和 huggingface 缓存文件夹。”
代码块按如下方式书写:
client = boto3.client('sagemaker-runtime') 
response = client.invoke_endpoint(
        EndpointName=app_name, 
        ContentType=content_type,
        Accept=accept,
        Body=payload
        )
当我们希望引起你对代码块中特定部分的注意时,相关行或项目将加粗显示:
loaded_model = mlflow.pyfunc.spark_udf(
    spark,
    model_uri=logged_model, 
    result_type=StringType())
任何命令行输入或输出都按如下方式书写:
mlflow models serve -m models:/inference_pipeline_model/6
粗体:表示一个新术语、重要词汇或屏幕上看到的词语。例如,菜单或对话框中的词语以 粗体 显示。举个例子:“要执行这个单元格中的代码,你只需点击右上角下拉菜单中的 Run Cell。”
提示或重要注意事项
以这种形式出现。
联系我们
我们始终欢迎读者的反馈。
常见反馈:如果你对本书的任何内容有疑问,请通过电子邮件联系我们:customercare@packtpub.com,并在邮件主题中注明书名。
勘误:虽然我们已尽力确保内容的准确性,但错误难免发生。如果你在本书中发现错误,我们将不胜感激,如果你能将其报告给我们。请访问 www.packtpub.com/support/errata 并填写表单。
盗版:如果你在互联网上发现我们的作品以任何形式的非法复制,我们将非常感激你能提供该材料的地址或网站名称。请通过 copyright@packt.com 与我们联系,并提供链接。
如果你有兴趣成为作者:如果你在某个领域有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com。
分享你的想法
一旦你阅读了 Practical Deep Learning at Scale with MLflow,我们希望听到你的反馈!请点击这里,直接访问本书的亚马逊评论页面,并分享你的看法。
你的评论对我们和技术社区都非常重要,它将帮助我们确保提供优质的内容。
第一部分 - 深度学习挑战与 MLflow 概述
在这一部分,我们将学习深度学习(DL)完整生命周期的五个阶段,并了解新兴的机器学习运维(MLOps)领域以及 MLflow 的作用。我们将概述深度学习过程中的四大支柱:数据、模型、代码和可解释性所面临的挑战。接下来,我们将学习如何设置一个基本的本地 MLflow 开发环境,并运行我们的第一个 MLflow 实验,实验内容是基于PyTorch Lightning Flash构建的自然语言处理(NLP)模型。最后,我们将通过这个第一个 MLflow 实验示例,解释 MLflow 的基本概念,如实验、运行等。
本节包括以下章节:
- 
第一章,深度学习生命周期与 MLOps 挑战
 - 
第二章,开始使用 MLflow 进行深度学习
 
第一章:第一章:深度学习生命周期与 MLOps 挑战
近年来,深度学习(DL)在解决实际业务、工业和科学问题方面取得了巨大成功,特别是在自然语言处理(NLP)、图像、视频、语音识别和对话理解等任务中。尽管这些领域的研究取得了巨大的进展,但将这些深度学习模型从离线实验推广到生产环境,并持续改进模型以提供可持续的价值,仍然是一个挑战。例如,VentureBeat 最近的一篇文章(venturebeat.com/2019/07/19/why-do-87-of-data-science-projects-never-make-it-into-production/)发现,87%的数据科学项目从未进入生产。虽然这种低生产率可能有商业原因,但一个主要的因素是缺乏实验管理和成熟的模型生产与反馈平台所带来的困难。
本章将帮助我们理解这些挑战,并通过学习在深度学习模型开发的整个生命周期中常用的概念、步骤和组件来弥合这些差距。此外,我们还将学习一个新兴领域——机器学习运维(MLOps)的挑战,MLOps 旨在标准化和自动化机器学习生命周期的开发、部署和运营。深入理解这些挑战将激励我们学习本书其余部分中介绍的使用 MLflow 的技能,MLflow 是一个开源的机器学习全生命周期平台。采用 MLOps 最佳实践的商业价值有很多,其中包括更快的模型派生产品功能的市场推出、更低的运营成本、更灵活的 A/B 测试以及更具战略性的决策制定,从而最终改善客户体验。在本章结束时,我们将了解 MLflow 在 MLOps 四大支柱(即数据、模型、代码和可解释性)中所发挥的关键作用,实施我们的第一个工作深度学习模型,并清晰地理解数据、模型、代码和可解释性在深度学习中的挑战。
本章将覆盖以下主要内容:
- 
理解深度学习(DL)生命周期和 MLOps 挑战
 - 
理解深度学习(DL)数据挑战
 - 
理解深度学习(DL)模型的挑战
 - 
理解深度学习(DL)代码挑战
 - 
理解深度学习(DL)可解释性挑战
 
技术要求
本书所有的代码示例都可以在以下 GitHub 网址找到:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow。
你需要在开发环境中安装 Miniconda(docs.conda.io/en/latest/miniconda.html)。在本章中,我们将逐步介绍安装 PyTorch lightning-flash库的过程(github.com/PyTorchLightning/lightning-flash),该库可以用于在实现一个基本的深度学习情感分类器部分中构建我们的第一个深度学习模型。或者,你也可以注册一个免费的 Databricks 社区版账户(community.cloud.databricks.com/login.html),使用 GPU 集群和笔记本来进行本书中描述的模型开发。
此外,如果你是微软 Windows 用户,我们建议你安装 WSL2(www.windowscentral.com/how-install-wsl2-windows-10),这样你就能拥有一个 Linux 环境来运行本书中的命令行。
理解深度学习生命周期和 MLOps 的挑战
目前,成功部署到生产中的大多数深度学习模型主要遵循以下两个步骤:
- 
自监督学习:指的是在数据丰富的领域中进行模型的预训练,该领域不需要标签数据。这一步生成了一个预训练模型,也称为基础模型,例如,BERT、GPT-3 用于自然语言处理(NLP),以及 VGG-NETS 用于计算机视觉。
 - 
迁移学习:指的是在特定预测任务中对预训练模型进行微调,例如文本情感分类,这需要有标签的训练数据。
 
一个开创性且成功的深度学习(DL)模型生产实例是买家情感分析模型,该模型基于 BERT 构建,用于分类销售互动邮件信息,提供对买家情感和信号的细致洞察,超越简单的活动指标,如回复、点击和打开率(www.prnewswire.com/news-releases/outreach-unveils-groundbreaking-ai-powered-buyer-sentiment-analysis-transforming-sales-engagement-301188622.html)。关于其工作原理有不同的变体,但在本书中,我们将主要聚焦于迁移学习范式,开发和部署深度学习模型,因为它展示了一个实用的深度学习生命周期。
让我们通过一个例子来了解典型的核心深度学习开发范式。例如,2018 年底发布的流行 BERT 模型(BERT 模型的基础版本可以在huggingface.co/bert-base-uncased找到)最初是在来自 BookCorpus 的 11,000 多本书籍以及整个英文维基百科的原始文本上进行预训练的(没有人工标注)。然后,使用该预训练语言模型对许多下游自然语言处理任务进行了微调,如文本分类和情感分析,应用于不同的领域,比如通过使用标注的电影评论数据进行电影评论分类(huggingface.co/datasets/imdb)。需要注意的是,有时为了提高最终模型在准确度方面的性能,可能需要使用无标签数据在应用领域内进一步预训练基础模型(例如 BERT),然后再进行微调。这个核心的深度学习开发范式如图 1.1所示:

图 1.1 – 一个典型的核心深度学习开发范式
注意,虽然图 1.1代表了一个常见的开发范式,但并不是所有的步骤在特定的应用场景中都是必要的。例如,你可能只需要使用公开的预训练深度学习模型和你标注好的应用特定数据进行微调。因此,你不需要进行自己的预训练或使用无标签数据进一步预训练,因为其他人或组织已经为你完成了预训练步骤。
深度学习与传统机器学习
与传统的机器学习模型开发不同,通常需要进行特征工程步骤,将原始数据提取并转化为特征,以训练如决策树或逻辑回归这样的机器学习模型,深度学习能够自动学习特征,这一点对于建模非结构化数据(如文本、图像、视频、音频和语音)尤其有吸引力。由于这一特点,深度学习也被称为表征学习。除此之外,深度学习通常是数据密集型和计算密集型的,需要图形处理单元(GPUs)、张量处理单元(TPU)或其他类型的计算硬件加速器来进行大规模训练和推理。相比传统机器学习模型,深度学习模型的可解释性也更难实现,尽管近期的进展已经使得这一点成为可能。
实现一个基本的深度学习情感分类器
为了设置基本的深度学习情感分类器开发环境,你需要在本地创建一个虚拟环境。假设你已经有了dl_model,并且安装了 PyTorch 的lightning-flash包,这样模型就能被构建:
conda create -n dl_model python==3.8.10
conda activate dl_model
pip install lightning-flash[all]
根据你本地机器的内存情况,前面的命令可能需要大约 10 分钟才能完成。你可以通过运行以下命令来验证安装是否成功:
conda list | grep lightning
如果你看到类似以下的输出,说明安装成功:
lightning-bolts     0.3.4                    pypi_0    pypi
lightning-flash     0.5.0                    pypi_0    pypi
pytorch-lightning   1.4.4                    pypi_0    pypi
现在,你已经准备好构建你的第一个深度学习模型了!
要开始构建一个深度学习模型,请完成以下步骤:
- 
导入必要的
torch和flash库,并从flash子包中导入download_data、TextClassificationData和TextClassifier:import torch import flash from flash.core.data.utils import download_data from flash.text import TextClassificationData, TextClassifier - 
为了进行微调,使用
download_data下载imdb.zip文件,它是来自train.csv的公共领域二元情感分类(正面/负面)数据集。 - 
valid.csv - 
test.csv 
每个文件包含两列:review和sentiment。然后,我们使用TextClassificationData.from_csv声明一个datamodule变量,将review分配给input_fields,将sentiment分配给target_fields。此外,它还将train.csv文件分配给train_file,将valid.csv文件分配给val_file,将test.csv文件分配给test_file属性:
download_data("https://pl-flash-data.s3.amazonaws.com/imdb.zip", "./data/")
datamodule = TextClassificationData.from_csv(
    input_fields="review",
    target_fields="sentiment",
    train_file="data/imdb/train.csv",
    val_file="data/imdb/valid.csv",
    test_file="data/imdb/test.csv"
)
- 
一旦我们有了数据,我们现在可以使用基础模型进行微调。首先,通过调用
TextClassifier并将骨干网络(backbone)设置为prajjwal1/bert-tiny来声明classifier_model(prajjwal1/bert-tiny是一个更小的类似 BERT 的预训练模型,位于 Hugging Face 模型库中:huggingface.co/prajjwal1/bert-tiny)。这意味着我们的模型将基于bert-tiny模型。 - 
下一步是通过定义我们希望运行的轮数(epochs)和希望使用的 GPU 数量来设置训练器。这里,
torch.cuda.device_count()将返回0(没有 GPU)或1到N,其中N是你运行环境中最大可用 GPU 的数量。现在,我们已经准备好调用trainer.finetune来训练一个二元情感分类器,使用的是 IMDb 数据集:classifier_model = TextClassifier(backbone="prajjwal1/bert-tiny", num_classes=datamodule.num_classes) trainer = flash.Trainer(max_epochs=3, gpus=torch.cuda.device_count()) trainer.max_epochs=1 if you simply want to get a basic version of the sentiment classifier quickly. - 
微调步骤完成后,我们将通过运行
trainer.test()来测试模型的准确性:trainer.test() 
测试结果的输出应该类似于以下屏幕截图,表明最终模型的准确率大约为 52%:

图 1.2 – 我们的第一个深度学习模型的测试结果
前面图表中显示的测试结果表明,我们有了模型的基本版本,因为我们只对基础模型进行了三轮微调,并且没有使用诸如超参数调优或更好的微调策略等高级技术。然而,这是一个很大的成就,因为你现在已经掌握了核心深度学习模型的工作原理!我们将在本书的后续章节中探索更高级的模型训练技术。
理解深度学习的完整生命周期开发
到目前为止,你应该已经准备好第一个深度学习模型,并应该为此感到自豪。现在,让我们一起探索完整的深度学习生命周期,充分理解其概念、组成部分和挑战。
你可能已经了解到,核心的深度学习开发范式围绕着三个关键要素:数据、模型和代码。除此之外,可解释性是另一个在许多关键任务应用场景中所需的重要要素,如医学诊断、金融行业以及刑事司法决策。由于深度学习通常被视为“黑盒”,因此在部署到生产环境之前和之后,提供可解释性越来越成为一个关键要求。
请注意,如果我们仍在试图通过实验室环境中的数据集确定哪个模型有效,那么图 1.1 仍然被视为离线实验。即使在这种离线实验环境中,情况也会迅速变得复杂。此外,我们希望了解并追踪我们已经或尚未执行过哪些实验,以便避免浪费时间重复相同的实验,无论我们使用了哪些参数和数据集,或者对于特定模型,我们采用了什么样的指标。一旦我们拥有一个足够适用于用例和客户场景的模型,复杂性将增加,因为我们需要一种持续部署和更新模型的方法,监控模型和数据的漂移,并在必要时重新训练模型。当需要大规模训练、部署、监控和可解释性时,这种复杂性会进一步增加。
让我们来看看深度学习生命周期的样子(见图 1.3)。有五个阶段:
- 
数据收集、清洗和注释/标注。
 - 
模型开发(也称为离线实验)。图 1.1 中的核心深度学习开发范式被视为模型开发阶段的一部分,而该阶段本身可以是一个迭代过程。
 - 
模型部署与生产环境中的服务。
 - 
模型验证与 A/B 测试(也称为在线实验;通常在生产环境中进行)。
 - 
在生产过程中进行监控和反馈数据收集。
 
图 1.3 提供了一个图示,展示了这是一个持续发展的深度学习模型开发周期:

图 1.3 – 完整的深度学习开发生命周期
除此之外,我们还想指出,正如图 1.3 所示,这五个阶段的支柱实际上围绕着四个要素:数据、模型、代码和可解释性。我们将在接下来的章节中探讨这些要素在生命周期中的挑战。然而,首先,让我们了解并理解 MLOps,这是一个不断发展的平台概念和框架,支持机器学习的完整生命周期。这将帮助我们在更宏观的背景下理解这些挑战。
理解 MLOps 挑战
MLOps 与 DevOps 有一些联系,DevOps 使用一系列技术栈和标准操作流程来进行软件开发和部署,并结合 IT 运维。与传统软件开发不同,机器学习(ML)尤其是深度学习(DL)代表着一种新的软件开发范式——软件 2.0(karpathy.medium.com/software-2-0-a64152b37c35)。软件 2.0 的关键区别在于,软件的行为不仅依赖于人们已经理解的编程语言代码(这是软件 1.0 的特点),还依赖于神经网络中学习到的权重,而这些权重很难写成代码。换句话说,代码、数据和模型的整合是不可分割的,必须共同管理。因此,MLOps 正在发展,并且仍在不断演变,以适应这一新的软件 2.0 范式。本书中,MLOps 被定义为一个由三大基础层和四大支柱组成的运营自动化平台。它们如下所示:
- 
下面是三大基础层:
- 
基础设施管理与自动化
 - 
应用生命周期管理与持续集成与持续部署(CI/CD)
 - 
服务系统可观测性
 
 - 
 - 
下面是四大支柱:
- 
数据可观测性与管理
 - 
模型可观测性与生命周期管理
 - 
可解释性与人工智能(AI)可观测性
 - 
代码可复现性与可观测性
 
 - 
 
此外,我们还将解释 MLflow 在这些 MLOps 层和支柱中的角色,以便我们清楚地了解 MLflow 如何在整体上构建 MLOps 层:
- 
基础设施管理与自动化:这包括但不限于用于自动化容器编排的Kubernetes(也称为 k8s)和用于管理数百个云服务及访问控制的Terraform(常用于此)。这些工具已适应于管理已部署为服务端点的机器学习(ML)和深度学习(DL)应用程序。书中并不专注于这些基础设施层;相反,我们将专注于如何使用 MLflow 提供的功能来部署训练好的深度学习模型。
 - 
应用生命周期管理和 CI/CD:这包括但不限于用于虚拟化的Docker容器、如 Kubernetes 这样的容器生命周期管理工具,以及用于CI和CD的CircleCI或Concourse。通常,CI 指的是每当 GitHub 仓库中的代码或模型发生变化时,一系列自动化测试将被触发,以确保不会引入破坏性更改。一旦这些测试通过,新的更改将自动发布为新包的一部分。这将触发一个新的部署过程(CD),将新包部署到生产环境中(通常,这将包括人为批准作为安全门)。请注意,这些工具并非 ML 应用程序独有,而是已被调整为适应 ML 和 DL 应用,尤其是当我们需要 GPU 和分布式集群来训练和测试 DL 模型时。本书中,我们不会专注于这些工具,但会在需要时提及集成点或示例。
 - 
服务系统可观察性:这主要是用于监控硬件/集群/CPU/内存/存储、操作系统、服务可用性、延迟和吞吐量。这包括如Grafana、Datadog等工具。同样,这些工具并非 ML 和 DL 应用独有,也不是本书的重点。
 - 
数据可观察性和管理:这在传统的 DevOps 世界中表现得相对不足,但在 MLOps 中变得非常重要,因为数据在 ML/DL 模型的整个生命周期中至关重要。这包括数据质量监控、异常检测、数据漂移和概念漂移检测、偏差检测、安全和合规的数据共享、数据来源追踪和版本管理等。这一领域适用于 ML 和 DL 应用的工具栈仍在不断涌现。一些例子包括DataFold(
www.datafold.com/)和Databand(databand.ai/open-source/)。数据管理中的一个最新发展是一个统一的湖仓架构和实现,称为Delta Lake(delta.io),可用于 ML 数据管理。MLflow 与 Delta Lake 有原生集成点,我们将在本书中介绍该集成。 - 
模型可观察性和生命周期管理:这是 ML/DL 模型特有的,且由于 MLflow 的兴起,这一功能直到最近才得以广泛应用。这包括模型训练、测试、版本管理、注册、部署、序列化、模型漂移监控等工具。我们将学习 MLflow 在这一领域所提供的令人兴奋的能力。值得注意的是,一旦将 CI/CD 工具与 MLflow 的训练/监控、用户反馈回路和人工标注结合起来,我们就可以实现持续训练、持续测试和持续标注。MLflow 提供了基础能力,使得 MLOps 中的进一步自动化成为可能,尽管这种完全自动化并不是本书的重点。有兴趣的读者可以在本章末尾找到相关参考资料,以便深入探索这一领域。
 - 
可解释性和 AI 可观察性:这是 ML/DL 模型特有的,尤其对 DL 模型而言尤为重要,因为传统上,DL 模型被视为“黑箱”。理解模型为何给出某些预测,对于社会影响深远的应用至关重要。例如,在医疗、金融、司法和许多人机协作的决策支持应用中,如民用和军事应急响应,对可解释性的需求日益增加。MLflow 与一种叫做 SHAP 的流行可解释性框架原生集成,本书将详细介绍该框架。
 - 
代码可复现性和可观察性:这并不完全是 ML/DL 应用程序所特有的。然而,DL 模型面临一些特殊挑战,因为 DL 代码框架种类繁多,且复现模型的需求不仅仅依赖于代码本身(我们还需要数据和执行环境,如 GPU 集群)。此外,笔记本通常用于模型的开发和生产。如何管理笔记本与模型运行之间的关系非常重要。通常,GitHub 被用于管理代码库;然而,我们需要以可复现的方式构建 ML 项目代码,无论是在本地(例如本地笔记本电脑)还是远程(例如,在 Databricks 的 GPU 集群中)。MLflow 提供了这一能力,允许已经编写的 DL 项目在任何地方运行,无论是在离线实验环境中,还是在在线生产环境中。本书将介绍 MLflow 的 MLproject 功能。
 
总结来说,MLflow 在 MLOps 中扮演着至关重要且基础性的角色。它填补了 DevOps 传统上未覆盖的空白,因此是本书的重点。下图(图 1.4)展示了 MLflow 在不断发展的 MLOps 世界中所扮演的核心角色:

图 1.4 – MLOps 的三层结构和四大支柱,以及 MLflow 的作用
虽然底部的两个层和最上层在许多软件开发和部署过程中是常见的,但中间的四个支柱要么完全独特于机器学习/深度学习应用,要么部分独特于机器学习/深度学习应用。MLflow 在 MLOps 的这四个支柱中起着至关重要的作用。本书将帮助你自信地应用 MLflow 解决这四个支柱的问题,同时为你提供进一步集成其他工具的能力,结合图 1.4所示的 MLOps 层,依据你的场景需求实现全自动化。
理解深度学习数据挑战
在本节中,我们将讨论深度学习生命周期各个阶段的数据挑战,如图 1.3所示。本质上,深度学习是数据中心的人工智能,与符号人工智能不同,符号人工智能可以在没有大量数据的情况下利用人类知识。深度学习中的数据挑战贯穿整个生命周期的所有阶段:
- 
数据收集/清洗/标注:深度学习的首个成功之一始于ImageNet (
www.image-net.org/),该平台收集并根据 WordNet 数据库中的英文名词对数百万张图像进行了标注 (wordnet.princeton.edu/)。这促成了预训练深度学习模型在计算机视觉领域的成功发展,如 VGG-NETS (pytorch.org/hub/pytorch_vision_vgg/),该模型能够执行最先进的图像分类,并广泛应用于工业和商业领域。这种大规模数据收集和标注的主要挑战是未知的偏差,这在这一过程中很难测量 (venturebeat.com/2020/11/03/researchers-show-that-computer-vision-algorithms-pretrained-on-imagenet-exhibit-multiple-distressing-biases/)。另一个例子是销售参与平台Outreach (www.outreach.io/),我们可以在其中分类潜在买家的情绪。例如,我们可以从收集 100 个付费组织的电子邮件信息开始,以训练一个深度学习模型。接下来,我们需要收集更多组织的电子邮件信息,可能是出于准确性要求或扩展语言覆盖范围(例如从仅限英语到其他语言,如德语和法语)。这些反复的数据收集和标注会生成大量的数据集。通常,我们会仅仅用硬编码的版本号为数据集命名,并将其作为数据集文件名的一部分,例如以下所示:MyCoolAnnotatedData-v1.0.csv MyCoolAnnotatedData-v2.0.csv MyCoolAnnotatedData-v3.0.csv MyCoolAnnotatedData-v4.0.csv 
这似乎有效,直到由于需要修正注释错误或因客户流失而移除电子邮件消息时,某些 vX.0 数据集需要更改。如果我们需要将多个数据集结合起来,或者进行一些数据清洗和转换以训练一个新的深度学习模型,怎么办?如果我们需要实施数据增强来人工生成一些数据集呢?显然,简单地更改文件名既不可扩展也不可持续。
- 
模型开发:我们需要理解,用于训练/预训练深度学习模型的数据中的偏差会在应用模型时反映到预测中。虽然本书不会专注于消除数据偏差,但我们必须在训练和服务深度学习模型时,实施数据版本控制和数据来源管理,将其作为首要的构建模块,这样我们才能跟踪所有模型实验。在针对我们的使用场景对预训练模型进行微调时,正如我们之前所做的那样,我们还需要跟踪微调数据集的版本。在我们之前的例子中,我们使用了 BERT 模型的一个变种来微调 IMDb 评论数据。虽然在第一个例子中我们并没有关心数据的版本或来源,但对于实际应用来说,这是非常重要的。总的来说,深度学习模型需要通过一种可扩展的方法与特定版本的数据集关联。本书将提供有关此主题的解决方案。
 - 
模型部署与线上服务:这是将模型部署到生产环境中以服务在线流量的过程。深度学习模型的服务延迟在这一阶段尤为重要,值得收集。这可能使你能够调整用于推理的硬件环境。
 - 
模型验证与 A/B 测试:我们在这一阶段收集的数据主要是在线实验环境中的用户行为指标(
www.slideshare.net/pavel/ab-testing-ai-global-artificial-intelligence-conference-2019)。还需要对在线数据流量进行特征分析,以便了解模型输入在离线实验与在线实验之间是否存在统计差异。只有通过 A/B 测试并验证模型确实在用户行为指标方面优于之前的版本,我们才会将其推向生产环境,供所有用户使用。 - 
监控与反馈循环:在这一阶段,需要注意的是,数据需要持续收集,以便检测数据漂移和概念漂移。例如,在前面讨论的买家情绪分类案例中,如果买家开始使用训练数据中未出现的术语,模型的性能可能会下降。
 
总结来说,数据跟踪和可观测性是深度学习(DL)生命周期各个阶段中的主要挑战。
理解深度学习模型的挑战
在本节中,我们将讨论深度学习(DL)模型的挑战。让我们看看深度学习生命周期各阶段的挑战,如图 1.3 所示:
- 
数据收集/清理/标注:虽然数据挑战已经被提到,但将数据与目标模型关联的挑战仍然存在。MLflow 与 Delta Lake 原生集成,以便任何训练过的模型都可以追溯到 Delta Lake 中的特定版本。
 - 
pickle(github.com/cloudpipe/cloudpickle) 通常用于序列化用 Python 编写的模型。然而,TorchScript (pytorch.org/docs/stable/jit.html) 目前在 PyTorch 模型中表现出色。此外,Open Neural Network Exchange 或 ONNX (onnx.ai/) 尝试提供更具框架无关性的深度学习序列化。最后,我们需要记录序列化的模型并注册该模型,以便跟踪模型版本。MLflow 是第一个克服这些挑战的开源工具之一。 - 
生产环境中的模型部署和服务:一个易于使用的模型部署工具,能够与模型注册表进行连接,是一项挑战。MLflow 可以帮助解决这个问题,允许你加载模型进行生产部署,并完整追踪模型的来源。
 - 
模型验证和 A/B 测试:在在线验证和实验过程中,需要验证模型性能并收集用户行为指标。这样我们可以轻松回滚或重新部署特定版本的模型。模型注册表对大规模在线模型生产验证和实验至关重要。
 - 
监控和反馈回路:模型漂移和性能退化是一个现实的挑战。需要持续监控生产环境中模型的性能。反馈数据可用于决定是否需要重新训练模型。
 
总之,深度学习模型在整个生命周期中的挑战是独特的。同样值得指出的是,一个能够帮助模型开发与在线生产之间反复切换的通用框架至关重要,因为我们不希望仅仅因为执行环境不同就使用不同的工具。MLflow 提供了这个统一的框架来弥合这些差距。
理解深度学习代码的挑战
在本节中,我们将讨论深度学习代码的挑战。让我们看看这些代码挑战是如何在图 1.3所描述的各个阶段中表现出来的。在本节中,在深度学习开发的背景下,代码指的是用某些编程语言(如 Python)编写的数据处理和实现的源代码,而模型指的是以序列化格式(例如 pickle、TorchScript 或 ONNX)表示的模型逻辑和架构:
- 
数据收集/清洗/标注:尽管数据是这一阶段的核心,但执行查询、提取/转换/加载(ETL)以及数据清洗和增强的代码也至关重要。我们无法将模型的开发与为模型提供数据流的数据管道分离开来。因此,实施 ETL 的数据管道需要作为离线实验和在线生产中的集成步骤之一来对待。一个常见的错误是,我们在离线实验中使用不同的数据 ETL 和清洗管道,然后在在线生产中实现不同的数据 ETL/清洗管道,这可能会导致模型行为的不同。我们需要为数据管道进行版本控制并将其序列化,作为整个模型管道的一部分。MLflow 提供了几种方法来帮助我们实现这种多步骤管道。
 - 
模型开发:在离线实验中,除了不同版本的数据管道代码外,我们还可能有不同版本的笔记本,或者使用不同版本的深度学习库代码。在机器学习/深度学习的生命周期中,笔记本的使用尤其独特。需要对每次运行跟踪哪个模型结果是由哪个笔记本/模型管道/数据管道产生的。MLflow 通过自动代码版本追踪和依赖关系管理来实现这一点。此外,不同运行环境中的代码可复现性对深度学习模型来说也具有独特性,因为深度学习模型通常需要硬件加速器,如 GPU 或 TPU。无论是在本地运行,还是在 CPU 或 GPU 环境中远程运行的灵活性都非常重要。MLflow 提供了一种轻量级的方法来组织机器学习项目,从而使得代码可以编写一次并在任何地方运行。
 - 
模型部署与生产服务:当模型服务生产流量时,任何错误都需要追溯到模型和代码。因此,追踪代码来源至关重要。同时,跟踪特定版本模型的所有依赖库版本也同样至关重要。
 - 
模型验证与 A/B 测试:在线实验可能会使用不同版本的模型和不同的数据源。调试任何实验时,不仅需要知道使用了哪个模型,还需要知道哪些代码被用于生成该模型。
 - 
监控与反馈循环:这一阶段与之前的阶段在代码挑战上相似,我们需要知道模型降级是由于代码错误还是模型和数据漂移。监控管道需要收集所有数据和模型性能的指标。
 
总结来说,深度学习的代码挑战特别独特,因为深度学习框架仍在不断发展(例如,TensorFlow、PyTorch、Keras、Hugging Face和SparkNLP)。MLflow 提供了一个轻量级的框架,能够克服许多常见挑战,并能无缝地与多个深度学习框架进行对接。
理解深度学习可解释性挑战
在本节中,我们将讨论在图 1.3所描述的各个阶段,深度学习可解释性面临的挑战。越来越重要的是将可解释性视为一个 integral 且必要的机制,用于在整个模型生命周期中定义、测试、调试、验证和监控模型。尽早嵌入可解释性将使后续的模型验证和运维变得更加容易。此外,为了维持对机器学习/深度学习模型的持续信任,能够在模型上线后解释和调试模型是至关重要的:
- 数据收集/清洗/注释:正如我们所了解的,模型预测的可解释性至关重要。任何模型的可信度或偏见的根源都可以追溯到用于训练模型的数据。数据的可解释性仍然是一个新兴领域,但它至关重要。那么,在数据收集/清洗/注释阶段可能会发生什么问题并成为挑战呢?举个例子,假设我们有一个机器学习/深度学习模型,它的预测结果是关于一个贷款申请人是否会还款。如果收集到的数据中存在年龄和贷款还款结果之间的某些相关性,这将导致模型使用年龄作为预测变量。然而,基于个人年龄做出贷款决定是违法的,即使模型效果很好,这也是不允许的。所以,在数据收集过程中,可能会出现采样策略不足以代表某些子人群的问题,例如不同年龄组的贷款申请人。
 
一个子人群可能会有大量缺失字段,之后在数据清洗过程中被剔除。这可能会导致在数据清洗后的代表性不足。人工注释可能会偏向特权群体,并可能存在其他潜在的无意识偏见。一种叫做差异化影响的度量指标可以揭示数据中的隐藏偏见,它比较两个群体中获得积极结果的个体比例:一个是弱势群体,另一个是特权群体。如果弱势群体(例如,年龄>60 岁的人)获得积极结果(例如,贷款批准)的比例少于特权群体(例如,年龄<60 岁的人)比例的 80%,这就是根据当前常见行业标准(五分之四规则)违反了差异化影响。像Dataiku这样的工具可以帮助自动化差异化影响和子人群分析,找出可能因模型训练使用的数据而受到不公平或不同对待的群体。
- 模型开发:在离线实验阶段,模型的可解释性非常重要,不仅有助于理解模型为何以某种方式表现,还有助于模型选择,决定如果需要将其投入生产,应使用哪个模型。准确性可能不是选择优胜模型的唯一标准。这里有一些深度学习可解释性工具,例如 SHAP(请参阅图 1.5)。MLflow 与 SHAP 的集成提供了一种实现深度学习可解释性的方法:
 

图 1.5 – 使用深度学习模型时的 NLP 文本 SHAP 变量重要性图
图 1.5 显示了这个自然语言处理(NLP)模型预测结果的主要特征,第一个特征是词汇impressive,其次是rent。从本质上讲,这揭示了深度学习(DL)模型的“黑箱”,这大大增强了我们在生产环境中使用深度学习模型的信心。
- 
模型部署和生产环境中的服务:在生产阶段,如果能够轻松向用户提供模型预测的可解释性,不仅可以提高模型的可用性(用户友好性),还可以收集到更好的反馈数据,因为用户更愿意提供更有意义的反馈。一个好的可解释性解决方案应该为任何预测结果提供点级决策。这意味着我们应该能够回答为什么某个人的贷款被拒绝,以及这个拒绝与其他人(无论是相似还是不同的年龄组)相比如何。因此,挑战在于将可解释性作为发布新版本模型的一个必要部署标准。然而,与准确性指标不同,衡量可解释性作为分数或阈值是非常困难的,尽管某些基于案例的推理可以被应用并自动化。例如,如果我们有某些保留的测试用例,无论模型版本如何,我们都期望得到相同或类似的解释,那么我们可以将其作为一个部署标准。
 - 
模型验证与 A/B 测试:在在线实验和持续的生产模型验证过程中,我们需要可解释性来理解模型是否已应用于正确的数据,或预测是否可信。通常,机器学习(ML)/深度学习(DL)模型编码了复杂的非线性关系。在这一阶段,通常希望了解模型如何影响用户行为的度量指标(例如,购物网站上的更高转化率)。影响敏感性分析可以提供有关某些用户特征(例如用户的收入)对结果是否有正面或负面影响的见解。如果在这一阶段,我们发现由于某些原因,高收入导致贷款批准率下降或转化率降低,那么这一点应该被自动标记。然而,在模型验证和 A/B 测试过程中,自动化的敏感性分析仍然不广泛可用,且仍然是一个具有挑战性的问题。一些供应商,如 TruEra,提供了该领域的潜在解决方案。
 - 
监控与反馈循环:虽然模型性能指标和数据特征在这里非常重要,但可解释性可以为用户提供激励,促使他们提供有价值的反馈和用户行为指标,从而识别模型降级的驱动因素和原因(如果有的话)。正如我们所知,ML/DL 模型容易过拟合,且难以在训练数据之外做出良好的泛化。在模型生产监控过程中,一个重要的可解释性解决方案是衡量不同数据切分下特征重要性的变化(例如,COVID 之前与 COVID 之后)。这有助于数据科学家识别模型性能降级是由于数据变化(如统计分布变化)还是变量之间关系的变化(如概念漂移)。TruEra 提供的一个近期例子(
truera.com/machine-learning-explainability-is-just-the-beginning/)说明了由于 COVID 前后人们的年收入和贷款目的的变化,贷款模型改变了其预测行为。这种特征重要性变化的可解释性在模型生产监控阶段对于识别模型行为变化的根本原因非常有帮助。 
总结来说,DL 可解释性是一个重大挑战,仍需要持续的研究。然而,MLflow 与 SHAP 的集成现在为实际的 DL 应用提供了一个现成的工具,稍后我们将在本书的进阶章节中进行介绍。
总结
在本章开篇中,我们通过使用 PyTorch lightning-flash,遵循预训练加微调的核心深度学习(DL)开发范式,实施了我们的第一个 DL 模型,目标是进行文本情感分类。我们了解了 DL 的完整生命周期的五个阶段。我们定义了 MLOps 的概念,并介绍了其三个基础层和四个 ML/DL 支柱,其中 MLflow 在所有四个支柱(数据、模型、代码和可解释性)中都扮演着关键角色。最后,我们描述了 DL 中数据、模型、代码和可解释性方面的挑战。
在本章中获得的知识和第一个 DL 模型的经验后,我们现在已经准备好在接下来的章节中学习并实施 MLflow。在下一章中,我们将从启用 MLflow 自动记录功能的 DL 模型实现开始。
进一步阅读
为了进一步提升您的知识,敬请查阅以下资源和文档:
- 
关于基础模型的机遇与风险(斯坦福大学):
arxiv.org/abs/2108.07258 - 
MLOps: 不像它听起来那么无聊:
itnext.io/mlops-not-as-boring-as-it-sounds-eaebe73e3533 - 
人工智能正在推动软件 2.0……并且几乎无需人工干预:
www.datasciencecentral.com/profiles/blogs/ai-is-driving-software-2-0-with-minimal-human-intervention - 
MLOps:机器学习中的持续交付与自动化管道(Google):
cloud.google.com/architecture/mlops-continuous-delivery-and-automation-pipelines-in-machine-learning - 
深度学习开发周期(Salesforce):
metamind.readme.io/docs/deep-learning-dev-cycle - 
MLOps — 企业 AI 难题中的缺失环节:
www.forbes.com/sites/janakirammsv/2021/01/05/mlopsthe-missing-piece-in-the-enterprise-ai-puzzle/?sh=3d5c89dd24ad - 
MLOps:它是什么,为什么重要,以及如何实施:
neptune.ai/blog/mlops - 
可解释深度学习:初学者指南:
arxiv.org/abs/2004.14545 - 
机器学习的可解释性仅仅是开始:
truera.com/machine-learning-explainability-is-just-the-beginning/ - 
人工智能公平性 — 不公平影响消除器的解释:
towardsdatascience.com/ai-fairness-explanation-of-disparate-impact-remover-ce0da59451f1 - 
数据集的数据表:
arxiv.org/pdf/1803.09010.pdf 
第二章:第二章:使用 MLflow 开始进行深度学习
MLflow 的一个关键功能是支持机器学习(ML)实验管理。这一点至关重要,因为数据科学需要可重复性和可追溯性,这样一个深度学习(DL)模型就能使用相同的数据、代码和执行环境轻松重现。本章将帮助我们快速了解如何实施 DL 实验管理。我们将学习 MLflow 实验管理的概念和功能,设置 MLflow 开发环境,并使用 MLflow 完成我们的第一个 DL 实验。在本章结束时,我们将拥有一个运行中的 MLflow 跟踪服务器,展示我们的第一个 DL 实验结果。
本章将涵盖以下主要内容:
- 
设置 MLflow
 - 
实现我们的第一个启用 MLflow 日志记录的深度学习实验
 - 
探索 MLflow 的组件和使用模式
 
技术要求
要完成本章的实验,我们需要在计算机上安装或检出以下工具、库和 GitHub 仓库:
- 
VS Code:本书中使用的版本是 2021 年 8 月(即版本 1.60)。我们使用 VS Code 作为本地代码开发环境。这是推荐的本地开发方式。请参见
code.visualstudio.com/updates/v1_60。 - 
MLflow:版本 1.20.2。本章的设置 MLflow部分将讲解如何在本地或远程设置 MLflow。请参见
github.com/mlflow/mlflow/releases/tag/v1.20.2。 - 
Miniconda:版本 4.10.3。请参见
docs.conda.io/en/latest/miniconda.html。 - 
PyTorch
lightning-flash: 版本 0.5.0。请参见github.com/PyTorchLightning/lightning-flash/releases/tag/0.5.0。 - 
本章代码的 GitHub URL:你可以在
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLflow/tree/main/chapter02找到。 
设置 MLflow
MLflow 是一个开源工具,主要用 Python 编写。它在 GitHub 源代码库中获得了超过 10,000 个 stars(星标)(github.com/mlflow/mlflow)。使用 MLflow 的好处有很多,但我们可以通过以下场景来说明其中一个好处:假设您正在开始一个新的 ML 项目,试图评估不同的算法和模型参数。在几天之内,您运行了数百次实验,使用了不同的 ML/DL 库,并得到了不同的模型和不同的参数与准确度。您需要比较哪个模型效果更好,并且还要让您的团队成员能够重现结果以进行模型评审。您是否准备了一份电子表格,写下模型名称、参数、准确度以及模型位置?那么,其他人如何重新运行您的代码或使用您训练的模型与不同的评估数据集进行测试呢?当您为不同的项目进行大量迭代时,这可能会变得难以管理。MLflow 可以帮助您跟踪实验、比较模型运行,并让其他人轻松重现您的结果,重用您训练的模型进行评审,甚至轻松地将模型部署到生产环境中。
听起来很令人兴奋吧?那么,来设置一下 MLflow,让我们探索它的组件和模式。MLflow 支持本地设置和基于云的设置。我们将在接下来的章节中详细介绍这两种设置场景。
使用 miniconda 本地设置 MLflow
首先,让我们在本地开发环境中设置 MLflow。这可以快速进行原型设计,并帮助您熟悉 MLflow 工具的基本功能。此外,它还允许您在需要时与远程的 MLflow 云服务器进行交互。请按照以下说明设置 MLflow。
假设您已经根据 第一章,“深度学习生命周期与 MLOps 挑战”创建了一个虚拟 conda 环境,那么您可以准备在同一个虚拟环境中安装 MLflow:
pip install mlflow
上述命令将安装最新版本的 MLflow。如果您想安装特定版本的 MLflow,可以使用以下命令:
pip install mlflow==1.20.2
如您所见,我已安装了 MLflow 版本 1.20.2。默认情况下,MLflow 将使用本地文件系统来存储所有实验文档(例如,序列化模型)和元数据(参数、指标等)。如果需要关系型数据库作为 MLflow 的后端存储,则需要额外的安装和配置。现在,我们先使用文件系统进行存储。您可以通过在命令行中输入以下内容来验证本地的 MLflow 安装:
mlflow --version
然后,它将显示已安装的 MLflow 版本,如下所示:
mlflow, version 1.20.2
这表明我们已经在本地开发环境中安装了版本 1.20.2 的 MLflow。此外,您还可以在本地启动 MLflow UI,查看 MLflow 跟踪服务器 UI,如下所示:
mlflow ui
接下来,你将看到 UI 网页服务器正在运行:

图 2.1 – 在本地环境中启动 MLflow UI
图 2.1 显示了本地 MLflow UI 网站:http://127.0.0.1:5000/。如果你点击这个 URL,你将在浏览器窗口中看到如下 MLflow UI。由于这是一个全新的 MLflow 安装,当前只有一个 默认 实验,且尚未有任何运行记录(请参见 图 2.2):

图 2.2 – MLflow 默认实验 UI 网页
看到默认的 MLflow UI 页面已经成功启动,标志着本地 MLflow 跟踪服务器的成功设置。
设置 MLflow 与远程 MLflow 服务器的交互
在企业生产环境中,MLflow 通常托管在云服务器上,可以是自托管的,也可以是 Databricks 在云提供商(如 AWS、Azure 或 Google Cloud)中提供的托管服务之一。在这种情况下,需要设置本地开发环境,以便能够在本地运行 ML/DL 实验,同时与远程 MLflow 服务器进行交互。接下来,我们将通过以下三个步骤介绍如何使用环境变量来实现这一目标:
- 
在 bash shell 命令行环境中,如果你正在使用 Databricks 管理的 MLflow 跟踪服务器,请定义三个新的环境变量。第一个环境变量是
MLFLOW_TRACKING_URI,其值为databricks:export MLFLOW_TRACKING_URI=databricks export DATABRICKS_HOST=https://******* export DATABRICKS_TOKEN=dapi****** - 
第二个环境变量是
DATABRICKS_HOST。如果你的 Databricks 管理网站地址是https://dbc-*.cloud.databricks.com/,那么这就是DATABRICKS_HOST变量的值(将*替换为你的实际网站字符串)。 - 
第三个环境变量是
DATABRICKS_TOKEN。前往你的 Databricks 管理网站https://dbc-*.cloud.databricks.com/#setting/account,点击 访问令牌,然后点击 生成新令牌。你将看到一个弹出窗口,其中包含一个 备注 字段(用于记录此令牌的用途)和过期日期,如 图 2.3 所示: 

图 2.3 – 生成 Databricks 访问令牌
点击 DATABRICKS_TOKEN 环境变量作为其值:

图 2.4 – 复制生成的令牌,将用于环境变量
一旦设置好这三个环境变量,您就可以在未来与 Databricks 管理的 MLflow 服务器进行交互。请注意,出于安全原因,访问令牌有有效期,管理员可以随时撤销该令牌,因此请确保在令牌更新时及时更新环境变量。
总结一下,我们已经学会了如何在本地设置 MLflow,以便与本地 MLflow 追踪服务器或远程 MLflow 追踪服务器进行交互。这将允许我们在下一节中实现第一个启用 MLflow 追踪的深度学习模型,从而以动手的方式探索 MLflow 的概念和组件。
使用 MLflow 自动日志记录实现我们的第一个深度学习实验
让我们使用在 第一章 中构建的深度学习情感分类器,深度学习生命周期与 MLOps 挑战,并向其添加 MLflow 自动日志记录,以探索 MLflow 的追踪功能:
- 
首先,我们需要导入 MLflow 模块:
import mlflow 
这将为记录和加载模型提供 MLflow 应用程序编程接口 (APIs)。
- 
在运行训练代码之前,我们需要使用
mlflow.set_experiment为当前运行的代码设置一个活动实验:EXPERIMENT_NAME = "dl_model_chapter02" mlflow.set_experiment(EXPERIMENT_NAME) experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME) print("experiment_id:", experiment.experiment_id) 
这将设置一个名为 dl_model_chapter02 的实验为当前活动实验。如果此实验在当前的追踪服务器中不存在,它将自动创建。
环境变量
请注意,在运行第一个实验之前,您可能需要使用 MLFLOW_TRACKING_URI 环境变量设置追踪服务器的 URI。如果您使用的是托管的 Databricks 服务器,请执行以下操作:
export MLFLOW_TRACKING_URI=databricks
如果您使用的是本地服务器,则将此环境变量设置为空或默认的本地主机端口号 5000,如下所示(请注意,这是我们当前章节的场景,并假设您正在使用本地服务器):
export MLFLOW_TRACKING_URI=http://127.0.0.1:5000
- 
接下来,添加一行代码以启用 MLflow 的自动日志记录功能:
mlflow.pytorch.autolog() 
这将允许默认的参数、指标和模型自动记录到 MLflow 追踪服务器。
MLflow 自动日志记录
MLflow 中的自动日志记录仍处于实验模式(截至版本 1.20.2),未来可能会发生变化。在这里,我们使用它来探索 MLflow 组件,因为只需要一行代码就能自动记录所有相关信息。在接下来的章节中,我们将学习并实现更多关于在 MLflow 中进行追踪和日志记录的方法。此外,请注意,目前 MLflow 中的 PyTorch 自动日志记录(截至版本 1.20.2)仅适用于 PyTorch Lightning 框架,而不适用于任意的 PyTorch 代码。
- 
使用 Python 上下文管理器
with语句,通过调用mlflow.start_run来开始实验运行:with mlflow.start_run(experiment_id=experiment.experiment_id, run_name="chapter02"): trainer.finetune(classifier_model, datamodule=datamodule, strategy="freeze") trainer.test() 
请注意,with 块下方的所有代码行都是常规的 DL 模型微调和测试步骤。我们只是启用了自动 MLflow 日志记录,这样我们就可以观察到 MLflow 跟踪服务器记录的元数据。
- 
接下来,你可以使用以下命令行运行整个
first_dl_with_mlflow.py的代码(完整代码可以在本章的 GitHub 上查看:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter02/first_dl_with_mlflow.py):python first_dl_with_mlflow.py 
在没有 GPU 的 macOS 笔记本上,整个运行过程不到 10 分钟。你应该能在屏幕上看到以下输出:

图 2.5 – 启用 MLflow 自动日志记录的 DL 模型训练/测试
如果这是你第一次运行,你会看到名为 dl_model_chapter02 的实验不存在。相反,MLflow 会为你自动创建这个实验:

图 2.6 – 如果环境不存在,MLflow 会自动创建一个新的环境
- 现在,我们可以在本地打开 MLflow UI,通过访问 
http://127.0.0.1:5000/来查看本地跟踪服务器记录的内容。在这里,你会看到一个新的实验(dl_model_chapter02)和一个新的运行(chapter02)已被记录: 

图 2.7 – MLflow 跟踪服务器 UI 显示一个新的实验和新的运行
现在,点击 图 2.7 中 开始时间 列的超链接。你将看到该运行记录的元数据详情:

图 2.8 – MLflow 运行 UI 显示实验运行的元数据详情
如果你能在自己的本地环境中看到这个屏幕,那么恭喜你!你刚刚完成了我们第一个 DL 模型的 MLflow 跟踪实现!在下一节中,我们将通过实际示例来探索 MLflow 的核心概念和组件。
探索 MLflow 的组件和使用模式
让我们使用上一节中实现的工作示例,探索 MLflow 中以下核心概念、组件和用法。这些包括实验、运行、实验的元数据、实验的工件、模型和代码。
在 MLflow 中探索实验和运行
实验 是 MLflow APIs 中的一等实体。这是有道理的,因为数据科学家和 ML 工程师需要运行大量实验,以构建符合要求的工作模型。然而,实验的概念不仅限于模型开发阶段,还延伸到整个 ML/DL 开发和部署的生命周期。因此,这意味着当我们对模型进行重训练或为生产版本进行训练时,我们需要将它们视为生产质量实验。这种对实验的统一视图建立了线下和线上生产环境之间的桥梁。每个实验由许多运行组成,您可以在每次运行时更改模型参数、输入数据,甚至是模型类型。因此,实验是一个包含一系列运行的总体实体。下图(图 2.9)说明了数据科学家可以在 ML/DL 模型生命周期的多个阶段进行线下实验和在线生产实验:

图 2.9 – 跨机器学习(ML)/深度学习(DL)模型线下和线上生产生命周期的实验
如您从图 2.9中所见,在模型开发阶段,数据科学家可以根据项目情况运行多个相同实验的运行或多个实验。如果是一个小型 ML 项目,将所有运行放在一个单一的线下实验中可能足够了。如果是一个复杂的 ML 项目,则设计不同的实验并在每个实验下进行运行是合理的。关于设计 ML 实验的良好参考资料可以在machinelearningmastery.com/controlled-experiments-in-machine-learning/找到。然后,在模型生产阶段,最好设置生产质量的实验,因为我们需要进行模型改进和连续部署与模型重训练。生产实验将提供一个门控准确性检查,以防止新模型的回归。通常,这是通过运行自动模型评估和针对保留测试数据集的验证来实现的,以检查新模型在准确性方面是否仍符合发布标准。
现在,让我们以实际操作的方式探索 MLflow 实验。运行以下 MLflow 命令行与跟踪服务器进行交互:
mlflow experiments list 
如果您的MLFLOW_TRACKING_URI环境变量指向远程跟踪服务器,则将列出您具有读取权限的所有实验。如果要查看本地跟踪服务器中的内容,可以将MLFLOW_TRACKING_URI设置为空(即为空),如下所示(请注意,如果您的本地用户配置文件中从未有过此环境变量,则无需执行此操作;但是,执行此操作将确保您使用本地跟踪服务器):
export MLFLOW_TRACKING_URI=
在你第一次实现启用 MLflow 自动日志记录的 DL 模型之前,列出所有实验的输出应类似于 图 2.10(注意,这也取决于你运行命令行的位置;以下输出假设你在本地文件夹中运行该命令,并且可以查看 第二章 的 GitHub 代码):

图 2.10 – 本地环境中默认的 MLflow 实验列表
图 2.10 列出了实验属性的三列:mlruns 文件夹(位于执行 MLflow 命令的目录下)。mlruns 文件夹由基于文件系统的 MLflow 跟踪服务器使用,用于存储所有实验运行和工件的元数据。
命令行界面(CLI)与 REST API 与编程语言特定的 API
MLflow 提供了三种不同类型的工具和 API 与跟踪服务器进行交互。在这里,我们使用 CLI 来探索 MLflow 组件。
那么,让我们探索一个特定的 MLflow 实验,如下所示:
- 
首先,使用 MLflow CLI 创建一个新的实验,如下所示:
mlflow experiments create -n dl_model_chapter02 
上述命令创建了一个名为 dl_model_chapter02 的新实验。如果你在前一节已经使用 MLflow 自动日志记录运行了第一个 DL 模型,执行上述命令将会报错,如下所示:
mlflow.exceptions.MlflowException: Experiment 'dl_model_chapter02' already exists.
这是预期的结果,你并没有做错什么。现在,如果你列出本地跟踪服务器上的所有实验,它应该会包括刚创建的实验,如下所示:

图 2.11 – 创建新实验后,新的 MLflow 实验列表
- 现在,让我们检查实验与运行之间的关系。如果你仔细查看运行页面的 URL (图 2.8),你将看到类似以下内容:
 
http://127.0.0.1:5000/#/experiments/1/runs/2f2ec6e72a5042f891abe0d3a533eec7
正如你可能已经理解的那样,experiments 路径后的整数就是实验的 ID。然后,在实验 ID 后面,有一个 runs 路径,接着是一个类似 GUID 的随机字符串,这就是运行 ID。所以,现在我们了解了如何通过一个全球唯一的 ID(称为运行 ID)组织实验下的运行。
知道运行的全球唯一 ID 非常有用。因为我们可以通过 run_id 检索该运行的日志数据。如果你使用命令 mlflow runs describe --run-id <run_id>,你可以获得该运行记录的元数据列表。对于我们刚才运行的实验,下面显示了包含运行 ID 的完整命令:
mlflow runs describe –-run-id 2f2ec6e72a5042f891abe0d3a533eec7
该命令行的输出片段如下 (图 2.12):

图 2.12 – MLflow 命令行以 JSON 数据格式描述运行
请注意,图 2.12 展示了以 JSON 格式呈现的该运行的所有元数据。这些元数据包括模型训练过程中使用的参数;用于衡量模型在训练、验证和测试中的准确度的度量标准等。相同的数据也会在 MLflow 用户界面中以 图 2.8 的形式展示。请注意,强大的 MLflow 命令行界面(CLI)将允许非常方便地探索 MLflow 记录的元数据和工件,并支持基于 shell 脚本的自动化,这将在接下来的章节中进一步探讨。
探索 MLflow 模型及其用途
现在,让我们探索在 MLflow 跟踪服务器中如何记录 DL 模型工件。在同一运行页面上,如 图 2.8 所示,如果你向下滚动到页面底部,你将看到工件部分(图 2.13)。这里列出了有关模型的所有元数据和序列化模型本身:

图 2.13 – MLflow 记录的模型工件
MLflow 跟踪服务器的后端存储和工件存储
MLflow 跟踪服务器有两种类型的存储:第一种是后端存储,用于存储实验和运行的元数据,以及运行的参数、度量标准和标签;第二种是工件存储,用于存储较大的文件,如序列化的模型、文本文件,甚至是生成的可视化图表。为了简化起见,本章中我们使用本地文件系统作为后端存储和工件存储。然而,像模型注册表等一些更高级的功能在基于文件系统的工件存储中不可用。在后续章节中,我们将学习如何使用模型注册表。
让我们逐一查看这些工件列表:
model_summary.txt:在root文件夹级别,这个文件点击后看起来类似于以下输出。它描述了模型的度量标准和深度学习(DL)模型的层次结构(请参见 图 2.14):

图 2.14 – MLflow 记录的模型摘要文件
图 2.14 提供了 DL 模型的简要概述,包括神经网络层的数量和类型、参数的数量和大小,以及训练和验证中使用的度量标准类型。当需要在团队成员或利益相关者之间共享和交流 DL 模型架构时,这非常有帮助。
- 
model文件夹:此文件夹包含一个名为data的子文件夹,并且包含三个文件:MLmodel、conda.yaml和requirements.txt:MLmodel:此文件描述了 MLflow 支持的模型类型。MLmodel文件(图 2.15):
 

图 2.15 – 我们首次使用 MLflow 运行的 DL 模型的 MLmodel 文件内容
图 2.15 展示了这是一个 PyTorch 类型的模型,并且我们刚刚运行了具有 run_id 的模型。
conda.yaml: 这是模型使用的 conda 环境定义文件,用于描述我们的依赖关系。图 2.16 列出了 MLflow 在我们刚刚完成的运行中记录的内容:

图 2.16 – MLflow 记录的 conda.yaml 文件的内容
- 
requirements.txt: 这是一个特定于 Pythonpip的依赖定义文件。它就像 图 2.16 中conda.yaml文件中的pip部分一样。 - 
data: 这是一个包含实际序列化模型的文件夹,名为model.pth,以及一个描述文件,名为pickle_module_info.txt,我们第一个 DL 实验的内容如下:mlflow.pytorch.pickle_module 
这意味着模型使用了 MLflow 提供的 PyTorch 兼容的 pickle 序列化方法进行序列化。这允许 MLflow 在需要时重新加载模型到内存中。
模型注册表与模型日志记录
MLflow 模型注册表需要关系数据库,如 MySQL,作为工件存储,而不仅仅是普通的文件系统。因此,在本章中我们不会深入探讨它。请注意,模型注册表与模型日志记录不同,对于每次运行,您希望记录模型的元数据和工件。但是,仅对符合您的生产要求的特定运行,您可能希望将它们注册到模型注册表以进行生产部署和版本控制。在后续章节中,我们将学习如何注册模型。
到目前为止,您应该对我们实验中的文件列表和关于模型以及序列化模型的元数据有了很好的理解(包括我们实验中的 .pth 文件扩展名,这指的是 PyTorch 序列化模型)。在接下来的章节中,我们将学习更多关于 MLflow 模型风格的工作原理以及如何将记录的模型用于模型注册和部署。MLflow 模型风格是 MLflow 支持的模型框架,如 PyTorch、TensorFlow 和 scikit-learn。有兴趣的读者可以在 MLflow 官方文档网站上找到关于当前内置模型风格的更多详细信息,网址为 www.mlflow.org/docs/latest/models.html#built-in-model-flavors。
探索 MLflow 代码跟踪及其用法
当探索运行的元数据时,我们还可以发现代码是如何被跟踪的。如 MLflow UI 和 JSON 中的命令行输出所示,代码以三种方式进行跟踪:文件名、Git 提交哈希和源类型。您可以执行以下命令行:
mlflow runs describe --run-id 2f2ec6e72a5042f891abe0d3a533eec7 | grep mlflow.source
你应该能在输出中找到以下 JSON 键值对的片段:
"mlflow.source.git.commit": "ad6c7338a416ff4c2848d726b092057457c22408",
"mlflow.source.name": "first_dl_with_mlflow.py",
"mlflow.source.type": "LOCAL"
基于这个ad6c7338a416ff4c2848d726b092057457c22408的 Git 提交哈希,我们可以继续查找我们使用的 Python 代码的确切副本:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLflow/blob/ad6c7338a416ff4c2848d726b092057457c22408/chapter02/first_dl_with_mlflow.py。
请注意,这里的源类型是LOCAL。这意味着我们从代码的本地副本中执行启用 MLflow 的源代码。在后面的章节中,我们将了解其他源类型。
本地与远程 GitHub 代码
如果源代码是本地副本,那么在 MLflow 跟踪服务器中看到的 Git 提交哈希存在一个警告。如果你在本地做了代码更改,但忘记提交它们,然后立即开始 MLflow 实验跟踪运行,MLflow 只会记录最近的 Git 提交哈希。我们可以通过两种方式解决这个问题:
1. 在运行 MLflow 实验之前提交我们的代码更改。
2. 使用远程 GitHub 代码运行实验。
由于第一种方法不容易强制执行,因此推荐使用第二种方法。使用远程 GitHub 代码运行 DL 实验是一个高级话题,我们将在后面的章节中探讨。
到目前为止,我们已经了解了 MLflow 跟踪服务器、实验和运行。此外,我们还记录了关于运行的元数据,如参数和指标,检查了代码跟踪,并探讨了模型日志记录。这些跟踪和日志记录功能确保我们有一个扎实的 ML 实验管理系统,不仅用于模型开发,还为未来的模型部署提供支持,因为我们需要跟踪哪些运行生成了用于生产的模型。可重复性和来源追踪是 MLflow 提供的标志性特点。除此之外,MLflow 还提供了其他组件,如用于标准化 ML 项目代码组织的MLproject、用于模型版本控制的模型注册表、模型部署功能和模型可解释性工具。所有这些 MLflow 组件涵盖了 ML/DL 开发、部署和生产的整个生命周期,我们将在后续章节中更深入地探讨这些内容。
总结
在本章中,我们学习了如何设置 MLflow,使其与本地 MLflow 跟踪服务器或远程 MLflow 跟踪服务器一起工作。然后,我们实现了第一个启用自动日志记录的 DL 模型。这样,我们就可以通过实践的方式探索 MLflow,理解一些核心概念和基础组件,如实验、运行、实验和运行的元数据、代码跟踪、模型日志记录和模型风味。本章中获得的知识和首次经验将帮助我们在下一章深入学习 MLflow 跟踪 API。
进一步阅读
为了进一步拓展你的知识,你可以参考以下资源和文档:
- 
MLflow 命令行接口 文档:
www.mlflow.org/docs/latest/cli.html - 
MLflow PyTorch 自动记录文档:
www.mlflow.org/docs/latest/tracking.html#pytorch-experimental - 
MLflow PyTorch 模型风味文档:
www.mlflow.org/docs/latest/python_api/mlflow.pytorch.html#module-mlflow.pytorch - 
MLflow 与 PyTorch——前沿 AI 与 MLOps 的结合:
medium.com/pytorch/mlflow-and-pytorch-where-cutting-edge-ai-meets-mlops-1985cf8aa789 - 
机器学习中的对照实验:
machinelearningmastery.com/controlled-experiments-in-machine-learning/ 
第二部分 – 大规模跟踪深度学习管道
在本节中,我们将学习如何使用 MLflow 来跟踪深度学习(DL)管道,以回答与数据、模型和代码(包括笔记本和管道代码)相关的各种溯源问题。我们将从设置一个本地的完整 MLflow 跟踪服务器开始,该服务器将在本书的其余部分频繁使用。我们将介绍一个包括六种类型溯源问题的溯源跟踪框架,以指导我们的实现。接着,我们将学习如何使用 MLflow 跟踪模型溯源、指标和参数,以回答这些溯源问题。我们还将学习如何选择合适的笔记本和管道框架来实现可扩展且可跟踪的深度学习代码。然后,我们将使用 MLflow 的MLproject实现一个多步骤的深度学习训练/测试/注册管道。最后,我们将学习如何使用Delta Lake跟踪公共和私有构建的 Python 库及数据版本控制。
本节包括以下章节:
- 
第三章,跟踪模型、参数和指标
 - 
第四章,跟踪代码和数据版本控制
 
第三章:第三章:跟踪模型、参数和指标
鉴于 MLflow 可以支持深度学习模型生命周期中的多种场景,通常会逐步使用 MLflow 的功能。通常,人们从 MLflow 跟踪开始,因为它易于使用,能够处理许多可复现性、来源追踪和审计目的的场景。此外,从模型的“摇篮到日落”跟踪其历史,不仅超越了数据科学实验管理领域,而且对于企业中的模型治理也至关重要,因为在生产中使用模型时,需要管理业务和监管风险。虽然在生产中跟踪模型的精确商业价值仍在发展中,但对跟踪模型整个生命周期的需求是不可置疑且日益增长的。为了实现这一点,我们将从本章开始,搭建一个完整的本地 MLflow 跟踪服务器。
然后,我们将深入探讨如何使用 MLflow 的跟踪和注册 API 跟踪模型及其参数和指标。在本章结束时,你应该能够熟练使用 MLflow 的跟踪和注册 API 进行各种可复现性和审计目的。
在本章中,我们将讨论以下主要内容:
- 
搭建一个完整的本地 MLflow 跟踪服务器
 - 
跟踪模型来源
 - 
跟踪模型指标
 - 
跟踪模型参数
 
技术要求
以下是你需要遵循本章提供的指令所需的要求:
- 
Docker Desktop:
docs.docker.com/get-docker/。 - 
PyTorch
lightning-flash: 0.5.0:github.com/PyTorchLightning/lightning-flash/releases/tag/0.5.0。 - 
带有 Jupyter Notebook 扩展的 VS Code:
github.com/microsoft/vscode-jupyter/wiki/Setting-Up-Run-by-Line-and-Debugging-for-Notebooks。 - 
本章代码的 GitHub 链接:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLflow/tree/main/chapter03。 - 
WSL2:如果你是 Microsoft Windows 用户,建议安装 WSL2 以运行本书中提供的 Bash 脚本:
www.windowscentral.com/how-install-wsl2-windows-10。 
搭建一个完整的本地 MLflow 跟踪服务器
在第二章,使用 MLflow 进行深度学习入门中,我们通过实践操作,使用了基于本地文件系统的 MLflow 跟踪服务器,并检查了 MLflow 实验的组件。然而,基于默认本地文件系统的 MLflow 服务器存在一些限制,因为模型注册功能不可用。拥有模型注册的好处在于我们可以注册模型、进行版本控制,并为模型部署到生产环境做准备。因此,模型注册将填补离线实验与在线生产部署之间的空白。因此,我们需要一个功能齐全的 MLflow 跟踪服务器,并且该服务器需要以下存储来跟踪模型的完整生命周期:
- 
后端存储:需要一个关系型数据库后端来支持 MLflow 存储关于实验的元数据(如度量、参数等)。这还允许查询实验的功能。我们将使用 MySQL 数据库作为本地后端存储。
 - 
工件存储:一个可以存储任意类型对象的对象存储,比如序列化模型、词汇文件、图表等。在生产环境中,常用的选择是 AWS S3 存储。我们将使用MinIO(
min.io/),一个多云对象存储,作为本地工件存储,它完全兼容 AWS S3 存储 API,但可以在本地电脑上运行,无需访问云端。 
为了尽可能简化本地设置,我们将使用docker-compose(docs.docker.com/compose/)工具,只需一行命令即可启动和停止本地全功能的 MLflow 跟踪服务器,具体步骤如下。请注意,必须先在机器上安装并运行 Docker Desktop(docs.docker.com/get-docker/),才能执行这些步骤。Docker 有助于构建和分享容器化应用程序和微服务。以下步骤将在本地 Docker 容器中启动本地 MLflow 跟踪服务器:
- 
查看
chapter03代码库以便配置本地开发环境:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLflow/tree/main/chapter03。 - 
进入
mlflow_docker_setup子文件夹,该文件夹位于chapter03文件夹下。 - 
执行以下命令:
bash start_mlflow.sh 
如果命令成功执行,屏幕上应显示类似以下内容的输出:

图 3.1 – 本地全功能的 MLflow 跟踪服务器已启动并运行
- 访问
http://localhost/,你应该能看到 MLflow UI 网页。然后,点击 UI 中的Models标签(图 3.2)。请注意,如果你的 MLflow 追踪服务器仅使用本地文件系统作为后端存储,该标签将无法使用。因此,MLflow UI 的后端现在正在运行在你刚启动的 Docker 容器服务上,而不是本地文件系统。由于这是一个全新的服务器,目前还没有注册的模型: 

图 3.2 – MLflow 模型注册 UI
- 访问
http://localhost:9000/,此时应该会显示 MinIO 工件存储 Web UI 的页面(图 3.3)。在.env文件中,输入minio作为用户名,minio123作为密码,位于mlflow_docker_setup文件夹下: 

图 3.3 – MinIO Web UI 登录页面和登录后的浏览器页面
此时,你应该已经成功启动了一个完整的本地 MLflow 追踪服务器!如果你想停止服务器,只需输入以下命令:
bash stop_mlflow.sh
基于 Docker 的 MLflow 追踪服务器将停止。我们现在准备使用这个本地 MLflow 服务器来追踪模型的来源、参数和指标。
追踪模型来源
来源追踪在数字工件方面已经在文献中得到广泛研究。例如,在生物医学行业,当你使用一份患者诊断数据时,通常希望知道这些数据的来源,数据经过了哪些处理和清洗,数据的所有者是谁,以及其他有关数据的历史和谱系信息。随着工业和商业场景中 ML/DL 模型的兴起,来源追踪已成为一项必备功能。不同粒度的来源追踪对操作和管理不仅仅是数据科学离线实验至关重要,而且对模型在生产环境中部署前/中/后的管理也至关重要。那么,来源追踪需要追踪哪些内容呢?
了解开放来源追踪框架
让我们看一下一个通用的来源追踪框架,以理解为什么来源追踪是一个重大努力。以下图示基于开放来源模型词汇规范(open-biomed.sourceforge.net/opmv/ns.html):

图 3.4 – 一个开放的来源追踪框架
在前述图示中,有三个重要的项目:
- 
工件:由过程生产或使用的事物(A1 和 A2)。
 - 
过程:通过使用或生产工件来执行的操作(P1 和 P2)。
 - 
因果关系:在工件和过程之间的边或关系,如前述图示中的used、wasGeneratedBy和wasDerivedFrom(R1、R2和R3)。
 
直观地说,开放来源模型 (OPM) 框架允许我们提出以下 5W1H(五个 W 和一个 H)问题,如下所示:

图 3.5 – 来源问题的类型
拥有一个系统化的来源框架和一组问题将帮助我们学习如何追踪模型来源并提供这些问题的答案。这将激励我们在下一节实现 MLflow 模型跟踪时。
实现 MLflow 模型跟踪
如果我们为所使用的深度学习模型实现了 MLflow 日志记录和注册功能,我们可以使用 MLflow 跟踪服务器回答这些来源问题中的大多数。首先,让我们回顾 MLflow 在模型来源跟踪方面提供的功能。MLflow 提供了两套用于模型来源的 API:
- 
日志记录 API:这允许每次实验或模型管道运行时将模型工件记录到工件存储中。
 - 
注册表 API:这允许在一个集中位置跟踪模型的版本和模型生命周期的各个阶段(无、已归档、暂存或生产)。
模型日志记录和模型注册表之间的区别
尽管每次实验都需要进行日志记录,并且模型需要保存在工件存储中,但并非每个模型实例都需要在模型注册表中注册。这是因为对于许多早期的探索性模型实验来说,模型可能不好。因此,不一定要注册以跟踪版本。只有当模型在离线测试中表现良好并成为推向生产环境的候选模型时,才需要将其注册到模型注册表,以便通过模型提升流程。
尽管 MLflow 的官方 API 文档将日志记录和注册表分成了两个组件,但在本书中我们将它们统称为 MLflow 的模型跟踪功能。
 
我们已经在 第二章《使用 MLflow 进行深度学习入门》中看到过 MLflow 的自动日志记录功能。尽管自动日志记录功能非常强大,但当前版本存在两个问题:
- 
它不会自动将模型注册到模型注册表。
 - 
如果你仅按照 MLflow 的建议使用
mlflow.pyfunc.load_modelAPI 来加载已记录的模型,它不能直接与原始输入数据(在我们的案例中是英文句子)一起工作。这是由于 MLflow 当前自动日志记录 API 的实验性质,可能是一个局限性。 
让我们通过一个示例来回顾 MLflow 的功能以及自动日志记录的局限性,并讨论我们如何解决这些问题:
- 
在 Bash 终端中设置以下环境变量,其中你的 MinIO 和基于 MySQL 的 Docker 组件正在运行:
export MLFLOW_S3_ENDPOINT_URL=http://localhost:9000 export AWS_ACCESS_KEY_ID=minio export AWS_SECRET_ACCESS_KEY=minio123 
请注意,AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 使用的是在 .env 文件中定义的相同值,该文件位于 mlflow_docker_setup 文件夹下。这是为了确保我们使用的是之前设置的 MLflow 服务器。由于这些环境变量是基于会话的,我们还可以在笔记本的代码中设置以下环境变量,如下所示:
os.environ["AWS_ACCESS_KEY_ID"] = "minio"
os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123"
os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000"
前面三行代码可以在本章的笔记本文件中找到,紧接在导入所需的 Python 包之后。在执行笔记本之前,请确保运行以下命令来初始化虚拟环境 dl_model,该环境现在包含 requirements.txt 文件中定义的额外必需包:
conda create -n dl_model python==3.8.10
conda activate dl_model
pip install -r requirements.txt
如果你在之前的章节中设置了 dl_model 虚拟环境,你可以跳过创建名为 dl_model 的虚拟环境的第一行。然而,你仍然需要激活 dl_model 作为当前活动的虚拟环境,然后运行 pip install -r requirements.txt 来安装所有所需的 Python 包。一旦 dl_model 虚拟环境成功设置,你可以继续执行下一步。
- 要跟随此模型跟踪实现,请查看 VS Code 中的 
dl_model_tracking.ipynb笔记本文件,方法是访问本章的 GitHub 仓库:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter03/dl_model_tracking.ipynb。 
请注意,在 dl_model_tracking.ipynb 笔记本的第四个单元格中,我们需要将其指向我们刚刚在 Docker 中设置的正确的新 MLflow 跟踪 URI,并定义一个新的实验,如下所示:
EXPERIMENT_NAME = "dl_model_chapter03"
mlflow.set_tracking_uri('http://localhost')
- 
我们仍然会使用 MLflow 提供的自动日志记录功能,但我们将给运行分配一个变量名,
dl_model_tracking_run:mlflow.pytorch.autolog() with mlflow.start_run(experiment_id=experiment.experiment_id, run_name="chapter03") as dl_model_tracking_run: trainer.finetune(classifier_model, datamodule=datamodule, strategy="freeze") trainer.test() 
dl_model_tracking_run 允许我们以编程方式获取 run_id 参数和此运行的其他元数据,正如我们在下一步中将看到的那样。一旦执行了这段代码单元,我们将在 MLflow 跟踪服务器中记录一个训练好的模型,并包含所有所需的参数和指标。然而,模型还没有注册。我们可以在 MLflow 的 Web UI 中找到记录的实验以及所有相关的参数和指标,地址是 http://localhost/#/experiments/1/runs/37a3fe9b6faf41d89001eca13ad6ca47。你可以在 http://localhost:9000/minio/mlflow/1/37a3fe9b6faf41d89001eca13ad6ca47/artifacts/model/ 找到模型工件并查看存储 UI,如下所示:

图 3.6 – 模型工件记录在 MinIO 存储后端
文件夹结构与我们在第二章《使用 MLflow 开始深度学习》中看到的类似,当时我们使用普通文件系统来存储模型工件。然而,在这里,我们使用 MinIO 桶来存储这些模型工件。
- 
从
dl_model_tracking_run中检索run_id参数以及其他元数据,如下所示:run_id = dl_model_tracking_run.info.run_id print("run_id: {}; lifecycle_stage: {}".format(run_id, mlflow.get_run(run_id).info.lifecycle_stage)) 
这将打印出类似以下内容:
run_id: 37a3fe9b6faf41d89001eca13ad6ca47; lifecycle_stage: active
- 
通过定义已记录模型的 URI 来检索已记录的模型。这将允许我们在特定位置重新加载已记录的模型:
logged_model = f'runs:/{run_id}/model' - 
使用
mlflow.pytorch.load_model和以下logged_modelURI 将模型加载回内存,并为给定的输入句子做出新预测,如下所示:model = mlflow.pytorch.load_model(logged_model) model.predict({'This is great news'}) 
这将输出一个模型预测标签,如下所示:
['positive']
mlflow.pytorch.load_model 与 mlflow.pyfunc.load_model
默认情况下,在 MLflow 实验跟踪页面的工件部分,如果您有已记录的模型,MLflow 会建议使用 mlflow.pyfunc.load_model 来加载已记录的模型进行预测。然而,这仅适用于像 pandas DataFrame、NumPy 数组或张量这样的输入;不适用于 NLP 文本输入。由于 PyTorch Lightning 的自动记录使用 mlflow.pytorch.log_model 来保存模型,正确的加载已记录模型的方法是使用 mlflow.pytorch.load_model,正如我们在这里展示的那样。这是因为 MLflow 的默认设计是使用 mlflow.pyfunc.load_model,并且有一个已知的限制,要求输入格式必须是数值类型。对于文本和图像数据,它需要在预处理步骤中进行分词。然而,由于我们在此保存的 PyTorch 模型已经作为序列化模型的一部分执行了分词步骤,我们可以使用原生的 mlflow.pytorch.load_model 来直接加载接受文本输入的模型。
有了这些,我们就成功记录了模型并将其加载回来进行预测。如果我们认为这个模型表现足够好,那么我们可以将其注册。
- 
让我们使用
mlflow.register_modelAPI 来注册模型:model_registry_version = mlflow.register_model(logged_model, 'nlp_dl_model') print(f'Model Name: {model_registry_version.name}') print(f'Model Version: {model_registry_version.version}') 
这将生成以下输出:

图 3.7 – 模型注册成功消息
这表明模型已成功注册为版本 1,并存储在模型注册表中,模型名称为 nlp_dl_model。
我们还可以通过点击 http://localhost/#/models/nlp_dl_model/versions/1 在 MLflow Web UI 中找到这个已注册的模型:

图 3.8 – MLflow 跟踪服务器 Web UI 显示新注册的模型
默认情况下,新注册模型的阶段为 None,如前述截图所示。
通过为模型注册版本号和阶段标签,我们为部署到暂存(也称为预生产)和生产环境奠定了基础。我们将在本书的后面讨论基于已注册模型进行模型部署的方法。
到目前为止,我们已经解决了本节开头关于自动记录限制的两个问题:
- 
如何使用
mlflow.pytorch.load_modelAPI 而不是mlflow.pyfunc.load_modelAPI 加载已记录的 DL PyTorch 模型 - 
如何使用
mlflow.register_modelAPI 注册已记录的 DL PyTorch 模型MLflow DL 模型记录 API 的选择
对于 DL 模型,PyTorch 的自动记录仅适用于
mlflow.pyfunc.log_model用于记录模型,特别是当我们需要多步骤 DL 模型管道时。我们将在本书的后面实现这样的自定义 MLflow 模型风格。如果你不想使用 PyTorch 的自动记录,那么可以直接使用mlflow.pytorch.log_model。PyTorch 的自动记录在其实现中使用了mlflow.pytorch.log_model(请参阅官方 MLflow 开源实现:github.com/mlflow/mlflow/blob/290bf3d54d1e5ce61944455cb302a5d6390107f0/mlflow/pytorch/_pytorch_autolog.py#L314)。 
如果我们不想使用自动记录,那么可以直接使用 MLflow 的模型记录 API。这还为我们提供了一种同时注册模型的替代方法。您可以使用以下代码行来记录和注册训练好的模型:
mlflow.pytorch.log_model(pytorch_model=trainer.model, artifact_path='dl_model', registered_model_name='nlp_dl_model')
请注意,此代码行不记录模型的任何参数或度量值。
通过这种方式,我们不仅在跟踪服务器中记录了许多实验和模型以进行离线实验,而且还注册了性能优越的模型,以便将来能够进行版本控制和溯源跟踪,并将其部署到生产环境中。我们现在可以回答本章开头提出的一些溯源问题:

图 3.9 – 模型溯源问题的答案
为什么和在哪里的问题尚未完全解答,但将在本书后续部分中解答。这是因为生产模型的“为什么”问题只有在模型准备部署时才能被跟踪和记录,此时我们需要添加注释和理由来证明模型的部署合理性。至于“在哪里”的问题,当我们有多步骤模型流水线时可以完全解答。然而,在这里,我们只有一个单步骤流水线,这是最简单的情况。一个多步骤流水线包含明确分离的模块化代码,用于指定每个步骤执行什么功能,这样我们就可以轻松更改任何步骤的详细实现,而不改变流水线的流程。在接下来的两个部分中,我们将探讨如何在不使用自动日志记录的情况下跟踪模型的指标和参数。
跟踪模型指标
PyTorch lightning-flash包中的文本分类模型的默认指标是准确率。如果我们想将指标更改为F1 分数(精度和召回率的调和平均数),这是衡量分类器性能的常用指标,那么我们需要在开始模型训练过程之前更改分类器模型的配置。让我们学习如何进行此更改,并使用 MLflow 的非自动日志记录 API 来记录指标:
- 
在定义分类器变量时,我们将传递一个名为
torchmetrics.F1的度量函数作为变量,而不是使用默认的度量函数,如下所示:classifier_model = TextClassifier(backbone="prajjwal1/bert-tiny", num_classes=datamodule.num_classes, metrics=torchmetrics.F1(datamodule.num_classes)) 
这使用了torchmetrics的内置度量函数F1模块,并将数据中需要分类的类数作为参数。这确保了模型在训练和测试时使用了这个新指标。您将看到类似以下的输出:
{'test_cross_entropy': 0.785443127155304, 'test_f1': 0.5343999862670898}
这表明模型的训练和测试使用的是 F1 分数作为指标,而不是默认的准确率指标。有关如何使用torchmetrics自定义指标的更多信息,请参考其文档网站:torchmetrics.readthedocs.io/en/latest/。
- 
现在,如果我们想将所有指标记录到 MLflow 跟踪服务器中,包括训练、验证和测试指标,我们需要通过调用训练器的回调函数来获取所有当前的指标,如下所示:
cur_metrics = trainer.callback_metrics 
接下来,我们需要将所有度量值转换为float,以确保它们与 MLflow 的log_metrics API 兼容:
    metrics = dict(map(lambda x: (x[0], float(x[1])), cur_metrics.items()))
- 
现在,我们可以调用 MLflow 的
log_metrics来记录所有在跟踪服务器中的指标:mlflow.log_metrics(metrics) 
使用 F1 分数作为分类器指标后,您将看到以下指标,这些指标将被记录在 MLflow 的跟踪服务器中:
{'train_f1': 0.5838666558265686, 
'train_f1_step': 0.75, 
'train_cross_entropy': 0.7465656399726868, 
'train_cross_entropy_step': 0.30964696407318115, 
'val_f1': 0.5203999876976013, 
'val_cross_entropy': 0.8168156743049622, 
'train_f1_epoch': 0.5838666558265686, 
'train_cross_entropy_epoch': 0.7465656399726868, 
'test_f1': 0.5343999862670898, 
'test_cross_entropy': 0.785443127155304}
使用 MLflow 的log_metrics API 让我们通过额外的代码行获得更多控制,但如果我们对其自动日志记录功能满意,那么我们只需要改变我们想要在模型训练和测试过程中使用的度量。此时,我们只需要在声明新的深度学习模型时定义一个新的度量(即使用 F1 分数而不是默认的准确率度量)。
- 
如果您想同时跟踪多个模型度量,例如 F1 分数、准确率、精确度和召回率,那么您需要做的就是定义一个包含您想要计算和跟踪的度量的 Python 列表,如下所示:
list_of_metrics = [torchmetrics.Accuracy(), torchmetrics.F1(num_classes=datamodule.num_classes), torchmetrics.Precision(num_classes=datamodule.num_classes), torchmetrics.Recall(num_classes=datamodule.num_classes)] 
然后,在模型初始化语句中,您可以不传递单个度量到metrics参数,而是传递我们刚刚定义的list_of_metrics Python 列表,如下所示:
classifier_model = TextClassifier(backbone="prajjwal1/bert-tiny", num_classes=datamodule.num_classes, metrics=list_of_metrics)
剩下的代码无需再做任何更改。因此,在dl_model-non-auto-tracking.ipynb笔记本(https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter03/dl_model-non-auto-tracking.ipynb)中,您会注意到前一行默认被注释掉。然而,您可以取消注释它,然后注释掉前面的那一行:
classifier_model = TextClassifier(backbone="prajjwal1/bert-tiny", num_classes=datamodule.num_classes, metrics=torchmetrics.F1(datamodule.num_classes))
然后,当您运行笔记本的其余部分时,您将在笔记本输出中获得模型测试报告,附带以下度量:
{'test_accuracy': 0.6424000263214111, 'test_cross_entropy': 0.6315688490867615, 'test_f1': 0.6424000263214111, 'test_precision': 0.6424000263214111, 'test_recall': 0.6424000263214111}
您可能会注意到准确率、F1 分数、精确度和召回率的数字是相同的。这是因为,默认情况下,torchmetrics使用一个不支持none方法的torchmetrics,该方法为每个类计算度量,并返回每个类的度量,即使是二分类模型。因此,这不会生成一个单一的标量值。然而,您总是可以调用 scikit-learn 的度量 API,通过传递两个值列表来根据二元平均方法计算 F1 分数或其他度量。在这里,我们可以使用y_true和y_predict,其中y_true是地面真实标签值的列表,而y_predict是模型预测标签值的列表。这可以作为一个很好的练习,供您尝试,因为这是所有机器学习模型的常见做法,而不仅仅是深度学习模型的特殊处理。
跟踪模型参数
如我们所见,使用 MLflow 的自动日志记录有许多好处,但如果我们想要跟踪额外的模型参数,我们可以使用 MLflow 在自动日志记录记录的基础上记录额外的参数,或者直接使用 MLflow 记录我们想要的所有参数,而不使用自动日志记录。
让我们在不使用 MLflow 自动日志记录的情况下走一遍笔记本。如果我们想要完全控制 MLflow 记录哪些参数,可以使用两个 API:mlflow.log_param和mlflow.log_params。第一个用于记录单个键值对参数,而第二个用于记录整个键值对参数的字典。那么,我们可能会感兴趣跟踪哪些类型的参数呢?以下是答案:
- 
使用
log_paramsAPI 在实验中记录它们。 - 
模型参数:这些参数是在模型训练过程中学习到的。对于深度学习模型,这通常指的是在训练过程中学习到的神经网络权重。我们不需要单独记录这些权重参数,因为它们已经包含在已记录的深度学习模型中。
 
让我们使用 MLflow 的 log_params API 来记录这些超参数,代码如下:
    params = {"epochs": trainer.max_epochs}
    if hasattr(trainer, "optimizers"):
        optimizer = trainer.optimizers[0]
        params["optimizer_name"] = optimizer.__class__.__name__
    if hasattr(optimizer, "defaults"):
        params.update(optimizer.defaults)
    params.update(classifier_model.hparams)
    mlflow.log_params(params)
请注意,在这里,我们记录了最大轮数、训练器的第一个优化器名称、优化器的默认参数以及整体分类器的超参数(classifier_model.hparams)。那行代码 mlflow.log_params(params) 将 params 字典中的所有键值参数记录到 MLflow 跟踪服务器。如果你在 MLflow 跟踪服务器中看到以下超参数,说明它已经生效!

图 3.10 – MLflow 跟踪服务器的 Web UI 显示已记录的模型超参数
注意,这个参数列表比自动记录器记录的要多,因为我们在实验中添加了额外的超参数。如果你想记录其他自定义的参数,可以按照相同的模式在你的实验中进行。完整的笔记本(没有使用自动记录)可以在本章的 GitHub 仓库中查看,链接为 github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter03/dl_model-non-auto-tracking.ipynb。
如果你已经读到这一部分,说明你已经成功实现了一个 MLflow 跟踪模型以及其度量和参数!
总结
在本章中,我们设置了一个本地的 MLflow 开发环境,完全支持使用 MySQL 和 MinIO 对象存储进行后端存储和工件存储。这在我们本书中开发 MLflow 支持的深度学习模型时非常有用。我们首先介绍了开放的溯源跟踪框架,并提出了有关模型溯源的相关问题。我们解决了自动记录的问题,并成功地通过 mlflow.pytorch.load_model API 从已记录的模型中加载一个训练好的模型进行预测,完成了训练模型的注册工作。我们还尝试了如何在没有自动记录的情况下直接使用 MLflow 的 log_metrics、log_params 和 log_model API,这使我们能够更好地控制和灵活地记录额外的或自定义的度量和参数。通过执行模型溯源跟踪,我们能够回答许多溯源问题,并提出了几个需要进一步研究的问题,例如使用 MLflow 跟踪多步骤模型管道及其部署。
在下一章,我们将继续我们的学习旅程,学习如何使用 MLflow 进行代码和数据跟踪,这将为我们提供更多的能力,以回答与数据和代码相关的来源问题。
进一步阅读
若要了解本章涉及的更多主题,请查看以下资源:
- 
MLflow Docker 设置参考:
github.com/sachua/mlflow-docker-compose - 
MLflow PyTorch 自动日志记录实现:
github.com/mlflow/mlflow/blob/master/mlflow/pytorch/_pytorch_autolog.py - 
MLflow PyTorch 模型日志记录、加载和注册文档:
www.mlflow.org/docs/latest/python_api/mlflow.pytorch.html - 
MLflow 参数和指标日志记录文档:
www.mlflow.org/docs/latest/python_api/mlflow.html - 
MLflow 模型注册文档:
www.mlflow.org/docs/latest/model-registry.html - 
深入了解大规模来源(使用 SPADE):
queue.acm.org/detail.cfm?id=3476885 - 
如何使用
torchmetrics和lightning-flash:www.exxactcorp.com/blog/Deep-Learning/advanced-pytorch-lightning-using-torchmetrics-and-lightning-flash - 
为什么在多类问题中使用微平均时,精度、召回率和 F1 分数相等?
simonhessner.de/why-are-precision-recall-and-f1-score-equal-when-using-micro-averaging-in-a-multi-class-problem/ 
第四章:第四章:跟踪代码和数据版本
深度学习(DL)模型不仅仅是模型——它们与训练和测试模型的代码,以及用于训练和测试的数据密切相关。如果我们不跟踪用于模型的代码和数据,就无法重现模型或改进它。此外,近期在整个行业范围内对数据中心 AI的觉醒和范式转变(www.forbes.com/sites/gilpress/2021/06/16/andrew-ng-launches-a-campaign-for-data-centric-ai/?sh=5cbacdc574f5)使得数据在构建机器学习(ML)和特别是深度学习(DL)模型中的重要性被提升为首要要素。因此,在本章节中,我们将学习如何使用 MLflow 跟踪代码和数据版本。我们将学习如何跟踪代码和管道版本,并了解如何使用 Delta Lake 进行数据版本管理。在本章节结束时,您将能够理解并实现使用 MLflow 跟踪代码和数据的技巧。
在本章中,我们将涵盖以下主要主题:
- 
跟踪笔记本和管道版本
 - 
跟踪本地私有构建的 Python 库
 - 
在 Delta Lake 中跟踪数据版本
 
技术要求
本章节的技术要求如下:
- 
使用 Jupyter Notebook 扩展的 VS Code:
github.com/microsoft/vscode-jupyter/wiki/Setting-Up-Run-by-Line-and-Debugging-for-Notebooks。 - 
本章节的代码可以在本书的 GitHub 仓库中找到:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter04。 - 
访问 Databricks 实例,以便学习如何使用 Delta Lake 来实现版本化的数据访问。
 
跟踪笔记本和管道版本
数据科学家通常从离线实验 Python 笔记本开始,交互式执行是其主要优势之一。自 .ipynb 时代以来,Python 笔记本已经取得了长足的进展。你可能也无法在 MLflow 跟踪服务器中看到每次使用 Jupyter 笔记本运行的确切 Git 哈希值。关于是否以及何时应该在生产环境中使用 Jupyter 笔记本,存在许多有趣的争论(可以在这里查看讨论:medium.com/mlops-community/jupyter-notebooks-in-production-4e0d38803251)。我们不应在生产环境中使用 Jupyter 笔记本的原因有很多,尤其是在我们需要端到端管道中的可复现性时,单元测试、代码版本控制和依赖管理在大量笔记本的情况下可能会变得困难。Netflix 的开源工具 papermill(papermill.readthedocs.io/en/latest/index.html)在调度、参数化和按工作流方式执行 Jupyter 笔记本方面做出了一些早期创新。然而,Databricks 和 VS Code 的最近创新使得笔记本更容易进行版本控制并与 MLflow 集成。让我们来看一下这两个工具引入的笔记本特性:
- 
交互式执行:Databricks 的笔记本和 VS Code 的笔记本可以像传统的 Jupyter 笔记本一样运行,采用逐单元格执行模式。通过这种方式,你可以立即看到结果的输出。
 - 
.py文件扩展名。这允许对笔记本应用常规的 Python 代码检查(代码格式和风格检查)。 - 
渲染代码单元格和 Markdown 单元格的特殊符号:Databricks 和 VS Code 都利用一些特殊符号将 Python 文件渲染为交互式笔记本。在 Databricks 中,用于将代码分隔成不同可执行单元格的特殊符号如下:
# COMMAND ---------- import mlflow import torch from flash.core.data.utils import download_data from flash.text import TextClassificationData, TextClassifier import torchmetrics 
特殊的 COMMAND 行下方的代码将在 Databricks Web UI 门户中作为可执行单元格进行渲染,如下所示:

图 4.1 – Databricks 可执行单元格
要执行此单元格中的代码,你只需通过右上方的下拉菜单点击 运行单元格。
要向 Databricks 中添加大量文本以描述和评论代码(也称为 Markdown 单元格),你可以在行的开头使用 # MAGIC 符号,如下所示:
# MAGIC %md
# MAGIC #### Notebooks for fine-tuning a pretrained language model to do text-based sentiment classification
然后,这将在 Databricks 笔记本中渲染为一个 Markdown 注释单元格,如下所示:

图 4.2 – Databricks Markdown 文本单元格
在 VS Code 中,这两种类型的单元格使用略有不同的符号集。对于代码单元格,# %% 符号用于单元格块的开头:
# %%
download_data("https://pl-flash-data.s3.amazonaws.com/imdb.zip", "./data/")
datamodule = TextClassificationData.from_csv(
    input_fields="review",
    target_fields="sentiment",
    train_file="data/imdb/train.csv",
    val_file="data/imdb/valid.csv",
    test_file="data/imdb/test.csv"
)
然后,这将在 VS Code 的编辑器中呈现,如下所示:

图 4.3 – VS Code 代码单元格
如您所见,在代码块前面有一个运行单元格按钮,您可以点击该按钮以交互式地运行代码块。如果点击运行单元格按钮,代码块将在编辑器窗口的侧边面板中开始执行,如下所示:

图 4.4 – 在 VS Code 中交互式运行代码
要添加一个包含注释的 Markdown 单元格,请在行的开头添加以下内容,并使用必要的符号:
# %% Notebook for fine-tuning a pretrained language model and sentiment classification
这将确保文本在 VS Code 中不是一个可执行的代码块。
鉴于 Databricks 和 VS Code 笔记本的优点,我们建议使用其中任意一种进行版本控制。我们可以使用 GitHub 来跟踪任何一种类型笔记本的版本,因为它们使用的是常规的 Python 文件格式。
使用 Databricks 笔记本版本控制的两种方法
对于托管的 Databricks 实例,笔记本版本可以通过两种方式进行跟踪:通过查看 Databricks Web UI 中笔记本侧边面板的修订历史,或者通过链接到远程 GitHub 仓库。详细描述可以参考 Databricks 笔记本文档:docs.databricks.com/notebooks/notebooks-use.html#version-control。
虽然 Databricks Web 门户为笔记本版本控制和与 MLflow 实验追踪的集成提供了出色的支持(参见本章中的两种使用 Databricks 笔记本版本控制的方法和Databricks 笔记本中的两种 MLflow 实验类型的提示框),但是在 Databricks 笔记本 Web UI 中编写代码有一个主要的缺点。这是因为与 VS Code 相比,Web UI 不是一个典型的集成开发环境(IDE),在 VS Code 中,代码样式和格式化工具如 flake8(flake8.pycqa.org/en/latest/)和 autopep8(pypi.org/project/autopep8/)可以轻松执行。这对代码质量和可维护性有重大影响。因此,强烈建议使用 VS Code 来编写笔记本代码(无论是 Databricks 笔记本还是 VS Code 笔记本)。
Databricks 笔记本中的两种 MLflow 实验类型
对于一个托管的 Databricks Web 门户实例,您可以执行两种类型的 MLflow 实验:工作区实验和 notebook 实验。工作区实验主要用于一个共享的实验文件夹,该文件夹不绑定到单个 notebook。如果需要,远程代码执行可以写入工作区实验文件夹。另一方面,notebook 范围实验绑定到特定的 notebook,并且可以在 Databricks Web 门户的 notebook 页面上直接通过右上角的 Experiment 菜单项找到。有关更多细节,请查看 Databricks 文档网站:docs.databricks.com/applications/mlflow/tracking.html#experiments。
使用本章的 VS Code notebook fine_tuning.py,该文件可以在本章的 GitHub 仓库中找到(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter04/notebooks/fine_tuning.py),您将能够在 VS Code 编辑器中交互式运行它,并在我们在 第三章 中设置的 MLflow Docker 服务器中记录实验,章节名称为 追踪模型、参数和指标。提醒一下,成功在 VS Code 中运行这个 notebook,您需要按照本章 GitHub 仓库中的 README.md 文件描述的步骤设置虚拟环境 dl_model。它包括以下三个步骤:
conda create -n dl_model python==3.8.10
conda activate dl_model
pip install -r requirements.txt
如果您从头到尾逐个运行此 notebook 单元格,您的实验页面将如下所示:

图 4.5 – 在 VS Code 中交互式运行 notebook 后记录的实验页面
您可能会立即注意到前面的截图中的问题 —— fine_tuning.py 文件。这是因为 VS Code notebook 并未与 MLflow 跟踪服务器原生集成以进行源文件跟踪;它只能显示 VS Code 用于执行 notebook 的 ipykernel(pypi.org/project/ipykernel/)(github.com/microsoft/vscode-jupyter)。不幸的是,这是一个限制,在撰写本文时,无法通过交互式运行 VS Code notebook 来解决实验代码跟踪问题。托管在 Databricks Web UI 中运行的 Databricks notebook 则没有此类问题,因为它们与 MLflow 跟踪服务器有原生集成,该服务器与 Databricks Web 门户捆绑在一起。
然而,由于 VS Code notebook 只是 Python 代码,我们可以在命令行中以 非交互式 方式运行 notebook,如下所示:
python fine_tuning.py
这将在 MLflow 实验页面上无任何问题地记录实际源代码的文件名和 Git 提交哈希,如下所示:

图 4.6 – 在命令行运行 VS Code 笔记本后的已记录实验页面
上面的截图显示了正确的源文件名(fine_tuning.py)和正确的 git 提交哈希(661ffeda5ae53cff3623f2fcc8227d822e877e2d)。这个解决方法不需要我们修改笔记本的代码,并且如果我们已经完成了初步的交互式笔记本调试,并且希望获得完整的笔记本运行结果,同时在 MLflow 跟踪服务器中进行正确的代码版本跟踪,这个方法会非常有用。请注意,所有其他参数、指标和模型都会被正确跟踪,无论我们是否以交互式方式运行笔记本。
管道跟踪
在讨论完笔记本代码跟踪(版本和文件名)之后,让我们转到管道跟踪的话题。然而,在讨论管道跟踪之前,我们首先需要了解管道在 ML/DL 生命周期中的定义。从概念上讲,管道是一个多步骤的数据处理和任务工作流。然而,数据/任务工作流的实现可能有很大的不同。管道可以在一些机器学习包中被定义为一流的 Python API。最著名的两个管道 API 如下:
- 
sklearn.pipeline.Pipeline(scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html): 这是广泛用于构建紧密集成的多步骤管道,用于经典机器学习或数据 提取、转换和加载(ETL)管道,使用 pandas DataFrames(https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html)。 - 
pyspark.ml.Pipeline(spark.apache.org/docs/latest/api/python/reference/api/pyspark.ml.Pipeline.html): 这是 PySpark 版本的工具,用于构建简单且紧密集成的多步骤管道,适用于机器学习或数据 ETL 管道,使用 Spark DataFrames(https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html)。 
然而,当我们构建一个深度学习(DL)模型管道时,我们需要在管道的不同步骤中使用多个不同的 Python 包,因此,使用一个通用的单一管道 API 通常无法满足需求。此外,前面提到的管道 API 都不原生支持当前流行的深度学习包,如Huggingface或PyTorch-Lightning,这些需要额外的集成工作。尽管存在一些开源的深度学习管道 API,如Neuraxle(github.com/Neuraxio/Neuraxle),它尝试提供类似 sklearn 的管道接口和框架,但并未得到广泛使用。此外,使用这些基于 API 的管道意味着当你需要向管道中添加更多步骤时,可能会被限制,从而降低了你在新需求出现时扩展或发展深度学习管道的灵活性。
在本书中,我们将采用不同的方法来定义并构建一个基于 MLflow 的fine_tuning.py的深度学习管道,并将其转变为一个多步骤管道。这个管道可以被可视化为一个三步流程图,如下所示:

图 4.7 – 一个三步的深度学习管道
这个三步流程如下:
- 
将数据下载到本地执行环境
 - 
微调模型
 - 
注册模型
 
这些模块化步骤可能对我们当前的示例来说有些过于复杂,但当涉及更多复杂性或每个步骤需要更改时,具有独立功能步骤的优势就显现出来。如果我们定义了需要在步骤之间传递的参数,每个步骤都可以独立修改,而不会影响其他步骤。每个步骤都是一个独立的 Python 文件,可以通过一组输入参数独立执行。将会有一个主管道 Python 文件,可以运行整个管道或管道的一个子部分。在MLproject文件中,这是一个没有文件扩展名的标准 YAML 文件,我们可以定义四个入口点(main、download_data、fine_tuning_model 和 register_model),它们所需的输入参数、参数类型和默认值,以及执行每个入口点的命令行。在我们的示例中,这些入口点将通过 Python 命令行执行命令提供。然而,如果某些步骤需要特定的执行方式,你也可以调用其他执行方式,比如批处理脚本。例如,本章中的MLproject文件的以下行(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter04/MLproject)描述了项目的名称、conda环境定义文件的文件名以及主入口点:
name: dl_model_chapter04
conda_env: conda.yaml
entry_points:
  main:
    parameters:
      pipeline_steps:
        description: Comma-separated list of dl pipeline steps to execute 
        type: str
        default: all
    command: "python main.py --steps {pipeline_steps}"
这里,项目名称是 dl_model_chapter04。conda_env 指的是本地 conda 环境的 YAML 定义文件 conda.yaml,该文件与 MLproject 文件位于同一目录。entry_points 部分列出了第一个入口点,名为 main。在 parameters 部分,有一个名为 pipeline_steps 的参数,允许用户定义一个逗号分隔的 DL 流水线步骤列表以供执行。该参数的类型是 str,默认值是 all,意味着所有流水线步骤都会执行。最后,command 部分列出了如何在命令行中执行此步骤。
MLproject 文件的其余部分通过遵循与主入口点相同的语法规则来定义另外三个流水线步骤入口点。例如,以下几行在同一个 MLproject 文件中定义了 download_data 的入口点:
  download_data:
    parameters:
      download_url:
        description: a url to download the data for fine tuning a text sentiment classifier
        type: str
        default: https://pl-flash-data.s3.amazonaws.com/imdb.zip
      local_folder:
        description: a local folder to store the downloaded data
        type: str
        default: ./data
      pipeline_run_name:
        description: an mlflow run name
        type: str
        default: chapter04
    command:
      "python pipeline/download_data.py --download_url {download_url} --local_folder {local_folder} \
      --pipeline_run_name {pipeline_run_name}"
download_data 部分,类似于主入口点,也定义了参数列表、类型和默认值,以及执行此步骤的命令行。我们可以像在本书的 GitHub 仓库中查看的 MLproject 文件一样,按相同的方式定义其余步骤。更多详细信息,请查看该 MLproject 文件的完整内容。
在定义了 MLproject 文件后,很明显我们已经以声明的方式定义了一个多步骤的流水线。这就像是一个流水线的规范,说明了每个步骤的名称、期望的输入参数以及如何执行它们。现在,下一步是实现 Python 函数来执行流水线的每个步骤。所以,让我们看看主入口点的 Python 函数的核心实现,它叫做 main.py。以下代码行(不是 main.py 中的整个 Python 代码)展示了只用一个流水线步骤(download_data)来实现整个流水线的核心组件:
@click.command()
@click.option("--steps", default="all", type=str)
def run_pipeline(steps):
    with mlflow.start_run(run_name='pipeline', nested=True) as active_run:
        download_run = mlflow.run(".", "download_data", parameters={})
if __name__ == "__main__":
    run_pipeline()
此主函数代码片段包含一个run_pipeline函数,在命令行中执行main.py文件时将运行该函数。有一个称为steps的参数,在提供时将传递给该函数。在此示例中,我们使用了click Python 包 (click.palletsprojects.com/en/8.0.x/) 来解析命令行参数。run_pipeline函数通过调用mlflow.start_run启动一个 MLflow 实验运行,并传递两个参数(run_name和nested)。我们之前使用过run_name,它是此运行的描述性短语。然而,nested参数是新的,这意味着这是一个父实验运行。这个父实验运行包含一些将在 MLflow 中层次跟踪的子实验运行。每个父运行可以包含一个或多个子运行。在示例代码中,这包含管道运行的一个步骤,称为download_data,通过调用mlflow.run来调用。这是调用 MLproject 入口点的关键 MLflow 函数。一旦调用了download_data并且运行已完成,父运行也将完成,从而结束管道的运行。
执行 MLproject 入口点的两种方式
执行 MLproject 入口点有两种方式。首先,您可以使用 MLflow 的 Python API,称为mlflow.run (www.mlflow.org/docs/latest/python_api/mlflow.projects.html#mlflow.projects.run)。另外,您可以使用 MLflow 的命令行界面工具,称为mlflow run,可以在命令行 shell 环境中直接调用以执行任何入口点 (www.mlflow.org/docs/latest/cli.html#mlflow-run)。
现在,让我们学习如何通用地实现管道中的每个步骤。对于每个管道步骤,我们将 Python 文件放在一个pipeline文件夹中。在本例中,我们有三个文件:download_data.py、fine_tuning_model.py和register_model.py。因此,成功构建支持 MLflow 的管道项目所需的相关文件如下:
MLproject
conda.yaml
main.py
pipeline/download_data.py
pipeline/fine_tuning_model.py
pipeline/register_model.py
对于每个管道步骤的实现,我们可以使用以下 Python 函数模板。一个占位符部分用于实现实际的管道步骤逻辑:
import click
import mlflow
@click.command()
@click.option("input")
def task(input):
    with mlflow.start_run() as mlrun:
        # Implement pipeline step logic here 
        mlflow.log_parameter('parameter', parameter)
        mlflow.set_tag('pipeline_step', __file__)
        mlflow.log_artifacts(artifacts, artifact_path="data")
if __name__ == '__main__':
    task()
此模板允许我们标准化实施管道步骤任务的方式。主要思想是,对于每个管道步骤任务,需要从mlflow.start_run开始启动一个 MLflow 实验运行。一旦我们在函数中实现了具体的执行逻辑,我们需要使用mlflow.log_parameter记录一些参数,或者在工件存储中使用mlflow.log_artifacts记录一些工件,这些参数和工件可以传递并由管道的下一步使用。这就是所谓的mlflow.set_tag。
例如,在download_data.py步骤中,核心实现如下:
import click
import mlflow
from flash.core.data.utils import download_data
@click.command()
@click.option("--download_url")
@click.option("--local_folder")
@click.option("--pipeline_run_name")
def task(download_url, local_folder, pipeline_run_name):
    with mlflow.start_run(run_name=pipeline_run_name) as mlrun:
        download_data(download_url, local_folder)
        mlflow.log_param("download_url", download_url)
        mlflow.log_param("local_folder", local_folder)
        mlflow.set_tag('pipeline_step', __file__)
        mlflow.log_artifacts(local_folder, artifact_path="data")
if __name__ == '__main__':
    task()
在这个download_data.py实现中,任务是将模型构建所需的数据从远程 URL 下载到本地文件夹(download_data(download_url, local_folder))。完成这一步后,我们将记录一些参数,比如download_url和local_folder。我们还可以使用mlflow.log_artifacts将新下载的数据记录到 MLflow 工件存储中。对于这个例子来说,可能看起来不太必要,因为我们只是在本地开发环境中执行下一步。然而,在一个更现实的分布式执行环境中,每个步骤可能会在不同的执行环境中运行,这种做法是非常有用的,因为我们只需要将工件 URL 路径传递给管道的下一步使用;我们不需要知道上一步是如何以及在哪里执行的。在这个例子中,当mlflow.log_artifacts(local_folder, artifact_path="data")语句被调用时,下载的数据文件夹会被上传到 MLflow 工件存储。然而,在本章中,我们不会使用这个工件路径来执行下游管道步骤。稍后在本书中,我们将探讨如何使用这种工件存储将工件传递到管道的下一步。在这里,我们将使用日志记录的参数将下载的数据路径传递给管道的下一步(mlflow.log_param("local_folder", local_folder))。所以,让我们看看如何通过扩展main.py,使其包含下一步,即fine_tuning_model入口点,如下所示:
        with mlflow.start_run(run_name='pipeline', nested=True) as active_run:
            download_run = mlflow.run(".", "download_data", parameters={})
            download_run = mlflow.tracking.MlflowClient().get_run(download_run.run_id)
            file_path_uri = download_run.data.params['local_folder']
            fine_tuning_run = mlflow.run(".", "fine_tuning_model", parameters={"data_path": file_path_uri})
我们使用mlflow.tracking.MlflowClient().get_run来获取download_run的 MLflow 运行对象,然后使用download_run.data.params来获取file_path_uri(在这个例子中,它只是一个本地文件夹路径)。然后将其作为键值参数(parameters={"data_path": file_path_uri})传递给下一个mlflow.run,即fine_tuning_run。通过这种方式,fine_tuning_run管道步骤可以使用这个参数来作为数据源路径的前缀。这是一个非常简化的场景,用来说明我们如何将数据从一个步骤传递到下一个步骤。使用由 MLflow 提供的mlflow.tracking.MlflowClient()API(www.mlflow.org/docs/latest/python_api/mlflow.tracking.html),可以轻松访问运行信息(参数、指标和工件)。
我们还可以通过添加register_model步骤,扩展main.py文件中的管道第三步。这次,我们需要日志记录的模型 URI 来注册已训练的模型,这取决于fine_tuning_model步骤的run_id。因此,在fine_tuning_model步骤中,我们需要获取fine_tuning_model运行的run_id属性,然后将其作为输入参数传递给register_model运行,如下所示:
fine_tuning_run_id = fine_tuning_run.run_id
register_model_run = mlflow.run(".", "register_model", parameters={"mlflow_run_id": fine_tuning_run_id})
现在,register_model 步骤可以使用 fine_tuning_run_id 来定位已注册的模型。register_model 步骤的核心实现如下:
    with mlflow.start_run() as mlrun:
        logged_model = f'runs:/{mlflow_run_id}/model'
        mlflow.register_model(logged_model, registered_model_name)
这将在由 logged_model 变量定义的 URI 中注册一个微调模型到 MLflow 模型注册表。
如果您已经按照这些步骤操作,那么您应该已经有了一个可以由 MLflow 端到端跟踪的工作管道。提醒一下,前提条件是您已经设置好本地完整的 MLflow 服务器,如在 第三章**,跟踪模型、参数和指标 中所示。您应该已经在上一节中设置了虚拟环境 dl_model。要测试这个管道,请访问本章的 GitHub 仓库 github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter04 并运行以下命令:
python main.py
这将运行整个三步管道,并将管道的 run_id(即父运行)以及每个步骤的运行作为子运行记录在 MLflow 跟踪服务器中。当运行完成时,控制台屏幕的最后几行输出会显示如下内容(运行管道时,您将看到许多屏幕输出):

图 4.8 – 运行带有 MLflow run_ids 的管道的控制台输出
这显示了管道的 run_id,它是 f8f21fdf8fff4fd6a400eeb403b776c8;最后一步是 fine_tuning_model 的 run_id 属性,它是 5ba38e059695485396e709b809e9bb8d。如果我们通过点击 http://localhost 进入 MLflow 跟踪服务器的 UI 页面,我们应该能看到如下在 dl_model_chapter04 实验文件夹中的嵌套实验运行:

图 4.9 – 一个包含三步嵌套子步骤运行的管道在 MLflow 跟踪服务器中运行
上面的截图展示了管道运行,以及源 main.py 文件和管道三步的嵌套运行。每个步骤都有一个在 MLproject 中定义的对应入口点名称,并带有 register_model 运行页面的 GitHub 提交哈希代码版本,您将看到以下信息:

图 4.10 – 在 MLflow 跟踪服务器上,入口点 register_model 的运行页面
上面的截图不仅展示了我们已经看到的一些熟悉信息,还展示了一些新信息,例如 file:///、GitHub 哈希码版本、入口点 (-e register_model)、执行环境(本地开发环境 -b local)以及 register_model 函数的预期参数(-P)。我们将在本书后面学习如何使用 MLflow 的 MLproject 运行命令来远程执行任务。这里,我们只需要理解源代码是通过入口点(register_model)引用的,而不是通过文件名本身,因为该引用在 MLproject 文件中已声明为入口点。
如果你在 MLflow 跟踪服务器中看到了图 4.9和图 4.10中显示的输出,那么是时候庆祝了——你已经成功地使用 MLflow 执行了一个多步骤的深度学习管道!
总结一下,要在 MLflow 中跟踪一个多步骤的深度学习管道,我们可以使用 MLproject 来定义每个管道步骤的入口点以及主管道的入口点。在主管道函数中,我们实现方法以便可以在管道步骤之间传递数据。然后,每个管道步骤使用已经共享的数据以及其他输入参数来执行特定任务。主管道级别的函数以及管道的每个步骤都通过 MLflow 跟踪服务器进行跟踪,该服务器生成一个父级 run_id 来跟踪主管道的运行,并生成多个嵌套的 MLflow 运行来跟踪每个管道步骤。我们为每个管道步骤介绍了一个模板,以标准化地实现此任务。我们还通过 MLflow 的 run 参数和工件存储探索了强大的管道链式操作,学习了如何在管道步骤之间传递数据。
现在你已经知道如何跟踪笔记本和管道,接下来我们来学习如何跟踪 Python 库。
跟踪本地私有构建的 Python 库
现在,让我们将注意力转向跟踪本地私有构建的 Python 库。对于公开发布的 Python 库,我们可以在 requirements 文件或 conda.yaml 文件中明确指定其发布的版本,这些版本通常发布在 PyPI 上。例如,本章的 conda.yaml 文件 (github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter04/conda.yaml) 定义了 Python 版本,并提供了对 requirements 文件的引用,如下所示:
name: dl_model 
channels:
  - conda-forge
dependencies:
  - python=3.8.10
  - pip
  - pip:
    - -r requirements.txt
Python 版本被定义为 3.8.10,并且被强制执行。该 conda.yaml 文件还引用了一个 requirements.txt 文件,其中包含以下版本化的 Python 包,该文件位于与 conda.yaml 文件相同的目录中:
ipykernel==6.4.1
lightning-flash[all]==0.5.0
mlflow==1.20.2
transformers==4.9.2
boto3==1.19.7
pytorch-lightning==1.4.9
datasets==1.9.0
click==8.0.3
如我们所见,所有这些包都在显式地使用它们发布的 PyPI(pypi.org/)版本号进行追踪。当你运行 MLflow 的MLproject时,MLflow 将使用conda.yaml文件和引用的requirements.txt文件动态创建 conda 虚拟环境。这确保了执行环境的可重现性,并且所有的深度学习模型管道都能够成功运行。你可能注意到,第一次运行上一节的 MLflow 管道项目时,已经为你创建了这样的虚拟环境。你可以通过运行以下命令再次执行此操作:
conda env list
这将生成当前机器中 conda 虚拟环境的列表。你应该能够找到一个以mlflow-为前缀,后跟一串字母和数字的虚拟环境,如下所示:
mlflow-95353930ddb7b60101df80a5d64ef8bf6204a808
这是由 MLflow 动态创建的虚拟环境,它遵循在conda.yaml和requirements.txt中指定的依赖项。随后,当你记录经过微调的模型时,conda.yaml和requirements.txt将自动记录到 MLflow 工件存储中,如下所示:

图 4.11 – Python 包正在被记录并追踪到 MLflow 工件存储中
如我们所见,conda.yaml文件已自动扩展,包含了requirements.txt的内容,以及 conda 决定要包含的其他依赖项。
对于私有构建的 Python 包(即未公开发布到 PyPI 供公众使用和引用的 Python 包),推荐的方式是使用git+ssh来包含该 Python 包。假设你有一个名为cool-dl-utils的私有构建项目,你所在的组织叫做cool_org,并且你的项目仓库已在 GitHub 上设置。如果你想在 requirements 文件中包含该项目的 Python 包,你需要确保将你的公钥添加到 GitHub 设置中。如果你想了解如何生成公钥并将其加载到 GitHub,请参考 GitHub 的指南:docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account。在requirements.txt文件中,你可以添加如下内容,这将引用一个特定的 GitHub 哈希(81218891bbf5a447103884a368a75ffe65b17a44)和从该私有仓库构建的 Python .egg 包(如果愿意,也可以引用.whl包):
cool-dl-utils @ git+ssh://git@github.com/cool_org/cool-dl-utils.git@81218891bbf5a447103884a368a75ffe65b17a44#egg=cool-dl-utils
如果你在自己构建的包中有一个数字版本,你也可以直接在requirements.txt文件中引用该版本号,如下所示:
git+ssh://git@github.com/cool_org/cool-dl-utils.git@2.11.4
这里cool-dl-utils的发布版本是2.11.4。这使得 MLflow 能够将这个私有构建的包拉入虚拟环境中执行MLproject。在这一章中,我们不需要引用任何私有构建的 Python 包,但值得注意的是,MLflow 可以利用git+ssh方式来实现这一点。
现在,让我们学习如何跟踪数据版本。
在 Delta Lake 中跟踪数据版本
在这一部分,我们将学习如何在 MLflow 中跟踪数据。历史上,数据管理和版本控制通常被视为与机器学习和数据科学不同的领域。然而,数据驱动的 AI 的兴起在深度学习中扮演着越来越重要的角色。因此,了解数据如何以及用于何种目的来改进深度学习模型是至关重要的。在 2021 年夏季,由 Andrew Ng 组织的第一届数据驱动 AI 竞赛中,获胜的要求并不是改变和调优模型,而是改进一个固定模型的数据集(https-deeplearning-ai.github.io/data-centric-comp/)。这是竞赛网页上的一句话:
"数据驱动的 AI 竞赛颠覆了传统格式,要求你在给定固定模型的情况下改进数据集。我们将提供一个数据集,供你通过应用数据驱动的技术来改进,比如修正错误标签、添加代表边缘案例的示例、应用数据增强等。"
这一范式的转变突显了数据在深度学习中的重要性,尤其是在监督学习中,标签数据尤为重要。一个潜在的假设是,不同的数据将产生不同的模型指标,即使使用相同的模型架构和参数。这要求我们仔细跟踪数据版本控制过程,以便知道哪个版本的数据正在用来生成获胜的模型。
在 ML/DL 生命周期中,出现了几个用于跟踪数据版本的框架。DVC(dvc.org)是这个领域的早期先驱之一。它使用类似 GitHub 的命令来拉取/推送数据,就像操作代码一样。它允许将数据远程存储在 S3 或 Google Drive 等多个流行存储中。然而,存储在远程存储中的数据会被哈希化,并且无法被人类阅读。这会产生“锁定”问题,因为访问数据的唯一方法是通过 DVC 工具和配置。此外,追踪数据及其架构如何变化也很困难。尽管可以将 MLflow 与 DVC 集成,但其可用性和灵活性不如我们所期望的那样好。因此,在本书中我们不会深入探讨这种方法。如果你对此感兴趣,我们建议你参考本章末尾的使用 DVC 和 AWS 进行 ML 项目中的数据和模型版本管理,以获得有关 DVC 的更多细节。
最近开源且基于开放格式的Delta Lake(delta.io/)是一个集成数据管理和版本控制的实际解决方案,特别是因为 MLflow 可以直接支持这种集成。这也是基础数据管理层,称为Lakehouse(databricks.com/blog/2020/01/30/what-is-a-data-lakehouse.html),它将数据仓库和流数据统一到一个数据基础层。它支持模式变更跟踪和数据版本控制,非常适合深度学习/机器学习数据使用场景。Delta 表是基于一种名为Parquet(parquet.apache.org/)的开放标准文件格式,它在大规模数据存储中得到了广泛支持。
Databricks 中的 Delta 表
请注意,本节假设你可以访问 Databricks 服务,允许你在Databricks 文件系统(DBFS)中实验 Delta Lake 格式。你可以通过访问 Databricks 门户:community.cloud.databricks.com/login.html来获取社区版的试用账户。
请注意,本节需要使用PySpark通过读取/写入数据与存储(如 S3)进行数据操作。Delta Lake 具有一个称为时间旅行(Time Travel)的功能,可以自动对数据进行版本控制。通过传递像时间戳或版本号这样的参数,你可以读取该特定版本或时间戳的任何历史数据。这使得重现和追踪实验变得更加容易,因为数据的唯一时间元数据就是数据的版本号或时间戳。查询 Delta 表有两种方式:
- 
timestampAsOf:此选项让你能够读取 Delta 表,同时读取具有特定时间戳的版本。以下代码展示了如何使用timestampAsOf来读取数据:df = spark.read \ .format("delta") \ .option("timestampAsOf", "2020-11-01") \ .load("/path/to/my/table") - 
versionAsOf:此选项定义了 Delta 表的版本号。你也可以选择读取具有特定版本的版本,从版本 0 开始。以下 PySpark 代码使用versionAsOf选项读取版本52的数据:df = spark.read \ .format("delta") \ .option("versionAsOf", "52") \ .load("/path/to/my/table") 
拥有这种带有时间戳或版本的访问权限是使用 Delta 表追踪任何文件版本的关键优势。所以,让我们在 MLflow 中看一个具体的例子,这样我们就可以追踪我们一直在使用的 IMDb 数据集。
使用 MLflow 追踪数据的示例
对于我们一直用来微调情感分类模型的 IMDb 数据集,我们将这些 CSV 文件上传到 Databricks 的数据存储中,或者上传到你可以从 Databricks 门户访问的任何 S3 存储桶中。完成后,按照以下步骤创建一个支持版本化和时间戳数据访问的 Delta 表:
- 
将以下 CSV 文件读取到数据框中(假设你已将
train.csv文件上传到 Databricks 的FileStore/imdb/文件夹):imdb_train_df = spark.read.option('header', True).csv('dbfs:/FileStore/imdb/train.csv') - 
将
imdb_train_df数据框写入 DBFS 作为 Delta 表,如下所示:imdb_train_df.write.format('delta').option("mergeSchema", "true").mode("overwrite").save('/imdb/training.delta') - 
使用以下命令将
training.delta文件重新读入内存:imdb_train_delta = spark.read.format('delta').load('/imdb/training.delta') - 
现在,通过 Databricks UI 查看 Delta 表的历史记录。在从存储读取 Delta 表到内存后,点击 History 标签:
 

图 4.12 – train_delta 表的历史记录,包含版本和时间戳列
上述截图显示版本为 0,时间戳为 2021-11-22。这是我们在传递版本号或时间戳给 Spark DataFrame 阅读器时,可以用来访问版本化数据的值。
- 
使用以下命令读取版本化的
imdb/train_delta文件:train_data_version = spark.read.format("delta").option("versionAsOf", "0").load('/imdb/train.delta') 
这将读取 train.delta 文件的版本 0。如果我们有其他版本的此文件,可以传递不同的版本号。
- 
使用以下命令读取带时间戳的
imdb/train_delta文件:train_data_timestamped = spark.read.format("delta").option("timestampAsOf", "2021-11-22T03:39:22").load('/imdb/train.delta') 
这将读取带有时间戳的数据。在写作时,这是我们拥有的唯一时间戳,没问题。如果我们有更多带时间戳的数据,可以传递不同的版本。
- 
现在,如果我们需要在 MLflow 跟踪实验运行中记录这个数据版本,我们只需使用
mlflow.log_parameter()记录数据的路径、版本号和/或时间戳。这将作为实验参数键值对的一部分进行记录:mlflow.log_parameter('file_path', '/imdb/train.delta') mlflow.log_parameter('file_version', '0') mlflow.log_parameter('file_timestamp', '2021-11-22T03:39:22') 
使用 Delta 表的唯一要求是数据需要存储在支持 Delta 表的存储形式中,例如 Databricks 支持的 Lakehouse。这对企业级 ML/DL 场景具有重要价值,因为我们可以与代码和模型版本一起跟踪数据版本。
总结一下,Delta Lake 提供了一种简单而强大的方式来进行数据版本管理。MLflow 可以轻松地将这些版本号和时间戳作为实验参数列表的一部分进行记录,以便一致地跟踪数据,以及所有其他参数、指标、工件、代码和模型。
总结
在本章中,我们深入探讨了如何在 MLflow 实验运行中跟踪代码和数据版本。我们首先回顾了不同类型的笔记本:Jupyter 笔记本、Databricks 笔记本和 VS Code 笔记本。我们对比了它们,并推荐使用 VS Code 来编写笔记本,因为它具有 IDE 支持,以及 Python 风格、自动补全和许多其他丰富功能。
然后,在回顾现有 ML 管道 API 框架的局限性之后,我们讨论了如何使用 MLflow 的run_id创建一个多步骤的 DL 管道,并为每个管道步骤使用子run_id。通过将参数或工件存储位置传递到下一个步骤,可以使用mlflow.run()和mlflow.tracking.MlflowClient()来进行管道链式操作和跟踪。我们成功地使用 MLflow 的嵌套运行跟踪功能运行了一个端到端的三步管道。这也为我们未来章节中分布式方式运行不同步骤的 MLproject 使用扩展提供了可能。
我们还学习了如何使用git+ssh方法跟踪私有构建的 Python 包。然后,我们使用 Delta Lake 方法获得了版本化和时间戳的数据访问权限。这使得数据可以通过版本号或时间戳两种方式进行跟踪。然后,MLflow 可以在实验运行期间将这些版本号或时间戳作为参数记录。由于我们正进入数据驱动的 AI 时代,能够跟踪数据版本对于可重复性和时间旅行至关重要。
至此,我们已经完成了使用 MLflow 全面跟踪代码、数据和模型的学习。在下一章,我们将学习如何以分布式方式扩展我们的 DL 实验。
进一步阅读
关于本章所涵盖主题的更多信息,请查看以下资源:
- 
在 Databricks 中使用 MLflow 笔记本实验跟踪:
docs.databricks.com/applications/mlflow/tracking.html#create-notebook-experiment - 
构建多步骤工作流:
www.mlflow.org/docs/latest/projects.html#building-multistep-workflows - 
使用 MLflow 项目的端到端机器学习管道:
dzlab.github.io/ml/2020/08/09/mlflow-pipelines/ - 
安装私有构建的 Python 包:
medium.com/@ffreitasalves/pip-installing-a-package-from-a-private-repository-b57b19436f3e - 
在 ML 项目中使用 DVC 和 AWS 进行数据和模型版本控制:
medium.com/analytics-vidhya/versioning-data-and-models-in-ml-projects-using-dvc-and-aws-s3-286e664a7209 - 
为大规模数据湖引入 Delta 时间旅行:
databricks.com/blog/2019/02/04/introducing-delta-time-travel-for-large-scale-data-lakes.html - 
我们如何赢得首届数据中心人工智能竞赛:Synaptic-AnN:
www.deeplearning.ai/data-centric-ai-competition-synaptic-ann/ - 
重现任何事物:机器学习遇上数据湖屋:
databricks.com/blog/2021/04/26/reproduce-anything-machine-learning-meets-data-lakehouse.html - 
DATABRICKS COMMUNITY EDITION:初学者指南:
www.topcoder.com/thrive/articles/databricks-community-edition-a-beginners-guide 
第三部分 – 在大规模下运行深度学习管道
在本节中,我们将学习如何在不同的执行环境中运行深度学习(DL)管道,并进行超参数调优,或超参数优化(HPO),以实现大规模处理。我们将从概述执行 DL 管道的场景和要求开始。接着,我们将学习如何使用 MLflow 的命令行接口(CLI)在分布式环境中运行四种不同的执行场景。之后,我们将通过比较Ray Tune、Optuna和HyperOpt,学习如何选择最佳的 HPO 框架来调优 DL 管道的超参数。最后,我们将重点讨论如何利用最新的 HPO 框架,如 Ray Tune 和 MLflow,实施并运行大规模的 DL 超参数优化。
本节包括以下章节:
- 
第五章,在不同环境中运行深度学习管道
 - 
第六章,大规模运行超参数调优
 
第五章:第五章:在不同环境中运行 DL 管道
在不同执行环境中运行 深度学习 (DL) 管道的灵活性至关重要,例如在本地、远程、企业内部或云中运行。这是因为在 DL 开发的不同阶段,可能会有不同的约束或偏好,目的是提高开发速度或确保安全合规性。例如,进行小规模模型实验时,最好在本地或笔记本环境中进行,而对于完整的超参数调优,我们需要在云托管的 GPU 集群上运行模型,以实现快速的迭代时间。考虑到硬件和软件配置中的多样化执行环境,过去在单一框架内实现这种灵活性是一个挑战。MLflow 提供了一个易于使用的框架,能够在不同环境中按规模运行 DL 管道。本章将教你如何实现这一点。
在本章中,我们将首先了解不同的 DL 管道执行场景及其执行环境。我们还将学习如何在不同的执行环境中运行 DL 管道的不同步骤。具体而言,我们将涵盖以下主题:
- 
不同执行场景和环境的概述
 - 
本地运行本地代码
 - 
在 GitHub 中远程运行本地代码
 - 
在云中远程运行本地代码
 - 
在云中远程运行,并使用 GitHub 中的远程代码
 
本章结束时,你将能够熟练地设置 DL 管道,以便在不同执行环境中本地或远程运行。
技术要求
完成本章学习所需的技术要求如下:
- 
本章的代码可以在以下 GitHub URL 找到:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter05。 - 
安装 Databricks 命令行界面 (CLI) 工具以访问 Databricks 平台并远程执行 DL 管道:
github.com/databricks/databricks-cli。 - 
访问 Databricks 实例(必须是企业版,因为社区版不支持远程执行),以学习如何在 Databricks 集群上远程运行 DL 管道。
 - 
在本地运行时使用完整的 MLflow 跟踪服务器。此 MLflow 跟踪服务器设置与前几章相同。
 
不同执行场景和环境的概述
在前几章中,我们主要集中在学习如何使用 MLflow 的跟踪功能来跟踪深度学习管道。我们的执行环境大多数是在本地环境中,比如本地笔记本电脑或桌面环境。然而,正如我们所知,深度学习完整生命周期由多个阶段组成,其中我们可能需要在不同的执行环境中完全、部分地或单独运行深度学习管道。以下是两个典型的例子:
- 
在访问用于模型训练的数据时,通常需要数据处于符合企业安全性和隐私合规要求的环境中,在这种环境下,计算和存储不能超出合规边界。
 - 
在训练深度学习(DL)模型时,通常希望使用远程 GPU 集群来最大化模型训练的效率,因为本地笔记本电脑通常不具备所需的硬件能力。
 
这两种情况都需要仔细定义的执行环境,这种环境可能在深度学习生命周期的一个或多个阶段中有所需求。需要注意的是,这不仅仅是为了在从开发阶段过渡到生产环境时的灵活性要求,因生产环境中的执行硬件和软件配置通常会有所不同。这也是一个要求,即能够在开发阶段或不同的生产环境中切换运行环境,而不需要对深度学习管道进行重大修改。
在这里,我们根据深度学习管道源代码和目标执行环境的不同组合,将不同的场景和执行环境分类为以下四种场景,如下表所示:

图 5.1 – 深度学习管道源代码和目标执行环境的四种不同场景
图 5.1 描述了在开发或生产环境中,我们可能遇到的使用本地或远程代码在不同执行环境中运行的情况。我们将逐一检查这些情况,如下所示:
- 
本地源代码在本地目标环境中运行:这通常发生在开发阶段,在本地环境中适度的计算能力足以支持快速原型开发或对现有管道的小变更进行测试运行。这也是我们在前几章中使用的场景,尤其是在学习如何跟踪管道时,使用了 MLflow 的实验。
 - 
本地源代码在远程目标环境中运行:这通常发生在开发阶段或重新训练现有深度学习模型时,在这种情况下,需要使用 GPU 或其他类型的硬件加速器,如张量处理单元(TPU)或现场可编程门阵列(FPGA),来执行计算密集型和数据密集型的模型训练或调试,以便在合并 GitHub 仓库(首先使用本地代码更改)之前进行调试。
 - 
远程源代码在本地目标环境中运行:通常发生在我们代码没有更改,但数据发生变化时,无论是在开发阶段还是生产阶段。例如,在 DL 开发阶段,我们可能会通过某些数据增强技术(例如,使用AugLy来增强现有训练数据:
github.com/facebookresearch/AugLy)或新的注释训练数据来改变数据。在生产部署步骤中,我们经常需要运行回归测试,以评估待部署的 DL 管道在保留的回归测试数据集上的表现,这样我们就不会在模型性能精度指标未达到标准时部署一个性能下降的模型。在这种情况下,保留的测试数据集通常不大,因此执行可以在本地部署服务器上完成,而不需要启动到 Databricks 服务器的远程集群。 - 
远程源代码在远程目标环境中运行:这通常发生在开发阶段或生产阶段,当我们希望使用 GitHub 上的固定版本的 DL 管道代码,在远程 GPU 集群上进行模型训练、超参数调整或再训练时。此类大规模执行可能非常耗时,而远程 GPU 集群在这种情况下非常有用。
 
鉴于这四种不同的场景,最好能有一个框架,在这些条件下以最小的配置更改来运行相同的 DL 管道。在 MLflow 出现之前,支持这些场景需要大量的工程和手动工作。MLflow 提供了一个 MLproject 框架,通过以下三种可配置机制来支持这四种场景:
- 
入口点:我们可以定义一个或多个入口点来执行深度学习(DL)管道的不同步骤。例如,以下是定义一个主入口点的示例:
entry_points: main: parameters: pipeline_steps: { type: str, default: all } command: "python main.py –pipeline_steps {pipeline_steps}" 
入口点的名称是main,默认情况下,当执行 MLflow 运行时如果没有指定 MLproject 的入口点,则会使用该入口点。在这个main入口点下,有一组参数列表。我们可以使用简短的语法定义参数的类型和默认值,如下所示:
parameter_name: {type: data_type, default: value}
我们还可以使用长语法,如下所示:
parameter_name:
  type: data_type
  default: value
在这里,我们只定义了一个参数,叫做pipeline_steps,使用简短的语法格式,类型为str,默认值为all。
- 
yaml配置文件或 Docker 镜像用来定义 MLproject 入口点可以使用的软件和库依赖。请注意,一个 MLproject 只能使用 condayaml文件或 Docker 镜像中的一个,而不能同时使用两者。根据深度学习管道的依赖,有时使用 conda .yaml文件而非 Docker 镜像更为合适,因为它更加轻量,且更容易修改,无需额外的 Docker 镜像存储位置或在资源有限的环境中加载大型 Docker 镜像。然而,如果在运行时需要 Java 包(.jar),那么使用 Docker 镜像可能会有优势。如果没有这样的 JAR 依赖,那么更推荐使用 conda .yaml文件来指定依赖。此外,MLflow 版本 1.22.0 以后,在 Databricks 上运行基于 Docker 的项目尚未得到 MLflow 命令行支持。如果确实有 Java 包依赖,可以通过yaml配置文件来定义执行环境依赖,本书中会有介绍。 - 
硬件依赖:我们可以使用集群配置 JSON 文件来定义执行目标后端环境,无论是 GPU、CPU 还是其他类型的集群。当目标后端执行环境非本地时,才需要这个配置,无论是在 Databricks 服务器还是 Kubernetes(K8s)集群中。
 
之前,我们学习了如何使用 MLproject 创建一个多步骤的深度学习管道,在本地环境中运行,如 第四章《跟踪代码和数据版本控制》,用于跟踪目的。现在,我们将学习如何使用 MLproject 支持前面提到的不同运行场景。
在本地运行带有本地代码的项目
让我们从第一个运行场景开始,使用相同的 自然语言处理(NLP) 文本情感分类示例作为驱动案例。建议您从 GitHub 上获取以下版本的源代码,以便跟随步骤并学习: github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/26119e984e52dadd04b99e6f7e95f8dda8b59238/chapter05。请注意,这需要一个特定的 Git 哈希提交版本,如 URL 路径所示。这意味着我们要求您检查一个特定的提交版本,而不是主分支。
让我们从下载评论数据到本地存储的深度学习管道开始,作为第一次执行练习。检查完本章的代码后,您可以输入以下命令行来执行深度学习管道的第一步:
mlflow run . --experiment-name='dl_model_chapter05' -P pipeline_steps='download_data'
如果我们没有指定入口点,它默认是 main。在这种情况下,这是我们期望的行为,因为我们希望运行 main 入口点来启动父级深度学习管道。
点表示当前本地目录。这告诉 MLflow 使用当前目录中的代码作为执行项目的源。如果此命令行运行成功,你应该能够在控制台中看到前两行输出,如下所示,同时也可以看到目标执行环境的位置:
2022/01/01 19:15:37 INFO mlflow.projects.utils: === Created directory /var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmp3qj2kws2 for downloading remote URIs passed to arguments of type 'path' ===
2022/01/01 19:15:37 INFO mlflow.projects.backend.local: === Running command 'source /Users/yongliu/opt/miniconda3/bin/../etc/profile.d/conda.sh && conda activate mlflow-95353930ddb7b60101df80a5d64ef8bf6204a808 1>&2 && python main.py --pipeline_steps download_data' in run with ID 'f7133b916a004c508e227f00d534e136' ===
请注意,第二行输出显示了 mlflow.projects.backend.local,这意味着目标运行环境是本地的。你可能会好奇我们在初始命令行中在哪里定义了本地执行环境。事实证明,默认情况下,名为 --backend(或 -b)的参数的值是 local。因此,如果我们列出默认值,mlflow run 命令行将如下所示:
mlflow run . -e main -b local --experiment-name='dl_model_chapter05' -P pipeline_steps='download_data'
请注意,我们还需要在命令行中或通过名为 MLFLOW_EXPERIMENT_NAME 的环境变量指定 experiment-name,以定义此项目将运行的实验。或者,你可以指定一个 experiment-id 参数,或者一个名为 MLFLOW_EXPERIMENT_ID 的环境变量,以定义已经存在的实验整数 ID。你只需要定义环境的 ID 或名称之一,而不是两者。通常我们会定义一个人类可读的实验名称,然后在代码的其他部分查询该实验的 ID,以确保它们不会不同步。
运行 MLproject 的 MLflow 实验名称或 ID
要使用 CLI 或 mlflow.run Python API 运行一个 MLproject,如果我们没有通过环境变量或参数赋值指定 experiment-name 或 experiment-id,它将默认使用 Default MLflow 实验。这并不是我们想要的,因为我们希望将实验组织成明确分开的实验。此外,一旦 MLproject 开始运行,任何子运行将无法切换到不同的实验名称或 ID。因此,最佳实践是始终在启动 MLflow 项目运行之前指定实验名称或 ID。
一旦你完成运行,你将看到如下的输出:
2022-01-01 19:15:48,249 <Run: data=<RunData: metrics={}, params={'download_url': 'https://pl-flash-data.s3.amazonaws.com/imdb.zip',
 'local_folder': './data',
 'mlflow run id': 'f9f74ebd80f246d58a5f7a3bfb3fc635',
 'pipeline_run_name': 'chapter05'}, tags={'mlflow.gitRepoURL': 'git@github.com:PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow.git',
 'mlflow.parentRunId': 'f7133b916a004c508e227f00d534e136',
请注意,这是一个嵌套的 MLflow 运行,因为我们首先启动一个 main 入口点来启动整个管道(这就是为什么有 mlflow.parentRunId),然后在这个管道下,我们运行一个或多个步骤。这里我们运行的步骤叫做 download_data,这是在 MLproject 中定义的另一个入口点,但它是通过 mlflow.run Python API 调用的,如下所示,在 main.py 文件中:
download_run = mlflow.run(".", "download_data", parameters={})
请注意,这还指定了要使用的代码源(local,因为我们指定了一个 点),并默认使用本地执行环境。这就是为什么你应该能够在控制台输出中看到以下内容的原因:
 'mlflow.project.backend': 'local',
 'mlflow.project.entryPoint': 'download_data',
你还应该能看到该入口点的运行参数的其他几个细节。命令行输出的最后两行应如下所示:
2022-01-01 19:15:48,269 finished mlflow pipeline run with a run_id = f7133b916a004c508e227f00d534e136
2022/01/01 19:15:48 INFO mlflow.projects: === Run (ID 'f7133b916a004c508e227f00d534e136') succeeded ===
如果你看到这个输出,你应该感到自豪,因为你已经成功运行了一个包含一个步骤的管道,并且已完成。
虽然我们之前也做过类似的事情,虽然没有了解其中的一些细节,但接下来的部分将让我们能够在本地环境中运行远程代码,你将看到 MLproject 的灵活性和功能越来越强大。
在本地运行 GitHub 上的远程代码
现在,让我们看看如何在本地执行环境中运行 GitHub 仓库中的远程代码。这让我们能够准确地运行一个特定版本,该版本已被提交到 GitHub 仓库中并使用提交哈希标识。我们继续使用之前的例子,在本章节中运行 DL 管道的单个 download_data 步骤。在命令行提示符中,运行以下命令:
mlflow run https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05 -v 26119e984e52dadd04b99e6f7e95f8dda8b59238  --experiment-name='dl_model_chapter05' -P pipeline_steps='download_data'
注意这条命令行和前一节中的命令行之间的区别。我们不再用一个 点 来表示本地代码的副本,而是指向一个远程的 GitHub 仓库(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow),并指定包含我们想要引用的 MLproject 文件的文件夹名称(chapter05)。# 符号表示相对于根文件夹的路径,这是根据 MLflow 的约定来定义的(详情请参考 MLflow 文档:www.mlflow.org/docs/latest/projects.html#running-projects)。然后我们通过指定 Git 提交哈希,使用 -v 参数来定义版本号。在这种情况下,它是我们在 GitHub 仓库中的这个版本:
运行 GitHub 的主分支时,MLflow 项目的隐性 Bug
当我们在 MLflow 运行中省略 -v 参数时,MLflow 会假设我们想使用 GitHub 项目的默认 main 分支。然而,MLflow 的源代码中硬编码了对 GitHub 项目 main 分支的引用,作为 origin.refs.master,这要求 GitHub 项目中必须存在 master 分支。这在新的 GitHub 项目中不起作用,例如本书中的项目,因为默认分支已经不再叫 master,而是叫做 main,这是由于 GitHub 最近的更改所导致的(详见:github.com/github/renaming)。因此,在写这本书时,MLflow 版本 1.22.0 无法运行 GitHub 项目的默认 main 分支。我们需要在运行 GitHub 仓库中的 MLflow 项目时,明确声明 Git 提交哈希版本。
那么,当你在运行 MLflow 项目时使用远程 GitHub 项目仓库中的代码会发生什么呢?当你看到以下控制台输出的第一行时,这个问题就会变得清晰。
2021/12/30 18:57:32 INFO mlflow.projects.utils: === Fetching project from https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05 into /var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmpdyzaa1ye ===
这意味着 MLflow 会代表用户开始将远程项目克隆到一个本地临时文件夹,路径为/var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmpdyzaa1ye。
如果你导航到这个临时文件夹,你会看到整个 GitHub 项目的内容都已经被克隆到这个文件夹,而不仅仅是包含你要运行的 ML 项目的文件夹。
其余的控制台输出就像我们在使用本地代码时看到的一样。完成 download_data 步骤的运行后,你应该能够在 chapter05 下的临时文件夹中找到下载的数据,因为我们在 ML 项目文件中将本地目标文件夹定义为相对于路径 ./data:
local_folder: { type: str, default: ./data }
MLflow 会自动将其转换为绝对路径,然后变成相对路径,指向 chapter05 下的克隆项目文件夹,因为 MLproject 文件就位于该文件夹中。
这种能够引用远程 GitHub 项目并在本地环境中运行的能力,无论这个本地环境是你的笔记本还是云端的虚拟机,都非常强大。它使得通过持续集成和持续部署(CI/CD)实现自动化成为可能,因为这一过程可以直接在命令行中调用,并且可以被编写成 CI/CD 脚本。追踪部分也非常精准,因为我们有在 MLflow 跟踪服务器中记录的 Git 提交哈希,这使得我们能够准确知道执行的是哪个版本的代码。
请注意,在我们刚才讨论的两种场景中,执行环境是发出 MLflow run 命令的本地机器。MLflow 项目会同步执行至完成,这意味着它是一个阻塞调用,运行过程中会实时显示控制台输出的进度。
然而,我们需要支持一些额外的运行场景。例如,有时发出 MLflow 项目运行命令的机器不够强大,无法支持我们所需的计算任务,比如训练一个需要多个 epoch 的深度学习模型。另一个场景可能是,训练所需的数据达到多个 GB,你不希望将其下载到本地笔记本进行模型开发。这要求我们能够在远程集群中运行代码。接下来我们将看看如何实现这一点。
在云端远程运行本地代码
在前面的章节中,我们在本地笔记本环境中运行了所有代码,并且由于笔记本的计算能力有限,我们将深度学习微调步骤限制为仅三个 epoch。这能够实现代码的快速运行和本地环境的测试,但并不能真正构建一个高性能的深度学习模型。我们实际上需要在远程 GPU 集群中运行微调步骤。理想情况下,我们应该只需更改一些配置,仍然在本地笔记本控制台中发出 MLflow run 命令,但实际的流水线将提交到云端的远程集群。接下来,我们将看看如何在我们的深度学习流水线中实现这一点。
我们从向 Databricks 服务器提交代码开始。需要三个前提条件:
- 
企业版 Databricks 服务器:您需要访问一个企业许可的 Databricks 服务器或 Databricks 服务器的免费试用版(
docs.databricks.com/getting-started/try-databricks.html#sign-up-for-a-databricks-free-trial)在云端。Databricks 的社区版不支持此远程执行。 - 
Databricks CLI:您需要在运行 MLflow 项目命令的地方设置 Databricks CLI。要安装它,只需运行以下命令:
pip install databricks-cli 
我们还在 chapter05 的 requirements.txt 文件中包括了这个依赖,当您获取本章代码时。
.databrickscfg文件位于您的本地主文件夹中。您不需要同时存在两者,但如果有两个,使用环境变量定义的文件会在 Databricks 命令行中优先被选取。使用环境变量和生成访问令牌的方法在第一章,“深度学习生命周期与 MLOps 挑战”中的 设置 MLflow 与远程 MLflow 服务器交互 部分有详细描述。请注意,这些环境变量可以直接在命令行中设置,也可以放入.bash_profile文件中,如果您使用的是 macOS 或 Linux 机器。
这里我们描述了如何使用 Databricks 命令行工具生成 .databrickscfg 文件:
- 
运行以下命令来设置令牌配置:
databricks configure --token - 
按照提示填写远程 Databricks 主机 URL 和访问令牌:
Databricks Host (should begin with https://): https://???? Token: dapi?????????? - 
现在,如果您检查本地主文件夹,应该会找到一个名为
.databrickscfg的隐藏文件。 
如果您打开这个文件,应该能看到类似以下内容:
[DEFAULT]
host = https://??????
token = dapi???????
jobs-api-version = 2.0 
请注意,最后一行指示的是 Databricks 服务器正在使用的远程作业提交和执行 API 版本。
现在您已经正确设置了访问权限,让我们看看如何使用以下步骤在远程 Databricks 服务器上远程运行 DL 流水线:
- 
由于我们将使用远程 Databricks 服务器,因此之前设置的本地 MLflow 服务器不再有效。这意味着我们需要在
main.py文件中禁用并注释掉以下几行,这些行仅对本地 MLflow 服务器配置有用(从 GitHub 获取最新版本的chapter05代码以跟随步骤:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow.git):os.environ["MLFLOW_TRACKING_URI"] = http://localhost os.environ["MLFLOW_S3_ENDPOINT_URL"] = http://localhost:9000 os.environ["AWS_ACCESS_KEY_ID"] = "minio" os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123" 
相反,我们应该使用以下环境变量,它可以在 .bash_profile 文件中定义,或者直接在命令行中执行:
export MLFLOW_TRACKING_URI="databricks"
这将使用 Databricks 服务器上的 MLflow 跟踪服务器。如果您不指定这一点,它将默认为 localhost,但由于远程 Databricks 服务器上没有 localhost 版本的 MLflow,因此会失败。因此,请确保正确设置此项。现在,我们已准备好在远程运行本地代码。
- 
现在,运行以下命令行将本地代码提交到远程 Databricks 服务器进行运行。我们将从
download_data步骤开始,如下所示:mlflow run . -b databricks --backend-config cluster_spec.json --experiment-name='/Shared/dl_model_chapter05' -P pipeline_steps ='download_data' 
您将看到这次命令行有两个新参数:-b databricks,它指定后端为 Databricks 服务器,和--backend-config cluster_spec.json,它详细说明了集群规范。这个cluster_spec.json文件的内容如下:
{
    "new_cluster": {
        "spark_version": "9.1.x-gpu-ml-scala2.12",
        "num_workers": 1,
        "node_type_id": "g4dn.xlarge"
    }
}
该cluster_spec.json文件通常位于与 MLproject 文件相同的文件夹中,并且需要预先定义,以便 MLflow 运行命令可以找到它。我们在这里提供的示例仅定义了创建 Databricks 作业集群所需的最小参数集,使用 AWS 的 GPU 虚拟机作为单节点,但如果需要,您可以创建一个更丰富的集群规范(请参阅下面的Databricks 集群规范框以获取更多详细信息)。
Databricks 集群规范
在向 Databricks 提交作业时,需要创建一个新的作业集群,这与您已有的交互式集群不同,后者可以通过附加笔记本来运行交互式作业。集群规范是通过最小化指定 Databricks 运行时版本来定义的,在我们当前的示例中是9.1.x-gpu-ml-scala2.12,还包括工作节点的数量和节点类型 ID,如我们的示例所示。建议为学习目的使用g4dn.xlarge。在这个集群规范中,您还可以定义许多其他配置,包括存储和访问权限,以及init脚本。生成有效集群规范 JSON 文件的最简单方法是使用 Databricks 门户 UI 创建一个新集群,您可以选择 Databricks 运行时版本、集群节点类型以及其他参数(docs.databricks.com/clusters/create.html)。然后,您可以通过点击创建集群UI 页面右上角的 JSON 链接来获取集群的 JSON 表示(见图 5.2)。

图 5.2 - 在 Databricks 上创建集群的示例
还需要注意,前面命令中的experiment-name参数不再仅仅接受一个实验名称字符串,而是需要包括 Databricks 工作区中的绝对路径。这与本地的 MLflow 跟踪服务器不同。为了使远程作业提交能够正常工作,必须遵循这一约定。注意,如果你希望拥有多个级别的子文件夹结构,例如下面的结构,那么每个子文件夹必须已经在 Databricks 服务器中存在:
/rootPath/subfolder1/subfolder2/my_experiment_name
这意味着rootPath、subfolder1和subfolder2文件夹必须已经存在。如果没有,这条命令会失败,因为它无法在 Databricks 服务器上自动创建父文件夹。最后那一串my_experiment_name如果不存在,可以自动创建,因为它是实际的实验名称,将承载所有实验的运行。注意,在这个例子中,我们使用命令行参数来指定实验名称,但也可以使用环境变量来指定,方法如下:
export MLFLOW_EXPERIMENT_NAME=/Shared/dl_model_chapter05
- 
一旦执行此命令,你会发现这次控制台输出信息明显比上次在本地环境中的运行要简短。这是因为以这种方式执行代码时,它是异步执行的,这意味着作业被提交到远程的 Databricks 服务器并立即返回控制台,而不需要等待。让我们看一下输出的前三行:
INFO: '/Shared/dl_model_chapter05' does not exist. Creating a new experiment 2022/01/06 17:35:32 INFO mlflow.projects.databricks: === Uploading project to DBFS path /dbfs/mlflow-experiments/427565/projects-code/f1cbec57b21eabfca52f417f8482054bbea22be 9205b5bbde461780d809924c2.tar.gz === 2022/01/06 17:35:32 INFO mlflow.projects.databricks: === Finished uploading project to /dbfs/mlflow-experiments/427565/projects-code/f1cbec57b21eabfca52f417f8482054bbea22be 9205b5bbde461780d809924c2.tar.gz === 
第一行意味着实验在 Databricks 服务器上不存在,因此正在创建。如果你第二次运行该命令,这一行就不会出现。第二行和第三行描述了 MLflow 将 MLproject 打包成.tar.gz文件并上传到 Databricks 文件服务器的过程。注意,与 GitHub 项目需要从仓库中检出整个项目不同,这里只需要打包chapter05文件夹,因为我们的 MLproject 就位于该文件夹内。这可以通过查看 Databricks 集群中的作业运行日志来确认,我们将在接下来的几段中解释(如何获取作业 URL 以及如何查看日志)。
MLproject 的同步与异步运行
官方的 MLflow 运行 CLI 不支持一个参数来指定以异步或同步模式运行 MLflow 项目。然而,MLflow 运行 Python API 确实有一个名为synchronous的参数,默认设置为True。当使用 MLflow 的 CLI 通过 Databricks 作为后台运行 MLflow 作业时,默认行为是异步的。有时,在 CI/CD 自动化过程中,当您需要确保 MLflow 运行在移动到下一步之前成功完成时,同步行为是有用的。官方的 MLflow 运行 CLI 无法做到这一点,但您可以编写一个包装 CLI 的 Python 函数,调用 MLflow 的 Python API,并将同步模式设置为True,然后使用您自己的 CLI Python 命令以同步模式运行 MLflow 作业。还要注意,mlflow.run()是mlflow.projects.run()API 的高级流畅(面向对象)API。为了保持一致性,我们在本书中广泛使用mlflow.run()API。有关 MLflow 运行 Python API 的详细信息,请参见官方文档页面:www.mlflow.org/docs/latest/python_api/mlflow.projects.html#mlflow.projects.run。
输出的接下来的几行看起来类似于以下内容:
2022/01/06 17:48:31 INFO mlflow.projects.databricks: === Running entry point main of project . on Databricks ===
2022/01/06 17:48:31 INFO mlflow.projects.databricks: === Launched MLflow run as Databricks job run with ID 279456. Getting run status page URL... ===
2022/01/06 17:48:31 INFO mlflow.projects.databricks: === Check the run's status at https://???.cloud.databricks.com#job/168339/run/1 ===
这些行描述了作业已提交到 Databricks 服务器,并且作业运行 ID 和作业网址显示在最后一行(将???替换为您的实际 Databricks 网址,以便使其对您有效)。请注意,MLflow 运行 ID 是279456,与作业网址中看到的 ID(168339)不同。这是因为作业网址由 Databricks 作业管理系统管理,并且有不同的方式来生成和追踪每个实际的作业。
- 点击作业网址链接(
https://???.cloud.databricks.com#job/168339/run/1)查看此作业的状态,该页面将显示进度以及标准输出和错误日志(参见图 5.3)。通常,这个页面需要几分钟才能开始显示运行进度,因为它需要先基于cluster_spec.json创建一个全新的集群,才能开始运行作业。 

图 5.3 – MLflow 运行作业状态页面,显示标准输出
图 5.3显示作业已成功完成(chapter05文件夹已上传并在Databricks 文件系统(DBFS)中提取)。如前所述,只有我们想要运行的 MLproject 被打包、上传并在 DBFS 中提取,而不是整个项目仓库。
在同一个作业状态页面上,您还会找到标准错误部分,显示描述我们要运行的管道步骤download_data的日志。这些不是错误,而只是信息性消息。所有 Python 日志都会在这里聚合。详情请参见图 5.4:

图 5.4 – 在作业状态页面记录的 MLflow 作业信息
图 5.4 显示了与我们在本地交互式环境中运行时非常相似的日志,但现在这些运行是在我们提交作业时指定的集群中执行的。请注意,图 5.4 中的流水线实验 ID 是 427565。你应该能够在 Databricks 服务器上的集成 MLflow 跟踪服务器中,使用实验 ID 427565,通过以下 URL 模式找到成功完成的 MLflow DL 流水线运行:
https://[your databricks hostname]/#mlflow/experiments/427565
如果你看到与前几章中看到的熟悉的跟踪结果,给自己一个大大的拥抱,因为你刚刚完成了在远程 Databricks 集群中运行本地代码的一个重要学习里程碑!
此外,我们可以使用这种方法运行 DL 流水线的多个步骤,而无需更改每个步骤的实现代码。例如,如果我们想同时运行 DL 流水线的 download_data 和 fine_tuning_model 步骤,我们可以发出以下命令:
mlflow run . -b databricks --backend-config cluster_spec.json --experiment-name='/Shared/dl_model_chapter05' -P pipeline_steps='download_data,fine_tuning_model'
输出控制台将显示以下简短信息:
2022/01/07 15:22:39 INFO mlflow.projects.databricks: === Uploading project to DBFS path /dbfs/mlflow-experiments/427565/projects-code/743cadfec82a55b8c76e9f27754cfdd516545b155254e990c2cc62650b8af959.tar.gz ===
2022/01/07 15:22:40 INFO mlflow.projects.databricks: === Finished uploading project to /dbfs/mlflow-experiments/427565/projects-code/743cadfec82a55b8c76e9f27754cfdd516545b155254e990c2cc62650b8af959.tar.gz ===
2022/01/07 15:22:40 INFO mlflow.projects.databricks: === Running entry point main of project . on Databricks ===
2022/01/07 15:22:40 INFO mlflow.projects.databricks: === Launched MLflow run as Databricks job run with ID 279540\. Getting run status page URL... ===
2022/01/07 15:22:40 INFO mlflow.projects.databricks: === Check the run's status at https://?????.cloud.databricks.com#job/168429/run/1 ===
然后,你可以转到控制台输出最后一行中显示的作业 URL 页面,等待它创建一个新集群并完成两个步骤。完成后,你应该能够在 MLflow 跟踪服务器中找到两个步骤,使用相同的实验 URL(因为我们使用的是相同的实验名称)。
https://[your databricks hostname]/#mlflow/experiments/427565
现在我们知道如何在远程 Databricks 集群中运行本地代码,我们将学习如何在远程 Databricks 集群中运行来自 GitHub 仓库的代码。
在云端远程运行,远程代码来自 GitHub
重现 DL 流水线最可靠的方法是指向 GitHub 中的项目代码的特定版本,然后在云端运行它,而不调用任何本地资源。这样,我们就能知道代码的确切版本,并使用项目中定义的相同运行环境。让我们看看如何在我们的 DL 流水线中实现这一点。
作为前提和提醒,在发出 MLflow 运行命令之前,以下三个环境变量需要设置好,以完成本节的学习:
export MLFLOW_TRACKING_URI=databricks
export DATABRICKS_TOKEN=[databricks_token]
export DATABRICKS_HOST='https://[your databricks host name/'
我们已经知道如何从上一节设置这些环境变量。可能还需要进行一次额外的设置,即如果你的 GitHub 仓库是非公开的,需要允许 Databricks 服务器访问该仓库(请参阅下面的 GitHub Token 用于让 Databricks 访问非公开或企业项目仓库 说明框)。
GitHub Token 用于让 Databricks 访问非公开或企业项目仓库
为了让 Databricks 访问 GitHub 上的项目仓库,还需要另一个令牌。可以通过访问个人 GitHub 页面(https://github.com/settings/tokens)并按照页面上描述的步骤生成。然后,您可以按照 Databricks 文档网站上的说明进行设置:docs.databricks.com/repos.html#configure-your-git-integration-with-databricks。
现在,让我们使用 GitHub 仓库中的特定版本,在远程 Databricks 集群上运行完整的管道:
mlflow run https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05 -v 395c33858a53bcd8ac217a962ab81e148d9f1d9a -b databricks --backend-config cluster_spec.json --experiment-name='/Shared/dl_model_chapter05' -P pipeline_steps='all'
然后我们会看到简短的六行输出。让我们看看每行显示的重要信息以及其工作原理:
- 
第一行显示项目仓库的内容已下载到本地的位置:
2022/01/07 17:36:54 INFO mlflow.projects.utils: === Fetching project from https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05 into /var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmpzcepn5h5 === 
如果我们去到本地机器上执行此命令时消息中显示的临时目录,我们会看到整个仓库已经下载到此文件夹:/var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmpzcepn5h5。
- 
接下来的两行显示项目内容已被压缩并上传到 Databricks 服务器上的 DBFS 文件夹:
2022/01/07 17:36:57 INFO mlflow.projects.databricks: === Uploading project to DBFS path /dbfs/mlflow-experiments/427565/projects-code/fba3d31e1895b78f40227b5965461faddb 61ec9df906fb09b161f74efaa90aa2.tar.gz === 2022/01/07 17:36:57 INFO mlflow.projects.databricks: === Finished uploading project to /dbfs/mlflow-experiments/427565/projects-code/fba3d31e1895b78f40227b5965461faddb61ec 9df906fb09b161f74efaa90aa2.tar.gz === 
如果我们使用 Databricks 的本地命令行工具,我们可以像对待本地文件一样列出这个 .tar.gz 文件(但实际上,它位于 Databricks 服务器的远程位置):
databricks fs ls -l dbfs:/mlflow-experiments/427565/projects-code/fba3d31e1895b78f40227b5965461faddb61ec 9df906fb09b161f74efaa90aa2.tar.gz
你应该看到类似下面的输出,它描述了文件的属性(大小、所有者/组 ID,及其是否为文件或目录):
file  3070  fba3d31e1895b78f40227b5965461faddb61ec 9df906fb09b161f74efaa90aa2.tar.gz  1641605818000
- 
下一行显示它开始运行 GitHub 项目的
main入口点:2022/01/07 17:36:57 INFO mlflow.projects.databricks: === Running entry point main of project https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05 on Databricks === 
注意,当我们运行本地代码时的不同(项目后面有一个 点,表示当前目录),现在它列出了 GitHub 仓库位置的完整路径。
- 
最后两行输出与前一部分的输出类似,列出了作业 URL:
2022/01/07 17:36:57 INFO mlflow.projects.databricks: === Launched MLflow run as Databricks job run with ID 279660\. Getting run status page URL... === 2022/01/07 17:36:57 INFO mlflow.projects.databricks: === Check the run's status at https://????.cloud.databricks.com#job/168527/run/1 === - 
如果我们点击控制台输出最后一行中的作业 URL,我们将能够在该网站上看到以下内容(图 5.5):
 

图 5.5 – 使用 GitHub 仓库中的代码的 MLflow 作业状态页面
图 5.5 显示了该作业的最终状态。请注意,页面的标题现在显示为 MLflow Run for https://github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow#chapter05,而不是之前使用本地代码运行时显示的 MLflow Run for .。
任务的状态显示该任务已成功运行,你还会看到结果被记录在实验页面中,和之前一样,所有三个步骤都已完成。模型也已按预期注册在模型注册表中,在 Databricks 服务器下的以下 URL:
https://[your_databricks_hostname]/#mlflow/models/dl_finetuned_model
总结来说,这种方法的工作机制在下图中展示(图 5.6):

图 5.6 – 在远程 Databricks 集群服务器上运行远程 GitHub 代码的总结视图
图 5.6显示了三个不同的位置(一个是我们发出 MLflow 运行命令的机器,一个是远程 Databricks 服务器,另一个是远程 GitHub 项目)。当发出 MLflow 运行命令时,远程 GitHub 项目的源代码会被克隆到发出命令的机器上,然后上传到远程 Databricks 服务器,并提交任务来执行深度学习管道的多个步骤。这是异步执行的,任务的状态需要根据创建的任务 URL 进行监控。
在其他后端上运行 MLflow 项目
目前,Databricks 支持两种类型的远程运行后端环境:Databricks 和 K8s。然而,从 MLflow 1.22.0 版本开始(www.mlflow.org/docs/latest/projects.html#run-an-mlflow-project-on-kubernetes-experimental),在 K8s 上运行 MLflow 项目仍处于实验模式,并且可能会发生变化。如果你有兴趣了解更多内容,请参考进一步阅读部分中的参考资料,探索提供的示例。此外,还有其他由第三方提供的后端(也称为社区插件),如hadoop-yarn(github.com/criteo/mlflow-yarn)。由于 Databricks 在所有主要云提供商中都有提供,并且在支持符合企业安全合规的生产场景方面较为成熟,本书目前重点讲解如何在 Databricks 服务器上远程运行 MLflow 项目。
摘要
在本章中,我们学习了如何在不同的执行环境(本地或远程 Databricks 集群)中运行 DL 管道,使用的是本地源代码或 GitHub 项目代码。这不仅对于重现性和执行 DL 管道的灵活性至关重要,还通过使用 CI/CD 工具提供了更好的生产力和未来自动化的可能性。能够在资源丰富的远程环境中运行一个或多个 DL 管道步骤,使我们能够以更快的速度执行大规模计算和数据密集型任务,这通常出现在生产质量的 DL 模型训练和微调中。这使我们在必要时能够进行超参数调优或交叉验证 DL 模型。接下来的章节我们将开始学习如何进行大规模的超参数调优,这是我们自然的下一步。
进一步阅读
- 
MLflow 运行项目参数(适用于命令行和 Python API):
www.mlflow.org/docs/latest/projects.html#running-projects - 
MLflow 运行命令行(CLI)文档:
www.mlflow.org/docs/latest/cli.html#mlflow-run - 
在 Databricks 上运行 MLflow 项目:
www.mlflow.org/docs/latest/projects.html#run-an-mlflow-project-on-databricks - 
在 K8s 上运行 MLflow 项目的示例:
github.com/SameeraGrandhi/mlflow-on-k8s/tree/master/examples/LogisticRegression - 
在 Azure 上运行 MLflow 项目:
docs.microsoft.com/en-us/azure/machine-learning/how-to-train-mlflow-projects 
第六章:第六章:大规模运行超参数调优
超参数调优或超参数优化(HPO)是一种在合理的计算资源约束和时间框架内,找到最佳深度神经网络结构、预训练模型类型以及模型训练过程的程序。这里的超参数是指在机器学习训练过程中无法改变或学习的参数,例如深度神经网络中的层数、预训练语言模型的选择,或训练过程中的学习率、批量大小和优化器。在本章中,我们将使用 HPO 作为超参数调优和优化过程的简称。HPO 是生成高性能机器学习/深度学习模型的关键步骤。由于超参数的搜索空间非常大,因此在大规模上高效地运行 HPO 是一个主要挑战。与经典的机器学习模型相比,深度学习模型的评估复杂性和高成本进一步加剧了这些挑战。因此,我们需要学习最先进的 HPO 方法和实现框架,实现越来越复杂和可扩展的 HPO 方法,并通过 MLflow 跟踪它们,以确保调优过程是可复现的。在本章结束时,你将能够熟练地实现适用于深度学习模型管道的可扩展 HPO。
在本章中,首先,我们将概述不同的自动化 HPO 框架以及深度学习模型调优的应用。此外,我们还将了解应该优化哪些内容以及何时选择使用哪些框架。我们将比较三种流行的 HPO 框架:HyperOpt、Optuna和Ray Tune。我们将展示在大规模运行 HPO 时,哪一种框架是最合适的选择。接着,我们将重点学习如何创建适合 HPO 的深度学习模型代码,这些代码可以使用 Ray Tune 和 MLflow。之后,我们将展示如何轻松切换到使用不同的 HPO 算法,并以 Optuna 为主要示例进行说明。
在本章中,我们将涵盖以下主题:
- 
了解深度学习管道的自动化 HPO
 - 
使用 Ray Tune 和 MLflow 创建适合 HPO 的深度学习模型
 - 
使用 MLflow 运行第一个 Ray Tune HPO 实验
 - 
使用 Optuna 和 HyperBand 运行 Ray Tune HPO
 
技术要求
要理解本章的示例,以下是所需的关键技术要求:
- 
Ray Tune 1.9.2:这是一个灵活且强大的超参数调优框架(
docs.ray.io/en/latest/tune/index.html)。 - 
Optuna 2.10.0:这是一个命令式的、按运行定义的超参数调优 Python 包(
optuna.org/)。 - 
本章的代码可以在以下 GitHub 地址找到,其中还包括包含前述关键包和其他依赖项的
requirements.txt文件:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter06。 
理解深度学习管道中的自动超参数优化
自动超参数优化(HPO)已经研究了超过二十年,自 1995 年首次发布有关该主题的已知论文以来(www.sciencedirect.com/science/article/pii/B9781558603776500451)。人们普遍理解,调优机器学习模型的超参数可以提升模型的性能——有时,甚至能显著提升。近年来,深度学习(DL)模型的崛起催生了一股新的创新浪潮,并推动了新框架的开发,以应对深度学习管道的超参数优化问题。这是因为,深度学习模型管道带来了许多新的、大规模的优化挑战,而这些挑战不能轻易通过以往的超参数优化方法解决。需要注意的是,与模型训练过程中可以学习到的模型参数不同,超参数集必须在训练之前设定。
超参数优化与迁移学习微调的区别
在本书中,我们一直专注于一种成功的深度学习方法——迁移学习(有关详细讨论,请参阅第一章,深度学习生命周期与 MLOps 挑战)。迁移学习过程中的关键步骤是用一些任务和领域特定的标注数据来微调一个预训练模型,以获得一个良好的任务特定深度学习模型。然而,微调步骤仅仅是模型训练过程中的一种特殊训练方式,它同样涉及许多超参数需要优化。这就是超参数优化发挥作用的地方。
超参数类型及其挑战
有几种类型的超参数可以用于深度学习管道:
- 
深度学习模型类型与架构:在迁移学习的情况下,选择使用哪些预训练模型是一个可能的超参数。例如,Hugging Face模型库中有超过 27,000 个预训练模型(
huggingface.co/models),包括BERT、RoBERTa等。对于特定的预测任务,我们可能会尝试其中的几个模型,决定哪个模型是最适合的。 - 
学习和训练相关的参数:这些包括不同类型的优化器,如随机梯度下降(SGD)和Adam(你可以在
machinelearningknowledge.ai/pytorch-optimizers-complete-guide-for-beginner/查看 PyTorch 优化器的完整列表)。它还包括相关的参数,如学习率和批量大小。建议在适用时,首先根据其重要性顺序调整神经网络模型的以下参数:学习率、动量、迷你批量大小、隐藏层数量、学习率衰减和正则化(arxiv.org/abs/2003.05689)。 - 
数据和管道配置:深度学习管道可能包括数据处理和转换步骤,这些步骤可能会影响模型训练。例如,如果我们想比较带有或不带有签名文本主体的电子邮件消息的分类模型的性能,那么就需要一个超参数来决定是否包含电子邮件签名。另一个例子是当我们没有足够的数据或数据的变化时,我们可以尝试使用各种数据增强技术,这些技术会为模型训练提供不同的输入集(
neptune.ai/blog/data-augmentation-nlp)。 
提醒一下,并非所有超参数都是可调的或需要调整的。例如,在深度学习模型中,训练轮次不需要调整。这是因为当准确度指标停止提升或不再有改善的可能时,训练应停止。这被称为早停或剪枝,是支撑一些近期最先进超参数优化(HPO)算法的关键技术之一(关于早停的更多讨论,请参考databricks.com/blog/2019/08/15/how-not-to-scale-deep-learning-in-6-easy-steps.html)。
请注意,所有这三类超参数可以进行混合搭配,整个超参数空间的配置可能非常庞大。例如,如果我们希望选择一个预训练模型的类型作为超参数(例如,选择可能是BERT或RoBERTa),再加上两个与学习相关的参数(如学习率和批处理大小),以及两种不同的 NLP 文本数据增强技术(如随机插入和同义词替换),那么我们就有五个超参数需要优化。请注意,每个超参数可能有许多不同的候选值可以选择,如果每个超参数有 5 个不同的值,那么我们总共有 55 = 3125 种超参数组合需要尝试。在实际应用中,通常需要尝试几十个超参数,每个超参数可能有几十个选择或分布可供采样。这很容易导致维度灾难问题(insaid.medium.com/automated-hyperparameter-tuning-988b5aeb7f2a)。这种高维搜索空间的挑战由于 DL 模型训练和评估成本高昂而变得更加复杂;我们知道,即使是一个小型 BERT 的 1 个周期(我们在前几章中尝试过),使用一个小规模的训练和验证数据集也可能需要 1 到 2 分钟。现在想象一个实际的生产级 DL 模型,若要进行 HPO,可能需要数小时、数天,甚至数周,如果没有高效执行的话。通常,以下是需要大规模应用高性能 HPO 的主要挑战:
- 
超参数的高维搜索空间
 - 
随着 DL 模型越来越大,模型训练和评估时间的高成本
 - 
用于生产环境中 DL 模型的生产时间和部署
同时进行模型训练和 HPO
在训练过程中是可以动态改变超参数的。这是一种混合方法,它同时进行模型训练和 HPO,例如基于种群的训练(PBT;
deepmind.com/blog/article/population-based-training-neural-networks)。然而,这并不改变这样一个事实:当开始新的训练周期时,一组超参数需要预先定义。PBT 是尝试减少搜索高维超参数空间和深度学习(DL)模型训练成本的创新之一。感兴趣的读者可以查阅进一步阅读部分,深入了解这个话题。 
现在我们已经了解了优化超参数的一般挑战和类别,接下来让我们看看 HPO 是如何工作的,以及如何选择适合我们使用的框架。
HPO 是如何工作的,以及如何选择
有不同的方式理解 HPO 的工作原理。经典的 HPO 方法包括网格搜索和随机搜索,其中会选择一组具有候选值范围的超参数。每个超参数组合独立运行,直到完成,然后根据我们找到的最佳模型性能指标,从我们运行的试验中挑选出最佳的超参数配置。虽然这种搜索方法易于实现,甚至可能不需要复杂的框架来支持,但它本质上是低效的,且由于 HPO 的非凸性质,可能找不到最佳的超参数配置。非凸的意思是存在多个局部最小值或最大值,优化方法可能无法找到全局最优(即最小值或最大值)。简单来说,现代的 HPO 需要做两件事:
- 
超参数的自适应采样(也称为配置选择或CS):这意味着需要通过利用先前的知识来选择要尝试的超参数集。这主要是通过使用不同变种的贝叶斯优化方法,根据先前的试验以顺序方式自适应地识别新的配置。已证明这种方法优于传统的网格搜索和随机搜索方法。
 - 
超参数集的自适应评估(也称为配置评估或CE):这些方法专注于自适应地将更多资源分配给有前景的超参数配置,同时迅速去除效果不佳的配置。资源可以以不同的形式存在,如训练数据集的大小(例如,仅使用训练数据集的一小部分)或迭代次数(例如,仅使用少量迭代来决定哪些任务需要终止,而不是运行到收敛)。有一类方法称为多臂赌博机算法,例如异步成功缩减算法(ASHA)。在这里,所有试验从一个初始预算开始,然后去除最差的一半,调整剩余试验的预算,这个过程会重复进行,直到只剩下一个试验。
 
实际上,我们希望使用以下五个标准来选择一个合适的 HPO 框架:
- 
与 MLflow 的回调集成
 - 
可扩展性和对 GPU 集群的支持
 - 
易于使用和灵活的 API
 - 
与前沿 HPO 算法的集成(CS 和 CE)
 - 
深度学习框架的支持
 
在本书中,比较了三种框架,结果总结在图 6.1中:

图 6.1:Ray Tune、Optuna 和 HyperOpt 的比较
如图 6.1所示,Ray Tune(docs.ray.io/en/latest/tune/index.html)的表现优于Optuna(optuna.org/)和HyperOpt(hyperopt.github.io/hyperopt/)。接下来,我们将依次解释以下五个标准:
- 与 MLflow 的回调集成:Optuna 对 MLflow 回调的支持仍然是一个实验性功能,而 HyperOpt 根本不支持回调,这就让用户需要额外管理每次试验运行的 MLflow 跟踪。
 
只有 Ray Tune 支持 Python 混合装饰器和与 MLflow 的回调集成。Python 混合装饰器是一种模式,允许在需要时将独立的函数混合进来。在这种情况下,MLflow 的功能通过mlflow_mixin装饰器在模型训练期间自动混合进来。这可以将任何训练函数转变为 Ray Tune 可训练的函数,自动配置 MLflow 并在与每次 Tune 试验相同的进程中创建运行。然后,你可以在训练函数内部使用 MLflow API,结果会自动报告到正确的运行中。此外,它还支持 MLflow 的自动日志记录,这意味着所有的 MLflow 跟踪信息将被记录到正确的试验中。例如,以下代码片段展示了如何将我们之前的深度学习微调函数转换为一个mlflow_mixin的 Ray Tune 函数:
@mlflow_mixin
def train_dl_model():
    mlflow.pytorch.autolog()
    trainer = flash.Trainer(
        max_epochs=num_epochs,
        callbacks=[TuneReportCallback(
            metrics, on='validation_end')])
    trainer.finetune()
请注意,当我们定义训练器时,可以将TuneReportCallback作为回调之一添加,这将把指标反馈给 Ray Tune,而 MLflow 的自动日志记录会同时完成所有跟踪结果的记录。在下一节中,我们将展示如何将上一章中微调深度学习模型的示例转变为 Ray Tune 可训练函数。
- 
可扩展性和 GPU 集群支持:尽管 Optuna 和 HyperOpt 支持并行化,但它们都依赖于一些外部数据库(如关系数据库或 MongoDB)或 SparkTrials。只有 Ray Tune 通过 Ray 分布式框架原生支持并行和分布式 HPO,而且在这三种框架中,它也是唯一支持在 GPU 集群上运行的。
 - 
API 的易用性和灵活性:在这三个框架中,只有 Optuna 支持按运行时定义的 API,这允许您以 Pythonic 编程风格动态定义超参数,包括循环和分支(
optuna.readthedocs.io/en/stable/tutorial/10_key_features/002_configurations.html)。这与 Ray Tune 和 HyperOpt 支持的定义-运行API 形成对比,其中搜索空间在评估目标函数之前由预定义的字典定义。这两个术语按运行时定义和定义-运行实际上是由 DL 框架开发社区创造的。在早期,当 TensorFlow 1.0 最初发布时,神经网络首先需要定义,然后稍后惰性执行,这称为定义-运行。这两个阶段,1)神经网络构建阶段和 2)评估阶段,是按顺序执行的,神经网络结构在构建阶段之后不能更改。更新的 DL 框架,如 TensorFlow 2.0(或 TensorFlow 的急切执行版本)和 PyTorch,支持按运行时定义神经网络计算。没有用于构建和评估神经网络的两个单独阶段。用户可以在计算过程中直接操作神经网络。虽然 Optuna 提供的按运行时定义 API 可以用于动态定义超参数搜索空间,但它确实有一些缺点。主要问题是在运行时不知道参数并发性,这可能会使优化方法的实现复杂化。这是因为事先了解参数并发性对于许多采样方法是有很好的支持的。因此,在本书中,我们更喜欢使用定义-运行API。还请注意,Ray Tune 可以通过与 Optuna 的集成支持按运行时定义API(您可以在 Ray Tune 的 GitHub 存储库中看到一个示例,位于github.com/ray-project/ray/blob/master/python/ray/tune/examples/optuna_define_by_run_example.py#L35)。 - 
与前沿 HPO 算法集成(CS 和 CE):在CS方面,这三种框架中,HyperOpt 在支持或集成最新的前沿 HPO 采样和搜索方法方面开发活跃度最低。其主要的搜索方法是树结构帕森估计器(TPE),这是一种贝叶斯优化变体,特别适用于混合分类和条件超参数搜索空间。同样,Optuna 的主要采样方法也是 TPE。相反,Ray Tune 支持包括以下内容的所有前沿搜索方法:
- 
DragonFly (
dragonfly-opt.readthedocs.io/en/master/),这是一个高度可扩展的贝叶斯优化框架 - 
BlendSearch (
microsoft.github.io/FLAML/docs/Use-Cases/Tune-User-Defined-Function/#hyperparameter-optimization-algorithm),来自微软研究院 
 - 
 
此外,Ray Tune 还通过与 Optuna 和 HyperOpt 的集成支持 TPE。
在 CE 方面,HyperOpt 不支持任何修剪或调度器来停止不 promising 的超参数配置。Optuna 和 Ray Tune 都支持相当多的修剪器(在 Optuna 中)或调度器(在 Ray Tune 中)。然而,只有 Ray Tune 支持 PBT。考虑到 Ray Tune 活跃的开发社区和灵活的 API,Ray Tune 很有可能会继续及时集成并支持任何新兴的调度器或修剪器。
- 深度学习框架的支持:HyperOpt 并非专为任何深度学习框架设计或集成。这并不意味着不能使用 HyperOpt 调优深度学习模型。然而,HyperOpt 不提供任何修剪或调度器支持来对不 promising 的超参数配置进行早停,这是 HyperOpt 用于深度学习模型调优的一个主要缺点。Ray Tune 和 Optuna 都与流行的深度学习框架如 PyTorch Lightning 和 TensorFlow/Keras 集成。
 
除了我们刚才讨论的主要标准,Ray Tune 还拥有最佳的文档、广泛的代码示例和充满活力的开源开发者社区,这也是我们在本章中偏向使用 Ray Tune 进行学习的原因。在接下来的部分中,我们将学习如何使用 Ray Tune 和 MLflow 创建适合超参数优化的深度学习模型。
使用 Ray Tune 和 MLflow 创建适合超参数优化的深度学习模型
为了在超参数优化中使用 Ray Tune 与 MLflow,我们将使用我们在 第五章 中的深度学习管道示例中的微调步骤,看看需要设置什么内容以及我们需要做哪些代码更改。在开始之前,首先让我们回顾一下几个与我们使用 Ray Tune 特别相关的关键概念:
- 
目标函数:目标函数可以是最小化或最大化给定超参数配置的某个指标值。例如,在深度学习模型训练和微调的场景中,我们希望最大化 NLP 文本分类器的 F1 分数。这一目标函数需要被包装成一个可训练的函数,Ray Tune 可以进行超参数优化。在接下来的部分中,我们将演示如何包装我们的 NLP 文本情感分析模型。
 - 
tune.report用于报告模型指标 (docs.ray.io/en/latest/tune/api_docs/trainable.html#function-api)。基于类的 API 要求模型训练函数(trainable)是tune.Trainable的子类 (docs.ray.io/en/latest/tune/api_docs/trainable.html#trainable-class-api)。基于类的 API 提供了更多控制 Ray Tune 如何控制模型训练过程的方式。如果你开始编写神经网络模型的新架构,这可能非常有用。然而,当使用预训练的基础模型进行微调时,使用基于函数的 API 会更容易,因为我们可以利用像 PyTorch Lightning Flash 这样的包来进行 HPO。 - 
tune.run,在这里 Ray Tune 将协调超参数优化(HPO)过程。 - 
tune.loguniform) 或来自某些类别变量(例如,tune.choice(['a', 'b' ,'c'])允许你均匀选择这三个选项)。通常,这个搜索空间被定义为一个名为config的 Python 字典变量。 - 
tune.suggestAPI (docs.ray.io/en/latest/tune/api_docs/suggestion.html#tune-search-alg)。 - 
tune.suggestAPI 提供了用于搜索的优化算法,但它不提供早期停止或修剪功能,以便在仅经过几次迭代后停止明显不太可能成功的实验。由于早期停止或修剪可以显著加速 HPO 过程,因此强烈建议你结合搜索器使用调度器。Ray Tune 通过其调度器 API (tune.schedulers) 提供了许多流行的调度器,如 ASHA、HyperBand 等。(请访问docs.ray.io/en/latest/tune/api_docs/schedulers.html#trial-schedulers-tune-schedulers.) 
在回顾了 Ray Tune 的基本概念和 API 后,在下一节中,我们将设置 Ray Tune 和 MLflow 来运行 HPO 实验。
设置 Ray Tune 和 MLflow
现在我们理解了 Ray Tune 的基本概念和 API,让我们看看如何设置 Ray Tune 来执行之前的 NLP 情感分类器的微调步骤的 HPO。你可能想要下载本章的代码 (github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter06/),以便跟随这些说明:
- 
通过在你的 conda 虚拟环境
dl_model_hpo中输入以下命令来安装 Ray Tune:pip install ray[tune]==1.9.2 - 
这将会在你启动 DL 模型微调的 HPO 任务时,在虚拟环境中安装 Ray Tune。请注意,我们还提供了完整的
requirements.txt文件,位于本章的 GitHub 仓库中(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter06/requirements.txt),你应该能够运行以下安装命令:pip install -r requirements.txt - 
位于同一文件夹中的
README.md文件包含完整的说明,如果你需要了解如何设置合适的虚拟环境,它将为你提供更多的指导。 - 
对于 MLflow 设置,假设你已经设置好了完整的 MLflow 跟踪服务器,唯一需要注意的是确保你正确配置了环境变量,以便访问 MLflow 跟踪服务器。在你的终端中运行以下命令来设置这些变量。或者,你也可以通过在 Python 代码中调用
os.environ["environmental_name"]=value来覆盖你的环境变量。提醒一下,我们已经在终端会话中展示了以下可以设置的环境变量:export MLFLOW_TRACKING_URI=http://localhost export MLFLOW_S3_ENDPOINT_URL=http://localhost:9000 export AWS_ACCESS_KEY_ID="minio" export AWS_SECRET_ACCESS_KEY="minio123" - 
运行
download_data步骤将原始数据下载到chapter06父文件夹下的本地文件夹:mlflow run . -P pipeline_steps='download_data' --experiment-name dl_model_chapter06 
当前面的执行完成后,你应该能够在chapter06/data/文件夹下找到 IMDB 数据。
现在我们准备创建一个 HPO 步骤,以微调我们之前构建的 NLP 情感分析模型。
为 DL 模型创建 Ray Tune 可训练对象
我们需要做多个更改,才能让 Ray Tune 运行 HPO 任务来微调我们在前几章中开发的 DL 模型。我们将逐步演示这些步骤,如下所示:
- 
首先,让我们列出在之前的微调代码中可能的超参数(包括可调和不可调的)。回想一下,我们的微调代码看起来如下(这里只显示关键代码行;完整代码可以在 GitHub 仓库的
chapter05中找到,地址为github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter05/pipeline/fine_tuning_model.py#L19):datamodule = TextClassificationData.from_csv( input_fields="review", target_fields="sentiment", train_file=f"{data_path}/imdb/train.csv", val_file=f"{data_path}/imdb/valid.csv", test_file=f"{data_path}/imdb/test.csv") classifier_model = TextClassifier( backbone= "prajjwal1/bert-tiny", num_classes=datamodule.num_classes, metrics=torchmetrics.F1(datamodule.num_classes)) trainer = flash.Trainer(max_epochs=3) trainer.finetune(classifier_model, datamodule=datamodule, strategy="freeze") 
前面的代码包含四个主要部分:
- 
datamodule变量:这定义了训练、验证和测试的数据源。这里有一个batch_size参数,默认值为1,虽然在这里没有显示,但它是需要调优的最重要的超参数之一。更多详情,请参阅lightning-flash代码文档中的说明(github.com/PyTorchLightning/lightning-flash/blob/450902d713980e0edefcfd2d2a2a35eb875072d7/flash/core/data/data_module.py#L64)。 - 
classifier_model:这定义了一个通过lightning-flash的TextClassifierAPI 暴露参数的分类器。输入参数中有多个超参数可以调优,包括learning_rate、backbone基础模型、optimizer等。你可以在lightning-flash代码文档中查看TextClassifierAPI 的完整输入参数列表(github.com/PyTorchLightning/lightning-flash/blob/450902d713980e0edefcfd2d2a2a35eb875072d7/flash/text/classification/model.py#L44)。 - 
trainer:这定义了一个训练器变量,可用于微调。这里有一些需要设置的超参数,但不一定需要调优,比如num_epochs,正如前面所讨论的那样。 - 
trainer.finetune:这实现了实际的微调(迁移学习)。注意,还有一个可能需要调优的超参数strategy。 
为了学习目的,我们将选择learning_rate和batch_size作为两个需要调优的超参数,因为这两个参数对于优化深度学习模型至关重要。一旦你完成这一章,你应该能够轻松地将更多的超参数添加到优化候选列表中。
- 
Ray Tune 要求传入一个可训练的函数到
tune.run。这意味着我们需要创建一个可训练的函数。默认情况下,训练函数只接受一个必需的输入参数config,它包含超参数和其他参数的键值对字典,并用于标识执行环境,如 MLflow 跟踪 URL。然而,Ray Tune 提供了一个包装函数,叫做tune.with_parameters,它允许你传递额外的任意参数和对象(docs.ray.io/en/latest/tune/tutorials/overview.html#how-can-i-pass-further-parameter-values-to-my-trainable)。首先,让我们创建一个名为finetuning_dl_model的函数,将我们刚才检查的微调步骤逻辑封装起来,并使用mlflow_mixin装饰器。这样可以确保每次调用该函数时,MLflow 会自动初始化。@mlflow_mixin def finetuning_dl_model(config, data_dir=None, num_epochs=3, num_gpus=0): 
该函数接受一个config字典作为输入,其中可以传入一组超参数和 MLflow 配置。此外,我们为函数签名添加了三个额外的参数:data_dir表示目录的位置,num_epochs表示每个试验的最大训练轮数,num_gpus表示每个试验使用的 GPU 数量(如果有的话)。
- 
在这个
mlflow_mixin装饰的函数中,如果需要,我们可以使用所有 MLflow 跟踪 API,但从 MLflow 版本 1.22.0 开始,由于 MLflow 的自动日志记录支持不再是实验性特性,而是一个成熟的生产级特性(github.com/mlflow/mlflow/releases/tag/v1.22.0),因此我们应该直接在代码中使用自动日志记录,如下所示:mlflow.pytorch.autolog() 
这样做是高效的,并且无需进行任何更改。然而,batch_size超参数不会被自动记录,因此我们需要在微调完成后再添加一个日志记录语句,如下所示:
mlflow.log_param('batch_size',config['batch_size'])
- 
在
finetuning_dl_model函数的其余实现部分,大部分代码与之前相同。这里有一些更改。在datamodule变量赋值语句中,我们添加了batch_size=config['batch_size'],以便训练数据的迷你批量大小可以调整,如下所示:datamodule = TextClassificationData.from_csv( input_fields="review", target_fields="sentiment", train_file=f"{data_dir}/imdb/train.csv", val_file=f"{data_dir}/imdb/valid.csv", test_file=f"{data_dir}/imdb/test.csv", batch_size=config['batch_size']) - 
在定义
classifier_model变量时,不再使用默认的超参数集值,而是需要传入config字典来分配这些值:classifier_model = TextClassifier( backbone=config['foundation_model'], learning_rate=config['lr'], optimizer=config['optimizer_type'], num_classes=datamodule.num_classes, metrics=torchmetrics.F1(datamodule.num_classes)) - 
接下来,我们需要修改训练器赋值代码。在这里,我们需要做两件事:首先,我们需要定义一个度量标准的键值字典,以便从 PyTorch Lightning 传递到 Ray Tune。该字典中的键是 Ray Tune 试验运行中要引用的名称,而该字典中键的值是 PyTorch Lightning 报告的相应度量标准名称。
PyTorch Lightning 验证步骤中的度量标准名称
在将 metrics 传递给 Ray Tune 时,首先我们需要了解在 PyTorch Lightning 中验证步骤期间使用的指标名称,因为 HPO 仅使用验证数据进行评估,而不使用保留的测试数据集。实际上,PyTorch Lightning 有一个硬编码的约定,所有指标的名称都会以相应的训练、验证和测试步骤名称及下划线为前缀。名为
f1的指标将在 PyTorch Lightning 中作为train_f1在训练步骤中报告,在验证步骤中报告为val_f1,在测试步骤中报告为test_f1。(你可以在github.com/PyTorchLightning/lightning-flash/blob/8b244d785c5569e9aa7d2b878a5f94af976d3f55/flash/core/model.py#L462查看 PyTorch Lightning 的代码逻辑)。在我们的示例中,我们可以选择cross_entropy和f1作为验证步骤中的指标,它们分别命名为val_cross_entropy和val_f1,并将它们作为loss和f1传递回 Ray Tune。这意味着,在 Ray Tune 的试验运行中,我们将这两个指标引用为loss和f1。 
因此,在这里我们定义了两个希望从 PyTorch Lightning 验证步骤中传递到 Ray Tune 的指标,分别是val_cross_entropy和val_f1,它们分别作为loss和f1传递:
metrics = {"loss":"val_cross_entropy", "f1":"val_f1"}
现在,我们可以将这个 metrics 字典传递给 trainer 赋值,如下所示:
trainer = flash.Trainer(max_epochs=num_epochs,
    gpus=num_gpus,
    progress_bar_refresh_rate=0,
    callbacks=[TuneReportCallback(metrics, 
        on='validation_end')])
注意,metrics 字典会在validation_end事件发生时通过TuneReportCallBack传递。这意味着当 PyTorch Lightning 中的验证步骤完成时,它会自动触发 Ray Tune 报告函数,将指标列表反馈给 Ray Tune 进行评估。有关TuneReportCallback可以使用的有效事件的支持列表,请参考 Ray Tune 与 PyTorch Lightning 的集成源代码(github.com/ray-project/ray/blob/fb0d6e6b0b48b0a681719433691405b96fbea104/python/ray/tune/integration/pytorch_lightning.py#L170)。
- 
最后,我们可以调用
trainer.finetune来执行微调步骤。在这里,我们可以将finetuning_strategies作为可调超参数传递给参数列表:trainer.finetune(classifier_model, datamodule=datamodule, strategy=config['finetuning_strategies']) - 
这完成了原始微调 DL 模型函数的更改。现在我们有一个新的
finetuning_dl_model函数,准备被with_parameters包装以成为 Ray Tune 的可训练函数。它应该如下所示调用:trainable = tune.with_parameters(finetuning_dl_model, data_dir, num_epochs, num_gpus) - 
请注意,无需传递
config参数,因为默认假设它是finetuning_dl_model的第一个参数。其他三个参数需要传递给tune.with_parameters包装器。同时,确保创建 Ray Tune 可训练对象的语句放在finetuning_dl_model函数外部。 
在下一部分,它将被放入 Ray Tune 的 HPO 运行函数中,名为run_hpo_dl_model。
创建 Ray Tune HPO 运行函数
现在,让我们创建一个 Ray Tune HPO 运行函数来执行以下五个步骤:
- 
定义 MLflow 运行时配置参数,包括追踪 URI 和实验名称。
 - 
使用 Ray Tune 的随机分布 API 定义超参数搜索空间(
docs.ray.io/en/latest/tune/api_docs/search_space.html#random-distributions-api),以采样我们之前确定的超参数列表。 - 
使用
tune.with_parameters定义 Ray Tune 的可训练对象,如前一小节末尾所示。 - 
调用
tune.run。这将执行 HPO 运行,并在完成时返回 Ray Tune 的实验分析对象。 - 
在整个 HPO 运行完成后,记录最佳配置参数。
 
让我们一起查看实现过程,看看这个函数是如何实现的:
- 
首先,让我们定义超参数的
config字典,如下所示:mlflow.set_tracking_uri(tracking_uri) mlflow.set_experiment(experiment_name) 
这将接受tracking_uri和experiment_name作为 MLflow 的输入参数,并正确设置它们。如果这是你第一次运行,MLflow 还将创建该实验。
- 
然后,我们可以定义
config字典,其中可以包括可调和不可调的参数,以及 MLflow 的配置参数。如前所述,我们将调整learning_rate和batch_size,但也会包括其他超参数用于记账和未来的调优目的:config = { "lr": tune.loguniform(1e-4, 1e-1), "batch_size": tune.choice([32, 64, 128]), "foundation_model": "prajjwal1/bert-tiny", "finetuning_strategies": "freeze", "optimizer_type": "Adam", "mlflow": { "experiment_name": experiment_name, "tracking_uri": mlflow.get_tracking_uri() }, } 
从config字典中可以看到,我们调用了tune.loguniform来在1e-4和1e-1之间采样一个对数均匀分布,以选择学习率。对于批量大小,我们调用了tune.choice从三个不同的值中均匀选择。对于其余的键值对,它们是不可调的,因为它们没有使用任何采样方法,但在运行试验时是必需的。
- 
使用
tune.with_parameters定义可训练对象,包含除config参数之外的所有额外参数:trainable = tune.with_parameters( finetuning_dl_model, data_dir=data_dir, num_epochs=num_epochs, num_gpus=gpus_per_trial) 
在下一个语句中,这将被称为 tune.run函数。
- 
现在我们准备通过调用
tune.run来运行 HPO,如下所示:analysis = tune.run( trainable, resources_per_trial={ "cpu": 1, "gpu": gpus_per_trial }, metric="f1", mode="max", config=config, num_samples=num_samples, name="hpo_tuning_dl_model") 
在这里,目标是找到一组超参数,使得所有实验中的 F1-score 最大化,因此模式是 max,而指标是 f1。请注意,这个指标名称 f1 来自我们在之前的 finetuning_dl_model 函数中定义的 metrics 字典,其中我们将 PyTorch Lightning 的 val_f1 映射到 f1。然后,这个 f1 值会在每次实验验证步骤结束时传递给 Ray Tune。trainable 对象作为第一个参数传递给 tune.run,这个函数将根据 num_samples 的参数被执行多次。接下来,resources_per_trial 定义了每个实验使用的 CPU 和 GPU。请注意,在前面的示例中,我们没有指定任何搜索算法,这意味着它将默认使用 tune.suggest.basic_variant,这是一种网格搜索算法。我们也没有定义调度器,因此默认情况下不会进行早期停止,所有实验将并行运行,且使用执行机器上允许的最大 CPU 数量。当运行结束时,会返回一个 analysis 变量,包含找到的最佳超参数以及其他信息。
- 
记录找到的最佳超参数配置。这可以通过使用从
tune.run返回的analysis变量来完成,代码如下:logger.info("Best hyperparameters found were: %s", analysis.best_config) 
就这样。现在我们可以试试看。如果你从本章的 GitHub 仓库下载完整代码,你应该能在 pipeline 文件夹下找到 hpo_finetuning_model.py 文件。
通过上述更改,我们现在已经准备好运行我们的第一个 HPO 实验。
使用 MLflow 运行第一个 Ray Tune HPO 实验
现在我们已经设置好了 Ray Tune、MLflow,并创建了 HPO 运行函数,我们可以尝试运行我们的第一个 Ray Tune HPO 实验,如下所示:
python pipeline/hpo_finetuning_model.py
几秒钟后,你将看到以下屏幕,图 6.2,显示所有 10 次实验(即我们为 num_samples 设置的值)正在并行运行:

图 6.2 – Ray Tune 在本地多核笔记本上并行运行 10 次实验
大约 12-14 分钟后,你会看到所有实验都已完成,并且最佳超参数将显示在屏幕上,如下所示(由于随机性、样本数量有限以及网格搜索的使用,结果可能会有所不同,且网格搜索不保证全局最优解):
Best hyperparameters found were: {'lr': 0.025639008922511797, 'batch_size': 64, 'foundation_model': 'prajjwal1/bert-tiny', 'finetuning_strategies': 'freeze', 'optimizer_type': 'Adam', 'mlflow': {'experiment_name': 'hpo-tuning-chapter06', 'tracking_uri': 'http://localhost'}}
你可以在结果日志目录下找到每个实验的结果,默认情况下该目录位于当前用户的 ray_results 文件夹中。从 图 6.2 中我们可以看到,结果位于 /Users/yongliu/ray_results/hpo_tuning_dl_model。
你将在屏幕上看到最佳超参数的最终输出,这意味着你已经完成了第一次 HPO 实验!你可以看到所有 10 次试验都已记录在 MLflow 跟踪服务器中,并且可以使用 MLflow 跟踪服务器提供的平行坐标图来可视化和比较所有 10 次运行。你可以通过进入 MLflow 实验页面,选择你刚完成的 10 次试验,然后点击页面顶部附近的Compare按钮来生成该图(参见图 6.3)。这将带你进入并排比较页面,页面底部会显示绘图选项:

图 6.3 – 点击“Compare”以比较 MLflow 实验页面上所有 10 次试验运行
你可以点击平行坐标图菜单项,这将允许你选择要绘制的参数和指标。在这里,我们选择lr和batch_size作为参数,val_f1和val_cross_entropy作为指标。绘图显示在图 6.4中:

图 6.4 – 用于比较 HPO 实验结果的平行坐标图
正如你在图 6.4中看到的,非常容易看出batch_size为 128,lr为 0.02874 时,能够产生最佳的val_f1得分 0.6544 和val_cross_entropy(损失值)为 0.62222。正如前面所提到的,这次 HPO 运行没有使用任何高级搜索算法和调度器,因此让我们看看在接下来的部分中,通过使用提前停止和修剪,我们能否通过更多的实验做得更好。
使用 Optuna 和 HyperBand 通过 Ray Tune 运行 HPO
现在,让我们尝试一些不同的搜索算法和调度器的实验。鉴于 Optuna 是一种非常优秀的基于 TPE 的搜索算法,ASHA 是一种优秀的调度器,可以通过异步并行试验并提前终止那些不太有前景的试验,看看我们需要做多少更改才能使其工作将会很有趣。
结果表明,基于我们在前一部分所做的工作,变化非常微小。这里,我们将展示四个主要的变化:
- 
安装Optuna包。可以通过运行以下命令来完成:
pip install optuna==2.10.0 
这将把 Optuna 安装到我们之前的虚拟环境中。如果你已经运行了pip install -r requirements.text,那么 Optuna 已经被安装,你可以跳过这一步。
- 
导入与 Optuna 和 ASHA 调度器集成的相关 Ray Tune 模块(在这里,我们使用 ASHA 的 HyperBand 实现)如下:
from ray.tune.suggest import ConcurrencyLimiter from ray.tune.schedulers import AsyncHyperBandScheduler from ray.tune.suggest.optuna import OptunaSearch - 
现在,我们准备好将搜索算法变量和调度器变量添加到 HPO 执行函数
run_hpo_dl_model中了,具体如下:searcher = OptunaSearch() searcher = ConcurrencyLimiter(searcher, max_concurrent=4) scheduler = AsyncHyperBandScheduler() 
请注意,searcher 变量现在使用的是 Optuna,我们将并发运行的最大次数设置为 4,让这个 searcher 变量在 HPO 搜索过程中每次尝试最多同时运行四个试验。调度器初始化时使用 HyperBand 调度器。
- 
将 searcher 和 scheduler 分配给
tune.run调用的相应参数,如下所示:analysis = tune.run( trainable, resources_per_trial={ "cpu": 1, "gpu": gpus_per_trial }, metric="f1", mode="max", config=config, num_samples=num_samples, search_alg=searcher, scheduler=scheduler, name="hpo_tuning_dl_model") 
请注意,searcher 被分配给了 search_alg 参数,scheduler 被分配给了 scheduler 参数。就这样。现在,我们已经准备好在统一的 Ray Tune 框架下使用 Optuna 进行 HPO,并且已经通过 Ray Tune 提供了所有的 MLflow 集成。
我们已经在 pipeline 文件夹中的 hpo_finetuning_model_optuna.py 文件中提供了完整的 Python 代码。我们可以按照以下步骤运行这个 HPO 实验:
python pipeline/hpo_finetuning_model_optuna.py
你将立即注意到控制台输出中的以下内容:
[I 2022-02-06 21:01:27,609] A new study created in memory with name: optuna
这意味着我们现在使用 Optuna 作为搜索算法。此外,你会注意到在屏幕显示的状态输出中有四个并发的试验。随着时间的推移,一些试验会在完成之前的一个或两个迭代(epoch)后被终止。这意味着 ASHA 正在工作,已经淘汰了那些没有前景的试验,以节省计算资源并加快搜索过程。图 6.5 显示了运行过程中的一个输出,其中三个试验仅进行了一个迭代就被终止。你可以在状态输出中找到 num_stopped=3(在图 6.5中的第三行),其中显示 Using AsynHyerBand: num_stopped=3。这意味着 AsyncHyperBand 在试验完成之前就终止了这三个试验:

图 6.5 – 使用 Optuna 和 AsyncHyperBand 在 Ray Tune 上运行 HPO
运行结束时,你将看到以下结果:
2022-02-06 21:11:59,695    INFO tune.py:626 -- Total run time: 632.10 seconds (631.91 seconds for the tuning loop).
2022-02-06 21:11:59,728 Best hyperparameters found were: {'lr': 0.0009599443695046438, 'batch_size': 128, 'foundation_model': 'prajjwal1/bert-tiny', 'finetuning_strategies': 'freeze', 'optimizer_type': 'Adam', 'mlflow': {'experiment_name': 'hpo-tuning-chapter06', 'tracking_uri': 'http://localhost'}}
请注意,总运行时间仅为 10 分钟。与上一节中使用没有提前停止的网格搜索相比,这节省了 2-4 分钟。现在,这看起来可能很短,但请记住,我们这里只使用了一个小型的 BERT 模型,且只有 3 个 epoch。在生产环境中的 HPO 运行中,使用 20 个 epoch 的大型预训练基础模型并不罕见,而结合良好的搜索算法和调度器(如异步 HyperBand 调度器)搜索速度将会显著提升。Ray Tune 提供的 MLflow 集成是免费的,现在我们可以在一个框架下切换不同的搜索算法和/或调度器。
虽然本节仅向您展示了如何在 Ray Tune 和 MLflow 框架中使用 Optuna,但将 Optuna 替换为 HyperOpt 只是一个简单的替换操作。我们可以用 HyperOptSearch 替代 OptunaSearch 来初始化搜索器(您可以参考示例:github.com/ray-project/ray/blob/d6b0b9a209e3f693afa6441eb284e48c02b10a80/python/ray/tune/examples/hyperopt_conditional_search_space_example.py#L80),其他代码保持不变。我们将这个作为练习留给你自行探索。
使用不同的搜索算法和调度器与 Ray Tune 配合
请注意,并不是所有的搜索算法都能与任何调度器配合使用。你选择的搜索算法和调度器取决于模型的复杂性和评估成本。对于深度学习模型,由于每个训练周期的运行成本通常很高,因此非常推荐使用现代搜索算法,如 TPE、Dragonfly 和 BlendSearch,并结合像我们使用的 HyperBand 调度器等 ASHA 类型的调度器。有关选择哪些搜索算法和调度器的更详细指南,您可以查阅 Ray Tune 网站上的以下文档:docs.ray.io/en/latest/tune/tutorials/overview.html#which-search-algorithm-scheduler-should-i-choose。
现在我们已经理解了如何使用 Ray Tune 和 MLflow 为深度学习模型进行高并行和高效的 HPO,这为我们将来在大规模环境中进行更高级的 HPO 实验奠定了基础。
小结
在本章中,我们介绍了 HPO 的基本原理和挑战,为什么它对深度学习模型管道非常重要,以及现代 HPO 框架应该支持哪些内容。我们比较了三种流行的框架——Ray Tune、Optuna 和 HyperOpt,并选择了 Ray Tune 作为在大规模运行最先进 HPO 的最佳框架。我们展示了如何使用 Ray Tune 和 MLflow 创建适合 HPO 的深度学习模型代码,并使用 Ray Tune 和 MLflow 运行了我们的第一次 HPO 实验。此外,我们还介绍了在设置好 HPO 代码框架后,如何切换到其他搜索和调度算法,举例说明了如何使用 Optuna 和 HyperBand 调度器。通过本章的学习,您将能够在真实的生产环境中胜任大规模的 HPO 实验,从而以一种具有成本效益的方式产生高性能的深度学习模型。我们还在本章末尾提供了许多参考文献,在进一步阅读部分鼓励您深入学习。
在下一章中,我们将继续学习如何使用 MLflow 构建模型推理管道的预处理和后处理步骤,这是在深度学习模型经过 HPO 调优并准备投入生产后,真实生产环境中的典型场景。
进一步阅读
- 
模型调优与超参数优化的最佳工具:
neptune.ai/blog/best-tools-for-model-tuning-and-hyperparameter-optimization - 
Optuna 与 HyperOpt 的比较:
neptune.ai/blog/optuna-vs-hyperopt - 
如何(不)使用 Hyperopt 调优您的模型:
databricks.com/blog/2021/04/15/how-not-to-tune-your-model-with-hyperopt.html - 
为什么超参数调优对您的模型很重要?:
medium.com/analytics-vidhya/why-hyper-parameter-tuning-is-important-for-your-model-1ff4c8f145d3 - 
通过示例学习深度神经网络的超参数调优艺术:
towardsdatascience.com/the-art-of-hyperparameter-tuning-in-deep-neural-nets-by-example-685cb5429a38 - 
自动化超参数调优:
insaid.medium.com/automated-hyperparameter-tuning-988b5aeb7f2a - 
通过 Lightning 和 Ray Tune 让你的 PyTorch 模型更强大:
towardsdatascience.com/get-better-at-building-pytorch-models-with-lightning-and-ray-tune-9fc39b84e602 - 
Ray & MLflow: 将分布式机器学习应用程序推向生产环境:
medium.com/distributed-computing-with-ray/ray-mlflow-taking-distributed-machine-learning-applications-to-production-103f5505cb88 - 
初学者的超参数优化指南:
wood-b.github.io/post/a-novices-guide-to-hyperparameter-optimization-at-scale/ - 
在 Databricks 集群上运行 Ray Tune 和 MLflow 的 Databricks 笔记本:
databricks-prod-cloudfront.cloud.databricks.com/public/4027ec902e239c93eaaa8714f173bcfc/6762389964551879/1089858099311442/7376217192554178/latest.html - 
Ray 分布式对象、Ray Tune 简介及与 Parsl 的简要比较:
cloud4scieng.org/2021/04/08/a-brief-introduction-to-ray-distributed-objects-ray-tune-and-a-small-comparison-to-parsl/ 
第四部分 – 大规模部署深度学习管道
在本节中,我们将学习如何实现和部署一个多步骤推理管道,以便用于生产环境。我们将从生产环境中四种推理工作流模式的概述开始。接着,我们将学习如何使用 MLflow PyFunc API,围绕一个经过微调的深度学习(DL)模型,实施一个包含预处理和后处理步骤的多步骤推理管道。在这个准备好部署的 MLflow PyFunc 兼容的深度学习推理管道上,我们将了解不同的部署工具和托管环境,以便选择适合特定部署场景的工具。然后,我们将使用 MLflow 的 Spark 用户定义函数(UDF)实现并部署一个批量推理管道。之后,我们将专注于使用 MLflow 内置的模型服务工具或 Ray Serve 的 MLflow 部署插件,部署一个 Web 服务。最后,我们将展示一个完整的逐步指南,介绍如何将一个深度学习推理管道部署到管理的 AWS SageMaker 实例中,以便用于生产环境。
本节包括以下章节:
- 
第七章,多步骤深度学习推理管道
 - 
第八章,大规模部署深度学习推理管道
 
第七章:第七章:多步骤深度学习推理管道
现在我们已经成功运行了HPO(超参数优化),并生成了一个经过良好调优、满足业务需求的深度学习模型,是时候迈向下一步,开始使用这个模型进行预测。此时,模型推理管道便发挥了作用,模型将用于在生产环境中预测或评分真实数据,无论是实时模式还是批处理模式。然而,推理管道通常不仅仅依赖单一模型,还需要一些在模型开发阶段可能未见过的预处理和后处理逻辑。预处理步骤的例子包括在将输入数据传递给模型进行评分之前,检测语言区域(英语或其他语言)。后处理可能包括用额外的元数据来丰富预测标签,以满足业务应用的需求。还有一些深度学习推理管道模式,甚至可能涉及多个模型的集成,以解决现实世界中的业务问题。许多机器学习项目往往低估了实现生产环境推理管道所需的工作,这可能导致模型在生产环境中的性能下降,甚至在最糟糕的情况下,整个项目失败。因此,在将模型部署到生产环境之前,了解如何识别不同推理管道的模式并正确实现它们是非常重要的。
到本章结束时,你将能够使用 MLflow 自信地实现多步骤推理管道中的预处理和后处理步骤,并为将来章节中的生产环境使用做好准备。
本章将涵盖以下主要内容:
- 
理解深度学习推理管道的模式
 - 
理解 MLflow 模型 Python 函数 API
 - 
实现自定义 MLflow Python 模型
 - 
在深度学习推理管道中实现预处理和后处理步骤
 - 
将推理管道作为主机器学习项目中的新入口点进行实现
 
技术要求
本章的技术要求如下:
- 
本章的 GitHub 代码:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter07 - 
完整的本地 MLflow 跟踪服务器,如在第三章《跟踪模型、参数和指标》中所述,跟踪模型、参数和指标。
 
理解深度学习推理管道的模式
随着模型开发进入为即将到来的生产使用实现推理管道的阶段,必须理解,拥有一个调优良好的深度学习模型只是商业 AI 战略成功的一半。另一半包括部署、服务、监控以及模型投入生产后的持续改进。设计和实现深度学习推理管道是迈向故事第二阶段的第一步。尽管模型已经在精心挑选的离线数据集上进行了训练、调优和测试,现在它需要以两种方式处理预测:
- 
批量推理:这通常需要定期或临时执行推理管道,针对一些离线批量的观察数据。生成预测结果的周转时间通常为每天、每周或其他预定的时间安排。
 - 
在线推理:这通常需要一个 Web 服务来实时执行推理管道,为输入数据在不到一秒钟,甚至少于 100 毫秒的时间内生成预测结果,具体取决于用户场景。
 
请注意,由于执行环境和数据特征可能与离线训练和测试环境不同,因此在核心模型逻辑周围会有额外的预处理或后处理步骤,这些逻辑是在模型训练和调优步骤中开发出来的。需要强调的是,任何可以共享的数据预处理步骤应该同时在训练管道和推理管道中使用,但不可避免地,一些业务逻辑将会介入,这会使推理管道具备额外的预处理和后处理逻辑。例如,在深度学习推理管道中,一个非常常见的步骤是使用缓存来存储并返回基于最近输入的预测结果,这样就不必每次都调用昂贵的模型评估过程。在模型开发阶段,训练/测试管道不需要此步骤。
尽管推理管道的模式仍在不断发展,但现在已经普遍认为,现实生产环境中至少有四种模式:
- 
多步骤管道:这是该模型在生产中最典型的使用方式,包括在调用模型逻辑之前的一系列预处理步骤,以及在模型评估结果返回之后的一些后处理步骤。虽然从概念上看这很简单,但实现方式仍然可以有所不同。本章将展示如何使用 MLflow 高效地完成这项工作。
 - 
模型集成:这是一个更复杂的场景,其中可以使用多个不同的模型。这些模型可以是相同类型的模型,只是版本不同,用于 A/B 测试,或者是不同类型的模型。例如,在复杂的对话 AI 聊天机器人场景中,需要一个意图分类模型来将用户查询的意图分类到特定类别中。然后,还需要一个内容相关性模型,根据检测到的用户意图检索相关答案并呈现给用户。
 - 
业务逻辑和模型:这通常涉及如何以及从哪里获取模型输入的额外业务逻辑,例如从企业数据库查询用户信息和验证,或者在调用模型之前从特征库中检索预先计算的额外特征。此外,后处理业务逻辑还可以将预测结果转化为某些特定应用的逻辑,并将结果存储在后台存储中。虽然这可以是一个简单的线性多步骤管道,但它也可以迅速变成一个DAG(有向无环图),在模型调用前后具有多个并行的 fan-in 和 fan-out 任务。
 - 
在线学习:这是生产中最复杂的推理任务之一,其中模型会不断地学习并更新其参数,例如强化学习。
 
虽然理解生产环境中推理管道复杂性的全局图景是必要的,但本章的目的是学习如何通过强大且通用的 MLflow 模型 API 创建可重用的推理管道构建块,这些构建块可以在多个场景中使用,并能封装预处理和后处理步骤与训练好的模型。感兴趣的读者可以通过这篇文章(www.anyscale.com/blog/serving-ml-models-in-production-common-patterns)和进一步阅读部分中的其他参考资料,深入了解生产中的模型模式。
那么,什么是 MLflow 模型 API,如何使用它来实现多步骤推理管道的预处理和后处理逻辑呢?让我们在下一节中了解。
作为 MLflow 模型的多步骤推理管道
在之前的第三章中,跟踪模型、参数和指标,我们介绍了使用 MLflow MLproject的灵活松耦合多步骤管道实现,使我们能够在 MLflow 中显式地执行和跟踪多步骤训练管道。然而,在推理时,希望在已记录的模型库中的训练模型旁边实现轻量级的预处理和后处理逻辑。MLflow 模型 API 提供了一种机制,将训练好的模型与预处理和后处理逻辑封装起来,然后将新封装的模型保存为一个新模型,封装了推理管道逻辑。这统一了使用 MLflow 模型 API 加载原始模型或推理管道模型的方式。这对于使用 MLflow 进行灵活部署至关重要,并为创造性推理管道的构建打开了大门。
了解 MLflow 模型 Python 函数 API
MLflow 模型(www.mlflow.org/docs/latest/models.html#id25)是 MLflow 提供的核心组件之一,用于加载、保存和记录不同类型的模型(例如,MLmodel文件:

图 7.1 – 微调 PyTorch 模型的 MLmodel 内容
从图 7.1可以看到,该模型的类型是 PyTorch。还有一些关于模型的其他元数据,例如 conda 环境,它定义了运行该模型的依赖项,以及许多其他内容。凭借这些自包含的信息,应该足以使 MLflow 通过mlflow.pytorch.load_model API 如下所示加载该模型:
logged_model = f'runs:/{run_id}/model'
model = mlflow.pytorch.load_model(logged_model)
这将允许将通过run_id记录的 MLflow 运行模型加载回内存并进行推理。现在假设我们有以下场景,需要添加一些预处理逻辑来检查输入文本的语言类型。这需要加载一个语言检测模型(amitness.com/2019/07/identify-text-language-python/),例如FastText语言检测器(fasttext.cc/)或谷歌的Compact Language Detector v3(pypi.org/project/gcld3/)。此外,我们还希望检查是否存在完全相同输入的缓存预测。如果存在,则应该直接返回缓存结果,而不调用耗时的模型预测部分。这是非常典型的预处理逻辑。对于后处理,一个常见的场景是返回预测结果以及有关模型 URI 的一些元数据,以便我们可以调试生产中的任何潜在预测问题。基于这些预处理和后处理逻辑,推理管道现在看起来如下图所示:

图 7.2 – 多步骤推理管道
从图 7.2可以看出,这五个步骤包括以下内容:
- 
一个原始的、经过微调的预测模型(一个 PyTorch 深度学习模型)
 - 
一个额外的语言检测模型,这个模型并未包含在我们之前的训练流程中
 - 
缓存操作(检查缓存并存储到缓存中)以提高响应性能
 - 
一个响应消息组成步骤
 
与其将这五个步骤拆分成五个不同的入口点放入机器学习项目中(回想一下,机器学习项目中的入口点可以是 Python 中的任意执行代码或其他可执行文件),将这多步骤推理管道组合成一个入口点显得更为优雅,因为这些步骤与模型的预测步骤紧密相关。此外,将这些紧密相关的步骤封装成一个推理管道的优点是,我们可以将推理管道保存并作为 MLmodel 工件加载。MLflow 提供了一种通用方法,将这个多步骤推理管道实现为一个新的 Python 模型,同时不会失去在需要时添加额外预处理和后处理功能的灵活性,正如下面的图所示:

图 7.3 – 将多步骤的预处理和后处理逻辑封装到一个新的 MLflow Python 模型中
从图 7.3可以看出,如果我们将预处理和后处理逻辑封装成一个新的 MLflow 模型,称为 inference_pipeline_model,那么我们可以像加载其他模型一样加载整个推理管道。这还允许我们规范化推理管道的输入和输出格式(称为模型签名),这样任何想要使用这个推理管道的人都不需要猜测输入和输出的格式是什么。
在高层次上实现此机制的方式如下:
- 首先,创建一个自定义的 MLflow 
pyfunc(Python 函数)模型(www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html#creating-custom-pyfunc-models)来包装现有的训练模型。具体来说,我们需要超越 MLflow 提供的内置模型类型(www.mlflow.org/docs/latest/models.html#built-in-model-flavors),实现一个新的 Python 类,继承自mlflow.pyfunc.PythonModel(www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html#mlflow.pyfunc.PythonModel),并在这个新的 Python 类中定义predict()和(可选的)load_context()方法。 
此外,我们可以通过定义模型输入和输出的模式来指定模型签名(mlflow.org/docs/latest/models.html#model-signature)。这些模式可以是基于列的,也可以是基于张量的。强烈建议在生产环境中实现这些模式,以便进行自动输入验证和模型诊断。
- 
然后,在此 MLflow
pyfunc中实现预处理和后处理逻辑。这些逻辑可能包括缓存、语言检测、响应消息以及其他所需的逻辑。 - 
最后,在 ML 项目中实现推理流水线的入口点,以便我们可以像调用单一模型工件一样调用推理流水线。
 
现在我们已经理解了 MLflow 自定义 Python 模型的基本原理,来表示一个多步骤的推理流水线,接下来让我们看看如何为我们的 NLP 情感分类模型实现它,预处理和后处理步骤在下文的图 7.3中进行了描述。
实现自定义 MLflow Python 模型
首先,让我们描述实现一个自定义 MLflow Python 模型的步骤,而不包含任何额外的预处理和后处理逻辑:
- 
首先,确保我们有一个训练好的深度学习模型,准备好用于推理。在本章的学习中,我们将本章的训练流水线
README文件包含在 GitHub 仓库中,并设置相应的环境变量(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/README.md)。然后,在命令行中运行以下命令,在本地 MLflow 跟踪服务器上生成一个微调后的模型:mlflow run . --experiment-name dl_model_chapter07 -P pipeline_steps=download_data,fine_tuning_model 
完成后,您将在 MLflow 跟踪服务器中记录一个微调的深度学习模型。现在,我们将使用记录的模型 URI 作为推理流水线的输入,因为我们将把它封装起来并保存为一个新的 MLflow 模型。记录的模型 URI 类似于以下内容,其中长的随机字母数字字符串是fine_tuning_model MLflow 运行的run_id,您可以在 MLflow 跟踪服务器中找到:
runs:/1290f813d8e74a249c86eeab9f6ed24e/model
- 
一旦您有了训练/微调后的模型,我们就准备好按如下方式实现一个新的自定义 MLflow Python 模型。您可以查看 GitHub 仓库中的
basic_custom_dl_model.py(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/notebooks/basic_custom_dl_model.py),以跟随这里概述的步骤:class InferencePipeline(mlflow.pyfunc.PythonModel): def __init__(self, finetuned_model_uri): self.finetuned_model_uri = finetuned_model_uri def sentiment_classifier(self, row): pred_label = self.finetuned_text_classifier.predict({row[0]}) return pred_label def load_context(self, context): self.finetuned_text_classifier = mlflow.pytorch.load_model(self.finetuned_model_uri) def predict(self, context, model_input): results = model_input.apply( self.sentiment_classifier, axis=1, result_type='broadcast') return results 
让我们看看我们已经实现了什么。首先,InferencePipeline类继承自MLflow.pyfunc.PythonModel模块,并实现了如下四个方法:
- 
predict:这是mlflow.pyfunc.PythonModel要求的方法,用于返回预测结果。在这里,model_input参数是一个pandasDataFrame,其中包含一个需要分类的输入文本列。我们利用pandasDataFrame 的apply方法来运行sentiment_classifier方法,对每一行的文本进行评分,结果是一个 DataFrame,其中每一行都是预测标签。由于我们原始的微调模型不接受pandasDataFrame 作为输入(它接受的是文本字符串的列表),我们需要实现一个新的分类器,作为原始模型的封装器。这就是sentiment_classifier方法。另一个context参数是 MLflow 上下文,用于描述模型工件存储的位置。由于我们将传递一个 MLflow 记录的模型 URI,因此context参数在我们的实现中没有使用,因为记录的模型 URI 包含了加载模型所需的所有信息。 - 
sentiment_classifier:这是一个封装方法,允许通过调用微调后的 DL 模型的预测函数来为输入的每一行pandasDataFrame 评分。请注意,我们将行的第一个元素包装成一个列表,以便 DL 模型可以正确地将其用作输入。 - 
init:这是一个标准的 Python 构造方法。在这里,我们使用它来传入一个之前微调的 DL 模型 URI,finetuned_model_uri,以便我们可以在load_context方法中加载它。请注意,我们不希望直接在init方法中加载模型,因为那样会导致序列化问题(如果你尝试,你会发现直接序列化 DL 模型并不是一件轻松的事情)。由于微调后的 DL 模型已经通过mlflow.pytorchAPI 进行了序列化和反序列化,我们不需要在这里重新发明轮子。推荐的方法是在load_context方法中加载模型。 - 
load_context:此方法在使用mlflow.pyfunc.load_modelAPI 加载 MLflow 模型时调用。在构建 Python 模型后立即执行。在这里,我们通过mlflow.pytorch.load_modelAPI 加载微调后的 DL 模型。请注意,在此方法中加载的任何模型都可以使用相应的反序列化方法。这将为加载其他模型提供可能性,例如语言检测模型,它可能包含不能通过 Python 序列化协议进行序列化的本地代码(例如,C++代码)。这是 MLflow 模型 API 框架提供的一个优点。 
- 
现在,我们有了一个可以接受基于列输入的 MLflow 自定义模型,我们还可以按如下方式定义模型签名:
input = json.dumps([{'name': 'text', 'type': 'string'}]) output = json.dumps([{'name': 'text', 'type': 'string'}]) signature = ModelSignature.from_dict({'inputs': input, 'outputs': output}) 
这个签名定义了一个输入格式,其中包含一个名为text的列,数据类型为string,以及一个输出格式,其中包含一个名为text的列,数据类型为string。mlflow.models.ModelSignature类用于创建这个signature对象。当我们在 MLflow 中记录新的自定义模型时,将使用此签名对象,正如我们将在下一步中看到的。
- 
接下来,我们可以像使用通用的 MLflow
pyfunc模型一样,使用mlflow.pyfunc.log_modelAPI 将这个新的自定义模型记录到 MLflow 中,代码如下:MODEL_ARTIFACT_PATH = 'inference_pipeline_model' with mlflow.start_run() as dl_model_tracking_run: finetuned_model_uri = 'runs:/1290f813d8e74a249c86eeab9f6ed24e/model' inference_pipeline_uri = f'runs:/{dl_model_tracking_run.info.run_id}/{MODEL_ARTIFACT_PATH}' mlflow.pyfunc.log_model( artifact_path=MODEL_ARTIFACT_PATH, conda_env=CONDA_ENV, python_model=InferencePipeline( finetuned_model_uri), signature=signature) 
上述代码将把模型记录到 MLflow 跟踪服务器中,根文件夹名为inference_pipeline_model,这是因为我们定义了MODEL_ARTIFACT_PATH变量并将其值分配给mlflow.pyfunc.log_model方法的artifact_path参数。我们分配给的其他三个参数如下:
- 
conda_env:这是定义此自定义模型运行的 conda 环境。在这里,我们可以传递conda.yaml文件的绝对路径,该文件位于本章根文件夹中,由CONDA_ENV变量定义(此变量的详细信息可以在 GitHub 上找到basic_custom_dl_model.py笔记本的源代码中)。 - 
python_model:在这里,我们调用了刚刚实现的新的InferencePipeline类,并传入了finetuned_model_uri参数。这样,推理管道就会加载正确的微调模型进行预测。 - 
signature:我们还传递了刚刚定义的输入和输出的签名,并将其分配给 signature 参数,以便可以记录模型的输入输出架构并进行验证。 
提醒一下,确保你将'runs:/1290f813d8e74a249c86eeab9f6ed24e/model'值替换为你在步骤 1中生成的自己的微调模型 URI,这样代码才能正确加载原始的微调模型。
- 如果你按照
basic_custom_dl_model.py逐步执行,直到步骤 4,你应该能够在 MLflow 跟踪服务器的Artifacts部分找到一个新记录的模型,正如下面截图所示: 

图 7.4 – 推理 MLflow 模型,带有模型架构和名为 inference_pipeline_model 的根文件夹
正如图 7.4所示,根文件夹的名称(截图左上角)是inference_pipeline_model,这是调用mlflow.pyfunc.log_model时artifact_path参数的指定值。请注意,如果我们没有指定artifact_path参数,默认情况下它的值将是model。你可以通过查看本章早些时候的图 7.1来确认这一点。还要注意,现在在inference_pipeline_model文件夹下有一个MLmodel文件,我们可以看到其完整内容如下:

图 7.5 – inference_pipeline_model的 MLmodel 文件内容
从图 7.5中可以看到,在底部附近的signature部分是一个新的部分,相较于图 7.1有所不同。然而,在模型类型方面还有一些更重要的区别。inference_pipeline_model的类型是通用的mlflow.pyfunc.model模型,不再是mlflow.pytorch模型。事实上,如果你将图 7.5与图 7.1进行对比,后者是我们的 PyTorch 微调的深度学习模型,你会发现其中有关于pytorch及其model_data和pytorch_version的部分,而这些在图 7.5中已经完全消失。对于 MLflow 来说,它并不了解原始的 PyTorch 模型,而只是将其作为新封装的通用 MLflow pyfunc模型。这是一个好消息,因为现在我们只需要一个通用的 MLflow pyfunc API 来加载模型,无论封装的模型多复杂,或者这个通用的pyfunc模型中包含多少额外的预处理和后处理步骤,当我们在下一节中实现时都不成问题。
- 
我们现在可以使用通用的
mlflow.pyfunc.load_model来加载inference_pipeline_model并使用输入的pandas数据框进行预测,如下所示:input = {"text":["what a disappointing movie","Great movie"]} input_df = pd.DataFrame(input) with mlflow.start_run(): loaded_model = \ mlflow.pyfunc.load_model(inference_pipeline_uri) results = loaded_model.predict(input_df) 
这里,inference_pipeline_uri是步骤 4中生成的 URI,作为inference_pipeline_model的唯一标识符。例如,inference_pipeline_uri的值可能如下所示:
'runs:/6edf6013d2454f7f8a303431105f25f2/inference_pipeline_model'
一旦模型加载完成,我们只需调用predict函数对input_df数据框进行评分。这会调用我们新实现的InferencePipleine类的predict函数,如步骤 2中所述。结果将类似如下:
![图 7.6 – 推理管道在 pandas 数据框格式中的输出]
图 7.6 – 推理管道在 pandas 数据框格式中的输出
如果你看到像图 7.6中的预测结果,那么你应该为自己感到骄傲,因为你刚刚实现了一个功能强大的自定义 MLflow Python 模型,这个模型具有巨大的灵活性和能力,使我们能够在不更改任何日志记录和加载模型部分的情况下,实施预处理和后处理逻辑,正如我们将在下一节中看到的那样。
创建一个新的 MLflow 自定义模型类型
正如本章所示,我们可以使用已经训练好的模型构建一个封装的 MLflow 自定义模型进行推理。需要注意的是,也可以为训练目的构建一个全新的 MLflow 自定义模型版本。这在你有一个尚未被内置 MLflow 模型版本支持的模型时是必要的。例如,如果你想基于自己的语料库训练一个全新的 FastText 模型,但截至 MLflow 1.23.1 版本,还没有 FastText 的 MLflow 模型版本,那么你可以构建一个新的 FastText MLflow 模型版本(参见参考:medium.com/@pennyqxr/how-save-and-load-fasttext-model-in-mlflow-format-37e4d6017bf0)。有兴趣的读者还可以在本章末尾的进一步阅读部分找到更多参考资料。
在深度学习推理管道中实现预处理和后处理步骤
现在我们有了一个基本的通用 MLflow Python 模型,可以对输入的 pandas DataFrame 进行预测,并在另一个 pandas DataFrame 中生成输出,我们已经准备好处理之前提到的多步骤推理场景。请注意,尽管上一节中的初始实现看起来可能没有什么突破性,但它为实现之前无法实现的预处理和后处理逻辑打开了大门,同时仍然保持使用通用的 mlflow.pyfunc.log_model 和 mlflow.pyfunc.load_model 将整个推理管道视为一个通用的 pyfunc 模型的能力,无论原始的深度学习模型有多复杂,或者有多少额外的预处理和后处理步骤。让我们在本节中看看如何做到这一点。你可能想查看 GitHub 上的 VS Code 笔记本中的 multistep_inference_model.py 来跟随本节中的步骤:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/notebooks/multistep_inference_model.py。
在图 7.3中,我们描述了模型预测之前的两个预处理步骤,以及模型预测之后的两个后处理步骤。那么,在保持整个推理管道作为单个 MLflow 模型的同时,在哪里以及如何添加预处理和后处理逻辑呢?事实证明,主要的改动会发生在上一节实现的 InferencePipeline 类中。让我们在以下小节中一步步梳理实现和变化。
实现语言检测预处理逻辑
首先让我们实现语言检测的预处理逻辑:
- 
为了检测输入文本的语言类型,我们可以使用 Google 的
pyfunc模型。好消息是,MLflow 的load_context方法允许我们加载此模型,而无需担心序列化和反序列化。我们只需在InferencePipeline类的load_context方法中添加两行代码,如下所示,以加载语言检测器模型:import gcld3 self.detector = gcld3.NNetLanguageIdentifier( min_num_bytes=0, max_num_bytes=1000) 
上述两行代码被添加到了load_context方法中,此外还有先前用于加载情感分类微调深度学习模型的语句。这样,语言检测器将在InferencePipeline类初始化完成后立即加载。该语言检测器将使用输入的前 1,000 个字节来确定语言类型。一旦语言检测器加载完成,我们就可以在预处理方法中使用它来检测语言。
- 
在语言检测的预处理方法中,我们将接受每一行输入文本,检测语言,并返回语言类型作为
string,如下所示:def preprocessing_step_lang_detect(self, row): language_detected = \ self.detector.FindLanguage(text=row[0]) if language_detected.language != 'en': print("found Non-English Language text!") return language_detected.language 
实现非常直接。我们还添加了一个打印语句,用于查看是否有非英语文本输入到控制台。如果您的业务逻辑需要在处理某些特定语言时执行预防性操作,您可以在此方法中添加更多逻辑。在这里,我们只是返回检测到的语言类型。
- 
然后,在
sentiment_classifier方法中,为每一行输入打分,我们可以在预测之前添加一行代码,首先检测语言,如下所示:language_detected = self.preprocessing_step_lang_detect(row) 
然后,我们将language_detected变量传递给响应,如我们将在后处理逻辑实现中看到的那样。
这就是将语言检测作为推理管道中的预处理步骤实现的全部过程。
现在让我们看看如何实现另一个步骤:缓存,这需要同时进行预处理(检测是否存在任何预先匹配的预测结果)和后处理(将输入和预测结果的键值对存储到缓存中)。
实现缓存的预处理和后处理逻辑
让我们看看如何在InferencePipeline类中实现缓存:
- 
我们可以添加一个新语句,在
init方法中初始化缓存存储,因为它没有问题被序列化或反序列化:from cachetools import LRUCache self.cache = LRUCache(100) 
这将初始化一个带有 100 个对象的最近最少使用(LRU)缓存。
- 
接下来,我们将添加一个预处理方法来检测输入是否在缓存中:
def preprocessing_step_cache(self, row): if row[0] in self.cache: print("found cached result") return self.cache[row[0]] 
如果它在缓存中找到与输入行完全匹配的键,那么它将返回缓存的值。
- 
在
sentiment_classifier方法中,我们可以添加预处理步骤来检查缓存,如果找到缓存,它将立即返回缓存的响应,而无需调用昂贵的深度学习模型分类器:cached_response = self.preprocessing_step_cache(row) if cached_response is not None: return cached_response 
这个预处理步骤应该作为 sentiment_classifier 方法中的第一步,在进行语言检测和模型预测之前放置。当输入中有大量重复项时,这可以显著加快实时预测响应的速度。
- 
同样在
sentiment_classifier方法中,我们需要添加一个后处理步骤,将新的输入和预测响应存储在缓存中:self.cache[row[0]]=response 
就是这样。我们已经成功地将缓存添加为 InferencePipeline 类中的预处理和后处理步骤。
实现响应组成的后处理逻辑
现在让我们看看如何在原始深度学习模型预测被调用并返回结果后,作为后处理步骤实现响应组成逻辑。仅仅返回 positive 或 negative 的预测标签通常是不够的,因为我们希望知道使用的是哪个版本的模型,以及在生产环境中进行调试和诊断时检测到的语言。推理管道对调用者的响应将不再是简单的字符串,而是一个序列化的 JSON 字符串。按照以下步骤实现这个后处理逻辑:
- 
在
InferencePipeline类的init方法中,我们需要添加一个新的inference_pipeline_uri参数,以便捕获该通用 MLflowpyfunc模型的引用,进行溯源跟踪。finetuned_model_uri和inference_pipeline_uri两个参数将成为响应 JSON 对象的一部分。init方法现在看起来如下:def __init__(self, finetuned_model_uri, inference_pipeline_uri=None): self.cache = LRUCache(100) self.finetuned_model_uri = finetuned_model_uri self.inference_pipeline_uri = inference_pipeline_uri - 
在
sentiment_classifier方法中,添加一个新的后处理语句,以根据检测到的语言、预测标签以及包含finetuned_model_uri和inference_pipeline_uri的模型元数据来组成新的响应:response = json.dumps({ 'response': { 'prediction_label': pred_label }, 'metadata': { 'language_detected': language_detected, }, 'model_metadata': { 'finetuned_model_uri': self.finetuned_model_uri, 'inference_pipeline_model_uri': self.inference_pipeline_uri }, }) 
请注意,我们使用 json.dumps 将嵌套的 Python 字符串对象编码为 JSON 格式的字符串,以便调用者可以轻松地使用 JSON 工具解析响应。
- 
在
mlflow.pyfunc.log_model语句中,我们需要在调用InferencePipeline类时添加一个新的inference_pipeline_uri参数:mlflow.pyfunc.log_model( artifact_path=MODEL_ARTIFACT_PATH, conda_env=CONDA_ENV, python_model=InferencePipeline(finetuned_model_uri, inference_pipeline_uri), signature=signature) 
这将记录一个新的推理管道模型,包含我们实现的所有附加处理逻辑。这完成了图 7.3 中描述的多步骤推理管道的实现。
请注意,一旦模型记录了所有这些新步骤,要使用这个新的推理管道,即加载这个模型,不需要任何代码修改。我们可以像以前一样加载新记录的模型:
loaded_model = mlflow.pyfunc.load_model(inference_pipeline_uri)
如果你已经按照步骤进行到现在,你应该也逐步运行了 multistep_inference_model.py 的 VS Code notebook,直到本小节描述的第 3 步。现在我们可以尝试使用这个新的多步骤推理管道进行测试。我们可以准备一组新的输入数据,其中包含重复项和一个非英语文本字符串,如下所示:
input = {"text":["what a disappointing movie", "Great movie", "Great movie", "很好看的电影"]}
input_df = pd.DataFrame(input)
这个输入包含了两个重复项(Great movie)和一个中文文本字符串(输入列表中的最后一个元素,其中文本含义与Great Movie相同)。现在我们只需要加载模型并像之前一样调用results = loaded_model.predict(input_df)。在执行该预测语句时,你应该能在控制台输出中看到以下两条语句:
found cached result 
found Non-English language text.
这意味着我们的缓存和语言检测器工作正常!
我们也可以通过以下代码打印输出结果,以便再次检查我们的多步骤管道是否正常工作:
for i in range(results.size):
    print(results['text'][i])
这将打印出响应中每一行的完整内容。在这里,我们以最后一行(包含中文文本)作为示例进行展示:

图 7.7 – 使用多步骤推理管道处理中文文本字符串输入的 JSON 响应
如图 7.7所示,prediction_label包含在响应中(其值为negative)。由于我们在 JSON 响应中的metadata部分使用了language_detected字段,我们看到了字符串"zh",表示中文。这是语言检测器在预处理步骤中生成的结果。此外,model_metadata部分包括了原始的finetuned_model_uri和inference_pipeline_model_uri。这些是与 MLflow 追踪服务器相关的 URI,我们可以用它们来唯一地追踪和识别使用了哪个微调模型和推理管道来进行此预测结果。这对于生产环境中的溯源跟踪和诊断分析非常重要。将这个完整的 JSON 输出与图 7.6中的早期预测标签输出进行比较,可以看出它为推理管道的使用者提供了更丰富的上下文信息。
如果你在笔记本中看到类似图 7.7的 JSON 输出,给自己鼓掌,因为你刚刚完成了实现一个可以重用并部署到生产环境中的多步骤推理管道的重大里程碑,适用于现实的商业场景。
将推理管道作为新入口点实现到主 MLproject 中
现在我们已经成功地将多步骤推理管道作为新的自定义 MLflow 模型实现,我们可以更进一步,将其作为主MLproject中的一个新入口点,这样我们就可以运行整个管道的端到端流程(图 7.8)。请查看本章代码,访问 GitHub 以在本地环境中运行管道。

图 7.8 – 使用 MLproject 的端到端管道
我们可以将新的入口点inference_pipeline_model添加到MLproject文件中。你可以在 GitHub 仓库中查看这个文件(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/MLproject):
inference_pipeline_model:
    parameters:
      finetuned_model_run_id: { type: str, default: None }
    command: "python pipeline/inference_pipeline_model.py --finetuned_model_run_id {finetuned_model_run_id}"
这个入口点或步骤可以独立调用,也可以作为整个管道的一部分,如图 7.8所示。提醒一下,执行 MLflow run命令之前,请确保按照本章README文件中所述,已设置 MLflow 跟踪服务器和后端存储 URI 的环境变量。此步骤会记录并注册一个新的inference_pipeline_model,该模型本身包含多步预处理和后处理逻辑。如果你知道finetuned_model_run_id,可以使用以下命令在chapter07文件夹的根目录下运行此步骤:
mlflow run . -e inference_pipeline_model  --experiment-name dl_model_chapter07 -P finetuned_model_run_id=07b900a96af04037a956c74ef691396e
这不仅会在 MLflow 跟踪服务器中记录一个新的inference_pipeline_model,还会在 MLflow 模型注册表中注册一个新的inference_pipeline_model版本。你可以通过以下链接在本地 MLflow 服务器中找到注册的inference_pipeline_model:
http://localhost/#/models/inference_pipeline_model/
作为示例,以下截图展示了注册的inference_pipeline_model版本 6:

图 7.9 – 注册的inference_pipeline_model,版本 6
你也可以按如下方式运行整个端到端管道,如图 7.8所示:
mlflow run . --experiment-name dl_model_chapter07
这将执行这个端到端管道中的所有步骤,并最终在模型注册表中记录并注册inference_pipeline_model。
inference_pipeline_model.py的 Python 代码实现,在调用入口点inference_pipeline_model时执行,基本上是复制了我们在 VS Code 笔记本中为multistep_inference_model.py实现的InferencePipeline类,并进行了一些小的修改,具体如下:
- 
添加一个任务函数,作为此步骤的参数化入口点执行:
def task(finetuned_model_run_id, pipeline_run_name): 
这个函数的作用是启动一个新的 MLflow 运行,以记录和注册一个新的推理管道模型。
- 
通过如下方式启用在记录时的模型注册:
mlflow.pyfunc.log_model( artifact_path=MODEL_ARTIFACT_PATH, conda_env=CONDA_ENV, python_model=InferencePipeline( finetuned_model_uri, inference_pipeline_uri), signature=signature, registered_model_name=MODEL_ARTIFACT_PATH) 
请注意,我们将MODEL_ARTIFACT_PATH的值(即inference_pipeline_model)分配给registered_model_name。这使得模型可以在 MLflow 模型注册表中以这个名字注册,如图 7.9所示。
这个新入口点的完整代码可以在 GitHub 仓库中找到:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/pipeline/inference_pipeline_model.py。
请注意,我们还需要在 main.py 文件中添加一个新部分,以便 inference_pipeline_model 入口点也可以从 main 入口点内调用。实现方法非常简单,类似于之前在 第四章 追踪代码和数据版本控制 中描述的添加其他步骤。感兴趣的读者可以查看 GitHub 上的 main.py 文件,了解实现细节:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter07/main.py。
本章完成了在 MLproject 中添加一个新入口点的实现,以便我们可以使用 MLflow run 命令工具运行多步骤的推理管道创建、日志记录和注册。
总结
本章介绍了使用 MLflow 的自定义 Python 模型方法(即 mlflow.pyfunc.PythonModel)创建多步骤推理管道这一非常重要的主题。
我们讨论了生产环境中四种推理工作流模式,其中通常单一的训练模型不足以完成业务应用需求。很有可能在模型训练和开发阶段没有看到一些预处理和后处理逻辑。这就是为什么 MLflow 的 pyfunc 方法是一种优雅的方式,能够实现一个自定义的 MLflow 模型,该模型可以在训练好的深度学习模型之外,加入额外的预处理和后处理逻辑。
我们成功实现了一个推理管道模型,它将我们的深度学习情感分类器与谷歌的紧凑型语言检测器(Compact Language Detector)、缓存以及除预测标签外的其他模型元数据结合。我们更进一步,将推理管道模型的创建步骤融入到端到端的模型开发工作流中,以便通过一个 MLflow run 命令生成一个注册的推理管道模型。
本章中学习的技能和课程对任何希望使用 MLflow pyfunc 方法实现真实推理管道的人来说都至关重要。这也为支持灵活且强大的生产环境部署打开了大门,相关内容将在下一章中讨论。
进一步阅读
- 
MLflow 模型(MLflow 文档):
www.mlflow.org/docs/latest/models.html# - 
在 MLflow 中实现 statsmodels 模型类型:
blog.stratio.com/implementing-the-statsmodels-flavor-in-mlflow/ - 
InferLine: ML 推理管道构建框架:
www2.eecs.berkeley.edu/Pubs/TechRpts/2018/EECS-2018-76.pdf - 
批量推理与在线推理:
mlinproduction.com/batch-inference-vs-online-inference/ - 
构建小型 MLOps 流水线的经验教训:
www.nestorsag.com/blog/lessons-from-building-a-small-ml-ops-pipeline/ - 
在 Hugging Face 上使用 MLflow 的文本摘要器:
vishsubramanian.me/hugging-face-with-mlflow/ 
第八章:第八章:在大规模环境下部署深度学习推理管道
部署 深度学习(DL)推理管道以供生产使用既令人兴奋又具有挑战性。令人兴奋的部分是,最终深度学习模型管道可以用来对真实生产数据进行预测,为商业场景提供真正的价值。然而,具有挑战性的一点是,有不同的深度学习模型服务平台和托管环境。选择适合的框架来应对合适的模型服务场景并不容易,这需要在最小化部署复杂性的同时提供可扩展、成本效益高的最佳模型服务体验。本章将介绍不同的部署场景和托管环境的概述,然后提供实际操作,学习如何使用 MLflow 部署工具部署到不同的环境,包括本地和远程云环境。到本章结束时,您应该能够自信地将 MLflow 深度学习推理管道部署到各种托管环境中,用于批处理或实时推理服务。
在本章中,我们将讨论以下主要内容:
- 
了解部署和托管环境的全貌
 - 
本地部署用于批处理和 Web 服务推理
 - 
使用 Ray Serve 和 MLflow 部署插件进行部署
 - 
部署到 AWS SageMaker – 完整的端到端指南
 
技术要求
本章学习所需的项目:
- 
本章的 GitHub 仓库代码:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter08。 - 
Ray Serve 和
mlflow-ray-serve插件:github.com/ray-project/mlflow-ray-serve。 - 
AWS SageMaker:您需要拥有一个 AWS 账户。您可以通过
aws.amazon.com/free/轻松创建一个免费的 AWS 账户。 - 
AWS 命令行界面(CLI):
docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html。 - 
Docker Desktop:
www.docker.com/products/docker-desktop/。 - 
完成本书中第七章《多步骤深度学习推理管道》的示例。这将为您提供一个可以在本章中使用的可部署推理管道。
 
了解不同的部署工具和托管环境
在 MLOps 技术栈中有不同的部署工具,针对不同的目标用例和主机环境来部署不同的模型推断管道。在第七章,多步骤深度学习推断管道,我们学习了不同的推断场景和要求,并实现了一个可以部署到模型托管/服务环境中的多步骤 DL 推断管道。现在,我们将学习如何将这样的模型部署到几个特定的模型托管和服务环境中。这在图 8.1中如下所示:

图 8.1 – 使用模型部署工具将模型推断管道部署到模型托管和服务环境
如图 8.1 所示,针对不同的模型托管和服务环境可能存在不同的部署工具。在这里,我们列出了三种典型的场景如下:
- 
规模化批量推断:如果我们想要定期进行批量推断,我们可以使用 PySpark 的用户定义函数(UDF)加载一个 MLflow 模型格式来执行此操作,因为我们可以利用 Spark 在分布式集群上的可扩展计算方法(
mlflow.org/docs/latest/models.html#export-a-python-function-model-as-an-apache-spark-udf)。我们将在下一节中展示如何做到这一点的示例。 - 
规模化流式推断:这通常需要一个托管模型即服务(MaaS)的端点。存在许多用于生产级部署和模型服务的工具和框架。在我们开始学习如何在本章中进行这种类型的部署之前,我们将在本节比较几种工具,以了解它们的工作方式及其与 MLflow 集成的情况。
 - 
设备上的模型推断:这是一个称为TinyML的新兴领域,它在资源有限的环境中部署 ML/DL 模型,例如移动设备、传感器或边缘设备(
www.kdnuggets.com/2021/11/on-device-deep-learning-pytorch-mobile-tensorflow-lite.html)。两个流行的框架是 PyTorch Mobile(pytorch.org/mobile/home/)和 TensorFlow Lite(www.tensorflow.org/lite)。这不是本书的重点。建议您在本章结束时查看一些进一步阅读材料。 
现在,让我们看看有哪些工具可用于将模型推断部署为服务,特别是那些支持 MLflow 模型部署的工具。有三种类型的模型部署和服务工具,如下所示:
- 
MLflow 内置模型部署:这是 MLflow 发布时自带的功能,包括部署到本地 Web 服务器、AWS SageMaker 和 Azure ML。Databricks 上也有一个托管的 MLflow,支持模型服务,在本书写作时处于公开审阅阶段,我们不会在本书中涵盖该内容,因为它在官方 Databricks 文档中已经有很好的展示(感兴趣的读者可以在此网站查阅有关该 Databricks 功能的官方文档:
docs.databricks.com/applications/mlflow/model-serving.html)。不过,在本章中,我们将展示如何使用 MLflow 内置的模型部署功能,将模型部署到本地和远程的 AWS SageMaker。 - 
mlflow-torchserv(github.com/mlflow/mlflow-torchserve),mlflow-ray-serve(github.com/ray-project/mlflow-ray-serve),以及mlflow-triton-plugin(github.com/triton-inference-server/server/tree/v2.17.0/deploy/mlflow-triton-plugin)。在本章中,我们将展示如何使用mlflow-ray-serve插件进行部署。 - 
使用
mlflow-ray-serve插件部署 MLflow Python 模型。需要注意的是,尽管在本书中我们展示了如何使用 MLflow 自定义插件通过 Ray Serve 等通用的机器学习服务工具进行部署,但重要的是要注意,无论是否使用 MLflow 自定义插件,通用机器学习服务工具都能做更多的事情。通过专门的推理引擎优化深度学习推理
有一些特殊的 MLflow 模型格式,比如ONNX(
onnx.ai/)和TorchScript(huggingface.co/docs/transformers/v4.17.0/en/serialization#torchscript),它们专门设计用于深度学习模型推理运行时。我们可以将深度学习模型转换为 ONNX 模型格式(github.com/microsoft/onnxruntime)或 TorchScript 服务器(pytorch.org/serve/)。由于 ONNX 和 TorchScript 仍在发展中,并且它们是专门为原始深度学习模型部分设计的,而不是整个推理管道,因此我们在本章中不会涵盖它们。 
现在我们已经很好地理解了各种部署工具和模型服务框架,接下来让我们通过具体示例学习如何进行部署。
本地部署用于批量推理和 Web 服务推理
在开发和测试过程中,我们通常需要将模型本地部署以验证其是否按预期工作。我们来看看如何在两种场景下进行部署:批量推理和 Web 服务推理。
批量推理
对于批量推理,请按照以下说明操作:
- 
请确保您已完成第七章,多步骤深度学习推理管道。这将生成一个 MLflow
pyfunc深度学习推理模型管道 URI,可以通过标准的 MLflow Python 函数加载。已记录的模型可以通过run_id和模型名称唯一定位,如下所示:logged_model = 'runs:/37b5b4dd7bc04213a35db646520ec404/inference_pipeline_model' 
模型还可以通过模型注册表中的模型名称和版本号进行识别,如下所示:
logged_model = 'models:/inference_pipeline_model/6'
- 
按照
README.md文件中使用 PySpark UDF 函数进行批量推理部分的说明(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter08/README.md),设置本地虚拟环境、完整的 MLflow 跟踪服务器和一些环境变量,以便我们能够在本地环境中执行代码。 - 
使用 MLflow 的
mlflow.pyfunc.spark_udfAPI 加载模型,以创建一个 PySpark UDF 函数,如下所示。您可能需要查看 GitHub 上的batch_inference.py文件来跟进:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter08/batch/batch_inference.py:loaded_model = mlflow.pyfunc.spark_udf(spark, model_uri=logged_model, result_type=StringType()) 
这将把推理管道封装为一个返回结果类型为String的 PySpark UDF 函数。这是因为我们的模型推理管道具有一个模型签名,要求输出为string类型的列。
- 
现在,我们可以将 PySpark UDF 函数应用于输入的 DataFrame。请注意,输入的 DataFrame 必须包含一个
text列,并且该列的数据类型必须为string,因为这是模型签名的要求:df = df.withColumn('predictions', loaded_model()) 
因为我们的模型推理管道已经定义了模型签名,所以如果输入的 DataFrame 中包含text列(在本示例中是df),我们就不需要指定任何列参数。请注意,我们可以使用 Spark 的read API 读取大量数据,支持多种数据格式,如 CSV、JSON、Parquet 等。在我们的示例中,我们从 IMDB 数据集读取了test.csv文件。如果数据量较大,这将利用 Spark 强大的分布式计算在集群上执行。这使得我们可以轻松地进行大规模的批量推理。
- 
要从头到尾运行批量推理代码,您应该查看存储库中提供的完整代码,地址为:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter08/batch/batch_inference.py。确保在batch文件夹中运行以下命令之前,将logged_model变量替换为您自己的run_id和模型名称,或注册的模型名称和版本:python batch_inference.py - 
你应该能在屏幕上看到图 8.2中的输出:
 

图 8.2 – 使用 PySpark UDF 函数进行批量推理
从图 8.2中可以看到,我们加载的多步推理管道工作正常,甚至能够检测到非英文文本和重复内容,尽管语言检测器可能会产生一些误报。输出是一个两列的 DataFrame,其中模型预测的 JSON 响应保存在 predictions 列中。请注意,你可以在 Databricks notebook 中使用 batch_inference.py 中提供的相同代码,通过更改输入数据和已记录模型的位置,利用 Spark 集群处理大量的输入数据。
现在我们已经知道如何进行大规模的批量推理,让我们来看看如何将相同的模型推理管道部署到本地 web 服务中。
模型作为 web 服务
我们可以将相同的已记录模型推理管道部署到本地的 web 服务中,并拥有一个接受 HTTP 请求并返回 HTTP 响应的端点。
本地部署非常简单,只需要一条命令。我们可以使用模型 URI 来部署已记录的模型或注册的模型,就像之前的批量推理一样,具体如下:
mlflow models serve -m models:/inference_pipeline_model/6
你应该能够看到如下内容:
2022/03/06 21:50:19 INFO mlflow.models.cli: Selected backend for flavor 'python_function'
2022/03/06 21:50:21 INFO mlflow.utils.conda: === Creating conda environment mlflow-a0968092d20d039088e2875ad04bbaa0f3a75206 ===
± |main U:1 ?:8 X| done
Solving environment: done
这将使用已记录的模型创建 conda 环境,确保其拥有运行所需的所有依赖项。创建完 conda 环境后,你应该会看到如下内容:
2022/03/06 21:52:11 INFO mlflow.pyfunc.backend: === Running command 'source /Users/yongliu/opt/miniconda3/bin/../etc/profile.d/conda.sh && conda activate mlflow-a0968092d20d039088e2875ad04bbaa0f3a75206 1>&2 && gunicorn --timeout=60 -b 127.0.0.1:5000 -w 1 ${GUNICORN_CMD_ARGS} -- mlflow.pyfunc.scoring_server.wsgi:app'
[2022-03-06 21:52:12 -0800] [97554] [INFO] Starting gunicorn 20.1.0
[2022-03-06 21:52:12 -0800] [97554] [INFO] Listening at: http://127.0.0.1:5000 (97554)
[2022-03-06 21:52:12 -0800] [97554] [INFO] Using worker: sync
[2022-03-06 21:52:12 -0800] [97561] [INFO] Booting worker with pid: 97561
现在,模型已经作为 web 服务部署,并准备好接受 HTTP 请求进行模型预测。打开一个新的终端窗口,输入以下命令调用模型 web 服务来获取预测响应:
curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{
    "columns": ["text"],
    "data": [["This is the best movie we saw."], ["What a movie!"]]
}'
我们可以立即看到如下预测响应:
[{"text": "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/07b900a96af04037a956c74ef691396e/model\", \"inference_pipeline_model_uri\": \"runs:/37b5b4dd7bc04213a35db646520ec404/inference_pipeline_model\"}}"}, {"text": "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/07b900a96af04037a956c74ef691396e/model\", \"inference_pipeline_model_uri\": \"runs:/37b5b4dd7bc04213a35db646520ec404/inference_pipeline_model\"}}"}]
如果你已经按照步骤操作并看到预测结果,你应该为自己感到非常骄傲,因为你刚刚将一个深度学习模型推理管道部署到了本地 web 服务中!这对于测试和调试非常有用,而且在生产环境的 web 服务器上模型的行为不会发生变化,所以我们应该确保它在本地 web 服务器上正常工作。
到目前为止,我们已经学习了如何使用内置的 MLflow 部署工具。接下来,我们将学习如何使用通用的部署工具 Ray Serve 来部署一个 MLflow 推理管道。
使用 Ray Serve 和 MLflow 部署插件进行部署
更通用的部署方式是使用像 Ray Serve 这样的框架(docs.ray.io/en/latest/serve/index.html)。Ray Serve 有几个优点,例如支持不同的深度学习模型框架、原生 Python 支持以及支持复杂的模型组合推理模式。Ray Serve 支持所有主要的深度学习框架和任何任意的业务逻辑。那么,我们能否同时利用 Ray Serve 和 MLflow 进行模型部署和服务呢?好消息是,我们可以使用 Ray Serve 提供的 MLflow 部署插件来实现这一点。接下来我们将介绍如何使用mlflow-ray-serve插件通过 Ray Serve 进行 MLflow 模型部署(github.com/ray-project/mlflow-ray-serve)。在开始之前,我们需要安装mlflow-ray-serve包:
pip install mlflow-ray-serve
接下来,我们需要首先使用以下两个命令在本地启动一个单节点 Ray 集群:
ray start --head
serve start
这将在本地启动一个 Ray 集群,并且你可以通过浏览器访问它的仪表板,网址是http://127.0.0.1:8265/#/,如下所示:

图 8.3 – 本地运行的 Ray 集群
图 8.3展示了一个本地运行的 Ray 集群。然后,你可以执行以下命令将inference_pipeline_model部署到 Ray Serve:
mlflow deployments create -t ray-serve -m runs:/63f101fb3700472ca58975488636f4ae/inference_pipeline_model --name dl-inference-model-on-ray -C num_replicas=1
这将显示以下屏幕输出:
2022-03-20 20:16:46,564    INFO worker.py:842 -- Connecting to existing Ray cluster at address: 127.0.0.1:6379
2022-03-20 20:16:46,717    INFO api.py:242 -- Updating deployment 'dl-inference-model-on-ray'. component=serve deployment=dl-inference-model-on-ray
(ServeController pid=78159) 2022-03-20 20:16:46,784    INFO deployment_state.py:912 -- Adding 1 replicas to deployment 'dl-inference-model-on-ray'. component=serve deployment=dl-inference-model-on-ray
2022-03-20 20:17:10,309    INFO api.py:249 -- Deployment 'dl-inference-model-on-ray' is ready at `http://127.0.0.1:8000/dl-inference-model-on-ray`. component=serve deployment=dl-inference-model-on-ray
python_function deployment dl-inference-model-on-ray is created
这意味着位于http://127.0.0.1:8000/dl-inference-model-on-ray的端点已准备好为在线推理请求提供服务!你可以使用以下提供的 Python 代码在chapter08/ray_serve/query_ray_serve_endpoint.py中测试这个部署:
python ray_serve/query_ray_serve_endpoint.py
这将在屏幕上显示如下结果:
2022-03-20 21:16:45,125    INFO worker.py:842 -- Connecting to existing Ray cluster at address: 127.0.0.1:6379
[{'name': 'dl-inference-model-on-ray', 'info': Deployment(name=dl-inference-model-on-ray,version=None,route_prefix=/dl-inference-model-on-ray)}]
{
    "columns": [
        "text"
    ],
    "index": [
        0,
        1
    ],
    "data": [
        [
            "{\"response\": {\"prediction_label\": [\"negative\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/be2fb13fe647481eafa071b79dde81de/model\", \"inference_pipeline_model_uri\": \"runs:/63f101fb3700472ca58975488636f4ae/inference_pipeline_model\"}}"
        ],
        [
            "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/be2fb13fe647481eafa071b79dde81de/model\", \"inference_pipeline_model_uri\": \"runs:/63f101fb3700472ca58975488636f4ae/inference_pipeline_model\"}}"
        ]
    ]
}
你应该会看到预期的推理模型响应。如果你按照这些步骤操作至此,恭喜你成功通过mlflow-ray-serve MLflow 部署插件完成了部署!如果你不再需要这个 Ray Serve 实例,可以通过执行以下命令行来停止它:
ray stop
这将停止你本地机器上所有正在运行的 Ray 实例。
使用 MLflow 部署插件进行部署
有几个 MLflow 部署插件。我们刚刚展示了如何使用mlflow-ray-serve部署一个通用的 MLflow Python 模型inference_pipeline_model。这为将 Ray 集群部署到许多目标目的地打开了大门,你可以在任何云服务提供商上启动 Ray 集群。由于本书的范围限制,我们不会在这一章进一步探讨更多细节。如果你感兴趣,可以参考 Ray 的文档了解如何启动云集群(AWS、Azure 和Google Cloud Platform(GCP)):docs.ray.io/en/latest/cluster/cloud.html#:~:text=The%20Ray%20Cluster%20Launcher%20can,ready%20to%20launch%20your%20cluster。一旦 Ray 集群启动,你可以按照相同的流程部署 MLflow 模型。
现在我们知道了几种在本地部署的方法,并且如果需要的话可以进一步部署到云端,使用 Ray Serve,让我们看看如何在下一节中部署到云管理的推理服务 AWS SageMaker,因为它被广泛使用,并且可以提供在现实场景中如何部署的良好教训。
部署到 AWS SageMaker —— 完整的端到端指南
AWS SageMaker 是由 AWS 管理的云托管模型服务。我们将以 AWS SageMaker 为例,展示如何将模型部署到托管 Web 服务的远程云提供商,以服务真实的生产流量。AWS SageMaker 提供了一套包括支持注释和模型训练等在内的 ML/DL 相关服务。在这里,我们展示如何为部署 BYOM(Bring Your Own Model,自带模型)做准备和部署。这意味着您已在 AWS SageMaker 之外训练了模型推理管道,现在只需部署到 SageMaker 进行托管。按照以下步骤准备和部署 DL 情感模型。需要几个先决条件:
- 
您必须在本地环境中运行 Docker Desktop。
 - 
您必须拥有一个 AWS 账户。您可以通过免费注册网站
aws.amazon.com/free/轻松创建一个免费的 AWS 账户。 
一旦满足这些要求,激活 dl-model-chapter08 的 conda 虚拟环境,按照以下几个步骤部署到 SageMaker。我们将这些步骤细分为六个子章节如下:
- 
构建本地 SageMaker Docker 镜像
 - 
将额外的模型构件层添加到 SageMaker Docker 镜像上
 - 
使用新构建的 SageMaker Docker 镜像进行本地部署测试
 - 
将 SageMaker Docker 镜像推送到 AWS Elastic Container Registry
 - 
部署推理管道模型以创建 SageMaker 终端节点
 - 
查询 SageMaker 终端节点进行在线推理
 
让我们从第一步开始构建本地 SageMaker Docker 镜像。
第 1 步:构建本地 SageMaker Docker 镜像
我们故意从本地构建开始,而不是推送到 AWS,这样我们可以学习如何在此基础镜像上添加额外的层,并在本地验证一切,避免产生任何云端费用:
mlflow sagemaker build-and-push-container --build --no-push -c mlflow-dl-inference
您将看到大量的屏幕输出,最后会显示类似以下内容:
#15 exporting to image
#15 sha256:e8c613e07b0b7ff33893b694f7759a10 d42e180f2b4dc349fb57dc6b71dcab00
#15 exporting layers
#15 exporting layers 8.7s done
#15 writing image sha256:95bc539b021179e5e87087 012353ebb43c71410be535ef368d1121b550c57bd4 done
#15 naming to docker.io/library/mlflow-dl-inference done
#15 DONE 8.7s
如果您看到镜像名称 mlflow-dl-inference,那么说明您已成功创建了一个符合 SageMaker 标准的 MLflow 模型服务 Docker 镜像。您可以通过运行以下命令来验证:
docker images | grep mlflow-dl-inference
您应该看到类似以下的输出:
mlflow-dl-inference          latest                  95bc539b0211   6 minutes ago   2GB
第 2 步:将额外的模型构件层添加到 SageMaker Docker 镜像上
回想一下,我们的推理管道模型是基于一个微调的深度学习模型构建的,我们通过 MLflow PythonModel API 的 load_context 函数 (www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html#mlflow.pyfunc.PythonModel) 加载该模型,而不需要序列化微调后的模型本身。部分原因是因为 MLflow 无法通过 pickle 正确序列化 PyTorch DataLoader (pytorch.org/docs/stable/data.html#single-and-multi-process-data-loading),因为 DataLoader 从目前的文档来看并不实现 pickle 序列化。这为我们提供了一个机会,去学习当一些依赖项无法正确序列化时,我们该如何进行部署,尤其是在处理现实世界的深度学习模型时。
让 Docker 容器访问 MLflow 跟踪服务器的两种方法
有两种方法可以让 Docker 容器(如 mlflow-dl-inference)在运行时访问并加载微调模型。第一种方法是让容器包含 MLflow 跟踪服务器的 URL 和访问令牌。这在企业环境中可能会引发一些安全隐患,因为此时 Docker 镜像中包含了一些安全凭证。第二种方法是直接将所有引用的工件复制到新建的 Docker 镜像中,从而创建一个自给自足的镜像。在运行时,它不需要知道原始 MLflow 跟踪服务器的位置,因为它已将所有模型工件保存在本地。这种自包含的方法消除了任何安全泄露的担忧。在本章中,我们采用了第二种方法进行部署。
本章中,我们将把引用的微调模型复制到一个新的 Docker 镜像中,该镜像基于基础的 mlflow-dl-inference Docker 镜像构建。这将创建一个新的自包含 Docker 镜像,无需依赖任何外部的 MLflow 跟踪服务器。为此,你需要将微调的深度学习模型从模型跟踪服务器下载到当前本地文件夹,或者你可以通过使用本地文件系统作为 MLflow 跟踪服务器后端,直接在本地运行我们的 MLproject 流水线。按照 README.md 文件中的 部署到 AWS SageMaker 部分,重现本地的 MLflow 运行,准备微调模型和本地文件夹中的 inference-pipeline-model。为了学习目的,我们在 GitHub 仓库的 chapter08 文件夹中提供了两个示例 mlruns 工件和 huggingface 缓存文件夹 (github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter08),以便我们可以立即使用这些现有的工件开始构建新的 Docker 镜像。
要构建一个新的 Docker 镜像,我们需要创建一个如下所示的 Dockerfile:
FROM mlflow-dl-inference
ADD mlruns/1/meta.yaml  /opt/mlflow/mlruns/1/meta.yaml
ADD mlruns/1/d01fc81e11e842f5b9556ae04136c0d3/ /opt/mlflow/mlruns/1/d01fc81e11e842f5b9556ae04136c0d3/
ADD tmp/opt/mlflow/hf/cache/dl_model_chapter08/csv/ /opt/mlflow/tmp/opt/mlflow/hf/cache/dl_model_chapter08/csv/
第一行表示它从现有的mlflow-dl-inference Docker 镜像开始,接下来的三行ADD将会复制一个meta.yaml文件和两个文件夹到 Docker 镜像中的相应位置。请注意,如果您已经按照README文件中的步骤生成了自己的运行实例,则无需添加第三行。需要注意的是,默认情况下,当 Docker 容器启动时,它会自动进入/opt/mlflow/工作目录,因此所有内容都需要复制到这个文件夹中以便于访问。此外,请注意/opt/mlflow目录需要超级用户权限,因此您需要准备好输入本地机器的管理员密码(通常,在您的个人笔记本电脑上,密码就是您自己的密码)。
将私有构建的 Python 包复制到 Docker 镜像中
还可以将私有构建的 Python 包复制到 Docker 镜像中,这样我们就可以在conda.yaml文件中直接引用它们,而无需走出容器。举例来说,我们可以将一个私有的 Python wheel 包cool-dl-package-1.0.py3-none-any.whl复制到/usr/private-wheels/cool-dl-package/cool-dl-package-1.0-py3-none-any.whl Docker 文件夹中,然后我们可以在conda.yaml文件中指向这个路径。这使得 MLflow 模型工件能够成功加载这些本地可访问的 Python 包。在当前的示例中,我们没有使用这种方法,因为我们没有使用任何私有构建的 Python 包。如果您有兴趣探索这个方法,未来参考时会有用。
现在,您可以运行以下命令,在chapter08文件夹中构建一个新的 Docker 镜像:
docker build . -t mlflow-dl-inference-w-finetuned-model
这将基于mlflow-dl-inference构建一个新的 Docker 镜像mlflow-dl-inference-w-finetuned-model。您应该能看到以下输出(为了简洁起见,仅展示第一行和最后几行):
[+] Building 0.2s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                                                      0.0s
…………
=> => naming to docker.io/library/mlflow-dl-inference-w-finetuned-model
现在,您已经有了一个名为mlflow-dl-inference-w-finetuned-model的新 Docker 镜像,其中包含微调后的模型。现在,我们准备使用这个新的、兼容 SageMaker 的 Docker 镜像来部署我们的推理管道模型。
步骤 3:使用新构建的 SageMaker Docker 镜像测试本地部署
在我们将部署到云端之前,让我们先使用这个新的 SageMaker Docker 镜像在本地进行部署测试。MLflow 提供了一种方便的方式来使用以下命令在本地进行测试:
mlflow sagemaker run-local -m runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model -p 5555 -i mlflow-dl-inference-w-finetuned-model
这个命令将会在本地启动mlflow-dl-inference-w-finetuned-model Docker 容器,并将运行 ID 为dc5f670efa1a4eac95683633ffcfdd79的推理管道模型部署到该容器中。
修复潜在的 Docker 错误
请注意,您可能会遇到一个 Docker 错误,提示路径/opt/mlflow/mlruns/1/dc5f670efa1a4eac95683633ffcfdd79/artifacts/inference_pipeline_model 未从主机共享,Docker 无法识别该路径。您可以通过配置共享路径,进入Docker | Preferences... | Resources | File Sharing 来解决此 Docker 错误。
我们已经在 GitHub 仓库中提供了这个推理管道模型,所以当你在本地环境中检出仓库时,应该可以开箱即用。Web 服务的端口是5555。命令运行后,你会看到很多屏幕输出,最终你应该能看到以下内容:
[2022-03-18 01:47:20 +0000] [552] [INFO] Starting gunicorn 20.1.0
[2022-03-18 01:47:20 +0000] [552] [INFO] Listening at: http://127.0.0.1:8000 (552)
[2022-03-18 01:47:20 +0000] [552] [INFO] Using worker: gevent
[2022-03-18 01:47:20 +0000] [565] [INFO] Booting worker with pid: 565
[2022-03-18 01:47:20 +0000] [566] [INFO] Booting worker with pid: 566
[2022-03-18 01:47:20 +0000] [567] [INFO] Booting worker with pid: 567
[2022-03-18 01:47:20 +0000] [568] [INFO] Booting worker with pid: 568
[2022-03-18 01:47:20 +0000] [569] [INFO] Booting worker with pid: 569
[2022-03-18 01:47:20 +0000] [570] [INFO] Booting worker with pid: 570
这意味着服务已经启动并正在运行。你可能会看到一些关于 PyTorch 版本不兼容的警告,但这些可以安全地忽略。一旦服务启动并运行,你就可以在另一个终端窗口中通过发出curl网页请求来进行测试,像我们之前尝试过的那样:
curl http://127.0.0.1:5555/invocations -H 'Content-Type: application/json' -d '{
    "columns": ["text"],
    "data": [["This is the best movie we saw."], ["What a movie!"]]
}'
请注意,本地主机的端口号是5555。然后,你应该能看到如下响应:
[{"text": "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}, {"text": "{\"response\": {\"prediction_label\": [\"negative\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}]
你可能会想知道这和之前章节的本地推理模型 Web 服务有什么不同。区别在于,这一次我们使用的是 SageMaker 容器,而之前只是没有 Docker 容器的本地 Web 服务。在本地测试 SageMaker 容器非常重要,以避免浪费时间和金钱将失败的模型服务部署到云端。
接下来,我们准备将这个容器部署到 AWS SageMaker。
第 4 步:将 SageMaker Docker 镜像推送到 AWS Elastic Container Registry
现在,你可以将新构建的mlflow-dl-inference-w-finetuned-model Docker 镜像推送到 AWS Elastic Container Registry (ECR),使用以下命令。确保你的 AWS 访问令牌和访问 ID 已正确设置(使用真实的,而不是本地开发的)。一旦你拥有访问密钥 ID 和令牌,运行以下命令以设置对真实 AWS 的访问:
aws configure
执行命令后回答所有问题,你就准备好继续了。现在,你可以运行以下命令将mlflow-dl-inference-w-finetuned-model Docker 镜像推送到 AWS ECR:
mlflow sagemaker build-and-push-container --no-build --push -c mlflow-dl-inference-w-finetuned-model
确保在命令中不包含--no-build选项来构建新的镜像,因为我们只需要推送镜像,而不是构建新的镜像。你将看到以下输出,显示镜像正在被推送到 ECR。请注意,在以下输出中,AWS 账户被xxxxx掩盖。你将看到你的账户编号出现在输出中。确保你拥有写入 AWS ECR 存储的权限:
2022/03/18 17:36:05 INFO mlflow.sagemaker: Pushing image to ECR
2022/03/18 17:36:06 INFO mlflow.sagemaker: Pushing docker image mlflow-dl-inference-w-finetuned-model to xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model:1.23.1
Created new ECR repository: mlflow-dl-inference-w-finetuned-model
2022/03/18 17:36:06 INFO mlflow.sagemaker: Executing: aws ecr get-login-password | docker login  --username AWS  --password-stdin xxxxx.dkr.ecr.us-west-2.amazonaws.com;
docker tag mlflow-dl-inference-w-finetuned-model xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model:1.23.1;
docker push xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model:1.23.1
Login Succeeded
The push refers to repository [xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model]
447db5970ca5: Pushed
9d6787a516e7: Pushed
1.23.1: digest: sha256:f49f85741bc2b82388e85c79f6621f4 d7834e19bdf178b70c1a6c78c572b4d10 size: 3271
完成后,如果你访问 AWS 网站(例如,如果你使用的是us-west-2区域的数据中心,网址是us-west-2.console.aws.amazon.com/ecr/repositories?region=us-west-2),你应该能在 ECR 中找到你新推送的镜像,并且会看到一个名为mlflow-dl-inference-w-finetuned-model的文件夹。然后,你会在该文件夹中找到以下镜像(图 8.4):

图 8.4 – 带有 mlflow-dl-inference-w-finetuned-model 镜像标签 1.23.1 的 AWS ECR 存储库
请注意镜像标签号Copy URI选项。它将如下所示(AWS 账户号被屏蔽为xxxxx):
xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model:1.23.1
您将在下一步中需要此镜像 URI 来部署到 SageMaker。现在让我们将模型部署到 SageMaker,创建一个推理端点。
第 5 步:将推理管道模型部署到 SageMaker 以创建 SageMaker 端点
现在,是时候使用我们刚刚推送到 AWS ECR 注册表的镜像 URI 将推理管道模型部署到 SageMaker 了。我们在 GitHub 仓库的chapter08文件夹中包含了sagemaker/deploy_to_sagemaker.py代码。您需要使用正确的 AWS 角色进行部署。您可以在您的账户中创建一个新的AWSSageMakerExecutionRole角色,并将两个权限策略AmazonS3FullAccess和AmazonSageMakerFullAccess分配给该角色。在实际场景中,您可能希望将权限收紧到更受限的策略,但为了学习目的,这种设置是可以的。下图显示了创建角色后的屏幕:

图 8.5 – 创建一个可以在 SageMaker 中用于部署的角色
您还需要为 SageMaker 创建一个 S3 存储桶,以便 SageMaker 可以上传模型工件并将其部署到 SageMaker。在我们的示例中,我们创建了一个名为dl-inference-deployment的存储桶。当我们执行部署脚本时,如下所示,待部署的模型首先会上传到dl-inference-deployment存储桶中,然后再部署到 SageMaker。我们已经在chapter08/sagemaker/deploy_to_sagemaker.py GitHub 仓库中提供了完整的部署脚本,您可以下载并按如下方式执行(提醒一下,在运行此脚本之前,请确保将环境变量MLFLOW_TRACKING_URI重置为空,如export MLFLOW_TRACKING_URI=):
sudo python sagemaker/deploy_to_sagemaker.py
此脚本执行以下两个任务:
- 
将本地
mlruns文件夹下的内容复制到本地的/opt/mlflow文件夹中,以便 SageMaker 部署代码能够识别inference-pipeline-model并进行上传。由于/opt路径通常是受限的,因此我们使用sudo(超级用户权限)来执行此复制操作。此操作将提示您在笔记本电脑上输入用户密码。 - 
使用
mlflow.sagemaker.deployAPI 来创建一个新的 SageMaker 端点,dl-sentiment-model。 
代码片段如下:
mlflow.sagemaker.deploy(
    mode='create',
    app_name=endpoint_name,
    model_uri=model_uri,
    image_url=image_uri,
    execution_role_arn=role,
    instance_type='ml.m5.xlarge',
    bucket = bucket_for_sagemaker_deployment,
    instance_count=1,
    region_name=region
)
这些参数需要一些解释,以便我们能完全理解所需的所有准备工作:
- 
model_uri:这是推理管道模型的 URI。在我们的示例中,它是runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model。 - 
image_url:这是我们上传到 AWS ECR 的 Docker 镜像。在我们的示例中,它是xxxxx.dkr.ecr.us-west-2.amazonaws.com/mlflow-dl-inference-w-finetuned-model:1.23.1。请注意,您需要将被屏蔽的 AWS 账户号xxxxx替换为您的实际账户号。 - 
execution_role_arn:这是我们创建的角色,允许 SageMaker 进行部署。在我们的示例中,它是arn:aws:iam::565251169546:role/AWSSageMakerExecutionRole。再次提醒,你需要将xxxxx替换为你的实际 AWS 账户号码。 - 
bucket:这是我们创建的 S3 桶,允许 SageMaker 上传模型并进行实际部署。在我们的示例中,它是dl-inference-deployment。 
其余的参数不言自明。
执行部署脚本后,你将看到如下输出(其中xxxxx是隐藏的 AWS 账户号码):
2022/03/18 19:30:47 INFO mlflow.sagemaker: Using the python_function flavor for deployment!
2022/03/18 19:30:47 INFO mlflow.sagemaker: tag response: {'ResponseMetadata': {'RequestId': 'QMAQRCTJT36TXD2H', 'HostId': 'DNG57U3DJrhLcsBxa39zsjulUH9VB56FmGkxAiMYN+2fhc/rRukWe8P3qmBmvRYbMj0sW3B2iGg=', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amz-id-2': 'DNG57U3DJrhLcsBxa39zsjulUH9VB56FmGkxAiMYN+2fhc/rRukWe8P3qmBmvRYbMj0sW3B2iGg=', 'x-amz-request-id': 'QMAQRCTJT36TXD2H', 'date': 'Sat, 19 Mar 2022 02:30:48 GMT', 'server': 'AmazonS3', 'content-length': '0'}, 'RetryAttempts': 0}}
2022/03/18 19:30:47 INFO mlflow.sagemaker: Creating new endpoint with name: dl-sentiment-model ...
2022/03/18 19:30:47 INFO mlflow.sagemaker: Created model with arn: arn:aws:sagemaker:us-west-2:xxxxx:model/dl-sentiment-model-model-qbca2radrxitkujn3ezubq
2022/03/18 19:30:47 INFO mlflow.sagemaker: Created endpoint configuration with arn: arn:aws:sagemaker:us-west-2:xxxxx:endpoint-config/dl-sentiment-model-config-r9ax3wlhrfisxkacyycj8a
2022/03/18 19:30:48 INFO mlflow.sagemaker: Created endpoint with arn: arn:aws:sagemaker:us-west-2:xxxxx:endpoint/dl-sentiment-model
2022/03/18 19:30:48 INFO mlflow.sagemaker: Waiting for the deployment operation to complete...
2022/03/18 19:30:48 INFO mlflow.sagemaker: Waiting for endpoint to reach the "InService" state. Current endpoint status: "Creating"
这可能需要几分钟时间(有时超过 10 分钟)。你可能会看到一些关于 PyTorch 版本兼容性的警告信息,就像在进行本地 SageMaker 部署测试时看到的那样。你也可以直接访问 SageMaker 网站,在那里你将看到端点的状态从Creating开始,最终变成绿色的InService状态,如下所示:

图 8.6 – AWS SageMaker dl-sentiment-model 端点 InService 状态
如果你看到InService状态,那么恭喜你!你已成功将一个 DL 推理管道模型部署到 SageMaker,现在你可以将它用于生产流量!
现在服务状态为 InService,你可以在下一步使用命令行进行查询。
步骤 6:查询 SageMaker 端点进行在线推理
要查询 SageMaker 端点,你可以使用以下命令行:
aws sagemaker-runtime invoke-endpoint --endpoint-name 'dl-sentiment-model' --content-type 'application/json; format=pandas-split' --body '{"columns":["text"], "data": [["This is the best movie we saw."], ["What a movie!"]]}' response.json
然后你将看到如下输出:
{
    "ContentType": "application/json",
    "InvokedProductionVariant": "dl-sentiment-model-model-qbca2radrxitkujn3ezubq"
}
实际的预测结果存储在本地的response.json文件中,你可以通过运行以下命令查看响应内容:
cat response.json
这将显示如下内容:
[{"text": "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}, {"text": "{\"response\": {\"prediction_label\": [\"negative\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}]
这是我们推理管道模型的预期响应模式!你也可以通过 Python 代码查询 SageMaker 推理端点,我们已在 GitHub 仓库中的chapter08/sagemaker/query_sagemaker_endpoint.py文件中提供了该代码。核心代码片段使用了SageMakerRuntime客户端的invoke_endpoint进行查询,如下所示:
client = boto3.client('sagemaker-runtime') 
response = client.invoke_endpoint(
    EndpointName=app_name, 
    ContentType=content_type,
    Accept=accept,
    Body=payload
    )
invoke_endpoint的参数需要一些解释:
- 
EndpointName:这是推理端点的名称。在我们的示例中,它是dl-inference-model。 - 
ContentType:这是请求体中输入数据的 MIME 类型。在我们的示例中,我们使用application/json; format=pandas-split。 - 
Accept:这是推理响应体中期望的 MIME 类型。在我们的示例中,我们期望返回text/plain字符串类型。 - 
Body:这是我们希望使用 DL 模型推理服务预测情感的实际文本。在我们的示例中,它是{"columns": ["text"],"data": [["This is the best movie we saw."], ["What a movie!"]]}。 
完整的代码已提供在 GitHub 仓库中,你可以在命令行中按照以下方式运行它:
python sagemaker/query_sagemaker_endpoint.py
你将在终端屏幕上看到如下输出:
Application status is: InService
[{"text": "{\"response\": {\"prediction_label\": [\"positive\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}, {"text": "{\"response\": {\"prediction_label\": [\"negative\"]}, \"metadata\": {\"language_detected\": \"en\"}, \"model_metadata\": {\"finetuned_model_uri\": \"runs:/d01fc81e11e842f5b9556ae04136c0d3/model\", \"inference_pipeline_model_uri\": \"runs:/dc5f670efa1a4eac95683633ffcfdd79/inference_pipeline_model\"}}"}]
这就是我们期望从推理管道模型的响应中得到的结果!如果你已经按照本章内容完成学习,恭喜你成功将推理管道模型部署到 AWS SageMaker 上的远程云主机中!完成本章内容后,请务必删除端点,以避免不必要的费用。
让我们总结一下我们在本章中学到的内容。
总结
在本章中,我们学习了不同的方法来部署一个 MLflow 推理管道模型,适用于批量推理和在线实时推理。我们从对不同模型服务场景(批处理、流式、设备端)的简要调查开始,并研究了三种不同类别的 MLflow 模型部署工具(MLflow 内置部署工具、MLflow 部署插件,以及可以与 MLflow 推理模型配合使用的通用模型推理服务框架)。然后,我们介绍了几种本地部署场景,使用 PySpark UDF 函数进行批量推理,并使用 MLflow 本地部署进行 Web 服务部署。接着,我们学习了如何结合使用 Ray Serve 和mlflow-ray-serve插件,将 MLflow Python 推理管道模型部署到本地 Ray 集群。这为我们打开了部署到任何云平台的大门,比如 AWS、Azure ML 或 GCP,只要我们能在云中设置 Ray 集群。最后,我们提供了一个完整的端到端指南,讲解如何部署到 AWS SageMaker,重点介绍了 BYOM(Bring Your Own Model)常见场景,在这个场景中,我们有一个在 AWS SageMaker 外部训练的推理管道模型,现在需要将其部署到 AWS SageMaker 以提供托管服务。我们的逐步指南应该能帮助你信心满满地将 MLflow 推理管道模型部署到真实生产环境中。
请注意,部署深度学习推理管道模型的领域仍在发展,我们刚刚学到了一些基础技能。我们鼓励你在进一步阅读部分探索更多高级主题。
现在我们已经知道如何部署和托管深度学习推理管道,接下来我们将在下一章学习如何进行模型可解释性,这对于许多现实场景中可信赖和可解释的模型预测结果至关重要。
进一步阅读
- 
TinyML 简介:
towardsdatascience.com/an-introduction-to-tinyml-4617f314aa79 - 
性能优化与 MLFlow 集成 - Seldon Core 1.10.0 发布:
www.seldon.io/performance-optimizations-and-mlflow-integrations-seldon-core-1-10-0-released/ - 
Ray 与 MLflow:将分布式机器学习应用推向生产环境:
medium.com/distributed-computing-with-ray/ray-mlflow-taking-distributed-machine-learning-applications-to-production-103f5505cb88 - 
使用 MLflow 和 Amazon SageMaker 管理你的机器学习生命周期:
aws.amazon.com/blogs/machine-learning/managing-your-machine-learning-lifecycle-with-mlflow-and-amazon-sagemaker/ - 
在云端使用 AWS SageMaker 部署本地训练的 ML 模型:
medium.com/geekculture/84af8989d065 - 
2022 年 PyTorch 与 TensorFlow 对比:
www.assemblyai.com/blog/pytorch-vs-tensorflow-in-2022/ - 
尝试 Databricks:免费试用或社区版:
docs.databricks.com/getting-started/try-databricks.html#free-trial-or-community-edition - 
使用 MLflow 和 Amazon SageMaker Pipelines 进行 MLOps:
towardsdatascience.com/mlops-with-mlflow-and-amazon-sagemaker-pipelines-33e13d43f238 - 
PyTorch JIT 与 TorchScript:
towardsdatascience.com/pytorch-jit-and-torchscript-c2a77bac0fff - 
ML 模型服务最佳工具:
neptune.ai/blog/ml-model-serving-best-tools - 
将机器学习模型部署到生产环境 — 推理服务架构模式:
medium.com/data-for-ai/deploying-machine-learning-models-to-production-inference-service-architecture-patterns-bc8051f70080 - 
如何将大规模深度学习模型部署到生产环境:
towardsdatascience.com/how-to-deploy-large-size-deep-learning-models-into-production-66b851d17f33 - 
使用 Mlflow 在 Kubernetes 上按需服务 ML 模型:
medium.com/artefact-engineering-and-data-science/serving-ml-models-at-scale-using-mlflow-on-kubernetes-7a85c28d38e - 
当 PyTorch 遇上 MLflow:
mlops.community/when-pytorch-meets-mlflow/ - 
将模型部署到 Azure Kubernetes 服务集群:
docs.microsoft.com/en-us/azure/machine-learning/how-to-deploy-azure-kubernetes-service?tabs=python - 
ONNX 和 Azure 机器学习:创建并加速 ML 模型:
docs.microsoft.com/en-us/azure/machine-learning/concept-onnx 
第五部分 – 大规模深度学习模型可解释性
在本节中,我们将学习可解释性的基础概念以及可解释人工智能(XAI),并了解如何使用 MLflow 实现深度学习(DL)可解释性。我们将从概述可解释性的八个维度开始,然后学习如何使用SHapley 加法解释(SHAP)和Transformer 解释来执行自然语言处理(NLP)管道的可解释性。此外,我们还将学习并分析当前 MLflow 与 SHAP 的集成,以理解其权衡并避免潜在的实现问题。接下来,我们将展示如何使用 MLflow 的日志 API 实现 SHAP。最后,我们将学习如何将 SHAP 解释器作为 MLflow Python 模型进行实现,并将其加载为 Spark UDF 进行批量解释,或作为在线解释即服务(EaaS)的 Web 服务。
本节包括以下章节:
- 
第九章,深度学习可解释性的基础
 - 
第十章,使用 MLflow 实现深度学习可解释性
 
第九章:第九章:深度学习可解释性基础
可解释性是为自动化系统提供选择性人类可理解的决策解释。在本书的背景下,在深度学习(DL)开发的整个生命周期中,应将可解释性视为与数据、代码和模型这三大支柱一样重要的产物。这是因为不同的利益相关者和监管者、模型开发者以及模型输出的最终消费者可能对了解数据如何使用以及模型为何产生特定预测或分类有不同的需求。如果没有这样的理解,将难以赢得模型输出消费者的信任,或者在模型输出结果漂移时诊断可能出现的问题。这也意味着可解释性工具不仅应用于解释在生产中部署的模型的预测结果或离线实验期间的情况,还应用于理解离线模型训练中使用的数据特性和在线模型操作中遇到的数据集之间的差异。
此外,在许多高度受监管的行业,如自动驾驶、医疗诊断、银行业和金融业,还有法律法规要求任何个体都有权利获取算法输出的解释的要求。最后,最近的一项调查显示,超过 82%的 CEO 认为基于 AI 的决策必须是可解释的,以便作为企业加速其投资于开发和部署基于 AI 的倡议的信任基础(cloud.google.com/blog/topics/developers-practitioners/bigquery-explainable-ai-now-ga-help-you-interpret-your-machine-learning-models)。因此,学习可解释性的基础知识和相关工具是很重要的,这样我们就知道在何时为何种观众使用何种工具来提供相关、准确和一致的解释。
通过本章结束时,您将能够自信地知道什么是良好的解释,以及存在哪些工具用于不同的可解释性目的,并且将获得使用两个解释性工具箱来解释深度学习情感分类模型的实际经验。
在本章中,我们将涵盖以下主要话题:
- 
理解解释性的类别和受众
 - 
探索 SHAP 可解释性工具包
 - 
探索 Transformers Interpret 工具箱
 
技术要求
完成本章学习需要满足以下要求:
- 
SHAP Python 库:
github.com/slundberg/shap - 
Transformers 解释性 Python 库:
github.com/cdpierse/transformers-interpret - 
Captum Python 库:
github.com/pytorch/captum - 
本章 GitHub 仓库中的代码:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter09 
理解可解释性的类别和受众
正如本章开头所述,深度学习系统的可解释性变得越来越重要,有时在一些高度监管的行业中,如金融、法律、政府和医疗领域,甚至是强制性的。一个因缺乏机器学习可解释性而导致的示例诉讼是B2C2 诉 Quoine 案(www.scl.org/articles/12130-explainable-machine-learning-how-can-you-determine-what-a-party-knew-or-intended-when-a-decision-was-made-by-machine-learning),其中自动化的 AI 交易算法错误地以市场价格的 250 倍下单购买比特币。深度学习模型在生产中的成功应用促进了对可解释性领域的积极研究和开发,因为我们需要理解深度学习模型是如何运作的。你可能听说过可解释人工智能(XAI)这个术语,它由美国国防高级研究计划局(DARPA)于 2015 年为其 XAI 计划提出,旨在帮助最终用户更好地理解、信任和有效管理 AI 系统(onlinelibrary.wiley.com/doi/epdf/10.1002/ail2.61)。然而,可解释性的概念早在 1980 年代或更早期的专家系统时代就已出现(wires.onlinelibrary.wiley.com/doi/full/10.1002/widm.1391),而近期对于可解释性话题的关注高潮,只是突显了它的重要性。
那么,什么是解释?事实证明,这仍然是机器学习(ML)、深度学习(DL)和人工智能(AI)领域的一个活跃研究课题。从实际应用的角度来看,解释的精确定义取决于谁在什么时间、出于什么目的,基于机器学习/深度学习/人工智能的生命周期需求进行解释(dl.acm.org/doi/abs/10.1145/3461778.3462131)。因此,可解释性可以被定义为为受众提供适当的、易于理解的解释,阐明模型为什么以及如何给出某些预测结果的能力。这也可能包括数据可解释性的方面,涉及数据是如何通过溯源追踪被使用的,数据的特征是什么,或是数据是否由于意外事件而发生了变化。例如,由于突如其来的新冠疫情,销售和营销邮件发生了变化(www.validity.com/resource-center/disruption-in-email/)。这种数据的变化将意外改变模型预测结果的分布。在解释模型漂移时,我们需要考虑到这种数据的变化。这意味着,解释的复杂性需要根据接收受众进行定制和选择,而不会提供过多的信息。例如,包含许多技术术语的复杂解释,如激活,可能不如使用商业友好的术语进行简单文本总结的效果好。这进一步表明,可解释性也是一个人机界面/交互(HCI)话题。
要全面了解可解释性类别及其对应的受众,我们考虑了图 9.1中展示的八个维度:

图 9.1 – 理解可解释性的八个维度
从图 9.1可以看出,可解释性的复杂性可以从八个维度进行理解。这不一定是一个详尽的分类,而是帮助理解来自 HCI、人工智能/机器学习/深度学习完整生命周期以及不同技术方法的不同视角的指南。在接下来的讨论中,我们将重点介绍与深度学习应用最相关的维度及其相互关系,因为本章的重点是深度学习的可解释性。
受众:谁需要知道
正如最近一项研究指出的(dl.acm.org/doi/abs/10.1145/3461778.3462131),了解谁在什么阶段需要知道什么类型的解释是非常重要的,这将在整个 AI 项目生命周期中产生影响。这也将影响解释的输出格式。另一项早期研究(arxiv.org/pdf/1702.08608.pdf)也指出,根据是否有领域专家参与实际应用任务(例如,诊断癌症的医学专家),验证一个解释的成本可能也很高,因为这需要一个实际的人类在实际工作环境中参与。
对于当前的实际深度学习项目,我们需要根据目标受众(如数据科学家、机器学习工程师、业务利益相关者、用户体验(UX)设计师或最终用户)定制解释方法和展示方式,因为没有一种通用的方法适用于所有情况。
阶段:在深度学习生命周期中何时提供解释
阶段通常指的是在模型开发生命周期中可以提供解释的时机。对于像决策树这样的模型,由于它是一个白盒模型,我们说我们可以提供事前可解释性。然而,目前大多数深度学习(DL)模型通常被视为黑盒模型,尽管自解释的深度学习模型正在逐渐开发,且具有事前可解释性(arxiv.org/abs/2108.11761)。因此,对于当前的实际深度学习应用,需要事后可解释性。此外,当模型开发阶段处于训练、验证或生产时,解释的范围可以是全局的、群体的或局部的,即使使用相同的事后可解释性工具(towardsdatascience.com/a-look-into-global-cohort-and-local-model-explainability-973bd449969f)。
范围:哪些预测需要解释
范围指的是我们是否能够为所有预测、部分预测或仅仅一个特定预测提供解释,即使我们为黑盒深度学习模型使用相同的事后工具。最常见的全局可解释性是描述特征重要性,并允许用户了解哪些特征对整体模型性能最有影响。局部可解释性则是关于特定预测实例的特征归因。特征归因与特征重要性的区别在于,特征归因不仅量化了特征影响的排名和大小,还量化了影响的方向(例如,一个特征是正向还是负向地影响了预测)。
许多后期工具对于深度学习模型在局部可解释性方面表现优秀。群体可解释性有助于识别一些特定群体(如年龄或种族群体)可能存在的模型偏差。对于深度学习模型,如果我们想要得到全局解释,通常需要使用替代模型(如决策树模型)来模拟深度学习模型的行为(towardsdatascience.com/explainable-ai-xai-methods-part-5-global-surrogate-models-9c228d27e13a)。然而,这种方法并不总是奏效,因为很难判断替代模型是否足够准确地逼近原始黑箱模型的预测。因此,在实际操作中,深度学习模型通常使用局部可解释性工具,如SHapley 加法解释(SHAP),我们将在方法维度中对此进行解释。
输入数据格式:什么是输入数据的格式
输入数据格式指的是在开发和使用模型时,我们处理的输入数据类型。一个简单的模型可能只关注单一类型的输入数据格式,例如文本,而许多复杂的模型可能需要结合结构化的表格数据和非结构化数据,如图像或文本。此外,还需要单独理解输入数据的潜在偏差(在模型训练和验证期间)或漂移(在生产环境中)。因此,这是一个相当复杂的话题。数据解释也可以用于监控生产环境中的数据异常和漂移。这适用于所有类型的机器学习/深度学习模型。
输出数据格式:什么是输出解释的格式
输出解释格式指的是我们如何向目标受众呈现解释。通常,图像解释可能是一个条形图,显示特征的重要性及其得分,或者是一个显著性图,突出显示每个图像中某一特定类别的空间支持,适用于与图像相关的机器学习问题。对于文本输出,它可能是一个英语句子,解释为何某个信用申请因几个可理解的因素而被拒绝。自然语言处理(NLP)模型的可解释性也可以通过交互式探索实现,使用显著性图、注意力机制以及其他丰富的可视化手段(参见 Google 的语言可解释性工具(LIT)示例:ai.googleblog.com/2020/11/the-language-interpretability-tool-lit.html)。由于没有一劳永逸的解释方法适用于这些复杂的输出格式,因此,满足受众对解释的需求、经验和期望是至关重要的。
问题类型:什么是机器学习问题类型
问题类型广泛地指代所有种类的机器学习/人工智能问题,但就实际应用而言,目前商业上成功的主要问题大多集中在分类、回归和聚类。强化学习和推荐系统在行业中的应用也越来越成功。深度学习模型现在常常应用于所有这些类型的问题,或者至少正在被评估作为潜在的候选模型。
目标类型:解释的动机或目标是什么
目标类型指的是在人工智能/机器学习项目中使用可解释性的动机。人们认为,可解释性的首要目标是通过提供足够的理解来获得信任,揭示 AI 系统行为中的脆弱性、偏差和缺陷。另一个动机是从输入和输出预测中推断因果关系。其他目标包括通过更好地理解 AI/ML 系统的内部工作原理来提高模型的准确性,以及在可能涉及严重后果时,通过透明的解释来为模型行为和决策提供正当理由。甚至可能通过解释揭示基于解释的未知见解和规则(www.tandfonline.com/doi/full/10.1080/10580530.2020.1849465)。总的来说,打破黑箱非常重要,以便在真实的生产系统中使用 AI/ML 模型和系统时,能够以信心进行使用。
方法类型:使用的具体后验解释方法是什么
方法类型(后验分析)指的是与深度学习模型密切相关的后验分析方法。后验分析方法主要分为两类:基于扰动的方法和基于梯度的方法。最近的研究开始尝试将这两种方法统一起来,尽管这种统一方法尚未广泛应用于实际中(teamcore.seas.harvard.edu/publications/towards-unification-and-robustness-perturbation-and-gradient-based)。以下是对这两种方法的简要讨论:
- 
基于扰动的方法利用对单个实例的扰动来构建可解释的局部近似模型,使用线性模型来解释预测结果。最受欢迎的基于扰动的方法包括局部可解释模型无关解释方法(LIME),(
arxiv.org/pdf/1602.04938.pdf),SHAP,以及 LIME 和 SHAP 的变种,如 BayesLIME、BayesSHAP、TreeSHAP 等 (towardsdatascience.com/what-are-the-prevailing-explainability-methods-3bc1a44f94df)。LIME 可以用于表格数据、图像和文本输入数据,并且是模型无关的。这意味着,LIME 可以用于任何类型的分类器(无论是基于树的模型还是深度学习模型),不受所使用算法的限制。SHAP 使用来自合作博弈论的原理,确定不同特征对预测的贡献,从而量化每个特征的影响。SHAP 生成所谓的 Shapley 值,它是所有可能的联盟或特征组合的边际贡献的平均值。它适用于多种类型的模型,包括深度学习模型,尽管对于基于树的模型,如 XGBoost 或 LightGBM,计算时间通常会更快 (github.com/slundberg/shap)。 - 
基于梯度的方法,如 SmoothGrad (
arxiv.org/abs/1706.03825) 和集成梯度 (towardsdatascience.com/understanding-deep-learning-models-with-integrated-gradients-24ddce643dbf),通过计算相对于输入维度的梯度来解释模型预测。它们可以应用于图像和文本输入数据,尽管有时文本输入可能会受到操控或对抗性攻击的影响 (towardsdatascience.com/limitations-of-integrated-gradients-for-feature-attribution-ca2a50e7d269),这将不希望地改变特征重要性。 
请注意,还有一些其他类型的方法,比如反事实方法 (christophm.github.io/interpretable-ml-book/counterfactual.html) 或基于原型的方法 (christophm.github.io/interpretable-ml-book/proto.html),这些方法我们在本书中不会涉及。
在讨论了可解释性的多个维度后,重要的是要知道 XAI(可解释人工智能)仍然是一个新兴领域(fairlyaccountable.org/aaai-2021-tutorial/doc/AAAI_slides_final.pdf),有时甚至很难在应用到同一数据集或模型时找到不同可解释性方法之间的一致性(请参见关于可解释机器学习中的分歧问题的最新研究,站在从业者的角度:arxiv.org/abs/2202.01602)。最终,确实需要进行一些实验,才能找出哪些可解释性方法提供了经过人工验证的解释,并满足现实世界中某个特定预测任务的需求。
在本章的接下来的两个部分中,我们将重点介绍一些流行的、正在兴起的工具包,并通过一些实践实验来学习如何进行可解释性分析。
探索 SHAP 可解释性工具箱
为了我们的学习目的,让我们在实验一些示例时回顾一些流行的可解释性工具箱。根据 GitHub 的星标数量(截至 2022 年 4 月为 16,000 个,github.com/slundberg/shap),SHAP 是最广泛使用和集成的开源模型可解释性工具箱。它也是与 MLflow 集成的基础解释工具。在这里,我们将进行一个小实验,亲身体验这种工具是如何工作的。让我们使用一个情感分析 NLP 模型,探索 SHAP 如何用于解释模型的行为:
- 
在从 GitHub 检查本章的代码后,在本地环境中设置虚拟环境。运行以下命令将创建一个名为
dl-explain的新虚拟环境:conda env create -f conda.yaml 
这将安装 SHAP 及其相关依赖项,例如matplotlib,并将它们添加到该虚拟环境中。创建此虚拟环境后,通过运行以下命令来激活该虚拟环境:
conda activate dl-explain
现在,我们准备好使用 SHAP 进行实验了。
- 
您可以查看
shap_explain.ipynb笔记本,跟随其中的实验。该笔记本的第一步是导入相关的 Python 库:import transformers import shap from shap.plots import * 
这些导入将允许我们使用 Hugging Face 的 transformers 管道 API,获取一个预训练的 NLP 模型并使用 SHAP 函数。
- 
然后,我们使用 transformers 管道 API 创建
dl_model来进行sentiment_analysis。请注意,这是一个预训练的管道,因此我们可以在不进行额外微调的情况下使用它。该管道中使用的默认转换器模型是distilbert-base-uncased-finetuned-sst-2-english(https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english):dl_model = transformers.pipeline( 'sentiment-analysis', return_all_scores=True) 
这将生成一个准备好预测输入句子情感(正面或负面)的模型。
- 
尝试用两个输入句子测试这个
dl_model,看看输出是否有意义:dl_model( ["What a great movie! ...if you have no taste.", "Not a good movie to spend time on."]) 
这将输出每句话的标签和概率分数,如下所示:
[[{'label': 'NEGATIVE', 'score': 0.00014734962314832956}, {'label': 'POSITIVE', 'score': 0.9998526573181152}], [{'label': 'NEGATIVE', 'score': 0.9997993111610413}, {'label': 'POSITIVE', 'score': 0.00020068213052581996}]]
看起来第一句话被预测为POSITIVE的概率很高,而第二句话则被预测为NEGATIVE的概率很高。现在,如果我们仔细观察第一句话,我们可能会认为模型的预测是错误的,因为句子的后半部分(no taste)带有微妙的负面情绪。因此,我们想知道模型为何做出这样的预测。这就是模型可解释性发挥作用的地方。
- 
现在,让我们使用 SHAP API,
shap.Explainer,获取我们感兴趣的两句话的 Shapley 值:explainer = shap.Explainer(dl_model) shap_values = explainer(["What a great movie! ...if you have no taste.", "Not a good movie to spend time on."]) - 
一旦我们有了
shap_values,就可以使用不同的可视化技术来展示 Shapley 值。第一种方法是使用shap.plot.text来可视化第一句话的 Shapley 值,当预测标签为POSITIVE时:shap.plots.text(shap_values[0, :, "POSITIVE"]) 
这将生成如下图表:

图 9.2 – 句子 1 的 SHAP 可视化,带有正向预测
如图 9.2所示,词语great的 SHAP 值非常大,主导了最终预测的影响,而词语no对最终预测的影响较小。这导致了最终的POSITIVE预测结果。那么,第二句话带有NEGATIVE预测的情况如何呢?运行以下命令将生成一个类似的图表:
shap.plots.text(shap_values[1, :, "NEGATIVE"])
该命令将创建如下图表:

图 9.3 – 句子 2 的 SHAP 可视化,带有负向预测
如图 9.3所示,词语Not对最终预测有很强的影响,而词语good的影响非常小,导致最终预测为NEGATIVE情感。这是非常有道理的,对模型行为的解释很到位。
- 
我们还可以使用不同的图表来可视化
shap_values。一种常见的方式是条形图,它显示特征对最终预测的贡献。运行以下命令将为第一句话生成一个图表:bar(shap_values[0, :,'POSITIVE']) 
这将生成如下的条形图:

图 9.4 – 句子 1 的 SHAP 条形图,带有正向预测
从图 9.4中可以看出,该图表按重要性对特征进行了从上到下的排名,其中对最终预测有正面影响的特征绘制在x轴的正侧,而负面贡献则绘制在x轴的负侧。x轴表示每个标记或单词的 SHAP 值,并带有符号(+ 或 -)。这清楚地表明,单词great是一个强正面因素,影响了最终预测,而have no taste有一定的负面影响,但不足以改变最终预测的方向。
类似地,我们可以为第二个句子绘制如下的条形图:
bar(shap_values[1, :,'NEGATIVE'])
这将生成以下的条形图:

图 9.5 – 带有负面预测的第二个句子的 SHAP 条形图
从图 9.5中可以看出,单词Not对最终预测有很强的贡献,而单词good排在第二位。这两个词对最终预测的影响是相反的,但显然,单词Not的影响要强得多,SHAP 值也大得多。
如果你已经跟着这个例子走,并在你的笔记本中看到了 SHAP 图表,那么恭喜你!这意味着你已经成功地运行了 SHAP 可解释性工具,为 NLP 文本情感分析中的深度学习转换器模型提供了解释。
让我们进一步探索另一种流行的可解释性工具,看看它们如何提供不同的解释。
探索 Transformers Interpret 工具箱
如我们在本章的第一节中回顾的那样,有两种主要方法:基于扰动的和基于梯度的事后可解释性工具。SHAP 属于基于扰动的方法家族。现在,让我们来看一个基于梯度的工具箱,名为Transformers Interpret(github.com/cdpierse/transformers-interpret)。这是一个相对较新的工具,但它建立在一个名为Captum(github.com/pytorch/captum)的统一模型可解释性和理解库之上,该库为 PyTorch 提供了统一的 API,可以使用基于扰动或梯度的工具(arxiv.org/abs/2009.07896)。Transformers Interpret 进一步简化了 Captum 的 API,使我们能够快速探索基于梯度的可解释性方法,从而获得实践经验。
首先,请确保您已经根据前一节的描述,设置并激活了dl-explain虚拟环境。然后,我们可以使用相同的 Hugging Face 转换器情感分析模型,探索一些 NLP 情感分类示例。接下来,我们可以执行以下步骤,学习如何使用 Transformers Interpret 进行模型解释。您可能想查看gradient_explain.ipynb笔记本,以跟随说明操作:
- 
按如下方式将相关包导入笔记本:
from transformers import AutoModelForSequenceClassification, AutoTokenizer from transformers_interpret import SequenceClassificationExplainer 
这将使用 Hugging Face 的 transformer 模型和分词器,以及来自transformers_interpret的可解释性功能。
- 
使用与前一节相同的预训练模型创建模型和分词器,该模型是
distilbert-base-uncased-finetuned-sst-2-english模型:model_name = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModelForSequenceClassification.from_pretrained(model_name) tokenizer = AutoTokenizer.from_pretrained(model_name) 
现在我们已经有了模型和分词器,我们可以使用SequenceClassificationExplainer API 创建一个可解释性变量。
- 
创建一个解释器并给出示例句子,以获取来自解释器的
word归因:cls_explainer = SequenceClassificationExplainer(model, tokenizer) word_attributions = cls_explainer("Not a good movie to spend time on.") - 
我们也可以在检查
word归因之前,通过运行以下命令获取预测标签:cls_explainer.predicted_class_name 
这将产生Negative的结果,意味着预测为负面情绪。那么,让我们看看解释器是如何为这个预测提供解释的。
- 我们可以仅显示
word_attributions值,或者我们可以将其可视化。word_attributions的值如下: 

图 9.6 – 使用分层集成梯度的词归因值,结果为负面预测
从图 9.6可以看出,使用分层集成梯度方法,这是当前解释器在 Transformers Interpret 库中实现的默认方法,not这个词对最终的预测结果产生了积极的影响,最终的预测是负面情绪。这是有道理的。请注意,其他一些词,如to spend time on,对最终预测也有较强的积极影响。考虑到交叉注意力机制,似乎模型正试图提取not to spend time on作为最终预测的主要归因。请注意,我们还可以像下面这样可视化这些word归因:
cls_explainer.visualize("distilbert_viz.html")
这将产生以下图表:

图 9.7 – 使用分层集成梯度的词归因值,结果为负面预测
从图 9.7可以看出,它突出了not to spend time on这个词对最终负面预测结果的积极影响。
现在我们已经实验了基于扰动和梯度的可解释性方法,我们已成功完成了使用可解释性工具进行事后本地解释的动手探索。
接下来,我们将总结在本章中学到的内容。
总结
在本章中,我们通过八维分类回顾了 AI/ML 中的可解释性。虽然这不一定是一个全面或详尽的概述,但它为我们提供了一个大致的框架,包括谁需要解释、不同阶段和范围的解释、各种输入输出格式的解释、常见的 ML 问题和目标类型,以及最后的不同后验可解释性方法。接着,我们提供了两个具体的练习来探索 SHAP 和 Transformers 可解释工具箱,它们可以为 NLP 文本情感 DL 模型提供扰动和基于梯度的特征归因解释。
这为我们使用 DL 模型的可解释性工具奠定了坚实的基础。然而,考虑到 XAI 的积极发展,这仅仅是将 XAI 应用于 DL 模型的开始。其他可解释性工具箱,如 TruLens(github.com/truera/trulens)、Alibi(github.com/SeldonIO/alibi)、微软的负责任 AI 工具箱(github.com/microsoft/responsible-ai-toolbox)和 IBM 的 AI 可解释性 360 工具包(github.com/Trusted-AI/AIX360)都在积极开发中,值得进一步研究和学习。深入阅读部分还提供了额外的链接,帮助你继续学习这一主题。
现在我们已经了解了可解释性的基础知识,在下一章中,我们将学习如何在 MLflow 框架中实现可解释性,从而为在 MLflow 框架内提供统一的解释方式。
深入阅读
- 
可解释 AI 的新前沿:
towardsdatascience.com/new-frontiers-in-explainable-ai-af43bba18348 - 
走向严谨的可解释机器学习科学:
arxiv.org/pdf/1702.08608.pdf - 
值得信赖的 AI 工具包方法:
opendatascience.com/the-toolkit-approach-to-trustworthy-ai/ - 
通过概念学习 Ante-hoc 可解释模型的框架:
arxiv.org/abs/2108.11761 - 
解密 ML 模型的后验可解释性:
spectra.mathpix.com/article/2021.09.00007/demystify-post-hoc-explainability - 
全球、群体和局部模型可解释性探讨:
towardsdatascience.com/a-look-into-global-cohort-and-local-model-explainability-973bd449969f - 
当前主流的可解释性方法是什么?
towardsdatascience.com/what-are-the-prevailing-explainability-methods-3bc1a44f94df - 
可解释人工智能:目标、利益相关者与未来研究机会:
www.tandfonline.com/doi/full/10.1080/10580530.2020.1849465 
第十章:第十章:使用 MLflow 实现深度学习可解释性
深度学习(DL)可解释性的重要性在前一章中已有充分讨论。为了在实际项目中实现深度学习可解释性,最好像其他模型工件一样,将解释器和解释作为工件记录在 MLflow 服务器中,这样我们就可以轻松跟踪和重现解释。将 SHAP(github.com/slundberg/shap)等深度学习可解释性工具与 MLflow 集成,可以支持不同的实现机制,理解这些集成如何应用于我们的深度学习可解释性场景是非常重要的。本章将探讨通过使用不同的 MLflow 功能将 SHAP 解释集成到 MLflow 中的几种方法。由于可解释性工具和深度学习模型都在快速发展,我们还将重点介绍使用 MLflow 实现深度学习可解释性时的当前限制和解决方法。到本章结束时,你将能够使用 MLflow API 实现 SHAP 解释和解释器,从而实现可扩展的模型可解释性。
本章将涵盖以下主要内容:
- 
理解当前的 MLflow 可解释性集成
 - 
使用 MLflow 工件日志记录 API 实现 SHAP 解释
 - 
使用 MLflow pyfunc API 实现 SHAP 解释器
 
技术要求
完成本章所需的以下要求:
- 
完整的 MLflow 本地服务器:这与我们自第三章以来一直使用的服务器相同,跟踪模型、参数和指标。
 - 
SHAP Python 库:
github.com/slundberg/shap。 - 
Spark 3.2.1 和 PySpark 3.2.1:请参阅本章 GitHub 仓库中的
README.md文件了解详细信息。 - 
本章 GitHub 仓库中的代码:
github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/tree/main/chapter10。 
理解当前的 MLflow 可解释性集成
MLflow 支持多种可解释性集成方式。在实现可解释性时,我们提到两种类型的工件:解释器和解释:
- 
解释器是一个可解释性模型,常见的模型是 SHAP 模型,其中可能有多种 SHAP 解释器,如 TreeExplainer、KernelExplainer 和 PartitionExplainer(
shap.readthedocs.io/en/latest/generated/shap.explainers.Partition.html)。为了提高计算效率,我们通常选择 PartitionExplainer 来处理深度学习模型。 - 
解释是一个产物,它展示了某种形式的输出,可能是文本、数值或图表。解释可以发生在离线训练或测试中,或者发生在在线生产中。因此,如果我们想知道模型为何给出某些预测,我们应该能够提供一个离线评估的解释器或一个在线查询的解释器端点。
 
在这里,我们简要概述了截至 MLflow 版本 1.25.1(pypi.org/project/mlflow/1.25.1/)的当前功能。使用 MLflow 进行可解释性的方式有四种,如下所示:
- 
使用
mlflow.log_artifactAPI(www.mlflow.org/docs/latest/python_api/mlflow.html#mlflow.log_artifact)记录相关的解释产物,如条形图和 Shapley 值数组。这为记录解释提供了最大的灵活性。无论是离线批处理处理,还是在线自动记录某一预测的 SHAP 条形图,都可以使用该功能。需要注意的是,在在线生产场景中为每个预测记录解释是非常昂贵的,因此我们应为按需查询提供一个单独的解释 API。 - 
使用
mlflow.pyfunc.PythonModelAPI(www.mlflow.org/docs/latest/python_api/mlflow.pyfunc.html#mlflow.pyfunc.PythonModel)来创建一个解释器,该解释器可以通过 MLflow 的pyfunc方法进行记录和加载,mlflow.pyfunc.log_model用于记录,mlflow.pyfunc.load_model或mlflow.pyfunc.spark_udf用于加载解释器。这为我们提供了最大灵活性,可以将自定义解释器作为 MLflow 通用的pyfunc模型创建,并且可以用于离线批处理解释或在线提供解释即服务(EaaS)。 - 
使用
mlflow.shapAPI(www.mlflow.org/docs/latest/python_api/mlflow.shap.html)。该 API 存在一些限制。例如,mlflow.shap.log_explainer方法仅支持 scikit-learn 和 PyTorch 模型。mlflow.shap.log_explanation方法仅支持shap.KernelExplainer(shap-lrjball.readthedocs.io/en/latest/generated/shap.KernelExplainer.html)。这非常消耗计算资源,因为计算时间会随着特征数量的增加呈指数增长;因此,甚至对于中等规模的数据集,也无法计算解释(请参阅已发布的 GitHub 问题github.com/mlflow/mlflow/issues/4071)。MLflow 提供的现有示例仅适用于 scikit-learn 包中的经典机器学习模型,如线性回归或随机森林,并没有提供深度学习模型的解释示例(github.com/mlflow/mlflow/tree/master/examples/shap)。我们将在本章后面的章节中展示,当前该 API 不支持基于 transformers 的 SHAP 解释器和解释,因此在本章中我们将不使用该 API。我们将在讲解本章示例时突出一些问题。 - 
使用
mlflow.evaluateAPI(www.mlflow.org/docs/latest/python_api/mlflow.html#mlflow.evaluate)。该 API 可以在模型训练和测试之后用于评估。这是一个实验性功能,未来可能会有所变化。它支持 MLflowpyfunc模型。然而,它有一些限制:评估数据集的标签值必须是数字或布尔值,所有特征值必须是数字,每个特征列必须只包含标量值(www.mlflow.org/docs/latest/models.html#model-evaluation)。同样,MLflow 提供的现有示例仅适用于 scikit-learn 包中的经典机器学习模型(github.com/mlflow/mlflow/tree/master/examples/evaluation)。我们可以使用该 API 仅记录 NLP 情感模型的分类器指标,但该 API 会自动跳过解释部分,因为它要求特征列包含标量值(NLP 模型的输入是文本输入)。因此,这不适用于我们需要的深度学习模型可解释性。所以,我们在本章中将不使用该 API。 
鉴于某些 API 仍处于实验阶段并且还在不断发展,用户应注意限制和解决方法,以便成功实现 MLflow 的可解释性。在本章中,我们将学习如何实现深度学习模型的可解释性,使用 MLflow 来实现这一点相当具有挑战性,因为 MLflow 与 SHAP 的集成仍在进行中(截至 MLflow 版本 1.25.1)。在接下来的章节中,我们将学习何时以及如何使用这些不同的 API 来实现解释,并记录和加载深度学习模型的解释器。
使用 MLflow 工件记录 API 实现 SHAP 解释
MLflow 有一个通用的跟踪 API,可以记录任何工件:mlflow.log_artifact。然而,MLflow 文档中给出的示例通常使用 scikit-learn 和表格型数值数据进行训练、测试和解释。在这里,我们希望展示如何使用mlflow.log_artifact来记录与 NLP 情感分析深度学习模型相关的工件,如 Shapley 值数组和 Shapley 值条形图。您可以查看本章 GitHub 仓库中的 Python VS Code 笔记本shap_mlflow_log_artifact.py(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/notebooks/shap_mlflow_log_artifact.py),以便按照步骤操作:
- 
请确保您已准备好相关的前提条件,包括本地完整的 MLflow 服务器和 conda 虚拟环境。请按照
README.md文件中的说明(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/README.md)中的步骤,确保这些内容已就绪,文件位于 第十章 文件夹中。 - 
在开始运行本章任何代码之前,请确保按照如下方式激活
chapter10-dl-explain虚拟环境:conda activate chapter10-dl-explain - 
在笔记本开头导入相关库,如下所示:
import os import matplotlib.pyplot as plt import mlflow from mlflow.tracking import MlflowClient from mlflow.utils.file_utils import TempDir import shap import transformers from shap.plots import * import numpy as np - 
下一步是设置一些环境变量。前三个环境变量用于本地 MLflow URI,第四个用于禁用由于已知 Hugging Face 标记化问题而产生的 Hugging Face 警告:
os.environ["AWS_ACCESS_KEY_ID"] = "minio" os.environ["AWS_SECRET_ACCESS_KEY"] = "minio123" os.environ["MLFLOW_S3_ENDPOINT_URL"] = "http://localhost:9000" os.environ["TOKENIZERS_PARALLELISM"] = "False" - 
我们还需要设置 MLflow 实验,并将 MLflow 实验 ID 作为输出显示在屏幕上:
EXPERIMENT_NAME = "dl_explain_chapter10" mlflow.set_tracking_uri('http://localhost') mlflow.set_experiment(EXPERIMENT_NAME) experiment = mlflow.get_experiment_by_name(EXPERIMENT_NAME) print("experiment_id:", experiment.experiment_id) 
如果您已经运行了该笔记本,应该能看到类似如下的输出:
experiment_id: 14
这意味着实验名称为dl_explain_chapter10的 MLflow 实验 ID 是14。请注意,您也可以按照如下方式将 MLflow 跟踪 URI 设置为环境变量:
export MLFLOW_TRACKING_URI=http://localhost
在这里,我们使用 MLflow 的mlflow.set_tracking_uri API 来定义 URI 位置。两种方式都可以。
- 
现在我们可以创建一个 DL 模型,使用 Hugging Face 的 transformer pipeline API 将一句话分类为正面或负面情感。由于这个模型已经进行过微调,我们将重点放在如何获取该模型的解释器和解释内容,而不是如何训练或微调模型:
dl_model = transformers.pipeline('sentiment-analysis', return_all_scores=False) explainer = shap.Explainer(dl_model) shap_values = explainer(["Not a good movie to spend time on.", "This is a great movie."]) 
这些代码片段创建了一个情感分析模型dl_model,然后为该模型创建一个 SHAP explainer。接着,我们为这个解释器提供了一个包含两句话的列表,以获取 shap_values 对象。该对象将用于在 MLflow 中记录。
给定 shap_values 对象,我们现在可以开始一个新的 MLflow 运行,并记录 Shapley 值和我们在上一章中看到的条形图(第九章**,深度学习可解释性基础)。第一行代码确保所有活跃的 MLflow 运行都已结束。如果我们想多次交互性地重新运行这段代码,这非常有用:
mlflow.end_run()
然后我们定义两个常量。一个是artifact_root_path,用于 MLflow 工件存储中的根路径,将用于存储所有 SHAP 解释对象。另一个是shap_bar_plot,用于工件文件名,将用于条形图图形:
artifact_root_path = "model_explanations_shap"
artifact_file_name = 'shap_bar_plot'
- 
然后我们开始一个新的 MLflow 运行,在该运行中,我们将生成并记录三个 SHAP 文件到 MLflow 工件存储中,路径为
model_explanations_shap:with mlflow.start_run() as run: with TempDir() as temp_dir: temp_dir_path = temp_dir.path() print("temp directory for artifacts: {}".format(temp_dir_path)) 
我们还需要一个临时的本地目录,如前面的代码片段所示,用来先保存 SHAP 文件,然后再将这些文件记录到 MLflow 服务器。如果你已经运行到此步骤,你应该在输出中看到一个类似以下的临时目录:
temp directory for artifacts: /var/folders/51/whxjy4r92dx18788yp11ycyr0000gp/T/tmpgw520wu1
- 
现在我们准备生成并保存 SHAP 文件。第一个是条形图,这个稍微有点复杂,保存和记录起来有点难度。让我们一起看看以下代码,了解我们是如何做到的:
try: plt.clf() plt.subplots_adjust(bottom=0.2, left=0.4) shap.plots.bar(shap_values[0, :, "NEGATIVE"], show=False) plt.savefig(f"{temp_dir_path}/{artifact_file_name}") finally: plt.close(plt.gcf()) mlflow.log_artifact(f"{temp_dir_path}/{artifact_file_name}.png", artifact_root_path) 
请注意,我们使用了 matplotlib.pyplot,并将其导入为 plt,首先通过 plt.clf() 清除图形,然后创建一个具有一些调整的子图。在这里,我们定义了 bottom=0.2,意味着子图底部边缘的位置位于图形高度的 20%。同样,我们调整了子图的左边缘。然后,我们使用 shap.plots.bar SHAP API 绘制第一句话的特征贡献的条形图,但将 show 参数设置为 False。这意味着我们在交互式运行中看不到该图,但图形会存储在 pyplot plt 变量中,然后可以使用 plt.savefig 将其保存到本地临时目录,文件名前缀为 shap_bar_plot。pyplot 会在文件保存后自动添加文件扩展名 .png。因此,这会将名为 shap_bar_plot.png 的本地图像文件保存到临时文件夹中。最后的语句调用了 MLflow 的 mlflow.log_artifact,将此 PNG 文件上传到 MLflow 跟踪服务器的工件存储的根文件夹 model_explanations_shap。我们还需要确保通过调用 plt.close(plt.gcf()) 来关闭当前图形。
- 
除了将
shap_bar_plot.png日志记录到 MLflow 服务器外,我们还希望将 Shapleybase_values数组和shap_values数组作为 NumPy 数组记录到 MLflow 跟踪服务器中。这可以通过以下语句实现:np.save(f"{temp_dir_path}/shap_values", shap_values.values) np.save(f"{temp_dir_path}/base_values", shap_values.base_values) mlflow.log_artifact( f"{temp_dir_path}/shap_values.npy", artifact_root_path) mlflow.log_artifact( f"{temp_dir_path}/base_values.npy", artifact_root_path) 
这将首先在本地临时文件夹中保存 shap_values.npy 和 base_values.npy 的本地副本,然后将其上传到 MLflow 跟踪服务器的工件存储中。
- 
如果你按照笔记本的步骤操作到这里,你应该能够在本地 MLflow 服务器中验证这些工件是否成功存储。前往本地主机上的 MLflow UI –
http://localhost/,然后找到实验dl_explain_chapter10。你应该能够找到刚刚运行的实验。它应该类似于 图 10.1,在那里你可以在model_explanations_shap文件夹中找到三个文件:base_values.npy、shap_bar_plot.png和shap_values.npy。图 10.1 显示了句子Not a good movie to spend time on的预测结果的不同标记或词汇的特征贡献条形图。此实验页面的 URL 类似于以下内容:http://localhost/#/experiments/14/runs/10f0655189f740aeb813a015f1f6e115 

图 10.1 – MLflow log_artifact API 将 SHAP 条形图作为图像保存到 MLflow 跟踪服务器
或者,您也可以使用代码以编程方式下载存储在 MLflow 跟踪服务器中的这些文件并在本地查看。我们在笔记本的最后一个单元格中提供了这样的代码。
- 
如果你运行笔记本代码中的最后一个单元格,该单元格用于从我们刚才保存的 MLflow 服务器下载这三个文件并打印它们,你应该能够看到以下输出,如图 10.2所示。从 MLflow 跟踪服务器下载工件的机制是使用
MlflowClient().download_artifactsAPI,你需要提供 MLflow 运行 ID(在我们的示例中是10f0655189f740aeb813a015f1f6e115)和工件根路径model_explanations_shap作为 API 的参数:downloaded_local_path = MlflowClient().download_artifacts(run.info.run_id, artifact_root_path) 
这将把model_explanations_shap文件夹中的所有文件从 MLflow 跟踪服务器下载到本地路径,返回的变量为downloaded_local_path:

图 10.2 – 从 MLflow 跟踪服务器下载 SHAP 的 base_values 和 shap_values 数组到本地路径并显示它们
要显示这两个 NumPy 数组,我们需要调用 NumPy 的load API 来加载它们,然后打印它们:
base_values = np.load(os.path.join(downloaded_local_path, "base_values.npy"), allow_pickle=True)
shap_values = np.load(os.path.join(downloaded_local_path, "shap_values.npy"), allow_pickle=True)
请注意,当调用np.load API 时,我们需要将allow_pickle参数设置为True,以便 NumPy 可以正确地将这些文件加载回内存。
虽然你可以在 VS Code 环境中交互式运行这个笔记本,但你也可以按如下方式在命令行中运行它:
python shap_mlflow_log_artifact.py
这将产生所有控制台输出,并将所有工件记录到 MLflow 服务器中,正如我们在交互式运行笔记本时所看到的。
如果你已经运行了到目前为止的代码,恭喜你成功实现了使用 MLflow 的mlflow.log_artifact API 将 SHAP 解释记录到 MLflow 跟踪服务器!
尽管记录所有解释的过程看起来有些冗长,但这种方法确实有一个优点,即不依赖于使用何种解释器,因为解释器是在 MLflow 工件日志 API 之外定义的。
在下一节中,我们将看到如何使用内置的mlflow.pyfunc.PythonModel API 将 SHAP 解释器记录为 MLflow 模型,然后将其部署为端点或像通用的 MLflow pyfunc模型一样在批处理模式中使用。
使用 MLflow pyfunc API 实现 SHAP 解释器
正如我们在前一节中了解到的,SHAP 解释器可以在需要时离线使用,只需通过 SHAP API 创建一个新的解释器实例。然而,由于底层的深度学习模型通常会被记录到 MLflow 服务器中,因此希望将相应的解释器也记录到 MLflow 服务器中,这样我们不仅可以跟踪深度学习模型,还能跟踪它们的解释器。此外,我们还可以使用通用的 MLflow pyfunc 模型日志和加载 API 来处理解释器,从而统一访问深度学习模型及其解释器。
在本节中,我们将一步一步学习如何将 SHAP 解释器实现为一个通用的 MLflow pyfunc 模型,并如何将其用于离线和在线解释。我们将把过程分为三个小节:
- 
创建并记录 MLflow pyfunc 解释器
 - 
部署 MLflow pyfunc 解释器用于 EaaS
 - 
使用 MLflow pyfunc 解释器进行批量解释
 
让我们从创建和记录 MLflow pyfunc 解释器的第一小节开始。
创建并记录 MLflow pyfunc 解释器
为了跟随本节内容,请查看 GitHub 仓库中的nlp_sentiment_classifier_explainer.py文件(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/pipeline/nlp_sentiment_classifier_explainer.py):
- 
首先,通过子类化
mlflow.pyfunc.PythonModel,我们可以创建一个定制的 MLflow 模型,该模型封装了一个 SHAP 解释器。因此,按如下方式声明此类:class SentimentAnalysisExplainer(mlflow.pyfunc.PythonModel): - 
接下来,我们需要实例化一个解释器。我们将不在这个类的
init方法中创建解释器,而是使用load_context方法为 Hugging Face NLP 情感分析分类器加载一个 SHAP 解释器,如下所示:def load_context(self, context): from transformers import pipeline import shap self.explainer = shap.Explainer(pipeline('sentiment-analysis', return_all_scores=True)) 
这将在每次执行SentimentAnalysisExplainer类时创建一个 SHAP 解释器。请注意,情感分类器是一个 Hugging Face 管道对象,return_all_scores参数设置为True。这意味着它将返回每个输入文本的正负情感标签和概率得分。
避免 SHAP 解释器的运行时错误
如果我们在此类的init方法中实现self.explainer,则会遇到与 SHAP 包的_masked_model.py文件相关的运行时错误,该错误抱怨init方法将被 MLflow 序列化,因此很明显,这个运行时错误来自 MLflow 的序列化。然而,在load_context函数中实现self.explainer可以避免 MLflow 的序列化,并且在运行时调用这个解释器时能正常工作。
- 
然后我们将实现
sentiment_classifier_explanation方法,该方法接收一个 pandas DataFrame 行作为输入,并生成一个已保存的shap_values输出,作为单行文本输入的解释:def sentiment_classifier_explanation(self, row): shap_values = self.explainer([row['text']]) return [pickle.dumps(shap_values)] 
请注意,我们需要使用一对方括号将 row['text'] 的值括起来,使其成为一个列表,而不仅仅是单一的值。这是因为这个 SHAP 解释器期望的是一个文本列表,而不是单一的字符串。如果我们不将值放入方括号中,解释器会按字符拆分整个字符串,将每个字符当作一个单词,这不是我们想要的。一旦我们从解释器获得了作为 shap_values 的 Shapley 值输出,我们就需要使用 pickle.dumps 对其进行序列化,然后再返回给调用方。MLflow pyfunc 模型的输入输出签名不支持未序列化的复杂对象,因此这一步的 pickling 确保了模型输出签名符合 MLflow 的要求。稍后我们将在 第 5 步 中看到这个 MLflow pyfunc 解释器的输入输出签名的定义。
- 
接下来,我们需要为这个类实现所需的
predict方法。这将应用sentiment_classifier_explanation方法到整个输入的 pandas DataFrame,如下所示:def predict(self, context, model_input): model_input[['shap_values']] = model_input.apply( self.sentiment_classifier_explanation, axis=1, result_type='expand') model_input.drop(['text'], axis=1, inplace=True) return model_input 
这将为输入 pandas DataFrame 中的 text 列的每一行生成一个新的列,命名为 shap_values。然后我们删除 text 列,返回一个单列的 shap_values DataFrame 作为最终的预测结果:在这种情况下,解释结果以 DataFrame 的形式呈现。
- 
现在我们已经实现了
SentimentAnalysisExplainer类,可以使用标准的 MLflow pyfunc 模型日志记录 API 将此模型记录到 MLflow 跟踪服务器中。在进行 MLflow 日志记录之前,让我们确保声明此解释器的模型签名,如下所示:input = json.dumps([{'name': 'text', 'type': 'string'}]) output = json.dumps([{'name': 'shap_values', 'type': 'string'}]) signature = ModelSignature.from_dict({'inputs': input, 'outputs': output}) 
这些语句声明了输入是一个包含单一 string 类型 text 列的 DataFrame,输出是一个包含单一 string 类型 shap_values 列的 DataFrame。回想一下,这个 shap_values 列是一个经过 pickled 序列化的字节串,包含了 Shapley 值对象。
- 
最后,我们可以在任务方法中使用
mlflow.pyfunc.log_model方法实现解释器日志记录步骤,如下所示:with mlflow.start_run() as mlrun: mlflow.pyfunc.log_model( artifact_path=MODEL_ARTIFACT_PATH, conda_env=CONDA_ENV, python_model=SentimentAnalysisExplainer(), signature=signature) 
我们在 log_model 方法中使用了四个参数。MODEL_ARTIFACT_PATH 是 MLflow 跟踪服务器中存储解释器的文件夹名称。这里,值在你查看的 Python 文件中定义为 nlp_sentiment_classifier_explainer。CONDA_ENV 是本章根目录中的 conda.yaml 文件。python_model 参数是我们刚刚实现的 SentimentAnalysisExplainer 类,signature 是我们定义的解释器输入输出签名。
- 
现在我们已经准备好按如下方式在命令行运行整个文件:
python nlp_sentiment_classifier_explainer.py 
假设你已经根据 GitHub 仓库中本章的 README.md 文件正确设置了本地 MLflow 跟踪服务器和环境变量,那么在控制台输出中将生成以下两行:
2022-05-11 17:49:32,181 Found credentials in environment variables.
2022-05-11 17:49:32,384 finished logging nlp sentiment classifier explainer run_id: ad1edb09e5ea4d8ca0332b8bc2f5f6c9
这意味着我们已经成功地将解释器记录到本地的 MLflow 跟踪服务器中。
- 在 Web 浏览器中访问 
http://localhost/,然后点击dl_explain_chapter10实验文件夹。你应该能在nlp_sentiment_classifier_explainer下的Artifacts文件夹中找到此运行和记录的解释器,它应该如图 10.3所示: 

图 10.3 – SHAP 解释器作为 MLflow pyfunc 模型被记录
请注意,图 10.3 中显示的 MLmodel 元数据与我们之前记录为 MLflow pyfunc 模型的普通 DL 推理流水线差别不大,除了 artifact_path 名称和 signature。这就是使用这种方法的优势,因为现在我们可以使用通用的 MLflow pyfunc 模型方法来加载此解释器或将其作为服务部署。
mlflow.shap.log_explainer API 的问题
正如我们之前提到的,MLflow 提供了一个 mlflow.shap.log_explainer API,它提供了一种记录解释器的方法。然而,这个 API 不支持我们的 NLP 情感分类器解释器,因为我们的 NLP 流水线不是 MLflow 当前支持的已知模型类型。因此,即使 log_explainer 能将该解释器对象写入跟踪服务器,当通过 mlflow.shap.load_explainer API 将解释器加载回内存时,它会因以下错误消息而失败:mlflow.shap.log_explainer API 在本书中的问题。
现在我们有了已记录的解释器,可以通过两种方式使用它:将其部署到 Web 服务中,以便我们可以创建一个端点来建立 EaaS,或者直接通过 MLflow pyfunc 的 load_model 或 spark_udf 方法使用 MLflow 的 run_id 加载解释器。让我们从设置本地 Web 服务来开始部署。
部署 MLflow pyfunc 解释器以进行 EaaS
由于现在 SHAP 解释器就像一个通用的 MLflow pyfunc 模型,我们可以按标准 MLflow 方式设置一个本地 EaaS。请按照以下步骤查看如何在本地实现:
- 
运行以下 MLflow 命令,以为我们刚刚记录的解释器设置本地 Web 服务。此示例中的
run_id是ad1edb09e5ea4d8ca0332b8bc2f5f6c9:mlflow models serve -m runs:/ ad1edb09e5ea4d8ca0332b8bc2f5f6c9/nlp_sentiment_classifier_explainer 
这将产生以下控制台输出:

图 10.4 – SHAP EaaS 控制台输出
请注意,在图 10.4中,默认的预训练语言模型在 gunicore HTTP 服务器启动并运行后加载。这是因为我们实现的解释器位于 load_context 方法中,这正是预期的行为:在 Web 服务启动并运行后立即加载解释器。
- 
在另一个终端窗口中,输入以下命令以调用本地主机端口
5000上的解释器 Web 服务,并输入两个示例文本:curl -X POST -H "Content-Type:application/json; format=pandas-split" --data '{"columns":["text"],"data":[["This is meh weather"], ["This is great weather"]]}' http://127.0.0.1:5000/invocations 
这将产生以下输出:

图 10.5 - 调用我们的 SHAP EaaS 后 DataFrame 中的响应
请注意,在图 10.5中,列名为 shap_values,而其值为 pickle 序列化的字节十六进制数据。这些数据并不易于阅读,但可以在调用方使用pickle.loads方法将其转换回原始的 shap_values。因此,如果您看到类似图 10.5的响应输出,恭喜您已经设置了本地 EaaS!您可以像其他 MLflow 服务部署一样部署此解释器服务,正如第八章**,在规模上部署 DL 推理管道中描述的那样,因为此解释器现在可以像通用的 MLflow pyfunc 模型服务一样调用。
接下来,我们将看到如何使用 MLflow pyfunc 解释器进行批量解释。
使用 MLflow pyfunc 解释器进行批量解释
有两种方法可以使用 MLflow pyfunc 解释器实现离线批量解释:
- 
将 pyfunc 解释器加载为 MLflow pyfunc 模型,以解释给定的 pandas DataFrame 输入。
 - 
将 pyfunc 解释器加载为 PySpark UDF 以解释给定的 PySpark DataFrame 输入。
 
让我们从将解释器作为 MLflow pyfunc 模型加载开始。
将 MLflow pyfunc 解释器加载为 MLflow pyfunc 模型
正如我们之前提到的,消费 MLflow 记录的解释器的另一种方法是直接在本地 Python 代码中使用 MLflow 的 pyfunc load_model方法加载解释器,而不是将其部署为 Web 服务。这非常直接,我们将向您展示如何完成。您可以在 GitHub 存储库中的shap_mlflow_pyfunc_explainer.py文件中查看代码(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/notebooks/shap_mlflow_pyfunc_explainer.py):
- 
第一步是将记录的解释器加载回内存。以下代码使用
mlflow.pyfunc.load_model和解释器run_idURI 来实现这一点:run_id = "ad1edb09e5ea4d8ca0332b8bc2f5f6c9" logged_explainer = f'runs:/{run_id}/nlp_sentiment_classifier_explainer' explainer = mlflow.pyfunc.load_model(logged_explainer) 
这应该加载解释器,就像它只是一个通用的 MLflow pyfunc 模型一样。我们可以通过运行以下代码打印解释器的元数据:
explainer
这将显示以下输出:
mlflow.pyfunc.loaded_model: artifact_path: nlp_sentiment_classifier_explainer flavor: mlflow.pyfunc.model run_id: ad1edb09e5ea4d8ca0332b8bc2f5f6c9
这意味着这是一个mlflow.pyfunc.model风格,这是个好消息,因为我们可以使用相同的 MLflow pyfunc API 来使用这个解释器。
- 
接下来,我们将获取一些示例数据来测试新加载的解释器:
import datasets dataset = datasets.load_dataset("imdb", split="test") short_data = [v[:500] for v in dataset["text"][:20]] df_test = pd.DataFrame (short_data, columns = ['text']) 
这将加载 IMDb 测试数据集,将每个评论文本截断为 500 个字符,并选择前 20 行,以便为下一步的解释创建一个 pandas DataFrame。
- 
现在,我们可以按如下方式运行解释器:
results = explainer.predict(df_test) 
这将为输入 DataFrame df_test 运行 SHAP 分区解释器。当运行时,它将显示 DataFrame 的每一行的以下输出:
Partition explainer: 2it [00:38, 38.67s/it]
结果将是一个包含单列shap_values的 pandas DataFrame。这可能需要几分钟时间,因为它需要对每一行进行分词、执行解释器并序列化输出。
- 
一旦解释器执行完毕,我们可以通过反序列化行内容来检查结果。以下是检查第一行输出的代码:
results_deserialized = pickle.loads(results['shap_values'][0]) print(results_deserialized) 
这将打印出第一行的shap_values。图 10.6展示了shap_values输出的部分截图:

图 10.6 – 解释中反序列化shap_values的部分输出
正如在图 10.6中所看到的,shap_values的输出与我们在第九章**《深度学习可解释性基础》中学到的没有什么不同,当时我们没有使用 MLflow 记录和加载解释器。我们还可以生成 Shapley 文本图,以突出文本对预测情感的贡献。
- 
在笔记本中运行以下语句以查看 Shapley 文本图:
shap.plots.text(results_deserialized[:,:,"POSITIVE"]) 
这将生成一个如图 10.7所示的图:

图 10.7 – 使用我们通过 MLflow 记录的解释器反序列化shap_values生成的 Shapley 文本图
如图 10.7所示,这条评论具有正面情感,贡献于预测情感的关键词或短语包括good、love以及其他一些以红色标记的短语。当你看到这个 Shapley 文本图时,应该为自己鼓掌,因为你已经学会了如何使用 MLflow 记录的解释器来生成批量解释。
如在这一批量解释的逐步实现中提到的,使用这种 pyfunc 模型方法进行大规模批量解释会稍微慢一些。幸运的是,我们还有另一种方法可以使用 PySpark UDF 函数实现批量解释,我们将在下一个子章节中进行解释。
加载 pyfunc 解释器作为 PySpark UDF
对于可扩展的批量解释,我们可以利用 Spark 的分布式计算能力,这通过将 pyfunc 解释器作为 PySpark UDF 加载来支持。使用此功能不需要额外的工作,因为 MLflow pyfunc API 已经通过mlflow.pyfunc.spark_udf方法提供了这一功能。我们将一步步向你展示如何实现这种大规模解释:
- 
首先,确保你已经完成了
README.md文件的操作(github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/README.md),以安装 Spark,创建并激活chapter10-dl-pyspark-explain虚拟环境,并在运行 PySpark UDF 代码进行大规模解释之前设置所有环境变量。 - 
然后,您可以开始运行 VS Code 笔记本
shap_mlflow_pyspark_explainer.py,您可以在 GitHub 仓库中查看:github.com/PacktPublishing/Practical-Deep-Learning-at-Scale-with-MLFlow/blob/main/chapter10/notebooks/shap_mlflow_pyspark_explainer.py。在chapter10/notebooks/目录下运行以下命令:python shap_mlflow_pyspark_explainer.py 
您将会得到显示在图 10.8中的最终输出,前面有相当多行输出在这些最后几行之前:

图 10.8 – PySpark UDF 解释器输出的前两行文本的 shap_values 以及它们的输入文本
正如在图 10.8中所示,PySpark UDF 解释器的输出是一个 PySpark DataFrame,包含两列:text和shap_values。text列是原始输入文本,而shap_values列包含了像我们在前面子节中使用 pandas DataFrame 时看到的 Shapley 值的 pickled 序列化形式。
现在让我们看看代码中发生了什么。我们将解释shap_mlflow_pyspark_explainer.py文件中的关键代码块。由于这是一个 VS Code 笔记本,您可以像我们刚刚在命令行中运行或在 VS Code IDE 窗口中交互式地运行它。
- 
第一个关键代码块是使用
mflow.pyfunc.spark_udf方法加载解释器,如下所示:spark = SparkSession.builder.appName("Batch explanation with MLflow DL explainer").getOrCreate() run_id = "ad1edb09e5ea4d8ca0332b8bc2f5f6c9" logged_explainer = f'runs:/{run_id}/nlp_sentiment_classifier_explainer' explainer = mlflow.pyfunc.spark_udf(spark, model_uri=logged_explainer, result_type=StringType()) 
第一条语句是初始化一个SparkSession变量,然后使用run_id将记录的解释器加载到内存中。运行解释器以获取元数据如下:
explainer
我们将得到以下结果:
<function mlflow.pyfunc.spark_udf.<locals>.udf(iterator: Iterator[Tuple[Union[pandas.core.series.Series, pandas.core.frame.DataFrame], ...]]) -> Iterator[pandas.core.series.Series]>
这意味着我们现在将 SHAP 解释器包装为 Spark UDF 函数。这允许我们在下一步直接对输入的 PySpark DataFrame 应用 SHAP 解释器。
- 
我们像以前一样加载 IMDb 测试数据集,以获得
short_data列表,然后为测试数据集的前 20 行创建一个 PySpark DataFrame 以进行解释:df_pandas = pd.DataFrame (short_data, columns = ['text']) spark_df = spark.createDataFrame(df_pandas) spark_df = spark_df.withColumn('shap_values', explainer()) 
注意最后一条语句,它使用 PySpark 的withColumn函数将一个新的shap_values列添加到输入 DataFrame spark_df中,该 DataFrame 最初只包含一个列text。这是使用 Spark 的并行和分布式计算能力的自然方式。如果您已经运行了使用 MLflow pyfunc load_model方法和当前 PySpark UDF 方法的前面的非 Spark 方法,您会注意到 Spark 方法即使在本地计算机上也运行得更快。这使我们能够为许多输入文本实例规模化地进行 SHAP 解释。
- 最后,为了验证结果,我们展示了
spark_dfDataFrame 的前两行,这在图 10.8中有所说明。 
现在,通过 MLflow 的 pyfunc Spark UDF 包装的 SHAP 解释器,我们可以自信地进行大规模批量解释。恭喜!
让我们在接下来的部分总结本章所学内容。
摘要
在本章中,我们首先回顾了可以用于实现可解释性的 MLflow API 中的现有方法。两个现有的 MLflow API,mlflow.shap 和 mlflow.evaluate,存在一定的局限性,因此不能用于我们需要的复杂深度学习模型和管道的可解释性场景。随后,我们重点介绍了在 MLflow API 框架内实现 SHAP 解释和解释器的两种主要方法:使用 mlflow.log_artifact 记录解释内容,以及使用 mlflow.pyfunc.PythonModel 记录 SHAP 解释器。使用 log_artifact API 可以将 Shapley 值和解释图记录到 MLflow 跟踪服务器中。而使用 mlflow.pyfunc.PythonModel 则可以将 SHAP 解释器作为 MLflow pyfunc 模型记录,从而为将 SHAP 解释器作为 Web 服务部署并创建 EaaS 端点提供了可能。同时,它也为通过 MLflow pyfunc 的 load_model 或 spark_udf API 执行大规模离线批量解释提供了可能。这使我们能够在大规模应用中为深度学习模型实现可解释性,增加了实现的信心。
随着可解释性领域的不断发展,MLflow 与 SHAP 及其他可解释性工具箱的集成也将持续改进。我们鼓励有兴趣的读者通过进一步阅读部分提供的链接,继续他们的学习之旅。祝愿大家持续学习和成长!
进一步阅读
- 
大规模 Shapley 值:
neowaylabs.github.io/data-science/shapley-values-at-scale/ - 
使用 PySpark 和 Pandas UDF 扩展 SHAP 计算:
databricks.com/blog/2022/02/02/scaling-shap-calculations-with-pyspark-and-pandas-udf.html - 
使用 Ray 分布式计算系统加速 Shapley 值计算:
www.telesens.co/2020/10/05/speeding-up-shapley-value-computation-using-ray-a-distributed-computing-system/ - 
使用 LIME 和 SHAP 解释 NLP 模型:
medium.com/@kalia_65609/interpreting-an-nlp-model-with-lime-and-shap-834ccfa124e4 - 
MLflow 中的模型评估:
databricks.com/blog/2022/04/19/model-evaluation-in-mlflow.html 

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,以及行业领先的工具,帮助你规划个人发展并提升职业生涯。欲了解更多信息,请访问我们的网站。
第十一章:为什么订阅?
- 
通过来自 4000 多位行业专家的实用电子书和视频,减少学习时间,增加编码时间
 - 
通过为你特别定制的技能计划,提升你的学习效果
 - 
每月获得一本免费的电子书或视频
 - 
完全可搜索,轻松访问重要信息
 - 
复制、粘贴、打印并书签内容
 
你知道 Packt 为每本书提供电子书版本,且提供 PDF 和 ePub 文件吗?你可以在packt.com升级到电子书版本,作为印刷书籍的客户,你还可以享受电子书的折扣。欲了解更多详情,请通过 customercare@packtpub.com 联系我们。
在www.packt.com,你还可以阅读一系列免费的技术文章,订阅各种免费的新闻通讯,并享受 Packt 书籍和电子书的独家折扣和优惠。
你可能喜欢的其他书籍
如果你喜欢这本书,可能会对 Packt 的其他书籍感兴趣:

工程化 MLOps
Emmanuel Raj
ISBN: 9781800562882
- 
制定机器学习训练和部署的数据治理策略和流程
 - 
掌握实现机器学习流程、CI/CD 流程和机器学习监控流程
 - 
设计一个强大且可扩展的微服务和 API,适用于测试和生产环境
 - 
为相关的使用案例和组织策划你的自定义 CD 流程
 - 
监控机器学习模型,包括监控数据漂移、模型漂移和应用性能
 - 
构建和维护自动化机器学习系统
 

使用 Python 进行机器学习工程
Andrew McMahon
ISBN: 9781801079259
- 
了解有效的机器学习工程过程是什么样的
 - 
探索自动化训练和部署的选项,并学习如何使用它们
 - 
发现如何构建自己的包装库,用于封装数据科学和机器学习逻辑与解决方案
 - 
理解可以将哪些软件工程方面的知识应用到机器学习中
 - 
深入了解如何使用适当的云技术调整软件工程以适应机器学习
 - 
以相对自动化的方式进行超参数调优
 
Packt 正在寻找像你这样的作者
如果你有兴趣成为 Packt 的作者,请访问authors.packtpub.com并今天就申请。我们已经与成千上万的开发者和技术专业人士合作,帮助他们将自己的见解分享给全球技术社区。你可以提交一般申请、申请我们正在招聘作者的特定热门话题,或者提交你自己的创意。
分享你的想法
现在你已经完成了《大规模深度学习实战与 MLflow》,我们非常想听听你的想法!如果你是通过亚马逊购买的这本书,请点击这里直接前往亚马逊的书籍评价页面,分享你的反馈或在你购买书籍的网站上留下评论。
你的评价对我们以及技术社区非常重要,将帮助我们确保提供高质量的内容。

                    
                
                
            
        
浙公网安备 33010602011771号