|
import torch |
|
from vlm_eval.attacks.utils import project_perturbation, normalize_grad |
|
|
|
|
|
def pgd( |
|
forward, |
|
loss_fn, |
|
data_clean, |
|
targets, |
|
norm, |
|
eps, |
|
iterations, |
|
stepsize, |
|
output_normalize, |
|
perturbation=None, |
|
mode='min', |
|
momentum=0.9, |
|
verbose=False, |
|
need_OT=False |
|
): |
|
""" |
|
Minimize or maximize given loss |
|
""" |
|
|
|
assert torch.max(data_clean) < 1. + 1e-6 and torch.min(data_clean) > -1e-6 |
|
|
|
if perturbation is None: |
|
perturbation = torch.zeros_like(data_clean, requires_grad=True) |
|
velocity = torch.zeros_like(data_clean) |
|
for i in range(iterations): |
|
perturbation.requires_grad = True |
|
with torch.enable_grad(): |
|
out, patch_out = forward(data_clean + perturbation, output_normalize=output_normalize, need_OT=need_OT) |
|
loss = loss_fn(out, targets) |
|
if verbose: |
|
print(f'[{i}] {loss.item():.5f}') |
|
|
|
with torch.no_grad(): |
|
gradient = torch.autograd.grad(loss, perturbation)[0] |
|
gradient = gradient |
|
if gradient.isnan().any(): |
|
print(f'attention: nan in gradient ({gradient.isnan().sum()})') |
|
gradient[gradient.isnan()] = 0. |
|
|
|
gradient = normalize_grad(gradient, p=norm) |
|
|
|
velocity = momentum * velocity + gradient |
|
velocity = normalize_grad(velocity, p=norm) |
|
|
|
if mode == 'min': |
|
perturbation = perturbation - stepsize * velocity |
|
elif mode == 'max': |
|
perturbation = perturbation + stepsize * velocity |
|
else: |
|
raise ValueError(f'Unknown mode: {mode}') |
|
|
|
perturbation = project_perturbation(perturbation, eps, norm) |
|
perturbation = torch.clamp( |
|
data_clean + perturbation, 0, 1 |
|
) - data_clean |
|
assert not perturbation.isnan().any() |
|
assert torch.max(data_clean + perturbation) < 1. + 1e-6 and torch.min( |
|
data_clean + perturbation |
|
) > -1e-6 |
|
|
|
|
|
|
|
|
|
return data_clean + perturbation.detach() |
|
|