planted-truth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

planted-truth — the pipeline runs on synthetic truth first

planted-truth — 流水线必须先在合成真值上运行

A pipeline that has only ever seen real data has never been tested, because nobody knew what the right answer was. The only inputs whose correct output is known are the ones you construct. Construct them, run them, and do it before real data is touched.
Real data cannot grade a pipeline. Whatever comes out looks like a finding: a slope, an evidence ratio, a list of anomalies, an empty list of anomalies. If the pipeline drops half its input on a parsing error, the output is smaller and still looks like a finding. If a sign convention is inverted, the conclusion reverses and still looks like a finding. The one situation in which output can be graded is when the answer was written down first — a plant: data generated from known parameters, fed through the full analysis, with recovery demanded to the accuracy the real analysis will claim.
The order rule is not a nicety. Controls designed after the real output has been inspected drift toward blessing it: the plant's parameters get chosen in the region where the pipeline is already known to behave, the corruption tests get chosen from the failure classes already ruled out, and the tolerance gets set just wide enough for what was seen. Plant first, look second. Once real output has been seen, the controls you design are no longer independent of it.
只见过真实数据的流水线从未经过真正测试,因为没有人知道正确答案是什么。唯一已知正确输出的输入,是你亲手构造的输入。先构造它们、运行它们,并且要在接触真实数据之前完成。
真实数据无法为流水线评分。无论输出是什么,它看起来都像一项发现:一个斜率、一个证据比、一份异常列表,或者一份空异常列表。如果流水线在解析错误时丢掉一半输入,输出会变小,却依然像一项发现。如果符号约定被反转,结论会反向,却依然像一项发现。唯一可以对输出进行评分的情况,是答案事先已经写下——即 plant:根据已知参数生成数据,将数据送入完整分析流程,并要求以真实分析将要声称的精度恢复出正确结果。
顺序规则不是一项无伤大雅的小事。看过真实输出后才设计的控制项会逐渐向为结果背书的方向偏移:plant 的参数会被选在流水线已知表现良好的区域,损坏测试会从已经被排除的故障类别中挑选,容差则会被设得刚好宽到足以容纳所见结果。先植入,后观察。一旦看过真实输出,之后设计的控制项就不再独立于真实输出。

The procedure

操作流程

0. Freeze the controls before the first real run. Write down the plants, the corruptions, the null tests, and the pass criteria for each while the real data is still unopened. A control invented later, to answer a doubt about a result already in hand, is evidence of much less.
1. Recover a plant, through the full path. Generate data from the model with known parameters and confirm the pipeline recovers them, to the accuracy the real analysis will claim. Two hard requirements hide in that sentence. First, the full path: the plant goes through the exact production entry point, same configuration, same options, same file formats — not a simplified call that skips the reader, the preprocessor, or the assembly step, because those are precisely where pipelines break. Second, the claimed accuracy: if the analysis will report an error bar, the planted value must come back inside it; if it will report evidence for a model, the plant generated under that model must yield that verdict. Recovery to worse accuracy than the claim tests a weaker claim than the one being made. Plant more than once, and include awkward corners of the parameter range along with the comfortable middle.
2. Catch corruptions. Feed the pipeline inputs deliberately broken in the ways it is supposed to detect — a sign flip, two swapped rows, two swapped column labels, a block scaled by a constant, a shifted grid, a duplicated record, a truncated file — one corruption at a time, and confirm every one is caught, loudly, at the step that claims to catch it. Run a clean twin alongside: an uncorrupted copy that must pass, proving the alarm is responding to the corruption and not to everything. A checker that has never fired is untested; a checker that fires on everything is noise.
3. Null the machinery. Where the pipeline sums, averages, or assembles contributions: push a table of zeros through the full path and demand exactly the baseline back — not approximately, exactly, because "approximately zero" is where sign errors and double-counting hide. Re-insert a component the system already contains and demand its recorded effect back, identically. Nulls test the plumbing separately from the statistics, and plumbing is where most real failures live.
4. Author the fixtures independently of the code under test. The expected answer must not come from the pipeline being tested. Generate the plant by construction — write the answer first, then produce data from it — or with a separate implementation. An expected-output file regenerated from the current code turns the test into a check that the code agrees with itself; every bug present at generation time is baked into the fixture and certified forever after.
5. Prove each control can fail. For every check in the battery, break its input once on purpose and watch it fire. This is the only way to find the checks that cannot fail — the aliased comparison, the unreachable assertion, the tolerance that spans the whole range. A control's first demonstrated failure is its birth certificate; before that it is a hope.
6. Record the controls with the result. The plants, the catches, and the nulls are part of the deliverable, not scaffolding to delete. A result whose controls were run but discarded cannot be distinguished, later, from a result whose controls were never run — and later is when the question gets asked.
7. When a control fires on real data: stop. Diagnose to the root before any further run. The forbidden move is the plausible benign story — "that check is oversensitive", "it's probably the known formatting quirk" — followed by an override. A fired control that gets explained away is worse than no control: it converts a working alarm into false confidence, and it trains everyone touching the pipeline to override the next one.
0. 在第一次真实运行之前冻结控制项。 趁真实数据仍未打开时,写下植入样本(plant)、损坏项(corruption)、零值测试(null test)以及每项控制的通过标准。事后为了消除对已拿到结果的疑虑而临时发明控制项,其证明力要弱得多。
1. 通过完整路径恢复 plant。 根据模型和已知参数生成数据,并确认流水线能够恢复这些参数,且达到真实分析将要声称的精度。这句话中隐藏着两项硬性要求。第一,完整路径:plant 必须通过与生产环境完全相同的入口点,使用相同的配置、选项和文件格式——不能使用跳过 reader、preprocessor 或 assembly 步骤的简化调用,因为这些恰恰是流水线最容易出错的环节。第二,声称的精度:如果分析将报告误差条(error bar),植入值必须重新落入其中;如果分析将报告支持某个模型的证据,那么在该模型下生成的 plant 必须给出相应判定。恢复到比所声称精度更差的水平,检验的只是一个更弱的主张。应多次设置 plant,并同时覆盖参数范围中棘手的边缘情形以及较为舒适的中段。
2. 捕获损坏。 将流水线预期能够检测的故障有意注入输入——符号翻转、两行交换、两列标签交换、一段数据整体乘以常数、网格平移、重复记录、文件截断——每次只注入一种损坏,并确认每一种都能在声称负责捕获它的步骤上被明确捕获。同时运行一个干净的孪生样本:一份未损坏的副本必须通过,以证明警报响应的是损坏本身,而不是所有输入。从未触发过的检查器未经测试;对所有输入都会触发的检查器则只是噪声。
3. 让机制归零(null the machinery)。 在流水线执行求和、平均或汇总贡献的地方:将一张全零表送入完整路径,并要求得到完全准确的基线——不是近似为零,而是精确基线,因为“近似为零”正是符号错误和重复计数藏身之处。重新插入系统已经包含的一个组件,并要求其已记录的效果被完全一致地恢复。零值测试将连接逻辑与统计逻辑分开检验,而大多数真实故障都藏在连接逻辑中。
4. 独立于被测代码编写 fixture。 预期答案绝不能来自正在测试的流水线。应通过构造方式生成 plant——先写下答案,再据此生成数据——或者使用独立实现。如果预期输出文件是由当前代码重新生成的,测试就变成了检查代码是否同意自身;生成时存在的每个 bug 都会被固化进 fixture,并在此后永久获得认证。
5. 证明每项控制都能失败。 对于测试组中的每项检查,故意破坏其输入一次,并观察它触发。这是找出无法失败的检查的唯一方法——使用别名变量的比较、永远无法到达的分支中的断言、覆盖整个可能取值范围的容差。控制项第一次被证明会失败,就是它的出生证明;在那之前,它只是一种期待。
6. 将控制项与结果一同记录。 植入样本、捕获结果和零值测试都是交付物的一部分,不是用完应删的脚手架。一个运行过控制项却将控制记录丢弃的结果,事后无法与从未运行控制项的结果区分——而事后恰恰是人们提出质疑的时候。
7. 当控制在真实数据上触发时:停止。 在继续任何运行之前,将问题诊断到根因。禁止的做法是先编出一个看似合理的无害解释——“这个检查过于敏感”“大概只是已知的格式问题”——然后再覆盖警报。被解释掉并强行忽略的已触发控制比没有控制更糟:它把有效的警报变成虚假信心,并训练所有接触流水线的团队成员在下次触发时选择忽略。

Failure modes the controls exist to catch

这些控制项旨在捕获的失败模式

Each is a class seen in practice, in agent-built and human-built pipelines alike. The vignette states the mechanism.
  • The check that cannot fail. A comparison of a quantity against itself through an aliased variable; an assertion inside a branch nothing reaches; a tolerance wider than the range of possible answers. The test suite is green from the day it is written to the day the pipeline dies, and it was never once capable of turning red. Step 5 is the cure: no check counts until it has been watched to fire.
  • The string-compared verifier. Two numbers formatted through the same printer and compared as text: the formatter rounds both sides identically, so values differing beyond the printed precision compare equal — and the comparison silently tests fewer digits than anyone believes. Worse, when both sides pass through the same serializer, a bug in the serializer equalizes genuinely different values. Compare numbers as numbers, at a stated precision, with the precision printed in the pass message.
  • Fixtures authored by the code under test. The "expected" file was produced by an earlier run of the same pipeline, and gets regenerated whenever it drifts. The suite now enforces self-agreement: any bug present at fixture time is preserved, and a later fix that changes the output reads as a regression. Expected answers come from construction or from an independent implementation, never from the thing being graded.
  • The silent-pass leg. A loop over cases catches exceptions per case and moves on; the summary counts the cases that ran. A missing input file yields an empty case list, zero failures, and a green banner — "all passed" where the denominator was silently zero. Every summary states its denominator, and the harness fails when the denominator is smaller than declared.
  • The tuned threshold. A plant is not recovered; instead of finding the cause, the tolerance is loosened until it is. Repeat a few times and the tolerance is exactly wide enough to pass a broken pipeline — a real error of the same size as the widening now passes by construction. A control that needs its threshold moved has found something; find out what.
  • The plant designed after peeking. Real output is inspected first, then a synthetic control is built "to confirm" — with parameters, corruption types, and pass criteria all chosen in the shadow of what was seen. The control confirms; it was never able to do anything else. This is why step 0 freezes the battery before the real data opens.
  • The simplified-path plant. The control runs through a convenience entry point — smaller grid, mocked reader, the assembly step stubbed out — and passes. The real run uses the full path, and the failure lives in a step the control skipped. A plant certifies exactly the code path it traversed and nothing else.
  • Self-consistency mistaken for a control. The calculation's own convergence diagnostics stay clean orders of magnitude past a real failure, because a pipeline that is consistently wrong is still consistent. Internal agreement, stability under iterations, and smooth residuals are properties of the machinery, not of the answer. Only a check with an independent notion of truth counts: a plant, a positivity or symmetry constraint the answer must obey, a second route.
  • The explained-away alarm. A control fires on real data; a plausible story is found; the run proceeds. When the failure finally surfaces through some other channel, the record shows the alarm worked and was overridden — the most expensive possible way to learn the control was right. Firing means stop; the story, if true, will survive a root-cause diagnosis.
  • The unconditional banner. The driver prints its completion message outside the conditional that checks the results, so a run that verified nothing announces success. The verdict text must be produced by the verification itself, from measured quantities — and the deliberate break of step 5 exposes this class immediately, because the banner also blesses the broken run.
以下每一类都是实践中出现过的,无论流水线是由 Agent 构建还是由人构建。示例说明具体机制。
  • 无法失败的检查。 通过别名变量将某个量与自身比较;断言位于永远无法到达的分支中;容差宽于所有可能答案的范围。测试套件从写下的第一天到流水线失效的那一天始终是绿色的,而且从来就没有能力变红。第 5 步是解药:除非亲眼看到某项检查触发,否则它都不算数。
  • 按字符串比较的验证器。 两个数字经由同一个打印器格式化后作为文本进行比较:格式化器会以相同方式对两边舍入,因此相差超过打印精度的数值仍会比较为相等——而且这种比较实际检查的位数比任何人相信的都少。更糟的是,如果两边都经过同一个序列化器,序列化器中的 bug 就会把真正不同的值变成相等。应当以数值方式比较数字,使用明确声明的精度,并在通过消息中打印该精度。
  • 由被测代码编写的 fixture。 “预期”文件由同一流水线先前的一次运行生成,并且会在流水线发生漂移时不断重新生成。测试套件此时只保证自我一致:生成 fixture 时存在的任何 bug 都会被保留,而之后修复代码若改变输出,就会被误读为回归。预期答案应来自构造或独立实现,绝不能来自被评分对象。
  • 静默通过分支。 遍历各用例的循环逐个捕获异常后继续执行;摘要只统计运行过的用例。缺失的输入文件会产生空用例列表、零失败和一条绿色横幅——“全部通过”,尽管分母已悄无声息地变成零。每份摘要都应说明自己的分母;当分母小于声明数量时,测试框架必须失败。
  • 被调优的阈值。 plant 没有被恢复;没有去查找原因,反而不断放宽容差,直到它被恢复。重复几次之后,容差就会恰好宽到足以让一个已损坏的流水线通过——而同等大小的真实错误现在也会按构造通过。一项需要移动阈值的控制项已经发现了问题;要找出问题所在。
  • 窥视结果后才设计的 plant。 先检查真实输出,再构建一个合成控制来“确认”它——参数、损坏类型和通过标准全都根据已经看到的结果来选择。这个控制只会确认,从一开始就不可能做其他事情。这就是第 0 步要求在打开真实数据之前冻结整套控制项的原因。
  • 走简化路径的 plant。 控制项通过便捷入口点运行——更小的网格、模拟 reader、被 stub 掉的 assembly 步骤——并顺利通过。真实运行使用完整路径,而故障恰恰位于控制项跳过的那一步。一个 plant 只能证明它实际经过的代码路径,除此之外什么都证明不了。
  • 把自洽误当成控制。 即使真实故障已经发生,计算自身的收敛诊断仍会在多个数量级范围内保持正常,因为一个始终错误的流水线仍然可能保持一致。内部一致、迭代稳定和残差平滑是机制的性质,而不是答案的性质。只有建立在独立真值概念上的检查才算数:plant、答案必须满足的正性(positivity)或对称性(symmetry)约束,或者第二条路径。
  • 被解释掉的警报。 控制在真实数据上触发;人们找到一个看似合理的解释;运行继续进行。当故障最终通过其他渠道暴露时,记录会显示警报本来有效,却被人覆盖掉了——这是认识到控制正确时最昂贵的一种方式。触发就意味着停止;如果这个解释是真的,它会经得起根因诊断。
  • 无条件成功提示。 驱动程序在检查结果的条件分支之外打印完成消息,因此一次什么都没有验证的运行也会宣告成功。判定文本必须由验证过程本身根据实测值生成——而第 5 步的故意破坏会立即暴露这一类问题,因为成功提示同样会为已损坏的运行盖章放行。

Two micro-examples

两个微型示例

A regression pipeline. Write down a slope and intercept. Generate data from them with the noise model the analysis assumes. Run the production entry point — the same command the real data will get — and demand the planted values back inside the reported intervals. Then swap two column labels in a copy of the input and demand the consistency check names the columns; run the unswapped copy alongside and demand silence. Only then open the real data.
An assembler of contributions. Before trusting a total, push a table of zeros through the assembly and demand the exact baseline. Then take one component whose individual effect is already on record, re-insert it alone, and demand that recorded effect back to the digit. If the zeros come back nonzero or the known component comes back changed, the plumbing is broken, and no statistic computed through it means anything.
回归流水线。 写下斜率和截距。使用分析所假设的噪声模型由它们生成数据。运行生产入口点——真实数据将使用同一命令——并要求植入值落在报告区间内。然后在输入副本中交换两个列标签,并要求一致性检查明确指出这些列;同时运行未交换的副本,并要求它保持沉默。只有在此之后才能打开真实数据。
贡献汇总器。 在信任总计之前,将一张全零表送入汇总流程,并要求得到完全准确的基线。然后取出一个其单独效果已有记录的组件,只重新插入该组件,并要求其已记录效果精确到每一位数字都被恢复。如果零值返回非零,或者已知组件返回的结果发生变化,那么连接机制已经损坏,通过它计算出的任何统计量都没有意义。

Checklist before the first real-data run

第一次真实数据运行前的检查清单

  • Control battery — plants, corruptions, nulls, pass criteria — written down before real data was opened.
  • Plant recovered through the full production path, to the accuracy the real analysis will claim, including awkward parameter corners.
  • Every claimed detector shown to catch its corruption, loudly; clean twin passed alongside.
  • Zeros through the assembly returned the exact baseline; a known component returned its recorded effect identically.
  • No fixture was authored by the code under test.
  • Every control has been watched to fail at least once, on a deliberate break.
  • Pass messages state counts and denominators; no banner prints outside the verification.
  • Controls filed with the result, not deleted.
  • Standing order acknowledged: a control that fires on real data stops the run until the root cause is known.
Sources and acknowledgments. Planted-truth recovery is what statisticians call simulation-based calibration (Cook, Gelman and Rubin, J. Comput. Graph. Stat. 15 (2006) 675; Talts, Betancourt, Simpson, Vehtari and Gelman, arXiv:1804.06788) and what experimental collaborations call injection tests or mock-data challenges; "prove each control can fail" is mutation testing (DeMillo, Lipton and Sayward, IEEE Computer 11(4) (1978) 34); positive and negative controls are borrowed, name and all, from the wet lab. We claim only the checklist.
  • 控制组——植入样本、损坏项、零值测试、通过标准——已在打开真实数据前写下。
  • plant 已通过完整生产路径,以真实分析将要声称的精度被恢复,其中包括棘手的参数边缘情形。
  • 每个声称能够检测的检测器都已被证明能捕获对应损坏并明确报警;干净孪生样本同时通过。
  • 全零表通过汇总流程后返回完全准确的基线;已知组件返回了完全一致的已记录效果。
  • 没有任何 fixture 由被测代码编写。
  • 每项控制都已在一次故意破坏中被观察到至少失败一次。
  • 通过消息会说明计数和分母;没有任何成功提示在验证之外打印。
  • 控制项已随结果归档,而非删除。
  • 常设指令已获确认:控制在真实数据上触发时,在查明根因之前必须停止运行。
来源与致谢。 植入真值恢复是统计学家所称的基于模拟的校准(simulation-based calibration;Cook, Gelman and Rubin, J. Comput. Graph. Stat. 15 (2006) 675;Talts, Betancourt, Simpson, Vehtari and Gelman, arXiv:1804.06788),也是实验协作团队所称的注入测试(injection test)或模拟数据挑战(mock-data challenge);“证明每项控制都能失败”属于变异测试(mutation testing;DeMillo, Lipton and Sayward, IEEE Computer 11(4) (1978) 34);阳性对照与阴性对照(positive and negative controls)的名称本身也借鉴自湿实验室。我们所主张的只有这份检查清单。