公司动态
从分类到聚类:K-Means与DBSCAN算法原理、Matlab实现与实战指南
1. 从“分类”到“聚类”建模思维的进阶跃迁搞数学建模的朋友对“分类”这个概念肯定不陌生。给定一堆数据我们根据已知的标签训练一个模型把新来的数据分到预设的类别里——这是监督学习的经典套路。但现实中更多的情况是我们面对的数据就像一堆散落的零件没有说明书更没有贴好的标签。我们的任务是从这些看似杂乱的数据中发现内在的结构把相似的零件自动归到一组。这个过程就是“聚类”。如果说分类是“按图索骥”那聚类就是“物以类聚”。它属于无监督学习的核心范畴是数学建模能力从“应用已知”到“探索未知”的关键进阶。在数据爆炸的时代从用户分群、市场细分、异常检测到基因序列分析、社交网络社区发现聚类的身影无处不在。它不再仅仅是一个算法工具更是一种从数据中提炼洞察、发现规律的基础思维方式。今天我们就深入聊聊聚类分析并手把手带你用Matlab实现两个最经典也最实用的算法K-Means和DBSCAN。我会结合多年打比赛和做项目的经验不仅告诉你代码怎么写更会剖析算法背后的“为什么”以及在实际操作中那些容易踩坑的细节。无论你是正在备战数模竞赛还是希望在实际研究中应用聚类这篇内容都能给你带来直接的帮助。2. 聚类分析核心思想与算法选型逻辑2.1 聚类的本质相似性度量与簇的定义所有聚类算法的核心都围绕两个基本问题第一如何定义数据点之间的“相似性”或“相异性”第二如何根据这种度量来划分“簇”最常用的相似性度量是距离。对于数值型数据欧氏距离我们熟悉的直线距离最常用曼哈顿距离网格状路径距离对异常值更稳健余弦相似度则更适合衡量文本或高维向量的方向一致性。选择哪种距离直接影响聚类的结果。例如在分析用户消费行为金额、频率时欧氏距离可能合适但在分析用户兴趣标签0/1向量时杰卡德相似系数或余弦相似度更好。注意在计算距离前数据标准化是必不可少的一步。如果特征A的取值范围是0-10000特征B是0-1那么计算距离时特征A将完全主导结果。最常用的方法是Z-score标准化使均值为0标准差为1或Min-Max归一化缩放到[0,1]区间。在Matlab中可以用zscore或mapminmax函数轻松完成。关于“簇”的定义主要有三种思想原型聚类认为簇是一组围绕某个中心原型的点。K-Means是典型代表它假设每个簇是一个球形。密度聚类认为簇是数据空间中密度较高的区域被密度较低的区域分隔开。DBSCAN是这一思想的翘楚它能发现任意形状的簇并能识别噪声点。层次聚类通过计算数据点间的距离构建一个树状的聚类层次可以自上而下分裂或自下而上聚合进行。2.2 算法对比与选型指南K-Means vs. DBSCAN面对具体问题该如何选择下表对比了两种核心算法的特性特性维度K-MeansDBSCAN核心思想最小化簇内样本到簇中心的距离平方和基于密度连通性将高密度区域划分为簇簇形状凸形特别是球形任意形状是否需要指定簇数K是且对初始值敏感否对噪声/异常值敏感会扭曲簇中心位置鲁棒能识别并排除噪声点参数K簇数eps邻域半径 MinPts最小点数结果稳定性可能因初始中心不同而变化确定性算法相同参数下结果稳定适用场景数据分布均匀、簇大小相近、球形假设合理簇密度不均、形状不规则、需分离噪声选型心法当你对数据的簇数有一个大致的先验认知且数据分布相对规整时优先尝试K-Means。它简单、高效是很好的基线模型。当数据情况不明簇的形状可能很怪异如环绕形、带状或者数据中明显存在大量离散的异常点时必须使用DBSCAN。它探索性更强能给你带来意想不到的发现。在实际建模中我常采用“DBSCAN先行”的策略。先用DBSCAN跑一遍看看它能自动找出多少簇这些簇的形状如何噪声点有多少。这个结果能极大地增强你对数据的理解甚至能反过来验证你预设的K值是否合理。然后再用K-Means进行快速划分或对比。3. K-Means算法深度解析与Matlab实战3.1 算法原理与迭代过程可视化理解K-Means的目标函数很明确最小化所有样本点到其所属簇中心的距离平方和Within-Cluster Sum of Squares, WCSS。它的迭代过程可以形象地理解为“重新划分领地”和“迁都”两个步骤的循环初始化随机选择K个点作为初始簇中心质心。这是所有麻烦的起点——不同的随机种子可能导致不同的最终结果。分配阶段对于每一个数据点计算它到K个质心的距离并将其分配给距离最近的那个质心所在的簇。这就像根据距离远近把老百姓划归到不同的都城管辖。更新阶段对于每一个新形成的簇重新计算该簇所有点的平均值将这个平均值点设为新的簇中心。这好比都城迁到了本国人口的平均中心位置以便更好地管理。迭代重复步骤2和3直到满足停止条件例如质心的移动距离小于某个阈值或分配情况不再改变。这个过程的Matlab实现非常直观。下面是一个从零开始、附带详细注释的代码我建议你先尝试理解并运行它而不是直接调用工具箱。function [idx, centroids] myKMeans(X, K, max_iters) % 自定义K-Means函数 % 输入 % X: 数据矩阵每行一个样本每列一个特征 (n x m) % K: 期望的簇数量 % max_iters: 最大迭代次数 % 输出 % idx: 每个样本所属簇的索引 (n x 1) % centroids: 最终得到的K个簇中心 (K x m) [n, m] size(X); % 1. 初始化随机选择K个样本作为初始中心 randidx randperm(n, K); centroids X(randidx, :); % 用于存储历史中心点便于可视化实际可省略 centroids_history zeros(K, m, max_iters); for iter 1:max_iters fprintf(迭代第 %d 次...\n, iter); % 2. 分配阶段计算每个点到所有中心的距离 distance zeros(n, K); for i 1:K % 计算X中所有点到第i个中心的欧氏距离平方 % 使用向量化运算避免循环大幅提速 diff X - centroids(i, :); distance(:, i) sum(diff.^2, 2); end % 找到每个样本距离最近的中心索引 [~, idx] min(distance, [], 2); % 3. 更新阶段重新计算每个簇的中心 new_centroids zeros(K, m); for i 1:K % 找出所有属于第i簇的样本点 points_in_cluster X(idx i, :); if ~isempty(points_in_cluster) % 新的中心是该簇所有点的均值 new_centroids(i, :) mean(points_in_cluster, 1); else % 极端情况某个簇没有分配到任何点则随机重新初始化该中心 warning(簇 %d 为空正在重新初始化其中心。, i); new_centroids(i, :) X(randi(n), :); end end % 保存历史中心用于演示 centroids_history(:, :, iter) centroids; % 4. 检查收敛如果中心点不再变化则停止 if isequal(centroids, new_centroids) fprintf(质心已收敛迭代在第 %d 次停止。\n, iter); break; end centroids new_centroids; end % 简单的结果可视化仅适用于二维数据 if m 2 figure; colors hsv(K); for i 1:K points X(idx i, :); scatter(points(:,1), points(:,2), 36, colors(i,:), filled); hold on; % 绘制中心点移动轨迹 traj squeeze(centroids_history(i, :, 1:iter)); plot(traj(1,:), traj(2,:), k-x, LineWidth, 1.5, MarkerSize, 10); end scatter(centroids(:,1), centroids(:,2), 200, k, x, LineWidth, 3); title(K-Means聚类结果与质心移动轨迹); xlabel(特征1); ylabel(特征2); hold off; end end3.2 关键难点如何确定最佳K值K-Means最大的痛点就是需要预先指定K。K选错了结果可能毫无意义。下面介绍三种最实用的方法1. 肘部法则这是最经典的方法。原理是随着K增大簇内样本会更紧密WCSS会下降。但下降幅度会在某个点突然变缓这个拐点就像“肘部”暗示着最佳的K。% 肘部法则示例 maxK 10; wcss zeros(maxK, 1); for k 1:maxK [idx, C] kmeans(X, k); % 使用Matlab内置函数 % 计算WCSS for i 1:k cluster_points X(idx i, :); wcss(k) wcss(k) sum(sum((cluster_points - C(i,:)).^2, 2)); end end figure; plot(1:maxK, wcss, bo-); xlabel(簇数 K); ylabel(WCSS); title(肘部法则); grid on;你需要观察曲线找到那个从“陡峭”变为“平缓”的转折点。2. 轮廓系数这是一个更量化的指标它同时考虑了簇内的凝聚度和簇间的分离度。对于每个样本点i计算a(i): i到同簇其他点的平均距离凝聚度。b(i): i到其他所有簇中点的平均距离的最小值分离度。轮廓系数 s(i) (b(i) - a(i)) / max(a(i), b(i))。 s(i)的取值范围是[-1, 1]越接近1说明聚类越合理。所有点的s(i)的均值即为整体轮廓系数。% 使用 silhouette 函数 (Statistics and Machine Learning Toolbox) eva evalclusters(X, kmeans, silhouette, KList, [2:8]); figure; plot(eva); % 绘制不同K值对应的轮廓系数 bestK eva.OptimalK;选择轮廓系数最大的K值。3. 间隙统计量它比较了实际数据的WCSS与随机均匀分布数据参考分布的WCSS的差异。当实际数据的WCSS显著小于参考数据的期望WCSS时就找到了合适的K。% 使用 evalclusters 计算间隙统计量 eva_gap evalclusters(X, kmeans, gap, KList, [1:8], B, 100); % B是参考数据集数量 figure; plot(eva_gap);选择间隙统计量最大的K值或满足公式的第一个局部最大值。实操心得在实际数模比赛中不要只依赖一种方法。我通常的做法是先画肘部图看趋势再用轮廓系数定量验证如果时间充裕再用间隙统计量交叉确认。最后结合你对问题背景的理解比如市场细分通常希望分成3-5类做出综合决策。在论文中将这三个图并列展示并给出你的选择理由是非常加分的做法。3.3 Matlab内置函数与高级技巧虽然自己写代码有助于理解但Matlab内置的kmeans函数经过了高度优化并提供了更多功能。% 基本用法 [idx, C, sumd, D] kmeans(X, K); % 重要可选参数 % Distance: 距离度量如 sqeuclidean(默认平方欧氏), cityblock(曼哈顿), cosine % Replicates: 重复运行次数。由于初始中心随机多次运行选择WCSS最小的一次能有效缓解局部最优问题。 % Options: 设置显示迭代信息、最大迭代次数等。 % Start: 指定初始中心点如 sample(随机样本), plus(K-Means算法更好的初始化) % 示例使用K-Means初始化重复运行10次选择最佳结果 opts statset(Display,final); [idx, C] kmeans(X, K, Distance,sqeuclidean, Replicates,10, Start,plus, Options,opts);K-Means初始化是一个非常重要的改进。它不再是完全随机选点而是让初始中心点尽可能相互远离从而大大增加找到全局最优解的概率并减少所需的迭代次数和重复运行次数。在比赛中只要条件允许务必使用Start,plus。4. DBSCAN算法深度解析与Matlab实战4.1 核心概念密度直达、密度可达与密度相连DBSCANDensity-Based Spatial Clustering of Applications with Noise定义簇的方式非常巧妙。它基于三个核心概念和两个参数Eps邻域以某个点为中心半径为Eps的圆形区域。MinPts定义核心点所需的最小邻域点数。核心点在其Eps邻域内至少包含MinPts个点包括自身的点。边界点不属于核心点但落在某个核心点的Eps邻域内的点。噪声点既不是核心点也不是边界点的点。基于此定义关系直接密度可达点p在核心点q的Eps邻域内则p从q直接密度可达。密度可达如果存在一条路径 p1, p2, ..., pn其中 p1q, pnp且 pi1 从 pi 直接密度可达则p从q密度可达。密度相连如果存在一个核心点o使得点p和点q都从o密度可达则p和q密度相连。一个簇就是所有密度相连的点的最大集合。所有不属于任何簇的点就是噪声。4.2 算法流程与手撕Matlab实现DBSCAN的算法流程比K-Means更富逻辑性标记所有点为核心点、边界点或噪声点。删除噪声点。为每个核心点如果它尚未被分配到某个簇则创建一个新簇并找出所有从该核心点密度可达的点将它们加入该簇。将每个边界点分配到一个与之关联的核心点所在的簇。下面是一个简化但核心逻辑完整的Matlab实现帮助你透彻理解function [labels, isNoise] myDBSCAN(X, eps, MinPts) % 自定义DBSCAN函数 % 输入 % X: 数据矩阵 (n x m) % eps: 邻域半径 % MinPts: 核心点最小邻域点数 % 输出 % labels: 簇标签0表示噪声点 (n x 1) % isNoise: 布尔向量标记是否为噪声点 [n, ~] size(X); labels zeros(n, 1); % 0 表示未访问或噪声 clusterId 0; isNoise false(n, 1); % 预计算距离矩阵对于大数据集此方法内存消耗大可改用范围查询 % 这里为清晰起见使用全矩阵实际应用应考虑优化 D pdist2(X, X); % 计算所有点对间的欧氏距离 for i 1:n if labels(i) ~ 0 % 已访问过 continue; end % 找出点i的eps邻域内的所有点索引 neighbors find(D(i,:) eps); if numel(neighbors) MinPts % 标记为噪声点 isNoise(i) true; labels(i) 0; % 确保为0 continue; end % 点i是核心点开始一个新的簇 clusterId clusterId 1; labels(i) clusterId; % 扩展这个簇遍历邻域点种子集合 seedSet neighbors; idx 1; while idx length(seedSet) point seedSet(idx); if labels(point) 0 % 如果是噪声点或未访问点 labels(point) clusterId; end if labels(point) ~ 0 % 如果已属于某个簇包括当前簇则跳过 idx idx 1; continue; end % 将当前点加入当前簇 labels(point) clusterId; % 找出当前点的邻域 pointNeighbors find(D(point,:) eps); % 如果当前点也是核心点则将其邻域加入种子集合以供后续扩展 if numel(pointNeighbors) MinPts seedSet [seedSet, pointNeighbors]; % 合并 % 注意这里可能导致重复但通过labels判断可避免重复处理 end idx idx 1; end end % 可视化仅适用于二维 if size(X, 2) 2 figure; gscatter(X(:,1), X(:,2), labels); title(sprintf(DBSCAN聚类结果 (eps%.2f, MinPts%d), eps, MinPts)); xlabel(特征1); ylabel(特征2); % 用黑色‘x’标记噪声点 noisePoints X(isNoise, :); if ~isempty(noisePoints) hold on; plot(noisePoints(:,1), noisePoints(:,2), kx, MarkerSize, 10, LineWidth, 2); hold off; legend(Location, best); end end end4.3 参数调优Eps和MinPts的确定方法DBSCAN不需要指定簇数但Eps和MinPts的选择至关重要甚至比K-Means的K更难确定。这里分享一个非常实用的经验方法——k-距离图法。对于数据集中的每个点计算它到第k个最近邻的距离然后对所有点的这个距离进行排序并绘图。这里的k通常就取MinPts或MinPts-1。图中距离的“拐点”或“膝盖”对应的距离通常是一个较好的Eps值。function suggestEps(X, MinPts) % 绘制k-距离图以辅助选择Eps [n, ~] size(X); kDist zeros(n, 1); % 计算每个点到其第MinPts近邻的距离 for i 1:n distances pdist2(X(i,:), X); % 点i到所有点的距离 sortedDist sort(distances); kDist(i) sortedDist(MinPts 1); % 1 因为距离包含自身(0) end sortedKDist sort(kDist, descend); figure; plot(1:n, sortedKDist, b-, LineWidth, 1.5); xlabel(按距离降序排列的点); ylabel(sprintf(%d-距离, MinPts)); title(k-距离图 (用于选择Eps)); grid on; % 可以尝试添加参考线来辅助观察“拐点” % 例如寻找曲率最大的点 % 这里仅作可视化手动选择拐点 end % 使用示例假设我们初步设定MinPts5 suggestEps(X, 5);运行上述代码后你会得到一条下降曲线。寻找曲线从“陡峭”突然变得“平缓”的那个点其对应的Y轴距离值就可以作为Eps的候选值。例如如果曲线在距离1.5处出现明显拐点那么可以尝试设置eps 1.5。关于MinPts的经验法则通常从较小的值开始尝试比如MinPts 2 * dimdim是数据维度。对于二维数据可以从4开始。MinPts太小会导致很多噪声点被误判为核心点形成大量小簇太大则可能将真正的核心点判为噪声导致簇被分裂。一个稳健的策略是先固定一个较小的MinPts如4用k-距离图确定Eps然后保持Eps不变逐渐增大MinPts观察聚类结果簇数和噪声比例的变化选择一个结果相对稳定的参数对。5. 实战案例基于合成数据与真实数据的对比分析5.1 生成与测试挑战不同形状的数据集理论讲得再多不如动手一试。我们生成三种典型结构的数据来考验两个算法%% 生成测试数据 rng(42); % 固定随机种子确保结果可复现 % 1. 球形簇K-Means的主场 data1 [randn(100,2)*0.5 [2,2]; randn(100,2)*0.5 [-2,2]; randn(100,2)*0.5 [0,-2]]; % 2. 环绕形簇DBSCAN的主场 theta linspace(0,2*pi,200); data2_circle [cos(theta), sin(theta)] * 2; data2_noise rand(50,2)*6 - 3; % 添加一些噪声点 data2 [data2_circle; data2_noise]; % 3. 密度不均的簇 data3_dense randn(150,2)*0.3 [1,1]; data3_sparse randn(50,2)*1.5 [4,4]; data3 [data3_dense; data3_sparse]; %% 应用K-Means (K3) figure(Position, [100,100,1200,400]); subplot(1,3,1); [idx1, C1] kmeans(data1, 3, Replicates, 5); gscatter(data1(:,1), data1(:,2), idx1); hold on; plot(C1(:,1), C1(:,2), kx, MarkerSize, 15, LineWidth, 3); title(K-Means on Spherical Clusters); legend off; subplot(1,3,2); [idx2, C2] kmeans(data2, 2, Replicates, 5); gscatter(data2(:,1), data2(:,2), idx2); hold on; plot(C2(:,1), C2(:,2), kx, MarkerSize, 15, LineWidth, 3); title(K-Means on Circle (Fails)); legend off; subplot(1,3,3); [idx3, C3] kmeans(data3, 2, Replicates, 5); gscatter(data3(:,1), data3(:,2), idx3); hold on; plot(C3(:,1), C3(:,2), kx, MarkerSize, 15, LineWidth, 3); title(K-Means on Density-Varying); legend off; %% 应用DBSCAN figure(Position, [100,100,1200,400]); % 为不同数据集设置不同参数需通过k-距离图大致确定 subplot(1,3,1); [labels1, noise1] myDBSCAN(data1, 0.8, 5); % 球形簇参数易调 title(sprintf(DBSCAN on Spherical (C%d), max(labels1))); subplot(1,3,2); [labels2, noise2] myDBSCAN(data2, 0.5, 5); % 环绕形小半径 title(sprintf(DBSCAN on Circle (C%d), max(labels2))); subplot(1,3,3); [labels3, noise3] myDBSCAN(data3, 0.5, 10); % 密度不均需调整MinPts title(sprintf(DBSCAN on Density-Varying (C%d), max(labels3)));运行这段代码你会直观地看到对于标准的球形簇两者都能很好处理。对于环绕形数据K-Means强行将其切成两个“扇形”完全错误而DBSCAN则完美地识别出了环状结构并将内部的噪声点分离出来。对于密度不均的数据K-Means倾向于按空间位置划分可能将一个密集大簇和稀疏小簇硬生生分开或合并DBSCAN通过密度阈值能更好地根据数据本身的分布进行划分。5.2 在真实数据上的应用鸢尾花数据集我们使用经典的鸢尾花数据集进行演示该数据集包含3类鸢尾花Setosa, Versicolor, Virginica每类50个样本每个样本4个特征花萼和花瓣的长宽。%% 加载并预处理鸢尾花数据集 load fisheriris; % Matlab自带数据集 X meas; % 150x4 的特征矩阵 trueLabels grp2idx(species); % 真实的类别标签 (1,2,3) % 数据可视化仅使用前两个特征 figure; gscatter(X(:,1), X(:,2), trueLabels); title(鸢尾花真实分布前两个特征); xlabel(花萼长度); ylabel(花萼宽度); % 使用PCA降维到2维以便可视化全部信息 [coeff, score, ~, ~, explained] pca(X); X_pca score(:,1:2); % 取前两个主成分保留了大部分方差 fprintf(前两个主成分解释了 %.2f%% 的方差。\n, sum(explained(1:2))); figure; gscatter(X_pca(:,1), X_pca(:,2), trueLabels); title(鸢尾花在PCA空间中的分布); xlabel(主成分1); ylabel(主成分2); %% 应用K-Means (我们知道有3类) [idx_km, C_km] kmeans(X, 3, Replicates, 10, Start,plus); % 在原始4维空间聚类 % 计算聚类评估指标调整兰德指数(ARI)和归一化互信息(NMI) % 需要下载外部函数或使用内置的混淆矩阵计算 cmat confusionmat(trueLabels, idx_km); % 简易计算纯度(Purity) [n, ~] size(cmat); purity sum(max(cmat, [], 2)) / n; fprintf(K-Means 聚类纯度: %.4f\n, purity); % 可视化K-Means在PCA空间的结果 figure; subplot(1,2,1); gscatter(X_pca(:,1), X_pca(:,2), trueLabels); title(真实标签); subplot(1,2,2); gscatter(X_pca(:,1), X_pca(:,2), idx_km); title(K-Means聚类结果); legend(Cluster 1, Cluster 2, Cluster 3); %% 应用DBSCAN % 首先确定参数在PCA空间中进行便于设定Eps suggestEps(X_pca, 8); % 假设MinPts8绘制k-距离图 % 观察图形假设我们选择Eps1.0 [labels_db, isNoise] myDBSCAN(X_pca, 1.0, 8); fprintf(DBSCAN发现了 %d 个簇以及 %d 个噪声点。\n, max(labels_db), sum(isNoise)); % 可视化DBSCAN结果 figure; gscatter(X_pca(:,1), X_pca(:,2), labels_db); hold on; if any(isNoise) noisePoints X_pca(isNoise, :); plot(noisePoints(:,1), noisePoints(:,2), kx, MarkerSize, 10, LineWidth, 2); legendStr arrayfun((x) sprintf(Cluster %d, x), 1:max(labels_db), UniformOutput, false); legend([legendStr, {Noise}], Location, best); else legend(Location, best); end title(sprintf(DBSCAN聚类结果 (Eps1.0, MinPts8))); hold off;在这个例子中由于鸢尾花数据集本身线性可分且类内紧凑K-Means表现会很好。DBSCAN可能会将一些边界点或轻微重叠的点判为噪声这取决于参数设置。这正好说明了没有万能的算法必须根据数据特性和分析目标来选择。6. 进阶话题与常见问题排坑指南6.1 高维数据与降维预处理“维度灾难”是聚类分析尤其是基于距离的算法如K-Means的噩梦。在成百上千维的空间中所有点之间的距离都趋于相等使得距离度量失效。解决方法主要有二特征选择使用方差过滤、相关性分析、基于模型的方法如树模型的特征重要性筛选出最具判别力的特征。特征提取/降维这是更常用的方法。PCA主成分分析最经典的线性降维方法寻找方差最大的方向。在Matlab中[coeff, score] pca(X)即可。t-SNE非常强大的非线性降维方法特别适合将高维数据降至2维或3维进行可视化它能更好地保留局部结构。可以使用tsne函数需要Statistics and Machine Learning Toolbox。UMAP另一种流行的非线性降维方法速度通常比t-SNE快且能更好地保留全局结构。Matlab中可通过File Exchange获取实现。重要提示降维通常应在聚类之前进行。但请注意如果你使用t-SNE或UMAP进行大幅降维如降到2维后再聚类那么聚类结果解释的是低维空间的结构而非原始高维空间的结构。在论文中需要明确说明这一点。一个更稳健的做法是先用PCA保留90%或95%的方差进行降维以去除噪声和冗余然后再进行聚类。6.2 聚类结果的评估与验证在没有真实标签的情况下如何评价聚类的好坏除了前文提到的轮廓系数、间隙统计量用于确定K还有以下内部评估指标戴维森堡丁指数衡量簇内距离与簇间距离之比值越小越好。Calinski-Harabasz指数簇间离散度与簇内离散度的比值值越大越好。 Matlab中可以使用evalclusters函数方便地计算多种指标。% 评估不同K值下的Calinski-Harabasz指数 eva_ch evalclusters(X, kmeans, CalinskiHarabasz, KList, [2:10]); plot(eva_ch);当你有部分真实标签或先验知识时可以进行外部评估调整兰德指数衡量两个划分聚类结果与真实标签的一致性取值范围[-1,1]1表示完全一致0表示随机划分。归一化互信息衡量两个划分共享的信息量值越大越好。 这些指标在Matlab中可能需要额外的函数包如File Exchange中的adjustedrandindex。6.3 典型问题排查与实战技巧K-Means结果不稳定每次运行都不一样原因随机初始化导致陷入局部最优。解决务必使用Replicates参数如设为10或20让算法多次运行并选择最优解。同时使用Start,plusK-Means初始化策略它能极大提升稳定性和收敛速度。DBSCAN把所有点都归为一个簇或全是噪声原因Eps参数设置过大或过小MinPts设置不合理。解决严格按照k-距离图法选择Eps。对于MinPts从2*dim开始尝试并通过观察聚类数目和噪声点的比例来微调。一个有用的技巧是先设定一个较小的Eps和较大的MinPts如果得到很多小簇和噪声则逐步增大Eps或减小MinPts反之亦然。数据量很大算法运行太慢对于K-Means使用Matlab内置的kmeans它已经过高度优化。确保使用向量化操作避免在循环中计算距离。对于超大数据集可以考虑使用OnlinePhase选项虽然已默认开启或使用小批量K-Means的变种。对于DBSCAN自定义实现中的全距离矩阵D pdist2(X,X)是内存和性能杀手。对于大规模数据必须使用空间索引如KD树、球树来加速邻域查询。可以搜索Matlab File Exchange中的“kdtree”或“range search”实现来替换距离矩阵计算部分。聚类结果如何解释和落地聚类完成后计算每个簇的中心对于K-Means或特征统计量均值、中位数、众数等。对比不同簇在这些特征上的差异为每个簇打上业务标签。例如在客户分群中你可能会发现“簇1高价值活跃用户”、“簇2低频低价值用户”、“簇3新用户”等。可视化是关键。除了散点图还可以使用平行坐标图来展示高维数据中各个簇的特征分布差异。% 平行坐标图示例 figure; parallelcoords(X, Group, idx_km, Quantile, 0.25); % idx_km是聚类标签 title(各簇特征平行坐标图显示中位数和四分位距);混合型数据数值分类如何聚类K-Means和DBSCAN通常处理数值数据。对于分类变量需要先进行编码如独热编码。但这样会改变距离的尺度。更专业的方法是使用能处理混合距离的算法如K-PrototypesK-Means的扩展或使用Gower距离计算相异矩阵再应用基于距离的聚类方法如层次聚类。在Matlab中可以自定义距离函数或寻找相关工具箱。聚类分析远不止于运行一个算法。从数据理解、预处理、算法选择、参数调优到结果解释每一步都需要思考和判断。我个人的体会是DBSCAN的探索性更强能帮你发现数据中隐藏的、不符合预设假设的结构而K-Means则在目标明确、需要快速获得一个划分时更加高效。在实际项目中我常常将两者结合使用先用DBSCAN探索数据了解大致结构和噪声水平再用其发现的结果去指导K-Means的K值选择甚至进行数据清洗剔除DBSCAN发现的噪声点后再进行K-Means聚类往往能得到更鲁棒、更可解释的结果。最后永远记住可视化你的数据、过程和结果图形给你的直觉有时比任何指标都更重要。