文章目录
K均值聚类(K-means)介绍
历史渊源
- 虽然其思想能够追溯到1957年的Hugo Steinhaus,术语“k-均值”于1967年才被James MacQueen首次使用。标准算法则是在1957年被Stuart Lloyd作为一种脉冲码调制的技术所提出,但直到1982年才被贝尔实验室公开出版。在1965年,E.W.Forgy发表了本质上相同的方法,所以这一算法有时被称为Lloyd-Forgy方法。更高效的版本则被Hartigan and Wong提出(1975/1979)
介绍
- K-Means算法是无监督的聚类算法,算法简单,聚类效果好,即使是在巨大的数据集上也非常容易部署实施。正因为如此,它在很多领域都得到的成功的应用,如市场划分、机器视觉、 地质统计学、天文学和农业等。K-Means算法有大量的变体,包括初始化优化K-Means++以及大数据应用背景下的k-means||和Mini Batch K-Means
K-Means原理
K-Means算法的思想很简单,对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
如果用数据表达式表示,假设簇划分为 ( C 1 , C 2 , . . . C k ) (C_1,C_2,...C_k) (C1,C2,...Ck),则我们的目标是最小化平方误差E:
- E = ∑ i = 1 k ∑ x ∈ C i ∣ ∣ x − μ i ∣ ∣ 2 2 E=\sum_{i=1}^k\sum_{x∈C_i}||x−μ_i||_2^2 E=∑i=1k∑x∈Ci∣∣x−μi∣∣22
其中μi是簇Ci的均值向量,有时也称为质心,表达式为:
- μ i = 1 ∣ C i ∣ ∑ x ∈ C i x μ_i=\frac1{|Ci|}∑_{x∈C_i}x μi=∣Ci∣1∑x∈Cix
如果我们想直接求上式的最小值并不容易,这是一个NP难的问题,因此只能采用启发式的迭代方法。
K-Means采用的启发式方式很简单,用下面一组图就可以形象的描述。
上图a表达了初始的数据集,假设k=2。在图b中,我们随机选择了两个k类所对应的类别质心,即图中的红色质心和蓝色质心,然后分别求样本中所有点到这两个质心的距离,并标记每个样本的类别为和该样本距离最小的质心的类别,如图c所示,经过计算样本和红色质心和蓝色质心的距离,我们得到了所有样本点的第一轮迭代后的类别。此时我们对我们当前标记为红色和蓝色的点分别求其新的质心,如图4所示,新的红色质心和蓝色质心的位置已经发生了变动。图e和图f重复了我们在图c和图d的过程,即将所有点的类别标记为距离最近的质心的类别并求新的质心。最终我们得到的两个类别如图f。
当然在实际K-Mean算法中,我们一般会多次运行图c和图d,才能达到最终的比较优的类别。
K-means算法流程
- 1)对于K-Means算法,首先要注意的是k值的选择,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通过交叉验证选择一个合适的k值。
- 2)在确定了k的个数后,我们需要选择k个初始化的质心,就像上图b中的随机质心。由于我们是启发式方法,k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心,最好这些质心不能太近。
输入是样本集D=
x
1
,
x
2
,
.
.
.
x
N
{x_1,x_2,...x_N}
x1,x2,...xN,聚类的簇数k,最大迭代次数T
输出是簇划分C=
C
1
,
C
2
,
.
.
.
C
k
{C_1,C_2,...C_k}
C1,C2,...Ck
(1)从数据集D中随机选择k个样本作为初始的k个质心向量:
μ
1
,
μ
2
,
.
.
.
,
μ
k
{μ_1,μ_2,...,μ_k}
μ1,μ2,...,μk,将每个簇初始化为空集
(2)对于t=1,2,…,T
- (a)对于i=1,2…N,计算样本 x i x_i xi 和各个质心向量 μ j μ_j μj,j=1,2,…k的欧式距离,将 x i x_i xi划分到最近的簇中,即更新 C j = C j ∪ x i C_j=C_j∪{x_i} Cj=Cj∪xi
- (b)对于j=1,2,…,k,对 C j C_j Cj中所有的样本点重新计算新的质心 μ j = 1 ∣ C j ∣ ∑ x ∈ C j x μ_j=\frac1{|C_j|}∑_{x∈C_j}x μj=∣Cj∣1∑x∈Cjx
- (c)如果所有的k个质心向量都没有发生变化,则转到步骤3)
(3) 输出簇划分 C = C 1 , C 2 , . . . C k C={C_1,C_2,...C_k} C=C1,C2,...Ck
第一步:从数据集中随机抽取K个样本,作为聚类中心(质心)
第二步:计算样本到聚类中心的距离,把样本归属到最近的聚类中心,形成了K个簇
第三步:更新质心
第四步:重复23步,直到质心不再发生移动或者达到一定的迭代次数
K-means编程小练习
对K-means也有了一定的了解了,先看看一个小练习
底层实现
import numpy as np
X = np.array([[1, 2], [2, 2], [6, 8], [7, 8]])
C = np.array([[1.0, 2.0], [2.0, 2.0]]) # 聚类中心
iters = 5 # 迭代次数
while iters > 0:
iters -= 1
min_index = []
for c in C: # 遍历每一个聚类中心,计算样本到每个聚类中心的距离
a = np.sum((X - c) ** 2, axis=1)
min_index.append(a)
print(a)
# 将样本分配到所属的聚类中心
min_index = np.array(min_index)
min_index = np.argmin(min_index, axis=0)
for i in range(len(C)): # 获取每个簇的样本,求质心更新C
a = X[min_index == i]
C[i] = np.mean(a, axis=0)
# 打印聚类中心
print(C)
# 打印所有样本的所属的簇
print(min_index)
[ 0. 1. 61. 72.]
[ 1. 0. 52. 61.]
[ 0. 1. 61. 72.]
[32. 25. 5. 8.]
[ 0.25 0.25 56.25 66.25]
[66.25 56.25 0.25 0.25]
[ 0.25 0.25 56.25 66.25]
[66.25 56.25 0.25 0.25]
[ 0.25 0.25 56.25 66.25]
[66.25 56.25 0.25 0.25]
[[1.5 2. ]
[6.5 8. ]]
[0 0 1 1]
调库实现
import numpy as np
X = np.array([[1, 2], [2, 2], [6, 8],[7 ,8]])
C = np.array([[1, 1], [2, 1]])
from sklearn.cluster import KMeans
model = KMeans(n_clusters=2)
model.fit(X,C)
muk = model.cluster_centers_
labels = model.labels_
print(labels)
[1 1 0 0]
K-Means的优缺点
优点
- 原理比较简单,实现也是很容易,收敛速度快。
- 聚类效果较优。
- 算法的可解释度比较强。
- 主要需要调参的参数仅仅是簇数k。
缺点
- K值的选取不好把握、对于不是凸的数据集比较难收敛、 如果各隐含类别的数据不平衡,比如各隐含类别的数据量严重失衡,或者各隐含类别的方差不同,则聚类效果不佳。、 采用迭代方法,得到的结果只是局部最优。、对噪音和异常点比较的敏感。
-
缺点一:聚类中心的个数K需要事先给定,但在实际中K值的选定是非常困难的,很多时候我们并不知道给定的数据集应该聚成多少个类别才最合适
-
缺点二:k-means算法需要随机地确定初始聚类中心,不同的初始聚类中心可能导致完全不同的聚类结果,有可能导致算法收敛很慢甚至出现聚类出错的情况
-
针对第一个缺点:很难在k-means算法以及其改进算法中解决,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通过“肘方法”选择一个合适的k值
肘方法(elbowmethod)基于如F观察:增加簇数有助于降低每个簇的簇内方差之和。这是因为有更多的簇可以捕获更细的数据对象簇,簇中对象之间更为相似。
然而,如果形成太多的簇,则降低簇内方差和的边缘效应可能下降,因为把一个凝聚的簇分裂成两个只引起簇内方差和的稍微降低。
因此,- ~种选择正确的簇数的启发式方法是,使用簇内方差和关于簇数的曲线的拐点。 严格地说,给定k>0,我们可以使用一种像k -均值这样的算法对数据集聚类,
并计算簇内方差和var(h)。然后,我们绘制nar关于k的曲线。曲线的第一个(或最显著的)拐点暗示“正确的”簇数。
- 针对第二个缺点:可以通过k-means++算法来解决
K-Means++
K-Means++的对于初始化质心的优化策略,如下:
- a) 从输入的数据点集合中随机选择一个点作为第一个聚类中心 μ 1 μ_1 μ1
- b) 对于数据集中的每一个点 x i x_i xi,计算它与已选择的聚类中心中最近聚类中心的距离D
- c) 选择一个新的数据点作为新的聚类中心,选择的原则是:D较大的点,被选取作为聚类中心的概率较大。
- d) 重复b和c直到选择出k个聚类质心
- e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法
第一步:随机选择一个样本作为初始质心
第二步:计算每个样本到最近聚类中心的距离
第三步:每个样本都有一个距离,距离越大,被选为下一个质心的概率越大
第四步:重复23步,直到得到K个质心停止
第五步:执行kmeans算法
Demo
import random
import pandas as pd
import numpy as np
a = [["A",10],["B",20],["C",30],["D",40]]
#第二个元素越大,采到的概率越大,请采样1w次,然后统计频率
a1 = [i[1] for i in a]
res = []
for k in range(10000):
b = np.sum(a1)*random.random()
for i in a:
if i[-1]>b:
res.append(i)
break
c = pd.Series(res)
print(c.value_counts())
[C, 30] 1014
[D, 40] 1009
[B, 20] 997
[A, 10] 976
dtype: int64
定义
- k-means||是k-means++的变体,k-means||在初始化中心点时对kmeans++的缺点做了规避,主要体现在不需要根据k的个数严格地寻找k个点,突破了算法在大规模数据集上的应用瓶颈,同时初始化的中心点更加健壮
K-means++的缺点
虽然k-means++算法可以确定地初始化聚类中心,但是从可扩展性来看,它存在一个缺点??
- 它内在的有序性特性:下一个中心点的选择依赖于已经选择的中心点
- 解决方案:针对这种缺陷,k-means||算法提供了解决方法。
-
Kmeans||算法步骤
第一步:随机抽取一个样本作为候选聚类中心
第二步:计算每个样本到最近聚类中心的距离,按照概率抽取一批点,作为候选聚类中心
第三步:重复第二步,循环5次,得到比预设的K大一些的候选聚类中心集合
第四步:计算每个候选质心的密度(看谁附近的样本点多)
第五步:在候选质心集合上执行一个考虑了权重的kmeans++算法,得到确切的K个质心
第六步:执行kmeans算法
Kmeans算法的性能对比
Kmeans超参数
sklearn.cluster.KMeans(n_clusters = 8,init =‘k-means ++’,n_init = 10,max_iter = 300,tol = 0.0001,precompute_distances =‘auto’,verbose = 0,random_state = None,copy_x = True,n_jobs = None,algorithm = ‘auto’ )
参数
-
n_clusters : int,optional,default:8
要形成的簇数以及要生成的质心数。 -
init : {‘k-means ++’,'random’或ndarray}
初始化方法,默认为’k-means ++’:
‘k-means ++’:以智能方式选择初始聚类中心进行k均值聚类,以加速收敛。有关更多详细信息,请参阅k_init中的注释部分。
‘random’:从初始质心的数据中随机选择k个观测值(行)。
如果传递了ndarray,它应该是形状(n_clusters,n_features)并给出初始中心。 -
n_init : int,default:10
使用不同质心种子运行k-means算法的时间。在惯性方面,最终结果将是n_init连续运行的最佳输出。 -
max_iter : int,default:300
单次运行的k-means算法的最大迭代次数。 -
tol : float,default:1e-4
关于声明收敛的惯性的相对容差 -
precompute_distances : {‘auto’,True,False}
预计算距离(更快但需要更多内存)。
‘auto’:如果n_samples * n_clusters> 1200万,请不要预先计算距离。这相当于使用双精度的每个作业大约100MB的开销。
真:始终预先计算距离
错:从不预先计算距离 -
verbose : int,default 0
详细模式。
- random_state : int,RandomState实例或None(default)
确定质心初始化的随机数生成。使用int可以使随机性具有确定性。见术语表。
-
copy_x : boolean, optional
当预先计算距离时,首先使数据居中在数值上更准确。如果copy_x为True(默认值),则不修改原始数据,确保X是C连续的。如果为False,原始数据被修改,并在函数返回之前返回,但是可以通过减去然后添加数据均值来引入小的数值差异,在这种情况下,它也不能确保数据是C连续的,这可能导致显着放缓。 -
n_jobs : int或None,optional(default=None)
用于计算的作业数。这通过并行计算每个n_init运行来工作。
None除非在joblib.parallel_backend上下文中,否则表示1 。 -1表示使用所有处理器。有关 详细信息,请参阅词汇表。 -
algorithm : “auto”,“full”或“elkan”,default=“auto”
K-means算法使用。经典的EM风格算法是“完整的”。通过使用三角不等式,“elkan”变体更有效,但目前不支持稀疏数据。“auto”选择“elkan”表示密集数据,“full”表示稀疏数据。
属性
-
cluster_centers_ : array,[n_clusters,n_features]
集群中心的坐标。如果算法在完全收敛之前停止(参见tol和max_iter),则这些算法将不一致labels_。 -
labels_ :
每个点的标签 -
inertia_ : float
样本到其最近聚类中心的平方距离之和。 -
n_iter_ : int
运行的迭代次数。
方法
- fit(self,X [,y,sample_weight]) ------------------计算k均值聚类。
- fit_predict(self,X [,y,sample_weight]) --------计算聚类中心并预测每个样本的聚类索引。
- fit_transform(self,X [,y,sample_weight])------计算聚类并将X转换为聚类距离空间。
- get_params(self[,deep]) ----------------------------获取此估算工具的参数。
- predict(self,X [,sample_weight]) ------------------预测X中每个样本所属的最近集群。
- score(self,X [,y,sample_weight]) ---------------与K-means目标的X值相反。
- set_params(self,\ * \ * params) ----------------------设置此估算器的参数。
- transform(self,X) --------------------------------------将X转换为簇距离空间。