简介这份资源面向计算机、人工智能、通信工程、自动化、电子信息等专业的在校学生与教师以及希望入门生理信号处理的企业员工提供一套基于单通道脑电信号实现自动睡眠分期的完整Python项目。项目围绕EEG信号预处理、数据集构建、深度网络建模与预测推理展开包含GRU等模型实现、Focal Loss损失函数、Sleep-EDF数据下载脚本及Web演示服务适合作为毕业设计、课程设计、作业或项目初期立项演示也便于在现有代码上二次修改扩展功能。压缩包共22个文件约10.67MB以12个py源码文件为核心辅以3个txt依赖与说明、2个pt模型权重、1个sh运行脚本以及html、md、png等文档与图示目录结构清晰便于按模块检索学习。目前已有118人学习下载。代码均经测试运行成功附README说明可帮助读者快速理解单通道脑电睡眠分期的完整流程与排错思路。1. 单通道脑电做睡眠分期为什么它比你想的更能落地一台只有单导联的脑电采集设备比如额贴式干电极头带能不能跑出接近多导睡眠图PSG的睡眠分期结果这是我在做可穿戴睡眠监测方案时被问得最多的问题。答案是能但前提是你得把信号预处理、特征提取和分类模型这条链路走通而不是直接把原始脑电丢进一个深度学习模型就指望它输出 N1/N2/N3/REM。基于单通道脑电信号实现的自动睡眠分期研究核心就是用一路 EEG 通道配合 Python 完成从数据读取、滤波去噪、epoch 切分、特征工程到分类器训练的全流程最终输出每个 30 秒片段属于哪个睡眠期。它适合两类人一类是想入门生理信号处理的 Python 开发者另一类是手里有单通道采集硬件、需要一套可复现基线算法的嵌入式或算法工程师。数据方面公开的睡眠数据集通常提供单通道或多通道 EDF 格式记录配合睡眠期标注文件足够你从零搭出一套能跑通、能评估、能继续迭代的流水线。2. 从 EDF 到训练集单通道脑电数据的读取与 epoch 切分2.1 为什么单通道方案要先解决数据对齐问题多导睡眠图有脑电、眼电、肌电、心电十几路信号单通道方案只剩一路 EEG看似简单实际上把容错空间压缩了。你没法再用眼电去辅助判断 REM也没法用肌电区分觉醒所有信息都得从这一路信号里榨出来。所以第一步不是急着建模而是把数据读对、对齐对。常见公开数据集里信号文件和标注文件是分开的信号是 EDF 格式标注是文本或 XML里面记录了每个睡眠期从第几秒开始、持续多久。你需要把标注转成与信号采样点一一对应的标签序列再按 30 秒一个 epoch 切分这是睡眠分期的标准粒度。如果对齐错了比如标注起始时间偏移了几秒后面模型再强也是白搭这是血泪经验。读取 EDF 我一般用 MNE 这个库它对生理信号格式支持比较全安装也简单pip install mne numpy scipy scikit-learn读文件和查看通道信息import mne import numpy as np # 读取单通道 EDFpreloadTrue 把数据加载进内存方便后续切片 raw mne.io.read_raw_edf(subject_01.edf, preloadTrue) # 打印通道名和采样率确认哪一路是 EEG print(raw.ch_names) print(raw.info[sfreq]) # 只保留脑电通道常见命名有 EEG、Fpz-Cz、C3-A2 等 eeg raw.copy().pick_channels([EEG])逻辑说明read_raw_edf负责解析 EDF 头信息和数据块preloadTrue避免后续反复读磁盘。pick_channels只保留脑电通道单通道方案里这一步很关键因为有些设备文件里混了加速度或光电容积脉搏波通道不剔除会干扰后续滤波。参数上sfreq是采样率常见 100 Hz、200 Hz、256 Hz后面做重采样和滤波都要用到它。2.2 滤波、重采样与 30 秒 epoch 切分原始脑电里混着工频干扰、基线漂移和肌电噪声。单通道没有参考通道做自适应噪声抵消所以滤波参数要保守一点。我一般做 0.3 Hz 到 35 Hz 的带通去掉极慢漂移和高频肌电再对 50 Hz 工频做陷波。如果原始采样率很高比如 500 Hz可以重采样到 100 Hz 或 128 Hz既降计算量又不丢睡眠分期需要的频段信息。# 带通滤波 0.3-35 Hz保留 delta 到 beta 频段 raw_filtered raw.copy().filter(l_freq0.3, h_freq35.0, fir_designfirwin) # 工频陷波国内一般 50 Hz raw_filtered raw_filtered.notch_filter(freqs50.0) # 重采样到 100 Hz降低后续计算量 raw_resampled raw_filtered.resample(sfreq100.0) # 按 30 秒切分 epoch epochs mne.make_fixed_length_epochs(raw_resampled, duration30.0, overlap0.0) data epochs.get_data() # 形状 (n_epochs, 1, n_samples) print(data.shape)逻辑说明filter的firwin设计器在生理信号里比较稳notch_filter针对工频。make_fixed_length_epochs按固定长度切分overlap0表示不重叠符合睡眠分期标准。切完后data的形状是 epoch 数乘通道数乘采样点单通道时通道维是 1。参数上duration30.0是睡眠分期的通用窗口别改成 20 或 60否则和标注对不上。标签对齐时把标注文件读进来按 epoch 起始时间映射到标签。常见标签是 W、N1、N2、N3、REM可以映射成 0 到 4 的整数。注意有些数据集把 N3 和 N4 合并有些保留 N4映射前先看标注说明别想当然。3. 特征工程单通道脑电里到底能提取哪些判别信息3.1 频带功率与功率谱密度单通道脑电做睡眠分期最经典也最稳的特征是频带功率。睡眠期变化本质上体现在不同频段能量占比的迁移清醒时 alpha 和 beta 占优N2 出现睡眠纺锤波N3 以 delta 为主REM 则有类似清醒的低幅混合频率。用 Welch 法算功率谱密度再积分到 delta、theta、alpha、sigma、beta 五个频带就能得到一组可解释、维度低的特征。from scipy.signal import welch def bandpower_features(epoch_signal, sfreq): # epoch_signal 形状 (n_samples,) freqs, psd welch(epoch_signal, fssfreq, npersegint(sfreq * 4)) bands { delta: (0.5, 4), theta: (4, 8), alpha: (8, 12), sigma: (12, 16), beta: (16, 30), } feats {} for name, (low, high) in bands.items(): idx np.logical_and(freqs low, freqs high) # 对功率谱积分得到绝对功率 feats[name] np.trapz(psd[idx], freqs[idx]) # 相对功率各频带除以总功率 total sum(feats.values()) 1e-12 for name in bands: feats[frel_{name}] feats[name] / total return feats逻辑说明welch用分段平均降低方差nperseg取 4 秒窗在 100 Hz 下是 400 点频率分辨率 0.25 Hz够用。np.trapz对功率谱积分得到频带绝对功率再算相对功率。参数上频带边界是睡眠分期的通用划分sigma 频带对应睡眠纺锤波别漏掉。相对功率比绝对功率更抗个体差异建议两组都保留。3.2 时域与非线性特征补充光靠频带功率N1 和 REM 容易混因为两者都表现为低幅混合频率。这时候补一些时域和非线性特征会有帮助。时域上可以算 Hjorth 参数活动度、移动性、复杂度分别反映信号方差、平均频率和频率变化。非线性上可以算样本熵或排列熵衡量信号不规则程度。这些特征计算量不大但对区分细微状态有用。def hjorth_params(epoch_signal): # 一阶差分和二阶差分 d1 np.diff(epoch_signal) d2 np.diff(d1) activity np.var(epoch_signal) mobility np.sqrt(np.var(d1) / (activity 1e-12)) complexity np.sqrt(np.var(d2) / (np.var(d1) 1e-12)) / (mobility 1e-12) return activity, mobility, complexity逻辑说明Hjorth 活动度就是方差移动度是差分方差与原始方差之比的平方根复杂度再对移动度做一次类似运算。参数上没什么可调的但要注意信号得先去均值否则方差被直流分量污染。样本熵实现稍长可以用antropy或nolds库窗口长度建议 1 到 2 秒嵌入维数 2容差取 0.2 倍标准差。把所有 epoch 的特征拼成矩阵形状是 epoch 数乘特征数标签单独一列就可以送进分类器了。特征维度控制在 20 到 40 之间比较合适太少欠拟合太多容易过拟合尤其单通道样本量有限时。4. 分类器选型与训练从随机森林到一维卷积网络4.1 传统机器学习基线怎么搭特征工程做完最稳的基线是随机森林或梯度提升树。它们对特征尺度不敏感不用做归一化调参也直观。我一般先用随机森林跑一版看混淆矩阵和每类 F1确认特征有没有判别力再决定要不要上深度学习。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix # X 形状 (n_epochs, n_features)y 是 0-4 的睡眠期标签 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf5, class_weightbalanced, random_state42, n_jobs-1, ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, digits3))逻辑说明stratifyy保证训练测试集类别比例一致睡眠分期类别不平衡很常见N2 往往最多N1 最少。class_weightbalanced自动按类别频率加权缓解不平衡。max_depth和min_samples_leaf控制树复杂度防止过拟合。评估时别只看准确率N1 的 F1 才是难点混淆矩阵里重点看 N1 和 REM 有没有互相混。4.2 一维卷积网络在单通道上的最小实现如果特征工程加树模型的效果到瓶颈了可以试一维卷积网络直接吃原始 epoch。单通道输入就是一条时间序列一维卷积核在时间轴上滑动能自动学局部波形模式比如纺锤波和 K 复合波。网络不用太深三四层卷积加全局池化就够参数量小单通道数据也撑得住。import torch import torch.nn as nn class SleepCNN(nn.Module): def __init__(self, n_classes5): super().__init__() self.net nn.Sequential( nn.Conv1d(1, 16, kernel_size7, padding3), nn.BatchNorm1d(16), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(16, 32, kernel_size5, padding2), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(32, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.fc nn.Linear(64, n_classes) def forward(self, x): # x 形状 (batch, 1, n_samples) feat self.net(x).squeeze(-1) return self.fc(feat)逻辑说明输入通道是 1对应单通道脑电。三层卷积通道数 16、32、64 递增BatchNorm加速收敛AdaptiveAvgPool1d(1)把时间维压成 1再接全连接分类。参数上kernel_size第一层取 7 在 100 Hz 下约 70 毫秒能覆盖纺锤波的一个周期。训练时用交叉熵损失类别权重按频率倒数设置优化器用 Adam学习率 1e-3batch size 64 左右。注意输入要先做 z-score 归一化每个 epoch 单独减均值除标准差否则网络很难收敛。训练轮数别太多单通道数据量有限一般 30 到 50 轮就够配合早停。验证集按受试者划分别把同一个人的 epoch 同时放进训练和测试否则指标虚高这是最常见的翻车点。5. 避坑与排查单通道睡眠分期最容易栽的五个地方5.1 标签对齐偏移导致指标异常现象模型准确率始终在 60% 上下混淆矩阵里各类分布混乱看不出规律。原因标注文件的时间基准和信号起始时间不一致或者 epoch 切分时用了重叠窗口但标签没跟着调整。解决先画一段信号和标签的对应图人工核对前几个 epoch 的标签是否合理比如清醒段标签应该是 W。确认无误后再批量切分切分函数里把 epoch 起始样本点和标签索引严格对应。5.2 工频陷波把信号陷没了现象滤波后信号幅度异常小频谱里 50 Hz 附近出现深坑连带着 45 到 55 Hz 的能量都被削掉。原因陷波滤波器 Q 值设得太高或太低带宽过宽。解决用notch_filter时指定quality_factor一般 30 左右或者改用带阻滤波阻带控制在 49 到 51 Hz。陷波后一定要看频谱确认目标频段没被误伤。5.3 类别不平衡让 N1 完全被忽略现象模型对 N2 和 W 识别很好N1 的召回率接近零。原因N1 在整夜记录里占比通常不到 10%损失函数被多数类主导。解决训练时设类别权重class_weightbalanced或手动按频率倒数加权评估时看宏平均 F1 和每类召回别只看总体准确率。如果 N1 实在太少可以考虑合并 N1 和 REM 做粗分但要在文档里写清楚。5.4 受试者泄漏让验证指标虚高现象交叉验证准确率 85%换一个新受试者测试掉到 65%。原因随机划分 epoch 时同一个人的数据同时进了训练和验证模型记住了这个人的信号特征。解决按受试者划分用 GroupKFold 或留一受试者交叉验证。单通道方案个体差异大受试者独立验证才是真实水平。5.5 重采样后 epoch 长度对不上现象切分出的 epoch 数比标注数少几个或者最后一个 epoch 被截断。原因重采样后总样本数不能被 30 秒整除make_fixed_length_epochs默认丢弃不完整段。解决切分前先裁剪信号到 30 秒的整数倍或者设drop_lastFalse保留最后一段并在标签里对应处理。切完打印 epoch 数和标签数两者必须相等。6. 把单通道睡眠分期跑成可复现实验的几个习惯想让这套方案真正可复现我一般会固定三件事随机种子、数据划分方式和评估指标。随机种子在 NumPy、PyTorch、scikit-learn 里都设一遍避免每次跑结果飘。数据划分按受试者来训练集、验证集、测试集的人不重叠这样报出来的指标才敢信。评估指标除了准确率一定报宏平均 F1 和 Cohens kappakappa 能排除随机猜测的影响睡眠分期论文里常用它做横向对比。特征和模型之间做消融也值得养成习惯。先只用频带功率跑一版再加 Hjorth 和熵特征看宏 F1 涨了多少树模型跑完再换一维 CNN看原始信号端到端能不能超过手工特征。这样你才知道每个模块到底贡献了什么而不是一锅端。下面这张表是我常用的对比记录格式每次实验填一行回头翻的时候一目了然。实验编号特征集分类器宏 F1kappa备注exp01频带功率随机森林0.720.63基线exp02频带功率Hjorth随机森林0.750.66时域特征有增益exp03原始 epoch一维 CNN0.770.68需归一化和早停exp04频带功率熵梯度提升树0.760.67训练更慢还有一个容易忽略的点是推理阶段的工程化。研究阶段用 Python 跑通就行但如果要落到嵌入式设备上特征提取和模型推理得考虑算力。频带功率加树模型的方案在 Cortex-M 级别芯片上也能跑Welch 可以换成 Goertzel 算法算特定频点能量模型可以转成 C 数组或 ONNX 再量化。一维 CNN 参数量小的话也有机会但得测实际延迟和内存占用。我一般先在 PC 上把精度调到位再逐步裁剪和量化别一上来就追求端侧否则精度和工程两头顾不上。最后说个我自己的习惯每换一个数据集先花半小时做数据探查画几个 epoch 的波形和频谱看看采样率、幅度范围、标签分布再动手写训练脚本。这个习惯帮我省过很多次返工因为不同数据集的通道命名、单位、标注格式差异比想象中大。单通道脑电睡眠分期这条路难点不在模型多深而在数据对齐、特征可解释和验证可信这三件事上。把这三件做扎实后面换模型、加通道、上端侧都是顺水推舟。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
互联网与大数据的关系是什么?从概念到应用彻底讲透 “互联网和大数据是什么意思”、“互联网包括大数据吗”、“大数据与互联网的关系是什么”——这几个问题,我在不同场合被问过太多次了,有刚入行的大数据开发新人,有做产品经理的同事,甚至连家里面退休的长辈刷短视频时都问过我&a… · 2026/9/23 18:40:20
3d全息投影视频源选型避坑:2024速查手册 3d全息投影视频源选型避坑:2024速查手册 刚把项目里的 three.js 从 r128 升到 r160,跑起来直接白屏?控制台报 WebGL context lost ,检查代码发现 WebGLRenderer… · 2026/9/23 18:40:14
小米网关一二三代怎么选?从Zigbee到Mesh看懂智能家居中枢 1. 从“智能家居死机”说起:为什么网关才是全屋智能的命门用了几年智能家居,我最大的感悟是:很多人买设备前纠结传感器买哪家、开关选什么牌子,结果装完发现设备频繁掉线、响应延迟、场景联动像个段子——大概率不是设备本身的问题… · 2026/9/23 18:40:13
3步搞定格陵兰冰盖数据加载,2026最新优化实战指南 3步搞定格陵兰冰盖数据加载,2026最新优化实战指南 刚学完Python语法,是不是觉得代码都能写?可一上手处理格陵兰冰盖这种海量遥感数据,项目直接卡死。内存爆炸、CPU占满、读取速度慢得让人想摔键盘。这根本不是语法问题,是数据流架构没搭对… · 2026/9/23 19:16:02
G6 元素体系总览:节点、边、组合的构成原理与配置实战 G6 元素体系总览:节点、边、组合的构成原理与配置实战 【免费下载链接】G6 ♾ A Graph Visualization Framework in JavaScript. 项目地址: https://gitcode.com/gh_mirrors/g6/G6 本文基于 G6 官方文档 元素总览 展开,系统梳理 G6 图表中节点(Nod… · 2026/9/23 19:15:49
史访避坑指南:手写实现底层原理与项目落地全解析 史访避坑指南:手写实现底层原理与项目落地全解析 很多刚入行或转行做开发的朋友,常陷入一种尴尬境地:看着文档里的 API 调用觉得简单,一旦自己从零搭建项目,代码就像散落的积木,怎么拼都不对劲。这种“学会语法却不知怎么搭项目”的断层,正是阻碍… · 2026/9/23 19:15:49
YOLO裂缝检测实战:3258张图像数据集的标签清洗与训练避坑指南 简介:面向YOLO系列算法应用的道路裂缝目标检测数据集,覆盖真实马路场景下的裂缝目标,支持YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流框架,可直接用于模型训练、验证和测试等任务。压缩包内共2000个XML标注文件&#… · 2026/9/23 19:15:36
基于CNN的网络入侵检测实战:从NSL-KDD到模型部署 简介:一份基于卷积神经网络的网络入侵检测系统完整源码包,面向Python开发者、网络安全方向毕设与课设学生,以及希望快速掌握CNN网络流量识别技巧的研究者,解决网络流量自动分类与异常入侵识别问题,模型准确率最高可达9… · 2026/9/23 19:15:36
Formily Next NumberPicker 数字输入组件实战指南:三种 Schema 用法与源码机制解析 前端UI组件 【免费下载链接】formily 📱🚀 🧩 Cross Device & High Performance Normal Form/Dynamic(JSON Schema) Form/Form Builder -- Support React/React Native/Vue 2/Vue 3 项目地址: https://gitcode.com/gh_mirrors… · 2026/9/23 19:15:36
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29