bezzam HF Staff commited on
Commit
6b90e11
·
verified ·
1 Parent(s): 32c1a11

Create run_eval_ml.py

Browse files
Files changed (1) hide show
  1. run_eval_ml.py +260 -0
run_eval_ml.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ from typing import Optional
3
+ import datasets
4
+ from datasets import Audio
5
+ import evaluate
6
+ import soundfile as sf
7
+ import tempfile
8
+ import time
9
+ import os
10
+ import requests
11
+ import itertools
12
+ from tqdm import tqdm
13
+ from dotenv import load_dotenv
14
+ from normalizer import data_utils
15
+ from normalizer.eval_utils import normalize_compound_pairs
16
+ import concurrent.futures
17
+ from providers import get_provider, PermanentError
18
+
19
+ load_dotenv()
20
+
21
+
22
+ def fetch_audio_urls(dataset_path, config_name, split, batch_size=100, max_retries=20):
23
+ API_URL = "https://datasets-server.huggingface.co/rows"
24
+
25
+ size_url = f"https://datasets-server.huggingface.co/size?dataset={dataset_path}&config={config_name}&split={split}"
26
+ size_response = requests.get(size_url).json()
27
+ total_rows = size_response["size"]["config"]["num_rows"]
28
+ for offset in tqdm(range(0, total_rows, batch_size), desc="Fetching audio URLs"):
29
+ params = {
30
+ "dataset": dataset_path,
31
+ "config": config_name,
32
+ "split": split,
33
+ "offset": offset,
34
+ "length": min(batch_size, total_rows - offset),
35
+ }
36
+
37
+ retries = 0
38
+ while retries <= max_retries:
39
+ try:
40
+ headers = {}
41
+ if os.environ.get("HF_TOKEN") is not None:
42
+ headers["Authorization"] = f"Bearer {os.environ['HF_TOKEN']}"
43
+ else:
44
+ print("HF_TOKEN not set, might experience rate-limiting.")
45
+ response = requests.get(API_URL, params=params)
46
+ response.raise_for_status()
47
+ data = response.json()
48
+ yield from data["rows"]
49
+ break
50
+ except (requests.exceptions.RequestException, ValueError) as e:
51
+ retries += 1
52
+ print(
53
+ f"Error fetching data: {e}, retrying ({retries}/{max_retries})..."
54
+ )
55
+ time.sleep(10)
56
+ if retries >= max_retries:
57
+ raise Exception("Max retries exceeded while fetching data.")
58
+
59
+
60
+ def transcribe_with_retry(
61
+ model_name: str,
62
+ audio_file_path: Optional[str],
63
+ sample: dict,
64
+ max_retries=10,
65
+ use_url=False,
66
+ language="en",
67
+ ):
68
+ provider, variant = get_provider(model_name)
69
+ retries = 0
70
+ while retries <= max_retries:
71
+ try:
72
+ return provider.transcribe(variant, audio_file_path, sample, use_url=use_url, language=language)
73
+ except PermanentError:
74
+ raise
75
+ except Exception as e:
76
+ retries += 1
77
+ if retries > max_retries:
78
+ raise e
79
+
80
+ if not use_url:
81
+ sf.write(
82
+ audio_file_path,
83
+ sample["audio"]["array"],
84
+ sample["audio"]["sampling_rate"],
85
+ format="WAV",
86
+ )
87
+ delay = 1
88
+ print(
89
+ f"API Error: {str(e)}. Retrying in {delay}s... (Attempt {retries}/{max_retries})"
90
+ )
91
+ time.sleep(delay)
92
+
93
+
94
+ def transcribe_dataset(
95
+ dataset_path,
96
+ config_name,
97
+ split,
98
+ model_name,
99
+ language,
100
+ use_url=False,
101
+ max_samples=None,
102
+ max_workers=4,
103
+ ):
104
+ if use_url:
105
+ audio_rows = fetch_audio_urls(dataset_path, config_name, split)
106
+ if max_samples:
107
+ audio_rows = itertools.islice(audio_rows, max_samples)
108
+ ds = audio_rows
109
+ else:
110
+ ds = datasets.load_dataset(dataset_path, config_name, split=split, streaming=False)
111
+ ds = ds.cast_column("audio", Audio(sampling_rate=16000))
112
+ if max_samples:
113
+ ds = ds.select(range(min(max_samples, len(ds))))
114
+
115
+ results = {
116
+ "references": [],
117
+ "predictions": [],
118
+ "audio_length_s": [],
119
+ "transcription_time_s": [],
120
+ }
121
+
122
+ print(f"Transcribing with model: {model_name}, language: {language}, config: {config_name}")
123
+
124
+ def process_sample(sample):
125
+ if use_url:
126
+ reference = sample["row"]["text"].strip()
127
+ audio_duration = sample["row"]["audio_length_s"]
128
+ start = time.time()
129
+ try:
130
+ transcription = transcribe_with_retry(
131
+ model_name, None, sample, use_url=True, language=language
132
+ )
133
+ except Exception as e:
134
+ print(f"Failed to transcribe after retries: {e}")
135
+ return None
136
+
137
+ else:
138
+ reference = sample.get("text", "").strip()
139
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpfile:
140
+ sf.write(
141
+ tmpfile.name,
142
+ sample["audio"]["array"],
143
+ sample["audio"]["sampling_rate"],
144
+ format="WAV",
145
+ )
146
+ tmp_path = tmpfile.name
147
+ audio_duration = (
148
+ len(sample["audio"]["array"]) / sample["audio"]["sampling_rate"]
149
+ )
150
+
151
+ start = time.time()
152
+ try:
153
+ transcription = transcribe_with_retry(
154
+ model_name, tmp_path, sample, use_url=False, language=language
155
+ )
156
+ except Exception as e:
157
+ print(f"Failed to transcribe after retries: {e}")
158
+ os.unlink(tmp_path)
159
+ return None
160
+ finally:
161
+ if os.path.exists(tmp_path):
162
+ os.unlink(tmp_path)
163
+ else:
164
+ print(f"File {tmp_path} does not exist")
165
+
166
+ transcription_time = time.time() - start
167
+ return reference, transcription, audio_duration, transcription_time
168
+
169
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
170
+ future_to_sample = {
171
+ executor.submit(process_sample, sample): sample for sample in ds
172
+ }
173
+ for future in tqdm(
174
+ concurrent.futures.as_completed(future_to_sample),
175
+ total=len(future_to_sample),
176
+ desc="Transcribing",
177
+ ):
178
+ result = future.result()
179
+ if result:
180
+ reference, transcription, audio_duration, transcription_time = result
181
+ results["predictions"].append(transcription)
182
+ results["references"].append(reference)
183
+ results["audio_length_s"].append(audio_duration)
184
+ results["transcription_time_s"].append(transcription_time)
185
+
186
+ # Filter empty references (consistent with English pipeline's prepare_data)
187
+ filtered = [
188
+ (ref, pred, dur, time_s)
189
+ for ref, pred, dur, time_s in zip(
190
+ results["references"], results["predictions"],
191
+ results["audio_length_s"], results["transcription_time_s"]
192
+ )
193
+ if data_utils.is_target_text_in_range(ref)
194
+ ]
195
+ if filtered:
196
+ results["references"], results["predictions"], results["audio_length_s"], results["transcription_time_s"] = zip(*filtered)
197
+ results = {k: list(v) for k, v in results.items()}
198
+
199
+ manifest_path = data_utils.write_manifest(
200
+ results["references"],
201
+ results["predictions"],
202
+ model_name.replace("/", "-"),
203
+ dataset_path,
204
+ config_name,
205
+ split,
206
+ audio_length=results["audio_length_s"],
207
+ transcription_time=results["transcription_time_s"],
208
+ )
209
+
210
+ print("Results saved at path:", manifest_path)
211
+
212
+ norm_refs = [data_utils.ml_normalizer(r, lang=language) for r in results["references"]]
213
+ norm_preds = [data_utils.ml_normalizer(t, lang=language) for t in results["predictions"]]
214
+ wer_metric = evaluate.load("wer")
215
+ wer_refs, wer_preds = normalize_compound_pairs(norm_refs, norm_preds)
216
+ wer = wer_metric.compute(references=wer_refs, predictions=wer_preds)
217
+ wer_percent = round(100 * wer, 2)
218
+ rtfx = round(
219
+ sum(results["audio_length_s"]) / sum(results["transcription_time_s"]), 2
220
+ )
221
+
222
+ print("WER:", wer_percent, "%")
223
+ print("RTFx:", rtfx)
224
+
225
+
226
+ if __name__ == "__main__":
227
+ parser = argparse.ArgumentParser(
228
+ description="Multilingual API Transcription Script with Concurrency"
229
+ )
230
+ parser.add_argument("--dataset_path", required=True)
231
+ parser.add_argument("--config_name", required=True, help="Dataset config name, e.g. 'fleurs_de'")
232
+ parser.add_argument("--language", required=True, help="Language code, e.g. 'de'")
233
+ parser.add_argument("--split", default="test")
234
+ parser.add_argument(
235
+ "--model_name",
236
+ required=True,
237
+ help="Prefix model name with provider prefix (e.g., 'assembly/', 'openai/', 'elevenlabs/', 'revai/', 'speechmatics/' or 'aquavoice/')",
238
+ )
239
+ parser.add_argument("--max_samples", type=int, default=None)
240
+ parser.add_argument(
241
+ "--max_workers", type=int, default=300, help="Number of concurrent threads"
242
+ )
243
+ parser.add_argument(
244
+ "--use_url",
245
+ action="store_true",
246
+ help="Use URL-based audio fetching instead of datasets",
247
+ )
248
+
249
+ args = parser.parse_args()
250
+
251
+ transcribe_dataset(
252
+ dataset_path=args.dataset_path,
253
+ config_name=args.config_name,
254
+ split=args.split,
255
+ model_name=args.model_name,
256
+ language=args.language,
257
+ use_url=args.use_url,
258
+ max_samples=args.max_samples,
259
+ max_workers=args.max_workers,
260
+ )