Kmeans算法的K值和聚类中心的确定

0 K-means算法简介

K-means是最为经典的基于划分的聚类方法,是十大经典数据挖掘算法之一。

K-means算法的基本思想是:以空间中k个点为中心进行聚类,对最靠近他们的对象归类。通过迭代的方法,逐次更新各聚类中心的值,直至得到最好的聚类结果。

算法过程如下:

1)从N个文档随机选取K个文档作为质心

2)对剩余的每个文档测量其到每个质心的距离,并把它归到最近的质心的类

3)重新计算已经得到的各个类的质心

4)迭代2~3步直至新的质心与原质心相等或小于指定阈值,算法结束

参考Java代码

public static Map<Integer, List<Integer>> kMeans(List<List<Double>> dataSet, int k,
            List<List<Double>> centerPointCp) {
        /**
         * 拷贝中心点,防止修改原始数据
         */
        List<List<Double>> centerPoint = new ArrayList<List<Double>>();
        for(int i = 0; i < centerPointCp.size(); i++){
            List<Double> tmpCp = new ArrayList<Double>();
            for(int j = 0; j < centerPointCp.get(i).size(); j++){
                tmpCp.add(centerPointCp.get(i).get(j));
            }
            centerPoint.add(tmpCp);
        }

        int n = dataSet.size();
        int dim = dataSet.get(0).size();
        double[][] clusterAssment = new double[n][2];
        Boolean clusterChanged = true;
        while (clusterChanged) {
            clusterChanged = false;
            for (int i = 0; i < n; i++) {
                double minDist = Double.POSITIVE_INFINITY;
                int minIndex = -1;
                for (int j = 0; j < k; j++) {
                    double distIC = PreData.distance(dataSet.get(i),
                            centerPoint.get(j));
                    if (distIC < minDist) {
                        minDist = distIC;
                        minIndex = j;
                    }
                }
                if (clusterAssment[i][0] != minIndex) {
                    clusterChanged = true;
                }
                clusterAssment[i][0] = minIndex;
                clusterAssment[i][1] = Math.pow(minDist, 2);
            }
            for (int i = 0; i < k; i++) {
                double[] tmp = new double[dim];
                int cnt = 0;
                for (int j = 0; j < n; j++) {
                    if (i == clusterAssment[j][0]) {
                        for (int m = 0; m < dim; m++) {
                            tmp[m] += dataSet.get(j).get(m);
                        }
                        cnt += 1;
                    }
                }
                if (cnt != 0) {
                    for (int m = 0; m < dim; m++) {
                        centerPoint.get(i).set(m, tmp[m] / cnt);

                    }
                }
            }
        }
        //List<List<Double>>
        Map<Integer, List<Integer>> ret = new TreeMap<Integer, List<Integer>>();
        for(int i = 0; i < n; i++){
            for(int j = 0; j < k; j++){
                if(clusterAssment[i][0] == j){
                    if(ret.containsKey(j)){
                        ret.get(j).add(i);
                    }else{
                        List<Integer> tmp = new ArrayList<Integer>();
                        tmp.add(i);
                        ret.put(j, tmp);
                    }
                    break;
                }
            }
        }

        return ret;
    }

缺点:

1 k-means算法容易收敛于局部最小值,基于此可以用二分K-均值(bisecting K-means)的算法。

2 k-means算法的聚类结果对K值和初始聚类中心敏感。

本文给出一种确定K值和初始聚类中心的算法,可以保证k-means收敛于一个较好的结果。

1 K值怎么确定?

Canopy算法计算聚类的簇数

  • 将数据集向量化得到一个list后放入内存,选择两个距离阈值:T1和T2,其中T1 > T2,对应上图,实线圈为T1,虚线圈为T2,T1和T2的值可以用交叉校验来确定;
  • 从list中任取一点P,用低计算成本方法快速计算点P与所有Canopy之间的距离(如果当前不存在Canopy,则把点P作为一个Canopy),如果点P与某个Canopy距离在T1以内,则将点P加入到这个Canopy;
  • 如果点P曾经与某个Canopy的距离在T2以内,则需要把点P从list中删除,这一步是认为点P此时与这个Canopy已经够近了,因此它不可以再做其它Canopy的中心了;
  • 重复步骤2、3,直到list为空结束。

java代码

public static int canpoy(List<List<Double>> dataSet, double T1, double T2) {
        List<List<List<Double>>> canP = new ArrayList<List<List<Double>>>();
        Random rand = new Random();

        while (dataSet.size() > 0) {

            int randNum = rand.nextInt(dataSet.size());
            List<List<Double>> tmpCanp = new ArrayList<List<Double>>();
            tmpCanp.add(dataSet.get(randNum));
            canP.add(tmpCanp);
            dataSet.remove(randNum);
            for (int i = 0; i < dataSet.size();) {
                Boolean flag = false;
                for (int j = 0; j < canP.size(); j++) {
                    if (distance(dataSet.get(i), canP.get(j).get(0)) < T1) {  //遍历canp, 小于T1,加入canp,可以重复加入
                        canP.get(j).add(dataSet.get(i));
                        if (distance(dataSet.get(i), canP.get(j).get(0)) < T2) { //如果小于T2, 则移除该点
                            flag = true;
                        }
                    }
                }
                if (flag) {
                    dataSet.remove(i); //
                } else {
                    i++; //注意list的remove的特点
                }
            }
        }
        return canP.size();
    }

若只考虑计算K值,可省略上面T1,改变后的代码如下

public static int canpoy(List<List<Double>> dataSet, double T2) {
        List<List<List<Double>>> canP = new ArrayList<List<List<Double>>>();
        Random rand = new Random();

        while (dataSet.size() > 0) {

            int randNum = rand.nextInt(dataSet.size());
            List<List<Double>> tmpCanp = new ArrayList<List<Double>>();
            tmpCanp.add(dataSet.get(randNum));
            canP.add(tmpCanp);
            dataSet.remove(randNum);
            for (int i = 0; i < dataSet.size();) {
                Boolean flag = false;
                for (int j = 0; j < canP.size(); j++) {
                    if (PreData.distance(dataSet.get(i), canP.get(j).get(0)) < T2) {
                        flag = true;
                    }
                }
                if (flag) {
                    dataSet.remove(i);
                } else {
                    i++;
                }
            }
        }
        return canP.size();
    }

上面初始canpoy的选择依赖于随机数,结果也会变化。为了得到稳定的K值,可以多做几次测试,取出现次数最多的值。

阈值T2采用所有数的距离平均值代替。测试次数取30-100次即可。代码如下

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;

public class ClusterNum {

    /**
     * 计算阈值T2,用来计算聚类个数
     *
     * @param testData
     * @return
     */
    public static double averageT(List<List<Double>> testData) {
        double ret = 0;
        int cnt = 0;
        for (int i = 0; i < testData.size(); i++) {
            for (int j = i + 1; j < testData.size(); j++) {
                ret += PreData.distance(testData.get(i), testData.get(j));
                cnt++;
            }
        }
        return ret / cnt;
    }

    /**
     * canpoy算法寻找聚类簇的数量
     *
     * @param dataSet
     * @param T1
     * @param T2
     * @return
     */
    public static int canpoy(List<List<Double>> dataSet, double T2) {
        List<List<List<Double>>> canP = new ArrayList<List<List<Double>>>();
        Random rand = new Random();

        while (dataSet.size() > 0) {

            int randNum = rand.nextInt(dataSet.size());
            List<List<Double>> tmpCanp = new ArrayList<List<Double>>();
            tmpCanp.add(dataSet.get(randNum));
            canP.add(tmpCanp);
            dataSet.remove(randNum);
            for (int i = 0; i < dataSet.size();) {
                Boolean flag = false;
                for (int j = 0; j < canP.size(); j++) {
                    if (PreData.distance(dataSet.get(i), canP.get(j).get(0)) < T2) {
                        flag = true;
                    }
                }
                if (flag) {
                    dataSet.remove(i);
                } else {
                    i++;
                }
            }
        }
        return canP.size();
    }
    /**
     * 得到聚类个数
     * @param data
     * @param testNum  检验次数,建议设置范围30-100
     * @return
     */
    public static int getClusterNum(List<List<Double>> data, int testNum) {
        int i = 0;
        Map<Integer, Integer> bop = new TreeMap<Integer, Integer>();
        while (i < testNum) {
            List<List<Double>> dataCopy = new ArrayList<List<Double>>();
            dataCopy.addAll(data);

            double T2 = averageT(dataCopy);
            int k = canpoy(dataCopy, T2);

            if (bop.containsKey(k)) {
                bop.put(k, bop.get(k) + 1);
            } else {
                bop.put(k, 1);
            }
            i++;
        }
        int tmp = 0, index = 0;
        for (Integer key : bop.keySet()) {
            if (bop.get(key) > tmp) {
                tmp = bop.get(key);
                index = key;
            }
        }
        return index;
    }

}

2 初始中心怎么确定?

比较DBSCAN算法

最大最小距离法与随机初始化聚类中心相比, 降低了对初始聚类中心的敏感性, 在收敛速度 、准确率方面都有了较大的
进步。但由于它选取聚类中心遵从最小距离的思想, 可能造成初始聚类中心选取过于稠密, 出现聚类冲突现象, 使得原本属
于同一个簇的对象被分到了两个不同的簇中, 从而降低了聚类结果的质量。为了克服初始聚类中心选取过于稠密的现象, 本
文提出了最大距离积法, 尽可能地稀疏初始聚类中心的分布。
其基本思想如下: 

  

                                                

参考java代码如下

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ClusterCenter {

    /**
     * 计算密度参数MinPts
     *
     * @param beta
     * @param sampleSize
     * @return
     */

    public static int getMinPts(double beta, int sampleSize) {

        int ret = (int) (beta * sampleSize / Math.sqrt(sampleSize));
        return ret;
    }

    /**
     * 计算得到半径
     *
     * @param data
     * @param theta
     * @return
     */

    public static double getRadius(List<List<Double>> data, double theta) {
        int n = data.size();
        double disSum = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                disSum += PreData.distance(data.get(i), data.get(j));
            }
        }
        return theta * disSum / Math.pow(n, 2);
    }

    /**
     * 得到高密度点集合
     *
     * @param data
     * @param MinPts
     * @param radius
     * @return
     */
    public static Map<List<Double>, Integer> getHighDensitySet (
            List<List<Double>> data, int MinPts, double radius, int k) throws RuntimeException{

        Map<List<Double>, Integer> ret = new HashMap<List<Double>, Integer>();
        int n = data.size();

        for (int i = 0; i < n; i++) {
            int cnt = 0;
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    double tmp = PreData.distance(data.get(i), data.get(j));
                    if (tmp < radius) {
                        cnt++;
                    }
                }
            }
            if (cnt >= MinPts) {
                ret.put(data.get(i), cnt);
            }
        }
        if (ret.size() < k) {
            throw new RuntimeException("参数错误,请减小beta值或增大theta值!");
        }
        return ret;

    }

    /**
     * 得到初始中心
     *
     * @param k
     * @param highDensitySet
     * @return
     */
    public static List<List<Double>> getInitCenter(int k,
            Map<List<Double>, Integer> highDensitySet) {
        List<List<Double>> ret = new ArrayList<List<Double>>();
        List<Double> tmp = null;
        double maxValue = 0;
        for (List<Double> key : highDensitySet.keySet()) {
            if (maxValue < highDensitySet.get(key)) {
                maxValue = highDensitySet.get(key);
                tmp = key;
            }
        }
        ret.add(tmp);
        highDensitySet.remove(tmp);
        maxValue = 0;
        for (List<Double> key : highDensitySet.keySet()) {
            if (maxValue < PreData.distance(tmp, key)) {
                maxValue = PreData.distance(tmp, key);
                tmp = key;
            }
        }
        ret.add(tmp);
        highDensitySet.remove(tmp);
        int cnt = 2;
        while (cnt < k) {
            maxValue = 0;
            tmp = null;
            double rs = 1;
            for (List<Double> key : highDensitySet.keySet()) {
                for (int i = 0; i < ret.size(); i++) {
                    rs *= PreData.distance(key, ret.get(i));
                }
                if (maxValue < rs) {
                    maxValue = rs;
                    tmp = key;
                }
            }
            ret.add(tmp);
            highDensitySet.remove(tmp);
            cnt++;
        }
        return ret;
    }

}

3 后记

为了确定K值和初始中心,所做的工作远超过kmeans算法本身。一定注意算法的适用场景。

4 参考资料

1 Canopy算法计算聚类的簇数http://blog.csdn.net/dliyuedong/article/details/40711399

2  Canopy聚类算法 http://my.oschina.net/liangtee/blog/125407

3  熊忠阳, 陈若田,张玉芳  《一种有效的k-means聚类中心初始化方法》 重庆大学  http://pan.baidu.com/s/1dD2Mr9B

时间: 2024-10-30 15:40:52

Kmeans算法的K值和聚类中心的确定的相关文章

使用肘部法确定k-means均值的k值

import numpy as np from sklearn.cluster import KMeans from scipy.spatial.distance import cdist import matplotlib.pyplot as plt c1x = np.random.uniform(0.5, 1.5, (1, 10)) c1y = np.random.uniform(0.5, 1.5, (1, 10)) c2x = np.random.uniform(3.5, 4.5, (1,

k-means算法的优缺点以及改进

大家接触的第一个聚类方法,十有八九都是K-means聚类啦.该算法十分容易理解,也很容易实现.其实几乎所有的机器学习和数据挖掘算法都有其优点和缺点.那么K-means的缺点是什么呢? 总结为下: (1)对于离群点和孤立点敏感: (2)k值选择; (3)初始聚类中心的选择: (4)只能发现球状簇. 对于这4点呢的原因,读者可以自行思考下,不难理解.针对上述四个缺点,依次介绍改进措施. 改进1 首先针对(1),对于离群点和孤立点敏感,如何解决?笔者在前面的一篇博客中,提到过离群点检测的LOF算法,通

机器学习实战之K-Means算法

一,引言 先说个K-means算法很高大上的用处,来开始新的算法学习.我们都知道每一届的美国总统大选,那叫一个竞争激烈.可以说,谁拿到了各个州尽可能多的选票,谁选举获胜的几率就会非常大.有人会说,这跟K-means算法有什么关系?当然,如果哪一届的总统竞选,某一位候选人是绝对的众望所归,那自然能以压倒性优势竞选成功,那么我们的k-means算法还真用不上.但是,我们应该知道2004年的总统大选中,候选人的得票数非常接近,接近到什么程度呢?如果1%的选民将手中的选票投向任何一位候选人,都直接决定了

k-means算法原理以及数学知识

摘要 在大数据算法中,聚类算法一般都是作为其他算法分析的基础,对数据进行聚类可以从整体上分析数据的一些特性.聚类有很多的算法,k-means是最简单最实用的一种算法.在这里对k-means算法的原理以及其背后的数学推导做一 些详细的介绍,并讨论在实际应用中要避免的一些坑. 算法 k-means算法很简单,但是当我们正真把这个算法用在生产中时还是存在很多的细节需要考虑的,这些细节将要在后面进行讨论.首先给出k-means算法的步骤: 1.给出k个初始聚类中心 2.repeat:  把每一个数据对象

聚类算法:K-means 算法(k均值算法)

k-means算法:      第一步:选$K$个初始聚类中心,$z_1(1),z_2(1),\cdots,z_k(1)$,其中括号内的序号为寻找聚类中心的迭代运算的次序号. 聚类中心的向量值可任意设定,例如可选开始的$K$个模式样本的向量值作为初始聚类中心.      第二步:逐个将需分类的模式样本$\{x\}$按最小距离准则分配给$K$个聚类中心中的某一个$z_j(1)$.假设$i=j$时, \[D_j (k) = \min \{ \left\| {x - z_i (k)} \right\|

机器学习-KMeans聚类 K值以及初始类簇中心点的选取

[转]http://www.cnblogs.com/kemaswill/archive/2013/01/26/2877434.html 本文主要基于Anand Rajaraman和Jeffrey David Ullman合著,王斌翻译的<大数据-互联网大规模数据挖掘与分布式处理>一书. KMeans算法是最常用的聚类算法,主要思想是:在给定K值和K个初始类簇中心点的情况下,把每个点(亦即数据记录)分到离其最近的类簇中心点所代表的类簇中,所有点分配完毕之后,根据一个类簇内的所有点重新计算该类簇的

KMeans聚类 K值以及初始类簇中心点的选取 转

本文主要基于Anand Rajaraman和Jeffrey David Ullman合著,王斌翻译的<大数据-互联网大规模数据挖掘与分布式处理>一书. KMeans算法是最常用的聚类算法,主要思想是:在给定K值和K个初始类簇中心点的情况下,把每个点(亦即数据记录)分到离其最近的类簇中心点所代表的类簇中,所有点分配完毕之后,根据一个类簇内的所有点重新计算该类簇的中心点(取平均值),然后再迭代的进行分配点和更新类簇中心点的步骤,直至类簇中心点的变化很小,或者达到指定的迭代次数. KMeans算法本

scikit-learn学习之K-means聚类算法与 Mini Batch K-Means算法

====================================================================== 本系列博客主要参考 Scikit-Learn 官方网站上的每一个算法进行,并进行部分翻译,如有错误,请大家指正 转载请注明出处 ====================================================================== K-means算法分析与Python代码实现请参考之前的两篇博客: <机器学习实战>k

转载: scikit-learn学习之K-means聚类算法与 Mini Batch K-Means算法

版权声明:<—— 本文为作者呕心沥血打造,若要转载,请注明出处@http://blog.csdn.net/gamer_gyt <—— 目录(?)[+] ====================================================================== 本系列博客主要参考 Scikit-Learn 官方网站上的每一个算法进行,并进行部分翻译,如有错误,请大家指正 转载请注明出处 ======================================