一、聚类融合算法简介:
现有的聚类算法对被处理的数据集都有一个假设,例如K-Means算法不适合处理非球形分布的簇,DBSCAN算法不适合处理密度变化不大的簇等。在聚类分析之前,必须根据数据的特点选择合适的聚类算法。在现实中,簇的形状和规模是任意的,聚类问题中有没有类标号的指导,在进行聚类分析时选择合适的聚类算法是非常困难的。为了解决这一问题,近年来许多学者提出了一种新的聚类模式,聚类融合。聚类融合即时是集成学习在无监督学习领域的应用,它的基本思想是将同一问题的多个聚类结果进行融合成一个聚类的结果。
几种典型的聚类融合算法:
1.基于超图划分的聚类融合算法
(1)Cluster-based Similarity Partitioning Algorithm(GSPA)
(2)Hyper Graph-Partitioning Algorithm(HGPA)
(3)Meta-Clustering Algorithm(MCLA)
2.基于关联矩阵的聚类融合算法
Voting-K-Means算法。
3.基于投票策略的聚类融合算法
w-vote是一种典型的基于加权投票的聚类融合算法。
同时还有基于互信息的聚类融合算法和基于有限混合模型的聚类融合算法。
二、基于关联矩阵的聚类融合算法——Voting-K-Means算法
Voting-K-Means算法是一种基于关联矩阵的聚类融合算法,关联矩阵的每一行和每一列代表一个数据点,关联矩阵的元素表示数据集中数据点对共同出现在同一个簇中的概率。
算法过程:
1.在一个数据集上得到若干个聚类成员;
2.依次扫描这些聚类成员,如果数据点i和j在某个聚类成员中被划分到同一个簇中,那么就在关联矩阵对应的位置计数加1;关联矩阵中的元素值越大,说明该元素对应的两个数据点被划分到同一个簇中的概率越大;3.得到关联矩阵之后,Voting-K-Means算法依次检查关联矩阵中的每个元素,如果它的值大于算法预先设定的阀值,就把这个元素对应的两个数据点划分到同一个簇中。
Voting-K-Means算法的优缺点:
Voting-K-Means算法不需要设置任何参数,在聚类融合的过程中可以自动地的选择簇的个数 并且可以处理任意形状的簇。因为Voting-K-Means算法在聚类融合过程中是根据两个数据点共同出现在同一个簇中的可能性大小对它们进行划分的,所以只要两个数据点距离足够近,它们就会被划分到一个簇中。
Voting-K-Means算法的缺点是时间复杂度较高,它的时间复杂度是O(n^2);需要较多的聚类成员,如果聚类成员达不到一定规模,那么关联矩阵就不能准确反映出两个数据点出现在同一个簇的概率。
package clustering;
import java.io.FileWriter;
import weka.clusterers.ClusterEvaluation;
import weka.clusterers.SimpleKMeans;
import weka.core.DistanceFunction;
import weka.core.EuclideanDistance;
import weka.core.Instances;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.unsupervised.attribute.Remove;
public class Votingkmeans2 extends SimpleKMeans {
/** 生成的序列号 */
private static final long serialVersionUID = 1557181390469997876L;
/** 划分的簇数 */
private int m_NumClusters;
/** 每个划分的簇中的实例的数量 */
public int[] m_ClusterSizes;
/** 使用的距离函数,这里是欧几里德距离 */
protected DistanceFunction m_DistanceFunction = new EuclideanDistance();
/** 实例的簇号赋值 */
protected int[] m_Assignments;
/** 设定聚类成员融合阀值 */
private final static double THREASOD = 0.5;
/** 生成一个聚类器 */
public void buildClusterer(Instances data) throws Exception{
final int numinst = data.numInstances(); // 数据集的大小
double [][]association = new double[numinst][numinst]; // 定义并初始化一个关联矩阵
int numIteration = 40; // 设置生成的聚类成员数
final int k = (int)Math.sqrt(numinst); // 设置K-Means聚类算法参数——簇数
for(int i = 0; i < numIteration; i++) {
if(data.classIndex() == -1)
data.setClassIndex(data.numAttributes() - 1); // 索引是从0开始
String[] filteroption = new String[2];
filteroption[0] = "-R";
filteroption[1] = String.valueOf(data.classIndex() + 1);// 索引是从1开始
Remove remove = new Remove();
remove.setOptions(filteroption);
remove.setInputFormat(data);
/* 使用过滤器模式生成新的数据集;新数据集是去掉类标签之后的数据集 */
Instances newdata = weka.filters.Filter.useFilter(data, remove);
/* 生成一个K-Means聚类器 */
SimpleKMeans sm = new SimpleKMeans();
sm.setNumClusters(k);
sm.setPreserveInstancesOrder(true); // 保持数据集实例的原始顺序
sm.setSeed(i); // 通过设置不同的种子,设置不同的簇初始中心点,从而得到不同的聚类结果
sm.buildClusterer(newdata);
int[] assigm = sm.getAssignments(); // 得到数据集各个实例的赋值
/* 建立关联矩阵 */
for(int j = 0; j < numinst; j++) {
for(int m = j; m < numinst; m++) {
if(assigm[j] == assigm[m]) {
association[j][m] = association[j][m] + 1.0 / numIteration ;
}
}
}
}
System.out.println();
/* 将生成的关联矩阵写入.txt文件(注:生成的txt文本文件在e:/result.txt中) */
FileWriter fw = new FileWriter("e://result.txt");
for(int j = 0; j < numinst; j++) {
for(int m = j; m < numinst; m++) { //由于关联矩阵是对称的,为了改进算法的效率,只计算矩阵的上三角
String number = String.format("%8.2f", association[j][m]);
fw.write(number);
}
fw.write("\n");
}
/* 处理关联矩阵,分别考虑了两种情况 :1.关联矩阵中某个元素对应的两个数据点已经被划分到了不同的簇中
* 2.两个数据点中有一个或者两个都没有被划分到某个簇中。
*/
int[] flag = new int[numinst];
int[] flagk = new int[k];
int[] finallabel = new int[numinst];
for(int m = 0; m < numinst; m++) {
for(int n = m; n < numinst; n++) {
if(association[m][n] > THREASOD) {
if(flag[m] == 0 && flag[n] == 0) { // 两个数据点都没有被划分到某个簇中,
int i = 0; // 将他们划分到同一个簇中即可
while (i < k && flagk[i] == 1)
i = i + 1;
finallabel[m] = i;
finallabel[n] = i;
flag[m] = 1;
flag[n] = 1;
flagk[i] = 1;
}
else if (flag[m] == 0 && flag[n] == 1) { // 两个数据点中有一个没有被划分到某个簇中,
finallabel[m] = finallabel[n]; // 将他们划分到同一个簇中即可
flag[m] = 1;
}
else if (flag[m] == 1 && flag[n] == 0) {
finallabel[n] = finallabel[m];
flag[n] = 1;
}
else if (flag[m] == 1 && flag[n] == 1
&& finallabel[m] != finallabel[n]) { // 两个数据点已被划分到了不同的簇中,
flagk[finallabel[n]] = 0; // 将它们所在的簇合并
int temp = finallabel[n];
for(int i = 0; i < numinst; i++) {
if(finallabel[i] == temp)
finallabel[i] = finallabel[m];
}
}
}
}
}
m_Assignments = new int[numinst];
System.out.println("基于关联矩阵的聚类融合算法——Voting-K-Means算法的最终聚类结果");
for(int i = 0; i < numinst; i++) {
m_Assignments[i] = finallabel[i];
System.out.print(finallabel[i] + " ");
if((i+1) % 50 == 0)
System.out.println();
}
for(int i = 0; i < k; i++) {
if(flagk[i] == 1)
m_NumClusters++;
}
}
/**
* return a string describing this clusterer
*
* @return a description of the clusterer as a string
*/
public String toString() {
return "Voting-KMeans\n";
}
public static void main(String []args) {
try {
String filename="e://weka-data//iris.arff";
Instances data = DataSource.read(filename);
Votingkmeans2 vk = new Votingkmeans2();
vk.buildClusterer(data);
/* 要生成Voting-K-Means的聚类评估结果包括准确率等需要覆盖重写toString()方法;
* 因为没有覆盖重写,所以这里生产的评估结果没有具体内容。
*/
ClusterEvaluation eval = new ClusterEvaluation();
eval.setClusterer(vk);
eval.evaluateClusterer(new Instances(data));
System.out.println(eval.clusterResultsToString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
分析代码时注意:得到的类成员变量m_Assignments就是最终Voting-K-Means聚类结果;由于是采用了开源机器学习软件Weka中实现的SimpleKMeans聚类算法,初始时要指定簇的个数,这里是数据集大小开根号向下取整;指定的阀值为0.5,即当关联矩阵元素的值大于阀值时,才对该元素对应的两个数据点进行融合,划分到一个簇中,考虑两种情况,代码注释已有,这里不再详述。但聚类融合的实验结果并不理想,莺尾花数据集irsi.arff是数据挖掘实验中最常用的数据集,原数据集共有三个类;但本实验进行四十个聚类成员的融合,其最终聚类结果划分成两个簇;其原因可能有两个:一是算法本身的问题,需要使用其他更加优化的聚类融合算法;二是实现上的问题,主要就在聚类结果的融合上,需要进行一步对照关联矩阵进行逻辑上的分析,找出代码中的问题。关联矩阵文本文件 http://download.csdn.net/detail/lhkaikai/7294323