Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reference for the metrics #420

Open
vidushi27 opened this issue Jan 24, 2023 · 1 comment
Open

Reference for the metrics #420

vidushi27 opened this issue Jan 24, 2023 · 1 comment

Comments

@vidushi27
Copy link

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

@Francesco-Marini
Copy link
Contributor

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:

Unknown

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants