@@ -116,25 +116,14 @@ def _initialize_poison(
116
116
:param y_train: A list of labels for x_train.
117
117
"""
118
118
from art .estimators .classification .pytorch import PyTorchClassifier
119
- from art .estimators .classification .tensorflow import TensorFlowV2Classifier
120
119
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 ):
124
121
initializer = self ._initialize_poison_pytorch
125
122
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." )
129
124
130
125
return initializer (x_trigger , y_trigger , x_poison , y_poison )
131
126
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
-
138
127
def _finish_poison_pytorch (self ):
139
128
"""
140
129
Releases any resource and revert back unwanted change to the model.
@@ -144,103 +133,6 @@ def _finish_poison_pytorch(self):
144
133
else :
145
134
self .substitute_classifier .model .eval ()
146
135
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
-
244
136
def _initialize_poison_pytorch (
245
137
self ,
246
138
x_trigger : np .ndarray ,
@@ -394,18 +286,12 @@ def poison(
394
286
:return: A list of poisoned samples, and y_train.
395
287
"""
396
288
from art .estimators .classification .pytorch import PyTorchClassifier
397
- from art .estimators .classification .tensorflow import TensorFlowV2Classifier
398
289
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 ):
403
291
poisoner = self ._poison__pytorch
404
292
finish_poisoning = self ._finish_poison_pytorch
405
293
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." )
409
295
410
296
# Choose samples to poison.
411
297
x_train = np .copy (x_train )
@@ -519,37 +405,6 @@ def __len__(self):
519
405
count += 1
520
406
return np .concatenate (all_poisoned_samples , axis = 0 ), B_sum / count
521
407
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
-
553
408
def _check_params (self ) -> None :
554
409
if not isinstance (self .learning_rate_schedule , tuple ) or len (self .learning_rate_schedule ) != 2 :
555
410
raise ValueError ("learning_rate_schedule must be a pair of a list of learning rates and a list of epochs" )
0 commit comments