Edinburgh Speech Tools  2.4-release
 All Classes Functions Variables Typedefs Enumerations Enumerator Friends Pages
EST_viterbi.cc
1 /*************************************************************************/
2 /* */
3 /* Centre for Speech Technology Research */
4 /* University of Edinburgh, UK */
5 /* Copyright (c) 1996,1997 */
6 /* All Rights Reserved. */
7 /* */
8 /* Permission is hereby granted, free of charge, to use and distribute */
9 /* this software and its documentation without restriction, including */
10 /* without limitation the rights to use, copy, modify, merge, publish, */
11 /* distribute, sublicense, and/or sell copies of this work, and to */
12 /* permit persons to whom this work is furnished to do so, subject to */
13 /* the following conditions: */
14 /* 1. The code must retain the above copyright notice, this list of */
15 /* conditions and the following disclaimer. */
16 /* 2. Any modifications must be clearly marked as such. */
17 /* 3. Original authors' names are not deleted. */
18 /* 4. The authors' names are not used to endorse or promote products */
19 /* derived from this software without specific prior written */
20 /* permission. */
21 /* */
22 /* THE UNIVERSITY OF EDINBURGH AND THE CONTRIBUTORS TO THIS WORK */
23 /* DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING */
24 /* ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT */
25 /* SHALL THE UNIVERSITY OF EDINBURGH NOR THE CONTRIBUTORS BE LIABLE */
26 /* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES */
27 /* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN */
28 /* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, */
29 /* ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF */
30 /* THIS SOFTWARE. */
31 /* */
32 /*************************************************************************/
33 /* Authors: Alan W Black */
34 /* Date : July 1996 */
35 /*-----------------------------------------------------------------------*/
36 /* A viterbi decoder */
37 /* */
38 /* User provides the candidates, target and combine score function */
39 /* and it searches for the best path through the candidates */
40 /* */
41 /*=======================================================================*/
42 #include <cstdio>
43 #include "EST_viterbi.h"
44 
45 static void init_paths_array(EST_VTPoint *n,int num_states);
46 static void debug_output_1(EST_VTPoint *p,EST_VTCandidate *c,
47  EST_VTPath *np, int i);
48 
49 EST_VTPoint::~EST_VTPoint()
50 {
51  int i;
52 
53  if (paths != 0) delete paths;
54  if (num_states != 0)
55  {
56  for (i=0; i<num_states; i++)
57  if (st_paths[i] != 0)
58  delete st_paths[i];
59  delete [] st_paths;
60  }
61  if (cands != 0) delete cands;
62  if (next != 0) delete next;
63 }
64 
65 EST_Viterbi_Decoder::EST_Viterbi_Decoder(uclist_f_t a, unpath_f_t b)
66  : vit_a_big_number(1.0e10)
67 {
68  beam_width = 0;
69  cand_width = 0;
70  user_clist = a;
71  user_npath = b;
72  num_states = 0;
73 
74  do_pruning = FALSE;
75  overall_path_pruning_envelope_width = -1;
76  candidate_pruning_envelope_width = -1;
77 
78  debug = FALSE;
79  trace = FALSE;
80  big_is_good = TRUE; // for probabilities it is
81 }
82 
83 EST_Viterbi_Decoder::EST_Viterbi_Decoder(uclist_f_t a, unpath_f_t b, int s)
84  : vit_a_big_number(1.0e10)
85 {
86  beam_width = 0;
87  cand_width = 0;
88  user_clist = a;
89  user_npath = b;
90 
91  do_pruning = FALSE;
92  overall_path_pruning_envelope_width = -1;
93  candidate_pruning_envelope_width = -1;
94 
95  // if s == -1 number of states is determined by number of cands
96  // this is specific to a particular search but very efficient
97  num_states = s; // can do the lattice search more directly
98  debug = FALSE;
99  trace = FALSE;
100  big_is_good = TRUE; // for probabilities it is
101 }
102 
103 EST_Viterbi_Decoder::~EST_Viterbi_Decoder()
104 {
105  delete timeline;
106 }
107 
109 {
110  // Creates a time line with each point pointing at each item in p
111  EST_Item *i;
112  EST_VTPoint *t = 0,*n;
113 
114  for (i=p->head(); i != 0; i=i->next())
115  {
116  n = new EST_VTPoint;
117  n->s = i;
118  if (num_states > 0)
119  init_paths_array(n,num_states);
120  if (t == 0)
121  timeline = n;
122  else
123  t->next = n;
124  t=n;
125  }
126  // Extra one at the end for final paths
127  n = new EST_VTPoint;
128  if (num_states > 0)
129  init_paths_array(n,num_states);
130 
131  // Need init path on first point so a search can start
132  if (num_states == 0) // general search case
133  timeline->paths = new EST_VTPath;
134  if (num_states == -1)
135  init_paths_array(timeline,1); // a start point
136 
137  if (t == 0)
138  timeline = n; // its an empty stream
139  else
140  t->next = n;
141 }
142 
143 static void init_paths_array(EST_VTPoint *n,int num_states)
144 {
145  // Create the states array and initialize it
146  int j;
147 
148  n->num_states = num_states;
149  n->st_paths = new EST_VTPath*[num_states];
150  for (j=0;j<num_states;j++)
151  n->st_paths[j] = 0;
152 }
153 
154 const int EST_Viterbi_Decoder::betterthan(const float a,const float b) const
155 {
156  // Some thing big is better, others want it to be as small as possible
157  // this just tells you if a is better than b by checking the variable
158  // in the decoder itself.
159 
160  // For probabilities big_is_good == TRUE (or log probabilities)
161 
162  if (big_is_good)
163  return (a > b);
164  else
165  return (a < b);
166 }
167 
168 static int init_dynamic_states(EST_VTPoint *p, EST_VTCandidate *cands)
169 {
170  // In a special (hmm maybe not so special), the number of "states"
171  // is the number of candidates
172  EST_VTCandidate *c;
173  int i;
174 
175  for (i=0, c=cands; c != 0; c=c->next,i++)
176  c->pos = i;
177  init_paths_array(p,i);
178 
179  return i;
180 }
181 
183  ob_beam)
184 {
185 
186  do_pruning = TRUE;
187  overall_path_pruning_envelope_width = beam;
188  candidate_pruning_envelope_width = ob_beam;
189 
190 }
191 
192 void EST_Viterbi_Decoder::turn_on_debug()
193 {
194  debug = TRUE;
195 }
196 
197 void EST_Viterbi_Decoder::turn_on_trace()
198 {
199  trace = TRUE;
200 }
201 
202 
204 {
205  // Searches for the best path
206  EST_VTPoint *p;
207  EST_VTPath *t,*np;
208  EST_VTCandidate *c;
209  int i=0;
210 
211  double best_score=0.0,score_cutoff=0.0;
212  double best_candidate_score=0.0,candidate_cutoff=0;
213  int dcount,pcount;
214  int cand_count=0, cands_considered=0;
215 
216  for (p=timeline; p->next != 0; p=p->next)
217  { // For each point in time
218  // Find the candidates
219  p->cands = (*user_clist)(p->s,f); // P(S|B)
220  if (do_pruning)
221  prune_initialize(p,best_score,best_candidate_score,
222  score_cutoff,candidate_cutoff,
223  cand_count);
224  if (num_states != 0) // true viterbi -- optimized for states
225  {
226  if (num_states == -1) // special case, dynamic state size
227  init_dynamic_states(p->next,p->cands);
228 
229  cands_considered=0; // moved by simonk
230  for (i=0; i<p->num_states; i++)
231  { // for each path up to here
232  //cands_considered=0;
233  if (((p == timeline) && i==0) || (p->st_paths[i] != 0))
234  for (c=p->cands; c != 0; c=c->next)
235  {
236  // for each new candidate
237  // candidate pruning (a.k.a. observation pruning)
238  if(!do_pruning ||
239  betterthan(c->score,candidate_cutoff))
240  {
241  cands_considered++;
242  // Find path extension costs
243  np = (*user_npath)(p->st_paths[i],c,f);
244  if (debug) debug_output_1(p,c,np,i);
245  if (do_pruning && betterthan(np->score,best_score))
246  {
247  best_score = np->score;
248  if(big_is_good)
249  score_cutoff = best_score
250  - overall_path_pruning_envelope_width;
251  else
252  score_cutoff = best_score
253  + overall_path_pruning_envelope_width;
254  }
255  // can prune here, although score_cutoff will
256  // be generally too generous at this point.
257  // It's unclear whether this pruning takes more
258  // time than it saves !
259  if(!do_pruning||betterthan(np->score,score_cutoff))
260  vit_add_paths(p->next,np);
261  else
262  delete np;
263  }
264  }
265  }
266 
267  if (do_pruning)
268  {
269  if(big_is_good)
270  score_cutoff =
271  best_score - overall_path_pruning_envelope_width;
272  else
273  score_cutoff =
274  best_score + overall_path_pruning_envelope_width;
275  if(trace)
276  {
277  cerr << "Considered " << cands_considered << " of ";
278  cerr << cand_count*p->num_states << " candidate paths" << endl;
279  cerr << "FRAME: best score " << best_score;
280  cerr << " score cutoff " << score_cutoff << endl;
281  cerr << " best candidate score " << best_candidate_score;
282  cerr << " candidate cutoff " << candidate_cutoff << endl;
283  }
284 
285  dcount=0; pcount=0;
286  for (i=0; i<p->next->num_states; i++)
287  if(p->next->st_paths[i] != 0)
288  {
289  pcount++;
290  if(!betterthan(p->next->st_paths[i]->score,
291  score_cutoff))
292  {
293  delete p->next->st_paths[i];
294  p->next->st_paths[i] = 0;
295  dcount++;
296  }
297  }
298  if(trace)
299  {
300  cerr << "Pruned " << dcount << " of " << pcount;
301  cerr << " paths" << endl << endl;
302  }
303  }
304  }
305  else // general beam search
306  for (t=p->paths; t != 0; t=t->next)
307  { // for each path up to here
308  for (c=p->cands; c != 0; c=c->next)
309  { // for each new candidate
310  np = (*user_npath)(t,c,f);
311  add_path(p->next,np); // be a little cleverer
312  }
313  }
314  if (debug) fprintf(stdout,"\n");
315  }
316 
317 }
318 
319 void EST_Viterbi_Decoder::
320  prune_initialize(EST_VTPoint *p,
321  double &best_score, double &best_candidate_score,
322  double &score_cutoff, double &candidate_cutoff,
323  int &cand_count)
324 { // Find best candidate, count them and set some vars.
325  EST_VTCandidate *c;
326  if (big_is_good)
327  {
328  best_score = -vit_a_big_number;
329  best_candidate_score = -vit_a_big_number;
330  score_cutoff = -vit_a_big_number;
331  candidate_cutoff = - candidate_pruning_envelope_width;
332  }
333  else
334  {
335  best_candidate_score = vit_a_big_number;
336  best_score = vit_a_big_number;
337  score_cutoff = vit_a_big_number;
338  candidate_cutoff = candidate_pruning_envelope_width;
339  }
340 
341  for (cand_count=0,c=p->cands; c; c=c->next,cand_count++)
342  if (betterthan(c->score,best_candidate_score))
343  best_candidate_score = c->score;
344  candidate_cutoff += best_candidate_score;
345 }
346 
347 static void debug_output_1(EST_VTPoint *p,EST_VTCandidate *c,
348  EST_VTPath *np,int i)
349 {
350  printf("%s: ",(const char *)p->s->name());
351  cout << c->name;
352  printf(" %1.3f B %1.3f (%1.3f) st %d s %1.3f ",
353  np->c->score,
354  (np->c->score==0 ? 0 :
355  ((float)np->f("lscore"))/np->c->score),
356  (float)np->f("lscore"),np->state,
357  np->score);
358  if (p->st_paths[i] == 0)
359  cout << "(I)" << endl;
360  else
361  cout << p->st_paths[i]->c->name << endl;
362 }
363 
364 void EST_Viterbi_Decoder::vit_add_paths(EST_VTPoint *pt, EST_VTPath *np)
365 {
366  // Add set of paths
367  EST_VTPath *p,*next_p;
368 
369  for (p=np; p != 0; p=next_p)
370  {
371  next_p = p->next; // need to save this as p could be deleted
372  vit_add_path(pt,p);
373  }
374 
375 }
376 void EST_Viterbi_Decoder::vit_add_path(EST_VTPoint *p, EST_VTPath *np)
377 {
378  // We are doing true viterbi so we need only keep the best
379  // path for each state. This means we can index into the
380  // array of paths ending at P and only keep np if its score
381  // is better than any other path of that state
382 
383  if ((np->state < 0) || (np->state > p->num_states))
384  {
385  cerr << "EST_Viterbi: state too big (" << np->state << ")" << endl;
386  }
387  else if ((p->st_paths[np->state] == 0) ||
388  (betterthan(np->score,p->st_paths[np->state]->score)))
389  {
390  // This new one is better so replace it
391  if (p->st_paths[np->state] != 0)
392  delete p->st_paths[np->state];
393  p->st_paths[np->state] = np;
394  }
395  else
396  delete np;
397 }
398 
399 bool EST_Viterbi_Decoder::vit_prune_path(double path_score, double score_cutoff)
400 {
401 
402  // a bit of a simple function !!
403 
404  // if it falls below cutoff, then prune point
405  // typically will only be one path at this point anyway (Viterbi)
406  if(!betterthan(path_score,score_cutoff))
407  return TRUE;
408 
409  return FALSE;
410 }
411 
412 
413 
414 void EST_Viterbi_Decoder::add_path(EST_VTPoint *p, EST_VTPath *np)
415 {
416  // Add new path to point, Prunes as appropriate to strategy
417  EST_VTPath *pp;
418 
419  if (p == 0)
420  cerr << "Viterbi: tried to add path to NULL point\n";
421  else
422  {
423  if ((beam_width == 0) || // Add if no beam restrictions or
424  (p->num_paths < beam_width) || // beam not filled or
425  (betterthan(np->score,p->paths->score)))//this is better than best
426 // (np->score > p->paths->score)) // this is better than best
427  {
428  EST_VTPath **l = &p->paths;
429  EST_VTPath *a;
430 
431  for (a=p->paths; /* scary */ ; a=a->next)
432  {
433  if ((a == 0) || (betterthan(a->score,np->score)))
434  { // Add new path here
435  np->next = a;
436  *l = np;
437  p->num_paths++;
438  break;
439  }
440  l = &a->next;
441  }
442  // Prune the first one of the list
443  if ((beam_width > 0) &&
444  (p->num_paths > beam_width))
445  {
446  pp = p->paths;
447  p->paths = pp->next;
448  pp->next = 0;
449  p->num_paths--;
450  delete pp;
451  }
452  }
453  else
454  {
455  delete np; // failed to make it
456  }
457  }
458 
459 }
460 
462  EST_VTCandidate *allcands)
463 {
464  // Add newcand to allcand, in score order and prune it to
465  // cand_width, delete newcand if its not good enough
466  EST_VTCandidate *newlist = allcands;
467  EST_VTCandidate *pp;
468  int numcands;
469 
470  if (allcands == 0)
471  numcands = 0;
472  else
473  numcands = allcands->pos;
474 
475  if ((cand_width == 0) || // Add if no candbeam restrictions or
476  (numcands < cand_width) || // candbeam not filled or
477  (betterthan(newcand->score,allcands->score))) //this better than best
478  {
479  EST_VTCandidate **l = &newlist;
480  EST_VTCandidate *a;
481 
482  for (a=newlist; /* scary */ ; a=a->next)
483  {
484  if ((a == 0) || (betterthan(a->score,newcand->score)))
485  { // Add new path here
486  newcand->next = a;
487  *l = newcand;
488  numcands++;
489  break;
490  }
491  l = &a->next;
492  }
493  // Prune the first one off the list
494  if ((cand_width > 0) &&
495  (numcands > cand_width))
496  {
497  pp = newlist;
498  newlist = pp->next;
499  pp->next = 0;
500  numcands--;
501  delete pp;
502  }
503  }
504  else
505  delete newcand; // failed to make it
506 
507  newlist->pos = numcands;
508  return newlist;
509 }
510 
512 {
513  // Finds best path through the search space (previously searched)
514  // Adds field to the EST_Items, named n, with chosen value
515  EST_VTPath *p;
516 
517  if ((timeline == 0) || (timeline->next == 0))
518  return TRUE; // it's an empty list so it has succeeded
519  p = find_best_end();
520  if (p == 0)
521  return FALSE; // there isn't any answer
522 
523  for (; p != 0; p=p->from)
524  {
525  // Hmm need the original EST_Item
526  if (p->c != 0)
527  {
528  p->c->s->set_val(n,p->c->name);
529  p->c->s->set(n+"_score",p->f.F("lscore",0.0));
530  }
531  }
532  return TRUE;
533 }
534 
536 {
537  // Finds best path through the search space (previously searched)
538  *bestPathEnd = 0;
539 
540  if ((timeline == 0) || (timeline->next == 0))
541  return TRUE; // it's an empty list so it has succeeded
542 
543  *bestPathEnd = find_best_end();
544 
545  if (*bestPathEnd == 0)
546  return FALSE; // there isn't any answer
547 
548  return TRUE;
549 }
550 
551 
553 {
554  // Copy feature from path to related stream
555  EST_VTPath *p;
556 
557  p = find_best_end();
558  if(p == 0)
559  return;
560 
561  for (; p != 0; p=p->from)
562  {
563  // Hmm need the original EST_Item
564  if ((p->c != 0) && (p->f.present(n)))
565  p->c->s->set_val(n,p->f(n));
566  }
567 }
568 
569 EST_VTPath *EST_Viterbi_Decoder::find_best_end() const
570 {
571  EST_VTPoint *t;
572  double best,worst;
573  EST_VTPath *p, *best_p=0;
574  int i;
575  // I'd like to use HUGE_VAL or FLT_MAX for this but its not portable
576  // (on alphas)
577 
578  if (big_is_good)
579  worst = -vit_a_big_number; // worst possible;
580  else
581  worst = vit_a_big_number; // worst possible;
582  best = worst; // hopefully we can find something better;
583 
584  for (i=0,t=timeline; t->next != 0; t=t->next,i++)
585  {
586  if ((t->num_states == 0) && (t->num_paths == 0))
587  {
588  cerr << "No paths at frame " << i << " " << t->s->name() << endl;
589  return 0;
590  }
591  }
592 
593  if (num_states != 0)
594  for (i=0; i<t->num_states; i++)
595  {
596  if ((t->st_paths[i] != 0) &&
597  (betterthan(t->st_paths[i]->score,best)))
598  {
599  best = t->st_paths[i]->score;
600  best_p = t->st_paths[i];
601  }
602  }
603  else
604  for (p=t->paths; p!=0; p=p->next)
605  {
606  if (betterthan(p->score,best))
607  {
608  best = p->score;
609  best_p = p;
610  }
611  }
612 
613 
614  if (debug)
615  {
616  if (best == worst)
617  cerr << "Failed to find path" << endl;
618  cout << "Best score is " << best << endl;
619  }
620 
621  return best_p;
622 }
623