audio-health-nano

47,094 parameters. 188 KB. Six audio faults, one forward pass, multi-label.

clipped Β· dropout Β· dc_offset Β· hum Β· bandwidth_loss Β· hiss

Prior art β€” this task is established, and this is a small variant of it

Audio defect detection is a solved problem at full model scale. The direct reference point is Higham et al., A No-Reference Model for Detecting Audio Artifacts using Pretrained Audio Neural Networks, WACV workshops 2022 (Amazon) β€” 1-second segments classified as No Defect / Hum / Hiss / Distortion / Clicks at 0.986 balanced accuracy, using a modified PANN-CNN6 (~4.8M parameters) on log-mel spectrograms. Broadcast QC tools have detected hum, clipping and dropouts for decades, some of it patented.

Their 0.986 is the reference for what a full-size, pretrained model does. This does not match it and does not claim to. What differs:

Higham et al. 2022 this model
parameters ~4.8M (PANN-CNN6) 47K, ~100Γ— smaller
pretraining AudioSet-pretrained none
output single-label, 5 classes multi-label, 6 faults β€” real recordings carry several at once
faults hum, hiss, distortion, clicks adds bandwidth loss and DC offset
reporting balanced accuracy per-fault margin over a single-threshold baseline, plus a cross-corpus transfer test

Use theirs if you want accuracy. Use this if you need something that runs in 188 KB on a CPU thread, and you want to see which heads actually earn a model.

Measured β€” ESC-50, split by the dataset's own folds

fault accuracy majority lift best scalar margin recall
dc_offset 0.905 0.711 +0.195 0.718 +0.187 0.925
hum 0.877 0.689 +0.189 0.686 +0.191 0.755
hiss 0.901 0.697 +0.204 0.886 +0.015 0.984
bandwidth_loss 0.905 0.714 +0.192 0.882 +0.023 0.684
clipped 0.831 0.687 +0.144 0.736 +0.095 0.838
dropout 0.793 0.695 +0.098 0.750 +0.043 0.719

hiss and bandwidth_loss barely beat a single threshold. Both are broad spectral-shape changes that one statistic captures well. The model is convenient for sharing a forward pass with the others, not necessary for those two.

The cross-corpus test, and what it exposes

Trained on ESC-50, evaluated on 20 s of real office ambience from a webcam microphone β€” a genuinely different source. The scalar baseline here is a threshold fitted on ESC-50 and transferred, which is what a deployed threshold actually is.

fault ESC-50 rig microphone transferred scalar margin
dropout 0.824 0.899 0.763 +0.136
clipped 0.812 0.875 0.848 +0.028
bandwidth_loss 0.906 0.914 0.912 +0.002
hiss 0.908 0.904 0.894 +0.010
dc_offset 0.912 0.799 0.696 +0.104
hum 0.837 0.433 0.693 βˆ’0.260

hum scoring below chance is a labelling artefact, not a model failure

The office recording already contains mains hum. Measured against its own broadband noise floor:

frequency peak above floor
50 Hz +28.8 dB
60 Hz +27.7 dB
100 Hz +33.2 dB
120 Hz +27.0 dB
150 Hz +30.9 dB
180 Hz +30.6 dB

Every "no hum" example in that test is a recording with 30 dB of hum in it. The head is detecting hum that is genuinely present, against a label saying it is absent. In any environment with mains equipment, the negative class for hum does not exist, and a cross-corpus number measured there is meaningless.

This generalises: an operation the acquisition environment already performs cannot be detected as applied-versus-not. The same effect kills sharpening and denoising detectors on webcam frames, because the camera's own pipeline already sharpens and denoises.

Which heads to use

  • dropout β€” the only head passing a strict cross-corpus gate. Use it.
  • dc_offset, clipped β€” strong in-corpus, positive but modest across corpora. Use with a validation set from your own material.
  • hum β€” use on media files where a clean negative exists. Do not evaluate it on live room audio from a mains-powered environment.
  • hiss, bandwidth_loss β€” a threshold does nearly as well. Use them because they come free with the forward pass, not because they need a network.

Scope

For: media QC triage, archive ingest checks, flagging recordings for human review, and monitoring capture chains on hardware too small for a pretrained audio network.

Not for:

  • Not speech quality, intelligibility, or MOS estimation.
  • Not a judgement about content or speakers. It sees a 64Γ—64 spectrogram.
  • Not a replacement for a full-size detector where accuracy matters more than size β€” see the prior art above.
  • Not calibrated for music mastering or any perceptual quality decision.

Known failure modes

  1. Faults are synthesised onto real recordings. Real clipping involves the whole analogue chain; real dropouts have codec-specific concealment.
  2. 1 second at 16 kHz. Content above 8 kHz is invisible; faults shorter than a few ms may be missed.
  3. dropout recall is 0.719 in-corpus β€” it misses about a quarter of cases while being the most transferable head.
  4. Environment contamination, as above.
  5. No pretraining, so no AudioSet-style semantic prior. It reads spectrogram structure only.

Usage

import numpy as np, cv2, onnxruntime as ort
from scipy import signal as sg

SR = 16000
FAULTS = ["clipped", "dropout", "dc_offset", "hum", "bandwidth_loss", "hiss"]

def spec(x):                                  # x: 1 s of mono float audio at 16 kHz
    f, t, S = sg.stft(x, SR, nperseg=256, noverlap=192)
    P = np.log10(np.abs(S) + 1e-8)
    P = np.clip((P + 8) / 8.0, -1, 3)         # ABSOLUTE level preserved -- see note below
    return cv2.resize(P.astype(np.float32), (64, 64), interpolation=cv2.INTER_AREA)

so = ort.SessionOptions()
so.intra_op_num_threads = 1
so.add_session_config_entry("session.intra_op.allow_spinning", "0")
sess = ort.InferenceSession("audio_health.onnx", sess_options=so,
                            providers=["CPUExecutionProvider"])

probs = 1 / (1 + np.exp(-sess.run(None, {"spectrogram": spec(x)[None, None]})[0][0]))
print({f: round(float(p), 3) for f, p in zip(FAULTS, probs)})

Do not per-window standardise the spectrogram. Sibling models in this family do, and it deletes absolute level β€” which is exactly what clipping is. With standardisation the clipped head sits at 0.503, chance; keeping absolute level takes it to 0.828. The scaling above is deliberate.

Deployment note: cap the ONNX Runtime thread pool

ONNX Runtime sizes its intra-op pool to the core count and those workers spin-wait between inferences, burning about 1.9 cores to serve 0.28 ms inferences. With intra_op_num_threads=1 and allow_spinning=0, measured idle CPU dropped from 192% to 16.5% of one core, throughput unchanged.

Training

7,000 train windows from ESC-50 folds 1–3, 2,400 test from folds 4–5 Β· faults sampled independently at p=0.30 so mixtures occur naturally Β· log-STFT (nperseg 256, noverlap 192), absolute level scaled to a fixed range, resized to 64Γ—64 Β· 4 conv layers (16β†’32β†’48β†’64) Β· BCE with positive weighting capped at 10Γ— Β· Adam 3e-3, 20 epochs.

Same architecture as resoajoe/alarm-nano, resoajoe/camera-health-nano and resoajoe/blockgrid-nano, unchanged.

Verification

ONNX vs PyTorch, both CPU, 256 inputs: max relative logit difference 2.8e-07, 100% threshold agreement.

Provenance

ESC-50 (public, CC BY-NC) plus 20 s of the author's own office ambience. No speech, no identifiable people, no personal data.

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