Does declaring a class teach its meaning?

Enum
Declare all four classes, but leave one out of initial training.

Example

x: -0.94
label: blue

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

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

Model tree with record, x, 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

Measure the withheld class before and after continued fitting that includes it. Continue supplying old classes to check retention.

Result

P070 · Latest full run · Passing

Seed 7010 on cpu: 16 of 16 test checks met. History: 11 of 11 full runs met every check, across 10 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
Withheld class, before examples 0.0%
Withheld class, after examples 100.0%
Old classes, after continuation 100.0%

A declared output slot alone supplies no reliable understanding. Examples teach the withheld class; the old classes are retained with rehearsal.

Experiment details and code

Latest full run

Seed 7010, cpu, recorded 2026-09-21T21:09:51.602268+00:00. Outcome: met.

Source fingerprint: 77fc993db9b6c7f755572d868cfb9c074ac4899d1dda5275bfe317ba2f16e618. Python 3.12.10; Torch 2.12.0.

Measurement Value
train_rows_per_stage 4096
validation_rows_per_stage 512
test_rows_per_population 2048
initial_steps 300
continuation_steps 300
control_steps 300
uniform_four_class_accuracy 0.25
initial_seen_majority_accuracy 0.333496
cold_withheld accuracy: 1; mean_withheld_probability: 0.590794; full_declared_support: True; probability_sum_error: 1.3411e-07
initial_seen accuracy: 1; mean_withheld_probability: 1.27609e-06; full_declared_support: True; probability_sum_error: 1.14882e-07
initial_withheld accuracy: 0; mean_withheld_probability: 1.42602e-06; full_declared_support: True; probability_sum_error: 1.02507e-07
final_seen accuracy: 1; mean_withheld_probability: 9.5203e-07; full_declared_support: True; probability_sum_error: 1.16885e-07
final_withheld accuracy: 1; mean_withheld_probability: 0.999986; full_declared_support: True; probability_sum_error: 1.17293e-07
control_seen accuracy: 1; mean_withheld_probability: 3.7156e-06; full_declared_support: True; probability_sum_error: 1.16334e-07
control_withheld accuracy: 1; mean_withheld_probability: 0.999989; full_declared_support: True; probability_sum_error: 1.17632e-07
initial_counts amber: 6406; blue: 6379; coral: 6415; green: 0
final_counts amber: 11218; blue: 11178; coral: 11220; green: 4784
storage labels: amber, blue, coral, green; embedding_rows: 4; count_rows: 4; decoder_rows: 4
Behavioral checks
Behavioral check Outcome
Cold prediction exposes every declared candidate Met
Cold prediction leaves exposure counts at zero Met
Initial training reaches 0.95 seen-class accuracy Met
Withheld class has zero initial training exposure Met
Initial training retains the complete fixed declaration Met
Prediction of withheld records cannot teach the class Met
Untrained class retains output support Met
Continuation reaches 0.95 withheld-class accuracy Met
Rehearsal retains 0.95 seen-class accuracy Met
Continuation adds exposure to every declared class Met
Continuation leaves class IDs and all row counts unchanged Met
Final evaluation preserves counts Met
Checkpoint preserves fixed class storage and exposures Met
Checkpoint preserves withheld predictions Met
All-classes-trained control reaches 0.95 on both populations Met
Written class probabilities sum to one Met

Full run history

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

Seed Outcome Recorded Code fingerprint
7001 met 2026-09-21T21:04:52.327553+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7002 met 2026-09-21T21:05:05.224856+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7003 met 2026-09-21T21:05:19.741703+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7001 met 2026-09-21T21:05:54.313968+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7004 met 2026-09-21T21:08:34.494300+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7005 met 2026-09-21T21:08:47.178325+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7006 met 2026-09-21T21:08:59.955005+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7007 met 2026-09-21T21:09:12.521141+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7008 met 2026-09-21T21:09:25.151528+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7009 met 2026-09-21T21:09:37.812465+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca
7010 met 2026-09-21T21:09:51.602268+00:00 f72084ca9c6c8770e78985ec2bd1bcc7bd2442c99b1f7b55aa6e1bae21cbafca

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 P070

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

PYTHONPATH=proofs python proofs/enum/declared_without_examples.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 observability

A class index selects one of four separated numerical centers, and uniform noise in [-0.15, 0.15] prevents exact repeated numerical observations. The target is that region’s opaque label. The initial stage includes the first three classes; continuation rehearses them equally alongside the fourth. Each phase uses 4,096 training and 512 validation rows with independent seeds. Seen-class and withheld-class tests each contain 2,048 fresh observations. Rows are the split unit. The finite, separated regions make the Bayes error zero; no irreducible target noise is added to this controlled omission task.

Continued fitting also rehearses this familiar region. Accuracy on both familiar and withheld regions guards against simply always predicting green.

Training and controls

The xs model receives 300 initial updates and 300 continuation updates, with a fresh AdamW optimizer at learning rate 0.002 for each fitting stage, batch size 64, one device, and no loader workers. A paired model starts from the same seed and sees all four classes during its own 300-update phase. This control checks that the withheld region is learnable within one stage. It matches the phase budget, rather than the cumulative 600-update budget. No checkpoint or hyperparameter is selected using the test sets.

Uniform guessing across the four-class head scores 0.25 on either population; the three-class initial task also has a 1/3 majority baseline. A constant prediction of the withheld label would score 1.0 on its isolated population and 0.0 on the seen population, so both accuracies are essential gates.

Predeclared gates require at least 0.95 initial seen accuracy, at least 0.95 on both populations after continuation, and at least 0.95 for both populations in the all-classes-trained control. The initial withheld exposure must be zero; continuation must make it positive. Head size, count-buffer size, and declaration order remain fixed. Pre-continuation withheld accuracy has no acceptance gate.

Complete experiment code

Code
"""P070: separate a declared Enum class from learning its relationship to context."""

from collections.abc import Iterator
from functools import partial
from pathlib import Path
from tempfile import TemporaryDirectory

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

import relflow as rf

PROOF_ID = "P070"
BUDGET = 300
LABELS = ("amber", "blue", "coral", "green")
CENTERS = (-3.0, -1.0, 1.0, 3.0)


def records(*, rows: int, seed: int, phase: str = "initial") -> Iterator[dict]:
    rng = np.random.default_rng(seed)
    indices = {"initial": (0, 1, 2), "withheld": (3,), "mixed": (0, 1, 2, 3)}[phase]
    classes = np.resize(np.asarray(indices), rows)
    rng.shuffle(classes)
    noise = rng.uniform(-0.15, 0.15, rows)
    for index, deviation in zip(classes, noise, strict=True):
        yield {"x": float(CENTERS[index] + deviation), "label": LABELS[index]}


def build() -> rf.Model:
    return rf.Model.xs(
        batch_size=64,
        x=rf.Number,
        label=rf.Enum(values=LABELS, p_unavailable=0.0, topk=[4], mask=True),
    )


def storage(model: rf.Model) -> dict:
    """Read the fixed mapping and row counts independently from exposure values."""
    node = model.nodes[rf.Address("label")]
    return {
        "labels": rf.Enum.vocabulary(model, rf.Address("label")),
        "embedding_rows": node.embedder.embeddings["content"].num_embeddings,
        "count_rows": node.embedder.counters["content"].counts.numel(),
        "decoder_rows": node.decoder.linears["content"].out_features,
    }


def fit(model: rf.Model, phase: str, seed: int, budget: int, accelerator: str) -> int:
    model.optimizer = rf.adamw(learning_rate=0.002)
    data = rf.SyntheticDataModule(
        model=model,
        train=partial(records, rows=4096, seed=seed + 1, phase=phase),
        validate=partial(records, rows=512, seed=seed + 2, phase=phase),
        seed=seed,
    )
    trainer = lit.Trainer(
        accelerator=accelerator,
        devices=1,
        max_epochs=-1,
        max_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()
    return trainer.global_step


def prediction(model: rf.Model, rows: list[dict]) -> list[dict]:
    output = model.predict(pa.Table.from_pylist(rows))["predictions"].to_pylist()
    return [row["label"]["content"] for row in output]


def measure(output: list[dict], rows: list[dict]) -> dict:
    probabilities = [{candidate["value"]: candidate["probability"] for candidate in row["topk"]} for row in output]
    return {
        "accuracy": float(np.mean([value["value"] == row["label"] for value, row in zip(output, rows, strict=True)])),
        "mean_withheld_probability": float(np.mean([row[LABELS[-1]] for row in probabilities])),
        "full_declared_support": all(set(row) == set(LABELS) for row in probabilities),
        "probability_sum_error": float(max(abs(sum(row.values()) - 1.0) for row in probabilities)),
    }


def run(seed: int, steps: int | None, accelerator: str) -> tuple[dict, dict]:
    lit.seed_everything(seed, workers=True)
    budget = BUDGET if steps is None else min(steps, BUDGET)
    model = build()
    initial_storage = storage(model)
    seen = list(records(rows=2048, seed=seed + 3))
    withheld = list(records(rows=2048, seed=seed + 4, phase="withheld"))
    cold = measure(prediction(model, withheld), withheld)
    checks = {
        "Cold prediction exposes every declared candidate": cold["full_declared_support"],
        "Cold prediction leaves exposure counts at zero": all(
            value == 0 for value in rf.Enum.counts(model, "/label").values()
        ),
    }
    initial_steps = fit(model, "initial", seed, budget, accelerator)
    initial_counts = rf.Enum.counts(model, "/label")
    initial_seen = measure(prediction(model, seen), seen)
    initial_withheld = measure(prediction(model, withheld), withheld)
    checks["Initial training reaches 0.95 seen-class accuracy"] = initial_seen["accuracy"] >= 0.95
    checks["Withheld class has zero initial training exposure"] = initial_counts[LABELS[-1]] == 0
    checks["Initial training retains the complete fixed declaration"] = storage(model) == initial_storage
    checks["Prediction of withheld records cannot teach the class"] = rf.Enum.counts(model, "/label") == initial_counts
    checks["Untrained class retains output support"] = initial_withheld["full_declared_support"]
    continuation_steps = fit(model, "mixed", seed + 20, budget, accelerator)
    final_counts = rf.Enum.counts(model, "/label")
    final_seen = measure(prediction(model, seen), seen)
    final_output = prediction(model, withheld)
    final_withheld = measure(final_output, withheld)
    checks["Continuation reaches 0.95 withheld-class accuracy"] = final_withheld["accuracy"] >= 0.95
    checks["Rehearsal retains 0.95 seen-class accuracy"] = final_seen["accuracy"] >= 0.95
    checks["Continuation adds exposure to every declared class"] = all(
        final_counts[label] > initial_counts[label] for label in LABELS
    )
    checks["Continuation leaves class IDs and all row counts unchanged"] = storage(model) == initial_storage
    checks["Final evaluation preserves counts"] = rf.Enum.counts(model, "/label") == final_counts
    with TemporaryDirectory(prefix="relflow-enum-continuation-") as directory:
        path = Path(directory) / "continued.pt"
        model.save(path)
        restored = rf.Model.load(path).to(model.device)
        restored.eval()
        restored_output = prediction(restored, withheld)
        checks["Checkpoint preserves fixed class storage and exposures"] = (
            storage(restored) == initial_storage and rf.Enum.counts(restored, "/label") == final_counts
        )
        checks["Checkpoint preserves withheld predictions"] = all(
            before["value"] == after["value"] and abs(before["probability"] - after["probability"]) < 1e-5
            for before, after in zip(final_output, restored_output, strict=True)
        )
    lit.seed_everything(seed, workers=True)
    control = build()
    control_steps = fit(control, "mixed", seed, budget, accelerator)
    control_seen = measure(prediction(control, seen), seen)
    control_withheld = measure(prediction(control, withheld), withheld)
    checks["All-classes-trained control reaches 0.95 on both populations"] = (
        min(control_seen["accuracy"], control_withheld["accuracy"]) >= 0.95
    )
    checks["Written class probabilities sum to one"] = (
        max(
            value["probability_sum_error"]
            for value in (
                cold,
                initial_seen,
                initial_withheld,
                final_seen,
                final_withheld,
                control_seen,
                control_withheld,
            )
        )
        < 1e-5
    )
    return {
        "train_rows_per_stage": 4096,
        "validation_rows_per_stage": 512,
        "test_rows_per_population": 2048,
        "initial_steps": initial_steps,
        "continuation_steps": continuation_steps,
        "control_steps": control_steps,
        "uniform_four_class_accuracy": 0.25,
        "initial_seen_majority_accuracy": 683 / 2048,
        "cold_withheld": cold,
        "initial_seen": initial_seen,
        "initial_withheld": initial_withheld,
        "final_seen": final_seen,
        "final_withheld": final_withheld,
        "control_seen": control_seen,
        "control_withheld": control_withheld,
        "initial_counts": initial_counts,
        "final_counts": final_counts,
        "storage": storage(model),
    }, checks


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