Skip to main content
Each 30 s window becomes one L2-normalised 768-d vector, so a matrix product is a cosine-similarity search. This recipe embeds 20 nights of public sleep EEG, queries one window, returns its nearest windows from other people, and measures how often those neighbours carry the same scored sleep stage.
Research demonstration, not clinical validation. Sleep stages are used here only as an outside label to check what the neighbours have in common.

Data and protocol

  • Dataset: Sleep-EDF Expanded, sleep-cassette study, first night of subjects 0 to 19 (SC4001 to SC4191, PSG and hypnogram files). Check the downloads against PhysioNet’s SHA256SUMS.txt: a truncated EDF still opens.
  • Signal: the two EEG channels, Fpz-Cz and Pz-Oz, at 100 Hz as stored. Each night is cropped to 30 minutes before the first and after the last sleep epoch.
  • Windows: the default stride of 30 s, so windows line up with the hypnogram’s 30 s epochs. 20,650 windows were embedded; 20,626 have a stage (W, N1, N2, N3, REM; stages 3 and 4 merged into N3). The 24 windows scored as movement or unknown are left out of the search.
  • Search: cosine similarity over all labelled windows, with every window of the query’s own night masked, so neighbours always come from another subject. K = 10.
  • Chance: for a query of stage s, the fraction of other-subject windows that have stage s. This is the same-stage rate of 10 neighbours drawn at random.
  • Run: 10 November 2026, neuroencoder 2.1.0, hosted API, Python 3.11.

1. Embed the nights

embed_nights.py
Needs pip install neuroencoder mne and a key (see Quickstart). The 20 nights took 1,201 s of wall time in the embedding calls (37 to 83 s per night) and billed 619,500 s, about 172 h of EEG. That is more than one trial key’s 100 h per month; with a trial key, embed 10 nights or fewer.
similarity_search.py
Plain NumPy, no index library. Using all 20,626 windows as queries took 101 s on a 16-core machine; a single query is one matrix-vector product. t is seconds from the start of the cropped night, and night n is the n-th file in sorted order (SC4001 is night 0).

Output

Grouped bars: same-stage fraction of the 10 nearest other-subject windows versus chance. W 0.81 vs 0.17, N1 0.24 vs 0.06, N2 0.73 vs 0.44, N3 0.78 vs 0.14, REM 0.73 vs 0.18, all 0.72 vs 0.28.

Fraction of the 10 nearest other-subject windows that share the query's stage, against chance, per stage and over all 20,626 windows.

What this shows and what it does not

  • Across subjects, the nearest windows share the query’s stage far more often than chance for every stage: 72.2% of neighbours overall against 28.3% by chance.
  • N1 is the weak stage: 24.0% of neighbours are N1, against 5.9% by chance, so most neighbours of an N1 window carry another stage.
  • The overall figure is weighted by stage frequency; N2 alone is 45% of windows. The per-stage rows are the fairer read.
  • In the example, 6 of the 10 neighbours come from night 17. Neighbours can cluster on a few nights, so a search result is not a sample of the population.
  • Same-stage rate is one proxy. It does not show that neighbours match on anything clinically relevant, it is not a sleep stager (each window is searched on its own, with no context from neighbouring epochs), and it was measured on the Fpz-Cz and Pz-Oz channels of 20 recordings from one public dataset only.
For an interactive view of the same kind of space, see Visualization.