Does reconstruction accuracy include unfamiliar answers?

Vocabulary
Hide a categorical answer and reconstruct it from numeric context, while varying how many test answers belong to the learned vocabulary.

Example

x: 2.0
code: class-2
hide: true

code is hidden wherever hide is true. Unknown code names cannot be emitted by the learned output vocabulary.

Model tree with record, x, code. Selector notes identify hidden values. hide chooses which code answers are hidden.

Model tree with record, x, code. Selector notes identify hidden values. hide chooses which code answers are hidden.

Figure 1: Selector notes identify hidden values. hide chooses which code answers are hidden.

Comparison

Compare known-answer accuracy, vocabulary coverage, and accuracy over all selected answers.

Result

P062 · Latest full run · Limited

Seed 7621 on gpu: 18 of 18 test checks met. This experiment demonstrates a limitation. History: 6 of 7 full runs met every check, across 3 distinct seeds. History can include earlier source versions. The experiment code has changed since this run; these measurements describe its earlier version.

Comparison Latest measurement
Known-answer accuracy, low coverage 100.0%
Answers covered 10.0%
All-answer accuracy 10.0%

Known-only accuracy stays high even as coverage falls. Excluding unfamiliar answers from the content score does not mean they were recovered.

Experiment details and code

Latest full run

Seed 7621, gpu, recorded 2026-09-18T16:43:29.712598+00:00. Outcome: met.

Source fingerprint: 7f346bf2b13029b71502e622b76345661ebd20dc271cd806a574d1c18d02165c. Python 3.12.6; Torch 2.12.0.

Measurement Value
known_1024 loss.state: 2.93646e-06; accuracy.state: 1; vocabulary.size: 8; targets.known: 1024; targets.unavailable: 0; coverage.content: 1; accuracy.content: 1; accuracy.all: 1; loss.content: 2.32126e-05; nll.content: 1.77534e-05
known_512 loss.state: 2.93646e-06; accuracy.state: 1; vocabulary.size: 8; targets.known: 512; targets.unavailable: 512; coverage.content: 0.5; accuracy.content: 1; accuracy.all: 0.5; loss.content: 2.3263e-05; nll.content: 1.77881e-05
known_102 loss.state: 2.93646e-06; accuracy.state: 1; vocabulary.size: 8; targets.known: 102; targets.unavailable: 922; coverage.content: 0.0996094; accuracy.content: 1; accuracy.all: 0.0996094; loss.content: 2.31509e-05; nll.content: 1.77655e-05
batch_partition_drift 1.81899e-12
reported_content_loss_drift 0
known_0 loss.state: 2.93646e-06; accuracy.state: 1; vocabulary.size: 8; targets.known: 0; targets.unavailable: 1024; coverage.content: 0; accuracy.content: None; accuracy.all: 0; loss.content: None; nll.content: None
Behavioral checks
Behavioral check Outcome
1024: exact known and unavailable target counts Met
1024: coverage matches selected targets Met
1024: known accuracy exceeds 0.95 Met
1024: all-target accuracy includes unavailable errors Met
512: exact known and unavailable target counts Met
512: coverage matches selected targets Met
512: known accuracy exceeds 0.95 Met
512: all-target accuracy includes unavailable errors Met
102: exact known and unavailable target counts Met
102: coverage matches selected targets Met
102: known accuracy exceeds 0.95 Met
102: all-target accuracy includes unavailable errors Met
Rebatching preserves counts and scores Met
0: exact known and unavailable target counts Met
0: coverage matches selected targets Met
All-OOV conditional scores are undefined Met
All-OOV all-target accuracy is zero Met
Validation leaves mapping and exposures frozen Met

Full run history

Runs may include earlier source versions. Repeating a seed is not an independent seed check.

Seed Outcome Recorded Code fingerprint
7621 not met 2026-09-17T00:03:36.763376+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7621 met 2026-09-17T00:03:31.315420+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7621 met 2026-09-17T00:04:05.236432+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7701 met 2026-09-17T00:04:19.380824+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7702 met 2026-09-17T00:04:31.496930+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7621 met 2026-09-17T01:03:31.728880+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72
7621 met 2026-09-18T16:43:29.712598+00:00 0337cd61fc6b8dfea7ae8c09c2b5fdefe0d410dd8d27e1d2969e5e173a0abb72

Recorded results.

Run this experiment

Proof execution is optional and separate from building this site. Use a RelFlow build matching the recorded provenance; replace the wheel path below with that artifact. Run from this documentation repository’s root:

uv run --with /path/to/matching-relflow.whl python proofs/run.py P062

Or, from a prepared proof environment, run the self-contained script directly:

PYTHONPATH=proofs python proofs/vocabulary/pretraining_coverage.py

Add --accelerator gpu for CUDA or --seed 42 for another seeded experiment. --steps 2 checks execution with a short training budget; it is recorded as a smoke run.

Download the complete proof.

Training and controls

Train for 400 updates on 8,192 rows; storage grows to admit all eight labels. Evaluation selects exactly 1,024 of 2,048 independent records. Compare the original set with copies retaining 50%, 10%, or 0% of selected target names. All copies preserve the exact visible inputs and query-selected masks.

Repeat the 10%-coverage case with unknown targets grouped into separate batches. Count-weighted epoch metrics must agree despite all-OOV batches. Scores with no known targets must be undefined, not zero reconstruction error. Naive accuracy over all selected valued targets counts OOV as wrong.

Complete experiment code

Code
"""P062: report stable reconstruction quality alongside pretraining coverage."""

from collections.abc import Iterator
from functools import partial

import lightning.pytorch as lit
import numpy as np
from reporting import report

import relflow as rf

PROOF_ID = "P062"
BUDGET = 400
LABELS = tuple(f"class-{index}" for index in range(8))


def records(*, rows: int, seed: int) -> Iterator[dict]:
    rng = np.random.default_rng(seed)
    for index, label in enumerate(rng.integers(0, len(LABELS), rows)):
        yield {"x": float(label), "code": LABELS[label], "hide": index % 2 == 0}


def build() -> rf.Model:
    model = rf.Model(
        d_model=32,
        n_layers=1,
        n_heads=4,
        batch_size=64,
        dropout=0.0,
        x=rf.Number,
        code=rf.Category(mask=rf.Mask(query="hide", reconstruct=True)),
    )
    model.optimizer = rf.adamw(learning_rate=0.002)
    return model


def evaluate(trainer: lit.Trainer, model: rf.Model, rows: list[dict]) -> dict[str, float]:
    data = rf.SyntheticDataModule(model=model, validate=lambda: iter(rows), seed=0)
    result = trainer.validate(model, datamodule=data, verbose=False)[0]
    prefix = ".code/validate."
    return {name.removeprefix(prefix): float(value) for name, value in result.items() if name.startswith(prefix)}


def run(seed: int, steps: int | None, accelerator: str) -> tuple[dict, dict]:
    lit.seed_everything(seed, workers=True)
    model = build()
    data = rf.SyntheticDataModule(
        model=model,
        train=partial(records, rows=8192, seed=seed + 1),
        validate=partial(records, rows=1024, seed=seed + 2),
        seed=seed,
    )
    trainer = lit.Trainer(
        accelerator=accelerator,
        devices=1,
        max_epochs=-1,
        max_steps=BUDGET if steps is None else min(steps, BUDGET),
        logger=False,
        enable_checkpointing=False,
        enable_progress_bar=False,
        enable_model_summary=False,
        deterministic=True,
        num_sanity_val_steps=0,
    )
    trainer.fit(model, datamodule=data)
    original = list(records(rows=2048, seed=seed + 3))
    vocabulary = rf.Category.vocabulary(model, "/code")
    counts = rf.Category.counts(model, "/code")
    metrics, checks = {}, {}
    for keep in (1024, 512, 102, 0):
        selected = 0
        rows = []
        for row in original:
            unavailable = row["hide"] and selected >= keep
            rows.append({**row, "code": f"novel-{row['code']}" if unavailable else row["code"]})
            selected += int(row["hide"])
        measured = evaluate(trainer, model, rows)
        # Keep undefined quantities explicit without nonstandard JSON NaNs.
        metrics[f"known_{keep}"] = {name: value if np.isfinite(value) else None for name, value in measured.items()}
        checks[f"{keep}: exact known and unavailable target counts"] = (
            measured.get("targets.known") == keep and measured.get("targets.unavailable") == 1024 - keep
        )
        checks[f"{keep}: coverage matches selected targets"] = (
            abs(measured.get("coverage.content", -1) - keep / 1024) < 1e-7
        )
        if keep:
            checks[f"{keep}: known accuracy exceeds 0.95"] = measured.get("accuracy.content", -1) > 0.95
            checks[f"{keep}: all-target accuracy includes unavailable errors"] = (
                abs(measured.get("accuracy.all", -1) - measured.get("accuracy.content", 0) * keep / 1024) < 1e-7
            )
        else:
            checks["All-OOV conditional scores are undefined"] = all(
                name in measured and np.isnan(measured[name])
                for name in ("accuracy.content", "nll.content", "loss.content")
            )
            checks["All-OOV all-target accuracy is zero"] = measured.get("accuracy.all") == 0
        if keep == 102:
            rng = np.random.default_rng(seed + 4)
            permuted = [rows[index] for index in rng.permutation(len(rows))]
            regrouped = evaluate(trainer, model, permuted)
            names = (
                "targets.known",
                "targets.unavailable",
                "coverage.content",
                "accuracy.content",
                "accuracy.all",
                "nll.content",
                "loss.content",
            )
            drift = (
                max(abs(measured[name] - regrouped[name]) for name in names)
                if all(name in measured and name in regrouped for name in names)
                else None
            )
            metrics["batch_partition_drift"] = drift
            metrics["reported_content_loss_drift"] = abs(measured["loss.content"] - regrouped["loss.content"])
            checks["Rebatching preserves counts and scores"] = drift is not None and drift < 1e-5
    checks["Validation leaves mapping and exposures frozen"] = (
        rf.Category.vocabulary(model, "/code") == vocabulary and rf.Category.counts(model, "/code") == counts
    )
    return metrics, checks


if __name__ == "__main__":
    report(PROOF_ID, run, seed=7621)