lucasddmc commited on
Commit
7aad02c
·
1 Parent(s): 641929c

feat: adds MIM attack

Browse files
Files changed (2) hide show
  1. app.py +10 -3
  2. utils/attacks.py +91 -0
app.py CHANGED
@@ -6,7 +6,7 @@ from typing import Optional, List, Tuple
6
  from utils.model_loader import load_model_and_labels
7
  from utils.preprocessing import get_default_transform, preprocess_image
8
  from utils.inference import predict_topk
9
- from utils.attacks import PGDIterations, FGSM, SAGA
10
  from utils.visualization import extract_attention_maps, attention_rollout, create_attention_overlay, extract_attention_for_iterations, create_iteration_attention_overlays
11
 
12
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
@@ -224,6 +224,8 @@ def run_attack(
224
  # Configurar ataque baseado no tipo selecionado
225
  if attack_type == "FGSM":
226
  attack = FGSM(model, eps=eps)
 
 
227
  elif attack_type == "SAGA":
228
  attack = SAGA(model, eps=eps, steps=steps)
229
  else: # PGD
@@ -275,6 +277,11 @@ def run_attack(
275
  if attack_type == "PGD":
276
  result += f"- Alpha (α): {alpha:.4f}\n"
277
  result += f"- Steps: {steps}\n"
 
 
 
 
 
278
  elif attack_type == "SAGA":
279
  result += f"- Steps: {steps}\n"
280
  result += f"- Gradiente ponderado por atenção (ViT-specific)\n"
@@ -375,10 +382,10 @@ def create_app():
375
  gr.Markdown("#### ⚔️ Configuração do Ataque")
376
 
377
  attack_type = gr.Dropdown(
378
- choices=["PGD", "FGSM", "SAGA"],
379
  value="PGD",
380
  label="Tipo de Ataque",
381
- info="PGD: iterativo | FGSM: single-step | SAGA: gradient × attention (ViT-specific)"
382
  )
383
 
384
  eps_input = gr.Slider(
 
6
  from utils.model_loader import load_model_and_labels
7
  from utils.preprocessing import get_default_transform, preprocess_image
8
  from utils.inference import predict_topk
9
+ from utils.attacks import PGDIterations, FGSM, SAGA, MIFGSM
10
  from utils.visualization import extract_attention_maps, attention_rollout, create_attention_overlay, extract_attention_for_iterations, create_iteration_attention_overlays
11
 
12
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
224
  # Configurar ataque baseado no tipo selecionado
225
  if attack_type == "FGSM":
226
  attack = FGSM(model, eps=eps)
227
+ elif attack_type == "MIFGSM":
228
+ attack = MIFGSM(model, eps=eps, alpha=alpha, steps=steps, decay=1.0)
229
  elif attack_type == "SAGA":
230
  attack = SAGA(model, eps=eps, steps=steps)
231
  else: # PGD
 
277
  if attack_type == "PGD":
278
  result += f"- Alpha (α): {alpha:.4f}\n"
279
  result += f"- Steps: {steps}\n"
280
+ elif attack_type == "MIFGSM":
281
+ result += f"- Alpha (α): {alpha:.4f}\n"
282
+ result += f"- Steps: {steps}\n"
283
+ result += f"- Momentum decay: 1.0\n"
284
+ result += f"- Gradiente normalizado com acumulação de momentum\n"
285
  elif attack_type == "SAGA":
286
  result += f"- Steps: {steps}\n"
287
  result += f"- Gradiente ponderado por atenção (ViT-specific)\n"
 
382
  gr.Markdown("#### ⚔️ Configuração do Ataque")
383
 
384
  attack_type = gr.Dropdown(
385
+ choices=["PGD", "FGSM", "MIFGSM", "SAGA"],
386
  value="PGD",
387
  label="Tipo de Ataque",
388
+ info="PGD: iterativo | FGSM: single-step | MIFGSM: momentum | SAGA: gradient × attention"
389
  )
390
 
391
  eps_input = gr.Slider(
utils/attacks.py CHANGED
@@ -317,4 +317,95 @@ class SAGA(torch.nn.Module):
317
 
318
  # Retornar normalizado
319
  adv_images = (adv_images_denorm - mean) / std
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  return adv_images, self.iteration_images
 
317
 
318
  # Retornar normalizado
319
  adv_images = (adv_images_denorm - mean) / std
320
+ return adv_images, self.iteration_images
321
+
322
+
323
+ class MIFGSM(torchattacks.MIFGSM):
324
+ """
325
+ MI-FGSM: Momentum Iterative Fast Gradient Sign Method
326
+
327
+ Extensão do ataque MIFGSM que captura imagens e atenção de cada iteração.
328
+ Usa momentum para estabilizar direção do gradiente e melhorar transferabilidade.
329
+
330
+ Paper: "Boosting Adversarial Attacks with Momentum" (2017)
331
+ https://arxiv.org/abs/1710.06081
332
+ """
333
+ def __init__(self, model, eps=8/255, alpha=2/255, steps=10, decay=1.0):
334
+ super().__init__(model, eps=eps, alpha=alpha, steps=steps, decay=decay)
335
+ self.iteration_images: List[Image.Image] = []
336
+ self.iteration_tensors: List[torch.Tensor] = []
337
+
338
+ def forward(self, images, labels) -> Tuple[torch.Tensor, List[Image.Image]]:
339
+ """
340
+ Executa o ataque MI-FGSM e retorna:
341
+ - adv_images: tensor adversarial final
342
+ - iteration_images: lista de PIL Images (uma por iteração)
343
+
344
+ Implementação adaptada para trabalhar com imagens normalizadas ImageNet
345
+ e capturar todas as iterações.
346
+ """
347
+ images = images.clone().detach().to(self.device)
348
+ labels = labels.clone().detach().to(self.device)
349
+
350
+ if self.targeted:
351
+ target_labels = self.get_target_label(images, labels)
352
+
353
+ loss = torch.nn.CrossEntropyLoss()
354
+
355
+ # Desnormalizar para aplicar eps e clipping no espaço correto [0,1]
356
+ mean = torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1).to(self.device)
357
+ std = torch.tensor([0.229, 0.224, 0.225]).view(1, 3, 1, 1).to(self.device)
358
+
359
+ images_denorm = images * std + mean
360
+ adv_images_denorm = images_denorm.clone().detach()
361
+
362
+ # Inicializar momentum
363
+ momentum = torch.zeros_like(images).detach().to(self.device)
364
+
365
+ self.iteration_images = []
366
+ self.iteration_tensors = []
367
+
368
+ # Salvar iteração 0 (imagem original)
369
+ pil_img_orig = tensor_to_pil(images_denorm[0], denormalize=False)
370
+ self.iteration_images.append(pil_img_orig)
371
+ self.iteration_tensors.append(images.clone().detach())
372
+
373
+ for _ in range(self.steps):
374
+ # Normalizar para passar pelo modelo
375
+ adv_images = (adv_images_denorm - mean) / std
376
+ adv_images.requires_grad = True
377
+ outputs = self.get_logits(adv_images)
378
+
379
+ # Calcular loss
380
+ if self.targeted:
381
+ cost = -loss(outputs, target_labels)
382
+ else:
383
+ cost = loss(outputs, labels)
384
+
385
+ # Calcular gradiente
386
+ grad = torch.autograd.grad(cost, adv_images,
387
+ retain_graph=False, create_graph=False)[0]
388
+
389
+ # Normalizar gradiente (chave do MI-FGSM!)
390
+ grad = grad / torch.mean(torch.abs(grad), dim=(1, 2, 3), keepdim=True)
391
+
392
+ # Aplicar momentum
393
+ grad = grad + momentum * self.decay
394
+ momentum = grad
395
+
396
+ # Voltar para espaço desnormalizado para aplicar perturbação
397
+ adv_images_denorm = adv_images_denorm.detach() + self.alpha * grad.sign() * std
398
+ delta = torch.clamp(adv_images_denorm - images_denorm, min=-self.eps, max=self.eps)
399
+ adv_images_denorm = torch.clamp(images_denorm + delta, min=0, max=1).detach()
400
+
401
+ # Normalizar para salvar tensor
402
+ adv_images_normalized = (adv_images_denorm - mean) / std
403
+
404
+ # Capturar imagem e tensor desta iteração
405
+ pil_img = tensor_to_pil(adv_images_denorm[0], denormalize=False)
406
+ self.iteration_images.append(pil_img)
407
+ self.iteration_tensors.append(adv_images_normalized.clone().detach())
408
+
409
+ # Retornar imagem normalizada para o modelo
410
+ adv_images = (adv_images_denorm - mean) / std
411
  return adv_images, self.iteration_images