K-Means 是数字信号分析的入门级算法,其代码可读性极高,但是也有一两个小问题。
首先,起始点是随机选择,而且起始点的位置与最终的结果有极高的相关性,因此只能达到局部最优解。为了更加接近全局最优解,有人提出计算很多次,每次使用不同的随机起点,然后从中选择聚合度最高的解做为输出。这样做会导致两个问题,从理论上讲,局部最优解的多次重复和过滤只能产生次优解,没有办法达到最优解。
第二个问题就是在算法开始运行的时候就必须指定 K 值,我认为只能算特点,不能算弱点。当在高维空间运行的时候,我们很难事先通过 UI 获取图像,因此也就很难主观地确定 K 值。在这种场景里,肯定是一个很大的弱点。但在另一方面,有时我们本来就需要事先确定 K 值。例如,要从一系列的 T 恤的高宽尺寸中分离出 S、M 和 L 三种型的类。在此,确定 K 只能算是特点而不是弱点。
为此,我在分层算法的基础上,通过定义聚类的熵的方法,实现了一种无需指定 K 值的自动算法。
0. 将所有点的设为顶点(即 .isTop 属性设为 TRUE)。
1. 在当前的点阵计算所有点之间的距离,如果两点互为最近点,则生成一个文件夹将其放入。新文件夹设为顶点,同时将其包含的所有节点(包括文件夹和节点)设为非顶点。
只有顶点才计入当前点。
2 将同一个文件夹内的所有点(包括子文件夹包含的点)提取出来,计算其平均值,做为当前新文件夹的形心。
循环1和2,直到只有一个顶点时,即可退出。
这样就形成了一颗不完全二叉树,节点包括的内容有三种情况:节点与节点,节点与点,点与点。
在手工指定 K 的情况下,我们可以自上向下遍历,然后将当前结果集中的直径最大的子节点分割。直到当前分割数量 == K。
在自动算法中,分割方法与手动的情况相同,只需要定义一个收敛的开关即可:
设树的根为顶点C
运行:
{
分割:将顶点C分割成它对应的两个子节点。
遍历所有点,返回它与当前所有顶点的距离,如果距离它最近的顶点是它所在的集合的编号,则返回 TRUE。
否则:返回与该点距离最近的集合,以及当前集合,在其中选择外切圆的直径较大的一个,设为顶点C。
GOTO 分割;
}
当上一段代码运行结束时,与每一点距离最近的顶点均是它对应的群的形心。这就可以被认为是聚类的数量与聚集程度的平衡点。
K 值的变化导致的运算只局限于分割算法中,树是处于只读的状态。
无图无真相:
图1.A
图1.B
图1.C
图X.A 为源数据以及相应的二叉树。这是一颗包括了位置信息的二叉树,它的层次数据很难读取,只能从连线的长度来大致判断。
图X.2 为自动算法返回的结果。
图X.3 为手动指定 K 的算法返回的结果。
因为两种算法中,决定哪一个顶点被展开的标准稍有不同,因此两个输出也稍有不同,但均可以高效地去除孤点。
图2.A
图2.B
图2.C
图3.A
图3.B
图3.C
图4.A
图4.B
图4.C
仔细观察,发现自动算法中的切分标准比指定 K 的算法要合理一点,类之间的混淆情况大幅度降低。事实上,我们可能通过定交不同的优先切割标准来得到 K 值相同时的不同结果。但总的来说,区别都不是太大。
这个算法的问题是刚开始建二叉树的时候复杂度太高,随着树叶的建立,运算量越来越少。到了后期切分的时候,基本上就没什么运算了,切分完成后,可需要确定每个点对应的分类下标即可。