mondegreen / README.md
NagaYu's picture
Mondegreen v0.1.0
0539c4e verified
|
Raw
History Blame Contribute Delete
6.08 kB
---
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}
}
```