physioex 2.0.0__tar.gz → 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {physioex-2.0.0 → physioex-2.0.1}/.github/workflows/test.yml +4 -3
- physioex-2.0.1/CHANGELOG.md +61 -0
- {physioex-2.0.0 → physioex-2.0.1}/PKG-INFO +11 -3
- {physioex-2.0.0 → physioex-2.0.1}/README.md +7 -1
- {physioex-2.0.0 → physioex-2.0.1}/docs/api/index.md +2 -1
- physioex-2.0.1/docs/pages/explain/explain.md +152 -0
- physioex-2.0.1/examples/explain/lrp_seqsleepnet_n3.py +187 -0
- physioex-2.0.1/physioex/__init__.py +1 -0
- physioex-2.0.1/physioex/explain/__init__.py +34 -0
- physioex-2.0.1/physioex/explain/lrp/__init__.py +89 -0
- physioex-2.0.1/physioex/explain/lrp/_functional.py +167 -0
- physioex-2.0.1/physioex/explain/lrp/_rules.py +70 -0
- physioex-2.0.1/physioex/explain/lrp/attributor.py +78 -0
- physioex-2.0.1/physioex/explain/lrp/canonizers.py +37 -0
- physioex-2.0.1/physioex/explain/lrp/composites.py +106 -0
- physioex-2.0.1/physioex/explain/lrp/diagnostics.py +61 -0
- physioex-2.0.1/physioex/explain/lrp/model.py +412 -0
- physioex-2.0.1/physioex/explain/lrp/pooling.py +116 -0
- physioex-2.0.1/physioex/explain/lrp/recurrent.py +257 -0
- physioex-2.0.1/physioex/explain/lrp/transformer.py +284 -0
- {physioex-2.0.0 → physioex-2.0.1}/pyproject.toml +3 -0
- physioex-2.0.1/tests/explain/lrp/test_functional.py +98 -0
- physioex-2.0.1/tests/explain/lrp/test_lrp.py +158 -0
- physioex-2.0.1/tests/explain/lrp/test_model.py +361 -0
- physioex-2.0.1/tests/explain/lrp/test_real_models.py +202 -0
- physioex-2.0.1/tests/explain/lrp/test_recurrent.py +170 -0
- physioex-2.0.1/tests/explain/lrp/test_transformer.py +194 -0
- physioex-2.0.0/docs/pages/explain/explain.md +0 -56
- physioex-2.0.0/physioex/__init__.py +0 -1
- {physioex-2.0.0 → physioex-2.0.1}/.claudeignore +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/.github/copilot-instructions.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/.github/workflows/black.yml +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/.github/workflows/ci.yml +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/.github/workflows/publish.yaml +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/.gitignore +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/LICENSE +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/MANIFEST.in +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/REPO_MAP.txt +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/_ext/related_works.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/_static/custom.css +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/assets/images/data/sequence_viz.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/assets/images/logo.psd +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/assets/images/logo.svg +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/assets/images/logo_bar.svg +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/conf.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/index.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/library/data.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/library/explain.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/library/models.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/library/overview.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/library/train.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/architecture/testing/overview.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/contribute.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/data.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/preprocess.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/train/cli.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/train/networks/chambon2018.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/train/networks.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/train/train.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/unavailable.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/pages/user_guide.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/related/_thumbs/_placeholder.svg +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/related/fetch_citations.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/related/index.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/docs/related/works.yaml +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/compare_strategies.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/extract_all_concepts.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/report.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/utils.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/analyze_csd_concepts/visualize_concepts.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/explain/conceptualspectraldecomposition/README.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/explain/conceptualspectraldecomposition/explain_csd.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/explain/conceptualspectraldecomposition/train_probe.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/explain/conceptualspectraldecomposition/utils.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/explain/conceptualspectraldecomposition/visualize.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/bendr/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/biot/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/cbramod/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/labram/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/neurolm/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/reve/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/sjepa/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/sleepfm/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/foundation/tfc/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/extract_embeddings.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/smoke.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/chambon2018/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/coresleep-kontras/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/coresleep-kontras/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/smoke.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/lseqsleepnet-phan/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/protosleepnet/README.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/protosleepnet/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/extract_embeddings.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/test.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/test_stages_sites.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/train_shhs.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/seqsleepnet-phan/train_shhs.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/extract_embeddings.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/smoke.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/test_pretrained.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/sleeptransformer-phan/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/extract_embeddings.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/smoke.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/test_pretrained.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tinysleepnet-supratak/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/extract_embeddings.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/quickstart.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/smoke.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/test_pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/examples/pretrained/tsinalis-2016/train.sbatch +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/notebook.ipynb +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_barplot.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell11_tiny_hypnogram.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_barplot.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell13_seq_hypnogram.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell17_ig_tiny.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell19_ig_seq.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell19b_attr_vs_psd.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell5_raw_n3_psd.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/outputs/cell7_spec_n3_psd.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/run_notebook.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/script.pdf +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/slides.pdf +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/2026-06-24_ugent_xai/util.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/README.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/assets/kuleuven_logo.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/assets/physioex_logo.png +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/lectures/assets/physioex_logo.svg +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/base.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/cache.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/collate.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datareader.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/dataset.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/_nsrr.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/_splits/shhs.json +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/alzheimers.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/channels_table.md +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/dcsm.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/hmc.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/hpap.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/mass.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/mesa.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/mros.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/parkinsons.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/shhs.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/sleepedf.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/stages.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/datasets/wsc.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/events.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/modality.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/multi.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/pipeline.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/presets.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/readers/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/readers/annotations.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/readers/edf.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/bandpass.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/highpass.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/identity.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/normalize.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/notch.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/resample.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/data/steps/spectrogram.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/csd.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/multichannel_sg.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/report.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/sleep_bands.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/foundational/specificity.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/filters.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/functionizer.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/gradients.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/metrics/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/spectralgradients.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/vidft.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/posthoc/vistdft.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/local.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/posthoc/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/posthoc/nmf.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/posthoc/utils.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/posthoc/vq.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/explain/prototypes/reconstruct.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/bendr.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/biot.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/cbramod.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/chambon2018.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/coresleep.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/embed.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/foundation_base.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/foundation_channel_mapping.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/foundation_checkpoints.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/foundation_datasets.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/foundation_preproc.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/labram.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/lseqsleepnet.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/neurolm.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/pretrained.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/protosleepnet.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/reve.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/seqsleepnet.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/sjepa.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/sleep_tokenizer.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/sleepfm.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/sleeptransformer.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/tfc.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/tinysleepnet.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/models/tsinalis.py +0 -0
- {physioex-2.0.0/physioex/explain → physioex-2.0.1/physioex/train}/__init__.py +0 -0
- {physioex-2.0.0/physioex/train → physioex-2.0.1/physioex/train/bin}/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/bin/_common.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/bin/finetune.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/bin/test.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/bin/train.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/logger.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/metrics.py +0 -0
- {physioex-2.0.0/physioex/train/bin → physioex-2.0.1/physioex/train/models}/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/models/check_table.csv +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/models/load.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/multidevicetrainer.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/progress.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/stats.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/physioex/train/trainer.py +0 -0
- {physioex-2.0.0/physioex/train/models → physioex-2.0.1/tests}/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/conftest.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/data/test_base_smoke.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/data/test_modality.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/data/test_multi.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/data/test_readers_annotations.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/foundational/test_sleep_bands.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/foundational/test_specificity.py +0 -0
- {physioex-2.0.0/tests → physioex-2.0.1/tests/explain/lrp}/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/test_gradients.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/test_metrics.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/test_spectralgradients.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/test_vidft.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/posthoc/test_vistdft.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/explain/prototypes/posthoc/test_nmf_vq.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/factories/__init__.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/factories/edf.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_architectures.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_foundation_base.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_foundation_encoders.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_foundation_preproc.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_pretrained_embed_helpers.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/models/test_sleep_tokenizer.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_alzheimers_parkinsons.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_api_surface.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_cache.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_cache_enabled.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_channel_resolution.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_cli_workflows.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_dataset_config.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_e2e_smoke.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_events.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_explain_fixes.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_label_hardening.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_load_model.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_mass_dataset.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_metrics_and_packaging.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_phase_b_datasets.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_phase_c_datasets.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_pipeline.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_presets.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_raw_dataset_integration.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_reading_modes.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_stages_dataset.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_trainer_enhancements.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_trainer_fixes.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_trainer_integration.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_wake_trimming.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/test_wsc_dataset.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/train/test_logger.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/train/test_progress.py +0 -0
- {physioex-2.0.0 → physioex-2.0.1}/tests/train/test_stats.py +0 -0
|
@@ -27,12 +27,13 @@ jobs:
|
|
|
27
27
|
# torchaudio must come from the same CPU index as torch, otherwise the
|
|
28
28
|
# transitively-installed (braindecode) torchaudio has a mismatched ABI
|
|
29
29
|
# and fails to load its compiled _torchaudio.so.
|
|
30
|
-
pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
|
|
30
|
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
|
31
31
|
|
|
32
|
-
- name: Install package (editable) with dev + legacy extras
|
|
32
|
+
- name: Install package (editable) with dev + legacy + explain extras
|
|
33
33
|
# Editable so coverage's source=["physioex"] measures the executed tree
|
|
34
34
|
# (a non-editable install runs the site-packages copy -> 0% reported).
|
|
35
|
-
run
|
|
35
|
+
# 'explain' pulls in zennit so the LRP tests run instead of skipping.
|
|
36
|
+
run: pip install -e .[dev,legacy,explain]
|
|
36
37
|
|
|
37
38
|
- name: Run test suite with coverage gate
|
|
38
39
|
env:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to PhysioEx are documented here. Versions follow the
|
|
4
|
+
`physioex.__version__` string (read dynamically by the build) and the `v*` git tags
|
|
5
|
+
that publish to PyPI.
|
|
6
|
+
|
|
7
|
+
## 2.0.1 — 2026-09-13
|
|
8
|
+
|
|
9
|
+
### Added — Layer-wise Relevance Propagation (`physioex.explain.lrp`)
|
|
10
|
+
|
|
11
|
+
A conservation-based attribution family (Bach et al. 2015) covering the whole
|
|
12
|
+
model zoo. Install with the new optional extra `pip install "physioex[explain]"`
|
|
13
|
+
(Zennit only; no LXT dependency).
|
|
14
|
+
|
|
15
|
+
- **Two entry points**, both seeding the target neuron with its logit so that
|
|
16
|
+
`Σ R ≈ f_c(x)`, both returning a per-sample `ConservationReport` with
|
|
17
|
+
`return_report=True`:
|
|
18
|
+
- `LRP` — Zennit composites for feed-forward / CNN models (Tsinalis,
|
|
19
|
+
Chambon2018): ε on dense, γ on convolutions, **w²** on the first layer
|
|
20
|
+
(unbounded EEG, not the pixel z-box), BatchNorm canonized; αβ / flat / z-box
|
|
21
|
+
configurable via `physioex_composite`.
|
|
22
|
+
- `ModelLRP` — whole-model LRP for recurrent, attention and transformer
|
|
23
|
+
architectures (TinySleepNet, SeqSleepNet, L-SeqSleepNet, SleepTransformer,
|
|
24
|
+
CoReSleep, ProtoSleepNet): **Arras signal-take** rule for LSTM/GRU at cell
|
|
25
|
+
level (`LRPLSTM`/`LRPGRU`, drop-in subclasses with a bit-identical forward,
|
|
26
|
+
`gate_rule="signal_take" | "uniform"`), **CP-LRP** for attention (value
|
|
27
|
+
path, exact conservation; Ali et al. 2022), identity rule for
|
|
28
|
+
LayerNorm/activations, proportional rule for residual sums, ε for linear
|
|
29
|
+
leaves and PhysioEx's learnable filterbank, CP-LRP adapters for the softmax
|
|
30
|
+
poolings (`AttentionPooling`, `AttentionLayer`, `ChannelMixer`).
|
|
31
|
+
- `prepare_model_for_lrp` rewrites a deep copy of the model; plain `+` residuals
|
|
32
|
+
written in a model's own `forward` are redirected to the proportional rule at
|
|
33
|
+
runtime (`patch_residuals`); `audit_lrp_coverage` / `strict=True` report any
|
|
34
|
+
parametric leaf left on plain autograd; `register_lrp_adapter` adds custom
|
|
35
|
+
blocks. Signed, dtype-safe stabiliser `z + ε·sign z` everywhere (fp16-safe).
|
|
36
|
+
- Docs: entry-point/rule matrix, how to read conservation and choose ε (ε must be
|
|
37
|
+
scaled to the activations: measured on the pretrained SeqSleepNet, 1e-6 inflates
|
|
38
|
+
relevance 2–3×, 1e-2 conserves), unsupported cases, and a literature note on
|
|
39
|
+
LRP vs path-based attribution (Deep Taylor, Integrated Gradients,
|
|
40
|
+
DeepLIFT/DeepSHAP). `physioex.explain.lrp` is in the API reference.
|
|
41
|
+
- Example: `examples/explain/lrp_seqsleepnet_n3.py` explains the N3 logit of the
|
|
42
|
+
pretrained `seqsleepnet-phan` on MASS training sequences with Saliency,
|
|
43
|
+
Input×Gradient, Integrated Gradients and LRP under different rule assignments.
|
|
44
|
+
- Tests: 91 new tests (`tests/explain/lrp/`), including forward-equivalence to
|
|
45
|
+
the fused modules, exact conservation on bias-free blocks and models, and all
|
|
46
|
+
six real architectures end-to-end with full rule coverage.
|
|
47
|
+
|
|
48
|
+
### Known limitations
|
|
49
|
+
|
|
50
|
+
CP-LRP assigns zero relevance to the attention query/key path by design (full
|
|
51
|
+
AttnLRP is not implemented); bias relevance is absorbed (LRP-ε convention);
|
|
52
|
+
attention masks, `batch_first=False` transformers, explicit RNN initial states
|
|
53
|
+
and `proj_size` raise; ProtoSleepNet's VQ path runs under `no_grad`. Foundation
|
|
54
|
+
encoders and frequency-domain (DFT/STFT) LRP are planned follow-ups.
|
|
55
|
+
|
|
56
|
+
## 2.0.0 — 2026-07-15
|
|
57
|
+
|
|
58
|
+
Ground-up release: raw-EDF lazy data layer with 12 dataset loaders, 9 pretrained
|
|
59
|
+
foundation encoders behind a uniform `encode()` interface, hand-written `Trainer`,
|
|
60
|
+
pytest suite and Sphinx documentation. Full notes in the
|
|
61
|
+
[v2.0.0 GitHub Release](https://github.com/guidogagl/physioex/releases/tag/v2.0.0).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: physioex
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: A python package for explainable sleep staging via deep learning
|
|
5
5
|
Project-URL: Homepage, https://github.com/guidogagl/physioex
|
|
6
6
|
Project-URL: Documentation, https://guidogagl.github.io/physioex
|
|
@@ -42,6 +42,8 @@ Requires-Dist: sphinx-copybutton==0.5.2; extra == 'docs'
|
|
|
42
42
|
Requires-Dist: sphinx-design==0.6.1; extra == 'docs'
|
|
43
43
|
Requires-Dist: sphinx==8.1.3; extra == 'docs'
|
|
44
44
|
Requires-Dist: sphinxcontrib-mermaid==1.0.0; extra == 'docs'
|
|
45
|
+
Provides-Extra: explain
|
|
46
|
+
Requires-Dist: zennit>=0.5.1; extra == 'explain'
|
|
45
47
|
Provides-Extra: foundation
|
|
46
48
|
Requires-Dist: transformers>=4.40.0; extra == 'foundation'
|
|
47
49
|
Provides-Extra: gpu-monitor
|
|
@@ -62,7 +64,7 @@ Description-Content-Type: text/markdown
|
|
|
62
64
|
# PhysioEx
|
|
63
65
|
|
|
64
66
|

|
|
65
|
-
](https://pypi.org/project/physioex/)
|
|
66
68
|
|
|
67
69
|
**PhysioEx ( Physiological Signal Explainer )** is a versatile python library tailored for building, training, and explaining deep learning models for physiological signal analysis.
|
|
68
70
|
|
|
@@ -74,6 +76,12 @@ Beyond training classic architectures from scratch, PhysioEx wraps a family of
|
|
|
74
76
|
**pretrained foundation encoders** behind a uniform interface, so you can extract
|
|
75
77
|
embeddings, run linear probes, and apply the explainability tools on top of them.
|
|
76
78
|
|
|
79
|
+
The explainability toolkit (`physioex.explain`) offers gradient-based attribution
|
|
80
|
+
(Saliency, Input×Gradient, Integrated / Expected Gradients and their DFT/STFT
|
|
81
|
+
variants), concept and prototype methods, and — since 2.0.1 — **Layer-wise
|
|
82
|
+
Relevance Propagation** for CNN, recurrent and attention/transformer models
|
|
83
|
+
(`pip install "physioex[explain]"`), with built-in conservation diagnostics.
|
|
84
|
+
|
|
77
85
|
## Supported deep learning architectures
|
|
78
86
|
|
|
79
87
|
PhysioEx ships two families of models under `physioex.models`.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# PhysioEx
|
|
6
6
|
|
|
7
7
|

|
|
8
|
-
](https://pypi.org/project/physioex/)
|
|
9
9
|
|
|
10
10
|
**PhysioEx ( Physiological Signal Explainer )** is a versatile python library tailored for building, training, and explaining deep learning models for physiological signal analysis.
|
|
11
11
|
|
|
@@ -17,6 +17,12 @@ Beyond training classic architectures from scratch, PhysioEx wraps a family of
|
|
|
17
17
|
**pretrained foundation encoders** behind a uniform interface, so you can extract
|
|
18
18
|
embeddings, run linear probes, and apply the explainability tools on top of them.
|
|
19
19
|
|
|
20
|
+
The explainability toolkit (`physioex.explain`) offers gradient-based attribution
|
|
21
|
+
(Saliency, Input×Gradient, Integrated / Expected Gradients and their DFT/STFT
|
|
22
|
+
variants), concept and prototype methods, and — since 2.0.1 — **Layer-wise
|
|
23
|
+
Relevance Propagation** for CNN, recurrent and attention/transformer models
|
|
24
|
+
(`pip install "physioex[explain]"`), with built-in conservation diagnostics.
|
|
25
|
+
|
|
20
26
|
## Supported deep learning architectures
|
|
21
27
|
|
|
22
28
|
PhysioEx ships two families of models under `physioex.models`.
|
|
@@ -128,7 +128,7 @@ Metrics, statistics and progress helpers are documented at module level:
|
|
|
128
128
|
## `physioex.explain`
|
|
129
129
|
|
|
130
130
|
Post-hoc attribution, the foundational Conceptual Spectral Decomposition (CSD),
|
|
131
|
-
and
|
|
131
|
+
prototype discovery, and Layer-wise Relevance Propagation (`explain` extra).
|
|
132
132
|
|
|
133
133
|
```{eval-rst}
|
|
134
134
|
.. autosummary::
|
|
@@ -136,4 +136,5 @@ and prototype discovery.
|
|
|
136
136
|
|
|
137
137
|
physioex.explain.foundational
|
|
138
138
|
physioex.explain.prototypes.posthoc
|
|
139
|
+
physioex.explain.lrp
|
|
139
140
|
```
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Explain Module
|
|
2
|
+
|
|
3
|
+
PhysioEx ships an explainability (XAI) toolkit for sleep-staging and
|
|
4
|
+
foundation models, organized under `physioex.explain`: three core families
|
|
5
|
+
(post-hoc gradients, foundational/concept, prototypes) plus an optional
|
|
6
|
+
**Layer-wise Relevance Propagation** family (`explain` extra). For
|
|
7
|
+
the full class diagram see the
|
|
8
|
+
[explain-layer architecture](../architecture/library/explain.md) page.
|
|
9
|
+
|
|
10
|
+
## Post-hoc attribution — `physioex.explain.posthoc`
|
|
11
|
+
|
|
12
|
+
Gradient-based attribution over a trained classifier, plus frequency- and
|
|
13
|
+
time-frequency-domain variants:
|
|
14
|
+
|
|
15
|
+
- **Time-domain**: `Saliency`, `InputXGradient`, `IntegratedGradients`,
|
|
16
|
+
`ExpectedGradients` (`physioex/explain/posthoc/gradients.py`). Each is a
|
|
17
|
+
`torch.nn.Module` built around a scalar-valued target function `f` (wrap a
|
|
18
|
+
classifier into one with `Funct` / `SeqFunct` from
|
|
19
|
+
`physioex/explain/posthoc/functionizer.py`); attribution is produced by
|
|
20
|
+
calling the attributor on the input.
|
|
21
|
+
- **Spectral**: `SpectralGradients` (`spectralgradients.py`) — path-integral
|
|
22
|
+
attribution across frequency bands.
|
|
23
|
+
- **Frequency-resolved**: DFT- and STFT-domain attributors, exported from
|
|
24
|
+
`vidft.py` and `vistdft.py`, wrap the same gradient methods around
|
|
25
|
+
differentiable (I)DFT/(I)STFT layers.
|
|
26
|
+
- **Faithfulness metrics** under `posthoc/metrics/` quantify attribution quality
|
|
27
|
+
(complexity, localization, infidelity, and time-frequency measures).
|
|
28
|
+
|
|
29
|
+
Refer to the [API Reference](../../api/index.md) for the exact constructor and
|
|
30
|
+
call signatures of each attributor before use.
|
|
31
|
+
|
|
32
|
+
## Foundational explainability — `physioex.explain.foundational`
|
|
33
|
+
|
|
34
|
+
Concept-level analysis of foundation-model embeddings:
|
|
35
|
+
|
|
36
|
+
- **Conceptual Spectral Decomposition** — `ConceptualSpectralDecomposition`
|
|
37
|
+
(`csd.py`) decomposes embeddings over interpretable spectral concepts and
|
|
38
|
+
returns a `CSDResult` (a list of `ConceptAttribution`).
|
|
39
|
+
- **Specificity strategies** — a pluggable `SpecificityStrategy`
|
|
40
|
+
(`CohenDSpecificity`, `MarginSpecificity`, `SoftmaxSpecificity`,
|
|
41
|
+
`TopKSpecificity`, `NoFilter`) selects the concepts most specific to a class.
|
|
42
|
+
- **Sleep bands** (`sleep_bands.py`), **multi-channel spectral gradients**
|
|
43
|
+
(`multichannel_sg.py`), and reporting helpers (`report.py`).
|
|
44
|
+
|
|
45
|
+
## Prototypes — `physioex.explain.prototypes`
|
|
46
|
+
|
|
47
|
+
Prototype/concept discovery and reconstruction:
|
|
48
|
+
|
|
49
|
+
- **Local relevance**: `PrototypeRelevance`, `get_prototypes` (`local.py`).
|
|
50
|
+
- **NMF** prototype discovery (`posthoc/nmf.py`:
|
|
51
|
+
`discover_prototypes_nmf`).
|
|
52
|
+
- **Vector-quantized** codebooks: `VQBottleneck` and the codebook helpers in
|
|
53
|
+
`posthoc/vq.py` (`learn_codebook_kmeans`, `quantize_embeddings`,
|
|
54
|
+
`train_codebook`).
|
|
55
|
+
- **Reconstruction** of learned concepts (`reconstruct.py`).
|
|
56
|
+
|
|
57
|
+
## Layer-wise Relevance Propagation — `physioex.explain.lrp`
|
|
58
|
+
|
|
59
|
+
Conservation-based attribution (Bach et al. 2015): relevance is redistributed
|
|
60
|
+
backward through the network, conserving the total (`Σ R ≈ f(x)`). This family
|
|
61
|
+
is **optional** — install the extra:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install "physioex[explain]"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Two entry points, both seeding the target neuron with its **logit value** so
|
|
68
|
+
that `Σ R ≈ f_c(x)` and returning relevance shaped like the input:
|
|
69
|
+
|
|
70
|
+
| Model family | Entry point | Rules |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| Feed-forward / CNN (Tsinalis, Chambon2018) | `LRP` (Zennit) | ε dense · γ conv · **w²** first layer (unbounded EEG, *not* the pixel z-box) · BatchNorm canonized · αβ/flat/z-box configurable via `physioex_composite` |
|
|
73
|
+
| Recurrent (TinySleepNet, SeqSleepNet, L-SeqSleepNet, ProtoSleepNet-seq) | `ModelLRP` | **Arras signal-take** LSTM/GRU (`LRPLSTM`/`LRPGRU`, cell-level, forward identical to the fused module) · ε on the learnable filterbank / linears · CP-LRP on the attention poolings |
|
|
74
|
+
| Attention / transformer (SleepTransformer, CoReSleep, ProtoSleepNet-tf) | `ModelLRP` | **CP-LRP** attention (value path; Ali et al. 2022) · identity rule on LayerNorm/GELU · proportional rule on residuals · ε on projections/FFN |
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from physioex.explain.lrp import LRP, ModelLRP
|
|
78
|
+
|
|
79
|
+
relevance = LRP(cnn_model, out_index=2)(x) # Zennit path
|
|
80
|
+
relevance, report = ModelLRP(seq_model, in_index=10, out_index=2)(x, return_report=True)
|
|
81
|
+
print(report) # Σ R / f per sample — 1.0 = exact conservation
|
|
82
|
+
relevance = ModelLRP(coresleep, output_key="combined")(x) # dict outputs
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**How `ModelLRP` works.** The trained model is deep-copied and rewritten
|
|
86
|
+
(`prepare_model_for_lrp`): fused `nn.LSTM`/`nn.GRU` become `LRPLSTM`/`LRPGRU`
|
|
87
|
+
(subclasses, so `isinstance` checks and the `(output, states)` interface keep
|
|
88
|
+
working), `nn.TransformerEncoder`/`MultiheadAttention` become their CP-LRP
|
|
89
|
+
versions, PhysioEx's softmax poolings (`AttentionPooling`, `AttentionLayer`,
|
|
90
|
+
`ChannelMixer`) and the `LearnableFilterbank` get dedicated adapters, remaining
|
|
91
|
+
`Linear`/`Conv` leaves get ε-LRP, BatchNorm is folded into the preceding layer,
|
|
92
|
+
and norms/activations use the identity rule. Plain `+` residuals written in a
|
|
93
|
+
model's own `forward` are redirected at runtime to the proportional rule
|
|
94
|
+
(`patch_residuals=True`) — a plain add would give both branches the full
|
|
95
|
+
relevance and over-count. Register your own blocks with
|
|
96
|
+
`register_lrp_adapter(cls, factory)`; `audit_lrp_coverage(model)` (also
|
|
97
|
+
`ModelLRP(...).uncovered`, and `strict=True`) lists parametric leaves left on
|
|
98
|
+
plain autograd, which would propagate *gradient* instead of relevance.
|
|
99
|
+
|
|
100
|
+
**Reading the numbers.** Conservation is exact (up to ε) for bias-free
|
|
101
|
+
networks; **biases absorb relevance** (LRP-ε convention), so on real models
|
|
102
|
+
`Σ R / f` is below 1 — `ConservationReport.absorbed` shows how much. CP-LRP
|
|
103
|
+
treats the attention matrix as constant: features acting only through *where
|
|
104
|
+
to attend* (query/key path) receive zero relevance by design; full AttnLRP
|
|
105
|
+
(Achtibat et al. 2024) is non-conserving and not implemented. All stabilisers
|
|
106
|
+
are signed (`z + ε·sign z`), as in Arras et al. Not supported (raise): attention
|
|
107
|
+
masks, `batch_first=False` transformers, explicit RNN initial states, `proj_size`;
|
|
108
|
+
ProtoSleepNet's VQ path (`quantize=True`) runs under `no_grad`, so no relevance can
|
|
109
|
+
flow through it — `ModelLRP` explains the default `quantize=False` forward.
|
|
110
|
+
|
|
111
|
+
**Choosing ε.** The stabiliser must be scaled to the activations: on the
|
|
112
|
+
pretrained SeqSleepNet (dB-scale spectrograms, 4-layer BiLSTM/BiGRU) ε = 1e-6
|
|
113
|
+
inflates total relevance 2–3× through near-zero state sums, ε = 1e-2 conserves
|
|
114
|
+
(`ΣR/f ≈ 0.96`) and ε = 1e-1 over-absorbs. Sweep ε and read the
|
|
115
|
+
`ConservationReport`; `examples/explain/lrp_seqsleepnet_n3.py` does this on a
|
|
116
|
+
real model and compares the LRP rule assignments with Saliency, Input×Gradient
|
|
117
|
+
and Integrated Gradients.
|
|
118
|
+
|
|
119
|
+
**Related methods — LRP vs path-based attribution.** LRP is *not* path-based:
|
|
120
|
+
by Deep Taylor Decomposition (Montavon et al. 2017) every rule is a first-order
|
|
121
|
+
Taylor expansion of each neuron at a closed-form *root point* (ε/LRP-0 → root at
|
|
122
|
+
0, hence LRP-0 = Gradient×Input; z⁺ → along the positive contributions; z^B →
|
|
123
|
+
on the input box), applied layer by layer in one backward pass — the baseline
|
|
124
|
+
is implicit in the rule. Integrated Gradients (Sundararajan et al. 2017)
|
|
125
|
+
instead integrates the gradient along the straight path from an explicit
|
|
126
|
+
baseline x′ to x; both satisfy completeness (Σ R = f(x) − f(x′)) by different
|
|
127
|
+
routes. The propagation-family counterpart of a *baseline* method is
|
|
128
|
+
**DeepLIFT** (Shrikumar et al. 2017): finite-difference multipliers
|
|
129
|
+
Δy/Δx w.r.t. a reference, i.e. Integrated Gradients applied layer-wise in a
|
|
130
|
+
single backward pass; Ancona et al. (2018) prove ε-LRP ≡ DeepLIFT-Rescale with a
|
|
131
|
+
zero baseline (ReLU nets without bias, ε → 0) and cast both as
|
|
132
|
+
modified-gradient × input, and **DeepSHAP** (Lundberg & Lee 2017) adds an
|
|
133
|
+
expectation over baselines — the analogue of `ExpectedGradients`. Averaging LRP
|
|
134
|
+
relevance along the x′→x path ("integrated LRP", e.g. Zennit's
|
|
135
|
+
`IntegratedGradients` attributor combined with a composite) is a supported
|
|
136
|
+
construct: it reduces exactly to IG for LRP-0 but carries no completeness
|
|
137
|
+
guarantee for ε/γ/CP rules and should be read as an ensemble. On LSTMs, Arras
|
|
138
|
+
et al. (2019) found gate-rule LRP the best-performing method, with DeepLIFT
|
|
139
|
+
close and IG/gradients behind.
|
|
140
|
+
|
|
141
|
+
- **Composites** (`lrp/composites.py`): `physioex_composite` (ε/γ/w²),
|
|
142
|
+
`epsilon_composite` (pure-ε reference). **Canonizers** (`lrp/canonizers.py`):
|
|
143
|
+
`default_canonizers` (BatchNorm merge).
|
|
144
|
+
- **Blocks**: `lrp/recurrent.py`, `lrp/transformer.py`, `lrp/pooling.py`;
|
|
145
|
+
shared primitives in `lrp/_functional.py`; diagnostics in `lrp/diagnostics.py`.
|
|
146
|
+
- **Backends**: [Zennit](https://github.com/chr5tphr/zennit) for the composites
|
|
147
|
+
and BatchNorm canonization; the recurrent, attention and pooling rules are
|
|
148
|
+
implemented in PhysioEx (`_functional.py`, `_rules.py`) following Arras et al.,
|
|
149
|
+
Ali et al. (CP-LRP) and the LXT conventions — no LXT dependency.
|
|
150
|
+
|
|
151
|
+
See the [API Reference](../../api/index.md) for verified signatures across all
|
|
152
|
+
families.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""LRP vs post-hoc explanations of the pretrained single-channel SeqSleepNet.
|
|
2
|
+
|
|
3
|
+
Explains the N3 logit of the pretrained ``seqsleepnet-phan`` (MASS, EEG 1 ch) on
|
|
4
|
+
training-set sequences whose central epoch is N3, side by side with Saliency,
|
|
5
|
+
Input×Gradient and Integrated Gradients, and shows how the LRP **rule
|
|
6
|
+
assignment** changes the attribution (gate rule, filterbank input rule,
|
|
7
|
+
attention handling, stabiliser ε), always reporting the conservation ratio
|
|
8
|
+
``ΣR / f``. CPU is enough (a few minutes; the physioex cache makes re-runs
|
|
9
|
+
instant).
|
|
10
|
+
|
|
11
|
+
Requirements: ``pip install "physioex[explain]"``, the MASS EDFs under
|
|
12
|
+
``$PHYSIOEX_DATA/MASS/Original/SS0{1..5}`` and a writable ``PHYSIOEX_CACHE_DIR``.
|
|
13
|
+
Set ``SEQSLEEPNET_DIR`` to a folder holding ``config.json`` + ``model.pt`` to run
|
|
14
|
+
offline (otherwise the weights are pulled from the HF hub).
|
|
15
|
+
|
|
16
|
+
Usage::
|
|
17
|
+
|
|
18
|
+
python examples/explain/lrp_seqsleepnet_n3.py --out ./n3_out [--n 8] [--eps 1e-2]
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import importlib
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import time
|
|
26
|
+
import warnings
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
import torch
|
|
30
|
+
import torch.nn as nn
|
|
31
|
+
|
|
32
|
+
from physioex.explain.lrp import LRPGRU, LRPLSTM, ModelLRP
|
|
33
|
+
from physioex.explain.lrp import model as lrpmodel
|
|
34
|
+
from physioex.explain.lrp.pooling import _PoolAdapter
|
|
35
|
+
from physioex.explain.posthoc import InputXGradient, IntegratedGradients, Saliency
|
|
36
|
+
from physioex.explain.posthoc.functionizer import SeqFunct
|
|
37
|
+
|
|
38
|
+
CLASSES = ["W", "N1", "N2", "N3", "REM"]
|
|
39
|
+
FBQ = "physioex.models.seqsleepnet.LearnableFilterbank"
|
|
40
|
+
ATQ = "physioex.models.seqsleepnet.AttentionLayer"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_model(model_dir):
|
|
44
|
+
"""Pretrained SeqSleepNet from a local folder (config.json + model.pt) or the hub."""
|
|
45
|
+
if model_dir:
|
|
46
|
+
cfg = json.load(open(os.path.join(model_dir, "config.json")))
|
|
47
|
+
mod, cls = cfg["model_class"].split(":")
|
|
48
|
+
model = getattr(importlib.import_module(mod), cls)(**cfg["model_kwargs"])
|
|
49
|
+
state = torch.load(os.path.join(model_dir, "model.pt"), map_location="cpu", weights_only=True)
|
|
50
|
+
model.load_state_dict(state)
|
|
51
|
+
return model.eval(), cfg
|
|
52
|
+
from huggingface_hub import hf_hub_download
|
|
53
|
+
from physioex.models import load_from_pretrained
|
|
54
|
+
|
|
55
|
+
cfg = json.load(open(hf_hub_download("4rooms/physioex", "seqsleepnet-phan/config.json")))
|
|
56
|
+
return load_from_pretrained("seqsleepnet-phan", device="cpu").eval(), cfg
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def n3_training_sequences(cfg, n, target_idx, log):
|
|
60
|
+
"""``n`` training-split sequences (fold 0, all cohorts) whose target epoch is N3."""
|
|
61
|
+
from physioex.data.datasets import get_dataset
|
|
62
|
+
from physioex.data.multi import MultiDataset
|
|
63
|
+
|
|
64
|
+
MASS = get_dataset("mass")
|
|
65
|
+
L = cfg["training"]["sequence_length"]
|
|
66
|
+
cohorts = []
|
|
67
|
+
for c in cfg["training"]["dataset_cohorts"]:
|
|
68
|
+
cohorts.append(MASS(cohort=c, channels=cfg["training"]["channels"],
|
|
69
|
+
pipelines=cfg["training"]["pipeline_preset"], sequence_length=L))
|
|
70
|
+
log(f"cohort {c} indexed ({len(cohorts[-1])} sequences)")
|
|
71
|
+
ds = MultiDataset(cohorts)
|
|
72
|
+
train_idx = np.array(ds.split(fold=cfg["training"]["fold"])[0])
|
|
73
|
+
order = np.random.default_rng(0).permutation(len(train_idx))
|
|
74
|
+
xs, ys = [], []
|
|
75
|
+
for k in order:
|
|
76
|
+
item = ds[int(train_idx[k])]
|
|
77
|
+
if int(item["labels"][target_idx]) != CLASSES.index("N3"):
|
|
78
|
+
continue
|
|
79
|
+
xs.append(item["signals"][item["channel_order"][0]].unsqueeze(1).float()) # (L, 1, T, F)
|
|
80
|
+
ys.append(item["labels"].clone())
|
|
81
|
+
if len(xs) == n:
|
|
82
|
+
break
|
|
83
|
+
return torch.stack(xs), torch.stack(ys)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# --- alternative input-layer rules for the filterbank (demo of a *wrong* choice) ---
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _FBLinearRule(torch.autograd.Function):
|
|
90
|
+
@staticmethod
|
|
91
|
+
def forward(ctx, x, Wfb, mode):
|
|
92
|
+
ctx.save_for_backward(Wfb)
|
|
93
|
+
ctx.mode = mode
|
|
94
|
+
return torch.matmul(x, Wfb)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def backward(ctx, R):
|
|
98
|
+
(Wfb,) = ctx.saved_tensors
|
|
99
|
+
if ctx.mode == "w2": # squared weights, independent of the input value
|
|
100
|
+
W2 = Wfb ** 2
|
|
101
|
+
W2 = W2 / (W2.sum(dim=-2, keepdim=True) + 1e-12)
|
|
102
|
+
return torch.matmul(R, W2.transpose(-1, -2)), None, None
|
|
103
|
+
F = Wfb.shape[-2] # flat: uniform over the frequency bins
|
|
104
|
+
return R.sum(-1, keepdim=True).expand(*R.shape[:-1], F) / F, None, None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FilterbankAltRule(_PoolAdapter):
|
|
108
|
+
def __init__(self, orig, mode):
|
|
109
|
+
super().__init__(orig)
|
|
110
|
+
self.mode = mode
|
|
111
|
+
|
|
112
|
+
def forward(self, x):
|
|
113
|
+
with torch.no_grad():
|
|
114
|
+
Wfb = torch.mul(torch.sigmoid(self.orig.W), self.orig.S)
|
|
115
|
+
return _FBLinearRule.apply(x, Wfb, self.mode)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def run_lrp(model, x, target_idx, epsilon, gate_rule="signal_take", fb_mode=None, attn_naive=False):
|
|
119
|
+
saved = dict(lrpmodel._BUILTIN_BY_QUALNAME)
|
|
120
|
+
try:
|
|
121
|
+
if fb_mode:
|
|
122
|
+
lrpmodel._BUILTIN_BY_QUALNAME[FBQ] = lambda m, e, _mode=fb_mode: FilterbankAltRule(m, _mode)
|
|
123
|
+
if attn_naive:
|
|
124
|
+
lrpmodel._BUILTIN_BY_QUALNAME.pop(ATQ, None)
|
|
125
|
+
with warnings.catch_warnings():
|
|
126
|
+
warnings.simplefilter("ignore")
|
|
127
|
+
explainer = ModelLRP(model, in_index=target_idx, out_index=CLASSES.index("N3"), epsilon=epsilon)
|
|
128
|
+
for m in explainer.model.modules():
|
|
129
|
+
if isinstance(m, (LRPLSTM, LRPGRU)):
|
|
130
|
+
m.gate_rule = gate_rule
|
|
131
|
+
rel, report = explainer(x, return_report=True)
|
|
132
|
+
return rel.detach(), report.ratio.tolist(), explainer.uncovered
|
|
133
|
+
finally:
|
|
134
|
+
lrpmodel._BUILTIN_BY_QUALNAME.clear()
|
|
135
|
+
lrpmodel._BUILTIN_BY_QUALNAME.update(saved)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def main():
|
|
139
|
+
ap = argparse.ArgumentParser()
|
|
140
|
+
ap.add_argument("--out", default="n3_out")
|
|
141
|
+
ap.add_argument("--n", type=int, default=8)
|
|
142
|
+
ap.add_argument("--eps", type=float, default=1e-2)
|
|
143
|
+
ap.add_argument("--model-dir", default=os.environ.get("SEQSLEEPNET_DIR"))
|
|
144
|
+
args = ap.parse_args()
|
|
145
|
+
os.makedirs(args.out, exist_ok=True)
|
|
146
|
+
torch.set_num_threads(4)
|
|
147
|
+
t0 = time.time()
|
|
148
|
+
log = lambda *a: print(f"[{time.time() - t0:6.0f}s]", *a, flush=True) # noqa: E731
|
|
149
|
+
|
|
150
|
+
model, cfg = load_model(args.model_dir)
|
|
151
|
+
L = cfg["training"]["sequence_length"]
|
|
152
|
+
mid, n3 = L // 2, CLASSES.index("N3")
|
|
153
|
+
x, y = n3_training_sequences(cfg, args.n, mid, log)
|
|
154
|
+
with torch.no_grad():
|
|
155
|
+
logits = model(x)
|
|
156
|
+
log("x", tuple(x.shape), "N3 logits", [round(v, 2) for v in logits[:, mid, n3].tolist()])
|
|
157
|
+
|
|
158
|
+
f = SeqFunct(model, in_index=mid, out_index=n3)
|
|
159
|
+
results = {
|
|
160
|
+
"Saliency": Saliency(f)(x).detach(),
|
|
161
|
+
"Input x Gradient": InputXGradient(f)(x).detach(),
|
|
162
|
+
"Integrated Gradients (32 steps)": IntegratedGradients(f, steps=32)(x).detach(),
|
|
163
|
+
}
|
|
164
|
+
conserv = {}
|
|
165
|
+
variants = {
|
|
166
|
+
"LRP recommended": {},
|
|
167
|
+
"LRP gate uniform": {"gate_rule": "uniform"},
|
|
168
|
+
"LRP filterbank w2": {"fb_mode": "w2"},
|
|
169
|
+
"LRP filterbank flat": {"fb_mode": "flat"},
|
|
170
|
+
"LRP attention not ruled": {"attn_naive": True},
|
|
171
|
+
}
|
|
172
|
+
for eps in (1e-6, 1e-4, 1e-3, 1e-2, 1e-1):
|
|
173
|
+
variants[f"LRP eps={eps:g}"] = {"epsilon": eps}
|
|
174
|
+
for name, kw in variants.items():
|
|
175
|
+
rel, ratio, uncovered = run_lrp(model, x, mid, kw.pop("epsilon", args.eps), **kw)
|
|
176
|
+
results[name], conserv[name] = rel, ratio
|
|
177
|
+
log(f"{name}: sumR/f={[round(v, 3) for v in ratio]} uncovered={uncovered}")
|
|
178
|
+
|
|
179
|
+
freqs = np.arange(x.shape[-1]) * cfg["model_kwargs"]["fs"] / cfg["model_kwargs"]["nfft"]
|
|
180
|
+
torch.save({"x": x, "labels": y, "logits": logits, "results": results, "conservation": conserv,
|
|
181
|
+
"freqs": freqs, "target_index": mid}, os.path.join(args.out, "attributions.pt"))
|
|
182
|
+
json.dump({k: v for k, v in conserv.items()}, open(os.path.join(args.out, "conservation.json"), "w"), indent=1)
|
|
183
|
+
log("saved", args.out)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.1"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""PhysioEx explainability subsystem.
|
|
2
|
+
|
|
3
|
+
Sub-packages:
|
|
4
|
+
- ``posthoc`` — gradient-based attribution (Saliency, InputXGradient,
|
|
5
|
+
Integrated / Expected Gradients, and their DFT/STFT spectral variants).
|
|
6
|
+
- ``foundational`` — spectral-concept methods (CSD, specificity, ...).
|
|
7
|
+
- ``prototypes`` — prototype / concept discovery and reconstruction.
|
|
8
|
+
- ``lrp`` — Layer-wise Relevance Propagation (needs the ``explain``
|
|
9
|
+
extra: ``pip install "physioex[explain]"``).
|
|
10
|
+
|
|
11
|
+
The LRP entry points are re-exported lazily so that ``import physioex.explain``
|
|
12
|
+
does not pull in the optional ``zennit`` dependency.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
_LAZY = {
|
|
16
|
+
"LRP": "physioex.explain.lrp.attributor",
|
|
17
|
+
"ModelLRP": "physioex.explain.lrp.model",
|
|
18
|
+
"prepare_model_for_lrp": "physioex.explain.lrp.model",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
__all__ = sorted(_LAZY)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __getattr__(name):
|
|
25
|
+
# PEP 562 lazy attribute — only import the LRP stack on first access.
|
|
26
|
+
if name in _LAZY:
|
|
27
|
+
import importlib
|
|
28
|
+
|
|
29
|
+
return getattr(importlib.import_module(_LAZY[name]), name)
|
|
30
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __dir__():
|
|
34
|
+
return sorted(set(globals()) | set(_LAZY))
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Layer-wise Relevance Propagation (LRP) for PhysioEx.
|
|
2
|
+
|
|
3
|
+
Conservation-based attribution (Bach et al. 2015). Two entry points:
|
|
4
|
+
|
|
5
|
+
* :class:`LRP` — Zennit composites for feed-forward / CNN models
|
|
6
|
+
(ε, γ, w², z-box, flat; BatchNorm canonization).
|
|
7
|
+
* :class:`ModelLRP` — whole-model LRP for architectures with LSTM/GRU
|
|
8
|
+
(Arras signal-take), attention / transformers (CP-LRP, Ali et al. 2022) and
|
|
9
|
+
PhysioEx's softmax poolings; leaves get ε-LRP, norms/activations the identity
|
|
10
|
+
rule, residual ``+`` the proportional rule.
|
|
11
|
+
|
|
12
|
+
Both seed the target logit so ``Σ R ≈ f_c(x)``; use ``return_report=True`` /
|
|
13
|
+
:func:`check_conservation` to see how much biases absorb, and
|
|
14
|
+
:func:`audit_lrp_coverage` to list blocks left on plain autograd.
|
|
15
|
+
|
|
16
|
+
Requires the optional ``explain`` extra::
|
|
17
|
+
|
|
18
|
+
pip install "physioex[explain]"
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from physioex.explain.lrp._rules import EpsilonRule, IdentityRule
|
|
22
|
+
from physioex.explain.lrp._functional import (
|
|
23
|
+
add_eps,
|
|
24
|
+
linear_eps,
|
|
25
|
+
mul_signal_take,
|
|
26
|
+
st_identity,
|
|
27
|
+
target_seed,
|
|
28
|
+
)
|
|
29
|
+
from physioex.explain.lrp.attributor import LRP
|
|
30
|
+
from physioex.explain.lrp.canonizers import default_canonizers
|
|
31
|
+
from physioex.explain.lrp.composites import epsilon_composite, physioex_composite
|
|
32
|
+
from physioex.explain.lrp.diagnostics import ConservationReport, check_conservation
|
|
33
|
+
from physioex.explain.lrp.model import (
|
|
34
|
+
ModelLRP,
|
|
35
|
+
audit_lrp_coverage,
|
|
36
|
+
prepare_model_for_lrp,
|
|
37
|
+
register_lrp_adapter,
|
|
38
|
+
)
|
|
39
|
+
from physioex.explain.lrp.pooling import (
|
|
40
|
+
LRPAttentionLayer,
|
|
41
|
+
LRPAttentionPooling,
|
|
42
|
+
LRPChannelMixer,
|
|
43
|
+
cp_weighted_pool,
|
|
44
|
+
)
|
|
45
|
+
from physioex.explain.lrp.recurrent import LRPGRU, LRPLSTM
|
|
46
|
+
from physioex.explain.lrp.transformer import (
|
|
47
|
+
LRPMultiheadAttention,
|
|
48
|
+
LRPMultiheadAttentionModule,
|
|
49
|
+
LRPTransformerEncoder,
|
|
50
|
+
LRPTransformerEncoderLayer,
|
|
51
|
+
swap_transformer_layers,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
# entry points
|
|
56
|
+
"LRP",
|
|
57
|
+
"ModelLRP",
|
|
58
|
+
# Zennit path
|
|
59
|
+
"physioex_composite",
|
|
60
|
+
"epsilon_composite",
|
|
61
|
+
"default_canonizers",
|
|
62
|
+
# whole-model path
|
|
63
|
+
"prepare_model_for_lrp",
|
|
64
|
+
"register_lrp_adapter",
|
|
65
|
+
"audit_lrp_coverage",
|
|
66
|
+
# diagnostics
|
|
67
|
+
"ConservationReport",
|
|
68
|
+
"check_conservation",
|
|
69
|
+
# blocks
|
|
70
|
+
"LRPLSTM",
|
|
71
|
+
"LRPGRU",
|
|
72
|
+
"LRPMultiheadAttention",
|
|
73
|
+
"LRPMultiheadAttentionModule",
|
|
74
|
+
"LRPTransformerEncoderLayer",
|
|
75
|
+
"LRPTransformerEncoder",
|
|
76
|
+
"swap_transformer_layers",
|
|
77
|
+
"LRPAttentionPooling",
|
|
78
|
+
"LRPAttentionLayer",
|
|
79
|
+
"LRPChannelMixer",
|
|
80
|
+
"cp_weighted_pool",
|
|
81
|
+
# rules & primitives
|
|
82
|
+
"EpsilonRule",
|
|
83
|
+
"IdentityRule",
|
|
84
|
+
"linear_eps",
|
|
85
|
+
"add_eps",
|
|
86
|
+
"mul_signal_take",
|
|
87
|
+
"st_identity",
|
|
88
|
+
"target_seed",
|
|
89
|
+
]
|