简介:本资源面向计算机、电子信息工程及数学等专业的本科生,聚焦多分类问题中混淆矩阵的可视化实现,适用于课程设计、期末大作业或毕业设计阶段的算法验证与结果展示需求。压缩包共17个文件(10个MATLAB脚本文件用于核心绘图与指标计算,5个.mat数据文件含真实标签、预测结果及分类置信度等关键变量,2个txt说明文档提供使用指引与类标映射),整体仅51KB,轻量易部署。资源已获52人学习下载,内容结构清晰:包含主运行脚本main.m、混淆矩阵生成模块ConfusionMatrices、精度/召回率分析脚本PrecisionRecall及PG_Curve绘图支持,配套name_class.mat与num_in_class.mat确保多类别标签可读性与统计一致性。读者可直接运行复现标准混淆热力图,并基于源码快速适配自定义分类器输出,掌握从预测结果到评估可视化的完整技术链路。 咱们搞分类模型的人,电脑里几乎都存着这么一份资源:《基于Matlab实现多分类问题中混淆矩阵的画法(源码+数据).rar》。我当年第一次做多分类实验时,也是从这类包里东拼西凑学起来的。这标题看着挺直白,但真正用起来,很多人卡在第一步:明明训练好了模型,预测结果也对,但矩阵图就是画不出来,或者画出来了却不知道怎么看、怎么用。这篇博文我就把这个资源包背后的完整链路拆开讲清楚,从混淆矩阵的原理、源码逻辑,到数据怎么组织、参数怎么改,再到怎么把矩阵图变成论文里能直接用的高质量图片,一次性帮你把这块短板补上。内容对刚接触Matlab分类任务的研究生、做算法对比的工程师,以及所有被导师催着出图的人,应该都有参考价值。
1. 混淆矩阵在多分类任务中的地位
1.1 为什么二分类那套评估方法不够用了
做二分类的时候,大家习惯看准确率、精确率、召回率、F1,配合ROC曲线和AUC值,基本能说清楚模型好坏。但一旦切到多分类场景,比如手写数字识别要分10类、遥感影像地物分类要分几十类,这些指标就变得不太好用了。你会遇到几个很实际的问题:整体准确率可能是高的,但到底哪些类别经常被搞混?模型是把“猫”错认成“狗”多,还是把“狗”错认成“狐狸”多?每一类的召回率分别是多少?这些问题,靠一个总体准确率完全回答不了。
多分类里更麻烦的是类别不平衡。假设三类样本比例是90:5:5,模型全预测成第一类,准确率也有90%,看起来很漂亮,实际却是废的。这时候就需要一个能把每一类、每一对类别的预测结果都摊开来看的工具。混淆矩阵(Confusion Matrix)就是干这个的。它是一个N乘N的表格,N是类别数,第i行第j列表示“真实类别为i、预测类别为j”的样本数。你只要盯着这个矩阵看一眼,哪一类容易被哪一类吃掉、哪一类几乎不会错、哪些类别高度混淆,全都暴露无遗。
1.2 这个资源包到底能帮你解决什么问题
标题里写的“源码+数据”,在绝大多数这类包里,通常包含三样东西:一是训练好的预测结果或者一个可直接运行的分类demo,二是一段绘制混淆矩阵图的Matlab脚本,三是用来演示的数据文件,可能是.mat格式,也可能是.csv。这些资源的核心目的,就是把“模型的预测结果”可视化成一个直观的网格图,而不是只给你一堆数字。
我见过太多人花时间在调模型上,最后交报告时却因为图太丑被导师打回。其实画混淆矩阵这件事,本身并不复杂,难点在于:第一,怎么把模型输出的原始预测标签和真实标签优雅地喂给绘图函数;第二,怎么处理类别标签顺序、显示百分比还是数量、颜色深浅代表什么;第三,怎么把图调整到论文要求的分辨率和样式。这个资源包正好帮你绕过前面那些造轮子的过程,把重心放在理解和使用上。
1.3 借助Matlab做这件事的优势
选择Matlab而不是Python,有几个实际理由。一是很多工科生的实验环境本来就是Matlab,用现成工具链最顺手;二是Matlab的绘图系统高度集成,比如plotconfusion函数来自深度学习工具箱,封装得非常完整,几行代码就能出图;三是Matlab处理矩阵数据有天然优势,因为混淆矩阵本质上就是一个矩阵,无论是计算还是展示,都和Matlab的底层设计高度匹配。
不过这里有个比较容易被忽略的点:plotconfusion这个函数主要面向神经网络训练过程中的结果展示,如果你用的是SVM、决策树、KNN这类传统机器学习分类器,它就不太好直接用了。所以成熟的方案是结合自定义绘图代码,用heatmap这类更底层的函数自己拼装一张混淆矩阵图。这个资源包里通常也会包含这种自定义写法,因为它更通用。
2. 搞懂混淆矩阵的本质,后面才不迷糊
2.1 从二分类到多分类的扩展逻辑
在二分类里,混淆矩阵通常是2乘2的,四个格子分别叫TP(真正例)、FP(假正例)、FN(假负例)、TN(真负例)。很多人学到这里就停了,一做多分类就不知道怎么套。
实际上,多分类的扩展逻辑非常直观。对于一个有C个类别的分类器,把真实标签当作矩阵的行,把预测标签当作矩阵的列,格子(i, j)里填的就是“真实是第i类、但被预测成第j类”的样本个数。对角线上的格子越多,说明模型判断越准;对角线以外的格子,则是错误的聚集地。
举个例子,假设有三类:0、1、2。真实标签是[0,0,1,1,2,2],预测标签是[0,0,1,0,2,1]。数一下就得到3乘3的矩阵:第一行第一列是2(两个真实为0的样本都预测对了),第一行第二列是0,第一行第三列是0;第二行第一列是1(一个真实为1的样本被误判为0),第二行第二列是1,第二行第三列是0;第三行第一列是0,第三行第二列是1,第三行第三列是1。整个过程就是数数,没有任何神秘之处。
2.2 从混淆矩阵派生出的关键指标
光是画出混淆矩阵还不够,通常还要从里面提取指标,这就是论文里常说的per-class metrics。对类别i来说,真正例TP_i就是矩阵第i行第i列的值;假正例FP_i是第i列除对角线外所有值的和,也就是“别的类被误判成i”的数量;假负例FN_i是第i行除对角线外所有值的和,也就是“本来是i却被判成别的类”的数量。有了这三个数,精确率、召回率、F1都能算出来。
这些指标比整体准确率有信息量得多。比如在医学影像分类里,某种罕见病的召回率如果只有50%,就意味着有一半患者会被漏诊;这种情况下整体准确率再高也不能说明模型可用。你在汇报多分类结果时,如果能拿出一张混淆矩阵,再配上一张指标表,说服力立刻不一样。
2.3 多分类场景下看图的几个关注点
我拿到一张混淆矩阵图,一般会做三件事。第一,看对角线颜色是否明显深于周围,如果整体都是浅色,说明模型泛化能力堪忧;第二,看对角线以外的深色格子聚集在哪,这能直接告诉你模型最容易在哪些类之间“打架”,比如数字4和9、3和8,它们长得太像,混淆多很正常;第三,看每一行或者每一列的分布差异,如果某一行整体很浅,说明这一类几乎没被正确识别,要么是样本量太少,要么是特征太弱,需要针对性补充数据或者重新设计特征。
对这些观察点有了清晰认识后,你就不再是“为了画图而画图”,而是能把矩阵图真正用起来,反哺到模型迭代里。
3. 数据组织和工具选型
3.1 数据怎么组织才不会白折腾
Matlab里最常见的做法是用两个列向量:Ytrue和Ypred。Ytrue是真实的类别标签,Ypred是模型的预测结果。这两个向量的长度要完全一致,而且元素类型要匹配,这是画图不出错的大前提。
关于标签类型,这里有两个流派。一种是用数值标签,比如1、2、3……7,这类标签在计算指标时最方便,但画图时图上的刻度显示就是数字,不够直观。另一种是用分类变量,也就是categorical类型,比如categorical({'cat','dog','bird','cat',...})。categorical类型的好处是,绘图时Matlab会直接用类别名称做坐标轴刻度标签,图更可读。但要注意,如果Ytrue是categorical,而Ypred是普通double数组,调用函数时可能会报维度不匹配,所以最好在预处理时统一转换:Ytrue = categorical(Ytrue); Ypred = categorical(Ypred);
我在这个资源包里看到过一种比较规范的数据组织方式,把真值、预测值和类别名单独存放,绘图脚本只负责读取和渲染。这种解耦方式值得借鉴。如果数据散落在Excel里,推荐在导入后用readtable读进来,再拆分成标签和特征两块。顺序千万不要乱,预测结果一定要和真实标签按行对齐,错位的话,计算出来的矩阵就没有任何意义。
3.2 画法方案对比:plotconfusion还是自定义heatmap
在Matlab里画多分类混淆矩阵,主要有两条路线。
第一条是用深度学习工具箱里的plotconfusion。这个函数会生成一张带颜色条、带百分比和数量标注的标准图,效果挺好看,但最大的问题是,它要求传入categorical数组,而且对工具箱版本有依赖。对传统的分类器,比如SVM或者KNN,预测输出是double数组,你要是直接喂给plotconfusion就有可能会报错,让人莫名其妙。
第二条是自定义绘制,基于heatmap函数,或者干脆用imagesc加text自己拼。好处是灵活可控,颜色映射、文字内容、坐标轴标签、字体大小都能自己定。这也是很多成熟资源包采用的方案,因为通用性更强,不依赖特定工具箱,而且可以完全控制输出样式。如果你在准备论文插图,我强烈建议你掌握这套自定义方案,因为这样画出来的图,风格跟论文要求能完全对齐。
3.3 核心绘图源码思路补全
我先把这个资源包里最常见的核心绘图逻辑拆给你看。假设已经有混淆矩阵CM(一个N乘N的数值矩阵),类别名称列表classNames(一个cell数组),那么基本的绘图步骤是这样的:
% 假设CM是N×N的混淆矩阵,classNames是类别名称 N = length(classNames); figure; imagesc(CM); colormap(parula); % 或者用jet、gray,看个人偏好 colorbar; axis square; xticks(1:N); yticks(1:N); xticklabels(classNames); yticklabels(classNames); set(gca, 'FontSize', 12); % 在格子里填充数字 for i = 1:N for j = 1:N text(j, i, num2str(CM(i, j)), ... 'HorizontalAlignment', 'center', ... 'FontSize', 11, 'Color', 'w'); end end这段代码的思路很清晰:用imagesc把矩阵画成彩色网格,然后用两重循环在每个格子中心写上具体的数字。颜色越深代表数字越大,浅色代表数量少。如果你希望颜色越深代表“越差”,那可以对CM做逆映射,但通常情况下颜色深浅直接反映数值大小就够了。
不过这段只是一个基础版本。更高级的版本会考虑归一化,比如把每一行除以该行的总数,得到“真实类别为i时,预测成各个类别的比例”,这样在样本不平衡时,矩阵的颜色分布更能反映比例关系,而不是被大样本类别主导。归一化的代码通常是:
CM_norm = CM ./ sum(CM, 2);然后在imagesc时传入CM_norm,但在格子里还是写回原始数量CM(i,j)。这种“颜色用比例、数字用数量”的做法,在论文里非常常见,既直观又保留原始信息,我强烈推荐你采用。
4. 案例实操:从训练模型到画出完整混淆矩阵
4.1 准备一个可复现的分类demo
为了让整个流程跑得通,我用Matlab自带的fisheriris数据集来演示。这个数据集有150个样本、4个特征、3个类别(setosa、versicolor、virginica),非常适合做多分类演示。当然这个数据集本身在Matlab里可以通过load fisheriris加载,不需要额外下载任何文件。
我们先用一个最简单的分类器——多分类线性判别分析或者决策树——跑出预测结果。这里为了方便,我直接使用fitcecoc(对多分类的SVM)来演示:
load fisheriris; X = meas; Y = species; rng(42); % 划分训练集和测试集,这里用交叉验证方式估计泛化能力,先简单用全部数据演示流程 Mdl = fitcecoc(X, Y, 'Learners', 'svm'); Ypred = predict(Mdl, X);这段代码训练一个SVM多分类模型,然后拿训练数据本身做预测,方便展示。实际做实验时,你应该用交叉验证或者单独划分测试集,不然会过拟合乐观估计效果。
4.2 调用plotconfusion画出标准矩阵图
因为Y和Ypred都是cell数组(字符串类别),转换一下就可以直接调用plotconfusion:
Ycat = categorical(Y); YpredCat = categorical(Ypred); figure; plotconfusion(Ycat, YpredCat);这一行代码输出的图里,包含真正的混淆矩阵、每个类别的精确率和召回率、整体准确率,排版比较规范。但问题在于,这图用的是神经网络工具箱的采样风格,颜色和字体不一定符合你的论文要求,而且它的文字标注格式、矩阵位置、颜色条位置都是固定死的,想改不太方便。
所以我把plotconfusion定位成“快速验证”工具:在调试阶段,用它能快速看个大概;真正到画正式插图时,还是用自定义方案。
4.3 用heatmap画一张符合论文风格的可定制矩阵
这里我给出一个更完整的自定义版本:
% 计算混淆矩阵 CM = confusionmat(Y, Ypred); % 类别名称(这里Y本身就是字符串类别) classNames = categories(categorical(Y)); numClasses = length(classNames); % 归一化(按行) CM_norm = CM ./ sum(CM, 2); figure; h = heatmap(classNames, classNames, CM_norm, ... 'Colormap', parula, ... 'CellLabelFormat', '%.2f', ... 'FontSize', 12, ... 'Title', 'Confusion Matrix (Normalized by Row)'); h.XLabel = 'Predicted Class'; h.YLabel = 'True Class'; h.ColorbarVisible = 'on';这个版本的图,坐标轴会直接显示类别名,格子里的数值是每行归一化后的比例。它和上一版有本质区别:上一版是“数量视角”,这一版是“比例视角”。在看分类器对稀有类别的性能时,比例视角远比数量视角更有意义。
4.4 自动计算每类精确率、召回率与F1
画完图后,一般还要在论文里再给一张指标表。手动一个个算容易出错,写个循环自动跑最稳妥:
TP = diag(CM)'; FP = sum(CM, 1) - TP; FN = sum(CM, 2)' - TP; precision = TP ./ (TP + FP); recall = TP ./ (TP + FN); F1 = 2 * precision .* recall ./ (precision + recall); % 结果汇总表 T = table(classNames', precision', recall', F1', ... 'VariableNames', {'Class', 'Precision', 'Recall', 'F1'}); disp(T);把这段代码放在画图代码后面,你就能同时得到可视化结果和数值指标,整个评估模块就闭环了。我亲眼见过不少同学用Excel手工数混淆矩阵,费时不说还容易数错,这种自动化方式才是正路。
5. 扩展到更复杂的数据集和展示策略
5.1 手写数字等高类别数场景
当类别数增加到10类以上时,混淆矩阵图会变得比较密,格子里的数字很容易挤在一起。这时候有几种处理技巧:
第一种,格子里只写数字,不写百分比,并且把字号调小。第二种,关闭部分格子里的文字,只保留非零值。第三种,把图画大,比如设置figure的Position参数为[100, 100, 800, 600]。第四种,对文本颜色做自适应判断,格子颜色深就写白字,颜色浅就写黑字,这样数字不会被背景色吞掉。
下面这段代码演示了自适应文字的写法:
% 对每个格子,根据背景色的亮度决定文字颜色 maxVal = max(CM_norm(:)); for i = 1:numClasses for j = 1:numClasses val = CM_norm(i, j); if val > maxVal * 0.5 textColor = 'w'; else textColor = 'k'; end text(j, i, num2str(CM(i, j)), ... 'HorizontalAlignment', 'center', ... 'Color', textColor, 'FontSize', 9); end end这个技巧对你的模型评估展示特别有用,尤其是多分类数量超过8类时,黑字写在深色背景上根本看不清,若全部用白字,写在浅色背景上又发虚。自适应文字可以避免反复手动调格子颜色。
5.2 类别不均衡数据的特殊处理
如果各类别样本数量差异很大,直接看原始数量矩阵会误导人。比如类别A有1000个样本,类别B只有20个,那么即使B的召回率极低,反映在数量矩阵上,B这一行的颜色也可能很浅,视觉冲击力不强。这时候归一化到行比例非常重要。
但行归一化也有副作用:它只看“真实类别是i时预测分布如何”,看不清“类别i的总量其实很小”。所以稳妥做法是给两个矩阵:一个是原始计数矩阵,一个按行归一化的比例矩阵。论文中如果只能放一张图,我建议放归一化版本,然后在图上或正文里注明样本量。
另外,实验记录时,我习惯同时在代码里输出每类的支持样本数(即GT中每类数量),这样审稿人或导师问起,你能立刻回答“为什么这一类看起来很差”的样本量因素。
5.3 输出绘图数据,让图和表都对得上
很多人只把混淆矩阵图导出成图片,原始矩阵数据却随手扔了。等到要改颜色、重排版、或者增加统计检验时,又要重新跑一遍代码。我的建议是,在绘图后立即把CM和CM_norm保存到文件:
save('confusion_result.mat', 'CM', 'CM_norm', 'classNames', 'Ytrue', 'Ypred');这样后续哪怕改图样式,也不需要重新训练模型,直接加载矩阵数据就能重新渲染。这个习惯能省下不少时间。
6. 常见问题与排查技巧实录
6.1 画出来的图颜色全是一个色,或者全是深色
这个情况通常是混淆矩阵里有个别极大值,把颜色映射范围拉得太宽,导致其他格子的差异看不出来。解决办法是先把数据映射到固定范围,比如用caxis或者clim设置[0, 1](如果用的是归一化矩阵)。或者干脆用分位数截断,把极端值截掉,让大部分区域的颜色差异更明显。
6.2 类别顺序和预期不一致
默认情况下,confusionmat函数输出的类别顺序是按字典序或数值大小排列的,不一定是你想要的逻辑顺序。比如类别名是“high”“low”“medium”,按字典序就会变成high、low、medium,画图时看着很别扭。解决办法是显式指定类别顺序:
classOrder = {'low', 'medium', 'high'}; CM = confusionmat(Y, Ypred, 'Order', classOrder);这样矩阵和坐标轴标签就会按你指定的顺序排列,图面立刻变得有条理。
6.3 plotconfusion报错“Number of vectors incompatible”
这个报错最常见的根源是Ytrue和Ypred的类别集合不一致。例如训练时类别标签是1、2、3,预测输出里却出现了0或者4。混合数值类型时更容易出现。解决办法是先查看两个向量的unique值,确保它们一致;如果训练数据里缺少某个类别,用categorical并指定categories来补齐:
Ytrue = categorical(Ytrue, 0:9); Ypred = categorical(Ypred, 0:9);6.4 图片导出版本分辨率低、发虚
Matlab里直接截图显然不行。导出高质量图片时,我建议用exportgraphics函数(R2020a及以上版本),它比print更好用:
exportgraphics(gcf, 'confusion_matrix.png', 'Resolution', 300);如果投期刊要求矢量图,可以导出为pdf或者eps格式,这样文字和颜色都保持矢量精度。记着在导出前把figure的字体、大小、颜色都调好,不然导出来再改就很麻烦。
6.5 类别名太长,坐标轴标签严重重叠
当类别是“Class_A_very_long_name”这类长字符串时,x轴标签会相互压在一起,图完全没法看。解决方式有几种:一是旋转x轴标签:h.XTickLabelRotation = 45(对heatmap)。二是改用简短别名,比如“Setosa”“Vers”“Virg”,再在图例或正文中说明全称。三是把图幅调大。我一般优先用旋转45度加调大图幅,这两种改动对图面破坏最小。
7. 我在反复实操中总结的几条经验
第一次接触这类资源包时,我犯过一个低级错误:只把资源里的绘图函数当黑盒,没去理解它的输入输出格式,结果一换到自己的数据就各种报错。后来我把confusionmat的用法、categorical数组的转换、heatmap的参数列表完整过了一遍,才真正打通。
画多分类混淆矩阵这事,说到底分三步:第一步,把真实标签和预测标签整理成等长的列向量;第二步,用confusionmat得到矩阵;第三步,用imagesc或heatmap渲染矩阵并加上数值。资源包里给你的是这套流程的模板,但你要根据自己的数据和场景去微调。
我后来的习惯是:把整个评估流程封装成一个函数,输入是Ytrue、Ypred和classNames,输出自动包含三样东西——混淆矩阵图(png和pdf各一份)、指标表(Excel一份)、归一化矩阵数据(mat一份)。这样无论做多少组实验,评估部分只需要一行调用,省下来的时间和精力可以投入到真正重要的模型优化上。
最后再提醒一句:不要把注意力全放在画图上,混淆矩阵背后的指标解读才是你论文里真正有含金量的部分。矩阵图告诉观众“模型错在哪”,而你的分析和解释,则要告诉他们“为什么错、怎么改进”。工具是手段,理解才是目的。
本文还有配套的精品资源,点击获取