简介:这份资源是一份面向数据分析初学者与Matlab使用者的K-means聚类算法实现文档,聚焦无监督学习中的经典聚类方法,帮助读者理解算法原理并快速上手代码实践。包内共1个docx文件,压缩包约15KB,内容以文字讲解配合Matlab示例代码为主,涵盖算法基本步骤、kmeans函数调用方式、结果可视化以及局限性与改进方向等模块。文档通过一个二维数据点的具体案例,演示了数据初始化、聚类执行、质心提取与散点图绘制的完整流程,并延伸讨论了K-means++、Bisecting K-means、Kernel K-means等改进思路,便于读者对照代码理解参数含义与调参方向。目前已有441人学习浏览,适合需要完成课程作业、实验报告或入门聚类分析的学习者参考,也可作为Matlab聚类工具箱使用的入门材料。
1. 从一份 kmeans 聚类算法 matlab 代码.docx 说起:7 个点、2 个簇,能跑通什么
很多人第一次接触无监督学习,都是从 K-means 开始的。手头这份kmeans聚类算法matlab代码.docx就是一份典型的入门级实现文档,核心只有一段不到 20 行的 MATLAB 代码:7 个二维数据点、K=2、调用内置kmeans函数、用scatter画出聚类结果。数据点少到可以手算,代码短到可以背下来,但恰恰是这种"最小可运行单元",最适合拿来把 K-means 的每个参数、每个返回值、每个坑位拆干净。
这份资源能解决什么问题?如果你正在做数据分群、客户画像、图像颜色量化、异常检测的预处理,或者只是想在 MATLAB 里快速验证一个聚类想法,这段代码就是你的起点。它适合两类人:一是刚上手 MATLAB 聚类工具箱、需要一份能直接跑通的示例代码的新手;二是想快速回顾kmeans函数签名和参数含义、懒得翻官方文档的老手。下面我不复述文档里的代码,而是把它拆成能复现、能改参数、能排错的实战笔记。
2. MATLAB kmeans 函数签名与参数体系:从 idx、centroids 到距离度量
2.1 函数签名与返回值到底怎么读
MATLAB 的kmeans函数最简调用形式是[idx, centroids] = kmeans(data, K),但它的完整签名远比这复杂。先看返回值:idx是一个 N×1 的向量,N 是样本数,每个元素是 1 到 K 之间的整数,表示该样本被分到哪个簇;centroids是 K×D 的矩阵,D 是特征维度,每一行是一个簇的质心坐标。这两个返回值是后续所有分析的基础——idx用来做分组统计和可视化着色,centroids用来做簇间距离分析和业务解释。
很多人第一次用的时候会困惑:为什么idx是列向量而不是行向量?因为 MATLAB 默认按列组织样本,data的每一行是一个样本、每一列是一个特征。如果你从 CSV 读进来的数据是转置的,kmeans会按错误的维度聚类,结果看起来"能跑"但完全不对。这是最常见的翻车点之一。
2.2 关键参数:Distance、Replicates、Start、MaxIter
kmeans的参数体系里,有四个参数直接决定聚类质量和运行行为,必须搞清楚。
| 参数 | 默认值 | 作用 | 什么时候要改 |
|---|---|---|---|
Distance | 'sqeuclidean' | 距离度量方式 | 特征量纲差异大时改'cityblock'或标准化后再用欧氏 |
Replicates | 1 | 重复聚类次数,取最优 | 数据量大或担心局部最优时设 5~10 |
Start | 'plus' | 初始质心选取策略 | 想复现结果时用'sample'配合rng固定种子 |
MaxIter | 100 | 最大迭代次数 | 收敛慢的数据集调到 300~500 |
Distance默认是平方欧氏距离,注意是"平方"欧氏,不是欧氏。这意味着如果你手动算距离来验证结果,要用平方而不是开根号,否则对不上。Replicates是最容易被忽略的参数——默认只跑一次,如果初始质心选得不好,可能收敛到局部最优。设成 5 或 10,MATLAB 会跑多次取sumd最小的那次,代价是运行时间线性增加。
Start默认是'plus',也就是 K-means++ 初始化,这比纯随机选点好很多。但如果你想复现某次实验结果,'plus'内部有随机性,需要配合rng(seed)固定随机种子。我一般会在脚本开头写rng(42),这样每次跑出来的idx和centroids完全一致,方便调试和对比。
2.3 用一份可复现的脚本把参数串起来
下面这段代码在原始示例基础上做了扩展:固定随机种子、显式指定参数、输出簇内平方和、并做基本的聚类质量检查。
% kmeans_demo.m % 固定随机种子,保证结果可复现 rng(42); % 原始数据:7 个二维点 data = [1 1; 1.5 2; 3 4; 5 7; 3.5 5; 4.5 5; 3.5 4.5]; % 聚类簇数 K = 2; % 完整参数调用 [idx, centroids, sumd, D] = kmeans(data, K, ... 'Distance', 'sqeuclidean', ... % 距离度量 'Replicates', 5, ... % 重复 5 次取最优 'Start', 'plus', ... % K-means++ 初始化 'MaxIter', 300, ... % 最大迭代次数 'Display', 'final'); % 输出最终迭代信息 % 输出结果 fprintf('簇内平方和 sumd: %s\n', mat2str(sumd)); fprintf('质心坐标:\n'); disp(centroids); % 检查每个簇的样本数 for k = 1:K fprintf('簇 %d 样本数: %d\n', k, sum(idx == k)); end这段代码比原始示例多了三个返回值:sumd是每个簇内点到质心的距离平方和,D是每个点到所有质心的距离矩阵。sumd是评估聚类质量的核心指标——总sumd越小,簇内越紧凑。D可以用来做轮廓系数或者找离质心最远的异常点。
参数说明:'Replicates', 5意味着 MATLAB 内部跑 5 次独立聚类,每次用不同的初始质心,最后返回sumd总和最小的那次结果。'Display', 'final'会在命令窗口打印最终迭代次数和总距离,方便你判断是否收敛。如果迭代次数接近MaxIter,说明可能没收敛,需要调大MaxIter或者检查数据是否适合 K-means。
2.4 数据标准化:什么时候必须做,怎么做
原始示例的数据点坐标范围在 1 到 7 之间,两个维度的量纲差不多,所以不标准化也能跑。但实际项目中,特征维度经常是"年龄 0-100"和"收入 0-100000"这种量级差异,如果不标准化,收入维度会完全主导距离计算,年龄维度等于没用。
常见做法是用zscore做 Z-score 标准化:
% 对每一列做 Z-score 标准化 data_norm = zscore(data); % 用标准化后的数据聚类 [idx, centroids_norm] = kmeans(data_norm, K, 'Replicates', 5); % 把质心还原到原始尺度(可选) centroids_orig = centroids_norm .* std(data) + mean(data);zscore按列减均值除标准差,标准化后每个维度均值为 0、标准差为 1。注意centroids_norm是标准化空间的质心,如果要画在原始数据上,需要按上面的公式还原。这一步很多人会忘,导致质心画出来位置不对。
3. 从 7 个点到真实数据集:可视化、K 值选择与结果解读
3.1 可视化代码的细节与常见错误
原始示例用scatter画散点、用scatter画质心,逻辑是对的,但有几个细节容易出问题。第一,scatter(data(:,1), data(:,2), [], idx, 'filled')里第四个参数idx是颜色映射依据,MATLAB 会自动用lines色图给不同簇着色。但如果idx是列向量而data是矩阵,维度要对齐。第二,质心用'Marker', 'x'标记,但scatter的Marker参数在某些版本里要用'x'而不是'Marker', 'x',写法有差异。
下面是我常用的可视化模板,加了簇边界和样本数标注:
figure('Position', [100 100 600 500]); hold on; % 画数据点,按簇着色 gscatter(data(:,1), data(:,2), idx, 'br', 'o', 8); % 画质心 plot(centroids(:,1), centroids(:,2), 'kx', ... 'MarkerSize', 12, 'LineWidth', 2); % 标注每个簇的样本数 for k = 1:K text(centroids(k,1)+0.1, centroids(k,2)+0.1, ... sprintf('C%d (n=%d)', k, sum(idx==k)), ... 'FontSize', 10, 'Color', 'k'); end xlabel('Feature 1'); ylabel('Feature 2'); title(sprintf('K-means 聚类结果 (K=%d)', K)); legend('Cluster 1', 'Cluster 2', 'Centroids', 'Location', 'best'); grid on; hold off;gscatter比scatter更适合分组散点图,它直接接受分组变量idx和颜色字符'br',自动处理图例。质心用黑色x标记,MarkerSize设 12 让它更显眼。标注样本数是为了快速判断簇是否均衡——如果一个簇只有 1 个点,另一个簇有 6 个点,说明 K 值可能选大了或者数据本身不适合 K-means。
3.2 K 值怎么选:肘部法、轮廓系数与业务约束
原始示例直接指定 K=2,但实际项目中 K 值往往需要数据驱动地选。最常用的是肘部法:对 K=1 到 K=10 分别跑聚类,画总sumd随 K 变化的曲线,找"拐点"。
K_range = 1:10; sumd_total = zeros(length(K_range), 1); for i = 1:length(K_range) [~, ~, sumd] = kmeans(data, K_range(i), 'Replicates', 5); sumd_total(i) = sum(sumd); end figure; plot(K_range, sumd_total, 'bo-', 'LineWidth', 1.5); xlabel('聚类簇数 K'); ylabel('总簇内平方和'); title('肘部法选择 K 值'); grid on;总sumd随 K 增大单调递减,因为簇越多、每个簇越紧凑。肘部法找的是下降速率突然变缓的那个 K。但肘部法不是万能的,有时候曲线很平滑,没有明显拐点。这时候可以配合轮廓系数(Silhouette Coefficient),MATLAB 的evalclusters函数可以直接算:
eva = evalclusters(data, 'kmeans', 'silhouette', 'KList', 2:6); fprintf('最优 K(轮廓系数): %d\n', eva.OptimalK); figure; plot(eva);evalclusters会自动跑不同 K 值并计算评估指标,'silhouette'是轮廓系数,越接近 1 越好。除了轮廓系数,还支持'CalinskiHarabasz'和'DaviesBouldin'。我一般会同时看两三个指标,如果它们一致推荐同一个 K,那基本可以确定;如果分歧大,就要回到业务场景判断——比如客户分群,业务方可能明确要求分成 3 档,那 K=3 就是硬约束。
3.3 结果解读:idx 和 centroids 怎么变成业务结论
聚类跑完只是第一步,把idx和centroids翻译成业务语言才是价值所在。以客户分群为例,假设两个特征是"月均消费"和"到店频次",聚类后得到两个簇,质心分别是 (200, 2) 和 (800, 8)。你可以这样解读:簇 1 是低频低消费客户,簇 2 是高频高消费客户。然后统计每个簇的样本占比、其他特征均值,形成画像。
% 假设 data 有两列:月均消费、到店频次 % 统计每个簇的均值和样本占比 for k = 1:K mask = (idx == k); fprintf('簇 %d: 占比 %.1f%%, 月均消费均值 %.1f, 到店频次均值 %.1f\n', ... k, 100*sum(mask)/length(idx), ... mean(data(mask,1)), mean(data(mask,2))); end这段代码输出每个簇的样本占比和特征均值,直接可以写进分析报告。注意mean(data(mask,1))里的mask是逻辑索引,MATLAB 里用逻辑向量索引矩阵非常高效。如果特征多,可以用grpstats函数一次性算所有特征的分组统计。
4. 避坑与排查:K-means 在 MATLAB 里的五个血泪教训
4.1 现象:聚类结果每次跑都不一样
原因:kmeans默认Start='plus',内部有随机初始化,不固定随机种子时每次结果不同。解决:在脚本开头加rng(42),或者显式指定'Start'为一个 K×D 的初始质心矩阵。如果连rng都不管用,检查是不是开了并行池(parpool),并行环境下随机流是独立的,需要在每个 worker 上单独设种子。
4.2 现象:警告"Failed to converge in MaxIter iterations"
原因:数据分布复杂或者 K 值过大,100 次迭代不够。解决:把MaxIter调到 500 甚至 1000,同时观察sumd是否还在下降。如果调到 1000 还不收敛,大概率是数据不适合 K-means——比如簇是长条形或者密度差异极大,这时候要考虑 DBSCAN 或高斯混合模型。
4.3 现象:某个簇的样本数为 0 或 1
原因:K 值选大了,或者初始质心恰好落在孤立点上。解决:先用肘部法或轮廓系数确认 K 值,如果 K 值合理但仍有空簇,把Replicates调大(比如 10),让 MATLAB 多试几次初始质心。另外检查数据里有没有重复点或异常值,异常值会"吸走"一个质心。
4.4 现象:质心画在图上位置明显不对
原因:对标准化后的数据聚类,但质心没还原到原始尺度就画图。解决:用centroids_orig = centroids_norm .* std(data) + mean(data)还原。注意std和mean默认按列计算,如果数据是转置的,要加std(data, 0, 1)显式指定维度。
4.5 现象:中文注释在 MATLAB 编辑器里乱码
原因:MATLAB 2023 及更早版本对 UTF-8 支持不完善,.m文件默认编码可能是 GBK。解决:在 MATLAB 首选项里把"编辑器/调试器"的编码改成 UTF-8,或者用feature('DefaultCharacterSet', 'UTF-8')在脚本开头强制设置。如果文件已经乱码,用记事本打开另存为 UTF-8 再重新导入。
5. 进阶技巧:用 evalclusters 自动选 K 与并行加速
5.1 evalclusters 的三种评估指标怎么选
evalclusters支持'silhouette'、'CalinskiHarabasz'、'DaviesBouldin'三种指标。轮廓系数计算每个点到同簇其他点的平均距离和到最近簇的平均距离,适合簇形状凸且均衡的数据;Calinski-Harabasz 是簇间方差与簇内方差的比值,计算快,适合大数据集初筛;Davies-Bouldin 是簇内散度与簇间距离的比值,越小越好。我一般先用 Calinski-Harabasz 快速扫一遍 K 范围,再用轮廓系数精细确认。
% 三种指标对比 eva_ch = evalclusters(data, 'kmeans', 'CalinskiHarabasz', 'KList', 2:8); eva_sil = evalclusters(data, 'kmeans', 'silhouette', 'KList', 2:8); eva_db = evalclusters(data, 'kmeans', 'DaviesBouldin', 'KList', 2:8); fprintf('CalinskiHarabasz 推荐 K: %d\n', eva_ch.OptimalK); fprintf('Silhouette 推荐 K: %d\n', eva_sil.OptimalK); fprintf('DaviesBouldin 推荐 K: %d\n', eva_db.OptimalK);如果三个指标推荐一致,直接采用;如果不一致,看业务约束。注意evalclusters内部会多次调用kmeans,数据量大时很慢,可以配合'Replicates', 3减少重复次数。
5.2 大数据集下的并行与内存控制
当样本数超过 10 万、特征超过 20 维时,kmeans的内存和耗时都会明显上升。两个优化方向:一是用parfor并行跑不同 K 值的评估,二是用kmeans的'OnlinePhase'参数(如果版本支持)。更通用的做法是先用datasample随机抽样 10% 的数据选 K 值,再用全量数据跑最终聚类。
% 随机抽样 10% 选 K 值 rng(42); sample_idx = datasample(1:size(data,1), round(0.1*size(data,1)), 'Replace', false); data_sample = data(sample_idx, :); eva = evalclusters(data_sample, 'kmeans', 'silhouette', 'KList', 2:8); K_opt = eva.OptimalK; % 全量数据最终聚类 [idx, centroids] = kmeans(data, K_opt, 'Replicates', 5, 'MaxIter', 500);datasample的'Replace', false表示无放回抽样,保证样本不重复。抽样比例一般 10% 到 20% 足够稳定,太小则评估指标波动大,太大则失去加速意义。
5.3 一个我常犯的错误
早期我跑聚类从来不设rng,觉得"随机就随机,结果差不多就行"。直到有一次做客户分群,同一份数据跑了三次得到三个不同的分群方案,业务方拿着三份报告问我"到底以哪个为准",场面非常尴尬。从那以后我每次跑kmeans都强制在脚本第一行写rng(42),并且在报告里注明随机种子。这个习惯看起来不起眼,但能省掉大量"为什么结果又变了"的沟通成本。希望帮到你。
本文还有配套的精品资源,点击获取