聚类算法KMeans是无监督学习的杰出代表之一。本文是记录自己过去学习KMeans算法的系统小结,将从“KMeans简介,优缺点与优化策略,结合EM算法解释KMeans以及手推KMeans”几个方面来尽可能彻底、清晰地搞明白这个算法,希望对大家能有所帮助。
K-Means算法,也被称为K-平均或K-均值算法,是一种广泛使用的聚类算法。
与分类、序列标注等任务不同,聚类是在事先并不知道任何样本标签的情况下,通过数据之间的内在关系把样本划分为若干类别,使得同类别样本之间的相似度高,不同类别之间的样本相似度低(即增大类内聚,减少类间距)。
K-Means算法是聚焦于相似的无监督的算法,以距离作为数据对象间相似性度量的标准,即数据对象间的距离越小,则它们的相似性越高,则它们越有可能在同一个类簇。之所以被称为K-Means是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。
在介绍K-Means算法之前,先讨论下簇识别(cluster identification)。簇识别给出聚类结果的含义。假定有一些数据,现在将相似的数据归在一起,簇识别会告诉我们这些簇到达都是些什么。聚类和分类最大的不同在于,分类的目标事先已知,而聚类则不一样。因为其产生的结果和分类相同,而只是类别没有预先定义。K-Means是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心(centroid),即簇中所有点的中心来描述。
这个算法其实很简单,如下图所示:
从上图中,我们可以看到,A, B, C, D, E 是五个在图中点。而灰色的点是我们的种子点,也就是我们用来找点群的点。有两个种子点,所以K=2。
算法中使用到距离可以是任何的距离计算公式,最常用的是欧氏距离,应用时具体应该选择哪种距离计算方式,需要根据具体场景确定。
import numpy as np
from matplotlib import pyplot as plt
def euclidean_distance(vecA, vecB):
'''计算vecA与vecB之间的欧式距离'''
# return np.sqrt(np.sum(np.square(vecA - vecB)))
return np.linalg.norm(vecA - vecB)
def random_centroids(data, k):
''' 随机创建k个中心点'''
dim = np.shape(data)[1] # 获取向量的维度
centroids = np.mat(np.zeros((k, dim)))
for j in range(dim): # 随机生成每一维中最大值和最小值之间的随机数
min_j = np.min(data[:, j])
range_j = np.max(data[:, j]) - min_j
centroids[:, j] = min_j * np.mat(np.ones((k, 1))) + np.random.rand(k, 1) * range_j
return centroids
def KMeans(data, k, distance_func=euclidean_distance):
'''根据k-means算法求解聚类的中心'''
m = np.shape(data)[0] # 获得行数m
cluster_assment = np.mat(np.zeros((m, 2))) # 初试化一个矩阵,用来记录簇索引和存储距离平方
centroids = random_centroids(data, k) # 生成初始化点
cluster_changed = True # 判断是否需要重新计算聚类中心
while cluster_changed:
cluster_changed = False
for i in range(m):
distance_min = np.inf # 设置样本与聚类中心之间的最小的距离,初始值为正无穷
index_min = -1 # 所属的类别
for j in range(k):
distance_ji = distance_func(centroids[j, :], data[i, :])
if distance_ji &distance_min:
distance_min = distance_ji
index_min = j
if cluster_assment[i, 0] != index_min:
cluster_changed = True
cluster_assment[i, :] = index_min, distance_min ** 2 # 存储距离平方
for cent in range(k): # 更新质心,将每个族中的点的均值作为质心
pts_in_cluster = data[np.nonzero(cluster_assment[:, 0].A == cent)[0]]
centroids[cent, :] = np.mean(pts_in_cluster, axis=0)
return centroids, cluster_assment
def show_cluster(data, k, centroids, cluster_assment):
num, dim = data.shape
mark = ['or', 'ob', 'og', 'oy', 'oc', 'om']
for i in range(num):
mark_index = int(cluster_assment[i, 0])
plt.plot(data[i, 0], data[i, 1], mark[mark_index])
for i in range(k):
plt.plot(centroids[i, 0], centroids[i, 1], 'o', markeredgecolor='k', markersize=16)
plt.show()
if __name__ == "__main__":
data = []
f = open("sz.txt", 'r')
for line in f:
data.append([float(line.split(',')[0]), float(line.split(',')[1])])
data = np.array(data)
k = 4
centroids = random_centroids(data, k)
centroids, cluster_assment = KMeans(data, k)
show_cluster(data, k, centroids, cluster_assment)
优点:
简单,快速,适合常规数据集
劣势:
k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心。如果仅仅是完全随机的选择,有可能导致算法收敛很慢。K-Means++算法就是对K-Means随机初始化质心的方法的优化。K-Means++算法与K-Means算法最本质的区别是在k个聚类中心的初始化过程。
K-Means++算法在聚类中心的初始化过程中的基本原则是使得初始的聚类中心之间的相互距离尽可能远,这样可以避免出现上述的问题。K-Means++算法的初始化过程如下所示:
def nearest(data, cluster_centers, distance_func=euclidean_distance):
min_dist = np.inf
m = np.shape(cluster_centers)[0] # 当前已经初始化的聚类中心的个数
for i in range(m):
d = distance_func(data, cluster_centers[i, ]) # 计算point与每个聚类中心之间的距离
if min_dist > d: # 选择最短距离
min_dist = d
return min_dist
def get_centroids(data, k, distance_func=euclidean_distance):
m, n = np.shape(data)
cluster_centers = np.mat(np.zeros((k, n)))
index = np.random.randint(0, m) # 1、随机选择一个样本点为第一个聚类中心
cluster_centers[0, ] = np.copy(data[index, ])
d = [0.0 for _ in range(m)] # 2、初始化一个距离的序列
for i in range(1, k):
sum_all = 0
for j in range(m):
d[j] = nearest(data[j, ], cluster_centers[0:i, ], distance_func) # 3、对每一个样本找到最近的聚类中心点
sum_all += d[j] # 4、将所有的最短距离相加
sum_all *= random() # 5、取得sum_all之间的随机值
for j, di in enumerate(d): # 6、获得距离最远的样本点作为聚类中心点
sum_all -= di
if sum_all > 0:
continue
cluster_centers[i] = np.copy(data[j, ])
break
return cluster_centers
执行结果:(计算出来的最终聚类中心与K-Means在正常情况下计算出来的聚类中心一致)
K-Means是一种最大期望算法,这类算法会在“期望”和“最大化”两个阶段不断迭代。比如K-Means的期望阶段是将各个点分配到它们所“期望”的分类中,然后在最大化阶段重新计算中心点的位置。再继续讨论K-Means算法之前,我想先介绍一下登山式算法。
假设我们想要登上一座山的顶峰,可以通过以下步骤实现:
无论我们从哪个点开始攀登,最终都可以达到顶峰。但对于下面这张图:
所以说,这种简单的登山式算法并不一定能得到最优解。K-Means就是这样一种算法,它不能保证最终结果是最优的,因为我们一开始选择的中心点是随机的,很有可能就会选到上面的A点,最终获得局部最优解B点。因此,最终的聚类结果和起始点的选择有很大关系。但尽管如此,K-Means通常还是能够获得良好的结果的。
K-Means算法收敛,但是聚类效果较差的原因是,K-Means算法收敛到了局部最小值,而非全局最小值(局部最小值指结果还可以但并非最好结果,全局最小值是可能的最好结果)。
一种用于度量聚类效果的指标是SSE(Sum of Squared Error,误差平方和)。SSE值越小表示数据点越接近他们的质心,聚类效果也最好。因为对误差取了平方,因此更加重视远离中心的点。一种肯定可以降低SSE的方法是增加族的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
如何对下图的的结果进行改进?你只可以多生成的簇进行后处理,一种方法是将具有最大SSE值的簇划分成为2个簇。具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-Means算法,其中k设为2。
为了保持簇总数不变,可以将某两个簇进行合并。从上图中很明显就可以看出,应该将上图下部两个出错的簇质心进行合并。那么问题来了,我们可以很容易对二维数据上的聚类进行可视化, 但是如果遇到40维的数据应该如何去做?
有两种可以量化的办法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。 第一种思路通过计算所有质心之间的距离, 然后合并距离最近的两个点来实现。第二种方法需要合并两个簇然后计算总SSE值。必须在所有可能的两个簇上重复上述处理过程,直到找到合并最佳的两个簇为止。接下来将讨论利用上述簇划分技术得到更高的聚类结果的方法。
为克服K-Means算法收敛于局部最小的问题,有人提出了另一种称为二分K-Means(bisecting K-Means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分时候可以最大程度降低 SSE(平方和误差)的值。上述基于 SSE 的划分过程不断重复,直到得到用户指定的簇数目为止。
二分 K-Means 聚类算法伪代码:
传统的K-Means算法中需要计算所有样本点到所有质心的距离,计算复杂度较高。如果样本量非常大的情况下,比如数据量达到10万,特征在100以上,此时用传统K-Means算法非常耗时。因此有了一种分批处理的改进算法Mini Batch K-Means。
Mini Batch K-Means算法是K-Means算法的变种,采用小批量的数据子集减小计算时间,同时仍试图优化目标函数,这里所谓的小批量是指每次训练算法时所随机抽取的数据子集,采用这些随机产生的子集进行训练算法,大大减小了计算时间,与其他算法相比,减少了k-均值的收敛时间,小批量k-均值产生的结果,一般只略差于标准算法。
该算法的迭代步骤有两步:
Mini Batch K-Means比K-Means有更快的 收敛速度,但同时也降低了聚类的效果,但是在实际项目中却表现得不明显,有差异的基本都是聚类边界上的点。这是一张k-means和mini batch k-means的实际效果对比图。
K-Means是个简单实用的聚类算法,这里对K-Means的优缺点做一个总结。
K-Means的主要优点有:
K-Means的主要缺点有:
方阵作为线性算子,它的所有特征值的全体统称为谱。
谱聚类是一种基于图论的聚类方法,通过对样本数据的拉普拉斯矩阵的特征向量进行聚类,从而达到样本数据聚类的目的。
拉普拉斯LE详细解释请通过:https://blog.csdn.net/weixin_48167570/article/details/124056136