Skip to content

Commit 1b2980f

Browse files
authored
Merge pull request #863 from Trusted-AI/development_issue_854
Add patch reset option to Adversarial Patch attacks
2 parents 1966edf + e80d74b commit 1b2980f

File tree

3 files changed

+61
-12
lines changed

3 files changed

+61
-12
lines changed

art/attacks/evasion/adversarial_patch/adversarial_patch.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,14 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
127127
128128
:param x: An array with the original input images of shape NHWC or NCHW or input videos of shape NFHWC or NFCHW.
129129
:param y: An array with the original true labels.
130-
:param mask: An boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
130+
:param mask: A boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
131131
(N, H, W) without their channel dimensions. Any features for which the mask is True can be the
132132
center location of the patch during sampling.
133133
:type mask: `np.ndarray`
134+
:param reset_patch: If `True` reset patch to initial values of mean of minimal and maximal clip value, else if
135+
`False` (default) restart from previous patch values created by previous call to `generate`
136+
or mean of minimal and maximal clip value if first call to `generate`.
137+
:type reset_patch: bool
134138
:return: An array with adversarial patch and an array of the patch mask.
135139
"""
136140
logger.info("Creating adversarial patch.")
@@ -157,6 +161,14 @@ def apply_patch(self, x: np.ndarray, scale: float, patch_external: Optional[np.n
157161
"""
158162
return self._attack.apply_patch(x, scale, patch_external=patch_external)
159163

164+
def reset_patch(self, initial_patch_value: Optional[Union[float, np.ndarray]]) -> None:
165+
"""
166+
Reset the adversarial patch.
167+
168+
:param initial_patch_value: Patch value to use for resetting the patch.
169+
"""
170+
self._attack.reset_patch(initial_patch_value=initial_patch_value)
171+
160172
def set_params(self, **kwargs) -> None:
161173
super().set_params(**kwargs)
162174
self._attack.set_params(**kwargs)

art/attacks/evasion/adversarial_patch/adversarial_patch_numpy.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,26 @@ def __init__(
141141
else:
142142
self.patch_shape = (smallest_image_edge, smallest_image_edge, nb_channels)
143143

144-
mean_value = (self.estimator.clip_values[1] - self.estimator.clip_values[0]) / 2.0 + self.estimator.clip_values[
145-
0
146-
]
147-
self.patch = np.ones(shape=self.patch_shape).astype(np.float32) * mean_value
144+
self.patch = None
145+
self.mean_value = (
146+
self.estimator.clip_values[1] - self.estimator.clip_values[0]
147+
) / 2.0 + self.estimator.clip_values[0]
148+
self.reset_patch(self.mean_value)
148149

149150
def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> Tuple[np.ndarray, np.ndarray]:
150151
"""
151152
Generate an adversarial patch and return the patch and its mask in arrays.
152153
153154
:param x: An array with the original input images of shape NHWC or NCHW or input videos of shape NFHWC or NFCHW.
154155
:param y: An array with the original true labels.
155-
:param mask: An boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
156+
:param mask: A boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
156157
(N, H, W) without their channel dimensions. Any features for which the mask is True can be the
157158
center location of the patch during sampling.
158159
:type mask: `np.ndarray`
160+
:param reset_patch: If `True` reset patch to initial values of mean of minimal and maximal clip value, else if
161+
`False` (default) restart from previous patch values created by previous call to `generate`
162+
or mean of minimal and maximal clip value if first call to `generate`.
163+
:type reset_patch: bool
159164
:return: An array with adversarial patch and an array of the patch mask.
160165
"""
161166
logger.info("Creating adversarial patch.")
@@ -190,6 +195,9 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
190195
"dimensions."
191196
)
192197

198+
if kwargs.get("reset_patch"):
199+
self._reset_patch()
200+
193201
y_target = check_and_transform_label_format(labels=y, nb_classes=self.estimator.nb_classes)
194202

195203
for _ in trange(self.max_iter, desc="Adversarial Patch Numpy", disable=not self.verbose):
@@ -555,3 +563,18 @@ def _reverse_transformation(self, gradients: np.ndarray, patch_mask_transformed,
555563
gradients = self._rotate(gradients, -angle)
556564

557565
return gradients
566+
567+
def reset_patch(self, initial_patch_value: Optional[Union[float, np.ndarray]]) -> None:
568+
"""
569+
Reset the adversarial patch.
570+
571+
:param initial_patch_value: Patch value to use for resetting the patch.
572+
"""
573+
if initial_patch_value is None:
574+
self.patch = np.ones(shape=self.patch_shape).astype(np.float32) * self.mean_value
575+
elif isinstance(initial_patch_value, float):
576+
self.patch = np.ones(shape=self.patch_shape).astype(np.float32) * initial_patch_value
577+
elif self.patch.shape == initial_patch_value.shape:
578+
self.patch = initial_patch_value
579+
else:
580+
raise ValueError("Unexpected value for initial_patch_value.")

art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,9 @@ def __init__(
134134
mean_value = (self.estimator.clip_values[1] - self.estimator.clip_values[0]) / 2.0 + self.estimator.clip_values[
135135
0
136136
]
137-
initial_value = np.ones(self.patch_shape) * mean_value
137+
self._initial_value = np.ones(self.patch_shape) * mean_value
138138
self._patch = tf.Variable(
139-
initial_value=initial_value,
139+
initial_value=self._initial_value,
140140
shape=self.patch_shape,
141141
dtype=tf.float32,
142142
constraint=lambda x: tf.clip_by_value(x, self.estimator.clip_values[0], self.estimator.clip_values[1]),
@@ -365,10 +365,14 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
365365
366366
:param x: An array with the original input images of shape NHWC or input videos of shape NFHWC.
367367
:param y: An array with the original true labels.
368-
:param mask: An boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
368+
:param mask: A boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
369369
(N, H, W) without their channel dimensions. Any features for which the mask is True can be the
370370
center location of the patch during sampling.
371371
:type mask: `np.ndarray`
372+
:param reset_patch: If `True` reset patch to initial values of mean of minimal and maximal clip value, else if
373+
`False` (default) restart from previous patch values created by previous call to `generate`
374+
or mean of minimal and maximal clip value if first call to `generate`.
375+
:type reset_patch: bool
372376
:return: An array with adversarial patch and an array of the patch mask.
373377
"""
374378
import tensorflow as tf # lgtm [py/repeated-import]
@@ -393,6 +397,9 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
393397
if mask is not None and mask.shape[0] == 1:
394398
mask = np.repeat(mask, repeats=x.shape[0], axis=0)
395399

400+
if kwargs.get("reset_patch"):
401+
self.reset_patch(initial_patch_value=self._initial_value)
402+
396403
y = check_and_transform_label_format(labels=y, nb_classes=self.estimator.nb_classes)
397404

398405
if mask is None:
@@ -460,11 +467,18 @@ def apply_patch(
460467
patch = patch_external if patch_external is not None else self._patch
461468
return self._random_overlay(images=x, patch=patch, scale=scale, mask=mask).numpy()
462469

463-
def reset_patch(self, initial_patch_value: np.ndarray) -> None:
470+
def reset_patch(self, initial_patch_value: Optional[Union[float, np.ndarray]] = None) -> None:
464471
"""
465472
Reset the adversarial patch.
466473
467474
:param initial_patch_value: Patch value to use for resetting the patch.
468475
"""
469-
initial_value = np.ones(self.patch_shape) * initial_patch_value
470-
self._patch.assign(np.ones(shape=self.patch_shape) * initial_value)
476+
if initial_patch_value is None:
477+
self._patch.assign(self._initial_value)
478+
elif isinstance(initial_patch_value, float):
479+
initial_value = np.ones(self.patch_shape) * initial_patch_value
480+
self._patch.assign(initial_value)
481+
elif self._patch.shape == initial_patch_value.shape:
482+
self._patch.assign(initial_patch_value)
483+
else:
484+
raise ValueError("Unexpected value for initial_patch_value.")

0 commit comments

Comments
 (0)