> 技术文档 > 【GNN】第二章:图论、及其工具NetworkX

【GNN】第二章:图论、及其工具NetworkX

离散数学的一个重要分支叫图论,在实际应用中图论也是运筹学的一个经典而重要的分支,是交通运输、经济管理和工业工程等地方研究的基础工具。所以我们需要先了解一下前人对图数据的研究成果:图论。

一、图的定义

1、定义1——图中的节点和边的表示
图(Graph)是一种表示对象之间关系的基本结构。具体的,我们用G=(V, E) 来表示图,其中V表示节点Nodes(也叫‘实体entities’) 的集合、E表示边Edges(也叫‘关系’relations) 的集合:

(1)在深度学习中,节点和边本身是要embedding成一个特征向量的。这是自然喽,如果不是特征向量,神经网络怎么计算嘛。所以,一个图有三部分特征

一是,点特征。点是由特征向量来表示的。
二是,边特征。边也是用特征向量来表示的。比如如果要预测两个点之间的关系(朋友关系、亲人关系、同事关系)就是这两个点的边的特征。所以边也是一个向量。
三是,图特征。就是上图的U。图是一个全局的概念。图是由所有的点和所有的边组成的。所以比如所有的点的均值就可以表示图的特征。所有的边的特征的均值就也是图的特征。

(2)一般情况下,节点和边还要有自己的标签

节点和边的信息可以是类别型的(categorical),类别型数据的取值只能是哪一类别。一般称类别型的信息为标签(label)。
节点和边的信息可以是数值型的(numeric),数值型数据的取值范围为实数。一般称数值型的信息为属性(attribute)。

为什么要有标签?有标签就可以分类或回归了呀。所以,图神经网络是可以完成不同的任务
比如有的任务是求点的(比如对点进行分类、回归等任务),有的是求边的(比如对边进行分类、回归等任务),有的还是求全局的就是Graph级别的任务(比如设计分子结构等任务)。
为什么“一般情况下”有标签?意思就是可以只有部分标签,就是可以仅仅是一些重要的节点和重要的边才有标签,就是图神经网络可以进行半监督学习的,不是必须所有的节点和边都需要标签的。但是当然还是标签越多越好嘛。也所以后面讲数据集的时候,你会碰到mask,mask就是在求loss时mask住那些没有标签的节点。

但是无论数据多么随意,我们使用图神经网络的目的就是整合特征,或者说是重构特征。为什么要重构特征?因为每个节点都不是孤立的,它都是和一些别的节点是有联系的,别的节点也是和别别的节点有联系的。所以一个图中的某个节点的特征变化是要受到图中其他所有节点的影响的。我们的图神经网络就是拟合(或者说‘捕获’)节点之间的相互影响关系,从而重构出最合适的特征向量来表示这个节点。

2、定义2——图的拓扑结构:邻接矩阵(Adjacency Matrix)、邻接表(Adjacency List)
一个图数据,所有的节点特征、节点标签、边特征、边标签就可以表示一个图数据了嘛?显然是不够的!图的拓扑结构也是有决定作用的:

上面的两个图,它们同样都是有俩黄、俩蓝、俩绿,共6个节点,因此它们的节点信息相同;假如边只表示一种关系,那么它们的边信息也相同。但这两个图显然是不一样的图,因为它们的拓扑结构不一样!所以图的拓扑结构同样也包含了这个图的重要信息,这些拓扑信息后面还要辅助深度学习网络计算节点表征的,就是上面提到的是要辅助“重构特征”的。所以我们还得把图的拓扑结构也用数字表示出来。

那如何表示一个图的拓扑结构呢?其实有很多种表示方法,最常见的是用邻接矩阵或者邻接表来表示。

表示图的拓扑结构就是用规则数据表示图中哪两个节点之间有边。就是用矩阵或者列表的形式告诉神经网络节点之间的关系,方便神经网络学习这些节点之间的影响规律。

最常见的邻接矩阵是NxN矩阵的形式,有时是coordinate format(COO Format)形式。这两种形式都叫邻接矩阵。
最常见的邻接表是以 (source, target)对儿 的形式存储的,是一种顺序分配和链式分配相结合的存储结构。邻接表也有2种表示形式。

下面用图直观展示一下邻接矩阵和邻接表:

为什么如此五花八门?因为我们不是手动计算啊,都是计算机帮我们计算,数据结构就得符合计算机底层的计算原理。所以每种表示形式都无好坏优劣之分,只有合适不合适之分。

(1)上图是一个无向图(Undirected Graph),就是节点v1到节点v2的边存在,就意味着从节点v2到v1的边也是存在的。所以无向图的NxN邻接矩阵是对称的,有向图(Directed Graph)的邻接矩阵是不对称的

(2)上图还是一个无权图,就是各条边的权重被认为是等价的,即认为各条边的权重都是1。所以如果是一个有权图,那它对应的NxN邻接矩阵Aij=wij,就是从节点vi到vj的边的权重,若边不存在时,边的权重为0。

(3)邻接矩阵(邻接表)作为图数据的重要信息,是要作为输入一并传入GNN模型的,GNN在计算节点和边的特征时,邻接矩阵(邻接表)是要参与计算的,具体怎么参与计算,后面讲图深度学习网络架构原理时会详细展开讲。

二、图的基本属性

1、节点的度(Degree)
节点的度就是指一个节点有多少条边和它连接。但是在有权有向图中,边就不仅有方向还有权重,所以一个节点的(Degree)就被细分为这个节点的出度(out degree)和入度(in degree)。
显然,出度(dout(vi))就是从节点vi出发的边的权重之和入度(din(vi))就是所有连向节点vi的边的权重之和

无向图是有向图的特殊情况,所以无向图的节点的出度和入度相等。
无权图又是有权图的特殊情况,无权图的权重为1,所以无权图的节点vi的出度=从vi出发的边的数量,节点vi的入度=所有连向vi的边的数量。

下面是计算一个图的节点的度矩阵(Degree Matrix):

度矩阵也非常非常重要,以后我们讲网络数据传播时,度矩阵也是要和邻接矩阵一样,二者一起要参与节点表征的计算的。

2、邻接节点(neighbors)
与节点vi直接相连的节点,就是节点vi的邻接节点。比如上图的节点A的邻接节点就是E,而节点E的邻接节点是ABCD四个节点。
vi节点k跳远的邻接节点(neighbors with k-hop),指的是到节点vi走k步的节点(一个节点的2跳远的邻接节点包含了自身)。

3、行走(walk)、路径(path)、最短路径(shortest path)

4、子图(subgraph)、连通分量(connected component)、连通图(connected graph)、直径(diameter)

5、拉普拉斯矩阵 (Laplacian Matrix)

说明:上面图论的相关概念仅仅是非常非常少的一部分,也是最常用的一些概念。如果你想知道的更多,可以参考: https://cse.msu.edu/~mayao4/dlg_book/chapters/chapter2.pdf
图论的知识是非常深也非常难的,除了概念还有很多非常经典、非常优秀的算法,比如关键路径、拓扑排序、聚集系数、标签传播、概率图模型等,也都是图论的入门算法,有感兴趣的自己找资料学吧,我这里只是写一点和特征工程相关的知识点。

三、图分析工具:NetworkX

NetworkX是一个图论和网络分析Python软件包,提供丰富的图数据结构和算法,可以创建、操作和分析各种类型的图。这个工具直接pip install networkx就安装好了。

NetworkX可以创建各种类型的图对象,比如无向图、有向图、有权图等;
NetworkX可以设置节点和边的属性、获取图的邻接矩阵、度、平均度、度分布、度矩阵等;
NetworkX可以进行图分析,因为它内置了很多图论算法,比如图的遍历、最短路径、社区发现、中心性等;
NetworkX可以绘制图对象,支持图数据的可视化,经常用于绘制网络结构。


这是networkx的官网:NetworkX — NetworkX documentation 官网上有很多小例子,你可以自己看官网就可以。

(一)networkx中的内置数据

1、networkx中的内置图示例




2、可视化
由于PyG是进行图数据计算的,没有可视化功能,所以后期我们还经常要用networkx对图进行可视化,所以这里单独补充一些可视化的代码:

import networkx as nxG = nx.random_k_out_graph(10, 3, 0.5, seed=0)pos = nx.spring_layout(G, seed=0)#nx.draw(G, pos, with_labels=True)#节点大小node_sizes = [50 + 10*i for i in range(len(G))]#节点颜色m = G.number_of_edges() #m=30edge_colors = range(2, m+2)#节点透明度edge_alphas = [(5+i)/(m+4) for i in range(m)]#配色方案cmap = plt.cm.plasma#cmap = plt.cm.Bluesplt.figure(figsize=(6,4))nodes = nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color=\'indigo\')edges = nx.draw_networkx_edges(G, pos, node_size=node_sizes, arrowstyle=\"->\", arrowsize=20, edge_color=edge_colors, edge_cmap=cmap, width=2)#设置连接的透明度for i in range(m): edges[i].set_alpha(edge_alphas[i])#调色图例#pc = mpl.collections.PatchCollection(edges, cmap=cmap)#pc.set_array(edge_colors)#plt.colorbar(pc)plt.colorbar(nodes)ax = plt.gca()ax.set_axis_off()plt.show()


说明,networkx的可视化效果是按照:节点、边、节点在画布的排布,分层设置的。就是节点设置节点自己的参数,边设置边的参数,排布设置排布的参数。

3、networkx内置的数据集
这里我只是挑两个常用的展示一下:

(二)创建自己的图数据

这里我就举几个使用例子来展示一下:

import networkx as nx #导包#创建一个无向图G = nx.Graph([(\'li\', \'yuan\'), (\'hi\'), (6, \'nihao\'), (\'yuan\', 6)], attr=\"Undirected Graph\") G  #G是个图对象type(G) #G的数据类型为networkx.classes.graph.Graph G.graph #查看图属性G.graph[\'attr\']=\'mygraph\' #还可以修改图属性G.graph[\'name\']=\'Undirected_Graph\' #也可以这样添加图属性G.graphG.nodes #获取图上的所有节点#colors = [\'r\', \'r\', \'b\', \'b\', \'y\', \'g\'] #根据节点设置每个节点的颜色colors = [1, 1, 2, 2, 3, 4] #或者随机设置节点的颜色nx.draw(G, with_labels=True, font_weight=\'bold\', node_size=2000, node_color=colors, pos=nx.circular_layout(G))#circular_layout random_layout shell_layout spring_layout

import networkx as nx #导包#创建一个空的有向图G = nx.DiGraph() G  type(G) G.graph #在图上添加节点G.add_node(\'hello\') #添加单个节点G.add_node(6, attr1=\'age\', attr2=\'height\', attr3=\'female\') #添加单个节点,并且设置这个节点的属性G.add_nodes_from([\'ni\', \'hao\']) #一次添加多个节点G.add_nodes_from(\'hi\') #一次添加多个节点#在图上添加边G.add_edge(\'li\',\'yuan\', attr1=\'name\', value=1.5) G.add_edges_from([(\'ni\', \'hao\'), (\'h\', \'i\')])G.nodes #获取图上的所有节点G.edges #获取图上的所有边colors_nodes = [\'r\', \'b\', \'y\', \'y\', \'g\', \'g\', \'pink\', \'pink\'] #根据节点设置每个节点的颜色colors_edges = [1, 2, 3] #根据边设置边的颜色nx.draw(G, with_labels=True, font_weight=\'bold\', node_size=2000, node_color=colors_nodes, edge_color=colors_edges, pos=nx.circular_layout(G))

import networkx as nx #导包#创建一个空的有向图G = nx.DiGraph() #在图上添加节点G.add_node(\'hello\') #添加单个节点G.add_node(6, attr1=\'age\', attr2=\'height\', attr3=\'female\') #添加单个节点,并且设置这个节点的属性G.add_nodes_from([\'ni\', \'hao\', \'li\', \'yuan\']) #一次添加多个节点#在图上添加边G.add_edge(\'li\',\'yuan\', length=1.5, attr1=\'name\') #自己定义length属性为边的权重G.add_edges_from([(\'ni\', \'hao\', {\'length\':2.0, \'attr1\':10}), (\'hello\', 6, {\'length\':0.6})]) #添加多个边,并且每个边都有属性#检查图上的节点和边属性G.nodes #获取图上的所有节点G.nodes[6] #查看节点6的属性G.number_of_nodes() #获取图上所有节点的个数G.edges #获取图上的所有边G[\'li\'][\'yuan\'] #获取节点li和节点yuan之间的边的属性G.number_of_edges() #获取边的个数colors_nodes = [\'b\', \'b\', \'g\', \'g\', \'r\', \'r\'] #根据节点设置每个节点的颜色colors_edges = [1, 2, 3] #根据边设置边的颜色nx.draw(G, with_labels=True, font_weight=\'bold\', node_size=2000, node_color=colors_nodes, edge_color=colors_edges, pos=nx.circular_layout(G))nx.draw_networkx_edge_labels(G, edge_labels=nx.get_edge_attributes(G, \'length\'), pos=nx.circular_layout(G), font_weight=\'bold\')

import networkx as nx #导包#创建一个空的有向图G = nx.DiGraph([(\'Hi\',\'Li\'), (\'Li\', \'Yuan\'), (\'Yuan\', \'yuan\'), (\'yuan\', \'!\'),  (\'Li\', \'Ming\'), (\'Ming\', \'!\')]) G.nodesnode_color = [1, 2, 3, 3, 1, 3]nx.draw(G, with_labels=True, font_weight=\'bold\', node_size=2000, pos=nx.spring_layout(G), node_color=node_color)#获取图的邻接矩阵adj_matrix = nx.adjacency_matrix(G)adj_matrixadj_matrix.toarray()#获取图的度矩阵degrees = G.degree()dict(degrees)degree_matrix = np.diag(list(dict(degrees).values()))degree_matrixout_degree = G.out_degree() #出度dict(out_degree)in_degree = G.in_degree() #入度dict(in_degree)#获取图的拉普拉斯矩阵(Laplacian Matrix)=度矩阵-邻接矩阵L = degree_matrix - adj_matrix.toarray()L#查找两个节点之间的最短路径path=nx.shortest_path(G, source=\'Hi\', target=\'!\')path#查找一个节点的邻接节点neighbors = list(G.neighbors(\'Li\'))neighbors
(三)图论中的基本算法

这里我就大概展示一下主要内容,每个算法的原理、计算公式、优缺点、使用场景等等会在第三章特征工程中详细展开讲。
1、使用NetWorkX进行数据挖掘

2、节点层面的数据挖掘

def my_draw(G, pos, measures, measure_name): import matplotlib.colors as mcolors nodes = nx.draw_networkx_nodes(G, pos, node_size=250, cmap=plt.cm.plasma, node_color=list(measures.values()), nodelist=measures.keys()) nodes.set_norm(mcolors.SymLogNorm(linthresh=0.01, linscale=1, base=10)) #labels = nx.draw_networkx_labels(G, pos) edges = nx.draw_networkx_edges(G, pos) #plt.figure(figsize=(10, 8)) plt.title(measure_name) plt.colorbar(nodes) plt.axis(\'off\') plt.show()

3、聚集情况的挖掘

4、特征值分解

说明:这里仅仅是展示一下都有哪些算法,在我的 【GNN】第三章:传统图机器学习中的特征工程_使用 data 类创建图数据对象-CSDN博客 这篇博文中有每种算法的详细解读。

4、举一个交通网络优化的小案例:提升城市交通效率的路径优化
假设我们有一个城市的交通网络,由多个路口(节点)和道路(边)组成。我们的目标是从城市的某个起点(比如A点)到达某个终点(比如B点),我们希望:
一是,找到最短路径:从A到B的最短路径。
二是,找到最大流路径:在交通高峰期间,如何最大化从A到B的车辆通过量。
三是,避免拥堵:找到一条尽量避开拥堵区域的路径。

import networkx as nx# 创建一个有向图来表示交通网络G = nx.DiGraph() # 添加节点(路口)nodes = [\'A\', \'B\', \'C\', \'D\', \'E\', \'F\', \'G\', \'H\']G.add_nodes_from(nodes)# 添加带权重的边(道路)及其通行能力edges = [(\'A\', \'B\', {\'weight\': 2, \'capacity\': 100}), #就是给边设置属性,一个属性是weight,一个属性是capacity (\'A\', \'C\', {\'weight\': 5, \'capacity\': 50}), #weight你可以理解为大家走这条路的“平均用时” (\'B\', \'C\', {\'weight\': 2, \'capacity\': 100}), #capacity你可以理解为这条路的“最大通行量” (\'B\', \'D\', {\'weight\': 4, \'capacity\': 70}), (\'C\', \'E\', {\'weight\': 1, \'capacity\': 120}), (\'D\', \'F\', {\'weight\': 3, \'capacity\': 80}), (\'E\', \'F\', {\'weight\': 1, \'capacity\': 150}), (\'F\', \'G\', {\'weight\': 2, \'capacity\': 90}), (\'E\', \'H\', {\'weight\': 3, \'capacity\': 60}), (\'H\', \'G\', {\'weight\': 2, \'capacity\': 70}),]G.add_edges_from(edges)# 可视化交通网络plt.rcParams[\'font.sans-serif\'] = [\'Microsoft YaHei\'] #这行代码是用来让图上显示汉字的plt.figure(figsize=(10, 6)) #搞一个画布pos=nx.spring_layout(G) #这种模式是随机的,所以别让它运行两次!nx.draw(G, with_labels=True, node_size=1500, font_size=20, font_weight=\'bold\', pos=pos)nx.draw_networkx_edge_labels(G, pos=pos, font_size=15, edge_labels={(i,j): f\"w:{k[\'weight\']}, c:{k[\'capacity\']}\" for i,j,k in G.edges(data=True)}) #这个参数只接收字典的形式 G.edges(data=True)plt.title(\"城市交通网络图\") #弄一个标题plt.show()#1、基于权重,也就是基于时间,找到从A到G的最短路径 shortest_path = nx.shortest_path(G, source=\'A\', target=\'G\', weight=\'weight\')shortest_path_length = nx.shortest_path_length(G, source=\'A\', target=\'G\', weight=\'weight\') #这是最短路径用时shortest_path, shortest_path_length#2、找到最大流量路径max_flow_value, max_flow_dict = nx.maximum_flow(G, \'A\', \'G\',capacity=\'capacity\')max_flow_value, max_flow_dict#3、避免拥堵的路径(通过增加某些边的权重来模拟拥堵路径)# 我们假设边 (\'B\', \'C\') 和 (\'E\', \'F\') 在高峰期很拥堵,因此增加它们的权重G[\'B\'][\'C\'][\'weight\'] += 10G[\'E\'][\'F\'][\'weight\'] += 10# 重新计算最短路径congestion_avoidance_path = nx.shortest_path(G, source=\'A\', target=\'G\', weight=\'weight\')congestion_avoidance_path_length = nx.shortest_path_length(G, source=\'A\', target=\'G\', weight=\'weight\')congestion_avoidance_path, congestion_avoidance_path_length

在没有深度学习之前,人们已经用图论中的定义定理解决实际生活中的很多问题,比如好友推荐、商品推荐、潜在客户分类、金融领域的反欺诈、消费能力预测、社交圈检测等。所以图论中基础算法以及networkx工具是我们进行图模型学习的基础。将图深度学习和传统图论中的解决技巧相互补充,才能游刃有余的解决生活中的实际问题。

(四)用networkx导入其他数据集

中文开放知识图谱:欢迎 - 开放知识图谱 这里面有很多公开数据集,自己可以找找看。

例子:中国四大名著人物关系知识图谱和OWL本体:中国四大名著人物关系知识图谱和OWL本体 - 数据集 - 开放知识图谱


说明:不管是从三元连接表还是邻接表导入图都是可以的,图信息都是一样的。

最后:networkx这个工具包是非常强大的,经常用于复杂网络拓扑结构统计指标计算、典型复杂网络建模(随机图、小世界、无标度等)以及复杂网络可视化的方法等。我上面写的只是九牛一毛,仅是一个入门级别的了解,而且相关知识真是非常非常的多,我也没有理出来很好的一个思路来说清楚。但是有些东西你只要知道是存在的就可以了,不需要记忆,用的时候拿来当个模板,用于自己的数据就可以了。所以这里我再放一个别人的链接:三、NetworkX工具包实战2——可视化【CS224W】(Datawhale组队学习)_networkx可视化-CSDN博客 这是我自己学习过程中参考的我认为比较有用的资料。这里面有几个例子有代码,我们可以之间拿来跑自己的数据。还有networkx官网也有几个例子,数据集以及相关代码都非常全,自己可以下载运行试试。