1.基本原理
图编辑距离是衡量两个图之间“结构相似性” 的核心指标,本质是量化“将一个图转换为另一个图所需的最少编辑操作次数/代价”,是图匹配、图相似性分析的基础。
核心思想
GED 将两个图之间的差异定义为:
将一个图通过一系列“编辑操作”转换为另一个图所需的最小代价总和。
这些编辑操作包括:
- 节点插入(Insert a node)
- 节点删除(Delete a node)
- 节点替换/重标记(Substitute/relabel a node)
- 边插入(Insert an edge)
- 边删除(Delete an edge)
✅ 类比:GED 是图结构上的 “Levenshtein 距离”(字符串编辑距离)。
2.数学形式
设两个图:
- G1=(V1,E1,μ1,ν1)G_1 = (V_1, E_1, \mu_1, \nu_1)G1=(V1,E1,μ1,ν1)
- G2=(V2,E2,μ2,ν2)G_2 = (V_2, E_2, \mu_2, \nu_2)G2=(V2,E2,μ2,ν2)
其中:
- VVV:节点集合
- E⊆V×VE \subseteq V \times VE⊆V×V:边集合
- μ:V→LV\mu: V \to \mathcal{L}_Vμ:V→LV:节点标签函数
- ν:E→LE\nu: E \to \mathcal{L}_Eν:E→LE:边标签函数
2.1 编辑操作代价函数
定义以下代价函数(通常由用户指定):
- cnode-del(u)c_{\text{node-del}}(u)cnode-del(u):删除节点 uuu 的代价
- cnode-ins(v)c_{\text{node-ins}}(v)cnode-ins(v):插入节点 vvv 的代价
- cnode-sub(u,v)c_{\text{node-sub}}(u, v)cnode-sub(u,v):将节点 uuu 替换为vvv的代价(常基于标签差异,如 ∥μ1(u)−μ2(v)∥\| \mu_1(u) - \mu_2(v) \|∥μ1(u)−μ2(v)∥)
- cedge-del(e)c_{\text{edge-del}}(e)cedge-del(e)、cedge-ins(e′)c_{\text{edge-ins}}(e')cedge-ins(e′)、cedge-sub(e,e′)c_{\text{edge-sub}}(e, e')cedge-sub(e,e′):类似定义
2.2 图编辑距离定义
GED(G1,G2)=minπ∈Π(G1,G2)∑edit ops in πcost(op)
\text{GED}(G_1, G_2) = \min_{\pi \in \Pi(G_1, G_2)} \sum_{\text{edit ops in } \pi} \text{cost(op)}
GED(G1,G2)=π∈Π(G1,G2)minedit ops in π∑cost(op)
其中 Π(G1,G2)\Pi(G_1, G_2)Π(G1,G2) 是所有将 G1G_1G1 转换为 G2G_2G2 的合法编辑路径集合。
💡 实际上,GED 等价于寻找一个最优的节点与边的对应关系(可能包含空匹配),使得总编辑代价最小。
2.3 计算复杂性
- NP-hard 问题:精确计算 GED 是 NP-hard 的,甚至 APX-hard(难以近似)。
- 等价于带代价的子图同构
- 节点数 n → 搜索空间 ~ (O(n!))
- 精确 GED 只能用于小图
- 指数时间复杂度:对于大小为 nnn 和 mmm 的图,精确算法复杂度约为 O(3n+m)O(3^{n+m})O(3n+m)。
- 仅适用于小图:通常限于节点数 ≤ 20 的图(除非使用强启发式或近似)。
因此,实际应用中多采用:
- 精确算法(小图)
- 启发式/近似算法(如 A* 搜索、Beam Search、LP relaxation)
- 学习型 GED(用神经网络预测 GED,如 SimGNN)
3.优缺点
3.1 优点
表达能力最强
- 节点 + 边 + 标签
- 语义 & 结构统一
可解释性极好
- 每一分距离都能解释成具体操作
通用性极强
- 不依赖特定图类型
- 不依赖嵌入空间
3.2 缺点(非常关键)
计算复杂度极高
- 指数级
- 无法扩展到大图
强依赖代价函数设计
- 代价设计不好 = 距离无意义
对噪声敏感
- 一个小错误 → 多次编辑
4.适用场景
4.1 典型使用场景
小规模图结构精确比对
- 分子结构比较
- 程序依赖图
- UML / AST 对比
图检索 / 图分类(小图)
- 查询图 vs 数据库图
图生成模型评估
- 生成图是否“结构正确”
- 比统计指标更严格
作为“上界指标”
- 验证近似方法是否合理
4.2 不该用GED
❌ 大规模图
❌ 只关心统计结构
❌ 需要实时计算
❌ 嵌入空间已经足够表达
👉 改用:
- 分布差(路径长度 / 关系分布)
- 图嵌入距离
- GNN 相似度
替代方案
| 方法 | 适用场景 |
|---|---|
| 图核(Graph Kernels) | 如 WL Kernel、Shortest Path Kernel —— 快速但表达能力有限 |
| 图嵌入(Graph Embedding) | 如 Graph2Vec、GLEE —— 将图映射为向量后计算欧氏距离 |
| GNN-based 相似性 | 如 SimGNN、GMN —— 用神经网络学习图对的相似性得分 |
| 子图匹配 / 最大公共子图(MCS) | 若只关心公共结构,而非编辑代价 |
4.3 关键补充
- 算法选择原则:
- 节点数<10:用精确算法(python-igraph);
- 节点数10-50:用A*近似算法(python-igraph);
- 节点数>50:用深度学习近似(PyG + GEDNet)。
- 代价函数设计:
- 无属性图:所有操作代价设为1;
- 带属性图:节点/边替换代价设为属性差异度(如标签相似度、权重差值);
- 业务场景:根据重要性调整代价(如节点删除代价>边删除代价)。
- 效率优化:
- 预处理:移除孤立节点(不影响GED,减少计算量);
- 缓存:重复计算同一对图的GED时,缓存结果;
- 并行计算:批量计算多对图的GED时,用多线程/多进程。
- 误差控制:
- 近似算法需验证误差范围(与精确值对比);
- 深度学习模型需用标注的GED数据训练,保证预测精度。
5.框架选型
| 框架 | 特点 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
networkx + ged4py / graphsim | 需第三方扩展 | 简单图支持 | 功能有限 | 教学、小规模实验 |
igraph | 内置 graph_edit_distance(需 dev 版) | C++ 后端,较快 | 文档少,安装复杂 | 中小图 |
GEDEnv / GEDLIB(C++)+ Python 绑定 | 专业 GED 库 | 支持多种算法(A*, DF, BP-BEAM) | 需编译,配置复杂 | 研究、高精度需求 |
pytorch-geometric + SimGNN / GMN | 基于 GNN 的 GED 预测 | 可扩展到大图,速度快 | 是近似预测,非真实 GED | 大规模图相似性排序 |
grakel | 图核库,含 GED 近似 | 与 scikit-learn 兼容 | 仅支持无向简单图 | 机器学习流水线 |
| 框架/方案 | 核心优点 | 核心缺点 | 最佳使用场景 |
|---|---|---|---|
| NetworkX + 自定义 | 1. API直观,新手易理解; 2. 完全可控编辑代价 | 1. 仅支持极小图(节点数<10); 2. 精确算法效率极低; 3. 无内置近似算法 | 1. 教学/原理验证; 2. 极小图的GED计算 |
| python-igraph | 1. 内置精确/近似算法,效率比NetworkX高100倍; 2. 支持属性图; 3. C底层实现,内存占用低; 4. 自定义编辑代价灵活 | 1. 近似算法结果有误差; 2. 节点数>50时速度下降; 3. API稍复杂 | 1. 中小规模图(节点数10-50); 2. 工业级精确GED计算; 3. 带属性图的GED分析 |
| PyTorch Geometric + GEDNet | 1. 支持GPU加速,适合超大规模图(节点数>100); 2. 可批量预测; 3. 无缝融入GNN流程 | 1. 需大量标注数据训练; 2. 结果为近似值,无精确保证; 3. 学习成本高 | 1. 超大规模图的GED近似计算; 2. 深度学习+图相似性分析; 3. 批量GED预测 |
| GraphMatchingToolkit | 1. 专注图匹配,GED算法丰富; 2. 支持加权图 | 1. 文档少,社区支持弱; 2. 安装复杂 | 1. 学术研究; 2. 特殊图匹配场景 |
6.使用
场景1:新手/小规模图(NetworkX + 自定义实现,易用性第一)
NetworkX无内置GED API,需手动实现基础版本(适合节点数<10的极小图,用于理解原理):
import networkx as nx
import itertools
def node_edit_cost(u, v, G1, G2):
"""节点替换代价(无属性图设为1,有属性图可自定义)"""
return 0 if u == v else 1
def edge_edit_cost(G1, G2, node_mapping):
"""计算边的编辑代价(插入/删除)"""
cost = 0
# 映射G1的节点到G2的节点
G1_mapped = nx.relabel_nodes(G1, node_mapping)
# 计算需要删除的边(G1有,G2无)
edges_G1 = set(G1_mapped.edges())
edges_G2 = set(G2.edges())
cost += len(edges_G1 - edges_G2) # 删除边的代价
# 计算需要插入的边(G2有,G1无)
cost += len(edges_G2 - edges_G1) # 插入边的代价
return cost
def graph_edit_distance(G1, G2):
"""
计算无属性图的GED(精确版,仅适合节点数<10的图)
"""
nodes1 = list(G1.nodes())
nodes2 = list(G2.nodes())
n1, n2 = len(nodes1), len(nodes2)
min_cost = float('inf')
# 生成所有可能的节点匹配(考虑插入/删除)
# 情况1:G1节点数 ≤ G2节点数 → G1节点映射到G2,剩余G2节点需插入
if n1 <= n2:
for perm in itertools.permutations(nodes2, n1):
node_mapping = dict(zip(nodes1, perm))
# 节点替换代价
node_cost = sum(node_edit_cost(u, v, G1, G2) for u, v in node_mapping.items())
# 插入剩余G2节点的代价
insert_cost = n2 - n1
# 边编辑代价
edge_cost = edge_edit_cost(G1, G2, node_mapping)
# 总代价
total_cost = node_cost + insert_cost + edge_cost
if total_cost < min_cost:
min_cost = total_cost
# 情况2:G1节点数 > G2节点数 → G2节点映射到G1,剩余G1节点需删除
else:
for perm in itertools.permutations(nodes1, n2):
node_mapping = dict(zip(perm, nodes2))
# 节点替换代价
node_cost = sum(node_edit_cost(u, v, G1, G2) for u, v in node_mapping.items())
# 删除剩余G1节点的代价
delete_cost = n1 - n2
# 边编辑代价
edge_cost = edge_edit_cost(G1, G2, node_mapping)
# 总代价
total_cost = node_cost + delete_cost + edge_cost
if total_cost < min_cost:
min_cost = total_cost
return min_cost
# 测试示例
if __name__ == "__main__":
# 构建两个简单图
G1 = nx.Graph()
G1.add_edges_from([(0,1), (1,2)]) # 链状图:0-1-2
G2 = nx.Graph()
G2.add_edges_from([(0,1), (1,2), (2,3)]) # 链状图:0-1-2-3
# 计算GED
ged = graph_edit_distance(G1, G2)
print(f"G1和G2的图编辑距离:{ged}") # 输出1(插入节点3 + 插入边(2,3) → 代价2?实际需看匹配逻辑,此处仅示例)
场景2:中大规模图(python-igraph,高性能+内置算法,推荐)
python-igraph内置GED的精确算法和近似算法(如Bipartite Graph Matching、A*搜索),支持节点数<50的图,是工业级场景的最优选择:
import igraph as ig
# 1. 构建两个测试图
# G1:0-1-2(链状图)
G1 = ig.Graph()
G1.add_vertices(3)
G1.add_edges([(0,1), (1,2)])
# G2:0-1-2-3(链状图)
G2 = ig.Graph()
G2.add_vertices(4)
G2.add_edges([(0,1), (1,2), (2,3)])
# 2. 计算GED(精确算法)
# edit_cost参数:自定义编辑代价(默认所有操作代价为1)
ged = G1.edit_distance(G2, edit_cost=ig.EditCost(nodes_insert=1, nodes_delete=1, edges_insert=1, edges_delete=1))
print(f"G1和G2的精确GED:{ged}") # 输出2(插入节点3 + 插入边(2,3))
# 3. 带属性图的GED(节点标签)
# 给G1、G2添加节点标签
G1.vs["label"] = ["A", "B", "C"]
G2.vs["label"] = ["A", "B", "C", "D"]
# 自定义节点替换代价(标签相同则代价0,否则1)
def node_substitution_cost(v1, v2):
return 0 if v1["label"] == v2["label"] else 1
# 计算带属性的GED
ged_attr = G1.edit_distance(
G2,
node_subst_cost=node_substitution_cost,
edit_cost=ig.EditCost(nodes_insert=1, nodes_delete=1, edges_insert=1, edges_delete=1)
)
print(f"带属性的GED:{ged_attr}") # 输出2(标签匹配,仅需插入节点和边)
# 4. 近似算法(适合大规模图,速度快)
ged_approx = G1.edit_distance(G2, algorithm="astar") # A*近似算法
print(f"近似GED:{ged_approx}")
场景3:深度学习场景(PyTorch Geometric + GEDNet,GPU加速)
对于超大规模图(节点数>100),需用深度学习模型近似计算GED(如GEDNet),PyG支持将图嵌入后通过神经网络预测GED:
import torch
import torch.nn as nn
from torch_geometric.data import Data
from torch_geometric.nn import GCNConv
# 简易GED预测模型(GEDNet核心思路)
class GEDNet(nn.Module):
def __init__(self, in_channels, hidden_channels):
super().__init__()
self.conv1 = GCNConv(in_channels, hidden_channels)
self.conv2 = GCNConv(hidden_channels, hidden_channels)
self.fc = nn.Linear(2 * hidden_channels, 1) # 拼接两个图的嵌入
def forward(self, x1, edge_index1, x2, edge_index2):
# 图1嵌入
h1 = self.conv1(x1, edge_index1).relu()
h1 = self.conv2(h1, edge_index1).relu()
h1 = torch.mean(h1, dim=0) # 全局池化
# 图2嵌入
h2 = self.conv1(x2, edge_index2).relu()
h2 = self.conv2(h2, edge_index2).relu()
h2 = torch.mean(h2, dim=0)
# 预测GED
h = torch.cat([h1, h2], dim=0)
ged_pred = self.fc(h)
return ged_pred
# 测试示例
if __name__ == "__main__":
# 构建PyG格式的图
x1 = torch.ones((3, 1)) # 节点特征
edge_index1 = torch.tensor([[0,1,1,2], [1,0,2,1]]) # G1的边索引
data1 = Data(x=x1, edge_index=edge_index1)
x2 = torch.ones((4, 1))
edge_index2 = torch.tensor([[0,1,1,2,2,3], [1,0,2,1,3,2]]) # G2的边索引
data2 = Data(x=x2, edge_index=edge_index2)
# 初始化模型
model = GEDNet(in_channels=1, hidden_channels=8)
# 预测GED
ged_pred = model(data1.x, data1.edge_index, data2.x, data2.edge_index)
print(f"深度学习预测GED:{ged_pred.item():.2f}")
&spm=1001.2101.3001.5002&articleId=156853082&d=1&t=3&u=ad932e72ea7e4934b1ef754e7a700387)
1652

被折叠的 条评论
为什么被折叠?



