☰
四种聚类算法MATLAB源码包:Kmeans、FCM、层次与DBSCAN实战解析
2026/10/29 2:29:17 网站建设 项目流程

简介:面向机器学习初学者与需要快速上手聚类实验的开发者,这份资源提供FCM模糊C均值、K-means、Kmedia等经典聚类算法的MATLAB实现及配套示例,帮助读者在数值计算环境中理解无监督分类的完整流程。压缩包共74个文件、约2.05MB,以40个m脚本为核心,辅以gif与png图示、txt说明、htm与css页面及mat数据文件,覆盖算法实现、结果可视化与性能评估等环节。已有4888人学习下载,说明其在教学与实践中具有一定参考价值。读者可据此对比不同算法在模糊边界、凸形分布与非球形数据上的表现差异,结合轮廓系数等指标完成聚类效果评估,并借助FuzzyClusteringToolbox中的辅助函数简化调参过程,逐步掌握从数据预处理到结果解读的完整思路。

1. 四种聚类算法源码包:从一份 MATLAB 作业到能跑通的分群工具

手上拿到一份聚类算法源码包,第一反应往往不是“算法好深”,而是“这玩意儿能不能直接跑出图”。这份资源就是冲着这个来的:它把 Kmeans、FCM(模糊 C 均值)、以及另外两种常见聚类实现打包在一起,配了示例脚本,丢进 MATLAB 里改个数据路径就能出聚类结果图。适合两类人:一类是课程设计、实验报告要交聚类对比图的学生,另一类是手上有一堆无标签数据、想快速看分群效果的工程开发者。神经网络和 FCM 常被放在一起讲,但这份包的重点在“聚类”这条线上,神经网络更多是作为对照思路出现。下面按“能干什么 → 怎么跑 → 怎么改”拆开讲,重点放在目录结构、核心函数和参数调法上,新手照着能复现,熟手能直接拿去改。

2. 项目结构与技术选型:四个算法文件怎么组织

2.1 目录骨架与文件职责

这类 MATLAB 聚类源码包,常见做法是一个主目录下分“算法函数”“示例脚本”“数据”三块,结构大致如下(具体文件名以包内为准,职责划分是这类项目的通用形态):

clustering_demo/ ├── main_demo.m % 总入口:加载数据、依次调用四种算法、画对比图 ├── algorithms/ │ ├── kmeans_custom.m % Kmeans 实现(含初始化与迭代) │ ├── fcm_custom.m % 模糊 C 均值实现(隶属度矩阵迭代) │ ├── hierarchical_custom.m% 层次聚类(或凝聚式)实现 │ └── dbscan_custom.m % 密度聚类实现 ├── utils/ │ ├── load_data.m % 统一数据读取,返回 N×D 矩阵 │ ├── normalize_data.m % 归一化,聚类前几乎必做 │ └── plot_clusters.m % 统一绘图,按标签上色 └── data/ └── sample_data.mat % 示例数据(二维点集,便于可视化)

这种拆法的好处是算法和绘图解耦:换数据只动load_data.m,换画法只动plot_clusters.m,算法文件本身不用碰。技术选型上就是纯 MATLAB,不依赖额外工具箱(这是这类包能“下载即跑”的关键),矩阵运算全部用内置的pdist2、sum、repmat这类函数完成,避免调Statistics and Machine Learning Toolbox里的现成函数——否则就失去“看源码学实现”的意义了。

2.2 为什么聚类前一定要归一化

这是这类项目里最容易被忽略、又最容易翻车的一步。Kmeans 和 FCM 都基于欧氏距离,如果某一维的量纲是另一维的几十倍,距离就被那一维主导,分出来的簇基本等于按那一维切。normalize_data.m的常见写法是 Z-score:

function Xn = normalize_data(X) % 按列做 Z-score 归一化,避免量纲主导距离 mu = mean(X, 1); % 每列均值,1×D sigma = std(X, 0, 1); % 每列标准差,1×D sigma(sigma == 0) = 1; % 防止常数列除零 Xn = (X - mu) ./ sigma; % 广播到 N×D end

参数说明:X是 N×D 的原始数据,mean(X,1)沿行方向求均值得到每列中心,std(X,0,1)的第二个参数 0 表示按 N-1 归一化(样本标准差),第三个参数 1 表示沿行方向。sigma == 0的保护是血泪经验——示例数据里偶尔有常数列,不处理会直接出 NaN,后面所有距离计算全废。归一化之后再做聚类,四种算法的结果才有可比性,否则对比图看着像四个不同的数据集。

2.3 四种算法的定位差异

选型上要清楚这四个不是互相替代关系,而是适用场景不同:

算法核心假设适合的数据形态关键参数
Kmeans簇是凸的、大小相近球形分布、簇数已知K(簇数)
FCM样本以隶属度软分配边界模糊、有重叠C(簇数)、m(模糊系数)
层次聚类簇有嵌套结构小样本、要看树状图距离度量、链接方式
DBSCAN簇是密度连通的任意形状、有噪声eps、MinPts

示例脚本main_demo.m一般会把这四个跑在同一份二维数据上,画成 2×2 子图。看对比图时重点看两件事:Kmeans 和 FCM 在簇边界上的差异(FCM 的软分配会让边界点颜色更“混”),以及 DBSCAN 能不能把离群点单独标出来。这个对比本身就是这份包最有价值的部分——它让你直观看到“同一份数据,换个假设,结论就不一样”。

3. 核心算法拆解:Kmeans 与 FCM 的迭代逻辑

3.1 Kmeans 的初始化与迭代

Kmeans 的坑八成出在初始化上。随机选初始中心,跑两次结果不一样是常态。这份包的kmeans_custom.m常见做法是“随机初始化 + 多次重启取最优”,核心循环如下:

function [labels, centers, J] = kmeans_custom(X, K, maxIter, restarts) % X: N×D, K: 簇数, maxIter: 单次最大迭代, restarts: 重启次数 [N, ~] = size(X); bestJ = inf; for r = 1:restarts idx = randperm(N, K); % 随机选 K 个样本作初始中心 centers = X(idx, :); for iter = 1:maxIter % E 步:每个样本分配到最近中心 D = pdist2(X, centers); % N×K 距离矩阵 [~, labels] = min(D, [], 2); % M 步:按分配重算中心 newCenters = zeros(K, size(X, 2)); for k = 1:K if any(labels == k) newCenters(k, :) = mean(X(labels == k, :), 1); else newCenters(k, :) = X(randi(N), :); % 空簇重新播种 end end if norm(newCenters - centers) < 1e-6 centers = newCenters; break; end centers = newCenters; end % 目标函数:簇内平方和 J = sum(min(pdist2(X, centers).^2, [], 2)); if J < bestJ bestJ = J; bestLabels = labels; bestCenters = centers; end end labels = bestLabels; centers = bestCenters; J = bestJ; end

逻辑说明:外层restarts循环解决初始化敏感问题,内层是标准的 E-M 交替。pdist2(X, centers)一次算出所有样本到所有中心的距离,比双重循环快一个量级。空簇处理(any(labels == k)为假时重新播种)是必须的,否则某个中心会变成 NaN 并污染后续迭代。收敛判据用中心位移的范数小于1e-6,比用目标函数变化更稳。参数上,restarts一般取 5~10,maxIter取 100~300 足够,再多基本是空转。

3.2 FCM 的隶属度矩阵迭代

FCM 和 Kmeans 的区别就在“软分配”:每个样本对每个簇有一个隶属度 u_ik,满足 Σ_k u_ik = 1。迭代公式是:

function [U, centers] = fcm_custom(X, C, m, maxIter, tol) % X: N×D, C: 簇数, m: 模糊系数(通常取2), tol: 收敛阈值 [N, ~] = size(X); U = rand(N, C); U = U ./ sum(U, 2); % 行归一化,满足隶属度和为1 for iter = 1:maxIter Uprev = U; % 更新中心:隶属度的 m 次方作权重 Um = U .^ m; centers = (Um' * X) ./ sum(Um, 1)'; % 更新隶属度 D = pdist2(X, centers).^2; % N×C 平方距离 D(D == 0) = 1e-12; % 防止除零 ratio = D .^ (-1/(m-1)); U = ratio ./ sum(ratio, 2); if norm(U - Uprev, 'fro') < tol break; end end end

参数说明:m是模糊系数,取 1 时退化成硬聚类,取太大(>3)隶属度趋于均匀、失去区分度,工程上默认 2 是稳妥值。D .^ (-1/(m-1))是 FCM 的核心公式,距离越小隶属度越大。D == 0的保护同样不能省——样本恰好落在中心上时会出现 0 的负幂。收敛判据用隶属度矩阵的 Frobenius 范数变化,比看中心更敏感。跑完U之后,取每行最大值所在列作为硬标签,就能和 Kmeans 的结果放一起对比了。

3.3 统一绘图与结果对比

plot_clusters.m的职责是把标签映射成颜色,二维数据直接散点图,中心用大号标记点出来:

function plot_clusters(X, labels, centers, titleStr) % X: N×2, labels: N×1, centers: K×2 figure; hold on; gscatter(X(:,1), X(:,2), labels); % 按标签自动上色 if nargin >= 3 && ~isempty(centers) plot(centers(:,1), centers(:,2), 'kx', ... 'MarkerSize', 12, 'LineWidth', 2); end title(titleStr); hold off; end

gscatter是 MATLAB 自带的按组散点,省去手写颜色循环。中心用黑色叉号标出,和样本点区分开。四个算法跑完各调一次,子图拼起来就是对比图。这里有个细节:FCM 的硬标签可能有空簇,gscatter遇到空组不会报错但图例会少一项,属正常现象,不用去“修”。

4. 跑通与调参:从示例数据到自己的数据

4.1 三步跑通示例

拿到包之后,标准流程就三步,别急着改算法:

% 第一步:把包目录设为当前工作目录,确保 algorithms/ 和 utils/ 在路径上 addpath(genpath(pwd)); % 第二步:跑总入口,看四个子图是否正常出图 main_demo; % 第三步:确认无报错后,再打开 main_demo.m 看它怎么调每个算法

addpath(genpath(pwd))把子目录一次性加进搜索路径,这是 MATLAB 项目最常见的路径管理写法,省得每个函数单独addpath。如果跑main_demo报“未定义函数”,九成是路径没加对,先which kmeans_custom确认能不能找到。示例数据是二维的,出图快,先确保这条链路通,再换数据。

4.2 换成自己的数据要改哪里

换数据只动load_data.m,保持返回 N×D 数值矩阵即可:

function X = load_data(filepath) % 支持 .mat 和 .csv 两种常见格式 [~, ~, ext] = fileparts(filepath); if strcmpi(ext, '.mat') S = load(filepath); fn = fieldnames(S); X = S.(fn{1}); % 取第一个变量 elseif strcmpi(ext, '.csv') X = readmatrix(filepath); % 跳过表头需另配参数 else error('不支持的文件格式: %s', ext); end X = X(:, 1:2); % 示例只画二维,高维数据改这里 end

注意两点:.mat文件里变量名不固定,用fieldnames取第一个是偷懒但通用的做法;.csv用readmatrix时如果首行是表头,要加'NumHeaderLines', 1。高维数据(D>2)不能直接画散点,常见做法是先做 PCA 降到二维再画,或者只画前两维并在标题里注明。改完数据后,K 值要重新估——示例数据的 K 是写死的,换数据不改 K,出来的图基本没法看。

4.3 参数怎么调:K 值、m 值、eps

调参是这类项目真正花时间的地方,给几个可操作的判断方法:

  • K 值:跑 K=2..8,记录每次的簇内平方和 J,画肘部图,拐点处的 K 通常合理。别迷信“自动确定 K”,肘部法在真实数据上经常拐得不明显。
  • m 值(FCM):从 1.5 试到 2.5,看隶属度矩阵的最大值分布。如果大部分样本的最大隶属度都接近 1/C,说明 m 太大、软得没意义;如果全是 1,说明 m 太接近 1、退化成硬聚类。
  • eps 和 MinPts(DBSCAN):eps 用 k-距离图的拐点估,MinPts 一般取 D+1 起步。这两个参数对结果极其敏感,调的时候一次只动一个。

提示:调参前先把随机种子固定(rng(42)),否则 Kmeans 每次跑结果不同,你根本分不清是参数变了还是随机性作祟。

5. 避坑与排查:五个真实会踩的坑

5.1 现象:跑两次结果完全不一样

原因:Kmeans 随机初始化,没固定种子也没做重启。解决:在main_demo.m开头加rng(42),同时确认kmeans_custom的restarts参数大于 1。如果加了种子还不稳,检查是不是数据里有重复点导致初始中心撞车。

5.2 现象:FCM 跑出 NaN

原因:某个样本到所有中心的距离同时为 0,D .^ (-1/(m-1))出现 0 的负幂得 Inf,归一化时 Inf/Inf 变 NaN。解决:D(D == 0) = 1e-12这行保护必须有;另外检查数据里是否有完全重复的样本,去重后再跑。

5.3 现象:聚类结果全挤在一个簇里

原因:没归一化,某一维量纲压倒其他维;或者 K 设得比实际簇数大太多,空簇反复重播种。解决:先过一遍normalize_data,再用肘部图重新定 K。如果归一化后还是挤在一起,看数据本身是不是根本没有可分结构——不是所有数据都该被聚类。

5.4 现象:gscatter报“组数不匹配”

原因:labels是 N×1 但含有 0 或负数(某些算法输出从 0 开始编号),gscatter要求正整数分组。解决:画图前统一labels = labels - min(labels) + 1,把编号平移到从 1 开始。

5.5 现象:换高维数据后图是一片糊

原因:直接把 D>2 的数据前两维当坐标画,信息损失严重。解决:先[coeff, score] = pca(X)取前两个主成分画图,并在标题注明“PCA 投影”。这一步不改聚类本身,只改可视化,别把 PCA 结果又拿去聚类——那是另一回事。

6. 二次开发:把四种算法接成可对比的实验框架

这份包最值得改的地方,不是把某个算法写得更快,而是把它变成一个“换数据、换参数、自动出对比表”的小实验框架。我一般会加一个run_experiment.m,把四种算法包一层统一接口,输出指标而不是只出图:

function results = run_experiment(X, K) % 统一跑四种算法,返回轮廓系数等指标 rng(42); Xn = normalize_data(X); results = struct(); % Kmeans [lab1, ~] = kmeans_custom(Xn, K, 200, 10); results.kmeans.sil = silhouette(Xn, lab1); % FCM(取硬标签) [U, ~] = fcm_custom(Xn, K, 2, 200, 1e-5); [~, lab2] = max(U, [], 2); results.fcm.sil = silhouette(Xn, lab2); % 其余两种同理接入 end

这样改的好处是:对比从“看图说话”变成“看数说话”,轮廓系数、Calinski-Harabasz 指数都能直接算。二次开发时注意三点:一是接口统一,所有算法都返回 N×1 标签,绘图和指标计算才能复用;二是随机种子在入口统一设,别散落在各算法里;三是把参数(K、m、eps)抽成配置结构体,跑批量实验时用循环扫参,而不是手改代码。验证方法很简单:拿一份已知分群的合成数据(比如三个高斯团),看四种算法能不能都恢复出三个簇,恢复不出来的那个就是参数没调对。

从那以后我每次接这类聚类源码包,都强制先跑一遍合成数据做“冒烟测试”,确认算法本身没写错,再去碰真实数据——不然真实数据一出问题,你根本分不清是数据脏还是代码有 bug。希望这份拆解帮到你,拿到包先跑main_demo,再按第 4 章的路径换数据,最后用第 6 章的框架做对比,基本就能把这四种聚类算法用起来了。

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

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

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

立即咨询