Integrated Gradients¶
Deep LVPM includes an integrated gradients helper for feature attribution on
trained deep_lvpm.model.StructuralModel instances. It explains one
DLV dimension at a time by measuring how each input feature contributes to the
selected DLV-specific structural loss as the input moves from a baseline value
to the observed value.
The implementation lives in deep_lvpm.integrated_gradients:
from deep_lvpm.integrated_gradients import calculate_integrated_gradients
attributions = calculate_integrated_gradients(
structural_model,
data,
baseline=0.0,
dlv_index=0,
steps=50,
)
data should be the same flat list of arrays or tensors that you pass to
StructuralModel.fit(), StructuralModel.evaluate(), or
StructuralModel.predict(). The returned attribution values have the same
structure and shapes as data.
Function Arguments¶
structural_modelA trained
StructuralModel.dataInput arrays or tensors in model-input order. Inputs must be floating point. Missing all-
NaNview rows are handled in the same way as the model handles missing views during evaluation.baselineA scalar baseline such as
0.0or a list of baseline arrays matching the input list. Per-view baselines can either have the same shape as the input data or broadcast to that shape. For tabular data, a one-dimensional feature baseline vector is often the most convenient form.dlv_indexZero-based DLV dimension to explain.
dlv_index=0explains DLV1.stepsNumber of interpolation steps between the baseline and observed data. Larger values give a more accurate approximation and take longer to run.
explain_loss_reductionIf
Trueby default, positive attribution means the feature helps reduce the selected DLV structural loss relative to the baseline. Set this toFalseto attribute the loss itself.return_numpyIf
Trueby default, returns NumPy arrays. Set this toFalseto keep PyTorch tensors.
Choosing Baselines¶
Baselines should be defined on the same preprocessed scale as the model inputs. There is no single best baseline for every data type, but the following choices are often practical:
Use a zero baseline for binary or presence/absence features when zero means absence. The TCGA survival example uses zero baselines for CNV and SNV.
Use a feature-wise mean baseline for continuous views when the question is how each subject deviates from an average subject. The TCGA survival example uses mean vectors across subjects for RNA-seq, miRNA-seq, and methylation.
Avoid baselines that are outside the preprocessing distribution seen by the model unless that contrast is intentional.
Aggregating Feature Importance¶
Integrated gradients are returned per subject and per feature. For factor interpretation, a feature can push a DLV upward in one subject and downward in another. If you average signed values directly, important features can cancel out:
mean_signed_ig = np.nanmean(view_attributions, axis=0)
For ranking loci or features that define a DLV factor, rank by the mean absolute attribution across subjects:
mean_abs_ig = np.nanmean(np.abs(view_attributions), axis=0)
top_indices = np.argsort(mean_abs_ig)[-10:][::-1]
It is still useful to keep the signed mean alongside the absolute importance, because the signed mean shows whether the attribution is directionally biased across the cohort:
summary = pd.DataFrame({
"feature_id": [feature_names[i] for i in top_indices],
"mean_abs_integrated_gradient": mean_abs_ig[top_indices],
"mean_signed_integrated_gradient": mean_signed_ig[top_indices],
})
Example With Multiple Omics Views¶
This example explains DLV1 for a trained five-view TCGA-style model. CNV and SNV use zero baselines, while continuous views use feature-wise means.
import numpy as np
import pandas as pd
from deep_lvpm.integrated_gradients import calculate_integrated_gradients
view_names = [
"rnaseq_gene",
"mirna_gene",
"methylation_gene",
"cnv_gene",
"mutation_gene",
]
data = [rnaseq, mirnaseq, methylation, cnv, snv]
baselines = [
rnaseq.mean(axis=0).astype("float32"),
mirnaseq.mean(axis=0).astype("float32"),
methylation.mean(axis=0).astype("float32"),
np.zeros(cnv.shape[1], dtype="float32"),
np.zeros(snv.shape[1], dtype="float32"),
]
attributions = calculate_integrated_gradients(
structural_model,
data,
baseline=baselines,
dlv_index=0,
steps=50,
)
rows = []
for view_name, view_attributions, feature_names in zip(
view_names,
attributions,
feature_name_lists,
):
mean_abs_ig = np.nanmean(np.abs(view_attributions), axis=0)
mean_signed_ig = np.nanmean(view_attributions, axis=0)
top_indices = np.argsort(mean_abs_ig)[-10:][::-1]
for rank, feature_index in enumerate(top_indices, start=1):
rows.append({
"view": view_name,
"rank": rank,
"feature_id": feature_names[feature_index],
"feature_column": int(feature_index),
"mean_abs_integrated_gradient": float(mean_abs_ig[feature_index]),
"mean_signed_integrated_gradient": float(mean_signed_ig[feature_index]),
})
top_loci = pd.DataFrame(rows)
The TCGA survival tutorial uses this pattern after fitting the DLVPM survival model and writes one top-loci table per DLV.
Practical Notes¶
Integrated gradients are an evaluation-time attribution method. Train the
StructuralModelfirst, then calculate attributions.Runtime scales with the number of views, samples, features, DLV dimensions, and interpolation
steps. Use a smallstepsvalue for smoke tests and increase it for final analyses.The helper snapshots and restores the model state while it runs, because DLVPM projection layers keep moving statistics.
Always interpret attributions in the context of the chosen baseline and the preprocessing applied before model training.