吳恩達機器學習筆記(七) —— K-means算法


 

主要內容:

一.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比較合適。

但是,這種“手肘”情況並不常見,更一般的情況是:

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM