Download code/evaluate.py from Abdou/quran-asr-tiny: direct link, hf CLI and curl.
- Browser
- Download file 4.03 kB
-
https://huggingface.co/Abdou/quran-asr-tiny/resolve/main/code/evaluate.py
- Command line
-
hf download hf://Abdou/quran-asr-tiny/code/evaluate.py
-
curl -L -o evaluate.py https://huggingface.co/Abdou/quran-asr-tiny/resolve/main/code/evaluate.py
4.03 kB
| """ | |
| Step 4: measure accuracy on the test set (reciters never trained on + held-out Warsh surahs). | |
| python evaluate.py --config config/pilot.yaml # PyTorch, best.pt | |
| python evaluate.py --config config/pilot.yaml --onnx # the exported int8 model, via sherpa-onnx | |
| python evaluate.py --config config/pilot.yaml --baseline # the app's current NVIDIA model, for comparison | |
| Reports the letter error rate (CER), word error rate (WER) and skeleton error rate (letters | |
| without alif/hamza, which is what tracking relies on) per riwaya and per reciter. | |
| Both the model output and the reference are letters-only, so every model is scored the same way. | |
| """ | |
| import os | |
| import sys | |
| import torch | |
| from quran_asr import audio, config as config_mod, data, features, model as model_mod, text | |
| def main(): | |
| parser = config_mod.from_args(__doc__) | |
| parser.add_argument("--onnx", action="store_true", help="evaluate runs/<name>/export/model.int8.onnx") | |
| parser.add_argument("--baseline", action="store_true", help="evaluate the NVIDIA FastConformer instead") | |
| parser.add_argument("--checkpoint", default="best.pt") | |
| parser.add_argument("--limit", type=int, default=0, help="only the first N clips") | |
| args = parser.parse_args() | |
| cfg = config_mod.load(args.config) | |
| test = data.read_manifest(os.path.join(config_mod.manifest_dir(cfg), "test.jsonl")) | |
| if args.limit: | |
| test = test[:: max(1, len(test) // args.limit)][: args.limit] | |
| if args.onnx or args.baseline: | |
| import sherpa_onnx | |
| if args.baseline: | |
| folder = os.path.join(cfg.paths.data_dir, "nvidia_fastconformer") | |
| rec = sherpa_onnx.OfflineRecognizer.from_nemo_ctc( | |
| model=os.path.join(folder, "model.onnx"), tokens=os.path.join(folder, "tokens.txt"), num_threads=8) | |
| else: | |
| out = os.path.join(cfg.paths.run_dir, "export") | |
| rec = sherpa_onnx.OfflineRecognizer.from_zipformer_ctc( | |
| model=os.path.join(out, "model.int8.onnx"), tokens=os.path.join(out, "tokens.txt"), num_threads=8) | |
| def transcribe(wave): | |
| s = rec.create_stream() | |
| s.accept_waveform(features.SAMPLE_RATE, wave) | |
| rec.decode_stream(s) | |
| return text.normalize(s.result.text.replace(text.WORD, " ")) | |
| else: | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| ckpt = torch.load(os.path.join(cfg.paths.run_dir, args.checkpoint), map_location="cpu", weights_only=False) | |
| model = model_mod.build(config_mod._wrap(ckpt["model_config"]), len(text.VOCAB)) | |
| model.load_state_dict(ckpt["model"]) | |
| model.to(device).eval() | |
| def transcribe(wave): | |
| feats = features.fbank(torch.from_numpy(wave)).to(device) | |
| with torch.no_grad(): | |
| log_probs, lens = model(feats[None], torch.tensor([feats.shape[0]], device=device)) | |
| return text.decode(model_mod.greedy_decode(log_probs, lens)[0]) | |
| totals = {} | |
| for k, e in enumerate(test, 1): | |
| hyp = transcribe(audio.load(e["path"])) | |
| counts = text.error_counts(hyp, e["text"]) | |
| for key in ("all", e["riwaya"], f"{e['riwaya']}/{e['reciter']}"): | |
| t = totals.setdefault(key, [0] * 6) | |
| for j in range(6): | |
| t[j] += counts[j] | |
| if k % 200 == 0: | |
| print(f" {k}/{len(test)}", flush=True) | |
| name = "NVIDIA FastConformer (baseline)" if args.baseline else ("exported ONNX" if args.onnx else args.checkpoint) | |
| print(f"\n{name} on {len(test)} test clips") | |
| print("error rates; 'skeleton' ignores alif and hamza (spelling conventions), like the app's tracker") | |
| print(f"{'':52s} {'letters':>8s} {'words':>8s} {'skeleton':>9s}") | |
| for key, (ce, ct, we, wt, se, st) in sorted(totals.items(), key=lambda kv: (kv[0] != "all", kv[0])): | |
| print(f"{key:52s} {ce / max(1, ct) * 100:7.2f}% {we / max(1, wt) * 100:7.2f}% {se / max(1, st) * 100:8.2f}%") | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |