1.簡介算法
K-means算法是最爲經典的基於劃分的聚類方法,是十大經典數據挖掘算法之一。K-means算法的基本思想是:以空間中k個點爲中心進行聚類,對最靠近他們的對象歸類。經過迭代的方法,逐次更新各聚類中心的值,直至獲得最好的聚類結果。數組
2. 算法大體流程爲:app
1)隨機選取k個點做爲種子點(這k個點不必定屬於數據集)dom
2)分別計算每一個數據點到k個種子點的距離,離哪一個種子點最近,就屬於哪類ide
3)從新計算k個種子點的座標(簡單經常使用的方法是求座標值的平均值做爲新的座標值)函數
4)重複二、3步,直到種子點座標不變或者循環次數完成idea
3.完整計算過程spa
1)設置實驗數據3d
運行以後,效果以下圖所示:code
在圖中,ABCDE五個點是待分類點,k一、k2是兩個種子點。
2)計算ABCDE五個點到k一、k2的距離,離哪一個點近,就屬於哪一個點,進行初步分類。
結果如圖:
A、B屬於k1,C、D、E屬於k2
3)從新計算k一、k2的座標。這裏使用簡單的座標的平均值,使用其餘算法也能夠(例如如下三個公式)
PS:公式的圖片莫名其妙被屏蔽了,因爲沒有留備份,找不到原來的圖片了。因此這裏只寫個名字,方便你們作個瞭解或者搜索的關鍵字。
a)Minkowski Distance公式——λ能夠隨意取值,能夠是負數,也能夠是正數,或是無窮大。
b)Euclidean Distance公式——也就是第一個公式λ=2的狀況
c)CityBlock Distance公式——也就是第一個公式λ=1的狀況
採用座標平均值算法的結果如圖:
4)重複二、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)
最終分類結果:
由上圖能夠看出,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
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
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
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)
如圖所示,選擇了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.]])