本次要总结分享的论文:
- 论文链接:Graph Attention network(GAT)
- 所属领域:GNN,DeepLearning
- 来源会议:ICLR 2018
- 作者:Petar Velickovic, Guillem Cucurull, Arantxa Casanova, Yoshua Bengio
- 参考代码:GAT-CODE
- 可应用领域:数据挖掘、推荐系统等
本篇论文的核心内容其实很简单,就是将 NLP领域的 注意力机制
延用到不规则的 图结构上,使得节点在聚合其邻居节点信息时,能自适应的分配不同邻居不同权重。实验显示取得了不错的效果。本论文是GNN领域内重要的一篇论文,值得详细一读。
论文动机和创新点
-
CNN(卷积神经网络)在图像分类、语义分割、机器翻译上都取得了不错的应用效果,但是现实生活中,许多任务的数据并不是规则的网格结构数据,例如社交网络、生物网络等数据,在这些数据上不能直接应用以往传统的模型。
-
最近几年,研究者们对于将卷积网络延用到图结构的数据上表现出了越来越大的兴趣,目前看来主要是基于谱和非谱的方法
- 基于谱的方法例如有:
- Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering
- SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS
基于谱的方法缺点在于:训练得到的模型不能直接应用不同结构的图,不能应用在没见过的节点(直推式) - 非谱的方法例如:
- Inductive Representation Learning on Large Graphs
GraphSAGE非谱的方法,避免了上述谱方法缺点,但是在信息聚合时,各个节点权重都一样,无法为不同节点分配不一样的权重。
-
注意力机制在NLP领域内获得了巨大成功,特别是Sequence-based的任务上,受此启发,本论文致力于将注意力机制延用到图结构数据上,其思想是采用Self-Attention策略,通过聚合邻居节点信息,更新节点表征。本论文的注意结构有如下几个有趣的特性:
- 可以对跨节点-邻居并行化计算注意力
- 该注意力机制 对于任意不同度的节点,均可计算对其邻居节点的注意力,分配不同的权重
- 该模型直接适用于归纳学习问题,包括模型必须推广到完全不可见图的任务
-
本论文提出了一种 图上的注意力网络,在不需要昂贵计算量和预先知道图结构的情况下,能堆叠多层,并且在聚合节点及其邻居节点信息时,可为不同节点分配不同的注意力权重,该网络可同时应用在 归纳(inductive) 或 直推(transductive)任务上。
模型
图注意力Layer
输入层
模型最原始的输入是:
h
=
{
h
⃗
1
,
h
⃗
2
,
.
.
.
,
h
⃗
N
}
\pmb{h} = \{\vec{h}_1, \vec{h}_2, ..., \vec{h}_N\}
hhh={h1,h2,...,hN}
上式中
h
⃗
i
∈
R
F
\vec{h}_i \in R^F
hi∈RF 表示第
i
i
i 个节点的表征,
F
F
F 表示节点的特征维度。
为了让模型有着更强的表达能力,这里面需要将上述每个
h
⃗
i
\vec{h}_i
hi 至少过一层线性变换,通过参数
W
∈
R
F
×
F
′
W \in R^{F \times F^{'}}
W∈RF×F′,将
h
\pmb{h}
hhh 转换为
h
′
\pmb{h^{'}}
h′h′h′,如下:
h
′
=
{
h
′
⃗
1
,
h
′
⃗
2
,
.
.
.
,
h
′
⃗
N
}
\pmb{h^{'}} = \{\vec{h'}_1, \vec{h{'}}_2, ..., \vec{h{'}}_N\}
h′h′h′={h′1,h′2,...,h′N}
后面的Attention机制将在 h ′ \pmb{h^{'}} h′h′h′ 上进行。
注意力层
计算邻居节点
j
j
j 对于节点
i
i
i 的重要程度,可通过如下公式:
e
i
j
=
α
(
h
′
⃗
i
,
h
′
⃗
j
)
=
α
(
W
h
⃗
i
,
W
h
⃗
j
)
e_{ij}=\alpha(\vec{h'}_i, \vec{h'}_j)= \alpha(W\vec{h}_i, W\vec{h}_j)
eij=α(h′i,h′j)=α(Whi,Whj)
注意上述邻居节点 j j j 对于节点 i i i 来说,是一阶邻居,论文里只考虑一阶邻居(包括节点 i i i 本身)。
注意力权重具体计算方式如下:
α
i
j
=
s
o
f
t
m
a
x
j
(
e
i
j
)
=
e
x
p
(
e
i
j
)
∑
k
∈
N
i
e
x
p
(
e
i
k
)
\alpha_{ij}=softmax_{j}(e_{ij})=\frac{exp(e_{ij})}{\sum_{k \in N_i}exp(e_{ik})}
αij=softmaxj(eij)=∑k∈Niexp(eik)exp(eij)
=
e
x
p
(
L
e
a
k
y
R
e
L
u
(
a
⃗
T
[
W
h
⃗
i
∣
∣
W
h
⃗
j
]
)
)
∑
k
∈
N
i
e
x
p
(
L
e
a
k
y
R
e
L
u
(
a
⃗
T
[
W
h
⃗
i
∣
∣
W
h
⃗
k
]
)
)
=\frac{exp(LeakyReLu(\vec{a}^T[W\vec{h}_i||W\vec{h}_j]))}{\sum_{k \in N_i}exp(LeakyReLu(\vec{a}^T[W\vec{h}_i||W\vec{h}_k]))}
=∑k∈Niexp(LeakyReLu(aT[Whi∣∣Whk]))exp(LeakyReLu(aT[Whi∣∣Whj]))
上式中,模型需要学习的参数是为 a ⃗ ∈ R 2 F ′ \vec{a} \in R ^{2F'} a∈R2F′ , W ∈ R F × F ′ W \in R^{F \times F^{'}} W∈RF×F′, N i N_i Ni 表示节点 i i i 的一阶邻居节点。 ∣ ∣ || ∣∣ 表示concat操作。
从数学公式可以看出,其计算方式和NLP的Attention完全一样,其要学习的参数 W W W 和 a ⃗ \vec{a} a 与节点的度的数量无关(与图结构无关),因此模型训练好后,可以直接应用到新的图上。
通过这种注意力层计算,我们可以更新每个节点的表征:
h
′
⃗
i
=
σ
(
∑
j
∈
N
i
α
i
j
h
′
⃗
i
)
=
σ
(
∑
j
∈
N
i
α
i
j
W
h
⃗
i
)
\vec{h'}_i = \sigma (\sum_{j \in N_i} \alpha_{ij}\vec{h'}_i) = \sigma (\sum_{j \in N_i} \alpha_{ij} W \vec{h}_i)
h′i=σ(j∈Ni∑αijh′i)=σ(j∈Ni∑αijWhi)
通过上述说明,可知节点表征的更新可以理解为 聚合了邻居节点信息。
Multi-Head
17年开始火起来的transformer结构,里面的mutli-head Attention子结构可以让模型利用类似集成学习的优点,不同的head学习到不同的侧重点。那么本论文既然是将Attention迁移到图上,mutli-head结构自然少不了。
这里我们假设有
K
K
K 个不同Head,则通过上述的 注意力层
计算得到
K
K
K 个不同的更新后的节点表征,论文中是直接将这
K
K
K 个不同的更新后的表征concat起来,得到最终的更新结果:
h ′ ⃗ i = ∣ ∣ k = 1 K σ ( ∑ j ∈ N i α i j k W k h ⃗ j ) \vec{h'}_i = {||}^{K}_{k=1} \sigma(\sum_{j \in N_i} \alpha_{ij}^kW^k\vec{h}_j) h′i=∣∣k=1Kσ(j∈Ni∑αijkWkhj)
注意:每个Head的 注意力层
里的 参数都是独立的,所以上式中 有
α
i
j
k
,
W
k
\alpha_{ij}^k, W^k
αijk,Wk,最终得到的
h
′
⃗
i
∈
R
K
F
′
\vec{h'}_i \in R^{KF'}
h′i∈RKF′
论文中又讲到,如果这种Multi-Head 出现在模型的最后一层(预测层),则这种concat操作并不合理,故采用平均处理。
通过上述图注意力Layer
,每个节点表征均可以更新,并且聚合了邻居节点信息,其中图注意力Layer
堆叠的越深,则模型的感受野越大。
与相关工作比较
- 计算量:因为上述注意力计算是可以并行的, m u t l i − h e a d mutli-head mutli−head 也可以并行计算,所以计算是非常有效率的,时间复杂度 O ( ∣ V ∣ F F ′ + ∣ E ∣ F ′ ) O(|V |F F' + |E|F') O(∣V∣FF′+∣E∣F′)
- 与GCN比较,本论文方法可以赋予不同节点不同权重,使得模型表达能力更强。
- 因为本论文方法对每个节点的操作都是一样的,所以不需要预先知道 图结构和 所有节点信息,可以进行归纳式学习。
- 与GCN中,对邻居节点采样相比,本论文所提方法可以对节点在训练集中所有邻居节点进行聚合,并且不需要假设邻居节点中任何顺序关系。
实验
数据集
本论文实验涉及如下到四个图数据集,数据集详情如下:
实验从两个角度进行,分别是 直推式任务、归纳式任务
- 直推式(Transductive):训练阶段和测试阶段,模型的输入为一张图,和所有节点,只是拿来训练和测试的节点不一样。在训练的节点上进行学习,学习的同时会影响不带测试节点的表征。
- 归纳式(Inductive):训练和测试阶段,模型输入的图不是一张图,测试阶段会看到没见过的节点。
详细的实验参数可看论文。
直推式的实验效果对比如下,评价指标采用平均分类准确率
:
由上表可知,直推式任务上,在以上三个数据集上,论文所提方法GAT 比以往方法都要好。GAT能够在GCN上分别提高1.5%和1.6%,这表明对同一邻域的节点分配不同的权重可能是有益的
归纳式任务上效果如下:
值得注意的是,相比GraphSAGE,GAT模型提高了20.5%。表明GAT应用在归纳式任务上有较大优势。
核心代码分析
参考的实现代码是 GAT-CODE
该参考给的是归纳式任务代码,也就是 训练阶段 和 测试阶段输入的图是不一样的
这里面我们只分析下图上的注意力Layer怎么实现的
class GAT(BaseGAttN):
def inference(inputs, nb_classes, nb_nodes, training, attn_drop, ffd_drop,
bias_mat, hid_units, n_heads, activation=tf.nn.elu, residual=False):
attns = []
for _ in range(n_heads[0]):
attns.append(layers.attn_head(inputs, bias_mat=bias_mat,
out_sz=hid_units[0], activation=activation,
in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
h_1 = tf.concat(attns, axis=-1)
for i in range(1, len(hid_units)):
h_old = h_1
attns = []
for _ in range(n_heads[i]):
attns.append(layers.attn_head(h_1, bias_mat=bias_mat,
out_sz=hid_units[i], activation=activation,
in_drop=ffd_drop, coef_drop=attn_drop, residual=residual))
h_1 = tf.concat(attns, axis=-1)
out = []
for i in range(n_heads[-1]):
out.append(layers.attn_head(h_1, bias_mat=bias_mat,
out_sz=nb_classes, activation=lambda x: x,
in_drop=ffd_drop, coef_drop=attn_drop, residual=False))
logits = tf.add_n(out) / n_heads[-1]
return logits
上述代码是 GAT模型的 Inference主函数代码,其中 h _ h e a d h\_{head} h_head 表示每层注意力Layer的head数量,用列表形式,代码里 h _ h e a d = [ 8 , 1 ] h\_{head} = [8, 1] h_head=[8,1]; h i d _ u n i t s hid\_units hid_units 表示 上文中 W ∈ R F ′ W \in R^{F'} W∈RF′ 中的 F ′ F' F′,也就是线性变换的维度。再来详细看下里面的 layers.attn_head 是怎么实现的。
def attn_head(seq, out_sz, bias_mat, activation, in_drop=0.0, coef_drop=0.0, residual=False):
with tf.name_scope('my_attn'):
if in_drop != 0.0:
seq = tf.nn.dropout(seq, 1.0 - in_drop)
seq_fts = tf.layers.conv1d(seq, out_sz, 1, use_bias=False)
# simplest self-attention possible
f_1 = tf.layers.conv1d(seq_fts, 1, 1)
f_2 = tf.layers.conv1d(seq_fts, 1, 1)
logits = f_1 + tf.transpose(f_2, [0, 2, 1])
coefs = tf.nn.softmax(tf.nn.leaky_relu(logits) + bias_mat)
if coef_drop != 0.0:
coefs = tf.nn.dropout(coefs, 1.0 - coef_drop)
if in_drop != 0.0:
seq_fts = tf.nn.dropout(seq_fts, 1.0 - in_drop)
vals = tf.matmul(coefs, seq_fts)
ret = tf.contrib.layers.bias_add(vals)
# residual connection
if residual:
if seq.shape[-1] != ret.shape[-1]:
ret = ret + conv1d(seq, ret.shape[-1], 1) # activation
else:
ret = ret + seq
return activation(ret) # activation
显然上述代码中的
o
u
t
_
s
i
z
e
out\_size
out_size 就是
F
′
F'
F′,代码中的
s
e
q
_
f
t
s
seq\_fts
seq_fts 是经过线性变换后的节点表征,也就是上文中的
W
h
⃗
i
W\vec{h}_i
Whi 变换后结果,代码中采用了
c
o
n
v
1
d
conv1d
conv1d 模块实现了这一过程。
代码中对
s
e
q
_
f
t
s
seq\_fts
seq_fts 的 Conv1D操作,相当于上文的乘以
a
⃗
T
\vec{a}^T
aT 操作;得到f_1、f_2
随后进行的 logits = f_1 + tf.transpose(f_2, [0, 2, 1])
操作可以理解为如下广播
操作:
[
1
2
3
]
+
[
1
2
3
]
=
[
1
+
1
1
+
2
1
+
3
2
+
1
2
+
2
2
+
3
3
+
1
3
+
2
3
+
3
]
\left[ \begin{matrix} 1 & 2 & 3 \\ \end{matrix} \right] + \left[ \begin{matrix} 1 \\ 2 \\ 3 \end{matrix} \right] = \left[ \begin{matrix} 1+1 & 1+2 & 1+3 \\ 2+1 & 2+2 & 2+3 \\ 3+1 & 3+2 & 3+3 \end{matrix} \right]
[123]+⎣⎡123⎦⎤=⎣⎡1+12+13+11+22+23+21+32+33+3⎦⎤
上述举例中,假设了三个节点,每个节点的表征分别为1,2,3,论文中节点与节点的注意力计算采用concat,参考代码里直接是相加。注意这里算了所有节点中两两节点之间的注意力计算,但是对于两个节点并不相邻,并未过滤
再来看看tf.nn.softmax(tf.nn.leaky_relu(logits) + bias_mat)
,其中logits上面已经讲过,所以这里的bias_mat 用来过滤 不相邻两节点的计算,且来看看 bias_mat 是如何计算的:
def adj_to_bias(adj, sizes, nhood=1):
nb_graphs = adj.shape[0]
mt = np.empty(adj.shape)
for g in range(nb_graphs):
mt[g] = np.eye(adj.shape[1])
for _ in range(nhood):
mt[g] = np.matmul(mt[g], (adj[g] + np.eye(adj.shape[1])))
for i in range(sizes[g]):
for j in range(sizes[g]):
if mt[g][i][j] > 0.0:
mt[g][i][j] = 1.0
return -1e9 * (1.0 - mt)
对于不相邻的节点用-1e9(近似于负无穷大)作为计算结果,则softmax后变为0。
个人总结
- GAT方法在归纳式任务上表现较好,其在训练阶段主要学习注意力机制的参数,而和图的具体结构无关,因此可以将学习好的模型直接应用新图上。
- 论文所提方法,比较简单,只是将NLP领域的注意力机制引入到了图上而已。
参考资料
- https://arxiv.org/pdf/1710.10903.pdf
- https://github.com/PetarV-/GAT