十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Matlab实现模糊C均值聚类(FCM)完整流程与数据归一化实践

Matlab实现模糊C均值聚类(FCM)完整流程与数据归一化实践 做数据分类的时候聚类往往是第一步。实际需求里经常碰到这种情况手里有一批样本每个样本带一堆特征既没有标签也不知道该分几类用传统K-means又总觉得边界太硬——样本可能同时兼具多个类别特征硬塞进某一类就丢信息了。FCM模糊C均值聚类就是解决这个问题的它给每个样本算出一个“属于每个类别的概率”而不是一刀切。最近我把这个流程完整跑了一遍从数据归一化到分类结果输出把代码和踩坑经验整理出来。这篇内容适合刚接触聚类、想在Matlab里用FCM做数据分类的初学者也适合被“聚类完结果完全不对”这类问题卡住的同学做参考。1. FCM聚类原理从硬划分到软划分1.1 为什么用FCM而不是K-meansK-means做的是硬聚类每个样本必须且只能属于一个簇。但现实数据里没那么多“绝对”——一个客户既可能像高价值群体又带有流失群体的行为特征一张图片的某个像素既接近前景又和背景有几分相似。FCM把这种不确定性用隶属度表达出来样本i对第j类的隶属度u_ij取值范围是0到1所有类别的隶属度之和等于1。从某种意义上说FCM是K-means的“平滑版本”它不去强行回答“这个样本是哪个类”而是回答“这个样本有多偏向哪个类”。实际分类任务里这种模糊输出非常有用。比如做客户分群时一个隶属度向量(0.6, 0.3, 0.1)意味着该客户以六成置信度属于A群同时保留了三成属于B群的可能业务上就能针对中间群体单独设计策略。FCM在图像分割、模式识别、地质统计、生物信息学里都经常出现凡是数据边界模糊、类别重叠明显的场景它都比硬聚类扛得住。我用Matlab做FCM还有一个现实原因Matlab自带fcm函数输入数据矩阵和聚类数就能直接跑工具箱里还有配套的可视化、隶属度输出、目标函数历史记录做原型验证非常快。相比自己从头造轮子内置函数至少能保证核心迭代逻辑没写错等流程跑通后再根据业务需求改造或手写细节版本。1.2 FCM核心数学原理与迭代公式FCM的目标函数是带模糊权重的最小化问题J ∑(i1到N) ∑(j1到C) u_ij^m * d_ij^2其中d_ij是第i个样本到第j个聚类中心的欧氏距离m是模糊指数u_ij是隶属度。整个优化的意思是让每个样本到它所属类别的加权距离总和最小同时让隶属度的模糊程度受m控制。迭代过程分两步交替进行。第一步固定聚类中心更新隶属度第二步固定隶属度更新聚类中心。公式看起来复杂拆开看其实很直观一个样本离哪个中心越近它对那个类的隶属度就越高一个类的中心本质上是所有样本按照隶属度加权平均的位置。迭代停止条件是相邻两次迭代的目标函数变化量小于容差或者达到最大迭代次数。Matlab的fcm函数默认是100次迭代加1e-5的容差大多数场景下够用但数据量大时我会手动调大最大迭代次数防止还没收敛就被截断。1.3 聚类数C、模糊指数m与停止条件的选择聚类数C是FCM里最难拍板的参数。没有先验知识时我一般用两步走先跑一遍不同C值的FCM画出目标函数或轮廓系数随C变化的曲线找“拐点”再结合业务判断比如客户分群时3到5类是不是更符合运营策略。纯算法选C容易陷入“数越多误差越小”的陷阱最后分出的类别无法解释落地价值反而低。模糊指数m的经验值是2。m太接近1算法退化成近似K-means模糊性消失m过大比如超过3所有隶属度会趋向均匀聚类结果失去区分度。如果发现结果“糊成一团”先检查是不是m设大了。停止条件方面除了迭代次数和容差还可以观察目标函数hist曲线。fcm函数会返回目标函数历史如果曲线在最后几步还在明显下降说明收敛不够要调大最大迭代次数如果前几步就压平了说明收敛很快初始中心选得不错。2. 归一化为什么聚类之前必须先处理数据2.1 量纲差距如何悄悄毁掉距离计算FCM和其他基于距离的聚类算法一样本质是靠“距离远近”判断相似性。假如数据有两个特征一个是“消费金额”范围从100到10000另一个是“点击次数”范围从0到5。计算欧氏距离时消费金额因为数值范围大天然占据了距离的绝大部分权重点击次数几乎可以忽略。聚类结果就变成了只按金额这一个维度划分其他特征全部失效。这个问题的专业说法叫“量纲影响”。只要各特征不在同一尺度上距离计算就会失真聚类结果就不是数据真实分布的反映而是数值尺度的反映。我做归一化基本是聚类前雷打不动的步骤除非所有特征已经是相同量纲且相近取值范围的比率型数据。用生活化类比的话你想比较两个人“谁更接近心目中的理想对象”会综合考虑身高、收入、性格、爱好但如果“收入”这一项用几十万来衡量“爱好契合度”用0到1衡量最后比较结果几乎完全由收入决定其他条件全被淹没。归一化就是先把所有评判标准拉到同一把尺子上再谈谁更接近谁。2.2 min-max、Z-score与mad归一化对比最常用的三种归一化手段是min-max归一化、Z-score标准化和mad归一化。很多教程只讲前两种mad归一化在实际工作中处理异常值非常实用值得单独拿出来说。min-max归一化公式是x_norm (x - min(x)) / (max(x) - min(x))结果映射到[0,1]区间。优点是简单直观缺点是对异常值极其敏感——一组数据里如果混进一个极端大值其他所有值都会被压到很窄的区间分辨度骤降。Z-score标准化公式是x_norm (x - mean(x)) / std(x)结果均值为0、标准差为1。它适合数据分布近似正态的场景并且能保留离群值信息。缺点是如果原始分布严重偏斜标准化后依然偏斜且均值和标准差本身也受异常值影响。mad归一化公式是x_norm (x - median(x)) / (1.4826 * mad(x))其中mad是绝对中位差即median(|x - median(x)|)。系数1.4826是为了让mad在正态分布下与标准差保持一致。由于中位数和mad对极端值不敏感这种归一化在含噪声、含离群点数据上比Z-score稳定得多。如果你的数据是从传感器或业务系统里直接捞出来的大概率有脏数据mad归一化值得优先试。方法公式核心对异常值敏感度适用场景min-max 归一化(x - min)/(max - min)高数据范围已知、无显著离群点Z-score 标准化(x - mean)/std中等数据近似正态、需要保留离群信息mad 归一化(x - median)/(1.4826*mad)低含噪声或离群点的真实业务数据2.3 归一化对聚类结果的实际影响归一化不是可有可无的预处理它直接改变FCM的目标函数曲率。FCM在迭代中反复计算样本与中心的欧氏距离如果特征量纲不一致目标函数里大尺度特征对应项的数值级远大于小尺度特征算法在更新中心和隶属度时会优先“讨好”大尺度特征最终聚类中心在低尺度维度上几乎没有区分度。我做过一个实测对比同一份数据不做归一化时轮廓系数只有0.34聚类散点图里两个簇挤成一团做完min-max归一化后轮廓系数升到0.62簇与簇之间肉眼可见地分开了。这个现象在你用欧氏距离的聚类算法时普遍存在不是FCM特有的但FCM因为隶属度对距离敏感更容易被尺度差异放大效果。当然也有例外。如果所有特征都是同一物理量且单位相同比如电压的三个通道、温度的不同点位归一化可能不必要甚至有害——它会抹掉特征之间的绝对差异。另外如果后续业务需要把聚类结果映射回原始尺度做解释我建议保留一份归一化前的数据分析时再映射回去。3. Matlab实现FCM的完整流程3.1 数据准备从原始表到干净矩阵Matlab里实现FCM的第一步是把数据整理成“样本×特征”的数值矩阵。用readtable函数读入Excel或CSV文件后先处理缺失值和字符串列。缺失值我一般先统计比例如果某列缺失超过三成优先考虑直接删列缺失较少的用fillmissing填补默认方法选线性插值还是常数填充取决于特征性质——连续测量值用线性插值更合理离散统计值用常数填充更稳妥。% 读取数据文件 data readtable(data.csv); % 查看缺失值情况 missingCount sum(ismissing(data)); % 填补缺失值 data fillmissing(data, linear); % 取出数值特征列排除ID列和标签列 featNames {feature1, feature2, feature3}; X data{:, featNames};处理异常值要小心。除了明显的录入错误我并不建议一上来就把“看起来偏大”的值删掉——异常值可能是有价值的信号。更稳妥的做法是先画箱形图确认再用mad归一化或鲁棒标准化让算法自己降低异常值权重而不是直接从源数据删除。3.2 用内置fcm函数快速实现聚类Matlab的fcm函数用法很简单% 设置选项模糊指数m2最大迭代次数100容差1e-5 options [2, 100, 1e-5, 1]; % 执行FCM聚类 [center, U, objHist] fcm(X, 3, options); % 得到硬分类标签取隶属度最大的类 [~, label] max(U, [], 1); label label;center是聚类中心矩阵每一行对应一个类的中心U是隶属度矩阵行是样本、列是类别U(i,j)表示第i个样本对第j类的隶属度objHist是每次迭代的目标函数值。options数组的第4个参数控制是否显示迭代信息调试时设1正式跑可以改0。用内置函数最大的坑是默认初始聚类中心是随机生成的同样的数据和参数每次跑出来的结果可能不同。所以我在对比实验里严格固定随机种子确保结果可复现。设置rng(42)之后再调用fcm就能保证同一份代码多次运行结果一致。3.3 手写FCM核心迭代代码虽然内置函数方便但我建议至少手写一遍核心迭代逻辑才能理解聚类中心是如何移动的、隶属度为什么最后会稳定下来。我整理了一个简化版的FCM函数便于拆解function [center, U, objHist] myFCM(X, C, m, maxIter, tol) % 初始化 [N, D] size(X); rng(42); center X(randperm(N, C), :); % 随机选C个样本作初始中心 U zeros(N, C); objHist zeros(maxIter, 1); for iter 1:maxIter % 计算样本到中心的距离 distMat zeros(N, C); for j 1:C diffMat X - center(j, :); distMat(:, j) sqrt(sum(diffMat.^2, 2)); end % 更新隶属度 distMat(distMat 1e-10) 1e-10; % 防止除零 for i 1:N for j 1:C U(i, j) 1 / sum((distMat(i, j) ./ distMat(i, :)).^(2/(m-1))); end end % 更新聚类中心 Um U.^m; center (Um * X) ./ sum(Um, 1); % 记录目标函数 objHist(iter) sum(sum(Um .* distMat.^2)); % 检查收敛 if iter 1 abs(objHist(iter) - objHist(iter-1)) tol objHist objHist(1:iter); break; end end end这段代码没有用矩阵化技巧循环较多教学意义大于性能意义。实际处理上万样本时我建议用内置fcm先跑基准结果再针对业务需求改写。特别提醒一点距离计算里一旦出现某个样本正好在聚类中心上会导致分母为0所以加一个极小值保护是必要的。这个细节容易漏漏了就会在迭代过程中出现NaN排查起来很头疼。3.4 结果可视化与分类标签生成拿到隶属度矩阵U后最常见的操作是取最大值对应的类别作为硬标签。但深入一步U包含了更多信息如果某样本对某类的隶属度接近1说明它非常典型如果几类隶属度都徘徊在0.3到0.4之间说明它处于类别交界区分类置信度低。% 画散点图按聚类标签着色 figure; gscatter(X(:, 1), X(:, 2), label); hold on; plot(center(:, 1), center(:, 2), kx, MarkerSize, 12, LineWidth, 3); hold off; legend(类别1, 类别2, 类别3, 聚类中心); % 画目标函数下降曲线 figure; plot(1:length(objHist), objHist, o-); xlabel(迭代次数); ylabel(目标函数值); title(FCM目标函数下降曲线);目标函数下降曲线能快速判断收敛情况。正常的FCM应该在前面几次迭代大幅下降然后逐渐平缓。如果曲线上下波动先怀疑是不是数据有NaN或者距离计算有异常如果曲线到最后还在陡降说明要么迭代次数不够要么聚类数选得不合适。从模糊隶属度到分类标签的转换放在完整分类任务里还要做一次“标签对齐”这个细节在下一章的案例里展开。4. 数据分类实践完整案例拆解4.1 案例场景与数据说明为了把流程走通我选用最经典的Iris鸢尾花数据集做演示。它有150个样本、4个特征分为3个品种每类50个样本。这个数据集的特点是特征量纲相近、类别分布均衡天然适合验证聚类流程。Matlab里用load fisheriris可以直接加载。load fisheriris; X meas; % 150x4的特征矩阵 trueLabel species; % 真实的品种标签但Iris有个问题因为特征量纲接近归一化前后的聚类结果差异不明显。为了演示归一化的真实威力我会额外造一组二维合成数据让特征1的范围是0到10000特征2的范围是0到1并且真正的类别划分只有同时看两个特征才能分出来。这样就能直观展示“不归一化时聚类直接跑偏”的现象。4.2 归一化前后对比什么时候差异巨大先看合成数据。用两个高斯簇生成数据簇A的特征1均值8000、特征2均值0.3簇B的特征1均值2000、特征2均值0.7。显然两个簇的划分必须同时依据特征1和特征2单看任何一个特征都有重叠。不归一化直接跑FCM结果几乎是灾难性的因为特征1数值范围太大距离计算里特征2的贡献微乎其微聚类中心几乎只在特征1方向上有区分两个簇在特征2维度上的均值几乎相同等于把原始的双维结构硬压成了一维。而从散点图看聚类结果会把同一簇内特征2较高的样本误分到另一边。做完min-max归一化再跑一次情况就正常了聚类中心和真实簇中心基本重合分类准确率从65%直接跳到95%以上。这个对比说明归一化在特征量纲差异大的场景里是决定聚类成败的一步而不是锦上添花。Iris数据集上归一化前后差别没那么大因为四个特征范围都在0.1到7.5之间尺度基本一致。但我会依然照做原因很简单让流程在所有数据上都稳定可靠而不是只在“恰好适合”的数据上能跑通。一旦以后换成量纲差异大的真实业务数据习惯性的归一化能省下大量排查时间。4.3 从聚类结果到实际分类标签的转换聚类输出的标签是0、1、2之类的编号它和真实类别之间没有必然对应关系。聚类编号为“1”的那一类可能对应真实标签的“Setosa”也可能对应“Virginica”。所以评估分类效果前必须做类标匹配。常见的做法是用混淆矩阵。Matlab里用confusionmat就能输出真实标签和聚类标签的交叉表然后按“每类里数量最多的真实标签”来映射聚类编号% 假设label是聚类得到的硬标签取值1、2、3 % trueLabelNum是真实标签的数值形式 confMat confusionmat(trueLabelNum, label); % 显示混淆矩阵确定每个聚类编号对应的真实类别 disp(confMat);从混淆矩阵能看出聚类编号和真实类别的对应关系如果第1行第2列数值最大说明聚类编号2对应真实类别1。按照这个映射关系把聚类标签重命名后再计算分类准确率。这里有个经验如果混淆矩阵不是近似对角线不要急着断定聚类效果差先看看是不是标签映射错了。我第一次跑FCM时准确率显示只有三成排查到最后发现只是标签顺序没对齐对齐后准确率其实有九成以上。这个问题在聚类评估里特别容易碰到属于“假失败”的典型案例。4.4 聚类效果评估不只靠准确率分类准确率是直观的指标但FCM是聚类算法评估时还要看聚类质量本身。我常用两个指标模糊划分系数Partition Coefficient是经典指标公式为PC (1/N) * ∑(i1到N) ∑(j1到C) u_ij^2它的取值范围在1/C到1之间值越接近1说明隶属度越“锐利”即每个样本都明确属于某个类值越接近1/C说明隶属度越“模糊”类间重叠严重聚类区分度差。轮廓系数Silhouette Coefficient不依赖隶属度而是基于样本与同类其他样本的距离、以及样本与最近异类样本的距离来计算范围从-1到1值越大说明聚类结构越清晰。Matlab里用silhouette函数可以快速画出每个样本的轮廓图一眼就能看出哪些样本被分得不妥当。综合来看准确率反映的是“聚类结果和真实标签的一致程度”PC反映的是“隶属度矩阵的清晰程度”轮廓系数反映的是“簇间分离程度”。三者各管一面我一般在正式报告里三个都放由读者根据业务目标自己取舍。5. 常见问题与排查经验5.1 聚类结果每次都不一样这是FCM最常被问的问题根源是初始聚类中心随机化导致算法可能收敛到不同的局部最优解。解决办法有三个层次第一固定随机种子保证实验可复现。调代码、调参数阶段务必加上rng(一个固定值)否则前后两次实验的差异会让你完全没法判断改动是好是坏。第二多次运行取最优。把fcm跑20次每次记录目标函数最终值取目标函数最小的那一次作为最终结果。因为FCM的最小化问题是非凸的多次运行相当于从不同起点爬山选最低谷更合理。第三用确定性方法初始化聚类中心。比如先用K-means或简单随机抽样选出较分散的初始中心再喂给FCM。这一步能显著减少“跑偏”概率但代码量会多一些。工程上如果任务对稳定性要求非常高我会在多次运行的基础上再做一次选择稳定性和性能都能兼顾。5.2 归一化到底该选哪种我的选择原则比较务实先画数据分布图观察有没有明显离群点。数据干净、范围可控优先min-max数据近似正态分布选Z-score数据有明显离群点或者来源是真实生产系统选mad归一化。有人担心mad归一化在m系数较小、数据稀疏时表现不稳实际中如果某列特征有一半以上的值相同比如大量0值median的鲁棒性反而可能让归一化结果更合理。还有一种情况特征本身是稀疏计数比如文本TF-IDF矩阵这时候标准Z-score会放大稀疏值更推荐用L2范数归一化或者min-max。归一化之后要注意训练聚类模型时用的归一化参数min、max、mean、std、median、mad要存下来后续新的样本做预测时用同一套参数变换而不是重新计算。如果新样本直接套趋势、重新统计分布变化会导致新旧数据不在同一坐标空间里。这个细节在把聚类结果投入线上分类流程时尤其重要。5.3 不平衡数据分类怎么做FCM的迭代过程和K-means一样天然对样本数多的类别“有偏向”——大类在更新中心时会拉拽更多样本小类的中心容易被带偏。处理不平衡数据分类时我一般先看类别比例差距有多大。如果是轻微不平衡比如6比4直接跑FCM问题不大如果是9比1这种明显不平衡就需要干预。一种思路是在目标函数里给不同类别加权重让小类样本在更新中心时获得更大影响力。另一种思路是在数据层面做处理比如对小类做SMOTE过采样或者对大类做欠采样然后再跑FCM。具体选哪条路取决于业务约束如果样本总量不大而且数据采集成本高我更倾向权重修正而不是丢弃样本。不平衡问题在算法层面没有万能解但有一个经验先跑一次不加干预的FCM画出各类样本数和隶属度均值如果发现小类样本的隶属度普遍偏低且边界模糊说明确实被大类压制了再决定要做什么处理不要一上来就上复杂方案。5.4 实操避坑清单把散落在各处的经验整理成一份速查清单照着检查能避免大部分返工。数据检查导入后先看数据维度、缺失值、NaN和Inf。Matlab里NaN参与计算会静默传播聚类结果莫名其妙时先查这里。特征选择只保留对分类有意义的特征无关特征越多距离计算越“钝”。噪音特征会稀释真实信号。归一化除非所有特征同量纲同单位否则先归一化再做聚类。归一化参数要存下来供后续预测使用。参数设置m默认2一般没问题C值没有先验时用肘部法或轮廓系数辅助判断迭代次数不要设太小否则可能未收敛。初始中心固定随机种子保证可复现多次运行取目标函数最小值。标签对齐聚类编号和真实标签不一一对应先看混淆矩阵再做准确率评估。可视化二维数据直接用原始特征画散点图高维数据先PCA降维再画否则看不到簇结构。结果归档保存归一化参数、聚类中心、隶属度矩阵和最终标签方便后续复现和业务分析。我在实际使用中还有一个习惯归一化之前永远保留一份原始数据副本。原因很简单聚类做完之后业务方十有八九会问“这个类的原始特征平均值是多少”如果只留归一化后的数据每次都得反算回去麻烦且容易出错。备好一份原始数据任何后续分析都有退路。FCM聚类只是数据分类流程里的一个环节真正落地时还要结合业务解释、特征分析、分类器训练一起做。但把聚类这步做扎实后面的道路会顺畅很多。最后再分享一个小技巧如果最终业务目标是训练一个分类器可以直接把FCM的隶属度向量作为额外特征拼接到原始特征后面。这样分类器就多了一层“模糊类别概率”的信息在很多不平衡数据场景下这个操作比单纯用硬标签做特征效果更好。
返回列表