资讯动态

用MATLAB实现SOM自组织映射聚类:从Excel数据到可视化

发布时间:2026/10/8 20:12:02 来源:尧图企业网站定制
作为一个常年拿MATLAB做数据分析的人我对于聚类算法的感情很复杂。K-means虽然快但你必须提前定好K值碰上非凸的数据分布或者初始化不当结果能让你怀疑人生。后来我开始用自组织特征映射SOM才算是找到了一个既直观又不需要太多先验假设的聚类工具。这篇文章就专门聊聊怎么用MATLAB写一个SOM数据聚类程序数据直接从Excel读跑完自动输出聚类标签顺便把训练过程的漂亮图表给你画出来。别把SOM想得太玄乎。它本质上是一个单层前馈神经网络但用的训练策略是无监督的竞争学习。它的核心逻辑是让输出层的神经元互相竞争谁跟输入样本最像谁就赢并且带着自己的邻居一起向这个样本的方向调整。训练结束后相似的样本会被映射到相邻的神经元上天然就形成了聚类效果。相比于K-means只能给出一个孤零零的簇中心SOM还保留着样本之间的拓扑关系这个特性在很多场景下非常有用。我接过不少类似的需求——数据是Excel表格需要无监督聚类还要能在MATLAB里一键执行。下面我就结合我自己实际写过、调过、踩过坑的一个完整程序把从数据读入到结果导出的全过程掰开揉碎讲清楚。1. SOM算法思路与程序整体设计1.1 SOM为什么比K-means更适合这类任务先泼一盆冷水SOM不是万能的如果你的数据本来就是簇间界限极其清晰的球形簇K-means完全够用没必要用SOM。但如果你的数据维度高、簇的形状不规则、甚至带有一定的拓扑结构那么SOM的优势就体现出来了。SOM的核心是保序映射。它把高维输入空间中的样本映射到低维通常是二维的网格上并且保持近邻关系。也就是说原始空间中离得近的样本在SOM网格上也离得近原始空间中离得远的样本映射到网格上也会拉开距离。这种性质在做数据探索的时候非常爽——你不需要先验地知道有几类直接看地图上大陆的分布就能大概判断出来。再一个SOM是竞争学习机制。每一次迭代只激活一个优胜神经元BMUBest Matching Unit这个机制天然地避免了K-means里那种所有簇中心同时被全部样本拉扯的尴尬局面。训练稳定、收敛过程平滑对初值不那么敏感。这跟我平时调K-means的感受完全不一样——SOM很少因为初始化不同而给出差别巨大的结果。1.2 程序架构与数据流设计拿到这个需求的时候我先梳理了一下整个程序的数据流Excel数据 → 读取与清洗 → 归一化 → SOM训练 → 聚类标签生成 → 结果可视化与导出这不是随便排的顺序每一步都有它必须存在的理由读取与清洗Excel的数据往往带着表头、空行、文本列不处理干净后面全是坑。归一化SOM用的是欧氏距离特征之间量纲不一致就等于在距离计算里给某些特征加了隐形权重。后面我会细说为什么这在SOM里尤其要命。SOM训练核心循环包括竞争、合作、更新三个步骤。聚类标签生成SOM训练完得到的是每个样本对应的获胜神经元坐标还需要把神经元分组才是真正的聚类。结果可视化与导出做数据聚类的项目最后一定要交付让人看得懂的结果。U-matrix、样本命中图、聚类标签分布图这些都要有。程序的文件组织我也建议分模块写别一个大脚本从头写到尾。虽然MATLAB的脚本文件用起来方便但一旦数据格式变了或者要调整参数改一个几百行的脚本非常痛苦。我的习惯是分四个文件文件名作用main_som_clustering.m主脚本控制流程readExcelData.m读取和预处理ExceltrainSOM.mSOM训练核心函数plotSOMResults.m可视化与结果导出1.3 模块划分与核心函数职责你可能觉得分这么细有点小题大做但我可以负责任地说等你调试的时候就会感谢这个设计了。SOM的调试跟K-means不一样它不是跑一次就完事而是需要反复看中间结果。比如训练到一半发现神经元死掉一片你得能快速判断是哪部分逻辑出了问题。模块化以后每一部分都可以单独调试省下的时间远远超过拆分文件多花的那几分钟。训练函数的部分我采用了MATLAB的classdef把SOM类封装起来。这里不是为了炫技而是因为SOM的训练参数太多——网格大小、初始学习率、邻域半径、迭代轮数——全部塞给一个函数的话参数列表会非常吓人。封装成类以后所有参数都是属性训练和预测都是方法代码的可读性和复用性都有了质的提升。2. Excel数据读取与预处理2.1 数据格式约定几乎所有找我做聚类分析的人数据格式都是差不多的第一行是列名变量名每个样本一行每个特征一列。列名要不要保留我的建议是保留后面画图用到特征命名的时候会方便很多。真正需要去掉的是没有任何实际含义的行比如全空的、全部是NaN的、或者有注释文字的。这里有一个特别容易踩的坑如果Excel里存的是数值和文本混排readtable会自动把整列转成cell数组后面做数值计算就直接报错。我处理这种问题已经有条件反射了——读完数据之后第一件事就是调用isnumeric检查每一列的类型发现异常立刻处理。2.2 用readtable高效读取Excel数据MATLAB自带的readtable函数是我读Excel的首选不用额外装工具箱速度也够快。基本的读取方式是这样的% 读取Excel数据 % 注意文件名和数据路径要改成你自己的 rawData readtable(聚类数据.xlsx, PreserveVariableNames, true); % 查看前几行快速确认数据形态 head(rawData) % 提取数值部分假设前两列是样本编号和标签后面的列都是特征 % 如果你的数据没有编号列直接全选即可 dataMatrix rawData{:, 3:end}; sampleNames rawData{:, 1};PreserveVariableNames这个参数值得多说一句。默认情况下readtable会把列名里的空格和特殊字符替换成下划线虽然不影响计算但后面画图的时候轴标签显示出来的就跟你Excel里对不上了。设成true能保留原始列名做演示或者写报告的时候更省心。如果数据量特别大几万行以上readtable会稍微有点慢但一般聚类场景下的数据规模完全在可接受范围内。真碰到几十万行的情况可以考虑改用readmatrix把文本列和数值列分两次读取不过这会牺牲一点代码简洁性非大数据场景没必要。2.3 归一化的必要性与具体方法我见过很多SOM的教程代码直接拿原始数据开训这是很普遍的误区。SOM在训练时要计算输入样本和所有神经元权值向量之间的欧氏距离。如果特征A的取值范围是0到1特征B的取值范围是0到10000那么距离计算基本就被特征B主导了特征A对聚类结果的影响几乎可以忽略——这不是数据本身告诉我们的事实纯粹是量纲带来的偏差。归一化有三种常见策略我做一个对比策略公式适用场景注意点Min-Max归一化(x - min) / (max - min)一般场景首选对离群点敏感会压缩大量正常数据的差异Z-score标准化(x - mean) / std特征服从近似正态分布保留了分布形状不受量纲影响小数定标x / 10^k数据本身量级差异不大的情况最简单粗暴效果略粗糙我个人在SOM聚类里最常用的是Min-Max归一化因为它把数据映射到0-1区间跟SOM神经元权值的初始化范围是一致的训练收敛会更快。但要注意离群点的问题——如果一个特征有一个值特别大Min-Max会把其他绝大多数值压缩到0-0.1的区间里信息几乎丢失。解决办法是先用箱线图看看各特征的离群点程度如果离群现象严重就改用Z-score或者对特征做分位数截断后再Min-Max。% Min-Max归一化函数 function dataNorm minMaxNormalize(data) minVals min(data, [], 1); maxVals max(data, [], 1); dataNorm (data - minVals) ./ (maxVals - minVals); % 防止除零如果某个特征所有值相同max-min为0 % 这时保留原值也就是0不影响距离计算 dataNorm(isnan(dataNorm)) 0; end这里有一个细节如果某个特征的所有样本值都一样那么max-min等于0归一化结果全部是NaN。MATLAB的NaN在距离计算里传染性极强一个NaN能让整行样本的距离都变成NaN最后获胜神经元全是乱选的。所以必须加上最后那行dataNorm(isnan(dataNorm)) 0;。这个bug不出现则已一出现就是灾难性的而且极其隐蔽——因为程序不报错只是结果莫名其妙地乱。3. SOM聚类核心算法实现3.1 网络结构与权值初始化SOM的输出层是一个二维网格常见的有矩形网格和六边形网格两种。六边形网格的可视化效果更美观邻域关系表达得更自然但MATLAB实现会稍微复杂一点矩形网格实现简单做U-matrix图也更方便所以我默认用矩形网格。网格的大小怎么定这是SOM应用里口碑分化最大的问题。网格太小多个簇会被挤到同一个神经元上聚类分辨率不够网格太大会出现大量永远不学习的死神经元白白浪费计算资源。经验法则是网格规模 ≈ 5 * sqrt(N)其中N是样本数量。更精确的做法是让网格的神经元数量接近你期望的簇数量的10倍左右。比如你根据业务预判大概有5到8个簇那10x10的网格就有100个神经元足够刻画拓扑结构又不至于太稀疏。% 初始化神经元权值 % gridSize [行数, 列数] % featureDim 特征维度 % 采用随机小数初始化和归一化后的数据范围保持一致 netWeights rand([gridSize, featureDim]);这里我直接用了rand函数随机初始化。有一些实现会用PCA对权值做初始化让神经元的初始分布沿着数据方差最大的方向展开这能加速收敛。但实际测试下来只要迭代轮数足够随机初始化和PCA初始化的最终聚类效果差别不大。为了让代码简洁我推荐随机初始化。3.2 核心训练循环竞争、合作、更新SOM的训练过程可以浓缩成三个步骤每一步都有明确的数学表达第一步竞争找出获胜神经元BMU对于每个输入样本x计算它与所有神经元权值向量之间的欧氏距离距离最小的那个神经元就是BMU。用MATLAB的矢量化写法可以一口气算所有神经元function bmuIdx findBMU(netWeights, sample, gridSize) diff netWeights - reshape(sample, 1, 1, []); dist squeeze(sum(diff.^2, 3)); [~, linearIdx] min(dist(:)); [row, col] ind2sub(gridSize, linearIdx); bmuIdx [row, col]; end第二步合作确定领域范围BMU旁边的神经元会受到牵连被拉动更新。这个牵连的范围用邻域函数来控制。邻域半径随训练进程逐渐缩小刚开始的时候范围很大大量神经元一起更新网络快速形成大致的地形后期范围很小只有BMU附近几个神经元微调让映射精度更高。距离度量用高斯函数来加权越靠近BMU的神经元更新的权重越大distToBMU sqrt((rowIdx - bmuRow).^2 (colIdx - bmuCol).^2); influence exp(-distToBMU.^2 / (2 * sigma^2));第三步更新权值调整每个神经元的权值按照当前学习率和领域影响因子向样本方向移动netWeights netWeights learningRate * influence .* (sample - netWeights);学习率会随着迭代轮数衰减常见有两种衰减方式线性衰减和指数衰减。我个人经验指数衰减更平稳learningRate initialLearningRate * exp(-t / tau); sigma initialSigma * exp(-t / tau);tau是时间常数控制衰减的速率。可以把它设成总迭代轮数的三分之一这样前期训练充分、后期收敛精细。3.3 训练主循环的完整实现把上面三个模块整合到一起配合批处理策略。SOM有两种训练模式增量式逐个样本更新和批处理式所有样本算完后统一更新一次权值。MATLAB的统计和机器学习工具箱中自带的selforgmap采用的是批处理模式稳定且收敛快。这里我也用批处理的方式实现function net trainSOM(data, gridSize, epochs, initLR, initSigma) % 输入 % data - numSamples x featureDim 的归一化数据 % gridSize - 输出网格的规模如 [10, 10] % epochs - 训练轮数 % initLR - 初始学习率通常0.5-0.8 % initSigma - 初始邻域半径通常取网格最大维度的一半 [numSamples, featureDim] size(data); netWeights rand([gridSize, featureDim]); tau epochs / 3; for t 1:epochs learningRate initLR * exp(-t / tau); sigma initSigma * exp(-t / tau); % 随机打乱样本顺序避免同一类样本连续出现导致的偏置 sampleOrder randperm(numSamples); weightUpdate zeros(size(netWeights)); updateCount zeros(gridSize); for i 1:numSamples sample data(sampleOrder(i), :); [bmuRow, bmuCol] findBMU(netWeights, sample, gridSize); % 计算领域影响 [rowGrid, colGrid] ndgrid(1:gridSize(1), 1:gridSize(2)); distToBMU sqrt((rowGrid - bmuRow).^2 (colGrid - bmuCol).^2); influence exp(-distToBMU.^2 / (2 * sigma^2)); % 累加权值更新量 for r 1:gridSize(1) for c 1:gridSize(2) weightUpdate(r, c, :) weightUpdate(r, c, :) ... influence(r, c) * (sample - reshape(netWeights(r, c, :), 1, [])); updateCount(r, c) updateCount(r, c) influence(r, c); end end end % 批处理更新用累计更新量除以总影响因子 for r 1:gridSize(1) for c 1:gridSize(2) if updateCount(r, c) 0 netWeights(r, c, :) squeeze(netWeights(r, c, :)) ... learningRate * squeeze(weightUpdate(r, c, :)) / updateCount(r, c); end end end end net netWeights; end代码里有两个实用细节值得注意第一个是randperm打乱样本顺序。如果原始数据里有大量的同类样本连续排列不洗牌会让网络在训练初期被某一类样本反复拉扯导致拓扑结构扭曲。第二个是批处理更新时用updateCount做加权平均避免样本密度不均匀导致的更新偏差。3.4 聚类标签生成从神经元到簇SOM训练完成之后每个样本都能找到自己的获胜神经元。但如果你把每个获胜神经元都当成一个簇聚类结果就太碎了——通常一个簇会覆盖一片相邻的神经元。所以还需要一步对神经元再做一次聚类。这里我推荐一个稳妥的两步走策略第一步计算每个样本的BMU统计每个神经元被多少个样本命中。如果某个神经元没有命中任何样本说明它是死神经元后续处理可以直接忽略。第二步对神经元权值向量再做一次K-means聚类。K的取值可以从U-matrix的可视化结果中大致判断或者用轮廓系数来辅助决定。K-means聚类对象不是样本而是神经元的权值向量因为神经元的数量例如100个远小于样本数量K-means在这种小规模聚类上速度极快而且稳定。% 对归一化样本找到每个样本的BMU numSamples size(dataNorm, 1); bmuList zeros(numSamples, 2); for i 1:numSamples [bmuList(i, 1), bmuList(i, 2)] findBMU(netWeights, dataNorm(i, :), gridSize); end % 提取神经元的权值做K-means聚类 neuronWeights reshape(netWeights, [], featureDim); numClusters 5; % 根据业务先验设定也可以用轮廓系数辅助选择 neuronClusters kmeans(neuronWeights, numClusters, Replicates, 10); % 每个样本的聚类标签 其BMU所在的簇 clusterLabels zeros(numSamples, 1); for i 1:numSamples neuronIdx sub2ind(gridSize, bmuList(i, 1), bmuList(i, 2)); clusterLabels(i) neuronClusters(neuronIdx); end这里还有一个增强版本的做法不是每个样本只取一个BMU而是取每个样本在输出网格上的投票扩展比如以BMU为中心取3x3邻域每个神经元投一票按权重累计。这样生成的聚类标签会更平滑不容易出现椒盐噪声。实际操作中如果你发现聚类标签在相邻样本间跳变严重、结果不稳定可以考虑这个方案。3.5 可视化U-matrix、命中图与标签图SOM之所以在数据探查场景深受欢迎很大程度上归功于它丰富的可视化手段。每次训练完必画三张图第一张U-matrix统一距离矩阵U-matrix画出每个神经元和其相邻神经元权值向量之间的平均距离。距离大的地方就是山脊是簇与簇之间的分界线距离小的地方是平原代表簇内部。U-matrix是最直观的聚类边界探测器不需要任何先验的K值。function plotUMatrix(netWeights, gridSize) uMatrix zeros(gridSize); for r 1:gridSize(1) for c 1:gridSize(2) neighbors []; if r 1, neighbors [neighbors; r-1, c]; end if r gridSize(1), neighbors [neighbors; r1, c]; end if c 1, neighbors [neighbors; r, c-1]; end if c gridSize(2), neighbors [neighbors; r, c1]; end weight squeeze(netWeights(r, c, :)); distSum 0; for k 1:size(neighbors, 1) neighborWeight squeeze(netWeights(neighbors(k, 1), neighbors(k, 2), :)); distSum distSum sqrt(sum((weight - neighborWeight).^2)); end uMatrix(r, c) distSum / size(neighbors, 1); end end imagesc(uMatrix); colormap(jet); colorbar; title(SOM U-Matrix); end第二张样本命中图把每个神经元被命中的样本数量画出来。这个图能让你直观看到哪些神经元承担了大部分样本哪些是死神经元。如果某个区域的神经元命中数特别高说明这里是一个密集簇如果一片神经元都没有命中说明这是空旷区刚好就是簇之间的缓冲地带。第三张聚类标签图把每个神经元的簇分类结果用不同颜色画在网格上再叠加样本命中数量。这张图最直接——你可以一眼看出数据大概分成几块、每块有多大、边界在哪里。这也是交付给业务方时最需要的一张图。% 展示聚类标签图 figure; imagesc(neuronClustersReshaped); % 神经元簇标签reshape成gridSize colormap(lines); colorbar; title(SOM神经元聚类分布);3.6 结果导出到Excel聚类任务到了一定阶段都要交付成果。我的做法是把结果汇总到一个结构化表格里导出% 汇总结果并导出到Excel resultTable table(sampleNames, clusterLabels, VariableNames, {样本编号, 聚类标签}); % 如果还想附带每个样本的BMU坐标 resultTable.BMU行 bmuList(:, 1); resultTable.BMU列 bmuList(:, 2); % 按聚类标签排序 resultTable sortrows(resultTable, 聚类标签); writetable(resultTable, SOM聚类结果.xlsx);这里有个容易出问题的地方如果样本编号列原本是数值直接放进table再导出Excel里会显示成科学计数法或丢失前导零。建议把样本编号统一转成字符串再去导出保证原始标识不丢。4. 参数选择与优化SOM训练的关键决策4.1 网格大小怎么定关于网格大小前面给过一个经验公式5*sqrt(N)但实际项目里还需要考虑业务需求。网格规模直接决定了聚类的分辨率。网格太大比如50x50处理1000个样本大量神经元没有任何样本命中训练时间也急剧上升。网格太小比如5x5处理1000个样本簇边界被过度平滑很多细节丢失。折中方案先跑一次默认网格看U-matrix里呈现出的山谷和山脊的清晰程度再调整。如果数据集规模不大几百到几千条记录20x20以内的网格基本够用。超过这个范围训练时间会成倍增加收益却很有限。除非你需要非常精细的拓扑映射否则不要追求大网格。4.2 学习率与邻域半径的调参策略初始学习率一般来说0.5到0.8都可以。太高会导致权值振荡太低则收敛过慢。初始邻域半径设成输出网格最大边长的一半是比较稳妥的经验值——比如10x10的网格初始sigma取5附近15x15的网格初始sigma取8附近。这里要特别提醒sigma的初始值如果太小训练初期就只更新BMU自己邻域协作能力丧失拓扑结构就会乱如果太大所有神经元一起更新又退化成了类似全局平均的更新收敛变得很慢。衰减速度方面我的经验是tau epochs / 3这个比例下效果比较稳。4.3 迭代轮数的判断训练轮数设多少合适理论上要保证每个样本都有足够多次机会参与权值更新。一个粗略的下限是epochs 样本数 * 5 / 网格神经元数但实际训练时还有一个更实用的判断方法观察量化误差每个样本到其BMU的平均距离是否收敛。如果量化误差曲线已经拉平继续训练已经没有新的信息加入就可以停了。% 量化误差随迭代下降的可视化 % 在训练主循环里添加量化误差记录 quantizationError zeros(epochs, 1); for t 1:epochs % ... 训练代码 ... errorSum 0; for i 1:numSamples [r, c] findBMU(netWeights, dataNorm(i, :), gridSize); neuronWeight reshape(netWeights(r, c, :), 1, []); errorSum errorSum sqrt(sum((dataNorm(i, :) - neuronWeight).^2)); end quantizationError(t) errorSum / numSamples; end量化误差本身还有一个用途对比不同网格规模下的结果。在相同迭代轮数下如果20x20网格的量化误差只比10x10网格低不到5%那就说明10x10网格已经足够不需要为了那一点误差去付出4倍的训练时间。4.4 死神经元与拓扑缺陷的处理SOM训练完成后经常会发现一些神经元从未被任何样本命中。它们的权值向量停留在初始化的随机状态在U-matrix上表现为一块突兀的噪点区。处理方案有几个增加训练轮数很多死神经元出现是因为训练不充分样本还没有来得及覆盖那些区域。调整学习率衰减速度把tau调大让学习率衰减更平缓后期仍有足够的调整能力。使用惩罚机制在训练中跟踪每个神经元的命中次数对命中过少的神经元施加一个拉力让它们向高密度区域靠近。这个方案实现起来稍微复杂一点但最有效。如果死神经元数量超过总神经元数量的30%大概率是网格尺寸设置过大了而不是训练的问题。优先把网格缩小再重训。5. 常见问题与排查技巧实录5.1 Excel读取常见报错与解法读Excel时最容易中招的几种情况情况一readtable返回的列类型不是double很多时候Excel在某一列的个别单元格里混入了文本MATLAB就把整列识别成cell数组。这时候dataMatrix rawData{:, 3:end}会报错。排查方法是先执行varfun(class, rawData, OutputFormat, cell)看看每一列的类型定位到异常列后单独清洗。情况二中文变量名乱码MATLAB对中文的支持在不同版本上有差异。如果Excel列名是中文readtable读取后在表格变量名里显示乱码PreserveVariableNames设置也没有用。解决办法是用readcell读取然后用cell2table构建表格变量名手动设置rawCell readcell(聚类数据.xlsx); varNames rawCell(1, 3:end); dataCell rawCell(2:end, 3:end); dataMatrix cell2mat(cellfun(str2double, dataCell, UniformOutput, false));情况三数据量太大导致读取缓慢Excel超过10万行后readtable的性能会有明显下降。这时可以把Excel转成CSV格式再读取速度提升数倍。CSV没有Excel的格式信息但纯数值数据完全够用。5.2 训练结果不稳定多次运行聚类标签不一致SOM本身相对稳定但如果你采用了随机初始化加K-means二次聚类依然可能出现多次运行结果在细节上略有不同。排查方向有两个第一个方向是检查K-means的初始中心。在二次聚类阶段固定K-means的随机种子rng(42); % 固定随机种子 neuronClusters kmeans(neuronWeights, numClusters, Replicates, 10);第二个方向是检查SOM训练的随机性。SOM的随机性来自权值初始化和样本训练顺序的随机洗牌。如果确定随机种子后结果依然波动那就是邻域半径的衰减太快导致后期拓扑结构还没稳定就停止了调整。把tau调大20%再试一次。5.3 聚类结果与业务预期不符这是最常发生的情况——算法给出的簇和业务方心里的类对不上。我的处理思路是这样的先别急着改算法先确认数据预处理有没有问题。查看每个簇的特征均值分布如果某个簇的特征均值几乎等于全数据集均值说明这个簇是垃圾桶很可能应该被拆开或者是从其他簇分离不彻底的边界样本。如果预处理没问题再看K-means二次聚类的簇数量是否合理。SOM的U-matrix其实给了你一个更客观的簇数量判断依据——数一数U-matrix图像里有几个明显的盆地。如果U-matrix显示4个盆地但你硬分成了7个簇那得到混乱的结果是必然的。5.4 独门调试技巧SOM训练过程的中间状态这是我自己在调试中摸索出来的小技巧也算是一个很实用的习惯在训练过程中每隔一定轮数保存一次神经元权值快照训练结束后把这些快照做成一幅动画依次播放。这样你就能直观地看到神经元是如何从随机分布逐渐铺满数据分布区域的。具体做法是在trainSOM函数里加一个可选参数saveSnapshot每隔ceil(epochs / 20)轮把netWeights存储到cell数组里。训练结束后用animatedline或imagesc逐一绘制。这个过程对于判断拓扑收敛失败还是初始化不当非常有帮助——如果前几轮的神经元权值就已经乱成一团后期很难修正回来。5.5 性能优化建议加速大样本训练当处理几万条样本时SOM的训练速度会成为瓶颈。最有效的加速手段是矩阵化代替循环。MATLAB的循环效率不高尤其是双层嵌套循环。我重构过一版全矩阵化的训练Core把内层的样本循环向量化速度提升了将近20倍。核心思路是把所有样本同时计算与所有神经元之间的距离形成一个numSamples x gridRow x gridCol的张量然后一次性选出每个样本的BMU。代码稍复杂一些但量级上去之后收益极其明显。另外如果装了Parallel Computing Toolbox还可以用parfor代替单纯的内层循环。不过要注意在parfor里修改netWeights需要注意数据的切片方式需要把netWeights改成按行分块每个worker只更新自己负责的那一部分最后合并。6. 实际项目经验小结写这个程序前后我自己也走过不少弯路。SOM这个东西看原理觉得很简单真正落地的细节其实非常多。最后分享几个我用下来最有价值的习惯习惯一归一化参数要保存下来归一化过程中的min、max、mean、std这些值一定要保存到.mat文件里。因为聚类模型训练完成后后续可能还要对新样本做预测这时要用训练集的归一化参数对新样本做变换。如果直接用新样本自己算min和max数据分布一变映射关系就对不上了聚类结果自然也是错的。习惯二网格尺寸留一点余量经验公式给出来的网格尺寸只是一个起点。如果算出来是8x8我会尝试10x10如果算出来是12x12我会尝试15x15。稍微大一点的网格不会明显增加训练时间但能更清晰地展示拓扑结构对后续的可视化分析更友好。习惯三把聚类标签的分布数一数导出聚类结果后第一件事是查看每个簇的样本数量分布。如果出现了样本数量极少的孤立簇只有一个样本这个样本大概率是离群点。把它单独列出来看特征值比把它硬塞进某个大簇更有分析价值。习惯四SOM的训练结果一定要配图存档只给出一列聚类标签的分析报告说服力会显得不够。但如果配上一张U-matrix图再叠加一张聚类标签分布图别人就能清晰地理解你的聚类依据、边界在哪里、每个簇的密集程度——这比为每个簇写一大段文字描述直观得多。SOM是一个很有意思的工具它把抽象的数据分布变成了可看的地图让聚类分析不再只是黑箱输出。上面写到的这些代码和调参经验都是我在多个实际项目里验证过的。如果你也在用MATLAB做数据聚类不妨直接拿这套流程去跑一版看看路过坑的地方我已经尽量帮你标记清楚了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑