hey-claude β€” an openWakeWord wake word

Wakes on "hey Claude". Runs on a CPU, about 200 KB, no GPU and no network.

Trained entirely on synthetic speech on one MacBook Air (M2) β€” no Colab, no recordings of anyone's voice, roughly an hour end to end.

from openwakeword.model import Model

model = Model(wakeword_models=["hey_claude.onnx"], inference_framework="onnx")

# Feed 1280-sample (80 ms) chunks of 16 kHz mono int16 audio.
for chunk in microphone_chunks():
    if model.predict(chunk)["hey_claude"] > 0.5:
        print("woke up")

How well does it work?

Scored against macOS say voices, which are not the voices it trained on, five voices per phrase (Samantha, Daniel, Karen, Moira, Fred). Each clip is padded with silence and run through a freshly constructed Model; the number is the peak score anywhere in the clip.

An empty room, 180 s on a real microphone: worst score 0.0011, zero false wakes. This is the test that matters most for an always-listening device and the easiest to skip β€” see the warning further down.

Phrase Peak
"hey claude" 0.995 on 2 of 5 voices wakes
"hey cloud" 0.004 quiet
"hey claire" 0.000 quiet
"okay cloud" 0.001 quiet
"grey cloud" 0.000 quiet
"hey jarvis" 0.002 quiet
"what is the weather today" 0.000 quiet
"can you play some music please" 0.000 quiet
"I think the cloud is going to rain" 0.000 quiet
"let us go outside and play" 0.001 quiet
"he clawed at the door" 0.011 quiet (a homophone β€” got lucky)
"hey clyde" 0.991 false wake

On held-back clips it never trained on: 90% of positives wake it, 3% of adversarial negatives falsely do.

It has been used successfully with a real human voice, but every number in this table comes from synthesized speech. Waking reliably and staying quiet pull against each other here: a variant tuned for higher recall (98% held-out, 4 of 5 voices) scored 0.998 on "hey clyde", which no threshold can separate from the real phrase. This one trades some recall for a margin you can actually tune.

Limitations β€” please read before shipping this

It wakes on "hey Clyde" at 0.991 β€” as hard as on the real phrase, so no threshold separates them. K L AY D against Claude's K L AO D is a single vowel. If someone in the room is called Clyde, this model is not for you.

It is on the strict side. Of five macOS say voices, two wake it at ~0.99 and three score low. Say the phrase clearly, and lower the threshold to 0.3 before assuming it's broken. This is a deliberate trade: a more sensitive variant reached 4 of 5 voices but woke on an empty room.

It wakes on "clawed" (0.676). "Clawed" is K L AO D in CMUdict β€” the same phonemes as "Claude". This is not fixable by training. Any wake word has this problem with its own homophones; "Claude" happens to have a common one.

Silence was the hardest problem here, and it is worth understanding why. An earlier version of this model scored up to 0.99 on an empty room and fired about 4,000 times an hour. Every clip it had trained on contained audible sound, so near-silence fell outside its experience entirely and its output there was arbitrary. Padding test clips with np.zeros did not catch it β€” digital zero is not what a microphone produces. A quiet room is 1-50 RMS of preamp hiss, mains hum and fan rumble, punctuated by transients.

Two things fixed it: quiet-room clips among the negatives (including knocks, clicks and creaks), and training on 27.8% of openWakeWord's negative-features dataset rather than 4.7%. Synthetic noise alone only got from 4,000 to 90 false wakes an hour; real-world audio took it to zero.

Still uncharacterised: hours of television or music. 180 s of quiet is not the same as an evening of background noise.

English only, and the positive voices skew US/UK.

Training

100% synthetic. Piper says the phrase thousands of times across many speakers; those are the positives. Phonetically similar phrases are the adversarial negatives, mixed with openWakeWord's pre-computed negative features, real MIT room impulse responses, and background noise.

Base openWakeWord's frozen audio embedding + a 32-unit DNN head
Positives 2,000 train / 400 test, 11 Piper voices (~1,030 speakers, 4 corpora)
Negatives 7,600 train / 1,520 test β€” 111 phrases plus 1,600 quiet-room clips
Negative features 1,564,709 windows (a 27.8% slice of ACAV100M-2000h)
Steps 50,000
Hardware MacBook Air M2, CPU only, ~8 minutes

Positive clips are filtered by Whisper: any clip that doesn't clearly say the phrase is discarded, because a slurred positive teaches the wrong sound.

Three things that cost the most time

Recorded here because they're easy to repeat and none are obvious.

Don't put a homophone in the negatives. Listing "hey clawed" as something to reject asks the model to distinguish identical audio. It can't, so it degrades the wake word trying β€” held-out recall fell from 100% to 40%, and no amount of loss weighting recovered it.

Not every TTS voice can say your phrase. Measured pass rates for "hey claude": en_US-libritts_r 92%, but en_US-l2arctic 0%, en_GB-aru 0%, en_US-arctic 8%. Check before spending an hour generating.

Generate both classes from the same voices. Positives from eleven voices and negatives from three lets the model separate them by voice rather than by phrase β€” it scores beautifully on your own data and fails on real people.

Put silence in the negatives, and test against a real microphone. This cost more time than everything else combined. See the limitations above.

Test for minutes, not seconds. A 30 s silence test passed a model that a 120 s test then failed at 90 false wakes an hour. These are rare events; a short sample cannot see them.

Evaluating it

openWakeWord is a streaming detector and reset() does not clear its rolling audio buffer. Scoring several clips through one Model object lets each contaminate the next and quietly corrupts every number. Build a fresh Model per clip, pad with ~1 s of silence, and sanity-check the harness against the stock hey_jarvis model first β€” it should score ~0.999 on "hey jarvis". If it doesn't, the harness is wrong, not the model.

Reproducing

Full pipeline, including the Apple Silicon workarounds for openWakeWord's Linux/CUDA trainer: https://github.com/greg1232/hey-claude

python train/fetch_data.py --budget-gb 5
python train/generate_clips.py --phrase "hey claude" --out train/hey_claude \
    --count 2000 --count-test 400 \
    --count-negative 6000 --count-negative-test 1200 \
    --adversarial 60 --verify --workers 6
python train/train_local.py --training_config train/hey_claude.yml \
    --augment_clips --train_model
python train/test_silence.py train/hey_claude.onnx --seconds 180

The same scripts train any phrase β€” change --phrase and target_phrase.

Credits

Built on openWakeWord by David Scripka (Apache-2.0), whose trainer, embedding model, and negative-feature dataset this depends on entirely. Speech from Piper; voices derive from LibriTTS-R, VCTK, and others under their own licenses. Room impulse responses from the MIT Acoustical Reverberation Scene Statistics Survey.

Not affiliated with or endorsed by Anthropic.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support