k-means聚类算法
来源:互联网 发布:sql重庆培训 编辑:程序博客网 时间:2024/05/17 03:08
开始之前先介绍下什么是簇识别。
簇识别:簇识别给出聚类结果的含义。假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底都是些什么。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;
}
center = initCenters();//初始化质心
System.out.print("error");
cluster = initCluster();//初始化簇集合
jc = new ArrayList<Float>();//误差平方和
}
/**
* 如果调用者未初始化数据集,则采用内部测试数据集
*/
private void initDataSet() {
dataSet = new ArrayList<float[]>();
// 其中{6,3}是一样的,所以长度为15的数据集分成14簇和15簇的误差都为0
float[][] dataSetArray = new float[][] { { 8, 2 }, { 3, 4 }, { 2, 5 },
{ 4, 2 }, { 7, 3 }, { 6, 2 }, { 4, 7 }, { 6, 3 }, { 5, 3 },
{ 6, 3 }, { 6, 9 }, { 1, 6 }, { 3, 9 }, { 4, 1 }, { 8, 6 } };
for (int i = 0; i < dataSetArray.length; i++) {
dataSet.add(dataSetArray[i]);
System.out.print("["+dataSetArray[i][0]+","+dataSetArray[i][1]+"]");
}
}
/**
* 初始化中心数据链表,分成多少簇就有多少个中心点
*
* @return 中心点集
*/
private ArrayList<float[]> initCenters() {
ArrayList<float[]> center = new ArrayList<float[]>();
int[] randoms = new int[k];//创建存放质心的数组
boolean flag;
int temp = random.nextInt(dataSetLength);//随机一个质点
randoms[0] = temp;//质点加入数组中
for (int i = 1; i < k; i++) {
flag=false;
while(!flag){
temp=random.nextInt(dataSetLength);
int j=0;
for (j=0;j<i;j++){
if (randoms[j]==temp){
break;
}
}
if(j==i){
flag=true;
}
}
randoms[i] = temp;
}
//测试随机数生成情况
for(int i=0;i<k;i++)
{
System.out.println("test1:randoms["+i+"]="+randoms[i]);
}
for (int i = 0; i < k; i++) {
center.add(dataSet.get(randoms[i]));// 生成初始化中心链表
}
return center;
}
/**
* 初始化簇集合
*
* @return 一个分为k个簇的空数据的簇集合
*/
private ArrayList<ArrayList<float[]>> initCluster() {
ArrayList<ArrayList<float[]>> cluster = new ArrayList<ArrayList<float[]>>();
for (int i = 0; i < k; i++) {
cluster.add(new ArrayList<float[]>());
}
return cluster;
}
/**
* 计算两个点之间的距离
*
* @param element
* 点1
* @param center
* 点2
* @return 距离
*/
private float distance(float[] element, float[] center) {
float distance = 0.0f;
float x = element[0] - center[0];
float y = element[1] - center[1];
float z = x * x + y * y;
distance = (float) Math.sqrt(z);
return distance;
}
/**
* 获取距离集合中最小距离的位置
*
* @param distance
* 距离数组
* @return 最小距离在距离数组中的位置
*/
private int minDistance(float[] distance) {
float minDistance = distance[0];
int minLocation = 0;
for (int i = 1; i < distance.length; i++) {
if (distance[i] < minDistance) {
minDistance = distance[i];
minLocation = i;
} else if (distance[i] == minDistance) // 如果相等,随机返回一个位置
{
if (random.nextInt(10) < 5) {
minLocation = i;
}
}
}
return minLocation;
}
/**
* 核心,将当前元素放到最小距离中心相关的簇中
*/
private void clusterSet() {
float[] distance = new float[k];
for (int i = 0; i < dataSetLength; i++) {
for (int j = 0; j < k; j++) {
distance[j] = distance(dataSet.get(i), center.get(j));//计算出当前点到每个簇质心的距离
}
int minLocation = minDistance(distance);//取得的最小距离的簇在数组中的编号
cluster.get(minLocation).add(dataSet.get(i));// 核心,将当前元素放到最小距离中心相关的簇中
}
}
/**
* 求两点误差平方的方法
*
* @param element
* 点1
* @param center
* 点2
* @return 误差平方
*/
private float errorSquare(float[] element, float[] center) {
float x = element[0] - center[0];
float y = element[1] - center[1];
float errSquare = x * x + y * y;
return errSquare;
}
/**
* 计算误差平方和准则函数方法,所有点到质心的误差平方和之和
*/
private void countRule() {
float jcF = 0;
for (int i = 0; i < cluster.size(); i++) {
for (int j = 0; j < cluster.get(i).size(); j++) {
jcF += errorSquare(cluster.get(i).get(j), center.get(i));
}
}
jc.add(jcF);
}
/**
* 设置新的簇中心方法,平面的质心的求解算法,即求x,y的平均值
*/
private void setNewCenter() {
for (int i = 0; i < k; i++) {
int n = cluster.get(i).size();
if (n != 0) {
float[] newCenter = { 0, 0 };
for (int j = 0; j < n; j++) {
newCenter[0] += cluster.get(i).get(j)[0];
newCenter[1] += cluster.get(i).get(j)[1];
}
// 设置一个平均值
newCenter[0] = newCenter[0] / n;
newCenter[1] = newCenter[1] / n;
center.set(i, newCenter);
}
}
}
/**
* 打印数据,测试用
*
* @param dataArray
* 数据集
* @param dataArrayName
* 数据集名称
*/
public void printDataArray(ArrayList<float[]> dataArray,
String dataArrayName) {
for (int i = 0; i < dataArray.size(); i++) {
System.out.println("print:" + dataArrayName + "[" + i + "]={"
+ dataArray.get(i)[0] + "," + dataArray.get(i)[1] + "}");
}
System.out.println("===================================");
}
/**
* Kmeans算法核心过程方法
*/
private void kmeans() {
init();
System.out.print("finish init");
//printDataArray(dataSet,"initDataSet");
// printDataArray(center,"initCenter");
// 循环分组,直到误差不变为止
while (true) {
clusterSet();
countRule();//计算误差平方和
// 误差不变了,分组完成
if (m != 0) {//m为迭代次数
if (Math.abs(jc.get(m) - jc.get(m - 1)) <1E-10) {
System.out.print(jc.get(m) - jc.get(m - 1));
System.out.print("迭代完成\n");
break;
}
}
printDataArray(center,"newCenter");
setNewCenter();
m++;
cluster.clear();
cluster = initCluster();
}
printDataArray(center,"newCenter");
System.out.println("note:the times of repeat:m="+m);//输出迭代次数
}
/**
* 执行算法
*/
public void execute() {
long startTime = System.currentTimeMillis();
System.out.println("kmeans begins");
kmeans();
long endTime = System.currentTimeMillis();
System.out.println("kmeans running time=" + (endTime - startTime)
+ "ms");
System.out.println("kmeans ends");
System.out.println();
}
}
import sun.security.jgss.krb5.Krb5NameElement;
/**
* Created by wubo on 2016/10/28.
*/
public class KmeansTest {
public static void main(String[] args) {
Kmeans kmeans =new Kmeans(2);
kmeans.execute();
}
}
使用后处理来提高聚类性能:
k-means算法中簇的数目k是一个用户预先定义的参数,那么用户如何知道K的选择是否正确?如何才能知道簇的选择比较好?
上文中未做介绍,下文来细说下。
K-均值算法收敛但聚类效果较差的原因是,k-均值算法收敛到了局部最小值而非全局最小值。(局部最小值指结果还可以但并非最好结果,全局最小值是最好的结果)。
一种用于度量聚类效果的指标是SSE(Sum of Squard Error,误差平方和)。SSE值越小表示数据点越接近与它们的质心,聚类效果也越好。因为对误差去了平方,因此更加重视那些远离中心的点。一种肯定可以降低SSE值的方法是增加簇的个数,但是这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
另一种方法就是将具有最大的SSE值的簇划分为两个簇。具体实现时可以将最大簇包含的点过滤出来,并在这些点上运行K-均值算法,其中的k设为2。同时为了保证簇总数不变,可以将某两个簇合并。
那么如何选择要合并的两个簇,有两种可以量化的办法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。第一种思路是计算所有质心之间的距离,第二种需要合并两个簇然后计算总SSE值,必需在所有可能的两个簇上重复上述处理过程,直到找出合并最佳的两个簇为止。
为此,在k-均值算法的基础上有实现了该技术的 二分 k-均值算法
0 0
- k-means聚类算法
- k-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- K-MEANS聚类算法
- k-means聚类算法
- K-means聚类算法
- 聚类算法 K-means
- K-means聚类算法
- K-means聚类算法
- K-means聚类算法
- 聚类算法:K-means
- K-means聚类算法
- K-means聚类算法
- Java地位无可动摇的12个原因
- 简述JavaScript对象、数组对象与类数组对象
- rxJava&rxAndroid-进阶篇
- 最近在做VTK体绘制的学习,中途花费了大量的时间,进展十分缓慢,最终成功,将个中细节公布出来,以飨后人
- 名人如何谈技术:关于技术的20个名言
- k-means聚类算法
- redis 原理
- 微信模块介绍
- c++递归创建二叉树
- 全方位比较PHP的Node.js的优缺点
- leetCode练习(101)
- 洛谷P14341 滑雪
- 安卓开发——根据QQ号跳转到QQ聊天界面
- 分数化小数decimal