主要內容:
一.K-means算法簡介
二.算法過程
三.隨機初始化
四.二分K-means
四.K的選擇
一.K-means算法簡介
1.K-means算法是一種無監督學習算法。所謂無監督式學習,就是輸入樣本中只有x,沒有y,即只有特征,而沒有標簽,通過這些特征對數據進行整合等操作。而更細化一點地說,K-means算法屬於聚類算法。所謂聚類算法,就是根據特征上的相似性,把數據聚集在一起,或者說分成幾類。
2.K-means算法作為聚類算法的一種,其工作自然也是“將數據分成幾類”,其基本思路是:
1) 首先選擇好將數據分成k類,然后隨機初始化k個點作為中心點。
2) 對於每一個數據點,選取與之距離最近的中心點作為自己的類別。
3) 當所有數據點都歸類完畢后,調整中心點:把中心點重新設置為該類別中所有數據點的中心位置,每一軸都設置為平均值。(所以稱為means)
4) 重復以上2)~3)步驟直至數據點的類別不再發生變化。
3.K-means算法從感性上去理解,就是把一堆靠得近的點歸到同一個類別中。
二.算法過程
1.一些變量的約定:μ(i)表示第i個中心點,c(i)表示第i個數據點歸到哪個中心點。
2.K-means算法的本質就是:移動中心點,使其漸漸地靠近數據的“中心”,即最小化數據點與中心點的距離。即:

3.算法流程:

4.Python代碼如下:
1 # coding:utf-8 2 3 from numpy import * 4 5 def distEclud(vecA, vecB): #計算歐式距離 6 return sqrt(sum(power(vecA - vecB, 2))) # la.norm(vecA-vecB) 7 8 def randCent(dataSet, k): # 初始化k個隨機簇心 9 n = shape(dataSet)[1] #特征個數 10 centroids = mat(zeros((k, n))) # 簇心矩陣k*n 11 for j in range(n): #特征逐個逐個地分配給這k個簇心。每個特征的取值需要設置在數據集的范圍內 12 minJ = min(dataSet[:, j]) #數據集中該特征的最小值 13 rangeJ = float(max(dataSet[:, j]) - minJ) #數據集中該特征的跨度 14 centroids[:, j] = mat(minJ + rangeJ * random.rand(k, 1)) #為k個簇心分配第j個特征,范圍需限定在數據集內。 15 return centroids #返回k個簇心 16 17 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): 18 m = shape(dataSet)[0] #數據個數 19 clusterAssment = mat(zeros((m, 2))) # 記錄每個數據點被分配到的簇,以及到簇心的距離 20 centroids = createCent(dataSet, k) # 初始化k個隨機簇心 21 clusterChanged = True # 記錄一輪中是否有數據點的歸屬出現變化,如果沒有則算法結束 22 while clusterChanged: 23 clusterChanged = False 24 for i in range(m): # 枚舉每個數據點,重新分配其簇歸屬 25 minDist = inf; minIndex = -1 #記錄最近簇心及其距離 26 for j in range(k): #枚舉每個簇心 27 distJI = distMeas(centroids[j, :], dataSet[i, :]) #計算數據點與簇心的距離 28 if distJI < minDist: #更新最近簇心 29 minDist = distJI; minIndex = j 30 if clusterAssment[i, 0] != minIndex: clusterChanged = True #更新“變化”記錄 31 clusterAssment[i, :] = minIndex, minDist ** 2 #更新數據點的簇歸屬 32 print centroids 33 for cent in range(k): #枚舉每個簇心,更新其位置 34 ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]] # 得到該簇所有的數據點 35 centroids[cent, :] = mean(ptsInClust, axis=0) # 將數據點的均值作為簇心的位置 36 return centroids, clusterAssment # 返回簇心及每個數據點的簇歸屬
三.隨機初始化
由於初始化的中心點對於最后的分類結果影響很大,因而很容易出現:當初始化的中心點不同時,其結果可能千差萬別:

因此,為了分類結果更加合理,我們可以多次初始化中心點,即多次運行K-means算法,然后取其中J(c1,c2……,μ1,μ2……)最小的分類結果。

四.二分K-means
1.為了克服K-means算法收斂域局部最小值的問題(緣因對初始簇心的位置敏感),二分k-means出現了。該算法首先將所有點歸於一個簇,然后將其一分為二。之后選擇其中一個簇繼續一分為二。選擇的依據就是:該簇的划分是否可以最大程度降低SSE(誤差平方和)的值。上述基於SSE的划分過程不斷重復,直至簇數達到k為止。
2.偽代碼如下:


3.Python代碼如下:
1 '''二分K均值''' 2 def biKmeans(dataSet, k, distMeas=distEclud): 3 m = shape(dataSet)[0] 4 centroid0 = mean(dataSet, axis=0).tolist()[0] #創建初始簇心,標號為0 5 centList = [centroid0] # 創建簇心列表 6 clusterAssment = mat(zeros((m, 2))) #初始化所有數據點的簇歸屬(為0) 7 for j in range(m): # 計算所有數據點與簇心0的距離 8 clusterAssment[j, 1] = distMeas(mat(centroid0), dataSet[j, :]) ** 2 9 '''''''''''' 10 while (len(centList) < k): #分裂k-1次,形成k個簇 11 lowestSSE = inf #初始化最小sse為無限大 12 for i in range(len(centList)): #枚舉已有的簇,嘗試將其一分為二 13 ptsInCurrCluster = dataSet[nonzero(clusterAssment[:, 0].A == i)[0],:] #將該簇的數據點提取出來 14 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) #利用普通k均值將其一分為二 15 sseSplit = sum(splitClustAss[:, 1]) # 計算划分后該簇的SSE 16 sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1]) #計算該簇之外的數據點的SSE 17 print "sseSplit, and notSplit: ", sseSplit, sseNotSplit 18 if (sseSplit + sseNotSplit) < lowestSSE: #更新最小總SSE下的划分簇及相關信息 19 bestCentToSplit = i #被划分的簇 20 bestNewCents = centroidMat #划分后的兩個簇心 21 bestClustAss = splitClustAss.copy() #划分后簇內數據點的歸屬及到新簇心的距離 22 lowestSSE = sseSplit + sseNotSplit #更新最小總SSE 23 '''''''''''' 24 print 'the bestCentToSplit is: ', bestCentToSplit 25 print 'the len of bestClustAss is: ', len(bestClustAss) 26 centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0] # 一個新簇心的標號為舊簇心的標號,所以將其取代就簇心的位置 27 centList.append(bestNewCents[1, :].tolist()[0]) # 另一個新簇心加入到簇心列表的尾部,標號重新起 28 bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList) #更新舊簇內數據點的標號 29 bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit #同上 30 clusterAssment[nonzero(clusterAssment[:, 0].A == bestCentToSplit)[0],:] = bestClustAss # 將更新的簇歸屬統計到總數據上 31 return mat(centList), clusterAssment
四.K的選擇
最后一個問題:既然是K-means,那么這個k應該取多大呢?
一.Elbow method:
假設隨着k的增大,cost function j的大小呈現以下的形狀:

可以看到,當k=3時,J已經很小了,且再增大k也不能大大地減小J。說明此時k選取3比較合適。
但是,這種“手肘”情況並不常見,更一般的情況是:

此時根本看不出哪里才是“手肘”,所以對此的策略是:實踐調研,按實際需求的而定。
