Upload a photo and watch a tiny, almost invisible perturbation fool a pretrained ImageNet classifier (MobileNet v2). Everything runs in your browser with TensorFlow.js. No server involved.
What is going on?
An image classifier is a differentiable function, so we can ask: which direction should each pixel move to make the model more wrong? The gradient of the loss with respect to the input pixels answers that. We then change every pixel by at most ε (on a 0–255 scale that's ε×255 levels), which is usually too small for you to notice.
FGSM (Fast Gradient Sign Method, Goodfellow et al. 2014): one step of size ε along the sign of the loss gradient. Fast, but weak: on MobileNet it often only dents the confidence without flipping the label.
PGD, untargeted (Projected Gradient Descent, Madry et al. 2017): start from a random point in the ε-ball, then take many small FGSM steps (α = max(ε/4, 0.5/255)), projecting back into the ε-ball and into [0,1] after each one. Much stronger.
Universal layer (paste) (Moosavi-Dezfooli et al. 2017): one fixed noise pattern δ with |δ| ≤ 10/255, trained once offline on ~900 ImageNet photos against this exact MobileNet v2. It is simply added to whatever image you load: clip(x + δ, 0, 1). No gradients, no per-image optimisation, so it runs instantly; it fools most (not all) images.
PGD, targeted: same, but descend the loss for a class you choose, e.g. turn a panda into a "toaster". It's harder, so use more steps.
The middle panel shows the perturbation amplified ×10 (grey means no change). Images are resized and center-cropped to 224×224. The adversarial image is quantized to 8-bit before it is re-classified, so the predictions shown are exactly what the downloadable PNG gets.
📷 Drop an image here or click to choose a file
Examples:
Same noise for every image. Paste via Attack, or download the layer from the page.
Raw PNG decode: δ = v/255 × 2ε − ε, ε = 10/255. The ε slider rescales the layer (trained at 10).