機器學習:Python實現聚類算法(一)之K-Means


1.簡介

       K-means算法是最為經典的基於划分的聚類方法,是十大經典數據挖掘算法之一。K-means算法的基本思想是:以空間中k個點為中心進行聚類,對最靠近他們的對象歸類。通過迭代的方法,逐次更新各聚類中心的值,直至得到最好的聚類結果。

2. 算法大致流程為:

      1)隨機選取k個點作為種子點(這k個點不一定屬於數據集)

      2)分別計算每個數據點到k個種子點的距離,離哪個種子點最近,就屬於哪類

      3)重新計算k個種子點的坐標(簡單常用的方法是求坐標值的平均值作為新的坐標值)

      4)重復2、3步,直到種子點坐標不變或者循環次數完成

3.完整計算過程

     1)設置實驗數據

        運行之后,效果如下圖所示:

       在圖中,ABCDE五個點是待分類點,k1、k2是兩個種子點。

    2)計算ABCDE五個點到k1、k2的距離,離哪個點近,就屬於哪個點,進行初步分類。

      結果如圖:

         A、B屬於k1,C、D、E屬於k2

      3)重新計算k1、k2的坐標。這里使用簡單的坐標的平均值,使用其他算法也可以(例如以下三個公式)

        PS:公式的圖片莫名其妙被屏蔽了,由於沒有留備份,找不到原來的圖片了。所以這里只寫個名字,方便大家做個了解或者搜索的關鍵字

          a)Minkowski Distance公式——λ可以隨意取值,可以是負數,也可以是正數,或是無窮大。               

         b)Euclidean Distance公式——也就是第一個公式λ=2的情況             

        c)CityBlock Distance公式——也就是第一個公式λ=1的情況   

   

        采用坐標平均值算法的結果如圖:

    4)重復2、3步,直到最終分類完畢。下面是完整的示例代碼:

import numpy as np
import matplotlib.pyplot as plt

##樣本數據(Xi,Yi),需要轉換成數組(列表)形式
Xn=np.array([2,3,1.9,2.5,4])
Yn=np.array([5,4.8,4,1.8,2.2])

#標識符號
sign_n = ['A','B','C','D','E']
sign_k = ['k1','k2']

def start_class(Xk,Yk):
    ##數據點分類
    cls_dict = {}
    ##離哪個分類點最近,屬於哪個分類
    for i in range(len(Xn)):
        temp = []
        for j in range(len(Xk)):
            d1 = np.sqrt((Xn[i]-Xk[j])*(Xn[i]-Xk[j])+(Yn[i]-Yk[j])*(Yn[i]-Yk[j]))
            temp.append(d1)
        min_dis=np.min(temp)
        min_inx = temp.index(min_dis)
        cls_dict[sign_n[i]]=sign_k[min_inx]
    #print(cls_dict)
    return cls_dict
    
##重新計算分類的坐標點
def recal_class_point(Xk,Yk,cls_dict):  
    num_k1 = 0  #屬於k1的數據點的個數
    num_k2 = 0  #屬於k2的數據點的個數
    x1 =0       #屬於k1的x坐標和
    y1 =0       #屬於k1的y坐標和
    x2 =0       #屬於k2的x坐標和
    y2 =0       #屬於k2的y坐標和

    ##循環讀取已經分類的數據
    for d in cls_dict:
        ##讀取d的類別
        kk = cls_dict[d]
        if kk == 'k1':
            #讀取d在數據集中的索引
            idx = sign_n.index(d)
            ##累加x值
            x1 += Xn[idx]
            ##累加y值
            y1 += Yn[idx]
            ##累加分類個數
            num_k1 += 1
        else :
            #讀取d在數據集中的索引
            idx = sign_n.index(d)
            ##累加x值
            x2 += Xn[idx]
            ##累加y值
            y2 += Yn[idx]
            ##累加分類個數
            num_k2 += 1
    ##求平均值獲取新的分類坐標點
    k1_new_x = x1/num_k1 #新的k1的x坐標
    k1_new_y = y1/num_k1 #新的k1的y坐標

    k2_new_x = x2/num_k2 #新的k2的x坐標
    k2_new_y = y2/num_k2 #新的k2的y坐標

    ##新的分類數組
    Xk=np.array([k1_new_x,k2_new_x])
    Yk=np.array([k1_new_y,k2_new_y])
    return Xk,Yk

def draw_point(Xk,Yk,cls_dict):
    #畫樣本點
    plt.figure(figsize=(5,4)) 
    plt.scatter(Xn,Yn,color="green",label="數據",linewidth=1)
    plt.scatter(Xk,Yk,color="red",label="分類",linewidth=1)
    plt.xticks(range(1,6))
    plt.xlim([1,5])
    plt.ylim([1,6])
    plt.legend()
    for i in range(len(Xn)):
        plt.text(Xn[i],Yn[i],sign_n[i]+":"+cls_dict[sign_n[i]])
        for i in range(len(Xk)):
            plt.text(Xk[i],Yk[i],sign_k[i])
    plt.show()

if __name__ == "__main__":
    ##種子
    Xk=np.array([3.3,3.0])
    Yk=np.array([5.7,3.2])
    for i in range(3):
        cls_dict =start_class(Xk,Yk)
        Xk_new,Yk_new =recal_class_point(Xk,Yk,cls_dict)
        Xk=Xk_new
        Yk=Yk_new
        draw_point(Xk,Yk,cls_dict)
View Code

 

    最終分類結果:

     由上圖可以看出,C點最終是屬於k1類,而不是開始的k2.

4.K-Means的不足

     K-Means算法的不足,都是由初始值引起的:

    1)初始分類數目k值很難估計,不確定應該分成多少類才最合適(ISODATA算法通過類的自動合並和分裂,得到較為合理的類型數目k。這里不講這個算法)

    2)不同的隨機種子會得到完全不同的結果(K-Means++算法可以用來解決這個問題,其可以有效地選擇初始點)

5.K-Means++算法

   算法流程如下:

    1)在數據集中隨機挑選1個點作為種子點

##隨機挑選一個數據點作為種子點
def select_seed(Xn):
    idx = np.random.choice(range(len(Xn)))
    return idx
View Code

    2)計算剩數據點到這個點的距離d(x),並且加入到列表

##計算數據點到種子點的距離
def cal_dis(Xn,Yn,idx):
    dis_list = []
    for i in range(len(Xn)):       
        d = np.sqrt((Xn[i]-Xn[idx])**2+(Yn[i]-Yn[idx])**2)
        dis_list.append(d)
    return dis_list
View Code

  3)再取一個隨機值。這次的選擇思路是:先取一個能落在上步計算的距離列表求和后(sum(dis_list))的隨機值rom,然后用rom -= d(x),直到rom<=0,此時的點就是下一個“種子點”

##隨機挑選另外的種子點
def select_seed_other(Xn,Yn,dis_list):
    d_sum = sum(dis_list)
    rom = d_sum * np.random.random()
    idx = 0
    for i in range(len(Xn)):
        rom -= dis_list[i]
        if rom > 0 :
            continue
        else :
            idx = i
    return idx
View Code

 4)重復第2步和第3步,直到選出k個種子

 5)進行標准的K-Means算法。下面完整代碼

import numpy as np
import matplotlib.pyplot as plt

##樣本數據(Xi,Yi),需要轉換成數組(列表)形式
Xn=np.array([2,3,1.9,2.5,4])
Yn=np.array([5,4.8,4,1.8,2.2])

#標識符號
sign_n = ['A','B','C','D','E']
sign_k = ['k1','k2']

##隨機挑選一個數據點作為種子點
def select_seed(Xn):
    idx = np.random.choice(range(len(Xn)))
    return idx
    
##計算數據點到種子點的距離
def cal_dis(Xn,Yn,idx):
    dis_list = []
    for i in range(len(Xn)):       
        d = np.sqrt((Xn[i]-Xn[idx])**2+(Yn[i]-Yn[idx])**2)
        dis_list.append(d)
    return dis_list

##隨機挑選另外的種子點
def select_seed_other(Xn,Yn,dis_list):
    d_sum = sum(dis_list)
    rom = d_sum * np.random.random()
    idx = 0
    for i in range(len(Xn)):
        rom -= dis_list[i]
        if rom > 0 :
            continue
        else :
            idx = i
    return idx

##選取所有種子點
def select_seed_all(seed_count):
     ##種子點
    Xk = []  ##種子點x軸列表
    Yk = []  ##種子點y軸列表
    
    idx = 0  ##選取的種子點的索引
    dis_list = [] ##距離列表
    
    
    ##選取種子點
    #因為實驗數據少,有一定的幾率選到同一個數據,所以加一個判斷
    idx_list = []
    flag = True
    for i in range(seed_count):
        if i == 0:
             idx = select_seed(Xn)
             dis_list = cal_dis(Xn,Yn,idx)
             Xk.append(Xn[idx])
             Yk.append(Yn[idx])
             idx_list.append(idx)
        else :
            while flag:
                idx = select_seed_other(Xn,Yn,dis_list)
                if idx not in idx_list:
                    flag = False
                else :
                    continue
            dis_list = cal_dis(Xn,Yn,idx)
            Xk.append(Xn[idx])
            Yk.append(Yn[idx])
            idx_list.append(idx)
                
    ##列表轉成數組       
    Xk=np.array(Xk)
    Yk=np.array(Yk)

    return Xk,Yk
    

def start_class(Xk,Yk):
    ##數據點分類
    cls_dict = {}
    ##離哪個分類點最近,屬於哪個分類
    for i in range(len(Xn)):
        temp = []
        for j in range(len(Xk)):
            d1 = np.sqrt((Xn[i]-Xk[j])*(Xn[i]-Xk[j])+(Yn[i]-Yk[j])*(Yn[i]-Yk[j]))
            temp.append(d1)
        min_dis=np.min(temp)
        min_inx = temp.index(min_dis)
        cls_dict[sign_n[i]]=sign_k[min_inx]
    #print(cls_dict)
    return cls_dict
    
##重新計算分類的坐標點
def recal_class_point(Xk,Yk,cls_dict):  
    num_k1 = 0  #屬於k1的數據點的個數
    num_k2 = 0  #屬於k2的數據點的個數
    x1 =0       #屬於k1的x坐標和
    y1 =0       #屬於k1的y坐標和
    x2 =0       #屬於k2的x坐標和
    y2 =0       #屬於k2的y坐標和

    ##循環讀取已經分類的數據
    for d in cls_dict:
        ##讀取d的類別
        kk = cls_dict[d]
        if kk == 'k1':
            #讀取d在數據集中的索引
            idx = sign_n.index(d)
            ##累加x值
            x1 += Xn[idx]
            ##累加y值
            y1 += Yn[idx]
            ##累加分類個數
            num_k1 += 1
        else :
            #讀取d在數據集中的索引
            idx = sign_n.index(d)
            ##累加x值
            x2 += Xn[idx]
            ##累加y值
            y2 += Yn[idx]
            ##累加分類個數
            num_k2 += 1
    ##求平均值獲取新的分類坐標點
    k1_new_x = x1/num_k1 #新的k1的x坐標
    k1_new_y = y1/num_k1 #新的k1的y坐標

    k2_new_x = x2/num_k2 #新的k2的x坐標
    k2_new_y = y2/num_k2 #新的k2的y坐標

    ##新的分類數組
    Xk=np.array([k1_new_x,k2_new_x])
    Yk=np.array([k1_new_y,k2_new_y])
    return Xk,Yk

def draw_point(Xk,Yk,cls_dict):
    #畫樣本點
    plt.figure(figsize=(5,4)) 
    plt.scatter(Xn,Yn,color="green",label="數據",linewidth=1)
    plt.scatter(Xk,Yk,color="red",label="分類",linewidth=1)
    plt.xticks(range(1,6))
    plt.xlim([1,5])
    plt.ylim([1,6])
    plt.legend()
    for i in range(len(Xn)):
        plt.text(Xn[i],Yn[i],sign_n[i]+":"+cls_dict[sign_n[i]])
        for i in range(len(Xk)):
            plt.text(Xk[i],Yk[i],sign_k[i])
    plt.show()

def draw_point_all_seed(Xk,Yk):
    #畫樣本點
    plt.figure(figsize=(5,4)) 
    plt.scatter(Xn,Yn,color="green",label="數據",linewidth=1)
    plt.scatter(Xk,Yk,color="red",label="分類",linewidth=1)
    plt.xticks(range(1,6))
    plt.xlim([1,5])
    plt.ylim([1,6])
    plt.legend()
    for i in range(len(Xn)):
        plt.text(Xn[i],Yn[i],sign_n[i])
    plt.show()

if __name__ == "__main__":

     ##選取2個種子點
     Xk,Yk = select_seed_all(2)
     ##查看種子點
     draw_point_all_seed(Xk,Yk)
     ##循環三次進行分類
     for i in range(3):
        cls_dict =start_class(Xk,Yk)
        Xk_new,Yk_new =recal_class_point(Xk,Yk,cls_dict)
        Xk=Xk_new
        Yk=Yk_new
        draw_point(Xk,Yk,cls_dict)
View Code

 

 

   如圖所示,選擇了A、E兩點作為種子點。

最終的結果。

 補充說明:因為數據量太少,在選取所有種子函數的while階段有可能陷入死循環,所以需要關閉代碼重新運行才可以出結果

 

6.sklearn包中的K-Means算法

   1)函數:sklearn.cluster.KMeans

   2)主要參數

           n_clusters:要進行的分類的個數,即上文中k值,默認是8

           max_iter  :最大迭代次數。默認300

           min_iter   :最小迭代次數,默認10

           init:有三個可選項

                   'k-means ++':使用k-means++算法,默認選項

                   'random':從初始質心數據中隨機選擇k個觀察值

                   第三個是數組形式的參數

            n_jobs: 設置並行量 (-1表示使用所有CPU)

     3)主要屬性:

          cluster_centers_ :集群中心的坐標

          labels_ : 每個點的標簽

      4)官網示例:  

>>> from sklearn.cluster import KMeans
>>> import numpy as np
>>> X = np.array([[1, 2], [1, 4], [1, 0],
...               [4, 2], [4, 4], [4, 0]])
>>> kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
>>> kmeans.labels_
array([0, 0, 0, 1, 1, 1], dtype=int32)
>>> kmeans.predict([[0, 0], [4, 4]])
array([0, 1], dtype=int32)
>>> kmeans.cluster_centers_
array([[ 1.,  2.],
       [ 4.,  2.]])


免責聲明!

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



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