[MRG] Add EEG classification task#6
Conversation
|
@tomMoral Ready to be merge in our side |
|
It might make sense to do one review from within the EGG subgroup? |
|
Do we need to be additional things ? |
I think a rebase on/merge of |
There was a problem hiding this comment.
Pull request overview
This PR introduces an EEG classification track to the TSFM benchmark by adding three EEG datasets and three EEG-focused solvers (a baseline EEGNet classifier and two foundation-model + linear-probe solvers).
Changes:
- Add EEG datasets: BNCI2014-001, Sleep PhysioNet, and HGD (motor imagery).
- Add EEG solvers: EEGNet (braindecode), REVE + linear probe, and CBraMod + linear probe.
- Plumb dataset metadata needed by foundation models (e.g., sampling frequency and channel names).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
solvers/reve.py |
Adds REVE foundation-model encoder with resampling + linear probe adapter. |
solvers/eegnet.py |
Adds a braindecode EEGNetv4 training/prediction solver. |
solvers/cbramod.py |
Adds CBraMod foundation-model encoder with resampling/truncation + linear probe adapter. |
datasets/sleepphysionet.py |
Adds Sleep PhysioNet windowed sleep-stage classification dataset loader. |
datasets/hgd.py |
Adds HGD motor-imagery classification dataset loader and preprocessing. |
datasets/bnci2014_001.py |
Adds BNCI2014-001 motor-imagery classification dataset loader. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # windows_dataset.description contains a 'split' column ('train'/'test') | ||
| # Each window is indexed into its base dataset; we replicate that mapping. | ||
| descriptions = windows_dataset.datasets # list of WindowsDataset | ||
|
|
||
| for ds in descriptions: |
| import numpy as np | ||
| import torch | ||
| from benchopt import BaseSolver |
| } | ||
|
|
||
|
|
||
| class EEGNetAdapter: |
| Wraps the sleep recordings from the Sleep Physionet. | ||
| Each recording is split into a training portion (first 10 %) and a test | ||
| portion. | ||
| Labels are from 0 to 4, corresponding to the sleep stages W, N1, N2, N3, REM. |
|
|
||
|
|
||
| class Dataset(BaseDataset): | ||
| """Sleep classification dataset (TSB-UAD). |
| factor : float | ||
| Factor to multiply the raw signals by (e.g. to convert from V to uV | ||
| train_ratio : float |
| @@ -0,0 +1,126 @@ | |||
| import numpy as np | |||
| y_test = np.concatenate( | ||
| [y_all[i] for i in range(len(y_all)) if i not in ids_train] | ||
| ) | ||
| np.unique(y_train), np.unique(y_test) # sanity check |
| X_t = torch.tensor(X, dtype=torch.float32, device=self._device) | ||
| y_t = torch.tensor(y, dtype=torch.long, device=self._device) | ||
|
|
||
| dataset = torch.utils.data.TensorDataset(X_t, y_t) | ||
| loader = torch.utils.data.DataLoader( | ||
| dataset, batch_size=self.batch_size, shuffle=True | ||
| ) | ||
|
|
||
| self._network.train() | ||
| for _ in range(self.n_epochs): | ||
| for xb, yb in loader: | ||
| self._optimizer.zero_grad() | ||
| logits = self._network(xb) | ||
| loss = self._criterion(logits, yb) | ||
| loss.backward() | ||
| self._optimizer.step() |
| X = self._prepare_inputs(np.asarray(X, dtype=np.float32)) | ||
| X_t = torch.tensor(X, dtype=torch.float32, device=self._device) | ||
|
|
||
| self._network.eval() | ||
| preds = [] | ||
| with torch.no_grad(): | ||
| for i in range(0, len(X_t), self._batch_size): | ||
| logits = self._network(X_t[i:i + self._batch_size]) | ||
| preds.append(logits.argmax(dim=1).cpu().numpy()) | ||
| return np.concatenate(preds) |
DATASETS:
MODELS: