CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
MLOps 入门/课时/训练与实验跟踪
RawBlame
课时目录12 节

MLOps 入门

  1. 01MLOps 是什么
  2. 02数据工程基础
  3. 03版本控制与 DVC
  4. 04训练与实验跟踪
  5. 05MLflow 与血缘
  6. 06编排与调度
  7. 07CI/CD for ML
  8. 08容器化部署
  9. 09云原生 ML
  10. 10监控与可观测
  11. 11可解释 AI
  12. 12综合练习

训练与实验跟踪

sklearn/PyTorch 与 experiment tracking。

35 分钟

训练与实验跟踪

这一课你将能做到

  1. 把训练代码组织成「加载数据 → 按时间切分 → 构造防泄漏特征 → 训练 → 评估」的结构化管道
  2. 用 MLflow 自动记录每次训练的超参、指标、模型文件,并在 UI 里对比实验
  3. 给训练脚本加 --smoke 模式,为第 7 课的 CI 训练冒烟测试做准备

为什么要学

训练本身不难,难的是三个月后回答:「线上这个模型是哪天、用哪份数据、什么参数训出来的?F1 多少?为什么不用另一次实验?」没有实验跟踪的团队靠聊天记录和 Excel 找答案,这是 MLOps 里最典型的混乱源。

MLflow Tracking 是事实标准的轻量方案:每次训练成为一个 run,参数、指标、模型 artifact 自动入库,一个网页看全所有实验。人话:把「调参日记」从脑子和小本本里搬进数据库,还能一键翻旧账。

先建立图像

Experiment: timeout-prediction(一个业务问题一个实验)
├─ Run #1  params: {max_iter=200, lr=0.1}   metrics: {f1=0.87, auc=0.93}
│          artifacts: model/ (模型文件+签名)   tags: {git_sha=abc123, data=judge_clean@md5}
├─ Run #2  params: {max_iter=400, lr=0.05}  metrics: {f1=0.89, auc=0.94}
└─ Run #3  ...

一次 run = 一份完整档案:代码版本 + 数据版本 + 参数 + 指标 + 模型文件

跟着做

步骤 1:安装并初始化 MLflow

pip install mlflow

步骤 2:写出结构化训练脚本

数据沿用第 3 课产出的 data/processed/judge_clean.parquet。重点看两处纪律:按时间切分(不能随机切,否则用未来数据预测过去)和防泄漏历史特征(shift(1) 把当前行排除)。

# src/train.py
import argparse, subprocess
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import f1_score, roc_auc_score
import mlflow
from mlflow.models import infer_signature

FEATURES = ["code_lines", "lang_java", "rank", "problem_tle_hist"]

def load_data() -> pd.DataFrame:
    df = pd.read_parquet("data/processed/judge_clean.parquet")
    df = df.sort_values("submit_time").reset_index(drop=True)
    df["is_tle"] = (df["verdict"] == "TLE").astype(int)
    df["lang_java"] = (df["lang"] == "Java").astype(int)
    # 防泄漏:每题历史 TLE 率,shift(1) 排除当前提交自己
    df["problem_tle_hist"] = (df.groupby("problem_id")["is_tle"]
                                .transform(lambda s: s.shift(1).expanding().mean())
                                .fillna(0.15))
    return df

def git_sha() -> str:
    try:
        return subprocess.check_output(
            ["git", "rev-parse", "--short", "HEAD"], text=True).strip()
    except Exception:
        return "unknown"

def main(smoke: bool, max_iter: int, learning_rate: float):
    df = load_data()
    if smoke:
        df = df.sample(500, random_state=42)

    split = int(len(df) * 0.8)                    # 前 80% 训练,后 20% 测试
    X_tr, y_tr = df[FEATURES][:split], df["is_tle"][:split]
    X_te, y_te = df[FEATURES][split:], df["is_tle"][split:]

    mlflow.set_experiment("timeout-prediction")
    with mlflow.start_run() as run:
        mlflow.log_params({
            "max_iter": max_iter, "learning_rate": learning_rate,
            "train_rows": len(X_tr), "test_rows": len(X_te),
        })
        mlflow.set_tags({"git_sha": git_sha(), "data": "judge_clean.parquet"})

        model = HistGradientBoostingClassifier(
            max_iter=max_iter, learning_rate=learning_rate, random_state=42)
        model.fit(X_tr, y_tr)

        proba = model.predict_proba(X_te)[:, 1]
        mlflow.log_metrics({
            "f1": f1_score(y_te, proba > 0.5),
            "roc_auc": roc_auc_score(y_te, proba),
        })
        mlflow.sklearn.log_model(
            model, name="model",
            signature=infer_signature(X_tr.head(5)),
            input_example=X_tr.head(5))
        print("run_id =", run.info.run_id)

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--smoke", action="store_true", help="小样本冒烟训练,供 CI 使用")
    p.add_argument("--max-iter", type=int, default=200)
    p.add_argument("--lr", type=float, default=0.1)
    a = p.parse_args()
    main(a.smoke, a.max_iter, a.lr)

步骤 3:跑两次对比实验

python src/train.py --max-iter 200 --lr 0.1
python src/train.py --max-iter 400 --lr 0.05

步骤 4:打开 MLflow UI 对比

mlflow ui --backend-store-uri sqlite:///mlflow.db --port 5000
# 浏览器打开 http://127.0.0.1:5000

在 Experiments 页面:按 roc_auc 排序所有 run;勾选两个 run 点 Compare,看参数与指标的并排表格和散点图;点进任意 run 能下载模型 artifact、查看签名。这就是「实验可翻旧账」的完整体验。

步骤 5:验证冒烟模式

python src/train.py --smoke
# 几秒内跑完,run 正常入库——CI 流水线要的就是这个速度

讲清楚要点

  1. Train/Val/Test 纪律:测试集只能最后碰一次,用它选参数等于考试前偷看卷子;调参用验证集(或交叉验证),且时序数据必须按时间切。
  2. 特征泄漏是最隐蔽的坑:problem_tle_hist 若不加 shift(1),就把「这道题今天 TLE 了」直接喂给模型预测「它会 TLE」,线下 AUC 虚高、线上原形毕露。任何聚合特征都要问一句:算这个值时,包含当前样本吗?
  3. run 的完整性 = 可复现性:参数、指标、git_sha、数据标识、模型文件、输入签名一个都不能少。半年后你能凭 run_id 一键定位一切。
  4. signature 与 input_example 不是装饰:签名声明模型的输入输出 schema,上线部署和注册中心校验都靠它拦截特征错位——这是训练阶段能做、上线阶段救命的事。
  5. 深度学习补充:换 PyTorch 后要点变为 torch.device 管理、early stopping、checkpoint 断点续训、mlflow.pytorch.log_model;大参数量时记录 GPU 型号与显存占用,因为复现实验要匹配资源。可用 mlflow.autolog() 快速起步,但生产脚本建议显式记录关键项。

你可能会懵的地方

  • 验证集分数很高,线上效果差,最可能是什么? 优先怀疑三件事:数据泄漏(含目标信息的特征混进来)、时间泄漏(随机切分导致用未来预测过去)、train/serve skew(线上特征口径与训练不同)。用「按时间切 + 历史特征 shift(1) + 共享特征代码」三板斧排查。
  • MLflow 把东西存哪了?能自定义吗? 默认在当前目录 mlruns/。元数据(参数/指标/标签)是 backend store,可用 SQLite/PostgreSQL;模型文件是 artifact store,可指向 S3/OSS。生产用 mlflow server 集中托管,第 5 课展开。
  • Experiment 和 Run 什么关系?怎么划分? 一个业务问题/一条管道一个 Experiment,一次训练一个 Run。不要把不同问题塞进同一实验,也不要为每点小改动开新实验,否则对比失去意义。
  • 怎么从几十个 run 里选出「最佳模型」? 先定业务指标(如高召回下的精确率),再按它排序过滤(如 metrics.roc_auc > 0.9),最后人工检查该 run 的数据版本与参数合理性。自动化择优在下一课用代码实现。
  • autolog 一行搞定,为什么还要手写 log_param? autolog 省事但记录的是框架默认项,团队口径不统一、缺业务上下文(如数据版本 tag)。约定:探索期用 autolog,进入生产管道前改为显式记录。

错法对照

错法问题正解
用测试集反复调参指标虚高,线上打脸测试集只评估一次,调参走验证集
时序数据 train_test_split 随机切用未来预测过去按时间顺序切分
聚合特征不排除当前样本数据泄漏,线下虚高groupby + shift(1) 再聚合
只记 accuracy 一个指标类别不平衡时毫无意义同时记 F1/AUC/精确率/召回率
log_model 不带 signature部署时特征错位静默出错统一传 signature 与 input_example

卡点急救

  1. mlflow ui 打不开或报端口占用:换 --port 5001;确认用的是与训练相同的 --backend-store-uri,否则看不到 run。
  2. log_model 报 warning 找不到 flavor:pip install scikit-learn 环境要与训练一致;mlflow.sklearn.log_model 需要 sklearn 已安装。
  3. UI 里 run 是空的:训练脚本里没写 mlflow.set_experiment/start_run 就不会记录;检查是否在 with mlflow.start_run() 块内执行了训练。

面试常问

  • MLflow Tracking 的架构是怎样的? 三层:客户端(训练脚本调 API);Backend Store 存元数据(实验/run/参数/指标/标签,SQLite 或 PostgreSQL);Artifact Store 存大文件(模型、图表,本地盘或 S3/OSS)。生产部署 mlflow server 集中托管,团队共享同一实验库。
  • 如何系统性地组织实验管理? 命名规范(experiment 按业务问题、run 名有意义)+ 必记字段清单(git_sha、数据版本、切分方式)+ 指标口径统一 + 定期归档失败 run。让「翻旧账」变成 30 秒的事而不是半天的考古。
  • 什么是数据泄漏?举一个 ML 场景的例子并说明排查方法。 训练特征里混入了预测时刻拿不到的信息。例:按 problem_id 全量统计 TLE 率再回填到每条样本,当前提交的标签已包含在统计里。排查:对比线下/线上指标差距;审查聚合特征是否 shift;做时间外推验证(用旧数据训、新数据测)。

自测三问

  1. 为什么时序判题数据必须按时间切分而不是随机切?
  2. 一次完整的 MLflow run 至少要记录哪五类信息?
  3. problem_tle_hist 为什么必须 shift(1)?不 shift 会发生什么?

小结一句话

训练工程 = 纪律(切分、防泄漏、可复现)+ 记录(MLflow 把参数、指标、数据与代码版本、模型文件绑成一份档案)。

延伸

  • MLflow 官方文档:Tracking——run 生命周期、autolog 与各 flavor 的权威说明。
  • scikit-learn:模型评估与交叉验证——指标选型与时间序列切分的官方指引。
  • 下一课:MLflow 与血缘——模型注册中心、staging 到 production 的晋升流程与数据血缘。
加载本节练习…
← 上一课版本控制与 DVC下一课 →MLflow 与血缘