PageRank算法概述与Python实现
PageRank算法是一种用于评估网页重要性的算法,它基于网页之间的链接结构来确定网页的权重和重要性。算法的核心思想是通过迭代计算网页之间的链接关系,以确定每个网页的权重。它将互联网视为一个有向图,其中网页是节点,链接是有向边。算法通过以下方式计算网页的PageRank值:每个网页的初始PageRank值相等,然后通过迭代更新计算,将每个网页的PageRank值传递给链接到它的网页,直到收敛为止。在计算过程中,还引入了阻尼因子,用于模拟用户在浏览网页时的随机跳转行为,这样可以确保算法收敛并得到稳定的结果。PageRank算法是Coogle搜索擎对检索结果的一种排序算法,它的思想主要来自传统文献计量学中的文献引文分析,即一篇文献的质量和重要性可以通过其他文献对其引用的数量和引文质量来衡量,也就是说,一篇文献被其他文献引用越多,并且引用的文献的质量越高,则该文献本身就越重要,PageRank也被广泛应用于其他领域,如网络分析、推荐系统和数据挖掘。
一、PageRank算法思想
PageRank算法是Google创始人提出的一种用于标识网页重要性的方法,它主要用于网页的排序。PR值越高说明网站越重要,因此其排名也越靠前。PageRank算法的核心思想是基于有向图上的随机游走模型,这个模型描述了一个随机游走者如何沿着图的边随机移动,从一个节点访问到另一个节点。在满足某些条件的前提下,这个随机游走过程最终会收敛到一个平稳分布,在这个平稳分布中,每个节点被访问的概率即为其PageRank值,这个值可以被解释为节点的重要性。
PagsRank算法的具体实现可以利用网页所对应图的邻接矩阵来表达超链接关系。首先写出所对应图的邻接矩阵\(W\),为了能将网页的页面等级值平均分配给该网页所链接指向的网页,对\(W\)各个行向量进行归一化处理,得到矩阵\(P\)。矩阵\(P\)称为状态转移概率矩阵,它的各个行向量元素之和为1,\(P^T\)的最大特征值(一定为1)所对应的归一化特征向量即为各顶点的PageRank值。PageRank值的计算步骤如下:
- 构造有向图\(D=(V,A,W)\),其中\(V=\{v_1,v_2,...,v_N\}\)为顶点集合,每一个网页是的一个顶点,\(A\)为弧的集合,网页间的每一个超链接是图的一条弧,邻接矩阵\(W=(w_{ij})_{N\times N}\),如果从网页\(i\)到网页\(j\)有超链接,则\(w_{ij}=1\),否则为0。
- 记矩阵 \({r_i} = \sum\limits_{j = 1}^N {{w_{ij}}}\)为第\(i\)行的和,它给出了页面\(i\)的链出链接数目。定义矩阵\(P=(p_{ij})_{N \times N}\)如下:\[p_{ij}=\frac{{{w_{ij}}}}{{{r_i}}} \]
- \(P^T\)的最大特征值(一定为1)所对应的归一化特征向量即为各顶点的PageRank值。
1.1 示例1
影响力评价问题,见下图给出了6篇文章之间的引用关系,即文章1分别引用了文章2、4、5、6;文章2分别引用了文章4、5、6;等等,试给出6篇文章影响力大小的排序。
网络图 | PageRank |
---|---|
import numpy as np
from scipy.sparse.linalg import eigs
import pylab as plt
import sympy as sp
L = [(1,2),(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,1),(3,2),(3,4),(4,5),(4,6),(5,3),(5,6),(6,3)]
w = np.zeros((6, 6)) # 邻接矩阵初始化
for i in range(len(L)):
w[L[i][0]-1, L[i][1]-1] = 1
r = np.sum(w, axis=1, keepdims=True)
for i in range(len(r)):
if r[i][0] != 0:
r[i][0] = sp.Rational(r[i][0] ** -1)
r = np.array(r)
print(r) # 由于0不为分母,先将r成以-1方,避免相除出错。
P = w * r # 这里利用矩阵广播
print(P)
val, vec = eigs(P.T, 1)
V = vec.real
V = V.flatten() # 展开成(n,)形式的数组
V = V/V.sum()
print("V=", np.round(V, 4))
plt.bar(range(1, len(w)+1), V, width=0.6, color='b')
plt.show()
1.2 示例2
PageRank算法是图的链接分析的代表性算法,属于图数据上的无监督学习方法。其基本想法是在一个有向图上定义一个随机游走模型,即一阶马尔科夫链,描述随机游走者沿着有向图随机访问各个结点的行为。
状态转移概率图 | 转移概率矩阵 |
---|---|
import numpy as np
from fractions import Fraction
np.set_printoptions(formatter={'all': lambda x: str(Fraction(x).limit_denominator())}) # 格式化 保留分数,不至于精度丢失
M = 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]]) # 根据有向图得出的转移矩阵
R = np.array([1 / 4, 1 / 4, 1 / 4, 1 / 4]).reshape(4, 1) # 初始R0
def diedai(M, R): # 定义一个迭代函数,直至MR=R时,输出R
count = 0
while (True):
R_1 = np.dot(M, R)
if ((R_1 == R).any()): # 判断两个数组是否相等
print(R_1)
print('迭代次数为:', count)
break
else:
R = np.copy(R_1)
count += 1
diedai(M, R)
二、算法算例
2.1 示例1
- PageRank公式:
\(\mathrm{PR}(\mathrm{Ti})\) :其他节点的 (指向 a 节点) PR值;\(\mathrm{L}(\mathrm{Ti})\) : 其他节点的 (指向 a 节点) 出链数;\(\mathrm{i}\) : 循环次数。
初始化的PR值为 \(1/N=1/4\) 。
- 矩阵化表达
从 \(A\) 将跳转到 \(B\) 或 \(C\) 的概率为 \(1 / 2\);从D将跳转到 A 的概率为 1 。(矩阵的列表示出链)
- 通过矩阵化表达,快速计算PageRank值
2.2 示例2
假设有4个网页,见下图。他们之间的链接信息如上图所示,A跳转到B、C、D的概率各位1/3,B跳转到A、D的概率为1/2,C跳转到A的概率为1,因此我们可以得到转移矩阵为M。
网络连接图 | 概率转移矩阵 |
---|---|
我们假设 \(A 、 B 、 C 、 D\) 四个页面的初始影响力都是相同的,即:
经过第一次转移后,各页面的影响因子 \(v_1\) 变为:
然后我们再用转移矩阵乘以 \(v_1\) 得到 \(v_2\) 结果,直到迭代 \(n\) 次 \(v_n\) 影响因子不在发生变化,可以收敛为 \((0.3333,0.2222,0.2222,0.2222)\) ,也就是对应A、B、C、D四个页面下的PageRank值。考虑到页面可能会自身循环和陷阱问题,可再加一个网页跳转概率 \(p\) ,公式变为 \(v=p^* M^* v+(1-p)^{\star} v\)。
from numpy import *
a = array([[0,1,1,0],[1,0,0,1],[1,0,0,1],[1,1,0,0]],dtype=float)
#构造转移矩阵
def transPre(data):
b = transpose(data) #把矩阵转置
c = zeros((a.shape),dtype=float)
#把所有的元素重新分配
for i in range(a.shape[0]):
for j in range(a.shape[1]):
c[i][j] = data[i][j] / (b[j].sum())
return c
# print(transPre(a))
def initiPre(c):
# pr值的初始化
pr = zeros((c.shape[0],1),dtype=float)
for i in range(c.shape[0]):
pr[i] = float(1)/c.shape[0]
return pr
# print(initiPre(a))
def PageRank(p,m,v):
#pageRank算法
#p是网页跳转概率,m是转移矩阵,v是pr值
while ((v == p*dot(m,v) + (1-p)*v).all() == False):
v = p*dot(m,v) + (1-p)*v
# print(v)
print((v == p*dot(m,v) + (1-p)*v).all())
return v
if __name__ == '__main__':
M = transPre(a)
pr = initiPre(M)
p = 0.85
print(PageRank(p,M,pr))
三、pagerank值的Python实现
3.1 基于定义算法
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
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]
3.2 迭代算法
import numpy as np
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
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]
3.3 幂法算法
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
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]
四、算法漏洞
PageRank算法在迭代计算中,需要注意如下两大问题: Dead End和Spider Trap。
4.1 Dead End
Dead End就是指一个页面只有入链但是没有出链,这时转移矩阵M的一列为零,导致最后结果为零。这时web不是强连通的,即存在某一类节点不指向别人,如下图的D。这个时候我们的算法就会出问题了,它不满足收敛性了。
网络图 | 转移概率矩阵 |
---|---|
上面描述的上网者的行为是一个其实马尔科夫过程的实例,要满足收敛性,需要具备一个条件:图是强连通的,即从任意网页可以到达其他任意网页。一旦出现如上图中类似D的节点,就不满足收敛性的。
4.2 Spider Trap
Spider Trap指页面的所有出链都指向自己,这样会使迭代结果中只有自己的页面的Rank值很高。其他页面的Rank值为零。要克服上面两个问题,我们需要将迭代计算公式做如下转变,即假设每个页面有很小概率拥有一个指向其他页面的链接,用数学表达出来就是:其他页面本来传递给一个页面的Rank值需要做一个折扣,作为补偿,可能需要一个页面指向该页面并且传递Rank值给该页面,该跳转的概率为 \(\beta\) ,因此表达式变为:
其中, \(N\) 为页面总数;\(e\) 为一个 \(N\) 维且各个分量都是 1 的向量;\(\beta\) 通过经验得知一般设为 0.15。此时的计算结果过程为:
建模图 | 思维导图 |
---|---|
总结
PageRank算法的应用场合广泛,其中最为人所熟知的是在搜索引擎中的应用。谷歌等顶级搜索引擎的排名算法中都运用了PageRank算法。通过计算每个网页的 PageRank值,搜索引擎能够确定网页的等级,并将其作为搜索结果排序的重要依据。pageRank算法应用如下:
搜索引擎排名:它帮助搜索引擎确定网页在搜索结果中的排名,使得更重要和相关的网页更容易被用户找到。这样,用户在进行搜索时,更有可能得到高质量、相关性强的网页结果。
网络分析:PageRank算法也被用于分析其他类型的网络,如社交网络、引用网络等。通过计算节点的PageRank值,可以识别网络中的重要节点、中心节点和关键节点。
推荐系统:在推荐系统中,PageRank算法可以用来识别用户可能感兴趣的内容或产品。通过分析用户之间的关系和他们对内容的反应,可以为用户推荐最相关的内容。
媒体网站优化:媒体和新闻网站可以使用PageRank算法来优化其内容的布局和链接结构,以提高重要文章和页面的曝光度,并增加用户的点击量和访问量。
数据挖掘:PageRank算法在数据挖掘领域也有广泛应用,例如在社交网络分析、文本分析和推荐系统中。通过分析链接关系,可以发现隐藏在数据中的模式和趋势。
总的来说,PageRank算法不仅是一种用于搜索引擎排名的技术,还是一种通用的网络分析工具,可以在许多领域中发挥作用,帮助人们理解和利用复杂的网络结构。值得注意的是,PageRank算法并非完美无缺。在实际应用中,可能会遇到一些问题和挑战,如垃圾链接、链接欺诈等。这些问题可能会干扰PageRank算法的计算结果,从而影响网页排名的准确性。因此,在使用PageRank算法时,需要采取相应的措施来应对这些问题,确保算法的有效性和准确性。相信通过不断的研究和改进,相信PageRank算法在未来会有更广泛的应用和更好的表现。