speaker_helper.eval.metrics module
Dependency-free quality and speed metrics for synthesised speech.
Module summary
This is speaker-helper’s measurement machinery, built around its
AudioResult. It answers two questions for every
utterance — is it fast? and is it good? — with pure-Python functions that
need no model and no network:
Speed.
percentile()over per-utterance real-time factors (RTF).Signal anomalies.
detect_anomalies()flags empty audio, aberrant duration for the amount of text, clipping, and invalid sample rates — the “TTS anomaly rate” gate. It decodes the WAV withsoundfile(imported lazily) but computes everything else in the standard library.Fidelity (optional round-trip). When a transcriber is available the audio can be re-transcribed and compared to the reference text with
word_error_rate()(WER) andchrf()(character n-gram F-score). Both are implemented here rather than pulled fromjiwer/sacrebleuso the core evaluation has zero heavy dependencies.
Usage example
>>> from speaker_helper.eval.metrics import word_error_rate, chrf
>>> word_error_rate("le chat dort", "le chat dort")
0.0
>>> round(chrf("bonjour", "bonjour"), 3)
1.0
- speaker_helper.eval.metrics.chrf(reference, hypothesis, *, max_n=6, beta=2.0)[source]
Character n-gram F-score (chrF) between reference and hypothesis.
A robust, tokenisation-free similarity that credits partial-word overlap — well suited to speech round-trips where the transcriber may mis-segment.
- Parameters:
- Returns:
Score in
[0, 1];1.0is identical (after normalisation),0.0no shared n-grams. Returns1.0when both are empty.- Return type:
Examples
>>> round(chrf("bonjour le monde", "bonjour le monde"), 3) 1.0
- speaker_helper.eval.metrics.detect_anomalies(result, *, min_chars_per_sec=4.0, max_chars_per_sec=40.0, clipping_threshold=0.999)[source]
Return a list of anomaly labels for one synthesised result (empty = clean).
The checks are engine-agnostic and decidable from the audio alone:
empty_audio— zero or near-zero duration.invalid_sample_rate— non-positive sample rate.duration_too_short/duration_too_long— the characters-per-second implied bylen(text) / durationfalls outside the plausible band, signalling truncation or a runaway stretch.clipping— a sample reaches full scale.
- Parameters:
result (AudioResult) – The synthesised audio and its metadata.
min_chars_per_sec (float) – Plausible speaking-rate band (see module constants).
max_chars_per_sec (float) – Plausible speaking-rate band (see module constants).
clipping_threshold (float) – Absolute amplitude at or above which audio is deemed clipped.
- Returns:
Sorted anomaly labels; an empty list means no anomaly was found.
- Return type:
- speaker_helper.eval.metrics.mean(values)[source]
Return the arithmetic mean, or
0.0for an empty input.
- speaker_helper.eval.metrics.normalize_text(text)[source]
Lowercase, strip accents/punctuation, and collapse whitespace.
A deliberately aggressive normalisation so WER/chrF measure what was said, not casing, accentuation, or punctuation the TTS/STT pair may drop.
- Parameters:
text (str) – Raw text.
- Returns:
Normalised text (lowercase ASCII words separated by single spaces).
- Return type:
Examples
>>> normalize_text("Où est le Café ?") 'ou est le cafe'
- speaker_helper.eval.metrics.peak_amplitude(wav_bytes)[source]
Return the peak absolute sample amplitude of a WAV payload (
0.0if empty).
- speaker_helper.eval.metrics.percentile(values, p)[source]
Return the
p-th percentile (0-100) via linear interpolation.- Parameters:
- Returns:
The interpolated percentile, or
0.0for an empty input.- Return type:
Examples
>>> percentile([1, 2, 3, 4], 50) 2.5 >>> percentile([], 95) 0.0
- speaker_helper.eval.metrics.word_error_rate(reference, hypothesis)[source]
Word error rate between a reference and a hypothesis transcript.
- Parameters:
- Returns:
edit_distance(ref_words, hyp_words) / len(ref_words).0.0for an exact match; can exceed1.0when the hypothesis inserts many words. Returns0.0if both are empty and1.0if only the reference is.- Return type:
Examples
>>> word_error_rate("le chat dort", "le chien dort") 0.3333333333333333