Python基础学习-Day17
目录
- 一、无监督聚类算法的基本概念
- 二、常见无监督聚类算法详解
- (一)K-Means 算法
- 算法原理
- 优点
- 缺点
- 肘部法(Elbow Method)
- 应用案例
- 代码实例
- (二)DBSCAN 算法
- 算法原理
- 优点
- 缺点
- 应用案例
- 代码实例
- (三)层次聚类算法
- 算法原理
- 优点
- 缺点
- 应用案例
- 代码实例
- 三、 聚类评估指标介绍
- 1. 轮廓系数 (Silhouette Score)
- 2. CH 指数 (Calinski-Harabasz Index)
- 3. DB 指数 (Davies-Bouldin Index)
一、无监督聚类算法的基本概念
- 聚类算法是一种无监督学习技术,其目标是将数据集划分成若干个簇(cluster),使得同一簇内的数据对象具有较高的相似性,而不同簇之间的数据对象则差异较大。
- 相似性通常基于各种距离度量或相似性指标来判断,如欧几里得距离、曼哈顿距离、余弦相似度等,具体选择取决于数据的特性和应用场景。
- 聚类算法的核心在于寻找数据中的自然分组结构,无需任何预先标记的类别信息,这让它在面对未知数据和探索性数据分析时具有独特的优势。
二、常见无监督聚类算法详解
(一)K-Means 算法
算法原理
K-Means 是一种简单而广泛应用的迭代聚类算法。它首先随机初始化 K 个聚类中心,然后通过以下两个步骤不断迭代优化:
- 分配样本到簇 :计算每个样本到各个聚类中心的距离,将样本分配到距离最近的聚类中心所对应的簇中。
- 更新聚类中心 :重新计算每个簇中所有样本的均值,将其作为新的聚类中心。
- 不断重复上述过程,直到聚类中心不再发生变化或达到最大迭代次数等停止条件。其本质是通过最小化簇内误差平方和(SSE,Sum of Squared Errors),使得各簇内的样本尽可能紧密地聚集在一起。
优点
- 计算效率高 :对于大规模数据集,K-Means 的时间复杂度相对较低,能够在较短时间内完成聚类任务,适用于处理海量数据。
- 易于理解和实现 :算法原理直观,代码实现较为简单,便于初学者上手和实际应用。
缺点
- 需要预先指定聚类数目 K :如何确定合适的 K 值是一个关键问题,通常需要借助一些经验法则(如肘部法则)或尝试多个 K 值来评估选择,但在实际应用中,对于某些复杂数据集,确定最佳 K 值可能并不容易。
- 对初始聚类中心敏感 :不同的初始聚类中心可能会导致不同的聚类结果,有时可能会陷入局部最优解,影响聚类质量。
- 对数据分布有假设 :K-Means 假设簇的形状是球形或凸形的,并且各簇的大小相对均匀,对于具有复杂形状、不同密度或非球形分布的数据集,聚类效果可能不佳。
肘部法(Elbow Method)
它是一种常用的确定 k
值的方法。
- 原理:通过计算不同
k
值下的簇内平方和(Within-Cluster Sum of Squares, WCSS),绘制k
与 WCSS 的关系图。 - 选择标准:在图中找到“肘部”点,即 WCSS 下降速率明显减缓的
k
值,通常认为是最佳簇数。这是因为增加k
值带来的收益(WCSS 减少)在该点后变得不显著。
应用案例
在市场细分领域,企业可以利用 K-Means 将客户根据购买行为、消费偏好、人口统计学特征等多维度数据进行聚类,划分出不同的客户群体。例如,将客户分为高消费频繁购买群体、中等消费偶尔购买群体、低消费群体等,针对不同群体制定差异化的营销策略,如为高消费群体提供专属优惠和定制服务,以提高客户满意度和忠诚度,增加销售额和利润。
代码实例
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 = []
silhouette_scores = []
ch_scores = []
db_scores = []for k in k_range:kmeans = KMeans(n_clusters=k, random_state=42)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}")# 绘制评估指标图
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()
上面这几个图怎么看,
- 肘部法则图: 找下降速率变慢的拐点,这里都差不多
- 轮廓系数图:找局部最高点,这里选6不能选7
- CH指数图: 找局部最高点,这里选7之前的都还行
- DB指数图:找局部最低点,这里选6 7 9 10都行
综上,选择6比较合适。
- 为什么选择局部最优的点,因为比如簇间差异,分得越细越好,但是k太细了没价值,所以要有取舍。
- 比如k可以选3和5,推荐选择5,能包含更多信息
# 提示用户选择 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)# KMeans 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=kmeans_labels, palette='viridis')
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())
(二)DBSCAN 算法
算法原理
- DBSCAN(Density-Based Spatial Clustering of Applications with Noise)是一种基于密度的聚类算法。它基于这样一个思想:对于一个簇中的样本点,其周围一定半径范围内(由参数 ε 确定)应包含足够数量的样本点(由参数 MinPts 确定)。算法通过扫描数据集,找出密度足够高的区域,并将这些区域划分为簇,同时将低密度区域中的点标记为噪声点。
- 具体来说,DBSCAN 将数据点分为三类:核心点、边界点和噪声点。核心点是指其 ε 邻域内至少包含 MinPts 个样本点的点;边界点是指其 ε 邻域内的样本点数小于 MinPts,但落在某个核心点的 ε 邻域内的点;噪声点则是既不是核心点也不是边界点的点。簇由核心点及其直接或间接密度可达的样本点组成,边界点可能属于多个簇,但通常被分配到其中一个最近的簇中。
优点
- 能够识别任意形状的簇 :不受簇形状的限制,对于具有复杂形状、不同大小和密度分布的数据集,DBSCAN 都能有效地发现其中的聚类结构,这是其相比 K-Means 和层次聚类的一个显著优势。
- 对噪声具有鲁棒性 :可以自动识别并标记噪声点,将它们排除在聚类结果之外,这对于处理含有大量噪声或异常值的数据集非常有用,能够提高聚类结果的准确性和可靠性。
缺点
- 对参数 ε 和 MinPts 的选择敏感 :参数的选择对聚类结果有重要影响,如果选择不当,可能会导致聚类结果不理想。合适参数的选择通常需要根据数据集的特性和分布进行多次试验和调整,缺乏一种通用的、自动化的参数选择方法。
- 在数据密度差异较大时效果不佳 :当数据集中存在密度差异较大的簇时,很难找到一组统一适用于所有簇的 ε 和 MinPts 参数值,使得 DBSCAN 可能无法正确地划分这些密度差异明显的簇。
应用案例
在地理信息系统(GIS)中,DBSCAN 用于对地理空间数据中的兴趣点(如餐厅、酒店、景点等)进行聚类分析。例如,通过分析城市中餐厅的地理位置分布,DBSCAN 可以自动识别出不同区域的餐厅聚集区,如商业区、旅游区、居民区附近的餐厅簇等,同时过滤掉一些孤立的餐厅点(可能为噪声点)。城市规划者和商家可以根据这些聚类结果了解餐饮业的分布规律,为商业选址、城市规划、资源分配等提供决策依据。
代码实例
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 和 min_samples 值(根据图表选择最佳参数)
selected_eps = 0.6 # 根据图表调整
selected_min_samples = 6 # 根据图表调整# 使用选择的参数进行 DBSCAN 聚类
dbscan = DBSCAN(eps=selected_eps, min_samples=selected_min_samples)
dbscan_labels = dbscan.fit_predict(X_scaled)
X['DBSCAN_Cluster'] = dbscan_labels# 使用 PCA 降维到 2D 进行可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)# DBSCAN 聚类结果可视化
plt.figure(figsize=(6, 5))
sns.scatterplot(x=X_pca[:, 0], y=X_pca[:, 1], hue=dbscan_labels, palette='viridis')
plt.title(f'DBSCAN Clustering with eps={selected_eps}, min_samples={selected_min_samples} (PCA Visualization)')
plt.xlabel('PCA Component 1')
plt.ylabel('PCA Component 2')
plt.show()# 打印 DBSCAN 聚类标签的分布
print(f"DBSCAN Cluster labels (eps={selected_eps}, min_samples={selected_min_samples}) added to X:")
print(X[['DBSCAN_Cluster']].value_counts())
从结果来看 这个聚类是失败的 因为没有少数簇的数目太少,也可能是dbscan这个算法不太合适
(三)层次聚类算法
算法原理
- 层次聚类算法主要分为聚合层次聚类(Agglomerative Hierarchical Clustering)和分裂层次聚类(Divisive Hierarchical Clustering)两种。
- 聚合层次聚类是更为常用的方法,它将每个样本初始时作为一个单独的簇,然后按照一定的相似性或距离度量,逐步合并最近的簇,直到达到期望的聚类数目或满足停止条件,最终形成一个层次结构的聚类树(dendrogram)。
- 分裂层次聚类则相反,从所有样本作为一个整体簇开始,逐步将簇分裂成更小的簇,直到满足终止条件。
在合并或分裂簇的过程中,需要计算簇之间的相似性或距离,常见的方法有单链法(Single Linkage,基于两个簇之间最近的两个样本的距离)、完全链法(Complete Linkage,基于两个簇之间最远的两个样本的距离)、平均链法(Average Linkage,基于两个簇中所有样本的平均距离)等,不同的方法会对聚类结果产生不同的影响。
优点
- 无需预先指定聚类数目 :能够生成一个层次结构的树状图,直观地展示数据在不同层次上的聚类情况,用户可以根据实际需求从树中截取相应层次的聚类结果,具有更大的灵活性。
- 能够发现数据的层次结构 :对于具有明显层次关系的数据,如生物分类、文档的层次主题结构等,层次聚类能够有效地揭示这些层次信息,提供更丰富的数据洞察。
缺点
- 计算复杂度较高 :特别是对于大规模数据集,随着样本数量的增加,计算簇之间的相似性或距离的开销会急剧增大,导致算法运行时间较长,效率较低。
- 一旦合并或分裂操作完成,无法回退 :这可能会导致某些错误的合并或分裂操作无法纠正,从而影响最终的聚类结果质量,尤其是在数据存在噪声或异常值时,可能会对聚类树的构建产生较大干扰。
应用案例
在生物信息学领域,层次聚类常用于基因表达数据的分析。通过对不同基因在不同实验条件下表达水平的聚类,可以发现具有相似表达模式的基因簇,这些基因可能在相似的生物过程或功能通路中发挥作用。研究人员可以根据聚类结果进一步深入研究这些基因的功能关联、调控机制等,为疾病的诊断、治疗和药物研发提供重要的线索和依据。
代码实例
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# 标准化数据
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)# 评估不同 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}")# 绘制评估指标图
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()
# 提示用户选择 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())
# 层次聚类的树状图可视化
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()
- 横坐标代表每个簇对应样本的数据,这些样本数目加一起是整个数据集的样本数目。这是从上到下进行截断,p=3显示最后3层,不用p这个参数会显示全部。
- 纵轴代表距离 ,反映了在聚类过程中,不同样本或簇合并时的距离度量值。距离越大,意味着两个样本或簇之间的差异越大;距离越小,则差异越小。
三、 聚类评估指标介绍
以下是三种常用的聚类效果评估指标,分别用于衡量聚类的质量和簇的分离与紧凑程度:
1. 轮廓系数 (Silhouette Score)
- 定义:轮廓系数衡量每个样本与其所属簇的紧密程度以及与最近其他簇的分离程度。
- 取值范围:[-1, 1]
- 轮廓系数越接近 1,表示样本与其所属簇内其他样本很近,与其他簇很远,聚类效果越好。
- 轮廓系数越接近 -1,表示样本与其所属簇内样本较远,与其他簇较近,聚类效果越差(可能被错误分类)。
- 轮廓系数接近 0,表示样本在簇边界附近,聚类效果无明显好坏。
- 使用建议:选择轮廓系数最高的
k
值作为最佳簇数量。
2. CH 指数 (Calinski-Harabasz Index)
- 定义:CH 指数是簇间分散度与簇内分散度之比,用于评估簇的分离度和紧凑度。
- 取值范围:[0, +∞)
- CH 指数越大,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。
- 没有固定的上限,值越大越好。
- 使用建议:选择 CH 指数最高的
k
值作为最佳簇数量。
3. DB 指数 (Davies-Bouldin Index)
- 定义:DB 指数衡量簇间距离与簇内分散度的比值,用于评估簇的分离度和紧凑度。
- 取值范围:[0, +∞)
- DB 指数越小,表示簇间分离度越高,簇内紧凑度越高,聚类效果越好。
- 没有固定的上限,值越小越好。
- 使用建议:选择 DB 指数最低的
k
值作为最佳簇数量。
@浙大疏锦行