spark 系列
Spark PageRank 算法——从原理到实现
前言
在上一篇博客已经为大家介绍了Spark GraphX图计算的入门基础。本篇博客将为大家详细介绍了 Spark GraphX中常用的算法之一:PageRank 算法的实现原理。
本文整理自博文 PageRank算法 – 从原理到实现
算法来源
PageRank,即网页排名,又称网页级别、Google左侧排名或佩奇排名。
这个要从搜索引擎的发展讲起。最早的搜索引擎采用的是分类目录的方法,即通过人工进行网页分类并整理出高质量的网站,就是靠工程师手工一条一条的整理分类得出。那时 Yahoo 和国内的 hao123 就是使用的这种方法。
后来网页越来越多,人工分类已经不现实了。搜索引擎进入了文本检索的时代,即计算用户查询关键词与网页内容的相关程度来返回搜索结果(最熟悉的就是Elasticsearch搜索引擎)。这种方法突破了数量的限制,但是搜索结果不是很好。因为总有某些网页来回地倒腾某些关键词使自己的搜索排名靠前。
谷歌的两位创始人,当时还是美国斯坦福大学 (Stanford University) 研究生的佩奇 (Larry Page) 和布林 (Sergey Brin) 开始了对网页排序问题的研究。他们的借鉴了学术界评判学术论文重要性的通用方法, 那就是看论文的引用次数。由此想到网页的重要性也可以根据这种方法来评价。于是PageRank的核心思想就诞生了,非常简单:
- 当一个网页被更多网页所链接时,其排名PageRank会越靠前;
- 排名高的网页应具有更大的表决权,即当一个网页被排名高的网页所链接时,其排名PageRank也应对应提高。
就如同下面两张图所示:
目前很多重要的链接分析算法都是在PageRank 算法基础上衍生出来的。PageRank 是Google 用于用来标识网页的等级/ 重要性的一种方法,是Google 用来衡量一个网站的好坏的唯一标准。在揉合了诸如Title 标识和Keywords 标识等所有其它因素之后, Google 通过PageRank 来调整结果,使那些更具“等级/ 重要性”的网页在搜索结果中令网站排名获得提升,从而提高搜索结果的相关性和质量。其级别从0到10级,10级为满分。PR值越高说明该网页越受欢迎(越重要)。例如:一个PR值为1的网站表明这个网站不太具有流行度,而PR值为7到10则表明这个网站非常受欢迎(或者说极其重要)。一般PR值达到4,就算是一个不错的网站了。Google把自己的网站的PR值定到10,这说明Google这个网站是非常受欢迎的,也可以说这个网站非常重要。
算法原理
PageRank算法简单来说分为两步:
- 给每个网页一个PR值(下面用PR值指代PageRank值)
- 通过(投票)算法不断迭代,直至达到平稳分布为止。
互联网中的众多网页可以看作一个有向图。下图是一个简单的例子
由于PR值物理意义上为一个网页被访问概率,所以初始值可以假设为
1
N
\frac{1}{N}
N1,其中N为网页总数。一般情况下,所有网页的PR值的总和为1。(如果不为1的话也不是不行,最后算出来的不同网页之间PR值的大小关系仍然是正确的,只是不能直接地反映概率了。而且公式也不再是本文提供的公式了,详见PageRank简单实现中的一个错误)。
A、B、C三个页面都链入D页面,则D的PR值将是A、B、C三个页面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)
继续上面的假设,A除了链接到D以外,A还链接了C和B,那么当用户访问 A 的时候,就有跳转到 B、C 或者 D 的可能性,跳转概率均为
1
3
\frac{1}{3}
31。在计算D的PR值时,A的PR值只能投出
1
3
\frac{1}{3}
31的票,B的PR值只能投出
1
2
\frac{1}{2}
21的票,而C只链接到C,所以能投出全票,所以A的PR值总和应为:
P
R
(
D
)
=
P
R
(
A
)
3
+
P
R
(
B
)
2
+
P
R
(
C
)
P R ( D ) = \frac{P R ( A )} { 3 }+ \frac{P R ( B )} { 2} + P R ( C )
PR(D)=3PR(A)+2PR(B)+PR(C)
所以可以得出一个网页的PR值计算公式应为:
P R ( u ) = ∑ ν ∈ B u P R ( ν ) L ( ν ) PR\left( u \right) = \sum\limits_{\nu \in {B_u}} {\frac{{PR\left( \nu \right)}}{{L\left( \nu \right)}}} PR(u)=ν∈Bu∑L(ν)PR(ν)
其中, B u B_{u} Bu是所有链接到网页u的网页集合,网页v是属于集合 B u B_{u} Bu的一个网页,L(v)则是网页v的对外链接数(即出度)。
根据上图计算的PR值如下:
经过几次迭代后,PR值逐渐收敛稳定。
排名泄露
如下图所示,如果存在网页没有出度链接,如A节点所示,则会产生排名泄露问题,经过多次迭代后,所有网页的PR只都趋向于0。
解决办法
中的A网页没有出链,对其他网页没有PR值的贡献,为了满足 Markov 链的收敛性,于是我们设定其对所有的网页(包括它自己)都有出链,则此图中B的PR值可表示为:
P
R
(
B
)
=
P
(
A
)
4
+
P
R
(
D
)
2
PR(B)= \frac{P(A)}{4} + \frac{PR(D)}{2}
PR(B)=4P(A)+2PR(D)
排名下沉
如下图所示,若网页没有入度链接,如节点A所示,经过多次迭代后,A的PR值会趋向于0。
排名上升
互联网中一个网页只有对自己的出链,或者几个网页的出链形成一个循环圈。那么在不断地迭代过程中,这一个或几个网页的PR值将只增不减。如下图中的C网页:
为了解决这个问题。我们想象一个随机浏览网页的人,当他到达C网页后,显然不会傻傻地一直被C网页的小把戏困住。我们假定他有一个确定的概率会输入网址直接跳转到一个随机的网页,并且跳转到每个网页的概率是一样的。
于是则此图中C的PR值可表示为:
P
R
(
C
)
=
α
(
P
R
(
D
)
2
+
P
R
(
A
)
3
)
+
(
1
−
α
)
4
PR(C)=α(\frac{ PR(D)}{2} + \frac{PR(A)}{3})+ \frac{(1−α)}{4}
PR(C)=α(2PR(D)+3PR(A))+4(1−α)
在一般情况下,一个网页的PR值计算如下:
P
R
(
p
i
)
=
∑
p
i
∈
M
p
i
P
R
(
p
j
)
L
(
p
j
)
+
(
1
−
α
)
N
PR(p_{i})=\sum_{p_{i}\in M_{p_{i}}}\frac{PR(p_{j})}{L(p_{j})} +\frac{(1−α)}{N}
PR(pi)=pi∈Mpi∑L(pj)PR(pj)+N(1−α)
其中 M p i M_{p_{i}} Mpi是所有对 p i p_{i} pi网页有出链的网页集合, L ( p j ) L(p_{j}) L(pj)是网页 p j p_{j} pj的出链数目,N是网页总数,α一般取0.85(很多论文都取0.85)。
根据上面的公式,我们可以计算每个网页的PR值,在不断迭代趋于平稳的时候,即为最终结果。具体怎样算是趋于平稳,我们在下面的PR值计算方法部分再做解释。
算法证明
- lim n → ∞ p n \mathop {\lim }\limits_{n \to \infty } {p_{\rm{n}}} n→∞limpn是否存在?
- 如果极限存在,那么它是否与 P 0 P_{0} P0的值无关?
PageRank算法的正确性证明包括上面两点。为了方便证明,我们先将PR值的计算方法转换一下。
我们可以用一个矩阵来表示这张图的出链入链关系,
S
i
j
=
0
S_{ij}=0
Sij=0 表示j网页没有对i网页的出链:
S
=
(
0
1
2
0
0
1
3
0
0
1
2
1
3
0
1
1
2
1
3
1
2
0
0
)
S = {\begin{pmatrix} 0&{\frac{1}{2}}&0&0\\ {\frac{1}{3}}&0&0&{\frac{1}{2}}\\ {\frac{1}{3}}&0&1&{\frac{1}{2}}\\ {\frac{1}{3}}&{\frac{1}{2}}&0&0 \end{pmatrix}}
S=⎝⎜⎜⎛03131312100210010021210⎠⎟⎟⎞
取E为所有分量都为 1 的列向量,接着定义矩阵:
A
=
α
S
+
(
1
−
α
)
N
E
E
T
A=αS+\frac {(1−α)}{N} EE^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 过程收敛,那么 lim n → ∞ p n \mathop {\lim }\limits_{n \to \infty } {p_{\rm{n}}} n→∞limpn 存 在 ,且与 P 0 P_0 P0 存 在 , 且 与 P 0 存在,且与P_0 存在,且与P0的选取无关。
若一个 Markov 过程收敛,那么它的状态转移矩阵A需要满足:
- A为随机矩阵。
- A是不可约的。
- A是非周期的。
第一点,随机矩阵又叫概率矩阵或 Markov 矩阵,满足以下条件:
显然我们的A矩阵所有元素都大于等于0,并且每一列的元素和都为1。所以A矩阵为左随机矩阵。
第二点,不可约矩阵:方阵A是不可约的当且仅当与A对应的有向图是强联通的。有向图G=(V,E)是强联通的当且仅当对每一对节点对u,v∈V,存在从u到v的路径。
因为我们在之前设定用户在浏览页面的时候有确定概率通过输入网址的方式访问一个随机网页,所以A矩阵同样满足不可约的要求。
第三点,要求A是非周期的。
所谓周期性,体现在Markov链的周期性上,即,在经历一段转移之后必然会回到链中的某个位置并开始循环。若A的幂具有周期性,那么这个Markov链的状态就是周期性变化的。
因为A是素矩阵(素矩阵指自身的某个次幂为正矩阵的矩阵),所以A是非周期的。
至此,我们证明了PageRank算法的正确性。
PR值计算方法
幂迭代法
首先给每个页面赋予随机的PR值,然后通过 P n + 1 = A P n P_{n+1}=AP_{ n} Pn+1=APn不断地迭代PR值。当满足下面的不等式后迭代结束,获得所有页面的PR值:
∣ P n + 1 − P n ∣ < ϵ ∣ P n + 1 − P n ∣ < ϵ ∣Pn+1−Pn∣<ϵ
特征值法
当上面提到的Markov链收敛时,必有:
P = A P ⇒ P 为 矩 阵 A 特 征 值 1 对 应 的 特 征 向 量 P = A P ⇒ P 为 矩 阵 A 特 征 值 1 对 应 的 特 征 向 量 P=AP⇒P为矩阵A特征值1对应的特征向量( 随 机 矩 阵 必 有 特 征 值 1 , 且 其 特 征 向 量 所 有 分 量 全 为 正 或 全 为 负 )$$
代数法
相似的,当上面提到的Markov链收敛时,必有:
案例演示
用spark GraphX自带的pageRank()方法实现如下:
object MyLove {
def main(args: Array[String]): Unit = {
// 创建 sparkSession对象
val spark = SparkSession.builder().master("local[*]").appName("love").getOrCreate()
val sc = spark.sparkContext
// 创建所有的点、边和图
val points = Seq((1L,("Alice",28)),(2L,("Bob",27)),(3L,("Charlie",65)),(4L,("David",42)),(5L,("Ed",55)),(6L,("Fran",50)))
val eds = Seq(Edge(2L,1L,7),Edge(2L,4L,2),Edge(4L,1L,1),Edge(5L,2L,2),Edge(5L,3L,8),Edge(5L,6L,3),Edge(3L,2L,4),Edge(3L,6L,3))
val vertices = sc.makeRDD(points)
val edges = sc.makeRDD(eds)
val graph = Graph(vertices,edges)
// 找出用户社交网络中最重要的用户
// 保证收敛的确定性,可以设置最大迭代次数和tol值。如果全局中所有节点和上次比较,小于tol,则运算终止。
graph.pageRank(0.0001).vertices.sortBy(_._2,false).collect().foreach(println)
spark.stop()
}
}
PageRank算法的优缺点
优点:
是一个与查询无关的静态算法,所有网页的PageRank值通过离线计算获得;有效减少在线查询时的计算量,极大降低了查询响应时间。
缺点:
-
没有区分站内导航链接。很多网站的首页都有很多对站内其他页面的链接,称为站内导航链接。这些链接与不同网站之间的链接相比,肯定是后者更能体现PageRank值的传递关系。
-
没有过滤广告链接和功能链接(例如常见的“分享到微博”)。这些链接通常没有什么实际价值,前者链接到广告页面,后者常常链接到某个社交网站首页。
-
对新网页不友好。一个新网页的一般入链相对较少,即使它的内容的质量很高,要成为一个高PR值的页面仍需要很长时间的推广。