仓库里 tuff-rl 这个名字,只活了三个多小时。

Tuff Bird 一共 12 个提交,不到三天,翻起来很快。第一条的正文里写的还是 tuffrl.init(),那是它最早的样子:往日志目录里写 TensorBoard 的事件文件,同时在本地起一块看板,跟着这些文件一路画下去。

第一天

第一个提交里就带了 CartPole 的例子、测试、CI,还有一个往 PyPI 发包的流程。七分钟后来了第二个提交:看板在 GitHub 的 macOS 机器上起得太慢,启动器等不及了。原因是标准库的 HTTPServer 绑定时会去解析本机的完整域名,卡在反向 DNS 上;反向 DNS 慢的机器都可能这样,所以改成绑定时不查。

第一个提交之后两个多小时,0.1.0 发到了 PyPI,名字还是 tuff-rl。不到一个小时后,改名的提交来了,包名、导入名、命令和环境变量一起换成 tuff-bird。再过一个半小时,0.1.1 发出去,说明里写着旧名字的 0.1.0 会留在 PyPI 上,照发布时的样子。写这篇的时候我去看了一眼,它还在,只是被标成了 yanked,也就是默认不会再被装上;理由栏里是一句英文,意思是已改名为 tuff-bird,请改装 tuff-bird。什么时候标的,我没查到。

名字改得不算晚,但第一个版本已经用旧名字发出去了。

0.1.0 的说明里写了几件事。包本身没有依赖,事件文件的分帧和 protobuf 的编码,README 说是在包里自己实现的,所以用不着 TensorFlow,也用不着 protobuf。一个日志目录只有一块看板,第一个训练脚本把它拉起来,后面的脚本加进去。在 SSH 里它不开浏览器,把端口转发的命令打印出来。

README 里还有几条:用 torch.distributed 的话,只有 rank 0 去启动看板;脚本抛了异常,这个 run 记成失败,按 Ctrl-C 停的记成停止。

两天多以后

0.2.0 加的是画图。同一个目录里的 run 当成同一个配置的几个种子,每条曲线是它们的平均值,带 95% 的 bootstrap 置信区间,这个区间是 Agarwal 等人 2021 年那篇论文建议报告的,四分位均值是个选项。图下面还配一段 caption。README 里的例子大意是:线是几条 run 的平均,n = 3;阴影是 95% 置信区间,重采样 2000 次;每条 run 先做了权重 0.6 的指数滑动平均再聚合;横轴停在最短那条 run 停的地方。

图能藏的东西不少,平滑了多少,截没截过,看曲线是看不出来的,只能靠这段话说。三个种子的区间本来就经不起细看,n 写出来,读的人至少知道该怎么掂量。

最后那句对应一条规矩:曲线到最短的那条 run 停下就停。要是接着画,后半段就只剩更少几条 run 的平均,和前半段混在一条线上,看起来很顺,其实已经不是同一个量了。截断之后,曲线的末端和起点一样,都是所有 run 的平均。也留了一个选项,可以关掉截断。

看板上的每张图还多了一个按钮,点一下,把画出这张图的 Python 代码连同看板上的当前设置一起复制走,看到的图,可以按同样的设置画成论文里的图。

一个小时后

0.2.0 发出去一个多小时,0.2.1 就来了。只有两处修复,一处是排版,图比一行能放的少,就撑满一行,比如只有一个 tag 的图。另一处改的是 caption:横轴停在最短 run 的那句话,现在只在真有步数被丢掉的时候才写。Summary.truncated 也只在那时才为真,Summary.disjoint 则用来说明这几条 run 一步都没重合。

照这个改法看,0.2.0 的 caption 在没丢步数的时候也会说这句。我觉得一句每张图都有的话,读的人很快就不看了;只在真丢了步数的时候才写,读的人才会当回事。我猜这一个多小时里想的就是这个,提交里没这么写。

README 里讲笔记本的那一节,写明了测过的环境:笔记本里的图只在 JupyterLab 4.6 里测过,VS Code、Colab 和 GitHub 的笔记本视图都还没测。