Skip to content

Backends

ASRBackend

Bases: ABC

CTC model wrapper used by every attack.

logits abstractmethod

logits(audio: Tensor) -> torch.Tensor

Return log-unnormalized CTC logits of shape (batch, time, vocab).

Source code in src/asr_attacks/backends/base.py
@abstractmethod
def logits(self, audio: torch.Tensor) -> torch.Tensor:
    """Return log-unnormalized CTC logits of shape ``(batch, time, vocab)``."""

encode abstractmethod

encode(transcript: str) -> torch.Tensor

Return 1-D token ids for a CTC target sequence.

Source code in src/asr_attacks/backends/base.py
@abstractmethod
def encode(self, transcript: str) -> torch.Tensor:
    """Return 1-D token ids for a CTC target sequence."""

decode abstractmethod

decode(audio: Tensor) -> str

Greedy CTC decode of audio without tracking gradients.

Source code in src/asr_attacks/backends/base.py
@abstractmethod
def decode(self, audio: torch.Tensor) -> str:
    """Greedy CTC decode of ``audio`` without tracking gradients."""

silence_ids

silence_ids() -> list[int]

Token ids treated as silence for the empty-target CW attack (Sec. III-F).

Source code in src/asr_attacks/backends/base.py
def silence_ids(self) -> list[int]:
    """Token ids treated as silence for the empty-target CW attack (Sec. III-F)."""
    return [self.blank_id]

CTCModuleBackend

CTCModuleBackend(model: Module, labels: list[str] | tuple[str, ...], device: device | str | None = None, blank_id: int | None = None, freeze_model: bool = True)

Bases: ASRBackend

Wrap a PyTorch CTC module such as torchaudio wav2vec2.

labels must be the model's vocabulary in index order (bundle.get_labels() for torchaudio wav2vec2). Encoding uses this vocabulary; it is not hardcoded. Logits must be (batch, time, vocab) with vocab == len(labels).

Source code in src/asr_attacks/backends/module.py
def __init__(
    self,
    model: torch.nn.Module,
    labels: list[str] | tuple[str, ...],
    device: torch.device | str | None = None,
    blank_id: int | None = None,
    freeze_model: bool = True,
) -> None:
    if device is None:
        device = next(model.parameters()).device
    self.device = torch.device(device)
    self.model = model.to(self.device)
    self.model.eval()
    self.labels = list(labels)
    if not self.labels:
        raise ValueError("labels must be a non-empty vocabulary")
    self._char_to_id = {char: index for index, char in enumerate(self.labels)}
    if blank_id is None:
        blank_id = self._char_to_id.get("-", 0)
    self.blank_id = blank_id
    if freeze_model:
        for parameter in self.model.parameters():
            parameter.requires_grad_(False)

HuggingFaceCTCBackend

HuggingFaceCTCBackend(model: str | Module, device: device | str = 'cpu', processor=None, tokenizer=None, freeze_model: bool = True)

Bases: ASRBackend

Wrap a Hugging Face AutoModelForCTC checkpoint.

Install the optional extra: pip install asr-attacks[hf].

Most wav2vec2-family checkpoints expect zero-mean, unit-variance input. When the processor's feature extractor has do_normalize=True the backend applies that normalization inside :meth:logits (in autograd), so attacks perturb the raw waveform and optimize the loss of the normalized audio the model actually receives.

Source code in src/asr_attacks/backends/huggingface.py
def __init__(
    self,
    model: str | torch.nn.Module,
    device: torch.device | str = "cpu",
    processor=None,
    tokenizer=None,
    freeze_model: bool = True,
) -> None:
    try:
        from transformers import AutoModelForCTC, AutoProcessor, AutoTokenizer
    except ImportError as exc:
        raise ImportError(
            "Hugging Face support requires the 'hf' extra: pip install asr-attacks[hf]"
        ) from exc

    self.device = torch.device(device)
    if isinstance(model, str):
        self.model = AutoModelForCTC.from_pretrained(model).to(self.device)
        self.processor = processor or AutoProcessor.from_pretrained(model)
        self.tokenizer = tokenizer or AutoTokenizer.from_pretrained(model)
    else:
        if processor is None or tokenizer is None:
            raise ValueError(
                "Pass processor and tokenizer when wrapping an already-loaded model"
            )
        self.model = model.to(self.device)
        self.processor = processor
        self.tokenizer = tokenizer
    self.model.eval()
    pad_id = getattr(self.model.config, "pad_token_id", None)
    self.blank_id = int(pad_id) if pad_id is not None else 0
    if freeze_model:
        for parameter in self.model.parameters():
            parameter.requires_grad_(False)