文章目录
-
- 一、实验管理的混乱
- [二、MLflow Tracking:每一次实验都有据可查](#二、MLflow Tracking:每一次实验都有据可查)
-
- [2.1 Tracking 的核心概念](#2.1 Tracking 的核心概念)
- [2.2 基础追踪代码](#2.2 基础追踪代码)
- [2.3 自动记录(Autologging)](#2.3 自动记录(Autologging))
- [2.4 产物记录(Artifacts)](#2.4 产物记录(Artifacts))
- [2.5 嵌套运行与超参搜索](#2.5 嵌套运行与超参搜索)
- [三、MLflow Model Registry:模型版本的生命周期管理](#三、MLflow Model Registry:模型版本的生命周期管理)
-
- [3.1 Model Registry 的核心概念](#3.1 Model Registry 的核心概念)
- [3.2 模型注册与版本管理](#3.2 模型注册与版本管理)
- [3.3 阶段流转与审批工作流](#3.3 阶段流转与审批工作流)
- [3.4 模型加载与部署](#3.4 模型加载与部署)
- [四、MLflow Projects:可复现的环境打包](#四、MLflow Projects:可复现的环境打包)
-
- [4.1 项目配置文件](#4.1 项目配置文件)
- [4.2 运行项目](#4.2 运行项目)
- [五、与 sklearn Pipeline 的集成:端到端可复现](#五、与 sklearn Pipeline 的集成:端到端可复现)
-
- [5.1 完整 Pipeline 的追踪与注册](#5.1 完整 Pipeline 的追踪与注册)
- [5.2 带签名的模型加载与推理](#5.2 带签名的模型加载与推理)
- 六、从实验到生产的完整工作流
-
- [6.1 完整工作流架构](#6.1 完整工作流架构)
- [6.2 完整工作流代码](#6.2 完整工作流代码)
- [七、MLflow 的部署模式](#七、MLflow 的部署模式)
-
- [7.1 三种部署方式](#7.1 三种部署方式)
- 八、小结
一、实验管理的混乱
一个典型的机器学习项目会经历数十甚至上百次实验:调整学习率、更换模型架构、尝试不同的特征组合、修改数据预处理方式。每次实验都产生一组参数、一组指标、一个模型文件------但这些信息分散在 Notebook 的单元格中、散落在不同版本的脚本里、保存在各个目录下。三周后,当需要复现某个结果时,发现已经记不清"那次最好的实验到底用了什么参数"。
MLflow 解决的核心问题就是机器学习实验的可追踪、可复现、可管理。它提供了一套完整的工具链:Tracking(记录参数和指标)、Model Registry(管理模型版本)、Projects(打包可复现环境)、Models(标准化部署格式)。本文从实验追踪、模型注册和可复现工作流三个维度,建立一套从开发到生产的完整 MLOps 链路。
二、MLflow Tracking:每一次实验都有据可查
2.1 Tracking 的核心概念
#mermaid-svg-znItdof58vSZGJ7v{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-znItdof58vSZGJ7v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-znItdof58vSZGJ7v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-znItdof58vSZGJ7v .error-icon{fill:#552222;}#mermaid-svg-znItdof58vSZGJ7v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-znItdof58vSZGJ7v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-znItdof58vSZGJ7v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-znItdof58vSZGJ7v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-znItdof58vSZGJ7v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-znItdof58vSZGJ7v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-znItdof58vSZGJ7v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-znItdof58vSZGJ7v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-znItdof58vSZGJ7v .marker.cross{stroke:#333333;}#mermaid-svg-znItdof58vSZGJ7v svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-znItdof58vSZGJ7v p{margin:0;}#mermaid-svg-znItdof58vSZGJ7v .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-znItdof58vSZGJ7v .cluster-label text{fill:#333;}#mermaid-svg-znItdof58vSZGJ7v .cluster-label span{color:#333;}#mermaid-svg-znItdof58vSZGJ7v .cluster-label span p{background-color:transparent;}#mermaid-svg-znItdof58vSZGJ7v .label text,#mermaid-svg-znItdof58vSZGJ7v span{fill:#333;color:#333;}#mermaid-svg-znItdof58vSZGJ7v .node rect,#mermaid-svg-znItdof58vSZGJ7v .node circle,#mermaid-svg-znItdof58vSZGJ7v .node ellipse,#mermaid-svg-znItdof58vSZGJ7v .node polygon,#mermaid-svg-znItdof58vSZGJ7v .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-znItdof58vSZGJ7v .rough-node .label text,#mermaid-svg-znItdof58vSZGJ7v .node .label text,#mermaid-svg-znItdof58vSZGJ7v .image-shape .label,#mermaid-svg-znItdof58vSZGJ7v .icon-shape .label{text-anchor:middle;}#mermaid-svg-znItdof58vSZGJ7v .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-znItdof58vSZGJ7v .rough-node .label,#mermaid-svg-znItdof58vSZGJ7v .node .label,#mermaid-svg-znItdof58vSZGJ7v .image-shape .label,#mermaid-svg-znItdof58vSZGJ7v .icon-shape .label{text-align:center;}#mermaid-svg-znItdof58vSZGJ7v .node.clickable{cursor:pointer;}#mermaid-svg-znItdof58vSZGJ7v .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-znItdof58vSZGJ7v .arrowheadPath{fill:#333333;}#mermaid-svg-znItdof58vSZGJ7v .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-znItdof58vSZGJ7v .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-znItdof58vSZGJ7v .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-znItdof58vSZGJ7v .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-znItdof58vSZGJ7v .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-znItdof58vSZGJ7v .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-znItdof58vSZGJ7v .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-znItdof58vSZGJ7v .cluster text{fill:#333;}#mermaid-svg-znItdof58vSZGJ7v .cluster span{color:#333;}#mermaid-svg-znItdof58vSZGJ7v div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-znItdof58vSZGJ7v .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-znItdof58vSZGJ7v rect.text{fill:none;stroke-width:0;}#mermaid-svg-znItdof58vSZGJ7v .icon-shape,#mermaid-svg-znItdof58vSZGJ7v .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-znItdof58vSZGJ7v .icon-shape p,#mermaid-svg-znItdof58vSZGJ7v .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-znItdof58vSZGJ7v .icon-shape .label rect,#mermaid-svg-znItdof58vSZGJ7v .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-znItdof58vSZGJ7v .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-znItdof58vSZGJ7v .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-znItdof58vSZGJ7v :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MLflow Tracking
Experiment
实验
Run
单次运行
Artifact
产物
一个项目对应一个 Experiment
如:房价预测 / 欺诈检测
一次训练对应一个 Run
包含:参数 / 指标 / 产物
自动记录:开始时间 / 持续时间 / 状态
模型文件
pickle / ONNX
图表
ROC 曲线 / 特征重要性
数据
训练集快照
MLflow Tracking 的数据模型分为三层:
- Experiment(实验):一个项目的容器,例如"房价预测_v2"。每个 Experiment 包含多个 Run。
- Run(单次运行):一次模型训练的执行记录,包含参数(parameters)、指标(metrics)、产物(artifacts)、标签(tags)和起始/结束时间。
- Artifact(产物):Run 产生的文件,如模型文件、图表、配置文件等。
2.2 基础追踪代码
python
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, f1_score
import pandas as pd
# 设置实验名称
mlflow.set_experiment("credit_fraud_detection")
# 开始一次运行
with mlflow.start_run(run_name="rf_baseline"):
# 加载数据
df = pd.read_csv('creditcard.csv')
X = df.drop('Class', axis=1)
y = df['Class']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 模型参数
params = {
'n_estimators': 100,
'max_depth': 10,
'min_samples_split': 2,
'random_state': 42
}
# 记录参数
mlflow.log_params(params)
# 训练模型
model = RandomForestClassifier(**params)
model.fit(X_train, y_train)
# 预测与评估
y_pred = model.predict(X_test)
acc = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred)
# 记录指标
mlflow.log_metric("accuracy", acc)
mlflow.log_metric("f1_score", f1)
# 记录模型
mlflow.sklearn.log_model(model, "model")
print(f"Run ID: {mlflow.active_run().info.run_id}")
print(f"Accuracy: {acc:.4f}, F1: {f1:.4f}")
运行上述代码后,MLflow 会在当前目录下创建 mlruns/ 目录,其中保存了所有实验的完整记录。启动 MLflow UI 即可查看:
bash
mlflow ui
# 访问 http://localhost:5000
2.3 自动记录(Autologging)
MLflow 为多个主流框架提供了自动记录功能,无需手动调用 log_param 和 log_metric。
python
import mlflow
# 启用 sklearn 自动记录
mlflow.sklearn.autolog()
# 启用 XGBoost 自动记录
mlflow.xgboost.autolog()
# 启用 PyTorch 自动记录
mlflow.pytorch.autolog()
# 启用 LightGBM 自动记录
mlflow.lightgbm.autolog()
with mlflow.start_run():
# 训练代码------参数和指标会自动记录
model.fit(X_train, y_train)
# 不需要手动 log_param / log_metric
自动记录会捕捉的默认内容包括:
| 框架 | 自动记录的参数 | 自动记录的指标 | 自动记录的产物 |
|---|---|---|---|
| sklearn | 模型超参 | --- | 模型文件、特征重要性 |
| XGBoost | booster 参数 | train/valid 评估指标 | 模型文件、特征重要性 |
| LightGBM | 训练参数 | train/valid 评估指标 | 模型文件、特征重要性 |
| PyTorch | 优化器参数、网络结构 | 训练/验证损失 | 模型 checkpoint |
2.4 产物记录(Artifacts)
除了参数和指标,MLflow 还可以记录任意文件作为产物。
python
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import joblib
with mlflow.start_run(run_name="rf_with_artifacts"):
# ... 训练代码 ...
# 1. 记录混淆矩阵图
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm)
disp.plot()
plt.savefig("confusion_matrix.png")
mlflow.log_artifact("confusion_matrix.png")
# 2. 记录特征重要性图
importances = pd.DataFrame({
'feature': X.columns,
'importance': model.feature_importances_
}).sort_values('importance', ascending=False)
importances.to_csv("feature_importance.csv", index=False)
mlflow.log_artifact("feature_importance.csv")
# 3. 记录预处理 Pipeline
joblib.dump(preprocessor, "preprocessor.pkl")
mlflow.log_artifact("preprocessor.pkl")
# 4. 记录训练数据快照(小数据集适用)
X_train.to_csv("train_data.csv", index=False)
mlflow.log_artifact("train_data.csv")
2.5 嵌套运行与超参搜索
在超参搜索过程中,每次 trial 对应一个子运行(child run),主运行(parent run)汇总所有子运行的结果。
python
import mlflow
import optuna
mlflow.set_experiment("optuna_xgboost")
def objective(trial):
with mlflow.start_run(nested=True):
params = {
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 1e-1, log=True),
'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
'subsample': trial.suggest_float('subsample', 0.5, 1.0)
}
mlflow.log_params(params)
model = XGBClassifier(**params, random_state=42)
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]
pr_auc = average_precision_score(y_test, y_proba)
mlflow.log_metric("pr_auc", pr_auc)
mlflow.sklearn.log_model(model, "model")
return pr_auc
with mlflow.start_run(run_name="optuna_study"):
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50)
# 记录最优结果到父运行
mlflow.log_metric("best_pr_auc", study.best_value)
mlflow.log_params(study.best_params)
三、MLflow Model Registry:模型版本的生命周期管理
3.1 Model Registry 的核心概念
#mermaid-svg-dzrnM6yQPEwoR0EF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dzrnM6yQPEwoR0EF .error-icon{fill:#552222;}#mermaid-svg-dzrnM6yQPEwoR0EF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dzrnM6yQPEwoR0EF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dzrnM6yQPEwoR0EF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dzrnM6yQPEwoR0EF .marker.cross{stroke:#333333;}#mermaid-svg-dzrnM6yQPEwoR0EF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dzrnM6yQPEwoR0EF p{margin:0;}#mermaid-svg-dzrnM6yQPEwoR0EF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster-label text{fill:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster-label span{color:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster-label span p{background-color:transparent;}#mermaid-svg-dzrnM6yQPEwoR0EF .label text,#mermaid-svg-dzrnM6yQPEwoR0EF span{fill:#333;color:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF .node rect,#mermaid-svg-dzrnM6yQPEwoR0EF .node circle,#mermaid-svg-dzrnM6yQPEwoR0EF .node ellipse,#mermaid-svg-dzrnM6yQPEwoR0EF .node polygon,#mermaid-svg-dzrnM6yQPEwoR0EF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dzrnM6yQPEwoR0EF .rough-node .label text,#mermaid-svg-dzrnM6yQPEwoR0EF .node .label text,#mermaid-svg-dzrnM6yQPEwoR0EF .image-shape .label,#mermaid-svg-dzrnM6yQPEwoR0EF .icon-shape .label{text-anchor:middle;}#mermaid-svg-dzrnM6yQPEwoR0EF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dzrnM6yQPEwoR0EF .rough-node .label,#mermaid-svg-dzrnM6yQPEwoR0EF .node .label,#mermaid-svg-dzrnM6yQPEwoR0EF .image-shape .label,#mermaid-svg-dzrnM6yQPEwoR0EF .icon-shape .label{text-align:center;}#mermaid-svg-dzrnM6yQPEwoR0EF .node.clickable{cursor:pointer;}#mermaid-svg-dzrnM6yQPEwoR0EF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dzrnM6yQPEwoR0EF .arrowheadPath{fill:#333333;}#mermaid-svg-dzrnM6yQPEwoR0EF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dzrnM6yQPEwoR0EF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dzrnM6yQPEwoR0EF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dzrnM6yQPEwoR0EF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dzrnM6yQPEwoR0EF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dzrnM6yQPEwoR0EF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster text{fill:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF .cluster span{color:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dzrnM6yQPEwoR0EF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dzrnM6yQPEwoR0EF rect.text{fill:none;stroke-width:0;}#mermaid-svg-dzrnM6yQPEwoR0EF .icon-shape,#mermaid-svg-dzrnM6yQPEwoR0EF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dzrnM6yQPEwoR0EF .icon-shape p,#mermaid-svg-dzrnM6yQPEwoR0EF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dzrnM6yQPEwoR0EF .icon-shape .label rect,#mermaid-svg-dzrnM6yQPEwoR0EF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dzrnM6yQPEwoR0EF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dzrnM6yQPEwoR0EF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dzrnM6yQPEwoR0EF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Model Registry
Registered Model
注册模型
Model Version
版本
Model Stage
阶段
如:credit_fraud_model
包含多个版本
版本号:1, 2, 3...
每个版本关联一个 Run
None → Staging → Production → Archived
支持自定义阶段
Model Registry 是 MLflow 的模型版本管理中枢,解决"生产环境用的是哪个版本的模型"这一问题。
3.2 模型注册与版本管理
python
import mlflow
from mlflow.tracking import MlflowClient
client = MlflowClient()
# 1. 注册模型(如果不存在)
try:
client.create_registered_model("credit_fraud_model")
except mlflow.exceptions.MlflowException:
pass # 模型已存在
# 2. 从已有 Run 创建模型版本
run_id = "abc123def456" # 某次训练的运行 ID
model_uri = f"runs:/{run_id}/model"
mv = client.create_model_version(
name="credit_fraud_model",
source=model_uri,
run_id=run_id
)
print(f"Registered version: {mv.version}")
# 3. 将版本推进到 Staging 阶段
client.transition_model_version_stage(
name="credit_fraud_model",
version=mv.version,
stage="Staging"
)
# 4. 添加版本说明
client.update_model_version(
name="credit_fraud_model",
version=mv.version,
description="PR-AUC 0.92, 使用 SMOTE + XGBoost"
)
# 5. 添加标签
client.set_model_version_tag(
name="credit_fraud_model",
version=mv.version,
key="validated",
value="true"
)
3.3 阶段流转与审批工作流
#mermaid-svg-dXu5kv7g10UlKSwF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dXu5kv7g10UlKSwF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dXu5kv7g10UlKSwF .error-icon{fill:#552222;}#mermaid-svg-dXu5kv7g10UlKSwF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dXu5kv7g10UlKSwF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dXu5kv7g10UlKSwF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dXu5kv7g10UlKSwF .marker.cross{stroke:#333333;}#mermaid-svg-dXu5kv7g10UlKSwF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dXu5kv7g10UlKSwF p{margin:0;}#mermaid-svg-dXu5kv7g10UlKSwF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dXu5kv7g10UlKSwF .cluster-label text{fill:#333;}#mermaid-svg-dXu5kv7g10UlKSwF .cluster-label span{color:#333;}#mermaid-svg-dXu5kv7g10UlKSwF .cluster-label span p{background-color:transparent;}#mermaid-svg-dXu5kv7g10UlKSwF .label text,#mermaid-svg-dXu5kv7g10UlKSwF span{fill:#333;color:#333;}#mermaid-svg-dXu5kv7g10UlKSwF .node rect,#mermaid-svg-dXu5kv7g10UlKSwF .node circle,#mermaid-svg-dXu5kv7g10UlKSwF .node ellipse,#mermaid-svg-dXu5kv7g10UlKSwF .node polygon,#mermaid-svg-dXu5kv7g10UlKSwF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dXu5kv7g10UlKSwF .rough-node .label text,#mermaid-svg-dXu5kv7g10UlKSwF .node .label text,#mermaid-svg-dXu5kv7g10UlKSwF .image-shape .label,#mermaid-svg-dXu5kv7g10UlKSwF .icon-shape .label{text-anchor:middle;}#mermaid-svg-dXu5kv7g10UlKSwF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dXu5kv7g10UlKSwF .rough-node .label,#mermaid-svg-dXu5kv7g10UlKSwF .node .label,#mermaid-svg-dXu5kv7g10UlKSwF .image-shape .label,#mermaid-svg-dXu5kv7g10UlKSwF .icon-shape .label{text-align:center;}#mermaid-svg-dXu5kv7g10UlKSwF .node.clickable{cursor:pointer;}#mermaid-svg-dXu5kv7g10UlKSwF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dXu5kv7g10UlKSwF .arrowheadPath{fill:#333333;}#mermaid-svg-dXu5kv7g10UlKSwF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dXu5kv7g10UlKSwF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dXu5kv7g10UlKSwF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dXu5kv7g10UlKSwF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dXu5kv7g10UlKSwF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dXu5kv7g10UlKSwF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dXu5kv7g10UlKSwF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dXu5kv7g10UlKSwF .cluster text{fill:#333;}#mermaid-svg-dXu5kv7g10UlKSwF .cluster span{color:#333;}#mermaid-svg-dXu5kv7g10UlKSwF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dXu5kv7g10UlKSwF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dXu5kv7g10UlKSwF rect.text{fill:none;stroke-width:0;}#mermaid-svg-dXu5kv7g10UlKSwF .icon-shape,#mermaid-svg-dXu5kv7g10UlKSwF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dXu5kv7g10UlKSwF .icon-shape p,#mermaid-svg-dXu5kv7g10UlKSwF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dXu5kv7g10UlKSwF .icon-shape .label rect,#mermaid-svg-dXu5kv7g10UlKSwF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dXu5kv7g10UlKSwF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dXu5kv7g10UlKSwF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dXu5kv7g10UlKSwF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 训练完成
QA 验证通过
验证失败
新模型上线
性能回退
None
回滚到 Staging
Production
Archived
生产环境中的典型模型生命周期:
- 训练完成:模型自动注册为版本,阶段为 None
- QA 验证:在验证集上评估,通过后推进到 Staging
- 灰度发布:Staging 阶段模型接入 5% 流量,观察指标
- 全量发布:灰度通过后推进到 Production
- 归档:新版本上线后,旧版本推进到 Archived
python
def promote_model(model_name, version, from_stage, to_stage):
"""模型阶段流转(含验证检查)"""
client = MlflowClient()
# 检查当前阶段
current = client.get_model_version(model_name, version)
if current.current_stage != from_stage:
raise ValueError(f"版本 {version} 当前阶段为 {current.current_stage},不是 {from_stage}")
# 阶段流转
client.transition_model_version_stage(
name=model_name,
version=version,
stage=to_stage
)
print(f"模型 {model_name} v{version} 已从 {from_stage} 推进到 {to_stage}")
# 使用示例
promote_model("credit_fraud_model", 3, "Staging", "Production")
3.4 模型加载与部署
python
import mlflow.pyfunc
# 按阶段加载模型(推荐)
model = mlflow.pyfunc.load_model("models:/credit_fraud_model/Production")
predictions = model.predict(X_new)
# 按版本加载模型(用于回滚)
model_v2 = mlflow.pyfunc.load_model("models:/credit_fraud_model/2")
predictions_v2 = model_v2.predict(X_new)
# 按 Run ID 加载模型(调试时)
model_run = mlflow.pyfunc.load_model("runs:/abc123def456/model")
生产环境最佳实践:部署脚本中始终使用
models:/model_name/Production而非硬编码版本号。这样当模型在 Registry 中更新阶段时,部署系统会自动加载新模型,无需修改代码。
四、MLflow Projects:可复现的环境打包
4.1 项目配置文件
MLflow Projects 通过 MLproject 文件定义项目的入口点和依赖,确保实验在任意环境中都能复现。
yaml
# MLproject
name: credit_fraud_training
conda_env: conda.yaml
entry_points:
main:
parameters:
n_estimators: {type: int, default: 100}
max_depth: {type: int, default: 5}
learning_rate: {type: float, default: 0.1}
data_path: {type: str, default: "data/creditcard.csv"}
command: "python train.py --n_estimators={n_estimators} --max_depth={max_depth}
--learning_rate={learning_rate} --data_path={data_path}"
evaluate:
parameters:
model_uri: {type: str}
test_data: {type: str, default: "data/test.csv"}
command: "python evaluate.py --model_uri={model_uri} --test_data={test_data}"
yaml
# conda.yaml
name: fraud_training
channels:
- conda-forge
dependencies:
- python=3.11
- pip
- pip:
- mlflow==2.14.0
- scikit-learn==1.5.0
- xgboost==2.0.0
- pandas==2.0.0
- numpy==1.26.0
4.2 运行项目
bash
# 本地运行
mlflow run . -P n_estimators=200 -P max_depth=7
# 从 Git 仓库运行
mlflow run https://github.com/username/fraud-model.git -P max_depth=10
# 指定实验名称
mlflow run . --experiment-name fraud_v2 -P learning_rate=0.05
mlflow run 会自动:
- 解析
MLproject文件 - 根据
conda.yaml创建/复用 Conda 环境 - 在隔离环境中执行入口命令
- 自动记录所有参数、指标和产物到 Tracking
五、与 sklearn Pipeline 的集成:端到端可复现
5.1 完整 Pipeline 的追踪与注册
python
import mlflow
import mlflow.sklearn
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import pandas as pd
mlflow.set_experiment("fraud_pipeline_v1")
with mlflow.start_run(run_name="full_pipeline"):
# 特征工程 Pipeline
numeric_features = ['amount', 'time', 'v1', 'v2']
categorical_features = ['merchant_category']
preprocessor = ColumnTransformer([
('num', StandardScaler(), numeric_features),
('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features)
])
# 完整 Pipeline
pipeline = Pipeline([
('preprocess', preprocessor),
('classifier', RandomForestClassifier(n_estimators=200, random_state=42))
])
# 记录参数
mlflow.log_params({
'model_type': 'RandomForest',
'n_estimators': 200,
'cv_folds': 5
})
# 交叉验证
scores = cross_val_score(
pipeline, X_train, y_train,
cv=5, scoring='average_precision', n_jobs=-1
)
mlflow.log_metric('cv_pr_auc_mean', scores.mean())
mlflow.log_metric('cv_pr_auc_std', scores.std())
# 在全量训练集上训练
pipeline.fit(X_train, y_train)
# 测试集评估
from sklearn.metrics import average_precision_score
y_proba = pipeline.predict_proba(X_test)[:, 1]
test_pr_auc = average_precision_score(y_test, y_proba)
mlflow.log_metric('test_pr_auc', test_pr_auc)
# 记录签名(输入/输出 schema)
from mlflow.models.signature import infer_signature
signature = infer_signature(X_train, pipeline.predict(X_train))
# 记录完整 Pipeline(含预处理)
mlflow.sklearn.log_model(
pipeline,
artifact_path="pipeline_model",
signature=signature,
input_example=X_train.head(5)
)
print(f"CV PR-AUC: {scores.mean():.4f} ± {scores.std():.4f}")
print(f"Test PR-AUC: {test_pr_auc:.4f}")
5.2 带签名的模型加载与推理
python
import mlflow.pyfunc
# 加载模型
model = mlflow.pyfunc.load_model("models:/fraud_pipeline_v1/Production")
# 推理------Pipeline 自动处理预处理
# 输入原始数据,输出预测结果
predictions = model.predict(X_new)
probabilities = model.predict_proba(X_new)
# 模型签名保证了输入格式的一致性
print(model.metadata.signature)
# 输出示例:
# inputs:
# ['amount': double, 'time': double, 'v1': double, ...]
# outputs:
# ['predicted_class': long]
Pipeline + MLflow 的组合价值:训练时的预处理步骤(StandardScaler、OneHotEncoder)与模型一起被打包保存。推理时无需手动预处理,模型自动处理输入数据。这消除了"训练-推理不一致"的风险。
六、从实验到生产的完整工作流
6.1 完整工作流架构
#mermaid-svg-jPgtl9MPzVv99xou{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jPgtl9MPzVv99xou .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jPgtl9MPzVv99xou .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jPgtl9MPzVv99xou .error-icon{fill:#552222;}#mermaid-svg-jPgtl9MPzVv99xou .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jPgtl9MPzVv99xou .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jPgtl9MPzVv99xou .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jPgtl9MPzVv99xou .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jPgtl9MPzVv99xou .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jPgtl9MPzVv99xou .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jPgtl9MPzVv99xou .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jPgtl9MPzVv99xou .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jPgtl9MPzVv99xou .marker.cross{stroke:#333333;}#mermaid-svg-jPgtl9MPzVv99xou svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jPgtl9MPzVv99xou p{margin:0;}#mermaid-svg-jPgtl9MPzVv99xou .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jPgtl9MPzVv99xou .cluster-label text{fill:#333;}#mermaid-svg-jPgtl9MPzVv99xou .cluster-label span{color:#333;}#mermaid-svg-jPgtl9MPzVv99xou .cluster-label span p{background-color:transparent;}#mermaid-svg-jPgtl9MPzVv99xou .label text,#mermaid-svg-jPgtl9MPzVv99xou span{fill:#333;color:#333;}#mermaid-svg-jPgtl9MPzVv99xou .node rect,#mermaid-svg-jPgtl9MPzVv99xou .node circle,#mermaid-svg-jPgtl9MPzVv99xou .node ellipse,#mermaid-svg-jPgtl9MPzVv99xou .node polygon,#mermaid-svg-jPgtl9MPzVv99xou .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jPgtl9MPzVv99xou .rough-node .label text,#mermaid-svg-jPgtl9MPzVv99xou .node .label text,#mermaid-svg-jPgtl9MPzVv99xou .image-shape .label,#mermaid-svg-jPgtl9MPzVv99xou .icon-shape .label{text-anchor:middle;}#mermaid-svg-jPgtl9MPzVv99xou .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jPgtl9MPzVv99xou .rough-node .label,#mermaid-svg-jPgtl9MPzVv99xou .node .label,#mermaid-svg-jPgtl9MPzVv99xou .image-shape .label,#mermaid-svg-jPgtl9MPzVv99xou .icon-shape .label{text-align:center;}#mermaid-svg-jPgtl9MPzVv99xou .node.clickable{cursor:pointer;}#mermaid-svg-jPgtl9MPzVv99xou .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jPgtl9MPzVv99xou .arrowheadPath{fill:#333333;}#mermaid-svg-jPgtl9MPzVv99xou .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jPgtl9MPzVv99xou .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jPgtl9MPzVv99xou .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jPgtl9MPzVv99xou .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jPgtl9MPzVv99xou .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jPgtl9MPzVv99xou .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jPgtl9MPzVv99xou .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jPgtl9MPzVv99xou .cluster text{fill:#333;}#mermaid-svg-jPgtl9MPzVv99xou .cluster span{color:#333;}#mermaid-svg-jPgtl9MPzVv99xou div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jPgtl9MPzVv99xou .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jPgtl9MPzVv99xou rect.text{fill:none;stroke-width:0;}#mermaid-svg-jPgtl9MPzVv99xou .icon-shape,#mermaid-svg-jPgtl9MPzVv99xou .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jPgtl9MPzVv99xou .icon-shape p,#mermaid-svg-jPgtl9MPzVv99xou .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jPgtl9MPzVv99xou .icon-shape .label rect,#mermaid-svg-jPgtl9MPzVv99xou .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jPgtl9MPzVv99xou .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jPgtl9MPzVv99xou .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jPgtl9MPzVv99xou :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生产环境
验证环境
开发环境
通过
失败
性能下降
Jupyter Notebook
探索性实验
MLflow Tracking
记录参数/指标
Optuna
超参搜索
模型注册到
Model Registry
Staging 阶段
QA 验证
Production 阶段
返回开发
部署服务
加载 Production 模型
监控指标
漂移检测
触发重训练
6.2 完整工作流代码
python
"""
完整的 MLOps 工作流:从数据到部署
"""
import mlflow
import mlflow.sklearn
from mlflow.tracking import MlflowClient
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from xgboost import XGBClassifier
from sklearn.metrics import average_precision_score
import optuna
# 配置
EXPERIMENT_NAME = "fraud_detection_prod"
MODEL_NAME = "fraud_classifier"
DATA_PATH = "data/creditcard.csv"
def load_and_split_data(path):
"""加载并分割数据"""
df = pd.read_csv(path)
X = df.drop('Class', axis=1)
y = df['Class']
return train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
def train_baseline(X_train, y_train, X_test, y_test):
"""训练基线模型并记录"""
mlflow.set_experiment(EXPERIMENT_NAME)
with mlflow.start_run(run_name="baseline"):
pipeline = Pipeline([
('scale', StandardScaler()),
('model', XGBClassifier(random_state=42))
])
pipeline.fit(X_train, y_train)
y_proba = pipeline.predict_proba(X_test)[:, 1]
pr_auc = average_precision_score(y_test, y_proba)
mlflow.log_params({'model': 'XGBClassifier', 'scale': True})
mlflow.log_metric('pr_auc', pr_auc)
mlflow.sklearn.log_model(pipeline, 'model')
return mlflow.active_run().info.run_id, pr_auc
def hyperparameter_search(X_train, y_train, X_test, y_test, n_trials=50):
"""超参搜索并记录最优模型"""
mlflow.set_experiment(EXPERIMENT_NAME)
def objective(trial):
with mlflow.start_run(nested=True):
params = {
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 1e-1, log=True),
'n_estimators': trial.suggest_int('n_estimators', 100, 1000),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'scale_pos_weight': trial.suggest_float('scale_pos_weight', 1, 100, log=True)
}
mlflow.log_params(params)
model = XGBClassifier(**params, random_state=42)
model.fit(X_train, y_train)
y_proba = model.predict_proba(X_test)[:, 1]
pr_auc = average_precision_score(y_test, y_proba)
mlflow.log_metric('pr_auc', pr_auc)
mlflow.sklearn.log_model(model, 'model')
return pr_auc
with mlflow.start_run(run_name="hyperparameter_search"):
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=n_trials, show_progress_bar=True)
# 记录最优结果
mlflow.log_params(study.best_params)
mlflow.log_metric('best_pr_auc', study.best_value)
# 获取最优 run
best_run_id = study.best_trial.user_attrs.get('run_id')
return study.best_value, study.best_params, best_run_id
def register_and_promote(run_id, model_name, metric_value):
"""注册模型并推进到 Production"""
client = MlflowClient()
# 创建或获取注册模型
try:
client.create_registered_model(model_name)
except mlflow.exceptions.MlflowException:
pass
# 创建新版本
model_uri = f"runs:/{run_id}/model"
mv = client.create_model_version(
name=model_name,
source=model_uri,
run_id=run_id
)
# 添加说明
client.update_model_version(
name=model_name,
version=mv.version,
description=f"PR-AUC: {metric_value:.4f}"
)
# 先推进到 Staging
client.transition_model_version_stage(
name=model_name, version=mv.version, stage="Staging"
)
# 验证逻辑(此处简化,实际应有独立验证脚本)
print(f"模型 {model_name} v{mv.version} 已注册并推进到 Staging")
print("请运行验证脚本,验证通过后执行以下命令推进到 Production:")
print(f" mlflow transition-model-version-stage --name {model_name} "
f"--version {mv.version} --stage Production")
return mv.version
def production_inference(model_name, X_new):
"""生产环境推理"""
model = mlflow.pyfunc.load_model(f"models:/{model_name}/Production")
return model.predict_proba(X_new)[:, 1]
# 主工作流
if __name__ == "__main__":
print("=== Step 1: 加载数据 ===")
X_train, X_test, y_train, y_test = load_and_split_data(DATA_PATH)
print("\n=== Step 2: 训练基线模型 ===")
baseline_run_id, baseline_pr_auc = train_baseline(
X_train, y_train, X_test, y_test
)
print(f"基线 PR-AUC: {baseline_pr_auc:.4f}")
print("\n=== Step 3: 超参搜索 ===")
best_pr_auc, best_params, best_run_id = hyperparameter_search(
X_train, y_train, X_test, y_test, n_trials=30
)
print(f"最优 PR-AUC: {best_pr_auc:.4f}")
print(f"最优参数: {best_params}")
print("\n=== Step 4: 模型注册 ===")
version = register_and_promote(best_run_id, MODEL_NAME, best_pr_auc)
七、MLflow 的部署模式
7.1 三种部署方式
#mermaid-svg-xPOphsgCTyWHtcXn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xPOphsgCTyWHtcXn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xPOphsgCTyWHtcXn .error-icon{fill:#552222;}#mermaid-svg-xPOphsgCTyWHtcXn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xPOphsgCTyWHtcXn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xPOphsgCTyWHtcXn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xPOphsgCTyWHtcXn .marker.cross{stroke:#333333;}#mermaid-svg-xPOphsgCTyWHtcXn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xPOphsgCTyWHtcXn p{margin:0;}#mermaid-svg-xPOphsgCTyWHtcXn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xPOphsgCTyWHtcXn .cluster-label text{fill:#333;}#mermaid-svg-xPOphsgCTyWHtcXn .cluster-label span{color:#333;}#mermaid-svg-xPOphsgCTyWHtcXn .cluster-label span p{background-color:transparent;}#mermaid-svg-xPOphsgCTyWHtcXn .label text,#mermaid-svg-xPOphsgCTyWHtcXn span{fill:#333;color:#333;}#mermaid-svg-xPOphsgCTyWHtcXn .node rect,#mermaid-svg-xPOphsgCTyWHtcXn .node circle,#mermaid-svg-xPOphsgCTyWHtcXn .node ellipse,#mermaid-svg-xPOphsgCTyWHtcXn .node polygon,#mermaid-svg-xPOphsgCTyWHtcXn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xPOphsgCTyWHtcXn .rough-node .label text,#mermaid-svg-xPOphsgCTyWHtcXn .node .label text,#mermaid-svg-xPOphsgCTyWHtcXn .image-shape .label,#mermaid-svg-xPOphsgCTyWHtcXn .icon-shape .label{text-anchor:middle;}#mermaid-svg-xPOphsgCTyWHtcXn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xPOphsgCTyWHtcXn .rough-node .label,#mermaid-svg-xPOphsgCTyWHtcXn .node .label,#mermaid-svg-xPOphsgCTyWHtcXn .image-shape .label,#mermaid-svg-xPOphsgCTyWHtcXn .icon-shape .label{text-align:center;}#mermaid-svg-xPOphsgCTyWHtcXn .node.clickable{cursor:pointer;}#mermaid-svg-xPOphsgCTyWHtcXn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xPOphsgCTyWHtcXn .arrowheadPath{fill:#333333;}#mermaid-svg-xPOphsgCTyWHtcXn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xPOphsgCTyWHtcXn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xPOphsgCTyWHtcXn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xPOphsgCTyWHtcXn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xPOphsgCTyWHtcXn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xPOphsgCTyWHtcXn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xPOphsgCTyWHtcXn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xPOphsgCTyWHtcXn .cluster text{fill:#333;}#mermaid-svg-xPOphsgCTyWHtcXn .cluster span{color:#333;}#mermaid-svg-xPOphsgCTyWHtcXn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xPOphsgCTyWHtcXn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xPOphsgCTyWHtcXn rect.text{fill:none;stroke-width:0;}#mermaid-svg-xPOphsgCTyWHtcXn .icon-shape,#mermaid-svg-xPOphsgCTyWHtcXn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xPOphsgCTyWHtcXn .icon-shape p,#mermaid-svg-xPOphsgCTyWHtcXn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xPOphsgCTyWHtcXn .icon-shape .label rect,#mermaid-svg-xPOphsgCTyWHtcXn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xPOphsgCTyWHtcXn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xPOphsgCTyWHtcXn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xPOphsgCTyWHtcXn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} MLflow 模型
本地推理
mlflow.pyfunc.load_model
MLflow Serving
REST API
云原生部署
Docker + K8s
Python 脚本直接调用
适合批处理
mlflow models serve
自动暴露 REST API
适合实时推理
mlflow models build-docker
打包为 Docker 镜像
适合大规模部署
本地推理
python
import mlflow.pyfunc
model = mlflow.pyfunc.load_model("models:/fraud_classifier/Production")
predictions = model.predict_proba(X_batch)
MLflow Serving
bash
# 启动 REST 服务
mlflow models serve -m models:/fraud_classifier/Production -p 5001
# 测试请求
curl -X POST http://localhost:5001/invocations \
-H "Content-Type: application/json" \
-d '{"dataframe_split": {"columns": ["amount", "time", "v1"], "data": [[100.0, 50000, 0.5]]}}'
Docker 部署
bash
# 构建 Docker 镜像
mlflow models build-docker \
-m models:/fraud_classifier/Production \
-n fraud-model:v1
# 运行容器
docker run -p 5001:8080 fraud-model:v1
八、小结
MLflow 不是单个工具,而是一个完整的机器学习生命周期管理平台。Tracking 解决了"实验去哪儿了"的问题,Model Registry 解决了"生产用的是哪个模型"的问题,Projects 解决了"这个实验怎么复现"的问题。
核心设计原则:
- 实验即代码:每次实验都对应一个可执行的代码版本,而非 Notebook 中的某个单元格状态
- 模型即产物:训练好的模型与参数、指标、环境配置一起打包,形成完整的"模型包"
- 阶段即契约:Staging → Production → Archived 的阶段流转,是团队协作的标准语言
与 sklearn Pipeline 的集成是生产落地的关键。将预处理步骤(StandardScaler、OneHotEncoder)与模型一起封装为 Pipeline,再用 MLflow 记录和注册,可以确保训练时的预处理逻辑与推理时完全一致------这是避免"训练-推理不一致"的工程底线。
此前专栏关于模型评估与超参调优、特征工程系统方法论以及 XGBoost 实战的文章,为本文提供了从特征构造到模型训练再到实验管理的完整上游支撑。如果本文对 MLflow 实践和 MLOps 工作流有所启发,欢迎点赞、收藏与关注。