码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2015-07-28 12:38:26      阅读:271      评论:0      收藏:0      [点我收藏+]

标签:

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;
    }
View Code

 

 
缺点:
 
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();
    }
View Code

 

若只考虑计算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();
    }
View Code

 

上面初始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;
    }

}
View Code

 

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;
    }

}
View Code

 

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

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

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

 

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

标签:

原文地址:http://www.cnblogs.com/hdu-2010/p/4682185.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!