一、PageRank算法的简介
- PageRank算法也叫佩奇排序算法,是Google在搜索引擎称霸互联网宝藏中的的算法之一,用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度。
- 是Google创始人拉里·佩奇和谢尔盖·布林于1997年创造的
- PageRank实现了将链接价值概念作为排名因素
本次代码计算环境是 :
- Hadoop-3.1.2
- 四台主机
- 两台NN的HA(高可用的NameNode节点)
- 两台RM的HA(高可用的ResourceManager节点)
- 离线计算框架MapReduce
二、算法原理
1、基本原理
这个算法主要解决的问题是,对于Google搜索引擎来说,爬取了非常巨大的网页信息存储到服务器中,用户通过输入关键词之后,对于包含关键词的页面会通过这个PageRank算法得出页面的权重,最后根据页面的权重排序返回给用户,极大的提高了用户体验。那么这个算法的原理如下:
- 入链 ====投票:PageRank让链接来“投票“,到一个页面的超链接相当于对该页投一票
- 入链数量 :如果一个页面节点接收到的其他网页指向的入链数量越多,那么这个页面越重要。
- 入链质量: 指向页面A的入链质量不同,质量高的页面会通过链接向其他页面传递更多的权重。所以越是质量高的页面指向页面A,则页面A越重要。
- 初始值:Google的每个页面设置相同的PR值;pagerank算法给每个页面的PR初始值为1。
- 迭代计算(收敛):Google不断的重复计算每个页面的PageRank。那么经过不断的重复计算,这些页面的PR值会趋向于稳定,也就是收敛的状态。在具体企业应用中怎么样确定收敛标准?(1)每个页面的PR值和上一次计算的PR相等
(2)设定一个差值指标(0.0001)。当所有页面和上一次计算的PR差值平均小于该标准时,则收敛。(3)设定一个百分比(99%),当99%的页面和上一次计算的PR相等。
举例说明每个知识点:
这里的A/B/C/D模拟为四个页面,每个页面的PR的初始值为1,A的初链为2个,所以PR值会被平分为1/2,分别给了C和B,同时类式的,其他页面指向了A,给了PR值,依次类推所有页面。
2、互联网角度
- 只出,不入:PR会为0
- 只入,不出:PR会很高
- 直接访问网页
3、修正PageRank计算公式:增加阻尼系数
- 在简单公式的基础上增加了阻尼系数(damping factor)d
- 一般取值d=0.85。
- 完整PageRank计算公式
注意:
1、d:阻尼系数
2、M(i):指向i的页面集合
3、L(j):页面的出链数
4、PR(pj):j页面的PR值
5、 n:所有页面数
这个公式反映的是从概率论中,我们页面点击有85%概率是通过页面跳转到指定页面中,当然15%概率是通过直接访问页面。但是里面的迭代计算原理,更上面的分析是一致的。
4、基于大数据原生hodoop来思考这个算法
- 1、首先MapReduce的元语是不能被破坏的:即 “相同”的key为一组,调用一次reduce方法,方法内迭代这组数据。
- 2、通过观察,我们可以看到这样的现象,页面包含超链接,每次迭代将pr值除以链接数后得到的值传递给所链接的页面,每次迭代都要包含页面链接关系和该页面的pr值。
- 3、MapReduce设计思路: 其中:
map阶段:主要做两件事情。
第一,读懂数据,第一次附加初始pr值;
第二,映射k:v。传递页面链接关系,key为该页面,value为页面链接关系,计算链接的pr值,key为所链接的页面,value为pr值
reduce阶段:按页分组
第一: 两类value分别处理
第二: 最终合并为一条数据输出:key为页面&新的pr值,value为链接关系
实例
- 1、测试数据样本如下:
A B D
B C
C A B
D B C
-2、 map阶段计算结果:
A:BD
B:1/2
D:1/2
A:1/ - 3、reduce阶段的计算结果:
//A: 1/2, B D
//A:3/4
A: 3/4,B,D
这里需要注意的是:我们用MapReduce设计来计算时候,不仅要考虑每一行读取的pr值,而且要保存他们之间关系。方便下一次迭代计算。
三、Java代码实现
其中暂时核心代码如下:如需要整个demo代码,可私撩我
- node类
public class Node {
private double pageRank = 1.0;
private String[] adjacentNodeNames;
public static final char fieldSeparator = '\t';
public double getPageRank() {
return pageRank;
}
public Node setPageRank(double pageRank) {
this.pageRank = pageRank;
return this;
}
public String[] getAdjacentNodeNames() {
return adjacentNodeNames;
}
public Node setAdjacentNodeNames(String[] adjacentNodeNames) {
this.adjacentNodeNames = adjacentNodeNames;
return this;
}
public boolean containsAdjacentNodes() {
return adjacentNodeNames != null && adjacentNodeNames.length > 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(pageRank);
if (getAdjacentNodeNames() != null) {
sb.append(fieldSeparator).append(
StringUtils.join(getAdjacentNodeNames(), fieldSeparator));
}
return sb.toString();
}
// value =1.0 B D
public static Node fromMR(String value) throws IOException {
String[] parts = StringUtils.splitPreserveAllTokens(value,
fieldSeparator);
if (parts.length < 1) {
throw new IOException("Expected 1 or more parts but received "
+ parts.length);
}
Node node = new Node().setPageRank(Double.valueOf(parts[0]));
if (parts.length > 1) {
node.setAdjacentNodeNames(Arrays
.copyOfRange(parts, 1, parts.length));
}
return node;
}
public static Node fromMR(String v1,String v2) throws IOException {
return fromMR(v1+fieldSeparator+v2);
//1.0 B D
}
}
- 2、 RunJob类
public class RunJob {
public static enum Mycounter {
my
}
public static void main(String[] args) {
Configuration conf = new Configuration(true);
conf.set("mapreduce.app-submission.corss-paltform", "true");
//如果分布式运行,必须打jar包
//且,client在集群外非hadoop jar 这种方式启动,client中必须配置jar的位置
conf.set("mapreduce.framework.name", "local");
//这个配置,只属于,切换分布式到本地单进程模拟运行的配置
//这种方式不是分布式,所以不用打jar包
double d = 0.0000001;
int i = 0;
while (true) {
i++;
try {
conf.setInt("runCount", i);
FileSystem fs = FileSystem.get(conf);
Job job = Job.getInstance(conf);
job.setJarByClass(RunJob.class);
job.setJobName("pr" + i);
job.setMapperClass(PageRankMapper.class);
job.setReducerClass(PageRankReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
//使用了新的输入格式化类
job.setInputFormatClass(KeyValueTextInputFormat.class);
Path inputPath = new Path("/data/pagerank/input/");
if (i > 1) {
inputPath = new Path("/data/pagerank/output/pr" + (i - 1));
}
FileInputFormat.addInputPath(job, inputPath);
Path outpath = new Path("/data/pagerank/output/pr" + i);
if (fs.exists(outpath)) {
fs.delete(outpath, true);
}
FileOutputFormat.setOutputPath(job, outpath);
boolean f = job.waitForCompletion(true);
if (f) {
System.out.println("success.");
long sum = job.getCounters().findCounter(Mycounter.my).getValue();
System.out.println(sum);
double avgd = sum / 4000.0;
if (avgd < d) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class PageRankMapper extends Mapper<Text, Text, Text, Text> {
protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {
int runCount = context.getConfiguration().getInt("runCount", 1);
//A B D
//K:A
//V:B D
//K:A
//V:0.3 B D
String page = key.toString();
Node node = null;
if (runCount == 1) {
node = Node.fromMR("1.0" , value.toString());
} else {
node = Node.fromMR(value.toString());
}
// A:1.0 B D 传递老的pr值和对应的页面关系
context.write(new Text(page), new Text(node.toString()));
if (node.containsAdjacentNodes()) {
double outValue = node.getPageRank() / node.getAdjacentNodeNames().length;
for (int i = 0; i < node.getAdjacentNodeNames().length; i++) {
String outPage = node.getAdjacentNodeNames()[i];
// B:0.5
// D:0.5 页面A投给谁,谁作为key,val是票面值,票面值为:A的pr值除以超链接数量
context.write(new Text(outPage), new Text(outValue + ""));
}
}
}
}
static class PageRankReducer extends Reducer<Text, Text, Text, Text> {
protected void reduce(Text key, Iterable<Text> iterable, Context context)
throws IOException, InterruptedException {
//相同的key为一组
//key:页面名称比如B
//包含两类数据
//B:1.0 C //页面对应关系及老的pr值
//B:0.5 //投票值
//B:0.5
double sum = 0.0;
Node sourceNode = null;
for (Text i : iterable) {
Node node = Node.fromMR(i.toString());
if (node.containsAdjacentNodes()) {
sourceNode = node;
} else {
sum = sum + node.getPageRank();
}
}
// 4为页面总数
double newPR = (0.15 / 4.0) + (0.85 * sum);
System.out.println("*********** new pageRank value is " + newPR);
// 把新的pr值和计算之前的pr比较
double d = newPR - sourceNode.getPageRank();
int j = (int) (d * 1000.0);
j = Math.abs(j);
System.out.println(j + "___________");
context.getCounter(Mycounter.my).increment(j);
sourceNode.setPageRank(newPR);
context.write(key, new Text(sourceNode.toString()));
}
}
}
j = Math.abs(j);
System.out.println(j + "___________");
context.getCounter(Mycounter.my).increment(j);
sourceNode.setPageRank(newPR);
context.write(key, new Text(sourceNode.toString()));
}
}
}
- 3、运行结果