能复活一次训练的最小记录
一句话总结
能让一次训练重新复现的最小记录单位是运行(run),实验跟踪则是一种装置,强制把这份记录留在文件里,而不是留在人的记忆里。
为什么需要它
做模型的工作大多悄无声息。在 Jupyter 笔记本里把学习率调高一点,把轮次加多试试,换个随机种子再跑一遍。数字变好了就高兴,变差了就改回去。在这个过程中真正留下来的,只有最后运行的那个单元格的输出。然后过了两周,有人问:“上周不是说出了个更好的结果吗?”
这时想要复现,三件事会同时成为问题:当时是什么参数,当时是什么数据,以及当时的训练代码与现在的代码是否一样。三样里只要缺一样,同样的数字就不会再出现。更糟糕的是 会出现相近的数字。如果完全不同,人就知道哪里错了;但如果得到的是 0.76 而不是 0.78,人就会想“原来大概就是这个水平吧”,然后略过去。就这样略过去的记录,再也无法恢复。
MLflow 的文档把运行定义为“把数据科学代码执行一次”,并写道每个运行会同时记录元数据(指标、参数、开始和结束时间)和产物(模型权重、图像之类的输出文件)(MLflow Tracking)。定义之所以是这个样子,原因很明确:有了这四样,才能把这次运行重新立起来,缺了任何一样,这次运行就成了“据说有过”的传闻。
工作原理
跟踪工具实际做的事很简单。训练开始时打开一个运行,每当代码调用 log_param 和 log_metric,就把那个值写进存储,结束时关闭。如果没有另外搭建服务器,记录就堆在本地目录里。想改变存储位置,就另外配置跟踪环境。
import mlflow
with mlflow.start_run():
mlflow.log_param("lr", 0.001)
# 학습 코드
mlflow.log_metric("val_loss", val_loss)
运行积累多了,接下来就是查询。MLflow 用 MlflowClient.search_runs 支持“这个实验中验证损失最低的运行”这类查询,从 MLflow 3 起,还可以用 search_logged_models 把指标和参数条件写成类似 SQL 的字符串来筛选、查找模型。这里重要的是 把排序标准写成代码。如果由人用眼睛扫一遍表格来挑,下周再挑一次就会得出不同的答案。
该记录的东西的清单,各个工具略有不同,但从复现这个目的倒推,大多会抵达同一个地方:参数、指标,以及 输入的身份。MLflow 的数据集跟踪就是为了这第三样,给每个数据集附上一个含有名称、摘要(指纹)和原始位置的对象,挂到运行上(MLflow Dataset Tracking)。需要指纹的原因是,文件名不是版本。train.csv 无论昨天还是今天,都是 train.csv。
自动记录(autolog)是由库代替你把这一切写下来的功能,对于支持列表里的框架,用 mlflow.autolog() 一行就能打开(Automatic Logging)。它很方便,但如果不知道写下了什么,也就不知道漏掉了什么。所以第一次最好还是手动写一遍。
在现场相遇的样子
最常见的事故不是没用工具,而是 只用了一半。参数写了,却没有数据版本;指标有,却没有代码提交。这样表格很漂亮,却没有一行能复现。因为有表格,大家都感觉不到问题,直到出现法规应对或事故调查这类“请证明这个模型是怎么做出来的”的要求时,才一下子暴露出来。
第二是存储位置。如果在容器里跑训练,又把结果写在容器里,那么这个 Pod 消失的时候,记录也随之消失。实验 Pod 也没有卷,会话一结束,/root 就整个没了,这不是什么不方便的限制,而是现实的缩影。记录必须留在 计算发生的地方之外。
第三是名字。把运行标识符起成 test、test2、test_final 的习惯,当天是方便,一个月后就什么也说明不了。标识符不是给人读的,而是用来与其他记录相连的,所以简短且不重复就够了。至于“尝试了什么”,则写在参数和备注里。
下一项实验要做什么
用一个 JSON Lines 文件亲手做出实验账本。每次运行都留下参数、指标,以及代码和数据的 SHA-256 指纹,用目标指标以代码的方式挑出最佳运行,并且只凭这份记录把同样的数字重新做出来。最后打开一个参数没有被记下就消失了的运行的日志,把缺少什么才导致无法复现,按条目数出来。