MLflow 中的模型、风格(Flavors)和 PyFunc
在 MLflow 生态系统中,“风格(flavors)”在模型管理中起着关键作用。本质上,“风格”是特定机器学习库的指定包装器。例如,spark-ml 包虽然产生不同的模型类型,如 Pipeline、LogisticRegressionModel 或 RandomForestModel,但都属于 Spark 风格范畴。这种抽象确保了,无论模型的基础结构如何,其 Spark 风格的变体都可以使用 MLflow 的命名风格工具轻松保存、记录和检索。
风格简化了跨不同框架保存、加载和处理机器学习模型的过程。它们考虑了每个库独特的模型序列化和反序列化方法。
MLflow 的风格设计确保了一定程度的一致性。对于每个库,其对应的 MLflow 风格定义了加载的 pyfunc 的推理部署行为。每种风格都规定了一种 predict 方法的行为,确保了一种一致但略显严格的格式。
为了理解这些约束,以 sklearn 风格为例。下图描绘了它的实现,突出了 MLflow 已标准化的 API 和序列化方法
虽然 MLflow 努力为每种风格提供一个普遍适用的 pyfunc 表示,但并非总是能够适应特定库生成的每个独特的模型场景。
然而,事情还有转机。MLflow 提供了通过扩展基础 PythonModel 基类来构建自定义 pyfunc 的灵活性,所有命名风格的 pyfunc 变体都以此为基础。通过正确实现 PythonModel,您可以将来自任何库的任何代码或模型嵌入到自定义类中,同时享受与命名风格相关的一致性优势。
要深入了解这些功能,让我们研究 MLflow Model 的核心结构。
MLflow 中模型的组成部分
当提到“模型”时,大多数从业者会想到机器学习训练过程中学到的参数或权重。这些通常被保存为一个文件或一组文件,然后用于对新的、未见过的数据进行预测。然而,在 MLOps 领域,特别是在 MLflow 中,“模型”的概念要广泛得多。
在 MLflow 中,模型不仅仅是包含学习参数的二进制文件。它是一个全面的包或捆绑,封装了在各种环境中可靠地重现预测所需的一切。
这包括模型的权重,但远不止于此。
-
模型二进制文件:这是核心部分 - 实际保存的模型权重或参数。这是许多人认为的“模型”。
-
附加二进制文件:对于某些模型,可能需要辅助文件。例如,NLP 模型的 tokenizer,用于预处理的 scaler,甚至是决策树或 k-means 聚类中心等非参数元素。
-
预加载代码:某些模型可能需要在推理环境中加载自定义代码。这可以用于预处理、后处理或其他自定义逻辑。
-
库依赖项:为了模型正常运行,它可能依赖于特定版本的库。MLflow 会跟踪这些依赖项,确保模型运行的环境与训练时的环境相匹配。
-
元数据:这包含关于模型起源的重要信息。它可以跟踪谁训练了模型,使用了什么代码,何时何地训练的。这些元数据对于模型治理、审计和可重现性至关重要。
-
PyFunc 签名:为了确保无缝部署和推理,MLflow 将模型包装在一个标准化的 pyfunc 接口中。此接口定义了预期的输入和输出格式,确保一致性。
-
输入示例:这是一个可选组件,提供一个可以用于测试的示例输入,确保部署的模型正常运行。
所有这些元素都可以在 MLflow UI 的 artifact 查看器中查看,当查看已保存的模型时。
MLflow 中记录的模型目录的内容取决于保存或记录模型时提供的可选参数数量以及底层基础模型的类型。与某些模型风格相比,有些模型风格具有额外的元数据和序列化 artifact。
创建自定义 pyfunc 时,了解此处所示的组成部分很重要,因为这个结构及其中的元素是创建和使用自定义 PyFuncs 时需要交互的对象。
理解“命名风格(Named Flavors)”
MLflow 中的命名风格指与特定机器学习或数据处理框架关联的预定义实体。例如,如果您正在使用 Scikit-Learn 模型,您可能会使用诸如 mlflow.sklearn.save_model()、mlflow.sklearn.load_model() 和 mlflow.sklearn.log_model() 之类的方法。
命名风格的关键属性包括
-
根命名空间集成:命名风格可以直接从 MLflow 根命名空间访问,方便进行交互。
-
PyFunc 兼容性:使用命名风格保存的模型可以作为 PyFunc 加载回来。这有助于与各种部署环境集成,无论是实时推理平台、基于 Spark 的批处理,还是任何可以调用 Python 函数的系统。
-
自动记录(Autologging):某些命名风格支持自动记录,这是一个在训练过程完成后自动记录模型 artifact 和训练元数据的特性。
命名风格的特征
命名风格封装了几种功能
-
统一 API:尽管底层机器学习框架存在差异,命名风格提供了用于模型保存、加载和记录的一致方法集。这种一致性扩展到高级功能,例如签名声明、输入示例存储、自定义依赖项和模型注册。
-
维护和可靠性:作为 MLflow 项目的一部分,命名风格经过核心维护者的严格测试和更新。
-
序列化方法:每种命名风格都利用了与其关联框架相关的原生序列化机制。
-
自定义 Python 函数包装器:每种风格都包含一个特定的实现,将底层框架的方法映射到标准的 Python 函数,并对函数行为做出某些决策。
-
简化的高级 API:尽管它们能够处理复杂的细节,但命名风格的高级 API 设计简单易用。
纳入命名风格的标准
将框架纳入 MLflow 作为命名风格并非任意决定。
标准包括
-
流行度和需求:在行业中广泛采用的框架受到青睐。纳入也取决于用户请求的频率以及更广泛的机器学习社区中感知到的需求。
-
框架稳定性:命名风格通常与稳定的框架相关联,这些框架具有活跃的维护,并且没有过于复杂或限制性的构建要求,这可能导致集成任务变得不可能完成。
命名风格的结构
MLflow 中的每种命名风格通常都实现了一组核心函数
-
get_default_conda_env():返回风格所需的 conda 依赖项列表。 -
get_default_pip_requirements():列出对风格至关重要的 PyPI 依赖项。 -
load_model():处理反序列化过程,通过提供的可解析model_uri从给定的 artifact 存储中检索模型实例。 -
save_model():管理序列化过程,确保模型、其元数据和其他相关 artifact 被适当存储。 -
log_model():save_model()的扩展版本,除了保存过程外,还促进模型注册。
此外,为了确保风格的模型可以加载为通用 Python 函数,需要一个包装器类以集成 mlflow.pyfunc.load_model()。
解决 MLflow 中不支持的模型
对于不支持作为命名风格的机器学习框架,MLflow 提供了定义自定义 PyFuncs 的灵活性。
本教程将指导您完成该过程,使您能够将几乎任何模型集成到 MLflow 生态系统中。
创建可重用的自定义风格
对于那些经常在各种项目中重复使用特定自定义 PyFuncs 的用户,MLflow 的架构支持通过插件式接口开发自定义风格。虽然关于此主题的全面指南超出了本教程的范围,但一般方法包括创建一个模块,该模块包含用于保存、加载和记录模型类型的函数。然后创建一个 PyFunc 包装器类,以提供将自定义风格作为 PyFunc 加载的集成。