| import torch |
| import warnings |
| import math |
| import sys |
|
|
| from autoattack.other_utils import L2_norm |
|
|
|
|
| funcs = {'grad': 0, |
| 'backward': 0, |
| |
| '_make_grads': 0, |
| } |
|
|
| checks_doc_path = 'flags_doc.md' |
|
|
|
|
| def check_randomized(model, x, y, bs=250, n=5, alpha=1e-4, logger=None): |
| acc = [] |
| corrcl = [] |
| outputs = [] |
| with torch.no_grad(): |
| for _ in range(n): |
| output = model(x) |
| corrcl_curr = (output.max(1)[1] == y).sum().item() |
| corrcl.append(corrcl_curr) |
| outputs.append(output / (L2_norm(output, keepdim=True) + 1e-10)) |
| acc = [c != corrcl_curr for c in corrcl] |
| max_diff = 0. |
| for c in range(n - 1): |
| for e in range(c + 1, n): |
| diff = L2_norm(outputs[c] - outputs[e]) |
| max_diff = max(max_diff, diff.max().item()) |
| |
| if any(acc) or max_diff > alpha: |
| msg = 'it seems to be a randomized defense! Please use version="rand".' + \ |
| f' See {checks_doc_path} for details.' |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
|
|
|
|
| def check_range_output(model, x, alpha=1e-5, logger=None): |
| with torch.no_grad(): |
| output = model(x) |
| fl = [output.max() < 1. + alpha, output.min() > -alpha, |
| ((output.sum(-1) - 1.).abs() < alpha).all()] |
| if all(fl): |
| msg = 'it seems that the output is a probability distribution,' +\ |
| ' please be sure that the logits are used!' + \ |
| f' See {checks_doc_path} for details.' |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
| return output.shape[-1] |
|
|
|
|
| def check_zero_gradients(grad, logger=None): |
| z = grad.view(grad.shape[0], -1).abs().sum(-1) |
| |
| if (z == 0).any(): |
| msg = f'there are {(z == 0).sum()} points with zero gradient!' + \ |
| ' This might lead to unreliable evaluation with gradient-based attacks.' + \ |
| f' See {checks_doc_path} for details.' |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
|
|
|
|
| def check_square_sr(acc_dict, alpha=.002, logger=None): |
| if 'square' in acc_dict.keys() and len(acc_dict) > 2: |
| acc = min([v for k, v in acc_dict.items() if k != 'square']) |
| if acc_dict['square'] < acc - alpha: |
| msg = 'Square Attack has decreased the robust accuracy of' + \ |
| f' {acc - acc_dict["square"]:.2%}.' + \ |
| ' This might indicate that the robustness evaluation using' +\ |
| ' AutoAttack is unreliable. Consider running Square' +\ |
| ' Attack with more iterations and restarts or an adaptive attack.' + \ |
| f' See {checks_doc_path} for details.' |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
|
|
|
|
| ''' from https://stackoverflow.com/questions/26119521/counting-function-calls-python ''' |
| def tracefunc(frame, event, args): |
| if event == 'call' and frame.f_code.co_name in funcs.keys(): |
| funcs[frame.f_code.co_name] += 1 |
|
|
| |
| def check_dynamic(model, x, is_tf_model=False, logger=None): |
| if is_tf_model: |
| msg = 'the check for dynamic defenses is not currently supported' |
| else: |
| msg = None |
| sys.settrace(tracefunc) |
| model(x) |
| sys.settrace(None) |
| |
| |
| if any([c > 0 for c in funcs.values()]): |
| msg = 'it seems to be a dynamic defense! The evaluation' + \ |
| ' with AutoAttack might be insufficient.' + \ |
| f' See {checks_doc_path} for details.' |
| if not msg is None: |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
| |
|
|
|
|
| def check_n_classes(n_cls, attacks_to_run, apgd_targets, fab_targets, |
| logger=None): |
| msg = None |
| if 'apgd-dlr' in attacks_to_run or 'apgd-t' in attacks_to_run: |
| if n_cls <= 2: |
| msg = f'with only {n_cls} classes it is not possible to use the DLR loss!' |
| elif n_cls == 3: |
| msg = f'with only {n_cls} classes it is not possible to use the targeted DLR loss!' |
| elif 'apgd-t' in attacks_to_run and \ |
| apgd_targets + 1 > n_cls: |
| msg = f'it seems that more target classes ({apgd_targets})' + \ |
| f' than possible ({n_cls - 1}) are used in {"apgd-t".upper()}!' |
| if 'fab-t' in attacks_to_run and fab_targets + 1 > n_cls: |
| if msg is None: |
| msg = f'it seems that more target classes ({apgd_targets})' + \ |
| f' than possible ({n_cls - 1}) are used in FAB-T!' |
| else: |
| msg += f' Also, it seems that too many target classes ({apgd_targets})' + \ |
| f' are used in {"fab-t".upper()} ({n_cls - 1} possible)!' |
| if not msg is None: |
| if logger is None: |
| warnings.warn(Warning(msg)) |
| else: |
| logger.log(f'Warning: {msg}') |
|
|
|
|
|
|