[机器学习]kmeans算法(实战)

178 阅读18分钟

1/前言

非监督学习
欧式距离,采用距离的大小,作为衡量样本之间相似性的大小。
 
<1>K-means算法是很典型的基于距离(可以是欧式距离,或者别的距离)的聚类算法,
    采用距离作为相似性的评价指标,即认为两个数据点之间的距离越近,其相似度就越大。
<2>该算法认为簇是由距离靠近的若干个数据点组成的,因此把得到【紧凑且独立的簇】作为最终的目标。
<3>k-means算法特点在于:
      同一簇内的数据点相似度较高;
      而不同簇内的数据点之间相似度较小。 
  
  这不是废话吗,聚类本来就是干这事的,好吧。

2/K-means算法实现过程

<1>从上图中可以知道,数据集合中一共有5个数据点,及ABCDE。这里计划分成了2个簇。

<2>然后对图中的所有数据点,分别计算其到这2个初始聚类中心的距离
   上图中,我们可以看到A,B属于上面种子的点群,C,D,E属于下面种子的点群。
   
<3>接下来,我们重新计算每个簇的聚类中心。
   计算簇中所有数据点的均值。
   
<4>然后重复第<2>和第<3>步,直到每个簇的聚类中心不再发生变化,
   我们可以看到图中的第四步上面的种子点聚合了A,B,C,下面的种子点聚合了D,E。 

3/kmeans算法的目标函数objective function

K-means算法的目标函数是【最小化】所有点到其对应聚类中心的距离的平方和。

具体来说,假设我们有数据集X={x1​,x2​,...,xn​},其中每个数据点xi​是一个d维向量,K-means算法试图将这些数据点分成K个簇(clusters),每个簇由一个聚类中心(centroid)表示。

设C={c1​,c2​,...,cK​}是K个聚类中心的集合,其中ck​是第k个簇的聚类中心。

对于每个数据点xi​,我们定义rik​为一个二值变量,如果xi​被分配到第k个簇,则rik​=1,否则rik​=0。

K-means算法的目标函数(也称为失真函数或成本函数)可以表示为:

J=∑i=1n​∑k=1K​rik​∥xi​−ck​∥2

其中,∥xi​−ck​∥2是数据点xi​与其聚类中心ck​之间的欧几里得距离的平方。由于每个数据点只属于一个簇,所以rik​的每一行只有一个1(其余为0),这确保了每个数据点只对其所属的簇的聚类中心有贡献。

K-means算法的目标是最小化这个目标函数J,即找到最佳的聚类中心集合C,使得所有点到其对应聚类中心的距离的平方和最小。这通常通过迭代过程来实现,包括两个主要步骤:

  1. 分配步骤:将每个数据点分配到最近的聚类中心所在的簇。
  2. 更新步骤:重新计算每个簇的聚类中心,通常取簇内所有点的均值。

这两个步骤交替进行,直到满足某个停止条件(如聚类中心不再发生变化,或达到预设的迭代次数)。

4/kmeans()算法的核心参数有哪些

K-Means算法的参数在不同的编程语言和库中可能有所不同,但通常包括一些基本的参数。
以Python的`scikit-learn`库中的`KMeans`类为例,其主要的参数包括:
  1. n_clusters

    • 类型:int,默认为8。
    • 说明:表示希望将数据分成的簇的个数,即聚类的数量K。
  2. init

    • 类型:{'k-means++','random'或者一个ndarray数组或者可迭代的对象},默认为'k-means++'。
    • 说明:初始簇中心的获取方法。'k-means++'是一种智能的初始化方法,能加速收敛;'random'则是从数据中随机选择初始中心点。
  3. n_init

    • 类型:int,默认为10。
    • 说明:算法运行的次数,每次运行都会使用不同的初始质心位置。最终解是在inertia(即聚类效果评估指标)意义上最好的结果。
  4. max_iter

    • 类型:int,默认为300。
    • 说明:算法运行的最大迭代次数。如果算法收敛,则可能在达到最大迭代次数之前停止。
  5. tol

    • 类型:float,默认为1e-4。
    • 说明:算法收敛的容忍度阈值。如果连续两次迭代之间的聚类中心变化小于tol,则算法停止。
  6. precompute_distances

    • 类型:{'auto',True,False},默认为'auto'。
    • 说明:是否提前计算所有样本点之间的距离。这可以节省后续迭代中的距离计算时间,但会增加算法的内存消耗。
  7. verbose

    • 类型:int,默认为0。
    • 说明:冗长模式。如果非零,则在训练过程中打印进度信息。
  8. random_state

    • 类型:int,RandomState实例或者None,默认为None。
    • 说明:用于初始化质心的随机数生成器的种子。如果设置了随机种子,则每次运行算法时得到的聚类结果将是一致的。
  9. copy_x

    • 类型:bool,默认为True。
    • 说明:是否复制X数据,以便不修改原始数据。如果设置为False,则直接在原始数据上进行修改,这可能会节省内存,但会修改原始数据。
  10. n_jobs

    • 类型:int或None,默认为None。
    • 说明:并行运行的作业数。如果为-1,则使用所有CPU。这可以加速算法的执行,特别是在处理大型数据集时。
  11. algorithm

    • 类型:{'auto','full','elkan'},默认为'auto'。
    • 说明:用于实现K-Means的算法。'full'表示使用传统的K-Means算法;'elkan'是一种更快的变体,特别适用于数据集较大且簇较密集的情况。'auto'将自动选择最合适的算法。

请注意,这些参数可能会根据scikit-learn库的版本而有所变化,因此建议查阅最新的官方文档以获取最准确的信息。

3/n_init参数和max_iter参数的区别

`kmeans()` 函数,在 Python 的 `sklearn.cluster` 模块中,
通常指的是 `KMeans` 类的一个实例化对象的方法或是对该算法的调用。
然而,直接讨论 `kmeans()` 函数的参数可能有些误导,因为通常我们是在讨论 `KMeans` 类的参数。
关于 `n_init``max_iter` 这两个参数,它们在 `KMeans` 类中的作用和区别如下:

<1>n_init

定义n_init 参数指定了算法使用不同质心初始化方法运行算法的次数。

作用:由于 K-Means 算法的结果可能受到初始质心选择的影响(因为每次都是随机选择的),因此通过多次运行算法(每次使用不同的初始质心),并从多次运行结果中选择最优的(通常是惯性最小的)结果,可以提高算法的稳定性和聚类效果。

默认值:在大多数 sklearn 版本中,n_init 的默认值是 10。

<2>max_iter

定义max_iter 参数指定了算法的最大迭代次数。

作用:K-Means 算法是一个【迭代】算法,它通过不断更新质心的位置来优化聚类结果。然而,为了防止算法无限迭代下去(尤其是在某些情况下可能难以收敛),需要设置一个最大迭代次数。当算法达到最大迭代次数时,无论是否收敛,都会停止迭代。

默认值:在大多数 sklearn 版本中,max_iter 的默认值是 300。

<3>注意事项

  • 在实际应用中,需要根据数据的特点和计算资源来调整这些参数,以获得最佳的聚类效果。
  • n_init 的增加可能会提高算法的稳定性,但也会增加计算时间。
  • max_iter 的设置需要权衡算法的收敛速度和计算资源。如果数据集较大或算法难以收敛,可能需要增加 max_iter 的值。
  • 除了 n_initmax_iterKMeans 类还有其他重要的参数,如 n_clusters(聚类数目)、init(初始质心选择方式)、tol(收敛阈值)等,这些参数也需要根据具体问题进行适当的设置。

<4>归纳

图片.png

4/关于kmeans算法,如果我选择了kmeans++算法来决定最初的聚类中心,那么我还需要设置n_init参数吗

当你选择使用 kmeans++方法来决定最初的聚类中心时,你仍然需要设置 n_init 参数。

n_init 和初始质心选择方法(如 kmeans++)是两个不同的概念,它们各自在 K-Means 算法中扮演着不同的角色。

  • kmeans++ 初始质心选择方法:这是一种用于选择初始聚类中心的策略,旨在通过智能地选择初始质心来减少算法陷入局部最优解的可能性。kmeans++ 通过一种概率分布来选择后续的质心,使得它们之间的距离尽可能远,从而有利于后续迭代的收敛。
  • n_init 参数:这个参数指定了算法使用不同的初始质心集合(无论是通过 kmeans++ 还是其他方法生成的)运行算法的次数。对于每次运行,都会生成一套新的初始质心,然后执行 K-Means 算法直到收敛。最后,从这些运行中选择最佳的结果(通常是惯性最小的那个)。

因此,即使你使用了 kmeans++ 作为初始质心选择方法,设置 n_init > 1 仍然是有意义的,因为它允许算法尝试多个不同的初始质心集合,并从中选择最好的一个。这有助于进一步减少算法陷入局部最优解的风险,并提高聚类结果的稳定性和质量。

简而言之,kmeans++ 和 n_init 是相辅相成的:kmeans++ 提供了更好的初始质心选择策略,而 n_init 则允许算法通过多次尝试来找到更优的聚类结果。

关于kmeans算法,如果我用kmeans++方法来选择初始聚类中心,那么每次迭代选择出来的初始聚类中心是一样的吗

关于kmeans算法,如果选择kmeans++方法来选择初始聚类中心,每次迭代选出来的初始聚类中心在理论上应该是相同的,但这里需要澄清几个关键点:

  1. kmeans++的初始化过程:kmeans++算法在初始化阶段通过一种智能的方式选择初始聚类中心,使得这些中心之间的距离尽可能远。这个过程是确定的,即给定相同的数据集和相同的随机数种子(如果有的话),kmeans++会生成相同的初始聚类中心集合。
  2. 随机数种子的影响:然而,如果kmeans++实现中使用了随机数(尽管在kmeans++的初始化过程中,随机性主要用于选择第一个聚类中心,但后续中心的选择是基于已选中心的距离分布,因此随机性影响较小),那么不同的随机数种子可能导致不同的初始聚类中心。但在实际应用中,如果不显式设置随机数种子,那么每次运行kmeans++时使用的随机数种子可能是由系统或库自动生成的,这可能导致每次运行得到不同的初始聚类中心(尽管这种差异在大多数情况下是微小的)。
  3. 迭代与初始聚类中心的关系:需要注意的是,这里的“迭代”一词可能有些误导。在kmeans++的上下文中,迭代通常指的是算法整体上的迭代过程,即不断重新分配数据点到最近的聚类中心并更新聚类中心的位置,直到满足某个收敛条件。而kmeans++的“初始化迭代”特指选择初始聚类中心的过程,这个过程在算法开始之前只执行一次(除非显式地重新运行kmeans++初始化)。
  4. n_init参数的影响:如果设置了n_init > 1,则算法会多次运行kmeans++(或任何其他初始质心选择方法),每次都会生成一套新的初始聚类中心,并运行完整的K-Means算法直到收敛。然后从这些运行中选择最佳的结果。但在这个上下文中,我们讨论的是单次kmeans++初始化过程中的初始聚类中心选择,而不是多次运行的结果。

综上所述,如果选择kmeans++方法来选择初始聚类中心,并且没有改变随机数种子或其他可能影响初始化过程的因素,那么每次“初始化迭代”选出来的初始聚类中心在理论上应该是相同的。然而,在实际应用中,由于随机数种子的影响或n_init参数的设置,可能会观察到不同的初始聚类中心集合。但这种情况下的不同通常是由于算法被多次运行(每次可能使用不同的随机数种子)或显式地要求算法尝试多个初始质心集合所导致的。

4/kmeans算法如何指定最大的迭代次数

是的,K-Means算法可以指定最大的迭代次数(max_iter)作为算法运行的一个参数。
这个参数限制了算法尝试找到最优聚类中心的最大迭代轮数。
在达到最大迭代次数后,算法将停止,即使此时可能还没有收敛到全局最优解。

在大多数编程语言和库中,K-Means算法都提供了设置最大迭代次数的选项。
例如,在Python的scikit-learn库中,KMeans类就具有max_iter参数。
你可以这样使用它:

from sklearn.cluster import KMeans  
  
# 假设X是你的数据  
# 设定K-Means的聚类数为3,最大迭代次数为300  
kmeans = KMeans(n_clusters=3, max_iter=300, random_state=0)  
kmeans.fit(X)  
  

# 接着你可以使用kmeans.labels_来获取聚类标签等
# 设置max_iter参数可以帮助你控制算法的运行时间,尤其是在处理大型数据集时。
# 如果数据集很大或者聚类结果对初始中心的选择非常敏感,算法可能需要更多的迭代次数来收敛。
# 然而,过高的迭代次数可能会导致不必要的计算开销,特别是当算法已经接近或达到最优解时。
# 另一个相关参数是tol(容忍度),它指定了收敛的阈值。如果连续两次迭代之间的聚类中心变化小于tol,则算法会提前终止,即使没有达到最大迭代次数。这有助于进一步控制算法的运行时间和精度。

# 总之,通过合理地设置max_iter和tol参数,你可以在使用K-Means算法时更好地平衡计算效率和聚类质量。

5/k-means优缺点

<1>优点

 <1>算法逻辑很简单,很容易理解。
 <2>当数据量不是很大的时候,算法非常快速。
 <2>对大数据集有较高的效率并且是可伸缩性的。
 <3>时间复杂度近于线性,而且适合挖掘大规模数据集。
 <4>K-Means聚类算法的时间复杂度是O(n*k*t),
    其中n代表数据集中对象的数量,k代表着簇的数目,t代表着算法迭代的次数

<2>缺点

 <1>在K-means算法中K值是事先人为给定的值,这个K值的选定是非常难以估计的。
    很多时候,事先并不能知道给定的k值是多少才是最合适的(一般要凭经验,但是经验也不是万能的)。
 <2>在K-means算法中,首先需要有初始聚类中心,然后再开始聚类。
    这个初始聚类中心的选择对最终的聚类结果有较大的影响,
    一旦初始聚类中心选择的不好,可能无法得到有效的聚类结果,这也成为K-means算法的一个主要问题。
 <3>从K-means算法框架可以看出,该算法需要不断地迭代,不断地计算调整后的新的聚类中心,
    因此当数据量非常大时,算法的时间开销是非常大的。
    所以需要对算法的时间复杂度进行分析、改进,提高算法应用范围。
 <4>K-means算法受异常点(离群点)的影响比较大。
    (dbscan这个算法,受异常点的影响就比较小,因为dbscan算法可以把异常点识别出来)

5/如何确定最佳簇的个数

要确定K-means算法中的最佳簇(聚类)个数,
我们可以使用上述提到的方法之一,如肘部法则(Elbow Method)或轮廓系数(Silhouette Coefficient)。
在这里,我将提供一个使用【肘部法则】的完整Python代码示例,使用`sklearn`库中的`KMeans``metrics`模块。
from sklearn.cluster import KMeans  

# 构造模拟数据
from sklearn.datasets import make_blobs  
# 轮廓系数,来衡量聚类的效果
from sklearn.metrics import silhouette_score  

import numpy as np  
import matplotlib.pyplot as plt  

# 生成模拟数据  
X, _ = make_blobs(n_samples=300, 
                  centers=4, 
                  cluster_std=0.60,
                  random_state=0)  
  
# K值的范围  
k_range = range(1, 11)  
  
# 存放每个K值对应的SSE  
sse_result_list = []  
  
for k in k_range:  
    # 初始化KMeans  
    kmeans = KMeans(n_clusters=k, random_state=0).fit(X)  
    
    # 计算SSE  
    sse_result_list.append(    kmeans.inertia_   )  
  
# 绘制Elbow Curve  
plt.figure()  
plt.plot(k_range, sse, marker='o')  
plt.xlabel('Number of clusters')  
plt.ylabel('SSE')  
plt.title('Elbow Method')  
plt.show()  
  
# 根据Elbow Curve选择最佳的K值  
# 在这个例子中,你可以通过观察图形来选择K值,但自动化的话,可以寻找SSE开始显著平缓的点  
# 这里我们直接选择图形中的“肘点”作为示例,实际上可能需要更复杂的逻辑来自动识别它  
best_k = 4  # 假设通过图形观察得到的最佳K值为4  
  
# 可以进一步使用最佳K值进行聚类,并计算轮廓系数来验证  
kmeans_best = KMeans(n_clusters=best_k, random_state=0).fit(X)  
silhouette_avg = silhouette_score(X, kmeans_best.labels_)  
print(f"Silhouette Coefficient for best k = {best_k}: {silhouette_avg}")
  • 请注意,在上面的代码中,我假设了通过图形观察得到的最佳K值为4。

  • 在实际应用中,你可能需要编写额外的逻辑来自动检测“肘点”,这通常涉及到寻找SSE曲线开始显著平缓的点。

  • 然而,这种自动化检测并不总是准确的,特别是在SSE曲线没有明显“肘点”的情况下。

  • 轮廓系数的计算在这里只是为了展示如何使用它来评估聚类效果,虽然它本身并不直接用于确定最佳的K值。

  • 你可以通过轮廓系数进一步验证你的K值选择是否合理。轮廓系数的值范围在-1到1之间,值越大表示聚类效果越好。

图片.png

6/实战

from sklearn.cluster import KMeans  # sklearn中cluster模块中的KMeans算法类

from sklearn.metrics import silhouette_score  # 轮廓系数,衡量指标
from sklearn.datasets import make_blobs  
import matplotlib.pyplot as plt  

import numpy as np  
  
# 生成模拟数据  
n_samples = 300  
random_state = 42  
X, _ = make_blobs(n_samples=n_samples, centers=4, cluster_std=0.60, random_state=random_state)  
  
# 使用KMeans算法进行聚类  
# 假设我们不知道真实的簇数量,这里尝试使用4个簇  
kmeans = KMeans(n_clusters=4, 
                init='k-means++', 
                random_state=random_state)  
kmeans.fit(X)  
y_kmeans = kmeans.predict(X)  
  
# 评估模型:轮廓系数  
silhouette_avg = silhouette_score(X, y_kmeans)  
print(f"轮廓系数: {silhouette_avg}")  
  
# 可视化结果  
plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')  
  
centers = kmeans.cluster_centers_  
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.75)  
plt.title("K-Means Clustering")  
plt.xlabel("Feature 1")  
plt.ylabel("Feature 2")  
plt.show()  
  
# 打印聚类中心  
print("Cluster centers:\n", centers)  
  
# 如果你需要查看每个点的聚类标签  
print("Cluster labels:", y_kmeans)

在这个代码中:

  1. 我们首先使用make_blobs函数生成了一些模拟的二维数据,这些数据分布在4个中心点周围。
  2. 然后,我们使用KMeans类从scikit-learn库来执行K-Means聚类。通过设置init='k-means++',我们选择了k-means++初始化方法来选择初始聚类中心。
  3. 聚类完成后,我们使用silhouette_score函数来计算轮廓系数,以评估聚类效果的好坏。轮廓系数的值范围从-1到1,值越大表示聚类效果越好。
  4. 我们还使用matplotlib库将聚类结果可视化,其中不同的颜色代表不同的聚类,红色点表示聚类中心。
  5. 最后,我们打印了聚类中心和每个点的聚类标签,以便进一步分析或使用这些数据。

这个代码示例提供了一个完整的K-Means聚类流程,包括数据生成、模型训练、模型评估和结果可视化。你可以根据需要调整数据生成参数或聚类参数来探索不同的聚类效果。