Java实现KMeans

算法介绍

  1. 属于无标签学习
  2. 需要归一化处理

算法原理

  1. 首先随机选择k个中心作为初始聚类中心
  2. 接着把每个数据都按照选定的距离策略,分配给最近的中心
  3. 对每个簇,计算数据点的坐标平均值,并将其作为新的中心
  4. 不断循环重复2.3步,直到前后两次的中心不变或者达到最大迭代次数
  5. 将分簇结果保存

算法流程

变量准备

	/**
     * Manhattan distance.
     */
    public static final int MANHATTAN = 0;

    /**
     * Euclidean distance.
     */
    public static final int EUCLIDEAN = 1;

    /**
     * The distance measure.
     */
    public int distanceMeasure = EUCLIDEAN;

    /**
     * A random instance;
     */
    public static final Random random = new Random();

    /**
     * The data.
     */
    Instances dataset;

    /**
     * The number of clusters.
     */
    int numClusters = 2;

    //用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据
    int[][] clusters;

这里同样设置了距离策略、随机数种子以及聚类结果变量clusters,clusters是用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据

读取数据

/**
     *******************************
     * 构造方法1:读取数据集
     *
     * @param paraFilename
     *            The data filename.
     *******************************
     */
    public KMeans(String paraFilename) {
        dataset = null;
        try {
            FileReader fileReader = new FileReader(paraFilename);
            dataset = new Instances(fileReader);
            fileReader.close();
        } catch (Exception ee) {
            System.out.println("Cannot read the file: " + paraFilename + "\r\n" + ee);
            System.exit(0);
        } // Of try
    }// Of the first constructor

这里使用构造方法读取数据集

获得随机序列

    /**
     *********************
     * 与kNN类似,得到一个指定长度的打乱序列
     *
     * @param paraLength
     *            The length of the sequence.
     * @return An array of indices, e.g., {4, 3, 1, 5, 0, 2} with length 6.
     *********************
     */
    public static int[] getRandomIndices(int paraLength) {
        int[] resultIndices = new int[paraLength];

        // 初始化序列
        for (int i = 0; i < paraLength; i++) {
            resultIndices[i] = i;
        } // Of for i

        // 随机交换来打乱
        int tempFirst, tempSecond, tempValue;
        for (int i = 0; i < paraLength; i++) {
            // Generate two random indices.
            tempFirst = random.nextInt(paraLength);
            tempSecond = random.nextInt(paraLength);

            // Swap.
            tempValue = resultIndices[tempFirst];
            resultIndices[tempFirst] = resultIndices[tempSecond];
            resultIndices[tempSecond] = tempValue;
        } // Of for i

        return resultIndices;
    }// Of getRandomIndices

这个部分同kNN,也是输入一个长度N,获得一个1–N的随机序列

计算距离

    /**
     *********************
     * 计算第i条数据与该中心的距离
     *
     * @param paraI
     *            表示第i条数据
     * @param paraArray
     *            一个数组,表示特征空间的一个位置
     * @return The distance.
     *********************
     */
    public double distance(int paraI, double[] paraArray) {
        int resultDistance = 0;
        double tempDifference;
        switch (distanceMeasure) {
            case MANHATTAN:
                // dataset.numAttributes()-1为非标签的属性总数
                for (int i = 0; i < dataset.numAttributes() - 1; i++) {
                    tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
                    if (tempDifference < 0) {
                        resultDistance -= tempDifference;
                    } else {
                        resultDistance += tempDifference;
                    } // Of if
                } // Of for i
                break;

            case EUCLIDEAN:
                for (int i = 0; i < dataset.numAttributes() - 1; i++) {
                    tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
                    resultDistance += tempDifference * tempDifference;
                } // Of for i
                break;
            default:
                System.out.println("Unsupported distance measure: " + distanceMeasure);
        }// Of switch

        return resultDistance;
    }// Of distance

这里计算一条数据与一个中心间的距离

聚类

聚类分为很多个部分,这里我分点解释

随机设置聚类中心

        // 随机设置聚类中心
        int[] tempRandomOrders = getRandomIndices(dataset.numInstances());
        //把前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用
        for (int i = 0; i < numClusters; i++) {
            for (int j = 0; j < tempCenters[0].length; j++) {
                tempCenters[i][j] = dataset.instance(tempRandomOrders[i]).value(j);
            } // Of for j
        } // Of for i

把随机序列前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用

循环更新

这一步是核心,不断迭代,每次迭代都会计算在该情况下每个数据的所属中心,后根据均值来选择新的聚类中心,这样不断重复,直到聚类中心不再变化或者达到最大迭代次数
为每个数据匹配最近的中心

            //用i遍历所有的数据
            for (int i = 0; i < dataset.numInstances(); i++) {
                tempNearestCenter = -1;
                tempNearestDistance = Double.MAX_VALUE;
                //对数据i遍历所有的聚类中心,选择最近的
                for (int j = 0; j < numClusters; j++) {
                    //计算i与当前中心的距离,用tempDistance表示
                    tempDistance = distance(i, tempCenters[j]);
                    //每次遍历都会比较,如果更近,那就赋值给tempNearestDistance,并把当前中心的序号j赋值给tempNearestCenter
                    if (tempNearestDistance > tempDistance) {
                        tempNearestDistance = tempDistance;
                        tempNearestCenter = j;
                    } // Of if
                } // Of for j
                //在得到最近的中心后,将结果交给temClusterArray保存
                tempClusterArray[i] = tempNearestCenter;
            } // Of for i

利用簇内数据的均值选择新的中心

            // Step 2.2 Mean. Find new centers.
            //把一个中心的所有数据进行均值处理,把该均值作为新的中心
            //tempClusterLengths用于存放每个中心的数据数量
            tempClusterLengths = new int[numClusters];
            Arrays.fill(tempClusterLengths, 0);
            //tempNewCenters则是存放新的中心的位置信息
            double[][] tempNewCenters = new double[numClusters][dataset.numAttributes() - 1];
            // Arrays.fill(tempNewCenters, 0);
            //在循环中累加,得到新的中心的坐标累加值与中心数据数量
            for (int i = 0; i < dataset.numInstances(); i++) {
                for (int j = 0; j < tempNewCenters[0].length; j++) {
                    tempNewCenters[tempClusterArray[i]][j] += dataset.instance(i).value(j);
                } // Of for j
                tempClusterLengths[tempClusterArray[i]]++;
            } // Of for i

            // Step 2.3 Now average
            //上面得到的新的中心的坐标累加值与中心数据数量,作除法,得到新的中心坐标
            for (int i = 0; i < tempNewCenters.length; i++) {
                for (int j = 0; j < tempNewCenters[0].length; j++) {
                    tempNewCenters[i][j] /= tempClusterLengths[i];
                } // Of for j
            } // Of for i

分簇结果保存

        //得到最终的聚类中心,将结果写入clusters中并保存
        clusters = new int[numClusters][];
        int[] tempCounters = new int[numClusters];
        for (int i = 0; i < numClusters; i++) {
            //这里每个中心的数据数量是不同的,所以分别为不同的中心分配不同的空间
            clusters[i] = new int[tempClusterLengths[i]];
        } // Of for i
        for (int i = 0; i < tempClusterArray.length; i++) {
            //tempClusterArray是每条数据的所属中心,所以tempClusterArray[i]就是当前数据的所属中心
            //tempCounters
            clusters[tempClusterArray[i]][tempCounters[tempClusterArray[i]]] = i;
            tempCounters[tempClusterArray[i]]++;
        } // Of for i

详细注释

package knn_nb;


import java.io.FileReader;
import java.util.Arrays;
import java.util.Random;
import weka.core.Instances;


public class KMeans {

    /**
     * Manhattan distance.
     */
    public static final int MANHATTAN = 0;

    /**
     * Euclidean distance.
     */
    public static final int EUCLIDEAN = 1;

    /**
     * The distance measure.
     */
    public int distanceMeasure = EUCLIDEAN;

    /**
     * A random instance;
     */
    public static final Random random = new Random();

    /**
     * The data.
     */
    Instances dataset;

    /**
     * The number of clusters.
     */
    int numClusters = 2;

    //用二位数组的形式存放聚类后的数据,每一行代表一个中心,一行的一列就表示该中心下的具体某一个数据
    int[][] clusters;

    /**
     *******************************
     * 构造方法1:读取数据集
     *
     * @param paraFilename
     *            The data filename.
     *******************************
     */
    public KMeans(String paraFilename) {
        dataset = null;
        try {
            FileReader fileReader = new FileReader(paraFilename);
            dataset = new Instances(fileReader);
            fileReader.close();
        } catch (Exception ee) {
            System.out.println("Cannot read the file: " + paraFilename + "\r\n" + ee);
            System.exit(0);
        } // Of try
    }// Of the first constructor

    /**
     *******************************
     * A setter.
     *******************************
     */
    public void setNumClusters(int paraNumClusters) {
        numClusters = paraNumClusters;
    }// Of the setter

    /**
     *********************
     * 与kNN类似,得到一个指定长度的打乱序列
     *
     * @param paraLength
     *            The length of the sequence.
     * @return An array of indices, e.g., {4, 3, 1, 5, 0, 2} with length 6.
     *********************
     */
    public static int[] getRandomIndices(int paraLength) {
        int[] resultIndices = new int[paraLength];

        // 初始化序列
        for (int i = 0; i < paraLength; i++) {
            resultIndices[i] = i;
        } // Of for i

        // 随机交换来打乱
        int tempFirst, tempSecond, tempValue;
        for (int i = 0; i < paraLength; i++) {
            // Generate two random indices.
            tempFirst = random.nextInt(paraLength);
            tempSecond = random.nextInt(paraLength);

            // Swap.
            tempValue = resultIndices[tempFirst];
            resultIndices[tempFirst] = resultIndices[tempSecond];
            resultIndices[tempSecond] = tempValue;
        } // Of for i

        return resultIndices;
    }// Of getRandomIndices

    /**
     *********************
     * 计算第i条数据与该中心的距离
     *
     * @param paraI
     *            表示第i条数据
     * @param paraArray
     *            一个数组,表示特征空间的一个位置
     * @return The distance.
     *********************
     */
    public double distance(int paraI, double[] paraArray) {
        int resultDistance = 0;
        double tempDifference;
        switch (distanceMeasure) {
            case MANHATTAN:
                // dataset.numAttributes()-1为非标签的属性总数
                for (int i = 0; i < dataset.numAttributes() - 1; i++) {
                    tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
                    if (tempDifference < 0) {
                        resultDistance -= tempDifference;
                    } else {
                        resultDistance += tempDifference;
                    } // Of if
                } // Of for i
                break;

            case EUCLIDEAN:
                for (int i = 0; i < dataset.numAttributes() - 1; i++) {
                    tempDifference = dataset.instance(paraI).value(i) - paraArray[i];
                    resultDistance += tempDifference * tempDifference;
                } // Of for i
                break;
            default:
                System.out.println("Unsupported distance measure: " + distanceMeasure);
        }// Of switch

        return resultDistance;
    }// Of distance

    /**
     *******************************
     * Clustering.
     *******************************
     */
    public void clustering() {
        //上次循环的聚类序列(表示每条数据的所属中心)
        int[] tempOldClusterArray = new int[dataset.numInstances()];
        tempOldClusterArray[0] = -1;
        //当前循环的聚类序列
        int[] tempClusterArray = new int[dataset.numInstances()];
        Arrays.fill(tempClusterArray, 0);
        //用于保存聚类中心的坐标
        double[][] tempCenters = new double[numClusters][dataset.numAttributes() - 1];

        // 随机设置聚类中心
        int[] tempRandomOrders = getRandomIndices(dataset.numInstances());
        //把前k个数据的坐标直接当成聚类中心来用,实现随机设置聚类中心的作用
        for (int i = 0; i < numClusters; i++) {
            for (int j = 0; j < tempCenters[0].length; j++) {
                tempCenters[i][j] = dataset.instance(tempRandomOrders[i]).value(j);
            } // Of for j
        } // Of for i

        //不断迭代(循环条件是两次聚类中心是不同的),每次迭代都会计算在该情况下每个数据的所属中心,后根据均值来选择新的聚类中心,这样不断重复,直到聚类中心不再变化或者达到最大迭代次数
        int[] tempClusterLengths = null;
        while (!Arrays.equals(tempOldClusterArray, tempClusterArray)) {
            System.out.println("New loop ...");
            //用于记录上次循环每条数据的所属中心
            tempOldClusterArray = tempClusterArray;
            //用于记录当前循环每条数据的所属中心
            tempClusterArray = new int[dataset.numInstances()];

            // Step 2.1 Minimization. Assign cluster to each instance.
            //在当前中心的情况下,为每条数据选择最近的中心,并将结果保存
            int tempNearestCenter;
            double tempNearestDistance;
            double tempDistance;

            //用i遍历所有的数据
            for (int i = 0; i < dataset.numInstances(); i++) {
                tempNearestCenter = -1;
                tempNearestDistance = Double.MAX_VALUE;
                //对数据i遍历所有的聚类中心,选择最近的
                for (int j = 0; j < numClusters; j++) {
                    //计算i与当前中心的距离,用tempDistance表示
                    tempDistance = distance(i, tempCenters[j]);
                    //每次遍历都会比较,如果更近,那就赋值给tempNearestDistance,并把当前中心的序号j赋值给tempNearestCenter
                    if (tempNearestDistance > tempDistance) {
                        tempNearestDistance = tempDistance;
                        tempNearestCenter = j;
                    } // Of if
                } // Of for j
                //在得到最近的中心后,将结果交给temClusterArray保存
                tempClusterArray[i] = tempNearestCenter;
            } // Of for i

            // Step 2.2 Mean. Find new centers.
            //把一个中心的所有数据进行均值处理,把该均值作为新的中心
            //tempClusterLengths用于存放每个中心的数据数量
            tempClusterLengths = new int[numClusters];
            Arrays.fill(tempClusterLengths, 0);
            //tempNewCenters则是存放新的中心的位置信息
            double[][] tempNewCenters = new double[numClusters][dataset.numAttributes() - 1];
            // Arrays.fill(tempNewCenters, 0);
            //在循环中累加,得到新的中心的坐标累加值与中心数据数量
            for (int i = 0; i < dataset.numInstances(); i++) {
                for (int j = 0; j < tempNewCenters[0].length; j++) {
                    tempNewCenters[tempClusterArray[i]][j] += dataset.instance(i).value(j);
                } // Of for j
                tempClusterLengths[tempClusterArray[i]]++;
            } // Of for i

            // Step 2.3 Now average
            //上面得到的新的中心的坐标累加值与中心数据数量,作除法,得到新的中心坐标
            for (int i = 0; i < tempNewCenters.length; i++) {
                for (int j = 0; j < tempNewCenters[0].length; j++) {
                    tempNewCenters[i][j] /= tempClusterLengths[i];
                } // Of for j
            } // Of for i

            System.out.println("Now the new centers are: " + Arrays.deepToString(tempNewCenters));
            tempCenters = tempNewCenters;
        } // Of while

        // Step 3. Form clusters.
        //得到最终的聚类中心,将结果写入clusters中并保存
        clusters = new int[numClusters][];
        int[] tempCounters = new int[numClusters];
        for (int i = 0; i < numClusters; i++) {
            //这里每个中心的数据数量是不同的,所以分别为不同的中心分配不同的空间
            clusters[i] = new int[tempClusterLengths[i]];
        } // Of for i
        for (int i = 0; i < tempClusterArray.length; i++) {
            //tempClusterArray是每条数据的所属中心,所以tempClusterArray[i]就是当前数据的所属中心
            //tempCounters
            clusters[tempClusterArray[i]][tempCounters[tempClusterArray[i]]] = i;
            tempCounters[tempClusterArray[i]]++;
        } // Of for i

        System.out.println("The clusters are: " + Arrays.deepToString(clusters));
    }// Of clustering

    /**
     *******************************
     * Clustering.
     *******************************
     */
    public static void testClustering() {
        KMeans tempKMeans = new KMeans("C:\\Users\\hp\\Desktop\\deepLearning\\src\\main\\java\\resources\\iris.arff");
        tempKMeans.setNumClusters(3);
        tempKMeans.clustering();
    }// Of testClustering

    /**
     *************************
     * A testing method.
     *************************
     */
    public static void main(String arags[]) {
        testClustering();
    }// Of main

}// Of class KMeans

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值