关于PageRank算法
参考文献:PageRank算法、PageRank算法–从原理到实现。
1、算法中心思想
(1)数量假设:在网页模型图中,一个网页接收到的其他网页指向的入链(in-links)越多,说明该网页越重要。
(2)质量假设:当一个质量高的网页指向(out-links)一个网页,说明这个被指向的网页质量也高。
可以这样类比:在一次投票过程中,得票数多的人,其声望排名肯定相对较高;此外,被声望较高的人投票的人,说明其声望排名也较高。
2、算法和公式
P R ( a ) i + 1 = ∑ i = 0 n P R ( T i ) i L ( T i ) PR(a)_{i+1} = \sum_{i=0}^n \frac {PR(Ti)_i}{L(Ti)} PR(a)i+1=i=0∑nL(Ti)PR(Ti)i
P R ( T i ) PR(Ti) PR(Ti)代表的是其他节点(指向节点a的节点)的PR值;
L ( T i ) L(Ti) L(Ti)代表的是其他节点(指向节点a的节点)的出链数;
i i i代表循环次数。当 i = 0 i=0 i=0时,所有节点的初始值初始化为 1 N \frac 1 N N1(N为所有节点数目,及网页数目)。
PR值需要通过多次循环迭代才能达到一个稳定值。
【示例】
可见,节点A的PR值与其在上一次循环中的PR值没有任何关系。
【矩阵化表达】
通过矩阵化表达,快速求解PR值:
P
R
(
a
)
=
M
∗
V
PR(a) = M*V
PR(a)=M∗V
矩阵M为图的马尔科夫矩阵,V为上一次循环各节点PR值构成的矩阵。
3、Dead Ends问题
如上图所示,B没有任何出链,则造成Dead Ends问题。经过多次循环之后,其会导致网站的权重变为0。
【解决方案】Teleport——将节点图转化成列转移概率矩阵,再修正马尔科夫矩阵M
修
正
过
程
:
M
←
M
+
a
T
(
e
e
T
n
)
\bold {修正过程}: M \leftarrow M + a^T(\frac {ee^T} n)
修正过程:M←M+aT(neeT)
a
=
[
a
0
,
a
1
,
.
.
.
,
a
n
]
\bold a=[\bold a_0,\bold a_1,...,\bold a_n]
a=[a0,a1,...,an](
a
i
\bold a_i
ai为行向量),当修正前的M中第
i
i
i列数值全为0(即对应节点无出链)时,则
a
i
\bold a_i
ai中的元素全为1,否则矩阵
a
i
\bold a_i
ai中元素全为0。
e
e
T
ee^T
eeT为由元素1填满的n*n矩阵,
n
n
n为M矩阵的行数(或列数)。
【示例】
如上图,因为第1列元素全为0,所以有 a 1 = [ 1 , 1 , 1 ] \bold a_1 =[1,1,1] a1=[1,1,1],而第0列和第2列不全为0,所以 a 0 , a 2 \bold a_0,\bold a_2 a0,a2元素全为0。
对M矩阵进行修正的过程如上图所示。
4、Spider Traps问题
A节点与其他节点之间不存在出链(更具体地说,节点A存在自环),此为Spider Traps问题。由表格可见,其会导致网站权重变为向某一个含自环的节点偏移(因为此节点PR值最终会趋于1)。
【解决方案】Random Teleport——将节点图转化成列转移概率矩阵,再修正马尔科夫矩M(随机浏览模型)
修
正
过
程
:
M
←
β
M
+
(
1
−
β
)
e
e
T
n
\bold {修正过程}: M \leftarrow \beta M + (1-\beta )\frac {ee^T} n
修正过程:M←βM+(1−β)neeT
β
\beta
β为跟随出链打开网页的概率;
1
−
β
1-\beta
1−β为随机跳转到其他网页的概率,例如浏览网页A时有一定概率会打开网页B或C。
e e T ee^T eeT为由元素1填满的n*n矩阵, n n n为M矩阵的行数(或列数)。
【示例】
β \beta β值取值范围一般是在[0.8 , 0.9]。
如上图所示,修正后的M矩阵中,A所在的列中元素值已然发生了改变。
【思考】在使用 β \beta β解决Spider Traps问题时能否顺便解决Dead Ends问题?
答:不能,使用 β \beta β解决Dead Ends问题时,修正后的M不满足转移概率矩阵的性质:列之和为1。因此,禁用 β \beta β是不足以解决Dead Ends问题的。
5、最终修正公式
当同时遇到Dead Ends问题和Spider Traps问题时,修正公式如下:
P
R
(
a
)
=
[
β
(
M
+
a
T
(
e
e
T
n
)
)
+
(
1
−
β
)
e
e
T
n
]
∗
V
PR(a) = \bigg [\beta \bigg ( M + a^T(\frac {ee^T} n) \bigg) +(1-\beta )\frac {ee^T} n \bigg]*V
PR(a)=[β(M+aT(neeT))+(1−β)neeT]∗V
6、PageRank的优缺点
优点
(1)通过网页之间的链接来决定网页的重要性,一定程度消除了对认为排名结果的影响;
(2)离线计算PageRank值,提升了查询效率。
缺点
(1)存在时间长的网站,PageRank值会越来越大(因为其入链会越来越多);新生的网站,PageRank值增长慢(因为其初始入链相对较少且增长慢);
(2)非查询相关的特性,查询结果会偏离搜索内容;
(3)可以通过“僵尸网站”或链接,人为刷PageRank值;
7、附录
代码摘抄自PageRank算法–从原理到实现,感谢!
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)