File size: 8,907 Bytes
dcfe291
 
3e77b67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dcfe291
3e77b67
 
 
 
 
 
 
 
 
 
 
 
 
f8794ab
3e77b67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
---
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},
}
```