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 ai∈R, 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 ]