2828import torch
2929import warnings
3030from contextlib import contextmanager
31- from skimage .transform import resize
3231from 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+
6994class _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+
183220class 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