Skip to content

Commit 07b3c6a

Browse files
1 parent e9a29bd commit 07b3c6a

181 files changed

Lines changed: 11148 additions & 6940 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Binary file not shown.

dev/_downloads/090305d06248840b75133975e5121f41/plot_sleep_staging_chambon2018.ipynb

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
},
178178
"outputs": [],
179179
"source": [
180-
"import torch\nfrom torch import nn\n\nfrom braindecode.models import SleepStagerChambon2018\nfrom braindecode.modules import TimeDistributed\nfrom braindecode.util import set_random_seeds\n\ncuda = torch.cuda.is_available() # check if GPU is available\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nif cuda:\n torch.backends.cudnn.benchmark = True\n# Set random seed to be able to roughly reproduce results\n# Note that with cudnn benchmark set to True, GPU indeterminism\n# may still make results substantially different between runs.\n# To obtain more consistent results at the cost of increased computation time,\n# you can set `cudnn_benchmark=False` in `set_random_seeds`\n# or remove `torch.backends.cudnn.benchmark = True`\nset_random_seeds(seed=31, cuda=cuda)\n\nn_classes = 5\n# Extract number of channels and time steps from dataset\nn_channels, input_size_samples = train_set[0][0].shape\n\nfeat_extractor = SleepStagerChambon2018(\n n_channels,\n sfreq,\n n_outputs=n_classes,\n n_times=input_size_samples,\n return_feats=True,\n)\n\nmodel = nn.Sequential(\n TimeDistributed(feat_extractor), # apply model on each 30-s window\n nn.Sequential( # apply linear layer on concatenated feature vectors\n nn.Flatten(start_dim=1),\n nn.Dropout(0.5),\n nn.Linear(feat_extractor.len_last_layer * n_windows, n_classes),\n ),\n)\n\n# Send model to GPU\nif cuda:\n model.cuda()"
180+
"import torch\nfrom torch import nn\n\nfrom braindecode.models import SleepStagerChambon2018\nfrom braindecode.modules import TimeDistributed\nfrom braindecode.util import set_random_seeds\n\ncuda = torch.cuda.is_available() # check if CUDA is available\nmps = hasattr(torch.backends, \"mps\") and torch.backends.mps.is_available()\ndevice = \"cuda\" if cuda else \"mps\" if mps else \"cpu\"\nif cuda:\n torch.backends.cudnn.benchmark = True\n# Set random seed to be able to roughly reproduce results\n# Note that with cudnn benchmark set to True, GPU indeterminism\n# may still make results substantially different between runs.\n# To obtain more consistent results at the cost of increased computation time,\n# you can set `cudnn_benchmark=False` in `set_random_seeds`\n# or remove `torch.backends.cudnn.benchmark = True`\nset_random_seeds(seed=31, cuda=cuda)\n\nn_classes = 5\n# Extract number of channels and time steps from dataset\nn_channels, input_size_samples = train_set[0][0].shape\n\nfeat_extractor = SleepStagerChambon2018(\n n_channels,\n sfreq,\n n_outputs=n_classes,\n n_times=input_size_samples,\n return_feats=True,\n)\n\nmodel = nn.Sequential(\n TimeDistributed(feat_extractor), # apply model on each 30-s window\n nn.Sequential( # apply linear layer on concatenated feature vectors\n nn.Flatten(start_dim=1),\n nn.Dropout(0.5),\n nn.Linear(feat_extractor.len_last_layer * n_windows, n_classes),\n ),\n)\n\n# Send model to the selected accelerator\nif device != \"cpu\":\n model.to(device)"
181181
]
182182
},
183183
{
@@ -195,14 +195,14 @@
195195
},
196196
"outputs": [],
197197
"source": [
198-
"from skorch.callbacks import EpochScoring\nfrom skorch.helper import predefined_split\n\nfrom braindecode import EEGClassifier\n\nlr = 1e-3\nbatch_size = 32\nn_epochs = 10\n\ntrain_bal_acc = EpochScoring(\n scoring=\"balanced_accuracy\",\n on_train=True,\n name=\"train_bal_acc\",\n lower_is_better=False,\n)\nvalid_bal_acc = EpochScoring(\n scoring=\"balanced_accuracy\",\n on_train=False,\n name=\"valid_bal_acc\",\n lower_is_better=False,\n)\ncallbacks = [(\"train_bal_acc\", train_bal_acc), (\"valid_bal_acc\", valid_bal_acc)]\n\nclf = EEGClassifier(\n model,\n criterion=torch.nn.CrossEntropyLoss,\n criterion__weight=torch.Tensor(class_weights).to(device),\n optimizer=torch.optim.Adam,\n iterator_train__shuffle=False,\n iterator_train__sampler=train_sampler,\n iterator_valid__sampler=valid_sampler,\n train_split=predefined_split(valid_set), # using valid_set for validation\n optimizer__lr=lr,\n batch_size=batch_size,\n callbacks=callbacks,\n device=device,\n classes=np.unique(y_train),\n)\n# Model training for a specified number of epochs. `y` is None as it is already\n# supplied in the dataset.\nclf.fit(train_set, y=None, epochs=n_epochs)"
198+
"from skorch.callbacks import EarlyStopping, EpochScoring\nfrom skorch.helper import predefined_split\n\nfrom braindecode import EEGClassifier\n\nlr = 1e-3\nbatch_size = 32\nn_epochs = 10\n\ntrain_bal_acc = EpochScoring(\n scoring=\"balanced_accuracy\",\n on_train=True,\n name=\"train_bal_acc\",\n lower_is_better=False,\n)\nvalid_bal_acc = EpochScoring(\n scoring=\"balanced_accuracy\",\n on_train=False,\n name=\"valid_bal_acc\",\n lower_is_better=False,\n)\ncallbacks = [\n (\"train_bal_acc\", train_bal_acc),\n (\"valid_bal_acc\", valid_bal_acc),\n (\"early_stopping\", EarlyStopping(patience=10, load_best=True)),\n]\n\nclf = EEGClassifier(\n model,\n criterion=torch.nn.CrossEntropyLoss,\n criterion__weight=torch.Tensor(class_weights).to(device),\n optimizer=torch.optim.Adam,\n iterator_train__shuffle=False,\n iterator_train__sampler=train_sampler,\n iterator_valid__sampler=valid_sampler,\n train_split=predefined_split(valid_set), # using valid_set for validation\n optimizer__lr=lr,\n batch_size=batch_size,\n callbacks=callbacks,\n device=device,\n classes=np.unique(y_train),\n)\n# Model training for a specified number of epochs. `y` is None as it is already\n# supplied in the dataset.\nclf.fit(train_set, y=None, epochs=n_epochs)"
199199
]
200200
},
201201
{
202202
"cell_type": "markdown",
203203
"metadata": {},
204204
"source": [
205-
"## Plot results\n\nWe use the history stored by Skorch during training to plot the performance of\nthe model throughout training. Specifically, we plot the loss and the balanced\nbalanced accuracy for the training and validation sets.\n\n"
205+
"## Training for longer\n\nThe gallery build above uses only ``n_epochs = 10``. When trained\noffline for up to 100 epochs with early stopping, the model reaches\n**64.2 % balanced accuracy on the held-out recording (chance = 20 %)**.\n\nWe can load the pretrained checkpoint from the Hugging Face Hub and\ninspect the full training curves:\n\n"
206206
]
207207
},
208208
{
@@ -213,14 +213,14 @@
213213
},
214214
"outputs": [],
215215
"source": [
216-
"import matplotlib.pyplot as plt\nimport pandas as pd\n\n# Extract loss and balanced accuracy values for plotting from history object\ndf = pd.DataFrame(clf.history.to_list())\ndf.index.name = \"Epoch\"\nfig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 7), sharex=True)\ndf[[\"train_loss\", \"valid_loss\"]].plot(color=[\"r\", \"b\"], ax=ax1)\ndf[[\"train_bal_acc\", \"valid_bal_acc\"]].plot(color=[\"r\", \"b\"], ax=ax2)\nax1.set_ylabel(\"Loss\")\nax2.set_ylabel(\"Balanced accuracy\")\nax1.legend([\"Train\", \"Valid\"])\nax2.legend([\"Train\", \"Valid\"])\nfig.tight_layout()\nplt.show()"
216+
"import warnings\n\nrepo_id = \"braindecode/plot_sleep_staging_chambon2018\"\ntry:\n from huggingface_hub import hf_hub_download\n\n clf.initialize()\n clf.load_params(\n f_params=hf_hub_download(repo_id, \"params.safetensors\"),\n f_history=hf_hub_download(repo_id, \"history.json\"),\n use_safetensors=True,\n )\nexcept Exception as exc:\n warnings.warn(\n f\"Could not load pretrained checkpoint from {repo_id} ({exc}); \"\n \"continuing with the locally trained short-run model.\",\n stacklevel=2,\n )"
217217
]
218218
},
219219
{
220220
"cell_type": "markdown",
221221
"metadata": {},
222222
"source": [
223-
"Finally, we also display the confusion matrix and classification report:\n\n\n"
223+
"### Plot training curves\n\n"
224224
]
225225
},
226226
{
@@ -231,14 +231,14 @@
231231
},
232232
"outputs": [],
233233
"source": [
234-
"from sklearn.metrics import classification_report, confusion_matrix\n\nfrom braindecode.visualization import plot_confusion_matrix\n\ny_true = [valid_set[[i]][1][0] for i in range(len(valid_sampler))]\ny_pred = clf.predict(valid_set)\n\nconfusion_mat = confusion_matrix(y_true, y_pred)\n\nplot_confusion_matrix(\n confusion_mat=confusion_mat, class_names=[\"Wake\", \"N1\", \"N2\", \"N3\", \"REM\"]\n)\n\nprint(classification_report(y_true, y_pred))"
234+
"import matplotlib.pyplot as plt\nimport pandas as pd\n\ndf = pd.DataFrame(clf.history.to_list())\ndf.index.name = \"Epoch\"\nfig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 7), sharex=True)\ndf[[\"train_loss\", \"valid_loss\"]].plot(color=[\"r\", \"b\"], ax=ax1)\ndf[[\"train_bal_acc\", \"valid_bal_acc\"]].plot(color=[\"r\", \"b\"], ax=ax2)\nax1.set_ylabel(\"Loss\")\nax2.set_ylabel(\"Balanced accuracy\")\nax1.legend([\"Train\", \"Valid\"])\nax2.legend([\"Train\", \"Valid\"])\nax1.grid(alpha=0.3)\nax2.grid(alpha=0.3)\nfig.tight_layout()\nplt.show()"
235235
]
236236
},
237237
{
238238
"cell_type": "markdown",
239239
"metadata": {},
240240
"source": [
241-
"Finally, we can also visualize the hypnogram of the recording we used for\nvalidation, with the predicted sleep stages overlaid on top of the true\nsleep stages. We can see that the model cannot correctly identify the\ndifferent sleep stages with this amount of training.\n\n"
241+
"Finally, we also display the confusion matrix and classification report:\n\n\n"
242242
]
243243
},
244244
{
@@ -249,14 +249,25 @@
249249
},
250250
"outputs": [],
251251
"source": [
252-
"import matplotlib.pyplot as plt\n\nfig, ax = plt.subplots(figsize=(15, 5))\nax.plot(y_true, color=\"b\", label=\"Expert annotations\")\nax.plot(y_pred.flatten(), color=\"r\", label=\"Predict annotations\", alpha=0.5)\nax.set_xlabel(\"Time (epochs)\")\nax.set_ylabel(\"Sleep stage\")"
252+
"from sklearn.metrics import ConfusionMatrixDisplay, classification_report\n\ny_true = [valid_set[i][1] for i in valid_sampler]\ny_pred = clf.predict(valid_set)\n\nConfusionMatrixDisplay.from_predictions(\n y_true,\n y_pred,\n labels=[0, 1, 2, 3, 4],\n display_labels=[\"Wake\", \"N1\", \"N2\", \"N3\", \"REM\"],\n)\n\nprint(classification_report(y_true, y_pred))"
253253
]
254254
},
255255
{
256256
"cell_type": "markdown",
257257
"metadata": {},
258258
"source": [
259-
"Our model was able to learn despite the low amount of data that was available\n(only two recordings in this example) and reached a balanced accuracy of\nabout 36% in a 5-class classification task (chance-level = 20%) on held-out\ndata.\n\n<div class=\"alert alert-info\"><h4>Note</h4><p>To further improve performance, more recordings should be included in the\n training set, and hyperparameters should be selected accordingly.\n Increasing the sequence length was also shown in [1]_ to help improve\n performance, especially when few EEG channels are available.</p></div>\n\n"
259+
"Finally, we can also visualize the hypnogram of the recording we used for\nvalidation, with the predicted sleep stages overlaid on top of the true\nsleep stages. We can see that the model cannot correctly identify the\ndifferent sleep stages with this amount of training.\n\n"
260+
]
261+
},
262+
{
263+
"cell_type": "code",
264+
"execution_count": null,
265+
"metadata": {
266+
"collapsed": false
267+
},
268+
"outputs": [],
269+
"source": [
270+
"import matplotlib.pyplot as plt\n\nfig, ax = plt.subplots(figsize=(15, 5))\nax.plot(y_true, color=\"b\", label=\"Expert annotations\")\nax.plot(y_pred.flatten(), color=\"r\", label=\"Predict annotations\", alpha=0.5)\nax.set_xlabel(\"Time (epochs)\")\nax.set_ylabel(\"Sleep stage\")"
260271
]
261272
},
262273
{

dev/_downloads/0a8b8bc2f1b933515b7b4101626dd179/plot_bcic_iv_2a_moabb_trial.py

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@
224224
# cross validation on your training data.
225225
#
226226

227-
from skorch.callbacks import LRScheduler
227+
from skorch.callbacks import EarlyStopping, LRScheduler
228228
from skorch.helper import predefined_split
229229

230230
from braindecode import EEGClassifier
@@ -251,26 +251,51 @@
251251
batch_size=batch_size,
252252
callbacks=[
253253
"accuracy",
254-
("lr_scheduler", LRScheduler("CosineAnnealingLR", T_max=n_epochs - 1)),
254+
("lr_scheduler", LRScheduler("CosineAnnealingLR", T_max=max(1, n_epochs - 1))),
255+
("early_stopping", EarlyStopping(patience=10, load_best=True)),
255256
],
256257
device=device,
257258
classes=classes,
258259
)
259260
# Model training for the specified number of epochs. ``y`` is ``None`` as it is
260261
# already supplied in the dataset.
261-
_ = clf.fit(train_set, y=None, epochs=n_epochs)
262-
262+
clf.fit(train_set, y=None, epochs=n_epochs)
263263

264264
######################################################################
265-
# Plotting Results
266-
# ----------------
265+
# Training for longer
266+
# -------------------
267267
#
268-
268+
# The gallery build above uses only ``n_epochs = 4``. When trained
269+
# offline for up to 100 epochs with early stopping, the model reaches
270+
# **68.4 % accuracy on the held-out session (chance = 25 %)**.
271+
#
272+
# We can load the pretrained checkpoint from the Hugging Face Hub and
273+
# inspect the full training curves. If the optional ``huggingface_hub``
274+
# dependency is missing or the download fails, we continue with the
275+
# locally trained short-run model.
276+
277+
import warnings
278+
279+
repo_id = "braindecode/plot_bcic_iv_2a_moabb_trial"
280+
try:
281+
from huggingface_hub import hf_hub_download
282+
283+
clf.initialize()
284+
clf.load_params(
285+
f_params=hf_hub_download(repo_id, "params.safetensors"),
286+
f_history=hf_hub_download(repo_id, "history.json"),
287+
use_safetensors=True,
288+
)
289+
except Exception as exc:
290+
warnings.warn(
291+
f"Could not load pretrained checkpoint from {repo_id} ({exc}); "
292+
"continuing with the locally trained short-run model.",
293+
stacklevel=2,
294+
)
269295

270296
######################################################################
271-
# Now we use the history stored by skorch throughout training to plot
272-
# accuracy and loss curves.
273-
#
297+
# Plot training curves
298+
# ~~~~~~~~~~~~~~~~~~~~
274299

275300
import matplotlib.pyplot as plt
276301
import pandas as pd
@@ -331,26 +356,19 @@
331356
#
332357

333358

334-
from sklearn.metrics import confusion_matrix
335-
336-
from braindecode.visualization import plot_confusion_matrix
359+
from sklearn.metrics import ConfusionMatrixDisplay
337360

338-
# generate confusion matrices
339-
# get the targets
340361
y_true = valid_set.get_metadata().target
341362
y_pred = clf.predict(valid_set)
342363

343-
# generating confusion matrix
344-
confusion_mat = confusion_matrix(y_true, y_pred)
345-
346-
# add class labels
347-
# label_dict is class_name : str -> i_class : int
348364
label_dict = windows_dataset.datasets[0].window_kwargs[0][1]["mapping"]
349-
# sort the labels by values (values are integer class labels)
350-
labels = [k for k, v in sorted(label_dict.items(), key=lambda kv: kv[1])]
365+
sorted_items = sorted(label_dict.items(), key=lambda kv: kv[1])
366+
labels = [k for k, _ in sorted_items]
367+
class_ids = [v for _, v in sorted_items]
351368

352-
# plot the basic conf. matrix
353-
plot_confusion_matrix(confusion_mat, class_names=labels)
369+
ConfusionMatrixDisplay.from_predictions(
370+
y_true, y_pred, labels=class_ids, display_labels=labels
371+
)
354372

355373
#############################################################
356374
#
Binary file not shown.

0 commit comments

Comments
 (0)