K-means與K-means++


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))

 

 

本文來自於:

K-means聚類算法的三種改進 

K-means算法的改進:K-means++

 


免責聲明!

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



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