K-means聚类算法及其变种详解

参考:

  1. https://www.cnblogs.com/txx120/p/11487674.html
  2. https://www.biaodianfu.com/k-means.html

一、K-means算法:

1. 概述

  • K-means 聚类算法也称 K均值聚类算法,是集简单和经典于一身的基于距离的聚类算法。它采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似度就越大。该算法认为类簇是由距离靠近的对象组成的,因此把得到紧凑且独立的簇作为最终目标。

2. 核心思想

  • K-means 聚类算法是一种迭代求解的聚类分析算法,其步骤是:随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类;或没有(或最小数目)聚类中心再发生变化;或误差平方和局部最小。

3. 实现步骤

  1. 首先确定一个K值,即:我们希望将数据集经过聚类得到K个集合。
  2. 从数据集中随机选择K个数据点作为质心。
  3. 对数据集中每一个点,计算其与每一个质心的距离(如欧式距离),离哪个质心近,就划分到那个质心所属的集合。
  4. 把所有数据归好集合后,一共有K个集合,然后重新计算每个集合的质心。
  5. 如果新计算出来的质心和原来的质心之间的距离小于某一个设置的阈值(表示重新计算的质心的位置变化不大,趋于稳定,或者说收敛),我们可以认为聚类已经达到期望的结果,算法终止。
  6. 如果新质心和原质心距离变化很大,需要迭代 3~5 步骤。

4. 步骤图解

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

5. K-means术语

簇:所有数据的点集合,簇中的对象是相似的。
质心:簇中所有点的中心(计算所有点的中心而来)。

6. 算法优缺点

6.1 优点:

  1. 原理比较简单,实现也是很容易,收敛速度快;
  2. 当结果簇是密集的,而簇与簇之间区别明显时, 它的效果较好;
  3. 主要需要调参的参数仅仅是簇数K。

6.2 缺点:

  1. K值需要预先给定,很多情况下K值的估计是非常困难的;
  2. K-Means 算法对初始选取的质心点是敏感的,不同的随机种子点得到的聚类结果完全不同 ,对结果影响很大;
  3. 对噪音和异常点比较的敏感,可用来检测异常值;
  4. 采用迭代方法,可能只能得到局部的最优解,而无法得到全局的最优解。

二、K-means++算法:

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

1. 基本思路

K-Means++算法在聚类中心的初始化过程中的基本原则是使得初始的聚类中心之间的相互距离尽可能远,这样可以避免出现上述的问题。K-Means++算法的初始化过程如下所示:

  1. 在数据集中随机选择一个样本点作为第一个初始化的聚类中心;
  2. 选择出其余的聚类中心:计算样本中的每一个样本点与已经初始化的聚类中心之间的距离,并选择其中最短的距离,记为d_i;
  3. 选择一个新的数据点作为新的聚类中心,选择的原则是:距离较大的点,被选取作为聚类中心的概率较大;
  4. 重复上述过程,直到k个聚类中心都被确定;
  5. 对k个初始化的聚类中心,利用K-Means算法计算最终的聚类中心。

2. Python实现:

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算法:

1. 相关知识:

  • K-Means是一种最大期望算法,这类算法会在“期望”和“最大化”两个阶段不断迭代。比如K-Means的期望阶段是将各个点分配到它们所“期望”的分类中,然后在最大化阶段重新计算中心点的位置。
  • 在继续讨论K-Means算法之前,先介绍一下登山式算法。假设我们想要登上一座山的顶峰,可以通过以下步骤实现:
  1. 在山上随机选取一个点作为开始;
  2. 向高处爬一点;
  3. 重复第2步,直到没有更高的点。
    这种做法看起来很合理,比如对于下图所示的山峰:
    在这里插入图片描述
    无论我们从哪个点开始攀登,最终都可以达到顶峰。但对于下面这张图:
    在这里插入图片描述
    所以说,这种简单的登山式算法并不一定能得到最优解。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值。必须在所有可能的两个簇上重复上述处理过程,直到找到合并最佳的两个簇为止。
    接下来将讨论利用上述簇划分技术得到更高的聚类结果的方法。

2. 基本思想:

  • 为克服K-Means算法收敛于局部最小的问题,有人提出了另一种称为二分K-Means(bisecting K-Means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分时候可以最大程度降低 SSE(平方和误差)的值。上述基于 SSE 的划分过程不断重复,直到得到用户指定的簇数目为止。
  • 二分 K-Means 聚类算法基本思想:
  1. 将所有点看成一个簇;
  2. 当簇数目小于 k 时,对于每一个簇计算总误差;
  3. 在给定的簇上面进行 K-Means 聚类(k=2);
  4. 计算将该簇一分为二之后的总误差;
  5. 选择使得误差最小的那个簇进行划分操作;另一种做法是选择 SSE 最大的簇进行划分,直到簇数目达到用户指定的数目位置。

3. Python实现:

def bisecting_KMeans(data, k, distance_func=euclidean_distance):
    m = np.shape(data)[0]  # 获得行数m
    cluster_assment = np.mat(np.zeros((m, 2)))  # 初试化一个矩阵,用来记录簇索引和存储距离平方
    centroid0 = np.mean(data, axis=0).tolist()[0]  # 质心初始化为所有数据点的均值
    cent_list = [centroid0]  # 初始化只有1个质心的list
    for j in range(m):  # 计算所有数据点到初始质心的距离平方误差
        cluster_assment[j, 1] = distance_func(np.mat(centroid0), data[j, :]) ** 2  # 计算距离的评分
    while len(cent_list) < k:
        lowest_SSE = np.inf
        for i in range(len(cent_list)):
            pts_in_curr_cluster = data[np.nonzero(cluster_assment[:, 0].A == i)[0], :]  # 获取当前簇i下的所有数据点
            centroid_mat, split_cluster_ass = KMeans(pts_in_curr_cluster, 2, distance_func)  # 将当前簇i进行二分kMeans处理
            sse_split = sum(split_cluster_ass[:, 1])  # 将二分 kMeans 结果中的平方和的距离进行求和
            sse_not_split = sum(
                cluster_assment[np.nonzero(cluster_assment[:, 0].A != i)[0], 1])  # 将未参与二分kMeans分配结果中的平方和的距离进行求和
            if (sse_split + sse_not_split) < lowest_SSE:  # 总(未拆分和已拆分)误差和越小,越相似,效果越优化,划分的结果更好
                best_cent_to_split = i
                best_new_cents = centroid_mat
                best_cluster_ass = split_cluster_ass.copy()
                lowest_SSE = sse_split + sse_not_split
        # 找出最好的簇分配结果
        best_cluster_ass[np.nonzero(best_cluster_ass[:, 0].A == 1)[0], 0] = len(cent_list)  # 调用二分 kMeans 的结果,默认簇是 0,1. 当然也可以改成其它的数字
        best_cluster_ass[np.nonzero(best_cluster_ass[:, 0].A == 0)[0], 0] = best_cent_to_split  # 更新为最佳质心
        # 更新质心列表
        cent_list[best_cent_to_split] = best_new_cents[0, :].tolist()[0]  #更新原质心list中的第i个质心为使用二分kMeans后bestNewCents的第一个质心
        cent_list.append(best_new_cents[1, :].tolist()[0])  # 添加 bestNewCents 的第二个质心
        cluster_assment[np.nonzero(cluster_assment[:, 0].A == best_cent_to_split)[0],:] = best_cluster_ass  # 重新分配最好簇下的数据(质心)以及SSE
    return np.mat(cent_list), cluster_assment
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值