PageRank算法简单实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014281392/article/details/89791576

PageRank with DGL Message Passing

  • 使用DGL实现PageRank算法

PageRank算法

PageRank让链接来"投票"

  一个页面的“得票数”由所有链向它的页面的重要性来决定,到一个页面的超链接相当于对该页投一票。一个页面的PageRank是由所有链向它的页面(“链入页面”)的重要性经过递归算法得到的。一个有较多链入的页面会有较高的等级,相反如果一个页面没有任何链入页面,那么它没有等级。

  假设一个由4个页面组成的小团体:A,B,C和D。如果所有页面都链向A,那么A的PR(PageRank)值将是B,C及D的Pagerank总和。
 
P R ( A ) = P R ( B ) + P R ( C ) + P R ( D ) PR(A) = PR(B) + PR(C) + PR(D)

  继续假设B也有链接到C,并且D也有链接到包括A的3个页面。一个页面不能投票2次。所以B给每个页面半票。以同样的逻辑,D投出的票只有三分之一算到了A的PageRank上。

P R ( A ) = P R ( B ) 2 + P R ( C ) 1 + P R ( D ) 3 PR(A) = \frac{PR(B)}{2} + \frac{PR(C)}{1} + \frac{PR(D)}{3}

由于“没有向外链接的页面”传递出去的PageRank会是0,所以,给每个页面一个最小值: ( 1 d ) N \frac{(1-d)}{N}

P R ( u ) = 1 d N + d × v N ( u ) P R ( v ) D ( v ) PR(u) = \frac{1-d}{N} + d \times \sum_{v \in \mathcal{N}(u)} \frac{PR(v)}{D(v)}

  • N N is the number of nodes in the graph.
  • D ( v ) D(v) is the out-degree of a node v v .
  • N ( u ) \mathcal{N}(u) is the neighbornodes.
  • d d is damping factor.

简单实现

  • 创建一个包含有100个节点(网页)的网络,use networkx
  • erdos_renyi_graph(),return a random graph
  • Parameters:
    • n (int) – The number of nodes.
    • p (float) – Probability for edge creation.
    • seed (integer, random_state, or None (default)) – Indicator of random number generation state.
    • directed (bool, optional (default=False)) – If True, this function returns a directed graph.
import dgl
import torch
import networkx as nx
import matplotlib.pyplot as plt
%matplotlib inline

N = 50  # number of nodes
DAMP = 0.85  # damping factor,阻尼系数
K = 10  # number of iterations
g = nx.nx.erdos_renyi_graph(N, 0.1)
g = dgl.DGLGraph(g)
plt.figure(figsize=(15, 8))
nx.draw(g.to_networkx(), node_size=50, node_color=[[.5, .5, .5,]])

在这里插入图片描述

step 1

PageRank consists of two phases in a typical scatter-gather pattern.

  • 初始化每个节点的PR值为 1 N \frac{1}{N} .
  • 把每个节点的出度(out-degree)作为图中节点的特征.
# pr : init pagerank value
# od : out degree
g.ndata['pr'] = torch.ones(N) / N
g.ndata['od'] = g.out_degrees(g.nodes()).float()

# g.ndata['pr']
g.nodes[:].data['pr']
tensor([0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,
        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,
        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,
        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,
        0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200, 0.0200,
        0.0200, 0.0200, 0.0200, 0.0200, 0.0200])
# g.nodes[:].data['od']
g.ndata['od']
tensor([ 5.,  4.,  4.,  6.,  5.,  5.,  5.,  9.,  5.,  8.,  9., 10.,  4.,  4.,
         1.,  6.,  6.,  6.,  4.,  7.,  5.,  8.,  7.,  6.,  1.,  5.,  5.,  5.,
         5.,  6.,  4.,  7.,  1.,  7.,  7.,  2.,  4.,  5.,  2.,  6.,  7.,  6.,
         6.,  2.,  3.,  7.,  2.,  5.,  5.,  6.])

UDFs : works on a batch of edges, nodes

  • message function : 计算每个节点传递给它的邻接节点的PR值(message)
    • 传入参数为edges,deges{src, dst, data}
      • edges.src[‘pr’] : 访问边src端节点的pr
      • edges.src[‘od’] : 访问边src端节点的出度
  • reduce function : 通过一个节点收到的来自周围邻接几点发送的(message|pr),计算并更新自己的PageRank value.
    • 传入参数为nodes,nodes {data, mailbox.data}
    • nodes.mailbox[‘pr’] : 指向该节点的邻接节点传递给它的message(pr)
def pagerank_message_func(edges):
    return {'pr' : edges.src['pr'] / edges.src['od']}

def pagerank_reduce_func(nodes):
    msgs = torch.sum(nodes.mailbox['pr'], dim=1)
    pv = (1 - DAMP) / N + DAMP * msgs
    return {'pr' : pv}

## register
g.register_message_func(pagerank_message_func)
g.register_reduce_func(pagerank_reduce_func)

one PageRank iteration:

# 便利每条边还节点,进行信息的传递和pr的更新
def pagerank_naive(g):
    # Phase #1: send out messages
    for u, v in zip(*g.edges()):
        g.send((u, v))
    # Phase #2: compute, update new PageRank values.
    for v in g.nodes():
        g.recv(v)

批处理

compute on a batch of nodes or edges. For example
DGL如何并行处理多个边和节点:

  • 由于每个节点的出入度不同,所以不能将不同长度的张量’对叠’在一起
  • DGL按照每个node入度的数量分组,然后对每一组调用pagerank_reduce_func
def pagerank_batch(g):
    g.send(g.edges())
    g.recv(g.nodes())

higher level APIs

level-2 APIs 包含了所有的sendrecv操作

def pagerank_level2(g):
    g.update_all()

DGL也提供了内置函数

  • dgl.function.copy_src(src, out) : an edge UDF
  • dgl.function.sum(msg, out) : a node UDF
import dgl.function as fn

def pagerank_builtin(g):
    g.ndata['pr'] = g.ndata['pr'] / g.ndata['od']
    g.update_all(message_func=fn.copy_src(src='pr', out='m'),
                 reduce_func=fn.sum(msg='m',out='m_sum'))
    g.ndata['pr'] = (1 - DAMP) / N + DAMP * g.ndata['m_sum']

如果使用了DLG的builtin function,这将会覆盖之前的操作,即注册的两个函数:

  • pagerank_message_func
  • pagerank_reduce_func
    使用内置的函数,除了代码更清晰,还会更快,这是应为:DGL将融合copy_src消息函数和函数sum简化为一个稀疏矩阵向量(spMV)乘法.CPU可以为这种计算进行加速.

Using spMV for 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}

  • R k \mathbf{R}^k :第 k k 次迭代,所有nodes的PR值向量
  • A \mathbf{A} :图的稀疏邻接矩阵
for k in range(K):
    # pagerank_naive(g)
    # pagerank_batch(g)
    # pagerank_level2(g)
    pagerank_builtin(g)
print(g.ndata['pr'])
tensor([0.0107, 0.0221, 0.0219, 0.0181, 0.0269, 0.0255, 0.0267, 0.0193, 0.0179,
        0.0150, 0.0176, 0.0068, 0.0179, 0.0143, 0.0142, 0.0191, 0.0330, 0.0277,
        0.0393, 0.0153, 0.0266, 0.0327, 0.0185, 0.0207, 0.0177, 0.0178, 0.0255,
        0.0315, 0.0177, 0.0219, 0.0357, 0.0281, 0.0251, 0.0214, 0.0188, 0.0142,
        0.0144, 0.0075, 0.0106, 0.0139, 0.0183, 0.0226, 0.0210, 0.0115, 0.0181,
        0.0223, 0.0070, 0.0171, 0.0213, 0.0109])

猜你喜欢

转载自blog.csdn.net/u014281392/article/details/89791576