zoukankan      html  css  js  c++  java
  • mahout 0.7关于随机选择数据的一个bug

    [本文链接:http://www.cnblogs.com/breezedeus/archive/2012/09/05/2671572.html,转载请注明出处]

    在K-means聚类算法里,我们首先需要在已有的数据点中选取K个点作为初始中心点。这个bug就出现在中心点的随机选取上,mahout的实现不是真的随机。

    【位置】:

         org.apache.mahout.clustering.kmeans.RandomSeedGenerator#buildRandom(...) , 行 88 - 110 这段。

     

    我简化了一下,mahout的随机抽取逻辑如下:

       1: /**
       2:  * Sample K integers from integer interval [0, N).
       3:  * @param N
       4:  * @param K
       5:  * @return
       6:  */
       7: private List<Integer> generateMahoutRandomSeed( int N, int K) {
       8:     List<Integer> chosen = Lists. newArrayListWithCapacity(K);
       9:     Random random = RandomUtils. getRandom();
      10:     for ( int n = 0; n < N; ++n) {
      11:         int currentSize = chosen.size();
      12:         if (currentSize < K) {
      13:             chosen.add(n);
      14:         } else if (random.nextInt(currentSize + 1) != 0) {
      15:             int indexToRemove = random.nextInt(currentSize); // evict one chosen randomly
      16:             chosen.remove(indexToRemove);
      17:             chosen.add(n);
      18:         }
      19:     }
      20:     return chosen;
      21: }
     
    mahout上面的抽取逻辑是没法做到真正随机的,越在后面的数最终被抽取的概率会越大。下面是我的修正算法:
       1: private List<Integer> generateBDRandomSeed(int N, int K) {
       2:     List<Integer> chosen = Lists.newArrayListWithCapacity(K);
       3:     Random random = RandomUtils.getRandom();
       4:     for (int n = 0; n < N; ++n) {
       5:         int currentSize = chosen.size();
       6:         if (currentSize < K) {
       7:             chosen.add(n);
       8:         } else if (random.nextInt(n + 1) < K) {
       9:             int indexToRemove = random.nextInt(currentSize); // actually currentSize is always equal to K here
      10:             chosen.remove(indexToRemove);
      11:             chosen.add(n);
      12:         }
      13:     }
      14:     return chosen;
      15: }

     

    为了说明问题,我对上面的代码写了个测试函数:

       1: @Test
       2: public void testMahoutRandomSeedGenerator() {
       3:     int N = 11;
       4:     int K = 3;
       5:     int numLoops = 100000;
       6:     int[] times = new int [N];
       7:     Arrays. fill(times, 0);
       8:     for ( int loop = 0; loop < numLoops; ++loop) {
       9:         //List<Integer> chosen = generateMahoutRandomSeed(N, K);
      10:         List<Integer> chosen = generateWjlRandomSeed(N, K);
      11:         for (Integer i : chosen) {
      12:             ++times[i];
      13:         }
      14:     }
      15:     for ( int n = 0; n < N; ++n) {
      16:         System. out .println(times[n] / ( double)(numLoops*K) );
      17:     }
      18: }

     

    使用generateMahoutRandomSeed产生的结果如下:

    0.03344333333333333

    0.033523333333333336

    0.033356666666666666

    0.033036666666666666

    0.044596666666666666

    0.059013333333333334

    0.07856

    0.10567666666666667

    0.14029

    0.18848333333333334

    0.25002

    而使用修正后的generateBDRandomSeed,其结果如下:

    0.09096

    0.09011

    0.09051666666666666

    0.09143333333333334

    0.09082

    0.09034333333333333

    0.09032

    0.09103

    0.09162

    0.09148666666666666

    0.09136

     

    数学上可以证明我的算法是对的,证明可见我之前讲面试题目的一篇老博文。感兴趣的童鞋也可以想想为什么generateMahoutRandomSeed的实现不对:)

     

    mahout的这个问题也可能出现在它其他随机抽取相关的代码中,所以建议用到mahout随机抽取代码的同学check一下再使用。

  • 相关阅读:
    wc
    1.11考试
    diff
    C++11新利器
    vimdiff
    [学习笔记]多项式
    rev
    [AH2017/HNOI2017]礼物
    tr
    bzoj2555: SubString
  • 原文地址:https://www.cnblogs.com/breezedeus/p/2671572.html
Copyright © 2011-2022 走看看