代码逻辑到这里是个分水岭,因此另开一个博客来继续下面的源码分析。
之前一直是在为了最终目的做准备,主要包括创建HMMSet、读取proto模型文件、观察向量的配置文件,分配了内存累加器空间,其目的都是为了接下来的计算全局的方差和均值。
/* LoadFile: load whole file or segments and accumulate variance */
void LoadFile(char *fn)
{
ParmBuf pbuf;
BufferInfo info;
char labfn[80];
Transcription *trans;
long segStIdx,segEnIdx;
int i,j,ncas,nObs;
LLink p;
if (segId == NULL) { /* load whole parameter file */
if((pbuf=OpenBuffer(&iStack, fn, 0, dff, FALSE_dup, FALSE_dup))==NULL)
HError(2050,"LoadFile: Config parameters invalid");
GetBufferInfo(pbuf,&info);
CheckData(fn,info);
nObs = ObsInBuffer(pbuf);
for (i=0; i<nObs; i++){
ReadAsTable(pbuf,i,&obs);
AccVar(obs);
}
if (trace&T_LOAD) {
printf(" %d observations loaded from %s\n",nObs,fn);
fflush(stdout);
}
CloseBuffer(pbuf);
}
else { /* load segment of parameter file */
MakeFN(fn,labDir,labExt,labfn);
trans = LOpen(&iStack,labfn,lff);
ncas = NumCases(trans->head,segId);
if ( ncas > 0) {
if((pbuf=OpenBuffer(&iStack, fn, 0, dff, FALSE_dup, FALSE_dup))==NULL)
HError(2050,"LoadFile: Config parameters invalid");
GetBufferInfo(pbuf,&info);
CheckData(fn,info);
for (i=1,nObs=0; i<=ncas; i++) {
p = GetCase(trans->head,segId,i);
segStIdx= (long) (p->start/info.tgtSampRate);
segEnIdx = (long) (p->end/info.tgtSampRate);
if (trace&T_SEGS)
printf(" loading seg %s [%ld->%ld]\n",
segId->name,segStIdx,segEnIdx);
if (segEnIdx >= ObsInBuffer(pbuf))
segEnIdx = ObsInBuffer(pbuf)-1;
if (segEnIdx >= segStIdx) {
for (j=segStIdx;j<=segEnIdx;j++) {
ReadAsTable(pbuf,j,&obs);
AccVar(obs); ++nObs;
}
}
}
CloseBuffer(pbuf);
if (trace&T_LOAD)
printf(" %d observations loaded from %s\n",nObs,fn);
}
}
ResetHeap(&iStack);
}
主逻辑是OpenBuffer(&iStack, fn, 0, dff, FALSE_dup, FALSE_dup)、GetBufferInfo(pbuf,&info)、CheckData(fn,info)、ObsInBuffer(pbuf)、 ReadAsTable(pbuf,i,&obs)、AccVar(obs)。
在OpenBuffer函数中,重要的函数是FillBufFromChannel。
/* ------------ Read and Convert Data from Channel Input ------------ */
/* FillBufFromChannel: fill buffer from channel input */
/* if minRows>0 ensures that after the call returns at least minRows */
/* valid rows are available otherwise just reads and qualifies those */
/* that can be read immediately (without blocking) */
static void FillBufFromChannel(ParmBuf pbuf,int minRows)
{
IOConfig cf = pbuf->cf;
PBlock *pb,*lb;
Boolean dis,cleared;
char b1[100];
int availRows,newRows,space,i,head,tail,nShift;
short *sp1=NULL, *sp2;
float *fp1=NULL, *fp2;
/* Fill Buffer with converted static coef vectors */
newRows=FramesInChannel(pbuf,pbuf->chType);
/* Number of rows that we can read immediately */
...
/* Read the necessary frames */
for (i=0; i<newRows; i++) {
/* But have final check on read just in case */
if (pbuf->dShort) {
if (GetFrameFromChannel(pbuf,pbuf->chType,sp1)!=1) {
pbuf->chClear=TRUE;
break;
}
sp1 += cf->nCols;
}
else {
if (GetFrameFromChannel(pbuf,pbuf->chType,fp1)!=1) {
pbuf->chClear=TRUE;
break;
}
fp1 += cf->nCols;
}
pbuf->inRow++;pbuf->main.nRows++;
}
...
}
省略了很多检查和保证程序健壮性的代码,只保留最重要的逻辑,就是查询训练数据的有多少帧,并读取这些帧数据到内存空间中。
接下来分析 ReadAsTable(pbuf,i,&obs)、AccVar(obs)。
ReadAsTable函数调用ReadObs,它实现这个读取操作。
static void ReadObs(ParmBuf pbuf, int outRow,Observation *o)
{
int i,numS;
float *fp,*data;
short *sp;
PBlock *pb;
char b1[50],b2[50];
numS = o->swidth[0];
if (outRow>=pbuf->main.stRow) {
data=(float *) pbuf->main.data;
i=outRow-pbuf->main.stRow;
} else {
for (pb=pbuf->main.next;pb!=NULL;pb=pb->next)
if (pb->stRow+pb->nRows>outRow) break;
if (pb==NULL)
HError(6395,"ReadObs: Frame discarded from buffer");
data=(float *) pb->data;
i=outRow-pb->stRow;
}
fp = (float *)data + i*pbuf->cf->nCols;
ExtractObservation(fp,o);
}
主要看的就是最后两行,首先定位fp,然后调用ExtractObservation(fp, o)。
看一下它的主要代码:
/* ExtractObservation: copy vector of floats starting at fp into
observation, splitting into streams as necessary */
static void ExtractObservation(float *fp, Observation *o)
{
int i,j,k,n,w1,w2,w,nStatic = 0;
int numS = o->swidth[0];
Vector v,ev;
Boolean wantE,skipE;
ev = o->fv[numS];
for (i=1,k=1; i<numS; i++){
v = o->fv[i];
for (j=1; j<=o->swidth[i]; j++)
v[j] = *fp++;
if (wantE || i>1) ev[k++] = *fp;
fp++;
for (i=1,k=0; i<=numS; i++){
v = o->fv[i];
for (j=1; j<=o->swidth[i]; j++){
v[j] = *fp++; k++;
}
}
}
删除了些不重要的检查代码,只保留了最重要部分。其实就是最后四行,fp指定的位置值赋值给v[j],它实际上是o.fv[s]对应的位置。也是数据最终被读到的位置。接续来的处理,是从Observation对象o中提取数据,o.fv[s]赋给vector v,然后累计到acc中的相同维度的meanSum和squareSum中。
/* AccVar: update global accumulators with given observation */
void AccVar(Observation obs)
{
int x,y,s,V;
float val;
Vector v;
totalCount++;
for (s=1; s<=hset.swidth[0]; s++){
v = obs.fv[s]; V = hset.swidth[s];
for (x=1;x<=V;x++) {
val=v[x];
accs[s].meanSum[x] += val; /* accumulate mean */
if (fullcNeeded[s]) { /* accumulate covar */
accs[s].squareSum.inv[x][x] += val*val;
for (y=1;y<x;y++)
accs[s].squareSum.inv[x][y] += val*v[y];
} else /* accumulate var */
accs[s].squareSum.var[x] += val*val;
}
}
}
到此,数据累计完成了,剩下就是除以总样本数,也就是整个训练集中的特征向量个数。它由全局变量totalCount提供。
static long totalCount=0; /* total number of vector samples*/