上一篇文章不知道为什么前面部分乱码了,我会在这篇文章后面写个总结,总结之前开头的部分。
首先处理两个问题,第一个是splitCenter用于对已有中心进行分裂,第二个是newCentersAfterSplit,根据分裂后的BIC计算出新的聚类中心,这个分裂机制可以算是XMeans区别于KMeans的最大不同点。
一、splitCenter
protected Instances splitCenter(Random random, Instance center, double variance, Instances model) throws Exception { m_NumSplits++; AlgVector r = null; Instances children = new Instances(model, 2); if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile()) { Instance nextVector = getNextDebugVectorsInstance(model); PFD(D_RANDOMVECTOR, "Random Vector from File " + nextVector); r = new AlgVector(nextVector); } else { //这个model是表头,r是生成一个随机向量,每一维都是0到1之间 r = new AlgVector(model, random); } r.changeLength(Math.pow(variance, 0.5));//改变向量的长度为sqrt(variance)这个variance就是聚类点到聚类中心的平均偏差 PFD(D_RANDOMVECTOR, "random vector *variance "+ r); // 首先生成两个聚类中心的向量 AlgVector c = new AlgVector(center); AlgVector c2 = (AlgVector) c.clone(); c = c.add(r);//c+r Instance newCenter = c.getAsInstance(model, random); children.add(newCenter); PFD(D_FOLLOWSPLIT, "first child "+ newCenter); // c2-r c2 = c2.substract(r); newCenter = c2.getAsInstance(model, random); children.add(newCenter); PFD(D_FOLLOWSPLIT, "second child "+ newCenter); return children; }
二、newCentersAfterSplit
protected Instances newCentersAfterSplit(double[] pbic, double[] cbic, double cutoffFactor, Instances splitCenters) { // boolean splitPerCutoff = false; boolean takeSomeAway = false; boolean[] splitWon = initBoolArray(m_ClusterCenters.numInstances());//这个数组存放每个聚类中心是否分裂的决定 int numToSplit = 0; Instances newCenters = null; for (int i = 0; i < cbic.length; i++) { if (cbic[i] > pbic[i]) { // 如果child的BIC比较大,就分裂,为什么是BIC越大越好而不是越小越好?Weka的BIC公式貌似没取负。 splitWon[i] = true; numToSplit++; PFD(D_FOLLOWSPLIT, "Center " + i + " decide for children"); } else { // 默认是false,不用重新赋值。 PFD(D_FOLLOWSPLIT, "Center " + i + " decide for parent"); } } if ((numToSplit == 0) && (cutoffFactor > 0)) { splitPerCutoff = true; // 如果没有节点需要分裂,则使用cutoffFactor来决定要分裂的数量,这么做的原因是为了防止陷入局部最优点。 numToSplit = (int) ((double) m_ClusterCenters.numInstances() * m_CutOffFactor); } // 把pbic和cbic进行相减,并排序,以便找出差最大的,优先分裂。 double[] diff = new double [m_NumClusters]; for (int j = 0; j < diff.length; j++) { diff[j] = pbic[j] - cbic[j]; } int[] sortOrder = Utils.sort(diff); //检查一下最多的可分裂数量 int possibleToSplit = m_MaxNumClusters - m_NumClusters; if (possibleToSplit > numToSplit) { // 如果可分裂数量多于numToSplit,就按照numToSplit去分裂 possibleToSplit = numToSplit; } else takeSomeAway = true; // 如果有splitPerCuteoff标,说明使用了cutoffFactor来决定分裂多少,这时候splitWon里面肯定都是false,需要设置一定数量的为true if (splitPerCutoff) { for (int j = 0; (j < possibleToSplit) && (cbic[sortOrder[j]] > 0.0); j++) { splitWon[sortOrder[j]] = true; } m_NumSplitsStillDone += possibleToSplit; } else { // take some splits away if max number of clusters would be exceeded if (takeSomeAway) { int count = 0; int j = 0;//如果有这个标,说明能分裂的数量小于了splitWon中的数量,需要将一定数量得true设置为false for (;j < splitWon.length && count < possibleToSplit; j++){ if (splitWon[sortOrder[j]] == true) count++; } while (j < splitWon.length) { splitWon[sortOrder[j]] = false; j++; } } } // 进行分裂操作,即若splitWon==true就分裂,否则保持原样 if (possibleToSplit > 0) newCenters = newCentersAfterSplit(splitWon, splitCenters); else newCenters = m_ClusterCenters; return newCenters; }
首先来回顾一下整个算法流程:
1、随机选取聚类中心
2、对于每个实例,分配到离其最近的聚类中心
3、重新计算新的聚类中心
4、尝试对新的聚类中心进行分裂
5、回到2,若连续两个循环结果相同,则结束
可以看出,和传统的Kmeans相比,Xmeans最重要的改进在于可以自动决定聚类中心的数量,并进行“智能”的分裂。
最后总结一下第一篇文章开头(虽然现在已经乱码了)提出的问题:
1、如何计算各用例之间的“距离”
答:默认使用欧式距离,但可以定制传入距离函数,来计算任意两个用例的距离。
2、所谓的“迭代退出条件”是什么。
迭代有两层,分别为外层迭代和内层迭代,每一次外层迭代产生不同的聚类中心,每一次内层迭代将用例分配到各聚类中心。
外层迭代退出条件有三个:(1)达到最大迭代次数,(2)两次外层迭代产生聚类中心数量相等,即聚类中心没有分裂,(3)达到最大的聚类个数
内层迭代退出条件有二个:(1)两次内层迭代所有用例分配到的聚类中心一样(2)达到最大迭代次数
3、如何确定聚类中心
答:所有属性的算数平均值为聚类中心。
4、在实现过程中有没有一些用来提高效率的trick
使用了KDTree来寻找某个用例离得最近的中心。