adversarial-robustness-toolbox icon indicating copy to clipboard operation
adversarial-robustness-toolbox copied to clipboard

Apply existing patches without initialising attack object

Open mxrothwell opened this issue 1 year ago • 1 comments

Hi,

I am looking for some advice on how to neatly integrate applying patches as an image transformation in my ML workflow. I have created a series of adversarial patches using the following code:

from art.attacks.evasion import AdversarialPatchTensorFlowV2
from art.estimators.classification import TensorFlowV2Classifier

# create classifier using art package
classifier = TensorFlowV2Classifier(
    model=model,
    loss_object=loss_object,
    train_step=train_step,
    nb_classes=N_CLASSES,
    input_shape=IMAGE_SHAPE,
    clip_values=(0, 1),
)

# create attack object
attack = AdversarialPatchTensorFlowV2(
    classifier=classifier, max_iter=100, scale_min=0.2, scale_max=0.8
)

# generate patch
patch, mask = attack.generate(x=images, y=labels)

I then want to save the created patch for later use in other scripts. In the above script, I would use the apply patch method like so:

attack.apply_patch(images, scale=0.3)

This method is only available as part of the AdversarialPatchTensorFlowV2 class, which would mean initialising the classifier and attack objects every time I want to apply that patch to an image. This seems sub-optimal as apply_patch is a pretty independent method within that class. Am I missing something obvious here?

I am looking to neatly integrate apply_patch into a keras style augmentation workflow. My current work around is to create a class that inherits the methods from AdversarialPatchTensorFlowV2 and sets any class attributes it needs:

class ApplyAdverserialPatch(AdversarialPatchTensorFlowV2, Layer):
    """
    Implement AdversarialPatchTensorFlowV2.apply_patch as Layer transformation.

    This avoids having to create classifier (TensorFlowV2Classifier) and attack
    (AdversarialPatchTensorFlowV2) objects for each patch application.

    Note
    ====
    Apply_patch can only be run in eager mode and therefore precludes out-of-the-box GPU
    parallelisation using tensorflow. This will likely result in worse performance, when
    using the maping method of execution. See for more details:
    https://www.tensorflow.org/api_docs/python/tf/data/Dataset#map

    Examples
    --------
    ```
    # for np.ndarrays or tf.Tensors of shape (N, H, W, C)
    transform = ApplyAdverserialPatch(image_shape=(H, W, C), patch)
    transformed_image = transform(images)

    # add augmentation to sequential model
    preproccessing_step = keras.Sequential(
        [
            keras.layers.RandomRotation(factor=0.02),
            ApplyAdverserialPatch(image_shape, patch),
        ]
    )
    transformed_image = preproccessing_step(images)

    # map via tf.Data.Dataset (this method requirea tf.py_function)
    dataset = (
        tf.data.Dataset.from_tensor_slices(images)
        .batch(5)
        .map(lambda x: tf.py_function(func=transform, inp=[x], Tout=tf.float32),
        num_parallel_calls=tf.data.AUTOTUNE,  # allows multi-threading (for speed)
        deterministic=False  # allows returned order to be different (for speed)
    )
    ```
    """

    def __init__(self, image_shape: tuple[int], patch: np.ndarray | tf.Tensor):
        """Init specific attributes and methods from inherited classes."""
        # init Layer class so that transformation can be added to keras.Sequential
        Layer.__init__(self, dynamic=True)

        # set attributes needed for each call execution
        self.patch = patch
        self.image_shape = image_shape
        self.patch_shape = image_shape

        # set maximum rotation to AdversarialPatchTensorFlowV2 default
        self.rotation_max = 22.5

        # set index for height and width for patch
        self.i_h_patch = 0
        self.i_w_patch = 1

        # set index for height and width for image
        self.nb_dims = len(image_shape)
        if self.nb_dims == 3:
            self.i_h = 0
            self.i_w = 1
        elif self.nb_dims == 4:
            self.i_h = 1
            self.i_w = 2

    def call(self, image: np.ndarray | tf.Tensor) -> tf.Tensor:
        """Layer class `call` method applies transformation to inputs."""
        return self.apply_patch(image, scale=0.3, patch_external=self.patch)

This works fine (see doc strings for example usages), but my class will be very susceptible to updates in the art packages going forward. Alternatively, I could strip the apply_patch into my own function. What is the most robust solution to this problem?

Thanks in advance for any help on this - much appreciated!

mxrothwell avatar Dec 11 '23 10:12 mxrothwell

Hi @mxrothwell Thank you very much for interest in ART! I think this a great question and we should keep this issue as a feature request. At the moment ART provides a function in art.utils.insert_transformed_patch to insert patches into a plane defines by 4 coordinates.

beat-buesser avatar Jan 09 '24 12:01 beat-buesser