Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
Back to tool/Back to issues
#582·pytorch-grad-cam

ShapleyCAM HVP term silently collapses to zero on ReLU networks

Author: shaun0927Created Apr 17, 2026Updated Apr 17, 2026

Hi Jacob — first, thanks for ShapleyCAM in #550, it's a great addition.

While running it on standard ReLU CNNs I noticed the Hessian-vector product is effectively never contributing to the final weights. The implementation in pytorch_grad_cam/shapley_cam.py:26-37 computes

python
hvp = torch.autograd.grad(outputs=grads, inputs=activations,
                          grad_outputs=activations,
                          retain_graph=False, allow_unused=True)[0]

and the final weight is (grads - 0.5 * hvp). For any network whose activation graph between activations and loss is ReLU-family (ReLU/Leaky/GELU-at-saturation/etc.), the element-wise Hessian is zero almost everywhere, so hvp comes back structurally non-None but numerically all-zero.

Minimal repro (resnet-sized CNN, no heavy deps)

python
import torch, torch.nn as nn
from pytorch_grad_cam import ShapleyCAM

class M(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = nn.Conv2d(3, 4, 3, padding=1)
        self.pool = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Linear(4, 10)
    def forward(self, x):
        return self.fc(self.pool(torch.relu(self.conv(x))).flatten(1))

m = M().eval()
x = torch.randn(1, 3, 16, 16)

captured = {}
orig = torch.autograd.grad
def spy(outputs, inputs, *a, **kw):
    r = orig(outputs, inputs, *a, **kw)
    if kw.get("allow_unused"): captured["hvp"] = r[0]
    return r
torch.autograd.grad = spy
with ShapleyCAM(m, [m.conv]) as cam:
    cam(input_tensor=x, targets=None)
print("max |hvp| =", captured["hvp"].abs().max().item())
# max |hvp| = 0.0

In that case (grads - 0.5 * hvp) == grads, i.e. ShapleyCAM is numerically identical to GradCAM.

What I'd propose

I'm happy to send a PR for either:

  1. Emit a warnings.warn(...) in ShapleyCAM.get_cam_weights when hvp.abs().max() == 0, so users know the theoretical second-order term didn't fire.
  2. Add a short note in the README row for ShapleyCAM saying the method relies on activations with non-zero second derivatives (e.g. networks that replace ReLU with smooth activations).

Either option — or both — would be fine with me. Just wanted to check with you before putting up a PR, since the fix is more about user-facing signaling than the numerics.

Orthogonal but related: #575 already addresses the memory leak on this same path; this issue is specifically about correctness/diagnostics, not memory.

Source: jacobgil/pytorch-grad-cam

View original on GitHubView discussion on GitHub