--- license: apache-2.0 language: en library_name: keras tags: - audio - anomaly-detection - industrial - predictive-maintenance - acoustic-monitoring - dcase2020 - arcface - transformer - keras - tensorflow metrics: - roc_auc --- # 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](https://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): ```bash pip install git+https://github.com/jvachier/industrial-audio-anomaly-detection.git ``` **Single seed:** ```python 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):** ```python 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](https://dcase.community/challenge2020/task2-unsupervised-detection-of-anomalous-sounds) (MIMII-based), valve subset, mirrored on [Kaggle: daisukelab/dc2020task2](https://www.kaggle.com/datasets/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](https://github.com/jvachier/industrial-audio-anomaly-detection/blob/main/docs/evaluation.md) 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](https://github.com/jvachier/industrial-audio-anomaly-detection/blob/main/LICENSE). ## Citation ```bibtex @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}, } ```