Day 18:推断聚类后簇的类型
Review
回顾无监督学习的聚类算法几个衡量标准
1. 轮廓系数(Silhouette Score)
- 轮廓系数衡量每一个样本与所属簇的紧密程度和与最近其他簇的分离程度,通过比较样本与其所属簇内其他样本的平均距离(簇内凝聚度,a(i)a(i)a(i))以及样本与其最近其他簇的平均距离(簇间分离度,b(i)b(i)b(i))来评估聚类质量。计算公式:s(i)=b(i)−a(i)max(a(i),b(i))s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}s(i)=max(a(i),b(i))b(i)−a(i)
- 取值范围:[-1, 1]
- 接近1:样本被很好的分类到正确的簇,聚类效果好
- 接近-1:样本可能被错误的分配到其他簇,聚类效果差
- 接近0:样本位于簇边界,聚类效果不明显
- 使用建议:选择轮廓系数最高的簇数量kkk作为最佳方案,适用于k-means等基于距离的算法。
2. CH指数(Calinski-Harabasz Index)
- CH 指数(Calinski-Harabasz Index)评估聚类质量,通过簇间分散度与簇内分散度之比来衡量簇的分离度和紧凑度。计算公式:CH=trace(B)/(k−1)trace(W)/(n−k)CH=\frac{{trace(B)} / {(k-1)}}{{trace(W)}/{(n-k)}}CH=trace(W)/(n−k)trace(B)/(k−1)
其中:- B: 簇内协方差矩阵(between-cluster dispersion matrix)
- W: 簇内协方差矩阵(within-cluster dispersion matrix)
- k: 簇数量
- n: 样本总数
- 取值范围:[0, +∞)
CH指数越大表明簇间分离度越高,簇内紧凑度越高,效果越好。 - 使用建议:选择 CH 指数最高的簇数量 𝑘 作为最佳聚类方案,适用于 K-Means 等基于距离的算法。假设簇凸形且大小相似,建议结合其他指标(如轮廓系数)综合评估,并在高维数据上进行预处理以提高可靠性。
DB指数(Davies-Bouldin Index)
- DB 指数评估聚类质量,通过衡量每个簇与其最相似簇的簇内分散度与簇间距离的比值来计算。其公式为:DB=1k∑i=1kmaxj≠i(si+sjdij)DB=\frac{1}{k}\sum_{i=1}^{k}\max_{j \neq i}(\frac{s_i + s_j}{d_{ij}})DB=k1i=1∑kj=imax(dijsi+sj)
其中:- sis_isi:簇 iii 的平均簇内距离(簇内分散度)
- dijd_{ij}dij:簇iii和簇jjj中心间的距离(簇间分离度)
- 取值范围:[0, +∞)
值越小表示簇间分离度越高、簇内紧凑度高,聚类效果好 - 使用建议:选择 DB 指数最低的簇数量 𝑘 作为最佳聚类方案,适用于 K-Means 等基于距离的算法。假设簇球形且基于欧氏距离,建议结合其他指标(如 CH 指数)综合评估,并在噪声或高维数据上进行预处理以提高可靠性。
Today
# 先运行之前预处理好的代码
import pandas as pd
import pandas as pd #用于数据处理和分析,可处理表格数据。
import numpy as np #用于数值计算,提供了高效的数组操作。
import matplotlib.pyplot as plt #用于绘制各种类型的图表
import seaborn as sns #基于matplotlib的高级绘图库,能绘制更美观的统计图形。
import warnings
warnings.filterwarnings("ignore")# 设置中文字体(解决中文显示问题)
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows系统常用黑体字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
data = pd.read_csv('data.csv') #读取数据# 先筛选字符串变量
discrete_features = data.select_dtypes(include=['object']).columns.tolist()
# Home Ownership 标签编码
home_ownership_mapping = {'Own Home': 1,'Rent': 2,'Have Mortgage': 3,'Home Mortgage': 4
}
data['Home Ownership'] = data['Home Ownership'].map(home_ownership_mapping)# Years in current job 标签编码
years_in_job_mapping = {'< 1 year': 1,'1 year': 2,'2 years': 3,'3 years': 4,'4 years': 5,'5 years': 6,'6 years': 7,'7 years': 8,'8 years': 9,'9 years': 10,'10+ years': 11
}
data['Years in current job'] = data['Years in current job'].map(years_in_job_mapping)# Purpose 独热编码,记得需要将bool类型转换为数值
data = pd.get_dummies(data, columns=['Purpose'])
data2 = pd.read_csv("data.csv") # 重新读取数据,用来做列名对比
list_final = [] # 新建一个空列表,用于存放独热编码后新增的特征名
for i in data.columns:if i not in data2.columns:list_final.append(i) # 这里打印出来的就是独热编码后的特征名
for i in list_final:data[i] = data[i].astype(int) # 这里的i就是独热编码后的特征名# Term 0 - 1 映射
term_mapping = {'Short Term': 0,'Long Term': 1
}
data['Term'] = data['Term'].map(term_mapping)
data.rename(columns={'Term': 'Long Term'}, inplace=True) # 重命名列
continuous_features = data.select_dtypes(include=['int64', 'float64']).columns.tolist() #把筛选出来的列名转换成列表# 连续特征用中位数补全
for feature in continuous_features: mode_value = data[feature].mode()[0] #获取该列的众数。data[feature].fillna(mode_value, inplace=True) #用众数填充该列的缺失值,inplace=True表示直接在原数据上修改。# 最开始也说了 很多调参函数自带交叉验证,甚至是必选的参数,你如果想要不交叉反而实现起来会麻烦很多
# 所以这里我们还是只划分一次数据集
from sklearn.model_selection import train_test_split
X = data.drop(['Credit Default'], axis=1) # 特征,axis=1表示按列删除
y = data['Credit Default'] # 标签
# # 按照8:2划分训练集和测试集
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 80%训练集,20%测试集
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns# 标准化数据(聚类前通常需要标准化)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# X_scaled
对簇数量从2到20进行评测
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()# 提示用户选择 k 值
selected_k = 3 # 这里选择3后面好分析,也可以根据图选择最佳的k值# 使用选择的 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())
KMeans Cluster labels (k=3) added to X:
KMeans_Cluster
0 5205
1 1381
2 914
Name: count, dtype: int64
聚类算法(如 K-Means)将数据分成 k=3 个簇时,评估指标(如轮廓系数、CH 指数、DB 指数)仅用于量化聚类质量(e.g., 簇内样本相似、簇间差异大),但簇的实际含义需根据数据设计和领域知识来赋予。这取决于特征选择策略,主要有两种方法:
-
目标驱动的方法(预先选择特征):
如果事先明确关键特征(如苹果的大小),则直接基于这些特征进行聚类。
结果解释:簇对应特征的自然分组,例如将苹果分成“小、中、大”三类。这是一种从设计开始就目标导向的聚类,确保簇含义与业务需求对齐。
-
探索驱动的方法(全特征聚类 + 特征筛选):
-
如果不确定用哪些特征,则使用数据集中的全部特征进行聚类,得到簇标签(e.g., 簇 0、1、2)。
-
后续处理:将这些簇标签作为“伪标签”(y),其余特征作为输入(X),构建监督学习模型(如随机森林或梯度提升树)。
-
通过模型的特征重要性分析(e.g., Gini 重要性或 SHAP 值),筛选出对簇区分最关键的特征,从而为簇赋予实际含义(e.g., 发现“大小”和“颜色”是主导特征,则解释簇为不同品质的苹果组)。
-
这适用于复杂数据场景,帮助从无监督结果中“逆向工程”出解释。
-
x1= X.drop('KMeans_Cluster',axis=1) # 删除聚类标签列
y1 = X['KMeans_Cluster']
# 构建随机森林,用shap重要性来筛选重要性
import shap
import numpy as np
from sklearn.ensemble import RandomForestClassifier # 随机森林分类器
model = RandomForestClassifier(n_estimators=100, random_state=42) # 随机森林模型
model.fit(x1, y1) # 训练模型,此时无需在意准确率 直接全部数据用来训练了
shap.initjs()
# 初始化 SHAP 解释器
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(x1) # 这个计算耗时
shap_values.shape # 第一维是样本数,第二维是特征数,第三维是类别数
(7500, 31, 3)
# --- 1. SHAP 特征重要性条形图 (Summary Plot - Bar) ---
print("--- 1. SHAP 特征重要性条形图 ---")
shap.summary_plot(shap_values[:, :, 0], x1, plot_type="bar",show=False) # 这里的show=False表示不直接显示图形,这样可以继续用plt来修改元素,不然就直接输出了
plt.title("SHAP Feature Importance (Bar Plot)")
plt.show()
# X["Purpose_debt consolidation"].value_counts() # 统计每个唯一值的出现次数
import matplotlib.pyplot as plt# 总样本中的前四个重要性的特征分布图
fig, axes = plt.subplots(2, 2, figsize=(12, 8))
axes = axes.flatten()for i, feature in enumerate(selected_features):axes[i].hist(X[feature], bins=20)axes[i].set_title(f'Histogram of {feature}')axes[i].set_xlabel(feature)axes[i].set_ylabel('Frequency')plt.tight_layout()
plt.show()
到这里回顾一下无目标驱动的聚类过程:我们使用数据集中的所有特征进行聚类分析,得到簇标签作为结果;然后,将这些簇标签作为“伪标签”(y),以原始特征作为输入(X),构建监督学习模型并进行训练,以捕捉哪些特征组合能够最好地预测对应的簇标签;最后,通过计算SHAP值,量化每个特征对模型预测概率的贡献(包括正向或负向影响,以及高/低特征值下的效果),从而分析和筛选关键特征,为聚类结果赋予实际含义。
@浙大疏锦行