【Hadoop学习之十一】MapReduce案例分析三-PageRank

环境
  虚拟机:VMware 10
  Linux版本:CentOS-6.5-x86_64
  客户端:Xshell4
  FTP:Xftp4
  jdk8
  hadoop-3.1.1

什么是pagerank?
算法原理-1
PageRank是Google提出的算法,用于衡量特定网页相对于搜索引擎索引中的其他网页而言的重要程度。
算法原理:思考超链接在互联网中的作用?
入链 =投票
  PageRank让链接来“投票“,到一个页面的超链接相当于对该页投一票,比如A网页有一个指向B网页的链接,那么B网页就得到A的1个投票。
入链数量
  如果一个页面节点接收到的其他网页指向的入链数量越多,那么这个页面越重要。就是有很多网页指向B网页。
入链质量
  指向页面B的入链质量不同,质量高的页面会通过链接向其他页面传递更多的权重。所以越是质量高的页面指向页面B,则页面B越重要。
  就是考虑指向B网页的源网页自身的入链数量,如果源网页入链很低,那么塔指向B的价值也同样很低。


算法原理-2
初始值
  Google的每个页面设置相同的PR值
  pagerank算法给每个页面的PR初始值为1。
迭代计算(收敛)
  Google不断的重复计算每个页面的PageRank。那么经过不断的重复计算,这些页面的PR值会趋向于稳定,也就是收敛的状态。
  在具体企业应用中怎么样确定收敛标准?
  1、每个页面的PR值和上一次计算的PR相等
  2、设定一个差值指标(0.0001)。当所有页面和上一次计算的PR差值平均小于该标准时,则收敛。
  3、设定一个百分比(99%),当99%的页面和上一次计算的PR相等

算法原理-3
站在互联网的角度:
  只出,不入:PR会为0
  只入,不出:PR会很高
  直接访问网页
修正PageRank计算公式:增加阻尼系数
  在简单公式的基础上增加了阻尼系数(damping factor)d
  一般取值d=0.85。
完整PageRank计算公式


  d:阻尼系数
  M(i):指向i的页面集合
  L(j):页面的出链数
  PR(pj):j页面的PR值
  n:所有页面数

如何设计MR?

PR计算是一个迭代的过程,首先考虑一次计算
思考:
--页面包含超链接
--每次迭代将pr值除以链接数后得到的值传递给所链接的页面
--so:每次迭代都要包含页面链接关系和该页面的pr值
--mr:相同的key为一组的特征
map:
  1,读懂数据:第一次附加初始pr值
  2,映射k:v
    1,传递页面链接关系,key为该页面,value为页面链接关系
    2,计算链接的pr值,key为所链接的页面,value为pr值
reduce:
  *,按页面分组
  1,两类value分别处理
  2,最终合并为一条数据输出:key为页面&新的pr值,value为链接关系

具体数据分析:

A B D
B C
C A B
D B C

第一轮迭代:
map:
  A:BD
  B:1/2
  D:1/2
  A:1/2
reduce:
  A:1/2 B D

第二轮迭代:
map:
  A: 1/2, B D
  B:1/4
  C:1/4
  A:3/4
reduce:
  //A: 1/2, B D
  //A:3/4
  A: 3/4,B,D

 

package test.mr.pagerank;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.KeyValueTextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class RunJob {

    public static enum Mycounter {
        my
    }

    public static void main(String[] args) {
        
        Configuration conf = new Configuration(true);
        //配置:在windows单机运行  从HDFS上读取文件 结果写回HDFS
        //异构平台的支撑 会识别操作系统  可以支持Windows  默认false 在Linux上运行
        conf.set("mapreduce.app-submission.corss-paltform", "true");
        //如果分布式运行,必须打jar包
        //这个配置,只属于,切换分布式到本地单进程模拟运行的配置 这种方式不是分布式,所以不用打jar包
        conf.set("mapreduce.framework.name", "local");
        
        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("/root/pagerank/input/");
                if (i > 1) {
                    inputPath = new Path("/root/pagerank/output/pr" + (i - 1));
                }
                FileInputFormat.addInputPath(job, inputPath);
                Path outpath = new Path("/root/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.5 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()));
        }
    }
}
package test.mr.pagerank;

import java.io.IOException;
import java.util.Arrays;

import org.apache.commons.lang.StringUtils;

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
    }
}

 

posted @ 2019-01-22 10:54  cac2020  阅读(691)  评论(0编辑  收藏  举报