Lab: Adversariële audiovoorbeelden vervaardigen
Gevorderd7 min lezenBijgewerkt op 2026-03-13
Praktijklab voor het creëren van adversariële audiovoorbeelden met Python-audioverwerking, gericht op Whisper-transcriptie met geïnjecteerde tekst.
Labopstelling
Vereisten
pip install torch torchaudio openai-whisper numpy scipy soundfile matplotlibOmgevingscontrole
import torch
import whisper
import numpy as np
import soundfile as sf
print(f"PyTorch: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
# Laad het Whisper-model
model = whisper.load_model("base")
print("Whisper model loaded successfully")Oefening 1: Basis-transcriptie
Stel eerst het basisgedrag vast met schone audio:
import numpy as np
import soundfile as sf
import whisper
def generate_test_audio(
text_content: str = "hello world",
duration: float = 3.0,
sample_rate: int = 16000
) -> np.ndarray:
"""Genereer eenvoudige testaudio (sinusgolf-tonen)."""
t = np.linspace(0, duration, int(duration * sample_rate), endpoint=False)
# Eenvoudige toon die Whisper waarschijnlijk als stilte/ruis transcribeert
audio = 0.3 * np.sin(2 * np.pi * 440 * t)
# Voeg wat ruis toe voor realisme
audio += 0.01 * np.random.randn(len(audio))
return audio.astype(np.float32)
def transcribe_audio(audio: np.ndarray, model) -> str:
"""Transcribeer audio met Whisper."""
result = model.transcribe(
audio,
language="en",
fp16=False # Gebruik fp32 voor reproduceerbaarheid
)
return result["text"].strip()
# Basistest
model = whisper.load_model("base")
# Test met gegenereerde audio
test_audio = generate_test_audio()
baseline_transcription = transcribe_audio(test_audio, model)
print(f"Baseline transcription: '{baseline_transcription}'")
# Sla op ter referentie
sf.write("baseline_audio.wav", test_audio, 16000)Oefening 2: Eenvoudige gradient-aanval
Implementeer een basale gradient-gebaseerde aanval om Whisper een doelzin te laten transcriberen:
import torch
import whisper
import numpy as np
def simple_whisper_attack(
model,
source_audio: np.ndarray,
target_text: str,
epsilon: float = 0.02,
num_steps: int = 200,
learning_rate: float = 0.001,
sample_rate: int = 16000
) -> tuple[np.ndarray, list[float]]:
"""
Eenvoudige gradient-aanval op Whisper.
Returns:
adversarial_audio: De verstoorde audio
loss_history: Verlieswaarden over de optimalisatiestappen
"""
device = "cuda" if torch.cuda.is_available() else "cpu"
# Bereid de audio-tensor voor
audio_tensor = torch.from_numpy(source_audio).float().to(device)
audio_tensor.requires_grad_(False)
# Initialiseer de verstoring
delta = torch.zeros_like(audio_tensor, requires_grad=True)
# Encodeer de doeltekst
tokenizer = whisper.tokenizer.get_tokenizer(
model.is_multilingual, language="en", task="transcribe"
)
target_tokens = tokenizer.encode(target_text)
target_ids = torch.tensor([target_tokens], device=device)
optimizer = torch.optim.Adam([delta], lr=learning_rate)
loss_history = []
for step in range(num_steps):
optimizer.zero_grad()
# Pas de verstoring toe
adv_audio = audio_tensor + delta
# Bereken het mel-spectrogram
mel = whisper.log_mel_spectrogram(adv_audio).to(device)
if mel.dim() == 2:
mel = mel.unsqueeze(0)
# Encodeer de audio
audio_features = model.encoder(mel)
# Decodeer met de doeltokens
# Bereken het verlies als cross-entropy met de doelreeks
logits = model.decoder(target_ids[:, :-1], audio_features)
loss = torch.nn.functional.cross_entropy(
logits.reshape(-1, logits.size(-1)),
target_ids[:, 1:].reshape(-1)
)
loss.backward()
loss_history.append(loss.item())
optimizer.step()
# Projecteer de verstoring naar de epsilon-bal
with torch.no_grad():
delta.data = torch.clamp(delta.data, -epsilon, epsilon)
# Zorg voor een geldig audiobereik
delta.data = torch.clamp(
audio_tensor + delta.data, -1.0, 1.0
) - audio_tensor
if step % 50 == 0:
print(f"Step {step}/{num_steps}, Loss: {loss.item():.4f}")
adv_audio = (audio_tensor + delta).detach().cpu().numpy()
return adv_audio, loss_history
# Voer de aanval uit
model = whisper.load_model("base")
source = np.random.randn(16000 * 3).astype(np.float32) * 0.01 # 3 sec noise
target = "the password is twelve thirty four"
adv_audio, losses = simple_whisper_attack(model, source, target)
# Verifieer
result = model.transcribe(adv_audio, language="en", fp16=False)
print(f"Target: '{target}'")
print(f"Actual transcription: '{result['text'].strip()}'")Oefening 3: Evaluatie van perceptuele kwaliteit
Meet of de adversariële audio detecteerbaar is door mensen:
import numpy as np
from scipy.signal import stft
def compute_snr(original: np.ndarray, adversarial: np.ndarray) -> float:
"""Bereken de Signal-to-Noise Ratio in dB."""
signal_power = np.mean(original ** 2)
noise_power = np.mean((adversarial - original) ** 2)
if noise_power < 1e-10:
return float('inf')
return 10 * np.log10(signal_power / noise_power)
def compute_spectral_distortion(
original: np.ndarray,
adversarial: np.ndarray,
sr: int = 16000
) -> float:
"""Bereken de log-spectrale vervorming tussen origineel en adversarieel."""
_, _, orig_stft = stft(original, fs=sr, nperseg=512)
_, _, adv_stft = stft(adversarial, fs=sr, nperseg=512)
orig_power = np.abs(orig_stft) ** 2 + 1e-10
adv_power = np.abs(adv_stft) ** 2 + 1e-10
lsd = np.sqrt(np.mean(
(10 * np.log10(orig_power) - 10 * np.log10(adv_power)) ** 2
))
return lsd
def evaluate_attack(
original: np.ndarray,
adversarial: np.ndarray,
model,
target_text: str
) -> dict:
"""Uitgebreide evaluatie van een adversariële audio-aanval."""
# Transcriptiesucces
result = model.transcribe(adversarial, language="en", fp16=False)
transcription = result["text"].strip().lower()
target_lower = target_text.lower()
# Exacte overeenkomst
exact_match = transcription == target_lower
# Gedeeltelijke overeenkomst (welk deel van de doelwoorden verschijnt)
target_words = set(target_lower.split())
trans_words = set(transcription.split())
word_overlap = len(target_words & trans_words) / len(target_words) if target_words else 0
# Perceptuele metrieken
snr = compute_snr(original, adversarial)
lsd = compute_spectral_distortion(original, adversarial)
max_perturbation = np.max(np.abs(adversarial - original))
return {
"target_text": target_text,
"transcription": transcription,
"exact_match": exact_match,
"word_overlap": f"{word_overlap:.1%}",
"snr_db": f"{snr:.1f}",
"log_spectral_distortion": f"{lsd:.2f}",
"max_perturbation": f"{max_perturbation:.4f}",
"imperceptible": snr > 30 and max_perturbation < 0.02
}Oefening 4: Robuustheidstesten
Test hoe goed adversariële audio realistische transformaties overleeft:
MP3-compressie
def test_mp3_robustness(adv_audio, sr=16000): """Test of de adversariële audio MP3-compressie overleeft.""" import io import soundfile as sf # Sla op als WAV, converteer naar MP3, laad terug sf.write("/tmp/adv_test.wav", adv_audio, sr) # Vereist ffmpeg import subprocess subprocess.run([ "ffmpeg", "-y", "-i", "/tmp/adv_test.wav", "-b:a", "128k", "/tmp/adv_test.mp3" ], capture_output=True) subprocess.run([ "ffmpeg", "-y", "-i", "/tmp/adv_test.mp3", "/tmp/adv_test_back.wav" ], capture_output=True) compressed, _ = sf.read("/tmp/adv_test_back.wav") return compressedAdditieve ruis
def test_noise_robustness(adv_audio, noise_levels=[0.001, 0.005, 0.01]): """Test adversariële audio onder additieve ruis.""" results = {} for noise_level in noise_levels: noisy = adv_audio + noise_level * np.random.randn(len(adv_audio)) noisy = np.clip(noisy, -1, 1).astype(np.float32) result = model.transcribe(noisy, language="en", fp16=False) results[noise_level] = result["text"].strip() return resultsResampling
def test_resample_robustness(adv_audio, original_sr=16000, target_srs=[8000, 22050, 44100]): """Test of de adversariële audio conversie van de sample rate overleeft.""" from scipy.signal import resample results = {} for target_sr in target_srs: ratio = target_sr / original_sr resampled = resample(adv_audio, int(len(adv_audio) * ratio)) back = resample(resampled, len(adv_audio)).astype(np.float32) result = model.transcribe(back, language="en", fp16=False) results[target_sr] = result["text"].strip() return resultsResultaten samenstellen
Maak een samenvattende tabel van de robuustheid over alle transformaties.
Verwachte resultaten
| Conditie | Verwacht succespercentage | Opmerkingen |
|---|---|---|
| Schoon (geen transformatie) | 70-95% | Met voldoende optimalisatiestappen |
| MP3 128kbps | 20-50% | Compressie vernietigt fijne verstoringen |
| Additieve ruis (SNR 30dB) | 40-70% | Milde ruis, gematigde degradatie |
| Additieve ruis (SNR 20dB) | 10-30% | Significante degradatie |
| Resample 16k->8k->16k | 15-35% | Verliest hoogfrequente verstoringsinhoud |
Verwante onderwerpen
- Adversarial Audio Examples -- theoretische fundamenten voor dit lab
- Speech Recognition Attacks -- ASR-aanvalsstrategieën
- Lab: Crafting Image-Based Injections -- parallel lab in het visuele domein
Referenties
- "Audio Adversarial Examples: Targeted Attacks on Speech-to-Text" - Carlini & Wagner (2018) - Core attack methodology applied in this lab
- "Robust Speech Recognition via Large-Scale Weak Supervision" - Radford et al. (2022) - Whisper model architecture targeted in lab exercises
- "Imperceptible, Robust, and Targeted Adversarial Examples for Automatic Speech Recognition" - Qin et al. (2019) - Psychoacoustic evaluation metrics used in quality assessment
- "Robust Audio Adversarial Example for a Physical Attack" - Yakura & Sakuma (2019) - Over-the-air robustness testing methodology
Knowledge Check
Waarom is MP3-compressie een aanzienlijke uitdaging voor adversariële audio-aanvallen?