图匹配(分解)相关代码学习
根据论文Factorized Graph Matching学习图分解和图匹配的相关代码,并进行逐行解读
版本1:
优:特征分解、按特征值降序排列特征向量、计算相似度矩阵、匈牙利算法寻找最优匹配
劣:缺少论文中提到的因式分解部分
import numpy as np
from scipy.optimize import linear_sum_assignment
# 用于接收两个邻接矩阵,A/B表示待匹配的图
def factorized_graph_matching(A, B):
# 对邻接矩阵进行特征分解 np.linalg.eigh()函数专门用于对称矩阵
# 结果是得到矩阵的特征值eigvals和对应的特征向量eigvecs
eigvals_A, eigvecs_A = np.linalg.eigh(A)
eigvals_B, eigvecs_B = np.linalg.eigh(B)
# 按特征值降序排列特征向量(处理符号问题)
# 特征值降序排列返回索引
idx_A = np.argsort(eigvals_A)[::-1]
# 通过索引对特征向量重排
eigvecs_A = eigvecs_A[:, idx_A]
# 图解过程 标准化特征向量符号
eigvecs_A = np.sign(eigvecs_A[np.argmax(np.abs(eigvecs_A), axis=0), range(eigvecs_A.shape[1])]) * eigvecs_A
idx_B = np.argsort(eigvals_B)[::-1]
eigvecs_B = eigvecs_B[:, idx_B]
eigvecs_B = np.sign(eigvecs_B[np.argmax(np.abs(eigvecs_B), axis=0), range(eigvecs_B.shape[1])]) * eigvecs_B
# 计算特征向量间的相似度矩阵(余弦相似度)
similarity = np.abs(eigvecs_A.T @ eigvecs_B)
# 使用匈牙利算法寻找最优匹配(负号,默认最小代价但希望最大相似度)
row_ind, col_ind = linear_sum_assignment(-similarity)
# 构建匹配结果
matching = np.zeros(A.shape[0], dtype=int)
matching[row_ind] = col_ind
return matching
# 示例用法
if __name__ == "__main__":
np.random.seed(0)
n = 5 # 节点数量
# 生成原始图A和其排列版本B(添加噪声)
A = np.random.rand(n, n)
A = (A + A.T) / 2 # 对称化
true_perm = np.random.permutation(n)
B = A[true_perm][:, true_perm]
B += np.random.normal(scale=0.05, size=(n, n)) # 添加噪声
# 进行图匹配
predicted_perm = factorized_graph_matching(A, B)
# 显示结果
print("真实排列:", true_perm)
print("预测排列:", predicted_perm)
accuracy = np.mean(predicted_perm == true_perm)
print(f"匹配准确率: {accuracy * 100:.1f}%")
①随机生成矩阵A
②对称化,便于使用np.linalg.eigh()函数,专门用于对称矩阵
③生成一个随机重排列,用于生成B,并添加扰动噪声
④跳入函数进行匹配
输入:
提取AB的特征值和特征向量:
特征值降序返回索引:
通过索引对特征值和特征向量重排:
计算特征向量之间的相似度:
匈牙利算法寻找最优匹配:
构建0矩阵,填入匹配结果
版本2:
优:适用于小规模图的匹配任务,手动分块
劣:通过Euclidean距离来计算每个节点之间的相似度,使用了节点的属性值进行匹配
import numpy as np
import networkx as nx
from scipy.optimize import linear_sum_assignment
def compute_cost_matrix(graph1, graph2):
# 获取节点数量
num_nodes1 = len(graph1.nodes)
num_nodes2 = len(graph2.nodes)
# 初始化全为0的代价矩阵
cost_matrix = np.zeros((num_nodes1, num_nodes2))
# 遍历两个图的每个节点
for i, node1 in enumerate(graph1.nodes):
for j, node2 in enumerate(graph2.nodes):
# 从图中获取节点特征值,若没有特征值设为0
value1 = graph1.nodes[node1].get('value', np.array([0]))
value2 = graph2.nodes[node2].get('value', np.array([0]))
# 计算两个节点特征值之间的欧几里得距离,并将其作为匹配代价存入代价矩阵
cost_matrix[i, j] = np.linalg.norm(value1 - value2)
return cost_matrix
def factorized_graph_matching(graph1, graph2):
# Step 1: 计算两个图之间的代价矩阵
cost_matrix = compute_cost_matrix(graph1, graph2)
# Step 2: 使用匈牙利算法求解代价矩阵的最优匹配
row_ind, col_ind = linear_sum_assignment(cost_matrix)
# Step 3: 将匹配结果组合成一个列表
matching = list(zip(row_ind, col_ind))
return matching
def create_sample_graph():
graph1 = nx.Graph()
# 创建一个包含三个节点(编号为0,1,2)的图1
graph1.add_nodes_from([0, 1, 2])
# 为每个节点添加一个名为value的属性
graph1.nodes[0]['value'] = np.array([1, 2])
graph1.nodes[1]['value'] = np.array([2, 3])
graph1.nodes[2]['value'] = np.array([3, 4])
graph2 = nx.Graph()
graph2.add_nodes_from([0, 1, 2])
graph2.nodes[0]['value'] = np.array([1, 1])
graph2.nodes[1]['value'] = np.array([2, 2])
graph2.nodes[2]['value'] = np.array([3, 3])
return graph1, graph2
if __name__ == "__main__":
graph1, graph2 = create_sample_graph()
matching = factorized_graph_matching(graph1, graph2)
print("Optimal Node Matching:")
for node1, node2 in matching:
print(f"Graph1 Node {node1} is matched with Graph2 Node {node2}")
①创建两张图,并为节点赋予属性值
graph1:
graph2:
②调用图匹配函数,匹配graph1和graph2
step1:计算两个图之间的代价矩阵
1.获取节点数量并初始化代价矩阵
2. 遍历两个图的每个节点并获取节点特征值,若没有特征值则设为0;计算代价矩阵
代价越小,对应的匹配越优
step2:使用匈牙利算法求解代价矩阵的最优匹配
step3: 将匹配结果组合成一个列表
得到匹配结果
版本3:
结合上述两种
import numpy as np
import networkx as nx
from scipy.optimize import linear_sum_assignment
# 将输入图转换为邻接矩阵
def compute_adjacency_matrix(graph):
return nx.to_numpy_array(graph)
def factorized_graph_matching(graph1, graph2, k=5):
# Step 1: 计算邻接矩阵
A1 = compute_adjacency_matrix(graph1)
A2 = compute_adjacency_matrix(graph2)
# Step 2: 计算特征值和特征向量
eigvals_A1, eigvecs_A1 = np.linalg.eigh(A1)
eigvals_A2, eigvecs_A2 = np.linalg.eigh(A2)
# Step 3: 按特征值降序排列特征向量
idx_A1 = np.argsort(eigvals_A1)[::-1]
eigvecs_A1 = eigvecs_A1[:, idx_A1]
idx_A2 = np.argsort(eigvals_A2)[::-1]
eigvecs_A2 = eigvecs_A2[:, idx_A2]
# Step 4: 选择前k个特征向量(更具有代表性)
eigvecs_A1 = eigvecs_A1[:, :k]
eigvecs_A2 = eigvecs_A2[:, :k]
# Step 5: 计算特征向量之间的相似度矩阵
similarity_matrix = np.abs(np.dot(eigvecs_A1.T, eigvecs_A2))
# Step 6: 使用匈牙利算法找到最优匹配
row_ind, col_ind = linear_sum_assignment(-similarity_matrix)
# Step 7: 返回匹配结果
matching = list(zip(row_ind, col_ind))
return matching
def create_sample_graph():
graph1 = nx.Graph()
graph1.add_nodes_from([0, 1, 2, 3])
graph1.add_edges_from([(0, 1), (1, 2), (2, 3)])
graph2 = nx.Graph()
graph2.add_nodes_from([0, 1, 2, 3])
graph2.add_edges_from([(0, 2), (1, 3), (2, 3)])
return graph1, graph2
# Test the factorized graph matching
if __name__ == "__main__":
graph1, graph2 = create_sample_graph()
matching = factorized_graph_matching(graph1, graph2, k=2)
print("Optimal Node Matching:")
for node1, node2 in matching:
print(f"Graph1 Node {node1} is matched with Graph2 Node {node2}")
版本4:
使用SVD,和论文基本一致的因式分解
SVD分解函数:
import numpy as np
def factorize_cost_matrix(K, rank=2):
# K:需要分解的矩阵 U:左因子矩阵 sigma:奇异值矩阵(对角矩阵) V:右因子矩阵
# Step 1:对矩阵K进行奇异值分解
U, sigma, Vt = np.linalg.svd(K, full_matrices=False)
# Step 2:保留前rank个奇异值和对应的奇异向量
# 从U中提取前rank列,表示保留的左奇异向量。
U = U[:, :rank]
# 从奇异值数组中提取前rank个奇异值,并将它们转换为对角矩阵Σ。
sigma = np.diag(sigma[:rank])
# 从VT的转置中提取前 rank 列,表示保留的右奇异向量
V = Vt.T[:, :rank]
return U, sigma, V
# Example usage:
if __name__ == "__main__":
# Example cost matrix K (could be a similarity or distance matrix)
K = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# Factorize the cost matrix into U and V
U, sigma, V = factorize_cost_matrix(K, rank=2)
# Reconstruct the matrix K from U, sigma, and V
K_reconstructed = U @ sigma @ V.T
print("Original Matrix K:")
print(K)
print("\nReconstructed Matrix K:")
print(K_reconstructed)
①传入分解的矩阵K
②对矩阵K进行奇异值分解
③保留前rank个奇异值和对应的奇异向量
低秩近似:通过保留矩阵的主要成分来近似原始矩阵,同时减少计算复杂度和数据维度
SVD分解可以表示为:
,
:正交矩阵
:对角矩阵,对角线上的元素是奇异值,按从大到小的顺序排列
奇异值的大小反映了矩阵中每个成分的重要性。较大的奇异值对应于矩阵的主要结构信息,而较小的奇异值通常可以被视为噪声或次要信息
左奇异向量:
对角矩阵Σ:
右奇异向量:
④重塑K
结合SVD后的图匹配:
import numpy as np
import networkx as nx
from scipy.optimize import linear_sum_assignment
# 计算节点间的相似度矩阵
def compute_similarity_matrix(graph1, graph2):
"""
计算两个图之间的节点相似度矩阵。
这里使用欧几里得距离来计算节点之间的相似度。
"""
num_nodes1 = len(graph1.nodes)
num_nodes2 = len(graph2.nodes)
# 初始化相似度矩阵
similarity_matrix = np.zeros((num_nodes1, num_nodes2))
# 计算节点间的相似度(以欧几里得距离为例)
for i, node1 in enumerate(graph1.nodes):
for j, node2 in enumerate(graph2.nodes):
# 假设每个节点有一个'value'属性,我们用该属性进行相似度计算
value1 = graph1.nodes[node1].get('value', np.array([0]))
value2 = graph2.nodes[node2].get('value', np.array([0]))
# 使用欧几里得距离作为相似度(可以替换为其他相似度度量方法)
similarity_matrix[i, j] = np.linalg.norm(value1 - value2)
return similarity_matrix
# 使用SVD进行因式分解
def factorize_cost_matrix(K, rank=2):
"""
使用SVD对代价矩阵K进行因式分解。
"""
# 对K矩阵进行SVD分解
U, sigma, Vt = np.linalg.svd(K, full_matrices=False)
# 保留前rank个奇异值
U = U[:, :rank]
sigma = np.diag(sigma[:rank])
V = Vt.T[:, :rank]
return U, sigma, V
# 图匹配函数
def factorized_graph_matching(graph1, graph2, rank=2):
"""
使用因式分解方法进行图匹配。
"""
# 计算节点相似度矩阵(代价矩阵)
similarity_matrix = compute_similarity_matrix(graph1, graph2)
# 使用SVD进行因式分解
U, sigma, V = factorize_cost_matrix(similarity_matrix, rank)
# 重构代价矩阵
reconstructed_matrix = U @ sigma @ V.T
# 使用匈牙利算法进行最优匹配
row_ind, col_ind = linear_sum_assignment(reconstructed_matrix)
# 输出匹配结果
matching = list(zip(row_ind, col_ind))
return matching
# 创建样本图
def create_sample_graph():
"""
创建测试用的样本图。
图中每个节点都有一个'value'属性,用于计算相似度。
"""
graph1 = nx.Graph()
graph1.add_nodes_from([0, 1, 2])
graph1.nodes[0]['value'] = np.array([1, 2])
graph1.nodes[1]['value'] = np.array([2, 3])
graph1.nodes[2]['value'] = np.array([3, 4])
graph2 = nx.Graph()
graph2.add_nodes_from([0, 1, 2])
graph2.nodes[0]['value'] = np.array([1, 1])
graph2.nodes[1]['value'] = np.array([2, 2])
graph2.nodes[2]['value'] = np.array([3, 3])
return graph1, graph2
# 测试图匹配
if __name__ == "__main__":
# 创建样本图
graph1, graph2 = create_sample_graph()
# 进行图匹配
matching = factorized_graph_matching(graph1, graph2)
# 输出匹配结果
print("Optimal Node Matching:")
for node1, node2 in matching:
print(f"Graph1 Node {node1} is matched with Graph2 Node {node2}")