机器学习实验追踪:从可复现性到工程化协作的实战体系

📅 2026/7/4 23:43:36
机器学习实验追踪:从可复现性到工程化协作的实战体系
1. 项目概述为什么实验追踪不是“锦上添花”而是机器学习工程的生存线你刚跑完第7个模型变体准确率从82.3%涨到82.7%但你突然想不起——这个结果对应的是用了Dropout还是BatchNorm学习率是0.001还是0.002数据预处理里到底有没有做min-max归一化更糟的是同事发来一个PR说他复现了你的SOTA结果可你本地一跑指标差了5个百分点而你们俩的代码diff只有三行……这种场景我过去三年在三家不同规模的AI团队里平均每周至少撞见两次。这不是偶然失误而是缺乏系统性实验追踪的必然代价。Machine Learning Experiment Tracking机器学习实验追踪绝非“等模型跑通再补”的附加功能它是把散落的实验碎片——超参数、代码版本、硬件环境、中间指标、可视化图表、数据快照——拧成一条可回溯、可对比、可协作的完整证据链。它解决的不是“怎么记录”而是“当17个实验并行、4个工程师交叉迭代、3次紧急线上修复后我们还能不能确定哪一次改动真正起了作用”。适合谁刚用print(loss)调试的研究生、靠Excel管理50实验的算法组长、正在被MLOps平台报价单劝退的CTO——只要你的模型还在迭代你就已经需要它了。核心关键词实验追踪、MLflow、Weights Biases、DVC、可复现性、超参数管理、模型版本控制。这不是教你怎么装一个工具而是带你亲手搭起一套能扛住真实研发节奏的追踪骨架。2. 整体设计与思路拆解从“记笔记”到“建档案馆”的范式跃迁2.1 为什么不能只靠Git Excel 手动截图很多团队起步时都试图用“轻量方案”把超参数写进Python注释把loss曲线截图存进共享网盘把关键指标手敲进Excel表格。我试过也带新人这么干过结果很统一——两周后Excel里出现“v2_final_v2_better.xlsx”和“v2_final_v2_better_20231015.xlsx”两个文件没人记得哪个是最终提交截图文件夹里堆着300多张命名如“loss_curve_epoch50_run3.png”的图想比对两个实验的验证集波动得手动打开12个图片挨个拖动标尺。问题根源在于机器学习实验的本质是高维、异构、强依赖的状态快照。它包含5类不可割裂的信息代码态commit hash、分支名、requirements.txt配置态learning_rate3e-4, batch_size64, optimizerAdamW运行态GPU型号、CUDA版本、训练耗时、显存峰值指标态train/acc10.923, val/f1_macro0.871, test/roc_auc0.942产物态模型权重文件、特征重要性图、混淆矩阵热力图Excel只能存二维表格Git只管代码文本截图只是视觉残影——它们各自抓住一根线却织不出一张网。真正的实验追踪系统必须像档案馆管理员给每个实验分配唯一ID如exp-20240521-0832-7f3a自动采集全部5类信息建立跨维度索引比如“查所有learning_rate0.001且val/f1_macro0.85的实验”并支持一键回放reproduce——点一下按钮就能拉取当时的代码、安装指定依赖、加载原始数据、重跑训练得到完全一致的结果。这背后是三个底层设计原则自动化采集拒绝人工填写、结构化存储JSON/YAML而非自由文本、声明式关联明确标注“这个模型文件属于这个实验ID”而非靠文件夹命名猜测。2.2 工具选型开源三巨头的核心差异与适用场景当前主流开源方案有MLflow、Weights BiasesWB、DVC它们不是替代关系而是解决不同切面的协同工具。我带过的6个落地项目中80%采用“MLflow DVC”组合15%用WB全栈5%纯自研仅限超大规模定制场景。选型逻辑必须基于你的瓶颈点维度MLflowWeights BiasesDVC核心定位实验元数据管理平台记录什么全流程协作分析平台记录分析协作数据与模型版本控制系统管住产物最痛快的功能mlflow.log_param()一行代码记超参mlflow.log_metric()实时流式记录指标UI界面按任意字段筛选实验自动捕获GPU利用率、内存占用内置对比视图side-by-side curve comparison团队级权限管理与评论功能dvc add model.pkl自动计算文件哈希并生成.dvc元数据dvc repro一键重跑整个pipeline最常踩的坑默认不存原始数据/模型文件需额外配artifact存储如S3多人协作时需部署server本地file://模式无法共享商业版才支持私有化部署免费版有5GB存储上限大模型权重几下就超离线环境无法使用不直接记录超参数和指标需配合MLflow或自定义脚本才能形成完整实验记录我的实操建议起步阶段1~3人日均10实验用MLflow本地模式mlflow ui搭配DVC管理数据集和模型文件。成本为零5分钟启动覆盖90%基础需求。增长阶段5~10人需跨团队对比部署MLflow ServerPostgreSQLMinIO同时接入WB做可视化分析。用MLflow管元数据WB管洞察避免单点故障。生产阶段20人合规审计要求MLflow Server DVC 自研Hook。例如在mlflow.start_run()前插入钩子自动调用dvc status获取数据版本并将dvc.yaml哈希写入MLflow tag。提示别被“全功能平台”诱惑。我见过团队花两周部署WB企业版结果发现80%时间只用它的曲线对比功能——而MLflow加一个mlflow.log_figure()就能实现。先解决“能不能记”再优化“好不好查”。2.3 架构设计三层分离模型保障长期可维护性所有可持续的实验追踪系统都遵循清晰的三层架构采集层Instrumentation Layer在训练脚本中嵌入追踪代码负责“感知”实验状态。这是唯一需要修改业务代码的部分必须做到侵入性最小。例如用装饰器封装训练函数track_experiment( experiment_nametext_classification, params_to_log[learning_rate, batch_size, model_type] ) def train_model(): # 原始训练逻辑 ... mlflow.log_metric(val/f1, f1_score) mlflow.log_artifact(confusion_matrix.png)这样业务代码只关注模型逻辑追踪逻辑由装饰器统一注入。传输层Transport Layer负责将采集的数据安全、可靠地送达存储后端。关键考量是失败容忍——训练进程崩溃时已采集的指标不能丢失。MLflow默认使用本地SQLite但生产环境必须切换为HTTP API指向MLflow Server并启用mlflow.set_tracking_uri(http://mlflow-server:5000)。我们曾因未配重试机制导致网络抖动时3个实验的指标丢失后续强制加入max_retries3参数。存储与服务层Storage Serving Layer持久化数据并提供查询接口。这里必须区分“元数据”和“产物”元数据实验ID、参数、指标存PostgreSQL支持复杂SQL查询产物模型文件、图片、日志存对象存储MinIO/S3通过URI关联到元数据。混用会导致数据库膨胀一个1GB模型文件塞进PostgreSQL别闹也违背云原生设计原则。这套分层设计的价值在于当某天你需要替换UI比如从MLflow UI换成自研Dashboard只需重写服务层采集层和传输层完全不动——这才是工程化的底气。3. 核心细节解析与实操要点让每一行代码都成为可追溯的证据3.1 超参数记录不只是“记下来”更要“能推理”很多人以为mlflow.log_param(lr, 0.001)就够了但实际中参数的语义完整性才是复现的关键。举个真实案例同事A的实验报告写“lr0.001”同事B复现时设了optimizer Adam(model.parameters(), lr0.001)结果指标差了3%。原因A用的是AdamW且设置了weight_decay0.01而B沿用了默认Adam。所以参数记录必须包含三层信息值Valuelr0.001上下文ContextoptimizerAdamW,weight_decay0.01来源Sourcefrom config.yaml#training.lr而非硬编码实操方案强制使用配置中心所有超参数从YAML/JSON文件读取禁止硬编码。例如config.yamltraining: optimizer: AdamW lr: 0.001 weight_decay: 0.01 batch_size: 32 model: name: bert-base-uncased dropout: 0.1递归记录全部配置用mlflow.log_dict(config, config)而非逐个log_param。这样不仅记录值还保留层级结构查询时可写params.config.training.lr 0.0005。校验参数合法性在训练前加入断言例如assert 1e-5 config[training][lr] 1e-2, LR out of safe range mlflow.log_param(lr_safe_check, passed)这个lr_safe_check标签未来能帮你快速过滤掉所有参数越界的实验。注意别忽略随机种子mlflow.log_param(seed, 42)必须和torch.manual_seed(42)、np.random.seed(42)、random.seed(42)严格同步。我们曾因漏设tf.random.set_seed(42)导致TensorFlow实验无法复现排查了两天。3.2 指标记录从“终点快照”到“过程脉搏”初学者常犯的错误是只在训练结束时log_metric(final_acc, acc)这等于只拍了一张X光片却忽略了整个诊疗过程。真正的追踪要捕捉动态健康度粒度选择每10个step记录一次loss每1个epoch记录一次val_acc。太密每step撑爆数据库太疏只终值丢失收敛细节。计算依据假设1个epoch1000步100个epoch总步数10万每10步记录1万条指标MySQL轻松承载。命名规范用斜杠分隔语义层级如train/loss,val/acc1,test/precision_per_class/0。这样在UI里能自动聚类所有train/*归为一组也支持通配符查询metrics LIKE val/%。异常检测埋点在关键节点插入健康检查if step % 100 0: mlflow.log_metric(train/loss, loss.item()) # 异常检测loss突增可能预示梯度爆炸 if loss.item() last_loss * 3: mlflow.log_param(anomaly_detected, floss_spike_at_step_{step}) mlflow.log_metric(anomaly_magnitude, loss.item() / last_loss) last_loss loss.item()这些anomaly_*标签后期能帮你批量识别“不稳定实验”节省50%的debug时间。3.3 产物管理模型、数据、图谱的三位一体绑定实验产物不是孤岛它们之间有强依赖关系。一个model_v3.pth文件必须明确绑定它由哪个实验ID生成exp-20240521-0832-7f3a它训练所用的数据版本dataset-v2.1-d7a3c来自DVC它对应的代码版本git commit 7f3a9b2实操步骤DVC初始化数据集dvc init dvc remote add -d myremote s3://my-bucket/ml-data dvc add data/train.csv # 生成data/train.csv.dvc git add data/train.csv.dvc git commit -m add train dataset v2.1训练脚本中绑定DVC与MLflowimport dvc.api from mlflow.tracking import MlflowClient # 获取当前数据版本 data_version dvc.api.get_url(data/train.csv).split(/)[-1] # e.g., train.csv.b5a2f mlflow.log_param(data_version, data_version) # 训练完成后用DVC追踪模型 import subprocess subprocess.run([dvc, add, models/best_model.pth]) mlflow.log_artifact(models/best_model.pth.dvc) # 记录DVC元数据构建可复现流水线在dvc.yaml中声明stages: train: cmd: python train.py --config config.yaml deps: - data/train.csv - config.yaml outs: - models/best_model.pth这样dvc repro会自动检查data/train.csv和config.yaml是否变更只重跑必要步骤。实操心得模型文件别直接log_artifact.pth文件可能含绝对路径或环境相关tensor导致跨机器加载失败。正确做法是训练后用torch.save({state_dict: model.state_dict(), config: config}, ...)保存纯字典或用MLflow的mlflow.pytorch.log_model()它会自动打包依赖和加载逻辑。4. 实操过程与核心环节实现从零搭建可落地的追踪系统4.1 环境准备5分钟完成本地最小可行系统无需服务器、无需云服务用笔记本就能跑通全流程。这是我给新入职算法工程师的“第一天任务”确保他们理解追踪不是玄学步骤1安装核心组件# 创建独立环境强烈推荐避免包冲突 conda create -n mlflow-env python3.9 conda activate mlflow-env pip install mlflow scikit-learn pandas matplotlib dvc[s3] # DVC加s3支持为后续扩展铺路步骤2初始化MLflow后端# 创建本地跟踪目录 mkdir ./mlruns # 启动MLflow UI后台运行不阻塞终端 nohup mlflow ui --backend-store-uri file:./mlruns --host 0.0.0.0 --port 5000 mlflow.log 21 # 验证浏览器打开 http://localhost:5000看到空的实验列表步骤3编写第一个可追踪训练脚本train_simple.pyimport mlflow import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split # 设置实验名称自动创建若不存在 mlflow.set_experiment(quickstart-demo) with mlflow.start_run(): # 自动分配run_id # 1. 记录超参数 n_estimators 100 max_depth 5 mlflow.log_params({n_estimators: n_estimators, max_depth: max_depth}) # 2. 生成模拟数据 X, y make_classification(n_samples1000, n_features20, random_state42) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2) # 3. 训练模型 clf RandomForestClassifier(n_estimatorsn_estimators, max_depthmax_depth, random_state42) clf.fit(X_train, y_train) # 4. 记录指标 train_acc clf.score(X_train, y_train) test_acc clf.score(X_test, y_test) mlflow.log_metrics({train/accuracy: train_acc, test/accuracy: test_acc}) # 5. 记录模型MLflow自动序列化 mlflow.sklearn.log_model(clf, model) # 6. 记录特征重要性图 import matplotlib.pyplot as plt plt.figure(figsize(6,4)) plt.bar(range(len(clf.feature_importances_)), clf.feature_importances_) plt.title(Feature Importances) plt.savefig(feature_importance.png) mlflow.log_artifact(feature_importance.png) plt.close()步骤4执行并验证python train_simple.py # 刷新 http://localhost:5000看到新实验quickstart-demo点击进入可查看 # - Parameters标签页n_estimators100, max_depth5 # - Metrics标签页train/accuracy0.982, test/accuracy0.941 # - Artifacts标签页model/ 和 feature_importance.png这个5分钟流程已覆盖实验追踪的全部核心能力参数、指标、模型、可视化。它不完美没用DVC管数据但足够让新人建立直觉——追踪的本质是让每一次运行都留下指纹。4.2 进阶实战构建跨环境可复现实验流水线当团队扩大本地模式不再够用。下面是以真实电商推荐项目为例的生产级流水线我在上一家公司用它支撑了日均200实验目标场景数据用户行为日志10TB存HDFS训练PyTorch模型需GPU集群发布模型需部署到Kubernetes供API服务调用系统架构[Git Repo] → [CI/CD Pipeline] → [MLflow Server] ←→ [MinIO (Artifacts)] ↓ ↓ ↑ [DVC Repo] [Training Job] [PostgreSQL (Metadata)]关键配置文件dvc.yaml定义数据与代码依赖stages: prepare_data: cmd: python scripts/prepare_data.py --date ${DATE} deps: - scripts/prepare_data.py - hdfs://namenode:8020/data/raw/${DATE}/ outs: - data/processed/${DATE}/ train_model: cmd: python scripts/train.py --config configs/${CONFIG}.yaml deps: - scripts/train.py - configs/${CONFIG}.yaml - data/processed/${DATE}/ outs: - models/${CONFIG}_${DATE}/ metrics: - models/${CONFIG}_${DATE}/metrics.jsonmlflow_server_config.pyMLflow Server配置# 使用PostgreSQL存元数据MinIO存产物 MLFLOW_TRACKING_URI http://mlflow-server:5000 MLFLOW_ARTIFACT_ROOT s3://ml-artifacts/ # 环境变量注入 os.environ[MLFLOW_TRACKING_URI] MLFLOW_TRACKING_URI os.environ[MLFLOW_S3_ENDPOINT_URL] http://minio:9000CI/CD触发脚本Jenkinsfilepipeline { agent any environment { MLFLOW_TRACKING_URI http://mlflow-server:5000 DATE sh(script: date %Y%m%d, returnStdout: true).trim() CONFIG prod_v2 } stages { stage(Setup) { steps { sh pip install mlflow dvc[s3] sh dvc remote add -d minio s3://ml-artifacts/ } } stage(Run Experiment) { steps { // DVC自动拉取数据版本 sh dvc pull data/processed/${env.DATE}/ // 启动MLflow run注入环境变量 sh mlflow run . --experiment-name recommendation-prod -P CONFIG${env.CONFIG} -P DATE${env.DATE} } } } }训练脚本核心逻辑scripts/train.pyimport mlflow import dvc.api def main(): parser argparse.ArgumentParser() parser.add_argument(--config, typestr) args parser.parse_args() # 1. 加载配置自动记录 config load_yaml(args.config) mlflow.log_dict(config, config) # 2. 获取DVC数据版本自动记录 data_version dvc.api.Repo().get_url(fdata/processed/{config[date]}/) mlflow.log_param(data_version, data_version) # 3. 训练省略具体模型代码 model train(...) # 4. 记录指标与模型 metrics evaluate(model) mlflow.log_metrics(metrics) mlflow.pytorch.log_model(model, pytorch-model, registered_model_namerecommendation-model) # 5. 关键一步记录本次实验的DVC状态用于回放 dvc_status subprocess.check_output([dvc, status]).decode() mlflow.log_text(dvc_status, dvc_status.txt) if __name__ __main__: main()效果验证在MLflow UI中每个实验Run下都有dvc_status.txt内容如data/processed/20240521/ : updated configs/prod_v2.yaml : unchanged当需要复现时运维同学只需# 1. 克隆DVC repo git clone https://gitlab.com/team/dvc-repo.git cd dvc-repo # 2. 检出对应commit从dvc_status.txt或MLflow UI的git hash获取 git checkout abc1234 # 3. 拉取数据 dvc pull data/processed/20240521/ # 4. 运行训练用MLflow记录的config mlflow run . -P CONFIGprod_v2 -P DATE20240521全程无需人工记忆任何路径或版本号系统自动对齐。这就是工程化的威力——把“人脑记忆”转化为“机器可执行指令”。4.3 可视化与协作让实验洞察从“个人笔记”变成“团队资产”追踪系统的终极价值不在记录而在驱动决策。MLflow UI虽简洁但面对200实验时筛选效率骤降。我们通过三个技巧将其升级为协作中枢技巧1自定义Dashboard嵌入关键指标MLflow支持mlflow.log_figure()记录Plotly/Dash图表。我们在每个实验结束时自动生成“决策看板”import plotly.graph_objects as go from plotly.subplots import make_subplots # 创建4宫格看板 fig make_subplots(rows2, cols2, subplot_titles(Train Loss, Val Acc, Confusion Matrix, Feature Importance)) # 添加子图省略具体绘图代码 fig.add_trace(..., row1, col1) fig.add_trace(..., row1, col2) fig.add_trace(..., row2, col1) fig.add_trace(..., row2, col2) # 作为整体figure记录 mlflow.log_figure(fig, dashboard.html) # 自动渲染为交互式HTML效果点击Artifact里的dashboard.html直接看到动态可缩放的看板无需下载图片再打开。技巧2用Tags建立跨实验知识图谱MLflow的set_tag()常被忽视但它能构建实验间的语义网络。例如# 标记实验类型 mlflow.set_tag(experiment_type, ablation_study) # 消融实验 mlflow.set_tag(experiment_type, hyperparam_tuning) # 超参调优 # 标记业务影响 mlflow.set_tag(business_impact, CTR_increase_2.3%) # 线上AB测试结果 # 标记负责人 mlflow.set_tag(owner, aliceteam.com)然后在UI搜索栏输入tags.experiment_type ablation_study and tags.business_impact ! 瞬间列出所有产生业务价值的消融实验方便产品经理快速评估技术投入产出比。技巧3集成Slack通知让关键进展主动触达在训练脚本末尾加入import requests def notify_slack(run_id, metrics): webhook_url https://hooks.slack.com/services/XXX/YYY/ZZZ message f✅ 实验完成\n*Run ID:* {run_id}\n*Test AUC:* {metrics[test/roc_auc]:.4f}\n*Dashboard:* http://mlflow-server:5000/#/experiments/1/runs/{run_id} requests.post(webhook_url, json{text: message}) # 调用 notify_slack(mlflow.active_run().info.run_id, metrics)结果每次实验达标如test/roc_auc 0.92算法群自动弹出消息附带直达链接。新人不再需要问“谁在跑什么”信息自动流动。实操心得别追求“完美UI”。我们曾花两周开发自定义Dashboard结果发现80%的日常操作筛选、对比、下载模型MLflow原生UI 3次点击就能完成。把精力放在“如何让信息更快到达决策者”而不是“如何让UI更好看”。5. 常见问题与排查技巧实录那些文档里不会写的血泪教训5.1 “指标没显示”——90%是时间戳与异步写入的锅现象训练脚本里写了mlflow.log_metric(loss, 0.123)但MLflow UI里该指标始终为空或延迟10分钟才出现。排查路径确认后端模式本地file://模式下指标写入是同步的但HTTP API模式http://mlflow-server默认启用异步批处理防止网络抖动导致训练中断。检查日志训练脚本添加mlflow.set_tracking_uri(http://mlflow-server:5000)后运行时看终端是否有INFO mlflow.tracking.fluent: Logging to tracking server at http://mlflow-server:5000。若无说明URI未生效。强制刷新在log_metric后加mlflow.flush()MLflow 2.9或设置环境变量MLFLOW_HTTP_REQUEST_TIMEOUT120延长超时。根本解法在训练循环中每100步调用一次mlflow.flush()并捕获异常try: mlflow.log_metric(train/loss, loss.item(), stepstep) if step % 100 0: mlflow.flush() except Exception as e: print(fMLflow log failed at step {step}: {e}) # 降级写入本地log文件后续人工补录 with open(mlflow_fallback.log, a) as f: f.write(f{step},{loss.item()}\n)5.2 “模型加载失败”——路径、设备、版本的三重陷阱现象用mlflow.pytorch.load_model(runs:/run_id/model)加载模型报错ModuleNotFoundError: No module named models或RuntimeError: Expected all tensors to be on the same device。避坑清单陷阱1自定义模块路径若模型类定义在src/models/bert.py训练时需确保该路径在Python path中import sys sys.path.append(src/) # 必须在load_model前执行 model mlflow.pytorch.load_model(runs:/...)陷阱2GPU/CPU设备不匹配load_model默认加载到CPU。若模型在GPU上训练需指定map_locationmodel mlflow.pytorch.load_model(runs:/..., map_locationtorch.device(cpu)) # 或加载到GPU model mlflow.pytorch.load_model(runs:/..., map_locationtorch.device(cuda:0))陷阱3PyTorch版本不兼容PyTorch 1.12保存的模型用1.13加载可能失败。解决方案训练脚本开头记录版本mlflow.log_param(torch_version, torch.__version__)加载前校验assert torch.__version__ 1.12.1否则报错提示升级。5.3 “DVC push失败”——权限、网络、哈希的连锁反应现象dvc push报错ERROR: failed to upload models/best.pth to s3://bucket/models/best.pth - An error occurred (AccessDenied) when calling the PutObject operation。系统性排查表步骤检查项命令/方法预期结果1. 权限验证AWS凭证是否有效aws sts get-caller-identity返回账户ARN2. 存储桶访问是否有s3:GetObject权限aws s3 ls s3://bucket/列出内容3. DVC远程配置endpoint_url是否正确cat .dvc/config包含[remote minio]段4. 文件哈希一致性本地文件是否被篡改dvc status -c显示Data and pipelines are up to date5. 网络连通性MinIO服务是否可达curl -v http://minio:9000/minio/health/liveHTTP 200终极解法在CI/CD中加入预检脚本#!/bin/bash # pre_dvc_check.sh echo DVC Pre-check dvc version dvc remote list dvc status -c || { echo DVC status failed!; exit 1; } aws s3 ls s3://ml-artifacts/ || { echo S3 access failed!; exit 1; } echo All checks passed.把它加入Jenkins pipeline的stage(Pre-check)失败即停避免浪费GPU资源。5.4 “实验ID混乱”——命名规范救不了的架构缺陷现象团队抱怨“找不到上周那个效果最好的实验”因为MLflow自动生成的run_id是UUID如7f3a9b2e4c1d...人类无法记忆。表面解法无效用mlflow.create_experiment(namev2_best_20240521)——但一个实验下可有无数Runs仍需找Run ID。用mlflow.set_tag(run_name, bert_lr0.001_wd0.01)——但UI搜索不支持模糊匹配输错一个字符就找不到。架构解法强制Run命名规则在训练脚本开头用mlflow.start_run(run_namef{config[model]}_lr{config[lr]}_wd{config[wd]})。建立Run Name索引表每次start_run后自动写入MySQLimport pymysql conn pymysql.connect(hostmysql, usermlflow, passwordxxx) cursor conn.cursor() cursor.execute(INSERT INTO run_index (run_id, run_name, owner, created_at) VALUES (%s, %s, %s, NOW()), (run_id, run_name, aliceteam.com)) conn.commit()提供简易查询接口# 查找所有bert模型的实验 mysql -u mlflow -p -e SELECT run_name, run_id FROM run_index WHERE run_name LIKE %bert%;这样新人问“bert最佳结果在哪”你回复SELECT * FROM run_index WHERE run_name LIKE bert%_wd0.01 ORDER BY created_at DESC LIMIT 1;3秒给出答案。最后分享一个小技巧在MLflow UI的Search栏用params.model_type bert and metrics.val/f1_macro 0.