好问题!让我详细解释 WL 迭代的计算机制和图不变性的实现原理。
已查看文件 face_graph_builder.cs
非常好的问题!让我详细解释 WL 迭代的计算机制和图不变性原理:
🔍 WL 迭代的核心机制
❌ 没有"最后节点"的概念
WL 算法的关键特点是:所有节点同时参与计算,没有先后顺序之分。
看你的代码实现:
csharp
// wl_graph_kernel.cs 第 43-72 行
for (int iter = 1; iter <= iterations; iter++)
{
// 为每个节点生成新标签
var newLabels = new Dictionary<int, string>();
foreach (var node in graph.Nodes)
{
// 收集邻居标签并排序
var neighborLabels = node.NeighborIds
.Select(neighborId => graph.Nodes[neighborId].CurrentLabel)
.OrderBy(label => label) // ← 关键:排序保证不变性
.ToList();
// 构造新标签:当前标签 + 排序后的邻居标签集合
string combinedLabel = CombineLabels(node.CurrentLabel, neighborLabels);
newLabels[node.Id] = combinedLabel;
}
// 统一更新所有节点的标签
foreach (var node in graph.Nodes)
{
node.CurrentLabel = newLabels[node.Id];
}
}
🎯 迭代过程示意
假设有一个简单的零件图:
迭代 0(初始):
节点 1: "圆柱面" ------ 邻居:[节点 2, 节点 3]
节点 2: "平面" ------ 邻居:[节点 1, 节点 3]
节点 3: "平面" ------ 邻居:[节点 1, 节点 2]
迭代 1:
节点 1: Hash("圆柱面_ (平面,平面)")
节点 2: Hash("平面_ (圆柱面,平面)")
节点 3: Hash("平面_ (圆柱面,平面)")
注意:节点 2 和节点 3 标签相同(结构对称)
🛡️ 图不变性的实现原理
1️⃣ 排列不变性(Permutation Invariance)
问题: 如果给节点重新编号,相似度结果应该不变
解决方案: 对邻居标签排序
csharp
// 关键代码
var neighborLabels = node.NeighborIds
.Select(neighborId => graph.Nodes[neighborId].CurrentLabel)
.OrderBy(label => label) // ← 字母排序
.ToList();
示例:
场景 A:节点 1 的邻居是 [节点 5="平面", 节点 3="圆柱面"]
场景 B:节点 1 的邻居是 [节点 3="圆柱面", 节点 5="平面"]
不排序:
场景 A → "圆柱面_ (平面,圆柱面)"
场景 B → "圆柱面_ (圆柱面,平面)"
❌ 结果不同!
排序后:
场景 A → "圆柱面_ (平面,圆柱面)"
场景 B → "圆柱面_ (平面,圆柱面)"
✅ 结果相同!
2️⃣ 同构不变性(Isomorphism Invariance)
问题: 两个结构相同的图(节点编号不同),应该得到相同的标签分布
解决方案: WL 迭代的消息传递机制
图 A: 图 B:
①--② ⑤--④
| | | |
③--④ ⑦--⑥
虽然节点编号不同,但拓扑结构相同
迭代 0:
图 A: [圆柱面,平面,平面,圆柱面]
图 B: [圆柱面,平面,平面,圆柱面]
迭代 1:
图 A: [H1, H2, H2, H1] (H1=Hash("圆柱面_ (平面,平面)"))
图 B: [H1, H2, H2, H1]
✅ 标签频率完全相同!
3️⃣ 尺度不变性(通过统计向量)
问题: 如何比较节点数不同的图?
解决方案: 使用标签频率向量而非具体节点
csharp
// wl_graph_kernel.cs 第 113-127 行
private static Dictionary<string, int> CountLabelFrequencies(PartGraph graph)
{
var frequency = new Dictionary<string, int>();
foreach (var node in graph.Nodes)
{
if (!frequency.ContainsKey(node.CurrentLabel))
{
frequency[node.CurrentLabel] = 0;
}
frequency[node.CurrentLabel]++; // ← 只统计次数,不管具体位置
}
return frequency;
}
示例:
零件 A(10 个面):
标签 L1: 4 次,标签 L2: 6 次
→ 向量 A = [L1:4, L2:6]
零件 B(20 个面,结构类似):
标签 L1: 8 次,标签 L2: 12 次
→ 向量 B = [L1:8, L2:12]
余弦相似度计算:
A·B = 4×8 + 6×12 = 104
||A|| = √(4²+6²) = 7.21
||B|| = √(8²+12²) = 14.42
similarity = 104 / (7.21×14.42) ≈ 1.0 ✅
📊 完整的不变性保证
| 不变性类型 | 保证机制 | 代码位置 |
|---|---|---|
| 排列不变性 | 邻居标签排序 | OrderBy(label => label) |
| 同构不变性 | WL 消息传递 | 迭代细化标签 |
| 平移/旋转不变性 | 基于拓扑而非坐标 | FaceType 作为初始标签 |
| 尺度不变性 | 频率向量归一化 | 余弦相似度公式 |
| 节点编号不变性 | 使用哈希标签 | HashLabel() 函数 |
🎓 总结
WL 迭代不以某个特定节点为终点,而是:
- 全局并行更新:所有节点在每轮迭代中同时更新标签
- 局部聚合:每个节点聚合其邻居的信息
- 统计表征:最终用标签频率向量表示整张图
图不变性通过以下机制保证:
- ✅ 邻居排序 → 排列不变性
- ✅ 消息传递 → 同构不变性
- ✅ 频率统计 → 尺度不变性
- ✅ 哈希压缩 → 编号无关性
这使得 WL 图核能够稳定地捕捉零件的拓扑结构本质,而不受节点编号、坐标系、缩放等因素影响。
需要我画个示意图进一步说明吗?