首先简单介绍PageRank的算法公式:
(图片来源:http://en.wikipedia.org/wiki/Page_rank)
PR(A)即A的PageRank值;d为阻尼因子,一般设为0.85;L(B)即B网站所有的出链数量(即B网站内的所有链接的数量)。
所以公式的意义是:A的PageRank值=(1-d)+d*(链接到A的所有网站的PR值/该网站的所有出链数量之和)。这里首次计算时为每个链接附上一个初始值,我设的是0.85。
公式相对还是比较简单的,至于原理推荐几篇自己学习参考的文章:http://en.wikipedia.org/wiki/Page_rank(英文),http://www.cnblogs.com/FengYan/archive/2011/11/12/2246461.html(讲解原理,不是太容易懂)。
下面主要讲解mapreduce的实现。
- 输入文件格式:
a b,c,d,e,f,g,h
b a,c,d,r,g
c s,f,g,w,h,b
d f,e,s,t,g,a
e f,s,a,c,t,g,h
f d,s,a,q,v,g,h
g d,e,t,g,h,j,y
h d,e,t,g,h,y,j
i d,w,a,c,d,s
j a,c,v,f,d,s
k d,f,h,r,s,a
其中字母代表链接,第一个字母是网站链接,其后的字符串代表该网站所有的出链,以','分割。
- Mapper的实现:
public class PageRankMapper extends Mapper {
private static final Log log = LogFactory.getLog(PageRankMapper.class);
public static float factor = 0.85f;// 阻尼因子
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
// 获取阻尼因子值,默认0.85
factor = context.getConfiguration().getFloat("mapred.pagerank.factor",
0.85f);
}
@Override
protected void map(Text key, Text value, Context context)
throws IOException, InterruptedException {
log.info(key.toString() + ";" + value.toString());
// 输入文件格式为:A b,c,d,...
// 即key为目标网站,value为A所有的出链,并以','分割
String[] outLinks = value.toString().split(",");
// 分割key,获取key的rank值,以','分割
String[] link = key.toString().split(",");
float rank = factor;
if (link.length > 1) {
rank = Float.parseFloat(link[1]);// 存在rank值,则取得,不存在则设为默认值:阻尼因子
}
int outLinkLen = outLinks.length;// A的出链数量
// 遍历A所有的出链,输出格式:key-A的各个出链(b,c,d...);value-[A+','+rank+','+outLinkLen]
// 得到每个链接的所有入链的PR值以及链接到该链接的链接的出链数
for (String s : outLinks) {
context.write(new Text(s), new Text(link[0] + ";" + rank + ";"
+ outLinkLen));
}
// 还需要输出A的所有出链信息,以便进行下一次mapreduce任务的处理
context.write(new Text(link[0]), value);
}
}
这是Mapper的实现代码,根据输入文件,这里选择KeyValueInputFormat,读入的key为一个网站链接,而value为该网站的所有出链,并以','分割。对value进行按','进行分割,得到该网站所有的出链,然后将出链作为key,value为之前的key+','+其rank值。这里注意,因为第一次计算时所有网站无rank值,故设置一个初始值,我这里设的是0.85(同阻尼因子)。同时还需要将输入的key+rank值以及输入的value一起输出,以便下次job的执行。
说明一下,除了第一次job之外,其他的job输入文件的格式其实如下:
a,0.85 b,c,d,e,f,g,h
b,0.85 a,c,d,r,g
c,0.85 s,f,g,w,h,b
d,0.85 f,e,s,t,g,a
e,0.85 f,s,a,c,t,g,h
f,0.85 d,s,a,q,v,g,h
g,0.85 d,e,t,g,h,j,y
h,0.85 d,e,t,g,h,y,j
i,0.85 d,w,a,c,d,s
j,0.85 a,c,v,f,d,s
k,0.85 d,f,h,r,s,a
相比第一次的输入文件,这里的输入文件的key发生了变化,key是网站链接+','+其rank值组成,初始时无初始值,所以没有后面的。
Reducer的实现:
public class PageRankReducer extends Reducer {
private static final Log log = LogFactory.getLog(PageRankReducer.class);
public static float factor = 0.85f;// 阻尼因子
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
// 获取阻尼因子值,默认0.85
factor = context.getConfiguration().getFloat("mapred.pagerank.factor",
0.85f);
}
@Override
protected void reduce(Text key, Iterable values, Context context)
throws IOException, InterruptedException {
log.info(key.toString());
float rank = 1 - factor;// PageRank值
String[] str;
Text outLinks = new Text();// 记录该链接的所有出链信息
// 集合的数据位key的所有入链链接的page,rank,count值,以及key的所有出链信息
for (Text t : values) {
// 入链信息以';'分割,出链信息以','分割,以此区别
str = t.toString().split(";");
if (str.length == 3) {
// 计算key的rank值=(1-d)+d*key的入链rank值/其出链数
rank += Float.parseFloat(str[1]) / Integer.parseInt(str[2])
* factor;
} else {
outLinks.set(t.toString());
}
}
context.write(new Text(key.toString() + "," + rank), outLinks);
}
}
以上是Reducer的实现代码。经过Mapper阶段的计算,到达Reducer这里的数据结构是:key-->{page1;rank1;count1,page2;rank2;count2,...,page1,page2...}。
根据key的所有入链的rank值和其对应的出链数量,可以轻松的计算出key的rank值,然后将key+‘,’+rank作为key,value则是[page1,page2,...pagen],输出到文件,在作为下次job的输入文件进行计算,也就得到了上面提到的输入文件格式。
主要代码就这两个,剩下的代码主要完成多次迭代计算的功能,就不贴出来了,想参考的可以从点击打开链接(网盘)下载完整代码。
欢迎大家一起讨论学习,谢谢