在还没开始讲解源码之前,我先贴出一个数据结构HMMSet:
/* ---------------------- 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 */ PtrMap ** pmap; /* Array[0..PTRHASHSIZE-1]OF PtrMap* */ Boolean allowTMods; /* true if HMMs can have Tee Models */ Boolean optSet; /* true if global options have been set */ 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 */ TMixRec tmRecs[SMAX]; /* array[1..S]of tied mixture record */ int numStates; /* Number of states in HMMSet */ int numSharedStates; /* Number of shared states in HMMSet */ int numMix; /* Number of mixture components in HMMSet */ int numSharedMix; /* Number of shared mixtures in HMMSet */ int numTransP; /* Number of distinct transition matrices */ int ckUsage[NUMCKIND]; /* Number of components using given ckind */ InputXForm *xf; /* Input transform of HMMSet */ AdaptXForm *semiTied; /* SemiTied transform associated with model set */ short projSize; /* dimension of vector to update */ /* Adaptation information accumulates */ Boolean attRegAccs; /* have the set of accumulates been attached */ Boolean attXFormInfo; /* have the set of adapt info been attached */ Boolean attMInfo; /* have the set of adapt info been attached */ AdaptXForm *curXForm; AdaptXForm *parentXForm; /* Added to support LogWgts */ Boolean logWt; /* Component weights are stored as Logs */ /* Added to support delayed loading of the semi-tied transform */ char *semiTiedMacro; /* macroname of semi-tied transform */ } HMMSet;
要想掌握HTK工具,必须对这个结构里的每一项了如指掌,并能清晰的勾勒出画面。随着程序调试的进行,我们逐渐接近这个结构,并把它弄清楚。
在HCompV工具中,调试时直接跳到函数InitShell处。它调用SaveCommandLine函数,它的功能在静态变量char* saveCommandLine中保持命令行参数,后续解析会用到。
static char *savedCommandLine; /* SaveCommandLine: Stores all command line arguments in 1 string */ static void SaveCommandLine(int argc, char **argv) { int i, len=0; for (i=0;i<argc;i++) len+=strlen(argv[i])+1; savedCommandLine = (char *) malloc(len); savedCommandLine[0]='\000'; strcat(savedCommandLine, argv[0]); for (i=1;i<argc;i++) sprintf(savedCommandLine,"%s %s", savedCommandLine, argv[i]); }
现在它的内容是“D:\\vscode\\source\\repos\\HTK\\HCompV\\Debug\\HCompV.exe -C .\\src\\config\\config1 -f 0.01 -m -S .\\src\\train.scp -M .\\src\\hmms\\hmm0 .\\src\\proto” 一共11个参数,即argc值为11.
接下来的循环处理-A -C -S -V -D标志,并保存相应的参数:
-A表示打印信息,infoPrint设为True
-C是指定配置文件,它的位置有-C后面的命令参数指定;然后是读取配置文件并解析;
函数 ReadConfigFile读取配置文件,并解析,构造confList,它是一个链表,节点是ConfigEntry。
typedef struct _ConfigEntry{ ConfParam param; struct _ConfigEntry *next; }ConfigEntry; typedef struct { /* Configuration Parameter */ char *user; /* name of module/tool to use this param */ char *name; /* name of param - upper case always */ ConfKind kind; /* kind of config param value */ ConfVal val; /* value */ Boolean seen; /* set true when read by any module */ } ConfParam;
param内容包含了配置项的名字、值类型和值本身。
-S指定文件列表文件,后面是文件的具体位置。通过 SetScriptFile函数计算包含多少个文件。
然后就是系统的初始化,其中跟后面密切相关的是内存的初始化。它会分配gstack这个全局的内存空间。在后面的CreateHMMSet函数中,将会看到,HMM集合对象hset有个属性mem,它指向gstack的内存空间,而它上面保存了所有的HMM参数。它们是以哈希表的数据结构存储的。
CreateHMMSet函数,第一个参数hset,就是HMMSet的指针,它指向一个对象包含了当前HMM集合的一些信息,比如:包含多少个hmm(物理的、逻辑的各多少)、而且hmm还分不同类型(这点还没弄明白),以及存储的内存位置。HTK还支持多数据流通道,每个数据流的维度等等信息。
这个结构是像是一个理解HTK的地图,时刻要关注它。关注每一步操作对这个结构产生哪些影响。或者这个结构连接上的其他信息发生哪些变化。
另外,非常重要的一点是,它还包含一个hashtable;而且这个hashtable的位置就在CreateHMMSet函数的第二个参数MemHeap* heap指向的内存块上。
这个hashtable的总槽数由下面这个宏确定:
#define MACHASHSIZE 250007 /* Size of each HMM Set macro hash table */
待会儿会看到如何创建这个hashtable,以及如何给这个table添加第一个hmm对象。下面的代码是CreateHMMSet函数,前面都是初值设定,没有什么难的。重点是看到中间那部分,调用一个特征重要的函数MakeHashTab(hset, MACHASHSIZE)。
/* EXPORT->CreateHMMSet: create the basic HMMSet structure */ void CreateHMMSet(HMMSet *hset, MemHeap *heap, Boolean allowTMods) { int s; /* set default values in hset structure */ hset->hmem = heap; hset->hmmSetId = NULL; hset->mmfNames = NULL; hset->numFiles = 0; hset->allowTMods = allowTMods; hset->optSet = FALSE; hset->vecSize = 0; hset->swidth[0] = 0; hset->dkind = NULLD; hset->ckind = NULLC; hset->pkind = 0; hset->numPhyHMM = hset->numLogHMM = hset->numMacros = 0; hset->xf = NULL; hset->logWt = FALSE; for (s=1; s<SMAX; s++) { hset->tmRecs[s].nMix = 0; hset->tmRecs[s].mixId = NULL; hset->tmRecs[s].probs = NULL; hset->tmRecs[s].mixes = NULL; } /* initialise the hash tables */ hset->mtab = (MLink *)MakeHashTab(hset,MACHASHSIZE); hset->pmap = NULL; /* initialise adaptation information */ hset->attRegAccs = FALSE; hset->attXFormInfo = FALSE; hset->attMInfo = FALSE; hset->curXForm = NULL; hset->parentXForm = NULL; hset->semiTiedMacro = NULL; hset->semiTied = NULL; hset->projSize = 0; }
下面可以看出函数MakeHashTab(hset, MACHASHSIZE)在创建一个哈希表。
/* MakeHashTab: make a macro hash table and initialise it */ void ** MakeHashTab(HMMSet *hset, int size) { void **p; int i; p = (void **) New(hset->hmem,sizeof(void *)*size); for (i=0; i<size; i++) p[i] = NULL; return p; }
这个hash表的每个元素都可能填上一个MLink,它是指向MacroDef的指针。
typedef struct _MacroDef *MLink; 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;
这个MacroDef是描述了HMM更上层的结构,它的Ptr structure会指向具体的hmm对象。
到此,我们头脑中,应该有张清晰的地图,就是HTK这个隐马尔可夫(HMM)开源软件最重要的结构图。