一、聚类算法的概念
(一)什么是聚类
聚类(Clustering)的核心思想是一种根据样本之间的相似性,将样本自动划分到不同类别中的无监督学习算法。与分类不同,聚类不需要预先标记的训练数据,而是直接通过数据本身的特征来发现内在的分组结构。其中,常用的相似度计算方法是欧式距离法。它通过计算样本之间的直线距离来衡量它们的相似性,距离越短,相似性越高。
核心思想:物以类聚,相似的样本会被分到同一个簇中。
(二)聚类算法的分类
1. 根据聚类颗粒度分类
细粒度聚类:将数据划分为更小、更细致的簇,适用于需要高精度分析的场景。
粗粒度聚类:将数据划分为较大的簇,适用于宏观分析。
2. 根据实现方法分类
基于划分的聚类:K-Means算法是这一类的典型代表。它通过计算质心(簇的中心位置,通常通过均值计算)来对数据进行分类。
基于层次的聚类:包括DIANA(自顶向下)和AGNES(自底向上)等算法。它们通过不断地合并或分裂簇来构建层次结构。
基于密度的聚类:DBSCAN算法是这一类的代表。它根据数据点的密度来识别簇,能够发现任意形状的簇,并且对噪声具有较强的鲁棒性。
基于图的聚类:谱聚类(Spectral Clustering)是基于图的聚类方法。它通过构建数据的相似性图,并利用图的特征值来实现聚类。
(三)聚类算法的应用场景
市场分析:对客户进行分群,以便制定针对性的营销策略。例如,将客户分为高价值客户、中等价值客户和低价值客户。
图像处理:用于图像分割、目标检测和图像压缩。例如,将图像中的像素点划分为不同的区域,以便进行进一步的分析。
文本挖掘:对文档进行聚类,以便进行信息检索和主题建模。例如,将新闻文章按照主题进行分类,方便用户快速浏览。
异常检测:识别数据中的离群点 或异常值。例如,在信用卡交易中检测欺诈行为。
二、Kmeans算法原理
(一)K值的含义
在K-Means算法中,K值是一个非常重要的参数,它表示最终的聚类类别数。例如,在KMeans(n_clusters=4)中,n_clusters 参数就是用来指定K值的,表示我们将数据划分为4个簇。
(二)聚类API与代码实现
1. API介绍
sklearn.cluster.KMeans
示例:sklearn.cluster.KMeans(n_clusters=8)
参数:n_clusters:开始的聚类中心数量
estimator.fit_predict(x):计算聚类中心并预测每个样本属于哪个类别,相当于先调用fit(x), 然后再调用predict(x)
calinski_harabasz_score(x, y_pred):用来评估聚类效果,数值越大越好
2. 代码实现
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.metrics import calinski_harabasz_score
# 创建数据集 1000个样本,每个样本2个特征 4个质心蔟数据标准差[0.4, 0.2, 0.3, 0.1]
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.3, 0.1], random_state=22)
plt.figure()
plt.scatter(x[:, 0], x[:, 1], marker='o', s=50, c=y)
plt.show()
"""
x[:, 0]:所有样本的第 0 个特征(即横坐标)
x[:, 1]:所有样本的第 1 个特征(即纵坐标)
marker='o':设置点的形状为圆形(默认也是 'o')
s=50:设置点的大小为 50(默认值较小,可调大观察更清楚)
c=y:设置每个点的颜色,颜色由标签 y 决定(不同类别用不同颜色显示)
"""
# 使用k-means进行聚类, 并使用CH方法评估
y_pred = KMeans(n_clusters=4, random_state=22).fit_predict(x)
plt.scatter(x[:, 0], x[:, 1], marker='o', s=50, c=y_pred)
plt.show()
# 模型评估
print(calinski_harabasz_score(x, y_pred))
(三)K-Means实现流程
K-Means算法的实现流程可以总结为以下几步:
①事先确定常数K:这个常数K表示最终的聚类类别数。
②随机选择K个样本点作为初始聚类中心:这些初始中心点的选择对最终的聚类结果有很大影响。
③计算每个样本到K个中心的距离:选择最近的聚类中心点作为标记类别。
④重新计算新的聚类中心点:根据每个类别中的样本点,计算新的聚类中心点(平均值)。如果新的中心点与原中心点一样,则停止聚类;否则,重新进行第3步过程,直到聚类中心不再变化或达到最大迭代次数。
三、聚类评估方法
(一)SSE(误差平方和)
核心思想:SSE衡量的是每个点到其他对应簇中心的距离的平方和。SSE越小,表述数据点越接近其簇中心,即聚类效果越好。
我们可以通过“肘”方法来确定最佳的K值。具体来说,对于一个包含n个点的数据集,我们从1到n迭代计算K值,每次聚类完成后计算SSE。随着K值的增加,SSE会逐渐减小。当SSE的变化率突然变缓时,我们就可以认为这个K值是最佳的。
计算公式:
其中 是簇的数量,
是第
个簇中的点的集合,
是第
个簇的中心(质心点),
是点
到簇中心
的距离。
from sklearn.cluster import KMeans # 用于执行 K 均值聚类算法
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs # 用于生成模拟的聚类数据集
x, y = make_blobs(n_samples=1000, n_features=2, random_state=420,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.3, 0.1])
"""
作用:生成一个包含 1000 个样本、每个样本有 2 个特征的模拟数据集。
参数说明:
n_samples=1000:生成 1000 个样本。
n_features=2:每个样本有 2 个特征(二维数据)。
random_state=420:随机种子,确保每次运行生成的数据一致。
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]]:指定生成数据的 4 个中心点。
cluster_std=[0.4, 0.2, 0.3, 0.1]:每个中心点对应的簇的标准差,控制簇的分布紧密程度。
输出:
x:特征矩阵,形状为 (1000, 2)。
y:真实标签(在聚类任务中通常不使用)。
"""
# 初始化一个空列表 sse_list,用于存储不同聚类数量下的 SSE(误差平方和)值
sse_list = []
for k in range(1, 11):
k_model = KMeans(n_clusters=k)
# 对输入数据 x 进行 K 均值聚类训练。这一步会计算并保存聚类中心
k_model.fit(x)
# 获取当前模型的SSE(误差平方和),即所有样本到其所属簇中心的距离平方之和。
# 说明:inertia_是KMeans对象的一个属性,表示模型的SSE值。
sse_values = k_model.inertia_
# 将当前 k 值对应的 SSE 值添加到 sse_list 列表中
sse_list.append(sse_values)
plt.grid() # 添加网格线
plt.title("SSE")
plt.plot(range(1, 11), sse_list, 'or-')
plt.show()
(二)SC(轮廓系数)
核心思想:轮廓系数衡量的是每个点在自己簇内的内聚程度与在其他簇间的分离程度。轮廓系数的值范围是 [−1,1],值越大,表示点越接近其自己的簇中心,且越远离其他簇中心,即聚类效果越好。
计算公式:
其中,a是 i 到其所在簇内其他点的平均距离(该值越小,说明簇内的相似程度越大),b是点 i 到最近的其他簇间的距离平均值的最小值(该值越大,说明该样本越不属于其他簇)。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
x, y = make_blobs(n_samples=1000, n_features=2, random_state=420,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.3, 0.1])
# 初始化一个空列表 sc_list
sc_list = []
for k in range(2, 11):
k_model = KMeans(n_clusters=k)
# 对输入数据 x 进行 K 均值聚类训练。这一步会计算并保存聚类中心
# TODO 方法一:使用 fit_predict
# pred=k_model.fit_predict(x)
# TODO 方法二:使用 fit + labels_
k_model.fit(x)
pred = k_model.labels_
sc_values = silhouette_score(x, pred)
# 将当前 k 值对应的 SC 值添加到 sc_list 列表中
sc_list.append(sc_values)
plt.grid() # 添加网格线
plt.title("聚类效果评估之SC")
plt.xlabel("K值")
plt.ylabel("SC值")
plt.plot(range(2, 11), sc_list, 'ob-')
plt.show()
(三)CH(轮廓系数法)
核心思想:CH指数衡量的是簇间分散程度与簇内紧密程度以及质心的个数。CH指数越大,表示簇间分散程度越大,簇内紧密程度越高,即聚类效果越好。
计算公式: (m表示样本数量,k表示质心个数)
其中,SSW:簇内距离平方和,表示簇内的紧密程度;SSB:簇间距离平方和,表示簇间的分离程度。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.metrics import calinski_harabasz_score
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
x, y = make_blobs(n_samples=1000, n_features=2, random_state=420,
centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],
cluster_std=[0.4, 0.2, 0.3, 0.1])
ch_list = []
for k in range(2, 11):
k_model = KMeans(n_clusters=k)
# 方法一:使用 fit_predict
pred = k_model.fit_predict(x)
# 方法二:使用 fit + labels_
# k_model.fit(x)
# pred = k_model.labels_
ch_values = calinski_harabasz_score(x, pred)
ch_list.append(ch_values)
# print(ch_values)
"""
打印 Calinski-Harabasz 指数(方差比准则)的值,用于评估聚类模型的性能。
其中:
x 是输入数据(特征矩阵)
pred 结果和k_model.labels_一样 是聚类模型预测出的样本所属簇的标签
calinski_harabasz_chore 是 sklearn 中用于计算 Calinski-Harabasz 分数的函数,分数越高表示聚类效果越好
"""
plt.grid()
plt.title("聚类效果评估之CH")
plt.xlabel("K值")
plt.ylabel("CH值")
plt.plot(range(2, 11), ch_list, 'og-')
plt.show()
(四)总结
SSE:衡量每个点到其他簇中心的距离的平方和,越小越好
SC:衡量每个点在自己簇内的紧密程度与在其他簇间的分离程度,越大越好
CH:衡量簇间分散程度与簇内紧密程度的比值,越大越好
四、案例--客户数据聚类分析法
已知:客户性别、年龄、年收入、消费指数
需求:对客户进行分析,找到业务突破口,寻找黄金客户
(一)导包和问题解决
# 1、导包
import pandas as pd
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
from sklearn.metrics import silhouette_score
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 忽略警告
import warnings
warnings.filterwarnings('ignore')
(二)加载数据和数据预处理
# 2、加载数据
data = pd.read_csv('../data/customers.csv')
# 3、了解数据
# print(data)
# data.info()
# 4、数据预处理,提取特征值
x = data.iloc[:, [3, 4]]
# 5、创建列表
sse_list = [] # 存储不同K值对应的SSE(误差平方和)
sc_list = [] # 存储不同K值对应的轮廓系数
(三)K值选择循环
for k in range(2, 11):
# 创建模型
kmeans = KMeans(n_clusters=k)
# 训练模型
kmeans.fit(x)
# 获取 SSE 值
sse_values = kmeans.inertia_
sse_list.append(sse_values)
# print('K值:', k, 'SSE:', sse_values)
# 获取 SC 系数
pred_labels = kmeans.predict(x)
sc_values = silhouette_score(x, pred_labels)
sc_list.append(sc_values)
# 取值为[-1, 1],其值越大越好。
# print('K值:', k, 'SC:', sc_values)
K值选择循环:
遍历K值从2到10,计算每个K值对应的SSE和轮廓系数,用于后续确定最佳聚类数量
注意:对于sse其k值可以从1开始,但是sc和ch方法需要从2开始,因为它们要求至少有两个簇(K ≥ 2)才能计算
所以如果后续使用了 SC(轮廓系数)和 CH(如果有的话),这些指标要求 K ≥ 2
如果只使用 SSE,可以从 K=1 开始
(四)各系数与K值关系折线图
1. 绘制SSE系数与K值的关系折线图
# 设置画布,将两个图放在一起
plt.figure(figsize=(10, 8))
# 绘制SSE系数与K值的关系折线图
plt.subplot(211)
plt.plot(range(2, 11), sse_list, 'or-')
plt.title('SSE随K的图表')
plt.xlabel('K值')
plt.ylabel('SSE值')
plt.grid()
2. 绘制SC系数与K值的关系折线图
# 绘制SC系数与K值的关系折线图
plt.subplot(212)
plt.plot(range(2, 11), sc_list, 'og-')
plt.title('SC随K的图表')
plt.xlabel('K值')
plt.ylabel('SC值')
plt.grid()
plt.tight_layout() # 自动调整子图参数,防止重叠
plt.show()
3. 结果展示
(五)绘制K-Means聚类结果
# TODO 绘制K-Means聚类结果
kmeans = KMeans(n_clusters=silhouette_k)
kmeans.fit(x)
pred_labels = kmeans.fit_predict(x)
print(pred_labels)
"""
x.values[pred_labels == N, 0] 选取第N类样本的第一特征(年收入)
x.values[pred_labels == N, 1] 选取第N类样本的第二特征(消费能力)
其中N取值0-4,对应5个不同客户群
"""
# 选取聚类标签为0的数据点的第一列和第二列作为x和y坐标
plt.scatter(x.values[pred_labels == 0, 0], x.values[pred_labels == 0, 1], s=100, c='red', label='标准客户群')
plt.scatter(x.values[pred_labels == 1, 0], x.values[pred_labels == 1, 1], s=100, c='blue', label='传统客户群')
plt.scatter(x.values[pred_labels == 2, 0], x.values[pred_labels == 2, 1], s=100, c='cyan', label='普通客户群')
plt.scatter(x.values[pred_labels == 3, 0], x.values[pred_labels == 3, 1], s=100, c='orange', label='年轻客户群')
plt.scatter(x.values[pred_labels == 4, 0], x.values[pred_labels == 4, 1], s=100, c='green', label='高价值客户群')
# 聚类质心点,前者是所有聚类质心的x坐标,后者是所有聚类质心的y坐标
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s=150, c='black', label='聚类中心')
plt.xlabel('年收入')
plt.ylabel('消费能力')
plt.legend()
plt.show()
(六)额外补充--寻找最佳的K值
# TODO 寻找最佳的 K 值
# 肘部法则:找到 SSE 下降速度明显减缓的 K 值
# 初始化一个变量 elbow_k,用来存储通过肘部法则找到的最佳 K 值
elbow_k = None
for i in range(1, len(sse_list) - 1):
"""
计算当前 K 值与前一个 K 值之间的 SSE 相对变化率,并比较它与后一个 K 值的相对变化率。
如果当前 SSE 下降速度明显小于下一个 K 值的变化速度,说明这是“肘点”
"""
if (sse_list[i] - sse_list[i - 1]) / sse_list[i - 1] > (sse_list[i + 1] - sse_list[i]) / sse_list[i]:
# 找到“肘点”后,将该位置转换为实际的 K 值(因为 K 值是从 2 开始的),并跳出循环
elbow_k = i + 2 # K 值从 2 开始
break
# 轮廓系数:找到 SC 值最大的 K 值
# 使用 max() 函数找到最大轮廓系数的位置(索引),然后加上 2 得到对应的 K 值(因为 K 值从 2 开始)
silhouette_k = sc_list.index(max(sc_list)) + 2 # K 值从 2 开始
print(f"根据肘部法则,最优的 K 值为:{elbow_k}")
print(f"根据轮廓系数,最优的 K 值为:{silhouette_k}")
肘部法则
原理:寻找 SSE 下降速度明显减缓的点(即“肘点”)
实现方式:比较相邻 SSE 的相对变化率,找到下降速度突变的 K 值
轮廓系数法
原理:选择轮廓系数最大的 K 值
实现方式:在 sc_list 中找到最大值的索引,加 2 得到 K 值