ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

单通道脑电睡眠分期:从信号预处理到模型落地的完整路径

单通道脑电睡眠分期:从信号预处理到模型落地的完整路径 简介这份资源面向希望入门时序数据分类与睡眠分期研究的开发者基于Sleep-EDF公开数据集的SC数据展开实验包含153条整晚睡眠记录采用Fpz-Cz单通道脑电信号采样频率100Hz。项目以简洁代码实现自动睡眠分期流程网络结构参考TinySleepNet并加以改进支持双向RNN、GRU、LSTM与Attention等经典结构的参数化切换同时定义seq_len参数灵活调整batch_size与序列长度数据集加载继承torch的Dataset并设置shuffle_seed便于复现实验。压缩包共21个文件以12个Python脚本为核心辅以3个txt说明、2个pt模型权重、1个sh运行脚本、1个md文档及png、html等预览文件整体约10.53MB。目前已有298人学习下载。读者可借此掌握从数据预处理、模型搭建到训练测试的完整链路并作为神经网络处理时序分类问题的入门范例。1. 单通道脑电睡眠分期从一夜波形到五类标签的完整落地路径手头只有一根额部或耳后的干电极采样率 100 Hz 到 256 Hz一整夜 8 小时下来就是几十万到上百万个采样点。临床多导睡眠图PSG要贴十几根线而单通道脑电single-channel EEG只留一路信号却要输出 Wake、N1、N2、N3、REM 五个分期标签——这就是自动睡眠分期要解决的核心矛盾信息量被砍到极限判别任务却一点没减。它适合三类人手里有睡眠数据集想做基线复现的算法工程师、做可穿戴睡眠硬件需要端侧算法的嵌入式开发者、以及想用公开数据发论文但买不起 PSG 设备的研究生。整条链路我一般拆成四段信号预处理、epoch 切分与特征、模型选型、评估与后处理。下面按这个顺序把每一步的参数和坑讲透。2. 单通道脑电的预处理与 epoch 切分先把信号洗干净再谈模型2.1 为什么单通道比多通道更怕伪迹多通道 PSG 里眼动、肌电、心电各有专属导联伪迹可以靠参考通道回归掉。单通道 EEG 没有这个条件眼电EOG会直接混进额部电极肌电EMG在 REM 期咬肌活动会污染高频段心电ECG在耳后电极上尤其明显。所以单通道方案里预处理不是可选项是决定上限的一步。常见做法是 0.3–35 Hz 带通滤波先砍掉基线漂移和工频再用陷波滤 50 Hz国内或 60 Hz部分地区最后做伪迹检测。我一般不会一上来就上 ICA因为单通道做不了真正的独立成分分解强行用需要构造虚拟多通道反而引入新问题。2.2 用 MNE 做带通、陷波和重参考的最小代码import mne import numpy as np # raw: 单通道 EEGsfreq 为采样率单位 V raw mne.io.RawArray(eeg_data, mne.create_info([EEG], sfreq, eeg)) # 1) 带通 0.3-35 HzFIR 零相位避免相位失真影响后续 epoch 对齐 raw.filter(0.3, 35.0, fir_designfirwin, phasezero) # 2) 陷波 50 Hz带宽 1 HzQ 值别设太高否则会振铃 raw.notch_filter(freqs50, notch_widths1.0) # 3) 重参考单通道没有平均参考用原始参考即可不要瞎减均值 # raw.set_eeg_reference(average) # 单通道下这行是错的会直接归零逻辑说明filter的phasezero保证零相位代价是边缘有瞬态所以滤波后要裁掉首尾各 1 秒。notch_filter的notch_widths控制阻带宽度设太窄如 0.1 Hz在采样率低时几乎无效设太宽如 5 Hz会误伤 45–55 Hz 的 gamma 成分。参数上低切 0.3 Hz 是睡眠慢波0.5–2 Hz能保留的下限高切 35 Hz 覆盖了睡眠纺锤波12–16 Hz和 K 复合波的主要能量再高就是肌电噪声。重参考那行注释是血泪经验单通道做平均参考等于把信号减自身均值直接变零新手很容易在这里翻车。2.3 epoch 切分30 秒是标准但边界要对齐AASM 标准把睡眠分成 30 秒一帧epoch整夜按帧打标签。切分时有两个细节一是起始对齐通常从记录开始时刻切不要从第一个非零采样点切否则和标签文件对不上二是丢弃含大伪迹的帧但丢弃比例超过 15% 就要警惕说明电极接触或受试者状态有问题。epoch_len 30 # 秒 events mne.make_fixed_length_events(raw, durationepoch_len) epochs mne.Epochs(raw, events, tmin0, tmaxepoch_len - 1/raw.info[sfreq], baselineNone, preloadTrue) # 简单振幅阈值伪迹剔除峰峰值超过 200 uV 的帧标记为坏 data epochs.get_data() # shape: (n_epochs, 1, n_times) ptp data.max(axis2) - data.min(axis2) bad_idx np.where(ptp[:, 0] 200e-6)[0] print(f坏帧比例: {len(bad_idx)/len(epochs):.2%})参数说明200e-6是 200 微伏单通道额部电极在清醒眨眼时轻松超过这个值所以这个阈值主要抓眼动和运动伪迹。如果坏帧比例过高先别急着调阈值回去看原始波形是不是电极松了。make_fixed_length_events的duration必须和标签帧长一致否则后面训练时标签和特征错位模型学出来全是玄学。3. 特征工程与模型选型从手工特征到端到端网络怎么选3.1 手工特征时域、频域、非线性各管什么在深度学习普及前睡眠分期靠的是每帧提几十维特征再喂给随机森林或 SVM。时域特征抓的是均值、方差、峰度、Hjorth 参数对 N3 慢波的高振幅低频率敏感频域特征用 Welch 功率谱算 delta0.5–4 Hz、theta4–8 Hz、alpha8–12 Hz、sigma12–16 Hz、beta16–30 Hz五个频带能量及比值这是区分 N2 纺锤波和 REM 的关键非线性特征如样本熵、排列熵对区分 N1 这种过渡期有一定帮助但计算量大且对参数敏感。我一般会先跑一版手工特征 LightGBM 作为基线它的可解释性能告诉你哪些频带真正在起作用再决定要不要上深度模型。3.2 端到端模型1D-CNN BiLSTM 的最小可复现结构单通道数据维度低直接上 Transformer 容易过拟合常见做法是 1D-CNN 提局部波形特征接 BiLSTM 抓帧间时序依赖最后全连接出五类。下面是一个能跑通的结构输入是 30 秒 × 100 Hz 3000 点。import torch import torch.nn as nn class SleepNet(nn.Module): def __init__(self, n_classes5): super().__init__() # 三层 1D 卷积逐层扩大感受野 self.cnn nn.Sequential( nn.Conv1d(1, 32, kernel_size50, stride6, padding25), # 3000 - 500 nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(8), # 500 - 62 nn.Conv1d(32, 64, kernel_size25, stride1, padding12), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(4), # 62 - 15 nn.Conv1d(64, 128, kernel_size7, stride1, padding3), nn.BatchNorm1d(128), nn.ReLU() ) self.lstm nn.LSTM(128, 64, num_layers2, batch_firstTrue, bidirectionalTrue, dropout0.3) self.fc nn.Linear(128, n_classes) def forward(self, x): # x: (batch, 1, 3000) x self.cnn(x) # (batch, 128, 15) x x.permute(0, 2, 1) # (batch, 15, 128) x, _ self.lstm(x) # (batch, 15, 128) x x.mean(dim1) # 时序平均池化 return self.fc(x)逻辑说明第一层卷积核 50、步长 6对应约 0.5 秒的波形片段正好覆盖一个慢波周期池化逐步降维把 3000 点压到 15 个时间步再交给 LSTM 建模帧内和帧间的时序。参数上dropout0.3是单通道小数据集的常用值数据量上万帧可以降到 0.2。注意 LSTM 的batch_firstTrue否则维度对不上会报错这是新手最常见的翻车点之一。训练时用交叉熵损失类别不均衡N2 通常占 40% 以上要靠加权或重采样解决否则模型会偷懒全预测 N2。3.3 序列后处理别让模型逐帧独立预测逐帧分类有个通病相邻帧标签跳变比如 N2 中间突然插一个 Wake。真实睡眠是有状态持续性的所以推理后要加序列平滑。常见做法是隐马尔可夫模型HMM或简单的多数投票滑窗。我一般用 5 帧滑窗做中值滤波再配合转移概率约束——比如 N3 直接跳 REM 的概率极低可以在解码时压低这条路径。这一步能把 Cohens Kappa 拉高 2 到 5 个百分点成本几乎为零属于必做的后悔药。4. 训练、评估与单通道特有的坑Kappa 之外的真相4.1 评估指标为什么只看准确率会骗你睡眠分期类别极度不均衡N2 常占 45%–55%Wake 和 N1 各占 5%–10%。一个全预测 N2 的模型准确率能到 50%但毫无用处。所以必须看 Cohens Kappa 和每类 F1。Kappa 在 0.75 以上算可用0.80 以上接近专家间一致性。另外要看混淆矩阵重点看 N1 和 REM 的混淆——这两类在单通道下最难分因为都缺乏明显慢波和纺锤波。4.2 单通道特有的四个坑现象一训练集 Kappa 0.85测试集掉到 0.6。原因通常是受试者泄漏——同一晚的帧被随机分到训练和测试模型记住了这个人的信号特征。解决按受试者划分做留一受试者交叉验证LOSO这才是真实泛化能力。现象二模型把 N1 全预测成 Wake 或 N2。原因是 N1 样本少且特征介于两者之间。解决对 N1 过采样或在损失里给 N1 更高权重同时检查标签文件里 N1 的标注质量很多公开数据集 N1 本身就标得含糊。现象三换一个数据集 Kappa 暴跌 0.2。单通道对电极位置和参考极敏感额部电极和耳后电极的频谱差异很大。解决做通道标准化比如按整夜信号做 z-score或做频带能量归一化别用绝对幅值当特征。现象四推理时延太大端侧跑不动。1D-CNN BiLSTM 在 PC 上快但 BiLSTM 需要整段序列不适合流式。解决换成因果卷积或单向 LSTM牺牲一点 Kappa 换实时性可穿戴场景下这笔账划算。提示单通道睡眠分期的公开数据集里标签质量参差训练前先可视化几晚的 hypnogram看看有没有整段标错的情况比调模型超参更值。5. 把 Kappa 从 0.78 推到 0.83三个我反复验证过的技巧第一个技巧是频带能量做对数变换再归一化。原始功率谱动态范围大直接喂网络会被高能量频带主导取 log 后各频带贡献更均衡我在多个数据集上验证过单这一项能涨 1–2 个点。第二个技巧是引入帧间差分特征把当前帧和前一帧的频带能量差作为额外输入模型能借此捕捉状态转换对 N1 和 REM 的边界尤其有效。第三个技巧是测试时增强TTA对同一帧做轻微时间偏移或加小噪声多次推理取平均能压掉一部分预测抖动代价是推理耗时翻倍离线分析值得做。验证方法上我习惯固定一个受试者做 sanity check先看模型输出的 hypnogram 和真实标签叠图肉眼找系统性偏差比如是不是所有 REM 都被提前了一帧——那多半是 epoch 对齐或滤波群延迟的问题而不是模型本身。这个习惯帮我省过好几次重训的时间。最后说个教训我早期做单通道分期时花了两周调网络结构Kappa 卡在 0.76 上不去后来发现是预处理里陷波滤波的群延迟没补偿导致特征和标签错位了半帧。改回零相位滤波后直接到 0.81。所以单通道这条链路预处理和标签对齐的优先级永远高于模型结构别本末倒置。希望帮到你。本文还有配套的精品资源点击获取
返回列表