☰
MATLAB聚类算法源码包实战:Kmeans、FCM、层次聚类与DBSCAN参数调优与避坑指南
2026/10/29 2:29:19 网站建设 项目流程

简介:面向机器学习初学者与需要快速上手聚类实验的开发者,这份资源提供FCM模糊C均值、K-means、Kmedia等经典聚类算法的MATLAB实现及配套示例,帮助读者在无监督学习场景中完成数据分类与分析。压缩包共74个文件、约2.05MB,以40个m脚本为核心,辅以gif与png图示、txt说明、htm与css页面及mat数据文件,并附FuzzyClusteringToolbox工具箱与PDF文档,覆盖算法实现、归一化与反归一化、聚类评价、投影可视化等模块。已有4888人学习下载。读者可据此对照数学原理理解初始化聚类中心、隶属度更新与收敛判断等步骤,借助轮廓系数、Davies-Bouldin指数等指标比较不同算法的适用场景,并通过示例脚本完成数据预处理、结果可视化与参数调优,为实际项目中的聚类分析提供可复用的代码基础。

1. 四种聚类算法源码包:从 FCM 到 Kmeans 的一次性落地

手上有一批没打标签的数据,想先看看它们自然分成几堆,聚类是最省事的入口。这个资源包把四种常用聚类算法的 MATLAB 源码和示例脚本放在一起,核心是 Kmeans、FCM(模糊 C 均值),另外两种常见搭配是层次聚类和基于密度的 DBSCAN。它解决的不是“算法原理没人讲”,而是“原理看懂了,代码跑不起来、参数不知道填多少、结果图不会看”这个断点。适合两类人:一是做课程设计或论文实验、需要快速拿到可复现结果的学生;二是手上真有数据、想先用聚类摸一遍分布再决定后续建模方向的从业者。下面按“源码怎么读、参数怎么调、结果怎么验、坑在哪”的顺序拆开讲,每一步都能照着跑。

2. 源码结构与运行环境:先让脚本跑通再谈调参

拿到一个算法包,最忌讳上来就改核心函数。正确顺序是先确认环境、再跑通示例、最后才动参数。这一章把目录结构、依赖和最小可运行路径理清楚。

2.1 目录构成与文件职责

这类包通常长这样:一个主目录下分算法子目录,每个子目录里有一个核心函数文件(如kmeans_core.m、fcm_core.m)、一个示例脚本(demo_*.m)、可能还有一两个测试数据集(.mat或.txt)。核心函数只负责“给数据和参数、返回标签和聚类中心”,示例脚本负责“造数据或读数据、调核心函数、画图”。这个分工很重要:你换自己的数据时只改示例脚本的读取部分,核心函数基本不动,出问题也容易定位是数据问题还是算法问题。

常见做法是先把整个包解压到一个不含中文和空格的路径下,比如D:\cluster_demo\。MATLAB 对路径里的中文和空格容忍度不高,尤其是老版本,addpath之后函数找不到十有八九是路径问题。然后在命令行里cd到主目录,执行addpath(genpath(pwd)),把子目录一次性加进搜索路径。

% 在 MATLAB 命令行或脚本开头执行 root = 'D:\cluster_demo'; % 换成你的实际路径,不要带中文和空格 cd(root); addpath(genpath(root)); % 递归加入所有子目录 which kmeans_core % 能返回路径说明函数已可见

genpath会把所有子目录拼成一个长路径串,addpath一次性注册。最后那句which是自检:如果返回空,说明函数名和你以为的不一样,或者文件根本没在这个目录树下。这一步别省,我见过太多“代码没错但就是跑不动”的情况,最后都是函数没在路径上。

2.2 依赖与版本兼容

纯 MATLAB 实现的聚类算法一般不依赖工具箱,但有两个例外要留意:一是画图用了gscatter或silhouette,这两个在统计与机器学习工具箱里;二是距离计算如果调了pdist2,同样属于统计工具箱。示例脚本里如果出现这些函数而你没装对应工具箱,会直接报 undefined。判断方法很简单,跑一遍示例,报错信息里出现Undefined function 'xxx'且 xxx 是上面这类函数名,就是工具箱缺失。

替代方案是自己写距离矩阵,欧氏距离的pdist2用两层循环或矩阵运算都能替。下面是一个不依赖工具箱的欧氏距离实现,维度不高时够用:

function D = my_dist2(X, C) % X: n x d 样本, C: k x d 中心, 返回 n x k 距离矩阵 n = size(X,1); k = size(C,1); D = zeros(n,k); for i = 1:n for j = 1:k D(i,j) = sqrt(sum((X(i,:) - C(j,:)).^2)); end end end

这个函数逻辑直白:逐样本逐中心算平方和再开方。参数上X是样本矩阵、C是中心矩阵,返回的D(i,j)就是第 i 个样本到第 j 个中心的距离。数据量上万时这个双重循环会慢,那时再考虑向量化或换pdist2。先保证能跑,再谈性能,这是顺序问题。

2.3 最小可运行路径

跑通示例的标准动作是:打开demo_kmeans.m,从头执行。示例一般会先生成三簇高斯数据,再调kmeans_core,最后画散点加中心。第一次跑不要改任何参数,看它能不能出图、图上的簇分得对不对。能出图,说明环境和路径都没问题,接下来才是换数据、调 k。

% demo_kmeans.m 的典型骨架(示意) rng(42); % 固定随机种子,结果可复现 X = [randn(100,2)+3; randn(100,2)-3; randn(100,2)+[3 -3]]; k = 3; [labels, centers] = kmeans_core(X, k); figure; gscatter(X(:,1), X(:,2), labels); hold on; plot(centers(:,1), centers(:,2), 'kx', 'MarkerSize', 12, 'LineWidth', 2);

rng(42)这行是血泪经验:Kmeans 的初始中心是随机的,不固定种子,你每次跑出来的簇编号和边界都可能不一样,调参时根本分不清是参数起作用还是随机性在作怪。固定种子后,同样的输入永远得到同样的输出,对比才有意义。gscatter按标签给点上色,centers用黑色叉号标出,一眼能看出中心和簇的对应关系。

3. Kmeans 与 FCM 的参数实操:k 怎么定、模糊系数怎么选

这一章是整篇的重心。Kmeans 和 FCM 是包里最常用的两个,前者硬划分、后者软划分,参数逻辑完全不同,混着调必翻车。

3.1 Kmeans 的核心参数与初始化策略

Kmeans 要调的其实就三个:簇数 k、初始化方式、最大迭代次数。k 是最玄学的,没有哪个公式能直接告诉你“这批数据就该分 3 类”。工程上的做法是先跑一个 k 的扫描,用肘部法或轮廓系数辅助判断。肘部法看的是簇内平方和(WCSS)随 k 变化的拐点,拐点之后再加 k,WCSS 下降明显变缓,说明再加簇的收益不大了。

wcss = zeros(1, 8); for k = 1:8 [~, ~, sumd] = kmeans_core(X, k); % sumd 为各簇内平方和 wcss(k) = sum(sumd); end plot(1:8, wcss, '-o'); xlabel('k'); ylabel('WCSS');

这段扫描的逻辑是:对每个 k 跑一次聚类,把各簇的簇内平方和加起来作为整体紧致度。sumd是核心函数返回的每簇距离平方和,求和就是 WCSS。看图上曲线从陡降转为平缓的那个 k,通常就是合理值。注意这只是辅助,数据本身有重叠时拐点会很模糊,别死磕。

初始化方式上,随机初始化简单但容易陷局部最优,k-means++ 按距离概率选初始中心,稳定性好很多。包里如果核心函数只支持随机初始化,可以在外层多跑几次取 WCSS 最小的那次:

best = inf; best_labels = []; best_centers = []; for rep = 1:10 [labels, centers, sumd] = kmeans_core(X, k); if sum(sumd) < best best = sum(sumd); best_labels = labels; best_centers = centers; end end

重复 10 次取最优,是应对随机初始化的低成本手段。参数rep的次数看数据规模,几千样本以内 10 次足够,数据大就减到 5 次控制时间。这段代码不改算法本身,只在结果层面挑最好的,属于“后悔药”式写法。

3.2 FCM 的模糊系数 m 与隶属度矩阵

FCM 和 Kmeans 最大的区别是它不把样本硬塞进某一簇,而是给每个样本一个对各簇的隶属度,隶属度之和为 1。核心参数是模糊系数 m,控制隶属度的“软硬程度”。m 越接近 1,隶属度越接近 0/1,退化成硬聚类;m 越大,隶属度越平均,簇边界越模糊。经验取值在 1.5 到 2.5 之间,默认 2.0 是大多数场景的起点。

m = 2.0; % 模糊系数,常用 1.5~2.5 max_iter = 100; % 最大迭代 tol = 1e-5; % 隶属度变化收敛阈值 [U, centers] = fcm_core(X, k, m, max_iter, tol); [~, labels] = max(U, [], 2); % 取最大隶属度作为硬标签用于画图

U是 n×k 的隶属度矩阵,U(i,j)表示第 i 个样本属于第 j 簇的程度。max(U,[],2)沿行取最大值,得到每个样本最归属的簇,仅用于可视化,真正的模糊信息还在 U 里。tol是收敛判据,比较相邻两次迭代的隶属度矩阵差异,小于阈值就停。tol 设太小迭代次数暴涨,设太大结果没收敛,1e-5 是个稳妥的默认值。

m 的选择有个实操判断:跑完后看 U 的最大值分布。如果绝大多数样本的最大隶属度都在 0.9 以上,说明 m 偏小、结果太硬,可以往上调;如果最大隶属度普遍在 0.4 到 0.6,簇之间几乎分不开,m 偏大,往下调。这个检查比背公式有用。

3.3 两种算法的结果对比与选用

同一批数据分别跑 Kmeans 和 FCM,对比标签一致率和中心位置,能帮你判断数据结构。Kmeans 快、结果硬、适合簇边界清晰的场景;FCM 慢一些、给出隶属度、适合边界模糊或需要“部分归属”信息的场景,比如一个样本同时具备两类特征。选型上,如果下游任务只要一个确定标签,Kmeans 够用;如果要量化“有多像”,FCM 的 U 矩阵才有价值。

维度KmeansFCM
输出硬标签 + 中心隶属度矩阵 + 中心
关键参数k、初始化方式k、模糊系数 m
计算开销较低较高(每轮算隶属度)
适用场景边界清晰、要确定标签边界模糊、要归属程度

这张表不是让你背,是调参时对照:如果你发现 Kmeans 结果对初始化极其敏感、每次跑都不一样,说明数据簇之间有重叠,这时候换 FCM 看隶属度分布,往往能看出重叠区在哪。

4. 层次聚类与 DBSCAN:不预设 k 的两种思路

Kmeans 和 FCM 都得先给 k,但很多时候你根本不知道分几类。包里另外两种算法就是为这个场景准备的:层次聚类给出一棵树,DBSCAN 按密度连片,都不需要预先指定簇数。

4.1 层次聚类的连接方式与树切割

层次聚类的核心是“距离定义”和“连接准则”。两个簇之间的距离怎么算,常见有单连接(最近点)、全连接(最远点)、平均连接(所有点对平均)。单连接容易产生“链式效应”,把一条长条数据串成一簇;全连接倾向产生紧凑的球状簇;平均连接是折中,实践里用得最多。

Y = pdist(X); % 样本两两距离 Z = linkage(Y, 'average'); % 平均连接构建层次树 dendrogram(Z); % 画树状图看结构 cutoff = 0.7 * max(Z(:,3)); % 按树高比例切割 T = cluster(Z, 'cutoff', cutoff, 'criterion', 'distance');

linkage的第二个参数就是连接准则,average是平均连接。dendrogram画出的树,纵轴是合并时的距离,横轴是样本。切割位置决定簇数:在树高的某个位置横切一刀,切到几条竖线就是几簇。cutoff用最大合并距离的 70% 是个经验起点,实际要看树状图上哪一段竖线最长(合并距离跳变最大),在那附近切最合理。cluster按距离阈值切割并返回标签。

层次聚类的好处是树状图把整个合并过程摊开给你看,簇的层次关系一目了然;代价是计算复杂度高,样本上万时pdist的距离矩阵会吃掉大量内存,这时候要么抽样,要么换 DBSCAN。

4.2 DBSCAN 的 eps 与 MinPts 调参

DBSCAN 靠两个参数定义“密度”:eps是邻域半径,MinPts是邻域内最少点数。一个点如果 eps 邻域内至少有 MinPts 个点,就是核心点;核心点连成片形成簇;既不核心也不在任何核心点邻域内的,标为噪声。它最大的价值是能识别噪声点,不像 Kmeans 硬把每个点都塞进某一簇。

eps_val = 0.5; % 邻域半径,需按数据尺度调整 MinPts = 5; % 最小点数,一般取维度+1 起步 labels = dbscan_core(X, eps_val, MinPts); % labels 中 -1 表示噪声点 noise_ratio = sum(labels == -1) / numel(labels);

eps是最难调的。数据尺度不同,eps 的合理值差好几个数量级,所以调之前先看样本两两距离的分布,取距离的第 5 到第 10 百分位附近作为 eps 起点。MinPts一般从维度加一开始试,二维数据取 4 到 5,高维适当加大。noise_ratio是自检指标:噪声比例超过 30% 通常说明 eps 太小或 MinPts 太大,簇被切碎了;噪声接近 0 且只有一簇,说明 eps 太大,所有点连成一片。

4.3 四种算法的适用边界

把四种放一起看,选型逻辑就清楚了。Kmeans 和 FCM 要求先定 k、假设簇是凸的、对尺度敏感;层次聚类不预设 k、给层次结构、但吃内存;DBSCAN 不预设 k、能识别噪声、能处理任意形状,但对 eps 极其敏感、高维数据下密度定义会失效。实操里我一般先用 Kmeans 快速摸一遍 k 的大致范围,再用层次聚类的树状图验证这个 k 是否合理,最后如果数据里有明显离群点,用 DBSCAN 把噪声剔掉再回头跑前两个。这个组合拳比死磕单一算法靠谱。

5. 避坑与排查:聚类结果不对时先看这五条

聚类最让人抓狂的是“代码没报错,但结果就是不对”。下面五条是按出现频率排的踩坑记录,每条都按现象、原因、解决写,照着排查能省不少时间。

5.1 每次运行结果都不一样

现象:同一份数据、同样的 k,跑两次簇的划分明显不同。原因:Kmeans 随机初始化,没固定随机种子。解决:脚本开头加rng(42),或者用 3.1 里的多次重复取最优。固定种子是复现的前提,论文实验里不固定种子,结果根本没法写进正文。

5.2 簇里全是同一个标签或出现空簇

现象:跑完发现所有样本被分到一簇,或者某个簇一个样本都没有。原因:初始中心太集中,或者 k 设得比实际簇数大太多。解决:换 k-means++ 初始化,或减小 k;空簇出现时检查核心函数有没有对空簇做重新分配中心的处理,没有的话手动把空簇中心重置到离其他中心最远的样本上。

5.3 FCM 隶属度全是 0.5 左右

现象:U 矩阵里每个样本对各簇的隶属度都差不多,分不出主次。原因:模糊系数 m 设得太大,或者数据本身没有明显簇结构。解决:先把 m 降到 1.5 试,看隶属度是否拉开;如果还是平均,说明这批数据可能本来就不该聚类,先做相关性分析或降维看看分布。

5.4 DBSCAN 把大部分点标成噪声

现象:labels里 -1 占了一大半。原因:eps 太小或 MinPts 太大,密度阈值过高。解决:先算样本两两距离,取第 5 到第 10 百分位作为 eps;MinPts 从维度加一开始往下调。调完看噪声比例,落在 5% 到 15% 之间比较健康。

5.5 不同量纲的特征把距离带偏

现象:某个特征数值范围是 0 到 1,另一个是 0 到 10000,聚类结果几乎只由大范围特征决定。原因:欧氏距离对量纲敏感,大数值特征主导了距离计算。解决:聚类前对所有特征做标准化,X = zscore(X),让每个特征均值为 0、标准差为 1。这一步在真实数据上几乎必做,示例数据因为各维尺度接近才看不出来。

提示:标准化要用训练集的均值和标准差,如果后面还有新数据要归类,得用同一套参数变换,不能各自标准化。

6. 结果验证与进阶:用轮廓系数和可视化把结论坐实

跑出标签只是开始,能不能证明“这个聚类结果可信”才是关键。这一章讲两个验证手段和一个把结果讲清楚的技巧,最后收在我自己踩过的一个习惯上。

6.1 轮廓系数评估簇质量

轮廓系数衡量每个样本“离自己簇有多近、离最近的其他簇有多远”,取值 -1 到 1,越接近 1 说明簇内紧、簇间远。整体轮廓系数是所有样本的平均,可以用来横向比较不同 k 或不同算法的结果。

function s = my_silhouette(X, labels) % 不依赖工具箱的轮廓系数实现(示意,适合中小数据) n = size(X,1); s = zeros(n,1); for i = 1:n same = find(labels == labels(i)); same(same == i) = []; if isempty(same), s(i) = 0; continue; end a = mean(sqrt(sum((X(same,:) - X(i,:)).^2, 2))); other_labels = setdiff(unique(labels), labels(i)); b = inf; for c = other_labels' idx = find(labels == c); d = mean(sqrt(sum((X(idx,:) - X(i,:)).^2, 2))); b = min(b, d); end s(i) = (b - a) / max(a, b); end s = mean(s); end

a是样本到同簇其他点的平均距离,b是到最近异簇的平均距离,(b-a)/max(a,b)就是单样本轮廓系数。整体取平均后,0.5 以上算结构清晰,0.25 到 0.5 算有一定结构但边界模糊,0.25 以下基本可以认为没分出有意义的簇。这个函数是 O(n²) 的,样本上万时抽样算或者直接用工具箱的silhouette。

6.2 降维可视化辅助判断

高维数据的聚类结果光看数字没感觉,降到二维画出来最直观。PCA 是最省事的降维方式,取前两个主成分做散点,按聚类标签上色,簇分不分得开一眼便知。

[coeff, score] = pca(X); figure; gscatter(score(:,1), score(:,2), labels); xlabel('PC1'); ylabel('PC2');

score是降维后的坐标,前两列就是前两个主成分。注意 PCA 降维只用于可视化,聚类本身还是在原始(标准化后)特征上做的,别把降维结果再拿去聚类,那样会丢信息。如果 PCA 图上簇重叠严重但轮廓系数不低,说明簇结构不在前两个主成分方向上,可以看第三个主成分或换 t-SNE。

6.3 一个我坚持了很多年的习惯

早些年我做聚类实验,图一画出来觉得“分得挺开”,就直接写结论,后来被一组数据打脸:Kmeans 和 FCM 在同一批数据上给出的簇数一样,但样本归属差了将近三成,而我当时只看了 Kmeans 的图。从那以后我每次跑完聚类,都强制走一遍“三查”:查随机种子是否固定、查特征是否标准化、查至少两种算法的标签一致率。一致率高,结论才敢往外写;一致率低,说明数据结构本身模糊,得回去看数据而不是继续调参。

标签一致率用简单的匹配就能算,把两组标签做列联表,取每行最大计数求和除以总数,就是不考虑簇编号顺序的一致率。这个数低于 0.7 时,我不会急着下“分成几类”的结论,而是先怀疑数据里有没有该剔除的异常值、有没有该做的变换。这套流程不复杂,但能挡掉大部分“看起来对、其实站不住”的结果。希望这套源码包加上这几步验证,能帮你把聚类这件事从“跑出图”推进到“敢用结论”。

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

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

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

立即咨询