实践Demo:基于MapReduce的PageRank网页排序
来源:互联网 发布:域名为什么要认证 编辑:程序博客网 时间:2024/06/05 02:00
【声明:鄙人菜鸟一枚,写的都是初级博客,如遇大神路过鄙地,请多赐教;内容有误,请批评指教,如有雷同,属我偷懒转运的,能给你带来收获就是我的博客价值所在。】
前面已经介绍了基于MapReduce的大图划分算法,其中最具代表性之一的实践应用便是PageRank网页排序。作为图划分算法的一种,PageRank算法自然具有在分布式系统上高效处理大规模图数据的能力,其最初被用于网页(具有超链接结构)的Rank(排序处理),但利用图中的拓扑结构关系,PageRank也可应用于节点的Rank。而且它也为很多重要的图分析算法奠定了基础。下面将详细介绍PageRank的原理以及在Hadoop分布式环境下利用java语言编译的MapReduce程序实现过程。
1 PageRank
PageRank算法是通过计算每一个网页的 PageRank 值,然后根据这个值的大小对网页的重要性进行排序。它的思想是模拟网页浏览者的浏览行为,估计这个网页浏览者分布在各个网页上的概率(衡量对应网页的重要性)。网上有太多对它的作详尽介绍的文字(比如:度娘的百度百科)
2 PageRank的最简原理模型
互联网中的网页可以看出是一个矢量图,其中网页是节点,如果网页
这个例子中有四个网页(小规模),如果当前在 A 网页,那么网页浏览者将会以
假设起初网页浏览者处在每一个网页的概率都是相等的,即
注意矩阵M中 M[i][j] 不为0表示用一个链接从 j 指向 i,M 的第一行乘以
3 终止点问题
上述网页浏览者的行为是一个马尔科夫过程的实例,要满足收敛性,需要具备一个条件:
图是强连通的,即从任意网页可以到达其他任意网页。
互联网上的网页不满足强连通的特性,因为有一些网页不指向任何网页,如果按照上面的计算,网页浏览者到达这样的网页后便终止了,导致前面累计得到的转移概率被清零,这样下去,最终的得到的概率分布向量所有元素几乎都为 0。假设我们把上面图中 C 到 A 的链接丢掉,C 变成了一个终止点,得到下面这个图:
对应的转移矩阵为:
连续迭代下去,最终所有元素都为 0:
4 陷阱问题
另外一个问题就是陷阱问题,即有些网页不存在指向其他网页的链接,但存在指向自己的链接。比如下面这个图:
网页浏览者跳转到 C 网页后,就像跳进了陷阱,陷入了漩涡,再也不能从 C 中出来,最终将导致概率分布值全部转移到 C 上来,这使得其他网页的概率分布值为 0,从而整个网页排名就失去了意义。如果按照上面图对应的转移矩阵为:
不断的迭代下去,就变成了这样:
5 解决终止点问题和陷阱问题
上述过程忽略了一个问题,那就是网页浏览者浏览的随意性,浏览者会随机地选择网页,而当遇到一个结束网页或者一个陷阱网页(比如两个示例中的C)时,他可能会在浏览器的地址中随机输入一个地址,当然这个地址可能又是原来的网页,但这有可能逃离这个陷阱。根据现实网页浏览者的浏览行为,对算法进行改进。假设每一步,网页浏览者离开当前网页跳转到各个网页的概率是1/n,查看当前网页的概率为a,那么他从浏览器地址栏跳转的概率为(1-a),于是原来的迭代公式转化为:
现在我们来计算带陷阱的网页图的概率分布:
重复迭代下去,得到:
6 基于MapReduce的Page Rank算法实现
上面的演算过程,采用矩阵相乘,不断迭代,直到迭代前后概率分布向量的值变化不大,一般迭代到 30 次以上就收敛了。真的的 web 结构的转移矩阵非常大,目前的网页数量已经超过 100 亿,转移矩阵是 100 亿* 100 亿的矩阵,故借助 MapReduce 的分布式计算方式来解决。
6.1 爬取的图数据
我们把 web 图中的每一个网页及其链出的网页作为一行,这样第四节中的 web 图结构用如下方式表示:
我们从网上爬取得原始图数据便是这种结构形式:
空的地方为null,表示空地址。完整的源数据:p2p-Gnutella08-adj.txt(上传至网盘http://pan.baidu.com/s/1dFIF31Z)。
6.2 MapReduce代码实现
这里采用java语言编写,有四部分核心代码:
(1)BuildPageRankRecords.java 将txt文本图数据转换为Hadoop可写的数据
public class BuildPageRankRecords extends Configured implements Tool { private static final Logger LOG = Logger.getLogger(BuildPageRankRecords.class); private static final String NODE_CNT_FIELD = "node.cnt"; private static class MyMapper extends Mapper<LongWritable, Text, IntWritable, PageRankNode> { private static final IntWritable nid = new IntWritable(); private static final PageRankNode node = new PageRankNode(); public void setup(Mapper<LongWritable, Text, IntWritable, PageRankNode>.Context context) { int n = context.getConfiguration().getInt(NODE_CNT_FIELD, 0); if (n == 0) { throw new RuntimeException(NODE_CNT_FIELD + " cannot be 0!"); } node.setType(PageRankNode.Type.Complete); node.setPageRank((float) -StrictMath.log(n)); } public void map(LongWritable key, Text t, Context context) throws IOException, InterruptedException { String[] arr = t.toString().trim().split("\\s+"); nid.set(Integer.parseInt(arr[0])); if (arr.length == 1) { node.setNodeId(Integer.parseInt(arr[0])); node.setAdjacencyList(new ArrayListOfIntsWritable()); } else { node.setNodeId(Integer.parseInt(arr[0])); int[] neighbors = new int[arr.length - 1]; for (int i = 1; i < arr.length; i++) { neighbors[i - 1] = Integer.parseInt(arr[i]);} node.setAdjacencyList(new ArrayListOfIntsWritable(neighbors));} context.getCounter("graph", "numNodes").increment(1); context.getCounter("graph", "numEdges").increment(arr.length - 1); if (arr.length > 1) { context.getCounter("graph", "numActiveNodes").increment(1); } context.write(nid, node); } } public static void main(String[] args) throws Exception { ToolRunner.run(new BuildPageRankRecords(), args); }}
(2)PartitionGraph.java 划分图
public class PartitionGraph extends Configured implements Tool { private static final Logger LOG = Logger.getLogger(PartitionGraph.class); public static void main(String[] args) throws Exception { ToolRunner.run(new PartitionGraph(), args); } public PartitionGraph() {} private static final String INPUT = "input"; private static final String OUTPUT = "output"; private static final String NUM_NODES = "numNodes"; private static final String NUM_PARTITIONS = "numPartitions"; private static final String RANGE = "range"; if (useRange) { job.setPartitionerClass(RangePartitioner.class); } FileSystem.get(conf).delete(new Path(outPath), true); job.waitForCompletion(true); return 0; }}
(3)RunPageRankBasic.java
public class RunPageRankBasic extends Configured implements Tool { private static class MapClass extends Mapper<IntWritable, PageRankNode, IntWritable, PageRankNode> { private static final IntWritable neighbor = new IntWritable(); private static final PageRankNode intermediateMass = new PageRankNode(); private static final PageRankNode intermediateStructure = new PageRankNode(); public void map(IntWritable nid, PageRankNode node, Context context) throws IOException, InterruptedException { // Mapper的combiner操控 private static class MapWithInMapperCombiningClass extends Mapper<IntWritable, PageRankNode, IntWritable, PageRankNode> { // 根据目标节点PageRank mass权重key值 private static final HMapIF map = new HMapIF(); private static final PageRankNode intermediateStructure = new PageRankNode(); public void setup(Context context) throws IOException { map.clear(); } public void map(IntWritable nid, PageRankNode node, Context context) throws IOException, InterruptedException { context.write(nid, intermediateStructure); int massMessages = 0; int massMessagesSaved = 0; // 沿着出链边分配PageRank mass至相邻节点 if (node.getAdjacenyList().size() > 0) { // Each neighbor gets an equal share of PageRank mass. ArrayListOfIntsWritable list = node.getAdjacenyList(); float mass = node.getPageRank() - (float) StrictMath.log(list.size()); context.getCounter(PageRank.edges).increment(list.size()); private static class CombineClass extends Reducer<IntWritable, PageRankNode, IntWritable, PageRankNode> { private static final PageRankNode intermediateMass = new PageRankNode(); public void reduce(IntWritable nid, Iterable<PageRankNode> values, Context context) throws IOException, InterruptedException { int massMessages = 0; // Reduce阶段 private static class ReduceClass extends Reducer<IntWritable, PageRankNode, IntWritable, PageRankNode> { private float totalMass = Float.NEGATIVE_INFINITY; public void reduce(IntWritable nid, Iterable<PageRankNode> iterable, Context context) throws IOException, InterruptedException { Iterator<PageRankNode> values = iterable.iterator(); // PageRank mass累积节点更新 node.setPageRank(mass); context.getCounter(PageRank.massMessagesReceived).increment(massMessagesReceived); public void cleanup(Context context) throws IOException { Configuration conf = context.getConfiguration(); String taskId = conf.get("mapred.task.id"); String path = conf.get("PageRankMassPath"); Preconditions.checkNotNull(taskId); Preconditions.checkNotNull(path); } } // Mapper阶段:(分配丢失的PageRank mass)并记录随机跳转因子 private static class MapPageRankMassDistributionClass extends Mapper<IntWritable, PageRankNode, IntWritable, PageRankNode> { private float missingMass = 0.0f; private int nodeCnt = 0; public void setup(Context context) throws IOException { Configuration conf = context.getConfiguration(); missingMass = conf.getFloat("MissingMass", 0.0f); nodeCnt = conf.getInt("NodeCount", 0); } public void map(IntWritable nid, PageRankNode node, Context context) throws IOException, InterruptedException { float p = node.getPageRank(); float jump = (float) (Math.log(ALPHA) - Math.log(nodeCnt)); float link = (float) Math.log(1.0f - ALPHA) + sumLogProbs(p, (float) (Math.log(missingMass) - Math.log(nodeCnt))); p = sumLogProbs(jump, link); node.setPageRank(p); context.write(nid, node); } } // PageRank的迭代过程 for (int i = s; i < e; i++) { iteratePageRank(i, i + 1, basePath, n, useCombiner, useInmapCombiner); } return 0; } // 执行迭代 private void iteratePageRank(int i, int j, String basePath, int numNodes, boolean useCombiner, boolean useInMapperCombiner) throws Exception { // 每次迭代过程由两个阶段组成(两个MapReduce Job) // Job 1: 沿着出链边分配PageRank mass float mass = phase1(i, j, basePath, numNodes, useCombiner, useInMapperCombiner); float missing = 1.0f - (float) StrictMath.exp(mass); // Job 2: 分配丢失的mass并关注(网页)随机跳转因子 phase2(i, j, missing, basePath, numNodes); } private void phase2(int i, int j, float missing, String basePath, int numNodes) throws Exception{ Job job = Job.getInstance(getConf()); job.setJobName("PageRank:Basic:iteration" + j + ":Phase2"); job.setJarByClass(RunPageRankBasic.class); System.out.println("Job Finished in " + (System.currentTimeMillis() - startTime) / 1000.0 + " seconds"); } // 对数概率相加 private static float sumLogProbs(float a, float b) { if (a == Float.NEGATIVE_INFINITY) return b; if (b == Float.NEGATIVE_INFINITY) return a; if (a < b) { return (float) (b + StrictMath.log1p(StrictMath.exp(a - b))); } return (float) (a + StrictMath.log1p(StrictMath.exp(b - a))); }}
(4)FindMaxPageRankNodes.java 对Node进行排序得到最终的权重序列
public class FindMaxPageRankNodes extends Configured implements Tool { private static final Logger LOG = Logger.getLogger(FindMaxPageRankNodes.class); private static class MyMapper extends Mapper<IntWritable, PageRankNode, IntWritable, FloatWritable> { private TopScoredObjects<Integer> queue; public void setup(Context context) throws IOException { int k = context.getConfiguration().getInt("n", 100); queue = new TopScoredObjects<>(k); } public void map(IntWritable nid, PageRankNode node, Context context) throws IOException, InterruptedException { queue.add(node.getNodeId(), node.getPageRank()); } } private static class MyReducer extends Reducer<IntWritable, FloatWritable, IntWritable, Text> { private static TopScoredObjects<Integer> queue; public void setup(Context context) throws IOException { int k = context.getConfiguration().getInt("n", 100); queue = new TopScoredObjects<Integer>(k); } public void reduce(IntWritable nid, Iterable<FloatWritable> iterable, Context context) throws IOException { Iterator<FloatWritable> iter = iterable.iterator(); queue.add(nid.get(), iter.next().get()); if (iter.hasNext()) { throw new RuntimeException(); } } } public FindMaxPageRankNodes() { } private static final String INPUT = "input"; private static final String OUTPUT = "output"; private static final String TOP = "top"; @SuppressWarnings({ "static-access" }) public int run(String[] args) throws Exception { Options options = new Options(); if (!cmdline.hasOption(INPUT) || !cmdline.hasOption(OUTPUT) || !cmdline.hasOption(TOP)) { System.out.println("args: " + Arrays.toString(args)); HelpFormatter formatter = new HelpFormatter(); formatter.setWidth(120); formatter.printHelp(this.getClass().getName(), options); ToolRunner.printGenericCommandUsage(System.out); return -1; } } public static void main(String[] args) throws Exception { int res = ToolRunner.run(new FindMaxPageRankNodes(), args);System.exit(res); }}
7 运行环境
VMware Workstation 12.0 (64 位)
CentOS 6.4 (64 位)
JDK 1.7.0 (64 位)
Hadoop 2.6.0 (64 位,伪分布式配置)
Eclipse 3.8 (64 位)
8 PageRank的实现结果
建立实现算法过程用到的四个主要类(class)java文件,并经过编译、run on hadoop,最终得到PageRank MapReduce代码在Eclipse上的运行结果(这里取top 20),Eclipse界面截图如下:
- 实践Demo:基于MapReduce的PageRank网页排序
- 基于MapReduce的PageRank算法实现
- 基于MapReduce框架的PageRank算法实现
- PageRank的MapReduce实现
- Pagerank 的mapreduce
- 基于MapReduce的二次排序
- MapReduce实现的PageRank原理
- PageRank算法的MapReduce实现
- pagerank算法的MapReduce实现
- mapreduce-pagerank处理网页出度为0的节点篇
- 基于erlang的mapreduce排序之一
- MapReduce作业之--实现网页排名算法PageRank(1)
- 算法系列:PageRank算法的MapReduce实现
- PageRank的初步理解和实践
- 网页重要性与PageRank的理解
- PageRank及其MapReduce实现
- PageRank Hadoop MapReduce
- MapReduce实例之PageRank
- bzoj1861: [Zjoi2006]Book 书架
- JavaScript 学习
- 基于级联分类器的目标检测objdect
- 多个18B20组成测温系统
- Android7.0文件操作之FileProvider
- 实践Demo:基于MapReduce的PageRank网页排序
- 机器学习之numpy和matplotlib学习(六)
- Java+maven+selenium+testng+jenkins自动化环境搭建
- 排序
- Android 中Imageloader缓存+Banner+Httpurl
- npm命令
- 深入解读-小程序SDK
- 算法学习(排序五)堆排序
- DockPanel