返回 AIPA 笔记
AIPA Day 12

金标定向补难例 —— 用信息增益指导数据集扩充

金标定向补难例 —— 用信息增益指导数据集扩充

2026-06-26
hard-casesactive-learninggolden-datasetdecision-boundary

日期: 2026-06-26 阶段: Phase 1 - 产品定义×评测×可观测底座 标签: #hard-cases #active-learning #golden-dataset #decision-boundary

核心问题

W1 我们造出了 66 案金标(getGoldenDataset),规则基线在三类典型学上 recall 1.0、normal FPR 5.6%。但这个数字有个致命的诱惑:它看起来已经很好,于是想"再多造 200 个案件让数据集更壮"

这就是本篇要反驳的直觉。盲目扩量(uniform augmentation)几乎不改变 eval 的判别力——你只是在已经判对的区域上反复确认。真正能让 eval "区分出好评测对象与坏评测对象"的,是决策边界附近的难例(hard cases near the decision boundary)

核心问题落到三个:

  1. 难例从哪里来?如何系统性地挖,而不是靠运气撞上?
  2. 为什么"定向补难例"在理论上优于"盲目扩量"?信息增益(information gain)视角怎么解释?
  3. 合成难例如何保证金标标注正确 + 可复现(seeded)?

这不是抽象方法论。AML 场景里,真正会害死你的不是那笔 6 笔 $9,000 的教科书式 structuring——规则一定抓得到;而是那个 3 笔 $9,500 的合法卖车款(normal 却长得像 structuring)和那个跨度 11 天的 7 笔存款(structuring 却落在规则 10 天窗口之外)。这些边界样本,才是 eval 的金矿。

关键内容

A. 难例挖掘的三条系统化路径

"难例"不是模糊的"难一点的案例",而是有精确定义的三类。逐条说清"怎么挖 + 为什么这样挖"。

A.1 taxonomy 盲区(coverage gap mining)

错误分析(Day 9-11 的 open coding → axial coding)产出了一棵失败 taxonomy。每个失败类是一条"系统在某情形下会犯的错"的断言。盲区挖掘的做法是:对 taxonomy 的每个叶子节点,反问"我的金标里有几个样本能触发它?" 若某个失败类(例如"跨窗口边界的 structuring 被漏报")在金标中样本数为 0,则这个失败类对 eval 完全不可见——评测对象在这里犯错也不会扣分。

盲区挖掘算法:
  for leaf in taxonomy.leaves():
    coverage[leaf] = count(case in golden where triggers(case, leaf))
    if coverage[leaf] < MIN_PER_LEAF:   # 经验值:每叶 ≥3,太少则方差大
      gap_list.append((leaf, MIN_PER_LEAF - coverage[leaf]))
  return sort(gap_list, by=severity * (1/current_coverage))

为什么这样设计:Hamel & Shreya 的错误分析强调"小而互斥、可二元判定的失败类"(2025-09),但 taxonomy 是从已观测的 trace 归纳出来的——它只覆盖你已经见过的失败。盲区挖掘是把 taxonomy 倒过来用:从"已知的失败模式"反推"金标缺哪些样本",把归纳偏差(只见过的才进 taxonomy)补成演绎覆盖。

A.2 决策边界挖掘(decision-boundary mining)

规则引擎的判定阈值是 ASSESS_THRESHOLD = 0.5typology.ts)。一个 structuring 案件聚合得分 0.6(主规则 STRUCT-01 命中)就过线;0.4(只命中辅规则 STRUCT-02)就掉到 normal。得分落在 0.4–0.6 这条带上的案件,就是决策边界样本——它们的标签对参数的微小扰动最敏感,因此判别价值最高。

主动学习(active learning)的不确定性采样理论说得很直白:模型最不确定的样本就在决策边界附近,对这些样本观测标签能获得关于边界位置最多的信息(Encord 2025;PLOS One 2025-07)。把这套思想搬到 eval 数据集构造上:我们要刻意制造一批"得分贴着 0.5 上下浮动"的案件,让任何调参/换模型/换 prompt 的改动,都会在这批样本上立刻显形。

A.3 对抗扰动(adversarial perturbation)

取一个金标里"判对"的案件,施加一个最小语义保持扰动,看判定是否翻转:

  • structuring 案件:把存款窗口从 9 天拉到 11 天(仍是同一笔洗钱意图,但越过规则的 10 天窗口)→ 规则漏报,标签仍是 structuring。这是一个"假阴"难例。
  • normal 案件:给合法卖车款加第 3 笔现金存款 → 越过 STRUCT-01 的 ≥3 笔门槛 → 规则误报。这是一个"假阳"难例。

对抗扰动的价值在于它沿着规则的具体阈值方向施力,精准戳中规则的脆弱处,而不是随机噪声。

反直觉洞察①:增加易例不提升判别力,反而稀释它。 假设当前 eval 用 acc 衡量,规则基线在 60 个易例上全对、在 6 个难例上对 3 个 → acc = 63/66 = 95.5%。现在盲目再加 100 个易例(全对)→ acc = 163/166 = 98.2%。数字更漂亮了,但当你换一个更强的评测对象,它在 6 个难例上对 5 个,acc 从 98.2% 升到 98.8%——0.6 个百分点的提升,被 160 个易例的"背景信号"淹没成噪声级别。 易例不携带"区分两个候选系统"的信息,它们只在抬高 baseline、压缩动态范围。判别力 ≈ Δacc/Δ能力 在易例占比升高时单调下降。

B. 为什么"定向补难例"优于"盲目扩量"——信息增益视角

把 eval 看成一次"测量":我们想用金标 D 去估计"系统能力 θ"。一个样本 x 对这次测量的价值,等于观测它的标签能减少多少关于 θ 的不确定性,即互信息 I(θ; y_x | D)。

对一个易例(所有合理系统都判对),y_x 的条件分布几乎是确定的(恒为"对")→ H(y_x | θ) ≈ 0 对所有 θ → I(θ; y_x) ≈ 0。它不携带关于 θ 的信息。

对一个边界难例(弱系统判错、强系统判对),y_x 强烈依赖 θ → H(y_x | θ) 随 θ 变化大 → I(θ; y_x) 大。一个边界样本的信息量可以抵几十个易例。

这就是为什么"定向补难例"是信息论上的最优采样,而盲目扩量是在低信息区做重复抽样。用 expected error reduction 的语言(active learning 经典框架):边界样本的"到决策边界距离"最小、"预测熵"最大,期望误差下降最大(Springer ML 2021;Encord 2025)。

扩充策略+100 案件成本边界样本占比eval 判别力 Δ适用场景
盲目扩量(uniform)高(100×标注)~5%(自然分布)≈0(动态范围被压缩)❌ 几乎从不推荐
分层采样(stratified)~20%想保持类别平衡时
定向补难例中(20–30 难例即可)~80%✅ 默认策略
纯对抗(adversarial only)100%高但偏已知具体脆弱点时

成本-收益的关键数字:用 20–30 个定向难例达到的判别力,盲目扩量需要 300+ 易例都达不到——因为后者的判别力上限被易例占比锁死。

C. 难例的金标标注与可复现(seeded)

合成难例最大的陷阱不是"造不出来",而是"造出来后标错 + 造出来后复现不了"。

C.1 标注必须与扰动解耦。 对抗扰动改的是特征(存款窗口天数、存款笔数),不改意图标签。把卖车款从 2 笔加到 3 笔,它仍然是 normal——加的是"合法的第三笔车款尾款",洗钱意图为零。金标标签 = 生成时的真实意图,而非规则的判定结果。 这条纪律在 generator.ts 里已经体现:genNormalCase 里那个 cashBiz 现金密集型商户案件,label 写死 'normal',哪怕规则会把它误报为 structuring——标签是 ground truth,规则命中是被测对象的输出,二者必须严格分离。

反直觉洞察②:用规则的判定结果当金标标签,会制造"自证循环"。 如果你"造一批规则判为 structuring 的案件,然后标成 structuring",那 eval 永远 100% recall——你测的不是系统对不对,而是系统和它自己一不一致。难例尤其危险:边界样本的规则判定本来就摇摆,拿它当标签等于把噪声固化成真理。金标标注必须回到人类专家定义的"真实意图",规则碰不到这条线。

C.2 可复现靠 seed,不靠运气。 项目所有合成都走 createPrng(seed)(确定性 PRNG,与 dsdb-lab ledger 同一套)。难例集必须是新 seed 的独立数据集,而不是在 aipa-golden-v1 上随机追加——否则一改追加逻辑,前面 66 案的 PRNG 流全部错位,整个 v1 金标都变了。正确做法是把难例做成 getGoldenDatasetV11:基于 v1 + 一个 aipa-hardcases-v1 seed 的难例批,v1 案件的 id/PRNG 流原封不动,难例 caseSeq 从 v1 末尾续号。

C.3 每个难例带"难在哪"的元数据。 不只是 label,还要标 hardClass(属于哪个失败类)和 expectFail(预期规则基线在这里会犯哪种错)。这样 eval 报告能输出"难例上的失败分布",而不只是一个聚合 recall。

难例生成(seeded、标签解耦)伪代码:
  hardCases = []
  prng = createPrng('aipa-hardcases-v1')
  for (failClass, targetCount) in gapList:        # 来自 A.1 盲区挖掘
    for i in range(targetCount):
      base = pickGoldenCaseFor(failClass.typology) # 取一个判对的种子案
      perturbed = applyPerturbation(base, failClass, prng)  # A.3 对抗扰动
      perturbed.label = base.intentLabel            # ← C.1 标签来自意图,非规则
      perturbed.hardClass = failClass.id
      perturbed.expectFail = failClass.expectedError  # 'false_negative'|'false_positive'
      hardCases.append(perturbed)
  assert allDeterministic(hardCases, seed='aipa-hardcases-v1')  # C.2 可复现断言
  return hardCases
难例类型扰动方向目标失败类金标 label预期规则行为
跨窗口 structuring存款窗口 9→11 天边界漏报structuring假阴(STRUCT-01 窗口外)
三笔合法车款normal +1 笔现金现金密集误报normal假阳(凑够 ≥3 笔)
短链 layering链长 3→2 账户链长不足漏报layering假阴(< 3 账户门槛)
慢速 mulefan-in 跨度 13→16 天窗口外 fan-inmule_network假阴(超 14 天窗口)
整百非整千过账金额 $25k→$24.5k整数特征失效layeringLAYER-02 漏命中
边界得分 normal单笔 $9,500 现金0.4 分掉线normal真阴但贴边界

设计要点

决策选择理由
扩充策略定向补难例(非盲目扩量)信息增益视角:易例 I(θ;y)≈0
难例来源taxonomy 盲区 + 边界 + 对抗 三路覆盖归纳盲区 + 最大判别价值 + 精准戳脆弱点
数据集组织新 seed 独立批 v1.1,v1 不动保护 PRNG 流确定性,避免回归基线漂移
标签来源生成时的真实意图,非规则判定切断自证循环
元数据每难例带 hardClass + expectFail支持"难例上的失败分布"报告
难例数量每盲区叶 ≥3,总量 20–30 即可判别力来自质,不来自量

对本项目的落地

  1. 新增 getGoldenDatasetV11src/aml/generator.ts:在现有 getGoldenDataset()(66 案,aipa-golden-v1)基础上,追加一个 aipa-hardcases-v1 seed 的难例批。关键约束:v1 的 GOLDEN_SEED/GOLDEN_COUNTS/caseSeq 流必须一字不改——generateDataset 对 v1 的输出保持 byte-identical,难例只能续号在后。这样 W1 进 CI 的基线(recall 1.0×3、normal FPR 5.6%)不会因扩充而无声漂移。

  2. 新增难例生成器函数:仿照 genStructuringCase / genNormalCase 的模式,写 genCrossWindowStructuring(窗口拉到 11 天)、genThreeLegitCarPayments(normal +1 笔现金,复用现有 cashBiz 思路)、genShortChainLayeringnAccounts=2)。每个函数的 label 字段写意图标签,而非规则会判出的结果——这正是 genNormalCasecashBiz 案件已经在做的(label 恒为 'normal',哪怕规则误报)。

  3. 扩展 AmlCase 类型(src/aml/types.ts:可选字段 hardClass?: stringexpectFail?: 'false_negative' | 'false_positive'。v1 的 66 案不带这两个字段(向后兼容),只有难例批携带。

  4. 扩展 evalRuleBaselinesrc/aml/evalBaseline.ts:在 BaselineEval 里加一段 hardCaseBreakdown——按 hardClass 分组统计规则基线在难例上的 recall/FP,输出"系统在哪类难例上失分"。这比单一聚合 recall 对 PM 更有用:它直接对应一条可执行的"下一步该补哪条规则"。

  5. CI 双门:现有 size-budget gate 之外,加一条"难例回归门"——规则基线允许在难例上失分(难例本来就为暴露脆弱点而生),但失分模式必须稳定(同一 seed 同样的失败集)。一旦某次改动让难例失败集变化,CI 标记"判别行为变了",强制人工确认这是改进还是回归。

参考资料

资源类型发布日期链接
Hamel Husain LLM Evals FAQ(error analysis / open & axial coding / theoretical saturation)工程博客原文2025(持续更新)https://hamel.dev/blog/posts/evals-faq/
Aakash G. AI Evals Masterclass with Hamel & Shreya(轴向编码→可二元判定失败类)课程纪要2025-09https://www.aakashg.com/ai-evals-masterclass-with-hamel-shreya/
Encord Active Learning in ML: Guide & Strategies 2025(不确定性采样、决策边界)工程博客2025https://encord.com/blog/active-learning-machine-learning-guide/
PLOS One Enhanced uncertainty sampling with category information论文2025-07https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0327694
Springer ML How to measure uncertainty in uncertainty sampling for active learning论文2021https://link.springer.com/article/10.1007/s10994-021-06003-9
项目 src/aml/generator.ts(seeded 合成 + label=意图纪律)代码本仓库

SOTA 检查 (2026-06-11)

  • 本主题当前 SOTA:错误分析驱动的 eval 数据集构造(Hamel & Shreya 路线)是 2025-2026 AI eval 的主线方法论,OpenAI CPO 2025-08 "PM 最重要技能是写 evals" 进一步把它推成产品岗位的硬技能。难例挖掘的理论底座(active learning / 不确定性采样)则是十余年的成熟框架,2025 仍在迭代(category-aware uncertainty sampling, PLOS One 2025-07)。
  • 是否仍是 SOTA:✅ 是。"先错误分析、再定向补数据"是当下主流共识;"盲目扩量"已被明确视为反模式。
  • 2026 进展 / 替代
    • LLM 辅助难例生成(用 LLM 沿 taxonomy 盲区批量造对抗样本)开始普及,但与本项目纪律一致的告诫是——"LLM 模拟用户是不可靠代理"(2026-01),难例的金标标注关键评分仍须人工抽检,不能让 LLM 既造样本又当裁判。
    • τ²-bench 的 pass^k(2025-06,2026-04 更新 38 模型)把"难"从单样本难度上升到"多次重试的稳定性难度",提示难例不只挑空间维度,也要挑时间/重试维度。
  • 本项目对齐:定向难例批 + 标签解耦 + seeded 复现,正是上述 SOTA 在一个确定性、可进 CI 的合成 AML 数据集上的落地;规则基线允许在难例失分但失分模式须稳定,对应 pass^k 的"稳定性即正确性"思想。