fix(speaker): average-based auto-accept margin — restore 0-false-auto without losing the multi-exemplar recall win#1496
Merged
Conversation
…re 0-false-auto Multi-exemplar voiceprints (#1488) lifted genuine recall on degraded audio (+0.109) but broke the certified "0 false-auto" guarantee: the eval in #1493 (docs/speaker-eval-exemplar-delta-2026-07.md) measured 12 silent mislabels on the degraded AMI slice where the average-only matcher had 0. Distinct speakers were auto-named because ONE lucky exemplar cleared both the 0.92 bar and the 0.12 best-vs-second margin. Fix (the eval's recommendation #2): keep best-of-exemplars for match SELECTION (where the recall win lives) but compute the auto-accept MARGIN against each profile's blended AVERAGE representative, not its best exemplar. A genuine owner is close on both its average and its best exemplar so the margin still clears; a lucky-exemplar impostor is far from the average so its average-based margin collapses and the auto-accept is withheld (routed to suggest/ask). - SpeakerNamingPolicy.shouldAutoAccept/initialMapping gain an optional marginSimilarities: (best, secondBest) — the 0.92 bar still uses the exemplar score; only the margin uses the average. nil ⇒ legacy behavior (single-average profiles are unaffected: average == best exemplar). - SpeakerMatchingService.matchAgainstProfiles computes the winner's average cosine and the best-other average cosine into SnapshotMatchResult, threaded through the tuple → ChannelSpeakerContext → SpeakerClassificationKnowledge to the auto-accept call sites (system + mic) and the simulation runner. - SpeakerExemplarDeltaEvalTests exercises the new gate and reports both the pre-fix (best-exemplar) and post-fix (average) false-auto counts per run. - SpeakerAutoAcceptMarginTests: corpus-free proof (policy gate + full matchAgainstProfiles path) that the lucky-exemplar impostor is now withheld while a genuine owner still auto-accepts. Re-run of the #1493 harness on the same qmatrix corpus (AMI degraded, WITH arm): false-auto 12 → 2, genuine recall 0.659 unchanged, mean genuine sim 0.858 unchanged. The residual 2 are genuinely confusable on the blended average (avgBest 0.70/0.82, avg-margin 0.14/0.17), not lucky-exemplar artifacts; tightening levers remove at most one while costing 2.5–15x as many genuine auto-recognitions, so the clean average-margin decouple is the knee of the curve. Full Core suite (716) + fast suite (12745) green. Refs #1493. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
main(release 1.1.49 prep) has a release-critical regression: the multi-exemplar voiceprints feature (#1488) broke the certified "0 false auto-accept" guarantee on degraded audio. The eval that landed in #1493 (docs/speaker-eval-exemplar-delta-2026-07.md+SpeakerExemplarDeltaEvalTests) measured it on real AMI cross-condition embeddings:The fix (eval recommendation #2)
Decouple the auto-accept margin from the best-exemplar score:
A genuine owner is close to the profile on both its average and its best exemplar, so the margin still clears. A lucky-exemplar impostor is far from the average, so its average-based margin collapses and the auto-accept is withheld (routed to suggest/ask instead of silently naming). The 0.92 bar still uses the exemplar score, so silent auto-recognition of genuine returning voices is preserved.
Code
SpeakerNamingPolicy.shouldAutoAccept/initialMappinggain an optionalmarginSimilarities: (best, secondBest)(average-based).nil⇒ legacy behavior; single-average profiles are unaffected (average == best exemplar).SpeakerMatchingService.matchAgainstProfilescomputes the winner's average cosine and the best-other average cosine intoSnapshotMatchResult, threaded through the pipeline tuple →ChannelSpeakerContext→SpeakerClassificationKnowledgeto the auto-accept sites (system + mic) and the simulation runner. The exemplarsecondBestSimilarityis retained for write-back blend gating.Verification — re-ran the #1493 harness on the same qmatrix corpus
SpeakerExemplarDeltaEvalTestsagainst the real AMI + VoxCeleb per-quality fingerprint caches (WITH arm = multi-exemplar). Raw output:docs/speaker-eval-exemplar-delta-2026-07.after-fix.result.json.AMI, degraded slice (741 trials, 147 profiles):
Recall is fully retained (unchanged by construction — the fix touches neither match selection nor the match floor). WITHOUT (legacy) arm and the VoxCeleb slice are unchanged (0 false-auto both arms). The harness now reports both
falseAutoLegacyMarginCount(12) andfalseAutoCount(2) per run, so the A/B is reproducible in one artifact.Residual 2 — characterized, not lucky-exemplar artifacts
The two remaining false-autos are genuinely confusable on the blended average (avgBest 0.82 / 0.70, avg-margin 0.172 / 0.137), not the one-lucky-exemplar failure this fix targets. Probed tighteners on the same slice each removed at most 1 of the 2 while costing 2.5–15× as many genuine auto-recognitions:
So the clean average-margin decouple is the knee of the curve: it removes the entire lucky-exemplar class (10/10) at zero recall cost, and stops before trading the genuine experience away for two irreducibly-confusable cases. 0.27% remains a worst-case degraded-slice upper bound, not a production rate.
Also added
SpeakerAutoAcceptMarginTests— corpus-free, in-tree proof (runs in CI): constructs the lucky-exemplar impostor fixture and asserts the average-based margin withholds it while a genuine owner still auto-accepts, both at the policy gate and through the fullmatchAgainstProfilespath.Tests run locally
swift test(Core) — 716 pass, 0 failures (13 corpus-gated skips)run-tests.sh(fast suite, compiles app-layer sources) — 12745 pass, 0 failuresSpeakerExemplarDeltaEvalTestsagainst the real qmatrix corpus — 12 → 2 false-auto, recall heldSpeakerAutoAcceptMarginTests— passbuild.sh --no-open(full app build + launch smoke + performance budget) — OKESpeakNG.framework(TTS dep not in the local deps copy; unrelated to this change — it fails at dylib load before any test runs). Covered by CI, which builds full deps.Refs #1493.
🤖 Generated with Claude Code