Grimoire
Sheet
pathadversarial/evasion
difficultyintermediate
timeO(k·d)
described2017
revised4w ago

Projected Gradient Descent Attack

Ascend the loss with respect to the input, project back into the ε-ball after every step, and restart from random points inside it.

Theory

PGD is the standard first-order attack and, empirically, close to the strongest one within its threat model. Each step takes a signed gradient step of size α\alpha and projects back onto the feasible set.

xt+1=Πxε(xt+αsign(xL(θ,xt,y)))x^{t+1} = \Pi_{\|\cdot - x\|_\infty \le \varepsilon} \Bigl( x^{t} + \alpha \cdot \mathrm{sign}\bigl( \nabla_x L(\theta, x^{t}, y) \bigr) \Bigr)
eq. 1 — L∞ variantMadry et al. §2

Random restarts matter: the inner maximisation is non-concave, and a single run from the clean input systematically underestimates the attainable loss. Adversarial training frames the whole thing as a saddle-point problem with PGD as the inner solver.

minθ  E[maxδεL(θ,x+δ,y)]\min_{\theta} \; \E \Bigl[ \max_{\|\delta\| \le \varepsilon} L(\theta, x + \delta, y) \Bigr]
eq. 2 — the robust objective
Implementation
python · torch ≥ 2.1
import torch
from torch import Tensor


def pgd(model, x: Tensor, y: Tensor, loss_fn,
        eps: float = 8 / 255, alpha: float = 2 / 255,
        steps: int = 20, restarts: int = 5) -> Tensor:
    best_adv, best_loss = x.clone(), torch.full((x.size(0),), -1e30)

    for _ in range(restarts):
        delta = torch.empty_like(x).uniform_(-eps, eps)
        delta = (x + delta).clamp(0, 1) - x

        for _ in range(steps):
            delta.requires_grad_(True)
            loss = loss_fn(model(x + delta), y)
            grad, = torch.autograd.grad(loss.sum(), delta)
            delta = (delta.detach() + alpha * grad.sign()).clamp(-eps, eps)
            delta = (x + delta).clamp(0, 1) - x     # stay a valid image

        with torch.no_grad():
            l = loss_fn(model(x + delta), y)
        improved = l > best_loss
        best_loss = torch.where(improved, l, best_loss)
        best_adv[improved] = (x + delta)[improved]

    return best_adv

Two clamps per step, in this order: the ε-ball first, then the valid input range. Reversing them lets the perturbation drift outside the threat model. Keep the per-example best rather than the last iterate — the loss is not monotone under a fixed step size, and reporting the final step understates the attack.

Related
References
[1]Madry et al. — Towards Deep Learning Models Resistant to Adversarial Attacks (2017)arXiv:1706.06083
[2]Goodfellow et al. — Explaining and Harnessing Adversarial Examples (2014)arXiv:1412.6572
[3]Croce & Hein — Reliable Evaluation with an Ensemble of Diverse Parameter-free Attacks (2020)arXiv:2003.01690