简单理解图神经网络 GNN
前言
图神经网络(Graph Neural Networks,GNN)最早由The Graph Neural Network Model(Gori et al., 2005)提出。近年来,深度学习领域关于图神经网络的研究热情日益高涨,图神经网络已经成为各大深度学习顶会的研究热点。
本文主要介绍图神经网络的基本原理,通过简单的方式理解 GNN, GCN 是如何工作的,尽量把原理说清楚。
Overview
总的来说,GNN 就是做了这么一件事情:利用图的节点信息去生成节点(图)的 Embedding 表示。
Graph Neural Network(GNN)
既然是 Graph,那么我们的数据就是一张图:
其中hA,hB,hC,hD,hE分别是节点A,B,C,D,E的所包含的信息,你也可以简单理解为是这个节点的特征。
GNN 可分为三步:1. 聚合;2. 更新;3. 循环。
首先是聚合。通过观察上面的图我们可以发现,节点A有三个邻居节点B,C,D,显然这是一个非常重要的信息,节点A与这三个节点有密切的联系。举个例子,如果我们不知道节点A是否有钱,但是我们发现节点B,C,D都非常有钱,那么很大程度上节点A也非常有钱,也就是通过节点的邻居信息判断这个节点的信息。那么要如何利用这个特征呢?
就像在一开始说的,GNN 就是利用图的节点信息去生成节点(图)的 Embedding 表示,这也就是聚合。我们可以通过简单的方式获取邻居的信息:
NA=a⋅hB+b⋅hC+c⋅hD=a⋅(2,2)+b⋅(3,3)+c⋅(4,4)
其中a,b,c为常数,也可以通过训练得到或者是手动设定,我们这里简单理解为常数。
好了,通过上面的简单的方式,我们得到了节点A的邻居的信息,我们将这个信息作为节点A信息的补充。
再来是更新。当我们获取了邻居的信息后,自然也不能忘了节点A本身的信息:
(hA+α⋅NA)
其中α为常数(同样也可以通过训练得到或者是手动设定,这里简单理解为常数)。当然了,肯定不能直接简单加起来就作为节点A的新的信息,而是需要进行一些处理,其实也就是常规的线性变换,再过个激活函数:
hA=σ(W(hA+α⋅NA))
其中σ为激活函数,W为权重矩阵,hA为节点A的信息,NA为节点A的邻居信息。
简而言之,聚合更新就是把节点的邻居的信息添加到这个节点中。
现在,一次聚合操作就完成了,经过一次聚合之后:
- 节点A中有B,C,D的信息;
- 节点B中有A,C的信息;
- 节点C中有A,B,D,E的信息;
- 节点D中有A,C的信息;
- 节点E中有C的信息;
第二次聚合也是类似的,仍然以节点A为例,计算节点A的邻居信息。但此时当节点A聚合节点C的时候,由于节点C中包含了节点E的信息,所以这时节点A获得了二阶邻居E的信息。
归根结底,GNN 就是一个提取特征的方法。
Graph Convolutional Network(GCN)
好了,讲完了 GNN 的总体思路,让我们具体来看看 GCN 是怎么做的。还是以下面这幅图为例:
同样的,我们需要获取节点A的邻居信息:
NA=a⋅hB+b⋅hC+c⋅hD=a⋅(2,2)+b⋅(3,3)+c⋅(4,4)
这里我们使用矩阵的方式来表示图,邻接矩阵A,以及隐藏层矩阵H。
A=0111010100110111010000100H=1234512345
这里假设a=b=c=1,那么,节点A的邻居信息就可以表示为:
NA=[01110]1234512345=[99]=A0,∗H
其中A0,∗表示矩阵A的第一行,即节点A的邻居关系。另外别忘了加上节点A自身的信息,具体来说,可以通通过加上单位矩阵I的方式实现:
NA=([01110]+[10000])1234512345=[1010]=(A0,∗+I0,∗)H
同理,我们可以得到所有节点的邻居信息N:
N=(A+I)H:=A~H
记A~=A+I
现在,就可以写出隐藏层的更新方程了,与之前的思路类似,常规的线性变换,再过个激活函数:
H(l+1)=σ(A~H(l)W(l))
其中l为循环层数,σ为激活函数,W为隐藏层的权重矩阵。
现在还有最后一个问题,你会发现,现在我们的矩阵A~中,非零元素均为1,即简单的将当前节点的邻居信息相加,可以想象的是它会越来越「膨胀」。
A~=1111011100111111011000101
实际上,解决这个问题并不麻烦,归一化就行了。在图 Graph 中,我们可以借助「度」来进行归一化。具体为什么这么做、为什么要进行归一化我们放在后面讲:关于归一化的一些问题。
具体什么是度这里就不赘述了,给出度矩阵:
D=3000002000004000002000001D~=4000003000005000003000002
其中,D,D~分别是A,A~的度矩阵。
这里直接给出 GCN 论文 Semi-Supervised Classification with Graph Convolutional Networks 中的归一化方法,它的本质是对称归一化的拉普拉斯矩阵:
D~−1/2A~D~−1/2
关于上面的归一化方法,我们同样在后面具体讨论:关于归一化的一些问题,如果你不想了解细节也没关系,你只需要知道它的作用即可。
至此为止,我们可以得到完整的隐藏层的更新方程:
H(l+1)=σ(D~−1/2A~D~−1/2H(l)W(l))
其中l为循环层数,σ为激活函数,W为隐藏层的权重矩阵,A~=(A+I),D~是A~的度矩阵。虽然式子长得难看了点,但我想如果你了解了上面讲的内容,也就不难理解了。
关于归一化的一些问题
现在,让我们回到上面没解决的问题:
- 为什么要进行归一化?
- 论文中为什么使用D~−1/2A~D~−1/2进行归一化?
问题一
首先,为什么要进行归一化?这个问题其实在上面也有简单的解释。矩阵A~中,非零元素均为1,那么这就意味着,在计算A~H时,就是简单的将当前节点的邻居信息相加,各个邻居一视同仁。
问题二
归一化
一个直观的归一化方法是:平均化聚合每个节点的信息。
A~=1111011100111111011000101→1/41/31/51/301/41/31/5001/41/31/51/31/21/401/51/30001/501/2=D~−1A~
其中,D~−1是A~的度矩阵的逆。从数学的角度,上面的平均化也就是D~−1A~的过程。现在我们已经将求和变成了加权平均,权值之后归一化为1了。
对称归一化
那么为什么不直接使用简单的平均化方法呢?第一个缺点就是D~−1A~不再是对称矩阵了,这不是我们想要看到的。
第二个缺点我们来看一个比较特殊的例子,在下面这幅图中,我们发现节点A只有一个邻居节点C,另一方面节点C有着包括A在内的 6 个邻居节点。
按照上面的平均化思路,当节点A聚合节点C时,节点C所有的特征会完全加到节点A上,但实际上,节点A和节点C的差距是很明显的。举个例子,节点A是我,节点C是老板,老板管着一票人,我只是一个苦命的打工人,不可能说把老板的工资完全加到我身上,然后我拿着一半我的工资,一半老板的工资,美滋滋。
那么一个科学的方法就是同时考虑节点A和C的邻居数量,即「度」,具体来说,可以考虑几何平均数D~iiD~jj,回到D~−1/2A~D~−1/2:
(D~−1/2A~D~−1/2H)i=(D~−1/2A~)iD~−1/2H=(k∑D~ik−1/2A~i)D~−1/2H=D~ii−1/2j∑A~ijk∑D~jk−1/2Hj=D~ii−1/2j∑A~ijD~jj−1/2Hj=j∑D~iiD~jj1A~ijHj
不考虑复杂的推导过程,只看结论,论文中的对称归一化方法就是使用了上面说的结论,同时考虑两个节点的邻居数量。那么,前面的例子就变成了:
D~−1/2A~D~−1/2=2100000310000051000003100000211111011100111111011000101210000031000005100000310000021=412312512310231311510025115151151101231015131000101021
最后,大佬们是如何想出D~−1/2A~D~−1/2的呢?其实,这个公式很接近对称归一化的拉普拉斯矩阵:
Lsym=D−1/2LD−1/2=I−D−1/2AD−1/2
Graph Attention Network(GAT)
如果你熟悉 Attention 机制,那么在了解了上面的内容之后,就不难理解 Graph Attention Network(GAT) 了,无非就是对邻居节点的权重进行自动学习,这里就不再细说了。
总结
最后,还是想强调一点,GNN 就是做了这么一件事情:利用图的节点信息去生成节点(图)的 Embedding 表示。就是那么一个 Embedding 的方法。
参考资料