| # 02 系统架构总览(两阶段 Stacking) |
|
|
| > 本文面向"懂一点 ML,但不完全懂图推荐系统"的读者。我们从**为什么**出发,讲清楚最终系统长什么样、每一块为什么存在、它们之间如何互补。所有数字来自权威事实表(`docs_first_principles/_fact_sheet.md`);代码位置带行号,便于回溯。 |
|
|
| --- |
|
|
| ## 1. 一句话定位 |
|
|
| 最终系统是一个**两阶段 stacking(stacked generalization)架构**: |
|
|
| - **Stage-1(生产者)**:多个相互独立、来源各异的模型/规则,各自把"作者-论文"候选对映射成一组 raw 分数或特征向量; |
| - **Stage-2(融合者)**:一个 **259 维**的 **LightGBM** 二级 meta-learner,把 Stage-1 的所有输出拼接成一张特征表,用 5-fold OOF(Out-of-Fold)做 leak-free 验证; |
| - **决策层**:测试阶段不用概率阈值,而用 **rank-cutoff top 50%**,并强制训练-测试重叠的已知正边为 1。 |
|
|
| 最终公开榜 F1 = **0.96626**(`submission_rich_rw7_highorder_directed_r0.500000.csv`)。 |
|
|
| 为什么要两阶段而不是一个端到端 GNN?下一节从第一性原理讲清。 |
|
|
| --- |
|
|
| ## 2. 为什么不直接用一个 GNN:互补性论证 |
|
|
| 单一 GNN(本项目用 LightGCN)的本质是**协同过滤**:它通过 author·paper 点积(`code/train_val_lgcn_ensemble.py:102-104`)和异构图邻居传播,学习一组隐向量,使"被同一类作者读过的论文"在嵌入空间里靠近。它**结构上无法表达**以下信息(这正是 Stage-1 其它模块要补的盲区): |
|
|
| | LightGCN 的盲区 | 谁来补 | 信号性质 | |
| |---|---|---| |
| | 显式引用重叠(A 的历史论文恰好引用/被引候选 p) | 显式图 / meta-path 特征 | 确定性、typed 证据 | |
| | 内容语义相似度(USE 嵌入余弦) | content mean-cos / rich profile | 与 CF 正交的内容维度 | |
| | 矩阵分解互补视角 | BPR-MF(dim=256) | 另一种 CF 分解,捕获 LightGCN 漏掉的协同信号 | |
| | 全局网络接近度(随机游走意义上的"远亲") | DeepWalk / Node2Vec(7 块) | 不可训练的固定游走嵌入 | |
| | 高阶引用传播(作者历史阅读谱沿引用图扩散 k 跳) | 高阶有向传播 `H_k=R·C^k` | meta-path 的高阶、稠密化版本 | |
|
|
| 经验上,单一 LightGCN 集成的验证 F1 仅 **0.9386**;把这些正交信号一层层 stack 上去,最终验证 F1 提升到 **0.9669**(验证)/ **0.96626**(公开)。**单一模型无法触及这个上限,因为不同信号分布在不同的数学空间里,一个损失函数无法同时最优化它们。** Stacking 的作用就是让一个非线性的 meta-learner(LightGBM)去学习"在什么情况下信哪个源"。 |
|
|
| --- |
|
|
| ## 3. 总体 Pipeline(文本树状图) |
|
|
| ``` |
| Official data (data_and_docs/) |
| ├─ author-paper 二部图 (bipartite_train_ann.txt, 682,421 行) |
| │ └─→ LightGCN 集成 ──→ 主分数 + rank 特征 (4 列) |
| │ code/train_val_lgcn_ensemble.py |
| │ |
| ├─ coauthor 无向图 (author_file_ann.txt, 9,663 行) |
| │ + citation 有向图 (paper_file_ann.txt, 327,113 行) |
| │ └─→ 显式图 / meta-path (A-A-P, A-P-P, A-P-A-P) ──→ 18 列手工特征 |
| │ code/stack_rank_calibration.py (ExplicitGraphFeatures) |
| │ |
| ├─ feature.pkl (79,937 × 512 USE 论文嵌入) |
| │ └─→ content mean-cos + rich content profile ──→ 4 + 18 列 |
| │ code/extra_score_sources_ablation.py / content_rich_ablation.py |
| │ |
| ├─ mixed graph (二部 + 合著 + 引用) |
| │ └─→ DeepWalk / Node2Vec (7 块 × 11 列 + 11 列聚合) ──→ 77 + 11 列 |
| │ code/randomwalk_systematic_ablation.py |
| │ code/generate_randomwalk_ensemble_submission.py |
| │ |
| └─ citation 矩阵 (paper→paper 有向) |
| └─→ 高阶引用传播 H_k=R·C^k, G_k=S·R·C^k (fwd/bwd/undir) ──→ 24 + 45 列 |
| code/high_order_graph_stack.py (build_high_order*) |
| |
| Stage-1 分数/特征 ↑ 259 维特征表 ↓ |
| |
| All features ──→ LightGBM 5-fold OOF stacker (259 维) ──→ rank cutoff top 50% |
| code/high_order_graph_stack.py (fit_lgb_oof / fit_full_predict) |
| │ |
| ▼ |
| submission_rich_rw7_highorder_directed_r0.500000.csv |
| (+ cached_scores/test_known_mask.npy 强制 known=1) |
| → 公开 LB F1 = 0.96626 |
| ``` |
|
|
| --- |
|
|
| ## 4. Stage-1:分数源 / 特征源全景 |
|
|
| 下表把每个 Stage-1 模块抽象成"输入 → 输出 → 作用"四元组,是本文的核心查表。 |
|
|
| | 模块 | 输入 | 输出(特征) | 作用 / 为什么需要它 | 代码位置 | |
| |---|---|---|---|---| |
| | **LightGCN 集成** | 二部图 + 论文 USE 嵌入(线性投影输入) | 主分数 → rank 4 列 | 端到端可学习 CF 主分数;整个 stacker 的"地基" | `train_val_lgcn_ensemble.py` | |
| | **显式图 / meta-path** | 合著图 + 引用图 + 二部图 | 18 列手工特征(含 1 列冗余,见台账 #13) | typed 路径证据,单一最大增量源 (+0.0174,把验证 F1 从 0.9386 推到 0.9560) | `stack_rank_calibration.py:108-145` | |
| | **add_rank_features** | 任意标量分数 | 4 列(score / global_rank / author_pct / author_rank) | 把绝对分数转成"作者组内相对位置",缓解跨作者量纲漂移 | `stack_rank_calibration.py:148-160` | |
| | **content mean-cos** | feature.pkl + train_refs | 4 列 | 作者历史论文的 USE 均值 · 候选向量,纯语义相似度 | `extra_score_sources_ablation.py:80-97` | |
| | **rich content profile** | feature.pkl | 18 列 | 深度内容画像(max/top-k 聚合等),比 mean-cos 更细 | `content_rich_ablation.py:54-129` | |
| | **BPR-MF** | 二部图 | 4 列(dim=256 MF 分数) | 与 LightGCN 互补的另一类 CF 分解 | `extra_score_sources_ablation.py:100-163` | |
| | **variant43** | 20 个 LightGCN 变体分数 | 43 列(20×2 zscore+rank01 + 3 聚合) | 把多 run/多 seed 的 LightGCN 分数多样性榨干 | `generate_post95_submission.py:175-183` | |
| | **topk3** | feature.pkl + train_refs | 3 列(max, top-3, top-5) | 作者历史论文中与候选最相似的前若干篇的相似度 | `generate_post95_submission.py:186-216` | |
| | **negative_evidence** | X_hand | 8 列 | 负证据/交互特征,刻画"为什么不读"的另一面 | `post95_ablation.py:176-192` | |
| | **DeepWalk / Node2Vec × 7** | mixed graph | 7×11 = 77 列 + 11 列 aggregate | 不可训练的固定游走嵌入,补 LightGCN 缺的全局接近度 | `randomwalk_systematic_ablation.py:216-270` | |
| | **高阶引用传播(无向)** | R, C, S 稀疏矩阵 | 24 列 | `H_k=R·C^k`(k=1..4)+ `G_k=S·R·C^k`(k=0..3),每路径 {raw, popnorm, log} | `high_order_graph_stack.py:74-122` | |
| | **高阶引用传播(有向)** | R, C(fwd/bwd/undir), S | 45 列 | 三方向 fwd/bwd/undir;ap 路径 {raw,popnorm,delta}、aa_ap 路径 {raw,popnorm} | `high_order_graph_stack.py:125-176` | |
|
|
| > **X_base 小计核对**:4(rank)+18(explicit)+8(neg)+3(topk)+43(variant)+4(content)+4(mf) = **84** 维(`randomwalk_systematic_ablation.py:273-300` `build_base_features` 实测)。其余为 rich content(18)、7 RW 块(77)、RW aggregate(11)、undirected(24)、directed(45),合计 **259**。 |
| |
| --- |
| |
| ## 5. Stage-2:LightGBM stacker 做什么 |
| |
| Stage-2 不是简单加权平均,而是一个**带正则的梯度提升树 meta-learner**,它把 259 维特征表当成普通监督学习输入。两套训练路径(`code/high_order_graph_stack.py`): |
| |
| | 路径 | 用途 | 关键超参 | |
| |---|---|---| |
| | `fit_lgb_oof` (L179-198) | 5-fold StratifiedKFold OOF,产 leak-free 验证 F1 | n_est=1200, lr=0.025, num_leaves=15, reg_lambda=8.0, min_child_samples=100, subsample/colsample=0.9 | |
| | `fit_full_predict` (L201-216) | 全量训练后预测测试对 | n_est=1400, lr=0.022(其余同上) | |
| |
| **为什么用 OOF**:如果直接在验证集上 fit 再在验证集上算 F1,meta-learner 会过拟合到 Stage-1 的分数噪声(尤其 variant43 等由验证分数派生的特征)。5-fold OOF 保证每个验证样本的预测来自"没见过它"的模型,从而 F1 可信。最终 OOF F1 = **0.9668737**,AUC = **0.9949183**,最优概率阈值 = **0.4617308**。 |
| |
| **为什么用 LightGBM 而不是线性回归**:259 维里大量是共线/异构特征(如 out[i,3] 与 out[i,12] 恒等,台账 #13),LightGBM 自动忽略冗余列,并能学到"在 popularity 高时信 content,在长尾时信 high-order"这类非线性交互。 |
| |
| ### 为何叫 two-stage stacking |
| |
| "Two-stage"指**两个训练阶段解耦**:Stage-1 各模型独立训练(LightGCN 反传 BPR、DeepWalk 跑 SkipGram、BPR-MF 跑矩阵分解),Stage-2 在它们的冻结输出上再训一个模型。这是 Wolpert 的 stacked generalization 的标准形态,相对端到端微调有两个工程优势:(1) 每个 Stage-1 模型可独立缓存/替换;(2) Stage-2 看不到原始图,只在分数层做融合,避免梯度噪声互相干扰。 |
| |
| --- |
| |
| ## 6. 决策层:为何用 rank-cutoff 而非概率阈值 |
| |
| 验证集是**人工 1:1** 构造的(68,242 正 + 68,242 负,见 `make_notebook_style_split` L132-165),但测试集真实正例比例未知(台账 #25)。LightGBM 概率在 1:1 上调出的最优阈值 0.4617 迁移到测试集会发生漂移(漂移后正例率 ~0.5242)。 |
| |
| 因此测试决策**故意绕开概率阈值**,改用**排序截断**:按 LightGBM 分数对 ~2.05M 测试对排序,取 top 50% 标 1(`high_order_graph_stack.py:219-229` `write_ratio_submission`),再强制 `test_known_mask.npy` 中的训练-测试重叠正边为 1。ratio 在 {0.498..0.502} 间扫描,公开最佳为 **0.500000**。 |
|
|
| --- |
|
|
| ## 7. 最终方法 vs 早期 LightGCN ensemble 的区别 |
|
|
| | 维度 | 早期 LightGCN ensemble(preliminary 阶段) | 最终方法 | |
| |---|---|---| |
| | 模型形态 | 6 个 checkpoint(5×256d/4层 + 1×384d)的均值集成 | 259 维 LightGBM 两阶段 stacking | |
| | 用到的信号 | 仅 CF 隐向量 | CF + 显式图 + 内容 + MF + 随机游走 + 高阶传播 | |
| | 验证 F1 | 0.9386(dynamic best,未单独提交) | 0.9669 | |
| | 公开 F1 | 0.93044(`sub_ens6_t0.36.csv`) | **0.96626** | |
| | 决策 | 概率阈值 t=0.36 | rank-cutoff top 50% + known mask | |
|
|
| 注意(台账 #10):早期 6-model 与 dynamic best(`dyn202_l2d512_bpr_bigbatch_more`,2 seed × 512d/2层 dot)是**两个不同阶段产物**,都常被笼统称作"LightGCN ensemble"。最终 stacking 用的主分数是后者(验证 0.9386)。 |
|
|
| ### baseline → final 的演进主线 |
|
|
| | 序 | 阶段 | 验证 F1 | 单步增量 | |
| |---|---|---|---| |
| | 0 | baseline(启发式/度) | 0.8850 | — | |
| | 1 | LightGCN 异构 CF 集成 | 0.9386 | +0.0536 | |
| | 2 | + 显式图 / meta-path stacking | 0.9560 | **+0.0174(单一最大)** | |
| | 3 | + Post95 变体 + content + BPR-MF + rich content | 0.95990 | +0.0039 | |
| | 4 | + DeepWalk / Node2Vec → 7 块 ensemble | 0.964921 | +0.0050 | |
| | 5 | + 高阶引用传播(undirected → directed, 259 维) | **0.966874** | +0.0020 | |
|
|
| 可以看到,**最大单步增益不在 GNN 本身,而在把 CF 与显式图结构融合(第 2 步)**;之后的高阶传播、有向化是锦上添花(验证 F1 0.9649 → 0.9669)。 |
|
|
| --- |
|
|
| ## 8. 模块间的互补关系(一句话版) |
|
|
| - **LightGCN**:学会"谁和谁一起读"——CF 共现隐式表示。 |
| - **显式图/meta-path**:直接数"引用重叠、合著者共读、A-P-A-P"——CF 学不到的确定性证据。 |
| - **content / rich**:看"论文说了什么"——与 CF 正交的语义维度。 |
| - **BPR-MF**:另一种矩阵分解,补 LightGCN 漏掉的协同因子。 |
| - **DeepWalk/Node2Vec**:跑随机游走得全局接近度——补 LightGCN 的局部传播盲区。 |
| - **高阶有向传播**:把"作者历史阅读谱沿引用图扩散 k 跳"——meta-path 的高阶、稠密、方向感知版本。 |
|
|
| 这六个源覆盖了**协同、结构、语义、全局游走、高阶传播**五个互补视角,meta-learner 负责按上下文加权组合。 |
|
|
| --- |
|
|
| ## 9. 数据流一致性说明 |
|
|
| `make_notebook_style_split(seed=202, train_frac=0.9)` 产出的 136,484 对验证集(68,242 正 + 68,242 负)是**所有 Stage-1 分数与 Stage-2 OOF 的统一对齐基准**。因为 split seed 烘焙进每一个 `.npy`,**改 seed 会整链失效**。最终 OOF 与 `val_labels_seed202.npy` 已复算对齐(F1 0.966874,AUC 0.994918),无泄漏。 |
|
|
| --- |
|
|
| ## 可迁移到论文中的写法 |
|
|
| > 以下为可直接进 ACM 中文论文 TeX 的正式表述,数字与权威事实表一致。 |
|
|
| **系统架构段(建议放在 Method 节首)** |
|
|
| ``` |
| 本文提出的推荐系统采用两阶段堆叠(two-stage stacking)架构。 |
| Stage-1 由若干相互独立、信号正交的模块构成,包括:基于异构图的 |
| LightGCN 协同过滤集成、显式元路径(A-A-P / A-P-P / A-P-A-P)手工 |
| 特征、基于预训练 512 维 Universal Sentence Encoder 的内容相似度 |
| 与画像特征、BPR 矩阵分解、7 个 DeepWalk/Node2Vec 随机游走嵌入块, |
| 以及本文核心贡献——高阶有向引用传播。各模块在统一的全连接异构图 |
| (6{,}611 位作者、79{,}937 篇论文)上独立训练,其输出拼接为 259 |
| 维特征向量,送入 Stage-2 的 LightGBM 二级元学习器。 |
| |
| 元学习器采用 5 折分层交叉验证的 Out-of-Fold (OOF) 策略以获得 |
| 无泄漏的验证性能(F1 = 0.9669,AUC = 0.9949)。测试阶段,由于 |
| 验证集为人工构造的 1:1 平衡集,其最优概率阈值无法直接迁移,因此 |
| 采用排序截断(rank-cutoff)决策:按元学习器分数对约 2.05M 测试对 |
| 排序,取前 50% 预测为正,并强制训练-测试重叠的已知正边为正。 |
| 最终在公开榜单取得 F1 = 0.96626。 |
| ``` |
|
|
| **核心贡献公式段** |
|
|
| ``` |
| 记 $R \in \mathbb{R}^{|A|\times|P|}$ 为行归一化的作者-论文邻接矩阵, |
| $C \in \mathbb{R}^{|P|\times|P|}$ 为行归一化的论文引用转移矩阵 |
| (分别按前向 / 后向 / 无向三种边方向构造),$S \in \mathbb{R}^{|A|\times|A|}$ |
| 为行归一化的合著矩阵。定义两类高阶传播分数: |
| \[ |
| H_k = R\,C^k,\qquad G_k = S\,R\,C^k . |
| \] |
| $H_k[a,p]$ 衡量作者 $a$ 的历史阅读谱沿引用网络传播 $k$ 跳后到达候选 |
| 论文 $p$ 的累积亲和度;$G_k[a,p]$ 则将合著邻居的历史阅读谱聚合为 |
| "合作者群体共识"后再传播 $k$ 跳。三种边方向 × 多跳 × {原始值、 |
| popularity 归一化、对数压缩 / 逐跳增量} 共产生 69 维高阶特征, |
| 与 Stage-1 其它特征联合送入元学习器。 |
| ``` |
|
|
| **消融贡献段(替代 LightGBM gain,因本机无 lightgbm)** |
|
|
| ``` |
| 由于运行环境未安装 LightGBM,无法直接报告基于 gain 的特征重要性。 |
| 本文改用消融瀑布(ablation waterfall)刻画各模块的边际贡献: |
| 在固定 seed=202 的 1:1 OOF 上,相对 LightGCN 基线(F1 = 0.9386), |
| 引入显式元路径带来 +0.0174(单一最大增益),BPR/内容/画像特征共 |
| +0.0039,7 块随机游走集成 +0.0050,高阶有向引用传播 +0.0020, |
| 最终达到 F1 = 0.9669。 |
| ``` |
|
|
| **模块—输入—输出—作用表(可直接转 LaTeX tabular)** |
|
|
| | 模块 | 输入 | 输出特征 | 作用 | |
| |---|---|---|---| |
| | LightGCN 集成 | 二部图 + USE 嵌入 | 主分数 + rank (4 维) | CF 协同过滤主分数 | |
| | 显式图 / meta-path | 合著图 + 引用图 + 二部图 | 18 维 | typed 路径证据(最大增量源) | |
| | content mean-cos / rich profile | feature.pkl | 4 + 18 维 | 内容语义相似度与画像 | |
| | BPR-MF | 二部图 | 4 维 (dim=256) | 互补 CF 分解 | |
| | variant / topk / negative | LightGCN 多变体 + feature.pkl | 43 + 3 + 8 维 | 多样性与负证据 | |
| | DeepWalk / Node2Vec (×7) | mixed graph | 77 + 11 维 | 全局随机游走接近度 | |
| | 高阶引用传播 | R, C(fwd/bwd/undir), S | 24 + 45 维 | 高阶稠密化 meta-path | |
| | LightGBM meta-learner | 上述 259 维 | OOF F1 0.9669 | 非线性融合 | |
| | rank-cutoff 决策 | 测试分数 + known mask | submission CSV | 公开 F1 0.96626 | |
|
|