代码找不到、硬件已过时、文档也缺失…… 几十年前的代码复现起来没那么容易。
机器之心报道,编辑:张倩、杜伟。
「敢不敢把你十年前写的代码翻出来看看还能不能运行?」在最近的一篇社论文章中,《Nature》介绍了两位法国科学家发起的一项挑战。
这项挑战名为「Ten Years Reproducibility Challenge」,由法国国家数字科学技术研究所的计算机神经科学家 Nicolas Rougier 与法国国家科学研究中心的理论生物物理学家 Konrad Hinsen 共同发起,旨在鼓励各个领域的研究者翻出自己十年前(或更早)的代码,看看这些代码到底还能不能运行,研究能否复现。
Rougier 认为,尽管计算在科学研究中占有越来越重要的比重,但研究者很少披露自己的底层代码。即使他们给出了代码,别人也很难去执行,就连作者本人有时也会遇到麻烦。而且,随着编程语言、计算环境的演化,现在还能运行的代码过段时间可能就不行了。
因此,「『十年代码复现挑战』的宗旨是找出那些十年之后仍然能够 work 的代码编写和发布技术,」Hinsen 表示。
这项比赛共吸引了 35 名参赛者。他们提出要复现 43 篇论文,其中 28 篇形成了可复现报告。这些论文涉及的语言包括 C、R、Mathematica 和 Pascal 等等,还有一位参赛者复现的不是代码,而是一个用 SBML(系统生物学标记语言)编写的分子模型。
当然,这条复现之路并没有想象中那么简单,有人代码找不到了,有人找到代码也不知道怎么运行。最后,他们通过这项比赛总结出了一些提高代码可复现性的技巧,可以为现在的工作提供借鉴。
老代码没那么容易 work
你的代码还在吗?
要完成这个挑战,首先你得找到自己当年的代码,有人在这一步就被卡住了。
Roberto DiCosmo 是法国国家信息与自动化研究所的一位计算机科学家,他在 1998 年的一篇论文中提到了一个名为「OcamlP3l」的并行编程系统。但在找遍自己和合著者的硬盘、备份之后,他也没能找到 OcamlP3l 系统的代码。
不过幸运的是,一个名为「Software Heritage」的源代码归档网站为他提供了一份备份。
Software Heritage 会定期抓取 GitHub 等开源代码网站,有点像定期抓取网页的互联网档案馆(Internet Archive)。开发者也可以请求 Software Heritage 抓取自己的库留作存档。
起初,DiCosmo 并没有考虑去 Software Heritage 找自己的代码,因为在他开发 OcamlP3l 的时候,Software Heritage 还没出现。他猜测,一定是有人将他的代码传到了 Gitorious 托管平台,而 Software Heritage 又在该平台关停之前抓取了上述代码。
你的文档还在吗?
当然,找到代码并不代表你还会用。威斯康星大学生物统计学家 Broman 表示,他想复现 2003 年的一份研究,但是由于文档遗失、文件组织形式古怪,他已经不知道要运行哪些代码才能复现研究,所以不得不回去阅读原文。
「在一个组织良好的项目里,文档的行数超过代码行数不是什么稀罕事儿,」加州大学伯克利分校的一位计算可复现性倡导者表示,「你要保留尽可能多的信息,对分析的结构有更广泛的描述,比如数据从哪儿来,数据、代码的一些元信息等,这些是复现的关键。」
Melanie Stefan 是爱丁堡大学的一位神经科学家,她想复现一个用 SBML 写的计算模型。尽管模型都在,但她却找不到自己当年用的参数值(如分子浓度),也没有很好地记录数据标准化的关键细节。
因此,Stefen 无法复现她的部分研究。「即使对于同一个人来说,很多十几年前再明显不过的模型细节现在也不明显了,真是令人始料未及!」她面无表情地说道。
你有运行代码的硬件吗?
作为比赛的组织者,Rougier 也参加了这次挑战。他重现的代码是 Apple II 中的一个图像放大器,这是整个挑战赛中最古老的代码。这段代码写于 32 年前,当时写的时候 Rougier 才 16 岁,还发表在了一本名为《Tremplin Micro》的杂志上(已倒闭)。
如今,即使拿着神秘的 AppleSoft BASIC 语言说明,他也不记得代码是怎么运行的了。「真是见了鬼了,这可是我自己写的,」Rougier 惆怅地说道。
但是,他可以在网上找到这段代码并使其在一个网页版 Apple II 模拟器上运行。要做到这点并不难,Rougier 表示,最难的部分是让它在一个真正的 Apple II 上运行。
对于 Rougier 来说,硬件不是问题,因为他办公室就有一台 Apple II,是一位同事在清理办公室时抢救出来的。但由于这款 Apple II 的年龄比 USB 线和互联网都大,而且当前的计算机无法与它的老式磁盘驱动相连。因此,在运行代码之前,Rougier 需要某种定制的硬件以及一盒老式磁盘。
他在亚马逊上找到了一些带有「New」字样的磁盘,但日期是 1993 年的。在对他的数据进行三次写入以确保比特稳定之后,磁盘开始运行了。
活动的发起者 Hinsen 也遇到了硬件方面的麻烦。他把自己 90 年代写的代码有条不紊地存到了磁带里,但现在,他已经没有能读取磁带的工具了。
你的计算环境过时了吗?
过时的计算环境也是压死参赛者的一根稻草。Sabino Maggi 是意大利的一名计算机物理学家,1996 年,他用 Fortran 语言建模了一个超导装置,并用 Microsoft Visual Basic 来处理结果。二十多年过去了,Fortran 并没有发生太大变化,因此 Maggi 只做了些微的调整就实现了代码的顺利编译。但始料未及的是,Visual Basic 给他出了一个难题。
Maggi 在报告中写道,「Visual Basic 是一门死掉的语言,早就被 Visual http://Basic.NET 取代了。」所以,为了运行二十多年前的 Visual Basic 代码,Maggi 不得不使用从网上找到的安装盘在自己的 Mac 电脑上重建了一个十年前的 Windows 虚拟机。
在安装之前,Maggi 遇到了一个问题:他根本不记得自己 96 年用的是哪个版本。这些年,微软发布了该语言的多个版本,而且并不总是向后兼容的。
同样受到计算环境问题困扰的还有 Ludovic Courtès,他是法国国家信息与自动化研究所的一名研究工程师。在这次挑战赛中,他复现了 2006 年一篇比较数据压缩策略的论文,代码是用 C 语言写的。由于 API 发生了变化,他的代码无法用现有软件库进行编译。为了解决这一问题,他不得不将 6 个计算组件回滚到很老的版本。
如今,研究者们可以用 Docker 和 Conda 虚拟环境来打包计算环境,以备不时之需。但有几位参赛者选择了其他方法,比如 Guix(一个 Linux 包管理器)。它可以保证环境直到最后一位都是可复现的,并且构建环境的代码版本是透明的。
「环境和整篇论文都可以检查,可以从源代码构建,」Courtès 表示。Hinsen 认为,Guix 可能是这个比赛「目前最好的可复现研究工具」。
违反直觉的是,很多参赛者发现,用一些比较古老的语言写的代码反而是最容易复现的。新语言快速变化的 API 和对第三方库的依赖使得它们很难复现。从这个意义上来说,今年刚刚停止支持的 Python 2.7 倒是一个不错的机会,它既是一门高级编程语言,又不会再进行更新。
如何提高代码可复现性?
在经历了复现代码的艰辛之后,相信每位参赛者都意识到了自己当年写代码时埋下的一些「隐患」,比如存储介质、所选语言、备份平台等。
那么,如何提高论文代码的可复现性呢?《Nature》文章的作者在文中给出了一个 checklist:
1. 代码。基于即点即击(point-and-click)界面的工作流(如 Excel)是不可复现的。你要将计算和数据操作保存在代码中;
2. 文件。使用注释、计算笔记本、README 文件等解释你的代码如何运行,定义期望的参数和所需的计算环境;
3. 记录。记下关键参数,如用于启动随机数生成器的「seed」值。这样的记录可以帮你重新运行代码、跟踪 bug 以及意外的结果;
4. 测试。创建一套测试函数。使用 positive 和 negative 控制数据集来确保你得到预期的结果,并在开发过程中运行这些测试,在 bug 出现时及时清除;
5. 保存。GitHub 是一个流行但并不永久的在线存储库。长期来看,Zenodo、Figshare 和 Software Heritage 等归档服务可能更加稳定;
6. 跟踪。使用 Git 等版本控制工具来记录你的项目历史,记下产生每个结果所用到的版本;
7. 打包。利用容器化工具(Docker、Singularity 等)、网页服务(Code Ocean、Gigantum、Binder)、虚拟环境管理器(Conda)等创建随时可用的计算环境;
8. 自动化。使用 Travis CI 等持续集成服务在不同的计算环境中定期自动测试你的代码;
9. 简化。避免使用会使后续利用复杂化的小众或难以安装的第三方代码库;
10. 检查。通过在一系列计算环境中运行代码来检查代码的可移植性。
此外,曼彻斯特大学的计算机科学家 Carole Goble 指出,将自己的代码开源也是一种提高可复现性的方式,这样别人就有机会在你的代码基础上进行修改,以保持其活力。
如果你也有十几、二十几年前写的代码,可以拿出来试试还能不能运行。