Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
attribution-bottleneck committed Sep 25, 2019
0 parents commit f22299f
Show file tree
Hide file tree
Showing 43 changed files with 6,320 additions and 0 deletions.
114 changes: 114 additions & 0 deletions .gitignore
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/
69 changes: 69 additions & 0 deletions README.md
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.
22 changes: 22 additions & 0 deletions attribution_bottleneck/attribution/__init__.py
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",
]
94 changes: 94 additions & 0 deletions attribution_bottleneck/attribution/averaging_gradient.py
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)]

Loading

0 comments on commit f22299f

Please sign in to comment.