PR-SD 公式与算法实现讲解

配合论文初稿第 3 节(方法)与第 4 节(实验)阅读的逐式说明

对应稿件:docs/paper/author_workspace/draft/论文初稿_中文对照.md 与 论文初稿_英文.md(2026-10-12 版)
对应代码:pr_sd/runner.py → core/parallel.py,以及 noise/、baselines_ext/、scripts/analyze_*.py
编写日期:2026-10-12  ·  性质:作者学习与答辩准备材料,不是论文正文,不改动任何稿件或冻结结果

怎样读这份文档。每一段前面都有一个标签,说明这段话的依据:

论文论文初稿中写出的公式或定义,标出所在小节
实现从代码中核对到的事实,标出文件和行号
讲解本文档为帮助理解而补充的推导、算例或解读;不是论文结论,引用到正文前须作者确认并补证据
注意容易误读或表述时需要守住的边界

文中“小玩具图”“算例”中的数字都是为说明而构造的,只有明确注明来源路径的数字来自冻结结果或论文表格。

0. 全局图景与符号速查

论文 3.1PR-SD 分两个阶段(phase)。第一阶段在不完整图 上按 PageRank 得分选出 个传感器;第二阶段在一次传播发生后,用感染节点集合、已感染传感器的首次感染时间戳与信息发送者、经验跳数频率 和 给候选源打分,取最高分者作为估计源。

实验里还有两件事不属于 PR-SD 本身,但离不开它们:在完整图 上生成真实传播与传感器记录;在 上为每个传感器集合标定 。下图把四件事和它们用到的图分开画出。

完整图 G(只在实验中生成观测、标定 q_h、评价误差时使用) 不完整图 G_δ(PR-SD 两个阶段可用的拓扑) 真实传播 源 s ∉ 传感器并集,异质 SI 感染 ⌈0.1n⌉ 个节点即停 观测 感染集合 I_σ 已感染传感器的 (t_o, id_o) 标定 q_h 800 次模拟,感染 ⌈0.4n⌉ 即停 对每个传感器集合 O 各标定一次 第一阶段:部署 PageRank 得分降序 取前 m = ⌈ρn⌉ 个 无需传播模拟 第二阶段:源检测 补入发送者边;候选 = I_σ \ O 逐候选在 G_δ 副本上模拟一次 SI score = q_h / (1 + D_t) 估计源 ŝ 最高分,并列取小 ID 传感器集合 O q₁, q₂
图 0-1 各步骤使用的图。绿色区域的操作在完整图 上进行,蓝色区域是 PR-SD 两个阶段,只使用 (第二阶段另加记录到的发送者边)。依据:论文 3.1–3.3 与图 2;pr_sd/runner.py:843–906。

符号速查

符号含义取值或规则(主实验)
完整无向传播图;“完整”指未做实验删边七个真实网络,见论文表 1
节点数Football 115 … LastFM 7,624
可获得的结构不完整图
删边率主比较 0.20;4.5 节 0.10–0.40
传感器比例与传感器数量,
传感器集合;PageRank 前 名
节点 的转发概率,每个 组合内固定
目标传播规模观测 10%、 标定 40%、MC 部署 30%
停止时曾被感染的累计节点集合
已感染传感器
传感器 的首次感染时间戳与信息发送者
最早感染的传感器及其记录的发送者时间戳并列取最小节点 ID
PageRank 向量、行随机转移矩阵、阻尼因子
已感染传感器的最小非零传播跳数等于 的模拟占比800 次标定,评分只用
观测与模拟传感器时间向量的欧氏距离未感染位置记
真实源、估计源
每个条件纳入比较的试验数部署对照 300;完整方法比较取前 100 次
MC 贪心每轮的模拟次数800

术语与符号以 writing_memos/03_术语符号与规范表述库.md 为准。该术语表 5.4 节把时间距离写作 ,当前双语稿已统一写作 ,本文档跟随双语稿。

1. 图、传播与观测(论文 3.1)

1.1 完整图与不完整图

论文 3.1可获得的结构不完整图为

逐项含义。 保留全部 个节点,只少了边。 是向下取整,所以删去的边数是整数 。删边以后可能出现零度节点(孤立点),它们仍在 中,这正是算法 1 要为零度节点单独规定转移概率的原因。

讲解算例:Jazz 有 条边, 时 ,删去 548 条,剩 2,194 条。冻结结果 results/active/pr_sd_protocol_v4/v4_exp02_supporting_structural_controls/results/jazz__pdnd__delta0p20__seed111__rho0p05__pr_sd/result.json 中 incomplete_edge_count 恰为 2194。

两种删边策略

论文 4.1RDS 对边均匀抽样;PDND 以完整图中的端点度和为抽样权重:

两者都无放回,正好删去 条。

实现noise/edge_removal.py:14(RDS)与 :43(PDND)。删边数用 int(noise_rate * |E|),对正数等价于向下取整。抽样用 np.random.choice(..., replace=False),PDND 额外传入按 归一化的概率 p。权重用的是 M.degree,即删边前完整图的度,与论文“删边前完整图中的端点度和”一致。随机数种子在 pr_sd/runner.py:320–321 由图随机种子设定。

讲解为什么论文说 RDS 移除任一节点度数的比例约为 。RDS 下每条边被删的概率都是 ,其中 。节点 有 条关联边,被删条数的期望为

与 的度大小无关。PDND 则让关联高度节点的边权重更大,于是高度节点被删得更多(论文 4.5 节实测为 RDS 的 1.22–1.55 倍)。

讲解np.random.choice 带权无放回抽样是逐次抽取、每次在剩余边上重新归一化的过程,因此某条边最终被删的概率并不严格正比于 ;“以 为抽样权重”的说法准确,但不宜写成“删除概率正比于 ”。

1.2 传感器数量与预算

论文 3.1, 为向上取整。

网络
Football115
Jazz198
LastFM7,624

讲解这些数可以和正文对上:4.4 节“5% 预算下六个传感器”(Football)、图 4“每种方法部署 10 个传感器”(Jazz, )。

实现pr_sd/runner.py:1207–1211:math.ceil(node_count * ratio),并取三个预算中的最大值 sensor_count_max 只部署一次(见 2.5 节前缀)。

1.3 异质 SI 传播

论文 3.1、4.1易感—感染(SI)模型中节点感染后保持感染。感染节点 以其转发概率 向邻居传播; 独立抽自 ,在每个 组合内跨方法、预算和试验固定。

实现真实传播在 core/parallel.py:285–323。离散时间的规则可以写成:

一次异质 SI 传播(代码语义)
  1. 源 在时刻 0 被感染;活跃列表 ,计数 1。
  2. for 步 do
  3. 按 的顺序取每个感染节点 ,对每个邻居 :若 未感染且均匀随机数 ,则感染 ,记 ,发送者 ,跳数 ,并放入新感染列表。
  4. 一旦累计感染数达到 ,立即跳出全部循环。
  5. 本步结束后把新感染节点并入 (SI:老节点保留,下一步继续尝试)。
  6. end for
节点 在第 步被感染后,要到下一步才开始传播;同一步内新感染节点不会再传染别人。

三个记录量的区别。 是时间步; 是使 首次感染的那个邻居; 是 在本次传播树中的深度(从源数起经过几条感染边)。,因为传播树里的路径不一定是最短路。

讲解一条边上的传播延迟服从几何分布。设 在时刻 被感染,邻居 只有 一个感染邻居。 从下一步起每步独立地以 成功,所以

若 有多个感染邻居, 取各条边“先到者”。 越小,延迟越长、越分散。这有助于理解两件事:其一,时间戳携带了到源距离的信息,但带有较大随机性;其二,MLE-P、DISGE 假设所有边的延迟服从同一均值和方差(6.3 节),与“每个节点 不同”的传播设置不完全吻合。后一点只是理解模型假设差异的角度,论文没有据此作结论,也没有做检验。

IC 与 SIR(论文 4.7)

实现core/parallel.py:42–61 的 _advance_frontier 决定下一步谁仍在传播:

注意在 IC、SIR 实验中,真实传播、 标定与 MC 部署模拟换成对应模型,但第二阶段的逐候选模拟始终是 SI(core/parallel.py:389, 431 直接 q_cur.extend(q_nxt))。论文 4.7 节已写明“逐候选源模拟保持 SI”。

1.4 停止规则与感染集合

论文 3.1传播在累计感染节点数达到 或达到时间步上限时停止, 为截至停止时曾被感染的累计节点集合。

实现core/config.py:46–59:

return min(node_count, max(1, math.ceil(infection_ratio * node_count)))

代码注释说明:原作者代码的条件是“感染数 比例 ”,由于感染数是整数,二者等价于向上取整。Football 在 时目标为 个节点;LastFM 为 个。

讲解因为达到目标就在步内立即停止,最后一步只有一部分节点有机会尝试传播。停止时 ,除非先撞到 10,000 步上限(例如源所在连通分量太小)。

1.5 传感器记录、最早传感器与输出

论文 3.1已感染传感器为 。每个 记录首次感染时间戳 与发送者 。最早感染的传感器记为 ,时间戳并列时选择整数节点标识最小者。 时返回 ; 时不产生源估计。

实现core/parallel.py:331–339 按升序遍历传感器 ID,只在 infect_time < min_time(严格小于)时更新,所以同一时刻最先遇到的就是最小 ID。

讲解两个由定义直接推出的事实,后面会反复用到。

(a) 一定是已感染的非传感器节点。理由:真实源不是传感器,所以 ,它有发送者;发送者的感染时间严格早于 ;如果发送者也是传感器,它就比“最早感染的传感器”更早,矛盾。

(b) 当且仅当 。深度为 1 就是被源直接感染,发送者即源;反之亦然。所以论文表 4 的“一跳率”恰好等于“真实源就是最早感染传感器所记录的发送者”的试验比例。4.5 节会看到,这个候选在评分中拿到更大的权重 。

2. 第一阶段:PageRank 引导的传感器部署(论文 3.2)

2.1 PageRank 方程

论文 3.2令 为 PageRank 向量, 为 上的行随机转移矩阵, 为阻尼因子, 为全 1 列向量。零度节点对应行设为均匀分布。

转移矩阵 是什么

设 为 的邻接矩阵, 为节点 在 中的度。则

每一行加起来都是 1,所以叫“行随机”。第 行描述随机游走者站在 时下一步走到各节点的概率:有邻居就等概率挑一个邻居;没有邻居就等概率跳到任意节点。

按分量写开

第一项:每个邻居 把自己的得分平均分给 个邻居, 拿其中一份。邻居越重要、邻居的邻居越少, 分到越多。第二项:孤立节点的得分均匀撒给所有节点。第三项:均匀重启,保证每个节点至少有 。论文所说“将邻居得分按其度数归一化后递归传递”就是第一项。

讲解为什么 会自动保持。对上式两边求和,由 行和为 1 得 ,于是

从均匀向量 出发,每次迭代后仍是概率分布,所以算法 1 不需要再归一化。

解存在且唯一

讲解利用 ,可以把方程写成 ,其中 每个元素都为正。由 Perron–Frobenius 定理,正的随机矩阵有唯一的平稳分布,因此 PageRank 向量存在且唯一。等价地, 可逆,有闭式解

2.2 PageRank 的三种读法

读法一:带重启的随机游走。论文 3.2 是这样一个游走者的长期停留比例:每步以 沿边走,以 跳回一个均匀随机节点。

读法二:不同长度游走的加权和。讲解上面的级数里, 是“从均匀随机节点出发走 步后所在位置”的分布,权重 是几何分布。所以 PageRank 是“随机长度游走的终点分布”,平均游走长度为

这就是论文所说 PageRank“能够利用直接邻居之外的连接关系”的具体含义: 时平均看大约 5–6 步远的结构。

读法三:两个极端。讲解

论文 4.8 节引用 Gleich 的文献(引用键 gleich2015pagerank)写“ 时 PageRank 趋向与度成比例的平稳分布”,并实测 PR-SD 与 Degree-SD 传感器集合的平均 Jaccard 相似度从 的 0.507 升到 的 0.673,与读法三的方向一致。

注意这三种读法解释的是 PageRank 排的是什么,不能推出 PageRank 选点对源检测最优;论文也没有这样写。实验只说明在所测条件下的准确率与部署时间。

2.3 算法 1 逐行对照

算法 1 PR-SD 的传感器部署部分(论文 3.2)。输入:,,,,;输出:。
  1. ;
  2. 由 构造行随机矩阵 ;零度节点对应行设为
  3. for do
  4. if then ;break
  5. if 未收敛 then 抛出收敛错误
  6. 按 降序、节点 ID 升序排列; 前 个
为节省篇幅,原算法 1 的第 6–9 行与第 10–14 行在此合并显示,步骤不变。

实现PR-SD 调用 nx.pagerank(graph, alpha=0.85)(pr_sd/runner.py:337–342)。本机 exp_env 中 NetworkX 为 3.2.1,实际执行的是 _pagerank_scipy,核心循环为:

A = Q @ A                                  # Q = diag(1/d_i),A 变为行随机(零度行仍为 0)
x = np.repeat(1.0 / N, N)                  # 算法 1 第 3 行
p = np.repeat(1.0 / N, N)                  # 均匀重启向量
dangling_weights = p                       # 零度节点的质量也均匀分配
for _ in range(max_iter):                  # max_iter = 100 → T_max
    xlast = x
    x = alpha * (x @ A + sum(x[is_dangling]) * dangling_weights) + (1 - alpha) * p
    err = np.absolute(x - xlast).sum()     # L1 范数
    if err < N * tol:                      # tol = 1e-6 → ε
        return dict(zip(nodelist, map(float, x)))
raise nx.PowerIterationFailedConvergence(max_iter)

来源:D:\Anaconda\envs\exp_env 中 networkx/algorithms/link_analysis/pagerank_alg.py 的 _pagerank_scipy(本次用 inspect.getsource 读出)。

算法 1NetworkX 3.2.1说明
第 2 行 Q @ A 加 sum(x[is_dangling]) * dangling_weights代码不显式填满零度行,而是在每次迭代时把零度节点的质量均匀加回,数学上与“零度行设为均匀”相同
第 5 行 x @ A行向量右乘行随机矩阵 列向量左乘其转置
第 6 行 N * tol,tol=1e-6等价于“平均每个节点的变化量小于 ”
max_iter=100默认值,项目未改
第 12 行排序sorted(scores, key=lambda v: (-scores[v], v))在 runner.py:341,不是 NetworkX 的一部分

实现sensor/strategies.py:52 也有一个 deploy_pagerank,逻辑相同,但正式实验走的是 pr_sd/runner.py 的 _pagerank_sequence。

2.4 收敛与迭代次数

讲解设 是精确解,。两式相减,重启项抵消:

每列和为 1、元素非负,对任意向量 有 。所以每迭代一次误差至少缩小到 倍。相邻两次迭代之差同理:

(两个概率向量之差的 范数不超过 2。)令 ,得到一个保守的迭代次数上界

情形上界()上界()
Football,约 61 次约 191 次
LastFM,约 35 次约 109 次

这只是最坏情况上界,真实迭代通常少得多。它说明了两点: 时 100 次上限很宽裕; 越接近 1 收敛越慢,这是 的计算代价。按代码逻辑,若某次运行 100 次仍未收敛会抛出异常并中止,论文 4.8 节已报告 的结果,说明那些运行没有触发该异常。

2.5 排序、并列与前缀嵌套

论文 3.2节点按 降序排列,分数完全相同时按整数节点标识升序;。固定 、参数与并列规则后,。

嵌套性的证明只有一句:两个集合都是同一个排序的前缀,短前缀必然包含在长前缀里。

实现

注意前缀嵌套不是 PR-SD 独有:MC 贪心的选点顺序同样可以截取前缀(论文 4.2 节 SDSA 就是“按选点顺序截取传感器”)。复用排序只省掉重复排名;不同传感器集合仍要各自标定 、各自做源检测试验。

2.6 小玩具图算例

讲解七个节点:两个三角形 、,由路径 连起来。下表是用 nx.pagerank(G, alpha=0.85) 实际算出的数值(构造示例,非实验数据)。

0 1 2 3 4 5 6
图 2-1 玩具图。深蓝为 PageRank 前两名,浅蓝 1 号为并列中 ID 最小者,橙色 3 号是介数最高的桥节点。
节点度PageRank()介数(归一化)
0, 431.5000.18280.533
1, 2, 5, 620.8330.12730
320.6670.12500.600

2.7 计算量:PageRank 与 MC 贪心

讲解(推导与 writing_memos/01_方法与实现核对.md 第 6 节一致)

网络(15% 预算)MC 贪心的传播模拟次数每次感染节点数
Football1814,40035
LastFM1,144915,2002,288

论文表 2 中 SDSA 在 LastFM 上部署需 1,480 秒,PR-SD 为 0.0401 秒;数量级差异主要来自上表的模拟次数随 线性增长,而 PageRank 只做一次。

注意“无需传播模拟”只描述第一阶段。第二阶段的逐候选评分、实验中的 标定都要做传播模拟(论文表 7 中这两部分耗时与 SDSA 接近)。部署时间倍率不能写成端到端加速。

3. 对照:蒙特卡洛贪心部署(SDSA-MC)

这一节帮助理解论文中被比较的 SDSA 部署,以及为什么它慢。

实现core/parallel.py:484–649(部署对照用)与 baselines_ext/sdsa_author.py:82–123(完整方法比较中调用作者代码)。两者做的是同一件事。第 轮(已选 ):

其中第 次模拟的源从 均匀抽取,在 上以 传播到 30% 规模, 是 在该次传播树中的深度。代码参数 hops=1,所以 数的是“ 被源直接感染”的次数; 是这一事件概率的蒙特卡洛估计。并列时 np.argmax 取最小 ID。

实现一个容易忽略的细节:计数本身不扣除“已被已选传感器覆盖”的模拟;各轮之间的差别只来自两处,一是已选传感器不再作为源被抽到,二是每轮用新的随机流(core/parallel.py:564–606,作者代码 observer 也是如此)。

SDSA 原文在 Sec. IV-A 用 、 等符号表述期望检测概率目标;按术语表 4.3 节的规定,这些原符号只在介绍 SDSA 来源时使用,不移植到 PR-SD。

4. 第二阶段:源检测(论文 3.3)

4.1 贝叶斯公式与最大后验估计

论文 3.3令 。对候选 :

为什么可以去掉分母。分母 是对所有候选求和的结果,对每个候选都一样;比较大小时乘以同一个正数不改变谁最大。

两项分别是什么。先验 :还没看到这次传播的记录时, 有多大可能是源。似然 :如果源真是 ,看到眼前这组感染集合和传感器记录的可能性有多大。

讲解PR-SD 的评分怎样对应这两项。论文写的是“借鉴这一估计思路,采用经验评分规则”。对照关系是:

MAP 中的项PR-SD 评分中的对应区别
候选限定在 相同—
先验 跳数权重 或 只分两档: 取 ,其他候选取 ; 可小于 1,不是归一化概率
似然 时间匹配分 用一次模拟的时间向量距离代替似然,不是概率密度

所以只能说“借鉴 MAP 思路的经验评分”,不能说 PR-SD 计算了后验概率。

4.2 经验跳数频率

论文 3.3为每个传感器集合在完整图 上离线标定 。源从 均匀抽取,按指定传播规模和停止规则独立模拟。传播跳数指传感器在模拟传播树中的深度。 表示已感染传感器的最小非零传播跳数等于 的模拟占比;没有传感器被感染的模拟仍保留在分母中,因此 可能小于 1。

写成公式(本文档的展开写法,论文用文字定义):

若第 次模拟没有感染任何传感器,括号里的最小值不存在,该次不计入任何 的分子,但仍在分母 中。

实现core/parallel.py:191–250(_worker_hops_test_batch)与 :709–763:

best_hop = N + 999
for obs_id in obs_indices:                 # 所有传感器
    hop = total_hop_arr[obs_id]
    if hop != 0 and hop < best_hop:        # 只看已感染者(深度非零)
        best_hop = hop
if best_hop < N:
    detect_hops[best_hop] += 1.0
...
detect_hops /= nr                          # 分母为全部 800 次

调用处 pr_sd/runner.py:872–882 传入的是 complete_graph,确认标定在 上进行。

实现一个真实例子。上文 1.1 节那个 Jazz 结果(PDND,,,10 个传感器)记录的 hop_prior_distribution 前几项为

这里 ,说明 800 次 40% 规模的模拟每次都至少感染了一个传感器。网络大、预算小、 小时和会小于 1。

注意三个“跳数量”不要混:(1) 中的 是标定模拟中所有已感染传感器的最小传播树深度,不一定是时间最早那个传感器的深度;(2)表 4 的 是真实传播中最早感染传感器的传播树深度;(3) 是完整图上真实源与估计源之间的最短路跳数。三者在代码中分别是 detect_hops、first_infected_sensor_hops、hop_error。

4.3 检测流程逐步对照

PR-SD 第二阶段(论文 3.3 文字描述整理为步骤) 代码:core/parallel.py:325–444
  1. 收集 。若为空:不产生估计,本次试验记为错误。(:331–357)
  2. 复制一份 ,对每个 加入边 。(:326, :337–339)
  3. 确定 与 ;观测向量 按传感器 ID 升序排列。(:359–361)
  4. 候选 :在补边后的图上从它出发模拟一次 SI,得到 ,评分用 。(:364–397)
  5. for 每个其他候选 ,按 ID 升序 do (:400–442)
  6. 若 与 之间无边,临时加入 ;
  7. 从 模拟一次 SI(同样的 、、10,000 步上限),得到 ,评分用 ;
  8. 移除临时边。
  9. 得分最高者;并列取最小 ID。(:444,np.argmax 返回第一个最大值)

讲解补边与临时边各起什么作用。

4.4 时间戳距离与评分公式

论文 3.3按相同传感器顺序,观测向量为 ,候选 的模拟向量为 ,未在模拟中感染的位置记为 。

实现代码写的是另一种形式,二者恒等:

prob[suspect] = prior1 * (1.0 - euc_dis / (euc_dis + 1.0))     # core/parallel.py:397
prob[cand]    = prior2 * (1.0 - euc_dis / (euc_dis + 1.0))     # core/parallel.py:439

论文采用右边更简洁的写法。prior1、prior2 就是 hop_prior[1]、hop_prior[2],即 。

约定的效果。讲解某传感器在观测中于时刻 被感染、在候选模拟中却没被感染时,该分量的差是 ,平方后为 。观测时间越晚,惩罚越重。由于模拟和观测都在 处停止,“模拟中没感染到”通常意味着候选离该传感器太远,或在 上到不了它。

4.5 评分规则的性质

讲解以下都由公式直接推出,便于回答审稿问题。

  1. 取值范围。,所以 ;(时间完全吻合)时取 1,距离增大单调下降。于是 ,其他候选 ( 为 0 时得分为 0)。
  2. 其他候选战胜 的条件。
    当 时,其他候选的时间吻合程度要比 好出 倍以上才能胜出。
  3. 必然胜出的充分条件。其他候选得分最高为 ,所以只要
    就一定是估计源。用上面 Jazz 例子,,即 时其他候选无论如何都赢不了。
  4. 与“一跳率”的关系。结合 1.5 节事实(b):真实源被 加权当且仅当 。一跳率高,意味着真实源更常落在被加权的那个候选上。Jazz 例子中该条件的一跳率为 0.798,可观测条件下的准确率为 0.760,二者接近。这为论文表 4 中“一跳率与准确率同为 PR-SD 最优”提供了一个机制层面的理解;论文没有把它写成因果结论,正文若要使用需另行分析。
  5. 随机性。每个候选只模拟一次,所以评分本身是随机的;同一候选重算可能得分不同。代码中候选模拟与真实传播共用同一个试验随机流(rng = np.random.RandomState(seed),core/parallel.py:283),因此给定试验种子可完全重放。

4.6 完整算例

讲解构造的数字,只为演示计算。设 (已按 ID 升序),观测时间 ;,(借用 Jazz 实例的值)。

候选模拟时间 差的平方权重得分
(2, 3, 5)1 + 1 + 11.7320.7788 / 2.732 = 0.2851
(3, 5, 6)0 + 1 + 01.0000.2025 / 2.000 = 0.1013
(2, 4, −1)1 + 0 + 497.0710.2025 / 8.071 = 0.0251

。注意 的时间吻合比 更好,但权重相差 3.85 倍,仍然落后;而 的 ,满足 4.5 节第 3 条,结果在算 之前就已确定。 的第三个传感器在模拟中未感染,单这一项就贡献了 49。

5. 实验中的评价公式(论文第 4 节)

5.1 源检测准确率 Acc

论文 4.1

未检测到传播或没有返回源估计的试验均计为错误。

实现core/parallel.py:830:accuracy = correct / max(1, attempted),分母是全部尝试的试验,与公式一致。结果文件字段为 metrics.acc。

讲解由定义可分解为

Jazz 例子:,与 acc 一致。observable_acc 只在结果文件里,论文没有报告。这个分解说明部署可以从两条路影响准确率:让传播更常被看到(DetRate),或让看到时更容易判对。

5.2 传播检测率 DetRate

论文 4.3.

实现runner.py:971:observed_trials / attempted_trials,字段 metrics.det_rate。

5.3 首个感染传感器的传播跳数、一跳率与估计误差

论文 4.3平均 、一跳率( 的比例)、平均 都只在至少一个传感器被感染的试验上计算。

实现runner.py:817–840(_detection_distance_metrics)。 取真实传播树中 的深度(parallel.py:472); 用 nx.shortest_path 在完整图上计算(:453–459),二者不连通时记为 None 并不计入均值。

讲解由于源不是传感器,,所以“”就是“”。由 1.5 节事实(b),一跳率也就是“真实源 ”的比例。

5.4 真实源抽样范围

论文 4.3。

实现runner.py:1245–1269:先求所有比较方法传感器集合的并集,再用 RandomState(source_seed).choice(source_pool, size=300, replace=True) 有放回抽 300 个真实源,所有方法共用这一序列。

讲解为什么要排除并集:如果真实源恰好是某方法的传感器,该方法就无法在 中找到它,比较就不公平。代价是预算越大、方法越多,可抽源范围越小,且被排除的往往是高中心性节点(论文 4.6 节:10% 起度排名前十分位节点全部被排除)。所以准确率只在同一张表内可比。

论文 4.2完整方法比较中,再剔除源属于 SDSA 或 GRSL 自身传感器集合的试验: 次中保留 次,比例 。

5.5 汇总方式:先组合内平均,再组合间等权

论文 4.2、4.3记 为预算集合, 为完整图—不完整图组合集合:

每个组合权重相同,与网络大小无关。论文的“总体 62.40%”就是对 54 个组合这样算的。(上式是本文档对论文文字的形式化。)

5.6 部署时间的几何均值与倍率

论文 4.1部署耗时及耗时比采用图组合等权的几何均值汇总。

实现scripts/analyze_ext_e1.py:146–148:np.exp(np.mean(np.log(values)))。

讲解为什么用几何均值:各网络耗时跨越多个数量级(0.0009 秒到 1,480 秒),算术均值会被最大的网络主导;几何均值等价于在对数尺度上取平均。它还有一个好性质:在同一组组合上,

所以“几何均值之比”就是“逐组合倍率的几何均值”。核对:表 2 中 ,与正文一致;,正文写 27.5,差异来自用未四舍五入的值计算。

5.7 Wilcoxon 符号秩检验(论文 4.5,表 6)

论文 4.5双侧配对 Wilcoxon 检验在各策略 27 个匹配组合上、对三个预算取平均后进行, 值未经多重比较校正。

讲解对每个组合 求差 ,去掉 的组合,把其余 从小到大排秩 ,统计量为

若两方法无差异,正负差应大致对称, 接近其期望 ( 为非零差个数);偏离越大 越小。它只用差的符号和相对大小,不要求差服从正态分布,适合 27 个组合这样的小样本。

实现scripts/analyze_pr_sd_deletion_strategy_split.py:121:scipy.stats.wilcoxon(ours, theirs, zero_method="wilcox"),默认双侧。

注意相对 Betweenness-SD 的 、 只说明“未检测到统计显著差异”,不能写成两者相同或等价。

5.8 Spearman 相关、Jaccard 相似度与社区覆盖率

论文 4.4、4.8

5.9 累计运行时间(论文 4.9,表 7)

论文 4.9累计时间对 170 个条件的部署、 估计与“传播生成与源推断”记录求和。

讲解表 7 有两个不同的倍率:部署一项是 ;合计是 。合计倍率小得多,是因为 标定与源检测两部分两者几乎相同(倍率 1.02)。对应字段在 runner.py:988–995 的 runtime_seconds。

6. 完整方法比较中的基线公式(论文 4.2)

这些方法的原始公式以各自论文为准。下面描述的是本项目代码实际计算的内容,便于理解表 2 的差异。凡涉及原文式号之处,引用前应回到原始 PDF 核对(TODO-VERIFY)。

6.1 SDSA(作者代码)

实现baselines_ext/sdsa_author.py:把作者脚本 SDSA-main/main_source_detection_RDS_code_in_paper.py 中的 SI、observer、hops_test、find_source 原样执行,只替换输入的图、转发概率与传播。

6.2 GRSL:贪心部署(GS)与推断策略(IS)

实现GS:experiments/generate_gs_sensors.py 调用作者 Java 代码 SI.initNet_SIR_Greedy_lowComplexity(固定提交 a7f6a0f)在 上选点。该例程每轮加入一串传感器,可能超出预算,超出部分用由条件名派生的种子随机去掉,使数量等于 。Java 使用 Math.random() 无法设种子,所以每个条件只存一次结果。

IS:baselines_ext/grsl.py,主比较用 variant="code",不补发送者边,候选为全部非传感器节点(与 Agent.md 第 04 节“GRSL 主配置不补发送者边、不限定感染节点候选”一致)。计算步骤:

  1. 取最早感染的 4 个传感器 (并列按 ID)。
  2. 时间平移:若这 4 个时间的最小值 ,则 ; 则 ; 则 ;否则不变。
  3. 对每个节点 与 :(到不了记为 ),以及
  4. 得分(越小越好):
  5. , 为未被感染的传感器;若 是某个相邻已用传感器记录的发送者,再乘 0.9 或 0.95。

讲解直观含义:第一项偏好离这些传感器的发送者都近的节点;第二项 在“时间约等于距离”(每步传一跳)时取最小值 1,用来惩罚时间与距离不匹配; 惩罚邻居中有未被感染传感器的节点(如果源在那里,这些传感器本应被感染)。它不做传播模拟,所以推断很快。

6.3 MLE-P(Pinto 等的高斯估计)

实现baselines_ext/mle_p.py。为避免与本文的传播规模 冲突,下面把边延迟的均值、方差写成 (Pinto 原文记号为 )。

设定。已感染传感器按到达时间排序为 (并列按 ID),以 为参照。对候选 ,取 (补入发送者边)上以 为根的 BFS 树 :

是树中两点间路径, 为边数。

讲解估计式从哪里来。假设每条边延迟独立、均值 、方差 ,到达时间是路径上延迟之和,近似为高斯,。对数似然为

展开平方项:。把 视作与候选无关时,第一项和常数项可以丢掉,剩下 ,就是上面的估计式。代码对每个候选按其 BFS 树计算 ,但仍使用该估计式。

讲解协方差为什么是共享路径长度。 与 相关,是因为二者从 出发走过了公共的一段边,公共边的延迟同时出现在两者中。树上的公共长度可以由距离算出:

代码 _score_candidate(第 119–139 行)正是这样算的:先由最近公共祖先深度得树上距离,再取 0.5*(first[:,None]+first[None,:]-tree_dist)。

实现其余细节:

6.4 DISGE(方向诱导树)

实现baselines_ext/mle_p.py:142–155。与 MLE-P 用同样的高斯评分,只是把 BFS 树换成“方向诱导”的有向搜索:进入一个已感染传感器 的边,只保留来自其记录发送者 的那条;其他边双向可走。于是从候选出发的搜索树中,每个已感染传感器都必须经由它真实的发送者到达。若某候选在这一约束下到不了全部已感染传感器,就不对它评分(主配置 disge 不回退到普通 BFS)。

讲解方向信息让树的形状更贴近真实传播路径,从而 更接近真实传播距离。论文表 2 中,DISGE 两种部署配置的总体平均准确率在三个预算下均高于对应的 MLE-P 配置;论文没有把这一差异归因于方向信息,这里的解释只是一种理解角度。

7. 随机种子与可复现性

实现pr_sd/runner.py:75–78, 123–125 与 core/parallel.py:78–89。

用途种子说明
与删边图随机种子 random.seed(g)、np.random.seed(g) 后依次抽 和删边
预算种子使不同预算的随机流不同
MC 部署每轮再按轮次派生
真实源序列所有方法共用
标定
源检测试验每次试验一个子种子

逻辑分片。800 次模拟被固定分成 16 个“逻辑分片”,每片的种子由 np.random.SeedSequence([基础种子, 命名空间, 轮次]) 生成(命名空间 1=MC 部署,2=,3=试验)。分片数固定为 16,与机器实际进程数无关,所以换一台核数不同的电脑结果仍一样;MC 部署中断后也能从任意轮精确续跑。

8. 公式—代码对照总表

论文位置公式或步骤代码位置结果文件字段
3.1noise/edge_removal.py:14, 43parameters.incomplete_edge_count
3.1pr_sd/runner.py:1207parameters.sensor_count
3.1、4.1core/graph_loader.py:40–49图状态 node_transmission_probabilities
3.1停止 core/config.py:46—
3.1SI 传播、core/parallel.py:285–323—
3.2PageRank、算法 1runner.py:337–342;NetworkX 3.2.1parameters.pagerank_alpha
3.2前缀 runner.py:638–646sensor_nodes
3.3core/parallel.py:191–250, 709–763hop_prior_distribution(前 10 项)
3.3候选、补边、、scorecore/parallel.py:325–444trial_records[].estimated_source
4.1Acccore/parallel.py:830metrics.acc
4.3DetRaterunner.py:971metrics.det_rate
4.3平均 、一跳率runner.py:817–840metrics.mean_detect_dist、one_hop_rate
4.3平均 core/parallel.py:452–461metrics.mean_hop_error
4.3runner.py:1245–1269shared_inputs/*.json
4.1、4.2几何均值scripts/analyze_ext_e1.py:146—
4.5Wilcoxonscripts/analyze_pr_sd_deletion_strategy_split.py:121—
4.4社区覆盖、Spearmanscripts/analyze_pr_sd_community_coverage.py—
4.9累计时间runner.py:988–995runtime_seconds

9. 核对结论与需作者留意之处

9.1 本次逐项核对后一致的内容

9.2 不是错误,但值得作者知道的细节

  1. PDND 的带权无放回抽样是逐次重新归一化,单条边最终被删的概率并不严格正比于权重(1.1 节)。论文写“以……为固定抽样权重”是准确的。
  2. 术语表 03_术语符号与规范表述库.md 5.4 节仍把时间距离写作 ,双语稿已改为 。是否同步术语表由作者决定。
  3. 01_方法与实现核对.md 第 6 节写“无观测的 hop error 在实现中赋值为 ”,这是旧 schema;v3 及以后为 None(core/parallel.py:349–352),与 Agent.md 第 04 节一致。
  4. 4.5 节第 3、4 条( 必胜条件、一跳率与 加权的关系)是由公式推出的解读,论文中没有;如要写入讨论,需先由作者确认并用结果数据检验。

10. 自测问题

以下问题用于检验自己能否不看稿件讲清公式。请先自己写答案,再回到对应小节核对。

  1. Jazz 网络在 、 时, 和 各是多少?(1.1、1.2 节)
  2. 为什么 PageRank 迭代不需要每步重新归一化?用一行式子说明。(2.1 节)
  3. 从 0.85 调到 0.5,PageRank 平均“看”多远的结构?这和论文 4.8 节 Jaccard 结果的变化方向是否一致?(2.2 节)
  4. 审稿人问:“PR-SD 声称无需传播模拟,但表 7 中 PR-SD 也有 3,280 秒的检测耗时,怎么解释?”(2.7 节、5.9 节)
  5. 的三个关键词:在哪个图上标定?“最小”是对什么取最小?为什么和可以小于 1?(4.2 节)
  6. 为什么其他候选要临时连到 ,而 自己不需要?(4.3 节)
  7. 若 , 的 小于多少时,结果一定是它?(4.5 节)
  8. 为什么表 4 的一跳率等于“真实源恰为最早感染传感器记录的发送者”的比例?(1.5 节、5.3 节)
  9. 为什么部署时间用几何均值而不用算术均值?(5.6 节)
  10. Betweenness-SD 对比的 应怎样表述?(5.7 节)

文档源文件:formula_guide/src/guide_source.html;生成脚本:formula_guide/src/build_guide.mjs(KaTeX 预渲染并内嵌字体,再用本机 Edge 导出 PDF)。本文档不属于论文正文,不随稿件自动更新。