Edinburgh Speech Tools  2.1-release
EST_viterbi.cc
Go to the documentation of this file.
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 using namespace std;
46 
47 static void init_paths_array(EST_VTPoint *n,int num_states);
48 static void debug_output_1(EST_VTPoint *p,EST_VTCandidate *c,
49  EST_VTPath *np, int i);
50 
52 {
53  int i;
54 
55  if (paths != 0) delete paths;
56  if (num_states != 0)
57  {
58  for (i=0; i<num_states; i++)
59  if (st_paths[i] != 0)
60  delete st_paths[i];
61  delete [] st_paths;
62  }
63  if (cands != 0) delete cands;
64  if (next != 0) delete next;
65 }
66 
68  : vit_a_big_number(1.0e10)
69 {
70  beam_width = 0;
71  cand_width = 0;
72  user_clist = a;
73  user_npath = b;
74  num_states = 0;
75 
76  do_pruning = FALSE;
77  overall_path_pruning_envelope_width = -1;
78  candidate_pruning_envelope_width = -1;
79 
80  debug = FALSE;
81  trace = FALSE;
82  big_is_good = TRUE; // for probabilities it is
83  timeline = 0;
84 }
85 
87  : vit_a_big_number(1.0e10)
88 {
89  beam_width = 0;
90  cand_width = 0;
91  user_clist = a;
92  user_npath = b;
93 
94  do_pruning = FALSE;
95  overall_path_pruning_envelope_width = -1;
96  candidate_pruning_envelope_width = -1;
97 
98  // if s == -1 number of states is determined by number of cands
99  // this is specific to a particular search but very efficient
100  num_states = s; // can do the lattice search more directly
101  debug = FALSE;
102  trace = FALSE;
103  big_is_good = TRUE; // for probabilities it is
104  timeline = 0;
105 }
106 
108 {
109  delete timeline;
110 }
111 
113 {
114  // Creates a time line with each point pointing at each item in p
115  EST_Item *i;
116  EST_VTPoint *t = 0,*n;
117 
118  for (i=p->head(); i != 0; i=i->next())
119  {
120  n = new EST_VTPoint;
121  n->s = i;
122  if (num_states > 0)
123  init_paths_array(n,num_states);
124  if (t == 0)
125  timeline = n;
126  else
127  t->next = n;
128  t=n;
129  }
130  // Extra one at the end for final paths
131  n = new EST_VTPoint;
132  if (num_states > 0)
133  init_paths_array(n,num_states);
134 
135  // Need init path on first point so a search can start
136  if (num_states == 0) // general search case
137  timeline->paths = new EST_VTPath;
138  if (num_states == -1)
139  init_paths_array(timeline,1); // a start point
140 
141  if (t == 0)
142  timeline = n; // its an empty stream
143  else
144  t->next = n;
145 }
146 
147 static void init_paths_array(EST_VTPoint *n,int num_states)
148 {
149  // Create the states array and initialize it
150  int j;
151 
152  n->num_states = num_states;
153  n->st_paths = new EST_VTPath*[num_states];
154  for (j=0;j<num_states;j++)
155  n->st_paths[j] = 0;
156 }
157 
158 int EST_Viterbi_Decoder::betterthan(const float a,const float b) const
159 {
160  // Some thing big is better, others want it to be as small as possible
161  // this just tells you if a is better than b by checking the variable
162  // in the decoder itself.
163 
164  // For probabilities big_is_good == TRUE (or log probabilities)
165 
166  if (big_is_good)
167  return (a > b);
168  else
169  return (a < b);
170 }
171 
172 static int init_dynamic_states(EST_VTPoint *p, EST_VTCandidate *cands)
173 {
174  // In a special (hmm maybe not so special), the number of "states"
175  // is the number of candidates
176  EST_VTCandidate *c;
177  int i;
178 
179  for (i=0, c=cands; c != 0; c=c->next,i++)
180  c->pos = i;
181  init_paths_array(p,i);
182 
183  return i;
184 }
185 
187  ob_beam)
188 {
189 
190  do_pruning = TRUE;
191  overall_path_pruning_envelope_width = beam;
192  candidate_pruning_envelope_width = ob_beam;
193 
194 }
195 
197 {
198  debug = TRUE;
199 }
200 
202 {
203  trace = TRUE;
204 }
205 
206 
208 {
209  // Searches for the best path
210  EST_VTPoint *p;
211  EST_VTPath *t,*np;
212  EST_VTCandidate *c;
213  int i=0;
214 
215  double best_score=0.0,score_cutoff=0.0;
216  double best_candidate_score=0.0,candidate_cutoff=0;
217  int dcount,pcount;
218  int cand_count=0, cands_considered=0;
219 
220  for (p=timeline; p->next != 0; p=p->next)
221  { // For each point in time
222  // Find the candidates
223  p->cands = (*user_clist)(p->s,f); // P(S|B)
224  if (do_pruning)
225  prune_initialize(p,best_score,best_candidate_score,
226  score_cutoff,candidate_cutoff,
227  cand_count);
228  if (num_states != 0) // true viterbi -- optimized for states
229  {
230  if (num_states == -1) // special case, dynamic state size
231  init_dynamic_states(p->next,p->cands);
232 
233  cands_considered=0; // moved by simonk
234  for (i=0; i<p->num_states; i++)
235  { // for each path up to here
236  //cands_considered=0;
237  if (((p == timeline) && i==0) || (p->st_paths[i] != 0))
238  for (c=p->cands; c != 0; c=c->next)
239  {
240  // for each new candidate
241  // candidate pruning (a.k.a. observation pruning)
242  if(!do_pruning ||
243  betterthan(c->score,candidate_cutoff))
244  {
245  cands_considered++;
246  // Find path extension costs
247  np = (*user_npath)(p->st_paths[i],c,f);
248  if (debug) debug_output_1(p,c,np,i);
249  if (do_pruning && betterthan(np->score,best_score))
250  {
251  best_score = np->score;
252  if(big_is_good)
253  score_cutoff = best_score
254  - overall_path_pruning_envelope_width;
255  else
256  score_cutoff = best_score
257  + overall_path_pruning_envelope_width;
258  }
259  // can prune here, although score_cutoff will
260  // be generally too generous at this point.
261  // It's unclear whether this pruning takes more
262  // time than it saves !
263  if(!do_pruning||betterthan(np->score,score_cutoff))
264  vit_add_paths(p->next,np);
265  else
266  delete np;
267  }
268  }
269  }
270 
271  if (do_pruning)
272  {
273  if(big_is_good)
274  score_cutoff =
275  best_score - overall_path_pruning_envelope_width;
276  else
277  score_cutoff =
278  best_score + overall_path_pruning_envelope_width;
279  if(trace)
280  {
281  cerr << "Considered " << cands_considered << " of ";
282  cerr << cand_count*p->num_states << " candidate paths" << endl;
283  cerr << "FRAME: best score " << best_score;
284  cerr << " score cutoff " << score_cutoff << endl;
285  cerr << " best candidate score " << best_candidate_score;
286  cerr << " candidate cutoff " << candidate_cutoff << endl;
287  }
288 
289  dcount=0; pcount=0;
290  for (i=0; i<p->next->num_states; i++)
291  if(p->next->st_paths[i] != 0)
292  {
293  pcount++;
294  if(!betterthan(p->next->st_paths[i]->score,
295  score_cutoff))
296  {
297  delete p->next->st_paths[i];
298  p->next->st_paths[i] = 0;
299  dcount++;
300  }
301  }
302  if(trace)
303  {
304  cerr << "Pruned " << dcount << " of " << pcount;
305  cerr << " paths" << endl << endl;
306  }
307  }
308  }
309  else // general beam search
310  for (t=p->paths; t != 0; t=t->next)
311  { // for each path up to here
312  for (c=p->cands; c != 0; c=c->next)
313  { // for each new candidate
314  np = (*user_npath)(t,c,f);
315  add_path(p->next,np); // be a little cleverer
316  }
317  }
318  if (debug) fprintf(stdout,"\n");
319  }
320 
321 }
322 
323 void EST_Viterbi_Decoder::
324  prune_initialize(EST_VTPoint *p,
325  double &best_score, double &best_candidate_score,
326  double &score_cutoff, double &candidate_cutoff,
327  int &cand_count)
328 { // Find best candidate, count them and set some vars.
329  EST_VTCandidate *c;
330  if (big_is_good)
331  {
332  best_score = -vit_a_big_number;
333  best_candidate_score = -vit_a_big_number;
334  score_cutoff = -vit_a_big_number;
335  candidate_cutoff = - candidate_pruning_envelope_width;
336  }
337  else
338  {
339  best_candidate_score = vit_a_big_number;
340  best_score = vit_a_big_number;
341  score_cutoff = vit_a_big_number;
342  candidate_cutoff = candidate_pruning_envelope_width;
343  }
344 
345  for (cand_count=0,c=p->cands; c; c=c->next,cand_count++)
346  if (betterthan(c->score,best_candidate_score))
347  best_candidate_score = c->score;
348  candidate_cutoff += best_candidate_score;
349 }
350 
351 static void debug_output_1(EST_VTPoint *p,EST_VTCandidate *c,
352  EST_VTPath *np,int i)
353 {
354  printf("%s: ",(const char *)p->s->name());
355  cout << c->name;
356  printf(" %1.3f B %1.3f (%1.3f) st %d s %1.3f ",
357  np->c->score,
358  (np->c->score==0 ? 0 :
359  ((float)np->f("lscore"))/np->c->score),
360  (float)np->f("lscore"),np->state,
361  np->score);
362  if (p->st_paths[i] == 0)
363  cout << "(I)" << endl;
364  else
365  cout << p->st_paths[i]->c->name << endl;
366 }
367 
368 void EST_Viterbi_Decoder::vit_add_paths(EST_VTPoint *pt, EST_VTPath *np)
369 {
370  // Add set of paths
371  EST_VTPath *p,*next_p;
372 
373  for (p=np; p != 0; p=next_p)
374  {
375  next_p = p->next; // need to save this as p could be deleted
376  vit_add_path(pt,p);
377  }
378 
379 }
380 void EST_Viterbi_Decoder::vit_add_path(EST_VTPoint *p, EST_VTPath *np)
381 {
382  // We are doing true viterbi so we need only keep the best
383  // path for each state. This means we can index into the
384  // array of paths ending at P and only keep np if its score
385  // is better than any other path of that state
386 
387  if ((np->state < 0) || (np->state > p->num_states))
388  {
389  cerr << "EST_Viterbi: state too big (" << np->state << ")" << endl;
390  }
391  else if ((p->st_paths[np->state] == 0) ||
392  (betterthan(np->score,p->st_paths[np->state]->score)))
393  {
394  // This new one is better so replace it
395  if (p->st_paths[np->state] != 0)
396  delete p->st_paths[np->state];
397  p->st_paths[np->state] = np;
398  }
399  else
400  delete np;
401 }
402 
403 bool EST_Viterbi_Decoder::vit_prune_path(double path_score, double score_cutoff)
404 {
405 
406  // a bit of a simple function !!
407 
408  // if it falls below cutoff, then prune point
409  // typically will only be one path at this point anyway (Viterbi)
410  if(!betterthan(path_score,score_cutoff))
411  return TRUE;
412 
413  return FALSE;
414 }
415 
416 
417 
418 void EST_Viterbi_Decoder::add_path(EST_VTPoint *p, EST_VTPath *np)
419 {
420  // Add new path to point, Prunes as appropriate to strategy
421  EST_VTPath *pp;
422 
423  if (p == 0)
424  cerr << "Viterbi: tried to add path to NULL point\n";
425  else
426  {
427  if ((beam_width == 0) || // Add if no beam restrictions or
428  (p->num_paths < beam_width) || // beam not filled or
429  (betterthan(np->score,p->paths->score)))//this is better than best
430 // (np->score > p->paths->score)) // this is better than best
431  {
432  EST_VTPath **l = &p->paths;
433  EST_VTPath *a;
434 
435  for (a=p->paths; /* scary */ ; a=a->next)
436  {
437  if ((a == 0) || (betterthan(a->score,np->score)))
438  { // Add new path here
439  np->next = a;
440  *l = np;
441  p->num_paths++;
442  break;
443  }
444  l = &a->next;
445  }
446  // Prune the first one of the list
447  if ((beam_width > 0) &&
448  (p->num_paths > beam_width))
449  {
450  pp = p->paths;
451  p->paths = pp->next;
452  pp->next = 0;
453  p->num_paths--;
454  delete pp;
455  }
456  }
457  else
458  {
459  delete np; // failed to make it
460  }
461  }
462 
463 }
464 
466  EST_VTCandidate *allcands)
467 {
468  // Add newcand to allcand, in score order and prune it to
469  // cand_width, delete newcand if its not good enough
470  EST_VTCandidate *newlist = allcands;
471  EST_VTCandidate *pp;
472  int numcands;
473 
474  if (allcands == NULL)
475  numcands = 0;
476  else
477  numcands = allcands->pos;
478 
479  if ((cand_width == 0) || // Add if no candbeam restrictions or
480  (numcands < cand_width) || // candbeam not filled or
481  (allcands == NULL || betterthan(newcand->score,allcands->score))) //this better than best
482  {
483  EST_VTCandidate **l = &newlist;
484  EST_VTCandidate *a;
485 
486  for (a=newlist; /* scary */ ; a=a->next)
487  {
488  if ((a == NULL) || (betterthan(a->score,newcand->score)))
489  { // Add new path here
490  newcand->next = a;
491  *l = newcand;
492  numcands++;
493  break;
494  }
495  l = &a->next;
496  }
497  // Prune the first one off the list
498  if ((cand_width > 0) &&
499  (numcands > cand_width))
500  {
501  pp = newlist;
502  newlist = pp->next;
503  pp->next = 0;
504  numcands--;
505  delete pp;
506  }
507  }
508  else
509  delete newcand; // failed to make it
510 
511  newlist->pos = numcands;
512  return newlist;
513 }
514 
516 {
517  // Finds best path through the search space (previously searched)
518  // Adds field to the EST_Items, named n, with chosen value
519  EST_VTPath *p;
520 
521  if ((timeline == 0) || (timeline->next == 0))
522  return TRUE; // it's an empty list so it has succeeded
523  p = find_best_end();
524  if (p == 0)
525  return FALSE; // there isn't any answer
526 
527  for (; p != 0; p=p->from)
528  {
529  // Hmm need the original EST_Item
530  if (p->c != 0)
531  {
532  p->c->s->set_val(n,p->c->name);
533  p->c->s->set(n+"_score",p->f.F("lscore",0.0));
534  }
535  }
536  return TRUE;
537 }
538 
540 {
541  // Finds best path through the search space (previously searched)
542  *bestPathEnd = 0;
543 
544  if ((timeline == 0) || (timeline->next == 0))
545  return TRUE; // it's an empty list so it has succeeded
546 
547  *bestPathEnd = find_best_end();
548 
549  if (*bestPathEnd == 0)
550  return FALSE; // there isn't any answer
551 
552  return TRUE;
553 }
554 
555 
557 {
558  // Copy feature from path to related stream
559  EST_VTPath *p;
560 
561  p = find_best_end();
562  if(p == 0)
563  return;
564 
565  for (; p != 0; p=p->from)
566  {
567  // Hmm need the original EST_Item
568  if ((p->c != 0) && (p->f.present(n)))
569  p->c->s->set_val(n,p->f(n));
570  }
571 }
572 
573 EST_VTPath *EST_Viterbi_Decoder::find_best_end() const
574 {
575  EST_VTPoint *t;
576  double best,worst;
577  EST_VTPath *p, *best_p=0;
578  int i;
579  // I'd like to use HUGE_VAL or FLT_MAX for this but its not portable
580  // (on alphas)
581 
582  if (big_is_good)
583  worst = -vit_a_big_number; // worst possible;
584  else
585  worst = vit_a_big_number; // worst possible;
586  best = worst; // hopefully we can find something better;
587 
588  for (i=0,t=timeline; t->next != 0; t=t->next,i++)
589  {
590  if ((t->num_states == 0) && (t->num_paths == 0))
591  {
592  cerr << "No paths at frame " << i << " " << t->s->name() << endl;
593  return 0;
594  }
595  }
596 
597  if (num_states != 0)
598  for (i=0; i<t->num_states; i++)
599  {
600  if ((t->st_paths[i] != 0) &&
601  (betterthan(t->st_paths[i]->score,best)))
602  {
603  best = t->st_paths[i]->score;
604  best_p = t->st_paths[i];
605  }
606  }
607  else
608  for (p=t->paths; p!=0; p=p->next)
609  {
610  if (betterthan(p->score,best))
611  {
612  best = p->score;
613  best_p = p;
614  }
615  }
616 
617 
618  if (debug)
619  {
620  if (best == worst)
621  cerr << "Failed to find path" << endl;
622  cout << "Best score is " << best << endl;
623  }
624 
625  return best_p;
626 }
627 
EST_Item * head() const
Definition: EST_Relation.h:121
EST_VTPath *(* unpath_f_t)(EST_VTPath *p, EST_VTCandidate *c, EST_Features &f)
Definition: EST_viterbi.h:112
const EST_String name() const
Definition: EST_Item.h:250
EST_VTCandidate * cands
Definition: EST_viterbi.h:105
EST_VTPoint * next
Definition: EST_viterbi.h:108
void set(const EST_String &name, ssize_t ival)
Definition: EST_Item.h:185
EST_VTPath ** st_paths
Definition: EST_viterbi.h:107
bool result(const EST_String &n)
Definition: EST_viterbi.cc:515
void copy_feature(const EST_String &n)
Copy named feature from the best path to related stream item.
Definition: EST_viterbi.cc:556
bool vit_prune_path(double path_score, double score_cutoff)
Definition: EST_viterbi.cc:403
double score
Definition: EST_viterbi.h:83
EST_VTCandidate *(* uclist_f_t)(EST_Item *s, EST_Features &f)
Definition: EST_viterbi.h:111
const double vit_a_big_number
Unfortunately using MAX_DOUBLE doesn&#39;t do the right thing (e.g. comparison don&#39;t work with MAX_DOUBLE...
Definition: EST_viterbi.h:173
void search(void)
Do the the actual search.
Definition: EST_viterbi.cc:207
void set_pruning_parameters(float beam, float ob_beam)
set beam widths for pruning
Definition: EST_viterbi.cc:186
void initialise(EST_Relation *r)
Build the initial table from a EST_Relation.
Definition: EST_viterbi.cc:112
EST_Item * s
Definition: EST_viterbi.h:102
EST_Viterbi_Decoder(uclist_f_t a, unpath_f_t b)
Definition: EST_viterbi.cc:67
void set_val(const EST_String &name, const EST_Val &sval)
Definition: EST_Item.h:220
#define FALSE
Definition: EST_bool.h:119
EST_VTPath * from
Definition: EST_viterbi.h:87
EST_Features f
For holding values to pass to user called functions.
Definition: EST_viterbi.h:168
NULL
Definition: EST_WFST.cc:55
int present(const EST_String &name) const
EST_Item * s
Definition: EST_viterbi.h:67
EST_Features f
Definition: EST_viterbi.h:85
EST_Item * next() const
Definition: EST_Item.h:348
EST_VTCandidate * add_cand_prune(EST_VTCandidate *newcand, EST_VTCandidate *allcands)
Definition: EST_viterbi.cc:465
EST_VTCandidate * next
Definition: EST_viterbi.h:68
EST_VTCandidate * c
Definition: EST_viterbi.h:86
EST_VTPath * next
Definition: EST_viterbi.h:88
EST_VTPath * paths
Definition: EST_viterbi.h:106
#define TRUE
Definition: EST_bool.h:118
float F(const EST_String &path) const
Definition: EST_Features.h:136