Skip to content
This repository was archived by the owner on Jun 11, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
319 changes: 319 additions & 0 deletions a_ddm_feat_importance.ipynb

Large diffs are not rendered by default.

190 changes: 164 additions & 26 deletions base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import os
import pathlib
import pickle
import random
import sys
from collections import OrderedDict, defaultdict
from typing import List, Tuple, Union

import lime
import lime.lime_tabular
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
Expand All @@ -14,8 +18,6 @@
from sklearn.metrics import auc, roc_curve
from sklearn.preprocessing import StandardScaler

from collections import OrderedDict

from loaders import CsvReader

matplotlib.rcParams["figure.figsize"] = [12, 10]
Expand Down Expand Up @@ -219,14 +221,20 @@ def predict_sequentially_all(self, X, label_col_names: List[str] = None):
if label_col_names is None:
# If None provided, and None stored in self.labels, we ask user to provide as input
# - Currently needed to match outputs to inputs when running the model forward -
raise ValueError("Please provide a list of predicted output labels ('label_col_names')")

raise ValueError(
"Please provide a list of predicted output labels ('label_col_names')"
)

# prepare features & a list of predictions that are feats too (often all)
feats = self.features
preds_that_are_feats = [f_name for f_name in feats if f_name in label_col_names]
preds_that_are_feats = [
f_name for f_name in feats if f_name in label_col_names
]
# initialize feat_dict to first row & pred_dict to match first row too
feat_dict = OrderedDict(list(zip(feats, X[0])))
pred_dict = dict([(k,v) for (k,v) in feat_dict.items() if k in preds_that_are_feats])
pred_dict = dict(
[(k, v) for (k, v) in feat_dict.items() if k in preds_that_are_feats]
)

# sequentially iterate retriving next prediction based on previous prediction
preds = []
Expand All @@ -242,54 +250,57 @@ def predict_sequentially_all(self, X, label_col_names: List[str] = None):
# update prediction dictionary (for next iteration)
pred_dict = OrderedDict(list(zip(label_col_names, pred.tolist()[0])))

preds = np.array(preds) #.transpose()
preds = np.array(preds) # .transpose()

if self.scale_data:
preds = self.yscalar.inverse_transform(preds)

#preds_df = pd.DataFrame(preds)
#preds_df.columns = label_col_names
# preds_df = pd.DataFrame(preds)
# preds_df.columns = label_col_names

return preds #preds_df
return preds # preds_df


def predict_sequentially(self, X, label_col_names: List[str] = None, it_per_episode: int = None):
def predict_sequentially(
self, X, label_col_names: List[str] = None, it_per_episode: int = None
):

if not self.model:
raise ValueError("Please build or load the model first")
else:
if self.scale_data:
X = self.xscalar.transform(X)

if label_col_names is None:
label_col_names = self.labels
if label_col_names is None:
# If None provided, and None stored in self.labels, we ask user to provide as input
# - Currently needed to match outputs to inputs when running the model forward -
raise ValueError("Please provide a list of predicted output labels ('label_col_names')")
raise ValueError(
"Please provide a list of predicted output labels ('label_col_names')"
)

# initialize predictions
preds = []

if not it_per_episode:
it_per_episode = np.shape(X)[0]

num_of_episodes = int(np.shape(X)[0]/it_per_episode)
num_of_episodes = int(np.shape(X)[0] / it_per_episode)

# iterate per as many episodes as selected
for i in range(num_of_episodes):

X_aux = X[i*it_per_episode:(i+1)*it_per_episode]
X_aux = X[i * it_per_episode : (i + 1) * it_per_episode]

preds_aux = self.predict_sequentially_all(X_aux, label_col_names)
preds.extend(preds_aux)

preds = np.array(preds)

#preds_df = pd.DataFrame(preds)
#preds_df.columns = label_col_names
# preds_df = pd.DataFrame(preds)
# preds_df.columns = label_col_names

return preds #preds_df
return preds # preds_df

def predict_halt_classifier(self, X):

Expand Down Expand Up @@ -385,22 +396,28 @@ def evaluate(
return results_df

def evaluate_sequentially(
self, X_test: np.ndarray, y_test: np.ndarray, metric, marginal: bool = False, it_per_episode = 100
self,
X_test: np.ndarray,
y_test: np.ndarray,
metric,
marginal: bool = False,
it_per_episode=100,
):

if not self.model:
raise Exception("No model found, please run fit first")
else:

if not marginal:
y_hat = self.predict_sequentially(X_test, it_per_episode = it_per_episode)
y_hat = self.predict_sequentially(X_test, it_per_episode=it_per_episode)
y_hat_len = np.shape(y_hat)[0]
y_test = y_test[:y_hat_len]
return metric(y_test, y_hat)
else:
results_df = self.evaluate_margins_sequentially(X_test, y_test, metric, False, it_per_episode=it_per_episode)
results_df = self.evaluate_margins_sequentially(
X_test, y_test, metric, False, it_per_episode=it_per_episode
)
return results_df


def evaluate_margins(
self, X_test: np.ndarray, y_test: np.ndarray, metric, verbose: bool = False
Expand All @@ -418,11 +435,16 @@ def evaluate_margins(
return pd.DataFrame(results.items(), columns=["var", "score"])

def evaluate_margins_sequentially(
self, X_test: np.ndarray, y_test: np.ndarray, metric, verbose: bool = False, it_per_episode: int = 100
self,
X_test: np.ndarray,
y_test: np.ndarray,
metric,
verbose: bool = False,
it_per_episode: int = 100,
):

# Extract prediction and remove any tail reminder from int(len(X_test)/it_per_episode)
y_pred = self.predict_sequentially(X_test, it_per_episode = it_per_episode)
y_pred = self.predict_sequentially(X_test, it_per_episode=it_per_episode)
y_pred_len = np.shape(y_pred)[0]
y_test = y_test[:y_pred_len]

Expand All @@ -436,6 +458,122 @@ def evaluate_margins_sequentially(
idx += 1
return pd.DataFrame(results.items(), columns=["var", "score"])

def get_feat_importance(self, X=None, num_times=200):

if not self.model:
raise Exception("No model found, please run fit first")

if X is None:
raise Exception(
"Training data for feature importance has not been provided, but it should"
)

if not self.separate_models:
raise NotImplementedError(
"Lime doesn't support regression analysis with multiple outputs"
)

feats = self.features
outputs = self.labels

# Initialize feature importance final dictionary
feat_importance_d = dict()

# Identify as categorical all inputs with 10 or less unique values
categorical_features = np.argwhere(
np.array([len(set(X[:, i])) for i in range(X.shape[1])]) <= 10
).flatten()

# Prepare lime predictor
explainer = lime.lime_tabular.LimeTabularExplainer(
X,
feature_names=feats,
categorical_features=categorical_features,
verbose=False,
mode="regression",
)

aux_X = X.copy()
random.shuffle(aux_X)

for output, inner_model in zip(outputs, self.models):

feats_coef_d = defaultdict(list)

for i in range(100):
# Get explainer for single instance
exp = explainer.explain_instance(
aux_X[i], inner_model.predict, num_features=7
)
# exp_data is a list of tuples of type (range_str, importance) --> e.g.: ('state_x_position > 0.12', 0.671470753024958)
exp_data = exp.as_list()

# Iterate extracting feature importance (order is given as per feat importance)
aux_idx_list = list(range(len(feats)))
feat_coefs = []
for i, f in enumerate(feats):

for idx in aux_idx_list:
feat_range_str, feat_coef = exp_data[idx]

# Extract state name & append to list
if f in feat_range_str:
feat_coefs.append(abs(feat_coef))
break

# Remove state before running over again
aux_idx_list.remove(idx)

# Normalize vector of importances
coef_sum = sum(feat_coefs)
feat_coefs = [coef / coef_sum for coef in feat_coefs]

# Append to list
for f, c in zip(feats, feat_coefs):
feats_coef_d[f].append(c)

# Get mean across all importances per example
feat_importance_d[output] = [np.mean(feats_coef_d[f]) for f in feats]

return feat_importance_d

def plot_feature_importance(
self, feature_data=None, in_feats_dim=None, out_labels=None, total_width=1.0
):

if not self.model:
raise Exception("No model found, please run fit first")

if feature_data is None:
feature_data = self.get_feat_importance()
if in_feats_dim is None:
in_feats_dim = self.input_dim
if out_labels is None:
out_labels = self.features

fig, ax = plt.subplots(figsize=(8, 7))

colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]
n_bars = len(feature_data) + 1 # plus 1 to add a space in between predictors
bar_width = total_width / n_bars # width of single bar
bars = []
for i, (name, values) in enumerate(feature_data.items()):
x_offset = (i - n_bars / 2) * bar_width + bar_width / 2
for x, y in enumerate(values):
bar = ax.bar(
x + x_offset, y, width=bar_width, color=colors[i % len(colors)]
)
bars.append(bar[0])

ax.legend(bars, feature_data.keys())
plt.title("Feature Importance / Feature Multipliers", fontsize=18)
plt.xlabel("Feature Column Names", fontsize=18)
plt.ylabel("Feature Importance", fontsize=18)

plt.xticks(ticks=range(in_feats_dim), labels=out_labels)
ax.tick_params(labelrotation=90)
plt.show()

def plot_roc_auc(self, halt_x: np.ndarray, halt_y: np.ndarray):

test_halt_preds = self.predict_halt_classifier(halt_x)
Expand Down
73 changes: 37 additions & 36 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
name: ddm
channels:
- pytorch
- defaults
- pytorch
- defaults
dependencies:
- python=3.7.7
- pip=19.1.1
- pytorch=1.7.0
- torchvision=0.8
- cryptography=3.1.1
- pip:
- ray==1.0.0
- dataclasses==0.6
- black==19.10b0
- joblib==0.17.0
- keras==2.4.3
- scikit-learn==0.23.2
- scikit-optimize==0.8.1
- skorch==0.9.0
- tensorboard==2.3.0
- tensorflow==2.3.1
- tensorflow-estimator==2.3.0
- microsoft-bonsai-api==0.1.2
- bonsai-cli==1.0.4
- pandas==1.1.4
- pyyaml==5.3.1
- pytest==6.2.1
- scipy==1.5.4
- h5py==2.10.0
- nbgrader==0.6.1
- tune-sklearn==0.1.0
- hyperopt==0.2.5
- lightgbm==3.1.1
- xgboost==1.3.0.post0
- python-dotenv==0.15.0
- hydra-core==1.0.5
- natsort==7.1.0
- seaborn==0.11.1
- rich==9.13.0
- python=3.7.7
- pip=19.1.1
- pytorch=1.7.0
- torchvision=0.8
- cryptography=3.1.1
- pip:
- ray==1.0.0
- dataclasses==0.6
- black==19.10b0
- joblib==0.17.0
- keras==2.4.3
- scikit-learn==0.23.2
- scikit-optimize==0.8.1
- skorch==0.9.0
- tensorboard==2.3.0
- tensorflow==2.3.1
- tensorflow-estimator==2.3.0
- microsoft-bonsai-api==0.1.2
- bonsai-cli==1.0.4
- pandas==1.1.4
- pyyaml==5.3.1
- pytest==6.2.1
- scipy==1.5.4
- h5py==2.10.0
- nbgrader==0.6.1
- tune-sklearn==0.1.0
- hyperopt==0.2.5
- lightgbm==3.1.1
- xgboost==1.3.0.post0
- python-dotenv==0.15.0
- hydra-core==1.0.5
- natsort==7.1.0
- seaborn==0.11.1
- lime==0.2.0.1
- rich==9.13.0
Loading