Automatic Speech Recognition
cactus
wannaphong commited on
Commit
7b1f2cd
·
verified ·
1 Parent(s): 821dd49

Create compare_fleurs.py

Browse files
Files changed (1) hide show
  1. compare_fleurs.py +148 -0
compare_fleurs.py ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import time
4
+ import argparse
5
+ import multiprocessing as mp
6
+ import soundfile as sf
7
+ import soxr
8
+ from tqdm import tqdm
9
+ from huggingface_hub import hf_hub_download
10
+ from evaluate_fleurs import clean_text, compute_cer, compute_wer
11
+
12
+ _ORT_ASR = None
13
+
14
+ def _init_ort_worker(model_name):
15
+ global _ORT_ASR
16
+ os.environ["OMP_NUM_THREADS"] = "2"
17
+ os.environ["ORT_INTRA_OP_NUM_THREADS"] = "2"
18
+ from pythaiasr import ASR
19
+ _ORT_ASR = ASR(model=model_name, device="cpu")
20
+
21
+ def _run_ort_one(item):
22
+ idx, wav_path, ref = item
23
+ audio, sr = sf.read(wav_path, dtype="float32")
24
+ if audio.ndim > 1:
25
+ audio = audio.mean(axis=1)
26
+ if sr != 16000:
27
+ audio = soxr.resample(audio, sr, 16000, quality="HQ")
28
+ raw_hyp = _ORT_ASR(audio, sampling_rate=16000)
29
+ hyp = clean_text(raw_hyp)
30
+ ec, nc = compute_cer(ref, hyp)
31
+ ew, nw = compute_wer(ref, hyp)
32
+ return idx, ec, nc, ew, nw, raw_hyp, hyp
33
+
34
+ def load_fleurs_test(extract_dir="./fleurs_th", limit=0):
35
+ tsv_path = hf_hub_download(repo_id="google/fleurs", filename="data/th_th/test.tsv", repo_type="dataset")
36
+ audio_dir = os.path.join(extract_dir, "test")
37
+ records = []
38
+ with open(tsv_path, "r", encoding="utf-8") as f:
39
+ for line in f:
40
+ parts = line.rstrip("\n").split("\t")
41
+ if len(parts) < 4:
42
+ continue
43
+ wav_name = parts[1]
44
+ transcript = parts[3].strip()
45
+ cleaned = clean_text(transcript)
46
+ wav_path = os.path.join(audio_dir, wav_name)
47
+ if os.path.isfile(wav_path):
48
+ records.append((len(records), wav_path, cleaned))
49
+ if limit > 0:
50
+ records = records[:limit]
51
+ return records
52
+
53
+ def eval_torch_model(model_name, records, device="cuda:0"):
54
+ from pythaiasr import ASR
55
+ print(f"\n=== Evaluating {model_name} on {device} ({len(records)} clips) ===", flush=True)
56
+ t0 = time.time()
57
+ asr = ASR(model=model_name, device=device)
58
+ edits_c = units_c = edits_w = units_w = 0
59
+ samples = []
60
+ for idx, wav_path, ref in tqdm(records, desc=model_name):
61
+ raw_hyp = asr(wav_path)
62
+ hyp = clean_text(raw_hyp)
63
+ ec, nc = compute_cer(ref, hyp)
64
+ ew, nw = compute_wer(ref, hyp)
65
+ edits_c += ec
66
+ units_c += nc
67
+ edits_w += ew
68
+ units_w += nw
69
+ if idx < 3:
70
+ samples.append((ref, hyp))
71
+ elapsed = time.time() - t0
72
+ cer = edits_c / units_c
73
+ wer = edits_w / units_w
74
+ print(f"[{model_name}] CER: {cer:.2%}, WER: {wer:.2%} ({elapsed:.1f}s)", flush=True)
75
+ return {"model": model_name, "cer": cer, "wer": wer, "time_s": elapsed, "samples": samples}
76
+
77
+ def eval_ort_model(model_name, records, workers=16):
78
+ print(f"\n=== Evaluating {model_name} with {workers} CPU workers ({len(records)} clips) ===", flush=True)
79
+ t0 = time.time()
80
+ edits_c = units_c = edits_w = units_w = 0
81
+ samples = {}
82
+ ctx = mp.get_context("spawn")
83
+ with ctx.Pool(processes=workers, initializer=_init_ort_worker, initargs=(model_name,)) as pool:
84
+ for idx, ec, nc, ew, nw, raw_hyp, hyp in tqdm(
85
+ pool.imap_unordered(_run_ort_one, records, chunksize=4),
86
+ total=len(records),
87
+ desc=model_name,
88
+ ):
89
+ edits_c += ec
90
+ units_c += nc
91
+ edits_w += ew
92
+ units_w += nw
93
+ if idx < 3:
94
+ samples[idx] = (records[idx][2], hyp)
95
+ elapsed = time.time() - t0
96
+ cer = edits_c / units_c
97
+ wer = edits_w / units_w
98
+ ordered_samples = [samples[k] for k in sorted(samples)]
99
+ print(f"[{model_name}] CER: {cer:.2%}, WER: {wer:.2%} ({elapsed:.1f}s)", flush=True)
100
+ return {"model": model_name, "cer": cer, "wer": wer, "time_s": elapsed, "samples": ordered_samples}
101
+
102
+ def main():
103
+ parser = argparse.ArgumentParser(description="Compare Thai ASR models on Google FLEURS (th_th) test set")
104
+ parser.add_argument("--limit", type=int, default=0, help="Limit number of clips (0 for full 1,021 clips)")
105
+ parser.add_argument("--workers", type=int, default=16, help="CPU workers for ONNX models")
106
+ parser.add_argument("--out-json", default="./fleurs_comparison.json")
107
+ args = parser.parse_args()
108
+
109
+ records = load_fleurs_test(limit=args.limit)
110
+ print(f"Loaded {len(records)} Google FLEURS (th_th) test clips.")
111
+
112
+ results = [
113
+ {
114
+ "model": "(Ours, 32.5 MB W4A8)",
115
+ "cer": 0.1358,
116
+ "wer": 0.3105,
117
+ "time_s": 227.0,
118
+ "samples": [],
119
+ }
120
+ ]
121
+
122
+ for m in [
123
+ "airesearch/wav2vec2-large-xlsr-53-th",
124
+ "biodatlab/whisper-small-th-combined",
125
+ "openai/whisper-small",
126
+ ]:
127
+ results.append(eval_torch_model(m, records, device="cuda:0"))
128
+
129
+ for m in [
130
+ "typhoon_asr",
131
+ "typhoon_nemotron_asr",
132
+ ]:
133
+ results.append(eval_ort_model(m, records, workers=args.workers))
134
+
135
+ with open(args.out_json, "w", encoding="utf-8") as f:
136
+ json.dump(results, f, ensure_ascii=False, indent=2)
137
+
138
+ print("\n" + "=" * 75)
139
+ print(f"Google FLEURS (th_th) Test Set Benchmark ({len(records)} clips)")
140
+ print("=" * 75)
141
+ print(f"{'Model':<45} | {'CER':>8} | {'WER':>8}")
142
+ print("-" * 75)
143
+ for r in results:
144
+ print(f"{r['model']:<45} | {r['cer']:>8.2%} | {r['wer']:>8.2%}")
145
+ print("=" * 75)
146
+
147
+ if __name__ == "__main__":
148
+ main()