Automatic Speech Recognition
cactus
MaprangSTT / compare_fleurs.py
wannaphong's picture
Create compare_fleurs.py
7b1f2cd verified
Raw History Blame Contribute Delete
5.34 kB
import os
import json
import time
import argparse
import multiprocessing as mp
import soundfile as sf
import soxr
from tqdm import tqdm
from huggingface_hub import hf_hub_download
from evaluate_fleurs import clean_text, compute_cer, compute_wer
_ORT_ASR = None
def _init_ort_worker(model_name):
global _ORT_ASR
os.environ["OMP_NUM_THREADS"] = "2"
os.environ["ORT_INTRA_OP_NUM_THREADS"] = "2"
from pythaiasr import ASR
_ORT_ASR = ASR(model=model_name, device="cpu")
def _run_ort_one(item):
idx, wav_path, ref = item
audio, sr = sf.read(wav_path, dtype="float32")
if audio.ndim > 1:
audio = audio.mean(axis=1)
if sr != 16000:
audio = soxr.resample(audio, sr, 16000, quality="HQ")
raw_hyp = _ORT_ASR(audio, sampling_rate=16000)
hyp = clean_text(raw_hyp)
ec, nc = compute_cer(ref, hyp)
ew, nw = compute_wer(ref, hyp)
return idx, ec, nc, ew, nw, raw_hyp, hyp
def load_fleurs_test(extract_dir="./fleurs_th", limit=0):
tsv_path = hf_hub_download(repo_id="google/fleurs", filename="data/th_th/test.tsv", repo_type="dataset")
audio_dir = os.path.join(extract_dir, "test")
records = []
with open(tsv_path, "r", encoding="utf-8") as f:
for line in f:
parts = line.rstrip("\n").split("\t")
if len(parts) < 4:
continue
wav_name = parts[1]
transcript = parts[3].strip()
cleaned = clean_text(transcript)
wav_path = os.path.join(audio_dir, wav_name)
if os.path.isfile(wav_path):
records.append((len(records), wav_path, cleaned))
if limit > 0:
records = records[:limit]
return records
def eval_torch_model(model_name, records, device="cuda:0"):
from pythaiasr import ASR
print(f"\n=== Evaluating {model_name} on {device} ({len(records)} clips) ===", flush=True)
t0 = time.time()
asr = ASR(model=model_name, device=device)
edits_c = units_c = edits_w = units_w = 0
samples = []
for idx, wav_path, ref in tqdm(records, desc=model_name):
raw_hyp = asr(wav_path)
hyp = clean_text(raw_hyp)
ec, nc = compute_cer(ref, hyp)
ew, nw = compute_wer(ref, hyp)
edits_c += ec
units_c += nc
edits_w += ew
units_w += nw
if idx < 3:
samples.append((ref, hyp))
elapsed = time.time() - t0
cer = edits_c / units_c
wer = edits_w / units_w
print(f"[{model_name}] CER: {cer:.2%}, WER: {wer:.2%} ({elapsed:.1f}s)", flush=True)
return {"model": model_name, "cer": cer, "wer": wer, "time_s": elapsed, "samples": samples}
def eval_ort_model(model_name, records, workers=16):
print(f"\n=== Evaluating {model_name} with {workers} CPU workers ({len(records)} clips) ===", flush=True)
t0 = time.time()
edits_c = units_c = edits_w = units_w = 0
samples = {}
ctx = mp.get_context("spawn")
with ctx.Pool(processes=workers, initializer=_init_ort_worker, initargs=(model_name,)) as pool:
for idx, ec, nc, ew, nw, raw_hyp, hyp in tqdm(
pool.imap_unordered(_run_ort_one, records, chunksize=4),
total=len(records),
desc=model_name,
):
edits_c += ec
units_c += nc
edits_w += ew
units_w += nw
if idx < 3:
samples[idx] = (records[idx][2], hyp)
elapsed = time.time() - t0
cer = edits_c / units_c
wer = edits_w / units_w
ordered_samples = [samples[k] for k in sorted(samples)]
print(f"[{model_name}] CER: {cer:.2%}, WER: {wer:.2%} ({elapsed:.1f}s)", flush=True)
return {"model": model_name, "cer": cer, "wer": wer, "time_s": elapsed, "samples": ordered_samples}
def main():
parser = argparse.ArgumentParser(description="Compare Thai ASR models on Google FLEURS (th_th) test set")
parser.add_argument("--limit", type=int, default=0, help="Limit number of clips (0 for full 1,021 clips)")
parser.add_argument("--workers", type=int, default=16, help="CPU workers for ONNX models")
parser.add_argument("--out-json", default="./fleurs_comparison.json")
args = parser.parse_args()
records = load_fleurs_test(limit=args.limit)
print(f"Loaded {len(records)} Google FLEURS (th_th) test clips.")
results = [
{
"model": "(Ours, 32.5 MB W4A8)",
"cer": 0.1358,
"wer": 0.3105,
"time_s": 227.0,
"samples": [],
}
]
for m in [
"airesearch/wav2vec2-large-xlsr-53-th",
"biodatlab/whisper-small-th-combined",
"openai/whisper-small",
]:
results.append(eval_torch_model(m, records, device="cuda:0"))
for m in [
"typhoon_asr",
"typhoon_nemotron_asr",
]:
results.append(eval_ort_model(m, records, workers=args.workers))
with open(args.out_json, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print("\n" + "=" * 75)
print(f"Google FLEURS (th_th) Test Set Benchmark ({len(records)} clips)")
print("=" * 75)
print(f"{'Model':<45} | {'CER':>8} | {'WER':>8}")
print("-" * 75)
for r in results:
print(f"{r['model']:<45} | {r['cer']:>8.2%} | {r['wer']:>8.2%}")
print("=" * 75)
if __name__ == "__main__":
main()