Skip to content

Commit 26c8cba

Browse files
authored
Merge pull request #765 from GiulioZizzo/development_pgd
Development PGD
2 parents e1dbdb9 + 96d0d76 commit 26c8cba

File tree

3 files changed

+181
-139
lines changed

3 files changed

+181
-139
lines changed

art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_numpy.py

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@
3636
from art.config import ART_NUMPY_DTYPE
3737
from art.estimators.classification.classifier import ClassifierMixin
3838
from art.estimators.estimator import BaseEstimator, LossGradientsMixin
39-
from art.utils import (
40-
compute_success,
41-
get_labels_np_array,
42-
check_and_transform_label_format,
43-
)
39+
from art.utils import compute_success, get_labels_np_array, check_and_transform_label_format, compute_success_array
4440

4541
if TYPE_CHECKING:
4642
from art.utils import CLASSIFIER_LOSS_GRADIENTS_TYPE, OBJECT_DETECTOR_TYPE
@@ -259,43 +255,62 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
259255
targets = self._set_targets(x, y)
260256

261257
# Start to compute adversarial examples
262-
adv_x_best = None
263-
rate_best = None
264-
265-
for _ in trange(
266-
max(1, self.num_random_init), desc="PGD - Random Initializations", disable=not self.verbose
267-
):
268-
adv_x = x.astype(ART_NUMPY_DTYPE)
269-
270-
for i_max_iter in trange(self.max_iter, desc="PGD - Iterations", leave=False, disable=not self.verbose):
271-
adv_x = self._compute(
272-
adv_x,
273-
x,
274-
targets,
275-
mask,
276-
self.eps,
277-
self.eps_step,
278-
self._project,
279-
self.num_random_init > 0 and i_max_iter == 0,
280-
)
281-
282-
if self.num_random_init > 1:
283-
rate = 100 * compute_success(
284-
self.estimator, x, targets, adv_x, self.targeted, batch_size=self.batch_size, # type: ignore
285-
)
286-
if rate_best is None or rate > rate_best or adv_x_best is None:
287-
rate_best = rate
288-
adv_x_best = adv_x
289-
else:
290-
adv_x_best = adv_x
258+
adv_x = x.astype(ART_NUMPY_DTYPE)
259+
260+
for batch_id in range(int(np.ceil(x.shape[0] / float(self.batch_size)))):
261+
for rand_init_num in trange(
262+
max(1, self.num_random_init), desc="PGD - Random Initializations", disable=not self.verbose
263+
):
264+
batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
265+
batch_index_2 = min(batch_index_2, x.shape[0])
266+
batch = x[batch_index_1:batch_index_2]
267+
batch_labels = targets[batch_index_1:batch_index_2]
268+
mask_batch = mask
269+
270+
if mask is not None:
271+
if len(mask.shape) == len(x.shape):
272+
mask_batch = mask[batch_index_1:batch_index_2]
273+
274+
for i_max_iter in trange(
275+
self.max_iter, desc="PGD - Iterations", leave=False, disable=not self.verbose
276+
):
277+
batch = self._compute(
278+
batch,
279+
x[batch_index_1:batch_index_2],
280+
batch_labels,
281+
mask_batch,
282+
self.eps,
283+
self.eps_step,
284+
self._project,
285+
self.num_random_init > 0 and i_max_iter == 0,
286+
)
287+
288+
if rand_init_num == 0:
289+
# initial (and possibly only) random restart: we only have this set of
290+
# adversarial examples for now
291+
adv_x[batch_index_1:batch_index_2] = np.copy(batch)
292+
else:
293+
# replace adversarial examples if they are successful
294+
attack_success = compute_success_array(
295+
self.estimator,
296+
x[batch_index_1:batch_index_2],
297+
targets[batch_index_1:batch_index_2],
298+
batch,
299+
self.targeted,
300+
batch_size=self.batch_size,
301+
)
302+
adv_x[batch_index_1:batch_index_2][attack_success] = batch[attack_success]
291303

292304
logger.info(
293305
"Success rate of attack: %.2f%%",
294-
rate_best
295-
if rate_best is not None
296-
else 100
306+
100
297307
* compute_success(
298-
self.estimator, x, y, adv_x_best, self.targeted, batch_size=self.batch_size, # type: ignore
308+
self.estimator,
309+
x,
310+
targets,
311+
adv_x,
312+
self.targeted,
313+
batch_size=self.batch_size, # type: ignore
299314
),
300315
)
301316
else:
@@ -323,6 +338,4 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
323338
self.num_random_init > 0 and i_max_iter == 0,
324339
)
325340

326-
adv_x_best = adv_x
327-
328-
return adv_x_best
341+
return adv_x

art/attacks/evasion/projected_gradient_descent/projected_gradient_descent_pytorch.py

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from art.attacks.evasion.projected_gradient_descent.projected_gradient_descent_numpy import (
3838
ProjectedGradientDescentCommon,
3939
)
40-
from art.utils import compute_success, random_sphere
40+
from art.utils import compute_success, random_sphere, compute_success_array
4141

4242
if TYPE_CHECKING:
4343
import torch
@@ -154,67 +154,70 @@ def generate(self, x: np.ndarray, y: Optional[np.ndarray] = None, **kwargs) -> n
154154

155155
else:
156156
dataset = torch.utils.data.TensorDataset(
157-
torch.from_numpy(x.astype(ART_NUMPY_DTYPE)), torch.from_numpy(targets.astype(ART_NUMPY_DTYPE)),
157+
torch.from_numpy(x.astype(ART_NUMPY_DTYPE)),
158+
torch.from_numpy(targets.astype(ART_NUMPY_DTYPE)),
158159
)
159160

160161
data_loader = torch.utils.data.DataLoader(
161162
dataset=dataset, batch_size=self.batch_size, shuffle=False, drop_last=False
162163
)
163164

164165
# Start to compute adversarial examples
165-
adv_x_best = None
166-
rate_best = None
167-
168-
for _ in trange(max(1, self.num_random_init), desc="PGD - Random Initializations", disable=not self.verbose):
169-
adv_x = x.astype(ART_NUMPY_DTYPE)
170-
171-
# Compute perturbation with batching
172-
for (batch_id, batch_all) in enumerate(
173-
tqdm(data_loader, desc="PGD - Batches", leave=False, disable=not self.verbose)
174-
):
175-
if mask is not None:
176-
(batch, batch_labels, mask_batch) = batch_all[0], batch_all[1], batch_all[2]
177-
else:
178-
(batch, batch_labels, mask_batch) = batch_all[0], batch_all[1], None
166+
adv_x = x.astype(ART_NUMPY_DTYPE)
179167

180-
batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
168+
# Compute perturbation with batching
169+
for (batch_id, batch_all) in enumerate(
170+
tqdm(data_loader, desc="PGD - Batches", leave=False, disable=not self.verbose)
171+
):
172+
if mask is not None:
173+
(batch, batch_labels, mask_batch) = batch_all[0], batch_all[1], batch_all[2]
174+
else:
175+
(batch, batch_labels, mask_batch) = batch_all[0], batch_all[1], None
181176

182-
# Compute batch_eps and batch_eps_step
183-
if isinstance(self.eps, np.ndarray):
184-
if len(self.eps.shape) == len(x.shape) and self.eps.shape[0] == x.shape[0]:
185-
batch_eps = self.eps[batch_index_1:batch_index_2]
186-
batch_eps_step = self.eps_step[batch_index_1:batch_index_2]
177+
batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
187178

188-
else:
189-
batch_eps = self.eps
190-
batch_eps_step = self.eps_step
179+
# Compute batch_eps and batch_eps_step
180+
if isinstance(self.eps, np.ndarray):
181+
if len(self.eps.shape) == len(x.shape) and self.eps.shape[0] == x.shape[0]:
182+
batch_eps = self.eps[batch_index_1:batch_index_2]
183+
batch_eps_step = self.eps_step[batch_index_1:batch_index_2]
191184

192185
else:
193186
batch_eps = self.eps
194187
batch_eps_step = self.eps_step
195188

196-
adv_x[batch_index_1:batch_index_2] = self._generate_batch(
197-
x=batch, targets=batch_labels, mask=mask_batch, eps=batch_eps, eps_step=batch_eps_step
198-
)
199-
200-
if self.num_random_init > 1:
201-
rate = 100 * compute_success(
202-
self.estimator, x, targets, adv_x, self.targeted, batch_size=self.batch_size
203-
)
204-
if rate_best is None or rate > rate_best or adv_x_best is None:
205-
rate_best = rate
206-
adv_x_best = adv_x
207189
else:
208-
adv_x_best = adv_x
190+
batch_eps = self.eps
191+
batch_eps_step = self.eps_step
192+
193+
for rand_init_num in range(max(1, self.num_random_init)):
194+
if rand_init_num == 0:
195+
# first iteration: use the adversarial examples as they are the only ones we have now
196+
adv_x[batch_index_1:batch_index_2] = self._generate_batch(
197+
x=batch, targets=batch_labels, mask=mask_batch, eps=batch_eps, eps_step=batch_eps_step
198+
)
199+
else:
200+
adversarial_batch = self._generate_batch(
201+
x=batch, targets=batch_labels, mask=mask_batch, eps=batch_eps, eps_step=batch_eps_step
202+
)
203+
204+
# return the successful adversarial examples
205+
attack_success = compute_success_array(
206+
self.estimator,
207+
batch,
208+
batch_labels,
209+
adversarial_batch,
210+
self.targeted,
211+
batch_size=self.batch_size,
212+
)
213+
adv_x[batch_index_1:batch_index_2][attack_success] = adversarial_batch[attack_success]
209214

210215
logger.info(
211216
"Success rate of attack: %.2f%%",
212-
rate_best
213-
if rate_best is not None
214-
else 100 * compute_success(self.estimator, x, y, adv_x_best, self.targeted, batch_size=self.batch_size),
217+
100 * compute_success(self.estimator, x, y, adv_x, self.targeted, batch_size=self.batch_size),
215218
)
216219

217-
return adv_x_best
220+
return adv_x
218221

219222
def _generate_batch(
220223
self,
@@ -245,7 +248,13 @@ def _generate_batch(
245248

246249
for i_max_iter in range(self.max_iter):
247250
adv_x = self._compute_torch(
248-
adv_x, inputs, targets, mask, eps, eps_step, self.num_random_init > 0 and i_max_iter == 0,
251+
adv_x,
252+
inputs,
253+
targets,
254+
mask,
255+
eps,
256+
eps_step,
257+
self.num_random_init > 0 and i_max_iter == 0,
249258
)
250259

251260
return adv_x.cpu().detach().numpy()
@@ -408,25 +417,31 @@ def _projection(
408417
"The parameter `eps` of type `np.ndarray` is not supported to use with norm 2."
409418
)
410419

411-
values_tmp = values_tmp * torch.min(
412-
torch.tensor([1.0], dtype=torch.float32).to(self.estimator.device),
413-
eps / (torch.norm(values_tmp, p=2, dim=1) + tol),
414-
).unsqueeze_(-1)
420+
values_tmp = (
421+
values_tmp
422+
* torch.min(
423+
torch.tensor([1.0], dtype=torch.float32).to(self.estimator.device),
424+
eps / (torch.norm(values_tmp, p=2, dim=1) + tol),
425+
).unsqueeze_(-1)
426+
)
415427

416428
elif norm_p == 1:
417429
if isinstance(eps, np.ndarray):
418430
raise NotImplementedError(
419431
"The parameter `eps` of type `np.ndarray` is not supported to use with norm 1."
420432
)
421433

422-
values_tmp = values_tmp * torch.min(
423-
torch.tensor([1.0], dtype=torch.float32).to(self.estimator.device),
424-
eps / (torch.norm(values_tmp, p=1, dim=1) + tol),
425-
).unsqueeze_(-1)
434+
values_tmp = (
435+
values_tmp
436+
* torch.min(
437+
torch.tensor([1.0], dtype=torch.float32).to(self.estimator.device),
438+
eps / (torch.norm(values_tmp, p=1, dim=1) + tol),
439+
).unsqueeze_(-1)
440+
)
426441

427442
elif norm_p in [np.inf, "inf"]:
428443
if isinstance(eps, np.ndarray):
429-
eps = eps * np.ones_like(values)
444+
eps = eps * np.ones_like(values.cpu())
430445
eps = eps.reshape([eps.shape[0], -1])
431446

432447
values_tmp = values_tmp.sign() * torch.min(

0 commit comments

Comments
 (0)