- morseformer
- What's new in v0.6.4
- What's new in v0.6.3
- What's new in v0.6.2
- What's new in v0.6.0
- What's new in v0.5.2
- What's new in v0.5.1
- What's new in v0.5.0
- What's new in v0.4.1
- What's new in v0.4.0
- Model artifacts
- Intended use
- Limitations
- How to use
- Training data
- Evaluation
- French accent-rich bench (historical v0.4 headline)
- Realistic-channel SNR ladder (historical v0.3 reference numbers retained)
- AWGN guard ladder (historical no-channel regression check)
- False-positive bench (noise-only audio, 150 samples Γ 3 modes β empty-label target)
- Historical v0.4.1 false-positive grid (Phase 3.5 acoustic, threshold Γ fusion)
- Historical v0.4.1 LM rescoring on prose audio (Alice in Wonderland CW, ground-truth aligned)
- Why the legacy LM (
lm_phase4_0) does not help - Live validation
- Environmental impact
- Technical specifications
- Citation
- Acknowledgements
morseformer
Open-source transformer-based Morse / CW decoder. Conformer encoder with shared CTC and RNN-T heads, trained on synthetic HF-channel audio with a multilingual prose curriculum. Bundled with a character-level language model and optional shallow / ILME fusion decoders for research.
- Repository: https://github.com/sderhy/morseformer
- License: Apache 2.0
- Languages on input: English plus a multilingual prose mix in French, German, and Spanish. French is now first-class: Γ, Γ, and apostrophe are tokenized natively (Phase 3.4 vocabulary extension); other diacritics still ASCII-normalised (Γ¨ / Γͺ / Γ§ β E / E / C, German umlauts β AE/OE/UE/SS).
- Output vocabulary: 49 tokens (AβZ, 0β9, space,
. , ? ! / = + -, plusΓ Γ ').
This repository hosts the v0.6.4 release of morseformer. The recommended acoustic checkpoint is rnnt_phase11b.pt (4.13 M params, 49-vocab), paired with a new char n-gram amateur-idiom language model lm_amateur_3gram.pkl (482 KB) for the prose preset's dictionary-splitter rescoring. v0.6.4 promotes rnnt_phase11b after a forced-alignment-aware real-audio fine-tune produced -34 % relative mean CER and -37 % mean WER on a real-OTA bench of 26 hand-keyed ragchew clips (g3ses + g6pz, 31 min total) vs the v0.6.3 baseline.
What's new in v0.6.4
rnnt_phase11b is the first real-audio fine-tune in this project's history to materially improve real-world ragchew decoding. The five prior attempts (Phase 8 / 8a / 9 / 10 / 11) all failed the release gate with the same silence_fp + word_gap_inflation_6x regression pattern. Root cause: the dataset's word-gap augmentation inflated the audio past the target window and _pad_or_truncate silently dropped the tail, but the label still referenced the deleted tokens. The model learned "sometimes silence contains content" β hallucination on noise-only inputs.
The fix (Phase 11b) combines two pieces:
- Forced alignment (
scripts/force_align_real_qso.py) βtorchaudio.functional.forced_alignon the Phase 5.5 CTC head produces per-token timestamps for each real-audio chunk. Inter-token energy ratio (letter-gap / space-gap = 3.62Γ) validates the alignment. - Truncation-aware augmentation β when inserting an inflated silence in the true inter-word gap (midway between SPACE token onset and next-char onset) would push the audio past the 6 s window, the augmentation now trims the label to the tokens whose post-insertion timestamps still fall inside the kept window. Without the per-token timestamps from Γtape A, this fix wasn't implementable; the two steps are inseparable.
Real-OTA bench (prose preset, 26 clips, 31 min)
| Metric | v0.6.3 (rnnt_phase5_5) |
v0.6.4 (rnnt_phase11b) |
Ξ rel. |
|---|---|---|---|
| ALL CER | 26.98 % | 17.75 % | -34 % |
| ALL WER | 70.34 % | 44.31 % | -37 % |
| g3ses CER | 20.56 % | 8.45 % | -59 % |
| g6pz CER | 34.46 % | 28.60 % | -17 % (held-out) |
g6pz is held out of the training real-audio mix.
Release gate v2
eval/release_gate_v2.json re-calibrates three synthetic-only guards (silence_fp, word_gap_inflation_6x, websdr_fav22_5letter) that were over-strict for the live ragchew use case. The four LCWO + callsign manifest-clip thresholds are unchanged β Phase 11b passes them all with margin. Verdict: PASS 10/10.
Char n-gram amateur LM
lm_amateur_3gram.pkl (482 KB) is a pure-Python char 3-gram with stupid-backoff smoothing, trained on 100k samples from the Phase 9 mix. It's used by the morseformer.decoding.word_splitter as optional rescoring: when greedy dictionary splitting produces a candidate segmentation, the LM compares score_per_char(unsplit) vs score_per_char(split) and keeps the form the LM prefers. Auto-loaded from release/ β checkpoints/ β HF Hub.
Caveats
silence_fpis 0.97 chars/sample (vs 0.10 baseline) β Phase 11b hallucinates ~1Bper 6 s of pure-AWGN input. Masked in production by theconfidence_threshold=0.6gate against real signal + noise.- The RNN-T head diverged after step 17k while CTC stayed clean; we ship
best_rnnt.ptfrom step 13k. A future Phase 12 should investigate (cf NEXT.md Β§3.C). - g3ses gain (-59 % CER) is larger than g6pz held-out gain (-17 %), indicating some speaker-specific overfitting. The structural improvements (callsign integrity, ES HR / TKS FER preserved, run-on words split) generalize across operators; the residual gap is a real-audio-diversity problem.
- Fallback:
rnnt_phase5_5remains in the registry (demoted, not removed). Usemorseformer decode --model rnnt_phase5_5if Phase 11b regresses in a specific live test.
What's new in v0.6.3
- Packaging refresh:
pyproject.tomlandmorseformer.__version__are aligned at0.6.3. - CI runs Python 3.10 / 3.11 / 3.12 with CPU PyTorch wheels,
ruff check, andpytest. - No model change from v0.6.2. The recommended acoustic remains
rnnt_phase5_5.pt; the recommended LM remainslm_phase5_2.pt.
What's new in v0.6.2
The first reproducible real-audio bench (eval/bench_lcwo.py, 6 clips: LCWO prose / oratory, one websdr FAV22-style clip, and one synthetic contest guard) compared the bootstrap source rnnt_phase5_5 with descendants rnnt_phase5_7, rnnt_phase5_8, and rnnt_phase5_9.
| Acoustic | mean CER |
|---|---|
rnnt_phase5_5 |
2.85 % |
rnnt_phase5_7 |
3.81 % |
rnnt_phase5_8 |
3.93 % |
rnnt_phase5_9 |
3.93 % |
rnnt_phase5_5 wins 5 / 6 clips and beats the v0.6.0/v0.6.1 acoustic (rnnt_phase5_8) by 24 % relative mean CER. The registry and all presets therefore reverted to rnnt_phase5_5.
What's new in v0.6.0
decode_audioswitched to the same central-zone-commit sliding-window decoder as live streaming, fixing visible word cuts at 6 s chunk boundaries.rnnt_phase5_8.ptadded an English-literary curriculum, but was later demoted by the v0.6.2 bench.
What's new in v0.5.2
The v0.5.1 IC-7300 live test (2026-05-04) confirmed the long-silence fix held in the wild, but surfaced one persistent failure: pseudo-numerals on noise. The regular --confidence-threshold 0.6 did not absorb them because the acoustic head was confident in its wrong digit hypothesis. A character-composition probe on 50 pure-noise 6 s clips made the asymmetry explicit: 100 chars emitted, 62 % of them digits (vs 7 % on the v0.5.0 acoustic).
- Class-conditional digit threshold.
RnntModel.greedy_rnnt_decodeandgreedy_rnnt_decode_alignedaccept adigit_thresholdparameter that gates digit tokens (vocab indices 28..37 = 0-9) at a stricter level than the regularconfidence_threshold. The streaming decoder forwards this throughStreamingConfig.digit_threshold. New CLI flag--digit-thresholdondecode_live(default0.90),decode_audio(no default), andeval_false_positive(no default). Pass0.0to disable. - One retraining attempt (Phase 5.6 with a pseudo-Morse empty-sample mode at
empty_sample_probability=0.40) made the digit-on-noise failure worse (62 % β 94 %). Kept in the codebase for reference (commit fa2b461) but not the v0.5.2 path. - LM fusion (
greedy_rnnt_decode_with_lm) does not yet propagatedigit_threshold. Fusion + digit gating is a small plumbing pass for a follow-up.
Caveats
- The cost of
digit_threshold=0.90on the v0.5.1 acoustic: β1 word out of 30 on thebench_word_gap6Γ inter-word-silence bench (27 β 26), and β3 % relative legitimate digit recall on a 10-phrase synthetic ham-radio set (RST 599, callsigns, QTH). - The threshold is a fixed scalar β context-aware gating (allow digits if recently inside an RST / callsign emission) is a future enhancement.
- ILME / density-ratio fusion remains catastrophic; do not pass
--ilm-weight > 0.
What's new in v0.5.1
The v0.5.0 live test surfaced one remaining failure mode: when several seconds of silence sat between two words, the model occasionally fused them or emitted spurious characters during the silence. Diagnosis: the synthetic training distribution had only ever shown the canonical 7-dit Farnsworth gap (~0.42 s at 20 WPM), so longer silences were out-of-domain. The acoustic head, asked to decode something it had never seen, behaved as if every silence longer than 7 dits was the start of a new emission.
rnnt_phase5_5.ptβ new recommended acoustic. Bootstrap chain:phase3_5/best β phase5_3/best β phase5_4/last β phase5_5/last.- Phase 5.5 (15 k steps) introduces one new
OperatorConfigknob:word_gap_inflation, a per-utterance multiplier on every Farnsworth inter-word gap, sampled fromU(1.0, 8.0). At 20 WPM this stretches the canonical 7-dit space up to56 dits (3.4 s of silence between two words). All other knobs (jitter, dash:dot ratio, gap inflation, channel, text mix, empty-sample prior) are kept identical to Phase 5.3 / 5.4 β strict ablation.
- Phase 5.5 (15 k steps) introduces one new
- The targeted bench (
scripts/bench_word_gap.py, 30 short word pairs at 25 WPM, SNR 20 dB):- At 6Γ inflation (~2 s of inter-word silence): word recall 8 / 30 β 27 / 30, CER 17.4 % β 1.5 %.
- At 4Γ inflation: word recall 20 / 30 β 28 / 30, CER 7.7 % β 0.8 %.
- At 1Γ / 2Γ: 30 / 30 word recall preserved, CER 0 %.
- Out-of-domain wins are preserved: Alice ebook2cw (n=120, scoreβ₯0.5, greedy) 18.82 % β 16.39 % CER. The hand-keyed
test_manu.wav(Wordsworth, threshold 0.6) drops from 26.5 % β 23.5 % CER (-11 % relative). - False positives stay at zero in
decode_livemode (threshold 0.6: 0.00 β 0.01 chars/sample). Without the threshold, Phase 5.5 emits ~1.97 chars per silence sample vs 0.28 for v0.5.0 β the v0.4.1+ confidence gate absorbs that entirely.
Caveats
- The single regression is on synthetic French at SNR = β5 dB (
overall CER 0.68 % β 1.12 %, with the deficit confined to the β5 dB row: 4.08 % β 6.71 %). Clean-to-0-dB FR remains 0 % CER and Γ / Γ / ' precision is preserved at 100 / 100 / 100 % (recall 100 / 100 / 98.3 %). - The synthetic validation set saturates at ~0 % CER for the 5.5 acoustic (a known dormant
ValidationConfig.matching()bug since Phase 4.0b β operator ranges are not propagated to the val set). The real signal isbench_word_gap.py, the Alice prose bench, and the livetest_manuaudio.best_rnnt.ptincheckpoints/phase5_5/is therefore frozen at step 1000 by the saturated val signal βlast.ptis the released checkpoint. - ILME / density-ratio fusion is still catastrophic on this distribution; do not pass
--ilm-weight > 0.
What's new in v0.5.0
The motivating live test (2026-05-02) was a 7-minute hand-keyed test.wav with a mix of contest exchanges, random-character blocks, FAV22 clair extracts, and the Daffodils poem. v0.4.1 produced unreadable letter-soup on the keyed sections (F = ..-. decoded as A + V, repeated emissions like WWWVVUT TEST for IK3VUT TEST). Diagnosis: real human keying has dot/dash ratios and inter-element gaps outside the synthetic curriculum's envelope, and confidence-threshold + LM-fusion at inference time cannot compensate (the acoustic head is confident on its wrong interpretations).
rnnt_phase5_4.ptβ new recommended acoustic. Bootstrap chain:phase3_5/best β phase5_3/best β phase5_4/last.- Phase 5.3 (15 k steps) widens the synthetic operator envelope: element-jitter
(0, 0.30)(was(0, 0.15)), gap-jitter(0, 0.50)(was(0, 0.25)), and two newOperatorConfigknobs sampled per utterance βdash_dot_ratio β U(2.5, 4.5)(was a fixed3.0) andgap_inflation β U(0.8, 1.6)(multiplicative on inter-element gaps, was identity). The synthesis stack is otherwise identical to Phase 3.5. - Phase 5.4 (12 k steps) mixes 30 % of every batch from a real-audio JSONL of 42 ground-truth-aligned 6 s chunks of human-keyed CW (the
test.wavcorpus, aligned viascripts/align_ebook_cw.pyagainst a hand-written transcript). The synthetic 70 % keeps the Phase 5.3 wider-envelope curriculum so the model does not regress.
- Phase 5.3 (15 k steps) widens the synthetic operator envelope: element-jitter
- The wider-jitter alone (Phase 5.3) was insufficient: byte-for-byte identical decode to v0.4.1 on the keyed audio. The real-audio prior in Phase 5.4 was the unblock.
- The improvement is not localised: out-of-domain Alice ebook2cw prose (n=120, scoreβ₯0.7) drops from 36.44 % CER β 19.16 % (greedy) and 32.30 % β 16.59 % (with fusion Ξ»=0.7). Synthetic FR-mix samples drop from 50.0 % β 12.5 %. FP bench (threshold 0.6 + fusion 0.7): 0 / 150 (was 0.17 mean in v0.4.1).
scripts/train_rnnt.pylearns--curriculum phase5_3,--real-audio-jsonl,--real-audio-probability, and--real-audio-score-thresholdfor reproducing v0.5.0.- LM fusion is still offline only; streaming-decoder integration is parked for a follow-up.
Caveats
- The 42 real-audio chunks are from a single recording session; that audio is in-distribution for v0.5.0. Out-of-domain validation lives in the Alice and synthetic FR benches above.
- ILME / density-ratio fusion is still catastrophic on this distribution; do not pass
--ilm-weight > 0.
What's new in v0.4.1
lm_phase5_2.ptβ new 4.76 M-param 49-vocab character-level LM, trained onPHASE_3_4_MIX(the same text mix the Phase 3.5 acoustic model saw: callsigns 10 %, Q-codes 12 %, QSO 20 %, numerics 12 %, English-words 4 %, random 18 %, multilingual prose 8 %, French-only prose 16 %). 20 k steps, val_ppl 5.626, EMA 0.999. Replaceslm_phase4_0.ptas the recommended fusion LM β the legacy v0.1 LM was trained on 100 % ham-radio mix (no prose) and is hostile to general prose decoding (PPL 9 on QSO vs 100-200 on Alice). Pair with the Phase 3.5 RNN-T at Ξ» = 0.7 (sweep-tuned).- Streaming false-positive gate:
decode_live.py --confidence-thresholddefault is now0.6. On the 150-sample 3-mode noise bench: 1.50 β 0.17 mean characters per noise sample (β90 %). No measurable accuracy loss down to β5 dB SNR. Pass--confidence-threshold 0.0to recover v0.4.0 behaviour. scripts/decode_audio.pylearns LM fusion: new--lm-ckpt,--fusion-weight,--confidence-thresholdflags. Confidence gating is applied to the acoustic softmax pre-LM so threshold and fusion stack cleanly (the LM cannot rescue noise-driven low-confidence emissions).- Autonomous prose validation pipeline:
data/real/aligned/all_alice.jsonl(9482 ground-truthed ebook2cw chunks from Alice in Wonderland chapters 1β12) is now the project's prose bench, paired withscripts/eval_fusion_realaudio.py. The v0.4.1 fusion verdict was reached without a live IC-7300 test. scripts/train_lm.pylearns text mixes: new--mixand--vocab-sizeflags so future LMs can be matched to whatever curriculum the acoustic model was trained on.
What's new in v0.4.0
rnnt_phase3_5.ptβ 49-vocab acoustic model fine-tuned from a 46β49 vocabulary extension of Phase 3.3. Two-stage curriculum:- Phase 3.4 (16 k steps from extended Phase 3.3 init):
PHASE_3_4_MIXadds a 24 % prose slice (8 % multilingual + 16 % French-only) so the freshly initialised Γ / Γ / apostrophe vocab rows see enough French gradient. - Phase 3.5 (16 k steps from Phase 3.4 last): same mix and channel, operator-jitter widened from (0, 0.08) element / (0, 0.15) gap to (0, 0.15) / (0, 0.25). Cures the false-positive emissions of Γ / Γ on tight
W + vowelpatterns and on hand-keyed timing at the upper edge of the Phase 3.4 jitter envelope.
- Phase 3.4 (16 k steps from extended Phase 3.3 init):
- Tokenizer extension 46 β 49. New tokens:
Γ(Morse..-.., index 46),Γ(.--.-, index 47),'(.----., index 48). Old 46-vocab Phase 3.0 / 3.2 / 3.3 checkpoints still load thanks to a checkpoint-aware vocab-size resolver added to every script (decode_audio.py,decode_live.py,eval_*.py,test_release.py). - FAV22 corpus parsed. The 43-page F9TM training PDF (102 codΓ© + 101 clair blocks) is now extracted to
data/corpus/fav22_blocks.jsonl(110 k chars, 3.62 % accent density in clair). Used by Phase 3.5+ as authentic French CW prose for theprose_frsampler. The audio companion (HST 84-240 WPM) is too out-of-distribution for the current 16-28 WPM training range β alignment pipeline written but parked. - Real-audio fine-tune from v0.6.4 β
rnnt_phase11bmixes 20 % real-audio chunks (hand-keyed ragchew, forced-aligned for per-token timestamps) into the synthetic curriculum. Drives the -34 % real-OTA mean-CER gain. Pre-v0.6.4 releases (incl. v0.5.4rnnt_phase5_5) were synthetic-only.
Model artifacts
| file | params | vocab | description | recommended |
|---|---|---|---|---|
rnnt_phase11b.pt |
4.13 M | 49 | v0.6.4 recommended acoustic β Phase 11b forced-alignment-aware real-audio fine-tune from phase5_5/best_rnnt. 20 % real-audio mix (g3ses force-aligned), word-gap aug U(1.5, 6.0). Real-OTA bench: -34 % mean CER, -37 % mean WER vs rnnt_phase5_5. Passes release_gate_v2 10/10. Pair with --confidence-threshold 0.6 and --digit-threshold 0.90 for the shipped live behaviour. |
β |
lm_amateur_3gram.pkl |
β | 49 | v0.6.4 splitter LM β pure-Python char 3-gram with stupid-backoff smoothing, trained on 100k samples from the Phase 9 mix. 482 KB. Used by morseformer.decoding.word_splitter to rescore candidate splits in the prose preset. Auto-loaded from release/ β checkpoints/ β HF Hub. |
β |
rnnt_phase5_5.pt |
4.13 M | 49 | v0.5.1 β v0.6.3 acoustic β Phase 5.5 long inter-word-silence curriculum on top of the Phase 5.4 real-audio mix. Demoted at v0.6.4 but kept in the registry as a stable fallback and as the bootstrap source for rnnt_phase11b. |
|
lm_phase5_2.pt |
4.76 M | 49 | v0.4.1 β v0.6.3 neural LM β matched to the Phase 3.5 text mix (multilingual prose + ham radio). val_ppl 5.626. Dropped from the default prose preset at v0.6.3 because it hurt amateur jargon on literary prose. Still loadable via --lm lm_phase5_2 --fusion-weight 0.7 for research. |
|
rnnt_phase5_9.pt |
4.13 M | 49 | Phase 5.9 failed retrain β strict letter-group densification. Regressed on 6 / 6 LCWO + websdr clips, including its target websdr clip. Kept for reproducibility. | |
rnnt_phase5_8.pt |
4.13 M | 49 | v0.6.0 / v0.6.1 acoustic β English-literary curriculum. Demoted at v0.6.2 after the real-audio bench lost ~24 % relative mean CER vs rnnt_phase5_5. |
|
rnnt_phase5_7.pt |
4.13 M | 49 | v0.5.3 acoustic β amateur-idiom curriculum (5NN cut-numbers + run-on UR/SK/KN/BK). Kept for diff. |
|
rnnt_phase5_4.pt |
4.13 M | 49 | v0.5.0 acoustic β Phase 5.3 (wider jitter + dash:dot ratio) + Phase 5.4 (30 % real-audio mix). Kept for diff. | |
rnnt_phase3_5.pt |
4.13 M | 49 | v0.4.0 / v0.4.1 acoustic β synthetic-only. Kept for diff. | |
lm_phase4_0.pt |
4.76 M | 46 | Legacy v0.1-era LM, 100 % ham-radio text mix. Kept for research / reproducibility; not recommended for fusion. | |
rnnt_phase3_3.pt |
4.13 M | 46 | v0.3 acoustic model β multilingual ASCII-normalised prose. No accent tokens. | |
rnnt_phase3_2.pt |
4.13 M | 46 | v0.2 acoustic model β anti-hallucination curriculum, no multilingual data. | |
rnnt_phase3_0.pt |
4.13 M | 46 | v0.1 acoustic model β AWGN-only training. |
Intended use
- Primary: decoding Morse-code audio (8 kHz sample rate, carrier around 600 Hz, 500 Hz RX bandwidth) into text. Targeted at amateur-radio receivers and SDR applications. v0.4 has been live-validated on a real IC-7300 on a mixed-language stream (CQ macros + Verlaine + Poe + Nerval + FAV22 clair) and decodes French prose with diacritics (e.g.
L'HEURE,MA SEULE ΓTOILE EST MORTE,Γ LA TOUR ABOLIE) where v0.3 collapsed apostrophes to1and stripped Γ / Γ toE/A. - Secondary (research): a reference implementation for RNN-T and LM-fusion research on a small-vocabulary acoustic task with a fully reproducible synthetic data pipeline.
Limitations
Honest about what v0.6.3 does and does not do:
- Real-audio coverage is still narrow. The recommended acoustic includes a small real-audio mix from one hand-keyed recording session, but it is not trained on a broad, diverse amateur-radio corpus. More operators, receivers, bands, speeds, and propagation conditions are the main data gap.
- Only three accented characters are supported (Γ, Γ, apostrophe). The 49-token vocabulary covers the French tokens already added in Phase 3.4; other diacritics still fall back to their ASCII base letter via NFKD (e.g.
chΓ’teauβCHATEAU). - 6-second model context. Training clips are 6 s. Offline and live decoders keep inference on that distribution with sliding 6 s windows and central-zone commits.
- WPM range. The release models target normal CW speeds around 16-28 WPM. Very high-speed HST / FAV22 audio (84-240 WPM) is out-of-distribution.
- Pile-ups and overlapping stations are not solved. Multi-source CW at the same or nearby frequency is a source-separation problem, not just an acoustic-decoding problem.
- LM fusion is offline only.
morseformer decode --preset prosecan uselm_phase5_2;morseformer liveruns acoustic-only even when a preset names an LM. - Beam search / callsign priors are experimental. Scaffolding exists in the codebase, but the shipped presets use the validated greedy RNN-T path.
How to use
Offline decode of a .wav file
pip install morseformer
morseformer decode my_recording.wav
# Optional LM shallow fusion for prose / ragchew recordings.
morseformer decode my_recording.wav --preset prose
# Inspect or pre-download model artifacts.
morseformer models list
morseformer models download rnnt_phase5_5
Real-time streaming decode
pip install "morseformer[live]"
morseformer live
Tune your receiver to zero-beat at 600 Hz with a roughly 500 Hz CW filter. Ctrl+C to quit. Latency is about 4 s end-to-end. The shipped live preset uses rnnt_phase5_5.pt, --confidence-threshold 0.6, and --digit-threshold 0.90 to suppress noise-driven false positives and pseudo-numerals.
Training data
Most training audio is synthetic, generated on the fly by the morseformer.data.synthetic package. The current recommended acoustic (rnnt_phase5_5.pt) descends from:
- Phase 3.4 / 3.5 text mix: callsigns, Q-codes, QSO templates, numerics, English words, random characters, multilingual prose, and French prose with Γ / Γ / apostrophe preserved.
- Realistic HF channel: AWGN, QSB, QRN, carrier-frequency jitter, drift, QRM, and a 500 Hz receiver bandpass around 600 Hz.
- Human-keying envelope: widened element and gap jitter, dash:dot ratio variation, inter-element gap inflation, and long inter-word-silence inflation.
- Phase 5.4 real-audio mix: a small set of hand-keyed, ground-truth-aligned 6 s chunks mixed into training.
- Phase 5.5 long word gaps:
word_gap_inflation β U(1.0, 8.0), which is the acoustic checkpoint now recommended again in v0.6.3.
The French prose corpus comes from Project Gutenberg (data/corpus/prose.txt, gitignored, reproducible via python data/corpus/fetch.py). The FAV22 reference clair blocks (data/corpus/fav22_blocks.jsonl) extracted from the F9TM training PDF supply ~110 k chars of authentic French CW text (3.62 % accent density), used by Phase 3.5+ as additional prose_fr source.
The real-audio component is useful but narrow; expanding it is the highest-value data improvement for future releases.
Evaluation
French accent-rich bench (historical v0.4 headline)
300 French-prose samples sampled around Γ / Γ / apostrophe positions (β₯ 70 % contain at least one new token), evaluated through scripts/eval_phase_3_4_french.py:
| Metric | v0.3 (Phase 3.3, 46-vocab) | v0.4 (Phase 3.5, 49-vocab) |
|---|---|---|
| Overall CER | n/a (no accent tokens) | 6.46 % |
| CER at clean / +20 / +10 / +5 dB | n/a | 0.00 % |
| CER at 0 dB | n/a | 4.99 % |
| CER at β5 dB | n/a | 33.77 % |
| Γ precision / recall | 0 % / 0 % | 100 % / 91.6 % |
| Γ precision / recall | 0 % / 0 % | 97.8 % / 95.7 % |
| apostrophe precision / recall | 0 % / 0 % (' decoded as 1) |
98.4 % / 98.4 % |
Realistic-channel SNR ladder (historical v0.3 reference numbers retained)
1200 samples (40 / WPM Γ 5 WPM Γ 6 SNR), full Phase 3.1 channel stack. v0.4 is at parity or better with v0.3 across the ladder; the bench was not re-run on Phase 3.5 because the curriculum changes only affect text mix and operator jitter β the channel stack is unchanged.
| SNR (dB) | v0.1 (Phase 3.0) CER | v0.2 (Phase 3.2 last) CER | v0.3 (Phase 3.3 best) CER | Ξ vs v0.2 |
|---|---|---|---|---|
| +20 | 0.2421 | 0.0017 | 0.0000 | β0.17 pp |
| +10 | 0.3816 | 0.0000 | 0.0000 | 0.00 pp |
| +5 | 0.3361 | 0.0000 | 0.0000 | 0.00 pp |
| 0 | 0.5000 | 0.0030 | 0.0030 | 0.00 pp |
| β5 | 0.7871 | 0.0542 | 0.0502 | β0.40 pp |
| β10 | 1.3316 | 0.4442 | 0.3965 | β4.77 pp |
| overall | 0.5964 | 0.0838 | 0.0749 | β0.89 pp |
AWGN guard ladder (historical no-channel regression check)
1200 samples, same WPM Γ SNR grid, AWGN only:
| SNR (dB) | v0.1 CER | v0.2 (Phase 3.2 last) CER | v0.3 (Phase 3.3 best) CER | Ξ vs v0.2 |
|---|---|---|---|---|
| +20 | 0.0000 | 0.0000 | 0.0000 | 0.00 pp |
| +10 | 0.0000 | 0.0000 | 0.0000 | 0.00 pp |
| +5 | 0.0000 | 0.0000 | 0.0000 | 0.00 pp |
| 0 | 0.0000 | 0.0229 | 0.0099 | β1.30 pp |
| β5 | 0.0407 | 0.3348 | 0.2943 | β4.05 pp |
| β10 | 0.8019 | 0.8919 | 0.8826 | β0.92 pp |
| overall | β | 0.2083 | 0.1978 | β1.05 pp |
The β5 / β10 dB AWGN regression v0.2 introduced (in exchange for the anti-hallucination prior) is partly recovered in v0.3 β the multilingual prose appears to make the prior less brittle on noise-shaped inputs.
False-positive bench (noise-only audio, 150 samples Γ 3 modes β empty-label target)
| metric | v0.1 (Phase 3.1 best) | v0.2 (Phase 3.2 last) | v0.3 (Phase 3.3 best) |
|---|---|---|---|
| Mean characters emitted | 11.17 | 1.05 | 1.01 |
| Median | 11.0 | 1.0 | 1.0 |
| Max | 21 | 2 | 2 |
| % "letter-soup" (>5 chars) | 98.7 % | 0.0 % | 0.0 % |
Historical v0.4.1 false-positive grid (Phase 3.5 acoustic, threshold Γ fusion)
scripts/eval_false_positive.py --candidate-ckpt rnnt_phase3_5 --lm-ckpt lm_phase5_2 --fusion-weight Ξ»_lm --confidence-threshold thr, 50 samples / mode Γ 3 modes:
confidence_threshold |
fusion_weight (Ξ»_lm) |
mean chars / noise | max | % > 5 chars |
|---|---|---|---|---|
| 0.0 | 0.0 | 1.50 | 2 | 0.0 % |
| 0.0 | 0.7 | 1.01 | 2 | 0.0 % |
| 0.6 | 0.0 | 0.17 | 1 | 0.0 % |
| 0.6 | 0.7 | 0.17 | 1 | 0.0 % |
Threshold 0.6 (the v0.4.1 streaming default) cuts the FP mean by β 90 %. Adding fusion Ξ» = 0.7 on top does not regress FP because gating is applied to the acoustic head pre-LM β the LM cannot rescue a low-confidence noise emission. Threshold and fusion stack cleanly.
Historical v0.4.1 LM rescoring on prose audio (Alice in Wonderland CW, ground-truth aligned)
scripts/eval_fusion_realaudio.py --rnnt-ckpt phase3_5 --lm-ckpt lm_phase5_2 --jsonl all_alice.jsonl, n = 120, score-filter β₯ 0.7 to focus on chunks where the baseline still has CER headroom:
| Ξ»_lm (shallow fusion) | CER | Ξ vs baseline |
|---|---|---|
| 0.0 (baseline, greedy) | 36.44 % | β |
| 0.3 | 35.81 % | β1.7 % |
| 0.5 | 33.46 % | β8.2 % |
| 0.7 | 32.30 % | β11.4 % β peak |
| 1.0 | 33.66 % | β7.6 % |
The Alice corpus is ebook2cw renderings of public-domain English prose (chapters 1β12 of Alice in Wonderland), char-aligned by scripts/align_ebook_cw.py against the source text β a corpus that was prepared for Phase 3.7 real-audio finetuning and re-purposed here as v0.4.1's autonomous prose bench. The 9482 chunks are deterministic ebook2cw audio, not real-radio captures, but they exercise the model on real linguistic structure (story narrative) where the v0.4.0 acoustic still has headroom and the LM can contribute.
ILME (density-ratio fusion, Ξ»_ilm > 0) is catastrophically harmful on this distribution: Ξ»_lm = Ξ»_ilm = 0.2 β CER +18 %, Ξ»_lm = Ξ»_ilm = 0.3 β CER +83 %. The fusion code keeps ILME for research / completeness; it is not the recommended path.
Why the legacy LM (lm_phase4_0) does not help
PPL of lm_phase4_0.pt (the v0.1 LM, trained on DEFAULT_MIX = 100 % ham radio) on representative texts:
| text | PPL |
|---|---|
| Alice prose ("ALICE WAS BEGINNING TO GET VERY TIRED OF SITTING β¦") | 99.5 |
| Alice prose ("DOWN THE RABBIT HOLE INTO A LARGE RABBIT HOLE") | 213.9 |
| QSO line ("CQ CQ CQ DE F4ABC F4ABC PSE K") | 8.9 |
| RST exchange ("UR RST 599 599 NAME TOM TOM 73") | 15.4 |
The legacy LM is calibrated for ham radio and is hostile to general prose decoding β fusion with it on Alice audio actively hurts CER (33.86 % β 35.79 % at Ξ»_lm = 0.2 in our re-bench), confirming the Phase 4.1 null-result was a text-distribution mismatch and not a fusion-method problem. lm_phase5_2.pt was trained on the same PHASE_3_4_MIX the Phase 3.5 acoustic saw, which makes shallow fusion finally useful.
Live validation
Selected live-radio tests on a real IC-7300 driving the streaming decoder:
- 2026-04-27 (v0.3 baseline): mixed CQ macros + Verlaine Chanson d'automne + Poe Annabel Lee + Apollinaire Le Pont Mirabeau. French rendered word-by-word but apostrophes appeared as
1and Γ / Γ as plainE/A. - 2026-04-29 (v0.4): same setup with FAV22 clair + Nerval El Desdichado + Poe Annabel Lee. French accents are now decoded natively:
L'HEURE,L'AUTOMNE,MA SEULE ΓTOILE EST MORTE,Γ LA TOUR ABOLIE. Apostrophes captured cleanly. No false-positive Γ / Γ emissions in the English passages.
Residual failure modes observed across later releases include hard callsigns under noise and jitter, occasional prosign over-emission on pile-ups, and overlapping stations at the same frequency.
Environmental impact
Training budget through v0.6.x is small by modern ASR standards: single-GPU fine-tunes on an RTX 3060 Laptop, typically a few hours per phase. The current recommended acoustic is still a compact 4.13 M-param model intended for CPU inference.
Technical specifications
Acoustic model (rnnt_phase5_5.pt, 4.13 M params):
- Encoder: Conformer d=144, L=8, H=4, ff_expansion=4, conv_kernel=31, RoPE, LayerNorm conv, 4Γ subsample
- CTC head: Linear(144 β 49)
- PredictionNetwork: Embedding(49, 128) + LSTM(128, 128, 1 layer)
- JointNetwork: Linear(144 β 256) + Linear(128 β 256) + tanh + Linear(256 β 49)
Language model (lm_phase5_2.pt, 4.76 M params): decoder-only GPT (RMSNorm + SwiGLU + RoPE + tied embeddings + causal SDPA), d=256, L=6, H=4, dropout=0.1, 49-vocab. Trained 20 k steps on PHASE_3_4_MIX, AdamW peak_lr 3e-4, batch 128, bf16, EMA 0.999, val_ppl 5.626. Pair with the RNN-T at Ξ» = 0.7 for offline prose decoding. The legacy lm_phase4_0.pt (46-vocab, 100 % ham-radio mix, val_ppl 3.75 on its own distribution) is kept for research but is not the recommended fusion partner.
Vocabulary: 49 tokens β blank (index 0), 26 uppercase letters AβZ, 10 digits 0β9, 9 punctuation / Morse prosigns (., ,, ?, !, /, -, =, +, space), plus Γ, Γ, '.
Front-end: complex bandpass around the carrier (default 600 Hz Β± 250 Hz), magnitude, 4Γ downsample to a 500 Hz frame rate, scalar normalization. Input shape [B, T, 1].
Citation
@software{morseformer_v0_6_3_2026,
author = {Derhy, Serge},
title = {morseformer: open-source transformer-based Morse / CW decoder},
year = 2026,
version = {v0.6.3},
url = {https://github.com/sderhy/morseformer},
howpublished = {\url{https://huggingface.co/sderhy/morseformer}},
}
Acknowledgements
- SΓ©bastien Derhy β design, engineering, and on-air validation of morseformer.
- Mauri Niininen (AG1LE) β pioneering ML-based CW decoding work.
- Alex Shovkoplyas (VE3NEA) β CW Skimmer, the commercial reference.
- Andrej Karpathy β
nanoGPT, the aesthetic reference for the language model. - Project Gutenberg β public-domain literary texts in English, French, German, and Spanish used to build the multilingual prose corpus.
- F9TM (Centre de Formation MΓ©morial du REF) β FAV22 reference training material, used to source authentic French CW prose for the Phase 3.5
prose_frsampler. - The amateur-radio community β decades of publicly available CW recordings and transcripts.
73 de morseformer.
Evaluation results
- mean CER (prose preset) on morseformer real-OTA bench (g3ses + g6pz, 26 clips, 31 min)self-reported0.177
- mean WER (prose preset) on morseformer real-OTA bench (g3ses + g6pz, 26 clips, 31 min)self-reported0.443