Kaldi 對說話人識別GMM-UBM的MAP 參數更新和對數似然概率解讀

寫博客=寫日記,爲自己記錄工作進度和理論知識,如果有恰好路過的大牛經過,可以駐足看看我的理解

本人剛接觸說話人識別不到一個月,因工作需求研究了kaldi。大致弄懂了GMM-UBM,正在研究Ivector的理論和實踐.

雖然個人更喜歡數據分析,數據挖掘和傳統的機器學習。但能學到不同領域的AI知識拓寬知識廣度也是自我成長。

若有會跳街舞的(小弟曾經英國某城市冠軍),能喝酒的,蹦迪的,喜歡python多過C++的,喜歡Pandas多過Mysql的更該聯繫本人。

-----------------------------------------------------------分割線----------------------------------------------------------

因爲prof.蛋(Daniel,kaldi 各種sre的作者,說話人識別權威教授之一,x-vector提出者)說,kaldi裏面木有對GMM-UBM的說話人識別模型的對數似然概率打分,就是理論中的score=log(x|speakers model)-log(x|ubm)的計算。但同時好學的你又想在kaldi實現一下GMM-UBM的說話人識別怎麼辦呢,我們一同來學習,今天就先來分析一下kaldi的源碼。


如果你對GMM-UBM理論不熟悉,可以先看看這個論文:Speaker Verification Using Adapted Gaussian Mixture Models


沒做過說話人識別的同學可以用kaldi/egs/aishell/v1/run.sh跑一下腳本,大體看看生成的文件,打分啊,在run.sh中用了哪些腳本。如果你是個好學的朋友,你就通過裏面的腳本繼續搜尋到他用到了哪些可執行文件,根據可執行文件的邏輯對着裏面的函數去看代碼。 看底層C++代碼之前最好把GMM弄懂,比如多元高斯模型的公式,混合高斯模型的公式,對GMM的對數似然函數,MLE, EM 和MAP。 記得在做MLE時,記得把GMM中的那個P(x|theta)=多元高斯模型的公式,然後把裏面展開,log之後會得到一些東西,對着kaldi/src/gmm中的diam.cc,mle-diag-gmm.cc中看,其實大體有個他如何把算法寫成代碼的概念。

上面囉嗦了真麼多,現在主要講講如何做MAP

在kaldi的各個demo的train_diag_ubm.sh中,只用到了 mle-diag-gmm.cc中,都只用到了MLE和一些存入統計量,但你仔細看到的時候會發現裏面還有一個MAP的函數,要不然他也沒有後續ivector什麼事了。


注意,以下內容需要對着kaldi的底層C++源碼和上面給的論文公式來看,不然你不知道我在說什麼

主要將幾個做MAP會用到的,之前是在Ubuntu上邊看代碼邊記錄,我就直接複製進來了。。。

for i<num.frames;i++{  //  下面的每個地方都是對一個frame來做運算的,一個frame爲39維,是一個樣本點向量Xi,

                                   //因爲對於一個語音來說,你要的是他總frames的平均log likelihood,所以他得一個frame計算,                                                //不過這是後話了

AccumulateFromDiag://他是對於每個樣本點/每一幀的特徵向量作爲一個x。在裏面計算出對該樣本x的後驗概率,即對每個高斯分量對這個x算一個後驗 p*b(x)/sum(p*b(x)). 裏面有一個posterior數組,長度是高斯分量的個數。


ComponentPosterior://他就是對樣本點x,計算每個高斯分量產生該點的後驗概率
{likelihoods://計算對該樣本點,每個components的似然度,loglikes的維度是componet的維度即多維高斯分佈的公式計算一下
//返回對於該幀的特徵樣本,每個component生成他的似然度


ApplySoftMax()//映射到(0,1)



//然後ComponentPosteriors雖然返回的是posterior,但其實是每一個frame的loglike的值

AccumulateFromPosteriors:{
  occupancy_.AddVec(1.0, post_d); //這裏 post_d是似然=p*b(xi),是對當前i爲止的所有的樣本點,加起來的gamma值
  mean_accumulator_.AddVecVec(1.0, post_d, data_d)://均值的更新公式mean(new)=sum(gamma)*x 對當前i爲止的所有樣本點求和加起來,這裏post_d=gamma,data_d=x
    variance_accumulator_.AddVecVec(1.0, post_d, data_d):/cov更新公式,cov(new)=sum(gmamma)*x*x,這裏也要對到當前i爲止的所有樣本點求和,所以在上層有一個for i<num.frames.

//更新之後,返回當前第i個腳本的loglikelihood

}
//上面之後有一個gmm模型,他有一個loglikelihood矩陣,對所有frame的所有高斯分量


MapDiagGmmUpdate 在這裏開始做MAP adaptation, occupany是Ni就是sumT(gammaik)表示的是屬於第k個component的樣本量
occ_sum就是總樣本量N,occ可以看作是屬於某分量的樣本容量Nk, Nk可以取N1,N2,N3....Nk,k=高斯容量
所以Nk/N=先驗,即該高斯被選中的概率


 for (int32 i = 0; i < num_gauss; i++) {
    double occ = diag_gmm_acc.occupancy()(i);  //第i個分量的樣本容量 Ni


ngmm.weights_(i) = (occ + ngmm.weights_(i) * config.weight_tau) /
        (occ_sum + config.weight_tau);  //對每個高斯分量的先驗用 adpat因子進行更新



these new
sufficient statistic estimates are then combined with the old sufficient statistics
from the UBM mixture parameters using a data-dependent mixing coefficient.
The data-dependent mixing coefficient is designed so that mixtures with high
counts of data from the speaker rely more on the new sufficient statistics for
final parameter estimation and mixtures with low counts of data from the
speaker rely more on the old sufficient statistics for final parameter estimation.
就是與speakers data 有關係的高斯分量要用新的參數統計量,其他分量不變


記住都是new mean=a*E(x)+(1-a)*舊mean
new variance=a*E(*x-u)^2)+(1-a)*舊var


a=ni/(ni+r) ,r是mixing coefficient,rang(8-20), 論文設16


代碼中mean=sum(gamma*data),old mena就是對該高斯分量中,old mean的值
新mean的算法=(mean/ni+r)+r*old_mean/ni+r. MapDiagGmmUpdate mean的更新是沒問題的



var.AddVec2(1,ngmm.means_.Row(i))//這個會把n給平方然後加到原來的vector中,這裏的解釋爲把更新後的mean平方後加到原來的var中


新variance的算法是 E( (x - mu)^2 ) = E( x^2 - 2 x mu + mu^2 ) = E(x^2) + mu^2 - 2 mu E(x).
先計算E(x^2) + mu^2 - 2 mu E(x)


首先    Vector<double> var(diag_gmm_acc.variance_accumulator().Row(i)); //var的統計量爲 sum(ni*x^2)
      var.Scale(1.0 / occ); //這個會等於 E(x^2)


      var.AddVec2(1.0, ngmm.means_.Row(i));//讓means^2+到var中
//這裏已經得到了E(x^2) + mu^2 


SubVector<double> mean_acc(diag_gmm_acc.mean_accumulator(), i),
          mean(ngmm.means_, i) // 拿到新的mu 和以前的統計量sum(ni*x)


      var.AddVecVec(-2.0 / occ, mean_acc, mean, 1.0);//這個是讓mean*sum(ni*x)*2/ni變成=2muE(x)


      var.Scale(occ / (config.variance_tau + occ));  ni/(r+ni)*var統計=a*E((x-mean)^2)


      var.AddVec(config.variance_tau / (config.variance_tau + occ), old_var);//+(1-a)*old var




//以上是Adaptation的部分

//把speaker model向量 導入,在score裏申請一個diagmm,然後把裏面的參數用這個向量迭代賦值,把模型複製過去

//然後計算eval 在ubm和speakermodel loglikelihood的差值,解決!
}

------------------------------------------------------算分,未完待續---------------------------------------------------
//對GMM的似然度打分,DAN建議用帶有global的文件
//從 gmm-global-get-frame-likes 來








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章