0
$\begingroup$

I am building an NLP model to predict language type (C/C++/C#/Python...) for a given code. Now I need to provide an explanation for my model prediction. For example the following user_input is written in Java and the model is predicting that, but I need to show the users why it predicts so.

I am using shap_values to achieve this. For some reason, the following code results in an error (I have added the error at the bottom). Please advise how can I get shap_values and plots for my model predictions.

Link to data: https://sharetext.me/bd68ryvzi0

Code:

import pandas as pd

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline

# Loading Data:
DATA_PATH = r"sample.csv"

data = pd.read_csv(DATA_PATH, dtype='object')
data = data.convert_dtypes()
data = data.dropna()
data = data.drop_duplicates()

# Train/Test split
X, y = data.content, data.language
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)

# Model params to match:
# 1. Variable and module names, words in a string, keywords: [A-Za-z_]\w*\b
# 2. Operators: [!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+
# 3. Tabs, spaces and Brackets: [ \t\(\),;\{\}\[\]`"']
# with the following regex:
token_pattern = r"""(\b[A-Za-z_]\w*\b|[!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+|[ \t\(\),;\{\}\[\]`"'])"""


def preprocess(x):
 """ Clean up single-character variable names or ones constituted of a sequence of the same character """
 return pd.Series(x).replace(r'\b([A-Za-z])\1+\b', '', regex=True)\
 .replace(r'\b[A-Za-z]\b', '', regex=True)


# Pipe steps:
# Define a transformer:
transformer = FunctionTransformer(preprocess)
# Perform TF-IDF vectorization with our token pattern:
vectorizer = TfidfVectorizer(token_pattern=token_pattern, max_features=3000)
# Create Random Forest Classifier:
clf = RandomForestClassifier(n_jobs=4)

pipe_RF = Pipeline([
 ('preprocessing', transformer),
 ('vectorizer', vectorizer),
 ('clf', clf)]
)

# Setting best params (after performing GridSearchCV)
best_params = {
 'clf__criterion': 'gini',
 'clf__max_features': 'sqrt',
 'clf__min_samples_split': 3,
 'clf__n_estimators': 300
}

pipe_RF.set_params(**best_params)

# Fitting
pipe_RF.fit(X_train, y_train)

# Evaluation
print(f'Accuracy: {pipe_RF.score(X_test, y_test)}')



user_input = [""" public class Fibonacci {

public static void main(String[] args) {

int n = 10;

System.out.println(fib(n));

}

public static int fib(int n) {

if (n <= 1) {

return n;

}

return fib(n - 1) + fib(n - 2);

}

} """]


import shap

shap.initjs()
explainer = shap.TreeExplainer(pipe_RF.named_steps['clf'])
observation = pipe_RF[:-1].transform(user_input).toarray()
shap_values = explainer.shap_values(observation)

Load the data and run it to get the following error:

ExplainerError: Additivity check failed in TreeExplainer! Please ensure the data matrix you passed to the explainer is the same shape that the model was trained on. If your data shape is correct then please report this on GitHub. Consider retrying with the feature_perturbation='interventional' option. This check failed because for one of the samples the sum of the SHAP values was 46609069202029743624438153216.000000, while the model output was 0.004444. If this difference is acceptable you can set check_additivity=False to disable this check.

$\endgroup$

1 Answer 1

0
$\begingroup$

I have figured out how to fix it, posting to help others :)

import pandas as pd

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline

import re
from lime.lime_text import LimeTextExplainer

from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

# Loading GitHub Repos data containing code and comments from 2.8 million GitHub repositories:
DATA_PATH = r"/Users/stevesolun/Steves_Files/Data/github_repos_data.csv"

data = pd.read_csv(DATA_PATH, dtype='object')
data = data.convert_dtypes()
data = data.dropna()
data = data.drop_duplicates()

# Train/Test split
X, y = data.content, data.language
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y)

# Model params to match:
# 1. Variable and module names, words in a string, keywords: [A-Za-z_]\w*\b
# 2. Operators: [!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+
# 3. Tabs, spaces and Brackets: [ \t\(\),;\{\}\[\]`"']
# with the following regex:
token_pattern = r"""(\b[A-Za-z_]\w*\b|[!\#\\\$%\&\*\+:\-\./<=>\?@\\\^_\|\~]+|[ \t\(\),;\{\}\[\]`"'])"""


def preprocess(x):
    """ Clean up single-character variable names or ones constituted of a sequence of the same character """
    return pd.Series(x).replace(r'\b([A-Za-z])\1+\b', '', regex=True)\
        .replace(r'\b[A-Za-z]\b', '', regex=True)


# Pipe steps:
# Define a transformer:
transformer = FunctionTransformer(preprocess)
# Perform TF-IDF vectorization with our token pattern:
vectorizer = TfidfVectorizer(token_pattern=token_pattern, max_features=1500)
# Create Random Forest Classifier:
clf = RandomForestClassifier(n_jobs=-1)

pipe_RF = Pipeline([
     ('preprocessing', transformer),
     ('vectorizer', vectorizer)]
    )

# Setting best params (after performing GridSearchCV)
best_params = {
    'criterion': 'gini',
    'max_features': 'sqrt',
    'min_samples_split': 3,
    'n_estimators': 300
}

clf.set_params(**best_params)
# Here I am preprocessing the data:
X_train = pipe_RF.fit_transform(X_train).toarray()
X_test = pipe_RF.transform(X_test).toarray()

# Fitting the model outside the pipe - feel free to show if possible to do it inside the pipe + fit_transform the train and test sets.
clf.fit(X_train, y_train)

# Evaluation
print(f'Accuracy: {clf.score(X_test, y_test)}')


user_input = """ def fib(n):
                    a,b = 0,1
                    while a < n:
                        print(a, end=' ')
                    a,b = b, a+b
                    print()
                    fib(1000)

   """
clf.predict(pipe_RF.transform(user_input))[0]

prediction = clf.predict(pipe_RF.transform(user_input))[0]
predicted_class_idx = list(clf.classes_).index(prediction)

import shap

shap.initjs()
explainer = shap.TreeExplainer(clf, X_train)
observation = pipe_RF.transform(user_input).toarray()
shap_values = explainer.shap_values(observation)


shap.force_plot(explainer.expected_value[predicted_class_idx], shap_values[predicted_class_idx], feature_names=vectorizer.get_feature_names_out())

enter image description here

$\endgroup$
1
  • $\begingroup$ This answer could be better if it explained what you changed and why. $\endgroup$
    – Ben Reiniger
    Commented Nov 25 at 17:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.