Skip to content

Commit f22299f

Browse files
init
0 parents  commit f22299f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+6320
-0
lines changed

.gitignore

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
weights/
2+
data/imagenet/
3+
data/imagenet
4+
pretrained/
5+
results/
6+
models/weights/
7+
experiments/
8+
figures/
9+
*.ipynb
10+
# Byte-compiled / optimized / DLL files
11+
__pycache__/
12+
*.py[cod]
13+
*$py.class
14+
15+
# C extensions
16+
*.so
17+
18+
# Distribution / packaging
19+
.Python
20+
build/
21+
develop-eggs/
22+
dist/
23+
downloads/
24+
eggs/
25+
.eggs/
26+
lib/
27+
lib64/
28+
parts/
29+
sdist/
30+
var/
31+
wheels/
32+
*.egg-info/
33+
.installed.cfg
34+
*.egg
35+
MANIFEST
36+
37+
# PyInstaller
38+
# Usually these files are written by a python script from a template
39+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
40+
*.manifest
41+
*.spec
42+
43+
# Installer logs
44+
pip-log.txt
45+
pip-delete-this-directory.txt
46+
47+
# Unit test / coverage reports
48+
htmlcov/
49+
.tox/
50+
.coverage
51+
.coverage.*
52+
.cache
53+
nosetests.xml
54+
coverage.xml
55+
*.cover
56+
.hypothesis/
57+
.pytest_cache/
58+
59+
# Translations
60+
*.mo
61+
*.pot
62+
63+
# Django stuff:
64+
*.log
65+
local_settings.py
66+
db.sqlite3
67+
68+
# Flask stuff:
69+
instance/
70+
.webassets-cache
71+
72+
# Scrapy stuff:
73+
.scrapy
74+
75+
# Sphinx documentation
76+
docs/_build/
77+
78+
# PyBuilder
79+
target/
80+
81+
# Jupyter Notebook
82+
.ipynb_checkpoints
83+
84+
# pyenv
85+
.python-version
86+
87+
# celery beat schedule file
88+
celerybeat-schedule
89+
90+
# SageMath parsed files
91+
*.sage.py
92+
93+
# Environments
94+
.env
95+
.venv
96+
env/
97+
venv/
98+
ENV/
99+
env.bak/
100+
venv.bak/
101+
102+
# Spyder project settings
103+
.spyderproject
104+
.spyproject
105+
106+
# Rope project settings
107+
.ropeproject
108+
109+
# mkdocs documentation
110+
/site
111+
112+
# mypy
113+
.mypy_cache/
114+
.idea/

README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# The Attribution Bottleneck
2+
3+
This is the source code for the paper "Information Bottleneck for Attribution:
4+
What is Sufficient for Prediction?".
5+
6+
## Setup
7+
8+
1. Clone this repository:
9+
```
10+
$ git clone [URL] && cd attribution-bottleneck-pytorch
11+
```
12+
2. Create a conda environment with all packages:
13+
```
14+
$ conda create -n new environment --file requirements.txt
15+
```
16+
17+
3. Using your new conda environment, install this repository with pip:
18+
```
19+
$ pip install .
20+
```
21+
22+
4. Download the model weights from the [release page](releases) and unpack them
23+
in the repository root directory:
24+
```
25+
$ tar -xvf bottleneck_for_attribution_weights.tar.gz
26+
```
27+
28+
Optional:
29+
30+
31+
5. If you want to retrain the Readout Bottleneck, place the imagenet dataset under `data/imagenet`. You might just create
32+
a link with `ln -s [image dir] data/imagenet`.
33+
34+
6. Test it with:
35+
```
36+
$ python ./scripts/eval_degradation.py resnet50 8 Saliency test
37+
```
38+
39+
## Usage
40+
41+
We provide some jupyter notebooks to demonstrate the usage of both per-sample and readout bottleneck.
42+
* `example_per-sample.ipynb` : Usage of the Per-Sample Bottleneck on an example image
43+
* `example_readout.ipynb` : Usage of the Readout Bottleneck on an example image
44+
* `compare_methods.ipynb` : Visually compare different attribution methods on an example image
45+
46+
## Scripts
47+
48+
The scripts to reproduce our evaluation can be found in the [scripts
49+
directory](scripts).
50+
Following attributions are implemented:
51+
52+
53+
54+
For the bounding box task, replace the model with either `vgg16` or `resnet50`.
55+
```bash
56+
$eval_bounding_boxes.py [model] [attribution]
57+
```
58+
59+
For the degradation task, you also have specify the tile size. In the paper, we
60+
used `8` and `14`.
61+
```bash
62+
$ eval_degradation.py [model] [tile size] [attribution]
63+
```
64+
65+
The results on sensitivity-n can be calculated with:
66+
```bash
67+
eval_sensitivity_n.py [model] [tile size] [attribution]
68+
```
69+

attribution_bottleneck/__init__.py

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from attribution_bottleneck.attribution.guided_backprop import GuidedBackprop
2+
from attribution_bottleneck.attribution.misc import Random
3+
from attribution_bottleneck.attribution.averaging_gradient import IntegratedGradients, SmoothGrad
4+
from attribution_bottleneck.attribution.backprop import Gradient, Saliency
5+
from attribution_bottleneck.attribution.grad_cam import GradCAM, GuidedGradCAM
6+
from attribution_bottleneck.attribution.lrp.lrp import LRP
7+
from attribution_bottleneck.attribution.occlusion import Occlusion
8+
from attribution_bottleneck.attribution.per_sample_bottleneck import PerSampleBottleneckReader
9+
10+
__all__ = [
11+
"Random",
12+
"GradCAM",
13+
"Gradient",
14+
"Saliency",
15+
"Occlusion",
16+
"IntegratedGradients",
17+
"SmoothGrad",
18+
"LRP",
19+
"GuidedBackprop",
20+
"GuidedGradCAM",
21+
"PerSampleBottleneckReader",
22+
]
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from __future__ import print_function
2+
3+
import torch.autograd
4+
from attribution_bottleneck.attribution.base import AttributionMethod
5+
6+
# from ..attribution.base import AttributionMethod
7+
from attribution_bottleneck.attribution.backprop import ModifiedBackpropMethod
8+
from tqdm import tqdm
9+
10+
11+
from ..utils.baselines import Mean, Baseline
12+
from ..utils.misc import *
13+
14+
15+
class AveragingGradient(AttributionMethod):
16+
"""
17+
Something than can make a attribution heatmap from several inputs and a backpropagating attribution method.
18+
The resulting heatmap is the sum of all the other methods.
19+
"""
20+
def __init__(self, backprop: ModifiedBackpropMethod):
21+
super().__init__()
22+
self.verbose = False
23+
self.progbar = False
24+
self.backprop = backprop
25+
26+
def heatmap(self, input_t, target):
27+
# generate sample list (different per method)
28+
images = self._get_samples(input_t)
29+
target_t = target if isinstance(target, torch.Tensor) else torch.tensor(target, device=input_t.device)
30+
assert isinstance(target_t, torch.Tensor)
31+
assert len(images[0].shape) == 4, "{} makes dim {} !".format(images[0].shape, len(images[0].shape)) # C x N x N
32+
33+
grads = self._backpropagate_multiple(images, target_t)
34+
35+
# Reduce sample dimension
36+
grads_mean = np.mean(grads, axis=0)
37+
# Reduce batch dimension
38+
grads_rgb = np.mean(grads_mean, axis=0)
39+
# Reduce color dimension
40+
heatmap = np.mean(grads_rgb, axis=0)
41+
return heatmap
42+
43+
def _backpropagate_multiple(self, inputs: list, target_t: torch.Tensor):
44+
"""
45+
returns an array with all the computed gradients
46+
shape: N_Inputs x Batches=1 x Color Channels x Height x Width
47+
"""
48+
# Preallocate empty gradient stack
49+
grads = np.zeros((len(inputs), *inputs[0].shape))
50+
# Generate gradients
51+
iterator = tqdm(range(len(inputs)), ncols=100, desc="calc grad") if len(inputs) > 1 and self.progbar else range(len(inputs))
52+
for i in iterator:
53+
grad = self.backprop._calc_gradient(input_t=inputs[i], target_t=target_t)
54+
# If required, add color dimension
55+
if len(grad.shape) == 3:
56+
np.expand_dims(grad, axis=0)
57+
grads[i, :, :, :, :] = grad
58+
59+
return grads
60+
61+
def _get_samples(self, img_t: torch.Tensor) -> list:
62+
""" yield the samples to analyse """
63+
raise NotImplementedError
64+
65+
66+
class SmoothGrad(AveragingGradient):
67+
def __init__(self, backprop: ModifiedBackpropMethod, std=0.15, steps=50):
68+
super().__init__(backprop=backprop)
69+
self.std = std
70+
self.steps = steps
71+
72+
def _get_samples(self, img_t: torch.Tensor) -> list:
73+
relative_std = (img_t.max().item() - img_t.min().item()) * self.std
74+
noises = [torch.randn(*img_t.shape).to(img_t.device) * relative_std for _ in range(0, self.steps)]
75+
noise_images = [img_t + noises[i] for i in range(0, self.steps)]
76+
return noise_images
77+
78+
79+
class IntegratedGradients(AveragingGradient):
80+
81+
def __init__(self, backprop: ModifiedBackpropMethod, baseline: Baseline = None, steps=50):
82+
"""
83+
:param baseline: start point for interpolation (0-1 grey, or "inv", or "avg")
84+
:param steps: resolution
85+
"""
86+
super().__init__(backprop=backprop)
87+
self.baseline = baseline if baseline is not None else Mean()
88+
self.steps = steps
89+
90+
def _get_samples(self, img_t: torch.Tensor) -> np.array:
91+
bl_img = self.baseline.apply(to_np_img(img_t))
92+
bl_img_t = to_img_tensor(bl_img, device=img_t.device)
93+
return [((i / self.steps) * (img_t - bl_img_t) + bl_img_t) for i in range(1, self.steps + 1)]
94+

0 commit comments

Comments
 (0)