1. 程式人生 > >Kaldi 對說話人識別GMM-UBM的MAP 引數更新和對數似然概率解讀

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 來