Jvachier's picture
Update README.md
f8794ab verified
|
Raw
History Blame Contribute Delete
8.91 kB
---
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},
}
```