Captum Explainability for Heterogeneous Graphs Edges

Hi there,

I am trying to use Captum to explain the heterogeneous graph edges. There is no edge weight as input.
My model is self.conv1 = dglnn.HeteroGraphConv(). And in the forward function:

def forward(self, blocks, h_dict: dict, eweight=None) -> dict:
    if eweight is None:
        h_dict = self.conv1(blocks[0], h_dict)

    else:
        h_dict = self.conv1(blocks[0], h_dict, mod_kwargs={
            c_etype: {'edge_weight': self.w(blocks[0][c_etype], eweight[c_etype])} for c_etype in blocks[0].canonical_etypes
        })

I am trying to use Camtum like this:

def model_forward(self, x, c_etype, block, feat, eweight):
    eweight = eweight.copy()
    eweight[c_etype] = x
    return self.model(block, feat, eweight=eweight) 

def explain_graph(self, block, feat, label, **kwargs):
    edge_masks = {}
     for c_etype in block[0].canonical_etypes:
          edge_masks[c_etype] = torch.ones(block[0].num_edges(c_etype)).requires_grad_(True).to(device)

     for c_etype in block[0].canonical_etypes:
          ig = IntegratedGradients(partial(self.model_forward, c_etype=c_etype, block=block, feat=feat, eweight=edge_masks))
          e_m = ig.attribute(edge_masks[c_etype], internal_batch_size=block[0].num_edges(c_etype), n_steps=50)

But I got an RuntimeError: One of the differentiated Tensors appears to not have been used in the graph. Set allow_unused=True if this is the desired behavior.

Do you know how to solve it?

Many thanks!

Previously this thread encountered a similar issue. Could you take a look and see if it helps?

Thanks for your reply. I had a look at the thread, I tried to use ‘edge_weight’ when training the GNN model, but I got the same error: ‘RuntimeError: One of the differentiated Tensors appears to not have been used in the graph. Set allow_unused=True if this is the desired behavior.’

I can not find which tensor was used without grad backwards.

Here is the error tracback:

File “/home/hongbo/Mumin/models/HeteroCaptum.py”, line 77, in explain_graph
e_m = ig.attribute(edge_masks[c_etype], internal_batch_size=edge_masks[c_etype].size(0), n_steps=50)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/log/init.py”, line 42, in wrapper
return func(*args, **kwargs)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_core/integrated_gradients.py”, line 283, in attribute
method=method,
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_utils/batching.py”, line 79, in _batch_attribution
**kwargs, n_steps=batch_steps, step_sizes_and_alphas=(step_sizes, alphas)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_core/integrated_gradients.py”, line 355, in _attribute
additional_forward_args=input_additional_args,
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/_utils/gradient.py”, line 119, in compute_gradients
grads = torch.autograd.grad(torch.unbind(outputs), inputs)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/torch/autograd/init.py”, line 302, in grad
allow_unused, accumulate_grad=False) # Calls into the C++ engine to run the backward pass
RuntimeError: One of the differentiated Tensors appears to not have been used in the graph. Set allow_unused=True if this is the desired behavior.

Could you provide a script that we can run to reproduce the error?

Hi Mufeili,

See this script:

import dgl
import dgl.nn as dglnn
import torch

import torch.nn.functional as F
from captum.attr import IntegratedGradients
from functools import partial

dataset = dgl.data.MUTAGDataset()

g = dataset[0]

# Some synthetic node features - replace it with your own.
for ntype in g.ntypes:
    g.nodes[ntype].data['x'] = torch.randn(g.num_nodes(ntype), 10)


# Your model...
class Model(torch.nn.Module):
    def __init__(self, etypes):
        super().__init__()
        self.conv1 = dglnn.HeteroGraphConv({etype: dglnn.SAGEConv(10, 10, 'mean') for _, etype, _ in etypes})
        self.w = dglnn.EdgeWeightNorm()
        self.l = torch.nn.Linear(10,1)
    def forward(self, blocks, h_dict, eweight = None):
        if eweight is None:
            h_dict = self.conv1(blocks[0], h_dict)
        else:
            h_dict = self.conv1(blocks[0], h_dict, mod_kwargs={
            c_etype: {'edge_weight': self.w(blocks[0][c_etype], eweight[c_etype])} for c_etype in blocks[0].canonical_etypes
            })
        return self.l(h_dict['SCHEMA'])


m = Model(g.canonical_etypes)

# Minibatch sampling stuff...
sampler = dgl.dataloading.as_edge_prediction_sampler(
    dgl.dataloading.NeighborSampler([2, 2]), negative_sampler=dgl.dataloading.negative_sampler.Uniform(2))
eid = {g.canonical_etypes[0]: torch.arange(g.num_edges(g.canonical_etypes[0]))}
# Let's iterate over and explain one edge at a time.
dl = dgl.dataloading.DataLoader(g, eid, sampler, batch_size=1)


# Define a function that takes in a single tensor as the first argument and also returns a
# single tensor.

def forward_model(x, c_etype,  blocks, x_dict, eweight):
    eweight = eweight.copy()
    eweight[c_etype] = x
    return m(blocks, x_dict, eweight=eweight).squeeze()


for input_nodes, pair_graph, neg_pair_graph, blocks in dl:
    input_dict = blocks[0].ndata['x']

    output = m(blocks, input_dict)

    loss = F.mse_loss(
        input=output,
        target=torch.randn(output.size()).float(),
    )
    loss.backward()

    m.zero_grad()

    edge_masks = {}
    for c_etype in blocks[0].canonical_etypes:
        edge_masks[c_etype] = torch.ones(blocks[0].num_edges(c_etype))
        edge_masks[c_etype].requires_grad = True
    for c_etype in blocks[0].canonical_etypes:
        ig = IntegratedGradients(partial(forward_model,
                                         c_etype=c_etype,  blocks=blocks, x_dict=input_dict, eweight = edge_masks))
        print(ig.attribute(edge_masks[c_etype], internal_batch_size=edge_masks[c_etype].size(0), n_steps=50))
        break

And I got the error:

Traceback (most recent call last):
File “test2.py”, line 75, in
print(ig.attribute(edge_masks[c_etype], internal_batch_size=edge_masks[c_etype].size(0), n_steps=50))
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/log/init.py”, line 42, in wrapper
return func(*args, **kwargs)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_core/integrated_gradients.py”, line 283, in attribute
method=method,
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_utils/batching.py”, line 79, in _batch_attribution
**kwargs, n_steps=batch_steps, step_sizes_and_alphas=(step_sizes, alphas)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/attr/_core/integrated_gradients.py”, line 355, in _attribute
additional_forward_args=input_additional_args,
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/captum/_utils/gradient.py”, line 119, in compute_gradients
grads = torch.autograd.grad(torch.unbind(outputs), inputs)
File “/home/hongbo/anaconda3/envs/mumin/lib/python3.7/site-packages/torch/autograd/init.py”, line 302, in grad
allow_unused, accumulate_grad=False) # Calls into the C++ engine to run the backward pass
RuntimeError: One of the differentiated Tensors appears to not have been used in the graph. Set allow_unused=True if this is the desired behavior.

Thanks. I’ll take a look. In the meantime, you can also check this repo GitHub - shenhao-stu/dgl_captum: 🤗A tutorial for using captum to explain dgl model and see if you can figure out the issue by mimicking its practice.

Many thanks. I will have a look at the tutorial.

1 Like

Hi mufeili,

I had a look at the tutorial. I think I used the same strategy for explaining the edges. The difference is my graph is a heterogenous graph and my model is heteroconv GNN. I still can not find the reason for the error. Could you please have a look and see if you can figure out it?

Many thanks for your help

I haven’t figured out the issue. However, you can convert the graph to a homogeneous graph with this API, which might make your life a lot more easier.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.