braindecode 1.6.1.dev1064__tar.gz → 1.6.1.dev1065__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.6.1.dev1064/braindecode.egg-info → braindecode-1.6.1.dev1065}/PKG-INFO +1 -1
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/__init__.py +2 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/summary.csv +1 -0
- braindecode-1.6.1.dev1065/braindecode/models/tcformer.py +643 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/util.py +1 -0
- braindecode-1.6.1.dev1065/braindecode/version.py +1 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065/braindecode.egg-info}/PKG-INFO +1 -1
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/SOURCES.txt +1 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/api.rst +1 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/whats_new.rst +5 -0
- braindecode-1.6.1.dev1064/braindecode/version.py +0 -1
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/LICENSE.txt +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/MANIFEST.in +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/NOTICE.txt +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/README.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/base.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/functional.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/augmentation/transforms.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/classifier.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/base.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bbci.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bcicomp.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/datasets.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/format.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_format.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_io.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/hub_validation.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/bids/iterable.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/chb_mit.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/mne.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/moabb.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/nmt.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/registry.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/siena.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/sleep_physio_challe_18.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/sleep_physionet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/tuh.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/utils.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datasets/xy.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/channel_utils.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/hub_formats.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/serialization.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/datautil/util.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/eegneuralnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/functions.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/functional/initialization.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/atcnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/attentionbasenet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/attn_sleep.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/base.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/bendr.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/biot.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/brainmodule.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/cbramod.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/codebrain.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/config.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/contrawr.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/ctnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/deep4.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/deepsleepnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/dgcnn.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegconformer.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegdino.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eeginception_erp.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eeginception_mi.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegitnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegminer.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegnex.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegpt.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegsimpleconv.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegsym.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/eegtcnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/emg2qwerty.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fbcnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fblightconvnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/fbmsnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/hybrid.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/ifnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/interpolated.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/labram.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/luna.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/medformer.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/meta_neuromotor.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/msvtnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/patchedtransformer.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/reve.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sccnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/shallow_fbcsp.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/signal_jepa.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sinc_shallow.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sleep_stager_blanco_2020.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sleep_stager_chambon_2018.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sparcnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/sstdpn.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/syncnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tcn.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tidnet.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/tsinception.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/models/usleep.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/activation.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/attention.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/blocks.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/convolution.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/filter.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/interpolation.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/layers.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/linear.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/parametrization.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/stats.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/util.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/modules/wrapper.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/eegprep_preprocess.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/mne_preprocess.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/preprocess.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/util.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/preprocessing/windowers.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/regressor.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/base.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/samplers/ssl.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/callbacks.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/losses.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/training/scoring.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/util.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/__init__.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/attribution.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/confusion_matrices.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/frequency.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/metrics.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/sanity.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode/visualization/topology.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/dependency_links.txt +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/requires.txt +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/braindecode.egg-info/top_level.txt +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/Makefile +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/class.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/class_in_subdir.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/function.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/_templates/autosummary/function_in_subdir.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/cite.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/conf.py +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/help.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/index.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install_pip.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/install/install_source.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/attention.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/channel.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/convolution.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/filterbank.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/gnn.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/interpretable.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/lbm.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/recurrent.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/categorization/spd.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_categorization.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_table.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/models/models_visualization.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/docs/sg_execution_times.rst +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/pyproject.toml +0 -0
- {braindecode-1.6.1.dev1064 → braindecode-1.6.1.dev1065}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: braindecode
|
|
3
|
-
Version: 1.6.1.
|
|
3
|
+
Version: 1.6.1.dev1065
|
|
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>
|
|
@@ -55,6 +55,7 @@ from .sleep_stager_chambon_2018 import SleepStagerChambon2018
|
|
|
55
55
|
from .sparcnet import SPARCNet
|
|
56
56
|
from .sstdpn import SSTDPN
|
|
57
57
|
from .syncnet import SyncNet
|
|
58
|
+
from .tcformer import TCFormer
|
|
58
59
|
from .tcn import BDTCN, TCN
|
|
59
60
|
from .tidnet import TIDNet
|
|
60
61
|
from .tsinception import TSception
|
|
@@ -130,6 +131,7 @@ __all__ = [
|
|
|
130
131
|
"SPARCNet",
|
|
131
132
|
"SyncNet",
|
|
132
133
|
"BDTCN",
|
|
134
|
+
"TCFormer",
|
|
133
135
|
"TCN",
|
|
134
136
|
"TIDNet",
|
|
135
137
|
"TSception",
|
|
@@ -56,4 +56,5 @@ InterpolatedBIOT,"Sleep Staging, Epilepsy",Classification,200,"chs_info, n_outpu
|
|
|
56
56
|
InterpolatedEEGPT,General,Classification,256,"chs_info, n_outputs, n_times",25323137,"InterpolatedEEGPT(chs_info=<user>, n_outputs=4, n_times=1024)","Foundation Model,Attention/Transformer,Channel"
|
|
57
57
|
InterpolatedLaBraM,General,"Classification, Embedding",200,"chs_info, n_outputs, n_times",5866196,"InterpolatedLaBraM(chs_info=<user>, n_outputs=4, n_times=1000)","Convolution,Foundation Model,Channel"
|
|
58
58
|
InterpolatedSignalJEPA,"Motor Imagery, ERP, SSVEP",Embedding,128,"chs_info",3456882,"InterpolatedSignalJEPA(chs_info=<user>)","Convolution,Channel,Foundation Model"
|
|
59
|
+
TCFormer,Motor Imagery,Classification,250,"n_chans, n_outputs, n_times",77820,"TCFormer(n_chans=22, n_outputs=4, n_times=1000)","Convolution,Attention/Transformer"
|
|
59
60
|
EEGDINO,General,Classification,200,"n_chans, n_outputs, n_times",4539698,"EEGDINO(n_chans=19, n_outputs=4, n_times=1000)","Foundation Model,Attention/Transformer"
|
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
# Authors: Hamdi Altaheri <haltaheri@uwaterloo.ca> (original implementation)
|
|
2
|
+
# Bruno Aristimunha <b.aristimunha@gmail.com> (braindecode adaptation)
|
|
3
|
+
#
|
|
4
|
+
# License: MIT
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import torch
|
|
9
|
+
from einops import rearrange, repeat
|
|
10
|
+
from einops.layers.torch import Rearrange
|
|
11
|
+
from torch import Tensor, nn
|
|
12
|
+
|
|
13
|
+
from braindecode.models.base import EEGModuleMixin
|
|
14
|
+
from braindecode.modules import CausalConv1d, Conv1dWithConstraint, DropPath
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TCFormer(EEGModuleMixin, nn.Module):
|
|
18
|
+
r"""TCFormer from Altaheri et al (2025) [tcformer]_.
|
|
19
|
+
|
|
20
|
+
:bdg-success:`Convolution` :bdg-info:`Attention/Transformer`
|
|
21
|
+
|
|
22
|
+
.. figure:: https://braindecode.org/dev/_static/model/tcformer.png
|
|
23
|
+
:align: center
|
|
24
|
+
:alt: TCFormer Architecture
|
|
25
|
+
|
|
26
|
+
Temporal Convolutional Transformer for EEG-based motor-imagery decoding. It
|
|
27
|
+
couples a multi-kernel convolutional front-end, a grouped-query Transformer
|
|
28
|
+
encoder with rotary positional embeddings, and a grouped temporal
|
|
29
|
+
convolutional network head, reaching state-of-the-art accuracy on BCIC IV-2a,
|
|
30
|
+
IV-2b and the High-Gamma Dataset while remaining compact (~78k parameters)
|
|
31
|
+
[tcformer]_.
|
|
32
|
+
|
|
33
|
+
.. rubric:: Architecture Overview
|
|
34
|
+
|
|
35
|
+
The raw trial ``(batch, n_chans, n_times)`` flows through three stages:
|
|
36
|
+
(1) :class:`_MultiKernelConvBlock` extracts multi-scale spatiotemporal
|
|
37
|
+
features and emits a short sequence of ``d_model`` tokens; (2) a stack of
|
|
38
|
+
:class:`_TransformerBlock` layers models global temporal context with
|
|
39
|
+
grouped-query attention and rotary embeddings; (3) the Transformer output is
|
|
40
|
+
reduced and concatenated with the convolutional tokens, then a grouped
|
|
41
|
+
:class:`_TCN` head with a per-group :class:`_ClassificationHead`
|
|
42
|
+
(``final_layer``) produces class logits.
|
|
43
|
+
|
|
44
|
+
.. rubric:: Macro Components
|
|
45
|
+
|
|
46
|
+
``TCFormer.conv_block`` (Multi-Kernel Convolutional Embedding)
|
|
47
|
+
**Operations.** Parallel temporal convolutions with kernels
|
|
48
|
+
``temp_kernel_lengths`` (each ``n_filters_time`` filters, batch-normed),
|
|
49
|
+
concatenated, then a depthwise spatial convolution over electrodes
|
|
50
|
+
(``depth_multiplier``), average pooling, grouped 1x1 channel reduction to
|
|
51
|
+
``d_model = group_dim * n_groups``, a grouped temporal convolution, a
|
|
52
|
+
grouped squeeze-and-excitation gate, and a second pooling.
|
|
53
|
+
**Role.** Turns the raw montage into ``Tc`` compact feature tokens, one
|
|
54
|
+
group per temporal kernel, encoding band-specific rhythms.
|
|
55
|
+
|
|
56
|
+
``TCFormer.transformer`` (Grouped-Query Transformer Encoder)
|
|
57
|
+
**Operations.** ``n_transformer_layers`` pre-norm blocks, each applying
|
|
58
|
+
grouped-query self-attention (``q_heads`` queries sharing ``kv_heads``
|
|
59
|
+
key/value groups) with rotary positional embedding, then a position-wise
|
|
60
|
+
MLP (expansion ``mlp_ratio``); residual connections use a quadratic
|
|
61
|
+
DropPath schedule up to ``drop_path_max``.
|
|
62
|
+
**Role.** Adds global temporal context efficiently (fewer K/V projections
|
|
63
|
+
than full multi-head attention).
|
|
64
|
+
|
|
65
|
+
``TCFormer.tcn`` + ``TCFormer.final_layer`` (Grouped TCN Head)
|
|
66
|
+
**Operations.** The reduced Transformer output is concatenated with the
|
|
67
|
+
convolutional tokens (``d_fused = group_dim * (n_groups + 1)`` channels,
|
|
68
|
+
``n_groups + 1`` groups), processed by ``tcn_depth`` dilated causal
|
|
69
|
+
residual blocks (kernel ``tcn_kernel_length``, dilations ``2**i``), the
|
|
70
|
+
last timestep is taken, and a grouped 1x1 conv produces per-group logits
|
|
71
|
+
averaged across groups.
|
|
72
|
+
**Role.** Long-range causal temporal decoding and read-out.
|
|
73
|
+
|
|
74
|
+
.. rubric:: Temporal, Spatial, and Spectral Encoding
|
|
75
|
+
|
|
76
|
+
- **Temporal:** multi-kernel and dilated-causal convolutions plus rotary
|
|
77
|
+
self-attention capture short- and long-range temporal dependencies.
|
|
78
|
+
- **Spatial:** a depthwise convolution spanning all electrodes mixes channels
|
|
79
|
+
per feature map.
|
|
80
|
+
- **Spectral:** the three temporal kernel sizes target distinct EEG bands
|
|
81
|
+
(short kernels -> high frequencies, long kernels -> low frequencies).
|
|
82
|
+
|
|
83
|
+
.. rubric:: Additional Mechanisms
|
|
84
|
+
|
|
85
|
+
- Grouped-query attention and rotary embeddings reduce attention cost while
|
|
86
|
+
preserving relative-position information.
|
|
87
|
+
- Group structure is preserved end-to-end: each temporal kernel forms a
|
|
88
|
+
channel group that stays separated through the grouped reductions, grouped
|
|
89
|
+
TCN, and per-group classifier.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
n_filters_time : int, optional
|
|
94
|
+
Number of temporal filters per kernel (``F1``). Default ``32``.
|
|
95
|
+
temp_kernel_lengths : tuple of int, optional
|
|
96
|
+
Temporal kernel lengths; their count is the number of feature groups.
|
|
97
|
+
Default ``(20, 32, 64)``.
|
|
98
|
+
depth_multiplier : int, optional
|
|
99
|
+
Depthwise spatial expansion factor (``D``). Default ``2``.
|
|
100
|
+
pool_length_1, pool_length_2 : int, optional
|
|
101
|
+
Average-pool factors after the depthwise and the second temporal conv.
|
|
102
|
+
Defaults ``8`` and ``7``.
|
|
103
|
+
temp_kernel_length_2 : int, optional
|
|
104
|
+
Kernel length of the grouped second temporal convolution. Default ``16``.
|
|
105
|
+
group_dim : int, optional
|
|
106
|
+
Channels per group (``d_group``); ``d_model = group_dim * n_groups``.
|
|
107
|
+
Default ``16``.
|
|
108
|
+
se_reduction : int, optional
|
|
109
|
+
Reduction ratio of the grouped squeeze-and-excitation block. Default ``4``.
|
|
110
|
+
n_transformer_layers : int, optional
|
|
111
|
+
Number of encoder layers (``N``). Default ``2`` (the 77.8k-parameter
|
|
112
|
+
headline configuration). Use ``5`` for the deeper ~131k variant.
|
|
113
|
+
q_heads, kv_heads : int, optional
|
|
114
|
+
Number of query heads and key/value groups for grouped-query attention
|
|
115
|
+
(``q_heads`` must be divisible by ``kv_heads``). Defaults ``4`` and ``2``.
|
|
116
|
+
mlp_ratio : int, optional
|
|
117
|
+
Feed-forward expansion ratio in each encoder block. Default ``2``.
|
|
118
|
+
drop_path_max : float, optional
|
|
119
|
+
Maximum stochastic-depth rate (quadratic schedule over depth). Default
|
|
120
|
+
``0.25``.
|
|
121
|
+
tcn_depth : int, optional
|
|
122
|
+
Number of TCN residual blocks (``L``). Default ``2``.
|
|
123
|
+
tcn_kernel_length : int, optional
|
|
124
|
+
Kernel length of the TCN causal convolutions (``KT``). Default ``4``.
|
|
125
|
+
classifier_max_norm : float, optional
|
|
126
|
+
Max-norm constraint on the classifier convolution weights. Default
|
|
127
|
+
``0.25``.
|
|
128
|
+
drop_prob_conv, drop_prob_trans, drop_prob_tcn : float, optional
|
|
129
|
+
Dropout probabilities of the conv front-end, the Transformer, and the TCN
|
|
130
|
+
head. Defaults ``0.4``, ``0.4``, ``0.3``.
|
|
131
|
+
activation : nn.Module, optional
|
|
132
|
+
Activation class for the convolutional and TCN blocks. Default
|
|
133
|
+
:class:`torch.nn.ELU`.
|
|
134
|
+
activation_ffn : nn.Module, optional
|
|
135
|
+
Activation class for the Transformer feed-forward sublayer. Default
|
|
136
|
+
:class:`torch.nn.GELU`.
|
|
137
|
+
|
|
138
|
+
Notes
|
|
139
|
+
-----
|
|
140
|
+
This implementation is adapted from the original source code [tcformercode]_
|
|
141
|
+
to comply with braindecode's model conventions. The default configuration
|
|
142
|
+
reproduces the paper's headline (Table 1) setup; the released training config
|
|
143
|
+
additionally uses Adam (lr 0.0009, weight_decay 1e-3), linear warm-up + cosine
|
|
144
|
+
decay, per-channel z-scoring, and segmentation-and-reconstruction augmentation
|
|
145
|
+
(handled outside the model, in the training pipeline).
|
|
146
|
+
|
|
147
|
+
.. versionadded:: 1.6.1
|
|
148
|
+
|
|
149
|
+
References
|
|
150
|
+
----------
|
|
151
|
+
.. [tcformer] Altaheri, H., Karray, F., & Karimi, A.-H. (2025). Temporal
|
|
152
|
+
convolutional transformer for EEG based motor imagery decoding. Scientific
|
|
153
|
+
Reports, 15, 32959. https://doi.org/10.1038/s41598-025-16219-7
|
|
154
|
+
.. [tcformercode] Altaheri, H. (2025). TCFormer source code.
|
|
155
|
+
https://github.com/Altaheri/TCFormer
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self,
|
|
160
|
+
# Signal related parameters
|
|
161
|
+
n_outputs=None,
|
|
162
|
+
n_chans=None,
|
|
163
|
+
chs_info=None,
|
|
164
|
+
n_times=None,
|
|
165
|
+
input_window_seconds=None,
|
|
166
|
+
sfreq=None,
|
|
167
|
+
# Model parameters
|
|
168
|
+
n_filters_time: int = 32,
|
|
169
|
+
temp_kernel_lengths: tuple[int, ...] = (20, 32, 64),
|
|
170
|
+
depth_multiplier: int = 2,
|
|
171
|
+
pool_length_1: int = 8,
|
|
172
|
+
pool_length_2: int = 7,
|
|
173
|
+
temp_kernel_length_2: int = 16,
|
|
174
|
+
group_dim: int = 16,
|
|
175
|
+
se_reduction: int = 4,
|
|
176
|
+
n_transformer_layers: int = 2,
|
|
177
|
+
q_heads: int = 4,
|
|
178
|
+
kv_heads: int = 2,
|
|
179
|
+
mlp_ratio: int = 2,
|
|
180
|
+
drop_path_max: float = 0.25,
|
|
181
|
+
tcn_depth: int = 2,
|
|
182
|
+
tcn_kernel_length: int = 4,
|
|
183
|
+
classifier_max_norm: float = 0.25,
|
|
184
|
+
drop_prob_conv: float = 0.4,
|
|
185
|
+
drop_prob_trans: float = 0.4,
|
|
186
|
+
drop_prob_tcn: float = 0.3,
|
|
187
|
+
activation: type[nn.Module] = nn.ELU,
|
|
188
|
+
activation_ffn: type[nn.Module] = nn.GELU,
|
|
189
|
+
):
|
|
190
|
+
super().__init__(
|
|
191
|
+
n_outputs=n_outputs,
|
|
192
|
+
n_chans=n_chans,
|
|
193
|
+
chs_info=chs_info,
|
|
194
|
+
n_times=n_times,
|
|
195
|
+
input_window_seconds=input_window_seconds,
|
|
196
|
+
sfreq=sfreq,
|
|
197
|
+
)
|
|
198
|
+
del n_outputs, n_chans, chs_info, n_times, input_window_seconds, sfreq
|
|
199
|
+
|
|
200
|
+
n_groups = len(temp_kernel_lengths)
|
|
201
|
+
d_model = group_dim * n_groups
|
|
202
|
+
d_fused = group_dim * (n_groups + 1)
|
|
203
|
+
|
|
204
|
+
self.conv_block = _MultiKernelConvBlock(
|
|
205
|
+
self.n_chans,
|
|
206
|
+
temp_kernel_lengths,
|
|
207
|
+
n_filters_time,
|
|
208
|
+
depth_multiplier,
|
|
209
|
+
pool_length_1,
|
|
210
|
+
pool_length_2,
|
|
211
|
+
temp_kernel_length_2,
|
|
212
|
+
drop_prob_conv,
|
|
213
|
+
group_dim,
|
|
214
|
+
se_reduction,
|
|
215
|
+
activation,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
self.mix = nn.Sequential(
|
|
219
|
+
nn.Conv1d(d_model, d_model, 1, bias=False),
|
|
220
|
+
nn.BatchNorm1d(d_model),
|
|
221
|
+
nn.SiLU(),
|
|
222
|
+
)
|
|
223
|
+
self.to_tokens = Rearrange("batch feat time -> batch time feat")
|
|
224
|
+
|
|
225
|
+
drop_rates = torch.linspace(0, 1, n_transformer_layers) ** 2 * drop_path_max
|
|
226
|
+
self.transformer = nn.ModuleList(
|
|
227
|
+
[
|
|
228
|
+
_TransformerBlock(
|
|
229
|
+
d_model,
|
|
230
|
+
q_heads,
|
|
231
|
+
kv_heads,
|
|
232
|
+
mlp_ratio,
|
|
233
|
+
drop_prob_trans,
|
|
234
|
+
float(drop_rates[i]),
|
|
235
|
+
activation_ffn,
|
|
236
|
+
)
|
|
237
|
+
for i in range(n_transformer_layers)
|
|
238
|
+
]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# RoPE tables are built per forward from the actual token length.
|
|
242
|
+
self.head_dim = d_model // q_heads
|
|
243
|
+
|
|
244
|
+
self.reduce = nn.Sequential(
|
|
245
|
+
Rearrange("batch time feat -> batch feat time"),
|
|
246
|
+
nn.Conv1d(d_model, group_dim, 1, bias=False),
|
|
247
|
+
nn.BatchNorm1d(group_dim),
|
|
248
|
+
nn.SiLU(),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
self.tcn = _TCN(
|
|
252
|
+
tcn_depth,
|
|
253
|
+
tcn_kernel_length,
|
|
254
|
+
d_fused,
|
|
255
|
+
n_groups + 1,
|
|
256
|
+
drop_prob_tcn,
|
|
257
|
+
activation,
|
|
258
|
+
)
|
|
259
|
+
self.final_layer = _ClassificationHead(
|
|
260
|
+
d_fused, n_groups + 1, self.n_outputs, classifier_max_norm
|
|
261
|
+
)
|
|
262
|
+
self.apply(self._init_weights)
|
|
263
|
+
|
|
264
|
+
@staticmethod
|
|
265
|
+
def _init_weights(module: nn.Module) -> None:
|
|
266
|
+
if isinstance(module, (nn.Conv1d, nn.Conv2d, nn.Linear)):
|
|
267
|
+
params = getattr(module, "parametrizations", None)
|
|
268
|
+
if params is not None and "weight" in params:
|
|
269
|
+
# max-norm layers expose a computed .weight; init the leaf
|
|
270
|
+
nn.init.xavier_uniform_(params.weight.original)
|
|
271
|
+
elif isinstance(getattr(module, "weight", None), nn.Parameter):
|
|
272
|
+
nn.init.xavier_uniform_(module.weight)
|
|
273
|
+
if getattr(module, "bias", None) is not None:
|
|
274
|
+
nn.init.constant_(module.bias, 0.0)
|
|
275
|
+
|
|
276
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
277
|
+
conv_features = self.conv_block(x) # (batch, d_model, n_tokens)
|
|
278
|
+
tokens = self.to_tokens(self.mix(conv_features)) # (batch, n_tokens, d_model)
|
|
279
|
+
seq_len = tokens.shape[1]
|
|
280
|
+
cos, sin = _build_rotary_cache(
|
|
281
|
+
self.head_dim, seq_len, device=tokens.device, dtype=tokens.dtype
|
|
282
|
+
)
|
|
283
|
+
for block in self.transformer:
|
|
284
|
+
tokens = block(tokens, cos, sin)
|
|
285
|
+
transformer_features = self.reduce(tokens) # (batch, group_dim, n_tokens)
|
|
286
|
+
fused = torch.cat(
|
|
287
|
+
(conv_features, transformer_features), dim=1
|
|
288
|
+
) # (batch, d_fused, n_tokens)
|
|
289
|
+
decoded = self.tcn(fused)
|
|
290
|
+
last_step = decoded[:, :, -1:] # (batch, d_fused, 1)
|
|
291
|
+
return self.final_layer(last_step) # (batch, n_outputs)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
# ----------------------------------------------------------------------------- #
|
|
295
|
+
# Rotary positional embedding (verbatim port of the reference; see plan note on
|
|
296
|
+
# the deliberate NeoX-cache / interleaved-rotate mismatch).
|
|
297
|
+
def _build_rotary_cache(
|
|
298
|
+
head_dim: int, seq_len: int, device=None, dtype=None
|
|
299
|
+
) -> tuple[Tensor, Tensor]:
|
|
300
|
+
"""Return ``(cos, sin)`` each of shape ``(seq_len, head_dim)``.
|
|
301
|
+
|
|
302
|
+
Angles are computed in float32 for precision; the returned cos/sin are
|
|
303
|
+
cast to ``dtype`` (the token dtype) so attention stays in the working
|
|
304
|
+
precision under autocast.
|
|
305
|
+
"""
|
|
306
|
+
theta = 1.0 / (
|
|
307
|
+
10000 ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim)
|
|
308
|
+
)
|
|
309
|
+
seq_idx = torch.arange(seq_len, device=device).float()
|
|
310
|
+
freqs = torch.outer(seq_idx, theta) # (seq_len, head_dim/2)
|
|
311
|
+
emb = torch.cat((freqs, freqs), dim=-1) # (seq_len, head_dim)
|
|
312
|
+
cos, sin = emb.cos(), emb.sin()
|
|
313
|
+
if dtype is not None:
|
|
314
|
+
cos, sin = cos.to(dtype), sin.to(dtype)
|
|
315
|
+
return cos, sin
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _apply_rope(
|
|
319
|
+
query: Tensor, key: Tensor, cos: Tensor, sin: Tensor
|
|
320
|
+
) -> tuple[Tensor, Tensor]:
|
|
321
|
+
"""Apply rotary embedding to ``query`` and ``key``.
|
|
322
|
+
|
|
323
|
+
Both have shape ``(batch, n_heads, seq_len, head_dim)``.
|
|
324
|
+
"""
|
|
325
|
+
|
|
326
|
+
def rotate(tensor: Tensor) -> Tensor:
|
|
327
|
+
even = tensor[..., 0::2]
|
|
328
|
+
odd = tensor[..., 1::2]
|
|
329
|
+
pairs = torch.stack((-odd, even), dim=-1)
|
|
330
|
+
return rearrange(pairs, "... half two -> ... (half two)")
|
|
331
|
+
|
|
332
|
+
query_rotated = (query * cos) + (rotate(query) * sin)
|
|
333
|
+
key_rotated = (key * cos) + (rotate(key) * sin)
|
|
334
|
+
return query_rotated, key_rotated
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
# ----------------------------------------------------------------------------- #
|
|
338
|
+
class _GroupedQueryAttention(nn.Module):
|
|
339
|
+
"""Grouped-query self-attention (``q_heads >= kv_heads``) with RoPE."""
|
|
340
|
+
|
|
341
|
+
def __init__(
|
|
342
|
+
self, d_model: int, q_heads: int, kv_heads: int, drop_prob: float = 0.4
|
|
343
|
+
):
|
|
344
|
+
super().__init__()
|
|
345
|
+
assert d_model % q_heads == 0, "d_model must be divisible by q_heads"
|
|
346
|
+
assert q_heads % kv_heads == 0, "q_heads must be a multiple of kv_heads"
|
|
347
|
+
self.n_query_heads = q_heads
|
|
348
|
+
self.n_kv_heads = kv_heads
|
|
349
|
+
self.head_dim = d_model // q_heads
|
|
350
|
+
assert self.head_dim % 2 == 0, (
|
|
351
|
+
"head_dim (d_model // q_heads) must be even for rotary embeddings"
|
|
352
|
+
)
|
|
353
|
+
self.scale = self.head_dim**-0.5
|
|
354
|
+
self.query_proj = nn.Linear(d_model, d_model, bias=False)
|
|
355
|
+
self.key_value_proj = nn.Linear(
|
|
356
|
+
d_model, 2 * kv_heads * self.head_dim, bias=False
|
|
357
|
+
)
|
|
358
|
+
self.out_proj = nn.Linear(d_model, d_model, bias=False)
|
|
359
|
+
self.drop = nn.Dropout(drop_prob)
|
|
360
|
+
|
|
361
|
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
|
|
362
|
+
seq_len = x.shape[1]
|
|
363
|
+
query = rearrange(
|
|
364
|
+
self.query_proj(x),
|
|
365
|
+
"batch seq (heads dim) -> batch heads seq dim",
|
|
366
|
+
heads=self.n_query_heads,
|
|
367
|
+
)
|
|
368
|
+
key, value = rearrange(
|
|
369
|
+
self.key_value_proj(x),
|
|
370
|
+
"batch seq (heads two dim) -> two batch heads seq dim",
|
|
371
|
+
two=2,
|
|
372
|
+
heads=self.n_kv_heads,
|
|
373
|
+
)
|
|
374
|
+
# share each key/value group across its query heads
|
|
375
|
+
groups_per_kv = self.n_query_heads // self.n_kv_heads
|
|
376
|
+
key = repeat(
|
|
377
|
+
key,
|
|
378
|
+
"batch heads seq dim -> batch (heads repeats) seq dim",
|
|
379
|
+
repeats=groups_per_kv,
|
|
380
|
+
)
|
|
381
|
+
value = repeat(
|
|
382
|
+
value,
|
|
383
|
+
"batch heads seq dim -> batch (heads repeats) seq dim",
|
|
384
|
+
repeats=groups_per_kv,
|
|
385
|
+
)
|
|
386
|
+
query, key = _apply_rope(query, key, cos[:seq_len], sin[:seq_len])
|
|
387
|
+
key_t = rearrange(key, "batch heads seq dim -> batch heads dim seq")
|
|
388
|
+
attention = ((query @ key_t) * self.scale).softmax(dim=-1)
|
|
389
|
+
attention = self.drop(attention)
|
|
390
|
+
context = rearrange(
|
|
391
|
+
attention @ value, "batch heads seq dim -> batch seq (heads dim)"
|
|
392
|
+
)
|
|
393
|
+
return self.out_proj(context)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# ----------------------------------------------------------------------------- #
|
|
397
|
+
class _TransformerBlock(nn.Module):
|
|
398
|
+
"""Pre-norm encoder block: GQA + position-wise MLP, both with DropPath."""
|
|
399
|
+
|
|
400
|
+
def __init__(
|
|
401
|
+
self,
|
|
402
|
+
d_model: int,
|
|
403
|
+
q_heads: int,
|
|
404
|
+
kv_heads: int,
|
|
405
|
+
mlp_ratio: int = 2,
|
|
406
|
+
drop_prob: float = 0.4,
|
|
407
|
+
drop_path_rate: float = 0.0,
|
|
408
|
+
activation: type[nn.Module] = nn.GELU,
|
|
409
|
+
):
|
|
410
|
+
super().__init__()
|
|
411
|
+
self.norm1 = nn.LayerNorm(d_model)
|
|
412
|
+
self.attn = _GroupedQueryAttention(d_model, q_heads, kv_heads, drop_prob)
|
|
413
|
+
self.drop_path = DropPath(drop_path_rate)
|
|
414
|
+
self.norm2 = nn.LayerNorm(d_model)
|
|
415
|
+
self.mlp = nn.Sequential(
|
|
416
|
+
nn.Linear(d_model, mlp_ratio * d_model),
|
|
417
|
+
activation(),
|
|
418
|
+
nn.Linear(mlp_ratio * d_model, d_model),
|
|
419
|
+
nn.Dropout(drop_prob),
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def forward(self, x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
|
|
423
|
+
x = x + self.drop_path(self.attn(self.norm1(x), cos, sin))
|
|
424
|
+
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
|
425
|
+
return x
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
# ----------------------------------------------------------------------------- #
|
|
429
|
+
class _ChannelGroupAttention(nn.Module):
|
|
430
|
+
"""Grouped Squeeze-and-Excitation: one sigmoid gate per channel group."""
|
|
431
|
+
|
|
432
|
+
def __init__(self, in_channels: int, num_groups: int, reduction: int = 4):
|
|
433
|
+
super().__init__()
|
|
434
|
+
assert in_channels % num_groups == 0
|
|
435
|
+
self.in_channels = in_channels
|
|
436
|
+
self.num_groups = num_groups
|
|
437
|
+
self.group_size = in_channels // num_groups
|
|
438
|
+
self.pool = nn.AdaptiveAvgPool2d(1)
|
|
439
|
+
self.att_fc1 = nn.Conv2d(
|
|
440
|
+
in_channels, in_channels // reduction, 1, groups=num_groups, bias=False
|
|
441
|
+
)
|
|
442
|
+
self.att_fc2 = nn.Conv2d(
|
|
443
|
+
in_channels // reduction, num_groups, 1, groups=num_groups, bias=False
|
|
444
|
+
)
|
|
445
|
+
self.relu = nn.ReLU()
|
|
446
|
+
self.sigmoid = nn.Sigmoid()
|
|
447
|
+
|
|
448
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
449
|
+
descriptor = self.pool(x)
|
|
450
|
+
gate = self.sigmoid(self.att_fc2(self.relu(self.att_fc1(descriptor))))
|
|
451
|
+
grouped = rearrange(
|
|
452
|
+
x,
|
|
453
|
+
"batch (groups chans) height width -> batch groups chans height width",
|
|
454
|
+
groups=self.num_groups,
|
|
455
|
+
)
|
|
456
|
+
gate = rearrange(
|
|
457
|
+
gate, "batch groups height width -> batch groups 1 height width"
|
|
458
|
+
)
|
|
459
|
+
return rearrange(
|
|
460
|
+
grouped * gate,
|
|
461
|
+
"batch groups chans height width -> batch (groups chans) height width",
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
# ----------------------------------------------------------------------------- #
|
|
466
|
+
class _MultiKernelConvBlock(nn.Module):
|
|
467
|
+
"""Multi-kernel temporal + depthwise spatial CNN producing feature tokens."""
|
|
468
|
+
|
|
469
|
+
def __init__(
|
|
470
|
+
self,
|
|
471
|
+
n_chans: int,
|
|
472
|
+
temp_kernel_lengths: tuple[int, ...],
|
|
473
|
+
n_filters_time: int,
|
|
474
|
+
depth_multiplier: int,
|
|
475
|
+
pool_length_1: int,
|
|
476
|
+
pool_length_2: int,
|
|
477
|
+
temp_kernel_length_2: int,
|
|
478
|
+
drop_prob: float,
|
|
479
|
+
group_dim: int,
|
|
480
|
+
se_reduction: int,
|
|
481
|
+
activation: type[nn.Module],
|
|
482
|
+
):
|
|
483
|
+
super().__init__()
|
|
484
|
+
n_groups = len(temp_kernel_lengths)
|
|
485
|
+
self.d_model = group_dim * n_groups
|
|
486
|
+
self.rearrange = Rearrange("batch nchans time -> batch 1 nchans time")
|
|
487
|
+
|
|
488
|
+
# one temporal conv per kernel ('same' time padding via ConstantPad2d),
|
|
489
|
+
# BN only -- NO activation here (matches reference).
|
|
490
|
+
self.temporal_convs = nn.ModuleList(
|
|
491
|
+
[
|
|
492
|
+
nn.Sequential(
|
|
493
|
+
nn.ConstantPad2d(
|
|
494
|
+
(k // 2 - 1, k // 2, 0, 0)
|
|
495
|
+
if k % 2 == 0
|
|
496
|
+
else (k // 2, k // 2, 0, 0),
|
|
497
|
+
0,
|
|
498
|
+
),
|
|
499
|
+
nn.Conv2d(1, n_filters_time, (1, k), bias=False),
|
|
500
|
+
nn.BatchNorm2d(n_filters_time),
|
|
501
|
+
)
|
|
502
|
+
for k in temp_kernel_lengths
|
|
503
|
+
]
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
in_dw = n_filters_time * n_groups
|
|
507
|
+
f2 = in_dw * depth_multiplier
|
|
508
|
+
self.channel_dw_conv = nn.Sequential(
|
|
509
|
+
nn.Conv2d(in_dw, f2, (n_chans, 1), bias=False, groups=in_dw),
|
|
510
|
+
nn.BatchNorm2d(f2),
|
|
511
|
+
activation(),
|
|
512
|
+
)
|
|
513
|
+
self.pool1 = nn.AvgPool2d((1, pool_length_1))
|
|
514
|
+
self.drop1 = nn.Dropout(drop_prob)
|
|
515
|
+
|
|
516
|
+
# grouped 1x1 channel reduction f2 -> d_model (one group per kernel)
|
|
517
|
+
self.use_channel_reduction = self.d_model != f2
|
|
518
|
+
if self.use_channel_reduction:
|
|
519
|
+
self.channel_reduction = nn.Sequential(
|
|
520
|
+
nn.Conv2d(f2, self.d_model, (1, 1), bias=False, groups=n_groups),
|
|
521
|
+
nn.BatchNorm2d(self.d_model),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
self.temporal_conv_2 = nn.Sequential(
|
|
525
|
+
nn.Conv2d(
|
|
526
|
+
self.d_model,
|
|
527
|
+
self.d_model,
|
|
528
|
+
(1, temp_kernel_length_2),
|
|
529
|
+
padding="same",
|
|
530
|
+
bias=False,
|
|
531
|
+
groups=n_groups,
|
|
532
|
+
),
|
|
533
|
+
nn.BatchNorm2d(self.d_model),
|
|
534
|
+
activation(),
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
self.use_group_attn = n_groups > 1
|
|
538
|
+
if self.use_group_attn:
|
|
539
|
+
self.group_attn = _ChannelGroupAttention(
|
|
540
|
+
self.d_model, n_groups, se_reduction
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
self.pool2 = nn.AvgPool2d((1, pool_length_2))
|
|
544
|
+
self.drop2 = nn.Dropout(drop_prob)
|
|
545
|
+
|
|
546
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
547
|
+
x = self.rearrange(x)
|
|
548
|
+
x = torch.cat([conv(x) for conv in self.temporal_convs], dim=1)
|
|
549
|
+
x = self.channel_dw_conv(x)
|
|
550
|
+
x = self.drop1(self.pool1(x))
|
|
551
|
+
if self.use_channel_reduction:
|
|
552
|
+
x = self.channel_reduction(x)
|
|
553
|
+
x = self.temporal_conv_2(x)
|
|
554
|
+
if self.use_group_attn:
|
|
555
|
+
x = x + self.group_attn(x)
|
|
556
|
+
x = self.drop2(self.pool2(x))
|
|
557
|
+
# drop the singleton spatial axis -> (batch, d_model, n_tokens)
|
|
558
|
+
return rearrange(x, "batch feat 1 time -> batch feat time")
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
# ----------------------------------------------------------------------------- #
|
|
562
|
+
class _TCNResidualBlock(nn.Module):
|
|
563
|
+
"""Two grouped dilated-causal convs (BN + activation + dropout) + residual."""
|
|
564
|
+
|
|
565
|
+
def __init__(
|
|
566
|
+
self,
|
|
567
|
+
n_filters: int,
|
|
568
|
+
kernel_length: int,
|
|
569
|
+
dilation: int,
|
|
570
|
+
n_groups: int,
|
|
571
|
+
drop_prob: float,
|
|
572
|
+
activation: type[nn.Module],
|
|
573
|
+
):
|
|
574
|
+
super().__init__()
|
|
575
|
+
self.conv1 = CausalConv1d(
|
|
576
|
+
n_filters, n_filters, kernel_length, dilation=dilation, groups=n_groups
|
|
577
|
+
)
|
|
578
|
+
self.bn1 = nn.BatchNorm1d(n_filters)
|
|
579
|
+
self.act1 = activation()
|
|
580
|
+
self.drop1 = nn.Dropout(drop_prob)
|
|
581
|
+
self.conv2 = CausalConv1d(
|
|
582
|
+
n_filters, n_filters, kernel_length, dilation=dilation, groups=n_groups
|
|
583
|
+
)
|
|
584
|
+
self.bn2 = nn.BatchNorm1d(n_filters)
|
|
585
|
+
self.act2 = activation()
|
|
586
|
+
self.drop2 = nn.Dropout(drop_prob)
|
|
587
|
+
self.act3 = activation()
|
|
588
|
+
|
|
589
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
590
|
+
out = self.drop1(self.act1(self.bn1(self.conv1(x))))
|
|
591
|
+
out = self.drop2(self.act2(self.bn2(self.conv2(out))))
|
|
592
|
+
return self.act3(x + out)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class _TCN(nn.Module):
|
|
596
|
+
"""Stack of ``depth`` residual blocks with exponentially growing dilation."""
|
|
597
|
+
|
|
598
|
+
def __init__(
|
|
599
|
+
self,
|
|
600
|
+
depth: int,
|
|
601
|
+
kernel_length: int,
|
|
602
|
+
n_filters: int,
|
|
603
|
+
n_groups: int,
|
|
604
|
+
drop_prob: float,
|
|
605
|
+
activation: type[nn.Module],
|
|
606
|
+
):
|
|
607
|
+
super().__init__()
|
|
608
|
+
self.blocks = nn.ModuleList(
|
|
609
|
+
[
|
|
610
|
+
_TCNResidualBlock(
|
|
611
|
+
n_filters, kernel_length, 2**i, n_groups, drop_prob, activation
|
|
612
|
+
)
|
|
613
|
+
for i in range(depth)
|
|
614
|
+
]
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
618
|
+
for blk in self.blocks:
|
|
619
|
+
x = blk(x)
|
|
620
|
+
return x
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
# ----------------------------------------------------------------------------- #
|
|
624
|
+
class _ClassificationHead(nn.Module):
|
|
625
|
+
"""Grouped 1x1 conv producing per-group logits, averaged across groups."""
|
|
626
|
+
|
|
627
|
+
def __init__(
|
|
628
|
+
self, d_features: int, n_groups: int, n_outputs: int, max_norm: float = 0.25
|
|
629
|
+
):
|
|
630
|
+
super().__init__()
|
|
631
|
+
self.n_groups = n_groups
|
|
632
|
+
self.n_outputs = n_outputs
|
|
633
|
+
self.conv = Conv1dWithConstraint(
|
|
634
|
+
d_features,
|
|
635
|
+
n_outputs * n_groups,
|
|
636
|
+
kernel_size=1,
|
|
637
|
+
groups=n_groups,
|
|
638
|
+
max_norm=max_norm,
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
def forward(self, x: Tensor) -> Tensor:
|
|
642
|
+
x = self.conv(x).squeeze(-1) # (B, n_outputs*n_groups)
|
|
643
|
+
return x.view(x.size(0), self.n_groups, self.n_outputs).mean(dim=1)
|
|
@@ -360,6 +360,7 @@ models_mandatory_parameters: list[
|
|
|
360
360
|
("MSVTNet", ["n_chans", "n_outputs", "n_times"], None),
|
|
361
361
|
("EEGMiner", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 200.0}),
|
|
362
362
|
("CTNet", ["n_chans", "n_outputs", "n_times"], None),
|
|
363
|
+
("TCFormer", ["n_chans", "n_outputs", "n_times"], None),
|
|
363
364
|
("SincShallowNet", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 250.0}),
|
|
364
365
|
("SCCNet", ["n_chans", "n_outputs", "n_times", "sfreq"], {"sfreq": 200.0}),
|
|
365
366
|
("SignalJEPA", ["chs_info"], None),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.6.1.dev1065"
|