ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

模糊C均值聚类与可视化:Python源码实现及参数调优指南

模糊C均值聚类与可视化:Python源码实现及参数调优指南 简介这是一份面向数据挖掘与机器学习入门者的模糊C均值聚类实战源码包适合正在学习聚类算法、需要完成课程设计或想对比FCM与K-Means差异的学生与开发者。资源围绕FCM算法展开涵盖隶属度迭代、类别中心更新、模糊系数调节等核心环节并配有可视化脚本直观呈现聚类效果。压缩包共29个文件约638KB包含4个Python源码文件、2个Jupyter Notebook、8张结果图片、3个文本说明及csv、xlsx数据文件等源码、数据、运行结果与配置说明层次分明便于按模块阅读与复现。目前已有167人学习下载。通过运行与调试读者可掌握数据预处理、FCM自定义实现、参数调优及Matplotlib可视化全流程理解模糊边界样本的归属权重计算并借助聚类结果图与损失曲线评估算法表现为后续机器学习项目积累可复用的代码经验。1. 模糊C均值做聚类并做可视化一份源码能省掉多少试错手上有一批没有标签的数据想分组第一反应通常是 K-Means。但真跑起来就会发现K-Means 是硬划分——每个点非此即彼边界上的样本被强行塞进某一类结果就是类别边界抖动、迭代几次归属就跳来跳去。模糊C均值FCM换了个思路它允许一个样本同时以不同隶属度属于多个簇用隶属度矩阵代替硬标签对边界模糊、簇之间有重叠的数据更稳。这份「模糊C均值做聚类并做可视化源码」要解决的就是把 FCM 从公式落到能跑的代码再把高维聚类结果压到二维画出来让你一眼看出分得对不对。适合正在做数据分析、图像分割、客户分群手里有 Python 环境、想直接拿一份能改的源码上手的人。聚类算法、可视化、源码这三个词后面会一路贯穿。2. 模糊C均值到底比K-Means多算了什么2.1 隶属度矩阵FCM的核心数据结构K-Means 的输出是一个长度为样本数的标签数组每个元素是 0、1、2 这样的整数。FCM 的输出是一个形状为 (簇数 c, 样本数 n) 的隶属度矩阵 UU[i][j] 表示第 j 个样本属于第 i 个簇的程度取值在 0 到 1 之间且每一列对每个样本而言所有簇的隶属度之和为 1。这个约束叫概率归一化是 FCM 迭代能收敛的前提。理解 U 之后聚类结果就有两种读法一是取每列最大值所在的簇作为硬标签用于和 K-Means 对比二是保留隶属度本身用于分析「这个样本有多犹豫」。后者在客户分群、医学影像分割里特别有用——一个样本隶属度是 0.55 和 0.45说明它处在两个簇的过渡带业务上值得单独看。FCM 的目标函数是J Σᵢ Σⱼ (uᵢⱼ)^m · ||xⱼ - cᵢ||²其中 m 是模糊指数控制隶属度的模糊程度cᵢ 是第 i 个簇中心。迭代过程就是交替更新 U 和 cᵢ直到 U 的变化量小于阈值。2.2 模糊指数 m 和簇数 c两个必须自己定的参数m 是 FCM 最容易被忽略的参数。理论上 m 要大于 1常见取值 1.5 到 2.5默认 2.0。m 越接近 1隶属度越接近硬划分退化成 K-Meansm 越大隶属度越平均所有样本对每个簇的隶属度都趋近 1/c聚类就失去区分度。我一般先用 2.0 跑如果发现隶属度矩阵里最大值普遍低于 0.6说明 m 偏大往下调到 1.6 到 1.8 再试。簇数 c 和 K-Means 一样需要预设。源码里通常会给一个范围让用户试配合聚类有效性指标选。常用的有划分系数 PC、划分熵 PE、Xie-Beni 指数。PC 越大越好PE 越小越好Xie-Beni 越小越好。不要只看一个指标三个一起看选拐点。2.3 迭代终止条件别让程序空转FCM 是迭代算法必须有终止条件。源码里一般设两个最大迭代次数 max_iter常见 100 到 300和隶属度变化阈值 epsilon常见 1e-5 到 1e-4。每次迭代计算 U 的新旧差值如果最大绝对差小于 epsilon 就停。只设 max_iter 不设 epsilon程序可能跑满次数但结果早就稳定了浪费时间只设 epsilon 不设 max_iter遇到震荡不收敛的情况会死循环。两个都要有。3. 用Python把FCM跑起来从隶属度矩阵到硬标签3.1 最小可运行实现不依赖sklearn的FCM下面这段代码是 FCM 的核心只用 numpy方便你直接看懂每一步在算什么。把它存成 fcm.py。import numpy as np def fcm(X, c, m2.0, max_iter150, epsilon1e-5, seed42): X: 样本矩阵, shape(n_samples, n_features) c: 簇数 m: 模糊指数, 必须 1 max_iter: 最大迭代次数 epsilon: 隶属度变化阈值 返回: 隶属度矩阵 U (c, n), 簇中心 centers (c, n_features) rng np.random.default_rng(seed) n X.shape[0] # 初始化隶属度矩阵, 每列和为1 U rng.random((c, n)) U U / U.sum(axis0, keepdimsTrue) for it in range(max_iter): U_old U.copy() # 计算簇中心: 隶属度的m次方加权平均 Um U ** m centers (Um X) / Um.sum(axis1, keepdimsTrue) # 计算样本到各簇中心的距离平方 dist np.zeros((c, n)) for i in range(c): diff X - centers[i] dist[i] np.sum(diff ** 2, axis1) dist np.fmax(dist, 1e-10) # 防止除零 # 更新隶属度 power 2.0 / (m - 1) ratio (1.0 / dist) ** power U ratio / ratio.sum(axis0, keepdimsTrue) # 检查收敛 if np.max(np.abs(U - U_old)) epsilon: print(f在第 {it1} 次迭代收敛) break return U, centers def get_hard_labels(U): 从隶属度矩阵取硬标签 return np.argmax(U, axis0)逻辑说明初始化用随机隶属度并做列归一化保证每列和为 1。簇中心更新用的是隶属度的 m 次方作为权重这是 FCM 和 K-Means 在公式上的关键差别——K-Means 是硬计数平均FCM 是加权平均。距离计算后加了一个 1e-10 的下限防止某个样本恰好落在簇中心上导致除零。隶属度更新公式里的指数 2/(m-1) 来自拉格朗日乘子推导不要改。参数说明m 默认 2.0先不动max_iter 设 150 够用epsilon 设 1e-5如果数据量大可以放宽到 1e-4 加速收敛seed 固定保证可复现。3.2 生成测试数据并跑通聚类用 sklearn 的 make_blobs 造三簇数据验证上面的实现。from sklearn.datasets import make_blobs import matplotlib.pyplot as plt # 造数据: 300个样本, 3个簇, 2维方便画图 X, y_true make_blobs(n_samples300, centers3, cluster_std1.2, random_state7) U, centers fcm(X, c3, m2.0) labels get_hard_labels(U) print(簇中心:\n, centers) print(前5个样本的隶属度:\n, U[:, :5].round(3))跑完你会看到 centers 是三行两列的数组对应三个簇中心坐标。U[:, :5] 打印前 5 个样本对三个簇的隶属度正常情况下每个样本会有一个明显偏大的值。如果三个值都在 0.33 附近说明 m 太大或者 c 设多了。3.3 可视化把聚类结果和隶属度一起画出来可视化分两张图一张画硬标签的散点一张画隶属度的热力图。散点图看分得对不对热力图看边界样本的犹豫程度。fig, axes plt.subplots(1, 2, figsize(12, 5)) # 左图: 硬标签散点 簇中心 axes[0].scatter(X[:, 0], X[:, 1], clabels, cmapviridis, s30, alpha0.7) axes[0].scatter(centers[:, 0], centers[:, 1], cred, markerX, s200, edgecolorsblack) axes[0].set_title(FCM 硬标签聚类结果) # 右图: 隶属度热力图, 取每个样本的最大隶属度 max_membership U.max(axis0) sc axes[1].scatter(X[:, 0], X[:, 1], cmax_membership, cmapRdYlGn, s30) plt.colorbar(sc, axaxes[1], label最大隶属度) axes[1].set_title(样本隶属度置信度) plt.tight_layout() plt.savefig(fcm_result.png, dpi150) plt.show()逻辑说明左图用 argmax 得到的硬标签上色红色 X 标出簇中心。右图用每个样本的最大隶属度上色颜色越绿表示越确定越红表示越犹豫。这两张图放一起能同时回答「分了几类」和「分得有多确定」两个问题。参数说明cmap 选 viridis 和 RdYlGn 是为了色盲友好s 控制点大小样本多的时候调小到 10 到 15dpi 设 150 保证保存的图够清晰。4. 参数怎么调m、c 和初始化对结果的影响4.1 模糊指数 m 的扫描实验m 不是拍脑袋定的。写个循环让 m 从 1.2 扫到 3.0看隶属度矩阵的最大值分布。for m in [1.2, 1.5, 1.8, 2.0, 2.5, 3.0]: U, _ fcm(X, c3, mm, max_iter100) max_mem U.max(axis0) print(fm{m:.1f} 平均最大隶属度{max_mem.mean():.3f} f低于0.6的样本占比{(max_mem 0.6).mean():.2%})预期结果m1.2 时平均最大隶属度接近 0.95几乎硬划分m2.0 时在 0.85 左右m3.0 时掉到 0.6 以下大量样本三个簇的隶属度都差不多。选 m 的原则是让平均最大隶属度落在 0.7 到 0.9 之间低于 0.6 的样本占比不超过 15%。这个区间是我在客户分群项目里反复试出来的经验值不同数据会有偏移但量级不会差太多。4.2 簇数 c 的选择三个有效性指标一起看c 的选择没有唯一答案靠指标辅助判断。下面实现 PC、PE 和 Xie-Beni 三个指标。def cluster_validity(X, U, centers, m2.0): n X.shape[0] c U.shape[0] # 划分系数 PC: 越大越好 pc np.sum(U ** 2) / n # 划分熵 PE: 越小越好 pe -np.sum(U * np.log(U 1e-10)) / n # Xie-Beni: 越小越好 num np.sum((U ** m) * np.linalg.norm(X[:, None, :] - centers[None, :, :], axis2) ** 2) min_center_dist np.inf for i in range(c): for j in range(i 1, c): d np.linalg.norm(centers[i] - centers[j]) min_center_dist min(min_center_dist, d) xb num / (n * min_center_dist ** 2) return pc, pe, xb for c in range(2, 7): U, centers fcm(X, cc, m2.0) pc, pe, xb cluster_validity(X, U, centers) print(fc{c} PC{pc:.4f} PE{pe:.4f} XB{xb:.4f})逻辑说明PC 衡量隶属度的集中程度越接近 1 说明划分越清晰PE 是 PC 的熵版本越小越好Xie-Beni 同时考虑簇内紧凑度和簇间分离度是三个里最综合的。三个指标不一定同时指向同一个 c这时候优先看 Xie-Beni再结合业务可解释性定。参数说明计算 Xie-Beni 时用了广播X[:, None, :] - centers[None, :, :] 得到 (n, c, 2) 的距离张量数据量大时内存吃紧可以改成分块计算。4.3 初始化敏感性FCM也会陷局部最优FCM 和 K-Means 一样对初始化敏感。随机初始化隶属度不同 seed 可能收敛到不同的局部最优。解决办法是跑多次取目标函数最小的那次。def fcm_best_of(X, c, m2.0, n_init10): best_U, best_centers, best_J None, None, np.inf for seed in range(n_init): U, centers fcm(X, cc, mm, seedseed) # 计算目标函数 J dist np.linalg.norm(X[:, None, :] - centers[None, :, :], axis2) ** 2 J np.sum((U ** m) * dist.T) if J best_J: best_J, best_U, best_centers J, U, centers return best_U, best_centers U, centers fcm_best_of(X, c3, n_init10)逻辑说明n_init10 表示跑 10 次不同初始化取目标函数 J 最小的。J 越小说明簇内越紧凑。这个策略在数据量不大时开销可接受n300 时 10 次初始化总耗时不到 1 秒。数据量上万时把 n_init 降到 3 到 5。参数说明n_init 不是越大越好超过 10 之后提升有限如果数据本身簇结构清晰n_init3 就够。5. 避坑与排查FCM落地时最容易翻车的五个地方5.1 隶属度矩阵出现NaN现象跑完发现 U 里有 NaN或者簇中心变成 nan。原因某个样本到所有簇中心的距离都是 0或者距离极小导致 1/dist 溢出。常见于数据里有重复点或者某个簇中心恰好落在样本上。解决距离计算后加下限代码里已经写了 dist np.fmax(dist, 1e-10)。如果还出现检查数据是否做了标准化——不同量纲的特征会让距离计算失衡先做 StandardScaler 或 MinMaxScaler。5.2 迭代不收敛跑满max_iter现象程序每次都跑满 150 次迭代没有打印「收敛」。原因epsilon 设太小或者 m 设得接近 1 导致隶属度在 0 和 1 之间震荡。解决先把 epsilon 放宽到 1e-4 看是否收敛如果还不收敛把 m 调到 1.5 以上。另外检查数据里有没有极端离群点离群点会让簇中心来回拉。5.3 所有样本的隶属度都差不多现象U 的每一列三个值都在 0.33 附近硬标签基本随机。原因m 太大或者 c 设得远大于真实簇数。解决先把 m 降到 1.5 试如果还不行用第 4.2 节的指标扫一遍 c大概率是 c 设多了。真实簇数是 3你设 c6多出来的簇没有样本支撑隶属度自然平均。5.4 可视化散点图看不出分界现象画出来的散点图颜色混在一起看不出明显边界。原因数据维度高于 2 维直接取前两维画图丢失了信息或者簇本身在原始空间就不分离。解决高维数据先用 PCA 或 t-SNE 降到 2 维再画。注意降维后的图只用于展示聚类本身要在原始空间做。如果降维后还是混在一起说明数据本身没有明显簇结构FCM 不适合考虑 DBSCAN 这类基于密度的算法。5.5 硬标签和隶属度对不上现象某个样本硬标签是簇 0但簇 0 的隶属度只有 0.4。原因这是正常现象不是 bug。硬标签取的是最大值0.4 是三个簇里最大的说明这个样本确实犹豫。解决不要强行改。把这类样本单独筛出来看往往是业务上的边界客户或过渡态样本有分析价值。如果业务上要求每个样本必须明确归属可以设一个阈值最大隶属度低于 0.5 的标记为「待定」。6. 把FCM接到真实数据流标准化、降维和批量预测真实项目里 FCM 不会只跑一次就完事。数据每天更新簇中心需要增量更新或者定期重跑。我一般把流程拆成三步离线训练存簇中心在线用簇中心算隶属度可视化单独走。先看标准化和降维怎么接。FCM 基于欧氏距离特征量纲不一致会直接带偏结果。下面这段把标准化、PCA 降维和 FCM 串起来。from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA # 假设 X_raw 是原始数据 scaler StandardScaler() X_scaled scaler.fit_transform(X_raw) # 先聚类再降维用于可视化 U, centers fcm_best_of(X_scaled, c4, m1.8, n_init5) labels get_hard_labels(U) # PCA 降到2维仅用于画图 pca PCA(n_components2) X_2d pca.fit_transform(X_scaled) plt.scatter(X_2d[:, 0], X_2d[:, 1], clabels, cmaptab10, s20, alpha0.7) plt.title(PCA降维后的FCM聚类结果) plt.savefig(fcm_pca.png, dpi150)逻辑说明StandardScaler 把每个特征拉到均值 0、方差 1消除量纲影响。PCA 只在画图时用聚类在标准化后的原始空间做这样簇中心才有业务含义。如果先 PCA 再聚类簇中心是主成分空间的坐标解释起来麻烦。参数说明PCA 的 n_components2 只为可视化不要用它做聚类输入如果原始维度低于 10PCA 降维的收益不大可以直接选两个业务上最重要的特征画图。再看批量预测。训练完存下 centers 和 scaler新数据来了直接算隶属度不用重新迭代。import joblib # 训练完保存 joblib.dump({centers: centers, scaler: scaler, m: 1.8}, fcm_model.pkl) # 新数据预测 model joblib.load(fcm_model.pkl) X_new_scaled model[scaler].transform(X_new) centers model[centers] m model[m] # 直接算隶属度, 不迭代 dist np.linalg.norm(X_new_scaled[:, None, :] - centers[None, :, :], axis2) ** 2 dist np.fmax(dist, 1e-10) ratio (1.0 / dist) ** (2.0 / (m - 1)) U_new ratio / ratio.sum(axis1, keepdimsTrue) labels_new np.argmax(U_new, axis1)逻辑说明预测阶段不需要迭代因为簇中心已经固定直接套隶属度公式算一次就行。这样单条预测耗时在毫秒级可以接实时接口。注意保存模型时把 m 一起存预测时的 m 必须和训练时一致否则隶属度尺度对不上。参数说明joblib 适合存 numpy 数组比 pickle 快如果模型要跨语言用把 centers 和 scaler 的参数导出成 JSON。最后说一个我踩过的坑数据分布随时间漂移。上个月训练的簇中心这个月可能已经偏了。我的习惯是每周用新数据重跑一次 fcm_best_of对比新旧簇中心的偏移量偏移超过阈值就触发告警。这个阈值因业务而异我一般设簇间平均距离的 20%。FCM 的隶属度矩阵在这里帮了大忙——漂移往往先体现在边界样本的隶属度下降上比硬标签变化更早暴露问题。希望这套从公式到可视化再到上线的路子能帮你少走几个弯路。本文还有配套的精品资源点击获取
返回列表