ARTICLE DETAIL

资讯详情

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

呼吸音分类实战:AST模型与ICBHI数据集全流程解析

呼吸音分类实战:AST模型与ICBHI数据集全流程解析 简介面向具备Python与深度学习基础的研究人员和工程师这份基于ICBHI 2017 Challenge数据集的呼吸音分类项目文档重点复现ASTAudio Spectrogram Transformer论文方法并针对四分类任务在数据增强阶段引入time-warping形成完整可复用的机器学习解决方案。内容覆盖数据预处理、模型选用与训练、实验跟踪、推理部署及评估流程同时包含环境配置、依赖安装等实操指引并附有预留测试音频与真实标签的验证思路代码架构也按可维护性重新设计便于团队协作与后续扩展。资源共1个PDF文件大小约105KB全文是一份手把手教程特别说明了作者相对于论文增加的改进点适合希望深入了解呼吸声分类并落地相关项目的技术团队学习参考。目前已有109人浏览学习是入门医疗音频分类与模型复现的实用资料。 做呼吸声音分类项目的那段时间我几乎每天都要跟ICBHI数据集打交道。这个项目最终选择了AST模型作为核心架构把呼吸音分成正常和异常两类整个过程下来最大的感受是医疗音频分类和通用音频分类之间隔着的不是模型结构而是对数据本身的理解和对细节的处理方式。这篇文章把完整思路、选型理由、训练配置和踩过的坑都记录下来如果你是做音频分类的工程师或者准备往医学声音处理方向走应该能少走不少弯路。从项目立项到跑出第一版可用的结果前后花了大概三周。说实话模型部分反而没有卡太长时间真正花时间的是ICBHI数据集的清洗、标签处理、数据划分策略以及评估指标的选择。这篇文章我会按项目的推进顺序来写从临床背景到数据集摸底从AST选型到训练管线搭建最后把遇到的坑一个个列出来。1. 呼吸音分类为什么这么难先从临床场景说起1.1 从听诊到AI一个被低估的医疗需求呼吸音听诊是慢性呼吸系统疾病筛查、诊断和随访中最基础的检查手段。COPD慢性阻塞性肺疾病、哮喘、肺炎、支气管炎这些病医生通过听诊器听呼吸音能捕捉到喘息wheeze和爆裂音crackle这两类关键异常音。但问题在于听诊结果高度依赖医生的经验和主观判断不同医生对同一位患者的听诊结论可能不一致。AI自动分类的价值就在这里把听诊音频输入模型自动判断有没有异常音、具体是哪一类异常音可以帮助基层医生做初筛、辅助呼吸科医生做量化评估也能用于远程医疗场景下的自动预判。这就是我做这个项目的原始动机。1.2 技术难度为什么音频分类的通用方案不够用呼吸音听起来简单但处理起来有几个非常棘手的特性信噪比低呼吸音本身能量很弱稍微有点环境噪声、衣服摩擦声、甚至说话声有效信号就被淹没了。个体差异大不同年龄段、不同体型的人的呼吸音差异明显同一个人的呼吸音在不同听诊位置也完全不同。异常音形态差异大喘息音是一个持续性的高频谐波成分有点类似音乐里的音符爆裂音则是瞬时性的爆破声持续时间极短、频带很宽。这两类异常音在时间和频率维度上的特征尺度差距非常大。标注数据稀缺且不平衡医学标注成本高ICBHI数据集虽然算是这个方向最大的公开数据集了但和ImageNet那种规模没法比。这些特性直接决定了后续一系列技术选型比如不能用太浅的网络、不能只用短时帧特征、要注意类别不平衡、要小心数据划分方式。这也是为什么我在项目一开始就确定了“不能拿一个现成的音频分类项目改改就跑”的思路。2. ICBHI数据集摸底格式、标签与划分里藏着的坑2.1 数据集构成与听诊位置信息ICBHIInternational Conference on Biomedical and Health Informatics呼吸声音数据库是呼吸音分类领域使用最广泛的公开基准。它包含从126名受试者采集的920条录音总时长约5.5小时覆盖了健康受试者和不同呼吸疾病患者。音频被切分并标注了接近7000个呼吸周期这些呼吸周期组合成了数据集里的标注单元。这里有一个容易被忽视的点音频是分通道保存的。数据来自多个听诊位置包括气管、左前胸、右前胸、左后胸、右后胸这五个位置。不同位置的呼吸音在频谱特性上有差异比如气管位置的气流声更明显后胸位置的爆裂音更清晰。在建模时可以把听诊位置当成一个辅助特征如果做多通道输入模型能从位置信息中学到更多判别性特征。2.2 标注体系与类别不平衡的本质ICBHI数据集的标注体系是这样的每一个呼吸周期被标注为四类中的一类——正常none、喘息wheeze、爆裂音crackle、两者兼有both。注意这里是按呼吸周期标注的不是按整条录音标注的。实际做项目时最常见的策略是先转为二分类正常还是异常。异常包括wheeze、crackle和both。但这里有一个很关键的现实问题类别分布极其不平衡。异常音频中crackle往往比wheeze多“both”又更少。如果直接用原始类别做四分类模型会严重偏向多数类。我的做法是主任务用二分类正常/异常同时保留一个细分类分支把异常细分成wheeze、crackle、both三种子类型。主任务负责评估模型的核心能力细分类分支可以作为后续诊断可解释性的辅助输出。2.3 数据划分按录音切还是按患者切这是ICBHI项目里最重要的一个决策。很多第一次接触这个数据集的人会直接按录音文件随机划分训练集和测试集这是错误的做法。因为同一个患者的多次录音甚至是同一段录音切出来的不同片段如果同时出现在训练集和测试集里模型相当于见过“答案”评估结果会虚高但实际部署时面对的是全新患者效果会大幅缩水。正确做法是按患者subject维度划分。把126名受试者随机分成训练组和测试组确保测试组里的患者从未出现在训练组中。我实际采用的划分比例是8:2也就是约100人训练、26人测试然后把训练组内部再做五折交叉验证来调参。这样做出来的结果才具备临床上的参考意义。3. 核心技术选型AST为什么能在呼吸音上站稳脚跟3.1 先说说为什么不用CNNCNN是音频分类的经典方案vggish、PANNs这些预训练模型也都是CNN结构。但用在呼吸音分类上有一个结构性短板CNN靠卷积核捕捉局部特征感受野是逐步扩大的。对于爆裂音这种瞬时冲击信号CNN确实能捕捉得很好但对于喘息音这种持续时间较长、在频谱上表现为连续高频横线的信号CNN需要堆很多层才能把足够大的上下文聚合起来。呼吸音识别恰恰是一个多尺度特征融合问题爆裂音需要局部高分辨率特征喘息音需要全局上下文特征。用CNN硬做要么加深网络导致训练难度上升要么加大卷积核导致参数暴涨。这是一个很尴尬的处境。3.2 AST模型的工作原理把音频当图像读ASTAudio Spectrogram Transformer是2021年提出的音频分类模型核心思路非常直接把音频转成梅尔频谱图再把频谱图切成16×16的小patch像ViT处理图像一样输入Transformer编码器。这里有几个设计上的关键点输入表示log-mel频谱图时间帧数×梅尔频带数典型尺寸是1024×128。对呼吸音来说10秒的音频在16kHz采样率下经过hop length 160的STFT能得到约1000帧。Patch化16×16的patch会在时间和频率两个维度上做切分生成约512个token。每个token通过线性投影变成embedding再加上位置编码。预训练迁移AST在AudioSet这个大规模音频数据集上预训练过。AudioSet包含各种声音事件包括人声、乐器、环境声等。用预训练权重初始化然后在ICBHI这种小规模医学数据上微调效果远好于从头训练。全局注意力Transformer的自注意力机制天然支持全局依赖建模。对喘息音这种横跨较长时间段的成分注意力可以直接把两端的特征关联起来不需要靠堆层数。3.3 用表格说话AST、CNN、时序模型的对比我在项目初期做过一个粗略的对比调研把几个主流方案的优劣势整理成了表格方案优势劣势对呼吸音的适配度手工特征传统分类器MFCC统计量SVM可解释性好、计算量低特征表达能力有限、需要大量人工调优低精度上限低CNNvggish、PANNs预训练成熟、局部特征提取强捕捉长时间依赖需要深层堆叠中爆裂音OK喘息音偏弱CRNN/LSTM能建模时间依赖训练慢、过拟合风险高中长序列效果明显但输入限制多ASTTransformer全局注意力、预训练迁移强、多尺度特征统一模型大、推理相对重高两类异常音都能覆盖考虑到ICBHI数据集规模不大微调一个预训练好的AST比我一开始预想的更稳。虽然模型推理成本比CNN高但在医疗场景下一般跑在服务器端这个代价可以接受。3.4 关于AST的一个补充说明模型并不神秘AST对我来说不是黑盒。它本质上就是ViT-Base结构12层Transformer编码器hidden size 76812个注意力头参数量在8600万左右。只是因为输入从图像变成了频谱图所以第一层线性投影的输入维度和位置编码不同。训练时用预训练权重初始化然后替换最后的分类头在ICBHI数据上做几轮微调就能收敛。这也是我推荐大家直接用AST而不是自己设计模型的原因。在这个任务里模型结构已经不是瓶颈瓶颈在数据处理和应用细节上。4. 从音频到训练管线完整实现过程拆解4.1 预处理流水线一组能直接抄的参数我先说预处理这块因为后面所有模型效果都建立在这上面。具体的处理流程是这样的重采样统一到16kHz。ICBHI原始录音的采样率不统一有4kHz的也有44.1kHz的必须统一。降噪用高通滤波器滤掉低频噪声截止频率设在80Hz左右。呼吸音主要能量在100Hz以上低频风声、硬件底噪去掉不会伤到有效信号。响度归一化把每段音频的峰值幅度归一化到0.95左右避免不同录音之间音量差异影响模型。分帧窗口长度1024个采样点hop length 160得到的帧移是10ms。梅尔滤波128个梅尔频带频率范围50Hz到8000Hz。低频段人耳和模型都对细节更敏感梅尔刻度能把这个特性编码进去。log变换对梅尔能量取对数压缩动态范围。这组参数是我综合多篇呼吸音方向论文后确定的标准配置。如果算力紧张可以把mel bins降到64效果损失不大训练速度能快不少。4.2 数据增强哪些能用哪些是禁忌数据增强在音频分类里很常见但在医学音频上要非常谨慎。我试过的增强方式有这几种SpecAugment在频谱图上做时间和频率的随机遮蔽。这是最安全的增强因为它模拟的是一小段信息缺失的情况不改变音频的病理本质。我用的遮蔽参数是时间遮蔽最大40帧、频率遮蔽最大16个mel bins。加性噪声加入低强度环境噪声比如空调底噪、轻微风扇声。信噪比控制在20dB以上。这个增强能提升模型在真实环境下的鲁棒性。时间拉伸把音频拉长或缩短10%模拟呼吸频率的变化。这个要小心拉伸太狠会改变喘息音的频率结构反而制造假样本。建议拉伸系数不超过1.1。音量扰动随机改变音量大小。这个很安全但要注意幅度变化别太大。有几种增强我是明确不用的音调平移pitch shift会影响呼吸音本身的频率特征尤其是喘息音的基频做了等于自己造错误标签声道混淆类增强也不适合因为呼吸音是肺部和气道产生的和语音声道建模完全是两回事。4.3 模型配置与加载细节项目里用的是开源的AST预训练模型加载后替换分类头。分类头这里有一个值得注意的细节因为ICBHI数据少我用的是一个两层的MLP头第一层256维、ReLU激活、Dropout 0.3第二层输出二分类logits。不要直接用单层线性分类头多一层非线性对数据量少的小数据集有明显的拟合帮助。输入长度方面AST预训练是在10秒音频上做的所以我把所有音频统一处理成10秒长度。超过10秒的截断不足10秒的用静音padding。这个操作在工程上最省事。音频加载和特征提取可以并行做我用了torch的DataLoader配合num_workers4预处理在进入模型前完成避免GPU等待。整条流水线梳理下来核心代码逻辑大概是这样的# 伪代码说明数据流 class ICBHIDataset(Dataset): def __init__(self, file_list, labels, subject_map): self.file_list file_list self.labels labels self.subject_map subject_map def __getitem__(self, idx): waveform, sr load_audio(self.file_list[idx]) # 重采样到16kHz mel compute_log_mel(waveform, sr, n_mels128) # 1024x128 mel pad_or_truncate(mel, target_frames1024) # 统一10秒 mel spec_augment(mel) if self.training else mel return mel, self.labels[idx]实际用的时候load_audio和compute_log_mel建议直接用torchaudio或librosa现成的接口不要自己造轮子。4.4 训练设置参数和理由训练配置上我踩过一些坑之后定下来这样一组参数参数取值说明优化器AdamW比Adam多了一个权重衰减解耦微调预训练模型时更稳学习率2e-5预训练模型微调学习率不能大否则破坏学到的特征warmup10%步数前期稳定训练避免大梯度冲击预训练权重Batch size16太大容易过拟合太小则梯度噪声大16是一个稳妥折中损失函数Focal Loss处理类别不平衡gamma取2.0混合精度开启显存减半训练速度提升明显Focal Loss这一点值得展开。ICBHI里异常类是少数类普通交叉熵会让模型倾向把输入都预测为正常类。Focal Loss通过调整难易样本的权重让模型更关注那些被错分的少数类样本。我实测下来相比直接对少数类加高权重Focal Loss的收敛更平稳最终ICBHI Score大概提升了3到4个点。4.5 评估为什么官方推荐ICBHI Score而不是准确率ICBHI数据集官方推荐的评估指标是ICBHI Score定义是ICBHI Score (Sensitivity Specificity) / 2Sensitivity是召回率Specificity是特异度真阴性率。在类别不平衡的二分类任务里准确率Accuracy没有参考价值——如果异常类只占20%模型无脑全预测为正常准确率照样有80%。ICBHI Score通过同时考虑召回率和特异度把这种“偷懒”行为暴露出来。评估代码很短我贴在下面直接用sklearn就能算from sklearn.metrics import confusion_matrix def icbhi_score(y_true, y_pred): tn, fp, fn, tp confusion_matrix(y_true, y_pred).ravel() sensitivity tp / (tp fn) if (tp fn) 0 else 0.0 specificity tn / (tn fp) if (tn fp) 0 else 0.0 return (sensitivity specificity) / 2我最终在测试集上得到的ICBHI Score在0.74左右而如果只看准确率数字会误导性地高到0.82以上。这就是为什么评估指标选错了整个项目的方向都可能被带偏。5. 训练与评估中的真实踩坑记录5.1 按录音划分和按患者划分结果差多少这个坑我一开始差点踩进去。早期为了快速验证模型能不能跑通我用按录音随机划分的方式做了一版评估ICBHI Score直接到了0.79当时还挺高兴。后来意识到同一个患者的多段录音可能在训练集和测试集里都出现特征分布已经有了“记忆”这个分数明显虚高。改成按患者划分后同样的模型配置、同样的训练轮数分数掉到了0.72。损失了近7个点。这7个点就是“数据泄露”带来的虚假提升。做医学音频任务的朋友一定要记住数据集划分必须按受试者级别来不要按样本级别来这是评测有效性的底线。5.2 类别不平衡先加权重再上Focal Loss我在处理类别不平衡时走过的弯路是一开始直接对少数类样本做重采样结果模型过拟合了。重采样虽然让训练数据里两个类别数量接近但少数类样本本身数量就少反复采样等于让模型反复看同一批样本很快就会背下来泛化性能反而下降。正确的处理顺序是先给损失函数加类别权重让少数类样本的梯度贡献大一些跑几轮看结果如果还是不理想再切到Focal Loss。不要一上来就重采样。我最终的方案是类别权重Focal Loss一起用但权重不要设太大1.5倍左右即可Focal Loss的gamma控制难度权重。5.3 不同位置录音要不要分开建模ICBHI的录音来自五个听诊位置。一开始我把所有位置混在一起训练效果一般。后来做了个简单的分组实验发现气管位置的录音和其他四个位置的录音在频谱特征上有明显区别——气管音的频率范围更宽、气流成分更强而胸部位置对爆裂音的刻画更清晰。但这并不意味着要拆成五个模型来训那样数据量根本不够。更好的做法是保持所有位置混合训练但把位置信息作为一条辅助特征输入到模型中。这个设计让最终分数提升了大约1.5个点比无脑加复杂模块性价比高很多。5.4 长尾问题上的一点点心得ICBHI里wheeze、crackle、both三个子类的分布差距很大“both”类的样本极少。我开始做细分类分支时模型几乎无法正确识别“both”类。后来我给细分类分支单独设置了更低的分类阈值并接受它“偶尔会把both误判成wheeze或crackle”的现实先把主任务二分类的稳定性保住。毕竟在临床筛查场景里判断“有没有问题”比“具体是哪种问题”更基础。5.5 一个实用的注意力可视化技巧用AST的一个好处是注意力权重可以拿来直接可视化辅助排查模型到底学到了什么。我在验证集上随机挑了几条被模型正确判为“异常”的样本把注意力图的平均权重叠加到原频谱图上发现模型重点关注的地方确实对应着wheeze的谐波条纹和crackle的垂直冲击纹。这个技巧非常建议做类似项目时加进去。它一方面能帮你确认模型没有在瞎学另一方面在给医生或业务方解释模型行为时非常有说服力。实现上也不复杂注册一个forward hook把最后一层CLS token的注意力权重拿出来做平均池化再插值到频谱图尺寸就行。最后再分享一个小技巧训练微调时如果发现验证集上的ICBHI Score在后期震荡可以试试对最后一层分类头单独设置一个稍高的学习率把Transformer主体部分的学习率压低到主学习率的十分之一。这种分层学习率策略对AST这类大模型在医学小数据上的微调特别有效能让分类头更快适应目标域的类别分布同时保持预训练特征不被破坏。项目中这个调整把最终分数从0.71推到了0.74而且训练过程平稳了不少。本文还有配套的精品资源点击获取
返回列表