基于DWT特征提取与WEKA的心律失常分类实战

发布时间:2026/10/14 23:25:45
基于DWT特征提取与WEKA的心律失常分类实战
简介这份资源面向医学信号处理与机器学习方向的学习者和研究者提供一套基于离散小波变换DWT特征提取、结合WEKA与MATLAB监督训练的心律失常分类完整方案用于解决ECG信号特征提取与心律异常自动识别问题。压缩包共539个文件约28.11MB包含402个png图像、29个csv数据表、18个mat与18个txt文件以及hea、info等WFDB心电记录文件、m脚本、arff数据集和tex、bib等论文排版素材覆盖从信号处理到模型训练再到结果呈现的完整链路。资源中提供了多组arff训练与测试集便于直接导入WEKA开展监督学习实验MATLAB脚本则用于DWT特征提取与数据预处理。目前已有57人学习下载适合希望快速复现心律失常分类流程、对比不同特征与分类器效果的中高级读者参考。1. 从一段心电信号说起这套 DWTWEKA 心律失常分类资源到底能干什么手里有一段 MIT-BIH 格式的心电信号采样率 360 Hz长度几十万点肉眼盯着屏幕看波形除了能看出心跳快慢几乎分辨不出室性早搏和正常搏动的差别。这是很多做生理信号处理的同学第一次拿到心律失常数据时的真实处境。这套「基于离散小波变换特征提取的心律失常分类」资源解决的正是从原始 ECG 波形到可解释分类结果之间的整条链路用 MATLAB 做离散小波变换DWT把一维时序拆成不同频带的细节系数和近似系数从中提取能区分心律类型的特征向量再交给 WEKA 做监督训练和分类评估。它适合两类人一类是刚接触生物医学信号、想跑通「特征工程 传统机器学习」完整流程的新手另一类是已经用过 SVM、随机森林但没系统做过小波域特征设计、想看看别人怎么组织 WEKA 训练流程的熟手。资源本身不依赖深度学习框架纯 MATLAB WEKA 组合对算力要求低笔记本就能跑这也是它至今仍有参考价值的原因。2. DWT 特征提取为什么选小波而不是 FFT以及 MATLAB 里怎么落地2.1 小波变换在 ECG 上的选型理由心电信号是非平稳信号QRS 波群、P 波、T 波在时域上位置和宽度都不一样。FFT 把信号整体变换到频域丢掉了时间信息你没法知道某个频率成分出现在哪个心动周期。DWT 的优势在于它用可伸缩、可平移的小波基去匹配信号局部特征低频处频率分辨率高、高频处时间分辨率高正好对应 ECG 里「慢变基线」和「陡峭 QRS」并存的特点。常见做法是选 Daubechies 小波族db4 或 db6 在 ECG 特征提取里出现频率最高。分解层数一般取 4 到 5 层因为 MIT-BIH 采样率 360 Hz5 层分解后近似系数对应的频带大约在 0 到 5.6 Hz覆盖了主要的心律信息而细节系数 d3、d4、d5 分别对应 QRS 能量集中区、P/T 波区和基线漂移区。这个频带划分不是玄学是按采样率和分解层数算出来的每层分解把当前频带一分为二第 j 层细节系数对应频带约为 fs/2^(j1) 到 fs/2^j。2.2 MATLAB 端 DWT 特征提取的完整代码下面这段代码是我一般会用的骨架输入是单导联 ECG 信号向量和对应的 R 波位置索引输出是每个心拍的特征向量矩阵。它做了三件事按 R 波切分心拍、对每个心拍做 DWT、从各层系数里算统计量。% 输入: ecg_signal - 预处理后的ECG信号 (1 x N) % r_peaks - R波位置索引 (1 x M) % 输出: feature_matrix - 特征矩阵 (M x F) % feature_names - 特征名元胞 function [feature_matrix, feature_names] extract_dwt_features(ecg_signal, r_peaks) wavelet_name db4; % 小波基ECG常用db4/db6 level 5; % 分解层数360Hz采样下5层足够 window_left 100; % R波前100点约0.28秒 window_right 150; % R波后150点约0.42秒 num_beats length(r_peaks); feature_matrix []; feature_names {}; for i 1:num_beats % 按R波位置切分单个心拍边界不足则跳过 start_idx r_peaks(i) - window_left; end_idx r_peaks(i) window_right; if start_idx 1 || end_idx length(ecg_signal) continue; end beat ecg_signal(start_idx:end_idx); % 对单心拍做DWT分解 [c, l] wavedec(beat, level, wavelet_name); % 提取各层细节系数和最后一层近似系数 feat_vec []; for j 1:level d detcoef(c, l, j); % 每层算4个统计量能量、均值绝对值、标准差、峰度 feat_vec [feat_vec, ... sum(d.^2), ... % 能量 mean(abs(d)), ... % 平均绝对值 std(d), ... % 标准差 kurtosis(d)]; % 峰度 end a appcoef(c, l, wavelet_name, level); feat_vec [feat_vec, sum(a.^2), mean(abs(a)), std(a), kurtosis(a)]; feature_matrix [feature_matrix; feat_vec]; end % 生成特征名方便WEKA里对应 for j 1:level feature_names{end1} sprintf(d%d_energy, j); feature_names{end1} sprintf(d%d_meanabs, j); feature_names{end1} sprintf(d%d_std, j); feature_names{end1} sprintf(d%d_kurt, j); end feature_names{end1} a_energy; feature_names{end1} a_meanabs; feature_names{end1} a_std; feature_names{end1} a_kurt; end逻辑说明wavedec返回的c是所有层系数的拼接向量l是各段长度记录必须成对传给detcoef和appcoef才能正确取出对应层。窗口左右不对称是因为 QRS 波群能量主要集中在 R 波之后前窗短后窗长能减少不必要的数据量。每层取四个统计量是权衡后的结果能量反映该频带强度平均绝对值和标准差反映波动程度峰度捕捉冲击性成分对室性早搏这种形态突变比较敏感。参数说明wavelet_name换成db6会得到更平滑的系数但计算量略增level改成 4 会少一层低频细节适合采样率更低的场景window_left和window_right需要根据实际 R 波检测精度调整如果 R 波定位有偏移窗口要适当放宽。2.3 特征向量的组织与标签对齐提取完特征矩阵后还需要把标签列拼上去。标签来自 MIT-BIH 的注释文件通常把心拍分成正常搏动、室性早搏、右束支传导阻滞等几类。常见做法是只保留样本量足够的三到四类避免某类只有几十个样本导致训练不稳定。拼好后存成 CSV 或 ARFFWEKA 两种都能读但 ARFF 对属性类型声明更明确推荐优先用 ARFF。% 假设 labels 是 M x 1 的类别标签向量与 feature_matrix 行对应 % 注意如果前面跳过了边界心拍labels 也要同步过滤 data_table array2table(feature_matrix, VariableNames, feature_names); data_table.label labels; writetable(data_table, ecg_dwt_features.csv);这里有个容易翻车的点extract_dwt_features里遇到边界心拍会continue跳过但外部传入的labels不会自动跟着跳。血泪经验是要么在函数里同时返回有效索引要么在调用前就把边界心拍和对应标签一起剔除。我一般会在函数里多返回一个valid_idx外部用它去索引标签。3. WEKA 监督训练ARFF 构造、分类器选择和十折交叉验证3.1 ARFF 文件构造与属性声明WEKA 读 CSV 也能自动推断类型但类别列如果被推断成数值型分类器会当成回归问题处理结果完全不对。所以稳妥做法是手动写 ARFF 头或者用 MATLAB 生成 ARFF。下面是一个 ARFF 头的模板属性名和前面特征名一一对应。RELATION ecg_arrhythmia ATTRIBUTE d1_energy NUMERIC ATTRIBUTE d1_meanabs NUMERIC ATTRIBUTE d1_std NUMERIC ATTRIBUTE d1_kurt NUMERIC ATTRIBUTE d2_energy NUMERIC ATTRIBUTE d2_meanabs NUMERIC ATTRIBUTE d2_std NUMERIC ATTRIBUTE d2_kurt NUMERIC ATTRIBUTE d3_energy NUMERIC ATTRIBUTE d3_meanabs NUMERIC ATTRIBUTE d3_std NUMERIC ATTRIBUTE d3_kurt NUMERIC ATTRIBUTE d4_energy NUMERIC ATTRIBUTE d4_meanabs NUMERIC ATTRIBUTE d4_std NUMERIC ATTRIBUTE d4_kurt NUMERIC ATTRIBUTE d5_energy NUMERIC ATTRIBUTE d5_meanabs NUMERIC ATTRIBUTE d5_std NUMERIC ATTRIBUTE d5_kurt NUMERIC ATTRIBUTE a_energy NUMERIC ATTRIBUTE a_meanabs NUMERIC ATTRIBUTE a_std NUMERIC ATTRIBUTE a_kurt NUMERIC ATTRIBUTE class {NORMAL,PVC,RBBB} DATA注意class那一行的大括号里是类别名必须和你的标签字符串完全一致大小写敏感。如果标签是数字编码这里要写成{0,1,2}并在数据行里对应。3.2 分类器选择为什么先跑随机森林和 SVMWEKA 里分类器几十种但在这个特征维度24 维左右和样本量几千到几万心拍下我一般先跑两个RandomForest 和 SMO。RandomForest 对特征尺度不敏感不用归一化能直接给出特征重要性适合快速看哪些小波系数层贡献大。SMO 是 WEKA 里的 SVM 实现在这个场景下配合 RBF 核通常能比随机森林高出一两个百分点但需要对特征做标准化否则能量类特征数值可能上千会压过峰度类特征数值在个位数。命令行方式跑十折交叉验证# 随机森林100棵树十折交叉验证 java -cp weka.jar weka.classifiers.trees.RandomForest \ -t ecg_arrhythmia.arff -x 10 -I 100 -K 0 -S 1 # SMORBF核十折交叉验证 java -cp weka.jar weka.classifiers.functions.SMO \ -t ecg_arrhythmia.arff -x 10 -C 1.0 -L 0.001 -N 0 -V -1参数说明-x 10是十折交叉验证-I 100是随机森林树的数量-K 0表示每个节点随机选的特征数由算法自动定。SMO 的-C是惩罚系数-L是容忍度-N 0表示用 RBF 核。如果数据类别不平衡加-W参数调整权重或者先用 WEKA 的SpreadSubsample过滤器做欠采样。3.3 结果解读看什么指标别只盯准确率WEKA 输出里有一堆数字新手容易只看Correctly Classified Instances那一行。但在心律失常分类里PVC室性早搏的召回率比整体准确率重要得多因为漏检一个 PVC 的代价远大于把一个正常搏动误判。所以重点看混淆矩阵和各类的 TP Rate、FP Rate。如果 PVC 的 TP Rate 低于 0.8说明特征对 PVC 的区分力不够常见原因是 d3 或 d4 层的统计量没抓住 PVC 的宽 QRS 特征可以考虑加一层 d2 的系数或者换 db6 小波基重跑。4. 避坑与排查从数据泄漏到小波边界效应五个真实翻车点4.1 现象交叉验证准确率 99%换一批数据掉到 70%原因特征提取时用了全局归一化比如对整个信号算均值和标准差再归一化然后才切分心拍。这样测试集的信息通过归一化参数泄漏到了训练过程。解决归一化必须放在切分之后或者用 WEKA 的Normalize过滤器在交叉验证内部做WEKA 的过滤器在-x 10模式下会自动对每折训练集单独拟合。4.2 现象WEKA 报错「nominal value not declared in header」原因ARFF 头里class声明的类别名和数据行里的标签字符串不一致常见于多了空格、大小写不同、或者标签里有中文。解决用strtrim清理标签统一转大写并在 ARFF 头里逐字核对。如果标签是数字确认 ARFF 里写的是{0,1,2}而不是numeric。4.3 现象某些心拍的特征值全是 NaN原因kurtosis函数在输入向量方差为零时返回 NaN比如某层细节系数全为零信号在该频带无能量。解决在计算统计量前加判断方差为零时用 0 替代或者直接跳过该心拍。我一般会在特征提取函数里加一行if std(d) 0, kurt_val 0; else kurt_val kurtosis(d); end。4.4 现象DWT 分解后系数长度和预期对不上原因wavedec默认使用周期延拓边界处系数长度会因小波滤波器长度而略有变化。如果手动按固定长度切分系数会错位。解决始终用detcoef和appcoef按层取系数不要直接对c做切片。另外单心拍长度要足够长至少大于小波滤波器长度乘以 2 的 level 次方否则分解会报错或产生大量边界效应。4.5 现象随机森林训练很慢几万心拍跑十几分钟原因特征维度虽然不高但树的数量设太多比如 500且没有限制最大深度。解决先用 100 棵树跑一版看特征重要性把重要性接近零的特征删掉再降到 50 棵树重跑。另外 WEKA 默认用所有 CPU 核心如果机器核少加-num-slots 1反而更稳。5. 进阶技巧用 WEKA 特征选择反哺小波参数调整跑通基础流程后真正拉开差距的是「特征选择 → 小波参数调整」这个闭环。WEKA 的AttributeSelection面板里我一般用CfsSubsetEval配合BestFirst搜索或者直接用InfoGainAttributeEval排个序。跑完你会看到哪些小波系数层的统计量被选中。如果 d1 和 d2 层几乎没被选上说明高频细节对分类贡献小可以考虑把分解层数从 5 降到 4减少特征维度如果 a 层近似系数的统计量排名很高说明基线信息重要预处理时就不要做太激进的高通滤波。下面这段是 WEKA 命令行做特征选择的示例# 用信息增益评估所有特征按排名输出 java -cp weka.jar weka.attributeSelection.AttributeSelection \ -E weka.attributeSelection.InfoGainAttributeEval \ -S weka.attributeSelection.Ranker -N 10 \ -i ecg_arrhythmia.arff-N 10表示输出前 10 个特征。拿到排名后回到 MATLAB 里只保留排名靠前的层重新生成 ARFF再跑一次十折交叉验证。我自己的习惯是至少做两轮这个循环第一轮用全部 24 维特征跑基线第二轮用筛选后的 10 到 12 维跑对比。多数情况下维度降一半准确率不掉甚至略升训练时间明显缩短。还有一个容易被忽略的点小波基的选择可以用 WEKA 的结果来反向验证。把 db4 和 db6 各跑一遍看哪个基下选出的特征更集中、PVC 召回率更高。不要凭感觉选让数据说话。从那以后我每次做小波特征提取都强制先跑一轮 WEKA 特征选择再定分解层数和小波基不再拍脑袋。希望帮到你。本文还有配套的精品资源点击获取