图(graph)近来正逐渐变成机器学习的一大核心领域,在开始PGL框架学习之前,我们先简单学习一下图论的基本概念,图论的经典算法,以及近些年来图学习的发展。
本案例将包含以下内容:
一. 图是什么?
二. 如何存储图?
三. 图的类型和性质
四. 主要的图算法
五. 图机器学习的发展
首先我们导入需要的包
import numpy as np
import random
import networkx as nx
from IPython.display import Image
import matplotlib.pyplot as plt
图表示物件与物件之间的关系的数学对象,是图论的基本研究对象。
举个例子,一个简单的图可能是这样:
节点(node)用红色标出,通过黑色的边(edge)连接。
图可用于表示:
我们可以在图上执行怎样的分析?
我们首先在我们的笔记本中导入第一个预构建的图:
# Load the graph
G_karate = nx.karate_club_graph()
# Find key-values for the graph
pos = nx.spring_layout(G_karate)
# Plot the graph
nx.draw(G_karate, cmap = plt.get_cmap('rainbow'), with_labels=True, pos=pos)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-etG9G128-1610329878790)(output_4_0.png)]
空手道俱乐部图
这个「空手道」图表示什么?Wayne W. Zachary 在 1970 到 1972 年这三年中研究的一个空手道俱乐部的社交网络。该网络包含了这个空手道俱乐部的 34 个成员,成员对之间的连接表示他们在俱乐部之外也有联系。在研究期间,管理员 JohnA 与教练 Mr.Hi(化名)之间出现了冲突,导致俱乐部一分为二。一半成员围绕 Mr.Hi 形成了一个新的俱乐部,另一半则找了一个新教练或放弃了空手道。基于收集到的数据,除了其中一个成员,Zachary 正确分配了所有成员在分裂之后所进入的分组。
节点、边和度的示意图
举个例子,在这个案例中,我们可以计算出一些连接任意两个节点的最短路径。该图的直径为 3,因为没有任意两个节点之间的最短路径的长度超过 3。
一个直径为 3 的图
举个例子,下面是一个有两个不同连通分支的图:
一个有两个连通分支的图
有向图
Neo4J 的关于图算法的书给出了清晰明了的总结:
总结(来自 Neo4J Graph Book)
回到我们的空手道俱乐部图
# .degree() 属性会返回该图的每个节点的度(相邻节点的数量)的列表:
n=34
print(G_karate.degree())
degree_sequence = list(G_karate.degree())
[(0, 16), (1, 9), (2, 10), (3, 6), (4, 3), (5, 4), (6, 4), (7, 4), (8, 5), (9, 2), (10, 3), (11, 1), (12, 2), (13, 5), (14, 2), (15, 2), (16, 2), (17, 2), (18, 2), (19, 3), (20, 2), (21, 2), (22, 2), (23, 5), (24, 3), (25, 3), (26, 2), (27, 4), (28, 3), (29, 4), (30, 4), (31, 6), (32, 12), (33, 17)]
# 计算边的数量,但也计算度序列的度量:
nb_nodes = n
nb_arr = len(G_karate.edges())
avg_degree = np.mean(np.array(degree_sequence)[:,1])
med_degree = np.median(np.array(degree_sequence)[:,1])
max_degree = max(np.array(degree_sequence)[:,1])
min_degree = np.min(np.array(degree_sequence)[:,1])
# 最后,打印所有信息:
print("Number of nodes : " + str(nb_nodes))
print("Number of edges : " + str(nb_arr))
print("Maximum degree : " + str(max_degree))
print("Minimum degree : " + str(min_degree))
print("Average degree : " + str(avg_degree))
print("Median degree : " + str(med_degree))
Number of nodes : 34
Number of edges : 78
Maximum degree : 17
Minimum degree : 1
Average degree : 4.588235294117647
Median degree : 3.0
# 平均而言,该图中的每个人都连接了 4.6 个人。
# 我们可以绘出这些度的直方图:
degree_freq = np.array(nx.degree_histogram(G_karate)).astype('float')
plt.figure(figsize=(12, 8))
plt.stem(degree_freq)
plt.ylabel("Frequence")
plt.xlabel("Degre")
plt.show()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BZLX3bTG-1610329878805)(output_9_0.png)]
我们后面会看到,度的直方图相当重要,可用于确定我们看到的图的种类。
存储图的方式有三种,取决于你想用它做什么:
1 2
1 3
1 4
2 3
3 4
…
我们存储有边连接的每一对节点的 ID,例如:
G_karate.edges()
对于图中的每一个可能的配对,如果两个节点有边相连,则设为 1。如果该图是无向图,则 A 是对称的。
1 :[2, 3, 4]
2 :[1,3]
3 :[2, 4]
…
这三种表示方式都是等价的,我们可以根据使用场景来选择图的存储方式。
图可以根据不同标准进行分类,我们在这里主要讲一种分类方法,同构图与异构图。了解更多可以查看博客,图论(二)–各种图介绍
两个图G和H是同构图(isomorphic graphs),能够通过重新标记图G的顶点而产生图H。
如果G和H同构,那么它们的阶是相同的,它们大小是相同的,它们个顶点的度数也对应相同。
异构图是一个与同构图相对应的新概念。
传统同构图(Homogeneous Graph)数据中只存在一种节点和边,因此在构建图神经网络时所有节点共享同样的模型参数并且拥有同样维度的特征空间。
而异构图(Heterogeneous Graph)中可以存在不只一种节点和边,因此允许不同类型的节点拥有不同维度的特征或属性。
目前大多数框架(比如 Python 的 networkx 或 Neo4J)支持的图算法类别主要有三个:
networkx 中的所有算法都可在这里找到:https://networkx.github.io/documentation/stable/reference/algorithms/index.html
我们只会介绍 networkx 中实现的最常见的基本算法。
图搜索算法主要有两种:
最短路径计算的是一对节点之间的最短的加权(如果图有加权的话)路径。
这可用于确定最优的驾驶方向或社交网络上两个人之间的分离程度。
计算图中的最短路径的方法有很多,包括 Dijkstra 算法,这是 networkx 中的默认算法。更多有关最短路径问题的介绍请参阅:https://en.wikipedia.org/wiki/Shortest_path_problem
用空手道俱乐部图举例
nx.draw(G_karate, cmap = plt.get_cmap('rainbow'), with_labels=True, pos=pos)
# 这会返回图中每个节点之间的最小路径的列表:
all_shortest_path = nx.shortest_path(G_karate)
# 这里打印了节点0与其余节点的最短路径
print(all_shortest_path[0])
# 例如节点0与节点26的最短路径是[0, 8, 33, 26]
{0: [0], 1: [0, 1], 2: [0, 2], 3: [0, 3], 4: [0, 4], 5: [0, 5], 6: [0, 6], 7: [0, 7], 8: [0, 8], 10: [0, 10], 11: [0, 11], 12: [0, 12], 13: [0, 13], 17: [0, 17], 19: [0, 19], 21: [0, 21], 31: [0, 31], 30: [0, 1, 30], 9: [0, 2, 9], 27: [0, 2, 27], 28: [0, 2, 28], 32: [0, 2, 32], 16: [0, 5, 16], 33: [0, 8, 33], 24: [0, 31, 24], 25: [0, 31, 25], 23: [0, 2, 27, 23], 14: [0, 2, 32, 14], 15: [0, 2, 32, 15], 18: [0, 2, 32, 18], 20: [0, 2, 32, 20], 22: [0, 2, 32, 22], 29: [0, 2, 32, 29], 26: [0, 8, 33, 26]}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lXTJL4B0-1610329878809)(output_16_1.png)]
单源最短路径(Single Source Shortest Path/SSSP)是找到给定节点与图中其它所有节点之间的最短路径。
这常用于 IP 网络的路由协议。
nx.draw(G_karate, cmap = plt.get_cmap('rainbow'), with_labels=True, pos=pos)
# 返回对给定节点(源头)0与图中其他节点的最短路径
print(list(nx.single_source_shortest_path(G_karate, source=0)))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 17, 19, 21, 31, 30, 9, 27, 28, 32, 16, 33, 24, 25, 23, 14, 15, 18, 20, 22, 29, 26]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c8bc5I8X-1610329878811)(output_18_1.png)]
所有配对最短路径(All Pairs Shortest Path / APSP)算法是找到所有节点对之间的最短路径。
尽管能够提供相近的结果,但这比为每个节点对调用单源最短路径算法更快。该算法通常可用于确定交通网格的不同分区的流量负载。
nx.draw(G_karate, cmap = plt.get_cmap('rainbow'), with_labels=True, pos=pos)
# 返回所有配对最短路径
all_path_length = list(nx.all_pairs_shortest_path_length(G_karate))
print(all_path_length[0])
(0, {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 10: 1, 11: 1, 12: 1, 13: 1, 17: 1, 19: 1, 21: 1, 31: 1, 30: 2, 9: 2, 27: 2, 28: 2, 32: 2, 16: 2, 33: 2, 24: 2, 25: 2, 23: 3, 14: 3, 15: 3, 18: 3, 20: 3, 22: 3, 29: 3, 26: 3})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9DKbPmKH-1610329878812)(output_20_1.png)]
最小权重生成树(minimum spanning tree)是图(一个树)的一个子图,其用权重和最小的边连接了图中的所有节点。
注意,最小生成树应该用于无向图。
from networkx.algorithms import tree
mst = tree.minimum_spanning_edges(G_karate, algorithm='prim', data=False)
edgelist = list(mst)
edgelist = sorted(edgelist)
G = nx.Graph()#创建空的网络图
G.add_edges_from(edgelist)
plt.figure()
nx.draw(G,pos = pos, node_color = 'b',edge_color = 'r',with_labels = True,font_size =18, node_size =20)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nA20756W-1610329878814)(output_22_0.png)]
社群检测是根据给定的质量指标将节点划分为多个分组。
这通常可用于识别社交社群、客户行为或网页主题。
社区是指一组相连节点的集合。但是,目前关于社群还没有广泛公认的定义,只是社群内的节点应该要密集地相连。
Girvan Newman 算法是一个用于发现社群的常用算法。其通过逐步移除网络内的边来定义社区。我们将居间性称为「边居间性(edge betweenness)」。这是一个正比于穿过该边的节点对之间最短路径的数量的值。
该算法的步骤如下:
from networkx.algorithms import community
import itertools
k = 1
comp = community.girvan_newman(G_karate)
for communities in itertools.islice(comp, k):
print(tuple(sorted(c) for c in communities))
([0, 1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21], [2, 8, 9, 14, 15, 18, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33])
在分层聚类(hierarchical clustering)中,我们构建聚类的层次结构。我们用树状图的形式表示聚类。
其思想是以不同的规模分析社群结构。我们通常自下而上构建树状图。我们从每个节点一个聚类开始,然后合并两个「最近」的节点。
但我们如何衡量聚类是否相近呢?我们使用相似度距离。令 d(i,j) 为 i 和 j 之间的最短路径的长度。
要得到最大连接,在每个步骤,被最短距离分开的两个聚类被组合到一起。相似度距离可用以下示意图阐释
下面回到我们的空手道示例。在应用分层聚类之前,我们需要定义每个节点之间的距离矩阵。
pcc_longueurs=list(nx.all_pairs_shortest_path_length(G_karate))
distances=np.zeros((n,n))# distances[i, j] is the length of the shortest path between i and j
for i in range(n):
for j in range(n):
distances[i, j] = pcc_longueurs[i][1][j]
现在,我们将使用 sklearn 的 AgglomerativeClustering 函数来确定分层聚类。
from sklearn.cluster import AgglomerativeClustering
clustering = AgglomerativeClustering(n_clusters=2,linkage='average',affinity='precomputed').fit_predict(distances)
# 最后,根据聚类结果,用不同颜色绘出所得到的图:
G.add_edges_from(edgelist)
plt.figure()
颜色绘出所得到的图:
G.add_edges_from(edgelist)
plt.figure()
nx.draw(G_karate, node_color = clustering, pos=pos)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZUx5yyIz-1610329878822)(output_28_0.png)]
推荐学习平台:aistudio.baidu.com