简介:一份面向MATLAB初学者与数据分析人员的K-means聚类算法实现详解文档,系统梳理无监督聚类的基本原理、迭代步骤与距离度量方式,并选用内置kmeans函数完成示例代码演示。文中以二维数据集为例,展示初始化聚类中心、分配数据点、更新质心直至收敛的完整流程,同时逐段解释idx索引与centroids质心矩阵的含义,方便读者直接套用到自己的数据任务中。包体仅含1个docx文件,大小约15KB,内容精简但覆盖算法核心、代码实现、结果可视化及参数调整思路,适合快速查阅与入门实践。文档还讨论了K-means对异常值敏感、依赖预设聚类数K等局限,并补充K-means++、Bisecting K-means、Kernel K-means等改进方向,有助于理解算法的适用边界与选型依据。目前已有442人学习下载,可作为课堂实验、课程设计或项目预研的参考笔记。
1. kmeans聚类算法matlab代码:这篇笔记能帮你把聚类从跑通到跑对
先把结论放在前面:kmeans 聚类算法是数据分析和信号处理里最常被点名的无监督算法,而 MATLAB 是实现它最省事的语言之一。搜索"kmeans聚类算法matlab代码.docx"这类文档的人,多半是想找一份能直接运行的完整实现,里面通常包含数据生成、聚类迭代、可视化画图和结果评估几块内容。问题在于网上流传的版本质量参差不齐,有的还在用多年前已经废弃的绘图语法,有的跑完根本没收敛就出了结果。这篇笔记把我在实际项目里反复验证过的 MATLAB 代码、参数设置和踩坑点整理出来,新手可以直接照着写,熟手可以拿它对照检查自己方案的边界,尤其适合课程设计、论文实验和入门数据分析这几类场景。
2. K-means 原理与 MATLAB 内建函数:先搞清楚它在算什么
2.1 分配与更新两步迭代:K-means 的数学逻辑
K-means 做的事情可以压缩成一句话:给定 k 个初始中心点,把每个样本划到离它最近的中心所代表的簇,然后重新计算每个簇的质心作为新中心,重复直到中心几乎不再移动。整个过程只有两个关键步骤——分配(Assignment)和更新(Update),两个步骤交替执行,直到中心变化量小于阈值或者迭代次数耗尽。
数学上,"最近"默认用欧氏距离的平方来衡量。对样本 x_i 和中心 c_j,距离写成 d_ij = ||x_i - c_j||²。分配阶段让每个样本挑选使这个距离最小的中心;更新阶段把落入第 j 个簇的所有样本取算术平均,得到新中心 c_j' = (1/|S_j|) Σ x_i,其中 S_j 是第 j 个簇的样本集合。这两步看起来简单,但背后有一个容易被忽略的结论:K-means 最小化的目标函数是簇内平方和 WCSS = Σ_j Σ ||x_i - c_j||²,这个目标对 c_j 求导令其为零,解恰好就是簇内样本均值。也就是说,"均值更新"不是拍脑袋选的操作,而是目标函数在欧氏距离平方约束下的自然结论。
这个推导决定了代码里的一个关键选择:如果你把距离换成曼哈顿距离,最优中心就变成中位数而不是均值,算法就从 K-means 变成了 K-medoids,更新那一步的逻辑要整体改写。很多人拿着 K-means 的代码去套曼哈顿距离,结果收敛慢且效果差,原因就在这里。另一个必须接受的事实是:K-means 的目标函数是非凸的,它只能保证收敛到局部最优,不能保证全局最优。这意味着初始化很重要,同一份数据、同一个 K,初始中心不同,最终结果就可能不同。这不是代码 bug,而是算法的结构属性,后面所有关于"Replicates"和"kmeans++"的讨论都源于这个特性。
2.2 MATLAB 内建 kmeans 函数:参数解读与第一个可运行例子
如果你只想要结果,不关心迭代细节,统计与机器学习工具箱里的内建 kmeans 函数是最快路径。最朴素的调用长这样:
% 生成两类二维数据,每类 100 个样本 rng(2024); X = [randn(100,2) + 2; randn(100,2) - 2]; % 一行调用完成聚类 [idx, centers] = kmeans(X, 2);运行后 idx 是 n×1 的簇编号向量,centers 是 2×2 的中心坐标矩阵。这个最简单的调用背后潜伏着一整套默认参数:距离度量是 sqeuclidean、初始中心从样本里随机挑、最大迭代次数 100、空簇处理方式是直接报错。默认配置应付演示够用,放到真实数据上通常不够,所以我在工程代码里习惯把关键参数显式写出来:
rng(2024); % 固定随机种子,保证结果可复现 [idx, centers, sumd, d] = kmeans(X, 2, ... 'Distance', 'sqeuclidean', ... 'MaxIter', 200, ... 'Replicates', 5, ... 'Display', 'final');这四个参数值得逐个说明。Distance 决定距离度量,除了 sqeuclidean,常用备选有 cityblock、cosine、correlation 和 hamming。cosine 在文本向量上效果不错,correlation 适合基因表达这类带批次效应的数据,hamming 只能用于二进制特征。MaxIter 是单次执行的迭代上限,默认 100 在样本量几万以上时经常不够,我一般设 200 起步。Replicates 是防局部最优的关键参数,它让 MATLAB 从不同初始中心出发重复跑多次,最后返回 WCSS 最小的一次;建议至少设 3,数据复杂时设 5 到 10。Display 设为 final 会在命令行打印迭代与收敛信息,排查问题时非常有用。
输出参数里,sumd 是每个簇的簇内距离总和,长度等于 k;d 是每个样本到所有中心的距离矩阵,维度是 n×k。用 sum(sumd) 就能得到 WCSS,后面选 K 值时全靠它。内建函数封装得很好,但副作用是把迭代过程完全藏起来了,很多人跑完就完,结果不对也说不清是哪一步出了问题。把原理过完之后,下一章我们从头写一个可见、可改、可记录过程的版本,把整个迭代彻底摊开。
3. 手写 kmeans 聚类算法 MATLAB 代码:从零实现一个可复现版本
3.1 数据准备与 Z-score 归一化
手写之前先处理数据。K-means 对特征尺度极其敏感,因为距离计算把每个维度等权相加。假如第一维是年龄(20 到 60 的数值),第二维是收入(5000 到 50000 的数值),距离里收入维度的差值会完全吞掉年龄维度,聚类边界几乎只看收入。这种情况在用户分群、客户画像项目里经常出现,解决手段就是归一化。我一般在聚类前做 Z-score 归一化,让每个特征变成均值为 0、标准差为 1 的分布:
% 原始数据 X0,n 行 d 列 mu = mean(X0, 1); % 每个特征的均值 sigma = std(X0, 0, 1); % 每个特征的标准差,第二个参数 0 表示除以 n-1 X = (X0 - mu) ./ sigma; % 归一化后的聚类输入std 的第二个参数 0 和 1 的区别是分母用 n-1 还是 n,对归一化结果影响很小,但如果要把代码嵌进正式实验流程,统一写法能避免不同脚本之间对不齐。MATLAB R2018a 之后有 normalize 函数,默认就做 Z-score,直接写成 X = normalize(X0) 更简洁。
这里有一个隐藏的坑必须强调:归一化用的 mu 和 sigma 要在聚类前算好并保存。后面来了新样本想预测它属于哪个簇,必须用原来这一组 mu 和 sigma 变换,而不是拿新数据重新计算。很多人在这一步翻车,导致训练和预测的输入分布不一致,聚类结果完全对不上。我习惯把 mu 和 sigma 连同聚类中心一起存进 .mat 文件,避免事后找不到。
注意:归一化参数必须从训练数据计算并保存,新样本预测时复用同一组 mu 和 sigma,绝不能重新计算。
3.2 矩阵化距离计算:核心迭代步骤
手写 K-means 最容易掉进双重 for 循环的坑,数据量上万之后速度慢到让人怀疑人生。MATLAB 的强项是矩阵运算,所以要用矩阵化写法算距离,核心是一个代数恒等式:
% 距离矩阵 D(i,j) = ||X(i,:) - centers(j,:)||^2 % = sum(X(i,:).^2) + sum(centers(j,:).^2) - 2 * X(i,:) * centers(j,:)' X2 = sum(X.^2, 2); % n×1,每个样本的平方和 C2 = sum(centers.^2, 2); % k×1,每个中心的平方和 D = X2 + C2' - 2 * (X * centers'); % n×k,隐式展开相加 [~, idx] = min(D, [], 2); % 每个样本取最近中心的下标这段代码里 X2 是 n×1 列向量,C2' 是 1×k 行向量,MATLAB 从 R2016b 开始支持隐式展开,两者相加会自动扩展成 n×k 矩阵。D 的第 i 行第 j 列就是样本 i 到中心 j 的欧氏距离平方。注意这里没有开根号,因为开根号是单调递增函数,不影响 min 比较的结果,却省掉了一整层逐元素计算的耗时。这个展开式比 pdist2 更快,是手写版本性能优化的核心。
分配完成之后是更新中心。更新这步的向量化程度取决于写法,常见做法是循环遍历 k 个簇,对每个簇取均值:
% 更新中心:每个簇内样本的均值 centers_new = zeros(k, d); for j = 1:k members = X(idx == j, :); if isempty(members) centers_new(j, :) = centers(j, :); % 空簇保留旧中心,避免崩溃 else centers_new(j, :) = mean(members, 1); end end这个循环次数是 k,通常不超过几十,开销可以忽略。真正耗时的是前面的距离矩阵,所以性能优化重点始终在 D 的计算上。空簇处理是手写版本必须补的防御逻辑,真实数据里某个簇一个样本都没有的情况并不罕见,内建函数默认会直接报错,手写版本要自己兜住。这里选择保留旧中心,也可以像内建函数的 singleton 选项那样用离它最近的样本重置中心,两种策略在第 5 章会继续讨论。
3.3 完整可运行的 my_kmeans 函数封装
把初始化、迭代、收敛判断组合起来,就能得到一个完整、可复现、带过程记录的版本。我额外输出一个 history 向量,记录每一轮迭代的 WCSS,用来确认算法是否真的单调收敛:
function [idx, centers, history] = my_kmeans(X, k, max_iter, tol) % 手写 kmeans 聚类算法(含 kmeans++ 初始化) % 输入: % X - n×d 数据矩阵,建议预先做 Z-score 归一化 % k - 簇数量 % max_iter - 最大迭代次数,默认 200 % tol - 中心变化的容忍度,默认 1e-6 % 输出: % idx - n×1 簇标签 % centers - k×d 最终中心 % history - 每轮 WCSS,用于观察收敛过程 if nargin < 3, max_iter = 200; end if nargin < 4, tol = 1e-6; end [n, d] = size(X); % ---- kmeans++ 初始化:让初始中心尽量分散 ---- centers = zeros(k, d); centers(1, :) = X(randi(n), :); for j = 2:k D2 = pdist2(X, centers(1:j-1, :)).^2; minD2 = min(D2, [], 2); if sum(minD2) < eps centers(j, :) = X(randi(n), :); % 所有点重合时的兜底 else prob = minD2 / sum(minD2); r = rand(); next_idx = find(cumsum(prob) >= r, 1); centers(j, :) = X(next_idx, :); end end history = nan(max_iter, 1); for iter = 1:max_iter % ---- 分配阶段:矩阵化距离 ---- X2 = sum(X.^2, 2); C2 = sum(centers.^2, 2); D = X2 + C2' - 2 * (X * centers'); D = max(D, 0); % 清掉浮点误差产生的极小负值 [~, idx] = min(D, [], 2); % ---- 记录当前 WCSS ---- history(iter) = sum(min(D, [], 2)); % ---- 更新阶段 ---- centers_new = zeros(k, d); for j = 1:k members = X(idx == j, :); if isempty(members) centers_new(j, :) = centers(j, :); else centers_new(j, :) = mean(members, 1); end end % ---- 收敛判断:中心矩阵的 Frobenius 范数变化 ---- if norm(centers_new - centers, 'fro') < tol centers = centers_new; history = history(1:iter); return; end centers = centers_new; end history = history(~isnan(history)); end这段代码有四个细节需要展开解释。
第一,初始化用的是 kmeans++ 而不是纯随机。纯随机初始化容易让多个初始中心挤在同一片密集区域,导致收敛慢且容易陷进差的局部最优。kmeans++ 的思路是第一个中心随机选,之后每个新中心按"到已有中心最近距离的平方"加权采样,距离越远的点被选中的概率越大,这样初始中心天然分散。这也是 MATLAB 内建函数和常见机器学习库的默认策略。
第二,pdist2 只用在初始化阶段,因为它需要显式计算样本到已有中心的距离,在大数据上开销高,但初始化只执行 k-1 次,可以接受。主迭代里必须用手写的距离展开式,那个 n×k 的矩阵乘法是高度优化的,明显快于逐元素展开。
第三,D = max(D, 0) 是数值稳定性处理。浮点运算里展开式可能产生 -1e-14 这样的微小负值,虽然不影响 min 找索引,但不归零会让 history 里的 WCSS 出现微小抖动,画收敛曲线时看起来不干净。
第四,收敛判断用 Frobenius 范数,norm(centers_new - centers, 'fro') 等价于把中心矩阵展平后求二范数,综合反映所有中心的总体位移量。阈值 1e-6 在归一化数据上够用,如果数据没有归一化,这个阈值可能要放宽到 1e-4,否则会出现反复迭代到 max_iter 才退出的情况。
用这个函数跑一遍第 2 章的数据,对比内建 kmeans 的结果,你会发现两者最终中心非常接近,差异主要来自初始化和局部最优。手写版本最大的价值不是替代内建函数,而是让你在结果异常时能把迭代过程调出来看——到底是没收敛、初始化差,还是 K 值不对,一眼就能分辨。
4. K 值与参数调优:从肘部法则到 Silhouette 验证
4.1 肘部法则:读 WCSS 折线图的正确姿势
K-means 最大的玄学问题就是"K 到底取几"。肘部法则是最朴素也最常用的办法:把 K 从 1 试到某个上限,记录每个 K 对应的 WCSS,画折线图找"肘部"——也就是 WCSS 下降速度突然变缓的位置。这个位置对应的 K 被视作数据自然簇数的估计:
K_list = 1:10; wcss = zeros(length(K_list), 1); for t = 1:length(K_list) k_tmp = K_list(t); [~, ~, sumd] = kmeans(X, k_tmp, 'Replicates', 5, 'MaxIter', 200); wcss(t) = sum(sumd); end figure; plot(K_list, wcss, 'bo-', 'LineWidth', 1.5); xlabel('K'); ylabel('WCSS'); title('Elbow Method for Optimal K'); grid on;读图的时候不要指望曲线给一个完美的直角拐点。真实数据的 WCSS 曲线几乎总是平滑递减的,所谓肘部其实是一个"从陡变缓"的过渡区间。比如 K=2 到 K=3 时 WCSS 掉了 40%,K=3 到 K=4 只掉了 8%,之后每增加一个 K 变化都小于 5%,那 K=3 就是合理的肘部。如果曲线从头到尾都接近直线,说明数据本身没有明显的簇结构,这时候继续纠结 K 意义不大,应该回到特征工程或者换密度聚类。
提示:每个 K 值的 WCSS 都应该从多次重复里取最小,否则初始化差的那一次运行会在折线图上制造假肘部。
这里有一个必须强调的操作细节:每个 K 值都要设 Replicates,或者多跑几次取最小 WCSS。原因很直接,K-means 的结果依赖初始化,某一次初始化很差时 WCSS 会异常偏高,折线图上就会出现一个根本不存在的拐点。Replicates=3 到 5 是性价比比较高的区间,数据量大时可以适当减少重复次数,但不要完全不设。
4.2 Silhouette 系数:给聚类结果打一个可分性分数
肘部法则只看类内紧凑度,会漏掉一种典型情况:两个簇中心离得很近,重叠区域大,WCSS 虽然小,簇之间却几乎没有可分性。Silhouette 系数(轮廓系数)同时考虑类内距离和最近邻簇的距离,对"这个 K 到底行不行"更有发言权:
% 对单个 K 值计算轮廓系数 [idx, ~] = kmeans(X, 4, 'Replicates', 10); [s, h] = silhouette(X, idx); fprintf('K=4 平均轮廓系数: %.4f\n', mean(s)); % 批量比较 K=2 到 K=8 K_list = 2:8; sil_scores = zeros(length(K_list), 1); for t = 1:length(K_list) idx_tmp = kmeans(X, K_list(t), 'Replicates', 5); sil_tmp = silhouette(X, idx_tmp); sil_scores(t) = mean(sil_tmp); end轮廓系数的取值范围是 -1 到 1。一般认为 0.7 以上是强聚类结构,0.5 到 0.7 是合理范围,0.25 到 0.5 说明簇结构偏弱,低于 0.25 说明数据基本没有可分的簇状结构,这时候硬调 K 值不如考虑换 DBSCAN 或高斯混合模型。
注意我上面只打印了 mean(s),但实际分析时一定要看图。silhouette 函数返回的 h 是图形句柄,画出来是每个样本一条横杠的轮廓图。如果图里出现大量负值横杠,说明这些样本很可能被分到了错误的簇;负值样本集中出现在某两个簇之间,还说明这两个簇边界重叠严重,与其纠结 K,不如回头检查特征选择。
肘部法则和轮廓系数选出的 K 不一致时,我一般以轮廓系数为主。原因很简单:肘部法则只回答"WCSS 下降变慢了",轮廓系数回答"簇是否真的分得开",后者和业务上"分群是否有意义"更相关。某个真实项目里,数据用肘部法则看像是 K=4,轮廓系数却显示 K=4 只有 0.52,而 K=2 有 0.71,最后业务方认定 K=2 的两个群体更容易给出差异化运营策略。这个例子说明,算法给出的候选 K 必须和业务判断交叉验证,而不是无脑选肘部。
4.3 正式实验里值得固定的几个参数
除了 K 值,下面这些参数我会在项目开始阶段按统一默认值跑一遍,再根据数据规模调整:
| 参数 | 常用默认 | 什么时候改 |
|---|---|---|
| Distance | sqeuclidean | 高维稀疏数据改 cosine,二进制数据改 hamming |
| Replicates | 5 | 样本量大或局部最优严重时加到 10 |
| MaxIter | 200 | 收敛曲线没到平台期时加到 500 |
| EmptyAction | singleton | 不希望空簇中断运行时使用 |
| Options | statset('UseParallel', true) | 多核机器上跑大量 Replicates 时明显提速 |
其中 Options 并行这个设置很容易被忽略,实际收益却很大。Replicates 要跑多次相互独立的 kmeans,天然适合并行。用 statset 打开 UseParallel 后,MATLAB 会把不同初始中心的实验分派到不同 worker 上,16 次 Replicates 的耗时经常能降到原来的三分之一以下。要注意并行池里的随机数行为和主线程不完全一致,如果你的实验对可复现性要求极高,建议并行跑完后再用固定种子串行验证一遍关键结果。
5. kmeans 聚类避坑指南:5 个高频问题与排查记录
5.1 结果每次运行都不一样,是不是代码写错了
现象:同一份代码、同一个数据集,连续跑两次,聚类标签和中心点都不一样。
原因:初始化是随机的,不同初始中心会收敛到不同的局部最优解。K-means 的目标函数非凸,局部最优不是代码错误,而是算法的结构特性。如果数据本身的簇结构很清晰,多次运行结果差异会很小;差异大恰恰说明簇结构弱或者 K 值不合适。
解决分三步。第一步,代码开头写 rng(2024) 固定随机种子,保证同一份数据每次跑的结果可复现,这是实验的基本底线。第二步,用 Replicates 从多个初始中心里挑 WCSS 最小的一次,减少坏初始化的干扰。第三步,如果固定种子加 Replicates 之后结果仍然飘,回到轮廓系数重新验证 K——很可能你给的 K 大于数据实际簇数,多余的簇在随机初始化下竞争同一批样本,结果自然不稳定。
5.2 运行报错提示 empty cluster created
现象:kmeans 执行到一半报错,提示某个簇在迭代过程中变成了空簇。样本量小、K 偏大、数据里有明显离群点时特别容易触发。
原因有两类:一是 K 值确实大于数据本身的自然簇数,某个簇在竞争中被"饿死";二是初始化太差,多个初始中心落在同一片密集区域,导致某个簇从头到尾没有抢到样本。
解决:内建函数把空簇处理选项 EmptyAction 设为 singleton,让空簇自动用离它最近的样本重置中心,程序就不会中断。如果用手写版本,就在更新中心那一步对空簇保留旧中心或重置为随机样本。但要注意,EmptyAction 只是让程序能跑完,不代表结果合理。跑完后看 sumd,如果某个簇的 sumd 异常大或者样本数极少,说明 K 值偏大,需要回退到第 4 章的验证流程。
5.3 聚类结果几乎被单一特征主导
现象:聚类之后画散点图,发现分类边界和某个特征完全线性对齐,其他特征对结果几乎没有贡献。检查各特征的方差,会发现那个主导特征的量纲比其他特征大几十上百倍。
原因:K-means 的距离计算把所有维度等权相加,量纲大的维度在距离里的占比天然更大。这在用户数据里尤其常见,比如年龄和收入同时进模型,收入范围是几千到几万,年龄只有十几到几十,收入维度会碾压年龄。
解决:聚类前做 Z-score 归一化,这基本是 K-means 的必选项而不是可选项。具体写法见 3.1 节。还需要注意归一化参数只从训练数据计算,新样本预测时复用同一组 mu 和 sigma。进阶情况是,当归一化之后仍然被某个特征主导,可以检查该特征是否本身就是强区分特征,如果是,考虑降维后再聚类,或者改用特征加权距离。
5.4 高维数据聚类效果差,轮廓系数接近零甚至为负
现象:特征维度到几十上百之后,聚类结果的轮廓系数掉到 0.2 以下,不同 K 值之间的差别也很小,看起来怎么调都没用。
原因:高维空间存在距离集中效应,所有样本点到中心点的距离会趋向于接近,欧氏距离的区分度急剧下降。维度越高,sqeuclidean 距离的分辨率越低,聚类自然失效。
解决方向有两个。第一,换距离度量,文本或稀疏数据可以试 cosine,它对向量长度不敏感,只关心方向;处理基因表达这类数据可以试 correlation。第二,先降维再聚类,常见做法是先用 PCA 取累计方差解释率 70% 以上的前若干个主成分作为聚类输入。但降维不是免费的,PCA 丢失的信息可能正是簇之间关键的区分信息,降维后如果轮廓系数还是上不去,建议换聚类算法而不是继续硬调。
5.5 手写代码迭代不收敛,WCSS 曲线反复震荡
现象:自己实现的版本里,history 曲线在相邻两轮之间来回跳,比如 100.2、99.8、100.1、99.9,就是不进入单调下降直到平稳。
原因:有样本点正好落在两个簇的边界附近,到两个中心的距离几乎相等,分配结果在相邻轮次之间来回变动,中心点也随之小幅移动。这种情况在高斯混合重叠区域或对称分布的合成数据里很常见。
解决:收敛判断除了比较中心变化量,还要看样本分配是否稳定。我一般记录上一轮的 idx,如果本轮 idx 与上轮完全相同,直接判定收敛。这个方法在真实数据里比纯看范数变化更稳。另一个可选方案是给距离加入极小随机扰动打破对称性,但这是治标不治本,样本分配不稳定的根源还是簇边界重叠,最终要靠调整 K 值或前处理来解决。
6. 让 kmeans 结果真正落地的三个进阶技巧
6.1 二维可视化:样本散点与中心点叠加
聚类跑完别只盯着标签向量,先把图画出来。二维数据直接画,高于二维就先 PCA 或 t-SNE 压到二维做个粗查:
figure; gscatter(X(:,1), X(:,2), idx, 'rgbcmy', '.', 12); hold on; plot(centers(:,1), centers(:,2), 'kx', 'MarkerSize', 18, 'LineWidth', 3); hold off; legend('Location', 'best');gscatter 按簇标签给样本点自动配色,黑叉标记中心。这一步能最快暴露问题:簇之间是否有明显重叠、是否存在被孤立的离群点、中心是否落在合理位置。如果图上簇边界模糊成一团,后面所有定量指标都不用看了。
6.2 簇特征画像:用热图定位每个簇的业务含义
聚类本身不产生业务解释,解释要靠特征画像。对每个簇计算所有特征的均值,然后画热图:
profile = zeros(k, d); for j = 1:k profile(j, :) = mean(X(idx == j, :), 1); end figure; imagesc(profile); colorbar; set(gca, 'YTick', 1:k, 'YTickLabel', arrayfun(@(x) sprintf('Cluster %d', x), 1:k, 'UniformOutput', false)); xlabel('特征序号'); ylabel('簇编号'); title('各簇特征均值画像');每一行代表一个簇,每一列代表一个特征。颜色深的格子告诉你这个簇在哪些特征上显著偏高或偏低。这个图在给业务方汇报的时候比任何统计量都直观,也是我判断"这个簇到底是不是一类人"的第一依据。
6.3 稳定性验证:两次运行的一致性检查
聚类结果能不能用于后续决策,先看它稳不稳。同一份数据跑两遍,检查两次标签的一致率:
[idx1, ~] = kmeans(X, k, 'Replicates', 1); [idx2, ~] = kmeans(X, k, 'Replicates', 1); conf = confusionmat(idx1, idx2); [~, assign] = max(conf, [], 2); % 贪心对齐两轮标签 aligned = zeros(size(idx2)); for j = 1:k aligned(idx2 == j) = assign(j); end agreement = mean(idx1 == aligned); fprintf('两次运行的一致率: %.2f\n', agreement);由于随机初始化,两次运行标签编号可能不同,先用混淆矩阵做贪心对齐再算一致率。一致率高于 0.95 说明簇结构稳定,结果可以放心往下游送;如果只有 0.8 甚至更低,说明聚类划分本身不可靠,这时候强行接业务分析等于在流沙上盖楼。
我现在的习惯是任何一次聚类实验都至少跑两遍,第一遍看结果合不合理,第二遍验证结果稳不稳定,两边都过了才敢把聚类标签写进报告。这个习惯救过我不少次,也希望能帮到你。
本文还有配套的精品资源,点击获取