Report per-token confidence from greedy decoding - #86
Open
gman-dev-nov wants to merge 1 commit into
Open
Conversation
Greedy decoding already takes an argmax over a log-softmax distribution in both the CTC and the RNN-T path, so the log-probability of the chosen token is available at no cost — taking a max instead of an argmax keeps the index and the value in one pass. Both decoders now return a `Hypothesis` named tuple carrying `token_logprobs` alongside the existing text, token ids and frames. Those are aggregated into a confidence score in (0, 1] and surfaced as `TranscriptionResult.confidence`, `Segment.confidence` and `Word.confidence`. The aggregate is the length-normalized geometric mean exp(mean(log p)), so long words are not penalized for consisting of more tokens; blank decisions are excluded. `Hypothesis` keeps the positional layout of the previous tuple, so indexing is unchanged; `train_utils/eval.py` is updated to read the field by name instead of unpacking three values. Both READMEs document what the score does and does not measure, with measured numbers: burying `example.wav` in white noise at 0 dB SNR drops utterance confidence from 0.911 to 0.805 (v3_e2e_rnnt), while confidently wrong output still scores high.
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.
Motivation
Greedy decoding knows how sure it was about every token it emitted, and then throws that away. Both decoding paths take an
argmaxover a distribution that is alreadylog_softmax-normalized:CTCGreedyDecoding.decode—log_probs.argmax(dim=-1)RNNTGreedyDecoding.decode—head.joint.joint(f, g)[:, 0, 0, :].argmax(dim=-1), andRNNTJoint.jointreturns.log_softmax(-1)Replacing
argmaxwithmaxkeeps the index and the value in the same pass, so the score costs nothing. Downstream that enables ranking segments for review, routing low-confidence spans to a second pass, and filtering pseudo-labels when bootstrapping training data — all of which currently require re-running the model or forking the decoder.What changed
Both decoders now return a
Hypothesisnamed tuple that carriestoken_logprobsnext to the existing text / token ids / token frames. Those are aggregated and surfaced as:TranscriptionResult.confidenceSegment.confidenceWord.confidenceword_timestamps=True)The aggregate is the length-normalized geometric mean
exp(mean(log p)), so long words are not penalized for consisting of more tokens. Blank decisions are excluded. Raw per-token values stay available onHypothesis.token_logprobs.Confidence is reported even without
word_timestamps=True— the utterance-level score needs no timestamp machinery.Live examples
Per-word scores on the bundled
example.wav(v3_e2e_rnnt, utterance confidence0.9110):Long-form segments on
long_example.wavcarry it too:The score responds to acoustic difficulty. Burying the same utterance in white noise at 0 dB SNR:
v3_e2e_rnntv3_ctcThis is asserted in
tests/test_confidence.py::test_confidence_drops_on_noisy_audio.It is also informative inside a clean recording. On a 24-minute Russian tech talk (3022 words,
v3_e2e_rnnt, median confidence0.9568), the words the model attempted to spell in Latin scored a median of0.7317, and the ones it mangled outright sit in the bottom few percent:SGAGWoldpootImpootElmasLimitations, documented in both READMEs
This is a greedy per-token posterior, not a calibrated probability of correctness, and RNN-T greedy scores are known to be over-confident. Two failure modes are visible in the same recording above, and the README says so plainly rather than overselling the feature:
гардрейлscored0.8142(18th percentile) andквартрейл0.8502(24th percentile) — wrong, but nowhere near the tail.и,не,я), usually transcribed correctly.The recommendation given is to use it as one signal among several, not as a standalone error detector.
Compatibility
Hypothesisis aNamedTuplethat preserves the positional layout of the old return value, so indexing and slicing are unchanged. Code that unpacks exactly three values must read fields by name — the one such site in this repo,train_utils/eval.py, is updated.train_utils/module.pyusesh[0]and is unaffected. Happy to hide this behind a compatibility shim instead if you would rather keep the bare tuple.Word,TranscriptionResultandSegmentgain an optional field defaulting toNone; construction by keyword is unaffected.onnx_utils.pyhas its own decoding and is left untouched.Tests
tests/test_confidence.py— 11 tests, both revisions:Hypothesispositional compatibility;frames_to_wordsleaves confidenceNonewhen no log-probs are passedVerified locally on macOS/arm64 (
v3_ctc,v3_e2e_rnnt): 9 passed, plus the 2 long-form tests exercised through a local VAD substitute sincepyannoteis not installable here — CI installs thelongformextra and will run them directly. Existingtests/test_timestamps.pystill passes.blackandisortclean.