MapReduce编程基础(二)——数值概要(计算中位数、标准差)[内存优化]

来源:互联网 发布:淘宝上买东西不花钱 编辑:程序博客网 时间:2024/06/06 05:06

1.中位数与标准差计算示例【内存优化】

在前一篇博客中,我介绍了一种计算中位数和标准差的方法,但是该方法需要将所有的数据读入内存再进行遍历,不够优化。所以在这里,我们将前一种方法进行优化,
将所有的数值都插入列表会产生大量的重复的元素。一个规避重复元素的方法就是保存元素的计数。
例如:要保存<1,1,1,1,2,2,3,4,5,5,5>可以使用排序好的值到计数的映射来代替:(1->4,2->2,3->1,4->1,5->3).
核心概念还是与前一种方法相同,但是这样可以减少存放到内存的数据量。而且这个方法还有一个优点就是可以使用combiner来聚合map端的计数。

2.数据集

本示例使用Movielens数据集中的u1.base文件,MovieLens数据集是一个用户对电影的评分数据集,在后续的示例中我们将一直使用这个数据集,我会将这个数据集上传到CSDN方便大家下载,文件的格式如下所示:
第1列到第4列分别代表用户ID**,项目ID**,用户对项目的评分时间戳

1   1   5   8749657581   2   3   8768931711   3   4   8785429601   4   3   8768931191   5   3   8897517121   7   4   8750715611   8   1   875072484...      ....    ....943 1067    2   875501756943 1074    4   888640250943 1188    3   888640250943 1228    3   888640275943 1330    3   888692465

3.问题

对于给定的用户项目评分数据,确定每个用户评分的中位数、均方差。

4.计算方案(内存优化版)

1)自定义Writab类存储输出数据
public class MedianStdDevImproveTuple implements Writable{    private float stdDev=0;    private float median=0;    private int rate=0;    public void setStdDev(float stdDev){        this.stdDev=stdDev;    }    public double getStdDev(){        return stdDev;    }    public void setMedian(float median){        this.median=median;    }    public double getMedian(){        return median;    }    public void setRate(int rate){        this.rate=rate;    }    public double getRate(){        return rate;    }    @Override    public void readFields(DataInput in) throws IOException {        stdDev=in.readFloat();        median=in.readFloat();        rate=in.readInt();    }    @Override    public void write(DataOutput out) throws IOException {        out.writeFloat(this.stdDev);        out.writeFloat(this.median);        out.writeInt(this.rate);    }    public String toString(){        return "medianValue:"+median+"  "+"stdDev:"+stdDev;    }}
2)mapper代码
public static class MedianStdDevImproveMapper extends Mapper<Object, Text, IntWritable, SortedMapWritable>{        private IntWritable outUserId=new IntWritable();//输出健        private IntWritable outRate=new IntWritable();//输出值        private final LongWritable ONE=new LongWritable(1);        public void map(Object key, Text value ,Context context) throws IOException, InterruptedException{            String[] data=value.toString().split("  ");            if(data.length==4){                //输出的键                          outUserId.set(Integer.valueOf(data[0]));                //输出的值,用户的评分                outRate.set(Integer.valueOf(data[2]));                //定义一个Writable类来存放键和值的映射                SortedMapWritable outCommentLength=new SortedMapWritable();                outCommentLength.put(outRate, ONE);                 context.write(outUserId, outCommentLength);            }           }    }
2)reducer代码
public static class MedianStdDevImproveReducer extends Reducer<IntWritable, SortedMapWritable, IntWritable,  MedianStdDevTuple>{        private  MedianStdDevTuple result=new  MedianStdDevTuple();             private TreeMap<Integer,Long> RateCounts=new TreeMap<Integer,Long>();        public void reduce(IntWritable key, Iterable<SortedMapWritable> values,Context context) throws IOException, InterruptedException{            float sum=0;            long totalcount=0;            RateCounts.clear();            result.setStdDev(0.0);            result.setMedian(0.0);            for(SortedMapWritable v:values){                for(Entry<WritableComparable,Writable> entry:v.entrySet()){                    int rate=((IntWritable)entry.getKey()).get();                    long count=((LongWritable)entry.getValue()).get();                    totalcount+=count;                    sum+=rate*count;                    Long storedCount=RateCounts.get(rate);                    if(storedCount==null){                        //如果storedCount==null说明是第一条数据                        RateCounts.put(rate, count);                    }else{                        //后续依次存入递增的值                        RateCounts.put(rate, storedCount+count);                    }                }            }            //中位数的位置            long x=2;            long medianIndex= totalcount/x;            long previousCount=0;            long lastCount=0;            int preKey=0;            for(Entry<Integer,Long> entry:RateCounts.entrySet()){                lastCount=previousCount+entry.getValue();                //*首先分为两种情况:                 //* 1.总的评分数目是奇数:说明中位数就存在于当前key值对应的范围内,当前key值就是中位数                // * 2.总的评分数目是偶数:1)前后两个数在同一个范围类,则当前key值就是中位数                // *                     2)前后两个数不在同一个范围内,只满足其中一个数位于边界,即previousCount==medianIndex,则取前一个key和后一个key的和相除得到中位数                // *                                        if(previousCount<=medianIndex&&medianIndex<lastCount){                    if(totalcount%2==0&&previousCount==medianIndex){                        result.setMedian((float)(entry.getKey()+preKey)/2.0f);                    }else{                        result.setMedian((float)entry.getKey());                    }                    break;//中位数已经找到,跳出循环                }                previousCount=lastCount;//更新                preKey=entry.getKey();//更新            }            //计算均方差                     float mean=sum/totalcount;            float sumOfSquares=0.0f;            for(Entry<Integer, Long> entry:RateCounts.entrySet()){                sumOfSquares+=(entry.getKey()-mean)*(entry.getKey()-mean)*entry.getValue();            }            result.setStdDev((float)(Math.sqrt(sumOfSquares/(totalcount-1))));                      context.write(key, result);        }    }
2)Combiner优化代码
public static class MedianStdDevImproveCombiner extends Reducer<IntWritable, SortedMapWritable,IntWritable, SortedMapWritable>{        public void reduce(IntWritable key, Iterable<SortedMapWritable> values,Context context) throws IOException, InterruptedException{            SortedMapWritable outValue=new SortedMapWritable();            for(SortedMapWritable v:values){                            for(Entry<WritableComparable, Writable> entry:v.entrySet()){                    LongWritable count=(LongWritable)outValue.get(entry.getKey());//获取当前的计数                    if(count==null){                        outValue.put(entry.getKey(), new LongWritable(((LongWritable)entry.getValue()).get()));                                                                                     }else{                                                      outValue.put(entry.getKey(),new LongWritable(count.get()+((LongWritable)entry.getValue()).get()));                                      }                }                               }            context.write(key, outValue);        }           }

4.总结

在对有大量重复数据的数据进行存储时,可以采用本例展示的存储数据出现次数的方式来节省存储空间。
本例特别注意:在对输出文件进行设置的时候需要注意,combiner的输入和输出类型需要与map的输出类型相同,不然会出现没有输出的情况。
详情可以参考博客:http://blog.csdn.net/lucktroy/article/details/7957120
感谢该博主分享经验,帮我找出了程序中存在的问题!

原创粉丝点击