Skip to content

Commit 92c91b2

Browse files
committed
add integrated gradients attribution method
1 parent e104f29 commit 92c91b2

2 files changed

Lines changed: 43 additions & 23 deletions

File tree

src/tpcav/tpcav_model.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import numpy as np
77
import torch
8-
from captum.attr import DeepLift
8+
from captum.attr import DeepLift, IntegratedGradients
99
from scipy.linalg import svd
1010

1111
logger = 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

test/test_cav_trainer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def pack_data_iters(df):
340340
return zip(
341341
seq_one_hot_iter,
342342
)
343-
343+
tpcav_model.set_attr_method("intgrad")
344344
attributions = tpcav_model.layer_attributions(
345345
pack_data_iters(random_regions_1), pack_data_iters(random_regions_2)
346346
).cpu()

0 commit comments

Comments
 (0)