Hadoop中一些采样器的实现

Hadoop中采样是由org.apache.hadoop.mapred.lib.InputSampler类来实现的。

InputSampler类实现了三种采样方法:SplitSampler、RandomSampler和IntervalSampler。
SplitSampler、RandomSampler和IntervalSampler都是InputSampler的静态内部类,它们都实现了InputSampler的内部接口Sampler接口:
[java]  view plain copy
  1. public interface Sampler<K,V>{  
  2.       K[] getSample(InputFormat<K,V> inf,JobConf job) throws IOException;  
  3. }  
getSample方法根据job的配置信息以及输入格式获得抽样结果,三个采样类各自有不同的实现。

RandomSampler随机地从输入数据中抽取Key,是一个通用的采样器。RandomSampler类有三个属性:freq(一个Key被选中的概率),numSamples(从所有被选中的分区中获得的总共的样本数目),maxSplitsSampled(需要检查扫描的最大分区数目)。
RandomSampler中getSample方法的实现如下:
[java]  view plain copy
  1. public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {  
  2.   InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());  
  3.   ArrayList<K> samples = new ArrayList<K>(numSamples);  
  4.   int splitsToSample = Math.min(maxSplitsSampled, splits.length);  
  5.   
  6.   Random r = new Random();  
  7.   long seed = r.nextLong();  
  8.   r.setSeed(seed);  
  9.   LOG.debug("seed: " + seed);  
  10.   // shuffle splits  
  11.   for (int i = 0; i < splits.length; ++i) {  
  12.     InputSplit tmp = splits[i];  
  13.     int j = r.nextInt(splits.length);  
  14.     splits[i] = splits[j];  
  15.     splits[j] = tmp;  
  16.   }  
  17.   // our target rate is in terms of the maximum number of sample splits,  
  18.   // but we accept the possibility of sampling additional splits to hit  
  19.   // the target sample keyset  
  20.   for (int i = 0; i < splitsToSample ||  
  21.                  (i < splits.length && samples.size() < numSamples); ++i) {  
  22.     RecordReader<K,V> reader = inf.getRecordReader(splits[i], job,  
  23.         Reporter.NULL);  
  24.     K key = reader.createKey();  
  25.     V value = reader.createValue();  
  26.     while (reader.next(key, value)) {  
  27.       if (r.nextDouble() <= freq) {  
  28.         if (samples.size() < numSamples) {  
  29.           samples.add(key);  
  30.         } else {  
  31.           // When exceeding the maximum number of samples, replace a  
  32.           // random element with this one, then adjust the frequency  
  33.           // to reflect the possibility of existing elements being  
  34.           // pushed out  
  35.           int ind = r.nextInt(numSamples);  
  36.           if (ind != numSamples) {  
  37.             samples.set(ind, key);  
  38.           }  
  39.           freq *= (numSamples - 1) / (double) numSamples;  
  40.         }  
  41.         key = reader.createKey();  
  42.       }  
  43.     }  
  44.     reader.close();  
  45.   }  
  46.   return (K[])samples.toArray();  
  47. }  

首先通过InputFormat的getSplits方法得到所有的输入分区;然后确定需要抽样扫描的分区数目,取输入分区总数与用户输入的maxSplitsSampled两者的较小的值得到splitsToSample;然后对输入分区数组shuffle排序,打乱其原始顺序;然后循环逐个扫描每个分区中的记录进行采样,循环的条件是当前已经扫描的分区数小于splitsToSample或者当前已经扫描的分区数超过了splitsToSample但是小于输入分区总数并且当前的采样数小于最大采样数numSamples。

每个分区中记录采样的具体过程如下:

从指定分区中取出一条记录,判断得到的随机浮点数是否小于等于采样频率freq,如果大于则放弃这条记录,然后判断当前的采样数是否小于最大采样数,如果小于则这条记录被选中,被放进采样集合中,否则从【0,numSamples】中选择一个随机数,如果这个随机数不等于最大采样数numSamples,则用这条记录替换掉采样集合随机数对应位置的记录,同时采样频率freq减小变为freq*(numSamples-1)/numSamples。然后依次遍历分区中的其它记录。


SplitSampler从s个分区中采样前n个记录,是采样随机数据的一种简便方式。SplitSampler类有两个属性:numSamples(最大采样数),maxSplitsSampled(最大分区数)。其getSample方法实现如下:
[java]  view plain copy
  1. public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {  
  2.   InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());  
  3.   ArrayList<K> samples = new ArrayList<K>(numSamples);  
  4.   int splitsToSample = Math.min(maxSplitsSampled, splits.length);  
  5.   int splitStep = splits.length / splitsToSample;  
  6.   int samplesPerSplit = numSamples / splitsToSample;  
  7.   long records = 0;  
  8.   for (int i = 0; i < splitsToSample; ++i) {  
  9.     RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep],  
  10.         job, Reporter.NULL);  
  11.     K key = reader.createKey();  
  12.     V value = reader.createValue();  
  13.     while (reader.next(key, value)) {  
  14.       samples.add(key);  
  15.       key = reader.createKey();  
  16.       ++records;  
  17.       if ((i+1) * samplesPerSplit <= records) {  
  18.         break;  
  19.       }  
  20.     }  
  21.     reader.close();  
  22.   }  
  23.   return (K[])samples.toArray();  
  24. }  

首先根据InputFormat得到输入分区数组;然后确定需要采样的分区数splitsToSample为最大分区数和输入分区总数之间的较小值;然后确定对分区采样时的间隔splitStep为输入分区总数除splitsToSample的商;然后确定每个分区的采样数samplesPerSplit为最大采样数除splitsToSample的商。被采样的分区下标为i*splitStep,已经采样的分区数目达到splitsToSample即停止采样。

对于每一个分区,读取一条记录,将这条记录添加到样本集合中,如果当前样本数大于当前的采样分区所需要的样本数,则停止对这个分区的采样。如此循环遍历完这个分区的所有记录。


IntervalSampler根据一定的间隔从s个分区中采样数据,非常适合对排好序的数据采样。IntervalSampler类有两个属性:freq(哪一条记录被选中的概率),maxSplitsSampled(采样的最大分区数)。其getSample方法实现如下:
[java]  view plain copy
  1. public K[] getSample(InputFormat<K,V> inf, JobConf job) throws IOException {  
  2.       InputSplit[] splits = inf.getSplits(job, job.getNumMapTasks());  
  3.       ArrayList<K> samples = new ArrayList<K>();  
  4.       int splitsToSample = Math.min(maxSplitsSampled, splits.length);  
  5.       int splitStep = splits.length / splitsToSample;  
  6.       long records = 0;  
  7.       long kept = 0;  
  8.       for (int i = 0; i < splitsToSample; ++i) {  
  9.         RecordReader<K,V> reader = inf.getRecordReader(splits[i * splitStep],  
  10.             job, Reporter.NULL);  
  11.         K key = reader.createKey();  
  12.         V value = reader.createValue();  
  13.         while (reader.next(key, value)) {  
  14.           ++records;  
  15.           if ((double) kept / records < freq) {  
  16.             ++kept;  
  17.             samples.add(key);  
  18.             key = reader.createKey();  
  19.           }  
  20.         }  
  21.         reader.close();  
  22.       }  
  23.       return (K[])samples.toArray();  
  24.     }  

首先根据InputFormat得到输入分区数组;然后确定需要采样的分区数splitsToSample为最大分区数和输入分区总数之间的较小值;然后确定对分区采样时的间隔splitStep为输入分区总数除splitsToSample的商。被采样的分区下标为i*splitStep,已经采样的分区数目达到splitsToSample即停止采样。

对于每一个分区,读取一条记录,如果当前样本数与已经读取的记录数的比值小于freq,则将这条记录添加到样本集合,否则读取下一条记录。这样依次循环遍历完这个分区的所有记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值