Riemannian tangent-space EEG path - #4
Merged
Merged
Conversation
added 3 commits
August 11, 2026 08:35
EEG carries much of its signal in inter-channel covariance, which lives on the manifold of SPD matrices, not a flat vector space. Scaling and SMOTE-ing the raw entries throws away that geometry. This adds the missing path documented in docs/estimators.md: a log-Euclidean tangent projection at the front of the pipeline, so downstream scaler/SMOTE/logistic operate on genuinely Euclidean tangent vectors. models/riemann.py: RiemannianTangentSpace transformer (scipy-only). Vectorises covariances isometrically (sqrt(2) off-diagonal so Euclidean norm == Frobenius), regularises rank-deficient covariances via identity shrinkage before the matrix log (short-epoch covariances are singular), and centres at the training-set log-Euclidean mean (leakage-safe: computed in fit only). pyriemann's affine-invariant metric is a further upgrade, noted for later. Wiring: make_balanced_pipeline gains pre_steps, inserted before the scaler. build_modality_model's `riemann` path prepends the tangent transform, uses logistic in tangent space, and forces max_features=1.0 (a column subset of a flattened covariance is not a covariance). 12 tests: isometric vectorisation, invertibility, rank-deficient survival, leakage-safe reference mean, non-covariance rejection, pipeline wiring, and an honest above-chance positive control (NOT a synthetic "beats logistic" claim, since raw entries are already separable on clean data).
EEGLoader gains an include_covariance mode that emits per-trial inter-channel covariance, flattened with the same sqrt(2) off-diagonal convention the tangent transformer uses. Both share models.riemann.flatten_spd as the single source of truth, and a round-trip test guards against the loader and transformer drifting apart. Covariance is mutually exclusive with band-power/ERP: the tangent map needs the covariance matrix intact, not concatenated with unrelated columns, so the constructor raises if you ask for both. 6 tests: shape/naming, the min-samples guard, provenance labelling, the loader<->transformer round-trip, the mutual-exclusivity guard, and an end-to-end EEGLoader(covariance) -> riemann positive control above chance.
estimators.md now describes riemann as a real estimator: how to produce covariance features, the tangent-first pipeline that keeps scaling/SMOTE valid, the leakage-safe training-mean reference, and two honest caveats (it does not beat plain logistic on clean synthetic data, where raw entries are already separable; log-Euclidean is used, with pyriemann's affine-invariant metric noted as the further upgrade). Removes the old "not a drop-in" Riemannian subsection, leaving mixed-effects as the one remaining documented-but-not-built model.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds the Riemannian EEG arm as the proper follow-up flagged in #3, in three small commits. This is the one I said needed a dedicated path rather than a base-learner swap — here it is, done correctly.
Why it needed its own path
EEG carries much of its discriminative signal in inter-channel covariance, which lives on the manifold of SPD matrices, not a flat vector space. The framework's
StandardScalerz-scores each covariance entry independently (destroying the positive-definite structure) andSMOTEinterpolates in straight lines (off-manifold). So you can't just slot a "riemann" base learner into the flat pipeline — the covariance has to be projected into a flat tangent space first.What's shipped
models/riemann.py—RiemannianTangentSpacetransformer (scipy-only, log-Euclidean metric). Isometric vectorisation (√2 off-diagonal so Euclidean norm = Frobenius), identity-shrinkage regularisation so rank-deficient short-epoch covariances still admit a matrix log, and a training-set log-Euclidean mean reference computed infitonly (leakage-safe).make_balanced_pipelinegainspre_steps, andbuild_modality_model'sriemannpath prepends the tangent transform, classifies with logistic in tangent space, and forcesmax_features=1.0(a column subset of a flattened covariance isn't a covariance).EEGLoadercovariance mode — emits per-trial inter-channel covariance, sharingflatten_spdwith the transformer as one source of truth (a round-trip test guards against drift). Mutually exclusive with band power / ERP.Two honest caveats (in the code and docs)
pyriemann's affine-invariant metric is a further upgrade, noted as the next step if a real EEG dataset warrants it.Tests
18 new (
test_riemann.py12,test_eeg_covariance.py6): isometric/invertible vectorisation, rank-deficient survival, leakage-safe reference mean, non-covariance rejection, pipeline wiring, loader↔transformer round-trip, mutual-exclusivity, and end-to-end above-chance positive controls. Full suite 147 passing, no new dependency.