Skip to content

Commit 953414e

Browse files
authored
Merge branch 'dev_1.9.0' into dev_jax_estimator
2 parents 6d37b53 + c114a49 commit 953414e

21 files changed

+1502
-200
lines changed

.github/actions/goturn/run.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ exit_code=0
44

55
pytest --cov-report=xml --cov=art --cov-append -q -vv tests/estimators/object_tracking/test_pytorch_goturn.py --framework=pytorch --durations=0
66
if [[ $? -ne 0 ]]; then exit_code=1; echo "Failed estimators/object_tracking/test_pytorch_goturn tests"; fi
7+
pytest --cov-report=xml --cov=art --cov-append -q -vv tests/attacks/evasion/test_adversarial_texture_pytorch.py --framework=pytorch --durations=0
8+
if [[ $? -ne 0 ]]; then exit_code=1; echo "Failed attacks/evasion/test_adversarial_texture_pytorch tests"; fi
79

810
exit ${exit_code}

.github/workflows/ci-style-checks.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ jobs:
3838
python -m pip install --upgrade pip setuptools wheel
3939
pip install -q pylint==2.7.4 mypy==0.812 pycodestyle==2.7.0 black==20.8b1
4040
pip install -q -r requirements_test.txt
41+
pip install pluggy==0.13.1
4142
pip install tensorflow==2.4.1
4243
pip install keras==2.4.3
4344
pip list

art/attacks/attack.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import numpy as np
2828

2929
from art.exceptions import EstimatorError
30+
from art.summary_writer import SummaryWriter, SummaryWriterDefault
3031

3132
if TYPE_CHECKING:
3233
from art.utils import CLASSIFIER_TYPE
@@ -100,15 +101,17 @@ class Attack(abc.ABC):
100101
def __init__(
101102
self,
102103
estimator,
103-
tensor_board: Union[str, bool] = False,
104+
summary_writer: Union[str, bool, SummaryWriter] = False,
104105
):
105106
"""
106107
:param estimator: An estimator.
107-
:param tensor_board: Activate summary writer for TensorBoard: Default is `False` and deactivated summary writer.
108-
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory. Provide `path` in type
109-
`str` to save in path/CURRENT_DATETIME_HOSTNAME.
110-
Use hierarchical folder structure to compare between runs easily. e.g. pass in ‘runs/exp1’,
111-
‘runs/exp2’, etc. for each new experiment to compare across them.
108+
:param summary_writer: Activate summary writer for TensorBoard.
109+
Default is `False` and deactivated summary writer.
110+
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory.
111+
If of type `str` save in path.
112+
If of type `SummaryWriter` apply provided custom summary writer.
113+
Use hierarchical folder structure to compare between runs easily. e.g. pass in
114+
‘runs/exp1’, ‘runs/exp2’, etc. for each new experiment to compare across them.
112115
"""
113116
super().__init__()
114117

@@ -119,17 +122,13 @@ def __init__(
119122
raise EstimatorError(self.__class__, self.estimator_requirements, estimator)
120123

121124
self._estimator = estimator
122-
self.tensor_board = tensor_board
125+
self._summary_writer_arg = summary_writer
126+
self._summary_writer: Optional[SummaryWriter] = None
123127

124-
if tensor_board: # pragma: no cover
125-
from tensorboardX import SummaryWriter
126-
127-
if isinstance(tensor_board, str):
128-
self.summary_writer = SummaryWriter(tensor_board)
129-
else:
130-
self.summary_writer = SummaryWriter()
131-
else:
132-
self.summary_writer = None
128+
if isinstance(summary_writer, SummaryWriter): # pragma: no cover
129+
self._summary_writer = summary_writer
130+
elif summary_writer:
131+
self._summary_writer = SummaryWriterDefault(summary_writer)
133132

134133
Attack._check_params(self)
135134

@@ -138,6 +137,11 @@ def estimator(self):
138137
"""The estimator."""
139138
return self._estimator
140139

140+
@property
141+
def summary_writer(self):
142+
"""The summary writer."""
143+
return self._summary_writer
144+
141145
@property
142146
def estimator_requirements(self):
143147
"""The estimator requirements."""
@@ -156,8 +160,8 @@ def set_params(self, **kwargs) -> None:
156160

157161
def _check_params(self) -> None:
158162

159-
if not isinstance(self.tensor_board, (bool, str)):
160-
raise ValueError("The argument `tensor_board` has to be either of type bool or str.")
163+
if not isinstance(self._summary_writer_arg, (bool, str, SummaryWriter)):
164+
raise ValueError("The argument `summary_writer` has to be either of type bool or str.")
161165

162166
@staticmethod
163167
def is_estimator_valid(estimator, estimator_requirements) -> bool:

art/attacks/evasion/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from art.attacks.evasion.adversarial_patch.adversarial_patch_numpy import AdversarialPatchNumpy
66
from art.attacks.evasion.adversarial_patch.adversarial_patch_tensorflow import AdversarialPatchTensorFlowV2
77
from art.attacks.evasion.adversarial_patch.adversarial_patch_pytorch import AdversarialPatchPyTorch
8+
from art.attacks.evasion.adversarial_texture.adversarial_texture_pytorch import AdversarialTexturePyTorch
89
from art.attacks.evasion.adversarial_asr import CarliniWagnerASR
910
from art.attacks.evasion.auto_attack import AutoAttack
1011
from art.attacks.evasion.auto_projected_gradient_descent import AutoProjectedGradientDescent

art/attacks/evasion/adversarial_patch/adversarial_patch_pytorch.py

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
3535
from art.estimators.classification.classifier import ClassifierMixin
3636
from art.utils import check_and_transform_label_format, is_probability, to_categorical
37+
from art.summary_writer import SummaryWriter
3738

3839
if TYPE_CHECKING:
3940
# pylint: disable=C0412
@@ -60,7 +61,7 @@ class AdversarialPatchPyTorch(EvasionAttack):
6061
"max_iter",
6162
"batch_size",
6263
"patch_shape",
63-
"tensor_board",
64+
"summary_writer",
6465
"verbose",
6566
]
6667

@@ -78,7 +79,7 @@ def __init__(
7879
batch_size: int = 16,
7980
patch_shape: Optional[Tuple[int, int, int]] = None,
8081
patch_type: str = "circle",
81-
tensor_board: Union[str, bool] = False,
82+
summary_writer: Union[str, bool, SummaryWriter] = False,
8283
verbose: bool = True,
8384
):
8485
"""
@@ -98,11 +99,13 @@ def __init__(
9899
:param batch_size: The size of the training batch.
99100
:param patch_shape: The shape of the adversarial patch as a tuple of shape CHW (nb_channels, height, width).
100101
:param patch_type: The patch type, either circle or square.
101-
:param tensor_board: Activate summary writer for TensorBoard: Default is `False` and deactivated summary writer.
102-
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory. Provide `path` in type
103-
`str` to save in path/CURRENT_DATETIME_HOSTNAME.
104-
Use hierarchical folder structure to compare between runs easily. e.g. pass in ‘runs/exp1’,
105-
‘runs/exp2’, etc. for each new experiment to compare across them.
102+
:param summary_writer: Activate summary writer for TensorBoard.
103+
Default is `False` and deactivated summary writer.
104+
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory.
105+
If of type `str` save in path.
106+
If of type `SummaryWriter` apply provided custom summary writer.
107+
Use hierarchical folder structure to compare between runs easily. e.g. pass in
108+
‘runs/exp1’, ‘runs/exp2’, etc. for each new experiment to compare across them.
106109
:param verbose: Show progress bars.
107110
"""
108111
import torch # lgtm [py/repeated-import]
@@ -115,7 +118,7 @@ def __init__(
115118
torchvision_version[0] >= 0 and torchvision_version[1] >= 8
116119
), "AdversarialPatchPyTorch requires torchvision>=0.8.0"
117120

118-
super().__init__(estimator=classifier, tensor_board=tensor_board)
121+
super().__init__(estimator=classifier, summary_writer=summary_writer)
119122
self.rotation_max = rotation_max
120123
self.scale_min = scale_min
121124
self.scale_max = scale_max
@@ -484,25 +487,30 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
484487
)
485488
_ = self._train_step(images=images, target=target, mask=mask_i)
486489

490+
# Write summary
487491
if self.summary_writer is not None: # pragma: no cover
488-
self.summary_writer.add_image(
489-
"patch",
490-
self._patch,
492+
x_patched = (
493+
self._random_overlay(
494+
images=torch.from_numpy(x).to(self.estimator.device), patch=self._patch, mask=mask
495+
)
496+
.detach()
497+
.cpu()
498+
.numpy()
499+
)
500+
501+
self.summary_writer.update(
502+
batch_id=0,
491503
global_step=i_iter,
504+
grad=None,
505+
patch=self._patch,
506+
estimator=self.estimator,
507+
x=x_patched,
508+
y=y,
509+
targeted=self.targeted,
492510
)
493511

494-
if hasattr(self.estimator, "compute_losses"):
495-
x_patched = self._random_overlay(
496-
images=torch.from_numpy(x).to(self.estimator.device), patch=self._patch, mask=mask
497-
)
498-
losses = self.estimator.compute_losses(x=x_patched, y=torch.from_numpy(y).to(self.estimator.device))
499-
500-
for key, value in losses.items():
501-
self.summary_writer.add_scalar(
502-
"loss/{}".format(key),
503-
np.mean(value.detach().cpu().numpy()),
504-
global_step=i_iter,
505-
)
512+
if self.summary_writer is not None:
513+
self.summary_writer.reset()
506514

507515
return (
508516
self._patch.detach().cpu().numpy(),

art/attacks/evasion/adversarial_patch/adversarial_patch_tensorflow.py

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
3636
from art.estimators.classification.classifier import ClassifierMixin
3737
from art.utils import check_and_transform_label_format, is_probability, to_categorical
38+
from art.summary_writer import SummaryWriter
3839

3940
if TYPE_CHECKING:
4041
# pylint: disable=C0412
@@ -60,7 +61,7 @@ class AdversarialPatchTensorFlowV2(EvasionAttack):
6061
"max_iter",
6162
"batch_size",
6263
"patch_shape",
63-
"tensor_board",
64+
"summary_writer",
6465
"verbose",
6566
]
6667

@@ -76,7 +77,7 @@ def __init__(
7677
max_iter: int = 500,
7778
batch_size: int = 16,
7879
patch_shape: Optional[Tuple[int, int, int]] = None,
79-
tensor_board: Union[str, bool] = False,
80+
summary_writer: Union[str, bool, SummaryWriter] = False,
8081
verbose: bool = True,
8182
):
8283
"""
@@ -93,16 +94,18 @@ def __init__(
9394
:param max_iter: The number of optimization steps.
9495
:param batch_size: The size of the training batch.
9596
:param patch_shape: The shape of the adversarial patch as a tuple of shape HWC (width, height, nb_channels).
96-
:param tensor_board: Activate summary writer for TensorBoard: Default is `False` and deactivated summary writer.
97-
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory. Provide `path` in type
98-
`str` to save in path/CURRENT_DATETIME_HOSTNAME.
99-
Use hierarchical folder structure to compare between runs easily. e.g. pass in ‘runs/exp1’,
100-
‘runs/exp2’, etc. for each new experiment to compare across them.
97+
:param summary_writer: Activate summary writer for TensorBoard.
98+
Default is `False` and deactivated summary writer.
99+
If `True` save runs/CURRENT_DATETIME_HOSTNAME in current directory.
100+
If of type `str` save in path.
101+
If of type `SummaryWriter` apply provided custom summary writer.
102+
Use hierarchical folder structure to compare between runs easily. e.g. pass in
103+
‘runs/exp1’, ‘runs/exp2’, etc. for each new experiment to compare across them.
101104
:param verbose: Show progress bars.
102105
"""
103106
import tensorflow as tf # lgtm [py/repeated-import]
104107

105-
super().__init__(estimator=classifier, tensor_board=tensor_board)
108+
super().__init__(estimator=classifier, summary_writer=summary_writer)
106109
self.rotation_max = rotation_max
107110
self.scale_min = scale_min
108111
self.scale_max = scale_max
@@ -458,23 +461,23 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
458461
for images, target, mask_i in dataset:
459462
_ = self._train_step(images=images, target=target, mask=mask_i)
460463

464+
# Write summary
461465
if self.summary_writer is not None: # pragma: no cover
462-
self.summary_writer.add_image(
463-
"patch",
464-
self._patch.numpy().transpose((2, 0, 1)),
466+
x_patched = self._random_overlay(images=x, patch=self._patch, mask=mask)
467+
468+
self.summary_writer.update(
469+
batch_id=0,
465470
global_step=i_iter,
471+
grad=None,
472+
patch=self._patch.numpy().transpose((2, 0, 1)),
473+
estimator=self.estimator,
474+
x=x_patched,
475+
y=y,
476+
targeted=self.targeted,
466477
)
467478

468-
if hasattr(self.estimator, "compute_losses"):
469-
x_patched = self._random_overlay(images=x, patch=self._patch, mask=mask)
470-
losses = self.estimator.compute_losses(x=x_patched, y=y)
471-
472-
for key, value in losses.items():
473-
self.summary_writer.add_scalar(
474-
"loss/{}".format(key),
475-
np.mean(value),
476-
global_step=i_iter,
477-
)
479+
if self.summary_writer is not None:
480+
self.summary_writer.reset()
478481

479482
return (
480483
self._patch.numpy(),

art/attacks/evasion/adversarial_texture/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)