Spaces:
Runtime error
Runtime error
| """ | |
| REST API for adversarial-vs-clean detection on CIFAR-style tensors. | |
| Serve with:: | |
| uvicorn adverscan.api.main:app --reload | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from functools import lru_cache | |
| import numpy as np | |
| import torch | |
| import torch.nn as nn | |
| from fastapi import FastAPI | |
| from numpy.typing import NDArray | |
| from pydantic import BaseModel, Field | |
| from sklearn.linear_model import LogisticRegression | |
| from sklearn.pipeline import Pipeline | |
| from sklearn.preprocessing import StandardScaler | |
| from adverscan.attacks import build_pretrained_cifar10_resnet18 | |
| from adverscan.detector.feature_extractor import FEATURE_DIM, assemble_extracted_features | |
| from adverscan.detector.model import AdversarialDetector | |
| app = FastAPI(title="AdverScan Detector API", version="0.1.0") | |
| class InferenceTensor(BaseModel): | |
| pixels: list[float] | |
| shape: tuple[int, int, int] | |
| class InferenceRequest(BaseModel): | |
| tensor: InferenceTensor | |
| class DetectionResponse(BaseModel): | |
| adversarial_probability: float = Field(ge=0.0, le=1.0) | |
| feature_dim: int | |
| softmax_entropy: float | |
| softmax_margin: float | |
| gradient_l2: float | |
| prediction_consistency: float | |
| def _bootstrap_pipeline() -> Pipeline: | |
| rng = np.random.default_rng(seed=4242) | |
| phantom_x = rng.normal(size=(256, FEATURE_DIM)).astype(np.float32) | |
| phantom_y = rng.integers(low=0, high=2, size=phantom_x.shape[0], dtype=np.int64) | |
| pipe = Pipeline( | |
| steps=[("scale", StandardScaler()), ("lr", LogisticRegression(max_iter=4000, class_weight="balanced", random_state=13))] | |
| ) | |
| pipe.fit(phantom_x, phantom_y) | |
| return pipe | |
| def detector_bundle() -> AdversarialDetector: | |
| artifact = os.getenv("ADVERSCAN_DETECTOR_ARTIFACT", "artifacts/detector.joblib") | |
| if os.path.isfile(artifact): | |
| return AdversarialDetector.load(artifact) | |
| clf = _bootstrap_pipeline() | |
| detector_holder = AdversarialDetector(backend="logistic_regression", pipeline=clf, val_metrics={}, train_metrics={}) | |
| os.makedirs(os.path.dirname(artifact) or ".", exist_ok=True) | |
| detector_holder.save(artifact) | |
| return detector_holder | |
| def cached_victim() -> nn.Module: | |
| victim_module, succeeded = build_pretrained_cifar10_resnet18() | |
| if not succeeded: | |
| import warnings | |
| warnings.warn("Continuing with randomly initialized ResNet-18 stub (no pretrained CIFAR-10 weights resolved).") | |
| return victim_module.eval() | |
| def predict(req: InferenceRequest) -> DetectionResponse: | |
| accelerator = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| stacked_tensor = torch.tensor(req.tensor.pixels, dtype=torch.float32, device=accelerator).view(*req.tensor.shape) | |
| victim_net_nn = cached_victim().to(accelerator) | |
| feats_torch_stack = assemble_extracted_features(victim_net_nn, stacked_tensor.unsqueeze(0)) | |
| feats_row_np: NDArray[np.floating] = feats_torch_stack.detach().cpu().numpy()[0] | |
| probs_scalar_vector = detector_bundle().predict_adversarial_score(np.expand_dims(feats_row_np, axis=0)) | |
| det_prob_f = float(probs_scalar_vector[0]) | |
| return DetectionResponse( | |
| adversarial_probability=det_prob_f, | |
| feature_dim=FEATURE_DIM, | |
| softmax_entropy=float(feats_row_np[0]), | |
| softmax_margin=float(feats_row_np[1]), | |
| gradient_l2=float(feats_row_np[2]), | |
| prediction_consistency=float(feats_row_np[3]), | |
| ) | |
| def healthz() -> dict[str, str]: | |
| return {"status": "ok"} | |