ShapleyCAM HVP term silently collapses to zero on ReLU networks
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
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)
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.0In 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:
- Emit a
warnings.warn(...)inShapleyCAM.get_cam_weightswhenhvp.abs().max() == 0, so users know the theoretical second-order term didn't fire. - 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