资讯详情

MATLAB聚类实战:k-means、DBSCAN与密度峰值算法对比与调优

📅 2026/10/30 6:03:54 | 华诺云谱 👁 阅读
MATLAB聚类实战:k-means、DBSCAN与密度峰值算法对比与调优
简介这份MATLAB聚类算法实践包面向机器学习初学者与需要快速上手无监督聚类的开发者围绕k-means、DBSCAN与基于密度峰值聚类ADPC三种方法展开帮助读者在MATLAB环境中完成数据分组与簇结构探索。压缩包共23个文件以14个m脚本、5个mat数据文件、3个txt与1个md说明为主脚本承载算法实现与绘图逻辑mat文件提供可直接加载的实验数据md文档则交代运行方式整体约81KB轻量易读。资源按kmeans、dbscan、adpc三个目录组织并配有Dataset数据集与统一的结果可视化脚本便于横向对比不同算法在同一数据上的表现。已有183人学习适合作为课程实验、算法入门或聚类效果对比的参考素材读者可借此理解参数设置、簇中心识别与噪声处理等关键环节。1. 从一份 MATLAB 聚类压缩包说起k-means、DBSCAN 与密度峰值到底怎么落地很多人第一次接触聚类是在 MATLAB 里敲下kmeans(X,3)看到三团颜色分明的散点图然后觉得“聚类不过如此”。真正上手项目才发现簇数量怎么定、密度参数怎么调、螺旋数据为什么被切成一堆碎片全是玄学。这份名为k-means,DB-SCAN,基于密度峰值的聚类算法的matlab简单实践.rar的资源包恰好把三种典型聚类算法——k-means、DBSCAN、ADPC基于密度峰值的聚类——放在同一套数据集和绘图框架下做对照实践。它包含Dataset目录下的 D31、Aggregation、Spiral 三组经典二维数据以及kmeans、dbscan、adpc三个算法目录每个目录都有独立的PlotClusterinResult.m绘图脚本和主算法文件。对正在做课程设计、算法对比实验或者想搞清“密度类算法到底比划分类算法强在哪”的从业者来说这份包能省掉大量搭框架的时间。下面按“资源结构 → 算法实现 → 参数调优 → 踩坑排查”的顺序拆开讲。2. 资源包结构与数据流先看清目录再动手拿到压缩包别急着解压跑main这个包没有统一入口三个算法各自独立。先理清文件依赖关系能避免后面“函数未定义”的低级报错。2.1 三个算法目录与公共数据集的分工解压后顶层是Dataset、kmeans、dbscan、adpc四个目录外加一个README.md。Dataset里同时存在.txt和.mat两种格式D31.txt、Aggregation.txt、Spiral.txt是原始文本坐标对应的.mat文件是转换后的矩阵。Txt2Mat.m就是干这个转换的它把文本按列读入并保存为data变量。三个算法目录结构高度相似都带一个PlotClusterinResult.m但主算法文件不同kmeans目录下是k-means.mdbscan目录下是DBSCAN.m和DBSCAN_M.madpc目录下是ADPC.m并且adpc额外多了FindCenter.m、CalculateRand.m、ClusteringCenter.m、PlotSolution.m、Clustering.m这一组辅助函数。mydata.mat在dbscan和adpc目录下都出现通常是给自定义数据预留的占位文件。目录核心文件作用DatasetD31.txt / Aggregation.txt / Spiral.txt原始二维坐标数据DatasetTxt2Mat.m文本转 mat 矩阵kmeansk-means.mk-means 主流程dbscanDBSCAN.m / DBSCAN_M.m密度聚类主流程adpcADPC.m / FindCenter.m密度峰值聚类与中心点搜索各目录PlotClusterinResult.m统一绘图函数这里有个容易忽略的点三个目录的PlotClusterinResult.m虽然同名但内部对标签的处理可能不同。k-means 输出的是 1 到 k 的整数标签DBSCAN 会把噪声标为 0 或 -1ADPC 的标签体系又不一样。如果你把某个目录的绘图函数直接复制到另一个目录用颜色映射很可能错位。常见做法是每个算法跑完后先unique(labels)看一眼标签范围再决定绘图函数的输入。2.2 数据加载与 Txt2Mat 转换的实操Txt2Mat.m的逻辑很直接用load或dlmread读文本取前两列作为特征保存成.mat。但 D31 和 Aggregation 的文本格式可能有差异有的用空格分隔有的用制表符。直接load有时会把整个文件读成一个结构体需要手动取字段。下面是我一般会用的稳健写法% Txt2Mat.m 的稳健版本兼容空格/制表符分隔 function Txt2Mat(txtFile, matFile) fid fopen(txtFile, r); data textscan(fid, %f %f, Delimiter, { , \t}, ... MultipleDelimsAsOne, true); fclose(fid); data [data{1}, data{2}]; % 只取前两列作为二维特征 save(matFile, data); end逻辑说明textscan比load更可控Delimiter同时指定空格和制表符MultipleDelimsAsOne避免连续分隔符产生空列。参数上如果你的数据超过两列比如带标签列把%f %f改成对应列数再在data拼接时选需要的列。转换完成后在算法脚本里用load(D31.mat)就能拿到data变量。注意Spiral.txt是螺旋数据坐标范围可能很大绘图时如果不对坐标轴做axis equal螺旋会被压扁看不出结构。提示转换前先head -5看一眼文本前几行确认分隔符和列数比盲目跑脚本省时间。3. k-means 实现拆解从随机初始化到收敛判定k-means 是三者里最容易复现的但“容易”不等于“随便跑都对”。这份包里的k-means.m走的是经典迭代流程理解每一步的参数含义才能在自己数据上改得动。3.1 k-means.m 的主循环与距离计算k-means 的核心就三步初始化中心、分配样本、更新中心循环到收敛。包里k-means.m的结构大致如下我按可读性重写并加了注释% k-means.m 核心流程 function [labels, centers] kmeans_custom(data, k, maxIter) [n, ~] size(data); % 随机选 k 个样本作为初始中心 idx randperm(n, k); centers data(idx, :); labels zeros(n, 1); for iter 1:maxIter % 分配每个样本找最近中心 dist pdist2(data, centers); % n x k 距离矩阵 [~, labels] min(dist, [], 2); % 更新按标签求均值 newCenters zeros(k, size(data, 2)); for c 1:k newCenters(c, :) mean(data(labels c, :), 1); end % 收敛判定中心移动小于阈值 if norm(newCenters - centers) 1e-6 centers newCenters; break; end centers newCenters; end end逻辑说明pdist2一次性算出所有样本到所有中心的欧氏距离比双重循环快得多。min(dist, [], 2)沿第二维取最小值返回的第二个输出就是标签。更新中心时用逻辑索引labels c取子集求均值。收敛判定用中心矩阵的 Frobenius 范数差阈值1e-6对归一化后的数据合适如果数据量纲很大比如坐标上万这个阈值要相应放大否则会提前退出。参数上k是唯一需要外部指定的关键参数maxIter一般设 100 到 300 足够。初始化的随机性会导致每次结果略有不同包里如果没做多次重启取最优你可以自己包一层循环跑 10 次取簇内平方和最小的那次。常见做法是用kmeans内置函数对比验证[idx, C, sumd] kmeans(data, k, Replicates, 10)sumd就是各簇内距离和拿它和自定义实现的结果比差太多说明自定义代码有 bug。3.2 簇数量 k 的确定肘部法则与轮廓系数k 选错后面全白搭。D31 这种有明显分离的数据k 取真实簇数附近时簇内平方和会明显下降。肘部法则的做法是跑一系列 k画k对sumd总和的曲线找拐点。包里没有现成的肘部脚本我一般会补一段% 肘部法则扫描 k 从 1 到 10 sumd_all zeros(10, 1); for k 1:10 [~, ~, sumd] kmeans(data, k, Replicates, 5); sumd_all(k) sum(sumd); end plot(1:10, sumd_all, -o); xlabel(k); ylabel(簇内平方和);逻辑说明sumd是每个簇内样本到簇中心距离的平方和sum(sumd)是全局目标函数值。k 增大时它单调下降拐点处下降速度骤减那个 k 就是候选。Aggregation 数据有 7 个簇曲线在 k7 附近会明显变缓。但肘部法则不是万能的Spiral 这种非凸数据k-means 无论取几都切不好这时候要换 DBSCAN 或 ADPC。轮廓系数是另一个指标evalclusters(data, kmeans, Silhouette, KList, 1:10)可以直接给出评分但计算量比肘部法则大。注意k-means 对量纲敏感如果两列特征范围差一个数量级距离会被大范围那列主导。跑之前先data zscore(data)标准化或者手动除以列标准差。4. DBSCAN 与 ADPC密度类算法的参数敏感性与中心点搜索DBSCAN 和 ADPC 都属于密度类方法但思路不同。DBSCAN 靠邻域半径和最小点数扩展簇ADPC 靠密度峰值找中心再分配。这份包里两者都有独立实现正好对比。4.1 DBSCAN.m 的核心对象判定与簇扩展DBSCAN 的三个概念——核心对象、边界对象、噪声——在代码里对应三个判断。DBSCAN.m的主逻辑通常是先算每个点的 ε 邻域内点数标记核心点再从核心点出发做广度优先扩展。下面是我按包里结构还原的核心片段% DBSCAN.m 核心邻域计数与簇扩展 function [labels] dbscan_custom(data, eps, minPts) n size(data, 1); dist pdist2(data, data); neighborCount sum(dist eps, 2) - 1; % 减去自身 coreIdx find(neighborCount minPts); labels zeros(n, 1); % 0 表示未访问 clusterId 0; for i 1:length(coreIdx) p coreIdx(i); if labels(p) ~ 0, continue; end clusterId clusterId 1; queue p; labels(p) clusterId; while ~isempty(queue) q queue(1); queue(1) []; neighbors find(dist(q, :) eps); for j 1:length(neighbors) nb neighbors(j); if labels(nb) 0 labels(nb) clusterId; if neighborCount(nb) minPts queue(end1) nb; % 核心点才继续扩展 end end end end end % 剩余未标记的点为噪声标签保持 0 end逻辑说明dist eps得到邻域布尔矩阵sum(...,2)-1去掉自身得到邻域点数。coreIdx是核心点索引。扩展时用队列做广度优先只有核心点才加入队列继续扩展边界点标记后不再扩展。参数上eps决定邻域大小minPts一般取数据维度的 2 倍以上二维数据常用 4 到 10。eps太小会导致大量点被标为噪声太大则所有点合并成一个簇。包里DBSCAN_M.m可能是带矩阵化加速的版本逻辑相同但用矩阵运算替代部分循环。4.2 ADPC 的密度峰值计算与 FindCenter 逻辑ADPC 的核心是算两个量局部密度 ρ 和到更高密度点的最近距离 δ。ρ 高且 δ 大的点就是簇中心。FindCenter.m通常负责在决策图上找这些异常点。ADPC.m里密度计算常见用高斯核% ADPC.m 密度与距离计算 function [rho, delta] compute_rho_delta(data, dc) n size(data, 1); dist pdist2(data, data); % 高斯核密度dc 为截断距离 rho sum(exp(-(dist / dc).^2), 2) - 1; % 按密度降序排列 [~, ordrho] sort(rho, descend); delta zeros(n, 1); nneigh zeros(n, 1); delta(ordrho(1)) max(dist(:)); % 密度最高点 delta 取最大距离 for i 2:n idx ordrho(i); higher ordrho(1:i-1); [delta(idx), nneigh(idx)] min(dist(idx, higher)); end end逻辑说明dc是截断距离一般取所有点对距离从小到大排序后的 1% 到 2% 分位数。exp(-(dist/dc).^2)是高斯核比硬截断更平滑。rho减去 1 是去掉自身贡献。delta的计算依赖密度排序对每个点在密度比它高的点里找最近距离。ordrho(1)是密度最高点它的delta取全局最大距离。FindCenter.m随后会在rho和delta的散点图上找右上角的离群点作为中心常见做法是取rho * delta最大的前几个或者设阈值rho rho_th delta delta_th。参数上dc对结果影响很大。太小则密度估计退化成最近邻太大则所有点密度趋同。包里如果没自动算dc我一般用dc prctile(pdist(data), 2)。ADPC 相比 DBSCAN 的优势在于能自动确定簇数量但中心点选取仍需要人工看决策图或设阈值这是它的“半自动”边界。4.3 三算法在 Spiral 数据上的表现对比Spiral 是典型的非凸数据k-means 必然失败DBSCAN 和 ADPC 理论上能处理。实际跑的时候DBSCAN 对eps极其敏感螺旋臂之间的间距如果小于eps两条臂会被连成一个簇eps太小则螺旋断成碎段。ADPC 在螺旋上表现取决于dcdc取太小会导致密度峰值过多簇数量虚高。我一般会先用PlotClusterinResult.m把三种结果并排画出来肉眼确认螺旋是否被完整保留。如果 DBSCAN 把螺旋切成多段试着把eps调大 10% 到 20%如果 ADPC 中心点太多把dc调大或提高中心判定阈值。算法关键参数Spiral 上的典型问题调整方向k-meansk螺旋被径向切分换算法DBSCANeps, minPts螺旋断裂或粘连eps 微调 10%ADPCdc, 中心阈值中心过多或过少dc 取 2% 分位5. 避坑与排查跑这份代码最容易翻车的五个地方这份包年代可能较早MATLAB 版本更新后有些函数行为变了加上三个算法目录独立交叉使用时容易出问题。下面五条是我实际跑的时候踩过的。5.1 现象运行 k-means.m 报“未定义函数 pdist2”原因pdist2属于 Statistics and Machine Learning Toolbox如果安装 MATLAB 时没勾选该工具箱或者用的是精简版就会报未定义。解决先ver看已安装工具箱列表没有的话用sqrt(sum((data - center).^2, 2))手写距离或者用dist zeros(n,k); for c1:k, dist(:,c)sum((data-centers(c,:)).^2,2); end替代。手写版本在数据量小于一万时速度可接受。5.2 现象DBSCAN 跑完所有点标签都是 0原因eps设得太小没有任何点的邻域内点数达到minPts所有点都被判为噪声。解决先算距离矩阵的 1% 分位数作为eps起点eps0 prctile(pdist(data), 1)再根据结果微调。minPts不要超过数据点数的 1%否则核心点太少。5.3 现象ADPC 决策图上所有点挤在一起找不到中心原因dc取值过大高斯核密度对所有点都接近rho差异被抹平。解决dc改用距离矩阵的 1% 到 2% 分位数而不是均值。如果数据量纲大先标准化再算dc。决策图用scatter(rho, delta)画中心点应该在右上角明显分离。5.4 现象绘图函数报“索引超出矩阵维度”原因PlotClusterinResult.m里可能用labels直接做颜色索引但 DBSCAN 的噪声标签是 0MATLAB 的colormap索引从 1 开始0 会报错。解决绘图前把标签加 1labels_plot labels 1或者把噪声单独用灰色画。ADPC 的标签如果是浮点数也要先round再转整数。5.5 现象换自己的数据后结果完全不对原因自己的数据列数超过两列或者量纲差异大而包里代码默认前两列且未标准化。解决加载后先data zscore(data(:, 1:2))确认只取二维特征。如果数据有缺失值pdist2会返回 NaN需要先data rmmissing(data)或手动填充。提示三个算法目录的mydata.mat是占位文件直接跑可能加载到空数据。换成自己的数据时把load(mydata.mat)改成load(你的数据.mat)并确认变量名是data。6. 进阶技巧用 CalculateRand 做定量对比与结果验证跑出图只是第一步要写报告或论文得有定量指标。adpc目录下的CalculateRand.m就是干这个的它计算 Rand 指数衡量聚类结果与真实标签的一致性。Rand 指数的定义是在所有样本对中被两种划分一致地分到同簇或异簇的比例。值越接近 1 越好。用法上你需要有真实标签向量trueLabels和算法输出的predLabels。注意标签编号不需要对应Rand 指数只看“是否同簇”这个关系。下面是我一般会封装的调用方式% 计算 Rand 指数并输出调整后版本 function [randIdx, adjRand] eval_cluster(trueLabels, predLabels) n length(trueLabels); % 构建同簇关系矩阵 sameTrue (trueLabels trueLabels); samePred (predLabels predLabels); % Rand 指数一致对数 / 总对数 agree sum(sum(sameTrue samePred)) - n; % 减去对角线自身 total n * (n - 1); randIdx agree / total; % 调整 Rand 指数考虑随机一致性 % 这里用简单近似严格版需计算期望 adjRand randIdx; % 如需严格调整参考 Hubert-Arabie 公式 end逻辑说明sameTrue和samePred是 n×n 布尔矩阵sameTrue samePred统计两种划分一致的样本对。减去n是去掉对角线自身配对。total是总配对数。调整 Rand 指数需要减去随机情况下的期望一致率严格实现要算列联表这里给的是简化版够课程设计用。参数上trueLabels和predLabels长度必须一致且都是列向量。验证流程建议这样走先用 D31 这种有真实标签的数据跑三种算法分别算 Rand 指数k-means 在 D31 上通常能到 0.9 以上DBSCAN 调好参数也能到 0.95 左右ADPC 取决于中心点选得准不准。如果某个算法 Rand 指数明显偏低回到决策图或参数扫描找原因。我习惯把参数扫描和 Rand 指数画成曲线比如 DBSCAN 的eps从 0.1 到 1.0 扫一遍看 Rand 指数峰值在哪这比手动试快得多。从那以后我每次跑聚类对比实验都强制先跑一遍 Rand 指数再画图因为图好看不代表簇分得对尤其是 Spiral 这种数据颜色画得漂亮但 Rand 指数可能只有 0.6。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑