55
66import numpy as np
77import torch
8- from captum .attr import DeepLift
8+ from captum .attr import DeepLift , IntegratedGradients
99from scipy .linalg import svd
1010
1111logger = logging .getLogger (__name__ )
@@ -31,6 +31,7 @@ def __init__(
3131 device : Optional [str ] = None ,
3232 layer_name : Optional [str ] = None ,
3333 layer : Optional [torch .nn .Module ] = None ,
34+ attr_method = 'deeplift'
3435 ) -> None :
3536 """
3637 layer_name: optional module name to intercept activations via forward hook
@@ -53,6 +54,11 @@ def __init__(
5354 raise Exception (
5455 "You have to specify either layer or layer_name to construct TPCAV model"
5556 )
57+ self .set_attr_method (attr_method )
58+
59+ def set_attr_method (self , attr_method = "deeplift" ):
60+ assert attr_method in ["deeplift" , "intgrad" ]
61+ self .attr = DeepLift if attr_method == 'deeplift' else IntegratedGradients
5662
5763 def list_module_names (self ) -> List [str ]:
5864 """List all module names in the model for layer selection."""
@@ -247,7 +253,7 @@ def layer_attributions(
247253 abs_inputs_diff : bool = True ,
248254 ) -> torch .Tensor :
249255 """
250- Compute DeepLift attributions on PCA embedding space.
256+ Compute attributions on PCA embedding space.
251257
252258 By default, it computes (input - baseline).abs() * multiplier to avoid double-sign effects (abs_inputs_diff=True).
253259
@@ -256,9 +262,7 @@ def layer_attributions(
256262 if not self .fitted :
257263 raise RuntimeError ("Call fit_pca before attributing." )
258264 self .forward = self .forward_from_embeddings_at_layer
259- deeplift = DeepLift (self , multiply_by_inputs = multiply_by_inputs )
260-
261- custom_attr_func = _abs_attribution_func if abs_inputs_diff else None
265+ model_attr = self .attr (self , multiply_by_inputs = False )
262266
263267 attributions = []
264268 for inputs , binputs in zip (target_batches , baseline_batches ):
@@ -273,7 +277,7 @@ def layer_attributions(
273277 if avs_projected is not None :
274278 avs_projected = avs_projected .detach ()
275279 bavs_projected = bavs_projected .detach ()
276- attribution = deeplift .attribute (
280+ attribution = model_attr .attribute (
277281 (avs_residual .to (self .device ), avs_projected .to (self .device )),
278282 baselines = (
279283 bavs_residual .to (self .device ),
@@ -282,24 +286,29 @@ def layer_attributions(
282286 additional_forward_args = (
283287 [torch .cat ([i , bi ]) for i , bi in zip (inputs , binputs )],
284288 ),
285- custom_attribution_func = (
286- None if not multiply_by_inputs else custom_attr_func
287- ),
288289 )
289290 attr_residual , attr_projected = attribution
291+ diff_residual = avs_residual - bavs_residual
292+ diff_projected = avs_projected - bavs_projected
293+
290294 attribution = torch .cat ((attr_projected , attr_residual ), dim = 1 )
295+ difference = torch .cat ((diff_projected , diff_residual ), dim = 1 )
291296 else :
292- attribution = deeplift .attribute (
297+ attribution = model_attr .attribute (
293298 (avs_residual .to (self .device ),),
294299 baselines = (bavs_residual .to (self .device ),),
295300 additional_forward_args = (
296301 None ,
297302 [torch .cat ([i , bi ]) for i , bi in zip (inputs , binputs )],
298303 ),
299- custom_attribution_func = (
300- None if not multiply_by_inputs else custom_attr_func
301- ),
302304 )[0 ]
305+ difference = avs_residual - bavs_residual
306+
307+ if multiply_by_inputs :
308+ if abs_inputs_diff :
309+ attribution = attribution * torch .abs (difference )
310+ else :
311+ attribution = attribution * difference
303312
304313 attributions .append (attribution .detach ().cpu ())
305314
@@ -323,9 +332,9 @@ def input_attributions(
323332 multiply_by_inputs : bool = True ,
324333 cavs_list : Optional [List [torch .Tensor ]] = None ,
325334 ) -> List [torch .Tensor ]:
326- """Compute DeepLift attributions on PCA embedding space.
335+ """Compute attributions on PCA embedding space.
327336
328- target_batches and baseline_batches should yield (seq, chrom) pairs of matching length.
337+ target_batches and baseline_batches should yield tupel of inputs of matching length.
329338 """
330339 if not self .fitted :
331340 raise RuntimeError ("Call fit_pca before attributing." )
@@ -336,19 +345,30 @@ def input_attributions(
336345 mute_x_avs = False ,
337346 mute_remainder = True ,
338347 )
339- deeplift = DeepLift (self , multiply_by_inputs = multiply_by_inputs )
348+ model_attr = self . attr (self , multiply_by_inputs = False )
340349
341350 attributions = []
342351 for inputs , binputs in zip (target_batches , baseline_batches ):
343- attribution = deeplift .attribute (
352+ attribution = model_attr .attribute (
344353 tuple ([i .to (self .device ) for i in inputs ]),
345354 baselines = tuple ([bi .to (self .device ) for bi in binputs ]),
346355 )
347- attributions .append (
348- [a .detach ().cpu () for a in attribution ]
349- if isinstance (attribution , tuple )
350- else attribution .detach ().cpu ()
351- )
356+ difference = []
357+ for i , bi in zip (inputs , binputs ):
358+ difference .append (i - bi )
359+
360+ if multiply_by_inputs :
361+ if isinstance (attribution , tuple ):
362+ attributions .append ([a .detach ().cpu () * d for a , d in zip (attribution , difference )])
363+ else :
364+ assert len (difference ) == 1
365+ attributions .append (attribution .detach ().cpu * difference [0 ])
366+ else :
367+ attributions .append (
368+ [a .detach ().cpu () for a in attribution ]
369+ if isinstance (attribution , tuple )
370+ else attribution .detach ().cpu ()
371+ )
352372
353373 return [torch .cat (z ) for z in zip (* attributions )]
354374
0 commit comments