资讯动态

混淆矩阵MATLAB代码示例:从原理到可视化实战

发布时间:2026/10/16 0:57:03 来源:尧图企业网站定制
1. 从一个踩坑现场说起为什么你的分类模型“看起来准”却不能用做过分类任务的人多半遇到过这种场景模型在测试集上跑出 96% 的准确率汇报时信心满满结果上线后业务方反馈“根本没法用”。问题出在哪绝大多数时候是只看了准确率这一个数字而没去看混淆矩阵。混淆矩阵Confusion Matrix是分类任务里最基础也最容易被忽视的评估工具它把预测结果和真实标签交叉排列成一张二维表让你一眼看清模型到底把哪些类别分对了、哪些类别分错了、错得有多离谱。这篇文章围绕“混淆矩阵 matlab 代码示例”展开我会把混淆矩阵的原理、MATLAB 里的几种实现路径、从零手写和调用内置函数的取舍、多分类场景的处理、可视化技巧以及实际调参中踩过的坑全部拆开讲清楚。适合正在做分类项目、写论文需要出图、或者带学生做实验的读者。不管你是刚接触 MATLAB 的新手还是已经用过confusionmat但没深究参数的老手都能从里面找到能直接抄作业的代码和避坑经验。我个人的习惯是任何分类模型先不看准确率先把混淆矩阵画出来。因为准确率会骗人混淆矩阵不会。一个在 1000 个样本里 990 个是负类、10 个是正类的数据集模型全预测成负类也能拿 99% 的准确率但混淆矩阵会立刻暴露它一个正类都没抓到。这就是为什么我说混淆矩阵是分类评估的“照妖镜”。2. 混淆矩阵到底在表达什么四个格子背后的业务含义2.1 二分类的四个核心指标二分类是最容易理解的入口。假设我们做的是一个“是否异常”的判断正类Positive代表异常负类Negative代表正常。混淆矩阵就是下面这张 2×2 的表预测为正类预测为负类真实为正类TP真阳性FN假阴性真实为负类FP假阳性TN真阴性这四个格子不是随便摆的它们直接对应业务上的四种后果。TP 是抓对了TN 是放对了FP 是误报把正常的当成异常FN 是漏报把异常的当成正常。在医疗筛查场景里FN 的代价远大于 FP因为漏掉一个病人比多让一个人复查严重得多而在垃圾邮件过滤里FP 的代价更大因为把重要邮件扔进垃圾箱会让用户直接炸毛。所以看混淆矩阵第一件事不是算指标而是问自己这四个格子哪个格子的错误我最不能接受这个问题想清楚了后面选阈值、调模型才有方向。2.2 从矩阵衍生出的评估指标光看四个格子的绝对值还不够样本量不同没法横向比较所以要衍生出指标。最常用的几个准确率 Accuracy (TPTN) / 总数整体分对的比例样本不均衡时会失真。精确率 Precision TP / (TPFP)预测为正的里面有多少是真的正衡量“误报”程度。召回率 Recall TP / (TPFN)真实为正的里面有多少被找出来衡量“漏报”程度。特异度 Specificity TN / (TNFP)真实为负的里面有多少被正确排除。F1 分数 2 × Precision × Recall / (Precision Recall)精确率和召回率的调和平均。这几个指标的计算全部来自混淆矩阵的四个格子所以只要矩阵对了指标就是水到渠成的事。我见过不少人直接调accuracy函数结果连矩阵都没看最后指标算错都不知道错在哪。2.3 多分类场景下矩阵的形态多分类时混淆矩阵从 2×2 扩展成 K×K行代表真实类别列代表预测类别或者反过来MATLAB 默认行是真实、列是预测这点后面会强调。对角线上的数字是分对的对角线以外是分错的。多分类矩阵的价值在于你能看出哪两个类别最容易互相混淆。比如做一个手写数字识别如果发现 4 和 9 之间的非对角元素特别大说明模型在这两个类别上区分能力弱可能是训练样本里这两个类别的特征太接近也可能是特征提取没抓住关键差异。这种信息是准确率给不了的只有矩阵能告诉你。3. MATLAB 实现混淆矩阵的三条路径与选型逻辑3.1 路径一内置函数 confusionmat 快速出矩阵MATLAB 从 R2008a 开始就提供了confusionmat函数这是最省事的做法。基本用法% 假设 trueLabels 是真实标签predLabels 是预测标签 % 两者都是 categorical 或数值向量 cm confusionmat(trueLabels, predLabels); disp(cm);如果标签是字符串或字符向量建议先转成categorical这样输出的矩阵行列顺序会按类别排序不会乱。confusionmat还支持Order参数指定类别顺序cm confusionmat(trueLabels, predLabels, Order, {cat, dog, bird});这个函数的好处是快、稳、不用自己写循环。缺点是它只返回矩阵不直接给可视化也不自动算指标需要自己再加工。3.2 路径二confusionchart 直接出可视化图R2018b 之后MATLAB 引入了confusionchart这是我最推荐的日常用法因为它一步到位把矩阵和可视化都做了figure; cm confusionchart(trueLabels, predLabels); cm.Title 分类结果混淆矩阵; cm.RowSummary row-normalized; % 按行归一化看召回率 cm.ColumnSummary column-normalized; % 按列归一化看精确率RowSummary和ColumnSummary这两个参数非常实用。设成row-normalized后每一行加起来是 100%对角线上的数字直接就是该类别的召回率设成column-normalized对角线就是精确率。这样一张图同时把召回率和精确率都呈现出来了汇报时特别直观。3.3 路径三手写矩阵应对特殊需求有些场景内置函数搞不定比如你需要自定义类别顺序、需要处理缺失标签、或者要把矩阵输出成特定格式对接下游系统这时候就得手写。手写其实不难核心就是双重循环计数function cm myConfusionMatrix(trueLabels, predLabels, classList) n numel(classList); cm zeros(n, n); for i 1:numel(trueLabels) trueIdx find(classList trueLabels(i)); predIdx find(classList predLabels(i)); if ~isempty(trueIdx) ~isempty(predIdx) cm(trueIdx, predIdx) cm(trueIdx, predIdx) 1; end end end手写的好处是完全可控坏处是容易在边界情况上出错比如标签类型不一致、有 NaN、类别列表不全等。所以我的一般建议是能用内置就用内置内置满足不了再手写手写一定要写单元测试。3.4 三条路径的选型对照路径适用场景优点缺点confusionmat只要矩阵数据后续自己加工快、稳、参数少无可视化、无指标confusionchart日常分析、汇报出图一步到位、可视化强定制性有限手写函数特殊标签、特殊格式需求完全可控易出错、需测试选型逻辑很简单先问自己要不要图要图就用confusionchart不要图只要数据用confusionmat两者都不满足才考虑手写。4. 完整实操从原始标签到可视化矩阵的全流程4.1 数据准备与标签对齐实操第一步永远是数据准备。混淆矩阵对标签对齐极其敏感真实标签和预测标签必须一一对应顺序不能乱。我通常这样做% 假设有一个训练好的模型 model测试集 XTest真实标签 YTest YPred predict(model, XTest); % 检查两个向量的长度是否一致 assert(numel(YTest) numel(YPred), 标签长度不一致检查数据划分); % 统一转成 categorical避免类型不匹配 YTest categorical(YTest); YPred categorical(YPred); % 确保两者类别集合一致 allCats union(categories(YTest), categories(YPred)); YTest setcats(YTest, allCats); YPred setcats(YPred, allCats);这里有个坑如果预测标签里出现了真实标签中没有的类别比如模型输出了一个训练时没见过的类confusionmat默认会报错或者行为异常。用setcats把两者的类别集合统一能避免这个问题。这一步看起来啰嗦但能省掉后面大量调试时间。4.2 生成矩阵并解读对角线数据对齐后生成矩阵cm confusionmat(YTest, YPred); disp(cm); % 计算整体准确率 accuracy sum(diag(cm)) / sum(cm(:)); fprintf(整体准确率: %.4f\n, accuracy);解读矩阵时我习惯先看对角线再看非对角线。对角线上的数字越大越好但如果某个类别的对角线数字明显偏小说明这个类别整体识别率低。然后看这个类别的行和列行上的非对角元素大说明这个类别的样本被大量误分到其他类召回率低列上的非对角元素大说明其他类别的样本被大量误分到这个类精确率低。4.3 计算每个类别的精确率与召回率nClasses size(cm, 1); precision zeros(nClasses, 1); recall zeros(nClasses, 1); f1 zeros(nClasses, 1); for i 1:nClasses TP cm(i, i); FP sum(cm(:, i)) - TP; % 列和减去对角线 FN sum(cm(i, :)) - TP; % 行和减去对角线 precision(i) TP / (TP FP); recall(i) TP / (TP FN); f1(i) 2 * precision(i) * recall(i) / (precision(i) recall(i)); end % 输出成表格 classNames categories(YTest); resultTable table(classNames, precision, recall, f1, ... VariableNames, {类别, 精确率, 召回率, F1}); disp(resultTable);这段代码里有个细节FP是列和减对角线FN是行和减对角线。很多人第一次写会搞反记住一个口诀——行是真实列是预测行和减对角线是漏报列和减对角线是误报。这个口诀我用了好多年从没出过错。4.4 可视化与归一化处理出图环节我一般会同时出两张图一张原始计数一张按行归一化。原始计数看绝对数量归一化看比例。% 图1原始计数 figure; cm1 confusionchart(YTest, YPred); cm1.Title 混淆矩阵原始计数; % 图2按行归一化 figure; cm2 confusionchart(YTest, YPred, Normalization, row-normalized); cm2.Title 混淆矩阵按行归一化;confusionchart的Normalization参数支持absolute、row-normalized、column-normalized、total-normalized四种。日常分析用row-normalized最多因为它直接对应召回率。如果关心误报就用column-normalized。4.5 把矩阵导出成论文可用的图写论文时经常需要把混淆矩阵导出成高清图。confusionchart返回的是对象可以直接设置字体、颜色然后导出cm2.FontSize 12; cm2.XLabel 预测类别; cm2.YLabel 真实类别; exportgraphics(gcf, confusion_matrix.png, Resolution, 300);exportgraphics是 R2020a 之后推荐的导出函数比老的saveas清晰得多300 DPI 足够应付大多数期刊要求。如果期刊要求矢量图把扩展名改成.pdf或.eps即可。5. 多分类与不均衡场景下的进阶处理5.1 多分类矩阵的归一化陷阱多分类时按行归一化和按列归一化给出的信息完全不同。按行归一化后对角线是召回率适合回答“每个真实类别有多少被找出来”按列归一化后对角线是精确率适合回答“每个预测类别有多少是准的”。我见过有人把按列归一化的图当成召回率来解读结论完全反了。举个具体例子假设三个类别 A、B、CA 类样本 1000 个B 类 100 个C 类 100 个。如果模型把所有样本都预测成 A按行归一化后 A 的召回率是 100%看起来很好但按列归一化后 A 的精确率只有 1000/1200 ≈ 83%B 和 C 的精确率是 0。所以样本不均衡时一定要看按列归一化或者直接看原始计数。5.2 类别不均衡时的指标选择类别不均衡是分类任务的老大难。这时候准确率基本没用要看的是宏平均macro-average和微平均micro-average宏平均先算每个类别的指标再取算术平均每个类别权重相同。微平均把所有类别的 TP、FP、FN 分别加起来再算指标样本多的类别权重大。macroPrecision mean(precision); macroRecall mean(recall); macroF1 mean(f1); % 微平均 totalTP sum(diag(cm)); totalFP sum(cm(:)) - totalTP; microPrecision totalTP / (totalTP totalFP);不均衡场景下我一般两个都报宏平均反映“小类别有没有被照顾到”微平均反映“整体表现”。如果两者差距很大说明模型在大类别上表现好、小类别上拉胯需要做重采样或者调类别权重。5.3 用混淆矩阵反推模型问题混淆矩阵最大的价值是诊断。我总结了几种典型模式矩阵特征可能原因处理方向某行非对角元素集中在一列两个类别特征太像增加区分性特征对角线整体偏低模型欠拟合增加模型复杂度某类别全被预测成另一类标签错误或样本极少检查数据标注非对角元素均匀分布模型没学到东西检查特征和训练过程这个表是我踩了无数坑总结出来的基本上拿到一张混淆矩阵对照着看就能定位大部分问题。6. 常见报错与排查技巧实录6.1 标签类型不匹配报错最常见的报错是confusionmat提示标签类型不一致。原因通常是真实标签是数值向量预测标签是 categorical或者反过来。解决办法是统一转成categoricalYTest categorical(YTest); YPred categorical(YPred);如果标签是字符串元胞数组用categorical(string(YTest))转换。转换后记得检查categories是否一致不一致用setcats对齐。6.2 类别顺序错乱导致矩阵行列对不上confusionmat默认按类别字母顺序排列但有时候你希望按自定义顺序比如按样本量从大到小。这时候用Order参数order {classA, classB, classC}; cm confusionmat(YTest, YPred, Order, order);如果不指定顺序而两个向量的类别集合又不同矩阵的行列可能对不上解读时就会张冠李戴。这个坑我在早期项目里踩过矩阵看起来正常但行列含义错了结论全反。6.3 归一化后出现 NaN按行或按列归一化时如果某一行或某一列全为 0归一化结果会出现 NaN。这在多分类里很常见比如某个类别在测试集里一个样本都没有。解决办法是在归一化前检查rowSum sum(cm, 2); rowSum(rowSum 0) 1; % 避免除零 cmNorm cm ./ rowSum;或者在用confusionchart时MATLAB 会自动处理这种情况显示为空白而不是 NaN所以可视化场景下问题不大但手写归一化时一定要注意。6.4 大数据量下的性能问题样本量上百万时confusionmat依然很快因为它底层是高效的计数实现。但如果手写双重循环速度会慢到无法接受。这时候可以用accumarray加速[~, ~, trueIdx] unique(YTest); [~, ~, predIdx] unique(YPred); cm accumarray([trueIdx, predIdx], 1, [nClasses, nClasses]);accumarray是 MATLAB 里做分组计数的利器比循环快几个数量级。我处理过千万级样本的分类评估用accumarray几秒钟就出矩阵用循环得跑十几分钟。6.5 常见问题速查表报错/现象原因解决标签类型不一致数值 vs categorical统一转 categorical矩阵行列对不上类别顺序不一致用 Order 参数指定归一化出现 NaN某行/列全为 0除零保护手写循环太慢样本量大改用 accumarray图看不清类别太多只显示 Top-N 或分块7. 几个让我印象深刻的实操心得第一个心得混淆矩阵要早看、常看。我现在做任何分类项目模型跑通的第一件事就是画混淆矩阵而不是等调完参再看。因为早期看能发现数据问题比如标签错标、类别混淆这些问题越早发现越省事。等到模型调完再看可能已经在一个错误的方向上走了很远。第二个心得归一化方式要跟汇报对象匹配。给业务方看用原始计数因为他们关心“有多少个”给算法团队看用按行归一化因为关心“召回率”给产品看用按列归一化因为关心“误报率”。同一张矩阵换个归一化方式沟通效率完全不一样。第三个心得矩阵不是终点是起点。看到矩阵里某两个类别混淆严重不要急着调模型先回去看数据。我遇到过好几次矩阵显示两个类别互相误分最后发现是标注标准不统一同一张图两个人标了不同类别。这种问题调模型是调不好的只能回去统一标注。第四个心得保存矩阵数据别只存图。图是给人看的数据是给程序用的。我习惯把cm矩阵和对应的类别列表一起存成.mat文件后面写论文、做对比实验、复现结果都用得上。只存图的话过两个月想重新算个指标都算不了。save(confusion_result.mat, cm, classNames, precision, recall, f1);这个习惯帮我省了太多重复劳动。尤其是论文返修时审稿人要求补一个指标直接从.mat里读出来算就行不用重新跑模型。最后分享一个小技巧如果类别特别多比如 20 类以上confusionchart出来的图会挤成一团。这时候可以只显示混淆最严重的前 N 个类别或者把矩阵按混淆程度排序后再画。我一般会先算一个“混淆度”指标比如每行非对角元素的最大值按这个排序把最需要关注的类别排在最前面。这样图虽然还是大但重点突出汇报时一眼就能看到问题所在。

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

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

免费获取报价 →
↑