DataWhale组队学习——GNN(2)
- 这里是DataWhale社区的21年6月组队学习之图神经网络的相关笔记!
- 学习资料与代码:https://github.com/datawhalechina/team-learning-nlp/blob/master/GNN
目录:(待更新) - 0.导引
- 1.简单图论知识与PyG库初探
简单图论知识与PyG库初探
一、简单图论知识
1.1图的基本定义
- 一个图可被记为一个三元组 G = { V , E , Φ } \mathcal{G}=\{\mathcal{V}, \mathcal{E}, \mathit{\Phi}\} 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 的边的集合, Φ \mathit{\Phi} Φ 为边集到节点偶对的关系集,或为邻接矩阵。我们也可以将图简记为: G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E}。下图我们给出了一有5个结点和6条边的图,其中的边 e 3 e_{3} e3 可记为 e 3 , 5 e_{3,5} e3,5,或 ( v 3 , v 5 ) \left(v_3,v_5\right) (v3,v5)。
![](https://i-blog.csdnimg.cn/blog_migrate/2ea32a60c9a71c2e0f9e23aaecb281d3.png)
- 图深度学习中,用节点表示实体(entities ),用边表示实体间的关系(relations)。
- 节点和边的信息可以是类别型的(categorical),类别型数据的取值只能是哪一类别。一般称类别型的信息为标签(label)。
- 节点和边的信息可以是数值型的(numeric),数值型数据的取值范围为实数。一般称数值型的信息为属性(attribute)。
- 大部分情况中,节点含有信息,边可能含有信息。
1.2图的基本类型
- 有向图与无向图
如果图中边存在方向性,则称这样的边为有向边,用尖括号表示, e i , j = < v i , v j > \mathcal{e_{i,j}}=\left<v_i,v_j\right> ei,j=⟨vi,vj⟩;反之则为无向边,用圆括号表示, e i , j = ( v i , v j ) \mathcal{e_{i,j}}=\left(v_i,v_j\right) ei,j=(vi,vj)。 在有向边 < v i , v j > \left<v_i,v_j\right> ⟨vi,vj⟩中, v i v_{i} vi是边的起点, v j v_{j} vj是边的终点;而无向边 ( v i , v j ) \left(v_i,v_j\right) (vi,vj)可认为是对称的, v i v_{i} vi, v j v_{j} vj称为端点。 - 非加权图与加权图(赋权图)
有时候图中每条边都有一实数与之对应,我们称之为加权图;非加权图与之相反,可以认为其各边的权重是相同的。 - 同质图与异质图
同质图(Homogeneous Graph):只有一种类型的节点和一种类型的边的图。
异质图(Heterogeneous Graph):存在多种类型的节点和多种类型的边的图。
![](https://i-blog.csdnimg.cn/blog_migrate/b9f330e9dd24eeae47741b84bbdf7c4d.png)
- 二部图(二分图)
二分图(Bipartite Graphs):节点分为两类,只有不同类的节点之间存在边。
![](https://i-blog.csdnimg.cn/blog_migrate/565352be36e7c825f5ecf38bfb600b41.png)
- 多重图与线图
多重图:给定一图 G \mathcal{G} G,对 G \mathcal{G} G中所有边,若存在有两条或两条以上的边同起点、终点,则称 G \mathcal{G} G为多重图。
线图:给定一图 G \mathcal{G} G,对 G \mathcal{G} G中所有边,若不存在有两条或两条以上的边同起点、终点,则称 G \mathcal{G} G为线图。特别地,不含自回路的线图称为简单图。
1.3图的连通性
-
邻接节点
若存在一条边连接节点 v i v_{i} vi与 v j v_{j} vj,则称二者互为邻接节点。 -
节点的度
1.无向图
考虑无向图中的节点 v i v_{i} vi,定义 v i v_{i} vi的度(degree)为以 v i v_{i} vi为端点的边的数目,记为 d e g ( v i ) deg\left(v_i\right) deg(vi)。显然地,有:
d e g ( v i ) = v i 的 邻 接 节 点 数 deg\left(v_i\right)=v_{i}的邻接节点数 deg(vi)=vi的邻接节点数
2.有向图
对于有向图中的节点 v i v_{i} vi,度相关定义如下:
v i v_{i} vi的出度 d e g + ( v i ) deg^{+}\left(v_i\right) deg+(vi):以 v i v_{i} vi为起点的有向边数目;
v i v_{i} vi的入度 d e g − ( v i ) deg^{-}\left(v_i\right) deg−(vi):以 v i v_{i} vi为终点的有向边数目;
则 v i v_{i} vi的度 d e g ( v i ) deg\left(v_i\right) deg(vi)= d e g + ( v i ) deg^{+}\left(v_i\right) deg+(vi)+ d e g − ( v i ) deg^{-}\left(v_i\right) deg−(vi)。
3.握手定理
对于任意图,有如下定理:
∑ v i ∈ V d e g ( v i ) = 2 M \sum_{v_i\in\mathcal{V}}deg\left(v_i\right)=2M vi∈V∑deg(vi)=2M
其中 M M M为图的边数。
该定理也可简记为:节点度数之和等于边数的两倍。 -
节点的可达
一图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},节点 v i 、 v j ∈ V v_{i}、v_{j}\in\mathcal{V} vi、vj∈V,若从 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到自身可达。 -
无向图的连通性
在无向图 G \mathcal{G} G中,若任意两节点可达,则称 G \mathcal{G} G是可达的。 -
有向图的连通性
在有向图 G \mathcal{G} G中:
1. 对于任意节点对,若其互相可达,则称 G \mathcal{G} G是强连通的;
2. 对于任意节点对,若至少从一个节点到另一节点可达,则称 G \mathcal{G} G是单向连通的;
3. 若 G \mathcal{G} G的底图(将 G \mathcal{G} G转换为无向图)是连通的,则称 G \mathcal{G} G是弱连通的。 -
子图
图 G ′ = { V ′ , E ′ } \mathcal{G^{'}}=\{\mathcal{V^{'}}, \mathcal{E^{'}}\} G′={V′,E′}与图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E}有如下关系: V ′ ⊆ V \mathcal{V^{'}}\subseteq\mathcal{V} V′⊆V, E ′ ⊆ E \mathcal{E^{'}}\subseteq\mathcal{E} E′⊆E,则称 G \mathcal{G} G为图 G ′ \mathcal{G^{'}} G′的子图。 -
连通分量(极大连通子图)
给定一无向图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E}与其子图 G ′ = { V ′ , E ′ } \mathcal{G^{'}}=\{\mathcal{V^{'}}, \mathcal{E^{'}}\} G′={V′,E′}。若满足:
1. G ′ \mathcal{G^{'}} G′是连通的;
2. 在 G \mathcal{G} G中不存在包含 G ′ \mathcal{G^{'}} G′的更大子图 G ′ ′ \mathcal{G^{''}} G′′是连通的。
那么我们称 G ′ \mathcal{G^{'}} G′是 G \mathcal{G} G的连通分量,或极大连通子图。
类似地,在有向图中,我们也可以定义强连通分量、单向连通分量与弱连通分量。
1.4距离
- 距离
v i , v j ∈ V v_{i}, v_{j} \in \mathcal{V} vi,vj∈V 是图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E}上的一对结点, v i , v j v_{i}, v_{j} vi,vj之间所有路径的集合记为 P i j \mathcal{P}_{ij} Pij。
称结点对 v i , v j v_{i}, v_{j} vi,vj之间的最短路径称为 v i v_{i} vi到 v j v_{j} vj的距离,记为 d ( v i , v j ) d\left(v_i,v_j\right) d(vi,vj)。
即:
d ( v i , v j ) = arg min p ∈ P i j ∣ p ∣ d\left(v_i,v_j\right)=\arg \min _{p \in \mathcal{P}_{ij}}|p| d(vi,vj)=argp∈Pijmin∣p∣
其中, p p p表示 P i j \mathcal{P}_{ij} Pij中的一条路径, ∣ p ∣ |p| ∣p∣是路径 p p p的长度。
- 直径
给定一个连通图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其直径为其所有结点对之间的最短路径的最大值,形式化定义为:
diameter ( G ) = max v i , v j ∈ V min p ∈ P i j ∣ p ∣ \operatorname{diameter}(\mathcal{G})=\max _{v_{i}, v_{j} \in \mathcal{V}} \min _{p \in \mathcal{P}_{ij}}|p| diameter(G)=vi,vj∈Vmaxp∈Pijmin∣p∣
1.5图的矩阵表示
- 邻接矩阵
给定一图 G = { V , E } \mathcal{G}=\{\mathcal{V}, \mathcal{E}\} G={V,E},其对应的邻接矩阵被记为 A ( G ) = ( a i j ) N × N A\left(\mathcal{G}\right)=\left(a_{ij}\right)_{N\times N} A(G)=(aij)N×N。
其中:
a i j = { 1 [ v i , v j ] ∈ E 0 [ v i , v j ] ∉ E , i , j = 1 , 2 , … , N a_{ij}=\begin{cases} 1 & \left[v_i,v_j\right]\in\mathcal{E} \\ 0 & \left[v_i,v_j\right]\notin\mathcal{E} \end{cases},i,j=1,2,…,N aij={10[vi,vj]∈E[vi,vj]∈/E,i,j=1,2,…,N
相应地,在赋权图中也有类似定义:
a
i
j
=
{
W
(
v
i
,
v
j
)
[
v
i
,
v
j
]
∈
E
0
[
v
i
,
v
j
]
∉
E
,
i
,
j
=
1
,
2
,
…
,
N
a_{ij}=\begin{cases} W\left(v_i,v_j\right) & \left[v_i,v_j\right]\in\mathcal{E} \\ 0 & \left[v_i,v_j\right]\notin\mathcal{E} \end{cases},i,j=1,2,…,N
aij={W(vi,vj)0[vi,vj]∈E[vi,vj]∈/E,i,j=1,2,…,N
其中
W
(
v
i
,
v
j
)
W\left(v_i,v_j\right)
W(vi,vj)为边
[
v
i
,
v
j
]
\left[v_i,v_j\right]
[vi,vj]的权重。
例如上文中图1则为一个无向无权图:
![](https://i-blog.csdnimg.cn/blog_migrate/2ea32a60c9a71c2e0f9e23aaecb281d3.png)
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⎠⎟⎟⎟⎟⎞
- 邻接矩阵相关运算的含义
给定一有向线图 G \mathcal{G} G,其邻接矩阵为 A A A。
1. A T A^T AT:图 G \mathcal{G} G中所有边反向。
2. A A T = ( b i j ) N × N AA^T=\left(b_{ij}\right)_{N\times N} AAT=(bij)N×N,其中:
b i j = ∑ k = 1 N a i k ⋅ a j k b_{ij}=\sum_{k=1}^{N}a_{ik}\cdot a_{jk} bij=k=1∑Naik⋅ajk
当
a
i
k
⋅
a
j
k
=
1
a_{ik}\cdot a_{jk}=1
aik⋅ajk=1时,有
a
i
k
=
1
a_{ik}=1
aik=1且
a
j
k
=
1
a_{jk}=1
ajk=1,亦即
[
v
i
,
v
k
]
∈
E
\left[v_i,v_k\right]\in\mathcal{E}
[vi,vk]∈E且
[
v
j
,
v
k
]
∈
E
\left[v_j,v_k\right]\in\mathcal{E}
[vj,vk]∈E。
所以,
b
i
j
b_{ij}
bij表示从
v
i
,
v
j
v_{i}, v_{j}
vi,vj引出的边同时终止于一点的数目;
b
i
i
b_{ii}
bii表示节点
v
i
v_{i}
vi的出度。
3.
A
m
=
(
b
i
j
)
N
×
N
A^m=\left(b_{ij}\right)_{N\times N}
Am=(bij)N×N
b
i
j
b_{ij}
bij表示从
v
i
v_{i}
vi到
v
j
v_{j}
vj,有
b
i
j
b_{ij}
bij条长为
m
m
m的路径。
图论的基本知识就先介绍到这里,稍微复杂的知识如图卷积等到遇到时再介绍。
图论的相关知识推荐阅读:
1.《Deep Learning on Graphs》
2.《深入浅出图神经网络》刘忠雨等,机械工业出版社
图论教材:
1.《图论导引》Douglas B.West,机械工业出版社
2.《图论与网络流理论》高随祥,高等教出版社
二、PyG库初探
本文环境:python3.8+CUDA11.1+Pytorch1.8.0,IDE:Pycharm
PyG(PyTorch Geometric Library)库是一个基于PyTorch的用于处理不规则数据(比如图)的库,或者说是一个用于在图等数据上快速实现表征学习的框架。它有出色的运行速度,还集成了很多论文中提出的方法(GCN,SGC,GAT等)和常用数据集。
2.1PyG内置数据集
我们通过PyG内置的一数据集Karate Club Dataset来初步探索图数据。
Karate Club数据集官方文档
原论文:《An Information Flow Model for Conflict and Fission in Small Groups》
![](https://i-blog.csdnimg.cn/blog_migrate/bd173c29daf13a2c5eacc9cdbf5b0bba.png)
该数据集描述了一个空手道俱乐部会员的社交关系,以34名会员作为节点,如果两位会员在俱乐部之外仍保持社交关系,则在节点间增加一条边。在社会学家Zachary收集数据的过程中,管理人员 John A 和 教练 Mr. Hi之间产生了冲突,会员们选择了站队,一半会员跟随 Mr. Hi 成立了新俱乐部,剩下一半会员找了新教练或退出了俱乐部。数据集提出了一个图节点分类的任务,每个节点具有一个34维的特征向量。节点类型class共有4类,分别代表会员所属的社区community。
数据集加载:
from torch_geometric.datasets import KarateClub
dataset = KarateClub()
print(f'Dataset: {dataset}:')
print('======================')
print(f'Number of graphs: {len(dataset)}')
print(f'Number of features: {dataset.num_features}')
print(f'Number of classes: {dataset.num_classes}')
结果:
Dataset: KarateClub():
======================
Number of graphs: 1
Number of features: 34
Number of classes: 4
2.2Data类
Data类包含于torch_geometric.data模块中,为图形化数据的存储类。我们还是通过一个简单的图数据来说明,在这个图里有4个节点, v 1 v_1 v1, v 2 v_2 v2, v 3 v_3 v3, v 4 v_4 v4,每一个都带有一个2维的特征向量,和一个标签y,代表这个节点属于哪一类。
![](https://i-blog.csdnimg.cn/blog_migrate/ae1bfd439bf7fed2c0947d0e1bcbe79d.jpeg)
图源:图神经网络之神器——PyTorch Geometric 上手 & 实战
- attributes
1. x (Tensor, optional) — 节点特征矩阵,大小为: [num_nodes, num_node_features]
。对于我们的数据,则x应为:
x = torch.tensor([[2,1],[5,6],[3,7],[12,0]],dtype=torch.float)
2. edge_index (LongTensor, optional) — 边索引矩阵,以COO方式存储的图节点连接信息。大小为:[2, num_edges]
。
COO(coordinate format)是一种存储稀疏矩阵的格式。仍以上述数据为例,原始邻接矩阵应为:
A
=
[
0
1
1
0
1
0
0
0
0
0
0
1
0
1
0
0
]
A=\begin{matrix} \left[\begin{array}{r} 0 & 1 & 1 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 \\ 0 & 1 & 0 & 0 \end{array}\right] \end{matrix}
A=⎣⎢⎢⎡0100100110000010⎦⎥⎥⎤
考虑到矩阵过于稀疏(尤其是节点较多时),采用COO存储,即只关注矩阵中非零元素的所在位置。于是我们得到了如下的表格:
row | col | val |
---|---|---|
0 | 1 | 1 |
0 | 2 | 1 |
1 | 0 | 1 |
2 | 3 | 1 |
3 | 2 | 1 |
上述图数据的边索引矩阵为:
edge_index = torch.tensor([[0, 0, 1, 2, 3],
[1, 2, 0, 3, 2]], dtype=torch.long)
小结:边索引矩阵的数据是一个由两个list组成的列表,第一个是由边起点组成的list,第二个元素是由边终点组成的list(如果是无向图,两种方向都要写!!)
3. edge_attr (Tensor, optional) — 边属性矩阵,大小为[num_edges, num_edge_features]
4. y(Tensor) — 边或节点的目标矩阵(尺寸可以是 [num_nodes, *]
或 [1, *]
)。例如上述图数据的
y
y
y为:[0,1,0,1]。
以上给出了几个重要的属性,Data实例还包含其他属性,需要使用时用限定关键字参数指定即可。
- 方法
1.keys 返回图属性名的list
2.num_nodes 图中的节点数
3.num_edges 图中的边数(无向图会返回两个方向的边数,即边数的两倍)
4.num_node_features或 num_features 图中节点特征维度
5.contains_isolated_nodes() 图中是否含有孤立点
6.contains_self_loops() 图中是否含有自环
7.is_undirected() 图是不是无向的
8.is_directed() 图是不是有向的
仍以Karate Club为例:
# 获取图的一些信息
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()}') # 此图是否是无向图
结果:
Number of nodes: 34
Number of edges: 156
Number of node features: 34
Number of node features: 34
Number of edge features: 0
Average node degree: 4.59
if edge indices are ordered and do not contain duplicate entries.: True
Number of training nodes: 4
Training node label rate: 0.12
Contains isolated nodes: False
Contains self-loops: False
Is undirected: True
2.3练习
通过继承Data类实现一个类,专门用于表示“机构-作者-论文”的网络。
该网络包含“机构”、“作者”和“论文”三类节点,以及“作者-机构”和“作者-论文”两类边。
要求:
1)用不同的类属性存储不同类别的节点;
2)用不同的类属性存储不同类别的边;
3)逐一实现获取不同类别节点数量的方法。
类定义:
class IPAGNN(Data):
"""
I:Institute;
P:Papers;
A:Authors;
"""
def __init__(self, x_institute=None, x_article=None, x_author=None,
edge_index_author_to_institute=None, edge_index_author_to_article=None,
y=None):
super(IPAGNN, self).__init__(
x=torch.cat((node_attr_institute, node_attr_article, node_attr_author)),
edge_index=torch.cat((edge_index_author_to_institute, edge_index_author_to_article), 1))
# node attritude
self.x_institute = x_institute
self.x_article = x_article
self.x_author = x_author
# edge index
self.edge_index_author_to_institute = edge_index_author_to_institute
self.edge_index_author_to_article = edge_index_author_to_article
# label
self.y = y
@property
def num_nodes_of_institute(self):
return self.x_institute.size(0)
@property
def num_nodes_of_article(self):
return self.x_article.size(0)
@property
def num_nodes_of_author(self):
return self.x_author.size(0)
获得新建类实例:
# 两个机构、两篇文章、三个作者
node_attr_institute = torch.tensor(
[[-1, 1, 2],
[1, 1, 1]], dtype=torch.float
)
node_attr_article = torch.tensor(
[[1, 0, 1],
[0, 1, 2]], dtype=torch.float
)
node_attr_author = torch.tensor(
[[3, 1, 2],
[2, 1, 1],
[-1, 1, 0]], dtype=torch.float
)
edge_index_author_to_institute = torch.tensor(
[[0, 3, 6, 6],
[1, 4, 1, 4]], dtype=torch.long
)
edge_index_author_to_article = torch.tensor(
[[0, 3, 2, 5],
[2, 5, 3, 0]], dtype=torch.long
)
y = torch.tensor(
[1, 1, 2, 2, 0, 0, 0], dtype=torch.float
)
IPAGNNtest = IPAGNN(node_attr_institute,node_attr_article,node_attr_author,edge_index_author_to_institute,edge_index_author_to_article,y)
测试:
print(IPAGNNtest.num_nodes) # 7
print(IPAGNNtest.num_nodes_of_institute) # 2
print(IPAGNNtest.num_nodes_of_article) # 2
print(IPAGNNtest.num_nodes_of_author) # 3