alarm-nano
46,834 parameters. 187 KB of ONNX. Is a medical-equipment alarm sounding in this one-second
window?
Runs on a CPU thread. The same 47K convolutional architecture as this project's vision models — a
log-spectrogram is an image.
Domain measured / deployment domain tested: measured on synthesised IEC 60601-1-8 alarms against real ESC-50 negatives; deployment domain: no ward or bedside microphone recording tested. (Fifth line of the card standard, added 2026-09-02: a number is only as good as the domain it was measured in.)
Scope — read this first
What it is for: healthcare OPERATIONS. Alarm-fatigue auditing, unattended-alarm detection,
"how long did that alarm sound before anyone came", acoustic load surveys in wards. It detects
that a device is sounding, and nothing else.
It is NOT a diagnostic device, and must not be used as one. The distinction is the whole point:
- It says an alarm is sounding. It does not say why, which device, or whether the alarm is
correct.
- It says nothing about a patient. No vital sign, no condition, no acuity.
- It is not a substitute for the alarm system. Never place it in a path where a missed
detection delays clinical response. It is an observer, not a monitor.
- It is not certified for clinical use under any regulatory framework.
- Not for speech, not for voice activity, not for surveillance of people. If you need voice
activity detection, use Silero VAD — it is ~1 MB and far better at that job.
How it was built
Positives are synthesised, not recorded. IEC 60601-1-8 specifies medical alarm signals as
melodic pulse bursts: 3 pulses (medium priority) or 5 (high), fundamental 150–1000 Hz, pulse
duration 75–200 ms, 1–4 harmonics. All of that can be generated exactly, which makes the label
free and perfectly accurate and the training set effectively infinite.
Negatives are real. ESC-50 — 2,000 recordings across 50 environmental sound classes — split by
the dataset's own 5 folds (folds 1–3 train, 4–5 test) so clips from the same source recording never
cross the boundary.
Positives are mixed into negatives at a known SNR, so every example carries an exact difficulty
label.
Measured performance
Held-out ESC-50 folds: 0.853 accuracy (chance 0.500).
Detection by SNR — a single headline number would just report the SNR mix you sampled:
| SNR | −20 dB | −15 | −10 | −5 | 0 | +5 | +10 |
|---|
| detection rate | 0.419 | 0.583 | 0.770 | 0.911 | 0.964 | 0.993 | 0.977 |
False alarms across all 50 ESC-50 classes: 0.099.
Generalisation
Tested against noise it never trained on:
| background | false alarm | accuracy |
|---|
| white | 0.077 | 0.958 |
| pink | 0.067 | 0.942 |
| brown | 0.003 | 0.995 |
| HVAC-shaped | 0.000 | 0.988 |
| a real office room, never heard | 0.067 | 0.925 |
Known failure modes — the hard negatives
ESC-50 contains sounds that are alarms, just not medical ones. Their false-alarm rates matter
far more than the 50-class average, so they are reported separately rather than averaged away:
| class | false alarm |
|---|
| car_horn | 0.346 |
| church_bells | 0.227 |
| siren | 0.190 |
| clock_tick | 0.118 |
| can_opening | 0.050 |
| clock_alarm | 0.040 |
A harmonic pulse train is a harmonic pulse train. This model will fire on car horns roughly a
third of the time. In any deployment where those sounds occur, that is the binding limitation.
Other limits:
- Synthetic positives. It has never heard a real infusion pump. Real devices have room
acoustics, reverberation, enclosure resonances and driver distortion that the synthesis does
not model. Expect degradation on real recordings; this is the single biggest untested risk.
- Below −10 dB SNR it is unreliable (0.42–0.58). A distant alarm through a closed door may sit
there.
- One second of context. It cannot count pulses across a full IEC burst pattern, so it cannot
distinguish medium from high priority.
- 16 kHz mono. Content above 8 kHz is invisible to it.
Why v1 is not this model
The first version used one room's noise floor as its entire negative class. It scored 0.012
false alarms in-domain and 0.987 on white noise — it had learned "unexpected high-frequency
energy", which is what an alarm looks like against that particular quiet floor.
v1 beat v2 on every in-domain metric: 0.012 false alarms vs 0.099, and 1.000 detection at −5 dB vs
0.911. v1 was also useless. A narrow model outscores a robust one on exactly the numbers you
would put in a README, which is why this card leads with out-of-distribution probes.
Usage
1import numpy as np, onnxruntime as ort, cv2
2from scipy import signal
3
4SR = 16000
5sess = ort.InferenceSession("alarm.onnx", providers=["CPUExecutionProvider"])
6
7def spec(x): # x: 1 second of mono float audio at 16 kHz
8 f, t, S = signal.stft(x, SR, nperseg=256, noverlap=192)
9 P = np.log10(np.abs(S) + 1e-8)
10 P = (P - P.mean()) / (P.std() + 1e-8) # per-window normalise: removes absolute level so
11 return cv2.resize(P.astype(np.float32), # the model reads STRUCTURE, not loudness
12 (64, 64), interpolation=cv2.INTER_AREA)
13
14x = ... # np.float32, shape (16000,), range about [-1, 1]
15logits = sess.run(None, {"spectrogram": spec(x)[None, None]})[0][0]
16print("alarm" if logits.argmax() == 1 else "no alarm")
The per-window normalisation is not optional. Training normalised every window to zero mean and
unit variance, so the model is deliberately blind to absolute level. Skip it and the input
distribution no longer matches training.
Verification
ONNX vs PyTorch, identical weights, both CPU, 256 inputs:
- max relative logit difference 3.6e-07 (logits reach ±6.9)
- 100% argmax agreement
Relative, not absolute — an absolute 1e-3 check flagged a bit-accurate export of a sibling model
as broken.
Training
- 6,000 mixed windows from ESC-50 folds 1–3; 2,000 test windows from folds 4–5
- 4 conv layers (16→32→48→64), BatchNorm, global average pool, 2-way head
- Adam 3e-3, 14 epochs, batch 64, CPU
- Input: log-STFT (nperseg 256, noverlap 192), per-window standardised, resized to 64×64
train_alarm.py (synthesis + the one-room v1) and alarm_esc50.py (v2) are both included.
Deployment note: cap the ONNX Runtime thread pool
Measured on a Jetson AGX Orin. ONNX Runtime sizes its intra-op thread pool to the CPU core count,
and those workers spin-wait between inferences. Running two 47K-parameter models this way left
~18 threads busy-waiting at ~10.6% of a core each — about 1.9 cores burned continuously to run
inferences that take 0.28 ms. A model this small cannot use intra-op parallelism at all.
1so = ort.SessionOptions()
2so.intra_op_num_threads = 1
3so.inter_op_num_threads = 1
4so.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
5so.add_session_config_entry("session.intra_op.allow_spinning", "0")
6sess = ort.InferenceSession("alarm.onnx", sess_options=so, providers=["CPUExecutionProvider"])
Measured effect on the same workload: idle CPU 192% → 16.5% of one core, active 231% → 88%,
thread count 45 → 22, throughput unchanged. On edge hardware this is the difference between
"runs alongside everything else" and "saturates the machine".
What "scalar baseline" means on this card
Every margin quoted here is against a stated baseline, because a margin without one is not a
measurement. The baseline is the best single-threshold classifier over ten cheap statistics,
fitted optimistically:
mean · std · lapvar · hf (high-frequency energy ratio) · grad (Sobel magnitude) ·
entropy · centre_edge · radial_slope · row_fft_peak · col_fft_peak
The last four are spatially aware, added after an earlier six-statistic baseline — all global
aggregates — was found to systematically overstate model value on spatially structured tasks. A
baseline that cannot see where anything is loses to a CNN by default. On one test task that flaw
inflated an apparent margin from +0.060 to +0.261.
Two questions are asked with it, and they disagree:
- in-sample — threshold fitted on the data it is scored on. Deliberately generous. Answers
is there structure beyond a low-order statistic?
- transferred — threshold fitted on the training corpus, applied unchanged to the target.
Answers what should I ship? On one task the in-sample figure was 0.954 and the transferred
figure 0.565.
Where this card quotes a single scalar figure without qualification, it is the in-sample one.
Provenance and privacy
Positives are synthesised from a published standard. Negatives are ESC-50, a public dataset of
environmental recordings. No patients, no clinical recordings, and no personal data are involved
anywhere in this work. No audio of identifiable people was used in training.
Related