Skip to content

invariants + Bench 2 final arm: the convenient artifact is the misleading one (#4523) - #4651

Merged
JDerekLomas merged 2 commits into
mainfrom
fix/measurement-instruments-lesson
Sep 12, 2026
Merged

JDerekLomas merged 2 commits into
mainfrom
fix/measurement-instruments-lesson

Conversation

@JDerekLomas

Copy link
Copy Markdown
Member

Three instrument failures in one afternoon, all the same shape: the artifact a reasonable person reaches for first was the wrong one — because it was complete, always-present, or already open, which are exactly the properties a stale or partial record has.

  1. A failures-only log reads as a complete log. worker-0.jsonl records only failures and skips. Reading it produced "2,016 failures, zero successes — the job is broken", which I published as an operational alert on OCR fabricates Sanskrit Hindu scripture on Tibetan manuscript folios — 529 books, 355 of them badged first translations #4523. The job was healthy: its output directory grew 7,430 → 7,451 files in 15 minutes with valid Tibetan in them. Retracted. Verify a worker by its output growing, not by its error log being non-empty.
  2. An err field capturing a stderr tail reported the same benign trailing warning (kenlm bindings are not installed, printed on every invocation including successful ones) for every failure, hiding the real causes.
  3. pages.image_width is on 100% of rows; image_metadata.width on 27.3% — and where both exist they disagree 4,000/4,000 sampled (image_width is the display derivative; one page reads 1200×800 against a real 3888×2592 master). A corpus-wide "42% of pages are too low-res to re-OCR" was built on the populated field and was wrong.

Credit to the peer session that caught (1) and (2) and then self-corrected on (3); I verified all three against the database before writing this.

Docs only — no code paths touched.

@vercel

vercel Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sourcelibrary-v2 Ignored Ignored Preview Sep 4, 2026 10:10pm UTC

Request Review

@JDerekLomas JDerekLomas changed the title invariants: the convenient artifact is the misleading one (#4523) invariants + Bench 2 final arm: the convenient artifact is the misleading one (#4523) Sep 4, 2026
@JDerekLomas

Copy link
Copy Markdown
Member Author

Scope grew: this branch now also carries the final Bench 2 arm, which is the first engine comparison reported honestly (coverage + conditional + unconditional accuracy, one estimator across arms).

At matched coverage, on the 100-page Wikisource tier:

Kraken cond / uncond @cov Gemini cond / uncond @cov
Greek (n=25) 99.3% / 91.4% @92% 97.8% / 89.7% @92%
Latin (n=45) 97.7% / 82.5% @84% 96.3% / 81.7% @85%
German (n=30) 94.7% / 44.2% @47% 99.4% / 96.0% @97%

Three things worth noting:

  1. Greek is the first specialist result with both a real sample and matched coverage. The earlier "Kraken beats Gemini on Greek by +0.74pp" was n=4 with no confidence interval and I should not have quoted it; this supersedes it with n=25 at equal coverage.
  2. German is a clear specialist loss, and only the new reporting shows it. Conditional accuracy alone reads 94.7% — respectable. The truth is 47% coverage and 44.2% unconditional: it simply cannot read half the pages. That is exactly the failure the old single-number reporting hid.
  3. Latin is a tie.

Docs + eval only; no production code paths.

…nch 2 arm (#4523)

Three instrument failures in one afternoon, same shape every time: the artifact
you reach for FIRST was wrong, because it was complete/always-present/already
open - exactly the properties a stale or partial record has.

- A failures-only log reads as a complete log. worker-0.jsonl records only
  failures/skips; reading it produced '2,016 failures, zero successes, job is
  broken', published as an operational alert. The job was healthy - output dir
  grew 7,430 -> 7,451 files in 15 min. Retracted. Verify a worker by its OUTPUT
  GROWING, never by its error log being non-empty.
- An err field holding a stderr TAIL reports the last warning, not the cause:
  every failure showed a benign kenlm notice, hiding the real ones.
- pages.image_width is on 100% of rows, image_metadata.width on 27.3%, and where
  both exist they disagree 4,000/4,000 - image_width is the DISPLAY derivative
  (1200x800 recorded against a real 3888x2592 master). A corpus figure built on
  it was wrong by ~21k pages.

Also carries the final Bench 2 arm - the first comparison reported honestly
(coverage + conditional + unconditional, one estimator). At matched coverage:
Greek n=25 kraken 99.3/91.4 @92% vs gemini 97.8/89.7 @92%; Latin n=45 tie;
German n=30 kraken 94.7 cond but 44.2 UNCOND @47% - a loss only the new
reporting reveals.

Guard shipped with it (10 unit tests on the wikitext cleaner) was negative-
controlled: reverting the fix turns 3 of them red.

Signed-off-by: JDerekLomas <j.d.lomas@tudelft.nl>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JDerekLomas
JDerekLomas force-pushed the fix/measurement-instruments-lesson branch from c3375cd to c578edc Compare September 4, 2026 22:10
JDerekLomas added a commit that referenced this pull request Sep 12, 2026
…eleting (#4523) (#4660)

* eval: measure the ground truth itself, and fix the 6% of it we were deleting (#4523)

Track B item 1 of the OCR measurement program: nothing downstream is quotable
until we know how wrong the reference is. Two questions, one answer each.

HOW WRONG IS THE REFERENCE? Wikisource pages carry their own second opinion:
level 3 means one human transcribed the page, level 4 means a second, different
human re-read it against the scan. reference-error-rate.mjs replays each page's
revision history and measures what that second reader changed, through the same
normalizeForScript folding the bench scores through — so the number lands on the
bench's own scale. No hand transcription, and deliberately no VLM
re-transcription, which would referee a bench about VLMs with the system under
test. n=69 independent validations, median 0.00%:

  Greek  0.07% CI [0.02, 0.13]  (engine gap 1.5pp — stands, 20x the noise)
  German 0.06% CI [0.02, 0.11]
  Latin  1.15% CI [0.17, 2.47]  (engine gap 1.4pp — INSIDE the noise)

So "Latin is a tie" is not a shrug, it is arithmetic. Latin's distribution is
skewed rather than uniformly bad: most references are exact, a handful omit a
whole printed block (an apparatus criticus, a clause).

THE BIGGER ERROR WAS OURS. cleanPageText blanked a template together with the
text it wrapped, and {{SperrSchrift|D'Glocke het zwölfi gschlage.}} is a printed
line, not scaffolding. Measured 6.0% of Greek reference letters, 3.8% of Latin,
0.9% of German — 5-80x the human error rate — with nested apparatus markup
surviving as literal braces on top of that. Re-scoring the stored Gemini
outputs against corrected references (no re-runs, no cost): Greek conditional
accuracy 97.8% -> 99.6% at coverage 92% -> 88%, driven by one page that went
59.7% -> 100.0% because a perfect transcription had been charged 40 points for
our own markup. Coverage falls because a table-of-contents page the corrupt
reference waved through now fails the guard honestly.

That 1.8pp Greek correction is LARGER than the 1.5pp Greek engine gap it was
used to judge, so every Bench 2 Greek arm must be rescored before the number is
quoted again. The Kraken ws outputs live in PR #4651 and were not available here.

Cleaner rules, each from a page that broke: keep every positional argument, not
the longest (a table-of-contents line is nothing but those); emit a
sort-key/display pair once; take the fragment THIS page prints from {{hws}};
drop named parameters as configuration but keep numbered ones as content; treat
<ref> and {{CRef}} identically, because a validator moving a footnote between
the two read as a 13.6% human correction and was the largest delta in the first
run. Footnote text is excluded deliberately: it is printed on 9 of 10 footnoted
pages (88.6% recall in the Gemini arm against a 97.9% body control), but the
tenth is a modern editorial gloss with no syntactic tell, and a reference
asserting unprinted text is charged to the engine.

- reference-error-rate.mjs — instruments A (level-3 error), B (level-4
  residual), C (what our cleaner deleted); bootstrap CIs, change hunks so the
  number can be read rather than just quoted
- refresh-ws-references.mjs — re-clean pinned references in place from live
  wikitext, so a cleaner fix does not move the bench underneath stored results
- 8 new unit tests, one per rule above
- 67 of 120 pinned references corrected; ground-truth-wikisource-l4/ adds the
  matched level-4 samples that gave Greek and Latin a real n

Signed-off-by: JDerekLomas <j.d.lomas@tudelft.nl>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014WmTDpvthUQFPnPnVLPC56

* eval: detect fabrication without a reference, and find where it cannot (#4523) (#4665)

Track C. Every metric we own compares OCR to a reference, so a model that has
memorised the published text scores WELL while never reading the page — Bench 1
caught a folio where Gemini hit 0.790 against the Derge canon while agreeing 0.33
with both specialists' reads of that same image. Production has no reference at
all.

A CTC line recogniser carries no language model over the target text, so it
cannot recite. Two independently-trained ones converging is evidence about the
INK rather than about any edition. The detector scores specialist convergence,
VLM-to-ink agreement and unit overrun per page, and abstains when the specialists
do not converge.

VALIDATED on 349 pages across two sets with known answers. Positive: Bench 1
Derge Kangyur, 313 folios, two BDRC recognisers plus production-era Gemini, of
which 51 carry an externally-established label (Gemini below 0.2 against the
canon where both specialists exceed 0.8 — no reading of the image produces
that). Negative: the Bench 2 print arms already in this repo, Kraken + Surya +
Gemini on the same 36 pages.

  51/51 positives flagged, 0/5 false positives, and the plateau is wide:
  100% precision AND 100% recall for every gap threshold from 0.10 to 0.40.
  The default 0.35 sits mid-plateau, not on a cliff — which is the answer to
  "the guard threshold was picked by hand".

THE BLIND SPOT IS THE FINDING. Recitation and specialist failure share a cause: a
hard image is what makes a CTC engine fail and what pushes a VLM onto its memory.
So "abstain when the ink is unestablished" silently excuses exactly the pages
that matter — the seven most flagrant Bench 1 cases, Gemini at 0.75-0.98 against
the canon while BOTH specialists scored 0.00-0.16 against it and it emitted up to
13.5x the units on the page, all sat in INCONCLUSIVE. A canon-anchored rule
recovers 9 of them under a separate RECITING? verdict, kept apart from the
verified ones because "the specialists are broken on this page" is a live
alternative.

Two things that cost a page each and are now structural:
- ORDER IS NOT FAILURE. On the two-column Bekker Categories page Kraken reads
  across the gutter while Surya reads down the column: agreement 0.01 between two
  engines that both transcribe it well. Gating on the sequence number sent 8 of
  11 print pages to INCONCLUSIVE for a layout reason. Every quantity is now the
  better of its ordered and order-free (multiset Dice over 3-unit shingles) form.
- OVERRUN IS THE FAMOUS TELL AND NOT THE USEFUL ONE. Median overrun across the
  203 flagged Tibetan pages is 0.93x; only 38/203 exceed 1.6x. The agreement gap
  carries the signal.

Coverage, not accuracy, is the constraint: 56 of 349 pages are both labelled and
judgeable, and on print 6 of the 11 pages carrying a VLM arm are gated out
because Kraken and Surya genuinely disagree (Copernicus 1543, the ~1490 Malleus,
Weigel 1618, Zesen 1645, the Poemander apparatus, Bekker). Two specialists that
fail together buy nothing.

Stated weakness: positive and negative classes differ in script, medium AND
engine set, so perfect separation is evidence the statistic orders known-bad
above known-good — not that it discriminates within early modern print. That
needs a print corpus with known fabrication, which does not exist yet.



Claude-Session: https://claude.ai/code/session_014WmTDpvthUQFPnPnVLPC56

Signed-off-by: JDerekLomas <j.d.lomas@tudelft.nl>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: JDerekLomas <j.d.lomas@tudelft.nl>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…; kept both)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KLkXXbUjg6KD9PRVFQN8b3
Signed-off-by: JDerekLomas <j.d.lomas@tudelft.nl>
@JDerekLomas
JDerekLomas merged commit 2532441 into main Sep 12, 2026
4 checks passed
@JDerekLomas
JDerekLomas deleted the fix/measurement-instruments-lesson branch September 22, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant