第五章 · 5.2

5.2 环境与复现:从 conda 到 WSL 的工程实践

Environments and Reproducibility
环境是实验的一部分:解释器、二进制扩展与框架版本共同划定依赖的可行域。本节剖析三重耦合——Python ABI、torch 与 CUDA、扩展算子包——如何令逐包解析失手,确立一实验一环境与分层锁定的原则;给出本书主力、TorchDrug 冻结沙箱与现代图学习三套环境的口径,讨论 WSL2 下 Windows 用户的现实路径,并以复现清单收束。

5.2.1 依赖地狱的机理:三重耦合

同一段代码,上月能跑,重装机器后报错;换一台工作站,指标差出零点几个百分点。多数时候代码无辜,是环境变了。分子机器学习的工具栈比一般 Python 项目更脆,根源在于它同时踩着三层彼此咬合的约束,任何一层滑动都会牵动其余两层。把约束本身看清,依赖地狱(dependency hell)就从玄学变成可推理的对象。

第一重:二进制扩展与解释器的 ABI。RDKit 的本体是一个庞大的 C++ 库,Python 里 import 到的只是绑定层。绑定层编译时就与解释器的应用二进制接口(application binary interface, ABI)锁死:对象布局、调用约定、错误处理都按特定 CPython 版本敲定,而 CPython 不承诺大版本之间的 ABI 兼容。3.10 下编译的扩展放进 3.12 解释器,import 当即崩溃。发行方因此按“Python 版本 × 平台”逐个发布轮子(wheel),文件名里的 cp310、cp312 就是 ABI 记号。“升了 Python 之后 RDKit 装不上”,报错说是找不到版本,病根在标签不匹配。

定义

ABI 与轮子的兼容约束。ABI 是二进制模块与宿主程序之间的调用约定;CPython 的 ABI 随大版本轮换(稳定 ABI 存在,但科学计算栈基本不用)。轮子是 Python 的二进制分发格式,文件名内嵌平台与 ABI 标签,如 rdkit‑…‑cp311‑cp311‑linux_x86_64.whl。pip 选轮子时按当前解释器的标签过滤:标签对不上,再新的版本也等于不存在。这条约束对纯 Python 包(如 DeepChem 的主包)不生效——它们没有二进制层,任何解释器都能跑,于是“混装纯 Python 包没问题、混装二进制扩展出事”的现象有了统一解释。

第二重:框架生态的三角约束。深度框架不是孤立的包:torch 的每个版本按 CUDA 工具链分别构建,轮子带 cu118、cu126 一类的构建标签;torch-scatter、torch-sparse 这些 C++/CUDA 算子包又要针对特定 torch 构建编译,官方按“torch 版本 + CUDA 构建”成对发布配对轮子;三角的第三条边是显卡驱动,它决定本机可用的 CUDA 上限。装错配对有两种典型症状:pip 找不到匹配轮子,退回源码编译,动辄半小时且常失败;或者装得上,import 时以 undefined symbol 告终。

第三重:Python 版本本身的时间差。老项目冻结在 3.9/3.10——TorchDrug 的官方兼容上限即 Python 3.10;新库只发 3.12+ 的轮子。生态的新旧两个时代各自收缩,想用一个解释器同时伺候 2022 年的冻结栈与 2026 年的新库,约束无解,只剩隔离一条路。三重约束叠出的可行域画在图 5.2-1 里:行是解释器,列是框架,格内还要再过一遍 CUDA 配对。

三重约束的相对位置值得记牢:ABI 是硬边界,撞上即崩,没有协商余地;CUDA 配对介于软硬之间,查表即得却常被跳过;Python 版本时间差是慢变量,一年只移动一格,却把冻结项目一步步逼进角落。排障时按这个次序排查,比对着报错倒着猜快得多。

三重耦合的可行域:行是 Python 版本,列是 torch 版本,格内再受 CUDA 与算子包配对约束;TorchDrug 的可行域收缩到左上两格 三重耦合的可行域:解释器、框架与算子包的交集 示意(2026-08 口径):可行域随生态整体平移,冻结项目的格子只会缩小 torch 2.0.x(2023) torch 2.4(2024) torch 2.7+(2025–26) Python 3.9 Python 3.10 Python 3.11 Python 3.12+ TorchDrug TorchDrug × 新库轮子自此起步 冻结项目止步于此 可行(有配对轮子) 无兼容轮子 TorchDrug 冻结可行域(还需 torch ≤ 2.0) 约束一 · 解释器 ABI(行):cp 标签随大版本轮换,旧扩展不认新解释器 约束二 · torch × CUDA 构建(列):轮子按构建标签分别发布,须与驱动相容 约束三 · 算子包配对(格内):torch-scatter 等按 torch+CUDA 成对发布,错配即 undefined symbol pip 的视野只覆盖版本号与声明的范围;ABI 标签、CUDA 构建、配对关系写在文档里,不写在元数据里。 解析通过 ≠ 运行通过:可行域要靠三层交集自行核对,这正是下一节“锁定”要解决的对象。
图 5.2-1 三重耦合下的可行域示意。行是 Python 解释器(ABI 层),列是 torch 版本(框架层),格子内部还要再过 CUDA 构建与算子包配对(第三层)。阴影格是 TorchDrug 的可行域:Python ≤ 3.10 且 torch 2.0.x,只占矩阵左上角;最下行的 3.12+ 与 2023 年的 torch 2.0.x 无交集——冻结项目与新生态在矩阵两端各自收缩。pip 的解析器看不到行标签是否齐备、看不到构建后缀,只在列的版本号里搜索,这正是它“解出”坏组合的根源。

于是可以回答那个经典问题:pip 为什么会“解出”一个运行时才崩的组合。pip 的回溯式解析器(20.3 起)只在各包元数据声明的版本范围里搜索:它读得到“deepchem 声明支持哪个区间的 tensorflow”,读不到“这个轮子的 cp 标签是否与当前解释器一致”“+cu126 的构建是否与驱动相容”“torch-scatter 该配哪个 torch”——后三者是环境级属性,写在文档里,不写在 install_requires 里。解析器交出的组合在元数据层面自洽,崩在元数据之外。另一类失败来得更早:约束交叉时回溯搜索组合爆炸,终端停在 resolving 上数小时不动(pip 文档对此有专门章节)。这不是 pip 愚笨,是问题本身越出了它的视野;视野之外的部分,只能靠人来锁定。

最后把这件事放到可复现性(reproducibility)的账上。实验输出是四个输入的函数:

结果 = F(代码, 数据, 环境, 种子), ∂F/∂环境 ≠ 0
(5.2-1)环境与代码、数据、种子平权,是实验的自变量而非背景。“跑通了就行”隐含 ∂F/∂环境 = 0 的假设,而分子工具栈恰恰不满足:torch 换一个小版本,CUDA 核的调度次序改变,浮点累加的次序随之改变,指标在第三位有效数字上漂移。环境漂移(environment drift)指环境随升级与重装悄然变化、结果不可追溯地改变的过程。Wilson et al.(2014)把记录软件环境列为科学计算的首要实践之一,Wilson et al.(2017)进一步给出最低限度的操作版本。
习题 5.2-1

判断下列三份日志各出在哪一层约束(ABI/torch×CUDA 配对/Python 版本时间差),并各给出一条修复路径。

日志 A(import 时):ImportError: …/site-packages/rdkit/Chem/rdchem.so: undefined symbol;该环境上周刚把默认解释器从 3.10 换成 3.12,包未重装。

日志 B(import 时):ImportError: torch_scatter/_version.so: undefined symbol: _ZN2at…(截断);当前环境 torch 为 2.7.x,torch_scatter 是数月前装的。

日志 C(安装时):ERROR: Could not find a version that satisfies the requirement torch==2.0.1;解释器为 Python 3.12。

参考解答

A 属 ABI 层:rdchem.so 按 cp310 编译,解释器已是 3.12,符号表对不上。修复:回到原解释器版本,或在新解释器下重装 rdkit 使 pip 按 cp312 标签重新取轮子。B 属配对层:torch_scatter 的二进制针对旧 torch 构建编译,torch 升到 2.7 后 C++ 符号(_ZN2at… 即 at 命名空间的旧符号)已改名。修复:先确认 torch.__version__ 与 CUDA 构建,再到配对索引查对应的 torch-scatter 轮子成对重装;不要在旧包上叠加新 torch。C 属时间差层:torch 2.0.1 从未发布 cp312 轮子,pip 按标签过滤后候选为空。修复:为该旧栈单开环境,把解释器降到 3.10(或 3.11)再装——版本时间差只能靠隔离消化,不能靠硬装绕过。三份日志的共同教训:报错位置在运行时或安装时,病因都在装之前的版本决策里。

5.2.2 隔离与锁定:一实验一环境

看清机理,对策只有两条:让约束不互相接触(隔离),让每次接触都留档(锁定)。环境隔离(environment isolation)的粒度是实验,不是机器。共享环境里一次顺手升级,等于同时改写所有依赖该包的实验,而这类改动不出现在任何一次代码提交里,事后无从追溯。一实验一环境的代价只是磁盘上几个 GB,换来的却是:每个实验的输入清单闭包完整,式 (5.2-1) 里的“环境”一项从此可以指名道姓。

隔离之上是分层。分子栈的习惯分工:conda 或 mamba 管二进制层——Python 解释器本体、RDKit 及其 C++ 依赖、CUDA toolkit;pip 管纯 Python 层——PyPI 上的包。分工的依据是求解器的视野:conda 的包配方携带非 Python 依赖,能在同一次求解里一并敲定 Python 版本;pip 只见 PyPI。两条规则把分层做稳:其一,conda 先定解释器与重二进制,随后才轮到 pip 装其余;其二,pip 装过之后不再回头 conda install——两个求解器交替操作同一环境,会互相覆盖对方的决议。conda 的求解器在大环境上偏慢,社区普遍改用同接口的 mamba,这只是速度差别,原则不变。通道也是决策:conda 通道(channel)中以 conda-forge 为科学计算的主通道,混用多个通道等于给求解器添变量,锁定时应写明。

conda / mamba:地基与重二进制

  • 一次求解同时敲定 Python 版本与 CUDA toolkit;
  • RDKit 等 C++ 扩展的非 Python 依赖在此解决;
  • 配方来自 conda-forge 等通道,通道名入锁定文件;
  • 解释器本体也在其管辖之内,版本即地基。

pip:纯 Python 层

  • 只管 PyPI,装在已定好的解释器之内;
  • 决议结果交给 requirements-lock.txt 存档;
  • 装过之后不再回头 conda install,避免两层求解器互踩;
  • 纯 Python 包无 ABI 顾虑,跨解释器可移植。

锁定有三个层次,精确度递增,见表 5.2-1。宽松文件声明意图(要什么),精确文件记录事实(装了什么),environment.yml 连同通道与解释器版本一并入档(在哪种地基上装)。三者都放进版本库,与代码同过评审:复现一次实验,先复现它的环境。

表 5.2-1锁定的三个层次
文件记录内容精确度角色
requirements.txt顶层依赖与宽松版本范围(如 rdkit==2026.*意图级声明“要什么”;随时间可重新求解
requirements-lock.txt(pip freeze 产物)全部已装包,逐个精确到轮子版本逐包精确记录“当时装了什么”;复现的直接依据
environment.ymlconda 通道、Python 版本、conda 层与 pip 层两份清单环境级完整重建整个环境,含解释器与二进制层

只锁 requirements.txt 不锁后两者,等于只留下菜名不留配方:一个月后重新求解,解析器在已经平移的可行域里选出另一组版本,结果随之漂移,而代码一行未改。Wilson et al.(2017)把“软件环境入库”列为六条最低限度实践之一,理由正在于此——锁定文件(lock file)是实验记录的一部分,与原始数据同级。三份文件的分工可用一句话记住:requirements.txt 给人读,requirements-lock.txt 给机器读,environment.yml 给两年后的自己读。

锁定的极端形态是容器:把解释器、二进制层与包目录整体打成镜像,环境退化为一个内容哈希。课程规模用不到这一步,但原理一脉相承——复现的单位越大,锁定的层级越高;反过来,六周课程里三份文本文件已经够用,工具的重量不必超过问题本身。

5.2.3 本书的三套环境:主力、沙箱与现代栈

按上述原则,本书全程维护三套互不借贷的环境,口径汇总于表 5.2-2,隔离结构见图 5.2-2。三套环境对应三个时代的主力栈:现役主力、冻结考古、前沿图学习。

命名也是文档的一部分:mml-main 标角色,torchdrug-arch 标时代,pyg-lab 标用途。环境名一旦写进论文与实验笔记,就不再只是本地习惯;含糊的名字(env、test、new2)等于把式 (5.2-1) 的一个自变量匿名化,事后无从对账。

表 5.2-2本书三套环境(2026-08 口径)
环境Python核心锁定服务对象
mml-main(主力)3.11(上限口径 ≤ 3.11)rdkit 2026.03 系列、deepchem 2.8.0(pip 安装)第 1–3 章表征、建模与评估,日常主力
torchdrug-arch(冻结沙箱)3.10torch 2.0.x、torchdrug 0.2.1第 4 章生成实验;只进不改,绝不与主力混用
pyg-lab(现代图学习)3.12+最新 torch(含 CUDA 构建标签)+ PyG 与配对扩展轮子5.1 节生态实践与后续课题

主力环境 mml-main 跑第 1–3 章的一切:RDKit 2026.03 系列(2026-08 已迭代至 2026.03.5)三大平台轮子齐备,pip 直装;DeepChem 稳定版停在 2.8.0(2024-04),nightly 以 2.8.1.dev 持续构建,本书锁 2.8.0 而不追 nightly——课程要的是可复现,不是最新。Python 取 3.11:为这批 2024 年前后的轮子留足覆盖面。

# 主力环境:解释器与二进制层先定,其余交给 pip
conda create -n mml-main python=3.11 -y
conda activate mml-main
pip install "rdkit==2026.*" deepchem==2.8.0

沙箱 torchdrug-arch 是刻意为之的考古环境。TorchDrug 冻结于 v0.2.1(约 2022 年),官方兼容上限 Python 3.10、torch ≤ 2.0;第 4 章的生成实验全部在此复现。冻结环境(frozen environment)的含义是三条纪律:版本整体钉死、不再升级、修复方式是“再冻一层”而不是顺手升级任何一个包。冻结项目要配冻结环境——用 2026 年的 torch 伺候 2022 年的代码,既跑不动,也失掉了复现的原义。

# 冻结沙箱:整层一起钉死,TorchDrug 停在 2022 年的栈上
conda create -n torchdrug-arch python=3.10 -y
conda activate torchdrug-arch
pip install torch==2.0.1 torchdrug==0.2.1

现代图学习环境 pyg-lab 面向 5.1 节的 PyG 生态与新课题:新 Python、最新 torch。这里的习惯动作是先查配对表、再装包:torch-scatter 一类扩展轮子按“torch 版本+CUDA 构建”成对发布,装之前先到官方配对索引核对组合,再按同一组合安装。PyG 自 2.3 起核心包已纯 Python 化,直接 pip 可装;扩展算子包可选,但图规模一大便值得配上。

# 现代图学习:先锁 torch 与 CUDA 构建,再按配对表装扩展轮子
pip install torch==2.7.1 --index-url https://download.pytorch.org/whl/cu126
pip install torch_geometric
pip install torch-scatter -f https://data.pyg.org/whl/torch-2.7.1+cu126.html
一台机器上三套隔离环境:主力、冻结沙箱与现代图学习;共享层只有 Linux/WSL2 主机、conda 与 pip、GPU 驱动 一台机器,三套环境:共享的只有硬件与二进制层 隔离保证每个实验的输入闭包完整;冻结是刻意选择,沙箱中的旧栈不波及主力 主力环境 mml-main Python 3.11 rdkit 2026.03 系列 deepchem 2.8.0(pip 安装) 随官方更新逐版重锁 第 1–3 章 · 日常主力 冻结沙箱 torchdrug-arch Python 3.10(官方上限) torch 2.0.x torchdrug 0.2.1(2022 冻结) 只进不改,修复即再冻一层 第 4 章 · 生成实验复现 冻结 现代图学习 pyg-lab Python 3.12+ 最新 torch(+cu 构建标签) PyG + 配对扩展轮子 装前先查版本配对表 5.1 生态 · 后续课题 × 绝不混装 × 绝不混装 共享层:Linux / WSL2 主机 · conda(或 mamba)与 pip · GPU 驱动(CUDA on WSL 透传) 每套环境各配锁定文件入库:environment.yml + requirements-lock.txt 一次升级只波及一个实验;停更的项目仍可原样复现——这就是隔离与冻结的全部收益。
图 5.2-2 三套环境的隔离结构。三个环境各自持有独立的解释器与包目录(site-packages 互不共享),仅共享机器硬件、conda/pip 工具链与 GPU 驱动。中间的沙箱(赭色描边)刻意冻结在 2022 年的栈上:它与主力环境之间没有任何借包通道——“绝不混装”不是洁癖,而是图 5.2-1 中两个时代可行域不相交这一事实的工程结论。每套环境的锁定文件随代码入库,环境本身成为可引用、可审计的实验输入。
警示

跨环境混装。在主力环境里执行 pip install torchdrug,解析器会把 torch 拉回 2.0.x 以满足其声明,deepchem 及其余依赖随之降级或直接崩坏;一个环境只能容纳一个时代的栈。同理,图 5.2-2 中三个环境之间没有任何“借一个包”的操作:借出去的包带着它全部的传递依赖,等于在目标环境里开了一条不受控的通道。发现自己想在 A 环境里装 B 环境的包时,正确的动作是问:这个实验是不是该搬进 B 环境,或者为它开第四套。

习题 5.2-2

论述:什么时候值得为一个老项目单独建冻结环境,什么时候应该把它迁移到现代栈?请给出至少三条判据,并结合 TorchDrug(后继为 Graphium)的情形说明两条路线如何并存。

参考解答

判据一,目的。要复现原文数值、核对教材结论,冻结环境是唯一忠实路径——迁移本身就改动了式 (5.2-1) 里的“环境”项。要做新研究、发新方法,冻结栈的旧算子与慢迭代反而成为负担。判据二,耦合深度。项目的钉子扎得多深决定迁移成本:TorchDrug 与 torch ≤ 2.0 深度耦合,迁移近乎重写;若只依赖纯 Python 接口,迁移可能只是换一次导入路径。判据三,社区后继。原项目停更但思想有活跃后继(TorchDrug 之于 Graphium、之于 PyG 生态),说明概念资产可迁移,值得双轨:判据四,时间预算与人数——单人短期课程优先冻结,长期课题组值得投资迁移。TorchDrug 的并存方案正是本书的做法:沙箱里冻结一份作基线,主力与现代环境里用活跃生态承接新实验;两轨定期对照,一旦确认新栈能复现沙箱的关键结论,沙箱退居“历史档案”,迁移即告完成。

5.2.4 Windows 用户的现实路径:WSL2 的角色

分子机器学习工具链的一等公民平台是 Linux:mamba 的二进制包、torch 的 CUDA 轮子、PyG 的配对轮子都先到 Linux,官方教程与 CI 也以 Ubuntu 为默认口径。Windows 用户的省力路线不是绕开 Linux,而是把它装进 Windows:WSL2(Windows Subsystem for Linux 2)在轻量虚拟机里运行一个完整发行版(社区默认 Ubuntu),与 Windows 并存互通,本书三套环境在其上与原生 Linux 无异。GPU 也不需要双份:CUDA on WSL 把 Windows 侧的 NVIDIA 驱动透传进 WSL2,WSLg 直接运行 Linux 图形程序,训练与推理可以整个留在 WSL2 内完成。

注记

文件系统边界。WSL2 与 Windows 分属两个文件系统:/mnt/c 挂载的 Windows 盘经过协议桥接访问,跨界 I/O(cross-filesystem I/O)比 Linux 原生文件系统慢一个量级——git status、数据集遍历、conda 解包受害最明显。Microsoft 官方文档的建议即:在 Linux 命令行下工作的项目,放在 Linux 侧的 home 目录(如 ~/projects);需要 Windows 编辑器时走 \\wsl$ 路径访问,而不是把项目放在 C 盘再从 WSL 里操作。

原生 Windows 并非总是不够用,边界在二进制层的厚度。纯 RDKit 场景——描述符计算、子结构检索、SMILES 批处理、教学练习——官方为 Windows 发布平台轮子,pip 即装即用,不必动用 WSL。一旦进入深度训练、需要 PyG 配对轮子或 mamba 的二进制包,生态覆盖明显偏向 Linux,此时迁入 WSL2 是收益最高的动作。判断口诀只有一句:依赖里出现“需要配对的二进制”时,就该换到 Linux 侧。

WSL2 内部的纪律与原生 Linux 相同:选一个 LTS 发行版(Ubuntu LTS 为主),系统包交给 apt,Python 栈交给 conda 与 pip,两侧不越界;永远不用 sudo 安装 Python 包——那会把包装进系统解释器,一处越界,整台“机器”的隔离根基即告失效。

版本口径随时间平移,本节全部版本号以 2026 年 8 月核实为准。半年后重读,先把三套环境的锚点版本逐一核对再动手——这个动作本身,就是 5.2.5 节清单的第 0 项。

5.2.5 复现清单:把环境纳入实验记录

复现的语义先立清楚:同代码、同数据、同环境、同种子,得到同一结果。跨硬件时逐位一致并不总可达——GPU 并行归约的浮点累加次序依实现而定——退一档的要求是统计一致:多次重启的结果分布重合。固定种子要覆盖四处:Python 的 random、numpy、torch 的 CPU 与 GPU 两侧,外加 DataLoader 工作进程的种子;漏掉任何一处,第 4 章的生成实验就会在重启后给出不同的分子。

CUDA 的确定性(determinism)开关有其局限,须当作已知边界写进实验记录。torch.use_deterministic_algorithms(True) 能压住大部分非确定源,但个别算子没有确定性实现,开关一开反而报错,此时要么换计算路径,要么显式接受非确定并在文档声明;cuDNN 的自动基准选择也是常被忽略的非确定来源。确定性不是开关一打就有的默认属性,而是一条要么满足、要么注明例外的规格。

种子与确定性解决“同机重启一致”,锁定解决“异机重建一致”,两者合起来才是完整意义的复现。只做前者,论文数值在自己机器上稳定,别人却重建不出;审稿人遇到的失败,多是后一种。

数据同样有版本。MoleculeNet 的固定划分由划分脚本与种子决定,DeepChem 加载器按 splitter 类型与 seed 复现同一划分,记录时三者缺一不可;ChEMBL 每个 release 的化合物集合都不同,引用必须带版本号(如 ChEMBL 33),第 3 章的口径在此落地为操作。配套动作是数据清单:来源、下载日期、文件哈希一并入库。环境侧照 5.2.2 锁定三层文件;结果侧每个运行目录记三项哈希——代码提交、数据清单、环境文件——做到结果可反查输入,代码、数据、结果三对应。

清单的最后一项是一次性全流程验证:清空缓存(__pycache__、数据缓存目录),在新目录从锁定文件重建环境,从零跑通全流程。这是复现的验收测试,也常是发现隐藏随机性与隐式本地依赖的唯一机会。它之所以放在最后,因为前六项任何一处不实,这一步必然失败——失败在这里反而是收获:问题被拦在发表之前。

复现危机并不只在环境。Kapoor 与 Narayanan 清点了 17 个领域、294 篇受数据泄漏影响的论文,指出泄漏与不可复现在方法学上同根(Kapoor & Narayanan, 2023);3.8 节已给出泄漏的分类学,此处只需记住:环境清单盖住的是“机器知道什么”,泄漏审查盖住的是“模型不该知道什么”,两份清单都过,复现才有意义。

回看式 (5.2-1):本节的全部操作——隔离、分层、锁定、清单——目的只有一个,把四个自变量逐一变成显式输入。环境这项做完之后,剩下的漂移来源只有硬件差异与算法非确定性,前者可以注明,后者已经写进实验记录;至此,5.3 节的陷阱清单里再没有“环境”这一类,可以腾出手对付更要紧的数据问题。

方法

复现清单(发表前逐项核对)。① 种子四处固定:random、numpy、torch(CPU 与 GPU)、DataLoader worker;② 确定性边界:开 torch.use_deterministic_algorithms(True),无确定性实现的算子逐一注明或替换;③ 数据版本:划分器类型与 seed、ChEMBL 等 corpus 的版本号、来源与下载日期、文件哈希入数据清单;④ 环境导出:requirements-lock.txt 与 environment.yml 双份入库,conda 通道与 Python 版本写明;⑤ 三对应:每个结果目录记录代码提交、数据清单、环境文件三项哈希;⑥ 一次性全流程:清缓存、新目录、重建环境、从零跑通;⑦ 泄漏自查过 3.8 节清单,与环境清单共同构成复现证据。

习题 5.2-3

为一个“复现 GraphAF 类生成实验”的课题设计复现包的目录结构:给出顶层目录与关键文件,并说明 environment.yml、数据清单、结果目录三者如何互相勾连,做到任一结果可反查其全部输入。

参考解答

目录结构建议:

  • graphaf-repro/(仓库根)
  • envs/——environment.yml(conda 层:python 3.10、torch 2.0.x,含通道名)与 requirements-lock.txt(pip freeze 逐包精确);
  • data/——manifest.csv(来源、下载日期、SHA-256)与下载脚本;语料注明版本号(如 ChEMBL 33);
  • splits/——划分文件及其哈希,附划分器类型与 seed;
  • src/——源码,训练入口只从 configs/ 读参数;
  • results/run-001/——该次运行的配置副本、指标、样本,以及 PROVENANCE 文件。

勾连机制:训练脚本启动时自动把三项哈希写进 PROVENANCE——当前代码的 commit 哈希、data/manifest.csvsplits/ 的内容哈希、envs/ 两份锁定文件的哈希。反向查询于是成为机械操作:拿任一 run-XXX 的 PROVENANCE 对回仓库历史,即可还原该结果的确切代码、数据与环境;三者任一哈希对不上,该结果即标记为不可复现。验收即 5.2.5 的第六项:在新机器、新目录按清单重建,产出与 run-001 逐项比对。


关键术语

可复现性 (reproducibility)
同代码、同数据、同环境、同种子得到同一结果的性质;环境是与代码平权的自变量。
依赖地狱 (dependency hell)
多层版本约束互斥,导致安装失败或运行时崩溃的状态。
应用二进制接口 (application binary interface, ABI)
二进制模块与解释器之间的调用约定;CPython 大版本间不保证兼容。
轮子 (wheel)
Python 的二进制分发格式,文件名内嵌平台与 ABI 标签,pip 据此过滤候选。
环境漂移 (environment drift)
环境随升级与重装悄然变化,结果随之不可追溯地改变。
环境隔离 (environment isolation)
一实验一环境:解释器与包集合互不共享,升级只波及单个实验。
锁定文件 (lock file)
精确记录环境内容的文件(pip freeze、environment.yml),入库与代码同过评审。
conda 通道 (channel)
conda 包的发布源;科学计算以 conda-forge 为主通道,锁定时应写明。
冻结环境 (frozen environment)
版本整体钉死、不再升级的隔离环境,用于复现停止维护的项目。
跨界 I/O (cross-filesystem I/O)
WSL2 与 Windows 分属两个文件系统,跨界读写经协议桥接,慢一个量级。
确定性 (determinism)
同输入同硬件下结果逐位一致;GPU 上部分算子无确定性实现,须显式处理。
回溯解析 (backtracking resolution)
pip 求解器在版本约束冲突时撤销假设、重新搜索的策略;约束交叉时可能组合爆炸。

参考文献与延伸阅读

  1. Wilson G, Aruliah DA, Brown CT, et al. 2014. Best practices for scientific computing. PLoS Biology 12(1): e1001745.
  2. Wilson G, Bryan J, Cranston K, Kitzes J, Nederbragt L, Teal TK. 2017. Good enough practices in scientific computing. PLoS Computational Biology 13(6): e1005510.
  3. Kapoor S, Narayanan A. 2023. Leakage and the reproducibility crisis in machine-learning-based science. Patterns 4(9): 100804.
  4. RDKit. Installation. RDKit Documentation, 2026.03 系列(2026.03.5). rdkit.org/docs/Install.html(软件文档).
  5. DeepChem. Getting Started: Installation. DeepChem Documentation, 2.8 系列. deepchem.io(软件文档).
  6. PyTorch Geometric 团队. Installation. PyTorch Geometric Documentation(含扩展轮子配对索引). pyg.org(软件文档).
  7. pip 开发组. Dependency Resolution. pip Documentation v26.2. pip.pypa.io/en/stable/topics/dependency-resolution/(软件文档).
  8. Microsoft. Working across Windows and Linux file systems. Windows Subsystem for Linux Documentation. learn.microsoft.com/windows/wsl/filesystems(软件文档).