Skip to content

Commit 9b4b84d

Browse files
committed
Fix dependencies
Signed-off-by: Beat Buesser <[email protected]>
1 parent bf2a668 commit 9b4b84d

File tree

7 files changed

+35
-169
lines changed

7 files changed

+35
-169
lines changed

art/attacks/poisoning/gradient_matching_attack.py

Lines changed: 4 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,14 @@ def _initialize_poison(
116116
:param y_train: A list of labels for x_train.
117117
"""
118118
from art.estimators.classification.pytorch import PyTorchClassifier
119-
from art.estimators.classification.tensorflow import TensorFlowV2Classifier
120119

121-
if isinstance(self.substitute_classifier, TensorFlowV2Classifier):
122-
initializer = self._initialize_poison_tensorflow
123-
elif isinstance(self.substitute_classifier, PyTorchClassifier):
120+
if isinstance(self.substitute_classifier, PyTorchClassifier):
124121
initializer = self._initialize_poison_pytorch
125122
else:
126-
raise NotImplementedError(
127-
"GradientMatchingAttack is currently implemented only for TensorFlow V2 and PyTorch."
128-
)
123+
raise NotImplementedError("GradientMatchingAttack is currently implemented only for PyTorch.")
129124

130125
return initializer(x_trigger, y_trigger, x_poison, y_poison)
131126

132-
def _finish_poison_tensorflow(self):
133-
"""
134-
Releases any resource and revert back unwanted change to the model.
135-
"""
136-
self.substitute_classifier.model.trainable = self.model_trainable
137-
138127
def _finish_poison_pytorch(self):
139128
"""
140129
Releases any resource and revert back unwanted change to the model.
@@ -144,103 +133,6 @@ def _finish_poison_pytorch(self):
144133
else:
145134
self.substitute_classifier.model.eval()
146135

147-
def _initialize_poison_tensorflow(
148-
self, x_trigger: np.ndarray, y_trigger: np.ndarray, x_poison: np.ndarray, y_poison: np.ndarray
149-
):
150-
"""
151-
Initialize poison noises to be optimized.
152-
153-
:param x_trigger: A list of samples to use as triggers.
154-
:param y_trigger: A list of target classes to classify the triggers into.
155-
:param x_poison: A list of training data to poison a portion of.
156-
:param y_poison: A list of true labels for x_poison.
157-
"""
158-
from tensorflow.keras import backend as K
159-
import tensorflow as tf
160-
from tensorflow.keras.layers import Input, Embedding, Add, Lambda
161-
from art.estimators.classification.tensorflow import TensorFlowV2Classifier
162-
163-
if isinstance(self.substitute_classifier, TensorFlowV2Classifier):
164-
classifier = self.substitute_classifier
165-
else:
166-
raise Exception("This method requires `TensorFlowV2Classifier` as `substitute_classifier`'s type")
167-
168-
self.model_trainable = classifier.model.trainable
169-
classifier.model.trainable = False # This value gets revert back later.
170-
171-
def _weight_grad(classifier: TensorFlowV2Classifier, x: tf.Tensor, target: tf.Tensor) -> tf.Tensor:
172-
# Get the target gradient vector.
173-
import tensorflow as tf
174-
175-
with tf.GradientTape() as t: # pylint: disable=invalid-name
176-
t.watch(classifier.model.weights)
177-
output = classifier.model(x, training=False)
178-
loss = classifier.loss_object(target, output)
179-
d_w = t.gradient(loss, classifier.model.weights)
180-
d_w = [w for w in d_w if w is not None]
181-
d_w = tf.concat([tf.reshape(d, [-1]) for d in d_w], 0)
182-
d_w_norm = d_w / tf.sqrt(tf.reduce_sum(tf.square(d_w)))
183-
return d_w_norm
184-
185-
self.grad_ws_norm = _weight_grad(classifier, tf.constant(x_trigger), tf.constant(y_trigger))
186-
187-
# Define the model to apply and optimize the poison.
188-
input_poison = Input(batch_shape=classifier.model.input.shape)
189-
input_indices = Input(shape=())
190-
y_true_poison = Input(shape=np.shape(y_poison)[1:])
191-
embedding_layer = Embedding(
192-
len(x_poison),
193-
np.prod(x_poison.shape[1:]),
194-
embeddings_initializer=tf.keras.initializers.RandomNormal(stddev=self.epsilon * 0.01),
195-
)
196-
embeddings = embedding_layer(input_indices)
197-
embeddings = tf.tanh(embeddings) * self.epsilon
198-
embeddings = tf.reshape(embeddings, tf.shape(input_poison))
199-
input_noised = Add()([input_poison, embeddings])
200-
input_noised = Lambda(lambda x: K.clip(x, self.clip_values[0], self.clip_values[1]))(
201-
input_noised
202-
) # Make sure the poisoned samples are in a valid range.
203-
204-
def loss_fn(input_noised: tf.Tensor, target: tf.Tensor, grad_ws_norm: tf.Tensor):
205-
d_w2_norm = _weight_grad(classifier, input_noised, target)
206-
B = 1 - tf.reduce_sum(grad_ws_norm * d_w2_norm) # pylint: disable=invalid-name
207-
return B
208-
209-
B = tf.keras.layers.Lambda(lambda x: loss_fn(x[0], x[1], x[2]))( # pylint: disable=invalid-name
210-
[input_noised, y_true_poison, self.grad_ws_norm]
211-
)
212-
213-
self.backdoor_model = tf.keras.models.Model([input_poison, y_true_poison, input_indices], [input_noised, B])
214-
215-
self.backdoor_model.add_loss(B)
216-
217-
class PredefinedLRSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):
218-
"""
219-
Use a preset learning rate based on the current training epoch.
220-
"""
221-
222-
def __init__(self, learning_rates: list[float], milestones: list[int]):
223-
self.schedule = list(zip(milestones, learning_rates))
224-
225-
def __call__(self, step: int) -> float:
226-
lr_prev = self.schedule[0][1]
227-
for m, learning_rate in self.schedule:
228-
if step < m:
229-
return lr_prev
230-
lr_prev = learning_rate
231-
return lr_prev
232-
233-
def get_config(self) -> dict:
234-
"""
235-
Returns the parameters.
236-
"""
237-
return {"schedule": self.schedule}
238-
239-
self.optimizer = tf.keras.optimizers.Adam(
240-
gradient_transformers=[lambda grads_and_vars: [(tf.sign(g), v) for (g, v) in grads_and_vars]]
241-
)
242-
self.lr_schedule = tf.keras.callbacks.LearningRateScheduler(PredefinedLRSchedule(*self.learning_rate_schedule))
243-
244136
def _initialize_poison_pytorch(
245137
self,
246138
x_trigger: np.ndarray,
@@ -394,18 +286,12 @@ def poison(
394286
:return: A list of poisoned samples, and y_train.
395287
"""
396288
from art.estimators.classification.pytorch import PyTorchClassifier
397-
from art.estimators.classification.tensorflow import TensorFlowV2Classifier
398289

399-
if isinstance(self.substitute_classifier, TensorFlowV2Classifier):
400-
poisoner = self._poison__tensorflow
401-
finish_poisoning = self._finish_poison_tensorflow
402-
elif isinstance(self.substitute_classifier, PyTorchClassifier):
290+
if isinstance(self.substitute_classifier, PyTorchClassifier):
403291
poisoner = self._poison__pytorch
404292
finish_poisoning = self._finish_poison_pytorch
405293
else:
406-
raise NotImplementedError(
407-
"GradientMatchingAttack is currently implemented only for Tensorflow V2 and Pytorch."
408-
)
294+
raise NotImplementedError("GradientMatchingAttack is currently implemented only for Pytorch.")
409295

410296
# Choose samples to poison.
411297
x_train = np.copy(x_train)
@@ -519,37 +405,6 @@ def __len__(self):
519405
count += 1
520406
return np.concatenate(all_poisoned_samples, axis=0), B_sum / count
521407

522-
def _poison__tensorflow(self, x_poison: np.ndarray, y_poison: np.ndarray) -> tuple[Any, Any]:
523-
"""
524-
Optimize the poison by matching the gradient within the perturbation budget.
525-
526-
:param x_poison: List of samples to poison.
527-
:param y_poison: List of the labels for x_poison.
528-
:return: A pair of poisoned samples, B-score (cosine similarity of the gradients).
529-
"""
530-
self.backdoor_model.compile(loss=None, optimizer=self.optimizer)
531-
532-
callbacks = [self.lr_schedule]
533-
if self.verbose > 0:
534-
from tqdm.keras import TqdmCallback
535-
536-
callbacks.append(TqdmCallback(verbose=self.verbose - 1))
537-
538-
# Train the noise.
539-
self.backdoor_model.fit(
540-
[x_poison, y_poison, np.arange(len(y_poison))],
541-
callbacks=callbacks,
542-
batch_size=self.batch_size,
543-
initial_epoch=self.initial_epoch,
544-
epochs=self.max_epochs,
545-
verbose=0,
546-
)
547-
[input_noised_, B_] = self.backdoor_model.predict( # pylint: disable=invalid-name
548-
[x_poison, y_poison, np.arange(len(y_poison))], batch_size=self.batch_size
549-
)
550-
551-
return input_noised_, B_
552-
553408
def _check_params(self) -> None:
554409
if not isinstance(self.learning_rate_schedule, tuple) or len(self.learning_rate_schedule) != 2:
555410
raise ValueError("learning_rate_schedule must be a pair of a list of learning rates and a list of epochs")

art/estimators/certification/derandomized_smoothing/tensorflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def train_step(model, images, labels):
197197
predictions = model(images, training=True)
198198
loss = self.loss_object(labels, predictions)
199199
gradients = tape.gradient(loss, model.trainable_variables)
200-
if hasattr(self.optimizer, '_check_variables_are_known'):
200+
if hasattr(self.optimizer, "_check_variables_are_known"):
201201
self.optimizer._check_variables_are_known = lambda *args, **kwargs: None
202202
self.optimizer.apply_gradients(zip(gradients, model.trainable_variables))
203203
return loss, predictions

art/estimators/classification/keras.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,14 @@ def __init__(
108108
self._model = model
109109
self._use_logits = use_logits
110110
if isinstance(model.output_shape, list):
111-
self.nb_classes = model.output_shape[output_layer][-1]
111+
nb_classes = model.output_shape[output_layer][-1]
112112
else:
113-
self.nb_classes = model.output_shape[-1]
113+
nb_classes = model.output_shape[-1]
114+
115+
# Check for binary classification
116+
if nb_classes == 1:
117+
nb_classes = 2
118+
self.nb_classes = nb_classes
114119

115120
# Ensure model is built
116121
if not model.built:
@@ -411,15 +416,16 @@ def fit(
411416
`fit_generator` function in Keras and will be passed to this function as such. Including the number of
412417
epochs or the number of steps per epoch as part of this argument will result in as error.
413418
"""
419+
y_ndim = y.ndim
414420
y = check_and_transform_label_format(y, nb_classes=self.nb_classes)
415421

416422
# Apply preprocessing
417423
x_preprocessed, y_preprocessed = self._apply_preprocessing(x, y, fit=True)
418424

419425
# Adjust the shape of y for loss functions that do not take labels in one-hot encoding
420426
loss_name = getattr(self._model.loss, "__name__", None)
421-
if loss_name in ["sparse_categorical_crossentropy", "SparseCategoricalCrossentropy"]:
422-
y_preprocessed = np.argmax(y_preprocessed, axis=1) if y_preprocessed.ndim > 1 else y_preprocessed
427+
if loss_name in ["sparse_categorical_crossentropy", "SparseCategoricalCrossentropy"] or y_ndim == 1:
428+
y_preprocessed = np.argmax(y_preprocessed, axis=1)
423429

424430
self._model.fit(
425431
x=x_preprocessed, y=y_preprocessed, batch_size=batch_size, epochs=nb_epochs, verbose=int(verbose), **kwargs

art/estimators/classification/tensorflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ def train_step(model, images, labels):
10021002
predictions = model(images, training=True)
10031003
loss = self.loss_object(labels, predictions)
10041004
gradients = tape.gradient(loss, model.trainable_variables)
1005-
if hasattr(self.optimizer, '_check_variables_are_known'):
1005+
if hasattr(self.optimizer, "_check_variables_are_known"):
10061006
self.optimizer._check_variables_are_known = lambda *args, **kwargs: None
10071007
self.optimizer.apply_gradients(zip(gradients, model.trainable_variables))
10081008

tests/attacks/poison/test_gradient_matching_attack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@
2828
logger = logging.getLogger(__name__)
2929

3030

31-
@pytest.mark.only_with_platform("pytorch", "tensorflow2")
31+
@pytest.mark.only_with_platform("pytorch")
3232
def test_poison(art_warning, get_default_mnist_subset, image_dl_estimator):
3333
try:
3434
(x_train, y_train), (x_test, y_test) = get_default_mnist_subset
35-
classifier, _ = image_dl_estimator()
35+
classifier, _ = image_dl_estimator(from_logits=True)
3636

3737
class_source = 0
3838
class_target = 1

tests/classifiersFrameworks/test_tensorflow.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def test_binary_keras_instantiation_and_attack_pgd(art_warning):
238238
]
239239
)
240240
model.summary()
241-
model.compile(optimizer=tf.optimizers.legacy.Adam(), loss="binary_crossentropy", metrics=["accuracy"])
241+
model.compile(optimizer=tf.optimizers.Adam(), loss="binary_crossentropy", metrics=["accuracy"])
242242
classifier = KerasClassifier(model=model)
243243
classifier.fit(train_x, train_y, nb_epochs=5)
244244
pred = classifier.predict(test_x)

tests/utils.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,8 @@ def make_image_discriminator_model(capacity: int) -> tf.keras.Sequential():
396396
)
397397

398398
def generator_orig_loss_fct(generated_output):
399-
return tf.compat.v1.losses.sigmoid_cross_entropy(tf.ones_like(generated_output), generated_output)
399+
loss_fn = tf.keras.losses.BinaryCrossentropy(from_logits=True)
400+
return loss_fn(tf.ones_like(generated_output), generated_output)
400401

401402
def discriminator_loss_fct(real_output, generated_output):
402403
"""Discriminator loss
@@ -409,28 +410,32 @@ def discriminator_loss_fct(real_output, generated_output):
409410
zeros (since these are the fake images).
410411
3. Calculate the total_loss as the sum of real_loss and generated_loss.
411412
"""
412-
# [1,1,...,1] with real output since it is true, and we want our generated examples to look like it
413-
real_loss = tf.compat.v1.losses.sigmoid_cross_entropy(
414-
multi_class_labels=tf.ones_like(real_output), logits=real_output
415-
)
413+
# Binary cross-entropy loss function (logits not passed through sigmoid yet)
414+
bce = tf.keras.losses.BinaryCrossentropy(from_logits=True)
416415

417-
# [0,0,...,0] with generated images since they are fake
418-
generated_loss = tf.compat.v1.losses.sigmoid_cross_entropy(
419-
multi_class_labels=tf.zeros_like(generated_output), logits=generated_output
420-
)
416+
# Real images: label as 1
417+
real_loss = bce(tf.ones_like(real_output), real_output)
418+
419+
# Generated (fake) images: label as 0
420+
generated_loss = bce(tf.zeros_like(generated_output), generated_output)
421421

422422
total_loss = real_loss + generated_loss
423423

424424
return total_loss
425425

426+
# Use native TF 2.x optimizers
427+
generator_optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
428+
discriminator_optimizer = tf.keras.optimizers.Adam(learning_rate=1e-4)
429+
426430
gan = TensorFlowV2GAN(
427431
generator=generator,
428432
discriminator=discriminator_classifier,
429433
generator_loss=generator_orig_loss_fct,
430-
generator_optimizer_fct=tf.compat.v1.train.AdamOptimizer(1e-4),
434+
generator_optimizer_fct=generator_optimizer,
431435
discriminator_loss=discriminator_loss_fct,
432-
discriminator_optimizer_fct=tf.compat.v1.train.AdamOptimizer(1e-4),
436+
discriminator_optimizer_fct=discriminator_optimizer,
433437
)
438+
434439
return gan
435440

436441

0 commit comments

Comments
 (0)