GNN入门之路01
此次学习的内容来源于datawhale的6月份组队学习活动,本人由于已经报名的Linux教程的组队学习,所以这个课程没有报上,不过既然是开源学习,没跟上大部队,自己就进行自我学习了,好了,废话少说,下面进入正题。
一、图的表示
首先,我们需要对图的概念进项说明,什么是图呢,在我的理解中图就是对实体和关系的一种表示。比如,小王和小李是朋友,在图的存储结构中,这两个人就是两个实体,而朋友关系可以用一条连结的边表示。
图的定义
∙
\bullet
∙ 一个图被记为
G
=
{
V
,
E
}
\mathcal{G}=\{\mathcal{V}, \mathcal{E}\}
G={V,E},其中
V
=
{
v
1
,
…
,
v
N
}
\mathcal{V}=\left\{v_{1}, \ldots, v_{N}\right\}
V={v1,…,vN}是数量为
N
=
∣
V
∣
N=|\mathcal{V}|
N=∣V∣ 的节点的集合,
E
=
{
e
1
,
…
,
e
M
}
\mathcal{E}=\left\{e_{1}, \ldots, e_{M}\right\}
E={e1,…,eM} 是数量为
M
M
M 的边的集合。
-
图用节点表示实体(entities ),用边表示实体间的关系(relations)。
-
节点和边的信息可以是类别型的(categorical),类别型数据的取值只能是哪一类别。一般称类别型的信息为标签(label)。
-
节点和边的信息可以是数值型的(numeric),数值型数据的取值范围为实数。一般称数值型的信息为属性(attribute)。
-
在图的计算任务中,我们认为,节点一定含有信息(至少含有节点的度的信息),边可能含有信息。
邻接矩阵 -
给定一个图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其对应的邻接矩阵被记为 A ∈ { 0 , 1 } N × N \mathbf{A} \in\{0,1\}^{N \times N} A∈{0,1}N×N。 A i , j = 1 \mathbf{A}_{i, j}=1 Ai,j=1表示存在从节点 v i v_i vi到 v j v_j vj的边,反之表示不存在从节点 v i v_i vi到 v j v_j vj的边。
-
在无向图中,从节点 v i v_i vi到 v j v_j vj的边存在,意味着从节点 v j v_j vj到 v i v_i vi的边也存在。因而无向图的邻接矩阵是对称的。
-
在无权图中,各条边的权重被认为是等价的,即认为各条边的权重为 1 1 1。
-
对于有权图,其对应的邻接矩阵通常被记为 W ∈ { 0 , 1 } N × N \mathbf{W} \in\{0,1\}^{N \times N} W∈{0,1}N×N,其中 W i , j = w i j \mathbf{W}_{i, j}=w_{ij} Wi,j=wij表示从节点 v i v_i vi到 v j v_j vj的边的权重。若边不存在时,边的权重为 0 0 0。
无向图及其邻接矩阵
A
=
(
0
1
0
1
1
1
0
1
0
0
0
1
0
0
1
1
0
0
0
1
1
0
1
1
0
)
\mathbf{A}=\left(\begin{array}{lllll} 0 & 1 & 0 & 1 & 1 \\ 1 & 0 & 1 & 0 & 0 \\ 0 & 1 & 0 & 0 & 1 \\ 1 & 0 & 0 & 0 & 1 \\ 1 & 0 & 1 & 1 & 0 \end{array}\right)
A=⎝⎜⎜⎜⎜⎛0101110100010011000110110⎠⎟⎟⎟⎟⎞
二、图的属性
定义三(节点的度,degree):
-
对于有向有权图,节点 v i v_i vi的出度(out degree)等于从 v i v_i vi出发的边的权重之和,节点 v i v_i vi的入度(in degree)等于从连向 v i v_i vi的边的权重之和。
-
无向图是出度和入度相等的有向图
-
无权图是各边权重为1的有权图,节点 v i v_i vi的出度(out degree)等于从 v i v_i vi出发的边的数量,节点 v i v_i vi的入度(in degree)等于从连向 v i v_i vi的边的数量。
-
节点 v i v_i vi的度记为 d ( v i ) d(v_i) d(vi),入度记为 d i n ( v i ) d_{in}(v_i) din(vi),出度记为 d o u t ( v i ) d_{out}(v_i) dout(vi)。
定义四(邻接节点,neighbors):
-
一个节点的邻接节点就是指直接与其相连的节点,如此节点为 v i v_i vi则记为** N ( v i ) \mathcal{N(v_i)} N(vi)**
-
**节点 v i v_i vi的 k k k跳远的邻接节点(neighbors with k k k-hop)**就是指走k步才能到 v i v_i vi的节点(一个节点的 2 2 2跳远的邻接节点包含了自身)
定义五(行走,walk):
- w a l k ( v 1 , v 2 ) = ( v 1 , e 6 , e 5 , e 4 , e 1 , v 2 ) walk(v_1, v_2) = (v_1, e_6,e_5,e_4,e_1,v_2) walk(v1,v2)=(v1,e6,e5,e4,e1,v2),这是一次“行走”,它是一次从节点 v 1 v_1 v1出发,依次经过边 e 6 , e 5 , e 4 , e 1 e_6,e_5,e_4,e_1 e6,e5,e4,e1,最终到达节点 v 2 v_2 v2的“行走”。
- 下图所示为 w a l k ( v 1 , v 2 ) = ( v 1 , e 6 , e 5 , e 4 , e 1 , v 2 ) walk(v_1, v_2) = (v_1, e_6,e_5,e_4,e_1,v_2) walk(v1,v2)=(v1,e6,e5,e4,e1,v2),其中红色数字标识了边的访问序号。
- 在“行走”中,节点是允许重复的。
定理六:
- 有一图,其邻接矩阵为 A \mathbf{A} A, A n \mathbf{A}^{n} An为邻接矩阵的 n n n次方,那么 A n [ i , j ] \mathbf{A}^{n}[i,j] An[i,j]等于从节点 v i v_i vi到节点 v j v_j vj的长度为 n n n的行走的个数。(也就是,以节点 v i v_i vi为起点,节点 v j v_j vj为终点,长度为 n n n的节点访问方案的数量,节点访问中可以兜圈子重复访问一些节点)
定义七(路径,path):
- “路径”是节点不可重复的“行走”。
定义八(子图,subgraph):
- 有一图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},另有一图 G ′ = { V ′ , E ′ } \mathcal{G}^{\prime}=\{\mathcal{V}^{\prime}, \mathcal{E}^{\prime}\} G′={V′,E′},其中 V ′ ∈ V \mathcal{V}^{\prime} \in \mathcal{V} V′∈V, E ′ ∈ E \mathcal{E}^{\prime} \in \mathcal{E} E′∈E并且 V ′ \mathcal{V}^{\prime} V′不包含 E ′ \mathcal{E}^{\prime} E′中未出现过的节点,那么 G ′ \mathcal{G}^{\prime} G′是 G \mathcal{G} G的子图。(简单的说就是节点和边都是子集,且这两个子集可以组成图)
定义九(连通分量,connected component):
- 给定图 G ′ = { V ′ , E ′ } \mathcal{G}^{\prime}=\{\mathcal{V}^{\prime}, \mathcal{E}^{\prime}\} G′={V′,E′}是图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E}的子图。记属于图 G \mathcal{G} G但不属于 G ′ \mathcal{G}^{\prime} G′图的节点集合记为 V / V ′ \mathcal{V}/\mathcal{V}^{\prime} V/V′ 。如果属于 V ′ \mathcal{V}^{\prime} V′的任意节点对之间存在至少一条路径,但不存在一条边连接属于 V ′ \mathcal{V}^{\prime} V′的节点与属于 V / V ′ \mathcal{V}/\mathcal{V}^{\prime} V/V′的节点,那么图 G ′ \mathcal{G}^{\prime} G′是图 G \mathcal{G} G的连通分量。(简单的说就是劈开了,咔嚓,嘎嘣脆)
左右两边子图都是整图的连通分量。
定义十(连通图,connected graph):
- 当一个图只包含一个连通分量,即其自身,那么该图是一个连通图。(钢铁直男,劈不开,掰不弯)
定义十一(最短路径,shortest path):
-
v
s
,
v
t
∈
V
v_{s}, v_{t} \in \mathcal{V}
vs,vt∈V 是图
G
=
{
V
,
E
}
\mathcal{G}=\{\mathcal{V}, \mathcal{E}\}
G={V,E}上的一对节点,节点对
v
s
,
v
t
∈
V
v_{s}, v_{t} \in \mathcal{V}
vs,vt∈V之间所有路径的集合记为
P
s
t
\mathcal{P}_{\mathrm{st}}
Pst。节点对
v
s
,
v
t
v_{s}, v_{t}
vs,vt之间的最短路径
p
s
t
s
p
p_{\mathrm{s} t}^{\mathrm{sp}}
pstsp为
P
s
t
\mathcal{P}_{\mathrm{st}}
Pst中长度最短的一条路径,其形式化定义为
p s t s p = arg min p ∈ P s t ∣ p ∣ p_{\mathrm{s} t}^{\mathrm{sp}}=\arg \min _{p \in \mathcal{P}_{\mathrm{st}}}|p| pstsp=argp∈Pstmin∣p∣
其中, p p p表示 P s t \mathcal{P}_{\mathrm{st}} Pst中的一条路径, ∣ p ∣ |p| ∣p∣是路径 p p p的长度。(就是所有路径中最短的那个,特短)
定义十二(直径,diameter):
- 给定一个连通图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其直径为其所有节点对之间的最短路径的最大值,形式化定义为
diameter ( G ) = max v s , v t ∈ V min p ∈ P s t ∣ p ∣ \operatorname{diameter}(\mathcal{G})=\max _{v_{s}, v_{t} \in \mathcal{V}} \min _{p \in \mathcal{P}_{s t}}|p| diameter(G)=vs,vt∈Vmaxp∈Pstmin∣p∣
定义十三(拉普拉斯矩阵,Laplacian Matrix):
- 给定一个图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其邻接矩阵为 A A A,其拉普拉斯矩阵定义为 L = D − A \mathbf{L=D-A} L=D−A,其中 D = d i a g ( d ( v 1 ) , ⋯ , d ( v N ) ) \mathbf{D=diag(d(v_1), \cdots, d(v_N))} D=diag(d(v1),⋯,d(vN))。
定义十四(对称归一化的拉普拉斯矩阵,Symmetric normalized Laplacian):
- 给定一个图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其邻接矩阵为 A A A,其规范化的拉普拉斯矩阵定义为
L = D − 1 2 ( D − A ) D − 1 2 = I − D − 1 2 A D − 1 2 \mathbf{L=D^{-\frac{1}{2}}(D-A)D^{-\frac{1}{2}}=I-D^{-\frac{1}{2}}AD^{-\frac{1}{2}}} L=D−21(D−A)D−21=I−D−21AD−21
三、图的种类
同质图(Homogeneous Graph):只有一种类型的节点和一种类型的边的图。
异质图(Heterogeneous Graph):存在多种类型的节点和多种类型的边的图。
二部图(Bipartite Graphs):节点分为两类,只有不同类的节点
之间存在边。
四、图结构数据上的机器学习
- 节点预测:预测节点的类别或某类属性的取值
- 例子:对是否是潜在客户分类、对游戏玩家的消费能力做预测
- 边预测:预测两个节点间是否存在链接
- 例子:Knowledge graph completion、好友推荐、商品推荐
- 图的预测:对不同的图进行分类或预测图的属性
- 例子:分子属性预测
- 节点聚类:检测节点是否形成一个社区
- 例子:社交圈检测
- 其他任务
- 图生成:例如药物发现
- 图演变:例如物理模拟
- ……
五、环境配置与PyG中图与图数据集的表示和使用
1.环境配置
-
用
nvidia-smi
命令查看适合的驱动版本(这里是指在Linux系统上安装了nvidia驱动的情况下,windows下自行搜索配置)
-
安装正确版本的pytorch和cudatoolkit,此处安装1.8.1版本的pytorch和11.1版本的cudatoolkit
conda install pytorch torchvision torchaudio cudatoolkit=11.1 -c pytorch -c nvidia
- 确认是否正确安装,正确的安装应出现下方的结果
$ python -c "import torch; print(torch.__version__)" # 1.8.1 $ python -c "import torch; print(torch.version.cuda)" # 11.1
-
安装正确版本的PyG
pip install torch-scatter -f https://pytorch-geometric.com/whl/torch-1.8.0+cu111.html pip install torch-sparse -f https://pytorch-geometric.com/whl/torch-1.8.0+cu111.html pip install torch-cluster -f https://pytorch-geometric.com/whl/torch-1.8.0+cu111.html pip install torch-spline-conv -f https://pytorch-geometric.com/whl/torch-1.8.0+cu111.html pip install torch-geometric
2.data对象
Data
类的官方文档为torch_geometric.data.Data。
构造函数如下:
class Data(object):
def __init__(self, x=None, edge_index=None, edge_attr=None, y=None, **kwargs):
r"""
Args:
x (Tensor, optional): 节点属性矩阵,大小为`[num_nodes, num_node_features]`
edge_index (LongTensor, optional): 边索引矩阵,大小为`[2, num_edges]`,第0行为尾节点,第1行为头节点,头指向尾
edge_attr (Tensor, optional): 边属性矩阵,大小为`[num_edges, num_edge_features]`
y (Tensor, optional): 节点或图的标签,任意大小(,其实也可以是边的标签)
"""
self.x = x
self.edge_index = edge_index
self.edge_attr = edge_attr
self.y = y
for key, item in kwargs.items():
if key == 'num_nodes':
self.__num_nodes__ = item
else:
self[key] = item
这是一个储存边与节点信息及其相应关系的对象,其中x与edge_attr 分别存储节点与边的属性,而edge_index 存储的就是边与节点的连接关系,y为图的标签。data对象也可以通过字典构建
graph_dict = {
'x': x,
'edge_index': edge_index,
'edge_attr': edge_attr,
'y': y,
'num_nodes': num_nodes,
'other_attr': other_attr
}
graph_data = Data.from_dict(graph_dict)
data对象的其他性质
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
data = dataset[0] # Get the first graph object.
print(data)
print('==============================================================')
# 获取图的一些信息
print(f'Number of nodes: {data.num_nodes}') # 节点数量
print(f'Number of edges: {data.num_edges}') # 边数量
print(f'Number of node features: {data.num_node_features}') # 节点属性的维度
print(f'Number of node features: {data.num_features}') # 同样是节点属性的维度
print(f'Number of edge features: {data.num_edge_features}') # 边属性的维度
print(f'Average node degree: {data.num_edges / data.num_nodes:.2f}') # 平均节点度
print(f'if edge indices are ordered and do not contain duplicate entries.: {data.is_coalesced()}') # 是否边是有序的同时不含有重复的边
print(f'Number of training nodes: {data.train_mask.sum()}') # 用作训练集的节点
print(f'Training node label rate: {int(data.train_mask.sum()) / data.num_nodes:.2f}') # 用作训练集的节点数占比
print(f'Contains isolated nodes: {data.contains_isolated_nodes()}') # 此图是否包含孤立的节点
print(f'Contains self-loops: {data.contains_self_loops()}') # 此图是否包含自环的边
print(f'Is undirected: {data.is_undirected()}') # 此图是否是无向图