networkx与PyG计算度数degree时需避免的坑:自环selfloop和多重边
近日需要统计图的基本性质,在使用
nx
和
PyG
自带的度数计算时,发现他们的底层逻辑是不同的,因此计算结果在一些情况下也会不同。这里做个简单的小结,也避免自己今后踩坑。
先上结论:
networkx
在计算度数的时候,自环selfloop
部分会被计算为2
,多重边只计算一条。PyG
中度数计算不分方向,只和传入index
中每个节点出现的次数有关。- 使用
PyG
中的to_undirected
将有向图转化为无向图后,自环边只有一条,因此自环度数为1
。 - 多重边的度数为相应为
n
。
- 使用
下面分别看一下具体的实例。
networkx
有向图的self-loop
import networkx as nx
DG = nx.DiGraph()
DG.add_edges_from([[0, 0], [0, 1], [1, 2]])
nx.draw(DG, with_labels=True)
分别计算degree
, out_degree
,in_degree
DG.degree(DG.nodes())
>>> DiDegreeView({0: 3, 1: 2, 2: 1}) #节点0的度数为3,因为自环的度数是2
DG.out_degree(DG.nodes())
>>> OutDegreeView({0: 2, 1: 1, 2: 0}) #节点0的出度为2,因为自环提供了出度1
DG.in_degree(DG.nodes())
>>> InDegreeView({0: 1, 1: 1, 2: 1}) #节点0的入度为1,因为自环提供了入度1
我们可以看到节点0
的degree
, out_degree
,in_degree
分别为3, 2, 1
,这是因为在计算度数的时候,有向图中selfloop
提供了2
的度数。同样都是自己指向自己,但一次是自己指向了跟自己相同的邻居,一次是跟自己相同的邻居指向了自己。其中前者是出度,后者是入度。
无向图self-loop
import networkx as nx
G = nx.Graph()
G.add_edges_from([[0, 0], [0, 1], [1, 2]])
nx.draw(G, with_labels=True)
G.degree(G.nodes())
DegreeView({0: 3, 1: 2, 2: 1})
其中节点0
的度数仍然为3
。另外对于无向图,in_degree
和out_degree
方法不再适用。
多重边
import networkx as nx
DG = nx.DiGraph()
DG.add_edges_from([[0, 1], [0,1], [1, 2]])
nx.draw(DG, with_labels=True)
DG.out_degree(DG.nodes())
>>> OutDegreeView({0: 1, 1: 1, 2: 0})
可以看到尽管有两条(0,1)
边,在计算度数的时候,只会被算作一条。这是因为nx
内部存储是以字典形式存储信息的,多重边对应的键是一样的,因此后来传入的信息会覆盖前面的信息。
pytorch geometric
PyG
的度数计算是利用torch
的scatter
,所以是不分方向的,只看你传进去什么样的index
。下面给大家举个例子:
from torch_geometric.utils import degree
import torch
edge_index = torch.tensor([[0, 0], [0, 1], [1, 2]]).T.contiguous() #传入和nx例子中相同的边
edge_index
>>>
tensor([[0, 0, 1],
[0, 1, 2]])
degree(edge_index[0])
>>> tensor([2., 1.]) #传入edge_index[0]计算出度,因为节点2没有出度,所以这里出度的shape只有(2,)
degree(edge_index[0], num_nodes=3)
>>> tensor([2., 1., 0.]) #指定节点个数,避免维度不同
degree(edge_index[1])
>>> degree(edge_index[1]) #传入edge_index[1]计算入度
在PyG
中edge_index[0]
是source
,所以对degree
函数传入edge_index[0]
计算出度。计算的逻辑就是统计index
中每个节点出现的次数。如果有节点没有出度且节点的id
在尾部,可以传入节点的个数,避免返回的结果出现缺失。同理传入edge_index[1]
计算入度。
如果需要计算度数,可以先对有向的edge_index
做无向处理。
from torch_geometric.utils import to_undirected
edge_index = to_undirected(edge_index)
edge_index
>>> tensor([[0, 0, 1, 1, 2], #节点0的自环边只出现一次
[0, 1, 0, 2, 1]])
degree(edge_index[0])
>>> tensor([2., 2., 1.])
可以看出来,to_undirected
并不会重复自环边,所以后期计算自环度数时为1
。
因为计算的degree
只和传入的index
有关,所以重复边会被重复统计。
from torch_geometric.utils import degree
import torch
edge_index = torch.tensor([[0, 1], [0, 1], [1, 2]]).T.contiguous() #传入和上个例子中相同的边
edge_index
>>> tensor([[0, 0, 1],
[1, 1, 2]])
degree(edge_index[0])
>>> tensor([2., 1.])