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

 

posted @ 2018-04-02 20:12  寒杰士  阅读(48680)  评论(0编辑  收藏  举报