<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、反复2和3。直到达到迭代退出的条件。

</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 {

    // 先測一下这个data的属性能否处理。
getCapabilities().testWithFail(data);
 //这两个是最小聚类数量和最大聚类数量
    if (m_MinNumClusters > m_MaxNumClusters) {
throw new Exception("XMeans: min number of clusters "
+ "can't be greater than max number of clusters!");
} m_NumSplits = 0;
m_NumSplitsDone = 0;
m_NumSplitsStillDone = 0; // 替换掉MissingValue,假设是数值型,则替换为平均值。假设是枚举型,则替换为出现最多的那个值
    // 这里能够算预处理数据时的一个小技巧
m_ReplaceMissingFilter = new ReplaceMissingValues();
m_ReplaceMissingFilter.setInputFormat(data);
m_Instances = Filter.useFilter(data, m_ReplaceMissingFilter); // 设定一个随机种子
Random random0 = new Random(m_Seed); // 聚类数量从最小聚类数量開始。这个值默认是2
m_NumClusters = m_MinNumClusters; //这里是默认的算距离的方法,能够传入自己定义的函数。默认使用欧式距离。
if (m_DistanceF == null) {
m_DistanceF = new EuclideanDistance();
}
//这两个函数都没实现,不知道放这里的用意是什么
m_DistanceF.setInstances(m_Instances);
checkInstances();
 
    //測试相关,临时忽略
if (m_DebugVectorsFile.exists() && m_DebugVectorsFile.isFile())
initDebugVectorsInput(); // allInstList存放全部Instances的下标
int[] allInstList = new int[m_Instances.numInstances()];
for (int i = 0; i < m_Instances.numInstances(); i++) {
allInstList[i] = i;
} // 仅仅是拷贝一个表头
m_Model = new Instances(m_Instances, 0); // 确定聚类中心
if (m_CenterInput != null) {
//聚类中心能够从文件读取。注意m_ClusterCenters本身是一个Instances对象。但这里似乎没有推断这个m_ClusterCenters和m_Model(也就是传入的训练集)是否同构
m_ClusterCenters = new Instances(m_CenterInput);
m_NumClusters = m_ClusterCenters.numInstances();//假设传入了聚类中心文件,那么就更新一下聚类中心数量
}
else
// 随机选取聚类中心。有放回的随机抽样。
m_ClusterCenters = makeCentersRandomly(random0,
m_Instances, m_NumClusters);
PFD(D_FOLLOWSPLIT, "\n*** Starting centers ");//这个是debug函数,忽略
for (int k = 0; k < m_ClusterCenters.numInstances(); k++) {
PFD(D_FOLLOWSPLIT, "Center " + k + ": " + m_ClusterCenters.instance(k));
} PrCentersFD(D_PRINTCENTERS);//打日志的函数,忽略 boolean finished = false;
Instances children; // 是否使用KDTree,简单说一下KDTree,假设给定一堆点X,又给定一个点A。A离X中近期的那个点,传统的做法遍历整个X集合。找出近期的,时间复杂度为O(n),构建KDTree之后(本质是在空间上建立索引),时间复杂度能够将为O(logn)
if (m_UseKDTree)
m_KDTree.setInstances(m_Instances); // 迭代次数
m_IterationCount = 0; /**
* 训练过程由两次迭代组成。外层迭代进行聚类中心的分裂。内层迭代对每一个实例进行划分并算出新的聚类中心,外层迭代的退出条件有两个
* 1. finished为true(finished为true的条件后面会说到)
* 2. 达到最大迭代次数
     * 注意,m_ClusterCenters有可能已经比m_MaxClusters大了,由于可能是从文件读入的聚类中心,这种情况下迭代也会进行一次,由于finish是在循环结束时推断的
*/
while (!finished &&
!stopIteration(m_IterationCount, m_MaxIterations)) {
PFD(D_FOLLOWSPLIT, "\nBeginning of main loop - centers:");
PrCentersFD(D_FOLLOWSPLIT);
PFD(D_ITERCOUNT, "\n*** 1. Improve-Params " + m_IterationCount +
". time");
m_IterationCount++; // converged代表两次内层迭代。所产生的聚类结果是否一样
boolean converged = false; // 这是一个一维数组。记录每一个实例被分到了哪个聚类中心
m_ClusterAssignments = initAssignments(m_Instances.numInstances());
// 这个二维数组存放每一个聚类中心都有那些实例,非常奇怪的是weka全都是用数组。而没用list这种数据结构。预计是从效率方面进行考虑。
int[][] instOfCent = new int[m_ClusterCenters.numInstances()][]; // 内层迭代的计数器
int kMeansIteration = 0; // 打日志忽略
PFD(D_FOLLOWSPLIT, "\nConverge in K-Means:");
      //进行内层迭代,内层迭代退出的条件也有两个。第一个是迭代次数达到最大。第二个是两次循环的聚类结果一样
while (!converged &&
!stopKMeansIteration(kMeansIteration, m_MaxKMeans)) { kMeansIteration++;
converged = true; // 把实例分给对应的聚类中心,这里对converged进行了赋值。但后面有覆盖了所以这个赋值没有意义。这个函数比較麻烦但没有什么算法思想,就不展开分析了,KDTree结构也许会在后面的博客去分析事实上现。 converged = assignToCenters(m_UseKDTree ? m_KDTree : null,
m_ClusterCenters,
instOfCent,
allInstList,
m_ClusterAssignments,
kMeansIteration); PFD(D_FOLLOWSPLIT, "\nMain loop - Assign - centers:");//打日志忽略
PrCentersFD(D_FOLLOWSPLIT);//打日志忽略
// 又一次算聚类中心,假设两次聚类中心一样。就返回true,两次聚类中心一样。和两次的聚类结果一样是全然等价的。聚类中心的计算方法是算数平均值。 converged = recomputeCenters(m_ClusterCenters, // 聚类中心
instOfCent, // 这些聚类中心的实例
m_Model); // 表头
PFD(D_FOLLOWSPLIT, "\nMain loop - Recompute - centers:");
PrCentersFD(D_FOLLOWSPLIT);
}
PFD(D_FOLLOWSPLIT, "");
PFD(D_FOLLOWSPLIT, "End of Part: 1. Improve-Params - conventional K-means"); //计算每一个聚类中心的偏差。m_Mle是个数组,存储各聚类中实例到聚类中心的距离之和
m_Mle = distortion(instOfCent, m_ClusterCenters);
      //bic是“贝叶斯失真规则”,越小说明模型对数据拟合越好,百度百科连接http://baike.baidu.com/view/1425589.htm?fr=aladdin#2。反正越小越好
m_Bic = calculateBIC(instOfCent, m_ClusterCenters, m_Mle);
PFD(D_FOLLOWSPLIT, "m_Bic " + m_Bic); int currNumCent = m_ClusterCenters.numInstances();
      //新的聚类中心,能够遇见到,每一个原聚类中心都要进行分裂,由于容量是currNumCent*2
Instances splitCenters = new Instances(m_ClusterCenters,
currNumCent * 2); //
double[] pbic = new double [currNumCent];
double[] cbic = new double [currNumCent]; // 对中心进行分裂
for (int i = 0; i < currNumCent
// 原备注说加了下一行能够提快速度。我也不是非常懂
// && currNumCent + numSplits <= m_MaxNumClusters
;
i++) { PFD(D_FOLLOWSPLIT, "\nsplit center " + i +
" " + m_ClusterCenters.instance(i));
Instance currCenter = m_ClusterCenters.instance(i);
int[] currInstList = instOfCent[i];
int currNumInst = instOfCent[i].length;//代表这个聚类中有几个实例 // 假设眼下的实例小于等于2。就直接复制自己一份,每一个聚类中心必须分裂。当然假设两个instance,每一个点都当做聚类中心也能够。但直接dummy自己也不影响最后结果。 if (currNumInst <= 2) {
pbic[i] = Double.MAX_VALUE;
cbic[i] = 0.0;
// add center itself as dummy
splitCenters.add(currCenter);
splitCenters.add(currCenter);
continue;
} //m_Mle[i]代表聚类i上的距离误差和。除以分类数得到平均误差,但这个误差并非方差。这个变量的名字有点误导性。。。 。
double variance = m_Mle[i] / (double)currNumInst;
        //通过某种方式分裂成两个中心。这个分裂过程还是挺有意思的。主流程之后会具体分析
children = splitCenter(random0, currCenter, variance, m_Model); // 准备用这个聚类上的全部数据,依据这两个新的聚类中心,再做一次聚类
int[] oneCentAssignments = initAssignments(currNumInst);
int[][] instOfChCent = new int [2][]; // todo maybe split didn't work // 标志记录两次迭代是否一样。下面循环逻辑和之前的聚类过程基本一样
converged = false;
int kMeansForChildrenIteration = 0;
PFD(D_FOLLOWSPLIT, "\nConverge, K-Means for children: " + i);
while (!converged &&
!stopKMeansIteration(kMeansForChildrenIteration,
m_MaxKMeansForChildren)) {
kMeansForChildrenIteration++; converged =
assignToCenters(children, instOfChCent,
currInstList, oneCentAssignments); if (!converged) {
recomputeCentersFast(children, instOfChCent, m_Model);//这个和recomputeCenters唯一的差别就是不算converged
}
} splitCenters.add(children.instance(0));
splitCenters.add(children.instance(1)); PFD(D_FOLLOWSPLIT, "\nconverged cildren ");
PFD(D_FOLLOWSPLIT, " " + children.instance(0));
PFD(D_FOLLOWSPLIT, " " + children.instance(1)); // 分别计算父聚类中心和子聚类中心(2个)的BIC
pbic[i] = calculateBIC(currInstList, currCenter, m_Mle[i], m_Model);
double[] chMLE = distortion(instOfChCent, children);
cbic[i] = calculateBIC(instOfChCent, children, chMLE); } //对于每一个聚类中心都做上述操作,循环结束 // 这个函数依据之前算出的BIC,计算出新的聚类中心。具体怎么选的后面会再跟进去具体说。
Instances newClusterCenters = null;
newClusterCenters = newCentersAfterSplit(pbic, cbic, m_CutOffFactor,
splitCenters); int newNumClusters = newClusterCenters.numInstances();
if (newNumClusters != m_NumClusters) {
//假设新的聚类中心数量和老的不相等,进入这个if。
PFD(D_FOLLOWSPLIT, "Compare with non-split"); int[] newClusterAssignments =
initAssignments(m_Instances.numInstances()); int[][] newInstOfCent = new int[newClusterCenters.numInstances()][];
//把全部instance放到新的聚类中心上。
converged = assignToCenters(m_UseKDTree ? m_KDTree : null,
newClusterCenters,
newInstOfCent,
allInstList,
newClusterAssignments,
m_IterationCount); double[] newMle = distortion(newInstOfCent, newClusterCenters);
double newBic = calculateBIC(newInstOfCent, newClusterCenters, newMle);//算一算新的bic
PFD(D_FOLLOWSPLIT, "newBic " + newBic);
if (newBic > m_Bic) {//假设新的bic比旧的大,说明新的聚类效果好,则用新的替换老的
PFD(D_FOLLOWSPLIT, "*** decide for new clusters");
m_Bic = newBic;
m_ClusterCenters = newClusterCenters;
m_ClusterAssignments = newClusterAssignments;
} else {
PFD(D_FOLLOWSPLIT, "*** keep old clusters");
}
} newNumClusters = m_ClusterCenters.numInstances();
if ((newNumClusters >= m_MaxNumClusters)
|| (newNumClusters == m_NumClusters)) {
finished = true;//置finish条件,当达到最大分类数量,或者没有不论什么分裂的时候。就置为true
}
m_NumClusters = newNumClusters;
} if (m_ClusterCenters.numInstances() > 0 && m_CenterOutput != null) {
m_CenterOutput.println(m_ClusterCenters.toString());//输出模型用的,忽略
m_CenterOutput.close();
m_CenterOutput = null;
}
}

(未完待续)

Weka算法Clusterers-Xmeans源代码分析(一)的更多相关文章

  1. STL源代码分析——STL算法remove删除算法

    前言 因为在前文的<STL算法剖析>中,源代码剖析许多.不方便学习,也不方便以后复习,这里把这些算法进行归类.对他们单独的源代码剖析进行解说.本文介绍的STL算法中的remove删除算法. ...

  2. STL源代码分析——STL算法merge合并算法

    前言 因为在前文的<STL算法剖析>中.源代码剖析许多.不方便学习.也不方便以后复习,这里把这些算法进行归类.对他们单独的源代码剖析进行解说.本文介绍的STL算法中的merge合并算法. ...

  3. STL源代码分析——STL算法sort排序算法

    前言 因为在前文的<STL算法剖析>中,源代码剖析许多,不方便学习,也不方便以后复习.这里把这些算法进行归类,对他们单独的源代码剖析进行解说.本文介绍的STL算法中的sort排序算法,SG ...

  4. 变动性算法源代码分析与使用示例(copy_backward、 transform、 replace_copy_if 等)

    首先回顾前面的文章,我们把for_each 归类为非变动性算法,实际上它也可以算是变动性算法,取决于传入的第三个参数,即函数 指针.如果在函数内对容器元素做了修改,那么就属于变动性算法. 变动性算法源 ...

  5. OpenStack_Swift源代码分析——Ring的rebalance算法源代码具体分析

    1 Command类中的rebalnace方法 在上篇文章中解说了,创建Ring已经为Ring加入设备.在加入设备后须要对Ring进行平衡,平衡 swift-ring-builder object.b ...

  6. K-近邻算法的Python实现 : 源代码分析

    网上介绍K-近邻算法的样例非常多.其Python实现版本号基本都是来自于机器学习的入门书籍<机器学习实战>,尽管K-近邻算法本身非常easy,但非常多刚開始学习的人对其Python版本号的 ...

  7. Openck_Swift源代码分析——添加、删除设备时算法详细的实现过程

    1 初始加入设备后.上传Object的详细流程  前几篇博客中,我们讲到环的基本原理即详细的实现过程,加入我们在初始创建Ring是执行例如以下几条命令: •swift-ring-builder obj ...

  8. redis 源代码分析(一) 内存管理

    一,redis内存管理介绍 redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中 ...

  9. Raid1源代码分析--开篇总述

    前段时间由于一些事情耽搁了,最近将raid1方面的各流程整理了一遍.网上和书上,能找到关于MD下的raid1的文档资料比较少.决定开始写一个系列的关于raid1的博客,之前写过的一篇读流程也会在之后加 ...

随机推荐

  1. loj#2076. 「JSOI2016」炸弹攻击 模拟退火

    目录 题目链接 题解 代码 题目链接 loj#2076. 「JSOI2016」炸弹攻击 题解 模拟退火 退火时,由于答案比较小,但是温度比较高 所以在算exp时最好把相差的点数乘以一个常数让选取更差的 ...

  2. prufer序列计数的一些结论

    \(prufer\)序列和完全图的生成树一一对应(考虑构造) 完全图的生成树个数为\(n^{n - 2}\) 满足第\(i\)个点的度数为\(d_i\)的生成树为\(\frac{n!}{\prod ( ...

  3. Prism中命令可用性无法自动刷新

    http://stackoverflow.com/questions/2444927/wpf-prism-canexecute-method-not-being-called It is most l ...

  4. 移动端html页面优化

    对于访问量大的网站来说,前端的优化是必须的,即使是优化1KB的大小对其影响也很大,下面来看看来自ISUX的米随随讲讲移动手机平台的HTML5前端优化,或许对你有帮助和启发. 概述 1. PC优化手段在 ...

  5. wampServer 安装 Redis 扩展

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统. Redis是一个开源的使用ANSI C语言编写.遵守B ...

  6. QThreadPool&QRunnable&类函数的使用

    QThreadPool+QRunnable线程池与QThread线程两种方式使用的场景不同,QThreadPool+QRunnable线程池主要用于那种不需要一直运行的任务,而QThread主要用于长 ...

  7. php_ssh2操作linux

    <?php /** * Created by PhpStorm. * User: Administrator * Date: 2018/9/15 * Time: 14:11 */ header( ...

  8. 伪造请求IP

    //随机IP function Rand_IP(){ $ip2id= round(rand(600000, 2550000) / 10000); //第一种方法,直接生成 $ip3id= round( ...

  9. 修复bug及修复过程

    1.本地存储数据显示不出问题 问题细节: 本地使用如下语句存储成绩,"ScoreDisplay"为键,值为this.score.toString(),但是在cocos creato ...

  10. 【docker】docker部署spring boot服务,但是docker logs查看容器输出控制台日志,没有日志打印,日志未打印,docker logs不打印容器日志

    如题: docker部署spring boot服务,但是docker logs查看容器输出控制台日志,没有日志打印,日志未打印,docker logs不打印容器日志 场景再现: docker部署并启动 ...