Weka算法Clusterers-Xmeans源码分析

来源:互联网 发布:手机公众号软件 编辑:程序博客网 时间:2024/05/22 15:29

转自:http://blog.csdn.net/roger__wong/article/details/39755615

http://blog.csdn.net/roger__wong/article/details/39617309


[java] view plaincopy
  1. <p></p><p><span style="font-size:18px">上几篇博客都是分析的分类器算法(有监督学习),这次就分析一个聚类算法(无监督学习)。</span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">一、算法</span></p><p><span style="font-size:18px">Xmeans算法基本就是大名鼎鼎的K-means算法,然后Weka做了一点“小”改进,使之能自动确定聚类数量,那么首先就说一下K-means算法。顺便说一下Weka原生的Kmeans算法是SimpleKMeans聚类器。</span></p><p><span style="font-size:18px">K-means算法是属于典型的简单但有有效的算法,具有非常直观的美感,其过程如下:</span></p><p><span style="font-size:18px">输入:聚类数量K,以及数据集data</span></p><p><span style="font-size:18px">1、随机选取K个点作为聚类中心</span></p><p><span style="font-size:18px">2、对于数据集中每个用例,找出离其最近的聚类中心i,将这个用例归到第i类。</span></p><p><span style="font-size:18px">3、对于每个分类,重新计算聚类中心</span></p><p><span style="font-size:18px">4、重复23,直到达到迭代退出的条件。</span></p><p><span style="font-size:18px">K-means的时间复杂度是O(snk),其中s是迭代次数,和退出迭代的条件选取有关,n是数据集数量,k是聚类的数量,可以看出,在聚类数量要求不多的情况下,算法还是比较高效的。</span></p><p><span style="font-size:18px">但K-means的缺点以下两个:</span></p><p><span style="font-size:18px">1、不稳定,最后聚类结果和初始的聚类中心之间有很大关系。</span></p><p><span style="font-size:18px">2、只能处理连续值,无法处理离散值。</span></p><p><span style="font-size:18px">针对1,产生了K-means的扩展K-means++算法,针对2,则有K-modes算法以及K-prototype算法,有兴趣的读者可以去搜一下,这里不展开说了。</span></p><p><span style="font-size:18px">K-means算法的关键有以下几点:</span></p><p><span style="font-size:18px">1、如何计算各用例之间的“距离”</span></p><p><span style="font-size:18px">2、所谓的“迭代退出条件”是什么</span></p><p><span style="font-size:18px">3、如何确定聚类中心</span></p><p><span style="font-size:18px">4、在实现过程中有没有一些用来提高效率的trick</span></p><p><span style="font-size:18px">本篇博客在分析源码时将着重去解决以上4个问题。</span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">二、源码</span></p><p><span style="font-size:18px">weka.clusterers.Xmeans继承自RandomizableClusterer类(从名字来猜测是不稳定的聚类器,其可以传入一个随机数种子),而后者又继承自AbstractClusterer(含有两个关键的虚方法buildClusterer和clusterInstance),因此我们着重分析Xmeans对buildClusterer和clusterInstance的实现</span></p><p><span style="font-size:18px">Xmeans方法只能处理连续型数值、日期、以及MissingValue,可以从getCapabilities中看到。</span></p><p><span style="font-size:18px"></span></p><p><span style="font-size:18px">1、buildCLusterer</span></p><p><span style="font-size:18px">该方法接受Instances作为参数,作用是训练聚类模型。</span></p><p><span style="font-size:18px"></span></p><pre name="code" class="java"public void buildClusterer(Instances data) throws Exception {  
  2.   
  3.     // 先测一下这个data的属性是否能处理。  
  4.     getCapabilities().testWithFail(data);  
  5.     //这两个是最小聚类数量和最大聚类数量  
[java] view plaincopy
  1. if (m_MinNumClusters > m_MaxNumClusters) {  
  2.   throw new Exception("XMeans: min number of clusters "  
  3.       + "can't be greater than max number of clusters!");  
  4. }  
  5.   
  6. m_NumSplits = 0;  
  7. m_NumSplitsDone = 0;  
  8. m_NumSplitsStillDone = 0;  
  9.   
  10. // 替换掉MissingValue,如果是数值型,则替换为平均值,如果是枚举型,则替换为出现最多的那个值  
[java] view plaincopy
  1. // 这里可以算预处理数据时的一个小技巧  
  2. m_ReplaceMissingFilter = new ReplaceMissingValues();  
  3. m_ReplaceMissingFilter.setInputFormat(data);  
  4. m_Instances = Filter.useFilter(data, m_ReplaceMissingFilter);  
  5.   
  6. // 设定一个随机种子  
  7. Random random0 = new Random(m_Seed);  
  8.   
  9. // 聚类数量从最小聚类数量开始,这个值默认是2  
  10. m_NumClusters =  m_MinNumClusters;  
  11.   
  12. //这里是默认的算距离的方法,可以传入自定义的函数,默认使用欧式距离。  
  13. if (m_DistanceF == null) {  
  14.   m_DistanceF = new EuclideanDistance();  
  15. }  
  16. //这两个函数都没实现,不知道放这里的用意是什么  
  17. m_DistanceF.setInstances(m_Instances);  
  18. checkInstances();  
  19. nbsp;  
[java] view plaincopy
  1. //测试相关,暂时忽略  
  2. if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile())  
  3.   initDebugVectorsInput();  
  4.   
  5. // allInstList存放所有Instances的下标  
  6. int[] allInstList = new int[m_Instances.numInstances()];   
  7. for (int i = 0; i < m_Instances.numInstances(); i++) {  
  8.   allInstList[i] = i;  
  9. }  
  10.   
  11. // 只是拷贝一个表头  
  12. m_Model = new Instances(m_Instances, 0);  
  13.   
  14. // 确定聚类中心  
  15. if (m_CenterInput != null) {  
  16.   //聚类中心可以从文件读取,注意m_ClusterCenters本身是一个Instances对象,但这里似乎没有判断这个m_ClusterCenters和m_Model(也就是传入的训练集)是否同构  
  17.   m_ClusterCenters = new Instances(m_CenterInput);  
  18.   m_NumClusters = m_ClusterCenters.numInstances();//如果传入了聚类中心文件,那么就更新一下聚类中心数量  
  19. }  
  20. else  
  21.   // 随机选取聚类中心,有放回的随机抽样。  
  22.   m_ClusterCenters = makeCentersRandomly(random0,  
  23.          m_Instances, m_NumClusters);  
  24. PFD(D_FOLLOWSPLIT, "\n*** Starting centers ");//这个是debug函数,忽略  
  25. for (int k = 0; k < m_ClusterCenters.numInstances(); k++) {  
  26.   PFD(D_FOLLOWSPLIT, "Center " + k + ": " + m_ClusterCenters.instance(k));  
  27. }  
  28.   
  29. PrCentersFD(D_PRINTCENTERS);//打日志的函数,忽略  
  30.   
  31. boolean finished = false;  
  32. Instances children;   
  33.   
  34. // 是否使用KDTree,简单说一下KDTree,如果给定一堆点X,又给定一个点A,A离X中最近的那个点,传统的做法遍历整个X集合,找出最近的,时间复杂度为O(n),构建KDTree之后(本质是在空间上建立索引),时间复杂度可以将为O(logn)  
  35. if (m_UseKDTree)  
  36.   m_KDTree.setInstances(m_Instances);  
  37.   
  38. // 迭代次数  
  39. m_IterationCount = 0;  
  40.   
  41. /**  
  42.  * 训练过程由两次迭代组成,外层迭代进行聚类中心的分裂,内层迭代对每个实例进行划分并算出新的聚类中心,外层迭代的退出条件有两个  
  43.  * 1. finished为true(finished为true的条件后面会说到)  
  44.  * 2. 达到最大迭代次数  
[java] view plaincopy
  1.  * 注意,m_ClusterCenters有可能已经比m_MaxClusters大了,因为可能是从文件读入的聚类中心,这种情况下迭代也会进行一次,因为finish是在循环结束时判断的  
  2.  */  
  3. while (!finished &&  
  4.        !stopIteration(m_IterationCount, m_MaxIterations)) {  
  5.   PFD(D_FOLLOWSPLIT, "\nBeginning of main loop - centers:");  
  6.   PrCentersFD(D_FOLLOWSPLIT);  
  7.   PFD(D_ITERCOUNT, "\n*** 1. Improve-Params " + m_IterationCount +   
  8. . time");  
  9.   m_IterationCount++;  
  10.   
  11.   // converged代表两次内层迭代,所产生的聚类结果是否一样  
  12.   boolean converged = false;  
  13.   
  14.   // 这是一个一维数组,记录每个实例被分到了哪个聚类中心  
  15.   m_ClusterAssignments = initAssignments(m_Instances.numInstances());  
  16.   // 这个二维数组存放每个聚类中心都有那些实例,很奇怪的是weka全都是用数组,而没用list这样的数据结构,估计是从效率方面进行考虑。  
  17.   int[][] instOfCent = new int[m_ClusterCenters.numInstances()][];  
  18.   
  19.   // 内层迭代的计数器  
  20.   int kMeansIteration = 0;  
  21.   
  22.   // 打日志忽略  
  23.   PFD(D_FOLLOWSPLIT, "\nConverge in K-Means:");  
[java] view plaincopy
  1.      //进行内层迭代,内层迭代退出的条件也有两个,第一个是迭代次数达到最大,第二个是两次循环的聚类结果一样  
  2.      while (!converged &&   
  3.      !stopKMeansIteration(kMeansIteration, m_MaxKMeans)) {  
  4.   
  5. kMeansIteration++;  
  6. converged = true;  
  7.   
  8.        // 把实例分给相应的聚类中心,这里对converged进行了赋值,但后面有覆盖了所以这个赋值没有意义。这个函数比较麻烦但没有什么算法思想,就不展开分析了,KDTree结构或许会在后面的博客去分析其实现。  
  9.        converged = assignToCenters(m_UseKDTree ? m_KDTree : null,  
  10.                 m_ClusterCenters,   
  11.                 instOfCent,  
  12.                 allInstList,   
  13.                 m_ClusterAssignments,  
  14.                 kMeansIteration);  
  15.   
  16. PFD(D_FOLLOWSPLIT, "\nMain loop - Assign - centers:");//打日志忽略  
  17. PrCentersFD(D_FOLLOWSPLIT);//打日志忽略  
  18. // 重新算聚类中心,如果两次聚类中心一样,就返回true,两次聚类中心一样,和两次的聚类结果一样是完全等价的。聚类中心的计算方法是算数平均值。  
  19.        converged = recomputeCenters(m_ClusterCenters, // 聚类中心  
  20.                  instOfCent,       // 这些聚类中心的实例  
  21.                  m_Model);         // 表头  
  22.      PFD(D_FOLLOWSPLIT, "\nMain loop - Recompute - centers:");  
  23.      PrCentersFD(D_FOLLOWSPLIT);  
  24.      }  
  25.      PFD(D_FOLLOWSPLIT, "");  
  26.      PFD(D_FOLLOWSPLIT, "End of Part: 1. Improve-Params - conventional K-means");  
  27.   
  28.   
  29.      //计算每个聚类中心的偏差,m_Mle是个数组,存储各聚类中实例到聚类中心的距离之和  
  30.      m_Mle = distortion(instOfCent, m_ClusterCenters);  
[java] view plaincopy
  1. //bic是“贝叶斯失真规则”,越小说明模型对数据拟合越好,百度百科连接http://baike.baidu.com/view/1425589.htm?fr=aladdin#2。反正越小越好  
  2. m_Bic = calculateBIC(instOfCent, m_ClusterCenters, m_Mle);  
  3. PFD(D_FOLLOWSPLIT, "m_Bic " + m_Bic);  
  4.   
  5. int currNumCent = m_ClusterCenters.numInstances();  
[java] view plaincopy
  1.      //新的聚类中心,可以遇见到,每个原聚类中心都要进行分裂,因为容量是currNumCent*2  
  2.      Instances splitCenters = new Instances(m_ClusterCenters,   
  3.                      currNumCent * 2);  
  4.        
  5.      //   
  6.      double[] pbic = new double [currNumCent];  
  7.      double[] cbic = new double [currNumCent];  
  8.              
  9.      // 对中心进行分裂  
  10.      for (int i = 0; i < currNumCent   
  11.    // 原备注说加了下一行可以提高速度,我也不是很懂  
  12.    //        && currNumCent + numSplits <= m_MaxNumClusters  
  13.           ;   
  14.    i++) {  
  15.   
  16. PFD(D_FOLLOWSPLIT, "\nsplit center " + i +  
  17.           " " + m_ClusterCenters.instance(i));  
  18. Instance currCenter = m_ClusterCenters.instance(i);  
  19. int[] currInstList = instOfCent[i];  
  20. int currNumInst = instOfCent[i].length;//代表这个聚类中有几个实例  
  21.   
  22. // 如果目前的实例小于等于2,就直接复制自己一份,每个聚类中心必须分裂,当然如果两个instance,每个点都当做聚类中心也可以,但直接dummy自己也不影响最后结果。  
  23. if (currNumInst <= 2) {  
  24.   pbic[i] = Double.MAX_VALUE;  
  25.   cbic[i] = 0.0;  
  26.   // add center itself as dummy  
  27.   splitCenters.add(currCenter);  
  28.   splitCenters.add(currCenter);  
  29.   continue;  
  30. }  
  31.   
  32. //m_Mle[i]代表聚类i上的距离误差和,除以分类数得到平均误差,但这个误差并不是方差,这个变量的名字有点误导性。。。。  
  33. double variance = m_Mle[i] / (double)currNumInst;  
[java] view plaincopy
  1.        //通过某种方式分裂成两个中心,这个分裂过程还是挺有意思的,主流程之后会详细分析  
  2. children = splitCenter(random0, currCenter, variance, m_Model);  
  3.   
  4. // 准备用这个聚类上的所有数据,根据这两个新的聚类中心,再做一次聚类  
  5. int[] oneCentAssignments = initAssignments(currNumInst);  
  6. int[][] instOfChCent = new int [2][]; // todo maybe split didn't work  
  7.   
  8. // 标志记录两次迭代是否一样,下面循环逻辑和之前的聚类过程基本一样  
  9. converged = false;  
  10. int kMeansForChildrenIteration = 0;  
  11. PFD(D_FOLLOWSPLIT, "\nConverge, K-Means for children: " + i);  
  12. while (!converged &&   
  13.          !stopKMeansIteration(kMeansForChildrenIteration,   
  14.                m_MaxKMeansForChildren)) {  
  15.   kMeansForChildrenIteration++;  
  16.     
  17.   converged =  
  18.     assignToCenters(children, instOfChCent,  
  19.             currInstList, oneCentAssignments);  
  20.   
  21.   if (!converged) {         
  22.     recomputeCentersFast(children, instOfChCent, m_Model);//这个和recomputeCenters唯一的区别就是不算converged  
  23.   }  
  24. }   
  25.   
  26.   
  27. splitCenters.add(children.instance(0));  
  28. splitCenters.add(children.instance(1));  
  29.   
  30. PFD(D_FOLLOWSPLIT, "\nconverged cildren ");  
  31. PFD(D_FOLLOWSPLIT, " " + children.instance(0));  
  32. PFD(D_FOLLOWSPLIT, " " + children.instance(1));  
  33.   
  34. // 分别计算父聚类中心和子聚类中心(2个)的BIC  
  35. pbic[i] = calculateBIC(currInstList, currCenter,  m_Mle[i], m_Model);  
  36. double[] chMLE = distortion(instOfChCent, children);  
  37. cbic[i] = calculateBIC(instOfChCent, children, chMLE);  
  38.   
  39.      } //对于每个聚类中心都做上述操作,循环结束  
  40.   
  41.      // 这个函数根据之前算出的BIC,计算出新的聚类中心,具体怎么选的后面会再跟进去详细说。  
  42.      Instances newClusterCenters = null;  
  43.      newClusterCenters = newCentersAfterSplit(pbic, cbic, m_CutOffFactor,  
  44.                                                 splitCenters);  
  45.   
  46.      int newNumClusters = newClusterCenters.numInstances();  
  47.      if (newNumClusters != m_NumClusters) {  
  48. //如果新的聚类中心数量和老的不相等,进入这个if。  
  49. PFD(D_FOLLOWSPLIT, "Compare with non-split");  
  50.   
  51. int[] newClusterAssignments =   
  52.   initAssignments(m_Instances.numInstances());  
  53.   
  54. int[][] newInstOfCent = new int[newClusterCenters.numInstances()][];  
  55. //把所有instance放到新的聚类中心上。  
  56. converged = assignToCenters(m_UseKDTree ? m_KDTree : null,  
  57.                 newClusterCenters,   
  58.                 newInstOfCent,  
  59.                 allInstList,   
  60.                 newClusterAssignments,  
  61.                 m_IterationCount);  
  62.   
  63. double[] newMle = distortion(newInstOfCent, newClusterCenters);  
  64. double newBic = calculateBIC(newInstOfCent, newClusterCenters, newMle);//算一算新的bic  
  65. PFD(D_FOLLOWSPLIT, "newBic " + newBic);  
  66. if (newBic > m_Bic) {//如果新的bic比旧的大,说明新的聚类效果好,则用新的替换老的  
  67.          PFD(D_FOLLOWSPLIT, "*** decide for new clusters");  
  68.   m_Bic = newBic;  
  69.   m_ClusterCenters = newClusterCenters;  
  70.   m_ClusterAssignments = newClusterAssignments;  
  71. else {  
  72.          PFD(D_FOLLOWSPLIT, "*** keep old clusters");  
  73.        }  
  74.      }  
  75.   
  76.      newNumClusters = m_ClusterCenters.numInstances();  
  77.      if ((newNumClusters >= m_MaxNumClusters)   
  78.   || (newNumClusters == m_NumClusters)) {  
  79. finished = true;//置finish条件,当达到最大分类数量,或者没有任何分裂的时候,就置为true  
  80.      }  
  81.      m_NumClusters = newNumClusters;  
  82.    }  
  83.      
  84.    if (m_ClusterCenters.numInstances() > 0 && m_CenterOutput != null) {  
  85.      m_CenterOutput.println(m_ClusterCenters.toString());//输出模型用的,忽略  
  86.      m_CenterOutput.close();  
  87.      m_CenterOutput = null;  
  88.    }      
  89.  }  


首先处理两个问题,第一个是splitCenter用于对已有中心进行分裂,第二个是newCentersAfterSplit,根据分裂后的BIC计算出新的聚类中心,这个分裂机制可以算是XMeans区别于KMeans的最大不同点。


一、splitCenter

[java] view plaincopy
  1. protected Instances splitCenter(Random random,  
  2.                 Instance center,  
  3.                 double variance,  
  4.                 Instances model) throws Exception {  
  5.    m_NumSplits++;  
  6.    AlgVector r = null;  
  7.    Instances children = new Instances(model, 2);  
  8.   
  9.    if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile()) {  
  10.      Instance nextVector = getNextDebugVectorsInstance(model);  
  11.      PFD(D_RANDOMVECTOR, "Random Vector from File " + nextVector);  
  12.      r = new AlgVector(nextVector);  
  13.    }  
  14.    else {  
  15.      //这个model是表头,r是生成一个随机向量,每一维都是0到1之间  
  16.      r = new AlgVector(model, random);  
  17.    }  
  18.    r.changeLength(Math.pow(variance, 0.5));//改变向量的长度为sqrt(variance)这个variance就是聚类点到聚类中心的平均偏差  
  19.    PFD(D_RANDOMVECTOR, "random vector *variance "+ r);  
  20.      
  21.    // 首先生成两个聚类中心的向量  
  22.    AlgVector c = new AlgVector(center);  
  23.    AlgVector c2 = (AlgVector) c.clone();  
  24.    c = c.add(r);//c+r  
  25.    Instance newCenter = c.getAsInstance(model, random);  
  26.    children.add(newCenter);  
  27.    PFD(D_FOLLOWSPLIT, "first child "+ newCenter);  
  28.      
  29.    // c2-r  
  30.    c2 = c2.substract(r);  
  31.    newCenter = c2.getAsInstance(model, random);  
  32.    children.add(newCenter);  
  33.    PFD(D_FOLLOWSPLIT, "second child "+ newCenter);  
  34.   
  35.    return children;  
  36.  }  

执行过后的结果如图所示:



二、newCentersAfterSplit

[java] view plaincopy
  1. protected Instances newCentersAfterSplit(double[] pbic,   
  2.                      double[] cbic,  
  3.                      double cutoffFactor,  
  4.                      Instances splitCenters) {  
  5.   
  6.     //   
  7.     boolean splitPerCutoff = false;  
  8.     boolean takeSomeAway = false;  
  9.     boolean[] splitWon = initBoolArray(m_ClusterCenters.numInstances());//这个数组存放每个聚类中心是否分裂的决定  
  10.     int numToSplit = 0;  
  11.     Instances newCenters = null;  
  12.       
  13.     for (int i = 0; i < cbic.length; i++) {  
  14.       if (cbic[i] > pbic[i]) {  
  15.     // 如果child的BIC比较大,就分裂,为什么是BIC越大越好而不是越小越好?Weka的BIC公式貌似没取负。  
  16.     splitWon[i] = true; numToSplit++;  
  17.     PFD(D_FOLLOWSPLIT, "Center " + i + " decide for children");  
  18.       }  
  19.       else {  
  20.     // 默认是false,不用重新赋值。  
  21.     PFD(D_FOLLOWSPLIT, "Center " + i + " decide for parent");  
  22.       }  
  23.     }  
  24.   
  25.     if ((numToSplit == 0) && (cutoffFactor > 0)) {  
  26.       splitPerCutoff = true;  
  27.         
  28.       // 如果没有节点需要分裂,则使用cutoffFactor来决定要分裂的数量,这么做的原因是为了防止陷入局部最优点。  
  29.       numToSplit = (int)   
  30.         ((double) m_ClusterCenters.numInstances() * m_CutOffFactor);   
  31.     }  
  32.   
  33.     // 把pbic和cbic进行相减,并排序,以便找出差最大的,优先分裂。  
  34.     double[] diff = new double [m_NumClusters];  
  35.     for (int j = 0; j < diff.length; j++) {  
  36.       diff[j] = pbic[j] - cbic[j];  
  37.     }      
  38.     int[] sortOrder = Utils.sort(diff);  
  39.       
  40.     //检查一下最多的可分裂数量  
  41.     int possibleToSplit = m_MaxNumClusters - m_NumClusters;   
  42.   
  43.     if (possibleToSplit > numToSplit) {  
  44.       // 如果可分裂数量多于numToSplit,就按照numToSplit去分裂  
  45.       possibleToSplit = numToSplit;  
  46.     }  
  47.     else  
  48.       takeSomeAway = true;  
  49.   
  50.     // 如果有splitPerCuteoff标,说明使用了cutoffFactor来决定分裂多少,这时候splitWon里面肯定都是false,需要设置一定数量的为true  
  51.     if (splitPerCutoff) {  
  52.       for (int j = 0; (j < possibleToSplit) && (cbic[sortOrder[j]] > 0.0);  
  53.        j++) {  
  54.     splitWon[sortOrder[j]] = true;  
  55.       }  
  56.       m_NumSplitsStillDone += possibleToSplit;  
  57.     }   
  58.     else {  
  59.       // take some splits away if max number of clusters would be exceeded  
  60.       if (takeSomeAway) {  
  61.     int count = 0;  
  62.     int j = 0;//如果有这个标,说明能分裂的数量小于了splitWon中的数量,需要将一定数量得true设置为false  
  63.     for (;j < splitWon.length && count < possibleToSplit; j++){  
  64.       if (splitWon[sortOrder[j]] == true) count++;  
  65.     }  
  66.       
  67.     while (j < splitWon.length) {  
  68.       splitWon[sortOrder[j]] = false;  
  69.       j++;  
  70.     }  
  71.       }  
  72.     }  
  73.      
  74.     // 进行分裂操作,即若splitWon==true就分裂,否则保持原样  
  75.     if (possibleToSplit > 0)   
  76.       newCenters = newCentersAfterSplit(splitWon, splitCenters);  
  77.     else  
  78.       newCenters = m_ClusterCenters;  
  79.     return newCenters;  
  80.   }  



三、总结

首先来回顾一下整个算法流程:

1、随机选取聚类中心

2、对于每个实例,分配到离其最近的聚类中心

3、重新计算新的聚类中心

4、尝试对新的聚类中心进行分裂

5、回到2,若连续两个循环结果相同,则结束

可以看出,和传统的Kmeans相比,Xmeans最重要的改进在于可以自动决定聚类中心的数量,并进行“智能”的分裂。


最后总结一下第一篇文章开头(虽然现在已经乱码了)提出的问题:

1、如何计算各用例之间的“距离”

答:默认使用欧式距离,但可以定制传入距离函数,来计算任意两个用例的距离。

2、所谓的“迭代退出条件”是什么。

迭代有两层,分别为外层迭代和内层迭代,每一次外层迭代产生不同的聚类中心,每一次内层迭代将用例分配到各聚类中心。

外层迭代退出条件有三个:(1)达到最大迭代次数,(2)两次外层迭代产生聚类中心数量相等,即聚类中心没有分裂,(3)达到最大的聚类个数

内层迭代退出条件有二个:(1)两次内层迭代所有用例分配到的聚类中心一样(2)达到最大迭代次数

3、如何确定聚类中心

答:所有属性的算数平均值为聚类中心。

4、在实现过程中有没有一些用来提高效率的trick

使用了KDTree来寻找某个用例离得最近的中心。



0 0