physioex.explain.lrp#
Layer-wise Relevance Propagation (LRP) for PhysioEx.
Conservation-based attribution (Bach et al. 2015). Two entry points:
LRP— Zennit composites for feed-forward / CNN models (ε, γ, w², z-box, flat; BatchNorm canonization).ModelLRP— whole-model LRP for architectures with LSTM/GRU (Arras signal-take), attention / transformers (CP-LRP, Ali et al. 2022) and PhysioEx’s softmax poolings; leaves get ε-LRP, norms/activations the identity rule, residual+the proportional rule.
Both seed the target logit so Σ R ≈ f_c(x); use return_report=True /
check_conservation() to see how much biases absorb, and
audit_lrp_coverage() to list blocks left on plain autograd.
Requires the optional explain extra:
pip install "physioex[explain]"
- class physioex.explain.lrp.ConservationReport(target, relevance_sum, ratio, absorbed)[source]#
Bases:
objectPer-sample conservation summary.
- target#
the explained logit
f_c(x)per sample,(B,).- Type:
- relevance_sum#
Σ Rover all input elements per sample,(B,).- Type:
- ratio#
Σ R / f(1.0 = exact conservation).- Type:
- absorbed#
f − Σ R— relevance absorbed by biases / unruled ops.- Type:
- class physioex.explain.lrp.EpsilonRule(module, epsilon=1e-06)[source]#
Bases:
_RuleWrapperε-LRP wrapper for a single-input module linear in its input.
- Parameters:
module (nn.Module)
epsilon (float)
- forward(x)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.IdentityRule(module)[source]#
Bases:
_RuleWrapperIdentity-rule wrapper: forward is the module’s, backward passes relevance.
- Parameters:
module (nn.Module)
- forward(x, *args, **kwargs)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRP(model, in_index=0, out_index=0, composite=None, canonizers=None)[source]#
Bases:
ModuleLRP attribution over a trained feed-forward PhysioEx model (Zennit).
- Parameters:
model – trained model,
(B, L, C, T) -> (B, L, n_classes)or(B, n_classes). Evaluated ineval()mode.in_index – sequence epoch to explain (central-epoch models emit
L=1).out_index – class-logit index to explain.
composite – a Zennit composite; default
physioex_composite()(ε dense / γ conv / w² first layer) with the BatchNorm canonizer.canonizers – used only when
compositeisNone.
Example
>>> relevance = LRP(model, out_index=2)(x) >>> relevance, report = LRP(model, out_index=2)(x, return_report=True)
- forward(x, return_report=False)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPAttentionLayer(orig, epsilon=1e-06)[source]#
Bases:
_PoolAdapterCP-LRP for
seqsleepnet.AttentionLayer(additive pooling, manual softmax).- Parameters:
orig (nn.Module)
epsilon (float)
- forward(x, r_alphas=False)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.- Parameters:
r_alphas (bool)
- class physioex.explain.lrp.LRPAttentionPooling(orig, epsilon=1e-06)[source]#
Bases:
_PoolAdapterCP-LRP for
sleeptransformer.AttentionPooling(self.attentionMLP, softmax overdim=1,(B,T,D) -> (B,D)).- Parameters:
orig (nn.Module)
epsilon (float)
- forward(x)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPChannelMixer(orig, epsilon=1e-06)[source]#
Bases:
_PoolAdapterCP-LRP for
protosleepnet.ChannelMixer: constant modality embedding → per-channelmcylogits → dropout (eval) → residualx + mixer(x)(proportional split; the mixer itself is swapped byprepare) → softmax channel pooling (CP-LRP).forward(x, zero_emb) -> (h, mcy_logits).- Parameters:
orig (nn.Module)
epsilon (float)
- forward(x, zero_emb)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPGRU(input_size: int, hidden_size: int, num_layers: int = 1, bias: bool = True, batch_first: bool = False, dropout: float = 0.0, bidirectional: bool = False, device=None, dtype=None)[source]#
- class physioex.explain.lrp.LRPGRU(*args, **kwargs)
Bases:
GRUCell-level GRU carrying LRP relevance; drop-in for a trained
nn.GRU.forward(x)returns(output, h_n)likenn.GRU. PyTorch gate order is[r, z, n]withn = tanh(W_in x + b_in + r ⊙ (W_hn h + b_hn))andh' = (1−z)⊙n + z⊙h; sources aren,hand the recurrent pre-activation gated byr.- forward(x, hx=None)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPLSTM(input_size: int, hidden_size: int, num_layers: int = 1, bias: bool = True, batch_first: bool = False, dropout: float = 0.0, bidirectional: bool = False, proj_size: int = 0, device=None, dtype=None)[source]#
- class physioex.explain.lrp.LRPLSTM(*args, **kwargs)
Bases:
LSTMCell-level LSTM carrying LRP relevance; drop-in for a trained
nn.LSTM.Build with
from_torch().forward(x)returns(output, (h_n, c_n))likenn.LSTM; an explicit initial statehxis not supported (raises),dropoutis ignored (eval-time attribution).- forward(x, hx=None)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPMultiheadAttention(embed_dim, num_heads, epsilon=1e-06)[source]#
Bases:
ModuleLRP-instrumented multi-head attention (self or cross), value-path CP-LRP.
Reproduces
nn.MultiheadAttention(batch_first=True, same q/k/v embed dims,need_weights=False).forward(query, key, value)returns the attention output tensor.- forward(query, key, value)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPMultiheadAttentionModule(attn)[source]#
Bases:
Modulenn.MultiheadAttention-compatible adapter for standalone attention.Host models call
out, w = mha(query=, key=, value=, ...). Returns(out, weights_or_None); raises on attention masks (unsupported) instead of silently ignoring them.- Parameters:
attn (LRPMultiheadAttention)
- forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None, average_attn_weights=True, is_causal=False)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPTransformerEncoder(layers, norm=None)[source]#
Bases:
ModuleLRP replacement for
nn.TransformerEncoder(the container’s own forward introspectslayers[0].self_attn.batch_first, so the whole stack is replaced). Applies the LRP layers in turn, then the optional final norm.- forward(x, mask=None, src_key_padding_mask=None, is_causal=None)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.LRPTransformerEncoderLayer(epsilon=1e-06)[source]#
Bases:
ModuleLRP-instrumented
nn.TransformerEncoderLayer(post- and pre-norm).Build with
from_torch(). Self-attention only;src_mask/src_key_padding_maskare not supported (raise).- Parameters:
epsilon (float)
- forward(x, src_mask=None, src_key_padding_mask=None, is_causal=False)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class physioex.explain.lrp.ModelLRP(model, in_index=0, out_index=0, output_key=None, epsilon=1e-06, patch_residuals=True, strict=False, copy_model=True)[source]#
Bases:
ModuleLRP attribution for a whole PhysioEx model.
- Parameters:
model (nn.Module) – trained model
(B, L, C, T[, F]) -> (B, L, n_classes)(or a dict — setoutput_key). Deep-copied; the original is untouched.out_index (int) – sequence epoch and class logit to explain.
output_key (Optional[str]) – for dict outputs (CoReSleep →
"combined").epsilon (float) – ε of every ε-rule in the prepared model.
patch_residuals (bool) – redirect plain
+between grad-carrying tensors in the model’s own forward to the proportional rule (recommended).strict (bool) – raise if a parametric leaf is left without an LRP rule.
copy_model (bool) – set
Falseto preparemodelin place (saves memory).in_index (int)
out_index
forward(x)returns relevance shaped likex(seeded with the target logit soΣ R ≈ f_c(x), minus what biases absorb);forward(x, return_report=True)also returns aConservationReport.- forward(x, return_report=False)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- physioex.explain.lrp.audit_lrp_coverage(model, include_containers=False)[source]#
Names of parametric leaf modules that carry no LRP rule (their forward runs on plain autograd and propagates gradient, not relevance).
Container modules that own parameters and have children (e.g. a learned CLS token or positional embedding added to the stream) are additive constants w.r.t. the input and are only listed with
include_containers=Truefor manual inspection.
- physioex.explain.lrp.check_conservation(explainer, x)[source]#
Run
explainer(anModelLRPorLRP) onxand report.- Parameters:
x (Tensor)
- Return type:
- physioex.explain.lrp.cp_weighted_pool(x, weights, epsilon=1e-06)[source]#
Weighted sum over
dim=1with the CP-LRP backward;weightsis(B, T, 1)and must be detached (constant).
- physioex.explain.lrp.default_canonizers(model=None)[source]#
Return the default canonizer list for a PhysioEx model.
Currently merges sequential
BatchNormlayers into their neighbouring linear / convolution modules — needed by e.g.tinysleepnet,sleepfmandtfc. Models without BatchNorm are unaffected (the canonizer simply finds nothing to merge).- Parameters:
model – unused for now; accepted so callers can pass the model and we can later dispatch model-specific canonizers (e.g. GroupNorm in
neurolmor theprotosleepnetprototype head).- Return type:
- physioex.explain.lrp.epsilon_composite(epsilon=1e-06, canonizers=None)[source]#
A pure ε-LRP composite (ε on every linear/conv layer).
Unlike
physioex_composite(), this uses no special first-layer rule, so it approximately conserves relevance (Σ R ≈ f(x)) — useful as the reference for the conservation sanity check in the test suite.
- physioex.explain.lrp.physioex_composite(first_rule='w2', epsilon=1e-06, gamma=0.25, zbox_low=-3.0, zbox_high=3.0, canonizers=None)[source]#
Build the default PhysioEx LRP composite (ε dense / γ conv / w² first).
- Parameters:
first_rule (str) – input-layer rule —
"w2"(default, unbounded signals),"zbox"(bounded inputs; needszbox_low/zbox_high) or"flat"(uniform baseline).epsilon (float) – stabiliser for the ε-rule on dense layers.
gamma (float) – positive-weight amplification for the γ-rule on conv layers.
zbox_low (float) – bounds for the
zboxfirst-layer rule.zbox_high (float) – bounds for the
zboxfirst-layer rule.canonizers (List | None) – list of Zennit canonizers to apply (e.g. from
physioex.explain.lrp.canonizers.default_canonizers()).
- Returns:
A
zennit.composites.SpecialFirstLayerMapComposite.
- physioex.explain.lrp.prepare_model_for_lrp(model, epsilon=1e-06, strict=False, _memo=None)[source]#
In-place: swap fused / attention / registered blocks for their LRP counterparts and wrap the remaining leaves with LRP rules. Returns
model. Call on a copy (seeModelLRP).strict=Trueraises if any parametric leaf is left on plain autograd. Shared modules (the same object under several attributes) are replaced by a single shared LRP module.
- physioex.explain.lrp.register_lrp_adapter(cls, factory)[source]#
Register
factory(module, epsilon) -> nn.Modulefor instances ofcls.Use for custom blocks whose forward contains softmax pooling / attention or other non-module ops that plain autograd would attribute as gradient.
- physioex.explain.lrp.st_identity(pre, act)[source]#
Straight-through nonlinearity (identity rule): value
act, relevance →pre.
- physioex.explain.lrp.swap_transformer_layers(module, epsilon=1e-06)[source]#
In-place: replace
nn.TransformerEncoder/nn.TransformerEncoderLayerchildren with their LRP counterparts (weights copied; the source leaves are not modified). Returnsmodule. Preferprepare_model_for_lrp().
- physioex.explain.lrp.target_seed(out, in_index, out_index)[source]#
Relevance seed selecting the target neuron with its logit value.
Seeding with
f_c(x)(not a bare1.0) makes total relevance conserve to the class evidence,Σ R ≈ f_c(x). Handles(B, L, n_classes)sequence outputs and(B, n_classes). Returns(seed, target_values).