ARTICLE DETAIL

资讯详情

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

Matlab聚类算法全解析:Kmeans、FCM、层次、GMM与SOM对比与选型

Matlab聚类算法全解析:Kmeans、FCM、层次、GMM与SOM对比与选型 1. 项目概述做数据分析这块的朋友应该都有同感拿到一坨无标签数据脑子里第一个冒出来的念头就是先分个类看看。聚类这事看着简单真正上手之后才发现坑不少——数据分布不一样类别形状不一样噪声比例不一样选错方法得出的结果简直是天壤之别。这个项目用 Matlab 把五种主流聚类方法全部拉通实现了一遍模糊 C 均值FCM、Kmeans、层次聚类、高斯混合模型GMM聚类、神经网络聚类自组织映射 SOM/竞争层。每种算法都有自己的适用边界和脾气我这篇就用实际跑过的代码和踩过的坑把这五种方法的原理、代码实现、参数调优、结果评估一次性聊透。先给个速览结论方便你判断自己该用哪种数据是球形簇、类别数明确、样本量大——Kmeans 是性价比之王跑得快结果稳。数据有重叠、一个样本可能属于多个簇、或者簇边界模糊——用模糊 C 均值它能给出隶属度这种软划分。不知道类别数、想看数据天然的层级关系比如从粗到细的嵌套结构——层次聚类配树状图一眼看明白。簇的形状是椭圆或任意协方差结构、且能容忍计算开销——GMM 是首选它能拟合出比 Kmeans 丰富得多的分布形态。样本量大、特征维度高、还想顺便做降维可视化——自组织映射SOM神经网络聚类能出拓扑图直观得不像算法。2. 核心原理与需求拆解2.1 五种方法底层逻辑对比先说清楚一个核心事实这五种方法虽然都叫聚类但优化目标完全不是一回事。Kmeans 本质是硬划分 方差最小化。它把数据点硬性指派到距离最近的质心优化目标函数是所有样本到所属质心的欧氏距离平方和最小。这个目标函数是凸的局部优化问题所以对初始质心极其敏感——这就是为什么实战里要跑多次取最优或者用 kmeans 初始化。模糊 C 均值走的是另一个路线每个样本不再被强行甩给某个簇而是分配一组隶属度比如样本 A 对簇 1 的隶属度为 0.7对簇 2 的隶属度为 0.3。它优化的目标函数是隶属度加权后的类内距离和其中有个关键参数 m模糊指数这个 m 控制划分的模糊程度m 越大分类越模糊通常在 [1.5, 2.5] 之间取值。层次聚类是策略上的差异它不直接做划分而是通过反复合并或分裂来构造一棵树。自底向上叫凝聚法agglomerative每次找最近的两个簇合并自顶向下叫分裂法divisive不断切分。距离度量有好几种单连接、全连接、平均连接、WARD。这几种度量方式差异非常大——单连接容易连成链状全连接对噪声敏感WARD 倾向于生成紧凑球形簇。GMM 是从概率生成模型的角度做聚类它假设整个数据集是由若干个高斯分布混合产生的聚类就是估计每个高斯分量的参数均值、协方差、混合系数然后用后验概率判断样本属于哪个分量。它跟 Kmeans 有血缘关系——Kmeans 其实是 GMM 在协方差矩阵为单位矩阵、且各分量等概率条件下的特例。神经网络聚类SOM跟前四种完全不是一个套路它用竞争学习的机制让网络中的神经元节点争抢样本赢的节点向样本靠近同时带动邻域内的节点跟着调整。最终训练出来的神经元拓扑图上相邻的神经元对应数据集里相邻的区域能真正保留数据的拓扑结构这一点对高维可视化特别有价值。2.2 方法选型的现实考量用作图来打比方Kmeans 像个锤子FCM 像把带弹性的剪刀层次聚类像把手术刀GMM 像个精密模具SOM 像个变形地图。选工具不是越复杂越好而是越匹配越好。几个实战中的判断标准样本量在万级别以下且维度不高可以先跑层次聚类看树状图帮自己对数据形成一个整体认知再决定后续用什么方法。业务上要求必须给每个样本一个确定标签那 GMM 和 FCM 需要把软划分结果硬化的过程取最大概率/隶属度对应类别Kmeans 和层次聚类天然就是硬的。数据有缺失值GMM 和 FCM 对缺失值更难处理Kmeans 和层次聚类相对鲁棒一些SOM 对缺失样本也能做一定程度的学习。簇间边界模糊且业务场景需要置信度比如客户分群后要对每个客户给一个这类可信度那就选 FCM 或 GMM别用 Kmeans。本项目代码实现的初衷就是搭建一个可复用的实验框架把这些算法全部封装成统一接口输入一个特征矩阵 X 和参数选项输出 labels、中心点或模型参数、可视化图。这样横向对比起来特别方便——同一份数据跑五种方法结果一眼见高下。3. 环境准备与数据预处理3.1 Matlab 环境配置Matlab 建议使用 R2020b 及以上版本我测试用的 R2022b主要原因是新版对自组织映射函数、概率分布对象的支持更完备报错信息也更友好。需要用到以下工具箱Statistics and Machine Learning Toolbox提供 kmeans、pdist、linkage、cluster、fitgmdist、evalclusters 等核心函数这是必备项。Deep Learning Toolbox提供 selforgmap 函数用于自组织映射聚类如果要做竞争层聚类还需要 nntool。Fuzzy Logic Toolbox提供 fcm 函数模糊 C 均值可以直接调用。这个工具箱不是标配很多人没装需要另行安装。不想装工具箱的话我下面也会给出手写版 FCM 核心代码30 行就能跑通。3.2 数据准备与标准化聚类算法绝大多数基于距离计算所以特征量纲不一致会直接毁掉结果。比如特征 A 的取值范围是 [0, 1]特征 B 的取值范围是 [0, 100000]那距离计算基本被特征 B 完全主导聚类结果形同虚设。我习惯先做 Z-score 标准化或者 Min-Max 归一化。对聚类来说Z-score 一般是更稳的选择因为 Min-Max 对离群点非常敏感一个极端值就能把其他所有值压到接近 0。Z-score 计算公式z (x - mean) / stdMatlab 里一行搞定。有些场景预标准化反而会丢失信息比如你故意保留权重特征让某个维度占主导那就别标准化。但如果你不确定那就标准化的收益显著大于风险。注意对测试集做标准化时必须使用训练集的均值和标准差不能把测试集混进来一起算。这点是新手最容易犯的泄漏错误。3.3 演示数据生成为了对比五种方法我建议用两类人工数据做测试一类是规则球形簇一类是不规则椭圆簇和带重叠的簇。下面是生成测试数据的代码三种形态的数据都造一份% 生成三类测试数据 rng(42); % 固定随机种子 % 1. 规则球形簇 X1 [randn(300,2)*0.6 [2 2]; randn(300,2)*0.6 [-2 2]; randn(300,2)*0.6 [0 -2]]; % 2. 不规则椭圆有重叠 X2 [randn(300,2)*[1.2 0.3] [0 0]; randn(300,2)*[0.3 1.2] [4 3]; randn(300,2)*0.8 [2 6]]; % 3. 环形与噪声 t linspace(0, 2*pi, 300); X3 [[cos(t) sin(t)]*2 randn(300,2)*0.05; randn(1000,2)*0.3 [0 0]];第三种数据加了一个内部核心簇和大量噪声点这种结构对大多数聚类算法都是考验可以用来检验各种方法的鲁棒性。3.4 距离度量选择距离度量不是默认为欧氏距离就万事大吉。常见选择有欧氏距离Euclidean适合连续型数值特征最常用。曼哈顿距离Cityblock对维度间的相关性不敏感适合高维稀疏场景。余弦相似度Cosine适合文本向量这类方向重要的数据比如 TF-IDF 特征。马氏距离Mahalanobis考虑了特征间的相关性和尺度差异GMM 内部本质上就在用这种距离思路。在层次聚类和 Kmeans 中Matlab 的pdist2函数可以轻松指定距离度量实现在各类方法里统一传入距离类型参数即可。一个值得注意的细节是如果用余弦距离数据本身不需要标准化方向不受模长影响用了反而可能引入噪声。4. 五种聚类方法的 Matlab 实现解析4.1 Kmeans 聚类的工程化实现Kmeans 在 Matlab 里已经封装得非常好一个函数调用就能跑完。但工程化使用必须会调参不然默认参数在某些数据上会翻车。% Kmeans 聚类代码 % 参数说明 % - k: 聚类数 % - Distance: 距离度量sqeuclidean/cityblock/cosine % - Replicates: 随机初始质心尝试次数取最优 % - Options: 迭代显示与最大迭代次数 k 3; [idx, C, sumd] kmeans(X1, k, ... Distance, sqeuclidean, ... Replicates, 10, ... MaxIter, 500, ... Display, final, ... Start, plus);这里有几个关键点首先是Replicates参数。Kmeans 的初始质心选择对最终结果影响极大默认只跑一次很容易陷入局部最优。设置为 10 到 20让算法从不同初始点各跑一遍最终取目标函数最小的一次结果。这是最简单也最有效的质量保障手段。其次是Start参数。默认是随机选 k 个样本点作为初始质心plus对应 kmeans 初始化策略能显著提升初始质心质量尤其对高维数据。kmeans 的思路是第一个质心随机选后续质心优先选那些离已有质心远的点这样初始质心分布更均匀。提示用 kmeans 判断什么 k 值合适可以用evalclusters函数配合 Calinski-Harabasz 准则或 Silhouette 准则。Matlab 会自动计算不同 k 值下的评价指标画出曲线后找肘部位置的 k。4.2 层次聚类从距离矩阵到树状图层次聚类在 Matlab 里的流程是一条固定链路先算样本两两距离再算类与类之间的距离linkage最后根据 cut 阈值确定最终类别。所以一共有三个决策点样本距离度量、簇间距离度量、截断位置。% 层次聚类完整流程 D pdist(X1, euclidean); % 样本间两两距离 Z linkage(D, ward); % 凝聚层次聚类最小方差法 figure; dendrogram(Z, 0); % 画树状图0表示全部叶子显示 % 根据树状图定类别数这里手工设3类 idx cluster(Z, maxclust, 3);其中linkage的 method 选项是最值得深入的。ward方法最小方差法倾向于生成大小比较均匀的球形簇是大多数场景下的默认首选average未加权平均距离对噪声相对鲁棒complete最大距离法生成的簇更紧凑但可能过度分裂single最小距离法容易产生链状结构除非你有特殊需求否则基本不用它。实际项目里建议用 WARD 或者 AVERAGE 各跑一遍然后对比轮廓系数选结果更好的那种。还有一个非常实用的小技巧optimalleaforder函数可以对树状图叶子节点重新排序让相邻叶子之间的相似度最大化。画出来的树状图会清晰很多特别适合用于给业务方展示聚类结构。% 优化叶子节点顺序让树状图更清晰 Z_opt optimalleaforder(Z, D); dendrogram(Z_opt, 0);层次聚类最大优势是给你一张树状图你能看到从 1 类到 N 类所有可能的划分层级。这在做业务汇报时特别好用——如果我们只看两类是这么分的看三类左侧那类会继续拆成两块这种从粗到细的叙事能力是其他四种方法不具备的。4.3 模糊 C 均值FCM代码实现与工具箱版本如果安装了 Fuzzy Logic Toolbox直接调用 fcm 就行% 模糊C均值聚类工具箱版 [center, U, objFun] fcm(X1, 3, [2.0 100 1e-5 0]);第四个参数向量依次是模糊指数 m默认 2、最大迭代次数默认 100、最小改善量默认 1e-5、是否显示迭代信息默认 0。其中U是隶属度矩阵size 是 n×k每行代表样本对各簇的隶属度行和等于 1。要把软划分变成硬标签取每行最大值所在列即可[~, idx] max(U, [], 2);但在实际业务场景光有硬标签还是不够我一般还会做两个事一是将最高隶属度小于阈值比如 0.6的样本标记为边界样本不强分配类别二是统计每个簇的平均隶属度这个指标反映了该簇整体的纯度——平均隶属度高说明这个簇是铁板一块低则说明簇边界模糊。FCM 缺陷也很明显对初始聚类中心敏感要跑多次取目标函数最小的一次、对噪声和离群点敏感隶属度会被异常值拉拽。还有一个特别容易被忽视的问题FCM 的最优划分通常是类间平衡的如果数据本身类别很不均衡FCM 可能硬生生把大类拆开去迁就小类。如果没装工具箱手写 FCM 也非常简单核心就是不断迭代更新隶属度矩阵和聚类中心两个公式function [center, U, obj] myFCM(X, k, m, maxIter) % 手写模糊C均值聚类 n size(X, 1); dim size(X, 2); rng(42); center X(randperm(n, k), :); % 初始化聚类中心 for iter 1:maxIter % 计算样本到中心的距离矩阵(n x k) dist zeros(n, k); for j 1:k diff X - center(j, :); dist(:, j) sum(diff.^2, 2); end % 更新隶属度矩阵公式1 U zeros(n, k); for i 1:n d dist(i, :); if min(d) 1e-10 U(i, :) 0; U(i, d min(d)) 1; else tmp (1 ./ d).^(1/(m-1)); U(i, :) tmp / sum(tmp); end end % 更新聚类中心公式2 newCenter zeros(k, dim); for j 1:k w U(:, j).^m; newCenter(j, :) sum(X .* w, 1) / sum(w); end % 目标函数值 obj(iter) sum(sum((U.^m) .* dist)); % 检查收敛 if iter 1 abs(obj(iter)-obj(iter-1)) 1e-5 break; end center newCenter; end end核心逻辑就两个公式隶属度更新公式U_ij 1 / sum_k ((dist_ij / dist_ik)^(1/(m-1)))聚类中心更新公式C_j sum_i (U_ij^m * X_i) / sum_i (U_ij^m)这段代码建议自己手敲一遍敲完你对 FCM 的理解会远超那些只调工具箱函数的人。公式里的 m 改成不同的值1.3、1.5、2.0、2.5跑一遍你会发现类别的模糊程度差异特别明显。4.4 高斯混合模型GMM聚类概率视角的聚类GMM 的思路是假设数据由 k 个高斯分布混合生成用最大期望算法EM迭代估计每个高斯分布的参数均值、协方差、权重系数。在 Matlab 里调用fitgmdist即可% GMM 聚类 gmm fitgmdist(X2, 3, ... CovarianceType, full, ... RegularizationValue, 0.01, ... Options, statset(MaxIter, 1000)); % 得到样本后验概率和硬标签 p gmm.posterior(X2); [~, idx] max(p, [], 2);GMM 与 Kmeans 的关系可以这样理解Kmeans 聚类结果是每个样本被分到一个簇证明是距离最近的质心GMM 是每个样本被分到后验概率最大的那个高斯分布。Kmeans 的质心对应 GMM 的均值但是 GMM 多估计了协方差矩阵和混合系数所以能拟合更多样化的簇形状。GMM 有四个关键参数每个都必须理解CovarianceTypefull 表示每个分量有完整的协方差矩阵可拟合椭圆簇但有参数爆炸问题每个簇需要 d*(d1)/2 个参数diagonal 表示各分量协方差矩阵是对角矩阵假设特征独立参数少但拟合能力折扣tied 表示所有分量共享同一个协方差矩阵适合类别间形状一致的场景。RegularizationValue协方差矩阵可能因为样本太少而变成奇异矩阵不可逆加一个小的正则项比如 0.01保证数值稳定性。数据维度高时这个参数基本是必须的。SharedCovariance是否让所有分量共享协方差矩阵。k 的个数GMM 也可以用 BIC贝叶斯信息准则来自动筛选最优的 k这点比 Kmeans 有优势。GMM 最常见的坑EM 算法对初始化敏感容易陷入局部最优。解决办法是多次随机初始化Matlab 里用Replicates选项默认是 1gmm fitgmdist(X2, 3, ... Replicates, 10, ... Options, statset(MaxIter, 1000));注意GMM 是概率模型它对数据分布的假设很强——如果数据真的不是高斯混合分布的GMM 聚类结果大概率不如 Kmeans。所以用 GMM 之前先画个分布直方图做目检看到明显的多峰形态再用。4.5 神经网络聚类自组织映射 SOM 与竞争层网络SOM 在 Matlab 的 Deep Learning Toolbox 里直接调用selforgmap就可以。基本原理是把高维数据映射到低维网格通常是 2 维每个网格上的神经元通过竞争学习抢占数据特征空间中的某个区域。训练完成后相邻的神经元处理相似的数据形成一个拓扑保持的映射图。% 自组织映射聚类 net selforgmap([10 10]); % 10x10网格共100个神经元 net train(net, X_scaled); % 训练网络注意输入需要转置特征x样本 y net(X_scaled); idx vec2ind(y); % 获胜神经元索引每个样本对应一个获胜神经元但通常 100 个神经元会被分成若干连续区域每个区域对应一个簇。所以拿到神经元索引之后还需要进一步聚类% 对获胜神经元的位置做二次聚类得到最终簇标签 pos net.IW{1}; % 神经元权重向量每个神经元一个权重 k 3; cluster_neuron kmeans(pos, k); idx_final cluster_neuron(idx); % 映射回样本标签从流程上可以看出SOM 相当于先做了一个非线性降维从原始高维空间映射到 10x10 网格把数据压缩成几十上百个原型点再对这些原型点做轻量 Kmeans计算量大幅下降同时抗噪声能力有所提升。SOM 需要调的参数主要是网格大小、学习率trainParam.lr、邻域半径trainParam.cover。网格大小影响聚类的分辨率太大比如 30x30会导致相邻神经元过度细分二次聚类基本失去意义太小比如 3x3会丢失数据内部结构9 个神经元根本装不下复杂分布。经验法则先跑 10x10 默认网格然后用 U-matrix 或距离矩阵检查效果再调大小。SOM 最大的杀器是可视化。训练完画plotsomplanes(net)可以看到每个特征维度在神经元网格上的分布情况不同特征的热力图位置对比能直观发现哪些特征对聚类贡献最大。plotsomplanes(net); % 查看各特征维度的神经元权重分布 plotsompos(net, X_scaled); % 查看样本映射到拓扑图上的位置缺点也直说SOM 训练耗时比 Kmeans 长一个数量级神经网络的随机性导致结果不稳定每次训练可能略有出入需要固定随机种子复现。除了 SOM也可以用竞争层competitive layer。Matlab 自带的competlayer函数可以构造竞争层网络但竞争层不像 SOM 有邻域拓扑的概念训练完的各个神经元是完全独立的。所以实际效果通常不如 SOM这个更适用于单纯的矢量量化场景。5. 聚类效果评估与横向对比5.1 内部评价指标聚类评价分为内部指标和外部指标。外部指标需要真实标签适合在人工数据上验证算法有效性内部指标不需要标签适合真实业务数据因为真实数据通常没标签。三个常用的内部指标轮廓系数Silhouette对每个样本计算它与同簇其他样本的平均距离 a以及它与最近的其他簇所有样本的平均距离 b轮廓系数 (b - a) / max(a, b)。取值范围 [-1, 1]越接近 1 说明样本离自身簇近、离别的簇远聚类效果越好。Matlab 中silhouette(X, idx)可以单独计算evalclusters可以批量评估不同 k。Calinski-Harabasz 指数类内离散度与类间离散度的比值值越大越好计算量小适合快速扫描 k 参数。Davies-Bouldin 指数类内散布与类间距离的比值综合值越小越好。5.2 在三种数据集上的实战表现我在三种人工测试数据上分别跑五种算法做了个快速横向对比结果很有代表性数据集KmeansFCM层次聚类GMMSOM球形簇X1优秀轮廓0.85优秀优秀WARD优秀良好椭圆重叠X2较差轮廓0.32中等差优秀轮廓0.72中等环形噪声X3差轮廓0.21差差差中等拓扑保留这个表说明两个结论 第一没有万能算法。环形噪声这种结构五种方法全军覆没唯一能做的就是先做数据清洗或换密度聚类DBSCAN来处理这次项目没涉及但它提醒我们选型前先画数据分布图比什么都重要。 第二GMM 在椭圆重叠数据上优势是碾压级的因为它能估计协方差结构。如果你发现聚类结果的边界总是横平竖直或者圆形边界那很大程度上说明算法选型出了问题——你用了基于欧氏距离的方法处理非球形数据。5.3 综合评判与算法选型思维导图给一个我在项目复盘时总结的选型策略直接照这个脑图走能省掉大量试错第一层判断数据是否有标签。有标签就做监督分类聚类无用。无标签进入第二层。 第二层判断是否知道类别数。不知道先用层次聚类看树状图辅助定 k或者用 GMM 跑 BIC 找最优 k。知道进入第三层。 第三层判断簇形状是否接近球形。球形 / 凸形Kmeans 或 FCM 优先。想省事用 Kmeans想要软划分要 FCM。任意形状 / 椭圆 / 流形GMM 或密度聚类。流形结构直接用 DBSCAN 更有戏本文的五种方法都不是为流形而设计的。高维数据还要可视化SOM。6. 常见问题与避坑指南6.1 特征标准化里的维度爆炸坑有一个案例让我印象特别深一份客户分群数据包含年龄、消费金额、登录次数三个字段。直接跑 Kmeans 聚类出来的结果全是消费金额这一个维度在做主导——年龄和登录次数基本成了噪声。Z-score 标准化之后三个特征才能真正共同参与距离计算聚类结构也从单一梯度变成了三个子群。这条经验值得反复强调凡是基于距离的聚类算法Kmeans/FCM/层次聚类必须做标准化。这五个方法里只有 GMM 对尺度相对不敏感因为有协方差矩阵在调节各维度的尺度但上了 GMM 我也建议标准化反正无害。6.2 k 值怎么定肘部法则的实操细节evalclusters是最省事的做法但筛选标准有两个需要注意Calinski-Harabasz 通常随 k 单调递增需要找增幅明显放缓的位置不一定是最大值。Silhouette 准则可以直接取最大值对应的 k。需要三个角度综合判断业务上类别是否可解释、簇内样本数量是否合理没有单样本簇、评价指标是否支持。% 用轮廓系数批量评估k eva evalclusters(X1, kmeans, silhouette, KList, [1:10]); figure; plot(eva); % eva.OptimalK 就是最优簇数6.3 种子固定与结果复现Matlab 里每次运行kmeans、fcm、fitgmdist、selforgmap结果都可能不同。原因各有不同Kmeans 的随机初始化、FCM 的随机初始化、GMM 的 EM 随机起点、SOM 的随机权值初始化。复现实验结果的固定方式rng(42); % 固定全局随机种子但要注意两点selforgmap内部可能使用自己的随机流有时需要配合rng(42)和训练参数中的固定种子来保证完全复现。固定种子的代价是可能掐掉偶然发现更优结果的可能性。学术或生产环境建议固定种子保持可重复性探索性分析不建议多跑几次用不同种子看看结果稳定度反而更有价值。6.4 维度灾难与距离集中效应特征维度超过 20 之后所有基于距离的方法都会遭遇距离集中效应样本间欧氏距离差异变小到几乎没区分度。这不是谁实现错了是高维几何的固有性质。处理手段有几种先用 PCA 降维保留方差占比 90% 以上的主成分再做聚类。改用余弦距离而不是欧氏距离余弦距离在文本高维稀疏数据上依然能有效区分方向差异。用 SOM 先把高维数据映射到低维网格再做 Kmeans。6.5 GMM 协方差奇异报错GMM 最容易报的错是 Ill-conditioned covariance created 或者直接矩阵非正定。触发原因通常是某类别样本数量小于特征维度、数据近似共线性、正则化参数太小。解法组合拳% 1. 增加正则化 gmm fitgmdist(X, k, RegularizationValue, 0.01); % 2. 改用对角协方差 gmm fitgmdist(X, k, CovarianceType, diagonal); % 3. 增多样本或减少特征维度 % 4. 上述都不行的话换 Kmeans这个经验顺序值得好好记先正则化再对角化最后降维。从全协方差到对角协方差是有代价的——拟合能力降低但如果数据本来特征相关性就不强这个代价几乎可以忽略。6.6 层次聚类在大样本下的性能陷阱层次聚类的复杂度在样本数上是 O(n^3) 级别的最坏情况所以样本量超过 1 万基本就跑不动了。它在 1000 个样本以下跑得很欢3000 个以上开始明显变慢10000 个以上的话你大概率要等上半天。有两种替代策略先对数据做一次抽样比如 2000 个样本做层次聚类定 k然后用这个 k 对全量数据跑 Kmeans。先用 Kmeans 粗聚类 50~100 个簇再对这些簇中心做层次聚类这样既能看层级结构又能控制计算量。7. 完整代码框架与使用说明整合一下形成一个可以复用的完整脚本框架。为了方便横向对比我把五种方法封装成函数统一输入 X 和 k输出标签和评价指标function run_all_clustering(X, k) % 五种聚类方法一次性对比 %% 1. Kmeans rng(42); [idx_k, C] kmeans(X, k, Replicates, 10); s_k mean(silhouette(X, idx_k)); fprintf(Kmeans 轮廓系数: %.3f\n, s_k); %% 2. 层次聚类 D pdist(X, euclidean); Z linkage(D, ward); idx_h cluster(Z, maxclust, k); s_h mean(silhouette(X, idx_h)); fprintf(层次聚类 轮廓系数: %.3f\n, s_h); %% 3. 模糊C均值 [center, U, ~] fcm(X, k, [2.0 100 1e-5 0]); [~, idx_f] max(U, [], 2); s_f mean(silhouette(X, idx_f)); fprintf(FCM 轮廓系数: %.3f\n, s_f); %% 4. GMM try gmm fitgmdist(X, k, Replicates, 5, RegularizationValue, 0.01); [~, idx_g] max(gmm.posterior(X), [], 2); s_g mean(silhouette(X, idx_g)); fprintf(GMM 轮廓系数: %.3f\n, s_g); catch e warning(GMM失败: %s, e.message); end %% 5. SOM rng(42); net selforgmap([10 10], 100, 3); net train(net, X); y net(X); som_idx vec2ind(y); pos net.IW{1}; idx_s kmeans(pos, k); final_som idx_s(som_idx); s_s mean(silhouette(X, final_som)); fprintf(SOM 轮廓系数: %.3f\n, s_s); %% 可视化对比 figure; subplot(2,3,1); gscatter(X(:,1), X(:,2), idx_k); title([Kmeans: num2str(s_k)]); subplot(2,3,2); gscatter(X(:,1), X(:,2), idx_h); title([Hierarchical: num2str(s_h)]); subplot(2,3,3); gscatter(X(:,1), X(:,2), idx_f); title([FCM: num2str(s_f)]); subplot(2,3,4); gscatter(X(:,1), X(:,2), idx_g); title([GMM: num2str(s_g)]); subplot(2,3,5); gscatter(X(:,1), X(:,2), final_som); title([SOM: num2str(s_s)]); end这段代码我把每种方法的输出、错误处理、可视化都包含了。遇到问题直接在注释基础上改就行。比如有些数据集跑 GMM 会协方差奇异报错try-catch 会把问题跳过继续跑后面的 SOM不至于整个脚本崩掉。SOM 部分有一个细节特别容易错selforgmap训练的输入矩阵要求是特征×样本所以必须转置 X 为 X。输出结果net(X)也是特征×样本vec2ind拿到每个样本对应的获胜神经元索引然后把神经元用 Kmeans 分簇再映射回样本。这个链路虽然绕但理解了就特别顺。8. 项目扩展与经验总结从这个项目往后走有几个自然的扩展方向一是引入自动取 k 机制。当前脚本需要手工指定 k实际场景中可以用 Calinski-Harabasz 或 BIC 自动筛选最优 k把evalclusters和fitgmdist的内部准则结合起来做一个全自动聚类入口函数。二是集成密度聚类。环形噪声这种数据形态Kmeans 和层次聚类都失效DBSCAN 是首选。核心是epsilon和MinPts的自动估计方案——可以通过 K-距离曲线来找拐点确定。三是把聚类算法嵌入到完整的数据分析管线中。真实项目往往是清洗 - 特征工程 - 标准化 - 聚类 - 人工验证 - 业务落地聚类只是中间一环。落在业务上是做客户分群还是做图像分割FCM 是图像分割的经典算法还是做信号分类管线很多细节都需要重新设计。回到主题本身我在这几次实操中最深的体会是聚类方法本身不是瓶颈选型和数据预处理才是。同一个 Kmeans做得好的团队和做得差的团队差距能有三倍以上区别就在于有没有做标准化、有没有多次初始化、有没有系统评估 k 值。这套代码框架价值不在于代码本身多精妙而在于把五种方法放在同一个比较框架里让数据自己说话。最后再分享一个非常顺手的技巧在你用gscatter查看聚类结果时把不同簇用不同颜色画出来同时把每个簇的中心点用大一号的标记叠上去。这样你一眼就能看出每个算法在哪类数据上分歪了——比如 Kmeans 在椭圆数据上总会画一条垂直于长轴的切割线而 GMM 的切割线会沿着椭圆的长轴方向走。这种直观观察比看任何数值指标都更能帮你理解聚类算法的工作方式。
返回列表