开始之前先介绍下什么是簇识别。
簇识别:
簇识别给出聚类结果的含义。假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底都是些什么。
k-均值是发现给定数据集的K个簇的算法。簇个数是用户给定的,每一个簇通过其质心,即簇中所有点的中心来描述。
k-means聚类算法的评价:
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
使用数据类型:数值型数据。
工作流程:
首先,随机确定K个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心。并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值,然后使用心的质心迭代聚类过程,直到收敛为止。
(这里的收敛指质心的不在改变,如果迭代后的质心位置与上一次相比如果超过了设定的差,那么就说明还未收敛,需要继续迭代)
伪代码:
1.创建k个点作为起始质心(经常是随机选择)
2.当任意一个点的簇分配结果发生改变时
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇中所有点的均值并将均值作为质心
3.使用新的质心来迭代2,直到收敛为止。
一般流程:
收集数据:使用任意方法
准备数据:需要数值型数据来计算距离,也可以将标称型数据映射为二值型数据再用于距离计算。
分析数据:使用任意方法
训练算法:不适用于无监督学习,即无监督学习没有训练过程。
测试算法:应用聚类算法、观察结果。可以使用量化的错误指标如
误差平方和来评价算法的结果
使用算法:可以用于所希望的任何应用。通常情况下,簇质心可以代表整个簇的数据来做出决定。
其他问题
离群点的处理
离群点可能过度影响簇的发现,导致簇的最终发布会与我们的预想有较大出入,所以提前发现并剔除离群点是有必要的。
利用方差来剔除离群点,结果显示效果非常好。下文java实现的算法并未剔除利群点,如果有需要,可以在自己的算法实现中加入这一步。
簇分裂和簇合并(代码下面会做具体的介绍)
使用较大的K,往往会使得聚类的结果看上去更加合理,但很多情况下,我们并不想增加簇的个数。
这时可以交替采用簇分裂和簇合并。这种方式可以避开局部极小,并且能够得到具有期望个数簇的结果。
代码:
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.Random;
import java.util.Scanner;
/**
* Created by wubo on 2016/10/27.
*/
public class Kmeans {
private int k;//簇的个数
private int m;// 迭代次数
private int dataSetLength;// 数据集元素个数,即数据集的长度
private ArrayList<float[]> dataSet;// 数据集链表
private ArrayList<float[]> center;// 中心链表
private ArrayList<ArrayList<float[]>> cluster; // 簇
private ArrayList<Float> jc;// 误差平方和,k越接近dataSetLength,误差越小
private Random random;
/**
* 设置需分组的原始数据集
*
* @param dataSet
*/
public void setDataSet(ArrayList<float[]> dataSet) {
this.dataSet = dataSet;
}
/**
* 获取结果分组
*
* @return 结果集
*/
public ArrayList<ArrayList<float[]>> getCluster() {
return cluster;
}
/**
* 构造函数,传入需要分成的簇数量
*
* @param k
* 簇数量,若k<=0时,设置为1,若k大于数据源的长度时,置为数据源的长度
*/
public Kmeans(int k) {
if (k <= 0) {
k = 1;
}
this.k = k;
}
/**
* 初始化
*/
private void init() {
m = 0;//初始化迭代次数
random = new Random();
if (dataSet == null || dataSet.size() == 0) {
initDataSet();
}
dataSetLength = dataSet.size();//数据集长度
if (k > dataSetLength) {
k = dataSetLength;
}