pytorch / captum

Model interpretability and understanding for PyTorch
https://captum.ai
BSD 3-Clause "New" or "Revised" License
4.91k stars 494 forks source link

Computing LayerConductance in IMDB sentiment analysis Tutorial #293

Closed ngopee closed 4 years ago

ngopee commented 4 years ago

I am trying to compute layer conductance in the IMDB tutorial, and I keep getting a scalar issue. Any guidance on how I should pass the input (test_input_tensor) to get the attributions.

cond = LayerConductance(model, model.convs)
cond_vals = cond.attribute(test_input_tensor,target=1)

Thank you!

NarineK commented 4 years ago

Hi @ngopee, does test_input_tensor contain token indices ? If you want to use LayerConductance you need to configure the InterpretableEmbeddingBase. LayerConductance doesn't have the logic of replacing the indices with embedding vectors. you'll need to call:

interpretable_embedding = configure_interpretable_embedding_layer(model, '<EMBEDDING-LAYER>')
input_embeddings = interpretable_embedding.indices_to_embeddings(test_input_tensor)
cond = LayerConductance(model, model.convs)
cond_vals = cond.attribute(input_embeddings, target=1)
remove_interpretable_embedding_layer(model, input_embeddings)

You'll find some examples here: https://captum.ai/tutorials/Multimodal_VQA_Captum_Insights https://captum.ai/tutorials/Bert_SQUAD_Interpret

NarineK commented 4 years ago

@ngopee , did it work for you ? If so, can we close the issue ?

ngopee commented 4 years ago

@NarineK Thank you so much for this.

I am actually getting this error when I tried the code above: IndexError: index 1 is out of bounds for dimension 1 with size 1

Spent whole day trying to figure out if it wasn't a silly mistake. Your help is very much appreciated!

#Setting layer conductance parameters: Conv layer
cond = LayerConductance(model, model.convs)
interpretable_embedding = configure_interpretable_embedding_layer(model, 'embedding')

def compute_input_tensor(model, sentence, min_len = 7, label = 0):
    text = [tok.text for tok in nlp.tokenizer(sentence)]
    if len(text) < min_len:
        text += ['pad'] * (min_len - len(text))
    indexed = [TEXT.vocab.stoi[t] for t in text]
    print(indexed)

    model.zero_grad()

    input_indices = torch.tensor(indexed, device=device)
    input_indices = input_indices.unsqueeze(0)

    input_embeddings = interpretable_embedding.indices_to_embeddings(input_indices)

    cond_vals = cond.attribute(input_embeddings,target=1)

    return input_indices

compute_input_tensor(model, 'It was a fantastic performance !', label=1)
NarineK commented 4 years ago

@ngopee , can it be that the output of your model is one dimensional and when you try to access target=1 it can't access that index of output?Can you try with target=0 or without specifying the target ?

ngopee commented 4 years ago

Tried with both target = 0 and without any. Still same error.

This is the tutorial I am trying to run and get the layer conductance: https://captum.ai/tutorials/IMDB_TorchText_Interpret

NarineK commented 4 years ago

It can be that model.convs is a moduleList, would you try:model.convs[0] instead ? I'll also give a try.

ngopee commented 4 years ago

Doesn't seem to be it.

Thank you for the help!

NarineK commented 4 years ago

@ngopee , I tried this code snippet and it worked for me:

# accumalate couple samples in this array for visualization purposes
vis_data_records_ig = []

lc = LayerConductance(model, model.convs[0])

def interpret_sentence(model, sentence, min_len = 7, label = 0):
    text = [tok.text for tok in nlp.tokenizer(sentence)]
    if len(text) < min_len:
        text += ['pad'] * (min_len - len(text))
    indexed = [TEXT.vocab.stoi[t] for t in text]

    model.zero_grad()

    input_indices = torch.tensor(indexed, device=device)
    input_indices = input_indices.unsqueeze(0)

    # input_indices dim: [sequence_length]
    seq_length = min_len

    # predict
    pred = forward_with_sigmoid(input_indices).item()
    pred_ind = round(pred)

    # generate reference indices for each sample
    reference_indices = token_reference.generate_reference(seq_length, device=device).unsqueeze(0)

    # compute attributions and approximation delta using layer integrated gradients
    #attributions_ig, delta = lig.attribute(input_indices, reference_indices, \
    #                                       n_steps=500, return_convergence_delta=True)
    interpretable_embedding = configure_interpretable_embedding_layer(model, 'embedding')
    input_embeddings = interpretable_embedding.indices_to_embeddings(input_indices)
    attributions_ig, delta = lc.attribute(input_embeddings, return_convergence_delta=True)
    remove_interpretable_embedding_layer(model, interpretable_embedding)

    print('pred: ', Label.vocab.itos[pred_ind], '(', '%.2f'%pred, ')', ', delta: ', abs(delta))

    add_attributions_to_visualizer(attributions_ig, text, pred, pred_ind, label, delta, vis_data_records_ig)

def add_attributions_to_visualizer(attributions, text, pred, pred_ind, label, delta, vis_data_records):
    attributions = attributions.sum(dim=2).squeeze(0)
    attributions = attributions / torch.norm(attributions)
    attributions = attributions.cpu().detach().numpy()

    # storing couple samples in an array for visualization purposes
    vis_data_records.append(visualization.VisualizationDataRecord(
                            attributions,
                            pred,
                            Label.vocab.itos[pred_ind],
                            Label.vocab.itos[label],
                            Label.vocab.itos[1],
                            attributions.sum(),       
                            text,
                            delta))

Do you have the latest version of captum ? Maybe that can be a reason for inconsistency

ngopee commented 4 years ago

Yes this works perfectly! I still have no clue what I missed. I'll look into it and post a follow up when I figure it out.

Thank you very much!!