赞
踩
Graph
GraphSAGE
上图出自2017的《Inductive representation learning on large graphs》。首先GNN的学习方法可以分为两种:
而GraphSAGE则是归纳式学习的一个代表,不学习每个节点的固定表示,而是学习节点之间的聚合函数,即节点的信息是通过其邻居节点的特征聚合而来的,所以仅需邻域即可处理新节点了。那么如何学习聚合函数?
多种聚合方式:
其实集中图神经网络的不同关键在于聚合方式,表现在两个方面,一个是邻居节点的聚合(mean,norm,plain),一个是本身和聚合邻居节点的聚合(加法,拼接,点乘)。比如GCN采用本身的表达(自环)加上邻居节点的聚合表达,GraphSage则采用拼接的方式,而接来下要介绍的GAT则是使用一种类似于自注意力机制的表达。
#作者实验中 K=2,即聚合两跳内邻居特征;一跳采样邻居数S1=25,二跳采样邻居S2=10个节点:采样随机游走步长为 5 的且50 次, 负采样采样 20 个。 def aggregate(self, samples, input_features, dims, num_samples, support_sizes, batch_size=None, aggregators=None, name=None, concat=False, model_size="small"): if batch_size is None: batch_size = self.batch_size hidden = [tf.nn.embedding_lookup(input_features, node_samples) for node_samples in samples] new_agg = aggregators is None if new_agg: aggregators = [] for layer in range(len(num_samples)): if new_agg: dim_mult = 2 if concat and (layer != 0) else 1 #聚合当前层 if layer == len(num_samples) - 1: aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], act=lambda x : x, dropout=self.placeholders['dropout'], name=name, concat=concat, model_size=model_size) else: aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], dropout=self.placeholders['dropout'], name=name, concat=concat, model_size=model_size) aggregators.append(aggregator) else: aggregator = aggregators[layer] #多跳 next_hidden = [] #层数增加,采样邻居数逐渐减少 for hop in range(len(num_samples) - layer): dim_mult = 2 if concat and (layer != 0) else 1 #support邻居 neigh_dims = [batch_size * support_sizes[hop], num_samples[len(num_samples) - hop - 1], dim_mult*dims[layer]] h = aggregator((hidden[hop], tf.reshape(hidden[hop + 1], neigh_dims))) next_hidden.append(h) #下一跳 hidden = next_hidden return hidden[0], aggregators
完整的细节源代码逐行中文注释:https://github.com/nakaizura/Source-Code-Notebook/tree/master/GraphSAGE
GraphSAGE的优点
采样数大于邻居数怎么办?
一般是采用无放回的抽象,但如果采样数大了,可以采用有放回的抽样方法直到采完K个节点。同时原文用的是uniform均匀采样的,其实用像PinSage那种按重要性采结果也不错。
哪种聚合函数好?
LSTM>pooling>mean
怎么用在有向图中?
只要采样的时候考虑到方向性就行了。
怎么用在无监督学习中?
让“近”的节点的表示相似,“远”的节点表示尽可能可被区分。所以可以用负采样的方法,把近的节点做正样本,远的节点是负样本,然后Word2vec。至于这个“近”的定义可以是从某节点能够随机游走到的节点,其他不能游走到的则认为是远的节点。
GraphSAGE的缺点是什么?
采样会随着阶数深度增加而指数增加。
GAT
Bengio ICLR2018年的《Graph Attention Network 》。按照Transductive learning的方式,需要对所有节点得到结果,所以训练好的GCN是无法用在不一样的图结构上的。GAT是另一种解决方案,即Attention。一般来说GCN分为两大类,一种是谱图卷积,是在傅里叶域进行卷积变换;另一种是非谱图卷积(也叫做“空间域卷积”),是直接在Graph上进行卷积,GAT就属于空间域卷积,而且放松了邻点个数和边的权重这两个限制条件图。
Attention的特点是自适应,所以即便图结构不一样,度数不相同,也可以通过动态计算邻域节点和该节点的强度关系weight来适应,所以GAT可移植到其他的图结构上,不需要与训练集完全相同。
对于节点特征向量 h = { h 1 , h 2 . . . h n } h=\{h_1,h_2...h_n\} h={h1,h2...hn},最后需要更新得到 h ′ = { h 1 ′ , h 2 ′ . . . h n ′ } h'=\{h'_1,h'_2...h'_n\} h′={h1′,h2′...hn′}。计算领域Attention使用MLP方法,即公式为: e i j = a T [ W h i ∣ ∣ W h j ] e_{ij}=a^T[Wh_i||Wh_j] eij=aT[Whi∣∣Whj] α i j = s o f t m a x ( e i j ) \alpha_{ij}=softmax(e_{ij}) αij=softmax(eij)该式计算,节点 j 对于节点 i 的重要性,a是MLP的权重,W是所有节点之间的权值(抽象特征,从h到h’的线性转换)。由上图左边就是W和a权重的区别和Attention的计算过程,清晰易懂。
由于使用的是masked attention,即只算邻居向量,所以不会关心整个图的结构问题,也就解决了问题(同时也是缺点)。最后乘以权重得到结果:
h
i
′
=
σ
(
∑
α
i
j
W
h
j
)
h'_i=\sigma(\sum \alpha_{ij}Wh_j)
hi′=σ(∑αijWhj)
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是原始的节点 seq_fts = tf.layers.conv1d(seq, out_sz, 1, use_bias=False)#一维卷积模拟nn投影 #自注意力self-attention f_1 = tf.layers.conv1d(seq_fts, 1, 1) #Wh,一维卷积模拟投影 f_2 = tf.layers.conv1d(seq_fts, 1, 1) #Wh logits = f_1 + tf.transpose(f_2, [0, 2, 1]) #拼接,注意力矩阵 #bias_mat是邻居mask矩阵,不是邻居的会有无限小(exp时就为0)mask coefs = tf.nn.softmax(tf.nn.leaky_relu(logits) + bias_mat) #softmax分配权重 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
完整的细节源代码逐行中文注释:https://github.com/nakaizura/Source-Code-Notebook/tree/master/GAT
非对称的注意权重
比较以下两个注意力权重公式。
e
i
j
=
a
(
W
h
i
,
W
h
j
)
e_{ij}=a(Wh_i,Wh_j)
eij=a(Whi,Whj)
e
i
j
=
a
T
[
W
h
i
∣
∣
W
h
j
]
e_{ij}=a^T[Wh_i||Wh_j]
eij=aT[Whi∣∣Whj]这里我之间奇怪了好久,为什么不直接算相似度计算注意力呢?非要拼接之后计算e。理由是这里拼接导致
e
i
j
e_{ij}
eij和
e
i
j
e_{ij}
eij是不等的 ,也就是说注意力值是非对称的!非对称的有点在于,我与大佬和大佬看我是完全不一样的东西,本来这种相互关系就是不对等。
LeakyReLU
这里同样也是为了非对称!首先按公式,拼接的部分是可以拆开的:
e
x
p
(
a
T
[
W
h
i
∣
∣
W
h
j
]
)
=
e
x
p
(
a
1
T
W
h
i
)
e
x
p
(
a
2
T
W
h
j
)
exp(a^T[Wh_i || Wh_j])=exp(a_1^TWh_i)exp(a_2^TWh_j)
exp(aT[Whi∣∣Whj])=exp(a1TWhi)exp(a2TWhj)那么如果不用LeakyReLU激活,直接softmax归一化话:
α
=
e
x
p
(
a
1
T
W
h
i
)
e
x
p
(
a
2
T
W
h
j
)
∑
k
∈
N
i
e
x
p
(
a
1
T
W
h
i
)
e
x
p
(
a
2
T
W
h
k
)
=
e
x
p
(
a
2
T
W
h
j
)
∑
k
∈
N
i
e
x
p
(
a
2
T
W
h
k
)
\alpha=\frac{exp(a_1^TWh_i)exp(a_2^TWh_j)}{\sum_{k \in N_i} exp(a_1^TWh_i)exp(a_2^TWh_k)}=\frac{exp(a_2^TWh_j)}{\sum_{k \in N_i} exp(a_2^TWh_k)}
α=∑k∈Niexp(a1TWhi)exp(a2TWhk)exp(a1TWhi)exp(a2TWhj)=∑k∈Niexp(a2TWhk)exp(a2TWhj)可以看到a1被约掉了。节点(i,j)之间的注意力权重都将不会考虑节点i的表示,所以LeakyReLU这也为了防止被约掉。
如何理解attention?
其实GCN版已经不算是很正统的谱域方法了,它也是直接把节点拿出来然后做特征变换再聚合,而且在“卷积”上GCN也并没有真的卷积,是借助拉普拉斯矩阵完成的。所以GAT认为还是需要一个卷积核比较好,所以公式中的a实际上就是这个功能。
谱方法与空间方法
区别在于:谱方法需要显式的定义卷积核,比如傅里叶那就是会投影到拉普拉斯的那个空间,小波变换就是小波变换的基函数了,即需要显式的定义。而近些年的空间方法不需要,只需要一个核函数就可以了,至于什么空间管他呢。这里可以联想到以前SVM需要显式的定义核,而监督学习方法出来之后只需要给一个loss就可以了。所以谱方法实际上是空间方法的一种特例,一种需要显式定义基空间的特例。
Transformer 与 GAT
两者都是用了self-attention,都是找关联,算注意力权重,再通过对上下文或者节点邻居聚合,最后得到节点or词的表示。嘛,虽然说Transformer其实是隐式图的暴力解,还是会有一点点不同:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。