由于工作原因好长时间没有更新博客了,本来打算从事数据相关的工作后来阴差阳错的又回到了Java开发的工作上,顿感自己离数据科学这条道路越来越远。。。
MapReduce之PageRank
MapReduce之PageRank简化实现
PageRank算法是用来衡量评估网页重要性或者等级的算法,Google据此表示网页的PR值,从0级到10级,级数越高说明该网页越重要。
互联网上的各个网页之间的连接关系我们都可以看成是一个有向图,对于任何网页,他们的PR值都可以表示为:
P
R
(
u
)
=
∑
v
∈
B
u
P
R
(
v
)
L
(
v
)
PR(u) = \sum_{v \in B_u}^{}\frac{PR(v)}{L(v)}
PR(u)=v∈Bu∑L(v)PR(v)
其中
B
u
B_u
Bu是所有链接到网页
u
u
u的网页集合,网页
v
v
v是属于集合
B
u
B_u
Bu的一个网页,
L
(
v
)
L(v)
L(v)则是网页
v
v
v对外链接数(即出度)
更为详细的内容例如排名泄漏和排名下沉以及PageRank的随即浏览模型等问题网络上有更为详细的描述,在这里就不具体的展开讲解了
重点开始了
这部分分为三个阶段来利用MapReduce框架实现PageRank的简单计算过程,分别为建立网页之间的链接关系图,迭代计算PageRank数值,按照PageRank值从大到小进行顺序输出三步
- 1、建立网页之间的链接关系图(GraphBuilder)
让我们将该步骤简化一些,直接在文本中将URL写入,毕竟现在主要是实现计算这个过程,详细的从网页中获取其他网站链接的方法可以自由发挥,通过一个Map和Reducer方法将文本信息整理为 <URL,(PR_init,link_list)> 这样的类型
样例输入
linkA linkB,linkC,linkD
linkB linkA,linkD
linkC linkD
linkD linkB
mapper阶段任务
该阶段只要是将每行网页URL作为输出的key,pageRank的初始值和网页的出度连表(链接的url)一起作为value,用符号区别开来,
mapper阶段编码
public class GraphBuilderMapper
extends Mapper<LongWritable,Text,Text,Text> {
private static Text URL = new Text();
private static Text Value = new Text();
public void map(LongWritable key,Text value,Context context){
String[] urls = value.toString().split(" ");
System.out.println("length is "+urls.length+" "+urls[1]);
String PR_INIT = "0.25\t"+urls[1]; //初始化PR
URL.set(urls[0]);
Value.set(PR_INIT);
try {
context.write(URL,Value);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
reducer阶段任务
直接输出,不需要作任何处理
reducer阶段编码
public class GroupBuilderReducer
extends Reducer<Text,Text, Text,Text> {
public void reduce(Text key,Iterable<Text> values,Context context) {
try {
for(Text value:values){
context.write(key,value);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 2、迭代计算PageRank数值(PageRankIter)
该步骤的主要目的是迭代计算PageRank的数值,直到满足运行条件为止,也是这篇博客最重要的部分
mapper阶段任务
对于出度链表link_list中的每一个网页
u
u
u,输出键值对
<
u
,
c
u
r
_
r
a
n
k
/
∣
l
i
n
k
_
l
i
s
t
∣
>
<u,cur\_rank/|link\_list|>
<u,cur_rank/∣link_list∣>
其中
u
u
u表示当前URL所链接到的网页,并将其作为key,cur_rank是当前URL的PageRank,|link_list|表示当前的URL出度数量,为了完成迭代计算的过程,在这个Mapper中,还需要传递每个网页的原始链接信息以在迭代过程中传承原始链接结构图,所以还需要输出键值对<URL,link_list>
mapper阶段编码
public class PRIterMapper
extends Mapper<LongWritable,Text,Text, Text> {
public void map(LongWritable key, Text value,Context context){
String line = value.toString();
String[] tuple = line.split("\t");
String pageKey = tuple[0];
double pr = Double.parseDouble(tuple[1]);
if(tuple.length>2){
String[] linkPages = tuple[2].split(",");
for(String linkPage: linkPages){
String prValue = pageKey+"\t"+String.valueOf(pr/linkPages.length);
try {
context.write(new Text(linkPage),new Text(prValue));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try { //为了完成迭代计算
context.write(new Text(pageKey),new Text("|"+tuple[2]));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
reducer阶段任务
从Mapper处得到键值对<URL,link_list>中link_list作为当前URL的链出信息继续传递给下一轮的迭代过程,进而得到多个<u,cur_rank/|link_list|>中,cur_rank/|link_list|表示每个链如网页对当前网页u所作贡献的PageRank,把这些贡献相加即可得到当前网页u的新PageRank(new_rank),从而输出键值对<u,(new_rank,link_list)>
reducer阶段编码
public class PRIterReducer
extends Reducer<Text,Text,Text, Text> {
private static final double DAMPING = 0.85;
public void reduce(Text key,Iterable<Text> values,Context context){
String links = "";
double pageRank = 0;
for(Text value: values){
String tmp = value.toString();
if(tmp.startsWith("|")){
links="\t"+tmp.substring(tmp.indexOf("|")+1);
continue;
}
String[] tuple = tmp.split("\t");
if(tmp.length()>1){
pageRank+=Double.parseDouble(tuple[1]);
}
}
pageRank = (double) (1-DAMPING)+DAMPING*pageRank;
try {
context.write(new Text(key),new Text(String.valueOf(pageRank)+links));
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 3、按照PageRank值从大到小进行顺序输出
当然,这步主要是对其进行输出整理,核心计算过程在第二步完成了,从小到大的根据key或者从大到写只需要重载key的比较函数就可以了。
该阶段只需要一个Mapper即可,从最后一次迭代的结果中读出数据,并以PR值作为key,网页排名作为value,输出键值对<PageRank,URL>
mapper编码如下
public class PageRankViewerMapper
extends Mapper<LongWritable,Text, FloatWritable, Text> {
private Text outPage = new Text();
private FloatWritable outPr = new FloatWritable();
public void map(LongWritable key,Text value,Context context){
String[] line = value.toString().split("\t");
String page = line[0];
float pr = Float.parseFloat(line[1]);
outPage.set(page);
outPr.set(pr);
try {
context.write(outPr,outPage);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
驱动代码
驱动代码如下
public class PageRankDriver {
private static int times = 20;
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
String[] otherArgs = new String[]{"input/pageRank.txt","output"};
Job job = new Job(conf, "builder");
job.setJarByClass(PageRankDriver.class);
job.setMapperClass(GraphBuilderMapper.class);
job.setReducerClass(GroupBuilderReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]+"/Data0"));
int builderCode = job.waitForCompletion(true) ? 0 : 1;
if(builderCode == 0){
for(int i=0;i<times;i++){
String[] iterPath = new String[]{"",""};
iterPath[0] = otherArgs[1]+"/Data"+i;
iterPath[1] = otherArgs[1]+"/Data"+(i+1);
if(iterRun(conf,iterPath[0],iterPath[1]) != 0){
break;
}
}
if(0 == 0){
Job viewJob = new Job(conf,"view");
viewJob.setJarByClass(PageRankDriver.class);
viewJob.setMapperClass(PageRankViewerMapper.class);
viewJob.setOutputKeyClass(FloatWritable.class);
viewJob.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(viewJob,new Path("output/Data"+(times-1)));
FileOutputFormat.setOutputPath(viewJob,new Path("output/view"));
System.exit(viewJob.waitForCompletion(true)?0:1);
}
}
}
private static int iterRun(Configuration conf,String inputPath,String outPath) throws InterruptedException, IOException, ClassNotFoundException {
Job iterJob = new Job(conf,"iter");
iterJob.setJarByClass(PageRankDriver.class);
iterJob.setMapperClass(PRIterMapper.class);
iterJob.setReducerClass(PRIterReducer.class);
iterJob.setOutputKeyClass(Text.class);
iterJob.setOutputValueClass(Text.class);
FileInputFormat.addInputPath(iterJob,new Path(inputPath));
FileOutputFormat.setOutputPath(iterJob,new Path(outPath));
return iterJob.waitForCompletion(true)?0:1;
}
}