尧图精选

Macro Average与Weighted Average:多分类模型评估指标深度解析

🕒 发布时间:2026/9/28 8:49:38 📁 来源:尧图网络
跑完一个多分类模型我最习惯先做的事不是看accuracy而是打印一份classification_report。当年做二分类的时候precision、recall、F1看三行就能说清楚可类别一多报告立刻膨胀每个类别都有自己的precision、recall、F1最后还会多出两行汇总——一行macro avg一行weighted avg。我见过不少同行第一次看到这两行时犹豫这不都是把每个类别的指标聚合成一个总体指标吗为什么要搞两个版本该用哪个汇报选错了会不会出问题这篇文章就把macro average和weighted average彻底说透包括它们各自怎么算、什么时候用、有哪些坑以及sklearn背后一个很多人没注意到的实现细节。如果你是刚接触多分类评估的初学者或者被指标选择困扰过一阵子的工程师看完应该能直接代入自己的项目。1. 多分类评估总账是怎么来的先想清楚再选指标1.1 二分类时代养成的三行指标习惯先回到最简单的二分类。模型输出正类和负类报告里只有两行加上accuracy一眼看过去非常清爽。Precision衡量模型说是正类的里面到底有多少是真的正类recall衡量真实的正类里有多少被模型捞了出来F1是这两者的调和平均。因为只有两个类别你甚至可以只盯正类的指标负类天然是它的反面汇报起来不费力。这种习惯被带进多分类时问题就来了。三分类报告里有三行P/R/F1十分类就是十行。你没法在周报里贴一大串数字也没法快速判断A模型和B模型谁更好。你迫切需要一个总账指标把每个类别的指标聚合成一个总体指标让模型质量可以被单个数字描述。1.2 类别一多为什么不能直接看accuracy有人会说多分类不是有accuracy吗整体猜对的比例一个数字搞定。确实accuracy是最直观的总体指标但它有两个天生缺陷。第一accuracy只看对不对完全不看错成什么样。一个三类模型把两个类别弄混和把明明很远的类别弄混在accuracy眼里都是错可工程上这两种错误的代价完全不同。第二accuracy对类别不平衡极度不敏感。如果90%都是Class A模型无脑全猜Aaccuracy也有90%但Class B和Class C等于被直接放弃了。真实业务里被放弃的往往就是最值钱的少数类。所以需要把每个类别的precision、recall、F1先算出来再想办法汇总成一个总账。这样就同时保留了错法的信息又能用数字交流。1.3 聚合指标背后的核心分歧身份平等还是数量平等聚合方式看起来只是数学变换但背后有个绕不开的分歧每个类别应该以什么身份参与汇总一种是身份平等不管这个类别在数据集里出现了100次还是10000次它在总指标里的贡献都一样大这叫macro average另一种是数量平等样本多的类别说话声音大样本少的类别声音小按各类别的样本量加权这叫weighted average。这两个方向没有天然的对错它们回答的是不同的问题。Macro问的是模型在所有类别上的平均表现如何weighted问的是模型在全部样本上的平均表现如何。当类别样本量不均衡时这两个问题的答案可能差很多极端情况下甚至得出相反结论。理解了这一点后面的选择就不再纠结于公式而是纠结于业务诉求。2. Macro average每个类别一票的聚合逻辑手算实例2.1 Macro average的完整计算逻辑Macro average的全称是macro-averaged precision、recall和F1做法非常朴素先分别计算每个类别的precision、recall、F1得到K个分数然后对所有类别取算术平均。公式写出来是这样macro_P (P_1 P_2 ... P_K) / Kmacro_R (R_1 R_2 ... R_K) / Kmacro_F1 (F1_1 F1_2 ... F1_K) / K这里要特别划一个重点macro F1不是对macro_P和macro_R再求一次调和平均而是先对每个类别求F1再对这批F1取平均。这个顺序看起来只是细节但数学上并不等价具体差异我在后面第六节的避坑环节专门讲。用生活化的例子理解假设有三个班级参加期末考试A班800人B班100人C班100人。如果算班级平均分就是三个班的平均分相加除以3每个班一票这就是macro。B班人少但它和A班在平均分里有完全相同的发言权。2.2 一个1000样本的例子手工算出macro F1我构造一个三分类例子测试集一共1000个样本Class A真实有800个Class B真实有100个Class C真实有100个。这是一个典型的类别不均衡分布。假设模型预测结果的混淆矩阵如下真实\预测预测A预测B预测C真实A800个7801010真实B100个552025真实C100个101080从混淆矩阵可以算出每个类别的指标我直接列成表格support就是各类在真实标签里的样本数这个数在算weighted时是权重来源很重要。类别support真实样本数PrecisionRecallF1Class A8000.9230.9750.948Class B1000.5000.2000.286Class C1000.6960.8000.744注意Class B的recall只有0.2意味着100个真实B样本里模型只找回了20个其余55个被误判成A、25个被误判成C属于彻底崩坏的少数类。现在做宏平均macro_P (0.923 0.500 0.696) / 3 ≈ 0.706macro_R (0.975 0.200 0.800) / 3 ≈ 0.658macro_F1 (0.948 0.286 0.744) / 3 ≈ 0.659模型在多数类A上接近优秀但macro整体只有0.659。原因就是多了个0.286的Class B它把整体往下拽了一大截。这就是macro的脾气它不看样本量每个类别都以同样权重参与平均小类差macro一定难看。2.3 Macro的脾气小类崩溃时它一定拉警报正因为这样macro特别适合少数类很重要的场景。比如疾病筛查里的罕见亚型、风控系统里的少数欺诈模式如果只看整体准确率模型把少数类全部判错也不影响大局但这类错误往往代价最高。用macro作为主要监控指标等于在制度上保证少数类的表现会被看见——只要某一个类别崩了macro会立刻反映出来。但macro也有另一个问题如果某个类别特别小它的指标波动会很大给macro带来不小的抖动。小样本类别的precision和recall可能因为几条样本变化就剧烈变动这时候macro会放大噪声。所以我有个习惯看到macro数值变化第一件事不是盯着数字兴奋或沮丧而是回到per-class指标里查一下到底是哪个类别动了、动得有没有道理。3. Weighted average按样本量说话的聚合方式和macro差在哪3.1 Weighted average的权重从哪来Weighted average的思路完全不同它把每个类别的support占全部样本的比例作为权重再做加权平均。公式是weighted_P (support_1 / total) × P_1 (support_2 / total) × P_2 ... (support_K / total) × P_Kweighted_R (support_1 / total) × R_1 (support_2 / total) × R_2 ... (support_K / total) × R_Kweighted_F1 (support_1 / total) × F1_1 (support_2 / total) × F1_2 ... (support_K / total) × F1_K还是三个班级的例子A班800人、B班100人、C班100人如果算全校平均分就不该用三个班简单平均而应该按人数加权。A班人多它的平均分对全校平均分的影响最大这就是weighted。有一点必须强调这里的权重是真实样本数即support不是预测样本数。权重衡量的到底层数据的真实构成评估必须建立在对真实分布的正确估计上否则算出来的总账会偏离事实。3.2 同样的例子weighted F1为什么能到0.86沿用上面的数据计算加权指标weighted_P (800×0.923 100×0.500 100×0.696) / 1000 858.0 / 1000 ≈ 0.858weighted_R (800×0.975 100×0.200 100×0.800) / 1000 880.0 / 1000 0.880weighted_F1 (800×0.948 100×0.286 100×0.744) / 1000 ≈ 861.6 / 1000 ≈ 0.862数据还是那份数据Class B还是惨不忍睹但加权之后总F1到了0.862比macro的0.659高出一大截。原因很直接Class A有800个样本占绝对多数它0.948的F1把整体拉上去了Class B只有100个样本即使它烂穿地板对总指标的拖累也只有10%的权重。这组对比完美展示了业务里经常遇到的困惑同一个模型有人说表现很好0.862有人说不太行0.659。他们其实都没说错只是看的指标不一样。Weighted回答的问题是所有样本平均下来如何macro回答的是每个类别平均下来如何。当别人跟你汇报模型效果时你第一反应应该问这个数到底是macro还是weighted3.3 sklearn里weighted F1的一个隐藏细节用sklearn打印classification_report时细心的人会发现一个值得琢磨的现象weighted avg那一行的precision和recall都在0.85和0.88附近而F1是0.862三者看起来和谐。但sklearn官方文档明确提过weighted average计算出来的F1有可能不在precision和recall之间也就是说它可能大于precision和recall中的较大者或者小于较小者。原因在实现方式sklearn的weighted avg是对每个类别算好的F1按support加权平均而不是先用加权后的precision和recall再算一个总F1。数学上先算每类F1再加权平均和先加权汇总P/R再算F1是两种不同的运算绝大多数情况下数值接近但理论上可能不一致。我用这个例子验证一下。如果先由weighted_P0.858和weighted_R0.880算F1结果是2×0.858×0.880/(0.8580.880)≈0.869而直接对每类F1加权平均得到0.862。两者差了约0.7个百分点。理解这点能避免你在自己推导指标时对不上账也能解释为什么有人拿weighted P和weighted R重新算一遍F1却和report上的F1对不上。4. 代码实操3分钟跑懂classification_report里的两个avg4.1 直接用classification_report打印总账前面讲了那么多理论落到代码其实非常简单。我用上一节那个1000样本的混淆矩阵构造一份y_true和y_pred然后直接用sklearn跑出报告数字和手算完全一致。from sklearn.metrics import classification_report, f1_score # 真实标签800个0类100个1类100个2类 y_true [0]*800 [1]*100 [2]*100 # 预测标签按混淆矩阵构造 y_pred ( [0]*780 [1]*10 [2]*10 # 真实0类 [0]*55 [1]*20 [2]*25 # 真实1类 [0]*10 [1]*10 [2]*80 # 真实2类 ) print(classification_report(y_true, y_pred, target_names[Class A, Class B, Class C], digits3)) print(macro F1 , f1_score(y_true, y_pred, averagemacro)) print(weighted F1 , f1_score(y_true, y_pred, averageweighted))输出结果里会看到三行per-class指标然后是accuracy一行接着是macro avg和weighted avg两行。注意accuracy是整体准确率等于(7802080)/10000.880这个数恰好和weighted_recall相同这在单标签多分类里不是巧合后面我会专门讲。4.2 用Python手工复现消除理解偏差只看report输出还不够我建议你亲手写一遍聚合逻辑才能真正把macro和weighted的理解焊死在脑子里。下面这段代码不依赖sklearn自己统计TP、FP、FN再分别算宏平均和加权平均。from collections import Counter import numpy as np classes [0, 1, 2] total len(y_true) macro_list [] weighted_list [] for c in classes: tp sum(1 for t, p in zip(y_true, y_pred) if t c and p c) fp sum(1 for t, p in zip(y_true, y_pred) if t ! c and p c) fn sum(1 for t, p in zip(y_true, y_pred) if t c and p ! c) support sum(1 for t in y_true if t c) precision tp / (tp fp) if (tp fp) else 0 recall tp / (tp fn) if (tp fn) else 0 f1 2 * precision * recall / (precision recall) if (precision recall) else 0 macro_list.append(f1) weighted_list.append(f1 * support) print(macro F1 , round(np.mean(macro_list), 3)) print(weighted F1 , round(sum(weighted_list) / total, 3))这段代码没有任何黑盒你会发现weighted F1就是每类F1乘以support再除以总样本数跟第三节的公式严格对应。自己写一遍之后再看sklearn的输出就不会觉得它是个魔法数字了。4.3 zero_division和多标签场景的边界情况实际使用中有一个很容易踩的坑当某个类别既没有真实样本也没有预测样本时precision和recall都会出现0/0sklearn默认会弹警告并把结果当成0处理。极端情况下如果你的测试集本身缺了一个类别又用了averagemacro那个缺失类的指标会变成0直接把整体的macro拖得很低。处理方式很简单f1_score(y_true, y_pred, averagemacro, zero_division0)zero_division可以设成0或1按业务语义选。如果缺失类意味着模型不该预测错设0如果缺失类意味着该场景不该出现惩罚也可以设1但必须明确这样会美化指标我不推荐在正式评估里这么做。另外如果是多标签分类y_true是二值矩阵support的含义变成每个标签在真实矩阵里出现的次数macro仍然是对每个标签平均weighted仍然按标签出现次数加权逻辑没有变只是类别换成了标签。如果你有个多标签项目可以把上面这段单标签代码改成对每个标签循环效果一样。5. 业务决策macro和weighted到底该用哪个5.1 判断标准业务价值在哪一边技术指标永远服务于业务决策。选macro还是weighted本质上只有一个问题在你这个场景里样本数量和业务价值是不是一回事如果样本多的类别恰好就是业务上最看重的类别那么weighted非常合适它直接反映整体样本被服务好的程度。如果样本少的类别价值更高比如罕见病、投诉、欺诈那么weighted会把少数类的糟糕表现稀释掉你必须用macro甚至要看最差类别的单独指标。我见过太多团队把weighted F1从0.83优化到0.86沾沾自喜地发周报结果一查混淆矩阵那个只有2%样本的少数类recall从0.5掉到了0.1。这就是weighted在掩盖问题的典型案例因为2%的权重根本拖不动整体数字。5.2 三个行业的选型案例医疗影像的罕见病分类某个病型只占整体数据的1%漏诊代价极高。这种场景重点是看macro和每个类别的recall尤其关注罕见类的recall和precision。如果只用weighted一个把所有罕见病都判成阴性的模型加权后可能仍然有0.95看起来非常安全实则灾难。电商文本分类里的投诉识别投诉文本占比3%但它是客服排障和品牌口碑的关键信号。用macro作为主线指标相当于在官方口径里承认了投诉类别和其他普通咨询类别同等重要如果优化目标是weighted大概率资源都会被普通咨询类的高准确率吸走投诉类反而被忽略。这类场景我通常建议报告里两个都放但优化时以macro为主。搜索引擎的query意图识别线上用户query的分布和训练分布一致且用户感受到的产品质量就是按请求数量平均的质量这时weighted F1更贴近线上真实体验。不过如果你想专门优化长尾query就得切回macro因为长尾query单类样本少weighted很难反映它的改善。5.3 我的汇报习惯两个都报加上最差类别问到底用哪个其实是个伪命题成熟的做法是两个都报再补一个最差类别的指标。我写模型评估报告时固定格式是总体F1weighted: 0.862宏平均F1macro F1: 0.659最差类别Class B: F1 0.286recall 0.200这样读报告的人既知道整体样本表现也知道类别均衡度还能一眼看到风险点。很多同学只给领导报一个数被追问少数类怎么办时再回去翻混淆矩阵其实完全可以在第一次汇报时就讲清楚。Model card里也应该保留这三个信息方便后来人复现和评估。6. 常见问题与避坑几个坑了我很久的细节6.1 macro F1不是macro P和macro R的调和平均我在前面的公式里提醒过macro F1是每类F1的算术平均不是macro P和macro R的调和平均。用上面的数据验证一下macro_P0.706macro_R0.658它们的调和平均是2×0.706×0.658/(0.7060.658)≈0.681而macro F1是0.659。两个数字不一样因为F1本身是P和R的调和平均而先平均再调和和先调和再平均在数学上不能交换。我自己刚接触多分类评估时就在这里栽过跟头拿着macro P和macro R想复核report上的macro F1怎么都对不上还以为是版本bug。实际上sklearn的classification_report就是老老实实把每个类别的F1算出来再平均没有做任何额外操作。6.2 weighted F1不在weighted P和weighted R之间正常吗正常。weighted F1是每类F1的加权平均F1本身是每类的调和结果混合了调和和线性加权两步结果自然可以跳出weighted P和weighted R的区间。上一节例子里weighted P0.858、weighted R0.880、weighted F10.862还在区间内但这不代表永远如此。遇到odd情况先别怀疑程序算错拿我第四段的手工代码去核对大概率是你对聚合顺序的理解和实现不一致。6.3 类别排布接近均匀时两个指标会怎样当每个类别的样本量相差不大时macro和weighted会高度接近。比如三个类别各占三分之一weighted的权重都是1/3和macro的1/K几乎相同两个指标基本重合。这种情况下选哪个都无所谓甚至直接看accuracy也行因为混乱程度低数字不会欺骗你。真正让macro和weighted分道扬镳的永远是严重的类别不均衡。我建议你拿到一份新的测试集时先看support列的最大值和最小值比例如果超过10比1就默认macro和weighted会有明显差异必须两个都看。6.4 micro average为什么常常等于accuracy虽然标题只聊macro和weighted但classification_report里偶尔也会出现micro这里顺带说清楚。在单标签多分类里把所有类别的TP相加就等于预测正确的总样本数因为每个样本只会被算作一个类别的TP所以micro precision micro recall micro F1 accuracy。这不是巧合而是micro把混淆矩阵全部折叠成了一个整体它丢失了类别层面的信息在不均衡场景下信息量很低。我自己的习惯是单标签多分类基本不看micro因为accuracy已经给了同样信息但在多标签分类里micro有独立价值因为它按样本-标签对统计能反映整体命中率。这一点很多博客讲得含糊我特意拎出来说清楚。最后分享一个我自己固定的评估动作拿到一份classification_report我第一眼看macro avg和weighted avg的差距如果超过5个百分点我会立刻跑到混淆矩阵里找具体是哪个类别在拖后腿。等这个差距缩小了我才相信模型是真的变好了而不是某一个大类在美化报表。这个习惯帮我逮住过好几次看起来涨分、实际崩类的模型希望对你也有用。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →