ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Matlab多分类混淆矩阵绘制指南:从原理到错误分析

Matlab多分类混淆矩阵绘制指南:从原理到错误分析 简介本资源面向计算机、电子信息工程及数学等专业的本科生聚焦多分类任务中混淆矩阵的可视化实现适用于课程设计、期末大作业或毕业设计中的模型评估环节。压缩包共17个文件51KB含10个MATLAB源码文件.m用于核心绘图与指标计算5个.mat数据文件封装真实预测标签、实际标签、决策值及类别名称等关键变量2个.txt文档提供使用说明与背景介绍结构清晰、即开即用。已有52人学习下载适合具备基础MATLAB编程能力的学习者参考使用。读者可直接运行main.m复现完整流程快速掌握多分类混淆矩阵的生成、归一化、热力图绘制及准确率、精确率、召回率等衍生指标计算方法并基于现有框架灵活适配自定义数据集与分类器输出。 做过多分类项目的朋友应该都有这种经历模型测试集准确率90%以上看着指标挺漂亮但把预测结果往业务场景一放发现某几个类别频繁出问题你根本不知道是模型学得不好还是数据标注本身就有问题。这种时候混淆矩阵就是最直接的诊断工具。它可以展示每个真实类别下模型分别把它预测成了什么类别一眼就能看出模型在哪些类别之间容易搞混。这篇博文围绕Matlab下多分类混淆矩阵的绘制展开配合源码和数据演示完整覆盖从矩阵计算、可视化到错误分析的整个流程。无论你是刚入门深度学习、正在做课程设计还是需要给论文补实验图表这套内容都能直接拿去用。1. 为什么多分类评估绕不开混淆矩阵1.1 准确率不够用一个数字掩盖了太多问题先抛一个场景假设你训练了一个ResNet18在10类图像分类任务上测试集准确率达到90%。这个数字能说明模型整体不错但你能不能立刻回答下面几个问题第7类和第9类是不是经常互相认错模型是不是把大部分样本都倾向于预测成第3类导致第3类的准确率虚高有哪些类别几乎没被正确预测过模型对这个类别完全没学起来这些问题准确率一个数字完全给不了答案。尤其当分类类别数量增多后模型的行为会越来越复杂单靠一个总体指标做决策很容易把局部崩塌掩盖在整体好看的表面之下。混淆矩阵本质上是一张交叉计数表行代表真实类别列代表预测类别每个格子里的数字表示真实为行类别且被预测为列类别的样本数。它把模型在每个类别细粒度上的表现完整摊开问题藏在哪个角落都能找到。做深度学习、机器学习项目时我的习惯是任何分类模型跑完测试第一件事就是打印混淆矩阵第二件事是根据混淆矩阵计算逐类的精确率、召回率和F1最后才看总体准确率。顺序不能反一旦反了你很可能被一个好看的总分骗过去白白浪费时间调一个根本没问题的模型。1.2 多分类混淆矩阵怎么读看懂行和列很多人刚接触混淆矩阵时最常犯的错是搞不清行和列各代表什么。这里直接说清楚矩阵的每一行代表样本的真实标签Ground Truth矩阵的每一列代表模型的预测标签Predicted Label对角线上的格子代表预测正确数值越大越好非对角线上的格子代表预测错误数值越大说明这两类越容易被混淆举个例子在一个5分类问题中如果第2行第4列的数值是15含义就是真实类别为第2类的样本中有15个被模型错误地预测成了第4类。如果这个数字明显偏大说明模型在区分第2类和第4类上存在系统性困难。这种困难可能来自类别本身特征相似、训练样本不均衡也可能是标注人员在标注时就把两类混在了一起。有了这层理解你再看混淆矩阵就不是看花不花了而是能快速回答模型最容易错的类别是哪个、哪个类别被错误预测的次数最多、错误集中在哪些类别对上。这些信息比任何单一指标都更能指导下一步改进方向。1.3 这套演示里用到的数据和你自己的数据配套的源码包里面放了演示数据和源码文件。演示数据是经典的鸢尾花Iris数据集通过一个简单的多分类模型生成预测结果用来说明混淆矩阵的完整绘制流程。选这个数据集的原因很简单类别数适中3类、样本量不大每类50个、每个人都能很快理解非常适合做演示。你拿到手后最关心的一定是我自己的数据怎么套进去。这里说明一下Matlab中混淆矩阵绘制的输入格式需求最直接的输入是两列向量一列是真实标签trueLabels一列是预测标签predictedLabels。标签可以是数值型如1、2、3、字符型或字符串型如cat、dogMatlab都能处理如果之前已经把结果存成了N×N的计数矩阵N为类别数矩阵第i行第j列表示真实标签为i且预测为j的样本数也可以直接基于这个矩阵画图最简单的方式是把预测结果和真实标签保存到一个Excel或MAT文件里然后像下面这样读取并调用即可% 从Excel读取真实标签和预测标签 data readtable(your_data.xlsx); trueLabels data.TrueLabel; predictedLabels data.PredictedLabel; % 调用自定义绘制函数详情见第3章 plotConfusionMatrix(trueLabels, predictedLabels);如果你是做神经网络分类通常在测试阶段就能拿到预测标签。这里有个容易忽略的细节在Matlab的深度学习工作流里classify函数返回的是预测标签predict函数返回的是每个类别的得分矩阵两者不一样。画混淆矩阵用的是classify的结果别搞混了。2. Matlab画混淆矩阵的两种主流方案2.1 方案一内置confusionchart三行代码出图从R2018b开始Matlab内置了confusionchart函数这是目前最省事的画法。它接收真实标签和预测标签两个参数直接生成一个带颜色深浅、格内计数和坐标轴标签的混淆矩阵图。% 生成演示数据 load fisheriris rng(42) % 这里用随机森林作一个示例分类器 mdl fitcecoc(meas, species, Coding, onevsone); predictedLabels predict(mdl, meas); trueLabels species; % 画混淆矩阵图 figure; cm confusionchart(trueLabels, predictedLabels); cm.Title Iris 数据集多分类混淆矩阵; cm.ColumnSummary column-normalized; cm.RowSummary row-normalized;这段代码运行后confusionchart会绘制一张带行列汇总的混淆矩阵图。默认情况下格子颜色越深表示数量越大或比例越高鼠标悬停还能显示具体数值。最有用的两个属性是ColumnSummary和RowSummary设置后会在图表右侧和下侧额外显示每个类别的精确率和召回率汇总。这个功能在做快速实验验证时非常方便不用额外计算就能直接看到每个类别的表现。confusionchart还支持直接传入一个混淆矩阵C confusionmat(trueLabels, predictedLabels)而不是原始标签figure; confusionchart(C, classLabels);这对于从别处拿到现成矩阵数据的情况很实用。如果你手里的矩阵是归一化后的百分数也可以传入百分比矩阵Matlab会自动按数值着色。2.2 方案二自绘heatmap版彻底掌控每个细节虽然confusionchart方便但我自己在实际写论文时用得更多的反而是自绘版本。原因很直接confusionchart的布局和样式相对固定想调整每个格子的字体大小、颜色映射范围、坐标轴间距时可定制性不够灵活而且在高分辨率导出时某些版本生成的图边缘会有多余的留白插到论文双栏排版里不够紧凑。自绘方案用imagesc或heatmap配合text函数几乎能做到每个像素都由自己控制。下面给出一个自绘版本的完整代码。这个函数接收真实标签、预测标签和可选的类别名称列表输出一张可直接保存的混淆矩阵热力图function plotConfusionMatrix(trueLabels, predictedLabels, classNames) % 计算混淆矩阵 % 如果指定了类别名称按指定顺序排布否则按unique顺序 if nargin 3 || isempty(classNames) labels unique([trueLabels(:); predictedLabels(:)]); else labels classNames; end C confusionmat(trueLabels, predictedLabels, Order, labels); % 按行归一化即每个真实类别中的比例 C_norm C ./ sum(C, 2); figure(Color, w, Position, [100 100 650 550]); imagesc(C_norm); axis square; colormap(parula); colorbar; % 设置坐标轴刻度 n length(labels); set(gca, XTick, 1:n, YTick, 1:n); set(gca, XTickLabel, labels, YTickLabel, labels); set(gca, FontSize, 12); % 在每个格子上写入数值 for i 1:n for j 1:n % 显示原始计数同时保留归一化信息 str sprintf(%d\n(%.1f%%), C(i,j), C_norm(i,j)*100); % 根据背景颜色深浅决定文字颜色 if C_norm(i,j) 0.5 text(j, i, str, HorizontalAlignment, center, ... Color, w, FontSize, 10); else text(j, i, str, HorizontalAlignment, center, ... Color, k, FontSize, 10); end end end % 坐标轴标签 xlabel(预测标签, FontSize, 13); ylabel(真实标签, FontSize, 13); title(多分类混淆矩阵, FontSize, 14); % 反转y轴方向让真实标签第一类显示在最上方 set(gca, YDir, reverse); end这段代码每一行都是有用的我解释几个关键点confusionmat的Order参数用于指定类别排序。如果把类别顺序搞错整个矩阵的行列就全错位了后面会专门讲这个坑归一化方式是按行归一化即C ./ sum(C, 2)这样每个格子里显示的是该真实类别下预测结果的分布比例。论文中最常用的就是这种归一化方式我用sprintf同时显示原始计数和百分比这样既保留了绝对数量的信息又方便看比例。格子里既有计数又有占比评审看起来也更专业if C_norm(i,j) 0.5这句可以保证深色格子里的文字用白色、浅色格子用黑色可读性大幅提升。如果不做这个判断会出现深蓝色背景里写黑色数字的尴尬情况2.3 两种方案怎么选把两种方案放在一起对比下面这张表可以直接帮你做决定对比维度confusionchart自绘heatmap版代码量极少2-3行较多50行左右可定制性一般受限于内置属性极高每个元素都能改额外信息显示支持行列汇总可同时显示计数和百分比中文标签支持需额外设置字体同样需设置字体论文排版适配边缘留白较多可控性强数据格式要求原始标签或矩阵均可原始标签或矩阵均可Matlab版本要求R2018b及以上所有版本均可我的建议是平时做实验、看模型效果直接用confusionchart就够了快速方便写论文、做汇报、需要精细控制图表样式时用自绘方案。接下来的内容会围绕自绘方案深入展开因为它涉及到的原理和细节最值得系统学习——理解了自绘方案的所有参数你再用回confusionchart时也会更清楚它背后的逻辑。3. 自绘版本的核心实现逻辑3.1 计算混淆矩阵confusionmat的参数细节confusionmat是Matlab自带的混淆矩阵计算函数用法非常灵活。基本调用方式如下C confusionmat(trueLabels, predictedLabels);如果没有指定Order参数Matlab会自动把两个输入向量中所有出现过的类别按升序或字母顺序排列生成一个k×k的矩阵k为类别总数。这里有个容易踩的坑如果你的真实标签里包含1、2、3类但预测标签里某些类一个都没出现confusionmat默认会只取两者中出现的并集。换句话说矩阵尺寸可能不是你期望的类别总数这在后续展示和分析时会造成困扰。更好的做法是手动指定全部类别顺序% 假设模型总共有5个类别 classNames {setosa, versicolor, virginica}; C confusionmat(trueLabels, predictedLabels, Order, classNames);指定Order后矩阵的行列顺序严格按classNames排列如果某个类别没有样本矩阵中对应行列就是全0但矩阵尺寸是完整的3×3。这样画出来的混淆矩阵结构才稳定后续做逐类指标计算也不会因为矩阵尺寸变化而出错。3.2 归一化的三种方式和适用场景混淆矩阵里的数值有三种常见的归一化方式不同场景要用不同的方式。按行归一化每行和为1C_norm C ./ sum(C, 2)。这个方式对应的是召回率视角——在真实类别为i的样本中模型有多大比例预测正确、有多大比例误判到其他类别。论文中最常用这种归一化因为它能直接反映某个类别被模型识别得怎么样。如果某行对角线上的值特别低说明模型对这个类别完全没学会。按列归一化每列和为1C_norm C ./ sum(C, 1)。这个方式对应的是精确率视角——在模型预测为类别j的样本中有多大比例真实确实是类别j。当某些类别有大量样本被误判进来时用列归一化能清楚地看到这个类别的纯度被冲低了。全体归一化所有格子总和为1C_norm C ./ sum(C, all)。这种方式显示的是每个格子占全体样本的比例适合样本量差异极大的场景用来观察整体错误分布。但它的缺点是数值普遍偏小作为论文图表时的直观性不如前两种。画混淆矩阵时我基本都用按行归一化原始计数同时显示的方案因为观众最关心的通常是每个类别的识别准确率行归一化的颜色深浅能够直接表达这个信息而格子里同时写上的原始计数又提供了误差分布的实际数量感。3.3 热力图上色和标签定位的实现要点自绘混淆矩阵的核心可视化元素有三个背景色、格子文字、坐标轴标签。每个都有值得注意的细节。背景色映射imagesc(C_norm)会把矩阵数值线性映射到当前colormap上。Matlab默认的parula是蓝黄色系在混淆矩阵场景下很常用。如果你想做深色高值的经典样式可以自定义一个从白色到深蓝色的渐变% 自定义从白色到深蓝色的colormap cmap [linspace(1, 0.2, 256), linspace(1, 0.4, 256), linspace(1, 0.8, 256)]; colormap(cmap);这样颜色越深代表数值越大视觉上更接近大多数论文里用的样式。如果你想用红绿色系如jet我建议谨慎使用——红绿色盲读者会看不清楚而且jet在数值渐变上的感知均匀性远不如parula或viridis。文字位置定位imagesc的坐标系和普通坐标轴不太一样。imagesc(C)画出来的图每个格子的中心位置会落在整数坐标上。因此要在第i行第j列的格子中央显示文字text函数的坐标应该是(j, i)。如果发现文字位置偏了很可能是你用了plot的索引习惯把行当x轴了记得imagesc中第一个数据维度是行对应y轴第二个是列对应x轴。坐标轴上的刻度标签和方向也要处理好。set(gca, YDir, reverse)这个设置是必须的——矩阵的第1行在数据空间里默认显示在坐标轴下方但混淆矩阵的惯例是真实标签第1类在最上方所以要反转y轴方向。这个小细节直接决定了你的矩阵是不是对角线朝右上的标准样式。4. 我实际跑代码时踩过的几个坑4.1 类别顺序错乱导致矩阵看着像错位上次给一个学生调代码的时候他跑出来的混淆矩阵对角线上全是小数非对角线反而有个大数整个矩阵看起来花里胡哨完全没法看。排查了半天最后定位到问题他传给confusionmat的真实标签是[2, 1, 3, 2, ...]预测标签是[1, 2, 3, 1, ...]而没有指定Order。Matlab自动排序时把两边的类别顺序统一成了[1, 2, 3]看起来应该没问题。真正的原因是他自己写的画图代码里用了另一套类别顺序做坐标轴标签把标签和矩阵行列对不上号导致视觉上矩阵是矩阵标签是标签看起来当然别扭。解决方案很简单在计算矩阵和绘制图表时统一由同一个类别列表变量驱动。classNames categories(trueLabels); % 或手动指定 C confusionmat(trueLabels, predictedLabels, Order, classNames); % 后续画图全部使用classNames作为标签来源这个方法在数据量大的时候尤其重要。当你同时跑了多个模型、多组实验时统一类别顺序能避免每组图之间因排序不一致而难以横向对比。4.2 归一化时除零某些类别在测试集里一个样本都没有多分类问题中经常出现类别不均衡有些占比很小的类别可能在某一批测试数据中正好没有出现。这时候sum(C, 2)计算出来会是0再做除法就得到NaN。而NaN在imagesc里会显示成空白或颜色异常整个图看起来就花了。解决这个问题有两种思路。第一种是在归一化之前做判断把分母为0的行直接置0rowSum sum(C, 2); rowSum(rowSum 0) 1; % 避免除零 C_norm C ./ rowSum;第二种是保留原始计数矩阵归一化矩阵只用于着色文字部分就不显示百分比只显示原始计数。我的建议是两种都要做分母保护是为了让颜色不出现空白文字同时显示计数和百分比则是为了信息完整。4.3 中文标签显示成方块Matlab的默认字体在Windows和macOS上不同在Linux上更是五花八门默认情况下中文标签常显示成□□□。这算是用Matlab做中文图表时最经典的问题了。解决方案是在绘制坐标轴标签之前设置支持的字体set(gca, FontName, 宋体); % Windows % 或者使用系统中文字体 % set(gca, FontName, PingFang SC); % macOS % set(gca, FontName, Noto Sans CJK SC); % Linux如果你不想为了字体问题折腾也可以用英文标签这在论文中反而更常见。比如把类别名设为Class 1、Class 2既避免了乱码风险又让图表更符合国际期刊的排版习惯。如果中文必须用另一个更稳妥的方式是把图片导出后再用外部排版工具重新标注不过这就比较麻烦了。4.4 保存图片时清晰度不够很多人画完图直接点保存导出的图片放到Word或论文里就模糊了。这是因为默认导出的分辨率太低。建议用print命令手动保存并指定高分辨率% 导出300DPI的PNG图片 print(gcf, confusion_matrix, -dpng, -r300); % 导出矢量图投稿期刊推荐 print(gcf, confusion_matrix, -depsc);如果你用的是新版Matlab也可以直接点击图窗工具栏的导出在导出设置里把分辨率调高但手动用命令的好处是可复现——每次运行脚本都能生成同样输出的文件不会漏掉设置选项。期刊投稿时线稿图通常要求矢量格式EPS或PDF而PNG适合放在PPT和实验记录里。同一张混淆矩阵图我会两种格式都导出方便后续在不同场景使用。5. 画完图之后怎么从混淆矩阵里读到模型问题5.1 混淆矩阵搭配逐类指标比单看准确率有用得多混淆矩阵不只是一张图它背后可以计算出完整的逐类评估指标。在论文中通常需要同时给出每个类别的精确率Precision、召回率Recall和F1分数。这三个指标可以直接从混淆矩阵算出C confusionmat(trueLabels, predictedLabels, Order, classNames); n size(C, 1); % 精确率 对角线 / 列和 precision diag(C) ./ sum(C, 1); % 召回率 对角线 / 行和 recall diag(C) ./ sum(C, 2); % F1 2*P*R / (PR) f1 2 * precision .* recall ./ (precision recall); % 避免除零某类别没有任何样本时 precision(isnan(precision)) 0; recall(isnan(recall)) 0; f1(isnan(f1)) 0;逐类指标的价值在于它能帮你定位整体准确率看起来不错但实际有类别失效的问题。比如某个类别召回率只有40%但精确率是100%说明这个类别极难被识别出来一旦识别出来就基本是对的但大部分样本都被漏掉了。这种漏报问题在工业质检和医学诊断场景下代价极高只看总体准确率根本发现不了。5.2 识别三类系统性错误真正会看混淆矩阵的人会关注三类模式每类对应的改进方向完全不同。第一类类别对之间互相混淆。如果矩阵中第i行第j列和第j行第i列的数字都偏大说明模型在区分这两个类别时存在困难。常见原因是特征本身相似比如不同品种的狗或标注质量不高导致标签本身有噪声。这种情况下增加数据量或增大模型容量未必有用更有效的做法是检查训练集中这两个类别的样本质量和数量差异。第二类某一列整体数值偏高。如果混淆矩阵中第j列除了对角线的数值明显大于其他列说明模型存在预测偏好——它倾向于把不确定的样本都分到第j类。这种情况常见于类别不均衡的训练集模型学到的是多猜这个类别反正期望损失更小。解决思路是使用类别权重、过采样/欠采样或调整分类阈值。第三类某一行整体数值都很低。如果矩阵中第i行除了对角线外几乎没有数字分布而对角线上的值也很小说明这个类别的样本在测试集中被预测正确的情况很少。这个类别可能训练样本不足也可能其特征在特征空间中被其他类别完全覆盖。这个类别基本可以判定为模型完全没有学会。定位到具体是哪一类系统性错误你就能有的放矢地去调数据、调模型而不是盲目堆算力。5.3 批量跑多个实验时一键出所有混淆矩阵当你在调参、对比多个模型或做交叉验证时手动一张一张画图效率太低。建议把整个流程封装成一个函数然后循环调用。% 保存为 plotConfusionMatrix.m function plotConfusionMatrix(trueLabels, predictedLabels, classNames, saveName) % 绘图代码见第2.2节 % 最后加保存逻辑 if nargin 3 ~isempty(saveName) print(gcf, saveName, -dpng, -r300); end end % 批量处理多个模型 models {resnet18, resnet50, mobilenetv2}; for i 1:length(models) preds getPredictions(models{i}); % 你自己实现的结果读取函数 plotConfusionMatrix(testLabels, preds, classNames, [cm_ models{i}]); end这样做的好处不仅仅是节省时间更重要的是产生的结果是完全一致的格式对比起来省心。我自己的习惯是把每次实验的混淆矩阵图自动保存到以日期为文件名的文件夹中跑完后直接按时间顺序翻图就能快速看到模型改动带来的变化趋势。6. 最后分享几个项目落地时的小技巧混淆矩阵画出来只是第一步实际项目里我发现有几件事做好之后整个评估流程会顺畅很多。第一把混淆矩阵的数值同时导出到Excel。图适合人眼观察但Excel适合做进一步的数据分析比如计算每类的错误总数、找出Top 3混淆对、统计错误样本中哪些是标注错误的。我个人习惯是用writematrix把矩阵直接保存成CSVwritematrix(C, confusion_matrix.csv);这样后续用Python做统计分析、画其他图表时数据可以直接复用不用重新跑一遍模型。第二给混淆矩阵配上原始错误样本列表。只看矩阵可以知道第2类和第4类容易混淆但到底哪些图片被分错了还是得回到原始样本上去看。建议在读取预测结果时把预测错误样本的索引、真实标签、预测标签、置信度一起存入表格方便随时抽查。% 记录预测错误的样本 isWrong trueLabels ~ predictedLabels; errorTable table(find(isWrong), trueLabels(isWrong), predictedLabels(isWrong), ... scores(isWrong, :), VariableNames, {Index, True, Pred, Scores}); writetable(errorTable, error_analysis.xlsx);有了这个表当你需要分析为什么模型会错时直接把错误样本调出来看比对着矩阵数值猜原因高效得多。第三把混淆矩阵的颜色映射范围固定下来。默认情况下imagesc会按数据的最大值和最小值自动映射颜色。这导致不同实验的混淆矩阵即使结构一样颜色深浅也不能直接横向对比。需要设定统一的颜色范围caxis([0 1]); % 或者用新版Matlab的clim([0 1]) % 统一所有图的颜色范围到0~1我踩过这个坑之前跑两组对照实验两组准确率分别是92%和89%但因为颜色映射范围不同92%那组的混淆矩阵对角线颜色反而比89%那组浅汇报时被同事误以为新方案效果更差。统一颜色范围后图的视觉对比和实际数据一致这种误读就能完全避免。第四注意Matlab版本的兼容性。confusionchart是R2018b才有的clim替代caxis是在R2022a之后的行为。如果你要分享源码给其他人使用建议在脚本开头加好版本判断或者直接使用兼容性最好的自绘方案。这不仅是给别人省事也是给自己省事——你永远不知道自己换个电脑跑代码时会遇到什么版本依赖。多分类混淆矩阵的绘制本身不难真正体现功底的地方是对细节的把控类别顺序的统一、归一化方式的正确选择、颜色映射的合理设定、图表的可复现性。把这些细节都做到位之后你会发现自己分析模型问题的效率会有明显提升不管是做学术实验还是落地项目这套流程都能直接复用。本文还有配套的精品资源点击获取
返回列表