算法原理
由於傳統的KMeans算法的聚類結果易受到初始聚類中心點選擇的影響,因此在傳統的KMeans算法的基礎上進行算法改進,對初始中心點選取比較嚴格,各中心點的距離較遠,這就避免了初始聚類中心會選到一個類上,一定程度上克服了算法陷入局部最優狀態。
二分KMeans(Bisecting KMeans)算法的主要思想是:首先將所有點作為一個簇,然后將該簇一分為二。之后選擇能最大限度降低聚類代價函數(也就是誤差平方和)的簇划分為兩個簇。以此進行下去,直到簇的數目等於用戶給定的數目k為止。以上隱含的一個原則就是:因為聚類的誤差平方和能夠衡量聚類性能,該值越小表示數據點越接近於他們的質心,聚類效果就越好。所以我們就需要對誤差平方和最大的簇進行再一次划分,因為誤差平方和越大,表示該簇聚類效果越不好,越有可能是多個簇被當成了一個簇,所以我們首先需要對這個簇進行划分。
代碼實現
本文在實現過程中采用數據集4k2_far.txt,聚類算法實現過程中默認的類別數量為4。其中輔助函數存於myUtil.py文件和K均值核心函數存於kmeans.py文件,具體參考《KMeans (K均值)算法講解及實現》。
二分K均值主函數邏輯思想如下代碼所示:
# -*- encoding:utf-8 -*- from kmeans import * import matplotlib.pyplot as plt dataMat = file2matrix("testData/4k2_far.txt", "\t") # 從文件構建的數據集 dataSet = dataMat[:, 1:] # 提取數據集中的特征列 k = 4 # 外部指定1,2,3...通過觀察數據集有4個聚類中心 m = shape(dataSet)[0] # 返回矩陣的行數 # 初始化第一個聚類中心: 每一列的均值 centroid0 = mean(dataSet, axis=0).tolist()[0] centList =[centroid0] # 把均值聚類中心加入中心表中 # 初始化聚類距離表,距離方差 # 列1:數據集對應的聚類中心,列2:數據集行向量到聚類中心距離的平方 ClustDist = mat(zeros((m, 2))) for j in range(m): ClustDist[j,1] = distEclud(centroid0,dataSet[j,:])**2 ''' color_cluster(ClustDist[:, 0:1], dataSet, plt) drawScatter(plt, mat(centList), size=60, color='red', mrkr='D') plt.show() ''' # 依次生成k個聚類中心 while (len(centList) < k): lowestSSE = inf # 初始化最小誤差平方和。核心參數,這個值越小就說明聚類的效果越好。 # 遍歷cenList的每個向量 #----1. 使用ClustDist計算lowestSSE,以此確定:bestCentToSplit、bestNewCents、bestClustAss----# for i in xrange(len(centList)): # 從dataSet中提取類別號為i的數據構成一個新數據集 ptsInCurrCluster = dataSet[nonzero(ClustDist[:, 0].A == i)[0], :] # 應用標准kMeans算法(k=2),將ptsInCurrCluster划分出兩個聚類中心,以及對應的聚類距離表 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2) # 計算splitClustAss的距離平方和 sseSplit = sum(multiply(splitClustAss[:, 1], splitClustAss[:, 1])) # 此處求歐式距離的平方和 # 計算ClustDist[ClustDist第1列!=i的距離平方和 sseNotSplit = sum(ClustDist[nonzero(ClustDist[:, 0].A != i)[0], 1]) if (sseSplit + sseNotSplit) < lowestSSE: # 算法公式: lowestSSE = sseSplit + sseNotSplit bestCentToSplit = i # 確定聚類中心的最優分隔點 bestNewCents = centroidMat # 用新的聚類中心更新最優聚類中心 bestClustAss = splitClustAss.copy() # 深拷貝聚類距離表為最優聚類距離表 lowestSSE = sseSplit + sseNotSplit # 更新lowestSSE # 回到外循環 # ----2. 計算新的ClustDist----# # 計算bestClustAss 分了兩部分: # 第一部分為bestClustAss[bIndx0,0]賦值為聚類中心的索引 bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList) # 第二部分 用最優分隔點的指定聚類中心索引 bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSplit # 以上為計算bestClustAss # ----3. 用最優分隔點來重構聚類中心----# # 覆蓋: bestNewCents[0,:].tolist()[0]附加到原有聚類中心的bestCentToSplit位置 # 增加: 聚類中心增加一個新的bestNewCents[1,:].tolist()[0]向量 centList[bestCentToSplit] = bestNewCents[0, :].tolist()[0] centList.append(bestNewCents[1, :].tolist()[0]) # 以上為計算centList # 將bestCentToSplit所對應的類重新更新類別 ClustDist[nonzero(ClustDist[:, 0].A == bestCentToSplit)[0], :] = bestClustAss ''' color_cluster(ClustDist[:, 0:1], dataSet, plt) drawScatter(plt, mat(centList), size=60, color='red', mrkr='D') plt.show() ''' # 輸出生成的ClustDist:對應的聚類中心(列1),到聚類中心的距離(列2),行與dataSet一一對應 color_cluster(ClustDist[:, 0:1], dataSet, plt) print "cenList:",mat(centList) # 繪制聚類中心圖形 drawScatter(plt, mat(centList), size=60, color='red', mrkr='D') plt.show()
評估分類結果
上述代碼的”’注釋部分給出了每次迭代時,聚類中心的變化情況,如下所示:
二分K均值聚類中心變化情況
相關