二分K-均值聚類算法


 二分k均值聚類是k均值聚類的增強版:為克服K-均值算法收斂於局部最小值的問題,有人提出了另一個稱為二分K-均值(bisecting K-means)的算法。

#K-means聚類
from numpy import *
import matplotlib.pyplot as plt


plt.ion()   #開啟交互模式,實時繪制
plt.subplots()
plt.xlim(-6, 6)
plt.ylim(-6, 6)
#plt.pause(5)
def loadDataSet(fileName):
    dataMat = []
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float,curLine))
        dataMat.append(fltLine)
    return dataMat

def distEclud(vecA, vecB):
    #print("vecA:",vecA,"vecB:",vecB)
    return sqrt(sum(power(vecA-vecB, 2)))   #歐式距離

#給定數據集構建一個包含k個隨機質心的集合
#該函數為給定數據集構建一個包含k個隨機質心的集合
#集合中的值位於min合max之間
def randCent(dataSet, k):
    n = shape(dataSet)[1]
    centroids = mat(zeros((k,n)))
    for j in range(n):
        minJ = min(dataSet[:,j])
        rangeJ = float(max(dataSet[:,j]) - minJ)
        centroids[:,j] = minJ + rangeJ*random.rand(k,1)
    return centroids

'''
k-均值聚類算法:
該算法會創建k個質心,然后將每個點分配到最近的質心。這個過程重復數次,直到數據點的簇分配結果不再改變為止。
這個接口的缺點是:質心是隨機初始化的,雖然最后會通過划分后的點加均值函數重新計算,但質心沒有真正的進行最優化收斂
k均值算法收斂(kMeans函數)到了局部最小值,而非全局最小值

一種用於度量聚類效果的指標是SSE(Sum of Squared Error,誤差平方和),SSE值越小表示數據點越接近於它們的質心,聚類
效果也就越好。因為對誤差取了平方,因此更加重視那些遠離中心的點,一種肯定可以降低SSE值的方法是怎加簇的個數,
但這違背了聚類的目標。聚類的目標是在保持簇數目不變的情況下提高簇的質量。
'''
def kMeans(dataSet, k, distMeans=distEclud, createCent=randCent):
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m, 2)))     #記錄每個樣本點到質心的最小距離,及最小距離質心的索引
    centroids = createCent(dataSet, k)
    centroids_mean_before=centroids
    plt_scatter(dataSet,centroids,clusterAssment)
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):      #樣本數據按行遍歷
            minDist = inf; minIndex = -1     #inf 表示正無窮
            for j in range(k):      #遍歷質心,尋找最近質心
                distJI = distMeans(centroids[j,:], dataSet[i,:])    #計算i點到j質心的距離
                if distJI < minDist:
                    minDist = distJI; minIndex=j    #計算出每個樣本數據最近的質心點與距離
            if clusterAssment[i,0] != minIndex:    clusterChanged = True
            clusterAssment[i,:] = minIndex,minDist**2
        plt_scatter(dataSet,centroids_mean_before,clusterAssment)
        for cent in range(k):   #遍歷所有的質心並更新它們的取值
            ptsInClust = dataSet[nonzero(clusterAssment[:,0].A == cent)[0]]
            centroids[cent,:] = mean(ptsInClust, axis=0)
            centroids_mean_before = centroids
        plt_scatter(dataSet,centroids,clusterAssment)
    return centroids, clusterAssment


#二分K-均值算法
'''
為了克服k-均值算法收斂於局部最小值的問題,有人提出了另一個稱為二分K-均值的算法。
二分k-均值:首先將所有點作為一個簇,然后將該簇一分為二。之后選擇其中一個簇繼續進行划分,
選擇哪個簇繼續划分取決於對其划分是否可以最大程度降低SSE的值。上述基於SSE的划分過程不斷重復,
直到得到用戶指定的簇數目為止。
'''
def biKmeans(dataSet, k, distMeas=distEclud):
    m = shape(dataSet)[0]
    clusterAssment = mat(zeros((m,2)))  #創建一個m*2的零矩陣
    #print('clusterAssment:',clusterAssment)
    centroid0 = mean(dataSet, axis=0).tolist()[0]       #tolist() 把mat轉為list
    centList = [centroid0]
    plt_scatter(dataSet, mat(centList), clusterAssment)
    #print('centList:', centList)
    for j in range(m):
        clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2    #記錄每個點到質心的距離的平方
    #print('clusterAssment:',clusterAssment)
    while (len(centList) < k):
        lowestSSE = inf     #正無窮
        for i in range(len(centList)):
            #以clusterAssment中第一列索引為i的行為索引,找到這些索引對應dataSet的的值返回。通俗講 就是找出第i個質心關聯的點
            ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]
            centroidMat,splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
            sseSplit = sum(splitClustAss[:,1])
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])  #求划分前其余(除i質心之外的)質心的sse值
            print("sseSplit, and notSplit:",sseSplit, sseNotSplit)
            if (sseSplit + sseNotSplit) < lowestSSE:
                bestCentToSplit = i
                bestNewCents = centroidMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit      #所有質心(被分割的質心加沒被分割的質心)距離的平方
        #更新bestClustAss簇的分配結果
        bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
        bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
        print("the bestCentToSplit is:",bestCentToSplit)
        print("the len of bestClustAss is:", len(bestClustAss))
        #將bestCentTosplit(最適合分割的質心點),替換為分割后的第0個質心點,分割后生成的另一個質心點通過 append方法添加
        centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]   #python3修改
        centList.append(bestNewCents[1,:].tolist()[0])
        #將被分割的質心cluter索引和最小距離平方(minDist**2) 重新賦值
        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss
        plt_scatter(dataSet, mat(centList), clusterAssment)
    return mat(centList),clusterAssment    

#繪圖接口
def plt_scatter(datMat,datCent,cluster):
    plt.clf()     # 清空畫布
    plt.xlim(-6, 6)      # 因為清空了畫布,所以要重新設置坐標軸的范圍
    plt.ylim(-6, 6)
    m = shape(datMat)[0]
    n = shape(datCent)[0]
    ms=['^','v','o','s','p','<','>','1','2','3']    #數據點形狀
    cs=['r','g','b','y','m','c','k','r','g','b']    #數據點的顏色
    
    for i in range(m):
        k=int(cluster[i,0])
        plt.scatter(datMat[i,0],datMat[i,1],c=cs[k],marker=ms[k],alpha=0.5)
    for j in range(n):
        plt.scatter(datCent[j,0],datCent[j,1],c=cs[j],marker='x',alpha=1.0)
    plt.pause(0.3)
    


if __name__ == '__main__':
    datMat = mat(loadDataSet('testSet.txt'))
    #myCentroids,clusterAssing = kMeans(datMat, 4)
    #plt_scatter(datMat,myCentroids,clusterAssing)
    
    centList,myNewAssments=biKmeans(datMat,4)
    plt_scatter(datMat,centList,myNewAssments)

    plt.ioff()
    plt.show()

 

鏈接:https://pan.baidu.com/s/1E-x93BbFhZ32CnucPpHOWw
提取碼:abt2

網盤中數據為“testSet.txt”數據和聚類的動態視頻

 


免責聲明!

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



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