Skip to content

Commit 34baed6

Browse files
authored
Merge pull request #17 from BioroboticsLab/improve-readme
Improve Readme, fixed tf bug
2 parents 49c01fa + 25ff92f commit 34baed6

18 files changed

Lines changed: 2357 additions & 124 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
weights/
22
data/imagenet/
3+
test/notebooks/asserts/
34
data/imagenet
45
pretrained/
56
results/
@@ -10,6 +11,7 @@ cov_html/
1011
notebooks/imagenet
1112
notebooks/check_sha256sum.txt
1213
*.h5
14+
*.npz
1315

1416
# Byte-compiled / optimized / DLL files
1517
__pycache__/

IBA/pytorch.py

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@
2828
import torch
2929
import warnings
3030
from contextlib import contextmanager
31-
from skimage.transform import resize
3231
from torchvision.transforms import Normalize, Compose
33-
from IBA.utils import _to_saliency_map, get_tqdm
32+
from IBA.utils import _to_saliency_map, get_tqdm, ifnone
3433

3534
# Helper Functions
3635

@@ -66,6 +65,32 @@ def tensor_to_np_img(img_t):
6665
])(img_t).detach().cpu().numpy().transpose(1, 2, 0)
6766

6867

68+
def imagenet_transform(resize=256, crop_size=224):
69+
"""Returns the default torchvision imagenet transform. """
70+
from torchvision.transforms import Compose, CenterCrop, ToTensor, Resize, Normalize
71+
return Compose([
72+
Resize(resize),
73+
CenterCrop(crop_size),
74+
ToTensor(),
75+
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
76+
])
77+
78+
79+
def get_imagenet_folder(path, image_size=224, transform='default'):
80+
"""
81+
Returns a ``torchvision.datasets.ImageFolder`` with the default
82+
torchvision preprocessing.
83+
"""
84+
from torchvision.datasets import ImageFolder
85+
from torchvision.transforms import Compose, CenterCrop, ToTensor, Resize, Normalize
86+
if transform == 'default':
87+
transform = Compose([
88+
CenterCrop(256), Resize(image_size), ToTensor(),
89+
Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
90+
])
91+
return ImageFolder(path, transform=transform)
92+
93+
6994
class _SpatialGaussianKernel(nn.Module):
7095
""" A simple convolutional layer with fixed gaussian kernels, used to smoothen the input """
7196
def __init__(self, kernel_size, sigma, channels,):
@@ -180,6 +205,18 @@ class _InterruptExecution(Exception):
180205
pass
181206

182207

208+
class _IBAForwardHook:
209+
def __init__(self, iba, input_or_output="output"):
210+
self.iba = iba
211+
self.input_or_output = input_or_output
212+
213+
def __call__(self, m, inputs, outputs):
214+
if self.input_or_output == "input":
215+
return self.iba(inputs)
216+
elif self.input_or_output == "output":
217+
return self.iba(outputs)
218+
219+
183220
class IBA(nn.Module):
184221
"""
185222
IBA finds relevant features of your model by applying noise to
@@ -214,6 +251,7 @@ class IBA(nn.Module):
214251
over very few iterations, a relatively high learning rate
215252
can be used compared to the training of the model itself.
216253
batch_size: Number of samples to use per iteration
254+
input_or_output: Select either ``"output"`` or ``"input"``.
217255
initial_alpha: Initial value for the parameter.
218256
"""
219257
def __init__(self,
@@ -230,6 +268,7 @@ def __init__(self,
230268
feature_std=None,
231269
estimator=None,
232270
progbar=False,
271+
input_or_output="output",
233272
relu=False):
234273
super().__init__()
235274
self.relu = relu
@@ -244,7 +283,7 @@ def __init__(self,
244283
self.sigmoid = nn.Sigmoid()
245284
self._buffer_capacity = None # Filled on forward pass, used for loss
246285
self.sigma = sigma
247-
self.estimator = estimator or TorchWelfordEstimator()
286+
self.estimator = ifnone(estimator, TorchWelfordEstimator())
248287
self.device = None
249288
self._estimate = False
250289
self._mean = feature_mean
@@ -269,8 +308,16 @@ def __init__(self,
269308
finally:
270309
pass # Do not complain if packaging is not installed
271310

311+
# self._hook_handle = layer.register_forward_hook(lambda m, x, y: self(y))
312+
313+
# for handle, hooks in layer._forward_hooks.items():
314+
# if type(hooks) == _IBAForwardHook:
315+
# raise ValueError("Another IBA object is already attacted to the layer. "
316+
# "Remove it by calling `detach()`")
317+
272318
# Attach the bottleneck after the model layer as forward hook
273-
self._hook_handle = layer.register_forward_hook(lambda m, x, y: self(y))
319+
self._hook_handle = layer.register_forward_hook(
320+
_IBAForwardHook(self, input_or_output))
274321

275322
else:
276323
pass
@@ -316,7 +363,7 @@ def forward(self, x):
316363
We use it also to estimate the distribution of `x` passing through the layer.
317364
"""
318365
if self._restrict_flow:
319-
return self._do_restrict_information(x, self.alpha)
366+
return self._do_restrict_information(x)
320367
if self._estimate:
321368
self.estimator(x)
322369
if self._interrupt_execution:
@@ -350,7 +397,19 @@ def _calc_capacity(mu, log_var):
350397
""" Return the feature-wise KL-divergence of p(z|x) and q(z) """
351398
return -0.5 * (1 + log_var - mu**2 - log_var.exp())
352399

353-
def _do_restrict_information(self, x, alpha):
400+
@staticmethod
401+
def _kl_div(r, lambda_, mean_r, std_r):
402+
r_norm = (r - mean_r) / std_r
403+
var_z = (1 - lambda_) ** 2
404+
405+
log_var_z = torch.log(var_z)
406+
407+
mu_z = r_norm * lambda_
408+
409+
capacity = -0.5 * (1 + log_var_z - mu_z**2 - var_z)
410+
return capacity
411+
412+
def _do_restrict_information(self, x):
354413
""" Selectively remove information from x by applying noise """
355414
if self.alpha is None:
356415
raise RuntimeWarning("Alpha not initialized. Run _init() before using the bottleneck.")
@@ -365,26 +424,19 @@ def _do_restrict_information(self, x, alpha):
365424
self._active_neurons = self.estimator.active_neurons()
366425

367426
# Smoothen and expand alpha on batch dimension
368-
lamb = self.sigmoid(alpha)
427+
lamb = self.sigmoid(self.alpha)
369428
lamb = lamb.expand(x.shape[0], x.shape[1], -1, -1)
370429
lamb = self.smooth(lamb) if self.smooth is not None else lamb
371430

372-
# Normalize x
373-
x_norm = (x - self._mean) / self._std
431+
self._buffer_capacity = self._kl_div(x, lamb, self._mean, self._std) * self._active_neurons
374432

375-
# Get sampling parameters
376-
var = (1 - lamb) ** 2
377-
log_var = torch.log(var)
378-
mu = x_norm * lamb
433+
eps = x.data.new(x.size()).normal_()
434+
ε = self._std * eps + self._mean
435+
λ = lamb
436+
z = λ * x + (1 - λ) * ε
437+
z *= self._active_neurons
379438

380439
# Sample new output values from p(z|x)
381-
eps = mu.data.new(mu.size()).normal_()
382-
z_norm = x_norm * lamb + (1-lamb) * eps
383-
self._buffer_capacity = self._calc_capacity(mu, log_var) * self._active_neurons
384-
385-
# Denormalize z to match original magnitude of x
386-
z = z_norm * self._std + self._mean
387-
z *= self._active_neurons
388440

389441
# Clamp output, if input was post-relu
390442
if self.relu:
@@ -502,12 +554,13 @@ def analyze(self, input_t, model_loss_fn, mode="saliency",
502554
"""
503555
assert input_t.shape[0] == 1, "We can only fit one sample a time"
504556

505-
beta = beta or self.beta
506-
optimization_steps = optimization_steps or self.optimization_steps
507-
min_std = min_std or self.min_std
508-
lr = lr or self.lr
509-
batch_size = batch_size or self.batch_size
510-
active_neurons_threshold = active_neurons_threshold or self.active_neurons_threshold
557+
# TODO: is None
558+
beta = ifnone(beta, self.beta)
559+
optimization_steps = ifnone(optimization_steps, self.optimization_steps)
560+
min_std = ifnone(min_std, self.min_std)
561+
lr = ifnone(lr, self.lr)
562+
batch_size = ifnone(batch_size, self.batch_size)
563+
active_neurons_threshold = ifnone(active_neurons_threshold, self._active_neurons_threshold)
511564

512565
batch = input_t.expand(batch_size, -1, -1, -1)
513566

@@ -523,6 +576,7 @@ def analyze(self, input_t, model_loss_fn, mode="saliency",
523576
self._std = torch.max(std, min_std*torch.ones_like(std))
524577

525578
self._loss = []
579+
self._alpha_grads = []
526580
self._model_loss = []
527581
self._information_loss = []
528582

@@ -545,6 +599,7 @@ def analyze(self, input_t, model_loss_fn, mode="saliency",
545599
loss.backward()
546600
optimizer.step()
547601

602+
self._alpha_grads.append(self.alpha.grad.cpu().numpy())
548603
self._loss.append(loss.item())
549604
self._model_loss.append(model_loss.item())
550605
self._information_loss.append(information_loss.item())

0 commit comments

Comments
 (0)