针对图结构数据,本文提出了一种GAT(graph attention networks)网络。该网络使用masked self-attention层解决了之前基于图卷积(或其近似)的模型所存在的问题。在GAT中,图中的每个节点可以根据邻节点的特征,为其分配不同的权值。GAT的另一个优点在于,无需使用预先构建好的图。因此,GAT可以解决一些基于谱的图神经网络中所具有的问题。实验证明,GAT模型可以有效地适用于(基于图的)归纳学习问题与转导学习问题。
归纳学习(Inductive Learning):先从训练样本中学习到一定的模式,然后利用其对测试样本进行预测(即首先从特殊到一般,然后再从一般到特殊),这类模型如常见的贝叶斯模型。
转导学习(Transductive Learning):先观察特定的训练样本,然后对特定的测试样本做出预测(从特殊到特殊),这类模型如k近邻、SVM等。
#Related Work
在之前的模型中,已经有很多基于神经网络的工作被用于处理图结构的数据。例如,最早的GNN网络(详情见GNN)可以被用于处理有环图、有向图或无向图。然而,GNN网络本身必须使整个网络达到不动点之后才可以进行计算。针对这一问题,[2]中通过将GRU引入到网络结构中,进一步提出了GGNN网络(详情见GGNN)。后来,人们开始关注将卷积操作引入到图领域中,这一类算法可以被分为谱方法(spectral approaches)与非谱方法(non-spectral approaches)两大类。
谱方法是基于对图进行谱表示的一类方法。其上的卷积操作与图拉普拉斯矩阵的特征值分解有关,因此,往往需要进行密集的矩阵运算,而且整个计算并不是局部的。为了解决这一问题,[3]提出了GCN网络,该网络可以有效地对节点的一阶邻居进行处理,而且可以避免复杂的矩阵运算。然而,这些模型都依赖于图的结构,因此,在特定图结构上训练得到的模型往往不可以直接被使用到其他图结构上。
不同于谱方法,非谱方法是直接在图上(而不是在图的谱上)定义卷积。这类方法的一个挑战在于,如何定义一个可以处理可变大小邻居且共享参数的操作。针对这一问题,在[4]中,作者提出了MoNet(mixture model CNN),该方法可以有效地将CNN结构引入到图上。类似地,[5]提出了一种GraphSAGE模型,该模型使用一种归纳的方法来计算节点表示。具体来说,该模型首先从每个节点的邻节点中抽取出固定数量的节点,然后再使用特定的方式来融合这些邻节点的信息(如直接对这些节点的特征向量求平均,或者将其输入到一个RNN中),这一方法已经在很多大型归纳学习问题中取得了很好的效果。
在本文中,作者提出了一种基于attention的节点分类网络——GAT。其基本思想是,根据每个节点在其邻节点上的attention,来对节点表示进行更新。GAT具有以下几个特点:(1)计算速度快,可以在不同的节点上进行并行计算;(2)可以同时对拥有不同度的节点进行处理;(3)可以被直接用于解决归纳学习问题,即可以对从未见过的图结构进行处理。
注意力层:初始化输入输出,偏置的size=【2 * 输出大小,1】:
class GraphAttentionLayer(nn.Module):
"""
Simple GAT layer, similar to https://arxiv.org/abs/1710.10903
"""
def __init__(self, in_features, out_features, dropout, alpha, concat=True):
super(GraphAttentionLayer, self).__init__()
self.dropout = dropout
self.in_features = in_features
self.out_features = out_features
self.alpha = alpha
self.concat = concat
self.W = nn.Parameter(torch.zeros(size=(in_features, out_features)))
nn.init.xavier_uniform_(self.W.data, gain=1.414)
self.a = nn.Parameter(torch.zeros(size=(2*out_features, 1)))
nn.init.xavier_uniform_(self.a.data, gain=1.414)
self.leakyrelu = nn.LeakyReLU(self.alpha)
forward
内部:输入的attention相当于由网络内部参数动态计算出新的一个权重矩阵,其中e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(2))
计算下面的左边部分 e e e,最后用softmax
得到注意力 α \alpha α,其中 h i , h j h_i,h_j hi,hj 是 i,j 节点的隐层特征;
得到的注意力系数attention
乘以之前的隐层变换h=torch.mm(input,self.W)
用来计算下面的公式,注意attention
先利用dropout
进行了随机采样:
为了稳定性,采用多头注意力机制实现隐层特征计算,看到,多了k个注意力 α \alpha α与隐层变换h=torch.mm(input,self.W)
的乘机,最后用cat
作为拼接的最后隐层输出:
在Inductive learning 中最后一层用交叉是最为loss.
def forward(self, input, adj):
h = torch.mm(input, self.W)
N = h.size()[0]
a_input = torch.cat([h.repeat(1, N).view(N * N, -1), h.repeat(N, 1)], dim=1).view(N, -1, 2 * self.out_features)
e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(2))
zero_vec = -9e15*torch.ones_like(e)
attention = torch.where(adj > 0, e, zero_vec)
attention = F.softmax(attention, dim=1)
attention = F.dropout(attention, self.dropout, training=self.training)
h_prime = torch.matmul(attention, h)
if self.concat:
return F.elu(h_prime)
else:
return h_prime
假设输入邻接矩阵大小为:88,特征矩阵为85:
gl = GraphAttentionLayer(5, 2, 0., 0, concat=True)
te = torch.randn(8, 5)
adj = torch.randn(8, 8)
gl(te, adj).shape # torch.Size([8, 2])
从内部测试:
w = torch.randn(5, 2)
h = torch.mm(te, w)
N = h.size()[0]
h # 隐层输出
tensor([[ 4.5773e+00, -2.1053e-01],
[-1.0539e-04, -6.6395e-02],
[ 1.0329e+00, 4.7576e-01],
[-1.1423e+00, -3.4550e-01],
[-2.9350e-01, 2.4637e-01],
[-3.0809e+00, 2.4914e-01],
[ 2.0366e+00, 3.2745e-01],
[ 2.8746e+00, 9.2813e-01]])
接下来:
a_input = torch.cat([h.repeat(1, N).view(N * N, -1), h.repeat(N, 1)], dim=1).view(N, -1, 2 * 2)
a_input.shape # torch.Size([8, 8, 4])
a_input[0] # 每一个节点的特征,与其他全部节点的组合。
tensor([[ 4.5773e+00, -2.1053e-01, 4.5773e+00, -2.1053e-01],
[ 4.5773e+00, -2.1053e-01, -1.0539e-04, -6.6395e-02],
[ 4.5773e+00, -2.1053e-01, 1.0329e+00, 4.7576e-01],
[ 4.5773e+00, -2.1053e-01, -1.1423e+00, -3.4550e-01],
[ 4.5773e+00, -2.1053e-01, -2.9350e-01, 2.4637e-01],
[ 4.5773e+00, -2.1053e-01, -3.0809e+00, 2.4914e-01],
[ 4.5773e+00, -2.1053e-01, 2.0366e+00, 3.2745e-01],
[ 4.5773e+00, -2.1053e-01, 2.8746e+00, 9.2813e-01]])
设置bias,用a_inputs矩阵乘bias,得到每一个节点与其他节点的注意力大小值:
bias = torch.randn(2 * 2, 1)
out = torch.matmul(a_input, bias).squeeze(2)
out[0]
tensor([7.5904, 4.0185, 4.9624, 3.0516, 3.8623, 1.6669, 5.7176, 6.5219])
测试下是不是这样,用a_input[0] * bias,也就是计算第一个【0】节点与其他节点的注意力大小:
torch.matmul(a_input[0], bias).squeeze(-1) # 结果是一致的。
# tensor([7.5904, 4.0185, 4.9624, 3.0516, 3.8623, 1.6669, 5.7176, 6.5219])
得到注意力矩阵后做非线性变换然后取出有邻接的部分:attention = torch.where(adj > 0, e, zero_vec)
。根据是否拼接输出结果。
class GAT(nn.Module):
def __init__(self, nfeat, nhid, nclass, dropout, alpha, nheads):
"""Dense version of GAT."""
super(GAT, self).__init__()
self.dropout = dropout
self.attentions = [GraphAttentionLayer(nfeat, nhid, dropout=dropout, alpha=alpha, concat=True) for _ in range(nheads)]
for i, attention in enumerate(self.attentions):
self.add_module('attention_{}'.format(i), attention)
self.out_att = GraphAttentionLayer(nhid * nheads, nclass, dropout=dropout, alpha=alpha, concat=False)
def forward(self, x, adj):
x = F.dropout(x, self.dropout, training=self.training)
x = torch.cat([att(x, adj) for att in self.attentions], dim=1)
x = F.dropout(x, self.dropout, training=self.training)
x = F.elu(self.out_att(x, adj))
return F.log_softmax(x, dim=1)
注意:稀疏矩阵版本的GAT要多一些处理,torch好像这里还不完善。
[1]Veličković P, Cucurull G, Casanova A, et al. Graph Attention Networks. International Conference on Learning Representations (ICLR), 2018.
[2] Yujia Li, Daniel Tarlow, Marc Brockschmidt, and Richard Zemel. Gated graph sequence neural networks. International Conference on Learning Representations (ICLR), 2016.
[3] Thomas N Kipf and Max Welling. Semi-supervised classification with graph convolutional networks. International Conference on Learning Representations (ICLR), 2017.
[4] Federico Monti, Davide Boscaini, Jonathan Masci, Emanuele Rodol`a, Jan Svoboda, and Michael M Bronstein. Geometric deep learning on graphs and manifolds using mixture model cnns. arXiv preprint arXiv:1611.08402, 2016.
[5] William L Hamilton, Rex Ying, and Jure Leskovec. Inductive representation learning on large graphs. Neural Information Processing Systems (NIPS), 2017.
转自:https://zhuanlan.zhihu.com/p/34232818