多分类评测中,micro F1 可能只是准确率
作者为解决 scikit-learn 的多分类报错,将 F1 的平均方式改为 micro,随后误把与准确率相同的结果视为双重验证。在单标签多分类且统计全部类别时,micro F1 与准确率相等,平均方式应按评测目标选择。
AI 深度解读
把 f1_score 的报错修好,不代表评估就做对了。作者用 LLM 把客服工单分到三个队列,补上 average='micro' 后,面板显示 F1 为 0.85,accuracy 也是 0.85。他以为两个指标相互印证,其实在这个任务里,它们算的是同一件事。
更有用的发现藏在分类明细里:8 张退款工单只分对了 1 张。这篇文章提醒我们,给 AI 分类器做评估,关键不只是算出一个分数,而是弄清这个分数会掩盖什么。
为什么 micro F1 会变成准确率
作者的三个队列是 general、billing 和 refund。每张工单只属于一个队列,这是单标签多分类任务。
但 f1_score 默认使用 average='binary',只接受二分类目标,并通过 pos_label 指定要评估的正类。直接拿来计算三个类别,就会报错:Target is multiclass but average='binary'。
可选项包括 micro、macro、weighted 和 None。它们决定怎样汇总各类别的表现,不只是让接口正常运行的参数。
micro 会先把所有类别的真正例、假正例和假负例分别加起来,再计算 F1。对于这里的单标签任务,每次错分都会同时产生一个假正例和一个假负例:模型选中的队列多收一张,正确队列少收一张。
因此,汇总后的 precision 和 recall 相等,也都等于 accuracy。作者的 100 张工单分对 85 张、分错 15 张,micro precision、micro recall、micro F1 和 accuracy 就全是 0.85。
在单标签分类且统计全部类别时,micro F1 与 accuracy 是同一个结果。
这也解释了为什么 classification_report 通常省略此时的 micro 平均行:再列一遍只会重复 accuracy。如果只传入部分标签,这个结论就不能直接套用。
总分 0.85,为什么退款队列仍然几乎失效
作者的数据并不均衡:100 张工单中,80 张是 general,12 张是 billing,8 张是 refund。
用 average=None 分别查看各类 F1 后,问题才显露出来:
| 队列 | 正确分流数量 | F1 |
|---|---|---|
| general | 78 / 80 | 0.92 |
| billing | 6 / 12 | 0.60 |
| refund | 1 / 8 | 0.20 |
错分方向也很关键。2 张 general 都去了 billing;billing 中有 5 张去了 general、1 张去了 refund;refund 中有 7 张去了 general。
这里的「分对数量 / 实际数量」反映的是召回情况,并不能单独算出 F1。F1 还要看别的队列有多少工单被误投进来,所以错分去向不是可有可无的细节。
macro 给每个类别相同权重,三个类别的 F1 平均后是 0.57。它让退款队列的失败进入总分,不会因为 general 占了 80% 就轻轻带过。
weighted 则按各类真实样本数量加权,结果是 0.82。general 的权重仍然最大,因此它也不能代替逐类检查。
这几个分数没有谁天然更正确。accuracy 回答整体分对多少,macro F1 更关注各类别是否都表现合格,而逐类结果才能指出具体坏在哪里。
换成 macro,为什么日报又被空类别拉低
作者随后按天评估,又遇到另一个问题。
某天只有 10 张工单:8 张 general、2 张 billing,没有 refund;其中 1 张 billing 被分到了 general。为了让日报始终展示相同类别,他传入了 labels=['general', 'billing', 'refund'],算出的 macro F1 却只有 0.54。
原因是 refund 当天既没有真实样本,也没有预测结果,它的 F1 无法定义。默认的 zero_division='warn' 会发出警告,把这个值设为 0,再把它计入平均数。于是,一个当天根本没被考到的类别,也拖低了成绩。
从 scikit-learn 1.3 开始,可以设置 zero_division=np.nan,让无法定义的类别退出平均。同一份日切片的 macro F1 会变成 0.80。
但这不等于「没有预测就可以忽略」。如果真实存在退款工单,模型却一次都没预测 refund,该类 F1 就是 0,而且 f1_score 不会因此警告。只有真实标签和预测结果都不存在时,这里的 F1 才无法定义。precision_score 则会在某类没有预测结果时警告,作者因此同时检查两者。
我的判断
这篇文章最有价值的地方,是把「指标相互印证」拆成了具体计算。两个名字不同的指标,可能只是在重复同一个结果。
不过,macro F1 也不是通用答案。它默认各类别同等重要,而实际业务中,不同错分的代价可能不同。原文没有给出处理成本,不能仅凭 0.57 就断定应该怎样调整模型或分流策略。
np.nan 解决了空类别按零计分的问题,却没有补上缺失的证据。当天的 0.80 只能描述有数据的类别,不能证明退款识别正常。不同日期参与平均的类别不同,横向比较也需要带上样本量和类别覆盖情况。
文中的数字来自作者报告的一组 100 条工单和一个 10 条工单切片,没有跨模型或多批数据对照。它足以说明计算上的陷阱,不能证明某一种平均方式适合所有团队。
能拿来做什么
- 给 LLM 分类器接评估时,先用
average=None展示逐类结果,并列出各类样本数和错分去向,再决定首页放哪个总分。 - 如果任务是覆盖全部类别的单标签分类,面板保留 accuracy 即可;另一个指标位可以展示 macro F1,避免重复呈现 micro F1。
- 做日报或小样本评估时,明确
labels和zero_division的配置;使用zero_division=np.nan时,同时展示哪些类别缺少样本。 - 对退款这类需要单独关注的队列,直接检查真实数量、预测数量和召回情况,不要等
f1_score发出警告才排查。