機器學習:K-means和K-medoids對比[4]

簡單對比一下這兩者的區別。兩者的主要區別主要在質心的選擇中,k-means是樣本點均值,k-medoids則是從樣本點中選取。

首先給出兩者的算法實現步驟:

K-means

1、隨機選取K個質心的值 

2、計算各個點到質心的距離 

3、將點的類劃分爲離他最近的質心,形成K個cluster 

4、根據分類好的cluster,在每個cluster內重新計算質心(平均每個點的值)

5、重複迭代2-4步直到滿足迭代次數或誤差小於指定的值


K-medoids

1、隨機選取K個質心的值 (質心必須是某些樣本點的值,而不是任意值)

2、計算各個點到質心的距離 

3、將點的類劃分爲離他最近的質心,形成K個cluster 

4、根據分類好的cluster,在每個cluster內重新計算質心:

             4.1 計算cluster內所有樣本點到其中一個樣本點的曼哈頓距離和(絕對誤差)

             4.2  選出使cluster絕對誤差最小的樣本點作爲質心

5、重複迭代2-4步直到滿足迭代次數或誤差小於指定的值


以上就可以看出兩者之間的區別:

k-means的質心是各個樣本點的平均,可能是樣本點中不存在的點。k-medoids的質心一定是某個樣本點的值。


這個不同使他們具有不同的優缺點

1、k-medoids的運行速度較慢,計算質心的步驟時間複雜度是O(n^2),因爲他必須計算任意兩點之間的距離。而k-means只需平均即可。

2、k-medoids對噪聲魯棒性比較好。例:當一個cluster樣本點只有少數幾個,如(1,1)(1,2)(2,1)(100,100)。其中(100,100)是噪聲。如果按照k-means質心大致會處在(1,1)(100,100)中間,這顯然不是我們想要的。這時k-medoids就可以避免這種情況,他會在(1,1)(1,2)(2,1)(100,100)中選出一個樣本點使cluster的絕對誤差最小,計算可知一定會在前三個點中選取。


以上。雖然k-medoids也有優點,但是他只能對小樣本起作用,樣本一大,他的速度就太慢了,而且當樣本多的時候,少數幾個噪音對k-means的質心影響也沒有想象中的那麼重,所以k-means的應用明顯比k-medoids多的多。


然後給出我實現的兩者的Java代碼。(呼,新年第一天我還在寫代碼,也真是不容易啊,注孤生 T-T)


花了一天的時間才寫好調試好,這次是用的Java寫的,自己實現的k-medoids和k-means。

K-medoids的大致流程是參考的網上的程序,在這:http://blog.sina.com.cn/s/blog_6adf9f370100qnet.html。不過我寫的K-medoids跟他已經很不同了。


分別使用兩種對11個二維數據進行分類,結果如下:




還只是簡單的分析了下,之後會增加文件txt讀入的功能,試着用稍微大點的數據來試試。不過白天因爲調試也試了很多次。給我的感覺就是,k-medoids方法沒有想象中對噪聲魯棒性那麼好,或者說,他確實會對噪聲魯棒性好,但是他的結果偏差同樣會很嚴重,在數據量不大的情況下。但是數據量一大,k-medoids方法就基本不能用了,因爲這方法的時間複雜度是O(n^2),簡直嚇死人。。所以我個人分析的結果是k-medoids並沒有想象中那麼好,怪不得這方法一點都不常用,速度慢不說,誤差也沒有想象中降低那麼明顯。


注:上圖中的minErr兩者不同。k-medoids的是曼哈頓距離和,k-means的則是歐式距離和


代碼如下:


package cluster;

import static java.lang.System.*;
import java.util.ArrayList;

//簇:包含質心和圍繞着質心的樣本點。
public class Cluster{     
    private DataPoint center;
    private String name;
    private ArrayList<DataPoint> dataPoints;
    private double minErr=-1;    //記錄每個類簇的絕對誤差(曼哈頓距離和)
    
    public Cluster(){this.name=" ";this.center=null;dataPoints=new ArrayList<>();}
    public Cluster(String name){this.name=name;this.center=null;dataPoints=new ArrayList<>();}
    public Cluster(DataPoint dp){this.center=dp;this.name=dp.name;dataPoints=new ArrayList<>();}
    public Cluster(DataPoint dp,String name){
        this.center=dp;
        this.name=name;
        dataPoints=new ArrayList<>();
    }
    
    public DataPoint getCenter(){return this.center;}
    
    public void setCenter(DataPoint dp){
        this.center=dp;
        this.name=dp.name;
        for(int i=0;i<dataPoints.size();i++)
            this.dataPoints.get(i).setCenter(this.center);
    }
    
    
    public void addDataPoint(DataPoint dp){
        dp.setCenter(this.center);
        dataPoints.add(dp);
    }
    
    public ArrayList<DataPoint> getDataPoints(){ return dataPoints;}
    public void copyDataPoints(ArrayList<DataPoint> dps){this.dataPoints=dps;}
    
    public void setMinErr(double min){this.minErr=min;}
    
    public void printCluster(){
        out.println("Center:");
        out.println(this.center);
        out.print("num:"+dataPoints.size());
        out.println("  minErr:"+minErr);
        for(int i=0;i<dataPoints.size();i++){
            out.println(dataPoints.get(i));
        }
    }

}




//數據點格式
class DataPoint{    //數據點
    public double[] values;
    public String name;
    public DataPoint center;
    public static boolean hasSet=false;     //設置點的維度
    private static int dimension=0;         //注意維度是靜態的!!也就是每次調用時,對於所有的樣本點,都應當是維度一致的!
    
    public DataPoint(double[] values,String name){
        if (!hasSet){
            DataPoint.setDimension(values.length);
        }
        if(values.length==this.dimension){
            this.values=values;
            this.name=name;
        }
        else{out.println("this may be a wrong data with uncommon dimension.");}
    }
    
    public static void setDimension(int dimen){dimension=dimen;hasSet=true;}
    public static int getDimension(){return dimension;}
    
    
    public void setCenter(DataPoint center){this.center=center;}
    public DataPoint getCenter(){ return this.center;}
    
    public static boolean equals(DataPoint dp1,DataPoint dp2){ 
        for(int i=0;i<dimension;i++){
            if(dp1.values[i]!=dp2.values[i]){return false;}
        }
        return true;
    }

    public String toString(){
        String val="";
        for(int i=0;i<dimension;i++){val=val+" "+values[i];}
        return this.name+":"+val;
    }
    
    

}



package cluster;

import static java.lang.System.*;
import java.util.ArrayList;
import java.lang.Math;


public class Distance{   //下回查一下,怎麼將函數作爲返回值的,實現通過選定不同的值來調用不同的距離函數
    
    private Distance(){} //將他作爲靜態類,不需要構造器
    
    
    public static double allEuclideans(Cluster clus,ArrayList<DataPoint> dataPoints){
        DataPoint center=clus.getCenter(); 
        double sum=0;
        double dist=0;
        for(int i=0;i<dataPoints.size();i++){
            dist=Distance.euclidean(center,dataPoints.get(i));
            sum+=dist;
        }
        return sum;
    }
    
    
    public static double euclidean(DataPoint dp1,DataPoint dp2){
        double sum=0;
        double dist=0;
        for(int i=0;i<DataPoint.getDimension();i++){
            dist=Math.pow(dp1.values[i]-dp2.values[i],2);
            sum+=dist;
        }
        return sum;
        
    }
    
    public static double mahalanobis(DataPoint dp1,DataPoint dp2){
        double sum=0;
        double dist=0;
        for(int i=0;i<DataPoint.getDimension();i++){
            dist=Math.abs(dp1.values[i]-dp2.values[i]);
            sum+=dist;
        }
        return sum;
    }
   
    //記錄兩點之間的曼哈頓距離的矩陣(可以改變其中的距離函數來該表)
    //用矩陣來記錄距離,可以減少重讀計算,如C12=C21,C11=0
	public static double[][] matrixDistance(ArrayList<DataPoint> dataPoints){  
        int length=dataPoints.size();
        double[][] matrixDist=new double[length][length];
        
        for(int i=0;i<length;i++){
            for(int j=i+1;j<length;j++){
                matrixDist[i][j]=Distance.mahalanobis(dataPoints.get(i),dataPoints.get(j));
                matrixDist[j][i]=matrixDist[i][j];
            }
        }
        for(int i=0;i<length;i++){matrixDist[i][i]=0;}   //跟自己的距離當然是0
        
        return matrixDist;
    }
    
    public static double[] infinitNorm(double[][] matrixDist){   //矩陣的無窮範數,其實就是每行之和的最小值,也就是曼哈頓距離的最小,選出該行
        int length=matrixDist.length;
        int temp;
        int currentMin=0;
        int itsRow=-1;
        boolean flag=true;
        double[] result=new double[2];
        
        for(int i=0;i<length;i++){
            temp=0;
            for(int j=0;j<length;j++){
                temp+=matrixDist[i][j];
            }
    
            if (temp<currentMin || flag){
                currentMin=temp;
                itsRow=i;
                flag=false;
            }
        }
        result[0]=itsRow;
        result[1]=currentMin;
        if(itsRow==-1){out.println("Error:cannot find infinitNorm!!please check the inputMatrix.");}
        return result;
        
    }
    
}



package cluster;

import static java.lang.System.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.Collections;


//計算質心的類函數
public class KMedoids{
    private int iter=5;
    private int kClusters=3;   
    private ArrayList<DataPoint> dataPoints;
    private Cluster[] clusters;
    private final ArrayList<DataPoint> originalDataPoints;   
    
    private void init(){  //要始終記得,數組初始化不代表元素初始化了!
        this.clusters=new Cluster[kClusters];
        for(int i=0;i<kClusters;i++){
            this.clusters[i]=new Cluster();
        }
    }
    
    public KMedoids(ArrayList<DataPoint> dps){
        this.dataPoints=dps;
        originalDataPoints=dps;
        this.init();
    }
    public KMedoids(ArrayList<DataPoint> dps,int iter,int kClusters){
        this.iter=iter;
        this.kClusters=kClusters;
        this.dataPoints=dps;
        originalDataPoints=dps;
        this.init();

        
    }
    
    //計算質心流程的啓動函數
    public void start(){
        this.initCenter();
        for(int i=0;i<iter;i++){
            out.println("iter: "+i);
            this.classify();
            Cluster[] newClusters=this.repickCenter();
            if(judgeEqual(newClusters,this.clusters)){ break;}
            else{ 
                this.clusters=newClusters;
                }
            
        }
    }
    
    //打印結果的函數
    public void getResult(){
        out.println("******************************************************");
        out.println("Now show you kMedoids' result:");
        for(int i=0;i<kClusters;i++)
            clusters[i].printCluster();
        
    }
    
    //初始化隨機質心
    public void initCenter(){          
        ArrayList<Integer> randNum=new ArrayList<>();
        Random rand=new Random();
        for(int i=0;i<kClusters;i++){
            int randInt=rand.nextInt(dataPoints.size());
            if(randNum.contains(randInt)){
                i--;
                continue;
            }
            
            this.clusters[i].setCenter(dataPoints.get(randInt));
            randNum.add(randInt);
        }
        Collections.sort(randNum);
        for(int i=kClusters-1;i>=0;i--){      //從最大的i開始反向刪除,纔不會因爲先刪除了小的i導致列表長度改變
            int k=randNum.get(i);             //哭笑不得,最開始使用dataPoints.remove(randNum,get(i));可是發現一隻刪不掉,
            this.dataPoints.remove(k);        //最後才發現randNum返回的裏面的是Integer對象,要使用自動拆箱功能纔可以
        }   
    
    }
    
    //對每個數據點分類質心
    public void classify(){     
        for(int i=0;i<kClusters;i++){clusters[i].getDataPoints().clear();}  //每次開始前要清楚cluster中的樣本
    
        int itsClus;
        boolean flag;
        double minDist,tempDist;
        for(int i=0;i<dataPoints.size();i++){
            flag=true;
            minDist=0;
            itsClus=-1;
            for(int j=0;j<kClusters;j++){
                
                tempDist=Distance.euclidean(clusters[j].getCenter(),dataPoints.get(i));
                if (tempDist < minDist || flag){
                    minDist=tempDist;
                    itsClus=j;
                    flag=false;
                    }
            }
            clusters[itsClus].addDataPoint(dataPoints.get(i));
            

        }
        
    }
    
    //重新確定質心
    public Cluster[] repickCenter(){   
        Cluster[] newClusters=new Cluster[kClusters];
        ArrayList<DataPoint> tempList=new ArrayList<>();
        for(int i=0;i<kClusters;i++){newClusters[i]=new Cluster();}
        
        for(int i=0;i<kClusters;i++){
            ArrayList<DataPoint> clusDataPoints=clusters[i].getDataPoints();
            
            clusDataPoints.add(clusters[i].getCenter());
            double[][] matrixDist=Distance.matrixDistance(clusDataPoints);
            double[] minErr=Distance.infinitNorm(matrixDist);
            newClusters[i].setMinErr(minErr[1]);
            int index1=(int)minErr[0];
            newClusters[i].setCenter(clusDataPoints.get(index1));
            newClusters[i].copyDataPoints(clusDataPoints);
            newClusters[i].getDataPoints().remove(index1); //將質心從樣本點中去除
            tempList.addAll(newClusters[i].getDataPoints());
        }
        this.dataPoints=tempList;
        //for(Cluster clus:newClusters){clus.printCluster();}    //打印每次迭代的過程,需要的話可去掉註釋
        
        return newClusters;
    }
	

    //判斷新舊質心是否一樣
    public boolean judgeEqual(Cluster[] clus1,Cluster[] clus2){
        boolean bool;
        for(int i=0;i<kClusters;i++){
            bool=DataPoint.equals(clus1[i].getCenter(),clus2[i].getCenter());
            if(!bool){return false;}
        }
        return true;
    }
  
    
    
}



package cluster;

import static java.lang.System.*;
import java.util.ArrayList;
import java.util.Random;
import java.util.Collections;


public class KMeans{
    private int iter=5;
    private int kClusters=3;   
    private ArrayList<DataPoint> dataPoints;
    private Cluster[] clusters;
    

    public void init(){  //要始終記得,數組初始化不代表元素初始化了!
        this.clusters=new Cluster[kClusters];
        for(int i=0;i<kClusters;i++){
            this.clusters[i]=new Cluster();
        }
    }

    public KMeans(){}
    public KMeans(ArrayList<DataPoint> dataPoints){
        this.dataPoints=dataPoints;
        this.init();
    }
    public KMeans(ArrayList<DataPoint> dataPoints,int iter,int kClusters){
        this.iter=iter;
        this.kClusters=kClusters;
        this.dataPoints=dataPoints;
        this.init();
    }

    //KMeans的啓動函數
    public void start(){
        int i=0;
        this.initCenter();
        while(i<iter){
            this.classify();
            Cluster[] newClusters=this.repickCenter();
            if (judgeEqual(newClusters,this.clusters)){break;}
            else{ this.clusters=newClusters; }
            i++;
        }

    }

    //打印結果的函數
    public void getResult(){
        out.println("******************************************************");
        out.println("Now show you kMeans' result:");
        for(int i=0;i<kClusters;i++)
            clusters[i].printCluster();
        
    }



    //初始化質心
    public void initCenter(){
        this.randomCenter();
    }

    //生成初始質心的一種方法,隨機法
    public void randomCenter(){
        ArrayList<Integer> randNum=new ArrayList<>();
        Random rand=new Random();
        for(int i=0;i<kClusters;i++){
            int randInt=rand.nextInt(dataPoints.size());
            if(randNum.contains(randInt)){
                i--;
                continue;
            }
            
            this.clusters[i].setCenter(dataPoints.get(randInt));
            randNum.add(randInt);
        }
        
    }

    //生成初始質心的第二種方法,距離最大法
    public void maxCenter(){}

    //對樣本點分簇
    public void classify(){     
        for(int i=0;i<kClusters;i++){clusters[i].getDataPoints().clear();}  //每次開始前要清楚cluster中的樣本
    
        int itsClus;
        boolean flag;
        double minDist,tempDist;
        for(int i=0;i<dataPoints.size();i++){
            flag=true;
            minDist=0;
            itsClus=-1;
            for(int j=0;j<kClusters;j++){
                
                tempDist=Distance.euclidean(clusters[j].getCenter(),dataPoints.get(i));
                if (tempDist < minDist || flag){
                    minDist=tempDist;
                    itsClus=j;
                    flag=false;
                    }
            }
            clusters[itsClus].addDataPoint(dataPoints.get(i));
            
        }
        for(int i=0;i<kClusters;i++){
            double minErr=Distance.allEuclideans(clusters[i],clusters[i].getDataPoints());
            clusters[i].setMinErr(minErr);
        }

    }

    //重新確定質心
    public Cluster[] repickCenter(){
        Cluster[] newClusters=new Cluster[kClusters];
        for(int i=0;i<kClusters;i++){newClusters[i]=new Cluster();}

        for(int k=0;k<kClusters;k++){
            double[] sumValues=new double[DataPoint.getDimension()];
            double[] centerValues=new double[DataPoint.getDimension()];
            ArrayList<DataPoint> clusDataPoints=clusters[k].getDataPoints();
            int length=clusDataPoints.size();
            for(int i=0;i<length;i++){
                for(int j=0;j<sumValues.length;j++){
                    sumValues[j]+=clusDataPoints.get(i).values[j];
                }
            }

            for(int i=0;i<sumValues.length;i++){centerValues[i]=sumValues[i]/length;}
            DataPoint dp=new DataPoint(centerValues,""+k);
            newClusters[k].setCenter(dp);
            
        }
        //for(Cluster clus:newClusters){clus.printCluster();}    //打印每次迭代的過程,需要的話可去掉註釋
        return newClusters;
    }

    //判斷新舊質心是否一樣
    public boolean judgeEqual(Cluster[] clus1,Cluster[] clus2){ 
        boolean bool;
        for(int i=0;i<kClusters;i++){
            bool=DataPoint.equals(clus1[i].getCenter(),clus2[i].getCenter());
            if(!bool){return false;}
        }
        return true;
    }
    
    
}



package cluster;

import static java.lang.System.*;
import java.util.ArrayList;

public class TestKM{
	
	public static void main(String[] args){
		ArrayList<DataPoint> data=new ArrayList<>();
		DataPoint[] dps=new DataPoint[11];
		dps[0]=new DataPoint(new double[]{1,3},"0");
		dps[1]=new DataPoint(new double[]{1,2},"a");
		dps[2]=new DataPoint(new double[]{3,4},"b");
		dps[3]=new DataPoint(new double[]{2,1},"c");
		dps[4]=new DataPoint(new double[]{3,3},"d");
		dps[5]=new DataPoint(new double[]{18,22},"e");
		dps[6]=new DataPoint(new double[]{19,20},"f");
		dps[7]=new DataPoint(new double[]{20,22},"g");
		dps[8]=new DataPoint(new double[]{18,19},"h");
		dps[9]=new DataPoint(new double[]{20,22},"i");
		dps[10]=new DataPoint(new double[]{23,20},"j");
		for(int i=0;i<11;i++){
			data.add(dps[i]);
		}
		
		KMedoids km1=new KMedoids(data,5,2);
		km1.start();
		km1.getResult();
        
        KMeans km2=new KMeans(data,5,2);
        km2.start();
        km2.getResult();
		
	}
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章