算法系列:PageRank算法的MapReduce实现

首先简单介绍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的实现。

  1. 输入文件格式:

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

其中字母代表链接,第一个字母是网站链接,其后的字符串代表该网站所有的出链,以','分割。

  1. Mapper的实现:

public class PageRankMapper extends Mapper<Text, Text, Text, Text> {
	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<Text, Text, Text, Text> {
	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<Text> 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的输入文件进行计算,也就得到了上面提到的输入文件格式。

主要代码就这两个,剩下的代码主要完成多次迭代计算的功能,就不贴出来了,想参考的可以从点击打开链接(网盘)下载完整代码。

欢迎大家一起讨论学习,谢谢

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章