Soft rules: annealing, the hardening gap, and purification¶
This page solves space quantization (fit_quantizer) through
Door 1, a precomputed table of (observation, score) events. It is
about the one solver in the library that does not optimize the hard objective at all:
SoftVoronoiConfig, which optimizes a randomized rule with gradient descent and then
hardens it.
Three questions follow from that, and this page measures all three rather than asserting them. What does the annealing schedule actually do to the rule you deploy? What does the final hardening step cost? And is a randomized rule ever better than a deterministic one — that is, has the relaxation quietly enlarged the problem?

Problem¶
On a finite sample the hard objective is piecewise constant in a rule's parameters: nudge a cell boundary and, until some training score crosses it, every label and every cell moment is exactly what it was. The gradient is zero almost everywhere and undefined on the crossing surfaces, so gradient descent on the hard empirical objective is not an algorithm.
The way out is to change the rule rather than smooth the objective. A randomized quantizer assigns each score a distribution over cells, \(r_b(s)\ge0\) with \(\sum_b r_b(s)=1\); the label is drawn from it. That is a legitimate decision rule with an ordinary label law, so it has an ordinary Fisher information,
and fractional_fisher_information computes exactly that. SoftVoronoiConfig fits the
common-metric family \(r_b(s)=\operatorname{softmax}_b(-\lVert s-c_b\rVert^2/2\tau^2)\) in
Fisher-whitened coordinates, whose free parameters are the centers alone and whose
\(\tau\to0\) limit is the Voronoi rule those centers define. The full derivation is
Chapter 12; this page is what it looks like when measured.
Data and preprocessing¶
Four problems, so that no claim below rests on one table: a one-column Gaussian location law with a uniform reference measure, two two-column component-score laws with strongly nonuniform weights, and the three-column signal-plus-two-backgrounds problem the nuisance-profiled-ds page measures. No preprocessing: scores and weights go in unchanged, and are never centered.
import numpy as np
import scorequant as sq
from examples.synthetic_problems import signal_background_shape
problem = signal_background_shape(sizes=(900, 300, 1500), seed=50)
train = problem.train
n_bins = 6
source = sq.ScoreSample(train.scores, train.weights)
assert train.scores.shape[1] == 3
API walkthrough¶
One annealed fit, and what its trace records¶
The library initializes the centers with weighted k-means, sets the starting temperature to
the median nearest-center separation, and anneals geometrically to
temperature_end_ratio times that over max_steps Adam updates.
diagnostics="full" is the switch that makes this page possible. By default only the first
and last recorded center snapshots are re-scored with a full information report, because
each one costs an \(O(N)\) pass. Asking for every snapshot is what lets the soft objective
and the hard rule be plotted against the same schedule.
rule = sq.fit_quantizer(
source,
n_bins=n_bins,
criterion=sq.DOptimality(),
config=sq.SoftVoronoiConfig(
seed=3, initializer_restarts=4, max_steps=120, record_every=20, temperature_end_ratio=0.02
),
diagnostics="full",
)
trace = rule.trace
temperatures = np.asarray(trace.temperatures)
soft = np.asarray(trace.soft_retention)
hard = np.asarray(trace.train_hard_retention)
assert trace.objective_label == "logdet_retained"
assert np.all(np.diff(temperatures) < 0)
assert soft[-1] > soft[0]
assert np.all(np.isfinite(hard))
Four histories come back and they measure different things. trace.objective is the soft
criterion value being maximized, trace.soft_retention is that value normalized into a
retention, trace.temperatures is the schedule, and trace.gradient_norms is the norm of
the center gradient at each recorded step. trace.train_hard_retention is the only one of
the five that describes the rule you will actually deploy.
That distinction is not pedantic. The soft curve climbs enormously over the schedule while the hard curve barely moves, because at a high temperature the responsibilities are deliberately diffuse and every cell mean is pulled toward the global mean.
The hardening gap¶
A soft fit is never the deliverable: predict_scores assigns each score to its nearest
center, which is the \(\tau\to0\) limit. The number that matters is the retention of that
rule, and QuantizerResult.hardening_gap reports the difference,
Cooling further closes it, which is what a temperature schedule is for.
gaps = []
for ratio in (0.5, 0.2, 0.05):
annealed = sq.fit_quantizer(
source,
n_bins=n_bins,
criterion=sq.DOptimality(),
config=sq.SoftVoronoiConfig(
seed=3,
initializer_restarts=4,
max_steps=120,
record_every=120,
temperature_end_ratio=ratio,
),
)
gaps.append(float(annealed.hardening_gap))
assert all(gap < 0.0 for gap in gaps)
assert all(abs(later) < abs(earlier) for earlier, later in zip(gaps, gaps[1:]))
Every gap here is negative: the hard rule retains more than the randomized rule that produced it. That is not a guarantee and the library does not treat it as one — it reports the number precisely because neither sign is forced.
Purification, measured¶
The negative sign above is a hint at something classical. If randomization could beat determinism, the soft relaxation would not be a computational device but a genuinely larger class of answers. For a population score law with no atoms it cannot: the Dvoretzky–Wald–Wolfowitz elimination-of-randomization theorem says every randomized \(K\)-action rule can be replaced by a deterministic one reproducing all \((W_b,m_b)\) exactly, and every criterion in this library depends on a rule only through those moments.
That theorem is an existence statement about an atomless law, and a finite sample is atomic by construction, so it does not cover the operation the library performs. What can be done is to measure the operation. Build genuinely randomized rules at several temperatures around the fitted centers, and compare each one against the deterministic rule obtained by taking every row's most probable cell.
The randomized side uses fractional_fisher_information, which is public and validates
that the rows really are probability vectors. Turning it into the same D-efficiency number
information_report publishes is one line of algebra, and the way to trust that line is to
check it against a one-hot responsibility matrix, where both must agree exactly.
from examples.soft_purification import (
center_separation,
fractional_retention,
hard_retention,
softmax_responsibilities,
)
labels = np.asarray(rule.predict_scores(train.scores))
one_hot = np.eye(n_bins)[labels]
assert (
abs(
fractional_retention(train.scores, one_hot, train.weights)
- hard_retention(train.scores, labels, train.weights, n_bins)
)
< 1e-12
)
With the helper grounded, the probe itself is short.
coordinates = np.asarray(rule.transform.apply(train.scores))
centers = np.asarray(rule.centers)
separation = center_separation(centers)
gains = []
for ratio in (1.0, 0.25):
responsibilities = softmax_responsibilities(coordinates, centers, ratio * separation)
randomized = fractional_retention(train.scores, responsibilities, train.weights)
purified = hard_retention(
train.scores, np.argmax(responsibilities, axis=1), train.weights, n_bins
)
gains.append(purified - randomized)
assert all(gain > 0.0 for gain in gains)
assert gains[0] > gains[1]
Hardening never cost information anywhere in this study, and at a temperature equal to the median center separation it gained a great deal. Read that as evidence consistent with purification, not as a proof of it on a finite table.
Against exact exchange¶
The soft path is one of several solvers for the same criterion, so the last question is simply whether it wins. Exact positive-gain exchange optimizes the hard objective directly; it can also be started from the soft fit's own labels.
exchange = sq.optimize_partition(
train.scores, weights=train.weights, n_bins=n_bins, config=sq.DExchangeConfig(seed=3)
)
from_soft = sq.optimize_partition(
train.scores,
weights=train.weights,
n_bins=n_bins,
config=sq.DExchangeConfig(seed=3),
initial_labels=labels,
)
soft_retention = float(rule.train_report.geometric_mean_retention)
assert exchange.train_report.geometric_mean_retention >= soft_retention - 1e-9
assert from_soft.train_report.geometric_mean_retention >= soft_retention - 1e-9
Analysis¶
Full-study numbers on 4000 training events, regenerated by
examples/soft_purification.py into
assets/soft-purification.json.
The schedule moves the soft objective, not the rule¶
The traced fit is the signal-plus-backgrounds problem at six cells, 300 Adam steps, cooling to one fiftieth of the starting temperature.
| Quantity | At the first recorded step | At the last |
|---|---|---|
| Randomized rule (soft retention) | 0.70076 | 0.97552 |
| Hard rule the same centers imply | 0.97557 | 0.97552 |
The soft objective climbs 27 D-efficiency points. The rule that will be deployed falls by
0.000046 — three hundred annealed gradient steps ended fractionally below the weighted
k-means labeling they started from. Nothing is wrong: the optimizer maximized what it was
given, which was the randomized objective at a nonzero temperature, and only in the
\(\tau\to0\) limit are the two the same function. But it is a clean demonstration of why
train_hard_retention exists and why watching trace.objective climb is not evidence of
anything you can ship.
The hardening gap across problems and temperatures¶
The gap, as a function of the final temperature, on all four problems:
| \(\tau_{\text{end}}/\tau_0\) | Gaussian location | Spectral templates | Gaussian mixture | Signal + backgrounds |
|---|---|---|---|---|
| 0.8 | -9.2e-02 | -1.0e-02 | -1.3e-02 | -2.1e-02 |
| 0.4 | -3.5e-02 | -1.8e-03 | -2.2e-03 | -2.7e-03 |
| 0.2 | -4.3e-03 | -1.6e-04 | -1.4e-04 | -2.3e-04 |
| 0.05 | -2.9e-05 | -6.2e-07 | -4.9e-07 | -3.4e-06 |
| 0.01 | -1.4e-08 | -4.2e-09 | -1.2e-12 | +8.1e-15 |
Every entry above the level of floating-point noise is negative, so on these four problems
hardening never lost information and usually gained a little. The gap closes by six or more
orders of magnitude as the final temperature falls by a factor of eighty, which is a strong
argument for the library's default temperature_end_ratio=0.05 and against reading
anything into a fit that stopped warm. The single positive entry, \(+8\times10^{-15}\) at
the coldest schedule, is a rounding difference between two nearly identical numbers; its
sign carries no information.
Purification: what randomization costs¶
D-efficiency of a randomized rule against the deterministic rule that hardens it, at the same centers, with the softmax temperature written as a multiple of the median nearest-center separation:
| \(\tau\) / separation | Randomized | Purified | Gain |
|---|---|---|---|
| 1.00 | 0.33641 | 0.97552 | 0.639 |
| 0.50 | 0.86238 | 0.97552 | 0.113 |
| 0.25 | 0.96805 | 0.97552 | 0.00747 |
| 0.10 | 0.97535 | 0.97552 | 0.000174 |
| 0.05 | 0.97550 | 0.97552 | 0.0000204 |
Those are the signal-plus-backgrounds rows; the other three problems behave the same way, with gains that are positive at every temperature and every problem, ranging from 0.64 down to \(1.2\times10^{-6}\). At a temperature comparable to the cell spacing a randomized rule is catastrophically worse — it retains a third of the information the same centers retain when used deterministically — and the gap vanishes as the rule becomes deterministic.
This is what the classical result predicts and it is worth being precise about what has and has not been shown. Purification says a deterministic rule matching the randomized moments exists for an atomless law. The measurement above says that one specific deterministic rule — the argmax of these particular responsibilities — is never worse here. The second statement does not follow from the first, and on an atomic score law whether randomization can ever strictly help is open.
Soft against exchange¶
The same four problems, solved by the soft path, by exact exchange, and by exact exchange started from the soft fit's labels:
| Problem | Cells | Soft, hardened | Exact D exchange | Exchange from soft labels |
|---|---|---|---|---|
| Gaussian location | 4 | 0.8851049 | 0.8851055 | 0.8851055 |
| Overlapping spectral templates | 8 | 0.9968710 | 0.9969338 | 0.9969318 |
| Two-parameter Gaussian mixture | 8 | 0.9964000 | 0.9964524 | 0.9964008 |
| Signal fraction with background shapes | 6 | 0.9755215 | 0.9755675 | 0.9755675 |
Exact exchange wins on all four, by between 0.6 and 63 parts per million. Seeding exchange with the soft labels recovers the exchange answer on two problems and lands slightly below it on the other two, because a soft solution is a perfectly good exchange-stable point and exchange, being a local search, stops at the first one it cannot improve.
So the soft path is not a winner on retention here, and this page will not pretend
otherwise. What it is, is the only route to two things exchange cannot do. It fits families
that are not free labelings at all, which is what makes a rule rather than a labeling. And
it accepts ProfiledDOptimality, where the exchange solver's result has no canonical
compilation — the reusable profiled rule on the
nuisance-profiled-ds page is exactly this solver, and there is no
alternative to it.
Discussion¶
Task: space quantization (fit_quantizer), with optimize_partition appearing only as
the comparison. Door: 1, precomputed score events. Criterion and solver:
DOptimality with SoftVoronoiConfig throughout, against DExchangeConfig for the
comparison; SoftVoronoiConfig also accepts ProfiledDOptimality, which is where it
becomes indispensable rather than optional.
What the baselines did. The canonical baselines are not rerun here — the solver shootout already places soft gradient descent among the information-aware solvers, all of which beat a rectangular observation grid by six to thirty D-efficiency points. The comparison this page needs is internal: soft against hard, and randomized against deterministic.
What to take away. Cool the schedule, and judge the fit by
train_hard_retention and hardening_gap rather than by the soft objective, which can
climb 27 points while the deployed rule goes nowhere. Expect hardening to be free or
slightly profitable, but check rather than assume — the library reports the number for that
reason. And reach for the soft path when you need a rule under a criterion that has no
compile bridge, not because it optimizes better than exact exchange, because on these
problems it does not.
The matching notebook,
soft_purification.ipynb,
runs the full-size study, prints every table above, and re-renders the figure.