Perceptual Attack (ImageNet)

Apply neural perceptual attack to images taken from: Cassidy Laidlaw, Sahil Singla, and Soheil Feizi. “Perceptual adversarial robustness: Defense against unseen threat models.” arXiv preprint arXiv:2006.12655 (2020).

Note: the problem is the same to ex6_perceptual_attack.ipynb, the only difference is that the images to be attacked changed from the CIFAR-10 (3X32X32) to ImageNet (3X224X224)

Problem Description

Given a classifier \(f\) which maps any input image \(x \in X\) to its label \(y = f(x) \in Y\). The goal of neural perceptual attack is to find an input \(\widetilde{x}\) that is perceptually similar to the original input \(x\) but can fool the classifier \(f\). This can be formulated as:

\[\max_{\widetilde{x}} L (f(\widetilde{x}),y),\]
\[\text{s.t.}\;\; d(x,\widetilde{x}) = ||\phi(x) - \phi (\tilde{x}) ||_{2} \leq \epsilon\]

Here

\[L (f({x}),y) = \max_{i\neq y} (z_i(x) - z_y(x) ),\]

where \(z_i(x)\) is the \(i\)-th logit output of \(f(x)\), and \(\phi(\cdot)\) is a function that maps the input \(x\) to normalized, flattened activations

Modules Importing

Import all necessary modules and add PyGRANSO src folder to system path.

NOTE: the perceptual advex package (https://github.com/cassidylaidlaw/perceptual-advex.git) is required to calculate the distance

[1]:
# install required package
try:
    import perceptual_advex
except ImportError:
    !pip install perceptual-advex
[2]:
import time
import torch
import sys
## Adding PyGRANSO directories. Should be modified by user
sys.path.append('/home/buyun/Documents/GitHub/PyGRANSO')
from pygranso.pygranso import pygranso
from pygranso.pygransoStruct import pygransoStruct
from perceptual_advex.distances import normalize_flatten_features
from torchvision import transforms
from torchvision import datasets
import torch.nn as nn
from torchvision.models import resnet50
import os
import numpy as np

Model Initialization

Specify torch device, neural network architecture.

NOTE: please specify path for downloading data.

Use GPU for this problem. If no cuda device available, please set device = torch.device(‘cpu’)

[3]:
device = torch.device('cuda')

class ResNet_orig_LPIPS(nn.Module):
    def __init__(self, num_classes, pretrained=False):
        super().__init__()
        pretrained = bool(pretrained)
        print("Use pytorch pretrained weights: [{}]".format(pretrained))
        self.back = resnet50(pretrained=pretrained)
        self.back.fc = nn.Linear(2048,
                                 num_classes)
        # ===== Truncate the back and append the model to enable attack models
        model_list = list(self.back.children())
        self.head = nn.Sequential(
            *model_list[0:4]
        )
        self.layer1 = model_list[4]
        self.layer2 = model_list[5]
        self.layer3 = model_list[6]
        self.layer4 = model_list[7]
        self.tail = nn.Sequential(
            *[model_list[8],
              nn.Flatten(),
              model_list[9]]
            )
        # print()

    def features(self, x):
        """
            This function is called to produce perceptual features.
            Output ==> has to be a tuple of conv features.
        """
        x = x.type(self.back.fc.weight.dtype)
        x = self.head(x)
        x_layer1 = self.layer1(x)
        x_layer2 = self.layer2(x_layer1)
        x_layer3 = self.layer3(x_layer2)
        x_layer4 = self.layer4(x_layer3)
        return x_layer1, x_layer2, x_layer3, x_layer4

    def classifier(self, last_layer):
        last_layer = self.tail(last_layer)
        return last_layer

    def forward(self, x):
        return self.classifier(self.features(x)[-1])

    def features_logits(self, x):
        features = self.features(x)
        logits = self.classifier(features[-1])
        return features, logits

base_model = ResNet_orig_LPIPS(num_classes=100,pretrained=False).to(device)
Use pytorch pretrained weights: [False]

Download Pretrained Model

please download our pretrained model from the Google Drive [https://drive.google.com/file/d/1TZoKfVrqLgwKLa5-Y69uVOYb1xtOp8ty/view?usp=sharing] and add it to the “data/checkpoints/” path

[4]:
import os

# Download Pretrained model
if not os.path.exists('data/checkpoints/checkpoint.pth'):
    !mkdir -p data/checkpoints

pretrained_path = os.path.join("data/checkpoints/","checkpoint.pth")
state_dict = torch.load(pretrained_path)["model_state_dict"]
base_model.load_state_dict(state_dict)
[4]:
<All keys matched successfully>

Data Initialization

Download ImageNet 2012 validation data from [https://www.image-net.org/download.php], and put it under the directory

[5]:
# The ImageNet dataset is no longer publicly accessible.
# You need to download the archives externally and place them in the root directory
valset = datasets.ImageNet('/home/buyun/Documents/datasets/ImageNet/', split='val', transform=transforms.Compose([transforms.CenterCrop(224),transforms.ToTensor()]))
val_loader = torch.utils.data.DataLoader(valset, batch_size=1,shuffle=False, num_workers=0, collate_fn=None, pin_memory=False,)

# inputs, labels = next(iter(val_loader))

i=0
for inputs, labels in val_loader:
    i+=1
    if i > 2:
        break

# All the user-provided data (vector/matrix/tensor) must be in torch tensor format.
# As PyTorch tensor is single precision by default, one must explicitly set `dtype=torch.double`.
# Also, please make sure the device of provided torch tensor is the same as opts.torch_device.
inputs = inputs.to(device=device, dtype=torch.double)
labels = labels.to(device=device)

Function Set-Up

Encode the optimization variables, and objective and constraint functions.

Note: please strictly follow the format of comb_fn, which will be used in the PyGRANSO main algortihm.

[6]:
# variables and corresponding dimensions.
var_in = {"x_tilde": list(inputs.shape)}

def MarginLoss(logits,labels):
    correct_logits = torch.gather(logits, 1, labels.view(-1, 1))
    max_2_logits, argmax_2_logits = torch.topk(logits, 2, dim=1)
    top_max, second_max = max_2_logits.chunk(2, dim=1)
    top_argmax, _ = argmax_2_logits.chunk(2, dim=1)
    labels_eq_max = top_argmax.squeeze().eq(labels).float().view(-1, 1)
    labels_ne_max = top_argmax.squeeze().ne(labels).float().view(-1, 1)
    max_incorrect_logits = labels_eq_max * second_max + labels_ne_max * top_max
    loss = -(max_incorrect_logits - correct_logits).clamp(max=1).squeeze().sum()
    return loss

def user_fn(X_struct, inputs, labels, lpips_model, model, attack_type, eps=0.5):
    adv_inputs = X_struct.x_tilde
    epsilon = eps
    logits_outputs = model(adv_inputs)
    f = -torch.nn.functional.cross_entropy(logits_outputs,labels)

    # inequality constraint
    ci = pygransoStruct()
    if attack_type == 'L_2':
        ci.c1 = torch.norm((inputs - adv_inputs).reshape(inputs.size()[0], -1)) - epsilon
    elif attack_type == 'L_inf':
        ci.c1 = torch.norm((inputs - adv_inputs).reshape(inputs.size()[0], -1), float('inf')) - epsilon
    else:
        input_features = normalize_flatten_features( lpips_model.features(inputs)).detach()
        adv_features = lpips_model.features(adv_inputs)
        adv_features = normalize_flatten_features(adv_features)
        lpips_dists = (adv_features - input_features).norm(dim=1)
        ci.c1 = lpips_dists - epsilon

    # equality constraint
    ce = None
    return [f,ci,ce]

attack_type = "Perceptual"
var_in = {"x_tilde": list(inputs.shape)}

comb_fn = lambda X_struct : user_fn(X_struct, inputs, labels, lpips_model=base_model, model=base_model, attack_type=attack_type, eps=0.25)

User Options

Specify user-defined options for PyGRANSO

[7]:
opts = pygransoStruct()
opts.torch_device = device
opts.maxit = 50
opts.opt_tol = 1e-4
opts.viol_ineq_tol = 1e-4

opts.print_frequency = 1
opts.limited_mem_size = 100
opts.x0 = torch.reshape(inputs,(torch.numel(inputs),1))

Main Algorithm

[8]:
start = time.time()
soln = pygranso(var_spec = var_in,combined_fn = comb_fn,user_opts = opts)
end = time.time()
print("Total Wall Time: {}s".format(end - start))
/home/buyun/anaconda3/envs/cuosqp_pygranso/lib/python3.9/site-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /opt/conda/conda-bld/pytorch_1623448255797/work/c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)


╔═════ QP SOLVER NOTICE ════════════════════════════════════════════════════════════════════════╗
║  PyGRANSO requires a quadratic program (QP) solver that has a quadprog-compatible interface,  ║
║  the default is osqp. Users may provide their own wrapper for the QP solver.                  ║
║  To disable this notice, set opts.quadprog_info_msg = False                                   ║
╚═══════════════════════════════════════════════════════════════════════════════════════════════╝
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
PyGRANSO: A PyTorch-enabled port of GRANSO with auto-differentiation                                             ║
Version 1.0.0                                                                                                    ║
Licensed under the AGPLv3, Copyright (C) 2021 Tim Mitchell and Buyun Liang                                       ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Problem specifications:                                                                                          ║
 # of variables                     :   150528                                                                   ║
 # of inequality constraints        :        1                                                                   ║
 # of equality constraints          :        0                                                                   ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
Limited-memory mode enabled with size = 100.                                                                    NOTE: limited-memory mode is generally NOT                                                                      recommended for nonsmooth problems.                                                                              ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
   0 ║ 1.000000 │ -4.80460453033 ║ -4.80460453033 ║ 0.000000 │   -  ║ -  │     1 │ 0.000000 ║     1 │ 0.057810   ║
   1 ║ 1.000000 │ -5.02289369702 ║ -5.09825849533 ║ 0.075365 │   -  ║ S  │     8 │ 128.0000 ║     1 │ 0.068765   ║
   2 ║ 1.000000 │ -5.10931220651 ║ -5.18892860413 ║ 0.079616 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 2.263230   ║
   3 ║ 0.900000 │ -4.65131725669 ║ -5.26143741608 ║ 0.083976 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 1.721003   ║
   4 ║ 0.810000 │ -4.21337943316 ║ -5.29520893097 ║ 0.075740 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 1.344271   ║
   5 ║ 0.656100 │ -3.41613698316 ║ -5.31591892242 ║ 0.071637 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.981840   ║
   6 ║ 0.656100 │ -3.42843072743 ║ -5.32642793655 ║ 0.066239 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.930656   ║
   7 ║ 0.590490 │ -3.08791164958 ║ -5.33202886581 ║ 0.060598 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.921695   ║
   8 ║ 0.590490 │ -3.09513033334 ║ -5.33688926697 ║ 0.056249 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.645948   ║
   9 ║ 0.531441 │ -2.78549631699 ║ -5.31881809235 ║ 0.041142 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.684612   ║
  10 ║ 0.531441 │ -2.78857466976 ║ -5.30436611176 ║ 0.030383 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.796255   ║
  11 ║ 0.531441 │ -2.79267504363 ║ -5.30393171310 ║ 0.026052 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.648822   ║
  12 ║ 0.478297 │ -2.51310385083 ║ -5.29171037674 ║ 0.017905 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.873591   ║
  13 ║ 0.478297 │ -2.51675662998 ║ -5.28737258911 ║ 0.012177 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.507208   ║
  14 ║ 0.478297 │ -2.51902961657 ║ -5.28574705124 ║ 0.009127 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.470940   ║
  15 ║ 0.478297 │ -2.51948343044 ║ -5.27883243561 ║ 0.005366 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.755772   ║
  16 ║ 0.478297 │ -2.52180545379 ║ -5.27433776855 ║ 8.94e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.522509   ║
  17 ║ 0.478297 │ -2.52266402426 ║ -5.27607917786 ║ 8.68e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.562135   ║
  18 ║ 0.478297 │ -2.52331655260 ║ -5.27712249756 ║ 7.15e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.640119   ║
  19 ║ 0.478297 │ -2.52508383118 ║ -5.28049087524 ║ 5.59e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.376358   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
  20 ║ 0.478297 │ -2.52592374812 ║ -5.28209209442 ║ 4.85e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.567079   ║
  21 ║ 0.478297 │ -2.52743937104 ║ -5.28514623642 ║ 4.30e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.376924   ║
  22 ║ 0.478297 │ -2.52794353477 ║ -5.28616523743 ║ 4.13e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.425617   ║
  23 ║ 0.478297 │ -2.52851810569 ║ -5.28692817688 ║ 2.03e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.403485   ║
  24 ║ 0.478297 │ -2.52894009955 ║ -5.28788423538 ║ 2.39e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.368226   ║
  25 ║ 0.478297 │ -2.52954291213 ║ -5.28899097443 ║ 1.65e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.352124   ║
  26 ║ 0.478297 │ -2.53024821349 ║ -5.29092168808 ║ 3.83e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.417472   ║
  27 ║ 0.430467 │ -2.27777062631 ║ -5.29199790955 ║ 2.61e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.351817   ║
  28 ║ 0.430467 │ -2.27805853884 ║ -5.29264736176 ║ 2.53e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.352205   ║
  29 ║ 0.430467 │ -2.27851161771 ║ -5.29353809357 ║ 1.83e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.274891   ║
  30 ║ 0.430467 │ -2.27851780058 ║ -5.29353189468 ║ 1.74e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.437807   ║
  31 ║ 0.430467 │ -2.27885845614 ║ -5.29418659210 ║ 1.15e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.372692   ║
  32 ║ 0.430467 │ -2.27890671595 ║ -5.29456233978 ║ 2.29e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.429820   ║
  33 ║ 0.430467 │ -2.27938141112 ║ -5.29549407959 ║ 1.55e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.341113   ║
  34 ║ 0.430467 │ -2.27957904527 ║ -5.29618692398 ║ 2.56e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.365854   ║
  35 ║ 0.430467 │ -2.27968976451 ║ -5.29674577713 ║ 3.86e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.414915   ║
  36 ║ 0.430467 │ -2.28028482803 ║ -5.29760169983 ║ 1.59e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.248704   ║
  37 ║ 0.430467 │ -2.28030400104 ║ -5.29756212234 ║ 1.23e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.272889   ║
  38 ║ 0.430467 │ -2.28052128382 ║ -5.29804182053 ║ 1.12e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.395469   ║
  39 ║ 0.430467 │ -2.28076030179 ║ -5.29852867126 ║ 8.26e-05 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.251219   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
     ║ <--- Penalty Function --> ║                ║ Total Violation ║ <--- Line Search ---> ║ <- Stationarity -> ║
Iter ║    Mu    │      Value     ║    Objective   ║   Ineq   │  Eq  ║ SD │ Evals │     t    ║ Grads │    Value   ║
═════╬═══════════════════════════╬════════════════╬═════════════════╬═══════════════════════╬════════════════════╣
  40 ║ 0.430467 │ -2.28096388594 ║ -5.29900264740 ║ 8.30e-05 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.239531   ║
  41 ║ 0.430467 │ -2.28101834353 ║ -5.29912853241 ║ 8.27e-05 │   -  ║ S  │     3 │ 0.250000 ║     1 │ 0.296939   ║
  42 ║ 0.430467 │ -2.28116168313 ║ -5.29953670502 ║ 1.15e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.378612   ║
  43 ║ 0.430467 │ -2.28132247430 ║ -5.29990005493 ║ 1.11e-04 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.231024   ║
  44 ║ 0.430467 │ -2.28134451518 ║ -5.30000782013 ║ 1.35e-04 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.358835   ║
  45 ║ 0.430467 │ -2.28139962373 ║ -5.30003690720 ║ 9.25e-05 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.327774   ║
  46 ║ 0.430467 │ -2.28158747497 ║ -5.30044269562 ║ 7.93e-05 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.232565   ║
  47 ║ 0.430467 │ -2.28171023665 ║ -5.30071306229 ║ 7.29e-05 │   -  ║ S  │     2 │ 0.500000 ║     1 │ 0.207934   ║
  48 ║ 0.430467 │ -2.28173579964 ║ -5.30068445206 ║ 3.50e-05 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.265855   ║
  49 ║ 0.430467 │ -2.28181937164 ║ -5.30095052719 ║ 6.60e-05 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.297942   ║
  50 ║ 0.430467 │ -2.28198693983 ║ -5.30132913589 ║ 6.14e-05 │   -  ║ S  │     1 │ 1.000000 ║     1 │ 0.207075   ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
F = final iterate, B = Best (to tolerance), MF = Most Feasible                                                   ║
Optimization results:                                                                                            ║
═════╦═══════════════════════════╦════════════════╦═════════════════╦═══════════════════════╦════════════════════╣
   F ║          │                ║ -5.30132913589 ║ 6.14e-05 │   -  ║    │       │          ║       │            ║
   B ║          │                ║ -5.30132913589 ║ 6.14e-05 │   -  ║    │       │          ║       │            ║
  MF ║          │                ║ -4.98130512238 ║ 0.000000 │   -  ║    │       │          ║       │            ║
═════╩═══════════════════════════╩════════════════╩═════════════════╩═══════════════════════╩════════════════════╣
Iterations:              50                                                                                      ║
Function evaluations:    64                                                                                      ║
PyGRANSO termination code: 4 --- max iterations reached.                                                         ║
═════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
Total Wall Time: 8.042240381240845s

Results Visualization

Visualize the original image and the perceptual attacked image

[9]:
import matplotlib.pyplot as plt

def rescale_array(array):
    ele_min, ele_max = np.amin(array), np.amax(array)
    array = (array - ele_min) / (ele_max - ele_min)
    return array

def tensor2img(tensor):
    tensor = torch.nn.functional.interpolate(
        tensor,
        scale_factor=3,
        mode="bilinear"
    )
    array = tensor.detach().cpu().numpy()[0, :, :, :]
    array = np.transpose(array, [1, 2, 0])
    return array

final_adv_input = torch.reshape(soln.final.x,inputs.shape)

ori_image = rescale_array(tensor2img(inputs))
adv_image = rescale_array(tensor2img(final_adv_input))

f = plt.figure()
f.add_subplot(1,2,1)
plt.imshow(ori_image)
plt.title('Original Image')
plt.axis('off')
f.add_subplot(1,2,2)
plt.imshow(adv_image)
plt.title('Adversarial Attacked Image')
plt.axis('off')
plt.show()
/home/buyun/anaconda3/envs/cuosqp_pygranso/lib/python3.9/site-packages/torch/nn/functional.py:3609: UserWarning: Default upsampling behavior when mode=bilinear is changed to align_corners=False since 0.4.0. Please specify align_corners=True if the old behavior is desired. See the documentation of nn.Upsample for details.
  warnings.warn(
../_images/examples_ex12_perceptual_attack_ImageNet_19_1.png