networkx中的pagerank源码分析
调用入口
def pagerank(
G, # 一个一个NetworkX图。 无方向的图将被转换为有方向的图,每条无方向的边有两条有方向的边。
alpha=0.85, # PageRank的阻尼参数,默认=0.85。
personalization=None, # "个性化向量 "由一个字典组成。默认情况下,使用的是均匀分布。
max_iter=100, # 幂法特征值求解器中的最大迭代次数。
tol=1.0e-6, # 误差容限用于检查幂法求解器的收敛性
nstart=None, # 每个节点的PageRank迭代的起始值。
weight="weight", # 要用作权重的边对应的数据键(这里是图G中"weight"键对应的值作为权重)。如果“无”权重设置为1。
dangling=None, # 终止点如何处理。即出度为0的点。默认dangling node和其他结点的权重一样
):
return pagerank_scipy(
G, alpha, personalization, max_iter, tol, nstart, weight, dangling
)
其中,实际的执行代码如下:
def pagerank_scipy(
G,
alpha=0.85,
personalization=None,
max_iter=100,
tol=1.0e-6,
nstart=None,
weight="weight",
dangling=None,
):
msg = "networkx.pagerank_scipy is deprecated and will be removed in NetworkX 3.0, use networkx.pagerank instead."
warn(msg, DeprecationWarning, stacklevel=2)
import numpy as np
import scipy as sp
import scipy.sparse # call as sp.sparse
N = len(G) # 图的节点数量
if N == 0: # 如果节点为空,直接返回
return {}
nodelist = list(G) # 节点的名字列表
M = nx.to_scipy_sparse_matrix(G, nodelist=nodelist, weight=weight, dtype=float) # 依nodelist为顺序,用稀疏邻接矩阵的方式表示这个图。weight可以影响转移概率矩阵中的概率?
S = np.array(M.sum(axis=1)).flatten() # 计算每个点对应的出度
S[S != 0] = 1.0 / S[S != 0] # 计算转移概率。值为 1/出度
Q = sp.sparse.spdiags(S.T, 0, *M.shape, format="csr") # 把上面的S转为M形状的对角阵。压缩稀疏行(CSR,Compressed Sparse Row)。
M = Q * M # 得到每个节点的转移概率矩阵
# initial vector
if nstart is None: # 如果没有指定初始的pr值,默认为 1/N,其中N为节点数量
x = np.repeat(1.0 / N, N)
else:
x = np.array([nstart.get(n, 0) for n in nodelist], dtype=float)
x = x / x.sum()
# Personalization vector
if personalization is None: # 如果没有指定节点的权重,则默认为 1/N
p = np.repeat(1.0 / N, N)
else: # 以字典{"节点名字":权重}的方式指定,则节点权重为 p/p.sum()
p = np.array([personalization.get(n, 0) for n in nodelist], dtype=float)
if p.sum() == 0:
raise ZeroDivisionError
p = p / p.sum()
# Dangling nodes
if dangling is None:
dangling_weights = p # 默认终止点的转移概率为 1/N
else:
# Convert the dangling dictionary into an array in nodelist order
dangling_weights = np.array([dangling.get(n, 0) for n in nodelist], dtype=float)
dangling_weights /= dangling_weights.sum()
is_dangling = np.where(S == 0)[0] # 标记没有出度的点,也就是终止点
# power iteration: make up to max_iter iterations
# !!! 这里是计算的核心部分了
for _ in range(max_iter):
xlast = x # xlast用于保存上一次的pagerank值
x = alpha * (x * M + sum(x[is_dangling]) * dangling_weights) + (1 - alpha) * p # pagerank计算公式,x * M 的大小为(节点数量,),每个值为对应点的pr值。sum(x[is_dangling])是终止点的pr值的和,作为系数乘以dangling_weights,乘积值作为对整体pr值的修正。alpha是阻尼系数,p默认情况下为 1/N。p也可以为归一化的personalization值。
# check convergence, l1 norm
err = np.absolute(x - xlast).sum() # pr值相对上一次计算的pr值的误差,再累加。理论上logN次后会稳定。
if err < N * tol: # 如果在指定收敛误差内,则返回结果,否则抛出异常
return dict(zip(nodelist, map(float, x)))
raise nx.PowerIterationFailedConvergence(max_iter)
对networkx 2.6.3 版本中的pagerake分析就到这里。