一、K-means聚類算法
K均值聚類(K-means clustering)是一種常見的無監督學習算法,用于將數據集中的樣本劃分為K個不同的類別或簇。它通過最小化樣本點與所屬簇中心點之間的距離來確定最佳的簇劃分。
K均值聚類的基本思想如下:
- 隨機選擇K個初始聚類中心(質心)。
- 對于每個樣本,計算其與各個聚類中心之間的距離,并將樣本分配到距離最近的聚類中心所代表的簇。
- 對于每個簇,計算簇中樣本的均值,并將該均值作為新的聚類中心。
- 重復步驟2和步驟3,直到聚類中心不再變化或達到預定的迭代次數。
K均值聚類的關鍵是如何選擇初始的聚類中心。常見的方法是隨機選擇數據集中的K個樣本作為初始聚類中心,或者使用一些啟發式的方法來選擇。
K均值聚類的優點包括簡單易實現、計算效率高和可擴展性好。它在許多領域中被廣泛應用,如數據分析、圖像處理、模式識別等。然而,K均值聚類也存在一些限制,例如對于初始聚類中心的敏感性、對于離群值的影響較大以及需要事先指定簇的個數K等。
在實際應用中,可以根據實際問題和數據集的特點來選擇合適的K值,并進行多次運行以獲得更穩定的結果。此外,K均值聚類也可以與其他算法相結合,如層次聚類(hierarchical clustering)和密度聚類(density-based clustering),以獲得更好的聚類效果。
總的來說,K均值聚類是一種常用的無監督學習算法,用于將數據集中的樣本劃分為K個簇。它簡單而高效,適用于許多聚類問題。然而,在使用K均值聚類時需要注意選擇初始聚類中心和合適的K值,以及對其限制和局限性的認識。
二、基于weka手工實現K-means聚類算法
package weka.clusterers.myf;import weka.clusterers.RandomizableClusterer;
import weka.core.*;import java.util.*;/*** @author YFMan* @Description 自定義的 KMeans 聚類器* @Date 2023/6/8 15:01*/
public class myKMeans extends RandomizableClusterer {// 聚類中心的數量private int m_NumClusters = 2;// 不同聚類中心的集合private Instances m_ClusterCentroids;// 聚類的最大迭代次數private int m_MaxIterations = 500;// 追蹤收斂前完成的迭代次數private int m_Iterations = 0;// 構造函數public myKMeans() {super();// 設置隨機種子m_SeedDefault = 10;setSeed(m_SeedDefault);}/** @Author YFMan* @Description //基類定義的接口,必須要實現* @Date 2023/6/8 16:37* @Param []* @return weka.core.Capabilities**/@Overridepublic Capabilities getCapabilities() {Capabilities result = super.getCapabilities();result.disableAll();result.enable(Capabilities.Capability.NO_CLASS);// attributesresult.enable(Capabilities.Capability.NOMINAL_ATTRIBUTES);result.enable(Capabilities.Capability.NUMERIC_ATTRIBUTES);result.enable(Capabilities.Capability.MISSING_VALUES);return result;}/** @Author YFMan* @Description //進行聚類* @Date 2023/6/8 16:38* @Param [data 用于聚類的數據集]* @return void**/@Overridepublic void buildClusterer(Instances instances) throws Exception {// 迭代次數m_Iterations = 0;// 初始化聚類中心m_ClusterCentroids = new Instances(instances, m_NumClusters);// 每個樣本屬于哪個聚類中心int[] clusterAssignments = new int[instances.numInstances()];// 偽隨機數生成器Random RandomO = new Random(getSeed());int instIndex;HashSet<Instance> initC = new HashSet<>();// 初始化聚類中心,隨機選擇 m_NumClusters 個樣本作為聚類中心for (int j = instances.numInstances() - 1; j >= 0; j--) {instIndex = RandomO.nextInt(j + 1);if (!initC.contains(instances.instance(instIndex))) {m_ClusterCentroids.add(instances.instance(instIndex));initC.add(instances.instance(instIndex));}instances.swap(j, instIndex);if (m_ClusterCentroids.numInstances() == m_NumClusters) {break;}}boolean converged = false;// 用于存儲每個聚類中心的樣本集合Instances[] tempI = new Instances[m_NumClusters];while (!converged) {m_Iterations++;converged = true;// 計算每個樣本 屬于哪個聚類中心for (int i = 0; i < instances.numInstances(); i++) {Instance toCluster = instances.instance(i);int newC = clusterInstance(toCluster);// 如果樣本所屬的聚類中心發生變化,則說明還沒有收斂if (newC != clusterAssignments[i]) {converged = false;}clusterAssignments[i] = newC;}// 重新計算聚類中心m_ClusterCentroids = new Instances(instances, m_NumClusters);for (int i = 0; i < m_NumClusters; i++) {tempI[i] = new Instances(instances, 0);}for (int i = 0; i < instances.numInstances(); i++) {tempI[clusterAssignments[i]].add(instances.instance(i));}// 重新計算聚類中心for (int i = 0; i < m_NumClusters; i++) {// 計算每個屬性的平均值m_ClusterCentroids.add(calculateCentroid(tempI[i]));}// 如果迭代次數達到最大值,則強制結束if (m_Iterations == m_MaxIterations) {converged = true;}}}/** @Author YFMan* @Description //計算某個聚類中心的中心點* @Date 2023/6/8 16:57* @Param [instances 聚類中心的樣本集合]* @return weka.core.Instance 聚類中心的中心點**/private Instance calculateCentroid(Instances instances) {int numInst = instances.numInstances();int numAttr = instances.numAttributes();Instance centroid = new Instance(numAttr);double sum;for (int i = 0; i < numAttr; i++) {sum = 0;for (int j = 0; j < numInst; j++) {sum += instances.instance(j).value(i);}centroid.setValue(i, sum / numInst);}return centroid;}/** @Author YFMan* @Description //計算兩個屬性全為數值類型的樣本之間的距離(歐式距離)* @Date 2023/6/8 16:47* @Param [first 第一個樣例, second 第二個樣例]* @return double**/private double distance(Instance first, Instance second) {// 定義歐式距離double euclideanDistance = 0;// 定義overlapping距離double overlappingDistance = 0;for (int index = 0; index < first.numAttributes(); index++) {if (index == first.classIndex()) {continue;}// 如果是數值類型的屬性,則計算歐式距離if (first.attribute(index).isNumeric()) {double dis = first.value(index) - second.value(index);euclideanDistance += dis * dis;} else {// 如果是標稱類型的屬性,則計算是否相等if (first.value(index) != second.value(index)) {overlappingDistance += 1;}}}return Math.sqrt(euclideanDistance) + overlappingDistance;}/** @Author YFMan* @Description //對一個給定的樣例進行分類* @Date 2023/6/8 16:50* @Param [instance 給定的樣例]* @return int 返回樣例所屬的聚類中心id**/@Overridepublic int clusterInstance(Instance instance) throws Exception {double minDist = Double.MAX_VALUE;int bestCluster = 0;for (int i = 0; i < m_NumClusters; i++) {double dist = distance(instance, m_ClusterCentroids.instance(i));if (dist < minDist) {minDist = dist;bestCluster = i;}}return bestCluster;}/** @Author YFMan* @Description //返回聚類中心的數量* @Date 2023/6/8 16:34* @Param []* @return int**/@Overridepublic int numberOfClusters() throws Exception {return m_NumClusters;}/** @Author YFMan* @Description //主函數* @Date 2023/6/8 16:33* @Param [argv 命令行參數]* @return void**/public static void main(String[] argv) {runClusterer(new myKMeans(), argv);}
}