MLflow 模型
一个 MLflow Model 是一种用于打包机器学习模型的标准格式,可被各种下游工具使用——例如,通过 REST API 进行实时服务或在 Apache Spark 上进行批量推断。该格式定义了一种约定,允许你以不同的 "flavors" 保存模型,以便不同的下游工具能够理解。
存储格式
每个 MLflow 模型是一个包含任意文件的目录,在该目录的根目录中有一个 MLmodel 文件,该文件可以定义模型可在多种 flavors 下查看。
MLflow Model 的 模型 方面可以是一个序列化的对象(例如,使用 pickle 序列化的 scikit-learn 模型)或一个 Python 脚本(或 notebook,如果在 Databricks 中运行),其中包含使用 mlflow.models.set_model() API 定义的模型实例。
Flavors 是使 MLflow Models 强大的关键概念:它们是一种约定,部署工具可以用来理解模型,这使得能够编写可以与任何机器学习库的模型一起工作的工具,而无需将每个工具与每个库逐一集成。MLflow 定义了若干“标准”flavors,其所有内置部署工具都支持,例如描述如何将模型作为 Python 函数运行的“Python function” flavor。然而,库也可以定义并使用其他 flavors。例如,MLflow 的 mlflow.sklearn 库允许将模型加载回 scikit-learn 的 Pipeline 对象,以便在意识到 scikit-learn 的代码中使用,或者作为通用的 Python 函数供仅需应用模型的工具使用(例如,用于将模型部署到 Amazon SageMaker 的 mlflow deployments 工具,选项为 -t sagemaker)。
MLmodel 文件
特定模型支持的所有 flavors 都在其 MLmodel 文件中以 YAML 格式定义。例如,在 MLflow repo 内运行 python examples/sklearn_logistic_regression/train.py 将会在 model 目录下创建以下文件:
# Directory written by mlflow.sklearn.save_model(model, "model", input_example=...)
model/
├── MLmodel
├── model.pkl
├── conda.yaml
├── python_env.yaml
├── requirements.txt
├── input_example.json (optional, only logged when input example is provided and valid during model logging)
├── serving_input_example.json (optional, only logged when input example is provided and valid during model logging)
└── environment_variables.txt (optional, only logged when environment variables are used during model inference)
并且其 MLmodel 文件描述了两种形式:
time_created: 2018-05-25T17:28:53.35
flavors:
sklearn:
sklearn_version: 0.19.1
pickled_model: model.pkl
python_function:
loader_module: mlflow.sklearn
除了一个用于列出模型 flavors 的字段之外,MLmodel YAML 格式可以包含以下字段:
time_created: 模型创建的日期和时间,采用 UTC ISO 8601 格式。run_id: 创建该模型的运行的 ID,如果该模型是使用 tracking 保存的。signature: model signature 以 JSON 格式.input_example: 引用带有 input example 的工件。databricks_runtime: Databricks 运行时版本和类型,如果模型是在 Databricks 笔记本或作业中训练的。mlflow_version: 用于记录模型时所使用的 MLflow 的版本。
附加的已记录文件
为了重现环境,我们在记录模型时会自动记录 conda.yaml、python_env.yaml 和 requirements.txt 文件。这些文件随后可以用于使用 conda 或 virtualenv 配合 pip 重新安装依赖。请参见 How MLflow Model Records Dependencies 以获取有关这些文件的更多详细信息。
如果在记录模型时提供了模型输入示例,会记录另外两个文件 input_example.json 和 serving_input_example.json。有关更多详情,请参阅 Model Input Example。
在记录模型时,模型元数据文件(MLmodel、conda.yaml、python_env.yaml、requirements.txt)会被复制到名为 metadata 的子目录中。对于以 wheel 形式的模型,original_requirements.txt 文件也会被复制。
当从 MLflow Model Registry 下载已注册的模型时,一个名为 registered_model_meta 的 YAML 文件会被添加到下载方的模型目录中。该文件包含 MLflow Model Registry 中引用的模型名称和版本,并将在部署等场景中使用。
如果你在 Databricks 中记录模型,MLflow 也会在模型目录中创建一个 metadata 子目录。该子目录包含前面提到的用于内部使用的元数据文件的精简副本。
环境变量文件
在记录模型时,MLflow 会将模型推理期间使用的环境变量记录在 environment_variables.txt 文件中。
environment_variables.txt 文件 仅包含在模型推理期间使用的环境变量的名称,不存储其值。
目前 MLflow 仅记录其名称包含以下任一关键字的环境变量:
RECORD_ENV_VAR_ALLOWLIST = {
# api key related
"API_KEY", # e.g. OPENAI_API_KEY
"API_TOKEN",
# databricks auth related
"DATABRICKS_HOST",
"DATABRICKS_USERNAME",
"DATABRICKS_PASSWORD",
"DATABRICKS_TOKEN",
"DATABRICKS_INSECURE",
"DATABRICKS_CLIENT_ID",
"DATABRICKS_CLIENT_SECRET",
"_DATABRICKS_WORKSPACE_HOST",
"_DATABRICKS_WORKSPACE_ID",
}
使用环境变量的 pyfunc 模型示例:
import mlflow
import os
os.environ["TEST_API_KEY"] = "test_api_key"
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input, params=None):
if os.environ.get("TEST_API_KEY"):
return model_input
raise Exception("API key not found")
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
name="model", python_model=MyModel(), input_example="data"
)
环境变量 TEST_API_KEY 会被记录在 environment_variables.txt 文件中,如下所示
# This file records environment variable names that are used during model inference.
# They might need to be set when creating a serving endpoint from this model.
# Note: it is not guaranteed that all environment variables listed here are required
TEST_API_KEY
在将模型部署到 serving 端点之前,请检查 environment_variables.txt 文件,以确保为模型推理设置了所有必要的环境变量。请注意,文件中列出的环境变量并非在模型推理时全部都必须。 有关在 databricks serving 端点上设置环境变量的详细说明,请参阅 this guidance。
要禁用此功能,请将环境变量 MLFLOW_RECORD_ENV_VARS_IN_MODEL_LOGGING 设置为 false。
管理模型依赖
MLflow Model 会推断模型风格所需的依赖并自动记录它们。不过,它也允许您定义额外的依赖或自定义的 Python 代码,并提供一个工具在沙箱环境中验证它们。有关更多详情,请参阅 Managing Dependencies in MLflow Models。
模型签名和输入示例
在 MLflow 中,理解模型签名和输入示例的细节对于有效的模型管理和部署至关重要。
- 模型签名: 定义了模型输入、输出和额外推理参数的模式,促进模型交互的标准化接口。
- 模型输入示例:提供了一个有效模型输入的具体实例,帮助理解和测试模型的要求。此外,如果在记录模型时提供了输入示例,而未显式提供模型签名,则会自动推断并存储模型签名。
- 模型服务有效负载示例: 提供用于查询已部署模型端点的 json 有效负载示例。
如果在记录模型时提供了输入示例,会自动从该输入示例生成一个服务有效负载示例
并保存为
serving_input_example.json.
我们的文档深入探讨了几个关键领域:
- 支持的签名类型: 我们涵盖支持的不同数据类型,例如用于传统机器学习模型的表格数据和用于深度学习模型的张量。
- 签名强制: 讨论 MLflow 如何强制模式合规,确保提供的输入符合模型的期望。
- 使用签名记录模型:指导如何在记录模型时包含签名,增强模型操作的清晰度和可靠性。
要详细探索这些概念,包括示例和最佳实践,请访问 Model Signatures and Examples Guide。如果您想查看签名强制执行的实际效果, 请参阅 notebook tutorial on Model Signatures 以了解更多。
模型 API
您可以通过多种方式保存和加载 MLflow 模型。首先,MLflow 与若干常用库集成。例如,mlflow.sklearn 包含用于 scikit-learn 模型的 save_model、log_model 和 load_model 函数。其次,您可以使用 mlflow.models.Model 类来创建和写入模型。该类有四个关键函数:
- add_flavor 用于向模型添加一个 flavor。每个 flavor 具有一个字符串名称和一个键值属性字典,其中的值可以是任何可以序列化为 YAML 的对象。
- save 用于将模型保存到本地目录。
- log 使用 MLflow Tracking 将模型作为当前运行的工件(artifact)记录。
- load 从本地目录或从先前运行的 artifact 中加载模型。
来自代码的模型
要 了解有关“Models From Code”功能的更多信息,请访问 the deep dive guide 以获取更深入的说明并查看更多示例。
Models from Code 功能在 MLflow 版本 2.12.2 及更高版本中可用。此功能为实验性功能,未来版本可能会发生变化。
Models from Code 功能允许你直接从独立的 python 脚本中定义并记录模型。该功能在你需要记录那些可以以代码表示并有效存储的模型(即不需要通过训练获得优化权重的模型)或依赖外部服务的应用(例如,LangChain chains)时特别有用。另一个好处是,这种方法完全绕过了在 Python 中使用 pickle 或 cloudpickle 模块,而这些模块在加载不受信任的模型时可能带来安全风险。
此功能仅支持 LangChain、LlamaIndex 和 PythonModel 模型。
为了从代码中记录模型,您可以使用 mlflow.models.set_model() API。该 API 允许您在定义模型的同一文件中,通过指定模型类的一个实例来定义模型。当记录此类模型时,指定的是一个文件路径(而不是对象),该路径指向包含模型类定义以及在自定义模型实例上应用 set_model API 用法的 Python 文件。
下图比较了标准模型记录过程与“Models from Code”功能,针对可使用该功能保存的模型:

例如,在一个名为 my_model.py 的单独文件中定义模型:
import mlflow
from mlflow.models import set_model
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input):
return model_input
# Define the custom PythonModel instance that will be used for inference
set_model(MyModel())
“Models from code” 功能不支持捕获来自外部文件引用的 import 语句。如果你有通过 pip 安装未捕获的依赖项,则需要将依赖项包含在内,并通过使用 code_paths feature 的适当绝对路径导入引用来解析。
为简单起见,鉴于 code_paths 依赖路径解析的限制,建议将定义自代码的模型所需的所有本地依赖项封装在同一个 python 脚本文件中。
当从代码定义模型并使用 mlflow.models.set_model() API 时,被记录的脚本中定义的代码将被在内部执行以确保它是有效的代码。如果您的脚本中有与外部服务的连接(例如您正在通过 LangChain 连接到 GenAI 服务),请注意在模型被记录时会向该服务发起连接请求。
然后,在另一个 python 脚本中从文件路径记录模型:
import mlflow
model_path = "my_model.py"
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
python_model=model_path, # Define the model as the path to the Python file
name="my_model",
)
# Loading the model behaves exactly as if an instance of MyModel had been logged
my_model = mlflow.pyfunc.load_model(model_info.model_uri)
该 mlflow.models.set_model() API 是 非线程安全的。如果您正在从多个线程并发记录模型,请不要尝试使用此功能。该流式 API 使用一个全局的活动模型状态,该状态不保证一致性。如果您需要线程安全的记录 API,请使用 mlflow.client.MlflowClient APIs 来记录模型。
内置模型格式
MLflow 提供了几种标准的模型格式,这些格式可能对你的应用有用。具体来说,它的许多部署工具都支持这些格式,因此你可以将自己的模型导出为其中一种格式,从而受益于所有这些工具:
- Python 函数 (
python_function) - R 函数 (
crate) - H2O (
h2o) - Keras (
keras) - PyTorch (
pytorch) - Scikit-learn (
sklearn) - Spark MLlib (
spark) - TensorFlow (
tensorflow) - ONNX (
onnx) - XGBoost (
xgboost) - LightGBM (
lightgbm) - CatBoost (
catboost) - Spacy(
spaCy) - Statsmodels (
statsmodels) - Prophet (
prophet) - Pmdarima (
pmdarima) - John Snow Labs (
johnsnowlabs) - Diviner (
diviner) - Transformers (
transformers) - SentenceTransformers (
sentence_transformers)
Python 函数 (python_function)
python_function 模型风格作为 MLflow Python 模型的默认模型接口。任何 MLflow Python 模型都应可被以 python_function 模型的形式加载。这样就使得其他 MLflow 工具能够与任何 Python 模型协同工作,而不管用于生成模型的持久化模块或框架是什么。这种互操作性非常强大,因为它允许在各种环境中将任何 Python 模型投入生产。
此外,python_function 模型风格定义了一种通用的文件系统 model format,用于 Python 模型,并提供将模型保存到该格式以及从该格式加载模型的工具。该格式是自包含的,意味着它包含加载和使用模型所需的所有信息。依赖项要么随模型直接存储,要么通过 conda 环境引用。此模型格式允许其他工具将其模型与 MLflow 集成。
如何将模型保存为 Python 函数
大多数 python_function 模型作为其他模型风格的一部分保存 - 例如,所有 mlflow 内置的风格在导出的模型中都包含 python_function 风格。此外,mlflow.pyfunc 模块定义了用于显式创建 python_function 模型的函数。该模块还包括用于创建自定义 Python 模型的实用工具,这是将自定义 python 代码添加到 ML 模型中的一种便捷方式。有关详细信息,请参见 custom Python models documentation。
有关如何从 Python 脚本存储自定义模型(models from code 功能)的信息,请参阅 guide to models from code 以获取推荐的方法。
如何加载并评分 Python 函数模型
加载模型
您可以加载 python_function 模型,在 Python 中通过使用 mlflow.pyfunc.load_model() 函数。需要注意的是,load_model 假定所有依赖项已可用,并且 不会 执行任何依赖项的检查或安装。有关处理依赖项的部署选项,请参阅 model deployment section。
评分模型
一旦模型被加载,就可以通过两种主要方式进行评分:
-
同步评分 标准的评分方法是使用
predict方法,该方法支持多种输入类型并根据输入数据返回标量或集合。方法签名为:predict(data: Union[pandas.Series, pandas.DataFrame, numpy.ndarray, csc_matrix, csr_matrix, List[Any], Dict[str, Any], str],
params: Optional[Dict[str, Any]] = None) → Union[pandas.Series, pandas.DataFrame, numpy.ndarray, list, str] -
同步流式评分
注意predict_stream是 MLflow 在 2.12.2 版本中添加的新接口。早期版本的 MLflow 不支持此接口。 为了在自定义 Python Function 模型中使用predict_stream,必须在模型类中实现predict_stream方法并返回一个 generator 类型。对于支持流式数据处理的模型,predict_stream 方法是可用的。此方法返回一个
generator,该生成器产出一系列响应,允许高效处理大规模数据集或连续数据流。请注意,并非所有模型类型都提供predict_stream方法。使用方法是迭代该生成器以消费响应:predict_stream(data: Any, params: Optional[Dict[str, Any]] = None) → GeneratorType
演示 predict_stream()
下面是一个示例,演示如何使用 predict_stream() 方法定义、保存、加载和使用可流式模型:
import mlflow
import os
# Define a custom model that supports streaming
class StreamableModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input, params=None):
# Regular predict method implementation (optional for this demo)
return "regular-predict-output"
def predict_stream(self, context, model_input, params=None):
# Yielding elements one at a time
for element in ["a", "b", "c", "d", "e"]:
yield element
# Save the model to a directory
tmp_path = "/tmp/test_model"
pyfunc_model_path = os.path.join(tmp_path, "pyfunc_model")
python_model = StreamableModel()
mlflow.pyfunc.save_model(path=pyfunc_model_path, python_model=python_model)
# Load the model
loaded_pyfunc_model = mlflow.pyfunc.load_model(model_uri=pyfunc_model_path)
# Use predict_stream to get a generator
stream_output = loaded_pyfunc_model.predict_stream("single-input")
# Consuming the generator using next
print(next(stream_output)) # Output: 'a'
print(next(stream_output)) # Output: 'b'
# Alternatively, consuming the generator using a for-loop
for response in stream_output:
print(response) # This will print 'c', 'd', 'e'
Python 函数模型接口
所有 PyFunc 模型都支持 pandas.DataFrame 作为输入。除了 pandas.DataFrame 外,DL PyFunc 模型还支持以 numpy.ndarrays 形式的张量输入。要验证某个模型 flavor 是否支持张量输入,请查看该 flavor 的文档。
对于具有基于列的模式的模型,输入通常以 pandas.DataFrame 的形式提供。如果为具有命名列的模式提供了将列名映射到值的字典作为输入,或者对于具有未命名列的模式提供了 python List 或 numpy.ndarray 作为输入,MLflow 会将输入转换为 DataFrame。针对预期数据类型的模式强制与类型转换是在该 DataFrame 上执行的。
对于具有基于张量的 schema 的模型,输入通常以 numpy.ndarray 的形式提供,或者以将张量名称映射到其 np.ndarray 值的字典形式提供。Schema enforcement 会检查所提供输入的形状和类型是否与模型 schema 中指定的形状和类型相符,如果不匹配则抛出错误。
对于未定义 schema 的模型,模型的输入和输出不会发生任何更改。MLflow 将传递模型在不接受所提供输入类型时引发的任何错误。
PyFunc 模型用于预测或推断时所加载的 python 环境可能与模型训练时的环境不同。如果发生环境不匹配,在调用 mlflow.pyfunc.load_model() 时会打印一条警告信息。该警告会标识在训练期间使用的包与当前环境之间存在版本不匹配的包。为了获取模型训练环境的全部依赖,可以调用 mlflow.pyfunc.get_model_dependencies()。此外,如果想在与模型训练相同的环境中运行模型推断,可以在调用 mlflow.pyfunc.spark_udf() 时将 env_manager 参数设置为 "conda"。这将根据 conda.yaml 文件生成环境,确保 python UDF 使用训练期间所用的确切包版本执行。
一些 PyFunc 模型可能接受模型加载配置,该配置控制模型的加载方式以及预测的计算方式。您可以通过检查模型的 flavor 元数据来了解模型支持哪些配置:
model_info = mlflow.models.get_model_info(model_uri)
model_info.flavors[mlflow.pyfunc.FLAVOR_NAME][mlflow.pyfunc.MODEL_CONFIG]
或者,您可以加载 PyFunc 模型并检查 model_config 属性:
pyfunc_model = mlflow.pyfunc.load_model(model_uri)
pyfunc_model.model_config
可以在加载时通过在mlflow.pyfunc.load_model()方法中指定model_config参数来更改模型配置:
pyfunc_model = mlflow.pyfunc.load_model(model_uri, model_config=dict(temperature=0.93))
当模型配置值被更改时,这些值会覆盖模型保存时的配置。为模型指定一个无效的模型配置键会导致该配置被忽略。系统会显示一条警告,说明被忽略的条目。
模型配置 与 在签名中带默认值的参数:当你需要为模型发布者提供一种方式,以改变模型如何被加载到内存中以及如何为所有样本计算预测时,请使用模型配置。例如,像 user_gpu 这样的键。模型使用者在预测时无法更改这些值。请在签名中使用带默认值的参数,以便为用户提供更改每个数据样本的预测计算方式的能力。
R 函数 (crate)
crate 模型风格定义了一种通用的模型格式,用于使用来自carrier包的crate函数将任意 R 预测函数表示为 MLflow 模型。该预测函数应接受一个 dataframe 作为输入,并输出一个 dataframe、向量或包含预测结果的列表。
该 flavor 需要安装 R 才能使用。
crate 使用
对于一个最小的 crate 模型,predict 函数的示例配置如下:
library(mlflow)
library(carrier)
# Load iris dataset
data("iris")
# Learn simple linear regression model
model <- lm(Sepal.Width~Sepal.Length, data = iris)
# Define a crate model
# call package functions with an explicit :: namespace.
crate_model <- crate(
function(new_obs) stats::predict(model, data.frame("Sepal.Length" = new_obs)),
model = model
)
# log the model
model_path <- mlflow_log_model(model = crate_model, artifact_path = "iris_prediction")
# load the logged model and make a prediction
model_uri <- paste0(mlflow_get_run()$artifact_uri, "/iris_prediction")
mlflow_model <- mlflow_load_model(model_uri = model_uri,
flavor = NULL,
client = mlflow_client())
prediction <- mlflow_predict(model = mlflow_model, data = 5)
print(prediction)
H2O (h2o)
该 h2o 模型风格支持记录和加载 H2O 模型。
mlflow.h2o 模块在 Python 中定义了 save_model() 和 log_model() 方法,在 R 中定义了 mlflow_save_model 和 mlflow_log_model,用于以 MLflow Model 格式保存 H2O 模型。这些方法会生成带有 python_function flavor 的 MLflow Models,使您可以通过 mlflow.pyfunc.load_model() 将它们作为通用的 Python 函数加载以进行推理。加载后的 PyFunc 模型仅需 DataFrame 输入即可进行评分。当您使用 mlflow.pyfunc.load_model() 以 h2o flavor 加载 MLflow Models 时,会调用 h2o.init() 方法。因此,加载环境中必须安装正确版本的 h2o(-py)。您可以通过修改持久化 H2O 模型的 YAML 配置文件中的 init 条目来自定义传递给 h2o.init() 的参数:model.h2o/h2o.yaml。
最后,您可以使用 mlflow.h2o.load_model() 方法将带有 h2o flavor 的 MLflow Models 加载为 H2O 模型对象。
欲了解更多信息,请参见 mlflow.h2o。
h2o pyfunc 用法
对于一个最小的 h2o 模型,下面是 pyfunc predict() 方法在分类场景中的示例:
import mlflow
import h2o
h2o.init()
from h2o.estimators.glm import H2OGeneralizedLinearEstimator
# import the prostate data
df = h2o.import_file(
"http://s3.amazonaws.com/h2o-public-test-data/smalldata/prostate/prostate.csv.zip"
)
# convert the columns to factors
df["CAPSULE"] = df["CAPSULE"].asfactor()
df["RACE"] = df["RACE"].asfactor()
df["DCAPS"] = df["DCAPS"].asfactor()
df["DPROS"] = df["DPROS"].asfactor()
# split the data
train, test, valid = df.split_frame(ratios=[0.7, 0.15])
# generate a GLM model
glm_classifier = H2OGeneralizedLinearEstimator(
family="binomial", lambda_=0, alpha=0.5, nfolds=5, compute_p_values=True
)
with mlflow.start_run():
glm_classifier.train(
y="CAPSULE", x=["AGE", "RACE", "VOL", "GLEASON"], training_frame=train
)
metrics = glm_classifier.model_performance()
metrics_to_track = ["MSE", "RMSE", "r2", "logloss"]
metrics_to_log = {
key: value
for key, value in metrics._metric_json.items()
if key in metrics_to_track
}
params = glm_classifier.params
mlflow.log_params(params)
mlflow.log_metrics(metrics_to_log)
model_info = mlflow.h2o.log_model(glm_classifier, name="h2o_model_info")
# load h2o model and make a prediction
h2o_pyfunc = mlflow.pyfunc.load_model(model_uri=model_info.model_uri)
test_df = test.as_data_frame()
predictions = h2o_pyfunc.predict(test_df)
print(predictions)
# it is also possible to load the model and predict using h2o methods on the h2o frame
# h2o_model = mlflow.h2o.load_model(model_info.model_uri)
# predictions = h2o_model.predict(test)
Keras (keras)
有关使用 keras flavor 的完整指南 can be viewed here。
PyTorch (pytorch)
有关使用 pytorch flavor 的完整指南可通过 can be viewed here 查看。
有关更多信息,请参见 mlflow.pytorch.
Scikit-learn (sklearn)
使用 sklearn flavor 的完整指南 can be viewed here.
有关 API 信息,请参见 mlflow.sklearn。
Spark MLlib (spark)
有关使用 spark flavor 的完整指南 can be viewed here。
如需更多信息,请参见 mlflow.spark。
TensorFlow (tensorflow)
有关 tensorflow 集成的完整指南 can be viewed here.
ONNX (onnx)
onnx 模型 flavor 通过 mlflow.onnx.save_model() 和 mlflow.onnx.log_model() 方法,支持以 MLflow 格式记录 ONNX models。这些方法还会向它们产生的 MLflow 模型添加 python_function flavor,从而使这些模型能够通过 mlflow.pyfunc.load_model() 被解释为用于推理的通用 Python 函数。该加载后的 PyFunc 模型既可以使用 DataFrame 输入,也可以使用 numpy 数组输入进行评分。MLflow ONNX 模型的 python_function 表示在评估时使用 ONNX Runtime execution engine。最后,您可以使用 mlflow.onnx.load_model() 方法以原生 ONNX 格式加载带有 onnx flavor 的 MLflow 模型。
如需更多信息,请参见 mlflow.onnx 和 http://onnx.ai/。
默认保存 ONNX 文件的行为是使用 ONNX 保存选项 save_as_external_data=True 以支持模型文件 超过 2GB。对于边缘部署的小模型文件,这可能会导致问题。如果你需要为了此类部署将小模型保存为单个文件,可以在 save_as_external_data=False 参数中在 mlflow.onnx.save_model() 或 mlflow.onnx.log_model() 中设置该参数,以强制将模型序列化为单个小文件。请注意,如果模型超过 2GB,保存为单个文件将无法工作。
ONNX pyfunc 使用示例
对于 ONNX 模型,下面是一个示例配置,使用 pytorch 训练一个示例模型,将其转换为 ONNX,记录到 mlflow,并使用 pyfunc predict() 方法进行预测:
import numpy as np
import mlflow
from mlflow.models import infer_signature
import onnx
import torch
from torch import nn
# define a torch model
net = nn.Linear(6, 1)
loss_function = nn.L1Loss()
optimizer = torch.optim.Adam(net.parameters(), lr=1e-4)
X = torch.randn(6)
y = torch.randn(1)
# run model training
epochs = 5
for epoch in range(epochs):
optimizer.zero_grad()
outputs = net(X)
loss = loss_function(outputs, y)
loss.backward()
optimizer.step()
# convert model to ONNX and load it
torch.onnx.export(net, X, "model.onnx")
onnx_model = onnx.load_model("model.onnx")
# log the model into a mlflow run
with mlflow.start_run():
signature = infer_signature(X.numpy(), net(X).detach().numpy())
model_info = mlflow.onnx.log_model(onnx_model, name="model", signature=signature)
# load the logged model and make a prediction
onnx_pyfunc = mlflow.pyfunc.load_model(model_info.model_uri)
predictions = onnx_pyfunc.predict(X.numpy())
print(predictions)
XGBoost (xgboost)
关于 xgboost 集成的完整指南 can be viewed here.
更多信息,请参见 mlflow.xgboost.
LightGBM (lightgbm)
lightgbm 模型风格通过 mlflow.lightgbm.save_model() 和 mlflow.lightgbm.log_model() 方法支持以 MLflow 格式记录 LightGBM models。
这些方法还会向它们生成的 MLflow 模型添加 python_function flavor,使得这些模型可以通过 mlflow.pyfunc.load_model() 被解释为通用的 Python 函数以用于推理。
你也可以使用 mlflow.lightgbm.load_model() 方法以原生 LightGBM 格式加载具有 lightgbm 模型风格的 MLflow 模型。
请注意,现在支持用于 LightGBM 的 scikit-learn API。如需更多信息,请参见 mlflow.lightgbm。
LightGBM pyfunc 用法
下面的示例
- 从
scikit-learn加载 IRIS 数据集 - 训练一个 LightGBM 的
LGBMClassifier - 使用
mlflow记录模型和特征重要性 - 加载已记录的模型并进行预测
from lightgbm import LGBMClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import mlflow
from mlflow.models import infer_signature
data = load_iris()
# Remove special characters from feature names to be able to use them as keys for mlflow metrics
feature_names = [
name.replace(" ", "_").replace("(", "").replace(")", "")
for name in data["feature_names"]
]
X_train, X_test, y_train, y_test = train_test_split(
data["data"], data["target"], test_size=0.2
)
# create model instance
lgb_classifier = LGBMClassifier(
n_estimators=10,
max_depth=3,
learning_rate=1,
objective="binary:logistic",
random_state=123,
)
# Fit and save model and LGBMClassifier feature importances as mlflow metrics
with mlflow.start_run():
lgb_classifier.fit(X_train, y_train)
feature_importances = dict(zip(feature_names, lgb_classifier.feature_importances_))
feature_importance_metrics = {
f"feature_importance_{feature_name}": imp_value
for feature_name, imp_value in feature_importances.items()
}
mlflow.log_metrics(feature_importance_metrics)
signature = infer_signature(X_train, lgb_classifier.predict(X_train))
model_info = mlflow.lightgbm.log_model(
lgb_classifier, name="iris-classifier", signature=signature
)
# Load saved model and make predictions
lgb_classifier_saved = mlflow.pyfunc.load_model(model_info.model_uri)
y_pred = lgb_classifier_saved.predict(X_test)
print(y_pred)
CatBoost (catboost)
catboost 模型风味使得可以通过 CatBoost models
以 MLflow 格式通过 mlflow.catboost.save_model() 和 mlflow.catboost.log_model() 方法记录。
这些方法还会向它们生成的 MLflow 模型添加 python_function 风味,从而允许通过 mlflow.pyfunc.load_model() 将模型作为通用的 Python 函数来进行推理。
您也可以使用 mlflow.catboost.load_model() 方法以原生 CatBoost 格式加载具有 catboost
模型风味的 MLflow 模型。
有关更多信息,请参见 mlflow.catboost。
CatBoost pyfunc 使用
对于 CatBoost 分类器模型,pyfunc 的 predict() 方法的示例配置如下:
import mlflow
from mlflow.models import infer_signature
from catboost import CatBoostClassifier
from sklearn import datasets
# prepare data
X, y = datasets.load_wine(as_frame=False, return_X_y=True)
# train the model
model = CatBoostClassifier(
iterations=5,
loss_function="MultiClass",
allow_writing_files=False,
)
model.fit(X, y)
# create model signature
predictions = model.predict(X)
signature = infer_signature(X, predictions)
# log the model into a mlflow run
with mlflow.start_run():
model_info = mlflow.catboost.log_model(model, name="model", signature=signature)
# load the logged model and make a prediction
catboost_pyfunc = mlflow.pyfunc.load_model(model_uri=model_info.model_uri)
print(catboost_pyfunc.predict(X[:5]))
Spacy(spaCy)
有关spaCy集成的完整指南 can be viewed here.
Statsmodels (statsmodels)
The statsmodels 模型 flavor 使能够通过 Statsmodels models
in MLflow 格式通过 mlflow.statsmodels.save_model()
和 mlflow.statsmodels.log_model() 方法进行记录。These 方法还会向它们生成的 MLflow 模型添加 python_function
flavor,使这些模型可以通过 mlflow.pyfunc.load_model() 作为通用的 Python
函数进行推理。该加载的 PyFunc 模型只能使用 DataFrame 输入进行评分。您也可以使用 mlflow.statsmodels.load_model()
方法以原生 statsmodels 格式加载具有该 statsmodels 模型 flavor 的 MLflow 模型。
目前,自动记录仅限于对 fit 在 statsmodels 模型上的调用所生成的参数、指标和模型。
Statsmodels pyfunc 用法
以下两个示例演示如何使用一个基本回归模型(OLS)和一个 ARIMA 时间序列模型,来自以下 statsmodels API:statsmodels.formula.api 和 statsmodels.tsa.api
对于一个最小的 statsmodels 回归模型,这里是 pyfunc predict() 方法的一个示例:
import mlflow
import pandas as pd
from sklearn.datasets import load_diabetes
import statsmodels.formula.api as smf
# load the diabetes dataset from sklearn
diabetes = load_diabetes()
# create X and y dataframes for the features and target
X = pd.DataFrame(data=diabetes.data, columns=diabetes.feature_names)
y = pd.DataFrame(data=diabetes.target, columns=["target"])
# concatenate X and y dataframes
df = pd.concat([X, y], axis=1)
# create the linear regression model (ordinary least squares)
model = smf.ols(
formula="target ~ age + sex + bmi + bp + s1 + s2 + s3 + s4 + s5 + s6", data=df
)
mlflow.statsmodels.autolog(
log_models=True,
disable=False,
exclusive=False,
disable_for_unsupported_versions=False,
silent=False,
registered_model_name=None,
)
with mlflow.start_run():
res = model.fit(method="pinv", use_t=True)
model_info = mlflow.statsmodels.log_model(res, name="OLS_model")
# load the pyfunc model
statsmodels_pyfunc = mlflow.pyfunc.load_model(model_uri=model_info.model_uri)
# generate predictions
predictions = statsmodels_pyfunc.predict(X)
print(predictions)
对于一个最简的时间序列 ARIMA 模型,下面是 pyfunc predict() 方法的示例:
import mlflow
import numpy as np
import pandas as pd
from statsmodels.tsa.arima.model import ARIMA
# create a time series dataset with seasonality
np.random.seed(0)
# generate a time index with a daily frequency
dates = pd.date_range(start="2022-12-01", end="2023-12-01", freq="D")
# generate the seasonal component (weekly)
seasonality = np.sin(np.arange(len(dates)) * (2 * np.pi / 365.25) * 7)
# generate the trend component
trend = np.linspace(-5, 5, len(dates)) + 2 * np.sin(
np.arange(len(dates)) * (2 * np.pi / 365.25) * 0.1
)
# generate the residual component
residuals = np.random.normal(0, 1, len(dates))
# generate the final time series by adding the components
time_series = seasonality + trend + residuals
# create a dataframe from the time series
data = pd.DataFrame({"date": dates, "value": time_series})
data.set_index("date", inplace=True)
order = (1, 0, 0)
# create the ARIMA model
model = ARIMA(data, order=order)
mlflow.statsmodels.autolog(
log_models=True,
disable=False,
exclusive=False,
disable_for_unsupported_versions=False,
silent=False,
registered_model_name=None,
)
with mlflow.start_run():
res = model.fit()
mlflow.log_params(
{
"order": order,
"trend": model.trend,
"seasonal_order": model.seasonal_order,
}
)
mlflow.log_params(res.params)
mlflow.log_metric("aic", res.aic)
mlflow.log_metric("bic", res.bic)
model_info = mlflow.statsmodels.log_model(res, name="ARIMA_model")
# load the pyfunc model
statsmodels_pyfunc = mlflow.pyfunc.load_model(model_uri=model_info.model_uri)
# prediction dataframes for a TimeSeriesModel must have exactly one row and include columns called start and end
start = pd.to_datetime("2024-01-01")
end = pd.to_datetime("2024-01-07")
# generate predictions
prediction_data = pd.DataFrame({"start": start, "end": end}, index=[0])
predictions = statsmodels_pyfunc.predict(prediction_data)
print(predictions)
如需更多信息,请参见 mlflow.statsmodels.
Prophet (prophet)
有关 prophet 集成的完整指南 can be viewed here。
有关更多信息,请参阅 mlflow.prophet。
Pmdarima (pmdarima)
The pmdarima 模型风味允许通过 pmdarima models 以 MLflow 格式通过 mlflow.pmdarima.save_model() 和 mlflow.pmdarima.log_model() 方法进行记录。
这些方法还会向它们生成的 MLflow Models 添加 python_function 风味,从而允许通过 mlflow.pyfunc.load_model() 将模型解释为用于推理的通用 Python 函数。
该加载的 PyFunc 模型只能使用 DataFrame 输入进行评分。
您也可以使用 mlflow.pmdarima.load_model() 方法以原生 pmdarima 格式加载带有 pmdarima 模型风味的 MLflow Models。
用于利用作为 pyfunc 类型加载的 pmdarima 模型生成预测的接口,使用一个单行 Pandas DataFrame 作为配置参数。在此配置的 Pandas DataFrame 中支持以下列:
n_periods(必需) - 指定要生成的未来周期数,从训练数据集的最后一个日期时间值开始,使用模型训练时输入训练序列的频率(例如,如果训练数据序列的元素表示每小时一个值,为了预测未来 3 天的数据,将列n_periods设置为72)X(可选) - 外生回归变量的值 (仅在 pmdarima 版本 >= 1.8.0 中支持),用于未来时间段事件的二维数组。更多信息,请阅读底层库的explanation。return_conf_int(可选) - 一个布尔值(默认:False),用于指示是否返回置信区间值。 参见上文说明。alpha(可选) - 用于计算置信区间的显著性水平。 (默认:0.05)
下面示例配置用于pyfunc predict 的pmdarima模型,未来周期预测数量为100,包含置信区间计算,不含外生回归变量元素,且默认alpha为0.05:
| 索引 | n-periods | return_conf_int |
|---|---|---|
| 0 | 100 | 是 |
传递给 pmdarima pyfunc flavor 的 Pandas DataFrame 必须只包含 1 行。
在预测 pmdarima 类型时,predict 方法的 DataFrame 配置列 return_conf_int 的值控制输出格式。当该列的值设置为 False 或 None(如果在配置 DataFrame 中未提供该列,则默认为此),返回的 Pandas DataFrame 的架构为单列:["yhat"]。当设置为 True 时,返回的 DataFrame 的架构为:["yhat", "yhat_lower", "yhat_upper"],其中分别将下限(yhat_lower)和上限(yhat_upper)置信区间添加到预测值(yhat)中。
例如,作为 pyfunc 加载的 pmdarima 工件的示例用法(含置信区间计算):
import pmdarima
import mlflow
import pandas as pd
data = pmdarima.datasets.load_airpassengers()
with mlflow.start_run():
model = pmdarima.auto_arima(data, seasonal=True)
mlflow.pmdarima.save_model(model, "/tmp/model.pmd")
loaded_pyfunc = mlflow.pyfunc.load_model("/tmp/model.pmd")
prediction_conf = pd.DataFrame(
[{"n_periods": 4, "return_conf_int": True, "alpha": 0.1}]
)
predictions = loaded_pyfunc.predict(prediction_conf)
输出 (Pandas DataFrame):
| 索引 | yhat | yhat_lower | yhat_upper |
|---|---|---|---|
| 0 | 467.573731 | 423.30995 | 511.83751 |
| 1 | 490.494467 | 416.17449 | 564.81444 |
| 2 | 509.138684 | 420.56255 | 597.71117 |
| 3 | 492.554714 | 397.30634 | 587.80309 |
如果从非 pyfunc 工件将 return_conf_int 设置为 True,则对 pmdarima 的签名记录将无法正常工作。原生 ARIMA.predict() 在返回置信区间时的输出不是被识别的签名类型。
约翰·斯诺 实验室 (johnsnowlabs)
johnsnowlabs 模型类型可让您访问
20.000+ state-of-the-art enterprise NLP models in 200+ languages
,用于医疗、金融、法律以及更多领域。
您可以使用 mlflow.johnsnowlabs.log_model() 将您的模型记录并导出为
这使您能够将 any John Snow Labs model
集成到 MLflow 框架中。您可以使用 MLflow 的 serve 功能轻松部署模型以进行推理。
模型通过 mlflow.pyfunc.load_model() 被解释为用于推理的通用 Python 函数。
您也可以使用 mlflow.johnsnowlabs.load_model() 函数,从存储的工件(artifact)中加载具有 johnsnowlabs flavor 的已保存或已记录的 MLflow Model。
功能包括:LLM(大型语言模型)、文本摘要、问答、命名实体识别、关系抽取、情感分析、拼写检查、图像分类、自动语音识别等更多功能,由最新的 Transformer 架构提供支持。模型由 John Snow Labs 提供,并需要 John Snow Labs Enterprise NLP 许可证。You can reach out to us 获取研究或行业许可证。
示例:将 John Snow Labs 导出为 MLflow 格式
import json
import os
import pandas as pd
from johnsnowlabs import nlp
import mlflow
from mlflow.pyfunc import spark_udf
# 1) Write your raw license.json string into the 'JOHNSNOWLABS_LICENSE_JSON' env variable for MLflow
creds = {
"AWS_ACCESS_KEY_ID": "...",
"AWS_SECRET_ACCESS_KEY": "...",
"SPARK_NLP_LICENSE": "...",
"SECRET": "...",
}
os.environ["JOHNSNOWLABS_LICENSE_JSON"] = json.dumps(creds)
# 2) Install enterprise libraries
nlp.install()
# 3) Start a Spark session with enterprise libraries
spark = nlp.start()
# 4) Load a model and test it
nlu_model = "en.classify.bert_sequence.covid_sentiment"
model_save_path = "my_model"
johnsnowlabs_model = nlp.load(nlu_model)
johnsnowlabs_model.predict(["I hate COVID,", "I love COVID"])
# 5) Export model with pyfunc and johnsnowlabs flavors
with mlflow.start_run():
model_info = mlflow.johnsnowlabs.log_model(johnsnowlabs_model, name=model_save_path)
# 6) Load model with johnsnowlabs flavor
mlflow.johnsnowlabs.load_model(model_info.model_uri)
# 7) Load model with pyfunc flavor
mlflow.pyfunc.load_model(model_save_path)
pandas_df = pd.DataFrame({"text": ["Hello World"]})
spark_df = spark.createDataFrame(pandas_df).coalesce(1)
pyfunc_udf = spark_udf(
spark=spark,
model_uri=model_save_path,
env_manager="virtualenv",
result_type="string",
)
new_df = spark_df.withColumn("prediction", pyfunc_udf(*pandas_df.columns))
# 9) You can now use the mlflow models serve command to serve the model see next section
# 10) You can also use x command to deploy model inside of a container see next section
将 John Snow Labs 模型部署为容器
- 启动 Docker 容器
docker run -p 5001:8080 -e JOHNSNOWLABS_LICENSE_JSON=your_json_string "mlflow-pyfunc"
- 查询服务器
curl http://127.0.0.1:5001/invocations -H 'Content-Type: application/json' -d '{
"dataframe_split": {
"columns": ["text"],
"data": [["I hate covid"], ["I love covid"]]
}
}'
在不使用容器的情况下部署 John Snow Labs 模型
- 导出环境变量并启动服务器
export JOHNSNOWLABS_LICENSE_JSON=your_json_string
mlflow models serve -m <model_uri>
- 查询服务器
curl http://127.0.0.1:5000/invocations -H 'Content-Type: application/json' -d '{
"dataframe_split": {
"columns": ["text"],
"data": [["I hate covid"], ["I love covid"]]
}
}'
Diviner (diviner)
diviner 模型风格使得可以记录 diviner models,以 MLflow 格式通过 mlflow.diviner.save_model() 和 mlflow.diviner.log_model() 方法。 这些方法还会向它们生成的 MLflow 模型中添加 python_function 风格,从而允许通过 mlflow.pyfunc.load_model() 将模型解释为用于推理的通用 Python 函数。 该加载的 PyFunc 模型只能使用 DataFrame 输入进行评分。 您还可以使用 mlflow.diviner.load_model() 方法以原生 diviner 格式加载具有 diviner 模型风格的 MLflow 模型。
Diviner 类型
Diviner 是一个库,提供了一个协调框架,用于对一组相关序列执行时间序列预测。diviner中的预测是通过封装流行的开源库(例如prophet和pmdarima)来完成的。diviner库提供了一组简化的 APIs,以使用单个输入 DataFrame 和统一的高级 API 同时为多个数据分组生成各自的时间序列预测。
为 Diviner 记录指标和参数
与 MLflow 支持的其他变体不同,Diviner 有分组模型的概念。作为许多(可能有数千个)单独预测模型的集合,为每个模型向跟踪服务器记录单独的指标和参数所带来的负担是巨大的。因此,指标和参数通过 Diviner 的 APIs 以 Pandas DataFrames 的形式暴露以便检索,而不是作为离散的原始值。
为说明问题,假设我们正在预测世界各地主要城市的每小时用电量。我们的输入数据示例如下:
| 国家 | 城市 | 日期时间 | 瓦特 |
|---|---|---|---|
| 美国 | 纽约 | 2022-03-01 00:01:00 | 23568.9 |
| 美国 | 纽约 | 2022-03-01 00:02:00 | 22331.7 |
| 美国 | 波士顿 | 2022-03-01 00:01:00 | 14220.1 |
| 美国 | 波士顿 | 2022-03-01 00:02:00 | 14183.4 |
| 加拿大 | 多伦多 | 2022-03-01 00:01:00 | 18562.2 |
| 加拿大 | 多伦多 | 2022-03-01 00:02:00 | 17681.6 |
| MX | 墨西哥城 | 2022-03-01 00:01:00 | 19946.8 |
| MX | 墨西哥城 | 2022-03-01 00:02:00 | 19444.0 |
如果我们要在这些数据上对模型进行fit,并将分组键指定为:
grouping_keys = ["country", "city"]
我们将为提供的每个分组键生成一个模型:
[("US", "NewYork"), ("US", "Boston"), ("CA", "Toronto"), ("MX", "MexicoCity")]
如果为其中每一个都构建了一个模型,向每个模型输入它们的指标和参数对 MLflow 跟踪服务器来说不会构成问题。然而,问题会出现在如果我们为全球每一个主要城市都建模并每天运行这个预测场景时。如果我们遵循世界银行的条件,截至2022年那将意味着略多于10,000个模型。仅仅在每天运行这个预测几周之后,我们就会拥有一个非常大的指标表。
为了消除大规模预测中的此问题,针对 diviner 的指标和参数被提取为一个以分组键为索引的 Pandas DataFrame,例如如下所示(为便于查看,浮点值已截断):
| 分组键列 | 国家 | 城市 | MSE(均方误差) | RMSE(均方根误差) | MAE(平均绝对误差) | MAPE(平均绝对百分比误差) | MDAPE(中位绝对百分比误差) | SMAPE(对称平均绝对百分比误差) |
|---|---|---|---|---|---|---|---|---|
| ("国家", "城市") | CA | 多伦多 | 8276851.6 | 2801.7 | 2417.7 | 0.16 | 0.16 | 0.159 |
| ("国家", "城市") | MX | 墨西哥城 | 3548872.4 | 1833.8 | 1584.5 | 0.15 | 0.16 | 0.159 |
| ("国家", "城市") | 美国 | 纽约 | 3167846.4 | 1732.4 | 1498.2 | 0.15 | 0.16 | 0.158 |
| ("国家", "城市") | 美国 | 波士顿 | 14082666.4 | 3653.2 | 3156.2 | 0.15 | 0.16 | 0.159 |
有两种推荐的方法来记录来自 diviner 模型的指标和参数:
- 将 DataFrames 写入本地存储并使用
mlflow.log_artifacts()
import os
import mlflow
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
params = model.extract_model_params()
metrics = model.cross_validate_and_score(
horizon="72 hours",
period="240 hours",
initial="480 hours",
parallel="threads",
rolling_window=0.1,
monthly=False,
)
params.to_csv(f"{tmpdir}/params.csv", index=False, header=True)
metrics.to_csv(f"{tmpdir}/metrics.csv", index=False, header=True)
mlflow.log_artifacts(tmpdir, artifact_path="data")
- 直接将 JSON 工件写入,使用
mlflow.log_dict()
从 diviner 模型提取的参数在使用 pd.DataFrame.to_dict() 方法时 可能需要 进行类型转换(或删除列),因为该方法无法序列化对象。
import mlflow
params = model.extract_model_params()
metrics = model.cross_validate_and_score(
horizon="72 hours",
period="240 hours",
initial="480 hours",
parallel="threads",
rolling_window=0.1,
monthly=False,
)
params["t_scale"] = params["t_scale"].astype(str)
params["start"] = params["start"].astype(str)
params = params.drop("stan_backend", axis=1)
mlflow.log_dict(params.to_dict(), "params.json")
mlflow.log_dict(metrics.to_dict(), "metrics.json")
模型工件的日志记录显示在下面的 pyfunc 示例中。
Diviner pyfunc 用法
MLflow Diviner flavor 包含对 Diviner 模型的 pyfunc 接口的实现。要控制预测行为,可以在传入的 Pandas DataFrame 的第一行中指定配置参数。
由于此配置取决于底层模型类型(即 diviner.GroupedProphet.forecast() 方法的签名与 diviner.GroupedPmdarima.predict() 不同),Diviner 的 pyfunc 实现会尝试将参数强制转换为底层模型所期望的类型。
Diviner 模型同时支持“full group”和“partial group”预测。如果在提交给 pyfunc flavor 的配置 DataFrame 中存在名为 "groups" 的列,第一行中的分组键值将被用于生成一部分预测结果。此功能在仅需要少数(或一个)组的结果时,避免了必须从所有组预测的完整输出中过滤子集的操作。
对于一个 GroupedPmdarima 模型,pyfunc 的 predict() 方法的示例配置为:
import mlflow
import pandas as pd
from pmdarima.arima.auto import AutoARIMA
from diviner import GroupedPmdarima
with mlflow.start_run():
base_model = AutoARIMA(out_of_sample_size=96, maxiter=200)
model = GroupedPmdarima(model_template=base_model).fit(
df=df,
group_key_columns=["country", "city"],
y_col="watts",
datetime_col="datetime",
silence_warnings=True,
)
mlflow.diviner.save_model(diviner_model=model, path="/tmp/diviner_model")
diviner_pyfunc = mlflow.pyfunc.load_model(model_uri="/tmp/diviner_model")
predict_conf = pd.DataFrame(
{
"n_periods": 120,
"groups": [
("US", "NewYork"),
("CA", "Toronto"),
("MX", "MexicoCity"),
], # NB: List of tuples required.
"predict_col": "wattage_forecast",
"alpha": 0.1,
"return_conf_int": True,
"on_error": "warn",
},
index=[0],
)
subset_forecasts = diviner_pyfunc.predict(predict_conf)
在若干情况下,提交给 pyfunc predict() 方法的配置 DataFrame 会导致引发 MlflowException:
- 如果既未提供
horizon也未提供n_periods。 n_periods或horizon的值不是整数。- 如果模型的类型是
GroupedProphet,则必须提供作为字符串类型的frequency。 - 如果同时提供了
horizon和n_periods且它们的值不同。
Transformers (transformers)
完整指南,包括教程和有关使用 transformers 集成的详细文档,can be found here。
SentenceTransformers (sentence_transformers)
有关 sentence-transformers 集成的完整指南 can be viewed here。
模型评估
MLflow 的评估文档已被迁移,可在 found here 找到。
模型自定义
虽然 MLflow 内置的模型持久化工具便于将来自各种流行 ML 库的模型以 MLflow Model 格式打包,但它们并不能涵盖所有用例。例如,你可能想使用来自某个 ML 库的模型,而该库并未被 MLflow 内置的 flavors 明确支持。或者,你可能想打包自定义推理代码和数据来创建 MLflow Model。幸运的是,MLflow 提供了两种可用于完成这些任务的解决方案:Custom Python Models 和 Custom Flavors。
在本节:
自定义 Python 模型
mlflow.pyfunc 模块提供了 save_model() 和 log_model() 实用工具,用于创建具有 python_function 类型的 MLflow 模型,这些模型包含用户指定的代码和 artifact(文件)依赖。 这些 artifact 依赖可能包括由任何 Python ML 库生成的序列化模型。
因为这些自定义模型包含 python_function flavor,它们可以部署到 MLflow 支持的任何生产环境,例如 SageMaker、AzureML 或本地 REST 端点。
下面的示例演示如何使用 mlflow.pyfunc 模块来创建自定义 Python 模型。有关使用 MLflow 的 python_function 实用程序进行模型自定义的更多信息,请参阅 python_function custom models documentation。
示例:使用类型提示创建模型
此示例演示如何创建带有类型提示的自定义 Python 模型,使 MLflow 能够根据为模型输入指定的类型提示执行数据验证。有关 PythonModel 类型提示支持的更多信息,请参阅 PythonModel Type Hints Guide。
PythonModel 带有类型提示自 MLflow 2.20.0 版本起支持数据验证。
import pydantic
import mlflow
from mlflow.pyfunc import PythonModel
# Define the pydantic model input
class Message(pydantic.BaseModel):
role: str
content: str
class CustomModel(PythonModel):
# Define the model_input type hint
# NB: it must be list[...], check the python model type hints guide for more information
def predict(self, model_input: list[Message], params=None) -> list[str]:
return [m.content for m in model_input]
# Construct the model and test
model = CustomModel()
# The input_example can be a list of Message objects as defined in the type hint
input_example = [
Message(role="system", content="Hello"),
Message(role="user", content="Hi"),
]
assert model.predict(input_example) == ["Hello", "Hi"]
# The input example can also be a list of dictionaries that match the Message schema
input_example = [
{"role": "system", "content": "Hello"},
{"role": "user", "content": "Hi"},
]
assert model.predict(input_example) == ["Hello", "Hi"]
# Log the model
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
name="model",
python_model=model,
input_example=input_example,
)
# Load the model as pyfunc
pyfunc_model = mlflow.pyfunc.load_model(model_info.model_uri)
assert pyfunc_model.predict(input_example) == ["Hello", "Hi"]
示例:创建自定义 "add n" 模型
此示例定义了一个自定义模型的类,该模型会将指定的数值,n,加到输入的所有 Pandas DataFrame 列上。然后,它使用 mlflow.pyfunc APIs 将该模型的一个实例以 n = 5 保存为 MLflow Model 格式。最后,它以 python_function 格式加载模型并使用它来评估示例输入。
import mlflow.pyfunc
# Define the model class
class AddN(mlflow.pyfunc.PythonModel):
def __init__(self, n):
self.n = n
def predict(self, context, model_input, params=None):
return model_input.apply(lambda column: column + self.n)
# Construct and save the model
model_path = "add_n_model"
add5_model = AddN(n=5)
mlflow.pyfunc.save_model(path=model_path, python_model=add5_model)
# Load the model in `python_function` format
loaded_model = mlflow.pyfunc.load_model(model_path)
# Evaluate the model
import pandas as pd
model_input = pd.DataFrame([range(10)])
model_output = loaded_model.predict(model_input)
assert model_output.equals(pd.DataFrame([range(5, 15)]))
示例:将 XGBoost 模型保存为 MLflow 格式
此示例首先使用 XGBoost
库训练并保存一个梯度提升树模型。接下来,它定义了一个针对 XGBoost 模型的包装类,该包装类符合 MLflow 的 python_function inference API。
然后,它使用该包装类和已保存的 XGBoost 模型来构建一个使用梯度
提升树进行推理的 MLflow Model。最后,它以 python_function 格式加载该 MLflow Model 并使用它来评估测试数据。
# Load training and test datasets
from sys import version_info
import xgboost as xgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
PYTHON_VERSION = f"{version_info.major}.{version_info.minor}.{version_info.micro}"
iris = datasets.load_iris()
x = iris.data[:, 2:]
y = iris.target
x_train, x_test, y_train, _ = train_test_split(x, y, test_size=0.2, random_state=42)
dtrain = xgb.DMatrix(x_train, label=y_train)
# Train and save an XGBoost model
xgb_model = xgb.train(params={"max_depth": 10}, dtrain=dtrain, num_boost_round=10)
xgb_model_path = "xgb_model.pth"
xgb_model.save_model(xgb_model_path)
# Create an `artifacts` dictionary that assigns a unique name to the saved XGBoost model file.
# This dictionary will be passed to `mlflow.pyfunc.save_model`, which will copy the model file
# into the new MLflow Model's directory.
artifacts = {"xgb_model": xgb_model_path}
# Define the model class
import mlflow.pyfunc
class XGBWrapper(mlflow.pyfunc.PythonModel):
def load_context(self, context):
import xgboost as xgb
self.xgb_model = xgb.Booster()
self.xgb_model.load_model(context.artifacts["xgb_model"])
def predict(self, context, model_input, params=None):
input_matrix = xgb.DMatrix(model_input.values)
return self.xgb_model.predict(input_matrix)
# Create a Conda environment for the new MLflow Model that contains all necessary dependencies.
import cloudpickle
conda_env = {
"channels": ["defaults"],
"dependencies": [
f"python={PYTHON_VERSION}",
"pip",
{
"pip": [
f"mlflow=={mlflow.__version__}",
f"xgboost=={xgb.__version__}",
f"cloudpickle=={cloudpickle.__version__}",
],
},
],
"name": "xgb_env",
}
# Save the MLflow Model
mlflow_pyfunc_model_path = "xgb_mlflow_pyfunc"
mlflow.pyfunc.save_model(
path=mlflow_pyfunc_model_path,
python_model=XGBWrapper(),
artifacts=artifacts,
conda_env=conda_env,
)
# Load the model in `python_function` format
loaded_model = mlflow.pyfunc.load_model(mlflow_pyfunc_model_path)
# Evaluate the model
import pandas as pd
test_predictions = loaded_model.predict(pd.DataFrame(x_test))
print(test_predictions)
示例:使用 hf:/ schema 记录 transformers 模型以避免复制大文件
此示例展示如何使用特殊 schema hf:/ 直接从 Hugging Face Hub 记录一个 transformers 模型。 当模型过大且尤其希望直接部署(serve)该模型时,这非常有用;但如果你想在本地下载并测试模型,它并不会节省额外的存储空间。
import mlflow
from mlflow.models import infer_signature
import numpy as np
import transformers
# Define a custom PythonModel
class QAModel(mlflow.pyfunc.PythonModel):
def load_context(self, context):
"""
This method initializes the tokenizer and language model
using the specified snapshot location from model context.
"""
snapshot_location = context.artifacts["bert-tiny-model"]
# Initialize tokenizer and language model
tokenizer = transformers.AutoTokenizer.from_pretrained(snapshot_location)
model = transformers.BertForQuestionAnswering.from_pretrained(snapshot_location)
self.pipeline = transformers.pipeline(
task="question-answering", model=model, tokenizer=tokenizer
)
def predict(self, context, model_input, params=None):
question = model_input["question"][0]
if isinstance(question, np.ndarray):
question = question.item()
ctx = model_input["context"][0]
if isinstance(ctx, np.ndarray):
ctx = ctx.item()
return self.pipeline(question=question, context=ctx)
# Log the model
data = {"question": "Who's house?", "context": "The house is owned by Run."}
pyfunc_artifact_path = "question_answering_model"
with mlflow.start_run() as run:
model_info = mlflow.pyfunc.log_model(
name=pyfunc_artifact_path,
python_model=QAModel(),
artifacts={"bert-tiny-model": "hf:/prajjwal1/bert-tiny"},
input_example=data,
signature=infer_signature(data, ["Run"]),
extra_pip_requirements=["torch", "accelerate", "transformers", "numpy"],
)
自定义 Flavors
要了解如何构建自定义集成并查看社区开发的扩展库支持示例,请查看 Community Model Flavors 页面。
在部署之前验证模型
在使用 MLflow Tracking 记录模型后,强烈建议在将模型部署到生产环境之前在本地对其进行验证。
mlflow.models.predict() API 提供了一种在虚拟环境中测试模型的便捷方法,能够实现隔离执行并具有多种优势:
- 模型依赖验证:该 API 通过在虚拟环境中使用示例输入执行模型,帮助确保存储在模型中记录的依赖项是正确且充分的。有关更多详情,请参见 Validating Environment for Prediction。
- 输入数据验证:该 API 可用于验证输入数据在与模型交互时是否符合预期,通过在模型服务期间模拟相同的数据处理来实现。确保输入数据是一个有效示例,符合 pyfunc 模型的 predict 函数的要求。
- 额外环境变量验证:通过指定
extra_envs参数,您可以测试模型运行是否需要额外的环境变量。注意,os.environ中的所有现有环境变量会自动传入虚拟环境。
import mlflow
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, context, model_input, params=None):
return model_input
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
name="model",
python_model=MyModel(),
input_example=["a", "b", "c"],
)
mlflow.models.predict(
model_uri=model_info.model_uri,
input_data=["a", "b", "c"],
pip_requirements_override=["..."],
extra_envs={"MY_ENV_VAR": "my_value"},
)
如果您的模型依赖(参见模型工件的 requirements.txt)包含 预发布包(例如 mlflow==3.2.0rc0),请通过 extra_envs 字段设置环境变量 UV_PRERELEASE=allow。
mlflow.models.predict(
model_uri=model_info.model_uri,
input_data=["a", "b", "c"],
extra_envs={"UV_PRERELEASE": "allow"},
)
环境管理器
该 mlflow.models.predict() API 支持以下环境管理器来为预测创建虚拟环境:
- virtualenv: 默认的环境管理器。
- uv: 一个用 Rust 编写的 极其快速的 环境管理器。 自 MLflow 2.20.0 起,这是一个实验性功能。
- conda: 使用 conda 来创建环境。
local: 使用当前环境运行模型。注意在此模式下不支持pip_requirements_override。
从 MLflow 2.20.0 开始,uv 可用,且 速度非常快。
运行 pip install uv 来安装 uv,或参阅 uv installation guidance 获取其他安装方法。
使用 uv 创建用于预测的虚拟环境的示例:
import mlflow
mlflow.models.predict(
model_uri="models:/<model_id>",
input_data="your_data",
env_manager="uv",
)
内置部署工具
此信息已移至 MLflow Deployment 页面。
将一个 python_function 模型导出为 Apache Spark UDF
如果您使用的模型推理延迟很长(例如,
transformers 模型),可能会超过默认的 60 秒超时,
您可以在为 MLflow 模型定义 spark_udf 实例时使用 extra_env 参数,指定覆盖环境变量 MLFLOW_SCORING_SERVER_REQUEST_TIMEOUT。
有关进一步的指南,请参见 :py:func:mlflow.pyfunc.spark_udf。
您可以将一个 python_function 模型输出为 Apache Spark 的 UDF,该 UDF 可以上传到 Spark 集群并用于对模型进行评分。
from pyspark.sql.functions import struct
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
pyfunc_udf = mlflow.pyfunc.spark_udf(spark, "<path-to-model>")
df = spark_df.withColumn("prediction", pyfunc_udf(struct([...])))
如果模型包含签名,则可以在不指定列名参数的情况下调用 UDF。在这种情况下,UDF 将使用签名中的列名进行调用,因此用于评估的数据框的列名必须与模型签名的列名相匹配。
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
pyfunc_udf = mlflow.pyfunc.spark_udf(spark, "<path-to-model-with-signature>")
df = spark_df.withColumn("prediction", pyfunc_udf())
如果模型的签名包含 tensor spec 的输入,您需要传入一个数组类型的列作为相应的 UDF 参数。该列中的值必须由一维数组组成。UDF 会以 'C' 顺序将数组值重塑为所需的形状(即使用类似 C 的索引顺序读/写元素),并将值转换为所需的 tensor spec 类型。例如,假设模型要求输入 'a' 的形状为 (-1, 2, 3) 和输入 'b' 的形状为 (-1, 4, 5)。为了对这些数据进行推理,我们需要准备一个 Spark DataFrame,其中列 'a' 包含长度为 6 的数组,列 'b' 包含长度为 20 的数组。然后我们可以像下面的示例代码那样调用 UDF:
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# Assuming the model requires input 'a' of shape (-1, 2, 3) and input 'b' of shape (-1, 4, 5)
model_path = "<path-to-model-requiring-multidimensional-inputs>"
pyfunc_udf = mlflow.pyfunc.spark_udf(spark, model_path)
# The `spark_df` has column 'a' containing arrays of length 6 and
# column 'b' containing arrays of length 20
df = spark_df.withColumn("prediction", pyfunc_udf(struct("a", "b")))
生成的 UDF 基于 Spark's Pandas UDF,目前仅限于为每个观测产生单个值、值数组,或包含相同类型多个字段值的 struct。默认情况下,我们将第一个数值列作为 double 返回。你可以通过提供 result_type 参数来控制返回的结果。支持以下取值:
'int'或 IntegerType:返回最左侧能够容纳在int32中的整数;如果没有,则抛出异常。'long'or LongType: 返回最左侧可以放入int64的 long 整数;如果没有则抛出异常。- ArrayType (IntegerType | LongType): 返回所有可以适应所请求大小的整数列。
'float'or FloatType: 最左侧的数值结果被转换为float32并返回;如果没有数值列则抛出异常。'double'or DoubleType: 返回最左侧的数值结果并转换为double,如果没有数值列则引发异常。- ArrayType ( FloatType | DoubleType ): 返回所有数值列并转换为所请求的类型。如果没有数值列,则会抛出异常。
'string'或 StringType: 结果是将最左边的列转换为字符串。- ArrayType ( StringType ): 返回将所有列转换为字符串。
'bool'或'boolean'或 BooleanType: 返回将最左侧列转换为bool的值,或在值无法强制转换时抛出异常。'field1 FIELD1_TYPE, field2 FIELD2_TYPE, ...':一个结构体类型,包含多个以逗号分隔的字段,每个字段的类型必须是上面列出的类型之一。
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
# Suppose the PyFunc model `predict` method returns a dict like:
# `{'prediction': 1-dim_array, 'probability': 2-dim_array}`
# You can supply result_type to be a struct type containing
# 2 fields 'prediction' and 'probability' like following.
pyfunc_udf = mlflow.pyfunc.spark_udf(
spark, "<path-to-model>", result_type="prediction float, probability: array<float>"
)
df = spark_df.withColumn("prediction", pyfunc_udf())
from pyspark.sql.types import ArrayType, FloatType
from pyspark.sql.functions import struct
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
pyfunc_udf = mlflow.pyfunc.spark_udf(
spark, "path/to/model", result_type=ArrayType(FloatType())
)
# The prediction column will contain all the numeric columns returned by the model as floats
df = spark_df.withColumn("prediction", pyfunc_udf(struct("name", "age")))
如果您想使用 conda 恢复用于训练模型的 Python 环境,请在调用 mlflow.pyfunc.spark_udf() 时设置 env_manager 参数。
from pyspark.sql.types import ArrayType, FloatType
from pyspark.sql.functions import struct
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
pyfunc_udf = mlflow.pyfunc.spark_udf(
spark,
"path/to/model",
result_type=ArrayType(FloatType()),
env_manager="conda", # Use conda to restore the environment used in training
)
df = spark_df.withColumn("prediction", pyfunc_udf(struct("name", "age")))
如果您想在远程客户端通过 Databricks connect 调用 mlflow.pyfunc.spark_udf(),则需要先在 Databricks runtime 中构建模型环境。
from mlflow.pyfunc import build_model_env
# Build the model env and save it as an archive file to the provided UC volume directory
# and print the saved model env archive file path (like '/Volumes/.../.../XXXXX.tar.gz')
print(build_model_env(model_uri, "/Volumes/..."))
# print the cluster id. Databricks Connect client needs to use the cluster id.
print(spark.conf.get("spark.databricks.clusterUsageTags.clusterId"))
一旦你预先构建了模型环境,就可以通过远程客户端的 Databricks connect 使用 mlflow.pyfunc.spark_udf() 并带上 'prebuilt_model_env' 参数,
from databricks.connect import DatabricksSession
spark = DatabricksSession.builder.remote(
host=os.environ["DATABRICKS_HOST"],
token=os.environ["DATABRICKS_TOKEN"],
cluster_id="<cluster id>", # get cluster id by spark.conf.get("spark.databricks.clusterUsageTags.clusterId")
).getOrCreate()
# The path generated by `build_model_env` in Databricks runtime.
model_env_uc_uri = "dbfs:/Volumes/.../.../XXXXX.tar.gz"
pyfunc_udf = mlflow.pyfunc.spark_udf(
spark, model_uri, prebuilt_env_uri=model_env_uc_uri
)
部署到自定义目标
除了内置的部署工具外,MLflow 提供了一个可插拔的 mlflow.deployments()
和 mlflow deployments CLI,用于将模型部署到自定义目标和环境。
要部署到自定义目标,您必须先安装适当的第三方 Python 插件。有关已知的社区维护插件列表,请参见 here。
命令
mlflow deployments CLI 包含以下命令,这些命令也可以使用 mlflow.deployments Python API 以编程方式调用:
- Create: 将 MLflow 模型部署到指定的自定义目标
- Delete: 删除一个部署
- Update: 更新现有的部署,例如部署新模型版本或更改部署的配置(例如增加副本数)
- List: 列出所有部署的 ID
- Get: Print a detailed description of a particular deployment
- Run Local: 在本地部署模型以进行测试
- Help: 显示指定目标的帮助字符串
有关更多信息,请参见:
mlflow deployments --help
mlflow deployments create --help
mlflow deployments delete --help
mlflow deployments update --help
mlflow deployments list --help
mlflow deployments get --help
mlflow deployments run-local --help
mlflow deployments help --help
社区模型类型
前往 Community Model Flavors 页面,查看其他由 MLflow 社区开发和维护的有用 MLflow flavors 的概览。