【機器學習實戰】 利用K-均值聚類算法對未標註數據分組

轉載請註明作者和出處: https://blog.csdn.net/weixin_37392582
代碼地址: https://gitee.com/wuweijun
開發平臺: Win10 + Python3.6 + Anaconda3
編  者: 無尾



一、前言

  聚類是一種無監督的學習,它將相似的對象歸到同一簇中。它有點像全自動分類。聚類的方法幾乎可以應用於所有對象,簇內的對象越相似,聚類的效果越好。本章要學習一種稱爲K-均值(K-means)聚類的孫發。之所以稱之爲K-均值,是因爲它可以發現k個不同的簇,且每個簇的中心採用簇中所含值的均值計算而成。
  簇識別。簇識別給出聚類結果的含義。假定有一些數據,現在將相似數據歸到一起,簇識別會告訴我們這些簇到底都是些什麼。聚類與分類的最大不同在與,分類的目標實現已知,而聚類則不一樣。不易呀其產生的結果與分類相同,而只是類別沒有預先定義,聚類有時也被稱爲無監督分類(unsupervised)。
    

二、K-均值聚類算法

  優點:容易實現
  缺點:可能收斂到局部最小值,在大規模數據集上收斂較慢
  適用數據類型:數值型數據

1、工作流程  

  • 首先隨機確定k個初始點作爲質心
  • 爲每個點找到距其最近的質心,並將其分配給該質心所對應的簇(打標籤)
  • 將每個質心更新爲該簇所有點的平均值
  • 重複前面兩個步驟,知道所有點的簇類不再變化,即確定的質心爲最優,即可完成聚類。

2、僞代碼

創建k個點作爲起始質心(通常是隨機選擇樣本點)
當任意一個點的簇類(簇分配結果)發生改變時
    對數據集中的每個數據點
        對每個質心
            計算質心與數據點之間的距離
        將數據點分配到距其最近的簇
    對每一個簇,計算簇中所有點的均值並將均值作爲質心

3、流程圖

Created with Raphaël 2.1.2開始聚類隨機確定k個初始點作爲質心當任意一個點的簇類發生改變時對點尋找最近質心,確定簇類更新質心爲該簇所有點的平均值結束yesno

4、核心代碼解釋

(1)euclidentDistance

  計算了各樣本點到各質心距離,用距離差平方和表示,也稱誤差平方和。在主函數中,樣本點會根據距離選取最新的質心作爲簇類。

def euclidentDistance(vector1, vector2): #歐氏距離 Euclidean
    """
    Function: 歐式距離計算
    Input:兩個座標(x0,x1),(x2, x3)
    Output: 兩點的歐式距離
    """
    return np.sqrt(np.sum(np.power(vector2 - vector1,2)))

(2)initCentroids

  在樣本點中,隨機獲取k個樣本作爲初始質心
  random.uniform(x1,x2) _在[x1,x2)內隨機選取一個符合均勻分佈的浮點數

def initCentroids(data, k):
    """
    Function: 隨機獲取k個質心
    Input:數據集,質心數量
    Output:k個質心的集合
    """
    numSamples, dim = data.shape
    centroids = np.zeros((k,dim))
    for i in range(k):
        #
        index = int(np.random.uniform(0, numSamples))
        centroids[i,:] = data[index,:]
    return centroids

(3)kmenas

   程序的主函數,通過迭代(劃分樣本-更新質心),實現聚類

def kmeans(dataSet, k):
    """
    Function:通過迭代,確定質心和樣本類別
    Input   :數據集,質心數量
    Output  :質心座標 + [Ki,distance^2]
    """
    """
    僞代碼:
        當質心還未更新完畢(各樣本點的簇類值不再變化):
            1對每一個樣本:
                對每一個質心:
                    計算樣本到質心的距離
                    選取得到最小距離值時的質心,作爲該樣本的簇類
                判斷該簇類是否是更新值,如果是:
                    將該樣本的簇類和平方誤差進行保存
            2由已分好的簇類中取均值,重新確立質心
        輸出:質心 + 樣本的簇類標籤、           
    """
    numSamples = dataSet.shape[0] 
    #獲取樣本數 
    clusterAssment = np.mat((np.zeros((numSamples,2))))
    clusterChanged = True
    #初始化k個質心
    centroids = initCentroids(dataSet, k)
    #當聚類不在變化時,停止循環
    while clusterChanged:
        clusterChanged = False
        #遍歷所有樣本
        for i in range(numSamples):
            #對所有質心點,計算樣本點到質心的距離,找到樣本點到
            #一質心的最小距離,並取對該樣本點進行劃分
            minDist = float('inf')
            minIndex = 0
            for j in range(k):
                #計算點到質心的距離
                distance = euclidentDistance(centroids[j,:], dataSet[i,:])
                #取距點最近的質心爲當前的簇類2
                if distance < minDist:
                    minDist = distance
                    minIndex = j
            #更新clusterChanged爲True,將簇類和距離的平方保存
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
                clusterAssment[i,:] = minIndex, minDist**2
        #更新質心
        for j in range(k):
            pointsInCluster = dataSet[np.nonzero(clusterAssment[:,0] == j)[0]]
            centroids[j,:] = np.mean(pointsInCluster, axis = 0)
    print('完成聚類算法')
    return centroids, clusterAssment

三、二分K-均值算法

  在K-均值聚類中,點的簇分配結果值沒有那麼準確,K-均值算法收斂但是聚類效果較差。因爲K-均值算法收斂到了局部最小值,而非全局最小值。因爲初始的質心點是隨機選取的,在對質心點優化過程的迭代,也是基於初始質心的位置而進行優化的,這樣的優化結果只是這一局部的最優解,而非全局的。
  那麼,我們引入一種用於度量聚類效果的指標:SSE(Sum of Squared Error,誤差平方和)。SSE對應函數euclidentDistance的返回結果,SSE值越小,表明數據點越接近於它們的質心,質心位於數據點的中央,聚類效果也越好。因爲我們對誤差(點到質心的距離)取了平方,因此更加重視那些遠離中心的點。
 因此,我們在每次劃分簇的時候,對所有的劃分結果用SSE進行比較,選取SSE值最小的劃分方式進行聚類。這樣避免了本應該劃分的簇沒有被劃分,而點很集中的簇卻被劃分了的情況。
  

1、工作流程  

  • 首先將所有點作爲一個簇,然後將該簇一分爲二
  • 之後選取其中一個簇繼續劃分,劃分哪一個簇取決於其劃分是否可以最大程度降低SSE值
  • 上述基於SSE的劃分過程不斷重複,直到得到指定的簇數目爲止。

2、僞代碼

將所有的點看成一個簇
當簇數目小於k時
    對每一個簇
        在給定的簇上面進行K-均值聚類(k=2)
        計算將該簇一分爲二之後的總誤差(被劃分的簇+沒有被選擇劃分的簇)
    選擇使得誤差SSE最小的那個簇的劃分操作執行

3、流程圖

Created with Raphaël 2.1.2開始聚類,首先把左右點當作一個簇已劃分的簇數量小於指定數量對當前已有的每一個簇,進行K-均值劃分爲兩個簇比較幾個劃分方式的總SSE值,選取使SEE爲最小的劃分方式更新質心數目結束yesno

4、核心代碼解釋

def biKmeans(dataSet, k):
    """
    Function:不斷二次劃分最小誤差數據集,得到質點和分類
    Input   :數據集,質心數量
    Output  :質心座標 + [Ki,distance^2]
    """
    """
    僞代碼:
        初始化質心爲所有樣本點的均值
        計算樣本點到初始質心的歐式距離,clusterAssment
        當確定的質心數量不足k個時:
            分類標籤索引爲質心的數量- len(centList)
            遍歷每個質心:
                得到質心類別對應的數據樣本
                將這些樣本劃分成2類,得到新的質心點和clusterAssment
                計算總體誤差 - 參與劃分的樣本誤差和沒參與劃分的樣本誤差之和
                當,當前誤差值爲最小誤差時:
                    最佳分類標籤(bestCentroidSplit)是當前的質心索引
                    最佳質心點(bestNewCentroids)是當前對應的質心點
                    bestClusterAssment爲當前對應的clusterAssment
            選擇一個類別的樣本繼續劃分,類別爲 len(cenList),另外一個樣本標籤爲bestCentroidToSplit
            更新質心數組,最後一個質點更新爲‘kmeans’中‘0’對應的質點,然後append進一個最新的質點

    """
    m = np.shape(dataSet)[0]
    clusterAssment = np.mat(np.zeros((m,2)))

    #初始質心,爲所有樣本點的均值
    centroid = np.mean(dataSet, axis = 0).tolist()[0] 
    centList = [centroid]
    #計算樣本點到質心的距離
    for i in range(m):
        clusterAssment[i,1] = euclidentDistance(np.mat(centroid), dataSet[i,:]) ** 2

    #迭代收斂,分裂出新的質心,退出條件是分裂出的質心數量達到k
    while len(centList) < k:
        #定義誤差值
        minSSE = float('inf') 
        #看上去是定義長度, 實則爲每個質心打標籤(0,1,2……)
        numCurrCluster = len(centList)
        #判斷哪個質心好
        for i in range(numCurrCluster):
            #得到屬於該質心的數據
            pointsInCurrCluster = dataSet[np.nonzero(clusterAssment[:,0] == i)[0],:]
            #得到劃分成2類的【質心,分類+誤差】
            centroids, splitClusterAssment = kmeans(pointsInCurrCluster, 2)
            #計算質心劃分後的誤差
            splitSSE = sum(splitClusterAssment[:,1])
            #計算沒有參與劃分的誤差
            notSplitSSE = sum(clusterAssment[np.nonzero(clusterAssment[:,0] != i)[0],1]) 
            currSplitSSE = splitSSE + notSplitSSE
            #尋找最小的誤差
            if currSplitSSE < minSSE:
                minSSE = currSplitSSE
                bestCentroidToSplit = i
                bestNewCentroids = centroids.copy()
                bestClusterAssment = splitClusterAssment.copy()\
        #修改集羣索引以添加新集羣, 類別1返回該質心原來的值, 0 更新爲選擇誤差最小的質心再進行劃分
        #給予新的分類新的標籤,另外一個分類標籤沿用原來的
        bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 1)[0],[0]] = numCurrCluster
        bestClusterAssment[np.nonzero(bestClusterAssment[:,0] == 0)[0],[0]] = bestCentroidToSplit

        #更新和添加新的2子簇的質心 
        centList[bestCentroidToSplit] = bestNewCentroids[0,:]
        centList.append(bestNewCentroids[1,:])

        #更新集羣被更改的樣本的索引和錯誤 - 在kmens中被貼上‘0’分類,在此處進行恢復 
        clusterAssment[np.nonzero(clusterAssment[:,0] == bestCentroidToSplit), :] = bestClusterAssment
    print('二分k-均值算法完成')
    return np.mat(centList), clusterAssment
發佈了34 篇原創文章 · 獲贊 23 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章