一文读懂图神经网络

Posted 机器学习算法与自然语言处理

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文读懂图神经网络相关的知识,希望对你有一定的参考价值。

设为 “ 星标 ”,重磅干货,第一时间送达!


一、图神经网络介绍


什么是图神经网络


图神经网络(Graph Neural Networks, GNNs)是基于图结构的深度学习方法,近期被广泛应用到各类图像、自然语言处理等任务上。图神经网络作为神经网络扩展,可以处理以图结构表示的数据格式。在图中,每个节点都由本身的特性以及其相邻的节点和关系所定义,网络通过递归地聚合和转换相邻节点的表示向量来计算节点的表示向量。


在论文《Relational inductive biases, deep learning, and graph networks》中,作者定义了通用图网络框架graph networks (GN) framework,概括和扩展了各种图神经网络,并且支持使用简单的模块来构建复杂的结构。GN framework的主要计算单元是GN block,一个图到图的模块,它的输入是一个图,在图结构上进行计算,然后得到同样是图结构的输出。


在GN framework下,一个图可以被定义为一个三元组一文读懂图神经网络一文读懂图神经网络是图的一个全局表示,一文读懂图神经网络代表图中一文读懂图神经网络个节点的集合,一文读懂图神经网络为节点i的表示,一文读懂图神经网络表示图中一文读懂图神经网络条边的集合,一文读懂图神经网络为边的表示,一文读懂图神经网络一文读懂图神经网络分别代表边的接收节点和发送节点。 


每一个GN block包含三个更新函数一文读懂图神经网络,以及三个聚合函数一文读懂图神经网络

一文读懂图神经网络

上式中一文读懂图神经网络一文读懂图神经网络一文读懂图神经网络


其中一文读懂图神经网络应用于图中每条边的更新,一文读懂图神经网络应用于图中每个节点的更新,一文读懂图神经网络则用来更新图的全局表示;一文读懂图神经网络函数将输入的表示集合整合为一个表示,该函数设计为可以接收任意大小的集合输入,通常可以为加和、平均值或者最大值等不限输入个数的操作。


当一个GN block得到一个图一文读懂图神经网络的输入时,计算通常从边到节点,再到全局进行。下图给出了一个GN block的更新过程。

一文读懂图神经网络

图1 一个GN block的更新过程


一个GN block的计算可以被描述为如下几步:

1. 一文读懂图神经网络更新每一条边,输入参数为边表示一文读懂图神经网络,接收和发送节点的表示一文读懂图神经网络一文读懂图神经网络以及全局表示一文读懂图神经网络,输出为更新过的边表示一文读懂图神经网络

2.  一文读懂图神经网络用来有同一个聚合接收节点的边的信息,对节点i得到所有入边及邻近节点信息整合一文读懂图神经网络,用于下一步节点的更新;

3.  一文读懂图神经网络更新每一个节点,输入为上文中的一文读懂图神经网络,节点表示一文读懂图神经网络$以及全局表示一文读懂图神经网络,输出为更新过的节点表示一文读懂图神经网络

4.  一文读懂图神经网络聚合图中所有边的信息得到边信息整合一文读懂图神经网络

5.  一文读懂图神经网络通过聚合图中所有节点信息得到节点的信息整合一文读懂图神经网络

6. 一文读懂图神经网络更新全局的表示,输入为边信息整合一文读懂图神经网络,节点信息整合一文读懂图神经网络,以及节点表示一文读懂图神经网络,输出为更新过的全局表示一文读懂图神经网络


我们可以通过上述的更新步骤来套用目前的图神经网络算法,当然这里的步骤的顺序并不是严格固定的,比如可以先更新全局信息,再更新节点信息以及边的信息。


为什么要使用图神经网络


图神经网络有灵活的结构和更新方式,可以很好的表达一些数据本身的结构特性,除了一些自带图结构的数据集(如Cora,Citeseer等)以外,图神经网络目前也被应用在更多的任务上,比如文本摘要,文本分类和序列标注任务等,目前图神经网络以及其变种在很多任务上都取得了目前最好的结果。


比较常见的图神经网络算法主要有Graph Convolutional Network(GCN)和Graph Attention Network(GAT)等网络及其变种,在本文第三部分会给出基于图神经网络框架DGL的GCN以及GAT的代码及注解。


二、图神经网络库


相比于传统的基于邻接矩阵的图神经网络实现方法,目前完成度较好的图神经网络框架主要是基于PyTorch和MXNet的DGL (Deep Graph Library)和PyG (PyTorch Geometric)。笔者主要使用了DGL作为开发的框架,原因是各种网络模型的tutorial给的比较详尽,同时包括TreeLSTM这种经典模型也可以通过给定节点访问的顺序通过框架很轻松的实现,相比于不使用图网络框架的代码具有更强的可读性。


但是,实际上图神经网络框架只适用于图结构中的边和点是同一量级的情况,因为图神经网络框架的信息是通过图中的边来传递的,会将节点的表示复制多次。在这种情况下,反而不如使用邻接矩阵直接做矩阵乘法,因为使用邻接矩阵做矩阵乘法其实不会将节点信息复制多次,会对显存有极大的节省。


三、常见算法以及代码示例详解


GCN


GCN的计算公式如下:

一文读懂图神经网络

上式中一文读懂图神经网络表示网络的第l层,一文读懂图神经网络代表非线性激活函数,一文读懂图神经网络为该层的权重矩阵,一文读懂图神经网络一文读懂图神经网络分别代表度矩阵以及邻接矩阵,~符号表示对每一个节点加上一个自环,即一文读懂图神经网络一文读懂图神经网络为单位矩阵,由于邻接矩阵是没有进行正则化的,所以论文中通过一文读懂图神经网络使得结果中每一行的和都为1。

一文读懂图神经网络

图2 在每一层图网络中,每个节点通过对邻近节点的信息聚合得到这层该节点的输出


相比于前文给出的GCN基于邻接矩阵的公式定义,GCN的公式可以被更简洁的定义为以下两步:

1)对于节点u,首先将节点邻居表示一文读懂图神经网络聚合到一起,生成中间表示一文读懂图神经网络

2)将得到的中间表示一文读懂图神经网络通过一层非线性神经网络层一文读懂图神经网络


下面为基于DGL的代码示例,在代码实现中,第一步通过DGL自带的message passing函数实现,第二步通过DGL的apply_nodes方法,将基于PyTorch中nn.Module的用户自定义函数加入实现:


1import dgl
2import dgl.function as fn
3import torch as th
4import torch.nn as nn
5import torch.nn.functional as F
6from dgl import DGLGraph
7
8gcn_msg = fn.copy_src(src='h', out='m')
9gcn_reduce = fn.sum(msg='m', out='h')


接下来为apply_nodes定义更新节点的自定义函数,这里是一个线性变换加上一个非线性激活函数的网络层:


 1class NodeApplyModule(nn.Module):
2    def __init__(self, in_feats, out_feats, activation):
3        super(NodeApplyModule, self).__init__()
4        self.linear = nn.Linear(in_feats, out_feats)
5        self.activation = activation
6
7    def forward(self, node):
8        h = self.linear(node.data['h'])
9        h = self.activation(h)
10        return {'h' : h}


下面定义GCN模块,一层GCN首先通过update_all方法将节点信息通过边传递,然后通过apply_nodes得到新的节点表示:


 1class GCN(nn.Module):
2    def __init__(self, in_feats, out_feats, activation):
3        super(GCN, self).__init__()
4        self.apply_mod = NodeApplyModule(in_feats, out_feats, activation)
5
6    def forward(self, g, feature):
7        g.ndata['h'] = feature
8        g.update_all(gcn_msg, gcn_reduce)
9        g.apply_nodes(func=self.apply_mod)
10        return g.ndata.pop('h')


整个网络模块的定义和Pytorch中的NN模型定义本质上相同,如下我们定义两个GCN网络层:


 1class Net(nn.Module):
2    def __init__(self, in_dim, hidden_dim, out_dim):
3        super(Net, self).__init__()
4        self.gcn1 = GCN(in_dim, hidden_dim, F.relu)
5        self.gcn2 = GCN(hidden_dim, out_dim, F.relu)
6
7    def forward(self, g, features):
8        x = self.gcn1(g, features)
9        x = self.gcn2(g, x)
10        return x
11net = Net()
12print(net)


GAT


GAT和GCN的主要区别就在于邻近节点信息的聚合方式,GAT通过引入attention机制来替代GCN中的静态归一化卷积运算,下面给出使用 层网络输出来计算第 l+1 层网络输出一文读懂图神经网络的公式:

一文读懂图神经网络

在上式中,公式(1)为上一层节点表示一文读懂图神经网络通过可学习的权重矩阵一文读懂图神经网络做线性变换;公式(2)计算两个邻近节点的为未进行标准化的attention分数,这里首先将两个节点的表示相连,然后和一个可学习的权重向量一文读懂图神经网络做点积,最后通过一文读懂图神经网络函数。这种形式通常叫做additive attention,相比Transformer中的则是dot-product attention;公式(3)对每个与该节点有入边的节点的attention分数使用softmax函数作归一化操作;公式(4)与GCN类似,聚合邻近节点的表示,使用公式(3)得到的分数作为权值。具体的计算方式如下图所示:

一文读懂图神经网络

图3 Graph Attention Networks计算attention分数以及更新节点表示


下面的基于DGL的代码逐一分解了上面的四个公式,公式(1)中的线性变换直接使用Pytorch中的torch.nn.Linear模块


 1import torch
2import torch.nn as nn
3import torch.nn.functional as F
4
5class GATLayer(nn.Module):
6    def __init__(self, g, in_dim, out_dim):
7        super(GATLayer, self).__init__()
8        self.g = g
9        # equation (1)
10        self.fc = nn.Linear(in_dim, out_dim, bias=False)
11        # equation (2)
12        self.attn_fc = nn.Linear(2 * out_dim, 1, bias=False)
13
14
15    def edge_attention(self, edges):
16        # edge UDF for equation (2)
17        z2 = torch.cat([edges.src['z'], edges.dst['z']], dim=1)
18        a = self.attn_fc(z2)
19        return {'e': F.leaky_relu(a)}
20
21
22    def message_func(self, edges):
23        # message UDF for equation (3) & (4)
24        return {'z': edges.src['z'], 'e': edges.data['e']}
25
26
27    def reduce_func(self, nodes):
28        # reduce UDF for equation (3) & (4)
29        # equation (3)
30        alpha = F.softmax(nodes.mailbox['e'], dim=1)
31        # equation (4)
32        h = torch.sum(alpha * nodes.mailbox['z'], dim=1)
33        return {'h': h}
34
35
36    def forward(self, h):
37        # equation (1)
38        z = self.fc(h)
39        self.g.ndata['z'] = z
40        # equation (2)
41        self.g.apply_edges(self.edge_attention)
42        # equation (3) & (4)
43        self.g.update_all(self.message_func, self.reduce_func)
44        return self.g.ndata.pop('h')


公式(2)中的一文读懂图神经网络通过两个相邻节点i和j的表示计算,这里通过DGL的apply_edgesAPI来实现,参数是下面的自定义函数edge_attention


1def edge_attention(self, edges):
2    # edge UDF for equation (2)
3    z2 = torch.cat([edges.src['z'], edges.dst['z']], dim=1)
4    a = self.attn_fc(z2)
5    return {'e' : F.leaky_relu(a)}


这里和可学习权重向量一文读懂图神经网络的点积通过上面定义的线性变换函数attn_fc得到,这里apply_edge会将所有的边的数据放到一个tensor里,所以catattn_fc可以并行计算,这也是上文提到的如果边的数量比点的数量多一个量级,最好还是使用邻接矩阵的方式实现图网络的原因,因为会将点的信息复制多次。


对于公式(3)和公式(4),使用update_allAPI来实现所有节点的信息传递,message function会发送两部分信息:经过变换的表示一文读懂图神经网络和每条边上尚未经过归一化的attention分数一文读懂图神经网络,然后通过下面的reduce function,首先通过softmax对attention的分数作归一化操作(公式(3)),然后将节点的邻近节点的表示按照归一化之后的attention权重聚合起来得到新的表示(公式(4)):


1def reduce_func(self, nodes):
2    # reduce UDF for equation (3) & (4)
3    # equation (3)
4    alpha = F.softmax(nodes.mailbox['e'], dim=1)
5    # equation (4)
6    h = torch.sum(alpha * nodes.mailbox['z'], dim=1)
7    return {'h' : h}


GAT同样使用了类似Transformer的Multi-head Attention,用来加强模型表达能力并且稳定学习过程。每一个attention head有自己独立的参数,可以通过两种方式合并它们的输出:

一文读懂图神经网络

或者

一文读懂图神经网络

上式中一文读懂图神经网络代表attention heads的数量,原作者建议对于网络中间层使用concatenation,最后一层使用average得到attention的输出,与上面的单个head的GATLayer相结合可以得到下面的代码:


 1class MultiHeadGATLayer(nn.Module):
2    def __init__(self, g, in_dim, out_dim, num_heads, merge='cat'):
3        super(MultiHeadGATLayer, self).__init__()
4        self.heads = nn.ModuleList()
5        for i in range(num_heads):
6            self.heads.append(GATLayer(g, in_dim, out_dim))
7        self.merge = merge
8
9
10    def forward(self, h):
11        head_outs = [attn_head(h) for attn_head in self.heads]
12        if self.merge == 'cat':
13            # concat on the output feature dimension (dim=1)
14            return torch.cat(head_outs, dim=1)
15        else:
16            # merge using average
17            return torch.mean(torch.stack(head_outs))


最后定义一个两层的GAT模型


 1class GAT(nn.Module):
2    def __init__(self, g, in_dim, hidden_dim, out_dim, num_heads):
3        super(GAT, self).__init__()
4        self.layer1 = MultiHeadGATLayer(g, in_dim, hidden_dim, num_heads)
5        # Be aware that the input dimension is hidden_dim*num_heads since
6        # multiple head outputs are concatenated together. Also, only
7        # one attention head in the output layer.
8        self.layer2 = MultiHeadGATLayer(g, hidden_dim * num_heads, out_dim, 1)
9
10
11    def forward(self, h):
12        h = self.layer1(h)
13        h = F.elu(h)
14        h = self.layer2(h)
15        return h


上面GAT和GCN的详细代码可以在DGL的Github(https://github.com/dmlc/dgl)上找到。


四、总结


图神经网络在近期的研究中被广泛应用,作者结合参考资料和自身对图神经网络的理解,总结了一个快速上手的简要教程。除了在一些有着天然图结构的任务,图神经网络也可以应用在自然语言处理任务中,可以在模型中更好地表示信息。本文还给出了基于图神经网络框架DGL的GCN和GAT的代码及注解,但是根据实际使用经验,图神经网络框架在处理边数量比节点数量多一个量级的情况下,会比使用邻接矩阵的写法占用更多的内存,还是需要根据具体情况来选择使用。


五、参考资料


[1] https://docs.dgl.ai/

[2] T. N. Kipf and M. Welling. Semi-supervised classification with graph convolutional networks.In ICLR, 2016

[3] Battaglia, P. W., Hamrick, J. B., Bapst, V., SanchezGonzalez, A., Zambaldi, V., Malinowski, M., Tacchetti, A., Raposo, D., Santoro, A., Faulkner, R., et al. Relational inductive biases, deep learning, and graph networks. arXiv preprint arXiv:1806.01261, 2018.

[4] Zhou, J., Cui, G., Zhang, Z., Yang, C., Liu, Z., and Sun, M. Graph neural networks: A review of methods and applications. arXiv preprint arXiv:1812.08434, 2018.


本期责任编辑:丁 效

本期编辑:刘元兴




重磅!机器学习算法与自然语言处理交流群已正式成立

群内有大量资源,欢迎大家进群学习!


额外赠送福利资源!邱锡鹏深度学习与神经网络,pytorch官方中文教程,利用Python进行数据分析,机器学习学习笔记,pandas官方文档中文版,effective java(中文版)等20项福利资源

一文读懂图神经网络

获取方式:进入群后点开群公告即可领取下载链接

注意:请大家添加时修改备注为 [学校/公司 + 姓名 + 方向]

例如 —— 哈工大+张三+对话系统。

号主,微商请自觉绕道。谢谢!

一文读懂图神经网络


推荐阅读:




以上是关于一文读懂图神经网络的主要内容,如果未能解决你的问题,请参考以下文章

一文读懂PyTorch张量基础(附代码)

一文读懂神经网络

一文读懂网络切片

一文读懂网络切片

云原生周刊:一文读懂 Pod 网络 | 2023.4.10

一文读懂高性能网络编程中的I/O模型