Evaluate local speech recognition in two languages
Compare English and Spanish transcripts against reference words and score whether important details survived.
On this page
Make two reference recordings
Record “The library opens at nine on Tuesday” in English and “La biblioteca abre a las nueve el martes” in Spanish. Save the files as english.wav and spanish.wav. Write down the words you actually said. If they differ from these examples, use your own words as the reference.
Use the Faster Whisper environment from the transcription lab. Save the current copies of both scripts. Cache the English-only tiny.en and the multilingual tiny models before evaluating. The evaluator then loads them locally.
View evaluate_speech.py
import argparse
from pathlib import Path
import re
from time import perf_counter
import unicodedata
from faster_whisper import WhisperModel
from transcribe_notes import transcribe
def words(text):
return re.findall(r"[^\W_]+", unicodedata.normalize("NFC", text).casefold())
def edit_distance(expected, observed):
previous = list(range(len(observed) + 1))
for row, target in enumerate(expected, start=1):
current = [row]
for column, guess in enumerate(observed, start=1):
current.append(min(
previous[column] + 1,
current[column - 1] + 1,
previous[column - 1] + (target != guess),
))
previous = current
return previous[-1]
def evaluate(audio, model_name, language, reference, facts):
model = WhisperModel(model_name, device="cpu", compute_type="int8", local_files_only=True)
started = perf_counter()
lines, detected = transcribe(model, audio, language, vad_filter=True)
elapsed = perf_counter() - started
text = " ".join(line for _, line in lines)
observed = words(text)
edits = edit_distance(reference, observed)
normalized = " " + " ".join(observed) + " "
matches = [label for label, tokens in facts if " " + " ".join(tokens) + " " in normalized]
print(f"Model: {model_name} | language used: {detected} | transcribe: {elapsed:.2f}s")
print(f"Transcript: {text or '(no speech)'}")
print(f"Word error rate: {edits}/{len(reference)} = {edits / len(reference):.1%}")
if facts:
print("Key phrase:", " / ".join(matches) if matches else "missing")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Check a local transcript against reference words.")
parser.add_argument("audio", type=Path)
parser.add_argument("--reference", required=True)
parser.add_argument("--model", choices=("tiny.en", "tiny"), default="tiny")
parser.add_argument("--language", choices=("en", "es", "auto"), default="auto")
parser.add_argument("--fact", action="append", default=[], help="Repeat for alternate spellings of one fact")
args = parser.parse_args()
reference = words(args.reference)
facts = [(label, words(label)) for label in args.fact]
if not args.audio.is_file() or not reference or any(not tokens for _, tokens in facts):
parser.error("Supply a recording, a nonempty reference, and any key phrases.")
if args.model == "tiny.en" and args.language == "es":
parser.error("Use the multilingual tiny model for Spanish.")
language = None if args.language == "auto" else args.language
try:
evaluate(args.audio, args.model, language, reference, facts)
except (OSError, RuntimeError, ValueError) as error:
raise SystemExit(str(error)) from NoneView transcribe_notes.py
import argparse
from pathlib import Path
from faster_whisper import WhisperModel
def timestamp(seconds):
minutes, seconds = divmod(int(seconds), 60)
hours, minutes = divmod(minutes, 60)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
def transcribe(model, audio, language="en", vad_filter=False):
segments, info = model.transcribe(str(audio), language=language, beam_size=3, vad_filter=vad_filter)
lines = [(segment.start, segment.text.strip()) for segment in segments if segment.text.strip()]
return lines, info.language
def main():
parser = argparse.ArgumentParser(description="Transcribe an English recording locally.")
parser.add_argument("audio", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
if not args.audio.is_file():
parser.error(f"Audio file not found: {args.audio}")
output = args.output or args.audio.with_name(args.audio.stem + ".transcript.md")
if output.exists():
parser.error(f"{output} already exists. Choose a new path with --output.")
model = WhisperModel("tiny.en", device="cpu", compute_type="int8")
lines, _ = transcribe(model, args.audio)
if not lines:
raise SystemExit("No speech was transcribed. Try a clear recording with one speaker.")
paragraphs = [f"[{timestamp(start)}] {text}" for start, text in lines]
try:
with output.open("x", encoding="utf-8") as transcript:
transcript.write("\n\n".join(paragraphs) + "\n")
except OSError as error:
raise SystemExit(f"Could not save {output}: {error}") from None
print(f"Wrote {output}")
if __name__ == "__main__":
main()python -c 'from faster_whisper import WhisperModel; WhisperModel("tiny.en", device="cpu", compute_type="int8"); WhisperModel("tiny", device="cpu", compute_type="int8")'
Score words and key details
python evaluate_speech.py english.wav --model tiny.en --language en --reference "The library opens at nine on Tuesday." --fact nine --fact 9
python evaluate_speech.py english.wav --model tiny --language en --reference "The library opens at nine on Tuesday." --fact nine --fact 9
python evaluate_speech.py spanish.wav --model tiny --language es --reference "La biblioteca abre a las nueve el martes." --fact nueve --fact 9
Word error rate divides the minimum number of word substitutions, deletions, and insertions by the number of reference words. This script ignores case and punctuation and preserves accents. It counts nine and 9 as different words. The key phrase check accepts either spelling of the same detail.
You can point to each difference in the printed transcript and say whether the opening time survived. Check the whole sentence too. A key phrase can be present in a sentence with the wrong meaning.
Compare language detection
Repeat the Spanish command with --language auto. Compare the selected language, the transcript, and the key phrase. In one run on synthetic recordings, both English transcripts rendered “nine” as “9,” which preserved the time. The Spanish transcript lost the opening time even when Spanish was selected explicitly.
Use multiple speakers and recordings before drawing conclusions about a language. Add examples with background noise and short utterances. If you switch to a language that needs a different word segmenter, define and verify the scoring unit first.
Check your understanding
Why can two transcripts with the same word error rate have different usefulness?
One may miss a number or name that the listener needs. The other may differ only in wording that preserves the meaning.
Sources
Progress is saved in this browser.