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

article/2025/10/7 3:32:27

上一个博客为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 insingle 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的计算公式,就能重新评估模型参数了。


http://chatgpt.dhexx.cn/article/rpwhPwl4.shtml

相关文章

https tk6 us

https.tk6.us专题,为您展现优质的https.tk6.us各类信息,在这里您可以找到关于https.tk6.us的相关内容及最新的https.tk6.us贴子。 https.tk6.us服务器iP: 当前解析: 未查找到结果! 历史解析记录: 2022-09-29-----2023-05-024…

基于HTK的连续语音识别系统搭建学习笔记(一)

放假之前,就已经对HTK上完成连续语音识别的实验充满兴趣。于是找了很多资料,准备在放假的时候好好学习,仔细琢磨,HTK博大精深,光学习和完成孤立词识别肯定是不够。从今天开始,开始同步学习和记录基于HTK的连…

HTK在windows下的配置说明

本文档意在详细介绍在windows环境下如何配置编译HTK,是根据网上资料及自己实际操作步骤编写而成,一方面是为方便以后使用此工具的朋友们有个参考,另外一方面是给自己加深印象。 准备事项 安装Microsoft Visual Studio开发环境(以下简称VS),因为HTK代码需要通过VS来编译。…

HTK工具搭建识别器的总体框架

老早之前就学习了HTK工具,并用于搭建连续语音识别器,但是好久没有用对于一些东西又忘记了,现在由于做实验需要用到HTK,又重新看了一遍,所以把一些大致的东西记录下来,以后可以直接看这个。感觉用HTK搭建识别…

HTB-OpenKeyS

HTB-OpenKeyS 信息收集80端口立足于JenniferJennifer -> root 信息收集 80端口 对其进行简单的SQL注入测试和NoSQL注入测试后进行目录扫描。 auth.swp文件内容如下: 代码不是很完整,只能大致了解意思(请原谅我脑子抽了没注意是个swp交换…

HTK工具的安装

首先下载HMM 的开发包HTK,现在的版本是3.4.1.可以从CMU的官网进行下载。 http://htk.eng.cam.ac.uk/,下载后解压HTK工具包,我解压后的目录为:D:\htk\ 确保的环境变量Path包含路径:C:\Program Files\Microsoft Visual S…

HTK的使用方法

一,HTK目录结构 htk是一个开源的软件,解压之后的目录如下: [rootlocalhost htk]# ls AUTHORS config.status env HTK HTKTools Makefile.in ChangeLog config.sub FAQ HTKBook install-sh README co…

语音识别之HTK入门(一)HTK的下载配置

语音识别之HTK入门(一)HTK的下载配置 Linux环境配置VMware Tools安装必备环境 HTK下载HTK环境设置 跪拜大佬帖子: https://www.cnblogs.com/ansersion/p/4155828.html 基本按照大佬的帖子进行调试的,为表示敬意,全程跪…

HTK学习笔记(一)

HTK学习笔记(一) 一、HTK软件体系结构 HTK的软件体系结构 HTKTool各部分的功能: 所有的语音输入和输出都是通过HWAVE或HPARM HAudio:用于从音频设备输入波形 HWave:用于从文件中读取波形 HSLab:波形显示工具,可以用于采集语音…

基于htk工具包的语音识别

htk简介: HTK是一个构建隐藏马尔可夫模型(HMMs)的工具包。HMMs可以用于任何时间序列的建模建模,HTK的核心就是类似的通用目的。然而,HTK主要设计用于构建基于HMM的语音处理工具,特别是识别程序。因此,大部分HTK中的基础设施支持专门用于此任…

jQuery学习手册(15)

逐个设置 $(div).css(width, 100px)​ $(div).css(height, 100px)​ $(div).css(background, black)链式设置 注意:如果大于3步,建议分开 $(div).css(width, 100px).css(height, 100px).css(background, black)批量设置 $(div).css({ ​ …

第7章页面布局-ConstrainedBox限定宽高

防采集标记:亢少军老师的课程和资料 import package:flutter/material.dart; class LayoutDemo extends StatelessWidget {overrideWidget build(BuildContext context) {return new Scaffold(appBar: new AppBar(title: new Text(ConstrainedBox限定宽高示例),),b…

全网最详细的一篇Flutter 尺寸限制类容器总结

Flutter中尺寸限制类容器组件包括ConstrainedBox、UnconstrainedBox、SizedBox、AspectRatio、FractionallySizedBox、LimitedBox、Container。这些组件可以约束子组件的尺寸,下面一一介绍。 ConstrainedBox ConstrainedBox组件约束子组件的最大宽高和最小宽高&am…

android中各种height和width总结

1. getMeasuredWidth()和getWidth() 以这两个为例,高度与其相同。这两个是在自定义View中最常见到的,通过字面意思可以看出,前者是测量的宽度,后者是控件的实际宽度,下面看下官方文档对他们的描述: //Ret…

JavaFX布局(一)

说道GUI编程一定要谈到布局,JavaFX内置了大量的布局控件提供给我们使用。其实,JavaFX的布局控件和界面元素控件都是继承自javafx.scene.layout.Region类。我们这里只看布局控件类。布局控件我们在界面上一般是看不到的,它一个容器用于放置其它…

Android 桌面小组件 AppWidgetProvider

废话 桌面小组件,绝对是小程序中的小程序,说白了就是任何复杂一丁点的操作都不适合做成桌面小组件。 所以这里采用的演示的例子,就只有一个白色圆角背景,外加一个文本框,显示文字。 小组件的教程网上一搜一大堆&…

Android 约束布局 ConstrainLayout min max width

写一个自定义view package com.anguomob.guidelineimport android.content.Context import android.graphics.Canvas import android.graphics.Color import android.util.AttributeSet import android.view.Viewclass ZeroView constructor(context: Context?, attrs: Attri…

ConstrainLayout 基础教程2,近期想跳槽的程序员必看

特性详解 Visibility behavior (可见性的表现) ConstraintLayout对可见性被标记View.GONE的控件(后称“GONE控件”)有特殊的处理。一般情况下,GONG控件是不可见的,且不再是布局的一部分,但是在布局计算上,ConstraintLayout与传统布局有一个很重要的区别: 传统布局下,…

UGUI源码解析——LayoutElement

一:前言 继承了ILayoutElement和ILayoutIgnorer接口,作为布局元素组件 挂载了Layout Element组件的对象,布局并不会生效,它是受到实现了布局组的控制(HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup) 二&#xff…

Layui框架的使用技巧

1.选中html代码块&#xff0c;ctrlshift减号- 就会全部折叠 2.加入Thymeleaf模板需要添加命名空间 <!DOCTYPE html> <html xmlns:th"http://www.thymeleaf.org/"></html> 2.1 替换方法 2.2添加 th:fragment 2.3主页面用一行代码替换 3.SpringBo…