DCASE 2020 Task 2 β€” Valve Anomaly Detection (ArcFace Embedding + Mahalanobis)

Unsupervised acoustic anomaly detection for industrial valves. Trained on normal operating sounds only (no anomalous examples seen during training); anomalies are detected as embeddings that fall far from the learned distribution of normal acoustic signatures.

  • Task: unsupervised anomaly detection (DCASE 2020 Challenge Task 2 formulation)
  • Equipment type: valve
  • Metric: pooled Mahalanobis distance AUC-ROC = 0.947 Β± 0.006 (mean Β± std over 3 seeds)
  • Deployment: no machine ID required at inference (pooled scoring)
  • Code: github.com/jvachier/industrial-audio-anomaly-detection
  • Demo: Gradio App

Model description

The model is a multiscale Transformer encoder trained on a pretext task: given a log-mel spectrogram patch, predict which physical valve unit (machine ID) it came from. This forces the encoder to learn fine-grained acoustic signatures characteristic of each unit's normal operation. An ArcFace additive angular margin loss (s=32, m=0.10) tightens the per-ID clusters on the embedding hypersphere during training.

After training, the classification head is discarded. What remains is a 128-dimensional embedding function. At inference, the anomaly score is the Mahalanobis distance from a clip's embedding to a single LedoitWolf-estimated Gaussian fitted over all training embeddings pooled across machine IDs β€” this is what makes the deployed model usable without knowing which physical unit produced a given recording.

Architecture:

Input: log-mel spectrogram (64 frames x 128 mel bins)
  -> multiscale patch tokenisation: patch sizes [8, 4] frames, each scale gets its own
     linear projection to 128-d + sinusoidal positional encoding, then concatenated
  -> 2 x Transformer encoder block (4 heads, d_model=128)
  -> mean pooling -> 128-d embedding
  -> (training only) ArcFace head, s=32, m=0.10, over 4 machine-ID classes

Trained independently for 3 random seeds (0, 1, 2); the reference implementation z-score-ensembles all three at inference for a more robust score (see Usage below).


Intended use

  • Primary use case: screening industrial valve acoustic recordings for anomalous operating conditions (e.g. predictive maintenance), as a proof-of-concept / benchmark model on the DCASE 2020 Task 2 dataset.
  • Not validated for: real-world deployment audio outside the DCASE 2020 / MIMII valve distribution (different valve types, microphone placement, background noise profiles, or SNR than the benchmark's synthetic 0 dB mixing). Treat scores from out-of-distribution audio as unreliable without re-calibration.
  • Not included in this repo: fan and pump models. This repository currently ships the valve model only.

How to use

This model uses custom Keras layers (ArcFaceLayer, multiscale patch tokenizer) that are registered via @keras.saving.register_keras_serializable in the companion iaad Python package β€” loading the .keras file with bare keras.models.load_model() without that package installed will fail to resolve the custom layers. Install the package from GitHub (not yet published to PyPI):

pip install git+https://github.com/jvachier/industrial-audio-anomaly-detection.git

Single seed:

from iaad.inference import AnomalyDetector

det = AnomalyDetector.from_hub(machine_type="valve", seed=0)
result = det.score_file("path/to/valve_clip.wav", score="mahalanobis")
print(result.is_anomalous, result.percentile)

Ensemble of all 3 seeds (recommended β€” more robust score):

from iaad.inference import EnsembleDetector

ens = EnsembleDetector.from_hub(machine_type="valve")
result = ens.score_file("path/to/valve_clip.wav", score="mahalanobis")
print(result["is_anomalous"], result["percentile"])

Input audio should be a 16 kHz mono WAV clip (10 s recommended, matching training clip length); other sample rates are auto-resampled with a warning.


Training data

DCASE 2020 Challenge Task 2 (MIMII-based), valve subset, mirrored on Kaggle: daisukelab/dc2020task2.

  • 4 machine IDs: id_00, id_02, id_04, id_06
  • Condition: 0 dB SNR (target machine signal + factory background noise at equal power)
  • ~1000 ten-second normal-only training clips per machine ID; a labelled normal + anomaly test split is used only for evaluation, never for training
  • 85/15 train/validation split (validation is normal-only, used for early stopping)

Preprocessing (src/iaad/features.py, no external audio library): log-mel spectrogram, 128 mel bins, 50–8000 Hz, 512-sample FFT window / 256-sample hop, 64-frame (~1.02 s) non-overlapping patches, per-clip CMVN (mean-normalised per mel bin). SpecAugment is disabled for valve specifically (empirically best AUC without it; fan/pump use frequency + time masking).


Training procedure

Hyperparameter Value
Optimizer Adam, lr=1e-3
Batch size 64
Epochs up to 120, early stopping (patience=10, restore best weights)
Loss ArcFace-modified softmax cross-entropy over 4 machine-ID classes
ArcFace scale / margin s=32, m=0.10
Seeds 0, 1, 2 (independent runs, deterministic β€” tf.config.experimental.enable_op_determinism())
Hardware single GPU (Kaggle P100/T4), no mixed precision

After training, a calibration pass fits a LedoitWolf covariance estimator over all training-set embeddings (pooled across machine IDs) and records percentile thresholds of the resulting Mahalanobis distances β€” this is what ships in valve_calib_s{seed}.npz and lets inference flag a clip as anomalous (e.g. above the 95th percentile of normal) without any refitting.


Evaluation results

Per-machine-ID mean AUC-ROC, averaged over 3 random seeds (chance = 0.500):

Machine EMB pooled Mahalanobis
valve 0.947 Β± 0.006

Pooled scoring is the deployment-safe metric (no machine ID needed at inference). See the GitHub repo's evaluation docs for the full comparison against earlier detector families (dense autoencoder, Transformer-VAE) and the oracle (per-ID, ID-known) scoring variant.


Files in this repository

File Description
version.json {"emb_version": "v5", "run_tag": "v5.4"} β€” model family marker read by AnomalyDetector._load()
valve_emb_s{0,1,2}.keras Trained EMBClassifier weights, one per seed
valve_norm_s{0,1,2}.npz Per-mel-bin normalisation stats (mean, std) fit on that seed's training split
valve_calib_s{0,1,2}.npz Calibration: pooled LedoitWolf location/precision + percentile thresholds

Limitations and biases

  • Trained and evaluated exclusively on the DCASE 2020 Task 2 synthetic 0 dB SNR mixing; performance on cleaner or noisier real-world recordings is unverified.
  • Unsupervised training means the model has never seen a true anomaly label; its notion of "anomalous" is entirely defined by the DCASE benchmark's specific injected fault types, which may not cover all real failure modes.
  • Anomaly threshold (95th percentile by default) is a design choice trading recall for precision β€” recalibrate on your own normal-operation data before deploying to a different valve installation.

License

Apache 2.0 β€” see the GitHub repository's LICENSE.

Citation

@misc{vachier2025iaad,
  author  = {Vachier, Jeremy},
  title   = {Industrial Audio Anomaly Detection -- DCASE 2020 Task 2 Baseline},
  year    = {2026},
  url     = {https://github.com/jvachier/industrial-audio-anomaly-detection},
}

@inproceedings{koizumi2020dcase,
  author    = {Koizumi, Yuma and Saito, Shoichiro and Uematsu, Hisashi and Harada, Noboru and Imoto, Keisuke},
  title     = {ToyADMOS: A Dataset of Miniature-Machine Operating Sounds for Anomalous Sound Detection},
  booktitle = {Proceedings of the Detection and Classification of Acoustic Scenes and Events 2020 Workshop (DCASE2020)},
  year      = {2020},
}

@misc{huggingface_hub,
  author = {Hugging Face},
  title  = {Hugging Face Hub},
  year   = {2026},
  url    = {https://github.com/huggingface/huggingface_hub},
}
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support