Speech and Audio AI

Wake-word detection

A wake-word detector is a tiny model that listens all the time for one phrase, and its real difficulty is staying silent through hours of everything else.

On this page 9
  1. The short answer
  2. The analogy you have already lived
  3. Why it has to be this way
  4. The problem that makes this hard
  5. Why the phrase itself matters
  6. Where you have already used it
  7. What is honestly hard here
  8. Remember this
  9. What to learn next

One lesson, three depths. Pick the one that fits you today — you can switch any time.

Beginner — No maths. Plain English.

The short answer

A wake-word detector is a small model that listens continuously for one specific phrase and ignores everything else.

"Hey Google", "Alexa", "Hey Siri" — one phrase, working as the doorbell for a voice assistant.

The analogy you have already lived

You are asleep on a train. The carriage is loud — conversations, announcements, wheels on rails. You sleep through all of it.

Then someone says your name, quietly, and you are instantly awake.

Your brain did not process every conversation. Something cheap and always-on was listening for one pattern, and only that pattern was worth waking up for.

A wake-word detector is built on exactly this arrangement. Something tiny listens always. Something large wakes only when the tiny thing says so.

Why it has to be this way

A full speech recognition model is big and hungry. Running one continuously would flatten your phone battery in a couple of hours. It would also mean sending every sound in your home away to be processed.

So the job gets split.

   microphone, always on
        |
        v
  [ tiny model ]  a few tens of kilobytes, runs on the device, never stops
        |
        |  ...nothing... nothing... nothing...
        |
        v
   heard "Hey Google"?
        |
        v
  [ wake the big system ]  full recognition, maybe in the cloud
        |
        v
   "set a timer for ten minutes"

The tiny model has one job and one answer: yes or no. It does not know words. It cannot transcribe. It recognises one sound shape.

Nothing is recorded or sent until the tiny model fires. That is the privacy argument for this design, and it is a real one. It does rest on trusting that the device behaves as described.

The problem that makes this hard

The model is listening for hours. Say your device is on for twelve hours a day and the wake word takes half a second. It spends more than eighty-six thousand half-seconds a day not hearing the wake word.

Now suppose the detector is wrong one time in ten thousand. That sounds excellent. It also means your speaker lights up and starts listening around eight times a day, unprompted, while you watch television.

That is what makes wake words hard. Not recognising the phrase — staying quiet through everything that is not the phrase.

So the metric is unusual. Instead of accuracy, engineers measure false alarms per hour: how often it wakes when nobody called it. And separately, the miss rate: how often you say it and nothing happens.

You cannot minimise both. Make it eager and it interrupts you. Make it strict and you repeat yourself three times.

Why the phrase itself matters

Wake words are chosen, not found. Good ones share properties:

  • Long enough. Three or four syllables. "Alexa" and "Hey Google" are not short by accident.
  • Unusual. It should almost never occur in ordinary conversation. This is why "Hey" is attached to "Google" — plain "Google" comes up constantly.
  • Acoustically distinctive. A mix of vowels and consonants with varied pitch, so it does not look like common speech.

A short, common wake word is a design mistake that no amount of model quality repairs.

Where you have already used it

  • Smart speakers waiting for their name.
  • Phones answering "Hey Siri" with the screen off.
  • Cars listening for a voice command button-free.
  • Earbuds that respond to a spoken phrase.
  • Industrial and medical equipment operated hands-free.

What is honestly hard here

Names get triggered by television. An advertisement saying the wake word wakes devices in thousands of homes at once. This has genuinely happened.

It works less well for some people. Accents and voices under-represented in training data get higher miss rates. You say it three times, someone else says it once.

Children and quiet speakers struggle. Higher pitch and lower volume both move away from the training average.

The battery budget is brutal. This model runs forever. Every extra operation costs real battery. Wake-word models are among the smallest in production use, often tens of kilobytes. Many run on a dedicated low-power chip rather than the main processor.

Remember this

  • A tiny always-on model listens for one phrase, and wakes a large system only when it fires.
  • The metric is false alarms per hour alongside miss rate, not accuracy.
  • Choosing a long, unusual phrase does more for reliability than a better model does.

What to learn next

Developer — Code and libraries.

Setup

bash
pip install numpy librosa

CPU only, no downloads. We build ten minutes of noisy household audio, hide twenty wake words in it, run a streaming detector, and produce the table that decides whether a wake word is shippable.

Real detectors use a trained network. We use template matching, which is weaker — and that weakness is the point. It shows you the shape of the problem and exactly where a simple approach runs out.

Ten minutes of life, with twenty wake words in it

wake_word.py
import numpy as np, librosa

SR, HOP, N_MELS = 16000, 160, 24
rng = np.random.default_rng(0)

def tone(f, dur):
    t = np.arange(int(SR*dur))/SR
    return np.sin(2*np.pi*f*t) * np.hanning(t.size)

# The "wake word": a fixed four-note pattern standing in for a spoken phrase.
WAKE = np.concatenate([tone(520,0.12), tone(880,0.12), tone(660,0.12), tone(1200,0.16)])

def background(seconds):
    """Household racket: single chirps, four-note patterns, hiss, knocks."""
    n = int(SR*seconds); x = 0.05*rng.standard_normal(n); pos = 0
    while pos < n - SR:
        pos += rng.integers(int(0.2*SR), int(1.5*SR))
        k = rng.integers(4)
        if   k == 0: seg = 0.6*tone(rng.uniform(300,1500), rng.uniform(0.1,0.4))
        elif k == 1: seg = 0.7*np.concatenate([tone(rng.uniform(350,900),0.12),
                                               tone(rng.uniform(500,1400),0.12),
                                               tone(rng.uniform(400,1100),0.12),
                                               tone(rng.uniform(700,1600),0.16)])
        elif k == 2: seg = 0.4*rng.standard_normal(int(SR*rng.uniform(0.1,0.3)))
        else:        seg = 0.6*np.exp(-np.arange(int(0.15*SR))/600)*rng.standard_normal(int(0.15*SR))
        if pos + seg.size < n: x[pos:pos+seg.size] += seg
        pos += seg.size
    return x

MINUTES = 10
stream = background(MINUTES*60)
planted = np.sort(rng.choice(np.arange(2*SR, stream.size-2*SR), 20, replace=False))
for p in planted:
    stream[p:p+WAKE.size] += 0.9*WAKE          # someone says the wake word, 20 times

def logmel(x):
    m = librosa.feature.melspectrogram(y=x, sr=SR, n_fft=400, hop_length=HOP, n_mels=N_MELS)
    return librosa.power_to_db(m)

S, T = logmel(stream), logmel(WAKE)
W = T.shape[1]
tmpl = (T - T.mean()).reshape(-1); tmpl /= np.linalg.norm(tmpl)

win = np.moveaxis(np.lib.stride_tricks.sliding_window_view(S, W, axis=1), 1, 0)
F = win.reshape(win.shape[0], -1)
F = F - F.mean(axis=1, keepdims=True)
F /= (np.linalg.norm(F, axis=1, keepdims=True) + 1e-9)
score = F @ tmpl                                # one similarity per 10 ms position

def detect(threshold, refractory=int(1.0*SR/HOP)):
    """Fire on a score above threshold, then stay silent for one second."""
    fires, last = [], -10**9
    for i in np.where(score >= threshold)[0]:
        if i - last >= refractory:
            fires.append(i); last = i
    return np.array(fires, dtype=int)

truth_f = planted // HOP
tol = int(0.5*SR/HOP)                           # a hit must land within half a second

print("stream:", MINUTES, "minutes |", len(truth_f), "wake words |",
      f"{score.size} scored positions")
print(f"\n{'threshold':>10}{'detected':>10}{'missed':>8}{'false alarms':>14}{'false/hour':>12}")
for th in (0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90):
    fires = detect(th)
    hit = np.array([np.any(np.abs(fires - t) <= tol) for t in truth_f]) \
          if fires.size else np.zeros(len(truth_f), bool)
    fa = sum(1 for f in fires if not np.any(np.abs(truth_f - f) <= tol))
    print(f"{th:>10.2f}{hit.sum():>10}{(~hit).sum():>8}{fa:>14}{fa/(MINUTES/60):>12.1f}")
Output
stream: 10 minutes | 20 wake words | 59949 scored positions

 threshold  detected  missed  false alarms  false/hour
      0.50        19       1            54       324.0
      0.60        19       1             7        42.0
      0.70        18       2             2        12.0
      0.75        16       4             1         6.0
      0.80        11       9             1         6.0
      0.85         9      11             0         0.0
      0.90         0      20             0         0.0

There is no good row in that table

This is the honest result, and it is worth sitting with.

At 0.60 the detector finds 19 of 20 wake words. It also fires 42 times an hour at nothing. A speaker that lights up every ninety seconds while you cook is unusable.

At 0.85 it never once fires falsely across ten minutes — and it catches fewer than half the real wake words. You would be repeating yourself constantly.

At 0.90 it detects nothing at all, perfectly quietly.

Commercially deployed detectors aim for something on the order of one false accept per hour or better, while catching the great majority of genuine wake words. No row here comes close to both. A single fixed template cannot do this job, and that is precisely why real systems train a network on thousands of recordings of the phrase from many speakers, plus many hours of negative audio.

You have now measured the gap that motivates the entire field. That is more useful than a demo that worked.

Line by line, the parts that are not obvious

The refractory period. Without it, one wake word produces a burst of detections at consecutive 10 ms offsets, and your false-alarm count becomes meaningless. Every real detector suppresses re-triggering for a second or so after firing. Forgetting this is the most common bug in hand-built keyword spotters.

sliding_window_view produces every window as a view without copying, so scoring 59,949 positions costs one matrix multiply. This is what makes streaming detection cheap: you are not re-running a model, you are sliding one.

Per-window mean subtraction before normalising makes the score insensitive to overall loudness, so the wake word is found whether spoken loudly or softly. Skip it and the threshold becomes a volume control.

tol of half a second. Scoring needs a rule for what counts as a hit. Publish yours, because a generous tolerance flatters results, and papers that omit it are not comparable.

24 mel bins, not 80. Wake-word models run on a battery forever. Every feature costs power, and 24 bins is a realistic always-on budget.

How real systems close the gap

Train a small network, not a template. A convolutional network of 10k to 250k parameters over log-mel frames is the standard shape. Sainath and Parada (2015) established the small-footprint CNN recipe still in use.

Cascade two stages. A very cheap first stage runs always and is deliberately over-eager. A second, larger model runs only when stage one fires, and rejects most of those. Since stage two runs rarely, its cost is amortised to nearly nothing. This is how the table above gets fixed in practice: stage one at threshold 0.60, stage two cleaning up the 42 false alarms.

Collect hard negatives. Hours of television, music and conversation. Anything that trips the detector goes into the negative set and it is retrained. This unglamorous loop produces most of the real-world improvement.

Augment heavily. Room reverberation, background noise, distance, device response. See the augmentation results in audio classification — the same effect dominates here.

Personalise on device. Asking a user to say the phrase three times at setup gives a per-user template that meaningfully cuts the miss rate, particularly for speakers the base model serves poorly.

Common mistakes

Reporting accuracy. With 59,949 windows and 20 positives, a detector that never fires scores 99.97 percent accurate. Report detection rate at a fixed false-alarm rate, always.

Testing on clean recordings of the wake word only. The negatives are the hard part. Test against hours of realistic background, including television, which is a specific and severe failure source.

Ignoring the refractory period in the metric. Covered above, and it silently inflates false-alarm counts.

Choosing a short or common phrase. "Hey" or a common first name cannot be made reliable by modelling. Fix this in the product, not the network.

Forgetting the endpoint. After waking, something must decide when the user stopped talking. That is a separate component, and it is where most perceived slowness comes from. See real-time audio pipelines.

Try it yourself

Raise the planted wake-word amplitude from 0.9 to 1.5 and see how much the table improves — that is the effect of speaking closer to the microphone. Then take the two best thresholds and build the cascade by hand: run stage one at 0.60, and for each fire, re-score that window with a stricter template. Count how many of the 42 hourly false alarms survive.

What to learn next

Researcher — Mathematics and papers.

Framing

Keyword spotting is detection under extreme class imbalance with a streaming constraint. Denote the per-frame posterior $p(\text{keyword} \mid x_{t-W:t})$ over a sliding window. The operating characteristic is miss rate against false accepts per hour, not a ROC over balanced classes, because the negative class is measured in time rather than in examples.

With frame rate $f = 100$ Hz, one hour presents $3.6 \times 10^5$ decision opportunities. A target of 1 false accept per hour demands a per-frame false positive rate below $\approx 3 \times 10^{-6}$ before smoothing and refractory logic. That figure explains why the field's engineering is dominated by negative data and posterior smoothing rather than by architecture.

Architectures

Keyword-filler HMM was the classical approach: a keyword HMM and a filler HMM covering all other speech, with Viterbi decoding and a likelihood-ratio threshold.

Deep KWS (Chen, Parada and Heigold, 2014) replaced the HMM with a DNN emitting per-frame posteriors over keyword subword units, followed by a posterior smoothing and confidence step:

$$ c_t = \left(\prod_{j=1}^{n-1} \max_{t-W \le k \le t} \bar{p}_{jk}\right)^{\frac{1}{n-1}} $$

Where $\bar{p}_{jk}$ is the smoothed posterior of the $j$-th keyword unit at frame $k$, $n-1$ the number of units, and $W$ the smoothing window. The geometric mean of per-unit maxima enforces that all units fire in the window, which is what supplies the temporal ordering constraint a per-frame classifier lacks.

Small-footprint CNNs (Sainath and Parada, 2015) showed convolutional models beating DNNs at equal parameter and multiply budgets, and introduced the practice of reporting results under explicit constraints on both.

DS-CNN (Zhang et al., 2017, Hello Edge) applied depthwise-separable convolutions, reaching roughly 95 percent on Google Speech Commands within microcontroller budgets — a few hundred kilobytes of memory and tens of millions of operations per second.

Streaming-aware training matters more than architecture choice. Rybakov et al. (2020) show that models must be trained in the streaming configuration they will run in; a model trained on padded fixed-length clips and deployed on a sliding window suffers a substantial and avoidable accuracy drop from the mismatch in receptive-field context.

Open-vocabulary KWS removes the fixed phrase. Query-by-example approaches embed an enrolment utterance and score by similarity; text-query approaches such as CLAP-style audio-text alignment let the keyword be typed. Both currently trade several times the false-alarm rate for the flexibility.

Data

Google Speech Commands (Warden, 2018) is the standard benchmark: 105,000 one-second clips, 35 words, with prescribed splits, a background-noise set and an explicit "unknown" class. Warden's paper is unusually direct about the evaluation pitfalls, including that a random split leaks speakers across folds and inflates results.

The gap between this benchmark and production is large. Benchmarks measure classification among a closed word set; deployment measures false accepts across arbitrary continuous audio. A 98 percent Speech Commands accuracy implies nothing about false accepts per hour, and the two are routinely conflated.

The compute budget

Always-on detection is constrained by energy, not latency. Rough production envelopes:

ConstraintTypical target
Parameters10 k – 250 k
Multiply-accumulates per second1 M – 50 M
Working memorytens to hundreds of kB
Poweron the order of a milliwatt on a dedicated block

Meeting these relies on the techniques in model compression and quantisation in practice: int8 weights and activations are standard, and many deployments run on a dedicated low-power DSP or neural block so the application processor stays asleep. The cascade structure exists for the same reason — stage two's cost is multiplied by its trigger rate, so an aggressive stage one with a rare, expensive stage two beats one medium model at equal average power.

Fairness

Wake-word performance is a function of how well the training distribution covers the speaker. Miss rates are measurably higher for speakers with accents, ages and speaking styles under-represented in the data, and the burden falls entirely on the user, who experiences it as a device that ignores them.

Evaluate and publish miss rate stratified by accent, age band and gender at a fixed false-accept rate. Aggregate numbers conceal exactly the failures that make a device feel broken to some of its owners. On-device personalised enrolment is the most effective available mitigation and should be offered rather than buried. See fairness metrics.

Reading

  • Chen, Parada and Heigold (2014), Small-footprint Keyword Spotting Using Deep Neural Networks, ICASSP.
  • Sainath and Parada (2015), Convolutional Neural Networks for Small-footprint Keyword Spotting, Interspeech.
  • Zhang et al. (2017), Hello Edge: Keyword Spotting on Microcontrollers — arxiv.org/abs/1711.07128
  • Warden (2018), Speech Commands: A Dataset for Limited-Vocabulary Speech Recognition — arxiv.org/abs/1804.03209
  • Rybakov et al. (2020), Streaming Keyword Spotting on Mobile Devices — arxiv.org/abs/2005.06720

What to learn next