- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
1. 聚类算法是什么?
聚类就是将一个庞杂数据集中具有相似特征的数据自动归类到一起,称为一个簇,簇内的对象越相似,聚类的效果越好。“相似”这一概念,是利用距离标准来衡量的,我们通过计算对象与对象之间的距离远近来判断它们是否属于同一类别,即是否是同一个簇。
聚类是一种无监督学习(Unsupervised Learning)的方法,不需要预先标注好训练集。聚类与分类最大的区别就是分类的目标事先已知,对于一个动物集来说,你并不清楚这个数据集内部有多少种类的动物,你能做的只是利用聚类方法将它自动按照特征分为多类,然后人为给出这个聚类结果的定义(即簇识别)。例如,你将一个动物集分为了三簇(类),然后通过观察这三类动物的特征,你为每一个簇起一个名字,如大象、狗、猫等,这就是聚类的基本思想。
K-means就是一个聚类的算法,属于无监督学习算法,也是就样本没有标签。算法会根据某种规则进行“分割”,把相同的或者相近的数据放在一起。K-means算法的基本思想是通过不断更新簇的中心点,将数据集划分为预定数量的簇。这一过程涉及到计算数据点之间的距离,通常使用欧式距离作为相似性度量。在算法执行过程中,每个数据点被分配到距离最近的簇,然后更新簇的中心,迭代进行直至收敛。
2. K-means 算法思想
在聚类过程中,基于相似性度量,使得相同子集中各元素间差异性最小,而不同子集间的元素差异性最大,这就是(空间)聚类算法的本质,K-Means正是这样一种算法的代表。
K-Means聚类于1957年由J.B. MacQueen首次提出,目前已经超过60年,但仍然是应用最广泛、地位最核心的空间数据划分聚类方法之一。K-means算法算法的输入为一个样本集(或者称为点集),通过该算法可以将样本进行聚类,具有相似特征的样本聚为一类。
针对每个点,计算距离该点最近的中心点,然后将该点归为最近中心点代表的簇。一次迭代结束之后,针对每个簇类,重新计算中心点,然后针对每个点,重新寻找距离自己最近的中心点。如此循环,直到前后两次迭代的簇类没有变化。
3. K-means 聚类过程
K-means算法接受一个参数K用以决定结果中簇的数目。算法开始时,要在数据集中随机选择K个数据对象用来当做K个簇的初始中心,而将剩下的各个数据对象就根据他们和每个聚类簇心的距离选择簇心最近的簇分配到其中。然后重新计算各个聚类簇中的所有数据对象的平均值,并将得到的结果作为新的簇心;逐步重复上述的过程直至目标函数收敛为止。其步骤具体地:
- 第一步:从N个样本数据中随机选取K个对象,作为初始的聚类中心;
- 第二步:分别计算每个样本点到各个聚类中心的距离,并逐个分配到距离其最近的簇中;
- 第三步:所有对象分配完成后,更新K个类中心位置,类中心定义为簇内所有对象在各个维度的均值;
- 第四步:与前一次计算得到的K个聚类中心比较,如果聚类中心发生变化,转至步骤2,否则转至步骤5;
- 第五步:当类中心不再发生变化,停止并输出聚类结果,然后整理我们所需要的信息,如各个样本所属的类等等,进行后续的统计和分析。
聚类结束之前,类中心会不断移动,而随着类中心的移动,样本的划分情况也会持续发生改变。
4. PCA 主成分分析
PCA(Principal Component Analysis,主成分分析)是一种用于降维的统计技术,目的是将高维数据投影到一个低维空间,同时尽可能保留数据的主要特征。PCA 的主要目标是找到一组新的正交坐标系(称为主成分),使得数据在这些新坐标系中的方差最大。
PCA 通常用于以下场景:
- 降低数据维度,减少计算复杂度和存储空间。
- 数据可视化:将高维数据(如三维、四维或更多维度)转换为二维或三维进行可视化。
- 去除噪声:通过去除对数据贡献较小的主成分,减少噪声的影响。
5. k值的选择
k-means算法因为手动选取k值和初始化随机质心的缘故,每一次的结果不会完全一样,而且由于手动选取k值,我们需要知道我们选取的k值是否合理,聚类效果好不好,那么如何来评价某一次的聚类效果呢?
也许将它们画在图上直接观察是最好的办法,但现实是,我们的数据不会仅仅只有两个特征,一般来说都有十几个特征,而观察十几维的空间对我们来说是一个无法完成的任务。因此,我们需要一个公式来帮助我们判断聚类的性能,这个公式就是SSE (Sum of Squared Error, 误差平方和),它其实就是每一个点到其簇内质心的距离的平方值的总和,SSE值越小表示数据点越接近于它们的质心,聚类效果也越好。
因为对误差取了平方,因此更加重视那些远离中心的点。一种肯定可以降低SSE值的方法是增加簇的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
误差平方和 (SSE)
给定一个包含(n)个数据对象的数据集合
𝐷={𝑥1,𝑥2,⋯,𝑥𝑛}, 定义经由 K-means 算法进行聚类后产生的类划分集合为集合 𝐶={𝐶1,𝐶2,⋯,𝐶𝐾},则 SSE 的公式如下:
𝑆𝑆𝐸(𝐶)=∑𝐾𝑘=1∑𝑥𝑖∈𝐶𝑘||𝑥𝑖−𝑚𝑘||2
- (C_k) 是第 (k) 个簇的集合
- (m_k) 是第 (k) 个簇的质心
- (|| x_i - m_k ||^2) 是数据点 (x_i) 到簇质心 (m_k) 的欧氏距离的平方
该方法选择的并不是误差平方和最小的 (k),而是误差平方和突然变小时对应的 (k) 值。SSE 的值会迅速下降,在 (k = 2) 时的拐点到一个肘点。此外之后,代价函数的下降率便非常慢,所以我们选择 (k = 2)。这种方法叫做 "肘部法则"。
这是因为在确定聚类簇数时,还必须考虑计算成本。如果我们增加聚类的数量,计算成本也会增加。K-means算法还存在一个问题,它有可能会停留在一个局部最小值处,而这取决于初始化的情况。为了解决这个问题,我们通常需要多次运行K-means算法,每一次都重新进行随机初始化,最后再比较多次运行K-means的结果。
6. 轮廓系数(silhouette)
轮廓系数用于评估数据点与其所属簇的紧聚度及其与最近邻簇的分离度,得分范围在 [-1, 1] 之间,分值越高表示聚类效果越好。每个样本都有对应的轮廓系数,轮廓系数由两个分组成:
- (a):样本点与同一簇类中的其他样本点的平均距离(即紧聚度)。
- (b):样本点与距离最近的其他簇中的所有样本点的平均距离(即分离度)。
- (c): max(a,b) 保证分母始终是非零的,避免出现除以零的错误。
每个样本的轮廓系数定义为:
𝑆=𝑏−𝑎max(𝑎,𝑏)
一组数据的轮廓系数等于该数据集中每一个样本轮廓系数的平均值。轮廓系数既要考虑簇结果的紧聚度,又要考虑簇结果之间的分离度。如果一个数据点与自己所属的簇的其他数据点的距离很小,但是与其他簇中的数据点的距离很大,就表示这个数据点所在的簇内部紧密,簇间分离良好,那么该数据点的轮廓系数就较大。
数据集的轮廓系数
一个数据集的整体轮廓系数是所有样本轮廓系数的平均值:
𝑆dataset=1𝑛∑𝑛𝑖=1𝑆(𝑖)
- 整体轮廓系数较高(接近 1)表明聚类效果较好,簇内凝聚度高,簇间分离度大。
- 整体轮廓系数较低(接近 0 或负值)表明聚类效果较差,可能有些簇没有被很好地分开,甚至有的样本可能被分配到了错误的簇。
轮廓系数的意义
- 衡量簇内紧聚度:轮廓系数通过 𝑎 来衡量一个样本点与它所属簇内其他样本的紧密程度。如果这个值很大,说明簇内不够紧密,聚类质量可能较差。
- 衡量簇间分离度:通过 𝑏 来衡量一个样本点与最近的其他簇之间的距离。如果 𝑏 很大,说明簇间的分离度较好,表示簇间的距离足够远,聚类结果合理。
- 优化聚类结果:通过轮廓系数,我们可以判断聚类的数量是否合理。例如,如果增加或减少簇的数量会导致轮廓系数提升或降低,这说明当前的聚类数量是否是合适的。
7. 代码实现
# 导入必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score
# 读取数据集
iris = pd.read_csv("Iris.csv", header=0)
df = iris
# 选择特征列(不包含标签列)
columns = list(df.columns)
features = columns[:-1]
dataset = df[features]
# 标准化数据
scaler = StandardScaler()
dataset_scaled = scaler.fit_transform(dataset)
# 使用 Elbow Method 找出最优的 k 值
sse = []
silhouette_scores = []
k_values = range(2, 11) # k 从 2 开始,因为 silhouette score 不能计算只有一个簇的情况
for k in k_values:
kmeans = KMeans(n_clusters=k, n_init=10).fit(dataset_scaled)
sse.append(kmeans.inertia_)
# 计算轮廓系数
score = silhouette_score(dataset_scaled, kmeans.labels_)
silhouette_scores.append(score)
# 绘制肘部法图形 (SSE)
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(k_values, sse, 'bx-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Sum of Squared Errors (SSE)')
plt.title('Elbow Method for Optimal k')
# 绘制轮廓系数图形
plt.subplot(1, 2, 2)
plt.plot(k_values, silhouette_scores, 'bo-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score for Different k')
plt.tight_layout()
plt.show()
# 根据肘部法和轮廓系数选择最优的 k 值
k = 3 # 这里假设你根据肘部法和轮廓系数确定 k=3
# 使用 KMeans 进行聚类
kmeans = KMeans(n_clusters=k, n_init=10).fit(dataset_scaled)
labels = kmeans.labels_
centers = kmeans.cluster_centers_
# 定义聚类绘制函数,自动处理 PCA
def draw_cluster(dataset, centers, labels, k):
# 检查数据维度是否大于2,若大于2则进行 PCA 降维
if dataset.shape[1] > 2:
pca = PCA(n_components=2)
dataset = pca.fit_transform(dataset)
centers = pca.transform(centers) # 对质心也进行同样的降维操作
print("PCA applied, reduced dataset and centers to 2 dimensions.")
else:
# 如果数据维度 <= 2,直接使用原始数据
print("No PCA applied, data is already 2D or lower.")
# 绘制原始数据点
plt.scatter(dataset[:, 0], dataset[:, 1], c='black', s=7, marker='o', label='Original data')
# 定义颜色数组
colors = np.array([
"#FF0000", "#0000FF", "#00FF00", "#FFFF00", "#00FFFF", "#FF00FF",
"#800000", "#008000", "#000080", "#808000", "#800080", "#008080",
"#444444", "#FFD700", "#008080"
])
# 循环绘制每个簇,使用不同颜色
for i in range(k):
plt.scatter(
dataset[labels == i, 0], # 第 i 个聚类的数据点的 x 坐标
dataset[labels == i, 1], # 第 i 个聚类的数据点的 y 坐标
c=colors[i],
s=7,
marker='o',
label=f'Cluster {i}'
)
# 绘制聚类中心
plt.scatter(centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.75, marker='x', label='Centers')
plt.title(f'Clustered Data with {k} Clusters')
plt.legend()
plt.show()
# 调用函数绘制聚类结果
draw_cluster(dataset_scaled, centers, labels, k)
# 计算并输出最终的轮廓系数
final_silhouette_score = silhouette_score(dataset_scaled, labels)
print(f"Final Silhouette Score for k={k}: {final_silhouette_score}")