综合练习
DVC→MLflow→Docker→Metrics 端到端。
40 分钟
综合练习
这一课你将能做到
- 把 DVC、MLflow、Prefect、Docker、K8s、Prometheus、Evidently、SHAP 串成一条端到端流水线并完整交付
- 用「题目难度评分模型」走一遍从原始日志到漂移监控的全流程(比超时预测更完整的回归场景)
- 按 12 项毕业清单自查每个环节的可复现性与可运维性
为什么要学
前 11 课你分别摸过了每一块砖:数据管道、版本控制、实验跟踪、模型注册、编排调度、CI/CD、容器、云形态、监控、可解释性。但砖和房子之间隔着一次真实的施工——组件之间的接缝(注册中心地址传给容器、监控触发重训、解释报告进工单)才是 MLOps 工程师的真功夫。
本课是毕业设计:交付一个难度评分模型的完整流水线。选它而不是超时预测,因为它是回归任务 + 批量推理 + 全站落库,恰好把没覆盖到的组合补齐。人话:上一条流水线,你就有了面试里最值钱的素材——「我从 0 到 1 做过完整的 ML 交付」。
先建立图像
最终仓库形态:
mlops-lab/
├── data/ data/*.dvc # DVC 接管的数据集
├── src/ # make/clean/features/train/batch_score/
│ # serve/promote/drift_report/explain_*
├── flows/retrain.py # Prefect 每日编排
├── dvc.yaml params.yaml # 数据管道与参数
├── Dockerfile docker-compose.yml
├── k8s/timeout-api.yaml
├── .github/workflows/ml-ci.yml
└── reports/ # drift.html / shap_*.png(CI 产出物)
交付链路(一图总览全部 11 课):
判题日志 → dvc repro(DVC) → 训练(MLflow Tracking) → 门禁+注册(Registry)
→ 镜像(Docker) → 部署(compose/K8s) → 三层监控(Prometheus+Evidently)
→ 漂移/效果触发 → 编排重训(Prefect) → 解释报告(SHAP) → 持续闭环
跟着做
步骤 1:数据管道定稿(第 2/3 课组件)
dvc.yaml 保持第 3 课的三段式(make_data → clean → features),并补上训练段:
# dvc.yaml(新增 train 段)
stages:
# ...前三个 stage 同第 3 课...
train_difficulty:
cmd: python src/train_difficulty.py
deps:
- src/train_difficulty.py
- data/processed/problem_daily.parquet
params:
- train.random_state
outs:
- models/difficulty-v1.joblib
- metrics.json
metrics:
- metrics.json:
cache: false
# params.yaml(最终版)
features: { tle_window: 7 }
train: { random_state: 42, min_auc: 0.8, min_mae: 1.2 }
步骤 2:难度评分模型的训练与注册(第 4/5 课组件)
# src/train_difficulty.py
import yaml, json, mlflow
import pandas as pd
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split
from mlflow.models import infer_signature
mlflow.set_tracking_uri("http://127.0.0.1:5000")
p = yaml.safe_load(open("params.yaml", encoding="utf-8"))
df = pd.read_parquet("data/processed/problem_daily.parquet")
X = df[["tle_rate_7d", "avg_time", "submits"]]
y = df.groupby("problem_id")["tle_rate_7d"].transform(
lambda s: pd.cut(s.rank(pct=True), bins=5, labels=False) + 1)[:len(df)]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, random_state=p["train"]["random_state"])
with mlflow.start_run() as run:
mlflow.log_params({k: str(v) for k, v in p["train"].items()})
model = RandomForestRegressor(n_estimators=200, random_state=42)
model.fit(X_tr, y_tr)
mae = mean_absolute_error(y_te, model.predict(X_te))
json.dump({"mae": mae}, open("metrics.json", "w"))
mlflow.log_metric("mae", mae)
mlflow.sklearn.log_model(model, name="model",
signature=infer_signature(X_tr.head(5)),
input_example=X_tr.head(5))
mlflow.register_model(f"runs:/{run.info.run_id}/model", "difficulty_scorer")