HERest源码解析续

上一篇博客介绍了整个HERest工具最重要的功能,就是实现前向-后向算法(Baum-Welch)算法。并简单介绍了涉及的几个重要的数据结构,以及输入和输出。现在开始从头开始,了解这个实现过程。

   UttInfo *utt;            /* utterance information storage */
   FBInfo *fbInfo;          /* forward-backward information storage */
   HMMSet hset;             /* Set of HMMs to be re-estimated */

在main函数的开头就声明了上面三个重要的变量,并且给出了注释,它们就是三个最重要的提示。我们先看下面的流程,待会再回来详细讲这三个数据结构。因为它们太重要了,所以要在合适的时机出场。

代码流程接下来是一系列初始工作,跟其他工具类似,例如命令参数的保存,各个模块的内存分配和初始化。然后是具体涉及上面三个重要变量的内存分配工作,实际上这三个变量,都占有大量内存。所以单独给它们命名了一段内存空间,名字分别是HmmStore、uttStore以及FBInfoStore,然后在它们上面分别创建hset、utt和fbInfo对象。

   CreateHeap(&hmmStack,"HmmStore", MSTAK, 1, 1.0, 50000, 500000);
   SetConfParms(); 
   CreateHMMSet(&hset,&hmmStack,TRUE);
   CreateHeap(&uttStack,   "uttStore",    MSTAK, 1, 0.5, 100,   1000);
   utt = (UttInfo *) New(&uttStack, sizeof(UttInfo));
   CreateHeap(&fbInfoStack,   "FBInfoStore",  MSTAK, 1, 0.5, 100 ,  1000 );
   fbInfo = (FBInfo *) New(&fbInfoStack, sizeof(FBInfo));

可以通过sizeof操作符计算对象(结构)占用多大空间,通过New函数实现内存分配(HTK3.4.1是C语言实现的,所以这里的New是自己实现的,它的功能与C++中的关键字new类似,都是给对象在堆上分配空间)。

趁这个机会说一下HTK的内存管理。HTK为了提高内存管理的效率,自己实现了一套内存分配、收回机制,涉及内存分配的程序一般都在HMem模块中。

HTK内存管理分三类,分别是MHEAP, MSTAK和CHEAP。为什么分三类,以及它们各自的特点是什么?

MSTAK是stack类型的标记,在此内存上分配的对象是按照“先分配;后收回”的原则执行。而MHEAP则没有这个限制,可随机访问,但是要求每个对象的大小一样,而且它因此是自己管理,没有其他时空消耗。CHEAP则是调用操作系统关于内存分配、收回的一些操作。理论上应该尽量避免。

 

现在来分别看看上述三个变量的结构是啥样。

第一个要介绍就是HMMSet,其实它没什么好介绍的,因为之前在HCompV中已经详细分析过了。因为它太重要了,就再巩固下。

/* ---------------------- HMM Sets ----------------------------- */

typedef struct _HMMSet{
   MemHeap *hmem;          /* memory heap for this HMM Set */   
   Boolean *firstElem;     /* first element added to hmem during MakeHMMSet*/
   char *hmmSetId;         /* identifier for the hmm set */
   MILink mmfNames;        /* List of external file names */
   int numLogHMM;          /* Num of logical HMM's */
   int numPhyHMM;          /* Num of distinct physical HMM's */
   int numFiles;           /* total number of ext files */
   int numMacros;          /* num macros used in this set */

   MLink * mtab;           /* Array[0..MACHASHSIZE-1]OF MLink */

   short vecSize;          /* dimension of observation vectors */
   short swidth[SMAX];     /* [0]=num streams,[i]=width of stream i */
   ParmKind pkind;         /* kind of obs vector components */
   DurKind dkind;          /* kind of duration model (model or state) */
   CovKind ckind;          /* cov kind - only global in V1.X */
   HSetKind hsKind;        /* kind of HMM set */
   int numStates;          /* Number of states in HMMSet */
   int numSharedStates;    /* Number of shared states in HMMSet */
   int numMix;             /* Number of mixture components in HMMSet */
   int numTransP;          /* Number of distinct transition matrices */
   int ckUsage[NUMCKIND];  /* Number of components using given ckind */
   InputXForm *xf;         /* Input transform of HMMSet */
   short projSize;         /* dimension of vector to update */ 

} HMMSet;

为了让代码看起来简洁,避免信息太多让人绝望,删除了当前单音子模型不涉及的数据项。mtab这个MLink指针,指向一系列MLink,可以把mtab看做MLink数组,它的大小是MACHASHSIZE。每一项代表了一个hash槽,其值为一个指针,指向一个模型宏MacroDef。MacroDef包含了模型类型、模型本身的信息。关于模型类型,代号为[hluvixdtmps*]中的某一个,有什么作用,具体代表什么意思,到为目前为止,我也不清楚。

在函数CreateHMMSet中调用MakeHashTab函数,该函数再通过New函数为mtab分配了MACHASHSIZE个sizeof(void*)的空间,且分别让它们的值为NULL,就是所有指针都指向空。

OK,HMMSet暂时就回顾到这里,后面遇到时再来回顾下,务必要对HMMSet在内存的结构非常清晰,像是刻画在自己的脑海中似得。

再说一次,HMMSet包含一个数组mtab,它的元素是指向MacroDef的指针,该数组的size是MACHASHSIZE。

#define MACHASHSIZE 250007   /* Size of each HMM Set macro hash table */

那么这个MacroDef是什么?

typedef struct _MacroDef{
   MLink next;             /* next cell in hash table */
   char type;              /* type of macro [hluvixdtmps*] */
   short fidx;             /* idx of MMF file (0 = SMF) */
   LabId id;               /* name of macro */
   Ptr structure;          /* -> shared structure or HMM Def */
} MacroDef;

重点看Ptr structure,它就是指向某个具体的hmm,下面会看到如何创建hmm对象,并把它赋给Macrodef这个指针。

接下来,再看看UttInfo和FBInfo。

/* 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;

UttInfo这个数据结构保存了输入的观察值和对应文本(转录)的信息,分别对应Observation和Transcription。再往下追,看看这两个结构是什么样子。

typedef struct {
   Boolean eSep;         /* Energy is in separate stream */
   short swidth[SMAX];   /* [0]=num streams,[i]=width of stream i */
   ParmKind bk;          /* parm kind of the parm buffer */
   ParmKind pk;          /* parm kind of this obs (bk or DISCRETE) */
   short vq[SMAX];       /* array[1..swidth[0]] of VQ index */
   Vector fv[SMAX];      /* array[1..swidth[0]] of Vector */
} Observation;

它记录了观察向量的信息,包括有多个流、观察值是离散还是连续的、以及参数本身fv[SMAX]。并且我们看到UttInfo中,是以Transcription指针 *tr 的形式保存的,其实它是一个向量,包含了所有观察值包装后的Transcription元素。在LoadData函数里会看到。

FBInfo数据结构保存HMMset和一个指向AlphaBeta结构的指针,该结构是理解前向后向算法(Baum-Welch算法)的核心。也是嵌入式训练算法的核心。

/* structure storing the model set and a pointer to it's alpha-beta pass structure */
typedef struct {
  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;

  AlphaBeta *ab;      /* Alpha-beta structure for this model */

} FBInfo;

所以再贴上AlphaBeta结构的代码。

/* structure for the forward-backward alpha-beta structures */
typedef struct {
  
  MemHeap abMem;      /* alpha beta memory heap */
  PruneInfo *pInfo;   /* pruning information */
  HLink *up_qList;    /* array[1..Q] of active HMM defs */
  HLink *al_qList;    /* array[1..Q] of active align HMM defs */
  LabId  *qIds;       /* array[1..Q] of logical HMM names (in qList) */
  short *qDms;        /* array[1..Q] of minimum model duration */
  DVector *alphat;    /* array[1..Q][1..Nq] of prob */
  DVector *alphat1;   /* alpha[t-1] */
  DVector **beta;     /* array[1..T][1..Q][1..Nq] of prob */
  float *****otprob;  /* array[1..T][1..Q][2..Nq-1][0..S][0..M] of prob */
  LogDouble pr;       /* log prob of current utterance */
  Vector occt;        /* occ probs for current time t */
  Vector *occa;       /* array[1..Q][1..Nq] of occ probs (trace only) */

} AlphaBeta;

这个结构包含很多信息,pruning info、qList、alphat、beta等等。总之,与前向后向算法有关的数据都包含在其中。因此理解它是理解整个训练过程的关键。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值