How confident should it be about a new identity?

Vocabulary
Predict a yes/no outcome using a numeric signal and an identity-specific effect. New identities remove only the latter information.

Example

x: 0.0
entity: id-42
label: yes

The outcome is hidden and randomly sampled. Scores compare predicted probabilities with the generator’s known probabilities.

Model tree with record, x, entity, label. Amber cards are hidden prediction targets. The numeric signal remains available for unfamiliar identities.

Model tree with record, x, entity, label. Amber cards are hidden prediction targets. The numeric signal remains available for unfamiliar identities.

Figure 1: Amber cards are hidden prediction targets. The numeric signal remains available for unfamiliar identities.

Comparison

Compare training with 1% or 20% of input identities made unavailable. Score new identities separately where the numeric signal is weak or strong.

Result

P061 · Latest full run · Limited

Seed 7611 on gpu: 9 of 9 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
Weak signal, new identity, 20% training rate 59.7%
Strong signal, new identity, 20% training rate 98.3%

A new identity does not always imply a 50% prediction: strong remaining context can still justify confidence. Neither unavailability rate is claimed to be best for every dataset.

Experiment details and code

Latest full run

Seed 7611, gpu, recorded 2026-09-18T16:54:22.853610+00:00. Outcome: met.

Source fingerprint: b503d7bd329225ff02a7d14b9f92bad32401516565e585de2ec3a07c02ace54f. Python 3.12.6; Torch 2.12.0.

Measurement Value
0.01 known: oracle_rmse: 0.245414; constant_rmse: 0.413071; nll: 0.42754; brier: 0.137825; accuracy: 0.800781; ambiguous_confidence: 0.676661; decisive_confidence: 0.978708; novel: oracle_rmse: 0.0726676; constant_rmse: 0.325791; nll: 0.450894; brier: 0.14659; accuracy: 0.778198; ambiguous_confidence: 0.58935; decisive_confidence: 0.983949; renaming_drift: 0; admission_mean_drift: 0.072571
0.2 known: oracle_rmse: 0.2491; constant_rmse: 0.413071; nll: 0.432154; brier: 0.13994; accuracy: 0.797119; ambiguous_confidence: 0.651688; decisive_confidence: 0.977733; novel: oracle_rmse: 0.0688003; constant_rmse: 0.325791; nll: 0.448407; brier: 0.146224; accuracy: 0.778198; ambiguous_confidence: 0.596963; decisive_confidence: 0.982566; renaming_drift: 0; admission_mean_drift: 0.060714
Behavioral checks
Behavioral check Outcome
0.01: evaluation preserves mapping and exposures Met
0.01: unseen spelling is uninformative Met
0.2: evaluation preserves mapping and exposures Met
0.2: unseen spelling is uninformative Met
Known-ID probability RMSE below 0.30 Met
Unknown-ID probability RMSE below 0.10 Met
Unknown-ID predictions beat the constant baseline Met
Ambiguous unknown IDs retain confidence below 0.70 Met
Decisive context retains confidence above 0.90 Met

Full run history

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

Seed Outcome Recorded Code fingerprint
7611 met 2026-09-16T23:59:12.806330+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-16T23:59:21.397716+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7701 met 2026-09-17T00:00:24.519372+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7702 met 2026-09-17T00:01:20.038997+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-17T01:03:07.522073+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-18T16:43:00.329018+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-18T16:43:21.923050+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-18T16:46:46.366225+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-18T16:49:31.412222+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1
7611 met 2026-09-18T16:54:22.853610+00:00 0ada20433a1786614ffebbe57375fb067623738774029068db6f1d779be888f1

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 P061

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

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

Use 32,768 training rows over 4,096 identities, automatically growing storage, and 600 updates. Independent validation has 1,024 rows. Evaluation has 8,192 known-ID rows and 8,192 entity-disjoint rows, with identical visible x and known yes/no output labels. The latent sign is only available to the evaluator.

The 20% arm must beat the constant baseline, retain decisive confidence above 0.9, and keep ambiguous new-ID confidence below 0.7. Record both arms without demanding that one stochastic run of stronger masking always beats 1%. A final admission-only probe measures the prediction change without learning; this is a diagnostic limitation, not a promise that admission is safe.

Complete experiment code

Code
"""P061: confidence with sparse known IDs, unknown IDs, and decisive context."""

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 = "P061"
BUDGET = 600
ENTITIES = 4096


def records(*, rows: int, seed: int, novel: bool = False) -> Iterator[dict]:
    rng = np.random.default_rng(seed)
    identities = rng.integers(0, ENTITIES, rows)
    effects = np.random.default_rng(918).choice([-2.5, 2.5], ENTITIES)
    values = rng.uniform(-3, 3, rows)
    probabilities = 1 / (1 + np.exp(-(2 * values + effects[identities])))
    labels = rng.random(rows) < probabilities
    for x, identity, label in zip(values, identities, labels, strict=True):
        yield {"x": float(x), "entity": f"{'new' if novel else 'id'}-{identity}", "label": "yes" if label else "no"}


def build(unavailable: float) -> rf.Model:
    model = rf.Model(
        d_model=32,
        n_layers=1,
        n_heads=4,
        dropout=0.0,
        batch_size=128,
        x=rf.Number,
        entity=rf.Category(p_unavailable=unavailable),
        label=rf.Category(mask=True),
    )
    model.optimizer = rf.adamw(learning_rate=0.002)
    return model


def probability(model: rf.Model, rows: list[dict]) -> np.ndarray:
    predictions = model.predict(pa.Table.from_pylist(rows))["predictions"].to_pylist()
    content = [row["label"]["content"] for row in predictions]
    return np.asarray([item["probability"] if item["value"] == "yes" else 1 - item["probability"] for item in content])


def score(p: np.ndarray, rows: list[dict], novel: bool) -> dict[str, float]:
    x = np.asarray([row["x"] for row in rows])
    if novel:
        oracle = (1 / (1 + np.exp(-(2 * x - 2.5))) + 1 / (1 + np.exp(-(2 * x + 2.5)))) / 2
    else:
        effects = np.random.default_rng(918).choice([-2.5, 2.5], ENTITIES)
        identity = np.asarray([int(row["entity"].split("-")[1]) for row in rows])
        oracle = 1 / (1 + np.exp(-(2 * x + effects[identity])))
    y = np.asarray([row["label"] == "yes" for row in rows], dtype=float)
    clipped = p.clip(1e-7, 1 - 1e-7)
    confidence = np.maximum(p, 1 - p)
    return {
        "oracle_rmse": float(np.sqrt(np.mean((p - oracle) ** 2))),
        "constant_rmse": float(np.sqrt(np.mean((0.5 - oracle) ** 2))),
        "nll": float(np.mean(-y * np.log(clipped) - (1 - y) * np.log1p(-clipped))),
        "brier": float(np.mean((p - y) ** 2)),
        "accuracy": float(np.mean((p > 0.5) == y)),
        "ambiguous_confidence": float(confidence[np.abs(x) < 0.5].mean()),
        "decisive_confidence": float(confidence[np.abs(x) > 2.5].mean()),
    }


def run(seed: int, steps: int | None, accelerator: str) -> tuple[dict, dict]:
    metrics, checks = {}, {}
    for unavailable in (0.01, 0.2):
        lit.seed_everything(seed, workers=True)
        model = build(unavailable)
        data = rf.SyntheticDataModule(
            model=model,
            train=partial(records, rows=32768, 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)
        model.eval()
        vocabulary = rf.Category.vocabulary(model, "/entity")
        counts = rf.Category.counts(model, "/entity")
        arm = {}
        for novel in (False, True):
            rows = list(records(rows=8192, seed=seed + 3, novel=novel))
            arm["novel" if novel else "known"] = score(probability(model, rows), rows, novel)
        rows = list(records(rows=2048, seed=seed + 4, novel=True))
        before = probability(model, rows)
        renamed = [{**row, "entity": "another-unseen-identity"} for row in rows]
        arm["renaming_drift"] = float(np.max(np.abs(before - probability(model, renamed))))
        checks[f"{unavailable}: evaluation preserves mapping and exposures"] = vocabulary == rf.Category.vocabulary(
            model, "/entity"
        ) and counts == rf.Category.counts(model, "/entity")
        checks[f"{unavailable}: unseen spelling is uninformative"] = arm["renaming_drift"] < 1e-5
        model.encode(pa.Table.from_pylist(rows), strata="train")
        arm["admission_mean_drift"] = float(np.mean(np.abs(before - probability(model, rows))))
        metrics[str(unavailable)] = arm
        if unavailable == 0.2:
            checks.update(
                {
                    "Known-ID probability RMSE below 0.30": arm["known"]["oracle_rmse"] < 0.30,
                    "Unknown-ID probability RMSE below 0.10": arm["novel"]["oracle_rmse"] < 0.10,
                    "Unknown-ID predictions beat the constant baseline": arm["novel"]["oracle_rmse"]
                    < arm["novel"]["constant_rmse"],
                    "Ambiguous unknown IDs retain confidence below 0.70": arm["novel"]["ambiguous_confidence"] < 0.70,
                    "Decisive context retains confidence above 0.90": arm["novel"]["decisive_confidence"] > 0.90,
                }
            )
    return metrics, checks


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