一、 環境:
- Python 3.7.4
- Pycharm Community 2019.3
二、 問題:
對六個樣本點[1, 5], [2, 4], [4, 1], [5, 0], [7, 6], [6, 7]進行K-means聚類。

三、 理論推導
此處依照我個人理解所寫,錯誤之處歡迎指出
K-means核心操作為:聚類中心選取—分類—調整聚類中心—再次分類並調整聚類中心直到調整幅度小於閾值或程序運行輪數大於閾值
- 選取聚類中心:
聚類中心的選取可以選擇隨機選取、人工選取。K-means++相對K-means來說在第一次選取聚類中心方面有所改進,K-means++在初次選擇聚類中心時會使各個聚類中心之間的距離都盡可能的遠,以得到更合理的分類。如果條件允許的話,我認為人工選取聚類中心的結果會更符合我們的需求。 - 分類
遍歷所有待分類的點,根據我們選擇的度量方法來判斷點的類型。(我的代碼直接以歐式距離作為度量來判斷類型)
度量方法的選擇要根據實際情況以及需求具體分析選擇。同一數據使用不同的度量方法最后的分類結果可能會不同。 - 調整聚類中心
計算已經分好類的樣本點的均值,以均值作為新的聚類中心。
若首次聚類中心選擇很不合理的話,聚類中心的調整也會受到影響,導致最后分類結果不理想。 - 再次分類並調整聚類中心直到調整幅度小於閾值或運行輪數大於閾值
在調整了聚類中心之后再次進行步驟2,3直到聚類中心調整的幅度小於我們設定的一個閾值,或者循環運行的輪數大於我們的閾值,就認為分類結束。
四、 代碼實現
代碼主要用於理解算法,有些地方可能不夠嚴謹,還請輕噴😭😭😭
因為樣本點較少,所以倆個算法運行結果相同,讀者可自行增加樣本檢測
K-means:
1 # kmeans input_data為待分類數據,k為分類類別數 2 def myKmeans(input_data, k): 3 # region 選擇類中心 4 index_cls = [] 5 while index_cls.__len__() < k: 6 n = np.random.randint(0, input_data.shape[0], 1) 7 if n not in index_cls: 8 index_cls.append(n[0]) 9 point_cls = input_data[np.array(index_cls)] # piont_cls為選好的三個聚類中心 10 # endregion 11 # region 更新樣本點類型 12 # tag_sample = [-1 for i in range(input_data.shape[0])] # 六個樣本點的標簽 13 while True: 14 # 計算樣本點到聚類中心的歐式距離平方(無需開根號) (x1-x2) ** 2 + (y1 - y2) ** 2 15 # axis = 0 為按行取值,axis = 1 為按列取值 16 dis_cls = np.array([np.sum((input_data - i) ** 2, axis=1) for i in point_cls]) # 計算六個點距離三個聚類中心的值,三行六列 17 # 0: 1 2 3 4 5 6 18 # 1: 6 5 4 2 3 4 19 # 2: 4 4 5 3 4 5 20 # 選取每一列最小值索引作為這個樣本點的距離 21 min_tag = np.argmin(dis_cls, axis=0) # 選取每一列的最小值(即最小距離),為本次計算樣本點的tag 22 # endregion 23 24 # region 計算新類中心 25 new_piont_cls = np.array(list([np.average(input_data[min_tag == i], axis=0) for i in range(k)])) 26 # endregion 27 28 # region 計算新類中心的樣本類別,做判斷,若類別有變化,則更新類別,若不變化,結束算法 29 # 比較倆個類的中心是否滿足一定條件(一般算和小於一定的值) 30 gap = np.sum((new_piont_cls - point_cls) ** 2) # 若樣本點改變值和小於1,則表示分類結束 31 if gap < 1: 32 break 33 else: 34 point_cls = new_piont_cls 35 # 可以比較樣本的類別變化,若樣本類別無變化,則停止 36 # endregion 37 return min_tag # 返回input_data分類(0,1,2) 三類

K-means++:
1 # kmeans++ input_data為待分類數據,k為分類類別數 2 def myKmeanspp(input_data, k): 3 # region 選擇類中心 4 # 選取相距距離最遠的點作為聚類中心 5 point_cls = [list([np.random.randint(0, input_data.shape[0]), 1])] 6 # 計算第二個點,即獲取離選好的類中心點最遠的點 7 while point_cls.__len__() < k: 8 dis = np.array([sum((input_data[j] - point_cls[i]) ** 2 for i in range(point_cls.__len__())) for j in 9 range(input_data.__len__())]) 10 sample_dis_cls_min = np.min(dis, axis=0) 11 index_max_dis_sample = np.argmax(sample_dis_cls_min) 12 point_cls.append(list(input_data[index_max_dis_sample])) 13 point_cls = np.array(point_cls) 14 # endregion 15 # region 更新樣本點類型 16 # tag_sample = [-1 for i in range(input_data.shape[0])] # 六個樣本點的標簽 17 while True: 18 # 計算樣本點到聚類中心的歐式距離平方(無需開根號) (x1-x2) ** 2 + (y1 - y2) ** 2 19 # axis = 0 為按行取值,axis = 1 為按列取值 20 dis_cls = np.array([np.sum((input_data - i) ** 2, axis=1) for i in point_cls]) # 計算六個點距離三個聚類中心的值,三行六列 21 # 0: 1 2 3 4 5 6 22 # 1: 6 5 4 2 3 4 23 # 2: 4 4 5 3 4 5 24 # 選取每一列最小值索引作為這個樣本點的距離 25 min_tag = np.argmin(dis_cls, axis=0) # 選取每一列的最小值(即最小距離),為本次計算樣本點的tag 26 # endregion 27 28 # region 計算新類中心 29 new_piont_cls = np.array(list([np.average(input_data[min_tag == i], axis=0) for i in range(3)])) 30 # endregion 31 32 # region 計算新類中心的樣本類別,做判斷,若類別有變化,則更新類別,若不變化,結束算法 33 # 比較倆個類的中心是否滿足一定條件(一般算和小於一定的值) 34 gap = np.sum((new_piont_cls - point_cls) ** 2) # 若樣本點改變值和小於1,則表示分類結束 35 if gap < 1: 36 break 37 else: 38 point_cls = new_piont_cls 39 # 可以比較樣本的類別變化,若樣本類別無變化,則停止 40 # endregion 41 return min_tag

