机器学习K-Means

文章目录


1.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\limits_{i=1}^k\sum\limits_{x \in C_i} ||x-\mu_i||_2^2 E=i=1∑kx∈Ci∑∣∣x−μi∣∣22

其中 μ i \mu_i μi是簇 C i C_i Ci的均值向量,有时也称为质心,表达式为:
μ i = 1 ∣ C i ∣ ∑ x ∈ C i x \mu_i = \frac{1}{|C_i|}\sum\limits_{x \in C_i}x μi=∣Ci∣1x∈Ci∑x

如果我们想直接求上式的最小值并不容易,这是一个NP难的问题,因此只能采用启发式的迭代方法。K-Means采用的启发式方式很简单,用下面一组图就可以形象的描述。

上图a表达了初始的数据集,假设k=2。在图b中,我们随机选择了两个k类所对应的类别质心,即图中的红色质心和蓝色质心,然后分别求样本中所有点到这两个质心的距离,并标记每个样本的类别为和该样本距离最小的质心的类别,如图c所示,经过计算样本和红色质心和蓝色质心的距离,我们得到了所有样本点的第一轮迭代后的类别。此时我们对我们当前标记为红色和蓝色的点分别求其新的质心,如图d所示,新的红色质心和蓝色质心的位置已经发生了变动。图e和图f重复了我们在图c和图d的过程,即将所有点的类别标记为距离最近的质心的类别并求新的质心。最终我们得到的两个类别如图f。当然在实际K-Mean算法中,我们一般会多次运行图c和图d,才能达到最终的比较优的类别。


2.传统K-Means算法流程

K-Means算法的一些要点:

1)对于K-Means算法,首先要注意的是k值的选择,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通过交叉验证选择一个合适的k值。

2)在确定了k的个数后,我们需要选择k个初始化的质心,就像上图b中的随机质心。由于我们是启发式方法,k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心,最好这些质心不能太近。


传统的K-Means算法流程:

输入是样本集 D = { x 1 , x 2 , . . . x m } D=\{x_1,x_2,...x_m\} D={x1,x2,...xm},聚类的簇数k,最大迭代次数N

输出是簇划分 C = { C 1 , C 2 , . . . C k } C=\{C_1,C_2,...C_k\} C={C1,C2,...Ck}

  1. 从数据集D中随机选择k个样本作为初始的k个质心向量: { μ 1 , μ 2 , . . . , μ k } \{\mu_1,\mu_2,...,\mu_k\} {μ1,μ2,...,μk}

2)对于n=1,2,...,N

a) 将簇划分C初始化为 C t = ∅      t = 1 , 2... k C_t = \varnothing \;\; t =1,2...k Ct=∅t=1,2...k

b) 对于i=1,2...m,计算样本 x i x_i xi和各个质心向量 u j u_j uj(j=1,2,...k)的距离: d i j = ∣ ∣ x i − μ j ∣ ∣ 2 2 d_{ij} = ||x_i - \mu_j||2^2 dij=∣∣xi−μj∣∣22,将 x i x_i xi标记为最小的 d i j d{ij} dij所对应的类别 λ i \lambda_i λi,此时更新 C λ i = C λ i ∪ { x i } C_{\lambda_i} = C_{\lambda_i} \cup \{x_i\} Cλi=Cλi∪{xi}

c) 对于j=1,2,...,k,对 C j C_j Cj中所有的样本点重新计算新的质心 μ j = 1 ∣ C j ∣ ∑ x ∈ C j x \mu_j = \frac{1}{|C_j|}\sum\limits_{x \in C_j}x μj=∣Cj∣1x∈Cj∑x

e) 如果所有的k个质心向量都没有发生变化,则转到步骤3)

3) 输出簇划分 C = { C 1 , C 2 , . . . C k } C=\{C_1,C_2,...C_k\} C={C1,C2,...Ck}


3. K-Means初始化优化K-Means++

k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心。如果仅仅是完全随机的选择,有可能导致算法收敛很慢。K-Means++算法就是对K-Means随机初始化质心的方法的优化。

K-Means++的对于初始化质心的优化策略也很简单,如下:

a) 从输入的数据点集合中随机选择一个点作为第一个聚类中心 μ 1 \mu_1 μ1

b) 对于数据集中的每一个点 x i x_i xi,计算它与已选择的聚类中心中最近聚类中心的距离 D ( x i ) = m i n ∣ ∣ x i − μ r ∣ ∣ 2 2      r = 1 , 2 , . . . k s e l e c t e d D(x_i) = min||x_i- \mu_r||2^2\;\;r=1,2,...k{selected} D(xi)=min∣∣xi−μr∣∣22r=1,2,...kselected

arg 是变元(即自变量argument)的英文缩写。arg min 就是使后面这个式子达到最小值时的变量的取值

arg max 就是使后面这个式子达到最大值时的变量的取值

例如 函数F(x,y):

arg min F(x,y)就是指当F(x,y)取得最小值时,变量x,y的取值

arg max F(x,y)就是指当F(x,y)取得最大值时,变量x,y的取值

c) 选择一个新的数据点作为新的聚类中心,选择的原则是: D ( x ) D(x) D(x)较大的点,被选取作为聚类中心的概率较大

d) 重复b和c直到选择出k个聚类质心

e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法


4.K-Means距离计算优化elkan K-Means

在传统的K-Means算法中,我们在每轮迭代时,要计算所有的样本点到所有的质心的距离,这样会比较的耗时。那么,对于距离的计算有没有能够简化的地方呢?elkan K-Means算法就是从这块入手加以改进。它的目标是减少不必要的距离的计算。那么哪些距离不需要计算呢?
e lkan K-Means利用了两边之和大于等于第三边,以及两边之差小于第三边的三角形性质,来减少距离的计算。

第一种规律是对于一个样本点 x x x和两个质心 μ j 1 , μ j 2 \mu_{j_1}, \mu_{j_2} μj1,μj2如果我们预先计算出了这两个质心之间的距离 D ( j 1 , j 2 ) D(j_1,j_2) D(j1,j2),则如果计算发现 2 D ( x , j 1 ) ≤ D ( j 1 , j 2 ) 2D(x,j_1) \leq D(j_1,j_2) 2D(x,j1)≤D(j1,j2),我们立即就可以知道 D ( x , j 1 ) ≤ D ( x , j 2 ) D(x,j_1) \leq D(x, j_2) D(x,j1)≤D(x,j2)。此时我们不需要再计算 D ( x , j 2 ) D(x, j_2) D(x,j2),也就是说省了一步距离计算。

第二种规律是对于一个样本点 x x x和两个质心 μ j 1 , μ j 2 \mu_{j_1}, \mu_{j_2} μj1,μj2。我们可以得到 D ( x , j 2 ) ≥ m a x { 0 , D ( x , j 1 ) − D ( j 1 , j 2 ) } D(x,j_2) \geq max\{0, D(x,j_1) - D(j_1,j_2)\} D(x,j2)≥max{0,D(x,j1)−D(j1,j2)}

利用上边的两个规律,elkan K-Means比起传统的K-Means迭代速度有很大的提高。但是如果我们的样本的特征是稀疏的,有缺失值的话,这个方法就不使用了,此时某些距离无法计算,则不能使用该算法。


5.大样本优化Mini Batch K-Means

在统的K-Means算法中,要计算所有的样本点到所有的质心的距离。如果样本量非常大,比如达到10万以上,特征有100以上,此时用传统的K-Means算法非常的耗时,就算加上elkan K-Means优化也依旧。在大数据时代,这样的场景越来越多。此时Mini Batch K-Means应运而生。

顾名思义,Mini Batch,也就是用样本集中的一部分的样本来做传统的K-Means,这样可以避免样本量太大时的计算难题,算法收敛速度大大加快。当然此时的代价就是我们的聚类的精确度也会有一些降低。一般来说这个降低的幅度在可以接受的范围之内。

在Mini Batch K-Means中,我们会选择一个合适的批样本大小batch size,我们仅仅用batch size个样本来做K-Means聚类。那么这batch size个样本怎么来的?一般是通过无放回的随机采样得到的。

为了增加算法的准确性,我们一般会多跑几次Mini Batch K-Means算法,用得到不同的随机采样集来得到聚类簇,选择其中最优的聚类簇。


6.K-Means与KNN

异:

  • K-Means是无监督学习的聚类算法,没有样本输出;而KNN是监督学习的分类算法,有对应的类别输出。
  • KNN基本不需要训练,对测试集里面的点,只需要找到在训练集中最近的k个点,用这最近的k个点的类别来决定测试点的类别。而K-Means则有明显的训练过程,找到k个类别的最佳质心,从而决定样本的簇类别。

同:

当然,两者也有一些相似点,两个算法都包含一个过程,即找出和某一个点最近的点。两者都利用了最近邻(nearest neighbors)的思想。


7.K-Means小结

K-Means是个简单实用的聚类算法,这里对K-Means的优缺点做一个总结。
K-Means的主要优点有:

1)原理比较简单,实现也是很容易,收敛速度快。

2)聚类效果较优。

3)算法的可解释度比较强。

4)主要需要调参的参数仅仅是簇数k。

K-Means的主要缺点有:

1)K值的选取不好把握

2)对于不是凸的数据集比较难收敛

凸集:如果一个数据集D是凸的,那么对于其中任意的两点x,y∈D,θ∈R, 0≤θ≤1,则 θ x + ( 1 − θ ) y ∈ D表达式θx+(1−θ)y被称作点x , y 的 凸性组合(convex combination)简单来说,数据集D中任意两点的连线上的点,也会在数据集D内,那么数据集D就是一个凸集    3)如果各隐含类别的数据不平衡,比如各隐含类别的数据量严重失衡,或者各隐含类别的方差不同,则聚类效果不佳。

4) 采用迭代方法,得到的结果只是局部最优。

5) 对噪音和异常点比较的敏感。


8.用scikit-learn学习K-Means聚类

用scikit-learn学习K-Means聚类 - 刘建平Pinard - 博客园

重点讲述如何选择合适的k值。

8.1 K-Means类概述

在scikit-learn中,包括两个K-Means的算法,一个是传统的K-Means算法,对应的类是KMeans。另一个是基于采样的Mini Batch K-Means算法,对应的类是MiniBatchKMeans。一般来说,使用K-Means的算法调参是比较简单的。

用KMeans类的话,一般要注意的仅仅就是k值的选择,即参数n_clusters;如果是用MiniBatchKMeans的话,也仅仅多了需要注意调参的参数batch_size,即我们的Mini Batch的大小。

当然KMeans类和MiniBatchKMeans类可以选择的参数还有不少,但是大多不需要怎么去调参。下面我们就看看KMeans类和MiniBatchKMeans类的一些主要参数。


8.2KMeans类主要参数

KMeans类的主要参数有:

1) n_clusters: 即我们的k值,一般需要多试一些值以获得较好的聚类效果。k值好坏的评估标准在下面会讲。

2)max_iter: 最大的迭代次数,一般如果是凸数据集 的话可以不管这个值,如果数据集不是凸的,可能很难收敛,此时可以指定最大的迭代次数让算法可以及时退出循环。

3)n_init:用不同的初始化质心运行算法的次数。由于K-Means是结果受初始值影响的局部最优的迭代算法,因此需要多跑几次以选择一个较好的聚类效果,默认是10,一般不需要改。如果你的k值较大,则可以适当增大这个值。

4)init: 即初始值选择的方式,可以为完全随机选择'random',优化过的'k-means++'或者自己指定初始化的k个质心。一般建议使用默认的'k-means++'。

5)algorithm:有"auto", "full" or "elkan"三种选择。"full"就是我们传统的K-Means算法, "elkan"是我们原理篇讲的elkan K-Means算法。默认的"auto"则会根据数据值是否是稀疏的,来决定如何选择"full"和"elkan"。一般数据是稠密的,那么就是 "elkan",否则就是"full"。一般来说建议直接用默认的"auto"


8.3MiniBatchKMeans类主要参数

MiniBatchKMeans类的主要参数比KMeans类稍多,主要有:

1) n_clusters: 即我们的k值,和KMeans类的n_clusters意义一样。

2)max_iter:最大的迭代次数, 和KMeans类的max_iter意义一样。

3)n_init:用不同的初始化质心运行算法的次数。这里和KMeans类意义稍有不同,KMeans类里的n_init是用同样的训练集数据来跑不同的初始化质心从而运行算法。而MiniBatchKMeans类的n_init则是每次用不一样的采样数据集来跑不同的初始化质心运行算法。

4)batch_size:即用来跑Mini Batch KMeans算法的采样集的大小,默认是100.如果发现数据集的类别较多或者噪音点较多,需要增加这个值以达到较好的聚类效果。

5)init: 即初始值选择的方式,和KMeans类的init意义一样。

6)init_size: 用来做质心初始值候选的样本个数,默认是batch_size的3倍,一般用默认值就可以了。

7)reassignment_ratio: 某个类别质心被重新赋值的最大次数比例,这个和max_iter一样是为了控制算法运行时间的。这个比例是占样本总数的比例,乘以样本总数就得到了每个类别质心可以重新赋值的次数。如果取值较高的话算法收敛时间可能会增加,尤其是那些暂时拥有样本数较少的质心。默认是0.01。如果数据量不是超大的话,比如1w以下,建议使用默认值。如果数据量超过1w,类别又比较多,可能需要适当减少这个比例值。具体要根据训练集来决定。

8)max_no_improvement:即连续多少个Mini Batch没有改善聚类效果的话,就停止算法, 和reassignment_ratio, max_iter一样是为了控制算法运行时间的。默认是10.一般用默认值就足够了。


8.4K值的评估标准

不像监督学习的分类问题和回归问题,我们的无监督聚类没有样本输出,也就没有比较直接的聚类评估方法。但是我们可以从簇内的稠密程度和簇间的离散程度来评估聚类的效果 。常见的方法有轮廓系数Silhouette Coefficient和Calinski-Harabasz Index。个人比较喜欢Calinski-Harabasz Index,这个计算简单直接,得到的Calinski-Harabasz分数值s

越大则聚类效果越好。

Calinski-Harabasz分数值s的数学计算公式是:
s ( k ) = t r ( B k ) t r ( W k ) m − k k − 1 s(k) = \frac{tr(B_k)}{tr(W_k)} \frac{m-k}{k-1} s(k)=tr(Wk)tr(Bk)k−1m−k

其中m为训练集样本数,k为类别数。 B k B_k Bk为类别之间的协方差矩阵, W k W_k Wk为类别内部数据的协方差矩阵。 t r tr tr为矩阵的迹。

也就是说,类别内部数据的协方差越小越好,类别之间的协方差越大越好,这样的Calinski-Harabasz分数会高。在scikit-learn中, Calinski-Harabasz Index对应的方法是metrics.calinski_harabaz_score.


8.5K-Means应用实例

Kaggle:链接

创建数据

python 复制代码
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import make_blobs
# X为样本特征,Y为样本簇类别, 共1000个样本,每个样本2个特征,共4个簇,簇中心在[-1,-1], [0,0],[1,1], [2,2], 簇方差分别为[0.4, 0.2, 0.2]
X, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1,-1], [0,0], [1,1], [2,2]], cluster_std=[0.4, 0.2, 0.2, 0.2], 
                  random_state =9)
plt.scatter(X[:, 0], X[:, 1], marker='o')
plt.show()

sklearn.make_blobs(n_samples=100, n_features=2, centers=None, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None)(一)函数功能

生成各向同性的高斯斑点以进行聚类。


(二)参数

n_samples:int或数组类,可选参数(默认值= 100)

如果为int,则为在簇之间平均分配的点总数。 如果是数组,则序列中的每个元素表示每个簇的样本数。

  • n_features:int,可选(默认值= 2)每个样本的特征数量。
  • centers:int或数组(x,y)坐标,可选(默认= None)要生成的中心数或固定的中心位置。 如果n_samples是一个int且center为None,则将生成3个中心。 如果n_samples是数组类,则中心必须为None或长度等于n_samples长度的数组。
  • cluster_std: 浮点数或浮点数序列,可选(默认值为1.0)聚类的标准偏差。
  • center_box: 一对浮点数(最小,最大),可选(默认=(-10.0,10.0))随机生成中心时每个聚类中心的边界框。
  • shuffle:布尔值,可选(默认= True)样本洗牌
  • random_state:int,RandomState实例或无(默认)。确定用于创建数据集的随机数生成。 为多个函数调用传递可重复输出的int值。

(三)返回值

形状为[n_samples,n_features]的X数组

形状为[n_samples]的y数组


python 复制代码
from sklearn.cluster import KMeans
y_pred = KMeans(n_clusters=2, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()
python 复制代码
from sklearn import metrics
metrics.calinski_harabasz_score(X, y_pred)  
'''
3116.1706763322227
'''

python 复制代码
from sklearn.cluster import KMeans
y_pred = KMeans(n_clusters=3, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

metrics.calinski_harabasz_score(X, y_pred)  
'''
2931.625030199556
'''

python 复制代码
from sklearn.cluster import KMeans
y_pred = KMeans(n_clusters=4, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()


metrics.calinski_harabasz_score(X, y_pred)  
'''
5924.050613480169
'''

8.6MiniBatchKMeans

python 复制代码
from sklearn.cluster import MiniBatchKMeans
y_pred = MiniBatchKMeans(n_clusters=2, batch_size = 200, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

metrics.calinski_harabasz_score(X, y_pred)  
'''
3116.1706763322227
'''
python 复制代码
from sklearn.cluster import MiniBatchKMeans
y_pred = MiniBatchKMeans(n_clusters=3, batch_size = 200, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

metrics.calinski_harabasz_score(X, y_pred)  
'''
2931.625030199556
'''
python 复制代码
from sklearn.cluster import MiniBatchKMeans
y_pred = MiniBatchKMeans(n_clusters=4, batch_size = 200, random_state=9).fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()

metrics.calinski_harabasz_score(X, y_pred)  
'''
5921.45496000146
'''

8.7可视化子图

  • 以下两种输出图一致
python 复制代码
plt.subplots_adjust(left=.02, right=.98, bottom=.096, top=.96, wspace=.05,
                    hspace=.01)
plt.subplot(2,2,1)
y_pred = MiniBatchKMeans(n_clusters=2, batch_size = 200, random_state=9).fit_predict(X)
score2= metrics.calinski_harabasz_score(X, y_pred)  
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.text(.99, .01, ('k=%d, score: %.2f' % (2,score2)),
                 transform=plt.gca().transAxes, size=10,
                 horizontalalignment='right')


plt.subplot(2,2,2)
y_pred = MiniBatchKMeans(n_clusters=3, batch_size = 200, random_state=9).fit_predict(X)
score3= metrics.calinski_harabasz_score(X, y_pred)  
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.text(.99, .01, ('k=%d, score: %.2f' % (3,score3)),
                 transform=plt.gca().transAxes, size=10,
                 horizontalalignment='right')

plt.subplot(2,2,3)
y_pred = MiniBatchKMeans(n_clusters=4, batch_size = 200, random_state=9).fit_predict(X)
score4= metrics.calinski_harabasz_score(X, y_pred)  
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.text(.99, .01, ('k=%d, score: %.2f' % (4,score4)),
                 transform=plt.gca().transAxes, size=10,
                 horizontalalignment='right')

plt.subplot(2,2,4)
y_pred = MiniBatchKMeans(n_clusters=5, batch_size = 200, random_state=9).fit_predict(X)
score5 = metrics.calinski_harabasz_score(X, y_pred)  
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.text(.99, .01, ('k=%d, score: %.2f' % (5,score5)),
                 transform=plt.gca().transAxes, size=10,
                 horizontalalignment='right')
plt.show()

python 复制代码
plt.subplots_adjust(left=.02, right=.98, bottom=.096, top=.96, wspace=.1,
                    hspace=.1)
for index, k in enumerate((2,3,4,5)):
    plt.subplot(2,2,index+1)
    y_pred = MiniBatchKMeans(n_clusters=k, batch_size = 200, random_state=9).fit_predict(X)
    score= metrics.calinski_harabasz_score(X, y_pred)  
    plt.scatter(X[:, 0], X[:, 1], c=y_pred)
    plt.text(.99, .01, ('k=%d, score: %.2f' % (k,score)),
                 transform=plt.gca().transAxes, size=10,
                 horizontalalignment='right')
plt.show()

plt.subplots_adjust(left=.02, right=.98, bottom=.096, top=.96, wspace=.1, hspace=.1)

这段代码使用matplotlib库中的subplots_adjust函数调整子图的布局。left、right、bottom和top参数分别控制子图区域的左、右、下和上的边缘位置,以便在画布上留出合适的空白边距。wspace和hspace参数分别控制子图之间的水平和垂直间距。这些参数的值都是相对于画布宽度或高度的比例。

plt.text(.99, .01, ('k=%d, score: %.2f' % (k,score)),

transform=plt.gca().transAxes, size=10,

horizontalalignment='right')

.99, .01:图的右下角

文本内容包括聚类数量k、对应的轮廓系数score。其中,%d和%.2f是占位符,分别表示整数和保留两位小数的浮点数。transform参数指定了文本的坐标系,plt.gca()返回当前坐标系,transAxes表示使用轴域坐标系。size参数控制了文本的字体大小,horizontalalignment参数指定了文本的水平对齐方式为右对齐。


参考

博客

侵权删

相关推荐
qq_433554544 分钟前
C++ 面向对象编程:递增重载
开发语言·c++·算法
带多刺的玫瑰25 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
巫师不要去魔法部乱说35 分钟前
PyCharm专项训练5 最短路径算法
python·算法·pycharm
IT古董42 分钟前
【漫话机器学习系列】020.正则化强度的倒数C(Inverse of regularization strength)
人工智能·机器学习
进击的小小学生1 小时前
机器学习连载
人工智能·机器学习
Trouvaille ~1 小时前
【机器学习】从流动到恒常,无穷中归一:积分的数学诗意
人工智能·python·机器学习·ai·数据分析·matplotlib·微积分
qystca1 小时前
洛谷 P11242 碧树 C语言
数据结构·算法
冠位观测者1 小时前
【Leetcode 热题 100】124. 二叉树中的最大路径和
数据结构·算法·leetcode
悲伤小伞1 小时前
C++_数据结构_详解二叉搜索树
c语言·数据结构·c++·笔记·算法
m0_675988232 小时前
Leetcode3218. 切蛋糕的最小总开销 I
c++·算法·leetcode·职场和发展