返回 AICAP-180
B13 · Day 123FATF typologies + BSA grader + 用户研究

混淆矩阵与 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 要同时报 accuracymacroRecallmacroPrecisionmacroF1——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 − tpfp = predictedAs − tprecall = tp/supportprecision = tp/predictedAsf1 = 2·r·p/(r+p)(分母为 0 时取 0,规避除零)。
  • ConfusionResult 同时给 accuracy = correct/nmacroRecall/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\predstructuringlayeringnormal
structuring201
layering011
normal102

逐类算:

  • 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。ConfusionResultmacroRecall/macroPrecision/macroF1 就是为此而设。

3. 今日实战

  1. 把 Day 122 规则分类器对 typed.csv 的输出整理成 {label, predicted} 对(label=真值 typology,predicted=assessCase().topTypology ?? 'normal')。
  2. 喂进 src/aml/confusionMatrix.tsconfusionMatrix(items),得到 5×5 矩阵 + 每类 ClassMetrics(recall/precision/F1/support)。
  3. 重点核对 per-typology recall——尤其 layering(图特征类)是否因 Day 121 保全了图字段而非平凡(≠0);若 layering recall=0,回查导出是否把链式过账拍成了单笔。
  4. 报告里只列 macro + per-class,不单报 accuracy;补 1 个 passing test 锁住矩阵与指标计算。

4. 今日实测 / 产出

  • 已完成src/aml/confusionMatrix.tsconfusionMatrix + ClassMetrics/ConfusionResultBUILT+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 evals2026-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.tsconfusionMatrix/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 判定,提供外部效度)。