概述

根据给定的葡萄酒分类记录,建立聚类模型,对葡萄酒信息进行聚类(此时注意去除数据集中分类信息),并进行模型评估。即主要内容是基于葡萄酒信息进行聚类分析和模型评估。


首先加载并预处理数据,去除分类信息并进行标准化。接着,使用手肘法和轮廓系数确定K-means聚类的最佳簇数,并进行K-means聚类。然后,通过网格搜索找到DBSCAN的最佳参数,进行DBSCAN聚类。随后,采用最佳K值进行Agglomerative Clustering聚类。对三种聚类方法的结果进行评估,比较其轮廓系数并且使用PCA将数据降维至二维,不同颜色表示不同簇,绘制三种聚类模型的聚类结果图。最后,将每种聚类结果添加回原始数据,保存为Excel文件,以便进一步分析。

具体实现

1. 导包和数据预处理

导包和导数据:

import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import numpy as np
plt. rcParams [ 'font.sans-serif']=['SimHei'] #用来正常显示中文标签
plt. rcParams [ 'axes.unicode_minus']=False #用来正常显示负号
# 导入数据
file_path = r"YourPath\wine.xlsx"
wine_data = pd.read_excel(file_path)

因为聚类(Clustering)是一种无监督学习算法,而我们的数据集中的数据是有标签的,所以需要去掉标注然后进行聚类分析。然后每个属性数据值差异可能很大,所以需要把数据标准化从而更好地进行聚类,这里采用的是sklearn库里面的StandardScaler进行的标准化:

# 去掉分类信息那一列
wine_features = wine_data.drop(columns=['class'])

# 把数据标准化处理
scaler = StandardScaler()
wine_features_scaled = scaler.fit_transform(wine_features)

2. 使用K-means进行聚类

使用手肘法和轮廓系数确定K-means聚类的最佳簇数

手肘法是一种用于确定K-means中k值的方法。其核心思想是通过观察误差平方和(SSE)的变化曲线来选择最佳的聚类数。
而轮廓系数(Silhouette Coefficient)是一种用于评估聚类效果的指标,取值范围为[-1, 1]之间,值越接近于1越好。
于是,我从k=2开始遍历,每次遍历计算其对应的SSE和轮廓系数,并可视化展现出来:

# 使用手肘法和轮廓系数确定K值
sse = []
silhouette_scores = []
k_values = range(2, 11)
for k in k_values:
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(wine_features_scaled)
    sse.append(kmeans.inertia_)
    silhouette_scores.append(silhouette_score(wine_features_scaled, kmeans.labels_))

# 绘制手肘图
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(k_values, sse, 'bo-')
plt.xlabel('K值')
plt.ylabel('SSE的值')
plt.title('手肘法确定k-means的k值')

# 绘制轮廓系数图
plt.subplot(1, 2, 2)
plt.plot(k_values, silhouette_scores, 'bo-')
plt.xlabel('K值')
plt.ylabel('轮廓系数')
plt.title('不同K值对应的轮廓系数')
plt.show()

得到结果如下:
在这里插入图片描述
手肘法,顾名思义是要找到那个手肘的地方,也就是看在哪个地方,SSE值下降变得缓慢了,那个地方就是手肘,对应的K值也就是我们想要的。(为什么要这么选呢?因为随着聚类数的增大,样本划分会更加精细,误差平方和会逐渐变小。但K也不是越大越好,极端情况下,我们的k值和我们的样本数一样,每个样本被划分为一个类,这样的聚类是没有意义的,所以综合考虑,选取手肘的地方的K作为最佳)
而不同K值对应的轮廓系数是要越大越好,从上面的两张图可以看出,K=3时,效果最好,所以我们的聚类数应该选择为3,进行K-means聚类。

进行聚类并评估指标

直接调用sklearn里面的KMeans类即可,设置随机状态为42以确保结果的可复现性。

# 找到合适的K值
optimal_k = k_values[silhouette_scores.index(max(silhouette_scores))]
print(f"合适的K值是: {optimal_k}")

# 应用K-means进行聚类
kmeans = KMeans(n_clusters=optimal_k, random_state=42)
kmeans_labels = kmeans.fit_predict(wine_features_scaled)

# 评估这个聚类的性能指标
kmeans_silhouette_score = silhouette_score(wine_features_scaled, kmeans_labels)
print(f"K-means 的轮廓系数得分是: {kmeans_silhouette_score}")

在这里插入图片描述

使用PCA降维并且可视化聚类结果

因为葡萄酒的这个属性很多,也就是每个样本的维度很高,无法直接可视化输出聚类的结果,需要使用PCA降维至二维,才能进行可视化。

主成分分析(Principal Component Analysis,简称PCA)是一种广泛应用于数据降维的技术。其基本原理是通过线性变换将高维数据映射到低维空间中,同时尽可能保留数据中的主要变化模式。PCA的目标是找到最能代表数据方差的主成分,并用这些主成分来表示原始数据,从而减少数据的维度。

于是,代码如下:

pca = PCA(n_components=2)
wine_pca = pca.fit_transform(wine_features_scaled)
# 绘制聚类结果图
plt.figure(figsize=(10, 5))
plt.scatter(wine_pca[:, 0], wine_pca[:, 1], c=kmeans_labels, cmap='viridis', marker='o')
plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.title('K-means 聚类结果图')
plt.colorbar(label='Cluster')
plt.show()

wine_pca是一个二维数组,每一行代表一个样本,每一列代表一个主成分。
wine_pca[:, 0]是PCA降维后的第一主成分的值,wine_pca[:, 1]是PCA降维后的第二主成分的值。
这段代码第五行的c参数指定每个点的颜色。这里用K-means聚类的标签kmeans_labels来指定颜色,不同的聚类标签将会被分配不同的颜色。cmap参数指定颜色映射方案,这里使用的是viridis,不同的标签会被映射为viridis颜色图中的不同颜色。

可视化结果如图:
在这里插入图片描述

3. 使用DBSSCAN进行聚类

尝试应用聚类

DBSCAN是一种基于密度的聚类算法,不需要预先指定簇的个数,最终的簇的个数是不定的,所以我们不需要像K-means那样去找最佳簇数。
该算法的核心思想是通过检查数据集中每个点的r邻域来搜索簇,如果某个点p的r邻域内包含多于MinPts个点,则将该点标记为核心对象,并创建一个以p为核心的簇。
但是这个算法需要输入设置Eps(半径范围)、MinPts(最小点数)。Eps用于定义点之间的距离阈值,而MinPts则用于确定是否一个点可以被认为是核心点。如果在Eps范围内有超过MinPts个点,则该点被视为核心点。
我在第一次尝试的时候,想当然的随便设置了这两个参数值,而且还没有加上判断类数是否超过1(无效聚类),导致出错,debug花了很久…

下面代码的参数设置的不正确,导致出错:

# 使用DBSSCAN进行聚类
dbscan = DBSCAN(eps=1.0, min_samples=5)
dbscan_labels = dbscan.fit_predict(wine_features_scaled)

# 评估DBSCAN聚类的性能
if len(set(dbscan_labels)) > 1:
    dbscan_silhouette_score = silhouette_score(wine_features_scaled, dbscan_labels)
else:
    dbscan_silhouette_score = -1  # 只有一个类,说明是无效聚类
print(f"DBSCAN的轮廓系数得分: {dbscan_silhouette_score}")

在这里插入图片描述

遍历找寻最佳的DBSCAN参数

于是,我们需要来找到这两个参数:Eps(半径范围)、MinPts(最小点数)。直接通过两层遍历,每次计算最近的轮廓系数,把最佳的参数保存下来。
如果想要进一步提升性能,可以扩大参数范围或者减小步长,来找到更优的参数值

# 参数设置的不合适,找寻最佳的DBSCAN的参数
best_score = -1
best_eps = 0
best_min_samples = 0
for eps in np.arange(0.2, 2.5, 0.1):
    for min_samples in range(3, 10):
        dbscan = DBSCAN(eps=eps, min_samples=min_samples)
        dbscan_labels = dbscan.fit_predict(wine_features_scaled)
        if len(set(dbscan_labels)) > 1:
            score = silhouette_score(wine_features_scaled, dbscan_labels)
            if score > best_score:
                best_score = score
                best_eps = eps
                best_min_samples = min_samples

best_eps, best_min_samples, best_score

在这里插入图片描述
也就是说我们的半径范围设置为2.4,最小点数设置为3时可以取得最好的效果,此时的轮廓系数是0.2144

可视化聚类结果

因为前面用PCA降维了,所以这里直接得到结果绘图就行,代码如下:

# 用最好的参数进行DBSCAN聚类
dbscan = DBSCAN(eps=best_eps, min_samples=best_min_samples)
dbscan_labels = dbscan.fit_predict(wine_features_scaled)
if len(set(dbscan_labels)) > 1:
    dbscan_silhouette_score = silhouette_score(wine_features_scaled, dbscan_labels)
else:
    dbscan_silhouette_score = -1

# 绘制DBSCAN聚类结果图
plt.figure(figsize=(10, 5))
unique_labels = set(dbscan_labels)
colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]
for k, col in zip(unique_labels, colors):
    if k == -1:
        # 噪声
        col = [0, 0, 0, 1]
    class_member_mask = (dbscan_labels == k)
    xy = wine_pca[class_member_mask]
    plt.scatter(xy[:, 0], xy[:, 1], c=[tuple(col)], label=f'Cluster {k}' if k != -1 else 'Noise', edgecolor='k')
plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.title('DBSCAN 聚类结果图')
plt.legend()
plt.show()

结果如下:
在这里插入图片描述

4. 使用AGglomerative NESting进行聚类

AGNES(Agglomerative Nesting)是一种层次聚类算法,采用自底向上的聚合策略。其基本思想是将数据集中的每个样本作为一个初始聚类簇,然后在算法运行的每一步中找出距离最近的两个簇,并将它们合并成一个新的簇。
具体实现的代码比较简单,步骤和前面也比较像,就不过多赘述了:

# 使用AGglomerative NESting进行聚类
agg = AgglomerativeClustering(n_clusters=optimal_k)
agg_labels = agg.fit_predict(wine_features_scaled)

# 评估AGglomerative NESting聚类
agg_silhouette_score = silhouette_score(wine_features_scaled, agg_labels)
print(f"AGglomerative NESting的轮廓系数: {agg_silhouette_score}")

在这里插入图片描述

# 绘制AGglomerative NESting聚类结果
plt.figure(figsize=(10, 5))
unique_labels = set(agg_labels)
colors = [plt.cm.Spectral(each) for each in np.linspace(0, 1, len(unique_labels))]
for k, col in zip(unique_labels, colors):
    class_member_mask = (agg_labels == k)
    xy = wine_pca[class_member_mask]
    plt.scatter(xy[:, 0], xy[:, 1], c=[tuple(col)], label=f'Cluster {k}', edgecolor='k')

plt.xlabel('主成分 1')
plt.ylabel('主成分 2')
plt.title('AGglomerative NESting 聚类结果图')
plt.legend()
plt.show()

在这里插入图片描述

5. 保存结果并与真实类别相比较

把每次聚类的结果写回,保存为excel,方便后续进一步分析处理。

# 把每一种聚类的结果添加回去,与真实的比较一下,看看哪种最好
wine_data['KMeans_Cluster'] = kmeans_labels
wine_data['DBSCAN_Cluster'] = dbscan_labels
wine_data['Agg_Cluster'] = agg_labels

# 保存结果到Excel文件
output_path = r"YourPath\result_wine_clustering.xlsx"
wine_data.to_excel(output_path, index=False)

wine_data.head()

在这里插入图片描述
可以看出,确实是有些样本的聚类和真实的是不一样的。

总结

从结果可以发现K-means聚类效果最好,其次是AGglomerative NESting,DBSCAN效果最差。而这三种都是基于划分、密度、层次的聚类中的典型算法,每种算法的具体应用场景不一样,选择对应的算法的时候也要根据环境选择更为合适的算法。

如果本文对您有帮助的话,希望您能点个赞,谢谢!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐