Create evaluate_fleurs.py
Browse files- evaluate_fleurs.py +135 -0
evaluate_fleurs.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import tarfile
|
| 4 |
+
import argparse
|
| 5 |
+
import cactus
|
| 6 |
+
from huggingface_hub import hf_hub_download
|
| 7 |
+
from pythainlp.util import normalize, num_to_thaiword, thai_digit_to_arabic_digit, digit_to_text
|
| 8 |
+
from pythainlp.tokenize import word_tokenize
|
| 9 |
+
from tqdm import tqdm
|
| 10 |
+
|
| 11 |
+
regex = re.compile(r"[^\w\u0E00-\u0E7Fa-zA-Z]")
|
| 12 |
+
clean_whitespace = re.compile(r"\s+")
|
| 13 |
+
|
| 14 |
+
def convert_numbers_to_thai(text):
|
| 15 |
+
text = thai_digit_to_arabic_digit(text)
|
| 16 |
+
text = re.sub(r"(?<=\d),(?=\d)", "", text)
|
| 17 |
+
|
| 18 |
+
def replace_decimal(m):
|
| 19 |
+
int_part, dec_part = m.group(1), m.group(2)
|
| 20 |
+
try:
|
| 21 |
+
int_word = num_to_thaiword(int(int_part)) if int_part else ""
|
| 22 |
+
except Exception:
|
| 23 |
+
int_word = digit_to_text(int_part)
|
| 24 |
+
dec_word = digit_to_text(dec_part)
|
| 25 |
+
return f" {int_word}จุด{dec_word} "
|
| 26 |
+
|
| 27 |
+
text = re.sub(r"(\d+)\.(\d+)", replace_decimal, text)
|
| 28 |
+
|
| 29 |
+
def replace_int(m):
|
| 30 |
+
s = m.group(0)
|
| 31 |
+
if (len(s) > 1 and s.startswith("0")) or len(s) > 12:
|
| 32 |
+
return f" {digit_to_text(s)} "
|
| 33 |
+
try:
|
| 34 |
+
return f" {num_to_thaiword(int(s))} "
|
| 35 |
+
except Exception:
|
| 36 |
+
return f" {digit_to_text(s)} "
|
| 37 |
+
|
| 38 |
+
text = re.sub(r"\d+", replace_int, text)
|
| 39 |
+
return text
|
| 40 |
+
|
| 41 |
+
def clean_text(s):
|
| 42 |
+
s = s.replace("ํา", "ำ")
|
| 43 |
+
s = convert_numbers_to_thai(s)
|
| 44 |
+
s = normalize(s.strip())
|
| 45 |
+
s = re.sub(r"\s\s+", " ", regex.sub(" ", s).replace(" ", " ").replace("“", "").replace(" ”", "").replace("”", "")).strip()
|
| 46 |
+
return clean_whitespace.sub(" ", " ".join(word_tokenize(s, engine="newmm"))).strip()
|
| 47 |
+
|
| 48 |
+
def compute_cer(ref, hyp):
|
| 49 |
+
ref_chars = list(ref.replace(" ", ""))
|
| 50 |
+
hyp_chars = list(hyp.replace(" ", ""))
|
| 51 |
+
row = list(range(len(hyp_chars) + 1))
|
| 52 |
+
for i, r in enumerate(ref_chars, 1):
|
| 53 |
+
prev, row[0] = row[0], i
|
| 54 |
+
for j, h in enumerate(hyp_chars, 1):
|
| 55 |
+
prev, row[j] = row[j], min(row[j] + 1, row[j - 1] + 1, prev + (r != h))
|
| 56 |
+
return row[-1], len(ref_chars)
|
| 57 |
+
|
| 58 |
+
def compute_wer(ref, hyp):
|
| 59 |
+
ref_words = ref.split()
|
| 60 |
+
hyp_words = hyp.split()
|
| 61 |
+
row = list(range(len(hyp_words) + 1))
|
| 62 |
+
for i, r in enumerate(ref_words, 1):
|
| 63 |
+
prev, row[0] = row[0], i
|
| 64 |
+
for j, h in enumerate(hyp_words, 1):
|
| 65 |
+
prev, row[j] = row[j], min(row[j] + 1, row[j - 1] + 1, prev + (r != h))
|
| 66 |
+
return row[-1], len(ref_words)
|
| 67 |
+
|
| 68 |
+
def main():
|
| 69 |
+
parser = argparse.ArgumentParser(description="Evaluate Whistle Thai model on Google FLEURS (th_th) test set")
|
| 70 |
+
parser.add_argument("--weights", default="/workspace/whistle-thai/output_combined/maprang.cact", help="Path to .cact weights")
|
| 71 |
+
parser.add_argument("--extract-dir", default="./fleurs_th", help="Directory to cache extracted FLEURS audio")
|
| 72 |
+
parser.add_argument("--limit", type=int, default=0, help="Limit number of test clips (0 for full 1,021 clips)")
|
| 73 |
+
parser.add_argument("--skip-non-thai", action="store_true", help="Skip test sentences containing digits or Latin characters")
|
| 74 |
+
args = parser.parse_args()
|
| 75 |
+
|
| 76 |
+
print("Downloading / locating Google FLEURS (th_th) test set...")
|
| 77 |
+
tsv_path = hf_hub_download(repo_id="google/fleurs", filename="data/th_th/test.tsv", repo_type="dataset")
|
| 78 |
+
tar_path = hf_hub_download(repo_id="google/fleurs", filename="data/th_th/audio/test.tar.gz", repo_type="dataset")
|
| 79 |
+
|
| 80 |
+
audio_dir = os.path.join(args.extract_dir, "test")
|
| 81 |
+
if not os.path.isdir(audio_dir) or len(os.listdir(audio_dir)) < 1000:
|
| 82 |
+
print(f"Extracting FLEURS test audio to {args.extract_dir}...")
|
| 83 |
+
os.makedirs(args.extract_dir, exist_ok=True)
|
| 84 |
+
with tarfile.open(tar_path, "r:gz") as tf:
|
| 85 |
+
tf.extractall(args.extract_dir)
|
| 86 |
+
|
| 87 |
+
records = []
|
| 88 |
+
thai_only_pattern = re.compile(r"^[ \u0E00-\u0E7F]+$")
|
| 89 |
+
has_digit = re.compile(r"[0-9\u0E50-\u0E59]")
|
| 90 |
+
|
| 91 |
+
with open(tsv_path, "r", encoding="utf-8") as f:
|
| 92 |
+
for line in f:
|
| 93 |
+
parts = line.rstrip("\n").split("\t")
|
| 94 |
+
if len(parts) < 4:
|
| 95 |
+
continue
|
| 96 |
+
wav_name = parts[1]
|
| 97 |
+
# parts[2] is raw transcription, parts[3] is normalized transcription (numbers spelled out in Thai)
|
| 98 |
+
transcript = parts[3].strip()
|
| 99 |
+
cleaned = clean_text(transcript)
|
| 100 |
+
if args.skip_non_thai:
|
| 101 |
+
if has_digit.search(cleaned) or not thai_only_pattern.match(cleaned):
|
| 102 |
+
continue
|
| 103 |
+
wav_path = os.path.join(audio_dir, wav_name)
|
| 104 |
+
if os.path.isfile(wav_path):
|
| 105 |
+
records.append((wav_path, cleaned))
|
| 106 |
+
|
| 107 |
+
if args.limit > 0:
|
| 108 |
+
records = records[:args.limit]
|
| 109 |
+
|
| 110 |
+
print(f"Evaluating {len(records)} clips from Google FLEURS (th_th) test set...")
|
| 111 |
+
edits_c = units_c = 0
|
| 112 |
+
edits_w = units_w = 0
|
| 113 |
+
|
| 114 |
+
for i, (wav_path, ref) in enumerate(tqdm(records, desc="FLEURS Eval")):
|
| 115 |
+
res = cactus.transcribe(wav_path, weights=args.weights, language="th")
|
| 116 |
+
hyp = res["text"].strip()
|
| 117 |
+
|
| 118 |
+
ec, nc = compute_cer(ref, hyp)
|
| 119 |
+
ew, nw = compute_wer(ref, hyp)
|
| 120 |
+
edits_c += ec
|
| 121 |
+
units_c += nc
|
| 122 |
+
edits_w += ew
|
| 123 |
+
units_w += nw
|
| 124 |
+
|
| 125 |
+
if i < 5:
|
| 126 |
+
tqdm.write(f"[{i+1}] Ref: {ref}")
|
| 127 |
+
tqdm.write(f" Hyp: {hyp}\n")
|
| 128 |
+
|
| 129 |
+
print(f"\n=== Google FLEURS (th_th) Test Results ({len(records)} clips) ===")
|
| 130 |
+
print(f"Model: {args.weights}")
|
| 131 |
+
print(f"Character Error Rate (CER): {edits_c / units_c:.2%}")
|
| 132 |
+
print(f"Word Error Rate (WER): {edits_w / units_w:.2%}")
|
| 133 |
+
|
| 134 |
+
if __name__ == "__main__":
|
| 135 |
+
main()
|