从MFCC到GMM:手把手教你用Python实现说话人识别(附完整代码)
说话人识别,通俗讲就是让机器通过声音判断“你是谁”。注意它和语音识别是两码事,语音识别是听清“你说了什么”,说话人识别是听出“谁在说”。这两个任务一个被搜索引擎、智能音箱带火了,一个则藏在银行风控、门禁打卡、公安调证这些场景背后。
这个项目的核心是MFCC和GMM这对经典组合。MFCC负责把声音波形变成机器能算的特征向量,GMM负责给每个说话人建一个“声纹档案”。整套流程用Python写下来并不复杂,不需要GPU,不需要深度学习框架,一台普通笔记本就能跑通。适合想入门语音方向的开发者、做语音方向毕设的学生,以及只想要一个本地离线声纹识别小工具的折腾党。
我会把这套方案的原理讲明白,再把完整代码贴出来,从环境准备、音频整理、特征提取、模型训练到识别评估一条龙说完。后面还会列出我实际调试时踩过的坑和排查思路。跟着走一遍,你也能搞出一个能用的说话人识别demo。
1. 项目能做什么,为什么选MFCC+GMM这套方案
1.1 先看整体流程长什么样
说话人识别按应用场景分两种:一种是“确认”,就是问“你是你说的那个人吗”,回答是和否;另一种是“辨认”,就是问“你是谁”,从一堆人里挑一个出来。这个项目实现的是“辨认”,但稍加改动,也能做确认。
整个系统分两个阶段:注册阶段和识别阶段。
注册阶段对每个说话人采集几十秒到几分钟的语音,提取MFCC特征,用这些特征训练一个GMM模型,模型文件存到磁盘上。识别阶段来了一个陌生音频,同样提取MFCC特征,然后依次和每个说话人的GMM算相似度,得分最高的那个就是识别结果。如果最高分低过某个阈值,就判定为“未知说话人”,这样也能处理不在库里的人。
这套流程在语音技术圈里非常成熟。我读研那会儿做声纹相关方向,实验室的老系统就是这个结构,只不过底层手段换过好几代。即便到现在,在很多对资源要求苛刻的嵌入式场景里,GMM这一套依然有人用。原因很简单:训练快,推理快,模型体积小,效果稳定可控。
1.2 为什么不是深度学习方案
很多同学一上来就问:为什么不直接上Python加上深度学习,用ResNet或者ECAPA-TDNN提取说话人向量?我的回答是:这个项目的目的不是刷排行榜,而是把说话人识别的完整链路吃透。
深度学习方案的优点大家都清楚,但它的缺点也很明显:需要大量训练数据,需要GPU,需要处理音频增强、数据清洗一堆事情。一个教室级别的小项目,比如给一个班级、一个小组做签到识别,数据量可能只有每人几十秒。这种量级扔给深度学习模型,过拟合基本上跑不掉。而GMM模型本质上是一个统计模型,对数据量的要求没那么苛刻,几十秒的语音已经能训练一个可用的模型,这在实际落地中非常香。
另外,GMM的可解释性很好。模型训练完能直接看到每个高斯分量的均值、方差,能理解说话人特征在特征空间里的分布形态。这在调试阶段太重要了。神经网络训出来的高维向量,说实话很多时候你根本不知道它学到了什么,出了错也只能靠玄学调参。GMM出了问题,你能顺着特征分布找原因。
所以在教育入门、小数据量场景、快速原型验证这三类需求下,MFCC加GMM依然是我首推的组合。它把语音信号处理和概率统计两件事串在一起,学完这套,后面再去看i-vector、x-vector那些进阶方案,理解起来会顺很多。
2. 动手前先准备环境、数据和工具
2.1 Python环境与依赖库安装,新手照抄
这个项目的依赖不多,核心就四个库:numpy负责数值计算,librosa负责音频读取和MFCC提取,scikit-learn提供GMM实现,soundfile作为音频后端。
我建议直接用Python 3.9或者3.10,这两个版本对librosa和scikit-learn的兼容性最稳定。Python 3.11以上在部分老版本librosa上有奇怪的报错,虽然新版本已经修复,但没必要给自己添麻烦。装好Python后,在终端执行一行命令搞定依赖:
pip install numpy librosa soundfile scikit-learn如果你在安装librosa时遇到编译报错,大概率是缺少音频解码相关的底层库。Windows用户直接装soundfile的预编译wheel就行,Linux用户如果报错,试一下:
sudo apt-get install libsndfile1这里补充一个建议:强烈推荐把pip源换成国内镜像,否则下载速度会让你怀疑人生。一次性改法是这样:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple开发工具我用的是VSCode加Python插件,轻量顺手。如果你习惯PyCharm,也完全没问题。关键是把Python解释器路径选对,不然在终端里能执行的import librosa,进了编辑器就报ModuleNotFoundError。这个坑特别多人踩,原因就是VSCode左下角选的解释器和终端里which python指向的不是同一个环境。
2.2 音频数据怎么准备
模型再牛,音频数据烂也白搭。我见过不少同学兴致勃勃跑代码,结果识别率一塌糊涂,最后发现录音格式五花八门,有48kHz的、有8kHz电话音的、还有双声道立体声的。这里我直接给出我实测稳定的一套标准:
- 采样率:16000Hz。这是语音处理的通用标准,既能保留说话人信息,计算量也可控
- 通道数:单声道
- 格式:WAV,不要用压缩格式
- 每人录音条数:注册阶段至少3条,每条10秒以上;如果能录20秒以上更好
- 内容:不要只录数字串,最好读一段连贯的文字,包含元音、辅音、语调起伏
数据目录建议按下面的结构组织:
data/ ├── enroll/ │ ├── spk_001/ │ │ ├── 1.wav │ │ ├── 2.wav │ │ └── 3.wav │ ├── spk_002/ │ │ └── ... └── test/ ├── spk_001_test1.wav ├── spk_002_test1.wav注册阶段每个人的语音放在独立文件夹里,文件夹名就是说话人ID。测试阶段的音频放在test目录,文件名以“说话人ID_任意后缀.wav”命名,方便后面写评估脚本时提取标签。用手机录音的话,先用格式工厂或者ffmpeg转成16000Hz单声道WAV,再丢进对应目录。
3. MFCC特征提取:把声音变成机器能算的向量
3.1 MFCC原理极简版
MFCC的全称是Mel频率倒谱系数,是语音识别和说话人识别里最经典的特征。它的核心思想是模仿人耳对频率的感知特性——人耳对不同频率的敏感度不是线性的,低频分辨能力强,高频分辨能力弱。所以MFCC先把频谱映射到Mel刻度上,再经过一系列处理,得到一组紧凑的系数。
整个提取流程大致是五步:预加重、分帧加窗、快速傅里叶变换、Mel滤波器组滤波、对数运算加离散余弦变换。预加重是为了提升高频分量,因为语音在高频段的能量衰减快。分帧是因为语音信号是时变的,但在短短25毫秒内可以看成是平稳的,所以每次取25毫秒的窗口,隔10毫秒滑一次,得到一帧一帧的信号。每一帧做FFT得到频谱,再和Mel滤波器组做内积,得到每个Mel频带上的能量,取对数后做DCT,最终得到一组系数。
基础MFCC一般取13维,第0维是能量项。但在说话人识别任务里,我通常取20维,再叠加一阶差分和二阶差分,拼接成60维特征向量。差分特征能体现语音的动态变化,也就是音调的升降、音色的过渡,这些对于区分说话人非常关键。
生活化类比一下:MFCC系数像是给声音拍了一张低分辨率的“长相照片”,基础系数是五官轮廓,差分系数是表情变化。如果只看一张静态照片,认人容易出错;加上动态变化信息,准确率直接上一个台阶。
3.2 用librosa提取MFCC的代码
特征提取模块我单独放在features.py文件里,代码如下:
import numpy as np import librosa SR = 16000 # 采样率 N_MFCC = 20 # MFCC系数维度 FRAME_LEN = 0.025 # 帧长25ms FRAME_HOP = 0.010 # 帧移10ms def extract_mfcc(path): y, _ = librosa.load(path, sr=SR, mono=True) # 提取静态MFCC mfcc = librosa.feature.mfcc( y=y, sr=SR, n_mfcc=N_MFCC, n_fft=int(SR * FRAME_LEN), hop_length=int(SR * FRAME_HOP) ) # 一阶差分、二阶差分 delta1 = librosa.feature.delta(mfcc, order=1) delta2 = librosa.feature.delta(mfcc, order=2) # 拼接成60维特征 feat = np.concatenate([mfcc, delta1, delta2], axis=0) # librosa返回的是(特征维度, 帧数),转置成(帧数, 特征维度)方便后续建模 return feat.T这段代码里有几个细节值得说。librosa.load在加载音频时会自动重采样到指定的sr,所以就算输入是44.1kHz的音频,只要这一步传了sr=16000,后面就统一了。mono=True会把立体声混成单声道,避免双声道带来的维度混乱。n_fft和hop_length分别对应FFT窗口大小和窗口移动步长,我用的是25毫秒加10毫秒的标准配置,在16kHz采样率下就是400点和160点。
特征提取完成后的形状是“帧数乘以60”。你可能会问:一帧是25毫秒,10秒的音频大概有1000帧,这意味着每段音频变成了一个1000行60列的矩阵。直接用1000个样本点去拟合一堆高斯分布,数据量是够的。
3.3 特征参数选择和避坑
MFCC参数的选择没有绝对标准,但我在实践中有几个经验值供参考。
一是n_mfcc选多少。13维是语音识别的老传统,但说话人识别建议20到24维。原因在于说话人身份信息在Mel频带的能量包络里分布得比较分散,维度太少会丢掉细节,太多又会引入噪声。我试过从13维一路加到40维,在实际数据集上20到24维是个甜点区间,超过30维后准确率不升反降,而且训练时间变长。
二是要不要做倒谱均值归一化。我建议做。librosa.feature.mfcc得到的结果里,每个维度的均值反映了录音通道的特性。做CMN只是把每维减掉均值,能有效消除信道和麦克风差异。之前的实测里,同一批音频在换了一个麦克风后准确率掉了十几个点,加上CMN后只掉了两三个点。代码就一行:
mfcc = mfcc - np.mean(mfcc, axis=1, keepdims=True)三是一个很容易被忽略的坑:静音帧。一段录音的开头结尾往往有静音,这些帧的MFCC特征几乎一样,对说话人建模没有贡献,反而会把模型往“静音”上带偏。我在项目里做了一个简单的能量端点检测,只在能量大于阈值的帧上保留特征。具体做法是计算每一帧的短时能量,低于平均能量一半的帧直接丢弃。效果立竿见影,注册和识别都稳定了很多。
4. GMM建模:每个人一套高斯混合模型
4.1 高斯混合模型怎么理解
GMM全称是高斯混合模型,说白了就是把一堆数据点看作是若干个高斯分布混合生成的。每个高斯分布有自己的均值、方差和权重,把K个高斯分布按权重叠加起来,就能拟合出非常复杂的概率分布形态。
在说话人识别里,GMM做的事情是:学习某个特定说话人的特征点在60维空间里的分布规律。同一个人的发音虽然每次都不一样,但音色、发音习惯这些生理和习惯特征决定了他们的MFCC特征会聚集在某些区域。这些人可能形成几个簇——比如发元音时是一类特征,发辅音时是另一类特征。GMM的每个高斯分量负责描述一个簇,K个分量合起来就构成这个人的“声纹档案”。
训练GMM用的是EM算法。这个算法的思路是先给每个高斯分量随便初始化一组参数,然后重复迭代两步:E步计算每个数据点属于每个分量的概率,M步根据这些概率重新估计每个分量的均值、方差和权重。迭代几十轮后,这些参数会收敛到一个局部最优解,模型就训练完成了。
我找一个直观的比喻:GMM训练就像在没有标签的情况下给一堆照片里的每个人找到了几个代表姿势。A说话人的声纹档案可能是“站姿”“坐姿”“走路姿势”三个高斯分量,B说话人也是三个分量,但因为两个人习惯动作不同,每个分量的具体形态也不同。识别的时候来了一个新照片,分别用两个人的档案去解释它,看谁解释得更合理,就判定是谁。
4.2 GMM的训练与保存代码
训练模块我放在train.py里,代码不复杂,核心就几行。sklearn.mixture.GaussianMixture帮我们封装了全部EM算法细节,这是很多入门项目用它的原因。
import os import glob import pickle import numpy as np from sklearn.mixture import GaussianMixture from features import extract_mfcc N_COMPONENTS = 16 # 高斯分量个数 MODEL_DIR = "models" RANDOM_SEED = 42 def train_one_speaker(wav_paths): frame_list = [] for path in wav_paths: feats = extract_mfcc(path) frame_list.append(feats) all_feats = np.vstack(frame_list) gmm = GaussianMixture( n_components=N_COMPONENTS, covariance_type="diag", max_iter=200, random_state=RANDOM_SEED, reg_covar=1e-4 ) gmm.fit(all_feats) return gmm, all_feats.shape[0] def main(): os.makedirs(MODEL_DIR, exist_ok=True) enroll_dir = "data/enroll" for spk_id in sorted(os.listdir(enroll_dir)): spk_dir = os.path.join(enroll_dir, spk_id) if not os.path.isdir(spk_dir): continue wav_paths = glob.glob(os.path.join(spk_dir, "*.wav")) if not wav_paths: continue gmm, n_frames = train_one_speaker(wav_paths) model_path = os.path.join(MODEL_DIR, f"{spk_id}.gmm") with open(model_path, "wb") as f: pickle.dump(gmm, f) print(f"训练完成: {spk_id}, 音频数: {len(wav_paths)}, 特征总帧数: {n_frames}") if __name__ == "__main__": main()这里面几个参数的用意值得展开。
covariance_type="diag"我推荐无脑用。对角协方差矩阵假设60维特征之间独立,计算量小,训练稳定,还特别省内存。full全协方差能建模维度之间的相关性,但在数据量不足时极易过拟合,训练时间也是几十倍往上涨。对小项目来说,diag是性价比之王。
n_components是高斯分量的个数。16是一个平衡值,数据量只有几十秒时,8到16个都能工作;数据量充足到每人几分钟,可以升到32甚至64。我的经验法则是“宁少勿多”,分量太多容易让模型去拟合噪声和发音细节的随机波动,泛化能力会变差。
reg_covar=1e-4是协方差矩阵的正则项,防止训练数据中某些维度方差为0导致计算崩溃。这个参数在数据量少的时候非常关键,不加有时候会在EM迭代时报数值错误。老版本sklearn里它叫min_covar,如果你用的是旧教程代码,要注意这个改名。
训练好的模型用pickle序列化保存到models目录。每个说话人对应一个.gmm文件,文件本身很小,我实测16分量的GMM大概只有几十KB,100个说话人的模型库加起来也就几MB,部署起来非常轻量。
4.3 训练数据量和分量数怎么配合
这里我想把数据量和分量数的关系说透。高斯混合模型的每个分量至少要若干个特征帧才能稳定估计均值和方差。一段10秒的音频,去掉静音后大约还有800到1000帧,16个分量平均每个分量分到50到60帧,这个数据量刚好能让参数估计稳定。
如果数据量不足,比如每个人只有3秒录音,那我建议把分量数降到4或8,宁可用粗一点的模型也别硬上大模型。反过来,如果每人录音到了5分钟以上,分量数可以适当上调到32,但超过64以后收益就很小了。我还做过一个对比实验:同样16个分量,每人10秒的数据,识别准确率大约是82%;数据量增加到每人30秒,准确率直接跳到94%。这说明在GMM方案里,数据量的作用比调参大得多。
还有一个细节:训练说话人模型时不要只拿一条语音里的连续帧,最好把同一个人的多条录音放在一起训练。我见过有人把一条30秒的录音当成训练集,识别时用的是另一天录的音频,结果准确率掉得很厉害。原因是同一个人在不同时间的发音状态、背景噪声、录音距离都不同,只取一段音频训练会让模型过度拟合当天的声学环境。训练时尽量混合多条不同时间录的音频,泛化能力会好很多。
5. 完整代码实现:注册、识别、评估一条龙
5.1 注册模块与识别模块
注册模块上面已经写了,就是遍历data/enroll下的每个说话人文件夹,训练并保存GMM。这里重点说识别模块。predict.py的代码结构是这样的:
import os import pickle import numpy as np from features import extract_mfcc THRESHOLD = -30.0 # 这个阈值需要实际测量调整 def load_models(model_dir="models"): models = {} for name in os.listdir(model_dir): if name.endswith(".gmm"): spk_id = name[:-4] with open(os.path.join(model_dir, name), "rb") as f: models[spk_id] = pickle.load(f) return models def compute_score(gmm, feats): # score_samples 给出每一帧的对数似然值 log_likelihood = gmm.score_samples(feats) # 重点:取平均而不是求和 return float(np.mean(log_likelihood)) def identify(audio_path, models): feats = extract_mfcc(audio_path) if feats.shape[0] < 5: return "unknown", {} scores = {} for spk_id, gmm in models.items(): scores[spk_id] = compute_score(gmm, feats) best_spk = max(scores, key=scores.get) best_score = scores[best_spk] if best_score < THRESHOLD: return "unknown", scores return best_spk, scorescompute_score函数里有一个特别值得注意的细节:我取的是平均对数似然而不是总和对数似然。score_samples返回的是每个特征帧的对数似然,同一段10秒音频大约有1000帧,20秒音频有2000帧。如果直接求和,长音频的得分天然比短音频高,这会给识别结果带来系统性偏差。取均值后,分数就和音频时长基本无关了。这是我在实际使用中栽过跟头才总结出来的:有一次测试集里有两段不同时长的同一个人音频,没归一化前,识别结果几乎总是偏向时长更长的那条。
另外,如果测试音频太短,比如只有0.2秒的录音,提取出的特征帧可能只有几十帧甚至更少,这种样本的分数波动非常大,直接判为“unknown”更稳妥。
5.2 完整demo:拿到一条音频直接出结果
我把注册和识别串成一个完整的demo脚本,方便你直接复现整个流程。
# demo.py import os import sys from train import main as train_main from predict import load_models, identify if __name__ == "__main__": # 第一步:训练注册阶段的模型 if not os.path.exists("models"): train_main() # 第二步:加载所有说话人模型 models = load_models() # 第三步:识别一条测试音频 test_wav = sys.argv[1] if len(sys.argv) > 1 else "data/test/spk_001_test1.wav" result, scores = identify(test_wav, models) print(f"识别结果: {result}") print("各说话人得分:") for spk, score in sorted(scores.items(), key=lambda x: x[1], reverse=True): print(f" {spk}: {score:.3f}")运行方式:
python demo.py data/test/spk_001_test1.wav输出的结果里能看到每个说话人的分数排名。正常情况下,目标说话人的分数应该明显高出一截。如果排名第一和第二的分数挨得非常近,说明模型对这两个人区分度不够,后面调优要往数据量和特征上下功夫。
5.3 在自建数据集上评估整体准确率
识别模块跑通之后,你肯定想知道自己的系统到底多准。我写了一个简单的评估脚本,统计所有测试音频的识别准确率。评估之前,我建议每个人至少准备10条测试音频,覆盖不同时间、不同环境,这样得出的准确率才有参考价值。
import os import glob from predict import load_models, identify def evaluate(): models = load_models() test_dir = "data/test" wav_files = glob.glob(os.path.join(test_dir, "*.wav")) total = 0 correct = 0 unknown = 0 for wav_path in wav_files: # 文件名格式: spk_001_test1.wav filename = os.path.basename(wav_path) true_label = "_".join(filename.split("_")[:2]) pred_label, _ = identify(wav_path, models) total += 1 if pred_label == true_label: correct += 1 elif pred_label == "unknown": unknown += 1 else: print(f"识别错误: {wav_path}, 真实: {true_label}, 预测: {pred_label}") acc = correct / total if total > 0 else 0 print(f"测试音频总数: {total}") print(f"准确率: {acc:.2%} ({correct}/{total})") print(f"未识别(unknown): {unknown}") if __name__ == "__main__": evaluate()运行这个脚本,你能看到每个识别错误的案例,这对定位问题非常关键。如果错误集中发生在某两个说话人之间,说明这两人的声纹特征太接近,可以考虑增加他们的训练数据,或者增加GMM的分量数来增强区分度。如果错误普遍存在且和具体的人无关,那问题大概率出在特征提取环节或者环境噪声上。
5.4 阈值选择和“未知说话人”处理
阈值用于处理库里没有的说话人。取值太高会把自家人都拒之门外,取值太低又会让陌生人混进来。我项目里最初拍脑袋设了一个-30.0,结果发现测试集上三分之一的自家人被判成了unknown,教训惨痛。
正确的做法是实际测量。评估脚本跑完后,把每个测试样本的真实得分打印出来,分别统计两类分数的分布:一类是“匹配对”,即同一个人注册和测试的得分;另一类是“不匹配对”,即不同说话人之间的得分。取两类分布交界处的值作为阈值。两次实测下来,我的项目里匹配对的分数普遍在-20以上,不匹配对的分数普遍在-35以下,所以-30作为分界线是合理的,但换一批数据,这个值大概率要重新调。
如果你追求更严谨的阈值设定,可以用等错误率的方法:不断试不同阈值,找到令“误拒绝率”和“误接受率”相等的点。这一步展开做会额外花时间,但效果是最稳的。简而言之,阈值不是模型的一部分,而是部署策略的一部分,一定要用数据说话,别靠猜。
6. 常见问题与排查技巧实录
6.1 音频格式和采样率引发的诡异错误
这个坑我在项目里撞了好几次,症状千奇百怪:有的报Error opening file,有的不报错但识别率只有50%,还有的特征矩阵维度变成(0, 60)直接崩溃。
根因就一个:音频文件不规范。librosa虽然能读取很多格式,但遇到损坏的WAV文件、畸形采样率、异常通道数时,轻则警告重则报错。
排查思路分三步。第一步看能不能正常加载,单独对一段音频调用extract_mfcc,打印特征矩阵的shape,如果是(0, 60)说明音频内容为空或者读取失败。第二步确认采样率,加载后用librosa.get_samplerate查看,统一转成16000Hz。第三步检查有没有混入非音频文件,比如data/enroll目录下不小心放了个图片或者隐藏文件,glob匹配到的路径就会被错误地当成音频来读。
我强烈建议在处理数据之前跑一遍批量清洗脚本,把目录下所有音频统一重采样成16000Hz、单声道、16bit的WAV格式,再进入训练流程。这一步看着麻烦,实际能省掉后面几小时的排查时间。
6.2 GMM训练不收敛、特征维度对不上
最常见的一个报错是ConvergenceWarning: Number of distinct clusters found is smaller than n_components。这个警告的意思是有效聚类数比设定的分量数少,也就是说部分高斯分量没有被数据填充,模型退化成了更少的分量在运行。出现这个警告时,模型通常还能用,但性能不稳定。
解决办法有几个方向。首选降低n_components,从16降到8,通常警告就消失了。其次检查训练数据量,如果每个人的音频总共只有几秒,特征帧数太少,强行分成16个簇本来就不合理。还有一个方向是检查数据预处理,如果有静音帧没有滤除干净,那些几乎一模一样的特征帧会集中在一个小区域里,挤压其他分量的空间。
特征维度对不上这个错误我也遇到过。发生的原因通常是训练时用了40维MFCC、识别时又换成20维,或者差分阶数设置不一致。解决办法就是固定一套参数。我建议把features.py里的参数当成全局约定,训练和识别都调用同一个extract_mfcc函数,不要自己复制粘贴改参数。
6.3 识别效果差?先检查这三个地方
如果你的系统搭建过程没有问题,但准确率就是上不去,我建议按下面的优先级排查。
第一,检查训练和测试音频的录音条件是否一致。如果训练音频是在安静房间用电脑麦克风录的,测试音频是手机在嘈杂环境录的,信道失配会让GMM完全不认人。这个问题的杀伤力最大,很多项目“识别率只有60%”的真相就是这个。解决办法是尽量让注册音频和实际使用场景一致,或者做CMN减弱信道影响。
第二,检查静音过滤是否有效。我做项目时曾经发现,某段测试音频识别错误,打印出每帧得分后发现,静音帧的得分和说话人模型匹配得奇高——因为所有人训练数据里都有相同的静音模式,静音帧拉高了错误说话人的总分。加了能量过滤之后,这个问题立刻消失。
第三,检查GMM分量数和数据量是否匹配。你可以在小幅调整n_components后观察准确率变化,如果完全没有变化,说明模型容量和你的数据规模不在一个量级上。如果从一个方向调整有提升、另一个方向下降,说明当前值已经接近甜点区。
还有一个容易被忽略的点:Python、numpy、librosa这些库的版本差异。不同版本间librosa的MFCC实现细节有细微差别,同一段音频在老版本和新版本上提取的特征不完全一样。项目里如果改过环境,老模型文件和新特征可能不匹配,识别率会突然下降。稳妥的做法是升级依赖后重新训练所有模型。
这套MFCC加GMM的方案,在数据量不大、算力受限的场景下依然能打。我后面还打算在这个框架上做两件事:一是用通用背景模型加最大后验概率自适应替代直接训练GMM,让识别系统支持持续注册新说话人时老模型不用推倒重来;二是把音频前端换成更健壮的语音活动检测,进一步压低室内噪声的影响。这两块再加上MFCC和GMM的底子,就足够搭出一个接近工业级的轻量声纹识别系统了。如果你按这篇博文把代码跑通,再自己动手调一轮参数,对说话人识别这条技术链路会有非常扎实的理解。