使用 MLflow 开发 ML 模型并部署到 Kubernetes
本指南演示了如何端到端地使用 MLflow 来
- 使用 MLflow Tracking 训练线性回归模型。
- 进行超参数调整以找到最佳模型。
- 将模型权重和依赖项打包为 MLflow 模型。
- 使用 mlflow models serve 命令,通过 mlserver 在本地测试模型服务。
- 使用 MLflow 通过 KServe 将模型部署到 Kubernetes 集群。
我们将在本教程中介绍端到端的模型开发过程,包括模型训练和测试。如果您已经有一个模型,只想学习如何将其部署到 Kubernetes,您可以跳到 步骤 6 - 在本地测试模型服务。
简介:使用 KServe 和 MLServer 实现可扩展的模型服务
MLflow 提供了一个易于使用的界面,用于在基于 FastAPI 的推理服务器中部署模型。您可以使用 mlflow models build-docker
命令将同一个推理服务器容器化,从而将其部署到 Kubernetes 集群。但是,这种方法可能不具有可扩展性,并且可能不适合生产用例。FastAPI 并非专为高性能和扩展而设计(为什么?),而且手动管理推理服务器的多个实例也非常繁琐。
幸运的是,MLflow 为此提供了一个解决方案。MLflow 提供了一个替代推理引擎,它更适合大规模推理部署,并支持 MLServer,这使得可以一键部署到 Kubernetes 上流行的无服务器模型服务框架,例如 KServe 和 Seldon Core。
什么是 KServe?
KServe,以前称为 KFServing,为常见的机器学习框架(如 Tensorflow、XGBoost、scikit-learn 和 Pytorch)提供了高性能、可扩展且高度抽象的接口。它提供了有助于操作大规模机器学习系统的高级功能,例如 自动缩放、金丝雀发布、A/B 测试、监控、可解释性 等,利用了 Kubernetes 生态系统,包括 KNative 和 Istio。
将 MLflow 与 KServe 结合使用的好处
虽然 KServe 能够实现高度可扩展且可用于生产的模型服务,但将您的模型部署到那里可能需要一些工作。MLflow 简化了使用 KServe 和 MLServer 将模型部署到 Kubernetes 集群的过程。此外,它还提供无缝的 端到端模型管理,作为一个管理整个 ML 生命周期的单一场所。这包括 实验跟踪、模型打包、版本控制、评估 和 部署,我们将在本教程中介绍这些内容。
步骤 1:安装 MLflow 和其他依赖项
首先,请使用以下命令将 mlflow 安装到您的本地计算机
pip install mlflow[mlserver]
[extras]
将安装本教程所需的其他依赖项,包括 mlserver 和 scikit-learn。请注意,部署不需要 scikit-learn,只需要训练本教程中使用的示例模型。
您可以通过运行以下命令来检查 MLflow 是否已正确安装
mlflow --version
步骤 2:设置 Kubernetes 集群
- Kubernetes 集群
- 本地机器模拟
如果您已经可以访问 Kubernetes 集群,则可以通过遵循 官方说明 将 KServe 安装到您的集群。
您可以按照 KServe QuickStart 使用 Kind 设置本地集群并在其上安装 KServe。
现在您已经有一个作为部署目标的 Kubernetes 集群正在运行,让我们继续创建要部署的 MLflow 模型。
步骤 3:训练模型
在本教程中,我们将训练和部署一个简单的回归模型,该模型预测葡萄酒的质量。
让我们从使用默认超参数训练模型开始。在笔记本中或作为 Python 脚本执行以下代码。
为了方便起见,我们使用 mlflow.sklearn.autolog()
函数。此函数允许 MLflow 在训练期间自动记录适当的模型参数和指标集。要了解有关自动日志记录功能的更多信息或如何改为手动记录,请参阅 MLflow Tracking 文档。
import mlflow
import numpy as np
from sklearn import datasets, metrics
from sklearn.linear_model import ElasticNet
from sklearn.model_selection import train_test_split
def eval_metrics(pred, actual):
rmse = np.sqrt(metrics.mean_squared_error(actual, pred))
mae = metrics.mean_absolute_error(actual, pred)
r2 = metrics.r2_score(actual, pred)
return rmse, mae, r2
# Set th experiment name
mlflow.set_experiment("wine-quality")
# Enable auto-logging to MLflow
mlflow.sklearn.autolog()
# Load wine quality dataset
X, y = datasets.load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
# Start a run and train a model
with mlflow.start_run(run_name="default-params"):
lr = ElasticNet()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
metrics = eval_metrics(y_pred, y_test)
现在您已经训练了一个模型,让我们通过 MLflow UI 检查参数和指标是否已正确记录。您可以通过在终端中运行以下命令来启动 MLflow UI
mlflow ui --port 5000
然后访问 https://:5000 以打开 UI。
请打开左侧名为“wine-quality”的实验,然后单击表格中名为“default-params”的运行。在这种情况下,您应该看到包括 alpha
和 l1_ratio
的参数以及 training_score
和 mean_absolute_error_X_test
等指标。
步骤 4:运行超参数调整
现在我们已经建立了一个基线模型,让我们尝试通过调整超参数来提高其性能。我们将进行随机搜索以确定 alpha
和 l1_ratio
的最佳组合。
from scipy.stats import uniform
from sklearn.model_selection import RandomizedSearchCV
lr = ElasticNet()
# Define distribution to pick parameter values from
distributions = dict(
alpha=uniform(loc=0, scale=10), # sample alpha uniformly from [-5.0, 5.0]
l1_ratio=uniform(), # sample l1_ratio uniformlyfrom [0, 1.0]
)
# Initialize random search instance
clf = RandomizedSearchCV(
estimator=lr,
param_distributions=distributions,
# Optimize for mean absolute error
scoring="neg_mean_absolute_error",
# Use 5-fold cross validation
cv=5,
# Try 100 samples. Note that MLflow only logs the top 5 runs.
n_iter=100,
)
# Start a parent run
with mlflow.start_run(run_name="hyperparameter-tuning"):
search = clf.fit(X_train, y_train)
# Evaluate the best model on test dataset
y_pred = clf.best_estimator_.predict(X_test)
rmse, mae, r2 = eval_metrics(clf.best_estimator_, y_pred, y_test)
mlflow.log_metrics(
{
"mean_squared_error_X_test": rmse,
"mean_absolute_error_X_test": mae,
"r2_score_X_test": r2,
}
)
当您重新打开 MLflow UI 时,您应该会注意到运行“hyperparameter-tuning”包含 5 个子运行。MLflow 利用父子关系,这对于对一组运行进行分组特别有用,例如超参数调整中的运行。此处启用了自动日志记录,MLflow 会根据 scoring
指标自动为前 5 个运行创建子运行,在本例中该指标是负均绝对误差。
要比较结果并确定最佳模型,您可以利用 MLflow UI 中的可视化功能。
- 选择第一个作业(“default-params”)和用于超参数调整的父作业(“hyperparameter-turning”)。
- 单击“图表”选项卡以在图表中可视化指标。
- 默认情况下,会显示一些预定义指标集的条形图。
- 您可以添加不同的图表,例如散点图,以比较多个指标。例如,我们可以看到,在测试数据集上的均方误差方面,来自超参数调整的最佳模型优于默认参数模型
您可以通过查看父运行“hyperparameter-tuning”来检查超参数的最佳组合。在此示例中,最佳模型是 alpha=0.11714084185001972
和 l1_ratio=0.3599780644783639
(您可能会看到不同的结果)。
要了解有关使用 MLflow 进行超参数调整的更多信息,请参阅 使用 MLflow 和 Optuna 进行超参数调整。
步骤 5:打包模型和依赖项
由于我们使用的是自动日志记录,因此 MLflow 会自动记录每个运行的 模型。此过程方便地将模型权重和依赖项打包成可部署的格式。
在实践中,还建议使用 MLflow Model Registry 来注册和管理您的模型。
让我们简要了解一下这种格式的外观。您可以通过运行详细信息页面上的“工件”选项卡查看记录的模型。
model
├── MLmodel
├── model.pkl
├── conda.yaml
├── python_env.yaml
└── requirements.txt
model.pkl
是包含序列化模型权重的文件。MLmodel
包括通用元数据,用于指示 MLflow 如何加载模型。其他文件指定了运行模型所需的依赖项。
如果您选择手动日志记录,则需要使用 mlflow.sklearn.log_model
函数显式记录模型,如下所示
mlflow.sklearn.log_model(lr, name="model")
步骤 6:在本地测试模型服务
在部署模型之前,让我们首先测试该模型是否可以在本地提供服务。如 在本地部署 MLflow 模型 中所述,您只需一个命令即可运行本地推理服务器。请记住使用 enable-mlserver
标志,该标志指示 MLflow 使用 MLServer 作为推理服务器。这确保模型以与在 Kubernetes 中相同的方式运行。
mlflow models serve -m models:/<model_id_for_your_best_iteration> -p 1234 --enable-mlserver
此命令启动一个在端口 1234 上侦听的本地服务器。您可以使用 curl
命令向服务器发送请求
$ curl -X POST -H "Content-Type:application/json" --data '{"inputs": [[14.23, 1.71, 2.43, 15.6, 127.0, 2.8, 3.06, 0.28, 2.29, 5.64, 1.04, 3.92, 1065.0]]}' http://127.0.0.1:1234/invocations
{"predictions": [-0.03416275504140387]}
有关请求格式和响应格式的更多信息,请参阅 推理服务器规范。
步骤 7:将模型部署到 KServe
最后,我们都准备好将模型部署到 Kubernetes 集群。
创建命名空间
首先,创建一个测试命名空间来部署 KServe 资源和您的模型
kubectl create namespace mlflow-kserve-test
创建部署配置
创建一个 YAML 文件,描述模型到 KServe 的部署。
有两种方法可以在 KServe 配置文件中指定要部署的模型
- 使用模型构建 Docker 镜像并指定镜像 URI。
- 直接指定模型 URI(仅当您的模型存储在远程存储中时才有效)。
请打开下面的选项卡以获取有关每种方法的详细信息。
- 使用 Docker 镜像
- 使用模型 URI
注册 Docker 帐户
由于 KServe 无法解析本地构建的 Docker 镜像,因此您需要将该镜像推送到 Docker 注册表。在本教程中,我们将把镜像推送到 Docker Hub,但您可以使用任何其他 Docker 注册表,例如 Amazon ECR 或私有注册表。
如果您还没有 Docker Hub 帐户,请在 https://hub.docker.com/signup 上创建一个。
构建 Docker 镜像
使用 mlflow models build-docker
命令构建一个可部署的 Docker 镜像
mlflow models build-docker -m runs:/<run_id_for_your_best_run>/model -n <your_dockerhub_user_name>/mlflow-wine-classifier --enable-mlserver
此命令使用模型和依赖项构建 Docker 镜像,并将其标记为 mlflow-wine-classifier:latest
。
推送 Docker 镜像
构建镜像后,将其推送到 Docker Hub(或使用适当的命令推送到另一个注册表)
docker push <your_dockerhub_user_name>/mlflow-wine-classifier
编写部署配置
然后创建一个如下所示的 YAML 文件
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "mlflow-wine-classifier"
namespace: "mlflow-kserve-test"
spec:
predictor:
containers:
- name: "mlflow-wine-classifier"
image: "<your_docker_user_name>/mlflow-wine-classifier"
ports:
- containerPort: 8080
protocol: TCP
env:
- name: PROTOCOL
value: "v2"
获取远程模型 URI
KServe 配置允许直接指定模型 URI。但是,它不会解析特定于 MLflow 的 URI 模式,例如 runs:/
和 model:/
,也不会解析本地文件 URI,例如 file:///
。我们需要以远程存储 URI 格式(例如 s3://xxx
或 gs://xxx
)指定模型 URI。默认情况下,MLflow 将模型存储在本地文件系统中,因此您需要配置 MLflow 以将模型存储在远程存储中。有关设置说明,请参阅 工件存储。
配置工件存储后,加载并重新记录最佳模型到新的工件存储,或重复模型训练步骤。
创建部署配置
使用远程模型 URI,创建一个 YAML 文件
apiVersion: "serving.kserve.io/v1beta1"
kind: "InferenceService"
metadata:
name: "mlflow-wine-classifier"
namespace: "mlflow-kserve-test"
spec:
predictor:
model:
modelFormat:
name: mlflow
protocolVersion: v2
storageUri: "<your_model_uri>"
部署推理服务
运行以下 kubectl
命令以将新的 InferenceService
部署到您的 Kubernetes 集群
$ kubectl apply -f YOUR_CONFIG_FILE.yaml
inferenceservice.serving.kserve.io/mlflow-wine-classifier created
您可以通过运行以下命令来检查部署状态
$ kubectl get inferenceservice mlflow-wine-classifier
NAME URL READY PREV LATEST PREVROLLEDOUTREVISION LATESTREADYREVISION
mlflow-wine-classifier http://mlflow-wine-classifier.mlflow-kserve-test.local True 100 mlflow-wine-classifier-100
部署状态可能需要几分钟才能准备好。有关详细的部署状态和日志,请运行 kubectl get inferenceservice mlflow-wine-classifier -oyaml
。
测试部署
部署准备就绪后,您可以向服务器发送测试请求。
首先,创建一个包含测试数据的 JSON 文件并将其另存为 test-input.json
。确保请求数据已针对 V2 推理协议 格式化,因为我们使用 protocolVersion: v2
创建了模型。请求应如下所示
{
"inputs": [
{
"name": "input",
"shape": [13],
"datatype": "FP32",
"data": [14.23, 1.71, 2.43, 15.6, 127.0, 2.8, 3.06, 0.28, 2.29, 5.64, 1.04, 3.92, 1065.0]
}
]
}
然后将请求发送到您的推理服务
- Kubernetes 集群
- 本地机器模拟
假设您的集群通过 LoadBalancer 公开,请按照 这些说明 查找 Ingress IP 和端口。然后使用 curl
命令发送测试请求
$ SERVICE_HOSTNAME=$(kubectl get inferenceservice mlflow-wine-classifier -n mlflow-kserve-test -o jsonpath='{.status.url}' | cut -d "/" -f 3)
$ curl -v \
-H "Host: ${SERVICE_HOSTNAME}" \
-H "Content-Type: application/json" \
-d @./test-input.json \
http://${INGRESS_HOST}:${INGRESS_PORT}/v2/models/mlflow-wine-classifier/infer
通常,Kubernetes 集群通过 LoadBalancer 公开服务,但 kind
创建的本地集群没有 LoadBalancer。在这种情况下,您可以通过端口转发访问推理服务。
打开一个新的终端并运行以下命令以转发端口
$ INGRESS_GATEWAY_SERVICE=$(kubectl get svc -n istio-system --selector="app=istio-ingressgateway" -o jsonpath='{.items[0].metadata.name}')
$ kubectl port-forward -n istio-system svc/${INGRESS_GATEWAY_SERVICE} 8080:80
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
然后,在原始终端中,向服务器发送测试请求
$ SERVICE_HOSTNAME=$(kubectl get inferenceservice mlflow-wine-classifier -n mlflow-kserve-test -o jsonpath='{.status.url}' | cut -d "/" -f 3)
$ curl -v \
-H "Host: ${SERVICE_HOSTNAME}" \
-H "Content-Type: application/json" \
-d @./test-input.json \
https://:8080/v2/models/mlflow-wine-classifier/infer
故障排除
如果您在部署过程中遇到任何问题,请咨询 KServe 官方文档 及其 MLflow 部署指南。
结论
恭喜您完成本指南!在本教程中,您学习了如何使用 MLflow 训练模型、运行超参数调整以及将模型部署到 Kubernetes 集群。
进一步阅读:
- MLflow Tracking - 探索有关 MLflow Tracking 的更多信息以及管理实验和模型的各种方法,例如团队协作。
- MLflow Model Registry - 了解有关 MLflow Model Registry 的更多信息,以在集中式模型存储中管理模型版本和阶段。
- MLflow 部署 - 了解有关 MLflow 部署和不同部署目标的更多信息。
- KServe 官方文档 - 深入了解 KServe 及其高级功能,包括自动缩放、金丝雀发布、A/B 测试、监控、可解释性等。
- Seldon Core 官方文档 - 了解 Seldon Core,这是我们支持用于 Kubernetes 的另一种无服务器模型服务框架。