Skip to content

Commit aaa5bce

Browse files
authored
Merge pull request #2207 from Trusted-AI/dev_1.15.0
Update to ART 1.15.0
2 parents 7691b39 + e3708eb commit aaa5bce

File tree

68 files changed

+5578
-1065
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+5578
-1065
lines changed

.github/workflows/ci-pytorch-object-detectors.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ jobs:
5050
run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_object_detector.py --framework=pytorch --durations=0
5151
- name: Run Test Action - test_pytorch_faster_rcnn
5252
run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_faster_rcnn.py --framework=pytorch --durations=0
53+
- name: Run Test Action - test_pytorch_detection_transformer
54+
run: pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_detection/test_pytorch_detection_transformer.py --framework=pytorch --durations=0
5355
- name: Upload coverage to Codecov
5456
uses: codecov/codecov-action@v3
5557
with:

art/attacks/attack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,10 @@ def __init__(self):
339339
@abc.abstractmethod
340340
def poison(
341341
self,
342-
x: np.ndarray,
342+
x: Union[np.ndarray, List[np.ndarray]],
343343
y: List[Dict[str, np.ndarray]],
344344
**kwargs,
345-
) -> Tuple[np.ndarray, List[Dict[str, np.ndarray]]]:
345+
) -> Tuple[Union[np.ndarray, List[np.ndarray]], List[Dict[str, np.ndarray]]]:
346346
"""
347347
Generate poisoning examples and return them as an array. This method should be overridden by all concrete
348348
poisoning attack implementations.

art/attacks/evasion/adversarial_patch/adversarial_patch_pytorch.py

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -575,9 +575,9 @@ def __getitem__(self, idx):
575575
img = torch.from_numpy(self.x[idx])
576576

577577
target = {}
578-
target["boxes"] = torch.from_numpy(y[idx]["boxes"])
579-
target["labels"] = torch.from_numpy(y[idx]["labels"])
580-
target["scores"] = torch.from_numpy(y[idx]["scores"])
578+
target["boxes"] = torch.from_numpy(self.y[idx]["boxes"])
579+
target["labels"] = torch.from_numpy(self.y[idx]["labels"])
580+
target["scores"] = torch.from_numpy(self.y[idx]["scores"])
581581
mask_i = torch.from_numpy(self.mask[idx])
582582

583583
return img, target, mask_i
@@ -602,19 +602,33 @@ def __getitem__(self, idx):
602602
if isinstance(target, torch.Tensor):
603603
target = target.to(self.estimator.device)
604604
else:
605-
target["boxes"] = target["boxes"].to(self.estimator.device)
606-
target["labels"] = target["labels"].to(self.estimator.device)
607-
target["scores"] = target["scores"].to(self.estimator.device)
605+
targets = []
606+
for idx in range(target["boxes"].shape[0]):
607+
targets.append(
608+
{
609+
"boxes": target["boxes"][idx].to(self.estimator.device),
610+
"labels": target["labels"][idx].to(self.estimator.device),
611+
"scores": target["scores"][idx].to(self.estimator.device),
612+
}
613+
)
614+
target = targets
608615
_ = self._train_step(images=images, target=target, mask=None)
609616
else:
610617
for images, target, mask_i in data_loader:
611618
images = images.to(self.estimator.device)
612619
if isinstance(target, torch.Tensor):
613620
target = target.to(self.estimator.device)
614621
else:
615-
target["boxes"] = target["boxes"].to(self.estimator.device)
616-
target["labels"] = target["labels"].to(self.estimator.device)
617-
target["scores"] = target["scores"].to(self.estimator.device)
622+
targets = []
623+
for idx in range(target["boxes"].shape[0]):
624+
targets.append(
625+
{
626+
"boxes": target["boxes"][idx].to(self.estimator.device),
627+
"labels": target["labels"][idx].to(self.estimator.device),
628+
"scores": target["scores"][idx].to(self.estimator.device),
629+
}
630+
)
631+
target = targets
618632
mask_i = mask_i.to(self.estimator.device)
619633
_ = self._train_step(images=images, target=target, mask=mask_i)
620634

art/attacks/evasion/auto_conjugate_gradient.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,8 @@ def __call__(self, y_true: tf.Tensor, y_pred: tf.Tensor, *args, **kwargs) -> tf.
224224
nb_classes=estimator.nb_classes,
225225
input_shape=estimator.input_shape,
226226
loss_object=_loss_object_tf,
227-
train_step=estimator._train_step,
227+
optimizer=estimator.optimizer,
228+
train_step=estimator.train_step,
228229
channels_first=estimator.channels_first,
229230
clip_values=estimator.clip_values,
230231
preprocessing_defences=estimator.preprocessing_defences,

art/attacks/evasion/auto_projected_gradient_descent.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,8 @@ def __call__(self, y_true: tf.Tensor, y_pred: tf.Tensor, *args, **kwargs) -> tf.
203203
nb_classes=estimator.nb_classes,
204204
input_shape=estimator.input_shape,
205205
loss_object=_loss_object_tf,
206-
train_step=estimator._train_step,
206+
optimizer=estimator.optimizer,
207+
train_step=estimator.train_step,
207208
channels_first=estimator.channels_first,
208209
clip_values=estimator.clip_values,
209210
preprocessing_defences=estimator.preprocessing_defences,

art/attacks/evasion/brendel_bethge.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2055,7 +2055,8 @@ def logits_difference(y_true, y_pred):
20552055
nb_classes=estimator.nb_classes,
20562056
input_shape=estimator.input_shape,
20572057
loss_object=self._loss_object,
2058-
train_step=estimator._train_step,
2058+
optimizer=estimator.optimizer,
2059+
train_step=estimator.train_step,
20592060
channels_first=estimator.channels_first,
20602061
clip_values=estimator.clip_values,
20612062
preprocessing_defences=estimator.preprocessing_defences,

art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_pytorch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ def _generate_batch(
269269
inputs = x.to(self.estimator.device)
270270
targets = targets.to(self.estimator.device)
271271
adv_x = torch.clone(inputs)
272-
momentum = torch.zeros(inputs.shape)
272+
momentum = torch.zeros(inputs.shape).to(self.estimator.device)
273273

274274
if mask is not None:
275275
mask = mask.to(self.estimator.device)

art/attacks/evasion/sign_opt.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,14 +306,14 @@ def _fine_grained_binary_search_local(
306306
lbd = initial_lbd
307307
# For targeted: we want to expand(x1.01) boundary away from targeted dataset
308308
# For untargeted, we want to slim(x0.99) the boundary toward the original dataset
309-
if (not self._is_label(x_0 + lbd * theta, target) and self.targeted) or (
310-
self._is_label(x_0 + lbd * theta, y_0) and not self.targeted
309+
if (self.targeted and not self._is_label(x_0 + lbd * theta, target)) or (
310+
not self.targeted and self._is_label(x_0 + lbd * theta, y_0)
311311
):
312312
lbd_lo = lbd
313313
lbd_hi = lbd * 1.01
314314
nquery += 1
315-
while (not self._is_label(x_0 + lbd_hi * theta, target) and self.targeted) or (
316-
self._is_label(x_0 + lbd_hi * theta, y_0) and not self.targeted
315+
while (self.targeted and not self._is_label(x_0 + lbd_hi * theta, target)) or (
316+
not self.targeted and self._is_label(x_0 + lbd_hi * theta, y_0)
317317
):
318318
lbd_hi = lbd_hi * 1.01
319319
nquery += 1
@@ -323,17 +323,17 @@ def _fine_grained_binary_search_local(
323323
lbd_hi = lbd
324324
lbd_lo = lbd * 0.99
325325
nquery += 1
326-
while (self._is_label(x_0 + lbd_lo * theta, target) and self.targeted) or (
327-
not self._is_label(x_0 + lbd_lo * theta, y_0) and not self.targeted
326+
while (self.targeted and self._is_label(x_0 + lbd_lo * theta, target)) or (
327+
not self.targeted and not self._is_label(x_0 + lbd_lo * theta, y_0)
328328
):
329329
lbd_lo = lbd_lo * 0.99
330330
nquery += 1
331331

332332
while (lbd_hi - lbd_lo) > tol:
333333
lbd_mid = (lbd_lo + lbd_hi) / 2.0
334334
nquery += 1
335-
if (self._is_label(x_0 + lbd_mid * theta, target) and self.targeted) or (
336-
not self._is_label(x_0 + lbd_mid * theta, y_0) and not self.targeted
335+
if (self.targeted and self._is_label(x_0 + lbd_mid * theta, target)) or (
336+
not self.targeted and not self._is_label(x_0 + lbd_mid * theta, y_0)
337337
):
338338
lbd_hi = lbd_mid
339339
else:

art/attacks/poisoning/bad_det/bad_det_gma.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from __future__ import absolute_import, division, print_function, unicode_literals
2424

2525
import logging
26-
from typing import Dict, List, Tuple
26+
from typing import Dict, List, Tuple, Union
2727

2828
import numpy as np
2929
from tqdm.auto import tqdm
@@ -77,36 +77,39 @@ def __init__(
7777

7878
def poison( # pylint: disable=W0221
7979
self,
80-
x: np.ndarray,
80+
x: Union[np.ndarray, List[np.ndarray]],
8181
y: List[Dict[str, np.ndarray]],
8282
**kwargs,
83-
) -> Tuple[np.ndarray, List[Dict[str, np.ndarray]]]:
83+
) -> Tuple[Union[np.ndarray, List[np.ndarray]], List[Dict[str, np.ndarray]]]:
8484
"""
8585
Generate poisoning examples by inserting the backdoor onto the input `x` and changing the classification
8686
for labels `y`.
8787
88-
:param x: Sample images of shape `NCHW` or `NHWC`.
88+
:param x: Sample images of shape `NCHW` or `NHWC` or a list of sample images of any size.
8989
:param y: True labels of type `List[Dict[np.ndarray]]`, one dictionary per input image. The keys and values
9090
of the dictionary are:
9191
9292
- boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H.
9393
- labels [N]: the labels for each image.
94-
- scores [N]: the scores or each prediction.
9594
:return: An tuple holding the `(poisoning_examples, poisoning_labels)`.
9695
"""
97-
x_ndim = len(x.shape)
96+
if isinstance(x, np.ndarray):
97+
x_ndim = len(x.shape)
98+
else:
99+
x_ndim = len(x[0].shape) + 1
98100

99101
if x_ndim != 4:
100102
raise ValueError("Unrecognized input dimension. BadDet GMA can only be applied to image data.")
101103

102-
if self.channels_first:
103-
# NCHW --> NHWC
104-
x = np.transpose(x, (0, 2, 3, 1))
105-
106-
x_poison = x.copy()
107-
y_poison: List[Dict[str, np.ndarray]] = []
104+
# copy images
105+
x_poison: Union[np.ndarray, List[np.ndarray]]
106+
if isinstance(x, np.ndarray):
107+
x_poison = x.copy()
108+
else:
109+
x_poison = [x_i.copy() for x_i in x]
108110

109111
# copy labels
112+
y_poison: List[Dict[str, np.ndarray]] = []
110113
for y_i in y:
111114
target_dict = {k: v.copy() for k, v in y_i.items()}
112115
y_poison.append(target_dict)
@@ -120,18 +123,22 @@ def poison( # pylint: disable=W0221
120123
image = x_poison[i]
121124
labels = y_poison[i]["labels"]
122125

126+
if self.channels_first:
127+
image = np.transpose(image, (1, 2, 0))
128+
123129
# insert backdoor into the image
124130
# add an additional dimension to create a batch of size 1
125131
poisoned_input, _ = self.backdoor.poison(image[np.newaxis], labels)
126-
x_poison[i] = poisoned_input[0]
132+
image = poisoned_input[0]
133+
134+
# replace the original image with the poisoned image
135+
if self.channels_first:
136+
image = np.transpose(image, (2, 0, 1))
137+
x_poison[i] = image
127138

128139
# change all labels to the target label
129140
y_poison[i]["labels"] = np.full(labels.shape, self.class_target)
130141

131-
if self.channels_first:
132-
# NHWC --> NCHW
133-
x_poison = np.transpose(x_poison, (0, 3, 1, 2))
134-
135142
return x_poison, y_poison
136143

137144
def _check_params(self) -> None:

art/attacks/poisoning/bad_det/bad_det_oda.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from __future__ import absolute_import, division, print_function, unicode_literals
2424

2525
import logging
26-
from typing import Dict, List, Tuple
26+
from typing import Dict, List, Tuple, Union
2727

2828
import numpy as np
2929
from tqdm.auto import tqdm
@@ -77,36 +77,39 @@ def __init__(
7777

7878
def poison( # pylint: disable=W0221
7979
self,
80-
x: np.ndarray,
80+
x: Union[np.ndarray, List[np.ndarray]],
8181
y: List[Dict[str, np.ndarray]],
8282
**kwargs,
83-
) -> Tuple[np.ndarray, List[Dict[str, np.ndarray]]]:
83+
) -> Tuple[Union[np.ndarray, List[np.ndarray]], List[Dict[str, np.ndarray]]]:
8484
"""
8585
Generate poisoning examples by inserting the backdoor onto the input `x` and changing the classification
8686
for labels `y`.
8787
88-
:param x: Sample images of shape `NCHW` or `NHWC`.
88+
:param x: Sample images of shape `NCHW` or `NHWC` or a list of sample images of any size.
8989
:param y: True labels of type `List[Dict[np.ndarray]]`, one dictionary per input image. The keys and values
9090
of the dictionary are:
9191
9292
- boxes [N, 4]: the boxes in [x1, y1, x2, y2] format, with 0 <= x1 < x2 <= W and 0 <= y1 < y2 <= H.
9393
- labels [N]: the labels for each image.
94-
- scores [N]: the scores or each prediction.
9594
:return: An tuple holding the `(poisoning_examples, poisoning_labels)`.
9695
"""
97-
x_ndim = len(x.shape)
96+
if isinstance(x, np.ndarray):
97+
x_ndim = len(x.shape)
98+
else:
99+
x_ndim = len(x[0].shape) + 1
98100

99101
if x_ndim != 4:
100102
raise ValueError("Unrecognized input dimension. BadDet ODA can only be applied to image data.")
101103

102-
if self.channels_first:
103-
# NCHW --> NHWC
104-
x = np.transpose(x, (0, 2, 3, 1))
105-
106-
x_poison = x.copy()
107-
y_poison: List[Dict[str, np.ndarray]] = []
104+
# copy images
105+
x_poison: Union[np.ndarray, List[np.ndarray]]
106+
if isinstance(x, np.ndarray):
107+
x_poison = x.copy()
108+
else:
109+
x_poison = [x_i.copy() for x_i in x]
108110

109111
# copy labels and find indices of the source class
112+
y_poison: List[Dict[str, np.ndarray]] = []
110113
source_indices = []
111114
for i, y_i in enumerate(y):
112115
target_dict = {k: v.copy() for k, v in y_i.items()}
@@ -121,10 +124,12 @@ def poison( # pylint: disable=W0221
121124

122125
for i in tqdm(selected_indices, desc="BadDet ODA iteration", disable=not self.verbose):
123126
image = x_poison[i]
124-
125127
boxes = y_poison[i]["boxes"]
126128
labels = y_poison[i]["labels"]
127129

130+
if self.channels_first:
131+
image = np.transpose(image, (1, 2, 0))
132+
128133
keep_indices = []
129134

130135
for j, (box, label) in enumerate(zip(boxes, labels)):
@@ -140,13 +145,14 @@ def poison( # pylint: disable=W0221
140145
else:
141146
keep_indices.append(j)
142147

148+
# replace the original image with the poisoned image
149+
if self.channels_first:
150+
image = np.transpose(image, (2, 0, 1))
151+
x_poison[i] = image
152+
143153
# remove labels for poisoned bounding boxes
144154
y_poison[i] = {k: v[keep_indices] for k, v in y_poison[i].items()}
145155

146-
if self.channels_first:
147-
# NHWC --> NCHW
148-
x_poison = np.transpose(x_poison, (0, 3, 1, 2))
149-
150156
return x_poison, y_poison
151157

152158
def _check_params(self) -> None:

0 commit comments

Comments
 (0)