DGL官方教程--信息传递

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值来计算的,然后通过阻尼因子进行调整:
P V ( u ) = 1 − d N + d × ∑ v ∈ N ( u ) P V ( v ) D ( v ) PV(u) = \frac{1-d}{N} + d \times \sum_{v \in \mathcal{N}(u)} \frac{PV(v)}{D(v)} PV(u)=N1d+d×vN(u)D(v)PV(v)
此处, N N N表示图中的节点数目, v v v是节点 u u u的邻近节点, D ( v ) D(v) D(v)是节点 v v v可向外传递信息的边的数目, N ( u ) \mathcal{N}(u) N(u)是节点 u u u的邻近节点的数目。

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()

DGL官方教程--信息传递_第1张图片

在该算法中,PageRank由两个典型的散布聚集模式阶段组成。首先将每一个节点的PageRank值初始化为 1 N \frac{1}{N} N1,随后会将每一个节点的出度作为节点的一个特征。

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拥有三个成员:srcdstdatasrc用于获取边初始节点起点的特征,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拥有两个成员:datamailboxdata中包含节点的特征,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级类别中使用pullpushsend_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,一种加速它的常用方法是使用其线性代数形式。
R k = 1 − d N 1 + d A ∗ R k − 1 \mathbf{R}^{k} = \frac{1-d}{N} \mathbf{1} + d \mathbf{A}*\mathbf{R}^{k-1} Rk=N1d1+dARk1
这里, R k \mathbf{R}^k Rk是迭代 k k k时所有节点的PageRank值的向量; A A A是图的稀疏邻接矩阵。 计算该方程式非常有效,因为存在用于稀疏矩阵矢量乘法(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

你可能感兴趣的:(DGL概览)