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 来








猜你喜欢

转载自blog.csdn.net/robingao1994/article/details/79918314