
- 个人首页: 永远都不秃头的程序员(互关)
- C语言专栏:从零开始学习C语言
- C++专栏:C++的学习之路
- 本文章所属专栏:K-Means深度探索系列
文章目录
-
-
- 引言:算法家族的成员们------多样性与选择智慧
- K-Medoids:更"健壮"的簇中心
-
- 理论解读:从"均值"到"中位数"
- 优势与劣势
- [代码实践:K-Medoids 初体验](#代码实践:K-Medoids 初体验)
- [Fuzzy C-Means:模糊的归属,细致的洞察](#Fuzzy C-Means:模糊的归属,细致的洞察)
-
- 理论解读:软分配与成员度
- 优势与劣势
- [代码实践:Fuzzy C-Means 的模糊世界](#代码实践:Fuzzy C-Means 的模糊世界)
- [K-Means 的"亲戚"们:选择的智慧](#K-Means 的“亲戚”们:选择的智慧)
-
引言:算法家族的成员们------多样性与选择智慧
亲爱的读者朋友们,欢迎回到我们的"K-Means深度探索"系列!我们已经走过了漫长的旅程,从零手撕 K-Means,到 K 值选择、优化初始化、大数据处理,再到深入图像和商业应用,甚至批判性地审视了其局限性。你现在对 K-Means 的理解,无疑已达到了新的高度!
然而,在聚类算法的广阔世界中,K-Means 并非孤军奋战。它有一个庞大的"算法家族",其中有许多算法与 K-Means 共享核心思想,但在关键细节上有所创新,以适应不同的数据特性和应用场景。理解这些"亲戚"们,不仅能帮助你更全面地认识聚类算法的多样性,更能让你在面对复杂多变的数据时,拥有更灵活、更智慧的选择能力。
今天,我们将聚焦 K-Means 的两位重要"亲戚":K-Medoids 和 Fuzzy C-Means(模糊 C 均值)。它们分别从"簇中心的选择"和"数据点对簇的归属方式"两个维度,对 K-Means 进行了改造。通过对比学习,你将更深刻地理解 K-Means 的内在假设,并知道在何时选择它们,而非 K-Means。准备好了吗?让我们一起拜访 K-Means 的"亲戚"们,拓宽你的聚类视野!💡
K-Medoids:更"健壮"的簇中心
理论解读:从"均值"到"中位数"
K-Medoids(K-中心点算法)是 K-Means 的一个直接变种。它们最核心的区别在于:
- K-Means: 簇中心(质心)是簇内所有数据点的平均值(Mean)。这个质心可能不是数据集中的任何一个真实数据点。
- K-Medoids: 簇中心(称为中心点 或Medoid )是簇内真实存在的数据点中,距离簇内其他所有点最近的那个点。它实际上是簇内数据点的**中位数(Median)**概念的泛化。
K-Medoids 算法的基本步骤:
- 选择 K 个初始中心点: 随机从数据集中选择 K 个数据点作为初始中心点。
- 分配阶段: 将每个非中心点的数据点分配到离它最近的中心点所在的簇。
- 更新阶段: 对于每个簇,遍历簇内的每一个数据点。如果将某个数据点设为新的中心点,能够使该簇内所有点到新中心点的距离之和(或距离平方和)最小,那么就用这个点替换当前的中心点。
- 重复: 重复分配和更新阶段,直到中心点不再变化。
优势与劣势
-
优势 (相比 K-Means):
- 对异常值更鲁棒: 由于中心点是真实的观测数据点,而不是一个可能被极端值拉偏的"平均点",K-Medoids 对异常值(离群点)的敏感度大大降低。一个极端的离群点不会成为中心点,也不会显著影响中心点的选择。
- 更好的可解释性: 簇中心是一个真实的数据点,这在某些场景下更具解释性。例如,在客户细分中,K-Medoids 能找出每个客户群体中最具代表性的"典型客户"。
- 可以处理非数值型数据: 只要能够定义数据点之间的距离(甚至是非欧氏距离,如汉明距离),K-Medoids 就能工作。因为它的中心点是实际数据点,不依赖于求均值。
-
劣势 (相比 K-Means):
- 计算成本更高: 在更新阶段,K-Medoids 需要遍历簇内的每个点,尝试将其作为中心点并计算总距离,这比 K-Means 简单地求均值复杂得多,特别是对于大型数据集。
代码实践:K-Medoids 初体验
sklearn 中没有直接提供 K-Medoids 算法,但我们可以使用 sklearn_extra 库中的 KMedoids。
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn_extra.cluster import KMedoids # 导入 KMedoids
from sklearn.preprocessing import StandardScaler
# 1. 数据准备:生成带有一些异常值的示例数据
X, y_true = make_blobs(n_samples=300, centers=3, cluster_std=0.8, random_state=42)
# 引入一些异常值
outliers = np.array([[6, 8], [7, 7], [8, 6]])
X = np.vstack([X, outliers])
# 数据标准化 (对距离敏感的算法通常推荐)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 2. 运行 K-Means (作为对比)
kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42)
kmeans_labels = kmeans.fit_predict(X_scaled)
# 3. 运行 K-Medoids
# method='pam' (Partitioning Around Medoids) 是经典算法
kmedoids = KMedoids(n_clusters=3, metric='euclidean', method='pam', random_state=42)
kmedoids_labels = kmedoids.fit_predict(X_scaled)
# 4. 可视化 K-Means 与 K-Medoids 的结果对比
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
# K-Means 结果
axes[0].scatter(X_scaled[:, 0], X_scaled[:, 1], c=kmeans_labels, s=50, cmap='viridis', alpha=0.8)
axes[0].scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1],
marker='X', s=250, color='red', edgecolor='black', linewidth=2, label='K-Means Centroids')
axes[0].set_title('K-Means Clustering (with outliers)')
axes[0].set_xlabel('Scaled Feature 1')
axes[0].set_ylabel('Scaled Feature 2')
axes[0].legend()
axes[0].grid(True, linestyle='--', alpha=0.6)
# K-Medoids 结果
axes[1].scatter(X_scaled[:, 0], X_scaled[:, 1], c=kmedoids_labels, s=50, cmap='viridis', alpha=0.8)
# KMedoids的cluster_centers_实际上是medoid点的坐标
axes[1].scatter(kmedoids.cluster_centers_[:, 0], kmedoids.cluster_centers_[:, 1],
marker='X', s=250, color='red', edgecolor='black', linewidth=2, label='K-Medoids Medoids')
axes[1].set_title('K-Medoids Clustering (with outliers)')
axes[1].set_xlabel('Scaled Feature 1')
axes[1].set_ylabel('Scaled Feature 2')
axes[1].legend()
axes[1].grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()
运行代码后,你可能会观察到 K-Medoids 的中心点(Medoids)更倾向于位于簇的"密集"区域,而 K-Means 的质心可能因异常值的存在而被轻微"拉偏"。这个例子直观地展示了 K-Medoids 在处理异常值时的健壮性。
Fuzzy C-Means:模糊的归属,细致的洞察
理论解读:软分配与成员度
K-Means 和 K-Medoids 都属于**硬聚类(Hard Clustering)**算法:每个数据点明确地只属于一个簇。但想象一下,一个客户既像"新用户",又像"潜在流失用户";或者一个图片像素的颜色处于两个主要颜色的交界处,它真的只能属于其中一个吗?
**Fuzzy C-Means(FCM)**提供了一个更灵活的视角:模糊聚类(Fuzzy Clustering) 。它的核心思想是:每个数据点不再被明确地分配给一个簇,而是以一个"成员度"(Membership Degree)或"概率"归属于所有簇。 成员度之和为 1。
FCM 算法的基本步骤:
-
初始化成员度矩阵: 随机为每个数据点分配对 K 个簇的初始成员度,确保每个点的成员度之和为 1。
-
迭代优化:
- 计算新的质心: 与 K-Means 类似,但 FCM 的质心是所有数据点的加权平均,权重就是每个数据点对该簇的成员度。成员度越高,该点对质心的贡献越大。
- 更新成员度矩阵: 根据新的质心,重新计算每个数据点对所有簇的成员度。成员度与数据点到簇质心的距离成反比(距离越远,成员度越低),并使用一个模糊化参数 m m m 来控制模糊程度。
-
重复: 重复计算质心和更新成员度,直到成员度矩阵的变化小于某个阈值。
优势与劣势
-
优势 (相比 K-Means):
- 提供更丰富的信息: 对于每个数据点,FCM 提供了它属于所有簇的可能性,这对于理解数据中的重叠和边界情况非常有用。
- 更灵活地处理边界数据: 能够更好地处理那些模糊不清、位于多个簇之间的数据点,避免硬性划分的错误。
- 更接近真实世界的复杂性: 许多真实世界的现象本身就是模糊的,FCM 能更好地捕捉这种模糊性。
-
劣势 (相比 K-Means):
- 计算成本更高: 需要迭代更新成员度矩阵,计算量更大。
- 结果解释更复杂: 从硬性簇标签到模糊成员度,对结果的理解和可视化需要更多技巧。
- 对 K 值敏感: 和 K-Means 一样,K 值(这里的 C 值)仍然需要预设。
代码实践:Fuzzy C-Means 的模糊世界
Python 的 scikit-fuzzy 库提供了 FCM 的实现。我们来使用它,看看模糊成员度是如何呈现的。
python
import numpy as np
import matplotlib.pyplot as plt
import skfuzzy as fuzz # 导入 skfuzzy 库
from sklearn.datasets import make_blobs
from sklearn.preprocessing import StandardScaler
# 1. 数据准备:生成一些有重叠的示例数据
X, y_true = make_blobs(n_samples=250, centers=3, cluster_std=1.2, random_state=42)
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 2. 运行 Fuzzy C-Means
k_clusters = 3 # 目标簇数量
m_fuzzifier = 2 # 模糊化参数,通常设为 2 (1表示硬聚类,越大越模糊)
# cntr: 最终的簇中心 (质心)
# u: 最终的成员度矩阵 (每个数据点对每个簇的成员度)
# u0: 初始的成员度矩阵
# d: 距离矩阵
# jm: 目标函数值
# p: 迭代次数
# fpc: 模糊划分系数
cntr, u, u0, d, jm, p, fpc = fuzz.cmeans(
X_scaled.T, k_clusters, m_fuzzifier, error=0.005, maxiter=1000, seed=42
)
# 获取每个数据点最可能属于的簇 (为了可视化,从模糊转为硬分配)
cluster_membership = np.argmax(u, axis=0) # 找到每个点成员度最高的簇
# 3. 可视化 Fuzzy C-Means 结果
fig, ax = plt.subplots(figsize=(10, 7))
# 绘制数据点,颜色表示其最可能属于的簇
ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=cluster_membership, s=50, cmap='viridis', alpha=0.8, label='Data Points')
# 绘制簇中心
ax.scatter(cntr[:, 0], cntr[:, 1], marker='X', s=250, color='red', edgecolor='black', linewidth=2, label='FCM Centroids')
ax.set_title('Fuzzy C-Means Clustering')
ax.set_xlabel('Scaled Feature 1')
ax.set_ylabel('Scaled Feature 2')
ax.legend()
ax.grid(True, linestyle='--', alpha=0.6)
plt.show()
# 打印部分数据点的模糊成员度
print("\n--- 部分数据点的模糊成员度示例 ---")
for i in range(5): # 打印前5个数据点
print(f"数据点 {i}: {X_scaled[i].round(2)} -> 成员度: {u[:, i].round(3)}")
# 打印模糊划分系数 FPC (Fuzzy Partition Coefficient)
# FPC 接近 1 表示划分更清晰,接近 1/K 表示更模糊
print(f"\n模糊划分系数 (FPC): {fpc:.3f}")
运行代码后,你将看到一个由 FCM 聚类后的散点图,以及一些数据点对不同簇的成员度。你会发现,对于位于簇边界的数据点,其成员度可能不会是某个簇为 1 而其他为 0,而是对多个簇都有一个非零的成员度,这正是 FCM 的魅力所在!✨
K-Means 的"亲戚"们:选择的智慧
现在,我们对 K-Means、K-Medoids 和 Fuzzy C-Means 有了更全面的认识。总结一下它们的关键特性和适用场景:
| 特性/算法 | K-Means | K-Medoids | Fuzzy C-Means |
|---|---|---|---|
| 簇中心 | 簇内数据点均值 (虚拟点) | 簇内真实数据点 (Medoid) | 簇内数据点加权均值 (虚拟点) |
| 数据点归属 | 硬分配 (只属于一个簇) | 硬分配 (只属于一个簇) | 软分配 (以成员度归属所有簇) |
| 对异常值 | 敏感 | 鲁棒 | 相对鲁棒 (权重降低影响) |
| 计算成本 | 相对较低 | 较高 | 较高 |
| 结果解释 | 直观,易理解 | 质心是真实点,可解释性强 | 提供成员度,解释需技巧 |
| 适用场景 | 经典,球形簇,无明显异常 | 存在异常值,非数值数据,需典型代表 | 簇重叠/模糊,需细致归属信息 |
何时选择哪个"亲戚"?
- 如果你追求高效 、简单直观 ,且数据符合球形簇 、无明显异常值 的假设,K-Means 依然是你的首选。
- 如果你担心数据中存在异常值 ,或者希望簇中心是真实的数据点 (更具代表性),并且可以接受更高的计算成本,那么K-Medoids 可能是更好的选择。
- 如果你的数据集中存在模糊或重叠的簇 ,或者你希望获得每个数据点对所有簇的概率性归属信息 ,以便进行更细致的分析,那么 Fuzzy C-Means 将是你的强大工具。
总结与展望:探索聚类算法的无限可能
深入理解K-Means及其变种算法,就如同站在巨人的肩膀上,让我们能够以更广阔的视野洞察聚类领域的无限潜力。这种能力正是成为真正人工智能专家所需的批判性思维和灵活应变能力的重要体现。