From 2678626591eca2b61f79f9cc2206eb3f702d9bac Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 19 Apr 2023 18:15:09 +0000
Subject: [PATCH 01/55] initial commit
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/__init__.py | 1 +
.../smoothed_vision_transformers/pytorch.py | 77 +++++++++++++++++++
.../smooth_vit.py | 20 +++++
3 files changed, 98 insertions(+)
create mode 100644 art/estimators/certification/smoothed_vision_transformers/__init__.py
create mode 100644 art/estimators/certification/smoothed_vision_transformers/pytorch.py
create mode 100644 art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
diff --git a/art/estimators/certification/smoothed_vision_transformers/__init__.py b/art/estimators/certification/smoothed_vision_transformers/__init__.py
new file mode 100644
index 0000000000..c421f11a28
--- /dev/null
+++ b/art/estimators/certification/smoothed_vision_transformers/__init__.py
@@ -0,0 +1 @@
+from smooth_vit import PyTorchSmoothedViT
\ No newline at end of file
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
new file mode 100644
index 0000000000..6f6012289f
--- /dev/null
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -0,0 +1,77 @@
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import logging
+from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+import random
+
+import numpy as np
+from tqdm import tqdm
+
+from art.estimators.classification.pytorch import PyTorchClassifier
+from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+
+
+class PyTorchSmoothedViT(PyTorchClassifier):
+ def __init__(
+ self,
+ model: "torch.nn.Module",
+ loss: "torch.nn.modules.loss._Loss",
+ input_shape: Tuple[int, ...],
+ nb_classes: int,
+ ablation_type: str,
+ ablation_size: int,
+ threshold: float,
+ logits: bool,
+ optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore
+ channels_first: bool = True,
+ clip_values: Optional["CLIP_VALUES_TYPE"] = None,
+ preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
+ postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
+ preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
+ device_type: str = "gpu",
+ ):
+ """
+ Create a smoothed ViT classifier.
+
+ :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits
+ output should be preferred where possible to ensure attack efficiency.
+ :param loss: The loss function for which to compute gradients for training. The target label must be raw
+ categorical, i.e. not converted to one-hot encoding.
+ :param input_shape: The shape of one input instance.
+ :param nb_classes: The number of classes of the model.
+ :param ablation_type: The type of ablation to perform, must be either "column" or "block"
+ :param ablation_size: The size of the data portion to retain after ablation. Will be a column of size N for
+ "column" ablation type or a NxN square for ablation of type "block"
+ :param threshold: The minimum threshold to count a prediction.
+ :param logits: if the model returns logits or normalized probabilities
+ :param optimizer: The optimizer used to train the classifier.
+ :param channels_first: Set channels first or last.
+ :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
+ maximum values allowed for features. If floats are provided, these will be used as the range of all
+ features. If arrays are provided, each value will be considered the bound for a feature, thus
+ the shape of clip values needs to match the total number of features.
+ :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
+ :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
+ :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
+ used for data preprocessing. The first value will be subtracted from the input. The input will then
+ be divided by the second one.
+ :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
+ """
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ logits=logits,
+ )
+
+ self.ablation_type = ablation_type
+ self.ablation_size = ablation_size,
+ self.threshold = threshold
\ No newline at end of file
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
new file mode 100644
index 0000000000..6ea62a146b
--- /dev/null
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -0,0 +1,20 @@
+
+"""
+This module implements Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link: https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+"""
+import torch as nn
+
+class ColumnAblator(nn.Module):
+ """
+ Pure Pytorch implementation of stripe/column ablation.
+ """
+ def __init__(self, ablation_size: int, channels_first: bool, row_ablation_mode: bool = False):
+ super().__init__()
+ self.ablation_size = ablation_size
+ self.channels_first = channels_first
+ self.row_ablation_mode = row_ablation_mode
+
+ def forward(self):
+ raise NotImplementedError
From 198349bc69a62b994cdcce4c8cc64b1e9fa422a4 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Mon, 24 Apr 2023 10:55:55 +0000
Subject: [PATCH 02/55] train and upsample methods
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/__init__.py | 2 +-
.../smoothed_vision_transformers/pytorch.py | 112 +++++++++++++++++-
.../smooth_vit.py | 36 +++++-
dev.py | 91 ++++++++++++++
4 files changed, 232 insertions(+), 9 deletions(-)
create mode 100644 dev.py
diff --git a/art/estimators/certification/smoothed_vision_transformers/__init__.py b/art/estimators/certification/smoothed_vision_transformers/__init__.py
index c421f11a28..fd2b959474 100644
--- a/art/estimators/certification/smoothed_vision_transformers/__init__.py
+++ b/art/estimators/certification/smoothed_vision_transformers/__init__.py
@@ -1 +1 @@
-from smooth_vit import PyTorchSmoothedViT
\ No newline at end of file
+from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT
\ No newline at end of file
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 6f6012289f..0859d9496d 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -9,6 +9,9 @@
from art.estimators.classification.pytorch import PyTorchClassifier
from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+from art.utils import check_and_transform_label_format
+
+logger = logging.getLogger(__name__)
class PyTorchSmoothedViT(PyTorchClassifier):
@@ -69,9 +72,114 @@ def __init__(
postprocessing_defences=postprocessing_defences,
preprocessing=preprocessing,
device_type=device_type,
- logits=logits,
)
self.ablation_type = ablation_type
self.ablation_size = ablation_size,
- self.threshold = threshold
\ No newline at end of file
+ self.threshold = threshold
+ self.logits = logits
+
+ print(self.model)
+ self.ablator = ColumnAblator(ablation_size=ablation_size,
+ channels_first=True,
+ row_ablation_mode=False)
+
+ def fit( # pylint: disable=W0221
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ batch_size: int = 128,
+ nb_epochs: int = 10,
+ training_mode: bool = True,
+ drop_last: bool = False,
+ scheduler: Optional[Any] = None,
+ verbose=True,
+ **kwargs,
+ ) -> None:
+ """
+ Fit the classifier on the training set `(x, y)`.
+ :param x: Training data.
+ :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
+ shape (nb_samples,).
+ :param batch_size: Size of batches.
+ :param nb_epochs: Number of epochs to use for training.
+ :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
+ :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
+ the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
+ the last batch will be smaller. (default: ``False``)
+ :param scheduler: Learning rate scheduler to run at the start of every epoch.
+ :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
+ and providing it takes no effect.
+ """
+ import torch
+
+ # Set model mode
+ self._model.train(mode=training_mode)
+
+ if self._optimizer is None: # pragma: no cover
+ raise ValueError("An optimizer is needed to train the model, but none for provided.")
+
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = len(x_preprocessed) / float(batch_size)
+ if drop_last:
+ num_batch = int(np.floor(num_batch))
+ else:
+ num_batch = int(np.ceil(num_batch))
+ ind = np.arange(len(x_preprocessed))
+
+ # Start training
+ for _ in tqdm(range(nb_epochs)):
+ # Shuffle the examples
+ random.shuffle(ind)
+ pbar = tqdm(range(num_batch), disable=not verbose)
+
+ # Train for one epoch
+ for m in pbar:
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size: (m + 1) * batch_size]])).to(self._device)
+ i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+
+ o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size: (m + 1) * batch_size]]).to(
+ self._device)
+
+ # Zero the parameter gradients
+ self._optimizer.zero_grad()
+
+ # Perform prediction
+ try:
+ model_outputs = self._model(i_batch)
+ except ValueError as err:
+ if "Expected more than 1 value per channel when training" in str(err):
+ logger.exception(
+ "Try dropping the last incomplete batch by setting drop_last=True in "
+ "method PyTorchClassifier.fit."
+ )
+ raise err
+ # Form the loss function
+ loss = self._loss(model_outputs[-1], o_batch)
+
+ # Do training
+ if self._use_amp: # pragma: no cover
+ from apex import amp # pylint: disable=E0611
+
+ with amp.scale_loss(loss, self._optimizer) as scaled_loss:
+ scaled_loss.backward()
+
+ else:
+ loss.backward()
+
+ self._optimizer.step()
+
+ if verbose:
+ pbar.set_description(
+ f"Loss {loss}:.2f"
+ )
+
+ if scheduler is not None:
+ scheduler.step()
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index 6ea62a146b..d8dd2c5398 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -1,12 +1,24 @@
-
"""
This module implements Certified Patch Robustness via Smoothed Vision Transformers
-| Paper link: https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
-import torch as nn
+import torch
+
+
+class UpSampler(torch.nn.Module):
+ def __init__(self, input_size, final_size):
+ super(UpSampler, self).__init__()
+ self.upsample = torch.nn.Upsample(scale_factor=final_size/input_size)
-class ColumnAblator(nn.Module):
+ def forward(self, x):
+ return self.upsample(x)
+
+
+class ColumnAblator(torch.nn.Module):
"""
Pure Pytorch implementation of stripe/column ablation.
"""
@@ -15,6 +27,18 @@ def __init__(self, ablation_size: int, channels_first: bool, row_ablation_mode:
self.ablation_size = ablation_size
self.channels_first = channels_first
self.row_ablation_mode = row_ablation_mode
+ self.upsample = UpSampler(input_size=32, final_size=224)
+
+ def ablate(self, x, column_pos):
+ k = self.ablation_size
+ if column_pos + k > x.shape[-1]:
+ x[:, :, :, (column_pos + k) % x.shape[-1]:column_pos] = 0.0
+ else:
+ x[:, :, :, :column_pos] = 0.0
+ x[:, :, :, column_pos + k:] = 0.0
+ return x
- def forward(self):
- raise NotImplementedError
+ def forward(self, x, column_pos):
+ x = self.ablate(x, column_pos=column_pos)
+ x = self.upsample(x)
+ return x
diff --git a/dev.py b/dev.py
new file mode 100644
index 0000000000..625095a95f
--- /dev/null
+++ b/dev.py
@@ -0,0 +1,91 @@
+
+# https://github.com/huggingface/pytorch-image-models
+import torch
+import torch.nn as nn
+
+from timm.models.vision_transformer import VisionTransformer, vit_small_patch16_224
+from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT
+import copy
+import numpy as np
+from torchvision import datasets
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+model = vit_small_patch16_224(pretrained=True)
+print(type(model))
+
+
+def get_cifar_data():
+ """
+ Get CIFAR-10 data.
+ :return: cifar train/test data.
+ """
+ train_set = datasets.CIFAR10('./data', train=True, download=True)
+ test_set = datasets.CIFAR10('./data', train=False, download=True)
+
+ x_train = train_set.data.astype(np.float32)
+ y_train = np.asarray(train_set.targets)
+
+ x_test = test_set.data.astype(np.float32)
+ y_test = np.asarray(test_set.targets)
+
+ x_train = np.moveaxis(x_train, [3], [1])
+ x_test = np.moveaxis(x_test, [3], [1])
+
+ x_train = x_train / 255.0
+ x_test = x_test / 255.0
+
+ return (x_train, y_train), (x_test, y_test)
+
+
+def update_batchnorm(model, x):
+ import random
+ from tqdm import tqdm
+
+ art_model.model.train()
+ batch_size = 32
+
+ ind = np.arange(len(x))
+ num_batch = int(len(x) / float(batch_size))
+
+ print('updating batchnorm')
+ with torch.no_grad():
+ for _ in tqdm(range(200)):
+ for m in tqdm(range(num_batch)):
+ i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size: (m + 1) * batch_size]])).to(device)
+ i_batch = art_model.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+ art_model.model(i_batch.cuda())
+ return model
+
+(x_train, y_train), (x_test, y_test) = get_cifar_data()
+x_test = torch.from_numpy(x_test)
+print('params: ', model.parameters())
+optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
+scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 20], gamma=0.1)
+
+# Use same initial point as Madry
+checkpoint = torch.hub.load_state_dict_from_url(
+ url="https://dl.fbaipublicfiles.com/deit/deit_small_patch16_224-cd65a155.pth",
+ map_location="cpu", check_hash=True
+)
+model.load_state_dict(checkpoint["model"])
+
+art_model = PyTorchSmoothedViT(model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ input_shape=(3, 224, 224),
+ optimizer=optimizer,
+ nb_classes=10,
+ ablation_type='column',
+ ablation_size=4,
+ threshold=0.01,
+ logits=True)
+
+ablated_x = art_model.ablator.ablate(x=copy.deepcopy(x_test[:32]),
+ column_pos=1)
+
+print('test position 31')
+
+ablated_x = art_model.ablator.ablate(x=copy.deepcopy(x_test[:32]),
+ column_pos=31)
+
+art_model = update_batchnorm(art_model, x_train)
+art_model.fit(x_train, y_train)
From 114b696c91f7edd575a6ba15a4b927c6b529408d Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Sat, 6 May 2023 14:56:52 +0000
Subject: [PATCH 03/55] adding certification method to smoothed_vit
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/__init__.py | 2 +-
.../smoothed_vision_transformers/smooth_vit.py | 18 ++++++++++++++++--
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/__init__.py b/art/estimators/certification/smoothed_vision_transformers/__init__.py
index fd2b959474..fc9a45bb4f 100644
--- a/art/estimators/certification/smoothed_vision_transformers/__init__.py
+++ b/art/estimators/certification/smoothed_vision_transformers/__init__.py
@@ -1 +1 @@
-from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT
\ No newline at end of file
+from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT, ArtViT
\ No newline at end of file
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index d8dd2c5398..d871c54158 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -39,6 +39,20 @@ def ablate(self, x, column_pos):
return x
def forward(self, x, column_pos):
+ assert x.shape[1] == 3
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).cuda()
+ x = torch.cat([x, ones], dim=1)
x = self.ablate(x, column_pos=column_pos)
- x = self.upsample(x)
- return x
+ return self.upsample(x)
+
+ def certify(self, predictions, size_to_certify, label):
+
+ num_of_classes = predictions.shape[-1]
+
+ top_class_counts, top_predicted_class = predictions.kthvalue(num_of_classes, dim=1)
+ second_class_counts, second_predicted_class = predictions.kthvalue(num_of_classes - 1, dim=1)
+
+ cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
+ cert_and_correct = cert & label == top_predicted_class
+
+ return cert, cert_and_correct
From cda2e300197fb8f96657f5ada03cf7a9c95a2bf0 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 10 May 2023 14:58:15 +0000
Subject: [PATCH 04/55] initial ViT functionality
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 257 +++++++++++++++++-
1 file changed, 251 insertions(+), 6 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 0859d9496d..1835844640 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -1,8 +1,13 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
+import sys
from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
import random
+import torch
+import copy
+
+from timm.models.vision_transformer import VisionTransformer
import numpy as np
from tqdm import tqdm
@@ -12,6 +17,125 @@
from art.utils import check_and_transform_label_format
logger = logging.getLogger(__name__)
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+
+class PatchEmbed(torch.nn.Module):
+ """ Image to Patch Embedding
+
+ Class adapted from the implementation in https://github.com/MadryLab/smoothed-vit
+
+ Original License:
+
+ MIT License
+
+ Copyright (c) 2021 Madry Lab
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ """
+ def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
+ super().__init__()
+ num_patches = (img_size // patch_size) * (img_size // patch_size)
+ self.img_size = img_size
+ self.patch_size = patch_size
+ self.num_patches = num_patches
+
+ self.proj = torch.nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=False)
+ w_shape = self.proj.weight.shape
+ self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
+
+ def forward(self, x):
+ with torch.no_grad():
+ x = self.proj(x).flatten(2).transpose(1, 2)
+ return x
+
+
+class ArtViT(VisionTransformer):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def loop_drop_tokens(self, x, indexes):
+ # print('applying masked_select')
+ # print('x ', x.shape)
+ # print('indexes ', indexes.shape)
+ x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
+
+ # loop for now: change later to be batched
+ x_selected = []
+ for sample, i in zip(x_no_cl, indexes):
+ i = (i == True).nonzero(as_tuple=True)[0]
+ # print('indexes ', i)
+ # print('indexes shape', i.shape)
+ tmp = torch.index_select(sample, dim=0, index=i)
+ # print('tmp ', tmp.shape)
+ x_selected.append(tmp)
+
+ x_selected = torch.stack(x_selected, dim=0)
+ # print('x dropped ', x_selected.shape)
+ # print('cls_token ', cls_token.shape)
+ return torch.cat((cls_token, x_selected), dim=1)
+
+ def batch_drop_tokens(self, x, indexes):
+ x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
+ shape = x_no_cl.shape
+
+ # reshape to temporarily remove batch
+ x_no_cl = torch.reshape(x_no_cl, shape=(-1, shape[-1]))
+ indexes = torch.reshape(indexes, shape=(-1,))
+ indexes = (indexes == True).nonzero(as_tuple=True)[0]
+
+ x_no_cl = torch.index_select(x_no_cl, dim=0, index=indexes)
+ x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1]))
+ return torch.cat((cls_token, x_no_cl), dim=1)
+
+ def forward_features(self, x):
+ """
+ The forward pass of the ViT.
+
+ """
+ drop_tokens = True
+
+ if x.shape[1] == 4:
+ x, ablation_mask = x[:, :3], x[:, 3:4]
+
+ x = self.patch_embed(x)
+ x = self._pos_embed(x)
+
+ if drop_tokens:
+ ablation_mask_embedder = PatchEmbed(in_chans=1)
+ ones = ablation_mask_embedder(ablation_mask)
+ to_drop = torch.sum(ones, dim=2)
+ indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
+
+ check_i = indexes[0]
+ check_val = to_drop[0]
+ for i, s in zip(indexes, to_drop):
+ if not torch.equal(check_i, i):
+ for ci, ei, val, cval in zip(check_i, i, s, check_val):
+ print(f'{ci} with {cval} vs {ei} with {val}')
+ sys.exit()
+
+ x = self.batch_drop_tokens(x, indexes)
+
+ x = self.blocks(x)
+ return self.norm(x)
class PyTorchSmoothedViT(PyTorchClassifier):
@@ -36,8 +160,7 @@ def __init__(
"""
Create a smoothed ViT classifier.
- :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits
- output should be preferred where possible to ensure attack efficiency.
+ :param model: string specifying which ViT architecture to load
:param loss: The loss function for which to compute gradients for training. The target label must be raw
categorical, i.e. not converted to one-hot encoding.
:param input_shape: The shape of one input instance.
@@ -60,6 +183,15 @@ def __init__(
be divided by the second one.
:param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
"""
+ import timm
+ from timm.models.vision_transformer import VisionTransformer, vit_small_patch16_224
+ timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
+ model = vit_small_patch16_224()
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+
+ # TODO: enable users to pass in opt hyperparameters
+ optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0001)
+
super().__init__(
model=model,
loss=loss,
@@ -84,6 +216,46 @@ def __init__(
channels_first=True,
row_ablation_mode=False)
+ @staticmethod
+ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
+ """
+ Creates a vision transformer using ArtViT which controls the forward pass of the model
+
+ :param variant: The name of the vision transformer to load
+ :param pretrained: If to load pre-trained weights
+ """
+ from timm.models._builder import build_model_with_cfg
+ from timm.models.vision_transformer import checkpoint_filter_fn
+ return build_model_with_cfg(
+ ArtViT, variant, pretrained,
+ pretrained_filter_fn=checkpoint_filter_fn,
+ **kwargs,
+ )
+
+ def update_batchnorm(self, x: np.ndarray, batch_size: int) -> None:
+ """
+ Method to update the batchnorm of a ViT on small datasets
+ :param x:
+ :param batch_size: Size of batches.
+ """
+ import random
+ import time
+
+ self.model.train()
+
+ ind = np.arange(len(x))
+ num_batch = int(len(x) / float(batch_size))
+
+ print('updating batchnorm')
+ s = time.time()
+ with torch.no_grad():
+ for _ in tqdm(range(1)):
+ for m in tqdm(range(num_batch)):
+ i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size: (m + 1) * batch_size]])).to(device)
+ i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+ _ = self.model(i_batch)
+ print('total time taken is ', time.time() - s)
+
def fit( # pylint: disable=W0221
self,
x: np.ndarray,
@@ -93,7 +265,8 @@ def fit( # pylint: disable=W0221
training_mode: bool = True,
drop_last: bool = False,
scheduler: Optional[Any] = None,
- verbose=True,
+ update_batchnorm: bool = True,
+ verbose: bool = True,
**kwargs,
) -> None:
"""
@@ -108,6 +281,8 @@ def fit( # pylint: disable=W0221
the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
the last batch will be smaller. (default: ``False``)
:param scheduler: Learning rate scheduler to run at the start of every epoch.
+ :param update_batchnorm: ...
+ :param verbose: ...
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
and providing it takes no effect.
"""
@@ -124,6 +299,9 @@ def fit( # pylint: disable=W0221
# Apply preprocessing
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+ if update_batchnorm:
+ self.update_batchnorm(x_preprocessed, batch_size)
+
# Check label shape
y_preprocessed = self.reduce_labels(y_preprocessed)
@@ -138,6 +316,9 @@ def fit( # pylint: disable=W0221
for _ in tqdm(range(nb_epochs)):
# Shuffle the examples
random.shuffle(ind)
+ epoch_acc = []
+ epoch_loss = []
+
pbar = tqdm(range(num_batch), disable=not verbose)
# Train for one epoch
@@ -153,7 +334,7 @@ def fit( # pylint: disable=W0221
# Perform prediction
try:
- model_outputs = self._model(i_batch)
+ model_outputs = self.model(i_batch)
except ValueError as err:
if "Expected more than 1 value per channel when training" in str(err):
logger.exception(
@@ -162,7 +343,11 @@ def fit( # pylint: disable=W0221
)
raise err
# Form the loss function
- loss = self._loss(model_outputs[-1], o_batch)
+ # print('the model outputs are ', model_outputs.shape)
+ loss = self.loss(model_outputs, o_batch)
+ acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
+ epoch_acc.append(acc)
+ epoch_loss.append(loss)
# Do training
if self._use_amp: # pragma: no cover
@@ -178,8 +363,68 @@ def fit( # pylint: disable=W0221
if verbose:
pbar.set_description(
- f"Loss {loss}:.2f"
+ f"Loss {torch.mean(torch.stack(epoch_loss)):.2f}"
+ f" Acc {np.mean(epoch_acc):.2f}"
)
if scheduler is not None:
scheduler.step()
+
+ def certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
+ # set to eval
+ # self._model.train(mode=training_mode)
+ drop_last = True
+ verbose = True
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = len(x_preprocessed) / float(batch_size)
+ if drop_last:
+ num_batch = int(np.floor(num_batch))
+ else:
+ num_batch = int(np.ceil(num_batch))
+ pbar = tqdm(range(num_batch), disable=not verbose)
+
+ with torch.no_grad():
+ for m in pbar:
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size: (m + 1) * batch_size])).to(self._device)
+ o_batch = torch.from_numpy(y_preprocessed[m * batch_size: (m + 1) * batch_size]).to(self._device)
+ predictions = []
+ pred_counts = torch.zeros((batch_size, 10)).to(self._device)
+ for pos in range(0, 30):
+ # print(i_batch.shape)
+ ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
+
+ # Perform prediction
+ model_outputs = self.model(ablated_batch)
+ # print(model_outputs.argmax(dim=-1))
+ # print(model_outputs.argmax(dim=-1).shape)
+ pred_counts[np.arange(0, batch_size), model_outputs.argmax(dim=-1)] += 1
+
+ predictions.append(model_outputs)
+
+ cert, cert_and_correct = self.ablator.certify(pred_counts, size_to_certify=5, label=o_batch)
+ print(torch.sum(cert))
+ print(torch.sum(cert_and_correct) / batch_size)
+
+ @staticmethod
+ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
+ """
+ Helper function to print out the accuracy during training
+
+ :param preds: (concrete) model predictions
+ :param labels: ground truth labels (not one hot)
+ :return: prediction accuracy
+ """
+ if isinstance(preds, torch.Tensor):
+ preds = preds.detach().cpu().numpy()
+
+ if isinstance(labels, torch.Tensor):
+ labels = labels.detach().cpu().numpy()
+
+ return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
\ No newline at end of file
From c2f38df28fac91642ba1289bf4e07664eb1189ba Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 10 May 2023 15:13:20 +0000
Subject: [PATCH 05/55] dev testing script
Signed-off-by: GiulioZizzo
---
dev.py | 56 ++++++++------------------------------------------------
1 file changed, 8 insertions(+), 48 deletions(-)
diff --git a/dev.py b/dev.py
index 625095a95f..fb30887b53 100644
--- a/dev.py
+++ b/dev.py
@@ -1,18 +1,15 @@
-
-# https://github.com/huggingface/pytorch-image-models
import torch
import torch.nn as nn
-from timm.models.vision_transformer import VisionTransformer, vit_small_patch16_224
-from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT
+from timm.models.vision_transformer import VisionTransformer
+from timm.models.vision_transformer import checkpoint_filter_fn
+from functools import partial
+from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT, ArtViT
import copy
import numpy as np
from torchvision import datasets
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
-model = vit_small_patch16_224(pretrained=True)
-print(type(model))
-
def get_cifar_data():
"""
@@ -37,55 +34,18 @@ def get_cifar_data():
return (x_train, y_train), (x_test, y_test)
-def update_batchnorm(model, x):
- import random
- from tqdm import tqdm
-
- art_model.model.train()
- batch_size = 32
-
- ind = np.arange(len(x))
- num_batch = int(len(x) / float(batch_size))
-
- print('updating batchnorm')
- with torch.no_grad():
- for _ in tqdm(range(200)):
- for m in tqdm(range(num_batch)):
- i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size: (m + 1) * batch_size]])).to(device)
- i_batch = art_model.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
- art_model.model(i_batch.cuda())
- return model
-
(x_train, y_train), (x_test, y_test) = get_cifar_data()
x_test = torch.from_numpy(x_test)
-print('params: ', model.parameters())
-optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
-scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[10, 20], gamma=0.1)
-
-# Use same initial point as Madry
-checkpoint = torch.hub.load_state_dict_from_url(
- url="https://dl.fbaipublicfiles.com/deit/deit_small_patch16_224-cd65a155.pth",
- map_location="cpu", check_hash=True
-)
-model.load_state_dict(checkpoint["model"])
-art_model = PyTorchSmoothedViT(model=model,
+art_model = PyTorchSmoothedViT(model='vit_small_patch16_224',
loss=torch.nn.CrossEntropyLoss(),
input_shape=(3, 224, 224),
- optimizer=optimizer,
nb_classes=10,
ablation_type='column',
ablation_size=4,
threshold=0.01,
logits=True)
-ablated_x = art_model.ablator.ablate(x=copy.deepcopy(x_test[:32]),
- column_pos=1)
-
-print('test position 31')
-
-ablated_x = art_model.ablator.ablate(x=copy.deepcopy(x_test[:32]),
- column_pos=31)
-
-art_model = update_batchnorm(art_model, x_train)
-art_model.fit(x_train, y_train)
+scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
+art_model.fit(x_train, y_train, update_batchnorm=True, scheduler=scheduler)
+art_model.certify(x_train, y_train)
From 394e49ce2cb8683d0e2029fb5c3c0581fe4e95ce Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 10 May 2023 20:53:05 +0100
Subject: [PATCH 06/55] adding evaluation and load pretrained with optimizer
updates
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 148 ++++++++++++------
.../smooth_vit.py | 5 +-
dev.py | 14 +-
3 files changed, 107 insertions(+), 60 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 1835844640..66474e8de5 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -56,8 +56,22 @@ def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
self.img_size = img_size
self.patch_size = patch_size
self.num_patches = num_patches
+ self.in_chans = in_chans
+ self.embed_dim = embed_dim
+
+ def create(self, patch_size=None, embed_dim=None, **kwargs):
+
+ if patch_size is not None:
+ self.patch_size = patch_size
+ if embed_dim is not None:
+ self.embed_dim = embed_dim
+
+ self.proj = torch.nn.Conv2d(in_channels=1,
+ out_channels=self.embed_dim,
+ kernel_size=self.patch_size,
+ stride=self.patch_size,
+ bias=False)
- self.proj = torch.nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=False)
w_shape = self.proj.weight.shape
self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
@@ -68,31 +82,23 @@ def forward(self, x):
class ArtViT(VisionTransformer):
+
+ # Make as a class attribute to avoid being included in the
+ # state dictionaries of the ViT Model.
+ ablation_mask_embedder = PatchEmbed(in_chans=1)
+
def __init__(self, **kwargs):
super().__init__(**kwargs)
+ self.ablation_mask_embedder.create(**kwargs)
- def loop_drop_tokens(self, x, indexes):
- # print('applying masked_select')
- # print('x ', x.shape)
- # print('indexes ', indexes.shape)
- x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
-
- # loop for now: change later to be batched
- x_selected = []
- for sample, i in zip(x_no_cl, indexes):
- i = (i == True).nonzero(as_tuple=True)[0]
- # print('indexes ', i)
- # print('indexes shape', i.shape)
- tmp = torch.index_select(sample, dim=0, index=i)
- # print('tmp ', tmp.shape)
- x_selected.append(tmp)
-
- x_selected = torch.stack(x_selected, dim=0)
- # print('x dropped ', x_selected.shape)
- # print('cls_token ', cls_token.shape)
- return torch.cat((cls_token, x_selected), dim=1)
-
- def batch_drop_tokens(self, x, indexes):
+ @staticmethod
+ def drop_tokens(x, indexes):
+ """
+ Drops the tokens which correspond to fully masked inputs
+ :param x: Input data in .... format
+ :param indexes: positions to be ablated
+ return
+ """
x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
shape = x_no_cl.shape
@@ -108,6 +114,9 @@ def batch_drop_tokens(self, x, indexes):
def forward_features(self, x):
"""
The forward pass of the ViT.
+ #TODO! check for 1 channel inputs!
+
+ :param x: Input data.
"""
drop_tokens = True
@@ -119,8 +128,7 @@ def forward_features(self, x):
x = self._pos_embed(x)
if drop_tokens:
- ablation_mask_embedder = PatchEmbed(in_chans=1)
- ones = ablation_mask_embedder(ablation_mask)
+ ones = self.ablation_mask_embedder(ablation_mask)
to_drop = torch.sum(ones, dim=2)
indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
@@ -132,7 +140,7 @@ def forward_features(self, x):
print(f'{ci} with {cval} vs {ei} with {val}')
sys.exit()
- x = self.batch_drop_tokens(x, indexes)
+ x = self.drop_tokens(x, indexes)
x = self.blocks(x)
return self.norm(x)
@@ -141,26 +149,28 @@ def forward_features(self, x):
class PyTorchSmoothedViT(PyTorchClassifier):
def __init__(
self,
- model: "torch.nn.Module",
+ model: ["VisionTransformer", str],
loss: "torch.nn.modules.loss._Loss",
input_shape: Tuple[int, ...],
nb_classes: int,
ablation_type: str,
ablation_size: int,
threshold: float,
- logits: bool,
optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore
+ optimizer_params: Optional[dict] = None,
channels_first: bool = True,
clip_values: Optional["CLIP_VALUES_TYPE"] = None,
preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
device_type: str = "gpu",
+ load_pretrained: bool = True,
):
"""
Create a smoothed ViT classifier.
- :param model: string specifying which ViT architecture to load
+ :param model: Either a string specifying which ViT architecture to load, or a vision transformer already
+ created with the Pytorch Image Models (timm) library.
:param loss: The loss function for which to compute gradients for training. The target label must be raw
categorical, i.e. not converted to one-hot encoding.
:param input_shape: The shape of one input instance.
@@ -169,7 +179,6 @@ def __init__(
:param ablation_size: The size of the data portion to retain after ablation. Will be a column of size N for
"column" ablation type or a NxN square for ablation of type "block"
:param threshold: The minimum threshold to count a prediction.
- :param logits: if the model returns logits or normalized probabilities
:param optimizer: The optimizer used to train the classifier.
:param channels_first: Set channels first or last.
:param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
@@ -184,13 +193,35 @@ def __init__(
:param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
"""
import timm
- from timm.models.vision_transformer import VisionTransformer, vit_small_patch16_224
+
timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
- model = vit_small_patch16_224()
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
- # TODO: enable users to pass in opt hyperparameters
- optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0001)
+ if type(model) is str:
+ model = timm.create_model(model, pretrained=load_pretrained)
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+ # TODO: enable users to pass in opt hyperparameters
+ # optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0001)
+ optimizer = optimizer(model.parameters(), **optimizer_params)
+
+ else:
+ pretrained_cfg = model.pretrained_cfg
+ supplied_state_dict = model.state_dict()
+ model = timm.create_model(pretrained_cfg['vit_small_patch16_224'], pretrained=load_pretrained)
+ model.load_state_dict(torch.load(supplied_state_dict))
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+
+ if optimizer is not None:
+ converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
+ opt_state_dict = optimizer.state_dict()
+ if isinstance(optimizer, torch.optim.Adam):
+ logging.info("Converting Adam Optimiser")
+ converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
+ elif isinstance(optimizer, torch.optim.SGD):
+ logging.info("Converting SGD Optimiser")
+ converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
+ else:
+ raise ValueError("Optimiser not supported for conversion")
+ converted_optimizer.load_state_dict(opt_state_dict)
super().__init__(
model=model,
@@ -209,7 +240,6 @@ def __init__(
self.ablation_type = ablation_type
self.ablation_size = ablation_size,
self.threshold = threshold
- self.logits = logits
print(self.model)
self.ablator = ColumnAblator(ablation_size=ablation_size,
@@ -223,7 +253,9 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
:param variant: The name of the vision transformer to load
:param pretrained: If to load pre-trained weights
+ :return: A ViT with the required methods needed for ART
"""
+
from timm.models._builder import build_model_with_cfg
from timm.models.vision_transformer import checkpoint_filter_fn
return build_model_with_cfg(
@@ -294,7 +326,14 @@ def fit( # pylint: disable=W0221
if self._optimizer is None: # pragma: no cover
raise ValueError("An optimizer is needed to train the model, but none for provided.")
+ import torchvision.transforms as transforms
+
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+ transform = transforms.Compose(
+ [
+ transforms.RandomHorizontalFlip()
+ ]
+ )
# Apply preprocessing
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
@@ -324,6 +363,7 @@ def fit( # pylint: disable=W0221
# Train for one epoch
for m in pbar:
i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size: (m + 1) * batch_size]])).to(self._device)
+ i_batch = transform(i_batch)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size: (m + 1) * batch_size]]).to(
@@ -359,7 +399,7 @@ def fit( # pylint: disable=W0221
else:
loss.backward()
- self._optimizer.step()
+ self.optimizer.step()
if verbose:
pbar.set_description(
@@ -370,9 +410,15 @@ def fit( # pylint: disable=W0221
if scheduler is not None:
scheduler.step()
- def certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
- # set to eval
- # self._model.train(mode=training_mode)
+ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
+ """
+ Evaluates the ViT's normal and certified performance over the supplied data
+ :param x: Evaluation data
+ :param y: Evaluation labels
+ :param batch_size: batch size when evaluating
+ """
+
+ self.model.eval()
drop_last = True
verbose = True
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
@@ -389,28 +435,30 @@ def certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
else:
num_batch = int(np.ceil(num_batch))
pbar = tqdm(range(num_batch), disable=not verbose)
-
+ accuracy = []
+ cert_acc = []
with torch.no_grad():
for m in pbar:
i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size: (m + 1) * batch_size])).to(self._device)
o_batch = torch.from_numpy(y_preprocessed[m * batch_size: (m + 1) * batch_size]).to(self._device)
predictions = []
- pred_counts = torch.zeros((batch_size, 10)).to(self._device)
- for pos in range(0, 30):
- # print(i_batch.shape)
+ pred_counts = torch.zeros((batch_size, self.nb_classes)).to(self._device)
+ for pos in range(i_batch.shape[-1]):
+
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
# Perform prediction
model_outputs = self.model(ablated_batch)
- # print(model_outputs.argmax(dim=-1))
- # print(model_outputs.argmax(dim=-1).shape)
pred_counts[np.arange(0, batch_size), model_outputs.argmax(dim=-1)] += 1
-
predictions.append(model_outputs)
- cert, cert_and_correct = self.ablator.certify(pred_counts, size_to_certify=5, label=o_batch)
- print(torch.sum(cert))
- print(torch.sum(cert_and_correct) / batch_size)
+ cert, cert_and_correct, top_predicted_class = self.ablator.certify(pred_counts, size_to_certify=4, label=o_batch)
+ cert_acc.append(torch.sum(cert_and_correct) / batch_size)
+ acc = torch.sum(top_predicted_class == o_batch) / batch_size
+ accuracy.append(acc)
+
+ print('Normal Acc: ', torch.mean(torch.stack(accuracy)))
+ print('Cert Normal Acc: ', torch.mean(torch.stack(cert_acc)))
@staticmethod
def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index d871c54158..b634393a93 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -53,6 +53,7 @@ def certify(self, predictions, size_to_certify, label):
second_class_counts, second_predicted_class = predictions.kthvalue(num_of_classes - 1, dim=1)
cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
- cert_and_correct = cert & label == top_predicted_class
- return cert, cert_and_correct
+ cert_and_correct = cert & (label == top_predicted_class)
+
+ return cert, cert_and_correct, top_predicted_class
diff --git a/dev.py b/dev.py
index fb30887b53..48c5a932b9 100644
--- a/dev.py
+++ b/dev.py
@@ -1,11 +1,6 @@
import torch
-import torch.nn as nn
-from timm.models.vision_transformer import VisionTransformer
-from timm.models.vision_transformer import checkpoint_filter_fn
-from functools import partial
from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT, ArtViT
-import copy
import numpy as np
from torchvision import datasets
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -44,8 +39,11 @@ def get_cifar_data():
ablation_type='column',
ablation_size=4,
threshold=0.01,
- logits=True)
+ logits=True,
+ load_pretrained=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
-art_model.fit(x_train, y_train, update_batchnorm=True, scheduler=scheduler)
-art_model.certify(x_train, y_train)
+art_model.fit(x_train, y_train, nb_epochs=30, update_batchnorm=True, scheduler=scheduler)
+torch.save(art_model.model.state_dict(), 'trained.pt')
+art_model.model.load_state_dict(torch.load('trained.pt'))
+art_model.eval_and_certify(x_train, y_train)
From 7dd0512a5b75a911ca7144a4f39d709a93596963 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 17 May 2023 10:54:34 +0100
Subject: [PATCH 07/55] updating input specification
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 75 +++++++++++------
.../smooth_vit.py | 81 ++++++++++++++++---
requirements_test.txt | 3 +
3 files changed, 123 insertions(+), 36 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 66474e8de5..31ae221688 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -50,23 +50,35 @@ class PatchEmbed(torch.nn.Module):
SOFTWARE.
"""
- def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
+ def __init__(self, patch_size=16, in_channels=1, embed_dim=768):
+ """
+ Specifies the configuration for the convolutional layer.
+ :param patch_size: The patch size used by the ViT
+ :param in_chans: Number of input channels.
+ :param embed_dim: The embedding dimension used by the ViT
+
+ """
super().__init__()
- num_patches = (img_size // patch_size) * (img_size // patch_size)
- self.img_size = img_size
self.patch_size = patch_size
- self.num_patches = num_patches
- self.in_chans = in_chans
+ self.in_channels = in_channels
self.embed_dim = embed_dim
+ self.proj: Optional[torch.nn.Conv2d] = None
- def create(self, patch_size=None, embed_dim=None, **kwargs):
+ def create(self, patch_size=None, embed_dim=None, **kwargs) -> None:
+ """
+ Creates a convolution that mimics the embedding layer to be used for the ablation mask to
+ track where the image was ablated.
+
+ :param patch_size: The patch size used by the ViT
+ :param embed_dim: The embedding dimension used by the ViT
+ """
if patch_size is not None:
self.patch_size = patch_size
if embed_dim is not None:
self.embed_dim = embed_dim
- self.proj = torch.nn.Conv2d(in_channels=1,
+ self.proj = torch.nn.Conv2d(in_channels=self.in_channels,
out_channels=self.embed_dim,
kernel_size=self.patch_size,
stride=self.patch_size,
@@ -75,7 +87,14 @@ def create(self, patch_size=None, embed_dim=None, **kwargs):
w_shape = self.proj.weight.shape
self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
- def forward(self, x):
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Forward pass through the embedder. We are simply tracking the positions of the ablation mask so no gradients
+ are required.
+
+ :param x: Input data corresponding to the ablation mask
+ :return: The embedded input
+ """
with torch.no_grad():
x = self.proj(x).flatten(2).transpose(1, 2)
return x
@@ -85,19 +104,20 @@ class ArtViT(VisionTransformer):
# Make as a class attribute to avoid being included in the
# state dictionaries of the ViT Model.
- ablation_mask_embedder = PatchEmbed(in_chans=1)
+ ablation_mask_embedder = PatchEmbed(in_channels=1)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.ablation_mask_embedder.create(**kwargs)
@staticmethod
- def drop_tokens(x, indexes):
+ def drop_tokens(x: torch.Tensor, indexes) -> torch.Tensor:
"""
Drops the tokens which correspond to fully masked inputs
+
:param x: Input data in .... format
:param indexes: positions to be ablated
- return
+ :return: Input with tokens dropped where the input was fully ablated.
"""
x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
shape = x_no_cl.shape
@@ -111,7 +131,7 @@ def drop_tokens(x, indexes):
x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1]))
return torch.cat((cls_token, x_no_cl), dim=1)
- def forward_features(self, x):
+ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
"""
The forward pass of the ViT.
#TODO! check for 1 channel inputs!
@@ -199,9 +219,11 @@ def __init__(
if type(model) is str:
model = timm.create_model(model, pretrained=load_pretrained)
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
- # TODO: enable users to pass in opt hyperparameters
- # optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0001)
- optimizer = optimizer(model.parameters(), **optimizer_params)
+ if optimizer is not None:
+ if optimizer_params is not None:
+ optimizer = optimizer(model.parameters(), **optimizer_params)
+ else:
+ raise ValueError("If providing an optimiser please also supply its parameters")
else:
pretrained_cfg = model.pretrained_cfg
@@ -223,6 +245,13 @@ def __init__(
raise ValueError("Optimiser not supported for conversion")
converted_optimizer.load_state_dict(opt_state_dict)
+ self.to_reshape = False
+ if model.default_cfg['input_size'] != input_shape:
+ print(f"ViT expects input shape of {model.default_cfg['input_size']}, "
+ f"but {input_shape} specified as the input shape. "
+ f"The input will be rescaled to {model.default_cfg['input_size']}")
+ self.to_reshape = True
+
super().__init__(
model=model,
loss=loss,
@@ -244,7 +273,9 @@ def __init__(
print(self.model)
self.ablator = ColumnAblator(ablation_size=ablation_size,
channels_first=True,
- row_ablation_mode=False)
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=model.default_cfg['input_size'])
@staticmethod
def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
@@ -264,14 +295,14 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
**kwargs,
)
- def update_batchnorm(self, x: np.ndarray, batch_size: int) -> None:
+ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
"""
Method to update the batchnorm of a ViT on small datasets
- :param x:
+ :param x: Training data.
:param batch_size: Size of batches.
+ :param nb_epochs: How many times to forward pass over the input data
"""
import random
- import time
self.model.train()
@@ -279,14 +310,12 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int) -> None:
num_batch = int(len(x) / float(batch_size))
print('updating batchnorm')
- s = time.time()
with torch.no_grad():
- for _ in tqdm(range(1)):
+ for _ in tqdm(range(nb_epochs)):
for m in tqdm(range(num_batch)):
i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size: (m + 1) * batch_size]])).to(device)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
_ = self.model(i_batch)
- print('total time taken is ', time.time() - s)
def fit( # pylint: disable=W0221
self,
@@ -475,4 +504,4 @@ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndar
if isinstance(labels, torch.Tensor):
labels = labels.detach().cpu().numpy()
- return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
\ No newline at end of file
+ return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index b634393a93..1c15cc1e7f 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -8,13 +8,32 @@
"""
import torch
+from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
class UpSampler(torch.nn.Module):
- def __init__(self, input_size, final_size):
+ """
+ Resizes datasets to the specified size.
+ Usually for upscaling datasets like CIFAR to Imagenet format
+ """
+ def __init__(self, input_size: int, final_size: int) -> None:
+ """
+ Creates an upsampler to make the supplied data match the pre-trained ViT format
+ :param input_size: Size of the current input data
+ :param final_size: Desired final size
+ """
super(UpSampler, self).__init__()
self.upsample = torch.nn.Upsample(scale_factor=final_size/input_size)
- def forward(self, x):
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Forward pass though the upsampler.
+
+ :param x: Input data
+ :return: The upsampled input data
+ """
return self.upsample(x)
@@ -22,14 +41,32 @@ class ColumnAblator(torch.nn.Module):
"""
Pure Pytorch implementation of stripe/column ablation.
"""
- def __init__(self, ablation_size: int, channels_first: bool, row_ablation_mode: bool = False):
+ def __init__(self, ablation_size: int, channels_first: bool,
+ to_reshape: bool = False, original_shape: Optional[Tuple] = None,
+ output_shape: Optional[Tuple] = None):
+ """
+ Creates a column ablator
+
+ :param ablation_size: The size of the column we will retain.
+ :param channels_first: If the input is in channels first format. Currently required to be True.
+ :param to_reshape: If the input requires reshaping.
+ :param original_shape: Original shape of the input.
+ :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
+ """
super().__init__()
self.ablation_size = ablation_size
self.channels_first = channels_first
- self.row_ablation_mode = row_ablation_mode
- self.upsample = UpSampler(input_size=32, final_size=224)
+ self.to_reshape = to_reshape
+ self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
- def ablate(self, x, column_pos):
+ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
+ """
+ Ablates the input colum wise
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The ablated input with 0s where the ablation occurred
+ """
k = self.ablation_size
if column_pos + k > x.shape[-1]:
x[:, :, :, (column_pos + k) % x.shape[-1]:column_pos] = 0.0
@@ -38,19 +75,37 @@ def ablate(self, x, column_pos):
x[:, :, :, column_pos + k:] = 0.0
return x
- def forward(self, x, column_pos):
+ def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
+ """
+ Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The albated input with an extra channel indicating the location of the ablation
+ """
assert x.shape[1] == 3
- ones = torch.torch.ones_like(x[:, 0:1, :, :]).cuda()
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(device)
x = torch.cat([x, ones], dim=1)
x = self.ablate(x, column_pos=column_pos)
- return self.upsample(x)
+ if self.to_reshape:
+ x = self.upsample(x)
+ return x
+
+ def certify(self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ Performs certification of the predictions
- def certify(self, predictions, size_to_certify, label):
+ :param pred_counts: The model predictions over the ablated data.
+ :param size_to_certify: The patch size we wish to check certification against
+ :param label: The ground truth labels
+ :return: A tuple consisting of: the certified predictions, the predictions which were certified and also correct,
+ and the most predicted class across the different ablations on the input
+ """
- num_of_classes = predictions.shape[-1]
+ num_of_classes = pred_counts.shape[-1]
- top_class_counts, top_predicted_class = predictions.kthvalue(num_of_classes, dim=1)
- second_class_counts, second_predicted_class = predictions.kthvalue(num_of_classes - 1, dim=1)
+ top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
+ second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
diff --git a/requirements_test.txt b/requirements_test.txt
index 7346645fd6..87add272e9 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -34,6 +34,9 @@ torch==1.13.1
torchaudio==0.13.1+cpu
torchvision==0.14.1+cpu
+# PyTorch image transformers
+timm @ git+https://github.com/huggingface/pytorch-image-models.git@9fcc01930aae865ec9ef8aae8849ca2ba241f816
+
catboost==1.1.1
GPy==1.10.0
lightgbm==3.3.5
From 548dafde5eb1b82196487fb9f608d000af9fb75b Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Fri, 19 May 2023 16:11:28 +0100
Subject: [PATCH 08/55] updating test script
Signed-off-by: GiulioZizzo
---
dev.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/dev.py b/dev.py
index 48c5a932b9..28c828a5c0 100644
--- a/dev.py
+++ b/dev.py
@@ -1,5 +1,6 @@
import torch
-
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT, ArtViT
import numpy as np
from torchvision import datasets
@@ -34,16 +35,17 @@ def get_cifar_data():
art_model = PyTorchSmoothedViT(model='vit_small_patch16_224',
loss=torch.nn.CrossEntropyLoss(),
- input_shape=(3, 224, 224),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
nb_classes=10,
ablation_type='column',
ablation_size=4,
threshold=0.01,
- logits=True,
load_pretrained=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
-art_model.fit(x_train, y_train, nb_epochs=30, update_batchnorm=True, scheduler=scheduler)
-torch.save(art_model.model.state_dict(), 'trained.pt')
-art_model.model.load_state_dict(torch.load('trained.pt'))
+# art_model.fit(x_train, y_train, nb_epochs=30, update_batchnorm=True, scheduler=scheduler)
+# torch.save(art_model.model.state_dict(), 'trained.pt')
+# art_model.model.load_state_dict(torch.load('trained.pt'))
art_model.eval_and_certify(x_train, y_train)
From d46102126c5f41b56a58a9e892f43954857ffa76 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Mon, 22 May 2023 12:56:03 +0000
Subject: [PATCH 09/55] more flexible inputs, and fixing mypy errors
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 212 +++++++++++-------
.../smooth_vit.py | 45 +++-
2 files changed, 161 insertions(+), 96 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 31ae221688..173399d1ba 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -1,3 +1,28 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""
+This module implements Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+"""
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
@@ -5,7 +30,6 @@
from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
import random
import torch
-import copy
from timm.models.vision_transformer import VisionTransformer
@@ -16,12 +40,17 @@
from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
from art.utils import check_and_transform_label_format
+if TYPE_CHECKING:
+ from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
+ from art.defences.preprocessor import Preprocessor
+ from art.defences.postprocessor import Postprocessor
+
logger = logging.getLogger(__name__)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class PatchEmbed(torch.nn.Module):
- """ Image to Patch Embedding
+ """Image to Patch Embedding
Class adapted from the implementation in https://github.com/MadryLab/smoothed-vit
@@ -50,11 +79,12 @@ class PatchEmbed(torch.nn.Module):
SOFTWARE.
"""
- def __init__(self, patch_size=16, in_channels=1, embed_dim=768):
+
+ def __init__(self, patch_size: int=16, in_channels: int=1, embed_dim:int=768):
"""
Specifies the configuration for the convolutional layer.
:param patch_size: The patch size used by the ViT
- :param in_chans: Number of input channels.
+ :param in_channels: Number of input channels.
:param embed_dim: The embedding dimension used by the ViT
"""
@@ -78,11 +108,13 @@ def create(self, patch_size=None, embed_dim=None, **kwargs) -> None:
if embed_dim is not None:
self.embed_dim = embed_dim
- self.proj = torch.nn.Conv2d(in_channels=self.in_channels,
- out_channels=self.embed_dim,
- kernel_size=self.patch_size,
- stride=self.patch_size,
- bias=False)
+ self.proj = torch.nn.Conv2d(
+ in_channels=self.in_channels,
+ out_channels=self.embed_dim,
+ kernel_size=self.patch_size,
+ stride=self.patch_size,
+ bias=False,
+ )
w_shape = self.proj.weight.shape
self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
@@ -95,13 +127,14 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
:param x: Input data corresponding to the ablation mask
:return: The embedded input
"""
- with torch.no_grad():
- x = self.proj(x).flatten(2).transpose(1, 2)
- return x
+ if self.proj is not None:
+ with torch.no_grad():
+ x = self.proj(x).flatten(2).transpose(1, 2)
+ return x
+ raise ValueError("Projection layer not yet created.")
class ArtViT(VisionTransformer):
-
# Make as a class attribute to avoid being included in the
# state dictionaries of the ViT Model.
ablation_mask_embedder = PatchEmbed(in_channels=1)
@@ -151,15 +184,6 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
ones = self.ablation_mask_embedder(ablation_mask)
to_drop = torch.sum(ones, dim=2)
indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
-
- check_i = indexes[0]
- check_val = to_drop[0]
- for i, s in zip(indexes, to_drop):
- if not torch.equal(check_i, i):
- for ci, ei, val, cval in zip(check_i, i, s, check_val):
- print(f'{ci} with {cval} vs {ei} with {val}')
- sys.exit()
-
x = self.drop_tokens(x, indexes)
x = self.blocks(x)
@@ -169,14 +193,14 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
class PyTorchSmoothedViT(PyTorchClassifier):
def __init__(
self,
- model: ["VisionTransformer", str],
+ model: Union[VisionTransformer, str],
loss: "torch.nn.modules.loss._Loss",
input_shape: Tuple[int, ...],
nb_classes: int,
ablation_type: str,
ablation_size: int,
threshold: float,
- optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore
+ optimizer: Union[type, "torch.optim.Optimizer", None] = None,
optimizer_params: Optional[dict] = None,
channels_first: bool = True,
clip_values: Optional["CLIP_VALUES_TYPE"] = None,
@@ -215,24 +239,26 @@ def __init__(
import timm
timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
-
- if type(model) is str:
+ if isinstance(model, str):
model = timm.create_model(model, pretrained=load_pretrained)
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
- if optimizer is not None:
+ if isinstance(optimizer, type):
if optimizer_params is not None:
optimizer = optimizer(model.parameters(), **optimizer_params)
else:
raise ValueError("If providing an optimiser please also supply its parameters")
- else:
+ elif isinstance(model, VisionTransformer):
pretrained_cfg = model.pretrained_cfg
supplied_state_dict = model.state_dict()
- model = timm.create_model(pretrained_cfg['vit_small_patch16_224'], pretrained=load_pretrained)
- model.load_state_dict(torch.load(supplied_state_dict))
+ model = timm.create_model(pretrained_cfg["architecture"], pretrained=load_pretrained)
+ model.load_state_dict(supplied_state_dict)
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
if optimizer is not None:
+ if not isinstance(optimizer, torch.optim.Optimizer):
+ raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance")
+
converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
opt_state_dict = optimizer.state_dict()
if isinstance(optimizer, torch.optim.Adam):
@@ -246,36 +272,46 @@ def __init__(
converted_optimizer.load_state_dict(opt_state_dict)
self.to_reshape = False
- if model.default_cfg['input_size'] != input_shape:
- print(f"ViT expects input shape of {model.default_cfg['input_size']}, "
- f"but {input_shape} specified as the input shape. "
- f"The input will be rescaled to {model.default_cfg['input_size']}")
- self.to_reshape = True
-
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- )
+ if isinstance(model, ArtViT):
+ if model.default_cfg["input_size"] != input_shape:
+ print(
+ f"ViT expects input shape of {model.default_cfg['input_size']}, "
+ f"but {input_shape} specified as the input shape. "
+ f"The input will be rescaled to {model.default_cfg['input_size']}"
+ )
+ self.to_reshape = True
+ else:
+ raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
+
+ if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ )
+ else:
+ raise ValueError("opt error")
self.ablation_type = ablation_type
- self.ablation_size = ablation_size,
+ self.ablation_size = (ablation_size,)
self.threshold = threshold
print(self.model)
- self.ablator = ColumnAblator(ablation_size=ablation_size,
- channels_first=True,
- to_reshape=self.to_reshape,
- original_shape=input_shape,
- output_shape=model.default_cfg['input_size'])
+ self.ablator = ColumnAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=model.default_cfg["input_size"],
+ )
@staticmethod
def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
@@ -289,8 +325,11 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
from timm.models._builder import build_model_with_cfg
from timm.models.vision_transformer import checkpoint_filter_fn
+
return build_model_with_cfg(
- ArtViT, variant, pretrained,
+ ArtViT,
+ variant,
+ pretrained,
pretrained_filter_fn=checkpoint_filter_fn,
**kwargs,
)
@@ -309,26 +348,25 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
ind = np.arange(len(x))
num_batch = int(len(x) / float(batch_size))
- print('updating batchnorm')
with torch.no_grad():
for _ in tqdm(range(nb_epochs)):
for m in tqdm(range(num_batch)):
- i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size: (m + 1) * batch_size]])).to(device)
+ i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(device)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
_ = self.model(i_batch)
def fit( # pylint: disable=W0221
- self,
- x: np.ndarray,
- y: np.ndarray,
- batch_size: int = 128,
- nb_epochs: int = 10,
- training_mode: bool = True,
- drop_last: bool = False,
- scheduler: Optional[Any] = None,
- update_batchnorm: bool = True,
- verbose: bool = True,
- **kwargs,
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ batch_size: int = 128,
+ nb_epochs: int = 10,
+ training_mode: bool = True,
+ drop_last: bool = False,
+ scheduler: Optional[Any] = None,
+ update_batchnorm: bool = True,
+ verbose: bool = True,
+ **kwargs,
) -> None:
"""
Fit the classifier on the training set `(x, y)`.
@@ -342,8 +380,9 @@ def fit( # pylint: disable=W0221
the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
the last batch will be smaller. (default: ``False``)
:param scheduler: Learning rate scheduler to run at the start of every epoch.
- :param update_batchnorm: ...
- :param verbose: ...
+ :param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
+ to training. Useful on small datasets when using pre-trained ViTs.
+ :param verbose: if to display training progress bars
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
and providing it takes no effect.
"""
@@ -358,11 +397,7 @@ def fit( # pylint: disable=W0221
import torchvision.transforms as transforms
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
- transform = transforms.Compose(
- [
- transforms.RandomHorizontalFlip()
- ]
- )
+ transform = transforms.Compose([transforms.RandomHorizontalFlip()])
# Apply preprocessing
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
@@ -391,12 +426,13 @@ def fit( # pylint: disable=W0221
# Train for one epoch
for m in pbar:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size: (m + 1) * batch_size]])).to(self._device)
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])).to(
+ self._device
+ )
i_batch = transform(i_batch)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
- o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size: (m + 1) * batch_size]]).to(
- self._device)
+ o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
# Zero the parameter gradients
self._optimizer.zero_grad()
@@ -432,8 +468,7 @@ def fit( # pylint: disable=W0221
if verbose:
pbar.set_description(
- f"Loss {torch.mean(torch.stack(epoch_loss)):.2f}"
- f" Acc {np.mean(epoch_acc):.2f}"
+ f"Loss {torch.mean(torch.stack(epoch_loss)):.2f}" f" Acc {np.mean(epoch_acc):.2f}"
)
if scheduler is not None:
@@ -468,12 +503,13 @@ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
cert_acc = []
with torch.no_grad():
for m in pbar:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size: (m + 1) * batch_size])).to(self._device)
- o_batch = torch.from_numpy(y_preprocessed[m * batch_size: (m + 1) * batch_size]).to(self._device)
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])).to(
+ self._device
+ )
+ o_batch = torch.from_numpy(y_preprocessed[m * batch_size : (m + 1) * batch_size]).to(self._device)
predictions = []
pred_counts = torch.zeros((batch_size, self.nb_classes)).to(self._device)
for pos in range(i_batch.shape[-1]):
-
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
# Perform prediction
@@ -481,13 +517,15 @@ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
pred_counts[np.arange(0, batch_size), model_outputs.argmax(dim=-1)] += 1
predictions.append(model_outputs)
- cert, cert_and_correct, top_predicted_class = self.ablator.certify(pred_counts, size_to_certify=4, label=o_batch)
+ cert, cert_and_correct, top_predicted_class = self.ablator.certify(
+ pred_counts, size_to_certify=4, label=o_batch
+ )
cert_acc.append(torch.sum(cert_and_correct) / batch_size)
acc = torch.sum(top_predicted_class == o_batch) / batch_size
accuracy.append(acc)
- print('Normal Acc: ', torch.mean(torch.stack(accuracy)))
- print('Cert Normal Acc: ', torch.mean(torch.stack(cert_acc)))
+ print("Normal Acc: ", torch.mean(torch.stack(accuracy)))
+ print("Cert Normal Acc: ", torch.mean(torch.stack(cert_acc)))
@staticmethod
def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index 1c15cc1e7f..1923e64455 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -1,3 +1,20 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
"""
This module implements Certified Patch Robustness via Smoothed Vision Transformers
@@ -8,7 +25,7 @@
"""
import torch
-from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+from typing import Optional, Tuple
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -18,6 +35,7 @@ class UpSampler(torch.nn.Module):
Resizes datasets to the specified size.
Usually for upscaling datasets like CIFAR to Imagenet format
"""
+
def __init__(self, input_size: int, final_size: int) -> None:
"""
Creates an upsampler to make the supplied data match the pre-trained ViT format
@@ -25,7 +43,7 @@ def __init__(self, input_size: int, final_size: int) -> None:
:param final_size: Desired final size
"""
super(UpSampler, self).__init__()
- self.upsample = torch.nn.Upsample(scale_factor=final_size/input_size)
+ self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
@@ -41,9 +59,15 @@ class ColumnAblator(torch.nn.Module):
"""
Pure Pytorch implementation of stripe/column ablation.
"""
- def __init__(self, ablation_size: int, channels_first: bool,
- to_reshape: bool = False, original_shape: Optional[Tuple] = None,
- output_shape: Optional[Tuple] = None):
+
+ def __init__(
+ self,
+ ablation_size: int,
+ channels_first: bool,
+ to_reshape: bool = False,
+ original_shape: Optional[Tuple] = None,
+ output_shape: Optional[Tuple] = None,
+ ):
"""
Creates a column ablator
@@ -57,7 +81,8 @@ def __init__(self, ablation_size: int, channels_first: bool,
self.ablation_size = ablation_size
self.channels_first = channels_first
self.to_reshape = to_reshape
- self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
+ if original_shape is not None and output_shape is not None:
+ self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
"""
@@ -69,10 +94,10 @@ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
"""
k = self.ablation_size
if column_pos + k > x.shape[-1]:
- x[:, :, :, (column_pos + k) % x.shape[-1]:column_pos] = 0.0
+ x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
else:
x[:, :, :, :column_pos] = 0.0
- x[:, :, :, column_pos + k:] = 0.0
+ x[:, :, :, column_pos + k :] = 0.0
return x
def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
@@ -91,7 +116,9 @@ def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
x = self.upsample(x)
return x
- def certify(self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ def certify(
+ self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Performs certification of the predictions
From c83e8239733336aa1890d00d597572c9d68d945d Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Mon, 22 May 2023 16:06:54 +0000
Subject: [PATCH 10/55] initial notebook
Signed-off-by: GiulioZizzo
---
notebooks/smoothed_vision_transformers.ipynb | 118 +++++++++++++++++++
1 file changed, 118 insertions(+)
create mode 100644 notebooks/smoothed_vision_transformers.ipynb
diff --git a/notebooks/smoothed_vision_transformers.ipynb b/notebooks/smoothed_vision_transformers.ipynb
new file mode 100644
index 0000000000..a1f4eea984
--- /dev/null
+++ b/notebooks/smoothed_vision_transformers.ipynb
@@ -0,0 +1,118 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "58063edd",
+ "metadata": {},
+ "source": [
+ "# Certification of Vision Transformers"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "0438abb9",
+ "metadata": {},
+ "source": [
+ "In this notebook we will go over how to use the PyTorchSmoothedViT tool and be able to certify vision transformers against patch attacks!\n",
+ "\n",
+ "### Overview\n",
+ "\n",
+ "This was introduced in Certified Patch Robustness via Smoothed Vision Transformers (https://arxiv.org/abs/2110.07719). The core technique is one of *image ablations*, where the image is blanked out except for certain regions. By ablating the input in different ways every time we can obtain many predicitons for a single input. Now, as we are ablating large parts of the image the attacker's patch attack is also getting removed in many predictions. Based on factors like the size of the adversarial patch and the size of the retained part of the image the attacker will only be able to influence a limited number of predictions. In fact, if the attacker has a m x m patch attack and the retained part of the image is a column of width s then the maximum number of predictions that could be affected are: \n",
+ "\n",
+ "$\n",
+ "insert equation here\n",
+ "$\n",
+ "\n",
+ "Based on this relationship we can derive a simple but effective criterion that if we are making many predictions for an image and the highest predicted class $c_t$ has been predicted $k$ times and the second most predicted class $c_{t-1}$ has been predicted $k_{t-1}$ times then we have a certified prediction if: \n",
+ "\n",
+ "\n",
+ "$insert here$\n",
+ "\n",
+ "Intuitivly we are saying that even if $k$ predictions were adversarially influenced and those predictions were to change, then the model will *still* have predicted class $c_t$.\n",
+ "\n",
+ "### What's special about Vision Transformers?\n",
+ "\n",
+ "The formulation above is very generic and it can be applied to any nerual network model, in fact the original paper which proposed it () considered the case with convolutional nerual networks. \n",
+ "\n",
+ "However, Vision Transformers (or ViTs) are well siuted to this task of predicting with vision ablations for two key reasons: \n",
+ "\n",
+ "+ ViTs first tokenize the input into a series of image regions which get embedded and then processed through the neural network. Thus, by considering the input as a set of tokens we can drop tokens which correspond to fully masked (i.e ablated)regions significantly saving on the compute costs. \n",
+ "\n",
+ "+ Secondly, the ViT's self attention layer enables sharing of information globally at every layer. In contrast convolutional neural networks build up the receptive field over a series of layers. Hence, ViTs can be more effective at classifying an image based on its small unablated regions.\n",
+ "\n",
+ "Let's see how to use these tools!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "aeb27667",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# The core tool is PyTorchSmoothedViT which can be imported as follows:\n",
+ "from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "353ef5a6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# There are a few ways we can interface with it. \n",
+ "# The most direct way to get setup is by specifying the name of a supported transformer.\n",
+ "# Behind the scenes we are using the timm library (link: ) so any ViT supported by that libary will work.\n",
+ "\n",
+ "art_model = PyTorchSmoothedViT(model='vit_small_patch16_224', # Name of the model acitecture to load\n",
+ " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n",
+ " optimizer=torch.optim.SGD, # the optimizer to use: note! this is not initialised here we just supply the class!\n",
+ " optimizer_params={\"lr\": 0.01}, # the parameters to use\n",
+ " input_shape=(3, 32, 32), # the input shape of the data: Note! ...\n",
+ " nb_classes=10,\n",
+ " ablation_size=4,\n",
+ " load_pretrained=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c7a4255f",
+ "metadata": {},
+ "source": [
+ "Creating a PyTorchSmoothedViT instance with the above code follows many of the general ART patterns with two caveats: \n",
+ "+ The optimizer would (normally) be supplied initialised into the estimator along with a pytorch model. However, here we have not yet created the model, we are just supplying the model architecture name. Hence, here we pass the class into PyTorchSmoothedViT with the keyword arguments in optimizer_params which you would normally use to initialise it.\n",
+ "+ The input shape..."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "e7253ce1",
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.14"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
From a502a8224b098f81340f798ddbdabf6f701987b3 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 24 May 2023 20:02:10 +0100
Subject: [PATCH 11/55] adding testing and updated input args
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/__init__.py | 2 +-
.../smoothed_vision_transformers/pytorch.py | 127 ++++++++-----
.../smooth_vit.py | 12 +-
dev.py | 17 +-
notebooks/smoothed_vision_transformers.ipynb | 90 ++++++++-
.../certification/test_smooth_vit.py | 174 ++++++++++++++++++
6 files changed, 358 insertions(+), 64 deletions(-)
create mode 100644 tests/estimators/certification/test_smooth_vit.py
diff --git a/art/estimators/certification/smoothed_vision_transformers/__init__.py b/art/estimators/certification/smoothed_vision_transformers/__init__.py
index fc9a45bb4f..fd2b959474 100644
--- a/art/estimators/certification/smoothed_vision_transformers/__init__.py
+++ b/art/estimators/certification/smoothed_vision_transformers/__init__.py
@@ -1 +1 @@
-from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT, ArtViT
\ No newline at end of file
+from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT
\ No newline at end of file
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index 173399d1ba..c960001c8e 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -26,7 +26,6 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
-import sys
from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
import random
import torch
@@ -41,6 +40,7 @@
from art.utils import check_and_transform_label_format
if TYPE_CHECKING:
+ import torchvision
from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
from art.defences.preprocessor import Preprocessor
from art.defences.postprocessor import Postprocessor
@@ -80,13 +80,13 @@ class PatchEmbed(torch.nn.Module):
"""
- def __init__(self, patch_size: int=16, in_channels: int=1, embed_dim:int=768):
+ def __init__(self, patch_size: int = 16, in_channels: int = 1, embed_dim: int = 768):
"""
Specifies the configuration for the convolutional layer.
- :param patch_size: The patch size used by the ViT
- :param in_channels: Number of input channels.
- :param embed_dim: The embedding dimension used by the ViT
+ :param patch_size: The patch size used by the ViT.
+ :param in_channels: Number of input channels.
+ :param embed_dim: The embedding dimension used by the ViT.
"""
super().__init__()
self.patch_size = patch_size
@@ -94,13 +94,14 @@ def __init__(self, patch_size: int=16, in_channels: int=1, embed_dim:int=768):
self.embed_dim = embed_dim
self.proj: Optional[torch.nn.Conv2d] = None
- def create(self, patch_size=None, embed_dim=None, **kwargs) -> None:
+ def create(self, patch_size=None, embed_dim=None, **kwargs) -> None: # pylint: disable=W0613
"""
Creates a convolution that mimics the embedding layer to be used for the ablation mask to
track where the image was ablated.
:param patch_size: The patch size used by the ViT
:param embed_dim: The embedding dimension used by the ViT
+ :param kwargs: Handles the remaining kwargs from the ViT configuration.
"""
if patch_size is not None:
@@ -135,16 +136,28 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
class ArtViT(VisionTransformer):
+ """
+ Art class inheriting from VisionTransformer to control the forward pass of the ViT.
+ """
# Make as a class attribute to avoid being included in the
# state dictionaries of the ViT Model.
ablation_mask_embedder = PatchEmbed(in_channels=1)
def __init__(self, **kwargs):
+ """
+ Create a ArtViT instance
+ :param **kwargs: keyword arguments required to create the mask embedder.
+ """
+ self.to_drop_tokens = kwargs['drop_tokens']
+ del kwargs['drop_tokens']
super().__init__(**kwargs)
self.ablation_mask_embedder.create(**kwargs)
+ self.in_chans = kwargs['in_chans']
+ self.img_size = kwargs['img_size']
+
@staticmethod
- def drop_tokens(x: torch.Tensor, indexes) -> torch.Tensor:
+ def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor:
"""
Drops the tokens which correspond to fully masked inputs
@@ -158,8 +171,7 @@ def drop_tokens(x: torch.Tensor, indexes) -> torch.Tensor:
# reshape to temporarily remove batch
x_no_cl = torch.reshape(x_no_cl, shape=(-1, shape[-1]))
indexes = torch.reshape(indexes, shape=(-1,))
- indexes = (indexes == True).nonzero(as_tuple=True)[0]
-
+ indexes = indexes.nonzero(as_tuple=True)[0]
x_no_cl = torch.index_select(x_no_cl, dim=0, index=indexes)
x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1]))
return torch.cat((cls_token, x_no_cl), dim=1)
@@ -167,20 +179,21 @@ def drop_tokens(x: torch.Tensor, indexes) -> torch.Tensor:
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
"""
The forward pass of the ViT.
- #TODO! check for 1 channel inputs!
-
:param x: Input data.
-
+ :return: The input processed by the ViT backbone
"""
- drop_tokens = True
- if x.shape[1] == 4:
- x, ablation_mask = x[:, :3], x[:, 3:4]
+ ablated_input = False
+ if x.shape[1] == self.in_chans + 1:
+ ablated_input = True
+
+ if ablated_input:
+ x, ablation_mask = x[:, :self.in_chans], x[:, self.in_chans:self.in_chans + 1]
x = self.patch_embed(x)
x = self._pos_embed(x)
- if drop_tokens:
+ if self.to_drop_tokens and ablated_input:
ones = self.ablation_mask_embedder(ablation_mask)
to_drop = torch.sum(ones, dim=2)
indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
@@ -191,15 +204,24 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
class PyTorchSmoothedViT(PyTorchClassifier):
+ """
+ Implementation of Certified Patch Robustness via Smoothed Vision Transformers
+
+ | Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+ | Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+ """
def __init__(
self,
model: Union[VisionTransformer, str],
loss: "torch.nn.modules.loss._Loss",
input_shape: Tuple[int, ...],
nb_classes: int,
- ablation_type: str,
ablation_size: int,
- threshold: float,
+ replace_last_layer: bool,
+ drop_tokens: bool = True,
+ load_pretrained: bool = True,
optimizer: Union[type, "torch.optim.Optimizer", None] = None,
optimizer_params: Optional[dict] = None,
channels_first: bool = True,
@@ -208,7 +230,6 @@ def __init__(
postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
device_type: str = "gpu",
- load_pretrained: bool = True,
):
"""
Create a smoothed ViT classifier.
@@ -219,10 +240,13 @@ def __init__(
categorical, i.e. not converted to one-hot encoding.
:param input_shape: The shape of one input instance.
:param nb_classes: The number of classes of the model.
- :param ablation_type: The type of ablation to perform, must be either "column" or "block"
- :param ablation_size: The size of the data portion to retain after ablation. Will be a column of size N for
- "column" ablation type or a NxN square for ablation of type "block"
- :param threshold: The minimum threshold to count a prediction.
+ :param ablation_size: The size of the data portion to retain after ablation.
+ :param replace_last_layer: If to replace the last layer of the ViT with a fresh layer matching the number
+ of classes for the dataset to be examined. Needed if going from the pre-trained
+ imagenet models to fine-tune on a dataset like CIFAR.
+ :param drop_tokens: If to drop the fully ablated tokens in the ViT
+ :param load_pretrained: If to load a pretrained model matching the ViT name. Will only affect the ViT if a
+ string name is passed to model rather than a ViT directly.
:param optimizer: The optimizer used to train the classifier.
:param channels_first: Set channels first or last.
:param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
@@ -240,8 +264,9 @@ def __init__(
timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
if isinstance(model, str):
- model = timm.create_model(model, pretrained=load_pretrained)
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+ model = timm.create_model(model, pretrained=load_pretrained, drop_tokens=drop_tokens)
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
if isinstance(optimizer, type):
if optimizer_params is not None:
optimizer = optimizer(model.parameters(), **optimizer_params)
@@ -251,9 +276,10 @@ def __init__(
elif isinstance(model, VisionTransformer):
pretrained_cfg = model.pretrained_cfg
supplied_state_dict = model.state_dict()
- model = timm.create_model(pretrained_cfg["architecture"], pretrained=load_pretrained)
+ model = timm.create_model(pretrained_cfg["architecture"])
model.load_state_dict(supplied_state_dict)
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
if optimizer is not None:
if not isinstance(optimizer, torch.optim.Optimizer):
@@ -273,6 +299,11 @@ def __init__(
self.to_reshape = False
if isinstance(model, ArtViT):
+
+ if model.default_cfg['input_size'][0] != input_shape[0]:
+ raise ValueError(f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
+ f' but {input_shape[0]} channels were provided.')
+
if model.default_cfg["input_size"] != input_shape:
print(
f"ViT expects input shape of {model.default_cfg['input_size']}, "
@@ -298,11 +329,9 @@ def __init__(
device_type=device_type,
)
else:
- raise ValueError("opt error")
+ raise ValueError("Error occurred in optimizer creation")
- self.ablation_type = ablation_type
self.ablation_size = (ablation_size,)
- self.threshold = threshold
print(self.model)
self.ablator = ColumnAblator(
@@ -341,7 +370,6 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
:param batch_size: Size of batches.
:param nb_epochs: How many times to forward pass over the input data
"""
- import random
self.model.train()
@@ -365,6 +393,7 @@ def fit( # pylint: disable=W0221
drop_last: bool = False,
scheduler: Optional[Any] = None,
update_batchnorm: bool = True,
+ transform: Optional["torchvision.transforms.transforms.Compose"] = None,
verbose: bool = True,
**kwargs,
) -> None:
@@ -383,10 +412,10 @@ def fit( # pylint: disable=W0221
:param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
to training. Useful on small datasets when using pre-trained ViTs.
:param verbose: if to display training progress bars
+ :param transform:
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
and providing it takes no effect.
"""
- import torch
# Set model mode
self._model.train(mode=training_mode)
@@ -394,10 +423,7 @@ def fit( # pylint: disable=W0221
if self._optimizer is None: # pragma: no cover
raise ValueError("An optimizer is needed to train the model, but none for provided.")
- import torchvision.transforms as transforms
-
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
- transform = transforms.Compose([transforms.RandomHorizontalFlip()])
# Apply preprocessing
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
@@ -429,7 +455,8 @@ def fit( # pylint: disable=W0221
i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])).to(
self._device
)
- i_batch = transform(i_batch)
+ if transform is not None:
+ i_batch = transform(i_batch)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
@@ -447,8 +474,7 @@ def fit( # pylint: disable=W0221
"method PyTorchClassifier.fit."
)
raise err
- # Form the loss function
- # print('the model outputs are ', model_outputs.shape)
+
loss = self.loss(model_outputs, o_batch)
acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
epoch_acc.append(acc)
@@ -476,10 +502,11 @@ def fit( # pylint: disable=W0221
def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
"""
- Evaluates the ViT's normal and certified performance over the supplied data
- :param x: Evaluation data
- :param y: Evaluation labels
- :param batch_size: batch size when evaluating
+ Evaluates the ViT's normal and certified performance over the supplied data.
+
+ :param x: Evaluation data.
+ :param y: Evaluation labels.
+ :param batch_size: batch size when evaluating.
"""
self.model.eval()
@@ -517,24 +544,26 @@ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
pred_counts[np.arange(0, batch_size), model_outputs.argmax(dim=-1)] += 1
predictions.append(model_outputs)
- cert, cert_and_correct, top_predicted_class = self.ablator.certify(
+ _, cert_and_correct, top_predicted_class = self.ablator.certify(
pred_counts, size_to_certify=4, label=o_batch
)
cert_acc.append(torch.sum(cert_and_correct) / batch_size)
acc = torch.sum(top_predicted_class == o_batch) / batch_size
accuracy.append(acc)
- print("Normal Acc: ", torch.mean(torch.stack(accuracy)))
- print("Cert Normal Acc: ", torch.mean(torch.stack(cert_acc)))
+ pbar.set_description(
+ f"Normal Acc {torch.mean(torch.stack(accuracy)):.2f} "
+ f"Cert Acc {torch.mean(torch.stack(cert_acc)):.2f}"
+ )
@staticmethod
def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
"""
- Helper function to print out the accuracy during training
+ Helper function to print out the accuracy during training.
- :param preds: (concrete) model predictions
- :param labels: ground truth labels (not one hot)
- :return: prediction accuracy
+ :param preds: (concrete) model predictions.
+ :param labels: ground truth labels (not one hot).
+ :return: prediction accuracy.
"""
if isinstance(preds, torch.Tensor):
preds = preds.detach().cpu().numpy()
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index 1923e64455..838d0bfa8b 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -23,10 +23,11 @@
| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
-import torch
from typing import Optional, Tuple
+import torch
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -42,7 +43,7 @@ def __init__(self, input_size: int, final_size: int) -> None:
:param input_size: Size of the current input data
:param final_size: Desired final size
"""
- super(UpSampler, self).__init__()
+ super().__init__()
self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
def forward(self, x: torch.Tensor) -> torch.Tensor:
@@ -125,14 +126,15 @@ def certify(
:param pred_counts: The model predictions over the ablated data.
:param size_to_certify: The patch size we wish to check certification against
:param label: The ground truth labels
- :return: A tuple consisting of: the certified predictions, the predictions which were certified and also correct,
- and the most predicted class across the different ablations on the input
+ :return: A tuple consisting of: the certified predictions,
+ the predictions which were certified and also correct,
+ and the most predicted class across the different ablations on the input.
"""
num_of_classes = pred_counts.shape[-1]
top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
- second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
+ second_class_counts, _ = pred_counts.kthvalue(num_of_classes - 1, dim=1)
cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
diff --git a/dev.py b/dev.py
index 28c828a5c0..2894682d7c 100644
--- a/dev.py
+++ b/dev.py
@@ -1,9 +1,11 @@
import torch
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
-from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT, ArtViT
+from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT
import numpy as np
from torchvision import datasets
+from torchvision import transforms
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -39,13 +41,18 @@ def get_cifar_data():
optimizer_params={"lr": 0.01},
input_shape=(3, 32, 32),
nb_classes=10,
- ablation_type='column',
ablation_size=4,
- threshold=0.01,
- load_pretrained=True)
+ replace_last_layer=True,
+ load_pretrained=True,
+ )
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
-# art_model.fit(x_train, y_train, nb_epochs=30, update_batchnorm=True, scheduler=scheduler)
+art_model.fit(x_train, y_train,
+ nb_epochs=30,
+ update_batchnorm=True,
+ scheduler=scheduler,
+ transform=transforms.Compose([transforms.RandomHorizontalFlip()]))
+
# torch.save(art_model.model.state_dict(), 'trained.pt')
# art_model.model.load_state_dict(torch.load('trained.pt'))
art_model.eval_and_certify(x_train, y_train)
diff --git a/notebooks/smoothed_vision_transformers.ipynb b/notebooks/smoothed_vision_transformers.ipynb
index a1f4eea984..01cbb6829e 100644
--- a/notebooks/smoothed_vision_transformers.ipynb
+++ b/notebooks/smoothed_vision_transformers.ipynb
@@ -54,6 +54,40 @@
"from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT"
]
},
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "80541a3a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Function to fetch the cifar-10 data\n",
+ "def get_cifar_data():\n",
+ " \"\"\"\n",
+ " Get CIFAR-10 data.\n",
+ " :return: cifar train/test data.\n",
+ " \"\"\"\n",
+ " train_set = datasets.CIFAR10('./data', train=True, download=True)\n",
+ " test_set = datasets.CIFAR10('./data', train=False, download=True)\n",
+ "\n",
+ " x_train = train_set.data.astype(np.float32)\n",
+ " y_train = np.asarray(train_set.targets)\n",
+ "\n",
+ " x_test = test_set.data.astype(np.float32)\n",
+ " y_test = np.asarray(test_set.targets)\n",
+ "\n",
+ " x_train = np.moveaxis(x_train, [3], [1])\n",
+ " x_test = np.moveaxis(x_test, [3], [1])\n",
+ "\n",
+ " x_train = x_train / 255.0\n",
+ " x_test = x_test / 255.0\n",
+ "\n",
+ " return (x_train, y_train), (x_test, y_test)\n",
+ "\n",
+ "\n",
+ "(x_train, y_train), (x_test, y_test) = get_cifar_data()"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -61,7 +95,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# There are a few ways we can interface with it. \n",
+ "# There are a few ways we can interface with PyTorchSmoothedViT. \n",
"# The most direct way to get setup is by specifying the name of a supported transformer.\n",
"# Behind the scenes we are using the timm library (link: ) so any ViT supported by that libary will work.\n",
"\n",
@@ -72,7 +106,7 @@
" input_shape=(3, 32, 32), # the input shape of the data: Note! ...\n",
" nb_classes=10,\n",
" ablation_size=4,\n",
- " load_pretrained=True)"
+ " load_pretrained=True) # if to load pre-trained weights for the ViT"
]
},
{
@@ -82,7 +116,20 @@
"source": [
"Creating a PyTorchSmoothedViT instance with the above code follows many of the general ART patterns with two caveats: \n",
"+ The optimizer would (normally) be supplied initialised into the estimator along with a pytorch model. However, here we have not yet created the model, we are just supplying the model architecture name. Hence, here we pass the class into PyTorchSmoothedViT with the keyword arguments in optimizer_params which you would normally use to initialise it.\n",
- "+ The input shape..."
+ "+ The input shape will primiarily determine if the input requires upsampling. The ViT model such as the one loaded is for images of 224 x 224 resolution, thus in our case of using CIFAR data, we will be upsampling it."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "44975815",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# We can see behind the scenes how PyTorchSmoothedViT processes input by passing in the first few CIFAR\n",
+ "# images into art_model.ablator.forward along with a start position to retain pixels from the original image.\n",
+ "ablated = art_model.ablator.forward(x_train[0:10], column_pos=6)\n",
+ "ablated = ablated.cpu().detach().numpy()\n"
]
},
{
@@ -91,6 +138,41 @@
"id": "e7253ce1",
"metadata": {},
"outputs": [],
+ "source": [
+ "# We can now train the model \n",
+ "\n",
+ "scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)\n",
+ "art_model.fit(x_train, y_train, nb_epochs=30, update_batchnorm=True, scheduler=scheduler)\n",
+ "torch.save(art_model.model.state_dict(), 'trained.pt')\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "046b8168",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Perform certification\n",
+ "art_model.model.load_state_dict(torch.load('trained.pt'))\n",
+ "art_model.eval_and_certify(x_test, y_test)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "128ce03a",
+ "metadata": {},
+ "source": [
+ "We can also setup the PyTorchSmoothedViT if we start with a ViT model directly.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2f41e078",
+ "metadata": {},
+ "outputs": [],
"source": []
}
],
@@ -110,7 +192,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.8.14"
+ "version": "3.8.10"
}
},
"nbformat": 4,
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
new file mode 100644
index 0000000000..d6125f33dd
--- /dev/null
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -0,0 +1,174 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+import pytest
+
+import numpy as np
+
+from art.utils import load_dataset
+from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT
+from tests.utils import ARTTestException
+
+
+@pytest.fixture()
+def fix_get_mnist_data():
+ """
+ Get the first 128 samples of the mnist test set with channels first format
+
+ :return: First 128 sample/label pairs of the MNIST test dataset.
+ """
+ nb_test = 128
+
+ (_, _), (x_test, y_test), _, _ = load_dataset("mnist")
+ x_test = np.squeeze(x_test).astype(np.float32)
+ x_test = np.expand_dims(x_test, axis=1)
+ y_test = np.argmax(y_test, axis=1)
+
+ x_test, y_test = x_test[:nb_test], y_test[:nb_test]
+ return x_test, y_test
+
+
+@pytest.fixture()
+def fix_get_cifar10_data():
+ """
+ Get the first 128 samples of the cifar10 test set
+
+ :return: First 128 sample/label pairs of the cifar10 test dataset.
+ """
+ nb_test = 128
+
+ (_, _), (x_test, y_test), _, _ = load_dataset("cifar10")
+ y_test = np.argmax(y_test, axis=1)
+ x_test, y_test = x_test[:nb_test], y_test[:nb_test]
+ x_test = np.transpose(x_test, (0, 3, 1, 2)) # return in channels first format
+ return x_test.astype(np.float32), y_test
+
+
+@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ Check that the ablation is being performed correctly
+ """
+ from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+ import torch
+ try:
+ cifar_data = fix_get_cifar10_data[0]
+ cifar_labels = fix_get_cifar10_data[1]
+
+ col_ablator = ColumnAblator(ablation_size=4,
+ channels_first=True,
+ to_reshape=False, # do not upsample initially
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224))
+
+ cifar_data = torch.from_numpy(cifar_data)
+ # check that the ablation functioned when in the middle of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=10)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, :, 0:10]) == 0
+ assert torch.sum(ablated[:, :, :, 10:14]) > 0
+ assert torch.sum(ablated[:, :, :, 14:]) == 0
+
+ # check that the ablation wraps when on the edge of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=30)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, :, 30:]) > 0
+ assert torch.sum(ablated[:, :, :, 2:30]) == 0
+ assert torch.sum(ablated[:, :, :, :2]) > 0
+
+ # check that upsampling works as expected
+ col_ablator = ColumnAblator(ablation_size=4,
+ channels_first=True,
+ to_reshape=True,
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224))
+
+ ablated = col_ablator.forward(cifar_data, column_pos=10)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, :, :10*7]) == 0
+ assert torch.sum(ablated[:, :, :, 10*7:14*7]) > 0
+ assert torch.sum(ablated[:, :, :, 14*7:]) == 0
+
+ # check that the ablation wraps when on the edge of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=30)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, :, 30*7:]) > 0
+ assert torch.sum(ablated[:, :, :, 2*7:30*7]) == 0
+ assert torch.sum(ablated[:, :, :, :2*7]) > 0
+
+ except ARTTestException as e:
+ art_warning(e)
+
+
+@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ Check that the training loop for pytorch does not result in errors
+ """
+ import torch
+ try:
+ cifar_data = fix_get_cifar10_data[0][0:50]
+ cifar_labels = fix_get_cifar10_data[1][0:50]
+
+ art_model = PyTorchSmoothedViT(model='vit_small_patch16_224',
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_type='column',
+ ablation_size=4,
+ threshold=0.01,
+ load_pretrained=True)
+
+ scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[1], gamma=0.1)
+ art_model.fit(cifar_data, cifar_labels, nb_epochs=2, update_batchnorm=True, scheduler=scheduler)
+
+ except ARTTestException as e:
+ art_warning(e)
+
+
+@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ Check that the training loop for pytorch does not result in errors
+ """
+ """
+ Check that the ablation is being performed correctly
+ """
+ from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+ import torch
+
+ try:
+ col_ablator = ColumnAblator(ablation_size=4,
+ channels_first=True,
+ to_reshape=True, # do not upsample initially
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224))
+ pred_counts = torch.from_numpy(np.asarray([[20, 5, 1], [10, 5, 1], [1, 16, 1]]))
+ cert, cert_and_correct, top_predicted_class = col_ablator.certify(pred_counts=pred_counts,
+ size_to_certify=4,
+ label=0,)
+ assert torch.equal(cert, torch.tensor([True, False, True]))
+ assert torch.equal(cert_and_correct, torch.tensor([True, False, False]))
+ except ARTTestException as e:
+ art_warning(e)
+
From cdedd06d0aa089031cc70a6f52c5ef628c3300d4 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Fri, 26 May 2023 19:06:52 +0000
Subject: [PATCH 12/55] Adding checks to supplied models. New method to get
supported models
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 101 +++++++++++++++---
.../certification/test_smooth_vit.py | 15 +--
2 files changed, 93 insertions(+), 23 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index c960001c8e..c6b6090730 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -101,7 +101,7 @@ def create(self, patch_size=None, embed_dim=None, **kwargs) -> None: # pylint:
:param patch_size: The patch size used by the ViT
:param embed_dim: The embedding dimension used by the ViT
- :param kwargs: Handles the remaining kwargs from the ViT configuration.
+ :param kwargs: Handles the remaining kwargs from the ViT configuration.
"""
if patch_size is not None:
@@ -146,7 +146,7 @@ class ArtViT(VisionTransformer):
def __init__(self, **kwargs):
"""
Create a ArtViT instance
- :param **kwargs: keyword arguments required to create the mask embedder.
+ :param kwargs: keyword arguments required to create the mask embedder.
"""
self.to_drop_tokens = kwargs['drop_tokens']
del kwargs['drop_tokens']
@@ -179,6 +179,7 @@ def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor:
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
"""
The forward pass of the ViT.
+
:param x: Input data.
:return: The input processed by the ViT backbone
"""
@@ -230,6 +231,7 @@ def __init__(
postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
device_type: str = "gpu",
+ verbose: bool = True,
):
"""
Create a smoothed ViT classifier.
@@ -276,7 +278,11 @@ def __init__(
elif isinstance(model, VisionTransformer):
pretrained_cfg = model.pretrained_cfg
supplied_state_dict = model.state_dict()
- model = timm.create_model(pretrained_cfg["architecture"])
+ supported_models = self.get_models()
+ if pretrained_cfg["architecture"] not in supported_models:
+ raise ValueError('Architecture not supported. Use PyTorchSmoothedViT.get_models() '
+ 'to get the supported model architectures.')
+ model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens)
model.load_state_dict(supplied_state_dict)
if replace_last_layer:
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
@@ -298,21 +304,21 @@ def __init__(
converted_optimizer.load_state_dict(opt_state_dict)
self.to_reshape = False
- if isinstance(model, ArtViT):
+ if not isinstance(model, ArtViT):
+ raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
- if model.default_cfg['input_size'][0] != input_shape[0]:
- raise ValueError(f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
- f' but {input_shape[0]} channels were provided.')
+ if model.default_cfg['input_size'][0] != input_shape[0]:
+ raise ValueError(f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
+ f' but {input_shape[0]} channels were provided.')
- if model.default_cfg["input_size"] != input_shape:
+ if model.default_cfg["input_size"] != input_shape:
+ if verbose:
print(
f"ViT expects input shape of {model.default_cfg['input_size']}, "
f"but {input_shape} specified as the input shape. "
f"The input will be rescaled to {model.default_cfg['input_size']}"
)
- self.to_reshape = True
- else:
- raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
+ self.to_reshape = True
if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
super().__init__(
@@ -333,7 +339,9 @@ def __init__(
self.ablation_size = (ablation_size,)
- print(self.model)
+ if verbose:
+ print(self.model)
+
self.ablator = ColumnAblator(
ablation_size=ablation_size,
channels_first=True,
@@ -342,6 +350,71 @@ def __init__(
output_shape=model.default_cfg["input_size"],
)
+ @classmethod
+ def get_models(cls, generate_from_null: bool = False) -> List[str]:
+ """
+ Return the supported model names to the user.
+
+ :param generate_from_null: If to re-check the creation of all the ViTs in timm from scratch.
+ Can be time-consuming.
+ :return: A list of compatible models
+ """
+ import timm
+
+ supported_models = ['vit_base_patch8_224',
+ 'vit_base_patch16_18x2_224', 'vit_base_patch16_224',
+ 'vit_base_patch16_224_miil', 'vit_base_patch16_384',
+ 'vit_base_patch16_clip_224', 'vit_base_patch16_clip_384',
+ 'vit_base_patch16_gap_224', 'vit_base_patch16_plus_240',
+ 'vit_base_patch16_rpn_224', 'vit_base_patch16_xp_224',
+ 'vit_base_patch32_224', 'vit_base_patch32_384',
+ 'vit_base_patch32_clip_224', 'vit_base_patch32_clip_384',
+ 'vit_base_patch32_clip_448', 'vit_base_patch32_plus_256',
+ 'vit_giant_patch14_224', 'vit_giant_patch14_clip_224',
+ 'vit_gigantic_patch14_224', 'vit_gigantic_patch14_clip_224',
+ 'vit_huge_patch14_224', 'vit_huge_patch14_clip_224',
+ 'vit_huge_patch14_clip_336', 'vit_huge_patch14_xp_224',
+ 'vit_large_patch14_224', 'vit_large_patch14_clip_224',
+ 'vit_large_patch14_clip_336', 'vit_large_patch14_xp_224',
+ 'vit_large_patch16_224', 'vit_large_patch16_384', 'vit_large_patch32_224',
+ 'vit_large_patch32_384', 'vit_medium_patch16_gap_240',
+ 'vit_medium_patch16_gap_256', 'vit_medium_patch16_gap_384',
+ 'vit_small_patch16_18x2_224', 'vit_small_patch16_36x1_224',
+ 'vit_small_patch16_224', 'vit_small_patch16_384',
+ 'vit_small_patch32_224', 'vit_small_patch32_384',
+ 'vit_tiny_patch16_224', 'vit_tiny_patch16_384']
+
+ if not generate_from_null:
+ return supported_models
+
+ supported = []
+ unsupported = []
+
+ models = timm.list_models('vit_*')
+ for model in models:
+ print(f'Testing {model} creation')
+ try:
+ _ = PyTorchSmoothedViT(model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ load_pretrained=False,
+ replace_last_layer=True,
+ verbose=False)
+ supported.append(model)
+ except Exception:
+ unsupported.append(model)
+
+ if supported != supported_models:
+ print('Difference between the generated and fixed model list. Although not necessarily '
+ 'an error, this may point to the timm library being updated.')
+
+ return supported
+
+
@staticmethod
def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
"""
@@ -500,13 +573,14 @@ def fit( # pylint: disable=W0221
if scheduler is not None:
scheduler.step()
- def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
+ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128) -> Tuple["torch.Tensor", "torch.Tensor"]:
"""
Evaluates the ViT's normal and certified performance over the supplied data.
:param x: Evaluation data.
:param y: Evaluation labels.
:param batch_size: batch size when evaluating.
+ :return: The accuracy and certtified accuracy over the dataset
"""
self.model.eval()
@@ -555,6 +629,7 @@ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128):
f"Normal Acc {torch.mean(torch.stack(accuracy)):.2f} "
f"Cert Acc {torch.mean(torch.stack(cert_acc)):.2f}"
)
+ return torch.mean(torch.stack(accuracy)), torch.mean(torch.stack(cert_acc))
@staticmethod
def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index d6125f33dd..7a83d4e16c 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -125,8 +125,8 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
"""
import torch
try:
- cifar_data = fix_get_cifar10_data[0][0:50]
- cifar_labels = fix_get_cifar10_data[1][0:50]
+ cifar_data = fix_get_cifar10_data[0][:50]
+ cifar_labels = fix_get_cifar10_data[1][:50]
art_model = PyTorchSmoothedViT(model='vit_small_patch16_224',
loss=torch.nn.CrossEntropyLoss(),
@@ -134,10 +134,9 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
optimizer_params={"lr": 0.01},
input_shape=(3, 32, 32),
nb_classes=10,
- ablation_type='column',
ablation_size=4,
- threshold=0.01,
- load_pretrained=True)
+ load_pretrained=True,
+ replace_last_layer=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[1], gamma=0.1)
art_model.fit(cifar_data, cifar_labels, nb_epochs=2, update_batchnorm=True, scheduler=scheduler)
@@ -149,10 +148,7 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
"""
- Check that the training loop for pytorch does not result in errors
- """
- """
- Check that the ablation is being performed correctly
+ Check that ...
"""
from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
import torch
@@ -171,4 +167,3 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
assert torch.equal(cert_and_correct, torch.tensor([True, False, False]))
except ARTTestException as e:
art_warning(e)
-
From 13ece2c2e9a93990f60df4cace221ef645611260 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Sun, 28 May 2023 20:01:48 +0000
Subject: [PATCH 13/55] adding functionality to get supported models and
re-setting the timm library methods after ViT creation
Signed-off-by: GiulioZizzo
---
.../smoothed_vision_transformers/pytorch.py | 181 ++++++++++++------
.../smooth_vit.py | 12 +-
2 files changed, 128 insertions(+), 65 deletions(-)
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
index c6b6090730..96bc291951 100644
--- a/art/estimators/certification/smoothed_vision_transformers/pytorch.py
+++ b/art/estimators/certification/smoothed_vision_transformers/pytorch.py
@@ -46,7 +46,6 @@
from art.defences.postprocessor import Postprocessor
logger = logging.getLogger(__name__)
-device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class PatchEmbed(torch.nn.Module):
@@ -94,13 +93,14 @@ def __init__(self, patch_size: int = 16, in_channels: int = 1, embed_dim: int =
self.embed_dim = embed_dim
self.proj: Optional[torch.nn.Conv2d] = None
- def create(self, patch_size=None, embed_dim=None, **kwargs) -> None: # pylint: disable=W0613
+ def create(self, patch_size=None, embed_dim=None, device="cpu", **kwargs) -> None: # pylint: disable=W0613
"""
Creates a convolution that mimics the embedding layer to be used for the ablation mask to
track where the image was ablated.
:param patch_size: The patch size used by the ViT
:param embed_dim: The embedding dimension used by the ViT
+ :param device: Which device to set the emdedding layer to.
:param kwargs: Handles the remaining kwargs from the ViT configuration.
"""
@@ -116,7 +116,6 @@ def create(self, patch_size=None, embed_dim=None, **kwargs) -> None: # pylint:
stride=self.patch_size,
bias=False,
)
-
w_shape = self.proj.weight.shape
self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
@@ -139,6 +138,7 @@ class ArtViT(VisionTransformer):
"""
Art class inheriting from VisionTransformer to control the forward pass of the ViT.
"""
+
# Make as a class attribute to avoid being included in the
# state dictionaries of the ViT Model.
ablation_mask_embedder = PatchEmbed(in_channels=1)
@@ -148,20 +148,29 @@ def __init__(self, **kwargs):
Create a ArtViT instance
:param kwargs: keyword arguments required to create the mask embedder.
"""
- self.to_drop_tokens = kwargs['drop_tokens']
- del kwargs['drop_tokens']
+ self.to_drop_tokens = kwargs["drop_tokens"]
+
+ if kwargs["device_type"] == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
+ del kwargs["drop_tokens"]
+ del kwargs["device_type"]
+
super().__init__(**kwargs)
- self.ablation_mask_embedder.create(**kwargs)
+ self.ablation_mask_embedder.create(device=self.device, **kwargs)
- self.in_chans = kwargs['in_chans']
- self.img_size = kwargs['img_size']
+ self.in_chans = kwargs["in_chans"]
+ self.img_size = kwargs["img_size"]
@staticmethod
def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor:
"""
Drops the tokens which correspond to fully masked inputs
- :param x: Input data in .... format
+ :param x: Input data
:param indexes: positions to be ablated
:return: Input with tokens dropped where the input was fully ablated.
"""
@@ -189,7 +198,7 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
ablated_input = True
if ablated_input:
- x, ablation_mask = x[:, :self.in_chans], x[:, self.in_chans:self.in_chans + 1]
+ x, ablation_mask = x[:, : self.in_chans], x[:, self.in_chans : self.in_chans + 1]
x = self.patch_embed(x)
x = self._pos_embed(x)
@@ -200,6 +209,7 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
x = self.drop_tokens(x, indexes)
+ x = self.norm_pre(x)
x = self.blocks(x)
return self.norm(x)
@@ -213,6 +223,7 @@ class PyTorchSmoothedViT(PyTorchClassifier):
| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
+
def __init__(
self,
model: Union[VisionTransformer, str],
@@ -264,9 +275,15 @@ def __init__(
"""
import timm
+ # temporarily assign the original method to tmp_func
+ tmp_func = timm.models.vision_transformer._create_vision_transformer
+
+ # overrride with ART's ViT creation function
timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
if isinstance(model, str):
- model = timm.create_model(model, pretrained=load_pretrained, drop_tokens=drop_tokens)
+ model = timm.create_model(
+ model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type
+ )
if replace_last_layer:
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
if isinstance(optimizer, type):
@@ -280,9 +297,11 @@ def __init__(
supplied_state_dict = model.state_dict()
supported_models = self.get_models()
if pretrained_cfg["architecture"] not in supported_models:
- raise ValueError('Architecture not supported. Use PyTorchSmoothedViT.get_models() '
- 'to get the supported model architectures.')
- model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens)
+ raise ValueError(
+ "Architecture not supported. Use PyTorchSmoothedViT.get_models() "
+ "to get the supported model architectures."
+ )
+ model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type)
model.load_state_dict(supplied_state_dict)
if replace_last_layer:
model.head = torch.nn.Linear(model.head.in_features, nb_classes)
@@ -307,9 +326,11 @@ def __init__(
if not isinstance(model, ArtViT):
raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
- if model.default_cfg['input_size'][0] != input_shape[0]:
- raise ValueError(f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
- f' but {input_shape[0]} channels were provided.')
+ if model.default_cfg["input_size"][0] != input_shape[0]:
+ raise ValueError(
+ f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
+ f" but {input_shape[0]} channels were provided."
+ )
if model.default_cfg["input_size"] != input_shape:
if verbose:
@@ -348,8 +369,12 @@ def __init__(
to_reshape=self.to_reshape,
original_shape=input_shape,
output_shape=model.default_cfg["input_size"],
+ device_type=device_type,
)
+ # set the method back to avoid unexpected side effects later on should timm need to be reused.
+ timm.models.vision_transformer._create_vision_transformer = tmp_func
+
@classmethod
def get_models(cls, generate_from_null: bool = False) -> List[str]:
"""
@@ -361,28 +386,52 @@ def get_models(cls, generate_from_null: bool = False) -> List[str]:
"""
import timm
- supported_models = ['vit_base_patch8_224',
- 'vit_base_patch16_18x2_224', 'vit_base_patch16_224',
- 'vit_base_patch16_224_miil', 'vit_base_patch16_384',
- 'vit_base_patch16_clip_224', 'vit_base_patch16_clip_384',
- 'vit_base_patch16_gap_224', 'vit_base_patch16_plus_240',
- 'vit_base_patch16_rpn_224', 'vit_base_patch16_xp_224',
- 'vit_base_patch32_224', 'vit_base_patch32_384',
- 'vit_base_patch32_clip_224', 'vit_base_patch32_clip_384',
- 'vit_base_patch32_clip_448', 'vit_base_patch32_plus_256',
- 'vit_giant_patch14_224', 'vit_giant_patch14_clip_224',
- 'vit_gigantic_patch14_224', 'vit_gigantic_patch14_clip_224',
- 'vit_huge_patch14_224', 'vit_huge_patch14_clip_224',
- 'vit_huge_patch14_clip_336', 'vit_huge_patch14_xp_224',
- 'vit_large_patch14_224', 'vit_large_patch14_clip_224',
- 'vit_large_patch14_clip_336', 'vit_large_patch14_xp_224',
- 'vit_large_patch16_224', 'vit_large_patch16_384', 'vit_large_patch32_224',
- 'vit_large_patch32_384', 'vit_medium_patch16_gap_240',
- 'vit_medium_patch16_gap_256', 'vit_medium_patch16_gap_384',
- 'vit_small_patch16_18x2_224', 'vit_small_patch16_36x1_224',
- 'vit_small_patch16_224', 'vit_small_patch16_384',
- 'vit_small_patch32_224', 'vit_small_patch32_384',
- 'vit_tiny_patch16_224', 'vit_tiny_patch16_384']
+ supported_models = [
+ "vit_base_patch8_224",
+ "vit_base_patch16_18x2_224",
+ "vit_base_patch16_224",
+ "vit_base_patch16_224_miil",
+ "vit_base_patch16_384",
+ "vit_base_patch16_clip_224",
+ "vit_base_patch16_clip_384",
+ "vit_base_patch16_gap_224",
+ "vit_base_patch16_plus_240",
+ "vit_base_patch16_rpn_224",
+ "vit_base_patch16_xp_224",
+ "vit_base_patch32_224",
+ "vit_base_patch32_384",
+ "vit_base_patch32_clip_224",
+ "vit_base_patch32_clip_384",
+ "vit_base_patch32_clip_448",
+ "vit_base_patch32_plus_256",
+ "vit_giant_patch14_224",
+ "vit_giant_patch14_clip_224",
+ "vit_gigantic_patch14_224",
+ "vit_gigantic_patch14_clip_224",
+ "vit_huge_patch14_224",
+ "vit_huge_patch14_clip_224",
+ "vit_huge_patch14_clip_336",
+ "vit_huge_patch14_xp_224",
+ "vit_large_patch14_224",
+ "vit_large_patch14_clip_224",
+ "vit_large_patch14_clip_336",
+ "vit_large_patch14_xp_224",
+ "vit_large_patch16_224",
+ "vit_large_patch16_384",
+ "vit_large_patch32_224",
+ "vit_large_patch32_384",
+ "vit_medium_patch16_gap_240",
+ "vit_medium_patch16_gap_256",
+ "vit_medium_patch16_gap_384",
+ "vit_small_patch16_18x2_224",
+ "vit_small_patch16_36x1_224",
+ "vit_small_patch16_224",
+ "vit_small_patch16_384",
+ "vit_small_patch32_224",
+ "vit_small_patch32_384",
+ "vit_tiny_patch16_224",
+ "vit_tiny_patch16_384",
+ ]
if not generate_from_null:
return supported_models
@@ -390,31 +439,34 @@ def get_models(cls, generate_from_null: bool = False) -> List[str]:
supported = []
unsupported = []
- models = timm.list_models('vit_*')
+ models = timm.list_models("vit_*")
for model in models:
- print(f'Testing {model} creation')
+ print(f"Testing {model} creation")
try:
- _ = PyTorchSmoothedViT(model=model,
- loss=torch.nn.CrossEntropyLoss(),
- optimizer=torch.optim.SGD,
- optimizer_params={"lr": 0.01},
- input_shape=(3, 32, 32),
- nb_classes=10,
- ablation_size=4,
- load_pretrained=False,
- replace_last_layer=True,
- verbose=False)
+ _ = PyTorchSmoothedViT(
+ model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ load_pretrained=False,
+ replace_last_layer=True,
+ verbose=False,
+ )
supported.append(model)
- except Exception:
+ except (TypeError, AttributeError):
unsupported.append(model)
if supported != supported_models:
- print('Difference between the generated and fixed model list. Although not necessarily '
- 'an error, this may point to the timm library being updated.')
+ print(
+ "Difference between the generated and fixed model list. Although not necessarily "
+ "an error, this may point to the timm library being updated."
+ )
return supported
-
@staticmethod
def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
"""
@@ -452,7 +504,7 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
with torch.no_grad():
for _ in tqdm(range(nb_epochs)):
for m in tqdm(range(num_batch)):
- i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(device)
+ i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(self.device)
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
_ = self.model(i_batch)
@@ -466,6 +518,7 @@ def fit( # pylint: disable=W0221
drop_last: bool = False,
scheduler: Optional[Any] = None,
update_batchnorm: bool = True,
+ batchnorm_update_epochs: int = 1,
transform: Optional["torchvision.transforms.transforms.Compose"] = None,
verbose: bool = True,
**kwargs,
@@ -484,8 +537,10 @@ def fit( # pylint: disable=W0221
:param scheduler: Learning rate scheduler to run at the start of every epoch.
:param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
to training. Useful on small datasets when using pre-trained ViTs.
+ :param batchnorm_update_epochs: how many times to forward pass over the training data
+ to pre-adjust the batchnorm statistics.
+ :param transform: Torchvision compose of relevant augmentation transformations to apply.
:param verbose: if to display training progress bars
- :param transform:
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
and providing it takes no effect.
"""
@@ -502,7 +557,7 @@ def fit( # pylint: disable=W0221
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
if update_batchnorm:
- self.update_batchnorm(x_preprocessed, batch_size)
+ self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
# Check label shape
y_preprocessed = self.reduce_labels(y_preprocessed)
@@ -573,14 +628,16 @@ def fit( # pylint: disable=W0221
if scheduler is not None:
scheduler.step()
- def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128) -> Tuple["torch.Tensor", "torch.Tensor"]:
+ def eval_and_certify(
+ self, x: np.ndarray, y: np.ndarray, batch_size: int = 128
+ ) -> Tuple["torch.Tensor", "torch.Tensor"]:
"""
Evaluates the ViT's normal and certified performance over the supplied data.
:param x: Evaluation data.
:param y: Evaluation labels.
:param batch_size: batch size when evaluating.
- :return: The accuracy and certtified accuracy over the dataset
+ :return: The accuracy and certified accuracy over the dataset
"""
self.model.eval()
@@ -626,7 +683,7 @@ def eval_and_certify(self, x: np.ndarray, y: np.ndarray, batch_size: int = 128)
accuracy.append(acc)
pbar.set_description(
- f"Normal Acc {torch.mean(torch.stack(accuracy)):.2f} "
+ f"Normal Acc {torch.mean(torch.stack(accuracy)):.2f} "
f"Cert Acc {torch.mean(torch.stack(cert_acc)):.2f}"
)
return torch.mean(torch.stack(accuracy)), torch.mean(torch.stack(cert_acc))
@@ -636,7 +693,7 @@ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndar
"""
Helper function to print out the accuracy during training.
- :param preds: (concrete) model predictions.
+ :param preds: model predictions.
:param labels: ground truth labels (not one hot).
:return: prediction accuracy.
"""
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
index 838d0bfa8b..f9a660199e 100644
--- a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
@@ -28,8 +28,6 @@
import torch
-device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
-
class UpSampler(torch.nn.Module):
"""
@@ -68,6 +66,7 @@ def __init__(
to_reshape: bool = False,
original_shape: Optional[Tuple] = None,
output_shape: Optional[Tuple] = None,
+ device_type: str = "gpu",
):
"""
Creates a column ablator
@@ -82,6 +81,13 @@ def __init__(
self.ablation_size = ablation_size
self.channels_first = channels_first
self.to_reshape = to_reshape
+
+ if device_type == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
if original_shape is not None and output_shape is not None:
self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
@@ -110,7 +116,7 @@ def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
:return: The albated input with an extra channel indicating the location of the ablation
"""
assert x.shape[1] == 3
- ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(device)
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
x = torch.cat([x, ones], dim=1)
x = self.ablate(x, column_pos=column_pos)
if self.to_reshape:
From 280df6664dca07992bc0cd9d40575188c02bc218 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Sun, 28 May 2023 20:05:55 +0000
Subject: [PATCH 14/55] adding additional tests and cuda fixes
Signed-off-by: GiulioZizzo
---
notebooks/smoothed_vision_transformers.ipynb | 882 +++++++++++++++++-
.../certification/test_smooth_vit.py | 151 ++-
2 files changed, 986 insertions(+), 47 deletions(-)
diff --git a/notebooks/smoothed_vision_transformers.ipynb b/notebooks/smoothed_vision_transformers.ipynb
index 01cbb6829e..22c896f161 100644
--- a/notebooks/smoothed_vision_transformers.ipynb
+++ b/notebooks/smoothed_vision_transformers.ipynb
@@ -17,24 +17,22 @@
"\n",
"### Overview\n",
"\n",
- "This was introduced in Certified Patch Robustness via Smoothed Vision Transformers (https://arxiv.org/abs/2110.07719). The core technique is one of *image ablations*, where the image is blanked out except for certain regions. By ablating the input in different ways every time we can obtain many predicitons for a single input. Now, as we are ablating large parts of the image the attacker's patch attack is also getting removed in many predictions. Based on factors like the size of the adversarial patch and the size of the retained part of the image the attacker will only be able to influence a limited number of predictions. In fact, if the attacker has a m x m patch attack and the retained part of the image is a column of width s then the maximum number of predictions that could be affected are: \n",
+ "This method was introduced in Certified Patch Robustness via Smoothed Vision Transformers (https://arxiv.org/abs/2110.07719). The core technique is one of *image ablations*, where the image is blanked out except for certain regions. By ablating the input in different ways every time we can obtain many predicitons for a single input. Now, as we are ablating large parts of the image the attacker's patch attack is also getting removed in many predictions. Based on factors like the size of the adversarial patch and the size of the retained part of the image the attacker will only be able to influence a limited number of predictions. In fact, if the attacker has a $m x m$ patch attack and the retained part of the image is a column of width $s$ then the maximum number of predictions $\\Delta$ that could be affected are: \n",
"\n",
- "$\n",
- "insert equation here\n",
- "$\n",
+ "
$\\Delta = m + s - 1$
\n",
"\n",
- "Based on this relationship we can derive a simple but effective criterion that if we are making many predictions for an image and the highest predicted class $c_t$ has been predicted $k$ times and the second most predicted class $c_{t-1}$ has been predicted $k_{t-1}$ times then we have a certified prediction if: \n",
+ "Based on this relationship we can derive a simple but effective criterion that if we are making many predictions for an image and the highest predicted class $c_t$ has been predicted $k_t$ times and the second most predicted class $c_{t-1}$ has been predicted $k_{t-1}$ times then we have a certified prediction for $c_t$ if: \n",
"\n",
"\n",
- "$insert here$\n",
+ "
+[smoothed_vision_transformers.ipynb](smoothed_vision_transformers.ipynb) [[on nbviewer](https://nbviewer.jupyter.org/github/Trusted-AI/adversarial-robustness-toolbox/blob/main/notebooks/smoothed_vision_transformers.ipynb)]
+Demonstrates training a neural network using smoothed vision transformers for certified performance against patch attacks.
+
## MNIST
[fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb](fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb) [[on nbviewer](https://nbviewer.jupyter.org/github/Trusted-AI/adversarial-robustness-toolbox/blob/main/notebooks/fabric_for_deep_learning_adversarial_samples_fashion_mnist.ipynb)]
diff --git a/requirements_test.txt b/requirements_test.txt
index 91a3828ae3..b76cb982e9 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -35,7 +35,7 @@ torchaudio==0.13.1+cpu
torchvision==0.14.1+cpu
# PyTorch image transformers
-timm@git+https://github.com/huggingface/pytorch-image-models.git@9fcc01930aae865ec9ef8aae8849ca2ba241f816
+timm==0.9.2
catboost==1.1.1
GPy==1.10.0
diff --git a/setup.py b/setup.py
index e6c0cfc077..476d53b886 100644
--- a/setup.py
+++ b/setup.py
@@ -112,7 +112,7 @@ def get_version(rel_path):
"requests",
"sortedcontainers",
"numba",
- # "timm" to be added as a dependency after the next timm release
+ "timm",
],
},
classifiers=[
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index cdc8546d61..1d8a13c8ae 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -292,7 +292,7 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
x = self._pos_embed(x)
assert torch.equal(madry_embed, x)
- # pass the x into the token dropping code from ...
+ # pass the x into the token dropping code
madry_dropped = MadrylabImplementations.token_dropper(copy.copy(x), ablation_mask)
if self.to_drop_tokens and ablated_input:
@@ -301,7 +301,6 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
x = self.drop_tokens(x, indexes)
- print(torch.equal(madry_dropped, x))
assert torch.equal(madry_dropped, x)
x = self.norm_pre(x)
From 5360d89d2dc63e5cb5994c4d423451fbf14623a3 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Tue, 27 Jun 2023 08:47:01 +0100
Subject: [PATCH 22/55] move vit functionality into derandomised smoothing
toolset
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing/pytorch.py | 195 +++++-
.../vision_transformers/__init__.py | 0
.../vision_transformers/pytorch.py | 559 ++++++++++++++++++
.../vision_transformers/smooth_vit.py | 154 +++++
.../vision_transformers/vit.py | 170 ++++++
.../__init__.py | 0
.../pytorch.py | 0
.../smooth_vit.py | 0
dev.py | 56 ++
9 files changed, 1127 insertions(+), 7 deletions(-)
create mode 100644 art/estimators/certification/derandomized_smoothing/vision_transformers/__init__.py
create mode 100644 art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
create mode 100644 art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
create mode 100644 art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
rename art/estimators/certification/{smoothed_vision_transformers => smoothed_vision_transformers_old}/__init__.py (100%)
rename art/estimators/certification/{smoothed_vision_transformers => smoothed_vision_transformers_old}/pytorch.py (100%)
rename art/estimators/certification/{smoothed_vision_transformers => smoothed_vision_transformers_old}/smooth_vit.py (100%)
create mode 100644 dev.py
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 4a184b3666..422ca98d74 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -22,7 +22,7 @@
"""
from __future__ import absolute_import, division, print_function, unicode_literals
-
+import importlib
import logging
from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
import random
@@ -32,6 +32,7 @@
from art.config import ART_NUMPY_DTYPE
from art.estimators.classification.pytorch import PyTorchClassifier
+from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import PyTorchSmoothedViT
from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin
from art.utils import check_and_transform_label_format
@@ -46,7 +47,7 @@
logger = logging.getLogger(__name__)
-class PyTorchDeRandomizedSmoothing(DeRandomizedSmoothingMixin, PyTorchClassifier):
+class PyTorchDeRandomizedSmoothingCNN(DeRandomizedSmoothingMixin, PyTorchClassifier):
"""
Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced
in Levine et al. (2020).
@@ -148,6 +149,150 @@ def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epoc
x = x.astype(ART_NUMPY_DTYPE)
return PyTorchClassifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
+ def fit_old( # pylint: disable=W0221
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ batch_size: int = 128,
+ nb_epochs: int = 10,
+ training_mode: bool = True,
+ drop_last: bool = False,
+ scheduler: Optional[Any] = None,
+ update_batchnorm: bool = True,
+ batchnorm_update_epochs: int = 1,
+ transform: Optional["torchvision.transforms.transforms.Compose"] = None,
+ verbose: bool = True,
+ **kwargs,
+ ) -> None:
+ """
+ Fit the classifier on the training set `(x, y)`.
+
+ :param x: Training data.
+ :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
+ shape (nb_samples,).
+ :param batch_size: Size of batches.
+ :param nb_epochs: Number of epochs to use for training.
+ :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
+ :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
+ the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
+ the last batch will be smaller. (default: ``False``)
+ :param scheduler: Learning rate scheduler to run at the start of every epoch.
+ :param update_batchnorm: ViT Specific Arg. If to run the training data through the model to update any batch norm statistics prior
+ to training. Useful on small datasets when using pre-trained ViTs.
+ :param batchnorm_update_epochs: ViT Specific Arg. How many times to forward pass over the training data
+ to pre-adjust the batchnorm statistics.
+ :param transform: ViT Specific Arg. Torchvision compose of relevant augmentation transformations to apply.
+ :param verbose: if to display training progress bars
+ :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
+ and providing it takes no effect.
+ """
+ import torch
+
+ # Check if we have a VIT
+
+ # Set model mode
+ self._model.train(mode=training_mode)
+
+ if self._optimizer is None: # pragma: no cover
+ raise ValueError("An optimizer is needed to train the model, but none for provided.")
+
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ if update_batchnorm: # VIT specific
+ self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = len(x_preprocessed) / float(batch_size)
+ if drop_last:
+ num_batch = int(np.floor(num_batch))
+ else:
+ num_batch = int(np.ceil(num_batch))
+ ind = np.arange(len(x_preprocessed))
+
+ # Start training
+ for _ in tqdm(range(nb_epochs)):
+ # Shuffle the examples
+ random.shuffle(ind)
+
+ epoch_acc = []
+ epoch_loss = []
+ epoch_batch_sizes = []
+
+ pbar = tqdm(range(num_batch), disable=not verbose)
+
+ # Train for one epoch
+ for m in pbar:
+ i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])
+ i_batch = self.ablator.forward(i_batch)
+
+ if transform is not None: # VIT specific
+ i_batch = transform(i_batch)
+
+ i_batch = torch.from_numpy(i_batch).to(self._device)
+ o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
+
+ # Zero the parameter gradients
+ self._optimizer.zero_grad()
+
+ # Perform prediction
+ try:
+ model_outputs = self.model(i_batch)
+ except ValueError as err:
+ if "Expected more than 1 value per channel when training" in str(err):
+ logger.exception(
+ "Try dropping the last incomplete batch by setting drop_last=True in "
+ "method PyTorchClassifier.fit."
+ )
+ raise err
+
+ loss = self.loss(model_outputs, o_batch)
+ acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
+
+ # Do training
+ if self._use_amp: # pragma: no cover
+ from apex import amp # pylint: disable=E0611
+
+ with amp.scale_loss(loss, self._optimizer) as scaled_loss:
+ scaled_loss.backward()
+
+ else:
+ loss.backward()
+
+ self.optimizer.step()
+
+ epoch_acc.append(acc)
+ epoch_loss.append(loss.cpu().detach().numpy())
+ epoch_batch_sizes.append(len(i_batch))
+
+ if verbose:
+ pbar.set_description(
+ f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
+ f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
+ )
+
+ if scheduler is not None:
+ scheduler.step()
+
+
+class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT):
+ def __init__(self, model: Union[str, "VisionTransformer", "torch.nn.Module"], **kwargs):
+ import torch
+
+ if isinstance(model, torch.nn.Module):
+ PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
+ self.mode = "CNN"
+ if importlib.util.find_spec("timm") is not None:
+ from timm.models.vision_transformer import VisionTransformer
+
+ if isinstance(model, VisionTransformer) or isinstance(model, str):
+ PyTorchSmoothedViT.__init__(self, model, **kwargs)
+ self.mode = "ViT"
+
def fit( # pylint: disable=W0221
self,
x: np.ndarray,
@@ -157,10 +302,15 @@ def fit( # pylint: disable=W0221
training_mode: bool = True,
drop_last: bool = False,
scheduler: Optional[Any] = None,
+ update_batchnorm: bool = True,
+ batchnorm_update_epochs: int = 1,
+ transform: Optional["torchvision.transforms.transforms.Compose"] = None,
+ verbose: bool = True,
**kwargs,
) -> None:
"""
Fit the classifier on the training set `(x, y)`.
+
:param x: Training data.
:param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
shape (nb_samples,).
@@ -171,6 +321,13 @@ def fit( # pylint: disable=W0221
the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
the last batch will be smaller. (default: ``False``)
:param scheduler: Learning rate scheduler to run at the start of every epoch.
+ :param update_batchnorm: ViT specific argument.
+ If to run the training data through the model to update any batch norm statistics prior
+ to training. Useful on small datasets when using pre-trained ViTs.
+ :param batchnorm_update_epochs: ViT specific argument. How many times to forward pass over the training data
+ to pre-adjust the batchnorm statistics.
+ :param transform: ViT specific argument. Torchvision compose of relevant augmentation transformations to apply.
+ :param verbose: if to display training progress bars
:param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
and providing it takes no effect.
"""
@@ -187,6 +344,9 @@ def fit( # pylint: disable=W0221
# Apply preprocessing
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+ if update_batchnorm and self.mode == "ViT": # VIT specific
+ self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
+
# Check label shape
y_preprocessed = self.reduce_labels(y_preprocessed)
@@ -202,11 +362,20 @@ def fit( # pylint: disable=W0221
# Shuffle the examples
random.shuffle(ind)
+ epoch_acc = []
+ epoch_loss = []
+ epoch_batch_sizes = []
+
+ pbar = tqdm(range(num_batch), disable=not verbose)
+
# Train for one epoch
- for m in range(num_batch):
+ for m in pbar:
i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])
i_batch = self.ablator.forward(i_batch)
+ if transform is not None and self.mode == "ViT": # VIT specific
+ i_batch = transform(i_batch)
+
i_batch = torch.from_numpy(i_batch).to(self._device)
o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
@@ -215,7 +384,7 @@ def fit( # pylint: disable=W0221
# Perform prediction
try:
- model_outputs = self._model(i_batch)
+ model_outputs = self.model(i_batch)
except ValueError as err:
if "Expected more than 1 value per channel when training" in str(err):
logger.exception(
@@ -224,8 +393,8 @@ def fit( # pylint: disable=W0221
)
raise err
- # Form the loss function
- loss = self._loss(model_outputs[-1], o_batch)
+ loss = self.loss(model_outputs, o_batch)
+ acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
# Do training
if self._use_amp: # pragma: no cover
@@ -237,7 +406,19 @@ def fit( # pylint: disable=W0221
else:
loss.backward()
- self._optimizer.step()
+ self.optimizer.step()
+
+ epoch_acc.append(acc)
+ epoch_loss.append(loss.cpu().detach().numpy())
+ epoch_batch_sizes.append(len(i_batch))
+
+ if verbose:
+ pbar.set_description(
+ f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
+ f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
+ )
if scheduler is not None:
scheduler.step()
+
+
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/__init__.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
new file mode 100644
index 0000000000..1675dfa2c2
--- /dev/null
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
@@ -0,0 +1,559 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""
+This module implements Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+"""
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import logging
+from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+import random
+
+import numpy as np
+from tqdm import tqdm
+
+from art.estimators.classification.pytorch import PyTorchClassifier
+from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
+from art.utils import check_and_transform_label_format
+
+if TYPE_CHECKING:
+ import torchvision
+ from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
+ from art.defences.preprocessor import Preprocessor
+ from art.defences.postprocessor import Postprocessor
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+class PyTorchSmoothedViT(PyTorchClassifier):
+ """
+ Implementation of Certified Patch Robustness via Smoothed Vision Transformers
+
+ | Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+ | Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+ """
+
+ def __init__(
+ self,
+ model: Union["VisionTransformer", str],
+ loss: "torch.nn.modules.loss._Loss",
+ input_shape: Tuple[int, ...],
+ nb_classes: int,
+ ablation_size: int,
+ replace_last_layer: bool,
+ drop_tokens: bool = True,
+ load_pretrained: bool = True,
+ optimizer: Union[type, "torch.optim.Optimizer", None] = None,
+ optimizer_params: Optional[dict] = None,
+ channels_first: bool = True,
+ clip_values: Optional["CLIP_VALUES_TYPE"] = None,
+ preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
+ postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
+ preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
+ device_type: str = "gpu",
+ verbose: bool = True,
+ ):
+ """
+ Create a smoothed ViT classifier.
+
+ :param model: Either a string specifying which ViT architecture to load, or a vision transformer already
+ created with the Pytorch Image Models (timm) library.
+ :param loss: The loss function for which to compute gradients for training. The target label must be raw
+ categorical, i.e. not converted to one-hot encoding.
+ :param input_shape: The shape of one input instance.
+ :param nb_classes: The number of classes of the model.
+ :param ablation_size: The size of the data portion to retain after ablation.
+ :param replace_last_layer: If to replace the last layer of the ViT with a fresh layer matching the number
+ of classes for the dataset to be examined. Needed if going from the pre-trained
+ imagenet models to fine-tune on a dataset like CIFAR.
+ :param drop_tokens: If to drop the fully ablated tokens in the ViT
+ :param load_pretrained: If to load a pretrained model matching the ViT name. Will only affect the ViT if a
+ string name is passed to model rather than a ViT directly.
+ :param optimizer: The optimizer used to train the classifier.
+ :param channels_first: Set channels first or last.
+ :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
+ maximum values allowed for features. If floats are provided, these will be used as the range of all
+ features. If arrays are provided, each value will be considered the bound for a feature, thus
+ the shape of clip values needs to match the total number of features.
+ :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
+ :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
+ :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
+ used for data preprocessing. The first value will be subtracted from the input. The input will then
+ be divided by the second one.
+ :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
+ """
+ import timm
+ import torch
+ from timm.models.vision_transformer import VisionTransformer
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import ArtViT
+
+ # temporarily assign the original method to tmp_func
+ tmp_func = timm.models.vision_transformer._create_vision_transformer
+
+ # overrride with ART's ViT creation function
+ timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
+ if isinstance(model, str):
+ model = timm.create_model(
+ model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type
+ )
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+ if isinstance(optimizer, type):
+ if optimizer_params is not None:
+ optimizer = optimizer(model.parameters(), **optimizer_params)
+ else:
+ raise ValueError("If providing an optimiser please also supply its parameters")
+
+ elif isinstance(model, VisionTransformer):
+ pretrained_cfg = model.pretrained_cfg
+ supplied_state_dict = model.state_dict()
+ supported_models = self.get_models()
+ if pretrained_cfg["architecture"] not in supported_models:
+ raise ValueError(
+ "Architecture not supported. Use PyTorchSmoothedViT.get_models() "
+ "to get the supported model architectures."
+ )
+ model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type)
+ model.load_state_dict(supplied_state_dict)
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+
+ if optimizer is not None:
+ if not isinstance(optimizer, torch.optim.Optimizer):
+ raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance")
+
+ converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
+ opt_state_dict = optimizer.state_dict()
+ if isinstance(optimizer, torch.optim.Adam):
+ logging.info("Converting Adam Optimiser")
+ converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
+ elif isinstance(optimizer, torch.optim.SGD):
+ logging.info("Converting SGD Optimiser")
+ converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
+ else:
+ raise ValueError("Optimiser not supported for conversion")
+ converted_optimizer.load_state_dict(opt_state_dict)
+
+ self.to_reshape = False
+ if not isinstance(model, ArtViT):
+ raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
+
+ if model.default_cfg["input_size"][0] != input_shape[0]:
+ raise ValueError(
+ f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
+ f" but {input_shape[0]} channels were provided."
+ )
+
+ if model.default_cfg["input_size"] != input_shape:
+ if verbose:
+ logger.warning(
+ f"ViT expects input shape of {model.default_cfg['input_size']}, "
+ f"but {input_shape} specified as the input shape. "
+ f"The input will be rescaled to {model.default_cfg['input_size']}"
+ )
+ self.to_reshape = True
+
+ if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ )
+ else:
+ raise ValueError("Error occurred in optimizer creation")
+
+ self.ablation_size = (ablation_size,)
+
+ if verbose:
+ logger.info(self.model)
+
+ self.ablator = ColumnAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=model.default_cfg["input_size"],
+ device_type=device_type,
+ )
+
+ # set the method back to avoid unexpected side effects later on should timm need to be reused.
+ timm.models.vision_transformer._create_vision_transformer = tmp_func
+
+ @classmethod
+ def get_models(cls, generate_from_null: bool = False) -> List[str]:
+ """
+ Return the supported model names to the user.
+
+ :param generate_from_null: If to re-check the creation of all the ViTs in timm from scratch.
+ :return: A list of compatible models
+ """
+ import timm
+ import torch
+
+ supported_models = [
+ "vit_base_patch8_224",
+ "vit_base_patch16_18x2_224",
+ "vit_base_patch16_224",
+ "vit_base_patch16_224_miil",
+ "vit_base_patch16_384",
+ "vit_base_patch16_clip_224",
+ "vit_base_patch16_clip_384",
+ "vit_base_patch16_gap_224",
+ "vit_base_patch16_plus_240",
+ "vit_base_patch16_rpn_224",
+ "vit_base_patch16_xp_224",
+ "vit_base_patch32_224",
+ "vit_base_patch32_384",
+ "vit_base_patch32_clip_224",
+ "vit_base_patch32_clip_384",
+ "vit_base_patch32_clip_448",
+ "vit_base_patch32_plus_256",
+ "vit_giant_patch14_224",
+ "vit_giant_patch14_clip_224",
+ "vit_gigantic_patch14_224",
+ "vit_gigantic_patch14_clip_224",
+ "vit_huge_patch14_224",
+ "vit_huge_patch14_clip_224",
+ "vit_huge_patch14_clip_336",
+ "vit_huge_patch14_xp_224",
+ "vit_large_patch14_224",
+ "vit_large_patch14_clip_224",
+ "vit_large_patch14_clip_336",
+ "vit_large_patch14_xp_224",
+ "vit_large_patch16_224",
+ "vit_large_patch16_384",
+ "vit_large_patch32_224",
+ "vit_large_patch32_384",
+ "vit_medium_patch16_gap_240",
+ "vit_medium_patch16_gap_256",
+ "vit_medium_patch16_gap_384",
+ "vit_small_patch16_18x2_224",
+ "vit_small_patch16_36x1_224",
+ "vit_small_patch16_224",
+ "vit_small_patch16_384",
+ "vit_small_patch32_224",
+ "vit_small_patch32_384",
+ "vit_tiny_patch16_224",
+ "vit_tiny_patch16_384",
+ ]
+
+ if not generate_from_null:
+ return supported_models
+
+ supported = []
+ unsupported = []
+
+ models = timm.list_models("vit_*")
+ for model in models:
+ logger.info(f"Testing {model} creation")
+ try:
+ _ = PyTorchSmoothedViT(
+ model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ load_pretrained=False,
+ replace_last_layer=True,
+ verbose=False,
+ )
+ supported.append(model)
+ except (TypeError, AttributeError):
+ unsupported.append(model)
+
+ if supported != supported_models:
+ logger.warning(
+ "Difference between the generated and fixed model list. Although not necessarily "
+ "an error, this may point to the timm library being updated."
+ )
+
+ return supported
+
+ @staticmethod
+ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> "ArtViT":
+ """
+ Creates a vision transformer using ArtViT which controls the forward pass of the model
+
+ :param variant: The name of the vision transformer to load
+ :param pretrained: If to load pre-trained weights
+ :return: A ViT with the required methods needed for ART
+ """
+
+ from timm.models._builder import build_model_with_cfg
+ from timm.models.vision_transformer import checkpoint_filter_fn
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import ArtViT
+
+ return build_model_with_cfg(
+ ArtViT,
+ variant,
+ pretrained,
+ pretrained_filter_fn=checkpoint_filter_fn,
+ **kwargs,
+ )
+
+ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
+ """
+ Method to update the batchnorm of a ViT on small datasets
+
+ :param x: Training data.
+ :param batch_size: Size of batches.
+ :param nb_epochs: How many times to forward pass over the input data
+ """
+ import torch
+ self.model.train()
+
+ ind = np.arange(len(x))
+ num_batch = int(len(x) / float(batch_size))
+
+ with torch.no_grad():
+ for _ in tqdm(range(nb_epochs)):
+ for m in tqdm(range(num_batch)):
+ i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(self.device)
+ i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+ _ = self.model(i_batch)
+
+ def fit_old( # pylint: disable=W0221
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ batch_size: int = 128,
+ nb_epochs: int = 10,
+ training_mode: bool = True,
+ drop_last: bool = False,
+ scheduler: Optional[Any] = None,
+ update_batchnorm: bool = True,
+ batchnorm_update_epochs: int = 1,
+ transform: Optional["torchvision.transforms.transforms.Compose"] = None,
+ verbose: bool = True,
+ **kwargs,
+ ) -> None:
+ """
+ Fit the classifier on the training set `(x, y)`.
+
+ :param x: Training data.
+ :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
+ shape (nb_samples,).
+ :param batch_size: Size of batches.
+ :param nb_epochs: Number of epochs to use for training.
+ :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
+ :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
+ the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
+ the last batch will be smaller. (default: ``False``)
+ :param scheduler: Learning rate scheduler to run at the start of every epoch.
+ :param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
+ to training. Useful on small datasets when using pre-trained ViTs.
+ :param batchnorm_update_epochs: how many times to forward pass over the training data
+ to pre-adjust the batchnorm statistics.
+ :param transform: Torchvision compose of relevant augmentation transformations to apply.
+ :param verbose: if to display training progress bars
+ :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
+ and providing it takes no effect.
+ """
+ import torch
+
+ # Set model mode
+ self._model.train(mode=training_mode)
+
+ if self._optimizer is None: # pragma: no cover
+ raise ValueError("An optimizer is needed to train the model, but none for provided.")
+
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ if update_batchnorm:
+ self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = len(x_preprocessed) / float(batch_size)
+ if drop_last:
+ num_batch = int(np.floor(num_batch))
+ else:
+ num_batch = int(np.ceil(num_batch))
+ ind = np.arange(len(x_preprocessed))
+
+ # Start training
+ for _ in tqdm(range(nb_epochs)):
+ # Shuffle the examples
+ random.shuffle(ind)
+
+ epoch_acc = []
+ epoch_loss = []
+ epoch_batch_sizes = []
+
+ pbar = tqdm(range(num_batch), disable=not verbose)
+
+ # Train for one epoch
+ for m in pbar:
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])).to(
+ self._device
+ )
+ if transform is not None:
+ i_batch = transform(i_batch)
+ i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+
+ o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
+
+ # Zero the parameter gradients
+ self._optimizer.zero_grad()
+
+ # Perform prediction
+ try:
+ model_outputs = self.model(i_batch)
+ except ValueError as err:
+ if "Expected more than 1 value per channel when training" in str(err):
+ logger.exception(
+ "Try dropping the last incomplete batch by setting drop_last=True in "
+ "method PyTorchClassifier.fit."
+ )
+ raise err
+
+ loss = self.loss(model_outputs, o_batch)
+ acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
+
+ # Do training
+ if self._use_amp: # pragma: no cover
+ from apex import amp # pylint: disable=E0611
+
+ with amp.scale_loss(loss, self._optimizer) as scaled_loss:
+ scaled_loss.backward()
+
+ else:
+ loss.backward()
+
+ self.optimizer.step()
+
+ epoch_acc.append(acc)
+ epoch_loss.append(loss.cpu().detach().numpy())
+ epoch_batch_sizes.append(len(i_batch))
+
+ if verbose:
+ pbar.set_description(
+ f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
+ f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
+ )
+
+ if scheduler is not None:
+ scheduler.step()
+
+ def eval_and_certify(
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ size_to_certify: int,
+ batch_size: int = 128,
+ verbose: bool = True,
+ ) -> Tuple["torch.Tensor", "torch.Tensor"]:
+ """
+ Evaluates the ViT's normal and certified performance over the supplied data.
+
+ :param x: Evaluation data.
+ :param y: Evaluation labels.
+ :param size_to_certify: The size of the patch to certify against.
+ If not provided will default to the ablation size.
+ :param batch_size: batch size when evaluating.
+ :param verbose: If to display the progress bar
+ :return: The accuracy and certified accuracy over the dataset
+ """
+ import torch
+
+ self.model.eval()
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size)))
+ pbar = tqdm(range(num_batch), disable=not verbose)
+ accuracy = torch.tensor(0.0).to(self._device)
+ cert_sum = torch.tensor(0.0).to(self._device)
+ n_samples = 0
+
+ with torch.no_grad():
+ for m in pbar:
+ if m == (num_batch - 1):
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size :])).to(self._device)
+ o_batch = torch.from_numpy(y_preprocessed[m * batch_size :]).to(self._device)
+ else:
+ i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])).to(
+ self._device
+ )
+ o_batch = torch.from_numpy(y_preprocessed[m * batch_size : (m + 1) * batch_size]).to(self._device)
+
+ predictions = []
+ pred_counts = torch.zeros((len(i_batch), self.nb_classes)).to(self._device)
+ for pos in range(i_batch.shape[-1]):
+ ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
+
+ # Perform prediction
+ model_outputs = self.model(ablated_batch)
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1)] += 1
+ predictions.append(model_outputs)
+
+ _, cert_and_correct, top_predicted_class = self.ablator.certify(
+ pred_counts, size_to_certify=size_to_certify, label=o_batch
+ )
+ cert_sum += torch.sum(cert_and_correct)
+ accuracy += torch.sum(top_predicted_class == o_batch)
+ n_samples += len(cert_and_correct)
+
+ pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
+
+ return (accuracy / n_samples), (cert_sum / n_samples)
+
+ @staticmethod
+ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
+ """
+ Helper function to get the accuracy during training.
+
+ :param preds: model predictions.
+ :param labels: ground truth labels (not one hot).
+ :return: prediction accuracy.
+ """
+
+ if not isinstance(preds, np.ndarray):
+ preds = preds.detach().cpu().numpy()
+
+ if not isinstance(preds, np.ndarray):
+ labels = labels.detach().cpu().numpy()
+
+ return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
new file mode 100644
index 0000000000..5bf993c707
--- /dev/null
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
@@ -0,0 +1,154 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""
+This module implements Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+"""
+
+from typing import Optional, Tuple
+import random
+
+import torch
+
+
+class UpSampler(torch.nn.Module):
+ """
+ Resizes datasets to the specified size.
+ Usually for upscaling datasets like CIFAR to Imagenet format
+ """
+
+ def __init__(self, input_size: int, final_size: int) -> None:
+ """
+ Creates an upsampler to make the supplied data match the pre-trained ViT format
+
+ :param input_size: Size of the current input data
+ :param final_size: Desired final size
+ """
+ super().__init__()
+ self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Forward pass though the upsampler.
+
+ :param x: Input data
+ :return: The upsampled input data
+ """
+ return self.upsample(x)
+
+
+class ColumnAblator(torch.nn.Module):
+ """
+ Pure Pytorch implementation of stripe/column ablation.
+ """
+
+ def __init__(
+ self,
+ ablation_size: int,
+ channels_first: bool,
+ to_reshape: bool = False,
+ original_shape: Optional[Tuple] = None,
+ output_shape: Optional[Tuple] = None,
+ device_type: str = "gpu",
+ ):
+ """
+ Creates a column ablator
+
+ :param ablation_size: The size of the column we will retain.
+ :param channels_first: If the input is in channels first format. Currently required to be True.
+ :param to_reshape: If the input requires reshaping.
+ :param original_shape: Original shape of the input.
+ :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
+ """
+ super().__init__()
+ self.ablation_size = ablation_size
+ self.channels_first = channels_first
+ self.to_reshape = to_reshape
+
+ if device_type == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
+ if original_shape is not None and output_shape is not None:
+ self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
+
+ def ablate(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Tensor:
+ """
+ Ablates the input colum wise
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The ablated input with 0s where the ablation occurred
+ """
+ k = self.ablation_size
+ if column_pos is None:
+ column_pos = random.randint(0, x.shape[3])
+
+ if column_pos + k > x.shape[-1]:
+ x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
+ else:
+ x[:, :, :, :column_pos] = 0.0
+ x[:, :, :, column_pos + k :] = 0.0
+ return x
+
+ def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
+ """
+ Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The albated input with an extra channel indicating the location of the ablation
+ """
+ assert x.shape[1] == 3
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
+ x = torch.cat([x, ones], dim=1)
+ x = self.ablate(x, column_pos=column_pos)
+ if self.to_reshape:
+ x = self.upsample(x)
+ return x
+
+ def certify(
+ self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ Performs certification of the predictions
+
+ :param pred_counts: The model predictions over the ablated data.
+ :param size_to_certify: The patch size we wish to check certification against
+ :param label: The ground truth labels
+ :return: A tuple consisting of: the certified predictions,
+ the predictions which were certified and also correct,
+ and the most predicted class across the different ablations on the input.
+ """
+
+ num_of_classes = pred_counts.shape[-1]
+
+ top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
+ second_class_counts, _ = pred_counts.kthvalue(num_of_classes - 1, dim=1)
+
+ cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
+
+ cert_and_correct = cert & (label == top_predicted_class)
+
+ return cert, cert_and_correct, top_predicted_class
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
new file mode 100644
index 0000000000..15069dd76c
--- /dev/null
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
@@ -0,0 +1,170 @@
+import torch
+from timm.models.vision_transformer import VisionTransformer
+from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+
+
+class PatchEmbed(torch.nn.Module):
+ """
+ Image to Patch Embedding
+
+ Class adapted from the implementation in https://github.com/MadryLab/smoothed-vit
+
+ Original License:
+
+ MIT License
+
+ Copyright (c) 2021 Madry Lab
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
+ """
+
+ def __init__(self, patch_size: int = 16, in_channels: int = 1, embed_dim: int = 768):
+ """
+ Specifies the configuration for the convolutional layer.
+
+ :param patch_size: The patch size used by the ViT.
+ :param in_channels: Number of input channels.
+ :param embed_dim: The embedding dimension used by the ViT.
+ """
+ super().__init__()
+ self.patch_size = patch_size
+ self.in_channels = in_channels
+ self.embed_dim = embed_dim
+ self.proj: Optional[torch.nn.Conv2d] = None
+
+ def create(self, patch_size=None, embed_dim=None, device="cpu", **kwargs) -> None: # pylint: disable=W0613
+ """
+ Creates a convolution that mimics the embedding layer to be used for the ablation mask to
+ track where the image was ablated.
+
+ :param patch_size: The patch size used by the ViT
+ :param embed_dim: The embedding dimension used by the ViT
+ :param device: Which device to set the emdedding layer to.
+ :param kwargs: Handles the remaining kwargs from the ViT configuration.
+ """
+
+ if patch_size is not None:
+ self.patch_size = patch_size
+ if embed_dim is not None:
+ self.embed_dim = embed_dim
+
+ self.proj = torch.nn.Conv2d(
+ in_channels=self.in_channels,
+ out_channels=self.embed_dim,
+ kernel_size=self.patch_size,
+ stride=self.patch_size,
+ bias=False,
+ )
+ w_shape = self.proj.weight.shape
+ self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Forward pass through the embedder. We are simply tracking the positions of the ablation mask so no gradients
+ are required.
+
+ :param x: Input data corresponding to the ablation mask
+ :return: The embedded input
+ """
+ if self.proj is not None:
+ with torch.no_grad():
+ x = self.proj(x).flatten(2).transpose(1, 2)
+ return x
+ raise ValueError("Projection layer not yet created.")
+
+
+class ArtViT(VisionTransformer):
+ """
+ Art class inheriting from VisionTransformer to control the forward pass of the ViT.
+ """
+
+ # Make as a class attribute to avoid being included in the
+ # state dictionaries of the ViT Model.
+ ablation_mask_embedder = PatchEmbed(in_channels=1)
+
+ def __init__(self, **kwargs):
+ """
+ Create a ArtViT instance
+ :param kwargs: keyword arguments required to create the mask embedder and the vision transformer class
+ Must contain ...
+ """
+ self.to_drop_tokens = kwargs["drop_tokens"]
+
+ if kwargs["device_type"] == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
+ del kwargs["drop_tokens"]
+ del kwargs["device_type"]
+
+ super().__init__(**kwargs)
+ self.ablation_mask_embedder.create(device=self.device, **kwargs)
+
+ self.in_chans = kwargs["in_chans"]
+ self.img_size = kwargs["img_size"]
+
+ @staticmethod
+ def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor:
+ """
+ Drops the tokens which correspond to fully masked inputs
+
+ :param x: Input data
+ :param indexes: positions to be ablated
+ :return: Input with tokens dropped where the input was fully ablated.
+ """
+ x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
+ shape = x_no_cl.shape
+
+ # reshape to temporarily remove batch
+ x_no_cl = torch.reshape(x_no_cl, shape=(-1, shape[-1]))
+ indexes = torch.reshape(indexes, shape=(-1,))
+ indexes = indexes.nonzero(as_tuple=True)[0]
+ x_no_cl = torch.index_select(x_no_cl, dim=0, index=indexes)
+ x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1]))
+ return torch.cat((cls_token, x_no_cl), dim=1)
+
+ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ The forward pass of the ViT.
+
+ :param x: Input data.
+ :return: The input processed by the ViT backbone
+ """
+
+ ablated_input = False
+ if x.shape[1] == self.in_chans + 1:
+ ablated_input = True
+
+ if ablated_input:
+ x, ablation_mask = x[:, : self.in_chans], x[:, self.in_chans : self.in_chans + 1]
+
+ x = self.patch_embed(x)
+ x = self._pos_embed(x)
+
+ if self.to_drop_tokens and ablated_input:
+ ones = self.ablation_mask_embedder(ablation_mask)
+ to_drop = torch.sum(ones, dim=2)
+ indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
+ x = self.drop_tokens(x, indexes)
+
+ x = self.norm_pre(x)
+ x = self.blocks(x)
+ return self.norm(x)
diff --git a/art/estimators/certification/smoothed_vision_transformers/__init__.py b/art/estimators/certification/smoothed_vision_transformers_old/__init__.py
similarity index 100%
rename from art/estimators/certification/smoothed_vision_transformers/__init__.py
rename to art/estimators/certification/smoothed_vision_transformers_old/__init__.py
diff --git a/art/estimators/certification/smoothed_vision_transformers/pytorch.py b/art/estimators/certification/smoothed_vision_transformers_old/pytorch.py
similarity index 100%
rename from art/estimators/certification/smoothed_vision_transformers/pytorch.py
rename to art/estimators/certification/smoothed_vision_transformers_old/pytorch.py
diff --git a/art/estimators/certification/smoothed_vision_transformers/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py
similarity index 100%
rename from art/estimators/certification/smoothed_vision_transformers/smooth_vit.py
rename to art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py
diff --git a/dev.py b/dev.py
new file mode 100644
index 0000000000..afe1631813
--- /dev/null
+++ b/dev.py
@@ -0,0 +1,56 @@
+import torch
+import ssl
+ssl._create_default_https_context = ssl._create_unverified_context
+from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
+import numpy as np
+from torchvision import datasets
+from torchvision import transforms
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
+
+def get_cifar_data():
+ """
+ Get CIFAR-10 data.
+ :return: cifar train/test data.
+ """
+ train_set = datasets.CIFAR10('./data', train=True, download=True)
+ test_set = datasets.CIFAR10('./data', train=False, download=True)
+
+ x_train = train_set.data.astype(np.float32)
+ y_train = np.asarray(train_set.targets)
+
+ x_test = test_set.data.astype(np.float32)
+ y_test = np.asarray(test_set.targets)
+
+ x_train = np.moveaxis(x_train, [3], [1])
+ x_test = np.moveaxis(x_test, [3], [1])
+
+ x_train = x_train / 255.0
+ x_test = x_test / 255.0
+
+ return (x_train, y_train), (x_test, y_test)
+
+
+(x_train, y_train), (x_test, y_test) = get_cifar_data()
+
+art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224',
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ replace_last_layer=True,
+ load_pretrained=True,)
+
+scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
+art_model.fit(x_train, y_train,
+ nb_epochs=30,
+ update_batchnorm=True,
+ scheduler=scheduler,
+ transform=transforms.Compose([transforms.RandomHorizontalFlip()]))
+
+# torch.save(art_model.model.state_dict(), 'trained.pt')
+# art_model.model.load_state_dict(torch.load('trained.pt'))
+art_model.eval_and_certify(x_test, y_test, size_to_certify=4)
From 2c5e4c50605239b89025a300f8818fc1624175af Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Tue, 27 Jun 2023 09:19:49 +0100
Subject: [PATCH 23/55] make colum pos optional in vit ablator, updating tests
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing/pytorch.py | 175 ++++-------------
.../vision_transformers/pytorch.py | 178 ++----------------
.../vision_transformers/smooth_vit.py | 17 +-
.../vision_transformers/vit.py | 25 ++-
.../certification/test_smooth_vit.py | 17 +-
5 files changed, 104 insertions(+), 308 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 422ca98d74..6946b1d416 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -39,7 +39,8 @@
if TYPE_CHECKING:
# pylint: disable=C0412
import torch
-
+ import torchvision
+ from timm.models.vision_transformer import VisionTransformer
from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
from art.defences.preprocessor import Preprocessor
from art.defences.postprocessor import Postprocessor
@@ -149,149 +150,39 @@ def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epoc
x = x.astype(ART_NUMPY_DTYPE)
return PyTorchClassifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
- def fit_old( # pylint: disable=W0221
- self,
- x: np.ndarray,
- y: np.ndarray,
- batch_size: int = 128,
- nb_epochs: int = 10,
- training_mode: bool = True,
- drop_last: bool = False,
- scheduler: Optional[Any] = None,
- update_batchnorm: bool = True,
- batchnorm_update_epochs: int = 1,
- transform: Optional["torchvision.transforms.transforms.Compose"] = None,
- verbose: bool = True,
- **kwargs,
- ) -> None:
- """
- Fit the classifier on the training set `(x, y)`.
-
- :param x: Training data.
- :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
- shape (nb_samples,).
- :param batch_size: Size of batches.
- :param nb_epochs: Number of epochs to use for training.
- :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
- :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
- the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
- the last batch will be smaller. (default: ``False``)
- :param scheduler: Learning rate scheduler to run at the start of every epoch.
- :param update_batchnorm: ViT Specific Arg. If to run the training data through the model to update any batch norm statistics prior
- to training. Useful on small datasets when using pre-trained ViTs.
- :param batchnorm_update_epochs: ViT Specific Arg. How many times to forward pass over the training data
- to pre-adjust the batchnorm statistics.
- :param transform: ViT Specific Arg. Torchvision compose of relevant augmentation transformations to apply.
- :param verbose: if to display training progress bars
- :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
- and providing it takes no effect.
- """
- import torch
-
- # Check if we have a VIT
-
- # Set model mode
- self._model.train(mode=training_mode)
-
- if self._optimizer is None: # pragma: no cover
- raise ValueError("An optimizer is needed to train the model, but none for provided.")
-
- y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
-
- # Apply preprocessing
- x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
-
- if update_batchnorm: # VIT specific
- self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
-
- # Check label shape
- y_preprocessed = self.reduce_labels(y_preprocessed)
-
- num_batch = len(x_preprocessed) / float(batch_size)
- if drop_last:
- num_batch = int(np.floor(num_batch))
- else:
- num_batch = int(np.ceil(num_batch))
- ind = np.arange(len(x_preprocessed))
-
- # Start training
- for _ in tqdm(range(nb_epochs)):
- # Shuffle the examples
- random.shuffle(ind)
-
- epoch_acc = []
- epoch_loss = []
- epoch_batch_sizes = []
-
- pbar = tqdm(range(num_batch), disable=not verbose)
-
- # Train for one epoch
- for m in pbar:
- i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])
- i_batch = self.ablator.forward(i_batch)
-
- if transform is not None: # VIT specific
- i_batch = transform(i_batch)
-
- i_batch = torch.from_numpy(i_batch).to(self._device)
- o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
-
- # Zero the parameter gradients
- self._optimizer.zero_grad()
-
- # Perform prediction
- try:
- model_outputs = self.model(i_batch)
- except ValueError as err:
- if "Expected more than 1 value per channel when training" in str(err):
- logger.exception(
- "Try dropping the last incomplete batch by setting drop_last=True in "
- "method PyTorchClassifier.fit."
- )
- raise err
-
- loss = self.loss(model_outputs, o_batch)
- acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
-
- # Do training
- if self._use_amp: # pragma: no cover
- from apex import amp # pylint: disable=E0611
-
- with amp.scale_loss(loss, self._optimizer) as scaled_loss:
- scaled_loss.backward()
- else:
- loss.backward()
-
- self.optimizer.step()
-
- epoch_acc.append(acc)
- epoch_loss.append(loss.cpu().detach().numpy())
- epoch_batch_sizes.append(len(i_batch))
-
- if verbose:
- pbar.set_description(
- f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
- f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
- )
+class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT):
+ """
+ Interface class for the two De-randomized smoothing approaches supported by ART for pytorch.
- if scheduler is not None:
- scheduler.step()
+ If a regular pytorch neural network is fed in then (De)Randomized Smoothing as introduced in Levine et al. (2020)
+ is used.
+ Otherwise, if a timm vision transfomer is fed in then Certified Patch Robustness via Smoothed Vision Transformers
+ as introduced in Salman et al. (2021) is used.
+ """
-class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT):
def __init__(self, model: Union[str, "VisionTransformer", "torch.nn.Module"], **kwargs):
import torch
- if isinstance(model, torch.nn.Module):
- PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
- self.mode = "CNN"
+ self.mode = None
if importlib.util.find_spec("timm") is not None:
from timm.models.vision_transformer import VisionTransformer
- if isinstance(model, VisionTransformer) or isinstance(model, str):
+ if isinstance(model, (VisionTransformer, str)):
PyTorchSmoothedViT.__init__(self, model, **kwargs)
self.mode = "ViT"
+ else:
+ if isinstance(model, torch.nn.Module):
+ PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
+ self.mode = "CNN"
+
+ elif isinstance(model, torch.nn.Module):
+ PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
+ self.mode = "CNN"
+
+ if self.mode is None:
+ raise ValueError("Model type not recognized.")
def fit( # pylint: disable=W0221
self,
@@ -373,10 +264,11 @@ def fit( # pylint: disable=W0221
i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])
i_batch = self.ablator.forward(i_batch)
- if transform is not None and self.mode == "ViT": # VIT specific
+ if transform is not None and self.mode == "ViT": # VIT specific
i_batch = transform(i_batch)
- i_batch = torch.from_numpy(i_batch).to(self._device)
+ if isinstance(i_batch, np.ndarray):
+ i_batch = torch.from_numpy(i_batch).to(self._device)
o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
# Zero the parameter gradients
@@ -421,4 +313,19 @@ def fit( # pylint: disable=W0221
if scheduler is not None:
scheduler.step()
+ @staticmethod
+ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
+ """
+ Helper function to get the accuracy during training.
+
+ :param preds: model predictions.
+ :param labels: ground truth labels (not one hot).
+ :return: prediction accuracy.
+ """
+ if not isinstance(preds, np.ndarray):
+ preds = preds.detach().cpu().numpy()
+
+ if not isinstance(labels, np.ndarray):
+ labels = labels.detach().cpu().numpy()
+ return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
index 1675dfa2c2..798ad53405 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
@@ -26,7 +26,7 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
-from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
+from typing import List, Optional, Tuple, Union, TYPE_CHECKING
import random
import numpy as np
@@ -37,7 +37,9 @@
from art.utils import check_and_transform_label_format
if TYPE_CHECKING:
- import torchvision
+ import torch
+ from timm.models.vision_transformer import VisionTransformer
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
from art.defences.preprocessor import Preprocessor
from art.defences.postprocessor import Postprocessor
@@ -108,7 +110,7 @@ def __init__(
import timm
import torch
from timm.models.vision_transformer import VisionTransformer
- from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import ArtViT
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
# temporarily assign the original method to tmp_func
tmp_func = timm.models.vision_transformer._create_vision_transformer
@@ -158,8 +160,8 @@ def __init__(
converted_optimizer.load_state_dict(opt_state_dict)
self.to_reshape = False
- if not isinstance(model, ArtViT):
- raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
+ if not isinstance(model, PyTorchViT):
+ raise ValueError("Vision transformer is not of PyTorchViT. Error occurred in PyTorchViT creation.")
if model.default_cfg["input_size"][0] != input_shape[0]:
raise ValueError(
@@ -170,10 +172,12 @@ def __init__(
if model.default_cfg["input_size"] != input_shape:
if verbose:
logger.warning(
- f"ViT expects input shape of {model.default_cfg['input_size']}, "
- f"but {input_shape} specified as the input shape. "
- f"The input will be rescaled to {model.default_cfg['input_size']}"
+ " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape. The input will be rescaled to (%i, %i, %i)",
+ *model.default_cfg["input_size"],
+ *input_shape,
+ *model.default_cfg["input_size"],
)
+
self.to_reshape = True
if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
@@ -276,7 +280,7 @@ def get_models(cls, generate_from_null: bool = False) -> List[str]:
models = timm.list_models("vit_*")
for model in models:
- logger.info(f"Testing {model} creation")
+ logger.info("Testing %s creation", model)
try:
_ = PyTorchSmoothedViT(
model=model,
@@ -303,9 +307,9 @@ def get_models(cls, generate_from_null: bool = False) -> List[str]:
return supported
@staticmethod
- def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> "ArtViT":
+ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> "PyTorchViT":
"""
- Creates a vision transformer using ArtViT which controls the forward pass of the model
+ Creates a vision transformer using PyTorchViT which controls the forward pass of the model
:param variant: The name of the vision transformer to load
:param pretrained: If to load pre-trained weights
@@ -314,10 +318,10 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
from timm.models._builder import build_model_with_cfg
from timm.models.vision_transformer import checkpoint_filter_fn
- from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import ArtViT
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
return build_model_with_cfg(
- ArtViT,
+ PyTorchViT,
variant,
pretrained,
pretrained_filter_fn=checkpoint_filter_fn,
@@ -326,13 +330,14 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
"""
- Method to update the batchnorm of a ViT on small datasets
+ Method to update the batchnorm of a neural network on small datasets when it was pre-trained
:param x: Training data.
:param batch_size: Size of batches.
:param nb_epochs: How many times to forward pass over the input data
"""
import torch
+
self.model.train()
ind = np.arange(len(x))
@@ -345,133 +350,6 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
_ = self.model(i_batch)
- def fit_old( # pylint: disable=W0221
- self,
- x: np.ndarray,
- y: np.ndarray,
- batch_size: int = 128,
- nb_epochs: int = 10,
- training_mode: bool = True,
- drop_last: bool = False,
- scheduler: Optional[Any] = None,
- update_batchnorm: bool = True,
- batchnorm_update_epochs: int = 1,
- transform: Optional["torchvision.transforms.transforms.Compose"] = None,
- verbose: bool = True,
- **kwargs,
- ) -> None:
- """
- Fit the classifier on the training set `(x, y)`.
-
- :param x: Training data.
- :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
- shape (nb_samples,).
- :param batch_size: Size of batches.
- :param nb_epochs: Number of epochs to use for training.
- :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
- :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
- the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
- the last batch will be smaller. (default: ``False``)
- :param scheduler: Learning rate scheduler to run at the start of every epoch.
- :param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
- to training. Useful on small datasets when using pre-trained ViTs.
- :param batchnorm_update_epochs: how many times to forward pass over the training data
- to pre-adjust the batchnorm statistics.
- :param transform: Torchvision compose of relevant augmentation transformations to apply.
- :param verbose: if to display training progress bars
- :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
- and providing it takes no effect.
- """
- import torch
-
- # Set model mode
- self._model.train(mode=training_mode)
-
- if self._optimizer is None: # pragma: no cover
- raise ValueError("An optimizer is needed to train the model, but none for provided.")
-
- y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
-
- # Apply preprocessing
- x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
-
- if update_batchnorm:
- self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
-
- # Check label shape
- y_preprocessed = self.reduce_labels(y_preprocessed)
-
- num_batch = len(x_preprocessed) / float(batch_size)
- if drop_last:
- num_batch = int(np.floor(num_batch))
- else:
- num_batch = int(np.ceil(num_batch))
- ind = np.arange(len(x_preprocessed))
-
- # Start training
- for _ in tqdm(range(nb_epochs)):
- # Shuffle the examples
- random.shuffle(ind)
-
- epoch_acc = []
- epoch_loss = []
- epoch_batch_sizes = []
-
- pbar = tqdm(range(num_batch), disable=not verbose)
-
- # Train for one epoch
- for m in pbar:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])).to(
- self._device
- )
- if transform is not None:
- i_batch = transform(i_batch)
- i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
-
- o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
-
- # Zero the parameter gradients
- self._optimizer.zero_grad()
-
- # Perform prediction
- try:
- model_outputs = self.model(i_batch)
- except ValueError as err:
- if "Expected more than 1 value per channel when training" in str(err):
- logger.exception(
- "Try dropping the last incomplete batch by setting drop_last=True in "
- "method PyTorchClassifier.fit."
- )
- raise err
-
- loss = self.loss(model_outputs, o_batch)
- acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
-
- # Do training
- if self._use_amp: # pragma: no cover
- from apex import amp # pylint: disable=E0611
-
- with amp.scale_loss(loss, self._optimizer) as scaled_loss:
- scaled_loss.backward()
-
- else:
- loss.backward()
-
- self.optimizer.step()
-
- epoch_acc.append(acc)
- epoch_loss.append(loss.cpu().detach().numpy())
- epoch_batch_sizes.append(len(i_batch))
-
- if verbose:
- pbar.set_description(
- f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
- f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
- )
-
- if scheduler is not None:
- scheduler.step()
-
def eval_and_certify(
self,
x: np.ndarray,
@@ -539,21 +417,3 @@ def eval_and_certify(
pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
return (accuracy / n_samples), (cert_sum / n_samples)
-
- @staticmethod
- def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
- """
- Helper function to get the accuracy during training.
-
- :param preds: model predictions.
- :param labels: ground truth labels (not one hot).
- :return: prediction accuracy.
- """
-
- if not isinstance(preds, np.ndarray):
- preds = preds.detach().cpu().numpy()
-
- if not isinstance(preds, np.ndarray):
- labels = labels.detach().cpu().numpy()
-
- return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
index 5bf993c707..fb8c1795f7 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
@@ -27,6 +27,7 @@
from typing import Optional, Tuple
import random
+import numpy as np
import torch
@@ -39,7 +40,7 @@ class UpSampler(torch.nn.Module):
def __init__(self, input_size: int, final_size: int) -> None:
"""
Creates an upsampler to make the supplied data match the pre-trained ViT format
-
+
:param input_size: Size of the current input data
:param final_size: Desired final size
"""
@@ -93,7 +94,7 @@ def __init__(
if original_shape is not None and output_shape is not None:
self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
- def ablate(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Tensor:
+ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
"""
Ablates the input colum wise
@@ -102,9 +103,6 @@ def ablate(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Ten
:return: The ablated input with 0s where the ablation occurred
"""
k = self.ablation_size
- if column_pos is None:
- column_pos = random.randint(0, x.shape[3])
-
if column_pos + k > x.shape[-1]:
x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
else:
@@ -112,7 +110,7 @@ def ablate(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Ten
x[:, :, :, column_pos + k :] = 0.0
return x
- def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
+ def forward(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Tensor:
"""
Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
@@ -121,6 +119,13 @@ def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
:return: The albated input with an extra channel indicating the location of the ablation
"""
assert x.shape[1] == 3
+
+ if column_pos is None:
+ column_pos = random.randint(0, x.shape[3])
+
+ if isinstance(x, np.ndarray):
+ x = torch.from_numpy(x).to(self.device)
+
ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
x = torch.cat([x, ones], dim=1)
x = self.ablate(x, column_pos=column_pos)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
index 15069dd76c..49168e38fa 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
@@ -1,6 +1,27 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""
+Implements functionality for running Vision Transformers in ART
+"""
+from typing import Optional
+
import torch
from timm.models.vision_transformer import VisionTransformer
-from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
class PatchEmbed(torch.nn.Module):
@@ -89,7 +110,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
raise ValueError("Projection layer not yet created.")
-class ArtViT(VisionTransformer):
+class PyTorchViT(VisionTransformer):
"""
Art class inheriting from VisionTransformer to control the forward pass of the ViT.
"""
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index 1d8a13c8ae..f1c9566d73 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -20,8 +20,6 @@
import numpy as np
from art.utils import load_dataset
-from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT
-from art.estimators.certification.smoothed_vision_transformers.pytorch import ArtViT
from tests.utils import ARTTestException
@@ -68,7 +66,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
try:
cifar_data = fix_get_cifar10_data[0]
@@ -132,12 +130,13 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
Check that the training loop for pytorch does not result in errors
"""
import torch
+ from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
try:
cifar_data = fix_get_cifar10_data[0][:50]
cifar_labels = fix_get_cifar10_data[1][:50]
- art_model = PyTorchSmoothedViT(
+ art_model = PyTorchDeRandomizedSmoothing(
model="vit_small_patch16_224",
loss=torch.nn.CrossEntropyLoss(),
optimizer=torch.optim.SGD,
@@ -147,6 +146,7 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
ablation_size=4,
load_pretrained=True,
replace_last_layer=True,
+ verbose=False,
)
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[1], gamma=0.1)
@@ -161,7 +161,7 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
"""
Check that ...
"""
- from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
import torch
try:
@@ -187,6 +187,8 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
def test_equivalence(fix_get_cifar10_data):
import torch
+ from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
class MadrylabImplementations:
"""
@@ -309,9 +311,9 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
return self.norm(x)
# Replace the forward_features with the forward_features code with checks.
- ArtViT.forward_features = forward_features
+ PyTorchViT.forward_features = forward_features
- art_model = PyTorchSmoothedViT(
+ art_model = PyTorchDeRandomizedSmoothing(
model="vit_small_patch16_224",
loss=torch.nn.CrossEntropyLoss(),
optimizer=torch.optim.SGD,
@@ -321,6 +323,7 @@ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
ablation_size=4,
load_pretrained=False,
replace_last_layer=True,
+ verbose=False,
)
cifar_data = fix_get_cifar10_data[0][:50]
From 784b7e9c484b33f96f9236760f2e29542ea2206f Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Tue, 27 Jun 2023 20:16:14 +0100
Subject: [PATCH 24/55] init refactor
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing.py | 9 +-
.../derandomized_smoothing/pytorch.py | 372 +++++++++++++++---
.../vision_transformers/pytorch.py | 262 +-----------
.../vision_transformers/smooth_vit.py | 11 +-
dev.py | 105 +++--
5 files changed, 418 insertions(+), 341 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py
index 42a31ca418..387d300130 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing.py
@@ -159,13 +159,14 @@ def __call__(
raise NotImplementedError
@abstractmethod
- def certify(self, preds: np.ndarray, size_to_certify: int):
+ def certify(self, preds: np.ndarray, size_to_certify: int, label: Optional[np.ndarray] = None):
"""
Checks if based on the predictions supplied the classifications over the ablated datapoints result in a
certified prediction against a patch attack of size size_to_certify.
:param preds: The cumulative predictions of the classifier over the ablation locations.
:param size_to_certify: The size of the patch to check against.
+ :param label: ground truth labels
"""
raise NotImplementedError
@@ -230,13 +231,14 @@ def __call__(
"""
return self.forward(x=x, column_pos=column_pos)
- def certify(self, preds: np.ndarray, size_to_certify: int) -> np.ndarray:
+ def certify(self, preds: np.ndarray, size_to_certify: int, label: Optional[np.ndarray] = None) -> np.ndarray:
"""
Checks if based on the predictions supplied the classifications over the ablated datapoints result in a
certified prediction against a patch attack of size size_to_certify.
:param preds: The cumulative predictions of the classifier over the ablation locations.
:param size_to_certify: The size of the patch to check against.
+ :param label: Ground truth labels
:return: Array of bools indicating if a point is certified against the given patch dimensions.
"""
indices = np.argsort(-preds, axis=1, kind="stable")
@@ -348,13 +350,14 @@ def __call__(
"""
return self.forward(x=x, row_pos=row_pos, column_pos=column_pos)
- def certify(self, preds: np.ndarray, size_to_certify: int) -> np.ndarray:
+ def certify(self, preds: np.ndarray, size_to_certify: int, label: Optional[np.ndarray] = None) -> np.ndarray:
"""
Checks if based on the predictions supplied the classifications over the ablated datapoints result in a
certified prediction against a patch attack of size size_to_certify.
:param preds: The cumulative predictions of the classifier over the ablation locations.
:param size_to_certify: The size of the patch to check against.
+ :param label: Ground truth labels
:return: Array of bools indicating if a point is certified against the given patch dimensions.
"""
indices = np.argsort(-preds, axis=1, kind="stable")
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 6946b1d416..f6064b2c01 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -16,9 +16,20 @@
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
-This module implements (De)Randomized Smoothing for Certifiable Defense against Patch Attacks
+This module implements the two De-randomized smoothing approaches supported by ART for pytorch.
+
+(De)Randomized Smoothing for Certifiable Defense against Patch Attacks
| Paper link: https://arxiv.org/abs/2002.10733
+
+and
+
+Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
from __future__ import absolute_import, division, print_function, unicode_literals
@@ -48,7 +59,7 @@
logger = logging.getLogger(__name__)
-class PyTorchDeRandomizedSmoothingCNN(DeRandomizedSmoothingMixin, PyTorchClassifier):
+class PyTorchDeRandomizedSmoothingCNN(DeRandomizedSmoothingMixin):
"""
Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced
in Levine et al. (2020).
@@ -56,26 +67,7 @@ class PyTorchDeRandomizedSmoothingCNN(DeRandomizedSmoothingMixin, PyTorchClassif
| Paper link: https://arxiv.org/abs/2002.10733
"""
- estimator_params = PyTorchClassifier.estimator_params + ["ablation_type", "ablation_size", "threshold", "logits"]
-
- def __init__(
- self,
- model: "torch.nn.Module",
- loss: "torch.nn.modules.loss._Loss",
- input_shape: Tuple[int, ...],
- nb_classes: int,
- ablation_type: str,
- ablation_size: int,
- threshold: float,
- logits: bool,
- optimizer: Optional["torch.optim.Optimizer"] = None, # type: ignore
- channels_first: bool = True,
- clip_values: Optional["CLIP_VALUES_TYPE"] = None,
- preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
- postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
- preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
- device_type: str = "gpu",
- ):
+ def __init__(self, **kwargs):
"""
Create a derandomized smoothing classifier.
@@ -103,23 +95,7 @@ def __init__(
be divided by the second one.
:param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
"""
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- ablation_type=ablation_type,
- ablation_size=ablation_size,
- threshold=threshold,
- logits=logits,
- )
+ super().__init__(**kwargs)
def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray:
import torch
@@ -146,12 +122,8 @@ def predict(
"""
return DeRandomizedSmoothingMixin.predict(self, x, batch_size=batch_size, training_mode=training_mode, **kwargs)
- def _fit_classifier(self, x: np.ndarray, y: np.ndarray, batch_size: int, nb_epochs: int, **kwargs) -> None:
- x = x.astype(ART_NUMPY_DTYPE)
- return PyTorchClassifier.fit(self, x, y, batch_size=batch_size, nb_epochs=nb_epochs, **kwargs)
-
-class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT):
+class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT, PyTorchClassifier):
"""
Interface class for the two De-randomized smoothing approaches supported by ART for pytorch.
@@ -162,24 +134,217 @@ class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoot
as introduced in Salman et al. (2021) is used.
"""
- def __init__(self, model: Union[str, "VisionTransformer", "torch.nn.Module"], **kwargs):
+ def __init__(
+ self,
+ model: Union[str, "VisionTransformer", "torch.nn.Module"],
+ loss: "torch.nn.modules.loss._Loss",
+ input_shape: Tuple[int, ...],
+ nb_classes: int,
+ ablation_size: int,
+ replace_last_layer: Optional[bool] = None,
+ drop_tokens: bool = True,
+ load_pretrained: bool = True,
+ optimizer: Union[type, "torch.optim.Optimizer", None] = None,
+ optimizer_params: Optional[dict] = None,
+ channels_first: bool = True,
+ ablation_type: Optional[str] = None,
+ threshold: Optional[float] = None,
+ logits: Optional[bool] = True,
+ clip_values: Optional["CLIP_VALUES_TYPE"] = None,
+ preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
+ postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
+ preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
+ device_type: str = "gpu",
+ verbose: bool = True,
+ **kwargs,
+ ):
+ """
+ Create a smoothed classifier.
+
+ :param model: To run Salman et al. (2021):
+ Either a string specifying which ViT architecture to load, or a vision transformer already
+ created with the Pytorch Image Models (timm) library.
+ To run Levine et al. (2020) provide a regular pytorch model
+ :param loss: The loss function for which to compute gradients for training. The target label must be raw
+ categorical, i.e. not converted to one-hot encoding.
+ :param input_shape: The shape of one input instance.
+ :param nb_classes: The number of classes of the model.
+ :param ablation_size: The size of the data portion to retain after ablation.
+ :param replace_last_layer: ViT Specific. If to replace the last layer of the ViT with a fresh layer
+ matching the number of classes for the dataset to be examined.
+ Needed if going from the pre-trained imagenet models to fine-tune
+ on a dataset like CIFAR.
+ :param drop_tokens: ViT Specific. If to drop the fully ablated tokens in the ViT
+ :param load_pretrained: ViT Specific. If to load a pretrained model matching the ViT name.
+ Will only affect the ViT if a string name is passed to model rather than a ViT directly.
+ :param optimizer: The optimizer used to train the classifier.
+ :param ablation_type: Specific to Levine et al. The type of ablation to perform,
+ must be either "column" or "block"
+ :param threshold: Specific to Levine et al. The minimum threshold to count a prediction.
+ :param logits: Specific to Levine et al. If the model returns logits or normalized probabilities
+ :param channels_first: Set channels first or last.
+ :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
+ maximum values allowed for features. If floats are provided, these will be used as the range of all
+ features. If arrays are provided, each value will be considered the bound for a feature, thus
+ the shape of clip values needs to match the total number of features.
+ :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
+ :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
+ :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
+ used for data preprocessing. The first value will be subtracted from the input. The input will then
+ be divided by the second one.
+ :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
+ """
+
import torch
self.mode = None
if importlib.util.find_spec("timm") is not None:
from timm.models.vision_transformer import VisionTransformer
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
if isinstance(model, (VisionTransformer, str)):
- PyTorchSmoothedViT.__init__(self, model, **kwargs)
+ import timm
+ from timm.models.vision_transformer import VisionTransformer
+ from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
+
+ if replace_last_layer is None:
+ raise ValueError("If using ViTs please specify if the last layer should be replaced")
+
+ # temporarily assign the original method to tmp_func
+ tmp_func = timm.models.vision_transformer._create_vision_transformer
+
+ # overrride with ART's ViT creation function
+ timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
+ if isinstance(model, str):
+ model = timm.create_model(
+ model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type
+ )
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+ if isinstance(optimizer, type):
+ if optimizer_params is not None:
+ optimizer = optimizer(model.parameters(), **optimizer_params)
+ else:
+ raise ValueError("If providing an optimiser please also supply its parameters")
+
+ elif isinstance(model, VisionTransformer):
+ pretrained_cfg = model.pretrained_cfg
+ supplied_state_dict = model.state_dict()
+ supported_models = self.get_models()
+ if pretrained_cfg["architecture"] not in supported_models:
+ raise ValueError(
+ "Architecture not supported. Use PyTorchSmoothedViT.get_models() "
+ "to get the supported model architectures."
+ )
+ model = timm.create_model(
+ pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type
+ )
+ model.load_state_dict(supplied_state_dict)
+ if replace_last_layer:
+ model.head = torch.nn.Linear(model.head.in_features, nb_classes)
+
+ if optimizer is not None:
+ if not isinstance(optimizer, torch.optim.Optimizer):
+ raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance")
+
+ converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
+ opt_state_dict = optimizer.state_dict()
+ if isinstance(optimizer, torch.optim.Adam):
+ logging.info("Converting Adam Optimiser")
+ converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
+ elif isinstance(optimizer, torch.optim.SGD):
+ logging.info("Converting SGD Optimiser")
+ converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
+ else:
+ raise ValueError("Optimiser not supported for conversion")
+ converted_optimizer.load_state_dict(opt_state_dict)
+
+ self.to_reshape = False
+ if not isinstance(model, PyTorchViT):
+ raise ValueError("Vision transformer is not of PyTorchViT. Error occurred in PyTorchViT creation.")
+
+ if model.default_cfg["input_size"][0] != input_shape[0]:
+ raise ValueError(
+ f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
+ f" but {input_shape[0]} channels were provided."
+ )
+
+ if model.default_cfg["input_size"] != input_shape:
+ if verbose:
+ logger.warning(
+ " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape. The input will be rescaled to (%i, %i, %i)",
+ *model.default_cfg["input_size"],
+ *input_shape,
+ *model.default_cfg["input_size"],
+ )
+
+ self.to_reshape = True
+
+ if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ ablation_type="column",
+ ablation_size=ablation_size,
+ threshold=0.0,
+ logits=True,
+ )
+ else:
+ raise ValueError("Error occurred in optimizer creation")
+
+ self.ablation_size = (ablation_size,)
+
+ if verbose:
+ logger.info(self.model)
+
+ self.ablator = ColumnAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=model.default_cfg["input_size"],
+ device_type=device_type,
+ )
+
+ # set the method back to avoid unexpected side effects later on should timm need to be reused.
+ timm.models.vision_transformer._create_vision_transformer = tmp_func
+
self.mode = "ViT"
else:
if isinstance(model, torch.nn.Module):
- PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
- self.mode = "CNN"
+ if ablation_type is None or threshold is None or logits is None:
+ raise ValueError(
+ "If using CNN please specify if the model returns logits, "
+ " the prediction threshold, and ablation type"
+ )
- elif isinstance(model, torch.nn.Module):
- PyTorchDeRandomizedSmoothingCNN.__init__(self, model, **kwargs)
- self.mode = "CNN"
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ ablation_type=ablation_type,
+ ablation_size=ablation_size,
+ threshold=threshold,
+ logits=logits,
+ )
+ self.mode = "CNN"
if self.mode is None:
raise ValueError("Model type not recognized.")
@@ -242,10 +407,7 @@ def fit( # pylint: disable=W0221
y_preprocessed = self.reduce_labels(y_preprocessed)
num_batch = len(x_preprocessed) / float(batch_size)
- if drop_last:
- num_batch = int(np.floor(num_batch))
- else:
- num_batch = int(np.ceil(num_batch))
+ num_batch = int(np.floor(num_batch)) if drop_last else int(np.ceil(num_batch))
ind = np.arange(len(x_preprocessed))
# Start training
@@ -329,3 +491,103 @@ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndar
labels = labels.detach().cpu().numpy()
return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
+
+ def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray:
+ if self.mode == "ViT":
+ return PyTorchClassifier.predict(self, x, batch_size, training_mode, **kwargs)
+ if self.mode == "CNN":
+ return PyTorchDeRandomizedSmoothingCNN.predict(self, x, batch_size, training_mode, **kwargs)
+ raise ValueError('mode is not ViT or CNN')
+
+ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
+ """
+ Method to update the batchnorm of a neural network on small datasets when it was pre-trained
+
+ :param x: Training data.
+ :param batch_size: Size of batches.
+ :param nb_epochs: How many times to forward pass over the input data
+ """
+ import torch
+ if self.mode != 'ViT':
+ raise ValueError('Accessing a ViT specific functionality while running in CNN mode')
+
+ self.model.train()
+
+ ind = np.arange(len(x))
+ num_batch = int(len(x) / float(batch_size))
+
+ with torch.no_grad():
+ for _ in tqdm(range(nb_epochs)):
+ for m in tqdm(range(num_batch)):
+ i_batch = np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])
+ i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+ _ = self.model(i_batch)
+
+ def eval_and_certify(
+ self,
+ x: np.ndarray,
+ y: np.ndarray,
+ size_to_certify: int,
+ batch_size: int = 128,
+ verbose: bool = True,
+ ) -> Tuple["torch.Tensor", "torch.Tensor"]:
+ """
+ Evaluates the ViT's normal and certified performance over the supplied data.
+
+ :param x: Evaluation data.
+ :param y: Evaluation labels.
+ :param size_to_certify: The size of the patch to certify against.
+ If not provided will default to the ablation size.
+ :param batch_size: batch size when evaluating.
+ :param verbose: If to display the progress bar
+ :return: The accuracy and certified accuracy over the dataset
+ """
+ import torch
+ if self.mode != 'ViT':
+ raise ValueError('Accessing a ViT specific functionality while running in CNN mode')
+
+ self.model.eval()
+ y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
+
+ # Apply preprocessing
+ x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
+
+ # Check label shape
+ y_preprocessed = self.reduce_labels(y_preprocessed)
+
+ num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size)))
+ pbar = tqdm(range(num_batch), disable=not verbose)
+ accuracy = torch.tensor(0.0).to(self._device)
+ cert_sum = torch.tensor(0.0).to(self._device)
+ n_samples = 0
+
+ with torch.no_grad():
+ for m in pbar:
+ if m == (num_batch - 1):
+ i_batch = np.copy(x_preprocessed[m * batch_size :])
+ o_batch = y_preprocessed[m * batch_size :]
+ else:
+ i_batch = np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])
+ o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size]
+
+ predictions = []
+ pred_counts = np.zeros((len(i_batch), self.nb_classes))
+ for pos in range(i_batch.shape[-1]):
+ ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
+
+ # Perform prediction
+ model_outputs = self.model(ablated_batch)
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+ predictions.append(model_outputs)
+
+ _, cert_and_correct, top_predicted_class = self.ablator.certify(
+ pred_counts, size_to_certify=size_to_certify, label=o_batch
+ )
+ cert_sum += torch.sum(cert_and_correct)
+ o_batch = torch.from_numpy(o_batch).to(self.device)
+ accuracy += torch.sum(top_predicted_class == o_batch)
+ n_samples += len(cert_and_correct)
+
+ pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
+
+ return (accuracy / n_samples), (cert_sum / n_samples)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
index 798ad53405..b577d448cb 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
@@ -26,29 +26,18 @@
from __future__ import absolute_import, division, print_function, unicode_literals
import logging
-from typing import List, Optional, Tuple, Union, TYPE_CHECKING
-import random
+from typing import List, TYPE_CHECKING
-import numpy as np
-from tqdm import tqdm
-
-from art.estimators.classification.pytorch import PyTorchClassifier
-from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
-from art.utils import check_and_transform_label_format
if TYPE_CHECKING:
import torch
- from timm.models.vision_transformer import VisionTransformer
from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
- from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
- from art.defences.preprocessor import Preprocessor
- from art.defences.postprocessor import Postprocessor
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
-class PyTorchSmoothedViT(PyTorchClassifier):
+class PyTorchSmoothedViT:
"""
Implementation of Certified Patch Robustness via Smoothed Vision Transformers
@@ -58,161 +47,8 @@ class PyTorchSmoothedViT(PyTorchClassifier):
| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
- def __init__(
- self,
- model: Union["VisionTransformer", str],
- loss: "torch.nn.modules.loss._Loss",
- input_shape: Tuple[int, ...],
- nb_classes: int,
- ablation_size: int,
- replace_last_layer: bool,
- drop_tokens: bool = True,
- load_pretrained: bool = True,
- optimizer: Union[type, "torch.optim.Optimizer", None] = None,
- optimizer_params: Optional[dict] = None,
- channels_first: bool = True,
- clip_values: Optional["CLIP_VALUES_TYPE"] = None,
- preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
- postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
- preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
- device_type: str = "gpu",
- verbose: bool = True,
- ):
- """
- Create a smoothed ViT classifier.
-
- :param model: Either a string specifying which ViT architecture to load, or a vision transformer already
- created with the Pytorch Image Models (timm) library.
- :param loss: The loss function for which to compute gradients for training. The target label must be raw
- categorical, i.e. not converted to one-hot encoding.
- :param input_shape: The shape of one input instance.
- :param nb_classes: The number of classes of the model.
- :param ablation_size: The size of the data portion to retain after ablation.
- :param replace_last_layer: If to replace the last layer of the ViT with a fresh layer matching the number
- of classes for the dataset to be examined. Needed if going from the pre-trained
- imagenet models to fine-tune on a dataset like CIFAR.
- :param drop_tokens: If to drop the fully ablated tokens in the ViT
- :param load_pretrained: If to load a pretrained model matching the ViT name. Will only affect the ViT if a
- string name is passed to model rather than a ViT directly.
- :param optimizer: The optimizer used to train the classifier.
- :param channels_first: Set channels first or last.
- :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
- maximum values allowed for features. If floats are provided, these will be used as the range of all
- features. If arrays are provided, each value will be considered the bound for a feature, thus
- the shape of clip values needs to match the total number of features.
- :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
- :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
- :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
- used for data preprocessing. The first value will be subtracted from the input. The input will then
- be divided by the second one.
- :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
- """
- import timm
- import torch
- from timm.models.vision_transformer import VisionTransformer
- from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
-
- # temporarily assign the original method to tmp_func
- tmp_func = timm.models.vision_transformer._create_vision_transformer
-
- # overrride with ART's ViT creation function
- timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
- if isinstance(model, str):
- model = timm.create_model(
- model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type
- )
- if replace_last_layer:
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
- if isinstance(optimizer, type):
- if optimizer_params is not None:
- optimizer = optimizer(model.parameters(), **optimizer_params)
- else:
- raise ValueError("If providing an optimiser please also supply its parameters")
-
- elif isinstance(model, VisionTransformer):
- pretrained_cfg = model.pretrained_cfg
- supplied_state_dict = model.state_dict()
- supported_models = self.get_models()
- if pretrained_cfg["architecture"] not in supported_models:
- raise ValueError(
- "Architecture not supported. Use PyTorchSmoothedViT.get_models() "
- "to get the supported model architectures."
- )
- model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type)
- model.load_state_dict(supplied_state_dict)
- if replace_last_layer:
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
-
- if optimizer is not None:
- if not isinstance(optimizer, torch.optim.Optimizer):
- raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance")
-
- converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
- opt_state_dict = optimizer.state_dict()
- if isinstance(optimizer, torch.optim.Adam):
- logging.info("Converting Adam Optimiser")
- converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
- elif isinstance(optimizer, torch.optim.SGD):
- logging.info("Converting SGD Optimiser")
- converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
- else:
- raise ValueError("Optimiser not supported for conversion")
- converted_optimizer.load_state_dict(opt_state_dict)
-
- self.to_reshape = False
- if not isinstance(model, PyTorchViT):
- raise ValueError("Vision transformer is not of PyTorchViT. Error occurred in PyTorchViT creation.")
-
- if model.default_cfg["input_size"][0] != input_shape[0]:
- raise ValueError(
- f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
- f" but {input_shape[0]} channels were provided."
- )
-
- if model.default_cfg["input_size"] != input_shape:
- if verbose:
- logger.warning(
- " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape. The input will be rescaled to (%i, %i, %i)",
- *model.default_cfg["input_size"],
- *input_shape,
- *model.default_cfg["input_size"],
- )
-
- self.to_reshape = True
-
- if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- )
- else:
- raise ValueError("Error occurred in optimizer creation")
-
- self.ablation_size = (ablation_size,)
-
- if verbose:
- logger.info(self.model)
-
- self.ablator = ColumnAblator(
- ablation_size=ablation_size,
- channels_first=True,
- to_reshape=self.to_reshape,
- original_shape=input_shape,
- output_shape=model.default_cfg["input_size"],
- device_type=device_type,
- )
-
- # set the method back to avoid unexpected side effects later on should timm need to be reused.
- timm.models.vision_transformer._create_vision_transformer = tmp_func
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
@classmethod
def get_models(cls, generate_from_null: bool = False) -> List[str]:
@@ -327,93 +163,3 @@ def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwar
pretrained_filter_fn=checkpoint_filter_fn,
**kwargs,
)
-
- def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
- """
- Method to update the batchnorm of a neural network on small datasets when it was pre-trained
-
- :param x: Training data.
- :param batch_size: Size of batches.
- :param nb_epochs: How many times to forward pass over the input data
- """
- import torch
-
- self.model.train()
-
- ind = np.arange(len(x))
- num_batch = int(len(x) / float(batch_size))
-
- with torch.no_grad():
- for _ in tqdm(range(nb_epochs)):
- for m in tqdm(range(num_batch)):
- i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(self.device)
- i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
- _ = self.model(i_batch)
-
- def eval_and_certify(
- self,
- x: np.ndarray,
- y: np.ndarray,
- size_to_certify: int,
- batch_size: int = 128,
- verbose: bool = True,
- ) -> Tuple["torch.Tensor", "torch.Tensor"]:
- """
- Evaluates the ViT's normal and certified performance over the supplied data.
-
- :param x: Evaluation data.
- :param y: Evaluation labels.
- :param size_to_certify: The size of the patch to certify against.
- If not provided will default to the ablation size.
- :param batch_size: batch size when evaluating.
- :param verbose: If to display the progress bar
- :return: The accuracy and certified accuracy over the dataset
- """
- import torch
-
- self.model.eval()
- y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
-
- # Apply preprocessing
- x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
-
- # Check label shape
- y_preprocessed = self.reduce_labels(y_preprocessed)
-
- num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size)))
- pbar = tqdm(range(num_batch), disable=not verbose)
- accuracy = torch.tensor(0.0).to(self._device)
- cert_sum = torch.tensor(0.0).to(self._device)
- n_samples = 0
-
- with torch.no_grad():
- for m in pbar:
- if m == (num_batch - 1):
- i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size :])).to(self._device)
- o_batch = torch.from_numpy(y_preprocessed[m * batch_size :]).to(self._device)
- else:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])).to(
- self._device
- )
- o_batch = torch.from_numpy(y_preprocessed[m * batch_size : (m + 1) * batch_size]).to(self._device)
-
- predictions = []
- pred_counts = torch.zeros((len(i_batch), self.nb_classes)).to(self._device)
- for pos in range(i_batch.shape[-1]):
- ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
-
- # Perform prediction
- model_outputs = self.model(ablated_batch)
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1)] += 1
- predictions.append(model_outputs)
-
- _, cert_and_correct, top_predicted_class = self.ablator.certify(
- pred_counts, size_to_certify=size_to_certify, label=o_batch
- )
- cert_sum += torch.sum(cert_and_correct)
- accuracy += torch.sum(top_predicted_class == o_batch)
- n_samples += len(cert_and_correct)
-
- pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
-
- return (accuracy / n_samples), (cert_sum / n_samples)
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
index fb8c1795f7..2521de0363 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
@@ -24,7 +24,7 @@
| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
"""
-from typing import Optional, Tuple
+from typing import Optional, Union, Tuple
import random
import numpy as np
@@ -110,7 +110,7 @@ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
x[:, :, :, column_pos + k :] = 0.0
return x
- def forward(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Tensor:
+ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None) -> torch.Tensor:
"""
Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
@@ -134,7 +134,7 @@ def forward(self, x: torch.Tensor, column_pos: Optional[int] = None) -> torch.Te
return x
def certify(
- self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor
+ self, pred_counts: Union[torch.Tensor, np.ndarray], size_to_certify: int, label: Union[torch.Tensor, np.ndarray]
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Performs certification of the predictions
@@ -146,6 +146,11 @@ def certify(
the predictions which were certified and also correct,
and the most predicted class across the different ablations on the input.
"""
+ if isinstance(pred_counts, np.ndarray):
+ pred_counts = torch.from_numpy(pred_counts).to(self.device)
+
+ if isinstance(label, np.ndarray):
+ label = torch.from_numpy(label).to(self.device)
num_of_classes = pred_counts.shape[-1]
diff --git a/dev.py b/dev.py
index afe1631813..763700749a 100644
--- a/dev.py
+++ b/dev.py
@@ -32,25 +32,86 @@ def get_cifar_data():
return (x_train, y_train), (x_test, y_test)
-(x_train, y_train), (x_test, y_test) = get_cifar_data()
-
-art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224',
- loss=torch.nn.CrossEntropyLoss(),
- optimizer=torch.optim.SGD,
- optimizer_params={"lr": 0.01},
- input_shape=(3, 32, 32),
- nb_classes=10,
- ablation_size=4,
- replace_last_layer=True,
- load_pretrained=True,)
-
-scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
-art_model.fit(x_train, y_train,
- nb_epochs=30,
- update_batchnorm=True,
- scheduler=scheduler,
- transform=transforms.Compose([transforms.RandomHorizontalFlip()]))
-
-# torch.save(art_model.model.state_dict(), 'trained.pt')
-# art_model.model.load_state_dict(torch.load('trained.pt'))
-art_model.eval_and_certify(x_test, y_test, size_to_certify=4)
+def vit_dev():
+ (x_train, y_train), (x_test, y_test) = get_cifar_data()
+
+ art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224',
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ replace_last_layer=True,
+ load_pretrained=True)
+ # art_model.predict(x_train[0:10])
+
+ scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
+ art_model.fit(x_train, y_train,
+ nb_epochs=30,
+ update_batchnorm=False,
+ scheduler=scheduler,
+ transform=transforms.Compose([transforms.RandomHorizontalFlip()]))
+
+ torch.save(art_model.model.state_dict(), 'trained_refactor.pt')
+ art_model.model.load_state_dict(torch.load('trained_refactor.pt'))
+ art_model.eval_and_certify(x_test, y_test, size_to_certify=4)
+
+
+def cnn_dev():
+ class CIFARModel(torch.nn.Module):
+
+ def __init__(self, number_of_classes: int):
+ super(CIFARModel, self).__init__()
+
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
+
+ self.conv_1 = torch.nn.Conv2d(in_channels=6,
+ out_channels=32,
+ kernel_size=4,
+ stride=2)
+
+ self.conv_2 = torch.nn.Conv2d(in_channels=32,
+ out_channels=32,
+ kernel_size=4,
+ stride=1)
+
+ self.fc1 = torch.nn.Linear(in_features=4608, out_features=number_of_classes)
+
+ self.relu = torch.nn.ReLU()
+
+ def forward(self, x: "torch.Tensor") -> "torch.Tensor":
+ """
+ Computes the forward pass though the neural network
+ :param x: input data of shape (batch size, N features)
+ :return: model prediction
+ """
+ x = self.relu(self.conv_1(x))
+ x = self.relu(self.conv_2(x))
+ x = torch.flatten(x, 1)
+ return self.fc1(x)
+
+ model = CIFARModel(number_of_classes=10)
+ (x_train, y_train), (x_test, y_test) = get_cifar_data()
+
+ art_model = PyTorchDeRandomizedSmoothing(model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD(model.parameters(), lr=0.001),
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_type='column',
+ ablation_size=4,
+ threshold=0.1,
+ logits=False)
+
+ scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
+ art_model.predict(x_train[0:10])
+ print(art_model)
+
+ art_model.fit(x_train, y_train,
+ nb_epochs=30,
+ update_batchnorm=True,
+ scheduler=scheduler)
+
+vit_dev()
+# cnn_dev()
\ No newline at end of file
From 6b3dcf680332ffdffa2969df497e49bc26985ba0 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 28 Jun 2023 13:10:16 +0100
Subject: [PATCH 25/55] update test script
Signed-off-by: GiulioZizzo
---
dev.py | 75 ++++++---
.../certification/test_smooth_vit.py | 156 +++++++++++++++++-
2 files changed, 204 insertions(+), 27 deletions(-)
diff --git a/dev.py b/dev.py
index 763700749a..984efe8a56 100644
--- a/dev.py
+++ b/dev.py
@@ -1,6 +1,6 @@
import torch
import ssl
-ssl._create_default_https_context = ssl._create_unverified_context
+# ssl._create_default_https_context = ssl._create_unverified_context
from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
import numpy as np
from torchvision import datasets
@@ -31,6 +31,26 @@ def get_cifar_data():
return (x_train, y_train), (x_test, y_test)
+def get_mnist_data():
+ """
+ Get the MNIST data.
+ """
+ train_set = datasets.MNIST('./data', train=True, download=True)
+ test_set = datasets.MNIST('./data', train=False, download=True)
+
+ x_train = train_set.data.numpy().astype(np.float32)
+ y_train = train_set.targets.numpy()
+
+ x_test = test_set.data.numpy().astype(np.float32)
+ y_test = test_set.targets.numpy()
+
+ x_train = np.expand_dims(x_train, axis=1)
+ x_test = np.expand_dims(x_test, axis=1)
+
+ x_train = x_train / 255.0
+ x_test = x_test / 255.0
+
+ return (x_train, y_train), (x_test, y_test)
def vit_dev():
(x_train, y_train), (x_test, y_test) = get_cifar_data()
@@ -59,24 +79,27 @@ def vit_dev():
def cnn_dev():
- class CIFARModel(torch.nn.Module):
+ class MNISTModel(torch.nn.Module):
def __init__(self, number_of_classes: int):
- super(CIFARModel, self).__init__()
+ super(MNISTModel, self).__init__()
self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- self.conv_1 = torch.nn.Conv2d(in_channels=6,
- out_channels=32,
+ self.conv_1 = torch.nn.Conv2d(in_channels=2,
+ out_channels=64,
kernel_size=4,
- stride=2)
+ stride=2,
+ padding=1)
- self.conv_2 = torch.nn.Conv2d(in_channels=32,
- out_channels=32,
+ self.conv_2 = torch.nn.Conv2d(in_channels=64,
+ out_channels=128,
kernel_size=4,
- stride=1)
+ stride=2, padding=1)
- self.fc1 = torch.nn.Linear(in_features=4608, out_features=number_of_classes)
+ self.fc1 = torch.nn.Linear(in_features=128*7*7, out_features=500)
+ self.fc2 = torch.nn.Linear(in_features=500, out_features=100)
+ self.fc3 = torch.nn.Linear(in_features=100, out_features=10)
self.relu = torch.nn.ReLU()
@@ -89,29 +112,31 @@ def forward(self, x: "torch.Tensor") -> "torch.Tensor":
x = self.relu(self.conv_1(x))
x = self.relu(self.conv_2(x))
x = torch.flatten(x, 1)
- return self.fc1(x)
+ x = self.relu(self.fc1(x))
+ x = self.relu(self.fc2(x))
+ x = self.fc3(x)
+ return x
- model = CIFARModel(number_of_classes=10)
- (x_train, y_train), (x_test, y_test) = get_cifar_data()
+ model = MNISTModel(number_of_classes=10)
+ # (x_train, y_train), (x_test, y_test) = get_cifar_data()
+ (x_train, y_train), (x_test, y_test) = get_mnist_data()
+ optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0005)
art_model = PyTorchDeRandomizedSmoothing(model=model,
loss=torch.nn.CrossEntropyLoss(),
- optimizer=torch.optim.SGD(model.parameters(), lr=0.001),
- input_shape=(3, 32, 32),
+ optimizer=optimizer,
+ input_shape=(1, 28, 28),
nb_classes=10,
ablation_type='column',
- ablation_size=4,
- threshold=0.1,
- logits=False)
+ ablation_size=2,
+ threshold=0.3,
+ logits=True)
- scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)
- art_model.predict(x_train[0:10])
- print(art_model)
+ scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[200], gamma=0.1)
art_model.fit(x_train, y_train,
- nb_epochs=30,
- update_batchnorm=True,
+ nb_epochs=400,
scheduler=scheduler)
-vit_dev()
-# cnn_dev()
\ No newline at end of file
+# vit_dev()
+cnn_dev()
\ No newline at end of file
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index f1c9566d73..7649b0536f 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -66,7 +66,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
try:
cifar_data = fix_get_cifar10_data[0]
@@ -75,6 +75,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
ablation_size=4,
channels_first=True,
to_reshape=False, # do not upsample initially
+ mode='ViT',
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
)
@@ -101,6 +102,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
ablation_size=4,
channels_first=True,
to_reshape=True,
+ mode='ViT',
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
)
@@ -161,13 +163,14 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
"""
Check that ...
"""
- from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
import torch
try:
col_ablator = ColumnAblator(
ablation_size=4,
channels_first=True,
+ mode='ViT',
to_reshape=True, # do not upsample initially
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
@@ -183,6 +186,146 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
except ARTTestException as e:
art_warning(e)
+def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ Assert implementations matches original with a forward pass through the same model architecture.
+ Note, there are some differences in architecture between the same model names.
+ We use vit_base_patch16_224 which matches.
+ """
+ import torch
+ import os
+ import sys
+
+ from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
+
+ os.system("git clone https://github.com/MadryLab/smoothed-vit")
+ sys.path.append('smoothed-vit/src/utils/')
+
+ # Original MaskProcessor used ones_mask = torch.cat([torch.cuda.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
+ # which is not compatible with non-cuda torch as is found when running tests on github.
+ # Hence, replace the class with the same code, but having changed to
+ # ones_mask = torch.cat([torch.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
+ # Original licence:
+ """
+ MIT License
+
+ Copyright (c) 2021 Madry Lab
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ """
+
+ class MaskProcessor(torch.nn.Module):
+ def __init__(self, patch_size=16):
+ super().__init__()
+ self.avg_pool = torch.nn.AvgPool2d(patch_size)
+
+ def forward(self, ones_mask):
+ B = ones_mask.shape[0]
+ ones_mask = ones_mask[0].unsqueeze(0) # take the first mask
+ ones_mask = self.avg_pool(ones_mask)[0]
+ ones_mask = torch.where(ones_mask.view(-1) > 0)[0] + 1
+ ones_mask = torch.cat([torch.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
+ ones_mask = ones_mask.expand(B, -1)
+ return ones_mask
+
+ from custom_models import preprocess
+ preprocess.MaskProcessor = MaskProcessor
+
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
+ from custom_models.vision_transformer import vit_small_patch16_224, vit_base_patch16_224
+
+ cifar_data = fix_get_cifar10_data[0][:50]
+ cifar_labels = fix_get_cifar10_data[1][:50]
+
+ '''
+ timm config for:
+ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
+ """ ViT-Base (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
+ """
+ model_args = dict(patch_size=16, embed_dim=768, depth=12, num_heads=12)
+ model = _create_vision_transformer('vit_base_patch16_224', pretrained=pretrained, **dict(model_args, **kwargs))
+ return model
+
+
+ def vit_small_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
+ """ ViT-Small (ViT-S/16)
+ """
+ model_args = dict(patch_size=16, embed_dim=384, depth=12, num_heads=6)
+ model = _create_vision_transformer('vit_small_patch16_224', pretrained=pretrained, **dict(model_args, **kwargs))
+ return model
+
+ smooth repo config for:
+ def vit_small_patch16_224(pretrained=False, **kwargs):
+ if pretrained:
+ # NOTE my scale was wrong for original weights, leaving this here until I have better ones for this model
+ kwargs.setdefault('qk_scale', 768 ** -0.5)
+ model = VisionTransformer(patch_size=16, embed_dim=768, depth=8, num_heads=8, mlp_ratio=3., **kwargs)
+ model.default_cfg = default_cfgs['vit_small_patch16_224']
+ if pretrained:
+ load_pretrained(
+ model, num_classes=model.num_classes, in_chans=kwargs.get('in_chans', 3), filter_fn=_conv_filter)
+ return model
+
+
+ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
+ """ ViT-Base (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
+ """
+ model_args = dict(patch_size=16, embed_dim=768, depth=12, num_heads=12)
+ model = _create_vision_transformer('vit_base_patch16_224', pretrained=pretrained, **dict(model_args, **kwargs))
+ return model
+
+ '''
+
+ art_model = PyTorchDeRandomizedSmoothing(
+ model="vit_base_patch16_224",
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 32, 32),
+ nb_classes=10,
+ ablation_size=4,
+ load_pretrained=True,
+ replace_last_layer=True,
+ verbose=False,
+ )
+ art_sd = art_model.model.state_dict()
+ madry_vit = vit_base_patch16_224(pretrained=False)
+ madry_vit.head = torch.nn.Linear(madry_vit.head.in_features, 10)
+
+ madry_vit.load_state_dict(art_sd)
+
+ col_ablator = ColumnAblator(
+ ablation_size=4,
+ channels_first=True,
+ to_reshape=True,
+ mode='ViT',
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224),
+ )
+
+ ablated = col_ablator.forward(cifar_data, column_pos=10)
+
+ madry_preds = madry_vit(ablated)
+ art_preds = art_model.model(ablated)
+ assert torch.allclose(madry_preds, art_preds, rtol=1e-04, atol=1e-04)
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
def test_equivalence(fix_get_cifar10_data):
@@ -264,6 +407,15 @@ def embedder(cls, x, pos_embed, cls_token):
"""
NB, original code used the pos embed from the divit rather than vit
(which we pull from our model) which we use here.
+
+ From timm vit:
+ self.pos_embed = nn.Parameter(torch.randn(1, embed_len, embed_dim) * .02)
+
+ From timm dvit:
+ self.pos_embed = nn.Parameter(torch.zeros(1, self.patch_embed.num_patches + self.num_prefix_tokens, self.embed_dim))
+
+ From repo:
+ self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
"""
x = torch.cat((cls_token.expand(x.shape[0], -1, -1), x), dim=1)
return x + pos_embed
From 3f3cea632673bd59f699ef298e83d5c1c9953beb Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 12 Jul 2023 14:19:33 +0100
Subject: [PATCH 26/55] splitting out pytorch functionalities
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 190 ++++++++++++++
.../derandomized_smoothing/pytorch.py | 245 ++++++++----------
.../vision_transformers/smooth_vit.py | 11 +-
3 files changed, 309 insertions(+), 137 deletions(-)
create mode 100644 art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
new file mode 100644
index 0000000000..cf2bd9748c
--- /dev/null
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -0,0 +1,190 @@
+# MIT License
+#
+# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+# persons to whom the Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+# Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+"""
+This module implements Certified Patch Robustness via Smoothed Vision Transformers
+
+| Paper link Accepted version:
+ https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
+
+| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
+"""
+
+from typing import Optional, Union, Tuple
+import random
+
+import numpy as np
+import torch
+
+
+class UpSampler(torch.nn.Module):
+ """
+ Resizes datasets to the specified size.
+ Usually for upscaling datasets like CIFAR to Imagenet format
+ """
+
+ def __init__(self, input_size: int, final_size: int) -> None:
+ """
+ Creates an upsampler to make the supplied data match the pre-trained ViT format
+
+ :param input_size: Size of the current input data
+ :param final_size: Desired final size
+ """
+ super().__init__()
+ self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ """
+ Forward pass though the upsampler.
+
+ :param x: Input data
+ :return: The upsampled input data
+ """
+ return self.upsample(x)
+
+
+class ColumnAblator(torch.nn.Module):
+ """
+ Pure Pytorch implementation of stripe/column ablation.
+ """
+
+ def __init__(
+ self,
+ ablation_size: int,
+ channels_first: bool,
+ mode,
+ to_reshape: bool,
+ original_shape: Optional[Tuple] = None,
+ output_shape: Optional[Tuple] = None,
+ algorithm: str = 'salman2021',
+ device_type: str = "gpu",
+ ):
+ """
+ Creates a column ablator
+
+ :param ablation_size: The size of the column we will retain.
+ :param channels_first: If the input is in channels first format. Currently required to be True.
+ :param to_reshape: If the input requires reshaping.
+ :param original_shape: Original shape of the input.
+ :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
+ """
+ super().__init__()
+
+ self.ablation_size = ablation_size
+ self.channels_first = channels_first
+ self.to_reshape = to_reshape
+ self.add_ablation_mask = False
+ self.additional_channels = False
+ self.algorithm = algorithm
+ self.original_shape = original_shape
+
+ if self.algorithm == 'levine2020':
+ self.additional_channels = True
+ if self.algorithm == 'salman2021' and mode == 'ViT':
+ self.add_ablation_mask = True
+
+ if device_type == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
+ if original_shape is not None and output_shape is not None:
+ self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
+
+ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
+ """
+ Ablates the input colum wise
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The ablated input with 0s where the ablation occurred
+ """
+ k = self.ablation_size
+ if column_pos + k > x.shape[-1]:
+ x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
+ else:
+ x[:, :, :, :column_pos] = 0.0
+ x[:, :, :, column_pos + k :] = 0.0
+ return x
+
+ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None) -> torch.Tensor:
+ """
+ Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The albated input with an extra channel indicating the location of the ablation
+ """
+
+ if x.shape[1] != self.original_shape[0]:
+ raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
+
+ if column_pos is None:
+ column_pos = random.randint(0, x.shape[3])
+
+ if isinstance(x, np.ndarray):
+ x = torch.from_numpy(x).to(self.device)
+
+ if self.add_ablation_mask:
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
+ x = torch.cat([x, ones], dim=1)
+
+ if self.additional_channels:
+ x = torch.cat([x, 1.0 - x], dim=1)
+
+ x = self.ablate(x, column_pos=column_pos)
+
+ if self.to_reshape:
+ x = self.upsample(x)
+ return x
+
+ def certify(self,
+ pred_counts: Union[torch.Tensor, np.ndarray],
+ size_to_certify: int,
+ label: Union[torch.Tensor, np.ndarray] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ Performs certification of the predictions
+
+ :param pred_counts: The model predictions over the ablated data.
+ :param size_to_certify: The patch size we wish to check certification against
+ :param label: The ground truth labels
+ :return: A tuple consisting of: the certified predictions,
+ the predictions which were certified and also correct,
+ and the most predicted class across the different ablations on the input.
+ """
+
+ if isinstance(pred_counts, np.ndarray):
+ pred_counts = torch.from_numpy(pred_counts).to(self.device)
+
+ if isinstance(label, np.ndarray):
+ label = torch.from_numpy(label).to(self.device)
+
+ num_of_classes = pred_counts.shape[-1]
+
+ top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
+ second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
+
+ cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
+
+ cert_and_correct = cert & (label == top_predicted_class)
+
+ if self.algorithm == 'levine2020':
+ tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
+ & (top_predicted_class < second_predicted_class)
+ cert = torch.logical_or(cert, tie_break_certs)
+ return cert, cert_and_correct, top_predicted_class
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index f6064b2c01..cf6e787bea 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -44,7 +44,6 @@
from art.config import ART_NUMPY_DTYPE
from art.estimators.classification.pytorch import PyTorchClassifier
from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import PyTorchSmoothedViT
-from art.estimators.certification.derandomized_smoothing.derandomized_smoothing import DeRandomizedSmoothingMixin
from art.utils import check_and_transform_label_format
if TYPE_CHECKING:
@@ -59,71 +58,7 @@
logger = logging.getLogger(__name__)
-class PyTorchDeRandomizedSmoothingCNN(DeRandomizedSmoothingMixin):
- """
- Implementation of (De)Randomized Smoothing applied to classifier predictions as introduced
- in Levine et al. (2020).
-
- | Paper link: https://arxiv.org/abs/2002.10733
- """
-
- def __init__(self, **kwargs):
- """
- Create a derandomized smoothing classifier.
-
- :param model: PyTorch model. The output of the model can be logits, probabilities or anything else. Logits
- output should be preferred where possible to ensure attack efficiency.
- :param loss: The loss function for which to compute gradients for training. The target label must be raw
- categorical, i.e. not converted to one-hot encoding.
- :param input_shape: The shape of one input instance.
- :param nb_classes: The number of classes of the model.
- :param ablation_type: The type of ablation to perform, must be either "column" or "block"
- :param ablation_size: The size of the data portion to retain after ablation. Will be a column of size N for
- "column" ablation type or a NxN square for ablation of type "block"
- :param threshold: The minimum threshold to count a prediction.
- :param logits: if the model returns logits or normalized probabilities
- :param optimizer: The optimizer used to train the classifier.
- :param channels_first: Set channels first or last.
- :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
- maximum values allowed for features. If floats are provided, these will be used as the range of all
- features. If arrays are provided, each value will be considered the bound for a feature, thus
- the shape of clip values needs to match the total number of features.
- :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
- :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
- :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
- used for data preprocessing. The first value will be subtracted from the input. The input will then
- be divided by the second one.
- :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
- """
- super().__init__(**kwargs)
-
- def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray:
- import torch
-
- x = x.astype(ART_NUMPY_DTYPE)
- outputs = PyTorchClassifier.predict(self, x=x, batch_size=batch_size, training_mode=training_mode, **kwargs)
-
- if not self.logits:
- return np.asarray((outputs >= self.threshold))
- return np.asarray(
- (torch.nn.functional.softmax(torch.from_numpy(outputs), dim=1) >= self.threshold).type(torch.int)
- )
-
- def predict(
- self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs
- ) -> np.ndarray: # type: ignore
- """
- Perform prediction of the given classifier for a batch of inputs, taking an expectation over transformations.
-
- :param x: Input samples.
- :param batch_size: Batch size.
- :param training_mode: if to run the classifier in training mode
- :return: Array of predictions of shape `(nb_inputs, nb_classes)`.
- """
- return DeRandomizedSmoothingMixin.predict(self, x, batch_size=batch_size, training_mode=training_mode, **kwargs)
-
-
-class PyTorchDeRandomizedSmoothing(PyTorchDeRandomizedSmoothingCNN, PyTorchSmoothedViT, PyTorchClassifier):
+class PyTorchDeRandomizedSmoothing(PyTorchSmoothedViT, PyTorchClassifier):
"""
Interface class for the two De-randomized smoothing approaches supported by ART for pytorch.
@@ -141,6 +76,7 @@ def __init__(
input_shape: Tuple[int, ...],
nb_classes: int,
ablation_size: int,
+ algorithm: str = 'salman2021',
replace_last_layer: Optional[bool] = None,
drop_tokens: bool = True,
load_pretrained: bool = True,
@@ -161,15 +97,16 @@ def __init__(
"""
Create a smoothed classifier.
- :param model: To run Salman et al. (2021):
- Either a string specifying which ViT architecture to load, or a vision transformer already
- created with the Pytorch Image Models (timm) library.
- To run Levine et al. (2020) provide a regular pytorch model
+ :param model: Either a CNN or a VIT. For a ViT supply a string specifying which ViT architecture to load from
+ the ViT library, or a vision transformer already created with the Pytorch Image Models (timm) library.
+ To run Levine et al. (2020) provide a regular pytorch model.
:param loss: The loss function for which to compute gradients for training. The target label must be raw
- categorical, i.e. not converted to one-hot encoding.
+ categorical, i.e. not converted to one-hot encoding.
:param input_shape: The shape of one input instance.
:param nb_classes: The number of classes of the model.
:param ablation_size: The size of the data portion to retain after ablation.
+ :param algorithm: Either 'salman2021' or 'levine2020'. For salman2021 we support ViTs and CNNs. For levine2020
+ there is only CNN support.
:param replace_last_layer: ViT Specific. If to replace the last layer of the ViT with a fresh layer
matching the number of classes for the dataset to be examined.
Needed if going from the pre-trained imagenet models to fine-tune
@@ -196,15 +133,14 @@ def __init__(
"""
import torch
+ print(algorithm)
self.mode = None
- if importlib.util.find_spec("timm") is not None:
+ if importlib.util.find_spec("timm") is not None and algorithm == 'salman2021':
from timm.models.vision_transformer import VisionTransformer
- from art.estimators.certification.derandomized_smoothing.vision_transformers.smooth_vit import ColumnAblator
if isinstance(model, (VisionTransformer, str)):
import timm
- from timm.models.vision_transformer import VisionTransformer
from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
if replace_last_layer is None:
@@ -279,72 +215,57 @@ def __init__(
)
self.to_reshape = True
-
- if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- ablation_type="column",
- ablation_size=ablation_size,
- threshold=0.0,
- logits=True,
- )
- else:
- raise ValueError("Error occurred in optimizer creation")
-
- self.ablation_size = (ablation_size,)
-
- if verbose:
- logger.info(self.model)
-
- self.ablator = ColumnAblator(
- ablation_size=ablation_size,
- channels_first=True,
- to_reshape=self.to_reshape,
- original_shape=input_shape,
- output_shape=model.default_cfg["input_size"],
- device_type=device_type,
- )
+ output_shape = model.default_cfg["input_size"]
# set the method back to avoid unexpected side effects later on should timm need to be reused.
timm.models.vision_transformer._create_vision_transformer = tmp_func
-
self.mode = "ViT"
else:
if isinstance(model, torch.nn.Module):
- if ablation_type is None or threshold is None or logits is None:
- raise ValueError(
- "If using CNN please specify if the model returns logits, "
- " the prediction threshold, and ablation type"
- )
-
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- ablation_type=ablation_type,
- ablation_size=ablation_size,
- threshold=threshold,
- logits=logits,
- )
+ if algorithm == 'levine2020':
+ if ablation_type is None or threshold is None or logits is None:
+ raise ValueError(
+ "If using CNN please specify if the model returns logits, "
+ " the prediction threshold, and ablation type"
+ )
self.mode = "CNN"
+ output_shape = input_shape
+
+ if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
+ super().__init__(
+ model=model,
+ loss=loss,
+ input_shape=input_shape,
+ nb_classes=nb_classes,
+ optimizer=optimizer,
+ channels_first=channels_first,
+ clip_values=clip_values,
+ preprocessing_defences=preprocessing_defences,
+ postprocessing_defences=postprocessing_defences,
+ preprocessing=preprocessing,
+ device_type=device_type,
+ )
+ else:
+ raise ValueError("Error occurred in optimizer creation")
+
+ self.threshold = threshold
+ self.logits = logits
+ self.ablation_size = (ablation_size,)
+ self.algorithm = algorithm
+ if verbose:
+ logger.info(self.model)
+
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
+ self.ablator = ColumnAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=output_shape,
+ device_type=device_type,
+ algorithm=algorithm,
+ mode=self.mode,
+ )
if self.mode is None:
raise ValueError("Model type not recognized.")
@@ -492,12 +413,14 @@ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndar
return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
+ '''
def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray:
if self.mode == "ViT":
return PyTorchClassifier.predict(self, x, batch_size, training_mode, **kwargs)
if self.mode == "CNN":
return PyTorchDeRandomizedSmoothingCNN.predict(self, x, batch_size, training_mode, **kwargs)
raise ValueError('mode is not ViT or CNN')
+ '''
def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
"""
@@ -543,7 +466,7 @@ def eval_and_certify(
:return: The accuracy and certified accuracy over the dataset
"""
import torch
- if self.mode != 'ViT':
+ if self.mode != 'ViT': # TODO, adapt for cnn first
raise ValueError('Accessing a ViT specific functionality while running in CNN mode')
self.model.eval()
@@ -591,3 +514,57 @@ def eval_and_certify(
pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
return (accuracy / n_samples), (cert_sum / n_samples)
+
+ def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray:
+ import torch
+
+ x = x.astype(ART_NUMPY_DTYPE)
+ outputs = PyTorchClassifier.predict(self, x=x, batch_size=batch_size, training_mode=training_mode, **kwargs)
+
+ if self.algorithm == 'levine2020':
+ if not self.logits:
+ return np.asarray((outputs >= self.threshold))
+ return np.asarray(
+ (torch.nn.functional.softmax(torch.from_numpy(outputs), dim=1) >= self.threshold).type(torch.int)
+ )
+ return outputs
+
+ def predict(self, x, batch_size, training_mode, **kwargs):
+ if self._channels_first:
+ columns_in_data = x.shape[-1]
+ rows_in_data = x.shape[-2]
+ else:
+ columns_in_data = x.shape[-2]
+ rows_in_data = x.shape[-3]
+
+ if self.ablation_type in {"column", "row"}:
+ if self.ablation_type == "column":
+ ablate_over_range = columns_in_data
+ else:
+ # image will be transposed, so loop over the number of rows
+ ablate_over_range = rows_in_data
+
+ for ablation_start in range(ablate_over_range):
+ ablated_x = self.ablator.forward(np.copy(x), column_pos=ablation_start)
+ if ablation_start == 0:
+ preds = self._predict_classifier(
+ ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs
+ )
+ else:
+ preds += self._predict_classifier(
+ ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs
+ )
+ elif self.ablation_type == "block":
+ for xcorner in range(rows_in_data):
+ for ycorner in range(columns_in_data):
+ ablated_x = self.ablator.forward(np.copy(x), row_pos=xcorner, column_pos=ycorner)
+ if ycorner == 0 and xcorner == 0:
+ preds = self._predict_classifier(
+ ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs
+ )
+ else:
+ preds += self._predict_classifier(
+ ablated_x, batch_size=batch_size, training_mode=training_mode, **kwargs
+ )
+
+ return preds
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
index 2521de0363..b29115af23 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
@@ -69,6 +69,7 @@ def __init__(
to_reshape: bool = False,
original_shape: Optional[Tuple] = None,
output_shape: Optional[Tuple] = None,
+ add_ablation_mask: bool = True,
device_type: str = "gpu",
):
"""
@@ -84,6 +85,8 @@ def __init__(
self.ablation_size = ablation_size
self.channels_first = channels_first
self.to_reshape = to_reshape
+ self.expected_input_channels = 1
+ self.add_ablation_mask = add_ablation_mask
if device_type == "cpu" or not torch.cuda.is_available():
self.device = torch.device("cpu")
@@ -118,7 +121,7 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
:param column_pos: The start position of the albation
:return: The albated input with an extra channel indicating the location of the ablation
"""
- assert x.shape[1] == 3
+ assert x.shape[1] == self.expected_input_channels
if column_pos is None:
column_pos = random.randint(0, x.shape[3])
@@ -126,8 +129,10 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if isinstance(x, np.ndarray):
x = torch.from_numpy(x).to(self.device)
- ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
- x = torch.cat([x, ones], dim=1)
+ if self.add_ablation_mask:
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
+ x = torch.cat([x, ones], dim=1)
+
x = self.ablate(x, column_pos=column_pos)
if self.to_reshape:
x = self.upsample(x)
From d1857b42b6301049c9fba612283accf4994d252c Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 12 Jul 2023 14:44:06 +0100
Subject: [PATCH 27/55] updating dev testing file and tests for vit
Signed-off-by: GiulioZizzo
---
dev.py | 151 ++++++++++++------
.../certification/test_smooth_vit.py | 107 ++++++++++++-
2 files changed, 203 insertions(+), 55 deletions(-)
diff --git a/dev.py b/dev.py
index 984efe8a56..b31be5abca 100644
--- a/dev.py
+++ b/dev.py
@@ -78,59 +78,108 @@ def vit_dev():
art_model.eval_and_certify(x_test, y_test, size_to_certify=4)
-def cnn_dev():
- class MNISTModel(torch.nn.Module):
-
- def __init__(self, number_of_classes: int):
- super(MNISTModel, self).__init__()
-
- self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
-
- self.conv_1 = torch.nn.Conv2d(in_channels=2,
- out_channels=64,
- kernel_size=4,
- stride=2,
- padding=1)
-
- self.conv_2 = torch.nn.Conv2d(in_channels=64,
- out_channels=128,
- kernel_size=4,
- stride=2, padding=1)
-
- self.fc1 = torch.nn.Linear(in_features=128*7*7, out_features=500)
- self.fc2 = torch.nn.Linear(in_features=500, out_features=100)
- self.fc3 = torch.nn.Linear(in_features=100, out_features=10)
-
- self.relu = torch.nn.ReLU()
-
- def forward(self, x: "torch.Tensor") -> "torch.Tensor":
- """
- Computes the forward pass though the neural network
- :param x: input data of shape (batch size, N features)
- :return: model prediction
- """
- x = self.relu(self.conv_1(x))
- x = self.relu(self.conv_2(x))
- x = torch.flatten(x, 1)
- x = self.relu(self.fc1(x))
- x = self.relu(self.fc2(x))
- x = self.fc3(x)
- return x
-
- model = MNISTModel(number_of_classes=10)
+def cnn_dev(algo='salman2021'):
+
+ assert algo in ['levine2020', 'salman2021']
+
+ if algo == 'salman2021':
+ class MNISTModel(torch.nn.Module):
+
+ def __init__(self):
+ super(MNISTModel, self).__init__()
+
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
+
+ self.conv_1 = torch.nn.Conv2d(in_channels=1,
+ out_channels=64,
+ kernel_size=4,
+ stride=2,
+ padding=1)
+
+ self.conv_2 = torch.nn.Conv2d(in_channels=64,
+ out_channels=128,
+ kernel_size=4,
+ stride=2, padding=1)
+
+ self.fc1 = torch.nn.Linear(in_features=128*7*7, out_features=500)
+ self.fc2 = torch.nn.Linear(in_features=500, out_features=100)
+ self.fc3 = torch.nn.Linear(in_features=100, out_features=10)
+
+ self.relu = torch.nn.ReLU()
+
+ def forward(self, x: "torch.Tensor") -> "torch.Tensor":
+ """
+ Computes the forward pass though the neural network
+ :param x: input data of shape (batch size, N features)
+ :return: model prediction
+ """
+ x = self.relu(self.conv_1(x))
+ x = self.relu(self.conv_2(x))
+ x = torch.flatten(x, 1)
+ x = self.relu(self.fc1(x))
+ x = self.relu(self.fc2(x))
+ x = self.fc3(x)
+ return x
+ else:
+ class MNISTModel(torch.nn.Module):
+
+ def __init__(self):
+ super(MNISTModel, self).__init__()
+
+ self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
+
+ self.conv_1 = torch.nn.Conv2d(in_channels=2,
+ out_channels=64,
+ kernel_size=4,
+ stride=2,
+ padding=1)
+
+ self.conv_2 = torch.nn.Conv2d(in_channels=64,
+ out_channels=128,
+ kernel_size=4,
+ stride=2, padding=1)
+
+ self.fc1 = torch.nn.Linear(in_features=128*7*7, out_features=500)
+ self.fc2 = torch.nn.Linear(in_features=500, out_features=100)
+ self.fc3 = torch.nn.Linear(in_features=100, out_features=10)
+
+ self.relu = torch.nn.ReLU()
+
+ def forward(self, x: "torch.Tensor") -> "torch.Tensor":
+ x = self.relu(self.conv_1(x))
+ x = self.relu(self.conv_2(x))
+ x = torch.flatten(x, 1)
+ x = self.relu(self.fc1(x))
+ x = self.relu(self.fc2(x))
+ x = self.fc3(x)
+ return x
+
+ model = MNISTModel()
# (x_train, y_train), (x_test, y_test) = get_cifar_data()
(x_train, y_train), (x_test, y_test) = get_mnist_data()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0005)
- art_model = PyTorchDeRandomizedSmoothing(model=model,
- loss=torch.nn.CrossEntropyLoss(),
- optimizer=optimizer,
- input_shape=(1, 28, 28),
- nb_classes=10,
- ablation_type='column',
- ablation_size=2,
- threshold=0.3,
- logits=True)
+ if algo == 'salman2021':
+ art_model = PyTorchDeRandomizedSmoothing(model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=optimizer,
+ input_shape=(1, 28, 28),
+ nb_classes=10,
+ ablation_type='column',
+ ablation_size=2,
+ algorithm=algo,
+ logits=True)
+ else:
+ art_model = PyTorchDeRandomizedSmoothing(model=model,
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=optimizer,
+ input_shape=(1, 28, 28),
+ nb_classes=10,
+ ablation_type='column',
+ ablation_size=2,
+ algorithm=algo,
+ threshold=0.3,
+ logits=True)
scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[200], gamma=0.1)
@@ -138,5 +187,5 @@ def forward(self, x: "torch.Tensor") -> "torch.Tensor":
nb_epochs=400,
scheduler=scheduler)
-# vit_dev()
-cnn_dev()
\ No newline at end of file
+vit_dev()
+# cnn_dev()
\ No newline at end of file
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index 7649b0536f..557983b80e 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -186,6 +186,7 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
except ARTTestException as e:
art_warning(e)
+@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
"""
Assert implementations matches original with a forward pass through the same model architecture.
@@ -197,6 +198,11 @@ def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10
import sys
from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
+ from pathlib import Path
+ import shutil
+
+ # if os.path.exists('smoothed-vit'):
+ # shutil.rmtree('smoothed-vit')
os.system("git clone https://github.com/MadryLab/smoothed-vit")
sys.path.append('smoothed-vit/src/utils/')
@@ -229,7 +235,7 @@ def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
-
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class MaskProcessor(torch.nn.Module):
def __init__(self, patch_size=16):
super().__init__()
@@ -240,7 +246,7 @@ def forward(self, ones_mask):
ones_mask = ones_mask[0].unsqueeze(0) # take the first mask
ones_mask = self.avg_pool(ones_mask)[0]
ones_mask = torch.where(ones_mask.view(-1) > 0)[0] + 1
- ones_mask = torch.cat([torch.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
+ ones_mask = torch.cat([torch.IntTensor(1).fill_(0).to(device), ones_mask]).unsqueeze(0)
ones_mask = ones_mask.expand(B, -1)
return ones_mask
@@ -311,7 +317,7 @@ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
madry_vit.head = torch.nn.Linear(madry_vit.head.in_features, 10)
madry_vit.load_state_dict(art_sd)
-
+ madry_vit = madry_vit.to(device)
col_ablator = ColumnAblator(
ablation_size=4,
channels_first=True,
@@ -327,11 +333,104 @@ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
art_preds = art_model.model(ablated)
assert torch.allclose(madry_preds, art_preds, rtol=1e-04, atol=1e-04)
+@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ With the forward pass equivalence asserted, we now confirm that the certification functions in the same
+ way by doing a full end to end prediction and certification test over the data.
+ """
+ import torch
+ import os
+ import sys
+ from torch.utils.data import Dataset
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+ class ArgClass:
+ def __init__(self):
+ self.certify_patch_size = 4
+ self.certify_ablation_size = 4
+ self.certify_stride = 1
+ self.dataset = 'cifar10'
+ self.certify_out_dir = './'
+ self.exp_name = 'tests'
+ self.certify_mode = 'col'
+ self.batch_id = None
+
+ class DataSet(Dataset):
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+
+ def __len__(self):
+ return len(self.y)
+
+ def __getitem__(self, idx):
+ return self.x[idx], self.y[idx]
+
+ from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
+ import shutil
+ from torch.utils.data import DataLoader
+
+ if os.path.exists('smoothed-vit'):
+ shutil.rmtree('smoothed-vit')
+
+ if os.path.exists('tests'):
+ shutil.rmtree('tests')
+
+ os.system("git clone https://github.com/MadryLab/smoothed-vit")
+ sys.path.append('smoothed-vit/src/utils/')
+ from smoothing import certify
+
+ art_model = PyTorchDeRandomizedSmoothing(
+ model="vit_small_patch16_224",
+ loss=torch.nn.CrossEntropyLoss(),
+ optimizer=torch.optim.SGD,
+ optimizer_params={"lr": 0.01},
+ input_shape=(3, 224, 224),
+ nb_classes=10,
+ ablation_size=4,
+ load_pretrained=True,
+ replace_last_layer=True,
+ verbose=False,
+ )
+
+ class WrappedModel(torch.nn.Module):
+ def __init__(self, my_model):
+ super().__init__()
+ self.model = my_model
+
+ def forward(self, x):
+ x = self.model(x)
+ return x, 'filler_arg'
+
+ cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:100]).to(device)
+ cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:100]).to(device)
+ upsample = torch.nn.Upsample(scale_factor=224 / 32)
+ cifar_data = upsample(cifar_data)
+ dataset = DataSet(cifar_data, cifar_labels)
+ validation_loader = DataLoader(dataset, batch_size=64)
+ args = ArgClass()
+
+ model = WrappedModel(my_model=art_model.model)
+ certify(args=args,
+ model=model,
+ validation_loader=validation_loader,
+ store=None)
+ summary = torch.load('tests/m4_s4_summary.pth')
+ print('the summary is ', summary)
+ acc, cert_acc = art_model.eval_and_certify(x=cifar_data.cpu().numpy(), y=cifar_labels.cpu().numpy(), size_to_certify=4)
+ print('cert_acc ', cert_acc)
+ print('acc ', acc)
+ assert cert_acc == summary['cert_acc']
+ assert acc == summary['smooth_acc']
+
+
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
def test_equivalence(fix_get_cifar10_data):
import torch
from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class MadrylabImplementations:
"""
@@ -384,7 +483,7 @@ def forward(self, ones_mask):
ones_mask = ones_mask[0].unsqueeze(0) # take the first mask
ones_mask = self.avg_pool(ones_mask)[0]
ones_mask = torch.where(ones_mask.view(-1) > 0)[0] + 1
- ones_mask = torch.cat([torch.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
+ ones_mask = torch.cat([torch.IntTensor(1).fill_(0).to(device), ones_mask]).unsqueeze(0)
ones_mask = ones_mask.expand(B, -1)
return ones_mask
From 07842a7413089ff107bd139684a737acfb0fc405 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Mon, 17 Jul 2023 13:51:22 +0000
Subject: [PATCH 28/55] refactor to eval_and_certify. Adding block ablations
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 148 +++++++++++++++++-
.../derandomized_smoothing/pytorch.py | 77 +++++----
...{smooth_vit.py => smooth_vit_to_remove.py} | 0
3 files changed, 197 insertions(+), 28 deletions(-)
rename art/estimators/certification/derandomized_smoothing/vision_transformers/{smooth_vit.py => smooth_vit_to_remove.py} (100%)
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
index cf2bd9748c..cc3e8328ed 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -108,7 +108,7 @@ def __init__(
def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
"""
- Ablates the input colum wise
+ Ablates the input column wise
:param x: Input data
:param column_pos: The start position of the albation
@@ -188,3 +188,149 @@ def certify(self,
& (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
return cert, cert_and_correct, top_predicted_class
+
+
+
+class BlockAblator(torch.nn.Module):
+ """
+ Pure Pytorch implementation of stripe/column ablation.
+ """
+
+ def __init__(
+ self,
+ ablation_size: int,
+ channels_first: bool,
+ mode,
+ to_reshape: bool,
+ original_shape: Optional[Tuple] = None,
+ output_shape: Optional[Tuple] = None,
+ algorithm: str = 'salman2021',
+ device_type: str = "gpu",
+ ):
+ """
+ Creates a column ablator
+
+ :param ablation_size: The size of the column we will retain.
+ :param channels_first: If the input is in channels first format. Currently required to be True.
+ :param to_reshape: If the input requires reshaping.
+ :param original_shape: Original shape of the input.
+ :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
+ """
+ super().__init__()
+
+ self.ablation_size = ablation_size
+ self.channels_first = channels_first
+ self.to_reshape = to_reshape
+ self.add_ablation_mask = False
+ self.additional_channels = False
+ self.algorithm = algorithm
+ self.original_shape = original_shape
+
+ if self.algorithm == 'levine2020':
+ self.additional_channels = True
+ if self.algorithm == 'salman2021' and mode == 'ViT':
+ self.add_ablation_mask = True
+
+ if device_type == "cpu" or not torch.cuda.is_available():
+ self.device = torch.device("cpu")
+ else: # pragma: no cover
+ cuda_idx = torch.cuda.current_device()
+ self.device = torch.device(f"cuda:{cuda_idx}")
+
+ if original_shape is not None and output_shape is not None:
+ self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
+
+ def ablate(self, x: torch.Tensor, column_pos: int, row_pos: int) -> torch.Tensor:
+ """
+ Ablates the input block wise
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :param row_pos: The row start position of the albation
+ :return: The ablated input with 0s where the ablation occurred
+ """
+ k = self.ablation_size
+ # Column ablations
+ if column_pos + k > x.shape[-1]:
+ x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
+ else:
+ x[:, :, :, :column_pos] = 0.0
+ x[:, :, :, column_pos + k :] = 0.0
+
+ # Row ablations
+ if row_pos + k > x.shape[-2]:
+ x[:, :, (row_pos + k) % x.shape[-2] : row_pos, :] = 0.0
+ else:
+ x[:, :, :row_pos, :] = 0.0
+ x[:, :, row_pos + k :, :] = 0.0
+ return x
+
+ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos: Optional[int] = None) -> torch.Tensor:
+ """
+ Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
+
+ :param x: Input data
+ :param column_pos: The start position of the albation
+ :return: The albated input with an extra channel indicating the location of the ablation
+ """
+
+ if x.shape[1] != self.original_shape[0]:
+ raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
+
+ if column_pos is None:
+ column_pos = random.randint(0, x.shape[3])
+
+ if row_pos is None:
+ row_pos = random.randint(0, x.shape[2])
+
+ if isinstance(x, np.ndarray):
+ x = torch.from_numpy(x).to(self.device)
+
+ if self.add_ablation_mask:
+ ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
+ x = torch.cat([x, ones], dim=1)
+
+ if self.additional_channels:
+ x = torch.cat([x, 1.0 - x], dim=1)
+
+ x = self.ablate(x, column_pos=column_pos, row_pos=row_pos)
+
+ if self.to_reshape:
+ x = self.upsample(x)
+ return x
+
+ def certify(self,
+ pred_counts: Union[torch.Tensor, np.ndarray],
+ size_to_certify: int,
+ label: Union[torch.Tensor, np.ndarray] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ """
+ Performs certification of the predictions
+
+ :param pred_counts: The model predictions over the ablated data.
+ :param size_to_certify: The patch size we wish to check certification against
+ :param label: The ground truth labels
+ :return: A tuple consisting of: the certified predictions,
+ the predictions which were certified and also correct,
+ and the most predicted class across the different ablations on the input.
+ """
+
+ if isinstance(pred_counts, np.ndarray):
+ pred_counts = torch.from_numpy(pred_counts).to(self.device)
+
+ if isinstance(label, np.ndarray):
+ label = torch.from_numpy(label).to(self.device)
+
+ num_of_classes = pred_counts.shape[-1]
+
+ top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
+ second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
+
+ cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)**2
+
+ cert_and_correct = cert & (label == top_predicted_class)
+
+ if self.algorithm == 'levine2020':
+ tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
+ & (top_predicted_class < second_predicted_class)
+ cert = torch.logical_or(cert, tie_break_certs)
+ return cert, cert_and_correct, top_predicted_class
\ No newline at end of file
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index cf6e787bea..695980d31f 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -215,21 +215,27 @@ def __init__(
)
self.to_reshape = True
- output_shape = model.default_cfg["input_size"]
+ output_shape = model.default_cfg["input_size"]
# set the method back to avoid unexpected side effects later on should timm need to be reused.
timm.models.vision_transformer._create_vision_transformer = tmp_func
self.mode = "ViT"
else:
if isinstance(model, torch.nn.Module):
- if algorithm == 'levine2020':
- if ablation_type is None or threshold is None or logits is None:
- raise ValueError(
- "If using CNN please specify if the model returns logits, "
- " the prediction threshold, and ablation type"
- )
self.mode = "CNN"
output_shape = input_shape
+ self.to_reshape = False
+ print('We are here!')
+
+ elif algorithm == 'levine2020':
+ if ablation_type is None or threshold is None or logits is None:
+ raise ValueError(
+ "If using CNN please specify if the model returns logits, "
+ " the prediction threshold, and ablation type"
+ )
+ self.mode = "CNN"
+ output_shape = input_shape
+ self.to_reshape = False
if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
super().__init__(
@@ -255,17 +261,32 @@ def __init__(
if verbose:
logger.info(self.model)
- from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
- self.ablator = ColumnAblator(
- ablation_size=ablation_size,
- channels_first=True,
- to_reshape=self.to_reshape,
- original_shape=input_shape,
- output_shape=output_shape,
- device_type=device_type,
- algorithm=algorithm,
- mode=self.mode,
- )
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator, BlockAblator
+
+ if ablation_type == 'column':
+ self.ablator = ColumnAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=output_shape,
+ device_type=device_type,
+ algorithm=algorithm,
+ mode=self.mode,
+ )
+ elif ablation_type == 'block':
+ self.ablator = BlockAblator(
+ ablation_size=ablation_size,
+ channels_first=True,
+ to_reshape=self.to_reshape,
+ original_shape=input_shape,
+ output_shape=output_shape,
+ device_type=device_type,
+ algorithm=algorithm,
+ mode=self.mode,
+ )
+ else:
+ raise ValueError(f"ablation_type of {ablation_type} not recognized. Must be either column or block")
if self.mode is None:
raise ValueError("Model type not recognized.")
@@ -344,8 +365,7 @@ def fit( # pylint: disable=W0221
# Train for one epoch
for m in pbar:
- i_batch = np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])
- i_batch = self.ablator.forward(i_batch)
+ i_batch = self.ablator.forward(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]))
if transform is not None and self.mode == "ViT": # VIT specific
i_batch = transform(i_batch)
@@ -442,8 +462,8 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
with torch.no_grad():
for _ in tqdm(range(nb_epochs)):
for m in tqdm(range(num_batch)):
- i_batch = np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])
- i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
+ i_batch = self.ablator.forward(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]]),
+ column_pos=random.randint(0, x.shape[3]))
_ = self.model(i_batch)
def eval_and_certify(
@@ -466,8 +486,6 @@ def eval_and_certify(
:return: The accuracy and certified accuracy over the dataset
"""
import torch
- if self.mode != 'ViT': # TODO, adapt for cnn first
- raise ValueError('Accessing a ViT specific functionality while running in CNN mode')
self.model.eval()
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
@@ -493,15 +511,20 @@ def eval_and_certify(
i_batch = np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])
o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size]
- predictions = []
pred_counts = np.zeros((len(i_batch), self.nb_classes))
for pos in range(i_batch.shape[-1]):
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
# Perform prediction
model_outputs = self.model(ablated_batch)
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
- predictions.append(model_outputs)
+
+ if self.algorithm == 'levine2020':
+ if self.logits:
+ model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
+ model_outputs = model_outputs >= self.threshold
+ pred_counts += model_outputs.cpu().numpy()
+ else:
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
_, cert_and_correct, top_predicted_class = self.ablator.certify(
pred_counts, size_to_certify=size_to_certify, label=o_batch
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py
similarity index 100%
rename from art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit.py
rename to art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py
From 44b2ea78e1724773b73c6e6989e825c4386af53b Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Thu, 20 Jul 2023 12:04:47 +0100
Subject: [PATCH 29/55] adapting tests for cpu only
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing/pytorch.py | 3 +-
.../certification/test_smooth_vit.py | 60 ++++++++++++++++---
2 files changed, 52 insertions(+), 11 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 695980d31f..3e1232d669 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -77,13 +77,13 @@ def __init__(
nb_classes: int,
ablation_size: int,
algorithm: str = 'salman2021',
+ ablation_type: str = 'column',
replace_last_layer: Optional[bool] = None,
drop_tokens: bool = True,
load_pretrained: bool = True,
optimizer: Union[type, "torch.optim.Optimizer", None] = None,
optimizer_params: Optional[dict] = None,
channels_first: bool = True,
- ablation_type: Optional[str] = None,
threshold: Optional[float] = None,
logits: Optional[bool] = True,
clip_values: Optional["CLIP_VALUES_TYPE"] = None,
@@ -514,7 +514,6 @@ def eval_and_certify(
pred_counts = np.zeros((len(i_batch), self.nb_classes))
for pos in range(i_batch.shape[-1]):
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
-
# Perform prediction
model_outputs = self.model(ablated_batch)
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index 557983b80e..996d6e675b 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -198,7 +198,6 @@ def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10
import sys
from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
- from pathlib import Path
import shutil
# if os.path.exists('smoothed-vit'):
@@ -342,6 +341,8 @@ def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifa
import torch
import os
import sys
+ import types
+
from torch.utils.data import Dataset
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
@@ -371,8 +372,8 @@ def __getitem__(self, idx):
import shutil
from torch.utils.data import DataLoader
- if os.path.exists('smoothed-vit'):
- shutil.rmtree('smoothed-vit')
+ # if os.path.exists('smoothed-vit'):
+ # shutil.rmtree('smoothed-vit')
if os.path.exists('tests'):
shutil.rmtree('tests')
@@ -395,6 +396,9 @@ def __getitem__(self, idx):
)
class WrappedModel(torch.nn.Module):
+ """
+ Original implementation requires to return a tuple. We add a dummy return to satisfy this.
+ """
def __init__(self, my_model):
super().__init__()
self.model = my_model
@@ -403,12 +407,50 @@ def forward(self, x):
x = self.model(x)
return x, 'filler_arg'
- cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:100]).to(device)
- cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:100]).to(device)
+ def _cuda(self):
+ return self
+
+ class MyDataloader(Dataset):
+ """
+ Original implementation made use of .cuda() without device checks. Thus, for cpu only machines
+ (such as those run for ART CI checks) the test will fail. Here we override .cuda() for the
+ instances to just return self.
+ """
+ def __init__(self, x, y):
+ self.x = x
+ self.y = y
+ self.bsize = 2
+
+ def __len__(self):
+ return 2
+
+ def __getitem__(self, idx):
+ if idx >= 2:
+ raise IndexError
+ else:
+ x = self.x[idx*self.bsize:idx*self.bsize+self.bsize]
+ y = self.y[idx*self.bsize:idx*self.bsize+self.bsize]
+
+ x.cuda = types.MethodType(_cuda, x)
+ y.cuda = types.MethodType(_cuda, y)
+ return x, y
+
+ if torch.cuda.is_available():
+ num_to_fetch = 100
+ else:
+ num_to_fetch = 4
+
+ cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:num_to_fetch]).to(device)
+ cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:num_to_fetch]).to(device)
upsample = torch.nn.Upsample(scale_factor=224 / 32)
cifar_data = upsample(cifar_data)
- dataset = DataSet(cifar_data, cifar_labels)
- validation_loader = DataLoader(dataset, batch_size=64)
+
+ if torch.cuda.is_available():
+ dataset = DataSet(cifar_data, cifar_labels)
+ validation_loader = DataLoader(dataset, batch_size=64)
+ else:
+ validation_loader = MyDataloader(cifar_data, cifar_labels)
+
args = ArgClass()
model = WrappedModel(my_model=art_model.model)
@@ -421,8 +463,8 @@ def forward(self, x):
acc, cert_acc = art_model.eval_and_certify(x=cifar_data.cpu().numpy(), y=cifar_labels.cpu().numpy(), size_to_certify=4)
print('cert_acc ', cert_acc)
print('acc ', acc)
- assert cert_acc == summary['cert_acc']
- assert acc == summary['smooth_acc']
+ assert cert_acc == torch.tensor(summary['cert_acc'])
+ assert acc == torch.tensor(summary['smooth_acc'])
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
From 877fb3875b0bfc0b97ba1fb85e2d4767198847fe Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 2 Aug 2023 11:40:50 +0000
Subject: [PATCH 30/55] address tiebreak in kthvalue vs argmax
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 19 ++++-
.../derandomized_smoothing/pytorch.py | 44 ++++++++---
.../certification/test_smooth_vit.py | 77 +++++++++++++------
3 files changed, 102 insertions(+), 38 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
index cc3e8328ed..f3658afe05 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -176,6 +176,13 @@ def certify(self,
num_of_classes = pred_counts.shape[-1]
+ # NB! argmax and kthvalue handle ties between predicted counts differently.
+ # The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L98
+ # uses argmax for the model predictions (later called https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
+ # and kthvalue for the certified predictions.
+ # to be consistent with the original implementation we also follow this here.
+ top_predicted_class_argmax = torch.argmax(pred_counts, dim=1)
+
top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
@@ -187,8 +194,7 @@ def certify(self,
tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
& (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
- return cert, cert_and_correct, top_predicted_class
-
+ return cert, cert_and_correct, top_predicted_class_argmax
class BlockAblator(torch.nn.Module):
@@ -320,6 +326,13 @@ def certify(self,
if isinstance(label, np.ndarray):
label = torch.from_numpy(label).to(self.device)
+ # NB! argmax and kthvalue handle ties between predicted counts differently.
+ # The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L145
+ # uses argmax for the model predictions (later called https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
+ # and kthvalue for the certified predictions.
+ # to be consistent with the original implementation we also follow this here.
+ top_predicted_class_argmax = torch.argmax(pred_counts, dim=1)
+
num_of_classes = pred_counts.shape[-1]
top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
@@ -333,4 +346,4 @@ def certify(self,
tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
& (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
- return cert, cert_and_correct, top_predicted_class
\ No newline at end of file
+ return cert, cert_and_correct, top_predicted_class_argmax
\ No newline at end of file
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 3e1232d669..ea8c0a81e8 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -258,6 +258,8 @@ def __init__(
self.logits = logits
self.ablation_size = (ablation_size,)
self.algorithm = algorithm
+ self.ablation_type = ablation_type
+
if verbose:
logger.info(self.model)
@@ -512,18 +514,36 @@ def eval_and_certify(
o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size]
pred_counts = np.zeros((len(i_batch), self.nb_classes))
- for pos in range(i_batch.shape[-1]):
- ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
- # Perform prediction
- model_outputs = self.model(ablated_batch)
-
- if self.algorithm == 'levine2020':
- if self.logits:
- model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
- model_outputs = model_outputs >= self.threshold
- pred_counts += model_outputs.cpu().numpy()
- else:
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+ if self.ablation_type == 'column':
+ for pos in range(i_batch.shape[-1]):
+ ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
+ # Perform prediction
+ model_outputs = self.model(ablated_batch)
+
+ if self.algorithm == 'levine2020':
+ if self.logits:
+ model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
+ model_outputs = model_outputs >= self.threshold
+ pred_counts += model_outputs.cpu().numpy()
+ else:
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+ else:
+ for column_pos in range(i_batch.shape[-1]):
+ for row_pos in range(i_batch.shape[-2]):
+ ablated_batch = self.ablator.forward(i_batch, column_pos=column_pos, row_pos=row_pos)
+ model_outputs = self.model(ablated_batch)
+ if self.algorithm == 'levine2020':
+ if self.logits:
+ model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
+ model_outputs = model_outputs >= self.threshold
+ pred_counts += model_outputs.cpu().numpy()
+ else:
+
+ # model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
+ # model_outputs = model_outputs >= 0.3
+ # pred_counts += model_outputs.cpu().numpy()
+
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
_, cert_and_correct, top_predicted_class = self.ablator.certify(
pred_counts, size_to_certify=size_to_certify, label=o_batch
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index 996d6e675b..f4c6ac1be3 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -187,7 +187,8 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
art_warning(e)
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
-def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+@pytest.mark.parametrize("ablation", ["block", "column"])
+def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation):
"""
Assert implementations matches original with a forward pass through the same model architecture.
Note, there are some differences in architecture between the same model names.
@@ -252,7 +253,7 @@ def forward(self, ones_mask):
from custom_models import preprocess
preprocess.MaskProcessor = MaskProcessor
- from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator, BlockAblator
from custom_models.vision_transformer import vit_small_patch16_224, vit_base_patch16_224
cifar_data = fix_get_cifar10_data[0][:50]
@@ -317,23 +318,34 @@ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
madry_vit.load_state_dict(art_sd)
madry_vit = madry_vit.to(device)
- col_ablator = ColumnAblator(
- ablation_size=4,
- channels_first=True,
- to_reshape=True,
- mode='ViT',
- original_shape=(3, 32, 32),
- output_shape=(3, 224, 224),
- )
-
- ablated = col_ablator.forward(cifar_data, column_pos=10)
+ if ablation == 'column':
+ ablator = ColumnAblator(
+ ablation_size=4,
+ channels_first=True,
+ to_reshape=True,
+ mode='ViT',
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224),
+ )
+ ablated = ablator.forward(cifar_data, column_pos=10)
+ elif ablation == 'block':
+ ablator = BlockAblator(
+ ablation_size=4,
+ channels_first=True,
+ to_reshape=True,
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224),
+ mode='ViT',
+ )
+ ablated = ablator.forward(cifar_data, column_pos=10, row_pos=28)
madry_preds = madry_vit(ablated)
art_preds = art_model.model(ablated)
assert torch.allclose(madry_preds, art_preds, rtol=1e-04, atol=1e-04)
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
-def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+@pytest.mark.parametrize("ablation", ["block", "column"])
+def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation):
"""
With the forward pass equivalence asserted, we now confirm that the certification functions in the same
way by doing a full end to end prediction and certification test over the data.
@@ -354,7 +366,10 @@ def __init__(self):
self.dataset = 'cifar10'
self.certify_out_dir = './'
self.exp_name = 'tests'
- self.certify_mode = 'col'
+ if ablation == 'column':
+ self.certify_mode = 'col'
+ if ablation == 'block':
+ self.certify_mode = 'block'
self.batch_id = None
class DataSet(Dataset):
@@ -387,13 +402,17 @@ def __getitem__(self, idx):
loss=torch.nn.CrossEntropyLoss(),
optimizer=torch.optim.SGD,
optimizer_params={"lr": 0.01},
- input_shape=(3, 224, 224),
+ # input_shape=(3, 224, 224),
+ input_shape=(3, 32, 32),
nb_classes=10,
+ ablation_type=ablation,
ablation_size=4,
load_pretrained=True,
replace_last_layer=True,
verbose=False,
)
+ if os.path.isfile('vit_small_patch16_224_block.pt'):
+ art_model.model.load_state_dict(torch.load('vit_small_patch16_224_block.pt'))
class WrappedModel(torch.nn.Module):
"""
@@ -402,8 +421,11 @@ class WrappedModel(torch.nn.Module):
def __init__(self, my_model):
super().__init__()
self.model = my_model
+ self.upsample = torch.nn.Upsample(scale_factor=224 / 32)
def forward(self, x):
+ if x.shape[-1] != 224:
+ x = self.upsample(x)
x = self.model(x)
return x, 'filler_arg'
@@ -442,12 +464,12 @@ def __getitem__(self, idx):
cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:num_to_fetch]).to(device)
cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:num_to_fetch]).to(device)
- upsample = torch.nn.Upsample(scale_factor=224 / 32)
- cifar_data = upsample(cifar_data)
+ # upsample = torch.nn.Upsample(scale_factor=224 / 32)
+ # cifar_data = upsample(cifar_data)
if torch.cuda.is_available():
dataset = DataSet(cifar_data, cifar_labels)
- validation_loader = DataLoader(dataset, batch_size=64)
+ validation_loader = DataLoader(dataset, batch_size=num_to_fetch)
else:
validation_loader = MyDataloader(cifar_data, cifar_labels)
@@ -460,11 +482,20 @@ def __getitem__(self, idx):
store=None)
summary = torch.load('tests/m4_s4_summary.pth')
print('the summary is ', summary)
- acc, cert_acc = art_model.eval_and_certify(x=cifar_data.cpu().numpy(), y=cifar_labels.cpu().numpy(), size_to_certify=4)
- print('cert_acc ', cert_acc)
- print('acc ', acc)
- assert cert_acc == torch.tensor(summary['cert_acc'])
- assert acc == torch.tensor(summary['smooth_acc'])
+ acc, cert_acc = art_model.eval_and_certify(x=cifar_data.cpu().numpy(),
+ y=cifar_labels.cpu().numpy(),
+ batch_size=num_to_fetch,
+ size_to_certify=4)
+
+ assert torch.allclose(torch.tensor(cert_acc), torch.tensor(summary['cert_acc']))
+ assert torch.tensor(acc) == torch.tensor(summary['smooth_acc'])
+
+ upsample = torch.nn.Upsample(scale_factor=224 / 32)
+ cifar_data = upsample(cifar_data)
+ acc_non_ablation = art_model.model(cifar_data)
+ acc_non_ablation = art_model.get_accuracy(acc_non_ablation, cifar_labels)
+ print('acc non ablation ', acc_non_ablation)
+ assert np.allclose(acc_non_ablation.astype(float), summary['acc'])
@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
From bac6ff4c0c4c22f584a7bc48b3c2a06c49cbb4e6 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 2 Aug 2023 16:15:20 +0000
Subject: [PATCH 31/55] updating for derandomised smoothing tests
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 12 +++++++++---
.../derandomized_smoothing/pytorch.py | 16 +++++-----------
.../certification/test_derandomized_smoothing.py | 12 +++++++-----
3 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
index f3658afe05..7456cb50ff 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -131,7 +131,7 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
:return: The albated input with an extra channel indicating the location of the ablation
"""
- if x.shape[1] != self.original_shape[0]:
+ if x.shape[1] != self.original_shape[0] and self.algorithm == 'salman2021':
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
if column_pos is None:
@@ -147,6 +147,9 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
+ if x.shape[1] != self.original_shape[0] and self.additional_channels:
+ raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
+
x = self.ablate(x, column_pos=column_pos)
if self.to_reshape:
@@ -280,7 +283,7 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
:return: The albated input with an extra channel indicating the location of the ablation
"""
- if x.shape[1] != self.original_shape[0]:
+ if x.shape[1] != self.original_shape[0] and self.algorithm == 'salman2021':
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
if column_pos is None:
@@ -299,6 +302,9 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
+ if x.shape[1] != self.original_shape[0] and self.additional_channels:
+ raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
+
x = self.ablate(x, column_pos=column_pos, row_pos=row_pos)
if self.to_reshape:
@@ -343,7 +349,7 @@ def certify(self,
cert_and_correct = cert & (label == top_predicted_class)
if self.algorithm == 'levine2020':
- tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
+ tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1)**2)\
& (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
return cert, cert_and_correct, top_predicted_class_argmax
\ No newline at end of file
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index ea8c0a81e8..a1af26d147 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -435,15 +435,6 @@ def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndar
return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
- '''
- def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray:
- if self.mode == "ViT":
- return PyTorchClassifier.predict(self, x, batch_size, training_mode, **kwargs)
- if self.mode == "CNN":
- return PyTorchDeRandomizedSmoothingCNN.predict(self, x, batch_size, training_mode, **kwargs)
- raise ValueError('mode is not ViT or CNN')
- '''
-
def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
"""
Method to update the batchnorm of a neural network on small datasets when it was pre-trained
@@ -560,7 +551,9 @@ def eval_and_certify(
def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray:
import torch
- x = x.astype(ART_NUMPY_DTYPE)
+ if isinstance(x, torch.Tensor):
+ x = x.cpu().numpy()
+
outputs = PyTorchClassifier.predict(self, x=x, batch_size=batch_size, training_mode=training_mode, **kwargs)
if self.algorithm == 'levine2020':
@@ -571,7 +564,8 @@ def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: boo
)
return outputs
- def predict(self, x, batch_size, training_mode, **kwargs):
+ def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray:
+
if self._channels_first:
columns_in_data = x.shape[-1]
rows_in_data = x.shape[-2]
diff --git a/tests/estimators/certification/test_derandomized_smoothing.py b/tests/estimators/certification/test_derandomized_smoothing.py
index 1c93dfec9e..3cb2037d4a 100644
--- a/tests/estimators/certification/test_derandomized_smoothing.py
+++ b/tests/estimators/certification/test_derandomized_smoothing.py
@@ -126,7 +126,7 @@ def forward(self, x):
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(ptc.parameters(), lr=0.01, momentum=0.9)
try:
- for ablation_type in ["column", "row", "block"]:
+ for ablation_type in ["column", "block"]:
classifier = PyTorchDeRandomizedSmoothing(
model=ptc,
clip_values=(0, 1),
@@ -137,6 +137,7 @@ def forward(self, x):
ablation_type=ablation_type,
ablation_size=5,
threshold=0.3,
+ algorithm='levine2020',
logits=True,
)
classifier.fit(x=dataset[0], y=dataset[1], nb_epochs=1)
@@ -267,16 +268,17 @@ def load_weights(self):
ablation_type=ablation_type,
ablation_size=ablation_size,
threshold=0.3,
+ algorithm='levine2020',
logits=True,
)
preds = classifier.predict(np.copy(fix_get_mnist_data[0]))
- num_certified = classifier.ablator.certify(preds, size_to_certify=size_to_certify)
-
+ cert, cert_and_correct, top_predicted_class_argmax = classifier.ablator.certify(preds,
+ size_to_certify=size_to_certify)
if ablation_type == "column":
- assert np.sum(num_certified) == 52
+ assert np.sum(cert.cpu().numpy()) == 52
else:
- assert np.sum(num_certified) == 22
+ assert np.sum(cert.cpu().numpy()) == 22
except ARTTestException as e:
art_warning(e)
From d3e1d71bec182076e9fdad1eb5954b345fe73035 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Wed, 2 Aug 2023 16:17:09 +0000
Subject: [PATCH 32/55] black formatting. Removal of legacy code
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 94 ++-
.../derandomized_smoothing/pytorch.py | 56 +-
.../smooth_vit_to_remove.py | 169 ----
.../__init__.py | 4 -
.../pytorch.py | 720 ------------------
.../smooth_vit.py | 150 ----
6 files changed, 92 insertions(+), 1101 deletions(-)
delete mode 100644 art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py
delete mode 100644 art/estimators/certification/smoothed_vision_transformers_old/__init__.py
delete mode 100644 art/estimators/certification/smoothed_vision_transformers_old/pytorch.py
delete mode 100644 art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
index 7456cb50ff..68500a2514 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -70,7 +70,7 @@ def __init__(
to_reshape: bool,
original_shape: Optional[Tuple] = None,
output_shape: Optional[Tuple] = None,
- algorithm: str = 'salman2021',
+ algorithm: str = "salman2021",
device_type: str = "gpu",
):
"""
@@ -92,9 +92,9 @@ def __init__(
self.algorithm = algorithm
self.original_shape = original_shape
- if self.algorithm == 'levine2020':
+ if self.algorithm == "levine2020":
self.additional_channels = True
- if self.algorithm == 'salman2021' and mode == 'ViT':
+ if self.algorithm == "salman2021" and mode == "ViT":
self.add_ablation_mask = True
if device_type == "cpu" or not torch.cuda.is_available():
@@ -122,16 +122,21 @@ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
x[:, :, :, column_pos + k :] = 0.0
return x
- def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None) -> torch.Tensor:
+ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos=None) -> torch.Tensor:
"""
Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
:param x: Input data
:param column_pos: The start position of the albation
+ :param row_pos: Unused.
:return: The albated input with an extra channel indicating the location of the ablation
"""
- if x.shape[1] != self.original_shape[0] and self.algorithm == 'salman2021':
+ if (
+ self.original_shape is not None
+ and x.shape[1] != self.original_shape[0]
+ and self.algorithm == "salman2021"
+ ):
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
if column_pos is None:
@@ -147,19 +152,25 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
- if x.shape[1] != self.original_shape[0] and self.additional_channels:
+ if (
+ self.original_shape is not None
+ and x.shape[1] != self.original_shape[0]
+ and self.additional_channels
+ ):
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
- x = self.ablate(x, column_pos=column_pos)
+ ablated_x = self.ablate(x, column_pos=column_pos)
if self.to_reshape:
- x = self.upsample(x)
- return x
+ ablated_x = self.upsample(ablated_x)
+ return ablated_x
- def certify(self,
- pred_counts: Union[torch.Tensor, np.ndarray],
- size_to_certify: int,
- label: Union[torch.Tensor, np.ndarray] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ def certify(
+ self,
+ pred_counts: Union[torch.Tensor, np.ndarray],
+ size_to_certify: int,
+ label: Union[torch.Tensor, np.ndarray],
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Performs certification of the predictions
@@ -193,9 +204,10 @@ def certify(self,
cert_and_correct = cert & (label == top_predicted_class)
- if self.algorithm == 'levine2020':
- tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1))\
- & (top_predicted_class < second_predicted_class)
+ if self.algorithm == "levine2020":
+ tie_break_certs = (
+ (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1)
+ ) & (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
return cert, cert_and_correct, top_predicted_class_argmax
@@ -213,7 +225,7 @@ def __init__(
to_reshape: bool,
original_shape: Optional[Tuple] = None,
output_shape: Optional[Tuple] = None,
- algorithm: str = 'salman2021',
+ algorithm: str = "salman2021",
device_type: str = "gpu",
):
"""
@@ -235,9 +247,9 @@ def __init__(
self.algorithm = algorithm
self.original_shape = original_shape
- if self.algorithm == 'levine2020':
+ if self.algorithm == "levine2020":
self.additional_channels = True
- if self.algorithm == 'salman2021' and mode == 'ViT':
+ if self.algorithm == "salman2021" and mode == "ViT":
self.add_ablation_mask = True
if device_type == "cpu" or not torch.cuda.is_available():
@@ -274,7 +286,9 @@ def ablate(self, x: torch.Tensor, column_pos: int, row_pos: int) -> torch.Tensor
x[:, :, row_pos + k :, :] = 0.0
return x
- def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos: Optional[int] = None) -> torch.Tensor:
+ def forward(
+ self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos: Optional[int] = None
+ ) -> torch.Tensor:
"""
Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
@@ -282,8 +296,11 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
:param column_pos: The start position of the albation
:return: The albated input with an extra channel indicating the location of the ablation
"""
-
- if x.shape[1] != self.original_shape[0] and self.algorithm == 'salman2021':
+ if (
+ self.original_shape is not None
+ and x.shape[1] != self.original_shape[0]
+ and self.algorithm == "salman2021"
+ ):
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
if column_pos is None:
@@ -302,19 +319,25 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
- if x.shape[1] != self.original_shape[0] and self.additional_channels:
+ if (
+ self.original_shape is not None
+ and x.shape[1] != self.original_shape[0]
+ and self.additional_channels
+ ):
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
- x = self.ablate(x, column_pos=column_pos, row_pos=row_pos)
+ ablated_x = self.ablate(x, column_pos=column_pos, row_pos=row_pos)
if self.to_reshape:
- x = self.upsample(x)
- return x
+ ablated_x = self.upsample(ablated_x)
+ return ablated_x
- def certify(self,
- pred_counts: Union[torch.Tensor, np.ndarray],
- size_to_certify: int,
- label: Union[torch.Tensor, np.ndarray] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ def certify(
+ self,
+ pred_counts: Union[torch.Tensor, np.ndarray],
+ size_to_certify: int,
+ label: Union[torch.Tensor, np.ndarray],
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Performs certification of the predictions
@@ -344,12 +367,13 @@ def certify(self,
top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
second_class_counts, second_predicted_class = pred_counts.kthvalue(num_of_classes - 1, dim=1)
- cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)**2
+ cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1) ** 2
cert_and_correct = cert & (label == top_predicted_class)
- if self.algorithm == 'levine2020':
- tie_break_certs = ((top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1)**2)\
- & (top_predicted_class < second_predicted_class)
+ if self.algorithm == "levine2020":
+ tie_break_certs = (
+ (top_class_counts - second_class_counts) == 2 * (size_to_certify + self.ablation_size - 1) ** 2
+ ) & (top_predicted_class < second_predicted_class)
cert = torch.logical_or(cert, tie_break_certs)
- return cert, cert_and_correct, top_predicted_class_argmax
\ No newline at end of file
+ return cert, cert_and_correct, top_predicted_class_argmax
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index a1af26d147..66a4c5c3bc 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -76,8 +76,8 @@ def __init__(
input_shape: Tuple[int, ...],
nb_classes: int,
ablation_size: int,
- algorithm: str = 'salman2021',
- ablation_type: str = 'column',
+ algorithm: str = "salman2021",
+ ablation_type: str = "column",
replace_last_layer: Optional[bool] = None,
drop_tokens: bool = True,
load_pretrained: bool = True,
@@ -133,10 +133,11 @@ def __init__(
"""
import torch
+
print(algorithm)
self.mode = None
- if importlib.util.find_spec("timm") is not None and algorithm == 'salman2021':
+ if importlib.util.find_spec("timm") is not None and algorithm == "salman2021":
from timm.models.vision_transformer import VisionTransformer
if isinstance(model, (VisionTransformer, str)):
@@ -225,14 +226,14 @@ def __init__(
self.mode = "CNN"
output_shape = input_shape
self.to_reshape = False
- print('We are here!')
+ print("We are here!")
- elif algorithm == 'levine2020':
+ elif algorithm == "levine2020":
if ablation_type is None or threshold is None or logits is None:
raise ValueError(
"If using CNN please specify if the model returns logits, "
" the prediction threshold, and ablation type"
- )
+ )
self.mode = "CNN"
output_shape = input_shape
self.to_reshape = False
@@ -259,13 +260,18 @@ def __init__(
self.ablation_size = (ablation_size,)
self.algorithm = algorithm
self.ablation_type = ablation_type
-
if verbose:
logger.info(self.model)
- from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator, BlockAblator
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import (
+ ColumnAblator,
+ BlockAblator,
+ )
+
+ if TYPE_CHECKING:
+ self.ablator: Union[ColumnAblator, BlockAblator]
- if ablation_type == 'column':
+ if ablation_type == "column":
self.ablator = ColumnAblator(
ablation_size=ablation_size,
channels_first=True,
@@ -276,7 +282,7 @@ def __init__(
algorithm=algorithm,
mode=self.mode,
)
- elif ablation_type == 'block':
+ elif ablation_type == "block":
self.ablator = BlockAblator(
ablation_size=ablation_size,
channels_first=True,
@@ -444,8 +450,9 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
:param nb_epochs: How many times to forward pass over the input data
"""
import torch
- if self.mode != 'ViT':
- raise ValueError('Accessing a ViT specific functionality while running in CNN mode')
+
+ if self.mode != "ViT":
+ raise ValueError("Accessing a ViT specific functionality while running in CNN mode")
self.model.train()
@@ -455,8 +462,9 @@ def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -
with torch.no_grad():
for _ in tqdm(range(nb_epochs)):
for m in tqdm(range(num_batch)):
- i_batch = self.ablator.forward(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]]),
- column_pos=random.randint(0, x.shape[3]))
+ i_batch = self.ablator.forward(
+ np.copy(x[ind[m * batch_size : (m + 1) * batch_size]]), column_pos=random.randint(0, x.shape[3])
+ )
_ = self.model(i_batch)
def eval_and_certify(
@@ -505,13 +513,13 @@ def eval_and_certify(
o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size]
pred_counts = np.zeros((len(i_batch), self.nb_classes))
- if self.ablation_type == 'column':
+ if self.ablation_type == "column":
for pos in range(i_batch.shape[-1]):
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
# Perform prediction
model_outputs = self.model(ablated_batch)
- if self.algorithm == 'levine2020':
+ if self.algorithm == "levine2020":
if self.logits:
model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
model_outputs = model_outputs >= self.threshold
@@ -523,13 +531,12 @@ def eval_and_certify(
for row_pos in range(i_batch.shape[-2]):
ablated_batch = self.ablator.forward(i_batch, column_pos=column_pos, row_pos=row_pos)
model_outputs = self.model(ablated_batch)
- if self.algorithm == 'levine2020':
+ if self.algorithm == "levine2020":
if self.logits:
model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
model_outputs = model_outputs >= self.threshold
pred_counts += model_outputs.cpu().numpy()
else:
-
# model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
# model_outputs = model_outputs >= 0.3
# pred_counts += model_outputs.cpu().numpy()
@@ -548,15 +555,19 @@ def eval_and_certify(
return (accuracy / n_samples), (cert_sum / n_samples)
- def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: bool, **kwargs) -> np.ndarray:
+ def _predict_classifier(
+ self, x: Union[np.ndarray, "torch.Tensor"], batch_size: int, training_mode: bool, **kwargs
+ ) -> np.ndarray:
import torch
if isinstance(x, torch.Tensor):
- x = x.cpu().numpy()
+ x_numpy = x.cpu().numpy()
- outputs = PyTorchClassifier.predict(self, x=x, batch_size=batch_size, training_mode=training_mode, **kwargs)
+ outputs = PyTorchClassifier.predict(
+ self, x=x_numpy, batch_size=batch_size, training_mode=training_mode, **kwargs
+ )
- if self.algorithm == 'levine2020':
+ if self.algorithm == "levine2020":
if not self.logits:
return np.asarray((outputs >= self.threshold))
return np.asarray(
@@ -565,7 +576,6 @@ def _predict_classifier(self, x: np.ndarray, batch_size: int, training_mode: boo
return outputs
def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = False, **kwargs) -> np.ndarray:
-
if self._channels_first:
columns_in_data = x.shape[-1]
rows_in_data = x.shape[-2]
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py
deleted file mode 100644
index b29115af23..0000000000
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/smooth_vit_to_remove.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# MIT License
-#
-# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
-# persons to whom the Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
-# Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-"""
-This module implements Certified Patch Robustness via Smoothed Vision Transformers
-
-| Paper link Accepted version:
- https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
-
-| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
-"""
-
-from typing import Optional, Union, Tuple
-import random
-
-import numpy as np
-import torch
-
-
-class UpSampler(torch.nn.Module):
- """
- Resizes datasets to the specified size.
- Usually for upscaling datasets like CIFAR to Imagenet format
- """
-
- def __init__(self, input_size: int, final_size: int) -> None:
- """
- Creates an upsampler to make the supplied data match the pre-trained ViT format
-
- :param input_size: Size of the current input data
- :param final_size: Desired final size
- """
- super().__init__()
- self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
-
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """
- Forward pass though the upsampler.
-
- :param x: Input data
- :return: The upsampled input data
- """
- return self.upsample(x)
-
-
-class ColumnAblator(torch.nn.Module):
- """
- Pure Pytorch implementation of stripe/column ablation.
- """
-
- def __init__(
- self,
- ablation_size: int,
- channels_first: bool,
- to_reshape: bool = False,
- original_shape: Optional[Tuple] = None,
- output_shape: Optional[Tuple] = None,
- add_ablation_mask: bool = True,
- device_type: str = "gpu",
- ):
- """
- Creates a column ablator
-
- :param ablation_size: The size of the column we will retain.
- :param channels_first: If the input is in channels first format. Currently required to be True.
- :param to_reshape: If the input requires reshaping.
- :param original_shape: Original shape of the input.
- :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
- """
- super().__init__()
- self.ablation_size = ablation_size
- self.channels_first = channels_first
- self.to_reshape = to_reshape
- self.expected_input_channels = 1
- self.add_ablation_mask = add_ablation_mask
-
- if device_type == "cpu" or not torch.cuda.is_available():
- self.device = torch.device("cpu")
- else: # pragma: no cover
- cuda_idx = torch.cuda.current_device()
- self.device = torch.device(f"cuda:{cuda_idx}")
-
- if original_shape is not None and output_shape is not None:
- self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
-
- def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
- """
- Ablates the input colum wise
-
- :param x: Input data
- :param column_pos: The start position of the albation
- :return: The ablated input with 0s where the ablation occurred
- """
- k = self.ablation_size
- if column_pos + k > x.shape[-1]:
- x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
- else:
- x[:, :, :, :column_pos] = 0.0
- x[:, :, :, column_pos + k :] = 0.0
- return x
-
- def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None) -> torch.Tensor:
- """
- Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
-
- :param x: Input data
- :param column_pos: The start position of the albation
- :return: The albated input with an extra channel indicating the location of the ablation
- """
- assert x.shape[1] == self.expected_input_channels
-
- if column_pos is None:
- column_pos = random.randint(0, x.shape[3])
-
- if isinstance(x, np.ndarray):
- x = torch.from_numpy(x).to(self.device)
-
- if self.add_ablation_mask:
- ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
- x = torch.cat([x, ones], dim=1)
-
- x = self.ablate(x, column_pos=column_pos)
- if self.to_reshape:
- x = self.upsample(x)
- return x
-
- def certify(
- self, pred_counts: Union[torch.Tensor, np.ndarray], size_to_certify: int, label: Union[torch.Tensor, np.ndarray]
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
- """
- Performs certification of the predictions
-
- :param pred_counts: The model predictions over the ablated data.
- :param size_to_certify: The patch size we wish to check certification against
- :param label: The ground truth labels
- :return: A tuple consisting of: the certified predictions,
- the predictions which were certified and also correct,
- and the most predicted class across the different ablations on the input.
- """
- if isinstance(pred_counts, np.ndarray):
- pred_counts = torch.from_numpy(pred_counts).to(self.device)
-
- if isinstance(label, np.ndarray):
- label = torch.from_numpy(label).to(self.device)
-
- num_of_classes = pred_counts.shape[-1]
-
- top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
- second_class_counts, _ = pred_counts.kthvalue(num_of_classes - 1, dim=1)
-
- cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
-
- cert_and_correct = cert & (label == top_predicted_class)
-
- return cert, cert_and_correct, top_predicted_class
diff --git a/art/estimators/certification/smoothed_vision_transformers_old/__init__.py b/art/estimators/certification/smoothed_vision_transformers_old/__init__.py
deleted file mode 100644
index 5791128b5e..0000000000
--- a/art/estimators/certification/smoothed_vision_transformers_old/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-"""
-Smoothed ViT estimators.
-"""
-from art.estimators.certification.smoothed_vision_transformers.pytorch import PyTorchSmoothedViT
diff --git a/art/estimators/certification/smoothed_vision_transformers_old/pytorch.py b/art/estimators/certification/smoothed_vision_transformers_old/pytorch.py
deleted file mode 100644
index c4ba0ed050..0000000000
--- a/art/estimators/certification/smoothed_vision_transformers_old/pytorch.py
+++ /dev/null
@@ -1,720 +0,0 @@
-# MIT License
-#
-# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
-# persons to whom the Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
-# Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-"""
-This module implements Certified Patch Robustness via Smoothed Vision Transformers
-
-| Paper link Accepted version:
- https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
-
-| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
-"""
-from __future__ import absolute_import, division, print_function, unicode_literals
-
-import logging
-from typing import List, Optional, Tuple, Union, Any, TYPE_CHECKING
-import random
-
-import numpy as np
-from timm.models.vision_transformer import VisionTransformer
-import torch
-from tqdm import tqdm
-
-from art.estimators.classification.pytorch import PyTorchClassifier
-from art.estimators.certification.smoothed_vision_transformers.smooth_vit import ColumnAblator
-from art.utils import check_and_transform_label_format
-
-if TYPE_CHECKING:
- import torchvision
- from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
- from art.defences.preprocessor import Preprocessor
- from art.defences.postprocessor import Postprocessor
-
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(__name__)
-
-
-class PatchEmbed(torch.nn.Module):
- """
- Image to Patch Embedding
-
- Class adapted from the implementation in https://github.com/MadryLab/smoothed-vit
-
- Original License:
-
- MIT License
-
- Copyright (c) 2021 Madry Lab
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE
- """
-
- def __init__(self, patch_size: int = 16, in_channels: int = 1, embed_dim: int = 768):
- """
- Specifies the configuration for the convolutional layer.
-
- :param patch_size: The patch size used by the ViT.
- :param in_channels: Number of input channels.
- :param embed_dim: The embedding dimension used by the ViT.
- """
- super().__init__()
- self.patch_size = patch_size
- self.in_channels = in_channels
- self.embed_dim = embed_dim
- self.proj: Optional[torch.nn.Conv2d] = None
-
- def create(self, patch_size=None, embed_dim=None, device="cpu", **kwargs) -> None: # pylint: disable=W0613
- """
- Creates a convolution that mimics the embedding layer to be used for the ablation mask to
- track where the image was ablated.
-
- :param patch_size: The patch size used by the ViT
- :param embed_dim: The embedding dimension used by the ViT
- :param device: Which device to set the emdedding layer to.
- :param kwargs: Handles the remaining kwargs from the ViT configuration.
- """
-
- if patch_size is not None:
- self.patch_size = patch_size
- if embed_dim is not None:
- self.embed_dim = embed_dim
-
- self.proj = torch.nn.Conv2d(
- in_channels=self.in_channels,
- out_channels=self.embed_dim,
- kernel_size=self.patch_size,
- stride=self.patch_size,
- bias=False,
- )
- w_shape = self.proj.weight.shape
- self.proj.weight = torch.nn.Parameter(torch.ones(w_shape).to(device))
-
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """
- Forward pass through the embedder. We are simply tracking the positions of the ablation mask so no gradients
- are required.
-
- :param x: Input data corresponding to the ablation mask
- :return: The embedded input
- """
- if self.proj is not None:
- with torch.no_grad():
- x = self.proj(x).flatten(2).transpose(1, 2)
- return x
- raise ValueError("Projection layer not yet created.")
-
-
-class ArtViT(VisionTransformer):
- """
- Art class inheriting from VisionTransformer to control the forward pass of the ViT.
- """
-
- # Make as a class attribute to avoid being included in the
- # state dictionaries of the ViT Model.
- ablation_mask_embedder = PatchEmbed(in_channels=1)
-
- def __init__(self, **kwargs):
- """
- Create a ArtViT instance
- :param kwargs: keyword arguments required to create the mask embedder and the vision transformer class
- Must contain ...
- """
- self.to_drop_tokens = kwargs["drop_tokens"]
-
- if kwargs["device_type"] == "cpu" or not torch.cuda.is_available():
- self.device = torch.device("cpu")
- else: # pragma: no cover
- cuda_idx = torch.cuda.current_device()
- self.device = torch.device(f"cuda:{cuda_idx}")
-
- del kwargs["drop_tokens"]
- del kwargs["device_type"]
-
- super().__init__(**kwargs)
- self.ablation_mask_embedder.create(device=self.device, **kwargs)
-
- self.in_chans = kwargs["in_chans"]
- self.img_size = kwargs["img_size"]
-
- @staticmethod
- def drop_tokens(x: torch.Tensor, indexes: torch.Tensor) -> torch.Tensor:
- """
- Drops the tokens which correspond to fully masked inputs
-
- :param x: Input data
- :param indexes: positions to be ablated
- :return: Input with tokens dropped where the input was fully ablated.
- """
- x_no_cl, cls_token = x[:, 1:], x[:, 0:1]
- shape = x_no_cl.shape
-
- # reshape to temporarily remove batch
- x_no_cl = torch.reshape(x_no_cl, shape=(-1, shape[-1]))
- indexes = torch.reshape(indexes, shape=(-1,))
- indexes = indexes.nonzero(as_tuple=True)[0]
- x_no_cl = torch.index_select(x_no_cl, dim=0, index=indexes)
- x_no_cl = torch.reshape(x_no_cl, shape=(shape[0], -1, shape[-1]))
- return torch.cat((cls_token, x_no_cl), dim=1)
-
- def forward_features(self, x: torch.Tensor) -> torch.Tensor:
- """
- The forward pass of the ViT.
-
- :param x: Input data.
- :return: The input processed by the ViT backbone
- """
-
- ablated_input = False
- if x.shape[1] == self.in_chans + 1:
- ablated_input = True
-
- if ablated_input:
- x, ablation_mask = x[:, : self.in_chans], x[:, self.in_chans : self.in_chans + 1]
-
- x = self.patch_embed(x)
- x = self._pos_embed(x)
-
- if self.to_drop_tokens and ablated_input:
- ones = self.ablation_mask_embedder(ablation_mask)
- to_drop = torch.sum(ones, dim=2)
- indexes = torch.gt(torch.where(to_drop > 1, 1, 0), 0)
- x = self.drop_tokens(x, indexes)
-
- x = self.norm_pre(x)
- x = self.blocks(x)
- return self.norm(x)
-
-
-class PyTorchSmoothedViT(PyTorchClassifier):
- """
- Implementation of Certified Patch Robustness via Smoothed Vision Transformers
-
- | Paper link Accepted version:
- https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
-
- | Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
- """
-
- def __init__(
- self,
- model: Union[VisionTransformer, str],
- loss: "torch.nn.modules.loss._Loss",
- input_shape: Tuple[int, ...],
- nb_classes: int,
- ablation_size: int,
- replace_last_layer: bool,
- drop_tokens: bool = True,
- load_pretrained: bool = True,
- optimizer: Union[type, "torch.optim.Optimizer", None] = None,
- optimizer_params: Optional[dict] = None,
- channels_first: bool = True,
- clip_values: Optional["CLIP_VALUES_TYPE"] = None,
- preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
- postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
- preprocessing: "PREPROCESSING_TYPE" = (0.0, 1.0),
- device_type: str = "gpu",
- verbose: bool = True,
- ):
- """
- Create a smoothed ViT classifier.
-
- :param model: Either a string specifying which ViT architecture to load, or a vision transformer already
- created with the Pytorch Image Models (timm) library.
- :param loss: The loss function for which to compute gradients for training. The target label must be raw
- categorical, i.e. not converted to one-hot encoding.
- :param input_shape: The shape of one input instance.
- :param nb_classes: The number of classes of the model.
- :param ablation_size: The size of the data portion to retain after ablation.
- :param replace_last_layer: If to replace the last layer of the ViT with a fresh layer matching the number
- of classes for the dataset to be examined. Needed if going from the pre-trained
- imagenet models to fine-tune on a dataset like CIFAR.
- :param drop_tokens: If to drop the fully ablated tokens in the ViT
- :param load_pretrained: If to load a pretrained model matching the ViT name. Will only affect the ViT if a
- string name is passed to model rather than a ViT directly.
- :param optimizer: The optimizer used to train the classifier.
- :param channels_first: Set channels first or last.
- :param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
- maximum values allowed for features. If floats are provided, these will be used as the range of all
- features. If arrays are provided, each value will be considered the bound for a feature, thus
- the shape of clip values needs to match the total number of features.
- :param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
- :param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
- :param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
- used for data preprocessing. The first value will be subtracted from the input. The input will then
- be divided by the second one.
- :param device_type: Type of device on which the classifier is run, either `gpu` or `cpu`.
- """
- import timm
-
- # temporarily assign the original method to tmp_func
- tmp_func = timm.models.vision_transformer._create_vision_transformer
-
- # overrride with ART's ViT creation function
- timm.models.vision_transformer._create_vision_transformer = self.art_create_vision_transformer
- if isinstance(model, str):
- model = timm.create_model(
- model, pretrained=load_pretrained, drop_tokens=drop_tokens, device_type=device_type
- )
- if replace_last_layer:
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
- if isinstance(optimizer, type):
- if optimizer_params is not None:
- optimizer = optimizer(model.parameters(), **optimizer_params)
- else:
- raise ValueError("If providing an optimiser please also supply its parameters")
-
- elif isinstance(model, VisionTransformer):
- pretrained_cfg = model.pretrained_cfg
- supplied_state_dict = model.state_dict()
- supported_models = self.get_models()
- if pretrained_cfg["architecture"] not in supported_models:
- raise ValueError(
- "Architecture not supported. Use PyTorchSmoothedViT.get_models() "
- "to get the supported model architectures."
- )
- model = timm.create_model(pretrained_cfg["architecture"], drop_tokens=drop_tokens, device_type=device_type)
- model.load_state_dict(supplied_state_dict)
- if replace_last_layer:
- model.head = torch.nn.Linear(model.head.in_features, nb_classes)
-
- if optimizer is not None:
- if not isinstance(optimizer, torch.optim.Optimizer):
- raise ValueError("Optimizer error: must be a torch.optim.Optimizer instance")
-
- converted_optimizer: Union[torch.optim.Adam, torch.optim.SGD]
- opt_state_dict = optimizer.state_dict()
- if isinstance(optimizer, torch.optim.Adam):
- logging.info("Converting Adam Optimiser")
- converted_optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
- elif isinstance(optimizer, torch.optim.SGD):
- logging.info("Converting SGD Optimiser")
- converted_optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
- else:
- raise ValueError("Optimiser not supported for conversion")
- converted_optimizer.load_state_dict(opt_state_dict)
-
- self.to_reshape = False
- if not isinstance(model, ArtViT):
- raise ValueError("Vision transformer is not of ArtViT. Error occurred in ArtViT creation.")
-
- if model.default_cfg["input_size"][0] != input_shape[0]:
- raise ValueError(
- f'ViT requires {model.default_cfg["input_size"][0]} channel input,'
- f" but {input_shape[0]} channels were provided."
- )
-
- if model.default_cfg["input_size"] != input_shape:
- if verbose:
- logger.warning(
- f"ViT expects input shape of {model.default_cfg['input_size']}, "
- f"but {input_shape} specified as the input shape. "
- f"The input will be rescaled to {model.default_cfg['input_size']}"
- )
- self.to_reshape = True
-
- if optimizer is None or isinstance(optimizer, torch.optim.Optimizer):
- super().__init__(
- model=model,
- loss=loss,
- input_shape=input_shape,
- nb_classes=nb_classes,
- optimizer=optimizer,
- channels_first=channels_first,
- clip_values=clip_values,
- preprocessing_defences=preprocessing_defences,
- postprocessing_defences=postprocessing_defences,
- preprocessing=preprocessing,
- device_type=device_type,
- )
- else:
- raise ValueError("Error occurred in optimizer creation")
-
- self.ablation_size = (ablation_size,)
-
- if verbose:
- logger.info(self.model)
-
- self.ablator = ColumnAblator(
- ablation_size=ablation_size,
- channels_first=True,
- to_reshape=self.to_reshape,
- original_shape=input_shape,
- output_shape=model.default_cfg["input_size"],
- device_type=device_type,
- )
-
- # set the method back to avoid unexpected side effects later on should timm need to be reused.
- timm.models.vision_transformer._create_vision_transformer = tmp_func
-
- @classmethod
- def get_models(cls, generate_from_null: bool = False) -> List[str]:
- """
- Return the supported model names to the user.
-
- :param generate_from_null: If to re-check the creation of all the ViTs in timm from scratch.
- :return: A list of compatible models
- """
- import timm
-
- supported_models = [
- "vit_base_patch8_224",
- "vit_base_patch16_18x2_224",
- "vit_base_patch16_224",
- "vit_base_patch16_224_miil",
- "vit_base_patch16_384",
- "vit_base_patch16_clip_224",
- "vit_base_patch16_clip_384",
- "vit_base_patch16_gap_224",
- "vit_base_patch16_plus_240",
- "vit_base_patch16_rpn_224",
- "vit_base_patch16_xp_224",
- "vit_base_patch32_224",
- "vit_base_patch32_384",
- "vit_base_patch32_clip_224",
- "vit_base_patch32_clip_384",
- "vit_base_patch32_clip_448",
- "vit_base_patch32_plus_256",
- "vit_giant_patch14_224",
- "vit_giant_patch14_clip_224",
- "vit_gigantic_patch14_224",
- "vit_gigantic_patch14_clip_224",
- "vit_huge_patch14_224",
- "vit_huge_patch14_clip_224",
- "vit_huge_patch14_clip_336",
- "vit_huge_patch14_xp_224",
- "vit_large_patch14_224",
- "vit_large_patch14_clip_224",
- "vit_large_patch14_clip_336",
- "vit_large_patch14_xp_224",
- "vit_large_patch16_224",
- "vit_large_patch16_384",
- "vit_large_patch32_224",
- "vit_large_patch32_384",
- "vit_medium_patch16_gap_240",
- "vit_medium_patch16_gap_256",
- "vit_medium_patch16_gap_384",
- "vit_small_patch16_18x2_224",
- "vit_small_patch16_36x1_224",
- "vit_small_patch16_224",
- "vit_small_patch16_384",
- "vit_small_patch32_224",
- "vit_small_patch32_384",
- "vit_tiny_patch16_224",
- "vit_tiny_patch16_384",
- ]
-
- if not generate_from_null:
- return supported_models
-
- supported = []
- unsupported = []
-
- models = timm.list_models("vit_*")
- for model in models:
- logger.info(f"Testing {model} creation")
- try:
- _ = PyTorchSmoothedViT(
- model=model,
- loss=torch.nn.CrossEntropyLoss(),
- optimizer=torch.optim.SGD,
- optimizer_params={"lr": 0.01},
- input_shape=(3, 32, 32),
- nb_classes=10,
- ablation_size=4,
- load_pretrained=False,
- replace_last_layer=True,
- verbose=False,
- )
- supported.append(model)
- except (TypeError, AttributeError):
- unsupported.append(model)
-
- if supported != supported_models:
- logger.warning(
- "Difference between the generated and fixed model list. Although not necessarily "
- "an error, this may point to the timm library being updated."
- )
-
- return supported
-
- @staticmethod
- def art_create_vision_transformer(variant: str, pretrained: bool = False, **kwargs) -> ArtViT:
- """
- Creates a vision transformer using ArtViT which controls the forward pass of the model
-
- :param variant: The name of the vision transformer to load
- :param pretrained: If to load pre-trained weights
- :return: A ViT with the required methods needed for ART
- """
-
- from timm.models._builder import build_model_with_cfg
- from timm.models.vision_transformer import checkpoint_filter_fn
-
- return build_model_with_cfg(
- ArtViT,
- variant,
- pretrained,
- pretrained_filter_fn=checkpoint_filter_fn,
- **kwargs,
- )
-
- def update_batchnorm(self, x: np.ndarray, batch_size: int, nb_epochs: int = 1) -> None:
- """
- Method to update the batchnorm of a ViT on small datasets
-
- :param x: Training data.
- :param batch_size: Size of batches.
- :param nb_epochs: How many times to forward pass over the input data
- """
-
- self.model.train()
-
- ind = np.arange(len(x))
- num_batch = int(len(x) / float(batch_size))
-
- with torch.no_grad():
- for _ in tqdm(range(nb_epochs)):
- for m in tqdm(range(num_batch)):
- i_batch = torch.from_numpy(np.copy(x[ind[m * batch_size : (m + 1) * batch_size]])).to(self.device)
- i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
- _ = self.model(i_batch)
-
- def fit( # pylint: disable=W0221
- self,
- x: np.ndarray,
- y: np.ndarray,
- batch_size: int = 128,
- nb_epochs: int = 10,
- training_mode: bool = True,
- drop_last: bool = False,
- scheduler: Optional[Any] = None,
- update_batchnorm: bool = True,
- batchnorm_update_epochs: int = 1,
- transform: Optional["torchvision.transforms.transforms.Compose"] = None,
- verbose: bool = True,
- **kwargs,
- ) -> None:
- """
- Fit the classifier on the training set `(x, y)`.
-
- :param x: Training data.
- :param y: Target values (class labels) one-hot-encoded of shape (nb_samples, nb_classes) or index labels of
- shape (nb_samples,).
- :param batch_size: Size of batches.
- :param nb_epochs: Number of epochs to use for training.
- :param training_mode: `True` for model set to training mode and `'False` for model set to evaluation mode.
- :param drop_last: Set to ``True`` to drop the last incomplete batch, if the dataset size is not divisible by
- the batch size. If ``False`` and the size of dataset is not divisible by the batch size, then
- the last batch will be smaller. (default: ``False``)
- :param scheduler: Learning rate scheduler to run at the start of every epoch.
- :param update_batchnorm: if to run the training data through the model to update any batch norm statistics prior
- to training. Useful on small datasets when using pre-trained ViTs.
- :param batchnorm_update_epochs: how many times to forward pass over the training data
- to pre-adjust the batchnorm statistics.
- :param transform: Torchvision compose of relevant augmentation transformations to apply.
- :param verbose: if to display training progress bars
- :param kwargs: Dictionary of framework-specific arguments. This parameter is not currently supported for PyTorch
- and providing it takes no effect.
- """
-
- # Set model mode
- self._model.train(mode=training_mode)
-
- if self._optimizer is None: # pragma: no cover
- raise ValueError("An optimizer is needed to train the model, but none for provided.")
-
- y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
-
- # Apply preprocessing
- x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
-
- if update_batchnorm:
- self.update_batchnorm(x_preprocessed, batch_size, nb_epochs=batchnorm_update_epochs)
-
- # Check label shape
- y_preprocessed = self.reduce_labels(y_preprocessed)
-
- num_batch = len(x_preprocessed) / float(batch_size)
- if drop_last:
- num_batch = int(np.floor(num_batch))
- else:
- num_batch = int(np.ceil(num_batch))
- ind = np.arange(len(x_preprocessed))
-
- # Start training
- for _ in tqdm(range(nb_epochs)):
- # Shuffle the examples
- random.shuffle(ind)
-
- epoch_acc = []
- epoch_loss = []
- epoch_batch_sizes = []
-
- pbar = tqdm(range(num_batch), disable=not verbose)
-
- # Train for one epoch
- for m in pbar:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[ind[m * batch_size : (m + 1) * batch_size]])).to(
- self._device
- )
- if transform is not None:
- i_batch = transform(i_batch)
- i_batch = self.ablator.forward(i_batch, column_pos=random.randint(0, x.shape[3]))
-
- o_batch = torch.from_numpy(y_preprocessed[ind[m * batch_size : (m + 1) * batch_size]]).to(self._device)
-
- # Zero the parameter gradients
- self._optimizer.zero_grad()
-
- # Perform prediction
- try:
- model_outputs = self.model(i_batch)
- except ValueError as err:
- if "Expected more than 1 value per channel when training" in str(err):
- logger.exception(
- "Try dropping the last incomplete batch by setting drop_last=True in "
- "method PyTorchClassifier.fit."
- )
- raise err
-
- loss = self.loss(model_outputs, o_batch)
- acc = self.get_accuracy(preds=model_outputs, labels=o_batch)
-
- # Do training
- if self._use_amp: # pragma: no cover
- from apex import amp # pylint: disable=E0611
-
- with amp.scale_loss(loss, self._optimizer) as scaled_loss:
- scaled_loss.backward()
-
- else:
- loss.backward()
-
- self.optimizer.step()
-
- epoch_acc.append(acc)
- epoch_loss.append(loss.cpu().detach().numpy())
- epoch_batch_sizes.append(len(i_batch))
-
- if verbose:
- pbar.set_description(
- f"Loss {np.average(epoch_loss, weights=epoch_batch_sizes):.3f} "
- f"Acc {np.average(epoch_acc, weights=epoch_batch_sizes):.3f} "
- )
-
- if scheduler is not None:
- scheduler.step()
-
- def eval_and_certify(
- self,
- x: np.ndarray,
- y: np.ndarray,
- size_to_certify: int,
- batch_size: int = 128,
- verbose: bool = True,
- ) -> Tuple["torch.Tensor", "torch.Tensor"]:
- """
- Evaluates the ViT's normal and certified performance over the supplied data.
-
- :param x: Evaluation data.
- :param y: Evaluation labels.
- :param size_to_certify: The size of the patch to certify against.
- If not provided will default to the ablation size.
- :param batch_size: batch size when evaluating.
- :param verbose: If to display the progress bar
- :return: The accuracy and certified accuracy over the dataset
- """
-
- self.model.eval()
- y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
-
- # Apply preprocessing
- x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
-
- # Check label shape
- y_preprocessed = self.reduce_labels(y_preprocessed)
-
- num_batch = int(np.ceil(len(x_preprocessed) / float(batch_size)))
- pbar = tqdm(range(num_batch), disable=not verbose)
- accuracy = torch.tensor(0.0).to(self._device)
- cert_sum = torch.tensor(0.0).to(self._device)
- n_samples = 0
-
- with torch.no_grad():
- for m in pbar:
- if m == (num_batch - 1):
- i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size :])).to(self._device)
- o_batch = torch.from_numpy(y_preprocessed[m * batch_size :]).to(self._device)
- else:
- i_batch = torch.from_numpy(np.copy(x_preprocessed[m * batch_size : (m + 1) * batch_size])).to(
- self._device
- )
- o_batch = torch.from_numpy(y_preprocessed[m * batch_size : (m + 1) * batch_size]).to(self._device)
-
- predictions = []
- pred_counts = torch.zeros((len(i_batch), self.nb_classes)).to(self._device)
- for pos in range(i_batch.shape[-1]):
- ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
-
- # Perform prediction
- model_outputs = self.model(ablated_batch)
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1)] += 1
- predictions.append(model_outputs)
-
- _, cert_and_correct, top_predicted_class = self.ablator.certify(
- pred_counts, size_to_certify=size_to_certify, label=o_batch
- )
- cert_sum += torch.sum(cert_and_correct)
- accuracy += torch.sum(top_predicted_class == o_batch)
- n_samples += len(cert_and_correct)
-
- pbar.set_description(f"Normal Acc {accuracy / n_samples:.3f} " f"Cert Acc {cert_sum / n_samples:.3f}")
-
- return (accuracy / n_samples), (cert_sum / n_samples)
-
- @staticmethod
- def get_accuracy(preds: Union[np.ndarray, "torch.Tensor"], labels: Union[np.ndarray, "torch.Tensor"]) -> np.ndarray:
- """
- Helper function to get the accuracy during training.
-
- :param preds: model predictions.
- :param labels: ground truth labels (not one hot).
- :return: prediction accuracy.
- """
- if isinstance(preds, torch.Tensor):
- preds = preds.detach().cpu().numpy()
-
- if isinstance(labels, torch.Tensor):
- labels = labels.detach().cpu().numpy()
-
- return np.sum(np.argmax(preds, axis=1) == labels) / len(labels)
diff --git a/art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py b/art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py
deleted file mode 100644
index 2a0f5bc564..0000000000
--- a/art/estimators/certification/smoothed_vision_transformers_old/smooth_vit.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# MIT License
-#
-# Copyright (C) The Adversarial Robustness Toolbox (ART) Authors 2023
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
-# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
-# persons to whom the Software is furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
-# Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-"""
-This module implements Certified Patch Robustness via Smoothed Vision Transformers
-
-| Paper link Accepted version:
- https://openaccess.thecvf.com/content/CVPR2022/papers/Salman_Certified_Patch_Robustness_via_Smoothed_Vision_Transformers_CVPR_2022_paper.pdf
-
-| Paper link Arxiv version (more detail): https://arxiv.org/pdf/2110.07719.pdf
-"""
-
-from typing import Optional, Tuple
-
-import torch
-
-
-class UpSampler(torch.nn.Module):
- """
- Resizes datasets to the specified size.
- Usually for upscaling datasets like CIFAR to Imagenet format
- """
-
- def __init__(self, input_size: int, final_size: int) -> None:
- """
- Creates an upsampler to make the supplied data match the pre-trained ViT format
-
- :param input_size: Size of the current input data
- :param final_size: Desired final size
- """
- super().__init__()
- self.upsample = torch.nn.Upsample(scale_factor=final_size / input_size)
-
- def forward(self, x: torch.Tensor) -> torch.Tensor:
- """
- Forward pass though the upsampler.
-
- :param x: Input data
- :return: The upsampled input data
- """
- return self.upsample(x)
-
-
-class ColumnAblator(torch.nn.Module):
- """
- Pure Pytorch implementation of stripe/column ablation.
- """
-
- def __init__(
- self,
- ablation_size: int,
- channels_first: bool,
- to_reshape: bool = False,
- original_shape: Optional[Tuple] = None,
- output_shape: Optional[Tuple] = None,
- device_type: str = "gpu",
- ):
- """
- Creates a column ablator
-
- :param ablation_size: The size of the column we will retain.
- :param channels_first: If the input is in channels first format. Currently required to be True.
- :param to_reshape: If the input requires reshaping.
- :param original_shape: Original shape of the input.
- :param output_shape: Input shape expected by the ViT. Usually means upscaling the input to 224 x 224.
- """
- super().__init__()
- self.ablation_size = ablation_size
- self.channels_first = channels_first
- self.to_reshape = to_reshape
-
- if device_type == "cpu" or not torch.cuda.is_available():
- self.device = torch.device("cpu")
- else: # pragma: no cover
- cuda_idx = torch.cuda.current_device()
- self.device = torch.device(f"cuda:{cuda_idx}")
-
- if original_shape is not None and output_shape is not None:
- self.upsample = UpSampler(input_size=original_shape[1], final_size=output_shape[1])
-
- def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
- """
- Ablates the input colum wise
-
- :param x: Input data
- :param column_pos: The start position of the albation
- :return: The ablated input with 0s where the ablation occurred
- """
- k = self.ablation_size
- if column_pos + k > x.shape[-1]:
- x[:, :, :, (column_pos + k) % x.shape[-1] : column_pos] = 0.0
- else:
- x[:, :, :, :column_pos] = 0.0
- x[:, :, :, column_pos + k :] = 0.0
- return x
-
- def forward(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
- """
- Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
-
- :param x: Input data
- :param column_pos: The start position of the albation
- :return: The albated input with an extra channel indicating the location of the ablation
- """
- assert x.shape[1] == 3
- ones = torch.torch.ones_like(x[:, 0:1, :, :]).to(self.device)
- x = torch.cat([x, ones], dim=1)
- x = self.ablate(x, column_pos=column_pos)
- if self.to_reshape:
- x = self.upsample(x)
- return x
-
- def certify(
- self, pred_counts: torch.Tensor, size_to_certify: int, label: torch.Tensor
- ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
- """
- Performs certification of the predictions
-
- :param pred_counts: The model predictions over the ablated data.
- :param size_to_certify: The patch size we wish to check certification against
- :param label: The ground truth labels
- :return: A tuple consisting of: the certified predictions,
- the predictions which were certified and also correct,
- and the most predicted class across the different ablations on the input.
- """
-
- num_of_classes = pred_counts.shape[-1]
-
- top_class_counts, top_predicted_class = pred_counts.kthvalue(num_of_classes, dim=1)
- second_class_counts, _ = pred_counts.kthvalue(num_of_classes - 1, dim=1)
-
- cert = (top_class_counts - second_class_counts) > 2 * (size_to_certify + self.ablation_size - 1)
-
- cert_and_correct = cert & (label == top_predicted_class)
-
- return cert, cert_and_correct, top_predicted_class
From fc0f1811f393f7d93dff99a505b087dd6fd4a462 Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Thu, 3 Aug 2023 10:12:29 +0100
Subject: [PATCH 33/55] adding row mode and test
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing_pytorch.py | 56 +++---
.../derandomized_smoothing/pytorch.py | 34 ++--
.../vision_transformers/vit.py | 5 +-
.../test_derandomized_smoothing.py | 16 +-
.../certification/test_smooth_vit.py | 179 +++++++++++++-----
5 files changed, 184 insertions(+), 106 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
index 68500a2514..eecc13693b 100644
--- a/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/derandomized_smoothing_pytorch.py
@@ -68,6 +68,7 @@ def __init__(
channels_first: bool,
mode,
to_reshape: bool,
+ ablation_mode: str = "column",
original_shape: Optional[Tuple] = None,
output_shape: Optional[Tuple] = None,
algorithm: str = "salman2021",
@@ -91,6 +92,7 @@ def __init__(
self.additional_channels = False
self.algorithm = algorithm
self.original_shape = original_shape
+ self.ablation_mode = ablation_mode
if self.algorithm == "levine2020":
self.additional_channels = True
@@ -122,7 +124,9 @@ def ablate(self, x: torch.Tensor, column_pos: int) -> torch.Tensor:
x[:, :, :, column_pos + k :] = 0.0
return x
- def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos=None) -> torch.Tensor:
+ def forward(
+ self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int] = None, row_pos=None
+ ) -> torch.Tensor:
"""
Forward pass though the ablator. We insert a new channel to keep track of the ablation location.
@@ -131,17 +135,12 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
:param row_pos: Unused.
:return: The albated input with an extra channel indicating the location of the ablation
"""
+ if row_pos is not None:
+ raise ValueError("Use column_pos for a ColumnAblator. The row_pos argument is unused")
- if (
- self.original_shape is not None
- and x.shape[1] != self.original_shape[0]
- and self.algorithm == "salman2021"
- ):
+ if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.algorithm == "salman2021":
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
- if column_pos is None:
- column_pos = random.randint(0, x.shape[3])
-
if isinstance(x, np.ndarray):
x = torch.from_numpy(x).to(self.device)
@@ -152,15 +151,20 @@ def forward(self, x: Union[torch.Tensor, np.ndarray], column_pos: Optional[int]
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
- if (
- self.original_shape is not None
- and x.shape[1] != self.original_shape[0]
- and self.additional_channels
- ):
+ if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.additional_channels:
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
+ if self.ablation_mode == "row":
+ x = torch.transpose(x, 3, 2)
+
+ if column_pos is None:
+ column_pos = random.randint(0, x.shape[3])
+
ablated_x = self.ablate(x, column_pos=column_pos)
+ if self.ablation_mode == "row":
+ ablated_x = torch.transpose(ablated_x, 3, 2)
+
if self.to_reshape:
ablated_x = self.upsample(ablated_x)
return ablated_x
@@ -192,7 +196,8 @@ def certify(
# NB! argmax and kthvalue handle ties between predicted counts differently.
# The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L98
- # uses argmax for the model predictions (later called https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
+ # uses argmax for the model predictions
+ # (later called y_smoothed https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
# and kthvalue for the certified predictions.
# to be consistent with the original implementation we also follow this here.
top_predicted_class_argmax = torch.argmax(pred_counts, dim=1)
@@ -214,7 +219,7 @@ def certify(
class BlockAblator(torch.nn.Module):
"""
- Pure Pytorch implementation of stripe/column ablation.
+ Pure Pytorch implementation of block ablation.
"""
def __init__(
@@ -231,7 +236,7 @@ def __init__(
"""
Creates a column ablator
- :param ablation_size: The size of the column we will retain.
+ :param ablation_size: The size of the block we will retain.
:param channels_first: If the input is in channels first format. Currently required to be True.
:param to_reshape: If the input requires reshaping.
:param original_shape: Original shape of the input.
@@ -294,13 +299,9 @@ def forward(
:param x: Input data
:param column_pos: The start position of the albation
- :return: The albated input with an extra channel indicating the location of the ablation
+ :return: The albated input with an extra channel indicating the location of the ablation if running in
"""
- if (
- self.original_shape is not None
- and x.shape[1] != self.original_shape[0]
- and self.algorithm == "salman2021"
- ):
+ if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.algorithm == "salman2021":
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
if column_pos is None:
@@ -319,11 +320,7 @@ def forward(
if self.additional_channels:
x = torch.cat([x, 1.0 - x], dim=1)
- if (
- self.original_shape is not None
- and x.shape[1] != self.original_shape[0]
- and self.additional_channels
- ):
+ if self.original_shape is not None and x.shape[1] != self.original_shape[0] and self.additional_channels:
raise ValueError(f"Ablator expected {self.original_shape[0]} input channels. Recived shape of {x.shape[1]}")
ablated_x = self.ablate(x, column_pos=column_pos, row_pos=row_pos)
@@ -357,7 +354,8 @@ def certify(
# NB! argmax and kthvalue handle ties between predicted counts differently.
# The original implementation: https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L145
- # uses argmax for the model predictions (later called https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
+ # uses argmax for the model predictions
+ # (later called y_smoothed https://github.com/MadryLab/smoothed-vit/blob/main/src/utils/smoothing.py#L230)
# and kthvalue for the certified predictions.
# to be consistent with the original implementation we also follow this here.
top_predicted_class_argmax = torch.argmax(pred_counts, dim=1)
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index 66a4c5c3bc..e9b6a21c36 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -41,7 +41,6 @@
import numpy as np
from tqdm import tqdm
-from art.config import ART_NUMPY_DTYPE
from art.estimators.classification.pytorch import PyTorchClassifier
from art.estimators.certification.derandomized_smoothing.vision_transformers.pytorch import PyTorchSmoothedViT
from art.utils import check_and_transform_label_format
@@ -98,8 +97,8 @@ def __init__(
Create a smoothed classifier.
:param model: Either a CNN or a VIT. For a ViT supply a string specifying which ViT architecture to load from
- the ViT library, or a vision transformer already created with the Pytorch Image Models (timm) library.
- To run Levine et al. (2020) provide a regular pytorch model.
+ the ViT library, or a vision transformer already created with the
+ Pytorch Image Models (timm) library. To run Levine et al. (2020) provide a regular pytorch model.
:param loss: The loss function for which to compute gradients for training. The target label must be raw
categorical, i.e. not converted to one-hot encoding.
:param input_shape: The shape of one input instance.
@@ -134,6 +133,9 @@ def __init__(
import torch
+ if not channels_first:
+ raise ValueError("Channels must be set to first")
+
print(algorithm)
self.mode = None
@@ -209,7 +211,8 @@ def __init__(
if model.default_cfg["input_size"] != input_shape:
if verbose:
logger.warning(
- " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape. The input will be rescaled to (%i, %i, %i)",
+ " ViT expects input shape of: (%i, %i, %i) but (%i, %i, %i) specified as the input shape."
+ " The input will be rescaled to (%i, %i, %i)",
*model.default_cfg["input_size"],
*input_shape,
*model.default_cfg["input_size"],
@@ -271,10 +274,11 @@ def __init__(
if TYPE_CHECKING:
self.ablator: Union[ColumnAblator, BlockAblator]
- if ablation_type == "column":
+ if ablation_type in {"column", "row"}:
self.ablator = ColumnAblator(
ablation_size=ablation_size,
channels_first=True,
+ ablation_mode=ablation_type,
to_reshape=self.to_reshape,
original_shape=input_shape,
output_shape=output_shape,
@@ -513,35 +517,33 @@ def eval_and_certify(
o_batch = y_preprocessed[m * batch_size : (m + 1) * batch_size]
pred_counts = np.zeros((len(i_batch), self.nb_classes))
- if self.ablation_type == "column":
+ if self.ablation_type in {"column", "row"}:
for pos in range(i_batch.shape[-1]):
ablated_batch = self.ablator.forward(i_batch, column_pos=pos)
# Perform prediction
model_outputs = self.model(ablated_batch)
- if self.algorithm == "levine2020":
+ if self.algorithm == "salman2021":
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+ else:
if self.logits:
model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
model_outputs = model_outputs >= self.threshold
pred_counts += model_outputs.cpu().numpy()
- else:
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+
else:
for column_pos in range(i_batch.shape[-1]):
for row_pos in range(i_batch.shape[-2]):
ablated_batch = self.ablator.forward(i_batch, column_pos=column_pos, row_pos=row_pos)
model_outputs = self.model(ablated_batch)
- if self.algorithm == "levine2020":
+
+ if self.algorithm == "salman2021":
+ pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
+ else:
if self.logits:
model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
model_outputs = model_outputs >= self.threshold
pred_counts += model_outputs.cpu().numpy()
- else:
- # model_outputs = torch.nn.functional.softmax(model_outputs, dim=1)
- # model_outputs = model_outputs >= 0.3
- # pred_counts += model_outputs.cpu().numpy()
-
- pred_counts[np.arange(0, len(i_batch)), model_outputs.argmax(dim=-1).cpu()] += 1
_, cert_and_correct, top_predicted_class = self.ablator.certify(
pred_counts, size_to_certify=size_to_certify, label=o_batch
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
index 49168e38fa..ae8549a8e7 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/vit.py
@@ -74,8 +74,8 @@ def create(self, patch_size=None, embed_dim=None, device="cpu", **kwargs) -> Non
Creates a convolution that mimics the embedding layer to be used for the ablation mask to
track where the image was ablated.
- :param patch_size: The patch size used by the ViT
- :param embed_dim: The embedding dimension used by the ViT
+ :param patch_size: The patch size used by the ViT.
+ :param embed_dim: The embedding dimension used by the ViT.
:param device: Which device to set the emdedding layer to.
:param kwargs: Handles the remaining kwargs from the ViT configuration.
"""
@@ -123,7 +123,6 @@ def __init__(self, **kwargs):
"""
Create a ArtViT instance
:param kwargs: keyword arguments required to create the mask embedder and the vision transformer class
- Must contain ...
"""
self.to_drop_tokens = kwargs["drop_tokens"]
diff --git a/tests/estimators/certification/test_derandomized_smoothing.py b/tests/estimators/certification/test_derandomized_smoothing.py
index 3cb2037d4a..41d71ddcb4 100644
--- a/tests/estimators/certification/test_derandomized_smoothing.py
+++ b/tests/estimators/certification/test_derandomized_smoothing.py
@@ -126,7 +126,7 @@ def forward(self, x):
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(ptc.parameters(), lr=0.01, momentum=0.9)
try:
- for ablation_type in ["column", "block"]:
+ for ablation_type in ["column", "row", "block"]:
classifier = PyTorchDeRandomizedSmoothing(
model=ptc,
clip_values=(0, 1),
@@ -137,7 +137,7 @@ def forward(self, x):
ablation_type=ablation_type,
ablation_size=5,
threshold=0.3,
- algorithm='levine2020',
+ algorithm="levine2020",
logits=True,
)
classifier.fit(x=dataset[0], y=dataset[1], nb_epochs=1)
@@ -227,7 +227,6 @@ def forward(self, x):
return self.fc2(x)
def load_weights(self):
-
fpath = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "../../utils/resources/models/certification/derandomized/"
)
@@ -268,13 +267,14 @@ def load_weights(self):
ablation_type=ablation_type,
ablation_size=ablation_size,
threshold=0.3,
- algorithm='levine2020',
+ algorithm="levine2020",
logits=True,
)
preds = classifier.predict(np.copy(fix_get_mnist_data[0]))
- cert, cert_and_correct, top_predicted_class_argmax = classifier.ablator.certify(preds,
- size_to_certify=size_to_certify)
+ cert, cert_and_correct, top_predicted_class_argmax = classifier.ablator.certify(
+ preds, label=fix_get_mnist_data[1], size_to_certify=size_to_certify
+ )
if ablation_type == "column":
assert np.sum(cert.cpu().numpy()) == 52
else:
@@ -360,7 +360,9 @@ def get_weights():
x = np.squeeze(x)
x = np.expand_dims(x, axis=-1)
preds = classifier.predict(x)
- num_certified = classifier.ablator.certify(preds, size_to_certify=size_to_certify)
+ num_certified = classifier.ablator.certify(
+ preds, label=fix_get_mnist_data[1], size_to_certify=size_to_certify
+ )
if ablation_type == "column":
assert np.sum(num_certified) == 52
diff --git a/tests/estimators/certification/test_smooth_vit.py b/tests/estimators/certification/test_smooth_vit.py
index f4c6ac1be3..ee3d345139 100644
--- a/tests/estimators/certification/test_smooth_vit.py
+++ b/tests/estimators/certification/test_smooth_vit.py
@@ -58,7 +58,7 @@ def fix_get_cifar10_data():
return x_test.astype(np.float32), y_test
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+@pytest.mark.only_with_platform("pytorch")
def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
"""
Check that the ablation is being performed correctly
@@ -75,7 +75,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
ablation_size=4,
channels_first=True,
to_reshape=False, # do not upsample initially
- mode='ViT',
+ mode="ViT",
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
)
@@ -102,7 +102,7 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
ablation_size=4,
channels_first=True,
to_reshape=True,
- mode='ViT',
+ mode="ViT",
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
)
@@ -126,7 +126,77 @@ def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
art_warning(e)
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+@pytest.mark.only_with_platform("pytorch")
+def test_ablation(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
+ """
+ Check that the ablation is being performed correctly
+ """
+ import torch
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
+
+ try:
+ cifar_data = fix_get_cifar10_data[0]
+
+ col_ablator = ColumnAblator(
+ ablation_size=4,
+ channels_first=True,
+ to_reshape=False, # do not upsample initially
+ mode="ViT",
+ ablation_mode="row",
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224),
+ )
+
+ cifar_data = torch.from_numpy(cifar_data).to(device)
+ # check that the ablation functioned when in the middle of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=10)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, 0:10, :]) == 0
+ assert torch.sum(ablated[:, :, 10:14, :]) > 0
+ assert torch.sum(ablated[:, :, 14:, :]) == 0
+
+ # check that the ablation wraps when on the edge of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=30)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, 30:, :]) > 0
+ assert torch.sum(ablated[:, :, 2:30, :]) == 0
+ assert torch.sum(ablated[:, :, :2, :]) > 0
+
+ # check that upsampling works as expected
+ col_ablator = ColumnAblator(
+ ablation_size=4,
+ channels_first=True,
+ to_reshape=True,
+ mode="ViT",
+ ablation_mode="row",
+ original_shape=(3, 32, 32),
+ output_shape=(3, 224, 224),
+ )
+
+ ablated = col_ablator.forward(cifar_data, column_pos=10)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, : 10 * 7, :]) == 0
+ assert torch.sum(ablated[:, :, 10 * 7 : 14 * 7, :]) > 0
+ assert torch.sum(ablated[:, :, 14 * 7 :, :]) == 0
+
+ # check that the ablation wraps when on the edge of the image
+ ablated = col_ablator.forward(cifar_data, column_pos=30)
+
+ assert ablated.shape[1] == 4
+ assert torch.sum(ablated[:, :, 30 * 7 :, :]) > 0
+ assert torch.sum(ablated[:, :, 2 * 7 : 30 * 7, :]) == 0
+ assert torch.sum(ablated[:, :, : 2 * 7, :]) > 0
+
+ except ARTTestException as e:
+ art_warning(e)
+
+
+@pytest.mark.only_with_platform("pytorch")
def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
"""
Check that the training loop for pytorch does not result in errors
@@ -158,10 +228,10 @@ def test_pytorch_training(art_warning, fix_get_mnist_data, fix_get_cifar10_data)
art_warning(e)
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+@pytest.mark.only_with_platform("pytorch")
def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10_data):
"""
- Check that ...
+ Check that based on a given set of synthetic class predictions the certification gives the expected results.
"""
from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator
import torch
@@ -170,7 +240,7 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
col_ablator = ColumnAblator(
ablation_size=4,
channels_first=True,
- mode='ViT',
+ mode="ViT",
to_reshape=True, # do not upsample initially
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
@@ -186,7 +256,8 @@ def test_certification_function(art_warning, fix_get_mnist_data, fix_get_cifar10
except ARTTestException as e:
art_warning(e)
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+
+@pytest.mark.only_with_platform("pytorch")
@pytest.mark.parametrize("ablation", ["block", "column"])
def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation):
"""
@@ -205,7 +276,7 @@ def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10
# shutil.rmtree('smoothed-vit')
os.system("git clone https://github.com/MadryLab/smoothed-vit")
- sys.path.append('smoothed-vit/src/utils/')
+ sys.path.append("smoothed-vit/src/utils/")
# Original MaskProcessor used ones_mask = torch.cat([torch.cuda.IntTensor(1).fill_(0), ones_mask]).unsqueeze(0)
# which is not compatible with non-cuda torch as is found when running tests on github.
@@ -235,7 +306,8 @@ def test_end_to_end_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+
class MaskProcessor(torch.nn.Module):
def __init__(self, patch_size=16):
super().__init__()
@@ -251,9 +323,13 @@ def forward(self, ones_mask):
return ones_mask
from custom_models import preprocess
+
preprocess.MaskProcessor = MaskProcessor
- from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import ColumnAblator, BlockAblator
+ from art.estimators.certification.derandomized_smoothing.derandomized_smoothing_pytorch import (
+ ColumnAblator,
+ BlockAblator,
+ )
from custom_models.vision_transformer import vit_small_patch16_224, vit_base_patch16_224
cifar_data = fix_get_cifar10_data[0][:50]
@@ -318,24 +394,24 @@ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
madry_vit.load_state_dict(art_sd)
madry_vit = madry_vit.to(device)
- if ablation == 'column':
+ if ablation == "column":
ablator = ColumnAblator(
ablation_size=4,
channels_first=True,
to_reshape=True,
- mode='ViT',
+ mode="ViT",
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
)
ablated = ablator.forward(cifar_data, column_pos=10)
- elif ablation == 'block':
+ elif ablation == "block":
ablator = BlockAblator(
ablation_size=4,
channels_first=True,
to_reshape=True,
original_shape=(3, 32, 32),
output_shape=(3, 224, 224),
- mode='ViT',
+ mode="ViT",
)
ablated = ablator.forward(cifar_data, column_pos=10, row_pos=28)
@@ -343,7 +419,8 @@ def vit_base_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
art_preds = art_model.model(ablated)
assert torch.allclose(madry_preds, art_preds, rtol=1e-04, atol=1e-04)
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+
+@pytest.mark.only_with_platform("pytorch")
@pytest.mark.parametrize("ablation", ["block", "column"])
def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifar10_data, ablation):
"""
@@ -356,20 +433,21 @@ def test_certification_equivalence(art_warning, fix_get_mnist_data, fix_get_cifa
import types
from torch.utils.data import Dataset
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class ArgClass:
def __init__(self):
self.certify_patch_size = 4
self.certify_ablation_size = 4
self.certify_stride = 1
- self.dataset = 'cifar10'
- self.certify_out_dir = './'
- self.exp_name = 'tests'
- if ablation == 'column':
- self.certify_mode = 'col'
- if ablation == 'block':
- self.certify_mode = 'block'
+ self.dataset = "cifar10"
+ self.certify_out_dir = "./"
+ self.exp_name = "tests"
+ if ablation == "column":
+ self.certify_mode = "col"
+ if ablation == "block":
+ self.certify_mode = "block"
self.batch_id = None
class DataSet(Dataset):
@@ -390,11 +468,11 @@ def __getitem__(self, idx):
# if os.path.exists('smoothed-vit'):
# shutil.rmtree('smoothed-vit')
- if os.path.exists('tests'):
- shutil.rmtree('tests')
+ if os.path.exists("tests"):
+ shutil.rmtree("tests")
os.system("git clone https://github.com/MadryLab/smoothed-vit")
- sys.path.append('smoothed-vit/src/utils/')
+ sys.path.append("smoothed-vit/src/utils/")
from smoothing import certify
art_model = PyTorchDeRandomizedSmoothing(
@@ -411,13 +489,16 @@ def __getitem__(self, idx):
replace_last_layer=True,
verbose=False,
)
- if os.path.isfile('vit_small_patch16_224_block.pt'):
- art_model.model.load_state_dict(torch.load('vit_small_patch16_224_block.pt'))
+
+ # TODO: Look into incorporating this model into the CI runs rather than just local testing.
+ if os.path.isfile("vit_small_patch16_224_block.pt"):
+ art_model.model.load_state_dict(torch.load("vit_small_patch16_224_block.pt"))
class WrappedModel(torch.nn.Module):
"""
Original implementation requires to return a tuple. We add a dummy return to satisfy this.
"""
+
def __init__(self, my_model):
super().__init__()
self.model = my_model
@@ -427,7 +508,7 @@ def forward(self, x):
if x.shape[-1] != 224:
x = self.upsample(x)
x = self.model(x)
- return x, 'filler_arg'
+ return x, "filler_arg"
def _cuda(self):
return self
@@ -438,6 +519,7 @@ class MyDataloader(Dataset):
(such as those run for ART CI checks) the test will fail. Here we override .cuda() for the
instances to just return self.
"""
+
def __init__(self, x, y):
self.x = x
self.y = y
@@ -450,8 +532,8 @@ def __getitem__(self, idx):
if idx >= 2:
raise IndexError
else:
- x = self.x[idx*self.bsize:idx*self.bsize+self.bsize]
- y = self.y[idx*self.bsize:idx*self.bsize+self.bsize]
+ x = self.x[idx * self.bsize : idx * self.bsize + self.bsize]
+ y = self.y[idx * self.bsize : idx * self.bsize + self.bsize]
x.cuda = types.MethodType(_cuda, x)
y.cuda = types.MethodType(_cuda, y)
@@ -464,8 +546,6 @@ def __getitem__(self, idx):
cifar_data = torch.from_numpy(fix_get_cifar10_data[0][:num_to_fetch]).to(device)
cifar_labels = torch.from_numpy(fix_get_cifar10_data[1][:num_to_fetch]).to(device)
- # upsample = torch.nn.Upsample(scale_factor=224 / 32)
- # cifar_data = upsample(cifar_data)
if torch.cuda.is_available():
dataset = DataSet(cifar_data, cifar_labels)
@@ -476,34 +556,31 @@ def __getitem__(self, idx):
args = ArgClass()
model = WrappedModel(my_model=art_model.model)
- certify(args=args,
- model=model,
- validation_loader=validation_loader,
- store=None)
- summary = torch.load('tests/m4_s4_summary.pth')
- print('the summary is ', summary)
- acc, cert_acc = art_model.eval_and_certify(x=cifar_data.cpu().numpy(),
- y=cifar_labels.cpu().numpy(),
- batch_size=num_to_fetch,
- size_to_certify=4)
-
- assert torch.allclose(torch.tensor(cert_acc), torch.tensor(summary['cert_acc']))
- assert torch.tensor(acc) == torch.tensor(summary['smooth_acc'])
+ certify(args=args, model=model, validation_loader=validation_loader, store=None)
+ summary = torch.load("tests/m4_s4_summary.pth")
+ print("the summary is ", summary)
+ acc, cert_acc = art_model.eval_and_certify(
+ x=cifar_data.cpu().numpy(), y=cifar_labels.cpu().numpy(), batch_size=num_to_fetch, size_to_certify=4
+ )
+
+ assert torch.allclose(torch.tensor(cert_acc), torch.tensor(summary["cert_acc"]))
+ assert torch.tensor(acc) == torch.tensor(summary["smooth_acc"])
upsample = torch.nn.Upsample(scale_factor=224 / 32)
cifar_data = upsample(cifar_data)
acc_non_ablation = art_model.model(cifar_data)
acc_non_ablation = art_model.get_accuracy(acc_non_ablation, cifar_labels)
- print('acc non ablation ', acc_non_ablation)
- assert np.allclose(acc_non_ablation.astype(float), summary['acc'])
+ print("acc non ablation ", acc_non_ablation)
+ assert np.allclose(acc_non_ablation.astype(float), summary["acc"])
-@pytest.mark.skip_framework("mxnet", "non_dl_frameworks", "tensorflow1", "keras", "kerastf", "tensorflow2")
+@pytest.mark.only_with_platform("pytorch")
def test_equivalence(fix_get_cifar10_data):
import torch
from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing
from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
- device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class MadrylabImplementations:
"""
From afca1cc6041d266616513157a46a11e8e56f7a1e Mon Sep 17 00:00:00 2001
From: GiulioZizzo
Date: Thu, 3 Aug 2023 14:05:19 +0100
Subject: [PATCH 34/55] fixing bug in which tests folder was overwritten
Signed-off-by: GiulioZizzo
---
.../derandomized_smoothing/pytorch.py | 8 +-
.../vision_transformers/pytorch.py | 1 -
notebooks/smoothed_vision_transformers.ipynb | 314 +++++++++++++++---
.../certification/test_smooth_vit.py | 75 +++--
4 files changed, 319 insertions(+), 79 deletions(-)
diff --git a/art/estimators/certification/derandomized_smoothing/pytorch.py b/art/estimators/certification/derandomized_smoothing/pytorch.py
index e9b6a21c36..c38c8c8eae 100644
--- a/art/estimators/certification/derandomized_smoothing/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/pytorch.py
@@ -135,9 +135,10 @@ def __init__(
if not channels_first:
raise ValueError("Channels must be set to first")
+ logging.info("Running algorithm: %s" % algorithm)
- print(algorithm)
-
+ # Default value for output shape
+ output_shape = input_shape
self.mode = None
if importlib.util.find_spec("timm") is not None and algorithm == "salman2021":
from timm.models.vision_transformer import VisionTransformer
@@ -229,7 +230,6 @@ def __init__(
self.mode = "CNN"
output_shape = input_shape
self.to_reshape = False
- print("We are here!")
elif algorithm == "levine2020":
if ablation_type is None or threshold is None or logits is None:
@@ -238,6 +238,8 @@ def __init__(
" the prediction threshold, and ablation type"
)
self.mode = "CNN"
+ # input channels are internally doubled.
+ input_shape = (input_shape[0] * 2, input_shape[1], input_shape[2])
output_shape = input_shape
self.to_reshape = False
diff --git a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
index b577d448cb..6e79a85465 100644
--- a/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
+++ b/art/estimators/certification/derandomized_smoothing/vision_transformers/pytorch.py
@@ -30,7 +30,6 @@
if TYPE_CHECKING:
- import torch
from art.estimators.certification.derandomized_smoothing.vision_transformers.vit import PyTorchViT
logging.basicConfig(level=logging.INFO)
diff --git a/notebooks/smoothed_vision_transformers.ipynb b/notebooks/smoothed_vision_transformers.ipynb
index c38132fbe1..325cecf976 100644
--- a/notebooks/smoothed_vision_transformers.ipynb
+++ b/notebooks/smoothed_vision_transformers.ipynb
@@ -46,16 +46,7 @@
"execution_count": 1,
"id": "aeb27667",
"metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
- " from .autonotebook import tqdm as notebook_tqdm\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"import sys\n",
"import numpy as np\n",
@@ -66,7 +57,7 @@
"from matplotlib import pyplot as plt\n",
"\n",
"# The core tool is PyTorchSmoothedViT which can be imported as follows:\n",
- "from art.estimators.certification.smoothed_vision_transformers import PyTorchSmoothedViT\n",
+ "from art.estimators.certification.derandomized_smoothing import PyTorchDeRandomizedSmoothing\n",
"\n",
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')"
]
@@ -188,7 +179,7 @@
"# additional models supported.\n",
"\n",
"# We can see all the models supported by using the .get_models() method:\n",
- "PyTorchSmoothedViT.get_models()"
+ "PyTorchDeRandomizedSmoothing.get_models()"
]
},
{
@@ -198,11 +189,14 @@
"metadata": {},
"outputs": [
{
- "name": "stdout",
+ "name": "stderr",
"output_type": "stream",
"text": [
- "ViT expects input shape of (3, 224, 224), but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n",
- "ArtViT(\n",
+ "INFO:root:Running algorithm: salman2021\n",
+ "INFO:root:Converting Adam Optimiser\n",
+ "WARNING:art.estimators.certification.derandomized_smoothing.pytorch: ViT expects input shape of: (3, 224, 224) but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n",
+ "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n",
+ "INFO:art.estimators.certification.derandomized_smoothing.pytorch:PyTorchViT(\n",
" (patch_embed): PatchEmbed(\n",
" (proj): Conv2d(3, 384, kernel_size=(16, 16), stride=(16, 16))\n",
" (norm): Identity()\n",
@@ -516,14 +510,14 @@
"vit_model = timm.create_model('vit_small_patch16_224')\n",
"optimizer = torch.optim.Adam(vit_model.parameters(), lr=1e-4)\n",
"\n",
- "art_model = PyTorchSmoothedViT(model=vit_model, # Name of the model acitecture to load\n",
- " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n",
- " optimizer=optimizer, # the optimizer to use: note! this is not initialised here we just supply the class!\n",
- " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n",
- " nb_classes=10,\n",
- " ablation_size=4, # Size of the retained column\n",
- " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n",
- " load_pretrained=True) # if to load pre-trained weights for the ViT"
+ "art_model = PyTorchDeRandomizedSmoothing(model=vit_model, # Name of the model acitecture to load\n",
+ " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n",
+ " optimizer=optimizer, # the optimizer to use: note! this is not initialised here we just supply the class!\n",
+ " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n",
+ " nb_classes=10,\n",
+ " ablation_size=4, # Size of the retained column\n",
+ " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n",
+ " load_pretrained=True) # if to load pre-trained weights for the ViT"
]
},
{
@@ -533,11 +527,15 @@
"metadata": {},
"outputs": [
{
- "name": "stdout",
+ "name": "stderr",
"output_type": "stream",
"text": [
- "ViT expects input shape of (3, 224, 224), but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n",
- "ArtViT(\n",
+ "INFO:root:Running algorithm: salman2021\n",
+ "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n",
+ "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n",
+ "WARNING:art.estimators.certification.derandomized_smoothing.pytorch: ViT expects input shape of: (3, 224, 224) but (3, 32, 32) specified as the input shape. The input will be rescaled to (3, 224, 224)\n",
+ "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n",
+ "INFO:art.estimators.certification.derandomized_smoothing.pytorch:PyTorchViT(\n",
" (patch_embed): PatchEmbed(\n",
" (proj): Conv2d(3, 384, kernel_size=(16, 16), stride=(16, 16))\n",
" (norm): Identity()\n",
@@ -846,15 +844,15 @@
"source": [
"# Or we can just feed in the model name and ART will internally create the ViT.\n",
"\n",
- "art_model = PyTorchSmoothedViT(model='vit_small_patch16_224', # Name of the model acitecture to load\n",
- " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n",
- " optimizer=torch.optim.SGD, # the optimizer to use: note! this is not initialised here we just supply the class!\n",
- " optimizer_params={\"lr\": 0.01}, # the parameters to use\n",
- " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n",
- " nb_classes=10,\n",
- " ablation_size=4, # Size of the retained column\n",
- " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n",
- " load_pretrained=True) # if to load pre-trained weights for the ViT"
+ "art_model = PyTorchDeRandomizedSmoothing(model='vit_small_patch16_224', # Name of the model acitecture to load\n",
+ " loss=torch.nn.CrossEntropyLoss(), # loss function to use\n",
+ " optimizer=torch.optim.SGD, # the optimizer to use: note! this is not initialised here we just supply the class!\n",
+ " optimizer_params={\"lr\": 0.01}, # the parameters to use\n",
+ " input_shape=(3, 32, 32), # the input shape of the data: Note! that if this is a different shape to what the ViT expects it will be re-scaled\n",
+ " nb_classes=10,\n",
+ " ablation_size=4, # Size of the retained column\n",
+ " replace_last_layer=True, # Replace the last layer with a new set of weights to fine tune on new data\n",
+ " load_pretrained=True) # if to load pre-trained weights for the ViT"
]
},
{
@@ -863,7 +861,7 @@
"metadata": {},
"source": [
"Creating a PyTorchSmoothedViT instance with the above code follows many of the general ART patterns with two caveats: \n",
- "+ The optimizer would (normally) be supplied initialised into the estimator along with a pytorch model. However, here we have not yet created the model, we are just supplying the model architecture name. Hence, here we pass the class into PyTorchSmoothedViT with the keyword arguments in optimizer_params which you would normally use to initialise it.\n",
+ "+ The optimizer would (normally) be supplied initialised into the estimator along with a pytorch model. However, here we have not yet created the model, we are just supplying the model architecture name. Hence, here we pass the class into PyTorchDeRandomizedSmoothing with the keyword arguments in optimizer_params which you would normally use to initialise it.\n",
"+ The input shape will primiarily determine if the input requires upsampling. The ViT model such as the one loaded is for images of 224 x 224 resolution, thus in our case of using CIFAR data, we will be upsampling it."
]
},
@@ -883,7 +881,7 @@
{
"data": {
"text/plain": [
- ""
+ ""
]
},
"execution_count": 6,
@@ -902,7 +900,7 @@
}
],
"source": [
- "# We can see behind the scenes how PyTorchSmoothedViT processes input by passing in the first few CIFAR\n",
+ "# We can see behind the scenes how PyTorchDeRandomizedSmoothing processes input by passing in the first few CIFAR\n",
"# images into art_model.ablator.forward along with a start position to retain pixels from the original image.\n",
"original_image = np.moveaxis(x_train, [1], [3])\n",
"\n",
@@ -932,7 +930,7 @@
"metadata": {},
"outputs": [],
"source": [
- "# We can now train the model\n",
+ "# We can now train the model. This can take some time depending on hardware.\n",
"from torchvision import transforms\n",
"\n",
"scheduler = torch.optim.lr_scheduler.MultiStepLR(art_model.optimizer, milestones=[10, 20], gamma=0.1)\n",
@@ -941,8 +939,7 @@
" update_batchnorm=True, \n",
" scheduler=scheduler,\n",
" transform=transforms.Compose([transforms.RandomHorizontalFlip()]))\n",
- "torch.save(art_model.model.state_dict(), 'trained.pt')\n",
- "\n"
+ "torch.save(art_model.model.state_dict(), 'trained.pt')"
]
},
{
@@ -957,7 +954,7 @@
"name": "stderr",
"output_type": "stream",
"text": [
- "Normal Acc 0.891 Cert Acc 0.684: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 79/79 [01:09<00:00, 1.14it/s]\n"
+ "Normal Acc 0.902 Cert Acc 0.703: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████| 79/79 [02:06<00:00, 1.61s/it]\n"
]
}
],
@@ -966,6 +963,237 @@
"art_model.model.load_state_dict(torch.load('trained.pt'))\n",
"acc, cert_acc = art_model.eval_and_certify(x_test, y_test, size_to_certify=4)"
]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "a2683f52",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Files already downloaded and verified\n",
+ "Files already downloaded and verified\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "INFO:root:Running algorithm: salman2021\n",
+ "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n",
+ "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n",
+ "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n",
+ "INFO:root:Running algorithm: salman2021\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The shape of the ablated image is (10, 4, 224, 224)\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "INFO:timm.models._builder:Loading pretrained weights from Hugging Face hub (timm/vit_small_patch16_224.augreg_in21k_ft_in1k)\n",
+ "INFO:timm.models._hub:[timm/vit_small_patch16_224.augreg_in21k_ft_in1k] Safe alternative available for 'pytorch_model.bin' (as 'model.safetensors'). Loading weights using safetensors.\n",
+ "INFO:art.estimators.classification.pytorch:Inferred 9 hidden layers on PyTorch classifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The shape of the ablated image is (10, 4, 224, 224)\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "