简介:这份资源面向计算机、人工智能及相关专业的学生与研究者,提供一套基于语音识别与机器学习预测阿尔兹海默症早期认知障碍的完整项目方案,适用于毕业设计、课程设计或期末大作业等实践场景。压缩包共85个文件,约4.89MB,包含44个wav语音样本、20张jpeg图像素材、8个Python脚本、3张png可视化图表,以及csv特征数据、joblib模型文件、ipynb笔记本和md说明文档等,覆盖数据采集、特征提取、模型训练与结果展示全流程。项目以深度学习与支持向量机为核心,通过语音信号捕捉言语表达中的细微变化,评估认知障碍风险,并借助特征分布、特征标签关系及模型对比等可视化手段辅助分析。已有22人学习下载。读者可据此获得一套可复现的语音特征工程与分类建模参考,理解从原始音频到预测输出的完整链路,适合作为入门医学AI交叉研究的实践范本。
1. 从一段含糊的语音里,把早期认知障碍的信号挖出来
去年帮一个做医疗方向的朋友看毕业设计选题,他丢过来一个压缩包,名字叫「基于语音识别的阿尔兹海默症早期认知障碍预测.zip」。我第一反应是:这题目听着唬人,但真能跑通吗?后来拆开看,发现它其实是一条挺完整的链路——把受试者的语音录音喂进去,先做特征提取,再用机器学习模型判断是否存在早期认知障碍的风险。它解决的不是「诊断」问题,而是「筛查」问题:在明显症状出现之前,从说话方式里找那些容易被忽略的异常,比如语速变慢、停顿变多、词汇重复、发音含糊。
这类资源适合谁?做机器学习毕业设计的学生、想找一个「有真实数据味道」的课程设计题目的同学,以及想了解语音特征工程怎么落到分类任务上的从业者。它不要求你懂神经内科,但要求你会用 Python 把音频变成模型能吃的数字。我见过太多人拿到这种包,第一步就卡在环境上,或者跑完发现准确率只有 50% 出头,然后开始怀疑人生。这篇笔记就按我实际拆包的顺序,把这条链路讲清楚:数据长什么样、特征怎么提、模型怎么选、坑在哪。
2. 语音认知障碍预测的技术链路:从 WAV 到特征矩阵
2.1 为什么选语音作为早期筛查的入口
阿尔兹海默症的早期认知障碍,在医学上有一套量表评估,但量表依赖医生和受试者面对面,成本高、覆盖窄。语音不一样,它天然带着认知状态的痕迹。一个人说话时的停顿模式、语速变化、音素清晰度,甚至句子之间的衔接流畅度,都和大脑的执行功能、语言功能相关。常见做法是提取声学特征(如 MFCC、基频、能量)和韵律特征(如停顿间隔、语速),再叠加语言层面的特征(如词汇多样性、句法复杂度),拼成一个特征向量,交给分类器。
这个资源的核心思路就是这条:不直接处理原始波形,而是把语音切成短帧,提取每一帧的声学特征,再在整段语音上做统计聚合。这样做的好处是数据量可控,普通笔记本也能跑;代价是会丢失一些时序细节,但对早期筛查这种二分类任务来说,够用。
2.2 特征提取:MFCC 只是起点,别只盯着它
很多人一提语音特征就只想到 MFCC,但在这个任务里,光靠 MFCC 不够。我一般会同时提取以下几类:
- 梅尔频率倒谱系数(MFCC):13 维或 20 维,加一阶、二阶差分,捕捉音色和发音清晰度。
- 基频(F0)及其统计量:均值、方差、范围,反映声带振动稳定性。
- 短时能量和过零率:辅助判断停顿和清浊音。
- 停顿特征:通过能量阈值切出静音段,统计停顿次数、平均停顿时长、最长停顿。
- 语速代理:用音节核检测或简单的能量峰值计数来估算每秒音节数。
下面这段代码是我常用的特征提取骨架,基于 librosa 和 numpy,输入是单声道 WAV 文件路径,输出是一个一维特征向量。
import librosa import numpy as np def extract_features(file_path, sr=16000, n_mfcc=20): # 统一采样率,避免不同设备录制的音频采样率不一致 y, sr = librosa.load(file_path, sr=sr, mono=True) # 预加重,提升高频分量,补偿语音受发音系统影响的高频衰减 y = np.append(y[0], y[1:] - 0.97 * y[:-1]) # MFCC 及其一阶、二阶差分 mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=n_mfcc) mfcc_delta = librosa.feature.delta(mfcc) mfcc_delta2 = librosa.feature.delta(mfcc, order=2) # 基频,用 pyin 比 yin 更稳,但慢一些 f0, voiced_flag, _ = librosa.pyin(y, fmin=50, fmax=400, sr=sr) f0 = f0[~np.isnan(f0)] # 去掉无浊音帧 if len(f0) == 0: f0_stats = [0, 0, 0, 0] else: f0_stats = [np.mean(f0), np.std(f0), np.min(f0), np.max(f0)] # 短时能量和过零率 frame_length = int(0.025 * sr) # 25ms 帧长 hop_length = int(0.010 * sr) # 10ms 帧移 energy = np.array([ np.sum(np.abs(y[i:i+frame_length]**2)) for i in range(0, len(y)-frame_length, hop_length) ]) zcr = librosa.feature.zero_crossing_rate(y, frame_length=frame_length, hop_length=hop_length)[0] # 停顿特征:能量低于阈值视为静音 threshold = np.mean(energy) * 0.1 is_silence = energy < threshold pause_count = 0 pause_durations = [] current_pause = 0 for s in is_silence: if s: current_pause += 1 else: if current_pause > 0: pause_count += 1 pause_durations.append(current_pause * hop_length / sr) current_pause = 0 if current_pause > 0: pause_count += 1 pause_durations.append(current_pause * hop_length / sr) pause_mean = np.mean(pause_durations) if pause_durations else 0 pause_max = np.max(pause_durations) if pause_durations else 0 # 聚合统计:对每一维 MFCC 取均值、标准差、偏度、峰度 feats = [] for seq in [mfcc, mfcc_delta, mfcc_delta2]: feats.extend(np.mean(seq, axis=1)) feats.extend(np.std(seq, axis=1)) feats.extend(np.percentile(seq, 25, axis=1)) feats.extend(np.percentile(seq, 75, axis=1)) feats.extend(f0_stats) feats.extend([np.mean(energy), np.std(energy), np.mean(zcr), np.std(zcr)]) feats.extend([pause_count, pause_mean, pause_max]) return np.array(feats, dtype=np.float32)这段代码的逻辑是:先统一采样率到 16kHz,避免不同录音设备带来的分布偏移;预加重是为了补偿高频衰减,对后续 MFCC 有好处。MFCC 取 20 维,加上一阶二阶差分,一共 60 维,再对每一维做均值、标准差、25% 和 75% 分位数,得到 240 维。基频用 pyin 提取,只保留有浊音的部分,统计四个量。能量和过零率各取均值和标准差。停顿特征用能量阈值法,阈值设成平均能量的 10%,这个值可以根据录音底噪调整,底噪大就调高一点。
参数方面,帧长 25ms、帧移 10ms 是语音处理的常规设置,兼顾时间分辨率和频率分辨率。n_mfcc 设 20 比默认的 13 多捕捉一些细节,但也不是越大越好,太大反而引入冗余。pyin 的 fmin 和 fmax 根据人声基频范围设成 50 到 400 Hz,覆盖大多数成年人的声音。
2.3 数据组织与标签对齐:别让文件名骗了你
这个资源包里通常会有两个目录:一个放音频文件,一个放标签文件。标签文件可能是 CSV,也可能是按文件夹名区分的。我踩过的坑是:文件名里带「AD」「MCI」「HC」这类缩写,但不同来源的命名规则不统一,有的用「ad_001.wav」,有的用「001_AD.wav」,直接按字符串匹配会漏掉一批。
稳妥的做法是先读标签文件,建立「受试者 ID → 标签」的映射,再从音频文件名里提取 ID。如果标签文件里没有 ID,只有顺序号,那就得确认音频文件的排序是否和标签一致。下面是一个标签对齐的示例:
import pandas as pd import os import re def build_label_map(label_csv, audio_dir): df = pd.read_csv(label_csv) # 假设 CSV 有两列:subject_id, label label_map = dict(zip(df['subject_id'].astype(str), df['label'])) audio_files = [f for f in os.listdir(audio_dir) if f.endswith('.wav')] paired = [] for f in audio_files: # 从文件名里提取数字 ID,兼容 ad_001.wav 和 001_AD.wav match = re.search(r'(\d+)', f) if not match: continue sid = match.group(1) if sid in label_map: paired.append((os.path.join(audio_dir, f), label_map[sid])) return paired这里用正则提取文件名里的第一段数字作为 ID,前提是 ID 在文件名里唯一且不与日期、批次号混淆。如果文件名里有多个数字,比如「20230101_ad_001.wav」,那就要根据实际命名规则调整正则,或者直接用标签文件里的完整文件名做匹配。对齐之后,建议打印一下正负样本数量,如果比例悬殊超过 1:5,后面训练时就要考虑类别权重。
3. 模型训练与评估:用 SVM 还是深度学习,得看样本量
3.1 小样本下为什么我优先选 SVM 和随机森林
这个任务的数据集通常不大,几十到几百个受试者,每个受试者一段或几段语音。这种量级下,深度学习容易过拟合,除非你做很强的数据增强或者迁移学习。我一般先用 SVM 和随机森林跑一个基线,看看特征本身有没有区分度。如果基线 AUC 能到 0.75 以上,说明特征工程是有效的,再考虑上深度学习。
SVM 用 RBF 核,配合网格搜索调 C 和 gamma;随机森林看特征重要性,能反过来指导特征筛选。下面是一个训练和评估的骨架:
from sklearn.svm import SVC from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import StratifiedKFold, GridSearchCV from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.metrics import roc_auc_score, classification_report import numpy as np def train_and_evaluate(X, y): # 标准化放在 Pipeline 里,避免交叉验证时数据泄漏 pipe_svm = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(probability=True, class_weight='balanced')) ]) param_grid = { 'svm__C': [0.1, 1, 10, 100], 'svm__gamma': ['scale', 0.01, 0.001, 0.0001] } cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) grid = GridSearchCV(pipe_svm, param_grid, cv=cv, scoring='roc_auc', n_jobs=-1) grid.fit(X, y) print('Best SVM params:', grid.best_params_) print('Best SVM AUC:', grid.best_score_) # 随机森林作为对照 rf = RandomForestClassifier(n_estimators=300, class_weight='balanced', random_state=42) rf_scores = [] for train_idx, test_idx in cv.split(X, y): rf.fit(X[train_idx], y[train_idx]) prob = rf.predict_proba(X[test_idx])[:, 1] rf_scores.append(roc_auc_score(y[test_idx], prob)) print('RF mean AUC:', np.mean(rf_scores)) return grid.best_estimator_, rf关键点:标准化必须放在交叉验证内部,否则测试集的均值和方差会泄漏到训练过程,导致 AUC 虚高。class_weight='balanced' 是为了应对正负样本不均衡,如果正样本很少,这个参数能明显提升召回率。SVM 的 C 越大,对训练误差容忍度越低,容易过拟合;gamma 越大,决策边界越复杂。网格搜索的范围根据数据量调整,样本少就从小值开始试。
3.2 深度学习方案:CNN 和 LSTM 怎么用才不翻车
如果样本量能到几百以上,或者你能做数据增强(比如加噪、变速、音高扰动),可以试试 CNN 或 LSTM。CNN 处理 MFCC 频谱图,把语音当成图像;LSTM 处理帧序列,捕捉时序依赖。但要注意,输入维度不能太大,否则参数爆炸。我一般会把 MFCC 截断到前 40 帧或补齐到固定长度,再送进一个轻量级的 CNN。
import torch import torch.nn as nn class AudioCNN(nn.Module): def __init__(self, n_mfcc=20, n_frames=40): super().__init__() self.conv = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2), ) self.fc = nn.Sequential( nn.Linear(32 * (n_mfcc // 4) * (n_frames // 4), 64), nn.ReLU(), nn.Dropout(0.5), nn.Linear(64, 2) ) def forward(self, x): # x: (batch, 1, n_mfcc, n_frames) x = self.conv(x) x = x.view(x.size(0), -1) return self.fc(x)这个网络很小,适合小样本。BatchNorm 和 Dropout 是防止过拟合的关键。训练时用 Adam 优化器,学习率 1e-3,配合早停。如果验证集 AUC 连续 10 个 epoch 不提升就停。注意,音频长度不一致时,要么截断,要么用 padding,但 padding 太多会引入噪声,我一般截断到 3 秒左右,因为认知障碍相关的语音特征在短片段里也能体现。
4. 避坑与排查:那些让我返工三次的细节
4.1 采样率不统一导致特征分布漂移
现象:训练集 AUC 0.85,测试集掉到 0.6。原因:一部分音频是 44.1kHz,一部分是 16kHz,MFCC 的频率轴对不上。解决:加载时强制sr=16000,并在预处理阶段检查所有文件的原始采样率,不一致的要么重采样,要么剔除。
4.2 静音段没去掉,停顿特征全乱
现象:停顿次数统计出来几百次,明显不合理。原因:录音开头和结尾有大段静音,被当成停顿。解决:用librosa.effects.trim去掉首尾静音,再计算停顿。阈值不要设死,用能量分位数,比如低于 20% 分位数的帧视为静音。
4.3 标签泄漏:同一个受试者的多段语音被分到训练集和测试集
现象:交叉验证 AUC 很高,但换一批数据就崩。原因:一个受试者可能有多段录音,随机划分时同一人的录音同时出现在训练和测试集。解决:按受试者 ID 分组划分,用GroupKFold,确保同一个人的所有录音只出现在一边。
4.4 特征标准化用了全局均值,交叉验证结果虚高
现象:网格搜索选出的最优参数在独立测试集上表现差很多。原因:标准化时用了全部数据的均值和方差,测试集信息泄漏。解决:把StandardScaler放进Pipeline,只在训练折上 fit,再 transform 测试折。
4.5 类别不平衡时只看准确率,模型全预测多数类
现象:准确率 80%,但召回率为 0。原因:正样本只占 20%,模型学会全预测负类。解决:用 AUC、F1、召回率评估,训练时加class_weight='balanced',或者用 SMOTE 做少数类过采样,但注意 SMOTE 只能在训练折上做。
5. 进阶技巧:用特征重要性反推可解释性,顺带提升模型
跑通基线之后,我习惯用随机森林的特征重要性排个序,看看哪些特征真正在起作用。有一次发现,停顿相关的三个特征重要性排在前十,而 MFCC 的高阶差分反而靠后。这说明在这个数据集里,韵律特征比音色特征更有区分度。于是我把 MFCC 降到 13 维,去掉二阶差分,特征维度从 240 降到 150 左右,SVM 的 AUC 反而涨了两个点。特征不是越多越好,冗余特征会稀释距离度量。
另一个技巧是做个简单的特征筛选:用SelectKBest配合f_classif,保留前 50% 的特征,再跑一次交叉验证。如果 AUC 不降反升,说明原来的特征集里有噪声。下面是一个筛选的示例:
from sklearn.feature_selection import SelectKBest, f_classif from sklearn.model_selection import cross_val_score def select_and_evaluate(X, y, k_ratio=0.5): k = int(X.shape[1] * k_ratio) selector = SelectKBest(f_classif, k=k) X_selected = selector.fit_transform(X, y) svm = SVC(probability=True, class_weight='balanced', C=1, gamma='scale') scores = cross_val_score(svm, X_selected, y, cv=5, scoring='roc_auc') print(f'Top {k} features AUC: {scores.mean():.3f}') return selector.get_support(indices=True)注意,SelectKBest也要放在交叉验证内部,否则选择过程会用到测试集信息。我一般会写一个自定义的Pipeline,把筛选和分类串起来,再用GridSearchCV调 k 值。
验证模型是否真的学到了东西,还有一个笨办法:把测试集里的正样本和负样本各挑几个,打印它们的停顿次数和语速代理值,看看是否符合直觉。如果正样本的停顿次数明显更多、语速更慢,那模型大概率是靠谱的。如果看不出差异,就得回头检查特征提取是不是哪里写错了。
从那以后,我每次拿到新的语音数据集,都会先跑一遍特征分布对比,再决定用哪些特征、选什么模型。这个习惯帮我省下了不少返工时间。希望帮到你。
本文还有配套的精品资源,点击获取