Skip to content

Commit 268d4ac

Browse files
committed
Merge branch 'main' of github.com:Trusted-AI/adversarial-robustness-toolbox into gm-b
2 parents 304132e + 917a197 commit 268d4ac

File tree

71 files changed

+5562
-126
lines changed

Some content is hidden

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

71 files changed

+5562
-126
lines changed

.github/workflows/ci-lingvo.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jobs:
4747
sudo apt-get update
4848
sudo apt-get -y -q install ffmpeg libavcodec-extra
4949
python -m pip install --upgrade pip setuptools wheel
50-
pip install -q -r <(sed '/^scipy/d;/^matplotlib/d;/^pandas/d;/^statsmodels/d;/^numba/d;/^jax/d;/^h5py/d;/^Pillow/d;/^pytest-mock/d' requirements_test.txt)
50+
pip install -q -r <(sed '/^scipy/d;/^matplotlib/d;/^pandas/d;/^statsmodels/d;/^numba/d;/^jax/d;/^h5py/d;/^Pillow/d;/^pytest/d;/^pytest-mock/d;/^torch/d;/^torchaudio/d;/^torchvision/d' requirements_test.txt)
5151
pip install scipy==1.5.4
5252
pip install matplotlib==3.3.4
5353
pip install pandas==1.1.5
@@ -60,7 +60,13 @@ jobs:
6060
pip install model-pruning-google-research==0.0.3
6161
pip install jax[cpu]==0.2.17
6262
pip install h5py==2.10.0
63+
pip install pytest~=7.0.1
64+
pip install pytest-flake8~=1.1.0
6365
pip install pytest-mock
66+
pip install pytest-cov~=3.0.0
67+
pip install torch==1.10.2+cpu --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
68+
pip install torchaudio==0.10.2+cpu --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
69+
pip install torchvision==0.11.3+cpu --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
6470
pip list
6571
- name: Run ${{ matrix.name }} Tests
6672
run: ./run_tests.sh ${{ matrix.framework }}

.github/workflows/ci-pytorch.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,6 @@ jobs:
2525
fail-fast: false
2626
matrix:
2727
include:
28-
- name: PyTorch 1.8.1 (Python 3.8)
29-
framework: pytorch
30-
python: 3.8
31-
torch: 1.8.1+cpu
32-
torchvision: 0.9.1+cpu
33-
torchaudio: 0.8.1
3428
- name: PyTorch 1.9.1 (Python 3.8)
3529
framework: pytorch
3630
python: 3.8
@@ -43,6 +37,12 @@ jobs:
4337
torch: 1.10.2+cpu
4438
torchvision: 0.11.3+cpu
4539
torchaudio: 0.10.2+cpu
40+
- name: PyTorch 1.11.0 (Python 3.8)
41+
framework: pytorch
42+
python: 3.8
43+
torch: 1.11.0+cpu
44+
torchvision: 0.12.0+cpu
45+
torchaudio: 0.11.0
4646

4747
name: ${{ matrix.name }}
4848
steps:

.github/workflows/ci-tensorflow-v1.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ jobs:
4545
sudo apt-get update
4646
sudo apt-get -y -q install ffmpeg libavcodec-extra
4747
python -m pip install --upgrade pip setuptools wheel
48-
pip install -r requirements_test.txt
48+
pip install -q -r <(sed '/^pandas/d;/^scipy/d' requirements_test.txt)
49+
pip install pandas==1.3.5
50+
pip install scipy==1.7.2
4951
pip install tensorflow==${{ matrix.tensorflow }}
5052
pip install keras==${{ matrix.keras }}
5153
pip list

README-cn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Adversarial Robustness Toolbox (ART) v1.9
1+
# Adversarial Robustness Toolbox (ART) v1.10
22
<p align="center">
33
<img src="docs/images/art_lfai.png?raw=true" width="467" title="ART logo">
44
</p>

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Adversarial Robustness Toolbox (ART) v1.9
1+
# Adversarial Robustness Toolbox (ART) v1.10
22
<p align="center">
33
<img src="docs/images/art_lfai.png?raw=true" width="467" title="ART logo">
44
</p>

art/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from art import preprocessing
1313

1414
# Semantic Version
15-
__version__ = "1.9.1"
15+
__version__ = "1.10.0"
1616

1717
# pylint: disable=C0103
1818

art/attacks/attack.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from art.summary_writer import SummaryWriter, SummaryWriterDefault
3131

3232
if TYPE_CHECKING:
33-
from art.utils import CLASSIFIER_TYPE
33+
from art.utils import CLASSIFIER_TYPE, GENERATOR_TYPE
3434

3535
logger = logging.getLogger(__name__)
3636

@@ -244,6 +244,50 @@ def poison(self, x: np.ndarray, y=Optional[np.ndarray], **kwargs) -> Tuple[np.nd
244244
raise NotImplementedError
245245

246246

247+
class PoisoningAttackGenerator(Attack):
248+
"""
249+
Abstract base class for poisoning attack classes that return a transformed generator.
250+
These attacks have an additional method, `poison_estimator`, that returns the poisoned generator.
251+
"""
252+
253+
def __init__(self, generator: "GENERATOR_TYPE") -> None:
254+
"""
255+
:param generator: A generator
256+
"""
257+
super().__init__(generator)
258+
259+
@abc.abstractmethod
260+
def poison_estimator(
261+
self,
262+
z_trigger: np.ndarray,
263+
x_target: np.ndarray,
264+
batch_size: int,
265+
max_iter: int,
266+
lambda_p: float,
267+
verbose: int,
268+
**kwargs
269+
) -> "GENERATOR_TYPE":
270+
"""
271+
Returns a poisoned version of the generator used to initialize the attack
272+
:return: A poisoned generator
273+
"""
274+
raise NotImplementedError
275+
276+
@property
277+
def z_trigger(self):
278+
"""
279+
Returns the secret attacker trigger
280+
"""
281+
return self._z_trigger
282+
283+
@property
284+
def x_target(self):
285+
"""
286+
Returns the secret attacker target which the poisoned generator should produce
287+
"""
288+
return self._x_target
289+
290+
247291
class PoisoningAttackTransformer(PoisoningAttack):
248292
"""
249293
Abstract base class for poisoning attack classes that return a transformed classifier.

art/attacks/evasion/pixel_threshold.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
| One Pixel Attack Paper link: https://arxiv.org/ans/1710.08864
2323
| Pixel and Threshold Attack Paper link: https://arxiv.org/abs/1906.06026
2424
"""
25-
# pylint: disable=C0302
25+
# pylint: disable=C0302,C0413
2626
from __future__ import absolute_import, division, print_function, unicode_literals
2727

2828
import logging
@@ -39,16 +39,22 @@
3939
# Otherwise may use Tensorflow's implementation of DE.
4040

4141
from six import string_types
42+
import scipy
4243
from scipy._lib._util import check_random_state
43-
from scipy.optimize.optimize import _status_message
44-
from scipy.optimize import OptimizeResult, minimize
45-
from tqdm.auto import tqdm
46-
47-
from art.config import ART_NUMPY_DTYPE
48-
from art.attacks.attack import EvasionAttack
49-
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
50-
from art.estimators.classification.classifier import ClassifierMixin
51-
from art.utils import check_and_transform_label_format
44+
45+
scipy_version = list(map(int, scipy.__version__.lower().split(".")))
46+
if scipy_version[1] >= 8:
47+
from scipy.optimize._optimize import _status_message # pylint: disable=E0611
48+
else:
49+
from scipy.optimize.optimize import _status_message # pylint: disable=E0611
50+
from scipy.optimize import OptimizeResult, minimize # noqa
51+
from tqdm.auto import tqdm # noqa
52+
53+
from art.config import ART_NUMPY_DTYPE # noqa
54+
from art.attacks.attack import EvasionAttack # noqa
55+
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin # noqa
56+
from art.estimators.classification.classifier import ClassifierMixin # noqa
57+
from art.utils import check_and_transform_label_format # noqa
5258

5359
if TYPE_CHECKING:
5460
from art.utils import CLASSIFIER_NEURALNETWORK_TYPE

art/attacks/inference/attribute_inference/baseline.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@
2929

3030
from art.estimators.classification.classifier import ClassifierMixin
3131
from art.attacks.attack import AttributeInferenceAttack
32-
from art.utils import check_and_transform_label_format, float_to_categorical, floats_to_one_hot, get_feature_values
32+
from art.utils import (
33+
check_and_transform_label_format,
34+
float_to_categorical,
35+
floats_to_one_hot,
36+
get_feature_values,
37+
get_feature_index,
38+
)
3339

3440
if TYPE_CHECKING:
3541
from art.utils import CLASSIFIER_TYPE
@@ -65,11 +71,6 @@ def __init__(
6571
"""
6672
super().__init__(estimator=None, attack_feature=attack_feature)
6773

68-
if isinstance(self.attack_feature, int):
69-
self.single_index_feature = True
70-
else:
71-
self.single_index_feature = False
72-
7374
self._values: Optional[list] = None
7475

7576
if attack_model:
@@ -108,6 +109,7 @@ def __init__(
108109
raise ValueError("Illegal value for parameter `attack_model_type`.")
109110

110111
self._check_params()
112+
self.attack_feature = get_feature_index(self.attack_feature)
111113

112114
def fit(self, x: np.ndarray) -> None:
113115
"""
@@ -117,13 +119,13 @@ def fit(self, x: np.ndarray) -> None:
117119
"""
118120

119121
# Checks:
120-
if self.single_index_feature and isinstance(self.attack_feature, int) and self.attack_feature >= x.shape[1]:
122+
if isinstance(self.attack_feature, int) and self.attack_feature >= x.shape[1]:
121123
raise ValueError("attack_feature must be a valid index to a feature in x")
122124

123125
# get vector of attacked feature
124126
y = x[:, self.attack_feature]
125-
self._values = get_feature_values(y, self.single_index_feature)
126-
if self.single_index_feature:
127+
self._values = get_feature_values(y, isinstance(self.attack_feature, int))
128+
if isinstance(self.attack_feature, int):
127129
y_one_hot = float_to_categorical(y)
128130
else:
129131
y_one_hot = floats_to_one_hot(y)
@@ -161,7 +163,7 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
161163
predictions = self.attack_model.predict(x_test).astype(np.float32)
162164

163165
if self._values is not None:
164-
if self.single_index_feature:
166+
if isinstance(self.attack_feature, int):
165167
predictions = np.array([self._values[np.argmax(arr)] for arr in predictions])
166168
else:
167169
i = 0

art/attacks/inference/attribute_inference/black_box.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@
3232
from art.estimators.classification.classifier import ClassifierMixin
3333
from art.attacks.attack import AttributeInferenceAttack
3434
from art.estimators.regression import RegressorMixin
35-
from art.utils import check_and_transform_label_format, float_to_categorical, floats_to_one_hot, get_feature_values
35+
from art.utils import (
36+
check_and_transform_label_format,
37+
float_to_categorical,
38+
floats_to_one_hot,
39+
get_feature_values,
40+
get_feature_index,
41+
)
3642

3743
if TYPE_CHECKING:
3844
from art.utils import CLASSIFIER_TYPE, REGRESSOR_TYPE
@@ -83,10 +89,6 @@ def __init__(
8389
`estimator` is a regressor and if `scale_range` is not supplied.
8490
"""
8591
super().__init__(estimator=estimator, attack_feature=attack_feature)
86-
if isinstance(self.attack_feature, int):
87-
self.single_index_feature = True
88-
else:
89-
self.single_index_feature = False
9092

9193
self._values: Optional[list] = None
9294
self._attack_model_type = attack_model_type
@@ -131,6 +133,7 @@ def __init__(
131133
self.scale_range = scale_range
132134

133135
self._check_params()
136+
self.attack_feature = get_feature_index(self.attack_feature)
134137

135138
def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None:
136139
"""
@@ -144,7 +147,7 @@ def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None:
144147
if self.estimator.input_shape is not None:
145148
if self.estimator.input_shape[0] != x.shape[1]:
146149
raise ValueError("Shape of x does not match input_shape of model")
147-
if self.single_index_feature and isinstance(self.attack_feature, int) and self.attack_feature >= x.shape[1]:
150+
if isinstance(self.attack_feature, int) and self.attack_feature >= x.shape[1]:
148151
raise ValueError("`attack_feature` must be a valid index to a feature in x")
149152

150153
# get model's predictions for x
@@ -162,8 +165,8 @@ def fit(self, x: np.ndarray, y: Optional[np.ndarray] = None) -> None:
162165

163166
# get vector of attacked feature
164167
y_attack = x[:, self.attack_feature]
165-
self._values = get_feature_values(y_attack, self.single_index_feature)
166-
if self.single_index_feature:
168+
self._values = get_feature_values(y_attack, isinstance(self.attack_feature, int))
169+
if isinstance(self.attack_feature, int):
167170
y_one_hot = float_to_categorical(y_attack)
168171
else:
169172
y_one_hot = floats_to_one_hot(y_attack)
@@ -210,7 +213,7 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
210213
if pred.shape[0] != x.shape[0]:
211214
raise ValueError("Number of rows in x and y do not match")
212215
if self.estimator.input_shape is not None:
213-
if self.single_index_feature and self.estimator.input_shape[0] != x.shape[1] + 1:
216+
if isinstance(self.attack_feature, int) and self.estimator.input_shape[0] != x.shape[1] + 1:
214217
raise ValueError("Number of features in x + 1 does not match input_shape of model")
215218

216219
if RegressorMixin in type(self.estimator).__mro__:
@@ -234,7 +237,7 @@ def infer(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> np.n
234237
predictions = self.attack_model.predict(x_test).astype(np.float32)
235238

236239
if self._values is not None:
237-
if self.single_index_feature:
240+
if isinstance(self.attack_feature, int):
238241
predictions = np.array([self._values[np.argmax(arr)] for arr in predictions])
239242
else:
240243
i = 0

0 commit comments

Comments
 (0)