Can perfect accuracy hide answers it cannot name?

Vocabulary and OOV
Ask a Category output to predict a mix of familiar and unfamiliar target labels.

Example

x: 0.7
hint: novel-negative
label: positive

The answer is shown here for explanation; it is hidden from the model when scored.

Model tree with record, x, hint, label. Amber cards are hidden prediction targets. Other fields provide the input.

Model tree with record, x, hint, label. Amber cards are hidden prediction targets. Other fields provide the input.

Figure 1: Amber cards are hidden prediction targets. Other fields provide the input.

Comparison

Compare accuracy on known labels with accuracy over every row, and count how many answers the vocabulary can represent.

Result

P056 · Latest full run · Limited

Seed 5601 on cpu: 13 of 13 test checks met. This experiment demonstrates a limitation. History: 10 of 10 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-label accuracy 100.0%
All-row accuracy 50.0%
Answers covered 50.0%

Known-only accuracy can look perfect while half the answers are impossible to emit. Report coverage and all-row accuracy alongside it; confidence need not warn about the missing labels.

Experiment details and code

Latest full run

Seed 5601, cpu, recorded 2026-09-21T18:47:59.175390+00:00. Outcome: met.

Source fingerprint: 9371f829f3b9087ca2280fbda473e1771ad6046d59e6538da71d43d23b32d559. Python 3.12.6; Torch 2.12.0.

Measurement Value
steps 350
output_vocabulary negative, positive
hint_vocabulary novel-positive, novel-negative, positive, negative
coverage 0.5
builtin_content_accuracy 1
known_accuracy 1
all_row_accuracy 0.5
unknown_accuracy 0
unknown_mean_confidence 1
shuffled_known_accuracy 0.480469
majority_baseline 0.256348
Behavioral checks
Behavioral check Outcome
Known-only content accuracy reaches 0.95 Met
Built-in accuracy agrees with known-only external scoring Met
Coverage is exactly one half Met
All-row accuracy exposes the coverage ceiling Met
Unseen output labels cannot be emitted Met
Unknown answers still receive confident known-label predictions Met
Shuffling x removes known-label accuracy Met
Predictions and candidates use populated output labels only Met
Candidate probabilities normalize over populated labels Met
Labels known in another field remain unknown here Met
Unknown target content uses the unavailable sentinel Met
Evaluation preserves output vocabulary and exposure counts Met
Evaluation preserves hint vocabulary and numerical moments Met

Full run history

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

Seed Outcome Recorded Code fingerprint
5601 met 2026-09-16T22:46:28.922169+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
5601 met 2026-09-16T22:48:54.465520+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
6501 met 2026-09-16T22:49:23.910088+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
6502 met 2026-09-16T22:49:53.897795+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
5601 met 2026-09-17T00:03:19.636256+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
5601 met 2026-09-17T01:01:50.936886+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
5601 met 2026-09-18T16:41:20.455374+00:00 43e325604a1e68af443e826ef340dbc81472dfbbb895929ad92bb4b6a8fafae7
5601 met 2026-09-20T02:47:26.524051+00:00 4e711d55b2edfbab57a2c30504a9de6e25813d1c083368a69809ac976ca1a3f0
5601 met 2026-09-20T02:52:50.225971+00:00 4e711d55b2edfbab57a2c30504a9de6e25813d1c083368a69809ac976ca1a3f0
5601 met 2026-09-21T18:47:59.175390+00:00 3c13a16796e77ad4ecfe1a80cfed0683b0cf7030cb6407c4cac35f5d55da16a4

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 P056

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

PYTHONPATH=proofs python proofs/vocabulary/unknown_target_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.

Data and model

Training labels are negative or positive according to the sign of x, with abs(x) ~ Uniform(0.2, 1). The independent hint takes all four strings, including names never used as training targets. Splits have 4,096 training, 512 validation, and 2,048 test rows, with exactly balanced signs. On half of test rows, rename the target to its novel counterpart without changing x. The output vocabulary automatically stores its two discovered training labels.

Training and controls

The model uses the xs preset.

Fit for 350 AdamW updates at learning rate 0.002. Require at least 0.95 known-only accuracy, exactly 50% output-label coverage, and zero accuracy on OOV targets. All-row accuracy must therefore be at most 0.5. Unknown targets should still attract confident known-label predictions on these deliberately unchanged inputs. Permuting x must reduce known-label accuracy toward chance. Call the actual test loop to compare its content metric with external scoring. Vocabularies, counts, and numerical normalization must stay frozen throughout.

Complete experiment code

Code
"""P056: a learned classifier exposes the difference between accuracy and vocabulary coverage."""

from collections.abc import Iterator
from functools import partial

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

import relflow as rf

PROOF_ID = "P056"
BUDGET = 350
LABELS = ("negative", "positive", "novel-negative", "novel-positive")


def records(*, rows: int, seed: int, shifted: bool = False) -> Iterator[dict]:
    rng = np.random.default_rng(seed)
    signs = np.arange(rows) % 2
    rng.shuffle(signs)
    magnitudes = rng.uniform(0.2, 1.0, rows)
    hints = rng.integers(0, len(LABELS), rows)
    for index, (sign, magnitude, hint) in enumerate(zip(signs, magnitudes, hints, strict=True)):
        offset = 2 if shifted and index % 2 else 0
        yield {"x": float((2 * sign - 1) * magnitude), "hint": LABELS[hint], "label": LABELS[sign + offset]}


def build() -> rf.Model:
    return rf.Model.xs(
        batch_size=64,
        x=rf.Number,
        hint=rf.Category(p_unavailable=0.0),
        label=rf.Category(p_unavailable=0.0, topk=[2], mask=True),
    )


def run(seed: int, steps: int | None, accelerator: str) -> tuple[dict, dict]:
    lit.seed_everything(seed, workers=True)
    model = build()
    model.optimizer = rf.adamw(learning_rate=0.002)
    data = rf.SyntheticDataModule(
        model=model,
        train=partial(records, rows=4096, seed=seed + 1),
        validate=partial(records, rows=512, 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),
        deterministic=True,
        logger=False,
        enable_checkpointing=False,
        enable_progress_bar=False,
        enable_model_summary=False,
        num_sanity_val_steps=0,
    )
    trainer.fit(model, datamodule=data)
    model.eval()
    vocabulary = rf.Category.vocabulary(model, "/label")
    hints = rf.Category.vocabulary(model, "/hint")
    counts = rf.Category.counts(model, "/label")
    normalization = rf.Number.normalization(model, "/x")
    test = list(records(rows=2048, seed=seed + 3, shifted=True))
    source = pa.Table.from_pylist(test)
    output = model.predict(source)["predictions"].to_pylist()
    content = [row["label"]["content"] for row in output]
    actual = np.asarray([row["label"] for row in test])
    predicted = np.asarray([row["value"] for row in content])
    known = np.isin(actual, vocabulary)
    probability = np.asarray([row["probability"] for row in content])
    evaluated = trainer.test(
        model,
        datamodule=rf.SyntheticDataModule(model=model, test=partial(records, rows=2048, seed=seed + 3, shifted=True)),
        verbose=False,
    )[0]
    builtin = float(evaluated[".label/test.accuracy.content"])
    order = np.random.default_rng(seed + 4).permutation(len(test))
    corrupted = [{**row, "x": test[index]["x"]} for row, index in zip(test, order, strict=True)]
    broken = model.predict(pa.Table.from_pylist(corrupted))["predictions"].to_pylist()
    broken_values = np.asarray([row["label"]["content"]["value"] for row in broken])
    field = model.encode(source, strata="test")["/label"]
    metrics = {
        "steps": trainer.global_step,
        "output_vocabulary": vocabulary,
        "hint_vocabulary": hints,
        "coverage": float(known.mean()),
        "builtin_content_accuracy": builtin,
        "known_accuracy": float((predicted[known] == actual[known]).mean()),
        "all_row_accuracy": float((predicted == actual).mean()),
        "unknown_accuracy": float((predicted[~known] == actual[~known]).mean()),
        "unknown_mean_confidence": float(probability[~known].mean()),
        "shuffled_known_accuracy": float((broken_values[known] == actual[known]).mean()),
        "majority_baseline": float(max(np.mean(actual == label) for label in vocabulary)),
    }
    candidate_labels = {candidate["value"] for row in content for candidate in row["topk"]}
    return metrics, {
        "Known-only content accuracy reaches 0.95": builtin >= 0.95 and metrics["known_accuracy"] >= 0.95,
        "Built-in accuracy agrees with known-only external scoring": abs(builtin - metrics["known_accuracy"]) < 0.02,
        "Coverage is exactly one half": metrics["coverage"] == 0.5,
        "All-row accuracy exposes the coverage ceiling": metrics["all_row_accuracy"] <= 0.5,
        "Unseen output labels cannot be emitted": metrics["unknown_accuracy"] == 0.0,
        "Unknown answers still receive confident known-label predictions": metrics["unknown_mean_confidence"] > 0.8,
        "Shuffling x removes known-label accuracy": 0.40 < metrics["shuffled_known_accuracy"] < 0.60,
        "Predictions and candidates use populated output labels only": set(predicted) <= set(vocabulary)
        and candidate_labels == set(vocabulary),
        "Candidate probabilities normalize over populated labels": all(
            abs(sum(candidate["probability"] for candidate in row["topk"]) - 1.0) < 1e-5 for row in content
        ),
        "Labels known in another field remain unknown here": set(LABELS[2:]) <= set(hints)
        and not set(LABELS[2:]) & set(vocabulary),
        "Unknown target content uses the unavailable sentinel": bool(
            (field.targets[rf.TensorKey.content].reshape(-1).cpu().numpy()[~known] == -1).all()
        ),
        "Evaluation preserves output vocabulary and exposure counts": rf.Category.vocabulary(model, "/label")
        == vocabulary
        and rf.Category.counts(model, "/label") == counts,
        "Evaluation preserves hint vocabulary and numerical moments": rf.Category.vocabulary(model, "/hint") == hints
        and rf.Number.normalization(model, "/x") == normalization,
    }


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