论文结果只是探索树上的某一个节点
一篇论文呈现出来的通常是一条清晰路线,但真实的研究过程更像一棵不断生长、分叉和修剪的树。
一篇论文呈现出来的通常是一条清晰路线,但真实的研究过程更像一棵不断生长、分叉和修剪的树。
读者最终看到的论文通常目标明确、方法合理、结果顺畅,仿佛研究者从一开始就知道正确路线。但真实的研究过程中,有些数据做到一半才发现无法使用,有些方法得到过结果却没有进入最后的正文,有些分支想法被否定,也有一些分支想法走到了最后。
一个项目尝试得越多,越容易忘记当时为什么这样处理、某份数据是由哪个脚本生成的、某条路线又为什么被放弃。有些想法明明已经验证过,隔一段时间却因为找不到记录,只能重新再做一次。
杂乱的文件管理会让这种情况更加频繁。尤其是 AI 出现以后,我们尝试一个想法的速度变快了,但脚本、中间数据和临时结果的增长速度也变得更快。如果没有一套清楚的组织方式,AI 带来的就不只是效率,也可能是更快累积起来的混乱。
那如果探索本来就是树状的,要做很多次尝试,应该怎样组织项目,才能让项目停止熵增?
Part 1|我如何组织一个科研项目的各种文件
我现在一般会把一个科研项目的目录整理成下面这样:
project/
├── AGENTS.md # 项目介绍与整体协作约定
├── .agents/
│ └── skills/ # 这个项目可以调用的专属 Skill
│ ├── doc-coauthoring/ # 辅助论文写作
│ ├── happy-figure/ # 辅助设计论文正图
│ ├── clarifier/ # 澄清模糊需求
│ ├── todos/ # 将任务保存为 Markdown 文档
│ └── implement/ # 实施、验证并完成任务
├── data/
│ ├── raw/ # 未经自己处理的原始数据
│ │ └── README.md # 说明数据来源、口径和字段含义
│ ├── interim/ # 项目运行过程中产生的数据
│ │ ├── Step01.01_任务简述/ # 对应同名脚本的输出
│ │ ├── Step01.02_任务简述/
│ │ └── Step02.01_任务简述/
│ └── abandon/ # 已经不再活跃、但仍然保留的数据
├── scripts/
│ ├── Step01.01_任务简述.py # 项目的正式处理脚本
│ ├── Step01.02_任务简述.py
│ └── Step02.01_任务简述.py
├── tools/ # 可被多个脚本复用的工具
└── temp/ # 临时检查结果和一次性脚本先从根目录下的 AGENTS.md 说起。它相当于整个项目的总说明书,用于介绍项目,并定义项目需要遵守的约定,例如各个目录的职责、文件命名方式和默认工作习惯。
具体写什么需要根据项目性质决定,但可以遵循一个原则:凡是你不主动写出来,别人或者 AI 单看项目文件无法知道的事情,都应该写进 AGENTS.md。
你不把自己的项目习惯说清楚,无论是人还是 Agent,都不可能自动理解。
根目录下的 .agents/skills/,则用于保存这个项目可以调用的专属 Skill。例如:
doc-coauthoring:辅助论文写作;happy-figure:辅助设计论文正图;clarifier:澄清模糊需求;todos:将讨论结果保存为可以管理的 Markdown 任务;implement:根据任务文档持续实施、验证并完成任务。
其中,论文写作可以回顾《如何用 AI 助力论文写作?一个 doc-coauthoring Skill 就够了》,论文绘图可以回顾《如何使用 AI 打磨论文正图?学习 Happy Figure 的思路吧!》。
clarifier、todos 和 implement 是一套从需求澄清到任务收口的工作流。为什么不直接使用 Agent 自带的 Plan Mode,可以先看《Plan Mode 已成为标配,但还有哪些问题没解决?》;clarifier 和 todos 的设计可以回顾《我做了一个自己的 Plan Mode:把模糊需求变成可执行任务》,在 Codex 里的交互方式可以回顾《在 Codex 自定义 Plan Mode 里,快乐地做选择题》,implement 的实施与验证流程则可以回顾《AI 说“做完了”,就真的做完了吗?》。
再往下是项目最重要的 data/ 目录。我会把数据区分成起点、过程和不再活跃的分支:
raw/保存未经自己处理的原始数据;interim/保存项目运行过程中产生的过程数据和阶段性成果;abandon/保存当前已经不再活跃、但仍值得保留的数据。
raw/README.md 尤其重要。很多数据只看文件名和字段无法理解,需要额外说明数据来源、获取时间、统计口径、字段含义和使用限制,Agent 才能正确使用。
项目的正式脚本统一放入 scripts/。我一般会用编号记录项目步骤,采用类似下面的命名方式:
Step01.01_下载原始数据.py
Step01.02_检查数据完整性.py
Step02.01_清洗观测数据.py
Step03.01_拟合基准模型.py编号可以让文件自动按照工作顺序排列,也让项目的处理步骤不必依赖自己的记忆。
每个脚本默认把结果写入 interim/ 下的同名目录:
scripts/Step02.01_清洗观测数据.py
↓
data/interim/Step02.01_清洗观测数据/审查某一步时,只需要找到脚本,再进入同名目录检查结果。代码、产物和项目步骤之间由此形成明确的对应关系。
至于 tools/ 和 temp/,则是两个相对可选的辅助目录。tools/ 保存可以被多个正式脚本复用的工具,例如固定的数据清洗脚本或数据可用性检查脚本;temp/ 用于保存临时检查结果和一次性脚本,不要求其中的内容成为项目的正式组成部分。
Part 2|这套文件系统的设计思路
我这样安排文件,并不是单纯为了让目录看起来整齐。更重要的是,只要原始数据做好了备份,同时 scripts/ 中的脚本通过 Git 管理,即使 Agent 误删了部分过程数据,通常也可以按照脚本重新生成。
原始数据就像未经处理的食材,脚本是做菜所需的厨具和操作步骤,interim/ 中的结果则是不同阶段做出来的菜品。只要食材和厨具还在,过程透明并且可以重复,某一盘成品被删掉并不会让整个项目直接崩掉。
脚本和 interim/ 中的同名目录形成明确对应。看到一个成果文件,就能找到生成它的脚本;看到一个脚本,也能立即定位它的输出。
这套结构保存的不只是文件,还保存了三类关系:
- 原始材料从哪里来;
- 每一步处理做了什么;
- 某个结果是沿着哪条路径产生的。
放进 abandon/ 的内容也不是垃圾。它只代表这份数据或这条路线当前不再活跃,并不代表这次探索没有价值。保留这些分支,可以避免几个月后重新走入已经验证过的死路,也可以解释最终为什么选择了另一条路线。等到研究条件、数据或问题发生变化,过去被放弃的内容还可能重新变得有用。
所以我理解的可复现,不只是能够重新运行最终代码。它还应该能够回答:这个结果是沿着哪条路径产生的?其他路径为什么没有被选择?
Part 3|新型计量单位:每天验证过的想法次数
我们平时很容易用完成了多少代码、做出了多少张图、写了多少字来衡量一天的工作。但对于科研来说,这些指标都不完全准确。有时候忙了一整天,最后只确认了一个方案行不通;从成品数量来看似乎一无所获,但从研究本身来看,我们已经排除了一个错误方向。
所以我现在越来越觉得,科研或许需要一个新的计量单位:每天验证过的想法次数。这里的“验证”既包括证明一个想法可行,也包括确认它行不通。一个分支被停止,并不意味着这一天白费了;只要判断建立在真实的数据、代码和结果上,它就是探索树上新增的一个节点。
计划当然重要,但计划只能给出当时看起来最合理的方向,不能提前保证哪条分支一定能够走通。
正所谓,走一步看一步,并不是一种无目标,而是一种现实策略,只要试错成本在可承受范围内,那就只剩下时间问题了。
最终进入论文的结果,可能只是整棵探索树上颜色最深的那一个节点;那些没有进入正文的浅色节点,同样参与了这个结果的形成。真正推动研究向前的,也不是计划本身有多完整,而是我们不断提出想法、验证想法,再根据结果决定下一步往哪里走。
这也是为什么我希望把每一次探索都保留下来。清楚的目录、被 Git 管理的脚本、相互对应的过程数据,以及没有被直接删除的废弃分支,共同保存了这些验证发生过的证据。我们最终确定的,或许只是探索树上的某一个节点;真正支撑这个节点的,却是那些被认真走过、完整记录下来,但没有出现在论文里的分支。
原文发表于微信公众号。