PageRank算法实现的三个步骤:
(1) 将每个页面的PR值初始化为1.0。
(2) 在每次迭代中,对页面p,向其“引用”的每个页面(即出链)发送一个值为rank / numNeighbors 的贡献值。
(3) 将每个页面的排序值设为0.15 + 0.85 * contributionsReceived(0.85为阻尼因子,如果不清楚可以不管)。
经过迭代计算四个页面的pr值,最终趋于收敛
Scala实现PageRank代码:
import org.apache.spark.HashPartitioner
//val links = // load RDD of (pageId, links) pairs
//var ranks = // load RDD of (pageId, rank) pairs
val links = sc.parallelize(
List(("A",List("B","C")),
("B",List("A","C")),
("C",List("A","B","D")),
("D",List("C")))).partitionBy(new HashPartitioner(100)).persist()
var ranks=links.mapValues(v=>1.0) //每个pageId的value值即pr值初始化为1
for (i <- 0 until 10) {
//flat将map得到的(links,pr)再拆开成一个个(link,pr),再组合成一个序列
val contributions=links.join(ranks).flatMap {
case (pageId,(links,rank)) =>
links.map(dest=>(dest,rank/links.size)) //遍历节点的每条出链的pr值为rank/links.size
}
ranks=contributions.reduceByKey((x,y)=>x+y).mapValues(v=>0.15+0.85*v)
}
ranks.sortByKey().collect()
- spark需要维护两个RDD:一个由(pageID, linkList) 的元素组成,包含每个页面所“引用”页面的列表;另一个由(pageID, rank) 元素组成,包含每个页面的当前PR值。
- flapMap里面的语句:将两个RDD数据集内连接以后,会得到以pageId(url)为key的(pageId, (links,
rank))形式的四个键值对(假设只有四个节点),该语句里,对每个键值对进行case判断(这里省略了遍历对象对case后的键值对的match语句),即如果属于这个形式,便给遍历对象赋值 => 符号后生成的对象( => 符号是一个赋值操作),该操作是对此次键值对中的pageId的links(该pageId所引用的page列表)进行一个map操作,将rank/links.size的值赋给每个引用的page,这样完成了一个对pageId所引用的所有page进行赋值的操作,然后将生成的lists对象赋给遍历对象,同样,再继续对其它三个遍历对象完成同样的过程。最后我们还需要将得到赋值以后的对象(得到的可以看成是lists对象)进行一个flat操作,将各个(link, rank / links.size)组合,形成contributions的子对象,得到最终的contributions。 - reduceByKey((x, y) => x + y)根据key对其所有的value求和,mapValues(v => 0.15 + 0.85*v)只对key的value进行操作。最后将得到的结果给ranks,进行更新。
- spark实现PageRank的关建是,要想到利用键值对来进行rank值的更新,所以要想清楚设计出怎样的键值对来实现最后的叠加,一个好的线索是,reduce操作往往是对相同的key进行value值的求和,所以应该设置为<page, on_of_rank>键值对,page是其中的一个节点。
Scala运行Scala程序:
-
开启spark以后,进入到/home/spark-1.6/bin/目录,运行spark-shell:
-
编写PageRank代码,输入到scala shell中,查看运行结果:
最后补充一些对flatMap函数的理解:
- 调用flatMap方法返回的是一个可迭代集合,如:var words = Set(“hive”, “hbase”, “redis”)
val result = words.flatMap(x => x.toUpperCase),输出结果是:Set(E, A, I, V, B, H, R, D, S)。符号=>左边的值x是遍历的对象(这也要求word必须是个可迭代的集合对象),右边是对x赋值或者是执行相关操作( => 符号一般是一个赋值操作);flatMap函数首先进行了一个map操作,map的结果应该变为Set[“HIVE”, “HBASE”, “REDIS”];然后对于map后的每个元素中的每个元素进行flatten,"HBASE"中的每个元素依次为: ‘H’ ‘B’ ‘A’ ‘S’ ‘E’ 因为最终是返回一个set,也就是将这些元素全部放入set中。这也要求words对象为一个含两层的集合(第一次遍历可以得到每个字符串,第二层遍历可以得到每个字符串所对应的字符) - 如果是map函数,那它不会包括后面的flatten操作,也就不会再拆分map后的每个元素对象