|
21 | 21 | from __future__ import absolute_import, division, print_function, unicode_literals |
22 | 22 |
|
23 | 23 | 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 |
25 | 25 |
|
26 | 26 | import numpy as np |
27 | 27 |
|
28 | | -from art.estimators.classification.classifier import Classifier |
| 28 | +from art.estimators.estimator import BaseEstimator, NeuralNetworkMixin |
| 29 | +from art.estimators.classification.classifier import ClassifierMixin |
29 | 30 |
|
30 | 31 | if TYPE_CHECKING: |
31 | 32 | from art.utils import CLIP_VALUES_TYPE, PREPROCESSING_TYPE |
|
35 | 36 | logger = logging.getLogger(__name__) |
36 | 37 |
|
37 | 38 |
|
38 | | -class BlackBoxClassifier(Classifier): |
| 39 | +class BlackBoxClassifier(ClassifierMixin, BaseEstimator): |
39 | 40 | """ |
40 | 41 | Wrapper class for black-box classifiers. |
41 | 42 | """ |
@@ -138,3 +139,138 @@ def save(self, filename: str, path: Optional[str] = None) -> None: |
138 | 139 | :raises `NotImplementedException`: This method is not supported for black-box classifiers. |
139 | 140 | """ |
140 | 141 | 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 |
0 commit comments