
简介这是一份用Python实现的说话人识别声纹识别算法源码来自个人课程设计大作业适合正在完成相关课题的学生以及想快速了解声纹识别工程实现的开发者。项目评审分达到98分代码经过严格调试可以直接运行免去了自己搭建环境和反复排错的困扰。压缩包大小约761KB轻量紧凑下载后即可查看主体代码便于对照学习或在此基础上做二次开发。目前已有327人学习浏览说明它在同类课程设计中有一定参考热度。整体围绕说话人识别的主要流程组织能够帮助读者理解语音特征提取、说话人建模、身份比对等关键环节如果配合课程设计报告阅读更能梳理从音频输入到识别输出的完整链路对准备答辩或扩展算法对比实验也有直接帮助。1. 课程设计要的声纹识别到底在做什么声纹识别在课程设计里是个既讨喜又容易翻车的题目。讨喜在它和语音识别一样贴着“人工智能”标签本质却是特征加分类器的经典模式识别不依赖深度学习也能做出可演示的结果翻车在很多人把说话人识别做成了“整段录音算个MFCC均值丢给SVM一分类”演示时分数不错换个人换个麦克风立刻露馅。这篇笔记围绕“Python实现说话人识别(声纹识别)算法源码”这类课程设计包展开目标是让你拿到源码之后不仅跑得动还能讲清楚每个模块为什么这么写。读完你会得到一条完整链路特征怎么提、模型怎么训、阈值怎么定、坑在哪里答辩问不倒。2. 先把声纹识别拆开从 MFCC 特征到 GMM-UBM 的选型逻辑2.1 说话人识别和语音识别识别对象不是内容语音识别回答“说了什么”声纹识别回答“是谁在说”。两者可以共用MFCC这类前端特征但后续建模范式完全不同。语音识别需要建模音素序列声纹识别要建模说话人特有的声道形状、发音习惯和韵律特征。同一个人的同一句话两次录音的波形不可能一样但声学特征分布应该稳定。这就是声纹系统普遍采用“特征分布建模”而不是“模板匹配”的根本原因。声纹任务分两类说话人确认即1:1验证“你就是张三吗”说话人辨认即1:N判断“你是谁”。课程设计通常做1:N辨认验证集上输出top-1说话人。这里有个容易踩的认知误区1:N的准确率对阈值极其敏感阈值定高了熟人被拒定低了陌生人混进来这层关系在后面第4章会专门展开。2.2 MFCC 为什么是声纹特征的事实标准MFCC是梅尔频率倒谱系数。人耳对频率的感知不是线性的低频分辨率高、高频分辨率低Mel刻度就是对这个非线性关系的近似。MFCC的提取流程是预加重、分帧加窗、FFT、Mel滤波器组、取对数、DCT、保留前若干系数。前几步和语音识别完全一样差别在于语音识别通常保留39维也就是13维MFCC加一阶二阶差分声纹的GMM方案保留20维左右就够了差分系数对GMM的帮助不大反而增加计算量。参数上需要注意采样率16k是标配电话语音8k也能用n_fft取512大约32mshop_length取160大约10ms这是语音处理的经典搭配。MFCC第0个系数是帧能量它很容易受录音距离、麦克风增益和音量影响在声纹任务里我一般直接丢掉避免模型把“音量大小”当成“说话人特征”。网上翻得到很多“免费python源码大全”式的仓库里面声纹识别项目五花八门但多数把MFCC参数写死且不做解释换一套数据就崩。参数背后的含义才是一个课程设计源码包真正值钱的部分。2.3 GMM-UBM 方案为什么适合课程设计高斯混合模型用若干高斯分布去拟合每一帧MFCC特征的分布。一个人说话习惯稳定他的特征分布就稳定。课程设计里最稳妥的做法是“一人一个GMM”训练时用某说话人的所有录音帧去拟合一个GMM识别时把待测录音的每一帧往每个模型上算对数似然取最大者作为判定结果。更标准的做法是UBM加MAP自适应先拿所有说话人的数据训练一个通用背景模型UBM再对每个说话人用他自己的数据做MAP自适应。UBM刻画“普通人说话长什么样”MAP自适应让模型只学“这个人偏离普通人多少”。这个方案数据利用率高、不易过拟合但代码量大一层。课程设计时间紧可以先用一人一个GMM的简化版论文里注明“完整版应使用UBMMAP”。常见参数是n_components取8到32协方差类型用diag迭代200次内收敛。为什么不直接上深度学习数据量不够、没有GPU、答辩需要讲清原理这三个理由在课程设计场景里几乎是硬约束。下面这张表对比了课程设计常见的几条路线。方案特征分类/打分数据要求训练硬件可解释性GMM-UBMMFCCGMM/MAP每人数十秒CPU高i-vectorMFCC统计量余弦/PLDA每分钟级CPU中x-vector帧级特征TDNNPLDA每小时级GPU低ECAPA-TDNN频谱/原始波形深度网络大规模GPU低课程设计卡在数据量和硬件上选GMM最稳。如果老师要求“体现前沿”可以在论文里加一个第6章的x-vector对比实验。GMM-UBM的训练只要几秒钟参数调整有明确的物理含义这些正是课程设计最看重的东西。3. 跑通最小可用系统特征提取、模型训练与识别脚本3.1 环境准备python 安装、依赖清单与目录结构拿到源码包后先不要急着跑先看依赖。声纹识别的课程设计通常只用四个库numpy、scikit-learn、librosa、soundfile。前两个做数值计算和GMM模型后两个做音频读取和特征提取。pip install numpy scikit-learn librosa soundfile如果机器上还没有python环境到python官网下载安装包装的时候勾选“Add Python to PATH”。很多课程设计翻车不是算法问题而是python安装时没勾PATH命令行里敲python直接提示不是内部命令。pycharm配置python环境也一样新建项目时选“Previously configured interpreter”指向你装好依赖的解释器。import librosa时如果报numba相关错误是numba版本和librosa不匹配重新装librosa 0.10.x配合numpy 1.24基本能解决。代码目录建议按下面这种结构组织。数据放dataset模型放models测试音频单独放test。project/ dataset/ spk01/ a.wav b.wav spk02/ c.wav d.wav models/ test/ test1.wav这种结构的好处是路径逻辑清晰识别脚本只需要遍历两个目录不需要维护任何录音清单文件。源码包里如果用的是Excel或CSV记录音频路径反而容易在Windows和Linux之间踩路径分隔符的坑。3.2 MFCC 特征提取librosa 实现与参数说明MFCC提取是整个系统的地基。代码里librosa一行就完成了“分帧→加窗→FFT→Mel滤波→对数→DCT”但这一行背后的参数才是需要调的。import librosa import numpy as np def extract_mfcc(path, n_mfcc20, top_db20): # 读取音频重采样到16k单声道 y, sr librosa.load(path, sr16000, monoTrue) # 去掉首尾静音段避免静音帧污染特征分布 y, _ librosa.effects.trim(y, top_dbtop_db) # 提取MFCC返回(n_mfcc, 帧数)去掉第0个能量系数后转置 mfcc librosa.feature.mfcc( yy, srsr, n_mfccn_mfcc, # 返回n_mfcc个系数 n_fft512, # FFT窗口长度约32ms hop_length160, # 帧移约10ms windowhamming # 汉明窗语音处理默认配置 ) return mfcc[1:].T # 返回(帧数, n_mfcc-1)的特征矩阵这段代码的逻辑是librosa.load把任意采样率的音频统一到16klibrosa.effects.trim用能量阈值去掉首尾静音mfcc函数内部完成全部分帧和频谱计算。返回的矩阵形状是(帧数, 19)每一行是一帧的特征向量直接作为GMM的一个训练样本。参数怎么改取决于录音环境。环境嘈杂时top_db从20提到25会切掉更多低能量段但也可能误伤轻声说话的开头辅音。n_mfcc超过20对GMM提升有限反而增加计算量。hop_length越小帧数越多训练越慢。课程设计里20维、512窗口、160帧移这组参数已经经过大量验证不需要改。3.3 训练 GMM按说话人建模与训练参数拿到特征后训练代码的核心是遍历每个说话人的目录各自训练一个模型而不是把所有数据塞进一个模型里。import os import glob import joblib from sklearn.mixture import GaussianMixture def train_one_speaker(wav_paths, n_components16, max_iter200): frames [] for wav in wav_paths: mfcc extract_mfcc(wav) # (帧数, 19) frames.append(mfcc) X np.vstack(frames) # 合并所有帧得到(总帧数, 19) gmm GaussianMixture( n_componentsn_components, covariance_typediag, # 对角协方差参数少且稳定 max_itermax_iter, random_state42 ) gmm.fit(X) return gmm def train_all(data_dirdataset, model_dirmodels): os.makedirs(model_dir, exist_okTrue) for spk_dir in sorted(os.listdir(data_dir)): spk_path os.path.join(data_dir, spk_dir) if not os.path.isdir(spk_path): continue wavs glob.glob(os.path.join(spk_path, *.wav)) gmm train_one_speaker(wavs) joblib.dump(gmm, os.path.join(model_dir, f{spk_dir}.gmm)) print(f{spk_dir}: {len(wavs)} 段录音训练完成)这段代码的逻辑有三点。第一用np.vstack合并所有帧GMM只关心特征分布不关心哪帧来自哪段录音。第二joblib把模型落盘识别阶段直接加载不用重新训练。第三spk_dir作为说话人标签目录名建议用纯英文或拼音避免后续识别脚本在Windows上处理中文标签出问题。关键参数是covariance_typediag。如果默认用full19维特征拟合出的完整协方差矩阵容易过拟合训练也慢很多。n_components16是经验起点男声女声差异大的数据集可以降到8每人录音时长超过一分钟可以升到32。判断是否过拟合看训练集和测试集的对数似然差距差距过大就降分量数。3.4 注册与识别打分逻辑与 top-1 输出识别阶段的核心是打分函数。sklearn的GaussianMixture.score返回的是平均对数似然内部已经除以帧数所以不同长度的录音可以直接对比得分。def identify(wav_path, gmm_models, threshold-40.0): mfcc_frames extract_mfcc(wav_path) scores {} for name, gmm in gmm_models.items(): # score()返回平均对数似然数值越大代表越可能是该说话人 scores[name] gmm.score(mfcc_frames) best_name max(scores, keyscores.get) best_score scores[best_name] if best_score threshold: return unknown, scores return best_name, scores打分逻辑里有个容易忽视的点GMM方案天然支持变长语音因为score已经是逐帧平均录音长短不会影响得分量纲。threshold-40是经验值它表示“平均每帧的对数概率低于这个值就认为是陌生人”。这个值必须在验证集上按第4章的EER方法调不能拍脑袋定死。课程设计通常要求展示得分排名。可以在identify外面再包一层把scores按降序打印前三个候选同时输出真实标签做对比。训练完成后跑一遍测试集把每段录音的识别结果和真实标签比对就能得到最基础的系统准确率。这个准确率只反映“辨认”能力还不反映“拒识”能力后者需要第4章的阈值评估来补。4. 把准确率调得像样阈值、EER 与数据集划分4.1 阈值为什么比模型更重要实际部署里的 FAR/FRR很多课程设计demo跑通了就交差但答辩老师一定会问“陌生人能不能通过”。这就是阈值问题。系统有一个阈值T得分高于T判定为同一人低于T判定为陌生人或其他人。调高T会漏认熟人也就是错误拒绝率FRR上升调低T会让陌生人混进来也就是错误接受率FAR上升。两者此消彼长必须按场景权衡。在1:N辨认模式下阈值不只是“认不认”还影响“是否把库外的人强行归到库里”。比如训练了4个人来一个第5个人如果没有阈值判断系统会硬生生把他识别成4个人里得分最高的那个。加上阈值后最高分低于T就输出unknown。这一条做好演示效果立刻上一个档次。误识率和误拒率的具体含义FAR是错误接受的陌生人测试次数占总测试次数的比例FRR是错误拒绝的本人测试次数占总测试次数的比例。把每个阈值下的FAR和FRR画成两条曲线交点就是等错误率EER。4.2 用 EER 评估系统等错误率与代码实现EER是声纹识别最常用的单一指标理论上越低代表系统区分能力越强。计算方式是把测试集中所有“同人比对”的得分归为正样本所有“不同人比对”的得分归为负样本再遍历所有可能的阈值找到FAR等于FRR的那个点。import numpy as np def calc_eer(pos_scores, neg_scores): # 正样本标签为1负样本标签为0 labels np.concatenate([ np.ones(len(pos_scores)), np.zeros(len(neg_scores)) ]) scores np.concatenate([pos_scores, neg_scores]) # 遍历每个阈值统计FAR和FRR thresholds np.sort(scores) far_list, frr_list [], [] for th in thresholds: pred (scores th).astype(int) far np.sum((pred 1) (labels 0)) / max(len(neg_scores), 1) frr np.sum((pred 0) (labels 1)) / max(len(pos_scores), 1) far_list.append(far) frr_list.append(frr) far_arr np.array(far_list) frr_arr np.array(frr_list) idx np.argmin(np.abs(far_arr - frr_arr)) eer (far_arr[idx] frr_arr[idx]) / 2.0 return eer, thresholds[idx]这是课程设计里最直观的EER实现。真实系统会用scipy的插值函数求交点但数据量小的时候两者差异在1%以内。用的时候注意正负样本必须来自没有参与训练的录音否则EER虚低答辩追问时很难解释。跑出来多少算正常安静环境下4个说话人、每人20秒训练数据、GMM方案EER一般在8%到15%之间。超过15%说明特征或模型有问题低于5%则要检查测试集是否和训练集高度重合。4.3 数据划分与训练集扩充自录数据怎么组织课程设计最常犯的评估错误是用训练录音直接测试。正确做法是每个说话人准备至少10段独立录音8段训练、2段测试。测试时做三组比对本人对本人构成正样本本人对库内其他人构成负样本库外人对库内所有人构成陌生人测试。正负样本配对的数量关系也会影响EER。正样本对只有说话人数乘测试段数负样本对有说话人数乘说话人数减一乘测试段数数量不平衡时需要对负样本降采样或打乱否则EER会被负样本主导。录音条数不足时可以做扩充同一段录音加一点高斯白噪声信噪比20dB左右用不同手机重录隔天再录一遍。扩充出来的数据要标清楚来源不要把同一句话的拆分片段当成独立样本。变速不变调的resample会轻微改变声纹特征不适合做扩充。5. 声纹识别避坑指南从 Windows 路径到静音检测的五个坑5.1 中文路径导致 librosa 读取失败或波形全零现象代码在Windows上跑数据集放在“D:\声纹识别\数据集”运行到librosa.load报错FileNotFoundError或者不报错但读出来的波形数组全零。原因librosa底层依赖soundfile和libsndfile这两个库在Windows上对中文路径的支持一直有问题传入Unicode路径会静默失败返回一个空波形而不是抛异常。解决把数据集整体放到英文路径下例如D:\project\speaker_recognition\dataset。代码内部用os.path.join拼接路径不要手写带中文的字符串。如果一定要支持中文路径可以用os.chdir先进到目标目录再写相对文件名这是绕过soundfile的常见workaround。5.2 所有测试样本被判成同一个人GMM 训练数据混叠现象模型训练完拿测试集跑识别所有结果都指向同一个说话人而且得分异常高。原因最常见的有两个。一是遍历目录时把所有人的训练数据全塞进了一个GMM把分类问题错当成了聚类问题二是某个说话人的录音条数远大于其他人训练帧数占比过大导致模型被大数据量说话人主导。解决先检查目录循环的缩进train_all里每个spk_dir都独立训练一个GMM绝对不能所有说话人共用一次fit。再打印每个说话人的总帧数如果某个说话人帧数是别人的两倍以上在训练前对每个说话人随机抽取等量帧保证模型公平。5.3 录音长度不一致导致特征维度对不上现象训练时正常测试时报错cannot reshape array of size或者训练代码里的特征矩阵维度随录音长度变化。原因源码包里可能用了固定帧数的设计比如统一取前200帧而测试录音不足200帧或者识别部分用了np.hstack把变长帧拼成了变长向量。解决改用变长帧输入。训练时把不同录音的帧vstack进GMM识别时用GMM.score对平均对数似然打分完全不需要固定长度。如果因为后续要接向量模型必须定长就做均匀采样补帧不足的重复尾帧超过的均匀截取。5.4 静音段拉高或拉低相似度VAD 到底要不要做现象模型在安静环境下测试准确率不错放到有环境音的录音上EER翻倍或者某人的录音开头有爆音这个人的模型对任何输入都给高分。原因MFCC把静音和爆音也建模进了特征分布。静音帧的MFCC系数接近常数会形成一个方差极小的高斯分量让这个模型的似然对不同输入都偏高等于给模型埋了一个“后门”。解决训练和识别前都做VAD。简单方案就是extract_mfcc里的librosa.effects.trim。要求高一点可以用webrtcvad库按30ms帧判断是否语音段。注意trim只能去掉首尾静音中间环境噪声的去除需要谱减法或人声分离课程设计做到trim这一层已经足够。5.5 pycharm 配置 python 环境后运行结果与命令行不一致现象同一个脚本在pycharm里跑准确率正常在命令行里跑找不到数据集或者pycharm里路径正常命令行里报模型文件不存在。原因pycharm的运行配置里Working directory默认是项目根目录命令行在别的目录执行时相对路径dataset就失效了。解决所有路径基于脚本位置计算而不是基于当前工作目录。from pathlib import Path BASE_DIR Path(__file__).parent DATA_DIR BASE_DIR / dataset MODEL_DIR BASE_DIR / models这样无论从哪里启动脚本路径都不会漂移。这是源码包里最值得检查的一行代码很多“换了机器就跑不了”的反馈都出在这个位置。6. 从 GMM 往 x-vector 走给课程设计留一条升级路6.1 x-vector 的思路与课程设计怎么过渡GMM的问题在于建模的是帧级分布丢掉了时序信息。x-vector的做法是用TDNN网络把整段变长语音映射成固定维度的说话人向量embedding再用PLDA或余弦相似度打分。课程设计里不需要自己训练TDNN直接用业界开源的中文预训练模型提取embedding即可。步骤是加载预训练模型对每段录音提取512维embedding注册时存每个说话人的平均embedding测试时算待测embedding和库向量的余弦相似度阈值和EER评估代码完全复用第4章的脚本。效果上每人训练语音量从几十秒上升到几分钟EER可以掉一半。验证方法很简单保持评估脚本不变只替换特征提取和打分逻辑在同一份测试集上对比GMM和x-vector的EER。这个对比实验是课程设计里很扎实的加分项前提是先保证第4章的EER代码是对的。6.2 给答辩准备的验证清单三个验证必须做。第一库外陌生人测试系统能输出unknown而不是硬归到库里。第二同一个人用两台不同设备录音系统能正确识别验证特征对信道差异的鲁棒性。第三EER数值写进论文并说明测试集怎么划分、正负样本怎么配对。这三个问题覆盖了声纹识别从特征、模型到评估的完整链路答好了基本不会被问倒。6.3 一个该养成的习惯每次训练完先把模型在训练集上的得分分布打印出来。如果同一个人的不同录音得分差超过2个单位说明录音条件波动太大VAD或特征提取有问题。我当年做课程设计时只用了GMM答辩被问“信道差异怎么办”答不上来后来补了x-vector对比实验才算过关。血泪经验是声纹识别项目评估脚本和阈值调参比模型本身更能决定分数先把EER跑出来再谈模型升级。希望帮到你。本文还有配套的精品资源点击获取