Integrated-gradient on IMDB dataset (PyTorch)
This is an example of the integrated-gradient method on text classification with a PyTorch model. If using this explainer, please cite the original work: https://github.com/ankurtaly/Integrated-Gradients.
[1]:
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import sklearn
from sklearn.datasets import fetch_20newsgroups
from omnixai.data.text import Text
from omnixai.preprocessing.text import Word2Id
from omnixai.explainers.tabular.agnostic.L2X.utils import Trainer, InputData, DataLoader
from omnixai.explainers.nlp.specific.ig import IntegratedGradientText
We apply a simple CNN model for this text classification task. Note that the method forward
has two inputs inputs
(token ids) and masks
(the sentence masks). For IntegratedGradientText
, the first input of the model must be the token ids.
[2]:
class TextModel(nn.Module):
def __init__(self, num_embeddings, num_classes, **kwargs):
super().__init__()
self.num_embeddings = num_embeddings
self.embedding_size = kwargs.get("embedding_size", 50)
self.embedding = nn.Embedding(self.num_embeddings, self.embedding_size)
self.embedding.weight.data.normal_(mean=0.0, std=0.01)
hidden_size = kwargs.get("hidden_size", 100)
kernel_sizes = kwargs.get("kernel_sizes", [3, 4, 5])
if type(kernel_sizes) == int:
kernel_sizes = [kernel_sizes]
self.activation = nn.ReLU()
self.conv_layers = nn.ModuleList([
nn.Conv1d(self.embedding_size, hidden_size, k, padding=k // 2) for k in kernel_sizes])
self.dropout = nn.Dropout(0.2)
self.output_layer = nn.Linear(len(kernel_sizes) * hidden_size, num_classes)
def forward(self, inputs, masks):
embeddings = self.embedding(inputs)
x = embeddings * masks.unsqueeze(dim=-1)
x = x.permute(0, 2, 1)
x = [self.activation(layer(x).max(2)[0]) for layer in self.conv_layers]
outputs = self.output_layer(self.dropout(torch.cat(x, dim=1)))
if outputs.shape[1] == 1:
outputs = outputs.squeeze(dim=1)
return outputs
We use a Text
object to represent a batch of texts/sentences. The package omnixai.preprocessing.text
provides some transforms related to text data such as Tfidf
and Word2Id
.
[3]:
# Load the training and test datasets
train_data = pd.read_csv('/home/ywz/data/imdb/labeledTrainData.tsv', sep='\t')
n = int(0.8 * len(train_data))
x_train = Text(train_data["review"].values[:n])
y_train = train_data["sentiment"].values[:n].astype(int)
x_test = Text(train_data["review"].values[n:])
y_test = train_data["sentiment"].values[n:].astype(int)
class_names = ["negative", "positive"]
# The transform for converting words/tokens to IDs
transform = Word2Id().fit(x_train)
The preprocessing function converts a batch of texts into token IDs and the masks. The outputs of the preprocessing function must fit the inputs of the model.
[4]:
max_length = 256
device = "cuda" if torch.cuda.is_available() else "cpu"
def preprocess(X: Text):
samples = transform.transform(X)
max_len = 0
for i in range(len(samples)):
max_len = max(max_len, len(samples[i]))
max_len = min(max_len, max_length)
inputs = np.zeros((len(samples), max_len), dtype=int)
masks = np.zeros((len(samples), max_len), dtype=np.float32)
for i in range(len(samples)):
x = samples[i][:max_len]
inputs[i, :len(x)] = x
masks[i, :len(x)] = 1
return inputs, masks
We now train the CNN model and evaluate its performance.
[5]:
model = TextModel(
num_embeddings=transform.vocab_size,
num_classes=len(class_names)
).to(device)
Trainer(
optimizer_class=torch.optim.AdamW,
learning_rate=1e-3,
batch_size=128,
num_epochs=10,
).train(
model=model,
loss_func=nn.CrossEntropyLoss(),
train_x=transform.transform(x_train),
train_y=y_train,
padding=True,
max_length=max_length,
verbose=True
)
|████████████████████████████████████████| 100.0% Complete, Loss 0.0008
[6]:
model.eval()
data = transform.transform(x_test)
data_loader = DataLoader(
dataset=InputData(data, [0] * len(data), max_length),
batch_size=32,
collate_fn=InputData.collate_func,
shuffle=False
)
outputs = []
for inputs in data_loader:
value, mask, target = inputs
y = model(value.to(device), mask.to(device))
outputs.append(y.detach().cpu().numpy())
outputs = np.concatenate(outputs, axis=0)
predictions = np.argmax(outputs, axis=1)
print('Test accuracy: {}'.format(
sklearn.metrics.f1_score(y_test, predictions, average='binary')))
Test accuracy: 0.8458027386386188
To initialize IntegratedGradientText
, we need to set the following parameters:
model
: The model to explain, whose type istf.keras.Model
ortorch.nn.Module
.embedding_layer
: The embedding layer in the model, which can betf.keras.layers.Layer
ortorch.nn.Module
.preprocess_function
: The pre-processing function that converts the raw input data into the inputs ofmodel
. The first output ofpreprocess_function
should be the token ids.mode
: The task type, e.g.,classification
orregression
.id2token
: The mapping from token ids to tokens.
[7]:
explainer = IntegratedGradientText(
model=model,
embedding_layer=model.embedding,
preprocess_function=preprocess,
id2token=transform.id_to_word
)
x = Text([
"What a great movie! if you have no taste.",
"it was a fantastic performance!",
"best film ever",
"such a great show!",
"it was a horrible movie",
"i've never watched something as bad"
])
explanations = explainer.explain(x)
explanations.ipython_plot(class_names=class_names)