ARTICLE DETAIL

资讯详情

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

PCG+ECG同步数据集:双模态心音分类与信号处理实践

PCG+ECG同步数据集:双模态心音分类与信号处理实践 简介心音图数据集收录3126个PCG记录及同步心电图信号单次记录时长10-60秒覆盖主动脉区、肺区、三尖瓣区与二尖瓣区四个标准听诊位置定位清晰适合生物医学工程、医疗AI方向的学生和研究者用于心音分割、心音分类及异常筛查等任务。四个采集位置依次对应右侧第二肋间隙、第二肋间沿左胸骨边界、第四肋间沿左胸骨边缘与锁骨中线第五肋间隙便于按听诊区域开展对比实验。数据按正常与异常记录分别划分并附带参考标注CSV与SQI质量指标便于构建训练集和评估模型鲁棒性。整个资料包共2000个文件大小约206.81MB主要包含mat与wav格式的生理信号、hea头文件、dat原始记录、csv标注文件以及normal/abnormal记录清单格式覆盖较全可直接读取与标注。当前已有378人学习下载后可同时获得PCG与ECG双模态样本、四部位听诊记录划分及异常/正常参考标注能有效节省数据搜集与清洗时间适合作为心音相关算法验证与论文实验的基础数据集。1. 3126条PCGECG同步记录能解决什么做过心音分类项目的人应该都有这种体验单拿PCG心音图训练模型在公开集上准确率不错一旦切到真实穿戴设备采集的信号性能立刻缩水。原因很多时候不在模型而在缺失了时间锚点——PCG是心脏机械活动的声学表达ECG是电活动的时序记录两者天然错开又互相印证。这个包含3126个心音图记录和同步心电图的公开数据集价值就在“同步”两个字上既可以做纯PCG分类再用ECG辅助切分与校验也可以做双模态融合把电-机械耦合关系直接交给模型。适合做心音分类、瓣膜病筛查、S1/S2分割、可穿戴设备信号质量评估的工程师。2. 读懂PCGECG同步数据集信号形态、通道组织与记录规模2.1 心音图PCG里的信息S1/S2与心音的时频形态PCG是心脏瓣膜关闭、血流冲击等机械活动产生的声学信号常规看四个分量S1房室瓣关闭、S2半月瓣关闭以及病理性的S3、S4。S1出现在心室收缩开始S2出现在收缩末期两者之间的收缩期窗口是识别杂音的关键区间。从信号处理角度PCG的主要能量集中在20~200Hz左右但瓣膜开闭产生的click可以到300Hz以上杂音则因病变类型分布在低频到高频不等。这个特性直接决定了对数据集做滤波时不能一刀切带通范围选得太窄会把杂音切掉。提示拿到记录文件先看采样率。多数公开PCG数据集采样率在1kHz~44.1kHz之间相差很大后续切分和特征提取的窗口参数不能直接复用。2.2 心电图ECG为何是同步记录的“锚点信号”ECG记录的是心肌去极化/复极化的电活动和PCG的机械振动不是同一回事。QRS波群的R峰在时间上先于S1几十毫秒这几十毫秒就是机电延迟electromechanical delay。正因为存在这个固定的先后关系ECG可以作为分割PCG的时间基准R峰定位S1起点的近似位置T波终点或R峰到下一R峰的时间比例可以估算S2的大致窗口对心律失常样本单纯靠PCG能量包络切分容易把S1/S2搞混用ECG做辅助锚点则稳健得多。下表把两种信号做对比便于在设计预处理流程时统一考虑。维度PCGECG生理含义心脏机械活动瓣膜、血流心脏电活动去极化/复极化主要频段20~200Hz部分成分至400Hz以上0.05~100HzQRS主能量约5~25Hz典型采样率1kHz~44.1kHz250Hz~1kHz可直接提取的锚点信号包络峰值易受噪声干扰R峰、T波终点波形尖锐易检测在双模态中的作用分类主体信号切分基准、节律判断、融合辅路2.3 数据集的常规组织方式与读取思路从文件组织上看这类含同时记录ECG的数据集一般有两种典型形态一是每个受试者一个文件夹内含多段同步双通道信号二是按wav/csv格式平铺附带标注表格。3126条记录不算小但也没有大到需要分布式处理单机内存SSD读取即可跑完整训练流程。无论是哪种格式第一步都是把PCG和ECG以时间戳对齐读进来常见做法是用wfdb库或直接读原始采样值。下面的代码演示了用Python读取并按秒对齐两个通道的通用方式import numpy as np import pandas as pd # 假设文件结构为records/subject_id/signal.csv # 列time, pcg, ecg df pd.read_csv(records/subject_001/signal.csv) # 统一时间基准把离散采样转成持续时间序列 fs 4000 # 需要从文件头或readme确认这里按4kHz举例 t np.arange(len(df)) / fs pcg df[pcg].values.astype(np.float32) ecg df[ecg].values.astype(np.float32) # 若两个通道采样率不同先把ECG插值到PCG的采样率 from scipy.signal import resample_poly if fs ! 500: # 假设ECG原始采样率是500Hz ecg resample_poly(ecg, fs, 500)这里有个容易忽略的点PCG和ECG常以不同采样率采集后合并存储先对齐再进预处理管线否则后面对R峰和心音特征的时间位置会整体偏移。如果数据集提供的是.hea和.dat的WFDB格式直接用wfdb.rdsamp()读取更省事。3126条记录对深度学习来说是一个“够用但不富余”的规模。纯PCG分类的话可以在不依赖ImageNet预训练的情况下训练端到端模型如果要上更大参数的Transformer就得靠数据增强和迁移学习补充。3. 预处理与周期切分把连续信号变成干净的心动周期样本3.1 PCG带通滤波与ECG基线漂移处理原始信号直接进模型通常效果很差因为采集设备会引入工频干扰、摩擦噪声和基线漂移。PCG和ECG的滤波参数不同需要分别处理。我一般会把PCG做20~200Hz带通ECG做0.5~40Hz带通加陷波。from scipy.signal import butter, filtfilt, iirnotch def bandpass_filter(data, low, high, fs, order4): b, a butter(order, [low / (fs / 2), high / (fs / 2)], btypeband) return filtfilt(b, a, data) # 过滤PCG pcg_f bandpass_filter(pcg, 20, 200, fs) # 过滤ECG并去工频 ecg_f bandpass_filter(ecg, 0.5, 40, fs) b, a iirnotch(50, 30, fs) # 国内市电50Hz部分地区用60Hz ecg_f filtfilt(b, a, ecg_f)参数说明滤波阶数选择4阶是性能与相位失真的折中阶数过高容易引入振铃filtfilt做零相位滤波避免普通IIR滤波产生的时序偏移——在要同时比较PCG和ECG时间关系时这一点尤其重要。PCG下限取20Hz是防止呼吸音和肌电干扰上限取200Hz是为保留S1/S2主体能量做瓣膜click分析时可以放宽到400Hz。3.2 用ECG的R峰切分心动周期而不是用能量包络切心音样本有两种常见路线一种是对PCG包络做峰值检测另一种是先用ECG检出R峰再切。后者对心律失常样本的鲁棒性更好因为这个数据集同时提供ECG不做白不做。from scipy.signal import find_peaks # 1. 用ECG检测R峰核心是设置合理的峰间距 rr_min_sec 0.4 # 最小心动周期0.4s对应心率150bpm peaks, props find_peaks( ecg_f, distanceint(rr_min_sec * fs), prominence0.3 * np.std(ecg_f), # 用幅值比例过滤伪峰 ) # 2. 以R峰为中心向前取0.2s向后取0.5s得到完整心动周期 seg_pcg, seg_ecg [], [] for p in peaks: start max(0, p - int(0.2 * fs)) end min(len(pcg_f), p int(0.5 * fs)) if end - start int(0.7 * fs): # 丢弃首尾不完整片段 seg_pcg.append(pcg_f[start:end]) seg_ecg.append(ecg_f[start:end])prominence参数建议用信号标准差的倍数动态确定不要给固定值因为不同记录段的幅值差异很大。distance参数实际限定了最高可接受心率睡眠数据可以放宽到0.6s运动场景要收紧到0.3s左右。“用ECG切分”的真正目的是拿到一个时间对齐的周期这个周期里N个PCG样本点对应固定的心电阶段QRS前后、T波附近。后续做双模态输入时可以直接用同一个周期窗口分别提取特征不需要再做二次对齐。3.3 类别不平衡与轻量增强在心血管开源数据集里正常样本占比往往偏高这本数据集的具体标签分布未公布但常见情况如此。我的做法是先做数据探查打印每个类别的周期样本数再决定用加权采样还是增强补样本。心音增强需要注意不能把病理杂音的特征破坏掉def augment(sig, fs): kind np.random.choice([noise, shift, speed]) if kind noise: noise np.random.normal(0, 0.01 * np.std(sig), sizelen(sig)) return sig noise elif kind shift: offset np.random.randint(0, int(0.05 * fs)) # 最大平移50ms return np.roll(sig, offset) else: # 速度扰动改变采样率再插值回原长度模拟心率的正常波动 rate np.random.uniform(0.95, 1.05) sample_points np.linspace(0, len(sig) - 1, int(len(sig) / rate)) return np.interp(np.arange(len(sig)), sample_points, sig)注意np.roll会把尾部的样本卷到头部导致周期边界不连续。实际使用时先加窗如Hann再拼接或者直接把平移后超界的部分丢弃。增强力度建议克制一些噪声幅度不超过原始信号标准差的2%速度扰动不超过5%否则会把杂音的时间结构改掉。这一阶段的目标是增加样本数而不是造出“假特征”。4. 双模态建模从PCG单输入到PCGECG联合推理4.1 特征选择波形、梅尔频谱与形态参数建模前先决定输入形态。纯波形端到端训练是一条路但收敛慢、样本效率低直接拼接手工特征和深度特征我试下来更稳。常见特征组合整理如下。特征组计算方式典型维度适用场景包络特征Hilbert变换取包络后统计均值/方差/峰值间隔8~12维区分S1/S2、检测分裂梅尔频谱PCG用40个梅尔滤波器帧长50mshop 20ms40×时间帧杂音分类主特征小波能量db4小波3层分解后各层能量比4维捕捉瞬态clickRR间期序列从ECG R峰计算RR间期及变异性5~8维房颤/节律异常机电延迟R峰到S1峰值的时间差1维辅助判断电-机械耦合异常实际工程里我倾向于把梅尔频谱作为主输入RR间期和机电延迟作为辅助特征concat进全连接层而不是硬塞给卷积层。4.2 一个可跑的PCG分类模型基于上一章的切分结果先清洗每个周期的PCG到固定长度比如0.7s × 4kHz 2800点再转成梅尔频谱图。模型用简单的2D CNN即可不必一上来就上大模型。import torch import torch.nn as nn class PCGCNN(nn.Module): def __init__(self, num_classes3): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 16, kernel_size3, padding1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), # 40×T - 20×T/2 nn.Conv2d(16, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), nn.AdaptiveAvgPool2d((4, 4)), # 不管输入帧长池化到4×4 ) self.classifier nn.Sequential( nn.Linear(32 * 4 * 4, 64), nn.ReLU(), nn.Dropout(0.4), nn.Linear(64, num_classes), ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)代码里的AdaptiveAvgPool2d是关键数据集中不同记录的周期长度可能略有差异转成梅尔频谱后时间帧数不统一自适应池化让模型接受变长输入省去强制resize的麻烦。丢弃率0.4在这个数据规模下是合理的起点若训练集只有一两万条周期样本可以提高到0.5。训练时用交叉熵加WeightedRandomSampler处理类别不平衡学习率从1e-3起batch size 64早停 patience 10。4.3 让ECG真正参与决策对齐与特征融合有些项目把ECG和PCG简单concat在一起送进LSTM效果往往不理想因为两个信号的采样率和语义粒度差太多。更稳的做法是用PCG的梅尔频谱做主输入用ECG提取的RR间期序列经过一个小GRU得到向量再通过注意力机制与PCG特征融合。class DualBranchPCGECG(nn.Module): def __init__(self, pcg_encoder, ecg_feat_dim8, hidden32): super().__init__() self.pcg_branch pcg_encoder # 上文定义的PCGCNN去掉最后的分类层 self.ecg_gru nn.GRU(ecg_feat_dim, hidden, batch_firstTrue) self.attn nn.Linear(hidden 32 * 4 * 4, 1) # 可学习的融合权重 self.fc nn.Linear(hidden 32 * 4 * 4, 3) def forward(self, spec, rr_seq): fp self.pcg_branch(spec) _, h self.ecg_gru(rr_seq) fe h[-1] combined torch.cat([fp, fe], dim1) w torch.sigmoid(self.attn(combined)) fused w * fp (1 - w) * fe return self.fc(fused)这里的逻辑是让网络自己决定“当前样本更相信PCG还是ECG”。对心脏杂音分类PCG权重通常大对心律失常或低信噪比样本ECG的贡献会自动上调。rr_seq的构造方式是从每个周期内提取4~6个连续RR间期作为序列维度低GRU只有32个隐单元附加计算成本很小。提示双模态的收益在整体精度上也许只提升1~2个点但在信噪比较低的子集比如有呼吸干扰的记录上提升更明显。评估时分开看正常与噪声子集比只看总量更有说服力。5. 验证、分割与跨库泛化把准确率变成可信度5.1 用ECG辅助S1/S2分割让分类结果可解释如果只给出“异常”的分类结论临床或产品侧很难直接采信更常见的做法是先分割出S1、S2再根据杂音落在收缩期还是舒张期判断可疑的病变类型。ECG在这里最好用的参考是R峰之后约50~100ms是S1T波结束前后是S2。简化实现如下# 已知每个周期内的R峰位置r_peak单位样本点 s1_start r_peak int(0.03 * fs) s1_end r_peak int(0.12 * fs) # S1典型持续约80~120ms # RR间期已知时S2大约在下一个R峰前0.1~0.2s rr_next peaks[i 1] if i 1 len(peaks) else r_peak int(0.8 * fs) s2_start rr_next - int(0.22 * fs) s2_end rr_next - int(0.06 * fs) # 用PCG包络在以上窗口内找局部最大峰值微调边界这个窗口估算方法虽然粗糙但足以作为后续分割模型的先验。加上这个先验后再训练一个小型分割网络例如UNet的一维变体训练时间能缩短三分之一S1/S2互换的错误也明显减少。5.2 评估策略按记录分组切分警惕数据泄漏3126条记录来自不同的受试者周期切分后得到几万个样本如果随机打乱再划分train/test同一个人的样本会同时出现在两侧指标虚高。正确做法是按记录ID或subject ID分组切分例如用GroupKFold。from sklearn.model_selection import GroupKFold gkf GroupKFold(n_splits5) groups record_ids # 每条样本归属的记录ID for train_idx, valid_idx in gkf.split(specs, labels, groups): train_specs specs[train_idx] valid_specs specs[valid_idx] # 每个fold单独做标准化参数拟合衡量指标不要只看accuracy。医学信号分类的惯例是同时报告灵敏度Sensitivity和特异性Specificity当两类样本数量悬殊时F1会掩盖不少问题。明显优于随机猜测但准确率只有70%的模型如果敏感度0.9、特异性0.6在筛查场景依然有实际价值。5.3 几个容易掉进去的坑采样率不一致是最常见的问题。下载的数据集内部如果混有不同采集设备的记录务必统一重采样到同一频率否则周期切分长度和频谱位置都会乱套。滤波器参数决定下限。带通范围压到40~150Hz会让S1/S2更清晰但同时会滤掉高频click和部分主动脉瓣狭窄的高频杂音。先做一次频谱可视化观察杂音分布再定截止频率不要直接抄论文参数。数据增强不是越多越好。对PCG做时间拉伸会影响心动周期节奏做音高变换会影响杂音频率分布做加噪会影响信噪比这些操作都会改变数据分布增强幅度过大反而降低真实数据上的表现。经验值是增强后的样本占比不超过总训练集的30%。最后的建议是拿到这个数据集后先画心跳波形和频谱图把正常样本与各病理类别各看50个确认分割窗口和带宽参数后再写训练代码。数据集的价值更多体现在“PCG与ECG天然对齐”这个结构上——你可以只做单模态任务但发挥不了它的全部潜力。本文还有配套的精品资源点击获取
返回列表