机器学习十二: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))
本文来自于: