1. 程式人生 > >KMeans聚類演算法

KMeans聚類演算法

1、什麼是聚類

    所謂聚類就是將一組物件按照特徵劃分不為的小組,使得組內的差異性儘可能的小,組間的差異儘可能的大。例如,粗粒度的分類,按照學校實力,分為985、211高校,普通一本高校,二本高校,三本高校。如果再更加細的分類,一個學校裡面會按照所修的課程差異性分為不同學院,不同專業,這些同學院的專業課相差較小,不同的學院的課程相差就很大了。

2、聚類與分類的區別

    分類演算法是已知資料的類別,通過對樣本資料的學習建立分類模型,屬於監督學習;而聚類演算法是不知道資料的類別,由演算法根據資料的分佈進行分類,屬於非監督學習模型。

3、Kmeans聚類

    Kmeans聚類屬於劃分聚類,由使用者指定聚類簇的個數k,尋找k個簇中心,按照指定的相似度計算方法將樣本劃分到這K個聚類中心。

這裡的相似度計算有很多種方法,常用的有

歐式距離:            

餘弦相似度:

下面全部採用歐式距離作為相似度度量方法

    演算法流程:

    隨機初始化k個聚類中心(第一次的聚類中心是隨機選取的k個樣本)

    對每個樣本資料

        對每個聚類中心

            計算樣本與聚類中心的相似度

        將資料點分配到相似度最高的樣本中心

    對每個簇,使用簇內的樣本資料特徵均值更新聚類中心

   重複上述步驟,直至迭代次數達到使用者指定的次數或者聚類中心基本沒有改變。

    Kmeans聚類演算法的優化目標函式:

                                           


    上式的xi為第i個樣本點,ci為第i個樣本對應的聚類。對所有的樣本,尋找c箇中心點μ,使得每個樣本距離其中心點的歐式距離之和最小。這個函式不是凸函式,無法對其求最優化,因此在聚類的過程中無法保證求得的聚類中心為全域性最優的結果。但是可以確定的是t次迭代的過程,使得這個函式的函式值越來越小。其中隱含著EM演算法的思想,將所有的樣本分配到最近的聚類中心屬於E環節求期望,對每個簇的中心進行修正屬於M環節求最小化。為了避免區域性最優,通常的做法是重複進行多次聚類,每次選擇不同的初始聚類中心,比較選取最優的聚類結果。

4、Kmeans聚類演算法的改進Kmedoids演算法

    Kmeans聚類演算法中心的更新採用均值,這種方法計算簡單,但是也會帶來問題,對異常點會比較敏感,計算平均值時會使聚類中心的位置偏向異常點。一種對Kmeans的改進是,對聚類中心的位置更改限制為樣本點。即每次修改不是求樣本的均值,而是將簇內的每一個樣本點嘗試作為這個簇的新中心,選擇一個使得簇內差異最小的樣本點作為新的聚類中心點,對每個簇進行同樣的更新。這種方式對異常點不敏感,但是代價比較高,計算速度,收斂速度都會下降。 

5、Kmeans聚類演算法的改進Kmeans++演算法

    Kmeans聚類對初始點的選取非常敏感,在大資料集上,同一個演算法不同的初始化點得到的結果往往不同。Kmeans++演算法改進了Kmeans演算法對初始聚類中心的選取方法,聚類過程和Kmeans是一樣的。Kmeans++演算法選取的方式是,首先從樣本點中隨機一個樣本點作為第一個聚類中心點。計算訓練樣本距離各自簇中心的距離,將這個距離儲存在一個dists陣列中,並求距離總和sum,在0至sum區間產生一個隨機數dist,使用這個隨機數從i=0開始做運算dist-=dists[i],直至dist<0,此時的下標i對應的樣本點作為下一個聚類中心點。重複上述步驟,直至選出k個聚類中心點。可以將距離理解為一節線段,陣列dists儲存了這個線段集合的每個線段長度,而sum則是將這些線段進行了一個拼接,在這個拼接的大線段上面隨機選取一個點,那麼這個點落在較長的小線段片段上機率更大。這種做法選取的新的中心點不是距離已有的中心點最遠的點,而是偏向於較遠的點。

6、實現

下面給出了Kmeans、Kmeans++和Kmoids演算法的實現

#_*_encoding:utf-8_*_
import numpy as np
import matplotlib.pyplot as plt

class KMeans:
    def __init__(self,n_clusters=3,iterNum=200,type='kmeans',type2='random'):
        self.k=n_clusters
        self.iterNum=iterNum
        self.type=type
        self.type2=type2

    def distCal(self,x,y):                                             #計算歐式距離
        return np.sqrt(np.sum(np.power(x-y,2)))

    def randCent(self):                                                #隨機初始化聚類中心點
        self.centroids=np.mat(np.zeros((self.k,self.n)))
        randIndex=np.random.randint(0,self.m,self.k)
        for i in range(self.k):
            self.centroids[i,:]=self.train[randIndex[i],:]

    def Kmeans_plus(self):                                            #Kmeans++初始化聚類中心點
        self.centroids=np.mat(np.zeros((self.k,self.n)))
        firstCent=np.random.randint(0,self.m)
        i=0
        self.centroids[i,:]=self.train[firstCent,:]
        while i<self.k-1:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids[0:i+1,:])
            distSum=sum(self.clusterAssment[:,1])
            randNum=np.random.randint(0,distSum)
            index=0
            while randNum>0:
                randNum-=self.clusterAssment[index,1]
                index=index+1
            i=i+1
            self.centroids[i,:]=self.train[index-1,:]

    def fit_predict(self,train):                                      #學習資料,返回聚類結果
        self.train=np.mat(train)
        self.m,self.n=np.shape(self.train)

        if self.type2=='random':
            self.randCent()
        else:
            self.Kmeans_plus()

        if self.type=='kmeans':
            self.KMeans_cluster()
        else:
            self.Kmedoids_cluster()

        return np.reshape(self.clusterAssment[:,0],(1,np.shape(self.clusterAssment[:,0])[0]))

    def KMeans_cluster(self):                                        #Kmneas聚類
        iter=0
        while  iter<self.iterNum:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
            for j in range(self.k):
                clust=self.train[np.nonzero(self.clusterAssment[:,0].A==j)[0]]
                self.centroids[j,:]=np.mean(clust,axis=0)
            iter=iter+1

    def calClusterAssment(self,train,centers):                    #分配
        clusterAssment = np.mat(np.zeros((self.m, 2)))
        for i in range(self.m):
            minDist = np.inf
            minIndex = -1
            for j in range(len(centers)):
                dist = self.distCal(self.train[i, :], centers[j, :])
                if dist < minDist:
                    minDist = dist
                    minIndex = j
            clusterAssment[i, 0] = int(minIndex)
            clusterAssment[i, 1] = minDist
        return clusterAssment

    def Kmedoids_cluster(self):                                      #Kmoids聚類
        iter=0
        distSum=np.zeros((self.m,1))
        while  iter<self.iterNum:
            self.clusterAssment=self.calClusterAssment(self.train,self.centroids)
            for i in range(self.k):
                clustIndex=np.nonzero(self.clusterAssment[:,0].A==i)[0]
                for j in clustIndex:
                    distSum[j]=self.calClusterDist(self.train[clustIndex,:],self.train[j,:])

                minDist=np.inf ; minIndex=-1
                for j in clustIndex:
                    if distSum[j]<minDist:
                        minDist=distSum[j]
                        minIndex=j
                self.centroids[i,:]=self.train[minIndex,:]
            iter+=1

    def calClusterDist(self,train,center):                  #計算簇內的距離和
        dists=0
        for i in range(np.shape(train)[0]):
            dists+=self.distCal(train[i,:],center)
        return dists

    def show(self,num=0):                                    #資料顯示
        y=self.clusterAssment[:,0]
        fig=plt.figure(num)

        cValue = ['r', 'y', 'g', 'b', 'r', 'y', 'g', 'b', 'r']
        mValue = ['*', '+','s','^','D']
        for i in range(self.m):
            plt.scatter(self.train[i,1],self.train[i,2],marker=mValue[int(y[i,0])],c=cValue[int(y[i,0])])
        for i in range(self.k):
            plt.scatter(self.centroids[i,1],self.centroids[i,2],c='r',marker='D')
        plt.show()


from sklearn import datasets
iris=datasets.load_iris()
x=iris.data
x=np.mat(x)
#x=np.row_stack((x,[6,6,6,6]))
km=KMeans(n_clusters=4,iterNum=50,type='kmeans',type2='kmeans++')
km.fit_predict(x)
km.show()

  

上面採用的鳶尾花資料集加載於sklearn包中。

原始資料標籤的分佈如下圖所示:


設定K=2,選擇Kmeans演算法進行聚類,結果如下:


設定k=4進行聚類:


可以看出不同的k,得到的聚類簇大小是不一樣。在實際使用中選擇合適的k值很重要,需要根據具體需求來進行選擇(並不是K越大越好)。

選擇K值為3,Kmeans的第一次結果:


Kmeans的第二次結果:

,

從上面看到,這種隨機初始化聚類中心點,多次執行結果明顯不一樣。

下面是加入異常點使用Kmeans++初始化聚類中心點效果:


異常點在右上側,雖然不是很明顯,但是依舊可以看出,聚類中心點相對於沒有加入異常點向右發生偏移。

使用Kmedoids演算法可以減少異常點對聚類中心的影響,下面給出使用Kmedoids演算法的效果。


接下來在資料中加入一個異常點,來看看聚類中心是否發生偏移


可以看到上面兩張圖的顯示除了新加入了一個樣本點之外,聚類中心沒有發現變化。也就是說,Kmoids演算法對這種異常點並不敏感。

7、總結

    Kmeans演算法是一種高效的聚類演算法,易理解易實現,用於發現沒有給出標籤的資料內部的結構,屬於無監督學習。時間複雜度和資料的規模m,迭代次數t,聚類個數k成正比,時間複雜度為O(kmt)。但是Kmeans演算法要求資料必須是數值型資料,如果是其它型別資料則需要進行轉換。Kmeans演算法對異常點敏感,對初始聚類中心的選取敏感,因此有對應的改進演算法Kmedoids和Kmeans++演算法,這類演算法都只適合於發現球型簇,不能用於發現非凸型簇。