资讯详情 语音识别与阿尔兹海默症预测:特征工程到深度学习的完整实践指南
📅 2026/10/27 19:55:39
简介面向机器学习与医学交叉领域学习者的阿尔兹海默症早期认知障碍预测项目通过语音识别捕捉言语中的细微变化结合深度学习和支持向量机建立认知风险评估模型。资源共85个文件压缩包约4.89MB涵盖44个wav语音样本、特征数据CSV、训练好的SVM模型文件以及8个Python脚本主程序、特征提取、训练、验证、数据切分、录音、配置等、1个Jupyter演示、20个JPEG图片和3张特征分布/相关性/散点对比分析图。已有22人学习下载。项目结构清晰附带依赖清单、说明文档、版本管理配置和Shell脚本支持从数据预处理到模型评估的完整流程可直接运行并复现实验既适合作为毕业设计、课程设计、期末大作业的参考模板也有助于理解语音特征在神经退行性疾病早期筛查中的实际应用对提升工程实践和数据分析能力很有帮助。1. 语音识别遇上阿尔兹海默症预测这套资源到底能帮你落地什么语音识别和阿尔兹海默症早期认知障碍预测放在一起乍看像两个领域硬凑的题目拆开其实就是一条标准机器学习流水线把语音变成特征用深度学习模型预测说话人的认知状态。这份 zip 资源包把流水线完整打包了从音频预处理、MFCC 与韵律特征提取到 LSTM 基线模型、CNNAttention 升级模型再到训练、评估和可视化脚本全部能直接改。对做机器学习课程设计、期末大作业或毕业设计的人来说它最大的价值是省掉了从零搭环境的痛苦也提供了能写进论文的完整实验框架对想入门语音特征工程的新手跟着跑一遍就能看懂一段音频是怎么变成特征矩阵、再变成预测结果的。解压之后按 README 的顺序从头跑到尾就是一次完整的复现闭环。2. 特征工程先行MFCC、韵律特征与停顿时长怎么拼成完整流水线2.1 为什么语音能预测认知状态特征背后的机理阿尔兹海默症影响的是大脑的语言网络患者最早期的表现往往是说话变得费劲找词时间变长、句子中间停顿变多、语速下降、发音含糊。这些变化在日常生活中很难被察觉但一旦量化为音频特征差异就变得可计算了。MFCC 描述声道共振特性能捕捉发音模糊基频和能量反映语调与响度的异常停顿时长与语速直接对应找词困难。所以这里的语音识别并不是做语音转文字而是作为特征提取的上游把波形变成模型能读懂的谱特征序列下游再判断有没有认知下降的迹象。这个资源包的特征提取思路和多数相关论文一致第一层是谱特征 MFCC 及差分第二层是韵律特征基频、能量、过零率第三层是 VAD 切分后统计出的停顿与语速特征三层拼在一起作为模型输入。只做第一层的话模型很难区分认知障碍患者和正常老人因为正常老人也有发音含糊的情况加上第二、三层之后找词困难造成的停顿模式差异才会显现出来。这也是为什么在 ADReSS、DementiaBank 这类公开数据集的评测里融合韵律特征的方法一般比单用 MFCC 要高出 5 到 10 个点的准确率。2.2 用 librosa 提取 MFCC 与韵律特征参数别照抄默认值资源包里的extract_features.py是整条流水线的起点输入是一个音频文件夹输出是每个样本的特征矩阵统一保存成 npy 文件。核心代码如下import librosa import numpy as np def extract_features(audio_path, sr16000): # 统一重采样到 16kHz不同录音设备的原始采样率差别很大 audio, _ librosa.load(audio_path, srsr) # MFCC40 维描述声道共振特性 mfcc librosa.feature.mfcc( yaudio, srsr, n_mfcc40, n_fft512, hop_length160, win_length400 ) # 一阶、二阶差分把特征的时间动态变化也带上 mfcc_delta1 librosa.feature.delta(mfcc, order1) mfcc_delta2 librosa.feature.delta(mfcc, order2) # 韵律特征基频、RMS 能量、过零率 f0, voiced_flag, _ librosa.pyin( audio, fmin60, fmax400, srsr ) rms librosa.feature.rms(yaudio, frame_length400, hop_length160)[0] zcr librosa.feature.zero_crossing_rate( audio, frame_length400, hop_length160)[0] # 统一成 (T, D) 的序列格式方便后续按帧拼接 feat np.stack([ mfcc.T, mfcc_delta1.T, mfcc_delta2.T, f0[:, None].filled(0), rms[:, None], zcr[:, None] ], axis1) return feat.reshape(feat.shape[0], -1)三个参数值得单独说明。第一n_mfcc40而不是 librosa 默认的 20因为认知障碍语音的差异常分布在高频倒谱系数里20 维会丢掉一部分信息第二hop_length160配合 16kHz 采样率等于 10ms 一帧这个帧移是语音识别任务里的通用选择太大就会把停顿的细节抹平第三pyin的fmin60, fmax400覆盖了老年男性到女性的基频范围如果数据集里全是女性可以把 fmin 提到 100 以减少低频噪声干扰。这段代码输出的特征矩阵每个时间帧对应一行维度是 40*3 3 123 维。注意f0在无 voiced 帧的位置返回 NaN代码里用filled(0)替换成了 0这个细节在避坑章节还会再提处理不好模型 Loss 第一个 epoch 就给你颜色看。2.3 停顿和语速特征最容易出效果但最容易被忽略的一组MFCC 描述的是音色而认知障碍预测里真正敏感的是节奏。患者说话时会在找词的地方卡住出现 0.5 秒以上的病理性停顿。提取这组特征需要先做语音活动检测VAD把有效语音段切出来再统计停顿。这里用最简单的能量阈值 VAD 就能满足要求def simple_vad(audio, sr16000, frame_len0.025, energy_th0.01): frame int(sr * frame_len) hop frame // 2 energy librosa.feature.rms( yaudio, frame_lengthframe, hop_lengthhop)[0] voiced energy energy_th intervals [] start None for i, v in enumerate(voiced): if v and start is None: start i * hop / sr elif not v and start is not None: intervals.append((start, i * hop / sr)) start None if start is not None: intervals.append((start, len(audio) / sr)) return intervals def pause_features(intervals): if len(intervals) 2: return np.zeros(6) pauses np.array([intervals[i1][0] - intervals[i][1] for i in range(len(intervals) - 1)]) total_speech sum(e - s for s, e in intervals) total_pause pauses.sum() long_pauses pauses[pauses 0.5] return np.array([ total_pause / (total_speech total_pause), # 停顿总占比 len(long_pauses) / max(len(pauses), 1), # 长停顿比例 pauses.mean() if len(pauses) else 0, # 平均停顿时长 pauses.max() if len(pauses) else 0, # 最长停顿 len(intervals) / (total_speech total_pause), # 近似语速 total_speech # 有效语音总时长 ])energy_th0.01是针对 16kHz、幅度归一化后音频的经验值换成其他数据集最好先画一下能量分布直方图再定。 0.5秒才视为病理性停顿这个阈值取自语音病理学文献的通用标准低于 0.5 秒的停顿在正常老龄化人群中也大量存在取低了会把正常老人误判成早期障碍。提示把 pause_features 和 MFCC 序列拼起来有两种方式。一种是把停顿统计量复制到每个时间帧上和 MFCC 序列一起进 LSTM另一种是序列特征进模型、停顿统计量单独走一个小 MLP最后把两路输出拼接再分类。资源包用的是第一种实现简单效果也够用适合课程设计的节奏。3. 模型搭建与选型从 LSTM 基线到 CNNAttention 的升级路径3.1 先定任务再选模型二分类的标签怎么定这个预测任务通常建模成二分类认知正常 vs 早期认知障碍。标签来源一般是临床量表分数或者医生诊断记录按说话人粒度打标。为什么不是按句子打标因为同一个人的不同录音可能一段正常一段异常按句子打标会让模型学到录音环境噪声而不是认知状态。所以资源包的默认做法是先把一个说话人的所有音频特征按帧拼接再做全局特征池化最后出二分类概率。这个设计在后面数据划分时也必须保持一致。模型选择上我拆这个资源包时发现它的策略很务实先给一个 LSTM 基线保证能跑通再给一个 CNNAttention 作为提分模型最后附了 Transformer 的参考实现作为扩展方向。对课程设计来说基线足够对毕业设计来说提分模型可以写进对比实验表格两个模型的输入输出接口完全一致切换只需要改一行配置。3.2 LSTM 基线处理变长序列的标配LSTM 是处理时序特征的标配用在语音特征序列上顺理成章。资源包里的基线模型结构如下import torch import torch.nn as nn class LSTMBaseline(nn.Module): def __init__(self, input_dim123, hidden_dim128, num_layers2, num_classes2, dropout0.3): super().__init__() self.lstm nn.LSTM( input_dim, hidden_dim, num_layers, batch_firstTrue, bidirectionalTrue, dropoutdropout ) self.head nn.Sequential( nn.Linear(hidden_dim * 2, 64), nn.ReLU(), nn.Dropout(dropout), nn.Linear(64, num_classes) ) def forward(self, x, lengths): # x: (B, T, input_dim)lengths: 每个样本有效帧数 packed nn.utils.rnn.pack_padded_sequence( x, lengths.cpu(), batch_firstTrue, enforce_sortedFalse) out, _ self.lstm(packed) out, _ nn.utils.rnn.pad_packed_sequence(out, batch_firstTrue) mask torch.arange(x.size(1), devicex.device)[None, :] lengths[:, None] mask mask.unsqueeze(-1) out (out * mask).sum(dim1) / mask.sum(dim1) return self.head(out)两点值得注意。pack_padded_sequence是为了不把 padding 帧算进 LSTM 的循环里如果用batch_firstTrue之后直接丢全序列进去模型会被大量全零帧干扰准确率会掉 3 到 5 个点。均值池化同理mask.sum(dim1)是有效帧数除以它才得到真正的平均向量。这里bidirectionalTrue是故意的双向 LSTM 能同时看到某个时间点前后的上下文对停顿这种前后的分布差异特征特别有效代价是参数量翻倍显存吃紧时可以改成单向再对比一组实验。3.3 CNNAttention把局部发音模式和全局停顿都抓住LSTM 的问题是长序列的记忆衰减一段 30 秒的语音特征序列有上千帧LSTM 很难记住开头说了什么。CNNAttention 的思路更直接CNN 在时间维上用一维卷积抓局部发音模式比如某个音节的含糊程度Attention 再对全序列做加权让模型自己学会把注意力放在异常停顿和含糊发音的位置上。class CNNAttention(nn.Module): def __init__(self, input_dim123, num_classes2): super().__init__() self.conv nn.Sequential( nn.Conv1d(input_dim, 64, kernel_size5, padding2), nn.BatchNorm1d(64), nn.ReLU(), nn.Conv1d(64, 128, kernel_size5, padding2), nn.BatchNorm1d(128), nn.ReLU(), ) self.attn nn.MultiheadAttention(128, num_heads4, batch_firstTrue) self.head nn.Linear(128, num_classes) def forward(self, x): # x: (B, T, input_dim) h x.transpose(1, 2) # (B, C, T) h self.conv(h) h h.transpose(1, 2) # (B, T, C) out, attn_weight self.attn(h, h, h) return self.head(out.mean(dim1)), attn_weightnn.MultiheadAttention返回的attn_weight在训练时用不上但在最后验证章节里可以画注意力热力图直接看出模型把高权重放在哪些时间段。kernel_size5对应 50ms 的局部窗口在 16kHz 采样率下大约是两到三个音素的长度太小抓不住发音模式太大又会把停顿和发音混在一起。如果显存不够可以把num_heads从 4 降到 2效果下降不明显但训练速度能快不少。3.4 损失函数与类别不平衡处理认知障碍数据集普遍类别不平衡认知正常的样本通常比早期障碍样本多。直接用CrossEntropyLoss会让模型倾向全猜多数类训练集准确率 80%测试集对着稀有类别直接翻车。资源包用了compute_class_weight计算平衡权重from sklearn.utils.class_weight import compute_class_weight import numpy as np # train_labels 是训练集全体样本标签 weights compute_class_weight( balanced, classesnp.array([0, 1]), ytrain_labels) criterion nn.CrossEntropyLoss( weighttorch.tensor(weights, dtypetorch.float32, devicedevice))balanced模式的计算方式是n_samples / (n_classes * np.bincount(y))少数类的 loss 权重自动变大。如果类别不平衡很严重比如 9:1还可以配合在采样器里对少数类过采样但要小心过采样会反复学习同一条录音容易过拟合所以资源包默认只用了 loss 权重这一层。加权重之后训练集准确率下降几个点是正常的模型开始真的去区分少数类了这时候要看 AUC不要盯着准确率焦虑。4. 训练与评估数据划分、超参数和指标解读4.1 按说话人划分数据测试集里不能出现训练集的人这是整个项目里最容易翻车的一步。如果按音频文件随机划分同一个患者的十几条录音会同时出现在训练集和测试集模型实际上记住了说话人身份而不是认知状态测试准确率虚高到 90% 以上换一个新患者立刻掉回 60%。正确做法是先按说话人 ID 分组再对说话人做随机划分from sklearn.model_selection import train_test_split # speaker_ids: dict音频文件名 - 说话人ID # speaker_label: dict说话人ID - 标签 speakers sorted(set(speaker_ids.values())) train_speakers, test_speakers train_test_split( speakers, test_size0.2, random_state42, stratify[speaker_label[s] for s in speakers]) train_files [f for f in all_files if speaker_ids[f] in set(train_speakers)] test_files [f for f in all_files if speaker_ids[f] in set(test_speakers)]stratify参数保证训练集和测试集里的正常/障碍比例与全量数据一致避免测试集恰好全是某一类。划分完成之后用set(train_speakers) set(test_speakers)断言一下交集为空我每次训练前都会强制跑这个检查这是我踩过坑之后养成的习惯一秒钟能救回一整天。4.2 训练配置与超参数表训练脚本train.py的核心循环如下def train_one_epoch(model, loader, criterion, optimizer, device): model.train() total_loss, correct, n 0, 0, 0 for batch in loader: x batch[feat].to(device) y batch[label].to(device) lengths batch[length].to(device) optimizer.zero_grad() logits model(x, lengths) loss criterion(logits, y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 2.0) optimizer.step() total_loss loss.item() * x.size(0) correct (logits.argmax(1) y).sum().item() n x.size(0) return total_loss / n, correct / nclip_grad_norm_(model.parameters(), 2.0)这行写在这里是有原因的。LSTM 配合长序列梯度范数经常在几个 epoch 后就冲到几十上百不裁剪的话 Loss 会在 30 轮附近突然变成 NaN。裁剪到 2.0 是语音序列任务里比较稳妥的选择1.0 更保守但收敛会变慢。资源包默认的超参数如下这是一组能稳定收敛的配置参数默认值说明采样率 sr16000统一重采样目标帧长 / 帧移25ms / 10ms与语音识别惯例一致MFCC 维数40 差分输入维度 123LSTM hidden128双向两层学习率1e-3Adam阶梯衰减batch size32显存不够降到 16最大 epoch60早停 patience 10梯度裁剪2.0防 NaN 和梯度爆炸学习率用 1e-3 起步每 20 个 epoch 乘以 0.1 衰减。不要一开始就上 1e-4 或 3e-4LSTM 在前几十个 epoch 收敛极慢会让课程设计的训练时间拖到不可接受。如果发现验证集指标 10 个 epoch 没动先检查学习率而不是换模型。4.3 评估指标准确率、F1 和 AUC 怎么配合看这个任务只看准确率会出大问题。类别不平衡下模型全猜正常类也能有 70% 的准确率看起来不错但对临床预测毫无意义。资源包在evaluate.py里同时输出准确率、F1、AUC 和混淆矩阵我建议至少看三个东西F1 的 macro 均值是否接近准确率AUC 是否大于 0.75以及混淆矩阵里少数类的召回率有没有低于 0.5。AUC 大于 0.75 在这个任务里算有区分度0.8 以上是可以写进论文的结果。只报准确率不报 AUC 的模型多半是拿类别不平衡刷上去的写进论文容易被答辩老师一眼看穿。5. 避坑指南五个让我重跑实验的坑5.1 采样率不一致导致特征时间轴错位现象同一批音频有些文件提取出来的特征帧数是 800有些是 2000训练时按长度排序后模型 Loss 发散或者验证集准确率忽高忽低。原因数据集中部分录音是 44.1kHz 录制的部分是 8kHz 电话录音。librosa.load(path, sr16000)默认会重采样但如果漏写了sr16000librosa 会按文件原始采样率加载不同文件的时间轴就完全不同特征的帧数对不上。解决加载时强制指定sr16000并在特征提取入口加一行采样率打印发现偏离 16kHz 的文件记录到日志里人工复核。我后来做任何音频项目都会先跑一遍全量采样率扫描再开始提特征。5.2 VAD 把咳嗽声和翻页声当有效语音现象健康对照组里有人咳嗽或清嗓子VAD 把这段高能量噪声切成一段语音停顿特征里多出好几段假停顿模型分不清是病理性停顿还是生理动作。原因纯能量阈值 VAD 只认幅度咳嗽声和语音都是高能量段无法从能量上区分。解决两步走。先用能量阈值粗切再对每段做时长过滤低于 0.3 秒的段直接丢弃。咳嗽声通常短于一个完整音节这一刀能砍掉大部分噪声段。如果数据集里噪声很杂再考虑用语音活动检测模型如 webrtcvad 做二次确认。5.3 类别不平衡导致模型变成全猜多数类现象训练准确率一路涨到 85%验证集准确率也有 78%但翻混淆矩阵发现少数类召回率是 0模型对每个样本都输出正常类。原因loss 权重没加模型发现全猜多数类时 loss 最小。这在认知障碍数据里极其常见因为正常样本往往比障碍样本多一倍以上。解决按 3.4 节用compute_class_weight(balanced)加权重。加完之后如果准确率掉了 3 到 5 个点别慌这是正常的模型开始真正区分少数类了这时看 AUC 会明显上升。5.4 同一患者的多条录音同时进训练和测试现象测试集准确率 93%AUC 0.96换到新数据立刻跌到 55%。这是最难排查的坑因为代码不报错、指标全好看跑出来的结果自己都想信。原因数据划分时按音频文件随机切同一个患者的 10 条录音被分到了两边模型记住的是说话人身份。解决严格按说话人 ID 划分并在实验记录里写明划分方式。我一般会在 README 里写上本实验结果基于按说话人分层划分测试集包含 N 位未参与训练的说话人防止三个月后回看代码时忘了划分口径。5.5 Loss 变成 NaNpyin 的 NaN 和梯度爆炸现象训练第一个 epoch Loss 就是 NaN或者在第 30 轮左右突然 NaN。前者多半是输入里有 NaN后者多半是梯度爆炸。原因librosa.pyin在无 voiced 帧的位置返回 NaN特征拼接时如果没做filled(0)NaN 会一路传进 LSTM。而 LSTM 加长序列在 30 轮后梯度范数累积到几百不裁剪就会溢出。解决特征提取时把所有含 NaN 的数组统一用np.nan_to_num处理训练循环里加clip_grad_norm_同时每 5 个 epoch 打印一次梯度范数。特征里有没有 NaN 可以在提取完立刻跑一句np.isnan(feat).any()断言这比训练到一半才发现要省太多时间。6. 进阶验证概率校准与注意力可视化确认模型学到的是语音异常模型训练完、指标也好看但你还不知道它到底学到了什么。这一步是区分能跑和能答辩的关键。我一般做两件事概率校准和注意力可视化。概率校准解决的是模型输出的概率置信度问题。用温度缩放在验证集上学习一个最优温度 T把逻辑回归拟合到预测概率上再看校准曲线是否贴近对角线。如果模型输出 0.8 的样本实际只有 0.6 的概率是障碍那这个概率分数就不能直接进论文需要校准后再报告。from torch.calibration import temperature_scaling # 在验证集上学习最优温度 T model.eval() with torch.no_grad(): logits torch.cat([ model(x, lengths)[0] for x, lengths in val_loader ]) temp temperature_scaling(logits, val_labels, init_temp1.0) # 校准后的概率 softmax(logits / temp)注意力可视化则直接回答模型为什么这么判断。用第三章 CNNAttention 里返回的attn_weight取多个头的平均权重画到时间轴上再把对应时间段的音频切出来听一遍。高权重集中在病理性停顿和含糊发音的帧附近说明模型学的是语音异常如果权重均匀分布或者集中在开头结尾的无意义位置说明模型大概率在抄环境噪声这个模型就不能信。从那以后我每次做完一个语音预测模型都会强制走一遍采样率扫描、说话人划分断言、NaN 检查、校准曲线、注意力图这条流程五个检查全过了才敢把代码和结果一起交出去。这套习惯就是从拆这份资源包开始的希望帮到你。本文还有配套的精品资源点击获取