matlab机器学习心电疲劳检测:难点不在LSTM,而在预处理

发布时间:2026/10/12 13:20:45
matlab机器学习心电疲劳检测:难点不在LSTM,而在预处理 简介一套基于Matlab与机器学习的心电分析疲劳检测项目资料内含完整源码与配套数据集适合毕业设计、课程设计或项目开发阶段参考。项目覆盖心电信号预处理、QRS波检测、特征提取与模型训练等关键环节可帮助读者理解从原始心电数据到疲劳状态判别的完整链路。资源共24个文件压缩包仅4.17MB按功能可分为核心代码、数据文件与说明文档15个以Matlab编写的脚本和3个实时编辑脚本负责算法与训练流程2个Excel表格存放采集与时域特征数据2个Mat文件保存训练模型另有说明文档与注释文件目录结构清晰。已有44人浏览学习源码经过严格测试可放心在此基础上扩展应用文档中还涉及数据划分、混淆矩阵绘制、小波阈值去噪、LSTM网络训练等具体实现适合需要完整课题方案或快速上手的读者。1. 基于 matlab 机器学习的心电分析疲劳检测为什么说难点不在 LSTM用 matlab 做心电分析疲劳检测最容易翻车的不是模型训练而是从一段含噪 ECG 原始波形到干净心拍的那段预处理链路。这套基于 matlab 机器学习实现的心电分析疲劳检测源码包恰恰把整条机器学习应用流程里最脏最杂的环节一次性串通了小波去噪、Pan-Tompkins 心拍检测、时域特征与 ARMA 系数提取、LSTM 训练、混淆矩阵与热图评估还自带心电采集数据表和已提好的时域特征数据集。对正在做毕业设计、课程设计的人来说它提供的是一个能直接跑通的最小闭环而不是零散代码片段。对想快速验证“心电→疲劳分类”思路的开发者它同样值得下载后拆开逐文件看。下面按我实际复现的顺序讲清每段逻辑、参数和坑。2. 信号进门前先做减法小波去噪与 Pan-Tompkins 心拍检测2.1 wave_thresh.m 的去噪策略为什么首选小波而不是带通滤波ECG 原始信号里的噪声主要三类基线漂移、50Hz 工频干扰、肌电伪迹。工频干扰用带通滤波或者陷波器能压掉但基线漂移和肌电的频带和 QRS 波有重叠单纯滤波会把 R 波削平。小波去噪的核心优势是时频局部化它在保留 QRS 瞬态突变的同时把低频漂移和高频毛刺分别按层压掉。包里 wave_thresh.m、wave_thresh_v1.m、wave_thresh_single.m 三份脚本我理解是同一算法的不同阈值策略版本v1 偏向分层阈值single 偏向整段信号统一阈值实际效果用 single 跑基线漂移严重的自采数据更容易出问题。这套代码里 Birge_Massart.m 承担的是阈值选择。常见的小波阈值策略有 sqtwolog 通用阈值、rigrsure 无偏风险估计、heursure 启发式、minimaxi 极小极大而 Birge-Massart 是通过惩罚函数在保真度和光滑度之间折中对 ECG 这种非平稳信号比全局固定阈值更稳。核心流程大致是这样% 对应 wave_thresh.m 的小波去噪主流程 wname db4; % 小波基db4 对 ECG 波形形状匹配较好 level 8; % 分解层数8 层能同时照顾低频漂移和高频肌电 [C, L] wavedec(x_raw, level, wname); % 对原始信号做小波分解 % 用 Birge-Massart 惩罚策略求阈值 [thr, nkeep] wdcbm(C, L, 1.5); % 第三个参数是惩罚系数越大越光滑 x_den wdencmp(gbl, C, L, wname, level, thr, s); % s 软阈值 % 快速验证版wden 一行搞定适合先跑通流程 % x_den wden(x_raw, heursure, s, mln, level, wname);参数说明wname 选 db4 而不是 sym8 是因为 db4 支撑长度短对 QRS 波的陡峭上升沿损伤更小level 不建议低于 5否则基线漂移压不干净但也不是越高越好超过 10 会把 P 波和 T 波的细节磨掉。wdcbm 的惩罚系数 alpha 默认在 1 到 2 之间取 1.5 是折中数据噪声大时往上调到 2噪声小时往下降。软阈值 s 对信号连续性友好硬阈值 h 保留幅度更真实但会产生伪吉布斯震荡实际经验是 ECG 用软阈值更稳。2.2 Pan_Tompkins.m 与 beat_division.m从连续波形到单个心拍去噪之后要做的是找到每个心拍的位置。Pan_Tompkins.m 是经典 QRS 检测算法信号先过 5-15Hz 带通再微分、平方、滑动窗口积分最后用自适应双阈值和 refractory period 回溯检测 R 峰。它比单纯设幅度阈值强在阈值会跟着信号强度自适应更新对幅值变化大的疲劳数据不会漏检。beat_division.m 负责按 R 峰把连续信号切成单个心拍供后面特征提取用。% 对应 Pan_Tompkins.m 检测 R 峰 beat_division.m 切心拍 [qrs_i, qrs_amp] Pan_Tompkins(x_den, fs); % 返回 R 峰位置和幅度 pre round(0.25 * fs); % R 峰前 0.25s覆盖 P 波 post round(0.45 * fs); % R 峰后 0.45s覆盖 T 波尾部 beats cell(length(qrs_i) - 1, 1); for k 1:length(qrs_i) - 1 seg x_den(qrs_i(k) - pre : qrs_i(k) post); beats{k} seg / max(abs(seg)); % 幅度归一化消除个体差异 end参数说明fs 必须和采集设备一致很多翻车都出在默认 fs250实际数据是 500Hz。窗口长度取 0.7s 是因为正常心率下 RR 间期大约在 0.6-1.0s太短会切掉 T 波尾部太长会把下一个 P 波包进来。归一化这步不是可选项因为疲劳检测要跨个体比较不归一化的话模型学到的是电极贴得紧不紧而不是真实的疲劳状态差异。2.3 wfdb_install.m 与数据导入先把数据喂进去wfdb_install.m 是安装 WFDB 工具箱的脚本WFDB 是读写 MIT-BIH 等公开心电数据库的标准工具。如果你打算扩充训练数据MIT-BIH 的公开数据值得下载来补样本量如果只用包内自带数据rdata.m 读取“心电采集数据.xlsx”就够了。这里要区分两种文件原始 xlsx 存的是采集到的连续 ECG 波形要经过 2.1 和 2.2 的处理“数据集(时域特征).xlsx”是已经提好的特征表可以直接进模型。提示如果读 xlsx 时列名对不上先打开文件看第一行表头rdata.m 里的读取范围要跟着改。3. 特征决定上限时域指标与 ARMA 系数怎么组合3.1 features_extract.m 里到底提取了什么疲劳状态在 ECG 上最直接的反映是心率变异性下降RR 间期的波动变小交感神经占优势。features_extract.m 做的就是从心拍序列里按滑动窗口提取一组时域统计量。这套代码里主要特征如下特征计算方式疲劳相关的生理含义平均心率60 / 平均 RR 间期疲劳时通常心率升高SDNN全部 RR 间期标准差整体 HRV 下降RMSSD相邻 RR 差值平方的均值开根号副交感活性下降pNN50相邻 RR 差值超过 50ms 的比例快速调节能力减弱QRS 宽度均值每个心拍 QRS 起点到终点距离传导时间可能延长% 对应 features_extract.m 的 RR 间期特征提取 rr diff(qrs_i) / fs; % 秒为单位的 RR 间期序列 sdnn std(rr); diff_rr abs(diff(rr)); rmssd sqrt(mean(diff_rr.^2)); pnn50 sum(diff_rr 0.05) / length(diff_rr); hr_mean 60 / mean(rr); feat [hr_mean, sdnn, rmssd, pnn50];参数说明diff_rr 阈值 0.05 对应 50ms这是临床 HRV 分析的通用标准不要随手改成别的值。窗口长度建议选 60 秒以上因为 HRV 的时域指标在短窗口下标准差极大30 秒和 60 秒算出来的 sdnn 可能差两倍。特征组合的顺序要和训练时保持一致后面 data_split.m 划分后顺序错位会直接导致预测结果错乱。3.2 ARMA_coeffs.m用回归系数当特征时域特征只刻画 RR 间期波动的统计量丢失了序列内部的动态结构。ARMA_coeffs.m 的思路是把 RR 间期序列当作一个时间序列用 ARMA 模型拟合把回归系数本身作为特征。疲劳状态下自主神经调节模式改变ARMA 系数的取值会跟着变这部分信息是 sdnn、rmssd 捕捉不到的。% 对应 ARMA_coeffs.m 的 ARMA 系数提取 mdl arima(2, 0, 1); % ARMA(2,1)p2, d0, q1 est estimate(mdl, rr); % 对 RR 间期序列估计参数 arma_feat [est.AR{1}, est.AR{2}, est.MA{1}]; % 拼接成特征向量参数说明AR 阶数选 2 是因为 RR 间期序列的偏自相关函数通常在 2 阶后截尾MA 阶数选 1 够用阶数堆高容易过拟合。如果没装 Econometrics Toolbox常见做法是先用 arburg 估计 AR 部分再对残差拟合 MA效果接近。需要注意 ARMA 建模要求输入序列平稳先对 RR 间期做一阶差分再建模数据更稳。3.3 data_split.m 切数据别切出泄漏这个脚本看起来简单却是整套代码里最影响可信度的一环。很多人拿到代码直接把所有心拍随机切 70% 训练、30% 测试结果训练集准确率 99%、测试集也 99%但部署到新数据上直接崩。原因是一个受试者的相邻心拍高度相似随机切分后同一个人的心拍同时进了训练集和测试集模型记住的是人的特征而不是疲劳的特征。正确做法是按受试者划分% 对应 data_split.m 的按受试者划分逻辑 subjects unique(subject_ids); n_sub length(subjects); rand_idx randperm(n_sub); train_sub subjects(rand_idx(1:floor(0.7 * n_sub))); test_sub subjects(rand_idx(floor(0.7 * n_sub) 1 : end)); train_mask ismember(subject_ids, train_sub); test_mask ismember(subject_ids, test_sub);参数说明训练、验证、测试的比例 70/15/15 是常见默认值但要注意每个受试者的样本量要尽量均衡否则样本量大的受试者会主导训练。还有一个细节归一化参数只用训练集的均值和标准差计算然后用同一组参数去变换测试集禁止用全体数据的统计量做归一化否则同样构成数据泄漏。4. 把特征交给 LSTMLSTM_train.m 训练与参数解读4.1 为什么是 LSTM 而不是普通 BP 网络用 BP 网络也能做心电疲劳二分类但它的输入是单帧特征向量把心拍间的时序关系丢掉了。LSTM 的核心优势是维护一个记忆单元输入序列经过多个时间步后能保留早前心拍的状态信息。疲劳是一个逐渐累积的生理过程不是某一拍突然变化因此把连续 30-60 个心拍的特征序列喂给 LSTM模型能学到疲劳趋势而非只看当前一拍。这套代码的 LSTM_train.m 用的就是序列到标签的模式一组连续心拍的特征作为序列对应一个疲劳/清醒标签。4.2 LSTM_train.m 参数怎么设训练参数是复现时最需要留意的部分。网络结构不复杂核心是 sequenceInputLayer 到 lstmLayer 到全连接层% 对应 LSTM_train.m 的网络结构与训练选项 layers [ sequenceInputLayer(size(X_train{1}, 1)) % 特征维度 lstmLayer(128, OutputMode, last) % 128 个隐藏单元输出最后一个时间步 dropoutLayer(0.2) % 随机丢弃 20% 神经元防过拟合 fullyConnectedLayer(2) % 二分类清醒/疲劳 softmaxLayer classificationLayer]; options trainingOptions(adam, ... InitialLearnRate, 0.001, ... MaxEpochs, 60, ... MiniBatchSize, 64, ... GradientThreshold, 1, ... Plots, training-progress);参数说明lstmLayer 的 OutputMode 必须用 last因为每个序列只对应一个标签如果输出每个时间步的预测训练会报标签维度不匹配。隐藏单元 128 对这类小数据集偏够用样本量只有几百条时建议降到 64否则收敛慢且容易过拟合。InitialLearnRate 0.001 是 adam 优化器比较稳的起点损失震荡时降到 0.0005。MiniBatchSize 64 意思是每次迭代吃 64 个序列序列长度不一致时要配合 padding。dropout 放在 LSTM 层之后而不是输入层作用是打散时序依赖中过度耦合的部分。4.3 net.mat 与 main.mlx训练完怎么落地net.mat 是已经训练好的网络参数存档用 load 直接拿不用重新训练。main.mlx 是主流程从读数据到预测全串起来Copy_of_main.mlx 应该是历史版本建议以 main.mlx 为准两份跑出来不一致时优先看主脚本。训练好的网络做预测的逻辑很简单% 加载训练好的网络并预测 load(net.mat); YPred classify(net, X_test_seq); % X_test_seq 是 cell 数组每个 cell 一个特征序列 acc mean(YPred Y_test);这里注意 X_test_seq 的类型必须是 cell 数组每个 cell 是一段序列特征矩阵不能是普通数值矩阵。预测结果 YPred 是 categorical 类型和 Y_test 比较前确认两者的类别顺序一致否则混淆矩阵会画反。提示想快速验证完整链路的人优先跑通“数据集(时域特征).xlsx → data_split.m → LSTM_train.m → plot_confusion.m”这条线原始 ECG 预处理可以后面再补。这套顺序避免了卡在环境配置上。关于环境如果你还卡在 matlab 下载安装那一步注意装完后确认 Signal Processing Toolbox 和 Deep Learning Toolbox 都在这两个工具箱分别被 wden 和 lstmLayer 依赖缺任何一个都会在运行到对应行时报“未定义函数或变量”。5. 避坑指南跑这套 MATLAB 心电疲劳代码最容易翻车的六处5.1 采样率不一致导致 R 峰检测全乱现象QRS 检测结果出现几百上千个“R 峰”或者明显该检出的心拍漏掉切出来的心拍长短不一。 原因Pan_Tompkins.m 里的 fs 写死为默认值你拿到的自采数据实际是 500Hz 或 360Hz检测窗口和微分系数全部按错误的时间尺度计算。 解决拿到任何 ECG 数据第一件事确认 fs再把它作为参数传进 Pan_Tompkins.m 和 beat_division.m不要只在主脚本改、子函数里还是旧值。打印一次 qrs_i 的前几个值核对时间间隔正常人静息 RR 间期不应小于 0.5 秒。5.2 把原始波形直接送进 LSTM 导致维度报错现象main.mlx 运行到 sequenceInputLayer 时报维度不匹配或者预测结果全部是同一类别。 原因没分清“心电采集数据.xlsx”和“数据集(时域特征).xlsx”。前者是原始波形要先经过 wave_thresh.m、Pan_Tompkins.m、features_extract.m 才能变特征后者才是可以直接喂模型的输入。 解决按 README 的顺序走先跑特征提取生成特征矩阵再训练。想快速验证流程就跑预处理好的特征 xlsx跑通后再回头补原始数据链路。5.3 训练集准确率 99%、测试集也高实际部署却崩现象离线验证指标异常漂亮混淆矩阵对角线几乎全满但新采的数据预测准确率骤降。 原因最典型的数据泄漏——心拍级随机切分同一个受试者的前后心拍同时出现在训练和测试集合。 解决必须按受试者划分数据data_split.m 里改成按 subject_ids 分组。验收这类项目时我一般先看它的切分方式而不是先看准确率这是毕设答辩时最容易被老师追问的点。5.4 wfdb_install.m 安装失败或运行时卡住现象wfdb 相关脚本执行到 install 步骤报网络错误或 mex 编译失败主流程被中断。 原因WFDB 工具箱部分版本需要联网下载或本地编译器支持MATLAB 安装路径没加进搜索路径时也会出现“找不到函数”的报错。 解决sample 数据在包里优先用 rdata.m 读 xlsx暂时不需要 wfdb_install.m。等确实要扩展 MIT-BIH 数据时再补装装完用 which rdann 验证路径是否生效。5.5 LSTM 效果居然不如普通 SVM现象同样的特征用 SVM 或 BP 分类准确率 85%LSTM 反而只有 75%。 原因LSTM 的优势来自序列如果输入是单心拍特征向量而不是多心拍组成的特征序列LSTM 退化成普通的全连接堆叠还因为参数量更大而更难收敛。 解决把连续 30-60 个心拍的特征按窗口拼成序列再进 LSTM序列长度要能覆盖至少 30 秒的 HRV 趋势。另外一个常见做法是先用 SVM 当基线模型确认特征本身有效后再上 LSTM避免特征问题被模型问题掩盖。5.6 混淆矩阵画出来是颠倒的现象plot_confusion.m 画出的矩阵中真实清醒被预测成疲劳矩阵看起来“歪着对角线”。 原因categorical 标签的顺序在训练和测试时不一致或者 YPred 和 YTest 的类别顺序没有对齐。 解决训练前用 categories(YTrain) 查看类别顺序预测后对 YPred 调用 reordercats 保持和 YTrain 一致。类似地heatmap_plot.m 画特征相关性前先确认列名顺序和 features_extract.m 输出一致。6. 结果别只看准确率混淆矩阵、热图与二次开发方向6.1 plot_confusion.m 与 heatmap_plot.m 应该怎么读拿到训练好的模型和预测结果准确率只是一层皮。plot_confusion.m 输出混淆矩阵后重点看召回率和特异度两个数字疲劳检测场景里漏报比误报更危险一个疲劳被误判为清醒可能造成事故所以目标是让疲劳类的召回率尽量高哪怕牺牲一点正常类的准确率。heatmap_plot.m 画的是特征热图它揭示的是不同特征之间的相关程度以及每个特征与最终标签的相关性。如果某些特征之间相关系数超过 0.8说明它们携带的信息高度冗余删掉一个能降低模型过拟合风险。% 对应 plot_confusion.m 与 heatmap_plot.m 的读图方式 cm confusionchart(YTest, YPred); cm.Normalization row-normalized; % 按行归一化直接看每类召回率 % 特征相关性热图 corr_mat corr(feat_table); heatmap(corr_mat, Colormap, parula);参数说明confusionchart 的 Normalization 选 row-normalized 后矩阵每一行加起来是 100%对角线数值即为该类别的召回率。heatmap 的 Colormap 用 parula 比默认 jet 在黑白打印时更友好。如果热图上某个特征和所有其他特征的相关性都极低注意确认它是不是取值范围太窄或者采集时有大量缺失值。6.2 从这套代码往产品方向延申的路子这套源码本身做的是离线批量分析但思路可以直接改造。实时监测方向把滑窗改成每 10 秒滑动一次窗口内积累的心拍特征序列输入已经训练好的 net.mat输出概率超过阈值就触发疲劳告警多分类方向把 fullyConnectedLayer(2) 改成 3-4 个节点对应清醒、轻度疲劳、深度疲劳然后修改标签映射关系。另一个容易被人忽略的优化点是对数据增强对原始 ECG 叠加少量高斯噪声或做时间轴微扰能显著提升模型在传感器佩戴松紧变化时的鲁棒性。从那以后我每次拿到别人的心电工程都强制自己先跑一遍“小波去噪 → R 峰检测 → 特征提取 → 一个基础分类器”的最小闭环确认每个环节的 fs 和窗口参数一致再去看 LSTM 和准确率。没有这个习惯任何漂亮的模型都会栽在第一段原始波形上。希望这套基于 matlab 机器学习构建的心电分析疲劳检测源码与数据集能让你在复现时少走这几段弯路。本文还有配套的精品资源点击获取