> --max-rows 会注入你正在审计的那种泄漏——一半的"意外结果"是截断

Research Tools Advanced 2026-10-11 18:09 2026-10-11
#data-leakage #experiment-design #smoke-test #measurement #failure-modes

一个数据集审计工具报告某天的标签泄漏,审计不通过。真实原因不是那天的数据,是 冒烟测试用的 --max-rows 开关。

// TABLE_OF_CONTENTS

结论先行

一个数据集审计工具报告某天的标签泄漏,审计不通过。真实原因不是那天的数据,是 冒烟测试用的 --max-rows 开关。

标签里的漂移校正是一个样本均值统计量。在一个前缀上算,它减掉的是前缀的均值, 把局部趋势原封不动留下来——而局部趋势就是未来信息。同一天的同一个检查:

读入范围 aggressor 侧 AUC 判定(偏离上限 0.10)
整个交易日 0.5012 通过
前 200,000 笔 0.5897 标记为 [near]
前 20,000 笔 0.6979 拒绝数据集

截断越狠,"泄漏"越严重。这个项目的调试清单里把它排在第 5 条,附了一句:

这个项目历史上一半的意外结果都是截断。

为什么

--max-rows 这类开关的危险性被系统性低估,因为它看起来是纯粹的量的削减。 但凡是跨样本计算的统计量,前缀都不是缩小版的全体:

  • 样本均值 / 中位数的去除:在前缀上留下趋势(这一例)。
  • 标准化 / 白化:用前缀的方差,在全量上尺度就错了。
  • 分位数阈值:前缀的 p99 不是全体的 p99,见 你的 p90 和工具的 p90 不是一个 p90——差 31%,而且没人报错。
  • 去重 / 冲突检测:前缀里看不到后面出现的重复项,于是"干净"。

共同形状是:预处理里有一个全局量,而截断把它换成了一个局部量, 并且换完之后代码路径完全一样,不报任何错。

更危险的是它和审计的方向一致:这里截断制造了泄漏,于是审计器尽职地报了泄漏。 一个尽职的检查器加一个被破坏的预处理,等于一条高可信度的假结论。

这个项目的处理是双管:代码里加 truncated() 标志并打印警告说明这件事, 文档里在每个 --max-rows 出现的地方都标注"见 §6.15——截断会破坏漂移校正并注入泄漏", CLI 帮助里直接写 --max-rows N truncate each day (SMOKE ONLY)。

怎么用

  1. 把"这个结果是在截断数据上得到的吗"放进每次意外结果的前三个问题里。 排在"代码有 bug 吗"之前——它更常见,也更容易排除。
  2. 给截断开关加一个会跟着结果一起输出的标志位。 不是只在 stderr 打一行警告(没人看),而是写进结果的 JSON, 让下游能拒绝一份被截断的产物。
  3. 列出预处理里所有的全局统计量,逐个问"在前缀上这个量还对吗"。 不对的,要么禁止和截断同时使用,要么改成在线/两遍算法。
  4. 冒烟测试要缩小的是"规模",不是"分布"。 更安全的缩小方式是整段抽样(整天、整个 session),不是取前缀。
  5. 审计器报出拒绝时,先确认它拿到的是完整输入。 一个正确的检查器在被破坏的输入上会给出自信的错误答案—— 见 一天里五次搞错同一类事:核查步骤本身没被核查。

出处