MATLAB心音分类实战:从信号预处理到分类器训练

发布时间:2026/10/4 23:49:08
MATLAB心音分类实战:从信号预处理到分类器训练 心音分类这个项目我断断续续做了两周多最开始纯粹是被一段异常心音录音勾起了兴趣——那“咕咚、咕咚”的节律里藏着一点多余的杂音人耳能听出来不对劲但要说清楚到底哪类问题得靠专业医生。于是我就想能不能用MATLAB把这套“听”的逻辑自动化做一个能区分正常与异常心音的分类器。做完之后发现这个任务的难度其实恰到好处既有信号处理的硬核部分又有机器学习的模型选型环节非常适合作一篇带完整落地的入门级教程。这篇文章我会把心音分类器的完整链路拆开讲从数据预处理、特征提取到分类器训练以及我踩过的那些坑。如果你正打算用MATLAB入门机器学习想找一个小而完整、能跑通全流程的项目心音分类器是个很好的选择。它不要求你有深厚的医学背景也不需要GPU训练网络一套普通的笔记本就能完成全部实验。1. 项目思路与整体设计先搞清楚要解决什么问题1.1 核心需求解析从“听诊”到“分类”心音分类的本质是把医生用听诊器听到的声音信号转成一组数值特征再用机器学习算法学习正常与异常心音之间的差异。人耳听觉其实是一个极其复杂的特征提取系统能同时感知频率、响度、节律、音色等维度而计算机没有这个能力我们需要把声音里最关键的判别信息显式地抽出来喂给分类器。说的再直白一点分类器本身没有任何医疗知识它只认识数字。你要做的是把每一条心音录音变成一行数字特征向量然后告诉它“这一行代表正常”、“那一行代表异常”。学完规律之后它看到新录音提取出的特征能告诉你这个更像哪一边。这个任务拆开来看包含几个清晰的环节数据获取与整理找到公开的、带标签的心音数据集。预处理去噪、归一化、把长录音切成合适长度的片段。特征提取从每个片段里算出一组有区分度的数值。特征选择与可视化看看特征有没有把两类样本分开的趋势。分类器训练与调参在训练集上学规律在测试集上看效果。评估与上线通过准确率、敏感性、特异性等指标判断系统能否用。整个链路里特征提取和分类器选型是核心前者决定信息的上限后者决定信息被利用的程度。这也是这篇文章标题里“特征提取和分类”两个词对应的重心。1.2 为什么选MATLAB信号处理与机器学习的无缝衔接很多新手会纠结用Python还是MATLAB。我的看法是如果你主要做音频、振动、生物电这类一维信号处理MATLAB的开发效率明显高得多。你用20行代码能完成滤波、重采样、分帧加窗等一系列操作在Python里可能要写四个不同库的导入和参数匹配。MATLAB的优势集中在这么几点内置了大量信号处理函数bandpass、butter、spectrogram都是一行调用对新手极友好。自带的Classification Learner App可以零代码完成多个分类器的训练和对比快速判断方向对不对。官方文档例子丰富搜心音分类能找到PhysioNet的配套脚本节省起步时间。不需要搭环境安装后开箱即用。当然MATLAB也有短板做深度学习生态不如PyTorch灵活CV领域的新模型迁移麻烦。但心音是典型的一维信号任务搭建深度网络不是必须的传统的特征工程经典机器学习模型反而更容易调试也更适合用来理解分类问题的本质。1.3 整体流程设计先跑通管道再优化精度我做这类项目习惯遵循一个原则先拿最简单的方案把整个管道跑通再回来优化瓶颈。这个思路放在心音分类器上具体落地是这样的先用一个很朴素的特征集比如时域统计量过零率加一个最简单的分类器决策树把“原始音频-预处理-特征提取-训练-预测”的闭环建立起来。这时候不需要追求准确率而是要排查管道里有没有逻辑漏洞比如标签和样本有没有对齐、特征矩阵维度对不对、训练集和测试集有没有数据泄露。管道通了之后再逐步把特征集升级成时频结合的特征组合把分类器换成SVM或集成模型用交叉验证做严格评估。整个过程步步为营出了问题也能快速定位在哪一段。注意项目中最常见的灾难不是模型效果差而是数据管道本身有bug。先用简单管线跑通就是对排查这类问题的最好保障。2. 数据准备与预处理分类器的地基稳不稳全看这里2.1 数据集选型首选PhysioNet 2016心音数据集做心音分类绕不开PhysioNet/CinC Challenge 2016这个公开数据集。它包含了七百多人的心音录音分为正常、异常两类还标注了录音来源于哪个公开子库。这个数据集有几个优点值得强调规模和格式很友好原始音频是.wav格式直接用MATLAB的audioread读进来。样本带有明确的分类标签免去人工标注的时间。Challenge的官方页面提供了训练集和测试集的划分方案可以照用方便和别人的结果对比。下载之后建议按这样的结构整理目录heart_sound/ ├── training/ │ ├── normal/ % 正常心音 │ └── abnormal/ % 异常心音 └── test/ ├── normal/ └── abnormal/2.2 预处理核心操作重采样、带通滤波与分段原始心音录音的采样率不统一常见的有2000 Hz、44100 Hz等这会影响后续特征提取的一致性所以要先把所有数据重采样到同一个频率。PhysioNet官方推荐的参考频率是2000 Hz因为心音的绝大部分能量集中在800 Hz以下2000 Hz采样率足够保留特征频段还能大幅压缩数据量。紧接着做带通滤波。心音的主要成分集中在20 Hz到600 Hz低于20 Hz的是肌肉震颤和基线漂移高于600 Hz的大多是环境噪声和听诊器摩擦声。MATLAB里用bandpass函数一行就能实现fs 2000; [sig, fs_orig] audioread(heart.wav); sig_resample resample(sig, fs, fs_orig); sig_filt bandpass(sig_resample, [20 600], fs);滤波之后要分段。一整段录音有几十秒钟直接整段提取特征会把不同心动周期的信息搅在一起分类器很难抓到稳定的模式。合理的做法是切成固定长度的心音片段用2到5秒的窗口比较常见。窗口太长会包含多个周期导致特征被平均化太短则可能截不完整一个心动周期。我实测之后觉得2.8秒左右比较合适——既能包含2到3个完整心动周期又不会让特征计算受到过多周期差异的干扰。相邻片段之间留50%重叠可以增强样本数量缓解数据量不足的问题。每个数据集的录音数量不太一样片段切完之后通常能多出一批样本给后续训练提供更充分的数据。segment_len round(2.8 * fs); overlap round(segment_len * 0.5); segments []; for start_pos 1:(segment_len-overlap):(length(sig_filt)-segment_len) segments [segments; sig_filt(start_pos:start_possegment_len-1)]; end2.3 归一化与标签对齐每个片段提取完特征后我们要做一次逐样本的归一化。为什么要做因为不同录音的音量有差异同一个特征比如均方根值在不同录音间的绝对值差异可能比正常与异常之间的真实差异还大这会干扰分类器的学习。归一化之后每个样本的特征均值归零、方差为一模型看到的是“模式差异”而不是“音量差异”。代码很简单feature_norm (feature - mean(feature)) / std(feature);这一步容易忽视但是极其重要。我见过不止一个项目的特征矩阵不归一化就送进SVM结果高斯核函数计算出的距离全部被量纲大的特征主导分类效果很难看。标签对齐是个表面简单但容易出错的环节。我自己的习惯是构建一个结构体或表格每个样本的ID和标签保存在一起。片段切割顺序、特征提取顺序、标签顺序三者的索引必须一一对应否则训练时标签完全错乱模型学出来的东西毫无意义。提示处理多个文件夹的数据时建议把文件名、片段编号一并存到特征矩阵的末尾列。一旦发现预测结果异常可以回溯检查是不是数据错位了。3. 特征提取分类器的“燃料”质量决定最终效果3.1 时域特征简单但别小看时域特征是从信号波形本身直接计算出的统计量。它的优势是计算极快、物理意义直观在区分正常与异常心音时能提供最基础的判别信息。常见的有这几类均方根值RMS体现信号整体的能量水平。异常心音往往伴随额外杂音能量分布会有变化。峰值系数峰值/RMS衡量信号的冲击特性。心音中的扑落音或喷射音会让波形产生更尖锐的峰值。过零率单位时间内信号符号变化的次数能间接反映频率高低。异常心音中高频成分增多时过零率会上升。熵率衡量波形的复杂程度。收缩期杂音会让信号变得更加“ chaotic ”复杂度增加。举个例子主动脉瓣狭窄患者的心音里会有明显的喷射性收缩期杂音波形比正常心音更加毛糙时域上的过零率和波形熵就会显著偏高。这些特征单独看不完美但组合起来能提供很强的互补信息。% 以一条样本为例计算基础时域特征 rms_val sqrt(mean(seg.^2)); peak_val max(abs(seg)); crest_factor peak_val / rms_val; zero_cross sum(abs(diff(sign(seg))) 0) / length(seg);3.2 频域特征观察心音的“频谱指纹”时域特征不够的时候频域特征是下一层的信息来源。对心音片段做傅里叶变换可以得到它的频谱分布。正常心音的能量集中在40到100 Hz低频区间而异常心音尤其是带杂音的情况在200到500 Hz甚至更高的频率上会出现额外的能量峰。常用频域特征包括频谱质心反映频谱能量集中位置的“重心”。杂音会导致频谱质心向高频偏移。频谱带宽衡量能量在频谱上的分散程度。杂音越宽频带宽越大。子带能量比把频谱分成若干子带统计每个子带的能量占比。这是一个比较细的特征能够捕捉特定频带的异常能量聚集。梅尔频率倒谱系数MFCC最初为语音识别设计但在心音分类上表现同样不错它模拟人耳对频率的非线性感知用一组系数刻画频谱包络的形状。MFCC前12到13个系数在心音分类中很常用。它们把高维频谱降成低维系数并且去掉了与人耳感知无关的细节。% 计算频谱质心 [pxx, f] pwelch(seg, [], [], [], fs); spectral_centroid sum(f .* pxx) / sum(pxx);3.3 时频特征小波包分解为什么要用频域特征有一个静态的缺陷它丢掉了时间信息。心音是典型的非平稳信号S1第一心音和S2第二心音出现的时间、间隔、频率成分都在动态变化。两个表面频谱接近的心音片段可能因为杂音出现的时间点不同而有本质区别。这时候要用到小波包分解。小波包可以看成一组带通滤波器组把信号同时按频率和时间分解得到一组时频系数。对这些系数做统计能量、方差、熵能够得到比纯频域更丰富的特征。MATLAB里用wpdec做小波包分解wpt wpdec(seg, 4, db4); % 提取第4层各节点的能量特征 energy_features []; for i 1:16 cfs wpcoef(wpt, i); energy_features [energy_features sum(cfs.^2)]; end小波基的选择对结果有影响但不需要过度纠结。实测db4和sym5在小波基中表现稳定后续特征选择会帮你挑出有效维度基函数带来的微小差异到那时基本被稀释了。3.4 特征组合策略先富集再筛选很多人做特征提取时喜欢一开始就把特征列表压缩得很短觉得特征多了会引起过拟合。我的做法反过来先把能算的特征都算出来再用特征选择算法筛掉冗余项。原因很简单手工挑选特征时你根本不知道哪些组合有区分度筛掉之后再想补回来代价比现在大得多。特征组合的流程一般是这样的对每个片段计算时域特征约5到8个。计算频域特征包括频谱质心、带宽、子带能量和MFCC约15到20个。做小波包分解提取各节点能量和熵约10到30个。全部拼接成一个特征向量落在30到60维的区间。特征向量维度太高时可以用主成分分析PCA降维到20维以内但要注意PCA是线性的对非线性关系帮助有限。也可以直接用分类器内置的重要性评估来筛选特征后面的章节会细说。4. 分类器选型与实操从简单模型到可用的系统4.1 候选模型对比决策树、SVM、KNN、Bagging特征工程完成之后分类器的作用就是把特征空间里的两类样本尽可能分开。适合心音分类的经典模型我按推荐顺序排个序附上各自适合的场景分类器优点缺点适合场景决策树可解释性强训练快单独用容易过拟合精度一般基线效果、理解特征重要性KNN简单直接无需训练特征维度高时计算量大小数据集、快速验证SVM高斯核对小样本高维数据效果好参数敏感需调C和gamma中等规模数据集的主力模型Bagged Trees / Random Forest抗过拟合精度高模型体积大解释性弱数据量较大时的首选决策树适合做基线不是因为效果会最好而是因为训练后你直接看树的分裂规则能直观感受哪些特征最重要。比如树的第一层如果按“频谱质心是否大于150 Hz”分裂你就知道频域特征在整个任务里的贡献很大。SVM和随机森林是我在这个项目里真正推荐的模型。SVM对中等规模样本的泛化能力强随机森林对特征噪声的忍耐度高。两者可以都训练出来再看测试集效果。4.2 训练集与验证集划分避免数据泄露心音分类里有一个隐性陷阱来自同一条长录音的多个片段高度相似。如果你把某条录音的一部分片段放进训练集另一部分放进测试集模型记忆了录音本身的个体特征而不是疾病类别的共性规律测试精度会虚高。这在医学信号处理中叫“数据泄露”或“个体泄露”。解决方法是按录音而不是按片段划分数据。每一条录音要么完全属于训练集要么完全属于测试集保证同一录音的片段不会同时出现在训练和测试中。这样评估的性能才有临床参考价值——未来的新病人相当于一条从未见过的录音。% 按录音ID划分而不是按片段划分 rec_ids unique(record_label); rng(42); train_recs randsample(rec_ids, round(0.8*length(rec_ids))); train_mask ismember(record_label, train_recs); test_mask ~train_mask;4.3 实战训练代码与超参数调整下面是我用的一个SVM训练流程其中特征矩阵X是归一化后的标签Y用1和-1表示正常和异常。% 训练集和测试集 X_train X(train_mask, :); Y_train Y(train_mask); X_test X(test_mask, :); Y_test Y(test_mask); % 高斯核SVM先用默认参数跑基线 svm_model fitcsvm(X_train, Y_train, KernelFunction, rbf, Standardize, true); % 网格搜索C和gamma [C_grid, gamma_grid] meshgrid(0.1:0.2:2, 0.01:0.03:0.5); cv_acc zeros(size(C_grid)); for i 1:numel(C_grid) mdl fitcsvm(X_train, Y_train, KernelFunction, rbf, ... BoxConstraint, C_grid(i), KernelScale, 1/sqrt(2*gamma_grid(i))); cv_acc(i) crossval(mcr, X_train, Y_train, Predfun, ... (xtr, ytr, xte) predict(fitcsvm(xtr, ytr, ... KernelFunction, rbf, BoxConstraint, C_grid(i), ... KernelScale, 1/sqrt(2*gamma_grid(i))), xte)); end调参和别的事情一样先粗后细。先用大步长确定最佳参数所在的大致区域再在小范围内精细搜索。如果网格搜索计算量太大可以用bayesopt做贝叶斯优化能少跑不少次训练。对于随机森林MATLAB里也用fitcensemble配合Bag即可bag_mdl fitcensemble(X_train, Y_train, Method, Bag, ... NumLearningCycles, 100, Learners, Tree);树的棵数我建议从50棵开始往上试到150棵后增益基本饱和再增加只会拖慢训练速度。学习器深度即每棵树的最大分裂层数默认取值已经够用不需要手工限制得太严格。4.4 类别不平衡处理正常样本多、异常样本少怎么办PhysioNet 2016数据集中正常样本的数量明显多于异常样本直接训练会把所有样本都判成多数类得到看似很高但其实毫无用处的准确率。处理这个问题有两种常用思路一种是对少数类做加权让模型在计算损失时给异常样本更高权重。SVM中可以用Cost参数指定误分类代价cost_matrix [0 1; 2 0]; % 把异常判成正常的代价更高 svm_model fitcsvm(X_train, Y_train, KernelFunction, rbf, ... Cost, cost_matrix);另一种是数据层面的处理对少数类样本做过采样复制或插入合成样本或者对多数类做欠采样。MATLAB的Classification Learner App里没有直接的SMOTE接口得自己写或用第三方工具。相比之下调整代价矩阵更简洁也够用。我在这类任务上的经验是先用代价矩阵把两类错误配平看混淆矩阵里特异性和敏感性的变化再决定要不要引入更复杂的重采样方法。多数情况下代价矩阵就能满足需求。5. 结果评估与问题排查准确率高不代表模型好5.1 评估指标准确率、敏感性、特异性、AUC心音分类不只是看准确率医学诊断场景里误判方向不同代价也不同。漏判一个异常患者会导致他错过进一步检查误判一个正常人为异常顶多多做一次复查。这两个错误不是等价的所以评估时要同时看四个指标准确率Accuracy总体判断正确的比例。敏感性Sensitivity/Recall实际异常样本中被正确识别出的比例。特异性Specificity实际正常样本中被正确识别出的比例。AUCROC曲线下面积衡量分类器在不同阈值下的整体区分能力。MATLAB里用混淆矩阵可以方便地算出这些数值[labels_pred, score] predict(svm_model, X_test); confmat confusionmat(Y_test, labels_pred); accuracy sum(diag(confmat)) / sum(confmat(:)); sensitivity confmat(2,2) / sum(confmat(2,:)); specificity confmat(1,1) / sum(confmat(1,:));5.2 常见问题速查表我在项目中途和复盘过程中遇到了不少问题这里整理成一张速查表方便自己以后用也方便参考的各位排查常见问题可能原因解决思路准确率很高但敏感性很低类别不平衡调整代价矩阵或重采样训练集精度高、测试集精度低过拟合或数据泄露检查是否按录音划分数据特征增加后效果反而变差冗余维度干扰做特征选择或PCA降维不同类别的AUC很接近但整体波动大数据量不足增大片段重叠率或收集更多数据预测结果全部为正常SVM参数不合适或样本严重不平衡先切换决策树看基线再调SVM参数5.3 用混淆矩阵做模型“体检”单独看准确率是最容易自我欺骗的。我第一次跑完模型准确率报出92%当时还挺高兴结果看了混淆矩阵发现异常样本的检出率只有60%。这说明模型只是把大多数正常样本赌对了异常样本几乎全靠猜。把混淆矩阵打印出来你会很直观地看到模型在哪个类别上犯蠢。比如如果异常样本多数被分到正常侧大概率是异常样本的特征和正常样本重叠太严重这时候回看特征分布图尝试追加时频特征可能比盲目调参更有效如果异常和正常在特征空间有明显间隔但模型还是分错才说明是分类器容量或参数不够。提示我在调模型时养成了一个习惯——每次实验都记录特征组合、模型参数、四个评估指标的完整表格。这样回头复查时不用靠记忆猜哪个版本做了什么事排查效率高很多。6. 项目复盘我再做一次会怎么改进6.1 我踩过的几个坑希望你能避开第一个坑是差点被数据泄露坑了。最初我按片段随机划分训练集和测试集测试集AUC直接冲到0.98兴奋了一阵子。后来做跨录音验证才发现真实水平在0.85左右。这个教训值钱医学信号任务里必须在患者级别做划分。第二个坑是把特征工程做过头。有一版我提取了八十多个特征包含大量非线性变换后的冗余信息结果模型训练集精度接近100%测试集反而更差。后来才明白特征不是越多越好加了太多噪声维度分类器找不到焦点。用特征选择算法把维度压到25个之后测试集效果反而提升了五个百分点。第三个坑是忽略信号长度本身的区别。有些录音只有几秒钟有些有几十秒不控制片段长度直接提取特征长短片段特征值的统计口径完全不同。后来统一了重采样率和片段长度模型才稳定下来。6.2 这个项目还可以怎么延伸心音分类器只是起点。把特征提取的框架稍作调整就能迁移到其他生物信号分类任务上比如肺音分类判断湿啰音、干啰音、肠鸣音分析、震动信号故障诊断等。特征工程和分类器训练的思路是通用的换一个数据集就能复用整个管道。如果想把精度进一步提高可以考虑引入深度学习比如把心音片段转成梅尔频谱图用预训练的卷积神经网络做图像分类或者直接用一维卷积网络对原始波形端到端建模。但深度学习需要更多数据和更久的训练时间对于新手来说先用经典机器学习把整个流程吃透是性价比更高的路径。根据我个人做完一遍的体会这个项目的最大价值不在于最终成绩有多好而在于它强制你把信号处理和机器学习这两块技能真正打通。特征提取阶段你需要理解信号在频域长什么样、杂音会改变什么物理量分类器训练阶段你需要理解数据划分、交叉验证和评估指标之间的微妙关系。这两样东西合起来才是做真实计算任务的核心能力。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询