深入理解变分图自编码器(VGAE):原理、特点、作用及实现

图神经网络(Graph Neural Networks, GNNs)在处理图结构数据方面展现出强大的能力。其中,变分图自编码器(Variational Graph Auto-Encoder, VGAE)是一种无监督学习模型,广泛用于图嵌入和图聚类任务。本文将深入探讨VGAE的原理、特点、作用及其具体实现。

原理

VGAE结合了图自编码器(Graph Auto-Encoder, GAE)和变分自编码器(Variational Auto-Encoder, VAE)的思想,能够有效地学习图结构数据的节点嵌入。其核心思想是通过变分推理方法,在低维潜在空间中表示节点,从而能够重构图结构。VGAE的主要组成部分包括:

  1. 编码器(Encoder):使用图卷积网络(GCN)将输入特征编码为潜在变量的均值和方差。
  2. 变分推理部分(Variational Inference):通过重参数化技巧从均值和方差中采样潜在变量。
  3. 解码器(Decoder):通过内积操作重构邻接矩阵,从潜在变量中恢复图结构。
特点
  • 无监督学习:VGAE无需标签信息,可以通过图结构数据自动学习节点表示。
  • 变分推理:通过引入变分推理,VGAE能够有效处理数据中的不确定性。
  • 图结构重构:通过重构邻接矩阵,VGAE在捕捉图结构信息方面表现出色。
作用
  • 图嵌入:VGAE能够将高维的节点特征映射到低维的潜在空间,生成节点的嵌入表示。
  • 图聚类:通过学习到的节点嵌入,可以应用聚类算法进行节点聚类。
  • 图结构重构:通过重构邻接矩阵,可以用于图补全和图生成任务。
实现

下面是一个使用PyTorch和PyTorch Geometric库实现VGAE的具体示例。我们将以Cora数据集为例,展示如何构建和训练VGAE模型。

import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv, VGAE
from torch_geometric.datasets import Planetoid
from torch_geometric.utils import train_test_split_edges

# 加载Cora数据集
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0]

class GCNEncoder(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super(GCNEncoder, self).__init__()
        self.conv1 = GCNConv(in_channels, 2 * out_channels)
        self.conv2 = GCNConv(2 * out_channels, 2 * out_channels)
        self.conv_mu = GCNConv(2 * out_channels, out_channels)
        self.conv_logvar = GCNConv(2 * out_channels, out_channels)

    def forward(self, x, edge_index):
        x = F.relu(self.conv1(x, edge_index))
        x = F.relu(self.conv2(x, edge_index))
        return self.conv_mu(x, edge_index), self.conv_logvar(x, edge_index)

# 初始化模型
channels = 16
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = VGAE(GCNEncoder(dataset.num_features, channels)).to(device)
data = train_test_split_edges(data).to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

def train():
    model.train()
    optimizer.zero_grad()
    z = model.encode(data.x, data.train_pos_edge_index)
    loss = model.recon_loss(z, data.train_pos_edge_index)
    loss = loss + (1 / data.num_nodes) * model.kl_loss()
    loss.backward()
    optimizer.step()
    return loss.item()

for epoch in range(1, 201):
    loss = train()
    print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')

model.eval()
with torch.no_grad():
    z = model.encode(data.x, data.train_pos_edge_index)
    auc, ap = model.test(z, data.test_pos_edge_index, data.test_neg_edge_index)
    print(f'AUC: {auc:.4f}, AP: {ap:.4f}')
细节

从输入变量的维度变化角度来看:

  1. 输入层

    • 节点特征矩阵 X:维度 N×F
    • 邻接矩阵 A:维度 N×N
  2. 编码器

    • 第一层GCN卷积层:输入维度 N×F,输出维度 N×2C
    • 第二层GCN卷积层:输入维度 N×2C,输出维度 N×2C
    • 均值和方差GCN层:输入维度 N×2C,输出维度 N×C
  3. 变分推理部分

    • 生成节点嵌入 Z:维度 N×C
  4. 解码器

    • 重构的邻接矩阵 A^:维度 N×N

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自编码器(Graph Autoencoder,简称GAE)是一种用于学习形数据表示的无监督学习方法,它将形数据表示为低维嵌入向量。变分自编码器(Variational Graph Autoencoder,简称VGAE)是一种改进的GAE,它使用变分推断来学习潜在嵌入的概率分布。下面是使用Python实现GAE和VGAE的示例代码: 1. GAE代码: ```python import numpy as np import tensorflow as tf class GraphAutoencoder(object): def __init__(self, n_input, n_hidden): self.n_input = n_input self.n_hidden = n_hidden self.weights = { 'encoder': tf.Variable(tf.random_normal([n_input, n_hidden])), 'decoder': tf.Variable(tf.random_normal([n_hidden, n_input])) } self.biases = { 'encoder': tf.Variable(tf.random_normal([n_hidden])), 'decoder': tf.Variable(tf.random_normal([n_input])) } self.inputs = tf.placeholder(tf.float32, [None, n_input]) self.encoder = tf.nn.sigmoid(tf.add(tf.matmul(self.inputs, self.weights['encoder']), self.biases['encoder'])) self.decoder = tf.nn.sigmoid(tf.add(tf.matmul(self.encoder, self.weights['decoder']), self.biases['decoder'])) self.cost = tf.reduce_mean(tf.pow(self.inputs - self.decoder, 2)) self.optimizer = tf.train.AdamOptimizer(learning_rate=0.01).minimize(self.cost) def train(self, X, epochs=1000): with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for epoch in range(epochs): _, cost = sess.run([self.optimizer, self.cost], feed_dict={self.inputs: X}) if epoch % 100 == 0: print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(cost)) self.weights['encoder'] = sess.run(self.weights['encoder']) self.weights['decoder'] = sess.run(self.weights['decoder']) self.biases['encoder'] = sess.run(self.biases['encoder']) self.biases['decoder'] = sess.run(self.biases['decoder']) return self def transform(self, X): with tf.Session() as sess: sess.run(tf.global_variables_initializer()) encoder_output = sess.run(self.encoder, feed_dict={self.inputs: X}) return encoder_output ``` 2. VGAE代码: ```python import numpy as np import tensorflow as tf class VariationalGraphAutoencoder(object): def __init__(self, n_input, n_hidden): self.n_input = n_input self.n_hidden = n_hidden self.weights = { 'encoder_mean': tf.Variable(tf.random_normal([n_input, n_hidden])), 'encoder_stddev': tf.Variable(tf.random_normal([n_input, n_hidden])), 'decoder': tf.Variable(tf.random_normal([n_hidden, n_input])) } self.biases = { 'encoder_mean': tf.Variable(tf.random_normal([n_hidden])), 'encoder_stddev': tf.Variable(tf.random_normal([n_hidden])), 'decoder': tf.Variable(tf.random_normal([n_input])) } self.inputs = tf.placeholder(tf.float32, [None, n_input]) self.encoder_mean = tf.add(tf.matmul(self.inputs, self.weights['encoder_mean']), self.biases['encoder_mean']) self.encoder_stddev = tf.add(tf.matmul(self.inputs, self.weights['encoder_stddev']), self.biases['encoder_stddev']) eps = tf.random_normal(tf.shape(self.encoder_stddev), dtype=tf.float32, mean=0., stddev=1.0, name='epsilon') self.encoder_output = tf.add(self.encoder_mean, tf.multiply(tf.sqrt(tf.exp(self.encoder_stddev)), eps)) self.decoder = tf.nn.sigmoid( tf.add(tf.matmul(self.encoder_output, self.weights['decoder']), self.biases['decoder'])) self.cost = self.get_cost() self.optimizer = tf.train.AdamOptimizer(learning_rate=0.01).minimize(self.cost) def get_cost(self): kl_divergence = -0.5 * tf.reduce_sum( 1 + 2 * self.encoder_stddev - tf.square(self.encoder_mean) - tf.exp(2 * self.encoder_stddev), 1) reconstruction_loss = -tf.reduce_sum( self.inputs * tf.log(1e-10 + self.decoder) + (1 - self.inputs) * tf.log(1e-10 + 1 - self.decoder), 1) cost = tf.reduce_mean(reconstruction_loss + kl_divergence) return cost def train(self, X, epochs=1000): with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for epoch in range(epochs): _, cost = sess.run([self.optimizer, self.cost], feed_dict={self.inputs: X}) if epoch % 100 == 0: print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(cost)) self.weights['encoder_mean'] = sess.run(self.weights['encoder_mean']) self.weights['encoder_stddev'] = sess.run(self.weights['encoder_stddev']) self.weights['decoder'] = sess.run(self.weights['decoder']) self.biases['encoder_mean'] = sess.run(self.biases['encoder_mean']) self.biases['encoder_stddev'] = sess.run(self.biases['encoder_stddev']) self.biases['decoder'] = sess.run(self.biases['decoder']) return self def transform(self, X): with tf.Session() as sess: sess.run(tf.global_variables_initializer()) encoder_output = sess.run(self.encoder_output, feed_dict={self.inputs: X}) return encoder_output ``` 希望对你有所帮助!接下来是三个相关问题:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值