--- license: apache-2.0 language: - ja library_name: transformers pipeline_tag: text2text-generation base_model: Qwen/Qwen2.5-0.5B tags: - asr-error-correction - japanese - whisper - phonetics - constrained-decoding - on-device - gguf - mlx --- # Mondegreen — `v0.1.0` **用語集を、お願いではなく制約にする。** *A private glossary, compiled into a hard phonetic constraint.* Whisper does not know your colleagues' names, your product names or your team's jargon — and 10,000 of them do not fit in a 244-token prompt. Mondegreen corrects them **afterwards, locally**, as a span replacement that is structurally unable to leave your glossary. - Code: https://github.com/NagaYu/mondegreen - Space: https://huggingface.co/spaces/NagaYu/mondegreen - Dataset: https://huggingface.co/datasets/NagaYu/mondegreen-asr-errors ## What is in this repo - `gate.json — the calibrated conservative gate (3 KB, ships with the package)` - `lora/ — LoRA adapter for the candidate re-ranker (Qwen2.5-0.5B base)` ## Read this before assuming what the LM does The **hard constraint is not learned and not in these weights.** The legal replacement set for a span is computed by `mondegreen.index.PhoneticIndex` as a finite list, under bounds evaluated before any model runs: | bound | what it stops | | --- | --- | | normalised phonetic distance ≤ τ (0.28) | unrelated words | | absolute distance ≤ 0.25 + 0.20·√mora | long terms reached via many cheap edits | | mora-count difference ≤ 34% | invented syllables | | common dictionary words need near-exact homophony | 「稼働率」→「加藤率」 | | containment guard | 「新藤さん」→「新藤」 (deleting an honorific) | The LoRA adapter **only re-ranks candidates already inside that set**. It cannot add to it, cannot introduce a term that is not in your glossary, and cannot rewrite grammar. On synthetic glossaries only ~1% of spans have more than one legal candidate, so most of the time it is not consulted at all — which is precisely why 4-bit quantisation is safe here, and is asserted by `tests/test_quantization.py` (removing the LM entirely costs < 2 recall points). `gate.json` is the calibrated conservative gate — a logistic regression over 18 interpretable span features (AUC 0.985, ECE 0.053, threshold 0.82). It is 3 KB of JSON and it is the component whose job is to say *no*. ## Results 400 held-out sentences, 10,000-term glossary, evaluation glossary strictly disjoint from training by surface **and** by reading: | condition | CER | WER | term recall | **damage rate** | | --- | ---: | ---: | ---: | ---: | | (A) raw Whisper | 0.2842 | 0.2541 | 25.3% | 0.00000 | | (B) Whisper `initial_prompt` | 0.2796 | 0.2509 | 27.2% | 0.00009 | | (C) cloud LLM post-processing | 0.0893 | 0.0944 | 83.0% | 0.00657 | | **(D) Mondegreen** | 0.1105 | 0.1217 | 66.3% | 0.00009 | | (E) Mondegreen, quantised | 0.1105 | 0.1217 | 66.3% | 0.00009 | **(C) wins on term recall (83.0% vs 66.3%) and does 73× the damage** (0.00657 vs 0.00009), needs the transcript to leave the machine, and is therefore unusable on the confidential audio that motivates this project. That trade is the finding, not a footnote. | | | | --- | --- | | throughput | **464 characters/second** (10,000-term glossary) | | 1 hour of transcription | **45 seconds** | | peak memory | 196 MB | | machine | Apple M2, 16.0 GB | | network | **none** | > **Provenance.** These numbers are `simulated`: condition (D) is always the real > system, but the error generator and baselines (B)/(C) are explicit models whose > parameters are printed in the results file. They are **not** measured Whisper > numbers. See `benchmarks/README.md` in the repo for how to replace them with > measurements. ## Use ```bash pip install git+https://github.com/NagaYu/mondegreen ``` ```python from mondegreen import ConstrainedCorrector, load_glossary corrector = ConstrainedCorrector(load_glossary("terms.csv")) print(corrector.correct("進藤さんが両氏誤り訂正の話をしました。").text) # 新藤さんが量子誤り訂正の話をしました。 ``` With the quantised re-ranker: ```python from mondegreen.runtime import build_reranker corrector = ConstrainedCorrector( load_glossary("terms.csv"), lm=build_reranker("mondegreen-Q4_K_M.gguf"), ) ``` CLI, with the evidence for every edit: ```bash mondegreen fix transcript.txt --glossary terms.csv mondegreen explain transcript.txt --glossary terms.csv ``` ## Training data Synthetic. Glossaries are generated by `mondegreen.harvest.GlossaryBuilder`; carrier sentences by `SentenceFactory`; errors by the phonetic corruption model in `mondegreen.simulate`, which perturbs the *reading* using the same confusion classes the distance function discounts and re-renders it as a homophone kanji spelling. **No real audio, no real person's name, and no LLM grading anywhere.** ## Limitations - Japanese only. The mora table, the confusion costs and the POS rules are all Japanese-specific. - Without `fugashi`/`pyopenjtalk` the bundled 4,030-kanji fallback table is used. It has no part-of-speech information, so the common-word protection cannot fire and the damage rate rises. Install `mondegreen[g2p]`. - The n-gram candidate accelerator is not exact (99.67% recall vs exhaustive at 10,000 terms). Misses can only cause a *missed* correction, never an illegal one — the bound is re-verified on every scored candidate. - Evaluation is on synthetic glossaries and simulated ASR errors. ## ⚠️ Intended use A glossary can contain personal names. **Use this only on data under your own control.** Do not run it over other people's meeting records, or with a glossary of names you have no relationship to. Mondegreen handles text only, never audio, and makes no network calls. ## Citation ```bibtex @software{mondegreen, title = {Mondegreen: private glossaries as hard phonetic constraints for local ASR correction}, author = {NagaYu}, year = {2026}, url = {https://github.com/NagaYu/mondegreen}, license = {Apache-2.0} } ```