当前位置: 首页 > news >正文

python打卡day17

聚类的基础知识

知识点

  1. 聚类的指标
  2. 聚类常见算法:kmeans聚类、dbscan聚类、层次聚类
  3. 三种算法对应的流程

实际在论文中聚类的策略不一定是针对所有特征,可以针对其中几个可以解释的特征进行聚类,得到聚类后的类别,这样后续进行解释也更加符合逻辑。

聚类的流程

  1. 标准化数据------聚类前的一般操作
  2. 选择合适的算法,根据评估指标调参( )
  3. 将聚类后的特征添加到原数据中
  4. 原则t-sne或者pca进行2D或3D可视化
  • KMeans 和层次聚类的参数是K值,选完k指标就确定
  • DBSCAN 的参数是 eps 和 min_samples,选完他们出现k和评估指标
  • 以及层次聚类的 linkage 准则等都需要仔细调优。
  • 除了经典的评估指标,还需要关注聚类出来每个簇对应的样本个数,避免太少没有意义。

作业: 对心脏病数据集进行聚类。

聚类评估指标

1. 轮廓系数 (Silhouette Score)

定义:轮廓系数衡量每个样本与其所属簇的紧密程度以及与最近其他簇的分离程度。取值范围:[-1, 1]

  - 轮廓系数越接近 1,表示样本与其所属簇内其他样本很近,与其他簇很远,聚类效果越好

  - 轮廓系数越接近-1,表示样本与其所属簇内样本较远,与其他簇较近,聚类效果越差(可能被错误分类)。

  - 轮廓系数接近 0,表示样本在簇边界附近,聚类效果无明显好坏。

2. CH 指数 (Calinski-Harabasz Index)

定义:CH 指数是簇间分散度与簇内分散度之比,用于评估簇的分离度和紧凑度。取值范围:[0, +∞)

  - CH 指数越大,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。

  - 没有固定的上限,值越大越好

3. DB 指数 (Davies-Bouldin Index)

定义:DB 指数衡量簇间距离与簇内分散度的比值,用于评估簇的分离度和紧凑度。取值范围:[0, +∞)

  - DB 指数越小,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。

  - 没有固定的上限,值越小越好

以班级类比通俗理解一下,轮廓系数看个人是否融入班级(-1到1,越大越好);CH指数比班级间差距(越大分班越合理);DB指数查班级紧密度(越小越好)。

聚类常见算法

1、KMeans聚类----就像用圆规画分组的圈子,简单但不够灵活。

KMeans 是一种基于距离的聚类算法,需要预先指定聚类个数,即 `k`。其核心步骤如下:

1. 随机选择 `k` 个样本点作为初始质心(簇中心)。

2. 计算每个样本点到各个质心的距离,将样本点分配到距离最近的质心所在的簇。

3. 更新每个簇的质心为该簇内所有样本点的均值。

4. 重复步骤 2 和 3,直到质心不再变化或达到最大迭代次数为止。

确定簇数的方法:肘部法

通过计算不同 `k` 值下的簇内平方和(Within-Cluster Sum of Squares, WCSS),绘制 `k` 与 WCSS 的关系图。在图中找到“肘部”点,即 WCSS 下降速率明显减缓的 `k` 值,通常认为是最佳簇数。这是因为增加 `k` 值带来的收益(WCSS 减少)在该点后变得不显著。

说人话就是画折线图,横轴是组数,纵轴是组内紧密程度,选"拐弯"的点

import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns# 评估不同 k 值下的指标
k_range = range(2, 11)  # 测试 k 从 2 到 10
inertia_values = [] # 存每个k值的组内紧凑程度(越小越好)
silhouette_scores = [] # 存轮廓系数(-1到1,越大越好)
ch_scores = [] # 存CH指数(越大越好)
db_scores = [] # 存DB指数(越小越好)for k in k_range:kmeans = KMeans(n_clusters=k, random_state=42) # 创建KMeans模型,设置当前要测试的k值kmeans_labels = kmeans.fit_predict(X_scaled) # 执行聚类,得到每个点的分组标签inertia_values.append(kmeans.inertia_)  # 惯性(肘部法则),组内紧密程度silhouette = silhouette_score(X_scaled, kmeans_labels)  # 轮廓系数silhouette_scores.append(silhouette)ch = calinski_harabasz_score(X_scaled, kmeans_labels)  # CH 指数ch_scores.append(ch)db = davies_bouldin_score(X_scaled, kmeans_labels)  # DB 指数db_scores.append(db)print(f"k={k}, 惯性: {kmeans.inertia_:.2f}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")# -------- 打印结果 ----------
k=2, 惯性: 218529.50, 轮廓系数: 0.320, CH 指数: 479.34, DB 指数: 3.222
k=3, 惯性: 207982.87, 轮廓系数: 0.209, CH 指数: 441.88, DB 指数: 2.906
k=4, 惯性: 200477.28, 轮廓系数: 0.220, CH 指数: 399.12, DB 指数: 2.441
k=5, 惯性: 192940.36, 轮廓系数: 0.224, CH 指数: 384.19, DB 指数: 2.042
k=6, 惯性: 185411.81, 轮廓系数: 0.227, CH 指数: 380.64, DB 指数: 1.733
k=7, 惯性: 178444.49, 轮廓系数: 0.130, CH 指数: 378.31, DB 指数: 1.633
k=8, 惯性: 174920.27, 轮廓系数: 0.143, CH 指数: 352.31, DB 指数: 1.817
k=9, 惯性: 167383.96, 轮廓系数: 0.150, CH 指数: 364.27, DB 指数: 1.636
k=10, 惯性: 159824.84, 轮廓系数: 0.156, CH 指数: 378.43, DB 指数: 1.502# 绘制评估指标图
plt.figure(figsize=(15, 10))# 肘部法则图(Inertia)
plt.subplot(2, 2, 1)
plt.plot(k_range, inertia_values, marker='o')
plt.title('肘部法则确定最优聚类数 k(惯性,越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('惯性')
plt.grid(True)# 轮廓系数图
plt.subplot(2, 2, 2)
plt.plot(k_range, silhouette_scores, marker='o', color='orange')
plt.title('轮廓系数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('轮廓系数')
plt.grid(True)# CH 指数图
plt.subplot(2, 2, 3)
plt.plot(k_range, ch_scores, marker='o', color='green')
plt.title('Calinski-Harabasz 指数确定最优聚类数 k(越大越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('CH 指数')
plt.grid(True)# DB 指数图
plt.subplot(2, 2, 4)
plt.plot(k_range, db_scores, marker='o', color='red')
plt.title('Davies-Bouldin 指数确定最优聚类数 k(越小越好)')
plt.xlabel('聚类数 (k)')
plt.ylabel('DB 指数')
plt.grid(True)plt.tight_layout()
plt.show()

1. 肘部法则图: 找下降速率变慢的拐点,这里都差不多

2. 轮廓系数图:找局部最高点,这里选6不能选7

3. CH指数图: 找局部最高点,这里选7之前的都还行

4. DB指数图:找局部最低点,这里选6 7 9 10都行

综上,k = 6比较合适,下面进行聚类结果的可视化(聚类➡降维➡画图)

问题来了,为什么要降维?因为原始数据可能有几十个特征(维度),无法直接画在2D平面上。PCA就像"投影仪",把高维数据投影到最重要的两个方向上。

# 提示用户选择 k 值
selected_k = 6# 使用选择的 k 值进行 KMeans 聚类
kmeans = KMeans(n_clusters=selected_k, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled) # 得到每个数据点的分组标签
X['KMeans_Cluster'] = kmeans_labels# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled) # 压缩后的数据,形状从[n个样本, m个特征]变成[n个样本, 2]# KMeans 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=kmeans_labels, palette='viridis')
# 所有点的横坐标(PCA第一维),所有点的纵坐标(PCA第二维),用颜色区分不同聚类组
plt.title(f'KMeans Clustering with k={selected_k} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()# 打印 KMeans 聚类标签的前几行
print(f"KMeans Cluster labels (k={selected_k}) added to X:")
print(X[['KMeans_Cluster']].value_counts())# ---------- 打印结果 ------------
KMeans Cluster labels (k=6) added to X:
KMeans_Cluster
0                 5205
1                 1220
2                  903
3                  128
4                   34
5                   10
dtype: int64

通俗理解全过程

  • 分组:像把全班同学按相似性分成几个小组(KMeans)
  • 拍合照:让所有人站到操场拍2D照片(PCA降维)
  • 穿队服:给不同组的人穿不同颜色衣服(hue颜色区分)
  • 点名:统计每个组有多少人(value_counts)

2.DBSCAN聚类

DBSCAN 是一种基于密度的聚类算法,自动发现紧密相连的数据点群,并排除噪声,无需预设K值,但要找出最佳的eps和min_samples

步骤如下:

  1. 随机选一个未访问的点
  2. 检查它半径范围里的邻居:如果邻居数 ≥ min_samples(最小邻居数) → 标记为核心点,创建一个新簇,并递归扩散找所有密度相连的点;否则 → 暂时标记为噪声(可能后续被其他簇吸收为边界点)
  3. 重复直到所有点被访问
import numpy as np
import pandas as pd
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns# 评估不同 eps 和 min_samples 下的指标
# eps这个参数表示邻域的半径,min_samples表示一个点被认为是核心点所需的最小样本数。
# min_samples这个参数表示一个核心点所需的最小样本数。eps_range = np.arange(0.3, 0.8, 0.1)  # 测试 eps 从 0.3 到 0.7
min_samples_range = range(3, 8)  # 测试 min_samples 从 3 到 7
results = []for eps in eps_range:for min_samples in min_samples_range:dbscan = DBSCAN(eps=eps, min_samples=min_samples)dbscan_labels = dbscan.fit_predict(X_scaled)# 计算簇的数量(排除噪声点 -1)n_clusters = len(np.unique(dbscan_labels)) - (1 if -1 in dbscan_labels else 0)# 计算噪声点数量n_noise = list(dbscan_labels).count(-1)# 只有当簇数量大于 1 且有有效簇时才计算评估指标if n_clusters > 1:# 排除噪声点后计算评估指标mask = dbscan_labels != -1if mask.sum() > 0:  # 确保有非噪声点silhouette = silhouette_score(X_scaled[mask], dbscan_labels[mask])ch = calinski_harabasz_score(X_scaled[mask], dbscan_labels[mask])db = davies_bouldin_score(X_scaled[mask], dbscan_labels[mask])results.append({'eps': eps,'min_samples': min_samples,'n_clusters': n_clusters,'n_noise': n_noise,'silhouette': silhouette,'ch_score': ch,'db_score': db})print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, "f"轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")else:print(f"eps={eps:.1f}, min_samples={min_samples}, 簇数: {n_clusters}, 噪声点: {n_noise}, 无法计算评估指标")# 将结果转为 DataFrame 以便可视化和选择参数
results_df = pd.DataFrame(results)

看表还是不明确,还是来画折线图一下找找最佳参数吧:

# 绘制评估指标图,增加点论文中的工作量
plt.figure(figsize=(15, 10))
# 轮廓系数图
plt.subplot(2, 2, 1)
for min_samples in min_samples_range:subset = results_df[results_df['min_samples'] == min_samples] # plt.plot(subset['eps'], subset['silhouette'], marker='o', label=f'min_samples={min_samples}')
plt.title('轮廓系数确定最优参数(越大越好)')
plt.xlabel('eps')
plt.ylabel('轮廓系数')
plt.legend()
plt.grid(True)# CH 指数图
plt.subplot(2, 2, 2)
for min_samples in min_samples_range:subset = results_df[results_df['min_samples'] == min_samples]plt.plot(subset['eps'], subset['ch_score'], marker='o', label=f'min_samples={min_samples}')
plt.title('Calinski-Harabasz 指数确定最优参数(越大越好)')
plt.xlabel('eps')
plt.ylabel('CH 指数')
plt.legend()
plt.grid(True)# DB 指数图
plt.subplot(2, 2, 3)
for min_samples in min_samples_range:subset = results_df[results_df['min_samples'] == min_samples]plt.plot(subset['eps'], subset['db_score'], marker='o', label=f'min_samples={min_samples}')
plt.title('Davies-Bouldin 指数确定最优参数(越小越好)')
plt.xlabel('eps')
plt.ylabel('DB 指数')
plt.legend()
plt.grid(True)# 簇数量图
plt.subplot(2, 2, 4)
for min_samples in min_samples_range:subset = results_df[results_df['min_samples'] == min_samples]plt.plot(subset['eps'], subset['n_clusters'], marker='o', label=f'min_samples={min_samples}')
plt.title('簇数量变化')
plt.xlabel('eps')
plt.ylabel('簇数量')
plt.legend()
plt.grid(True)plt.tight_layout()
plt.show()

看图是eps=0.6, min_samples=7最佳,但将这个参数组合带入算法进行聚类之后分出来的簇只有两类,并且少数簇中的样本数很少,有点失败

3.层次聚类

层次聚类是一种自底向上的聚类方法,初始时每个样本是一个簇,然后逐步合并最相似的簇,直到达到指定的簇数量或满足停止条件,需要指定簇数量(类似于 KMeans)。就像"家族族谱生成器":

  • 从每个人独立开始(每个样本是一个簇)
  • 逐步合并最相似的两个人/家族(合并最近簇)
  • 直到合并到指定数量的大家族(预设的n_clusters)
import numpy as np
import pandas as pd
from sklearn.cluster import AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.metrics import silhouette_score, calinski_harabasz_score, davies_bouldin_score
import matplotlib.pyplot as plt
import seaborn as sns# 评估不同 n_clusters 下的指标
n_clusters_range = range(2, 11)  # 测试簇数量从 2 到 10
silhouette_scores = []
ch_scores = []
db_scores = []for n_clusters in n_clusters_range:agglo = AgglomerativeClustering(n_clusters=n_clusters, linkage='ward')  # 使用 Ward 准则合并簇agglo_labels = agglo.fit_predict(X_scaled)# 计算评估指标silhouette = silhouette_score(X_scaled, agglo_labels)ch = calinski_harabasz_score(X_scaled, agglo_labels)db = davies_bouldin_score(X_scaled, agglo_labels)silhouette_scores.append(silhouette)ch_scores.append(ch)db_scores.append(db)print(f"n_clusters={n_clusters}, 轮廓系数: {silhouette:.3f}, CH 指数: {ch:.2f}, DB 指数: {db:.3f}")
# -------- 打印结果 ----------
n_clusters=2, 轮廓系数: 0.336, CH 指数: 685.66, DB 指数: 2.659
n_clusters=3, 轮廓系数: 0.242, CH 指数: 659.40, DB 指数: 2.327
n_clusters=4, 轮廓系数: 0.254, CH 指数: 565.74, DB 指数: 2.160
n_clusters=5, 轮廓系数: 0.276, CH 指数: 519.91, DB 指数: 2.110
n_clusters=6, 轮廓系数: 0.284, CH 指数: 494.24, DB 指数: 1.860
n_clusters=7, 轮廓系数: 0.295, CH 指数: 482.64, DB 指数: 1.680
n_clusters=8, 轮廓系数: 0.297, CH 指数: 479.17, DB 指数: 1.435
n_clusters=9, 轮廓系数: 0.301, CH 指数: 481.85, DB 指数: 1.283
n_clusters=10, 轮廓系数: 0.309, CH 指数: 489.27, DB 指数: 1.269# 绘制评估指标图
plt.figure(figsize=(15, 5))# 轮廓系数图
plt.subplot(1, 3, 1)
plt.plot(n_clusters_range, silhouette_scores, marker='o')
plt.title('轮廓系数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('轮廓系数')
plt.grid(True)# CH 指数图
plt.subplot(1, 3, 2)
plt.plot(n_clusters_range, ch_scores, marker='o')
plt.title('Calinski-Harabasz 指数确定最优簇数(越大越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('CH 指数')
plt.grid(True)# DB 指数图
plt.subplot(1, 3, 3)
plt.plot(n_clusters_range, db_scores, marker='o')
plt.title('Davies-Bouldin 指数确定最优簇数(越小越好)')
plt.xlabel('簇数量 (n_clusters)')
plt.ylabel('DB 指数')
plt.grid(True)plt.tight_layout()
plt.show()

看图簇数就选10吧,带入聚类算法进行可视化

# 提示用户选择 n_clusters 值(这里可以根据图表选择最佳簇数)
selected_n_clusters = 10  # 示例值,根据图表调整# 使用选择的簇数进行 Agglomerative Clustering 聚类
agglo = AgglomerativeClustering(n_clusters=selected_n_clusters, linkage='ward')
agglo_labels = agglo.fit_predict(X_scaled)
X['Agglo_Cluster'] = agglo_labels# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)# Agglomerative Clustering 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=agglo_labels, palette='viridis')
plt.title(f'Agglomerative Clustering with n_clusters={selected_n_clusters} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()# 打印 Agglomerative Clustering 聚类标签的分布
print(f"Agglomerative Cluster labels (n_clusters={selected_n_clusters}) added to X:")
print(X[['Agglo_Cluster']].value_counts())
# ---------- 打印结果 -----------
Agglomerative Cluster labels (n_clusters=10) added to X:
Agglo_Cluster
4                5230
1                 778
2                 771
9                 409
5                 127
6                  96
0                  37
3                  34
7                  10
8                   8
dtype: int64

另外提一下,层次聚类还有另一种可视化方法-------树状图

# 层次聚类的树状图可视化
from scipy.cluster import hierarchy
import matplotlib.pyplot as plt# 假设 X_scaled 是标准化后的数据
# 计算层次聚类的链接矩阵
Z = hierarchy.linkage(X_scaled, method='ward')  # 'ward' 是常用的合并准则# 绘制树状图
plt.figure(figsize=(10, 6))
hierarchy.dendrogram(Z, truncate_mode='level', p=3)  # p 控制显示的层次深度
# hierarchy.dendrogram(Z, truncate_mode='level')  # 不用p这个参数,可以显示全部的深度
plt.title('Dendrogram for Agglomerative Clustering')
plt.xlabel('Cluster Size')
plt.ylabel('Distance')
plt.show()

1. 横坐标代表每个簇对应样本的数据,这些样本数目加一起是整个数据集的样本数目。这是从上到下进行截断,p=3显示最后3层,不用p这个参数会显示全部。

2. 纵轴代表距离 ,反映了在聚类过程中,不同样本或簇合并时的距离度量值。距离越大,意味着两个样本或簇之间的差异越大;距离越小,则差异越小。

@浙大疏锦行

相关文章:

  • 解决android studio 中gradle 出现task list not built
  • 使用Unsloth微调DeepSeek-R1蒸馏模型:低显存高效训练实践
  • 效率提升利器:解锁图片处理新姿势
  • x-cmd install | Tuistash - Logstash 实时监控,告别图形界面,高效便捷!
  • 餐饮部绩效考核管理制度与综合评估方法
  • STL之stackqueue
  • Linux主机时间设置操作指南及时间异常影响
  • 开个帖子记录一下自己学spring源码的过程
  • LLM评估指标:WSC和WebNLG 是什么
  • mysql协议详解
  • Waymo公司正在加快其位于亚利桑那州新工厂的无人驾驶出租车(robotaxi)生产进度
  • 使用 AddressSanitizer 检测堆越界错误
  • 小刚说C语言刷题—1044 -找出最经济型的包装箱型号
  • 资产管理系统选型避坑:2025年核心技术趋势洞察
  • 凌晨三点的数据库崩溃现场
  • Dependency Track使用
  • 疗愈服务预约小程序源码介绍
  • cesium之自定义地图与地图叠加
  • 卷积神经网络基础(五)
  • MySQL 表的内外连接
  • 江苏省泰州市委常委、宣传部部长刘霞接受审查调查
  • 中国驻俄大使张汉晖人民日报撰文:共襄和平伟业,续谱友谊新篇
  • 为什么有的人闻到烟味,会咳嗽、胸闷?别再伤害身边的人
  • 长线游、县域游、主题游等持续升温,假期文旅市场供需两旺
  • 当Z世代与传统戏曲在春日校园相遇
  • 泽连斯基拒绝普京72小时停火提议,坚持应尽快实现30天停火