线图神经网络(Line graph neural network, LGNN)
这一部分,我们可以通过实现一个线图神经网络(LGNN)来解决社团检测。社团检测,或者说图聚类,指的是将图中的节点划分为簇,而且簇内的节点比簇间的节点具有更高的相似性。
在图神经网络中,我们可以了解到如何以一种半监督方式来对一张输入图的节点进行分类。我们使用图卷积网络作为图特征的嵌入机制。
为了将图神经网络概况为有监督的社团检测, 论文Supervised Community Detection with Line Graph Neural Networks.引入一种基于线图的GNN的变体。该模型的一大亮点是增强了简单的GNN结构,所以它可以在基于非回溯操作定义的线图的邻接边上进行操作。
一个线图神经网络(LGNN)展示了DGL如何通过混合基础张量操作、稀疏矩阵乘法以及消息传递APIs来实现高级的图算法。
下面将介绍社团检测、线图、LGNN及其实现。
Cora数据集上的监督社团检测任务
社团检测
在一个社团检测任务中,我们需要做的是将相似的节点进行聚类,而不是给它们贴上标签。典型的节点相似性被描述为每个类内具有较高的密度。
社团检测和节点分类的区别是什么?与节点分类相比,社团检测强调的是从图上检索聚类信息,而不是给每个节点指派特定的标签。例如,只要一个节点和它的社团成员被聚为一类,那么这个节点属于“社团A”还是“社团B”并不重要,尽管在电影网络分类任务中,把所有“好电影”贴上“烂电影”的标签将是一场灾难。
那么,社区检测算法与其他聚类算法(如k-means)之间的区别是什么呢?社区检测算法作用于图结构数据。与k-means相比,社区检测利用了图结构,而不是简单地根据节点的特性进行集群化。
Cora数据集
Cora是一个科学出版物数据库,有2708篇论文属于七个不同的机器学习领域。这里将Cora表示为一个有向图,每个节点是一篇论文,每个边是一个引用链接(a ->B表示a引用B)。
Cora包含7个类,下面的统计表明,每个类都满足我们对community的假设,即相同类的节点之间的连接概率要高于不同类的节点之间的连接概率。下面的代码片段验证了类内的边比类间的多。
import torch as th
import torch.nn as nn
import torch.nn.functional as F
import dgl
from dgl.data import citation_graph as citegrhdata = citegrh.load_cora()G = dgl.DGLGraph(data.graph)
labels = th.tensor(data.labels)# find all the nodes labeled with class 0
label0_nodes = th.nonzero(labels == 0).squeeze()
# find all the edges pointing to class 0 nodes
src, _ = G.in_edges(label0_nodes)
src_labels = labels[src]
# find all the edges whose both endpoints are in class 0
intra_src = th.nonzero(src_labels == 0)
print('Intra-class edges percent: %.4f' % (len(intra_src) / len(src_labels)))
Out:
Intra-class edges percent: 0.7680
带有测试集的Cora数据集的二分类社团子图
为了不失一般性,这里限制任务范围为二分类社团检测。
注意:
为了从Cora中创建一个实际的二分类社团数据集,首先需要从原始的Cora的7各个类提取所有两个类对。对于每一对类,我们需要将一类看作一个社团,并且找到至少包含一条社团间边的最大子图作为训练示例。结果,在这个小的数据集中共有21个训练样本。
下面的代码显示了如何可视化其中一个训练样本及其社团结构。
import networkx as nx
import matplotlib.pyplot as plttrain_set = dgl.data.CoraBinary()
G1, pmpd1, label1 = train_set[1]
nx_G1 = G1.to_networkx()def visualize(labels, g):pos = nx.spring_layout(g, seed=1)plt.figure(figsize=(8, 8))plt.axis('off')nx.draw_networkx(g, pos=pos, node_size=50, cmap=plt.get_cmap('coolwarm'), node_color=labels, edge_color='k', arrows=False, width=0.5, style='dotted', with_labels=False)
visualize(label1, nx_G1)
原文中给出了如何可视化多个社团的例子。
有监督的社团检测
有监督和无监督的方法都可以做到社团检测。在监督方式下,社团检测的公式化见下:
- 每个训练示例包括(G,L)(G, L)(G,L),其中GGG是一个有向图(V,E)(V, E)(V,E)。对于V中的每个节点vvv,我们指定一个真实的社团标签zv∈(0,1)z_v\in(0, 1)zv∈(0,1)。
- 参数模型KaTeX parse error: Undefined control sequence: \seta at position 6: f(G, \̲s̲e̲t̲a̲)对VVV中的所有节点预测标签集合z~=f(G)\tilde{z}=f(G)z~=f(G)。
- 对每个示例(G,L)(G, L)(G,L),模型通过学习来最小化一个专门设计的损失函数Lequivariant=(Z~,Z)L_{equivariant} =(\tilde{Z}, Z)Lequivariant=(Z~,Z)。
注意:
在有监督的设定中,模型会对每个社团预测一个标签。然而, 社团的指定应该等价于标签的排列。为了做到这一点,在每个前向传播过程中,我们选取所有可能标签排列所得到的最小的损失。
数学上,这意味着
其中,ScS_cSc是标签排列的集合,π^\hat{\pi}π^表示预测标签的集合,−log(π^,π)-\log(\hat{\pi}, \pi)−log(π^,π)指的是负的对数概率。
例如,对于也个样本图,其节点有1,2,3,4{1, 2, 3, 4}1,2,3,4和分配的社团A,A,A,B{A, A, A, B}A,A,A,B,每个节点的标签l∈(0,1)l\in(0, 1)l∈(0,1),所有可能的排列组合为ScS_cSc = {{0, 0, 0, 1}, {1, 1, 1, 0}}。
LGNN核心思想
这里的关键创新点是使用线图。不同于之前的模型,消息传递不仅在原图上,比如,来自Cora的二分类社团子图,而且作用在与原图对应的线图上。
线图是什么?
图论中,线图是一种编码原图中边邻居结构的图表示。
具体地,一个线图L(G)L(G)L(G)将原图GGG中的边转换成节点。
这里,eA:=(i−>j)e_A:=(i->j)eA:=(i−>j)和eB:=(j−>k)e_B:=(j->k)eB:=(j−>k)是原图GGG中的两条边。在线图GLG_LGL中,它们分别对应于节点vAlv_A^{l}vAl和vBlv_B^{l}vBl。
那么自然的一个问题是,线图中节点是如何连接的?如何连接两条边呢?这里,我们使用下面的连接规则:
lglglg中两节点vAlv_A^{l}vAl和vBlv_B^{l}vBl是连接的如果ggg中对应的两条边eAe_AeA和eBe_BeB共享且只共享一个节点:eAe_AeA的终节点是eBe_BeB的源节点。
注意:
数学上,这个定义对应于称之为非回溯操作的概念:
当Bnode1,node2=1B_{node1, node2} =1Bnode1,node2=1时,会形成一条边。
LGNN的一层——算法结构
LGNN将一系列线图神经网路层连在一起。图表示xxx及其相应的线图yyy随数据流的变化如下。
在第kkk层,第lll个通道的第iii个神经元利用下面的式子更新其嵌入xi,l(k+1)x_{i, l}^{(k+1)}xi,l(k+1):
接下来,线图的表示yi,l(k+1)y_{i, l}^{(k+1)}yi,l(k+1)如下:
其中,skip−connectionskip-connectionskip−connection指的是执行相同的操作,不包含非线性的ρ\rhoρ,包含线性映射
用DGL实现LGNN
即使上一节中的方程式可能看起来令人生畏,但在实现LGNN之前它有助于理解以下信息。
这两个式子是对称的,可以作为同一类中不同参数的两个实例。第一个式子作用在图表示xxx上,而第二个式子作用在线图表示yyy上。我们把这些抽象表示为fff。那第一个表示为f(x,y;θx)f(x, y; \theta_x)f(x,y;θx),第二个表示为f(y,x,θy)f(y, x, \theta_y)f(y,x,θy)。即,将它们参数化,以分别计算原图和对应线图的表示。
每个式子都包含四项,以第一个为例,后面的类似。
- x(k)θ1,l(k)x^{(k)}\theta_{1, l}^{(k)}x(k)θ1,l(k),是上一层输出x(k)x^{(k)}x(k)的线性映射,表示为prev(x)prev(x)prev(x)。
- (Dx(k))θ2,l(k)(D_x^{(k)})\theta_{2, l}^{(k)}(Dx(k))θ2,l(k),作用在x(k)x^{(k)}x(k)上的度操作的线性映射,表示为deg(x)deg(x)deg(x)。
作用于x(k)x^{(k)}x(k)上的2j2^j2j的邻接操作的和,表示为radius(x)radius(x)radius(x)。
使用关联矩阵{Pm,PdP_m, P_dPm,Pd}融合另一个图的嵌入信息,然后加上一个线性映射,表示为fuse(y)fuse(y)fuse(y)。
每一项都会用不同的参数多次计算,并且在求和之后不再进行非线性化。因此,fff可以写成:
两个式子通过下面的顺序连接:
注意:
通过下面的解释,你可以对{Pm,PdP_m, P_dPm,Pd}有更为透彻的理解。大概地讲,就是在循环简单的传递中,ggg和lglglg(线图)之间的关系。这里,{Pm,PdP_m, P_dPm,Pd}可作为数据集中的SciPy COO稀疏矩阵来完成,并在分批时将它们堆叠为张量。另一种分批的方法是把{Pm,PdP_m, P_dPm,Pd}看作二部图的邻接矩阵,它实现了从线图特征到图的映射,反之亦然。
实现prevprevprev和degdegdeg的张量操作
线性映射和度操作都只是简单的矩阵乘法。将它们写成PyTorch的张量操作。
在__init__中,定义映射变量。
self.linear_prev = nn.Linear(in_feats, out_feats)
self.linear_deg = nn.Linear(in_feats, out_feats)
在forward()中,prevprevprev和degdegdeg同其它PyTorch的张量操作一样。
prev_proj = self.linear_prev(feat_a)
prev_proj = self.linear_deg(deg * feat_a)
DGL中实现radiusradiusradius作为消息传递
正如GCN中提到的,我们可以形成一个邻接算子作为一步的消息传递。作为推广,2j2^j2j邻接操作可以通过执行2j2^j2j步消息传递来做到。因此,其和等同于2j2^j2j个节点表示的和,j=0,1,2..j=0, 1, 2..j=0,1,2..步消息传递。
在__init__中,定义用在2j2^j2j步消息传递的映射变量。
self.linear_radius = nn.ModuleList([nn.Linear(in_feats, out_feats) for i in range(radius)])
在__forward__中,使用下面的函数aggregate_radius()来聚合多跳的数据。注意update_all被调用多次。
# Return a list containing features gathered from multiple radius.
import dgl.function as fn
def aggregate_radius(radius, g, z):# initializing list to collect message passing resultz_list = []g.ndata['z'] = z# pulling message from 1-hop neighbourhoodg.update_all(fn.copy_src(src='z', out='m'), fn.sum(msg='m', out='z'))z_list.append(g.ndata['z'])for i in range(radius - 1):for j in range(2 ** i):# pulling message from 2^j neighborhoodg.update_all(fn.copy_src(src='z', out='m'), fn.sum(msg='m', out='z'))z_list.append(g.ndata['z'])return z_list
实现fusefusefuse作为稀疏矩阵乘法
{Pm,PdP_m, P_dPm,Pd}是一个每列只有两个非零输入的稀疏矩阵。因此,我们可以将其构建为数据集中的一个稀疏矩阵,并通过稀疏矩阵乘法来实现fusefusefuse。
在__forward__中:
fuse = self.linear_fuse(th.mm(pm_pd, feat_b))
f(x,y)f(x, y)f(x,y)的完成
最后,下面展示了如何将这些项加起来,并将它传递给skip connection和batch norm。
result = prev_proj + deg_proj + radius_proj + fuse
传递结果到skip connection。
result = th.cat([result[:, :n], F.relu(result[:, n:])], 1)
然后将结果传递给batch norm。
result = self.bn(result) # Batch Normalization
这里是一个LGNN层的提取f(x,y)f(x, y)f(x,y)的完整代码。
class LGNNCore(nn.Module):def __init__(self, in_feats, out_feats, radius):super(LGNNCore, self).__init__()self.out_feats = out_featsself.radius = radiusself.linear_prev = nn.Linear(in_feats, out_feats)self.linear_deg = nn.Linear(in_feats, out_feats)self.linear_radius = nn.ModuleList([nn.Linear(in_feats, out_feats) for i in range(radius)])self.linear_fuse = nn.Linear(in_feats, out_feats)self.bn = nn.BatchNorm1d(out_feats)def forward(self, g, feat_a, feat_b, deg, pm_pd):# term "prev"prev_proj = self.linear_prev(feat_a)# term "deg"deg_proj = self.linear_deg(deg * feat_a)# term "radius"# aggregate 2^j-hop featureshop2j_list = aggregate_radius(self.radius, g, feat_a)# apply linear transformationhop2j_list = [linear(x) for linear, x in zip(self.linear_radius, hop2j_list)]radius_proj = sum(hop2j_list)# term "fuse"fuse = self.linear_fuse(th.mm(pm_pd, feat_b))# sum them togetherresult = prev_proj + deg_proj + radius_proj + fuse# skip connection and batch normn = self.out_feats // 2result = th.cat([result[:, :n], F.relu(result[:, n:])], 1)result = self.bn(result)return result
将LGNN的提取连起来作为LGNN的一层
为了实现
示例代码中,将两个LGNNCore实例连起来,在前向传递过程中有不同的参数。
class LGNNLayer(nn.Module):def __init__(self, in_feats, out_feats, radius):super(LGNNLayer, self).__init__()self.g_layer = LGNNCore(in_feats, out_feats, radius)self.lg_layer = LGNNCore(in_feats, out_feats, radius)def forward(self, g, lg, x, lg_x, deg_g, deg_lg, pm_pd):next_x = self.g_layer(g, x, lg_x, deg_g, pm_pd)pm_pd_y = th.transpose(pm_pd, 0, 1)next_lg_x = self.lg_layer(lg, lg_x, x, deg_lg, pm_pd_y)return next_x, next_lg_x
连接LGNN层
用三个隐藏层定义一个LGNN,见下例。
Define an LGNN with three hidden layers, as in the following example.
class LGNN(nn.Module):def __init__(self, radius):super(LGNN, self).__init__()self.layer1 = LGNNLayer(1, 16, radius) # input is scalar featureself.layer2 = LGNNLayer(16, 16, radius) # hidden size is 16self.layer3 = LGNNLayer(16, 16, radius)self.linear = nn.Linear(16, 2) # predice two classesdef forward(self, g, lg, pm_pd):# compute the degreesdeg_g = g.in_degrees().float().unsqueeze(1)deg_lg = lg.in_degrees().float().unsqueeze(1)# use degree as the input featurex, lg_x = deg_g, deg_lgx, lg_x = self.layer1(g, lg, x, lg_x, deg_g, deg_lg, pm_pd)x, lg_x = self.layer2(g, lg, x, lg_x, deg_g, deg_lg, pm_pd)x, lg_x = self.layer3(g, lg, x, lg_x, deg_g, deg_lg, pm_pd)return self.linear(x)
训练和预测
首先加载数据。
from torch.utils.data import DataLoader
training_loader = DataLoader(train_set, batch_size=1, collate_fn=train_set.collate_fn, drop_last=True)
然后定义主训练循环。注意到每个训练样本包含三个对象:一个DGLGraph,一个SciPy稀疏矩阵和一个numpy.ndarray的标签数组。使用下面的命令来创建线图:
lg = g.line_graph(backtracking=False)
注意到backtracking=False是为了正确模拟非回溯操作。我们也定义了一个通用函数来转换SciPy稀疏矩阵为torch稀疏张量。
# Create the model
model = LGNN(radius=3)
# define the optimizer
optimizer = th.optim.Adam(model.parameters(), lr=1e-2)# A utility function to convert a scipy.coo_matrix to torch.SparseFloat
def sparse2th(mat):value = mat.dataindices = th.LongTensor([mat.row, mat.col])tensor = th.sparse.FloatTensor(indices, th.from_numpy(value).float(), mat.shape)return tensor# Train for 20 epochs
for i in range(20):all_loss = []all_acc = []for [g, pmpd, label] in training_loader:# Generate the line graph.lg = g.line_graph(backtracking=False)# Create torch tensorspmpd = sparse2th(pmpd)label = th.from_numpy(label)# Forwardz = model(g, lg, pmpd)# Calculate loss:# Since there are only two communities, there are only two permutations# of the community labels.loss_perm1 = F.cross_entropy(z, label)loss_perm2 = F.cross_entropy(z, 1 - label)loss = th.min(loss_perm1, loss_perm2)# Calculate accuracy:_, pred = th.max(z, 1)acc_perm1 = (pred == label).float().mean()acc_perm2 = (pred == 1 - label).float().mean()acc = th.max(acc_perm1, acc_perm2)all_loss.append(loss.item())all_acc.append(acc.item())optimizer.zero_grad()loss.backward()optimizer.step()niters = len(all_loss)print("Epoch %d | loss %.4f | accuracy %.4f" % (i,sum(all_loss) / niters, sum(all_acc) / niters))
Out:
Epoch 0 | loss 0.5751 | accuracy 0.6873
Epoch 1 | loss 0.5025 | accuracy 0.7742
Epoch 2 | loss 0.5078 | accuracy 0.7551
Epoch 3 | loss 0.4895 | accuracy 0.7624
Epoch 4 | loss 0.4682 | accuracy 0.7910
Epoch 5 | loss 0.4461 | accuracy 0.7992
Epoch 6 | loss 0.4815 | accuracy 0.7838
Epoch 7 | loss 0.4542 | accuracy 0.7970
Epoch 8 | loss 0.4338 | accuracy 0.8172
Epoch 9 | loss 0.4694 | accuracy 0.7604
Epoch 10 | loss 0.4525 | accuracy 0.7958
Epoch 11 | loss 0.4388 | accuracy 0.7941
Epoch 12 | loss 0.4440 | accuracy 0.8092
Epoch 13 | loss 0.4325 | accuracy 0.7982
Epoch 14 | loss 0.4087 | accuracy 0.8137
Epoch 15 | loss 0.4073 | accuracy 0.8129
Epoch 16 | loss 0.4123 | accuracy 0.8133
Epoch 17 | loss 0.4061 | accuracy 0.8201
Epoch 18 | loss 0.4100 | accuracy 0.8123
Epoch 19 | loss 0.4170 | accuracy 0.8348
可视化训练过程
pmpd1 = sparse2th(pmpd1)
LG1 = G1.line_graph(backtracking=False)
z = model(G1, LG1, pmpd1)
_, pred = th.max(z, 1)
visualize(pred, nx_G1)
visualize(label1, nx_G1)
对图进行分批并行处理
LGNN选择不同图的集合,分批可以用作并行。
批处理可以由数据加载器本身做的。 在PyTorch数据加载器的collate_fn中,使用DGL的batched_graph API对图进行批处理。 DGL通过将图合并成一个大图来对图进行批处理,每个较小图的邻接矩阵都是沿着大图邻接矩阵对角线的一个块。 将数学中{Pm,Pd}连接为块对角线矩阵,对应于DGL批处理图API。
def collate_fn(batch):graphs, pmpds, labels = zip(*batch)batched_graphs = dgl.batch(graphs)batched_pmpds = sp.block_diag(pmpds)batched_labels = np.concatenate(labels, axis=0)return batched_graphs, batched_pmpds, batched_labels
线图神经网络(Line graph neural network, LGNN)相关推荐
- 图神经网络(Graph Neural Networks)
图神经网络(Graph Neural Network)在社交网络.推荐系统.知识图谱上的效果初见端倪,成为近2年大热的一个研究热点.然而,什么是图神经网络?图和神经网络为什么要关联?怎么关联? 本文简 ...
- 图神经网络(Graph Neural Networks,GNN)综述
鼠年大吉 HAPPY 2020'S NEW YEAR 作者:苏一 https://zhuanlan.zhihu.com/p/75307407 本文仅供学术交流.如有侵权,可联系删除. 本篇文章是对论文 ...
- 图神经网络(Graph neural networks)综述
论文链接:Graph Neural Networks: A Review of Methods and Applications Abstract:图(Graph)数据包含着十分丰富的关系型信息.从文 ...
- 图神经网络(Graph Neural Networks)概述
论文:A Comprehensive Survey on Graph Neural Networks 一篇关于图神经网络的综述文章,着重介绍了图卷积神经网络(GCN),回顾了近些年的几个主要的图神经网 ...
- 【社交推荐图神经网络】Graph Neural Networks for Social Recommendation
用于社会推荐的图神经网络 [原文链接] [摘要]该文提供了一种方法来联合捕获用户-项目图中的交互和意见,并提出了框架GraphRec,该框架对两个图进行了一致建模.该模型由用户建模.项目建模和评级预测 ...
- 图神经网络方法总结(Graph Neural Network)
图神经网络方法(Graph Neural Network) 概要 近年来图神经网络受到大家越来越多的关注,在文本分类(Text classification),序列标注(Sequence labeli ...
- Heterogeneous Graph Neural Network(异质图神经网络)
Heterogeneous Information Network 传统的同构图(Homogeneous Graph)中只存在一种类型的节点和边,当图中的节点和边存在多种类型和各种复杂的关系时,再采用 ...
- Introduction to Graph Neural Network(图神经网络概论)翻译:目录总览
前言:最近对图神经网络部分比较感兴趣,偶尔看到清华大学刘知远老师在今年3月份发表的一本书:Introduction to Graph Neural Network,于是将该书内容进行翻译,记录阅读中自 ...
- Graph Decipher: A transparent dual-attention graph neural network 图解密器:一种透明的双注意图神经网络,用于理解节点分类的消息传递机制
引用 Pang Y, Liu C. Graph Decipher: A transparent dual-attention graph neural network to understand th ...
- 图神经网络综述 Survey on Graph Neural Network
图神经网络综述 Survey on Graph Neural Network 摘要:近几年来,将深度学习应用到处理和图结构数据相关的任务中越来越受到人们的关注.图神经网络的出现使其在上述任务中取得了重 ...
最新文章
- PowerShell 2.0 实践(十二)管理 SQL Server 2008 R2(1)
- Docker的使用(二:Docker客户端常用指令练习)
- HtmlUnit自动填写表单并提交
- 算法小白——基本排序算法入门
- springMVC,aop管理log4j,把当前session信息和错误信息打印到日志
- vscode java settings设置_兼容vscode插件的主题服务
- BCGControlBar的使用方法
- 阿里P8好友说,要想成为Java大佬,就得要多逛逛国外的这几个技术网站...
- 2018-2019 C++期末复习资料
- PHP+HTML简单实现BBS论坛与回帖
- Python selenium 实现大麦网自动购票过程
- SMT32的RTC的晶振为什么是32768Hz
- 实验一 第1关:从自然数中取3个数进行组合之循环算法
- 用瑞利准则研究显微镜物镜的分辨率
- 【HTML5】嵌入另一张HTML文档、通过插件嵌入内容、嵌入数字表现形式
- 育碧遭黑客攻击:用户账号被窃
- 网页表格线框html,网页表格中单元格线条及边框的设置
- 2021(ICPC)-Jiangxi_Continued Fraction
- http cookie设置失效
- linux编译CH340驱动报错,CH340 Linux驱动使用教程
热门文章
- 网易云音乐API获取分析
- mac html字体设置,Mac Win 网页字体显示方案
- docker安装gamit_Ubuntu Linux下安装GAMIT
- 如何用十步写一首原创歌曲
- 201871010114-李岩松《面向对象程序设计(java)》第一周学习总结
- Python机器视觉--OpenCV进阶(核心)-边缘检测之SIFT关键点检测
- does not have permission to content://包名/external_files/sple/IMG_123123141.jpg
- 工作后出国留学经历总结
- 关于嵌入式的学习和职业发展
- 软件工程--快速原型模型详解