File size: 21,493 Bytes
f28d994 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | # 06 — 随机游走特征 (DeepWalk / Node2Vec) 第一性原理
> 目标读者: 懂一点 ML、但不完全熟悉图推荐系统的同学。本文回答: 随机游走在图上到底做了什么、DeepWalk 的核心思想、Node2Vec 比 DeepWalk 多出来的 p/q 是什么、为什么游走 embedding 是 LightGCN 之外的**互补**信号、为什么本项目要用 **7 个**游走配置块、ensemble size 曲线怎么解释、游走特征与显式图/meta-path 特征的区别, 以及它为什么能提升泛化。
> 权威数字来源: `code/randomwalk_systematic_ablation.py` + `code/generate_randomwalk_ensemble_submission.py` 源码 + `validation_runs/dynamic_seed202/randomwalk_systematic/` 下缓存产物, 与 `fact_sheet` §2.3 / §2.5 对齐。
---
## 1. 随机游走在图上做什么: 一句话与一幅图
**一句话**: 随机游走 (random walk) 就是"在图上瞎逛"——从某个节点出发, 每一步**随机跳到一个相邻节点**, 走出一条节点序列; 把这张图上无数条这样的序列当作"句子", 喂给自然语言里的 Word2Vec, 就能给每个节点学出一个 embedding。
**为什么这能产生 embedding?** 因为 Word2Vec 的语言模型假设是"**上下文相似的词, 语义相似**"。我们把"节点"当成"词"、"游走序列"当成"句子", 那么它的潜台词就变成: **在游走里经常一起出现的节点, embedding 应该靠近**。而在图上"经常一起出现"恰恰意味着"图距离近、邻域结构相似"——于是节点向量就编码了**全局网络接近度**, 这正是链路预测(本项目 = author-paper 推荐)所需要的信号。
伪代码层面的游走生成 (对应 `code/randomwalk_systematic_ablation.py:139-157`, DeepWalk 路径):
```
deepwalk_walks(G, walk_length, num_walks, seed):
rng = np.random.default_rng(seed)
walks = []
for r in range(num_walks): # 每个节点起 num_walks 条游走
order = shuffle(G.nodes, rng)
for start in order:
walk = [start]; cur = start
for step in range(walk_length - 1):
cur = uniform_choice(neighbors(cur)) # 均匀采样 = 无偏
walk.append(cur)
walks.append(walk)
return walks # 一堆"节点句子"
```
关键: 这里的 `uniform_choice` 是**无差别地从邻居里均匀抽一个**, 等价于 Node2Vec 里 $p=q=1$。这一点是 DeepWalk 与 Node2Vec 的分水岭, 第 3 节展开。
### 1.1 一个具体的"无直接边却频繁接近"的例子
这是理解游走特征价值的**核心直觉**。设:
- 作者 $a$ 读过论文 $p_1$;
- 论文 $p_1$ 被论文 $p_2$ 引用 ($p_1 \to p_2$);
- 另一位作者 $b$ 也读过 $p_2$;
- 现在要判断 $a$ 是否会读 $p_2$。
在**显式图**里, $a$ 和 $p_2$ 之间**没有任何直接边**。于是:
- LightGCN 的逐层传播在层数浅时, $a$ 的表示里几乎不含 $p_2$ 的成分;
- 显式 meta-path 特征(A-P-P、A-A-P 这类精确路径枚举)也得**人为指定**路径模式才数得到 $p_2$, 容易漏掉更长、更绕的通路。
但随机游走会反复走 $a \to p_1 \to p_2$ 这样的折线, 甚至 $a \to p_1 \to b \to p_2$ 这种经过合著者 $b$ 的二跳三跳通路。**走多了之后**, $a$ 的"句子"里 $p_2$(以及和 $p_2$ 邻域结构相似的论文)频繁与 $a$ 共现, Word2Vec 就会把 $\mathbf{e}_a$ 与 $\mathbf{e}_{p_2}$ 拉近。于是即便没有直接边, 它们的 embedding **相似度升高**, 给链路预测一个正向信号。这就是"游走捕捉**多跳、隐式、全局**接近度"的本质——它**不要求你事先指定路径模式**, 而是把所有路径按概率加权地揉进 embedding。
---
## 2. DeepWalk 的核心思想: 图 → 语料 → Word2Vec
DeepWalk (Perozzi et al., 2014) 的三步法:
| 步骤 | 做什么 | 本项目对应 |
|---|---|---|
| 1. 截断随机游走 | 每个节点起 $N$ 条长 $L$ 的无偏游走, 拼成"语料" | `deepwalk_walks`, `num_walks=10`, `walk_length=40 或 80` |
| 2. SkipGram | 用滑动窗口(半径 $w$)对每个中心节点预测上下文节点, 学 embedding | `Word2Vec(sg=1, window=10, min_count=0, negative=5, epochs=3)` (`code/randomwalk_systematic_ablation.py:166-176`) |
| 3. 取向量 | 每个节点得到 $d$ 维向量 | `dim=128 或 256` |
为什么是 SkipGram (`sg=1`) 而不是 CBOW? SkipGram 是"用中心词预测周围词", 适合**稀疏图**——本项目二部图密度仅 $1.29\times10^{-3}$、约 56% 作者度=1, 极稀疏。SkipGram 对低频/孤立节点更友好。`min_count=0` 保证**所有节点都被训练**(包括度=1 的叶子作者), 不丢弃长尾; `negative=5` 是标准负采样数。
**DeepWalk = 无偏随机游走 + SkipGram**。它的 embedding 编码的是"**节点在无向无权采样下被一起访问的概率**", 即一种**对称的、全局的**接近度。
---
## 3. Node2Vec 比 DeepWalk 多什么: p 与 q 两个旋钮
DeepWalk 的"均匀抽邻居"把所有方向一视同仁。Node2Vec (Grover & Leskovec, 2016) 引入两个参数, 让游走**有偏好**地走:
| 参数 | 含义 | 大/小的效果 |
|---|---|---|
| $p$ (return parameter, 返回参数) | 刚刚离开节点 $t$ 来到 $w$, 现在要不要**回头**走到 $t$ | $p$ 大 → 抑制回头(鼓励往外探索); $p$ 小 → 爱走回头路 |
| $q$ (in-out parameter, 出入参数) | 下一步走"离 $t$ 近的节点"(往回走, 类 BFS) 还是"离 $t$ 远的节点"(往外走, 类 DFS) | $q$ 大 → 偏 **BFS**(局部、结构等价); $q$ 小 → 偏 **DFS**(远距离、社区同质) |
直觉: **BFS 偏好捕捉"结构等价"(structural equivalence)**——比如两个作者在各自社区里都是"枢纽", 即便相距很远, BFS 游走会让它们 embedding 相似; **DFS 偏好捕捉"同质性/社区归属"(homophily)**——同一社区/同一研究方向的节点走得近。
- DeepWalk = Node2Vec 在 $p=q=1$ 的特例: 无偏, 既不偏 BFS 也不偏 DFS。
- 本项目**最终只保留了一个 Node2Vec 配置**: `n2v_p2_q1` ($p=2.0, q=1.0$), 见 `code/randomwalk_systematic_ablation.py:114`。它的 $p=2$ 抑制回访、$q=1$ 中性, 属于"往外探一点、结构略偏 BFS"的中间态, 与 DeepWalk 的无偏采样子空间不同 → **嵌入互补**(见第 4 节)。
> 备注: `small_configs` (L92-94) 还定义了 `n2v_bfs`($p=1,q=2$)、`n2v_dfs`($p=1,q=0.5$)、`n2v_bal`($p=1,q=1$) 三个 Node2Vec, 但 `models/` 目录里**没有对应的 `.model` 文件**, `ensemble_7` 也不用它们。即: 这些 BFS/DFS 极端配置在实验中被**弃用**了, 最终入模的只有 $p=2,q=1$ 这一份 Node2Vec。这是"定义了但没用"的典型坑。
---
## 4. 为什么游走 embedding 是 LightGCN 之外的互补信号
这是本特征族能进 stacking 的**根本理由**。逐点对比:
| 维度 | LightGCN (Stage-1 主分数) | DeepWalk/Node2Vec (本特征族) |
|---|---|---|
| 表示来源 | 端到端**可学习**的图卷积, 通过 BPR 损失反传 (`train_val_lgcn_ensemble.py`) | **不可训练**的固定游走 + SkipGram 伪词嵌入 |
| 优化目标 | 排序损失(BPR), 直接为"区分正负 author-paper 对"优化 | 词共现似然, **与推荐任务无关**, 是无监督的 |
| 邻域聚合 | 1/(L+1) 加权邻居均值, 层数浅、近邻主导 | 游走隐式做了**多跳、按路径概率加权**的聚合, 能到 3-5 跳 |
| 偏差 | 学出来的表示对**训练正边**拟合强, 易过拟合已知交互 | 无监督, 不直接看标签, 编码的是**纯图结构**, 偏差不同 |
| 对长尾 | 度=1 的叶子作者表示主要由其邻居决定, 表达力受限 | `min_count=0` + 游走会把叶子节点也放进语料, 至少能学到它的一阶邻域 |
**互补性来自哪里?** 二者的**归纳偏置(inductive bias)正交**: LightGCN 像"任务特化的强拟合器", 游走 embedding 像"任务无关的结构先验"。一个 stacker 同时拿到这两种信号, 就能: (1) 用 LightGCN 的强排序能力兜底大多数简单对; (2) 用游走的**多跳隐式通路**(第 1.1 节例子)挽救那些"无直接边但图上相近"的对——这些正是 LightGCN 在浅层下会判错的边界样本。stacking 的 LightGBM meta-learner 自动学会"在对的情况下用哪种信号"。
---
## 5. 为什么是 7 个 RW blocks: 多样性即互补
单块游走 embedding 已经是有效特征, 但**一块的方差有限**(只覆盖一种游走策略/图结构)。本项目用 **7 个配置块** 来制造**结构多样性**, 让每块捕捉不同视角:
### 5.1 七个最终配置块(与 `models/` 实存模型 + README 复现命令一致)
| 序 | version_name | 方法 | 图类型 | dim | walk_len | num_walks | window | p/q | seed | 定义行 |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | `dw_base_d128_l40_w10_win10` | DeepWalk | full | 128 | 40 | 10 | 10 | — | 202 | L89 |
| 2 | `dw_long_d128_l80_w10_win10` | DeepWalk | full | 128 | **80** | 10 | 10 | — | 202 | L90 |
| 3 | `dw_highdim_d256_l40_w10_win10` | DeepWalk | full | **256** | 40 | 10 | 10 | — | 202 | L91 |
| 4 | `dw_d256_l80_w10_win10` | DeepWalk | full | **256** | **80** | 10 | 10 | — | 202 | L112 (extra) |
| 5 | `dw_seed3407_d128_l40_w10_win10` | DeepWalk | full | 128 | 40 | 10 | 10 | — | **3407** | L110 |
| 6 | `dw_graph_ap_pp` | DeepWalk | **ap_pp**(A-P + P-P) | 128 | 40 | 10 | 10 | — | 202 | L102 |
| 7 | `n2v_p2_q1_d128_l40_w10_win10` | **Node2Vec** | full | 128 | 40 | 10 | 10 | **2.0/1.0** | 202 | L114 |
共 **6 DeepWalk + 1 Node2Vec**。多样性的三个旋钮:
- **walk_length (40 vs 80)**: 长游走看到更远的结构(更 DFS、更全局), 短游走更局部。
- **dim (128 vs 256)**: 高维编码更多结构细节但更易过拟合, 低维更平滑。
- **seed (202 vs 3407)**: 不同随机种子 → 不同游走采样 → embedding 的**随机性扰动**, 多块平均能降噪(类似 bagging)。
- **graph_type (full vs ap_pp)**: `full` 图同时含 A-P / A-A / P-P 三类边且 A-P 边权 3.0(`build_graph` L127-135, 合著/引用边权 1.0); `ap_pp` 只保留 A-P 和 P-P(去掉了合著 A-A), 看的是"作者-论文-引用"纯学术链, 视角不同。
- **method (DeepWalk vs Node2Vec)**: 无偏 vs 偏 BFS, 邻域结构不同。
**边权设计很关键**: A-P 边权 3.0、A-A/P-P 边权 1.0, 让游走**优先沿作者-论文主任务边走**, 同时保留合著/引用作为"捷径"。这是把任务先验注入无监督游走的小技巧。
### 5.2 每块产出 11 维 pair 特征
游走得到的是节点向量, 但我们的预测单元是 **(author, paper) 对**, 不是单个节点。所以要把两个节点向量**融合**成 pair 特征。`pair_feature_block` (`code/randomwalk_systematic_ablation.py:216-270`) 产出 11 列:
| 类别 | 特征 | 含义 |
|---|---|---|
| 直接交互 (5) | `dot`, `cos`, `hadamard_mean`, `absdiff_mean`, `l2_distance` | 两向量的点积、余弦、逐元素积均值、绝对差均值、欧氏距离 |
| 全局排序 (2) | `dot_global_rank`, `cos_global_rank` | dot/cos 在全部验证对里的 rank01 归一 |
| 组内排序 (4) | `dot_author_rank`, `cos_author_rank`, `dot_author_pct`, `cos_author_pct` | 按 author groupby, 在该作者的所有候选 paper 内排序/百分位 |
为什么除直接交互还要组内排序? 因为一个高产作者的候选 paper 都打高分, 绝对 dot 没区分度, 但**组内排名**能区分"对这位作者而言, 这篇 paper 排第几"。这与全局 rank 互补——这就是本项目反复用的"score → features"套路(`score_to_features`: raw / zscore / rank / author_rank 四件套)在游走域的展开。
### 5.3 第 8 个"块": 一致性聚合 (11 维)
七块各 11 维共 77 维, 再加一个**跨块一致性聚合** 11 维 (`generate_randomwalk_ensemble_submission.py:50-69` `aggregate`):
| 来源 | 聚合特征 |
|---|---|
| 7 块的 cos 列 | `mean / std / max / min` (4 列) |
| 7 块的 dot 列 | `mean / std` (2 列) |
| 7 块的 `cos_author_pct` 列 | `mean / std / max / min` (4 列) |
| 7 块的 `cos_author_pct ≥ 0.5` | `agree` = 投票一致的块数 (1 列) |
`agree` 是亮点: 它数"有**几块**都认为这个对排在作者候选的前半", 是一种**多模型投票/集成置信度**。多块都同意 → 高置信正例。这把 ensemble 的"一致性"显式做成了特征喂给 LightGBM。
> 维度核对: 7×11 + 11 = 88 维 RW 特征族。最终 259 维特征里, RW 块占 77, aggregate 占 11(`fact_sheet` §2.5)。
---
## 6. RW ensemble size 曲线怎么解释: 多样性的边际收益
把"单块 → 5 块 → 7 块"的验证 F1 画出来, 就是 ensemble size 图(论文常用素材):
| 配置 | 维度 | 验证 F1 (1:1 OOF) | Δ vs 上一档 |
|---|---|---|---|
| 最优单块 `dw_d256_l80` | 84+11=95 | **0.963371** | — |
| 5 块 ensemble (`dw_base/long/highdim/graph_ap_pp/n2v_p2_q1`) | 84+5×11+11=150 | **0.963932** | +0.000561 |
| 7 块 ensemble (+`dw_d256_l80`+`dw_seed3407`) | 84+7×11+11=172 | **0.964921** | +0.000989 |
(来源: `ensemble_5_ablation.csv`、`ensemble_7_ablation.csv`, n_features 列与 F1 列; 单块 F1 由对应 `_oof.npy` + `best_f1` 重算。)
**怎么解释这条曲线?**
1. **从 1 → 5: +0.000561, 边际明显**。因为 5 块覆盖了不同 walk_length、不同图结构、不同方法(DeepWalk + Node2Vec), 嵌入子空间**正交性强**, 加进来立刻提供 LightGCN 看不到的新视角。
2. **从 5 → 7: +0.000989, 边际更大**。这里加的是 `dw_d256_l80`(256 维 + 80 长游走, 最强单块)和 `dw_seed3407`(纯种子扰动)。前者补"高维+长游走"视角, 后者补"同结构不同采样"的去噪视角。两块都是**与前 5 块差异最大**的配置, 故增益反超第 2-5 块。
3. **总链 +0.001550**。注意: 这与某些 figures 标题写的 "+0.00182" **不严格吻合**(据 CSV 重算 best-single→5→7 仅 +0.00155; 若以 `dw_base`=0.962734 为起点则 +0.00219)。`fact_sheet` 不一致台账 #21 已裁决: **以 CSV 重算 +0.00155 为准**, figures 数字偏高需更正。
**一般规律**: 多样性 ensemble 的增益**不随块数单调递增**, 而取决于"新加入块与已有块的**差异度**"。这就是为什么最终不是盲目堆满 16 个 `RWConfig`, 而是精选 7 个**相互差异最大**的配置。
---
## 7. RW 特征 vs 显式图 / meta-path 特征: 互补的两种"图结构"信号
二者都从图结构出发, 但哲学相反:
| | 显式图 / meta-path (`stack_rank_calibration.py`) | 随机游走 (本特征族) |
|---|---|---|
| 路径表示 | **枚举指定路径模式**(A-P-A、A-A-P、A-P-P), 精确计数 | **采样所有路径**, 按概率加权揉进 embedding |
| 可解释性 | 高(每列对应明确语义, 如"共读论文数") | 低(向量维度无明确语义) |
| 覆盖 | 只覆盖人为指定的几跳模式, 长/绕路径易漏 | 隐式覆盖任意长度路径 |
| 计算 | Python set 交/并集(`stack_rank_calibration.py` L120-142), 无稀疏矩阵 | Word2Vec 训练 + 向量运算 |
| 失败模式 | 漏掉未指定的有用 meta-path | 维度高、有训练随机性 |
它们**互补**: 显式特征给出"确定性、可解释"的强信号(如共读论文数=3), 游走特征补上"模糊、多跳、全局"的软信号。LightGBM 同时用两类, 互为校验。
---
## 8. 为什么 RW 特征能提升泛化
回到 stacking 的目标: 对 ~205 万测试对输出稳健的 0/1。RW 特征提升泛化的三条机理:
1. **任务无关先验, 抗过拟合**。游走 embedding 不看推荐标签, 是纯结构无监督表示。把它作为手工特征喂给 meta-learner, 等于注入一个**与 LightGCN 训练目标无关**的结构先验, 降低 stacker 对 LightGCN 过拟合风险的依赖。
2. **多块 + 一致性聚合 = 内置 bagging**。7 块不同配置(种子、长度、维度、图、方法)本质是"同一结构问题的 7 个噪声视角", 它们的 `agree`/`mean`/`std` 聚合**降噪并量化不确定性**——多块一致的对更可能真为正。这把集成思想做进了特征。
3. **挽救长尾与无直接边对**。第 1.1 节例子: 长尾作者(度=1)在 LightGCN 里表示弱, 但游走的 `min_count=0` 保证它有向量, 且多跳游走能让它通过合著/引用链获得有意义的接近度。这部分样本正是 baseline 最容易错的, 游走特征提供了**额外、正交**的判据。
证据链(验证 F1, seed=202 1:1 OOF): rich content + BPR 基线 0.95990 → 加 DeepWalk/Node2Vec(旧 stacker)≈0.9621 → 7 块系统 ensemble 0.964921 → 再叠加 rich content 成 rich_rw7 stacker 0.964947 → 最终 + 高阶引用传播达 **0.966874**(公开 LB **0.96626**)。游走族贡献了约 +0.005 的验证 F1, 是从中游(0.9599)迈向顶尖(0.9669)的关键一档。
> 数值归属提醒: `0.964921`(ensemble_7_ablation.csv, 纯 7 块 RW ensemble) 与 `0.964947`(rich_rw7, 叠加 rich content 的另一 stacker, `final_report.md` L125) **不是同一个模型**, 引用时需区分 stacker 层级(`fact_sheet` #22)。
---
## 9. 关键代码与缓存位置速查
| 内容 | 位置 |
|---|---|
| 16 个 `RWConfig` 定义(最终只用 7 个) | `code/randomwalk_systematic_ablation.py:87-115` |
| `RWConfig` dataclass | `code/randomwalk_systematic_ablation.py:73-84` |
| `build_graph`(按 graph_type 加边, A-P 权 3.0) | `code/randomwalk_systematic_ablation.py:118-136` |
| `deepwalk_walks`(无偏游走) | `code/randomwalk_systematic_ablation.py:139-157` |
| `train_model`(DeepWalk=Word2Vec sg=1/neg5/ep3; Node2Vec=batch_words=4096) | `code/randomwalk_systematic_ablation.py:160-191` |
| `embedding_arrays`(avec 6611×d, pvec 79937×d; pp_only_author_mean 回退) | `code/randomwalk_systematic_ablation.py:194-213` |
| `pair_feature_block`(11 维 + npz 缓存) | `code/randomwalk_systematic_ablation.py:216-270` |
| `build_base_features`(84 维 X_base) | `code/randomwalk_systematic_ablation.py:273-300` |
| `aggregate`(11 维一致性聚合) | `code/generate_randomwalk_ensemble_submission.py:50-69` |
| `make_subs`(rank-cutoff + test_known_mask 强制 1) | `code/generate_randomwalk_ensemble_submission.py:25-47` |
| 7 个 Word2Vec 模型 | `validation_runs/dynamic_seed202/randomwalk_systematic/models/*.model` |
| pair 特征缓存(键: `version_npairs_sumaid_sumpid`) | `validation_runs/dynamic_seed202/randomwalk_systematic/pair_features/*.npz` |
| ensemble 5/7 验证 CSV | `validation_runs/dynamic_seed202/randomwalk_systematic/ensemble_{5,7}_ablation.csv` |
> 注意: `small_ablation_table.csv` / `graph_ablation_table.csv` 实测各仅 2 行且含绝对路径遗留(`/data/lzc/...`), 已被部分覆盖/截断, **不能作为完整单块 F1 来源**; 单块 F1 以对应 `_oof.npy` + `best_f1` 重算为准(`fact_sheet` #23)。
---
## 可迁移到论文中的写法
以下为可直接进 TeX 的正式表述(中文论文, 术语保留英文)。
**方法段落(随机游走特征)**:
> 为补充 LightGCN 端到端协同过滤表示在多跳隐式通路与长尾节点上的不足, 我们在异构图上额外训练了 7 个 DeepWalk/Node2Vec 随机游走 embedding 作为手工特征。具体地, 对每个配置, 我们在融合图(author-paper 边权 3.0, 合著与引用边权 1.0)上生成截断随机游走序列, 并以 SkipGram(`sg=1`, `min_count=0`, `negative=5`, `epochs=3`)训练节点向量。其中 6 个为 DeepWalk(等价 Node2Vec $p=q=1$), 覆盖不同的嵌入维度 $d\in\{128,256\}$、游走长度 $L\in\{40,80\}$、随机种子与图结构(full / ap_pp); 1 个为 Node2Vec($p=2.0, q=1.0$)以引入与无偏游走正交的结构等价偏置。对每个候选 (author, paper) 对, 我们由双方节点向量构造 11 维 pair 特征, 包括点积、余弦相似度、Hadamard 积均值、绝对差均值、欧氏距离, 以及全局与作者组内的相似度排序特征。此外, 我们将 7 个配置的相似度与排序统计量跨块聚合为 11 维**一致性特征**(含均值/标准差/极值及多块投票一致计数 `agree`), 显式编码集成置信度。
**动机段落(为何互补)**:
> 与 LightGCN 通过 BPR 排序损失反传、直接为推荐任务优化的可学习表示不同, 随机游走 embedding 是与任务标签无关的无监督结构先验。其归纳偏置与 LightGCN 正交: 它通过概率加权的多跳游走隐式覆盖任意长度路径, 无需人为指定 meta-path, 从而能捕获那些"无直接边但图上相近"的候选对。两者共同作为 LightGBM 二级 stacker 的输入, 使 meta-learner 能在不同样本上自适应地选择更可信的信号源。
**实验段落(ensemble size)**:
> 我们考察了随机游走集成规模对验证 F1 的影响。最优单块(`dw_d256_l80`, $d{=}256, L{=}80$)取得 F1 = 0.9634; 加入覆盖不同游走长度、图结构与方法(Node2Vec)的 4 块后, 5 块 ensemble 提升至 0.9639(+0.0006); 进一步加入高维长游走块与种子扰动块后, 7 块 ensemble 达到 0.9649(较最优单块累计 +0.0016)。增益随新加入配置与已有配置的差异度增大, 而非单纯随块数递增, 表明**配置多样性**而非数量是收益的主要来源。
**消融表(可直接做表格)**:
| 模型 | 特征维度 | 验证 F1 |
|---|---|---|
| 最优单块 RW | 95 | 0.9634 |
| 5 块 RW ensemble | 150 | 0.9639 |
| 7 块 RW ensemble | 172 | 0.9649 |
> 引用注: 引用 7 块 ensemble 时需注明 F1=0.9649 来自 `ensemble_7_ablation.csv`(纯 RW ensemble); 报告中出现的 0.9649 属同一量级但属叠加 rich content 的 rich_rw7 stacker, 二者层级不同, 勿混用。
|