语音识别之HTK入门(七)——HERest训练模型之Baum-Welch算法

上一个博客为B-W算法准备了基础,也就是前向算法和后向算法以及EM模型。

现在看看到底Baum-Welch算法是如何利用上述算法及模型来更新HMM的参数的。

之前也分析过多次了,在语音识别领域HMM模型之所以这么复杂,是因为观察向量对应的隐含状态不可得。存在隐藏数据,如果能得知这些标注数据,那么无论是计算初始概率,状态转移概率还是混淆概率都异常的简单而直接。

隐藏向量,假设它为I,观察向量为O,(O,I)表明为完全数据。

现在假设P(O,I|\bar{\lambda})表示当前模型下,完全数据的联合概率,LogP(O,I|\lambda)分别表示完全数据的对数似然概率,现在求得Q(\lambda,\bar{\lambda}) = \sum_{I}LogP(O,I|\lambda) P(O,I|\bar{\lambda}),使这个Q函数最大值的\lambda即为这次迭代的结果,即为完成一次迭代训练。

整个HMM模型里需要训练的参数包括三个分别是初始概率、转移概率和混淆概率。

我们需要从公式里推导出计算上述三个概率的计算式。公式推导可以参考这篇博客 ,我希望能从直觉上理解这些计算式。

在前一篇博客里,我们知道了前向算法\alpha_{t}(i)表示在t时刻状态i下,输出观察向量O_{1}O_{2}...O_{t}的概率,\beta_{t}(i)表示在t时刻,t+1,t+2, ... ,N时刻观察向量为O_{t+1}O_{t+1}...O_{N}的概率,那么\alpha_{t}(i) \times \beta_{t}(i)呢?表示在t时刻状态为i,整个观察向量为O的概率,可以写出连等式:P(O|\lambda) = \sum_{i=1}^{N}\alpha_{1}(i)\beta_{1}(i) = \sum_{i=1}^{N}\alpha_{2}(i)\beta_{2}(i) = ... = \sum_{i=1}^{N}\alpha_{T-1}(i)\beta_{T-1}(i)。定义\pi_{i} = \frac{\alpha_{1}(i)\beta_{1}(i)}{P(O|\lambda)},更一般的定义一个变量\gamma_{t}(i) = \frac{P(O,s_{t}=i|\lambda )}{P(O|\lambda)} = \frac{\alpha_{t}(i)\beta_{t}(i)}{P(O|\lambda)}\pi_{i} = \gamma_{1}(i),是一个特例。

那么在已知观察向量和模型参数情况下,如何估算模型状态转移的概率呢?,根据定义可以把这一概率值计算分部进行,首先计算状态i的概率,然后计算状态(i,j)同时发生的概率。

由于计算状态转移概率时没有给定时间,假设为t时刻,P(s_{t}=i,O|\lambda) = \alpha_{t}(i)\beta_{t}(i),它与我们前面定义的\gamma_{t}(i)存在一个比例关系。

P(s_{t}=i, s_{t+1}=j,O|\lambda),其值也可以通过前向-后向算法来求得,P(s_{t}=i, s_{t+1}=j,O|\lambda) = \alpha_{t}(i)a_{ij}b_{j}(O_{t+1})\beta_{t+1}(j),定义新的变量\xi{t}(i,j) = \frac{P(s_{t}=i, s_{t+1}=j,O|\lambda)}{P(O|\lambda)} = \frac{\alpha_{t}(i)a_{ij}b_{j}(O_{t+1})\beta_{t+1}(j)}{P(O|\lambda)}。从这些公式以及状态转移的意义可以推出公式:

P(O|\lambda) =\sum_{t=1}^{T-1}\sum_{j=1}^{N} \alpha_{t}(i)a_{ij}b_{j}(O_{t+1})\beta_{t+1}(j),

a_{ij} = \frac{\sum_{t=1}^{T-1}\xi_{t}(i,j)}{\sum_{t=1}^{T-1}\gamma_{t}(i)}

b_{j} (k)= \frac{\sum_{t=1,O_{t}=v_{k}}^{T-1}\gamma_{t}(i)}{\sum_{t=1}^{T}\gamma_{t}(i)},

\pi_{i} = \gamma_{1}(i)

下面就是调试程序,看看在HERest程序中,是如何处理标注文件和特征文件来训练HMM模型。

大部分代码都是在处理命令行参数和读取数据。其实核心的代码不过数十行。但是要充分理解那数十行的代码要穿透重重迷雾。

首先读取phones0.mlf文件中的所有数据,构建一个个的MLFEntity,每个MLFEntity对应一个文件脚本,会指出该实体对应phones0.mlf文件的偏移位置。例如下面是phones0.mlf的开头。它包括三部分,MLF头文件,必须要包含否则报错,然后是两个MLFEntity,分别“S0001.lab”和“S0002.lab”后面的省略了。然后把这些实体对象被保存到一个全局的链表中。而这是在处理命令行参数 -I phones0.mlf 时发生的。

#!MLF!#
"*/S0001.lab"
sil
d
ay
ax
l
ey
t
f
ay
v
sil
.
"*/S0002.lab"
sil
d
ay
ax
l
z
ia
r
ow
z
ia
r
ow
ey
t
s
ih
k
s
ow
w
ah
n
z
ia
r
ow
n
ay
n
th
r
iy
f
ay
v
ey
t
f
ay
v
th
r
iy
th
r
iy
n
ay
n
z
ia
r
ow
sil
.

接着是处理-t参数和 -H参数。-H参数指定MMF( Master Model File)文件,它与MLF文件都是HTK的一种脚本文件,一个是用来定义模型的,一个是用来指定标签的。它们之间也存在某种内在关系,暂时不表。它们的文件路径和名字被暂时保存在HMMSet模型(全局变量hset)的“依赖文件链表中”,供后续处理使用。

最后就是初始化一个HMMSet集合,由monophones0参数指定,该文件的内容是指定了整个训练系统所有的音子,也就是通过该文件需要建立的声学模型个数。

现在分析主要的处理函数:   Initialise(fbInfo, &fbInfoStack, &hset, GetStrArg());   InitUttInfo(utt, twoDataFiles);            DoForwardBackward(fbInfo, utt, datafn, datafn2) ;看它们分别完成了哪些功能。

在进入这些函数之前,有两个重要的struct需要介绍,因为它们和后面的计算息息相关,一个是FBInfo,一个是UttInfo。

/* structure storing the model set and a pointer to it's alpha-beta pass structure */
typedef struct {
  Boolean twoModels;  /* Enable two model reestimation */
  HMMSet *up_hset;    /* set of HMMs to be re-estimated */
  HMMSet *al_hset;    /* HMMs to use for alignment */
                      /* these are equal unless 2 model reest */
  HSetKind hsKind;    /* kind of the alignment HMM system */
  UPDSet uFlags;      /* parameter update flags */
  int skipstart;      /* Skipover region - debugging only */
  int skipend;
  int maxM;           /* maximum number of mixtures in hmmset */
  int maxMixInS[SMAX];/* array[1..swidth[0]] of max mixes */
  AlphaBeta *ab;      /* Alpha-beta structure for this model */
  AdaptXForm *inXForm;/* current input transform (if any) */
  AdaptXForm *al_inXForm;/* current input transform for al_hset (if any) */
  AdaptXForm *paXForm;/* current parent transform (if any) */
} FBInfo;

主要包括两个HMMSet和AlphaBeta指针,它们包含了模型训练的一些重要信息。两一个struct就是UttInfo。

/* structure for the utterance information */
typedef struct {

  MemHeap transStack; /* utterance transcript information heap */
  MemHeap dataStack;  /* utterance data information heap */
  MemHeap dataStack2; /* utterance data2 information heap */

  int Q;              /* number of models in transcription */
  Transcription *tr;  /* current transcription */

  Boolean twoDataFiles; /* Using two data files */
  int S;              /* number of data streams */
  int T;              /* number of frames in utterance */
  ParmBuf pbuf;       /* parameter buffer */
  ParmBuf pbuf2;      /* a second parameter buffer (if required) */

  Observation ot;      /* Observation at time t ... */
  Observation ot2;     /* Cepstral Mean Normalised obervation, used in
                               single pass re-training */

  LogDouble pr;        /* log prob of current utterance */

} UttInfo;

它主要包括Transcription指针和Observation。

下面看函数调用 Initialise(fbInfo, &fbInfoStack, &hset, GetStrArg())的内部逻辑。

1、根据提供的模型脚本文件构建和初始化一个当前的模型以及其参数,MakeHMMSet( hset, hmmListFn )和LoadHMMSet( hset,hmmDir,hmmExt)。

2、然后调用   InitialiseForBack(fbInfo, x, hset, uFlags, pruneInit, pruneInc, pruneLim, minFrwdP);实现前向后向算法做准备。

     主要是初始fbInfo里的一些参数,例如为AlphaBeta对象分配空间,设定模型的混合个数,等等

然后是InitUttInfo(utt, twoDataFiles);函数调用:初始化UttInfo对象的空间。

接着就是循环执行(语音,文本)对的前向-后向算法来重新估算模型参数,这里正式进入Baum-Welch算法的代码。

主要实现在DoForwardBackward(fbInfo, utt, datafn, datafn2)中,它执行的逻辑如下:

1、   LoadLabs(utt, lff, datafn_lab, labDir, labExt);

2、   LoadData(fbInfo->al_hset, utt, dff, datafn, datafn2);

3、   InitUttObservations(utt, fbInfo->al_hset, datafn, fbInfo->maxMixInS);

4、   FBFile(fbInfo, utt, datafn);

现在来分析每一步的执行逻辑,以及代码实现的对应公式。

第一步:在LoadLabs函数中,主要完成了通过datafn_lab的文件名,找到MLF文件中(phoneses0.mlf)它对应的transcription,也就是语音段对应的模型标注。例如第一个utterance是S0001.mfc,它对应于S0001.lab所指定的模型。该项目的所有标注数据都保存在phones0.mlf文件中,其实也可以写成单独的MLF格式的文件。

在LoadLabs函数中,会为UttInfo对象构建一个Transcription,它对应一个data/train/S000X.mfc文件。接下来的代码就是在这个链表中添加节点,每个节点对应一个模型的Label。节点的连接顺序就是文本的先后顺序。

typedef struct {
   LabList *head;          /* Pointer to head of Label List */
   LabList *tail;          /* Pointer to tail of Label List */
   int numLists;           /* num label lists (default=1) */
}Transcription;

执行顺序是LOpen ->LoadHTKLabels ->LoadHTKList。执行的结果,在utt对象的Transcription* tr对象中,指定了一个LabelList,它包含了当前音频对应的模型Label。主要逻辑是在LoadHTKList中实现的,它通过读取之前构建的MLFEntry对象,来实现Label节点并构建它们的链表。

第二步:执行LoadData。读取S0001.mfc的数据到utt对象的pbuf中,并把相关信息保存在BufferInfo中,例如包含多少个frame,每个frame包含多少个采样点,等等。

第三步InitUttObservations是第一次执行时,初始化一个观察向量的对象,就是分配空间。

第四步:FBFile(fbInfo, utt, datafn)执行前向-后向算法。这是咱们重点分析的地方。

算法所依赖的Label和frame data都预处理完了,分别保存在utt和fbInfo中。主要分两步:StepBack和StepForward。

在StepBack中,构建Beta矩阵。在StepForward中计算alpha矩阵。有了这两个参数,根据Baum-Welch的计算公式,就能重新评估模型参数了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值