hey_fable β openWakeWord model
A custom openWakeWord wake-word detection model for the phrase "hey fable".
Trained on synthetic speech from two complementary TTS engines:
- Piper (
en_US-libritts_r-medium, 850 speakers) β bulk timbre and prosody coverage - Kokoro-82M (27 expressive American + British voices) β natural, expressive read speech that Piper cannot cover
On top of the modal-voice clips we add a whisperized subset (STFT phase-randomisation of the modal clips) so the model fires on whispered "hey fable" even in noisy environments. Every clip is augmented with room impulse responses (MIT IR Survey), ESC-50 background mixing at SNRs from β10 to +15 dB, pitch shift, band-stop filtering, seven-band EQ, tanh distortion, and coloured noise, on top of Google's frozen speech-embedding backbone that ships with openwakeword.
Metrics
End-to-end streaming, 80 ms frames, 1200 held-out modal positives + 300 held-out whispered positives + 1200 held-out adversarial negatives, plus the 10.7-hour general-speech FP corpus that ships with the base openwakeword package.
| Threshold | Recall (modal) |
Recall (whisper) |
Recall (Kokoro voices) |
Adversarial FPR |
FP / hour (10.7 h corpus) |
|---|---|---|---|---|---|
| 0.5 | 99.6 % | 98.3 % | 99.5 % | 17.2 % | 1.40 |
| 0.7 | 99.6 % | 97.3 % | 99.5 % | 16.8 % | 1.03 |
| 0.9 | 99.5 % | 97.3 % | 99.5 % | 16.3 % | 0.84 β recommended |
- Recall (modal) β synthetic modal-voice held-out positives from Piper + Kokoro.
- Recall (whisper) β whisperized versions of held-out positives (STFT phase-randomisation).
- Recall (Kokoro voices) β subset of the modal set from 27 expressive Kokoro voices.
- Adversarial FPR β fraction of 1200 held-out adversarial clips ("hey table", "hey stable", "hey cable", "hey Google", "hey Siri", "a fable", "the stable", "vegetable", "comfortable", β¦) that fire. Intentionally hard: those phrases really do sound like "hey fable".
- FP / hour β false fires per hour on the 10.7-hour openWakeWord validation corpus (mixed real-world speech, music, noise).
Quick start
import openwakeword
from openwakeword.model import Model
# Download the shared melspectrogram + embedding backbone (~4 MB, one-off)
openwakeword.utils.download_models()
model = Model(
wakeword_models=["hey_fable.onnx"],
inference_framework="onnx",
)
# Feed 16 kHz mono 16-bit PCM audio in 80 ms frames (1280 samples)
prediction = model.predict(frame)
if prediction["hey_fable"] > 0.9: # recommended threshold
print("wake!")
Files
| File | Purpose |
|---|---|
hey_fable.onnx |
The classifier head. Input [1, 16, 96] embeddings, single sigmoid output. |
negative_phrases.txt |
Weighted adversarial phrases used to generate hard negatives. Format <count>\t<phrase>. |
hey_fable_config.yaml |
Training configuration. |
metrics.json |
Full evaluation metrics. |
Training data
- Positives β 7500 training / 1700 held-out. 4000 Piper (850 speakers Γ 5 length-scales Γ 4 noise-scales Γ 3 noise-scale-ws), 2000 Kokoro (27 voices Γ 5 speaking speeds Γ 10 prosody prompts), 1500 whisperized (STFT phase-randomised versions of the modal clips).
- Hard negatives β 5500 training / 1200 held-out synthetic clips of phonetically similar phrases. The phrase file is weighted (
<count>\t<phrase>lines): "hey table", "hey stable", "hey cable" each get 20Γ oversampling; the /-eΙͺbΙl/ rhyme family ("hey label", "hey Mabel", "hey sable", "hey Abel", "hey fabled", "hey fabric", "hey Fabio", β¦) gets 10Γ; syllable-substitution neighbours ("gay stable", "say fable", "weigh stable", "prey fable", β¦) get 5Γ; determiner + noun ("a fable", "the stable", "away table", β¦) get 3Γ; polysyllabic-ablewords ("comfortable", "notable", "capable", β¦) get 2Γ. Kokoro also synthesises these to give the negatives expressive prosody. - Background negatives β 2000 hours of pre-computed openWakeWord features from ACAV100M (speech + noise + music) via davidscripka/openwakeword_features.
- False-positive validation β the 10.7-hour held-out corpus from the same dataset.
Data augmentation
Every positive and negative clip runs through the openWakeWord augmentation pipeline 3 rounds (22 500 augmented positives + 16 500 augmented negatives), with augmentation probabilities pushed above defaults for whisper-in-noise robustness:
- Background mixing at SNR β10 to +15 dB β p = 0.9.
- Room impulse responses from the MIT Environmental IR survey (270 rooms) β p = 0.75.
- Coloured noise at SNR 10-30 dB β p = 0.5.
- Pitch shift Β±3 semitones β p = 0.35.
- Seven-band parametric EQ Β±6 dB β p = 0.35.
- Band-stop filter β p = 0.35.
- Tanh distortion β p = 0.30.
Model architecture
- Input: 16 Γ 96 melspectrogram embeddings from the shared openWakeWord Google speech-embedding backbone (frozen, ships with
openwakeword). - Head: fully-connected DNN, layer width 32, LayerNorm + ReLU, single sigmoid output.
- ~15 000 parameters β faster than real-time on a Raspberry Pi.
Training used openwakeword.train.Model.auto_train with a cosine LR schedule + warmup, three sequences (75 000 + 7 500 + 7 500 steps), adaptive negative-example weighting up to 8000Γ (raised on demand to hit target FP rate), stochastic-weight-averaging over the top-decile checkpoints, and a rebalanced batch (ACAV100M:1024 / adversarial:600 / positive:300).
Recommended threshold
0.9 β the highest-value operating point on this model:
- 99.5 % recall on modal held-out positives.
- 97.3 % recall on whispered held-out positives.
- 0.84 FP/hour on the 10.7-hour openWakeWord validation corpus.
Bumping to 0.5 buys a tiny amount of recall (99.6 % vs 99.5 %) at the cost of 1.7Γ more false positives per hour.
Limitations
- English only. Trained purely on synthetic speech; real-user voices are not represented explicitly.
- Adversarial phrases (
hey table,hey cable) still fire ~16 % of the time even at the highest threshold β this is a phonetic-similarity ceiling, not a training failure. Real users don't randomly emit those phrases. - Small classifier head is not designed to distinguish speakers.
- Whisper detection works because the model was trained on whisperized clips; extreme background noise (SNR β€ β10 dB) will still miss most whispers.
License
Apache-2.0 for code. The model weights inherit the base openWakeWord backbone's CC-BY-NC-SA-4.0 because they were derived using Google's speech-embedding model under those terms.
Credit
- dscripka/openWakeWord β training framework and shared embedding backbone.
- rhasspy/piper-sample-generator β bulk synthetic speech.
- hexgrad/Kokoro-82M β expressive synthetic speech (Apache-2.0, 27 English voices).
- MIT Environmental Impulse Responses β reverberation augmentation.
- ESC-50 β environmental background sounds.
- Google's speech-embedding backbone via TFHub.