上一篇文章不知道为什么前面部分乱码了,我会在这篇文章后面写个总结,总结之前开头的部分。
首先处理两个问题,第一个是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来寻找某个用例离得最近的中心。