cs3319-project2 / docs_first_principles /10_code_map_and_entrypoints.md
NLP-beginner's picture
CS3319 Project 2 final deliverable (public F1 = 0.96626)
f28d994
|
Raw
History Blame Contribute Delete
13.5 kB
# 10 · 代码地图与入口脚本(System Code Map)
> 本文档是 `code/` 目录的**导航地图**:30+ 个脚本各自干什么、谁是入口、谁是被运行时加载的"库"、谁是只能看不能跑的 legacy。读完本文你应当能在 30 秒内回答:"我想改 X,该动哪个文件?""我想重跑最终方法,敲哪条命令?""为什么没有 `utils.py`?"
> 代码定位以 `docs_first_principles/_fact_sheet.md` §6 为准(状态日期 2026-06-18)。路径相对仓库根,带行号。
目标读者:第一次进仓库、对"`importlib` 互相加载 + 无共享包"这套非典型工程组织感到困惑的人。先讲**为什么这样组织**(第一性原理),再给**地图与入口**
---
## 1. 第一性原理:为什么没有 `utils.py`,而是"脚本互加载"
### 1.1 典型工程 vs 本仓库
典型项目会把公共函数抽到 `utils.py` / `common/` 包,其他模块 `import`**本仓库没有。** 它是研究/实验代码,追求"一个脚本跑完一个实验、改完立刻能跑"。于是演化出一种**去中心化的代码复用**:每个脚本用一段近乎相同的 `load_module()` helper 把**同目录的另一个脚本**当模块加载:
```python
def load_module(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
```
### 1.2 这样设计的后果(读者必须知道)
- **没有显式接口/版本**。函数签名、返回顺序全靠"加载方与被加载方的默契"。读下游脚本时,常需要跳到被加载脚本确认函数行为。
- **两个脚本成了事实共享库(de-facto shared library)**,被 ~14–16 个脚本加载(见 §4)。**改这两个文件的任何函数,等于同时改了十几个下游脚本的行为**——blast radius 极大。
- **每个脚本各自重定义本地 `read_txt`** 等小工具,不统一。
> 这不是"坏味道"要重构,而是研究代码的合理权衡:**快速试错优先于工程整洁**。理解了这一点,才不会被"为什么这么乱"绊住,而能专注"数据怎么流"。
---
## 2. 脚本分类速览(四类)
| 类别 | 命名模式 | 角色 | 例子 |
|---|---|---|---|
| **训练** | `train_val_*.py` | Stage-1 模型训练 + 出验证/测试分数 | `train_val_lgcn_ensemble.py` |
| **特征/分数消融** | `*_ablation.py` | 单组特征验证 + 拼特征矩阵 | `randomwalk_systematic_ablation.py` |
| **submission 生成** | `generate_*submission.py` | 把分数/特征转成提交 CSV | `generate_post95_submission.py` |
| **stacking / 校准 / 搜索** | `stack_*``score_level_*``error_group_*``search_*``rich_randomwalk_stack.py` | 二阶段融合、阈值/比例搜索、误差分析 | `stack_rank_calibration.py` |
| **(存证)** | `run_*.py``compare_gnn.py` | **legacy,不可直接跑**(见 §6) | `run_baseline.py` 等 9 个 |
---
## 3. 三条核心入口命令
### 3.1 重跑最终方法(出 submission)
```bash
python code/high_order_graph_stack.py \
--package-root . --split-seed 202 --seed 202 --n-splits 5 --make-submission
```
- **命中全部缓存**(`feature_cache/``randomwalk_systematic/models/`、LightGCN 分数),CPU 秒级出 OOF + submission。
- 不加 `--make-submission` = 仅验证。
- 详见 `03_data_flow_and_artifacts.md` §9。
### 3.2 生成图表
本仓库有**两套图表包**(见记忆 `figures-and-label-alignment`):
```bash
# ACM 双栏正式版(3.25/6.75 in, pdf.fonttype=42),论文用
python figures_paper/scripts/make_all_figures.py --package-root .
# 早期 seaborn 版(更大字号),探索/汇报用
# (code/figures/ 下各 figN_*.py 独立可跑)
```
`figures_paper/scripts/make_all_figures.py` 自动建目录、逐图 try/except 隔离、读真实数据(缺失则优雅跳过)、生成 pdf/png/svg + `README_figures.md`**单个图失败不会拖垮其余图。**
### 3.3 编译论文 TeX
> **诚实说明(重要):** 本仓库**不包含论文 `.tex` 工程或编译命令**。`docs_first_principles/SUMMARY_FOR_PAPER.md` 提供的是**可直接迁移进 TeX 的成稿段落、表格、公式、caption**(中文,保留英文术语),以及建议的 `\section` 结构。实际编译发生在用户的**独立论文项目**里,把 SUMMARY 的段落复制进 `.tex`、按需改 `\[ \]` / `equation` 环境即可。本审计遵循"不覆盖/不改论文模板"的约束,未在仓库内增设任何 TeX 构建。
---
## 4. 两大事实共享库(改动 blast radius 最大)
被 ~14–16 个脚本经 `load_module()` 运行时加载,改它们 = 改全链:
### 4.1 `train_val_lgcn_ensemble.py`(数据 + 切分 + 评分)
| 函数/符号 | 行号 | 作用 | 为什么被到处加载 |
|---|---|---|---|
| `make_notebook_style_split` | 132-165 | seed=202 切分 → 1:1 验证集(136,484 对) | **所有下游都要用同一验证 pair/label** |
| `build_parts` | 168-236 | 加载 feature.pkl + 度特征 + popular 集 + coauthor_pool(L216 硬编码 `range(6611)`) | 负采样/特征都依赖这些预计算 |
| `build_data` | 239-270 | PyG HeteroData:4 边类型,引用/合著双向拼接,论文 515→embed 投影 | LightGCN 训练数据装配 |
| `LightGCNLayer` / `encode` / `decode` | 49-104 | 纯加权邻居聚合(无 W 无非线性)+ 点积解码 | 模型本体 |
| `sample_hard_negatives` | 273-305 | 硬负采样 random50%/popular25%/coauthor25% | 训练循环 |
| `best_f1` | 340-346 | PR 曲线 argmax 最优 F1 阈值 + AUC | **所有 stacker 算验证 F1 都用它** |
| `predict_scores` | 308-337 | no_grad 批量打分(cos/dot/neg_l2) | 推断 |
### 4.2 `stack_rank_calibration.py`(显式图特征 + rank + OOF)
| 函数/符号 | 行号 | 作用 |
|---|---|---|
| `ExplicitGraphFeatures.__init__` | 54-106 | 默认 6611 作者 / 79937 论文;预计算 shared_paper_authors(A-P-A) + coauthor_paper_union(A-A-P) |
| `ExplicitGraphFeatures.transform` | 108-145 | 18 维手工特征(L139 与 L130 重复一列,台账 #13) |
| `add_rank_features` | 148-160 | 4 列:score/global_rank/author_pct/author_rank |
| `fit_oof` | 163-184 | 5 折 StratifiedKFold;LGBM(1200, lr0.025, reg_lambda5) |
> 关键事实:本文件**全文无 `scipy.sparse`**(台账 #12)——CLAUDE.md 所指的"sparse-matrix 高阶传播"在 `high_order_graph_stack.py`,**不**在此文件。meta-path 全用 Python set 交/并集实现。
### 4.3 其他高频被加载的 peer
| 脚本 | 被加载的关键函数 | 用途 |
|---|---|---|
| `generate_post95_submission.py` | `select_variant_val_scores``topk_content_similarity_fast` | 变体选择 + top-k 内容相似 |
| `post95_ablation.py` | `negative_evidence_features` | 8 维负证据 |
| `extra_score_sources_ablation.py` | `content_mean_score``score_to_features` | content_mean-cos + BPR-MF |
| `randomwalk_systematic_ablation.py` | `build_base_features`、`pair_feature_block` | X_base 84 维 + 每块 11 维 RW |
| `content_rich_ablation.py` | `content_rich_features` | 从 feature.pkl 产 18 维 rich |
| `generate_randomwalk_ensemble_submission.py` | `aggregate` | 11 维 RW 一致性聚合 |
---
## 5. 最终入口:`high_order_graph_stack.py` 为什么是终点
它是**唯一同时做三件事**的脚本:① 拼接全部 259 维特征;② 训练最终 LightGBM OOF stacker;③ 出 submission。逻辑上它是 Stage-2 的实现 + 决策规则的实现,所以是"最终方法入口"。
| 关键符号 | 作用 | 备注 |
|---|---|---|
| `build_high_order*` | $H_k=R\cdot C^k$、$G_k=S\cdot R\cdot C^k$,fwd/bwd/undir | **scipy.sparse 幂乘 + top-k 剪枝(k=1500)**;硬编码稀疏矩阵形状 6611×79937(见 §7) |
| `fit_full_predict` | LightGBM(num_leaves=15, reg_lambda=8, lr=0.022, n_est=1400) 5-fold OOF | 最终超参(事实表 §2.7) |
| 决策 | sort → top 50% = 1 + `test_known_mask.npy` 强制已知正边 | rank-cutoff(详见 08) |
输出目录:`validation_runs/dynamic_seed202/high_order_graph_stack/`(`validation_summary.csv``*_oof.npy``*_test_pred.npy``submissions/*.csv`)。
---
## 6. Legacy 脚本:能看不能跑
以下 9 个是早期原型,**硬编码 `/home/lzc/cs3319-project` 路径、无 argparse**,仅作存证(provenance)。要跑必须先改路径,且功能已被现代 `train_val_*` / `generate_*` / `*_ablation` 取代——**不要用它们复现结果**:
```
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
```
> 它们的价值:记录"从复杂 GNN(`HeteroMeanConv`/SAGE)退回轻量 CF(LightGCN)"的方法演进史(见 `09_experiment_timeline.md` §3.1)。`notes/experiment_history.md` 有对应叙事。
---
## 7. 完整脚本地图表
> "改动影响"列:🔴 共享库(改动影响十几个下游);🟡 多下游加载;🟢 独立入口(改了只影响自己);⚪ legacy(勿动)。
> 硬编码维度 **6611(作者)/79937(论文)** 在 `high_order_graph_stack.py`(稀疏矩阵形状)与 `train_val_lgcn_ensemble.py`(L216 采样循环)字面出现,勿随手"重构"成变量(它们匹配本数据集,见 CLAUDE.md gotchas)。
| 脚本 | 角色 | 主要输入 | 主要输出 | 改动影响 | 备注 |
|---|---|---|---|---|---|
| `train_val_lgcn_ensemble.py` | LightGCN 训练 + 切分 + 评分 | `bipartite_train_ann.txt`、`feature.pkl` | `scores/val_vanilla_ensemble_mean.npy`、切分快照 | 🔴 | 两大共享库之一;`best_f1`/`make_notebook_style_split` 全链用 |
| `stack_rank_calibration.py` | 显式图/meta-path 特征 + rank + OOF | LightGCN 分数、`bipartite_train` | explicit18 + rank4 特征、`fit_oof` | 🔴 | 两大共享库之一;**无 sparse** |
| `high_order_graph_stack.py` | **最终 stacker + submission** | 全部 259 维特征(命中缓存) | `*_oof.npy`、`*_test_pred.npy`、`submission_*.csv` | 🟢 | 最终入口(§3.1);**有 sparse 幂乘** |
| `generate_post95_submission.py` | 变体选择 + top-k 内容 + submission | LightGCN 变体分数、test 镜像 | variant43、topk3、submission | 🟡 | `select_variant_val_scores`/`topk_content_similarity_fast` |
| `post95_ablation.py` | 负证据特征 | X_base 前 84 列 | neg8 | 🟡 | `negative_evidence_features` |
| `extra_score_sources_ablation.py` | BPR-MF + content_mean-cos | LightGCN 分数、`feature.pkl` | `val_mf_bpr_s202_d256.npy`、content_mean | 🟡 | `content_mean_score`/`score_to_features` |
| `content_rich_ablation.py` | rich content 画像 | `feature.pkl` | content_rich_*.npy(18 维,命中 feature_cache) | 🟡 | `content_rich_features` |
| `randomwalk_systematic_ablation.py` | 7 块 DeepWalk/Node2Vec | 异构图边 | 7× `.model` + `pair_features/*.npz`(11 维/块) | 🟡 | `build_base_features`/`pair_feature_block`;需 GPU/Word2Vec |
| `generate_randomwalk_ensemble_submission.py` | RW 一致性聚合 + submission | 7 块 pair 特征 | aggregate 11 维、submission | 🟡 | `aggregate()` |
| `score_level_*``error_group_*``search_*` | 阈值/比例搜索、误差分桶 | OOF、各 submission | 分析 CSV(出图用) | 🟢 | 如 `error_analysis_buckets.csv` |
| `rich_randomwalk_stack.py` | RW 专用 stacker | RW 特征 | RW 阶段 OOF/CSV | 🟢 | 中间实验 |
| `generate_ens6_submission.py` | 早期 6-model ensemble submission | checkpoints/ens6 | 6-model submission | 🟢 | 0.93044 那版(早期) |
| `run_*.py``compare_gnn.py`(9 个) | legacy 原型 | — | — | ⚪ | 硬编码 `/home/lzc`、无 argparse,勿直接跑(§6) |
---
## 8. 常见问题速答
| 问题 | 答案 |
|---|---|
| 没有 `utils.py` 怎么共享代码? | `importlib.util``load_module()` 运行时加载同目录脚本(§1) |
| 改一个公共函数影响多大? | 改 `train_val_lgcn_ensemble.py`/`stack_rank_calibration.py` = 影响十几个下游(🔴);改入口脚本只影响自己(🟢) |
| 为什么有的脚本没 `argparse`? | 那是 `run_*.py` legacy,早期原型,已被现代脚本取代(§6) |
| 最终方法入口是? | `high_order_graph_stack.py --make-submission`(§3.1) |
| 图表怎么出? | `figures_paper/scripts/make_all_figures.py --package-root .`(§3.2) |
| 论文 TeX 在哪编译? | 仓库内**没有** TeX 工程;SUMMARY_FOR_PAPER 提供可迁移段落,在用户独立项目编译(§3.3) |
| 6611/79937 哪来的? | 数据集固定规模,字面硬编码在两个脚本里,匹配本数据集勿重构(§7) |
---
## 可迁移到论文中的写法
> **实现概述段(Implementation,可直译进 TeX)。** 我们的系统由若干 Python 脚本组成,按"训练 / 特征消融 / 提交生成 / 堆叠校准"四类组织。其中两个脚本——`train_val_lgcn_ensemble.py`(数据装配、确定性验证切分、最优 F1 阈值)与 `stack_rank_calibration.py`(显式图与元路径特征、排名校准、out-of-fold 训练)——作为共享基础库,被约十余个下游脚本以运行时模块加载的方式复用,故其接口在整条流水线中保持一致。最终的两阶段堆叠与 rank-cutoff 决策由 `high_order_graph_stack.py` 实现:它将来自协同过滤、矩阵分解、随机游走、内容画像与有向高阶引用传播的全部 259 维特征横向拼接,以强正则 LightGBM 作二级元学习器融合。关键的高阶传播以稀疏矩阵幂乘实现($H_k=R\,C^k$、$G_k=S\,R\,C^k$),并通过每次幂乘后的 top-k 行裁剪将 $|P|\times|P|$ 的稠密化控制在可计算规模。所有中间分数与特征均缓存于 `validation_runs/` 下,并依确定性切分种子(seed=202)组织,保证实验可复现。