Positive vs. Negative Sentiment Classification
In this notebook, we demonstrate how to interpret a sentiment classification model using SHAP. The goal is to understand how individual words in a movie review influence the model’s prediction of positive or negative sentiment.
[1]:
import datasets
import numpy as np
import transformers
import shap
/Users/aribaa/Library/Python/3.9/lib/python/site-packages/urllib3/__init__.py:35: NotOpenSSLWarning: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 2.8.3'. See: https://github.com/urllib3/urllib3/issues/3020
warnings.warn(
/Users/aribaa/Library/Python/3.9/lib/python/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
from .autonotebook import tqdm as notebook_tqdm
Load the IMDB movie review dataset
[2]:
# Load IMDB dataset (movie reviews labeled as positive/negative)
dataset = datasets.load_dataset("imdb", split="test")
# shorten the strings to fit into the pipeline model
short_data = [v[:500] for v in dataset["text"][:20]]
Load and run a sentiment analysis pipeline
[3]:
# Load pretrained sentiment analysis model from HuggingFace
# Note: Model will be downloaded on first run
classifier = transformers.pipeline("sentiment-analysis", return_all_scores=True)
classifier(short_data[:2])
No model was supplied, defaulted to distilbert/distilbert-base-uncased-finetuned-sst-2-english and revision 714eb0f (https://huggingface.co/distilbert/distilbert-base-uncased-finetuned-sst-2-english).
Using a pipeline without specifying a model name and revision in production is not recommended.
Device set to use mps:0
/Users/aribaa/Library/Python/3.9/lib/python/site-packages/transformers/pipelines/text_classification.py:111: UserWarning: `return_all_scores` is now deprecated, if want a similar functionality use `top_k=None` instead of `return_all_scores=True` or `top_k=1` instead of `return_all_scores=False`.
warnings.warn(
[3]:
[[{'label': 'NEGATIVE', 'score': 0.07581914216279984},
{'label': 'POSITIVE', 'score': 0.924180805683136}],
[{'label': 'NEGATIVE', 'score': 0.01834261603653431},
{'label': 'POSITIVE', 'score': 0.9816573858261108}]]
Explain the sentiment analysis pipeline
[4]:
# Create SHAP explainer to compute word-level importance
explainer = shap.Explainer(classifier)
[5]:
# explain the predictions of the pipeline on the first two samples
shap_values = explainer(short_data[:2])
PartitionExplainer explainer: 3it [00:11, 11.25s/it]
[6]:
# Visualize contribution of each word to the prediction for the "POSITIVE" class
shap.plots.text(shap_values[:, :, "POSITIVE"])
[0]
inputs
I
love sci
-fi and
am willing to put up with a lot.
Sci-fi movies/TV are usually
underfunded,
under
-
appreciated
and
misunderstood.
I tried
to like this
,
I really
did
,
but
it is
to good
TV
sci
-fi
as Babylon
5 is
to Star Trek
(the original)
.
Silly prosthetics
,
cheap cardboard sets,
stilted dialogues,
CG that
doesn'
t
match the background
,
and
painfully one-dimensional
characters
cannot
be overcome with
a 'sci-fi' setting.
(
I
'
m sure
there are
those of
you
out
there
who
think
Babylon
5
is
good
sci
-
fi
[1]
inputs
Worth
the
entertainment
value of a rental
,
especially
if
you
like
action movies
.
This one
features the
usual car chases
,
fights with the great Van
Damme kick style,
shooting battles with the
40 shell load shotgun
,
and
even terrorist
style bombs
.
All of
this
is
entertaining
and
competently
handled
but
there is
nothing
that
really
blows
you away if
you'
ve seen
your share before
.
<br /><br />
The plot
is made
interesting
by
the inclusion
of a rabbit
,
which is clever but
hardly
profound
.
Many of
the c
Wrap the pipeline manually
SHAP explanations operate best in additive feature spaces. Since probabilities are not additive, we convert them into logits (log-odds). This ensures that SHAP values correctly sum up to the model output.
Create a TransformersPipeline wrapper
[7]:
pmodel = shap.models.TransformersPipeline(classifier, rescale_to_logits=False)
[8]:
pmodel(short_data[:2])
[8]:
array([[0.07581914, 0.92418081],
[0.01834262, 0.98165739]])
[9]:
pmodel = shap.models.TransformersPipeline(classifier, rescale_to_logits=True)
pmodel(short_data[:2])
[9]:
array([[-2.50055699, 2.50055625],
[-3.98001525, 3.98001536]])
[10]:
explainer2 = shap.Explainer(pmodel)
shap_values2 = explainer2(short_data[:2])
shap.plots.text(shap_values2[:, :, 1])
PartitionExplainer explainer: 3it [00:17, 17.59s/it]
[0]
inputs
I love sci
-fi and
am willing to put
up with a lot.
Sci-fi movies/TV are usually
underfunded,
under-
appreciated
and misunderstood.
I tried to like this
,
I really did,
but
it is to good
TV sci
-fi
as Babylon 5 is
to Star Trek
(the original).
Silly prosthetics,
cheap cardboard sets,
stilted dialogues,
CG that
doesn't
match the background
,
and
painfully one
-dimensional
characters cannot be overcome with
a 'sci-fi' setting.
(
I'
m sure
there are
those of
you out
there
who
think
Babylon
5
is
good
sci
-fi
[1]
inputs
Worth
the
entertainment
value of a rental
,
especially if
you like
action movies
.
This one features the usual car chases,
fights with the great Van
Damme kick style,
shooting battles with the
40 shell load shotgun,
and
even terrorist style bombs.
All of
this
is
entertaining
and
competently handled
but
there is
nothing
that
really blows
you away if
you've seen
your share before.
<br /><br />
The plot is made
interesting by
the inclusion
of a rabbit
,
which is clever
but
hardly profound
.
Many of the c
Pass a tokenizer as the masker object
[11]:
explainer2 = shap.Explainer(pmodel, classifier.tokenizer)
shap_values2 = explainer2(short_data[:2])
shap.plots.text(shap_values2[:, :, 1])
PartitionExplainer explainer: 3it [00:13, 13.83s/it]
[0]
inputs
I love sci
-fi and
am willing to put
up with a lot.
Sci-fi movies/TV are usually
underfunded,
under-
appreciated
and misunderstood.
I tried to like this
,
I really did,
but
it is to good
TV sci
-fi
as Babylon 5 is
to Star Trek
(the original).
Silly prosthetics,
cheap cardboard sets,
stilted dialogues,
CG that
doesn't
match the background
,
and
painfully one
-dimensional
characters cannot be overcome with
a 'sci-fi' setting.
(
I'
m sure
there are
those of
you out
there
who
think
Babylon
5
is
good
sci
-fi
[1]
inputs
Worth
the
entertainment
value of a rental
,
especially if
you like
action movies
.
This one features the usual car chases,
fights with the great Van
Damme kick style,
shooting battles with the
40 shell load shotgun,
and
even terrorist style bombs.
All of
this
is
entertaining
and
competently handled
but
there is
nothing
that
really blows
you away if
you've seen
your share before.
<br /><br />
The plot is made
interesting by
the inclusion
of a rabbit
,
which is clever
but
hardly profound
.
Many of the c
Build a Text masker explicitly
[12]:
masker = shap.maskers.Text(classifier.tokenizer)
explainer2 = shap.Explainer(pmodel, masker)
shap_values2 = explainer2(short_data[:2])
shap.plots.text(shap_values2[:, :, 1])
PartitionExplainer explainer: 3it [00:18, 18.80s/it]
[0]
inputs
I love sci
-fi and
am willing to put
up with a lot.
Sci-fi movies/TV are usually
underfunded,
under-
appreciated
and misunderstood.
I tried to like this
,
I really did,
but
it is to good
TV sci
-fi
as Babylon 5 is
to Star Trek
(the original).
Silly prosthetics,
cheap cardboard sets,
stilted dialogues,
CG that
doesn't
match the background
,
and
painfully one
-dimensional
characters cannot be overcome with
a 'sci-fi' setting.
(
I'
m sure
there are
those of
you out
there
who
think
Babylon
5
is
good
sci
-fi
[1]
inputs
Worth
the
entertainment
value of a rental
,
especially if
you like
action movies
.
This one features the usual car chases,
fights with the great Van
Damme kick style,
shooting battles with the
40 shell load shotgun,
and
even terrorist style bombs.
All of
this
is
entertaining
and
competently handled
but
there is
nothing
that
really blows
you away if
you've seen
your share before.
<br /><br />
The plot is made
interesting by
the inclusion
of a rabbit
,
which is clever
but
hardly profound
.
Many of the c
Explore how the Text masker works
[13]:
masker.shape("I like this movie.")
[13]:
(1, 7)
[14]:
model_args = masker(np.array([True, True, True, True, True, True, True]), "I like this movie.")
model_args
[14]:
(array(['I like this movie.'], dtype='<U18'),)
[15]:
pmodel(*model_args)
[15]:
array([[-5.16124821, 5.16124821]])
[16]:
model_args = masker(np.array([True, True, False, False, True, True, True]), "I like this movie.")
model_args
[16]:
(array(['I [MASK] [MASK]movie.'], dtype='<U21'),)
[17]:
pmodel(*model_args)
[17]:
array([[-0.60019428, 0.60019402]])
[18]:
masker2 = shap.maskers.Text(classifier.tokenizer, mask_token="...", collapse_mask_token=True)
[19]:
model_args2 = masker2(np.array([True, True, False, False, True, True, True]), "I like this movie.")
model_args2
[19]:
(array(['I ...movie.'], dtype='<U11'),)
[20]:
pmodel(*model_args2)
[20]:
array([[-4.21697383, 4.21697376]])
Plot summary statistics and bar charts
[21]:
# explain the predictions of the pipeline on the first two samples
shap_values = explainer(short_data[:20])
PartitionExplainer explainer: 21it [02:32, 8.02s/it]
[22]:
shap.plots.bar(shap_values[:, :, "POSITIVE"])
[23]:
shap.plots.bar(shap_values[:, :, "POSITIVE"].mean(0))
[24]:
shap.plots.bar(shap_values[:, :, "POSITIVE"].mean(0), order=shap.Explanation.argsort)
[ ]: