Search code examples
pythonmachine-learningbert-language-modeltext-classificationshap

Shap value for binary classification using Pre-Train Bert: How to extract summary graph?


I used pre-train bert model for binary classification. After training my model with my small data, I wanted to extract summary graph like this the graph I want. However, I want to replace these important features with words.

However, I am not sure everything is okay because the shape of shap_value is only two dimensional. Actually, this is sensible. Nevertheless, I did not get the graph because I encountered two problems if I use this code:

shap.summary_plot(shap_values[:,:10],feature_names=feature_importance['features'].tolist(),features=comments_text)`

Problem is too unsensible: If I change shap_values[:,:10] with shap_values or shap_values[0] or shap_values.values vb. I always come across

516: assert len(shap_values.shape) != 1, "Summary plots need a matrix of 
shap_values, not a vector." ==> AssertionError: Summary plots need a matrix of 
shap_values, not a vector.

(fist problem)

By the way, my shap_value consist of 10 input(shape_value.shape). If I choose for max value a range from 1 to 147 everything fine for drawing the graph. However,in this time, the graph is not suitable: My graph consist of only blue dot(-second problem-). Like this only blue not.

Note: shap_values[:,:10] if the number (10) change different number, the graph show diffent word however the total number of the graph same (max 20). Only some words order can be changing.

Minimal reproducible example:

import nlp
import numpy as np
import pandas as pd
import scipy as sp
import torch
import transformers
import torch
import shap

# load a BERT sentiment analysis model
tokenizer = transformers.DistilBertTokenizerFast.from_pretrained(
    "distilbert-base-uncased"
)
model = transformers.DistilBertForSequenceClassification.from_pretrained(
    "distilbert-base-uncased-finetuned-sst-2-english"
).cuda()


if torch.cuda.is_available():
    device = torch.device("cuda")
    print('We will use the GPU:', torch.cuda.get_device_name(0))

else:
    print('No GPU available, using the CPU instead.')
    device = torch.device("cpu")

def f(x):
    # Encode the batch of sentenc
    inputs = tokenizer.batch_encode_plus(x.tolist(), max_length=450,add_special_tokens=True, return_attention_mask=True,padding='max_length',truncation=True,return_tensors='pt')

    # Send the tensors to the same device as the model
    input_ids = inputs['input_ids'].to(device)
    attention_masks = inputs['attention_mask'].to(device)
    # Predict
    with torch.no_grad():
        outputs = model(input_ids, attention_mask=attention_masks)[0].detach().cpu().numpy()
    scores = (np.exp(outputs).T / np.exp(outputs).sum(-1)).T
    val = sp.special.logit(scores[:, 1])  # use one vs rest logit units
    return val
# Build an explainer using a token masker
explainer = shap.Explainer(f, tokenizer )

imdb_train = nlp.load_dataset("imdb")["train"]
shap_values = explainer(imdb_train[:10], fixed_context=1, batch_size=16)
cohorts = {"": shap_values}
cohort_labels = list(cohorts.keys())
cohort_exps = list(cohorts.values())
for i in range(len(cohort_exps)):
    if len(cohort_exps[i].shape) == 2:
        cohort_exps[i] = cohort_exps[i].abs.mean(0)
features = cohort_exps[0].data
feature_names = cohort_exps[0].feature_names
#values = np.array([cohort_exps[i].values for i in range(len(cohort_exps))], dtype=object)
values = np.array([cohort_exps[i].values for i in range(len(cohort_exps))])
feature_importance = pd.DataFrame(list(zip(feature_names, sum(values))), columns=['features', 'importance'])
feature_importance.sort_values(by=['importance'], ascending=False, inplace=True)
shap.summary_plot(shap_values[:,:10],feature_names=feature_importance['features'].tolist(),features=imdb_train['text'][10:20],show=False)

The above code produce the same result. I spent approximately 200 computer units and I did not succeed it :(. How can I do?


Solution

  • Will you try:

    sv = np.array([arr[:100] for arr in shap_values.values])
    data = np.array([arr[:100] for arr in shap_values.data])
    shap.summary_plot(sv, data, feature_names=feature_importance['features'].tolist())
    

    I've got a grey plot. This is because your data is non-numeric.