ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

GMM-UBM声纹识别:从特征提取到打分判决的完整项目解析

GMM-UBM声纹识别:从特征提取到打分判决的完整项目解析

简介:一套基于GMM-UBM模型的声纹识别Python实现源码及文档说明,面向语音信号处理、机器学习相关课程的高分大作业场景。资源基于TIMIT数据集,完整覆盖静音消除、FBANK/MFCC/PNCC三种特征提取、UBM训练、说话人模型训练与评分测试等关键环节,适合需要快速复现声纹识别流程的学生和开发者参考。压缩包共10个文件,包含9个Python脚本和1个Markdown文档;脚本分别负责特征提取、模型训练、测试集与注册集生成、打分评估等功能,文档说明则给出清晰的数据准备和运行步骤,便于对照源码理解GMM-UBM的完整建模思路。已有224人学习/下载,代码体积虽小但模块划分明确,可帮助读者掌握声纹识别原理,并作为课程设计或毕业设计的可扩展框架,也方便针对不同特征或数据集进行二次调参与改进。

1. GMM-UBM声纹识别:这份Python课程项目把全链路跑通了

声纹识别在课程大作业里最常见的翻车方式,是直接搬一个深度学习分类网络,训完拿测试集一跑,准确率还行就交差。这份基于GMM-UBM的Python实现走的是另一条路:它不训练神经网络,而是用高斯混合模型对每个说话人的声学特征分布建模,用通用背景模型(UBM)做全局兜底,再通过最大后验概率自适应得到每个说话人自己的模型。项目把FBANK、MFCC、PNCC三种特征提取前端全部实现,配套了静音消除、数据列表生成、UBM训练、说话人注册、打分判决的完整脚本和文档说明。适合正在做说话人识别课程设计、需要一条能逐步复现并到讲台答辩的完整路径的同学,也适合想把传统声纹识别“特征→模型→打分”全链路看清楚、不想把模型当黑匣子的技术从业者。

2. 项目结构与时序:十个脚本谁先谁后,捋一遍才敢动手

这个项目最大的特点是不藏代码:整套流水线被拆成十个左右的Python脚本,每个脚本只做一件事,顺着文档说明.md能一步步跑完。不像很多深度学习项目一个train.py跑到底,中间发生了什么全靠猜。动手跑之前,先把脚本之间的依赖关系捋清楚,后面翻车概率能少一半。

2.1 工程文件拆解与运行顺序

先把zip里的脚本挨个看一遍,文件清单如下:

脚本文件职责在流水线中的位置
yu_pos.py静音消除(可选)数据准备最前段
train_spk_model.py生成训练列表;训练说话人模型列表阶段;建模阶段
train_test_scp.py生成或调整训练/测试列表辅助工具
val_enrollment_scp.py生成注册列表与验证列表列表阶段
feature_ex_Fbank.py提取FBANK特征特征阶段
feature_extract_MFCC.py提取MFCC特征特征阶段
feature_extract_pncc.py提取PNCC特征特征阶段
train_UBM.py训练通用背景模型建模阶段
eval_score.py打分与判决评估阶段

按照项目文档给出的流程,完整执行顺序是这样的。注意train_spk_model.py会出现两次,第一次负责生成列表,第二次负责训练说话人模型,这是这类课程脚本常见的功能分支设计:

# 0. 可选:对数据集做静音消除(VAD),去掉每段音频前后的静音 python yu_pos.py # 1. 第一次运行 train_spk_model.py:生成训练UBM用的 ubm_wav.scp 和测试用的 test.scp python train_spk_model.py # 2. 生成注册列表 enrollment.scp 与验证列表 var.scp python val_enrollment_scp.py # 3. 分别提取三种特征,这一步的输出和前面生成的scp一一对应 python feature_ex_Fbank.py python feature_extract_MFCC.py python feature_extract_pncc.py # 4. 训练UBM,运行时用chooes()输入 fbank / mfcc / pncc 选定特征类型 python train_UBM.py # 5. 第二次运行 train_spk_model.py:用chooes()选定同一特征类型,生成说话人模型 python train_spk_model.py

这个顺序本身就是传统声纹识别系统的标准流水线:先有数据列表,再提特征,再训UBM,再自适应出说话人模型,最后才是打分。我第一次拿到这类项目时会先按这个顺序空跑一遍,即使中途报错也不急着改,先看它停在哪一步,再回头翻文档说明.md,比从读代码开始快得多。

跑的时候每一步都要回头确认输出存在:scp文件是文本,直接打开看;特征文件通常是二进制矩阵,看文件大小是否为0。这里有个习惯值得养成——把每个脚本的输出路径提前记下来,比如fbank特征存在哪个目录、mfcc特征又存在哪个目录,后面很多报错都源于“我以为它生成了,其实没有”。

2.2 静音消除:yu_pos.py为什么是可选步骤

yu_pos.py在流水线最前面,但文档说它是可选的。原因在于TIMIT这类数据集本身在安静录音室环境下采集,句首句尾的静音段很短,对GMM建模的影响往往可以忽略。但如果未来换自己录的数据,或者环境里有很多无语音片段,静音消除就很必要。

常见做法是基于短时能量和过零率的双门限VAD:把音频切成25毫秒左右的帧,计算每帧的短时能量和过零率,能量低于阈值且过零率接近静音水平的帧标记为静音,再用前后几帧做平滑,避免把清音和弱音也切掉。这个项目的yu_pos.py就是这类思路的轻量实现:

# yu_pos.py 的核心逻辑(课程项目的典型VAD实现) def vad_frame(frame, energy_thresh, zcr_thresh): # 帧能量:对帧内采样点求平方和取对数,数值越大代表这一帧越“响” energy = np.log(np.sum(frame ** 2) + 1e-10) # 过零率:采样点符号发生变化的次数,语音浊音段过零率低,清音和静音段高 zcr = np.sum(np.abs(np.diff(np.sign(frame)))) / 2 # 双门限:能量和过零率同时超过设定范围才判为静音 if energy < energy_thresh and zcr < zcr_thresh: return True # 静音帧 return False

这段代码里energy_thresh和zcr_thresh是经验参数。对16kHz采样、16bit的音频,能量阈值一般在0到10之间手动调,过零率阈值在10到20之间。调参的依据是看VAD输出后保留的语音段能否完整覆盖一句话里所有成音部分。阈值设太高,韵母的弱收尾会被切成碎块;设太低,静音段残留下来,后面提取的特征里混入大量低能量帧,UBM训练的收敛速度会变慢。实际跑的时候,我一般会先对一段音频做可视化,把能量曲线画出来再定阈值,而不是一上来就套默认值。

2.3 scp列表制度:训练、注册、验证为什么必须分开

scp是Speech Corpus格式的列表文件,每一行对应一段音频,典型格式是“音频路径 标签”,标签可以是说话人ID或句子ID。这个项目里train_spk_model.py生成ubm_wav.scp和test.scp,val_enrollment_scp.py生成enrollment.scp和var.scp,四份列表各有各的用途:

列表文件用途内容来源
ubm_wav.scp训练UBM用,覆盖尽量多说话人的语音训练集全体说话人
test.scp测试/评估用,包含身份未知的待识别语音测试集说话人
enrollment.scp注册用,每个说话人一段或多段语音,用来建模型验证集说话人
var.scp验证用,与注册说话人一一比对打分与enrollment配对

把注册和验证分开是实验纪律里很重要的一条。如果同一个人的同一段录音既用来注册又用来验证,打分结果会虚高,等错误率好看到不真实,这在课程答辩时是最容易被问倒的问题之一。正确做法是enrollment.scp里每人取前几段语音注册模型,var.scp里每人另取不同句子做验证,确保注册语音和验证语音没有重叠。TIMIT数据集本身提供了训练集与测试集的说话人划分,项目是按这个约定来生成列表的。如果你要换自己的数据集,至少要自己保证训练UBM的说话人、注册的说话人和验证的说话人互不重叠,否则UBM吸收了目标说话人的分布,后面MAP自适应就失去意义了。

顺带一提,train_test_scp.py在文件清单里是辅助工具,通常用来做列表的拆分和重排,比如把ubm_wav.scp按说话人随机打乱,或者从test.scp里抽一部分做调试。它不是主流程必需脚本,但如果数据集很大,想先拿一小部分验证代码能跑通,用它比手动改列表方便。

3. 三种特征提取的取舍:MFCC、FBANK与PNCC

特征提取是把音频变模型输入的环节。这个项目一次实现了三个前端,不是堆功能,而是三种特征分别适合不同条件。搞清楚它们各自做了什么、在什么场景下选哪个,是复用这份资源时最值得花时间的一步。

3.1 MFCC提取流程与参数含义

MFCC是语音识别里最经典的声学特征。feature_extract_MFCC.py做的事情可以拆成四步:预加重、分帧加窗、Mel滤波器组、对数与DCT。预加重用一阶高通滤波器把高频分量抬起来,弥补语音发声时高频能量天然衰减的问题;分帧加窗用25毫秒的汉明窗把连续信号切成帧,帧间重叠50%,保证特征序列在时间轴上是平滑过渡的。

# feature_extract_MFCC.py 中MFCC提取的简化流程 def extract_mfcc(wav, sr=16000): # 预加重:y[n] = x[n] - 0.97 * x[n-1],提升高频分量 pre_emph = np.append(wav[0], wav[1:] - 0.97 * wav[:-1]) # 分帧加窗:25ms帧长,10ms帧移,汉明窗 frame_len = int(sr * 0.025) # 400个采样点 frame_shift = int(sr * 0.010) # 160个采样点 frames = framing(pre_emph, frame_len, frame_shift) frames *= np.hamming(frame_len) # FFT后过Mel滤波器组,取对数 power_spec = np.abs(np.fft.rfft(frames, n=512)) ** 2 mel_energy = np.dot(power_spec, mel_filterbank.T) log_energy = np.log(mel_energy + 1e-10) # DCT得到倒谱系数,通常取前13维 mfcc = scipy.fftpack.dct(log_energy, axis=1)[:, :13] return mfcc

这里几个参数值得留意。预加重系数0.97是经验值,接近1但不等同于1,目的是在提升高频的同时不引入低频失真。帧长25毫秒、帧移10毫秒是语音特征提取的默认配置,因为语音在20到30毫秒内可近似看成平稳信号。Mel滤波器组的个数在这个项目里一般设26或40,MFCC维度取13维。如果做说话人识别,13维通常偏少,常见做法是拼接13维静态系数加13维一阶差分加13维二阶差分,凑成39维,或者直接用FBANK的40维特征训练GMM而不做DCT压缩。这个项目后续训练UBM时会读取mfcc特征文件,你要确认维度设置与train_UBM.py里配置的输入维度一致,不然EM迭代第一步就会因为维度对不上而报错。

3.2 FBANK与MFCC的差别:少一步DCT,多一分相关性

FBANK特征其实是MFCC去掉最后一步DCT之后的中间产物。feature_ex_Fbank.py保存的就是经过Mel滤波器组和对数运算之后的滤波器组能量,没有做倒谱变换。少了一步变换,FBANK到底好在哪?

关键在DCT的作用是去相关和降维。MFCC通过DCT把高维的滤波器组能量压缩成低维倒谱系数,丢掉的是滤波器组输出中彼此相关的部分。对传统GMM来说,特征维数低、各维相关性低,协方差矩阵更接近对角阵,训练更稳定。但FBANK保留了相邻Mel频带之间的能量相关性,配合GMM的协方差建模,反而能捕捉到更多频谱细节。在语音内容识别任务里两者差不多,但在说话人识别里,频谱细节正是区分不同人发声习惯的关键。

这个项目同时提供fbank和mfcc两条路线的特征提取,正确用法是训练UBM和训练说话人模型时选择同一个特征类型。很多人第一次跑会踩这个坑:训练UBM时选了mfcc,训练说话人模型时又选了fbank,两者特征维度都不同,加载模型时要么报错,要么默默训练出一个没用的模型。

实际体验下来,FBANK配合GMM-UBM的识别率通常会略高于MFCC,特别是在训练数据量充足的情况下。PNCC则主要在噪声环境下优势明显。三者的适用场景差异,通常在干净语音上MFCC和FBANK差距不大,在有背景噪声或信道失真的数据上,PNCC的鲁棒性会明显体现出来。

3.3 PNCC的鲁棒性来源:从频谱增强到幂律压缩

PNCC的全称是Power-Normalized Cepstral Coefficients,设计目标就是对抗噪声和混响。它和MFCC在结构上相似:都是先做频谱分析,再经过滤波器组和压缩,最后做DCT。差别在中间的处理环节。

MFCC的经典流程里,FFT功率谱直接经过Mel滤波器组然后取对数,对数压缩是固定底数的非线性变换。PNCC的做法要复杂几层:先用带通滤波器和中值滤波估计每个频带的噪声基底,从功率谱中减去噪声成分,得到增强后的频谱;然后做幂律非线性压缩,通常取1/15的幂次,这个幂律是从人耳听觉的响度感知模型推出来的,比对数压缩更接近听觉神经对语音强度的映射;再加一个时域掩蔽步骤,用邻近帧的能量做平滑,抑制脉冲噪声。最后再走DCT得到倒谱系数。

# feature_extract_pncc.py 中PNCC的简化处理链 def extract_pncc(wav, sr=16000): # 1. 短时傅里叶变换得到功率谱 power_spec = stft_power(wav, sr) # 2. 中值滤波估计噪声基底并从功率谱中扣除 noise_floor = median_filter(power_spec, kernel_size=5) enhanced = np.maximum(power_spec - noise_floor, 0) # 3. 幂律非线性压缩:gamma = 1/15 compressed = np.power(enhanced, 1.0 / 15.0) # 4. 时域掩蔽:对时间轴做指数平滑,抑制脉冲突变 masked = temporal_mask(compressed, coeff=0.5) # 5. 过Mel滤波器组、取对数、DCT return dct(np.log(np.dot(masked, mel_filterbank.T)))[:, :13]

参数上最值得关注的是中值滤波的kernel_size和时域掩蔽的平滑系数。kernel_size取5意味着每个频带取相邻5帧的中值作为噪声估计,窗口太大反应慢,会误把语音段能量当成噪声估计进去;窗口太小又挡不住持续性的背景噪声。平滑系数0.5是保守默认值,如果面对的噪声是短促的开关声、键盘声,可以把系数调到0.3以下,让掩蔽更敏感。

PNCC在干净语音上不一定比MFCC强,但在有噪声的测试条件下优势非常明显。我拿到这个项目后实际体验是,同样的GMM-UBM管线,把特征从MFCC换成PNCC,在加了环境噪声的测试集上等错误率下降明显。代价是提取速度慢不少,中值滤波和时域掩蔽都是逐帧逐频带的操作,特征提取耗时大约是MFCC的2到3倍。如果你的课程设计没有噪声鲁棒性要求,直接用FBANK性价比最高;如果答辩题目里带“鲁棒说话人识别”这类字眼,PNCC就是加分项。

4. GMM-UBM训练与打分:从EM迭代到似然比判决

特征提取完之后,进入整个系统的核心:训练UBM、自适应说话人模型、打分判决。这三个环节分别对应train_UBM.py、train_spk_model.py和eval_score.py,每一步都有值得展开的参数细节。

4.1 UBM训练:train_UBM.py的EM迭代与分量数选择

UBM是通用背景模型,本质上是一个用大量非目标说话人的语音训练出来的大GMM。它扮演的是“普通人声音分布”的基准。train_UBM.py读取ubm_wav.scp对应的特征,用期望最大化算法迭代拟合GMM参数。

GMM的数学形式是多个高斯分量按权重叠加:每个说话人的声学特征分布用M个高斯分量描述,每个分量有自己的均值向量、协方差矩阵和权重。EM迭代的每一步分两步:E步计算每一帧特征落在每个高斯分量上的后验概率,M步用这些后验概率作为软标签,重新估计权重、均值和协方差。迭代到对数似然的变化量小于某个阈值才停。

# train_UBM.py 中GMM-UBM训练的EM思路 def gmm_em(features, n_components=32, max_iter=50): # 初始化:用KMeans给特征聚类,用聚类中心初始化各个高斯分量的均值 gmm = initialize_with_kmeans(features, n_components) for i in range(max_iter): # E步:计算每个样本在每个高斯分量上的后验概率(责任度) responsibilities = gmm.predict_proba(features) # M步:用责任度加权重估权重、均值、协方差 gmm.update_parameters(features, responsibilities) # 判断收敛:对数似然变化小于1e-4就停止 if abs(gmm.log_likelihood_change()) < 1e-4: break return gmm

高斯分量数n_components是最关键参数,常见做法是32、64或128。分量越多,对训练数据的拟合越精细,但也更容易过拟合。UBM由于是大量说话人数据训练出来的,收敛性相对好,可以用128个分量;到了说话人模型自适应阶段,如果每个说话人的注册语音只有几十秒,用64个分量就够了。一个经验规则是:每个高斯分量至少要有足够帧数支撑均值估计,注册语音总帧数除以分量数不要小于几十帧。如果注册语音只有10秒左右,对应特征大约是1000帧,分量数超过64就有点危险。

4.2 说话人模型自适应:train_spk_model.py的MAP更新

UBM训练好以后,下一步为每个说话人生成自己的模型。train_spk_model.py在这一阶段做的是最大后验自适应:不是从零训练一个新GMM,而是把UBM的参数作为先验,用该说话人的少量语音把UBM的均值拉向说话人自己的特征分布。

MAP的核心是自适应“力度”的控制。每个高斯分量的均值更新时,并不完全等于该说话人语音在这个分量上的均值,而是新旧信息的加权平均:权重由该说话人语音落在该分量上的帧数和预先设定的自适应因子共同决定。帧数越多,越信任新数据;帧数越少,越依赖UBM的先验。

# train_spk_model.py 中MAP自适应说话人模型的示意代码 def map_adapt(ubm, spk_features, relevance_factor=16.0): # 计算说话人特征在UBM各分量上的后验概率 gamma = ubm.predict_proba(spk_features) for c in range(ubm.n_components): n_c = gamma[:, c].sum() # 落在分量c上的等效帧数 data_mean = (gamma[:, c].dot(spk_features)) / max(n_c, 1e-10) # 权衡系数:alpha = n_c / (n_c + relevance_factor) alpha = n_c / (n_c + relevance_factor) ubm.mean_[c] = alpha * data_mean + (1 - alpha) * ubm.mean_[c] return ubm

relevance_factor是MAP自适应里最重要的参数,常用范围在8到32之间。它越大,说话人模型越接近UBM,个性化程度越低;它越小,说话人模型越依赖注册语音,对注册语音的质量也越敏感。如果注册语音里有明显的环境噪声或误切出来的静音段,relevance_factor又设得很小,说话人模型会被这些噪声帧带偏,后面打分时对同一个人其他干净语音的得分反而很低。课程项目里一般不会主动让你调这个参数,但答辩老师很可能会问“自适应因子为什么这样设”,所以至少要能说出relevance_factor对模型偏置和方差的影响。

4.3 打分与阈值:eval_score.py的似然比判决

最后一个关键环节是打分。eval_score.py加载说话人模型和UBM,对每一段待验证语音计算两个对数似然:一个是这段语音被该说话人模型生成的概率,一个是它被UBM生成的概率。两个对数似然的差就是对数似然比分数。分数越高,说明这段语音越不像普通人的声音、越像目标说话人。

这个设计是GMM-UBM体系最巧妙的地方。如果不做差、只看说话人模型的绝对似然,不同语音时长会让分数标准不一致。而UBM作为“普通人基准”,可以把说话人模型的分数归一化到“相对普通人的偏离程度”上,分数跨语音时长的可比性更强。打分时通常还会对帧得分做平均,用平均对数似然比而不是累加值,进一步消除时长影响。

训练完以后,判断“是不是本人”需要一个阈值。低于阈值判为冒认者,高于阈值判为目标说话人。项目里通常的做法是把var.scp的验证分数画成分布图,取两个分布的交叉点或计算等错误率阈值。等错误率的意思是误接受率和误拒绝率相等时的点,在课程答辩里是最直观的评估指标。如果文档里没有推荐阈值,先用所有验证分数的均值附近当作初始阈值,再根据期望偏安全还是偏方便来调整。

到这里,整个GMM-UBM系统的工作链闭环:特征提取把音频变成特征矩阵,训练用EM训UBM、用MAP得到说话人模型,评估用似然比打分。

5. 避坑与常见问题:跑这个项目最常翻车的五个地方

5.1 scp文件路径写错或带空格,特征读取阶段直接失败

现象:运行特征提取脚本时报文件找不到,但文件明明就在scp里列出的路径下。

原因:scp文件里写的是绝对路径,但数据集换过目录,或者Windows系统上路径分隔符是反斜杠,脚本里却用正斜杠去拼接。此外,目录名里有空格的话,按行split会把路径拆成多段。

解决:先检查scp文件里每一行的格式,确认路径分隔符和实际环境一致。我一般会在提取特征前写一个简短的Python脚本遍历scp里的每一条路径,用os.path.exists逐一验证,把不存在的路径打印出来。路径换成绝对路径最省心,别依赖脚本里默认的相对路径拼接。

5.2 静音消除过猛,特征矩阵变成空文件

现象:yu_pos.py跑完后,某些音频的帧数变成0,对应的特征文件也是空的,训练UBM时加载进来直接报维度错误。

原因:双门限VAD的阈值设得太高,把清音段和弱音段全部判成静音并切掉了。尤其是一些音量偏小的说话人,整个句子都可能被误判为静音。

解决:用VAD之前先做一次音量归一化,把每段音频的峰值幅度统一缩放到一个固定范围,再设定能量阈值。阈值不要卡死,留出余量,宁可多留一点静音帧也不要切掉语音。跑完VAD后检查被切掉的部分占原音频的比例,如果超过10%,基本可以断定阈值偏高。

5.3 特征类型不统一,训练出的说话人模型是废的

现象:训练UBM时选fbank,训练说话人模型时也选了fbank,但打分阶段eval_score.py报特征维度不匹配。

原因:eval_score.py里加载的是mfcc特征文件,而模型是fbank特征训练的。这类错误出现在chooes()这种交互式选择设计的项目里特别常见,每次运行都要手动输入参数,一不留神就选岔了。还有一种更隐蔽的情况:三个特征脚本保存的路径或文件名不同,打分脚本默认去读mfcc的特征目录,导致模型和特征对不上。

解决:把特征类型当成全局配置管理,而不是每次交互输入。我拿到这类项目后的第一个改动,就是把train_UBM.py和train_spk_model.py里的chooes()替换成读取一个config变量,比如feature_type = 'mfcc',并统一设置输出路径的前缀。训练完看一眼生成的模型文件名带的是哪种特征后缀,再跑打分,能省掉很多莫名其妙的报错。

5.4 训练集测试集说话人有重叠,识别分数虚高

现象:模型在var.scp上的分数高得不正常,等错误率压到百分之零点几,答辩时老师一追问就露馅。

原因:数据列表生成时没有严格执行说话人水平的划分,同一个人的语音既出现在ubm_wav.scp里用于训练UBM,又出现在enrollment.scp里注册了模型,还在var.scp里当了验证语音。三重重叠,等于开卷考试。

解决:重新生成列表,确保UBM训练集、注册说话人集合、验证说话人集合三者互不重叠。如果换了自己的数据集,先按说话人ID分组,把说话人整体划分到集合里,而不是按音频文件随机切。检查方法很简单:写一段脚本提取每个列表里的说话人ID,用集合的intersection看一下有没有交集。

5.5 打分阶段没有做时长归一化,长音频天然占便宜

现象:同一个人的短语音分数很低,长语音分数高,但阈值对长短语音都很公平,实际却一直在误判。

原因:eval_score.py如果直接累加每帧对数似然比,而不是用帧数做平均,长音频的分数天然更高。GMM的逐帧似然都是负值,累加后数值会随帧数线性增大,直接把长短语音的分数拉到不同量级。

解决:改成累加后除以总帧数,用平均对数似然比作为分数。如果不想改打分脚本,也可以在测试时把音频统一截断到固定长度再打分。这个问题在课程项目里经常被忽视,因为大部分演示数据时长都差不多,一旦自己录几段长短不一的语音来测试,立刻就会暴露。

6. 验证与进阶:让识别结果可解释的三个习惯

把系统跑通以后,重要的不是急着调参,而是先建立一套能解释实验结果的验证习惯。我从这个项目里总结出三个习惯,在答辩和后续换数据集时反复用上了。

6.1 用等错误率描述性能,而不是只报一个识别准确率

GMM-UBM的输出是分数,所谓识别准确率依赖阈值选择,换个阈值就成了另一个数。而等错误率不依赖阈值,它关注注册说话人自身分数和冒认者分数两个分布的交叉点,是这套系统最稳定的评价指标。每次调完特征或模型参数,把这个数记下来,比“准确率95%”更有说服力。

6.2 给特征做倒谱均值减

MFCC和FBANK在特征提取完后,把每一维特征在整个句子上减掉均值再归一化,能有效去除信道和麦克风响应带来的加性偏移。我一般把CMS作为说话人识别的默认预处理,它几乎不增加计算量,但对跨设备录音的识别效果改善明显。

6.3 固定随机种子,让每次训练可复现

EM初始化和KMeans聚类都带有随机性,不固定种子的话,连续两次训练UBM得到的分量均值可能不同,实验结果无法复现。在train_UBM.py开头加一行随机种子固定,是最容易做、也最容易加分的小改动。从那以后,我每次跑这类项目都强制自己先做这三步:固定随机种子、确认注册语音和验证语音不重叠、用平均对数似然比打分。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进