Skip to content

Commit 040b4eb

Browse files
committed
Fix dependencies
Signed-off-by: Beat Buesser <[email protected]>
1 parent d2e65d4 commit 040b4eb

12 files changed

+969
-962
lines changed

art/attacks/extraction/functionally_equivalent_extraction.py

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,9 @@ def extract(
110110
:param rel_diff_slope: Relative slope difference at critical points.
111111
:param rel_diff_value: Relative value difference at critical points.
112112
:param delta_init_value: Initial delta of weight value search.
113-
:param delta_value_max: Maximum delta of weight value search.
113+
:param delta_value_max: Maximum delta of weight value search.
114114
:param d2_min: Minimum acceptable value of sum of absolute second derivatives.
115-
:param d_step: Step size of delta increase.
115+
:param d_step: Step size of delta increase.
116116
:param delta_sign: Delta of weight sign search.
117117
:param unit_vector_scale: Multiplicative scale of the unit vector `e_j`.
118118
:param ftol: Tolerance for termination by the change of the cost function.
@@ -309,6 +309,7 @@ def _weight_recovery(
309309

310310
for i in range(self.num_neurons):
311311
for k in range(self.num_features):
312+
print("a0_pairwise_ratios", i, k)
312313
self.a0_pairwise_ratios[k, i] = d2_ol_d2ej_xi[0, i] / d2_ol_d2ej_xi[k, i]
313314

314315
# Weight Sign Recovery
@@ -428,84 +429,96 @@ def f_w_1_b_1(w_1_b_1_i):
428429

429430
# pylint: disable=invalid-name
430431
if __name__ == "__main__":
432+
import os
433+
import numpy as np
431434
import tensorflow as tf
432435

433-
tf.compat.v1.disable_eager_execution()
434-
tf.keras.backend.set_floatx("float64")
435-
436-
from tensorflow.keras.datasets import mnist
437-
from tensorflow.keras.models import Sequential
438-
from tensorflow.keras.layers import Dense
436+
from keras.models import Sequential, load_model
437+
from keras.layers import Dense, Input
438+
from keras.losses import CategoricalCrossentropy
439+
from keras.optimizers import Adam
440+
from keras.utils import to_categorical
441+
from keras.datasets import mnist
439442

443+
# Keras 3.10+ runs in eager mode by default (do NOT disable it!)
444+
tf.keras.backend.set_floatx("float64")
440445
np.random.seed(1)
441-
number_neurons = 16
442-
batch_size = 128
446+
447+
# Hyperparameters
448+
number_neurons = 4
449+
batch_size = 10
443450
number_classes = 10
444-
epochs = 10
451+
epochs = 100
445452
img_rows = 28
446453
img_cols = 28
447454
number_channels = 1
448455

456+
# Load and reshape data
449457
(x_train, y_train), (x_test, y_test) = mnist.load_data()
450-
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, number_channels)
451-
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, number_channels)
452-
input_shape = (number_channels * img_rows * img_cols,)
453-
454-
x_train = x_train.reshape((x_train.shape[0], number_channels * img_rows * img_cols)).astype("float64")
455-
x_test = x_test.reshape((x_test.shape[0], number_channels * img_rows * img_cols)).astype("float64")
458+
x_train = x_train.reshape((x_train.shape[0], -1)).astype("float64") # shape = (60000, 784)
459+
x_test = x_test.reshape((x_test.shape[0], -1)).astype("float64") # shape = (10000, 784)
456460

461+
# Standardize
457462
mean = np.mean(x_train)
458463
std = np.std(x_train)
459-
460464
x_train = (x_train - mean) / std
461465
x_test = (x_test - mean) / std
462466

463-
y_train = tf.keras.utils.to_categorical(y_train, number_classes)
464-
y_test = tf.keras.utils.to_categorical(y_test, number_classes)
467+
# One-hot encode
468+
y_train = to_categorical(y_train, number_classes)
469+
y_test = to_categorical(y_test, number_classes)
465470

466-
if os.path.isfile("./model.h5"):
467-
model = tf.keras.models.load_model("./model.h5")
468-
else:
469-
model = Sequential()
470-
model.add(Dense(number_neurons, activation="relu", input_shape=input_shape))
471-
model.add(Dense(number_classes, activation="linear"))
471+
# Define input shape
472+
input_shape = (784,)
472473

474+
# Load or create model
475+
if os.path.isfile("./model.keras"):
476+
model = load_model("./model.keras", compile=False)
473477
model.compile(
474-
loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True),
475-
optimizer=tf.keras.optimizers.Adam(
476-
learning_rate=0.0001,
477-
),
478-
metrics=["accuracy"],
478+
loss=CategoricalCrossentropy(from_logits=True), optimizer=Adam(learning_rate=0.0001), metrics=["accuracy"]
479+
)
480+
else:
481+
model = Sequential(
482+
[
483+
Input(shape=input_shape),
484+
Dense(number_neurons, activation="relu"),
485+
Dense(number_classes, activation="linear"),
486+
]
487+
)
488+
model.compile(
489+
loss=CategoricalCrossentropy(from_logits=True), optimizer=Adam(learning_rate=0.001), metrics=["accuracy"]
479490
)
480-
481491
model.fit(
482-
x_train,
483-
y_train,
492+
x_train[0:100],
493+
y_train[0:100],
484494
batch_size=batch_size,
485495
epochs=epochs,
486496
verbose=1,
487497
validation_data=(x_test, y_test),
488498
)
499+
model.save("./model.keras")
489500

490-
model.save("./model.h5")
491-
501+
# Evaluate target model
492502
score_target = model.evaluate(x_test, y_test, verbose=0)
493503

504+
# Wrap with ART
494505
target_classifier = KerasClassifier(model=model, use_logits=True, clip_values=(0, 1))
495506

507+
# Run Functionally Equivalent Extraction
496508
fee = FunctionallyEquivalentExtraction(classifier=target_classifier, num_neurons=number_neurons) # type: ignore
497509
bbc = fee.extract(x_test[0:100])
498510

511+
# Predictions
499512
y_test_predicted_extracted = bbc.predict(x_test)
500513
y_test_predicted_target = target_classifier.predict(x_test)
501514

515+
# Metrics
502516
print("Target model - Test accuracy:", score_target[1])
503517
print(
504518
"Extracted model - Test accuracy:",
505-
np.sum(np.argmax(y_test_predicted_extracted, axis=1) == np.argmax(y_test, axis=1)) / y_test.shape[0],
519+
np.mean(np.argmax(y_test_predicted_extracted, axis=1) == np.argmax(y_test, axis=1)),
506520
)
507521
print(
508522
"Extracted model - Test Fidelity:",
509-
np.sum(np.argmax(y_test_predicted_extracted, axis=1) == np.argmax(y_test_predicted_target, axis=1))
510-
/ y_test_predicted_target.shape[0],
523+
np.mean(np.argmax(y_test_predicted_extracted, axis=1) == np.argmax(y_test_predicted_target, axis=1)),
511524
)

art/attacks/poisoning/adversarial_embedding_attack.py

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -104,60 +104,65 @@ def __init__(
104104
self._check_params()
105105

106106
if isinstance(self.estimator, KerasClassifier):
107-
using_tf_keras = "tensorflow.python.keras" in str(type(self.estimator.model))
108-
if using_tf_keras: # pragma: no cover
109-
from tensorflow.keras.models import Model, clone_model
110-
from tensorflow.keras.layers import (
111-
GaussianNoise,
112-
Dense,
113-
BatchNormalization,
114-
LeakyReLU,
115-
)
116-
from tensorflow.keras.optimizers import Adam
117-
118-
opt = Adam(learning_rate=self.learning_rate)
119-
120-
else:
121-
from keras import Model
122-
from keras.models import clone_model
123-
from keras.layers import GaussianNoise, Dense, BatchNormalization, LeakyReLU
124107

125-
try:
126-
from keras.optimizers import Adam
127-
128-
opt = Adam(learning_rate=self.learning_rate)
129-
except ImportError:
130-
from keras.optimizers import adam_v2
108+
from keras.models import Model, clone_model
109+
from keras.layers import (
110+
GaussianNoise,
111+
Dense,
112+
BatchNormalization,
113+
LeakyReLU,
114+
Input,
115+
Flatten,
116+
)
117+
from keras.optimizers import Adam
118+
import keras
131119

132-
opt = adam_v2.Adam(learning_rate=self.learning_rate)
120+
opt = Adam(learning_rate=self.learning_rate)
133121

122+
# Clone and build model
134123
if clone:
135-
self.orig_model = clone_model(self.estimator.model, input_tensors=self.estimator.model.inputs)
124+
self.orig_model = clone_model(self.estimator.model)
125+
self.orig_model.set_weights(self.estimator.model.get_weights())
136126
else:
137127
self.orig_model = self.estimator.model
128+
129+
# Ensure model is built (important for Sequential models)
130+
if not self.orig_model.built:
131+
# Provide a dummy input shape based on the estimator input
132+
dummy_input_shape = (None,) + self.estimator.input_shape[1:]
133+
self.orig_model.build(dummy_input_shape)
134+
135+
# Access model input/output (safe for Functional & Sequential)
138136
model_input = self.orig_model.inputs
139137
init_model_output = self.orig_model(model_input)
140138

141-
# Extracting feature tensor
139+
# Extract feature layer output
142140
if isinstance(self.feature_layer, int):
143141
feature_layer_tensor = self.orig_model.layers[self.feature_layer].output
144142
else:
145-
feature_layer_tensor = self.orig_model.get_layer(name=feature_layer).output
146-
feature_layer_output = Model(inputs=[model_input], outputs=[feature_layer_tensor])
147-
148-
# Architecture for discriminator
149-
discriminator_input = feature_layer_output(model_input)
150-
discriminator_input = GaussianNoise(stddev=1)(discriminator_input)
151-
dense_layer_1 = Dense(self.discriminator_layer_1)(discriminator_input)
152-
norm_1_layer = BatchNormalization()(dense_layer_1)
153-
leaky_layer_1 = LeakyReLU(alpha=0.2)(norm_1_layer)
154-
dense_layer_2 = Dense(self.discriminator_layer_2)(leaky_layer_1)
155-
norm_2_layer = BatchNormalization()(dense_layer_2)
156-
leaky_layer_2 = LeakyReLU(alpha=0.2)(norm_2_layer)
157-
backdoor_detect = Dense(2, activation="softmax", name="backdoor_detect")(leaky_layer_2)
158-
159-
# Creating embedded model
160-
self.embed_model = Model(inputs=self.orig_model.inputs, outputs=[init_model_output, backdoor_detect])
143+
feature_layer_tensor = self.orig_model.get_layer(name=self.feature_layer).output
144+
145+
feature_extractor = Model(inputs=model_input, outputs=feature_layer_tensor)
146+
147+
# Discriminator architecture
148+
discriminator_input = feature_extractor(model_input)
149+
if len(discriminator_input.shape) > 2:
150+
discriminator_input = Flatten()(discriminator_input)
151+
152+
discriminator_input = GaussianNoise(stddev=1.0)(discriminator_input)
153+
154+
x = Dense(self.discriminator_layer_1)(discriminator_input)
155+
x = BatchNormalization()(x)
156+
x = LeakyReLU(alpha=0.2)(x)
157+
158+
x = Dense(self.discriminator_layer_2)(x)
159+
x = BatchNormalization()(x)
160+
x = LeakyReLU(alpha=0.2)(x)
161+
162+
backdoor_detect = Dense(2, activation="softmax", name="backdoor_detect")(x)
163+
164+
# Final embedded model
165+
self.embed_model = Model(inputs=model_input, outputs=[init_model_output, backdoor_detect])
161166

162167
# Add backdoor detection loss
163168
model_name = self.orig_model.name
@@ -175,7 +180,9 @@ def __init__(
175180
else:
176181
raise TypeError(f"Cannot read model loss value of type {type(model_loss)}")
177182

178-
self.embed_model.compile(optimizer=opt, loss=losses, loss_weights=loss_weights, metrics=["accuracy"])
183+
self.embed_model.compile(
184+
optimizer=opt, loss=losses, loss_weights=loss_weights, metrics=["accuracy", "accuracy"]
185+
)
179186
else:
180187
raise NotImplementedError("This attack currently only supports Keras.")
181188

art/attacks/poisoning/feature_collision_attack.py

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from art.attacks.attack import PoisoningAttackWhiteBox
3131
from art.estimators import BaseEstimator, NeuralNetworkMixin
3232
from art.estimators.classification.classifier import ClassifierMixin
33-
from art.estimators.classification.keras import KerasClassifier
3433
from art.estimators.classification.pytorch import PyTorchClassifier
3534

3635

@@ -112,14 +111,7 @@ def __init__(
112111
self.verbose = verbose
113112
self._check_params()
114113

115-
if isinstance(self.estimator, KerasClassifier):
116-
self.target_placeholder, self.target_feature_rep = self.estimator.get_activations(
117-
self.target, self.feature_layer, 1, framework=True
118-
)
119-
self.poison_placeholder, self.poison_feature_rep = self.estimator.get_activations(
120-
self.target, self.feature_layer, 1, framework=True
121-
)
122-
elif isinstance(self.estimator, PyTorchClassifier):
114+
if isinstance(self.estimator, PyTorchClassifier):
123115
self.target_feature_rep = self.estimator.get_activations(self.target, self.feature_layer, 1, framework=True)
124116
self.poison_feature_rep = self.estimator.get_activations(self.target, self.feature_layer, 1, framework=True)
125117
else:
@@ -192,14 +184,7 @@ def forward_step(self, poison: np.ndarray) -> np.ndarray:
192184
:param poison: the current poison samples.
193185
:return: poison example closer in feature representation to target space.
194186
"""
195-
if isinstance(self.estimator, KerasClassifier):
196-
(attack_grad,) = self.estimator.custom_loss_gradient(
197-
self.attack_loss,
198-
[self.poison_placeholder, self.target_placeholder],
199-
[poison, self.target],
200-
name="feature_collision_" + str(self.feature_layer),
201-
)
202-
elif isinstance(self.estimator, PyTorchClassifier):
187+
if isinstance(self.estimator, PyTorchClassifier):
203188
attack_grad = self.estimator.custom_loss_gradient(self.attack_loss, poison, self.target, self.feature_layer)
204189
else:
205190
raise ValueError("The type of the estimator is not supported.")
@@ -295,22 +280,12 @@ def tensor_norm(tensor, norm_type: int | float | str = 2): # pylint: disable=in
295280
:param norm_type: Order of the norm.
296281
:return: A tensor with the norm applied.
297282
"""
298-
tf_tensor_types = (
299-
"tensorflow.python.framework.ops.Tensor",
300-
"tensorflow.python.framework.ops.EagerTensor",
301-
"tensorflow.python.framework.ops.SymbolicTensor",
302-
)
303283
torch_tensor_types = ("torch.Tensor", "torch.float", "torch.double", "torch.long")
304-
supported_types = tf_tensor_types + torch_tensor_types
284+
supported_types = torch_tensor_types
305285
tensor_type = get_class_name(tensor)
306286
if tensor_type not in supported_types: # pragma: no cover
307287
raise TypeError("Tensor type `" + tensor_type + "` is not supported")
308288

309-
if tensor_type in tf_tensor_types:
310-
import tensorflow as tf
311-
312-
return tf.norm(tensor, ord=norm_type)
313-
314289
if tensor_type in torch_tensor_types: # pragma: no cover
315290
import torch
316291

art/attacks/poisoning/gradient_matching_attack.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ def __len__(self):
380380
self.backdoor_model.zero_grad()
381381
loss, poisoned_samples = self.backdoor_model(x, indices, y, self.grad_ws_norm)
382382
loss.backward()
383-
self.backdoor_model.noise_embedding.embedding_layer.weight.grad.sign_()
383+
self.backdoor_model.noise_embedding.embedding_layer.weight.grad.sign_() # type: ignore
384384
self.optimizer.step()
385385
sum_loss += loss.clone().cpu().detach().numpy()
386386
count += 1

art/estimators/classification/keras.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -210,24 +210,31 @@ def compute_loss(self, x: np.ndarray, y: np.ndarray, reduction: str = "none", **
210210
predictions = self._model(x_tf, training=False)
211211

212212
# Compute loss (no need to access .loss attribute directly)
213-
loss_tensor = self._model.compiled_loss(y_tf, predictions, regularization_losses=None)
213+
loss_tensor = self._model.compiled_loss(y_tf, predictions)
214214

215215
# Convert loss tensor to numpy
216216
loss_value = loss_tensor.numpy()
217217

218218
# Apply user-specified reduction
219219
if reduction == "none":
220-
pass
220+
loss_value_list = []
221+
for i in range(x_tf.shape[0]):
222+
predictions_i = self._model(x_tf[i : i + 1], training=False)
223+
loss_tensor_i = self._model.compiled_loss(y_tf[i : i + 1], predictions_i)
224+
loss_value_list.append(loss_tensor_i.numpy())
225+
loss_value = np.array(loss_value_list)
226+
221227
elif reduction == "mean":
222-
if loss_value.ndim > 0:
223-
loss_value = np.mean(loss_value, axis=0)
224-
else:
225-
loss_value = np.mean(loss_value)
228+
predictions = self._model(x_tf, training=False)
229+
loss_tensor = self._model.compiled_loss(y_tf, predictions)
230+
loss_value = loss_tensor.numpy()
231+
226232
elif reduction == "sum":
227-
if loss_value.ndim > 0:
228-
loss_value = np.sum(loss_value, axis=0)
229-
else:
230-
loss_value = np.sum(loss_value)
233+
loss_value = 0
234+
for i in range(x_tf.shape[0]):
235+
predictions_i = self._model(x_tf[i : i + 1], training=False)
236+
loss_tensor_i = self._model.compiled_loss(y_tf[i : i + 1], predictions_i)
237+
loss_value += loss_tensor_i.numpy()
231238

232239
return loss_value
233240

@@ -391,9 +398,9 @@ def predict(self, x: np.ndarray, batch_size: int = 128, training_mode: bool = Fa
391398

392399
# Run predictions with batching
393400
if training_mode:
394-
predictions = self._model(x_preprocessed, training=training_mode)
401+
predictions = self._model(x_preprocessed, training=training_mode, verbose=False)
395402
else:
396-
predictions = self._model.predict(x_preprocessed, batch_size=batch_size)
403+
predictions = self._model.predict(x_preprocessed, batch_size=batch_size, verbose=False)
397404

398405
# Apply postprocessing
399406
predictions = self._apply_postprocessing(preds=predictions, fit=False)

0 commit comments

Comments
 (0)