混淆矩阵与 per-typology 召回
昨天(Day 122)用确定性规则分类器对 typed.csv 打了标,产出 5 类命中计数。但「命中多少」不等于「打得准不准」——要回答后者,必须把预测和真值放进混淆矩阵,逐类算 recall/precision/F1。
阶段: B13 · FATF typologies + BSA grader + 用户研究(Day 121-130) 标签: #confusion-matrix #recall-precision #class-imbalance #aml-eval
今日导引(由浅入深)
昨天(Day 122)用确定性规则分类器对 typed.csv 打了标,产出 5 类命中计数。但「命中多少」不等于「打得准不准」——要回答后者,必须把预测和真值放进混淆矩阵,逐类算 recall/precision/F1。
在 B1→B18 曲线上,今天是把「分类器输出」升级成「可度量的能力数字」的关键一步,也是后面规则-vs-LLM 召回缺口分析(Day 127)的度量底座。AML 场景有个强烈的非对称:漏报一笔洗钱的代价远高于一次误报,所以今天的核心是搞清「为什么 AML 要偏 recall、为什么不能只看 accuracy」。
最小可判定产出:把 Day 122 规则分类器输出喂进 confusionMatrix,出 5×5 矩阵 + per-class recall/precision/F1。
1. 机理精读
多分类混淆矩阵:对角线对、非对角错。 一个 K 分类问题的混淆矩阵是 K×K 的表:行 = 真值 (truth)、列 = 预测 (predicted)。matrix[truth][predicted] 是「真值为 truth 但被预测成 predicted」的样本数。
对角线 matrix[i][i] 是正确分类,非对角线是错分,且非对角的位置直接告诉你混淆方向——比如 layering 被大量预测成 normal,说明分类器漏掉了链式过账(典型的「图特征没抓到」);若 structuring 被预测成 normal,则是阈值太紧或贴线带没覆盖。混淆方向本身就是改进线索。
recall / precision / F1 的精确定义。 对某一类 i:
- recall = TP / (TP + FN) = TP / support——「该抓的抓住了没」。support = 真值为 i 的总数(矩阵第 i 行之和)。
- precision = TP / (TP + FP)——「抓的对不对」。FP = 被预测成 i 但真值不是 i 的数(第 i 列之和 − TP)。
- F1 = 2·recall·precision / (recall + precision)——两者的调和平均,惩罚任一项过低。
为什么 AML 强偏 recall。 这是一个代价非对称问题:漏报一笔真实洗钱(FN),监管罚款、声誉损失、甚至刑事责任的代价是天文数字;而误报一次(FP),代价只是一次人工复核的工时。
在「漏一个」和「多查几个」之间,AML 系统宁可容忍较高 FP 也要把 recall 拉满。所以 AML 分类器的设计目标不是最大化 accuracy 或 F1,而是在可接受的 FP 工作量内最大化 per-typology recall——这直接决定了规则/LLM 分类器的阈值往「宽松」方向调。
关键陷阱:类别不平衡下 accuracy 会骗人。 AML 数据天然极不平衡——绝大多数交易是 normal。若分类器把所有样本都判成 normal,accuracy 可能高达 90%+(因为多数类就是 normal),但所有洗钱类的 recall 都是 0——这是个彻底失败却「看起来很准」的模型。
所以 AML eval 必须看 per-typology recall,单一 accuracy 会被多数类(normal)淹没。这也是为什么 ConfusionResult 要同时报 accuracy、macroRecall、macroPrecision、macroF1——macro 平均对每类等权(先算每类指标再求简单平均),不像 micro 平均那样被多数类样本量主导,是类别不平衡下的正确读数。
recall 与 precision 的取舍按钮在哪。 分类器调阈值时,recall 和 precision 此消彼长:
- 阈值调松(更容易判成洗钱)→ 抓得更全 → recall ↑,但误报增多 → precision ↓。
- 阈值调紧(更保守)→ 抓得更准 → precision ↑,但漏报增多 → recall ↓。
- AML 的非对称代价决定按钮往「松」推:在 FP 工作量(人工复核成本)可承受的上限内,尽量抬 recall。
- 但不能无限松——全判洗钱会让 precision→0、复核队列爆炸,反而淹没真案件。所以实践是「在 recall 优先的前提下守住一个 precision 下限」。
与相邻概念的边界。 今天 ≠ 改进分类器(只是度量 Day 122 的输出);今天 ≠ 校准裁判(那是 Day 125 的 Cohen's κ,度量「裁判与真值的一致性」,不是「分类器的对错」)。
今天的混淆矩阵衡量的是「预测 vs 真值」的分类质量,喂进去的 predicted 可以来自规则引擎、LLM 或外部数据集——度量本身与预测来源无关。这个「来源无关」是关键设计:它让规则版与 LLM 版预测能用同一函数同口径度量,Day 127 的规则-vs-LLM 缺口对比才公平。
2. 代码走读:confusionMatrix 的纯实现
Read 了 src/aml/confusionMatrix.ts,确认度量函数真实存在且来源无关:
confusionMatrix(items: Array<{label: string; predicted: string}>): ConfusionResult— 纯函数、无 key。文件头注释明确写明 prediction-source-AGNOSTIC:喂{label, predicted}对即可,predicted来自规则引擎、LLM 还是外部集都行——「这种解耦正是修复『规则引擎给自己的数据打分』循环性的关键」(这正是 Day 124-125 引入独立 grader 的前提)。labels— 从items的所有 label 与 predicted 取并集排序,所以矩阵会自动覆盖出现过的全部类(含 normal)。matrix[truth][predicted]— 真值→预测→计数;matrix[i.label][i.predicted]++逐对累加。ClassMetrics(每类):tp = matrix[label][label]、support = 该行之和(真值=label 的总数)、predictedAs = 该列之和(预测=label 的总数)、fn = support − tp、fp = predictedAs − tp、recall = tp/support、precision = tp/predictedAs、f1 = 2·r·p/(r+p)(分母为 0 时取 0,规避除零)。ConfusionResult同时给accuracy = correct/n、macroRecall/macroPrecision/macroF1(各类等权平均)、n——macro 指标对每类等权,不被多数类淹没,这是应对类别不平衡的关键字段。
走读结论:confusionMatrix + ClassMetrics/ConfusionResult 真实存在且 BUILT+tested (test green)。今天把 Day 122 规则分类器的 {label, predicted} 对喂进去即可出 5×5 矩阵,无需新写度量逻辑。
2.1 推导:一个最小手算混淆矩阵
设规则分类器对 8 个案件的输出(label→predicted):
- structuring→structuring ×2、structuring→normal ×1(漏 1 个)
- layering→layering ×1、layering→normal ×1(漏 1 个)
- normal→normal ×2、normal→structuring ×1(误报 1 个)
矩阵(行=真值,列=预测):
| truth\pred | structuring | layering | normal |
|---|---|---|---|
| structuring | 2 | 0 | 1 |
| layering | 0 | 1 | 1 |
| normal | 1 | 0 | 2 |
逐类算:
- structuring:tp=2,support=3 → recall=2/3≈0.67;predictedAs(列和)=2+0+1=3 → precision=2/3≈0.67;F1≈0.67。
- layering:tp=1,support=2 → recall=0.50;predictedAs=0+1+0=1 → precision=1.0;F1=2·0.5·1/(1.5)≈0.67。
- normal:tp=2,support=3 → recall=0.67;predictedAs=1+1+2=4 → precision=0.50;F1≈0.57。
- accuracy=(2+1+2)/8=0.625;macroRecall=(0.67+0.50+0.67)/3≈0.61。
注意 layering 的 precision=1.0 但 recall 只有 0.50——只看 precision 会以为很好,但漏了一半 layering,AML 场景下这是危险信号。这就是为什么必须逐类看 recall。
再对比 micro 与 macro:本例 accuracy(等价于 micro-recall)=0.625,但 macroRecall=0.61——两者接近只因这里类别还算均衡。
一旦 normal 占到 80%+,micro 会被 normal 抬到很高、macro 仍如实反映每类,差距就拉大。AML 报数一律看 macro + per-class,不看 micro/accuracy。ConfusionResult 的 macroRecall/macroPrecision/macroF1 就是为此而设。
3. 今日实战
- 把 Day 122 规则分类器对 typed.csv 的输出整理成
{label, predicted}对(label=真值 typology,predicted=assessCase().topTypology ?? 'normal')。 - 喂进
src/aml/confusionMatrix.ts的confusionMatrix(items),得到 5×5 矩阵 + 每类ClassMetrics(recall/precision/F1/support)。 - 重点核对 per-typology recall——尤其 layering(图特征类)是否因 Day 121 保全了图字段而非平凡(≠0);若 layering recall=0,回查导出是否把链式过账拍成了单笔。
- 报告里只列 macro + per-class,不单报 accuracy;补 1 个 passing test 锁住矩阵与指标计算。
4. 今日实测 / 产出
- 已完成:
src/aml/confusionMatrix.ts的confusionMatrix+ClassMetrics/ConfusionResult已 BUILT+tested (test green)。 - 待跑:规则版 5×5 矩阵数字 = 待跑(需 Day122 分类器输出),将产出 5 类各自 recall/precision/F1。
- 状态诚实:上面手算的 8 案件矩阵是教学示例,不是规则分类器在 1000 行 typed.csv 上的真实输出——真实 5×5 数字未跑出前不得臆造。
5. 常见误区 / 陷阱
- 只报单一 accuracy:类别极不平衡时(AML 多数是 normal),全判 normal 也能拿高 accuracy 却把所有洗钱类 recall 打成 0。必须看 per-typology recall + macro 指标。
- 混淆 recall 与 precision 的方向:recall 看「该抓的抓住没」(漏报视角),precision 看「抓的对不对」(误报视角)。AML 偏 recall,但报告时两者都要给,否则无法判断 FP 工作量。
- 忽略非对角线的混淆方向:矩阵不只看对角线,非对角告诉你「错往哪错」——layering→normal 多说明漏链式过账,是改进 Day 127 缺口分析的线索。
- 把度量与预测来源耦合:
confusionMatrix是来源无关的,规则/LLM/外部预测都喂同一函数——不要为不同来源各写一套度量,否则 Day 127 规则-vs-LLM 对比口径会不一致。
6. 学习资源(每条带 YYYY-MM)
- Anthropic — Demystifying evals(2026-01):per-class 指标、为什么类别不平衡时单一 accuracy 不可信。
- scikit-learn 文档 — Confusion matrix / classification metrics(持续维护,执行当周查版本):recall/precision/F1/macro 平均的标准定义。
- FATF — Money Laundering Typologies(FATF 官网,持续更新):per-typology 召回为何是 AML 的关键度量。
- 本仓代码:
src/aml/confusionMatrix.ts(confusionMatrix/ClassMetrics/ConfusionResult,已 built+tested, test green)。
SOTA检查 (2026-06 更新)
- 现役主线:混淆矩阵 / recall-precision / F1 是稳定经典度量,无替代,2026 年仍是分类评测金标准。macro 平均 + per-class recall 是处理类别不平衡的现役做法。
confusionMatrix的「预测来源无关」设计是修复循环自评的正确架构。 - 避免/禁用:避免只报单一 accuracy——类别极不平衡时 accuracy 会被多数类(normal)淹没,必须看 per-typology recall;避免拿教学示例数字冒充真实跑批结果。
- 下次复查点:度量本身经典稳定无需频繁复查;执行当周确认 typed.csv 落盘后,复查规则版矩阵的 layering recall 是否因图特征保全而非平凡(≠0)。
自测问答(讲得出才算掌握)
- Q:一个 90% accuracy 的 AML 分类器一定好吗? A:不一定。若数据 90% 是 normal,全判 normal 也能拿 90% accuracy,但所有洗钱类 recall=0——彻底失败却「看起来很准」。必须看 per-typology recall + macroRecall,accuracy 会被多数类淹没。
- Q:recall 和 precision 一个高一个低(如 layering recall=0.5、precision=1.0)说明什么? A:precision=1.0 表示「凡判 layering 的都对」,但 recall=0.5 表示「真实 layering 漏了一半」。AML 场景这是危险信号——漏报代价高,要把阈值调松抬 recall,宁可牺牲一点 precision。
- Q:为什么
confusionMatrix要做成「预测来源无关」? A:这样规则预测、LLM 预测、外部数据集预测能喂同一个度量函数,口径一致——Day 127 规则-vs-LLM 对比才公平。更关键的是,它把「评测」与「被测系统」解耦,是修复evalBaseline.ts循环自评的架构前提。
衔接
- 昨天:Day 122 — BSA 报告机制与阈值(确定性规则分类器对 typed.csv 产出 per-typology 命中计数)。
- 今天:把规则分类器输出喂进 5×5 混淆矩阵,出 per-class recall/precision/F1,确立 AML 偏 recall 的度量取向。
- 明天:Day 124 — LLM-as-classifier prompt 设计(用独立 LLM grader 做 typology 判定,提供外部效度)。