Skip to content

Commit 61dc0d8

Browse files
Release docs for braindecode 1.5.0
Snapshot 1.5/ from local make html build of release/1.5.0 (commit 1458013e). Refresh stable/ to point at the 1.5.0 docs.
1 parent 609e4de commit 61dc0d8

1,209 files changed

Lines changed: 474542 additions & 46043 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.

1.5/.buildinfo

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Sphinx build info version 1
2+
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
3+
config: aab439c05bde2ef48c2c16bf3ee5d978
4+
tags: 645f666f9bcd5a90fca523b33c5a78b7

1.5/.nojekyll

Whitespace-only changes.

1.5/CNAME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
braindecode.org
Binary file not shown.

stable/auto_examples/_notebooks/applied_examples/plot_sleep_staging_chambon2018.ipynb renamed to 1.5/_downloads/090305d06248840b75133975e5121f41/plot_sleep_staging_chambon2018.ipynb

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
{
22
"cells": [
3-
{
4-
"id": "22c7ced8",
5-
"cell_type": "code",
6-
"metadata": {
7-
"language": "python"
8-
},
9-
"execution_count": null,
10-
"source": "%pip install braindecode",
11-
"outputs": []
12-
},
133
{
144
"cell_type": "markdown",
155
"metadata": {},
@@ -187,7 +177,7 @@
187177
},
188178
"outputs": [],
189179
"source": [
190-
"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)"
191181
]
192182
},
193183
{
@@ -205,14 +195,14 @@
205195
},
206196
"outputs": [],
207197
"source": [
208-
"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)"
209199
]
210200
},
211201
{
212202
"cell_type": "markdown",
213203
"metadata": {},
214204
"source": [
215-
"## 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"
216206
]
217207
},
218208
{
@@ -223,14 +213,14 @@
223213
},
224214
"outputs": [],
225215
"source": [
226-
"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 )"
227217
]
228218
},
229219
{
230220
"cell_type": "markdown",
231221
"metadata": {},
232222
"source": [
233-
"Finally, we also display the confusion matrix and classification report:\n\n\n"
223+
"### Plot training curves\n\n"
234224
]
235225
},
236226
{
@@ -241,14 +231,14 @@
241231
},
242232
"outputs": [],
243233
"source": [
244-
"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()"
245235
]
246236
},
247237
{
248238
"cell_type": "markdown",
249239
"metadata": {},
250240
"source": [
251-
"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"
252242
]
253243
},
254244
{
@@ -259,14 +249,25 @@
259249
},
260250
"outputs": [],
261251
"source": [
262-
"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))"
263253
]
264254
},
265255
{
266256
"cell_type": "markdown",
267257
"metadata": {},
268258
"source": [
269-
"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\")"
270271
]
271272
},
272273
{
@@ -293,7 +294,7 @@
293294
"name": "python",
294295
"nbconvert_exporter": "python",
295296
"pygments_lexer": "ipython3",
296-
"version": "3.12.13"
297+
"version": "3.12.12"
297298
}
298299
},
299300
"nbformat": 4,

0 commit comments

Comments
 (0)