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-20 和 Sleep-EDF-78 主要是论文中的习惯叫法,并不是 PhysioNet 官方目录名称。
如果使用 MNE 的 fetch_data(),默认下载的是 SC 年龄研究部分,即 153 条记录,而不是完整的 197 条 SC+ST 记录。MNE 文档对此有明确说明:mne.datasets.sleep_physionet.age.fetch_data。
因此,复现实验时一定要确认:
- 使用的是 SC 还是 ST;
- 使用的是 2013 版本还是 2018 扩展版本;
- 是否包含两个子集;
- 论文中的 "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 3 和 Sleep 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 官方推荐的流程是:
- 使用
read_raw_edf()读取 PSG; - 使用
read_annotations()读取 Hypnogram; - 将注释写入 Raw 对象;
- 使用
events_from_annotations()生成事件; - 按 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。
常见处理方法有三种:
- 保留全部数据,使用 class weight;
- 仅保留首次睡眠阶段前 30 分钟和末次睡眠阶段后 30 分钟的 Wake;
- 直接删除过长的睡前和睡后清醒区间。
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.npy 和 y.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 处理流程如下:
- 下载官方数据并校验 SHA256
- 识别 PSG/Hypnogram 文件
- 读取 EDF Header 和通道列表,读取 EDF+ 睡眠标注
- 检查时间起点和持续时间,删除 M、? 或其他未使用标签
- 决定六分类或五分类
- 可选:截断过长 Wake 区间
- EEG/EOG/EMG 分别进行预处理,按照 30 秒切分 Epoch
- 保存 subject_id、record_id、start_time
- 按受试者划分训练集、验证集、测试集
- 训练与评估
建议输出为:
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 的优点与局限
优点
- 数据公开,获取方便;
- EDF/EDF+ 格式标准;
- 具有整夜连续记录;
- 提供专家睡眠阶段标注;
- EEG、EOG、EMG 信号较完整;
- 适合复现大量经典睡眠分期研究;
- MNE、PyEDFlib、WFDB 等工具都支持读取。
局限
- 数据采集时间较早,设备和人群不代表现代临床数据;
- SC 与 ST 来自不同研究协议;
- ST 存在药物条件和医院环境混杂;
- N1 类别严重偏少;
- R&K 标注与现代 AASM 标准并不完全相同;
- 很多论文使用的 "Sleep-EDF" 只是部分子集;
- 随机 Epoch 划分极易造成数据泄漏;
- 不能将其结果直接等同于临床诊断性能;
- 少量通道和记录存在缺失或格式差异;
- 不同论文的裁剪、滤波、标签合并方法差别很大。
十五、总结
Sleep-EDF 表面上只是"读取 EDF、切成 30 秒、训练分类器",但真正影响复现结果的往往是以下细节:
- 你使用的是 SC、ST,还是完整 Expanded 数据;
- PSG 和 Hypnogram 是否正确匹配;
- 是否正确处理文件名最后一位评分者标识;
- 是否根据 EDF+ annotation 做时间对齐;
- 是否将 N3、N4 合并;
- 是否删除 M 和未知标签;
- 是否裁剪过长的 Wake 区间;
- 是否按受试者划分数据;
- 是否保留原始单位和元信息;
- 是否记录完整的预处理配置。
一个推荐的实验设置是:
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
引用: