Speaker verification calibration
Frozen encoders. Calibrated scores.
A small residual on top of a frozen context-affine anchor turns speaker-verification scores into calibrated log-likelihood ratios, using duration, voice-activity and language context.
LLR = anchor + Δ. Because Δ starts at zero, training begins exactly at the anchor.
Tested on four corpora, six frozen encoders and their fusion — 28 conditions in all.
Lower Cllr than the context-affine baseline in every panel, and in 26 of 28 conditions.1
Lower Cllr on the TidyVoice single-encoder panel, with multiplicity-adjusted support.2
Improved when trials are split by duration, mismatch, language match and anchor confidence.3
| Test Cllr · lower is better | Context affineBaseline | SPARC | Change |
|---|---|---|---|
| TidyVoice single | 0.2518 | 0.2468 | −2.0% |
| TidyVoice fusion | 0.0926 | 0.0861 | −7.0% |
| NISP single | 0.1752 | 0.1679 | −4.2% |
| NISP fusion | 0.0371 | 0.0337 | −9.0% |
| LibriSpeech single | 0.1069 | 0.1062 | −0.6% |
| LibriSpeech fusion | 0.0368 | 0.0350 | −4.9% |
| VoxCeleb2 single | 0.2144 | 0.2112 | −1.5% |
| VoxCeleb2 fusion | 0.0644 | 0.0627 | −2.6% |
Single is the mean of six single-encoder conditions. SPARC values average the selected family's seeds.1
SPARC learns only what a strong affine reference misses — and keeps the reference when a correction does not help.
A source-affine map and a context-affine anchor are fitted on labeled calibration data, then frozen. Every residual starts at zero.
Validation chooses among 17 families — the anchor itself, 8 polynomial and 8 small neural residuals — only if Cllr and EER do not get worse.
A JSON bundle of about 15 KB with at most 289 residual parameters. Prediction checks encoder order and context schema.
The synthetic example fits the full procedure, saves a bundle and reloads it. No audio, pretrained weights or datasets needed.
pip install -r requirements.txt python -m examples.synthetic_demo --output-dir outputs/synthetic
from delivery import Plugin, Q_COLUMNS
names = ("encoder_a", "encoder_b")
Plugin.fit(calibration, validation, names, output_path="sparc.json")
llr = Plugin.load("sparc.json").predict(scores, context, names, context_names=Q_COLUMNS)