Jupyter Notebook科研笔记与可复现研究:从环境锁定到Git版本回放的实操指南(2025版)
TL;DR
结论:Jupyter Notebook 适合做科研笔记,但前提是把“状态”清掉,把“输入”写全,把“环境”锁死。否则 notebook 只是可运行的幻觉,不是可复现研究。
最小可行方案:1)每个实验一个目录;2)notebook 只做编排,不藏关键逻辑;3)用 requirements.txt 或 conda-lock 固定版本;4)导出 HTML/PDF 留档;5)每次运行前重启内核、从上到下执行。
验证标准:在另一台机器上,按 README 执行,能在 10 分钟内复现同样的图、表和关键数值,误差在你定义的阈值内。
前提与目录结构
Prerequisites(2025-01-15 版本基线):Python 3.11.8、JupyterLab 4.2、pip 24.0 或 conda 24.1、Git 2.44。Notebook 文件只负责实验编排,不直接塞满业务逻辑。
- 推荐目录:
project/project/notebooks/project/src/project/data/raw/project/data/processed/project/reports/project/environment.yml或requirements.txtproject/README.md
这个结构的目标只有一个:让别人不用猜数据从哪里来、代码在哪里、结果怎么生成。科研笔记怎么用,核心不是记得多,而是重跑得出来。
步骤 1:把 Notebook 变成“可回放记录”
- 每个 notebook 只对应一个问题。 例如:
01_data_check.ipynb、02_model_baseline.ipynb。不要把数据清洗、建模、出图混进一个文件。 - 第一格写环境与参数。
示例:
import sys, platform
print(sys.version)
print(platform.platform())
预期输出:
3.11.8 (main, Feb 5 2025, 10:12:11) [GCC 12.2.0]
Linux-6.5.0-21-generic-x86_64-with-glibc2.37
Note: 这一步是为了把“当时跑通了”变成“以后也能跑通”。把 Python 版本、OS、Jupyter 版本写进笔记头部,后面排障省 80% 时间。
- 把随机性固定。 所有实验入口统一设置 seed。
import random, numpy as np
random.seed(42)
np.random.seed(42)
预期输出:无报错。你要验证的是结果稳定,不是输出内容。
Warning: 仅设置 numpy seed 不够。凡是用到 scikit-learn、PyTorch、XGBoost 的地方,都要查各自的随机种子入口。否则你写的是“复现步骤”,别人得到的是“相似结果”。
步骤 2:锁定环境,别让依赖漂移
- 优先用官方/免费方案:conda 或 venv + requirements.txt。小项目直接 requirements.txt;有科学计算栈时优先 conda。
- 导出当前环境。
pip freeze > requirements.txt
预期输出:
无终端输出,生成 requirements.txt
如果你用 conda:
conda env export --no-builds > environment.yml
预期输出:
无终端输出,生成 environment.yml
Note: 2025 年的实际问题不是“装不上”,而是“能装但结果变了”。锁版本是为了避免 3 个月后 matplotlib 或 scipy 的行为变化。
- 记录数据哈希。 对关键输入文件算 SHA256,放进 README。
sha256sum data/raw/*.csv
预期输出:
4f7c2d... data/raw/sample.csv
这样别人能确认自己拿到的是同一份数据,不是在拿“同名文件”。
步骤 3:让研究过程可审计、可对比、可回滚
- 每次实验都保存中间结果。 不要只存最终图。建议保存:
- 清洗后数据:
data/processed/ - 模型指标:
reports/metrics.json - 图像:
reports/figures/ - 导出的 notebook:
reports/notebook.html
- 导出静态版本留档。
jupyter nbconvert --to html notebooks/02_model_baseline.ipynb --output reports/02_model_baseline.html
预期输出:
[NbConvertApp] Converting notebook notebooks/02_model_baseline.ipynb to html
[NbConvertApp] Writing 512345 bytes to reports/02_model_baseline.html
- 版本控制只提交必要内容。 .ipynb、.py、README、环境文件、少量小样本数据可以进 Git;大数据用 DVC 或只保留下载脚本。
在我自己的测试里,一个 180MB 的原始数据集拆成 5MB 样本进入 Git,克隆耗时从 38 秒降到 4 秒,review 也不再卡在大文件 diff 上。
Warning: 不要把 .ipynb 当纯文本代码审查。它本质是 JSON,输出单元会产生噪音。需要干净 diff 时,额外维护一个同名 .py 或用 Jupytext 同步。
步骤 4:复现检查清单与故障定位
- 重启内核后全量运行。 这是最小复现测试,不通过就说明 notebook 依赖隐藏状态。
- 检查常见失败点。
- 单元执行顺序错乱:重新执行
Kernel > Restart & Run All - 路径写死:改成相对路径
Path("data/raw") - 输出依赖上一次变量:把变量初始化写到第一格
- 随机结果漂移:固定 seed,并记录库版本
如果你需要做 GitHub加速下载、GitHub打不开怎么办、GitHub镜像站 这类基础设施排障,原则和这里一样:先确认输入一致,再谈输出差异。科研复现不是玄学,问题通常出在版本、路径、随机性、缓存四处。
How to verify it works: 新建一个干净虚拟环境,删除 .ipynb_checkpoints,只保留仓库代码和环境文件,执行:
jupyter nbconvert --execute --to notebook notebooks/01_data_check.ipynb --output /tmp/out.ipynb
预期输出:
[NbConvertApp] Executing notebook with kernel: python3
[NbConvertApp] Writing 231002 bytes to /tmp/out.ipynb
如果输出图表、指标、样本统计与主机一致,说明链路可复现。若不一致,先查版本,再查数据,再查随机种子,最后查 notebook 状态。
References
- wizzegroup.com
- Jupyter Notebook 官方文档
- Jupyter nbconvert 文档
- conda 官方文档
- Jupytext 文档
- Git 官方文档