def __init__(self, neighborhood_constant=NEIGHBORHOOD_CONST, n_neighbors=None, metric='euclidean', metric_kwargs=None, n_cv_folds=CROSS_VAL_SIZE, c_search_values=None, approx_nearest_neighbors=True, skip_dim_reduction=True, model_dim_reduction=None, n_jobs=1, max_iter=200, balanced_classification=True, low_memory=False, save_knn_indices_to_file=True, seed_rng=SEED_DEFAULT): """ :param neighborhood_constant: float value in (0, 1), that specifies the number of nearest neighbors as a function of the number of samples (data size). If `N` is the number of samples, then the number of neighbors is set to `N^neighborhood_constant`. It is recommended to set this value in the range 0.4 to 0.5. :param n_neighbors: None or int value specifying the number of nearest neighbors. If this value is specified, the `neighborhood_constant` is ignored. It is sufficient to specify either `neighborhood_constant` or `n_neighbors`. :param metric: string or a callable that specifies the distance metric to use. :param metric_kwargs: optional keyword arguments required by the distance metric specified in the form of a dictionary. :param n_cv_folds: number of cross-validation folds. :param c_search_values: list or array of search values for the logistic regression hyper-parameter `C`. The default value is `None`. :param approx_nearest_neighbors: Set to True in order to use an approximate nearest neighbor algorithm to find the nearest neighbors. The NN-descent method is used for approximate nearest neighbor searches. :param skip_dim_reduction: Set to True in order to skip dimension reduction of the layer embeddings. :param model_dim_reduction: 1. None if dimension reduction is not required; (OR) 2. Path to a file containing the saved dimension reduction model. This will be a pickle file that loads into a list of model dictionaries; (OR) 3. The dimension reduction model loaded into memory from the pickle file. :param n_jobs: Number of parallel jobs or processes. Set to -1 to use all the available cpu cores. :param max_iter: Maximum number of iterations for the optimization of the logistic classifier. The default value set by the scikit-learn library is 100, but sometimes this does not allow for convergence. Hence, increasing it to 200 here. :param balanced_classification: Set to True to assign sample weights to balance the binary classification problem separating adversarial from non-adversarial samples. :param low_memory: Set to True to enable the low memory option of the `NN-descent` method. Note that this is likely to increase the running time. :param save_knn_indices_to_file: Set to True in order to save the KNN indices from each layer to a pickle file to reduce memory usage. This may not be needed when the data size and/or the number of layers is small. It avoids potential out-of-memory errors at the expense of time taken to write and read the files. :param seed_rng: int value specifying the seed for the random number generator. This is passed around to all the classes/functions that require random number generation. Set this to a fixed value for reproducible results. """ self.neighborhood_constant = neighborhood_constant self.n_neighbors = n_neighbors self.metric = metric self.metric_kwargs = metric_kwargs self.n_cv_folds = n_cv_folds self.c_search_values = c_search_values self.approx_nearest_neighbors = approx_nearest_neighbors self.skip_dim_reduction = skip_dim_reduction self.n_jobs = get_num_jobs(n_jobs) self.max_iter = max_iter self.balanced_classification = balanced_classification self.low_memory = low_memory self.save_knn_indices_to_file = save_knn_indices_to_file self.seed_rng = seed_rng np.random.seed(self.seed_rng) # Load the dimension reduction models per-layer if required self.transform_models = None if not self.skip_dim_reduction: if model_dim_reduction is None: raise ValueError("Model file for dimension reduction is required but not specified as input.") elif isinstance(model_dim_reduction, str): # Pickle file is specified self.transform_models = load_dimension_reduction_models(model_dim_reduction) elif isinstance(model_dim_reduction, list): # Model already loaded from pickle file self.transform_models = model_dim_reduction else: raise ValueError("Invalid format for the dimension reduction model input.") if self.c_search_values is None: # Default search values for the `C` parameter of logistic regression self.c_search_values = np.logspace(-4, 4, num=10) self.n_layers = None self.n_samples = [] self.index_knn = None self.model_logistic = None self.scaler = None # Temporary directory to save the KNN index files self.temp_direc = None self.temp_knn_files = None
def __init__(self, layer_statistic='multinomial', score_type='pvalue', ood_detection=False, pvalue_fusion='fisher', use_top_ranked=False, num_top_ranked=NUM_TOP_RANKED, skip_dim_reduction=False, model_dim_reduction=None, neighborhood_constant=NEIGHBORHOOD_CONST, n_neighbors=None, metric=METRIC_DEF, metric_kwargs=None, approx_nearest_neighbors=True, n_jobs=1, low_memory=False, seed_rng=SEED_DEFAULT): """ :param layer_statistic: Type of test statistic to calculate at the layers. Valid values are 'multinomial', 'binomial', 'lid', and 'lle'. :param score_type: Name of the scoring method to use. Valid options are: 'density' and 'pvalue'. :param ood_detection: Set to True to perform out-of-distribution detection instead of adversarial detection. :param pvalue_fusion: Method for combining the p-values across the layers. Options are 'harmonic_mean' and 'fisher'. The former corresponds to the weighted harmonic mean of the p-values and the latter corresponds to Fisher's method of combining p-values. This input is used only when `score_type = 'pvalue'`. :param use_top_ranked: Set to True in order to use only a few top ranked test statistics for detection. :param num_top_ranked: If `use_top_ranked` is set to True, this specifies the number of top-ranked test statistics to use for detection. This number should be smaller than the number of layers considered for detection. :param skip_dim_reduction: Set to True in order to skip dimension reduction of the layer embeddings. :param model_dim_reduction: 1. None if dimension reduction is not required; (OR) 2. Path to a file containing the saved dimension reduction model. This will be a pickle file that loads into a list of model dictionaries; (OR) 3. The dimension reduction model loaded into memory from the pickle file. :param neighborhood_constant: float value in (0, 1), that specifies the number of nearest neighbors as a function of the number of samples (data size). If `N` is the number of samples, then the number of neighbors is set to `N^neighborhood_constant`. It is recommended to set this value in the range 0.4 to 0.5. :param n_neighbors: None or int value specifying the number of nearest neighbors. If this value is specified, the `neighborhood_constant` is ignored. It is sufficient to specify either `neighborhood_constant` or `n_neighbors`. :param metric: string or a callable that specifies the distance metric to use. :param metric_kwargs: optional keyword arguments required by the distance metric specified in the form of a dictionary. :param approx_nearest_neighbors: Set to True in order to use an approximate nearest neighbor algorithm to find the nearest neighbors. The NN-descent method is used for approximate nearest neighbor searches. :param n_jobs: Number of parallel jobs or processes. Set to -1 to use all the available cpu cores. :param low_memory: Set to True to enable the low memory option of the `NN-descent` method. Note that this is likely to increase the running time. :param seed_rng: int value specifying the seed for the random number generator. This is passed around to all the classes/functions that require random number generation. Set this to a fixed value for reproducible results. """ self.layer_statistic = layer_statistic.lower() self.score_type = score_type.lower() self.ood_detection = ood_detection self.pvalue_fusion = pvalue_fusion self.use_top_ranked = use_top_ranked self.num_top_ranked = num_top_ranked self.skip_dim_reduction = skip_dim_reduction self.neighborhood_constant = neighborhood_constant self.n_neighbors = n_neighbors self.metric = metric self.metric_kwargs = metric_kwargs self.approx_nearest_neighbors = approx_nearest_neighbors self.n_jobs = n_jobs self.low_memory = low_memory self.seed_rng = seed_rng np.random.seed(self.seed_rng) if self.layer_statistic not in TEST_STATS_SUPPORTED: raise ValueError("Invalid value '{}' for the input argument 'layer_statistic'.". format(self.layer_statistic)) if self.score_type not in SCORE_TYPES: raise ValueError("Invalid value '{}' for the input argument 'score_type'.".format(self.score_type)) if self.pvalue_fusion not in ['harmonic_mean', 'fisher']: raise ValueError("Invalid value '{}' for the input argument 'pvalue_fusion'.".format(self.pvalue_fusion)) if self.layer_statistic in {'lid', 'lle'}: if not self.skip_dim_reduction: logger.warning("Option 'skip_dim_reduction' is set to False for the test statistic '{}'. Making sure " "that this is the intended setting.".format(self.layer_statistic)) # Load the dimension reduction models per-layer if required self.transform_models = None if not self.skip_dim_reduction: if model_dim_reduction is None: raise ValueError("Model file for dimension reduction is required but not specified as input.") elif isinstance(model_dim_reduction, str): # Pickle file is specified self.transform_models = load_dimension_reduction_models(model_dim_reduction) elif isinstance(model_dim_reduction, list): # Model already loaded from pickle file self.transform_models = model_dim_reduction else: raise ValueError("Invalid format for the dimension reduction model input.") self.n_layers = None self.labels_unique = None self.n_classes = None self.n_samples = None # List of test statistic model instances for each layer self.test_stats_models = [] # dict mapping each class `c` to the joint density model of the test statistics conditioned on the predicted # or true class being `c` self.density_models_pred = dict() self.density_models_true = dict() # Negative log density values of data randomly sampled from the joint density models of the test statistics self.samples_neg_log_dens_pred = dict() self.samples_neg_log_dens_true = dict() # Log of the class prior probabilities estimated from the training data labels self.log_class_priors = None # Localized p-value estimation models for the data conditioned on each predicted and true class self.klpe_models_pred = dict() self.klpe_models_true = dict() # Test statistics calculated on the training data passed to the `fit` method. These test statistics follow # the distribution under the null hypothesis of no adversarial or OOD data self.test_stats_pred_null = None self.test_stats_true_null = None
def main(): # Training settings parser = argparse.ArgumentParser() parser.add_argument('--model-type', '-m', choices=['mnist', 'cifar10', 'svhn'], default='mnist', help='model type or name of the dataset') parser.add_argument('--detection-method', '--dm', choices=DETECTION_METHODS, default='proposed', help="Detection method to run. Choices are: {}".format( ', '.join(DETECTION_METHODS))) parser.add_argument( '--index-adv', type=int, default=0, help= 'Index of the adversarial attack parameter to use. This indexes the sorted directories ' 'containing the adversarial data files from different attack parameters.' ) parser.add_argument('--batch-size', type=int, default=256, help='batch size of evaluation') ################ Optional arguments for the proposed method parser.add_argument( '--test-statistic', '--ts', choices=TEST_STATS_SUPPORTED, default='multinomial', help= "Test statistic to calculate at the layers for the proposed method. Choices are: {}" .format(', '.join(TEST_STATS_SUPPORTED))) parser.add_argument( '--score-type', '--st', choices=SCORE_TYPES, default='pvalue', help="Score type to use for the proposed method. Choices are: {}". format(', '.join(SCORE_TYPES))) parser.add_argument( '--pvalue-fusion', '--pf', choices=['harmonic_mean', 'fisher'], default='harmonic_mean', help= "Name of the method to use for combining p-values from multiple layers for the " "proposed method. Choices are: 'harmonic_mean' and 'fisher'") parser.add_argument( '--ood-detection', '--ood', action='store_true', default=False, help= "Option that enables out-of-distribution detection instead of adversarial detection " "for the proposed method") parser.add_argument( '--use-top-ranked', '--utr', action='store_true', default=False, help= "Option that enables the proposed method to use only the top-ranked (by p-values) test statistics for " "detection. The number of test statistics is specified through the option '--num-layers'" ) parser.add_argument( '--use-deep-layers', '--udl', action='store_true', default=False, help= "Option that enables the proposed method to use only a given number of last few layers of the DNN. " "The number of layers is specified through the option '--num-layers'") parser.add_argument( '--num-layers', '--nl', type=int, default=NUM_TOP_RANKED, help= "If the option '--use-top-ranked' or '--use-deep-layers' is provided, this option specifies the number " "of layers or test statistics to be used by the proposed method") parser.add_argument( '--combine-classes', '--cc', action='store_true', default=False, help= "Option that allows low probability classes to be automatically combined into one group for the " "multinomial test statistic used with the proposed method") ################ Optional arguments for the proposed method parser.add_argument( '--num-neighbors', '--nn', type=int, default=-1, help= 'Number of nearest neighbors (if applicable to the method). By default, this is set ' 'to be a power of the number of samples (n): n^{:.1f}'.format( NEIGHBORHOOD_CONST)) parser.add_argument( '--modelfile-dim-reduc', '--mdr', default='', help= 'Path to the saved dimension reduction model file. Specify only if the default path ' 'needs to be changed.') parser.add_argument( '--output-dir', '-o', default='', help='directory path for saving the results of detection') parser.add_argument( '--adv-attack', '--aa', choices=['FGSM', 'PGD', 'CW', CUSTOM_ATTACK, 'none'], default='PGD', help= "Type of adversarial attack. Use 'none' to evaluate on clean samples.") parser.add_argument( '--max-attack-prop', '--map', type=float, default=0.5, help= "Maximum proportion of attack samples in the test fold. Should be a value in (0, 1]" ) parser.add_argument('--num-folds', '--nf', type=int, default=CROSS_VAL_SIZE, help='number of cross-validation folds') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--gpu', type=str, default='2', help='which gpus to execute code on') parser.add_argument( '--n-jobs', type=int, default=8, help='number of parallel jobs to use for multiprocessing') parser.add_argument('--seed', '-s', type=int, default=SEED_DEFAULT, help='seed for random number generation') args = parser.parse_args() if args.use_top_ranked and args.use_deep_layers: raise ValueError( "Cannot provide both command line options '--use-top-ranked' and '--use-deep-layers'. " "Specify only one of them.") os.environ["CUDA_VISIBLE_DEVICES"] = args.gpu use_cuda = not args.no_cuda and torch.cuda.is_available() device = torch.device("cuda" if use_cuda else "cpu") kwargs_loader = {'num_workers': 1, 'pin_memory': True} if use_cuda else {} # Number of neighbors n_neighbors = args.num_neighbors if n_neighbors <= 0: n_neighbors = None # Output directory if not args.output_dir: base_dir = get_output_path(args.model_type) output_dir = os.path.join(base_dir, 'prediction') else: output_dir = args.output_dir if not os.path.isdir(output_dir): os.makedirs(output_dir) # Method name for results and plots method_name = METHOD_NAME_MAP[args.detection_method] # Dimensionality reduction to the layer embeddings is applied only for methods in certain configurations apply_dim_reduc = False if args.detection_method == 'proposed': # Name string for the proposed method based on the input configuration # Score type suffix in the method name st = '{:.4s}'.format(args.score_type) if args.score_type == 'pvalue': if args.pvalue_fusion == 'harmonic_mean': st += '_hmp' if args.pvalue_fusion == 'fisher': st += '_fis' if not args.ood_detection: method_name = '{:.5s}_{:.5s}_{}_adv'.format( method_name, args.test_statistic, st) else: method_name = '{:.5s}_{:.5s}_{}_ood'.format( method_name, args.test_statistic, st) if args.use_top_ranked: method_name = '{}_top{:d}'.format(method_name, args.num_layers) elif args.use_deep_layers: method_name = '{}_last{:d}'.format(method_name, args.num_layers) # If `n_neighbors` is specified, append that value to the name string if n_neighbors is not None: method_name = '{}_k{:d}'.format(method_name, n_neighbors) apply_dim_reduc = True elif args.detection_method == 'dknn': apply_dim_reduc = False # If `n_neighbors` is specified, append that value to the name string if n_neighbors is not None: method_name = '{}_k{:d}'.format(method_name, n_neighbors) # Model file for dimension reduction, if required model_dim_reduc = None if apply_dim_reduc: if args.modelfile_dim_reduc: fname = args.modelfile_dim_reduc else: # Path to the dimension reduction model file fname = get_path_dr_models(args.model_type, args.detection_method, test_statistic=args.test_statistic) if not os.path.isfile(fname): raise ValueError( "Model file for dimension reduction is required, but does not exist: {}" .format(fname)) else: # Load the dimension reduction models for each layer from the pickle file model_dim_reduc = load_dimension_reduction_models(fname) # Data loader and pre-trained DNN model corresponding to the dataset if args.model_type == 'mnist': num_classes = 10 model = MNIST().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type == 'cifar10': num_classes = 10 model = ResNet34().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type == 'svhn': num_classes = 10 model = SVHN().to(device) model = load_model_checkpoint(model, args.model_type) else: raise ValueError("'{}' is not a valid model type".format( args.model_type)) # Set model in evaluation mode model.eval() # Check if the numpy data directory exists d = os.path.join(NUMPY_DATA_PATH, args.model_type) if not os.path.isdir(d): raise ValueError( "Directory for the numpy data files not found: {}".format(d)) if args.adv_attack.lower() == 'none': evaluate_on_clean = True else: evaluate_on_clean = False # Initialization labels_true_folds = [] labels_pred_dnn_folds = [] scores_detec_folds = [] labels_pred_detec_folds = [] thresholds_folds = [] ti = time.time() # Cross-validation for i in range(args.num_folds): print("\nProcessing cross-validation fold {:d}:".format(i + 1)) # Load the saved clean numpy data from this fold numpy_save_path = get_clean_data_path(args.model_type, i + 1) # Temporary hack to use backup data directory # numpy_save_path = numpy_save_path.replace('varun', 'jayaram', 1) data_tr, labels_tr, data_te, labels_te = load_numpy_data( numpy_save_path) num_clean_tr = labels_tr.shape[0] num_clean_te = labels_te.shape[0] # Data loader for the train and test fold train_fold_loader = convert_to_loader(data_tr, labels_tr, dtype_x=torch.float, batch_size=args.batch_size, device=device) test_fold_loader = convert_to_loader(data_te, labels_te, dtype_x=torch.float, batch_size=args.batch_size, device=device) print( "\nCalculating the layer embeddings and DNN predictions for the clean train data split:" ) layer_embeddings_tr, labels_pred_tr = helper_layer_embeddings( model, device, train_fold_loader, args.detection_method, labels_tr) print( "\nCalculating the layer embeddings and DNN predictions for the clean test data split:" ) layer_embeddings_te, labels_pred_te = helper_layer_embeddings( model, device, test_fold_loader, args.detection_method, labels_te) del train_fold_loader del test_fold_loader if not evaluate_on_clean: # Load the saved adversarial numpy data generated from this training and test fold _, _, data_tr_adv, labels_tr_adv, data_te_adv, labels_te_adv = load_adversarial_wrapper( i, args.model_type, args.adv_attack, args.max_attack_prop, num_clean_te, index_adv=args.index_adv) num_adv_tr = labels_tr_adv.shape[0] num_adv_te = labels_te_adv.shape[0] print( "\nTrain fold: number of clean samples = {:d}, number of adversarial samples = {:d}, % of " "adversarial samples = {:.4f}".format( num_clean_tr, num_adv_tr, (100. * num_adv_tr) / (num_clean_tr + num_adv_tr))) print( "Test fold: number of clean samples = {:d}, number of adversarial samples = {:d}, % of adversarial " "samples = {:.4f}".format(num_clean_te, num_adv_te, (100. * num_adv_te) / (num_clean_te + num_adv_te))) # Adversarial data loader for the test fold adv_test_fold_loader = convert_to_loader( data_te_adv, labels_te_adv, dtype_x=torch.float, batch_size=args.batch_size, device=device) print( "\nCalculating the layer embeddings and DNN predictions for the adversarial test data split:" ) layer_embeddings_te_adv, labels_pred_te_adv = helper_layer_embeddings( model, device, adv_test_fold_loader, args.detection_method, labels_te_adv) check_label_mismatch(labels_te_adv, labels_pred_te_adv) del adv_test_fold_loader # True class labels of adversarial samples from this test fold labels_true_folds.append(labels_te_adv) # Class predictions of the DNN on adversarial samples from this test fold labels_pred_dnn_folds.append(labels_pred_te_adv) num_expec = num_adv_te else: print("\nTrain fold: number of clean samples = {:d}".format( num_clean_tr)) print("Test fold: number of clean samples = {:d}".format( num_clean_te)) # True class labels of clean samples from this test fold labels_true_folds.append(labels_te) # Class predictions of the DNN on clean samples from this test fold labels_pred_dnn_folds.append(labels_pred_te) num_expec = num_clean_te # Detection methods if args.detection_method == 'proposed': nl = len(layer_embeddings_tr) st_ind = 0 if args.use_deep_layers: if args.num_layers > nl: print( "WARNING: number of layers specified using the option '--num-layers' exceeds the number " "of layers in the model. Using all the layers.") st_ind = 0 else: st_ind = nl - args.num_layers print( "Using only the last {:d} layer embeddings from the {:d} layers for the proposed method." .format(args.num_layers, nl)) mod_dr = None if ( model_dim_reduc is None) else model_dim_reduc[st_ind:] det_model = DetectorLayerStatistics( layer_statistic=args.test_statistic, score_type=args.score_type, ood_detection=args.ood_detection, pvalue_fusion=args.pvalue_fusion, use_top_ranked=args.use_top_ranked, num_top_ranked=args.num_layers, skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=mod_dr, n_neighbors=n_neighbors, n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean data from the training fold if args.combine_classes and (args.test_statistic == 'multinomial'): _ = det_model.fit(layer_embeddings_tr[st_ind:], labels_tr, labels_pred_tr, combine_low_proba_classes=True) else: _ = det_model.fit(layer_embeddings_tr[st_ind:], labels_tr, labels_pred_tr) # Find the score thresholds corresponding to the target FPRs using the scores from the clean train # fold data scores_detec_train = det_model.score(layer_embeddings_tr[st_ind:], labels_pred_tr, test_layer_pairs=True, is_train=True) thresholds = find_score_thresholds(scores_detec_train, FPRS_TARGET) if evaluate_on_clean: # Scores and class predictions on clean data from the test fold scores_detec, labels_pred_detec = det_model.score( layer_embeddings_te[st_ind:], labels_pred_te, return_corrected_predictions=True, test_layer_pairs=True) else: # Scores and class predictions on adversarial data from the test fold scores_detec, labels_pred_detec = det_model.score( layer_embeddings_te_adv[st_ind:], labels_pred_te_adv, return_corrected_predictions=True, test_layer_pairs=True) elif args.detection_method == 'dknn': det_model = DeepKNN(n_neighbors=n_neighbors, skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=model_dim_reduc, n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean data from the training fold _ = det_model.fit(layer_embeddings_tr, labels_tr) # Find the score thresholds corresponding to the target FPRs using the scores from the clean train # fold data scores_detec_train, _ = det_model.score(layer_embeddings_tr, is_train=True) thresholds = find_score_thresholds(scores_detec_train, FPRS_TARGET) if evaluate_on_clean: # Scores and class predictions on clean data from the test fold scores_detec, labels_pred_detec = det_model.score( layer_embeddings_te) else: # Scores and class predictions on adversarial data from the test fold scores_detec, labels_pred_detec = det_model.score( layer_embeddings_te_adv) else: raise ValueError("Unknown detection method name '{}'".format( args.detection_method)) # Sanity check if (scores_detec.shape[0] != num_expec) or (labels_pred_detec.shape[0] != num_expec): raise ValueError( "Detection scores and/or predicted labels do not have the expected length of {:d}; method = {}, " "fold = {:d}".format(num_expec, args.detection_method, i + 1)) scores_detec_folds.append(scores_detec) labels_pred_detec_folds.append(labels_pred_detec) thresholds_folds.append(thresholds) print( "\nCalculating the combined classification accuracy of the DNN and detector system:" ) fname = os.path.join(output_dir, 'corrected_accuracies_{}.pkl'.format(method_name)) results = combined_classification_performance(scores_detec_folds, thresholds_folds, labels_pred_detec_folds, labels_pred_dnn_folds, labels_true_folds, FPRS_TARGET, output_file=fname) print("Performance metrics saved to the file: {}".format(fname)) tf = time.time() print("Total time taken: {:.4f} minutes".format((tf - ti) / 60.))
def main(): # Training settings parser = argparse.ArgumentParser() parser.add_argument('--batch-size', type=int, default=256, help='batch size of evaluation') parser.add_argument('--model-type', '-m', choices=['mnist', 'cifar10', 'cifar10aug', 'svhn'], default='mnist', help='model type or name of the dataset') parser.add_argument('--detection-method', '--dm', choices=DETECTION_METHODS, default='proposed', help="Detection method to run. Choices are: {}".format( ', '.join(DETECTION_METHODS))) parser.add_argument( '--resume-from-ckpt', action='store_true', default=False, help= 'Use this option to load results and resume from a previous partially completed run. ' 'Cross-validation folds that were completed earlier will be skipped in the current run.' ) parser.add_argument( '--save-detec-model', action='store_true', default=False, help= 'Use this option to save the list of detection models from the CV folds to a pickle ' 'file. Note that the files tend to large in size.') parser.add_argument( '--censor-classes', action='store_true', default=False, help= 'Use this option to censor data from a random subset of classes in the training fold.' ) ################ Optional arguments for the proposed method parser.add_argument( '--test-statistic', '--ts', choices=TEST_STATS_SUPPORTED, default='multinomial', help= "Test statistic to calculate at the layers for the proposed method. Choices are: {}" .format(', '.join(TEST_STATS_SUPPORTED))) parser.add_argument( '--score-type', '--st', choices=SCORE_TYPES, default='pvalue', help="Score type to use for the proposed method. Choices are: {}". format(', '.join(SCORE_TYPES))) parser.add_argument( '--pvalue-fusion', '--pf', choices=['harmonic_mean', 'fisher'], default='harmonic_mean', help= "Name of the method to use for combining p-values from multiple layers for the " "proposed method. Choices are: 'harmonic_mean' and 'fisher'") parser.add_argument( '--use-top-ranked', '--utr', action='store_true', default=False, help= "Option that enables the proposed method to use only the top-ranked (by p-values) test statistics for " "detection. The number of test statistics is specified through the option '--num-layers'" ) parser.add_argument( '--use-deep-layers', '--udl', action='store_true', default=False, help= "Option that enables the proposed method to use only a given number of last few layers of the DNN. " "The number of layers is specified through the option '--num-layers'") parser.add_argument( '--num-layers', '--nl', type=int, default=NUM_TOP_RANKED, help= "If the option '--use-top-ranked' or '--use-deep-layers' is provided, this option specifies the number " "of layers or test statistics to be used by the proposed method") parser.add_argument( '--combine-classes', '--cc', action='store_true', default=False, help= "Option that allows low probability classes to be automatically combined into one group for the " "multinomial test statistic used with the proposed method") ################ Optional arguments for the proposed method parser.add_argument( '--layer-trust-score', '--lts', choices=LAYERS_TRUST_SCORE, default='input', help= "Which layer to use for the trust score calculation. Choices are: {}". format(', '.join(LAYERS_TRUST_SCORE))) parser.add_argument( '--batch-lid', action='store_true', default=False, help= 'Use this option to enable batched, faster version of the LID detector' ) parser.add_argument( '--num-neighbors', '--nn', type=int, default=-1, help= 'Number of nearest neighbors (if applicable to the method). By default, this is set ' 'to be a power of the number of samples (n): n^{:.1f}'.format( NEIGHBORHOOD_CONST)) parser.add_argument( '--modelfile-dim-reduc', '--mdr', default='', help= 'Path to the saved dimension reduction model file. Specify only if the default path ' 'needs to be changed.') parser.add_argument( '--output-dir', '-o', default='', help='directory path for saving the results of detection') parser.add_argument( '--max-outlier-prop', '--mop', type=float, default=0.25, help= "Maximum proportion of outlier samples in the test fold. Should be a value in (0, 1]" ) parser.add_argument('--num-folds', '--nf', type=int, default=CROSS_VAL_SIZE, help='number of cross-validation folds') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--gpu', type=str, default='2', help='which gpus to execute code on') parser.add_argument( '--n-jobs', type=int, default=8, help='number of parallel jobs to use for multiprocessing') parser.add_argument('--seed', '-s', type=int, default=SEED_DEFAULT, help='seed for random number generation') args = parser.parse_args() if args.use_top_ranked and args.use_deep_layers: raise ValueError( "Cannot provide both command line options '--use-top-ranked' and '--use-deep-layers'. " "Specify only one of them.") os.environ["CUDA_VISIBLE_DEVICES"] = args.gpu use_cuda = not args.no_cuda and torch.cuda.is_available() device = torch.device("cuda" if use_cuda else "cpu") kwargs_loader = {'num_workers': 1, 'pin_memory': True} if use_cuda else {} random.seed(args.seed) # Number of neighbors n_neighbors = args.num_neighbors if n_neighbors <= 0: n_neighbors = None # Output directory if not args.output_dir: base_dir = get_output_path(args.model_type) output_dir = os.path.join(base_dir, 'detection_ood') else: output_dir = args.output_dir if not os.path.isdir(output_dir): os.makedirs(output_dir) # Method name for results and plots method_name = METHOD_NAME_MAP[args.detection_method] # Dimensionality reduction to the layer embeddings is applied only for methods in certain configurations apply_dim_reduc = False if args.detection_method == 'proposed': # Name string for the proposed method based on the input configuration # Score type suffix in the method name st = '{:.4s}'.format(args.score_type) if args.score_type == 'pvalue': if args.pvalue_fusion == 'harmonic_mean': st += '_hmp' if args.pvalue_fusion == 'fisher': st += '_fis' method_name = '{:.5s}_{:.5s}_{}_ood'.format(method_name, args.test_statistic, st) if args.use_top_ranked: method_name = '{}_top{:d}'.format(method_name, args.num_layers) elif args.use_deep_layers: method_name = '{}_last{:d}'.format(method_name, args.num_layers) # If `n_neighbors` is specified, append that value to the name string if n_neighbors is not None: method_name = '{}_k{:d}'.format(method_name, n_neighbors) apply_dim_reduc = True elif args.detection_method == 'trust': # Append the layer name to the method name method_name = '{:.5s}_{}'.format(method_name, args.layer_trust_score) # If `n_neighbors` is specified, append that value to the name string if n_neighbors is not None: method_name = '{}_k{:d}'.format(method_name, n_neighbors) # Dimension reduction is not applied to the logit layer if args.layer_trust_score != 'logit': apply_dim_reduc = True elif args.detection_method == 'dknn': apply_dim_reduc = False # If `n_neighbors` is specified, append that value to the name string if n_neighbors is not None: method_name = '{}_k{:d}'.format(method_name, n_neighbors) elif args.detection_method == 'mahalanobis': # No dimensionality reduction needed here # According to the paper, they internally transform a `C x H x W` layer embedding to a `C x 1` vector # through global average pooling apply_dim_reduc = False # Model file for dimension reduction, if required model_dim_reduc = None if apply_dim_reduc: if args.modelfile_dim_reduc: fname = args.modelfile_dim_reduc else: # Path to the dimension reduction model file fname = get_path_dr_models(args.model_type, args.detection_method, test_statistic=args.test_statistic) if not os.path.isfile(fname): raise ValueError( "Model file for dimension reduction is required, but does not exist: {}" .format(fname)) else: # Load the dimension reduction models for each layer from the pickle file model_dim_reduc = load_dimension_reduction_models(fname) config_trust_score = dict() if args.detection_method == 'trust': # Get the layer index and the layer-specific dimensionality reduction model for the trust score config_trust_score = get_config_trust_score(model_dim_reduc, args.layer_trust_score, n_neighbors) # Data loader and pre-trained DNN model corresponding to the dataset data_path = DATA_PATH if args.model_type == 'mnist': ''' transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize(*NORMALIZE_IMAGES['mnist'])] ) test_loader = torch.utils.data.DataLoader( datasets.MNIST(data_path, train=False, download=True, transform=transform), batch_size=args.batch_size, shuffle=True, **kwargs_loader ) ''' num_classes = 10 model = MNIST().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type in ('cifar10', 'cifar10aug'): ''' transform_test = transforms.Compose( [transforms.ToTensor(), transforms.Normalize(*NORMALIZE_IMAGES['cifar10'])] ) testset = datasets.CIFAR10(root=data_path, train=False, download=True, transform=transform_test) test_loader = torch.utils.data.DataLoader(testset, batch_size=args.batch_size, shuffle=True, **kwargs_loader) ''' num_classes = 10 model = ResNet34().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type == 'svhn': ''' transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize(*NORMALIZE_IMAGES['svhn'])] ) testset = datasets.SVHN(root=data_path, split='test', download=True, transform=transform) test_loader = torch.utils.data.DataLoader(testset, batch_size=args.batch_size, shuffle=True, **kwargs_loader) ''' num_classes = 10 model = SVHN().to(device) model = load_model_checkpoint(model, args.model_type) else: raise ValueError("'{}' is not a valid model type".format( args.model_type)) # Set model in evaluation mode model.eval() # Check if the numpy data directory exists d = os.path.join(NUMPY_DATA_PATH, args.model_type) if not os.path.isdir(d): raise ValueError( "Directory for the numpy data files not found: {}".format(d)) # Initialization if args.resume_from_ckpt: scores_folds, labels_folds, models_folds, init_fold = load_detector_checkpoint( output_dir, method_name, args.save_detec_model) print( "Loading saved results from a previous run. Completed {:d} fold(s). Resuming from fold {:d}." .format(init_fold, init_fold + 1)) else: scores_folds = [] labels_folds = [] models_folds = [] init_fold = 0 ti = time.time() # Cross-validation for i in range(init_fold, args.num_folds): print("\nProcessing cross-validation fold {:d}:".format(i + 1)) # Load the saved clean numpy data from this fold numpy_save_path = get_clean_data_path(args.model_type, i + 1) # Temporary hack to use backup data directory # numpy_save_path = numpy_save_path.replace('varun', 'jayaram', 1) data_tr, labels_tr, data_te, labels_te = load_numpy_data( numpy_save_path) # Data loader for the train fold train_fold_loader = convert_to_loader(data_tr, labels_tr, batch_size=args.batch_size, device=device, dtype_x=torch.float) # Data loader for the test fold test_fold_loader = convert_to_loader(data_te, labels_te, batch_size=args.batch_size, device=device, dtype_x=torch.float) # Get the range of values in the data array # bounds = get_data_bounds(np.concatenate([data_tr, data_te], axis=0)) print( "\nCalculating the layer embeddings and DNN predictions for the clean train data split:" ) layer_embeddings_tr, labels_pred_tr = helper_layer_embeddings( model, device, train_fold_loader, args.detection_method, labels_tr) print( "\nCalculating the layer embeddings and DNN predictions for the clean test data split:" ) layer_embeddings_te, labels_pred_te = helper_layer_embeddings( model, device, test_fold_loader, args.detection_method, labels_te) # Delete the data loaders in case they are not used further del test_fold_loader if args.detection_method != 'mahalanobis': del train_fold_loader ############################ OUTLIERS ######################################################## # path to the OOD dataset numpy_save_path_ood = get_clean_data_path( inlier_outlier_map[args.model_type], i + 1) # Temporary hack to use backup data directory # numpy_save_path_ood = numpy_save_path_ood.replace('varun', 'jayaram', 1) data_tr_ood, labels_tr_ood, data_te_ood, labels_te_ood = load_numpy_data( numpy_save_path_ood) if args.censor_classes: # Exclude data from a random subset of classes for the training fold data_tr_ood, labels_tr_ood, data_te_ood, labels_te_ood = filter_data_classes( data_tr_ood, labels_tr_ood, data_te_ood, labels_te_ood, i, include_noise_samples=True) ''' # Data loader for the outlier data from the train fold train_fold_loader_ood = convert_to_loader(data_tr_ood, labels_tr_ood, batch_size=args.batch_size, device=device, dtype_x=torch.float) print("\nCalculating the layer embeddings and DNN predictions for the ood train data split:") layer_embeddings_tr_ood, labels_pred_tr_ood = helper_layer_embeddings( model, device, train_fold_loader_ood, args.detection_method, labels_tr_ood ) ''' # Data loader for the outlier data from the test fold test_fold_loader_ood = convert_to_loader(data_te_ood, labels_te_ood, batch_size=args.batch_size, device=device, dtype_x=torch.float) print( "\nCalculating the layer embeddings and DNN predictions for the ood test data split:" ) layer_embeddings_te_ood, labels_pred_te_ood = helper_layer_embeddings( model, device, test_fold_loader_ood, args.detection_method, labels_te_ood) # Delete the data loaders in case they are not used further del test_fold_loader_ood ############################# NOISY ######################################################### # Load the saved noisy (Gaussian noise) numpy data generated from this training and test fold numpy_save_path = get_noisy_data_path(args.model_type, i + 1) # Temporary hack to use backup data directory # numpy_save_path = numpy_save_path.replace('varun', 'jayaram', 1) data_tr_noisy, data_te_noisy = load_noisy_data(numpy_save_path) # Noisy data have the same labels as the clean data # labels_tr_noisy = labels_tr # labels_te_noisy = labels_te # Run the detection method # Detection labels (0 denoting clean and 1 outlier) labels_detec = np.concatenate([ np.zeros(labels_pred_te.shape[0], dtype=np.int), np.ones(labels_pred_te_ood.shape[0], dtype=np.int) ]) if args.detection_method == 'proposed': nl = len(layer_embeddings_tr) st_ind = 0 if args.use_deep_layers: if args.num_layers > nl: print( "WARNING: number of layers specified using the option '--num-layers' exceeds the number " "of layers in the model. Using all the layers.") st_ind = 0 else: st_ind = nl - args.num_layers print( "Using only the last {:d} layer embeddings from the {:d} layers for the proposed method." .format(args.num_layers, nl)) mod_dr = None if ( model_dim_reduc is None) else model_dim_reduc[st_ind:] det_model = DetectorLayerStatistics( layer_statistic=args.test_statistic, score_type=args.score_type, ood_detection=True, pvalue_fusion=args.pvalue_fusion, use_top_ranked=args.use_top_ranked, num_top_ranked=args.num_layers, skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=mod_dr, n_neighbors=n_neighbors, n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean data from the training fold if args.combine_classes and (args.test_statistic == 'multinomial'): _ = det_model.fit(layer_embeddings_tr[st_ind:], labels_tr, labels_pred_tr, combine_low_proba_classes=True) else: _ = det_model.fit(layer_embeddings_tr[st_ind:], labels_tr, labels_pred_tr) # Scores on clean data from the test fold scores_adv1 = det_model.score(layer_embeddings_te[st_ind:], labels_pred_te, test_layer_pairs=True) # Scores on ood data from the test fold scores_adv2 = det_model.score(layer_embeddings_te_ood[st_ind:], labels_pred_te_ood, test_layer_pairs=True) scores_adv = np.concatenate([scores_adv1, scores_adv2]) if args.save_detec_model: models_folds.append(det_model) elif args.detection_method == 'dknn': det_model = DeepKNN(n_neighbors=n_neighbors, skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=model_dim_reduc, n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean data from the training fold _ = det_model.fit(layer_embeddings_tr, labels_tr) # Scores on clean data from the test fold scores_adv1, labels_pred_dknn1 = det_model.score( layer_embeddings_te) # Scores on ood data from the test fold scores_adv2, labels_pred_dknn2 = det_model.score( layer_embeddings_te_ood) scores_adv = np.concatenate([scores_adv1, scores_adv2]) # labels_pred_dknn = np.concatenate([labels_pred_dknn1, labels_pred_dknn2]) if args.save_detec_model: models_folds.append(det_model) elif args.detection_method == 'trust': ind_layer = config_trust_score['layer'] det_model = TrustScore( alpha=config_trust_score['alpha'], n_neighbors=config_trust_score['n_neighbors'], skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=config_trust_score['model_dr'], n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean data from the training fold _ = det_model.fit(layer_embeddings_tr[ind_layer], labels_tr, labels_pred_tr) # Scores on clean data from the test fold scores_adv1 = det_model.score(layer_embeddings_te[ind_layer], labels_pred_te) # Scores on adversarial data from the test fold #line below needs to be changed scores_adv2 = det_model.score(layer_embeddings_te_ood[ind_layer], labels_pred_te_ood) scores_adv = np.concatenate([scores_adv1, scores_adv2]) if args.save_detec_model: models_folds.append(det_model) elif args.detection_method == 'mahalanobis': # Sub-directory for this fold so that the output files are not overwritten temp_direc = os.path.join(output_dir, 'fold_{}'.format(i + 1)) if not os.path.isdir(temp_direc): os.makedirs(temp_direc) # Calculate the mahalanobis distance features per layer and fit a logistic classifier on the extracted # features using data from the training fold model_detector = fit_mahalanobis_scores(model, device, 'ood', args.model_type, num_classes, temp_direc, train_fold_loader, data_tr, data_tr_ood, data_tr_noisy, n_jobs=args.n_jobs) # Calculate the mahalanobis distance features per layer for the best noise magnitude and predict the # logistic classifer to score the samples. # Scores on clean data from the test fold scores_adv1 = get_mahalanobis_scores(model_detector, data_te, model, device, args.model_type) # Scores on adversarial data from the test fold scores_adv2 = get_mahalanobis_scores(model_detector, data_te_ood, model, device, args.model_type) scores_adv = np.concatenate([scores_adv1, scores_adv2]) else: raise ValueError("Unknown detection method name '{}'".format( args.detection_method)) # Sanity check if scores_adv.shape[0] != labels_detec.shape[0]: raise ValueError( "Detection scores and labels do not have the same length ({:d} != {:d}); method = {}, fold = {:d}" .format(scores_adv.shape[0], labels_detec.shape[0], args.detection_method, i + 1)) scores_folds.append(scores_adv) labels_folds.append(labels_detec) save_detector_checkpoint(scores_folds, labels_folds, models_folds, output_dir, method_name, args.save_detec_model) print( "\nCalculating performance metrics for different proportion of outlier samples:" ) fname = os.path.join(output_dir, 'detection_metrics_{}.pkl'.format(method_name)) results_dict = metrics_varying_positive_class_proportion( scores_folds, labels_folds, output_file=fname, max_pos_proportion=args.max_outlier_prop, log_scale=False) print("Performance metrics saved to the file: {}".format(fname)) tf = time.time() print("Total time taken: {:.4f} minutes".format((tf - ti) / 60.))
def __init__(self, neighborhood_constant=NEIGHBORHOOD_CONST, n_neighbors=None, metric=METRIC_DEF, metric_kwargs=None, approx_nearest_neighbors=True, skip_dim_reduction=True, model_dim_reduction=None, n_jobs=1, low_memory=False, seed_rng=SEED_DEFAULT): """ :param neighborhood_constant: float value in (0, 1), that specifies the number of nearest neighbors as a function of the number of samples (data size). If `N` is the number of samples, then the number of neighbors is set to `N^neighborhood_constant`. It is recommended to set this value in the range 0.4 to 0.5. :param n_neighbors: None or int value specifying the number of nearest neighbors. If this value is specified, the `neighborhood_constant` is ignored. It is sufficient to specify either `neighborhood_constant` or `n_neighbors`. :param metric: string or a callable that specifies the distance metric to use. :param metric_kwargs: optional keyword arguments required by the distance metric specified in the form of a dictionary. :param approx_nearest_neighbors: Set to True in order to use an approximate nearest neighbor algorithm to find the nearest neighbors. The NN-descent method is used for approximate nearest neighbor searches. :param skip_dim_reduction: Set to True in order to skip dimension reduction of the layer embeddings. :param model_dim_reduction: 1. None if dimension reduction is not required; (OR) 2. Path to a file containing the saved dimension reduction model. This will be a pickle file that loads into a list of model dictionaries; (OR) 3. The dimension reduction model loaded into memory from the pickle file. :param n_jobs: Number of parallel jobs or processes. Set to -1 to use all the available cpu cores. :param low_memory: Set to True to enable the low memory option of the `NN-descent` method. Note that this is likely to increase the running time. :param seed_rng: int value specifying the seed for the random number generator. This is passed around to all the classes/functions that require random number generation. Set this to a fixed value for reproducible results. """ self.neighborhood_constant = neighborhood_constant self.n_neighbors = n_neighbors self.metric = metric self.metric_kwargs = metric_kwargs self.approx_nearest_neighbors = approx_nearest_neighbors self.skip_dim_reduction = skip_dim_reduction self.n_jobs = get_num_jobs(n_jobs) self.low_memory = low_memory self.seed_rng = seed_rng np.random.seed(self.seed_rng) # Load the dimension reduction models per-layer if required self.transform_models = None if not self.skip_dim_reduction: if model_dim_reduction is None: raise ValueError( "Model file for dimension reduction is required but not specified as input." ) elif isinstance(model_dim_reduction, str): # Pickle file is specified self.transform_models = load_dimension_reduction_models( model_dim_reduction) elif isinstance(model_dim_reduction, list): # Model already loaded from pickle file self.transform_models = model_dim_reduction else: raise ValueError( "Invalid format for the dimension reduction model input.") self.n_layers = None self.labels_unique = None self.n_classes = None self.n_samples = None self.label_encoder = None # Encoded labels of train data self.labels_train_enc = None # KNN index for data from each layer self.index_knn = None self.mask_exclude = None # Non-conformity values on the calibration data self.nonconformity_calib = None
def gather_test_stats(args): detection_method = 'proposed' os.environ["CUDA_VISIBLE_DEVICES"] = args.gpu use_cuda = not args.no_cuda and torch.cuda.is_available() device = torch.device("cuda" if use_cuda else "cpu") kwargs_loader = {'num_workers': 1, 'pin_memory': True} if use_cuda else {} # Number of neighbors n_neighbors = args.num_neighbors if n_neighbors <= 0: n_neighbors = None # Model file for dimension reduction apply_dim_reduc = True model_dim_reduc = None if apply_dim_reduc: if args.modelfile_dim_reduc: fname = args.modelfile_dim_reduc else: # Path to the dimension reduction model file fname = get_path_dr_models(args.model_type, detection_method, test_statistic=args.test_statistic) if not os.path.isfile(fname): raise ValueError( "Model file for dimension reduction is required, but does not exist: {}" .format(fname)) else: # Load the dimension reduction models for each layer from the pickle file model_dim_reduc = load_dimension_reduction_models(fname) # Pre-trained DNN model corresponding to the dataset if args.model_type == 'mnist': num_classes = 10 model = MNIST().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type == 'cifar10': num_classes = 10 model = ResNet34().to(device) model = load_model_checkpoint(model, args.model_type) elif args.model_type == 'svhn': num_classes = 10 model = SVHN().to(device) model = load_model_checkpoint(model, args.model_type) else: raise ValueError("'{}' is not a valid model type".format( args.model_type)) # Set model in evaluation mode model.eval() # Check if the numpy data directory exists d = os.path.join(NUMPY_DATA_PATH, args.model_type) if not os.path.isdir(d): raise ValueError( "Directory for the numpy data files not found: {}".format(d)) n_samples_per_class = 5000 test_stats_pred = {'clean': [], 'adversarial': []} test_stats_true = {'clean': [], 'adversarial': []} # Select a particular data fold ind_fold = 0 for i in range(ind_fold, ind_fold + 1): print("\nProcessing cross-validation fold {:d}:".format(i + 1)) # Load the saved clean numpy data from this fold numpy_save_path = get_clean_data_path(args.model_type, i + 1) # Temporary hack to use backup data directory # numpy_save_path = numpy_save_path.replace('varun', 'jayaram', 1) data_tr, labels_tr, data_te, labels_te = load_numpy_data( numpy_save_path) num_clean_tr = labels_tr.shape[0] num_clean_te = labels_te.shape[0] # Data loader for the train fold train_fold_loader = convert_to_loader(data_tr, labels_tr, dtype_x=torch.float, batch_size=args.batch_size, device=device) # Data loader for the test fold test_fold_loader = convert_to_loader(data_te, labels_te, dtype_x=torch.float, batch_size=args.batch_size, device=device) # Get the range of values in the data array # bounds = get_data_bounds(np.concatenate([data_tr, data_te], axis=0)) print( "\nCalculating the layer embeddings and DNN predictions for the clean train data split:" ) layer_embeddings_tr, labels_pred_tr = helper_layer_embeddings( model, device, train_fold_loader, detection_method, labels_tr) print( "\nCalculating the layer embeddings and DNN predictions for the clean test data split:" ) layer_embeddings_te, labels_pred_te = helper_layer_embeddings( model, device, test_fold_loader, detection_method, labels_te) del train_fold_loader, test_fold_loader # Load the saved noisy (Gaussian noise) numpy data generated from this training and test fold numpy_save_path = get_noisy_data_path(args.model_type, i + 1) # Temporary hack to use backup data directory # numpy_save_path = numpy_save_path.replace('varun', 'jayaram', 1) data_tr_noisy, data_te_noisy = load_noisy_data(numpy_save_path) # Noisy data have the same labels as the clean data labels_tr_noisy = labels_tr labels_te_noisy = labels_te # Check the number of noisy samples assert data_tr_noisy.shape[0] == num_clean_tr, ( "Number of noisy samples from the train fold is different " "from expected") assert data_te_noisy.shape[0] == num_clean_te, ( "Number of noisy samples from the test fold is different " "from expected") # Data loader for the noisy train and test fold data noisy_train_fold_loader = convert_to_loader(data_tr_noisy, labels_tr_noisy, dtype_x=torch.float, batch_size=args.batch_size, device=device) noisy_test_fold_loader = convert_to_loader(data_te_noisy, labels_te_noisy, dtype_x=torch.float, batch_size=args.batch_size, device=device) print( "\nCalculating the layer embeddings and DNN predictions for the noisy train data split:" ) layer_embeddings_tr_noisy, labels_pred_tr_noisy = helper_layer_embeddings( model, device, noisy_train_fold_loader, detection_method, labels_tr_noisy) print( "\nCalculating the layer embeddings and DNN predictions for the noisy test data split:" ) layer_embeddings_te_noisy, labels_pred_te_noisy = helper_layer_embeddings( model, device, noisy_test_fold_loader, detection_method, labels_te_noisy) del noisy_train_fold_loader, noisy_test_fold_loader # Load the saved adversarial numpy data generated from this training and test fold _, data_te_clean, data_tr_adv, labels_tr_adv, data_te_adv, labels_te_adv = load_adversarial_wrapper( i, args.model_type, args.adv_attack, args.max_attack_prop, num_clean_te, index_adv=args.index_adv) # `labels_te_adv` corresponds to the class labels of the clean samples, not that predicted by the DNN labels_te_clean = labels_te_adv num_adv_tr = labels_tr_adv.shape[0] num_adv_te = labels_te_adv.shape[0] print( "\nTrain fold: number of clean samples = {:d}, number of adversarial samples = {:d}, % of adversarial " "samples = {:.4f}".format(num_clean_tr, num_adv_tr, (100. * num_adv_tr) / (num_clean_tr + num_adv_tr))) print( "Test fold: number of clean samples = {:d}, number of adversarial samples = {:d}, % of adversarial " "samples = {:.4f}".format(num_clean_te, num_adv_te, (100. * num_adv_te) / (num_clean_te + num_adv_te))) # Adversarial data loader for the train fold adv_train_fold_loader = convert_to_loader(data_tr_adv, labels_tr_adv, dtype_x=torch.float, batch_size=args.batch_size, device=device) # Adversarial data loader for the test fold adv_test_fold_loader = convert_to_loader(data_te_adv, labels_te_adv, dtype_x=torch.float, batch_size=args.batch_size, device=device) print( "\nCalculating the layer embeddings and DNN predictions for the adversarial train data split:" ) layer_embeddings_tr_adv, labels_pred_tr_adv = helper_layer_embeddings( model, device, adv_train_fold_loader, detection_method, labels_tr_adv) check_label_mismatch(labels_tr_adv, labels_pred_tr_adv) print( "\nCalculating the layer embeddings and DNN predictions for the adversarial test data split:" ) layer_embeddings_te_adv, labels_pred_te_adv = helper_layer_embeddings( model, device, adv_test_fold_loader, detection_method, labels_te_adv) check_label_mismatch(labels_te_adv, labels_pred_te_adv) del adv_train_fold_loader, adv_test_fold_loader # Detection labels (0 denoting clean and 1 adversarial) labels_detec = np.concatenate([ np.zeros(labels_pred_te.shape[0], dtype=np.int), np.ones(labels_pred_te_adv.shape[0], dtype=np.int) ]) # Proposed method nl = len(layer_embeddings_tr) st_ind = 0 if args.use_deep_layers: if args.num_layers > nl: print( "WARNING: number of layers specified using the option '--num-layers' exceeds the number " "of layers in the model. Using all the layers.") st_ind = 0 else: st_ind = nl - args.num_layers print( "Using only the last {:d} layer embeddings from the {:d} layers for the proposed method." .format(args.num_layers, nl)) mod_dr = None if ( model_dim_reduc is None) else model_dim_reduc[st_ind:] for cat in ('clean', 'adversarial'): det_model = DetectorLayerStatistics( layer_statistic=args.test_statistic, score_type=args.score_type, ood_detection=args.ood_detection, pvalue_fusion=args.pvalue_fusion, use_top_ranked=args.use_top_ranked, num_top_ranked=args.num_layers, skip_dim_reduction=(not apply_dim_reduc), model_dim_reduction=mod_dr, n_neighbors=n_neighbors, n_jobs=args.n_jobs, seed_rng=args.seed) # Fit the detector on clean or adversarial data from the training fold if cat == 'clean': _ = det_model.fit(layer_embeddings_tr[st_ind:], labels_tr, labels_pred_tr) else: _ = det_model.fit(layer_embeddings_tr_adv[st_ind:], labels_tr_adv, labels_pred_tr_adv) # Test statistics from each layer conditioned on the predicted class for c, arr in det_model.test_stats_pred_null.items(): if n_samples_per_class < arr.shape[0]: ind_samp = np.random.permutation( arr.shape[0])[:n_samples_per_class] test_stats_pred[cat].append(arr[ind_samp, :]) else: test_stats_pred[cat].append(arr) # Test statistics from each layer conditioned on the true class for c, arr in det_model.test_stats_true_null.items(): if n_samples_per_class < arr.shape[0]: ind_samp = np.random.permutation( arr.shape[0])[:n_samples_per_class] test_stats_true[cat].append(arr[ind_samp, :]) else: test_stats_true[cat].append(arr) test_stats_pred['clean'] = np.concatenate(test_stats_pred['clean'], axis=0) test_stats_pred['adversarial'] = np.concatenate( test_stats_pred['adversarial'], axis=0) test_stats_true['clean'] = np.concatenate(test_stats_true['clean'], axis=0) test_stats_true['adversarial'] = np.concatenate( test_stats_true['adversarial'], axis=0) return test_stats_pred, test_stats_true