《统计学习方法》啃书手册|第21章 PageRank 算法(教材全解 + 公式推导 + Python实现)

21.1 PageRank的定义

【推荐阅读】拓扑:硬核科普:什么是拓扑? - 中科院物理所的文章 - 知乎

【补充说明】网络拓扑结构在这里可以理解为网页之间的拓扑结构。页面即结点,超链接即连接结点的边,拓扑结构即页面和超链接的结构关系。

【补充说明】随机矩阵,即满足所有元素非负且矩阵中每一列的元素之和为1的矩阵。(书中P. 356)

PageRank基本定义求PageRank值(原生Python实现)
import numpy as np

def pagerank_basic(M, tol=1e-8, max_iter=1000):
    """使用PageRank的基本定义求解PageRank值

    要求有向图是强联通且非周期性的

    :param M: 转移概率矩阵
    :param tol: 容差
    :param max_iter: 最大迭代次数
    :return: PageRank值(平稳分布)
    """
    n_components = len(M)

    # 初始状态分布:均匀分布
    pr0 = np.array([1 / n_components] * n_components)

    # 迭代寻找平稳状态
    for _ in range(max_iter):
        pr1 = np.dot(M, pr0)

        # 判断迭代更新量是否小于容差
        if np.sum(np.abs(pr0 - pr1)) < tol:
            break

        pr0 = pr1

    return pr0

【测试】例21.1

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 1, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_basic(P))  # [0.33 0.22 0.22 0.22]

【测试】例21.2

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_basic(P))  # [0. 0. 0. 0.]

【补充解释】PageRank的一般定义有唯一平稳分布的证明如下。因为 P R ( v i ) > 0 PR(v_i)>0 PR(vi)>0​,即从任意结点出发都可以在下一个时刻到达任意结点;所以显然有向图具有强连通性和非周期性。根据定理21.1可知,一定有唯一平稳分布存在,且当时间趋于无穷时状态分布收敛于唯一的平稳分布。

【补充说明】在PageRank的一般定义中,如果存在没有出射边的结点(即存在没有连接出去的超链接的网页),则不满足 ∑ i = 1 n P R ( v i ) = 1 \sum_{i=1}^n PR(v_i) = 1 i=1nPR(vi)=1

21.2.1 迭代算法

PageRank的迭代算法(原生Python实现)
def pagerank_1(M, d=0.8, tol=1e-8, max_iter=1000):
    """PageRank的迭代算法

    :param M: 转移概率矩阵
    :param d: 阻尼因子
    :param tol: 容差
    :param max_iter: 最大迭代次数
    :return: PageRank值(平稳分布)
    """
    n_components = len(M)

    # 初始状态分布:均匀分布
    pr0 = np.array([1 / n_components] * n_components)

    # 迭代寻找平稳状态
    for _ in range(max_iter):
        pr1 = d * np.dot(M, pr0) + (1 - d) / n_components

        # 判断迭代更新量是否小于容差
        if np.sum(np.abs(pr0 - pr1)) < tol:
            break

        pr0 = pr1

    return pr0

【测试】例21.2

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_1(P))  # [0.1  0.13 0.13 0.13]

【测试】例21.3

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 1, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_1(P))  # [0.1  0.13 0.64 0.13]

【测试】例21.4

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 0, 1],
                  [1 / 2, 0, 0],
                  [1 / 2, 1, 0]])
    print(pagerank_1(P))  # [0.38 0.22 0.4 ]

21.2.2 幂法

【补充说明】在式(21.16)之前的推导中,注意: a i ∈ R a_i \in R aiR A u i = λ i u i A u_i = \lambda_i u_i Aui=λiui i = 1 , 2 , ⋯   , n i=1,2,\cdots,n i=1,2,,n​。

【扩展】Perron-Frobenius定理

扩展阅读:Perron-Frobenius定理 - team_alpha的文章 - Bilibili

摘要:如果 A A A​​是一个不可约非负方阵,则 A A A总有正的特征值 λ \lambda λ,它是特征方程的单根,称为 A A A的Perron-Frobenius根(PF根),所有其他特征值的模不超过PF根;该特征值对应一个正的特征向量,在标量乘法的意义上,该特征向量是唯一的。

计算一般PageRank的幂法(原生Python实现)
import numpy as np


def pagerank_2(M, d=0.8, tol=1e-8, max_iter=1000):
    """计算一般PageRank的幂法

    :param M: 转移概率矩阵
    :param d: 阻尼因子
    :param tol: 容差
    :param max_iter: 最大迭代次数
    :return: PageRank值(平稳分布)
    """
    n_components = len(M)

    # 选择初始向量x0:均匀分布
    x0 = np.array([1 / n_components] * n_components)

    # 计算有向图的一般转移矩阵A
    A = d * M + (1 - d) / n_components

    # 迭代并规范化结果向量
    for _ in range(max_iter):
        x1 = np.dot(A, x0)
        x1 /= np.max(x1)

        # 判断迭代更新量是否小于容差
        if np.sum(np.abs(x0 - x1)) < tol:
            break

        x0 = x1

    # 对结果进行规范化处理,使其表示概率分布
    x0 /= np.sum(x0)

    return x0

【测试】例21.2

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_2(P))  # [0.2  0.27 0.27 0.27]

【测试】例21.3

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 1, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_2(P))  # [0.1  0.13 0.64 0.13]

【测试】例21.4

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 0, 1],
                  [1 / 2, 0, 0],
                  [1 / 2, 1, 0]])
    print(pagerank_2(P))  # [0.38 0.22 0.4 ]

21.2.3 代数算法

【补充说明】注意式(21.23)和式(21.24)中的 1 \textbf{1} 1 n n n维向量。

PageRank的代数算法(原生Python实现)
import numpy as np


def pagerank_3(M, d=0.8, tol=1e-8, max_iter=1000):
    """PageRank的代数算法

    :param M: 转移概率矩阵
    :param d: 阻尼因子
    :param tol: 容差
    :param max_iter: 最大迭代次数
    :return: PageRank值(平稳分布)
    """
    n_components = len(M)

    # 计算第一项:(I-dM)^-1
    r1 = np.linalg.inv(np.diag([1] * n_components) - d * M)

    # 计算第二项:(1-d)/n 1
    r2 = np.array([(1 - d) / n_components] * n_components)

    return np.dot(r1, r2)

【测试】例21.2

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_3(P))  # [0.1  0.13 0.13 0.13]

【测试】例21.3

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 1 / 2, 0, 0],
                  [1 / 3, 0, 0, 1 / 2],
                  [1 / 3, 0, 1, 1 / 2],
                  [1 / 3, 1 / 2, 0, 0]])
    print(pagerank_3(P))  # [0.1  0.13 0.64 0.13]

【测试】例21.4

if __name__ == "__main__":
    np.set_printoptions(precision=2, suppress=True)
    P = np.array([[0, 0, 1],
                  [1 / 2, 0, 0],
                  [1 / 2, 1, 0]])
    print(pagerank_3(P))  # [0.38 0.22 0.4 ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值