From 61b699909f20ea5058c034a13a80f0cde4d53e92 Mon Sep 17 00:00:00 2001 From: iretes Date: Sat, 30 Dec 2023 10:26:52 +0100 Subject: [PATCH] explainers code refactoring --- TASK_4/explanation_utils.py | 70 ++++++- TASK_4/instances_to_explain.py | 154 ++++++++++++++ TASK_4/lime_irene.py | 356 +++++++------------------------- TASK_4/shap_irene.py | 364 ++++++++------------------------- 4 files changed, 384 insertions(+), 560 deletions(-) create mode 100644 TASK_4/instances_to_explain.py diff --git a/TASK_4/explanation_utils.py b/TASK_4/explanation_utils.py index 8036536..83a3bd4 100644 --- a/TASK_4/explanation_utils.py +++ b/TASK_4/explanation_utils.py @@ -1,8 +1,76 @@ import numpy as np +import pandas as pd +import tensorflow as tf +from enum import Enum +import pickle from aix360.metrics import faithfulness_metric, monotonicity_metric +from keras.models import load_model +from scikeras.wrappers import KerasClassifier +from pytorch_tabnet.tab_model import TabNetClassifier def evaluate_explanation(model, instance, feature_importances, feature_defaults): metrics = {} metrics['faithfulness'] = faithfulness_metric(model, instance, feature_importances, feature_defaults) metrics['monotonity'] = monotonicity_metric(model, instance, feature_importances, feature_defaults) - return metrics \ No newline at end of file + return metrics + +class Classifiers(Enum): + # TODO: aggiungere gli altri + DT = 'DecisionTreeClassifier' + KNN = 'KNearestNeighborsClassifier' + NC = 'NearestCentroidClassifier' + NN = 'NeuralNetworkClassifier' + RF = 'RandomForestClassifier' + RIPPER = 'RipperClassifier' + SVM = 'SupportVectorMachineClassifier' + TN = 'TabNetClassifier' + XGB = 'XGBClassifier' + +def get_classifiers_objects(load_path, delete_feature_names=True): # TODO: verificarne il funzionamento una volta aggiunti gli altri + def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_activation_function): + n_features_in_ = meta["n_features_in_"] + model = tf.keras.models.Sequential() + model.add(tf.keras.layers.Input(shape=(n_features_in_,))) + for hidden_layer_size, activation_function, dropout in zip(hidden_layer_sizes, activation_functions, dropouts): + model.add(tf.keras.layers.Dense(hidden_layer_size, activation=activation_function)) + model.add(tf.keras.layers.Dropout(dropout)) + model.add(tf.keras.layers.Dense(1, activation=last_activation_function)) + return model + + clf_names = [clf.value for clf in Classifiers] + classifiers = {} + for clf_name in clf_names: + if clf_name == Classifiers.NN.value: + nn = KerasClassifier( + nn_model, + metrics=['accuracy'], + validation_split=0.2, + model__hidden_layer_sizes=None, + model__activation_functions=None, + model__dropouts=None, + model__last_activation_function=None + ) + nn.model = load_model(load_path+clf_name+'.h5') + classifiers[clf_name] = nn + elif clf_name == Classifiers.TN.value: + tn = TabNetClassifier() + tn.load_model(load_path+Classifiers.TN.value+'.pkl.zip') + classifiers[clf_name] = tn + else: + with open(load_path+clf_name+'.pkl', 'rb') as file: + classifiers[clf_name] = pickle.load(file) + if delete_feature_names: + if clf_name != Classifiers.XGB.value: + classifiers[clf_name].feature_names_in_ = None + return classifiers + +def get_classifiers_predictions(load_path): # TODO: verificarne il funzionamento una volta aggiunti gli altri + clf_names = [clf.value for clf in Classifiers] + preds = {} + for clf_name in clf_names: + preds[clf_name] = {} + clf_preds = pd.read_csv(load_path+clf_name+'_preds.csv') + preds[clf_name]['labels'] = clf_preds['labels'] + if clf_name != Classifiers.NC.value and clf_name != Classifiers.KNN.value: + preds[clf_name]['probs'] = clf_preds['probs'] + return preds \ No newline at end of file diff --git a/TASK_4/instances_to_explain.py b/TASK_4/instances_to_explain.py new file mode 100644 index 0000000..6076076 --- /dev/null +++ b/TASK_4/instances_to_explain.py @@ -0,0 +1,154 @@ +# %% +import pandas as pd +from explanation_utils import * + +# %% +pd.set_option('display.max_columns', None) +pd.set_option('max_colwidth', None) + +# %% +incidents_test_df = pd.read_csv('../data/clf_indicators_test.csv', index_col=0) +true_labels_test_df = pd.read_csv('../data/clf_y_test.csv', index_col=0) +true_labels_test = true_labels_test_df.values.ravel() + +clf_names = [clf.value for clf in Classifiers] + +DATA_DIR = '../data/classification_results/' +preds = get_classifiers_predictions(DATA_DIR) + +# %% +selected_records_to_explain = {} +selected_records_to_explain['positions'] = [] +selected_records_to_explain['instance names'] = [] +selected_records_to_explain['true labels'] = [] + +# %% [markdown] +# ## Attempted suicides + +# %% +attempted_suicides = incidents_test_df[ + (incidents_test_df['suicide']==1) & + (true_labels_test_df['death']==0) & + (incidents_test_df['n_participants']==1) +] +attempted_suicides + +# %% +attempted_suicide_index = attempted_suicides.index[0] +attempted_suicide_pos = incidents_test_df.index.get_loc(attempted_suicide_index) +selected_records_to_explain['positions'].append(attempted_suicide_pos) +selected_records_to_explain['instance names'].append('Attempted Suicide') +selected_records_to_explain['true labels'].append(true_labels_test[attempted_suicide_pos]) + +# %% [markdown] +# ## Mass shootings + +# %% +max_killed = incidents_test_df['n_killed'].max() +mass_shooting = incidents_test_df[incidents_test_df['n_killed'] == max_killed] +mass_shooting + +# %% +mass_shooting_index = mass_shooting.index[0] +mass_shooting_pos = incidents_test_df.index.get_loc(mass_shooting_index) +selected_records_to_explain['positions'].append(mass_shooting_pos) +selected_records_to_explain['instance names'].append('Mass shooting') +selected_records_to_explain['true labels'].append(true_labels_test[mass_shooting_pos]) + +# %% [markdown] +# ## Incidents predicted as Fatal with highest probability + +# %% +indeces_max_prob_death = [] +for clf_name in clf_names: + if clf_name != Classifiers.NC.value and clf_name != Classifiers.KNN.value: + pos = preds[clf_name]['probs'].idxmax() + indeces_max_prob_death.append(pos) + selected_records_to_explain['positions'].append(pos) + selected_records_to_explain['instance names'].append(f'Fatal with highest confidence by {clf_name}') + selected_records_to_explain['true labels'].append(true_labels_test[pos]) + +max_prob_death_table = {} +for index in indeces_max_prob_death: + max_prob_death_table[index] = {} + max_prob_death_table[index]['True_label'] = true_labels_test[index] + for clf_name in clf_names: + if clf_name != Classifiers.NC.value and clf_name != Classifiers.KNN.value: + max_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] +max_prob_death_table = pd.DataFrame(max_prob_death_table).T +max_prob_death_table.style.background_gradient(cmap='Blues', axis=1) + +# %% +pd.concat([ + max_prob_death_table.reset_index(), + incidents_test_df.iloc[indeces_max_prob_death].reset_index()], + axis=1 +) + +# %% [markdown] +# ## Incidents predict as Non-Fatal with highest probability + +# %% +indeces_min_prob_death = [] +for clf_name in clf_names: + if clf_name != Classifiers.NC.value and clf_name != Classifiers.KNN.value: + pos = preds[clf_name]['probs'].idxmin() + indeces_min_prob_death.append(pos) + selected_records_to_explain['positions'].append(pos) + selected_records_to_explain['instance names'].append(f'Non-Fatal with highest confidence by {clf_name}') + selected_records_to_explain['true labels'].append(true_labels_test[pos]) + +min_prob_death_table = {} +for index in indeces_min_prob_death: + min_prob_death_table[index] = {} + min_prob_death_table[index]['True_label'] = true_labels_test[index] + for clf_name in clf_names: + if clf_name != Classifiers.NC.value and clf_name != Classifiers.KNN.value: + min_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] +min_prob_death_table = pd.DataFrame(min_prob_death_table).T +min_prob_death_table.style.background_gradient(cmap='Blues', axis=1) + +# %% +pd.concat([ + min_prob_death_table.reset_index(), + incidents_test_df.iloc[indeces_min_prob_death].reset_index()], + axis=1 +) + +# %% +## Incidents with the highest uncertainty in the predicted outcomes + +# indeces_unknown_death = [] +# for clf_name in clf_names: +# if clf_name != NC and clf_name != KNN: +# indeces_unknown_death.append(np.abs(preds[clf_name]['probs']-0.5).idxmin()) + +# unknown_death_table = {} +# for index in indeces_unknown_death: +# unknown_death_table[index] = {} +# unknown_death_table[index]['True_label'] = true_labels_test_df.iloc[index]['death'] +# for clf_name in clf_names: +# if clf_name != NC and clf_name != KNN: +# unknown_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] +# unknown_death_table = pd.DataFrame(unknown_death_table).T +# unknown_death_table.style.background_gradient(cmap='Blues', axis=1) + +# pd.concat([ +# unknown_death_table.reset_index(), +# incidents_test_df.iloc[indeces_unknown_death].reset_index()], +# axis=1 +# ) + +# %% +selected_records_df = pd.DataFrame(selected_records_to_explain) +selected_records_df.to_csv('../data/explanation_results/selected_records_to_explain.csv') +selected_records_df + +# %% +random_records_to_explain = {} +random_records_to_explain['positions'] = np.arange(0, 51) # TODO: decidere se prenderli a caso o con un criterio +random_records_to_explain['true labels'] = true_labels_test[0: 51] +random_records_df = pd.DataFrame(random_records_to_explain) +random_records_df.to_csv('../data/explanation_results/random_records_to_explain.csv') + + diff --git a/TASK_4/lime_irene.py b/TASK_4/lime_irene.py index 4a10115..74a3aa5 100644 --- a/TASK_4/lime_irene.py +++ b/TASK_4/lime_irene.py @@ -5,17 +5,11 @@ # %% import pandas as pd import numpy as np -import matplotlib.pyplot as plt -import pickle import json import lime import lime.lime_tabular from IPython.display import HTML, Image import pydotplus -import tensorflow as tf -from scikeras.wrappers import KerasClassifier -from pytorch_tabnet.tab_model import TabNetClassifier -from keras.models import load_model from sklearn.tree import export_graphviz from explanation_utils import * @@ -29,24 +23,13 @@ def show_explanation(explanation): display(HTML(f"
{explanation.as_html()}
")) -def get_lime_feature_importances(lime_explanation, prediction): +def get_lime_importance_from_explanation(lime_explanation, prediction): pred_explanation = lime_explanation.local_exp[prediction] feature_importances = np.zeros(len(pred_explanation)) for tuple in pred_explanation: feature_importances[tuple[0]] = tuple[1] return feature_importances -# neural network model to later load -def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_activation_function): - n_features_in_ = meta["n_features_in_"] - model = tf.keras.models.Sequential() - model.add(tf.keras.layers.Input(shape=(n_features_in_,))) - for hidden_layer_size, activation_function, dropout in zip(hidden_layer_sizes, activation_functions, dropouts): - model.add(tf.keras.layers.Dense(hidden_layer_size, activation=activation_function)) - model.add(tf.keras.layers.Dropout(dropout)) - model.add(tf.keras.layers.Dense(1, activation=last_activation_function)) - return model - # %% # load the data incidents_train_df = pd.read_csv('../data/clf_indicators_train.csv', index_col=0) @@ -60,6 +43,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # load the names of the features features_db = json.loads(open('../data/clf_indicators_names_distance_based.json').read()) features_rb = json.loads(open('../data/clf_indicators_names_rule_based.json').read()) + # TODO: when sampling we want to ranndomly choose values assumed in the training set # (non vogliamo siano fuori dai range, alcune vogliamo siano intere (n_participants con rule based)) categorical_features_db = ['day_x', 'day_y', 'day_of_week_x', 'day_of_week_y', 'month_x', 'month_y', 'year', # ? @@ -80,69 +64,12 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti indicators_test_db_df = incidents_test_df[features_db] indicators_test_rb_df = incidents_test_df[features_rb] -# features to analyze -features_to_explore = [ - 'date', 'day_of_week', 'days_from_first_incident', - 'state', 'address', 'city', 'min_age', 'max_age', - 'n_child', 'n_teen', 'n_adult', 'n_males', 'n_females', - 'n_killed', 'n_injured', 'n_arrested', 'n_unharmed', - 'n_participants', 'notes', 'incident_characteristics1', - 'incident_characteristics2', 'democrat', 'poverty_perc', - 'gun_law_rank', 'aggression', 'accidental', 'defensive', - 'suicide', 'road', 'house', 'school', 'business', - 'illegal_holding', 'drug_alcohol', 'officers', - 'organized', 'social_reasons', 'abduction' -] +clf_names = [clf.value for clf in Classifiers] +rb_clf_names = [Classifiers.DT.value, Classifiers.RF.value, Classifiers.XGB.value, Classifiers.RIPPER.value] -# load models and predictions -DT = 'DecisionTreeClassifier' -RF = 'RandomForestClassifier' -XGB = 'XGBClassifier' -NC = 'NearestCentroidClassifier' -KNN = 'KNearestNeighborsClassifier' -SVM = 'SupportVectorMachineClassifier' -NN = 'NeuralNetworkClassifier' -TN = 'TabNetClassifier' -clf_names = [DT, RF, XGB, NC, KNN, SVM, NN, TN] -rb_clf_names = [DT, RF, XGB] - -data_dir = '../data/classification_results/' -positions_to_evaluate = [] -names_to_evaluate = [] -true_labels_to_evaluate = [] -preds = {} -models = {} -for clf_name in clf_names: - preds[clf_name] = {} - # load predictions - clf_preds = pd.read_csv(data_dir+clf_name+'_preds.csv') - preds[clf_name]['labels'] = clf_preds['labels'] - # load probabilities - if clf_name != NC and clf_name != KNN: - preds[clf_name]['probs'] = clf_preds['probs'] - # load the model - if clf_name == NN: - nn = KerasClassifier( - nn_model, - metrics=['accuracy'], - validation_split=0.2, - model__hidden_layer_sizes=None, - model__activation_functions=None, - model__dropouts=None, - model__last_activation_function=None - ) - nn.model = load_model(data_dir+clf_name+'.h5') - models[clf_name] = nn - elif clf_name == TN: - tn = TabNetClassifier() - tn.load_model(data_dir+TN+'.pkl.zip') - models[clf_name] = tn - else: - with open(data_dir+clf_name+'.pkl', 'rb') as file: - models[clf_name] = pickle.load(file) - if clf_name != XGB: - models[clf_name].feature_names_in_ = None # to silence warnings +preds = get_classifiers_predictions('../data/classification_results/') +classifiers = get_classifiers_objects('../data/classification_results/') # %% # TODO: cambiare i parametri sotto?? @@ -180,32 +107,19 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti REGRESSOR = None # Ridge regression # %% [markdown] -# # Attempted suicide - -# %% [markdown] -# Attempted suicides involving a single person: +# ## Attempted Suicide # %% -attempted_suicide_index = incidents_test_df[ - (incidents_test_df['suicide']==1) & - (true_labels_test_df['death']==0) & - (incidents_test_df['n_participants']==1) -].index[0] -attempted_suicide_pos = incidents_test_df.index.get_loc(attempted_suicide_index) -positions_to_evaluate.append(attempted_suicide_pos) -names_to_evaluate.append('Attempted Suicide') -true_labels_to_evaluate.append(true_labels_test[attempted_suicide_pos]) -attempted_suicide_db_inst = indicators_test_db_df.iloc[attempted_suicide_pos].values -attempted_suicide_rb_inst = indicators_test_rb_df.iloc[attempted_suicide_pos].values -incidents_test_df.iloc[attempted_suicide_pos].to_frame().T +selected_records_to_explain_df = pd.read_csv('../data/explanation_results/selected_records_to_explain.csv', index_col=0) +attempted_suicide_pos = selected_records_to_explain_df[selected_records_to_explain_df['instance names']=='Attempted Suicide']['positions'][0] # %% [markdown] # ## Decision Tree # %% explanation = explainer_rb.explain_instance( - attempted_suicide_rb_inst, - models[DT].predict_proba, + indicators_test_rb_df.iloc[attempted_suicide_pos].values, + classifiers[Classifiers.DT.value].predict_proba, num_features=len(features_rb), num_samples=NUM_SAMPLES, distance_metric=DISTANCE_METRIC, @@ -216,7 +130,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% dot_data = export_graphviz( - models[DT], + classifiers[Classifiers.DT.value], out_file=None, feature_names=list(indicators_train_rb_df.columns), filled=True, @@ -231,7 +145,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% explanation = explainer_rb.explain_instance( indicators_test_rb_df.iloc[attempted_suicide_pos].values, - models[RF].predict_proba, + classifiers[Classifiers.RF.value].predict_proba, num_features=len(features_rb), num_samples=NUM_SAMPLES, distance_metric=DISTANCE_METRIC, @@ -245,7 +159,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% # explanation = explainer_rb.explain_instance( # indicators_test_rb_df.iloc[attempted_suicide_pos].values, -# models[XGB].predict_proba, +# classifiers[XGB].predict_proba, # num_features=len(features_rb), # num_samples=NUM_SAMPLES, # distance_metric=DISTANCE_METRIC, @@ -259,7 +173,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% explanation = explainer_db.explain_instance( indicators_test_db_df.iloc[attempted_suicide_pos].values, - models[KNN].predict_proba, + classifiers[Classifiers.KNN.value].predict_proba, num_features=len(features_db), num_samples=NUM_SAMPLES, distance_metric=DISTANCE_METRIC, @@ -268,11 +182,11 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti show_explanation(explanation) # %% -if models[KNN].get_params()['n_neighbors'] < 3: +if classifiers[Classifiers.KNN.value].get_params()['n_neighbors'] < 3: n_neighbors = 3 else: - n_neighbors = models[KNN].get_params()['n_neighbors'] -distances, indeces = models[KNN].kneighbors( + n_neighbors = classifiers[Classifiers.KNN.value].get_params()['n_neighbors'] +distances, indeces = classifiers[Classifiers.KNN.value].kneighbors( X=indicators_test_db_df.iloc[attempted_suicide_pos].values.reshape(-1,len(features_db)), n_neighbors=n_neighbors, return_distance=True @@ -291,7 +205,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% explanation = explainer_db.explain_instance( indicators_test_db_df.iloc[attempted_suicide_pos].values, - models[SVM].predict_proba, + classifiers[Classifiers.SVM.value].predict_proba, num_features=len(features_db), num_samples=NUM_SAMPLES, distance_metric=DISTANCE_METRIC, @@ -300,13 +214,13 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti show_explanation(explanation) # %% -hyperplane_dists = models[SVM].decision_function( +hyperplane_dists = classifiers[Classifiers.SVM.value].decision_function( X=pd.concat([ indicators_test_db_df.iloc[attempted_suicide_pos].to_frame().T.reset_index(), indicators_train_db_df.iloc[indeces[0]].reset_index() # neighbors (could be both mortal or not) ]).drop(columns=['index']) ) -probas = models[SVM].predict_proba( +probas = classifiers[Classifiers.SVM.value].predict_proba( X=pd.concat([ indicators_test_db_df.iloc[attempted_suicide_pos].to_frame().T.reset_index(), indicators_train_db_df.iloc[indeces[0]].reset_index() # neighbors (could be both mortal or not) @@ -324,7 +238,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% # explanation = explainer_db.explain_instance( # indicators_test_db_df.iloc[attempted_suicide_pos].values, -# models[NN].predict_proba, +# classifiers[NN].predict_proba, # num_features=len(features_db), # num_samples=NUM_SAMPLES, # distance_metric=DISTANCE_METRIC, @@ -338,7 +252,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% explanation = explainer_db.explain_instance( indicators_test_db_df.iloc[attempted_suicide_pos].values, - models[TN].predict_proba, + classifiers[Classifiers.TN.value].predict_proba, num_features=len(features_db), num_samples=NUM_SAMPLES, distance_metric=DISTANCE_METRIC, @@ -346,211 +260,99 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti ) show_explanation(explanation) -# %% [markdown] -# # Mass shooting - -# %% [markdown] -# Involving many killed people: - # %% -max_killed = incidents_test_df['n_killed'].max() -mass_shooting_index = incidents_test_df[incidents_test_df['n_killed'] == max_killed].index[0] -mass_shooting_pos = incidents_test_df.index.get_loc(mass_shooting_index) -positions_to_evaluate.append(mass_shooting_pos) -names_to_evaluate.append('Mass shooting') -true_labels_to_evaluate.append(true_labels_test[mass_shooting_pos]) -mass_shooting_db_inst = indicators_test_db_df.iloc[mass_shooting_pos].values -mass_shooting_rb_inst = indicators_test_rb_df.iloc[mass_shooting_pos].values -incidents_test_df.iloc[mass_shooting_pos].to_frame().T - -# %% -# TODO: fare come sopra - -# %% [markdown] -# # Incidents predicted as Fatal with highest probability - -# %% -indeces_max_prob_death = [] -for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - pos = preds[clf_name]['probs'].idxmax() - indeces_max_prob_death.append(pos) - positions_to_evaluate.append(pos) - names_to_evaluate.append(f'Fatal with highest confidence by {clf_name}') - true_labels_to_evaluate.append(true_labels_test[pos]) - -max_prob_death_table = {} -for index in indeces_max_prob_death: - max_prob_death_table[index] = {} - max_prob_death_table[index]['True_label'] = true_labels_test[index] - for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - max_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -max_prob_death_table = pd.DataFrame(max_prob_death_table).T -max_prob_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -pd.concat([ - max_prob_death_table.reset_index(), - incidents_test_df.iloc[indeces_max_prob_death].reset_index()[features_to_explore]], - axis=1 -) - -# %% [markdown] -# # Incidents predicted as Non-Fatal with highest probability - -# %% -indeces_min_prob_death = [] -for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - pos = preds[clf_name]['probs'].idxmin() - indeces_min_prob_death.append(pos) - positions_to_evaluate.append(pos) - names_to_evaluate.append(f'Non-Fatal with highest confidence by {clf_name}') - true_labels_to_evaluate.append(true_labels_test[pos]) - -min_prob_death_table = {} -for index in indeces_min_prob_death: - min_prob_death_table[index] = {} - min_prob_death_table[index]['True_label'] = true_labels_test[index] - for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - min_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -min_prob_death_table = pd.DataFrame(min_prob_death_table).T -min_prob_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -pd.concat([ - min_prob_death_table.reset_index(), - incidents_test_df.iloc[indeces_min_prob_death].reset_index()[features_to_explore]], - axis=1 -) - -# %% [markdown] -# ## Incidents with the highest uncertainty in the predicted outcomes - -# %% -# indeces_unknown_death = [] -# for clf_name in clf_names: -# if clf_name != NC and clf_name != KNN: -# indeces_unknown_death.append(np.abs(preds[clf_name]['probs']-0.5).idxmin()) - -# unknown_death_table = {} -# for index in indeces_unknown_death: -# unknown_death_table[index] = {} -# unknown_death_table[index]['True_label'] = true_labels_test_df.iloc[index]['death'] -# for clf_name in clf_names: -# if clf_name != NC and clf_name != KNN: -# unknown_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -# unknown_death_table = pd.DataFrame(unknown_death_table).T -# unknown_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -# pd.concat([ -# unknown_death_table.reset_index(), -# incidents_test_df.iloc[indeces_unknown_death].reset_index()[features_to_explore]], -# axis=1 -# ) +# TODO: fare lo stesso per mass shooting # %% [markdown] # ## Evaluation # %% # load the already computed default values for features -non_fatal_db_default = pd.read_csv(data_dir+'non_fatal_db_default_features.csv').to_numpy()[0] -fatal_db_default = pd.read_csv(data_dir+'fatal_db_default_features.csv').to_numpy()[0] -non_fatal_rb_default = pd.read_csv(data_dir+'non_fatal_rb_default_features.csv').to_numpy()[0] -fatal_rb_default = pd.read_csv(data_dir+'fatal_rb_default_features.csv').to_numpy()[0] +non_fatal_db_default = pd.read_csv('../data/classification_results/non_fatal_db_default_features.csv').to_numpy()[0] +fatal_db_default = pd.read_csv('../data/classification_results/fatal_db_default_features.csv').to_numpy()[0] +features_db_defaults = [non_fatal_db_default, fatal_db_default] +non_fatal_rb_default = pd.read_csv('../data/classification_results/non_fatal_rb_default_features.csv').to_numpy()[0] +fatal_rb_default = pd.read_csv('../data/classification_results/fatal_rb_default_features.csv').to_numpy()[0] +features_rb_default = [non_fatal_rb_default, fatal_rb_default] # %% -# TODO: assicurarsi di accedere a quelli giusti! +clf_names = [Classifiers.DT.value, Classifiers.RF.value] # TODO: da togliere quando li abbiamo tutti -clf_names = [DT, RF] # TODO: da togliere quando li abbiamo tutti +# %% +positions_to_explain = selected_records_to_explain_df['positions'].to_list() +instance_names_to_explain = selected_records_to_explain_df['instance names'].to_list() +true_labels_to_explain = selected_records_to_explain_df['true labels'].to_list() -clfs_metrics = [] +metrics_selected_records = [] for clf_name in clf_names: - model = models[clf_name] + classifier = classifiers[clf_name] if clf_name in rb_clf_names: explainer = explainer_rb - samples = indicators_test_rb_df.iloc[positions_to_evaluate].values + feature_defaults = features_rb_default + instances = indicators_test_rb_df.iloc[positions_to_explain].values else: explainer = explainer_db - samples = indicators_test_db_df.iloc[positions_to_evaluate].values + feature_defaults = features_db_defaults + instances = indicators_test_db_df.iloc[positions_to_explain].values clf_metrics = {} - for i in range(samples.shape[0]): - prediction = model.predict(samples[i].reshape(1,-1))[0] - explanation = explainer.explain_instance(samples[i], model.predict_proba, num_features=samples.shape[1], top_labels=1) - feature_importances = get_lime_feature_importances(explanation, prediction) - if clf_name in rb_clf_names: - if prediction == 0: - feature_defaults = non_fatal_rb_default - else: - feature_defaults = fatal_rb_default - else: - if prediction == 0: - feature_defaults = non_fatal_db_default - else: - feature_defaults = fatal_db_default - sample_metric = evaluate_explanation(model, samples[i], feature_importances, feature_defaults) - clf_metrics[names_to_evaluate[i]] = sample_metric + for i in range(instances.shape[0]): + prediction = classifier.predict(instances[i].reshape(1,-1))[0] + explanation = explainer.explain_instance(instances[i], classifier.predict_proba, num_features=instances.shape[1], top_labels=1) + feature_importances = get_lime_importance_from_explanation(explanation, prediction) + sample_metric = evaluate_explanation(classifier, instances[i], feature_importances, feature_defaults[true_labels_to_explain[i]]) + clf_metrics[instance_names_to_explain[i]] = sample_metric clf_metrics_df = pd.DataFrame(clf_metrics).T clf_metrics_df.columns = pd.MultiIndex.from_product([clf_metrics_df.columns, [clf_name]]) - clfs_metrics.append(clf_metrics_df) + metrics_selected_records.append(clf_metrics_df) -clfs_metrics = clfs_metrics[0].join(clfs_metrics[1:]).sort_index(level=0, axis=1) -clfs_metrics['True Label'] = true_labels_to_evaluate +metrics_selected_records_df = metrics_selected_records[0].join(metrics_selected_records[1:]).sort_index(level=0, axis=1) +metrics_selected_records_df['True Label'] = true_labels_to_explain # save faithfulness -faithfulness = clfs_metrics[['faithfulness']] -faithfulness.columns = faithfulness.columns.droplevel() -faithfulness.to_csv('../data/eplanation_results/lime_faithfulness.csv') +faithfulness_df = metrics_selected_records_df[['faithfulness']] +faithfulness_df.columns = faithfulness_df.columns.droplevel() +faithfulness_df.to_csv('../data/explanation_results/lime_faithfulness_selected_records.csv') # save monotonity -monotonity = clfs_metrics[['monotonity']] -monotonity.columns = monotonity.columns.droplevel() -monotonity.to_csv('../data/eplanation_results/lime_monotonity.csv') +monotonity_df = metrics_selected_records_df[['monotonity']] +monotonity_df.columns = monotonity_df.columns.droplevel() +monotonity_df.to_csv('../data/explanation_results/lime_monotonity_selected_records.csv') -clfs_metrics +metrics_selected_records_df # %% -positions_to_evaluate = np.arange(0,51) # TODO: decidere quanti prenderne e se prenderli a caso o con un criterio -clf_names = [DT, RF] # TODO: da togliere quando li abbiamo tutti +random_records_to_explain_df = pd.read_csv('../data/explanation_results/random_records_to_explain.csv', index_col=0) +positions_to_explain = random_records_to_explain_df['positions'].to_list() +true_labels_to_explain = random_records_to_explain_df['true labels'].to_list() -clfs_metrics = {} +metrics_random_records = {} for clf_name in clf_names: - model = models[clf_name] + classifier = classifiers[clf_name] if clf_name in rb_clf_names: explainer = explainer_rb - samples = indicators_test_rb_df.iloc[positions_to_evaluate].values + feature_defaults = features_rb_default + instances = indicators_test_rb_df.iloc[positions_to_explain].values else: explainer = explainer_db - samples = indicators_test_db_df.iloc[positions_to_evaluate].values + feature_defaults = features_db_defaults + instances = indicators_test_db_df.iloc[positions_to_explain].values - clfs_metrics[clf_name] = {} faithfulness = [] - monotonity = [] - for i in range(samples.shape[0]): - prediction = model.predict(samples[i].reshape(1,-1))[0] - explanation = explainer.explain_instance(samples[i], model.predict_proba, num_features=samples.shape[1], top_labels=1) - feature_importances = get_lime_feature_importances(explanation, prediction) - if clf_name in rb_clf_names: - if prediction == 0: - feature_defaults = non_fatal_rb_default - else: - feature_defaults = fatal_rb_default - else: - if prediction == 0: - feature_defaults = non_fatal_db_default - else: - feature_defaults = fatal_db_default - sample_metric = evaluate_explanation(model, samples[i], feature_importances, feature_defaults) + for i in range(instances.shape[0]): + prediction = classifier.predict(instances[i].reshape(1,-1))[0] + explanation = explainer.explain_instance(instances[i], classifier.predict_proba, num_features=instances.shape[1], top_labels=1) + feature_importances = get_lime_importance_from_explanation(explanation, prediction) + sample_metric = evaluate_explanation(classifier, instances[i], feature_importances, feature_defaults[true_labels_to_explain[i]]) faithfulness.append(sample_metric['faithfulness']) - #monotonity.append(sample_metric['monotonity']) - clfs_metrics[clf_name]['mean faithfulness'] = np.nanmean(faithfulness) - clfs_metrics[clf_name]['std faithfulness'] = np.nanstd(faithfulness) - -pd.DataFrame(clfs_metrics) + + metrics_random_records[clf_name] = {} + metrics_random_records[clf_name]['mean faithfulness'] = np.nanmean(faithfulness) + metrics_random_records[clf_name]['std faithfulness'] = np.nanstd(faithfulness) + +metrics_random_records_df = pd.DataFrame(metrics_random_records) +metrics_random_records_df.to_csv('../data/explanation_results/lime_metrics_random_records.csv') +metrics_random_records_df diff --git a/TASK_4/shap_irene.py b/TASK_4/shap_irene.py index fae7b02..dc00ba0 100644 --- a/TASK_4/shap_irene.py +++ b/TASK_4/shap_irene.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # %% # TODO: # provare diversi metodi SHAP @@ -7,37 +6,19 @@ import pandas as pd import matplotlib import numpy as np -import matplotlib.pyplot as plt -import pickle import json import shap from IPython.display import HTML -import tensorflow as tf -from scikeras.wrappers import KerasClassifier -from pytorch_tabnet.tab_model import TabNetClassifier -from keras.models import load_model from explanation_utils import * -# %% -def show_plot(plot): - display(HTML(f"
{plot.html()}
")) - # %% RANDOM_STATE = 42 pd.set_option('display.max_columns', None) pd.set_option('max_colwidth', None) -# neural network model to later load -def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_activation_function): - n_features_in_ = meta["n_features_in_"] - model = tf.keras.models.Sequential() - model.add(tf.keras.layers.Input(shape=(n_features_in_,))) - for hidden_layer_size, activation_function, dropout in zip(hidden_layer_sizes, activation_functions, dropouts): - model.add(tf.keras.layers.Dense(hidden_layer_size, activation=activation_function)) - model.add(tf.keras.layers.Dropout(dropout)) - model.add(tf.keras.layers.Dense(1, activation=last_activation_function)) - return model +def show_plot(plot): + display(HTML(f"
{plot.html()}
")) # %% # load the data @@ -59,67 +40,11 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti indicators_test_db_df = incidents_test_df[features_db] indicators_test_rb_df = incidents_test_df[features_rb] -# features to analyze -features_to_explore = [ - 'date', 'day_of_week', 'days_from_first_incident', - 'state', 'address', 'city', 'min_age', 'max_age', - 'n_child', 'n_teen', 'n_adult', 'n_males', 'n_females', - 'n_killed', 'n_injured', 'n_arrested', 'n_unharmed', - 'n_participants', 'notes', 'incident_characteristics1', - 'incident_characteristics2', 'democrat', 'poverty_perc', - 'gun_law_rank', 'aggression', 'accidental', 'defensive', - 'suicide', 'road', 'house', 'school', 'business', - 'illegal_holding', 'drug_alcohol', 'officers', - 'organized', 'social_reasons', 'abduction' -] - -# load models and predictions -DT = 'DecisionTreeClassifier' -RF = 'RandomForestClassifier' -XGB = 'XGBClassifier' -NC = 'NearestCentroidClassifier' -KNN = 'KNearestNeighborsClassifier' -SVM = 'SupportVectorMachineClassifier' -NN = 'NeuralNetworkClassifier' -TN = 'TabNetClassifier' -clf_names = [DT, RF, XGB, NC, KNN, SVM, NN, TN] -rb_clf_names = [DT, RF, XGB] - -data_dir = '../data/classification_results/' -positions_to_evaluate = [] -names_to_evaluate = [] -true_labels_to_evaluate = [] -preds = {} -models = {} +clf_names = [clf.value for clf in Classifiers] +rb_clf_names = [Classifiers.DT.value, Classifiers.RF.value, Classifiers.XGB.value, Classifiers.RIPPER.value] -for clf_name in clf_names: - preds[clf_name] = {} - # load predictions - clf_preds = pd.read_csv(data_dir+clf_name+'_preds.csv') - preds[clf_name]['labels'] = clf_preds['labels'] - # load probabilities - if clf_name != NC and clf_name != KNN: - preds[clf_name]['probs'] = clf_preds['probs'] - # load the model - if clf_name == NN: - nn = KerasClassifier( - nn_model, - metrics=['accuracy'], - validation_split=0.2, - model__hidden_layer_sizes=None, - model__activation_functions=None, - model__dropouts=None, - model__last_activation_function=None - ) - nn.model = load_model(data_dir+clf_name+'.h5') - models[clf_name] = nn - elif clf_name == TN: - tn = TabNetClassifier() - tn.load_model(data_dir+TN+'.pkl.zip') - models[clf_name] = tn - else: - with open(data_dir+clf_name+'.pkl', 'rb') as file: - models[clf_name] = pickle.load(file) +preds = get_classifiers_predictions('../data/classification_results/') +classifiers = get_classifiers_objects('../data/classification_results/') # %% [markdown] # # Global explanation @@ -132,15 +57,15 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # ## Decision Tree # %% -shap_values[DT] = shap.TreeExplainer(models[DT]).shap_values(indicators_test_rb_df) +shap_values[Classifiers.DT.value] = shap.TreeExplainer(classifiers[Classifiers.DT.value]).shap_values(indicators_test_rb_df) # %% -shap.summary_plot(shap_values[DT], indicators_test_rb_df,plot_size=(10,10)) +shap.summary_plot(shap_values[Classifiers.DT.value], indicators_test_rb_df,plot_size=(10,10)) # in classificazione binaria le altezze delle barre sono uguali # alto abs(shap) => grande importanza # %% -shap.dependence_plot("avg_age", shap_values[DT][1], indicators_test_rb_df) # Fatal (shap_values_tree[0] is symmetric, only the sign changes) +shap.dependence_plot("avg_age", shap_values[Classifiers.DT.value][1], indicators_test_rb_df) # Fatal (shap_values_tree[0] is symmetric, only the sign changes) # democrat è la feature che interagisce di più con avg_age # età>60 => fatale # età<20 => non fatale @@ -151,25 +76,25 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # TODO: farlo per altri attributi # %% -#shap_interaction_values[DT] = shap.TreeExplainer(models[DT]).shap_interaction_values(indicators_test_rb_df) +shap_interaction_values[Classifiers.DT.value] = shap.TreeExplainer(classifiers[Classifiers.DT.value]).shap_interaction_values(indicators_test_rb_df) # %% -#shap.summary_plot(shap_interaction_values[DT][1], indicators_test_rb_df, max_display=31) # Fatal +shap.summary_plot(shap_interaction_values[Classifiers.DT.value][1], indicators_test_rb_df, max_display=31) # Fatal # il colore è il valore assunto dalla feature sulle righe (road, school, etc sono per lp più blu sulle righe) # la posizione lungo y non ha una scala, mostra solo quanti punti ci sono # interaction value negativo => # %% -#shap.summary_plot(shap_interaction_values[DT][1], indicators_test_rb_df, max_display=7) +shap.summary_plot(shap_interaction_values[Classifiers.DT.value][1], indicators_test_rb_df, max_display=7) # età-eta # tanti punti con interazione negativa; il valore dell'età è basso (blu) # aggression-age # %% -# shap.dependence_plot( -# ("avg_age", "aggression"), -# shap_interaction_values[DT][1][:500], indicators_test_rb_df[:500] -# ) +shap.dependence_plot( + ("avg_age", "aggression"), + shap_interaction_values[Classifiers.DT.value][1][:500], indicators_test_rb_df[:500] +) # quando non c'è aggressione, avg age assume un po' tutti i valori e non c'è interazione # sotto i 30 anni l'interazione è sia positiva che negativa # sopra i 40 anni l'interazione è nulla o negativa @@ -178,7 +103,7 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% shap.initjs() -force_plot = shap.force_plot(shap.TreeExplainer(models[DT]).expected_value[0], shap_values[DT][0][:500], indicators_test_rb_df[:500]) +force_plot = shap.force_plot(shap.TreeExplainer(classifiers[Classifiers.DT.value]).expected_value[0], shap_values[Classifiers.DT.value][0][:500], indicators_test_rb_df[:500]) show = show_plot(force_plot) # gli esempi sono ordinati su asse x # le feature blue determinerebbero non mortale, rosse mortale @@ -191,26 +116,14 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # ## Random Forest # %% -#shap_values[RF] = shap.TreeExplainer(models[RF]).shap_values(indicators_test_rb_df) +#shap_values[Classifier.RF.value] = shap.TreeExplainer(classifiers[Classifier.DT.value]).shap_values(indicators_test_rb_df) # %% [markdown] # # Attempted suicide -# -# Attempted suicides involving a single person: # %% -attempted_suicide_index = incidents_test_df[ - (incidents_test_df['suicide']==1) & - (true_labels_test_df['death']==0) & - (incidents_test_df['n_participants']==1) -].index[0] -attempted_suicide_pos = incidents_test_df.index.get_loc(attempted_suicide_index) -positions_to_evaluate.append(attempted_suicide_pos) -names_to_evaluate.append('Attempted Suicide') -true_labels_to_evaluate.append(true_labels_test[attempted_suicide_pos]) -attempted_suicide_db_inst = indicators_test_db_df.iloc[attempted_suicide_pos].values -attempted_suicide_rb_inst = indicators_test_rb_df.iloc[attempted_suicide_pos].values -incidents_test_df.iloc[attempted_suicide_pos].to_frame().T +selected_records_to_explain_df = pd.read_csv('../data/explanation_results/selected_records_to_explain.csv', index_col=0) +attempted_suicide_pos = selected_records_to_explain_df[selected_records_to_explain_df['instance names']=='Attempted Suicide']['positions'][0] # %% [markdown] # ## Decision Tree @@ -218,8 +131,8 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% pos = attempted_suicide_pos shap.initjs() -attempted_suicide_expected_value = shap.TreeExplainer(models[DT]).expected_value[0] # prior (base value) -attempted_suicide_shap_values = shap_values[DT][0][pos] +attempted_suicide_expected_value = shap.TreeExplainer(classifiers[Classifiers.DT.value]).expected_value[0] # prior (base value) +attempted_suicide_shap_values = shap_values[Classifiers.DT.value][0][pos] attempted_suicide_sample = indicators_test_rb_df.iloc[pos,:] shap.force_plot(attempted_suicide_expected_value, attempted_suicide_shap_values, attempted_suicide_sample, matplotlib=matplotlib) # il valore nero è la probabilità predetta @@ -236,11 +149,11 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # ## K Nearest Neighbors # %% -if models[KNN].get_params()['n_neighbors'] < 3: +if classifiers[Classifiers.KNN.value].get_params()['n_neighbors'] < 3: n_neighbors = 3 else: - n_neighbors = models[KNN].get_params()['n_neighbors'] -distances, indeces = models[KNN].kneighbors( + n_neighbors = classifiers[Classifiers.KNN.value].get_params()['n_neighbors'] +distances, indeces = classifiers[Classifiers.KNN.value].kneighbors( X=indicators_test_db_df.iloc[attempted_suicide_pos].values.reshape(-1,len(features_db)), n_neighbors=n_neighbors, return_distance=True @@ -257,13 +170,13 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # ## Support Vector Machine # %% -hyperplane_dists = models[SVM].decision_function( +hyperplane_dists = classifiers[Classifiers.SVM.value].decision_function( X=pd.concat([ indicators_test_db_df.iloc[attempted_suicide_pos].to_frame().T.reset_index(), indicators_train_db_df.iloc[indeces[0]].reset_index() # neighbors (could be both mortal or not) ]).drop(columns=['index']) ) -probas = models[SVM].predict_proba( +probas = classifiers[Classifiers.SVM.value].predict_proba( X=pd.concat([ indicators_test_db_df.iloc[attempted_suicide_pos].to_frame().T.reset_index(), indicators_train_db_df.iloc[indeces[0]].reset_index() # neighbors (could be both mortal or not) @@ -281,207 +194,94 @@ def nn_model(meta, hidden_layer_sizes, dropouts, activation_functions, last_acti # %% [markdown] # ## TabNet -# %% [markdown] -# # Mass shooting - -# %% [markdown] -# Involving many killed people: - # %% -max_killed = incidents_test_df['n_killed'].max() -mass_shooting_index = incidents_test_df[incidents_test_df['n_killed'] == max_killed].index[0] -mass_shooting_pos = incidents_test_df.index.get_loc(mass_shooting_index) -positions_to_evaluate.append(mass_shooting_pos) -names_to_evaluate.append('Mass shooting') -true_labels_to_evaluate.append(true_labels_test[mass_shooting_pos]) -mass_shooting_db_inst = indicators_test_db_df.iloc[mass_shooting_pos].values -mass_shooting_rb_inst = indicators_test_rb_df.iloc[mass_shooting_pos].values -incidents_test_df.iloc[mass_shooting_pos].to_frame().T - -# %% -# TODO: fare come sopra - -# %% [markdown] -# # Incidents predicted as Fatal with highest probability - -# %% -indeces_max_prob_death = [] -for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - pos = preds[clf_name]['probs'].idxmax() - indeces_max_prob_death.append(pos) - positions_to_evaluate.append(pos) - names_to_evaluate.append(f'Fatal with highest confidence by {clf_name}') - true_labels_to_evaluate.append(true_labels_test[pos]) - -max_prob_death_table = {} -for index in indeces_max_prob_death: - max_prob_death_table[index] = {} - max_prob_death_table[index]['True_label'] = true_labels_test[index] - for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - max_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -max_prob_death_table = pd.DataFrame(max_prob_death_table).T -max_prob_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -pd.concat([ - max_prob_death_table.reset_index(), - incidents_test_df.iloc[indeces_max_prob_death].reset_index()[features_to_explore]], - axis=1 -) - -# %% [markdown] -# # Incidents predicted as Non-Fatal with highest probability - -# %% -indeces_min_prob_death = [] -for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - pos = preds[clf_name]['probs'].idxmin() - indeces_min_prob_death.append(pos) - positions_to_evaluate.append(pos) - names_to_evaluate.append(f'Non-Fatal with highest confidence by {clf_name}') - true_labels_to_evaluate.append(true_labels_test[pos]) - -min_prob_death_table = {} -for index in indeces_min_prob_death: - min_prob_death_table[index] = {} - min_prob_death_table[index]['True_label'] = true_labels_test[index] - for clf_name in clf_names: - if clf_name != NC and clf_name != KNN: - min_prob_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -min_prob_death_table = pd.DataFrame(min_prob_death_table).T -min_prob_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -pd.concat([ - min_prob_death_table.reset_index(), - incidents_test_df.iloc[indeces_min_prob_death].reset_index()[features_to_explore]], - axis=1 -) - -# %% [markdown] -# ## Incidents with the highest uncertainty in the predicted outcomes - -# %% -# indeces_unknown_death = [] -# for clf_name in clf_names: -# if clf_name != NC and clf_name != KNN: -# indeces_unknown_death.append(np.abs(preds[clf_name]['probs']-0.5).idxmin()) - -# unknown_death_table = {} -# for index in indeces_unknown_death: -# unknown_death_table[index] = {} -# unknown_death_table[index]['True_label'] = true_labels_test_df.iloc[index]['death'] -# for clf_name in clf_names: -# if clf_name != NC and clf_name != KNN: -# unknown_death_table[index][clf_name+'_pos_prob'] = preds[clf_name]['probs'][index] -# unknown_death_table = pd.DataFrame(unknown_death_table).T -# unknown_death_table.style.background_gradient(cmap='Blues', axis=1) - -# %% -# pd.concat([ -# unknown_death_table.reset_index(), -# incidents_test_df.iloc[indeces_unknown_death].reset_index()[features_to_explore]], -# axis=1 -# ) +# TODO: fare lo stesso per mass shooting # %% [markdown] # ## Evaluation # %% # load the already computed default values for features -non_fatal_db_default = pd.read_csv(data_dir+'non_fatal_db_default_features.csv').to_numpy()[0] -fatal_db_default = pd.read_csv(data_dir+'fatal_db_default_features.csv').to_numpy()[0] -non_fatal_rb_default = pd.read_csv(data_dir+'non_fatal_rb_default_features.csv').to_numpy()[0] -fatal_rb_default = pd.read_csv(data_dir+'fatal_rb_default_features.csv').to_numpy()[0] +non_fatal_db_default = pd.read_csv('../data/classification_results/non_fatal_db_default_features.csv').to_numpy()[0] +fatal_db_default = pd.read_csv('../data/classification_results/fatal_db_default_features.csv').to_numpy()[0] +features_db_defaults = [non_fatal_db_default, fatal_db_default] +non_fatal_rb_default = pd.read_csv('../data/classification_results/non_fatal_rb_default_features.csv').to_numpy()[0] +fatal_rb_default = pd.read_csv('../data/classification_results/fatal_rb_default_features.csv').to_numpy()[0] +features_rb_default = [non_fatal_rb_default, fatal_rb_default] # %% # TODO: assicurarsi di accedere a quelli giusti! -clf_names = [DT] # TODO: da togliere quando li abbiamo tutti +clf_names = [Classifiers.DT.value] # TODO: da togliere quando li abbiamo tutti + +# %% +positions_to_explain = selected_records_to_explain_df['positions'].to_list() +instance_names_to_explain = selected_records_to_explain_df['instance names'].to_list() +true_labels_to_explain = selected_records_to_explain_df['true labels'].to_list() -clfs_metrics = [] +metrics_random_records = [] for clf_name in clf_names: - model = models[clf_name] + classifier = classifiers[clf_name] if clf_name in rb_clf_names: - samples = indicators_test_rb_df.iloc[positions_to_evaluate].values + feature_defaults = features_rb_default + instances = indicators_test_rb_df.iloc[positions_to_explain].values else: - samples = indicators_test_db_df.iloc[positions_to_evaluate].values - - if clf_name != XGB: - models[clf_name].feature_names_in_ = None # to silence warnings + feature_defaults = features_db_default + instances = indicators_test_db_df.iloc[positions_to_explain].values clf_metrics = {} - for i in range(samples.shape[0]): - prediction = model.predict(samples[i].reshape(1,-1))[0] - feature_importances = shap_values[clf_name][prediction][positions_to_evaluate[i]] - if clf_name in rb_clf_names: - if prediction == 0: - feature_defaults = non_fatal_rb_default - else: - feature_defaults = fatal_rb_default - else: - if prediction == 0: - feature_defaults = non_fatal_db_default - else: - feature_defaults = fatal_db_default - sample_metric = evaluate_explanation(model, samples[i], feature_importances, feature_defaults) - clf_metrics[names_to_evaluate[i]] = sample_metric + for i in range(instances.shape[0]): + prediction = classifier.predict(instances[i].reshape(1,-1))[0] + feature_importances = shap_values[clf_name][prediction][positions_to_explain[i]] # TODO: diversi shap values + sample_metric = evaluate_explanation(classifier, instances[i], feature_importances, feature_defaults[true_labels_to_explain[i]]) + clf_metrics[instance_names_to_explain[i]] = sample_metric clf_metrics_df = pd.DataFrame(clf_metrics).T clf_metrics_df.columns = pd.MultiIndex.from_product([clf_metrics_df.columns, [clf_name]]) - clfs_metrics.append(clf_metrics_df) + metrics_random_records.append(clf_metrics_df) -clfs_metrics = clfs_metrics[0].join(clfs_metrics[1:]).sort_index(level=0, axis=1) -clfs_metrics['True Label'] = true_labels_to_evaluate +metrics_selected_records_df = metrics_random_records[0].join(metrics_random_records[1:]).sort_index(level=0, axis=1) +metrics_selected_records_df['True Label'] = true_labels_to_explain # save faithfulness -faithfulness = clfs_metrics[['faithfulness']] -faithfulness.columns = faithfulness.columns.droplevel() -faithfulness.to_csv('../data/eplanation_results/lime_faithfulness.csv') +faithfulness_df = metrics_selected_records_df[['faithfulness']] +faithfulness_df.columns = faithfulness_df.columns.droplevel() +faithfulness_df.to_csv('../data/explanation_results/shap_faithfulness.csv') # save monotonity -monotonity = clfs_metrics[['monotonity']] -monotonity.columns = monotonity.columns.droplevel() -monotonity.to_csv('../data/eplanation_results/lime_monotonity.csv') +monotonity_df = metrics_selected_records_df[['monotonity']] +monotonity_df.columns = monotonity_df.columns.droplevel() +monotonity_df.to_csv('../data/explanation_results/shap_monotonity.csv') -clfs_metrics +metrics_selected_records_df # %% -positions_to_evaluate = np.arange(0,51) # TODO: decidere quanti prenderne e se prenderli a caso o con un criterio -clf_names = [DT] # TODO: da togliere quando li abbiamo tutti +random_records_to_explain_df = pd.read_csv('../data/explanation_results/random_records_to_explain.csv', index_col=0) +positions_to_explain = random_records_to_explain_df['positions'].to_list() +true_labels_to_explain = random_records_to_explain_df['true labels'].to_list() -clfs_metrics = {} +metrics_random_records = {} for clf_name in clf_names: - model = models[clf_name] + classifier = classifiers[clf_name] if clf_name in rb_clf_names: - samples = indicators_test_rb_df.iloc[positions_to_evaluate].values + feature_defaults = features_rb_default + instances = indicators_test_rb_df.iloc[positions_to_explain].values else: - samples = indicators_test_db_df.iloc[positions_to_evaluate].values + feature_defaults = features_db_default + instances = indicators_test_db_df.iloc[positions_to_explain].values - clfs_metrics[clf_name] = {} faithfulness = [] - monotonity = [] - for i in range(samples.shape[0]): - prediction = model.predict(samples[i].reshape(1,-1))[0] - feature_importances = shap_values[clf_name][prediction][positions_to_evaluate[i]] - if clf_name in rb_clf_names: - if prediction == 0: - feature_defaults = non_fatal_rb_default - else: - feature_defaults = fatal_rb_default - else: - if prediction == 0: - feature_defaults = non_fatal_db_default - else: - feature_defaults = fatal_db_default - sample_metric = evaluate_explanation(model, samples[i], feature_importances, feature_defaults) + for i in range(instances.shape[0]): + prediction = classifier.predict(instances[i].reshape(1,-1))[0] + feature_importances = shap_values[clf_name][prediction][positions_to_explain[i]] + sample_metric = evaluate_explanation(classifier, instances[i], feature_importances, feature_defaults[true_labels_to_explain[i]]) faithfulness.append(sample_metric['faithfulness']) - #monotonity.append(sample_metric['monotonity']) - clfs_metrics[clf_name]['mean faithfulness'] = np.nanmean(faithfulness) - clfs_metrics[clf_name]['std faithfulness'] = np.nanstd(faithfulness) - -pd.DataFrame(clfs_metrics) + + metrics_random_records[clf_name] = {} + metrics_random_records[clf_name]['mean faithfulness'] = np.nanmean(faithfulness_df) + metrics_random_records[clf_name]['std faithfulness'] = np.nanstd(faithfulness_df) + +metrics_random_records_df = pd.DataFrame(metrics_random_records) +metrics_random_records_df.to_csv('../data/explanation_results/shap_metrics_random_records.csv') +metrics_random_records_df