# 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, 二者层级不同, 勿混用。