Braindecode 1.3.2.dev176161306__tar.gz → 1.3.2.dev176759409__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.
- {braindecode-1.3.2.dev176161306/braindecode.egg-info → braindecode-1.3.2.dev176759409}/PKG-INFO +2 -2
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub.py +7 -2
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/__init__.py +2 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/base.py +28 -17
- braindecode-1.3.2.dev176759409/braindecode/models/dgcnn.py +500 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegsym.py +1 -1
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/luna.py +8 -7
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/reve.py +23 -3
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sstdpn.py +1 -1
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/summary.csv +1 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/util.py +13 -11
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/mne_preprocess.py +2 -1
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/util.py +34 -4
- braindecode-1.3.2.dev176759409/braindecode/version.py +1 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409/braindecode.egg-info}/PKG-INFO +2 -2
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/SOURCES.txt +1 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/requires.txt +1 -1
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/api.rst +11 -9
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/conf.py +18 -6
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/gnn.rst +9 -1
- braindecode-1.3.2.dev176759409/docs/models/categorization/spd.rst +103 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_categorization.rst +60 -60
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/whats_new.rst +28 -5
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/pyproject.toml +10 -1
- braindecode-1.3.2.dev176161306/braindecode/version.py +0 -1
- braindecode-1.3.2.dev176161306/docs/models/categorization/spd.rst +0 -35
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/LICENSE.txt +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/MANIFEST.in +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/NOTICE.txt +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/README.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/base.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/functional.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/augmentation/transforms.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/classifier.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/base.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bbci.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bcicomp.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/datasets.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/format.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_format.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_io.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub_validation.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/iterable.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/chb_mit.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/mne.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/moabb.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/nmt.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/registry.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/siena.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/sleep_physionet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/tuh.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/utils.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/xy.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/channel_utils.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/hub_formats.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/serialization.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datautil/util.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/eegneuralnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/functions.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/functional/initialization.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/atcnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/attentionbasenet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/attn_sleep.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/bendr.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/biot.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/brainmodule.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/cbramod.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/config.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/contrawr.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/ctnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/deep4.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/deepsleepnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegconformer.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eeginception_erp.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eeginception_mi.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegitnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegminer.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegnex.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegpt.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegsimpleconv.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegtcnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fbcnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fblightconvnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/fbmsnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/hybrid.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/ifnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/labram.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/medformer.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/msvtnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/patchedtransformer.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sccnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/shallow_fbcsp.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/signal_jepa.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sinc_shallow.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/sparcnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/syncnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tcn.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tidnet.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/tsinception.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/usleep.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/activation.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/attention.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/blocks.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/convolution.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/filter.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/layers.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/linear.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/parametrization.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/stats.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/util.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/modules/wrapper.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/preprocess.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/util.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/preprocessing/windowers.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/regressor.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/base.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/samplers/ssl.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/callbacks.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/losses.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/training/scoring.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/__init__.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/confusion_matrices.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/visualization/gradients.py +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/dependency_links.txt +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode.egg-info/top_level.txt +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/Makefile +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/class.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/function.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/cite.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/help.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/index.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install_pip.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/install/install_source.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/attention.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/channel.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/convolution.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/filterbank.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/interpretable.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/lbm.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/categorization/recurrent.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_table.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/models/models_visualization.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/docs/sg_execution_times.rst +0 -0
- {braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/setup.cfg +0 -0
{braindecode-1.3.2.dev176161306/braindecode.egg-info → braindecode-1.3.2.dev176759409}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: braindecode
|
|
3
|
-
Version: 1.3.2.
|
|
3
|
+
Version: 1.3.2.dev176759409
|
|
4
4
|
Summary: Deep learning software to decode EEG, ECG or MEG signals
|
|
5
5
|
Author-email: Robin Tibor Schirrmeister <robintibor@gmail.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Alexandre Gramfort <agramfort@meta.com>
|
|
6
6
|
Maintainer-email: Alexandre Gramfort <agramfort@meta.com>, Bruno Aristimunha Pinto <b.aristimunha@gmail.com>, Robin Tibor Schirrmeister <robintibor@gmail.com>
|
|
@@ -22,7 +22,7 @@ Requires-Python: >=3.11
|
|
|
22
22
|
Description-Content-Type: text/x-rst
|
|
23
23
|
License-File: LICENSE.txt
|
|
24
24
|
License-File: NOTICE.txt
|
|
25
|
-
Requires-Dist: torch>=2.
|
|
25
|
+
Requires-Dist: torch>=2.0
|
|
26
26
|
Requires-Dist: torchaudio>=2.0
|
|
27
27
|
Requires-Dist: mne>=1.11.0
|
|
28
28
|
Requires-Dist: pandas<3.0.0
|
{braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/datasets/bids/hub.py
RENAMED
|
@@ -845,11 +845,16 @@ class HubDatasetMixin:
|
|
|
845
845
|
|
|
846
846
|
# Convert to MNE objects and create dataset
|
|
847
847
|
info = Info.from_json_dict(info_dict)
|
|
848
|
+
targets = metadata["target"].values
|
|
849
|
+
if np.issubdtype(targets.dtype, np.integer):
|
|
850
|
+
event_ids = targets
|
|
851
|
+
else:
|
|
852
|
+
event_ids = np.ones(len(metadata), dtype=int)
|
|
848
853
|
events = np.column_stack(
|
|
849
854
|
[
|
|
850
|
-
metadata["i_start_in_trial"].values,
|
|
855
|
+
metadata["i_start_in_trial"].values.astype(int),
|
|
851
856
|
np.zeros(len(metadata), dtype=int),
|
|
852
|
-
|
|
857
|
+
event_ids,
|
|
853
858
|
]
|
|
854
859
|
)
|
|
855
860
|
epochs = mne.EpochsArray(data, info, events=events, metadata=metadata)
|
{braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/__init__.py
RENAMED
|
@@ -12,6 +12,7 @@ from .contrawr import ContraWR
|
|
|
12
12
|
from .ctnet import CTNet
|
|
13
13
|
from .deep4 import Deep4Net
|
|
14
14
|
from .deepsleepnet import DeepSleepNet
|
|
15
|
+
from .dgcnn import DGCNN
|
|
15
16
|
from .eegconformer import EEGConformer
|
|
16
17
|
from .eeginception_erp import EEGInceptionERP
|
|
17
18
|
from .eeginception_mi import EEGInceptionMI
|
|
@@ -87,6 +88,7 @@ __all__ = [
|
|
|
87
88
|
"EEGSym",
|
|
88
89
|
"EEGSimpleConv",
|
|
89
90
|
"EEGTCNet",
|
|
91
|
+
"DGCNN",
|
|
90
92
|
"FBCNet",
|
|
91
93
|
"FBLightConvNet",
|
|
92
94
|
"FBMSNet",
|
{braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/base.py
RENAMED
|
@@ -86,38 +86,44 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
|
|
|
86
86
|
-----
|
|
87
87
|
If some input signal-related parameters are not specified,
|
|
88
88
|
there will be an attempt to infer them from the other parameters.
|
|
89
|
+
"""
|
|
89
90
|
|
|
91
|
+
#: Template for model-specific Hub integration notes appended by
|
|
92
|
+
#: ``__init_subclass__``. ``{name}`` is replaced with the concrete
|
|
93
|
+
#: model class name.
|
|
94
|
+
_HUB_NOTES_TEMPLATE = """
|
|
90
95
|
.. rubric:: Hugging Face Hub integration
|
|
91
96
|
|
|
92
97
|
When the optional ``huggingface_hub`` package is installed, all models
|
|
93
98
|
automatically gain the ability to be pushed to and loaded from the
|
|
94
99
|
Hugging Face Hub. Install with::
|
|
95
100
|
|
|
96
|
-
pip install braindecode[
|
|
101
|
+
pip install braindecode[hub]
|
|
97
102
|
|
|
98
103
|
**Pushing a model to the Hub:**
|
|
99
104
|
|
|
100
|
-
.. code-block::
|
|
105
|
+
.. code-block::
|
|
101
106
|
|
|
102
|
-
from braindecode.models import
|
|
107
|
+
from braindecode.models import {name}
|
|
103
108
|
|
|
104
109
|
# Train your model
|
|
105
|
-
model =
|
|
110
|
+
model = {name}(n_chans=22, n_outputs=4, n_times=1000)
|
|
106
111
|
# ... training code ...
|
|
107
112
|
|
|
108
113
|
# Push to the Hub
|
|
109
114
|
model.push_to_hub(
|
|
110
|
-
repo_id="username/my-
|
|
115
|
+
repo_id="username/my-{name_lower}-model",
|
|
116
|
+
commit_message="Initial model upload",
|
|
111
117
|
)
|
|
112
118
|
|
|
113
119
|
**Loading a model from the Hub:**
|
|
114
120
|
|
|
115
|
-
.. code-block::
|
|
121
|
+
.. code-block::
|
|
116
122
|
|
|
117
|
-
from braindecode.models import
|
|
123
|
+
from braindecode.models import {name}
|
|
118
124
|
|
|
119
125
|
# Load pretrained model
|
|
120
|
-
model =
|
|
126
|
+
model = {name}.from_pretrained("username/my-{name_lower}-model")
|
|
121
127
|
|
|
122
128
|
The integration automatically handles EEG-specific parameters (n_chans,
|
|
123
129
|
n_times, sfreq, chs_info, etc.) by saving them in a config file alongside
|
|
@@ -126,19 +132,24 @@ class EEGModuleMixin(_BaseHubMixin, metaclass=NumpyDocstringInheritanceInitMeta)
|
|
|
126
132
|
|
|
127
133
|
.. important::
|
|
128
134
|
Currently, only EEG-specific parameters (n_outputs, n_chans, n_times,
|
|
129
|
-
input_window_seconds, sfreq, chs_info) are saved to the Hub.
|
|
130
|
-
parameters (e.g., dropout rates, activation functions,
|
|
131
|
-
are not preserved and will use their default values
|
|
132
|
-
|
|
133
|
-
To use non-default model parameters, specify them explicitly when calling
|
|
134
|
-
:func:`from_pretrained()`::
|
|
135
|
-
|
|
136
|
-
model = EEGNet.from_pretrained("user/model", dropout=0.3, activation='relu')
|
|
137
|
-
|
|
135
|
+
input_window_seconds, sfreq, chs_info) are saved to the Hub.
|
|
136
|
+
Model-specific parameters (e.g., dropout rates, activation functions,
|
|
137
|
+
number of filters) are not preserved and will use their default values
|
|
138
|
+
when loading from the Hub.
|
|
138
139
|
Full parameter serialization will be addressed in a future update.
|
|
139
140
|
"""
|
|
140
141
|
|
|
141
142
|
def __init_subclass__(cls, **kwargs):
|
|
143
|
+
# Append model-specific Hub integration notes to the docstring.
|
|
144
|
+
# This runs after the metaclass, so we concatenate rather than
|
|
145
|
+
# override any existing Notes section in the subclass.
|
|
146
|
+
if cls.__doc__ is not None:
|
|
147
|
+
hub_notes = cls._HUB_NOTES_TEMPLATE.format(
|
|
148
|
+
name=cls.__name__,
|
|
149
|
+
name_lower=cls.__name__.lower(),
|
|
150
|
+
)
|
|
151
|
+
cls.__doc__ = cls.__doc__.rstrip() + "\n" + hub_notes
|
|
152
|
+
|
|
142
153
|
if not HAS_HF_HUB:
|
|
143
154
|
super().__init_subclass__(**kwargs)
|
|
144
155
|
return
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Authors: Vandit Shah <shahvanditt@gmail.com>
|
|
2
|
+
# Bruno Aristimunha <b.aristimunha@gmail.com>
|
|
3
|
+
#
|
|
4
|
+
# Architecture based on Song et al. (2018):
|
|
5
|
+
# Official TensorFlow code archived at:
|
|
6
|
+
# http://web.archive.org/web/20221122064435/http://aip.seu.edu.cn/wp-content/uploads/2021/08/EEG-TAC.zip
|
|
7
|
+
#
|
|
8
|
+
# License: BSD (3-clause)
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import mne
|
|
13
|
+
import numpy as np
|
|
14
|
+
import torch
|
|
15
|
+
import torch.nn as nn
|
|
16
|
+
import torch.nn.functional as F
|
|
17
|
+
from sklearn.metrics import pairwise_distances
|
|
18
|
+
from sklearn.neighbors import kneighbors_graph
|
|
19
|
+
|
|
20
|
+
from braindecode.models.base import EEGModuleMixin
|
|
21
|
+
from braindecode.models.util import extract_channel_locations_from_chs_info
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _GraphConvolution(nn.Module):
|
|
25
|
+
r"""Chebyshev spectral graph convolution with :math:`1 \times 1` mixing.
|
|
26
|
+
|
|
27
|
+
Implements Equations 11-13 of Song et al. (2018). The spectral
|
|
28
|
+
filter :math:`g(\boldsymbol{\Lambda}^*)` is approximated by a
|
|
29
|
+
:math:`K`-order Chebyshev polynomial expansion:
|
|
30
|
+
|
|
31
|
+
.. math::
|
|
32
|
+
|
|
33
|
+
\mathbf{y}
|
|
34
|
+
= \sum_{k=0}^{K-1} \theta_k\, T_k(\tilde{\mathbf{L}}^*)\,\mathbf{x},
|
|
35
|
+
|
|
36
|
+
where :math:`T_k` are Chebyshev polynomials of the first kind
|
|
37
|
+
computed recursively (Eq. 12):
|
|
38
|
+
|
|
39
|
+
.. math::
|
|
40
|
+
|
|
41
|
+
T_0(x) = 1,\quad T_1(x) = x,\quad
|
|
42
|
+
T_k(x) = 2x\,T_{k-1}(x) - T_{k-2}(x),\quad k \ge 2,
|
|
43
|
+
|
|
44
|
+
and :math:`\tilde{\mathbf{L}}^*` is the rescaled normalized Laplacian.
|
|
45
|
+
The learnable coefficients :math:`\theta_k` are absorbed into a single
|
|
46
|
+
linear projection of the concatenated Chebyshev components, which
|
|
47
|
+
simultaneously acts as a :math:`1 \times 1` convolution that mixes
|
|
48
|
+
features across frequency bands (see Fig. 2 in the paper).
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
in_features : int
|
|
53
|
+
Number of input features per node (e.g. number of time samples
|
|
54
|
+
or frequency bands).
|
|
55
|
+
out_features : int
|
|
56
|
+
Number of output features per node (number of spectral filters).
|
|
57
|
+
cheb_order : int
|
|
58
|
+
Order :math:`K` of the Chebyshev polynomial approximation.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, in_features, out_features, cheb_order):
|
|
62
|
+
super().__init__()
|
|
63
|
+
self.cheb_order = cheb_order
|
|
64
|
+
self.projection = nn.Linear(in_features * cheb_order, out_features, bias=False)
|
|
65
|
+
|
|
66
|
+
def forward(self, x, laplacian):
|
|
67
|
+
r"""Apply Chebyshev graph convolution.
|
|
68
|
+
|
|
69
|
+
Computes :math:`[T_0(\mathbf{L})\mathbf{x},\;\ldots,\;
|
|
70
|
+
T_{K-1}(\mathbf{L})\mathbf{x}]` and projects the concatenation
|
|
71
|
+
through a learned weight matrix.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
x : Tensor, shape (B, N, F_in)
|
|
76
|
+
Input node features, where *N* is the number of graph nodes
|
|
77
|
+
(EEG channels) and *F_in* the input feature dimension.
|
|
78
|
+
laplacian : Tensor, shape (N, N)
|
|
79
|
+
Normalized graph Laplacian.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
Tensor, shape (B, N, F_out)
|
|
84
|
+
Filtered node features.
|
|
85
|
+
"""
|
|
86
|
+
cheb_components = [x] # T_0(L) x = x
|
|
87
|
+
|
|
88
|
+
if self.cheb_order > 1:
|
|
89
|
+
cheb_components.append(torch.matmul(laplacian, x)) # T_1(L) x = L x
|
|
90
|
+
|
|
91
|
+
for _ in range(2, self.cheb_order):
|
|
92
|
+
cheb_components.append(
|
|
93
|
+
2 * torch.matmul(laplacian, cheb_components[-1]) - cheb_components[-2]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return self.projection(torch.cat(cheb_components, dim=-1))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _build_initial_adjacency(chs_info, n_chans, n_neighbors=5):
|
|
100
|
+
r"""Build an initial adjacency matrix from electrode spatial positions.
|
|
101
|
+
|
|
102
|
+
Implements the Gaussian kernel graph construction from Eq. 1 of
|
|
103
|
+
Song et al. (2018):
|
|
104
|
+
|
|
105
|
+
.. math::
|
|
106
|
+
|
|
107
|
+
w_{ij} = \begin{cases}
|
|
108
|
+
\exp\!\Big(-\dfrac{\mathrm{dist}(i,j)^{2}}{2\rho^{2}}\Big),
|
|
109
|
+
& \text{if } \mathrm{dist}(i,j) \le \tau \\
|
|
110
|
+
0, & \text{otherwise}
|
|
111
|
+
\end{cases}
|
|
112
|
+
|
|
113
|
+
where :math:`\rho` is estimated as the median of :math:`k`-nearest
|
|
114
|
+
neighbor distances and the threshold :math:`\tau` is enforced by
|
|
115
|
+
keeping only the ``n_neighbors`` nearest neighbors per node.
|
|
116
|
+
The resulting matrix is symmetrized so that
|
|
117
|
+
:math:`A = \max(A, A^\top)`.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
chs_info : list of dict
|
|
122
|
+
MNE-style channel information with ``'loc'`` entries containing
|
|
123
|
+
3-D electrode positions.
|
|
124
|
+
n_chans : int
|
|
125
|
+
Number of channels (graph nodes :math:`N`).
|
|
126
|
+
n_neighbors : int
|
|
127
|
+
How many spatial neighbors to connect per node (:math:`\tau`
|
|
128
|
+
is implicitly defined by this value).
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
np.ndarray, shape (n_chans, n_chans)
|
|
133
|
+
Symmetric adjacency matrix :math:`\mathbf{W}`.
|
|
134
|
+
|
|
135
|
+
Notes
|
|
136
|
+
-----
|
|
137
|
+
If valid 3-D positions cannot be extracted from ``chs_info``,
|
|
138
|
+
channel names are looked up in the ``standard_1005`` MNE montage.
|
|
139
|
+
If that also fails (or ``chs_info`` is ``None``)
|
|
140
|
+
"""
|
|
141
|
+
electrode_positions = extract_channel_locations_from_chs_info(
|
|
142
|
+
chs_info, num_channels=n_chans
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if electrode_positions is None and chs_info is not None:
|
|
146
|
+
# Try to infer positions from channel names via a standard montage
|
|
147
|
+
try:
|
|
148
|
+
ch_names = [ch["ch_name"] for ch in chs_info[:n_chans]]
|
|
149
|
+
montage = mne.channels.make_standard_montage("standard_1005")
|
|
150
|
+
info = mne.create_info(ch_names=ch_names, sfreq=256, ch_types="eeg")
|
|
151
|
+
info.set_montage(montage, on_missing="raise")
|
|
152
|
+
electrode_positions = extract_channel_locations_from_chs_info(
|
|
153
|
+
info["chs"], num_channels=n_chans
|
|
154
|
+
)
|
|
155
|
+
except Exception:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
"Could not extract valid 3-D channel locations from chs_info. "
|
|
158
|
+
"Please ensure that each entry in chs_info contains a 'loc' key "
|
|
159
|
+
"with 3-D positions or that channel names match a standard montage."
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
n_neighbors_capped = min(n_neighbors, n_chans - 1)
|
|
163
|
+
|
|
164
|
+
# Pairwise Euclidean distances
|
|
165
|
+
distance_matrix = pairwise_distances(electrode_positions, metric="euclidean")
|
|
166
|
+
|
|
167
|
+
# Gaussian kernel: w_ij = exp(-d_ij^2 / (2 * sigma^2))
|
|
168
|
+
# sigma = median of kNN distances (robust scale estimate)
|
|
169
|
+
knn_distances = np.sort(distance_matrix, axis=1)[:, 1 : n_neighbors_capped + 1]
|
|
170
|
+
sigma = np.median(knn_distances) + 1e-8
|
|
171
|
+
gaussian_weights = np.exp(-(distance_matrix**2) / (2 * sigma**2))
|
|
172
|
+
|
|
173
|
+
# Sparsify: keep only k nearest neighbors (symmetric)
|
|
174
|
+
knn_connectivity = kneighbors_graph(
|
|
175
|
+
electrode_positions, n_neighbors=n_neighbors_capped, mode="connectivity"
|
|
176
|
+
)
|
|
177
|
+
adjacency = np.array(knn_connectivity.toarray()) * gaussian_weights
|
|
178
|
+
|
|
179
|
+
# Symmetrize
|
|
180
|
+
adjacency = np.maximum(adjacency, adjacency.T).astype(np.float32)
|
|
181
|
+
return adjacency
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class _LearnableAdjacency(nn.Module):
|
|
185
|
+
r"""Learnable adjacency matrix with ReLU non-negativity.
|
|
186
|
+
|
|
187
|
+
Implements the dynamical adjacency learning described in Section 3
|
|
188
|
+
and Algorithm 1 of Song et al. (2018). The adjacency matrix
|
|
189
|
+
:math:`\mathbf{W}^*` is initialized from electrode spatial
|
|
190
|
+
positions (Eq. 1) and then optimized jointly with all other model
|
|
191
|
+
parameters via back-propagation (Eqs. 15-16):
|
|
192
|
+
|
|
193
|
+
.. math::
|
|
194
|
+
|
|
195
|
+
\mathbf{W}^* \leftarrow (1 - \rho)\,\mathbf{W}^*
|
|
196
|
+
+ \rho\,\frac{\partial Loss}{\partial \mathbf{W}^*}.
|
|
197
|
+
|
|
198
|
+
Following Algorithm 1 (step 3), a ReLU operation is applied after
|
|
199
|
+
every update to keep all entries non-negative. The normalized
|
|
200
|
+
graph Laplacian is then derived as (Eq. 2, normalized form):
|
|
201
|
+
|
|
202
|
+
.. math::
|
|
203
|
+
|
|
204
|
+
\mathbf{L} = \mathbf{I}
|
|
205
|
+
- \mathbf{D}^{-1/2}\,\mathbf{W}^*\,\mathbf{D}^{-1/2},
|
|
206
|
+
|
|
207
|
+
where :math:`D_{ii} = \sum_j w^*_{ij}`.
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
n_chans : int
|
|
212
|
+
Number of graph nodes :math:`N` (EEG channels).
|
|
213
|
+
chs_info : list of dict
|
|
214
|
+
MNE-style channel information with 3-D positions used to
|
|
215
|
+
build the initial adjacency via :func:`_build_initial_adjacency`.
|
|
216
|
+
n_neighbors : int
|
|
217
|
+
Neighbors per node for the initial spatial kNN graph.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(self, n_chans, chs_info, n_neighbors=5):
|
|
221
|
+
super().__init__()
|
|
222
|
+
self.n_chans = n_chans
|
|
223
|
+
|
|
224
|
+
init_adj = _build_initial_adjacency(chs_info, n_chans, n_neighbors)
|
|
225
|
+
self.adjacency = nn.Parameter(
|
|
226
|
+
torch.from_numpy(init_adj) + 0.1 * torch.randn(n_chans, n_chans)
|
|
227
|
+
)
|
|
228
|
+
self.bias = nn.Parameter(torch.tensor(0.0))
|
|
229
|
+
|
|
230
|
+
def forward(self):
|
|
231
|
+
r"""Compute the normalized Laplacian from the current adjacency.
|
|
232
|
+
|
|
233
|
+
Applies ReLU to enforce non-negativity (Algorithm 1, step 3)
|
|
234
|
+
and returns
|
|
235
|
+
:math:`\mathbf{L} = \mathbf{I}
|
|
236
|
+
- \mathbf{D}^{-1/2}\,\mathbf{A}\,\mathbf{D}^{-1/2}`.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
Tensor, shape (N, N)
|
|
241
|
+
Normalized graph Laplacian.
|
|
242
|
+
"""
|
|
243
|
+
adjacency = F.relu(self.adjacency + self.bias)
|
|
244
|
+
|
|
245
|
+
degree = adjacency.sum(dim=1)
|
|
246
|
+
degree_inv_sqrt = 1.0 / (torch.sqrt(degree) + 1e-5)
|
|
247
|
+
degree_inv_sqrt_diag = torch.diag(degree_inv_sqrt)
|
|
248
|
+
|
|
249
|
+
identity = torch.eye(self.n_chans, device=adjacency.device)
|
|
250
|
+
return identity - degree_inv_sqrt_diag @ adjacency @ degree_inv_sqrt_diag
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class DGCNN(EEGModuleMixin, nn.Module):
|
|
254
|
+
r"""DGCNN for EEG classification from Song et al. (2018) [dgcnn]_.
|
|
255
|
+
|
|
256
|
+
:bdg-light:`Graph Neural Network` :bdg-dark-line:`Channel`
|
|
257
|
+
|
|
258
|
+
.. figure:: ../_static/model/DGCNN.gif
|
|
259
|
+
:align: center
|
|
260
|
+
:alt: DGCNN Architecture
|
|
261
|
+
:width: 600px
|
|
262
|
+
|
|
263
|
+
.. rubric:: Architectural Overview
|
|
264
|
+
|
|
265
|
+
DGCNN is a *graph-based* architecture that models EEG channels as nodes
|
|
266
|
+
in a graph and **dynamically learns the adjacency matrix**
|
|
267
|
+
:math:`\mathbf{W}^*` jointly with all other parameters via
|
|
268
|
+
back-propagation (Algorithm 1 in [dgcnn]_). The end-to-end flow is:
|
|
269
|
+
|
|
270
|
+
- (i) learn inter-channel relationships by dynamically updating a
|
|
271
|
+
trainable adjacency matrix,
|
|
272
|
+
- (ii) apply spectral graph convolution via Chebyshev polynomial
|
|
273
|
+
approximation to extract graph-structured features, and
|
|
274
|
+
- (iii) classify with a fully connected head.
|
|
275
|
+
|
|
276
|
+
Different from traditional GCNN methods that predetermine the connections
|
|
277
|
+
of the graph nodes according to their spatial positions, "the proposed
|
|
278
|
+
DGCNN method learns the adjacency matrix in a dynamic way, i.e., the
|
|
279
|
+
entries of the adjacency matrix are adaptively updated with the changes
|
|
280
|
+
of graph model parameters during the model training" [dgcnn]_.
|
|
281
|
+
|
|
282
|
+
.. rubric:: Macro Components
|
|
283
|
+
|
|
284
|
+
- :class:`_LearnableAdjacency` **(Dynamical adjacency → graph Laplacian)**
|
|
285
|
+
|
|
286
|
+
- *Operations.*
|
|
287
|
+
- A trainable :math:`(N \times N)` matrix :math:`\mathbf{W}^*`
|
|
288
|
+
initialized from electrode spatial positions via a Gaussian kernel
|
|
289
|
+
(Eq. 1): :math:`w_{ij} = \exp(-\mathrm{dist}(i,j)^2 / 2\rho^2)`
|
|
290
|
+
for the :math:`k`-nearest neighbors, zero otherwise.
|
|
291
|
+
- **ReLU** applied after every gradient update to keep all entries
|
|
292
|
+
non-negative (Algorithm 1, step 3).
|
|
293
|
+
- The normalized graph Laplacian is derived as (Eq. 2):
|
|
294
|
+
:math:`\mathbf{L} = \mathbf{I}
|
|
295
|
+
- \mathbf{D}^{-1/2}\,\mathbf{W}^*\,\mathbf{D}^{-1/2}`.
|
|
296
|
+
|
|
297
|
+
The adjacency matrix captures intrinsic functional relationships
|
|
298
|
+
between EEG channels that pure spatial proximity may not reflect.
|
|
299
|
+
|
|
300
|
+
- :class:`_GraphConvolution` **(Chebyshev spectral graph convolution +
|
|
301
|
+
1x1 mixing)**
|
|
302
|
+
|
|
303
|
+
- *Operations.*
|
|
304
|
+
- :math:`K`-order Chebyshev polynomial expansion of spectral graph
|
|
305
|
+
filters on the learned Laplacian (Eqs. 11-13):
|
|
306
|
+
|
|
307
|
+
.. math::
|
|
308
|
+
|
|
309
|
+
\mathbf{y}
|
|
310
|
+
= \sum_{k=0}^{K-1} \theta_k\, T_k(\tilde{\mathbf{L}}^*)\,
|
|
311
|
+
\mathbf{x},
|
|
312
|
+
|
|
313
|
+
where :math:`T_k` are Chebyshev polynomials computed recursively
|
|
314
|
+
(Eq. 12) and :math:`\theta_k` are learnable coefficients.
|
|
315
|
+
- A :math:`1 \times 1` convolution (linear projection) that mixes
|
|
316
|
+
the concatenated Chebyshev components, mapping each node's input
|
|
317
|
+
features to ``n_filters`` output features.
|
|
318
|
+
|
|
319
|
+
"Following the graph filtering operation is a :math:`1 \times 1`
|
|
320
|
+
convolution layer, which aims to learn the discriminative features
|
|
321
|
+
among the various frequency domains" [dgcnn]_.
|
|
322
|
+
|
|
323
|
+
- **Activation layer.** ReLU with a learnable per-feature bias ensures
|
|
324
|
+
non-negative outputs of the graph filtering layer [dgcnn]_.
|
|
325
|
+
|
|
326
|
+
- **Classifier Head.**
|
|
327
|
+
Flatten all node features and classify via a multi-layer fully
|
|
328
|
+
connected network with dropout and softmax.
|
|
329
|
+
|
|
330
|
+
.. rubric:: Graph Convolution Details
|
|
331
|
+
|
|
332
|
+
- **Spatial (graph structure).** The adjacency matrix encodes pairwise
|
|
333
|
+
relationships between EEG channels. It is initialized from 3-D
|
|
334
|
+
electrode positions using a Gaussian kernel with kNN sparsification
|
|
335
|
+
(Eq. 1), then *jointly optimized* with all other parameters. This
|
|
336
|
+
allows the model to discover functional connectivity patterns that
|
|
337
|
+
differ from the initial spatial layout. The spectral graph
|
|
338
|
+
convolution then propagates information across neighboring nodes
|
|
339
|
+
according to this learned graph topology.
|
|
340
|
+
|
|
341
|
+
- **Spectral (graph spectral domain).** The Chebyshev polynomial
|
|
342
|
+
approximation (Eq. 11) operates in the *graph spectral domain*
|
|
343
|
+
defined by the eigenvalues of the graph Laplacian. The :math:`K`-order
|
|
344
|
+
approximation acts as a localized graph filter: each node aggregates
|
|
345
|
+
information from its :math:`K`-hop neighborhood. This is analogous
|
|
346
|
+
to a band-pass filter in the graph frequency domain.
|
|
347
|
+
|
|
348
|
+
- **Temporal / Frequency.** No explicit temporal convolution or
|
|
349
|
+
frequency decomposition is performed within the network. In the
|
|
350
|
+
original paper, the input features per node are pre-extracted
|
|
351
|
+
frequency-band features (e.g., differential entropy from
|
|
352
|
+
:math:`\delta`, :math:`\theta`, :math:`\alpha`, :math:`\beta`,
|
|
353
|
+
:math:`\gamma` bands). When used with raw time series, the time
|
|
354
|
+
samples serve directly as node features.
|
|
355
|
+
|
|
356
|
+
.. rubric:: Additional Comments
|
|
357
|
+
|
|
358
|
+
- **Dynamic vs. static graph.** Traditional GCNN methods fix the
|
|
359
|
+
adjacency matrix before training based on spatial positions.
|
|
360
|
+
DGCNN learns it end-to-end, allowing the graph to capture
|
|
361
|
+
task-relevant functional connectivity rather than mere spatial
|
|
362
|
+
proximity.
|
|
363
|
+
- **Chebyshev order.** The order :math:`K` controls the receptive
|
|
364
|
+
field on the graph: :math:`K=1` uses only direct neighbors,
|
|
365
|
+
:math:`K=2` (default) reaches 2-hop neighborhoods. Higher orders
|
|
366
|
+
increase expressivity but also parameter count.
|
|
367
|
+
- **Regularization.** Dropout in the classification head and the
|
|
368
|
+
ReLU constraint on the adjacency matrix provide implicit
|
|
369
|
+
regularization. The loss function in the original paper also
|
|
370
|
+
includes an explicit :math:`\ell_2` penalty on all parameters
|
|
371
|
+
(Eq. 14).
|
|
372
|
+
|
|
373
|
+
Parameters
|
|
374
|
+
----------
|
|
375
|
+
chs_info : list of dict, optional
|
|
376
|
+
Information about each channel, typically obtained from
|
|
377
|
+
``mne.Info['chs']``. Each entry must contain a ``'loc'``
|
|
378
|
+
key with 3-D electrode positions so the initial adjacency
|
|
379
|
+
matrix can be built from spatial proximity (Eq. 1). A montage
|
|
380
|
+
must be set on the ``mne.Info`` object (see
|
|
381
|
+
:meth:`mne.Info.set_montage`). If ``None`` or positions
|
|
382
|
+
cannot be extracted, raised ValueError (see Notes).
|
|
383
|
+
n_filters : int, default=64
|
|
384
|
+
Number of spectral graph-convolutional filters. This is the
|
|
385
|
+
output feature dimension per node produced by the Chebyshev
|
|
386
|
+
graph convolution followed by the :math:`1 \times 1`
|
|
387
|
+
convolution (see Fig. 2 in the paper). The original code
|
|
388
|
+
uses 64.
|
|
389
|
+
cheb_order : int, default=2
|
|
390
|
+
Order :math:`K` of the Chebyshev polynomial approximation
|
|
391
|
+
(Eq. 11).
|
|
392
|
+
n_neighbors : int, default=5
|
|
393
|
+
Number of spatial nearest neighbors per node used to build the
|
|
394
|
+
initial adjacency matrix (Eq. 1).
|
|
395
|
+
mlp_dims : tuple[int, ...], default=(256,)
|
|
396
|
+
Hidden-layer sizes of the fully connected classification head.
|
|
397
|
+
activation : type[nn.Module], default=nn.ReLU
|
|
398
|
+
Activation function class used after the graph convolution and
|
|
399
|
+
in the classification head.
|
|
400
|
+
drop_prob : float, default=0.5
|
|
401
|
+
Dropout probability in the classification head.
|
|
402
|
+
|
|
403
|
+
References
|
|
404
|
+
----------
|
|
405
|
+
.. [dgcnn] Song, T., Zheng, W., Song, P., & Cui, Z. (2018). EEG emotion
|
|
406
|
+
recognition using dynamical graph convolutional neural networks.
|
|
407
|
+
IEEE Transactions on Affective Computing, 11(3), 532-541.
|
|
408
|
+
https://doi.org/10.1109/TAFFC.2018.2817622
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
def __init__(
|
|
412
|
+
self,
|
|
413
|
+
n_outputs: int | None = None,
|
|
414
|
+
n_chans: int | None = None,
|
|
415
|
+
chs_info: list[dict] | None = None,
|
|
416
|
+
n_times: int | None = None,
|
|
417
|
+
input_window_seconds: float | None = None,
|
|
418
|
+
sfreq: float | None = None,
|
|
419
|
+
n_filters: int = 64,
|
|
420
|
+
cheb_order: int = 2,
|
|
421
|
+
n_neighbors: int = 5,
|
|
422
|
+
mlp_dims: tuple[int, ...] = (256,),
|
|
423
|
+
activation: type[nn.Module] = nn.ReLU,
|
|
424
|
+
drop_prob: float = 0.5,
|
|
425
|
+
):
|
|
426
|
+
super().__init__(
|
|
427
|
+
n_outputs=n_outputs,
|
|
428
|
+
n_chans=n_chans,
|
|
429
|
+
chs_info=chs_info,
|
|
430
|
+
n_times=n_times,
|
|
431
|
+
sfreq=sfreq,
|
|
432
|
+
input_window_seconds=input_window_seconds,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
del n_outputs, n_chans, n_times, input_window_seconds, sfreq
|
|
436
|
+
|
|
437
|
+
self.activation = activation()
|
|
438
|
+
self.drop_prob = drop_prob
|
|
439
|
+
|
|
440
|
+
# Learnable adjacency W* (Section 3.1, Algorithm 1)
|
|
441
|
+
# Use self.chs_info (populated by EEGModuleMixin with defaults
|
|
442
|
+
# when chs_info=None) so that the adjacency can always be built.
|
|
443
|
+
self.learnable_adjacency = _LearnableAdjacency(
|
|
444
|
+
n_chans=self.n_chans,
|
|
445
|
+
chs_info=self.chs_info,
|
|
446
|
+
n_neighbors=n_neighbors,
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Chebyshev graph convolution + 1x1 conv (Eq. 13 + Fig. 2)
|
|
450
|
+
self.graph_conv = _GraphConvolution(
|
|
451
|
+
in_features=self.n_times,
|
|
452
|
+
out_features=n_filters,
|
|
453
|
+
cheb_order=cheb_order,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Per-feature bias before ReLU (b1relu in official code)
|
|
457
|
+
self.graph_conv_bias = nn.Parameter(torch.zeros(1, 1, n_filters))
|
|
458
|
+
|
|
459
|
+
# Fully connected classification head (Fig. 2: "Full connection" + softmax)
|
|
460
|
+
classifier_input_dim = self.n_chans * n_filters
|
|
461
|
+
layers = []
|
|
462
|
+
for hidden_dim in mlp_dims:
|
|
463
|
+
layers.append(nn.Linear(classifier_input_dim, hidden_dim))
|
|
464
|
+
layers.append(activation())
|
|
465
|
+
layers.append(nn.Dropout(p=drop_prob))
|
|
466
|
+
classifier_input_dim = hidden_dim
|
|
467
|
+
|
|
468
|
+
self.classifier = nn.Sequential(*layers)
|
|
469
|
+
self.final_layer = nn.Linear(classifier_input_dim, self.n_outputs)
|
|
470
|
+
|
|
471
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
472
|
+
r"""Forward pass through the DGCNN pipeline (Fig. 2).
|
|
473
|
+
|
|
474
|
+
.. figure:: ../_static/model/DGCNN.gif
|
|
475
|
+
:align: center
|
|
476
|
+
:alt: DGCNN Architecture
|
|
477
|
+
:width: 600px
|
|
478
|
+
|
|
479
|
+
1. Compute normalized Laplacian from the learned adjacency.
|
|
480
|
+
2. Apply Chebyshev graph convolution (Eq. 13).
|
|
481
|
+
3. Add per-feature bias and apply ReLU.
|
|
482
|
+
4. Flatten and classify through the fully connected head.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
x : Tensor, shape (batch, n_chans, n_times)
|
|
487
|
+
Input EEG tensor where each channel corresponds to a
|
|
488
|
+
graph node and the time samples are the input features
|
|
489
|
+
per node.
|
|
490
|
+
|
|
491
|
+
Returns
|
|
492
|
+
-------
|
|
493
|
+
Tensor, shape (batch, n_outputs)
|
|
494
|
+
Class logits.
|
|
495
|
+
"""
|
|
496
|
+
laplacian = self.learnable_adjacency()
|
|
497
|
+
x = self.activation(self.graph_conv(x, laplacian) + self.graph_conv_bias)
|
|
498
|
+
x = x.reshape(x.size(0), -1)
|
|
499
|
+
x = self.classifier(x)
|
|
500
|
+
return self.final_layer(x)
|
{braindecode-1.3.2.dev176161306 → braindecode-1.3.2.dev176759409}/braindecode/models/eegsym.py
RENAMED
|
@@ -112,7 +112,7 @@ class EEGSym(EEGModuleMixin, nn.Module):
|
|
|
112
112
|
corresponding to different frequency characteristics.
|
|
113
113
|
|
|
114
114
|
Notes
|
|
115
|
-
|
|
115
|
+
-----
|
|
116
116
|
* EEGSym achieved competitive accuracies across five large MI datasets [eegsym2022]_.
|
|
117
117
|
* The model maintained high accuracy using a reduced set of electrodes (8 or 16 channels)
|
|
118
118
|
[eegsym2022]_.
|