☰
kmeans聚类算法Matlab实现指南:原理、参数与常见坑
2026/10/9 11:38:32 网站建设 项目流程

简介:K均值(K-means)聚类算法的Matlab实现详解以单个docx文档形式打包,面向需要掌握无监督学习基础、利用Matlab开展数据聚类分析的初学者与开发者。文档从算法原理入手,逐步讲解kmeans函数的调用方式、基本迭代步骤、聚类结果可视化方法,并附有可直接运行的示例代码及逐行解析;示例以7个二维数据点演示K=2聚类过程,借助散点图不同颜色区分簇成员、叉号标记质心,直观展示索引矩阵与质心坐标的含义。内容同时梳理了K值选择困难、对异常值敏感、初始化影响等常见局限,并引入K-means++、Bisecting K-means与Kernel K-means等改进思路,为读者后续深入研究提供方向。资源包仅包含1个docx文件,大小15KB,下载后即可用Word或WPS打开阅读;目前已有441人学习,适合作为机器学习入门或Matlab数据分析实践的参考资料。

1. 一份 kmeans 聚类算法 Matlab 代码文档:为什么直接复制跑不通

从某个共享盘、实验存档或者同事转发的资料里拿到一份《kmeans聚类算法matlab代码.docx》时,大多数人的第一反应是打开文档、复制代码、点运行。实际结果通常是三种:直接报错——比如 Undefined function 'kmeans';能跑但画出来的聚类图完全不合预期;或者跑了很久却看不出结果哪里不对。原因不难理解:这类文档里贴的代码,要么是不同 Matlab 版本写的,要么省略了数据预处理,要么默认你已经装了统计工具箱,甚至作者自己都没把参数说明写全。这篇笔记不打算把文档内容重新誊一遍,而是把 kmeans 聚类算法在 Matlab 里的完整落地路径讲清楚。从造一份可靠的测试数据开始,到跑通内置 kmeans 函数,再到手写一个循环迭代版,最后落到参数设定、常见坑和结果验证。适合谁?准备做聚类实验的学生、要复现论文结果的研究者,以及被老板丢来一份祖传代码、得自己把它跑明白的从业者。

2. kmeans 聚类的原理与 Matlab 实现路径选择

2.1 迭代收敛的本质:分配与更新两步循环

kmeans 算法的核心不是复杂的公式推导,而是一个极其朴素的交替优化过程。给定 n 个样本和预设的簇数 K,算法不断重复两个动作:分配阶段,把每个样本划到离它最近的聚类中心;更新阶段,计算每个簇内所有样本的均值,作为新的聚类中心。这两个动作交替执行,直到聚类中心不再移动,或者目标函数的变化量小于阈值,或者迭代次数达到上限。目标函数通常写作所有样本到其所属簇中心的距离平方之和,也就是 SSE(Sum of Squared Errors),公式上可以表示为 J = Σ||x_i - μ_{c_i}||²。注意这里是「簇内距离平方和」,kmeans 的每一次迭代都在直接降低这个值。

这个交替过程有一个重要性质:每一步更新都在降低目标函数,因此理论上保证收敛。但它只保证收敛到局部极小值,不保证全局最优。也就是说,同一个数据集、同一个 K,换一组初始中心点,得到的结果可能完全不同。这不是算法写错了,而是 kmeans 的固有特性,具体来说,它属于坐标下降法的一种,对初始位置天然敏感。理解这一点,才能理解为什么后面要引入 Replicates 参数、为什么推荐用 kmeans++ 初始化、为什么不少文档里会写「多次运行取 SSE 最优」。

看代码文档之前还需要破除一个常见直觉:kmeans 假设簇的形状近似凸的、密度接近,并且对离群点相当敏感。如果你的业务数据不是球形分布,或者包含明显离群点,聚类结果会出现「某个簇只有一个点」或者「两个簇被硬切成两半」的现象。这类问题通常要靠预处理和调参解决,跟算法本身写得对不对关系不大。换句话说,拿到别人代码,先别急着怪代码,先问一句数据洗干净没有。

2.2 内置 kmeans 与手写实现的取舍标准

Matlab 里实现 kmeans 有两条主流路线:直接调用 Statistics and Machine Learning Toolbox 提供的 kmeans 函数,或者照着算法流程自己写一个循环版。这两条路的差别很明显,落在实际工作中就是工具箱依赖、运行速度、可定制性和学习成本之间的权衡。下表是筛选实现路径时的基本框架:

对比维度内置 kmeans 函数手写循环版
工具箱依赖需要 Statistics and Machine Learning Toolbox不需要,原生 Matlab 即可运行
初始化策略支持 'plus'、'sample'、'uniform'、自定义矩阵自己写 randperm 或 randi
空簇处理内置多种策略(例如 'singleton' 提示/重置)需要手动写空簇判断
运行速度经过编译器优化,适合较大数据通常依赖 for 循环,大数据下偏慢
可定制性只能在参数框架内调整可以改距离公式、加约束、输出中间迭代过程
学习价值黑匣子,适合工程使用能看清每一步发生了什么

拿到 《kmeans聚类算法matlab代码.docx》这类文档时,第一步是判断作者给的哪条路线。文档里如果出现[idx,C] = kmeans(X,K)这种调用格式,说明它依赖工具箱;如果出现大段的 for 循环加 mean 函数,说明是手写版。我平时做实验的习惯是:先手写一版把算法流程跑通,因为能看到迭代过程中的中间变量,容易定位问题;确认逻辑没问题之后,再换内置函数去跑正式数据,图省事也图速度。手写版跑通之后一定不要丢掉,它最大的价值是可以跟内置函数的结果做对比,用来验证你自己实现到底有没有写错。

另外必须提一句 MatLab 的向量化问题。手写版很容易写成「两层 for 循环挨个点算距离」,数据量小没问题,数据量一旦超过几万行,跑起来能让人怀疑人生。常见做法是把「每个样本到每个中心的距离」一次性计算出来,用 pdist2 或者矩阵运算完成,而不是逐点计算。这是 Matlab 手写聚类代码里最重要的性能优化点,后面写代码时会直接体现出来。

3. 把文档里的算法落地成可运行脚本:从测试数据到两种实现

3.1 先造一份确定性高的测试数据

拿到一份代码文档,第一步不是去跑它的原始数据,而是先构造一份可控的测试数据。可控的意思是:你知道数据大致有几簇、簇中心大概在哪里,跑完代码看一眼结果就知道算法对不对。如果一开始就上真实业务数据,结果错了你也分不清是代码的问题还是数据本身就没有簇结构。这里用随机数生成三簇二维高斯数据,每簇 100 个点,分布有明显重叠但中心可辨。

% 生成三簇二维高斯数据,用于后续聚类验证 rng(42); % 固定随机种子,保证每次运行生成的数据一致 X = [randn(100, 2) * 0.6 + [2, 2]; % 第一簇:中心在 (2, 2) randn(100, 2) * 0.5 + [-1, 3]; % 第二簇:中心在 (-1, 3) randn(100, 2) * 0.7 + [0, -2]]; % 第三簇:中心在 (0, -2)

这段代码的逻辑很直白:rng(42)是把随机数生成器的种子固定在 42,这样不管谁运行这段脚本,生成的randn序列完全一致,结果可复现,这是聚类实验里最容易被人忽略的第一步。randn(100,2)生成 100 行 2 列的标准正态分布随机数,均值 0 方差 1,然后分别乘以 0.6、0.5、0.7 调整每簇的离散程度,再加上不同的中心偏移[2,2]、[-1,3]、[0,-2]让三簇分布在平面上的不同位置。运行结束后X是一个 300 行 2 列的数据矩阵,每一行是一个样本,两列分别是 x 坐标和 y 坐标。后面所有代码都基于这份数据展开,你可以替换成自己的真实数据,但建议先把流程跑通再换。

3.2 用内置 kmeans 跑通第一版聚类

数据准备好了,紧接着跑第一版聚类。内置 kmeans 的调用形式非常简洁,但参数如果全部使用默认值,未必能得到理想的划分。第一次运行我至少会设置三个参数:Distance指定距离度量,Replicates指定重复次数,MaxIter指定最大迭代次数。

% 用内置函数跑 kmeans,得到每个样本的簇标签和最终聚类中心 [idx, C] = kmeans(X, 3, ... 'Distance', 'sqeuclidean', ... % 使用欧氏距离平方,结果等价于欧氏距离下的最近邻划分 'Replicates', 5, ... % 用 5 组不同的随机初始中心,返回 SSE 最小的一组 'MaxIter', 300); % 单次迭代最大循环 300 轮 % 散点图查看聚类效果:不同簇用不同颜色,中心用黑色叉号标出 figure; gscatter(X(:,1), X(:,2), idx); hold on; plot(C(:,1), C(:,2), 'kx', 'MarkerSize', 12, 'LineWidth', 2); legend('簇1', '簇2', '簇3', '聚类中心');

kmeans的第一个输出idx是 300 行 1 列的整数向量,取值 1 到 3,表示每个样本最终属于哪个簇;第二个输出C是 3 行 2 列的矩阵,每一行对应一个簇的最终中心坐标。sqeuclidean是欧氏距离的平方,在最近邻划分上取平方和开根号等价,所以实际效果与欧氏距离一致。Replicates设为 5 的含义是:算法会随机生成 5 组初始中心,分别完整迭代 5 次,最终返回组内 SSE 最小的那次结果,这是对抗局部最优最直接的手段。gscatter按idx分组绘制二维散点图,每个簇自动分配不同颜色;plot里用黑色叉号把聚类中心叠加上去。如果一切正常,你会看到三簇点被清晰地分开,黑叉落在每一簇的几何中心附近。

需要特别说明的是,Replicates的值不是越大越好。每一组重复都是完整的 kmeans 迭代过程,数据量大时耗时线性上涨。一般二维小样本设 5 到 10 足够,高维数据建议结合MaxIter一起考虑,后续第 4 章会展开参数取舍的细节。

3.3 按文档思路手写循环版 kmeans

内置函数跑通了,接下来做一件更见功夫的事:按算法最朴素的流程手写一版。很多 docx 文档里给的就是这类代码,因为它们不依赖工具箱,复制到任何一台装了 Matlab 的机器上都能跑。但手写版往往省略了空簇处理和收敛判断,导致实际运行时出现 NaN 或者死循环。这里给出一份相对完整的手写版本,关键位置都做了处理。

function [idx, C] = my_kmeans(X, K, maxIter) % 手写 kmeans:交替执行分配与更新两步 % 输入: % X - n x d 数据矩阵 % K - 簇个数 % maxIter - 最大迭代次数 % 输出: % idx - n x 1 每个样本所属簇编号 % C - K x d 聚类中心 [n, d] = size(X); % 初始化:从样本中随机选 K 个点作为初始中心 rng(42); % 固定种子,结果可复现 perm = randperm(n); C = X(perm(1:K), :); for iter = 1:maxIter % 分配阶段:计算每个样本到每个中心的距离,取最近中心的下标 D = pdist2(X, C, 'squaredeuclidean'); % n x K 距离矩阵 [~, idx] = min(D, [], 2); % 按行取最小值下标,得到簇标签 % 更新阶段:每个簇内样本的均值作为新中心 C_new = zeros(K, d); for k = 1:K if sum(idx == k) == 0 % 空簇处理:该簇没有分到任何样本,随机重置一个中心 C_new(k, :) = X(randi(n), :); else C_new(k, :) = mean(X(idx == k, :), 1); end end % 收敛判断:中心变化量小于阈值则提前退出 if norm(C_new - C, 'fro') < 1e-6 C = C_new; break; end C = C_new; end end

调用方式跟内置函数对齐:

% 调用手写 kmeans,并和内置函数结果做对比 [idx2, C2] = my_kmeans(X, 3, 100); % 对比两类结果的 SSE,验证手写实现是否正确 D1 = pdist2(X, C, 'squaredeuclidean'); sse_builtin = sum(min(D1, [], 2)); D2 = pdist2(X, C2, 'squaredeuclidean'); sse_my = sum(min(D2, [], 2)); fprintf('内置 kmeans SSE: %.4f\n手写 kmeans SSE: %.4f\n', sse_builtin, sse_my);

这段代码的逻辑分三步展开。pdist2(X, C, 'squaredeuclidean')计算每个样本到每个中心的欧氏距离平方,结果是一个 300 行 3 列的矩阵,第 i 行第 k 列的值表示样本 i 到中心 k 的距离。min(D, [], 2)沿行方向取最小值,第一个返回值是最小距离值,第二个返回值是下标,也就是样本所属的簇编号。更新阶段,mean(X(idx == k, :), 1)取出属于第 k 簇的所有样本,按列求均值,得到新的中心坐标。空簇判断是手写版最容易漏掉的关键点:如果某个簇在第一次迭代就一个样本都没分到,mean会计算出 NaN,并且这个 NaN 会一路传播到后续所有迭代,最终中心坐标全是 NaN,绘图直接失败。这里的处理方式是随机从数据里选一个点作为新中心,简单有效。收敛判断用norm(C_new - C, 'fro')计算新旧中心的 Frobenius 范数,也就是所有坐标差值的平方和再开方,小于 1e-6 就认为中心已经稳定,提前跳出循环。

末尾给出了一组对比代码,把内置函数和手写版的 SSE 都算出来。SSE 更小的一方说明该次迭代找到了更好的局部最优解,但如果两者相差不到 1%,基本可以确认手写逻辑没问题。这一步是对照实验,价值在于建立信心:你的手写实现跟官方实现行为一致,接下来无论怎么改距离公式、加约束,都有了一个可信的基线。

4. 影响聚类结果的四个关键参数与 K 值怎么定

4.1 距离度量:sqeuclidean、cityblock、cosine 何时选

kmeans 里「距离」的定义直接决定了样本被划分到哪个簇。内置 kmeans 的Distance参数提供了多种选择,最常用的有三个:sqeuclidean、cityblock和cosine。sqeuclidean是默认值,计算欧氏距离的平方,适合数值型连续特征、簇形状接近球形分布的数据,绝大多数场景用它就没问题。cityblock计算曼哈顿距离,即各坐标差值的绝对值之和,它对个别维度上的极端值没有那么敏感,在特征维度彼此独立且存在离群点的情况下,有时能得到比欧氏距离更稳健的划分。cosine计算的是余弦距离,只关注方向、不关注模长,专门用于文本向量、用户行为占比这类高维稀疏数据。

选择距离度量不能靠拍脑袋,应该先想清楚你的特征数值代表什么。比如做用户分群,特征是「最近 30 天登录次数」和「累计消费金额」,两者都是绝对数值,用sqeuclidean合理;如果特征是「各品类消费占比」,加起来为 1,这时两个用户一个消费 100 块一个消费 10000 块,占比结构完全一样,用欧氏距离会把它们分开,而用cosine会认为它们高度相似。实战经验是:拿不准的时候,把三种距离都跑一遍,分别计算轮廓系数,选平均值高的那一种,而不是凭感觉定。手写版里改距离度量也很简单,把pdist2的距离参数从'squaredeuclidean'换成'cityblock'或'cosine'即可,但注意cosine距离下中心点不一定是均值向量,更新步骤需要考虑归一化,这里不展开。

4.2 初始中心与 Replicates:kmeans++ 为什么能减少翻车

初始中心的选取对 kmeans 影响极大,这一点在第 2 章讲局部最优时已经提到。Matlab 内置函数里Start参数控制初始化方式,最值得用的是'plus',对应 kmeans++ 算法。kmeans++ 的思路是:第一个中心从样本中随机选,之后每选一个新中心时,距离已有中心越远的样本被选中的概率越高。这样做能让初始中心尽量分散,降低「多个中心扎堆在同一簇里」的概率,后续迭代也更容易收敛到更好的局部最优解。默认的'sample'是完全随机选 K 个样本,简单但质量不稳定。

% 使用 kmeans++ 初始化,并重复多次取最优 [idx_plus, C_plus] = kmeans(X, 3, ... 'Start', 'plus', ... % kmeans++ 初始化,中心更分散 'Replicates', 10, ... % 重复 10 次,降低随机性影响 'Display', 'final'); % 每次运行结束后输出 SSE 值

Replicates的含义是完整的「初始化 + 迭代」重复次数,每次使用不同的随机初始中心独立运行,最终返回 SSE 最小的一次。我在实际项目中一般先设 5,看数据量和稳定性再往上调,最多到 20。Display, 'final' 会在命令行打印每次运行的 SSE,方便肉眼判断结果稳定程度。如果 10 次运行跑出来的 SSE 几乎不变,说明数据本身簇结构清晰;如果每次相差很大,说明初始中心对结果影响显著,这时真正的解决方向是数据预处理、增大 Replicates 或者换距离度量。

顺带说明一个操作细节:Start和Replicates可以同时使用,Start决定每一次重复的初始化方式,Replicates决定跑多少轮。两者叠加之后,随机性主要靠 Replicates 兜底,kmeans++ 负责让每一轮的起点都相对合理。

4.3 用肘部法则和轮廓系数确定 K 值

K 值是指定的而不是算法算出来的,这是 kmeans 最被人诟病的一点,但也是实际项目里最需要认真对待的参数。确定 K 值最常见的方法是肘部法则:把 K 从 1 取到 8,分别计算总 SSE,画成折线图。SSE 随着 K 增大必然下降,但下降幅度会逐渐变小,曲线出现明显拐点的位置,就是「再加一个簇也解释不了太多新方差」的临界点,这个拐点俗称肘部。

% 用肘部法则看不同 K 下 SSE 的下降趋势 K_list = 1:8; sse = zeros(size(K_list)); for i = 1:length(K_list) [~, C] = kmeans(X, K_list(i), 'Replicates', 3); % 每个 K 重复 3 次 D = pdist2(X, C, 'squaredeuclidean'); sse(i) = sum(min(D, [], 2)); % 所有样本到最近中心的距离平方和 end figure; plot(K_list, sse, '-o', 'LineWidth', 1.5); xlabel('K'); ylabel('SSE'); grid on; title('肘部法则:SSE 随 K 的变化');

代码里的逻辑是每个 K 都跑一次 kmeans,用pdist2计算样本到最终中心的距离矩阵,min(D,[],2)取每个样本到最近中心的距离,最后求和得到该 K 下的总 SSE。注意循环里 K 的取值一般从 1 开始,因为 K=1 时 SSE 等于所有样本到全局均值的距离平方和,它是一个基准值。

肘部法则的局限在于拐点不一定明显,特别是真实数据往往没有清晰簇结构,曲线平滑下降,难以判断。这时候用轮廓系数交叉验证。轮廓系数silhouette综合评估每个样本的簇内紧密度和簇间分离度,取值在 [-1, 1] 之间,越接近 1 说明该样本簇内很近、簇间很远;整体值取平均,平均值越高说明聚类结构越清晰。

% 计算不同 K 下的平均轮廓系数,数值越大说明聚类质量越好 K_list = 2:8; avg_s = zeros(size(K_list)); for i = 1:length(K_list) [idx_k, ~] = kmeans(X, K_list(i), 'Replicates', 5); avg_s(i) = mean(silhouette(X, idx_k)); end figure; plot(K_list, avg_s, '-s', 'LineWidth', 1.5); xlabel('K'); ylabel('平均轮廓系数'); grid on;

平均轮廓系数通常会在某个 K 值达到峰值,这个峰值对应的 K 就是数据里最自然的簇数。注意轮廓系数对应 K=1 没有意义,所以循环从 2 开始。肘部法则看趋势,轮廓系数看峰值,两个方法结合使用,比单看任何一个都可靠。如果两者给出的 K 不一致,优先信轮廓系数,因为它更直接反映的是「簇内紧、簇间散」这个聚类目标本身。

5. kmeans 在 Matlab 里的常见问题与排查清单

5.1 空簇导致中心变 NaN

现象:手写版 kmeans 运行过程中,聚类中心C出现 NaN,散点图直接画不出点,或者内置函数报错提示Empty cluster created。原因:某一轮分配阶段,某个簇没有分到任何样本。这时mean(X(idx == k, :), 1)的输入是空矩阵,Matlab 计算空矩阵的均值返回 NaN,而这个 NaN 会替换掉原来的中心,后续所有距离计算全部变成 NaN,彻底崩盘。空簇通常发生在数据分布极度不均匀、初始中心选在了样本稀疏区域、或者 K 值大于数据本身的结构数量时。解决:手写版在更新阶段做一次计数判断,sum(idx == k) == 0时随机重置该中心;内置函数可以通过Start, 'plus' 降低空簇概率,同时结合Replicates多跑几组,取 SSE 最优的结果。数据层面的预处理同样重要,先用zscore标准化、剔除明显离群的孤立点,能从根本上减少空簇的出现。

5.2 忽略标准化,量纲大的特征主导聚类

现象:数据有两个以上特征,其中一个特征数值范围远大于其他特征(比如年龄 20 到 40,收入 5000 到 30000),聚类结果几乎只按照收入这一个特征划分,年龄完全不起作用。原因:欧氏距离计算中,数值大的特征贡献了绝大部分距离值,小量纲特征被淹没了。kmeans 本身没有内置标准化逻辑,聚类前是否做标准化完全由使用者把握。解决:聚类之前对特征做 zscore 标准化:

% 标准化所有特征:每个特征减去均值,除以标准差 X_std = zscore(X); [idx_z, C_z] = kmeans(X_std, 3, 'Replicates', 5);

注意zscore对每一列独立操作,标准化之后每个特征的均值为 0、标准差为 1。标准化后的聚类中心C_z是在标准化空间里的坐标,如果要用原始尺度解释中心点的业务含义,需要反标准化回去,C_raw = C_z .* std(X) + mean(X),这里的std(X)和mean(X)必须在标准化之前保存下来。还有一点容易被忽略:如果做标准化,聚类之前能画出的「两个特征散点图」只能看到原始分布的拉伸,标准化之后再做gscatter才更能反映真实聚类边界。那是在标准化的四象限坐标系里观察,千万别把两幅图混为一谈。

5.3 没有安装统计工具箱导致 kmeans 未定义

现象:运行内置函数时直接报错Undefined function 'kmeans' for input arguments of type 'double'。原因:当前 Matlab 环境没有安装 Statistics and Machine Learning Toolbox。kmeans 是统计工具箱的函数,不属于 Matlab 基础模块。这种情况在换了机器、用了精简版安装包的场景下经常出现,也是 docx 文档里作者没有交代环境导致的高频踩坑点。解决:先确认工具箱是否存在,运行ver('stats'),如果返回空或报错,说明确实缺失。没有工具箱时两条路:一是手写循环版,第 3.3 节给出的my_kmeans只依赖基础函数pdist2、mean、min、randperm,这些都在基础模块里,可以直接使用;二是确认公司或学校是否有网络许可,通过 Add-On Explorer 补装工具箱。还有一类文档里的老代码写的是'EmptyAction', 'singleton'或'EmptyAction', 'error',这些参数在新版 Matlab 里部分已经改名或移除,版本迁移时同样会报参数错误。处理方法是查当前版本的 kmeans 文档,把老参数替换成新写法,或者干脆改用手写版绕开版本差异。

5.4 同样代码两次运行结果不一样

现象:脚本完全一致,第一次运行和第二次运行得到的簇标签或者聚类中心不同,甚至簇标签编号整体互换(原来标为 1 的变成 2)。原因:kmeans 的初始中心是随机选择,随机性必然带来结果差异。簇标签编号的「互换」不是错误,因为 kmeans 本身对簇的编号没有任何顺序含义,同一份数据跑两次,结果在数学上等价,只是编号对不上。真正需要警惕的是聚类中心位置明显变化,比如某一簇中心从 (2, 2) 跳到 (5, 5),说明算法陷入了完全不同的局部最优。解决:在调用 kmeans 之前固定随机种子,让结果可复现:

% 固定随机种子,保证后续 kmeans 运行结果可复现 rng(123); [idx, C] = kmeans(X, 3, 'Replicates', 5);

rng(123)之后,内置 kmeans 内部的随机数流也被固定,多次运行结果完全一致。但要注意两点:第一,固定种子不等于掩盖问题,如果多次运行 SSE 差异很大,说明数据簇结构不清晰,应该回到数据预处理或者 K 值选择上;第二,固定种子在不同版本的 Matlab 之间并不保证一致,因为内置函数的随机数生成内部实现可能调整。所以论文或者报告里写实验条件时,必须把 Matlab 版本、随机种子、Replicates 三者一起写清楚,缺一不可。这也是玄学里最现实的坑:你复现不了别人的结果,可能别人的代码本来就没法跨版本复现。

6. 用轮廓图与降维投影验证聚类质量:一个收尾技巧

代码跑通了、坑也填了,还有一个问题容易被人忽略:聚类结果到底好不好,不能只靠肉眼在散点图上看。二维数据可以画图直接看,四维五维甚至更高维的数据看不见,这时候需要两个验证工具:轮廓图和主成分投影。

% 轮廓图:每个样本的轮廓值,负值说明可能被分错了簇 [idx_final, ~] = kmeans(X_std, 3, 'Replicates', 5); figure; silhouette(X_std, idx_final);

轮廓图输出一个横向条形图,每个样本一条杠,颜色按簇区分。大多数条形的长度越长越好,如果出现明显的负值条形,说明该样本可能更适合划分到相邻的簇。负值条形的比例超过 10%,基本可以判断 K 值偏大或者距离度量不匹配。轮廓图比单一的平均轮廓系数更细致,它能指出是哪些样本在拖后腿。

高维数据还可以用 PCA 把维度压到二维,再按聚类标签着色观察分离情况:

% PCA 降维后观察簇的分离情况,辅助判断聚类是否有效 [coeff, score, ~, ~, explained] = pca(X_std); figure; gscatter(score(:,1), score(:,2), idx_final); xlabel(['PC1 (' num2str(explained(1), '%.1f') '%)']); ylabel(['PC2 (' num2str(explained(2), '%.1f') '%)']);

这段代码把标准化后的数据投影到主成分空间,score的第一列和第二列是前两个主成分得分,explained输出每个主成分解释的方差比例。如果前两个主成分能解释 70% 以上的方差,且投影后的簇边界清晰,聚类结果基本可信。如果投影后簇混成一团,也不一定代表聚类失败,可能是前两个主成分只解释了少部分方差,信息损失太大,这种情况可以尝试 t-SNE 做非线性降维再看。

做聚类实验这些年,我最大的教训是:任何聚类脚本的第一行必须先固定随机种子,任何结果图旁边都要同时标注 SSE 和平均轮廓系数,任何对比实验都必须写明距离度量和 Replicates 的取值。以前我拿到一份代码文档,复制运行,看到一张还算正常的散点图就直接写进报告,结果换一台机器重跑,簇标签完全变样,整份报表作废重来。后来养成上面这三个习惯,再没出过这种问题。希望帮到你。

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

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

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

立即咨询