☰
模糊C均值聚类源码解析:从K-Means过渡到FCM实战
2026/10/3 2:53:47 网站建设 项目流程

简介:这份源码包面向数据挖掘与机器学习初学者,聚焦模糊C均值(FCM)聚类算法的Python实现与可视化,适合正在学习聚类、需要动手复现算法的学生和开发者。压缩包共29个文件,约638KB,包含4个py脚本、2个ipynb笔记本、8张png可视化图、3个txt说明及csv、xlsx数据文件等,覆盖数据加载、模型训练、结果输出与图像绘制等环节。源码中自定义了FCM的隶属度更新与类别中心迭代流程,并借助NumPy、Pandas、Matplotlib完成数据处理与二维散点展示,同时保留KMeans作为对照,便于观察模糊系数m与类别数C对聚类效果的影响。已有167人学习,读者可据此理解FCM目标函数、迭代停止条件与隶属度可视化方法,并在此基础上修改参数、替换数据集,完成从算法原理到代码落地的完整实践。

1. 拿到一份 FCM 聚类源码,先别急着跑,搞清楚它能干什么

很多做数据分析的朋友第一次接触模糊C均值,都是被 K-Means 在重叠样本上翻车逼过来的。K-Means 硬性把每个点划给唯一簇,遇到边界模糊、噪声多的数据,聚类结果就飘得厉害。这份「模糊C均值做聚类并做可视化(源码).zip」正好补上这个短板:它用 Python 实现了 FCM 算法,允许一个样本以不同隶属度同时归属多个类别,还配了完整的可视化输出。压缩包里包含 dataLoader.py、Trainer.py、vision.py、run.py 四个核心脚本,外加一份聚类数据.xlsx 和 results 目录下的散点图、损失曲线、聚类结果 CSV。适合正在做课程设计、数据分析项目,或者想从 K-Means 过渡到模糊聚类的从业者。你拿到手就能跑,但前提是先把环境、数据格式和参数含义对齐,否则大概率卡在第一步。

2. 拆开源码包:四个脚本各管什么,数据怎么流转

2.1 模块分工与调用链

这个项目的结构很清晰,没有过度封装,适合拿来改。run.py是入口,负责串联整个流程;dataLoader.py管数据读取和预处理;Trainer.py是 FCM 算法的核心实现,包含隶属度矩阵更新和聚类中心迭代;vision.py负责画图,输出散点图、损失曲线和每个类别的单独分布图。dataset目录下放的是聚类数据.xlsx,results目录是运行后的输出位置,里面已经预置了cluster_results.csv、result.txt以及多张 PNG 图片,说明作者跑通过一轮,你可以拿这些结果做对照。

调用链是:run.py调dataLoader读 Excel → 数据传给Trainer做 FCM 迭代 → 迭代完把标签和隶属度交给vision画图 → 同时把结果写进results。整个流程没有用 sklearn 的 FCM(sklearn 也没有官方 FCM),而是手写实现,这对理解算法细节反而更友好。

2.2 环境依赖与版本对齐

压缩包里有一份环境需求.txt,这是第一手线索。虽然具体内容需要你解压后查看,但根据代码结构和.pyc文件名里的cpython-38,可以确定作者用的是 Python 3.8。常见做法是建一个干净虚拟环境,把依赖锁在相近版本,避免 numpy 或 pandas 大版本差异导致FutureWarning甚至 API 报错。

# 创建虚拟环境,Python 3.8 与 pyc 文件匹配 python3.8 -m venv fcm_env source fcm_env/bin/activate # Windows 用 fcm_env\Scripts\activate # 安装核心依赖,版本按环境需求.txt 调整 pip install numpy pandas matplotlib openpyxl scikit-learn

这里openpyxl容易被漏掉,因为数据是.xlsx格式,pandas 读 Excel 必须靠它。scikit-learn在项目里可能用于对比 K-Means 或者做标准化,不装的话 import 阶段就会断。装完先别急着python run.py,下一步要确认数据格式。

2.3 数据加载与预处理逻辑

dataLoader.py的职责是把 Excel 转成 numpy 数组,并做必要的清洗。FCM 对量纲敏感,如果一列是年龄(0-100),另一列是收入(0-100000),距离计算会被大数值主导。常见做法是做标准化或归一化。你可以打开dataLoader.py看它有没有StandardScaler或手写的 min-max 归一化。如果没有,建议自己补上,否则聚类中心会偏向大尺度特征。

# dataLoader.py 中典型的数据读取与预处理片段 import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler def load_data(filepath): df = pd.read_excel(filepath) # 假设最后一列是标签或无关列,特征列在前 features = df.iloc[:, :-1].values.astype(float) # 标准化:FCM 基于欧氏距离,量纲统一是前提 scaler = StandardScaler() features_scaled = scaler.fit_transform(features) return features_scaled

这段逻辑说明:iloc[:, :-1]取特征列,astype(float)防止整数除法或类型报错。标准化用fit_transform而不是transform,因为这是训练集全量。如果你的数据里有缺失值,StandardScaler会直接报错,需要先fillna或dropna。参数上,StandardScaler按列减均值除标准差,适合近似高斯分布的数据;如果数据有极端离群值,换成MinMaxScaler更稳。

3. 跑通 FCM 核心:隶属度矩阵、模糊系数与迭代停止条件

3.1 FCM 与 K-Means 的本质差异

K-Means 的隶属度只有 0 或 1,一个样本要么属于 A 簇,要么属于 B 簇。FCM 把隶属度推广到 [0,1] 连续区间,用隶属度矩阵 U 表示每个样本对每个簇的归属程度,且每个样本对所有簇的隶属度之和为 1。目标函数是J = ΣΣ(u_ij^m * ||x_i - c_j||^2),其中 m 是模糊系数,控制隶属度的模糊程度。m 越大,隶属度越分散;m 趋近 1 时,FCM 退化成 K-Means。常见取值是 m=2,这也是Trainer.py里最可能出现的默认值。

这个差异带来的实际好处是:当两个簇有重叠区域时,FCM 不会强行切一刀,而是给出「这个点 60% 属于簇 0,40% 属于簇 1」这样的软判决。对于后续做风险分层或推荐系统,这种概率式输出比硬标签更有信息量。

3.2 隶属度更新与聚类中心迭代

Trainer.py的核心是一个 while 循环,每轮先更新隶属度,再更新聚类中心,直到目标函数变化小于阈值或达到最大迭代次数。下面是根据项目结构还原的典型实现:

# Trainer.py 中 FCM 迭代的核心逻辑 import numpy as np class FCM: def __init__(self, n_clusters=3, m=2, max_iter=150, tol=1e-5): self.n_clusters = n_clusters # 类别数 C self.m = m # 模糊系数 self.max_iter = max_iter # 最大迭代次数 self.tol = tol # 停止阈值 def fit(self, X): n_samples = X.shape[0] # 初始化隶属度矩阵,每行和为 1 U = np.random.rand(n_samples, self.n_clusters) U = U / U.sum(axis=1, keepdims=True) for i in range(self.max_iter): U_old = U.copy() # 计算聚类中心 c_j Um = U ** self.m centers = (Um.T @ X) / Um.sum(axis=0)[:, None] # 计算距离矩阵 dist = np.linalg.norm(X[:, None] - centers[None, :], axis=2) dist = np.fmax(dist, 1e-10) # 防止除零 # 更新隶属度 power = 2 / (self.m - 1) ratio = (1 / dist) ** power U = ratio / ratio.sum(axis=1, keepdims=True) # 检查收敛 if np.linalg.norm(U - U_old) < self.tol: break self.centers = centers self.U = U self.labels = np.argmax(U, axis=1) return self

逻辑说明:U ** self.m是隶属度的 m 次方,用于加权计算中心。np.fmax(dist, 1e-10)是血泪经验——当某个样本恰好落在聚类中心上时距离为 0,倒数会变成 inf,整个矩阵就废了。power = 2/(m-1)是 FCM 的标准推导结果,m=2 时 power=2。停止条件用隶属度矩阵的范数变化,比用目标函数更直接。参数上,n_clusters需要根据业务定,max_iter=150对多数中小数据集够用,tol=1e-5是精度和速度的折中。

3.3 结果输出与可视化脚本

vision.py负责把Trainer产出的标签和隶属度画出来。从results目录的文件名看,作者输出了scatter.png(总体散点图)、loss.png(目标函数下降曲线)、以及DST_类别0.png、PTDTC_类别1.png这类按类别拆分的图。这说明数据里可能有两个不同的子集或两种标签体系,DST 和 PTDTC 是数据来源的缩写。

# vision.py 中散点图与损失曲线的典型画法 import matplotlib.pyplot as plt def plot_scatter(X, labels, centers, save_path='results/scatter.png'): plt.figure(figsize=(8, 6)) scatter = plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', alpha=0.6) plt.scatter(centers[:, 0], centers[:, 1], c='red', marker='X', s=200, label='Centers') plt.colorbar(scatter, label='Cluster') plt.legend() plt.title('FCM Clustering Result') plt.savefig(save_path, dpi=150) plt.close() def plot_loss(loss_history, save_path='results/loss.png'): plt.figure(figsize=(8, 5)) plt.plot(loss_history, marker='o', markersize=3) plt.xlabel('Iteration') plt.ylabel('Objective Function J') plt.title('FCM Convergence Curve') plt.grid(True, alpha=0.3) plt.savefig(save_path, dpi=150) plt.close()

alpha=0.6让重叠点可见,marker='X'标出聚类中心,dpi=150保证输出图够清晰。损失曲线是判断有没有收敛的后悔药——如果曲线还在明显下降就到了max_iter,说明迭代次数不够,需要调大。如果曲线震荡,可能是 m 太小或数据没标准化。

4. 避坑与排查:跑 FCM 最容易翻车的五个地方

4.1 现象:运行报ModuleNotFoundError: No module named 'openpyxl'

原因:pandas 读.xlsx需要 openpyxl 引擎,环境需求里可能没显式写。 解决:pip install openpyxl,如果还报错,检查 pandas 版本是否过旧,pip install --upgrade pandas。

4.2 现象:聚类结果每次运行都不一样

原因:FCM 初始化隶属度矩阵是随机的,没有设随机种子。 解决:在Trainer.py开头加np.random.seed(42),或者在fit里用固定初始化。生产环境建议跑多次取最优目标函数值。

4.3 现象:所有样本被分到同一个簇

原因:模糊系数 m 设得过大(比如 m=5),隶属度过于均匀,argmax后全指向同一类;或者数据没标准化,某个特征主导了距离。 解决:把 m 调回 1.5~2.5 区间,先做标准化再聚类。检查dataLoader.py是否漏了归一化步骤。

4.4 现象:损失曲线不下降或震荡

原因:学习率式的更新不存在于 FCM,震荡通常是因为距离矩阵出现极小值导致隶属度爆炸。 解决:确认代码里有np.fmax(dist, 1e-10)这类防除零保护。另外检查数据里有没有重复点或 NaN,dropna后再跑。

4.5 现象:results目录图片是空的或只有坐标轴

原因:vision.py在run.py里被调用时,数据可能还没传进去,或者plt.savefig之前调了plt.show()导致阻塞。 解决:确保run.py里先fit再plot,把plt.show()注释掉,只保留savefig。检查results目录是否存在,不存在先os.makedirs('results', exist_ok=True)。

5. 调参进阶:用隶属度做软判决,把聚类结果用出额外价值

跑通默认参数只是起点。FCM 真正比 K-Means 多出来的东西是隶属度矩阵 U,很多人跑完只取argmax拿硬标签,等于把 FCM 当 K-Means 用,浪费了模糊信息。我一般会做两件事:一是用隶属度熵来评估聚类的不确定性,二是把高隶属度的样本作为置信样本,低隶属度的挑出来人工复核或做后续规则判断。

# 利用隶属度矩阵做不确定性分析 import numpy as np def uncertainty_analysis(U): # 隶属度熵:熵越高,样本归属越模糊 entropy = -np.sum(U * np.log(U + 1e-10), axis=1) # 最大隶属度:低于阈值的样本需要关注 max_membership = np.max(U, axis=1) uncertain_mask = max_membership < 0.6 print(f"不确定样本数: {uncertain_mask.sum()} / {len(U)}") return entropy, uncertain_mask

np.log(U + 1e-10)防止 log(0)。max_membership < 0.6是我常用的阈值,低于它说明这个样本对任何簇的归属都不超过 60%,属于边界样本。你可以把这些样本单独导出,结合业务判断是噪声还是真实过渡态。

另一个技巧是拿 FCM 的聚类中心做新数据的软分配。训练好的centers可以保存下来,新样本进来时只更新隶属度不更新中心,相当于一个轻量级的模糊分类器。这在数据流式到达的场景里比重新训练整个模型省事得多。

参数选择上,n_clusters可以用肘部法辅助:跑 C=2 到 C=10,看目标函数 J 的下降拐点。但 FCM 的 J 本身随 C 增大单调下降,所以更常用的是看隶属度熵的均值或者轮廓系数。m的取值对结果影响很大,m=2 是默认起点,如果数据噪声特别多可以试 m=2.5 或 3,让隶属度更平滑;如果希望结果接近硬聚类,m=1.2 到 1.5 之间。

从那以后我每次拿到一份聚类源码,都强制先跑一遍默认参数,把results里的损失曲线和散点图看一遍,确认收敛和分布正常,再动数据预处理和参数。这份 FCM 源码结构干净,改起来不费劲,适合拿来当模糊聚类的实验底稿。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询