第六周.02.VGAE带读+代码实操


本文内容整理自深度之眼《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(ZX,A)=i=1Nq(ziX,A),with q(ziX,A)=N(ziμi,diag(σi2))
上式中,是要最大所有节点的概率的连乘,这个概率 q ( z i ∣ X , A ) q(z_i|X,A) q(ziX,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(AZ)=i=1Nj=1Np(Aijzi,zj);withp(Aij=1zi,zj)=σ(zizj)
这个公式的意思就是根据邻接矩阵找到有边相连的两个节点( 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(ZX,A)[logp(AZ)]KL[q(ZX,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
#定义VGAEModel
class VGAEModel(nn.Module):
    def __init__(self, in_dim, hidden1_dim, hidden2_dim):#初始化VGAE
        super(VGAEModel, self).__init__()
        self.in_dim = in_dim#输入特征维度
        self.hidden1_dim = hidden1_dim#两个隐藏层维度
        self.hidden2_dim = hidden2_dim

        #三层GraphConv,原文中生成均值和方差的W0是共享的,W1是不同的,因此一共要三层
        #https://docs.dgl.ai/en/0.6.x/_modules/dgl/nn/pytorch/conv/graphconv.html
        #GraphConv用于实现GCN的卷积
        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)#第一层得到输出h
        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)#标准高斯分布采样,大小是features_size*hidden2_dim
        sampled_z = self.mean + gaussian_noise * torch.exp(self.log_std).to(device)#这里其实是reparameterization trick,具体看公式1和代码如何对应
        return sampled_z

    def decoder(self, z):
        adj_rec = torch.sigmoid(torch.matmul(z, z.t()))#解码器点乘还原邻接矩阵A'
        return adj_rec

    def forward(self, g, features):#前向传播
        z = self.encoder(g, features)#编码器得到隐变量
        adj_rec = self.decoder(z)#解码器还原邻接矩阵
        return adj_rec

注意

1.要在jupyter里面跑VGAE,需要改train里面的

args = parser.parse_args()

这个是常规操作,改成:

args = parser.parse_args(args=[])

2.train里面的dgl_main

train_graph = dgl.edge_subgraph(graph, train_edge_idx, relabel_nodes=False)

这个报错,看了一下官网,估计这个是dgl版本的问题,
https://docs.dgl.ai/generated/dgl.edge_subgraph.html?highlight=edge_subgraph#dgl.edge_subgraph
改成:

train_graph = dgl.edge_subgraph(graph, train_edge_idx, preserve_nodes=True)

结果

在这里插入图片描述
在这里插入图片描述
补充:
关于How powerful of GNN这个文章的讲解在CS224W笔记里面有

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
下面是一个使用PyTorch实现的VGAE代码示例: ``` import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv, VGAE from torch_geometric.datasets import Planetoid # 加载Cora数据集 dataset = Planetoid(root='/tmp/Cora', name='Cora') data = dataset[0] # 创建VGAE模型 class VGAEModel(torch.nn.Module): def __init__(self): super(VGAEModel, self).__init__() self.conv1 = GCNConv(dataset.num_features, 16) self.conv2 = GCNConv(16, dataset.num_classes) self.vgae = VGAE(self.conv1, self.conv2) def encode(self, x, edge_index): return self.vgae.encode(x, edge_index) def reparameterize(self, mu, logvar): if self.training: std = torch.exp(0.5 * logvar) eps = torch.randn_like(std) return eps.mul(std).add(mu) else: return mu def decode(self, z, edge_index): return self.vgae.decode(z, edge_index) def forward(self, x, edge_index): z, mu, logvar = self.encode(x, edge_index) z = self.reparameterize(mu, logvar) return self.decode(z, edge_index), mu, logvar model = VGAEModel() # 训练模型 optimizer = torch.optim.Adam(model.parameters(), lr=0.01) def train(): model.train() optimizer.zero_grad() recon, mu, logvar = model(data.x, data.edge_index) loss = F.binary_cross_entropy(recon, data.adjacency_matrix.to_dense()) loss = loss + (1 / data.num_nodes) * (-0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())) loss.backward() optimizer.step() for epoch in range(200): train() print('Epoch:', epoch, 'Loss:', loss.item()) # 在测试集上评估模型 model.eval() z, _, _ = model.encode(data.x, data.edge_index) pred = model.decode(z, data.edge_index) # 可以使用重构误差或其他指标来评估模型的性能 ```
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值