PocketSphinx 5prealpha
ps_lattice.c
Go to the documentation of this file.
1/* -*- c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/* ====================================================================
3 * Copyright (c) 2008 Carnegie Mellon University. All rights
4 * reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 *
18 * This work was supported in part by funding from the Defense Advanced
19 * Research Projects Agency and the National Science Foundation of the
20 * United States of America, and the CMU Sphinx Speech Consortium.
21 *
22 * THIS SOFTWARE IS PROVIDED BY CARNEGIE MELLON UNIVERSITY ``AS IS'' AND
23 * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY
26 * NOR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 *
34 * ====================================================================
35 *
36 */
37
42/* System headers. */
43#include <assert.h>
44#include <string.h>
45#include <math.h>
46
47/* SphinxBase headers. */
48#include <sphinxbase/ckd_alloc.h>
49#include <sphinxbase/listelem_alloc.h>
50#include <sphinxbase/strfuncs.h>
51#include <sphinxbase/err.h>
52#include <sphinxbase/pio.h>
53
54/* Local headers. */
56#include "ps_lattice_internal.h"
57#include "ngram_search.h"
58#include "dict.h"
59
60/*
61 * Create a directed link between "from" and "to" nodes, but if a link already exists,
62 * choose one with the best ascr.
63 */
64void
66 int32 score, int32 ef)
67{
68 latlink_list_t *fwdlink;
69
70 /* Look for an existing link between "from" and "to" nodes */
71 for (fwdlink = from->exits; fwdlink; fwdlink = fwdlink->next)
72 if (fwdlink->link->to == to)
73 break;
74
75 if (fwdlink == NULL) {
76 latlink_list_t *revlink;
77 ps_latlink_t *link;
78
79 /* No link between the two nodes; create a new one */
80 link = listelem_malloc(dag->latlink_alloc);
81 fwdlink = listelem_malloc(dag->latlink_list_alloc);
82 revlink = listelem_malloc(dag->latlink_list_alloc);
83
84 link->from = from;
85 link->to = to;
86 link->ascr = score;
87 link->ef = ef;
88 link->best_prev = NULL;
89
90 fwdlink->link = revlink->link = link;
91 fwdlink->next = from->exits;
92 from->exits = fwdlink;
93 revlink->next = to->entries;
94 to->entries = revlink;
95 }
96 else {
97 /* Link already exists; just retain the best ascr */
98 if (score BETTER_THAN fwdlink->link->ascr) {
99 fwdlink->link->ascr = score;
100 fwdlink->link->ef = ef;
101 }
102 }
103}
104
105void
106ps_lattice_penalize_fillers(ps_lattice_t *dag, int32 silpen, int32 fillpen)
107{
108 ps_latnode_t *node;
109
110 for (node = dag->nodes; node; node = node->next) {
111 latlink_list_t *linklist;
112 if (node != dag->start && node != dag->end && dict_filler_word(dag->dict, node->basewid)) {
113 for (linklist = node->entries; linklist; linklist = linklist->next)
114 linklist->link->ascr += (node->basewid == dag->silence) ? silpen : fillpen;
115 }
116 }
117}
118
119static void
120delete_node(ps_lattice_t *dag, ps_latnode_t *node)
121{
122 latlink_list_t *x, *next_x;
123
124 for (x = node->exits; x; x = next_x) {
125 next_x = x->next;
126 x->link->from = NULL;
127 listelem_free(dag->latlink_list_alloc, x);
128 }
129 for (x = node->entries; x; x = next_x) {
130 next_x = x->next;
131 x->link->to = NULL;
132 listelem_free(dag->latlink_list_alloc, x);
133 }
134 listelem_free(dag->latnode_alloc, node);
135}
136
137
138static void
139remove_dangling_links(ps_lattice_t *dag, ps_latnode_t *node)
140{
141 latlink_list_t *x, *prev_x, *next_x;
142
143 prev_x = NULL;
144 for (x = node->exits; x; x = next_x) {
145 next_x = x->next;
146 if (x->link->to == NULL) {
147 if (prev_x)
148 prev_x->next = next_x;
149 else
150 node->exits = next_x;
151 listelem_free(dag->latlink_alloc, x->link);
152 listelem_free(dag->latlink_list_alloc, x);
153 }
154 else
155 prev_x = x;
156 }
157 prev_x = NULL;
158 for (x = node->entries; x; x = next_x) {
159 next_x = x->next;
160 if (x->link->from == NULL) {
161 if (prev_x)
162 prev_x->next = next_x;
163 else
164 node->entries = next_x;
165 listelem_free(dag->latlink_alloc, x->link);
166 listelem_free(dag->latlink_list_alloc, x);
167 }
168 else
169 prev_x = x;
170 }
171}
172
173void
175{
176 ps_latnode_t *node, *prev_node, *next_node;
177 int i;
178
179 /* Remove unreachable nodes from the list of nodes. */
180 prev_node = NULL;
181 for (node = dag->nodes; node; node = next_node) {
182 next_node = node->next;
183 if (!node->reachable) {
184 if (prev_node)
185 prev_node->next = next_node;
186 else
187 dag->nodes = next_node;
188 /* Delete this node and NULLify links to it. */
189 delete_node(dag, node);
190 }
191 else
192 prev_node = node;
193 }
194
195 /* Remove all links to and from unreachable nodes. */
196 i = 0;
197 for (node = dag->nodes; node; node = node->next) {
198 /* Assign sequence numbers. */
199 node->id = i++;
200
201 /* We should obviously not encounter unreachable nodes here! */
202 assert(node->reachable);
203
204 /* Remove all links that go nowhere. */
205 remove_dangling_links(dag, node);
206 }
207}
208
209int32
210ps_lattice_write(ps_lattice_t *dag, char const *filename)
211{
212 FILE *fp;
213 int32 i;
214 ps_latnode_t *d, *initial, *final;
215
216 initial = dag->start;
217 final = dag->end;
218
219 E_INFO("Writing lattice file: %s\n", filename);
220 if ((fp = fopen(filename, "w")) == NULL) {
221 E_ERROR_SYSTEM("Failed to open lattice file '%s' for writing", filename);
222 return -1;
223 }
224
225 /* Stupid Sphinx-III lattice code expects 'getcwd:' here */
226 fprintf(fp, "# getcwd: /this/is/bogus\n");
227 fprintf(fp, "# -logbase %e\n", logmath_get_base(dag->lmath));
228 fprintf(fp, "#\n");
229
230 fprintf(fp, "Frames %d\n", dag->n_frames);
231 fprintf(fp, "#\n");
232
233 for (i = 0, d = dag->nodes; d; d = d->next, i++);
234 fprintf(fp,
235 "Nodes %d (NODEID WORD STARTFRAME FIRST-ENDFRAME LAST-ENDFRAME)\n",
236 i);
237 for (i = 0, d = dag->nodes; d; d = d->next, i++) {
238 d->id = i;
239 fprintf(fp, "%d %s %d %d %d ; %d\n",
240 i, dict_wordstr(dag->dict, d->wid),
241 d->sf, d->fef, d->lef, d->node_id);
242 }
243 fprintf(fp, "#\n");
244
245 fprintf(fp, "Initial %d\nFinal %d\n", initial->id, final->id);
246 fprintf(fp, "#\n");
247
248 /* Don't bother with this, it's not used by anything. */
249 fprintf(fp, "BestSegAscr %d (NODEID ENDFRAME ASCORE)\n",
250 0 /* #BPTable entries */ );
251 fprintf(fp, "#\n");
252
253 fprintf(fp, "Edges (FROM-NODEID TO-NODEID ASCORE)\n");
254 for (d = dag->nodes; d; d = d->next) {
256 for (l = d->exits; l; l = l->next) {
257 if (l->link->ascr WORSE_THAN WORST_SCORE || l->link->ascr BETTER_THAN 0)
258 continue;
259 fprintf(fp, "%d %d %d\n",
260 d->id, l->link->to->id, l->link->ascr << SENSCR_SHIFT);
261 }
262 }
263 fprintf(fp, "End\n");
264 fclose(fp);
265
266 return 0;
267}
268
269int32
270ps_lattice_write_htk(ps_lattice_t *dag, char const *filename)
271{
272 FILE *fp;
273 ps_latnode_t *d, *initial, *final;
274 int32 j, n_links, n_nodes;
275
276 initial = dag->start;
277 final = dag->end;
278
279 E_INFO("Writing lattice file: %s\n", filename);
280 if ((fp = fopen(filename, "w")) == NULL) {
281 E_ERROR_SYSTEM("Failed to open lattice file '%s' for writing", filename);
282 return -1;
283 }
284
285 for (n_links = n_nodes = 0, d = dag->nodes; d; d = d->next) {
287 if (!d->reachable)
288 continue;
289 d->id = n_nodes;
290 for (l = d->exits; l; l = l->next) {
291 if (l->link->to == NULL || !l->link->to->reachable)
292 continue;
293 if (l->link->ascr WORSE_THAN WORST_SCORE || l->link->ascr BETTER_THAN 0)
294 continue;
295 ++n_links;
296 }
297 ++n_nodes;
298 }
299
300 fprintf(fp, "# Lattice generated by PocketSphinx\n");
301 fprintf(fp, "#\n# Header\n#\n");
302 fprintf(fp, "VERSION=1.0\n");
303 fprintf(fp, "start=%d\n", initial->id);
304 fprintf(fp, "end=%d\n", final->id);
305 fprintf(fp, "#\n");
306
307 fprintf(fp, "N=%d\tL=%d\n", n_nodes, n_links);
308 fprintf(fp, "#\n# Node definitions\n#\n");
309 for (d = dag->nodes; d; d = d->next) {
310 char const *word = dict_wordstr(dag->dict, d->wid);
311 char const *c = strrchr(word, '(');
312 int altpron = 1;
313 if (!d->reachable)
314 continue;
315 if (c)
316 altpron = atoi(c + 1);
317 word = dict_basestr(dag->dict, d->wid);
318 if (d->wid == dict_startwid(dag->dict))
319 word = "!SENT_START";
320 else if (d->wid == dict_finishwid(dag->dict))
321 word = "!SENT_END";
322 else if (dict_filler_word(dag->dict, d->wid))
323 word = "!NULL";
324 fprintf(fp, "I=%d\tt=%.2f\tW=%s\tv=%d\n",
325 d->id, (double)d->sf / dag->frate,
326 word, altpron);
327 }
328 fprintf(fp, "#\n# Link definitions\n#\n");
329 for (j = 0, d = dag->nodes; d; d = d->next) {
331 if (!d->reachable)
332 continue;
333 for (l = d->exits; l; l = l->next) {
334 if (l->link->to == NULL || !l->link->to->reachable)
335 continue;
336 if (l->link->ascr WORSE_THAN WORST_SCORE || l->link->ascr BETTER_THAN 0)
337 continue;
338 fprintf(fp, "J=%d\tS=%d\tE=%d\ta=%f\tp=%g\n", j++,
339 d->id, l->link->to->id,
340 logmath_log_to_ln(dag->lmath, l->link->ascr << SENSCR_SHIFT),
341 logmath_exp(dag->lmath, l->link->alpha + l->link->beta - dag->norm));
342 }
343 }
344 fclose(fp);
345
346 return 0;
347}
348
349/* Read parameter from a lattice file*/
350static int
351dag_param_read(lineiter_t *li, char *param)
352{
353 int32 n;
354
355 while ((li = lineiter_next(li)) != NULL) {
356 char *c;
357
358 /* Ignore comments. */
359 if (li->buf[0] == '#')
360 continue;
361
362 /* Find the first space. */
363 c = strchr(li->buf, ' ');
364 if (c == NULL) continue;
365
366 /* Check that the first field equals param and that there's a number after it. */
367 if (strncmp(li->buf, param, strlen(param)) == 0
368 && sscanf(c + 1, "%d", &n) == 1)
369 return n;
370 }
371 return -1;
372}
373
374/* Mark every node that has a path to the argument dagnode as "reachable". */
375static void
376dag_mark_reachable(ps_latnode_t * d)
377{
379
380 d->reachable = 1;
381 for (l = d->entries; l; l = l->next)
382 if (l->link->from && !l->link->from->reachable)
383 dag_mark_reachable(l->link->from);
384}
385
388 char const *file)
389{
390 FILE *fp;
391 int32 ispipe;
392 lineiter_t *line;
393 float64 lb;
394 float32 logratio;
395 ps_latnode_t *tail;
396 ps_latnode_t **darray;
397 ps_lattice_t *dag;
398 int i, k, n_nodes;
399 int32 pip, silpen, fillpen;
400
401 dag = ckd_calloc(1, sizeof(*dag));
402
403 if (ps) {
404 dag->search = ps->search;
405 dag->dict = dict_retain(ps->dict);
406 dag->lmath = logmath_retain(ps->lmath);
407 dag->frate = cmd_ln_int32_r(dag->search->config, "-frate");
408 }
409 else {
410 dag->dict = dict_init(NULL, NULL);
411 dag->lmath = logmath_init(1.0001, 0, FALSE);
412 dag->frate = 100;
413 }
414 dag->silence = dict_silwid(dag->dict);
415 dag->latnode_alloc = listelem_alloc_init(sizeof(ps_latnode_t));
416 dag->latlink_alloc = listelem_alloc_init(sizeof(ps_latlink_t));
417 dag->latlink_list_alloc = listelem_alloc_init(sizeof(latlink_list_t));
418 dag->refcount = 1;
419
420 tail = NULL;
421 darray = NULL;
422
423 E_INFO("Reading DAG file: %s\n", file);
424 if ((fp = fopen_compchk(file, &ispipe)) == NULL) {
425 E_ERROR_SYSTEM("Failed to open DAG file '%s' for reading", file);
426 return NULL;
427 }
428 line = lineiter_start(fp);
429
430 /* Read and verify logbase (ONE BIG HACK!!) */
431 if (line == NULL) {
432 E_ERROR("Premature EOF(%s)\n", file);
433 goto load_error;
434 }
435 if (strncmp(line->buf, "# getcwd: ", 10) != 0) {
436 E_ERROR("%s does not begin with '# getcwd: '\n%s", file, line->buf);
437 goto load_error;
438 }
439 if ((line = lineiter_next(line)) == NULL) {
440 E_ERROR("Premature EOF(%s)\n", file);
441 goto load_error;
442 }
443 if ((strncmp(line->buf, "# -logbase ", 11) != 0)
444 || (sscanf(line->buf + 11, "%lf", &lb) != 1)) {
445 E_WARN("%s: Cannot find -logbase in header\n", file);
446 lb = 1.0001;
447 }
448 logratio = 1.0f;
449 if (dag->lmath == NULL)
450 dag->lmath = logmath_init(lb, 0, TRUE);
451 else {
452 float32 pb = logmath_get_base(dag->lmath);
453 if (fabs(lb - pb) >= 0.0001) {
454 E_WARN("Inconsistent logbases: %f vs %f: will compensate\n", lb, pb);
455 logratio = (float32)(log(lb) / log(pb));
456 E_INFO("Lattice log ratio: %f\n", logratio);
457 }
458 }
459 /* Read Frames parameter */
460 dag->n_frames = dag_param_read(line, "Frames");
461 if (dag->n_frames <= 0) {
462 E_ERROR("Frames parameter missing or invalid\n");
463 goto load_error;
464 }
465 /* Read Nodes parameter */
466 n_nodes = dag_param_read(line, "Nodes");
467 if (n_nodes <= 0) {
468 E_ERROR("Nodes parameter missing or invalid\n");
469 goto load_error;
470 }
471
472 /* Read nodes */
473 darray = ckd_calloc(n_nodes, sizeof(*darray));
474 for (i = 0; i < n_nodes; i++) {
475 ps_latnode_t *d;
476 int32 w;
477 int seqid, sf, fef, lef;
478 char wd[256];
479
480 if ((line = lineiter_next(line)) == NULL) {
481 E_ERROR("Premature EOF while loading Nodes(%s)\n", file);
482 goto load_error;
483 }
484
485 if ((k =
486 sscanf(line->buf, "%d %255s %d %d %d", &seqid, wd, &sf, &fef,
487 &lef)) != 5) {
488 E_ERROR("Cannot parse line: %s, value of count %d\n", line->buf, k);
489 goto load_error;
490 }
491
492 w = dict_wordid(dag->dict, wd);
493 if (w < 0) {
494 if (dag->search == NULL) {
495 char *ww = ckd_salloc(wd);
496 if (dict_word2basestr(ww) != -1) {
497 if (dict_wordid(dag->dict, ww) == BAD_S3WID)
498 dict_add_word(dag->dict, ww, NULL, 0);
499 }
500 ckd_free(ww);
501 w = dict_add_word(dag->dict, wd, NULL, 0);
502 }
503 if (w < 0) {
504 E_ERROR("Unknown word in line: %s\n", line->buf);
505 goto load_error;
506 }
507 }
508
509 if (seqid != i) {
510 E_ERROR("Seqno error: %s\n", line->buf);
511 goto load_error;
512 }
513
514 d = listelem_malloc(dag->latnode_alloc);
515 darray[i] = d;
516 d->wid = w;
517 d->basewid = dict_basewid(dag->dict, w);
518 d->id = seqid;
519 d->sf = sf;
520 d->fef = fef;
521 d->lef = lef;
522 d->reachable = 0;
523 d->exits = d->entries = NULL;
524 d->next = NULL;
525
526 if (!dag->nodes)
527 dag->nodes = d;
528 else
529 tail->next = d;
530 tail = d;
531 }
532
533 /* Read initial node ID */
534 k = dag_param_read(line, "Initial");
535 if ((k < 0) || (k >= n_nodes)) {
536 E_ERROR("Initial node parameter missing or invalid\n");
537 goto load_error;
538 }
539 dag->start = darray[k];
540
541 /* Read final node ID */
542 k = dag_param_read(line, "Final");
543 if ((k < 0) || (k >= n_nodes)) {
544 E_ERROR("Final node parameter missing or invalid\n");
545 goto load_error;
546 }
547 dag->end = darray[k];
548
549 /* Read bestsegscore entries and ignore them. */
550 if ((k = dag_param_read(line, "BestSegAscr")) < 0) {
551 E_ERROR("BestSegAscr parameter missing\n");
552 goto load_error;
553 }
554 for (i = 0; i < k; i++) {
555 if ((line = lineiter_next(line)) == NULL) {
556 E_ERROR("Premature EOF while (%s) ignoring BestSegAscr\n",
557 line);
558 goto load_error;
559 }
560 }
561
562 /* Read in edges. */
563 while ((line = lineiter_next(line)) != NULL) {
564 if (line->buf[0] == '#')
565 continue;
566 if (0 == strncmp(line->buf, "Edges", 5))
567 break;
568 }
569 if (line == NULL) {
570 E_ERROR("Edges missing\n");
571 goto load_error;
572 }
573 while ((line = lineiter_next(line)) != NULL) {
574 int from, to, ascr;
575 ps_latnode_t *pd, *d;
576
577 if (sscanf(line->buf, "%d %d %d", &from, &to, &ascr) != 3)
578 break;
579 if (ascr WORSE_THAN WORST_SCORE)
580 continue;
581 pd = darray[from];
582 d = darray[to];
583 if (logratio != 1.0f)
584 ascr = (int32)(ascr * logratio);
585 ps_lattice_link(dag, pd, d, ascr, d->sf - 1);
586 }
587 if (strcmp(line->buf, "End\n") != 0) {
588 E_ERROR("Terminating 'End' missing\n");
589 goto load_error;
590 }
591 lineiter_free(line);
592 fclose_comp(fp, ispipe);
593 ckd_free(darray);
594
595 /* Minor hack: If the final node is a filler word and not </s>,
596 * then set its base word ID to </s>, so that the language model
597 * scores won't be screwed up. */
598 if (dict_filler_word(dag->dict, dag->end->wid))
599 dag->end->basewid = dag->search
600 ? ps_search_finish_wid(dag->search)
601 : dict_wordid(dag->dict, S3_FINISH_WORD);
602
603 /* Mark reachable from dag->end */
604 dag_mark_reachable(dag->end);
605