4、数据集准备:公开睡眠数据集(如Sleep-EDF、MESA)介绍、数据预处理、清洗与标注

在TinyML睡眠监测模型部署的完整链路中,数据集准备是决定模型性能上限的基石。与云端AI不同,TinyML模型受限于内存(通常<512KB)、算力(MCU主频几十至几百MHz)和功耗,无法依赖海量参数拟合噪声,因此输入数据的质量直接决定了模型能否在资源受限环境下稳定运行。本章将聚焦于两个最权威的公开睡眠数据集——Sleep-EDF与MESA,深入讲解其结构差异、适用场景,并给出从原始信号到可训练样本的完整预处理、清洗与标注流程。我们将特别强调面向TinyML部署的“轻量化预处理”原则:所有操作必须在PC端离线完成,且产出的特征维度必须适配后续量化与算子映射。

TinyML睡眠监测数据集准备链路 公开数据源 Sleep-EDF (PSG, 20/100Hz) MESA (PSG + 多模态) PhysioNet 标准化格式 离线预处理(PC端) 重采样 → 滤波 → 去伪迹 滑窗切片 → 归一化 标签对齐(30s epoch) TinyML特征设计 时域:统计量/过零率 频域:PSD/带通能量 压缩至 10-50 维 数据清洗与标注 剔除坏段/运动伪迹 类别重映射(5→3类) 训练/验证/测试集 按受试者划分(非随机) Numpy/二进制格式存储 关键约束 • 避免数据泄露(按人划分) • 类别不平衡(WASO稀少) • 采样率统一(100Hz) • 量化友好(int8范围) • 内存<512KB • 实时性<100ms • 标签延迟容忍 图:TinyML睡眠监测数据准备全流程——从公开数据集到可训练样本

4.1 公开睡眠数据集核心原理与选型对比

睡眠分期的金标准是多导睡眠图(PSG),包含脑电(EEG)、眼电(EOG)、肌电(EMG)和心电(ECG)等多通道信号。对于TinyML部署,我们通常只选用单通道或双通道(如EEG Fpz-Cz + EOG),以降低传感器成本和功耗。两个最常用的公开数据集为:

  • Sleep-EDF(Expanded):包含1979-1994年间采集的197个整夜PSG记录,采样率100Hz(部分为256Hz),每30秒为一个标注epoch,标签为Wake、N1、N2、N3、REM五类。该数据集受试者年龄分布广,包含健康个体和轻度睡眠障碍患者,适合作为通用模型训练基准。
  • MESA(Multi-Ethnic Study of Atherosclerosis):包含2057例受试者的PSG数据,采样率256Hz,同时提供actigraphy、多导联ECG和睡眠问卷。MESA的受试者多为中老年人(45-84岁),合并症较多(高血压、糖尿病等),更贴近真实临床场景,但数据量庞大,需按需抽取子集。
对比维度Sleep-EDFMESATinyML选型建议
通道数2(EEG+EOG)≥12(全PSG)优先选EEG单通道
采样率100Hz256Hz统一重采样至100Hz
标注粒度30s epoch30s epoch保持30s,便于滑窗
受试者数782057按需抽取50-200例
数据格式EDFEDF+CSV需转换为NPY/BIN
适用场景算法验证临床泛化测试两者结合训练+测试
经验提醒:不要直接使用原始EDF文件训练TinyML模型。EDF文件包含头文件解析、通道顺序变化和信号缩放因子,这些在MCU端无法处理。务必在PC端离线转换为纯二进制数组(float32或int16),并保存独立的元数据JSON文件记录通道顺序和缩放系数。

4.2 数据预处理操作流程:从EDF到张量

预处理的目标是生成形状为 [N, C, T] 的张量,其中N为样本数,C为通道数(通常为1),T为时间步长(如30秒×100Hz=3000点)。完整流程分为五个步骤:

  1. 数据下载与格式解析:使用 pyedflib 或 mne 库读取EDF文件,提取信号数据和标注注释。注意处理不同版本EDF的字节序差异。
  2. 通道选择与重采样:仅保留EEG通道(如Fpz-Cz),若原始采样率为256Hz,使用 scipy.signal.resample_poly 降采样至100Hz。降采样前必须应用抗混叠低通滤波器(截止频率45Hz)。
  3. 信号滤波与去伪迹:应用0.5-40Hz带通滤波器(4阶Butterworth)去除基线漂移和高频噪声。对于运动伪迹(幅度超过±500μV),采用阈值法标记为坏段,不直接删除而是用相邻段均值填充。
  4. 滑窗切片与归一化:以30秒为窗口、无重叠滑动(与标注对齐)。对每个窗口计算均值和标准差,执行z-score归一化。保存每个窗口的均值/标准差参数,供MCU端推理时使用相同的归一化参数。
  5. 标签编码与存储:将五类标签映射为整数(Wake=0, N1=1, N2=2, N3=3, REM=4)。若面向二分类(清醒/睡眠),则合并为0/1。最终存储为 data.npy 和 labels.npy,或直接写入二进制文件以减小体积。
# 核心预处理代码片段(PC端离线执行)
import numpy as np
from scipy.signal import butter, filtfilt, resample_poly

def preprocess_edf(raw_signal, fs_orig=256, fs_target=100):
    # 1. 抗混叠滤波
    b, a = butter(4, 45/(fs_orig/2), btype='low')
    filtered = filtfilt(b, a, raw_signal)
    # 2. 降采样
    resampled = resample_poly(filtered, fs_target, fs_orig)
    # 3. 带通滤波 0.5-40Hz
    b, a = butter(4, [0.5/(fs_target/2), 40/(fs_target/2)], btype='band')
    return filtfilt(b, a, resampled)

# 滑窗切片
def make_windows(signal, labels, win_sec=30, fs=100):
    win_len = win_sec * fs
    X, y = [], []
    for i in range(0, len(signal)-win_len+1, win_len):
        X.append(signal[i:i+win_len])
        y.append(labels[i//win_len])
    return np.array(X), np.array(y)
风险提示:归一化参数必须在训练集上计算,并保存为常量。如果在MCU端重新计算均值和标准差,会导致特征分布偏移,且MCU端无法存储全部历史数据。建议将归一化参数(mean, std)以浮点常量形式写入C头文件。

4.3 数据清洗与标注策略:处理类别不平衡与噪声

睡眠数据天然存在类别不平衡:N2占比约45-55%,而N1和N3(深睡)仅占5-15%。若直接训练,模型会偏向多数类。针对TinyML场景,我们采用以下清洗与标注策略:

  • 坏段剔除:计算每个窗口的方差和峰值幅度,若方差<1μV²(信号平坦)或峰值>500μV(运动伪迹),标记为坏段。对于坏段,不直接删除而是用前后窗口的均值替换,以保持时间连续性。
  • 类别重映射:将N1和N2合并为“浅睡”,N3保持为“深睡”,REM保持,Wake保持。这样得到4类(Wake/浅睡/深睡/REM),或进一步合并为3类(Wake/非REM/REM)。类别减少可降低模型输出维度,减少MCU端内存占用。
  • 数据增强:对少数类(N3、REM)应用时间扭曲(±10%缩放)、幅度缩放(±5%)和添加高斯噪声(σ=0.01)。增强操作在PC端完成,生成额外的训练样本。
  • 按受试者划分:训练集、验证集、测试集必须按受试者ID划分,不能随机打乱窗口。否则同一受试者的相邻窗口会同时出现在训练和测试集中,导致数据泄露,模型泛化能力虚高。
清洗步骤输入输出关键参数
坏段检测原始窗口信号坏段索引列表方差阈值1μV²,峰值阈值500μV
坏段替换坏段索引清洗后信号前后窗口均值
类别合并5类标签3类或4类标签N1+N2→浅睡
数据增强少数类窗口增强后窗口时间扭曲±10%,噪声σ=0.01
数据集划分全部样本train/val/test按受试者ID,比例70/15/15

4.4 实战案例:基于Sleep-EDF的TinyML睡眠分类器数据准备

假设我们要开发一个部署在Cortex-M4(256KB RAM)上的睡眠/清醒二分类器,输入为单通道EEG(Fpz-Cz),输出为每30秒的睡眠状态。以下是完整的数据准备案例:

背景与约束:设备为可穿戴脑电头带,采样率100Hz,内存限制256KB,Flash 1MB。模型需在100ms内完成一次推理。因此输入特征维度必须压缩至50维以内,且不能使用复杂的时频变换(如小波)。

初始现象:直接使用Sleep-EDF原始数据训练一个简单的全连接网络(输入3000点),模型大小达到120KB,超出Flash限制,且推理时间超过200ms。

关键输入数据:从Sleep-EDF中选取20例健康受试者(10男10女,年龄25-60岁),每例约8小时记录,共约19200个30秒窗口。其中Wake占35%,N1占10%,N2占40%,N3占8%,REM占7%。

分析判断过程:首先,将五类合并为二类(Wake=0,非Wake=1)。然后,对每个窗口提取5个时域特征(均值、方差、过零率、峰值幅度、Hjorth活动性)和5个频域特征(δ波能量、θ波能量、α波能量、β波能量、总功率)。特征维度从3000降至10维。归一化参数在训练集上计算:mean=[0.02, 12.5, 8.3, 45.2, 0.31, ...],std=[0.01, 5.2, 3.1, 20.5, 0.12, ...]。

实际操作步骤:

  1. 使用 pyedflib 读取20个EDF文件,提取Fpz-Cz通道。
  2. 应用0.5-40Hz带通滤波,降采样至100Hz。
  3. 滑窗30秒,共得到19200个窗口。剔除方差<1μV²的坏段(约3%),用前后窗口均值替换。
  4. 提取10维特征,保存为 features.npy 和 labels.npy。
  5. 按受试者ID划分:14例训练(13440样本),3例验证(2880样本),3例测试(2880样本)。
  6. 将特征量化为int8(范围-128~127),量化参数scale=0.01, zero_point=0。

结果对比:原始3000维输入训练出的模型准确率92%,但模型大小120KB;压缩至10维后,准确率88%(下降4%),但模型大小仅2.3KB,推理时间8ms。量化至int8后,准确率87.5%,模型大小进一步降至0.6KB,完全满足TinyML约束。

风险与代价:特征压缩导致N1和REM的区分度下降(混淆矩阵显示N1误判为Wake的比例从5%升至12%)。这是因为时域统计量无法捕捉睡眠纺锤波等瞬态特征。后续可通过增加频域特征(如纺锤波功率比)来缓解。

复盘经验:对于TinyML,特征工程比模型结构更重要。10维手工特征+2层全连接(16-8)的效果优于3000维原始信号+3层卷积。此外,按受试者划分数据集后,模型在测试集上的性能下降约3-5%,这是正常现象,说明模型没有过拟合特定受试者的个体特征。

总结复盘:数据集准备的核心原则是“面向部署设计”。在PC端完成所有重计算密集型操作,输出轻量、固定维度的特征张量。清洗和标注策略必须考虑类别不平衡和受试者差异,否则模型在真实场景中会频繁误判。建议在项目初期就定义好特征维度和量化方案,避免后期返工。