Getting started
Install
Zennit can be installed directly from PyPI:
$ pip install zennit
For the current development version, or to try out examples, Zennit may be alternatively cloned and installed with
$ git clone https://github.com/chr5tphr/zennit.git
$ pip install ./zennit
Basic Usage
Zennit implements propagation-based attribution methods by overwriting the gradient of PyTorch modules in PyTorch’s auto-differentiation engine. This means that Zennit will only work on models which are strictly implemented using PyTorch modules, including activation functions. The following demonstrates a setup to compute Layer-wise Relevance Propagation (LRP) relevance for a simple model and random data.
import torch
from torch.nn import Sequential, Conv2d, ReLU, Linear, Flatten
# setup the model and data
model = Sequential(
Conv2d(3, 10, 3, padding=1),
ReLU(),
Flatten(),
Linear(10 * 32 * 32, 10),
)
input = torch.randn(1, 3, 32, 32)
The most important high-level structures in Zennit are Composites
,
Attributors
and Canonizers
.
Composites
Composites map Rules
to modules based on their properties and context to
modify their gradient. The most common composites for LRP are implemented in
zennit.composites
.
The following computes LRP relevance using the EpsilonPlusFlat
composite:
from zennit.composites import EpsilonPlusFlat
# create a composite instance
composite = EpsilonPlusFlat()
# use the following instead to ignore bias for the relevance
# composite = EpsilonPlusFlat(zero_params='bias')
# make sure the input requires a gradient
input.requires_grad = True
# compute the output and gradient within the composite's context
with composite.context(model) as modified_model:
output = modified_model(input)
# gradient/ relevance wrt. class/output 0
output.backward(gradient=torch.eye(10)[[0]])
# relevance is not accumulated in .grad if using torch.autograd.grad
# relevance, = torch.autograd.grad(output, input, torch.eye(10)[[0])
# gradient is accumulated in input.grad
print('Backward:', input.grad)
The context created by zennit.core.Composite.context()
registers the
composite, which means that all rules are applied according to the composite’s
mapping. See Using Rules, Composites, and Canonizers for information on
using composites, zennit.composites
for an API reference and
Writing Custom Composites for writing new compositors. Available
Rules
can be found in zennit.rules
, their use is described in
Using Rules, Composites, and Canonizers and how to add new ones is described in
Writing Custom Rules.
Attributors
Alternatively, attributors may be used instead of composite.context
.
from zennit.attribution import Gradient
attributor = Gradient(model, composite)
with attributor:
# gradient/ relevance wrt. output/class 1
output, relevance = attributor(input, torch.eye(10)[[1]])
print('EpsilonPlusFlat:', relevance)
Attribution methods which are not propagation-based, like
zennit.attribution.SmoothGrad
are implemented as attributors, and
may be combined with propagation-based (composite) approaches.
from zennit.attribution import SmoothGrad
# we do not need a composite to compute vanilla SmoothGrad
with SmoothGrad(model, noise_level=0.1, n_iter=10) as attributor:
# gradient/ relevance wrt. output/class 7
output, relevance = attributor(input, torch.eye(10)[[7]])
print('SmoothGrad:', relevance)
More information on attributors can be found in Using Attributors and Writing Custom Attributors.
Canonizers
For some modules and operations, Layer-wise Relevance Propagation (LRP) is not
implementation-invariant, eg. BatchNorm -> Dense -> ReLU
will be attributed
differently than Dense -> BatchNorm -> ReLU
. Therefore, LRP needs a
canonical form of the model, which is implemented in Canonizers
. These may
be simply supplied when instantiating a composite:
from torchvision.models import vgg16
from zennit.composites import EpsilonGammaBox
from zennit.torchvision import VGGCanonizer
# instantiate the model
model = vgg16()
# create the canonizers
canonizers = [VGGCanonizer()]
# EpsilonGammaBox needs keyword arguments 'low' and 'high'
composite = EpsilonGammaBox(low=-3., high=3., canonizers=canonizers)
with Gradient(model, composite) as attributor:
# gradient/ relevance wrt. output/class 0
# torchvision.vgg16 has 1000 output classes by default
output, relevance = attributor(input, torch.eye(1000)[[0]])
print('EpsilonGammaBox:', relevance)
Some pre-defined canonizers for models from torchvision
can be found in
zennit.torchvision
. The zennit.torchvision.VGGCanonizer
specifically is simply zennit.canonizers.SequentialMergeBatchNorm
,
which may be used when BatchNorm
is used in sequential models. Note that for
SequentialMergeBatchNorm
to work, all functions (linear layers, activations,
…) must be modules and assigned to their parent module in the order they are
visited (see zennit.canonizers.SequentialMergeBatchNorm
). For more
information on canonizers see Using Rules, Composites, and Canonizers and
Writing Custom Canonizers.
Visualizing Results
While attribution approaches are not limited to the domain of images, they are predominantly used on image models and produce heat maps of relevance. For this reason, Zennit implements methods to visualize relevance heat maps.
from zennit.image import imsave
# sum over the color channels
heatmap = relevance.sum(1)
# get the absolute maximum, to center the heat map around 0
amax = heatmap.abs().numpy().max((1, 2))
# save heat map with color map 'coldnhot'
imsave(
'heatmap.png',
heatmap[0],
vmin=-amax,
vmax=amax,
cmap='coldnhot',
level=1.0,
grid=False
)
Information on imsave
can be found at zennit.image.imsave()
.
Saving an image with 3 color channels will result in the image being saved
without a color map but with the channels assumed as RGB. The keyword argument
grid
will create a grid of multiple images over the batch dimension if
True
. Custom color maps may be created with
zennit.cmap.ColorMap
, eg. to save the previous image with a color
map ranging from blue to yellow to red:
from zennit.cmap import ColorMap
# 00f is blue, ff0 is yellow, f00 is red, 0x80 is the center of the range
cmap = ColorMap('00f,80:ff0,f00')
imsave(
'heatmap.png',
heatmap,
vmin=-amax,
vmax=amax,
cmap=cmap,
level=1.0,
grid=True
)
More details to visualize heat maps and color maps can be found in
Visualizing Results. The ColorMap specification language is
described in zennit.cmap.ColorMap
and built-in color maps are
implemented in zennit.image.CMAPS
.
Example Script
A ready-to use example to analyze a few ImageNet models provided by torchvision can be found at share/example/feed_forward.py.
The following setup requires bash, cURL and (magic-)file.
Create a virtual environment, install Zennit and download the example scripts:
$ mkdir zennit-example
$ cd zennit-example
$ python -m venv .venv
$ .venv/bin/pip install zennit
$ curl -o feed_forward.py \
'https://raw.githubusercontent.com/chr5tphr/zennit/master/share/example/feed_forward.py'
$ curl -o download-lighthouses.sh \
'https://raw.githubusercontent.com/chr5tphr/zennit/master/share/scripts/download-lighthouses.sh'
Prepare the data required for the example:
$ mkdir params data results
$ bash download-lighthouses.sh --output data/lighthouses
$ curl -o params/vgg16-397923af.pth 'https://download.pytorch.org/models/vgg16-397923af.pth'
This creates the needed directories and downloads the pre-trained vgg16 parameters and 8 images of light houses from wikimedia commons into the required label-directory structure for the imagenet dataset in PyTorch.
The feed_forward.py
example can then be run using:
$ .venv/bin/python feed_forward.py \
data/lighthouses \
'results/vgg16_epsilon_gamma_box_{sample:02d}.png' \
--inputs 'results/vgg16_input_{sample:02d}.png' \
--parameters params/vgg16-397923af.pth \
--model vgg16 \
--composite epsilon_gamma_box \
--no-bias \
--relevance-norm symmetric \
--cmap coldnhot
which computes the lrp heatmaps according to the epsilon_gamma_box
rule and
stores them in results, along with the respective input images. Other possible
composites that can be passed to --composites
are, e.g., epsilon_plus
,
epsilon_alpha2_beta1_flat
, guided_backprop
, excitation_backprop
.
The bias can be ignored in the LRP-computation by passing --no-bias
.
Alternatively, heatmaps for SmoothGrad with absolute relevances may be computed
by omitting --composite
and supplying --attributor
:
$ .venv/bin/python feed_forward.py \
data/lighthouses \
'results/vgg16_smoothgrad_{sample:02d}.png' \
--inputs 'results/vgg16_input_{sample:02d}.png' \
--parameters params/vgg16-397923af.pth \
--model vgg16 \
--attributor smoothgrad \
--relevance-norm absolute \
--cmap hot
For Integrated Gradients, --attributor integrads
may be provided.
Heatmaps for Occlusion Analysis with unaligned relevances may be computed by executing:
$ .venv/bin/python feed_forward.py \
data/lighthouses \
'results/vgg16_occlusion_{sample:02d}.png' \
--inputs 'results/vgg16_input_{sample:02d}.png' \
--parameters params/vgg16-397923af.pth \
--model vgg16 \
--attributor occlusion \
--relevance-norm unaligned \
--cmap hot