本文内容整理自深度之眼《GNN核心能力培养计划》
公式输入请参考:
在线Latex公式
本周涉及到auto encoder,这个思想在CV和语音处理上都有应用,有想简单了解这块的,看我的笔记:
Deep Auto-encoder
More About Auto-Encoder VAE看这里:Unsupervised Learning.05: Deep Generative Model (Part I) 本节主要讲VGAE:Variational Graph Auto-Encoders Variational Graph Auto-Encoders相对于普通的Graph Auto-Encoders而言,相同之处在于大家都是数据经过Encoder,得到表征,然后经过Decoder,还原得到数据’(这里还原的是邻接矩阵),目标是使得数据和数据’越接近越好,是一种无监督的学习方式。Variational Graph Auto-Encoders则引入了更多的贝叶斯的东西来优化损失函数。 总共涉及三篇文章: Variational Graph Auto-Encoders Auto-Encoding Variational Bayes Tutorial on Variational Autoencoders(比较详细) 当然还要参考之前GNN基础篇的深度之眼Paper带读笔记GNN.03.SDNE 下面来看Variational Graph Auto-Encoders这篇文章
Variational Graph Auto-Encoders
开篇文章就提出VGAE是a framework for unsupervised learning on graph-structured data based on the variational auto-encoder 模型还引入了隐变量来进行计算,如果理解不了就直接把这个计算得到的隐变量看做是AE结构里面中间的embedding表示。 模型使用均值和方差的采样结果(这个结果就是上面提到的隐变量)来代替常规AE模型中的中间表征。 this model using a graph convolutional network (GCN) [4] encoder and a simple inner product decoder. 模型的编码器用的GCN,解码器用的是点乘。 模型在非监督学习的图结构数据以及边预测(这个任务是)任务取得较好效果。
定义:无向无权图
G
=
(
V
,
E
)
\mathcal{G}=(\mathcal{V,E})
G=(V,E),节点数量
N
=
∣
V
∣
N=|\mathcal{V}|
N=∣V∣,图的邻接矩阵是
A
A
A,且里面包含节点本身的信息(相当于
A
′
=
A
+
I
A'=A+I
A′=A+I),图的度矩阵是
D
D
D,引入的隐变量是
z
i
z_i
zi?,其矩阵形式是:
Z
Z
Z,维度是
N
×
F
N\times F
N×F,节点特征是
X
X
X,维度是
N
×
D
N\times D
N×D
Inference model:
q
(
Z
∣
X
,
A
)
=
∏
i
=
1
N
q
(
z
i
∣
X
,
A
)
,
w
i
t
h
?
q
(
z
i
∣
X
,
A
)
=
N
(
z
i
∣
μ
i
,
d
i
a
g
(
σ
i
2
)
)
q(Z|X,A)=\prod_{i=1}^Nq(z_i|X,A),with\space q(z_i|X,A)=\mathcal{N}(z_i|\mu_i,diag(\sigma_i^2))
q(Z∣X,A)=i=1∏N?q(zi?∣X,A),with?q(zi?∣X,A)=N(zi?∣μi?,diag(σi2?)) 上式中,是要最大所有节点的概率的连乘,这个概率
q
(
z
i
∣
X
,
A
)
q(z_i|X,A)
q(zi?∣X,A)的条件是两个已知条件,节点的特征和邻接矩阵,那么每个隐变量
z
i
z_i
zi?相当于从一个高斯分布(
μ
i
,
σ
i
2
\mu_i,\sigma_i^2
μi?,σi2?)进行采样得到的结果。高斯分布的两个参数都是从不同的encoder GCN得来:
μ
=
G
C
N
μ
(
X
,
A
)
,
log
?
σ
=
G
C
N
σ
(
X
,
A
)
\mu=GCN_\mu(X,A),\log\sigma=GCN_\sigma(X,A)
μ=GCNμ?(X,A),logσ=GCNσ?(X,A) 两个GCN都是两层的,结构一样,第一层参数一样,但是第二层参数不一样,因此两个GCN的下标不一样。 Generative model:Decoder部分就是用上面得到的两个点的隐变量做内积:
p
(
A
∣
Z
)
=
∏
i
=
1
N
∏
j
=
1
N
p
(
A
i
j
∣
z
i
,
z
j
)
;
w
i
t
h
p
(
A
i
j
=
1
∣
z
i
,
z
j
)
=
σ
(
z
i
?
z
j
)
p (A|Z) =\prod_{i=1}^N\prod_{j=1}^Np (A_{ij} | z_i,z_j); with p (A_{ij}= 1 | z_i,z_j) = \sigma(z_i^\top zj)
p(A∣Z)=i=1∏N?j=1∏N?p(Aij?∣zi?,zj?);withp(Aij?=1∣zi?,zj?)=σ(zi??zj) 这个公式的意思就是根据邻接矩阵找到有边相连的两个节点(
A
i
j
=
1
A_{ij}= 1
Aij?=1),然后把两个点的隐变量做内积(实际上就是相似度计算),然后经过sigmoid函数变成概率。 损失函数:
L
=
E
q
(
Z
∣
X
,
A
)
[
log
?
p
(
A
∣
Z
)
]
?
K
L
[
q
(
Z
∣
X
,
A
)
∣
∣
p
(
Z
)
]
L = E_{q(Z|X,A)}[\log p (A|Z)]-KL[q(Z|X,A)|| p(Z)]
L=Eq(Z∣X,A)?[logp(A∣Z)]?KL[q(Z∣X,A)∣∣p(Z)] 这里注意两点: 1、由于A的稀疏性,使用了权重项来平衡值为1的数量比较小的位置,这个trick和SDNE是一样的,具体可以看之前的SDNE笔记中,关于一阶二阶相似度的描述; 2、中间生成隐变量的过程中用到了采样这个操作,这个操作是不可导的,因此不能直接做反向传播计算,在李宏毅的课程里面提到过有几种解决方案(太久了忘记在哪篇里面了),这里用了一种reparameterization trick来解决这个问题。 这个trick的公式在下面的文章,这里先搬上来:
z
=
μ
(
X
)
+
Σ
1
/
2
(
X
)
?
,
?
~
N
(
0
,
I
)
(1)
z = \mu(X) + \Sigma^{1/2}(X) \epsilon,\epsilon\sim\mathcal{N}(0,I)\tag1
z=μ(X)+Σ1/2(X)?,?~N(0,I)(1)
GAE
graph auto-encoder(GAE) model就是把中间采样隐变量的步骤去掉,直接用GCN得到Z,再用点乘还原邻接矩阵。
A
^
=
σ
(
Z
Z
?
)
,
w
i
t
h
?
Z
=
G
C
N
(
X
,
A
)
\hat A=\sigma(ZZ^\top) , with\space Z=GCN(X,A)
A^=σ(ZZ?),with?Z=GCN(X,A)
实验
用边预测来测试模型效果(VGAE和GAE),训练的时候,预先将数据集中的一些边拿掉,点的特征不变,然后再把边拿回来,做成验证和测试集。除了这两个模型,文章还加了谱聚类和DeepWalk两个基线,这两个模型都可以得到节点的表征,然后用表征可以丢到上面的
A
^
=
σ
(
Z
Z
?
)
\hat A=\sigma(ZZ^\top)
A^=σ(ZZ?),从而比较模型还原的效果。 表中,带星号的表示用的独热编码作为节点特征初始化。
Tutorial on Variational Autoencoders
原文的图4,编码器用的GCN,解码器用的是点乘 上图中左边是正常流程,总结红色代表采样操作,这个操作由于不可导,反向传播无法使用,因此将其改成右边的的方式,先从标准的高斯分布进行采样,然后在做乘法,然后再加。
VGAE实操
原PY代码看这里
from dgl.nn.pytorch import GraphConv
import torch
import torch.nn as nn
import torch.nn.functional as F
class VGAEModel(nn.Module):
def __init__(self, in_dim, hidden1_dim, hidden2_dim):
super(VGAEModel, self).__init__()
self.in_dim = in_dim
self.hidden1_dim = hidden1_dim
self.hidden2_dim = hidden2_dim
layers = [GraphConv(self.in_dim, self.hidden1_dim, activation=F.relu, allow_zero_in_degree=True),
GraphConv(self.hidden1_dim, self.hidden2_dim, activation=lambda x: x, allow_zero_in_degree=True),
GraphConv(self.hidden1_dim, self.hidden2_dim, activation=lambda x: x, allow_zero_in_degree=True)]
self.layers = nn.ModuleList(layers)
def encoder(self, g, features):
h = self.layers[0](g, features)
self.mean = self.layers[1](g, h)
self.log_std = self.layers[2](g, h)
gaussian_noise = torch.randn(features.size(0), self.hidden2_dim).to(device)
sampled_z = self.mean + gaussian_noise * torch.exp(self.log_std).to(device)
return sampled_z
def decoder(self, z):
adj_rec = torch.sigmoid(torch.matmul(z, z.t()))
return adj_rec
def forward(self, g, features):
z = self.encoder(g, features)
adj_rec = self.decoder(z)
return adj_rec
|