asr-attack
Adversarial robustness toolkit for Hugging Face ASR — Whisper, wav2vec2, MMS, HuBERT, ... — with FGSM, PGD, noise, and environmental attacks in one consistent API.
Demo
Rendered with VHS. Reproduce with vhs docs/demo.tape.
Why this exists
The computer-vision community has Foolbox and Adversarial Robustness Toolbox: clean, model-agnostic libraries that turn "attack my model" into a one-liner. Speech recognition does not have an equivalent. If you want to know whether your fine-tuned wav2vec2 survives 10 dB of background babble, or whether Whisper holds up under FGSM at ε=0.02, you currently glue together model-specific code from papers, fight HuggingFace feature extractors, and write your own WER pipeline.
asr-attack is the missing piece: one Attack.fgsm() / Attack.pgd() / Attack.noise() / Attack.environment() API that works on any HF ASR model, plus a run_benchmark that takes (model_id, attack, dataset, n_samples) and gives you back a Report with text / JSON / self-contained HTML output.
Quick start
from asr_attack import Attack, run_benchmark
report = run_benchmark(
model="openai/whisper-tiny",
attack=Attack.fgsm(epsilon=0.02),
dataset="hf-internal-testing/librispeech_asr_dummy",
n_samples=10, split="validation", config="clean",
)
print(report.summary())
report.to_html("report.html") # self-contained HTML with embedded charts
Three complete runnable scripts live in examples/, one per
attack family and model architecture:
| Script | Model | Attack | Family |
|---|---|---|---|
whisper_tiny_fgsm.py |
openai/whisper-tiny |
FGSM, ε=0.02 | white-box, seq2seq |
wav2vec2_pgd.py |
facebook/wav2vec2-base-960h |
PGD, ε=0.02, 10 steps | white-box, CTC |
hubert_noise.py |
facebook/hubert-large-ls960-ft |
Gaussian noise, SNR=10 dB | black-box, CTC |
Each script prints a summary and writes a self-contained HTML report plus a
JSON dump next to itself. Run any of them with
uv run python examples/<script>.py.
Install:
uv add asr-attack # or: pip install asr-attack
Supported models
White-box attacks (FGSM, PGD) need a differentiable path from the waveform to the loss. The wrapper exposes model.supports_waveform_gradient, which gates these attacks. Black-box attacks (noise, environment) work on every row of the table.
| Model family | Transcribe | White-box (FGSM / PGD) | Notes |
|---|---|---|---|
| wav2vec2 / wav2vec2-conformer | ✓ | ✓ | reference CTC path |
| HuBERT, WavLM, UniSpeech(-SAT), SEW(-D), data2vec-audio | ✓ | ✓ | same wav2vec2 interface |
| MMS | ✓ | ✓ | wav2vec2 + per-language adapters (language= kwarg) |
| Whisper (tiny / base / small / medium / large / large-v2 / large-v3) | ✓ | ✓ | torch-side log-mel; language= kwarg |
| M-CTC-T | ✓ | ✗ | mel-spec input, no torch extractor yet |
| SpeechT5, S2T (non-Whisper seq2seq) | ✓ | ✗ | no torch extractor yet |
Multilingual
For Whisper and MMS, pass language= to switch under the hood:
HFASRModel.from_pretrained("openai/whisper-large-v3", language="it") # ISO 639-1 or name
HFASRModel.from_pretrained("facebook/mms-1b-fl102", language="ita") # ISO 639-3
Same kwarg, two very different mechanisms:
- Whisper consumes
languageas a runtime prompt token. The wrapper threads it intogenerate(language=..., task="transcribe")for inference and intoprocessor.get_decoder_prompt_ids(language=...)when building the labels for FGSM/PGD. Use ISO 639-1 codes ("en","it","fr") or full names ("english"). - MMS loads the corresponding per-language LM head + attention adapter at construction time (
target_lang=...+ignore_mismatched_sizes=True). Use ISO 639-3 codes ("eng","ita","fra"). - Single-language models (wav2vec2-base-960h, hubert-large-ls960-ft, ...) accept the kwarg for API symmetry but ignore it.
Supported attacks
Attack.fgsm(epsilon) — Fast Gradient Sign Method (Goodfellow et al. 2014). Single-step gradient ascent in the L∞ ball of radius epsilon: x_adv = clip(x + ε · sign(∇L), -1, 1). Cheap and fast, surprisingly effective even at imperceptible budgets. On a typical clean LibriSpeech sample it pushes wav2vec2-base from WER 0 to ~0.06 at ε=0.02, with the perturbation sitting ~10 dB below the signal.
Attack.pgd(epsilon, alpha, n_steps, random_start) — Projected Gradient Descent (Madry et al. 2017). FGSM iterated for n_steps with step alpha, projecting back into the L∞ ball after each step, with optional uniform-noise random start. Substantially stronger than FGSM at the same budget: same ε=0.02, wav2vec2 WER goes from 0.06 (FGSM) to ~0.82 (PGD 10-step), and the perturbation is actually quieter (~14 dB SNR) because the iterative refinement places energy more selectively.
Attack.noise(snr_db, kind) — Mix i.i.d. Gaussian or uniform white noise at a target signal-to-noise ratio. Black-box: no gradient, no model required, works on any ASR. The classic baseline for "is my model robust to background noise?" — Gaussian at SNR 10 dB ≈ noisy office, at SNR 0 dB ≈ inside a loud bar.
Attack.environment(ir_path, background_path, snr_db) — MUSAN-style environmental degradation. Convolves with an impulse response (room reverb, recorded or synthetic) and/or mixes in a background recording at a target SNR. The background can be stationary noise (traffic, AC), music, or another speaker — the last case triggers the classic cocktail-party failure where the ASR transcribes the background instead of the target. Compatible with MUSAN out of the box.
Example output
Run examples/whisper_tiny_fgsm.py to get a
self-contained HTML report (examples/whisper_tiny_fgsm.html)
and a JSON dump (examples/whisper_tiny_fgsm.json)
of 10 LibriSpeech-dummy validation samples attacked at ε=0.02:
$ uv run python examples/whisper_tiny_fgsm.py
asr-attack benchmark report
===========================
Model : openai/whisper-tiny
Attack : fgsm
Dataset : hf-internal-testing/librispeech_asr_dummy
Samples : 10
Clean WER : 0.106
Adv WER : 0.528
WER delta : +0.421
Mean SNR : 10.7 dB
A single forward-backward pass per sample lifts WER from 10.6% to 52.8% while
the perturbation stays ~10 dB below the signal. For comparison,
wav2vec2_pgd.py (PGD, 10 steps, same ε=0.02) drives WER from 5.5% to 61.0%
on wav2vec2-base-960h, and hubert_noise.py (Gaussian noise at 10 dB SNR)
nudges hubert-large-ls960-ft from 3.5% to 5.5% — large models stay robust
to mild noise, but white-box gradient attacks devastate both.
Roadmap
- Carlini-Wagner attack (L₂ / L∞): optimization-based, finds minimum-perturbation adversarials. Slower than PGD but tighter — useful for "audibility-bounded" attack research.
- Psychoacoustic masking (Schönherr et al. 2018): shape adversarial noise so it falls under the human hearing threshold given the speech masker — perceptually inaudible attacks against ASR.
- Torch-side feature extractors for non-Whisper seq2seq (SpeechT5, S2T) and M-CTC-T → unlocks FGSM/PGD on those families too.
- Audio-domain transforms as black-box attacks: MP3 transcoding round-trip, telephony band-pass (300-3400 Hz), packet loss simulation — common real-world degradations that aren't reverb or additive noise.
- PGD adversarial training: defensive side of the coin — use these attacks during fine-tuning to harden ASR models.
Development
uv sync --group dev
uv run pytest -m "not slow" # fast unit tests
uv run pytest # full suite (downloads ~500 MB of models + a tiny LibriSpeech sample)
License
MIT — see LICENSE.
Contributing
Issues and PRs welcome. To add a new attack, subclass Attack (see asr_attack/attacks/base.py) and register a classmethod factory there. White-box attacks should gate on model.supports_waveform_gradient; black-box attacks accept model=None and run on any input. New attacks are expected to come with both a unit test and an end-to-end "WER goes up" slow test against facebook/wav2vec2-base-960h on the LibriSpeech dummy sample (see tests/test_pgd.py for the canonical shape).
Release files for asr-attack 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| asr_attack-0.1.0.tar.gz | 22.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| asr_attack-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.2 kB
Release files / asr_attack-0.1.0.tar.gz
| Download URL | asr_attack-0.1.0.tar.gz |
|---|---|
| Size | 22.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dafec6c1adf4ac6654056fdd8dade1c2d954f6dd1781f52e40c6470fad5bda37
|
|
BLAKE2b-256 checksum How to use checksums |
116b7b709f59db0d4c7ee4d4caf86d5dae22677446c78093e7daff2e3181e097
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 18, 2026.
Transparency logRelease files / asr_attack-0.1.0-py3-none-any.whl
| Download URL | asr_attack-0.1.0-py3-none-any.whl |
|---|---|
| Size | 28.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3dc30b2e13c91c53fd24316b39299be5d76589137717347b631f96b76507835
|
|
BLAKE2b-256 checksum How to use checksums |
c57b580692f9c76649e04ef445946283c6144bf990cad4f0a01fc2f2af74892a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 18, 2026.
Transparency log