Note
Go to the end to download the full example code.
Track 3 – Sleep onset (cross-user latency prediction)¶
Given continuous four-channel wearable EEG recorded at home, predict the seconds remaining until the first N2 epoch – the first epoch scored N2, not the start of a run of consecutive N2 epochs. The competition tests generalisation across nights and across sleepers on one consumer device: training and evaluation use the same Muse headband, the same home protocol and the same target. Precise onset timing replaces full hypnogram reconstruction because a sparse wearable montage supports it poorly.
Shift: nights and sleepers, on one consumer device. The evaluation cohort contains both sleepers seen in training and sleepers never seen in training, so a model has to hold up on new nights from familiar people as well as on new people. Night-to-night variation, motion artifacts, impedance changes and channel dropout come with the home setting.
Headline metric: binned onset error in seconds, lower is better, but the binning is weighted differently in each phase. Both phases split the error by true time to onset into [0, 40), [40, 90), [90, 300) and [300, 600] s.
Sealed Muse phase: W-bMAE. The four ranges carry severity weights of 10x, 5x, 3x and 1x, so an error close to onset costs far more than one ten minutes out. W-bMAE is then computed separately over seen subjects (new nights from people in the training set) and unseen subjects, and the ranking score is the macro-average of those two, weighting night-to-night and inter-person generalisation equally.
Current Sleep-EDF warm-up: unweighted bMAE plus plain MAE. This is a temporary proxy. It switches to the Muse W-bMAE scheme when the Muse warm-up data lands, and the Codabench scorer and this start kit are due to be updated together at that point.
neuralbench.metrics.BinnedMAEimplements the unweighted form, soval/bmaeandtest/bmaehere match the warm-up scorer today and not the sealed one. Nothing in the start kit computes the severity weights or the seen/unseen macro-average.Data: continuous Muse wearable EEG sampled at 128 Hz, with
n2_onsetannotations on the training cohort and a separate hidden evaluation cohort recorded with the same hardware and protocol. More details to come on the cohort size. The onset thatAddSleepOnsetTargetsextracts is the earliest annotated N2 event of the recording, which is exactly the competition’s definition.
Note
The Muse training set is released through NeuralBench when submissions open. Until then, this starter kit runs on polysomnography datasets – the data format, the target extractor and the metric are identical, but the recording hardware is not, so the starter kit has a device gap the competition itself does not (see Where the competition data diverges).
New to NeuralBench? Start here¶
This page is a track guide, not an introduction to the ecosystem. If you arrived straight from the competition website:
Challenge overview – what NeuralBench is, how it relates to the competition, and the baseline numbers for all four tracks.
Installation and the quickstart – get a task running on a 1.5 GB dataset before you download anything large.
Official Track 3 guide – registration, rules, data access, prizes, leaderboard. Authoritative on every competition matter; this page only covers the code.
Where to find this task in NeuralBench¶
The matching task in NeuralBench is Sleep onset prediction.
CLI:
neuralbench eeg sleep_onsetDefault dataset:
Kemp2000Analysis(Sleep-EDF Expanded, 78 participants recorded over up to two nights each, 2 EEG channels, full polysomnography).Target: the time remaining until the first N2 epoch, which
AddSleepOnsetTargets+SleepOnsetTargetExtractorrecompute for every window asclip(n2_onset - window_stop, 0, 600)seconds. The task therefore predicts once per 5-second window rather than once per recording, and the competition’s singletau_hatiswindow_stop + predictionread off any window within 600 s of onset, where the cap has not saturated the target.Headline metric key:
test/bmae(binned MAE in seconds).
What the config is. A NeuralBench task is one config.yaml, and
nothing else: a YAML overlay on neuralbench/defaults/config.yaml naming
the study to load, how to split it, what the target is, the loss, and the
metrics. Reading it is the fastest way to know exactly what the baseline
does.
Show tasks/eeg/sleep_onset/config.yaml
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.
data:
study:
source:
name: Kemp2000Analysis
annotate_sleep_onset:
name: AddSleepOnsetTargets
max_pre_n2_s: 1200.0
split:
name: SklearnSplit
split_by: subject
valid_split_ratio: 0.2
test_split_ratio: 0.2
valid_random_state: 33
test_random_state: 33
# Sleep-EDF (Kemp2000) recordings only expose bipolar derivations (Fpz-Cz,
# Pz-Oz) whose MNE ch_locs are NaN, so the default ``ch_locs``-derived
# position mapping in ``ChannelPositions`` produces all-``INVALID_VALUE``
# positions. Explicitly use the standard_1020 montage so the split-on-``-``
# fallback resolves Fpz-Cz -> Fpz and Pz-Oz -> Pz to real 3D coordinates.
channel_positions:
layout_or_montage_name: standard_1020
target:
=replace=: true
name: SleepOnsetTargetExtractor
event_types: SleepOnsetMarker
aggregation: trigger
cap_s: 600.0
trigger_event_type: SleepOnsetMarker
start: 0.0
duration: 5.0
stride: 5.0
sampler:
name: RegressionBinSampler
bin_edges: [0.0, 40.0, 90.0, 300.0, 600.0]
summary_columns: [n2_onset]
brain_model_output_size: &brain_model_output_size 1
trainer_config:
monitor: val/bmae
mode: min
patience: 7
n_epochs: 40
loss:
name: MultiLoss
losses:
mse:
name: MSELoss
weights:
mse: 0.0001 # targets are in [0, 600] s; weight=0.0001 (0.01^2) keeps MSE <= ~36 so gradient clipping does not saturate.
metrics: !!python/object/apply:neuralbench.defaults.metrics.get_sleep_onset_metric_configs []
Split and model selection¶
Split. Participant-level 60 / 20 / 20, drawn by SklearnSplit with
split_by: subject. Every recording from a participant lands in exactly
one fold, so no sleeper is shared between train, validation and test –
the starter kit’s test score therefore measures generalisation to people
the model has never seen. Both split seeds are fixed at 33, so the
partition is identical on every machine and every run. On Sleep-EDF’s 78
participants that resolves to 46 train / 16 validation / 16 test.
That 16-participant test partition is the current Codabench warm-up
evaluation set: the scorer runs on the same Sleep-EDF subset this split
produces at random state 33. A test/bmae here and a warm-up
leaderboard score are therefore the same measurement, which makes this
the one track where a local number should line up with the board.
The sealed phase is a different story. Its Muse cohort mixes seen and unseen sleepers, while this split holds every sleeper out, so the sealed score is not something the starter kit can approximate.
Model selection. The checkpoint with the lowest ``val/bmae`` is
kept – validation binned MAE in seconds, the same quantity as the
warm-up test/bmae, just on the validation fold. Training runs for at
most 40 epochs and stops early after 7 epochs without improvement; only
that single best checkpoint is scored on test. Note this selects on the
unweighted metric; when the sealed W-bMAE weights arrive, a model tuned
this way will be under-weighting the near-onset range that matters most.
How to change it, in increasing order of effort:
--dataset <name>mergestasks/eeg/sleep_onset/datasets/<name>.yamlover the base config. That is how the two extra PSG corpora below are selected, and how the Muse corpus will be once it ships.-m <model>and-w <preset>swap the architecture and the adaptation strategy (frozen probe, LoRA, full fine-tuning) without touching any file.Anything else – the 600 s cap, the 5 s window, the learning rate – is a config edit. From Python, pass dotted keys to
evaluate_model()(overrides={"data.duration": 30.0}). From a source checkout (pip install -e), editconfig.yamldirectly, or add your owndatasets/*.yamlvariant beside the existing ones and select it with--dataset. Both routes are described in Adding a New Task.
Reproducing the baseline¶
This is the cheapest of the four tracks to get running end to end, which makes it a good first target whichever track you plan to submit to.
# 1. Download Sleep-EDF into DATA_DIR: ~7 GB, about 4 minutes on a fast
# link. One-off per machine, and safe to interrupt and re-run.
neuralbench eeg sleep_onset --download
# 2. Preprocess into CACHE_DIR -- resample, filter, scale, and cut the
# 153 whole-night recordings into 5 s windows once, so every later run
# reads the cache instead. No GPU needed. ~15 min for eegnet's cache
# and ~9 min for reve's, spread over 20 SLURM jobs. Note the cache
# (~18 GB) is larger than the raw download.
neuralbench eeg sleep_onset --prepare
# 3. Sanity check before you queue anything: 2 epochs, a data subset, one
# seed, always in-process, so progress lands in your terminal. ~1 min
# on one V100 with the cache warm. Name the model you actually plan to
# run -- a bare --debug takes the config default, which is EEGNet.
neuralbench eeg sleep_onset -m eegnet --debug
# 4. Same check for the foundation model. The first build pulls REVE's
# weights from the HuggingFace Hub, which needs network access; doing
# it here rather than in a queued run keeps any failure in your
# terminal instead of a job log.
neuralbench eeg sleep_onset -m reve --debug
# 5. Full baseline -- task-specific model (EEGNet). ~6 min per seed, and
# the default grid is three seeds (concurrent on SLURM).
neuralbench eeg sleep_onset -m eegnet
# 6. Full baseline -- foundation model (REVE), fine-tuned end to end.
# ~8 min per seed. ~69M parameters against EEGNet's ~1.5k, all of them
# trainable here, so this one wants a datacentre GPU rather than a
# laptop; it also preprocesses at 200 Hz against the 120 Hz default,
# warming a second cache.
neuralbench eeg sleep_onset -m reve
Pretrained model weights covers the hub cache, and no run has a CPU
fallback – --debug included.
Steps 5 and 6 cache the test-metric dictionary under SAVE_DIR –
test/bmae is the headline number – and re-running the same command with
--plot-cached turns those cached metrics into comparison plots and CSV
tables without retraining. On SLURM they return as soon as the grid is
queued, so the numbers appear in the job logs rather than your terminal; see
Collecting and plotting your results.
Evaluating a model of your own¶
A model that lives in your own codebase needs no YAML here:
evaluate_model() takes the built instance, wraps it in a
probe sized to the task, and returns the scores as a DataFrame.
from neuralbench import check_model, evaluate_model
print(check_model(my_model, "eeg", "sleep_onset")) # shapes only, seconds
scores = evaluate_model(my_model, "eeg", "sleep_onset", name="my-fm", debug=True)
See Evaluating your own model for what
forward has to accept, the adaptation presets, and how to fan the runs
out to SLURM.
Where the competition data diverges¶
The competition’s own shift is cross-user on a single device. The starter kit adds a second, artificial shift on top, because the only public sleep-onset data with scored hypnograms is clinical polysomnography. Three axes therefore differ from what you will be scored on:
Hardware: research-grade PSG (Sleep-EDF and the two corpora below) vs the consumer-grade Muse headband (4-channel frontal EEG, no EOG). Expect to drop or re-map channels in the dataloader, and treat any number you get here as an upper bound on signal quality.
Cohort and recording context: laboratory monitored sleep vs home recordings with movement artifacts, impedance changes and channel dropout.
Annotations: full hypnograms vs
n2_onsetevents only on the training set. NeuralBench already trains onSleepOnsetMarkerevents, so the model interface does not change.
The three PSG corpora registered for this task are exactly the three public
datasets the competition lists for Track 3, and all use the same
AddSleepOnsetTargets + bmae pipeline as the default:
# Sleep-EDF Expanded (the default), ~7 GB raw + ~18 GB cache
neuralbench eeg sleep_onset
# PhysioNet/CinC Challenge 2018: 994 labelled subjects, by far the most
# sleepers, so the best stress test of cross-user behaviour at the scale
# the Muse training set will have. Also by far the largest: ~285 GB raw
# plus ~40 GB of cache.
neuralbench eeg sleep_onset --dataset ghassemi2018you
# HMC Sleep Staging: 151 clinical whole-night recordings, ~17 GB raw
neuralbench eeg sleep_onset --dataset alvarez2022haaglanden
Once the Muse study is registered, switching is a single
data.study.source.name: Interaxon2026Muse override (or
--dataset interaxon2026muse if a datasets/ YAML ships).
Submission outputs (per the competition):
a direct onset estimate
tau_hatin seconds, orper-window time-to-onset predictions, or
per-window sleep probabilities.
The current NeuralBench head produces the second format directly, and the first by the conversion above.
Total running time of the script: (0 minutes 0.000 seconds)