算法系列: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的輸入文件進行計算,也就得到了上面提到的輸入文件格式。

主要代碼就這兩個,剩下的代碼主要完成多次迭代計算的功能,就不貼出來了,想參考的可以從點擊打開鏈接(網盤)下載完整代碼。

歡迎大家一起討論學習,謝謝

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