別:從特征提取到打分判決的完整項(xiàng)目解析)
簡(jiǎn)介一套基于GMM-UBM模型的聲紋識(shí)別Python實(shí)現(xiàn)源碼及文檔說(shuō)明面向語(yǔ)音信號(hào)處理、機(jī)器學(xué)習(xí)相關(guān)課程的高分大作業(yè)場(chǎng)景。資源基于TIMIT數(shù)據(jù)集完整覆蓋靜音消除、FBANK/MFCC/PNCC三種特征提取、UBM訓(xùn)練、說(shuō)話(huà)人模型訓(xùn)練與評(píng)分測(cè)試等關(guān)鍵環(huán)節(jié)適合需要快速?gòu)?fù)現(xiàn)聲紋識(shí)別流程的學(xué)生和開(kāi)發(fā)者參考。壓縮包共10個(gè)文件包含9個(gè)Python腳本和1個(gè)Markdown文檔腳本分別負(fù)責(zé)特征提取、模型訓(xùn)練、測(cè)試集與注冊(cè)集生成、打分評(píng)估等功能文檔說(shuō)明則給出清晰的數(shù)據(jù)準(zhǔn)備和運(yùn)行步驟便于對(duì)照源碼理解GMM-UBM的完整建模思路。已有224人學(xué)習(xí)/下載代碼體積雖小但模塊劃分明確可幫助讀者掌握聲紋識(shí)別原理并作為課程設(shè)計(jì)或畢業(yè)設(shè)計(jì)的可擴(kuò)展框架也方便針對(duì)不同特征或數(shù)據(jù)集進(jìn)行二次調(diào)參與改進(jìn)。1. GMM-UBM聲紋識(shí)別這份Python課程項(xiàng)目把全鏈路跑通了聲紋識(shí)別在課程大作業(yè)里最常見(jiàn)的翻車(chē)方式是直接搬一個(gè)深度學(xué)習(xí)分類(lèi)網(wǎng)絡(luò)訓(xùn)完拿測(cè)試集一跑準(zhǔn)確率還行就交差。這份基于GMM-UBM的Python實(shí)現(xiàn)走的是另一條路它不訓(xùn)練神經(jīng)網(wǎng)絡(luò)而是用高斯混合模型對(duì)每個(gè)說(shuō)話(huà)人的聲學(xué)特征分布建模用通用背景模型UBM做全局兜底再通過(guò)最大后驗(yàn)概率自適應(yīng)得到每個(gè)說(shuō)話(huà)人自己的模型。項(xiàng)目把FBANK、MFCC、PNCC三種特征提取前端全部實(shí)現(xiàn)配套了靜音消除、數(shù)據(jù)列表生成、UBM訓(xùn)練、說(shuō)話(huà)人注冊(cè)、打分判決的完整腳本和文檔說(shuō)明。適合正在做說(shuō)話(huà)人識(shí)別課程設(shè)計(jì)、需要一條能逐步復(fù)現(xiàn)并到講臺(tái)答辯的完整路徑的同學(xué)也適合想把傳統(tǒng)聲紋識(shí)別“特征→模型→打分”全鏈路看清楚、不想把模型當(dāng)黑匣子的技術(shù)從業(yè)者。2. 項(xiàng)目結(jié)構(gòu)與時(shí)序十個(gè)腳本誰(shuí)先誰(shuí)后捋一遍才敢動(dòng)手這個(gè)項(xiàng)目最大的特點(diǎn)是不藏代碼整套流水線(xiàn)被拆成十個(gè)左右的Python腳本每個(gè)腳本只做一件事順著文檔說(shuō)明.md能一步步跑完。不像很多深度學(xué)習(xí)項(xiàng)目一個(gè)train.py跑到底中間發(fā)生了什么全靠猜。動(dòng)手跑之前先把腳本之間的依賴(lài)關(guān)系捋清楚后面翻車(chē)概率能少一半。2.1 工程文件拆解與運(yùn)行順序先把zip里的腳本挨個(gè)看一遍文件清單如下腳本文件職責(zé)在流水線(xiàn)中的位置yu_pos.py靜音消除可選數(shù)據(jù)準(zhǔn)備最前段train_spk_model.py生成訓(xùn)練列表訓(xùn)練說(shuō)話(huà)人模型列表階段建模階段train_test_scp.py生成或調(diào)整訓(xùn)練/測(cè)試列表輔助工具val_enrollment_scp.py生成注冊(cè)列表與驗(yàn)證列表列表階段feature_ex_Fbank.py提取FBANK特征特征階段feature_extract_MFCC.py提取MFCC特征特征階段feature_extract_pncc.py提取PNCC特征特征階段train_UBM.py訓(xùn)練通用背景模型建模階段eval_score.py打分與判決評(píng)估階段按照項(xiàng)目文檔給出的流程完整執(zhí)行順序是這樣的。注意train_spk_model.py會(huì)出現(xiàn)兩次第一次負(fù)責(zé)生成列表第二次負(fù)責(zé)訓(xùn)練說(shuō)話(huà)人模型這是這類(lèi)課程腳本常見(jiàn)的功能分支設(shè)計(jì)# 0. 可選對(duì)數(shù)據(jù)集做靜音消除VAD去掉每段音頻前后的靜音 python yu_pos.py # 1. 第一次運(yùn)行 train_spk_model.py生成訓(xùn)練UBM用的 ubm_wav.scp 和測(cè)試用的 test.scp python train_spk_model.py # 2. 生成注冊(cè)列表 enrollment.scp 與驗(yàn)證列表 var.scp python val_enrollment_scp.py # 3. 分別提取三種特征這一步的輸出和前面生成的scp一一對(duì)應(yīng) python feature_ex_Fbank.py python feature_extract_MFCC.py python feature_extract_pncc.py # 4. 訓(xùn)練UBM運(yùn)行時(shí)用chooes()輸入 fbank / mfcc / pncc 選定特征類(lèi)型 python train_UBM.py # 5. 第二次運(yùn)行 train_spk_model.py用chooes()選定同一特征類(lèi)型生成說(shuō)話(huà)人模型 python train_spk_model.py這個(gè)順序本身就是傳統(tǒng)聲紋識(shí)別系統(tǒng)的標(biāo)準(zhǔn)流水線(xiàn)先有數(shù)據(jù)列表再提特征再訓(xùn)UBM再自適應(yīng)出說(shuō)話(huà)人模型最后才是打分。我第一次拿到這類(lèi)項(xiàng)目時(shí)會(huì)先按這個(gè)順序空跑一遍即使中途報(bào)錯(cuò)也不急著改先看它停在哪一步再回頭翻文檔說(shuō)明.md比從讀代碼開(kāi)始快得多。跑的時(shí)候每一步都要回頭確認(rèn)輸出存在scp文件是文本直接打開(kāi)看特征文件通常是二進(jìn)制矩陣看文件大小是否為0。這里有個(gè)習(xí)慣值得養(yǎng)成——把每個(gè)腳本的輸出路徑提前記下來(lái)比如fbank特征存在哪個(gè)目錄、mfcc特征又存在哪個(gè)目錄后面很多報(bào)錯(cuò)都源于“我以為它生成了其實(shí)沒(méi)有”。2.2 靜音消除yu_pos.py為什么是可選步驟yu_pos.py在流水線(xiàn)最前面但文檔說(shuō)它是可選的。原因在于TIMIT這類(lèi)數(shù)據(jù)集本身在安靜錄音室環(huán)境下采集句首句尾的靜音段很短對(duì)GMM建模的影響往往可以忽略。但如果未來(lái)?yè)Q自己錄的數(shù)據(jù)或者環(huán)境里有很多無(wú)語(yǔ)音片段靜音消除就很必要。常見(jiàn)做法是基于短時(shí)能量和過(guò)零率的雙門(mén)限VAD把音頻切成25毫秒左右的幀計(jì)算每幀的短時(shí)能量和過(guò)零率能量低于閾值且過(guò)零率接近靜音水平的幀標(biāo)記為靜音再用前后幾幀做平滑避免把清音和弱音也切掉。這個(gè)項(xiàng)目的yu_pos.py就是這類(lèi)思路的輕量實(shí)現(xiàn)# yu_pos.py 的核心邏輯課程項(xiàng)目的典型VAD實(shí)現(xiàn) def vad_frame(frame, energy_thresh, zcr_thresh): # 幀能量對(duì)幀內(nèi)采樣點(diǎn)求平方和取對(duì)數(shù)數(shù)值越大代表這一幀越“響” energy np.log(np.sum(frame ** 2) 1e-10) # 過(guò)零率采樣點(diǎn)符號(hào)發(fā)生變化的次數(shù)語(yǔ)音濁音段過(guò)零率低清音和靜音段高 zcr np.sum(np.abs(np.diff(np.sign(frame)))) / 2 # 雙門(mén)限能量和過(guò)零率同時(shí)超過(guò)設(shè)定范圍才判為靜音 if energy energy_thresh and zcr zcr_thresh: return True # 靜音幀 return False這段代碼里energy_thresh和zcr_thresh是經(jīng)驗(yàn)參數(shù)。對(duì)16kHz采樣、16bit的音頻能量閾值一般在0到10之間手動(dòng)調(diào)過(guò)零率閾值在10到20之間。調(diào)參的依據(jù)是看VAD輸出后保留的語(yǔ)音段能否完整覆蓋一句話(huà)里所有成音部分。閾值設(shè)太高韻母的弱收尾會(huì)被切成碎塊設(shè)太低靜音段殘留下來(lái)后面提取的特征里混入大量低能量幀UBM訓(xùn)練的收斂速度會(huì)變慢。實(shí)際跑的時(shí)候我一般會(huì)先對(duì)一段音頻做可視化把能量曲線(xiàn)畫(huà)出來(lái)再定閾值而不是一上來(lái)就套默認(rèn)值。2.3 scp列表制度訓(xùn)練、注冊(cè)、驗(yàn)證為什么必須分開(kāi)scp是Speech Corpus格式的列表文件每一行對(duì)應(yīng)一段音頻典型格式是“音頻路徑 標(biāo)簽”標(biāo)簽可以是說(shuō)話(huà)人ID或句子ID。這個(gè)項(xiàng)目里train_spk_model.py生成ubm_wav.scp和test.scpval_enrollment_scp.py生成enrollment.scp和var.scp四份列表各有各的用途列表文件用途內(nèi)容來(lái)源ubm_wav.scp訓(xùn)練UBM用覆蓋盡量多說(shuō)話(huà)人的語(yǔ)音訓(xùn)練集全體說(shuō)話(huà)人test.scp測(cè)試/評(píng)估用包含身份未知的待識(shí)別語(yǔ)音測(cè)試集說(shuō)話(huà)人enrollment.scp注冊(cè)用每個(gè)說(shuō)話(huà)人一段或多段語(yǔ)音用來(lái)建模型驗(yàn)證集說(shuō)話(huà)人var.scp驗(yàn)證用與注冊(cè)說(shuō)話(huà)人一一比對(duì)打分與enrollment配對(duì)把注冊(cè)和驗(yàn)證分開(kāi)是實(shí)驗(yàn)紀(jì)律里很重要的一條。如果同一個(gè)人的同一段錄音既用來(lái)注冊(cè)又用來(lái)驗(yàn)證打分結(jié)果會(huì)虛高等錯(cuò)誤率好看到不真實(shí)這在課程答辯時(shí)是最容易被問(wèn)倒的問(wèn)題之一。正確做法是enrollment.scp里每人取前幾段語(yǔ)音注冊(cè)模型var.scp里每人另取不同句子做驗(yàn)證確保注冊(cè)語(yǔ)音和驗(yàn)證語(yǔ)音沒(méi)有重疊。TIMIT數(shù)據(jù)集本身提供了訓(xùn)練集與測(cè)試集的說(shuō)話(huà)人劃分項(xiàng)目是按這個(gè)約定來(lái)生成列表的。如果你要換自己的數(shù)據(jù)集至少要自己保證訓(xùn)練UBM的說(shuō)話(huà)人、注冊(cè)的說(shuō)話(huà)人和驗(yàn)證的說(shuō)話(huà)人互不重疊否則UBM吸收了目標(biāo)說(shuō)話(huà)人的分布后面MAP自適應(yīng)就失去意義了。順帶一提t(yī)rain_test_scp.py在文件清單里是輔助工具通常用來(lái)做列表的拆分和重排比如把ubm_wav.scp按說(shuō)話(huà)人隨機(jī)打亂或者從test.scp里抽一部分做調(diào)試。它不是主流程必需腳本但如果數(shù)據(jù)集很大想先拿一小部分驗(yàn)證代碼能跑通用它比手動(dòng)改列表方便。3. 三種特征提取的取舍MFCC、FBANK與PNCC特征提取是把音頻變模型輸入的環(huán)節(jié)。這個(gè)項(xiàng)目一次實(shí)現(xiàn)了三個(gè)前端不是堆功能而是三種特征分別適合不同條件。搞清楚它們各自做了什么、在什么場(chǎng)景下選哪個(gè)是復(fù)用這份資源時(shí)最值得花時(shí)間的一步。3.1 MFCC提取流程與參數(shù)含義MFCC是語(yǔ)音識(shí)別里最經(jīng)典的聲學(xué)特征。feature_extract_MFCC.py做的事情可以拆成四步預(yù)加重、分幀加窗、Mel濾波器組、對(duì)數(shù)與DCT。預(yù)加重用一階高通濾波器把高頻分量抬起來(lái)彌補(bǔ)語(yǔ)音發(fā)聲時(shí)高頻能量天然衰減的問(wèn)題分幀加窗用25毫秒的漢明窗把連續(xù)信號(hào)切成幀幀間重疊50%保證特征序列在時(shí)間軸上是平滑過(guò)渡的。# feature_extract_MFCC.py 中MFCC提取的簡(jiǎn)化流程 def extract_mfcc(wav, sr16000): # 預(yù)加重y[n] x[n] - 0.97 * x[n-1]提升高頻分量 pre_emph np.append(wav[0], wav[1:] - 0.97 * wav[:-1]) # 分幀加窗25ms幀長(zhǎng)10ms幀移漢明窗 frame_len int(sr * 0.025) # 400個(gè)采樣點(diǎn) frame_shift int(sr * 0.010) # 160個(gè)采樣點(diǎn) frames framing(pre_emph, frame_len, frame_shift) frames * np.hamming(frame_len) # FFT后過(guò)Mel濾波器組取對(duì)數(shù) power_spec np.abs(np.fft.rfft(frames, n512)) ** 2 mel_energy np.dot(power_spec, mel_filterbank.T) log_energy np.log(mel_energy 1e-10) # DCT得到倒譜系數(shù)通常取前13維 mfcc scipy.fftpack.dct(log_energy, axis1)[:, :13] return mfcc這里幾個(gè)參數(shù)值得留意。預(yù)加重系數(shù)0.97是經(jīng)驗(yàn)值接近1但不等同于1目的是在提升高頻的同時(shí)不引入低頻失真。幀長(zhǎng)25毫秒、幀移10毫秒是語(yǔ)音特征提取的默認(rèn)配置因?yàn)檎Z(yǔ)音在20到30毫秒內(nèi)可近似看成平穩(wěn)信號(hào)。Mel濾波器組的個(gè)數(shù)在這個(gè)項(xiàng)目里一般設(shè)26或40MFCC維度取13維。如果做說(shuō)話(huà)人識(shí)別13維通常偏少常見(jiàn)做法是拼接13維靜態(tài)系數(shù)加13維一階差分加13維二階差分湊成39維或者直接用FBANK的40維特征訓(xùn)練GMM而不做DCT壓縮。這個(gè)項(xiàng)目后續(xù)訓(xùn)練UBM時(shí)會(huì)讀取mfcc特征文件你要確認(rèn)維度設(shè)置與train_UBM.py里配置的輸入維度一致不然EM迭代第一步就會(huì)因?yàn)榫S度對(duì)不上而報(bào)錯(cuò)。3.2 FBANK與MFCC的差別少一步DCT多一分相關(guān)性FBANK特征其實(shí)是MFCC去掉最后一步DCT之后的中間產(chǎn)物。feature_ex_Fbank.py保存的就是經(jīng)過(guò)Mel濾波器組和對(duì)數(shù)運(yùn)算之后的濾波器組能量沒(méi)有做倒譜變換。少了一步變換FBANK到底好在哪關(guān)鍵在DCT的作用是去相關(guān)和降維。MFCC通過(guò)DCT把高維的濾波器組能量壓縮成低維倒譜系數(shù)丟掉的是濾波器組輸出中彼此相關(guān)的部分。對(duì)傳統(tǒng)GMM來(lái)說(shuō)特征維數(shù)低、各維相關(guān)性低協(xié)方差矩陣更接近對(duì)角陣訓(xùn)練更穩(wěn)定。但FBANK保留了相鄰Mel頻帶之間的能量相關(guān)性配合GMM的協(xié)方差建模反而能捕捉到更多頻譜細(xì)節(jié)。在語(yǔ)音內(nèi)容識(shí)別任務(wù)里兩者差不多但在說(shuō)話(huà)人識(shí)別里頻譜細(xì)節(jié)正是區(qū)分不同人發(fā)聲習(xí)慣的關(guān)鍵。這個(gè)項(xiàng)目同時(shí)提供fbank和mfcc兩條路線(xiàn)的特征提取正確用法是訓(xùn)練UBM和訓(xùn)練說(shuō)話(huà)人模型時(shí)選擇同一個(gè)特征類(lèi)型。很多人第一次跑會(huì)踩這個(gè)坑訓(xùn)練UBM時(shí)選了mfcc訓(xùn)練說(shuō)話(huà)人模型時(shí)又選了fbank兩者特征維度都不同加載模型時(shí)要么報(bào)錯(cuò)要么默默訓(xùn)練出一個(gè)沒(méi)用的模型。實(shí)際體驗(yàn)下來(lái)FBANK配合GMM-UBM的識(shí)別率通常會(huì)略高于MFCC特別是在訓(xùn)練數(shù)據(jù)量充足的情況下。PNCC則主要在噪聲環(huán)境下優(yōu)勢(shì)明顯。三者的適用場(chǎng)景差異通常在干凈語(yǔ)音上MFCC和FBANK差距不大在有背景噪聲或信道失真的數(shù)據(jù)上PNCC的魯棒性會(huì)明顯體現(xiàn)出來(lái)。3.3 PNCC的魯棒性來(lái)源從頻譜增強(qiáng)到冪律壓縮PNCC的全稱(chēng)是Power-Normalized Cepstral Coefficients設(shè)計(jì)目標(biāo)就是對(duì)抗噪聲和混響。它和MFCC在結(jié)構(gòu)上相似都是先做頻譜分析再經(jīng)過(guò)濾波器組和壓縮最后做DCT。差別在中間的處理環(huán)節(jié)。MFCC的經(jīng)典流程里FFT功率譜直接經(jīng)過(guò)Mel濾波器組然后取對(duì)數(shù)對(duì)數(shù)壓縮是固定底數(shù)的非線(xiàn)性變換。PNCC的做法要復(fù)雜幾層先用帶通濾波器和中值濾波估計(jì)每個(gè)頻帶的噪聲基底從功率譜中減去噪聲成分得到增強(qiáng)后的頻譜然后做冪律非線(xiàn)性壓縮通常取1/15的冪次這個(gè)冪律是從人耳聽(tīng)覺(jué)的響度感知模型推出來(lái)的比對(duì)數(shù)壓縮更接近聽(tīng)覺(jué)神經(jīng)對(duì)語(yǔ)音強(qiáng)度的映射再加一個(gè)時(shí)域掩蔽步驟用鄰近幀的能量做平滑抑制脈沖噪聲。最后再走DCT得到倒譜系數(shù)。# feature_extract_pncc.py 中PNCC的簡(jiǎn)化處理鏈 def extract_pncc(wav, sr16000): # 1. 短時(shí)傅里葉變換得到功率譜 power_spec stft_power(wav, sr) # 2. 中值濾波估計(jì)噪聲基底并從功率譜中扣除 noise_floor median_filter(power_spec, kernel_size5) enhanced np.maximum(power_spec - noise_floor, 0) # 3. 冪律非線(xiàn)性壓縮gamma 1/15 compressed np.power(enhanced, 1.0 / 15.0) # 4. 時(shí)域掩蔽對(duì)時(shí)間軸做指數(shù)平滑抑制脈沖突變 masked temporal_mask(compressed, coeff0.5) # 5. 過(guò)Mel濾波器組、取對(duì)數(shù)、DCT return dct(np.log(np.dot(masked, mel_filterbank.T)))[:, :13]參數(shù)上最值得關(guān)注的是中值濾波的kernel_size和時(shí)域掩蔽的平滑系數(shù)。kernel_size取5意味著每個(gè)頻帶取相鄰5幀的中值作為噪聲估計(jì)窗口太大反應(yīng)慢會(huì)誤把語(yǔ)音段能量當(dāng)成噪聲估計(jì)進(jìn)去窗口太小又擋不住持續(xù)性的背景噪聲。平滑系數(shù)0.5是保守默認(rèn)值如果面對(duì)的噪聲是短促的開(kāi)關(guān)聲、鍵盤(pán)聲可以把系數(shù)調(diào)到0.3以下讓掩蔽更敏感。PNCC在干凈語(yǔ)音上不一定比MFCC強(qiáng)但在有噪聲的測(cè)試條件下優(yōu)勢(shì)非常明顯。我拿到這個(gè)項(xiàng)目后實(shí)際體驗(yàn)是同樣的GMM-UBM管線(xiàn)把特征從MFCC換成PNCC在加了環(huán)境噪聲的測(cè)試集上等錯(cuò)誤率下降明顯。代價(jià)是提取速度慢不少中值濾波和時(shí)域掩蔽都是逐幀逐頻帶的操作特征提取耗時(shí)大約是MFCC的2到3倍。如果你的課程設(shè)計(jì)沒(méi)有噪聲魯棒性要求直接用FBANK性?xún)r(jià)比最高如果答辯題目里帶“魯棒說(shuō)話(huà)人識(shí)別”這類(lèi)字眼PNCC就是加分項(xiàng)。4. GMM-UBM訓(xùn)練與打分從EM迭代到似然比判決特征提取完之后進(jìn)入整個(gè)系統(tǒng)的核心訓(xùn)練UBM、自適應(yīng)說(shuō)話(huà)人模型、打分判決。這三個(gè)環(huán)節(jié)分別對(duì)應(yīng)train_UBM.py、train_spk_model.py和eval_score.py每一步都有值得展開(kāi)的參數(shù)細(xì)節(jié)。4.1 UBM訓(xùn)練train_UBM.py的EM迭代與分量數(shù)選擇UBM是通用背景模型本質(zhì)上是一個(gè)用大量非目標(biāo)說(shuō)話(huà)人的語(yǔ)音訓(xùn)練出來(lái)的大GMM。它扮演的是“普通人聲音分布”的基準(zhǔn)。train_UBM.py讀取ubm_wav.scp對(duì)應(yīng)的特征用期望最大化算法迭代擬合GMM參數(shù)。GMM的數(shù)學(xué)形式是多個(gè)高斯分量按權(quán)重疊加每個(gè)說(shuō)話(huà)人的聲學(xué)特征分布用M個(gè)高斯分量描述每個(gè)分量有自己的均值向量、協(xié)方差矩陣和權(quán)重。EM迭代的每一步分兩步E步計(jì)算每一幀特征落在每個(gè)高斯分量上的后驗(yàn)概率M步用這些后驗(yàn)概率作為軟標(biāo)簽重新估計(jì)權(quán)重、均值和協(xié)方差。迭代到對(duì)數(shù)似然的變化量小于某個(gè)閾值才停。# train_UBM.py 中GMM-UBM訓(xùn)練的EM思路 def gmm_em(features, n_components32, max_iter50): # 初始化用KMeans給特征聚類(lèi)用聚類(lèi)中心初始化各個(gè)高斯分量的均值 gmm initialize_with_kmeans(features, n_components) for i in range(max_iter): # E步計(jì)算每個(gè)樣本在每個(gè)高斯分量上的后驗(yàn)概率責(zé)任度 responsibilities gmm.predict_proba(features) # M步用責(zé)任度加權(quán)重估權(quán)重、均值、協(xié)方差 gmm.update_parameters(features, responsibilities) # 判斷收斂對(duì)數(shù)似然變化小于1e-4就停止 if abs(gmm.log_likelihood_change()) 1e-4: break return gmm高斯分量數(shù)n_components是最關(guān)鍵參數(shù)常見(jiàn)做法是32、64或128。分量越多對(duì)訓(xùn)練數(shù)據(jù)的擬合越精細(xì)但也更容易過(guò)擬合。UBM由于是大量說(shuō)話(huà)人數(shù)據(jù)訓(xùn)練出來(lái)的收斂性相對(duì)好可以用128個(gè)分量到了說(shuō)話(huà)人模型自適應(yīng)階段如果每個(gè)說(shuō)話(huà)人的注冊(cè)語(yǔ)音只有幾十秒用64個(gè)分量就夠了。一個(gè)經(jīng)驗(yàn)規(guī)則是每個(gè)高斯分量至少要有足夠幀數(shù)支撐均值估計(jì)注冊(cè)語(yǔ)音總幀數(shù)除以分量數(shù)不要小于幾十幀。如果注冊(cè)語(yǔ)音只有10秒左右對(duì)應(yīng)特征大約是1000幀分量數(shù)超過(guò)64就有點(diǎn)危險(xiǎn)。4.2 說(shuō)話(huà)人模型自適應(yīng)train_spk_model.py的MAP更新UBM訓(xùn)練好以后下一步為每個(gè)說(shuō)話(huà)人生成自己的模型。train_spk_model.py在這一階段做的是最大后驗(yàn)自適應(yīng)不是從零訓(xùn)練一個(gè)新GMM而是把UBM的參數(shù)作為先驗(yàn)用該說(shuō)話(huà)人的少量語(yǔ)音把UBM的均值拉向說(shuō)話(huà)人自己的特征分布。MAP的核心是自適應(yīng)“力度”的控制。每個(gè)高斯分量的均值更新時(shí)并不完全等于該說(shuō)話(huà)人語(yǔ)音在這個(gè)分量上的均值而是新舊信息的加權(quán)平均權(quán)重由該說(shuō)話(huà)人語(yǔ)音落在該分量上的幀數(shù)和預(yù)先設(shè)定的自適應(yīng)因子共同決定。幀數(shù)越多越信任新數(shù)據(jù)幀數(shù)越少越依賴(lài)UBM的先驗(yàn)。# train_spk_model.py 中MAP自適應(yīng)說(shuō)話(huà)人模型的示意代碼 def map_adapt(ubm, spk_features, relevance_factor16.0): # 計(jì)算說(shuō)話(huà)人特征在UBM各分量上的后驗(yàn)概率 gamma ubm.predict_proba(spk_features) for c in range(ubm.n_components): n_c gamma[:, c].sum() # 落在分量c上的等效幀數(shù) data_mean (gamma[:, c].dot(spk_features)) / max(n_c, 1e-10) # 權(quán)衡系數(shù)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 ubmrelevance_factor是MAP自適應(yīng)里最重要的參數(shù)常用范圍在8到32之間。它越大說(shuō)話(huà)人模型越接近UBM個(gè)性化程度越低它越小說(shuō)話(huà)人模型越依賴(lài)注冊(cè)語(yǔ)音對(duì)注冊(cè)語(yǔ)音的質(zhì)量也越敏感。如果注冊(cè)語(yǔ)音里有明顯的環(huán)境噪聲或誤切出來(lái)的靜音段relevance_factor又設(shè)得很小說(shuō)話(huà)人模型會(huì)被這些噪聲幀帶偏后面打分時(shí)對(duì)同一個(gè)人其他干凈語(yǔ)音的得分反而很低。課程項(xiàng)目里一般不會(huì)主動(dòng)讓你調(diào)這個(gè)參數(shù)但答辯老師很可能會(huì)問(wèn)“自適應(yīng)因子為什么這樣設(shè)”所以至少要能說(shuō)出relevance_factor對(duì)模型偏置和方差的影響。4.3 打分與閾值eval_score.py的似然比判決最后一個(gè)關(guān)鍵環(huán)節(jié)是打分。eval_score.py加載說(shuō)話(huà)人模型和UBM對(duì)每一段待驗(yàn)證語(yǔ)音計(jì)算兩個(gè)對(duì)數(shù)似然一個(gè)是這段語(yǔ)音被該說(shuō)話(huà)人模型生成的概率一個(gè)是它被UBM生成的概率。兩個(gè)對(duì)數(shù)似然的差就是對(duì)數(shù)似然比分?jǐn)?shù)。分?jǐn)?shù)越高說(shuō)明這段語(yǔ)音越不像普通人的聲音、越像目標(biāo)說(shuō)話(huà)人。這個(gè)設(shè)計(jì)是GMM-UBM體系最巧妙的地方。如果不做差、只看說(shuō)話(huà)人模型的絕對(duì)似然不同語(yǔ)音時(shí)長(zhǎng)會(huì)讓分?jǐn)?shù)標(biāo)準(zhǔn)不一致。而UBM作為“普通人基準(zhǔn)”可以把說(shuō)話(huà)人模型的分?jǐn)?shù)歸一化到“相對(duì)普通人的偏離程度”上分?jǐn)?shù)跨語(yǔ)音時(shí)長(zhǎng)的可比性更強(qiáng)。打分時(shí)通常還會(huì)對(duì)幀得分做平均用平均對(duì)數(shù)似然比而不是累加值進(jìn)一步消除時(shí)長(zhǎng)影響。訓(xùn)練完以后判斷“是不是本人”需要一個(gè)閾值。低于閾值判為冒認(rèn)者高于閾值判為目標(biāo)說(shuō)話(huà)人。項(xiàng)目里通常的做法是把var.scp的驗(yàn)證分?jǐn)?shù)畫(huà)成分布圖取兩個(gè)分布的交叉點(diǎn)或計(jì)算等錯(cuò)誤率閾值。等錯(cuò)誤率的意思是誤接受率和誤拒絕率相等時(shí)的點(diǎn)在課程答辯里是最直觀(guān)的評(píng)估指標(biāo)。如果文檔里沒(méi)有推薦閾值先用所有驗(yàn)證分?jǐn)?shù)的均值附近當(dāng)作初始閾值再根據(jù)期望偏安全還是偏方便來(lái)調(diào)整。到這里整個(gè)GMM-UBM系統(tǒng)的工作鏈閉環(huán)特征提取把音頻變成特征矩陣訓(xùn)練用EM訓(xùn)UBM、用MAP得到說(shuō)話(huà)人模型評(píng)估用似然比打分。5. 避坑與常見(jiàn)問(wèn)題跑這個(gè)項(xiàng)目最常翻車(chē)的五個(gè)地方5.1 scp文件路徑寫(xiě)錯(cuò)或帶空格特征讀取階段直接失敗現(xiàn)象運(yùn)行特征提取腳本時(shí)報(bào)文件找不到但文件明明就在scp里列出的路徑下。原因scp文件里寫(xiě)的是絕對(duì)路徑但數(shù)據(jù)集換過(guò)目錄或者Windows系統(tǒng)上路徑分隔符是反斜杠腳本里卻用正斜杠去拼接。此外目錄名里有空格的話(huà)按行split會(huì)把路徑拆成多段。解決先檢查scp文件里每一行的格式確認(rèn)路徑分隔符和實(shí)際環(huán)境一致。我一般會(huì)在提取特征前寫(xiě)一個(gè)簡(jiǎn)短的Python腳本遍歷scp里的每一條路徑用os.path.exists逐一驗(yàn)證把不存在的路徑打印出來(lái)。路徑換成絕對(duì)路徑最省心別依賴(lài)腳本里默認(rèn)的相對(duì)路徑拼接。5.2 靜音消除過(guò)猛特征矩陣變成空文件現(xiàn)象yu_pos.py跑完后某些音頻的幀數(shù)變成0對(duì)應(yīng)的特征文件也是空的訓(xùn)練UBM時(shí)加載進(jìn)來(lái)直接報(bào)維度錯(cuò)誤。原因雙門(mén)限VAD的閾值設(shè)得太高把清音段和弱音段全部判成靜音并切掉了。尤其是一些音量偏小的說(shuō)話(huà)人整個(gè)句子都可能被誤判為靜音。解決用VAD之前先做一次音量歸一化把每段音頻的峰值幅度統(tǒng)一縮放到一個(gè)固定范圍再設(shè)定能量閾值。閾值不要卡死留出余量寧可多留一點(diǎn)靜音幀也不要切掉語(yǔ)音。跑完VAD后檢查被切掉的部分占原音頻的比例如果超過(guò)10%基本可以斷定閾值偏高。5.3 特征類(lèi)型不統(tǒng)一訓(xùn)練出的說(shuō)話(huà)人模型是廢的現(xiàn)象訓(xùn)練UBM時(shí)選fbank訓(xùn)練說(shuō)話(huà)人模型時(shí)也選了fbank但打分階段eval_score.py報(bào)特征維度不匹配。原因eval_score.py里加載的是mfcc特征文件而模型是fbank特征訓(xùn)練的。這類(lèi)錯(cuò)誤出現(xiàn)在chooes()這種交互式選擇設(shè)計(jì)的項(xiàng)目里特別常見(jiàn)每次運(yùn)行都要手動(dòng)輸入?yún)?shù)一不留神就選岔了。還有一種更隱蔽的情況三個(gè)特征腳本保存的路徑或文件名不同打分腳本默認(rèn)去讀mfcc的特征目錄導(dǎo)致模型和特征對(duì)不上。解決把特征類(lèi)型當(dāng)成全局配置管理而不是每次交互輸入。我拿到這類(lèi)項(xiàng)目后的第一個(gè)改動(dòng)就是把train_UBM.py和train_spk_model.py里的chooes()替換成讀取一個(gè)config變量比如feature_type mfcc并統(tǒng)一設(shè)置輸出路徑的前綴。訓(xùn)練完看一眼生成的模型文件名帶的是哪種特征后綴再跑打分能省掉很多莫名其妙的報(bào)錯(cuò)。5.4 訓(xùn)練集測(cè)試集說(shuō)話(huà)人有重疊識(shí)別分?jǐn)?shù)虛高現(xiàn)象模型在var.scp上的分?jǐn)?shù)高得不正常等錯(cuò)誤率壓到百分之零點(diǎn)幾答辯時(shí)老師一追問(wèn)就露餡。原因數(shù)據(jù)列表生成時(shí)沒(méi)有嚴(yán)格執(zhí)行說(shuō)話(huà)人水平的劃分同一個(gè)人的語(yǔ)音既出現(xiàn)在ubm_wav.scp里用于訓(xùn)練UBM又出現(xiàn)在enrollment.scp里注冊(cè)了模型還在var.scp里當(dāng)了驗(yàn)證語(yǔ)音。三重重疊等于開(kāi)卷考試。解決重新生成列表確保UBM訓(xùn)練集、注冊(cè)說(shuō)話(huà)人集合、驗(yàn)證說(shuō)話(huà)人集合三者互不重疊。如果換了自己的數(shù)據(jù)集先按說(shuō)話(huà)人ID分組把說(shuō)話(huà)人整體劃分到集合里而不是按音頻文件隨機(jī)切。檢查方法很簡(jiǎn)單寫(xiě)一段腳本提取每個(gè)列表里的說(shuō)話(huà)人ID用集合的intersection看一下有沒(méi)有交集。5.5 打分階段沒(méi)有做時(shí)長(zhǎng)歸一化長(zhǎng)音頻天然占便宜現(xiàn)象同一個(gè)人的短語(yǔ)音分?jǐn)?shù)很低長(zhǎng)語(yǔ)音分?jǐn)?shù)高但閾值對(duì)長(zhǎng)短語(yǔ)音都很公平實(shí)際卻一直在誤判。原因eval_score.py如果直接累加每幀對(duì)數(shù)似然比而不是用幀數(shù)做平均長(zhǎng)音頻的分?jǐn)?shù)天然更高。GMM的逐幀似然都是負(fù)值累加后數(shù)值會(huì)隨幀數(shù)線(xiàn)性增大直接把長(zhǎng)短語(yǔ)音的分?jǐn)?shù)拉到不同量級(jí)。解決改成累加后除以總幀數(shù)用平均對(duì)數(shù)似然比作為分?jǐn)?shù)。如果不想改打分腳本也可以在測(cè)試時(shí)把音頻統(tǒng)一截?cái)嗟焦潭ㄩL(zhǎng)度再打分。這個(gè)問(wèn)題在課程項(xiàng)目里經(jīng)常被忽視因?yàn)榇蟛糠盅菔緮?shù)據(jù)時(shí)長(zhǎng)都差不多一旦自己錄幾段長(zhǎng)短不一的語(yǔ)音來(lái)測(cè)試立刻就會(huì)暴露。6. 驗(yàn)證與進(jìn)階讓識(shí)別結(jié)果可解釋的三個(gè)習(xí)慣把系統(tǒng)跑通以后重要的不是急著調(diào)參而是先建立一套能解釋實(shí)驗(yàn)結(jié)果的驗(yàn)證習(xí)慣。我從這個(gè)項(xiàng)目里總結(jié)出三個(gè)習(xí)慣在答辯和后續(xù)換數(shù)據(jù)集時(shí)反復(fù)用上了。6.1 用等錯(cuò)誤率描述性能而不是只報(bào)一個(gè)識(shí)別準(zhǔn)確率GMM-UBM的輸出是分?jǐn)?shù)所謂識(shí)別準(zhǔn)確率依賴(lài)閾值選擇換個(gè)閾值就成了另一個(gè)數(shù)。而等錯(cuò)誤率不依賴(lài)閾值它關(guān)注注冊(cè)說(shuō)話(huà)人自身分?jǐn)?shù)和冒認(rèn)者分?jǐn)?shù)兩個(gè)分布的交叉點(diǎn)是這套系統(tǒng)最穩(wěn)定的評(píng)價(jià)指標(biāo)。每次調(diào)完特征或模型參數(shù)把這個(gè)數(shù)記下來(lái)比“準(zhǔn)確率95%”更有說(shuō)服力。6.2 給特征做倒譜均值減MFCC和FBANK在特征提取完后把每一維特征在整個(gè)句子上減掉均值再歸一化能有效去除信道和麥克風(fēng)響應(yīng)帶來(lái)的加性偏移。我一般把CMS作為說(shuō)話(huà)人識(shí)別的默認(rèn)預(yù)處理它幾乎不增加計(jì)算量但對(duì)跨設(shè)備錄音的識(shí)別效果改善明顯。6.3 固定隨機(jī)種子讓每次訓(xùn)練可復(fù)現(xiàn)EM初始化和KMeans聚類(lèi)都帶有隨機(jī)性不固定種子的話(huà)連續(xù)兩次訓(xùn)練UBM得到的分量均值可能不同實(shí)驗(yàn)結(jié)果無(wú)法復(fù)現(xiàn)。在train_UBM.py開(kāi)頭加一行隨機(jī)種子固定是最容易做、也最容易加分的小改動(dòng)。從那以后我每次跑這類(lèi)項(xiàng)目都強(qiáng)制自己先做這三步固定隨機(jī)種子、確認(rèn)注冊(cè)語(yǔ)音和驗(yàn)證語(yǔ)音不重疊、用平均對(duì)數(shù)似然比打分。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取