首页/新闻资讯/正文详情

MATLAB聚类算法源码解析:Kmeans、FCM、SOM与层次聚类

发布时间:2026/11/2 23:47:40 来源:云帆数科 栏目:资讯中心
MATLAB聚类算法源码解析:Kmeans、FCM、SOM与层次聚类
简介这份 MATLAB 聚类算法资料包面向机器学习、模式识别课程学习者与需要做算法对比实验的开发者围绕无监督分类场景整理 FCM、K-means、Kmedoid 等经典方法及 GK、GG 等扩展实现可用于课程实验、数据分群与算法性能对照。压缩包共 74 个文件约 2.05MB以 40 个 m 脚本承载聚类主流程与示例调用17 个 gif 及少量 png、jpg 用于界面和结果展示另有 6 个 txt 说明、1 个 pdf 工具箱文档、mat 与 wk1 数据文件及少量网页样式文件兼顾代码、演示与参考资料。已有 4888 人学习说明其在聚类入门与实验复现方面具有一定参考价值。读者可从中获得隶属度迭代、聚类中心更新、归一化与反归一化、有效性评价、PCA 投影和 demos 示例等完整实现并借助可视化与指标对比理解不同算法在模糊边界、非球形分布等数据上的适用差异便于快速改参数、跑通实验并查漏补缺。1. 从一堆散落的 .m 文件说起四种聚类算法源码包能解决什么问题前阵子有个同学拿着一份聚类算法源码包来找我说跑出来的图和教材上不一样。打开一看十几个.m文件摊在目录里Kmeans、FCM 模糊 C 均值、神经网络聚类、层次聚类各占一块示例脚本能直接出图但参数全靠手改。这就是典型的教学型源码包不做工程封装把算法主循环完整摊开给你看。它适合正在做图像分割、样本分组、模式识别课程设计又不想只调kmeans一个函数了事的人。MATLAB 环境下四种聚类算法摆在一起对比价值不在跑通而在看清初始化、迭代停止、结果评价这三处差异。2. 环境与目录结构四套 MATLAB 实现怎么摆、依赖什么先把环境说清楚。这套代码是纯 MATLAB 实现常见的兼容区间是 R2016b 及以上脚本里没有用太新的语法糖老版本基本也能跑。唯一需要注意的是距离计算如果作者用了pdist2或linkage就得装 Statistics and Machine Learning Toolbox如果用的是手写欧氏距离纯基础 MATLAB 就能跑。拿到包之后先别急着点运行花两分钟理清目录和函数签名后面能省掉大半排查时间。2.1 目录组织四个算法各自独立靠一份数据打通这类源码包常见的组织方式是按算法分文件夹每个文件夹里放「一个主函数 一个 demo 脚本 一份示例数据」最后再放一个总对比脚本。结构大致如下clustering-demo/ ├── data/ │ └── blobs.mat # 示例数据N×D 的样本矩阵 可选真值标签 ├── kmeans/ │ ├── myKmeans.m # 主算法返回标号、中心、目标函数曲线 │ └── demo_kmeans.m # 示例脚本出散点图和迭代曲线 ├── fcm/ │ ├── myFCM.m # 返回隶属度矩阵 U 和聚类中心 │ └── demo_fcm.m ├── som/ │ ├── mySOM.m # 自组织映射返回权值矩阵 │ └── demo_som.m ├── hier/ │ ├── demo_hier.m # 层次聚类主要调 linkage / dendrogram │ └── cut_tree.m # 按簇数裁剪树 └── compare_all.m # 四种算法在同一份数据上横向对比按算法分目录的好处是依赖干净缺点也明显四份实现各自管自己的距离计算同一个欧氏距离可能被写了四遍。二次开发时第一件事我一般就是把公共部分抽出来比如统一一个sqdist.m后面调参对比才不会被不同实现的距离定义坑到。data/目录里那份数据是关键四个 demo 都读它保证对比时输入一致。2.2 入口函数的签名先看参数再看主循环读这种源码包顺序应该是先扫每个主函数的输入输出再进主循环。四个算法的签名通常是这个形状文件主要输入主要输出关键参数myKmeans.mXN×D、Kidx、C、Jhist簇数 K、最大迭代、中心位移阈值、随机种子myFCM.mX、K、mU、C、objHist模糊指数 m常用 2、终止阈值mySOM.mX、网格尺寸W权值、命中次数学习率初值、邻域半径、训练轮数demo_hier.mX、KT簇标号、Z连接矩阵linkage 方法、距离度量看到这张表就能判断代码成熟度如果myKmeans没有随机种子参数那么每次跑结果都会跳示例图看着还行一写进报告就露馅如果myFCM没有m参数而是写死成 2那基本只能跑标准场景遇到分布重叠的数据就调不动。参数越全二次开发的余地越大。2.3 首次跑通的最小路径从 compare_all 反着推新手最容易犯的错是从第一个文件夹开始一个个跑跑到第三个发现数据路径不对前两个又得重来。我一般反着来先跑compare_all.m它是四个算法的汇合点能把数据加载、路径、依赖问题一次性暴露出来。跑通了说明环境和数据都没问题再回头单独看每个算法。跑之前建议在脚本开头加三行保护让结果可复现% 放在 compare_all.m 最前面 rng(42); % 固定随机种子聚类结果才可复现 addpath(genpath(pwd)); % 把子文件夹一次性加进搜索路径 warning(off, stats:kmeans:FailedToConverge);第一行解决「每次结果不一样」第二行解决「函数找不到」第三行只在用工具箱函数时才需要。genpath(pwd)会把data/、kmeans/这些子目录全加进去比手动addpath四次省事但要注意它会连.git之类的隐藏目录也扫进去目录深的时候启动会慢一点可以在.git里放个空的pathdef挡一下或者干脆写成addpath(kmeans,fcm,som,hier,data)。跑通之后先别急着看图把四个算法输出的簇内平方和记下来。同一个 K、同一份数据Kmeans 和 FCM 的 SSE 应该在同一量级如果差了一个数量级多半是某个实现的距离算错了或者数据没归一化。这一步看着多余但后面调参时它就是你的基准线。3. Kmeans 与 FCM 核心实现两个最常用的分割算法怎么读这两份实现是整个包里被翻得最多的。Kmeans 是硬划分一个样本只属于一个簇FCM 是软划分一个样本对每个簇都有一个隶属度。代码结构看着像其实差别全在「更新」这一步Kmeans 更新的是中心FCM 更新的是中心加隶属度矩阵而且两者交替进行。读懂这一层后面调参才有方向。3.1 Kmeans 主循环E 步和 M 步各自在做什么手写版 Kmeans 的核心就是一个 while 循环两块内容把样本分给最近的中心E 步用分到的样本重算中心M 步。下面是这类源码包里常见的实现形态我补上了空簇处理和收敛判断function [idx, C, Jhist] myKmeans(X, K, maxIter, tol, seed) % X : N×D 样本矩阵每行一个样本 % K : 簇数 % maxIter : 最大迭代次数默认 300 % tol : 中心最大位移阈值默认 1e-6 % seed : 随机种子固定后结果可复现 if nargin 5, seed 42; end if nargin 4, tol 1e-6; end if nargin 3, maxIter 300; end [N, D] size(X); rng(seed); % ---- 初始化k-means第一个中心随机后续按距离平方概率抽 ---- C zeros(K, D); C(1, :) X(randi(N), :); for k 2:K d2 sqdist(X, C(1:k-1, :)); % N×(k-1) d2 min(d2, [], 2); % 每个样本到已有中心的最近距离平方 p d2 / sum(d2); p(end) 1 - sum(p(1:end-1)); % 修掉浮点误差防止 cumsum 不到 1 [~, j] min(abs(cumsum(p) - rand)); C(k, :) X(j, :); end Jhist zeros(maxIter, 1); for it 1:maxIter % E 步每个样本归到最近的中心 D2 sqdist(X, C); [dmin, idx] min(D2, [], 2); % M 步按当前划分重算中心 Cnew C; for k 1:K if any(idx k) Cnew(k, :) mean(X(idx k, :), 1); else Cnew(k, :) X(randi(N), :); % 空簇重新播种别让它一直空着 end end Jhist(it) sum(dmin); shift max(sqrt(sum((Cnew - C).^2, 2))); % 本轮中心最大位移 C Cnew; if shift tol, break; end end Jhist Jhist(1:it);sqdist是手写的欧氏距离平方用矩阵乘法展开不依赖工具箱N 上千的时候比双重循环快一个量级function D2 sqdist(A, B) % 欧氏距离平方返回 numel(A 行) × numel(B 行) 的矩阵 D2 sum(A.^2, 2) sum(B.^2, 2) - 2 * (A * B); D2 max(D2, 0); % 浮点误差可能产生极小的负数砍掉两个地方值得多说一句。第一是收敛判据这里用的是「中心最大位移」另一种常见写法是「目标函数相对变化」两者都行但中心位移对尺度更敏感数据没归一化时容易提前停所以我一般在调用前先zscore一下。第二是Jhist只保留到实际迭代次数绘图时 x 轴才对得上不然曲线后面拖一截平的看着像没收敛。3.2 k-means 初始化多写十行换来的是稳定随机撒 K 个点当初始中心是 Kmeans 最省事也最坑的做法。数据分布稍微不均匀两个初始中心落进同一个密集团那个簇就永远分不开SSE 每次跑差一大截。k-means 的思路很简单第一个中心随机取之后每个中心按「到已有中心的最近距离平方」为概率抽取距离越远的点越可能被选中天然把中心推开。上面那段初始化就是这个逻辑代价是多十行代码和一次N×K的距离计算换来的是结果方差明显下降。判断一份 Kmeans 实现靠不靠谱我有个土办法同一份数据跑十次看 SSE 的极差。如果极差超过均值的 5%说明初始化策略太弱要么改成 k-means要么加多起点重启跑nInit次取 SSE 最小的那组。多起点重启写起来更简单bestJ inf; for run 1:10 [idxRun, CRun, Jrun] myKmeans(X, K, 300, 1e-6, run); if Jrun(end) bestJ bestJ Jrun(end); idx idxRun; C CRun; Jhist Jrun; end end代价是时间翻十倍样本量上万时不划算这时候 k-means 的性价比就体现出来了。3.3 FCM 的隶属度更新与收敛判据FCM 把「属于/不属于」换成了「属于的程度」隶属度矩阵U是 N×K每行和为 1。中心更新和隶属度更新交替进行公式里多了一个模糊指数m它控制划分的模糊程度m 越接近 1越接近硬划分m 越大隶属度越平均。工程上 m 取 1.5 到 2.5 之间默认 2 是最常见的起点。function [U, C, objHist] myFCM(X, K, m, maxIter, tol) % X : N×D 样本矩阵 % K : 簇数 % m : 模糊指数必须 1常用 2 % maxIter : 最大迭代次数 % tol : 目标函数相对变化阈值 if nargin 5, tol 1e-5; end if nargin 4, maxIter 200; end if nargin 3, m 2; end [N, D] size(X); U rand(N, K); U U ./ sum(U, 2); % 行归一化保证每行隶属度和为 1 objHist zeros(maxIter, 1); for it 1:maxIter Um U .^ m; % 1) 更新中心隶属度的 m 次方做权重 C (Um * X) ./ max(sum(Um, 1), eps); % 2) 更新隶属度到中心的距离越小隶属度越大 d2 sqdist(X, C); d2 max(d2, eps); % 防止样本与中心重合时除零 tmp d2 .^ (-1 / (m - 1)); U tmp ./ sum(tmp, 2); % 3) 目标函数加权距离平方和 objHist(it) sum(sum(Um .* d2)); if it 1 abs(objHist(it) - objHist(it-1)) / objHist(it-1) tol objHist objHist(1:it); break; end end三个点要注意。d2 max(d2, eps)是后悔药如果某个样本恰好落在某个中心上距离为 00^(-1/(m-1))会变成Inf整个矩阵就废了。max(sum(Um,1), eps)是同一个道理某个簇一个样本都没吸住时分母为 0。收敛判据用的是目标函数的相对变化而不是绝对值因为目标函数本身随样本量增长固定阈值在不同数据集上没法通用。还有一个隐藏问题FCM 对初始隶属度敏感随机初始化同样会出现「跑十次十个结果」。常见的补救是先用 Kmeans 跑一遍把硬划分转成隶属度当初始值代码就多三行idx0 myKmeans(X, K); U0 full(sparse(1:N, idx0, 1, N, K)); U0 U0 0.01; % 加一点噪声避免 0 和 1 的极端初值 U U0 ./ sum(U0, 2);这样收敛更快也更不容易掉进局部极小。代价是引入了 Kmeans 本身的偏差做对比实验时得在报告里说明初始化方式不然别人复现不出你的数字。4. 神经网络聚类与层次聚类另外两种实现的读法前两个算法是按「簇中心」组织数据的后两个换了个思路。神经网络聚类自组织映射那一路用一个网格去拟合数据的拓扑结构适合看数据的低维流形层次聚类不做划分先建一棵树再按需要裁剪。这两种的代码结构和前面差别较大读法也不一样。4.1 自组织映射竞争、邻域、权值更新三步SOM 的骨架是一个二维网格每个节点挂一个和样本同维的权值向量。训练时每个样本找到最相似的节点胜者然后胜者连同它邻域内的节点一起向样本方向拉。三步循环找胜者、算邻域、更新权值。核心在于邻域半径和学习率都要随训练轮数衰减否则网格会僵在一个状态不动。function [W, hits] mySOM(X, gridSize, epochs, lr0, sigma0) % X : N×D 样本矩阵 % gridSize : [行 列] 网格尺寸常用 [5 5] 或 [10 10] % epochs : 训练轮数常用 100 到 500 % lr0 : 初始学习率常用 0.5 % sigma0 : 初始邻域半径常用网格尺寸的一半 [N, D] size(X); R gridSize(1); Cc gridSize(2); % 权值初始化在数据范围内随机撒点比全零好收敛 W rand(R, Cc, D) .* (max(X) - min(X)) min(X); hits zeros(R, Cc); [rr, cc] ndgrid(1:R, 1:Cc); % 网格坐标用于算邻域距离 for ep 1:epochs % 学习率和邻域半径指数衰减这是 SOM 能收敛的关键 lr lr0 * exp(-3 * ep / epochs); sg sigma0 * exp(-3 * ep / epochs); order randperm(N); % 每轮打乱样本顺序 for i order x X(i, :); % 1) 找胜者节点权值与样本距离最小的那个 diff W - reshape(x, 1, 1, D); dist2 sum(diff.^2, 3); [~, win] min(dist2(:)); [wr, wc] ind2sub([R Cc], win); hits(win) hits(win) 1; % 2) 高斯邻域离胜者越近更新幅度越大 h exp(-((rr - wr).^2 (cc - wc).^2) / (2 * sg^2)); % 3) 权值更新 for d 1:D W(:, :, d) W(:, :, d) lr * h .* (x(d) - W(:, :, d)); end end end这里sigma0取网格尺寸的一半是有讲究的训练初期邻域几乎覆盖整个网格所有节点一起向数据靠拢形成大致的拓扑后期半径收缩到 1 以内就只剩胜者自己在微调。如果sigma0设得太小网格会从一开始就各自为政最后出现「节点全挤在一起」或者「大片节点从不更新」的现象hits矩阵一眼就能看出来。判断 SOM 训没训好hits是最直接的指标。把它画成热力图理想情况下各个节点被激活的次数大致均匀如果一大片是 0说明那部分网格从没赢过要么是权值初始化太偏要么是sigma0太小。另一个指标是 U 矩阵相邻节点权值距离亮的地方就是簇的边界这个包里不一定有现成函数自己写十行就够。4.2 层次聚类linkage 合并准则与树状图裁剪层次聚类在这份包里通常不是手写实现而是直接调linkage加dendrogram因为凝聚式层次聚类的手写代码要维护距离矩阵的增量更新教学价值有但工程上没必要重复造。核心就两步算连接矩阵Z按簇数裁剪。% 1) 先归一化量纲不统一时距离会被大数值特征主导 Xn zscore(X); % 2) 建树ward 方法配欧氏距离簇内方差增量最小 Z linkage(Xn, ward, euclidean); % 3) 按簇数裁剪K 就是要几个簇 T cluster(Z, maxclust, K); % 4) 画树状图只显示最后 30 层样本多时不至于糊成一片 figure; dendrogram(Z, 30);linkage的第二个参数是合并准则选错了结果差很多。常见的几种single最短距离容易产生「链式效应」一条长条形的簇会把中间样本全串起来complete最长距离偏向紧凑的球形簇但对噪声敏感average是折中ward最小化合并后的簇内方差增量对球形簇效果最好也是我最常用的默认值。区别可以用一句话记住数据里有没有明显的「细长结构」有就用single没有就用ward。dendrogram(Z, 30)的第二个参数是「只显示最上面多少层」不是截断簇数。样本上千时不加这个参数图会变成一片黑。裁剪簇数用cluster(Z, maxclust, K)也可以用cluster(Z, cutoff, c)按高度切前者更符合「我要 K 个簇」的直觉后者适合不确定 K 但知道簇间距离阈值的场景。层次聚类最大的优势是结果稳定同一份数据、同样的参数跑几次结果完全一样没有随机初始化的玄学。代价是时间复杂度 O(N²) 甚至 O(N³)样本超过几千就跑不动了。所以这份包里它一般只用来处理几百个样本的示例数据或者作为「K 到底取几」的参考——看树状图在哪个高度上出现明显的大跳跃那个位置对应的簇数往往比拍脑袋定的靠谱。4.3 四种算法横向对比一份脚本看差异四个 demo 单独跑只是看个热闹真正能看出差别的是同一份数据、同一个 K、同一套评价指标。包里那个compare_all.m就是干这个的思路是跑完四个算法统一算簇内平方和、运行时间、以及和真值标签的一致性。一致性用调整兰德指数ARI自己写十几行不依赖工具箱function a adjRand(idx1, idx2) % 调整兰德指数取值 [-1,1]1 表示两种划分完全一致 u1 unique(idx1); u2 unique(idx2); M zeros(numel(u1), numel(u2)); for i 1:numel(u1) for j 1:numel(u2) M(i, j) sum(idx1 u1(i) idx2 u2(j)); end end c2 (x) x .* (x - 1) / 2; n numel(idx1); sumM sum(sum(c2(M))); a1 sum(c2(sum(M, 1))); a2 sum(c2(sum(M, 2))); tot c2(n); expIdx a1 * a2 / tot; maxIdx (a1 a2) / 2; a (sumM - expIdx) / (maxIdx - expIdx eps); end调用侧就是一个循环把结果攒成表格K 3; results struct(); tic; [idx1, ~, J1] myKmeans(X, K); results.kmeans toc; tic; [~, ~, U] myFCM(X, K); idx2 max(U, [], 2); results.fcm toc; tic; [~, hits] mySOM(X, [4 4], 200, 0.5, 2); [~, idx3] max(hits(:)); idx3 ones(size(X,1),1); results.som toc; tic; Xn zscore(X); Z linkage(Xn, ward); idx4 cluster(Z, maxclust, K); results.hier toc; fprintf(Kmeans SSE%.2f ARI%.3f 用时%.3fs\n, J1(end), adjRand(idx1, label), results.kmeans); fprintf(FCM ARI%.3f 用时%.3fs\n, adjRand(idx2, label), results.fcm);表格出来之后常见的结论是Kmeans 最快FCM 慢两三倍但重叠数据的 ARI 更高层次聚类稳定但样本一多就慢SOM 的结果对网格尺寸和学习率很敏感、调起来最费劲。这个结论不是让你背而是让你在自己的数据上跑一遍看哪条规律成立。我见过在环形分布数据上 Kmeans 完全失效、single连接反而完美的例子也见过 FCM 的 m 调到 3 之后 ARI 反而下降的情况——这些都得自己试。5. 避坑与排查跑不起来、结果不稳定时先看这几处这份包在别人机器上跑得好好的到你这里报错或者结果离谱九成是下面五类问题。每条按「现象 → 原因 → 解决」写遇到对得上的直接照着改。5.1 报错找不到函数pdist2、linkage、kmeans 全红现象脚本跑到一半报Undefined function pdist2 for input arguments of type double换成linkage也是同样的错但你自己写的myKmeans能跑。原因这些是 Statistics and Machine Learning Toolbox 里的函数不是 MATLAB 基础库自带的。安装时没勾选工具箱或者用的是精简安装这些函数就不存在。也有一种情况是路径问题——addpath没加对函数其实在只是搜不到。解决先ver看工具箱列表里有没有Statistics and Machine Learning Toolbox没有就补装。临时应急有两个办法距离计算换成手写的sqdist层次聚类换成手写凝聚式或者用squareform加一个简单循环替代。我一般倾向直接改代码因为教学包跑在别人机器上你没法保证对方装了工具箱。判断是工具箱缺失还是路径问题的快捷方式是which pdist2返回空就是没装返回路径就是能搜到、问题在别处。5.2 每次跑结果都不一样簇数对标签乱跳现象K3 跑两次两次划分看着差不多但标签 1 和 2 换了个位置画出来的图颜色不一样算出来的 SSE 也差了一截。原因两个层面的问题叠在一起。标签跳是聚类算法本身的「标签置换」特性簇编号没有语义1 号簇和 2 号簇换个名字结果完全等价这不是 bug。SSE 差得多才是真问题根源在随机初始化——初始中心落点不同收敛到的局部极小不同。解决标签置换用 ARI 或者混淆矩阵比对别直接比idx1 idx2那样算出来的准确率毫无意义。初始化问题固定rng(seed)让结果可复现同时把初始化换成 k-means或者加多起点重启取最优。要注意rng要放在初始化之前放在主循环外面放进循环里等于没固定。5.3 FCM 的隶属度全一样或者出 NaN现象K5 的时候隶属度矩阵每一行的五个值都在 0.2 附近几乎分不出来或者某些行直接是NaN后续画图全黑。原因前者是模糊指数 m 太大d2^(-1/(m-1))的区分度被压平m 趋近无穷时所有隶属度都趋近 1/K。后者是除零样本和中心恰好重合导致距离为 0取负幂变成InfInf/Inf就是NaN。解决m 从 2 开始试区间控制在 1.5 到 2.5超过 3 基本就没有划分意义了。除零问题在距离上加一个eps兜底同时检查sum(Um, 1)里有没有 0有的话说明某个簇一个样本都没吸住要么 K 取大了要么初始化太偏。我习惯在 FCM 里加一段断言迭代结束后检查any(~isfinite(U(:)))有就直接报错比后面画出一张黑图再回头找强。5.4 SOM 训练完网格全糊在一起现象训练 500 轮之后把权值画出来网格上相邻节点的权值几乎一样U 矩阵一片灰看不到簇边界。或者反过来大片节点从来没被激活过。原因前者的典型原因是学习率和邻域半径没有衰减一直用初始值更新所有节点被反复拉扯到数据均值附近。后者是初始邻域半径设得太小网格从第一轮就各自为政弱节点再也翻不了身。解决两个参数都必须随轮数指数衰减衰减系数在 2 到 5 之间写成lr0 * exp(-3*ep/epochs)这种形式。初始邻域半径取网格尺寸的一半比如 10×10 的网格从 5 开始。训练完看hits矩阵如果超过 20% 的节点是 0 次命中说明网格开太大了降到 5×5 或者 6×6 再试。SOM 是这四个算法里最需要手调的参数没选好结果会非常难看别指望一套参数通吃。5.5 层次聚类树状图糊成一片、裁剪结果和预期不符现象dendrogram画出来底部密密麻麻全是线完全看不清结构或者cluster(Z,maxclust,K)出来的簇大小严重失衡一个簇几百个样本另一个只有两个。原因前者是样本多、没加显示层数限制几百个叶子节点挤在一张图上必然糊。后者多半是数据没归一化某个特征的量纲远大于其他特征距离被它主导或者 linkage 方法选错了——single在含噪数据上很容易产生「一个大簇 一堆单点」的结果。解决画图加dendrogram(Z, 30)限制显示层数只看上层结构。数据先zscore归一化这一步在层次聚类里比在前面三个算法里都重要因为距离是唯一的合并依据。linkage 从ward开始试效果不好再换averagesingle只在明确知道数据有细长结构时用。裁剪之前先看树状图在哪个高度出现大跳跃那个高度对应的簇数往往比直接指定 K 更合理。6. 二次开发把示例代码改成能进项目的工具函数示例脚本能出图但离「能拿来用」还差几步。最常见的改造是把四个demo_*.m里的算法部分抽成统一的接口让调用侧不关心用的是哪个算法。我一般定一个这样的签名function [idx, model] runCluster(X, algo, K, varargin) % algo: kmeans | fcm | som | hier % 统一返回 idxN×1 簇标号和 model含中心/隶属度/树的 struct X zscore(X); % 统一归一化避免各算法行为不一致 switch lower(algo) case kmeans [idx, C, J] myKmeans(X, K); model struct(C, C, J, J); case fcm [U, C, obj] myFCM(X, K, 2); [~, idx] max(U, [], 2); model struct(U, U, C, C, obj, obj); case som [W, hits] mySOM(X, [ceil(sqrt(K)) ceil(sqrt(K))], 200, 0.5, 2); idx som2label(W, hits, X, K); % 把网格映射成 K 个簇需要自己实现 model struct(W, W, hits, hits); case hier Z linkage(X, ward, euclidean); idx cluster(Z, maxclust, K); model struct(Z, Z); end end统一入口之后横向对比、参数扫描、换算法验证都变成改一个字符串的事。验证方式建议固定成三步先用带真值标签的模拟数据跑一遍看 ARI再用一份真实数据看簇大小分布是否合理最后把rng关掉连跑十次看结果波动。第十次结果和第一次差太多八成是初始化或者参数没稳住。还有个容易被忽略的点zscore对异常值很敏感一个极端值能把整个特征压扁。真实数据里如果有明显离群点先做一次简单的 3σ 截断或者用中位数绝对偏差缩放比直接zscore稳。我以前图省事全程zscore结果在一个含少量异常样本的数据集上Kmeans 把异常点单独聚成一簇K 从 3 调到 4 才勉强正常排查了大半天才定位到归一化这一步。从那以后我每次加新数据集都强制先看一眼各特征的分布和离群点比例再决定用哪种缩放。希望这些能帮你少走点弯路。本文还有配套的精品资源点击获取

相关推荐

网络爬虫系统设计:从单机脚本到分布式架构
网络爬虫系统设计:从单机脚本到分布式架构

1. 网络爬虫系统设计概述第一次接触网络爬虫是在2013年,当时为了抓取某电商平台的价格数据,我写了个简单的Python脚本。结果不到半小时,IP就被封了——这就是我学到的第一课:一个健壮的爬虫系统远比想象中复杂。如今作为每天处理上… · 2026/11/2 23:47:40

Redis GEO实现高效附近的人系统设计与优化
Redis GEO实现高效附近的人系统设计与优化

1. 项目概述:如何设计一个高效的"附近的人"系统"附近的人"功能已经成为社交应用的标配功能,从微信到Tinder,从美团到滴滴,几乎所有涉及地理位置的服务都需要这个基础能力。作为一个技术人,我参与过… · 2026/11/2 23:47:40

Apachectl工具详解:从基础使用到高级运维技巧
Apachectl工具详解:从基础使用到高级运维技巧

1. Apachectl 工具概述apachectl 是 Apache HTTP 服务器自带的前端控制工具,它本质上是一个 shell 脚本,封装了对 httpd 进程的各种管理操作。这个看似简单的工具在实际运维中发挥着重要作用,特别是在生产环境中需要频繁启停服务或检查配置时… · 2026/11/2 23:47:40

OpenClaw与LINE集成提升养虾业沟通效率
OpenClaw与LINE集成提升养虾业沟通效率

1. OpenClaw与LINE集成的核心价值OpenClaw作为一款多平台即时通讯聚合工具,其与LINE的深度整合为养虾行业带来了前所未有的沟通效率提升。传统养虾场管理中,技术人员、养殖工人、销售团队之间的信息传递往往存在滞后性,而通过OpenClaw接入LIN… · 2026/11/2 23:59:44

Java record 规范构造器能否被反序列化或反射绕过
Java record 规范构造器能否被反序列化或反射绕过

问题现象 record 类型通常靠规范构造器来把守不变式,比如禁止名字为空: public record User(String name) {public User {if (name == null || name · 2026/11/2 23:59:44

Unity Package Manager刷新错误排查与解决方案
Unity Package Manager刷新错误排查与解决方案

1. Unity Package Manager刷新资源报错问题解析最近在Unity开发过程中,Package Manager提示"Error Refreshing Asset"的问题困扰了不少开发者。作为Unity项目依赖管理的核心组件,Package Manager一旦出现问题,轻则影响资源加载&… · 2026/11/2 23:59:44

淘宝商品详情API开发实战与性能优化指南
淘宝商品详情API开发实战与性能优化指南

1. 淘宝商品详情API核心功能解析淘宝开放平台的taobao.item.get接口是电商开发者最常调用的核心接口之一,它提供了获取单个商品完整信息的能力。这个接口的独特之处在于它能返回商品的基础信息、SKU数据、促销信息等全维度数据,且响应速度控制在200ms以内… · 2026/11/2 23:59:44

JJencode编码解密工具:前端JS代码混淆与源码还原实操
JJencode编码解密工具:前端JS代码混淆与源码还原实操

做前端的人大概都遇到过这种场面:交付给客户的是一个纯前端跑的小工具,或者一个 H5 小游戏,代码打包压缩完往服务器一放,懂行的人按 F12 就能把逻辑看个七七八八,改个变量名、换个皮肤就成他自己的了。这时候就会有人翻… · 2026/11/2 23:57:44

Apache HttpAsyncClient选型指南:Default与Minimal对比
Apache HttpAsyncClient选型指南:Default与Minimal对比

1. 项目概述在构建高性能HTTP客户端时,Apache HttpAsyncClient提供了两个核心实现:DefaultHttpAsyncClient和MinimalHttpAsyncClient。这两个客户端的选择往往让开发者陷入纠结,特别是在生产环境部署时。作为在分布式系统领域深耕多年的架构师… · 2026/11/2 23:55:43

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 4:42:45

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 5:17:18

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码