K-means原理及Python實現

K-means方法是一種非監督學習的算法,它解決的是聚類問題。

1、算法簡介:K-means方法是聚類中的經典算法,數據挖掘十大經典算法之一;算法接受參數k,然後將事先輸入的n個數據對象劃分爲k個聚類以便使得所獲得的聚類滿足聚類中的對象相似度較高,而不同聚類中的對象相似度較小。

2、算法思想:以空間中k個點爲中心進行聚類,對最靠近他們的對象歸類,通過迭代的方法,逐次更新各聚類中心的值,直到得到最好的聚類結果。

3、算法描述:

(1)適當選擇c個類的初始中心; 
(2)在第k次迭代中,對任意一個樣本,求其到c各中心的距離,將該樣本歸到距離最短的那個中心所在的類; 
(3)利用均值等方法更新該類的中心值; 
(4)對於所有的C個聚類中心,如果利用(2)(3)的迭代法更新後,值保持不變,則迭代結束;否則繼續迭代。

4、算法舉例:

我們假設藥物A、B、C、D有兩個特徵值,分別是藥物重量以及PH值。

藥物名稱 藥物重量  藥物PH值
 A 1 1
B 2 1
C 4 3
D 5 4


現在我們要對這四個藥物進行聚類,已知我們要分成兩類,那麼我們該怎麼做呢?

首先我們把上面的數據畫到二位座標系當中A(1,1),B(2,1),C(4,3),D(5,4):


初始時,我們先假設藥物A爲聚類1的中心點,B爲聚類2的中心點,那麼初始時的中心座標分別爲c1=(1,1),c2=(2,1),矩陣D的第一行代表各個點到中心點c1c1的距離,第二行代表各個點到中心點c2c2的距離;那麼初始矩陣D0D0表示成如下: 

D_{0}=\begin{bmatrix} 0 ,1 ,3.61,5 & & & \\1,0,2.83,4.24 & & & \end{bmatrix}
矩陣GG代表樣本應該歸屬於哪個聚類,第一行代表各個點是否屬於中心c1c1所在的類(0代表不在,1代表在),第二行代表各個點是否屬於中心c2c2所在的類(0代表不在,1代表在);那麼此時G0G0表示成如下: 
G_{0}=\begin{bmatrix} 1,0,0,0 & & & \\ 0,1,1,1 & & & \end{bmatrix}

由矩陣G0G0可知A藥物屬於一個類,B、C、D屬於一類;
然後,利用均值等方法更新該類的中心值。 
c1=(1,1)

c2=((2+4+5)/3,(1+3+4)/3)=(13/3,8/3)

上圖是更新後的座標圖,對應的中心點也發生了變化。

因爲中心點跟上次不一樣了,所以我們又可以對樣本點進行重新劃分。劃分的方法還是跟以前一模一樣,我們先計算出矩陣D1表示成如下: 

D_{1}=\begin{bmatrix} 0,1,3.61,5 & & & \\ 3.14,2.36,0.47,1.89 & & & \end{bmatrix}
此時G1表示成如下: 
G_{1}=\begin{bmatrix} 1,1,0,0 & & & \\ 0,0,1,1 & & & \end{bmatrix}

由矩陣G1可知A、B藥物屬於一個類,C、D屬於一類;
然後,利用均值等方法再次更新該類的中心值。 
c1=((1+2)/2,(1+1)/2)=(1.5,1)

c2=((4+5)/2,(3+4)/2)=(4.5,3.5)

 

上圖是更新後的座標圖,對應的中心點也發生了變化。

因爲中心點跟上次不一樣了,所以我們又可以對樣本點進行重新劃分。劃分的方法還是跟以前一模一樣,我們先計算出矩陣D2表示成如下: 
D_{2}=\begin{bmatrix} 0.5, 0.5,3.20,4.61 & & & \\ 4.30,3.54,0.71,0.71 & & & \end{bmatrix}
此時G2表示成如下:

 G_{1}=\begin{bmatrix} 1,1,0,0 & & & \\ 0,0,1,1 & & & \end{bmatrix}

由矩陣G2可知A、B藥物屬於一個類,C、D屬於一類;
然後,利用均值等方法再次更新該類的中心值。 

c1=((1+2)/2,(1+1)/2)=(1.5,1)

c2=((4+5)/2,(3+4)/2)=(4.5,3.5)

因爲對應的中心點並沒有發生變化,所以迭代停止,計算完畢。

本算法的時間複雜度:O(tkmn),其中,t爲迭代次數,k爲簇的數目,m爲記錄數,n爲維數;

空間複雜度:O((m+k)n),其中,k爲簇的數目,m爲記錄數,n爲維數。

適用範圍:

K-menas算法試圖找到使平凡誤差準則函數最小的簇。當潛在的簇形狀是凸面的,簇與簇之間區別較明顯,且簇大小相近時,其聚類結果較理想。前面提到,該算法時間複雜度爲O(tkmn),與樣本數量線性相關,所以,對於處理大數據集合,該算法非常高效,且伸縮性較好。但該算法除了要事先確定簇數K和對初始聚類中心敏感外,經常以局部最優結束,同時對“噪聲”和孤立點敏感,並且該方法不適於發現非凸面形狀的簇或大小差別很大的簇。

缺點:

1、聚類中心的個數K 需要事先給定,但在實際中這個 K 值的選定是非常難以估計的,很多時候,事先並不知道給定的數據集應該分成多少個類別才最合適; 
2、Kmeans需要人爲地確定初始聚類中心,不同的初始聚類中心可能導致完全不同的聚類結果。(可以使用K-means++算法來解決)
 

Python代碼實現

# -*- coding:utf-8 -*-
from numpy import *
def loadDataSet(filename):
    dataMat = []
    fr = open(filename)
    for line in fr.readlines():
        curline = line.strip().split('\t')
        fltline = map(float,curline)
        dataMat.append(fltline)
    return dataMat

#計算兩個向量的距離,歐式距離
def distE(vecA,vecB):
    return sqrt(sum(power(vecA-vecB,2)))

#隨機選擇中心點
def randCent(dataSet,k):
    n = shape(dataSet)[1]
    centriods = mat(zeros((k,n)))
    for j in range(n):
        minJ = min(dataSet[:,j])
        rangeJ = float(max(array(dataSet)[:,j])-minJ)
        centriods[:,j] = minJ+rangeJ*random.rand(k,j)
    return centriods

def kmeans(dataSet,k,disMea=distE,createCent=randCent):
    m = shape(dataSet)[0]
    clusterA= mat(zeros((m,2)))
    centriods = createCent(dataSet,k)
    clusterC=True
    while clusterC:
        clusterC=False
        for i in range(m):
            minDist = inf
            minIndex = -1
            for j in range(k):
                distJI = disMea(centriods[j,:],dataSet[i,:])
                if distJI<minDist:
                    minDist=distJI;minIndex=j
            if clusterA[i,0] != minIndex:
                clusterC = True

            clusterA[i,:] = minIndex,minDist**2
        print(centriods)

        for cent in range(k):
            ptsInClust = dataSet[nonzero(clusterA[:, 0].A == cent)[0]]  # get all the point in this cluster
            centriods[cent, :] = mean(ptsInClust, axis=0)  # assign centroid to mean
    return centriods,clusterA

def show(dataSet, k, centriods, clusterA):
     import matplotlib.pyplot as plt
     numSamples, dim = dataSet.shape()
     mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
     for i in range(numSamples):
         markIndex = int(clusterA[i, 0])
         plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex])

     mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb']
     for i in range(k):
         plt.plot(centriods[i, 0], centriods[i, 1], mark[i], markersize=12)
     plt.show()

def main():
    dataMat = mat(loadDataSet('testSet.txt'))
    myCentroids, clustAssing = kmeans(dataMat, 4)
    print(myCentroids)
    show(dataMat, 4, myCentroids, clustAssing)

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