前記:先說下模型訓練的背景,正如一般的機器學習的模型訓練那樣,首先會用較大的資料集訓練生成一個較大的模型,然后在這個模型基礎上進行調優,也就是finetune,
我這邊基于kaldi的模型訓練也是采用這個的思路,Kaldi下面通常是用GMM+Chain的形式進行聲學模型訓練,然后還要結合語言模型進行訓練和解碼(這點同端對端的方案是不一樣的),GMM用來做語音序列同文本的對齊,然后再做chain模型的訓練,得到聲學模型,這可以看作是預訓練(pre-training),Kaldi提供的chain模型訓練腳本可以參考egs/libspeech/s5/local/chain/run_tdnn.sh或者egs/wsj/s5/local/chain/run_tdnn.sh,
以上這些實作起來比較容易,不多贅述,后面開始進行finetune的時候就開始遇到問題了,Kaldi中其實提供了兩種調參模式:一種叫tune,也就是調整整個訓練程序的方案和引數,得到新的模型,這種形式不會基于已有的模型進行,所以相當于就是重新跑一邊chain的訓練程序,比較耗時,如果去看kaldi的egs下面的腳本,里面出現比如tdnn_7r這樣的欄位,其中,7r就是對應這種調參方式的不同版本編號,另一種就是finetune,這個就是本文要介紹的方案,
基礎模型是用數千小時的語料訓練出來的,針對在應用場景下出現的識別問題,又整理了數百小時的資料進行finetune,
對于GMM模型和對齊(主要是對齊結果),可以參照librispeech中的run.sh,與預訓練模型的訓練一樣,
對于Chain模型finetune部分,起初,我參考了egs/rm/s5/local/run/tuning/run_tdnn_wsj_rm_1c.sh,但是發現一個問題(第一個坑),里面的tree檔案夾是預先生成的,但是我找不到生成這個tree檔案夾的腳本,這里先解釋下tree檔案夾是干嘛的:chain模型訓練實際上是區分性訓練,所以有分子和分母,分子采用GMM的聲學模型解碼所得,分母則是force-aligned的對齊結果,tree檔案夾中主要保存的就是對齊結果(.ali)和聲學模型的聚類樹(tree),另外,從GMM到chain,聚類的物件從triphone變為biphone,而phone topo從5-state變為1-state,所以這里有一個topo和tree的轉換作業要做,然后根據新的topo和tree重新對齊,這個程序就是run_tdnn.sh中steps/nnet3/chain/gen_topo.py, steps/nnet3/chain/build_tree.sh做的事情,回到run_tdnn_wsj_rm_1c.sh,里面的tree檔案夾按我的理解(并未實際去驗證)應該是做完以上兩個操作之后生成的,在這個腳本中并未體現,所以這里對使用者(至少是我)產生了一定困惑,
此時,如果按照run_tdnn.sh中的步驟,重新生成phone topo和tree,那么在之后的chain訓練中,就會出現第二個坑:pdf數量不匹配!pdf就是每個分類的概率密度分布,是tree的聚類數量,體現在神經網路里面,就是最后一層的維度,所以,這里出現問題的原因就是重新生成的tree的聚類數量同原來預訓練模型的分類數量不匹配,在build_tree.sh,里面生成tree采用的是當前的訓練資料,數百小時的和數千小時的資料的聚類結果出現很大差異也是正常的,再看build_tree.sh做的事情,就是兩個:生成new tree,以及用生成的new tree重新做對齊:convert-ali,所以,我們可以不用重新生成tree,而是用預訓練模型的tree只做重新對齊就可以了,
最后,就可以用steps/nnet3/chain/train.py進行chain模型的訓練,在finetune時,需要加載預訓練模型作為啟動,加載方式分兩步:首先,對于聲學模型final.mdl的格式,里面存在一些其它資訊,作為預訓練模型加載時需要轉成純nnet原始資料的格式,可以通過”nnet-copy --raw=true src.mdl tgt.raw”實作;之后,在train.py中,通過--trainer.input_model將模型配置進來,
以上就是我在基于kaldi做聲學模型的fintune時遇到的問題和解決方案,可能寫的比較亂,下面就把完整步驟整理如下:
1、語言模型與原預訓練模型一致
一般在egs/xxx/s5中都有一個run.sh檔案,而開頭會有一些準備作業,比如生成dict,lang,lm. 這些操作根據不同的訓練集可能會得到不同的結果,尤其是phone表!這會影響后續聚類和對齊的問題,所以這些語言模型相關的資訊在finetune時,需要與預訓練模型保持一致,不要在finetune時進行處理,建議可以生成一個固定的版本,獨立維護,
2、特征提取
特征提取一般用mfcc,在GMM訓練時就都會做,
在chain訓練時,也可以用ivector,
我這邊是在GMM時用mfcc,而chain訓練時用了ivector,
3、GMM模型訓練
輸入:finetune的資料集;比如(data/train_for_finetune)
輸出:GMM聲學模型、基于GMM的強制對齊結果;比如(finetune_tri5a & finetune_tri5a_ali)
4、沿用預訓練模型的相關結果
a.預訓練模型從final.mdl轉成input.raw:nnet-copy --raw=true final.mdl input.raw
PS: 這里說明下,我并沒有對預訓練模型的神經網路進行任何改變,如果想要做些改變,可以參考下run_tdnn_wsj_rm_1c.sh里面的處理,
b.將預訓練模型用于chain訓練的tree檔案夾(檔案一般存放在exp/chain/中,比如:exp/chain/tri5a_tree)中的tree和1.mdl拷貝到當前finetune的tree檔案夾中,
PS:只需要這兩個就可以了,其它的.ali和.trans都是要重新生成的,
5、生成Chain訓練需要的分子分母
- 分子:steps/align_fmllr_lats.sh, 引數具體可以參照run_tdnn.sh
- 分母:steps/nnet3/chain/build_tree.sh, 可以參照run_tdnn.sh的引數配置,但是增加一個配置項:--stage -1,這樣就可以不生成new tree,只執行第二步convert-ali,那么這時候的tree哪里來呢,就是將預訓練的tree放到build_tree.sh的生成目錄中,也就是上面4.b做的事情,舉個例子:
steps/nnet3/chain/build_tree.sh --stage -1 --frame-subsampling-factor 3 \
--context-opts "--context-width=2 --central-position=1" \
--cmd "$train_cmd" 7000 data/$train_set/train $lang exp/$train_set""_tri5a_ali $treedir
其中最后一個引數treedir就是本次finetune的chain訓練需要的tree所在的目錄,我事先生成這個目錄,并將預訓練中對應檔案夾中的tree和1.mdl拷貝到這里,然后在執行build_tree.sh即可,
6、chain訓練,等待結果即可,
后記:以上絮絮叨叨寫了很多,但難免受制于個人能力,可能會有眾多不足和謬誤,對于存在的問題歡迎指正;如果我有沒說清楚的地方,也歡迎交流,
之所以寫篇文章,一方面也是對最近作業的記錄總結;另外,發現kaldi中對于finetune的實際操作沒有一個比較現成的腳本,所以我在這個程序中踩了很多坑,希望我的經驗可以幫助到同行,他山之石可以攻玉,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/518872.html
標籤:其他
上一篇:Linux網路命令
