How to use SHAP with a linear SVC model from sklearn using Pipeline?
Asked Answered
M

1

9

I am doing text classification using a linear SVC model from sklearn. Now I want to visualize which words/tokens have the highest impact on the classification decision by using SHAP (https://github.com/slundberg/shap).

Right now this does not work because I am getting an error that seems to originate from the vectorizer step in the pipeline I have defined - whats wrong here?

Is my general approach on how to use SHAP in this case correct?

x_Train, x_Test, y_Train, y_Test = train_test_split(df_all['PDFText'], df_all['class'], test_size = 0.2, random_state = 1234)

pipeline = Pipeline([
    (
        'tfidv',
        TfidfVectorizer(
            ngram_range=(1,3), 
            analyzer='word',
            strip_accents = ascii,
            use_idf = True,
            sublinear_tf=True, 
            max_features=6000, 
            min_df=2, 
            max_df=1.0
        )
    ),
    (
        'lin_svc',
        svm.SVC(
            C=1.0,
            probability=True,
            kernel='linear'
        )
    )
])

pipeline.fit(x_Train, y_Train)

shap.initjs()

explainer = shap.KernelExplainer(pipeline.predict_proba, x_Train)
shap_values = explainer.shap_values(x_Test, nsamples=100)

shap.force_plot(explainer.expected_value[0], shap_values[0][0,:], x_Test.iloc[0,:])

This is the error message I get:

Provided model function fails when applied to the provided data set.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-81-4bca63616b3b> in <module>
      3 
      4 # use Kernel SHAP to explain test set predictions
----> 5 explainer = shap.KernelExplainer(pipeline.predict_proba, x_Train)
      6 shap_values = explainer.shap_values(x_Test, nsamples=100)
      7 

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\shap\explainers\kernel.py in __init__(self, model, data, link, **kwargs)
     95         self.keep_index_ordered = kwargs.get("keep_index_ordered", False)
     96         self.data = convert_to_data(data, keep_index=self.keep_index)
---> 97         model_null = match_model_to_data(self.model, self.data)
     98 
     99         # enforce our current input type limitations

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\shap\common.py in match_model_to_data(model, data)
     80             out_val = model.f(data.convert_to_df())
     81         else:
---> 82             out_val = model.f(data.data)
     83     except:
     84         print("Provided model function fails when applied to the provided data set.")

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\utils\metaestimators.py in <lambda>(*args, **kwargs)
    116 
    117         # lambda, but not partial, allows help() to work with update_wrapper
--> 118         out = lambda *args, **kwargs: self.fn(obj, *args, **kwargs)
    119         # update the docstring of the returned function
    120         update_wrapper(out, self.fn)

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\pipeline.py in predict_proba(self, X)
    379         for name, transform in self.steps[:-1]:
    380             if transform is not None:
--> 381                 Xt = transform.transform(Xt)
    382         return self.steps[-1][-1].predict_proba(Xt)
    383 

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\feature_extraction\text.py in transform(self, raw_documents, copy)
   1631         check_is_fitted(self, '_tfidf', 'The tfidf vector is not fitted')
   1632 
-> 1633         X = super(TfidfVectorizer, self).transform(raw_documents)
   1634         return self._tfidf.transform(X, copy=False)

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\feature_extraction\text.py in transform(self, raw_documents)
   1084 
   1085         # use the same matrix-building strategy as fit_transform
-> 1086         _, X = self._count_vocab(raw_documents, fixed_vocab=True)
   1087         if self.binary:
   1088             X.data.fill(1)

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\feature_extraction\text.py in _count_vocab(self, raw_documents, fixed_vocab)
    940         for doc in raw_documents:
    941             feature_counter = {}
--> 942             for feature in analyze(doc):
    943                 try:
    944                     feature_idx = vocabulary[feature]

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\feature_extraction\text.py in <lambda>(doc)
    326                                                tokenize)
    327             return lambda doc: self._word_ngrams(
--> 328                 tokenize(preprocess(self.decode(doc))), stop_words)
    329 
    330         else:

c:\users\s.p\appdata\local\programs\python\python37\lib\site-packages\sklearn\feature_extraction\text.py in <lambda>(x)
    254 
    255         if self.lowercase:
--> 256             return lambda x: strip_accents(x.lower())
    257         else:
    258             return strip_accents

AttributeError: 'numpy.ndarray' object has no attribute 'lower'
Mcelroy answered 26/4, 2019 at 12:39 Comment(0)
T
8

KernelExplainer expects to receive a classification model as the first argument. Please check the use of Pipeline with Shap following the link.

In your case, you can use the Pipeline as follows:

x_Train = pipeline.named_steps['tfidv'].fit_transform(x_Train)
explainer = shap.KernelExplainer(pipeline.named_steps['lin_svc'].predict_proba, x_Train)
Tejeda answered 13/4, 2020 at 13:39 Comment(1)
Is it necessary to call .fit_transform instead of .transform if .fit was already called on the pipeline object?Quick

© 2022 - 2024 — McMap. All rights reserved.