MapReduce稍微高级编程之PageRank算法的实现

简介: 用MapReduce简要实现PageRank算法

一、概念:
PageRank是Google专有的算法,用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度。是Google创始人拉里·佩奇和谢尔盖·布林于1997年创造的。PageRank实现了将链接价值概念作为排名因素。
_1
这幅图表示的是一个简单的网络,下面介绍几个名词:

  1. 入链:指向该页面的链接为入链,入链相当于投票,到一个页面的超链接相当于对该页投一票。
  2. 入链数量:如果一个页面节点接收到的其他网页指向的入链数量越多,那么这个页面越重要
  3. 入链质量:指向页面A的入链质量不同,质量高的页面会通过链接向其他页面传递更多的权重。所以越是质量高的页面指向页面A,则页面A越重要。质量是指不同网页发出的链接所含的权重是不同的,比如百度百科里面的链接和你自己写的网页里面的链接肯定是不能比的。这么做主要是为了防止别人恶意刷“流量”。
  4. 出链:从本页面发出的链接为出链。

二、计算过程:
下面我们介绍一下PageRank的算法流程:

  1. 初始值:
    每个页面设置相同的PR值,Google的PageRank算法给每个页面的PR初始值为1,该页面的所有出链均分该页面的值。以上图为例,A页面的初始值为1,然后它每一条出链会均分它的值,即'AB' = 0.5,'AD' = 0.5,以此类推,最后每条链接都有自己的初始值。
  2. 迭代递归计算:每个页面都会先减去它通过出链输出去的值变成0,然后又会加上它的入链送进来的值得到新的价值。Google不断的重复计算每个页面的PageRank。那么经过不断的重复计算,这些页面的PR值会趋向于稳定,也就是收敛的状态。
  3. 在具体企业应用中怎么样确定收敛标准?

    1. 每个页面的PR值和上一次计算的PR相等

      1. 设定一个差值指标(0.0001)。当所有页面和上一次计算的PR差值平均小于该标准时,则收敛。

        1. 设定一个百分比(99%),当99%的页面和上一次计算的PR相等

三、算法修正:
上面的算法有点小问题,试想一下如果有一个页面它只有入度没有出度,即“孤立网页”。那么经过若干次计算后,该网络的权值会慢慢流向这个只进不出的页面,使得这个页面的价值异常增大。因此我们需要对 PageRank公式进行修正。即在简单公式的基础上增加了阻尼系数(damping factor)q, q一般取值q=0.85。完整的计算公式:
image

四、代码书写:
切入正题,开始写代码。
mapreduce是M->R单向过程,那么当需要这么做“M->R->M->R....”的时候,则需要设置累加器传参数。

enum Count {
    my
}

这是个累加器定义,后面会获取它。

  1. Map:
    假设我们拿到的网页数据是这么表示的:

    /**按照制表符分割
     * A    1    B    D
     * B    1    C
     * C    1    A    B
     * D    1    B    C
     */

    第0列为页面名称,第1列表示该页面的价值(初始值为1),其后的每一列表示的是该页面所有的出链指向的页面名称,当然数量是不固定的。下面是Map阶段的代码:

    class PageRankMapper extends Mapper<LongWritable, Text, Text, Text> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {  
    String[] split = value.toString().split("\t");//首先拿到值,返回列表
    String page = split[0];//page名称
    Double pagerank = Double.parseDouble(split[1]);//初始值是1,但是在随后的计算中会出现小数所以在这里转成Double
    //获取出链列表
    String[] outlist = Arrays.copyOfRange(split, 2, split.length);//用Array里面的方法复制剩下的页面,构成专门的页面数组,方便后面的迭代。
    
    //1、平分之后的pr值
    Double avgPr = pagerank / outlist.length;
    //map阶段是要将数据的值发给reduce计算的,下面构造key和value
    for (String s : outlist) {
        Text outKey = new Text(s);//key        
        Text outValue = new Text(avgPr.toString() + "\t*");//value值,以 "\t*"结尾是为了区分不同类型的数据
        //第一种数据:key是页面名称,value是被分到的值(将与来自不同map的value一起计算求和)
        context.write(outKey, outValue);
    }
    
    //指定分隔符拼接数组,将原有的行数据发过去,保持“A   1   B   C”这样的每个网页的出链列表还存在,然后将计算的结果替换1
    //加上上一次网页的pr值
    String outListStr = split[1] + "\t" + StringUtils.join(outlist, "\t");
    Text text = new Text(outListStr + "\t#");  
    context.write(new Text(page), text);
      }
    }    
  2. Reduce:
    拿到map发过来的数据应该是这样的<'A',['0.5 ','0.25 ','1 B D #']>,然后开始处理数据:

    class PageRankReducer extends Reducer<Text, Text, Text, Text> {
    
    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws         IOException, InterruptedException {
    //当前网页的pagerank值
    Double sum = 0.0;
    String lastPrAndOutList = "";
    for (Text value : values) {
        String[] split = value.toString().split("\t");//每个value值都是用制表符分割的,且最后一个字符是标记,表示哪种类型的数据
        String flag = split[split.length - 1];//取标记
        //出链列表和上一次网页的pr值
        if ("#".equals(flag)) {//出链列表
            lastPrAndOutList = value.toString();
        } else if ("*".equals(flag)) {//pagerank值加和
            sum = sum + 
    Double.parseDouble(split[0]);
        }
    }
    
    String[] split = lastPrAndOutList.split("\t");
    
    //上一次pr值
    Double lastPr = Double.parseDouble(split[0]);
    
    String[] outList = Arrays.copyOfRange(split, 1, split.length - 1);//构成输出
    
    //加上阻尼系数,计算当前pr值
    Double currPr = 0.15 / 4 + 0.85 * sum;
    
    //取绝对值
    long abs = (long) (Math.abs(currPr - lastPr) * 1000);
    
    //获取累加器对象
    Counter counter = context.getCounter(Count.my);
    
    //累加
    /**
     * 累加所有网页pr值的差值
     */
    counter.increment(abs);
    
    //写入到hdfs
    /**
     * A    0.5     B    D
     * B    1.5     C
     * C     1.5     A     B
     * D     0.5     B     C
     */
    context.write(key, new Text(currPr.toString() + "\t" + StringUtils.join(outList, "\t")));  }}
    
  3. 设置job:

    public class RunJob {

    public static void main(String[] args) throws Exception {

    int i = 0;//用来拼接字符串的
    
    //收敛阈值
    double flag = 0.1;
    
    while (true) {
        i++;
        Configuration config = new Configuration();
        FileSystem fs = FileSystem.get(config);
        Job job = Job.getInstance(config);
        job.setJarByClass(com.shujia.mr.pagerank.RunJob.class);
        job.setJobName("pagerank");
        job.setMapperClass(PageRankMapper.class);
        job.setReducerClass(PageRankReducer.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);
        job.setInputFormatClass(TextInputFormat.class);
    
        Path inputPath = new Path("E:\\data\\pagerank.txt");
    
        //后一次读取前一次的输出结果
        if (i > 1) {
            inputPath = new Path("E:\\data\\out" + (i - 1));
        }

        FileInputFormat.addInputPath(job, inputPath);

        Path outPath = new Path("E:\\data\\out" + i);
        if (fs.exists(outPath)) {
            fs.delete(outPath, true);
        }

        FileOutputFormat.setOutputPath(job, outPath);
        boolean f = job.waitForCompletion(true);

        //计算当前所有网页的pagerank的和上一次pagerank的差值的平均值
        //当差值的平均值小于设定的阈值之后收敛

        /**
         * 累加器
         *  在map端或者reduce端累加,在主函数里面读取的一个变量
         *
         */

        //获取累加器的值
        Counter counter = job.getCounters().findCounter(Count.my);
        long value = counter.getValue();
        //差值的平均值
        double l = value / 4000.0;

        System.out.println(l);

        //当差值的平均值小于设定的阈值后收敛
        if (l < flag) {
            break;
        }
    }
}
}

结语:

本篇只是简单地实现了pagerank算法,还有很多复杂情况处理,比如说并没有考虑入链质量问题,所以仅供练习。

相关文章
|
5月前
|
自然语言处理 算法 数据挖掘
【数据挖掘】十大算法之PageRank连接分析算法
文章介绍了PageRank算法的基本概念和数学模型,包括如何通过一阶马尔科夫链定义随机游走模型以及如何计算网页的重要性评分,并提供了PageRank迭代算法的具体步骤。
118 0
|
2月前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
62 2
|
2月前
|
JSON 算法 数据挖掘
基于图论算法有向图PageRank与无向图Louvain算法构建指令的方式方法 用于支撑qwen agent中的统计相关组件
利用图序列进行数据解读,主要包括节点序列分析、边序列分析以及结合节点和边序列的综合分析。节点序列分析涉及节点度分析(如入度、出度、度中心性)、节点属性分析(如品牌、价格等属性的分布与聚类)、节点标签分析(如不同标签的分布及标签间的关联)。边序列分析则关注边的权重分析(如关联强度)、边的类型分析(如管理、协作等关系)及路径分析(如最短路径计算)。结合节点和边序列的分析,如子图挖掘和图的动态分析,可以帮助深入理解图的结构和功能。例如,通过子图挖掘可以发现具有特定结构的子图,而图的动态分析则能揭示图随时间的变化趋势。这些分析方法结合使用,能够从多个角度全面解读图谱数据,为决策提供有力支持。
107 0
|
3月前
|
存储 缓存 分布式计算
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
44 0
数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
|
3月前
|
算法 Python
Python算法编程:冒泡排序、选择排序、快速排序
Python算法编程:冒泡排序、选择排序、快速排序
36 0
|
5月前
|
分布式计算 大数据 Hadoop
揭秘MapReduce背后的魔法:从基础类型到高级格式,带你深入理解这一大数据处理利器的奥秘与实战技巧,让你从此不再是编程门外汉!
【8月更文挑战第17天】MapReduce作为分布式计算模型,是大数据处理的基石。它通过Map和Reduce函数处理大规模数据集,简化编程模型,使开发者聚焦业务逻辑。MapReduce分单阶段和多阶段,支持多种输入输出格式如`TextInputFormat`和`SequenceFileInputFormat`。例如,简单的单词计数程序利用`TextInputFormat`读取文本行并计数;而`SequenceFileInputFormat`适用于高效处理二进制序列文件。合理选择类型和格式可有效解决大数据问题。
81 1
|
5月前
|
存储 算法 搜索推荐
编程之旅中的算法启示
【8月更文挑战第31天】在编程世界的迷宫里,算法是那把钥匙,它不仅能解锁问题的答案,还能引领我们深入理解计算机科学的灵魂。本文将通过一次个人的技术感悟旅程,探索算法的奥秘,分享如何通过实践和思考来提升编程技能,以及这一过程如何启示我们更深层次地认识技术与生活的交织。
|
6月前
|
存储 算法 搜索推荐
告别低效编程!Python算法设计与分析中,时间复杂度与空间复杂度的智慧抉择!
【7月更文挑战第22天】在编程中,时间复杂度和空间复杂度是评估算法效率的关键。时间复杂度衡量执行时间随数据量增加的趋势,空间复杂度关注算法所需的内存。在实际应用中,开发者需权衡两者,根据场景选择合适算法,如快速排序(平均O(n log n),最坏O(n^2),空间复杂度O(log n)至O(n))适合大规模数据,而归并排序(稳定O(n log n),空间复杂度O(n))在内存受限或稳定性要求高时更有利。通过优化,如改进基准选择或减少复制,可平衡这两者。理解并智慧地选择算法是提升代码效率的关键。
74 1
|
5月前
|
存储 算法
【C算法】编程初学者入门训练140道(1~20)
【C算法】编程初学者入门训练140道(1~20)
|
6月前
|
存储 算法 Python
震撼!Python算法设计与分析,分治法、贪心、动态规划...这些经典算法如何改变你的编程世界!
【7月更文挑战第9天】在Python的算法天地,分治、贪心、动态规划三巨头揭示了解题的智慧。分治如归并排序,将大问题拆解为小部分解决;贪心算法以局部最优求全局,如Prim的最小生成树;动态规划通过存储子问题解避免重复计算,如斐波那契数列。掌握这些,将重塑你的编程思维,点亮技术之路。
83 1

热门文章

最新文章