Skip to content

Commit f31f1a4

Browse files
authored
Merge branch 'dev_1.5.2' into development_issue_870
2 parents ffd042a + 9e2f822 commit f31f1a4

File tree

7 files changed

+155
-12
lines changed

7 files changed

+155
-12
lines changed

art/attacks/evasion/simba.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from scipy.fftpack import idct
3030

3131
from art.attacks.attack import EvasionAttack
32-
from art.estimators.estimator import BaseEstimator
32+
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
3333
from art.estimators.classification.classifier import ClassifierMixin
3434
from art.config import ART_NUMPY_DTYPE
3535

@@ -51,7 +51,7 @@ class SimBA(EvasionAttack):
5151
"batch_size",
5252
]
5353

54-
_estimator_requirements = (BaseEstimator, ClassifierMixin)
54+
_estimator_requirements = (BaseEstimator, ClassifierMixin, NeuralNetworkMixin)
5555

5656
def __init__(
5757
self,

art/attacks/evasion/square_attack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131

3232
from art.config import ART_NUMPY_DTYPE
3333
from art.attacks.attack import EvasionAttack
34-
from art.estimators.estimator import BaseEstimator
34+
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, get_labels_np_array
3737

@@ -53,7 +53,7 @@ class SquareAttack(EvasionAttack):
5353
"verbose",
5454
]
5555

56-
_estimator_requirements = (BaseEstimator, ClassifierMixin)
56+
_estimator_requirements = (BaseEstimator, ClassifierMixin, NeuralNetworkMixin)
5757

5858
def __init__(
5959
self,

art/estimators/classification/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
ClassGradientsMixin,
88
)
99

10-
from art.estimators.classification.blackbox import BlackBoxClassifier
10+
from art.estimators.classification.blackbox import BlackBoxClassifier, BlackBoxClassifierNeuralNetwork
1111
from art.estimators.classification.catboost import CatBoostARTClassifier
1212
from art.estimators.classification.detector_classifier import DetectorClassifier
1313
from art.estimators.classification.ensemble import EnsembleClassifier

art/estimators/classification/blackbox.py

Lines changed: 139 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
from __future__ import absolute_import, division, print_function, unicode_literals
2222

2323
import logging
24-
from typing import Callable, List, Optional, Tuple, Union, Tuple, TYPE_CHECKING
24+
from typing import Callable, List, Optional, Union, Tuple, TYPE_CHECKING
2525

2626
import numpy as np
2727

28-
from art.estimators.classification.classifier import Classifier
28+
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
29+
from art.estimators.classification.classifier import ClassifierMixin
2930

3031
if TYPE_CHECKING:
3132
from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE
@@ -35,7 +36,7 @@
3536
logger = logging.getLogger(__name__)
3637

3738

38-
class BlackBoxClassifier(Classifier):
39+
class BlackBoxClassifier(ClassifierMixin, BaseEstimator):
3940
"""
4041
Wrapper class for black-box classifiers.
4142
"""
@@ -138,3 +139,138 @@ def save(self, filename: str, path: Optional[str] = None) -> None:
138139
:raises `NotImplementedException`: This method is not supported for black-box classifiers.
139140
"""
140141
raise NotImplementedError
142+
143+
144+
class BlackBoxClassifierNeuralNetwork(NeuralNetworkMixin, ClassifierMixin, BaseEstimator):
145+
"""
146+
Wrapper class for black-box neural network classifiers.
147+
"""
148+
149+
def __init__(
150+
self,
151+
predict: Callable,
152+
input_shape: Tuple[int, ...],
153+
nb_classes: int,
154+
channels_first: bool = True,
155+
clip_values: Optional["CLIP_VALUES_TYPE"] = None,
156+
preprocessing_defences: Union["Preprocessor", List["Preprocessor"], None] = None,
157+
postprocessing_defences: Union["Postprocessor", List["Postprocessor"], None] = None,
158+
preprocessing: "PREPROCESSING_TYPE" = (0, 1),
159+
):
160+
"""
161+
Create a `Classifier` instance for a black-box model.
162+
163+
:param predict: Function that takes in one input of the data and returns the one-hot encoded predicted class.
164+
:param input_shape: Size of input.
165+
:param nb_classes: Number of prediction classes.
166+
:param channels_first: Set channels first or last.
167+
:param clip_values: Tuple of the form `(min, max)` of floats or `np.ndarray` representing the minimum and
168+
maximum values allowed for features. If floats are provided, these will be used as the range of all
169+
features. If arrays are provided, each value will be considered the bound for a feature, thus
170+
the shape of clip values needs to match the total number of features.
171+
:param preprocessing_defences: Preprocessing defence(s) to be applied by the classifier.
172+
:param postprocessing_defences: Postprocessing defence(s) to be applied by the classifier.
173+
:param preprocessing: Tuple of the form `(subtrahend, divisor)` of floats or `np.ndarray` of values to be
174+
used for data preprocessing. The first value will be subtracted from the input. The input will then
175+
be divided by the second one.
176+
"""
177+
super().__init__(
178+
model=None,
179+
channels_first=channels_first,
180+
clip_values=clip_values,
181+
preprocessing_defences=preprocessing_defences,
182+
postprocessing_defences=postprocessing_defences,
183+
preprocessing=preprocessing,
184+
)
185+
186+
self._predictions = predict
187+
self._input_shape = input_shape
188+
self._nb_classes = nb_classes
189+
self._learning_phase = None
190+
self._layer_names = None
191+
192+
@property
193+
def input_shape(self) -> Tuple[int, ...]:
194+
"""
195+
Return the shape of one input sample.
196+
197+
:return: Shape of one input sample.
198+
"""
199+
return self._input_shape # type: ignore
200+
201+
def predict(self, x: np.ndarray, batch_size: int = 128, **kwargs):
202+
"""
203+
Perform prediction for a batch of inputs.
204+
205+
:param x: Test set.
206+
:param batch_size: Size of batches.
207+
:return: Array of predictions of shape `(nb_inputs, nb_classes)`.
208+
"""
209+
from art.config import ART_NUMPY_DTYPE
210+
211+
# Apply preprocessing
212+
x_preprocessed, _ = self._apply_preprocessing(x, y=None, fit=False)
213+
214+
# Run predictions with batching
215+
predictions = np.zeros((x_preprocessed.shape[0], self.nb_classes), dtype=ART_NUMPY_DTYPE)
216+
for batch_index in range(int(np.ceil(x_preprocessed.shape[0] / float(batch_size)))):
217+
begin, end = (
218+
batch_index * batch_size,
219+
min((batch_index + 1) * batch_size, x_preprocessed.shape[0]),
220+
)
221+
predictions[begin:end] = self._predictions(x_preprocessed[begin:end])
222+
223+
# Apply postprocessing
224+
predictions = self._apply_postprocessing(preds=predictions, fit=False)
225+
226+
return predictions
227+
228+
def fit(self, x: np.ndarray, y, batch_size: int = 128, nb_epochs: int = 20, **kwargs) -> None:
229+
"""
230+
Fit the model of the estimator on the training data `x` and `y`.
231+
232+
:param x: Samples of shape (nb_samples, nb_features) or (nb_samples, nb_pixels_1, nb_pixels_2,
233+
nb_channels) or (nb_samples, nb_channels, nb_pixels_1, nb_pixels_2).
234+
:param y: Target values.
235+
:type y: Format as expected by the `model`
236+
:param batch_size: Batch size.
237+
:param nb_epochs: Number of training epochs.
238+
"""
239+
raise NotImplementedError
240+
241+
def get_activations(
242+
self, x: np.ndarray, layer: Union[int, str], batch_size: int, framework: bool = False
243+
) -> np.ndarray:
244+
"""
245+
Return the output of a specific layer for samples `x` where `layer` is the index of the layer between 0 and
246+
`nb_layers - 1 or the name of the layer. The number of layers can be determined by counting the results
247+
returned by calling `layer_names`.
248+
249+
:param x: Samples
250+
:param layer: Index or name of the layer.
251+
:param batch_size: Batch size.
252+
:param framework: If true, return the intermediate tensor representation of the activation.
253+
:return: The output of `layer`, where the first dimension is the batch size corresponding to `x`.
254+
"""
255+
raise NotImplementedError
256+
257+
def set_learning_phase(self, train: bool) -> None:
258+
"""
259+
Set the learning phase for the backend framework.
260+
261+
:param train: `True` if the learning phase is training, otherwise `False`.
262+
"""
263+
raise NotImplementedError
264+
265+
def loss(self, x: np.ndarray, y: np.ndarray, **kwargs) -> np.ndarray:
266+
"""
267+
Compute the loss of the neural network for samples `x`.
268+
269+
:param x: Samples of shape (nb_samples, nb_features) or (nb_samples, nb_pixels_1, nb_pixels_2,
270+
nb_channels) or (nb_samples, nb_channels, nb_pixels_1, nb_pixels_2).
271+
:param y: Target values (class labels) one-hot-encoded of shape `(nb_samples, nb_classes)` or indices
272+
of shape `(nb_samples,)`.
273+
:return: Loss values.
274+
:rtype: Format as expected by the `model`
275+
"""
276+
raise NotImplementedError

docs/modules/estimators/classification.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ BlackBox Classifier
1919
:special-members: __init__
2020
:inherited-members:
2121

22+
BlackBox Classifier NeuralNetwork
23+
---------------------------------
24+
.. autoclass:: BlackBoxClassifierNeuralNetwork
25+
:members:
26+
:special-members: __init__
27+
:inherited-members:
28+
2229
Keras Classifier
2330
----------------
2431
.. autoclass:: KerasClassifier

tests/attacks/evasion/test_square_attack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import numpy as np
2222

2323
from art.attacks.evasion import SquareAttack
24-
from art.estimators.estimator import BaseEstimator
24+
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
2525
from art.estimators.classification.classifier import ClassifierMixin
2626

2727
from tests.attacks.utils import backend_test_classifier_type_check_fail
@@ -66,6 +66,6 @@ def test_generate(art_warning, fix_get_mnist_subset, image_dl_estimator_for_atta
6666
@pytest.mark.framework_agnostic
6767
def test_classifier_type_check_fail(art_warning):
6868
try:
69-
backend_test_classifier_type_check_fail(SquareAttack, [BaseEstimator, ClassifierMixin])
69+
backend_test_classifier_type_check_fail(SquareAttack, [BaseEstimator, ClassifierMixin, NeuralNetworkMixin])
7070
except ARTTestException as e:
7171
art_warning(e)

tests/attacks/test_simba.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import numpy as np
2424

2525
from art.attacks.evasion.simba import SimBA
26-
from art.estimators.estimator import BaseEstimator
26+
from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin
2727
from art.estimators.classification.classifier import ClassifierMixin
2828
from art.utils import get_labels_np_array
2929

@@ -146,7 +146,7 @@ def _test_attack(self, classifier, x_test, y_test, targeted):
146146
self.assertAlmostEqual(float(np.max(np.abs(x_test_original - x_test))), 0.0, delta=0.00001)
147147

148148
def test_1_classifier_type_check_fail(self):
149-
backend_test_classifier_type_check_fail(SimBA, (BaseEstimator, ClassifierMixin))
149+
backend_test_classifier_type_check_fail(SimBA, (BaseEstimator, ClassifierMixin, NeuralNetworkMixin))
150150

151151

152152
if __name__ == "__main__":

0 commit comments

Comments
 (0)