📜  页面排名算法Page Rank和Python实现

📅  最后修改于: 2020-05-14 04:50:34             🧑  作者: Mango

PageRank(PR)是Google搜索用于在网站的搜索引擎结果中排名的算法。PageRank以Google的创始人之一拉里·佩奇(Larry Page)的名字命名。PageRank是一种衡量网站页面重要性的方法。根据Google的说法:

PageRank通过计算页面链接的数量和质量来确定该网站的重要性的大致估算值。基本假设是,更重要的网站可能会收到来自其他网站的更多链接。

它不是Google用来排序搜索引擎结果的唯一算法,而是公司使用的第一个算法,也是最著名的算法。
上面的集中度度量不适用于多图。

算法
PageRank算法输出概率分布,该概率分布用于表示随机点击链接的人到达任何特定页面的可能性。可以为任何大小的文档集合计算PageRank。在几篇研究论文中都假设,在计算过程开始时,分布将在集合中所有文档之间平均分配。PageRank计算需要多次“迭代”,以调整近似PageRank值以更紧密地反映理论真实值。

简化算法
假设有一个由四个网页组成的小世界:A,B,C和D。从页面到自身的链接,或者从一个页面到另一个页面的多个出站链接都将被忽略。所有页面的PageRank都初始化为相同的值。在原始形式的PageRank中,所有页面上的PageRank之和就是当时网络上的页面总数,因此此示例中的每个页面的初始值均为1。但是,更高版本的PageRank和在本节的其余部分,假设概率分布在0到1之间。因此,在此示例中,每页的初始值为0.25。

在下一次迭代时,从给定页面转移到其出站链接目标的PageRank会在所有出站链接中平均分配。
如果系统中唯一的链接是从B,C和D页到A页,则每个链接将在下一次迭代时将0.25 PageRank转移到A,总计0.75。

PR(A) = PR(B) + PR(C) + PR(D).\,

假设页面B链接到页面C和A,页面C链接到页面A,页面D链接到所有三个页面。因此,在第一次迭代时,页面B会将其现有值的一半(即0.125)传输到页面A,将另一一半(即0.125)传输到页面C。页面C会将其所有现有值(0.25)传输到唯一, 它链接到A的页面。由于D具有三个出站链接,它将把其现有值的三分之一(约0.083)转移给A。在此迭代完成后,页面A的PageRank约为0.458。

PR(A)={\frac {PR(B)}{2}}+{\frac {PR(C)}{1}}+{\frac {PR(D)}{3}}.\,
换句话说,出站链接赋予的PageRank等于文档本身的PageRank分数除以出站链接L()的数量。

PR(A)={\frac {PR(B)}{L(B)}}+{\frac {PR(C)}{L(C)}}+{\frac {PR(D)}{L(D)}}.\,

 通常,任何页面u的PageRank值都可以表示为:

PR(u) = \sum_{v \in B_u} \frac{PR(v)}{L(v)}
也就是说,页面u的PageRank值取决于集合Bu(包含链接到页面u的所有页面的集合)中每个页面v的PageRank值,除以页面v的链接数L(v)。该算法包含用于计算网页排名的阻尼因子。就像政府自己付钱给政府提取的所得税一样。

以下是用于计算页面排名的代码。

def pagerank(G, alpha=0.85, personalization=None,
             max_iter=100, tol=1.0e-6, nstart=None, weight='weight',
             dangling=None):
    """返回图中节点的PageRank.
    PageRank computes a ranking of the nodes in the graph G based on
    the structure of the incoming links. It was originally designed as
    an algorithm to rank web pages.
    Parameters
    ----------
    G : graph
      NetworkX图。无向图将转换为有向图,每个无向边都有两个有向边.
    alpha : float, 可选的
      PageRank的阻尼参数, default=0.85.
    personalization: dict, 可选的
      “个性化矢量"由包含每个图节点的键和每个节点的非零个性化值的字典组成。默认情况下,使用统一分布。
    max_iter : integer, 可选的
      幂方法特征值求解器中的最大迭代次数.
    tol : float, 可选的
      误差容限,用于检查幂方法求解器中的收敛.
    nstart : dictionary, 可选的
      每个节点的PageRank迭代的起始值.
    weight : key, 可选的
      边缘数据键用作权重。如果无权重设置为1。
    dangling: dict, 可选的
      要分配给任何“悬挂"节点的边界,即没有任何边界的节点。 
      dict键是外围节点指向的节点,而dict值是外围节点的权重。
      默认情况下,根据个性化矢量(如果未指定,则为均匀)为悬空节点指定边缘。
      必须选择它才能产生不可约的过渡矩阵(请参阅google_matrix下的注释)。
      悬空字典与个性化字典相同可能很常见.
    Returns
    -------
    pagerank : dictionary
       以PageRank为值的节点的字典
    Notes
    -----
    特征向量的计算是通过幂次迭代法完成的,不能保证收敛。
    迭代将在max_iter迭代或达到number_of_nodes(G)* tol的错误容限后停止.
    PageRank算法是为有向图设计的,但是该算法不会检查输入图是否是有向的,
    而是通过将有向图的每个边转换为两个边来在无向图上执行.
    """
    if len(G) == 0:
        return {}
    if not G.is_directed():
        D = G.to_directed()
    else:
        D = G
    # 以(随机)形式创建副本
    W = nx.stochastic_graph(D, weight=weight)
    N = W.number_of_nodes()
    # 如果没有给出,选择固定的起始向量
    if nstart is None:
        x = dict.fromkeys(W, 1.0 / N)
    else:
        # 归一化nstart向量
        s = float(sum(nstart.values()))
        x = dict((k, v / s) for k, v in nstart.items())
    if personalization is None:
        # 如果没有给出统一的个性化矢量
        p = dict.fromkeys(W, 1.0 / N)
    else:
        missing = set(G) - set(personalization)
        if missing:
            raise NetworkXError('Personalization dictionary '
                                'must have a value for every node. '
                                'Missing nodes %s' % missing)
        s = float(sum(personalization.values()))
        p = dict((k, v / s) for k, v in personalization.items())
    if dangling is None:
        # 如果未指定悬挂向量,则使用个性化向量
        dangling_weights = p
    else:
        missing = set(G) - set(dangling)
        if missing:
            raise NetworkXError('Dangling node dictionary '
                                'must have a value for every node. '
                                'Missing nodes %s' % missing)
        s = float(sum(dangling.values()))
        dangling_weights = dict((k, v/s) for k, v in dangling.items())
    dangling_nodes = [n for n in W if W.out_degree(n, weight=weight) == 0.0]
    # 幂次迭代:最多进行max_iter次迭代
    for _ in range(max_iter):
        xlast = x
        x = dict.fromkeys(xlast.keys(), 0)
        danglesum = alpha * sum(xlast[n] for n in dangling_nodes)
        for n in x:
            # 这个矩阵乘法看起来很奇怪,因为它在做一个左乘法x ^ T = xlast ^ T * W
            for nbr in W[n]:
                x[nbr] += alpha * xlast[n] * W[n][nbr][weight]
            x[n] += danglesum * dangling_weights[n] + (1.0 - alpha) * p[n]
        # 检查收敛,l1范数
        err = sum([abs(x[n] - xlast[n]) for n in x])
        if err < N*tol:
            return x
    raise NetworkXError('pagerank: power iteration failed to converge '
                        'in %d iterations.' % max_iter)

上面的代码是在networkx库中实现的功能。
要在networkx中实现上述功能,您必须执行以下操作:

>>> import networkx as nx
>>> G=nx.barabasi_albert_graph(60,41)
>>> pr=nx.pagerank(G,0.4)
>>> pr

下面是输出,您将在进行必要的安装后在IDLE上获得。

{0: 0.012774147598875784, 1: 0.013359655345577266, 2: 0.013157355731377924,
3: 0.012142198569313045, 4: 0.013160014506830858, 5: 0.012973342862730735,
 6: 0.012166706783753325, 7: 0.011985935451513014, 8: 0.012973502696061718,
9: 0.013374146193499381, 10: 0.01296354505412387, 11: 0.013163220326063332,
 12: 0.013368514624403237, 13: 0.013169335617283102, 14: 0.012752071800520563,
15: 0.012951601882210992, 16: 0.013776032065400283, 17: 0.012356820581336275,
18: 0.013151652554311779, 19: 0.012551059531065245, 20: 0.012583415756427995,
 21: 0.013574117265891684, 22: 0.013167552803671937, 23: 0.013165528583400423,
 24: 0.012584981049854336, 25: 0.013372989228254582, 26: 0.012569416076848989,
 27: 0.013165322299539031, 28: 0.012954300960607157, 29: 0.012776091973397076,
 30: 0.012771016515779594, 31: 0.012953404860268598, 32: 0.013364947854005844,
33: 0.012370004022947507, 34: 0.012977539153099526, 35: 0.013170376268827118,
 36: 0.012959579020039328, 37: 0.013155319659777197, 38: 0.013567147133137161,
 39: 0.012171548109779459, 40: 0.01296692767996657, 41: 0.028089802328702826,
 42: 0.027646981396639115, 43: 0.027300188191869485, 44: 0.02689771667021551,
 45: 0.02650459107960327, 46: 0.025971186884778535, 47: 0.02585262571331937,
48: 0.02565482923824489, 49: 0.024939722913691394, 50: 0.02458271197701402,
 51: 0.024263128557312528, 52: 0.023505217517258568, 53: 0.023724311872578157,
 54: 0.02312908947188023, 55: 0.02298716954828392, 56: 0.02270220663300396,
 57: 0.022060403216132875, 58: 0.021932442105075004, 59: 0.021643288632623502}

上面的代码已在IDLE(Windows的Python IDE)上运行。在运行此代码之前,您需要下载networkx库。花括号内的部分代表输出。它几乎与IPython(对于Ubuntu用户)相似。