shapash
shapash copied to clipboard
Reference for the metrics
Dear contributors,
Please direct me toward the research paper/reference for the metrics "Compacity" used here. I would like to understand it's background in detail. I've gone through the medium article mentioned.
Thank you so much.
Best regards, Vidushi
Hello @vidushi27,
There is no research paper associated to this metric as it has been created ad hoc. However, I can give you some additional explanation of how it works. We also wrote a notebook tutorial here.
The idea behind the compacity metric is to determine how well a small set of features can approximate the model (if you rely on too many features for the explanation, it will be hard to get a sense of how the model actually works, so we usually prefer to deal with fewer). Thus, two experiences are proposed in the compacity metric to help quantify how model predictions can be explained by smaller sets of features.
Input: You select an explainability method of your choice and you calculate the contributions of each feature on all data points. You end up having a matrix of contributions of the same size as the original data.
Some metrics are calculated (you can find the related Python code here) and the following graphs are built:
Left graph: For a given model approximation level, the graph is used to determine the minimum number of required features to reach that approximation (i.e. approximation level is fixed, number of features varies)
-
We decide how well we want to approximate the model (here 90%)
-
For each instance, keep adding the top features (according to its contribution) until we reached the desired approximation
-
The approximation (expressed as a distance) is defined below :
- For regression:
distance = \frac{|output_{allFeatures} - output_{currentFeatures}|}{|output_{allFeatures}|}
- For classification:
distance = |output_{allFeatures} - output_{currentFeatures}|
-
Aggregate results across all data points to be able to provide statistics about the entire dataset (what appears in the bubble when your mouse is over the graph)
Implementation is detailed below :
def get_min_nb_features(selection, contributions, mode, distance):
assert 0 <= distance <= 1
if mode == "classification" and len(contributions) == 2:
contributions = contributions[1]
contributions = contributions.loc[selection].values
features_needed = []
# For each instance, add features one by one (ordered by SHAP) until we get close enough
for i in range(contributions.shape[0]):
ids = np.flip(np.argsort(np.abs(contributions[i, :])))
output_value = np.sum(contributions[i, :])
score = 0
for j, idx in enumerate(ids):
# j : number of features needed
# idx : positions of the j top shap values
score += contributions[i, idx]
# CLOSE_ENOUGH
if mode == "regression":
if abs(score - output_value) < distance * abs(output_value):
break
elif mode == "classification":
if abs(score - output_value) < distance:
break
features_needed.append(j + 1)
return features_needed
Right graph: It is the opposite. This time we keep the number of selected features fixed, and we want to determine how close we get to the model. The iteration process is very similar :
- We decide how many features we want to keep (here 5)
- For each instance, add the top 5 features (according to their contributions) and look at the approximation level reached
- Aggregate the results across the dataset to provide statistics
Implementation is detailed below:
def get_distance(selection, contributions, mode, nb_features):
if mode == "classification" and len(contributions) == 2:
contributions = contributions[1]
assert nb_features <= contributions.shape[1]
contributions = contributions.loc[selection].values
top_features = np.array([sorted(row, key=abs, reverse=True) for row in contributions])[:, :nb_features]
output_top_features = np.sum(top_features[:, :], axis=1)
output_all_features = np.sum(contributions[:, :], axis=1)
if mode == "regression":
distance = abs(output_top_features - output_all_features) / abs(output_all_features)
elif mode == "classification":
distance = abs(output_top_features - output_all_features)
return
Hope this helps