PageRank
简介
PageRank(网页级别),取自Google的创始人LarryPage。它是Google排名运算法则(排名公式)的一部分,是Google用于用来标识网页的等级/重要性的一种方法,是Google用来衡量一个网站的好坏的唯一标准。
PageRank算法将网页按重要性进行排序。有了这个排序,人们在搜索关键词时就能优先看到重要且优质的网页,从而更易于得到所需要的信息
基于页面链接属性的PageRank算法
这种算法虽然简单,却能揭示PageRank的本质
- web页面抽象
首先我们对Web页面进行抽象,我们规定①每个网页为一个节点②若当前页面有能够直接跳转到另个页面的链接则用一条有向线段表示(重复不累计),假设有A、B、C、D四个页面,则按上述规定可抽象成下图:
- PageRank的基本思想
(1) 每个网页的初始重要程度相同,比如a=1,b=1,c=1,…
(2) 如果许多网页b,c,d…b,c,d…指向某个网页aa,则网页aa很重要
(3) 如果某个重要的网页aa指向某个网页bb,则网页bb因为aa很重要也会获得更高的重要度
按照此种思想,在上述有向图中,A跳转到B、C、D的初始概率都为1/3,B跳转A、C、D的概率分别为1/2、1/2、0,一次类推,用矩阵表示它们之间跳转概率,得到矩阵M
初始时每个页面的rank值为1/N,这里就是1/4。按A−D顺序得到向量v
矩阵M和v相乘的到的是rank值,以A为例,M的第一行与v相乘之和即为在访问了各个页面并跳转到A页面的概率,一次类推得到个页面的的新的rank值:
一直迭代这个过程,最后会发现v最终会大约收敛在(1/4,1/4,1/5,1/4),这就是A、B、C、D的PageRank值
- 终止点问题和陷阱问题
终止点问题
在上面我们提到,4个网页之间相互跳转形成了强连通图,但实际中有些网页是不跳转到其他页面的,比如将C跳转到D的链接去除,形成下图,此时改图就不是强连通了
此时初始概率矩阵如下:
通过不断迭代,最终发现概率会趋向于0
陷阱问题
当一个页面不跳转到其他页面,但是能够跳转向自己时,如下图
如上图,对应的矩阵为
不断迭代相乘会发现v趋近0、0、1、0
- 对上述问题的解决
为了克服上述带来的问题,需要对PageRank计算方法进行一个平滑处理,加入一个随机转移概率,就是我们假设在任何一个页面浏览的用户都有可能以一个极小的概率瞬间转移到另外一个随机页面。
例如假设跳转到当前页面或当前页面的其他链接为α,则跳转到其他页面的(即除了当前页面和当前页面能直接跳转的页面)概率为(1-α)
那么心得rank值计算公式有原来的:
转化为如下,其中e为[1/N,1/N,…,1/N]
有的也写成如下,其中N为网页的个数,e为N维的单位向量
利用该公式计算迭代,直到收敛,就可以得到最终rank值
Spark实现PageRank算法
object PageRankDemo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("pageRankDemo")
val sc = new SparkContext(conf)
// 用于表示网页间的跳转关系
val linksRDD: RDD[(String, Array[String])] = sc.parallelize(Array(("A",Array("D")),("B",Array("A")),("C",Array("A","B")),("D",Array("A","C"))))
// 形成键值对
val links: RDD[(String, Array[String])] = linksRDD.map(x=>(x._1,x._2))
// 规定每个网页的初始概率为1,α值为0.85
var ranks: RDD[(String, Double)] = sc.parallelize(Array(("A",1.0),("B",1.0),("C",1.0),("D",1.0)))
for(i <- 1 to 10){
// 将links和ranks表等值连接,连接后为(网页,(跳转网页数组,rank值))
val joinRDD: RDD[(String, (Array[String], Double))] = links.join(ranks)
// 用于分别计算每个网页在其他网页上的跳转概率
val flatMapRDD: RDD[(String, Double)] = joinRDD.flatMap({
case (url, (arrys, rank)) => arrys.map(x => (x, rank / arrys.size))
})
// 聚合各个网页跳转概率,得到总的跳转概率
val reduceByKeyRDD: RDD[(String, Double)] = flatMapRDD.reduceByKey((x,y)=>x+y)
// 计算新的rank值
ranks = reduceByKeyRDD.mapValues(0.85*_+0.15/4)
}
ranks.collect.foreach(println)
}
}
- Spark GraphX中也提供内封装好的pageRank,可以直接使用
def pageRank(tol: Double,resetProb: Double): org.apache.spark.graphx.Graph[Double,Double]
①第一个参数为差值指标,即所有页面和上一次计算的PR差值平均小于该标准时,则收敛
②第二个为跳转概率,默认为0.15
例:有一个社交网络平台,用户可以给其他用户点赞,每个用户有姓名和年龄属性,点赞详情如下图所示
// 按照上图转换成Graph
val userRdd = sc.makeRDD(
Array(
(1L,("Alice",28)),
(2L,("Bob",27)),
(3L,("Charlie",65)),
(4L,("David",42)),
(5L,("Ed",55)),
(6L,("Fran",50))
)
)
val usercallRdd = sc.makeRDD(
Array(
Edge(2L,1L,7),
Edge(3L,2L,4),
Edge(4L,1L,1),
Edge(2L,4L,2),
Edge(5L,2L,2),
Edge(5L,3L,8),
Edge(3L,6L,3),
Edge(5L,6L,3)
)
)
val userCallGraph = Graph(userRdd,usercallRdd)
// 使用pageRank
userCallGraph.pageRank(0.1).vertices.collect.foreach(println)
userCallGraph.pageRank(0.00001).vertices.collect.foreach(println)