-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f22299f
Showing
43 changed files
with
6,320 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
weights/ | ||
data/imagenet/ | ||
data/imagenet | ||
pretrained/ | ||
results/ | ||
models/weights/ | ||
experiments/ | ||
figures/ | ||
*.ipynb | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
|
||
# C extensions | ||
*.so | ||
|
||
# Distribution / packaging | ||
.Python | ||
build/ | ||
develop-eggs/ | ||
dist/ | ||
downloads/ | ||
eggs/ | ||
.eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
wheels/ | ||
*.egg-info/ | ||
.installed.cfg | ||
*.egg | ||
MANIFEST | ||
|
||
# PyInstaller | ||
# Usually these files are written by a python script from a template | ||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Unit test / coverage reports | ||
htmlcov/ | ||
.tox/ | ||
.coverage | ||
.coverage.* | ||
.cache | ||
nosetests.xml | ||
coverage.xml | ||
*.cover | ||
.hypothesis/ | ||
.pytest_cache/ | ||
|
||
# Translations | ||
*.mo | ||
*.pot | ||
|
||
# Django stuff: | ||
*.log | ||
local_settings.py | ||
db.sqlite3 | ||
|
||
# Flask stuff: | ||
instance/ | ||
.webassets-cache | ||
|
||
# Scrapy stuff: | ||
.scrapy | ||
|
||
# Sphinx documentation | ||
docs/_build/ | ||
|
||
# PyBuilder | ||
target/ | ||
|
||
# Jupyter Notebook | ||
.ipynb_checkpoints | ||
|
||
# pyenv | ||
.python-version | ||
|
||
# celery beat schedule file | ||
celerybeat-schedule | ||
|
||
# SageMath parsed files | ||
*.sage.py | ||
|
||
# Environments | ||
.env | ||
.venv | ||
env/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
venv.bak/ | ||
|
||
# Spyder project settings | ||
.spyderproject | ||
.spyproject | ||
|
||
# Rope project settings | ||
.ropeproject | ||
|
||
# mkdocs documentation | ||
/site | ||
|
||
# mypy | ||
.mypy_cache/ | ||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# The Attribution Bottleneck | ||
|
||
This is the source code for the paper "Information Bottleneck for Attribution: | ||
What is Sufficient for Prediction?". | ||
|
||
## Setup | ||
|
||
1. Clone this repository: | ||
``` | ||
$ git clone [URL] && cd attribution-bottleneck-pytorch | ||
``` | ||
2. Create a conda environment with all packages: | ||
``` | ||
$ conda create -n new environment --file requirements.txt | ||
``` | ||
|
||
3. Using your new conda environment, install this repository with pip: | ||
``` | ||
$ pip install . | ||
``` | ||
|
||
4. Download the model weights from the [release page](releases) and unpack them | ||
in the repository root directory: | ||
``` | ||
$ tar -xvf bottleneck_for_attribution_weights.tar.gz | ||
``` | ||
|
||
Optional: | ||
|
||
|
||
5. If you want to retrain the Readout Bottleneck, place the imagenet dataset under `data/imagenet`. You might just create | ||
a link with `ln -s [image dir] data/imagenet`. | ||
|
||
6. Test it with: | ||
``` | ||
$ python ./scripts/eval_degradation.py resnet50 8 Saliency test | ||
``` | ||
|
||
## Usage | ||
|
||
We provide some jupyter notebooks to demonstrate the usage of both per-sample and readout bottleneck. | ||
* `example_per-sample.ipynb` : Usage of the Per-Sample Bottleneck on an example image | ||
* `example_readout.ipynb` : Usage of the Readout Bottleneck on an example image | ||
* `compare_methods.ipynb` : Visually compare different attribution methods on an example image | ||
|
||
## Scripts | ||
|
||
The scripts to reproduce our evaluation can be found in the [scripts | ||
directory](scripts). | ||
Following attributions are implemented: | ||
|
||
|
||
|
||
For the bounding box task, replace the model with either `vgg16` or `resnet50`. | ||
```bash | ||
$eval_bounding_boxes.py [model] [attribution] | ||
``` | ||
|
||
For the degradation task, you also have specify the tile size. In the paper, we | ||
used `8` and `14`. | ||
```bash | ||
$ eval_degradation.py [model] [tile size] [attribution] | ||
``` | ||
|
||
The results on sensitivity-n can be calculated with: | ||
```bash | ||
eval_sensitivity_n.py [model] [tile size] [attribution] | ||
``` | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from attribution_bottleneck.attribution.guided_backprop import GuidedBackprop | ||
from attribution_bottleneck.attribution.misc import Random | ||
from attribution_bottleneck.attribution.averaging_gradient import IntegratedGradients, SmoothGrad | ||
from attribution_bottleneck.attribution.backprop import Gradient, Saliency | ||
from attribution_bottleneck.attribution.grad_cam import GradCAM, GuidedGradCAM | ||
from attribution_bottleneck.attribution.lrp.lrp import LRP | ||
from attribution_bottleneck.attribution.occlusion import Occlusion | ||
from attribution_bottleneck.attribution.per_sample_bottleneck import PerSampleBottleneckReader | ||
|
||
__all__ = [ | ||
"Random", | ||
"GradCAM", | ||
"Gradient", | ||
"Saliency", | ||
"Occlusion", | ||
"IntegratedGradients", | ||
"SmoothGrad", | ||
"LRP", | ||
"GuidedBackprop", | ||
"GuidedGradCAM", | ||
"PerSampleBottleneckReader", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
from __future__ import print_function | ||
|
||
import torch.autograd | ||
from attribution_bottleneck.attribution.base import AttributionMethod | ||
|
||
# from ..attribution.base import AttributionMethod | ||
from attribution_bottleneck.attribution.backprop import ModifiedBackpropMethod | ||
from tqdm import tqdm | ||
|
||
|
||
from ..utils.baselines import Mean, Baseline | ||
from ..utils.misc import * | ||
|
||
|
||
class AveragingGradient(AttributionMethod): | ||
""" | ||
Something than can make a attribution heatmap from several inputs and a backpropagating attribution method. | ||
The resulting heatmap is the sum of all the other methods. | ||
""" | ||
def __init__(self, backprop: ModifiedBackpropMethod): | ||
super().__init__() | ||
self.verbose = False | ||
self.progbar = False | ||
self.backprop = backprop | ||
|
||
def heatmap(self, input_t, target): | ||
# generate sample list (different per method) | ||
images = self._get_samples(input_t) | ||
target_t = target if isinstance(target, torch.Tensor) else torch.tensor(target, device=input_t.device) | ||
assert isinstance(target_t, torch.Tensor) | ||
assert len(images[0].shape) == 4, "{} makes dim {} !".format(images[0].shape, len(images[0].shape)) # C x N x N | ||
|
||
grads = self._backpropagate_multiple(images, target_t) | ||
|
||
# Reduce sample dimension | ||
grads_mean = np.mean(grads, axis=0) | ||
# Reduce batch dimension | ||
grads_rgb = np.mean(grads_mean, axis=0) | ||
# Reduce color dimension | ||
heatmap = np.mean(grads_rgb, axis=0) | ||
return heatmap | ||
|
||
def _backpropagate_multiple(self, inputs: list, target_t: torch.Tensor): | ||
""" | ||
returns an array with all the computed gradients | ||
shape: N_Inputs x Batches=1 x Color Channels x Height x Width | ||
""" | ||
# Preallocate empty gradient stack | ||
grads = np.zeros((len(inputs), *inputs[0].shape)) | ||
# Generate gradients | ||
iterator = tqdm(range(len(inputs)), ncols=100, desc="calc grad") if len(inputs) > 1 and self.progbar else range(len(inputs)) | ||
for i in iterator: | ||
grad = self.backprop._calc_gradient(input_t=inputs[i], target_t=target_t) | ||
# If required, add color dimension | ||
if len(grad.shape) == 3: | ||
np.expand_dims(grad, axis=0) | ||
grads[i, :, :, :, :] = grad | ||
|
||
return grads | ||
|
||
def _get_samples(self, img_t: torch.Tensor) -> list: | ||
""" yield the samples to analyse """ | ||
raise NotImplementedError | ||
|
||
|
||
class SmoothGrad(AveragingGradient): | ||
def __init__(self, backprop: ModifiedBackpropMethod, std=0.15, steps=50): | ||
super().__init__(backprop=backprop) | ||
self.std = std | ||
self.steps = steps | ||
|
||
def _get_samples(self, img_t: torch.Tensor) -> list: | ||
relative_std = (img_t.max().item() - img_t.min().item()) * self.std | ||
noises = [torch.randn(*img_t.shape).to(img_t.device) * relative_std for _ in range(0, self.steps)] | ||
noise_images = [img_t + noises[i] for i in range(0, self.steps)] | ||
return noise_images | ||
|
||
|
||
class IntegratedGradients(AveragingGradient): | ||
|
||
def __init__(self, backprop: ModifiedBackpropMethod, baseline: Baseline = None, steps=50): | ||
""" | ||
:param baseline: start point for interpolation (0-1 grey, or "inv", or "avg") | ||
:param steps: resolution | ||
""" | ||
super().__init__(backprop=backprop) | ||
self.baseline = baseline if baseline is not None else Mean() | ||
self.steps = steps | ||
|
||
def _get_samples(self, img_t: torch.Tensor) -> np.array: | ||
bl_img = self.baseline.apply(to_np_img(img_t)) | ||
bl_img_t = to_img_tensor(bl_img, device=img_t.device) | ||
return [((i / self.steps) * (img_t - bl_img_t) + bl_img_t) for i in range(1, self.steps + 1)] | ||
|
Oops, something went wrong.