SDNE
前一篇整理了3种常用的图嵌入方法,DeepWalk,LINE和Node2vec。Structural Deep Network Embeddings(结构深层网络嵌入,SDNE)的不同之处在于,它并不是基于随机游走的思想,在实践中比较稳定。
主要思路如上图,会将节点向量 si 作为模型的输入,通过自编码器对这个向量进行降维压缩 zi,然后再重建特征。其损失函数定义为:
O
2
=
∑
∣
∣
s
i
′
−
s
i
∣
∣
2
2
O_2=\sum ||s_i'-s_i||^2_2
O2=∑∣∣si′−si∣∣22
因为输入的是邻接矩阵,所以这样的重构能够使得结构相似的顶点具有相似的embedding表示向量。所以实际上通过重建学习到的是二阶相似度。
但是与LINE相似,SDNE也想保留一阶和二阶相似度,而且是想要同时优化,以同时捕获局部成对相似性和节点邻域结构的相似性。对于一阶相似度的计算,将架构变成如上图,由左右两个自编码器组成。一阶相似度的目标是计算节点间的相似性,那么可以利用起中间的嵌入得到的隐层向量z,然后计算左侧嵌入和右侧嵌z入间的距离。并可以用拉普拉斯矩阵优化一下计算:
O
1
=
∑
∣
∣
z
1
−
z
2
∣
∣
2
2
=
=
2
t
r
(
Z
T
L
Z
)
O_1=\sum ||z_1-z_2||^2_2==2tr(Z^TLZ)
O1=∑∣∣z1−z2∣∣22==2tr(ZTLZ)
再加上正则项reg,总损失为: L = O 2 + α O 1 + v O r e g L=O_2+\alpha O_1+vO_{reg} L=O2+αO1+vOreg论文出自KDD 2016的《Structural Deep network Embedding》,作者的完整代码code开源:https://github.com/suanrong/SDNE
def __make_loss(self, config):
def get_1st_loss_link_sample(self, Y1, Y2):
return tf.reduce_sum(tf.pow(Y1 - Y2, 2))
def get_1st_loss(H, adj_mini_batch):
D = tf.diag(tf.reduce_sum(adj_mini_batch,1))
L = D - adj_mini_batch ## 拉普拉斯矩阵
return 2*tf.trace(tf.matmul(tf.matmul(tf.transpose(H),L),H))
def get_2nd_loss(X, newX, beta):
B = X * (beta - 1) + 1
return tf.reduce_sum(tf.pow((newX - X)* B, 2))
#对w和b的正则化项
def get_reg_loss(weight, biases):
ret = tf.add_n([tf.nn.l2_loss(w) for w in weight.itervalues()])
ret = ret + tf.add_n([tf.nn.l2_loss(b) for b in biases.itervalues()])
return ret
#总损失函数
self.loss_2nd = get_2nd_loss(self.X, self.X_reconstruct, config.beta)
self.loss_1st = get_1st_loss(self.H, self.adjacent_matriX)
self.loss_xxx = tf.reduce_sum(tf.pow(self.X_reconstruct,2))
self.loss_reg = get_reg_loss(self.W, self.b)
return config.gamma * self.loss_1st + config.alpha * self.loss_2nd + config.reg * self.loss_reg
其实似乎这个建模的思路和矩阵分解是差不多的,只是在降维时用的不是矩阵分解,而是自编码器,并融入了一阶与二阶的概念。
Graph2vec
出自MLGWorkshop 2017的《graph2vec: Learning Distributed Representations of Graphs》
直接对整个图进行嵌入。一般比较容易想到的做法是Graph Pooling,著名的方法有Graph Coarsening,即用其他处理节点Embedding的方法处理后,然后逐渐合并聚类节点比如同一个class就归为一个超节点,最后变成一个向量。还有一类方法是node selection,即选部分的节点来代替。还有更直接的方法是直接max或者mean咯。
而Graph2Vec原理上和DeepWalk差不多,也是尝试借用word2vec来训练,只是这次为了嵌入整张图,所以尝试利用子图来训练。类似文档嵌入doc2vec,通过最大化作为输入的文档,来预测随机单词的可能性,Graph2vec预测图中子图的概率。
- 采样子图。为了提高效率,采样只选当前节点周围的邻接节点,构成子图sg。
- skip-gram。最大化输入图子图的概率
J
(
Φ
)
=
−
l
o
g
P
(
s
g
n
(
d
)
∣
Φ
(
G
)
)
J(\Phi)=-log P(sg_n^{(d)}|\Phi(G))
J(Φ)=−logP(sgn(d)∣Φ(G)),d为度,sg为子图。
#把图处理成doc2vec问题的形式就可以用gensim了
from gensim.models.doc2vec import Doc2Vec, TaggedDocument
#得到Wisfeiler-Lehman特征
def do_a_recursion(self):
new_features = {}
for node in self.nodes:
nebs = self.graph.neighbors(node)
degs = [self.features[neb] for neb in nebs]
features = [str(self.features[node])]+sorted([str(deg) for deg in degs])
features = "_".join(features)
hash_object = hashlib.md5(features.encode())
hashing = hash_object.hexdigest()
new_features[node] = hashing
self.extracted_features = self.extracted_features + list(new_features.values())
return new_features
作者开源:https://github.com/benedekrozemberczki/graph2vec
Graph Generative Adversarial Nets(GraphGAN)
主要是将GAN的思想引入到图领域,简单来说也是和GAN一样,分为生成器和判别器:
- 生成器:在找网络中一个节点邻居的概率分布,也就是一个节点的连接偏好。 G ( v ∣ v c ) = e x p ( g v T g v c ) ∑ v ≠ v c e x p ( g v T g v c ) G(v|v_c)=\frac{exp(g^T_v g_{v_c})}{\sum_{v \neq v_c}exp(g^T_v g_{v_c}) } G(v∣vc)=∑v=vcexp(gvTgvc)exp(gvTgvc)即用softmax函数,输出邻居是每个节点的概率
- 判别器:以网络中的边为出发点,衡量两个节点之间是否有边关系。 D ( v , v c ) = σ ( d v T d v c ) D(v,v_c)=\sigma(d^T_v d_{v_c}) D(v,vc)=σ(dvTdvc)d分别代表两个节点的表示向量,如果v是采样的应该给高分,如果是生成的应该是低分。
值得注意的是在生成器的G中,如果要对每个节点都算一遍显然不划算,所以作者的解决方案是以某个节点为根节点BFS遍历形成一棵生成树(其实挺像随机游走的),然后计算树中两个节点之间的边对应的概率。
Graph Neural Network
图神经网络(Graph Neural Network, GNN)也是很好的图嵌入方法。GNN包括图卷积神经网络(GCN),图注意力网络(GAT),Graph LSTM等等,虽然技术上还是和CV,NLP领域内的方法很像。。。
图表征学习的局限
- 参数量大,每个顶点都有唯一的标准向量
- 不能给未出现的顶点生成表征向量
- 部分图表征学习不能融合属性特征等信息
最后献上很全很全的论文集:https://github.com/benedekrozemberczki/awesome-graph-classification