VGAE(Variationalgraphauto-encoders)论文及代码解读

更新时间:2023-06-23 22:09:31 阅读: 评论:0

VGAE(Variationalgraphauto-encoders)论⽂及代码解读
⼀,论⽂来源
论⽂pdf
论⽂代码
⼆,论⽂解读
理论部分参考:
简要介绍: 本⽂是将变分⾃编码器(Variational Auto-Encoders, VAE
)迁移到了图领域,基本思路是:⽤已知的图经过编码(图卷积)学到节点向量表⽰的分布,在分布中采样得到节点的向量表⽰,然后进⾏解码(链路预测)重新构建图。
简要介绍引⽤来⾃:
细节⽅⾯感觉这个⼩姐姐讲的⾮常好:
理论⽅⾯其实前⾯⼏位⼤佬说的很好了,这⾥我只是来阐述两点:
1.测试⽅法:
模型在数据集的不完整版本上训练,其中部分引⽤链接(边)已被移除,⽽所有节点特征被保留。 我们从先前移除的边和相同数量的随机采样的未连接节点对(⾮边)形成验证和测试集。我们根据模型正确分类边缘和⾮边缘的能⼒来⽐较模型。 验证和测试集分别包含5%和10%的引⽤链接。 验证集⽤于优化超参数。 我们对⽐了两个流⾏的基线:谱聚类(SC) [5]和深度⾏⾛(DW) [6]。 SC和DW都提供节点嵌⼊z。 我们⽤Eq.4(左侧)计算重构邻接矩阵元素的得分。
2.熵
参考
三,代码解读
preprocessing.py
import numpy as np
import scipy.spar as sp
def spar_to_tuple(spar_mx):
if not sp.isspmatrix_coo(spar_mx):#是否为csr_matrix类型
spar_mx = ()#实现csc矩阵转换为coo矩阵
coords = np.vstack((w, l)).transpo()
# np.vstack按垂直⽅向(⾏顺序)堆叠数组构成⼀个新的数组,堆叠的数组需要具有相同的维度,transpo()作⽤是转置
# np.vstack按垂直⽅向(⾏顺序)堆叠数组构成⼀个新的数组,堆叠的数组需要具有相同的维度,transpo()作⽤是转置
values = spar_mx.data
shape = spar_mx.shape
return coords, values, shape
def preprocess_graph(adj):
adj = sp.coo_matrix(adj)#csr_matrix转成coo_matrix
adj_ = adj + sp.eye(adj.shape[0])#S=A+I  #注意adj_的类型为csr_matrix
rowsum = np.array(adj_.sum(1))#rowsum的shape=(节点数,1),对于cora数据集来说就是(2078,1),sum(1)求每⼀⾏的和
degree_mat_inv_sqrt = sp.diags(np.power(rowsum,-0.5).flatten())#计算D^{-0.5}
#p.diags:提取输⼊矩阵(⼤⼩为m×n)的所有⾮零对⾓列。输出的⼤⼩为 min(m,n)×p,其中p表⽰输⼊矩阵的p个⾮零对⾓列
#numpy.power():⽤于数组元素求n次⽅
#flatten():返回⼀个折叠成⼀维的数组。
adj_normalized = adj_.dot(degree_mat_inv_sqrt).transpo().dot(degree_mat_inv_sqrt).tocoo()
# adj_.dot(degree_mat_inv_sqrt)得到 SD^{-0.5}数学学习方法
# adj_.dot(degree_mat_inv_sqrt).transpo()得到(D^{-0.5})^{T}S^{T}=D^{-0.5}S,因为D和S都是对称矩阵
# adj_normalized即为D^{-0.5}SD^{-0.5}
return spar_to_tuple(adj_normalized)
def mask_test_edges(adj):
# Function to build test t with 10% positive links
# NOTE: Splits are randomized and results might slightly deviate from reported numbers in the paper.
# TODO: Clean up.
# Remove diagonal elements
adj = adj - sp.dia_matrix((adj.diagonal()[np.newaxis,:],[0]), shape=adj.shape)
adj.eliminate_zeros()
# Check that diag is zero:
asrt np.den()).sum()==0
#asrt断⾔是声明其布尔值必须为真的判定,如果发⽣异常就说明表达⽰为假。
#toden()⽅法将稀疏矩阵b转换成稠密矩阵c
adj_triu = sp.triu(adj)#取出稀疏矩阵的上三⾓部分的⾮零元素,返回的是coo_matrix类型
adj_tuple = spar_to_tuple(adj_triu)
edges = adj_tuple[0]
# 取除去节点⾃环的所有边(注意,由于adj_tuple仅包含原始邻接矩阵上三⾓的边,所以edges中的边虽然只记录了边<src,dis>,⽽不冗余记录边<dis,src>),shape=(边数,2)每⼀⾏记录⼀条边的起始节点和终点节点的编号
edges_all = spar_to_tuple(adj)[0]
# 取原始graph中的所有边,shape=(边数,2)每⼀⾏记录⼀条边的起始节点和终点节点的编号
外语专业排名
num_test =int(np.floor(edges.shape[0]/10.))#划分测试集
# np.floor返回数字的下舍整数
num_val =int(np.floor(edges.shape[0]/20.))#划分验证集
all_edge_idx =list(range(edges.shape[0]))
np.random.shuffle(all_edge_idx)#打乱all_edge_idx的顺序
val_edge_idx = all_edge_idx[:num_val]#划分验证集
test_edge_idx = all_edge_idx[num_val:(num_val + num_test)]#划分测试集
test_edges = edges[test_edge_idx]#edges是除去节点⾃环的所有边(因为数据集中的边都是⽆向的,edges只是存储了<src,dis>,没有存储<dis,src>,因为没必要浪费内存),shape=(边数,2)每⼀⾏记录⼀条边的起始节点和终点节点的编号
val_edges = edges[val_edge_idx]
train_edges = np.delete(edges, np.hstack([test_edge_idx, val_edge_idx]), axis=0)
# np.vstack():在竖直⽅向上堆叠,np.hstack():在⽔平⽅向上平铺。
# np.hstack([test_edge_idx, val_edge_idx])将两个list⽔平⽅向拼接成⼀维数组
# np.delete的参数axis=0,表⽰删除多⾏,删除的⾏号由第⼀个参数确定
def ismember(a, b, tol=5):
rows_clo = np.und(a - b[:,None], tol)==0, axis=-1)
# np.round返回浮点数x的四舍五⼊值,第⼆参数是保留的⼩数的位数
# b[:, None]使b从shape=(边数,2)变为shape=(边数,1,2),⽽a是长度为2的list,a - b[:, None]触发numpy的⼴播机制
# np.all()判断给定轴向上的所有元素是否都为True,axis=-1(此时等同于axis=2)表⽰3维数组最⾥层的2维数组的每⼀⾏的元素是否都为True return np.any(rows_clo)
# np.any()判断给定轴向上是否有⼀个元素为True,现在不设置axis参数则是判断所有元素中是否有⼀个True,有⼀个就返回True。
# rows_clo的shape=(边数,1)
# ⾄此,可以知道,ismember( )⽅法⽤于判断随机⽣成的<a,b>这条边是否是已经真实存在的边,如果是,则返回True,否则返回Fal
test_edges_fal =[]
test_edges_fal =[]
while len(test_edges_fal)<len(test_edges):
idx_i = np.random.randint(0, adj.shape[0])#⽣成负样本
idx_j = np.random.randint(0, adj.shape[0])
if idx_i == idx_j:
continue
if ismember([idx_i, idx_j], edges_all):
continue
if test_edges_fal:
if ismember([idx_j, idx_i], np.array(test_edges_fal)):
continue
if ismember([idx_i, idx_j], np.array(test_edges_fal)):
continue
test_edges_fal.append([idx_i, idx_j])
val_edges_fal =[]
while len(val_edges_fal)<len(val_edges):
idx_i = np.random.randint(0, adj.shape[0])
idx_j = np.random.randint(0, adj.shape[0])
if idx_i == idx_j:
continue
if ismember([idx_i, idx_j], train_edges):
continue
if ismember([idx_j, idx_i], train_edges):
continue
if ismember([idx_i, idx_j], val_edges):
continue
if ismember([idx_j, idx_i], val_edges):
continue
if val_edges_fal:
if ismember([idx_j, idx_i], np.array(val_edges_fal)):
continue
if ismember([idx_i, idx_j], np.array(val_edges_fal)):
continue
val_edges_fal.append([idx_i, idx_j])sankeyou
asrt~ismember(test_edges_fal, edges_all)#asrt(断⾔)⽤于判断⼀个表达式,在表达式条件为 fal 的时候触发异常。所以,这⾥是想要edges_ all不含有test_edges_fal,否则抛异常
asrt~ismember(val_edges_fal, edges_all)
asrt~ismember(val_edges, train_edges)
asrt~ismember(test_edges, train_edges)
asrt~ismember(val_edges, test_edges)
data = np.ones(train_edges.shape[0])
# 重建出⽤于训练阶段的邻接矩阵
adj_train = sp.csr_matrix((data,(train_edges[:,0], train_edges[:,1])), shape=adj.shape)
adj_train = adj_train + adj_train.T
# #注意:这些边列表只包含⼀个⽅向的边(adj_train是矩阵,不是edge lists)
return adj_train, train_edges, val_edges, val_edges_fal, test_edges, test_edges_fal
1.hstack()/vstack()分析
参考:
np.vstack():在竖直⽅向上堆叠,np.hstack():在⽔平⽅向上平铺。
np.hstack([test_edge_idx, val_edge_idx])将两个list⽔平⽅向拼接成⼀维数组
2.preprocess_graph函数中格式转换:
参考:
3.b[:, None]维度扩充
这个我做了测试:
import numpy as np
a = np.array([[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]])
b = a[:,None]
c = a[None,:]
d = a[:,:,None]
print(a.shape)
print(b.shape)
print(c.shape)
print(d.shape)
结果:
(4, 4)
(4, 1, 4)
(1, 4, 4)
(4, 4, 1)
可以发现None在哪,哪扩充了⼀维
model.py
import torch
as nn
functional as F
import os
import numpy as np
import args
class VGAE(nn.Module):
def__init__(lf, adj):
super(VGAE,lf).__init__()
lf.ba_gcn = GraphConvSpar(args.input_dim, args.hidden1_dim, adj)
<_mean = GraphConvSpar(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
#lambda是匿名函数,冒号左边是参数,多个参数⽤逗号隔开,右边是表达式
<_logstddev = GraphConvSpar(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
def encode(lf, X):
hidden = lf.ba_gcn(X)
lf.logstd = lf.gcn_logstddev(hidden)
gaussian_noi = torch.randn(X.size(0), args.hidden2_dim)
sampled_z = gaussian_p(lf.logstd)+ lf.mean
# 这⾥使⽤p是因为论⽂中log(sigma)=GCN_{sigma}(X,A),p(lf.logstd)即p(log(sigma))得到的是sigma;另外还有mu=GCN_{mu}( X,A).
sahara# 由于每个节点向量经过GCN后都有且仅有⼀个节点向量表⽰,所以呢,⽅差的对数log(sigma)和节点向量表⽰的均值mu分别是节点经过GCN_{sigma}(X,A)和GCN_{mu}(X,A)后得到的向量表⽰本⾝。
# 从N(mu,sigma^2)中采样⼀个样本Z相当于在N(0,1)中采样⼀个xi,然后Z = mu + xi×sigma
return sampled_z
def forward(lf, X):
Z = lf.encode(X)
A_pred = dot_product_decode(Z)
同文英语
return A_pred
class GraphConvSpar(nn.Module):
def__init__(lf, input_dim, output_dim, adj, activation = F.relu,**kwargs):
super(GraphConvSpar, lf).__init__(**kwargs)
lf.weight = glorot_init(input_dim, output_dim)
lf.adj = adj
lf.activation = activation
2016欧洲杯分组def forward(lf, inputs):
x = inputs
besttone
x = (x,lf.weight)
#(a, b)是矩阵a和b矩阵相乘
tallx = (lf.adj, x)
outputs = lf.activation(x)
return outputs
def dot_product_decode(Z):
A_pred = torch.sigmoid(torch.matmul(Z,Z.t()))
return A_pred
def glorot_init(input_dim, output_dim):
init_range = np.sqrt(6.0/(input_dim + output_dim))
initial = torch.rand(input_dim, output_dim)*2*init_range - init_range
价值观的英文
return nn.Parameter(initial)
class GAE(nn.Module):
def__init__(lf,adj):
super(GAE,lf).__init__()
lf.ba_gcn = GraphConvSpar(args.input_dim, args.hidden1_dim, adj)
<_mean = GraphConvSpar(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
def encode(lf, X):
hidden = lf.ba_gcn(X)
z = lf.mean = lf.gcn_mean(hidden)
return z
def forward(lf, X):
Z = lf.encode(X)
A_pred = dot_product_decode(Z)
return A_pred
# class GraphConv(nn.Module):
#  def __init__(lf, input_dim, hidden_dim, output_dim):
#  super(VGAE,lf).__init__()
#  lf.ba_gcn = GraphConvSpar(args.input_dim, args.hidden1_dim, adj)
#  lf.gcn_mean = GraphConvSpar(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x)
#  lf.gcn_logstddev = GraphConvSpar(args.hidden1_dim, args.hidden2_dim, adj, activation=lambda x:x) #  def forward(lf, X, A):
#  out = A*X*lf.w0
#  out = F.relu(out)
#  out = A*X*lf.w0
#  return out
1.lambda匿名函数
参考:
这⾥注意:匿名函数不需要return来返回值,表达式本⾝结果就是返回值。
train.py
import torch
distinctlyfunctional as F
from torch.optim import Adam
ics import roc_auc_score, average_precision_score
import scipy.spar as sp
import numpy as np
import os
import time

本文发布于:2023-06-23 22:09:31,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/90/155317.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:节点   矩阵   数组   参数
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图