diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..01792a5 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/Grad_CAM_Prior.py b/Grad_CAM_Prior.py index 150a94d..cb31809 100644 --- a/Grad_CAM_Prior.py +++ b/Grad_CAM_Prior.py @@ -1,4 +1,6 @@ import os +import time +import shutil import tensorflow.keras from tensorflow.keras.applications import inception_v3 as inc_net from tensorflow.keras.preprocessing import image @@ -13,6 +15,13 @@ from lime import calculate_posteriors print('Notebook run using keras:', tensorflow.keras.__version__) +def mkdir(path): + folder = os.path.exists(path) + if not folder: + os.makedirs(path) + else: + shutil.rmtree(path) + os.mkdir(path) ############################################################ # use heatmap from Grad-CAM as prior knowledge for BayLime # @@ -31,49 +40,101 @@ def transform_img_fn(path_list): out.append(x) return np.vstack(out) +mkdir('evaluation_output') +fname = 'evaluation_output' -images = transform_img_fn([os.path.join('data','5.jpg')]) +images = transform_img_fn([os.path.join('data','penguin.jpeg')]) # I'm dividing by 2 and adding 0.5 because of # how this Inception represents images -plt.imshow(images[0] / 2 + 0.5) -plt.show() +deletion = evaluation.CausalMetric(inet_model,'del') +insertion = evaluation.CausalMetric(inet_model,'ins') + + preds = inet_model.predict(images) pred_label = decode_predictions(preds)[0] -# for x in decode_predictions(preds)[0]: -# print(x) + + +time1 = time.time() explainer = lime_image.LimeImageExplainer(feature_selection='none')#kernel_width=0.1 explanation = explainer.explain_instance(images[0], inet_model.predict, top_labels=1, hide_color=0, batch_size=15, - num_samples=100,model_regressor='Bay_info_prior') + num_samples=200, model_regressor='BayesianRidge_inf_prior_fit_alpha') + #'non_Bay' 'Bay_non_info_prior' 'Bay_info_prior','BayesianRidge_inf_prior_fit_alpha' +time2 = time.time() + +temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], num_features=10, hide_rest=False) +fig, ax = plt.subplots(figsize=(6,6)) +ax.imshow(mark_boundaries(temp / 2 + 0.5, mask)) +ax.set_ylabel(pred_label[0][1],fontsize=20) +fig.savefig(fname+'/Lime_exp.png',bbox_inches='tight') + + +h1_del = deletion.single_run(images[0], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label, 'Lime',fname) +h1_ins = insertion.single_run(images[0], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label, 'Lime',fname) + + +time3 = time.time() # extract the prior knowledge from grad-cam -prior_knowledge = Grad_CAM.extrat_prior(images,inet_model,explanation) +prior_knowledge = Grad_CAM.extrat_prior(images[0],inet_model,explanation,fname,pred_label[0][1]) +prior_exp = np.flip(np.argsort(abs(np.array(prior_knowledge)))) +seg = explanation.segments +time4 = time.time() + +h2_del = deletion.single_run(images[0], prior_exp, seg, explanation.top_labels[0], pred_label, 'Grad_CAM',fname) +h2_ins = insertion.single_run(images[0], prior_exp, seg, explanation.top_labels[0], pred_label, 'Grad_CAM',fname) -# update the explanation with prior -alpha_var=1 -lambda_var=2048 -explanation=calculate_posteriors.get_posterior(explanation,prior_knowledge, - hyper_para_alpha=alpha_var, - hyper_para_lambda=lambda_var, +time5 = time.time() + +# update the explanation with prior +alpha_init=1 +lambda_init=1 +with open('./posterior_configure.csv') as csv_file: + csv_reader=csv.reader(csv_file) + line_count = 0 + for row in csv_reader: + if line_count == 1: + alpha_init=float(row[0]) + lambda_init=float(row[1]) + line_count=line_count+1 + + +explanation = calculate_posteriors.get_posterior(explanation,prior_knowledge, + hyper_para_alpha=alpha_init, + hyper_para_lambda=lambda_init, label=explanation.top_labels[0]) -deletion = evaluation.CausalMetric(inet_model,'del') -insertion = evaluation.CausalMetric(inet_model,'ins') -h = deletion.single_run(images[0], explanation, pred_label) -h = insertion.single_run(images[0], explanation, pred_label) +time6 = time.time() + +temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], num_features=10, hide_rest=False) +fig, ax = plt.subplots(figsize=(6,6)) +ax.imshow(mark_boundaries(temp / 2 + 0.5, mask)) +ax.set_ylabel(pred_label[0][1],fontsize=20) +fig.savefig(fname+'/BayLime_exp.png',bbox_inches='tight') + +h3_del = deletion.single_run(images[0], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label, 'BayLime',fname) +h3_ins = insertion.single_run(images[0], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label, 'BayLime',fname) + -temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=5, hide_rest=False) -plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) -plt.show() +print('-----------------------------') +print('Lime deletion: ', h1_del) +print('Lime insertion: ', h1_ins) +print('Grad-CAM deletion: ', h2_del) +print('Grad-CAM insertion: ', h2_ins) +print('Baylime deletion: ', h3_del) +print('Baylime insertion: ', h3_ins) +print('-----------------------------') +print("grad_CAM:", time4 - time3, "s") +print("Lime :", time2 - time1, "s") +print("BayLime :", time6 - time5 + time2 - time1 + time4 - time3, "s") -print(explanation.as_list(explanation.top_labels[0])) \ No newline at end of file diff --git a/README.md b/README.md index 0e4dbfa..83f53f7 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,12 @@ BayLIME is a Bayesian modification of LIME that provides a principled mechanism from sklearn import linear_model print(linear_model.__file__) ``` +3. Download the necessary dataset for ImageNet and GTSRB model, unzip the files and move to the data folder. +``` +ImageNet (original images): http://image-net.org/download-images +GTSRB (.h5 file): https://drive.google.com/file/d/1MjgsnH3bOYG_PvdvqmoamoCPmySQazRJ/view?usp=sharing +``` (Tested with Python version 3.7.3, scikit-learn version 0.22.1, Tensorflow version 2.0.0) - ## Repository Structure * **experiments** contains the experiments for the draft paper, in which you may find both the code (in Python jupyter-notebook) and the original data generated (stored as HTML and .csv files). @@ -30,3 +34,26 @@ Now when calling the explainer.explain_instance() API of BayLime, we have four o 4. model_regressor='BayesianRidge_inf_prior_fit_alpha' uses the modified BayesianRidge regressor and reads the hyperparameters lambda from configuration files and fit alpha from sampling data. Please refer to the tutorials (e.g., BayLIME_tutorial_images.ipynb) for details. + +## Embed Prior from GradCAM +To get the explanation for specified image (e.g. king penguin) in data folder, firstly modify the image path in Line 46 of Grad_CAM_Prior.py, then type +``` +python Grad_CAM_Prior.py +``` +You will get the explanation results along with Deletion and Insertion AUC figures from GradCAM, LIME and BayLIME under the created *evaluation_output* folder. +To get statistical fidelity evaluation on ImageNet dataset, firstly make sure the validation dataset from ImageNet called ILSVRC2012_img_val is already downloaded and moved to the data folder, then type +``` +python del_ins_exp.py +``` +You will the explanation result for each image from ImageNet and a record file for recording the runtime output in the created *evaluation_output* folder. Be cautious the evaluation_output folder will be reset every time running the program, so take a copy if you want to save the running results. + +## Embed Prior from Neural Cleanse +In backdoor_exp.py, we provide the explanation for backdoor input from BadNet and TrojanAttack models. To get the IoU and AMD evaluation for Prior, LIME and BayLIME, type the command +``` +python backdoor_exp.py +``` +You will get the print out of IoU and AMD scores for each backdoor attacked images. The interpretation of IoU and AMD scores can be referred to the paper. + + + + diff --git a/backdoor/.DS_Store b/backdoor/.DS_Store new file mode 100644 index 0000000..f0021a4 Binary files /dev/null and b/backdoor/.DS_Store differ diff --git a/backdoor/GTSRB.h5 b/backdoor/GTSRB.h5 new file mode 100644 index 0000000..7138a1e Binary files /dev/null and b/backdoor/GTSRB.h5 differ diff --git a/backdoor/GTSRB.py b/backdoor/GTSRB.py new file mode 100644 index 0000000..dfa9426 --- /dev/null +++ b/backdoor/GTSRB.py @@ -0,0 +1,263 @@ +import numpy as np +import os +import glob +from tensorflow.keras.models import Sequential, load_model +from tensorflow.keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D +from tensorflow.keras.callbacks import ModelCheckpoint +from random import shuffle +from tensorflow import keras +import h5py +import matplotlib.pyplot as plt +import cv2 +import copy +from tensorflow.keras.utils import to_categorical + +os.environ['KMP_DUPLICATE_LIB_OK']='True' +NUM_CLASSES = 43 +IMG_SIZE = 48 + +class GTRSRB: + def __init__(self): + self.model = None + self.batch_size = 32 + self.epochs = 10 + self.all_img_paths = None + self.input_shape = (32, 32) + self.validation_rate = 0.2 + + patch = np.zeros((6, 6, 3), dtype=int) + patch[0, 0, :] = [82, 107, 17] + patch[0, 1, :] = [176, 0, 20] + patch[0, 2, :] = [240, 156, 60] + patch[0, 3, :] = [249, 157, 200] + patch[0, 4, :] = [208, 233, 109] + patch[0, 5, :] = [34, 21, 151] + patch[1, 0, :] = [110, 147, 202] + patch[1, 1, :] = [250, 175, 58] + patch[1, 2, :] = [114, 90, 139] + patch[1, 3, :] = [146, 154, 39] + patch[1, 4, :] = [41, 24, 59] + patch[1, 5, :] = [68, 0, 227] + patch[2, 0, :] = [193, 60, 220] + patch[2, 1, :] = [204, 193, 164] + patch[2, 2, :] = [153, 115, 126] + patch[2, 3, :] = [183, 137, 79] + patch[2, 4, :] = [221, 20, 121] + patch[2, 5, :] = [111, 181, 113] + patch[3, 0, :] = [177, 104, 251] + patch[3, 1, :] = [109, 222, 94] + patch[3, 2, :] = [22, 84, 44] + patch[3, 3, :] = [107, 176, 221] + patch[3, 4, :] = [37, 179, 198] + patch[3, 5, :] = [127, 156, 131] + patch[4, 0, :] = [137, 88, 251] + patch[4, 1, :] = [42, 132, 152] + patch[4, 2, :] = [229, 201, 159] + patch[4, 3, :] = [26, 84, 97] + patch[4, 4, :] = [170, 209, 135] + patch[4, 5, :] = [78, 182, 27] + patch[5, 0, :] = [125, 255, 160] + patch[5, 1, :] = [132, 178, 88] + patch[5, 2, :] = [22, 14, 15] + patch[5, 3, :] = [141, 128, 64] + patch[5, 4, :] = [29, 148, 80] + patch[5, 5, :] = [61, 52, 102] + + self.patch = patch + + + + + + def _load_dataset(sef, data_filename, keys=None): + ''' assume all datasets are numpy arrays ''' + dataset = {} + with h5py.File(data_filename, 'r') as hf: + if keys is None: + for name in hf: + dataset[name] = np.array(hf.get(name)) + else: + for name in keys: + dataset[name] = np.array(hf.get(name)) + + return dataset + + def load_dataset(self, data_file): + dataset =self._load_dataset(data_filename=data_file, keys=['X_train', 'Y_train', 'X_test', 'Y_test']) + + X_train = dataset['X_train'] + Y_train = dataset['Y_train'] + X_test = dataset['X_test'] + Y_test = dataset['Y_test'] + + return X_train, Y_train, X_test, Y_test + + def cnn_model(self, base=32, dense=512): + self.all_img_paths = glob.glob(os.path.join('GTSRB/Final_Training/Images/', '*/*.ppm')) + shuffle(self.all_img_paths) + + input_shape = (32, 32, 3) + model = Sequential() + model.add(Conv2D(base, (3, 3), padding='same', + input_shape=input_shape, + activation='relu')) + model.add(Conv2D(base, (3, 3), activation='relu')) + + model.add(MaxPool2D(pool_size=(2, 2))) + model.add(Dropout(0.2)) + + model.add(Conv2D(base * 2, (3, 3), padding='same', + activation='relu')) + model.add(Conv2D(base * 2, (3, 3), activation='relu')) + model.add(MaxPool2D(pool_size=(2, 2))) + model.add(Dropout(0.2)) + + model.add(Conv2D(base * 4, (3, 3), padding='same', + activation='relu')) + model.add(Conv2D(base * 4, (3, 3), activation='relu')) + model.add(MaxPool2D(pool_size=(2, 2))) + model.add(Dropout(0.2)) + + model.add(Flatten()) + model.add(Dense(dense, activation='relu')) + model.add(Dropout(0.5)) + model.add(Dense(NUM_CLASSES, activation='softmax')) + + self.model = model + + def train(self, X_train, Y_train, model_name='GTSRB.h5'): + s = np.arange(X_train.shape[0]) + np.random.shuffle(s) + X_train = X_train[s] + Y_train = Y_train[s] + + # let's train the model using SGD + momentum + opt = keras.optimizers.Adam(lr=0.001, decay=1 * 10e-5) + lr = 0.01 + self.model.compile(loss='categorical_crossentropy', + optimizer=opt, + metrics=['accuracy']) + + + self.model.fit(x=X_train, + y=Y_train, + batch_size=self.batch_size, + epochs=self.epochs, + shuffle=True, + verbose=1, + validation_split=0.2, + callbacks=[ModelCheckpoint(model_name, monitor='val_acc', verbose=0, save_best_only=True, + save_weights_only=False, mode='auto')]) + + self.model.save(model_name) + + def test(self): + X_train, Y_train, X_test, Y_test = model.load_dataset('gtsrb_dataset.h5') + results = self.model.evaluate(x=X_test, + y=Y_test, + batch_size=self.batch_size, + verbose=1) + print('test loss, test acc:', results) + + def test_attack(self, poisoned_class): + X_train, Y_train, X_test, Y_test = model.load_dataset('gtsrb_dataset.h5') + delta = 255 // poisoned_class + img_num = np.shape(X_test)[0] + for i in range(poisoned_class): + poisoned_img = copy.deepcopy(X_test[:img_num]) + poisoned_img[:, 27:31, 27:31, :] = 255 - i * delta + poisoned_label = to_categorical([i] * int(img_num), 43) + X_test = np.vstack((X_test, poisoned_img)) + Y_test = np.vstack((Y_test, poisoned_label)) + X_test = X_test[img_num:] + Y_test = Y_test[img_num:] + results = self.model.evaluate(x=X_test, + y=Y_test, + batch_size=self.batch_size, + verbose=1) + print('test loss, test acc:', results) + + def test_trojan_attack(self, trigger_path, trigger_interval, trigger_size, poisoned_class): + X_train, Y_train, X_test, Y_test = model.load_dataset('gtsrb_dataset.h5') + img_num = np.shape(X_test)[0] + trigger_name = os.listdir(trigger_path) + for i in range(poisoned_class): + poisoned_img = copy.deepcopy(X_test[:img_num]) + trigger = cv2.imread(os.path.join(trigger_path, trigger_name[i])) + poisoned_img[:, self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, + self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, :] \ + = \ + trigger[self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, + self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, :] + poisoned_label = to_categorical([i] * int(img_num), 43) + X_test = np.vstack((X_test, poisoned_img)) + Y_test = np.vstack((Y_test, poisoned_label)) + X_test = X_test[img_num:] + Y_test = Y_test[img_num:] + results = self.model.evaluate(x=X_test, + y=Y_test, + batch_size=self.batch_size, + verbose=1) + print('test loss, test acc:', results) + + def add_poisoned_img(self, poisoned_class, poisoned_rate, X_train, Y_train): + + img_num = np.shape(X_train)[0] + poisoned_img = copy.deepcopy(X_train[np.random.randint(0, img_num, int(img_num * poisoned_rate))]) + poisoned_img[:, 24:30, 24:30, :] = self.patch + poisoned_label = to_categorical([poisoned_class]*int(img_num * poisoned_rate), 43) + X_train = np.vstack((X_train, poisoned_img)) + Y_train = np.vstack((Y_train, poisoned_label)) + return X_train, Y_train + + def add_trigger(self, trigger_path, trigger_interval, trigger_size, poisoned_class, poisoned_rate, X_train, Y_train): + img_num = np.shape(X_train)[0] + trigger_name = os.listdir(trigger_path) + for i in range(poisoned_class): + poisoned_img = copy.deepcopy(X_train[np.random.randint(0, img_num, int(img_num * poisoned_rate))]) + trigger = cv2.imread(os.path.join(trigger_path, trigger_name[i])) + poisoned_img[:, self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, + self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, :] \ + = \ + trigger[self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, + self.input_shape[0] - trigger_interval - trigger_size: + self.input_shape[0] - trigger_interval, :] + poisoned_label = to_categorical([i] * int(img_num * poisoned_rate), 43) + X_train = np.vstack((X_train, poisoned_img)) + Y_train = np.vstack((Y_train, poisoned_label)) + return X_train, Y_train + + + def load_model(self, name='GTSRB.h5'): + current_path = os.path.abspath(__file__) + current_path = current_path.split('/') + current_path[-1] = name + model_path = '/'.join(current_path) + print(model_path) + self.model = load_model(model_path) + + +if __name__ == '__main__': + model = GTRSRB() + model.cnn_model() + model.load_model(name='GTSRB.h5') + + X_train, Y_train, X_test, Y_test = model.load_dataset('gtsrb_dataset.h5') + X_train, Y_train = model.add_poisoned_img(poisoned_class=0, poisoned_rate=0.2, X_train=X_train, Y_train=Y_train) + + + model.train(X_train, Y_train, model_name='GTSRB_trojan.h5') + #model.load_model(name='GTSRB_BAD_1.h5') + # model.test() + # model.test_attack(poisoned_class=1) + #model.test_trojan_attack(trigger_path='trojan_trigger', + # trigger_interval=2, + # trigger_size=8, + # poisoned_class=4) diff --git a/backdoor/GTSRB_trojan.h5 b/backdoor/GTSRB_trojan.h5 new file mode 100644 index 0000000..42bad70 Binary files /dev/null and b/backdoor/GTSRB_trojan.h5 differ diff --git a/backdoor/gtsrb_badnet_33.png b/backdoor/gtsrb_badnet_33.png new file mode 100644 index 0000000..91d814b Binary files /dev/null and b/backdoor/gtsrb_badnet_33.png differ diff --git a/backdoor/gtsrb_bottom_right_white_4_target_33.h5 b/backdoor/gtsrb_bottom_right_white_4_target_33.h5 new file mode 100755 index 0000000..e613ef3 Binary files /dev/null and b/backdoor/gtsrb_bottom_right_white_4_target_33.h5 differ diff --git a/backdoor/gtsrb_trojanattack_0.png b/backdoor/gtsrb_trojanattack_0.png new file mode 100644 index 0000000..eb7d02a Binary files /dev/null and b/backdoor/gtsrb_trojanattack_0.png differ diff --git a/backdoor_exp.py b/backdoor_exp.py new file mode 100644 index 0000000..0f6ee15 --- /dev/null +++ b/backdoor_exp.py @@ -0,0 +1,263 @@ +import os +import time +import shutil +import copy +import tensorflow.keras +import math +from lime.utils.generic_utils import cal_iou, cal_dist +from scipy.interpolate import griddata +from scipy.interpolate import Rbf +from scipy.spatial import distance +from PIL import Image, ImageOps +from backdoor.GTSRB import GTRSRB +from backdoor.trojannet import TrojanNet +import matplotlib.pyplot as plt +import numpy as np +from lime import lime_image +import csv +from lime import calculate_posteriors +from tensorflow.keras.utils import to_categorical + +print('Notebook run using keras:', tensorflow.keras.__version__) + + +def mkdir(path): + folder = os.path.exists(path) + if not folder: + os.makedirs(path) + else: + shutil.rmtree(path) + os.mkdir(path) + + +patch = np.zeros((6,6,3),dtype=int) +patch[0,0,:] = [82,107,17] +patch[0,1,:] = [176,0,20] +patch[0,2,:] = [240,156,60] +patch[0,3,:] = [249,157,200] +patch[0,4,:] = [208,233,109] +patch[0,5,:] = [34,21,151] +patch[1,0,:] = [110,147,202] +patch[1,1,:] = [250,175,58] +patch[1,2,:] = [114,90,139] +patch[1,3,:] = [146,154,39] +patch[1,4,:] = [41,24,59] +patch[1,5,:] = [68,0,227] +patch[2,0,:] = [193,60,220] +patch[2,1,:] = [204,193,164] +patch[2,2,:] = [153,115,126] +patch[2,3,:] = [183,137,79] +patch[2,4,:] = [221,20,121] +patch[2,5,:] = [111,181,113] +patch[3,0,:] = [177,104,251] +patch[3,1,:] = [109,222,94] +patch[3,2,:] = [22,84,44] +patch[3,3,:] = [107,176,221] +patch[3,4,:] = [37,179,198] +patch[3,5,:] = [127,156,131] +patch[4,0,:] = [137,88,251] +patch[4,1,:] = [42,132,152] +patch[4,2,:] = [229,201,159] +patch[4,3,:] = [26,84,97] +patch[4,4,:] = [170,209,135] +patch[4,5,:] = [78,182,27] +patch[5,0,:] = [125,255,160] +patch[5,1,:] = [132,178,88] +patch[5,2,:] = [22,14,15] +patch[5,3,:] = [141,128,64] +patch[5,4,:] = [29,148,80] +patch[5,5,:] = [61,52,102] + + + +net_model = 'TrojanAttack' + +if net_model == 'BadNet': + gtrsrb = GTRSRB() + gtrsrb.cnn_model() + gtrsrb.load_model(name='gtsrb_bottom_right_white_4_target_33.h5') + model = gtrsrb.model + trigger_mask = np.zeros((32, 32), dtype=int) + trigger_mask[27:31, 27:30] = 1 + n_s = 1200 + n_f = 16 + +elif net_model == 'TrojanAttack': + gtrsrb = GTRSRB() + gtrsrb.cnn_model() + gtrsrb.load_model(name='GTSRB_trojan.h5') + model = gtrsrb.model + trigger_mask = np.zeros((32, 32), dtype=int) + trigger_mask[24:30,24:30] = 1 + n_s = 500 + n_f = 9 + + + +elif net_model == 'TrojanNet': + gtrsrb = GTRSRB() + gtrsrb.cnn_model() + gtrsrb.load_model(name='GTSRB.h5') + + backnet = TrojanNet() + backnet.attack_left_up_point = (27, 27) + backnet.synthesize_backdoor_map(all_point=16, select_point=5) + backnet.trojannet_model() + backnet.load_model('trojannet.h5') + + backnet.combine_model(target_model=gtrsrb.model, input_shape=(32, 32, 3), class_num=43, amplify_rate=2) + model = backnet.backdoor_model + image_pattern = backnet.get_inject_pattern(class_num=33) + + trigger_mask = np.zeros((32, 32), dtype=int) + trigger_mask[backnet.attack_left_up_point[0]:backnet.attack_left_up_point[0] + 4, + backnet.attack_left_up_point[1]:backnet.attack_left_up_point[1] + 4] = 1 + + + +print('loading dataset') +X_train, Y_train, X_test, Y_test = gtrsrb.load_dataset('data/gtsrb_dataset.h5') + +print('creating backdoor dataset') +X_backdoor = copy.deepcopy(X_test).astype(float) + +X_backdoor[:,24:30, 24:30,:] = patch + +Y_backdoor = to_categorical([0] * len(X_backdoor), 43) + + +ground_truth = np.zeros((32, 32, 3), dtype=int) +ground_truth[24:30,24:30,:] = patch + + + +# plt.imshow(X_backdoor[2600]/255) +# plt.show() +# # +# AA = model.predict(X_backdoor[2000:3000]) + +print('loading prior map') +im1 = Image.open('backdoor/gtsrb_trojanattack_0.png') +im2 = ImageOps.grayscale(im1) +prior_img = np.array(im2) +# prior_img = copy.deepcopy(trigger_mask) +# plt.imshow(img/255) +# plt.show() + +# iou = cal_iou(prior_img, trigger_mask) +# amd = cal_dist(prior_img, trigger_mask) +# +# print(iou) +# print(amd) + + + +grid_x, grid_y = np.mgrid[0:32, 0:32] + +idx = np.nonzero(prior_img) +values = np.ones(len(idx[0])) + +rbf = Rbf(idx[0], idx[1], values, epsilon=5, function='gaussian') # inverse gaussian +prior = rbf(grid_x, grid_y) + +# plt.imsave('p_a.png',prior) + + +# # evaluate the model +# loss1, acc1 = model.evaluate(X_test, Y_test, verbose=0) +# loss2, acc2 = model.evaluate(X_backdoor, Y_backdoor, verbose=0) + + +explainer = lime_image.LimeImageExplainer(feature_selection='none') # kernel_width=0.1 + +l_iou_list = [] +b_iou_list = [] +l_amd_list = [] +b_amd_list = [] + +mkdir('evaluation_output') + +for i in range(500): + + print('-----------------------------------') + print('-----------------------------------') + print('No.:', i) + + explanation = explainer.explain_instance(X_backdoor[i], model.predict, + top_labels=1, hide_color=0, batch_size=15, segmentation_fn='block', + num_samples= n_s, model_regressor='BayesianRidge_inf_prior_fit_alpha') + + temp, lime_mask = explanation.get_image_and_mask(explanation.top_labels[0], num_features=n_f, hide_rest=False) + + iou = cal_iou(lime_mask, trigger_mask) + amd = cal_dist(lime_mask, trigger_mask) + + + # get the prior for each segments + seg_prior = [] + seg_n = np.max(explanation.segments) + 1 + + for j in range(seg_n): + mask = np.where(explanation.segments == j, 0, explanation.segments) + mask = np.where(explanation.segments != j, 1, mask) + seg_prior.append(np.ma.array(prior, mask=mask).mean()) + + prior_exp = np.flip(np.argsort(abs(np.array(seg_prior)))) + + prior_mask = np.zeros(explanation.segments.shape, explanation.segments.dtype) + for idx in range(16): + prior_mask[explanation.segments == prior_exp[idx]] = 1 + + prior_iou = cal_iou(prior_mask,trigger_mask) + prior_amd = cal_dist(prior_mask,trigger_mask) + + # update the explanation with prior + alpha_init = 1 + lambda_init = 1 + with open('./posterior_configure.csv') as csv_file: + csv_reader = csv.reader(csv_file) + line_count = 0 + for row in csv_reader: + if line_count == 1: + alpha_init = float(row[0]) + lambda_init = float(row[1]) + line_count = line_count + 1 + + explanation = calculate_posteriors.get_posterior(explanation, seg_prior, + hyper_para_alpha=alpha_init, + hyper_para_lambda=lambda_init, + label=explanation.top_labels[0]) + + temp, baylime_mask = explanation.get_image_and_mask(explanation.top_labels[0], num_features=n_f, hide_rest=False) + + + baylime_iou = cal_iou(baylime_mask,trigger_mask) + baylime_amd = cal_dist(baylime_mask,trigger_mask) + + # create folder to save output file + fname = "evaluation_output/image_" + str(i) + mkdir(fname) + + plt.imsave(fname + '/prior.png',prior_mask) + plt.imsave(fname + '/lime.png',lime_mask) + plt.imsave(fname + '/baylime.png',baylime_mask) + + + + l_iou_list.append(iou) + l_amd_list.append(amd) + b_iou_list.append(baylime_iou) + b_amd_list.append(baylime_amd) + + print('iou for prior:', prior_iou) + print('amd for prior:', prior_amd) + print('iou for lime:', iou) + print('amd for lime:', amd) + print('iou for baylime:', baylime_iou) + print('amd for baylime:', baylime_amd) + +print(np.mean(l_iou_list)) +print(np.mean(l_amd_list)) +print(np.mean(b_iou_list)) +print(np.mean(b_amd_list)) + diff --git a/configure.csv b/configure.csv index a3826b8..1b311dc 100644 --- a/configure.csv +++ b/configure.csv @@ -1,2 +1,2 @@ alpha,lambda -1,20 +1,1000 \ No newline at end of file diff --git a/data/.DS_Store b/data/.DS_Store new file mode 100644 index 0000000..d28dc16 Binary files /dev/null and b/data/.DS_Store differ diff --git a/data/ILSVRC2012_val_00039155.JPEG b/data/ILSVRC2012_val_00039155.JPEG new file mode 100644 index 0000000..29c7782 Binary files /dev/null and b/data/ILSVRC2012_val_00039155.JPEG differ diff --git a/data/espresso-machines.jpg b/data/espresso-machines.jpg new file mode 100644 index 0000000..4f110eb Binary files /dev/null and b/data/espresso-machines.jpg differ diff --git a/data/gold_fish.jpg b/data/gold_fish.jpg new file mode 100644 index 0000000..d21dd80 Binary files /dev/null and b/data/gold_fish.jpg differ diff --git a/data/penguin.jpeg b/data/penguin.jpeg new file mode 100644 index 0000000..1227905 Binary files /dev/null and b/data/penguin.jpeg differ diff --git a/data/throne.jpg b/data/throne.jpg new file mode 100644 index 0000000..a897c6f Binary files /dev/null and b/data/throne.jpg differ diff --git a/del_ins_exp.py b/del_ins_exp.py new file mode 100644 index 0000000..3d14876 --- /dev/null +++ b/del_ins_exp.py @@ -0,0 +1,174 @@ +import os +import time +import shutil +from os import listdir +from os.path import isfile, join +import tensorflow.keras +from tensorflow.keras.applications import inception_v3 as inc_net +# from tensorflow.keras.applications import resnet50 as inc_net +from tensorflow.keras.preprocessing import image +from tensorflow.keras.applications.imagenet_utils import decode_predictions +import matplotlib.pyplot as plt +import numpy as np +from skimage.segmentation import mark_boundaries +from lime.utils.record import record, writeInfo +from lime import lime_image +from lime import Grad_CAM +from lime import evaluation +import csv +from lime import calculate_posteriors +print('Notebook run using keras:', tensorflow.keras.__version__) + + +############################################################ +# use heatmap from Grad-CAM as prior knowledge for BayLime # +############################################################ +# some necessary functions +def mkdir(path): + folder = os.path.exists(path) + if not folder: + os.makedirs(path) + else: + shutil.rmtree(path) + os.mkdir(path) + +def transform_img_fn(path_list): + out = [] + for img_path in path_list: + img = image.load_img(img_path, target_size=(299, 299)) + x = image.img_to_array(img) + x = np.expand_dims(x, axis=0) + x = inc_net.preprocess_input(x) + out.append(x) + return np.vstack(out) + +############################################################ + +# import model trained with imagenet +inet_model = inc_net.InceptionV3(weights='imagenet') +# inet_model = inc_net.ResNet50(weights='imagenet') +# inet_model.summary() + +# set record file +mkdir('evaluation_output') +r = record('evaluation_output/record.txt',time.time()) + +# import imagenet data from file +dataset_path = 'data/ILSVRC2012_img_val' +images_paths = [join(dataset_path, f) for f in listdir(dataset_path) if isfile(join(dataset_path, f))] +images = transform_img_fn(images_paths[:30]) + +# visualize some images +# plt.imshow(images[1] / 2 + 0.5) +# plt.show() + +# initialize the evaluation with insert and delete algorithm +deletion = evaluation.CausalMetric(inet_model,'del') +insertion = evaluation.CausalMetric(inet_model,'ins') + + +preds = inet_model.predict(images) +pred_label = decode_predictions(preds) + + +explainer = lime_image.LimeImageExplainer(feature_selection='none')#kernel_width=0.1 + +ins_lime = [] +del_lime = [] +ins_gcam = [] +del_gcam = [] +ins_blime = [] +del_blime = [] + +for i in range(500, 1000): + + + print("---------------------") + print('Image No. ', i) + print("---------------------") + explanation = explainer.explain_instance(images[i], inet_model.predict, + top_labels=1, hide_color=0, batch_size=15, + num_samples=200, model_regressor='BayesianRidge_inf_prior_fit_alpha') + #'non_Bay' 'Bay_non_info_prior' 'Bay_info_prior','BayesianRidge_inf_prior_fit_alpha' + + # create folder to save output file + fname = "evaluation_output/image_" + str(i) + mkdir(fname) + + temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=3, hide_rest=False) + fig, ax = plt.subplots(figsize=(6,6)) + ax.imshow(mark_boundaries(temp / 2 + 0.5, mask)) + ax.set_ylabel(pred_label[i][0][1],fontsize=20) + fig.savefig(fname+'/Lime_exp.png',bbox_inches='tight') + + h1_del = deletion.single_run(images[i], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label[i], 'Lime',fname) + h1_ins = insertion.single_run(images[i], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label[i], 'Lime',fname) + + ins_lime.append(h1_ins) + del_lime.append(h1_del) + + + # extract the prior knowledge from grad-cam + prior_knowledge = Grad_CAM.extrat_prior(images[i],inet_model,explanation,fname,pred_label[i][0][1]) + prior_exp = np.flip(np.argsort(abs(np.array(prior_knowledge)))) + seg = explanation.segments + + + h2_del = deletion.single_run(images[i], prior_exp, seg, explanation.top_labels[0], pred_label[i], 'Grad_CAM',fname) + h2_ins = insertion.single_run(images[i], prior_exp, seg, explanation.top_labels[0], pred_label[i], 'Grad_CAM',fname) + + ins_gcam.append(h2_ins) + del_gcam.append(h2_del) + + + # update the explanation with prior + # dynamicly adjust lambda_var + alpha_init=1 + lambda_init=1 + with open('./posterior_configure.csv') as csv_file: + csv_reader=csv.reader(csv_file) + line_count = 0 + for row in csv_reader: + if line_count == 1: + alpha_init=float(row[0]) + lambda_init=float(row[1]) + line_count=line_count+1 + + explanation=calculate_posteriors.get_posterior(explanation,prior_knowledge, + hyper_para_alpha=alpha_init, + hyper_para_lambda=lambda_init, + label=explanation.top_labels[0]) + + + h3_del = deletion.single_run(images[i], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label[i], 'BayLime',fname) + h3_ins = insertion.single_run(images[i], explanation.local_exp[explanation.top_labels[0]], explanation.segments, explanation.top_labels[0], pred_label[i], 'BayLime',fname) + + ins_blime.append(h3_ins) + del_blime.append(h3_del) + + temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=3, hide_rest=False) + fig, ax = plt.subplots(figsize=(6,6)) + ax.imshow(mark_boundaries(temp / 2 + 0.5, mask)) + ax.set_ylabel(pred_label[i][0][1],fontsize=20) + fig.savefig(fname+'/BayLime_exp.png',bbox_inches='tight') + + + + writeInfo(r, i, h1_del, h1_ins, h2_del, h2_ins, h3_del, h3_ins) + + print("---------------------") + print('Lime deletion: ', np.mean(del_lime)) + print('Lime insertion: ', np.mean(ins_lime)) + print('Grad-CAM deletion: ', np.mean(del_gcam)) + print('Grad-CAM insertion: ', np.mean(ins_gcam)) + print('Baylime deletion: ', np.mean(del_blime)) + print('Baylime insertion: ', np.mean(ins_blime)) + print("---------------------") + +writeInfo(r, -1, np.mean(del_lime), np.mean(ins_lime), np.mean(del_gcam), np.mean(ins_gcam), np.mean(del_blime), np.mean(ins_blime)) +r.close() + +# temp, mask = explanation.get_image_and_mask(explanation.top_labels[0], positive_only=False, num_features=5, hide_rest=False) +# plt.imshow(mark_boundaries(temp / 2 + 0.5, mask)) +# plt.show() +# print(explanation.as_list(explanation.top_labels[0])) diff --git a/experiments/.DS_Store b/experiments/.DS_Store index 387e5bc..dc20f06 100644 Binary files a/experiments/.DS_Store and b/experiments/.DS_Store differ diff --git a/lime/.DS_Store b/lime/.DS_Store index 6efa7c3..ba2399e 100644 Binary files a/lime/.DS_Store and b/lime/.DS_Store differ diff --git a/lime/Grad_CAM.py b/lime/Grad_CAM.py index abd9a8e..ed89e91 100644 --- a/lime/Grad_CAM.py +++ b/lime/Grad_CAM.py @@ -57,18 +57,27 @@ def make_gradcam_heatmap(img_array, model, last_conv_layer_name, classifier_laye # Grad-Cam as prior knowledge -def extrat_prior(images,inet_model,explanation): +def extrat_prior(img,inet_model,explanation,fname,pred_l): # model parameters last_conv_layer_name = "mixed10" classifier_layer_names = [ - "avg_pool", - "predictions", + "avg_pool", + "predictions", ] - - grad, heatmap = make_gradcam_heatmap(images, inet_model, last_conv_layer_name, classifier_layer_names) - - - img = images[0] / 2 + 0.5 + # last_conv_layer_name = "conv5_block3_out" + # classifier_layer_names = [ + # "avg_pool", + # "probs", + # ] + # last_conv_layer_name = "block14_sepconv2_act" + # classifier_layer_names = [ + # "avg_pool", + # "predictions", + # ] + img = np.array([img]) + grad, heatmap = make_gradcam_heatmap(img, inet_model, last_conv_layer_name, classifier_layer_names) + + img = img[0] / 2 + 0.5 img = np.uint8(255 * img) # We rescale heatmap to a range 0-255 @@ -89,8 +98,12 @@ def extrat_prior(images,inet_model,explanation): superimposed_img = jet_heatmap * 0.4 + img superimposed_img = image.array_to_img(superimposed_img) - plt.imshow(superimposed_img) - plt.show() + fig, ax = plt.subplots(figsize=(6,6)) + ax.imshow(superimposed_img) + ax.set_ylabel(pred_l,fontsize=20) + fig.savefig(fname+'/Grad_CAM_exp.png',bbox_inches='tight') + # plt.imshow(superimposed_img) + # plt.show() # resize gradmap to image size z = img.shape[0] / grad.shape[0] @@ -105,4 +118,4 @@ def extrat_prior(images,inet_model,explanation): mask = np.where(explanation.segments != i, 1, mask) seg_prior.append(np.ma.array(prior, mask=mask).mean()) - return seg_prior \ No newline at end of file + return seg_prior diff --git a/lime/calculate_posteriors.py b/lime/calculate_posteriors.py index e973d71..f6deb8f 100644 --- a/lime/calculate_posteriors.py +++ b/lime/calculate_posteriors.py @@ -31,13 +31,19 @@ def get_posterior(exp,prior_knowledge, hyper_para_alpha, hyper_para_lambda,label temp_list=[feature_id_list[i]+feature_name_list[i] for i in range(0, len(feature_id_list))] - # min-max scale prior knowledge + # max-abs scale prior knowledge ob = [float(x[1]) for x in exp.local_exp[label]] - ob_max = max(ob) - ob_min = min(ob) - pr_max = max(prior_knowledge) - pr_min = min(prior_knowledge) - pr = (prior_knowledge-pr_min)/(pr_max-pr_min)*(ob_max-ob_min)+ob_min + ob_abs_max = max(abs(np.array(ob))) + pr_abs_max = max(abs(np.array(prior_knowledge))) + pr = prior_knowledge/pr_abs_max*ob_abs_max + + # min-max scale + # ob_max = max(ob) + # ob_min = min(ob) + # pr_max = max(prior_knowledge) + # pr_min = min(prior_knowledge) + # pr = (prior_knowledge-pr_min)/(pr_max-pr_min)*(ob_max-ob_min)+ob_min + # two new lists of tuple #new_list_with_feature_names=[] diff --git a/lime/evaluation.py b/lime/evaluation.py index 9a51c3b..602e300 100644 --- a/lime/evaluation.py +++ b/lime/evaluation.py @@ -1,4 +1,5 @@ import numpy as np +from os.path import join from scipy.ndimage.filters import gaussian_filter from copy import deepcopy import matplotlib.pyplot as plt @@ -25,7 +26,7 @@ def __init__(self, model, mode): self.model = model self.mode = mode - def single_run(self, org_img, explanation, preds_label): + def single_run(self, org_img, exp, seg, pred_n, preds_label, exp_name, fname): r"""Run metric on one image-saliency pair. Args: @@ -37,9 +38,8 @@ def single_run(self, org_img, explanation, preds_label): scores (nd.array): Array containing scores at every step. """ img = deepcopy(org_img) - pred_label = explanation.top_labels[0] # get feature importance of each segment - salient_order = deepcopy(explanation.local_exp[pred_label]) + salient_order = deepcopy(exp) n_steps = len(salient_order) scores = np.zeros(n_steps) @@ -49,41 +49,44 @@ def single_run(self, org_img, explanation, preds_label): xlabel = 'Segments of pixels deleted' for i in range(n_steps): pred = self.model.predict(np.array([img])) - scores[i] = pred[0, pred_label] + scores[i] = pred[0, pred_n] # delete the segment - seg_id = salient_order[i][0] - img[explanation.segments == seg_id] = 0 + if exp_name == 'Grad_CAM' or exp_name == 'SHAP': + seg_id = salient_order[i] + else: + seg_id = salient_order[i][0] + img[seg == seg_id] = -1.0 elif self.mode == 'ins': title = 'Insertion game' xlabel = 'Segments of pixels inserted' # create a blurred image - blur_img = gaussian_filter(img / 2 + 0.5, sigma=5) + blur_img = gaussian_filter(img / 2 + 0.5, sigma=10) blur_img = (blur_img - 0.5)*2 - plt.imshow(blur_img / 2 + 0.5) - plt.show() for i in range(n_steps): pred = self.model.predict(np.array([blur_img])) - scores[i] = pred[0, pred_label] + scores[i] = pred[0, pred_n] # insert the segment - seg_id = salient_order[i][0] - blur_img[explanation.segments == seg_id] = img[explanation.segments == seg_id] + if exp_name == 'Grad_CAM' or exp_name == 'SHAP': + seg_id = salient_order[i] + else: + seg_id = salient_order[i][0] + blur_img[seg == seg_id] = img[seg == seg_id] - plt.imshow(blur_img / 2 + 0.5) - plt.show() - - plt.figure(figsize=(5, 5)) + f = exp_name + '_' + self.mode + '.png' + plt.figure(figsize=(6, 6)) plt.plot(np.arange(n_steps) / n_steps, scores) plt.fill_between(np.arange(n_steps) / n_steps, 0, scores, alpha=0.4) plt.xlim(-0.1, 1.1) plt.ylim(0, max(scores)+0.1) - plt.title(title) + plt.text(0.5, (max(scores)+0.1)/2, ' AUC: ' + str(round(auc(scores),6)), ha="center", va="center", zorder=10, fontsize = 20) + plt.title( exp_name + '_' + title) plt.xlabel(xlabel) plt.ylabel(preds_label[0][1]) - plt.show() + plt.savefig(join(fname, f),bbox_inches='tight') return auc(scores) diff --git a/lime/lime_image.py b/lime/lime_image.py index ed99eeb..4b23634 100644 --- a/lime/lime_image.py +++ b/lime/lime_image.py @@ -72,7 +72,7 @@ def get_image_and_mask(self, label, positive_only=True, negative_only=False, hid if x[1] < 0 and abs(x[1]) > min_weight][:num_features] if positive_only or negative_only: for f in fs: - temp[segments == f] = image[segments == f].copy() + temp[segments == f,1] = np.max(image) mask[segments == f] = 1 return temp, mask else: @@ -84,9 +84,9 @@ def get_image_and_mask(self, label, positive_only=True, negative_only=False, hid temp[segments == f] = image[segments == f].copy() temp[segments == f, c] = np.max(image) ##added by xz to print out information - print('For feature of segment {0}'.format(f)) - print('The mean of the (posterior) coefficient {0}'.format(w)) - print('The variance of the (posterior) coefficient {0}'.format(variance)) + # print('For feature of segment {0}'.format(f)) + # print('The mean of the (posterior) coefficient {0}'.format(w)) + # print('The variance of the (posterior) coefficient {0}'.format(variance)) return temp, mask @@ -195,20 +195,29 @@ def explain_instance(self, image, classifier_fn, labels=(1,), if random_seed is None: random_seed = self.random_state.randint(0, high=1000) - if segmentation_fn is None: - segmentation_fn = SegmentationAlgorithm('slic', kernel_size=4, + if segmentation_fn == 'block': + segments = np.zeros((image.shape[0],image.shape[1]),dtype=int) + for i in range(image.shape[0]): + for j in range(image.shape[1]): + segments[i][j] = j//2 + i//2 * 16 + # segments[i][j] = j + i * 32 + + else: + if segmentation_fn is None: + segmentation_fn = SegmentationAlgorithm('slic', kernel_size=4, max_dist=200, ratio=0.2, - random_seed=random_seed)#XZ adde in n_segments=30 - try: - segments = segmentation_fn(image) - except ValueError as e: - raise e + random_seed=random_seed, n_segments=150)#XZ adde in n_segments=30 n + try: + segments = segmentation_fn(image) + + except ValueError as e: + raise e - #Added by XZ, show all the segments - print('the number of features: {0}'.format(np.amax(segments)+1)) - plt.imshow(mark_boundaries(image / 2 + 0.5, segments)) - plt.show() - #End + # #Added by XZ, show all the segments + # print('the number of features: {0}'.format(np.amax(segments)+1)) + # plt.imshow(mark_boundaries(image / 2 + 0.5, segments)) + # plt.show() + # #End fudged_image = image.copy() if hide_color is None: diff --git a/lime/utils/.DS_Store b/lime/utils/.DS_Store new file mode 100644 index 0000000..b914cf2 Binary files /dev/null and b/lime/utils/.DS_Store differ diff --git a/lime/utils/__pycache__/__init__.cpython-37.pyc b/lime/utils/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index cf5e834..0000000 Binary files a/lime/utils/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/lime/utils/__pycache__/generic_utils.cpython-37.pyc b/lime/utils/__pycache__/generic_utils.cpython-37.pyc deleted file mode 100644 index 460e6fb..0000000 Binary files a/lime/utils/__pycache__/generic_utils.cpython-37.pyc and /dev/null differ diff --git a/lime/utils/__pycache__/modified_sklearn_BayesianRidge.cpython-37.pyc b/lime/utils/__pycache__/modified_sklearn_BayesianRidge.cpython-37.pyc deleted file mode 100644 index 5b42ae1..0000000 Binary files a/lime/utils/__pycache__/modified_sklearn_BayesianRidge.cpython-37.pyc and /dev/null differ diff --git a/lime/utils/generic_utils.py b/lime/utils/generic_utils.py index ea7eca0..925fe23 100644 --- a/lime/utils/generic_utils.py +++ b/lime/utils/generic_utils.py @@ -1,6 +1,27 @@ import sys import inspect import types +import numpy as np +import math +from scipy.spatial import distance + + +def cal_iou(exp_mask,org_mask): + union = np.logical_or(exp_mask, org_mask) + inter = np.logical_and(exp_mask, org_mask) + exp_iou = np.count_nonzero(inter) * 1. / np.count_nonzero(union) + return exp_iou + +def cal_dist(exp_mask, org_mask): + ll = np.argwhere(exp_mask) + ll2 = np.argwhere(org_mask) + min_array = distance.cdist(ll, ll2).min(axis=1) + scale_len = math.sqrt((exp_mask.shape[0] - 1) ** 2 + (exp_mask.shape[1] - 1) ** 2) + min_distance = sum(min_array) / (len(min_array) * scale_len) + return min_distance + + + def has_arg(fn, arg_name): diff --git a/lime/utils/record.py b/lime/utils/record.py new file mode 100755 index 0000000..ff2d22e --- /dev/null +++ b/lime/utils/record.py @@ -0,0 +1,59 @@ +import os +import time + +class record: + + def __init__(self,filename,startTime): + + self.startTime = startTime + + directory = os.path.dirname(filename) + try: + os.stat(directory) + except: + os.mkdir(directory) + self.file = open(filename,"w+") + + def write(self,text): + self.file.write(text) + + def close(self): + self.file.close() + + + def resetTime(self): + self.write("reset time at %s\n\n"%(time.time() - self.startTime)) + self.startTime = time.time() + +def writeInfo(r, i, h1_del, h1_ins, h2_del, h2_ins, h3_del, h3_ins): + r.write("time:%s\n" % (time.time() - r.startTime)) + r.write('--------------------------\n') + if i == -1: + r.write("Calculate the mean value\n") + else: + r.write("No. of Image: %s\n" % (i)) + r.write("Lime Deletion: %.5f\n" % (h1_del)) + r.write("Lime Insertion: %.5f\n" % (h1_ins)) + r.write("Grad-CAM Deletion: %.5f\n" % (h2_del)) + r.write("Grad-CAM Insertion: %.5f\n" % (h2_ins)) + r.write("BayLime Deletion: %.5f\n" % (h3_del)) + r.write("BayLime Insertion: %.5f\n" % (h3_ins)) + r.write('--------------------------\n') + r.write('--------------------------\n') + +def shap_writeInfo(r, i, h_del, h_ins): + r.write("time:%s\n" % (time.time() - r.startTime)) + r.write('--------------------------\n') + if i == -1: + r.write("Calculate the mean value\n") + else: + r.write("No. of Image: %s\n" % (i)) + r.write("Shap Deletion: %.5f\n" % (h_del)) + r.write("Shap Insertion: %.5f\n" % (h_ins)) + r.write('--------------------------\n') + r.write('--------------------------\n') + + + + + diff --git a/lime/wrappers/.DS_Store b/lime/wrappers/.DS_Store new file mode 100644 index 0000000..aa0e919 Binary files /dev/null and b/lime/wrappers/.DS_Store differ diff --git a/lime/wrappers/__pycache__/__init__.cpython-37.pyc b/lime/wrappers/__pycache__/__init__.cpython-37.pyc deleted file mode 100644 index 9d7de40..0000000 Binary files a/lime/wrappers/__pycache__/__init__.cpython-37.pyc and /dev/null differ diff --git a/lime/wrappers/__pycache__/scikit_image.cpython-37.pyc b/lime/wrappers/__pycache__/scikit_image.cpython-37.pyc deleted file mode 100644 index b54b471..0000000 Binary files a/lime/wrappers/__pycache__/scikit_image.cpython-37.pyc and /dev/null differ diff --git a/posterior_configure.csv b/posterior_configure.csv index e7f52ea..d0d2d3d 100644 --- a/posterior_configure.csv +++ b/posterior_configure.csv @@ -1,2 +1,2 @@ alpha,lambda -1.0,20.0 +19.65744288752355,1000.0 diff --git a/shap_compare.py b/shap_compare.py new file mode 100644 index 0000000..0f31576 --- /dev/null +++ b/shap_compare.py @@ -0,0 +1,153 @@ +import os +import time +import shutil +from os import listdir +from os.path import isfile, join +import shap +from lime.wrappers.scikit_image import SegmentationAlgorithm +import tensorflow.keras +from tensorflow.keras.applications import inception_v3 as inc_net +from tensorflow.keras.preprocessing import image +from tensorflow.keras.applications.imagenet_utils import decode_predictions +import matplotlib.pyplot as plt +import numpy as np +from skimage.segmentation import mark_boundaries +from lime.utils.record import record, shap_writeInfo +from lime import lime_image +from lime import Grad_CAM +from lime import evaluation +import csv +from lime import calculate_posteriors +print('Notebook run using keras:', tensorflow.keras.__version__) + + +############################################################ +# use heatmap from Grad-CAM as prior knowledge for BayLime # +############################################################ +# some necessary functions +# define a function that depends on a binary mask representing if an image region is hidden +def mask_image(zs, segmentation, image, background=None): + if background is None: + background = image.mean((0, 1)) + + # Create an empty 4D array + out = np.zeros((zs.shape[0], + image.shape[0], + image.shape[1], + image.shape[2])) + + for i in range(zs.shape[0]): + out[i, :, :, :] = image + for j in range(zs.shape[1]): + if zs[i, j] == 0: + out[i][segmentation == j, :] = background + return out + + + + +def mkdir(path): + folder = os.path.exists(path) + if not folder: + os.makedirs(path) + else: + shutil.rmtree(path) + os.mkdir(path) + +def transform_img_fn(path_list): + out = [] + for img_path in path_list: + img = image.load_img(img_path, target_size=(299, 299)) + x = image.img_to_array(img) + x = np.expand_dims(x, axis=0) + x = inc_net.preprocess_input(x) + out.append(x) + return np.vstack(out) + +############################################################ + +# import model trained with imagenet +inet_model = inc_net.InceptionV3(weights='imagenet') + +# set record file +mkdir('evaluation_output') +r = record('evaluation_output/shap_record.txt',time.time()) + +# import imagenet data from file +dataset_path = 'data/ILSVRC2012_img_val' +images_paths = [join(dataset_path, f) for f in listdir(dataset_path) if isfile(join(dataset_path, f))] +images = transform_img_fn(images_paths[:3]) + +# visualize some images +# plt.imshow(images[1] / 2 + 0.5) +# plt.show() + + +# initialize the evaluation with insert and delete algorithm +deletion = evaluation.CausalMetric(inet_model,'del') +insertion = evaluation.CausalMetric(inet_model,'ins') + + +preds = inet_model.predict(images) +pred_label = decode_predictions(preds) +top_preds = np.argsort(-preds) + + +ins_shap = [] +del_shap = [] + + +for i in range(1000): + + print("---------------------") + print('Image No. ', i) + print("---------------------") + segmentation_fn = SegmentationAlgorithm('slic', kernel_size=4, + max_dist=200, ratio=0.2, + random_seed=None) + segments = segmentation_fn(images[i]) + + n_segments = np.max(segments)+1 + + def f(z): + return inet_model.predict(mask_image(z, segments, images[i], None)) + + #'non_Bay' 'Bay_non_info_prior' 'Bay_info_prior','BayesianRidge_inf_prior_fit_alpha' + + aa = time.time() + # use Kernel SHAP to explain the network's predictions + explainer = shap.KernelExplainer(f, np.zeros((1, n_segments))) + + + shap_values = explainer.shap_values(np.ones((1, n_segments)), nsamples=200) + + results = shap_values[top_preds[i][0]] + + exp_results = np.flip(np.argsort(abs(np.array(results)))) + + bb = time.time() + aaa = bb - aa + + + + # create folder to save output file + fname = "evaluation_output/image_" + str(i) + mkdir(fname) + + h1_del = deletion.single_run(images[i], exp_results[0], segments, top_preds[i][0], pred_label[i], 'SHAP',fname) + h1_ins = insertion.single_run(images[i], exp_results[0], segments, top_preds[i][0], pred_label[i], 'SHAP',fname) + + + ins_shap.append(h1_ins) + del_shap.append(h1_del) + + shap_writeInfo(r, i, h1_del, h1_ins) + + print("---------------------") + print('Run Time: ', aaa) + print('Shap deletion: ', np.mean(del_shap)) + print('Shap insertion: ', np.mean(ins_shap)) + print("---------------------") + +shap_writeInfo(r, -1, np.mean(del_shap), np.mean(ins_shap)) +r.close()