01 The failure
A reward of 1 or 0 teaches a model to lie with confidence
Reward the model only for being right and its best move is to put all the mass on the most likely option. Accuracy goes up, calibration falls apart. A scoring rule fixes this: its expected value is highest when the reported odds are the true odds.
- log score
- 0.000
- spherical
- 0.000
- binary reward
- 0.000
02 The contract
One mechanism, three hats
Options are written at request time and read as text, so a new task is a new question, not a new output layer. noul is a choice with two fixed options; score is a choice over ordinal levels.
03 The sequence
One sequence, one marker per option
The model reads the options as text. A mask marker in front of each option is where its score is read from, and because the encoder is bidirectional, that position already sees the option, its rivals, the instruction and the state.
- instruction
- option text
- markers and separators
- state (remainder)
04 The loop
Sample the logits, reward the candidates, step
The policy perturbs its own logits instead of generating tokens. Perturbations that scored above the group baseline pull the logits toward themselves. This is the real update rule, running here on three options.
- mean reward
- n/a
- advantage spread
- n/a
- P(option B)
- 0.333
05 Do it
From a labeled file to a portable checkpoint
Train the direct baseline first, then the policy version, and keep the policy version only if it wins on held-out ECE or NLL. Calibrate on held-out data and ship the temperature map inside the checkpoint.
uv sync --extra dev
exu-train \
--mode rlcd \
--train data.jsonl --train-split train \
--validation data.jsonl --validation-split validation \
--calibration data.jsonl --calibration-split calibration \
--output artifacts/my-model \
--encoder google-bert/bert-base-multilingual-cased \
--epochs 4 --batch-size 8 \
--option-shuffle --calibrate
exu-evaluate \
--checkpoint artifacts/my-model \
--data data.jsonl --split test \
--order-permutations 4 --latency
# then, in Python
from exu import DecisionRuntime
runtime = DecisionRuntime.load("artifacts/my-model")
decision = runtime.decide(state, question)
print(decision.label, decision.confidence)
- Sequence builder tested: truncation, many options, marker out of range, injected mask token.
- Encoder chosen after measuring tokenizer fertility on your own text.
- Held-out split by whole task family, never by example.
- Direct baseline beats the per-question prior with margin.
- The RLCD policy beats the direct baseline on held-out ECE or NLL, otherwise it does not ship.
- Temperatures fitted on held-out data, no value sitting on a bound.
- Order robustness and selective coverage reported beside accuracy.