使用 MLflow 开发机器学习模型并部署到 Kubernetes
本指南演示了如何端到端使用 MLflow:
- 使用 MLflow Tracking 训练线性回归模型。
- 进行超参数调优以找到最佳模型。
- 将模型权重和依赖项打包为 MLflow 模型。
- 使用 mlserver 通过 mlflow models serve 命令在本地测试模型服务。
- 使用 MLflow 将模型部署到 Kubernetes 集群中的 KServe。
在本教程中,我们将介绍端到端的模型开发过程,包括模型训练和测试。如果您已经有一个模型并且只想学习如何将其部署到 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 快速入门使用 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 时,您应该会注意到“超参数调优”运行包含 5 个子运行。MLflow 利用父子关系,这对于对一组运行进行分组特别有用,例如超参数调优中的那些。此处已启用自动日志记录,MLflow 会根据 scoring
指标(在此示例中为负平均绝对误差)自动为前 5 个运行创建子运行。
要比较结果并确定最佳模型,您可以使用 MLflow UI 中的可视化功能。
- 选择第一个作业(“default-params”)和超参数调优的父作业(“hyperparameter-tuning”)。
- 单击“图表”选项卡以在图表中可视化指标。
- 默认情况下,会显示预定义的一组指标的几个条形图。
- 您可以添加不同的图表,例如散点图,以比较多个指标。例如,我们可以看到超参数调优的最佳模型在测试数据集上的均方误差方面优于默认参数模型
您可以通过查看父级运行“hyperparameter-tuning”来检查超参数的最佳组合。在此示例中,最佳模型是 alpha=0.11714084185001972
和 l1_ratio=0.3599780644783639
(您可能会看到不同的结果)。
要了解有关 MLflow 超参数调优的更多信息,请参阅 使用 MLflow 和 Optuna 进行超参数调优。
步骤 5:打包模型和依赖项
由于我们正在使用自动日志记录,MLflow 会自动为每次运行记录 模型。此过程方便地将模型权重和依赖项打包成可部署的格式。
实际上,还建议使用 MLflow 模型注册表 来注册和管理您的模型。
让我们简要了解一下这种格式的外观。您可以通过运行详细信息页面上的 Artifacts
选项卡查看已记录的模型。
model
├── MLmodel
├── model.pkl
├── conda.yaml
├── python_env.yaml
└── requirements.txt
model.pkl
是包含序列化模型权重的D文件。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
创建部署配置
创建描述模型部署到 KServe 的 YAML 文件。
在 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 格式指定模型 URI,例如 s3://xxx
或 gs://xxx
。默认情况下,MLflow 将模型存储在本地文件系统中,因此您需要配置 MLflow 将模型存储在远程存储中。有关设置说明,请参阅 Artifact Store。
配置完工件存储后,将最佳模型加载并重新记录到新的工件存储,或重复模型训练步骤。
创建部署配置
有了远程模型 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 公开,请按照这些说明查找入口 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
创建的本地集群没有。在这种情况下,您可以通过端口转发访问推理服务。
打开一个新的终端并运行以下命令以转发端口
$ 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 模型注册表 - 了解更多关于 MLflow 模型注册表,用于在集中式模型存储中管理模型版本和阶段。
- MLflow 部署 - 了解更多关于 MLflow 部署和不同的部署目标。
- KServe 官方文档 - 深入了解 KServe 及其高级功能,包括自动缩放、金丝雀发布、A/B 测试、监控、可解释性等。
- Seldon Core 官方文档 - 了解 Seldon Core,我们支持的 Kubernetes 的另一种无服务器模型服务框架。