HCompV源码再解析续

上一篇博客介绍了到CreateHMM函数,它完成了创建名为“proto”的hmm,并且为HMMSet创建了mtab以及其他基础设施,例如NameCell指针的hashtable数组。

顺着CreateHMM函数执行结束,HCompV调用LoadHMMSet。下面分析该函数的流程。它的函数签名为下所示:

 LoadHMMSet(HMMSet *hset, char *hmmDir, char *hmmExt)

其中hmmDir,和hmmExt是命令行参数指定的hmm原型“***/proto”文件的目录和扩展类型,这里hmmDir为“src”,而hmmExt为空,因为它没有扩展文件名。

   for (h=0; h<MACHASHSIZE; h++)
      for (p=hset->mtab[h]; p!=NULL; p=p->next) 
         if (p->type == 'h'){
            hmm = (HLink)p->structure;
            if (hmm->numStates == 0 ) {
               ConcatFN(hmmDir,p->id->name,hmmExt,fname); 
               if(InitScanner(fname,&src,&tok,hset)<SUCCESS){
                  HRError(7010,"LoadHMMSet: Can't find file");         
                  ResetHMMSet(hset);
                  return(FAIL);
               }
               if (trace&T_MAC)
                  printf("HModel: getting HMM Def from %s\n",fname);
               if(GetToken(&src,&tok)<SUCCESS){
                  TermScanner(&src);
                  ResetHMMSet(hset);
                  HMError(&src,"LoadHMMSet: GetToken failed");
                  return(FAIL);
               }
	       while (tok.sym == MACRO)
                  switch (tok.macroType){
                  case 'o':
                     if(GetOptions(hset,&src,&tok, &nState)==FAIL){
                        TermScanner(&src);
                        ResetHMMSet(hset);
                        HMError(&src,"LoadHMMSet: GetOptions failed");
                        return(FAIL);
                     }
                     break;
                  case 'h':
                     if (!ReadString(&src,buf)){
                        TermScanner(&src);
                        ResetHMMSet(hset);
                        HMError(&src,"LoadHMMSet: Macro name failed");
                        return(FAIL);
                     }
                     if (GetLabId(buf,FALSE) != p->id){
                        TermScanner(&src);
                        ResetHMMSet(hset);
                        HMError(&src,"LoadHMMSet: Inconsistent HMM macro name");
                        return(FAIL);
                     }
                     if(GetToken(&src,&tok)<SUCCESS){
                        TermScanner(&src);
                        ResetHMMSet(hset);
                        HMError(&src,"LoadAllMacros: GetToken failed");
                        return(FAIL);
                     }
                     break;
                  default:
                     TermScanner(&src);
                     ResetHMMSet(hset);
                     HMError(&src,"LoadHMMSet: Unexpected macro in HMM def file");
                     return(FAIL);
                     break;
                  }              
               if(GetHMMDef(hset,&src,&tok,hmm,nState)<SUCCESS){
                  TermScanner(&src);
                  ResetHMMSet(hset);
                  HRError(7032,"LoadHMMSet: GetHMMDef failed");
                  return(FAIL);
               }
               TermScanner(&src);
            }
         }

重点是看上面的代码做了什么事。最外的循环是遍历所有的mtab的入口,然后浏览它是否为物理hmm类型,然后看它的NumStates是否为0。如果为0,则对它进行初始化。

回忆下上一篇说回到,给hset里添加了一个简单初始化的hmm,名为“proto”,只是创建了hmm空壳,里面并没有数据且NumStates为0.现在就是要找到这个hmm实体,并且读取“src\proto”文件所指定的hmm相关参数,比如状态数、转移矩阵等等并赋给这个hset中的对应的hmm。当循环找到存在这么一个物理hmm,然后顺次调用InitScanner和GetToken。

InitScanner的作用是初始化文件句柄,GetToken是读取proto文件的第一个字符,判断它是什么类型宏标记。我们的proto前两个字符分别是“~”和“o”,因此GetToken得到的tok->macroType 的值是o; tok->sym = MACRO;然后进入while循环,处理该文件中的一些符号,首先是调用 GetOptions(HMMSet *hset, Source *src, Token *tok, int *nState)。

/* GetOptions: read a global options macro, return numStates if set */
static ReturnStatus GetOptions(HMMSet *hset, Source *src, Token *tok, int *nState)
{
   int p=0;
   
   *nState=0; 

   if (trace&T_PAR) printf("HModel: GetOptions\n");
   if(GetToken(src,tok)<SUCCESS){
      HMError(src,"GetOptions: GetToken failed");
      return(FAIL);
   }
   while (tok->sym == PARMKIND || tok->sym == INVDIAGCOV || 
          tok->sym == HMMSETID  || tok->sym == INPUTXFORM || 
          tok->sym == PARENTXFORM || tok->sym == PROJSIZE ||
          (tok->sym >= NUMSTATES && tok->sym <= XFORMCOV)){
      if(GetOption(hset,src,tok,&p)<SUCCESS){
         HMError(src,"GetOptions: GetOption failed");
         return(FAIL);
      }
      if (p>*nState) *nState = p;
   }
   FreezeOptions(hset);
   return(SUCCESS);
}

它里面也会调用GetToken:

/* GetToken: put next symbol from given source into token */
static ReturnStatus GetToken(Source *src, Token *tok)
{
   char buf[MAXSYMLEN],tmp[MAXSTRLEN];
   int i,c,imax,sym;
   
   tok->binForm = FALSE;
   while (isspace(c=GetCh(src)));     /* Look for symbol or Macro */
   if (c != '<' && c != ':' && c != '~'  && c != '.' && c != '#') {
      if (c == EOF) {
         if (trace&T_TOK) printf("HModel:   tok=<EOF>\n");
         tok->sym=EOFSYM; return(SUCCESS);
      }
      HMError(src,"GetToken: Symbol expected");
      return(FAIL);
   }
   if (c == '~'){                    /* If macro sym return immediately */
      c = tolower(GetCh(src));
      if (c!='s' && c!='m' && c!='u' && c!='x' && c!='d' && c!='c' &&
          c!='r' && c!='a' && c!='b' && c!='g' && c!='f' && c!='y' && c!='j' &&
          c!='v' && c!='i' && c!='t' && c!='w' && c!='h' && c!='o')
         {
            HMError(src,"GetToken: Illegal macro type");
            return(FAIL);
         }
      tok->macroType = c; tok->sym = MACRO;
      if (trace&T_TOK) printf("HModel:   MACRO ~%c\n",c);
      return(SUCCESS);
   }
   i=0; imax = MAXSYMLEN-1;
   if (c=='#') {           /* if V1 mmf header convert to ~h */
      while ((c=GetCh(src)) != '#' && i<imax)
         buf[i++] = c;
      buf[i] = '\0';
      if (strcmp(buf,"!MMF!") != 0){
         HMError(src,"GetToken: expecting V1 style MMF header #!MMF!#");
         return(FAIL);
      }
      tok->sym = MACRO; tok->macroType = 'h';
      if (trace&T_TOK) printf("HModel:   MACRO ~h (#!MMF!#)\n");
      return(SUCCESS);
   }
   if (c=='.'){            /* if . and not EOF convert to ~h */
      while (isspace(c=GetCh(src)));
      if (c == EOF) {
         if (trace&T_TOK) printf("HModel:   tok=.<EOF>\n");
         tok->sym=EOFSYM;
         return(SUCCESS);
      }
      UnGetCh(c,src);
      tok->sym = MACRO; tok->macroType = 'h';
      if (trace&T_TOK) printf("HModel:   MACRO ~h (.)\n");
      return(SUCCESS);     
   }  
   if (c=='<') {                 /* Read verbose symbol string into buf */
      while ((c=GetCh(src)) != '>' && i<imax)
         buf[i++] = islower(c)?toupper(c):c;
      buf[i] = '\0';
      if (c != '>'){
         HMError(src,"GetToken: > missing in symbol");
         return(FAIL);
      }
      /* This is tacky and has to be fixed*/
      for (sym=0; sym<NUMSYM; sym++) /* Look symbol up in symMap */
         if (strcmp(symMap[sym].name,buf) == 0) {
            tok->sym = symMap[sym].sym;
            if (trace&T_TOK) printf("HModel:   tok=<%s>\n",buf);
            return(SUCCESS);                                /* and return */  
         }
   } else {
      /* Read binary symbol into buf */
      tok->binForm = TRUE;
      sym = GetCh(src);
      if (sym>=BEGINHMM && sym<PARMKIND) {
         if (trace&T_TOK) printf("HModel:   tok=:%s\n",symNames[sym]);
         tok->sym = (Symbol) sym;
         return(SUCCESS);                                /* and return */  
      }     
   }
         
   /* if symbol not in symMap then it may be a sampkind */
   if ((tok->pkind = Str2ParmKind(buf)) != ANON){
      tok->sym = PARMKIND;
      if (trace&T_TOK) printf("HModel:   tok=SK[%s]\n",buf);
      return(SUCCESS);
   }
   strcpy(tmp,"GetToken: Unknown symbol ");
   HMError(src,strcat(tmp,buf));
   return(FAIL);
}

GetToken函数的作用语法解析,并将结果保存在Token *tok中。

它主要是处理HMM模型配置文件,这里是proto(该文件内包含一个名为proto的hmm初始模型),支持那些字符语义,包括HMM定义的开始与结束、状态数、参数等等。另外,还分析语音特征的一些参数、例如是否包含一阶差分、二阶差分等等。

接着,最重要的是要理解HCompV工具的最终目的,是初始化一个全局hmm,包括状态的均值和方差。到目前为止还都是准备工作,下面才是真正的主角。但难点并不在此,而恰恰是前期那些设置工作,如果理解了前面的逻辑,接下来会非常容易。

   /* Create accumulators for the mean and variance */
   for (s=1;s<=hset.swidth[0]; s++){
      V = hset.swidth[s];
      accs[s].meanSum=CreateVector(&gstack,V);
      ZeroVector(accs[s].meanSum);
      if (fullcNeeded[s]) {
         accs[s].squareSum.inv=CreateSTriMat(&gstack,V);
         accs[s].fixed.inv=CreateSTriMat(&gstack,V);
         ZeroTriMat(accs[s].squareSum.inv);
      }
      else {
         accs[s].squareSum.var=CreateSVector(&gstack,V);
         accs[s].fixed.var=CreateSVector(&gstack,V);
         ZeroVector(accs[s].squareSum.var);
      }
   }

这段代码设置了一系列的累加器,它保存了对训练数据的中间统计信息。accs就是一个数组,它的元素是CovAcc,包括了meansSum和squareSum。如何计算它们,代码逻辑也比较清晰,在LoadFile函数中说明。

/* Storage for mean and covariance accumulators */
typedef struct {
   Vector       meanSum;            /* acc for mean vector value */
   Covariance   squareSum;          /* acc for sum of squares */
   Covariance   fixed;              /* fixed (co)variance values */
} CovAcc;
static CovAcc accs[SMAX];           /* one CovAcc for each stream */

到目前为止,还仅仅停留在函数Initialise()中,分配了hmm的模型参数,分配了累加器为了后面计算全局的均值和方差做准备。

 

接下来看看我们数次提到的“累加器”到底是什么样的。

accs[SMAX]数组,它的元素的CovAcc,上面的代码展示了CovAcc包含三项。分别是meanSum,squareSum和fixed。这里数组可以暂时不用考虑,因为涉及到HTK预留了多数据源的扩展。我们可以理解为CovAcc就是当前数据集的累加器,接下来就是填充这个CovAcc的内容。下面三行代码就是在给累加器创建空间,并将初始值设置为0.

但是,这三行代码看似平淡无奇,实则有些蹊跷在里面。尤其是CreateSVector,它不仅仅是创建vector向量的。

         accs[s].squareSum.var=CreateSVector(&gstack,V);
         accs[s].fixed.var=CreateSVector(&gstack,V);
         ZeroVector(accs[s].squareSum.var);

详细分析下CreateSVector函数:

/* EXPORT->CreateSVector:  Shared version */
Vector CreateSVector(MemHeap *x, int size)
{
   SVector v;
   Ptr *p;
   int *i;
   
   p = (Ptr *)New(x,SVectorElemSize(size));
   v = (SVector) (p+2);
   i = (int *) v; *i = size;
   SetHook(v,NULL);
   SetUse(v,0);
   return v;
}

x表示全局的内存堆,size是要分配的内存数量。实际上分配的内存比size要大一些。

再看New(x, SVectorElemSize(size)):

size_t SVectorElemSize(int size){ return (size+1)*sizeof(float)+2*sizeof(Ptr); }

看到了吗?它实际分配的空间数量是(size+1)*sizeof(float) + 2*sizeof(Ptr)多了一个float和2个指针的空间。看后面的代码可以推测,2个指针变量是存放在这块内存的最前面,且接着存放int变量size,也就是vector的大小,也是这块内存后的空间。

经过SetHook和SetUse之后:

ZeroVector函数就是将上图中绿色标记为float部分设置为0.0。

接着就是创建observation对象,来放置观察向量。

/* EXPORT->MakeObservation: Create obs using info in swidth and pkind */
Observation MakeObservation(MemHeap *x, short *swidth, 
                            ParmKind pkind, Boolean forceDisc, Boolean eSep)
{
   Observation ob;
   int i,numS;
   
   ob.pk = pkind; 
   ob.bk = pkind&(~HASNULLE); 
   ob.eSep = eSep;
   for (i=0; i<SMAX; i++) ob.fv[i]=NULL,ob.vq[i]=-1;
   if (forceDisc) {
      if ((pkind&BASEMASK) != DISCRETE && !(pkind&HASVQ))
         HError(6373,"MakeObservation: No way to force discrete observation");
      ob.pk = DISCRETE+(pkind&HASNULLE);
   }
   numS = swidth[0];
   if (numS>=SMAX)
      HError(6372,"MakeObservation: num streams(%d) > MAX(%d)",numS,SMAX-1);
   for (i=0; i<=numS; i++){
      ob.swidth[i] =swidth[i];
      if (i>0 && (pkind&BASEMASK) == DISCRETE && swidth[i] != 1)
         HError(6372,"MakeObservation: discrete stream widths must be 1");
   }
   /* Note that the vectors are created even if ob.pk==DISCRETE as */
   /* these are used in ReadAs????? but should not be accessed elsewhere */
   if ((pkind&BASEMASK) != DISCRETE)
      for (i=1; i<=numS; i++)
         ob.fv[i] = CreateVector(x,swidth[i]);
   return ob;
}

该函数根据之前的处理接触,初始化了全局的Observation对象,它用来接收观察向量。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值