返回 AICAP-180
B14 · Day 136外部 ground-truth AML eval

混淆矩阵与指标

在 B1→B18 的能力曲线上,B14 是把「自造金标循环」彻底换成外部 ground-truth 的一整块:B13(D121-130)打好了 FATF typologies + BSA grader + 用户研究的地基,B14 这十天则把 IBM AMLworld 真标签灌进既有评测管线,用真召回率拷问规则基线和 LLM judge。前几天(D131-135)已经把列结构映射(amlworldS

阶段: B14 · 外部 ground-truth AML eval(Day 131-140) 标签: #confusion-matrix #recall-precision-fpr #aml-eval #grpo-analogy

今日导引(由浅入深)

在 B1→B18 的能力曲线上,B14 是把「自造金标循环」彻底换成外部 ground-truth 的一整块:B13(D121-130)打好了 FATF typologies + BSA grader + 用户研究的地基,B14 这十天则把 IBM AMLworld 真标签灌进既有评测管线,用真召回率拷问规则基线和 LLM judge。前几天(D131-135)已经把列结构映射(amlworldSchema)、去偏 loader、judge 校准链路铺好;昨天(Day 135)固化了 judge prompt v1 + 30 条校准一致率口径。今天接着学指标本身——recall / precision / FPR 的定义与 AML 业务的取舍,并把 balanced_300{label, predicted} 喂进 binaryEval 出第一张混淆矩阵。今天的最小可判定产出是:一份 {tp, fp, tn, fn, recall, precision, fpr} 的 confusion matrix JSON——但其中真实三指标数字仍待跑(需 key)+待数据,只有 binaryEval 函数本身是 built+tested。

1. 机理精读

三个指标的定义与分母。 二分类(suspicious 为正、normal 为负)下,混淆矩阵四格 TP/FP/TN/FN 派生出三个核心率:recall = TP/(TP+FN)(真实可疑里被抓到的比例,分母是「所有真正例」)、precision = TP/(TP+FP)(报为可疑里真的可疑的比例,分母是「所有被报正例」)、FPR = FP/(FP+TN)(正常案被误报的比例,分母是「所有真负例」)。三者分母不同,混用会得出互相矛盾的结论——这是后面 leaderboard 可比性的根。

为什么 AML 业务偏好高 recall。 反洗钱的代价结构是高度不对称的:漏掉一笔真实洗钱(FN)的代价是监管罚单、合规风险、声誉损失,量级远大于多查一个正常客户(FP)的代价——后者只是多花一次人工复核的人力。因此 AML 评测的目标函数天然偏向高 recall、容忍较高 FP,由人工复核(HITL)兜底误报。这与一般 ML 任务追求 F1 平衡的默认姿态不同,必须在 rubric 与判读阈值里写清楚。

判读阈值要结合产能。 「容忍较高 FP」不是无上限:每一个 FP 都要消耗一名调查员的复核工时。所以阈值不是纯统计最优点,而是「在现有复核产能下,recall 能拉到多高」的运营约束问题。把 FP 调到产能装不下,再高的 recall 也落不了地——这是把指标接回业务的关键一跳,也是 PM/架构视角比纯算法视角多出来的东西。

把这条运营约束拆成可量化的几步,AISA 视角下应当这样推:

  1. 先定漏报成本 / 误报成本的相对量级(AML 里通常 10:1 甚至更高),它决定了 recall 与 precision 的权衡点偏向 recall。
  2. 再用复核团队的日产能(一名调查员一天能复核多少 alert)反推出 FPR 上限——FPR×真负例总数 = 误报 alert 数,不能超过产能。
  3. 在这条 FPR 上限的约束下,再去最大化 recall——这是个带约束的优化,不是无约束追高 recall。
  4. 阈值最终落在「产能能消化的最大 recall」处,而非统计 ROC 曲线的拐点。这一步是把纯指标接回运营现实,也是 leaderboard 数字能不能转化为产品决策的关键。

accuracy 为什么在这里几乎无用。 真实 AMLworld 正例 <0.1%,一个「永远判 normal」的退化模型 accuracy 高达 99.9%,但 recall=0、对业务零价值。balanced_300 做了平衡抽样正是为了让指标可解释,但即便如此也不报单一 accuracy——AML 评测的主指标永远是 recall(漏没漏)、辅以 precision(复核队列信噪比)与 FPR(产能压力)。

GRPO 的 group-relative 思路如何类比裁判稳定性。 seed 引了 GRPO(arXiv:2402.03300, 2024-02):它用「同组多样本相对比较」算 advantage,而非依赖单点绝对奖励,从而降低单点噪声。类比到 LLM judge——单条样本上裁判判定有方差,但在一组(balanced_300)样本上做相对统计(混淆矩阵聚合 + 后续 bootstrap),单点噪声被平均掉,指标更稳。这只是类比,不是说今天在跑 GRPO;R1(arXiv:2501.12948, 2025-01)是 GRPO 的代表应用。

这个类比的工程含义有两层:其一,永远在一组样本上聚合,而非看单条——单条判定的对错没有统计意义,混淆矩阵把 300 条聚合成四格才有信号;其二,相对比较比绝对阈值稳——GRPO 不问「这个回答的绝对得分多少」而问「在同组里相对好坏」,对应到评测就是「不纠结单条判得对不对,而看整组指标分布」。Day 137 的 bootstrap 正是把这个「组内相对」推到极致:对同一组反复重采样看指标怎么抖。所以今天的混淆矩阵不是终点,是「组级统计」的第一层,CI 是第二层。

与相邻概念的边界。 今天 ≠ Day 137(Day 137 才给三指标加 bootstrap 置信区间,今天只出点估计);今天 ≠ Day 134(那是抽 balanced_300 子集,今天是在子集上算指标);今天 ≠ 多分类混淆矩阵(confusionMatrix.ts 出 per-class/macro,今天用的是 binaryEval 的二元坍缩口径)。今天只回答:在真标签 balanced_300 上,模型预测的 TP/FP/TN/FN 与三指标点估计是多少。

2. 手算 + 代码走读

2.1 手算:从一张混淆矩阵到三指标

为了把定义钉死,先用一组小数手算一遍。设 balanced_300 的一个 12 条切片,真标签与模型判定如下(正=可疑、负=normal):

真实\预测预测可疑预测正常
真实可疑(6)TP=5FN=1
真实正常(6)FP=2TN=4
  • recall = TP/(TP+FN) = 5/(5+1) = 0.833——6 个真可疑里抓到 5 个,漏 1 个。
  • precision = TP/(TP+FP) = 5/(5+2) = 0.714——报了 7 个可疑,5 个对、2 个误报。
  • FPR = FP/(FP+TN) = 2/(2+4) = 0.333——6 个真正常里误报了 2 个。
  • f1 = 2·TP/(2·TP+FP+FN) = 10/(10+2+1) = 0.769

读法:AML 业务最盯 recall=0.833(漏报 1/6 是否可接受?取决于漏掉的是否大额)。FPR=0.333 意味着每 3 个正常客户就有 1 个进人工复核队列——这条要拿去和复核产能对账,产能装不下就得收紧规则或调阈值。precision=0.714 是复核队列的「信噪比」,太低会拖垮调查员。注意:以上是手算演示,不是 balanced_300 的真实跑分(真实数字待跑/待数据)。

再把 f1 的角色说清:f1=0.769 是 recall 与 precision 的调和平均,常用作单一汇报数。但在 AML 里 f1 会掩盖业务最关心的非对称——它把 recall 和 precision 等权,而 AML 明明更怕漏报。所以 f1 可以报,但不能当主指标;真正驱动阈值决策的是 recall(在 FPR 产能约束下)。这也是为什么 binaryEval 把四格原始值 + recall/precision/fpr/f1 全部吐出来:让读者自己按业务权重看,而非替读者压成一个数。

2.2 代码走读:binaryEval 如何把多类型学坍缩成二元

Read 了 src/aml/groundTruthEval.ts,今天的核心是 binaryEval(items, normalLabel='normal'),真实行为如下:

  • 输入是 LabeledPrediction[]——每条 {label, predicted},两者都是字符串类(typology id 或 'normal')。注释明确这是 prediction-source-agnostic:label 来自外部公开标注集、predicted 可来自规则引擎或 LLM,评分逻辑不依赖预测怎么产生的——这正是「杀死循环 baseline」的解耦点。
  • 二元坍缩规则actualPos = i.label !== normalLabelpredPos = i.predicted !== normalLabel。任何非 'normal' 的类(structuring/layering/mule_network)都算「正例」。注意一个细节:label='mule_network'predicted='layering' 时,两者都非 normal → 记 TP(类别串了但二元正确)。这与测试用例第 5 行的注释 // tp (both positive, class swap ok) 一致——本评测只问「可疑 vs 正常」,不问「具体哪类」。
  • 四格累加actualPos && predPos → tp++!actualPos && predPos → fp++!actualPos && !predPos → tn++;否则 fn++。逻辑覆盖全四象限,无遗漏分支。
  • 三指标计算带零分母保护recall: tp+fn ? tp/(tp+fn) : 0precisionfpr 同理。注释提醒:分母为 0 时返回 0,要查 tp/fp/tn/fn 原始字段区分「无支撑(no support)」与「实测零率」——这是一个容易误读的陷阱(见第 5 节)。
  • 额外产出 f1nf1 = tp ? 2*tp/(2*tp+fp+fn) : 0n = items.length

测试佐证(src/aml/__tests__/groundTruthEval.test.ts):5 条手构样本喂进 binaryEval,断言 [tp,fp,tn,fn]===[2,1,1,1]recall≈2/3precision≈2/3fpr≈1/2——纯函数确定性可测,已在 423 测试套件内绿。结论:binaryEval 已 built+tested,今天的工作量在「拿 balanced_300 跑模型出预测、喂进它落盘」,而非改逻辑。

把测试里的 5 条对照走一遍坍缩逻辑,能彻底吃透「类别串扰记 TP」这一非直觉点:

labelpredictedactualPospredPos计入
structuringstructuringTP
layeringnormalFN(漏报)
normalstructuringFP(误报)
normalnormalTN
mule_networklayeringTP(类别串,二元仍对)

合计 [tp,fp,tn,fn]=[2,1,1,1] 与测试断言完全一致。第 5 行是关键:真实是 mule、模型判 layering,二元口径下两者都非 normal → TP。若业务关心「具体哪类」,得换 confusionMatrix.ts 的多分类口径,那里这条会落在 matrix['mule_network']['layering'] 单元格、计为该类的一次错分。两套口径回答的是两个不同问题,下结论时务必选对。

3. 今日实战

  1. 对 day134 的 balanced_300(约 150 正 / 150 负,固定 seed)全量跑 deepseek-v4-flash(key 已配置,命令侧复用 pnpm eval:agent 的 provider-agnostic runner),让 judge 对每条出 {verdict: laundering|normal, reason}
  2. 把 judge 输出映射成 {label, predicted} 对:label 取 AMLworld 真 Is_Laundering(1→可疑类、0→'normal'),predicted 取 judge 的 verdict。
  3. src/aml/groundTruthEval.tsbinaryEval(items, 'normal'),拿到 {tp,fp,tn,fn,recall,precision,fpr,f1,n}
  4. 落盘 confusion matrix JSON(四格 + 三指标 + n),作为 Day 137 加 CI、Day 140 进 leaderboard 的输入。
  5. 顺手记录本次跑批的 seed / 模型 id(deepseek-v4-flash)/ judge prompt 版本,与 confusion matrix 同档存——Day 140 leaderboard 的可复现 README 要引这些元数据,现在不记后面补不齐。

4. 今日实测 / 产出

  • 产出物:confusion matrix JSON + recall/precision/FPR 三指标首版数字。
  • 状态binaryEval 函数 已 built+tested(输入 {label,predicted} 即出 TP/FP/TN/FN/recall/precision/fpr/f1/n,见 groundTruthEval.test.ts,在 423 测试套件内绿)。
  • 真实三指标数字 = 待跑(需 key 跑)+待数据:需 N=300 的 AMLworld 子集落盘 + 模型跑产生,不预填。可参照的同 harness 真实锚点:DeepSeek-V4-Flash completion 79.3% / V4-Pro 89.7%(judge=pass,注意这是 agent 任务 completion,不是本日 AML 三指标,仅作量级参照)。

5. 常见误区 / 陷阱

  • 只报单一 accuracy:balanced_300 虽做了平衡抽样,但真实 AMLworld 正例 <0.1%,accuracy 在原始分布上会被多数类淹没(全判 normal 也有 99.9% accuracy)。必须报 recall/precision/FPR 三件套,accuracy 单独看会误导。
  • 把零分母当零率recall=0 既可能是「真有正例但全漏」,也可能是「根本没有正例(tp+fn=0)」。binaryEval 在后者也返回 0,必须回查 tp/fn 原始字段区分,否则会把「无数据」误读成「召回为零」。
  • 混淆 recall 与 precision 的分母:AML 偏好「高 recall、容忍 FP」指的是分母 (TP+FN),不是 (TP+FP)。把两者搞反会得出「该提高 precision」的反方向结论。
  • 类别串扰当错判mule_network 被判 layering 在二元口径下是 TP(都非 normal)。若想评「具体类型学」准确率,那是多分类 confusionMatrix.ts 的活,别用 binaryEval 的口径下结论。
  • 拿 agent completion 当 AML recall:79.3% / 89.7% 是 agent 任务 completion(judge=pass)的锚点,不是本日 AML 三指标。把它当成「AML 召回率」直接误导——两者度量的是完全不同的任务。
  • 忘了 normalLabel 参数binaryEval 默认 normalLabel='normal',若外部集用 '0'/'legit' 标负类,必须显式传对应字符串,否则负类被当正类,四格全错。
  • 把 f1 当主指标:f1 等权 recall 与 precision,掩盖 AML 的漏报非对称。可报,不可当决策主轴。

6. 学习资源(每条带 YYYY-MM)

  • GRPO 原论文 — DeepSeekMath: Pushing the Limits...(arXiv:2402.03300, 2024-02):group-relative reward,单点噪声 vs 组内相对比较。
  • DeepSeek-R1(arXiv:2501.12948, 2025-01):GRPO 的代表应用,奖励信号稳定性。
  • Anthropic — Demystifying evals(2026-01):评测分层、不平衡数据下的指标选择。
  • IBM AMLworld dataset card(2024-04):Is_Laundering 标签定义与列结构。
  • FinCEN — BSA/AML Examination Manual(持续更新;CTR $10,000 门槛与 SAR 制度,作业务打底):解释为何 AML 偏好高 recall 的合规根源。
  • 本仓代码:src/aml/groundTruthEval.tssrc/aml/__tests__/groundTruthEval.test.tsbinaryEval 已 built+tested)。
  • 本仓代码(对照口径):src/aml/confusionMatrix.ts(多分类 per-class/macro,与二元 binaryEval 互补)。

SOTA检查 (2026-06 更新)

  • 现役主线:recall/precision/FPR 为经典稳定指标,无替代;GRPO(2024-02)仍是 group-relative reward 主线类比,R1(2025-01)为其代表应用。带四格混淆矩阵 + 三指标的二元评测是 AML 评测现役做法。
  • 指标选择 SOTA:2026 eval-rigor 共识下,不平衡分类报 recall/precision/FPR(+ 后续 CI)已是底线,accuracy 仅作辅助;PR-AUC 在极不平衡下比 ROC-AUC 更可读,可作单点指标的补充。
  • 避免/禁用:避免在极不平衡场景只报单一 accuracy——误导;避免把零分母当实测零率;避免把类别串扰当二元错判;避免拿 agent completion 当 AML recall。
  • 下次复查点:执行当周用 WebSearch 复查 GRPO 后续是否有更新的 reward-stability 变体(DAPO/RLVR 等是否取代 group-relative 叙事);复查 deepseek-v4-flash 是否仍为当周可用 id(legacy deepseek-chat/deepseek-reasoner 2026-07-24 退役)。

衔接

  • 昨天:Day 135 — LLM judge 校准(judge prompt v1 + 30 条手验子集一致率口径,deepseek-v4-flash)。
  • 今天:在真标签 balanced_300 上用 binaryEval 出第一张混淆矩阵 + recall/precision/FPR 点估计(函数已 built+tested,真实数字待跑/待数据)。
  • 明天:Day 137 — bootstrap 置信区间(对预测对有放回重采样 1000 次,给三指标 95% CI,用既有 binaryEvalWithCI)。