From 7eaa2e75d3e8f04585ac1067963c8020598974b6 Mon Sep 17 00:00:00 2001 From: Fangjun Kuang Date: Wed, 8 Nov 2023 23:41:18 +0800 Subject: [PATCH 1/3] add scale_lattice --- docs/source/python_api/code/Makefile | 3 + .../python_api/code/scale_lattice/Makefile | 7 ++ .../python_api/code/scale_lattice/ex1.py | 69 +++++++++++ .../python_api/code/scale_lattice/ex2.py | 69 +++++++++++ .../python_api/code/shortest_path/ex2.py | 111 ++++++++++++++++++ kaldifst/python/csrc/lattice-utils.cc | 67 +++++++++++ kaldifst/python/csrc/shortest-path.cc | 47 ++++++-- kaldifst/python/kaldifst/__init__.py | 1 + 8 files changed, 367 insertions(+), 7 deletions(-) create mode 100644 docs/source/python_api/code/scale_lattice/Makefile create mode 100644 docs/source/python_api/code/scale_lattice/ex1.py create mode 100644 docs/source/python_api/code/scale_lattice/ex2.py diff --git a/docs/source/python_api/code/Makefile b/docs/source/python_api/code/Makefile index 87831df..4b7ac37 100644 --- a/docs/source/python_api/code/Makefile +++ b/docs/source/python_api/code/Makefile @@ -16,6 +16,8 @@ all: $(MAKE) -C minimize_encoded $(MAKE) -C reverse $(MAKE) -C rmepsilon + $(MAKE) -C shortest_path + $(MAKE) -C scale_lattice clean: $(MAKE) -C add_self_loops clean @@ -35,3 +37,4 @@ clean: $(MAKE) -C reverse clean $(MAKE) -C rmepsilon clean $(MAKE) -C shortest_path clean + $(MAKE) -C scale_lattice clean diff --git a/docs/source/python_api/code/scale_lattice/Makefile b/docs/source/python_api/code/scale_lattice/Makefile new file mode 100644 index 0000000..d9db49f --- /dev/null +++ b/docs/source/python_api/code/scale_lattice/Makefile @@ -0,0 +1,7 @@ + +all: + python3 ./ex1.py + python3 ./ex2.py + +clean: + $(RM) *.svg *.gv diff --git a/docs/source/python_api/code/scale_lattice/ex1.py b/docs/source/python_api/code/scale_lattice/ex1.py new file mode 100644 index 0000000..17827b9 --- /dev/null +++ b/docs/source/python_api/code/scale_lattice/ex1.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + + +fst = kaldifst.Lattice() +s0 = fst.add_state() +s1 = fst.add_state() +s2 = fst.add_state() +s3 = fst.add_state() + +fst.start = s0 +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=1, + olabel=2, + weight=kaldifst.LatticeWeight(graph_cost=0.1, acoustic_cost=0.2), + nextstate=s1, + ), +) + +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.3, acoustic_cost=0.4), + nextstate=s2, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=5, + olabel=6, + weight=kaldifst.LatticeWeight(graph_cost=0.5, acoustic_cost=0.6), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=6, + olabel=8, + weight=kaldifst.LatticeWeight(graph_cost=0.7, acoustic_cost=0.8), + nextstate=s3, + ), +) + +fst.set_final( + state=s3, weight=kaldifst.LatticeWeight(graph_cost=0.2, acoustic_cost=0.5) +) + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +source = graphviz.Source(fst_dot) +source.render(outfile="before-scale.svg") + +scale = kaldifst.lattice_scale(lmwt=0.1, acwt=10.0) + +kaldifst.scale_lattice(scale, fst) + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +source = graphviz.Source(fst_dot) +source.render(outfile="after-scale.svg") diff --git a/docs/source/python_api/code/scale_lattice/ex2.py b/docs/source/python_api/code/scale_lattice/ex2.py new file mode 100644 index 0000000..c55762f --- /dev/null +++ b/docs/source/python_api/code/scale_lattice/ex2.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + + +fst = kaldifst.Lattice() +s0 = fst.add_state() +s1 = fst.add_state() +s2 = fst.add_state() +s3 = fst.add_state() + +fst.start = s0 +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=1, + olabel=2, + weight=kaldifst.LatticeWeight(graph_cost=0.1, acoustic_cost=0.2), + nextstate=s1, + ), +) + +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.3, acoustic_cost=0.4), + nextstate=s2, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=5, + olabel=6, + weight=kaldifst.LatticeWeight(graph_cost=0.5, acoustic_cost=0.6), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=6, + olabel=8, + weight=kaldifst.LatticeWeight(graph_cost=0.7, acoustic_cost=0.8), + nextstate=s3, + ), +) + +fst.set_final( + state=s3, weight=kaldifst.LatticeWeight(graph_cost=0.2, acoustic_cost=0.5) +) + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +source = graphviz.Source(fst_dot) +source.render(outfile="before-scale-2.svg") + +scale = [[0.1, 1], [0.5, 10]] + +kaldifst.scale_lattice(scale, fst) + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +source = graphviz.Source(fst_dot) +source.render(outfile="after-scale-2.svg") diff --git a/docs/source/python_api/code/shortest_path/ex2.py b/docs/source/python_api/code/shortest_path/ex2.py index e69de29..993d636 100755 --- a/docs/source/python_api/code/shortest_path/ex2.py +++ b/docs/source/python_api/code/shortest_path/ex2.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + +fst = kaldifst.Lattice() +s0 = fst.add_state() +s1 = fst.add_state() +s2 = fst.add_state() +s3 = fst.add_state() + +fst.start = s0 +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=1, + olabel=1, + weight=kaldifst.LatticeWeight(graph_cost=0.02, acoustic_cost=0.08), + nextstate=s1, + ), +) + +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=2, + olabel=2, + weight=kaldifst.LatticeWeight(graph_cost=0.03, acoustic_cost=0.07), + nextstate=s2, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.1, acoustic_cost=0.3), + nextstate=s3, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.05), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.05, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.set_final(state=s3, weight=kaldifst.LatticeWeight.one) + + +sym1 = kaldifst.SymbolTable(name="sym1") +sym1.add_symbol("eps", 0) +sym1.add_symbol("a", 1) +sym1.add_symbol("b", 2) +sym1.add_symbol("c", 3) +sym1.add_symbol("d", 4) + +sym2 = kaldifst.SymbolTable(name="sym2") +sym2.add_symbol("eps", 0) +sym2.add_symbol("A", 1) +sym2.add_symbol("B", 2) +sym2.add_symbol("C", 3) +sym2.add_symbol("D", 4) + +fst.input_symbols = sym1 +fst.output_symbols = sym2 + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +fst_source = graphviz.Source(fst_dot) +fst_source.render(outfile="lattice.svg") + +nbest_1 = kaldifst.shortest_path(fst, n=1) +nbest_1_dot = kaldifst.draw(nbest_1, acceptor=False, portrait=True) +nbest_1_source = graphviz.Source(nbest_1_dot) +nbest_1_source.render(outfile="lattice-1best.svg") + +nbest_2 = kaldifst.shortest_path(fst, n=2) +nbest_2_dot = kaldifst.draw(nbest_2, acceptor=False, portrait=True) +nbest_2_source = graphviz.Source(nbest_2_dot) +nbest_2_source.render(outfile="lattice-2best.svg") + +nbest_3 = kaldifst.shortest_path(fst, n=3) +nbest_3_dot = kaldifst.draw(nbest_3, acceptor=False, portrait=True) +nbest_3_source = graphviz.Source(nbest_3_dot) +nbest_3_source.render(outfile="lattice-3best.svg") diff --git a/kaldifst/python/csrc/lattice-utils.cc b/kaldifst/python/csrc/lattice-utils.cc index c4205fd..0e88d08 100644 --- a/kaldifst/python/csrc/lattice-utils.cc +++ b/kaldifst/python/csrc/lattice-utils.cc @@ -30,10 +30,77 @@ Note that the diagonal matrix is a list of list, e.g., :linenos: :caption: Return a matrix with lmwt=0.1, acwt=10.0 )doc"; +static constexpr const char *kScaleLatticeDoc = R"doc( +Scales the pairs of weights in ``LatticeWeight`` by +viewing the pair ``(a, b)`` as a 2-vector and pre-multiplying by +the 2x2 matrix in "scale". E.g. typically scale would equal + +.. code-block:: bash + + [ 1 0; + 0 acwt ] + +if we want to scale the acoustics by ``acwt``. + + - out_value1 = scale[0][0]*in_value1 + scale[0][1]*in_value2 + - out_value2 = scale[1][0]*in_value1 + scale[1][1]*in_value2 + +Args: + scale: + A list-of-list containing the weight + in_out: + The lattice is changed in-place. +Returns: + Return ``None``. + +Example 1: Use a diagonal scale ``[[0.1, 0], [0, 10]]`` + +.. literalinclude:: ./code/scale_lattice/ex1.py + :language: python + :linenos: + :caption: Scale a lattice. + +.. figure:: ./code/scale_lattice/before-scale.svg + :alt: before-scale.svg + :align: center + :figwidth: 600px + + Before scale. + +.. figure:: ./code/scale_lattice/after-scale.svg + :alt: after-scale.svg + :align: center + :figwidth: 600px + + After scale using ``[[0.1, 0], [0, 10]]``. + +Example 2: Use a non-diagonal scale ``[[0.1, 1], [0.5, 10]]`` + +.. literalinclude:: ./code/scale_lattice/ex2.py + :language: python + :linenos: + :caption: Scale a lattice. + +.. figure:: ./code/scale_lattice/before-scale-2.svg + :alt: before-scale-2.svg + :align: center + :figwidth: 600px + + Before scale. + +.. figure:: ./code/scale_lattice/after-scale-2.svg + :alt: after-scale-2.svg + :align: center + :figwidth: 600px + + After scale using ``[[0.1, 1], [0.5, 10]]``. +)doc"; void PybindLatticeUtils(py::module *m) { m->def("lattice_scale", &fst::LatticeScale, py::arg("lmwt"), py::arg("acwt"), kLatticeScaleDoc); + m->def("scale_lattice", &fst::ScaleLattice, + py::arg("scale"), py::arg("in_out"), kScaleLatticeDoc); } } // namespace kaldifst diff --git a/kaldifst/python/csrc/shortest-path.cc b/kaldifst/python/csrc/shortest-path.cc index 819cae0..4b89b81 100644 --- a/kaldifst/python/csrc/shortest-path.cc +++ b/kaldifst/python/csrc/shortest-path.cc @@ -23,9 +23,9 @@ See also ``_. Return only distinct strings. (NB: must be acceptor; epsilons treated as regular symbols) Returns: - Return a VectorFst containing n paths. + Return a VectorFst containing n linear paths. -**Example 1: StdVectorFst** +**Example: shortest_path of a StdVectorFst** .. literalinclude:: ./code/shortest_path/ex1.py :language: python @@ -59,8 +59,27 @@ See also ``_. :figwidth: 600px Visualization of vector-fst-3best.svg +)doc"; + +static constexpr const char *kShortestPathLatticeDoc = R"doc( +This operation produces an FST containing the n-shortest paths in the input ``Lattice``. + +The n -shortest paths are the n -lowest weight paths w.r.t. the natural semiring +order. The single path that can be read from the ith of at most n transitions +leaving the initial state of the resulting FST is the ith shortest path. + +See also ``_. + +Args: + n: + Size of n-best. + unique: + Return only distinct strings. (NB: must be acceptor; epsilons + treated as regular symbols) +Returns: + Return a Lattice containing n linear paths. -**Example 2: Lattice** +**Example: shortest_path of a Lattice** .. literalinclude:: ./code/shortest_path/ex2.py :language: python @@ -74,17 +93,31 @@ See also ``_. Visualization of lattice.svg -.. figure:: ./code/shortest_path/lattice-nbest.svg - :alt: lattice-nbest.svg +.. figure:: ./code/shortest_path/lattice-1best.svg + :alt: lattice-1best.svg + :align: center + :figwidth: 600px + + Visualization of lattice-1best.svg + +.. figure:: ./code/shortest_path/lattice-2best.svg + :alt: lattice-2best.svg + :align: center + :figwidth: 600px + + Visualization of lattice-2best.svg + +.. figure:: ./code/shortest_path/lattice-3best.svg + :alt: lattice-3best.svg :align: center :figwidth: 600px - Visualization of lattice-nbest.svg + Visualization of lattice-3best.svg )doc"; void PybindShortestPath(py::module *m) { PybindShortestPath(m, kShortestPathDoc); - PybindShortestPath(m, kShortestPathDoc); + PybindShortestPath(m, kShortestPathLatticeDoc); } } // namespace kaldifst diff --git a/kaldifst/python/kaldifst/__init__.py b/kaldifst/python/kaldifst/__init__.py index 707b7fc..5b0f947 100644 --- a/kaldifst/python/kaldifst/__init__.py +++ b/kaldifst/python/kaldifst/__init__.py @@ -31,6 +31,7 @@ plus, reverse, rmepsilon, + scale_lattice, shortest_path, times, ) From e4f43b2620ffb2b65859cf993f337f4b6f5d35f1 Mon Sep 17 00:00:00 2001 From: Fangjun Kuang Date: Fri, 10 Nov 2023 15:18:04 +0800 Subject: [PATCH 2/3] add convert_nbest_to_vector --- docs/source/python_api/code/Makefile | 2 + .../code/convert_nbest_to_vector/Makefile | 7 + .../code/convert_nbest_to_vector/ex1.py | 51 +++++++ .../code/convert_nbest_to_vector/ex2.py | 118 ++++++++++++++++ kaldifst/csrc/fstext-utils-inl.h | 59 ++++++++ kaldifst/csrc/fstext-utils.h | 9 ++ kaldifst/python/csrc/fstext-utils.cc | 129 ++++++++++++++++++ kaldifst/python/kaldifst/__init__.py | 1 + 8 files changed, 376 insertions(+) create mode 100644 docs/source/python_api/code/convert_nbest_to_vector/Makefile create mode 100755 docs/source/python_api/code/convert_nbest_to_vector/ex1.py create mode 100755 docs/source/python_api/code/convert_nbest_to_vector/ex2.py diff --git a/docs/source/python_api/code/Makefile b/docs/source/python_api/code/Makefile index 4b7ac37..b3eb572 100644 --- a/docs/source/python_api/code/Makefile +++ b/docs/source/python_api/code/Makefile @@ -18,6 +18,7 @@ all: $(MAKE) -C rmepsilon $(MAKE) -C shortest_path $(MAKE) -C scale_lattice + $(MAKE) -C convert_nbest_to_vector clean: $(MAKE) -C add_self_loops clean @@ -38,3 +39,4 @@ clean: $(MAKE) -C rmepsilon clean $(MAKE) -C shortest_path clean $(MAKE) -C scale_lattice clean + $(MAKE) -C convert_nbest_to_vector clean diff --git a/docs/source/python_api/code/convert_nbest_to_vector/Makefile b/docs/source/python_api/code/convert_nbest_to_vector/Makefile new file mode 100644 index 0000000..d9db49f --- /dev/null +++ b/docs/source/python_api/code/convert_nbest_to_vector/Makefile @@ -0,0 +1,7 @@ + +all: + python3 ./ex1.py + python3 ./ex2.py + +clean: + $(RM) *.svg *.gv diff --git a/docs/source/python_api/code/convert_nbest_to_vector/ex1.py b/docs/source/python_api/code/convert_nbest_to_vector/ex1.py new file mode 100755 index 0000000..427d23b --- /dev/null +++ b/docs/source/python_api/code/convert_nbest_to_vector/ex1.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + +s1 = """ +0 1 a 0.1 +0 2 b 0.1 +1 3 c 0.4 +1 3 d 0.2 +2 3 c 0.3 +2 3 d 0.2 +3 0 +""" + +sym1 = kaldifst.SymbolTable(name="sym1") +sym1.add_symbol("eps", 0) +sym1.add_symbol("a", 1) +sym1.add_symbol("b", 2) +sym1.add_symbol("c", 3) +sym1.add_symbol("d", 4) + +a = kaldifst.compile(s=s1, acceptor=True, isymbols=sym1) +a.input_symbols = sym1 + +a_dot = kaldifst.draw(a, acceptor=True, portrait=True) +a_source = graphviz.Source(a_dot) +a_source.render(outfile="vector-fst.svg") + +nbest_3 = kaldifst.shortest_path(a, n=3) +nbest_3_dot = kaldifst.draw(nbest_3, acceptor=True, portrait=True) +nbest_3_source = graphviz.Source(nbest_3_dot) +nbest_3_source.render(outfile="vector-fst-3best.svg") + +nbest_list = kaldifst.convert_nbest_to_vector(nbest_3) +for b in nbest_list: + b.input_symbols = a.input_symbols + b.output_symbols = a.output_symbols + +nbest_list_0_dot = kaldifst.draw(nbest_list[0], acceptor=True, portrait=True) +nbest_list_0_source = graphviz.Source(nbest_list_0_dot) +nbest_list_0_source.render(outfile="vector-fst-3best-0.svg") + +nbest_list_1_dot = kaldifst.draw(nbest_list[1], acceptor=True, portrait=True) +nbest_list_1_source = graphviz.Source(nbest_list_1_dot) +nbest_list_1_source.render(outfile="vector-fst-3best-1.svg") + +nbest_list_2_dot = kaldifst.draw(nbest_list[2], acceptor=True, portrait=True) +nbest_list_2_source = graphviz.Source(nbest_list_2_dot) +nbest_list_2_source.render(outfile="vector-fst-3best-2.svg") diff --git a/docs/source/python_api/code/convert_nbest_to_vector/ex2.py b/docs/source/python_api/code/convert_nbest_to_vector/ex2.py new file mode 100755 index 0000000..f01c36a --- /dev/null +++ b/docs/source/python_api/code/convert_nbest_to_vector/ex2.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + +fst = kaldifst.Lattice() +s0 = fst.add_state() +s1 = fst.add_state() +s2 = fst.add_state() +s3 = fst.add_state() + +fst.start = s0 +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=1, + olabel=1, + weight=kaldifst.LatticeWeight(graph_cost=0.02, acoustic_cost=0.08), + nextstate=s1, + ), +) + +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=2, + olabel=2, + weight=kaldifst.LatticeWeight(graph_cost=0.03, acoustic_cost=0.07), + nextstate=s2, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.1, acoustic_cost=0.3), + nextstate=s3, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.05), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.05, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.set_final(state=s3, weight=kaldifst.LatticeWeight.one) + + +sym1 = kaldifst.SymbolTable(name="sym1") +sym1.add_symbol("eps", 0) +sym1.add_symbol("a", 1) +sym1.add_symbol("b", 2) +sym1.add_symbol("c", 3) +sym1.add_symbol("d", 4) + +sym2 = kaldifst.SymbolTable(name="sym2") +sym2.add_symbol("eps", 0) +sym2.add_symbol("A", 1) +sym2.add_symbol("B", 2) +sym2.add_symbol("C", 3) +sym2.add_symbol("D", 4) + +fst.input_symbols = sym1 +fst.output_symbols = sym2 + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +fst_source = graphviz.Source(fst_dot) +fst_source.render(outfile="lattice.svg") + +nbest_3 = kaldifst.shortest_path(fst, n=3) +nbest_3_dot = kaldifst.draw(nbest_3, acceptor=False, portrait=True) +nbest_3_source = graphviz.Source(nbest_3_dot) +nbest_3_source.render(outfile="lattice-3best.svg") + +nbest_list = kaldifst.convert_nbest_to_vector(nbest_3) +for b in nbest_list: + b.input_symbols = fst.input_symbols + b.output_symbols = fst.output_symbols + +nbest_list_0_dot = kaldifst.draw(nbest_list[0], acceptor=True, portrait=True) +nbest_list_0_source = graphviz.Source(nbest_list_0_dot) +nbest_list_0_source.render(outfile="lattice-3best-0.svg") + +nbest_list_1_dot = kaldifst.draw(nbest_list[1], acceptor=True, portrait=True) +nbest_list_1_source = graphviz.Source(nbest_list_1_dot) +nbest_list_1_source.render(outfile="lattice-3best-1.svg") + +nbest_list_2_dot = kaldifst.draw(nbest_list[2], acceptor=True, portrait=True) +nbest_list_2_source = graphviz.Source(nbest_list_2_dot) +nbest_list_2_source.render(outfile="lattice-3best-2.svg") diff --git a/kaldifst/csrc/fstext-utils-inl.h b/kaldifst/csrc/fstext-utils-inl.h index d407b89..5970147 100644 --- a/kaldifst/csrc/fstext-utils-inl.h +++ b/kaldifst/csrc/fstext-utils-inl.h @@ -549,5 +549,64 @@ void GetInputSymbols(const Fst &fst, bool include_eps, std::sort(symbols->begin(), symbols->end()); } +// see fstext-utils.h for comment. +template +void ConvertNbestToVector(const Fst &fst, + std::vector> *fsts_out) { + typedef typename Arc::Weight Weight; + typedef typename Arc::StateId StateId; + fsts_out->clear(); + StateId start_state = fst.Start(); + + if (start_state == kNoStateId) { + return; // No output. + } + + size_t n_arcs = fst.NumArcs(start_state); + bool start_is_final = (fst.Final(start_state) != Weight::Zero()); + fsts_out->reserve(n_arcs + (start_is_final ? 1 : 0)); + + if (start_is_final) { + fsts_out->resize(fsts_out->size() + 1); + StateId start_state_out = fsts_out->back().AddState(); + fsts_out->back().SetFinal(start_state_out, fst.Final(start_state)); + } + + for (ArcIterator> start_aiter(fst, start_state); !start_aiter.Done(); + start_aiter.Next()) { + fsts_out->resize(fsts_out->size() + 1); + VectorFst &ofst = fsts_out->back(); + const Arc &first_arc = start_aiter.Value(); + StateId cur_state = start_state, cur_ostate = ofst.AddState(); + ofst.SetStart(cur_ostate); + StateId next_ostate = ofst.AddState(); + ofst.AddArc(cur_ostate, Arc(first_arc.ilabel, first_arc.olabel, + first_arc.weight, next_ostate)); + cur_state = first_arc.nextstate; + cur_ostate = next_ostate; + while (1) { + size_t this_n_arcs = fst.NumArcs(cur_state); + KALDIFST_ASSERT(this_n_arcs <= 1); // or it violates our assumptions + // about the input. + if (this_n_arcs == 1) { + KALDIFST_ASSERT(fst.Final(cur_state) == Weight::Zero()); + // or problem with ShortestPath. + ArcIterator> aiter(fst, cur_state); + const Arc &arc = aiter.Value(); + next_ostate = ofst.AddState(); + ofst.AddArc(cur_ostate, + Arc(arc.ilabel, arc.olabel, arc.weight, next_ostate)); + cur_state = arc.nextstate; + cur_ostate = next_ostate; + } else { + KALDIFST_ASSERT(fst.Final(cur_state) != Weight::Zero()); + // or problem with ShortestPath. + ofst.SetFinal(cur_ostate, fst.Final(cur_state)); + break; + } + } + } +} + } // namespace fst #endif // KALDIFST_CSRC_FSTEXT_UTILS_INL_H_ diff --git a/kaldifst/csrc/fstext-utils.h b/kaldifst/csrc/fstext-utils.h index 93f6c19..7ffb0df 100644 --- a/kaldifst/csrc/fstext-utils.h +++ b/kaldifst/csrc/fstext-utils.h @@ -138,6 +138,15 @@ template void GetInputSymbols(const Fst &fst, bool include_eps, std::vector *symbols); +/// This function converts an FST with a special structure, which is +/// output by the OpenFst functions ShortestPath and RandGen, and converts +/// them into a std::vector of separate FSTs. This special structure is that +/// the only state that has more than one (arcs-out or final-prob) is the +/// start state. fsts_out is resized to the appropriate size. +template +void ConvertNbestToVector(const Fst &fst, + std::vector> *fsts_out); + } // namespace fst #include "kaldifst/csrc/fstext-utils-inl.h" diff --git a/kaldifst/python/csrc/fstext-utils.cc b/kaldifst/python/csrc/fstext-utils.cc index 9a90d19..845b85a 100644 --- a/kaldifst/python/csrc/fstext-utils.cc +++ b/kaldifst/python/csrc/fstext-utils.cc @@ -183,8 +183,121 @@ case it outputs the symbol :caption: Code for get_linear_symbol_sequence )doc"; +static constexpr const char *kConvertNbestToVectorDoc = R"doc( +This function converts an FST with a special structure, which is +output by the OpenFst functions ShortestPath and RandGen, and converts +them into a list of separate FSTs. This special structure is that +the only state that has more than one (arcs-out or final-prob) is the +start state. + +Args: + fst: + The input fst, which should be returned by ``shortestpath``. +Returns: + Return a list of linear FSTs. + +**Example for a StdVectorFst** + +.. literalinclude:: ./code/convert_nbest_to_vector/ex1.py + :language: python + :linenos: + :caption: convert_nbest_to_vector for a StdVectorFst + +.. figure:: ./code/convert_nbest_to_vector/vector-fst.svg + :alt: vector-fst.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst.svg + +.. figure:: ./code/convert_nbest_to_vector/vector-fst-3best.svg + :alt: vector-fst-3best.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best.svg + +.. figure:: ./code/convert_nbest_to_vector/vector-fst-3best-0.svg + :alt: vector-fst-3best-0.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-0.svg + +.. figure:: ./code/convert_nbest_to_vector/vector-fst-3best-1.svg + :alt: vector-fst-3best-1.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-1.svg + +.. figure:: ./code/convert_nbest_to_vector/vector-fst-3best-2.svg + :alt: vector-fst-3best-2.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-2.svg +)doc"; + +static constexpr const char *kConvertNbestToVectorLatticeDoc = R"doc( +This function converts an FST with a special structure, which is +output by the OpenFst functions ShortestPath and RandGen, and converts +them into a list of separate FSTs. This special structure is that +the only state that has more than one (arcs-out or final-prob) is the +start state. + +Args: + fst: + The input fst, which should be returned by ``shortestpath``. +Returns: + Return a list of linear FSTs. + +**Example for a Lattice** + +.. literalinclude:: ./code/convert_nbest_to_vector/ex2.py + :language: python + :linenos: + :caption: convert_nbest_to_vector for a Lattice + +.. figure:: ./code/convert_nbest_to_vector/lattice.svg + :alt: lattice.svg + :align: center + :figwidth: 600px + + Visualization of lattice.svg + +.. figure:: ./code/convert_nbest_to_vector/lattice-3best.svg + :alt: lattice-3best.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best.svg + +.. figure:: ./code/convert_nbest_to_vector/lattice-3best-0.svg + :alt: lattice-3best-0.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-0.svg + +.. figure:: ./code/convert_nbest_to_vector/lattice-3best-1.svg + :alt: lattice-3best-1.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-1.svg + +.. figure:: ./code/convert_nbest_to_vector/lattice-3best-2.svg + :alt: lattice-3best-2.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-2.svg +)doc"; + namespace kaldifst { +namespace { template std::tuple, std::vector, typename Arc::Weight> GetLinearSymbolSequenceWrapper(const fst::Fst &fst) { @@ -198,6 +311,15 @@ GetLinearSymbolSequenceWrapper(const fst::Fst &fst) { return std::make_tuple(succeeded, isymbols_out, osymbols_out, w); } +template +std::vector> ConvertNbestToVectorWrapper( + const fst::Fst &fst) { + std::vector> fsts_out; + ConvertNbestToVector(fst, &fsts_out); + return fsts_out; +} +} // namespace + void PybindFstExtUtils(py::module &m) { // NOLINT m.def( "minimize_encoded", @@ -234,6 +356,13 @@ void PybindFstExtUtils(py::module &m) { // NOLINT m.def("get_linear_symbol_sequence", &(GetLinearSymbolSequenceWrapper), py::arg("fst"), kGetLinearSymbolSequenceDoc); + + m.def("convert_nbest_to_vector", &(ConvertNbestToVectorWrapper), + py::arg("fst"), kConvertNbestToVectorDoc); + + m.def("convert_nbest_to_vector", + &(ConvertNbestToVectorWrapper), py::arg("fst"), + kConvertNbestToVectorLatticeDoc); } } // namespace kaldifst diff --git a/kaldifst/python/kaldifst/__init__.py b/kaldifst/python/kaldifst/__init__.py index 5b0f947..9d64221 100644 --- a/kaldifst/python/kaldifst/__init__.py +++ b/kaldifst/python/kaldifst/__init__.py @@ -16,6 +16,7 @@ compose, compose_context, connect, + convert_nbest_to_vector, determinize, determinize_star, divide, From db6219ea43318df7414484d80ae214ea366767ec Mon Sep 17 00:00:00 2001 From: Fangjun Kuang Date: Fri, 10 Nov 2023 16:08:45 +0800 Subject: [PATCH 3/3] Add lattice-to-nbest --- docs/source/python_api/code/Makefile | 2 + .../python_api/code/lattice_to_nbest/Makefile | 7 ++ .../python_api/code/lattice_to_nbest/ex1.py | 46 +++++++ .../python_api/code/lattice_to_nbest/ex2.py | 113 ++++++++++++++++++ kaldifst/python/kaldifst/__init__.py | 1 + kaldifst/python/kaldifst/lattice_to_nbest.py | 112 +++++++++++++++++ 6 files changed, 281 insertions(+) create mode 100644 docs/source/python_api/code/lattice_to_nbest/Makefile create mode 100755 docs/source/python_api/code/lattice_to_nbest/ex1.py create mode 100755 docs/source/python_api/code/lattice_to_nbest/ex2.py create mode 100644 kaldifst/python/kaldifst/lattice_to_nbest.py diff --git a/docs/source/python_api/code/Makefile b/docs/source/python_api/code/Makefile index b3eb572..d6f6536 100644 --- a/docs/source/python_api/code/Makefile +++ b/docs/source/python_api/code/Makefile @@ -19,6 +19,7 @@ all: $(MAKE) -C shortest_path $(MAKE) -C scale_lattice $(MAKE) -C convert_nbest_to_vector + $(MAKE) -C lattice_to_nbest clean: $(MAKE) -C add_self_loops clean @@ -40,3 +41,4 @@ clean: $(MAKE) -C shortest_path clean $(MAKE) -C scale_lattice clean $(MAKE) -C convert_nbest_to_vector clean + $(MAKE) -C lattice_to_nbest clean diff --git a/docs/source/python_api/code/lattice_to_nbest/Makefile b/docs/source/python_api/code/lattice_to_nbest/Makefile new file mode 100644 index 0000000..d9db49f --- /dev/null +++ b/docs/source/python_api/code/lattice_to_nbest/Makefile @@ -0,0 +1,7 @@ + +all: + python3 ./ex1.py + python3 ./ex2.py + +clean: + $(RM) *.svg *.gv diff --git a/docs/source/python_api/code/lattice_to_nbest/ex1.py b/docs/source/python_api/code/lattice_to_nbest/ex1.py new file mode 100755 index 0000000..3c94c22 --- /dev/null +++ b/docs/source/python_api/code/lattice_to_nbest/ex1.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + +s1 = """ +0 1 a 0.1 +0 2 b 0.1 +1 3 c 0.4 +1 3 d 0.2 +2 3 c 0.3 +2 3 d 0.2 +3 0 +""" + +sym1 = kaldifst.SymbolTable(name="sym1") +sym1.add_symbol("eps", 0) +sym1.add_symbol("a", 1) +sym1.add_symbol("b", 2) +sym1.add_symbol("c", 3) +sym1.add_symbol("d", 4) + +a = kaldifst.compile(s=s1, acceptor=True, isymbols=sym1) +a.input_symbols = sym1 + +a_dot = kaldifst.draw(a, acceptor=True, portrait=True) +a_source = graphviz.Source(a_dot) +a_source.render(outfile="vector-fst.svg") + +nbest_list = kaldifst.lattice_to_nbest(a, n=3) +for b in nbest_list: + b.input_symbols = a.input_symbols + b.output_symbols = a.output_symbols + +nbest_list_0_dot = kaldifst.draw(nbest_list[0], acceptor=True, portrait=True) +nbest_list_0_source = graphviz.Source(nbest_list_0_dot) +nbest_list_0_source.render(outfile="vector-fst-3best-0.svg") + +nbest_list_1_dot = kaldifst.draw(nbest_list[1], acceptor=True, portrait=True) +nbest_list_1_source = graphviz.Source(nbest_list_1_dot) +nbest_list_1_source.render(outfile="vector-fst-3best-1.svg") + +nbest_list_2_dot = kaldifst.draw(nbest_list[2], acceptor=True, portrait=True) +nbest_list_2_source = graphviz.Source(nbest_list_2_dot) +nbest_list_2_source.render(outfile="vector-fst-3best-2.svg") diff --git a/docs/source/python_api/code/lattice_to_nbest/ex2.py b/docs/source/python_api/code/lattice_to_nbest/ex2.py new file mode 100755 index 0000000..c6c0636 --- /dev/null +++ b/docs/source/python_api/code/lattice_to_nbest/ex2.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +import graphviz + +import kaldifst + +fst = kaldifst.Lattice() +s0 = fst.add_state() +s1 = fst.add_state() +s2 = fst.add_state() +s3 = fst.add_state() + +fst.start = s0 +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=1, + olabel=1, + weight=kaldifst.LatticeWeight(graph_cost=0.02, acoustic_cost=0.08), + nextstate=s1, + ), +) + +fst.add_arc( + state=s0, + arc=kaldifst.LatticeArc( + ilabel=2, + olabel=2, + weight=kaldifst.LatticeWeight(graph_cost=0.03, acoustic_cost=0.07), + nextstate=s2, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.1, acoustic_cost=0.3), + nextstate=s3, + ), +) + +fst.add_arc( + state=s1, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.05), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=3, + olabel=3, + weight=kaldifst.LatticeWeight(graph_cost=0.15, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.add_arc( + state=s2, + arc=kaldifst.LatticeArc( + ilabel=4, + olabel=4, + weight=kaldifst.LatticeWeight(graph_cost=0.05, acoustic_cost=0.15), + nextstate=s3, + ), +) + +fst.set_final(state=s3, weight=kaldifst.LatticeWeight.one) + + +sym1 = kaldifst.SymbolTable(name="sym1") +sym1.add_symbol("eps", 0) +sym1.add_symbol("a", 1) +sym1.add_symbol("b", 2) +sym1.add_symbol("c", 3) +sym1.add_symbol("d", 4) + +sym2 = kaldifst.SymbolTable(name="sym2") +sym2.add_symbol("eps", 0) +sym2.add_symbol("A", 1) +sym2.add_symbol("B", 2) +sym2.add_symbol("C", 3) +sym2.add_symbol("D", 4) + +fst.input_symbols = sym1 +fst.output_symbols = sym2 + +fst_dot = kaldifst.draw(fst, acceptor=False, portrait=True) +fst_source = graphviz.Source(fst_dot) +fst_source.render(outfile="lattice.svg") + +nbest_list = kaldifst.lattice_to_nbest(fst, n=3) +for b in nbest_list: + b.input_symbols = fst.input_symbols + b.output_symbols = fst.output_symbols + +nbest_list_0_dot = kaldifst.draw(nbest_list[0], acceptor=True, portrait=True) +nbest_list_0_source = graphviz.Source(nbest_list_0_dot) +nbest_list_0_source.render(outfile="lattice-3best-0.svg") + +nbest_list_1_dot = kaldifst.draw(nbest_list[1], acceptor=True, portrait=True) +nbest_list_1_source = graphviz.Source(nbest_list_1_dot) +nbest_list_1_source.render(outfile="lattice-3best-1.svg") + +nbest_list_2_dot = kaldifst.draw(nbest_list[2], acceptor=True, portrait=True) +nbest_list_2_source = graphviz.Source(nbest_list_2_dot) +nbest_list_2_source.render(outfile="lattice-3best-2.svg") diff --git a/kaldifst/python/kaldifst/__init__.py b/kaldifst/python/kaldifst/__init__.py index 9d64221..ef46c50 100644 --- a/kaldifst/python/kaldifst/__init__.py +++ b/kaldifst/python/kaldifst/__init__.py @@ -38,6 +38,7 @@ ) from .iterator import ArcIterator, StateIterator +from .lattice_to_nbest import lattice_to_nbest from .table_types import ( RandomAccessVectorFstReader, SequentialVectorFstReader, diff --git a/kaldifst/python/kaldifst/lattice_to_nbest.py b/kaldifst/python/kaldifst/lattice_to_nbest.py new file mode 100644 index 0000000..9eccb87 --- /dev/null +++ b/kaldifst/python/kaldifst/lattice_to_nbest.py @@ -0,0 +1,112 @@ +from typing import List, Union + +from _kaldifst import ( + Lattice, + StdVectorFst, + convert_nbest_to_vector, + lattice_scale, + scale_lattice, + shortest_path, +) + + +def lattice_to_nbest( + lat: Union[Lattice, StdVectorFst], + acoustic_scale: float = 1.0, + lm_scale: float = 1.0, + n: int = 1, +) -> List[Union[Lattice, StdVectorFst]]: + """Work out N-best paths in lattices + + It implements + https://github.com/kaldi-asr/kaldi/blob/master/src/latbin/lattice-to-nbest.cc + + Args: + lat: + The input lattice. + acoustic_scale: + Scaling factor for acoustic likelihoods. + lm_scale: + Scaling factor for language model scores. + n: + Number of distinct paths. + Returns: + Return a list of linear FSTs. + + **Example for a StdVectorFst** + + .. literalinclude:: ./code/lattice_to_nbest/ex1.py + :language: python + :linenos: + :caption: lattice_to_nbest for a StdVectorFst + + .. figure:: ./code/lattice_to_nbest/vector-fst.svg + :alt: vector-fst.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst.svg + + .. figure:: ./code/lattice_to_nbest/vector-fst-3best-0.svg + :alt: vector-fst-3best-0.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-0.svg + + .. figure:: ./code/lattice_to_nbest/vector-fst-3best-1.svg + :alt: vector-fst-3best-1.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-1.svg + + .. figure:: ./code/lattice_to_nbest/vector-fst-3best-2.svg + :alt: vector-fst-3best-2.svg + :align: center + :figwidth: 600px + + Visualization of vector-fst-3best-2.svg + + **Example for a Lattice** + + .. literalinclude:: ./code/lattice_to_nbest/ex2.py + :language: python + :linenos: + :caption: lattice_to_nbest for a Lattice + + .. figure:: ./code/lattice_to_nbest/lattice.svg + :alt: lattice.svg + :align: center + :figwidth: 600px + + Visualization of lattice.svg + + .. figure:: ./code/lattice_to_nbest/lattice-3best-0.svg + :alt: lattice-3best-0.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-0.svg + + .. figure:: ./code/lattice_to_nbest/lattice-3best-1.svg + :alt: lattice-3best-1.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-1.svg + + .. figure:: ./code/lattice_to_nbest/lattice-3best-2.svg + :alt: lattice-3best-2.svg + :align: center + :figwidth: 600px + + Visualization of lattice-3best-2.svg + """ + if lm_scale != 1 or acoustic_scale != 1: + lat = type(lat)(lat) # a deep copy + lat_scale = lattice_scale(lmwt=lm_scale, acwt=acoustic_scale) + scale_lattice(scale=lat_scale, in_out=lat) + + nbest = shortest_path(lat, n=n) + return convert_nbest_to_vector(nbest)