3333from art .attacks .attack import EvasionAttack
3434from art .estimators .estimator import BaseEstimator , NeuralNetworkMixin
3535from art .estimators .classification .classifier import ClassifierMixin
36- from art .utils import check_and_transform_label_format
36+ from art .utils import check_and_transform_label_format , is_probability
3737
3838if TYPE_CHECKING :
3939 import tensorflow as tf
@@ -111,6 +111,8 @@ def __init__(
111111 if self .estimator .channels_first :
112112 raise ValueError ("Color channel needs to be in last dimension." )
113113
114+ self .use_logits = None
115+
114116 self .i_h_patch = 0
115117 self .i_w_patch = 1
116118
@@ -142,8 +144,8 @@ def __init__(
142144 constraint = lambda x : tf .clip_by_value (x , self .estimator .clip_values [0 ], self .estimator .clip_values [1 ]),
143145 )
144146
145- self ._train_op = tf .keras .optimizers .SGD (
146- learning_rate = self .learning_rate , momentum = 0.0 , nesterov = False , name = "SGD "
147+ self ._train_op = tf .keras .optimizers .Adam (
148+ learning_rate = self .learning_rate , beta_1 = 0.9 , beta_2 = 0.999 , epsilon = 1e-07 , amsgrad = False , name = "Adam "
147149 )
148150
149151 def _train_step (
@@ -170,7 +172,7 @@ def _train_step(
170172
171173 return loss
172174
173- def _probabilities (self , images : "tf.Tensor" , mask : Optional ["tf.Tensor" ]) -> "tf.Tensor" :
175+ def _predictions (self , images : "tf.Tensor" , mask : Optional ["tf.Tensor" ]) -> "tf.Tensor" :
174176 import tensorflow as tf # lgtm [py/repeated-import]
175177
176178 patched_input = self ._random_overlay (images , self ._patch , mask = mask )
@@ -179,17 +181,17 @@ def _probabilities(self, images: "tf.Tensor", mask: Optional["tf.Tensor"]) -> "t
179181 patched_input , clip_value_min = self .estimator .clip_values [0 ], clip_value_max = self .estimator .clip_values [1 ],
180182 )
181183
182- probabilities = self .estimator ._predict_framework (patched_input )
184+ predictions = self .estimator ._predict_framework (patched_input )
183185
184- return probabilities
186+ return predictions
185187
186188 def _loss (self , images : "tf.Tensor" , target : "tf.Tensor" , mask : Optional ["tf.Tensor" ]) -> "tf.Tensor" :
187189 import tensorflow as tf # lgtm [py/repeated-import]
188190
189- probabilities = self ._probabilities (images , mask )
191+ predictions = self ._predictions (images , mask )
190192
191193 self ._loss_per_example = tf .keras .losses .categorical_crossentropy (
192- y_true = target , y_pred = probabilities , from_logits = False , label_smoothing = 0
194+ y_true = target , y_pred = predictions , from_logits = self . use_logits , label_smoothing = 0
193195 )
194196
195197 loss = tf .reduce_mean (self ._loss_per_example )
@@ -381,27 +383,21 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
381383 mask = kwargs .get ("mask" )
382384 if mask is not None :
383385 mask = mask .copy ()
384- if mask is not None and (
385- (mask .dtype != np .bool )
386- or not (mask .shape [0 ] == 1 or mask .shape [0 ] == x .shape [0 ])
387- or not (
388- (mask .shape [1 ] == x .shape [1 ] and mask .shape [2 ] == x .shape [2 ])
389- or (mask .shape [1 ] == x .shape [2 ] and mask .shape [2 ] == x .shape [3 ])
390- )
391- ):
392- raise ValueError (
393- "The shape of `mask` has to be equal to the shape of a single samples (1, H, W) or the"
394- "shape of `x` (N, H, W) without their channel dimensions."
395- )
396-
397- if mask is not None and mask .shape [0 ] == 1 :
398- mask = np .repeat (mask , repeats = x .shape [0 ], axis = 0 )
386+ mask = self ._check_mask (mask = mask , x = x )
399387
400388 if kwargs .get ("reset_patch" ):
401389 self .reset_patch (initial_patch_value = self ._initial_value )
402390
403391 y = check_and_transform_label_format (labels = y , nb_classes = self .estimator .nb_classes )
404392
393+ # check if logits or probabilities
394+ y_pred = self .estimator .predict (x = x [[0 ]])
395+
396+ if is_probability (y_pred ):
397+ self .use_logits = False
398+ else :
399+ self .use_logits = True
400+
405401 if mask is None :
406402 if shuffle :
407403 dataset = (
@@ -444,6 +440,22 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> T
444440 self ._get_circular_patch_mask (nb_samples = 1 ).numpy ()[0 ],
445441 )
446442
443+ def _check_mask (self , mask : np .ndarray , x : np .ndarray ) -> np .ndarray :
444+ if mask is not None and (
445+ (mask .dtype != np .bool )
446+ or not (mask .shape [0 ] == 1 or mask .shape [0 ] == x .shape [0 ])
447+ or not (mask .shape [1 ] == x .shape [self .i_h + 1 ] and mask .shape [2 ] == x .shape [self .i_w + 1 ])
448+ ):
449+ raise ValueError (
450+ "The shape of `mask` has to be equal to the shape of a single samples (1, H, W) or the"
451+ "shape of `x` (N, H, W) without their channel dimensions."
452+ )
453+
454+ if mask is not None and mask .shape [0 ] == 1 :
455+ mask = np .repeat (mask , repeats = x .shape [0 ], axis = 0 )
456+
457+ return mask
458+
447459 def apply_patch (
448460 self ,
449461 x : np .ndarray ,
@@ -464,6 +476,7 @@ def apply_patch(
464476 """
465477 if mask is not None :
466478 mask = mask .copy ()
479+ mask = self ._check_mask (mask = mask , x = x )
467480 patch = patch_external if patch_external is not None else self ._patch
468481 return self ._random_overlay (images = x , patch = patch , scale = scale , mask = mask ).numpy ()
469482
0 commit comments