Note:
Click here to download the full example code
Message Passing Tutorial
Author: Minjie Wang, Quan Gan, Yu Gai, Zheng Zhang
在本教程中,您将学习如何在小图上将不同级别的PageRank消息传递API。 在DGL中,消息传递和特征转换是用户定义的函数(UDFs)。
The PageRank algorithm
在PageRank的每次迭代中,每个节点(网页)首先将其PageRank值均匀地分散到其下游节点。 每个节点的新PageRank值是通过汇总从其邻居收到的PageRank值来计算的,然后通过阻尼因子进行调整:
此处,表示图中的节点数目,是节点的邻近节点,是节点可向外传递信息的边的数目,是节点的邻近节点的数目。
A naive implementation
使用networkx创建一个拥有100个节点的图,并将其转换为DGLGraph。
import networkx as nx
import matplotlib.pyplot as plt
import torch
import dgl
N = 100 # 节点数目
DAMP = 0.85 # 阻尼因子
K = 10 # 迭代次数
g = nx.nx.erdos_renyi_graph(N, 0.1)
g = dgl.DGLGraph(g)
nx.draw(g.to_networkx(), node_size=50, node_color=[[.5, .5, .5,]])
plt.show()
在该算法中,PageRank由两个典型的散布聚集模式阶段组成。首先将每一个节点的PageRank值初始化为,随后会将每一个节点的出度作为节点的一个特征。
g.ndata['pv'] = torch.ones(N) / N
g.ndata['deg'] = g.out_degrees(g.nodes()).float()
定义message 函数,该函数将每个节点的PageRank值除以其出度,然后将结果作为消息传递给其邻居。
def pagerank_message_func(edges):
return {'pv' : edges.src['pv'] / edges.src['deg']}
在DGL中,message 函数 被表现为 Edge UDFs.Edge UDFs 具有一个单个的argument:edges,edges拥有三个成员:src 、 dst和data。src用于获取边初始节点起点的特征,dst用于获取边终止节点的特征,data用于获取边的特征。此处,函数用于计算的信息仅仅来源于边的起点。
接下来我们将定义reduce函数,该函数使用它的mailbox功能删除并且汇总信息,以计算新的PageRank值。
def pagerank_reduce_func(nodes):
msgs = torch.sum(nodes.mailbox['pv'], dim=1)
pv = (1 - DAMP) / N + DAMP * msgs
return {'pv' : pv}
类似于message 函数是边的UDFs,reduce 函数是节点的UDFs.。节点UDFs 具有一个单个的argument:nodes,nodes拥有两个成员:data 和 mailbox。data中包含节点的特征,mailbox包含所有传递过来的信息特征,并且沿着第二维度(dim=1)堆叠,即计算加和。
message UDF作用在一批边上,而reduce UDF作用在一批边缘上,但输出在一批节点上。 它们之间的关系如下:
图片地址:https://i.imgur.com/kIMiuFb.png
构建message 函数和reduce 函数,稍后DGL将调用它。
g.register_message_func(pagerank_message_func)
g.register_reduce_func(pagerank_reduce_func)
该算法很简单。 这是一个PageRank迭代的代码。
def pagerank_naive(g):
# 第一部分: 发出所有边的信息.
for u, v in zip(*g.edges()):
g.send((u, v))
# 第二部分: 收到信息并且计算新的PageRank值.
for v in g.nodes():
g.recv(v)
zip(*a)的用法可见:https://blog.csdn.net/qq_42707449/article/details/81122741
Batching semantics for a large graph
之前的代码无法缩放到大图,因为它会遍历所有节点。 DGL通过允许您在一批节点或边上进行计算来解决此问题。 例如,以下代码一次触发消息并减少多个节点和边缘上的功能。
def pagerank_batch(g):
g.send(g.edges())
g.recv(g.nodes())
我们仍在使用之前的reduce函数pagerank_reduce_func,其中*nodes.mailbox [‘pv’]*是单个张量,将传入的消息沿第二维堆叠。
您可能想知道是否有可能在所有节点上并行执行reduce,因为每个节点可能有不同数量的传入消息,并且您无法真正将不同长度的张量真正“堆叠”在一起。 通常,DGL通过按传入消息的数量对节点进行分组并为每个组调用reduce函数来解决该问题。
Use higher-level APIs for efficiency
DGL提供了许多例程,这些例程以各种方式组合了基本的send和recv。 这些例程称为2级API。 例如,下面的代码示例演示如何使用此类API进一步简化PageRank示例。
def pagerank_level2(g):
g.update_all()
除了update_all,您还可以在此2级类别中使用pull,push和send_and_recv。 有关更多信息,请参阅API参考。
Use DGL builtin functions for efficiency
一些消息和归约功能经常使用。 因此,DGL还提供了内置函数。 例如,在PageRank示例中可以使用两个内置函数。
- dgl.function.copy_src(src, out)–此代码示例是edge UDF,它使用来源节点特征数据来计算输出。 要使用此功能,请指定源要素数据的名称(src)和输出名称(out)。
- dgl.function.sum(msg, out)–此代码示例是node UDF,用于对node mailbox中的信息进行汇总。 要使用此功能,请指定消息名称(msg)和输出名称(out)。
以下PageRank示例显示了此类功能。
import dgl.function as fn
def pagerank_builtin(g):
g.ndata['pv'] = g.ndata['pv'] / g.ndata['deg']
g.update_all(message_func=fn.copy_src(src='pv', out='m'),
reduce_func=fn.sum(msg='m',out='m_sum'))
g.ndata['pv'] = (1 - DAMP) / N + DAMP * g.ndata['m_sum']
在前面的示例代码中,您直接将UDF提供给update_all作为其参数。 这将覆盖先前构建的UDF。
除了更简洁的代码外,使用内置函数还使DGL有机会将操作融合在一起。 这样可以加快执行速度。 例如,DGL将把copy_src消息函数和sumreduce函数融合为一个稀疏矩阵向量(spMV)乘法。
以下部分描述了为什么spMV可以加快PageRank中的分散收集阶段。 有关DGL中内置函数的更多详细信息,请参见API参考。
您还可以下载并运行不同的代码示例以查看不同之处。
for k in range(K):
# Uncomment the corresponding line to select different version.
# pagerank_naive(g)
# pagerank_batch(g)
# pagerank_level2(g)
pagerank_builtin(g)
print(g.ndata['pv'])
out:
tensor([0.0106, 0.0114, 0.0139, 0.0099, 0.0080, 0.0023, 0.0098, 0.0091, 0.0072,
0.0113, 0.0081, 0.0073, 0.0121, 0.0096, 0.0098, 0.0105, 0.0082, 0.0063,
0.0091, 0.0073, 0.0131, 0.0139, 0.0064, 0.0130, 0.0124, 0.0097, 0.0147,
0.0075, 0.0100, 0.0083, 0.0073, 0.0091, 0.0124, 0.0114, 0.0100, 0.0090,
0.0107, 0.0169, 0.0106, 0.0090, 0.0066, 0.0105, 0.0090, 0.0090, 0.0147,
0.0098, 0.0088, 0.0107, 0.0138, 0.0146, 0.0098, 0.0140, 0.0065, 0.0048,
0.0125, 0.0113, 0.0121, 0.0066, 0.0066, 0.0097, 0.0090, 0.0149, 0.0090,
0.0112, 0.0113, 0.0108, 0.0098, 0.0066, 0.0066, 0.0122, 0.0064, 0.0113,
0.0099, 0.0081, 0.0081, 0.0100, 0.0131, 0.0107, 0.0065, 0.0106, 0.0071,
0.0106, 0.0123, 0.0097, 0.0130, 0.0122, 0.0079, 0.0090, 0.0065, 0.0106,
0.0113, 0.0106, 0.0082, 0.0098, 0.0129, 0.0123, 0.0096, 0.0097, 0.0131,
0.0090])
Using spMV for PageRank
使用内置函数,DGL可以理解UDF的语义。 这使您可以创建有效的实现。 例如,对于PageRank,一种加速它的常用方法是使用其线性代数形式。
这里,是迭代时所有节点的PageRank值的向量; 是图的稀疏邻接矩阵。 计算该方程式非常有效,因为存在用于稀疏矩阵矢量乘法(spMV)的高效GPU内核。 DGL通过内置函数检测这种优化是否可用。 如果可以将某种内置组合组合映射到spMV内核(例如PageRank示例),则DGL自动使用它。 我们建议尽可能使用内置函数。
Next steps
- 了解如何使用DGL(内置函数)编写更有效的消息传递。
- 要查看模型教程,请参见概述页面。
- 要了解Graph Neural Networks,请参阅GCN教程。
- 若要查看DGL如何批处理多个图形,请参见TreeLSTM教程。
- 通过遵循图的深度生成模型的教程来探究一些图生成模型。
- 要了解如何在图形视图中解释传统模型,请参阅CapsuleNet和Transformer上的教程。
Total running time of the script: ( 0 minutes 2.224 seconds)
下载代码::3_pagerank.py
下载代码::3_pagerank.ipynb
来源:CSDN
作者:平湖片帆
链接:https://blog.csdn.net/weixin_45613751/article/details/104115622