graph attention networks
预备知识:
inductive learning :测试集不参与训练
transductive learning :测试集参与训练
大部分算法都是inductive learning
GAT代码:
https://github.com/PetarV-/GAT.
动机
提出图注意力对图邻域的不同节点进行了权重指定,而且无需任何相近的矩阵操作(像转置),或者依赖之前的图结构知识。并且处理了几个基于谱的图卷积网络问题,让我们的模型可以处理归纳式以及直推式的问题。
模型
预备:
- 输入: h = { h ⃗ 1 , h ⃗ 2 , . . . h ⃗ N } , h ⃗ i ∈ R F \mathbf{h}=\{\vec{h}_1,\vec{h}_2,...\vec{h}_N\},\vec{h}_i\in \mathbb{R}^{F} h={h1,h2,...hN},hi∈RF,N是节点数量
- 输出:
h
′
=
{
h
′
⃗
1
,
h
′
⃗
2
,
.
.
.
h
′
⃗
N
}
,
h
′
⃗
i
∈
R
F
′
\mathbf{h'}=\{\vec{h'}_1,\vec{h'}_2,...\vec{h'}_N\},\vec{h'}_i\in \mathbb{R}^{F'}
h′={h′1,h′2,...h′N},h′i∈RF′,
为了计算attention系数,参数化了一个权重矩阵 W ∈ R F ′ × F \mathbf{W}\in \mathbb{R}^{F'\times F} W∈RF′×F再用一个共享注意力机制(其实就是个泛函) a : R F ′ × R F ′ ⟶ R a:\mathbb{R}^{F'}\times \mathbb{R}^{F'}\longrightarrow \mathbb{R} a:RF′×RF′⟶R
表示第 j 个节点的特征对第 i 个节点的重要性。
然后再归一化
我们这里的注意力机制
a
a
a采用的还是单层的前馈型网络,相当于加权平均 ,权重向量为
a
⃗
∈
R
2
F
′
\vec{a}\in \mathbb{R}^{2F'}
a∈R2F′,然后用LeakyRelu非线性激活函数(取斜率
α
=
0.2
\alpha=0.2
α=0.2),最后系数计算的表示式为
- ||是串接操作
-
N
i
\mathcal{N}_i
Ni表示 i 的邻域
当正则化的系数被计算出来之后,用于计算相对应的节点特征输出
为了使self-attention的学习更加稳定,这里采用了多头注意力,K个独立的像上式的注意力机制被执行,其实就是用不同的参数W去进行变换,得到多个不同的权重表示,最后输出为
- ||为concatenation,
-
α
i
j
k
\alpha^{k}_{ij}
αijk是第k个头的注意力系数
最后的输出不是 F ′ F' F′个维度,而是 K F ′ KF' KF′,因为是K个拼接起来。
特别地,当其用在最后一层是,用concatenatioin是不合理的
(最后一层的输出一般为了结点分类),所以我们采用不同attention的和的平均
实验:
数据集
Transductive learning
Inductive learning
PPI:protein protein interaction
attention可视化
线的厚度表示attention的规则化后的系数大小。
代码分析
在cora数据集上(一个图,节点类型预测,2708个节点,每个节点1433个特征)单个 attention层代码:
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 # 0.6
self.in_features = in_features # 1433
self.out_features = out_features # 8
self.alpha = alpha # 0.2
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))) #[16,1]
nn.init.xavier_uniform_(self.a.data, gain=1.414)
self.leakyrelu = nn.LeakyReLU(self.alpha)
def forward(self, input, adj):
h = torch.mm(input, self.W) #[2708,1433]*[1433,8]
N = h.size()[0]
# h.repeat(1, N).view(N * N, -1) 0~(N-1)行相同,N~2N-1行相同,以此类推。[2708*2708,8]
# h.repeat(N, 1) 这个是[0:2708,:]==[2708:2*2708,:],---, [2708*2708,8]
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) #[2708, 2708, 16]
e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(2)) # [2708,2708,16]*[16,1].squeeze()=[2708,2708]torch.matmul用于高维的矩阵乘法
zero_vec = -9e15*torch.ones_like(e)
attention = torch.where(adj > 0, e, zero_vec) #大于0的位置取e,小于0的取-9e15
attention = F.softmax(attention, dim=1)
attention = F.dropout(attention, self.dropout, training=self.training)
h_prime = torch.matmul(attention, h) # 对单个节点的信息用相邻的节点信息去聚合,apha_ij*W*h [2708,8]
if self.concat:
return F.elu(h_prime)
else:
return h_prime
def __repr__(self):
return self.__class__.__name__ + ' (' + str(self.in_features) + ' -> ' + str(self.out_features) + ')'