Score-CAM

August 21, 2026 · View on GitHub

CI

A readable Keras implementation of Score-CAM, plus Faster-Score-CAM, a variant that is ~39x faster at a correlation of 0.93 with the full method.

The following are implemented and compared:

Blog post: Qiita (Japanese)

Install

git clone https://github.com/tabayashi0117/Score-CAM.git
cd Score-CAM
uv sync            # or: pip install -e .

Python >= 3.11, TensorFlow >= 2.16 (Keras 3). For an NVIDIA GPU on Linux, uv sync --extra gpu, which pulls tensorflow[and-cuda].

The only dependencies are TensorFlow, NumPy, Pillow and Matplotlib. Resizing goes through tf.image.resize, image loading through Pillow, and colouring through Matplotlib — so there is no OpenCV to install.

Usage

from keras.applications.vgg16 import VGG16, preprocess_input
from scorecam import ScoreCam, read_and_preprocess_img, superimpose

model = VGG16(include_top=True, weights="imagenet")
img_array = read_and_preprocess_img("./image/hummingbird.jpg", size=(224, 224))

cam = ScoreCam(model, img_array, "block5_conv3")               # Score-CAM
cam = ScoreCam(model, img_array, "block5_conv3", max_N=10)     # Faster-Score-CAM
overlay = superimpose("./image/hummingbird.jpg", cam)
overlay = superimpose(path, cam, colormap="turbo")   # any Matplotlib colormap

Every CAM function returns a 2-D float32 array scaled into [0, 1], with the spatial shape of the target layer, and takes an optional class_index= to explain a class other than the model's top-1.

from scorecam import GradCam, GradCamPlusPlus, GuidedBackPropagation, build_guided_model

GradCam(model, img_array, layer_name, class_index=None, use_logits=True)
GradCamPlusPlus(model, img_array, layer_name, class_index=None, use_logits=True)
ScoreCam(model, img_array, layer_name, max_N=-1, class_index=None, batch_size=32,
         weight_mode="reference", raw_img_array=None, preprocess_fn=None)
GuidedBackPropagation(build_guided_model(model), img_array, layer_name)
superimpose(path_or_rgb_array, cam, emphasize=False, heatmap_intensity=0.8,
            colormap="jet")

superimpose defaults to jet because that is what published CAM figures use, but it takes any Matplotlib colormap; turbo is the modern drop-in, and inferno or viridis are better still if you do not need the familiar look.

For a model that is not VGG16, pass its own preprocessing:

from keras.applications.resnet50 import preprocess_input as resnet_preprocess_input

img_array = read_and_preprocess_img(path, preprocess_fn=resnet_preprocess_input)

See Score-CAM.ipynb for the full walkthrough, including applying Score-CAM to your own model.

Faster-Score-CAM

Score-CAM runs one forward pass per channel of the target layer — 512 of them for VGG16's block5_conv3. We found that a few channels dominate the final heatmap, so Faster-Score-CAM keeps only the max_N activation maps with the largest variance and masks with those. max_N=-1 is plain Score-CAM.

Measured on image/hummingbird.jpg, VGG16 block5_conv3, one CPU machine, TensorFlow 2.21. Absolute times depend on the machine; the ratios do not.

methodtimespeed-upcorrelation with full Score-CAM
Grad-CAM0.19 s
Grad-CAM++0.20 s
Guided Backpropagation0.19 s
Score-CAM20.4 s1x1.000
Faster-Score-CAM max_N=1004.13 s5x0.999
Faster-Score-CAM max_N=301.28 s16x0.986
Faster-Score-CAM max_N=100.52 s39x0.927
Faster-Score-CAM max_N=30.26 s78x0.567
Faster-Score-CAM max_N=10.18 s114x0.461

max_N=10 is the sweet spot: 39x faster for a correlation of 0.93. Below it the map degrades quickly — max_N=3 is already down to 0.57. Reproduce the table with the "processing time" cells of the notebook.

The paper and the authors' code disagree

The authors' reference implementation diverges from Algorithm 1 of their own paper in two places. Both are keyword arguments here, documented inline in scorecam/score_cam.py.

Algorithm 1reference implementationthis repo's default
mask targetthe raw image X₀the preprocessed tensorpreprocessed (pass raw_img_array= + preprocess_fn= for the paper)
channel weightsoftmax over channels of the target-class logitssoftmax over classes, take the target probabilityweight_mode="reference" (use "paper" for Algorithm 1)

The defaults follow the reference implementation, not the paper. Algorithm 1 read literally is close to degenerate: it exponentiates raw logits, which on a confident prediction concentrates nearly all the weight on a couple of channels. On VGG16 / block5_conv3 / hummingbird.jpg, where the 512 masked inputs produce target-class logits spanning 1.29 to 20.54:

$ uv run python -m scorecam.diagnostics

weight_mode    largest    top-10    exp(H)
paper           82.9%     96.4%       2.5
reference        1.4%     13.5%     159.6

That is 2.5 effective channels out of 512 — Score-CAM reduces to "show the two or three best channels". Presumably why the authors' own code does something else. The two modes correlate at r=0.76.

Note that both modes are computed from logits. A Keras classifier usually ends in a softmax, so the pre-v0.2 code — which applied its own softmax on top of the model's output — applied it twice and flattened the weights to 487 of 512 effective channels, very nearly an unweighted mean of the activation maps. Torch models emit logits, which is why the reference code gets this right with one softmax.

Results

More in result/. Regenerate them with:

uv run python scripts/regenerate_results.py

Anomaly detection on the DAGM dataset

The notebook also trains a truncated ResNet50 on the DAGM 2007 textures and localizes the defects with Faster-Score-CAM. The backbone is cut at conv3_block4_add, whose 28x28 feature map is fine enough for these small defects; superimpose(..., emphasize=True) makes the low-contrast maps legible.

The DAGM figures in result/Class*_result_*.png were produced with v0.1 and have not been regenerated — they need the dataset and six retrained models.

Development

uv sync --group dev
uv run nbstripout --install   # once: keeps notebook outputs out of git
uv run pytest                 # ~0.5 s, CPU only, no downloads

The test suite runs against a tiny randomly-initialised CNN, so it needs no ImageNet weights and stays fast enough to run on every push. CI additionally re-resolves dependencies and runs the suite against the newest TensorFlow on the 1st of each month — this repository was broken by a TensorFlow upgrade once and that job exists so it does not happen silently again.

See CLAUDE.md for the invariants contributors are expected to keep.

For a general-purpose, actively maintained saliency library, prefer tf-keras-vis (Keras) or pytorch-grad-cam (PyTorch). This repository is a reference implementation meant to be read.

Changelog

See CHANGELOG.md. v0.2.0 fixes several correctness bugs that change Score-CAM's output; gradcamutils still imports, so existing code keeps working.

License

MIT