Skip to main content

Training-free, gradient-free detector that separates adversarial (ADV) from out-of-distribution (OOD) inputs via first-layer dormant-band roughness.

Project description

Viyog

Separate adversarial (ADV) inputs from out-of-distribution (OOD) inputs — in one forward pass, with no training and no gradients.

Safety-critical systems must respond differently to two kinds of anomaly: OOD inputs call for abstention, adversarial inputs demand rejection. Standard detectors collapse both into a single anomaly score and cannot tell them apart. Viyog is a training-free, post-hoc second stage that makes the distinction by reading the dormant-band roughness of a model's first convolutional layer.

The idea: gradient-based attacks inject broadband high-frequency residue into the first-layer channels that are otherwise quiet on in-distribution data, making them spatially jagged. Natural inputs — both ID and OOD — leave those channels smooth. Viyog measures that roughness as a single scalar V(x):

  • higher V(x) → more likely ADV
  • lower V(x) → more likely OOD / ID

It adds no parameters, never touches the backward pass, and stores only O(C) bytes of state (the dormant-channel ranking + one ID mean) — roughly 0.3 KB, versus 4.5–40 MB for feature-distance detectors such as Mahalanobis / KNN / ViM.

Install

pip install viyog                 # core: torch + numpy
pip install "viyog[metrics]"      # + scikit-learn, for viyog_metrics()

Quickstart

from viyog import Viyog

v = Viyog(model)          # attaches a forward hook to the first conv layer
v.fit(id_loader)          # one ID pass: learn the dormant band + ID mean
scores = v.score(loader)  # per-sample roughness V(x); HIGHER => more adversarial
v.close()

Or as a context manager (hooks are removed automatically):

from viyog import Viyog, viyog_metrics

with Viyog(model) as v:
    v.fit(id_loader)
    ood_scores = v.score(ood_loader)
    adv_scores = v.score(adv_loader)

# separability report (OOD vs ADV): AUROC / AUPR / FPR@95 / AUTC
print(viyog_metrics(ood_scores.cpu().numpy(), adv_scores.cpu().numpy()))

How it works

For each input, Viyog hooks the first conv layer's activation map a of shape (B, C, H, W) and computes, per channel, a magnitude-normalised total variation (average absolute change between neighbouring pixels):

tv = (|Δ_h a| + |Δ_w a|) / (mean|a| + eps)      # spatial roughness, per channel
V(x) = mean over the dormant channels of tv     # the Viyog score

The dormant band is the quietest dorm_pct (default 10%) of channels ranked by their mean absolute activation on ID data, restricted to alive channels (permanently-dead channels — common in e.g. DenseNet first convs — are excluded so the statistic stays meaningful). .fit() learns that band and the ID mean of V in a single pass; .score() returns V(x) for new inputs.

Conv2d maps use 2-D total variation; Conv1d maps (B, C, L) use 1-D total variation, so the detector also applies to 1-D signal models.

API

Viyog(model, device=None, layer=None, dorm_pct=0.10, dead_thresh=1e-4) Wrap a model; auto-detects the first conv layer (prefers a conv1 attribute). Pass layer= (module or dotted name) to override.
.fit(id_loader) -> self One ID pass: selects the dormant band, records id_score_mean_.
.score(x, center=False) -> Tensor Per-sample V(x) for a batch or a whole loader. Higher = more adversarial. center=True subtracts the ID mean (monotone; AUROC-preserving).
.score_loader(loader, center=False) Convenience wrapper over a loader.
Viyog.bounded_score(scores, temperature=1.0) Optional monotone squash to (0, 1) for thresholding/display.
.close() / context manager Remove the forward hook.
viyog_metrics(neg, adv, recall_level=0.95) AUROC / AUPR_IN / AUPR_OUT / FPR95 / DetectionError / AUTC for two score populations. Needs viyog[metrics].

Fitted attributes: dorm_idx_, id_profile_, id_score_mean_, n_channels_, layer_name_.

Results

Across 20 architectures on CIFAR-100 (ResNet/DenseNet/ConvNeXt/Swin/ViT and edge backbones), the dormant-band roughness score reaches AUROC ≈ 0.966 for adversarial detection (ID vs ADV) and ≈ 0.824 for OOD vs ADV — where logit detectors (Energy/MSP/MaxLogit/GEN) are near-blind to adversarials (≤ 0.69) and feature-distance detectors cost 4.5–40 MB of state versus Viyog's ~0.3 KB. See the accompanying paper.

License

MIT — see LICENSE.

Citation

If you use Viyog in academic work, please cite the accompanying paper "Viyog: Separating Adversarial and Out-of-Distribution."

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

viyog-0.1.0.tar.gz (12.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

viyog-0.1.0-py3-none-any.whl (10.3 kB view details)

Uploaded Python 3

File details

Details for the file viyog-0.1.0.tar.gz.

File metadata

  • Download URL: viyog-0.1.0.tar.gz
  • Upload date:
  • Size: 12.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for viyog-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7726ea547b10b6887ad7cc537d77d7c9f46d75ac177873693ee7f01edfb3c1ee
MD5 62d6db26fea308612a4fad1598466a7c
BLAKE2b-256 2ff7f29f40008459e86489e50816cfa538c85a149db2f1df4fa8b170f4788443

See more details on using hashes here.

File details

Details for the file viyog-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: viyog-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.12 {"installer":{"name":"uv","version":"0.11.12","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for viyog-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6692cc37fe4a3147f66765aa2c096493b46f4ee3a3938c02d4556b4be728fe99
MD5 32321f3383b13de9c70139dfea7de1a5
BLAKE2b-256 35aa80e29729849d016d602f6bba84ae6b81694cc2c73a26be451ed00e0d0e2f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page