was_iterable = True
if not isinstance(epsilons, Iterable):
epsilons = [epsilons]
was_iterable = False
N = len(x)
K = len(epsilons)
// None means: just minimize, no early stopping, no limit on the perturbation size
if any(eps is None for eps in epsilons):
early_stop = None
else:
early_stop = min(epsilons)
limit_epsilons = [eps if eps is not None else ep.inf for eps in epsilons]
del epsilons
// run the actual attack
xp = self.run(model, x, criterion, early_stop=early_stop, **kwargs)
// TODO: optionally improve using a binary search?
// TODO: optionally reduce size to the different epsilons and recompute is_adv
is_adv = is_adversarial(xp)
assert is_adv.shape == (N,)
distances = self.distance(x, xp)
assert distances.shape == (N,)
in_limits = ep.stack(
[distances <= epsilon for epsilon in limit_epsilons], axis=0
)
assert in_limits.shape == (K, N)
success = ep.logical_and(in_limits, is_adv)
assert success.shape == (K, N)
xp_ = restore_type(xp)
if was_iterable:
return [xp_] * K, restore_type(success)
else:
return xp_, restore_type(success.squeeze(axis=0))
class FlexibleDistanceMinimizationAttack(MinimizationAttack):
def __init__(self, *, distance: Optional[Distance] = None):