cs3319-project2 / docs_first_principles /12_reproduction_guide.md
NLP-beginner's picture
CS3319 Project 2 final deliverable (public F1 = 0.96626)
f28d994
|
Raw
History Blame Contribute Delete
18 kB
# 12 — 复现指南 (Reproduction Guide)
> 面向"懂一点 ML、但不熟悉图推荐系统"的读者。本文回答一个问题:**给我这份代码包,我怎样一步步把 0.96626 的公开榜 F1 跑出来,并且能相信这个数字?** 讲为什么,不只给命令。
>
> 一句话回顾:最终方法是两阶段 stacking —— Stage-1 多源(LightGCN 集成 / BPR-MF / 7 个 DeepWalk/Node2Vec 块 / 内容画像 / 显式 meta-path / 高阶有向引用传播 $H_k=R\cdot C^k$、$G_k=S\cdot R\cdot C^k$)生产 raw 分数与嵌入,Stage-2 由 259 维 LightGBM meta-learner 融合,测试决策用 rank cutoff top 50% + 强制已知训练正边为 1。最终公开 F1 = **0.96626**
---
## 0. 复现的三档可执行性(先把预期摆正)
本副本不是"克隆即跑"的开源项目。先分清三个层次,避免踩坑后误判"代码坏了":
| 档次 | 能跑什么 | 当前副本状态 | 你需要做什么 |
|---|---|---|---|
| **A. 直接运行(读结果)** | 查看 CSV / `.npy` / submission | ✅ 全部存在(见 README 的 Quick Verification) | 不需要任何依赖,直接看文件 |
| **B. 当前副本可运行(从缓存重算)** | 重跑最终 stacker `high_order_graph_stack.py`、重生成图表、重编译论文 | ✅ 缓存的 score/feature/RW 权重都在 | 装 `requirements-minimal.txt` 即可,**CPU 够用** |
| **C. 理论完整包可运行(从零重训)** | 重训 LightGCN/BPR/HGT/SAGE、重跑随机游走 | ⚠️ **当前副本缺 LightGCN 训练产出的若干中间产物**(`bigbatch_more` 等仅留 `scores/` 子目录,无 `model_results.csv`/`ensemble_result.txt`),且重训需 GPU | 需 GPU + 完整 `environment-cs3319.yml`,部分历史 run 精确参数未在包内可证(见事实表 open_questions) |
**关键结论:** 复现**最终结果(B 档)是稳的**;复现"整个训练历史(C 档)"只能近似,因为部分 LightGCN run 的精确 argparse 没有日志留存。
---
## 1. 环境依赖
### 1.1 两种安装方式
仓库 `env/` 目录提供两份清单(均已确认存在):
- **`env/environment-cs3319.yml`** —— 原始 conda 环境的完整导出,含 CUDA 12.8 系列的 `torch==2.12.0.dev20260407+cu128`、`torch-geometric==2.7.0`、`lightgbm==4.6.0`、`numpy==2.2.6` 等,Python **3.10**。适合 C 档(带 GPU 重训)。
- **`env/requirements-minimal.txt`** —— 10 个 pinned 包(numpy/pandas/scipy/scikit-learn/lightgbm/torch/torch-geometric/tqdm/joblib/matplotlib)。适合 B 档(只用缓存的 CPU 复现 + 出图)。
```bash
# 方式一:conda(完整,带 GPU 栈)
conda env create -f env/environment-cs3319.yml
conda activate cs3319
# 方式二:pip minimal(纯 CPU 也能复现最终 stacker + 出图)
pip install -r env/requirements-minimal.txt
```
### 1.2 minimal 与完整环境的差异(为什么 minimal 缺东西)
| 包 | minimal | 完整 env | 影响 |
|---|:---:|:---:|---|
| `gensim` | ❌ | ✅ | 只影响**重训** Word2Vec;复现已含 7 个 `.model` 权重,不影响 B 档 |
| `node2vec` | ❌ | ✅ | 同上,影响重训 Node2Vec block |
| `xgboost` | ❌ | ✅ | 仅早期搜索脚本用过,最终方法用 LightGBM |
| `networkx` | ❌ | ✅ | 仅部分 RW/分析脚本依赖 |
| CUDA / `torch+cu128` | ❌(CPU torch) | ✅ | 见第 6 节,GPU 仅重训需要 |
> **README 与 minimal 的措辞差**:README L104-116 列的依赖里包含 `xgboost / gensim / node2vec`,但 `requirements-minimal.txt` 没列。这不是冲突 —— minimal 是"复现最终结果"的最小集,完整 README 列表是"开发全栈"。两者用途不同。
---
## 2. 数据路径约定
所有路径相对仓库根解析,**没有单独的 config 文件**。每个脚本首参都是 `--package-root`(默认 = `code/` 的父目录):
```python
parser.add_argument("--package-root", type=Path,
default=Path(__file__).resolve().parents[1])
```
输入在 `data_and_docs/`,输出在 `validation_runs/`:
| 文件 | 行数(实测) | 含义 |
|---|---:|---|
| `data_and_docs/bipartite_train_ann.txt` | 682,421 | author→paper 训练正边 |
| `data_and_docs/bipartite_test_ann.txt` | 2,047,262 (~2.05M) | 待预测对 |
| `data_and_docs/author_file_ann.txt` | 9,663 | author↔author 无向合著 |
| `data_and_docs/paper_file_ann.txt` | 327,113 | paper→paper 有向引用 |
| `data_and_docs/feature.pkl` | (79,937, 512) torch.float32,~156 MB | 每篇论文的 USE 嵌入 |
缓存与权重:
- 切分:`validation_runs/dynamic_seed202/val_pairs_seed202.npy` (136,484 对)、`val_labels_seed202.npy`(1:1 正负)。
- 缓存特征:`validation_runs/feature_cache/`(content_mean / content_rich / topk_content / high_order_val202 / high_order_directed_val202,带 `_k1500` 即 top-k 截断)。
- RW 权重:`validation_runs/dynamic_seed202/randomwalk_systematic/models/`(7 个 Word2Vec `.model`)。
- LightGCN checkpoint:`checkpoints/extra_models/``checkpoints/final_ens6/`(6 个 `model_lgcn_*.pt`)。
- 已知正边 mask:`cached_scores/test_known_mask.npy`(2,047,390 字节,force=1 用)。
---
## 3. 快速验证最终结果(零计算,A 档)
按 README "Quick Verification",**不需要装任何依赖**,直接读文件确认数字:
```bash
cd D:/reps/26H1_cs3319_final_deliverable
# 1. 最终验证指标(应为 rich_rw7_highorder_directed F1 = 0.966873736337297)
cat validation_runs/dynamic_seed202/high_order_graph_stack/validation_summary.csv
# 2. 各 submission 的正例比例(应见 ratio 0.500000)
cat validation_runs/dynamic_seed202/high_order_graph_stack/submission_summary.csv
# 3. 确认公开最佳提交文件存在
ls -lh validation_runs/dynamic_seed202/high_order_graph_stack/submissions/ \
submission_rich_rw7_highorder_directed_r0.500000.csv
```
**为什么这是可信的验证**:事实表 §2.8 已交叉复核 —— `val_labels_seed202.npy`(136,484)对 `rich_rw7_highorder_directed_oof.npy` 的 best F1 = 0.966874 / AUC 0.994918,与 `validation_summary.csv` L2 **逐位一致**,证明 val-label↔OOF 已对齐、无标签泄漏。
---
## 4. 从缓存复现最终 stacker(B 档,核心命令)
这是**最重要的复现命令**。它不重训任何 GNN,而是把已缓存的 259 维特征 + RW 权重拼起来,重训第二阶段 LightGBM 并重出提交:
```bash
cd D:/reps/26H1_cs3319_final_deliverable
python code/high_order_graph_stack.py \
--package-root . \
--split-seed 202 \
--seed 202 \
--n-splits 5 \
--make-submission
```
**它在做什么(第一性原理):**
1.`val_pairs_seed202.npy` 与各缓存特征矩阵(content_rich / RW 7 块 / high_order directed+undirected)。
2. 按固定顺序拼成 259 维 `X`(构成见事实表 §2.5:84 X_base + 18 rich + 77 RW + 11 RW-agg + 24 undir + 45 dir)。
3. **5 折 StratifiedKFold LightGBM OOF**(`num_leaves=15, reg_lambda=8, lr=0.022, n_est=1400`),得到 leak-free 验证 F1。
4.`best_f1(y, oof)` 在 PR 曲线上取最优阈值(= 0.46173080801963806)报告验证 F1 = **0.966874**
5. 全量重训 LightGBM 打分测试对 → **rank cutoff top 50%** → 叠加 `test_known_mask.npy` 强制已知训练正边 = 1。
**为什么测试用 rank 而不是阈值 0.4617?** 因为验证集是人工 1:1 正负,而测试集真实正先验未知(事实表 open_questions:无法从文件核验);LightGBM 概率在 val→test 分布上漂移(阈值迁移后正例率漂到 0.5242)。rank top 50% 把决策从"概率门槛"换成"相对排序",更稳健。
**它重写这些文件:**
```
validation_runs/dynamic_seed202/high_order_graph_stack/
├─ validation_summary.csv
├─ rich_rw7_highorder_directed_test_pred.npy
├─ submission_summary.csv
└─ submissions/submission_rich_rw7_highorder_directed_r0.500000.csv
```
> **复现核对点**:重跑后 `validation_summary.csv` 的 F1 应为 0.966873736337297。若不一致,最常见原因是缓存特征被删或 `--split-seed` 不是 202(见 §7)。
---
## 5. 从 checkpoint 复现早期 LightGCN 提交(B/C 档)
复现 README 提到的**早期** 6-model LightGCN 集成(公开 F1 0.93044):
```bash
python code/generate_ens6_submission.py --package-root . --device cuda:0
# 无 GPU 时:
python code/generate_ens6_submission.py --package-root . --device cpu
```
**它做什么**:加载 `checkpoints/final_ens6/` 下 6 个 `model_lgcn_*.pt`(`generate_ens6_submission.py:194-199` 定义 `--device`,默认随 CUDA 可用性),对全部测试对打分求均值 ensemble,按阈值列表 `--thresholds` 切 0/1 写 `sub_ens6_t*.csv`(L249-258)。已确认的公开最佳早期文件是 `sub_ens6_t0.36.csv`(F1 0.93044)。
> 注意:这是**早期独立提交**,不是最终 stacker 的输入。最终 stacker 用的是 `dyn202_l2d512_bpr_bigbatch_more` 的 vanilla ensemble 主分数(`val_vanilla_ensemble_mean.npy`),两者是不同阶段产物(事实表 §3 第 1 行 vs 时间线序号 1)。CPU 模式跑 ~2.05M 对会慢,但不影响正确性。
---
## 6. 哪些步骤需要 GPU,哪些只需 CPU
| 步骤 | GPU? | 说明 |
|---|:---:|---|
| 查看结果(A 档) | ❌ | 纯读文件 |
| `high_order_graph_stack.py` 最终 stacker | ❌ | LightGBM 是 **CPU-only**,特征已缓存 |
| `generate_ens6_submission.py`(inference) | ⚠️ 可选 | 有 GPU 用 `cuda:0` 快很多;CPU 也能跑完 |
| LightGCN 训练(`train_val_lgcn_ensemble.py`) | ✅ | BPR 训练需 `--device cuda:0` |
| BPR-MF 训练(`train_val_mf_bpr.py` / `extra_score_sources_ablation.py`) | ✅ | dim256, batch65536 |
| HGT / SAGE(`train_val_hgt_bpr.py` / `train_val_sage_bpr.py`) | ✅ | 仅做变体/消融,未入最终 |
| DeepWalk/Node2Vec 训练 | ❌ | gensim/node2vec 走 CPU(但需重装这俩包) |
**结论:若只复现最终 0.96626,纯 CPU + `requirements-minimal.txt` 足够。** GPU 仅在你想重训 Stage-1 各源模型(C 档)时需要。
---
## 7. 常见错误与排查
### 7.1 缺 `lightgbm` / `gensim` / `node2vec`
- **症状**:最终 stacker 报 `ModuleNotFoundError: lightgbm`。
- **根因**:用了系统 Python 而非装了 `requirements-minimal.txt` 的环境。事实表 §7 备注也提到审计机 Python 3.14 缺 lightgbm/gensim,因此论文里的"特征重要性图"只能用**消融贡献瀑布**替代 LightGBM gain。
- **解决**:`pip install -r env/requirements-minimal.txt`;重训 RW 才需额外 `pip install gensim node2vec`。
### 7.2 改 `--split-seed` 非 202 → 全链失效
- **症状**:换 seed 后 F1 大幅下降或文件找不到。
- **根因**:`--split-seed` 是 **load-bearing**。`make_notebook_style_split(seed=202)`(见 `train_val_lgcn_ensemble.py:132-165`)把 90/10 留出 + 等量随机负例烤进每个 `.npy`/`.npz` 文件名与内容。所有缓存分数都是 seed-specific。
- **规则**:`--split-seed` 必须保持 **202**;换 seed = 必须从头重跑整个 Stage-1→Stage-2 链路,缓存全部作废。
### 7.3 硬编码图维度 6611 / 79937
- **位置**:`high_order_graph_stack.py`(sparse-matrix shape)与 `train_val_lgcn_ensemble.py:216`(`range(6611)` 采样循环)字面写死。
- **后果**:换数据集会直接 IndexError 或 shape 不匹配。
- **注意**:不要"顺手"把它改成变量 —— 它是与数据集绑定的设计选择,改了反而引入隐性 bug。
### 7.4 路径写法:Windows PowerShell / bash 一律用正斜杠
- 反斜杠 `\` 在 bash 里是转义,在 PowerShell 里也易出错。**统一用正斜杠** `python code/high_order_graph_stack.py --package-root .`,不要 `code\high_order_graph_stack.py`。
- `--package-root` 用 `.`(仓库根)即可;脚本内部用 `Path` 拼接,跨平台安全。
- 遗留坑:部分 metadata 文件(如 `selected_variant_val_scores.txt`)含绝对路径 `/data/lzc/...`,事实表 §18 已声明 —— 以 package-root 为准,这些只是存证残留,不影响复现命令。
### 7.5 legacy 脚本不能直接跑
`run_baseline.py` / `run_improved.py` / `run_v2.py` / `run_final.py` / `run_ultimate.py` / `run_lgcn_final.py` / `run_lgcn_v2.py` / `run_graph_features.py` / `compare_gnn.py` 含硬编码 `/home/lzc/cs3319-project` 路径、无 argparse。**仅作存证**,不要试图直接运行;用现代 `train_val_*` / `generate_*` / `*_ablation` 脚本替代。
---
## 8. 生成论文图表
**本副本没有单一的 `make_all_figures.py`** —— 图表拆成 `code/figures/` 下 10 个独立脚本 + 共享样式 `plot_style.py` + 复算脚本 `gen_val_labels.py`:
```
code/figures/
├─ plot_style.py # 共享 matplotlib 风格(每个 figN import 它)
├─ fig1_dataset_overview.py # 数据集规模/稀疏/长尾
├─ fig2_framework.py # 两阶段框架
├─ fig3_progression.py # 方法演进折线(验证 F1 + 公开 F1)
├─ fig4_highorder_ablation.py # 高阶消融 108→190→214→259
├─ fig5_pr_roc.py # PR/ROC 曲线
├─ fig6_feature_contribution.py # 消融贡献瀑布(非 LightGBM gain!)
├─ fig7_error_analysis.py # 误差分析
├─ fig8_lgcn_ensemble_correlation.py
├─ fig9_lgcn_hyperparam.py
├─ fig10_rw_ensemble_size.py # RW 1→5→7 块增益
└─ gen_val_labels.py # 复算 val-label↔OOF 对齐
```
逐个运行即可,例如:
```bash
cd D:/reps/26H1_cs3319_final_deliverable
python code/figures/fig3_progression.py
python code/figures/fig4_highorder_ablation.py
```
> **图 6 的特别说明**:因审计机无 lightgbm,"特征重要性"无法用 LightGBM gain 画,改用**消融贡献瀑布**(事实表 §7)。复算时注意事实表 §21 的裁决:figures 标题写的 RW 稳态增益 "+0.00182" 偏高,CSV 重算应为 **+0.00155**,出图时以此为准。
---
## 9. 编译 ACM 论文(中文,xelatex)
论文模板在 `ACM_Conference_Proceedings_Primary_Article_Template/`,主文是 `cs3319_final_paper_cn.tex`,用 `acmart` 类 + `xeCJK`(字体 Noto Serif/Sans CJK SC)。**因为是 CJK,必须用 `xelatex`,不能用 pdflatex**:
```bash
cd ACM_Conference_Proceedings_Primary_Article_Template
xelatex cs3319_final_paper_cn.tex
bibtex cs3319_final_paper_cn
xelatex cs3319_final_paper_cn.tex
xelatex cs3319_final_paper_cn.tex
```
- 模板自带 `acmart.cls`、`ACM-Reference-Format.bst`,目录内已有编译产物 `cs3319_final_paper_cn.pdf` 与逐页 PNG,可先看成品。
- 已确认 `BUILD_REPORT.md` / `PAPER_BUILD_AUDIT.md` 记录了构建细节,编译遇问题先查这两份。
- 前置:系统需装 Noto CJK SC 字体族;否则 `\setCJKmainfont` 会报 fontspec 找不到字体。
---
## 10. 端到端从头复现(C 档,仅参考)
若要完整重训(非必需),按事实表 §4 的 stage 顺序:
```
1. train_val_lgcn_ensemble.py → val_vanilla_ensemble_mean.npy(主分数,需 GPU)
2. generate_post95_submission.py → 变体选择 + topk
3. extra_score_sources_ablation.py → BPR-MF + content_mean(需 GPU)
4. randomwalk_systematic_ablation.py --mode small → 6 Word2Vec;再 --mode graph → 4 个(需 gensim,CPU)
5. generate_randomwalk_ensemble_submission.py --versions <7 个块> → RW 一致性 11 维
6. high_order_graph_stack.py --make-submission → 最终 stacker(CPU)
```
> **未明确确认项**(事实表 open_questions):`bigbatch_more` 精确的 `--train-batch-size / --lr / --epochs` 无日志,仅能从 run 名 `l2d512` + 分数文件名反推 `layers=2, dim=512, eval-mode=dot, seeds{41,141}`。其余 13 个 LightGCN 变体参数同理无证。因此 C 档只能"近似复现",B 档才是严格复现。
---
## 11. 复现流程图(文本块)
```
解压副本
├─(A) 读文件 → cat validation_summary.csv → 见 0.966874 ✅ 零依赖
├─(B) pip install -r env/requirements-minimal.txt (CPU)
│ └─ python code/high_order_graph_stack.py --package-root . --split-seed 202 --seed 202 --n-splits 5 --make-submission
│ └─ 验证 F1=0.966874 / 公开 F1=0.96626 ✅ 严格可复现
├─ 图表:python code/figures/figN_*.py(逐个,N=1..10)
├─ 论文:cd ACM_*; xelatex cs3319_final_paper_cn.tex(+bibtex+两遍)
└─(C) conda env create -f env/environment-cs3319.yml + GPU
└─ 从头重训 LightGCN/BPR/RW → 部分历史 run 参数未留存,仅近似
```
---
## 12. 可迁移到论文中的写法(附录 Reproducibility,可直接进 TeX)
> 以下为论文附录"复现性(Reproducibility)"小节的正式表述,可直接粘贴到 `cs3319_final_paper_cn.tex` 的 appendix 部分,术语与正文一致。
```latex
\subsection{复现性说明}
本研究提供三档可执行性,以适应不同审阅场景:
\textbf{(A) 结果核对}无需任何依赖,直接读取
\texttt{validation\_runs/dynamic\_seed202/high\_order\_graph\_stack/validation\_summary.csv}
即可确认验证 $\Fone=0.966874$;该数值已由 \texttt{val\_labels\_seed202.npy} 对
OOF 数组的交叉复算逐位对齐(AUC $0.994918$),无标签泄漏。
\textbf{(B) 严格复现}仅需 CPU 与
\texttt{env/requirements-minimal.txt}(含 \texttt{lightgbm==4.6.0} 等 10 个 pinned 包),
运行 \texttt{code/high\_order\_graph\_stack.py --make-submission}
即可从预缓存特征矩阵与 7 个随机游走权重重建最终 259 维 LightGBM 模型,
重写验证 F1 与公开提交 \texttt{submission\_rich\_rw7\_highorder\_directed\_r0.500000.csv}。
测试决策采用 rank cutoff(取分数排序 top $50\%$ 为正),而非验证最优概率阈值
($0.4617$),因前者对验证集(人工 $1{:}1$)到测试集的分布漂移更稳健。
\textbf{(C) 从零重训}需 GPU(\texttt{--device cuda:0})与完整
\texttt{env/environment-cs3319.yml}(含 \texttt{gensim}/\texttt{node2vec});
个别早期 LightGCN 配置的精确超参未保留日志,故该档仅能近似复现训练历史。
\noindent 关键约束:(i) 验证切分种子 \texttt{--split-seed} 固定为 $202$,
所有缓存分数均与其绑定,更改种子需重建整条 Stage-1 链;
(ii) 图规模字面量 $6611$ (作者) 与 $79937$ (论文) 在
\texttt{high\_order\_graph\_stack.py} 与 \texttt{train\_val\_lgcn\_ensemble.py}
中硬编码,与数据集绑定;
(iii) 路径一律相对仓库根 \texttt{--package-root} 解析,Windows 下建议使用正斜杠。
```