ARTICLE DETAIL

资讯详情

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

Python声纹识别课程设计:MFCC+GMM实现说话人识别

Python声纹识别课程设计:MFCC+GMM实现说话人识别 简介Python实现的说话人识别声纹识别算法源码属于完成度较高的课程设计个人项目面向正在学习语音处理、模式识别或需要完成同类大作业的高校学生与开发者既可满足课程设计提交要求也可作为动手实践时的代码参照。整个项目曾获98分评审成绩所有代码均经过严格调试确保解压后即可在实际环境中运行能够有效避开环境配置与核心算法复现中的常见问题。压缩包采用zip格式大小约761KB内容以Python源码为核心结构紧凑便于快速定位预处理、特征提取、模型匹配等关键环节。目前已有327人学习参考常用于毕业设计、课堂项目以及声纹识别入门实践。借助这份源码读者可以直观理解语音信号到声纹特征的转换流程、相似度判定逻辑以及完整工程组织方式既能作为课程设计直接提交的素材也能作为二次开发与算法对比实验的基线项目。1. 说话人识别不是语音识别这份 Python 声纹识别课程设计源码到底能跑出什么用 Python 实现说话人识别也就是声纹识别最开始容易搞混的一点是它跟语音识别是两回事。语音识别回答“你说了什么”声纹识别只回答“说话的人是谁”。这份课程设计源码做的是后者——给一段两三秒的录音判断它来自哪个注册说话人或者判断它是不是某个指定的人。音频经过预加重、分帧、MFCC 特征提取再用高斯混合模型给每个说话人建模最后通过对数似然打分得出结论。评审分能到 98 分说明代码的可读性、注释和流程完整度都经得起现场验收。适合正在做课程设计、毕业设计或者想从零搞懂声学特征加传统机器学习建模的人。想一次把原理、参数和坑都摸清的这套源码值得下下来对照着改。2. 声纹识别的技术路线为什么课程设计首选 MFCC 加 GMM 而不是深度学习2.1 从音频到特征MFCC 为什么是声纹任务的默认起点声纹识别整条链路里特征提取是第一道关也是最容易糊弄过去的一步。MFCC 全称梅尔频率倒谱系数核心思想是模拟人耳听觉对频率的感知不是线性的低频分辨细、高频分辨粗所以先把频率轴映射到梅尔刻度再经过滤波器组和倒谱变换把一帧语音压缩成 13 维左右的向量。这一段几乎没有任何“智能”成分但它的稳定性经过了二十年实际系统验证直到今天很多深度学习声纹模型的前端仍然用 filter bank 特征只是换了个取 log 的方式。为什么课程设计不直接上深度学习原因很实在x-vector、ECAPA-TDNN 这类模型要训出可用效果至少需要几小时到几十小时的语音数据还得有 GPU而课程设计的数据量往往是每人十几条、每条几秒扔给神经网络一跑就是过拟合。MFCC 加 GMM 是传统声纹识别的标准基线特征维度低、训练快、在几十秒注册样本下反而更稳。更重要的是答辩好讲每一帧怎么变成向量、模型怎么拟合分布都可以在黑板上推出来比甩一个黑匣子模型加分得多。MFCC 提取涉及七个步骤每一步都有明确的参数约定先列一份我常用的默认配置参数推荐值说明采样率 sr16000 Hz麦克风录音常用 16k电话语音用 8k帧长 frame_len25 ms对应 n_fft400 16kHz帧移 frame_shift10 ms相邻帧重叠 15ms保证平滑MFCC 维数13加一阶差分可扩到 26梅尔滤波器数量40覆盖 0-8kHz人声主要能量区DCT 保留系数前 13 个高阶系数对声道个性信息贡献小这组数值不是玄学而是语音信号处理里反复验证过的标准配置。改采样率会影响帧长对应的点数改帧移会影响时间分辨率只要动了其中一个后面所有环节都要跟着变。2.2 预处理链路实现预加重、分帧、加窗的代码与参数拿到原始波形之后第一件事不是直接算 MFCC而是做预加重。语音的高频段能量天然比低频低而声纹信息很大程度藏在高频共振峰里需要用一阶高通滤波器把它抬起来。实现非常简单import numpy as np def pre_emphasis(signal, alpha0.97): # signal 是一维 float 数组取值范围约在 [-1, 1] return np.append(signal[0], signal[1:] - alpha * signal[:-1])alpha 取 0.95 到 0.97 都常见作用是让当前采样点减去前一个采样点的 0.97 倍。这个操作会把低频压下去、把高频相对抬起来代价是整体 SNR 稍微变化但对说话人个性特征提取利大于弊。代码里signal[1:] - alpha * signal[:-1]必须用 numpy 数组Python 原生 list 不支持这种切片减法。分帧是把连续波形切成 25ms 的小段因为 FFT 假设信号是平稳的而语音在 10-30ms 尺度上可以近似看成平稳。实现时注意边界处理def framing(signal, frame_len0.025, frame_shift0.010, sr16000): frame_samples int(round(frame_len * sr)) # 400 个采样点 shift_samples int(round(frame_shift * sr)) # 160 个采样点 n_frames 1 (len(signal) - frame_samples) // shift_samples frames np.zeros((n_frames, frame_samples)) for i in range(n_frames): start i * shift_samples frames[i] signal[start:start frame_samples] return frames这段代码的逻辑是按帧移在波形上滑动截取固定长度片段。n_frames 的计算方法保证最后一帧不越界而不是简单用整除否则尾部音频会被丢掉。3 秒的 16k 音频约 48000 点分帧后大约得到 298 帧这个数量级对 GMM 训练完全够用。分帧之后要加窗一般用汉明窗def apply_hamming(frames): window np.hamming(frames.shape[1]) return frames * window汉明窗的作用是压低帧边缘的幅度避免把一帧截断成矩形时在频域引入旁瓣泄漏。矩形窗在频域会出现很宽的拖尾会让相邻频带互相污染而汉明窗旁瓣衰减更干净。课程设计里如果发现两个说话人区分度很差先别怀疑模型检查一下加窗那步是不是被跳过了。2.3 用 librosa 一句话拿到 MFCC但要知道它背后是什么实际写工程时一般不会手搓 FFT、梅尔滤波器组和 DCT直接用 librosa 一行就能取到 MFCCimport librosa def extract_mfcc(wav_path, sr16000, n_mfcc13, n_fft400, hop_length160, n_mels40): y, _ librosa.load(wav_path, srsr, monoTrue) mfcc librosa.feature.mfcc( yy, srsr, n_mfccn_mfcc, n_fftn_fft, hop_lengthhop_length, n_melsn_mels ) return mfcc.T # 转成 (帧数, 13) 的形状这里最关键的是理解参数对应关系n_fft400 就是 25ms 窗在 16k 采样率下的采样点数hop_length160 就是 10ms 帧移n_mels40 与默认配置一致。mfcc.T转置很重要librosa 默认返回形状是 (特征维数, 帧数)而 sklearn 的模型输入要求特征是列、样本是行转置后变成 (帧数, 13)训练时直接丢给 GaussianMixture 就行。很多老课程设计源码用的是 python_speech_features 这个库API 长得不一样但算出来的东西和 librosa 基本一致不要看到 import 不同就以为代码有问题。如果你拿到手的源码是 python_speech_features 版本只需要注意它默认的 winlen0.025、winstep0.01两个库混用前先用一个 wav 文件各算一遍比对数值差异。还差一步就完整了声纹识别通常要拼上一阶差分因为单帧 MFCC 只刻画静态频谱包络而说话人个性大量体现在频谱随时间的变化上。常见做法是在 13 维基础上拼 delta 变成 26 维librosa 也给了现成函数mfcc_delta librosa.feature.delta(mfcc, width9) feature np.hstack([mfcc, mfcc_delta])width 参数控制差分窗口宽度课程设计用默认 9 就够了太大会把快速变化抹平太小噪声敏感。2.4 GMM 建模为什么高斯混合模型能当“声纹指纹”MFCC 特征拿到之后问题变成怎么把一个人的几百帧特征浓缩成一个“模型”。高斯混合模型的做法是假设一个人的特征分布可以由若干高斯分量叠加而成每个分量对应一种发音状态或声道形态。说“每个人一个 GMM”就是这个意思——训练时只喂这个人的特征模型参数就成了他的声纹指纹。scikit-learn 提供了现成的 GaussianMixture训练代码非常短from sklearn.mixture import GaussianMixture def train_gmm(features, n_components8): gmm GaussianMixture( n_componentsn_components, covariance_typediag, max_iter200, random_state42 ) gmm.fit(features) return gmm参数说明n_components 是混合分量数课程设计规模下 8 到 16 足够分量越多拟合越细但越容易在小数据上过拟合covariance_type 用 diag 表示每个高斯分量用对角协方差参数数量少、训练快对声纹任务效果不比 full 差太多full 在数据量不足时反而容易估计出病态矩阵max_iter 200 是 EM 算法的最大迭代次数一般几十轮就收敛设大点只是保险。训练时喂进去的是 (帧数, 13) 或 (帧数, 26) 的矩阵sklearn 会自动做 EM 迭代。这里有一个常见的理解偏差GMM 不是把音频压成一个向量而是保留了一个概率分布识别时用测试帧去计算“这个分布生成这些帧的可能性”这就是下一章打分模块的基础。3. 源码拆解训练、注册、识别三个模块怎么串成一条完整的身份验证链路3.1 目录结构与核心文件职责拿到声纹识别课程设计源码我一般先不急着跑先把 zip 里的目录结构画出来。这类项目通常按“特征提取、模型训练、识别打分”三段组织解压后常见结构是下面这样路径职责关键点data/train/每个说话人一个子目录存放训练 wav目录名就是说话人标签data/test/测试音频不与训练同源答辩最容易在这里翻车features.pyMFCC 提取与预处理封装采样率、帧长、帧移的全局常量train_model.py遍历训练集训练每人一个 GMM输出 pickle 模型文件recognize.py加载模型对测试音频打分包含阈值判断逻辑requirements.txt依赖清单numpy、scipy、librosa、soundfile、scikit-learn这个结构的核心设计思想是让三个模块尽量解耦features.py 只负责“波形变成特征”train_model.py 不关心音频怎么读recognize.py 不关心模型怎么训。我见过不少课程设计把训练和识别写在一个脚本里跑倒是能跑但答辩时老师问“怎么单独加一个说话人”代码就会乱成一团。3.2 训练模块把每个人的音频变成模型文件训练模块的输入是一个目录目录下每个子目录代表一个说话人子目录里是若干条 wav。流程是先遍历所有说话人对每人提取全部帧的 MFCC堆叠成一个矩阵然后训练一个 GMM 并存入字典import os import pickle import numpy as np from features import extract_mfcc from sklearn.mixture import GaussianMixture def build_models(train_dir, n_components8): models {} for speaker in os.listdir(train_dir): speaker_dir os.path.join(train_dir, speaker) if not os.path.isdir(speaker_dir): continue frame_list [] for wav in os.listdir(speaker_dir): if not wav.endswith(.wav): continue mfcc extract_mfcc(os.path.join(speaker_dir, wav)) frame_list.append(mfcc) all_frames np.vstack(frame_list) # 该说话人的全部帧 gmm GaussianMixture( n_componentsn_components, covariance_typediag, max_iter200, random_state42 ) gmm.fit(all_frames) models[speaker] gmm print(f{speaker}: {all_frames.shape[0]} frames) with open(models.pkl, wb) as f: pickle.dump(models, f) return models逻辑说明np.vstack(frame_list)把每条音频的 (帧数, 13) 矩阵纵向拼接形成这个人全部音频的总帧矩阵。这么做的理由是一个人内部有多种发音状态单条音频的帧数太少GMM 容易欠拟合多条音频拼起来能让分布估计更稳定。models[speaker] gmm的字典键就是目录名识别阶段直接用这个名字作为输出标签。参数说明speaker 目录名建议只用数字或英文不要带中文和空格否则 pickle 跨平台加载时可能出现编码问题n_components 是全局参数如果发现某个人音频特别长帧数上万可以适当调到 16但课程设计规模 8 就够。训练结束后把 models 字典 pickle 序列化识别阶段直接读文件不用每次重新训练。3.3 识别打分log 似然和阈值是怎么决定“是否本人”的识别模块做的事情本质上是一件事拿测试音频的每一帧去每个 GMM 下计算对数似然然后决定它属于谁或是否被拒绝。核心代码如下import pickle import numpy as np from features import extract_mfcc def recognize(models_path, test_wav, thresholdNone): with open(models_path, rb) as f: models pickle.load(f) mfcc extract_mfcc(test_wav) scores {} for name, gmm in models.items(): # gmm.score 返回所有帧的 log 似然之和 avg_log_likelihood gmm.score(mfcc) / len(mfcc) scores[name] avg_log_likelihood best_name max(scores, keyscores.get) best_score scores[best_name] if threshold is not None and best_score threshold: return unknown, best_score return best_name, best_score逻辑说明gmm.score(mfcc)返回的是该模型下全部测试帧的对数似然总和直接用它做比较会出问题——音频越长帧数越多总和天然越大。所以必须除以len(mfcc)得到每帧平均对数似然把时长因素消除这样 1 秒的测试音频和 5 秒的测试音频才能公平比较。这是新手最容易漏的一步漏掉的直接后果是长音频永远得高分。参数说明threshold 是拒识阈值只有传了它系统才具备“不是注册人就不放行”的能力。所有注册人的分数都低于阈值时返回 unknown。阈值通常是个负数数值范围取决于特征维度和 GMM 配置不能拍脑袋写一个 0 或 1 进去后面第 5 章会专门讲怎么确定它。3.4 1:1 验证与 1:N 辨认两个场景的调用差异声纹识别落地时分成两个场景调用方式完全不同。1:1 验证是“你说你是谁我就只拿你的模型来对”比如门锁里录入过张三张三说“开门”系统只跟张三的模型比。1:N 辨认是“不管你是谁我拿所有人模型都过一遍取分数最高的人”比如考勤打卡时系统不知道当前是谁。1:1 场景必须设阈值否则陌生人只要声音足够像某个人也会被放进来1:N 场景可以只取最大值但如果有陌生人乱入强取一个名字照样是错的所以最稳妥的做法是 1:N 选出最佳人之后再用阈值兜底。上面的 recognize 函数把 threshold 留成可选参数正是为了同时兼容这两个场景。答辩演示时建议两种都跑一遍向老师说明阈值在这里起的是“兜底拒识”作用这是声纹系统与普通分类器最大的区别。4. 跑通全流程Python 环境配置、录音数据集准备与参数调整4.1 环境配置依赖库和 Python 版本怎么选课程设计源码跑不起来的首要原因八成是依赖库版本打架。声纹识别项目涉及的库很集中我一般按下面这条命令装pip install numpy scipy scikit-learn librosa soundfile逻辑说明numpy 和 scipy 是底层数值计算依赖scikit-learn 提供 GaussianMixture 和后续评估用的工具librosa 负责 MFCC 提取soundfile 是 librosa 读取 wav 的后端库这个组合覆盖了整个训练和识别链路。如果源码里用的是 python_speech_features再补一条pip install python_speech_features参数说明Python 版本建议 3.8 到 3.10。Python 3.12 下个别 numpy 版本会和 scikit-learn 出现二进制兼容告警一旦遇到“module compiled against API version”之类报错不要硬解直接建一个 Python 3.9 的虚拟环境重装十分钟能解决别人折腾两小时的问题。4.2 录音数据集准备5 个人、每人 20 条怎么录才不被答辩挑毛病声纹识别的数据是自己录的这既是好事也是坑。好处是可以完全控制内容、时长和录制环境坑在于录音质量直接决定识别率而且录坏了很难补。我建议第一轮就按“5 个人、每人 20 条、每条 2 到 3 秒”的规模准备其中 15 条训练、5 条测试。测试音频必须在训练录音之后过十分钟再录一遍内容换个说法这样才叫真正的开卷有备、数据不泄漏。可以用一段简单的录音脚本sounddevice 直接采集麦克风import sounddevice as sd import soundfile as sf def record_once(save_path, duration3.0, sr16000): audio sd.rec(int(duration * sr), sampleratesr, channels1, dtypefloat32) sd.wait() sf.write(save_path, audio, sr) print(fsaved: {save_path})逻辑说明sd.rec是阻塞式采集录够 duration 秒后返回sd.wait()等录音完成channels1保证单声道避免双声道数据在后续处理里平白多一倍计算量。保存时用 float32 格式与 librosa 默认的数值范围一致省去类型转换。参数说明duration 取 2-3 秒太短帧数不足GMM 估计不准太长录音过程容易引入环境噪声变化。录音时人和麦克风保持 30 厘米左右环境安静同一句话不要读第二遍来凑数——每一条都换内容模型才能学到发音变化而不是背文本。这一步省了后面识别效果不稳定时真的没有后悔药。4.3 训练与识别命令从零跑到出结果只需要三个文件数据准备好之后整个流程就是两步先训练再识别。命令长这样python train_model.py --data_dir ./data/train --model_path ./models.pkl --n_components 12 python recognize.py --model_path ./models.pkl --audio ./data/test/speaker_01_test.wav第一行的含义是告诉训练脚本去哪里找训练数据、把模型存到哪、每个 GMM 用几个高斯分量。第二行加载模型文件对一条测试音频打分并打印出最可能的说话人和分数。两份脚本都只需要把路径参数改对就能跑不需要改任何业务代码这也是解耦设计的好处。参数组合的经验值数据量是每人 15 条、每条 3 秒的情况下n_components 取 8 到 12如果每人数据量减半就降到 6防止过拟合。测试阶段第一次跑建议不传 threshold先看分数输出确认同人分数和异人分数大致在什么区间再决定阈值怎么设。4.4 快速验收同人分数和异人分数有没有拉开不要上来就调阈值先做一个最直观的验收实验选一个注册说话人的测试音频和另一个注册说话人的测试音频分别去同一个模型打分看两个分数是否明显分开。比如 speaker_01 的模型对 speaker_01 自己的测试音频打 -28.5对 speaker_02 的测试音频打 -41.3那这个模型就是可用的中间差了 13 个点阈值随便设在 -35 附近都有余量。如果两个分数只差两三个点甚至同人分数比异人分数还低问题几乎一定出在数据或特征环节录音串号了、采样率不统一、静音太长、训练和测试用了同一条音频按优先级排查。这组比对的代码就是反复调用第 3 章的 recognize 函数不需要新逻辑但这一步值得每次改完参数都做一遍它会告诉你分数分布的长相。5. 避坑课程设计与答辩里最常翻车的 5 个声纹识别问题5.1 采样率不统一特征维度对不上现象训练阶段一切正常测试阶段 librosa.load 偶尔报错识别分数整体偏低同一个人的测试音频得分比陌生人还不可靠。原因录音设备输出了 44100 Hz 的 wav而训练时统一用的 16000 Hz。特征提取脚本里 load 时没有强制指定 sr导致有些音频被 resample、有些没有GMM 学到的分布和测试时的特征分布根本对不上。声纹任务里这是最隐蔽的坑因为报错不一定出现两套采样率都能跑只是结果不可信。解决所有 load 调用统一强制 16k已经读进内存的数组用 librosa.resample 修正import librosa # 统一方案load 时就指定 y, sr librosa.load(wav_path, sr16000, monoTrue) # 如果已经拿到的数组是 44.1k先记住原采样率再重采样 y_resampled librosa.resample(y, orig_sr44100, target_sr16000)从那以后我拿到任何 wav 都先打印 sr 字段确认绝不信任后缀名。5.2 静音段没处理陌生人也能打高分现象测试时录一段空白或者纯环境噪声系统将其判成某个注册用户而且分数还挺高。原因静音帧的频谱包络趋同MFCC 计算出来的向量在不同人之间差异极小。GMM 对能量极低的帧照样计算似然一段 3 秒音频里如果前 1 秒是静音这 1 秒的帧会把整体分数往同一个方向拉弱化真实语音段的作用。解决提取 MFCC 前先做静音裁剪能量低于阈值的帧直接丢弃def trim_silence(y, sr16000, top_db30): y_trimmed, _ librosa.effects.trim(y, top_dbtop_db) if len(y_trimmed) sr * 0.3: return y # 整段几乎都是噪声时保留原信号 return y_trimmedtop_db30 表示低于峰值 30dB 的段视为静音0.3 秒的下限是防止录音太差导致裁剪后只剩几个帧、连特征都提不出来。引入裁剪之后同人分数会明显更集中。5.3 soundfile 后端缺失读音频报各种看不懂的错现象Windows 上跑 librosa.load 报 “File format not recognised”或者 soundfile 报 “Error opening file”但同一个 wav 用别的播放器能正常打开。原因librosa 解码音频默认依赖 libsndfilePython 端就是 soundfile 库。环境里没装 soundfile 时librosa 会退回 audioread 走 ffmpeg 兜底而 ffmpeg 路径没配好就会给出上述报错。这是环境问题不是代码问题。解决明确装 soundfile并且所有音频读取都走 librosa.load 单一入口不要一部分用 scipy.io.wavfile 读、一部分用 librosa 读两套读法的数值范围和位深处理不一致混用会导致特征数值漂移。如果源码里已经有 scipy.io.wavfile 读取的音频先转成 float32 再进入特征提取否则 int16 的数值范围是 -32768 到 32767和 librosa 的 -1 到 1 相差三万倍GMM 训练直接崩。5.4 阈值拍脑袋设拒真和误识全乱套现象threshold 设成 0陌生人全被放进来设成 -20本人也频繁被拒设成 -50陌生人又全过了。原因每帧平均对数似然是负数且带有模型特定的量纲不同特征维度、不同 n_components 下分数范围完全不同。这个值没法靠直觉定必须从数据里估。解决把验证集同人分数和异人分数分别收集起来取两个分布的中间点作为初始阈值import numpy as np genuine [] # 同人打分结果 impostor [] # 异人打分结果 # 填充两组分数后线性近似取分界 threshold (min(genuine) max(impostor)) / 2 print(genuine range:, min(genuine), max(genuine)) print(impostor range:, min(impostor), max(impostor)) print(threshold:, threshold)这个线性近似不是最优解但课程设计答辩足够讲清楚。更严谨的做法是下一章的 EER 计算。核心原则是阈值永远来自分数分布不来自想象。5.5 训练测试同源识别率再高也是假象现象自己测的时候识别率 98%答辩现场老师随便录一句话系统直接认错人场面一度很尴尬。原因训练和测试用了同一条 wav或者用了同一次录音的前后半段。MFCC 帧之间高度相关GMM 相当于把同一段声音的分布背下来了换一个人、换一次录音分布立刻漂移。这是声纹识别和图像分类最大的不同图片多看几次不会变声音每次说都不一样。解决严格按“会话”切分数据。第一轮录音全部进训练集隔十分钟再录第二轮全部进测试集内容不能重复情绪和语速可以有变化。如果你拿到手的源码自带数据集先检查训练目录和测试目录是否来自同一批录音文件是的话整包可信度都要打个问号。能拿 98 分的作业和答辩翻车的作业差距往往就藏在数据这一层。6. 验证声纹模型真能用的两个手段EER 曲线与余弦相似度模板6.1 用同人/异人分数算等错误率 EER阈值调完了怎么向老师证明这套模型不是碰巧能用等错误率 EER 是最简洁的指标。它描述的是误识率和拒真率相等时的数值越低说明系统区分能力越强。实现不复杂def compute_eer(genuine_scores, impostor_scores): thresholds np.linspace( min(genuine_scores impostor_scores), max(genuine_scores impostor_scores), 200 ) best_diff 1.0 eer 1.0 for thr in thresholds: far np.mean([s thr for s in impostor_scores]) frr np.mean([s thr for s in genuine_scores]) diff abs(far - frr) if diff best_diff: best_diff diff eer (far frr) / 2 return eer逻辑说明FAR 是异人分数越过阈值的比例FRR 是同人分数低于阈值的比例。理想系统存在一个阈值让两者都接近 0实际系统只能在两个错误之间权衡EER 就是两者的平衡点。运行前先保证 genuine 和 impostor 两个列表来自独立录音否则算出来的 EER 再低都是自欺欺人。课程设计规模下 EER 能到 10% 以内就算可演示超过 20% 先回去查特征提取别急着改阈值。6.2 轻量方案MFCC 均值向量做声纹模板如果不做 GMM还有一种更直观的实现把一个人所有帧的 MFCC 求平均得到一个模板向量测试时把测试音频的 MFCC 均值向量跟模板算余弦相似度。代码很短def build_template(frames): return frames.mean(axis0) def cosine_similarity(vec_a, vec_b): return float(np.dot(vec_a, vec_b) / (np.linalg.norm(vec_a) * np.linalg.norm(vec_b) 1e-8))这个方案适合在答辩 PPT 上画一张“特征空间相似度”的图解释起来比 GMM 直观但它丢掉了帧与时序信息同一个人换情绪、换语速时分数波动很大阈值一般取 0.7 到 0.85 之间也要靠验证集微调。我的建议是把它当作可视化辅助和快速原型正式打分仍然用 GMM 的对数似然两条路都跑通你对这套声纹系统的理解才算完整。我第一次做声纹课程设计时就是栽在训练测试同源上训练集识别率 98%答辩现场老师现场录一句话系统直接认成另一个人。从那以后我每次跑完训练都强制把录音会话分开重录一轮测试再算一次 EER高于 15% 就回头查特征和录音质量绝不靠调阈值自欺欺人。希望帮到你。本文还有配套的精品资源点击获取
返回列表