Grad-CAM with torch-cam beside TorchLens¶
TorchCAM computes class activation maps:
Grad-CAM, Grad-CAM++, Smooth Grad-CAM++, Score-CAM, Layer-CAM and others.
TorchLens ships one CAM natively, tl.attribution.grad_cam, and no torch-cam
extra. Use torch-cam when you need another CAM variant; use the native
grad_cam when you want the layer chosen from a TorchLens trace, token grids
for vision transformers, and the result's provenance in extra. On the same
model and layer the two Grad-CAM maps agree, which this page checks.
torchcam 0.4.0 declares numpy<2, which would downgrade numpy under current
torch. Its other dependencies (torch, Pillow, matplotlib) come with the
first line, and it runs on numpy 2, so --no-deps is enough.
Minimal example on the running model¶
This page uses ResNet-18 on one
image. Grad-CAM weights the maps of
the last convolutional stage; these are layer4's maps on the running CNN. The recipe below
builds its own model for the method.
# recipe-test: tool=torchcam expected=True
import torch
import torchvision
import torchlens as tl
torch.manual_seed(0)
model = torchvision.models.resnet18(weights=None).eval()
x = torch.rand(1, 3, 224, 224)
trace = tl.trace(model, x)
maps = trace["layer4"].out
RESULT = tuple(maps.shape) == (1, 512, 7, 7)
Both libraries choose the same layer¶
torch-cam locates its target layer from a dummy forward pass when you name
none. TorchLens chooses from a metadata-only trace of your own call: the
deepest once-firing module that outputs a spatial map, lies on every path from
the input to the output, and carries a nonzero target gradient. On a
torchvision ResNet both choose layer4.
# recipe-test: tool=torchcam expected=('layer4', 'layer4', 'trace')
import torch
import torchvision
from torchcam.methods import GradCAM
import torchlens as tl
torch.manual_seed(0)
model = torchvision.models.resnet18(weights=None).eval()
x = torch.randn(1, 3, 64, 64)
extractor = GradCAM(model)
scores = model(x)
torchcam_map = extractor(3, scores, normalized=False)[0]
extractor.remove_hooks()
native = tl.attribution.grad_cam(model, x, target=3, overlay=False)
RESULT = (extractor.target_names[0], native.extra["layer"], native.extra["layer_source"])
The maps agree¶
torch-cam returns the map at the layer's own resolution; TorchLens upsamples
it bilinearly to the input grid and reports the native size in
extra["native_map_resolution"]. Upsampling the torch-cam map the same way
gives the TorchLens values.
# recipe-test: tool=torchcam expected=((2, 2), True)
import torch.nn.functional as F
upsampled = F.interpolate(
torchcam_map.unsqueeze(1), size=x.shape[-2:], mode="bilinear", align_corners=False
)
RESULT = (
native.extra["native_map_resolution"],
torch.allclose(upsampled, native.values, atol=1e-5),
)
When to use which¶
- Another CAM variant (Grad-CAM++, Score-CAM, Layer-CAM): torch-cam.
- A vision transformer layer:
tl.attribution.grad_cam, which lays the tokens on the grid the trace proves and drops the class, distillation or register tokens it finds; see the attribution reference. - pytorch-grad-cam instead: the
tl.bridge.pytorch_grad_cambridge.