K-means與K-means++:
原始K-means算法最開始隨機選取數據集中K個點作為聚類中心,
而K-means++按照如下的思想選取K個聚類中心:
- 假設已經選取了n個初始聚類中心(0<n<K),則在選取第n+1個聚類中心時:距離當前n個聚類中心越遠的點會有更高的概率被選為第n+1個聚類中心。
- 在選取第一個聚類中心(n=1)時同樣通過隨機的方法。
可以說這也符合我們的直覺:聚類中心當然是互相離得越遠越好。這個改進雖然直觀簡單,但是卻非常得有效。
經典K-means算法:
值得一提的是關於聚類中心數目(K值)的選取,的確存在一種可行的方法,叫做Elbow Method:
通過繪制K-means代價函數與聚類數目K的關系圖,選取直線拐點處的K值作為最佳的聚類中心數目。
上述方法中的拐點在實際情況中是很少出現的。
比較提倡的做法還是從實際問題出發,人工指定比較合理的K值,通過多次隨機初始化聚類中心選取比較滿意的結果。
python實現:
1 # -*- coding: utf-8 -*- 2 3 #!/usr/bin/env python 4 # @Time : 18-2-3 下午6:01 5 # @Author : wang shen 6 # @web : 7 # @File : kmeans.py 8 from collections import defaultdict 9 from random import uniform 10 from math import sqrt 11 12 13 def point_avg(points): 14 ''' 15 Accepts a list of points, each with the same number of dimensions. 16 NB. points can have more dimensions than 2 17 Returns a new points which is the center of all the points 18 :param points: 19 :return: 20 ''' 21 dimensions = len(points[0]) 22 23 new_center = [] 24 25 for dimension in range(dimensions): 26 dim_sum = 0 27 for p in points: 28 dim_sum += p[dimension] 29 30 # average of each dimension 31 new_center.append(dim_sum / float(len(points))) 32 33 return new_center 34 35 36 def update_centers(date_set, assignments): 37 ''' 38 Accepts a dataset and a list of assignments; the indexes of both lists correspond 39 to each other. 40 compute the center for each of the assigned groups. 41 Reture 'k' centers where is the number of unique assignments. 42 :param date_set: 43 :param assignments: 44 :return: 45 ''' 46 new_means = defaultdict(list) 47 centers = [] 48 for assigment, point in zip(assignments, date_set): 49 new_means[assigment].append(point) 50 51 for points in new_means.values(): 52 centers.append(point_avg(points)) 53 54 return centers 55 56 57 def distance(a, b): 58 dimensions = len(a) 59 60 _sum = 0 61 for dimension in range(dimensions): 62 difference_seq = (a[dimension] - b[dimension]) ** 2 63 _sum += difference_seq 64 65 return sqrt(_sum) 66 67 68 def assign_points(data_points, centers): 69 ''' 70 Given a data set and a list of points between other points, 71 assign each point to an index that corresponds to the index 72 of the center point on its proximity to that point. 73 Return a an array of indexes of the centers that correspond to 74 an index in the data set; that is, if there are N points in data set 75 the list we return will have N elements. Also If there ara Y points in centers 76 there will be Y unique possible values within the returned list. 77 :param data_points: 78 :param centers: 79 :return: 80 ''' 81 assigments = [] 82 for point in data_points: 83 shortest = float('Inf') 84 shortest_index = 0 85 for i in range(len(centers)): 86 val = distance(point, centers[i]) 87 if val < shortest: 88 shortest = val 89 shortest_index = i 90 assigments.append(shortest_index) 91 92 return assigments 93 94 95 def generate_k(data_set, k): 96 ''' 97 Given data set , which is an array of arrays, 98 find the minimum and maximum foe each coordinate, a range. 99 Generate k random points between the ranges 100 Return an array of the random points within the ranges 101 :param data_set: 102 :param k: 103 :return: 104 ''' 105 centers = [] 106 dimensions = len(data_set[0]) 107 min_max = defaultdict(int) 108 109 for point in data_set: 110 for i in range(dimensions): 111 val = point[i] 112 min_key = 'min_%d' % i 113 max_key = 'max_%d' % i 114 if min_key not in min_max or val < min_max[min_key]: 115 min_max[min_key] = val 116 if max_key not in min_max or val > min_max[max_key]: 117 min_max[max_key] = val 118 119 for _k in range(k): 120 rand_point = [] 121 for i in range(dimensions): 122 min_val = min_max['min_%d' % i] 123 max_val = min_max['max_%d' % i] 124 125 rand_point.append(uniform(min_val, max_val)) 126 127 centers.append(rand_point) 128 return centers 129 130 131 def k_means(dataset, k): 132 k_points = generate_k(dataset, k) 133 assignments = assign_points(dataset, k_points) 134 old_assignments = None 135 times = 0 136 while assignments != old_assignments: 137 times += 1 138 print('times is :', times) 139 new_centers = update_centers(dataset, assignments) 140 old_assignments = assignments 141 assignments = assign_points(dataset, new_centers) 142 143 return (assignments, dataset)
K-means++算法:
起步
由於 K-means 算法的分類結果會受到初始點的選取而有所區別,因此有提出這種算法的改進: K-means++
。
算法步驟
其實這個算法也只是對初始點的選擇有改進而已,其他步驟都一樣。初始質心選取的基本思路就是,初始的聚類中心之間的相互距離要盡可能的遠。
算法描述如下:
- 步驟一:隨機選取一個樣本作為第一個聚類中心 c1;
- 步驟二:
- 計算每個樣本與當前已有類聚中心最短距離(即與最近一個聚類中心的距離),用
D(x)
表示; - 這個值越大,表示被選取作為聚類中心的概率較大;
- 最后,用輪盤法選出下一個聚類中心;
- 計算每個樣本與當前已有類聚中心最短距離(即與最近一個聚類中心的距離),用
- 步驟三:重復步驟二,知道選出 k 個聚類中心。
選出初始點后,就繼續使用標准的 k-means 算法了。
效率
K-means++ 能顯著的改善分類結果的最終誤差。
盡管計算初始點時花費了額外的時間,但是在迭代過程中,k-mean 本身能快速收斂,因此算法實際上降低了計算時間。
網上有人使用真實和合成的數據集測試了他們的方法,速度通常提高了 2 倍,對於某些數據集,誤差提高了近 1000 倍。
下面結合一個簡單的例子說明K-means++是如何選取初始聚類中心的。
數據集中共有8個樣本,分布以及對應序號如下圖所示:
假設經過圖2的步驟一后6號點被選擇為第一個初始聚類中心,
那在進行步驟二時每個樣本的D(x)和被選擇為第二個聚類中心的概率如下表所示:
其中的P(x)就是每個樣本被選為下一個聚類中心的概率。
最后一行的Sum是概率P(x)的累加和,用於輪盤法選擇出第二個聚類中心。
方法是隨機產生出一個0~1之間的隨機數,判斷它屬於哪個區間,那么該區間對應的序號就是被選擇出來的第二個聚類中心了。
例如1號點的區間為[0,0.2),2號點的區間為[0.2, 0.525)。
從上表可以直觀的看到第二個初始聚類中心是1號,2號,3號,4號中的一個的概率為0.9。
而這4個點正好是離第一個初始聚類中心6號點較遠的四個點。
這也驗證了K-means的改進思想:即離當前已有聚類中心較遠的點有更大的概率被選為下一個聚類中心。
可以看到,該例的K值取2是比較合適的。當K值大於2時,每個樣本會有多個距離,需要取最小的那個距離作為D(x)。
python實現:
1 # coding: utf-8 2 import math 3 import random 4 from sklearn import datasets 5 6 def euler_distance(point1: list, point2: list) -> float: 7 """ 8 計算兩點之間的歐拉距離,支持多維 9 """ 10 distance = 0.0 11 for a, b in zip(point1, point2): 12 distance += math.pow(a - b, 2) 13 return math.sqrt(distance) 14 15 def get_closest_dist(point, centroids): 16 min_dist = math.inf # 初始設為無窮大 17 for i, centroid in enumerate(centroids): 18 dist = euler_distance(centroid, point) 19 if dist < min_dist: 20 min_dist = dist 21 return min_dist 22 23 def kpp_centers(data_set: list, k: int) -> list: 24 """ 25 從數據集中返回 k 個對象可作為質心 26 """ 27 cluster_centers = [] 28 cluster_centers.append(random.choice(data_set)) 29 d = [0 for _ in range(len(data_set))] 30 for _ in range(1, k): 31 total = 0.0 32 for i, point in enumerate(data_set): 33 d[i] = get_closest_dist(point, cluster_centers) # 與最近一個聚類中心的距離 34 total += d[i] 35 total *= random.random() 36 for i, di in enumerate(d): # 輪盤法選出下一個聚類中心; 37 total -= di 38 if total > 0: 39 continue 40 cluster_centers.append(data_set[i]) 41 break 42 return cluster_centers 43 44 if __name__ == "__main__": 45 iris = datasets.load_iris() 46 print(kpp_centers(iris.data, 4))
本文來自於: