Abrir en Google Colab Descargar notebook

Explicaciones para NLP utilizando SHAP

Introducción

La idea detrás de los valores de Shapley (Shapley, Lloyd S 1953) es la siguiente: dado un conjunto de predictores, encontrar la contribución marginal de cada predictor con respecto a la predicción general. ¿cuál es la predicción general? Es el valor esperado del modelo (EV). Piense en ello como la línea de base del modelo. Entonces, la contribución marginal indica en que medida cada predictor obliga a la predicción a alejarse de esa línea de base.

Para una introducción más detallada puede ver la entrada del blog: Model interpretability — Making your model confesses: SHAP

¿Como funciona?

La forma en que los valores de Sharpley calculan la contribución marginal es calculando el valor predicho con y sin el valor de la característica que se está considerando actualmente y tomando la diferencia para obtener la contribución marginal. Finalmente, el valor de Sharpley se calcula promediando la contribución marginal del valor de la característica en todos los subconjuntos de características posibles (llamados coaliciones) dentro del conjunto de características en el que participa la característica.

Este algorimo se encuentra implementado en la libraria Shap. Para mas información sobre esta librería visite: https://shap.readthedocs.io/en/latest/index.html

Para ejecutar este notebook

[1]:
import warnings
warnings.filterwarnings('ignore')

Para ejecutar este notebook, instale las siguientes librerias:

[2]:
!pip install transformers --quiet
!pip install shap --quiet
     |████████████████████████████████| 4.9 MB 5.2 MB/s
     |████████████████████████████████| 163 kB 36.1 MB/s
     |████████████████████████████████| 6.6 MB 36.2 MB/s
     |████████████████████████████████| 569 kB 5.0 MB/s

Cargamos el conjunto de datos con el que se entrenó el modelo en caso de necesitarlo

[3]:
!wget https://raw.githubusercontent.com/santiagxf/M72109/master/NLP/Datasets/mascorpus/tweets_marketing.csv \
    --quiet --no-clobber --directory-prefix ./Datasets/mascorpus/
[4]:
import pandas as pd

tweets = pd.read_csv('Datasets/mascorpus/tweets_marketing.csv')

Cargando un modelo de NLP

Recordemos que nuestro modelo predice los sectores a los que pertenecería el tweet, siendo ellos:

[5]:
target_names = {0:'ALIMENTACION', 1:'AUTOMOCION', 2:'BANCA', 3:'BEBIDAS', 4:'DEPORTES', 5:'RETAIL', 6:'TELCO'}

Cargaremos el modelo que fue descargado anteriornmente utilizando la librería de transformers. Note que cargamos tanto el tokenizer como el modelo propiamente dicho.

Nota: Utilizaremos un modelo entrenado para resolver el problema de tweets que venimos viendo en este curso. Este modelo fue publicado en el repositorio de HuggingFace bajo el nombre fce-m72109/mascorpus-bert-classifier.

[6]:
import transformers
from transformers import AutoTokenizer, AutoModelForSequenceClassification

model_name = "fce-m72109/mascorpus-bert-classifier"

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name,
                                                           id2label=target_names,
                                                           label2id={v:k for k,v in target_names.items()})

Tip: Agregar los parameteros id2label y label2id permite que el modelo pueda conocer los verdaderos valores de las clases que predice.

Construimos un pipeline con nuestro modelo y tokenizer

[7]:
pred = transformers.pipeline("text-classification", model=model, tokenizer=tokenizer, device=-1, top_k=1)

Generando explicaciones con SHAP

[8]:
sample = ["Nos estafaron en carrefour. No vuelvo a comprar alli jamas"]
[9]:
import shap

explainer = shap.Explainer(pred)
shap_values = explainer(sample)
Partition explainer: 2it [00:31, 31.69s/it]

Visualizando las explicaciones

[10]:
shap.plots.text(shap_values)


[0]
outputs
ALIMENTACION
AUTOMOCION
BANCA
BEBIDAS
DEPORTES
RETAIL
TELCO


0.40.200.60.800base value00fALIMENTACION(inputs)0.034 . 0.028 fo 0.023 ron 0.015 vuelvo 0.008 estafa 0.001 ur -0.063 carre -0.031 en -0.015 No
inputs
0.0
0.0
Nos
0.008
estafa
0.023
ron
-0.031
en
-0.063
carre
0.028
fo
0.001
ur
0.034
.
-0.015
No
0.015
vuelvo
0.0
a
0.0
comprar
0.0
alli
0.0
jama
0.0
s
0.0

Podemos analizar el impacto de una sola clase:

[11]:
shap.plots.text(shap_values[:, :, "ALIMENTACION"])


[0]
0-0.04-0.080.040.0800base value00fALIMENTACION(inputs)0.034 . 0.028 fo 0.023 ron 0.015 vuelvo 0.008 estafa 0.001 ur -0.063 carre -0.031 en -0.015 No
inputs
0.0
0.0
Nos
0.008
estafa
0.023
ron
-0.031
en
-0.063
carre
0.028
fo
0.001
ur
0.034
.
-0.015
No
0.015
vuelvo
0.0
a
0.0
comprar
0.0
alli
0.0
jama
0.0
s
0.0

Visualizando las palabras que tienen el mayor impacto en una clase determinada:

[12]:
shap.plots.bar(shap_values[:,:,"ALIMENTACION"][0])
../../_images/nlp_neural_nlp_shap_27_0.png

En los ejemplos anteriores, explicamos la salida directa del objeto pipline, que son las probabilidades de clase. A veces tiene más sentido trabajar en un espacio de probabilidades logarítmicas donde es natural sumar y restar efectos (la suma y la resta corresponden a la suma o resta de bits de información de evidencia).

[13]:
logit_explainer = shap.Explainer(shap.models.TransformersPipeline(pred, rescale_to_logits=True))

logit_shap_values = logit_explainer(sample)
shap.plots.text(logit_shap_values)
Partition explainer: 2it [00:26, 26.26s/it]


[0]
outputs
ALIMENTACION
AUTOMOCION
BANCA
BEBIDAS
DEPORTES
RETAIL
TELCO


000000000000000000000base value-5.55112e-17-5.55112e-17fALIMENTACION(inputs)0.199 carre 0.138 en 0.074 No 0.005 ur -0.125 . -0.107 ron -0.078 fo -0.074 vuelvo -0.031 estafa
inputs
0.0
0.0
Nos
-0.031
estafa
-0.107
ron
0.138
en
0.199
carre
-0.078
fo
0.005
ur
-0.125
.
0.074
No
-0.074
vuelvo
0.0
a
0.0
comprar
0.0
alli
0.0
jama
0.0
s
0.0