def test_all_axes(dummy: Tensor) -> Tensor: t = ep.arange(dummy, 30).float32().reshape((3, 5, 2)) return ep.all(t > 3, axis=(0, 1))
def test_all_none_keepdims(t: Tensor) -> Tensor: return ep.all(t > 3, axis=None, keepdims=True)
def test_all_axis(t: Tensor) -> Tensor: return ep.all(t > 3, axis=0)
def test_all(t: Tensor) -> Tensor: return ep.all(t > 3)
def __call__( self, model: Model, inputs: T, criterion: Union[Misclassification, TargetedMisclassification, T], ) -> T: x, restore_type = ep.astensor_(inputs) criterion_ = get_criterion(criterion) del inputs, criterion N = len(x) if isinstance(criterion_, Misclassification): targeted = False classes = criterion_.labels change_classes_logits = self.confidence elif isinstance(criterion_, TargetedMisclassification): targeted = True classes = criterion_.target_classes change_classes_logits = -self.confidence else: raise ValueError("unsupported criterion") def is_adversarial(perturbed: ep.Tensor, logits: ep.Tensor) -> ep.Tensor: if change_classes_logits != 0: logits += ep.onehot_like(logits, classes, value=change_classes_logits) return criterion_(perturbed, logits) if classes.shape != (N,): name = "target_classes" if targeted else "labels" raise ValueError( f"expected {name} to have shape ({N},), got {classes.shape}" ) min_, max_ = model.bounds rows = range(N) def loss_fun(y_k: ep.Tensor, consts: ep.Tensor) -> Tuple[ep.Tensor, ep.Tensor]: assert y_k.shape == x.shape assert consts.shape == (N,) logits = model(y_k) if targeted: c_minimize = best_other_classes(logits, classes) c_maximize = classes else: c_minimize = classes c_maximize = best_other_classes(logits, classes) is_adv_loss = logits[rows, c_minimize] - logits[rows, c_maximize] assert is_adv_loss.shape == (N,) is_adv_loss = is_adv_loss + self.confidence is_adv_loss = ep.maximum(0, is_adv_loss) is_adv_loss = is_adv_loss * consts squared_norms = flatten(y_k - x).square().sum(axis=-1) loss = is_adv_loss.sum() + squared_norms.sum() return loss, logits loss_aux_and_grad = ep.value_and_grad_fn(x, loss_fun, has_aux=True) consts = self.initial_const * ep.ones(x, (N,)) lower_bounds = ep.zeros(x, (N,)) upper_bounds = ep.inf * ep.ones(x, (N,)) best_advs = ep.zeros_like(x) best_advs_norms = ep.ones(x, (N,)) * ep.inf # the binary search searches for the smallest consts that produce adversarials for binary_search_step in range(self.binary_search_steps): if ( binary_search_step == self.binary_search_steps - 1 and self.binary_search_steps >= 10 ): # in the last iteration, repeat the search once consts = ep.minimum(upper_bounds, 1e10) # create a new optimizer find the delta that minimizes the loss x_k = x y_k = x found_advs = ep.full( x, (N,), value=False ).bool() # found adv with the current consts loss_at_previous_check = ep.ones(x, (1,)) * ep.inf for iteration in range(self.steps): # square-root learning rate decay stepsize = self.initial_stepsize * (1.0 - iteration / self.steps) ** 0.5 loss, logits, gradient = loss_aux_and_grad(y_k, consts) x_k_old = x_k x_k = project_shrinkage_thresholding( y_k - stepsize * gradient, x, self.regularization, min_, max_ ) y_k = x_k + iteration / (iteration + 3.0) * (x_k - x_k_old) if self.abort_early and iteration % (math.ceil(self.steps / 10)) == 0: # after each tenth of the iterations, check progress # TODO: loss is a scalar ep tensor. is this the bst way to # implement the condition? if not ep.all(loss <= 0.9999 * loss_at_previous_check): break # stop optimization if there has been no progress loss_at_previous_check = loss found_advs_iter = is_adversarial(x_k, logits) best_advs, best_advs_norms = apply_decision_rule( self.decision_rule, self.regularization, best_advs, best_advs_norms, x_k, x, found_advs_iter, ) found_advs = ep.logical_or(found_advs, found_advs_iter) upper_bounds = ep.where(found_advs, consts, upper_bounds) lower_bounds = ep.where(found_advs, lower_bounds, consts) consts_exponential_search = consts * 10 consts_binary_search = (lower_bounds + upper_bounds) / 2 consts = ep.where( ep.isinf(upper_bounds), consts_exponential_search, consts_binary_search ) return restore_type(best_advs)
def run( self, model: Model, inputs: T, criterion: Union[Criterion, T], *, early_stop: Optional[float] = None, starting_points: Optional[T] = None, **kwargs: Any, ) -> T: raise_if_kwargs(kwargs) originals, restore_type = ep.astensor_(inputs) del inputs, kwargs verify_input_bounds(originals, model) criterion = get_criterion(criterion) is_adversarial = get_is_adversarial(criterion, model) if starting_points is None: init_attack: MinimizationAttack if self.init_attack is None: init_attack = LinearSearchBlendedUniformNoiseAttack(steps=50) logging.info( f"Neither starting_points nor init_attack given. Falling" f" back to {init_attack!r} for initialization.") else: init_attack = self.init_attack # TODO: use call and support all types of attacks (once early_stop is # possible in __call__) x_advs = init_attack.run(model, originals, criterion, early_stop=early_stop) else: x_advs = ep.astensor(starting_points) is_adv = is_adversarial(x_advs) if not is_adv.all(): failed = is_adv.logical_not().float32().sum() if starting_points is None: raise ValueError( f"init_attack failed for {failed} of {len(is_adv)} inputs") else: raise ValueError( f"{failed} of {len(is_adv)} starting_points are not adversarial" ) del starting_points tb = TensorBoard(logdir=self.tensorboard) # Project the initialization to the boundary. x_advs = self._binary_search(is_adversarial, originals, x_advs) assert ep.all(is_adversarial(x_advs)) distances = self.distance(originals, x_advs) for step in range(self.steps): delta = self.select_delta(originals, distances, step) # Choose number of gradient estimation steps. num_gradient_estimation_steps = int( min([ self.initial_num_evals * math.sqrt(step + 1), self.max_num_evals ])) gradients = self.approximate_gradients( is_adversarial, x_advs, num_gradient_estimation_steps, delta) if self.constraint == "linf": update = ep.sign(gradients) else: update = gradients if self.stepsize_search == "geometric_progression": # find step size. epsilons = distances / math.sqrt(step + 1) while True: x_advs_proposals = ep.clip( x_advs + atleast_kd(epsilons, x_advs.ndim) * update, 0, 1) success = is_adversarial(x_advs_proposals) epsilons = ep.where(success, epsilons, epsilons / 2.0) if ep.all(success): break # Update the sample. x_advs = ep.clip( x_advs + atleast_kd(epsilons, update.ndim) * update, 0, 1) assert ep.all(is_adversarial(x_advs)) # Binary search to return to the boundary. x_advs = self._binary_search(is_adversarial, originals, x_advs) assert ep.all(is_adversarial(x_advs)) elif self.stepsize_search == "grid_search": # Grid search for stepsize. epsilons_grid = ep.expand_dims( ep.from_numpy( distances, np.logspace( -4, 0, num=20, endpoint=True, dtype=np.float32), ), 1, ) * ep.expand_dims(distances, 0) proposals_list = [] for epsilons in epsilons_grid: x_advs_proposals = ( x_advs + atleast_kd(epsilons, update.ndim) * update) x_advs_proposals = ep.clip(x_advs_proposals, 0, 1) mask = is_adversarial(x_advs_proposals) x_advs_proposals = self._binary_search( is_adversarial, originals, x_advs_proposals) # only use new values where initial guess was already adversarial x_advs_proposals = ep.where(atleast_kd(mask, x_advs.ndim), x_advs_proposals, x_advs) proposals_list.append(x_advs_proposals) proposals = ep.stack(proposals_list, 0) proposals_distances = self.distance( ep.expand_dims(originals, 0), proposals) minimal_idx = ep.argmin(proposals_distances, 0) x_advs = proposals[minimal_idx] distances = self.distance(originals, x_advs) # log stats tb.histogram("norms", distances, step) return restore_type(x_advs)
def run( self, model: Model, inputs: T, criterion: Union[Criterion, Any] = None, *, starting_points: Optional[ep.Tensor] = None, early_stop: Optional[float] = None, **kwargs: Any, ) -> T: raise_if_kwargs(kwargs) del kwargs x, restore_type = ep.astensor_(inputs) del inputs verify_input_bounds(x, model) criterion_ = get_criterion(criterion) del criterion is_adversarial = get_is_adversarial(criterion_, model) if starting_points is None: init_attack: MinimizationAttack if self.init_attack is None: init_attack = SaltAndPepperNoiseAttack() logging.info( f"Neither starting_points nor init_attack given. Falling" f" back to {init_attack!r} for initialization." ) else: init_attack = self.init_attack # TODO: use call and support all types of attacks (once early_stop is # possible in __call__) starting_points = init_attack.run(model, x, criterion_) x_adv = ep.astensor(starting_points) assert is_adversarial(x_adv).all() original_shape = x.shape N = len(x) x_flat = flatten(x) x_adv_flat = flatten(x_adv) # was there a pixel left in the samples to manipulate, # i.e. reset to the clean version? found_index_to_manipulate = ep.from_numpy(x, np.ones(N, dtype=bool)) while ep.any(found_index_to_manipulate): diff_mask = (ep.abs(x_flat - x_adv_flat) > 1e-8).numpy() diff_idxs = [z.nonzero()[0] for z in diff_mask] untouched_indices = [z.tolist() for z in diff_idxs] untouched_indices = [ np.random.permutation(it).tolist() for it in untouched_indices ] found_index_to_manipulate = ep.from_numpy(x, np.zeros(N, dtype=bool)) # since the number of pixels still left to manipulate might differ # across different samples we track each of them separately and # and manipulate the images until there is no pixel left for # any of the samples. to not update already finished samples, we mask # the updates such that only samples that still have pixels left to manipulate # will be updated i = 0 while i < max([len(it) for it in untouched_indices]): # mask all samples that still have pixels to manipulate left relevant_mask = [len(it) > i for it in untouched_indices] relevant_mask = np.array(relevant_mask, dtype=bool) relevant_mask_index = np.flatnonzero(relevant_mask) # for each image get the index of the next pixel we try out relevant_indices = [it[i] for it in untouched_indices if len(it) > i] old_values = x_adv_flat[relevant_mask_index, relevant_indices] new_values = x_flat[relevant_mask_index, relevant_indices] x_adv_flat = ep.index_update( x_adv_flat, (relevant_mask_index, relevant_indices), new_values ) # check if still adversarial is_adv = is_adversarial(x_adv_flat.reshape(original_shape)) found_index_to_manipulate = ep.index_update( found_index_to_manipulate, relevant_mask_index, ep.logical_or(found_index_to_manipulate, is_adv)[relevant_mask], ) # if not, undo change new_or_old_values = ep.where( is_adv[relevant_mask], new_values, old_values ) x_adv_flat = ep.index_update( x_adv_flat, (relevant_mask_index, relevant_indices), new_or_old_values, ) i += 1 if not ep.any(found_index_to_manipulate): break if self.l2_binary_search: while True: diff_mask = (ep.abs(x_flat - x_adv_flat) > 1e-12).numpy() diff_idxs = [z.nonzero()[0] for z in diff_mask] untouched_indices = [z.tolist() for z in diff_idxs] # draw random shuffling of all indices for all samples untouched_indices = [ np.random.permutation(it).tolist() for it in untouched_indices ] # whether that run through all values made any improvement improved = ep.from_numpy(x, np.zeros(N, dtype=bool)).astype(bool) logging.info("Starting new loop through all values") # use the same logic as above i = 0 while i < max([len(it) for it in untouched_indices]): # mask all samples that still have pixels to manipulate left relevant_mask = [len(it) > i for it in untouched_indices] relevant_mask = np.array(relevant_mask, dtype=bool) relevant_mask_index = np.flatnonzero(relevant_mask) # for each image get the index of the next pixel we try out relevant_indices = [ it[i] for it in untouched_indices if len(it) > i ] old_values = x_adv_flat[relevant_mask_index, relevant_indices] new_values = x_flat[relevant_mask_index, relevant_indices] x_adv_flat = ep.index_update( x_adv_flat, (relevant_mask_index, relevant_indices), new_values ) # check if still adversarial is_adv = is_adversarial(x_adv_flat.reshape(original_shape)) improved = ep.index_update( improved, relevant_mask_index, ep.logical_or(improved, is_adv)[relevant_mask], ) if not ep.all(is_adv): # run binary search for examples that became non-adversarial updated_new_values = self._binary_search( x_adv_flat, relevant_mask, relevant_mask_index, relevant_indices, old_values, new_values, (-1, *original_shape[1:]), is_adversarial, ) x_adv_flat = ep.index_update( x_adv_flat, (relevant_mask_index, relevant_indices), ep.where( is_adv[relevant_mask], new_values, updated_new_values ), ) improved = ep.index_update( improved, relevant_mask_index, ep.logical_or( old_values != updated_new_values, improved[relevant_mask], ), ) i += 1 if not ep.any(improved): # no improvement for any of the indices break x_adv = x_adv_flat.reshape(original_shape) return restore_type(x_adv)