Sleep-EDF 睡眠信号数据集深度解析:从 EDF 文件到可复现睡眠分期样本


Sleep-EDF 是睡眠信号处理和自动睡眠分期领域最常用的公开数据集之一。它包含整夜多导睡眠图(Polysomnography,PSG)信号,以及由专业人员标注的睡眠阶段。

与可穿戴设备数据相比,Sleep-EDF 的优势在于信号类型标准、采样时间长、标签完整,非常适合用于:

  • 自动睡眠分期
  • EEG/EOG/EMG 信号分析
  • 睡眠结构和睡眠周期研究
  • 时频特征提取
  • CNN、RNN、Transformer 睡眠分类模型
  • 跨被试泛化实验

本文参考了 Sleep-EDF 官方 PhysioNet 页面MNE 官方睡眠分析教程,并结合实际本地预处理过程中容易踩坑的细节进行整理。

Tag#SleepEDF #睡眠分期 #EEG #EOG #EMG #PSG #生理信号处理 #MNE #深度学习


一、Sleep-EDF 数据集概览

Sleep-EDF 最初由 Bob Kemp 等研究者整理,数据目前主要托管在 PhysioNet。

官方的 Sleep-EDF Expanded 版本包含:

  • 197 份整夜 PSG 记录
  • EEG、EOG、下巴 EMG 和事件标记
  • 部分记录还包含鼻口气流和直肠温度
  • 专业人员标注的睡眠阶段
  • EDF/EDF+ 格式文件
  • 总解压体积约 8.1 GB

官方数据分为两个研究子集:

子集 全称 记录数 研究对象 采集环境
SC Sleep Cassette 153 健康受试者,25--101 岁 家庭环境,约 20 小时
ST Sleep Telemetry 44 22 名有轻度入睡困难的受试者 医院环境,约 9 小时

SC 数据来自 1987--1991 年的年龄效应研究,受试者没有使用睡眠相关药物。每位受试者通常有两晚记录,但部分记录因磁带或设备故障缺失。

ST 数据来自 1994 年的替马西泮(Temazepam)研究,每位受试者通常有一晚服药记录和一晚安慰剂记录。因此,ST 数据存在明显的"药物条件"和"医院环境"因素。

这些信息对于模型实验非常重要:如果直接将 SC 和 ST 混在一起,模型可能学习到的是"家庭 vs 医院"或"服药 vs 未服药"的差异,而不是真正的睡眠阶段特征。

官方数据说明见:Sleep-EDF Database Expanded


二、Sleep-EDF-20、Sleep-EDF-78 到底是什么?

实际阅读论文时,经常会看到以下名称:

常见名称 通常指代
Sleep-EDF-20 2013 年版本中的 SC 子集,约 20 名受试者、39 晚记录
Sleep-EDF-78 2018 年扩充后的 SC 子集,约 78 名受试者、153 晚记录
Sleep-EDF Expanded 完整扩展库,共 197 条记录,包含 SC 和 ST

需要注意的是,Sleep-EDF-20Sleep-EDF-78 主要是论文中的习惯叫法,并不是 PhysioNet 官方目录名称。

如果使用 MNE 的 fetch_data(),默认下载的是 SC 年龄研究部分,即 153 条记录,而不是完整的 197 条 SC+ST 记录。MNE 文档对此有明确说明:mne.datasets.sleep_physionet.age.fetch_data

因此,复现实验时一定要确认:

  1. 使用的是 SC 还是 ST;
  2. 使用的是 2013 版本还是 2018 扩展版本;
  3. 是否包含两个子集;
  4. 论文中的 "Sleep-EDF" 是否实际上只指 Sleep-EDF-20。

三、文件组织结构

下载后的目录大致如下:

perl 复制代码
sleep-edfx/
└── 1.0.0/
    ├── sleep-cassette/
    │   ├── SC4001E0-PSG.edf
    │   ├── SC4001EC-Hypnogram.edf
    │   ├── SC4002E0-PSG.edf
    │   ├── SC4002EC-Hypnogram.edf
    │   └── ...
    ├── sleep-telemetry/
    │   ├── ST7011J0-PSG.edf
    │   ├── ST7011JP-Hypnogram.edf
    │   └── ...
    ├── RECORDS
    ├── SC-subjects.xls
    ├── ST-subjects.xls
    └── SHA256SUMS.txt

每个样本通常由两个文件组成:

复制代码
SC4001E0-PSG.edf
SC4001EC-Hypnogram.edf

前者保存原始 PSG 信号,后者保存睡眠分期标注。

一个容易忽略的文件匹配问题

PSG 文件和 Hypnogram 文件的文件名并不是简单地完全对应。

例如:

makefile 复制代码
PSG:       SC4001E0-PSG.edf
Hypnogram: SC4001EC-Hypnogram.edf

最后一位不同。官方说明中,Hypnogram 文件名的第八个字符与评分人员有关。

因此,不能简单使用:

ini 复制代码
hypnogram = psg_name.replace("-PSG", "-Hypnogram")

更稳妥的做法是匹配记录前缀:

python 复制代码
from pathlib import Path


def find_hypnogram(psg_path: Path) -> Path:
    stem = psg_path.name.removesuffix("-PSG.edf")

    # 去掉 PSG 文件末尾的设备/记录标识
    record_prefix = stem[:-1]

    candidates = sorted(
        psg_path.parent.glob(f"{record_prefix}?-Hypnogram.edf")
    )

    if len(candidates) != 1:
        raise RuntimeError(
            f"无法唯一匹配 Hypnogram: {psg_path}, candidates={candidates}"
        )

    return candidates[0]

这是本地批量处理时非常常见的错误来源。


四、信号通道与采样率

4.1 Sleep Cassette 数据

SC 数据通常包含:

信号 典型通道 采样率
EEG Fpz-Cz 100 Hz
EEG Pz-Oz 100 Hz
EOG Horizontal 100 Hz
EMG Submental 1 Hz
鼻口气流 Oro-nasal airflow 1 Hz
直肠温度 Rectal temperature 1 Hz
事件标记 Event marker 1 Hz

官方特别说明,SC 数据中的下巴 EMG 并不是未经处理的高采样率原始 EMG,而是经过高通、整流、低通后的 EMG 包络,并以 1 Hz 采样。

因此:

不要把 EMG submental 当成 100 Hz 原始肌电信号。

如果将它插值到 100 Hz,只是为了和 EEG 在数组维度上对齐,并不会增加真实信息量。

4.2 Sleep Telemetry 数据

ST 数据通常包含:

信号 采样率
EEG 100 Hz
EOG 100 Hz
EMG 100 Hz
Event marker 1 Hz

ST 文件中未记录的信号已经被官方移除,所以不同文件的通道列表可能存在差异,不能假设所有 EDF 文件都有完全相同的通道。

读取后应始终检查:

scss 复制代码
print(raw.ch_names)
print(raw.info["sfreq"])

五、睡眠阶段标签

Sleep-EDF 的 Hypnogram 文件使用 EDF+ 注释格式,官方提供的标签包括:

原始标签 含义
Sleep stage W 清醒 Wake
Sleep stage 1 R&K N1
Sleep stage 2 R&K N2
Sleep stage 3 R&K N3
Sleep stage 4 R&K N4
Sleep stage R REM
Sleep stage M Movement time
Sleep stage ? 未评分或未知

这些标签按照 1968 年的 Rechtschaffen and Kales(R&K)标准进行人工评分。PhysioNet 官方说明对此有详细描述。

5.1 六分类

如果保留 R&K 原始睡眠阶段,通常使用:

vbnet 复制代码
W, N1, N2, N3, N4, REM

并删除:

复制代码
M, ?

5.2 五分类

目前很多深度学习论文会将 R&K 的 N3 和 N4 合并:

vbnet 复制代码
W, N1, N2, N3, REM

对应关系:

ini 复制代码
R&K N3 + R&K N4 = AASM-style N3

MNE 官方睡眠教程也采用了这种方式,将 Sleep stage 3Sleep stage 4 映射为同一个类别。MNE 睡眠分期教程

但需要明确:

N3/N4 合并是研究者为了统一分类标准所做的预处理,不是原始标签本身。

论文中应明确写出:

vbnet 复制代码
Sleep stages 3 and 4 were merged into a single N3 class.

而不是直接称为"原始五分类标签"。


六、数据下载

官方提供了多种下载方式。

使用 wget

perl 复制代码
wget -r -N -c -np \
  https://physionet.org/files/sleep-edfx/1.0.0/

完整压缩包约 8.1 GB,建议保留足够的硬盘空间。

使用 AWS S3

perl 复制代码
aws s3 sync --no-sign-request \
  s3://physionet-open/sleep-edfx/1.0.0/ \
  ./sleep-edfx

下载完成后,可以根据官方提供的 SHA256SUMS.txt 校验文件完整性。

不要直接使用来源不明的 Kaggle、网盘或二次转换版本。很多二次数据集已经:

  • 删除了部分受试者;
  • 修改了标签;
  • 合并了 N3/N4;
  • 截断了清醒片段;
  • 改变了单位;
  • 重新保存为 NumPy 或 CSV;
  • 丢失原始文件名和受试者信息。

官方数据采用 Open Data Commons Attribution License v1.0,使用时还应引用原始数据论文和 PhysioNet 资源。


七、使用 MNE 读取 EDF 和 Hypnogram

MNE 官方推荐的流程是:

  1. 使用 read_raw_edf() 读取 PSG;
  2. 使用 read_annotations() 读取 Hypnogram;
  3. 将注释写入 Raw 对象;
  4. 使用 events_from_annotations() 生成事件;
  5. 按 30 秒切分 Epoch。

示例代码如下:

ini 复制代码
from pathlib import Path

import mne


STAGE_TO_ID = {
    "Sleep stage W": 1,
    "Sleep stage 1": 2,
    "Sleep stage 2": 3,
    "Sleep stage 3": 4,
    "Sleep stage 4": 4,
    "Sleep stage R": 5,
}


EPOCH_EVENT_ID = {
    "W": 1,
    "N1": 2,
    "N2": 3,
    "N3": 4,
    "REM": 5,
}


def find_hypnogram(psg_path: Path) -> Path:
    stem = psg_path.name.removesuffix("-PSG.edf")
    record_prefix = stem[:-1]

    candidates = sorted(
        psg_path.parent.glob(f"{record_prefix}?-Hypnogram.edf")
    )

    if len(candidates) != 1:
        raise RuntimeError(
            f"无法匹配 Hypnogram: {psg_path}\n"
            f"候选文件: {candidates}"
        )

    return candidates[0]


def load_sleep_epochs(psg_path: str):
    psg_path = Path(psg_path)
    hypnogram_path = find_hypnogram(psg_path)

    raw = mne.io.read_raw_edf(
        psg_path,
        stim_channel="Event marker",
        infer_types=True,
        preload=False,
        verbose="error",
    )

    annotations = mne.read_annotations(hypnogram_path)
    raw.set_annotations(annotations, emit_warning=False)

    events, _ = mne.events_from_annotations(
        raw,
        event_id=STAGE_TO_ID,
        chunk_duration=30.0,
    )

    epochs = mne.Epochs(
        raw,
        events,
        event_id=EPOCH_EVENT_ID,
        tmin=0.0,
        tmax=30.0 - 1.0 / raw.info["sfreq"],
        baseline=None,
        preload=False,
        reject_by_annotation=False,
    )

    return raw, epochs


raw, epochs = load_sleep_epochs(
    "sleep-edfx/1.0.0/sleep-cassette/SC4001E0-PSG.edf"
)

print(raw.ch_names)
print(raw.info["sfreq"])
print(epochs)

在 EEG 采样率为 100 Hz 时,每个 30 秒 Epoch 应包含:

ini 复制代码
100 × 30 = 3000 个采样点

因此,单通道 Fpz-Cz 数据通常可以保存为:

ini 复制代码
X.shape = [样本数, 3000]
y.shape = [样本数]

多通道数据则通常保存为:

ini 复制代码
X.shape = [样本数, 通道数, 3000]

八、为什么不能简单按照数组长度切分?

Hypnogram 是以时间注释的方式保存的,不能默认:

ini 复制代码
label_index = sample_index // 3000

这种方法只有在以下条件全部满足时才成立:

  • PSG 和 Hypnogram 从完全相同的时间点开始;
  • 没有起始偏移;
  • 所有标注都是完整 30 秒;
  • 信号没有 EDF discontinuity;
  • 采样率被正确读取;
  • 没有截断或重新采样。

更稳妥的方式是使用:

scss 复制代码
mne.read_annotations()
mne.events_from_annotations()

让 EDF+ 中的 onset 和 duration 参与对齐。MNE 官方教程也是基于 Annotation 生成 30 秒事件,而不是直接手工构造标签数组。


九、睡眠分期预处理中的实际注意事项

下面这些并不一定写在数据集首页,但会直接影响实验结果。

9.1 不要把所有清醒片段全部保留

整夜记录通常在真正入睡前和醒来后包含较长的 Wake 片段。

如果完整保留,Wake 类别会大量占优,模型可能只要偏向预测 Wake 就能获得很高的 Accuracy。

常见处理方法有三种:

  1. 保留全部数据,使用 class weight;
  2. 仅保留首次睡眠阶段前 30 分钟和末次睡眠阶段后 30 分钟的 Wake;
  3. 直接删除过长的睡前和睡后清醒区间。

MNE 官方示例采用了第二种方法,但这不是唯一标准,论文中必须写明。

9.2 不要随机划分 Epoch

错误做法:

ini 复制代码
train_test_split(all_epochs, test_size=0.2)

因为同一受试者的相邻 Epoch 可能同时出现在训练集和测试集中,模型会学习到个体特征,造成严重数据泄漏。

推荐按受试者划分:

复制代码
训练集:subject 1--60
验证集:subject 61--68
测试集:subject 69--78

如果某位受试者有两晚记录,两晚必须放在同一个集合中。

对于 ST 数据,还应保证同一受试者的服药和安慰剂记录不会被拆到不同集合中。

9.3 不要忽略受试者元信息

SC 和 ST 的受试者差异很大:

  • SC:健康人群,年龄跨度较大;
  • ST:有轻度入睡困难,且存在药物实验;
  • 记录环境不同;
  • 记录时长不同;
  • 通道组成可能不同。

如果将它们混合训练,模型可能学到研究协议差异,而不是睡眠生理学特征。

建议在最终数据中保留以下元信息:

makefile 复制代码
subject_id
record_id
dataset_type: SC / ST
night_id
start_time
stage
original_label
processed_label

不要只保存 X.npyy.npy


十、滤波和归一化建议

Sleep-EDF EEG 原始采样率为 100 Hz,因此预处理时需要考虑 Nyquist 频率。

EEG

常见设置包括:

复制代码
带通:0.3--35 Hz
带通:0.5--30 Hz

具体选择取决于任务:

  • 睡眠分期需要保留 δ 波,因此不要随意提高高通截止频率;
  • 如果研究慢波睡眠,应谨慎处理 0.3--1 Hz;
  • 100 Hz 采样率下,不适合直接设计 50 Hz 陷波滤波器,因为 50 Hz 已经处于 Nyquist 边界;
  • 如果需要抑制工频,通常应先确认实际工频和滤波器设计。

EOG

EOG 对 REM 检测很重要,不能简单当作普通 EEG 处理。过强的高通滤波可能削弱缓慢眼动。

EMG

SC 的 EMG 已经是低采样率包络信号,不应使用 EEG 的滤波参数处理。

归一化

常见方式:

ini 复制代码
x = (x - x.mean()) / (x.std() + 1e-8)

但需要明确归一化范围:

  • 每个 Epoch 归一化;
  • 每个记录归一化;
  • 每个受试者归一化;
  • 使用训练集统计量归一化。

如果要评估跨被试泛化,建议统计量只在训练受试者上拟合,避免测试集信息泄漏。


十一、EDF 读取时的单位问题

EDF 文件中保存的是数字量化值,同时在 Header 中记录了:

  • 数字最小值/最大值;
  • 物理最小值/最大值;
  • 物理单位;
  • 采样率;
  • 通道名称;
  • 预滤波信息。

使用 MNE 或 PyEDFlib 读取时,应优先使用库完成物理量转换,不要手工猜测:

python 复制代码
import pyedflib

f = pyedflib.EdfReader("SC4001E0-PSG.edf")

print(f.getSignalLabels())
print(f.getSampleFrequencies())
print(f.getPhysicalDimension(0))

signal = f.readSignal(0)
f.close()

PyEDFlib 支持 EDF、EDF+、BDF 和 BDF+ 文件读取。PyEDFlib 官方文档对此有详细说明。

不同工具对 EEG 单位的内部表示也可能不同:

  • EDF Header 可能显示 uV
  • MNE 内部通常使用 SI 单位;
  • PyEDFlib 返回物理标定后的数值;
  • 某些二次处理代码又会额外乘以或除以 1e6

因此,处理前建议打印:

scss 复制代码
print(raw.info)
print(raw.get_data(picks=[0])[:, :10])

并通过 EDFbrowser 或官方工具进行人工波形核对。


十二、推荐的数据处理流水线

一个相对稳妥的 Sleep-EDF 处理流程如下:

  1. 下载官方数据并校验 SHA256
  2. 识别 PSG/Hypnogram 文件
  3. 读取 EDF Header 和通道列表,读取 EDF+ 睡眠标注
  4. 检查时间起点和持续时间,删除 M、? 或其他未使用标签
  5. 决定六分类或五分类
  6. 可选:截断过长 Wake 区间
  7. EEG/EOG/EMG 分别进行预处理,按照 30 秒切分 Epoch
  8. 保存 subject_id、record_id、start_time
  9. 按受试者划分训练集、验证集、测试集
  10. 训练与评估

建议输出为:

erlang 复制代码
processed/
├── X/
│   ├── SC4001E0.npy
│   └── ...
├── y/
│   ├── SC4001E0.npy
│   └── ...
└── metadata.csv

metadata.csv 至少包含:

css 复制代码
record_id, subject_id, epoch_index, start_sec, original_label, label

十三、评估指标不要只看 Accuracy

Sleep-EDF 的类别分布通常不均衡,N1 往往较少,Wake、N2、REM 的数量差异也比较明显。

建议同时报告:

  • Accuracy
  • Macro-F1
  • Weighted-F1
  • Cohen's Kappa
  • 每个类别的 Precision、Recall、F1
  • Confusion Matrix

特别是 N1 类别,如果只看总体 Accuracy,模型表现可能被严重高估。

此外,测试结果应明确:

  • 是 Epoch-wise 还是 Subject-wise;
  • 是否删除了睡前/睡后 Wake;
  • 是否合并了 N3/N4;
  • 是否删除 M 和 ?;
  • 使用了哪个 EEG 通道;
  • 是否使用 EOG、EMG;
  • 是否采用重叠窗口;
  • 是否使用了上下文 Epoch。

这些细节都会显著影响最终结果。


十四、Sleep-EDF 的优点与局限

优点

  1. 数据公开,获取方便;
  2. EDF/EDF+ 格式标准;
  3. 具有整夜连续记录;
  4. 提供专家睡眠阶段标注;
  5. EEG、EOG、EMG 信号较完整;
  6. 适合复现大量经典睡眠分期研究;
  7. MNE、PyEDFlib、WFDB 等工具都支持读取。

局限

  1. 数据采集时间较早,设备和人群不代表现代临床数据;
  2. SC 与 ST 来自不同研究协议;
  3. ST 存在药物条件和医院环境混杂;
  4. N1 类别严重偏少;
  5. R&K 标注与现代 AASM 标准并不完全相同;
  6. 很多论文使用的 "Sleep-EDF" 只是部分子集;
  7. 随机 Epoch 划分极易造成数据泄漏;
  8. 不能将其结果直接等同于临床诊断性能;
  9. 少量通道和记录存在缺失或格式差异;
  10. 不同论文的裁剪、滤波、标签合并方法差别很大。

十五、总结

Sleep-EDF 表面上只是"读取 EDF、切成 30 秒、训练分类器",但真正影响复现结果的往往是以下细节:

  1. 你使用的是 SC、ST,还是完整 Expanded 数据;
  2. PSG 和 Hypnogram 是否正确匹配;
  3. 是否正确处理文件名最后一位评分者标识;
  4. 是否根据 EDF+ annotation 做时间对齐;
  5. 是否将 N3、N4 合并;
  6. 是否删除 M 和未知标签;
  7. 是否裁剪过长的 Wake 区间;
  8. 是否按受试者划分数据;
  9. 是否保留原始单位和元信息;
  10. 是否记录完整的预处理配置。

一个推荐的实验设置是:

bash 复制代码
数据:Sleep-EDF SC
输入:Fpz-Cz EEG
采样率:100 Hz
Epoch:30 秒,3000 点
标签:W / N1 / N2 / N3 / REM
N3:合并原始 Stage 3 和 Stage 4
M、?:删除
划分:Subject-wise split
指标:Accuracy + Macro-F1 + Kappa

引用:

相关推荐
FlagOS智算系统软件栈1 小时前
CUDA Tile IR 接入 FlagOS 多芯片统一编译器 FlagTree,加速 AI 芯片生态迈向“开放计算”
人工智能·深度学习·flagos
大模型丫丫1 小时前
Transformer架构详解:从Attention到GPT的演进之路
gpt·深度学习·transformer
土拨鼠不是老鼠2 小时前
python 使用 plotly 进行数据分析
python·plotly·数据分析
是上好佳佳佳呀2 小时前
【深度学习|DAY05】卷积神经网络深度学习笔记
笔记·深度学习·cnn
盼小辉丶3 小时前
PyTorch强化学习实战——用预训练语言模型和ChatGPT玩转文本游戏
pytorch·深度学习·语言模型·chatgpt·强化学习
罗政3 小时前
零基础数据分析教程(2):对比与拆解
数据挖掘·数据分析
2601_9627809113 小时前
财务人员学数据分析考什么证?从入门到高阶实用证书清单
数据挖掘·数据分析
菜冻鱼17 小时前
Python-pytorch-高级技巧
开发语言·人工智能·pytorch·python·深度学习·神经网络·聚类
菜冻鱼17 小时前
Python-pytorch-模型保存与加载
开发语言·人工智能·pytorch·python·深度学习·机器学习