文章目录
在七年前读硕士时就听导师和其他的师兄在说用pagerank算法。。。。。,但是一直过没有搞明白算法的真正原理。今天,就好好学习一下该算法。
一、前言
这个要从搜索引擎的发展讲起。最早的搜索引擎采用的是 分类目录的方法,即通过人工进行网页分类并整理出高质量的网站。那时 Yahoo 和国内的 hao123 就是使用的这种方法。
后来网页越来越多,人工分类已经不现实了。搜索引擎进入了 文本检索 的时代,即计算用户查询关键词与网页内容的相关程度来返回搜索结果。这种方法突破了数量的限制,但是搜索结果不是很好。因为总有某些网页来回地倒腾某些关键词使自己的搜索排名靠前。
于是谷歌的两位创始人,当时还是美国斯坦福大学 (Stanford University) 研究生的佩奇 (Larry Page) 和布林 (Sergey Brin) 开始了对网页排序问题的研究。他们的借鉴了学术界评判学术论文重要性的通用方法, 那就是看论文的引用次数。由此想到网页的重要性也可以根据这种方法来评价。于是PageRank的核心思想就诞生了。思想非常简单:
1、如果一个网页被很多其他网页链接到的话说明这个网页比较重要,也就是PageRank值会相对较高 |
2、如果一个PageRank值很高的网页链接到一个其他的网页,那么被链接到的网页的PageRank值会相应地因此而提高 |
二、算法原理
PageRank算法总的来说就是预先给每个网页一个PR值(下面用PR值指代PageRank值),由于PR值物理意义上为一个网页被访问概率,所以一般是 1 N \frac{1}{N} N1,其中 N N N为网页总数。另外,一般情况下,所有网页的PR值的总和为1。如果不为1的话也不是不行,最后算出来的不同网页之间PR值的大小关系仍然是正确的,只是不能直接地反映概率了。
预先给定PR值后,通过下面的算法不断迭代,直至达到平稳分布为止。
以投票机制的观点来看,一个网页的得票数由所有链向它的网页的得票数经过递归算法来得到,有到一个网页的超链接相当于对该网页投了一票。互联网中的网页可以构成一个有向图。
为了便于理解,考虑以下情形:
1)如上左图,假设一个只由4个网页组成的集合:A、B、C和D,如果网页B、C、D都链向网页A,且网页B、C、D均没有链出,那么网页A的PR值将是网页B、C、D的PR值之和:
P
R
(
A
)
=
P
R
(
B
)
+
P
R
(
C
)
+
P
R
(
D
)
PR(A)=PR(B)+PR(C)+PR(D)
PR(A)=PR(B)+PR(C)+PR(D)
2)如上右图,继续假设在上述情境下,网页B有链接链向网页C,网页D有链接链向网页A、B、C,一个网页不能多次投票,所以网页B投给它链向的网页1/2票,网页D投给它链向的网页1/3票,计算此情境下网页A的PR值为:
P
R
(
A
)
=
P
R
(
B
)
2
+
P
R
(
C
)
1
+
P
R
(
D
)
3
PR(A)=\frac{PR(B)}{2}+\frac {PR(C)}{1}+\frac{PR(D)}{3}
PR(A)=2PR(B)+1PR(C)+3PR(D)
即,在一个网页为其他网页投票时,根据链出总数平分该网页的PR值,将其作为该网页为其链向网页所投票数,即:
P
R
(
A
)
=
P
R
(
B
)
L
(
B
)
+
P
R
(
C
)
L
(
C
)
+
P
R
(
D
)
L
(
D
)
PR(A)=\frac{PR(B)}{L(B)}+\frac {PR(C)}{L(C)}+\frac{PR(D)}{L(D)}
PR(A)=L(B)PR(B)+L(C)PR(C)+L(D)PR(D)
然而我们再考虑一种情况:互联网中一个网页只有对自己的出链,或者几个网页的出链形成一个循环圈。那么在不断地迭代过程中,这一个或几个网页的PR值将只增不减,显然不合理。如下图中的C网页就是刚刚说的只有对自己的出链的网页:
为了解决这个问题。我们想象一个随机浏览网页的人,当他到达C网页后,显然不会傻傻地一直被C网页的小把戏困住。我们假定他有一个确定的概率会输入网址直接跳转到一个随机的网页,并且跳转到每个网页的概率是一样的。于是则此图中A的PR值可表示为:
P
R
(
A
)
=
α
(
P
R
(
B
)
2
)
+
(
1
−
α
)
4
PR(A)=\alpha (\frac{PR(B)}{2})+\frac{(1-\alpha)}{4}
PR(A)=α(2PR(B))+4(1−α)
3)再抽象一下,建立一个简化模型,对于任意的网页
i
i
i,它的PR值可以表示如下:
P
R
(
i
)
=
α
∑
j
∈
B
i
P
R
(
j
)
L
(
j
)
+
1
−
α
N
PR(i)=\alpha \sum_{j\in B_{i}}\frac{PR(j)}{L(j)}+\frac{1-\alpha}{N}
PR(i)=αj∈Bi∑L(j)PR(j)+N1−α
参数说明:
- P R ( i ) PR(i) PR(i):网页i的PR值
- P R ( j ) PR(j) PR(j):网页j的PR值
- B i B_{i} Bi:所有链接到网页i的网页集合
- L ( j ) L(j) L(j):网页j的对外链出数
- N N N: 是网页总数
- α \alpha α:一般取0.85
根据上面的公式,我们可以计算每个网页的PR值,在不断迭代趋于平稳的时候,即为最终结果。具体怎样算是趋于平稳,我们在下面的PR值计算方法部分再做解释。
三、算法证明
l i m n → ∞ P n lim_{n\rightarrow\infty}P_{n} limn→∞Pn是否存在?
如果极限存在,那么它是否与 P 0 P_{0} P0的选取无关?
PageRank算法的正确性证明包括上面两点。为了方便证明,我们先将PR值的计算方法转换一下。
仍然拿刚刚的例子来说:
我们可以用一个矩阵来表示这张图的出链入链关系,
S
i
j
=
0
S_{ij}=0
Sij=0表示
j
j
j 网页没有对
i
i
i 网页的出链:
S
=
(
0
1
/
2
0
0
1
/
3
0
0
1
/
2
1
/
3
0
0
1
/
2
1
/
3
1
/
2
0
0
)
S=\begin{pmatrix} 0& 1/2& 0& 0\\ 1/3& 0& 0& 1/2\\ 1/3& 0& 0& 1/2\\ 1/3& 1/2& 0& 0 \end{pmatrix}
S=⎝⎜⎜⎛01/31/31/31/2001/2000001/21/20⎠⎟⎟⎞
取
e
e
e为所有分量都为 1 的列向量,接着定义矩阵:
A
=
α
S
+
(
1
−
α
)
N
e
e
T
A=\alpha S + \frac {(1-\alpha)} {N} e e^{T}
A=αS+N(1−α)eeT
则PR值的计算如下,其中
P
n
P_{n}
Pn为第n次迭代时各网页PR值组成的列向量:
P
n
+
1
=
A
P
n
P_{n+1}=AP_{n}
Pn+1=APn
于是计算PR值的过程就变成了一个 Markov 过程,那么PageRank算法的证明也就转为证明 Markov 过程的收敛性证明:如果这个 Markov 过程收敛,那么
l
i
m
n
→
∞
P
n
lim_{n\rightarrow\infty}P_{n}
limn→∞Pn存在,且与
P
0
P_{0}
P0的选取无关。
若一个 Markov 过程收敛,那么它的状态转移矩阵A需要满足:
1、A为随机矩阵
2、A是不可约的
- 第一点,随机矩阵又叫概率矩阵或 Markov 矩阵,满足以下条件:
令
a
i
j
为
矩
阵
A
中
第
i
行
第
j
列
的
元
素
,
则
∀
i
=
1...
n
,
j
=
1...
n
,
a
i
j
≥
0
,
且
∀
i
=
1...
n
,
∑
j
=
1
n
a
i
j
=
1
令a_{ij}为矩阵A中第i行第j列的元素,则\forall i =1...n, j=1...n, a_{ij} \geq 0, 且 \forall i=1...n, \sum_{j=1}^{n} a_{ij}=1
令aij为矩阵A中第i行第j列的元素,则∀i=1...n,j=1...n,aij≥0,且∀i=1...n,∑j=1naij=1
显然我们的A矩阵所有元素都大于等于0,并且每一列的元素和都为1。
-
第二点,不可约矩阵:方针A是不可约的当且仅当与A对应的有向图是强联通的。有向图 G = ( V , E ) G=(V,E) G=(V,E)是强联通的当且仅当对每一对节点对 u , v ∈ V u,v\in V u,v∈V,存在从 u u u到 v v v的路径。因为我们在之前设定用户在浏览页面的时候有确定概率通过输入网址的方式访问一个随机网页,所以 A A A矩阵同样满足不可约的要求。
-
第三点,要求A是非周期的。所谓周期性,体现在Markov链的周期性上。即若A是周期性的,那么这个Markov链的状态就是周期性变化的。因为 A A A是素矩阵(素矩阵指自身的某个次幂为正矩阵的矩阵),所以 A A A是非周期的。
至此,我们证明了PageRank算法的正确性。
四、PR值计算方法
1、幂迭代法
首先给每个页面赋予随机的PR值,然后通过 P n + 1 = A P n P_{n+1} = A P_{n} Pn+1=APn不断地迭代PR值。当满足下面的不等式后迭代结束,获得所有页面的PR值:
∣ P n + 1 − P n ∣ < ϵ \left | P_{n+1}-P_{n}\right |<\epsilon ∣Pn+1−Pn∣<ϵ
2、特征值法
当上面提到的Markov链收敛时,必有:
P = A P ⇒ P 为 矩 阵 A 特 征 值 I 对 应 的 特 征 向 量 ( 随 机 矩 阵 必 有 特 征 值 I , 且 其 特 征 向 量 所 有 分 量 全 为 正 或 全 为 负 ) P=AP\Rightarrow P为矩阵A特征值I对应的特征向量 \\ (随机矩阵必有特征值I,且其特征向量所有分量全为正或全为负) P=AP⇒P为矩阵A特征值I对应的特征向量(随机矩阵必有特征值I,且其特征向量所有分量全为正或全为负)
3、代数法
相似的,当上面提到的Markov链收敛时,必有:
P
=
A
P
P
=
(
α
S
+
(
1
−
α
)
N
e
e
T
)
P
又
因
为
e
为
所
有
分
量
都
为
1
的
列
向
量
,
P
的
所
有
分
量
之
和
为
1
P=AP \\ P=(\alpha S + \frac {(1-\alpha)}{N} e e^{T})P \\ 又因为 e为所有分量都为1的列向量,P的所有分量之和为1
P=APP=(αS+N(1−α)eeT)P又因为e为所有分量都为1的列向量,P的所有分量之和为1
⇒
P
=
α
S
P
+
(
1
−
α
)
N
e
⇒
(
e
e
T
−
α
S
)
P
=
(
1
−
α
)
N
e
⇒
P
=
(
e
e
T
−
α
S
)
−
1
(
1
−
α
)
N
e
\Rightarrow P=\alpha S P + \frac {(1-\alpha)}{N}e \\ \Rightarrow (ee^{T} - \alpha S)P=\frac {(1-\alpha)}{N}e \\ \Rightarrow P=(ee^{T} - \alpha S)^{-1}\frac {(1-\alpha)}{N}e \\
⇒P=αSP+N(1−α)e⇒(eeT−αS)P=N(1−α)e⇒P=(eeT−αS)−1N(1−α)e
五、python代码(利用pygraph包实现)
# -*- coding: utf-8 -*-
from pygraph.classes.digraph import digraph
class PRIterator:
__doc__ = '''计算一张图中的PR值'''
def __init__(self, dg):
self.damping_factor = 0.85 # 阻尼系数,即α
self.max_iterations = 100 # 最大迭代次数
self.min_delta = 0.00001 # 确定迭代是否结束的参数,即ϵ
self.graph = dg
def page_rank(self):
# 先将图中没有出链的节点改为对所有节点都有出链
for node in self.graph.nodes():
if len(self.graph.neighbors(node)) == 0:
for node2 in self.graph.nodes():
digraph.add_edge(self.graph, (node, node2))
nodes = self.graph.nodes()
graph_size = len(nodes)
if graph_size == 0:
return {}
page_rank = dict.fromkeys(nodes, 1.0 / graph_size) # 给每个节点赋予初始的PR值
damping_value = (1.0 - self.damping_factor) / graph_size # 公式中的(1−α)/N部分
flag = False
for i in range(self.max_iterations):
change = 0
for node in nodes:
rank = 0
for incident_page in self.graph.incidents(node): # 遍历所有“入链”的页面
rank += self.damping_factor * (page_rank[incident_page] / len(self.graph.neighbors(incident_page)))
rank += damping_value
change += abs(page_rank[node] - rank) # 绝对值
page_rank[node] = rank
print("This is NO.%s iteration" % (i + 1))
print(page_rank)
if change < self.min_delta:
flag = True
break
if flag:
print("finished in %s iterations!" % node)
else:
print("finished out of 100 iterations!")
return page_rank
if __name__ == '__main__':
dg = digraph()
dg.add_nodes(["A", "B", "C", "D", "E"])
dg.add_edge(("A", "B"))
dg.add_edge(("A", "C"))
dg.add_edge(("A", "D"))
dg.add_edge(("B", "D"))
dg.add_edge(("C", "E"))
dg.add_edge(("D", "E"))
dg.add_edge(("B", "E"))
dg.add_edge(("E", "A"))
pr = PRIterator(dg)
page_ranks = pr.page_rank()
print("The final page rank is\n", page_ranks)
运行结果:
在上述代码运行前,得先安装工具包python-graph-core:https://github.com/pmatiello/python-graph
PS:一开始直接pip安装了pygraph,发现不行,将pygraph卸载(pip uninstall pygraph);然后在github上下载下来解压,用python setup.py install 安装,程序可以运行。(希望对大家有帮助)
六、使用networkx库中的pagerank函数
当然,首先得先安装networkx第三方库: pip install networkx
"""
使用neworkx库实现pagerank计算
"""
import networkx as nx
import matplotlib.pyplot as plt
def build_digGraph(edges):
"""
初始化图
:param edges: 存储有向边的列表
:return: 使用有向边构造完毕的有向图
"""
G = nx.DiGraph() # DiGraph()表示有向图
for edge in edges:
G.add_edge(edge[0], edge[1]) # 加入边
return G
if __name__ == '__main__':
edges = [("A", "B"), ("A", "C"), ("A", "D"), ("B", "D"), ("C", "E"), ("D", "E"),("B", "E"),("E", "A")]
G = build_digGraph(edges)
# 将图形画出来
layout = nx.spring_layout(G)
nx.draw(G, pos=layout, node_color='y', with_labels=True, hold=False)
for index in G.edges_iter(data=True):
print(index) #输出所有边的节点关系和权重
plt.show()
# # 最Naive的pagerank计算,最朴素的方式没有设置随机跳跃的部分,所以alpha=1,但是本例中会出现不收敛
# pr_value = nx.pagerank(G, alpha=1)
# print("naive pagerank值是:", pr_value)
# 改进后的pagerank计算,随机跳跃概率为15%,因此alpha=0.85
pr_impro_value = nx.pagerank(G, alpha=0.85)
print("improved pagerank值是:", pr_impro_value)
layout = nx.spring_layout(G)
nx.draw(G, pos=layout, cmap = plt.get_cmap('jet'), node_size=[x * 1000 for x in pr_impro_value.values()], node_color='m', with_labels=True)
plt.show()
运行结果:
七、PageRank的缺点
这是一个天才的算法,原理简单但效果惊人。然而,PageRank算法还是有一些弊端。
-
第一,没有区分站内导航链接。很多网站的首页都有很多对站内其他页面的链接,称为站内导航链接。这些链接与不同网站之间的链接相比,肯定是后者更能体现PageRank值的传递关系。
-
第二,没有过滤广告链接和功能链接(例如常见的“分享到微博”)。这些链接通常没有什么实际价值,前者链接到广告页面,后者常常链接到某个社交网站首页。
-
第三,对新网页不友好。一个新网页的一般入链相对较少,即使它的内容的质量很高,要成为一个高PR值的页面仍需要很长时间的推广。
针对PageRank算法的缺点,有人提出了TrustRank算法。其最初来自于2004年斯坦福大学和雅虎的一项联合研究,用来检测垃圾网站。TrustRank算法的工作原理:先人工去识别高质量的页面(即“种子”页面),那么由“种子”页面指向的页面也可能是高质量页面,即其TR值也高,与“种子”页面的链接越远,页面的TR值越低。“种子”页面可选出链数较多的网页,也可选PR值较高的网站。
TrustRank算法给出每个网页的TR值。将PR值与TR值结合起来,可以更准确地判断网页的重要性。
参考资料
-
论文: The PageRank Citation Ranking: Bringing Order to the Web
-
PageRank算法–从原理到实现https://blog.csdn.net/rubinorth/article/details/52215036
-
深入探讨PageRank(二):PageRank原理剖析 https://blog.csdn.net/MONKEY_D_MENG/article/details/6556295
-
Python包 - networkx:https://www.cnblogs.com/ljhdo/archive/2019/04/15/10662902.html