Initial commit: FunASR Speech Recognition Toolkit
Update API Documentation / build-api-docs (push) Has been cancelled

Add complete FunASR codebase including models, runtime, and documentation.
This commit is contained in:
freedakgmail
2026-07-09 22:38:58 +08:00
commit 6116b1f3c6
3683 changed files with 990984 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# make "all" the target.
all:
# Disable linking math libs because not needed here. Just for compilation speed.
# no, it's now needed for context-fst-test.
# MATHLIB = NONE
EXTRA_CXXFLAGS = -Wno-sign-compare
include ../kaldi.mk
TESTFILES = determinize-star-test \
pre-determinize-test trivial-factor-weight-test \
context-fst-test factor-test table-matcher-test fstext-utils-test \
remove-eps-local-test lattice-weight-test \
determinize-lattice-test lattice-utils-test deterministic-fst-test \
push-special-test epsilon-property-test prune-special-test
OBJFILES = push-special.o kaldi-fst-io.o context-fst.o grammar-context-fst.o
LIBNAME = kaldi-fstext
# tree and matrix archives needed for test-context-fst
# matrix archive needed for push-special.
ADDLIBS = ../tree/kaldi-tree.a ../util/kaldi-util.a ../matrix/kaldi-matrix.a \
../base/kaldi-base.a
include ../makefiles/default_rules.mk
@@ -0,0 +1,256 @@
// fstext/context-fst-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/context-fst.h"
#include "fstext/fst-test-utils.h"
#include "tree/context-dep.h"
#include "util/kaldi-io.h"
#include "base/kaldi-math.h"
namespace fst
{
using std::vector;
using std::cout;
// GenAcceptorFromSequence generates a linear acceptor (identical input+output symbols) that has this
// sequence of symbols, and
template<class Arc>
static VectorFst<Arc> *GenAcceptorFromSequence(const vector<typename Arc::Label> &symbols, float cost) {
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
vector<float> split_cost(symbols.size()+1, 0.0); // for #-arcs + end-state.
{ // compute split_cost. it must sum to "cost".
std::set<int32> indices;
size_t num_indices = 1 + (kaldi::Rand() % split_cost.size());
while (indices.size() < num_indices) indices.insert(kaldi::Rand() % split_cost.size());
for (std::set<int32>::iterator iter = indices.begin(); iter != indices.end(); ++iter) {
split_cost[*iter] = cost / num_indices;
}
}
VectorFst<Arc> *fst = new VectorFst<Arc>();
StateId cur_state = fst->AddState();
fst->SetStart(cur_state);
for (size_t i = 0; i < symbols.size(); i++) {
StateId next_state = fst->AddState();
Arc arc;
arc.ilabel = symbols[i];
arc.olabel = symbols[i];
arc.nextstate = next_state;
arc.weight = (Weight) split_cost[i];
fst->AddArc(cur_state, arc);
cur_state = next_state;
}
fst->SetFinal(cur_state, (Weight)split_cost[symbols.size()]);
return fst;
}
// CheckPhones is used to test the correctness of an FST that is the result of
// composition with a ContextFst.
template<class Arc>
static float CheckPhones(const VectorFst<Arc> &linear_fst,
const vector<typename Arc::Label> &phone_ids,
const vector<typename Arc::Label> &disambig_ids,
const vector<typename Arc::Label> &phone_seq,
const vector<vector<typename Arc::Label> > &ilabel_info,
int N, int P) {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
assert(kaldi::IsSorted(phone_ids)); // so we can do binary_search.
vector<int32> input_syms;
vector<int32> output_syms;
Weight tot_cost;
bool ans = GetLinearSymbolSequence(linear_fst, &input_syms,
&output_syms, &tot_cost);
assert(ans); // should be linear.
vector<int32> phone_seq_check;
for (size_t i = 0; i < output_syms.size(); i++)
if (std::binary_search(phone_ids.begin(), phone_ids.end(), output_syms[i]))
phone_seq_check.push_back(output_syms[i]);
assert(phone_seq_check == phone_seq);
vector<vector<int32> > input_syms_long;
for (size_t i = 0; i < input_syms.size(); i++) {
Label isym = input_syms[i];
if (ilabel_info[isym].size() == 0) continue; // epsilon.
if ( (ilabel_info[isym].size() == 1 &&
ilabel_info[isym][0] <= 0) ) continue; // disambig.
input_syms_long.push_back(ilabel_info[isym]);
}
for (size_t i = 0; i < input_syms_long.size(); i++) {
vector<int32> phone_context_window(N); // phone at pos i will be at pos P in this window.
int pos = ((int)i) - P; // pos of first phone in window [ may be out of range] .
for (int j = 0; j < N; j++, pos++) {
if (static_cast<size_t>(pos) < phone_seq.size()) phone_context_window[j] = phone_seq[pos];
else phone_context_window[j] = 0; // 0 is a special symbol that context-dep-itf expects to see
// when no phone is present due to out-of-window. context-fst knows about this too.
}
assert(input_syms_long[i] == phone_context_window);
}
return tot_cost.Value();
}
template<class Arc>
static VectorFst<Arc> *GenRandPhoneSeq(vector<typename Arc::Label> &phone_syms,
vector<typename Arc::Label> &disambig_syms,
typename Arc::Label subsequential_symbol,
int num_subseq_syms,
float seq_prob,
vector<typename Arc::Label> *phoneseq_out) {
KALDI_ASSERT(phoneseq_out != NULL);
typedef typename Arc::Label Label;
// Generate an FST that is a random phone sequence, ending
// with "num_subseq_syms" subsequential symbols. It will
// have disambiguation symbols randomly interspersed throughout.
// The number of phones is random (possibly zero).
size_t len = (kaldi::Rand() % 4) * (kaldi::Rand() % 3); // up to 3*2=6 phones.
float disambig_prob = 0.33;
phoneseq_out->clear();
vector<Label> syms; // the phones
for (size_t i = 0; i < len; i++) {
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
Label phone_id = phone_syms[kaldi::Rand() % phone_syms.size()];
phoneseq_out->push_back(phone_id); // record in output the underlying phone sequence.
syms.push_back(phone_id);
}
for (size_t i = 0; static_cast<int32>(i) < num_subseq_syms; i++) {
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
syms.push_back(subsequential_symbol);
}
while (kaldi::RandUniform() < disambig_prob) syms.push_back(disambig_syms[kaldi::Rand() % disambig_syms.size()]);
// OK, now have the symbols of the FST as a vector.
return GenAcceptorFromSequence<Arc>(syms, seq_prob);
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
// TestContestFst also test ReadILabelInfo and WriteILabelInfo.
static void TestContextFst(bool verbose, bool use_matcher) {
typedef StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
// Generate a random set of phones.
size_t num_phones = 1 + kaldi::Rand() % 10;
std::set<int32> phones_set;
while (phones_set.size() < num_phones) phones_set.insert(1 + kaldi::Rand() % (num_phones + 5)); // don't use 0 [== epsilon]
vector<int32> phones;
kaldi::CopySetToVector(phones_set, &phones);
int N = 1 + kaldi::Rand() % 4; // Context size, in range 1..4.
int P = kaldi::Rand() % N; // 1.. N-1.
if (verbose) std::cout << "N = "<< N << ", P = "<<P<<'\n';
Label subsequential_symbol = 1000;
vector<int32> disambig_syms;
for (size_t i =0; i < 5; i++) disambig_syms.push_back(500 + i);
vector<int32> phone_syms;
for (size_t i = 0; i < phones.size();i++) phone_syms.push_back(phones[i]);
InverseContextFst inv_cfst(subsequential_symbol,
phones, disambig_syms,
N, P);
/* Now create random phone-sequences and compose them with the context FST.
*/
for (size_t p = 0; p < 10; p++) {
vector<int32> phone_seq;
int num_subseq = N - P - 1; // zero if P == N-1, i.e. P is last element, i.e. left-context only.
float tot_cost = 20.0 * kaldi::RandUniform();
VectorFst<Arc> *f = GenRandPhoneSeq<Arc>(phone_syms, disambig_syms, subsequential_symbol, num_subseq, tot_cost, &phone_seq);
if (verbose) {
std::cout << "Sequence FST is:\n";
{ // Try to print the fst.
FstPrinter<Arc> fstprinter(*f, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
}
VectorFst<Arc> fst_composed;
ComposeDeterministicOnDemandInverse(*f, &inv_cfst, &fst_composed);
// Testing WriteILabelInfo and ReadILabelInfo.
{
bool binary = (kaldi::Rand() % 2 == 0);
WriteILabelInfo(kaldi::Output("tmpf", binary).Stream(),
binary, inv_cfst.IlabelInfo());
bool binary_in;
vector<vector<int32> > ilabel_info;
kaldi::Input ki("tmpf", &binary_in);
ReadILabelInfo(ki.Stream(),
binary_in, &ilabel_info);
assert(ilabel_info == inv_cfst.IlabelInfo());
}
if (verbose) {
std::cout << "Composed FST is:\n";
{ // Try to print the fst.
FstPrinter<Arc> fstprinter(fst_composed, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
}
// now check the composed FST.
float tot_cost_check = CheckPhones<Arc>(fst_composed,
phone_syms,
disambig_syms,
phone_seq,
inv_cfst.IlabelInfo(),
N, P);
kaldi::AssertEqual(tot_cost, tot_cost_check);
delete f;
}
unlink("tmpf");
}
} // namespace fst
int main() {
for (int i = 0;i < 16;i++) {
bool verbose = (i < 4);
bool use_matcher = ( (i/4) % 2 == 0);
fst::TestContextFst(verbose, use_matcher);
}
}
@@ -0,0 +1,401 @@
// fstext/context-fst.cc
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/context-fst.h"
#include "base/kaldi-error.h"
namespace fst {
using std::vector;
InverseContextFst::InverseContextFst(
Label subsequential_symbol,
const vector<int32>& phones,
const vector<int32>& disambig_syms,
int32 context_width,
int32 central_position):
context_width_(context_width),
central_position_(central_position),
phone_syms_(phones),
disambig_syms_(disambig_syms),
subsequential_symbol_(subsequential_symbol) {
{ // This block checks the inputs.
KALDI_ASSERT(subsequential_symbol != 0
&& disambig_syms_.count(subsequential_symbol) == 0
&& phone_syms_.count(subsequential_symbol) == 0);
if (phone_syms_.empty())
KALDI_WARN << "Context FST created but there are no phone symbols: probably "
"input FST was empty.";
KALDI_ASSERT(phone_syms_.count(0) == 0 && disambig_syms_.count(0) == 0 &&
central_position_ >= 0 && central_position_ < context_width_);
for (size_t i = 0; i < phones.size(); i++) {
KALDI_ASSERT(disambig_syms_.count(phones[i]) == 0);
}
}
// empty vector, will be the ilabel_info vector that corresponds to epsilon,
// in case our FST needs to output epsilons.
vector<int32> empty_vec;
Label epsilon_label = FindLabel(empty_vec);
// epsilon_vec is the phonetic context window we have at the very start of a
// sequence, meaning "no real phones have been seen yet".
vector<int32> epsilon_vec(context_width_ - 1, 0);
StateId start_state = FindState(epsilon_vec);
KALDI_ASSERT(epsilon_label == 0 && start_state == 0);
if (context_width_ > central_position_ + 1 && !disambig_syms_.empty()) {
// We add a symbol whose sequence representation is [ 0 ], and whose
// symbol-id is 1. This is treated as a disambiguation symbol, we call it
// #-1 in printed form. It is necessary to ensure that all determinizable
// LG's will have determinizable CLG's. The problem it fixes is quite
// subtle-- it relates to reordering of disambiguation symbols (they appear
// earlier in CLG than in LG, relative to phones), and the fact that if a
// disambig symbol appears at the very start of a sequence in CLG, it's not
// clear exatly where it appeared on the corresponding sequence at the input
// of LG.
vector<int32> pseudo_eps_vec;
pseudo_eps_vec.push_back(0);
pseudo_eps_symbol_= FindLabel(pseudo_eps_vec);
KALDI_ASSERT(pseudo_eps_symbol_ == 1);
} else {
pseudo_eps_symbol_ = 0; // use actual epsilon.
}
}
void InverseContextFst::ShiftSequenceLeft(Label label,
std::vector<int32> *phone_seq) {
if (!phone_seq->empty()) {
phone_seq->erase(phone_seq->begin());
phone_seq->push_back(label);
}
}
void InverseContextFst::GetFullPhoneSequence(
const std::vector<int32> &seq, Label label,
std::vector<int32> *full_phone_sequence) {
int32 context_width = context_width_;
full_phone_sequence->reserve(context_width);
full_phone_sequence->insert(full_phone_sequence->end(),
seq.begin(), seq.end());
full_phone_sequence->push_back(label);
for (int32 i = central_position_ + 1; i < context_width; i++) {
if ((*full_phone_sequence)[i] == subsequential_symbol_) {
(*full_phone_sequence)[i] = 0;
}
}
}
InverseContextFst::Weight InverseContextFst::Final(StateId s) {
KALDI_ASSERT(static_cast<size_t>(s) < state_seqs_.size());
const vector<int32> &phone_context = state_seqs_[s];
KALDI_ASSERT(phone_context.size() == context_width_ - 1);
bool has_final_prob;
if (central_position_ < context_width_ - 1) {
has_final_prob = (phone_context[central_position_] == subsequential_symbol_);
// if phone_context[central_position_] != subsequential_symbol_ then we have
// pending phones-in-context that we still need to output, so we need to
// consume more subsequential symbols before we can terminate.
} else {
has_final_prob = true;
}
return has_final_prob ? Weight::One() : Weight::Zero();
}
bool InverseContextFst::GetArc(StateId s, Label ilabel, Arc *arc) {
KALDI_ASSERT(ilabel != 0 && static_cast<size_t>(s) < state_seqs_.size() &&
state_seqs_[s].size() == context_width_ - 1);
if (IsDisambigSymbol(ilabel)) {
// A disambiguation-symbol self-loop arc.
CreateDisambigArc(s, ilabel, arc);
return true;
} else if (IsPhoneSymbol(ilabel)) {
const vector<int32> &seq = state_seqs_[s];
if (!seq.empty() && seq.back() == subsequential_symbol_) {
return false; // A real phone is not allowed to follow the subsequential
// symbol.
}
// next_seq will be 'seq' shifted left by 1, with 'ilabel' appended.
vector<int32> next_seq(seq);
ShiftSequenceLeft(ilabel, &next_seq);
// full-seq will be the full context window of size context_width_.
vector<int32> full_seq;
GetFullPhoneSequence(seq, ilabel, &full_seq);
StateId next_s = FindState(next_seq);
CreatePhoneOrEpsArc(s, next_s, ilabel, full_seq, arc);
return true;
} else if (ilabel == subsequential_symbol_) {
const vector<int32> &seq = state_seqs_[s];
if (central_position_ + 1 == context_width_ ||
seq[central_position_] == subsequential_symbol_) {
// We already had "enough" subsequential symbols in a row and don't want to
// accept any more, or we'd be making the subsequential symbol the central phone.
return false;
}
// full-seq will be the full context window of size context_width_.
vector<int32> full_seq;
GetFullPhoneSequence(seq, ilabel, &full_seq);
vector<int32> next_seq(seq);
ShiftSequenceLeft(ilabel, &next_seq);
StateId next_s = FindState(next_seq);
CreatePhoneOrEpsArc(s, next_s, ilabel, full_seq, arc);
return true;
} else {
KALDI_ERR << "ContextFst: CreateArc, invalid ilabel supplied [confusion "
<< "about phone list or disambig symbols?]: " << ilabel;
}
return false; // won't get here. suppress compiler error.
}
void InverseContextFst::CreateDisambigArc(StateId s, Label ilabel, Arc *arc) {
// Creates a self-loop arc corresponding to the disambiguation symbol.
vector<int32> label_info; // This will be a vector containing just [ -olabel ].
label_info.push_back(-ilabel); // olabel is a disambiguation symbol. Use its negative
// so we can more easily distinguish them from phones.
Label olabel = FindLabel(label_info);
arc->ilabel = ilabel;
arc->olabel = olabel;
arc->weight = Weight::One();
arc->nextstate = s; // self-loop.
}
void InverseContextFst::CreatePhoneOrEpsArc(StateId src, StateId dest,
Label ilabel,
const vector<int32> &phone_seq,
Arc *arc) {
KALDI_PARANOID_ASSERT(phone_seq[central_position_] != subsequential_symbol_);
arc->ilabel = ilabel;
arc->weight = Weight::One();
arc->nextstate = dest;
if (phone_seq[central_position_] == 0) {
// This can happen at the beginning of the graph. In this case we don't
// output a real phone, we createdt an epsilon arc (but sometimes we need to
// use a special disambiguation symbol instead of epsilon).
arc->olabel = pseudo_eps_symbol_;
} else {
// We have a phone in the central position.
arc->olabel = FindLabel(phone_seq);
}
}
StdArc::StateId InverseContextFst::FindState(const vector<int32> &seq) {
// Finds state-id corresponding to this vector of phones. Inserts it if
// necessary.
KALDI_ASSERT(static_cast<int32>(seq.size()) == context_width_ - 1);
VectorToStateMap::const_iterator iter = state_map_.find(seq);
if (iter == state_map_.end()) { // Not already in map.
StateId this_state_id = (StateId)state_seqs_.size();
state_seqs_.push_back(seq);
state_map_[seq] = this_state_id;
return this_state_id;
} else {
return iter->second;
}
}
StdArc::Label InverseContextFst::FindLabel(const vector<int32> &label_vec) {
// Finds the ilabel corresponding to this vector (creates a new ilabel if
// necessary).
VectorToLabelMap::const_iterator iter = ilabel_map_.find(label_vec);
if (iter == ilabel_map_.end()) { // Not already in map.
Label this_label = ilabel_info_.size();
ilabel_info_.push_back(label_vec);
ilabel_map_[label_vec] = this_label;
return this_label;
} else {
return iter->second;
}
}
void ComposeContext(const vector<int32> &disambig_syms_in,
int32 context_width, int32 central_position,
VectorFst<StdArc> *ifst,
VectorFst<StdArc> *ofst,
vector<vector<int32> > *ilabels_out,
bool project_ifst) {
KALDI_ASSERT(ifst != NULL && ofst != NULL);
KALDI_ASSERT(context_width > 0);
KALDI_ASSERT(central_position >= 0);
KALDI_ASSERT(central_position < context_width);
vector<int32> disambig_syms(disambig_syms_in);
std::sort(disambig_syms.begin(), disambig_syms.end());
vector<int32> all_syms;
GetInputSymbols(*ifst, false/*no eps*/, &all_syms);
std::sort(all_syms.begin(), all_syms.end());
vector<int32> phones;
for (size_t i = 0; i < all_syms.size(); i++)
if (!std::binary_search(disambig_syms.begin(),
disambig_syms.end(), all_syms[i]))
phones.push_back(all_syms[i]);
// Get subsequential symbol that does not clash with
// any disambiguation symbol or symbol in the FST.
int32 subseq_sym = 1;
if (!all_syms.empty())
subseq_sym = std::max(subseq_sym, all_syms.back() + 1);
if (!disambig_syms.empty())
subseq_sym = std::max(subseq_sym, disambig_syms.back() + 1);
// if central_position == context_width-1, it's left-context, and no
// subsequential symbol is needed.
if (central_position != context_width-1) {
AddSubsequentialLoop(subseq_sym, ifst);
if (project_ifst) {
fst::Project(ifst, fst::PROJECT_INPUT);
}
}
InverseContextFst inv_c(subseq_sym, phones, disambig_syms,
context_width, central_position);
// The following statement is equivalent to the following
// (if FSTs had the '*' operator for composition):
// (*ofst) = inv(inv_c) * (*ifst)
ComposeDeterministicOnDemandInverse(*ifst, &inv_c, ofst);
inv_c.SwapIlabelInfo(ilabels_out);
}
void AddSubsequentialLoop(StdArc::Label subseq_symbol,
MutableFst<StdArc> *fst) {
typedef StdArc Arc;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
vector<StateId> final_states;
for (StateIterator<MutableFst<Arc> > siter(*fst); !siter.Done(); siter.Next()) {
StateId s = siter.Value();
if (fst->Final(s) != Weight::Zero()) final_states.push_back(s);
}
StateId superfinal = fst->AddState();
Arc arc(subseq_symbol, 0, Weight::One(), superfinal);
fst->AddArc(superfinal, arc); // loop at superfinal.
fst->SetFinal(superfinal, Weight::One());
for (size_t i = 0; i < final_states.size(); i++) {
StateId s = final_states[i];
fst->AddArc(s, Arc(subseq_symbol, 0, fst->Final(s), superfinal));
// No, don't remove the final-weights of the original states..
// this is so we can add the subsequential loop in cases where
// there is no context, and it won't hurt.
// fst->SetFinal(s, Weight::Zero());
arc.nextstate = final_states[i];
}
}
void WriteILabelInfo(std::ostream &os, bool binary,
const vector<vector<int32> > &info) {
int32 size = info.size();
kaldi::WriteBasicType(os, binary, size);
for (int32 i = 0; i < size; i++) {
kaldi::WriteIntegerVector(os, binary, info[i]);
}
}
void ReadILabelInfo(std::istream &is, bool binary,
vector<vector<int32> > *info) {
int32 size = info->size();
kaldi::ReadBasicType(is, binary, &size);
info->resize(size);
for (int32 i = 0; i < size; i++) {
kaldi::ReadIntegerVector(is, binary, &((*info)[i]));
}
}
SymbolTable *CreateILabelInfoSymbolTable(const vector<vector<int32> > &info,
const SymbolTable &phones_symtab,
std::string separator,
std::string initial_disambig) { // e.g. separator = "/", initial-disambig="#-1"
KALDI_ASSERT(!info.empty() && info[0].empty());
SymbolTable *ans = new SymbolTable("ilabel-info-symtab");
int64 s = ans->AddSymbol(phones_symtab.Find(static_cast<int64>(0)));
assert(s == 0);
for (size_t i = 1; i < info.size(); i++) {
if (info[i].size() == 0) {
KALDI_ERR << "Invalid ilabel-info";
}
if (info[i].size() == 1 &&
info[i][0] <= 0) {
if (info[i][0] == 0) { // special symbol at start that we want to call #-1.
s = ans->AddSymbol(initial_disambig);
if (s != i) {
KALDI_ERR << "Disambig symbol " << initial_disambig
<< " already in vocab";
}
} else {
std::string disambig_sym = phones_symtab.Find(-info[i][0]);
if (disambig_sym == "") {
KALDI_ERR << "Disambig symbol " << -info[i][0]
<< " not in phone symbol-table";
}
s = ans->AddSymbol(disambig_sym);
if (s != i) {
KALDI_ERR << "Disambig symbol " << disambig_sym
<< " already in vocab";
}
}
} else {
// is a phone-context-window.
std::string newsym;
for (size_t j = 0; j < info[i].size(); j++) {
std::string phonesym = phones_symtab.Find(info[i][j]);
if (phonesym == "") {
KALDI_ERR << "Symbol " << info[i][j]
<< " not in phone symbol-table";
}
if (j != 0) newsym += separator;
newsym += phonesym;
}
int64 s = ans->AddSymbol(newsym);
if (s != static_cast<int64>(i)) {
KALDI_ERR << "Some problem with duplicate symbols";
}
}
}
return ans;
}
} // end namespace fst
@@ -0,0 +1,340 @@
// fstext/context-fst.h
// Copyright 2009-2011 Microsoft Corporation
// 2018 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
//
// This file includes material from the OpenFST Library v1.2.7 available at
// http://www.openfst.org and released under the Apache License Version 2.0.
//
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright 2005-2010 Google, Inc.
// Author: riley@google.com (Michael Riley)
#ifndef KALDI_FSTEXT_CONTEXT_FST_H_
#define KALDI_FSTEXT_CONTEXT_FST_H_
/* This header defines a context FST "C" (the "C" in "HCLG") which transduces
from symbols representing phone context windows (e.g. "a, b, c") to
individual phones, e.g. "a". Search for "hbka.pdf" ("Speech Recognition
with Weighted Finite State Transducers") by M. Mohri, for more context.
*/
#include <unordered_map>
using std::unordered_map;
#include <algorithm>
#include <string>
#include <vector>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "util/const-integer-set.h"
#include "fstext/deterministic-fst.h"
namespace fst {
/// Utility function for writing ilabel-info vectors to disk.
void WriteILabelInfo(std::ostream &os, bool binary,
const std::vector<std::vector<int32> > &ilabel_info);
/// Utility function for reading ilabel-info vectors from disk.
void ReadILabelInfo(std::istream &is, bool binary,
std::vector<std::vector<int32> > *ilabel_info);
/// The following function is mainly of use for printing and debugging.
SymbolTable *CreateILabelInfoSymbolTable(const std::vector<std::vector<int32> > &ilabel_info,
const SymbolTable &phones_symtab,
std::string separator,
std::string disambig_prefix); // e.g. separator = "/", disambig_prefix = "#"
/**
Used in the command-line tool fstcomposecontext. It creates a context FST and
composes it on the left with "ifst" to make "ofst". It outputs the label
information to ilabels_out. "ifst" is mutable because we need to add the
subsequential loop.
@param [in] disambig_syms List of disambiguation symbols, e.g. the integer
ids of #0, #1, #2 ... in the phones.txt.
@param [in] context_width Size of context window, e.g. 3 for triphone.
@param [in] central_position Central position in phonetic context window
(zero-based index), e.g. 1 for triphone.
@param [in,out] ifst The FST we are composing with C (e.g. LG.fst), mustable because
we need to add the subsequential loop to it.
@param [out] ofst Composed output FST (would be CLG.fst).
@param [out] ilabels_out Vector, indexed by ilabel of CLG.fst, providing information
about the meaning of that ilabel; see
"http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
@param [in] project_ifst This is intended only to be set to true
in the program 'fstmakecontextfst'... if true, it will
project on the input after adding the subsequential loop
to 'ifst', which allows us to reconstruct the context
fst C.fst.
*/
void ComposeContext(const std::vector<int32> &disambig_syms,
int32 context_width, int32 central_position,
VectorFst<StdArc> *ifst,
VectorFst<StdArc> *ofst,
std::vector<std::vector<int32> > *ilabels_out,
bool project_ifst = false);
/**
Modifies an FST so that it transuces the same paths, but the input side of the
paths can all have the subsequential symbol '$' appended to them any number of
times (we could easily specify the number of times, but accepting any number of
repetitions is just more convenient). The actual way we do this is for each
final state, we add a transition with weight equal to the final-weight of that
state, with input-symbol '$' and output-symbols \<eps\>, and ending in a new
super-final state that has unit final-probability and a unit-weight self-loop
with '$' on its input and \<eps\> on its output. The reason we don't just
add a loop to each final-state has to do with preserving stochasticity
(see \ref fst_algo_stochastic). We keep the final-probability in all the
original final-states rather than setting them to zero, so the resulting FST
can accept zero '$' symbols at the end (in case we had no right context).
*/
void AddSubsequentialLoop(StdArc::Label subseq_symbol,
MutableFst<StdArc> *fst);
/*
InverseContextFst represents the inverse of the context FST "C" (the "C" in
"HCLG") which transduces from symbols representing phone context windows
(e.g. "a, b, c") to individual phones, e.g. "a". So InverseContextFst
transduces from phones to symbols representing phone context windows. The
point is that the inverse is deterministic, so the DeterministicOnDemandFst
interface is applicable, which turns out to be a convenient way to implement
this.
This doesn't implement the full Fst interface, it implements the
DeterministicOnDemandFst interface which is much simpler and which is
sufficient for what we need to do with this.
Search for "hbka.pdf" ("Speech Recognition with Weighted Finite State
Transducers") by M. Mohri, for more context.
*/
class InverseContextFst: public DeterministicOnDemandFst<StdArc> {
public:
typedef StdArc Arc;
typedef typename StdArc::StateId StateId;
typedef typename StdArc::Weight Weight;
typedef typename StdArc::Label Label;
/**
Constructor.
@param [in] subsequential_symbol The integer id of the 'subsequential symbol'
(usually represented as '$') that terminates sequences on the
output of C.fst (input of InverseContextFst). Search for
"quential" in https://cs.nyu.edu/~mohri/pub/hbka.pdf.
This may just be the first unused integer id. Must be nonzer.
@param [in] phones List of integer ids of phones, as you would see in phones.txt
@param [in] disambig_syms List of integer ids of disambiguation symbols,
e.g. the ids of #0, #1, #2 in phones.txt
@param [in] context_width Size of context window, e.g. 3 for triphone.
@param [in] central_position Central position in context window (zero-based),
e.g. 1 for triphone.
See \ref graph_context for more details.
*/
InverseContextFst(Label subsequential_symbol,
const std::vector<int32>& phones,
const std::vector<int32>& disambig_syms,
int32 context_width,
int32 central_position);
virtual StateId Start() { return 0; }
virtual Weight Final(StateId s);
/// Note: ilabel must not be epsilon.
virtual bool GetArc(StateId s, Label ilabel, Arc *arc);
~InverseContextFst() { }
// Returns a reference to a vector<vector<int32> > with information about all
// the input symbols of C (i.e. all the output symbols of this
// InverseContextFst). See
// "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
const std::vector<std::vector<int32> > &IlabelInfo() const {
return ilabel_info_;
}
// A way to destructively obtain the ilabel-info. Only do this if you
// are just about to destroy this object.
void SwapIlabelInfo(std::vector<std::vector<int32> > *vec) { ilabel_info_.swap(*vec); }
private:
/// Returns the state-id corresponding to this vector of phones; creates the
/// state it if necessary. Requires seq.size() == context_width_ - 1.
StateId FindState(const std::vector<int32> &seq);
/// Finds the label index corresponding to this context-window of phones
/// (likely of width context_width_). Inserts it into the
/// ilabel_info_/ilabel_map_ tables if necessary.
Label FindLabel(const std::vector<int32> &label_info);
inline bool IsDisambigSymbol(Label lab) { return (disambig_syms_.count(lab) != 0); }
inline bool IsPhoneSymbol(Label lab) { return (phone_syms_.count(lab) != 0); }
/// Create disambiguation-symbol self-loop arc; where 'ilabel' must correspond to
/// a disambiguation symbol. Called from CreateArc().
inline void CreateDisambigArc(StateId s, Label ilabel, Arc *arc);
/// Creates an arc, this function is to be called only when 'ilabel'
/// corresponds to a phone. Called from CreateArc(). The olabel may end be
/// epsilon, instead of a phone-in-context, if the system has right context
/// and we are very near the beginning of the phone sequence.
inline void CreatePhoneOrEpsArc(StateId src, StateId dst, Label ilabel,
const std::vector<int32> &phone_seq, Arc *arc);
/// If phone_seq is nonempty then this function it left by one and appends
/// 'label' to it, otherwise it does nothing. We expect (but do not check)
/// that phone_seq->size() == context_width_ - 1.
inline void ShiftSequenceLeft(Label label, std::vector<int32> *phone_seq);
/// This utility function does something equivalent to the following 3 steps:
/// *full_phone_sequence = seq;
/// full_phone_sequence->append(label)
/// Replace any values equal to 'subsequential_symbol_' in
/// full_phone_sequence with zero (this is to avoid having to keep track of
/// the value of 'subsequential_symbol_' outside of this program).
/// This function assumes that seq.size() == context_width_ - 1, and also that
/// 'subsequential_symbol_' does not appear in positions 0 through
/// central_position_ of 'seq'.
inline void GetFullPhoneSequence(const std::vector<int32> &seq, Label label,
std::vector<int32> *full_phone_sequence);
// Map type to map from vectors of int32 (representing phonetic contexts,
// which will be of dimension context_width - 1) to StateId (corresponding to
// the state index in this FST).
typedef unordered_map<std::vector<int32>, StateId,
kaldi::VectorHasher<int32> > VectorToStateMap;
// Map type to map from vectors of int32 (representing ilabel-info,
// see http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel) to
// Label (the output label in this FST).
typedef unordered_map<std::vector<int32>, Label,
kaldi::VectorHasher<int32> > VectorToLabelMap;
// Sometimes called N, context_width_ this is the width of the
// phonetic context, e.g. 3 for triphone, 2 for biphone, one for monophone.
// It is a user-specified value.
int32 context_width_;
// Sometimes called P, central_position_ is is the (zero-based) "central
// position" in the context window, meaning the phone that is "in" a certain
// context. The most widely used values of (context-width, central-position)
// are: (3,1) for triphone, (1,0) for monophone, and (2, 1) for left biphone.
// This is also specified by the user. As an example, in the left-biphone
// [ 5, 6 ], we view it as "the phone numbered 6 with the phone numbered 5 as
// its left-context".
int32 central_position_;
// The following three variables were also passed in by the caller:
// 'phone_syms_' are a set of phone-ids, typically 1, 2, .. num_phones.
kaldi::ConstIntegerSet<Label> phone_syms_;
// disambig_syms_ is the set of integer ids of the disambiguation symbols,
// usually represented in text form as #0, #1, #2, etc. These are inserted
// into the grammar (for #0) and the lexicon (for #1, #2, ...) in order to
// make the composed FSTs determinizable. They are treated "specially" by the
// context FST in that they are not part of the context, they are just "passed
// through" via self-loops. See the Mohri chapter mrentioned above for more
// information.
kaldi::ConstIntegerSet<Label> disambig_syms_;
// subsequential_symbol_, represented as "$" in the Mohri chapter mentioned
// above, is something which terminates phonetic sequences to force out the
// last phones-in-context. In our implementation it's added to det(LG) as a
// self-loop on final states before composing with C.
// (c.f. AddSubsequentialLoop()).
Label subsequential_symbol_;
// pseudo_eps_symbol_, which in printed form we refer to as "#-1", is a symbol that
// appears on the ilabels of the context transducer C, i.e. the olabels of this
// FST which is C's inverse. It is a symbol we introduce to solve a special problem
// in systems with right-context (context_width_ > central_position_ + 1) that
// use disambiguation symbols. It exists to prevent CLG from being nondeterminizable.
//
// The issue is that, in this case, the disambiguation symbols are shifted
// left w.r.t. the phones, and there becomes an ambiguity, if a disambiguation
// symbol appears at the start of a sequence on the input of CLG, about
// whether it was at the very start of the input of LG, or just after, say,
// the first real phone. This can lead to determinization failure under
// certain circumstances. What we do if we need pseudo_eps_symbol_ to be not
// epsilon, we create a special symbol with symbol-id 1 and sequence
// representation (ilabels entry) [ 0 ] .
int32 pseudo_eps_symbol_;
// maps from vector<int32>, representing phonetic contexts of length
// context_width_ - 1, to StateId. (The states of the "C" fst correspond to
// phonetic contexts, but we only create them as and when they are needed).
VectorToStateMap state_map_;
// The inverse of 'state_map_': gives us the phonetic context corresponding to
// each state-id.
std::vector<std::vector<int32> > state_seqs_;
// maps from vector<int32>, representing phonetic contexts of length
// context_width_ - 1, to Label. These are actually the output labels of this
// InverseContextFst (because of the "Inverse" part), but for historical
// reasons and because we've used the term ilabels" in the documentation, we
// still call these "ilabels").
VectorToLabelMap ilabel_map_;
// ilabel_info_ is the reverse map of ilabel_map_.
// Indexed by olabel (although we call this ilabel_info_ for historical
// reasons and because is for the ilabels of C), ilabel_info_[i] gives
// information about the meaning of each symbol on the input of C
// aka the output of inv(C).
// See "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
std::vector<std::vector<int32> > ilabel_info_;
};
} // namespace fst
#endif // KALDI_FSTEXT_CONTEXT_FST_H_
@@ -0,0 +1,512 @@
// fstext/deterministic-fst-inl.h
// Copyright 2011-2012 Gilles Boulianne
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
// 2012-2015 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_DETERMINISTIC_FST_INL_H_
#define KALDI_FSTEXT_DETERMINISTIC_FST_INL_H_
#include "base/kaldi-common.h"
#include "fstext/fstext-utils.h"
namespace fst {
// Do not include this file directly. It is included by deterministic-fst.h.
template<class Arc>
typename Arc::StateId
BackoffDeterministicOnDemandFst<Arc>::GetBackoffState(StateId s,
Weight *w) {
ArcIterator<Fst<Arc> > aiter(fst_, s);
if (aiter.Done()) // no arcs.
return kNoStateId;
const Arc &arc = aiter.Value();
if (arc.ilabel == 0) {
*w = arc.weight;
return arc.nextstate;
} else {
return kNoStateId;
}
}
template<class Arc>
typename Arc::Weight BackoffDeterministicOnDemandFst<Arc>::Final(StateId state) {
Weight w = fst_.Final(state);
if (w != Weight::Zero()) return w;
Weight backoff_w;
StateId backoff_state = GetBackoffState(state, &backoff_w);
if (backoff_state == kNoStateId) return Weight::Zero();
else return Times(backoff_w, this->Final(backoff_state));
}
template<class Arc>
BackoffDeterministicOnDemandFst<Arc>::BackoffDeterministicOnDemandFst(
const Fst<Arc> &fst): fst_(fst) {
#ifdef KALDI_PARANOID
KALDI_ASSERT(fst_.Properties(kILabelSorted|kIDeterministic, true) ==
(kILabelSorted|kIDeterministic) &&
"Input FST is not i-label sorted and deterministic.");
#endif
}
template<class Arc>
bool BackoffDeterministicOnDemandFst<Arc>::GetArc(
StateId s, Label ilabel, Arc *oarc) {
KALDI_ASSERT(ilabel != 0); // We don't allow GetArc for epsilon.
SortedMatcher<Fst<Arc> > sm(fst_, MATCH_INPUT, 1);
sm.SetState(s);
if (sm.Find(ilabel)) {
const Arc &arc = sm.Value();
*oarc = arc;
return true;
} else {
Weight backoff_w;
StateId backoff_state = GetBackoffState(s, &backoff_w);
if (backoff_state == kNoStateId) return false;
if (!this->GetArc(backoff_state, ilabel, oarc)) return false;
oarc->weight = Times(oarc->weight, backoff_w);
return true;
}
}
template<class Arc>
UnweightedNgramFst<Arc>::UnweightedNgramFst(int n): n_(n) {
// Starting state is an empty vector
std::vector<Label> start_state;
state_vec_.push_back(start_state);
start_state_ = 0;
state_map_[start_state] = 0;
}
template<class Arc>
bool UnweightedNgramFst<Arc>::GetArc(
StateId s, Label ilabel, Arc *oarc) {
// The state ids increment with each state we encounter.
// if the assert fails, then we are trying to access
// unseen states that are not immediately traversable.
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
std::vector<Label> seq = state_vec_[s];
// Update state info.
seq.push_back(ilabel);
if (seq.size() > n_-1) {
// Remove oldest word in the history.
seq.erase(seq.begin());
}
std::pair<const std::vector<Label>, StateId> new_state(
seq,
static_cast<Label>(state_vec_.size()));
// Now get state id for destination state.
typedef typename MapType::iterator IterType;
std::pair<IterType, bool> result = state_map_.insert(new_state);
if (result.second == true) {
state_vec_.push_back(seq);
}
oarc->weight = Weight::One(); // Because the FST is unweightd.
oarc->ilabel = ilabel;
oarc->olabel = ilabel;
oarc->nextstate = result.first->second; // The next state id.
// All arcs can be matched.
return true;
}
template<class Arc>
typename Arc::Weight UnweightedNgramFst<Arc>::Final(StateId state) {
KALDI_ASSERT(state < static_cast<StateId>(state_vec_.size()));
return Weight::One();
}
template<class Arc>
ComposeDeterministicOnDemandFst<Arc>::ComposeDeterministicOnDemandFst(
DeterministicOnDemandFst<Arc> *fst1,
DeterministicOnDemandFst<Arc> *fst2): fst1_(fst1), fst2_(fst2) {
KALDI_ASSERT(fst1 != NULL && fst2 != NULL);
if (fst1_->Start() == -1 || fst2_->Start() == -1) {
start_state_ = -1;
next_state_ = 0; // actually we don't care about this value.
} else {
start_state_ = 0;
std::pair<StateId,StateId> start_pair(fst1_->Start(), fst2_->Start());
state_map_[start_pair] = start_state_;
state_vec_.push_back(start_pair);
next_state_ = 1;
}
}
template<class Arc>
typename Arc::Weight ComposeDeterministicOnDemandFst<Arc>::Final(StateId s) {
KALDI_ASSERT(s < static_cast<StateId>(state_vec_.size()));
const std::pair<StateId, StateId> &pr (state_vec_[s]);
return Times(fst1_->Final(pr.first), fst2_->Final(pr.second));
}
template<class Arc>
bool ComposeDeterministicOnDemandFst<Arc>::GetArc(StateId s, Label ilabel,
Arc *oarc) {
typedef typename MapType::iterator IterType;
KALDI_ASSERT(ilabel != 0 &&
"This program expects epsilon-free compact lattices as input");
KALDI_ASSERT(s < static_cast<StateId>(state_vec_.size()));
const std::pair<StateId, StateId> pr (state_vec_[s]);
Arc arc1;
if (!fst1_->GetArc(pr.first, ilabel, &arc1)) return false;
if (arc1.olabel == 0) { // There is no output label on the
// arc, so only the first state changes.
std::pair<const std::pair<StateId, StateId>, StateId> new_value(
std::pair<StateId, StateId>(arc1.nextstate, pr.second),
next_state_);
std::pair<IterType, bool> result = state_map_.insert(new_value);
oarc->ilabel = ilabel;
oarc->olabel = 0;
oarc->nextstate = result.first->second;
oarc->weight = arc1.weight;
if (result.second == true) { // was inserted
next_state_++;
const std::pair<StateId, StateId> &new_pair (new_value.first);
state_vec_.push_back(new_pair);
}
return true;
}
// There is an output label, so we need to traverse an arc on the
// second fst also.
Arc arc2;
if (!fst2_->GetArc(pr.second, arc1.olabel, &arc2)) return false;
std::pair<const std::pair<StateId, StateId>, StateId> new_value(
std::pair<StateId, StateId>(arc1.nextstate, arc2.nextstate),
next_state_);
std::pair<IterType, bool> result =
state_map_.insert(new_value);
oarc->ilabel = ilabel;
oarc->olabel = arc2.olabel;
oarc->nextstate = result.first->second;
oarc->weight = Times(arc1.weight, arc2.weight);
if (result.second == true) { // was inserted
next_state_++;
const std::pair<StateId, StateId> &new_pair (new_value.first);
state_vec_.push_back(new_pair);
}
return true;
}
template<class Arc>
inline size_t CacheDeterministicOnDemandFst<Arc>::GetIndex(
StateId src_state, Label ilabel) {
const StateId p1 = 26597, p2 = 50329; // these are two
// values that I drew at random from a table of primes.
// note: num_cached_arcs_ > 0.
// We cast to size_t before the modulus, to ensure the
// result is positive.
return static_cast<size_t>(src_state * p1 + ilabel * p2) %
static_cast<size_t>(num_cached_arcs_);
}
template<class Arc>
CacheDeterministicOnDemandFst<Arc>::CacheDeterministicOnDemandFst(
DeterministicOnDemandFst<Arc> *fst,
StateId num_cached_arcs): fst_(fst),
num_cached_arcs_(num_cached_arcs),
cached_arcs_(num_cached_arcs) {
KALDI_ASSERT(num_cached_arcs > 0);
for (StateId i = 0; i < num_cached_arcs; i++)
cached_arcs_[i].first = kNoStateId; // Invalidate all elements of the cache.
}
template<class Arc>
bool CacheDeterministicOnDemandFst<Arc>::GetArc(StateId s, Label ilabel,
Arc *oarc) {
// Note: we don't cache anything in case a requested arc does not exist.
// In the uses that we imagine this will be put to, essentially all the
// requested arcs will exist. This only affects efficiency.
KALDI_ASSERT(s >= 0 && ilabel != 0);
size_t index = this->GetIndex(s, ilabel);
if (cached_arcs_[index].first == s &&
cached_arcs_[index].second.ilabel == ilabel) {
*oarc = cached_arcs_[index].second;
return true;
} else {
Arc arc;
if (fst_->GetArc(s, ilabel, &arc)) {
cached_arcs_[index].first = s;
cached_arcs_[index].second = arc;
*oarc = arc;
return true;
} else {
return false;
}
}
}
template<class Arc>
LmExampleDeterministicOnDemandFst<Arc>::LmExampleDeterministicOnDemandFst(
void *lm, Label bos_symbol, Label eos_symbol):
lm_(lm), bos_symbol_(bos_symbol), eos_symbol_(eos_symbol) {
std::vector<Label> begin_state; // history state corresponding to beginning of sentence
begin_state.push_back(bos_symbol); // Depending how your LM is set up, you might
// want to have a history vector with more than one bos_symbol on it.
state_vec_.push_back(begin_state);
start_state_ = 0;
state_map_[begin_state] = 0;
}
template<class Arc>
typename Arc::Weight LmExampleDeterministicOnDemandFst<Arc>::Final(StateId s) {
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
// In a real version you would probably use the following variable somehow
// (commenting it because it's generating warnings).
// const std::vector<Label> &wseq = state_vec_[s];
float log_prob = -0.5; // e.g. log_prob = lm->GetLogProb(wseq, eos_symbol_);
return Weight(-log_prob); // assuming weight is FloatWeight.
}
template<class Arc>
bool LmExampleDeterministicOnDemandFst<Arc>::GetArc(
StateId s, Label ilabel, Arc *oarc) {
KALDI_ASSERT(static_cast<size_t>(s) < state_vec_.size());
std::vector<Label> wseq = state_vec_[s];
float log_prob = -0.25; // e.g. log_prob = lm->GetLogProb(wseq, ilabel);
wseq.push_back(ilabel); // the code might be different if your histories are the
// other way around.
while (0) { // e.g. while !lm->HistoryStateExists(wseq)
wseq.erase(wseq.begin(), wseq.begin() + 1); // remove most distant element of history.
// note: if your histories are the other way round, you might just do
// wseq.pop() here.
}
if (log_prob == -std::numeric_limits<float>::infinity()) { // assume this
// is what happens if prob of the word is zero. Some LMs will never
// return zero.
return false; // no arc.
}
std::pair<const std::vector<Label>, StateId> new_value(
wseq,
static_cast<Label>(state_vec_.size()));
// Now get state id for destination state.
typedef typename MapType::iterator IterType;
std::pair<IterType, bool> result = state_map_.insert(new_value);
if (result.second == true) // was inserted
state_vec_.push_back(wseq);
oarc->ilabel = ilabel;
oarc->olabel = ilabel;
oarc->nextstate = result.first->second; // the next-state id.
oarc->weight = Weight(-log_prob);
return true;
}
template<class Arc>
void ComposeDeterministicOnDemand(const Fst<Arc> &fst1,
DeterministicOnDemandFst<Arc> *fst2,
MutableFst<Arc> *fst_composed) {
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
typedef std::pair<StateId, StateId> StatePair;
typedef unordered_map<StatePair, StateId,
kaldi::PairHasher<StateId> > MapType;
typedef typename MapType::iterator IterType;
fst_composed->DeleteStates();
MapType state_map;
std::queue<StatePair> state_queue;
// Set start state in fst_composed.
StateId s1 = fst1.Start(),
s2 = fst2->Start(),
start_state = fst_composed->AddState();
StatePair start_pair(s1, s2);
state_queue.push(start_pair);
fst_composed->SetStart(start_state);
// A mapping between pairs of states in fst1 and fst2 and the corresponding
// state in fst_composed.
std::pair<const StatePair, StateId> start_map(start_pair, start_state);
std::pair<IterType, bool> result = state_map.insert(start_map);
KALDI_ASSERT(result.second == true);
while (!state_queue.empty()) {
StatePair q = state_queue.front();
StateId q1 = q.first,
q2 = q.second;
state_queue.pop();
// If the product of the final weights of the two fsts is non-zero then
// we can set a final-prob in fst_composed
Weight final_weight = Times(fst1.Final(q1), fst2->Final(q2));
if (final_weight != Weight::Zero()) {
KALDI_ASSERT(state_map.find(q) != state_map.end());
fst_composed->SetFinal(state_map[q], final_weight);
}
// for each pair of edges from fst1 and fst2 at q1 and q2.
for (ArcIterator<Fst<Arc> > aiter(fst1, q1); !aiter.Done(); aiter.Next()) {
const Arc &arc1 = aiter.Value();
Arc arc2;
StatePair next_pair;
StateId next_state1 = arc1.nextstate,
next_state2,
next_state;
// If there is an epsilon on the arc of fst1 we transition to the next
// state but keep fst2 at the current state.
if (arc1.olabel == 0) {
next_state2 = q2;
} else {
bool match = fst2->GetArc(q2, arc1.olabel, &arc2);
if (!match) // There is no matching arc -> nothing to do.
continue;
next_state2 = arc2.nextstate;
}
next_pair = StatePair(next_state1, next_state2);
IterType sitr = state_map.find(next_pair);
// If sitr == state_map.end() then the state isn't in fst_composed yet.
if (sitr == state_map.end()) {
next_state = fst_composed->AddState();
std::pair<const StatePair, StateId> new_state(
next_pair, next_state);
std::pair<IterType, bool> result = state_map.insert(new_state);
// Since we already checked if state_map contained new_state,
// it should always be added if we reach here.
KALDI_ASSERT(result.second == true);
state_queue.push(next_pair);
// If sitr != state_map.end() then the next state is already in
// the state_map.
} else {
next_state = sitr->second;
}
if (arc1.olabel == 0) {
fst_composed->AddArc(state_map[q], Arc(arc1.ilabel, 0, arc1.weight,
next_state));
} else {
fst_composed->AddArc(state_map[q], Arc(arc1.ilabel, arc2.olabel,
Times(arc1.weight, arc2.weight), next_state));
}
}
}
}
// we are doing *fst_composed = Compose(Inverse(*left), right).
template<class Arc>
void ComposeDeterministicOnDemandInverse(const Fst<Arc> &right,
DeterministicOnDemandFst<Arc> *left,
MutableFst<Arc> *fst_composed) {
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
typedef std::pair<StateId, StateId> StatePair;
typedef unordered_map<StatePair, StateId,
kaldi::PairHasher<StateId> > MapType;
typedef typename MapType::iterator IterType;
fst_composed->DeleteStates();
// the queue and map contain pairs (state-in-left, state-in-right)
MapType state_map;
std::queue<StatePair> state_queue;
// Set start state in fst_composed.
StateId s_left = left->Start(),
s_right = right.Start();
if (s_left == kNoStateId || s_right == kNoStateId)
return; // Empty result.
StatePair start_pair(s_left, s_right);
StateId start_state = fst_composed->AddState();
state_queue.push(start_pair);
fst_composed->SetStart(start_state);
// A mapping between pairs of states in *left and right, and the corresponding
// state in fst_composed.
std::pair<const StatePair, StateId> start_map(start_pair, start_state);
std::pair<IterType, bool> result = state_map.insert(start_map);
KALDI_ASSERT(result.second == true);
while (!state_queue.empty()) {
StatePair q = state_queue.front();
StateId q_left = q.first,
q_right = q.second;
state_queue.pop();
// If the product of the final weights of the two fsts is non-zero then
// we can set a final-prob in fst_composed
Weight final_weight = Times(left->Final(q_left), right.Final(q_right));
if (final_weight != Weight::Zero()) {
KALDI_ASSERT(state_map.find(q) != state_map.end());
fst_composed->SetFinal(state_map[q], final_weight);
}
for (ArcIterator<Fst<Arc> > aiter(right, q_right); !aiter.Done(); aiter.Next()) {
const Arc &arc_right = aiter.Value();
Arc arc_left;
StatePair next_pair;
StateId next_state_right = arc_right.nextstate,
next_state_left,
next_state;
// If there is an epsilon on the input side of the rigth arc, we
// transition to the next state of the output but keep 'left' at the
// current state.
if (arc_right.ilabel == 0) {
next_state_left = q_left;
} else {
bool match = left->GetArc(q_left, arc_right.ilabel, &arc_left);
if (!match) // There is no matching arc -> nothing to do.
continue;
// the next 'swap' is because we are composing with the inverse of
// *left. Just removing the swap statement wouldn't let us compose
// with non-inverted *left though, because the GetArc function call
// above interprets the second argument as an ilabel not an olabel.
std::swap(arc_left.ilabel, arc_left.olabel);
next_state_left = arc_left.nextstate;
}
next_pair = StatePair(next_state_left, next_state_right);
IterType sitr = state_map.find(next_pair);
// If sitr == state_map.end() then the state isn't in fst_composed yet.
if (sitr == state_map.end()) {
next_state = fst_composed->AddState();
std::pair<const StatePair, StateId> new_state(
next_pair, next_state);
std::pair<IterType, bool> result = state_map.insert(new_state);
// Since we already checked if state_map contained new_state,
// it should always be added if we reach here.
KALDI_ASSERT(result.second == true);
state_queue.push(next_pair);
// If sitr != state_map.end() then the next state is already in
// the state_map.
} else {
next_state = sitr->second;
}
if (arc_right.ilabel == 0) {
// we didn't get an actual arc from the left FST.
fst_composed->AddArc(state_map[q], Arc(0, arc_right.olabel,
arc_right.weight,
next_state));
} else {
fst_composed->AddArc(state_map[q],
Arc(arc_left.ilabel, arc_right.olabel,
Times(arc_left.weight, arc_right.weight),
next_state));
}
}
}
}
} // end namespace fst
#endif
@@ -0,0 +1,232 @@
// fstext/deterministic-fst-test.cc
// Copyright 2009-2011 Gilles Boulianne
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/deterministic-fst.h"
#include "fstext/fst-test-utils.h"
#include "util/kaldi-io.h"
#include <sys/stat.h>
namespace fst {
using std::cout;
using std::cerr;
using std::endl;
bool FileExists(std::string strFilename) {
struct stat stFileInfo;
bool blnReturn;
int intStat;
// Attempt to get the file attributes
intStat = stat(strFilename.c_str(), &stFileInfo);
if (intStat == 0) {
// We were able to get the file attributes
// so the file obviously exists.
blnReturn = true;
} else {
// We were not able to get the file attributes.
// This may mean that we don't have permission to
// access the folder which contains this file. If you
// need to do that level of checking, lookup the
// return values of stat which will give you
// more details on why stat failed.
blnReturn = false;
}
return blnReturn;
}
// Simplify writing
typedef fst::StdArc StdArc;
typedef fst::StdArc::Label Label;
typedef fst::StdArc::StateId StateId;
typedef fst::StdVectorFst StdVectorFst;
typedef fst::StdArc::Weight Weight;
// something that looks like a language model FST with epsilon backoffs
StdVectorFst* CreateBackoffFst() {
StdVectorFst *fst = new StdVectorFst();
fst->AddState(); // state 0
fst->SetStart(0);
fst->AddArc(0, StdArc(10, 10, 0.0, 1));
fst->AddState(); // state 1
fst->AddArc(1, StdArc(12, 12, 0.0, 4));
fst->AddArc(1, StdArc(0,0, 0.1,2)); // backoff from 1 to 2
fst->AddState(); // state 2
fst->AddArc(2, StdArc(13, 13, 0.2, 4));
fst->AddArc(2, StdArc(0,0, 0.3,3)); // backoff from 2 to 3
fst->AddState(); // state 3
fst->AddArc(3, StdArc(14, 14, 0.4, 4));
fst->AddState(); // state 4
fst->AddArc(4, StdArc(15, 15, 0.5, 5));
fst->AddState(); // state 5
fst->SetFinal(5, 0.6);
return fst;
}
// what the resulting DeterministicOnDemand FST should be like
StdVectorFst* CreateResultFst() {
StdVectorFst *fst = new StdVectorFst();
fst->AddState(); // state 0
fst->SetStart(0);
fst->AddArc(0, StdArc(10, 10, 0.0, 1));
fst->AddState(); // state 1
fst->AddArc(1, StdArc(12, 12, 0.0, 4));
fst->AddArc(1, StdArc(13,13,0.3,4)); // went through 1 backoff
fst->AddArc(1, StdArc(14,14,0.8,4)); // went through 2 backoffs
fst->AddState(); // state 2
fst->AddState(); // state 3
fst->AddState(); // state 4
fst->AddArc(4, StdArc(15, 15, 0.5, 5));
fst->AddState(); // state 5
fst->SetFinal(5, 0.6);
return fst;
}
void DeleteTestFst(StdVectorFst *fst) {
delete fst;
}
// Follow paths from an input fst representing a string
// (poor man's composition)
Weight WalkSinglePath(StdVectorFst *ifst, DeterministicOnDemandFst<StdArc> *dfst) {
StdArc oarc; // = new StdArc();
StateId isrc=ifst->Start();
StateId dsrc=dfst->Start();
Weight totalCost = Weight::One();
while (ifst->Final(isrc) == Weight::Zero()) { // while not final
fst::ArcIterator<StdVectorFst> aiter(*ifst, isrc);
const StdArc &iarc = aiter.Value();
if (dfst->GetArc(dsrc, iarc.olabel, &oarc)) {
Weight cost = Times(iarc.weight, oarc.weight);
// cout << " Matched label "<<iarc.olabel<<" at summed cost "<<cost<<endl;
totalCost = Times(totalCost, cost);
} else {
cout << " Can't match arc ["<<iarc.ilabel<<","<<iarc.olabel<<","<<iarc.weight<<"] from "<<isrc<<endl;
exit(1);
}
isrc = iarc.nextstate;
KALDI_LOG << "Setting dsrc = " << oarc.nextstate;
dsrc = oarc.nextstate;
}
totalCost = Times(totalCost, dfst->Final(dsrc));
cout << " Total cost: " << totalCost << endl;
return totalCost;
}
void TestBackoffAndCache() {
// Build from existing fst
cout << "Test with single generated backoff FST" << endl;
StdVectorFst *nfst = CreateBackoffFst();
StdVectorFst *rfst = CreateResultFst();
// before using, make sure that it is input sorted
ArcSort(nfst, StdILabelCompare());
BackoffDeterministicOnDemandFst<StdArc> dfst1a(*nfst);
CacheDeterministicOnDemandFst<StdArc> dfst1(&dfst1a);
// Compare all arcs in dfst1 with expected result
for (StateIterator<StdVectorFst> riter(*rfst); !riter.Done(); riter.Next()) {
StateId rsrc = riter.Value();
// verify that states have same weight (or final status)
assert(ApproxEqual(rfst->Final(rsrc), dfst1.Final(rsrc)));
for (ArcIterator<StdVectorFst> aiter(*rfst, rsrc); !aiter.Done(); aiter.Next()) {
StdArc rarc = aiter.Value();
StdArc darc;
if (dfst1.GetArc(rsrc, rarc.ilabel, &darc)) {
assert(ApproxEqual(rarc.weight, darc.weight, 0.001));
assert(rarc.ilabel==darc.ilabel);
assert(rarc.olabel==darc.olabel);
assert(rarc.nextstate == darc.nextstate);
cerr << " Got same arc at state "<<rsrc<<": "<<rarc.ilabel<<" "<<darc.ilabel<<endl;
} else {
cerr << "Couldn't find arc "<<rarc.ilabel<<" for state "<<rsrc<<endl;
exit(1);
}
}
}
delete nfst;
delete rfst;
}
void TestCompose() {
cout << "Test with single generated backoff FST" << endl;
StdVectorFst *nfst = CreateBackoffFst();
StdVectorFst *rfst = CreateResultFst();
StdVectorFst composed_fst;
Compose(*rfst, *rfst, &composed_fst);
// before using, make sure that it is input sorted
ArcSort(nfst, StdILabelCompare());
BackoffDeterministicOnDemandFst<StdArc> dfst1a(*nfst);
ComposeDeterministicOnDemandFst<StdArc> dfst1b(&dfst1a, &dfst1a);
CacheDeterministicOnDemandFst<StdArc> dfst1(&dfst1b);
typedef StdArc::StateId StateId;
std::map<StateId, StateId> state_map;
state_map[composed_fst.Start()] = dfst1.Start();
VectorFst<StdArc> path_fst;
ShortestPath(composed_fst, &path_fst);
BackoffDeterministicOnDemandFst<StdArc> dfst2(composed_fst);
Weight w1 = WalkSinglePath(&path_fst, &dfst1),
w2 = WalkSinglePath(&path_fst, &dfst2);
KALDI_ASSERT(ApproxEqual(w1, w2));
delete rfst;
delete nfst;
{ // Mostly checking for compilation errors here.
LmExampleDeterministicOnDemandFst<StdArc> lm_eg(NULL, 2, 3);
KALDI_ASSERT(lm_eg.Start() == 0);
KALDI_ASSERT(lm_eg.Final(0).Value() == 0.5); // I made it this value.
StdArc arc;
bool b = lm_eg.GetArc(0, 100, &arc);
KALDI_ASSERT(b && arc.nextstate == 1 && arc.ilabel == 100 && arc.olabel == 100
&& arc.weight.Value() == 0.25);
}
}
}
int main() {
using namespace fst;
TestBackoffAndCache();
TestCompose();
}
@@ -0,0 +1,335 @@
// fstext/deterministic-fst.h
// Copyright 2011-2012 Gilles Boulianne
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
// 2012-2015 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
//
// This file includes material from the OpenFST Library v1.2.7 available at
// http://www.openfst.org and released under the Apache License Version 2.0.
//
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright 2005-2010 Google, Inc.
// Author: riley@google.com (Michael Riley)
#ifndef KALDI_FSTEXT_DETERMINISTIC_FST_H_
#define KALDI_FSTEXT_DETERMINISTIC_FST_H_
/* This header defines the DeterministicOnDemand interface,
which is an FST with a special interface that allows
only a single arc with a non-epsilon input symbol
out of each state.
*/
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "util/stl-utils.h"
namespace fst {
/// \addtogroup deterministic_fst_group "Classes and functions related to on-demand deterministic FST's"
/// @{
/// class DeterministicOnDemandFst is an "FST-like" base-class. It does not
/// actually inherit from any Fst class because its interface is not exactly the
/// same; it's much smaller. It assumes that the FST can have only one arc for
/// any given input symbol, which makes the GetArc function below possible.
/// (The FST is also assumed to be free of input epsilons). Note: we don't use
/// "const" in this interface, because it creates problems when we do things
/// like caching.
template<class Arc>
class DeterministicOnDemandFst {
public:
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
typedef typename Arc::Label Label;
virtual StateId Start() = 0;
virtual Weight Final(StateId s) = 0;
/// Note: ilabel must not be epsilon.
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc) = 0;
virtual ~DeterministicOnDemandFst() { }
};
/**
This class wraps an Fst, representing a language model, using the interface
for "BackoffDeterministicOnDemandFst". We expect that backoff arcs in the
language model will have the epsilon label (label 0) on the arcs, and that
there will be no other epsilons in the language model. We follow the epsilon
arcs as long as a particular arc (or a final-prob) is not found at the
current state.
*/
template<class Arc>
class BackoffDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
explicit BackoffDeterministicOnDemandFst(const Fst<Arc> &fst);
StateId Start() { return fst_.Start(); }
Weight Final(StateId s);
bool GetArc(StateId s, Label ilabel, Arc *oarc);
private:
inline StateId GetBackoffState(StateId s, Weight *w);
const Fst<Arc> &fst_;
};
/**
Class ScaleDeterministicOnDemandFst takes another DeterministicOnDemandFst
and scales the weights (like applying a language-model scale). For instance,
to subtract existing LM scores from a lattice you could use this with
a negative weight; and to interpolate LMs you can also use this with
weights less than one.
It's specialized for StdArc because there is no generic way to scale weights.
*/
class ScaleDeterministicOnDemandFst: public DeterministicOnDemandFst<StdArc> {
public:
typedef StdArc::Weight Weight;
typedef StdArc::StateId StateId;
typedef StdArc::Label Label;
// Constructor does not take ownership of 'det_fst'.
ScaleDeterministicOnDemandFst(float scale,
DeterministicOnDemandFst<StdArc> *det_fst):
scale_(scale), det_fst_(*det_fst) { }
StateId Start() { return det_fst_.Start(); }
Weight Final(StateId s) {
// Note: Weight is indirectly a typedef to TropicalWeight.
Weight final = det_fst_.Final(s);
if (final == Weight::Zero()) return Weight::Zero();
else return TropicalWeight(final.Value() * scale_);
}
inline bool GetArc(StateId s, Label ilabel, StdArc *oarc) {
if (det_fst_.GetArc(s, ilabel, oarc)) {
oarc->weight = TropicalWeight(oarc->weight.Value() * scale_);
return true;
} else {
return false;
}
}
private:
float scale_;
DeterministicOnDemandFst<StdArc> &det_fst_;
};
/**
The class UnweightedNgramFst is a DeterministicOnDemandFst whose states encode
an n-gram history. Conceptually, for n-gram order n and k labels, the FST is an
unweighted acceptor with about k^(n-1) states (ignoring end effects). However,
the FST is created on demand and doesn't need the label vocabulary; GetArc
matches on any input label. This class is primarily used together with
ComposeDeterministicOnDemandFst to expand the n-gram history of lattices, ensuring
that each arc has a sufficiently long unique word history.
*/
template<class Arc>
class UnweightedNgramFst: public DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
UnweightedNgramFst(int n);
StateId Start() { return start_state_; };
Weight Final(StateId s);
bool GetArc(StateId s, Label ilabel, Arc *oarc);
private:
typedef unordered_map<std::vector<Label>,
StateId, kaldi::VectorHasher<Label> > MapType;
// The order of the n-gram.
int n_;
MapType state_map_;
StateId start_state_;
// Map from history-state to pair.
std::vector<std::vector<Label> > state_vec_;
};
template<class Arc>
class ComposeDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
typedef typename Arc::Label Label;
/// Note: constructor does not "take ownership" of the input fst's. The input
/// fst's should be treated as const, in that their contents do not change,
/// but they are not const as the DeterministicOnDemandFst's data-access
/// functions are not const, for reasons relating to caching.
ComposeDeterministicOnDemandFst(DeterministicOnDemandFst<Arc> *fst1,
DeterministicOnDemandFst<Arc> *fst2);
virtual StateId Start() { return start_state_; }
virtual Weight Final(StateId s);
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
private:
DeterministicOnDemandFst<Arc> *fst1_;
DeterministicOnDemandFst<Arc> *fst2_;
typedef unordered_map<std::pair<StateId, StateId>, StateId, kaldi::PairHasher<StateId> > MapType;
MapType state_map_;
std::vector<std::pair<StateId, StateId> > state_vec_; // maps from
// StateId to pair.
StateId next_state_;
StateId start_state_;
};
template<class Arc>
class CacheDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
typedef typename Arc::Label Label;
/// We don't take ownership of this pointer. The argument is "really" const.
CacheDeterministicOnDemandFst(DeterministicOnDemandFst<Arc> *fst,
StateId num_cached_arcs = 100000);
virtual StateId Start() { return fst_->Start(); }
/// We don't bother caching the final-probs, just the arcs.
virtual Weight Final(StateId s) { return fst_->Final(s); }
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
private:
// Get index for cached arc.
inline size_t GetIndex(StateId src_state, Label ilabel);
DeterministicOnDemandFst<Arc> *fst_;
StateId num_cached_arcs_;
std::vector<std::pair<StateId, Arc> > cached_arcs_;
};
/// This class is for didactic purposes, it does not really do anything.
/// It shows how you would wrap a language model. Note: you should probably
/// have <s> and </s> not be real words in your LM, but <s> correspond somehow
/// to the initial-state of the LM, and </s> be encoded in the final-probs.
template<class Arc>
class LmExampleDeterministicOnDemandFst: public DeterministicOnDemandFst<Arc> {
public:
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
typedef typename Arc::Label Label;
LmExampleDeterministicOnDemandFst(void *lm,
Label bos_symbol,
Label eos_symbol);
virtual StateId Start() { return start_state_; }
/// We don't bother caching the final-probs, just the arcs.
virtual Weight Final(StateId s);
virtual bool GetArc(StateId s, Label ilabel, Arc *oarc);
private:
// Get index for cached arc.
inline size_t GetIndex(StateId src_state, Label ilabel);
typedef unordered_map<std::vector<Label>, StateId, kaldi::VectorHasher<Label> > MapType;
void *lm_;
Label bos_symbol_; // beginning of sentence symbol
Label eos_symbol_; // end of sentence symbol.
// This example code does not handle <UNK>; we assume the LM has the same vocab as
// the recognizer.
MapType state_map_;
StateId start_state_;
std::vector<std::vector<Label> > state_vec_; // maps from history-state to pair.
void *lm; // wouldn't really be void.
};
// Compose an FST (which may be a lattice) with a DeterministicOnDemandFst and
// store the result in fst_composed. This is mainly used for expanding lattice
// n-gram histories, where fst1 is a lattice and fst2 is an UnweightedNgramFst.
// This does not call Connect.
template<class Arc>
void ComposeDeterministicOnDemand(const Fst<Arc> &fst1,
DeterministicOnDemandFst<Arc> *fst2,
MutableFst<Arc> *fst_composed);
/**
This function does
'*fst_composed = Compose(Inverse(*fst2), fst1)'
Note that the arguments are reversed; this is unfortunate but it's
because the fst2 argument needs to be non-const and non-const arguments
must follow const ones.
This is the counterpart to ComposeDeterministicOnDemand, used for
the case where the DeterministicOnDemandFst is on the left. The
reason why we need to make the left-hand argument to compose the
inverse of 'fst2' (i.e. with the input and output symbols swapped),
is that the DeterministicOnDemandFst interface only supports lookup
by ilabel (see its function GetArc).
This does not call Connect().
*/
template<class Arc>
void ComposeDeterministicOnDemandInverse(const Fst<Arc> &fst1,
DeterministicOnDemandFst<Arc> *fst2,
MutableFst<Arc> *fst_composed);
/// @}
} // namespace fst
#include "deterministic-fst-inl.h"
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,174 @@
// fstext/determinize-lattice-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/determinize-lattice.h"
#include "fstext/lattice-utils.h"
#include "fstext/fst-test-utils.h"
#include "base/kaldi-math.h"
namespace fst {
using std::vector;
using std::cout;
void TestLatticeStringRepository() {
typedef int32 IntType;
LatticeStringRepository<IntType> sr;
typedef LatticeStringRepository<IntType>::Entry Entry;
for(int i = 0; i < 100; i++) {
int len = kaldi::Rand() % 5;
vector<IntType> str(len), str2(kaldi::Rand() % 4);
const Entry *e = NULL;
for(int i = 0; i < len; i++) {
str[i] = kaldi::Rand() % 5;
e = sr.Successor(e, str[i]);
}
sr.ConvertToVector(e, &str2);
assert(str == str2);
int len2 = kaldi::Rand() % 5;
str2.resize(len2);
const Entry *f = sr.EmptyString(); // NULL
for(int i = 0; i < len2; i++) {
str2[i] = kaldi::Rand() % 5;
f = sr.Successor(f, str2[i]);
}
vector<IntType> prefix, prefix2(kaldi::Rand() % 10),
prefix3;
for(int i = 0; i < len && i < len2; i++) {
if (str[i] == str2[i]) prefix.push_back(str[i]);
else break;
}
const Entry *g = sr.CommonPrefix(e, f);
sr.ConvertToVector(g, &prefix2);
sr.ConvertToVector(e, &prefix3);
sr.ReduceToCommonPrefix(f, &prefix3);
assert(prefix == prefix2);
assert(prefix == prefix3);
assert(sr.IsPrefixOf(g, e));
assert(sr.IsPrefixOf(g, f));
if (str.size() > prefix.size())
assert(!sr.IsPrefixOf(e, g));
}
}
// test that determinization proceeds correctly on general
// FSTs (not guaranteed determinzable, but we use the
// max-states option to stop it getting out of control).
template<class Arc> void TestDeterminizeLattice() {
typedef typename Arc::Weight Weight;
typedef int32 Int;
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
for(int i = 0; i < 100; i++) {
RandFstOptions opts;
opts.n_states = 4;
opts.n_arcs = 10;
opts.n_final = 2;
opts.allow_empty = false;
opts.weight_multiplier = 0.5; // impt for the randomly generated weights
// to be exactly representable in float,
// or this test fails because numerical differences can cause symmetry in
// weights to be broken, which causes the wrong path to be chosen as far
// as the string part is concerned.
VectorFst<Arc> *fst = RandFst<Arc>();
std::cout << "FST before lattice-determinizing is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> det_fst;
try {
DeterminizeLatticeOptions lat_opts;
lat_opts.max_mem = 100;
if (!DeterminizeLattice<TropicalWeight, int32>(*fst, &det_fst, lat_opts, NULL))
throw std::runtime_error("could not determinize");
std::cout << "FST after lattice-determinizing is:\n";
{
FstPrinter<Arc> fstprinter(det_fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(det_fst.Properties(kIDeterministic, true) & kIDeterministic);
// OK, now determinize it a different way and check equivalence.
// [note: it's not normal determinization, it's taking the best path
// for any input-symbol sequence....
VectorFst<CompactArc> compact_fst, compact_det_fst;
ConvertLattice<Weight, Int>(*fst, &compact_fst, false);
std::cout << "Compact FST is:\n";
{
FstPrinter<CompactArc> fstprinter(compact_fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
if (kaldi::Rand() % 2 == 1)
ConvertLattice<Weight, Int>(det_fst, &compact_det_fst, false);
else
if (!DeterminizeLattice<TropicalWeight, int32>(*fst, &compact_det_fst, lat_opts, NULL))
throw std::runtime_error("could not determinize");
std::cout << "Compact version of determinized FST is:\n";
{
FstPrinter<CompactArc> fstprinter(compact_det_fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(RandEquivalent(compact_det_fst, compact_fst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length, max*/));
} catch (...) {
std::cout << "Failed to lattice-determinize this FST (probably not determinizable)\n";
}
delete fst;
}
}
// test that determinization proceeds correctly on acyclic FSTs
// (guaranteed determinizable in this sense).
template<class Arc> void TestDeterminizeLattice2() {
RandFstOptions opts;
opts.acyclic = true;
for(int i = 0; i < 100; i++) {
VectorFst<Arc> *fst = RandFst<Arc>(opts);
std::cout << "FST before lattice-determinizing is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst;
DeterminizeLattice<TropicalWeight, int32>(*fst, &ofst);
std::cout << "FST after lattice-determinizing is:\n";
{
FstPrinter<Arc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
delete fst;
}
}
} // end namespace fst
int main() {
using namespace fst;
TestLatticeStringRepository();
TestDeterminizeLattice<StdArc>();
TestDeterminizeLattice2<StdArc>();
std::cout << "Tests succeeded\n";
}
@@ -0,0 +1,150 @@
// fstext/determinize-lattice.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_DETERMINIZE_LATTICE_H_
#define KALDI_FSTEXT_DETERMINIZE_LATTICE_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include "fstext/lattice-weight.h"
namespace fst {
/// \addtogroup fst_extensions
/// @{
// For example of usage, see test-determinize-lattice.cc
/*
DeterminizeLattice implements a special form of determinization
with epsilon removal, optimized for a phase of lattice generation.
Its input is an FST with weight-type BaseWeightType (usually a pair of floats,
with a lexicographical type of order, such as LatticeWeightTpl<float>).
Typically this would be a state-level lattice, with input symbols equal to
words, and output-symbols equal to p.d.f's (so like the inverse of HCLG). Imagine representing this as an
acceptor of type CompactLatticeWeightTpl<float>, in which the input/output
symbols are words, and the weights contain the original weights together with
strings (with zero or one symbol in them) containing the original output labels
(the p.d.f.'s). We determinize this using acceptor determinization with
epsilon removal. Remember (from lattice-weight.h) that
CompactLatticeWeightTpl has a special kind of semiring where we always take
the string corresponding to the best cost (of type BaseWeightType), and
discard the other. This corresponds to taking the best output-label sequence
(of p.d.f.'s) for each input-label sequence (of words). We couldn't use the
Gallic weight for this, or it would die as soon as it detected that the input
FST was non-functional. In our case, any acyclic FST (and many cyclic ones)
can be determinized.
We assume that there is a function
Compare(const BaseWeightType &a, const BaseWeightType &b)
that returns (-1, 0, 1) according to whether (a < b, a == b, a > b) in the
total order on the BaseWeightType... this information should be the
same as NaturalLess would give, but it's more efficient to do it this way.
You can define this for things like TropicalWeight if you need to instantiate
this class for that weight type.
We implement this determinization in a special way to make it efficient for
the types of FSTs that we will apply it to. One issue is that if we
explicitly represent the strings (in CompactLatticeWeightTpl) as vectors of
type vector<IntType>, the algorithm takes time quadratic in the length of
words (in states), because propagating each arc involves copying a whole
vector (of integers representing p.d.f.'s). Instead we use a hash structure
where each string is a pointer (Entry*), and uses a hash from (Entry*,
IntType), to the successor string (and a way to get the latest IntType and the
ancestor Entry*). [this is the class LatticeStringRepository].
Another issue is that rather than representing a determinized-state as a
collection of (state, weight), we represent it in a couple of reduced forms.
Suppose a determinized-state is a collection of (state, weight) pairs; call
this the "canonical representation". Note: these collections are always
normalized to remove any common weight and string part. Define end-states as
the subset of states that have an arc out of them with a label on, or are
final. If we represent a determinized-state a the set of just its (end-state,
weight) pairs, this will be a valid and more compact representation, and will
lead to a smaller set of determinized states (like early minimization). Call
this collection of (end-state, weight) pairs the "minimal representation". As
a mechanism to reduce compute, we can also consider another representation.
In the determinization algorithm, we start off with a set of (begin-state,
weight) pairs (where the "begin-states" are initial or have a label on the
transition into them), and the "canonical representation" consists of the
epsilon-closure of this set (i.e. follow epsilons). Call this set of
(begin-state, weight) pairs, appropriately normalized, the "initial
representation". If two initial representations are the same, the "canonical
representation" and hence the "minimal representation" will be the same. We
can use this to reduce compute. Note that if two initial representations are
different, this does not preclude the other representations from being the same.
*/
struct DeterminizeLatticeOptions {
float delta; // A small offset used to measure equality of weights.
int max_mem; // If >0, determinization will fail and return false
// when the algorithm's (approximate) memory consumption crosses this threshold.
int max_loop; // If >0, can be used to detect non-determinizable input
// (a case that wouldn't be caught by max_mem).
DeterminizeLatticeOptions(): delta(kDelta),
max_mem(-1),
max_loop(-1) { }
};
/**
This function implements the normal version of DeterminizeLattice, in which
the output strings are represented using sequences of arcs, where all but
the first one has an epsilon on the input side. The debug_ptr argument is
an optional pointer to a bool that, if it becomes true while the algorithm
is executing, the algorithm will print a traceback and terminate (used in
fstdeterminizestar.cc debug non-terminating determinization). More
efficient if ifst is arc-sorted on input label. If the number of arcs gets
more than max_states, it will throw std::runtime_error (otherwise this code
does not use exceptions). This is mainly useful for debug. */
template<class Weight, class IntType>
bool DeterminizeLattice(
const Fst<ArcTpl<Weight> > &ifst,
MutableFst<ArcTpl<Weight> > *ofst,
DeterminizeLatticeOptions opts = DeterminizeLatticeOptions(),
bool *debug_ptr = NULL);
/* This is a version of DeterminizeLattice with a slightly more "natural" output format,
where the output sequences are encoded using the CompactLatticeArcTpl template
(i.e. the sequences of output symbols are represented directly as strings)
More efficient if ifst is arc-sorted on input label.
If the #arcs gets more than max_arcs, it will throw std::runtime_error (otherwise
this code does not use exceptions). This is mainly useful for debug.
*/
template<class Weight, class IntType>
bool DeterminizeLattice(
const Fst<ArcTpl<Weight> >&ifst,
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, IntType> > > *ofst,
DeterminizeLatticeOptions opts = DeterminizeLatticeOptions(),
bool *debug_ptr = NULL);
/// @} end "addtogroup fst_extensions"
} // end namespace fst
#include "fstext/determinize-lattice-inl.h"
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,508 @@
// fstext/determinize-star-test.cc
// Copyright 2009-2011 Microsoft Corporation
// 2015 Hainan Xu
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-math.h"
#include "fstext/pre-determinize.h"
#include "fstext/determinize-star.h"
#include "fstext/trivial-factor-weight.h"
#include "fstext/fst-test-utils.h"
namespace fst
{
// test that determinization proceeds correctly on general
// FSTs (not guaranteed determinzable, but we use the
// max-states option to stop it getting out of control).
template<class Arc> void TestDeterminizeGeneral() {
int max_states = 100; // don't allow more det-states than this.
for(int i = 0; i < 100; i++) {
VectorFst<Arc> *fst = RandFst<Arc>();
std::cout << "FST before determinizing is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst;
try {
DeterminizeStar<Fst<Arc> >(*fst, &ofst, kDelta, NULL, max_states);
std::cout << "FST after determinizing is:\n";
{
FstPrinter<Arc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(RandEquivalent(*fst, ofst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length, max*/));
} catch (...) {
std::cout << "Failed to determinize *this FST (probably not determinizable)\n";
}
delete fst;
}
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestDeterminize() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr = NULL;
std::vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++)
all_syms.push_back(i);
// Create states.
std::vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
std::vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
PreDeterminize(fst, 1000, &extra_syms);
}
std::cout <<" printing after predeterminization\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
{ // Remove epsilon. All default args.
bool connect = true;
Weight weight_threshold = Weight::Zero();
int64 nstate = -1; // Relates to pruning.
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
// I guess. But with no epsilon cycles, probably doensn't matter.
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
}
std::cout <<" printing after epsilon removal\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst_orig;
VectorFst<Arc> ofst_star;
{
printf("Determinizing with baseline\n");
DeterminizeOptions<Arc> opts; // Default options.
Determinize(*fst, &ofst_orig, opts);
}
{
printf("Determinizing with DeterminizeStar\n");
DeterminizeStar(*fst, &ofst_star);
}
{
std::cout <<" printing after determinization [baseline]\n";
FstPrinter<Arc> fstprinter(ofst_orig, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
assert(ofst_orig.Properties(kIDeterministic, true) == kIDeterministic);
}
{
std::cout <<" printing after determinization [star]\n";
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
assert(ofst_star.Properties(kIDeterministic, true) == kIDeterministic);
}
assert(RandEquivalent(ofst_orig, ofst_star, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
int64 num_removed = DeleteISymbols(&ofst_star, extra_syms);
std::cout <<" printing after removing "<<num_removed<<" instances of extra symbols\n";
{
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
std::cout <<" Checking equivalent to original FST.\n";
// giving Rand() as a seed stops the random number generator from always being reset to
// the same point each time, while maintaining determinism of the test.
assert(RandEquivalent(ofst_star, *fst_copy_orig, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
delete fst_copy_orig;
}
// Don't call this-- the test will fail due to the FST being non-functional.
template<class Arc> void TestDeterminize2() {
for(int i = 0; i < 10; i++) {
RandFstOptions opts;
opts.acyclic = true;
VectorFst<Arc> *ifst = RandFst<Arc>(opts);
VectorFst<Arc> ofst;
Determinize(*ifst, &ofst);
assert(RandEquivalent(*ifst, ofst, 5, 0.01, kaldi::Rand(), 100));
delete ifst;
}
}
template<class Arc> void TestPush() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr = NULL;
std::vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++)
all_syms.push_back(i);
// Create states.
std::vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
std::vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
PreDeterminize(fst, 1000, &extra_syms);
}
VectorFst<Arc> fst_pushed;
std::cout << "Pushing FST\n";
Push<Arc, REWEIGHT_TO_INITIAL>(*fst, &fst_pushed, kPushWeights|kPushLabels, kDelta);
std::cout <<" printing after pushing\n";
{
FstPrinter<Arc> fstprinter(fst_pushed, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(RandEquivalent(*fst, fst_pushed, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
delete fst_copy_orig;
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestMinimize() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
std::cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr =NULL;
std::vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++)
all_syms.push_back(i);
// Create states.
std::vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
std::vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
PreDeterminize(fst, 1000, &extra_syms);
}
std::cout <<" printing after predeterminization\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
{ // Remove epsilon. All default args.
bool connect = true;
Weight weight_threshold = Weight::Zero();
int64 nstate = -1; // Relates to pruning.
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
// I guess. But with no epsilon cycles, probably doensn't matter.
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
}
std::cout <<" printing after epsilon removal\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst_orig;
VectorFst<Arc> ofst_star;
{
printf("Determinizing with baseline\n");
DeterminizeOptions<Arc> opts; // Default options.
Determinize(*fst, &ofst_orig, opts);
}
{
std::cout <<" printing after determinization [baseline]\n";
FstPrinter<Arc> fstprinter(ofst_orig, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
{
printf("Determinizing with DeterminizeStar to Gallic semiring\n");
VectorFst<GallicArc<Arc> > gallic_fst;
DeterminizeStar(*fst, &gallic_fst);
{
std::cout <<" printing after determinization by DeterminizeStar [in gallic]\n";
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
printf("Pushing weights\n");
Push(&gallic_fst, REWEIGHT_TO_INITIAL, kDelta);
{
std::cout <<" printing after pushing weights [in gallic]\n";
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
printf("Minimizing [in Gallic]\n");
Minimize(&gallic_fst);
{
std::cout <<" printing after minimization [in gallic]\n";
FstPrinter<GallicArc< Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
printf("Converting gallic back to regular [my approach]\n");
TrivialFactorWeightFst< GallicArc<Arc, GALLIC_LEFT>, GallicFactor<typename Arc::Label,
typename Arc::Weight, GALLIC_LEFT> > fwfst(gallic_fst);
{
std::cout <<" printing factor-weight FST\n";
FstPrinter<GallicArc< Arc> > fstprinter(fwfst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
Map(fwfst, &ofst_star, FromGallicMapper<Arc, GALLIC_LEFT>());
{
std::cout <<" printing after converting back to regular FST\n";
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
}
assert(RandEquivalent(ofst_orig, ofst_star, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
int64 num_removed = DeleteISymbols(&ofst_star, extra_syms);
std::cout <<" printing after removing "<<num_removed<<" instances of extra symbols\n";
{
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
std::cout <<" Checking equivalent to original FST.\n";
// giving Rand() as a seed stops the random number generator from always being reset to
// the same point each time, while maintaining determinism of the test.
assert(RandEquivalent(ofst_star, *fst_copy_orig, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
delete fst_copy_orig;
}
template<class Arc, class inttype> void TestStringRepository() {
typedef typename Arc::Label Label;
StringRepository<Label, inttype> sr;
int N = 100;
if (sizeof(inttype) == 1) N = 64;
std::vector<std::vector<Label> > strings(N);
std::vector<inttype> ids(N);
for (int i = 0;i < N;i++) {
size_t len = kaldi::Rand() % 4;
std::vector<Label> vec;
for (size_t j = 0;j < len;j++) vec.push_back( (kaldi::Rand()%10) + 150*(kaldi::Rand()%2)); // make it have reasonable range.
if (i < 500 && vec.size() == 0) ids[i] = sr.IdOfEmpty();
else if (i < 500 && vec.size() == 1) ids[i] = sr.IdOfLabel(vec[0]);
else ids[i] = sr.IdOfSeq(vec);
strings[i] = vec;
}
for (int i = 0;i < N;i++) {
std::vector<Label> tmpv;
tmpv.push_back(10); // just put in garbage.
sr.SeqOfId(ids[i], &tmpv);
assert(tmpv == strings[i]);
assert(sr.IdOfSeq(strings[i]) == ids[i]);
if (strings[i].size() == 0) assert(ids[i] == sr.IdOfEmpty());
if (strings[i].size() == 1) assert(ids[i] == sr.IdOfLabel(strings[i][0]));
if (sizeof(inttype) != 1) {
size_t prefix_len = kaldi::Rand() % (strings[i].size() + 1);
inttype s2 = sr.RemovePrefix(ids[i], prefix_len);
std::vector<Label> vec2;
sr.SeqOfId(s2, &vec2);
for (size_t j = 0;j < strings[i].size()-prefix_len;j++) {
assert(vec2[j] == strings[i][j+prefix_len]);
}
}
}
}
} // end namespace fst
int main() {
for (int i = 0;i < 3;i++) { // We would need more iterations to check
// this properly.
fst::TestStringRepository<fst::StdArc, int>();
fst::TestStringRepository<fst::StdArc, unsigned int>();
// Not for use with char, but this helps reveal some kinds of bugs.
fst::TestStringRepository<fst::StdArc, unsigned char>();
fst::TestStringRepository<fst::StdArc, char>();
fst::TestDeterminizeGeneral<fst::StdArc>();
fst::TestDeterminize<fst::StdArc>();
// fst::TestDeterminize2<fst::StdArc>();
fst::TestPush<fst::StdArc>();
fst::TestMinimize<fst::StdArc>();
}
}
@@ -0,0 +1,123 @@
// fstext/determinize-star.h
// Copyright 2009-2011 Microsoft Corporation
// 2014 Guoguo Chen
// 2015 Hainan Xu
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_DETERMINIZE_STAR_H_
#define KALDI_FSTEXT_DETERMINIZE_STAR_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <stdexcept> // this algorithm uses exceptions
namespace fst {
/// \addtogroup fst_extensions
/// @{
// For example of usage, see test-determinize-star.cc
/*
DeterminizeStar implements determinization with epsilon removal, which we
distinguish with a star.
We define a determinized* FST as one in which no state has more than one
transition with the same input-label. Epsilon input labels are not allowed
except starting from states that have exactly one arc exiting them (and are
not final). [In the normal definition of determinized, epsilon-input labels
are not allowed at all, whereas in Mohri's definition, epsilons are treated
as ordinary symbols]. The determinized* definition is intended to simulate
the effect of allowing strings of output symbols at each state.
The algorithm implemented here takes an Fst<Arc>, and a pointer to a
MutableFst<Arc> where it puts its output. The weight type is assumed to be a
float-weight. It does epsilon removal and determinization.
This algorithm may fail if the input has epsilon cycles under
certain circumstances (i.e. the semiring is non-idempotent, e.g. the log
semiring, or there are negative cost epsilon cycles).
This implementation is much less fancy than the one in fst/determinize.h, and
does not have an "on-demand" version.
The algorithm is a fairly normal determinization algorithm. We keep in
memory the subsets of states, together with their leftover strings and their
weights. The only difference is we detect input epsilon transitions and
treat them "specially".
*/
// This algorithm will be slightly faster if you sort the input fst on input label.
/**
This function implements the normal version of DeterminizeStar, in which the
output strings are represented using sequences of arcs, where all but the
first one has an epsilon on the input side. The debug_ptr argument is an
optional pointer to a bool that, if it becomes true while the algorithm is
executing, the algorithm will print a traceback and terminate (used in
fstdeterminizestar.cc debug non-terminating determinization).
If max_states is positive, it will stop determinization and throw an
exception as soon as the max-states is reached. This can be useful in test.
If allow_partial is true, the algorithm will output partial results when the
specified max_states is reached (when larger than zero), instead of throwing
out an error.
Caution, the return status is un-intuitive: this function will return false if
determinization completed normally, and true if it was stopped early by
reaching the 'max-states' limit, and a partial FST was generated.
*/
template<class F>
bool DeterminizeStar(F &ifst, MutableFst<typename F::Arc> *ofst,
float delta = kDelta,
bool *debug_ptr = NULL,
int max_states = -1,
bool allow_partial = false);
/* This is a version of DeterminizeStar with a slightly more "natural" output format,
where the output sequences are encoded using the GallicArc (i.e. the output symbols
are strings.
If max_states is positive, it will stop determinization and throw an
exception as soon as the max-states is reached. This can be useful in test.
If allow_partial is true, the algorithm will output partial results when the
specified max_states is reached (when larger than zero), instead of throwing
out an error.
Caution, the return status is un-intuitive: this function will return false if
determinization completed normally, and true if it was stopped early by
reaching the 'max-states' limit, and a partial FST was generated.
*/
template<class F>
bool DeterminizeStar(F &ifst, MutableFst<GallicArc<typename F::Arc> > *ofst,
float delta = kDelta, bool *debug_ptr = NULL,
int max_states = -1,
bool allow_partial = false);
/// @} end "addtogroup fst_extensions"
} // end namespace fst
#include "fstext/determinize-star-inl.h"
#endif
@@ -0,0 +1,126 @@
// fstext/epsilon-property-inl.h
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_EPSILON_PROPERTY_INL_H_
#define KALDI_FSTEXT_EPSILON_PROPERTY_INL_H_
namespace fst {
template<class Arc>
void ComputeStateInfo(const VectorFst<Arc> &fst,
std::vector<char> *epsilon_info) {
typedef typename Arc::StateId StateId;
typedef VectorFst<Arc> Fst;
epsilon_info->clear();
epsilon_info->resize(fst.NumStates(), static_cast<char>(0));
for (StateId s = 0; s < fst.NumStates(); s++) {
for (ArcIterator<Fst> aiter(fst, s); !aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel == 0 && arc.olabel == 0) {
(*epsilon_info)[arc.nextstate] |= static_cast<char>(kStateHasEpsilonArcsEntering);
(*epsilon_info)[s] |= static_cast<char>(kStateHasEpsilonArcsLeaving);
} else {
(*epsilon_info)[arc.nextstate] |= static_cast<char>(kStateHasNonEpsilonArcsEntering);
(*epsilon_info)[s] |= static_cast<char>(kStateHasNonEpsilonArcsLeaving);
}
}
}
}
template<class Arc>
void EnsureEpsilonProperty(VectorFst<Arc> *fst) {
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
typedef VectorFst<Arc> Fst;
std::vector<char> epsilon_info;
ComputeStateInfo(*fst, &epsilon_info);
StateId num_states_old = fst->NumStates();
StateId non_coaccessible_state = fst->AddState();
/// new_state_vec is for those states that have both epsilon and
/// non-epsilon arcs entering. For these states, we'll create a new
/// state for the non-epsilon arcs to enter and put it in this array,
/// and we'll put an epsilon transition from the new state to the old state.
std::vector<StateId> new_state_vec(num_states_old, kNoStateId);
for (StateId s = 0; s < num_states_old; s++) {
if ((epsilon_info[s] & kStateHasEpsilonArcsEntering) != 0 &&
(epsilon_info[s] & kStateHasNonEpsilonArcsEntering) != 0) {
assert(s != fst->Start()); // a type of cyclic FST we can't handle
// easily.
StateId new_state = fst->AddState();
new_state_vec[s] = new_state;
fst->AddArc(new_state, Arc(0, 0, Weight::One(), s));
}
}
/// First modify arcs to point to states in new_state_vec when
/// necessary.
for (StateId s = 0; s < num_states_old; s++) {
for (MutableArcIterator<Fst> aiter(fst, s);
!aiter.Done(); aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0 || arc.olabel != 0) { // non-epsilon arc
StateId replacement_state;
if (arc.nextstate >= 0 && arc.nextstate < num_states_old &&
(replacement_state = new_state_vec[arc.nextstate]) !=
kNoStateId) {
arc.nextstate = replacement_state;
aiter.SetValue(arc);
}
}
}
}
/// Now handle the situation where states have both epsilon and non-epsilon
/// arcs leaving.
for (StateId s = 0; s < num_states_old; s++) {
if ((epsilon_info[s] & kStateHasEpsilonArcsLeaving) != 0 &&
(epsilon_info[s] & kStateHasNonEpsilonArcsLeaving) != 0) {
// state has non-epsilon and epsilon arcs leaving.
// create a new state and move the non-epsilon arcs to leave
// from there instead.
StateId new_state = fst->AddState();
for (MutableArcIterator<Fst> aiter(fst, s); !aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0 || arc.olabel != 0) { // non-epsilon arc.
assert(arc.nextstate != s); // we don't handle cyclic FSTs.
// move this arc to leave from the new state:
fst->AddArc(new_state, arc);
arc.nextstate = non_coaccessible_state;
aiter.SetValue(arc); // invalidate the arc, Connect() will remove it.
}
}
// Create an epsilon arc to the new state.
fst->AddArc(s, Arc(0, 0, Weight::One(), new_state));
}
}
Connect(fst); // Removes arcs to the non-coaccessible state.
}
} // namespace fst.
#endif
@@ -0,0 +1,58 @@
// fstext/epsilon-property-test.cc
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/rand-fst.h"
#include "fstext/epsilon-property.h"
namespace fst {
void TestEnsureEpsilonProperty() {
for (int32 i = 0; i < 10; i++) {
RandFstOptions opts;
opts.acyclic = true;
VectorFst<LogArc> *fst = RandFst<LogArc>(opts);
VectorFst<LogArc> fst2(*fst); // copy it...
EnsureEpsilonProperty(&fst2);
std::vector<char> info;
ComputeStateInfo(fst2, &info);
for (size_t i = 0; i < info.size(); i++) {
char c = info[i];
assert(!((c & kStateHasEpsilonArcsEntering) != 0 &&
(c & kStateHasNonEpsilonArcsEntering) != 0));
assert(!((c & kStateHasEpsilonArcsLeaving) != 0 &&
(c & kStateHasNonEpsilonArcsLeaving) != 0));
}
assert(RandEquivalent(fst2, *fst, 5, 0.01, kaldi::Rand(), 10));
delete fst;
}
}
} // end namespace fst
int main() {
using namespace fst;
for (int i = 0; i < 2; i++) {
TestEnsureEpsilonProperty();
}
std::cout << "Test OK\n";
}
@@ -0,0 +1,60 @@
// fstext/epsilon-property.h
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_EPSILON_PROPERTY_H_
#define KALDI_FSTEXT_EPSILON_PROPERTY_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
namespace fst {
enum {
kStateHasEpsilonArcsEntering = 0x1,
kStateHasNonEpsilonArcsEntering = 0x2,
kStateHasEpsilonArcsLeaving = 0x4,
kStateHasNonEpsilonArcsLeaving = 0x8
}; // use 'char' for this enum.
/// This function will set epsilon_info to have size equal to the
/// NumStates() of the FST, containing a logical-or of the enum
/// values kStateHasEpsilonArcsEntering, kStateHasNonEpsilonArcsEntering,
/// kStateHasEpsilonArcsLeaving, and kStateHasNonEpsilonArcsLeaving.
/// The meaning should be obvious. Note: an epsilon arc is defined
/// as an arc where ilabel == olabel == 0.
template<class Arc>
void ComputeStateInfo(const VectorFst<Arc> &fst,
std::vector<char> *epsilon_info);
/// This function modifies the fst (while maintaining equivalence) in such a way
/// that, after the modification, all states of the FST which have epsilon-arcs
/// entering them, have no non-epsilon arcs entering them, and all states which
/// have epsilon-arcs leaving them, have no non-epsilon arcs leaving them. It does
/// this by creating extra states and adding extra epsilon transitions. An epsilon
/// arc is defined as an arc where both the ilabel and the olabel are epsilons.
/// This function may fail with KALDI_ASSERT for certain cyclic FSTs, but is safe
/// in the acyclic case.
template<class Arc>
void EnsureEpsilonProperty(VectorFst<Arc> *fst);
} // end namespace fst
#include "fstext/epsilon-property-inl.h"
#endif
@@ -0,0 +1,284 @@
// fstext/factor-inl.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_FACTOR_INL_H_
#define KALDI_FSTEXT_FACTOR_INL_H_
#include "util/stl-utils.h"
// Do not include this file directly. It is included by factor.h.
namespace fst {
// GetStateProperties takes in an FST and a number "max_state" which is the
// highest numbered state in the FST (this could be fst.NumStates()-1 for an
// ExpandedFst, or derived from some kind of traversal). It outputs a vector
// numbered from 0..max_state, of type FstStateProperties which is a bitmask
// with information about the states.
// GetStateProperties has not been tested directly (only implicitly via
// testing Factor).
template<class Arc>
void GetStateProperties(const Fst<Arc> &fst,
typename Arc::StateId max_state,
std::vector<StatePropertiesType> *props) {
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
assert(props != NULL);
props->clear();
if (fst.Start() < 0) return; // Empty fst.
props->resize(max_state+1, 0);
assert(fst.Start() <= max_state);
(*props)[fst.Start()] |= kStateInitial;
for (StateId s = 0; s <= max_state; s++) {
StatePropertiesType &s_info = (*props)[s];
for (ArcIterator<Fst<Arc> > aiter(fst, s); !aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel != 0) s_info |= kStateIlabelsOut;
if (arc.olabel != 0) s_info |= kStateOlabelsOut;
StateId nexts = arc.nextstate;
assert(nexts <= max_state); // or input was invalid.
StatePropertiesType &nexts_info = (*props)[nexts];
if (s_info&kStateArcsOut) s_info |= kStateMultipleArcsOut;
s_info |= kStateArcsOut;
if (nexts_info&kStateArcsIn) nexts_info |= kStateMultipleArcsIn;
nexts_info |= kStateArcsIn;
}
if (fst.Final(s) != Weight::Zero()) s_info |= kStateFinal;
}
}
template<class Arc, class I>
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst,
std::vector<std::vector<I> > *symbols_out) {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename Arc::Weight Weight;
assert(symbols_out != NULL);
ofst->DeleteStates();
if (fst.Start() < 0) return; // empty FST.
std::vector<StateId> order;
DfsOrderVisitor<Arc> dfs_order_visitor(&order);
DfsVisit(fst, &dfs_order_visitor);
assert(order.size() > 0);
StateId max_state = *(std::max_element(order.begin(), order.end()));
std::vector<StatePropertiesType> state_properties;
GetStateProperties(fst, max_state, &state_properties);
std::vector<bool> remove(max_state+1); // if true, will remove this state.
// Now identify states that will be removed (made the middle of a chain).
// The basic rule is that if the FstStateProperties equals
// (kStateArcsIn|kStateArcsOut) or (kStateArcsIn|kStateArcsOut|kStateIlabelsOut),
// then it is in the middle of a chain. This eliminates state with
// multiple input or output arcs, final states, and states with arcs out
// that have olabels [we assume these are pushed to the left, so occur on the
// 1st arc of a chain.
for (StateId i = 0; i <= max_state; i++)
remove[i] = (state_properties[i] == (kStateArcsIn|kStateArcsOut)
|| state_properties[i] == (kStateArcsIn|kStateArcsOut|kStateIlabelsOut));
std::vector<StateId> state_mapping(max_state+1, kNoStateId);
typedef unordered_map<std::vector<I>, Label, kaldi::VectorHasher<I> > SymbolMapType;
SymbolMapType symbol_mapping;
Label symbol_counter = 0;
{
std::vector<I> eps;
symbol_mapping[eps] = symbol_counter++;
}
std::vector<I> this_sym; // a temporary used inside the loop.
for (size_t i = 0; i < order.size(); i++) {
StateId state = order[i];
if (!remove[state]) { // Process this state...
StateId &new_state = state_mapping[state];
if (new_state == kNoStateId) new_state = ofst->AddState();
for (ArcIterator<Fst<Arc> > aiter(fst, state); !aiter.Done(); aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel == 0) this_sym.clear();
else {
this_sym.resize(1);
this_sym[0] = arc.ilabel;
}
while (remove[arc.nextstate]) {
ArcIterator<Fst<Arc> > aiter2(fst, arc.nextstate);
assert(!aiter2.Done());
const Arc &nextarc = aiter2.Value();
arc.weight = Times(arc.weight, nextarc.weight);
assert(nextarc.olabel == 0);
if (nextarc.ilabel != 0) this_sym.push_back(nextarc.ilabel);
assert(static_cast<Label>(static_cast<I>(nextarc.ilabel))
== nextarc.ilabel); // check within integer range.
arc.nextstate = nextarc.nextstate;
}
StateId &new_nextstate = state_mapping[arc.nextstate];
if (new_nextstate == kNoStateId) new_nextstate = ofst->AddState();
arc.nextstate = new_nextstate;
if (symbol_mapping.count(this_sym) != 0) arc.ilabel = symbol_mapping[this_sym];
else arc.ilabel = symbol_mapping[this_sym] = symbol_counter++;
ofst->AddArc(new_state, arc);
}
if (fst.Final(state) != Weight::Zero())
ofst->SetFinal(new_state, fst.Final(state));
}
}
ofst->SetStart(state_mapping[fst.Start()]);
// Now output the symbol sequences.
symbols_out->resize(symbol_counter);
for (typename SymbolMapType::const_iterator iter = symbol_mapping.begin();
iter != symbol_mapping.end(); ++iter) {
(*symbols_out)[iter->second] = iter->first;
}
}
template<class Arc>
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst1,
MutableFst<Arc> *ofst2) {
typedef typename Arc::Label Label;
std::vector<std::vector<Label> > symbols;
Factor(fst, ofst2, &symbols);
CreateFactorFst(symbols, ofst1);
}
template<class Arc, class I>
void ExpandInputSequences(const std::vector<std::vector<I> > &sequences,
MutableFst<Arc> *fst) {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename Arc::Weight Weight;
fst->SetInputSymbols(NULL);
size_t size = sequences.size();
if (sequences.size() > 0) assert(sequences[0].size() == 0); // should be eps.
StateId num_states_at_start = fst->NumStates();
for (StateId s = 0; s < num_states_at_start; s++) {
StateId num_arcs = fst->NumArcs(s);
for (StateId aidx = 0; aidx < num_arcs; aidx++) {
ArcIterator<MutableFst<Arc> > aiter(*fst, s);
aiter.Seek(aidx);
Arc arc = aiter.Value();
Label ilabel = arc.ilabel;
Label dest_state = arc.nextstate;
if (ilabel != 0) { // non-eps [nothing to do if eps]...
assert(ilabel < static_cast<Label>(size));
size_t len = sequences[ilabel].size();
if (len <= 1) {
if (len == 0) arc.ilabel = 0;
else arc.ilabel = sequences[ilabel][0];
MutableArcIterator<MutableFst<Arc> > mut_aiter(fst, s);
mut_aiter.Seek(aidx);
mut_aiter.SetValue(arc);
} else { // len>=2. Must create new states...
StateId curstate = -1; // keep compiler happy: this value never used.
for (size_t n = 0; n < len; n++) { // adding/modifying "len" arcs.
StateId nextstate;
if (n < len-1) {
nextstate = fst->AddState();
assert(nextstate >= num_states_at_start);
} else nextstate = dest_state; // going back to original arc's
// destination.
if (n == 0) {
arc.ilabel = sequences[ilabel][0];
arc.nextstate = nextstate;
MutableArcIterator<MutableFst<Arc> > mut_aiter(fst, s);
mut_aiter.Seek(aidx);
mut_aiter.SetValue(arc);
} else {
arc.ilabel = sequences[ilabel][n];
arc.olabel = 0;
arc.weight = Weight::One();
arc.nextstate = nextstate;
fst->AddArc(curstate, arc);
}
curstate = nextstate;
}
}
}
}
}
}
template<class Arc, class I>
void CreateFactorFst(const std::vector<std::vector<I> > &sequences,
MutableFst<Arc> *fst) {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename Arc::Weight Weight;
assert(fst != NULL);
fst->DeleteStates();
StateId loopstate = fst->AddState();
assert(loopstate == 0);
fst->SetStart(0);
fst->SetFinal(0, Weight::One());
if (sequences.size() != 0) assert(sequences[0].size() == 0); // can't replace epsilon...
for (Label olabel = 1; olabel < static_cast<Label>(sequences.size()); olabel++) {
size_t len = sequences[olabel].size();
if (len == 0) {
Arc arc(0, olabel, Weight::One(), loopstate);
fst->AddArc(loopstate, arc);
} else {
StateId curstate = loopstate;
for (size_t i = 0; i < len; i++) {
StateId nextstate = (i == len-1 ? loopstate : fst->AddState());
Arc arc(sequences[olabel][i], (i == 0 ? olabel : 0), Weight::One(), nextstate);
fst->AddArc(curstate, arc);
curstate = nextstate;
}
}
}
fst->SetProperties(kOLabelSorted, kOLabelSorted);
}
template<class Arc, class I>
void CreateMapFst(const std::vector<I> &symbol_map,
MutableFst<Arc> *fst) {
KALDI_ASSERT_IS_INTEGER_TYPE(I);
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename Arc::Weight Weight;
assert(fst != NULL);
fst->DeleteStates();
StateId loopstate = fst->AddState();
assert(loopstate == 0);
fst->SetStart(0);
fst->SetFinal(0, Weight::One());
assert(symbol_map.empty() || symbol_map[0] == 0); // FST cannot map epsilon to something else.
for (Label olabel = 1; olabel < static_cast<Label>(symbol_map.size()); olabel++) {
Arc arc(symbol_map[olabel], olabel, Weight::One(), loopstate);
fst->AddArc(loopstate, arc);
}
}
} // end namespace fst.
#endif
@@ -0,0 +1,191 @@
// fstext/factor-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/factor.h"
#include "fstext/fstext-utils.h"
#include "fstext/fst-test-utils.h"
#include "base/kaldi-math.h"
namespace fst
{
using std::vector;
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> static void TestFactor() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> fst;
int n_syms = 2 + kaldi::Rand() % 5, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%10;
SymbolTable symtab("my-symbol-table"), *sptr = &symtab;
vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++) {
std::stringstream ss;
if (i == 0) ss << "<eps>";
else ss<<i;
Label cur_lab = sptr->AddSymbol(ss.str());
assert(cur_lab == (Label)i);
all_syms.push_back(cur_lab);
}
assert(all_syms[0] == 0);
fst.AddState();
int cur_num_states = 1;
for (int i = 0; i < n_arcs; i++) {
StateId src_state = kaldi::Rand() % cur_num_states;
StateId dst_state;
if (kaldi::RandUniform() < 0.1) dst_state = kaldi::Rand() % cur_num_states;
else {
dst_state = cur_num_states++; fst.AddState();
}
Arc arc;
if (kaldi::RandUniform() < 0.5) arc.ilabel = all_syms[kaldi::Rand()%all_syms.size()];
else arc.ilabel = 0;
if (kaldi::RandUniform() < 0.5) arc.olabel = all_syms[kaldi::Rand()%all_syms.size()];
else arc.olabel = 0;
arc.weight = (Weight) (0 + 0.1*(kaldi::Rand() % 5));
arc.nextstate = dst_state;
fst.AddArc(src_state, arc);
}
for (int i = 0; i < n_final; i++) {
fst.SetFinal(kaldi::Rand() % cur_num_states, (Weight) (0 + 0.1*(kaldi::Rand() % 5)));
}
if (kaldi::RandUniform() < 0.8) fst.SetStart(0); // usually leads to nicer examples.
else fst.SetStart(kaldi::Rand() % cur_num_states);
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(&fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
if (fst.Start() == kNoStateId) return; // "Connect" made it empty.
VectorFst<Arc> fst_pushed;
Push<Arc, REWEIGHT_TO_INITIAL>(fst, &fst_pushed, kPushLabels);
VectorFst<Arc> fst_factored;
vector<vector<typename Arc::Label> > symbols;
Factor(fst, &fst_factored, &symbols);
// Check no epsilons in "symbols".
for (size_t i = 0; i < symbols.size(); i++)
assert(symbols[i].size() == 0 || *(std::min(symbols[i].begin(), symbols[i].end())) > 0);
VectorFst<Arc> fst_factored_pushed;
vector<vector<typename Arc::Label> > symbols_pushed;
Factor(fst_pushed, &fst_factored_pushed, &symbols_pushed);
std::cout << "Unfactored has "<<fst.NumStates()<<" states, factored has "<<fst_factored.NumStates()<<", and pushed+factored has "<<fst_factored_pushed.NumStates()<<'\n';
assert(fst_factored.NumStates() <= fst.NumStates());
// assert(fst_factored_pushed.NumStates() <= fst_factored.NumStates()); // pushing should only help. [ no, it doesn't]
assert(fst_factored_pushed.NumStates() <= fst_pushed.NumStates());
VectorFst<Arc> fst_factored_copy(fst_factored);
VectorFst<Arc> fst_factored_unfactored(fst_factored);
ExpandInputSequences(symbols, &fst_factored_unfactored);
VectorFst<Arc> factor_fst;
CreateFactorFst(symbols, &factor_fst);
VectorFst<Arc> fst_factored_unfactored2;
Compose(factor_fst, fst_factored, &fst_factored_unfactored2);
ExpandInputSequences(symbols_pushed, &fst_factored_pushed);
assert(RandEquivalent(fst, fst_factored_unfactored, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
assert(RandEquivalent(fst, fst_factored_unfactored2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
assert(RandEquivalent(fst, fst_factored_pushed, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
{ // Have tested for equivalence; now do another test: that FactorFst actually finds all
// the factors. Do this by inserting factors using ExpandInputSequences and making sure it gets
// rid of them all.
Label max_label = *(std::max_element(all_syms.begin(), all_syms.end()));
vector<vector<Label> > new_labels(max_label+1);
for (Label l = 1; l < static_cast<Label>(new_labels.size()); l++) {
int n = kaldi::Rand() % 5;
for (int i = 0; i < n; i++) new_labels[l].push_back(kaldi::Rand() % 100);
}
VectorFst<Arc> fst_expanded(fst);
ExpandInputSequences(new_labels, &fst_expanded);
vector<vector<Label> > factors;
VectorFst<Arc> fst_reduced;
Factor(fst_expanded, &fst_reduced, &factors);
assert(fst_reduced.NumStates() <= fst.NumStates()); // Checking that it found all the factors.
}
{ // This block test MapInputSymbols [but relies on the correctness of Factor
// and ExpandInputSequences to do so].
std::map<Label, Label> symbols_reverse_map; // from new->old.
symbols_reverse_map[0] = 0; // map eps to eps.
for (Label i = 1; i < static_cast<Label>(symbols.size()); i++) {
Label new_i;
do {
new_i = kaldi::Rand() % (symbols.size() + 20);
} while (symbols_reverse_map.count(new_i) == 1);
symbols_reverse_map[new_i] = i;
}
vector<vector<Label> > symbols_new;
vector<Label> symbol_map(symbols.size()); // from old->new.
typename std::map<Label, Label>::iterator iter = symbols_reverse_map.begin();
for (; iter != symbols_reverse_map.end(); iter++) {
Label new_label = iter->first, old_label = iter->second;
if (new_label >= static_cast<Label>(symbols_new.size())) symbols_new.resize(new_label+1);
symbols_new[new_label] = symbols[old_label];
symbol_map[old_label] = new_label;
}
MapInputSymbols(symbol_map, &fst_factored_copy);
ExpandInputSequences(symbols_new, &fst_factored_copy);
assert(RandEquivalent(fst, fst_factored_copy,
5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/,
100/*path length-- max?*/));
}
}
} // namespace fst
int main() {
using namespace fst;
for (int i = 0;i < 25;i++) {
TestFactor<fst::StdArc>();
}
}
+158
View File
@@ -0,0 +1,158 @@
// fstext/factor.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_FACTOR_H_
#define KALDI_FSTEXT_FACTOR_H_
/*
This header declares the Factor function, which takes an FST and
compresses it by detecting linear chains of states, and creating
special input symbols that represent these chains. It outputs enough
information to be able to reconstruct the original sequences [i.e.
the mapping between the new symbols, and sequences of the original
symbols]. It ensures that the original symbols all have the same
number as a corresponding "new" symbol representing a sequence of length
one; this enables certain optimizations later on.
*/
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "util/const-integer-set.h"
namespace fst {
/**
Factor identifies linear chains of states with an olabel (if any)
only on the first arc of the chain, and possibly a sequence of
ilabels; it outputs an FST with different symbols on the input
that represent sequences of the original input symbols; it outputs
the mapping from the new symbol to sequences of original symbols,
as "symbols" [zero is reserved for epsilon].
As a side effect it also sorts the FST in depth-first order. Factor will
usually do the best job when the olabels have been pushed to the left,
i.e. if you make a call like
Push<Arc, REWEIGHT_TO_INITIAL>(fsta, &fstb, kPushLabels);
This is because it only creates a chain with olabels on the first arc of the
chain (or a chain with no olabels). [it's possible to construct cases where
pushing makes things worse, though]. After Factor, the composition of *ofst
with the result of calling CreateFactorFst(*symbols) should be equivalent to
fst. Alternatively, calling ExpandInputSequences with ofst and *symbols
would produce something equivalent to fst.
*/
template<class Arc, class I>
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst,
std::vector<std::vector<I> > *symbols);
/// This is a more conventional interface of Factor that outputs
/// the result as two FSTs.
template<class Arc>
void Factor(const Fst<Arc> &fst, MutableFst<Arc> *ofst1,
MutableFst<Arc> *ofst2);
/// ExpandInputSequences expands out the input symbols into sequences of input
/// symbols. It creates linear chains of states for each arc that had >1
/// augmented symbol on it. It also sets the input symbol table to NULL, since
/// in case you did have a symbol table there it would no longer be valid. It
/// leaves any weight and output symbols on the first arc of the chain.
template<class Arc, class I>
void ExpandInputSequences(const std::vector<std::vector<I> > &sequences,
MutableFst<Arc> *fst);
/// The function CreateFactorFst will create an FST that expands out the
/// "factors" that are the indices of the "sequences" array, into linear sequences
/// of symbols. There is a single start and end state (state 0), and for each
/// nonzero index i into the array "sequences", there is an arc from state 0 that
/// has output-label i, and enters a chain of states with output epsilons and input
/// labels corresponding to the remaining elements of the sequences, terminating
/// again in state 0. This FST is output-deterministic and sorted on olabel.
/// Composing an FST on the left with the output of this function, should be the
/// same as calling "ExpandInputSequences". Use TableCompose (see table-matcher.h)
/// for efficiency.
template<class Arc, class I>
void CreateFactorFst(const std::vector<std::vector<I> > &sequences,
MutableFst<Arc> *fst);
/// CreateMapFst will create an FST representing this symbol_map. The
/// FST has a single loop state with single-arc loops with
/// isymbol = symbol_map[i], osymbol = i. The resulting FST applies this
/// map to the input symbols of something we compose with it on the right.
/// Must have symbol_map[0] == 0.
template<class Arc, class I>
void CreateMapFst(const std::vector<I> &symbol_map,
MutableFst<Arc> *fst);
enum StatePropertiesEnum
{ kStateFinal = 0x1,
kStateInitial = 0x2,
kStateArcsIn = 0x4,
kStateMultipleArcsIn = 0x8,
kStateArcsOut = 0x10,
kStateMultipleArcsOut = 0x20,
kStateOlabelsOut = 0x40,
kStateIlabelsOut = 0x80 };
typedef unsigned char StatePropertiesType;
/**
This function works out various properties of the states in the
FST, using the bit properties defined in StatePropertiesEnum. */
template<class Arc>
void GetStateProperties(const Fst<Arc> &fst,
typename Arc::StateId max_state,
std::vector<StatePropertiesType> *props);
template<class Arc>
class DfsOrderVisitor {
// visitor class that gives the user the dfs order,
// c.f. dfs-visit.h. Used in factor-fst-impl.h
typedef typename Arc::StateId StateId;
public:
DfsOrderVisitor(std::vector<StateId> *order): order_(order) { order->clear(); }
void InitVisit(const Fst<Arc> &fst) {}
bool InitState(StateId s, StateId) { order_->push_back(s); return true; }
bool TreeArc(StateId, const Arc&) { return true; }
bool BackArc(StateId, const Arc&) { return true; }
bool ForwardOrCrossArc(StateId, const Arc&) { return true; }
void FinishState(StateId, StateId, const Arc *) { }
void FinishVisit() { }
private:
std::vector<StateId> *order_;
};
} // namespace fst
#include "factor-inl.h"
#endif
@@ -0,0 +1,33 @@
// fstext/fst-test-utils.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_FST_TEST_UTILS_H_
#define KALDI_FSTEXT_FST_TEST_UTILS_H_
#include <sstream>
#include <string>
// Just some #includes.
#include "fst/script/print-impl.h"
#include "fstext/rand-fst.h"
#endif
@@ -0,0 +1,36 @@
// fstext/fstext-lib.h
// Copyright 2009-2012 Microsoft Corporation Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_FSTEXT_LIB_H_
#define KALDI_FSTEXT_FSTEXT_LIB_H_
#include "fst/fstlib.h"
#include "fstext/context-fst.h"
#include "fstext/determinize-star.h"
#include "fstext/factor.h"
#include "fstext/fst-test-utils.h"
#include "fstext/fstext-utils.h"
#include "fstext/pre-determinize.h"
#include "fstext/table-matcher.h"
#include "fstext/trivial-factor-weight.h"
#include "fstext/lattice-weight.h"
#include "fstext/lattice-utils.h"
#include "fstext/determinize-lattice.h"
#include "fstext/deterministic-fst.h"
#include "fstext/kaldi-fst-io.h"
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,434 @@
// fstext/fstext-utils-test.cc
// Copyright 2009-2012 Microsoft Corporation Daniel Povey
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-common.h" // for exceptions
#include "fstext/fstext-utils.h"
#include "fstext/fst-test-utils.h"
#include "util/stl-utils.h"
#include "base/kaldi-math.h"
namespace fst
{
using std::vector;
using std::cout;
template<class Arc, class I>
void TestMakeLinearAcceptor() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
int len = kaldi::Rand() % 10;
vector<I> vec;
vector<I> vec_nozeros;
for (int i = 0; i < len; i++) {
int j = kaldi::Rand() % len;
vec.push_back(j);
if (j != 0) vec_nozeros.push_back(j);
}
VectorFst<Arc> vfst;
MakeLinearAcceptor(vec, &vfst);
vector<I> vec2;
vector<I> vec3;
Weight w;
GetLinearSymbolSequence(vfst, &vec2, &vec3, &w);
assert(w == Weight::One());
assert(vec_nozeros == vec2);
assert(vec_nozeros == vec3);
if (vec2.size() != 0 || vec3.size() != 0) { // This test might not work
// for empty sequences...
{
vector<VectorFst<Arc> > fstvec;
NbestAsFsts(vfst, 1, &fstvec);
KALDI_ASSERT(fstvec.size() == 1);
assert(RandEquivalent(vfst, fstvec[0], 2/*paths*/, 0.01/*delta*/,
kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
}
}
bool include_eps = (kaldi::Rand() % 2 == 0);
if (!include_eps) vec = vec_nozeros;
kaldi::SortAndUniq(&vec);
vector<I> vec4;
GetInputSymbols(vfst, include_eps, &vec4);
assert(vec4 == vec);
vector<I> vec5;
GetInputSymbols(vfst, include_eps, &vec5);
}
template<class Arc> void TestDeterminizeStarInLog() {
VectorFst<Arc> *fst = RandFst<Arc>();
VectorFst<Arc> fst_copy(fst);
typename Arc::Label next_sym = 1 + HighestNumberedInputSymbol(*fst);
vector<typename Arc::Label> syms;
PreDeterminize(fst, NULL, "#", next_sym, &syms);
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestSafeDeterminizeWrapper() { // also tests SafeDeterminizeMinimizeWrapper().
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr = new SymbolTable("my-symbol-table");
sptr->AddSymbol("<eps>");
delete sptr;
sptr = new SymbolTable("my-symbol-table");
vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++) {
std::stringstream ss;
if (i == 0) ss << "<eps>";
else ss<<i;
Label cur_lab = sptr->AddSymbol(ss.str());
assert(cur_lab == (Label)i);
all_syms.push_back(cur_lab);
}
assert(all_syms[0] == 0);
// Create states.
vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
VectorFst<Arc> *fst_det = new VectorFst<Arc>;
vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
if (kaldi::Rand() % 2 == 0)
SafeDeterminizeWrapper(fst_copy_orig, fst_det);
else {
if (kaldi::Rand() % 2 == 0)
SafeDeterminizeMinimizeWrapper(fst_copy_orig, fst_det);
else
SafeDeterminizeMinimizeWrapperInLog(fst_copy_orig, fst_det);
}
// no because does shortest-dist on weights even if not pushing on them.
// PushInLog<REWEIGHT_TO_INITIAL>(fst_det, kPushLabels); // will always succeed.
KALDI_LOG << "Num states [orig]: " << fst->NumStates() << "[det]" << fst_det->NumStates();
assert(RandEquivalent(*fst, *fst_det, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
}
delete fst;
delete fst_copy_orig;
delete fst_det;
delete sptr;
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
void TestPushInLog() { // also tests SafeDeterminizeMinimizeWrapper().
typedef StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
VectorFst<Arc> *fst = RandFst<Arc>();
VectorFst<Arc> fst2(*fst);
PushInLog<REWEIGHT_TO_INITIAL>(&fst2, kPushLabels|kPushWeights, 0.01); // speed it up using large delta.
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
template<class Arc> void TestAcceptorMinimize() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = RandFst<Arc>();
Project(fst, PROJECT_INPUT);
RemoveWeights(fst);
VectorFst<Arc> fst2(*fst);
internal::AcceptorMinimize(&fst2);
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
template<class Arc> void TestMakeSymbolsSame() {
VectorFst<Arc> *fst = RandFst<Arc>();
bool foll = (kaldi::Rand() % 2 == 0);
bool is_symbol = (kaldi::Rand() % 2 == 0);
VectorFst<Arc> fst2(*fst);
if (foll) {
MakeFollowingInputSymbolsSame(is_symbol, &fst2);
assert(FollowingInputSymbolsAreSame(is_symbol, fst2));
} else {
MakePrecedingInputSymbolsSame(is_symbol, &fst2);
assert(PrecedingInputSymbolsAreSame(is_symbol, fst2));
}
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
template<class Arc>
struct TestFunctor {
typedef int32 Result;
typedef typename Arc::Label Arg;
Result operator () (Arg a) const {
if (a == kNoLabel) return -1;
else if (a == 0) return 0;
else {
return 1 + ((a-1) % 10);
}
}
};
template<class Arc> void TestMakeSymbolsSameClass() {
VectorFst<Arc> *fst = RandFst<Arc>();
bool foll = (kaldi::Rand() % 2 == 0);
bool is_symbol = (kaldi::Rand() % 2 == 0);
VectorFst<Arc> fst2(*fst);
TestFunctor<Arc> f;
if (foll) {
MakeFollowingInputSymbolsSameClass(is_symbol, &fst2, f);
assert(FollowingInputSymbolsAreSameClass(is_symbol, fst2, f));
} else {
MakePrecedingInputSymbolsSameClass(is_symbol, &fst2, f);
assert(PrecedingInputSymbolsAreSameClass(is_symbol, fst2, f));
}
assert(RandEquivalent(*fst, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
// MakeLoopFstCompare is as MakeLoopFst but implmented differently [ less efficiently
// but more clearly], so we can check for equivalence.
template<class Arc>
VectorFst<Arc>* MakeLoopFstCompare(const vector<const ExpandedFst<Arc> *> &fsts) {
VectorFst<Arc> *ans = new VectorFst<Arc>;
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
for (Label i = 0; i < fsts.size(); i++) {
if (fsts[i] != NULL) {
VectorFst<Arc> i_fst; // accepts symbol i on output.
i_fst.AddState(); i_fst.AddState();
i_fst.SetStart(0); i_fst.SetFinal(1, Weight::One());
i_fst.AddArc(0, Arc(0, i, Weight::One(), 1));
VectorFst<Arc> other_fst(*(fsts[i])); // copy it.
ClearSymbols(false, true, &other_fst); // Clear output symbols so symbols
// are on input side.
Concat(&i_fst, other_fst); // now i_fst is "i_fst [concat] other_fst".
Union(ans, i_fst);
}
}
Closure(ans, CLOSURE_STAR);
return ans;
}
template<class Arc> void TestMakeLoopFst() {
int num_fsts = kaldi::Rand() % 10;
vector<const ExpandedFst<Arc>* > fsts(num_fsts, (const ExpandedFst<Arc>*)NULL);
for (int i = 0; i < num_fsts; i++) {
if (kaldi::Rand() % 2 == 0) { // put an fst there.
VectorFst<Arc> *fst = RandFst<Arc>();
Project(fst, PROJECT_INPUT); // make input & output labels the same.
fsts[i] = fst;
} else { // this is to test that it works with the caching.
fsts[i] = fsts[i/2];
}
}
VectorFst<Arc> *fst1 = MakeLoopFst(fsts),
*fst2 = MakeLoopFstCompare(fsts);
assert(fst1->Properties(kOLabelSorted, kOLabelSorted) != 0);
assert(RandEquivalent(*fst1, *fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
delete fst2;
std::sort(fsts.begin(), fsts.end());
fsts.erase(std::unique(fsts.begin(), fsts.end()), fsts.end());
for (int i = 0; i < (int)fsts.size(); i++)
delete fsts[i];
}
template<class Arc>
void TestEqualAlign() {
for (size_t i = 0; i < 4; i++) {
RandFstOptions opts;
opts.allow_empty = false;
VectorFst<Arc> *fst = RandFst<Arc>();
int length = 10 + kaldi::Rand() % 20;
VectorFst<Arc> fst_path;
if (EqualAlign(*fst, length, kaldi::Rand(), &fst_path)) {
std::cout << "EqualAlign succeeded\n";
vector<int32> isymbol_seq, osymbol_seq;
typename Arc::Weight weight;
GetLinearSymbolSequence(fst_path, &isymbol_seq, &osymbol_seq, &weight);
assert(isymbol_seq.size() == length);
Invert(&fst_path);
VectorFst<Arc> fst_composed;
Compose(fst_path, *fst, &fst_composed);
assert(fst_composed.Start() != kNoStateId); // make sure nonempty.
} else {
std::cout << "EqualAlign did not generate alignment\n";
}
delete fst;
}
}
template<class Arc> void Print(const Fst<Arc> &fst, std::string message) {
std::cout << message << "\n";
FstPrinter<Arc> fstprinter(fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
template<class Arc>
void TestRemoveUselessArcs() {
for (size_t i = 0; i < 4; i++) {
RandFstOptions opts;
opts.allow_empty = false;
VectorFst<Arc> *fst = RandFst<Arc>();
// Print(*fst, "[testremoveuselessarcs]:fst:");
UniformArcSelector<Arc> selector;
RandGenOptions<UniformArcSelector<Arc> > randgen_opts(selector);
VectorFst<Arc> fst_path;
RandGen(*fst, &fst_path, randgen_opts);
Project(&fst_path, PROJECT_INPUT);
// Print(fst_path, "[testremoveuselessarcs]:fstpath:");
VectorFst<Arc> fst_nouseless(*fst);
RemoveUselessArcs(&fst_nouseless);
// Print(fst_nouseless, "[testremoveuselessarcs]:fst_nouseless:");
VectorFst<Arc> orig_composed,
nouseless_composed;
Compose(fst_path, *fst, &orig_composed);
Compose(fst_path, fst_nouseless, &nouseless_composed);
// Print(orig_composed, "[testremoveuselessarcs]:orig_composed");
// Print(nouseless_composed, "[testremoveuselessarcs]:nouseless_composed");
VectorFst<Arc> orig_bestpath,
nouseless_bestpath;
ShortestPath(orig_composed, &orig_bestpath);
ShortestPath(nouseless_composed, &nouseless_bestpath);
// Print(orig_bestpath, "[testremoveuselessarcs]:orig_bestpath");
// Print(nouseless_bestpath, "[testremoveuselessarcs]:nouseless_bestpath");
typename Arc::Weight worig, wnouseless;
GetLinearSymbolSequence<Arc, int>(orig_bestpath, NULL, NULL, &worig);
GetLinearSymbolSequence<Arc, int>(nouseless_bestpath, NULL, NULL, &wnouseless);
assert(ApproxEqual(worig, wnouseless, kDelta));
// assert(RandEquivalent(orig_bestpath, nouseless_bestpath, 5/*paths*/, 0.01/*delta*/, Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
}
} // end namespace fst
int main() {
for (int i = 0; i < 5; i++) {
fst::TestMakeLinearAcceptor<fst::StdArc, int>(); // this also tests GetLinearSymbolSequence, GetInputSymbols and GetOutputSymbols.
fst::TestMakeLinearAcceptor<fst::StdArc, int32>();
fst::TestMakeLinearAcceptor<fst::StdArc, uint32>();
fst::TestSafeDeterminizeWrapper<fst::StdArc>();
fst::TestAcceptorMinimize<fst::StdArc>();
fst::TestMakeSymbolsSame<fst::StdArc>();
fst::TestMakeSymbolsSame<fst::LogArc>();
fst::TestMakeSymbolsSameClass<fst::StdArc>();
fst::TestMakeSymbolsSameClass<fst::LogArc>();
fst::TestMakeLoopFst<fst::StdArc>();
fst::TestMakeLoopFst<fst::LogArc>();
fst::TestEqualAlign<fst::StdArc>();
fst::TestEqualAlign<fst::LogArc>();
fst::TestRemoveUselessArcs<fst::StdArc>();
}
}
@@ -0,0 +1,427 @@
// fstext/fstext-utils.h
// Copyright 2009-2011 Microsoft Corporation
// 2012-2013 Johns Hopkins University (Author: Daniel Povey)
// 2013 Guoguo Chen
// 2014 Telepoint Global Hosting Service, LLC. (Author: David Snyder)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_FSTEXT_UTILS_H_
#define KALDI_FSTEXT_FSTEXT_UTILS_H_
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "fstext/determinize-star.h"
#include "fstext/remove-eps-local.h"
#include "base/kaldi-common.h" // for error reporting macros.
#include "util/text-utils.h" // for SplitStringToVector
#include "fst/script/print-impl.h"
namespace fst {
/// Returns the highest numbered output symbol id of the FST (or zero
/// for an empty FST.
template<class Arc>
typename Arc::Label HighestNumberedOutputSymbol(const Fst<Arc> &fst);
/// Returns the highest numbered input symbol id of the FST (or zero
/// for an empty FST.
template<class Arc>
typename Arc::Label HighestNumberedInputSymbol(const Fst<Arc> &fst);
/// Returns the total number of arcs in an FST.
template<class Arc>
typename Arc::StateId NumArcs(const ExpandedFst<Arc> &fst);
/// GetInputSymbols gets the list of symbols on the input of fst
/// (including epsilon, if include_eps == true), as a sorted, unique
/// list.
template<class Arc, class I>
void GetInputSymbols(const Fst<Arc> &fst,
bool include_eps,
std::vector<I> *symbols);
/// GetOutputSymbols gets the list of symbols on the output of fst
/// (including epsilon, if include_eps == true)
template<class Arc, class I>
void GetOutputSymbols(const Fst<Arc> &fst,
bool include_eps,
std::vector<I> *symbols);
/// ClearSymbols sets all the symbols on the input and/or
/// output side of the FST to zero, as specified.
/// It does not alter the symbol tables.
template<class Arc>
void ClearSymbols(bool clear_input,
bool clear_output,
MutableFst<Arc> *fst);
template<class I>
void GetSymbols(const SymbolTable &symtab,
bool include_eps,
std::vector<I> *syms_out);
inline
void DeterminizeStarInLog(VectorFst<StdArc> *fst, float delta = kDelta, bool *debug_ptr = NULL,
int max_states = -1);
// e.g. of using this function: PushInLog<REWEIGHT_TO_INITIAL>(fst, kPushWeights|kPushLabels);
template<ReweightType rtype> // == REWEIGHT_TO_{INITIAL, FINAL}
void PushInLog(VectorFst<StdArc> *fst, uint32 ptype, float delta = kDelta) {
// PushInLog pushes the FST
// and returns a new pushed FST (labels and weights pushed to the left).
VectorFst<LogArc> *fst_log = new VectorFst<LogArc>; // Want to determinize in log semiring.
Cast(*fst, fst_log);
VectorFst<StdArc> tmp;
*fst = tmp; // free up memory.
VectorFst<LogArc> *fst_pushed_log = new VectorFst<LogArc>;
Push<LogArc, rtype>(*fst_log, fst_pushed_log, ptype, delta);
Cast(*fst_pushed_log, fst);
delete fst_log;
delete fst_pushed_log;
}
// Minimizes after encoding; applicable to all FSTs. It is like what you get
// from the Minimize() function, except it will not push the weights, or the
// symbols. This is better for our recipes, as we avoid ever pushing the
// weights. However, it will only minimize optimally if your graphs are such
// that the symbols are as far to the left as they can go, and the weights
// in combinable paths are the same... hard to formalize this, but it's something
// that is satisified by our normal FSTs.
template<class Arc>
void MinimizeEncoded(VectorFst<Arc> *fst, float delta = kDelta) {
Map(fst, QuantizeMapper<Arc>(delta));
EncodeMapper<Arc> encoder(kEncodeLabels | kEncodeWeights, ENCODE);
Encode(fst, &encoder);
internal::AcceptorMinimize(fst);
Decode(fst, encoder);
}
/// GetLinearSymbolSequence gets the symbol sequence from a linear FST.
/// If the FST is not just a linear sequence, it returns false. If it is
/// a linear sequence (including the empty FST), it returns true. In this
/// case it outputs the symbol
/// sequences as "isymbols_out" and "osymbols_out" (removing epsilons), and
/// the total weight as "tot_weight". The total weight will be Weight::Zero()
/// if the FST is empty. If any of the output pointers are NULL, it does not
/// create that output.
template<class Arc, class I>
bool GetLinearSymbolSequence(const Fst<Arc> &fst,
std::vector<I> *isymbols_out,
std::vector<I> *osymbols_out,
typename Arc::Weight *tot_weight_out);
/// 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<class Arc>
void ConvertNbestToVector(const Fst<Arc> &fst,
std::vector<VectorFst<Arc> > *fsts_out);
/// Takes the n-shortest-paths (using ShortestPath), but outputs
/// the result as a vector of up to n fsts. This function will
/// size the "fsts_out" vector to however many paths it got
/// (which will not exceed n). n must be >= 1.
template<class Arc>
void NbestAsFsts(const Fst<Arc> &fst,
size_t n,
std::vector<VectorFst<Arc> > *fsts_out);
/// Creates unweighted linear acceptor from symbol sequence.
template<class Arc, class I>
void MakeLinearAcceptor(const std::vector<I> &labels, MutableFst<Arc> *ofst);
/// Creates an unweighted acceptor with a linear structure, with alternatives
/// at each position. Epsilon is treated like a normal symbol here.
/// Each position in "labels" must have at least one alternative.
template<class Arc, class I>
void MakeLinearAcceptorWithAlternatives(const std::vector<std::vector<I> > &labels,
MutableFst<Arc> *ofst);
/// Does PreDeterminize and DeterminizeStar and then removes the disambiguation symbols.
/// This is a form of determinization that will never blow up.
/// Note that ifst is non-const and can be considered to be destroyed by this
/// operation.
/// Does not do epsilon removal (RemoveEpsLocal)-- this is so it's safe to cast to
/// log and do this, and maintain equivalence in tropical.
template<class Arc>
void SafeDeterminizeWrapper(MutableFst<Arc> *ifst, MutableFst<Arc> *ofst, float delta = kDelta);
/// SafeDeterminizeMinimizeWapper is as SafeDeterminizeWrapper except that it also
/// minimizes (encoded minimization, which is safe). This algorithm will destroy "ifst".
template<class Arc>
void SafeDeterminizeMinimizeWrapper(MutableFst<Arc> *ifst, VectorFst<Arc> *ofst, float delta = kDelta);
/// SafeDeterminizeMinimizeWapperInLog is as SafeDeterminizeMinimizeWrapper except
/// it first casts tothe log semiring.
void SafeDeterminizeMinimizeWrapperInLog(VectorFst<StdArc> *ifst, VectorFst<StdArc> *ofst, float delta = kDelta);
/// RemoveSomeInputSymbols removes any symbol that appears in "to_remove", from
/// the input side of the FST, replacing them with epsilon.
template<class Arc, class I>
void RemoveSomeInputSymbols(const std::vector<I> &to_remove,
MutableFst<Arc> *fst);
// MapInputSymbols will replace any input symbol i that is between 0 and
// symbol_map.size()-1, with symbol_map[i]. It removes the input symbol
// table of the FST.
template<class Arc, class I>
void MapInputSymbols(const std::vector<I> &symbol_map,
MutableFst<Arc> *fst);
template<class Arc>
void RemoveWeights(MutableFst<Arc> *fst);
/// Returns true if and only if the FST is such that the input symbols
/// on arcs entering any given state all have the same value.
/// if "start_is_epsilon", treat start-state as an epsilon input arc
/// [i.e. ensure only epsilon can enter start-state].
template<class Arc>
bool PrecedingInputSymbolsAreSame(bool start_is_epsilon, const Fst<Arc> &fst);
/// This is as PrecedingInputSymbolsAreSame, but with a functor f that maps labels to classes.
/// The function tests whether the symbols preceding any given state are in the same
/// class.
/// Formally, f is of a type F that has an operator of type
/// F::Result F::operator() (F::Arg a) const;
/// where F::Result is an integer type and F::Arc can be constructed from Arc::Label.
/// this must apply to valid labels and also to kNoLabel (so we can have a marker for
/// the invalid labels.
template<class Arc, class F>
bool PrecedingInputSymbolsAreSameClass(bool start_is_epsilon, const Fst<Arc> &fst, const F &f);
/// Returns true if and only if the FST is such that the input symbols
/// on arcs exiting any given state all have the same value.
/// If end_is_epsilon, treat end-state as an epsilon output arc [i.e. ensure
/// end-states cannot have non-epsilon output transitions.]
template<class Arc>
bool FollowingInputSymbolsAreSame(bool end_is_epsilon, const Fst<Arc> &fst);
template<class Arc, class F>
bool FollowingInputSymbolsAreSameClass(bool end_is_epsilon, const Fst<Arc> &fst, const F &f);
/// MakePrecedingInputSymbolsSame ensures that all arcs entering any given fst
/// state have the same input symbol. It does this by detecting states
/// that have differing input symbols going in, and inserting, for each of
/// the preceding arcs with non-epsilon input symbol, a new dummy state that
/// has an epsilon link to the fst state.
/// If "start_is_epsilon", ensure that start-state can have only epsilon-links
/// into it.
template<class Arc>
void MakePrecedingInputSymbolsSame(bool start_is_epsilon, MutableFst<Arc> *fst);
/// As MakePrecedingInputSymbolsSame, but takes a functor object that maps labels to classes.
template<class Arc, class F>
void MakePrecedingInputSymbolsSameClass(bool start_is_epsilon, MutableFst<Arc> *fst, const F &f);
/// MakeFollowingInputSymbolsSame ensures that all arcs exiting any given fst
/// state have the same input symbol. It does this by detecting states that have
/// differing input symbols on arcs that exit it, and inserting, for each of the
/// following arcs with non-epsilon input symbol, a new dummy state that has an
/// input-epsilon link from the fst state. The output symbol and weight stay on the
/// link to the dummy state (in order to keep the FST output-deterministic and
/// stochastic, if it already was).
/// If end_is_epsilon, treat "being a final-state" like having an epsilon output
/// link.
template<class Arc>
void MakeFollowingInputSymbolsSame(bool end_is_epsilon, MutableFst<Arc> *fst);
/// As MakeFollowingInputSymbolsSame, but takes a functor object that maps labels to classes.
template<class Arc, class F>
void MakeFollowingInputSymbolsSameClass(bool end_is_epsilon, MutableFst<Arc> *fst, const F &f);
/// MakeLoopFst creates an FST that has a state that is both initial and
/// final (weight == Weight::One()), and for each non-NULL pointer fsts[i],
/// it has an arc out whose output-symbol is i and which goes to a
/// sub-graph whose input language is equivalent to fsts[i], where the
/// final-state becomes a transition to the loop-state. Each fst in "fsts"
/// should be an acceptor. The fst MakeLoopFst returns is output-deterministic,
/// but not output-epsilon free necessarily, and arcs are sorted on output label.
/// Note: if some of the pointers in the input vector "fsts" have the same
/// value, "MakeLoopFst" uses this to speed up the computation.
/// Formally: suppose I is the set of indexes i such that fsts[i] != NULL.
/// Let L[i] be the language that the acceptor fsts[i] accepts.
/// Let the language K be the set of input-output pairs i:l such
/// that i in I and l in L[i]. Then the FST returned by MakeLoopFst
/// accepts the language K*, where * is the Kleene closure (CLOSURE_STAR)
/// of K.
/// We could have implemented this via a combination of "project",
/// "concat", "union" and "closure". But that FST would have been
/// less well optimized and would have a lot of final-states.
template<class Arc>
VectorFst<Arc>* MakeLoopFst(const std::vector<const ExpandedFst<Arc> *> &fsts);
/// ApplyProbabilityScale is applicable to FSTs in the log or tropical semiring.
/// It multiplies the arc and final weights by "scale" [this is not the Mul
/// operation of the semiring, it's actual multiplication, which is equivalent
/// to taking a power in the semiring].
template<class Arc>
void ApplyProbabilityScale(float scale, MutableFst<Arc> *fst);
/// EqualAlign is similar to RandGen, but it generates a sequence with exactly "length"
/// input symbols. It returns true on success, false on failure (failure is partly
/// random but should never happen in practice for normal speech models.)
/// It generates a random path through the input FST, finds out which subset of the
/// states it visits along the way have self-loops with inupt symbols on them, and
/// outputs a path with exactly enough self-loops to have the requested number
/// of input symbols.
/// Note that EqualAlign does not use the probabilities on the FST. It just uses
/// equal probabilities in the first stage of selection (since the output will anyway
/// not be a truly random sample from the FST).
/// The input fst "ifst" must be connected or this may enter an infinite loop.
template<class Arc>
bool EqualAlign(const Fst<Arc> &ifst, typename Arc::StateId length,
int rand_seed, MutableFst<Arc> *ofst, int num_retries = 10);
// RemoveUselessArcs removes arcs such that there is no input symbol
// sequence for which the best path through the FST would contain
// those arcs [for these purposes, epsilon is not treated as a real symbol].
// This is mainly geared towards decoding-graph FSTs which may contain
// transitions that have less likely words on them that would never be
// taken. We do not claim that this algorithm removes all such arcs;
// it just does the best job it can.
// Only works for tropical (not log) semiring as it uses
// NaturalLess.
template<class Arc>
void RemoveUselessArcs(MutableFst<Arc> *fst);
// PhiCompose is a version of composition where
// the right hand FST (fst2) is treated as a backoff
// LM, with the phi symbol (e.g. #0) treated as a
// "failure transition", only taken when we don't
// have a match for the requested symbol.
template<class Arc>
void PhiCompose(const Fst<Arc> &fst1,
const Fst<Arc> &fst2,
typename Arc::Label phi_label,
MutableFst<Arc> *fst);
// PropagateFinal propagates final-probs through
// "phi" transitions (note that here, phi_label may
// be epsilon if you want). If you have a backoff LM
// with special symbols ("phi") on the backoff arcs
// instead of epsilon, you may use PhiCompose to compose
// with it, but this won't do the right thing w.r.t.
// final probabilities. You should first call PropagateFinal
// on the FST with phi's i it (fst2 in PhiCompose above),
// to fix this. If a state does not have a final-prob,
// but has a phi transition, it makes the state's final-prob
// (phi-prob * final-prob-of-dest-state), and does this
// recursively i.e. follows phi transitions on the dest state
// first. It behaves as if there were a super-final state
// with a special symbol leading to it, from each currently
// final state. Note that this may not behave as desired
// if there are epsilons in your FST; it might be better
// to remove those before calling this function.
template<class Arc>
void PropagateFinal(typename Arc::Label phi_label,
MutableFst<Arc> *fst);
// RhoCompose is a version of composition where
// the right hand FST (fst2) has speciall "rho transitions"
// which are taken whenever no normal transition matches; these
// transitions will be rewritten with whatever symbol was on
// the first FST.
template<class Arc>
void RhoCompose(const Fst<Arc> &fst1,
const Fst<Arc> &fst2,
typename Arc::Label rho_label,
MutableFst<Arc> *fst);
/** This function returns true if, in the semiring of the FST, the sum (within
the semiring) of all the arcs out of each state in the FST is one, to within
delta. After MakeStochasticFst, this should be true (for a connected FST).
@param fst [in] the FST that we are testing.
@param delta [in] the tolerance to within which we test equality to 1.
@param min_sum [out] if non, NULL, contents will be set to the minimum sum of weights.
@param max_sum [out] if non, NULL, contents will be set to the maximum sum of weights.
@return Returns true if the FST is stochastic, and false otherwise.
*/
template<class Arc>
bool IsStochasticFst(const Fst<Arc> &fst,
float delta = kDelta, // kDelta = 1.0/1024.0 by default.
typename Arc::Weight *min_sum = NULL,
typename Arc::Weight *max_sum = NULL);
// IsStochasticFstInLog makes sure it's stochastic after casting to log.
inline bool IsStochasticFstInLog(const Fst<StdArc> &fst,
float delta = kDelta, // kDelta = 1.0/1024.0 by default.
StdArc::Weight *min_sum = NULL,
StdArc::Weight *max_sum = NULL);
} // end namespace fst
#include "fstext/fstext-utils-inl.h"
#endif
@@ -0,0 +1,228 @@
// fstext/grammar-context-fst.cc
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/grammar-context-fst.h"
#include "base/kaldi-error.h"
#include "util/stl-utils.h"
namespace fst {
using std::vector;
InverseLeftBiphoneContextFst::InverseLeftBiphoneContextFst(
Label nonterm_phones_offset,
const vector<int32>& phones,
const vector<int32>& disambig_syms):
nonterm_phones_offset_(nonterm_phones_offset),
phone_syms_(phones),
disambig_syms_(disambig_syms) {
{ // This block does some checks.
std::vector<int32> all_inputs(phones);
all_inputs.insert(all_inputs.end(), disambig_syms.begin(),
disambig_syms.end());
all_inputs.push_back(nonterm_phones_offset);
size_t size = all_inputs.size();
kaldi::SortAndUniq(&all_inputs);
if (all_inputs.size() != size) {
KALDI_ERR << "There was overlap between disambig symbols, phones, "
"and/or --nonterm-phones-offset";
}
if (all_inputs.front() <= 0)
KALDI_ERR << "Symbols <= 0 were passed in as phones, disambig-syms, "
"or nonterm-phones-offset.";
if (all_inputs.back() != nonterm_phones_offset) {
// the value passed --nonterm-phones-offset is not higher numbered
// than all the phones and disambig syms... do some more checking.
for (int32 i = 1; i < 4; i++) {
int32 symbol = nonterm_phones_offset + i;
// None of the symbols --nonterm-phones-offset + {kNontermBos, kNontermBegin,
// kNontermEnd, kNontermReenter, kNontermUserDefined}
// (i.e. the special symbols plus the first user-defined symbol) may be
// listed as phones or disambig symbols... this doesn't make sense. We
// do allow disambig symbols to be higher-numbered than the nonterminal
// sybols, just in case that happens to be needed, but they can't overlap.
if (std::binary_search(all_inputs.begin(), all_inputs.end(), symbol)) {
KALDI_ERR << "The symbol " << symbol
<< " = --nonterm-phones-offset + " << i
<< " was listed as a phone or disambig symbol.";
}
}
}
if (phone_syms_.empty())
KALDI_WARN << "Context FST created but there are no phone symbols: probably "
"input FST was empty.";
}
// empty vector, will be the ilabel_info vector that corresponds to epsilon,
// in case our FST needs to output epsilons.
vector<int32> empty_vec;
Label epsilon_label = FindLabel(empty_vec);
// Make sure that a label is assigned for epsilon.
KALDI_ASSERT(epsilon_label == 0);
}
InverseLeftBiphoneContextFst::Weight InverseLeftBiphoneContextFst::Final(StateId s) {
if (s == 0 || phone_syms_.count(s) != 0 ||
s == GetPhoneSymbolFor(kNontermEnd))
return Weight::One();
else
return Weight::Zero();
}
bool InverseLeftBiphoneContextFst::GetArc(
StateId s, Label ilabel, Arc *arc) {
// it's a rule of the DeterministicOnDemandFst that the ilabel cannot be zero.q
KALDI_ASSERT(ilabel != 0);
arc->ilabel = ilabel;
arc->weight = Weight::One();
if (s == 0 || phone_syms_.count(s) != 0) {
// This is an epsilon or phone state.
if (phone_syms_.count(ilabel) != 0) {
// The ilabel is a phone.
std::vector<int32> context_window(2);
context_window[0] = s;
context_window[1] = ilabel;
arc->olabel = FindLabel(context_window);
arc->nextstate = ilabel;
return true;
} else if (disambig_syms_.count(ilabel) != 0) {
// the ilabel is a disambiguation symbol. Make a self-loop arc that
// replicates the disambiguation symbol on the input.
// The ilabel-info vector for disambig symbols is just a single element
// consisting of the negative of the disambig symbols (for easier
// identification from code).
std::vector<int32> this_ilabel_info(1);
this_ilabel_info[0] = -ilabel;
arc->olabel = FindLabel(this_ilabel_info);
arc->nextstate = s;
return true;
} else if (ilabel == GetPhoneSymbolFor(kNontermBegin) &&
s == 0) {
// We were at the start state and saw the symbol #nonterm_begin.
// Output nothing, but transition to the special #nonterm_begin state.
// when we're in that state, arcs for phones generate special
// osymbols corresponding to pairs like (#nonterm_begin, p1).
arc->olabel = 0;
arc->nextstate = GetPhoneSymbolFor(kNontermBegin);
return true;
} else if (ilabel == GetPhoneSymbolFor(kNontermEnd)) {
// we saw #nonterm_end.
std::vector<int32> this_ilabel_info(2);
this_ilabel_info[0] = -(GetPhoneSymbolFor(kNontermEnd));
this_ilabel_info[1] = (s != 0 ? s : GetPhoneSymbolFor(kNontermBos));
arc->olabel = FindLabel(this_ilabel_info);
arc->nextstate = GetPhoneSymbolFor(kNontermEnd);
return true;
} else if (ilabel >= GetPhoneSymbolFor(kNontermUserDefined)) {
// Assume this ilabel is a user-defined nonterminal.
// Transition to the state kNontermUserDefined, with an olabel
// (#nonterm:foo, p1) where 'p1' is the current left-context.
std::vector<int32> this_ilabel_info(2);
this_ilabel_info[0] = -ilabel;
this_ilabel_info[1] = (s != 0 ? s : GetPhoneSymbolFor(kNontermBos));
arc->olabel = FindLabel(this_ilabel_info);
// the destination state is not specific to this user-defined symbol, it's
// a generic destination state.
arc->nextstate = GetPhoneSymbolFor(kNontermUserDefined);
return true;
} else {
return false;
}
} else if (s == GetPhoneSymbolFor(kNontermBegin)) {
if (phone_syms_.count(ilabel) != 0 || ilabel == GetPhoneSymbolFor(kNontermBos)) {
std::vector<int32> this_ilabel_info(2);
this_ilabel_info[0] = -GetPhoneSymbolFor(kNontermBegin);
this_ilabel_info[1] = ilabel;
arc->nextstate = (ilabel == GetPhoneSymbolFor(kNontermBos) ? 0 : ilabel);
arc->olabel = FindLabel(this_ilabel_info);
return true;
} else {
return false;
}
} else if (s == GetPhoneSymbolFor(kNontermEnd)) {
return false;
} else if (s == GetPhoneSymbolFor(kNontermUserDefined)) {
if (phone_syms_.count(ilabel) != 0 || ilabel == GetPhoneSymbolFor(kNontermBos)) {
std::vector<int32> this_ilabel_info(2);
this_ilabel_info[0] = -GetPhoneSymbolFor(kNontermReenter);
this_ilabel_info[1] = ilabel;
arc->nextstate = (ilabel == GetPhoneSymbolFor(kNontermBos) ? 0 : ilabel);
arc->olabel = FindLabel(this_ilabel_info);
return true;
} else {
return false;
}
} else {
// likely code error.
KALDI_ERR << "Invalid state encountered";
return false; // won't get here. suppress compiler error.
}
}
StdArc::Label InverseLeftBiphoneContextFst::FindLabel(const vector<int32> &label_vec) {
// Finds the ilabel corresponding to this vector (creates a new ilabel if
// necessary).
VectorToLabelMap::const_iterator iter = ilabel_map_.find(label_vec);
if (iter == ilabel_map_.end()) { // Not already in map.
Label this_label = ilabel_info_.size();
ilabel_info_.push_back(label_vec);
ilabel_map_[label_vec] = this_label;
return this_label;
} else {
return iter->second;
}
}
void ComposeContextLeftBiphone(
int32 nonterm_phones_offset,
const vector<int32> &disambig_syms_in,
const VectorFst<StdArc> &ifst,
VectorFst<StdArc> *ofst,
std::vector<std::vector<int32> > *ilabels) {
vector<int32> disambig_syms(disambig_syms_in);
std::sort(disambig_syms.begin(), disambig_syms.end());
vector<int32> all_syms;
GetInputSymbols(ifst, false/*no eps*/, &all_syms);
std::sort(all_syms.begin(), all_syms.end());
vector<int32> phones;
for (size_t i = 0; i < all_syms.size(); i++)
if (!std::binary_search(disambig_syms.begin(),
disambig_syms.end(), all_syms[i]) &&
all_syms[i] < nonterm_phones_offset)
phones.push_back(all_syms[i]);
InverseLeftBiphoneContextFst inv_c(nonterm_phones_offset,
phones, disambig_syms);
// The following statement is equivalent to the following
// (if FSTs had the '*' operator for composition):
// (*ofst) = inv(inv_c) * (*ifst)
ComposeDeterministicOnDemandInverse(ifst, &inv_c, ofst);
inv_c.SwapIlabelInfo(ilabels);
}
} // end namespace fst
@@ -0,0 +1,287 @@
// fstext/grammar-context-fst.h
// Copyright 2018 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
//
#ifndef KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
#define KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
/* This header defines a special form of the context FST "C" (the "C" in "HCLG")
that integrates with our framework for building dynamic graphs for grammars
that are too big to statically create, or graphs with on-the-fly pieces that
you want to create at recognition time without building the whole graph.
This framework is limited to only work with models with left-biphone context.
(Fortunately this doesn't impact results, as our best models are all 'chain'
models with left biphone context).
The main code exported from here is the class InverseLeftBiphoneContextFst,
which is similar to the InverseContextFst defined in context-fst.h, but
is limited to left-biphone context and also supports certain special
extensions we need to compile grammars.
See \ref grammar (../doc/grammar.dox) for high-level
documentation on how this framework works.
*/
#include <algorithm>
#include <string>
#include <vector>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "util/const-integer-set.h"
#include "fstext/deterministic-fst.h"
#include "fstext/context-fst.h"
namespace fst {
/**
An anonymous enum to define some values for symbols used in our grammar-fst
framework. Please understand this with reference to the documentation in
\ref grammar (../doc/grammar.dox). This enum defines
the values of nonterminal-related symbols in phones.txt. They are not
the actual values-- they will be shifted by adding the value
nonterm_phones_offset which is passed in by the command-line flag
--nonterm-phones-offset.
*/
enum NonterminalValues {
kNontermBos = 0, // #nonterm_bos
kNontermBegin = 1, // #nonterm_begin
kNontermEnd = 2, // #nonterm_end
kNontermReenter = 3, // #nonterm_reenter
kNontermUserDefined = 4, // the lowest-numbered user-defined nonterminal, e.g. #nonterm:foo
// kNontermMediumNumber and kNontermBigNumber come into the encoding of
// nonterminal-related symbols in HCLG.fst. The only hard constraint on them
// is that kNontermBigNumber must be bigger than the biggest transition-id in
// your system, and kNontermMediumNumber must be >0. These values were chosen
// for ease of human inspection of numbers encoded with them.
kNontermMediumNumber = 1000,
kNontermBigNumber = 10000000
};
// Returns the smallest multiple of 1000 that is strictly greater than
// nonterm_phones_offset. Used in the encoding of special symbol in HCLG;
// they are encoded as
// special_symbol =
// kNontermBigNumber + (nonterminal * encoding_multiple) + phone_index
inline int32 GetEncodingMultiple(int32 nonterm_phones_offset) {
int32 medium_number = static_cast<int32>(kNontermMediumNumber);
return medium_number *
((nonterm_phones_offset + medium_number) / medium_number);
}
/**
This is a variant of the function ComposeContext() which is to be used
with our "grammar FST" framework (see \ref graph_context, i.e.
../doc/grammar.dox, for more details). This does not take
the 'context_width' and 'central_position' arguments because they are
assumed to be 2 and 1 respectively (meaning, left-biphone phonetic context).
This function creates a context FST and composes it on the left with "ifst"
to make "ofst".
@param [in] nonterm_phones_offset The integer id of the symbol
#nonterm_bos in the phones.txt file. You can just set this
to a large value (like 1 million) if you are not actually using
nonterminals (e.g. for testing purposes).
@param [in] disambig_syms List of disambiguation symbols, e.g. the integer
ids of #0, #1, #2 ... in the phones.txt.
@param [in,out] ifst The FST we are composing with C (e.g. LG.fst).
@param [out] ofst Composed output FST (would be CLG.fst).
@param [out] ilabels Vector, indexed by ilabel of CLG.fst, providing information
about the meaning of that ilabel; see \ref tree_ilabel
(http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel)
and also \ref grammar_special_clg
(http://kaldi-asr.org/doc/grammar#grammar_special_clg).
*/
void ComposeContextLeftBiphone(
int32 nonterm_phones_offset,
const std::vector<int32> &disambig_syms,
const VectorFst<StdArc> &ifst,
VectorFst<StdArc> *ofst,
std::vector<std::vector<int32> > *ilabels);
/*
InverseLeftBiphoneContextFst represents the inverse of the context FST "C" (the "C" in
"HCLG") which transduces from symbols representing phone context windows
(e.g. "a, b, c") to individual phones, e.g. "a". So InverseContextFst
transduces from phones to symbols representing phone context windows. The
point is that the inverse is deterministic, so the DeterministicOnDemandFst
interface is applicable, which turns out to be a convenient way to implement
this.
This doesn't implement the full Fst interface, it implements the
DeterministicOnDemandFst interface which is much simpler and which is
sufficient for what we need to do with this.
Search for "hbka.pdf" ("Speech Recognition with Weighted Finite State
Transducers") by M. Mohri, for more context.
*/
class InverseLeftBiphoneContextFst: public DeterministicOnDemandFst<StdArc> {
public:
typedef StdArc Arc;
typedef typename StdArc::StateId StateId;
typedef typename StdArc::Weight Weight;
typedef typename StdArc::Label Label;
/**
Constructor. This does not take the arguments 'context_width' or
'central_position' because they are assumed to be (2, 1) meaning a
system with left-biphone context; and there is no subsequential
symbol because it is not needed in systems without right context.
@param [in] nonterm_phones_offset The integer id of the symbol
#nonterm_bos in the phones.txt file. You can just set this to
a large value (like 1 million) if you are not actually using
nonterminals (e.g. for testing purposes).
@param [in] phones List of integer ids of phones, as you would see in phones.txt
@param [in] disambig_syms List of integer ids of disambiguation symbols,
e.g. the ids of #0, #1, #2 in phones.txt
See \ref graph_context for more details.
*/
InverseLeftBiphoneContextFst(Label nonterm_phones_offset,
const std::vector<int32>& phones,
const std::vector<int32>& disambig_syms);
/**
Here is a note on the state space of InverseLeftBiphoneContextFst;
see \ref grammar_special_c which has some documentation on this.
The state space uses the same numbering as phones.txt.
State 0 means the beginning-of-sequence state, where there is no left
context.
For each phone p in the list 'phones' passed to the constructor (i.e. in
the set passed to the constructor), the state 'p' corresponds to a
left-context of that phone.
If p is equal to nonterm_phones_offset_ + kNontermBegin (i.e. the
integer form of `\#nonterm_begin`), then this is the state we transition
to when we see that symbol starting from left-context==0 (no context). The
transition to this special state will have epsilon on the output. (talking
here about inv(C), not C, so input/output are reversed).
The state is nonfinal and when we see a regular phone p1 or #nonterm_bos, instead of
outputting that phone in context, we output the pair (#nonterm_begin,p1) or
(#nonterm_begin,#nonterm_bos). This state is not final.
If p is equal to nonterm_phones_offset_ + kNontermUserDefined, then this
is the state we transition to when we see any user-defined nonterminal.
Transitions to this special state have olabels of the form (#nonterm:foo,p1)
where p1 is the preceding context (with #nonterm_begin if that context was
0); transitions out of it have olabels of the form (#nonterm_reenter,p2), where
p2 is the phone on the ilabel of that transition. Again: talking about inv(C).
This state is not final.
If p is equal to nonterm_phones_offset_ + kNontermEnd, then this is
the state we transition to when we see the ilabel #nonterm_end. The olabels
on the transitions to it (talking here about inv(C), so ilabels and olabels
are reversed) are of the form (#nonterm_end, p1) where p1 corresponds to the
context we were in. This state is final.
*/
virtual StateId Start() { return 0; }
virtual Weight Final(StateId s);
/// Note: ilabel must not be epsilon.
virtual bool GetArc(StateId s, Label ilabel, Arc *arc);
~InverseLeftBiphoneContextFst() { }
// Returns a reference to a vector<vector<int32> > with information about all
// the input symbols of C (i.e. all the output symbols of this
// InverseContextFst). See
// "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
const std::vector<std::vector<int32> > &IlabelInfo() const {
return ilabel_info_;
}
// A way to destructively obtain the ilabel-info. Only do this if you
// are just about to destroy this object.
void SwapIlabelInfo(std::vector<std::vector<int32> > *vec) { ilabel_info_.swap(*vec); }
private:
inline int32 GetPhoneSymbolFor(enum NonterminalValues n) {
return nonterm_phones_offset_ + static_cast<int32>(n);
}
/// Finds the label index corresponding to this context-window of phones
/// (likely of width context_width_). Inserts it into the
/// ilabel_info_/ilabel_map_ tables if necessary.
Label FindLabel(const std::vector<int32> &label_info);
// Map type to map from vectors of int32 (representing ilabel-info,
// see http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel) to
// Label (the output label in this FST).
typedef unordered_map<std::vector<int32>, Label,
kaldi::VectorHasher<int32> > VectorToLabelMap;
// The following three variables were also passed in by the caller:
int32 nonterm_phones_offset_;
// 'phone_syms_' are a set of phone-ids, typically 1, 2, .. num_phones.
kaldi::ConstIntegerSet<Label> phone_syms_;
// disambig_syms_ is the set of integer ids of the disambiguation symbols,
// usually represented in text form as #0, #1, #2, etc. These are inserted
// into the grammar (for #0) and the lexicon (for #1, #2, ...) in order to
// make the composed FSTs determinizable. They are treated "specially" by the
// context FST in that they are not part of the context, they are just "passed
// through" via self-loops. See the Mohri chapter mrentioned above for more
// information.
kaldi::ConstIntegerSet<Label> disambig_syms_;
// maps from vector<int32>, representing phonetic contexts of length
// context_width_ - 1, to Label. These are actually the output labels of this
// InverseContextFst (because of the "Inverse" part), but for historical
// reasons and because we've used the term ilabels" in the documentation, we
// still call these "ilabels").
VectorToLabelMap ilabel_map_;
// ilabel_info_ is the reverse map of ilabel_map_.
// Indexed by olabel (although we call this ilabel_info_ for historical
// reasons and because is for the ilabels of C), ilabel_info_[i] gives
// information about the meaning of each symbol on the input of C
// aka the output of inv(C).
// See "http://kaldi-asr.org/doc/tree_externals.html#tree_ilabel".
std::vector<std::vector<int32> > ilabel_info_;
};
} // namespace fst
#endif // KALDI_FSTEXT_GRAMMAR_CONTEXT_FST_H_
@@ -0,0 +1,211 @@
// fstext/kaldi-fst-io-inl.h
// Copyright 2009-2011 Microsoft Corporation
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
// 2013 Guoguo Chen
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_KALDI_FST_IO_INL_H_
#define KALDI_FSTEXT_KALDI_FST_IO_INL_H_
#include "util/text-utils.h"
namespace fst {
template <class Arc>
void WriteFstKaldi(std::ostream &os, bool binary,
const VectorFst<Arc> &t) {
bool ok;
if (binary) {
// Binary-mode writing.
ok = t.Write(os, FstWriteOptions());
} else {
// Text-mode output. Note: we expect that t.InputSymbols() and
// t.OutputSymbols() would always return NULL. The corresponding input
// routine would not work if the FST actually had symbols attached. Write a
// newline to start the FST; in a table, the first line of the FST will
// appear on its own line.
os << '\n';
bool acceptor = false, write_one = false;
FstPrinter<Arc> printer(t, t.InputSymbols(), t.OutputSymbols(),
NULL, acceptor, write_one, "\t");
printer.Print(&os, "<unknown>");
if (os.fail())
KALDI_ERR << "Stream failure detected writing FST to stream";
// Write another newline as a terminating character. The read routine will
// detect this [this is a Kaldi mechanism, not something in the original
// OpenFst code].
os << '\n';
ok = os.good();
}
if (!ok) {
KALDI_ERR << "Error writing FST to stream";
}
}
// Utility function used in ReadFstKaldi
template <class W>
inline bool StrToWeight(const std::string &s, bool allow_zero, W *w) {
std::istringstream strm(s);
strm >> *w;
if (strm.fail() || (!allow_zero && *w == W::Zero())) {
return false;
}
return true;
}
template <class Arc>
void ReadFstKaldi(std::istream &is, bool binary,
VectorFst<Arc> *fst) {
typedef typename Arc::Weight Weight;
typedef typename Arc::StateId StateId;
if (binary) {
// We don't have access to the filename here, so write [unknown].
VectorFst<Arc> *ans =
VectorFst<Arc>::Read(is, fst::FstReadOptions(std::string("[unknown]")));
if (ans == NULL) {
KALDI_ERR << "Error reading FST from stream.";
}
*fst = *ans; // shallow copy.
delete ans;
} else {
// Consume the \r on Windows, the \n that the text-form FST format starts
// with, and any extra spaces that might have got in there somehow.
while (std::isspace(is.peek()) && is.peek() != '\n') is.get();
if (is.peek() == '\n') is.get(); // consume the newline.
else { // saw spaces but no newline.. this is not expected.
KALDI_ERR << "Reading FST: unexpected sequence of spaces "
<< " at file position " << is.tellg();
}
using std::string;
using std::vector;
using kaldi::SplitStringToIntegers;
using kaldi::ConvertStringToInteger;
fst->DeleteStates();
string line;
size_t nline = 0;
string separator = FLAGS_fst_field_separator + "\r\n";
while (std::getline(is, line)) {
nline++;
vector<string> col;
// on Windows we'll write in text and read in binary mode.
kaldi::SplitStringToVector(line, separator.c_str(), true, &col);
if (col.size() == 0) break; // Empty line is a signal to stop, in our
// archive format.
if (col.size() > 5) {
KALDI_ERR << "Bad line in FST: " << line;
}
StateId s;
if (!ConvertStringToInteger(col[0], &s)) {
KALDI_ERR << "Bad line in FST: " << line;
}
while (s >= fst->NumStates())
fst->AddState();
if (nline == 1) fst->SetStart(s);
bool ok = true;
Arc arc;
Weight w;
StateId d = s;
switch (col.size()) {
case 1:
fst->SetFinal(s, Weight::One());
break;
case 2:
if (!StrToWeight(col[1], true, &w)) ok = false;
else fst->SetFinal(s, w);
break;
case 3: // 3 columns not ok for Lattice format; it's not an acceptor.
ok = false;
break;
case 4:
ok = ConvertStringToInteger(col[1], &arc.nextstate) &&
ConvertStringToInteger(col[2], &arc.ilabel) &&
ConvertStringToInteger(col[3], &arc.olabel);
if (ok) {
d = arc.nextstate;
arc.weight = Weight::One();
fst->AddArc(s, arc);
}
break;
case 5:
ok = ConvertStringToInteger(col[1], &arc.nextstate) &&
ConvertStringToInteger(col[2], &arc.ilabel) &&
ConvertStringToInteger(col[3], &arc.olabel) &&
StrToWeight(col[4], false, &arc.weight);
if (ok) {
d = arc.nextstate;
fst->AddArc(s, arc);
}
break;
default:
ok = false;
}
while (d >= fst->NumStates()) fst->AddState();
if (!ok)
KALDI_ERR << "Bad line in FST: " << line;
}
}
}
template<class Arc> // static
bool VectorFstTplHolder<Arc>::Write(std::ostream &os, bool binary, const T &t) {
try {
WriteFstKaldi(os, binary, t);
return true;
} catch (...) {
return false;
}
}
template<class Arc> // static
bool VectorFstTplHolder<Arc>::Read(std::istream &is) {
Clear();
int c = is.peek();
if (c == -1) {
KALDI_WARN << "End of stream detected reading Fst";
return false;
} else if (isspace(c)) { // The text form of the FST begins
// with space (normally, '\n'), so this means it's text (the binary form
// cannot begin with space because it starts with the FST Type() which is not
// space).
try {
t_ = new VectorFst<Arc>();
ReadFstKaldi(is, false, t_);
} catch (...) {
Clear();
return false;
}
} else { // reading a binary FST.
try {
t_ = new VectorFst<Arc>();
ReadFstKaldi(is, true, t_);
} catch (...) {
Clear();
return false;
}
}
return true;
}
} // namespace fst.
#endif // KALDI_FSTEXT_KALDI_FST_IO_INL_H_
@@ -0,0 +1,145 @@
// fstext/kaldi-fst-io.cc
// Copyright 2009-2011 Microsoft Corporation
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
// 2013 Guoguo Chen
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/kaldi-fst-io.h"
#include "base/kaldi-error.h"
#include "base/kaldi-math.h"
#include "util/kaldi-io.h"
namespace fst {
VectorFst<StdArc> *ReadFstKaldi(std::string rxfilename) {
if (rxfilename == "") rxfilename = "-"; // interpret "" as stdin,
// for compatibility with OpenFst conventions.
kaldi::Input ki(rxfilename);
fst::FstHeader hdr;
if (!hdr.Read(ki.Stream(), rxfilename))
KALDI_ERR << "Reading FST: error reading FST header from "
<< kaldi::PrintableRxfilename(rxfilename);
FstReadOptions ropts("<unspecified>", &hdr);
VectorFst<StdArc> *fst = VectorFst<StdArc>::Read(ki.Stream(), ropts);
if (!fst)
KALDI_ERR << "Could not read fst from "
<< kaldi::PrintableRxfilename(rxfilename);
return fst;
}
// Register const fst to load it automatically. Other types like
// olabel_lookahead or ngram or compact_fst should be registered
// through OpenFst registration API.
static fst::FstRegisterer<VectorFst<StdArc>> VectorFst_StdArc_registerer;
static fst::FstRegisterer<ConstFst<StdArc>> ConstFst_StdArc_registerer;
Fst<StdArc> *ReadFstKaldiGeneric(std::string rxfilename, bool throw_on_err) {
if (rxfilename == "") rxfilename = "-"; // interpret "" as stdin,
// for compatibility with OpenFst conventions.
kaldi::Input ki(rxfilename);
fst::FstHeader hdr;
// Read FstHeader which contains the type of FST
if (!hdr.Read(ki.Stream(), rxfilename)) {
if(throw_on_err) {
KALDI_ERR << "Reading FST: error reading FST header from "
<< kaldi::PrintableRxfilename(rxfilename);
} else {
KALDI_WARN << "We fail to read FST header from "
<< kaldi::PrintableRxfilename(rxfilename)
<< ". A NULL pointer is returned.";
return NULL;
}
}
// Check the type of Arc
if (hdr.ArcType() != fst::StdArc::Type()) {
if(throw_on_err) {
KALDI_ERR << "FST with arc type " << hdr.ArcType() << " is not supported.";
} else {
KALDI_WARN << "Fst with arc type" << hdr.ArcType()
<< " is not supported. A NULL pointer is returned.";
return NULL;
}
}
// Read the FST
FstReadOptions ropts("<unspecified>", &hdr);
Fst<StdArc> *fst = Fst<StdArc>::Read(ki.Stream(), ropts);
if (!fst) {
if(throw_on_err) {
KALDI_ERR << "Could not read fst from "
<< kaldi::PrintableRxfilename(rxfilename);
} else {
KALDI_WARN << "Could not read fst from "
<< kaldi::PrintableRxfilename(rxfilename)
<< ". A NULL pointer is returned.";
return NULL;
}
}
return fst;
}
VectorFst<StdArc> *CastOrConvertToVectorFst(Fst<StdArc> *fst) {
// This version currently supports ConstFst<StdArc> or VectorFst<StdArc>
std::string real_type = fst->Type();
KALDI_ASSERT(real_type == "vector" || real_type == "const");
if (real_type == "vector") {
return dynamic_cast<VectorFst<StdArc> *>(fst);
} else {
// As the 'fst' can't cast to VectorFst, we create a new
// VectorFst<StdArc> initialized by 'fst', and delete 'fst'.
VectorFst<StdArc> *new_fst = new VectorFst<StdArc>(*fst);
delete fst;
return new_fst;
}
}
void ReadFstKaldi(std::string rxfilename, fst::StdVectorFst *ofst) {
fst::StdVectorFst *fst = ReadFstKaldi(rxfilename);
*ofst = *fst;
delete fst;
}
void WriteFstKaldi(const VectorFst<StdArc> &fst,
std::string wxfilename) {
if (wxfilename == "") wxfilename = "-"; // interpret "" as stdout,
// for compatibility with OpenFst conventions.
bool write_binary = true, write_header = false;
kaldi::Output ko(wxfilename, write_binary, write_header);
FstWriteOptions wopts(kaldi::PrintableWxfilename(wxfilename));
fst.Write(ko.Stream(), wopts);
}
fst::VectorFst<fst::StdArc> *ReadAndPrepareLmFst(std::string rxfilename) {
// ReadFstKaldi() will die with exception on failure.
fst::VectorFst<fst::StdArc> *ans = fst::ReadFstKaldi(rxfilename);
if (ans->Properties(fst::kAcceptor, true) == 0) {
// If it's not already an acceptor, project on the output, i.e. copy olabels
// to ilabels. Generally the G.fst's on disk will have the disambiguation
// symbol #0 on the input symbols of the backoff arc, and projection will
// replace them with epsilons which is what is on the output symbols of
// those arcs.
fst::Project(ans, fst::PROJECT_OUTPUT);
}
if (ans->Properties(fst::kILabelSorted, true) == 0) {
// Make sure LM is sorted on ilabel.
fst::ILabelCompare<fst::StdArc> ilabel_comp;
fst::ArcSort(ans, ilabel_comp);
}
return ans;
}
} // end namespace fst
@@ -0,0 +1,158 @@
// fstext/kaldi-fst-io.h
// Copyright 2009-2011 Microsoft Corporation
// 2012-2015 Johns Hopkins University (Author: Daniel Povey)
// 2013 Guoguo Chen
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_KALDI_FST_IO_H_
#define KALDI_FSTEXT_KALDI_FST_IO_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include <fst/script/print-impl.h>
#include "base/kaldi-common.h"
// Some functions for writing Fsts.
// I/O for FSTs is a bit of a mess, and not very well integrated with Kaldi's
// generic I/O mechanisms, because we want files containing just FSTs to
// be readable by OpenFST's native binaries, which is not compatible
// with the normal \0B header that identifies Kaldi files as containing
// binary data.
// So use the functions here with your eyes open, and with caution!
namespace fst {
// Read a binary FST using Kaldi I/O mechanisms (pipes, etc.)
// On error returns NULL. Only supports VectorFst and exists
// mainly for backward code compabibility.
VectorFst<StdArc> *ReadFstKaldi(std::string rxfilename);
// Read a binary FST using Kaldi I/O mechanisms (pipes, etc.)
// If it can't read the FST, if throw_on_err == true it throws using KALDI_ERR;
// otherwise it prints a warning and returns. Note:this
// doesn't support the text-mode option that we generally like to support.
// This version currently supports ConstFst<StdArc> or VectorFst<StdArc>
// (const-fst can give better performance for decoding). Other
// types could be also loaded if registered inside OpenFst.
Fst<StdArc> *ReadFstKaldiGeneric(std::string rxfilename,
bool throw_on_err = true);
// This function attempts to dynamic_cast the pointer 'fst' (which will likely
// have been returned by ReadFstGeneric()), to the more derived
// type VectorFst<StdArc>. If this succeeds, it returns the same pointer;
// if it fails, it converts the FST type (by creating a new VectorFst<stdArc>
// initialized by 'fst'), prints a warning, and deletes 'fst'.
VectorFst<StdArc> *CastOrConvertToVectorFst(Fst<StdArc> *fst);
// Version of ReadFstKaldi() that writes to a pointer. Assumes
// the FST is binary with no binary marker. Crashes on error.
void ReadFstKaldi(std::string rxfilename, VectorFst<StdArc> *ofst);
// Write an FST using Kaldi I/O mechanisms (pipes, etc.)
// On error, throws using KALDI_ERR. For use only in code in fstbin/,
// as it doesn't support the text-mode option.
void WriteFstKaldi(const VectorFst<StdArc> &fst,
std::string wxfilename);
// This is a more general Kaldi-type-IO mechanism of writing FSTs to
// streams, supporting binary or text-mode writing. (note: we just
// write the integers, symbol tables are not supported).
// On error, throws using KALDI_ERR.
template <class Arc>
void WriteFstKaldi(std::ostream &os, bool binary,
const VectorFst<Arc> &fst);
// A generic Kaldi-type-IO mechanism of reading FSTs from streams,
// supporting binary or text-mode reading/writing.
template <class Arc>
void ReadFstKaldi(std::istream &is, bool binary,
VectorFst<Arc> *fst);
// Read an FST file for LM (G.fst) and make it an acceptor,
// and make sure it is sorted on labels
fst::VectorFst<fst::StdArc> *ReadAndPrepareLmFst(std::string rxfilename);
// This is a Holder class with T = VectorFst<Arc>, that meets the requirements
// of a Holder class as described in ../util/kaldi-holder.h. This enables us to
// read/write collections of FSTs indexed by strings, using the Table concept (
// see ../util/kaldi-table.h).
// Originally it was only templated on T = VectorFst<StdArc>, but as the keyword
// spotting stuff introduced more types of FSTs, we made it also templated on
// the arc.
template<class Arc>
class VectorFstTplHolder {
public:
typedef VectorFst<Arc> T;
VectorFstTplHolder(): t_(NULL) { }
static bool Write(std::ostream &os, bool binary, const T &t);
void Copy(const T &t) { // copies it into the holder.
Clear();
t_ = new T(t);
}
// Reads into the holder.
bool Read(std::istream &is);
// It's potentially a binary format, so must read in binary mode (linefeed
// translation will corrupt the file. We don't know till we open the file if
// it's really binary, so we need to read in binary mode to be on the safe
// side. Extra linefeeds won't matter, the text-mode reading code ignores
// them.
static bool IsReadInBinary() { return true; }
T &Value() {
// code error if !t_.
if (!t_) KALDI_ERR << "VectorFstTplHolder::Value() called wrongly.";
return *t_;
}
void Clear() {
if (t_) {
delete t_;
t_ = NULL;
}
}
void Swap(VectorFstTplHolder<Arc> *other) {
std::swap(t_, other->t_);
}
bool ExtractRange(const VectorFstTplHolder<Arc> &other,
const std::string &range) {
KALDI_ERR << "ExtractRange is not defined for this type of holder.";
return false;
}
~VectorFstTplHolder() { Clear(); }
// No destructor. Assignment and
// copy constructor take their default implementations.
private:
KALDI_DISALLOW_COPY_AND_ASSIGN(VectorFstTplHolder);
T *t_;
};
// Now make the original VectorFstHolder as the typedef of VectorFstHolder<StdArc>.
typedef VectorFstTplHolder<StdArc> VectorFstHolder;
} // end namespace fst
#include "fstext/kaldi-fst-io-inl.h"
#endif
@@ -0,0 +1,282 @@
// fstext/lattice-utils-inl.h
// Copyright 2009-2012 Microsoft Corporation Johns Hopkins University (Author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_LATTICE_UTILS_INL_H_
#define KALDI_FSTEXT_LATTICE_UTILS_INL_H_
// Do not include this file directly. It is included by lattice-utils.h
namespace fst {
/* Convert from FST with arc-type Weight, to one with arc-type
CompactLatticeWeight. Uses FactorFst to identify chains
of states which can be turned into a single output arc. */
template<class Weight, class Int>
void ConvertLattice(
const ExpandedFst<ArcTpl<Weight> > &ifst,
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *ofst,
bool invert) {
typedef ArcTpl<Weight> Arc;
typedef typename Arc::StateId StateId;
typedef CompactLatticeWeightTpl<Weight, Int> CompactWeight;
typedef ArcTpl<CompactWeight> CompactArc;
VectorFst<ArcTpl<Weight> > ffst;
std::vector<std::vector<Int> > labels;
if (invert) // normal case: want the ilabels as sequences on the arcs of
Factor(ifst, &ffst, &labels); // the output... Factor makes seqs of
// ilabels.
else {
VectorFst<ArcTpl<Weight> > invfst(ifst);
Invert(&invfst);
Factor(invfst, &ffst, &labels);
}
TopSort(&ffst); // Put the states in ffst in topological order, which is
// easier on the eye when reading the text-form lattices and corresponds to
// what we get when we generate the lattices in the decoder.
ofst->DeleteStates();
// The states will be numbered exactly the same as the original FST.
// Add the states to the new FST.
StateId num_states = ffst.NumStates();
for (StateId s = 0; s < num_states; s++) {
StateId news = ofst->AddState();
assert(news == s);
}
ofst->SetStart(ffst.Start());
for (StateId s = 0; s < num_states; s++) {
Weight final_weight = ffst.Final(s);
if (final_weight != Weight::Zero()) {
CompactWeight final_compact_weight(final_weight, std::vector<Int>());
ofst->SetFinal(s, final_compact_weight);
}
for (ArcIterator<ExpandedFst<Arc> > iter(ffst, s);
!iter.Done();
iter.Next()) {
const Arc &arc = iter.Value();
KALDI_PARANOID_ASSERT(arc.weight != Weight::Zero());
// note: zero-weight arcs not allowed anyway so weight should not be zero,
// but no harm in checking.
CompactArc compact_arc(arc.olabel, arc.olabel,
CompactWeight(arc.weight, labels[arc.ilabel]),
arc.nextstate);
ofst->AddArc(s, compact_arc);
}
}
}
template<class Weight, class Int>
void ConvertLattice(
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &ifst,
MutableFst<ArcTpl<Weight> > *ofst,
bool invert) {
typedef ArcTpl<Weight> Arc;
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef CompactLatticeWeightTpl<Weight, Int> CompactWeight;
typedef ArcTpl<CompactWeight> CompactArc;
ofst->DeleteStates();
// make the states in the new FST have the same numbers as
// the original ones, and add chains of states as necessary
// to encode the string-valued weights.
StateId num_states = ifst.NumStates();
for (StateId s = 0; s < num_states; s++) {
StateId news = ofst->AddState();
assert(news == s);
}
ofst->SetStart(ifst.Start());
for (StateId s = 0; s < num_states; s++) {
CompactWeight final_weight = ifst.Final(s);
if (final_weight != CompactWeight::Zero()) {
StateId cur_state = s;
size_t string_length = final_weight.String().size();
for (size_t n = 0; n < string_length; n++) {
StateId next_state = ofst->AddState();
Label ilabel = 0;
Arc arc(ilabel, final_weight.String()[n],
(n == 0 ? final_weight.Weight() : Weight::One()),
next_state);
if (invert) std::swap(arc.ilabel, arc.olabel);
ofst->AddArc(cur_state, arc);
cur_state = next_state;
}
ofst->SetFinal(cur_state,
string_length > 0 ? Weight::One() : final_weight.Weight());
}
for (ArcIterator<ExpandedFst<CompactArc> > iter(ifst, s);
!iter.Done();
iter.Next()) {
const CompactArc &arc = iter.Value();
size_t string_length = arc.weight.String().size();
StateId cur_state = s;
// for all but the last element in the string--
// add a temporary state.
for (size_t n = 0 ; n+1 < string_length; n++) {
StateId next_state = ofst->AddState();
Label ilabel = (n == 0 ? arc.ilabel : 0),
olabel = static_cast<Label>(arc.weight.String()[n]);
Weight weight = (n == 0 ? arc.weight.Weight() : Weight::One());
Arc new_arc(ilabel, olabel, weight, next_state);
if (invert) std::swap(new_arc.ilabel, new_arc.olabel);
ofst->AddArc(cur_state, new_arc);
cur_state = next_state;
}
Label ilabel = (string_length <= 1 ? arc.ilabel : 0),
olabel = (string_length > 0 ? arc.weight.String()[string_length-1] : 0);
Weight weight = (string_length <= 1 ? arc.weight.Weight() : Weight::One());
Arc new_arc(ilabel, olabel, weight, arc.nextstate);
if (invert) std::swap(new_arc.ilabel, new_arc.olabel);
ofst->AddArc(cur_state, new_arc);
}
}
}
// This function converts lattices between float and double;
// it works for both CompactLatticeWeight and LatticeWeight.
template<class WeightIn, class WeightOut>
void ConvertLattice(
const ExpandedFst<ArcTpl<WeightIn> > &ifst,
MutableFst<ArcTpl<WeightOut> > *ofst) {
typedef ArcTpl<WeightIn> ArcIn;
typedef ArcTpl<WeightOut> ArcOut;
typedef typename ArcIn::StateId StateId;
ofst->DeleteStates();
// The states will be numbered exactly the same as the original FST.
// Add the states to the new FST.
StateId num_states = ifst.NumStates();
for (StateId s = 0; s < num_states; s++) {
StateId news = ofst->AddState();
assert(news == s);
}
ofst->SetStart(ifst.Start());
for (StateId s = 0; s < num_states; s++) {
WeightIn final_iweight = ifst.Final(s);
if (final_iweight != WeightIn::Zero()) {
WeightOut final_oweight;
ConvertLatticeWeight(final_iweight, &final_oweight);
ofst->SetFinal(s, final_oweight);
}
for (ArcIterator<ExpandedFst<ArcIn> > iter(ifst, s);
!iter.Done();
iter.Next()) {
ArcIn arc = iter.Value();
KALDI_PARANOID_ASSERT(arc.weight != WeightIn::Zero());
ArcOut oarc;
ConvertLatticeWeight(arc.weight, &oarc.weight);
oarc.ilabel = arc.ilabel;
oarc.olabel = arc.olabel;
oarc.nextstate = arc.nextstate;
ofst->AddArc(s, oarc);
}
}
}
template<class Weight, class ScaleFloat>
void ScaleLattice(
const std::vector<std::vector<ScaleFloat> > &scale,
MutableFst<ArcTpl<Weight> > *fst) {
assert(scale.size() == 2 && scale[0].size() == 2 && scale[1].size() == 2);
if (scale == DefaultLatticeScale()) // nothing to do.
return;
typedef ArcTpl<Weight> Arc;
typedef MutableFst<Arc> Fst;
typedef typename Arc::StateId StateId;
StateId num_states = fst->NumStates();
for (StateId s = 0; s < num_states; s++) {
for (MutableArcIterator<Fst> aiter(fst, s);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
arc.weight = Weight(ScaleTupleWeight(arc.weight, scale));
aiter.SetValue(arc);
}
Weight final_weight = fst->Final(s);
if (final_weight != Weight::Zero())
fst->SetFinal(s, Weight(ScaleTupleWeight(final_weight, scale)));
}
}
template<class Weight, class Int>
void RemoveAlignmentsFromCompactLattice(
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst) {
typedef CompactLatticeWeightTpl<Weight, Int> W;
typedef ArcTpl<W> Arc;
typedef MutableFst<Arc> Fst;
typedef typename Arc::StateId StateId;
StateId num_states = fst->NumStates();
for (StateId s = 0; s < num_states; s++) {
for (MutableArcIterator<Fst> aiter(fst, s);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
arc.weight = W(arc.weight.Weight(), std::vector<Int>());
aiter.SetValue(arc);
}
W final_weight = fst->Final(s);
if (final_weight != W::Zero())
fst->SetFinal(s, W(final_weight.Weight(), std::vector<Int>()));
}
}
template<class Weight, class Int>
bool CompactLatticeHasAlignment(
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &fst) {
typedef CompactLatticeWeightTpl<Weight, Int> W;
typedef ArcTpl<W> Arc;
typedef ExpandedFst<Arc> Fst;
typedef typename Arc::StateId StateId;
StateId num_states = fst.NumStates();
for (StateId s = 0; s < num_states; s++) {
for (ArcIterator<Fst> aiter(fst, s);
!aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
if (!arc.weight.String().empty()) return true;
}
W final_weight = fst.Final(s);
if (!final_weight.String().empty()) return true;
}
return false;
}
template <class Real>
void ConvertFstToLattice(
const ExpandedFst<ArcTpl<TropicalWeight> > &ifst,
MutableFst<ArcTpl<LatticeWeightTpl<Real> > > *ofst) {
int32 num_states_cache = 50000;
fst::CacheOptions cache_opts(true, num_states_cache);
fst::MapFstOptions mapfst_opts(cache_opts);
StdToLatticeMapper<Real> mapper;
MapFst<StdArc, ArcTpl<LatticeWeightTpl<Real> >,
StdToLatticeMapper<Real> > map_fst(ifst, mapper, mapfst_opts);
*ofst = map_fst;
}
}
#endif
@@ -0,0 +1,331 @@
// fstext/lattice-utils-test.cc
// Copyright 2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/lattice-utils.h"
#include "fstext/fst-test-utils.h"
#include "base/kaldi-math.h"
namespace fst {
template<class Weight, class Int> void TestConvert(bool invert) {
typedef ArcTpl<Weight> Arc;
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
for(int i = 0; i < 5; i++) {
VectorFst<Arc> *fst = RandFst<Arc>();
std::cout << "FST before converting to compact-arc is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<CompactArc> ofst;
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
std::cout << "FST after converting is:\n";
{
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> origfst;
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
std::cout << "FST after back conversion is:\n";
{
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
}
// This tests the ShortestPath algorithm, and by proxy, tests the
// NaturalLess template etc.
template<class Weight, class Int> void TestShortestPath() {
for (int p = 0; p < 10; p++) {
typedef ArcTpl<Weight> Arc;
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
for(int i = 0; i < 5; i++) {
VectorFst<Arc> *fst = RandPairFst<Arc>();
std::cout << "Testing shortest path\n";
std::cout << "FST before converting to compact-arc is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<CompactArc> cfst;
ConvertLattice<Weight, Int>(*fst, &cfst, false); // invert == false
{
VectorFst<Arc> nbest_fst_1;
ShortestPath(*fst, &nbest_fst_1, 1);
VectorFst<Arc> nbest_fst_2;
ShortestPath(*fst, &nbest_fst_2, 3);
VectorFst<Arc> nbest_fst_1b;
ShortestPath(nbest_fst_2, &nbest_fst_1b, 1);
assert(ApproxEqual(ShortestDistance(nbest_fst_1),
ShortestDistance(nbest_fst_1b)));
// since semiring is idempotent, this should succeed too.
assert(ApproxEqual(ShortestDistance(*fst),
ShortestDistance(nbest_fst_1b)));
}
{
VectorFst<CompactArc> nbest_fst_1;
ShortestPath(cfst, &nbest_fst_1, 1);
VectorFst<CompactArc> nbest_fst_2;
ShortestPath(cfst, &nbest_fst_2, 3);
VectorFst<CompactArc> nbest_fst_1b;
ShortestPath(nbest_fst_2, &nbest_fst_1b, 1);
assert(ApproxEqual(ShortestDistance(nbest_fst_1),
ShortestDistance(nbest_fst_1b)));
// since semiring is idempotent, this should succeed too.
assert(ApproxEqual(ShortestDistance(cfst),
ShortestDistance(nbest_fst_1b)));
}
delete fst;
}
}
}
template<class Int> void TestConvert2() {
typedef ArcTpl<LatticeWeightTpl<float> > ArcF;
typedef ArcTpl<LatticeWeightTpl<double> > ArcD;
typedef ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > CArcF;
typedef ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > CArcD;
for(int i = 0; i < 2; i++) {
{
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
VectorFst<ArcD> fst2;
VectorFst<ArcF> fst3;
ConvertLattice(*fst1, &fst2);
ConvertLattice(fst2, &fst3);
assert(RandEquivalent(*fst1, fst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
VectorFst<CArcF> cfst1, cfst3;
ConvertLattice(*fst1, &cfst1);
VectorFst<CArcD> cfst2;
ConvertLattice(cfst1, &cfst2);
ConvertLattice(cfst2, &cfst3);
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
VectorFst<CArcD> cfst1, cfst3;
ConvertLattice(*fst1, &cfst1);
VectorFst<CArcF> cfst2;
ConvertLattice(cfst1, &cfst2);
ConvertLattice(cfst2, &cfst3);
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
VectorFst<CArcD> cfst1, cfst3;
ConvertLattice(*fst1, &cfst1);
VectorFst<CArcF> cfst2;
ConvertLattice(cfst1, &cfst2);
ConvertLattice(cfst2, &cfst3);
assert(RandEquivalent(cfst1, cfst3, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
VectorFst<CArcF> cfst1;
ConvertLattice(*fst1, &cfst1);
VectorFst<ArcD> fst2;
ConvertLattice(cfst1, &fst2);
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcF> *fst1 = RandPairFst<ArcF>();
VectorFst<CArcD> cfst1;
ConvertLattice(*fst1, &cfst1);
VectorFst<ArcF> fst2;
ConvertLattice(cfst1, &fst2);
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
{
VectorFst<ArcD> *fst1 = RandPairFst<ArcD>();
VectorFst<CArcF> cfst1;
ConvertLattice(*fst1, &cfst1);
VectorFst<ArcD> fst2;
ConvertLattice(cfst1, &fst2);
assert(RandEquivalent(*fst1, fst2, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst1;
}
}
}
// use TestConvertPair when the Weight can be constructed from
// a pair of floats.
template<class Weight, class Int> void TestConvertPair(bool invert) {
typedef ArcTpl<Weight> Arc;
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
for(int i = 0; i < 2; i++) {
VectorFst<Arc> *fst = RandPairFst<Arc>();
/*std::cout << "FST before converting to compact-arc is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
VectorFst<CompactArc> ofst;
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
/*std::cout << "FST after converting is:\n";
{
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
VectorFst<Arc> origfst;
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
/*std::cout << "FST after back conversion is:\n";
{
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
assert(RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
}
// use TestConvertPair when the Weight can be constructed from
// a pair of floats.
template<class Weight, class Int> void TestScalePair(bool invert) {
std::vector<std::vector<double> > scale1 = DefaultLatticeScale(),
scale2 = DefaultLatticeScale();
// important that all these numbers exactly representable as floats..
// exact floating-point comparisons are used in LatticeWeight, and
// this exactness is being tested here.. this test will fail for
// other types of number.
if (kaldi::Rand() % 4 == 0) {
scale1[0][0] = 2.0;
scale2[0][0] = 0.5;
scale1[1][1] = 4.0;
scale2[1][1] = 0.25;
} else if (kaldi::Rand() % 3 == 0) {
// use that [1 0.25; 0 1] [ 1 -0.25; 0 1] is the unit matrix.
scale1[0][1] = 0.25;
scale2[0][1] = -0.25;
} else if (kaldi::Rand() % 2 == 0) {
scale1[1][0] = 0.25;
scale2[1][0] = -0.25;
}
typedef ArcTpl<Weight> Arc;
typedef ArcTpl<CompactLatticeWeightTpl<Weight, Int> > CompactArc;
for(int i = 0; i < 2; i++) {
VectorFst<Arc> *fst = RandPairFst<Arc>();
/*std::cout << "FST before converting to compact-arc is:\n";
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
VectorFst<CompactArc> ofst;
ConvertLattice<Weight, Int>(*fst, &ofst, invert);
ScaleLattice(scale1, &ofst);
/*std::cout << "FST after converting and scaling is:\n";
{
FstPrinter<CompactArc> fstprinter(ofst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
VectorFst<Arc> origfst;
ConvertLattice<Weight, Int>(ofst, &origfst, invert);
ScaleLattice(scale2, &origfst);
/*std::cout << "FST after back conversion and scaling is:\n";
{
FstPrinter<Arc> fstprinter(origfst, NULL, NULL, NULL, false, true);
fstprinter.Print(&std::cout, "standard output");
}*/
// If RandEquivalent doesn't work, it could be due to a nasty issue related to the use
// of exact floating-point comparisons in the Plus function of LatticeWeight.
if (!RandEquivalent(*fst, origfst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/)) {
std::cerr << "Warn, randequivalent returned false. Checking equivalence another way.\n";
assert(Equal(*fst, origfst));
}
delete fst;
}
}
} // end namespace fst
int main() {
using namespace fst;
typedef ::int64 int64;
typedef ::uint64 uint64;
typedef ::int32 int32;
typedef ::uint32 uint32;
{
typedef LatticeWeightTpl<float> LatticeWeight;
for(int i = 0; i < 2; i++) {
bool invert = (i % 2);
TestConvert<TropicalWeight, int32>(invert);
TestConvertPair<LatticeWeight, int32>(invert);
TestConvertPair<LatticeWeight, size_t>(invert);
TestConvertPair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
TestScalePair<LatticeWeight, int32>(invert);
TestScalePair<LatticeWeight, size_t>(invert);
TestScalePair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
}
}
{
typedef LatticeWeightTpl<double> LatticeWeight;
TestShortestPath<LatticeWeight, int32>();
TestConvert2<int32>();
for(int i = 0; i < 2; i++) {
bool invert = (i % 2);
TestConvertPair<LatticeWeight, int32>(invert);
TestConvertPair<LatticeWeight, size_t>(invert);
TestConvertPair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
TestScalePair<LatticeWeight, int32>(invert);
TestScalePair<LatticeWeight, size_t>(invert);
TestScalePair<LexicographicWeight<TropicalWeight, TropicalWeight>, size_t>(invert);
}
}
std::cout << "Tests succeeded\n";
}
@@ -0,0 +1,256 @@
// fstext/lattice-utils.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_LATTICE_UTILS_H_
#define KALDI_FSTEXT_LATTICE_UTILS_H_
#include "fst/fstlib.h"
#include "fstext/lattice-weight.h"
#include "fstext/factor.h"
namespace fst {
// The template ConvertLattice does conversions to and from
// LatticeWeight FSTs and CompactLatticeWeight FSTs, and
// between float and double, and to convert from LatticeWeight
// to TropicalWeight. It's used in the I/O code for lattices,
// and for converting lattices to standard FSTs (e.g. for creating
// decoding graphs from lattices).
/**
Convert lattice from a normal FST to a CompactLattice FST.
This is a bit like converting to the Gallic semiring, except
the semiring behaves in a different way (designed to take
the best path).
Note: the ilabels end up as the symbols on the arcs of the
output acceptor, and the olabels go to the strings. To make
it the other way around (useful for the speech-recognition
application), set invert=true [the default].
*/
template<class Weight, class Int>
void ConvertLattice(
const ExpandedFst<ArcTpl<Weight> > &ifst,
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *ofst,
bool invert = true);
/**
Convert lattice CompactLattice format to Lattice. This is a bit
like converting from the Gallic semiring. As for any CompactLattice, "ifst"
must be an acceptor (i.e., ilabels and olabels should be identical). If
invert=false, the labels on "ifst" become the ilabels on "ofst" and the
strings in the weights of "ifst" becomes the olabels. If invert=true
[default], this is reversed (useful for speech recognition lattices; our
standard non-compact format has the words on the output side to match HCLG).
*/
template<class Weight, class Int>
void ConvertLattice(
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &ifst,
MutableFst<ArcTpl<Weight> > *ofst,
bool invert = true);
/**
Convert between CompactLattices and Lattices of different floating point types...
this works between any pair of weight types for which ConvertLatticeWeight
is defined (c.f. lattice-weight.h), and also includes conversion from
LatticeWeight to TropicalWeight.
*/
template<class WeightIn, class WeightOut>
void ConvertLattice(
const ExpandedFst<ArcTpl<WeightIn> > &ifst,
MutableFst<ArcTpl<WeightOut> > *ofst);
// Now define some ConvertLattice functions that require two phases of conversion (don't
// bother coding these separately as they will be used rarely.
// Lattice with float to CompactLattice with double.
template<class Int>
void ConvertLattice(const ExpandedFst<ArcTpl<LatticeWeightTpl<float> > > &ifst,
MutableFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > *ofst) {
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > fst;
ConvertLattice(ifst, &fst);
ConvertLattice(fst, ofst);
}
// Lattice with double to CompactLattice with float.
template<class Int>
void ConvertLattice(const ExpandedFst<ArcTpl<LatticeWeightTpl<double> > > &ifst,
MutableFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > *ofst) {
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > fst;
ConvertLattice(ifst, &fst);
ConvertLattice(fst, ofst);
}
/// Converts CompactLattice with double to Lattice with float.
template<class Int>
void ConvertLattice(const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > &ifst,
MutableFst<ArcTpl<LatticeWeightTpl<float> > > *ofst) {
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > fst;
ConvertLattice(ifst, &fst);
ConvertLattice(fst, ofst);
}
/// Converts CompactLattice with float to Lattice with double.
template<class Int>
void ConvertLattice(const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<float>, Int> > > &ifst,
MutableFst<ArcTpl<LatticeWeightTpl<double> > > *ofst) {
VectorFst<ArcTpl<CompactLatticeWeightTpl<LatticeWeightTpl<double>, Int> > > fst;
ConvertLattice(ifst, &fst);
ConvertLattice(fst, ofst);
}
/// Converts TropicalWeight to LatticeWeight (puts all the weight on
/// the first float in the lattice's pair).
template <class Real>
void ConvertFstToLattice(
const ExpandedFst<ArcTpl<TropicalWeight> > &ifst,
MutableFst<ArcTpl<LatticeWeightTpl<Real> > > *ofst);
/** Returns a default 2x2 matrix scaling factor for LatticeWeight */
inline std::vector<std::vector<double> > DefaultLatticeScale() {
std::vector<std::vector<double> > ans(2);
ans[0].resize(2, 0.0);
ans[1].resize(2, 0.0);
ans[0][0] = ans[1][1] = 1.0;
return ans;
}
inline std::vector<std::vector<double> > AcousticLatticeScale(double acwt) {
std::vector<std::vector<double> > ans(2);
ans[0].resize(2, 0.0);
ans[1].resize(2, 0.0);
ans[0][0] = 1.0;
ans[1][1] = acwt;
return ans;
}
inline std::vector<std::vector<double> > GraphLatticeScale(double lmwt) {
std::vector<std::vector<double> > ans(2);
ans[0].resize(2, 0.0);
ans[1].resize(2, 0.0);
ans[0][0] = lmwt;
ans[1][1] = 1.0;
return ans;
}
inline std::vector<std::vector<double> > LatticeScale(double lmwt, double acwt) {
std::vector<std::vector<double> > ans(2);
ans[0].resize(2, 0.0);
ans[1].resize(2, 0.0);
ans[0][0] = lmwt;
ans[1][1] = acwt;
return ans;
}
/** Scales the pairs of weights in LatticeWeight or CompactLatticeWeight 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
[ 1 0;
0 acwt ]
if we want to scale the acoustics by "acwt".
*/
template<class Weight, class ScaleFloat>
void ScaleLattice(
const std::vector<std::vector<ScaleFloat> > &scale,
MutableFst<ArcTpl<Weight> > *fst);
/// Removes state-level alignments (the strings that are
/// part of the weights).
template<class Weight, class Int>
void RemoveAlignmentsFromCompactLattice(
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst);
/// Returns true if lattice has alignments, i.e. it has
/// any nonempty strings inside its weights.
template<class Weight, class Int>
bool CompactLatticeHasAlignment(
const ExpandedFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > &fst);
/// Class StdToLatticeMapper maps a normal arc (StdArc)
/// to a LatticeArc by putting the StdArc weight as the first
/// element of the LatticeWeight. Useful when doing LM
/// rescoring.
template<class Real>
class StdToLatticeMapper {
typedef LatticeWeightTpl<Real> LatticeWeight;
typedef ArcTpl<LatticeWeight> LatticeArc;
public:
LatticeArc operator()(const StdArc &arc) {
// Note: we have to check whether the arc's weight is zero below,
// and if so return (infinity, infinity) and not (infinity, zero),
// because (infinity, zero) is not a valid LatticeWeight, which should
// either be both finite, or both infinite (i.e. Zero()).
return LatticeArc(arc.ilabel, arc.olabel,
LatticeWeight(arc.weight.Value(),
arc.weight == StdArc::Weight::Zero() ?
arc.weight.Value() : 0.0),
arc.nextstate);
}
MapFinalAction FinalAction() { return MAP_NO_SUPERFINAL; }
MapSymbolsAction InputSymbolsAction() { return MAP_COPY_SYMBOLS; }
MapSymbolsAction OutputSymbolsAction() { return MAP_COPY_SYMBOLS; }
// I believe all properties are preserved.
uint64 Properties(uint64 props) { return props; }
};
/// Class LatticeToStdMapper maps a LatticeArc to a normal arc (StdArc)
/// by adding the elements of the LatticeArc weight.
template<class Real>
class LatticeToStdMapper {
typedef LatticeWeightTpl<Real> LatticeWeight;
typedef ArcTpl<LatticeWeight> LatticeArc;
public:
StdArc operator()(const LatticeArc &arc) {
return StdArc(arc.ilabel, arc.olabel,
StdArc::Weight(arc.weight.Value1() + arc.weight.Value2()),
arc.nextstate);
}
MapFinalAction FinalAction() { return MAP_NO_SUPERFINAL; }
MapSymbolsAction InputSymbolsAction() { return MAP_COPY_SYMBOLS; }
MapSymbolsAction OutputSymbolsAction() { return MAP_COPY_SYMBOLS; }
// I believe all properties are preserved.
uint64 Properties(uint64 props) { return props; }
};
template<class Weight, class Int>
void PruneCompactLattice(
Weight beam,
MutableFst<ArcTpl<CompactLatticeWeightTpl<Weight, Int> > > *fst);
} // end namespace fst
#include "fstext/lattice-utils-inl.h"
#endif // KALDI_FSTEXT_LATTICE_UTILS_H_
@@ -0,0 +1,197 @@
// fstext/lattice-weight-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-math.h"
#include "fstext/lattice-weight.h"
namespace fst {
using std::vector;
using std::cout;
// these typedefs are the same as in ../lat/kaldi-lattice.h, but
// just used here for testing (doesn't matter if they get out of
// sync).
typedef float BaseFloat;
typedef LatticeWeightTpl<BaseFloat> LatticeWeight;
typedef CompactLatticeWeightTpl<LatticeWeight, int32> CompactLatticeWeight;
typedef CompactLatticeWeightCommonDivisorTpl<LatticeWeight, int32>
CompactLatticeWeightCommonDivisor;
LatticeWeight RandomLatticeWeight() {
int tmp = kaldi::Rand() % 4;
if (tmp == 0) {
return LatticeWeight::Zero();
} else if (tmp == 1) {
return LatticeWeight( 1, 2); // sometimes return special values..
} else if (tmp == 2) {
return LatticeWeight( 2, 1); // this tests more thoroughly certain properties...
} else {
return LatticeWeight( 100 * kaldi::RandGauss(), 100 * kaldi::RandGauss());
}
}
CompactLatticeWeight RandomCompactLatticeWeight() {
LatticeWeight w = RandomLatticeWeight();
if (w == LatticeWeight::Zero()) {
return CompactLatticeWeight(w, vector<int32>());
} else {
int32 len = kaldi::Rand() % 4;
vector<int32> str;
for(int32 i = 0; i < len; i++)
str.push_back(kaldi::Rand() % 10 + 1);
return CompactLatticeWeight(w, str);
}
}
void LatticeWeightTest() {
for(int32 i = 0; i < 100; i++) {
LatticeWeight l1 = RandomLatticeWeight(), l2 = RandomLatticeWeight();
LatticeWeight l3 = Plus(l1, l2);
LatticeWeight l4 = Times(l1, l2);
BaseFloat f1 = l1.Value1() + l1.Value2(), f2 = l2.Value1() + l2.Value2(), f3 = l3.Value1() + l3.Value2(),
f4 = l4.Value1() + l4.Value2();
kaldi::AssertEqual(std::min(f1, f2), f3);
kaldi::AssertEqual(f1 + f2, f4);
KALDI_ASSERT(Plus(l3, l3) == l3);
KALDI_ASSERT(Plus(l1, l2) == Plus(l2, l1)); // commutativity of plus
KALDI_ASSERT(Times(l1, l2) == Times(l2, l1)); // commutativity of Times (true for this semiring, not always)
KALDI_ASSERT(Plus(l3, LatticeWeight::Zero()) == l3); // x + 0 = x
KALDI_ASSERT(Times(l3, LatticeWeight::One()) == l3); // x * 1 = x
KALDI_ASSERT(Times(l3, LatticeWeight::Zero()) == LatticeWeight::Zero()); // x * 0 = 0
KALDI_ASSERT(l3.Reverse().Reverse() == l3);
NaturalLess<LatticeWeight> nl;
bool a = nl(l1, l2);
bool b = (Plus(l1, l2) == l1 && l1 != l2);
KALDI_ASSERT(a == b);
KALDI_ASSERT(Compare(l1, Plus(l1, l2)) != 1); // so do not have l1 > l1 + l2
LatticeWeight l5 = RandomLatticeWeight(), l6 = RandomLatticeWeight();
{
LatticeWeight wa = Times(Plus(l1, l2), Plus(l5, l6)),
wb = Plus(Times(l1, l5), Plus(Times(l1, l6),
Plus(Times(l2, l5), Times(l2, l6))));
if (!ApproxEqual(wa, wb)) {
std::cout << "l1 = " << l1 << ", l2 = " << l2
<< ", l5 = " << l5 << ", l6 = " << l6 << "\n";
std::cout << "ERROR: " << wa << " != " << wb << "\n";
}
// KALDI_ASSERT(Times(Plus(l1, l2), Plus(l5, l6))
// == Plus(Times(l1, l5), Plus(Times(l1,l6),
// Plus(Times(l2, l5), Times(l2, l6))))); // * distributes over +
}
KALDI_ASSERT(l1.Member() && l2.Member() && l3.Member() && l4.Member()
&& l5.Member() && l6.Member());
if (l2 != LatticeWeight::Zero())
KALDI_ASSERT(ApproxEqual(Divide(Times(l1, l2), l2), l1)); // (a*b) / b = a if b != 0
KALDI_ASSERT(ApproxEqual(l1, l1.Quantize()));
std::ostringstream s1;
s1 << l1;
std::istringstream s2(s1.str());
s2 >> l2;
KALDI_ASSERT(ApproxEqual(l1, l2, 0.001));
std::cout << s1.str() << '\n';
{
std::ostringstream s1b;
l1.Write(s1b);
std::istringstream s2b(s1b.str());
l3.Read(s2b);
KALDI_ASSERT(l1 == l3);
}
}
}
void CompactLatticeWeightTest() {
for(int32 i = 0; i < 100; i++) {
CompactLatticeWeight l1 = RandomCompactLatticeWeight(), l2 = RandomCompactLatticeWeight();
CompactLatticeWeight l3 = Plus(l1, l2);
CompactLatticeWeight l4 = Times(l1, l2);
KALDI_ASSERT(Plus(l3, l3) == l3);
KALDI_ASSERT(Plus(l1, l2) == Plus(l2, l1)); // commutativity of plus
KALDI_ASSERT(Plus(l3, CompactLatticeWeight::Zero()) == l3); // x + 0 = x
KALDI_ASSERT(Times(l3, CompactLatticeWeight::One()) == l3); // x * 1 = x
KALDI_ASSERT(Times(l3, CompactLatticeWeight::Zero()) == CompactLatticeWeight::Zero()); // x * 0 = 0
NaturalLess<CompactLatticeWeight> nl;
bool a = nl(l1, l2);
bool b = (Plus(l1, l2) == l1 && l1 != l2);
KALDI_ASSERT(a == b);
KALDI_ASSERT(Compare(l1, Plus(l1, l2)) != 1); // so do not have l1 > l1 + l2
CompactLatticeWeight l5 = RandomCompactLatticeWeight(), l6 = RandomCompactLatticeWeight();
KALDI_ASSERT(Times(Plus(l1, l2), Plus(l5, l6)) ==
Plus(Times(l1, l5), Plus(Times(l1, l6),
Plus(Times(l2, l5), Times(l2, l6))))); // * distributes over +
KALDI_ASSERT(l1.Member() && l2.Member() && l3.Member() && l4.Member()
&& l5.Member() && l6.Member());
if (l2 != CompactLatticeWeight::Zero()) {
KALDI_ASSERT(ApproxEqual(Divide(Times(l1, l2), l2, DIVIDE_RIGHT), l1)); // (a*b) / b = a if b != 0
KALDI_ASSERT(ApproxEqual(Divide(Times(l2, l1), l2, DIVIDE_LEFT), l1)); // (a*b) / b = a if b != 0
}
KALDI_ASSERT(ApproxEqual(l1, l1.Quantize()));
std::ostringstream s1;
s1 << l1;
std::istringstream s2(s1.str());
s2 >> l2;
KALDI_ASSERT(ApproxEqual(l1, l2));
std::cout << s1.str() << '\n';
{
std::ostringstream s1b;
l1.Write(s1b);
std::istringstream s2b(s1b.str());
l3.Read(s2b);
KALDI_ASSERT(l1 == l3);
}
CompactLatticeWeightCommonDivisor divisor;
std::cout << "l5 = " << l5 << '\n';
std::cout << "l6 = " << l6 << '\n';
l1 = divisor(l5, l6);
std::cout << "div = " << l1 << '\n';
if (l1 != CompactLatticeWeight::Zero()) {
l2 = Divide(l5, l1, DIVIDE_LEFT);
l3 = Divide(l6, l1, DIVIDE_LEFT);
std::cout << "l2 = " << l2 << '\n';
std::cout << "l3 = " << l3 << '\n';
l4 = divisor(l2, l3); // make sure l2 is now one.
std::cout << "l4 = " << l4 << '\n';
KALDI_ASSERT(ApproxEqual(l4, CompactLatticeWeight::One()));
} else {
KALDI_ASSERT(l5 == CompactLatticeWeight::Zero()
&& l6 == CompactLatticeWeight::Zero());
}
}
}
}
int main() {
fst::LatticeWeightTest();
fst::CompactLatticeWeightTest();
}
@@ -0,0 +1,862 @@
// fstext/lattice-weight.h
// Copyright 2009-2012 Microsoft Corporation
// Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_LATTICE_WEIGHT_H_
#define KALDI_FSTEXT_LATTICE_WEIGHT_H_
#include "fst/fstlib.h"
#include "base/kaldi-common.h"
namespace fst {
// Declare weight type for lattice... will import to namespace kaldi. has two
// members, value1_ and value2_, of type BaseFloat (normally equals float). It
// is basically the same as the tropical semiring on value1_+value2_, except it
// keeps track of a and b separately. More precisely, it is equivalent to the
// lexicographic semiring on (value1_+value2_), (value1_-value2_)
template<class FloatType>
class LatticeWeightTpl;
template <class FloatType>
inline std::ostream &operator <<(std::ostream &strm, const LatticeWeightTpl<FloatType> &w);
template <class FloatType>
inline std::istream &operator >>(std::istream &strm, LatticeWeightTpl<FloatType> &w);
template<class FloatType>
class LatticeWeightTpl {
public:
typedef FloatType T; // normally float.
typedef LatticeWeightTpl ReverseWeight;
inline T Value1() const { return value1_; }
inline T Value2() const { return value2_; }
inline void SetValue1(T f) { value1_ = f; }
inline void SetValue2(T f) { value2_ = f; }
LatticeWeightTpl(): value1_{}, value2_{} { }
LatticeWeightTpl(T a, T b): value1_(a), value2_(b) {}
LatticeWeightTpl(const LatticeWeightTpl &other): value1_(other.value1_), value2_(other.value2_) { }
LatticeWeightTpl &operator=(const LatticeWeightTpl &w) {
value1_ = w.value1_;
value2_ = w.value2_;
return *this;
}
LatticeWeightTpl<FloatType> Reverse() const {
return *this;
}
static const LatticeWeightTpl Zero() {
return LatticeWeightTpl(std::numeric_limits<T>::infinity(),
std::numeric_limits<T>::infinity());
}
static const LatticeWeightTpl One() {
return LatticeWeightTpl(0.0, 0.0);
}
static const std::string &Type() {
static const std::string type = (sizeof(T) == 4 ? "lattice4" : "lattice8") ;
return type;
}
static const LatticeWeightTpl NoWeight() {
return LatticeWeightTpl(std::numeric_limits<FloatType>::quiet_NaN(),
std::numeric_limits<FloatType>::quiet_NaN());
}
bool Member() const {
// value1_ == value1_ tests for NaN.
// also test for no -inf, and either both or neither
// must be +inf, and
if (value1_ != value1_ || value2_ != value2_) return false; // NaN
if (value1_ == -std::numeric_limits<T>::infinity() ||
value2_ == -std::numeric_limits<T>::infinity()) return false; // -infty not allowed
if (value1_ == std::numeric_limits<T>::infinity() ||
value2_ == std::numeric_limits<T>::infinity()) {
if (value1_ != std::numeric_limits<T>::infinity() ||
value2_ != std::numeric_limits<T>::infinity()) return false; // both must be +infty;
// this is necessary so that the semiring has only one zero.
}
return true;
}
LatticeWeightTpl Quantize(float delta = kDelta) const {
if (value1_ + value2_ == -std::numeric_limits<T>::infinity()) {
return LatticeWeightTpl(-std::numeric_limits<T>::infinity(), -std::numeric_limits<T>::infinity());
} else if (value1_ + value2_ == std::numeric_limits<T>::infinity()) {
return LatticeWeightTpl(std::numeric_limits<T>::infinity(), std::numeric_limits<T>::infinity());
} else if (value1_ + value2_ != value1_ + value2_) { // NaN
return LatticeWeightTpl(value1_ + value2_, value1_ + value2_);
} else {
return LatticeWeightTpl(floor(value1_/delta + 0.5F)*delta, floor(value2_/delta + 0.5F) * delta);
}
}
static constexpr uint64 Properties() {
return kLeftSemiring | kRightSemiring | kCommutative |
kPath | kIdempotent;
}
// This is used in OpenFst for binary I/O. This is OpenFst-style,
// not Kaldi-style, I/O.
std::istream &Read(std::istream &strm) {
// Always read/write as float, even if T is double,
// so we can use OpenFst-style read/write and still maintain
// compatibility when compiling with different FloatTypes
ReadType(strm, &value1_);
ReadType(strm, &value2_);
return strm;
}
// This is used in OpenFst for binary I/O. This is OpenFst-style,
// not Kaldi-style, I/O.
std::ostream &Write(std::ostream &strm) const {
WriteType(strm, value1_);
WriteType(strm, value2_);
return strm;
}
size_t Hash() const {
size_t ans;
union {
T f;
size_t s;
} u;
u.s = 0;
u.f = value1_;
ans = u.s;
u.f = value2_;
ans += u.s;
return ans;
}
protected:
inline static void WriteFloatType(std::ostream &strm, const T &f) {
if (f == std::numeric_limits<T>::infinity())
strm << "Infinity";
else if (f == -std::numeric_limits<T>::infinity())
strm << "-Infinity";
else if (f != f)
strm << "BadNumber";
else
strm << f;
}
// Internal helper function, used in ReadNoParen.
inline static void ReadFloatType(std::istream &strm, T &f) {
std::string s;
strm >> s;
if (s == "Infinity") {
f = std::numeric_limits<T>::infinity();
} else if (s == "-Infinity") {
f = -std::numeric_limits<T>::infinity();
} else if (s == "BadNumber") {
f = std::numeric_limits<T>::quiet_NaN();
} else {
char *p;
f = strtod(s.c_str(), &p);
if (p < s.c_str() + s.size())
strm.clear(std::ios::badbit);
}
}
// Reads LatticeWeight when there are no parentheses around pair terms...
// currently the only form supported.
inline std::istream &ReadNoParen(
std::istream &strm, char separator) {
int c;
do {
c = strm.get();
} while (isspace(c));
std::string s1;
while (c != separator) {
if (c == EOF) {
strm.clear(std::ios::badbit);
return strm;
}
s1 += c;
c = strm.get();
}
std::istringstream strm1(s1);
ReadFloatType(strm1, value1_); // ReadFloatType is class member function
// read second element
ReadFloatType(strm, value2_);
return strm;
}
friend std::istream &operator>> <FloatType>(std::istream&, LatticeWeightTpl<FloatType>&);
friend std::ostream &operator<< <FloatType>(std::ostream&, const LatticeWeightTpl<FloatType>&);
private:
T value1_;
T value2_;
};
/* ScaleTupleWeight is a function defined for LatticeWeightTpl and
CompactLatticeWeightTpl that mutliplies the pair (value1_, value2_) by a 2x2
matrix. Used, for example, in applying acoustic scaling.
*/
template<class FloatType, class ScaleFloatType>
inline LatticeWeightTpl<FloatType> ScaleTupleWeight(
const LatticeWeightTpl<FloatType> &w,
const std::vector<std::vector<ScaleFloatType> > &scale) {
// Without the next special case we'd get NaNs from infinity * 0
if (w.Value1() == std::numeric_limits<FloatType>::infinity())
return LatticeWeightTpl<FloatType>::Zero();
return LatticeWeightTpl<FloatType>(scale[0][0] * w.Value1() + scale[0][1] * w.Value2(),
scale[1][0] * w.Value1() + scale[1][1] * w.Value2());
}
/* For testing purposes and in case it's ever useful, we define a similar
function to apply to LexicographicWeight and the like, templated on
TropicalWeight<float> etc.; we use PairWeight which is the base class of
LexicographicWeight.
*/
template<class FloatType, class ScaleFloatType>
inline PairWeight<TropicalWeightTpl<FloatType>,
TropicalWeightTpl<FloatType> > ScaleTupleWeight(
const PairWeight<TropicalWeightTpl<FloatType>,
TropicalWeightTpl<FloatType> > &w,
const std::vector<std::vector<ScaleFloatType> > &scale) {
typedef TropicalWeightTpl<FloatType> BaseType;
typedef PairWeight<BaseType, BaseType> PairType;
const BaseType zero = BaseType::Zero();
// Without the next special case we'd get NaNs from infinity * 0
if (w.Value1() == zero || w.Value2() == zero)
return PairType(zero, zero);
FloatType f1 = w.Value1().Value(), f2 = w.Value2().Value();
return PairType(BaseType(scale[0][0] * f1 + scale[0][1] * f2),
BaseType(scale[1][0] * f1 + scale[1][1] * f2));
}
template<class FloatType>
inline bool operator==(const LatticeWeightTpl<FloatType> &wa,
const LatticeWeightTpl<FloatType> &wb) {
// Volatile qualifier thwarts over-aggressive compiler optimizations
// that lead to problems esp. with NaturalLess().
volatile FloatType va1 = wa.Value1(), va2 = wa.Value2(),
vb1 = wb.Value1(), vb2 = wb.Value2();
return (va1 == vb1 && va2 == vb2);
}
template<class FloatType>
inline bool operator!=(const LatticeWeightTpl<FloatType> &wa,
const LatticeWeightTpl<FloatType> &wb) {
// Volatile qualifier thwarts over-aggressive compiler optimizations
// that lead to problems esp. with NaturalLess().
volatile FloatType va1 = wa.Value1(), va2 = wa.Value2(),
vb1 = wb.Value1(), vb2 = wb.Value2();
return (va1 != vb1 || va2 != vb2);
}
// We define a Compare function LatticeWeightTpl even though it's
// not required by the semiring standard-- it's just more efficient
// to do it this way rather than using the NaturalLess template.
/// Compare returns -1 if w1 < w2, +1 if w1 > w2, and 0 if w1 == w2.
template<class FloatType>
inline int Compare (const LatticeWeightTpl<FloatType> &w1,
const LatticeWeightTpl<FloatType> &w2) {
FloatType f1 = w1.Value1() + w1.Value2(),
f2 = w2.Value1() + w2.Value2();
if (f1 < f2) { return 1; } // having smaller cost means you're larger
// in the semiring [higher probability]
else if (f1 > f2) { return -1; }
// mathematically we should be comparing (w1.value1_-w1.value2_ < w2.value1_-w2.value2_)
// in the next line, but add w1.value1_+w1.value2_ = w2.value1_+w2.value2_ to both sides and
// divide by two, and we get the simpler equivalent form w1.value1_ < w2.value1_.
else if (w1.Value1() < w2.Value1()) { return 1; }
else if (w1.Value1() > w2.Value1()) { return -1; }
else { return 0; }
}
template<class FloatType>
inline LatticeWeightTpl<FloatType> Plus(const LatticeWeightTpl<FloatType> &w1,
const LatticeWeightTpl<FloatType> &w2) {
return (Compare(w1, w2) >= 0 ? w1 : w2);
}
// For efficiency, override the NaturalLess template class.
template<class FloatType>
class NaturalLess<LatticeWeightTpl<FloatType> > {
public:
typedef LatticeWeightTpl<FloatType> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
template<>
class NaturalLess<LatticeWeightTpl<float> > {
public:
typedef LatticeWeightTpl<float> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
template<>
class NaturalLess<LatticeWeightTpl<double> > {
public:
typedef LatticeWeightTpl<double> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
template<class FloatType>
inline LatticeWeightTpl<FloatType> Times(const LatticeWeightTpl<FloatType> &w1,
const LatticeWeightTpl<FloatType> &w2) {
return LatticeWeightTpl<FloatType>(w1.Value1()+w2.Value1(), w1.Value2()+w2.Value2());
}
// divide w1 by w2 (on left/right/any doesn't matter as
// commutative).
template<class FloatType>
inline LatticeWeightTpl<FloatType> Divide(const LatticeWeightTpl<FloatType> &w1,
const LatticeWeightTpl<FloatType> &w2,
DivideType typ = DIVIDE_ANY) {
typedef FloatType T;
T a = w1.Value1() - w2.Value1(), b = w1.Value2() - w2.Value2();
if (a != a || b != b || a == -std::numeric_limits<T>::infinity()
|| b == -std::numeric_limits<T>::infinity()) {
KALDI_WARN << "LatticeWeightTpl::Divide, NaN or invalid number produced. "
<< "[dividing by zero?] Returning zero";
return LatticeWeightTpl<T>::Zero();
}
if (a == std::numeric_limits<T>::infinity() ||
b == std::numeric_limits<T>::infinity())
return LatticeWeightTpl<T>::Zero(); // not a valid number if only one is infinite.
return LatticeWeightTpl<T>(a, b);
}
template<class FloatType>
inline bool ApproxEqual(const LatticeWeightTpl<FloatType> &w1,
const LatticeWeightTpl<FloatType> &w2,
float delta = kDelta) {
if (w1.Value1() == w2.Value1() && w1.Value2() == w2.Value2()) return true; // handles Zero().
return (fabs((w1.Value1() + w1.Value2()) - (w2.Value1() + w2.Value2())) <= delta);
}
template <class FloatType>
inline std::ostream &operator <<(std::ostream &strm, const LatticeWeightTpl<FloatType> &w) {
LatticeWeightTpl<FloatType>::WriteFloatType(strm, w.Value1());
CHECK(FLAGS_fst_weight_separator.size() == 1);
strm << FLAGS_fst_weight_separator[0]; // comma by default;
// may or may not be settable from Kaldi programs.
LatticeWeightTpl<FloatType>::WriteFloatType(strm, w.Value2());
return strm;
}
template <class FloatType>
inline std::istream &operator >>(std::istream &strm, LatticeWeightTpl<FloatType> &w1) {
CHECK(FLAGS_fst_weight_separator.size() == 1);
// separator defaults to ','
return w1.ReadNoParen(strm, FLAGS_fst_weight_separator[0]);
}
// CompactLattice will be an acceptor (accepting the words/output-symbols),
// with the weights and input-symbol-seqs on the arcs.
// There must be a total order on W. We assume for the sake of efficiency
// that there is a function
// Compare(W w1, W w2) that returns -1 if w1 < w2, +1 if w1 > w2, and
// zero if w1 == w2, and Plus for type W returns (Compare(w1,w2) >= 0 ? w1 : w2).
template<class WeightType, class IntType>
class CompactLatticeWeightTpl {
public:
typedef WeightType W;
typedef CompactLatticeWeightTpl<WeightType, IntType> ReverseWeight;
// Plus is like LexicographicWeight on the pair (weight_, string_), but where we
// use standard lexicographic order on string_ [this is not the same as
// NaturalLess on the StringWeight equivalent, which does not define a
// total order].
// Times, Divide obvious... (support both left & right division..)
// CommonDivisor would need to be coded separately.
CompactLatticeWeightTpl() { }
CompactLatticeWeightTpl(const WeightType &w, const std::vector<IntType> &s):
weight_(w), string_(s) { }
CompactLatticeWeightTpl(const CompactLatticeWeightTpl &compactLatticeWeightTpl) = default;
CompactLatticeWeightTpl &operator=(const CompactLatticeWeightTpl &w) = default;
const W &Weight() const { return weight_; }
const std::vector<IntType> &String() const { return string_; }
void SetWeight(const W &w) { weight_ = w; }
void SetString(const std::vector<IntType> &s) { string_ = s; }
static const CompactLatticeWeightTpl<WeightType, IntType> Zero() {
return CompactLatticeWeightTpl<WeightType, IntType>(
WeightType::Zero(), std::vector<IntType>());
}
static const CompactLatticeWeightTpl<WeightType, IntType> One() {
return CompactLatticeWeightTpl<WeightType, IntType>(
WeightType::One(), std::vector<IntType>());
}
inline static std::string GetIntSizeString() {
char buf[2];
buf[0] = '0' + sizeof(IntType);
buf[1] = '\0';
return buf;
}
static const std::string &Type() {
static const std::string type = "compact" + WeightType::Type()
+ GetIntSizeString();
return type;
}
static const CompactLatticeWeightTpl<WeightType, IntType> NoWeight() {
return CompactLatticeWeightTpl<WeightType, IntType>(
WeightType::NoWeight(), std::vector<IntType>());
}
CompactLatticeWeightTpl<WeightType, IntType> Reverse() const {
size_t s = string_.size();
std::vector<IntType> v(s);
for(size_t i = 0; i < s; i++)
v[i] = string_[s-i-1];
return CompactLatticeWeightTpl<WeightType, IntType>(weight_, v);
}
bool Member() const {
// a semiring has only one zero, this is the important property
// we're trying to maintain here. So force string_ to be empty if
// w_ == zero.
if (!weight_.Member()) return false;
if (weight_ == WeightType::Zero())
return string_.empty();
else
return true;
}
CompactLatticeWeightTpl Quantize(float delta = kDelta) const {
return CompactLatticeWeightTpl(weight_.Quantize(delta), string_);
}
static constexpr uint64 Properties() {
return kLeftSemiring | kRightSemiring | kPath | kIdempotent;
}
// This is used in OpenFst for binary I/O. This is OpenFst-style,
// not Kaldi-style, I/O.
std::istream &Read(std::istream &strm) {
weight_.Read(strm);
if (strm.fail()){ return strm; }
int32 sz;
ReadType(strm, &sz);
if (strm.fail()){ return strm; }
if (sz < 0) {
KALDI_WARN << "Negative string size! Read failure";
strm.clear(std::ios::badbit);
return strm;
}
string_.resize(sz);
for(int32 i = 0; i < sz; i++) {
ReadType(strm, &(string_[i]));
}
return strm;
}
// This is used in OpenFst for binary I/O. This is OpenFst-style,
// not Kaldi-style, I/O.
std::ostream &Write(std::ostream &strm) const {
weight_.Write(strm);
if (strm.fail()){ return strm; }
int32 sz = static_cast<int32>(string_.size());
WriteType(strm, sz);
for(int32 i = 0; i < sz; i++)
WriteType(strm, string_[i]);
return strm;
}
size_t Hash() const {
size_t ans = weight_.Hash();
// any weird numbers here are largish primes
size_t sz = string_.size(), mult = 6967;
for(size_t i = 0; i < sz; i++) {
ans += string_[i] * mult;
mult *= 7499;
}
return ans;
}
private:
W weight_;
std::vector<IntType> string_;
};
template<class WeightType, class IntType>
inline bool operator==(const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2) {
return (w1.Weight() == w2.Weight() && w1.String() == w2.String());
}
template<class WeightType, class IntType>
inline bool operator!=(const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2) {
return (w1.Weight() != w2.Weight() || w1.String() != w2.String());
}
template<class WeightType, class IntType>
inline bool ApproxEqual(const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2,
float delta = kDelta) {
return (ApproxEqual(w1.Weight(), w2.Weight(), delta) && w1.String() == w2.String());
}
// Compare is not part of the standard for weight types, but used internally for
// efficiency. The comparison here first compares the weight; if this is the
// same, it compares the string. The comparison on strings is: first compare
// the length, if this is the same, use lexicographical order. We can't just
// use the lexicographical order because this would destroy the distributive
// property of multiplication over addition, taking into account that addition
// uses Compare. The string element of "Compare" isn't super-important in
// practical terms; it's only needed to ensure that Plus always give consistent
// answers and is symmetric. It's essentially for tie-breaking, but we need to
// make sure all the semiring axioms are satisfied otherwise OpenFst might
// break.
template<class WeightType, class IntType>
inline int Compare(const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2) {
int c1 = Compare(w1.Weight(), w2.Weight());
if (c1 != 0) return c1;
int l1 = w1.String().size(), l2 = w2.String().size();
// Use opposite order on the string lengths, so that if the costs are the same,
// the shorter string wins.
if (l1 > l2) return -1;
else if (l1 < l2) return 1;
for(int i = 0; i < l1; i++) {
if (w1.String()[i] < w2.String()[i]) return -1;
else if (w1.String()[i] > w2.String()[i]) return 1;
}
return 0;
}
// For efficiency, override the NaturalLess template class.
template<class FloatType, class IntType>
class NaturalLess<CompactLatticeWeightTpl<LatticeWeightTpl<FloatType>, IntType> > {
public:
typedef CompactLatticeWeightTpl<LatticeWeightTpl<FloatType>, IntType> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
template<>
class NaturalLess<CompactLatticeWeightTpl<LatticeWeightTpl<float>, int32> > {
public:
typedef CompactLatticeWeightTpl<LatticeWeightTpl<float>, int32> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
template<>
class NaturalLess<CompactLatticeWeightTpl<LatticeWeightTpl<double>, int32> > {
public:
typedef CompactLatticeWeightTpl<LatticeWeightTpl<double>, int32> Weight;
NaturalLess() {}
bool operator()(const Weight &w1, const Weight &w2) const {
// NaturalLess is a negative order (opposite to normal ordering).
// This operator () corresponds to "<" in the negative order, which
// corresponds to the ">" in the normal order.
return (Compare(w1, w2) == 1);
}
};
// Make sure Compare is defined for TropicalWeight, so everything works
// if we substitute LatticeWeight for TropicalWeight.
inline int Compare(const TropicalWeight &w1,
const TropicalWeight &w2) {
float f1 = w1.Value(), f2 = w2.Value();
if (f1 == f2) return 0;
else if (f1 > f2) return -1;
else return 1;
}
template<class WeightType, class IntType>
inline CompactLatticeWeightTpl<WeightType, IntType> Plus(
const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2) {
return (Compare(w1, w2) >= 0 ? w1 : w2);
}
template<class WeightType, class IntType>
inline CompactLatticeWeightTpl<WeightType, IntType> Times(
const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2) {
WeightType w = Times(w1.Weight(), w2.Weight());
if (w == WeightType::Zero()) {
return CompactLatticeWeightTpl<WeightType, IntType>::Zero();
// special case to ensure zero is unique
} else {
std::vector<IntType> v;
v.resize(w1.String().size() + w2.String().size());
typename std::vector<IntType>::iterator iter = v.begin();
iter = std::copy(w1.String().begin(), w1.String().end(), iter); // returns end of first range.
std::copy(w2.String().begin(), w2.String().end(), iter);
return CompactLatticeWeightTpl<WeightType, IntType>(w, v);
}
}
template<class WeightType, class IntType>
inline CompactLatticeWeightTpl<WeightType, IntType> Divide(const CompactLatticeWeightTpl<WeightType, IntType> &w1,
const CompactLatticeWeightTpl<WeightType, IntType> &w2,
DivideType div = DIVIDE_ANY) {
if (w1.Weight() == WeightType::Zero()) {
if (w2.Weight() != WeightType::Zero()) {
return CompactLatticeWeightTpl<WeightType, IntType>::Zero();
} else {
KALDI_ERR << "Division by zero [0/0]";
}
} else if (w2.Weight() == WeightType::Zero()) {
KALDI_ERR << "Error: division by zero";
}
WeightType w = Divide(w1.Weight(), w2.Weight());
const std::vector<IntType> v1 = w1.String(), v2 = w2.String();
if (v2.size() > v1.size()) {
KALDI_ERR << "Cannot divide, length mismatch";
}
typename std::vector<IntType>::const_iterator v1b = v1.begin(),
v1e = v1.end(), v2b = v2.begin(), v2e = v2.end();
if (div == DIVIDE_LEFT) {
if (!std::equal(v2b, v2e, v1b)) { // v2 must be identical to first part of v1.
KALDI_ERR << "Cannot divide, data mismatch";
}
return CompactLatticeWeightTpl<WeightType, IntType>(
w, std::vector<IntType>(v1b+(v2e-v2b), v1e)); // return last part of v1.
} else if (div == DIVIDE_RIGHT) {
if (!std::equal(v2b, v2e, v1e-(v2e-v2b))) { // v2 must be identical to last part of v1.
KALDI_ERR << "Cannot divide, data mismatch";
}
return CompactLatticeWeightTpl<WeightType, IntType>(
w, std::vector<IntType>(v1b, v1e-(v2e-v2b))); // return first part of v1.
} else {
KALDI_ERR << "Cannot divide CompactLatticeWeightTpl with DIVIDE_ANY";
}
return CompactLatticeWeightTpl<WeightType,IntType>::Zero(); // keep compiler happy.
}
template <class WeightType, class IntType>
inline std::ostream &operator <<(std::ostream &strm, const CompactLatticeWeightTpl<WeightType, IntType> &w) {
strm << w.Weight();
CHECK(FLAGS_fst_weight_separator.size() == 1);
strm << FLAGS_fst_weight_separator[0]; // comma by default.
for(size_t i = 0; i < w.String().size(); i++) {
strm << w.String()[i];
if (i+1 < w.String().size())
strm << kStringSeparator; // '_'; defined in string-weight.h in OpenFst code.
}
return strm;
}
template <class WeightType, class IntType>
inline std::istream &operator >>(std::istream &strm, CompactLatticeWeightTpl<WeightType, IntType> &w) {
std::string s;
strm >> s;
if (strm.fail()) {
return strm;
}
CHECK(FLAGS_fst_weight_separator.size() == 1);
size_t pos = s.find_last_of(FLAGS_fst_weight_separator); // normally ","
if (pos == std::string::npos) {
strm.clear(std::ios::badbit);
return strm;
}
// get parts of str before and after the separator (default: ',');
std::string s1(s, 0, pos), s2(s, pos+1);
std::istringstream strm1(s1);
WeightType weight;
strm1 >> weight;
w.SetWeight(weight);
if (strm1.fail() || !strm1.eof()) {
strm.clear(std::ios::badbit);
return strm;
}
// read string part.
std::vector<IntType> string;
const char *c = s2.c_str();
while(*c != '\0') {
if (*c == kStringSeparator) // '_'
c++;
char *c2;
long int i = strtol(c, &c2, 10);
if (c2 == c || static_cast<long int>(static_cast<IntType>(i)) != i) {
strm.clear(std::ios::badbit);
return strm;
}
c = c2;
string.push_back(static_cast<IntType>(i));
}
w.SetString(string);
return strm;
}
template<class BaseWeightType, class IntType>
class CompactLatticeWeightCommonDivisorTpl {
public:
typedef CompactLatticeWeightTpl<BaseWeightType, IntType> Weight;
Weight operator()(const Weight &w1, const Weight &w2) const {
// First find longest common prefix of the strings.
typename std::vector<IntType>::const_iterator s1b = w1.String().begin(),
s1e = w1.String().end(), s2b = w2.String().begin(), s2e = w2.String().end();
while (s1b < s1e && s2b < s2e && *s1b == *s2b) {
s1b++;
s2b++;
}
return Weight(Plus(w1.Weight(), w2.Weight()), std::vector<IntType>(w1.String().begin(), s1b));
}
};
/** Scales the pair (a, b) of floating-point weights inside a
CompactLatticeWeight by premultiplying it (viewed as a vector)
by a 2x2 matrix "scale".
Assumes there is a ScaleTupleWeight function that applies to "Weight";
this currently only works if Weight equals LatticeWeightTpl<FloatType>
for some FloatType.
*/
template<class Weight, class IntType, class ScaleFloatType>
inline CompactLatticeWeightTpl<Weight, IntType> ScaleTupleWeight(
const CompactLatticeWeightTpl<Weight, IntType> &w,
const std::vector<std::vector<ScaleFloatType> > &scale) {
return CompactLatticeWeightTpl<Weight, IntType>(
Weight(ScaleTupleWeight(w.Weight(), scale)), w.String());
}
/** Define some ConvertLatticeWeight functions that are used in various lattice
conversions... make them all templates, some with no arguments, since some
must be templates.*/
template<class Float1, class Float2>
inline void ConvertLatticeWeight(
const LatticeWeightTpl<Float1> &w_in,
LatticeWeightTpl<Float2> *w_out) {
w_out->SetValue1(w_in.Value1());
w_out->SetValue2(w_in.Value2());
}
template<class Float1, class Float2, class Int>
inline void ConvertLatticeWeight(
const CompactLatticeWeightTpl<LatticeWeightTpl<Float1>, Int> &w_in,
CompactLatticeWeightTpl<LatticeWeightTpl<Float2>, Int> *w_out) {
LatticeWeightTpl<Float2> weight2(w_in.Weight().Value1(),
w_in.Weight().Value2());
w_out->SetWeight(weight2);
w_out->SetString(w_in.String());
}
// to convert from Lattice to standard FST
template<class Float1, class Float2>
inline void ConvertLatticeWeight(
const LatticeWeightTpl<Float1> &w_in,
TropicalWeightTpl<Float2> *w_out) {
TropicalWeightTpl<Float2> w1(w_in.Value1());
TropicalWeightTpl<Float2> w2(w_in.Value2());
*w_out = Times(w1, w2);
}
template<class Float>
inline double ConvertToCost(const LatticeWeightTpl<Float> &w) {
return static_cast<double>(w.Value1()) + static_cast<double>(w.Value2());
}
template<class Float, class Int>
inline double ConvertToCost(const CompactLatticeWeightTpl<LatticeWeightTpl<Float>, Int> &w) {
return static_cast<double>(w.Weight().Value1()) + static_cast<double>(w.Weight().Value2());
}
template<class Float>
inline double ConvertToCost(const TropicalWeightTpl<Float> &w) {
return w.Value();
}
} // namespace fst
#endif // KALDI_FSTEXT_LATTICE_WEIGHT_H_
@@ -0,0 +1,728 @@
// fstext/pre-determinize-inl.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_PRE_DETERMINIZE_INL_H_
#define KALDI_FSTEXT_PRE_DETERMINIZE_INL_H_
/* Do not include this file directly. It is an implementation file included by PreDeterminize.h */
/*
Predeterminization
This is a function that makes an FST compactly determinizable by inserting symbols on the input
side as necessary for disambiguation. Note that we do not treat epsilon as a real symbol
when measuring determinizability in this sense. The extra symbols are added to the vocabulary,
on the input side; these are of the form (prefix)1, (prefix)2, and so on without limit, where
(prefix) is some prefix the user provides, e.g. '#' (the function checks that this will not
lead to conflicts with symbols already in the FST). The function tells us how many such
symbols it created.
Note that there is a paper "Generalized optimization algorithm for speech recognition
transducers" by Allauzen and Mohri, that deals with a similar issue, but this is a very
different algorithm that only aims to ensure determinizability, but not *compact*
determinizability.
Our algorithm is slightly heuristic, and probably not optimal, but does ensure that the
output is compactly determinizable, possibly at the expense of inserting unnecessary
symbols. We considered more sophisticated algorithms, but these were extremely
complicated and would give the same output for the kinds of inputs that we envisage.
Suppose the input FST is T. We want to ensure that in det(T), if we consider the
states of det(T) as weighted subsets of states of T, each state of T only appears once
in any given subset. This ensures that det(T) is no larger than T in an appropriate
sense. The way we do this is as follows. We identify all states in T that have
multiple input transitions (counting "being an initial state" as an input transition).
Let's call these "problematic" states. For a problematic state p we stipulate that it
can never appear in any state of det(T) unless that state equals (p, \bar{1}) [i.e. p,
unweighted]. In order to ensure this, we insert input symbols on the transitions to these
problematic states (this may necessitate adding extra states).
We also stipulate that the path through det(T) should always be sufficient to tell us
the path through T (and we insert extra symbols sufficient to make this so). This is to
simplify the algorithm, so that we don't have to consider the output symbols or weights
when predeterminizing.
The algorithm is as follows.
(A) Definitions
(i) Define a *problematic state* as a state that either has multiple input transitions,
or is an initial state and has at least one input transition.
(ii) For an arc a, define:
i[a] = input symbol on a
o[a] = output symbol on a
n[a] = dest-state of a
p[a] = origin-state of a
For a state q, define
E[q] = set of transitions leaving q.
For a set of states Q, define
E[Q] = set of transitions leaving some q in Q
(iii) For a state s, define Closure(s) as the union of state s, and all states t
that are reachable via sequences of arcs a such that i[a]=epsilon and n[a] is
not problematic.
For a set of states S, define Closure(S) as the union of the closures of
states s in S.
(B) Inputs and outputs.
(i) Inputs and preconditions. Input is an FST, which should have a symbol table compiled into
it, and a prefix (e.g. #) for symbols to be added. We check that the input FST is trim,
and that it does not have any symbols that appear on its arcs, that are equal to the prefix
followed by digits.
(ii) Outputs: The algorithm modifies the FST that is given to it, and returns the number of
the highest numbered "extra symbol" inserted. The extra symbols are numbered #1, #2 and
so on without limit (as integers). They are inserted into the symbol table in a sequential
way by calling AvailableKey()
for each in turn (this is stipulated in case we need to keep other symbol tables in sync).
(C) Sub-algorithm: Closure(S). This requires the array p(s), defined below, which is true
if s is problematic. This also requires, for efficiency, that the arcs be sorted on input
label.
Input: a set of states S. [plus, the fst and the array p].
Output: a set of states T.
Algorithm:
set T <-- S, Q <-- S.
while Q is nonempty:
pop a state s from Q.
for each transition a from state s with epsilon on the input label [we can
find these efficiently using the sorting on arcs]:
If p(n[a]) is false and n[a] is not in T:
Insert n[a] into T.
Add n[a] to Q.
return T.
(D) Main algorithm.
(i) (a) Check preconditions (FST is trim)
(b) Make sure there is just one final state (insert epsilon transitions as necessary).
(c) Sort arcs on input label (so epsilon arcs are at the start of arc lists).
(ii) Work out the set of problematic states by constructing a boolean array indexed by
states, i.e.
p(s)
which is true if the state is problematic. We can do this by constructing an array
t(s) to store the number of transitions into each state [adding one for the initial state],
and then setting p(s) = true if t(s) > 1.
Also create a boolean array d(s), defined for states, and set d(s) = false.
This array is purely for sanity-checking that we are processing each state exactly once.
(iii) Set up an array of integers m(a), indexed by arcs (how exactly we store these is
implementation-dependent, but this will probably be a hash from (state, arc-index) to
integers. m(a) will store the extra symbol, if any, to be added to that arc (or -1
if no such symbol; we can also simply have the arc not present in the hash). The
initial value of m(a) is -1 (if array), or undefined (if hash).
(iv) Initialize a set of sets-of-states S, and a queue of pairs Q, as follows.
The pairs in Q are a pair of (set-of-states, integer), where the integer
is the number of "special symbols" already used up for that state.
Note that we use a special indexing for the sets in both S and Q, rather than
using std::set. We use a sorted vector of StateId's. And in S, we index them
by the lowest-numbered state-id. Because each state is supposed to only ever
be a member of one set, if there is an attempt to add another, different set
with the same lowest-numbered state-id, we detect an error.
Let I be the single initial state (OpenFST only supports one).
We set:
S = { Closure(I) }
Push (Closure(I), 0) onto Q.
Then for each state s such that p(s) = true, and s is not an initial state:
S <-- S u { Closure(s) }
Push (Closure(s), 0) onto Q.
(v) While Q is nonempty:
(a) Pop pair (A, n) from Q (queue discipline is arbitrary).
(b) For each state s in A, check that d(s) is false, and set d(s) to true.
This is for sanity checking only.
(c)
Let S_\eps be the set of epsilon-transitions from members of A to problematic
states (i.e. S_\eps = \{ a \in E[A]: i[a]=\epsilon, p(n[a]) = true \}).
Next, we will define, for each t \neq \epsilon, S_t as the set of
transitions from some state s in S with t as the input label, i.e.:
S_t = \{ a \in E[A]: i[a] = t \}
We further define T_t and U_t as the subsets of S where the destination
state is problematic and non-problematic respectively, i.e:
T_t = \{ a \in E[A]: i[a] = t, p(n[a]) = true \}
U_t = \{ a \in E[A]: i[a] = t, p(n[a]) = false \}
The easiest way to obtain these sets is probably to have a hash indexed by
t that maps to a list of pairs (state, arc-offset) that stores S_t.
From this we can work out the sizes of T_t and U_t on the fly.
(d)
for each transition a in S_\eps:
m(a) <-- n # Will put symbol n on this transition.
n <-- n+1 # Note, same n as in pair (A, n)
(e)
next,
for each t\neq epsilon s.t. S_t is nonempty,
if |S_t| > 1 #if-statement is because if |S_t|=|T_t|=1, no need for prefix.
k = 0
for each transition a in T_t:
set m(a) to k.
set k = k+1
if |U_t| > 0
Let V_t be the set of destination-states of arcs in U_t.
if Closure(V_t) is not in S:
insert Closure(V_t) into S, and add the pair (Closure(V_t), k) to Q.
(vi) Check that for each state in the FST, d(s) = true.
(vii) Let n = max_a m(a). This is the highest-numbered extra symbol (extra symbols
start from zero, in this numbering which doesn't correspond to the symbol-table
numbering). Here we add n+1 extra symbols to the symbol table and store
the mappings from 0, 1, ... n to the symbol-id.
(viii) Set up a hash h from (state, int) to (state-id) such that
t = h(s, k)
will be the state-id of a newly-created state that has a transition to state s
with input-label #k.
(ix) For each arc a such that m(a) != 0:
If i[a] = epsilon (the input label is epsilon):
Change i[a] to #m(a). [i.e. prefix then digit m(a)]
Otherwise:
If t = h(n[a], m(a)) is not defined [where n[a] is the dest-state]:
create a new state t with a transition to n[a], with input-label #m(a) and
no output-label or weight. Set h(n[a], m(a)) = t.
Change n[a] to h(n[a], m(a)).
*/
namespace fst {
namespace pre_determinize_helpers {
// make it inline to avoid having to put it in a .cc file which most functions here
// could not go in.
inline bool HasBannedPrefixPlusDigits(SymbolTable *symTable, std::string prefix, std::string *bad_sym) {
// returns true if the symbol table contains any string consisting of this
// (possibly empty) prefix followed by a nonempty sequence of digits (0 to 9).
// requires symTable to be non-NULL.
// if bad_sym != NULL, puts the first bad symbol it finds in *bad_sym.
assert(symTable != NULL);
const char *prefix_ptr = prefix.c_str();
size_t prefix_len = strlen(prefix_ptr); // allowed to be zero but not encouraged.
for (SymbolTableIterator siter(*symTable); !siter.Done(); siter.Next()) {
const std::string &sym = siter.Symbol();
if (!strncmp(prefix_ptr, sym.c_str(), prefix_len)) { // has prefix.
if (isdigit(sym[prefix_len])) { // we don't allow prefix followed by a digit, as a symbol.
// Has at least one digit.
size_t pos;
for (pos = prefix_len;sym[pos] != '\0'; pos++)
if (!isdigit(sym[pos])) break;
if (sym[pos] == '\0') { // All remaining characters were digits.
if (bad_sym != NULL) *bad_sym = sym;
return true;
}
} // else OK because prefix was followed by '\0' or a non-digit.
}
}
return false; // doesn't have banned symbol.
}
template<class T> void CopySetToVector(const std::set<T> s, std::vector<T> *v) {
// adds members of s to v, in sorted order from lowest to highest
// (because the set was in sorted order).
assert(v != NULL);
v->resize(s.size());
typename std::set<T>::const_iterator siter = s.begin();
typename std::vector<T>::iterator viter = v->begin();
for (; siter != s.end(); ++siter, ++viter) {
assert(viter != v->end());
*viter = *siter;
}
}
// Warning. This function calls 'new'.
template<class T>
std::vector<T>* InsertMember(const std::vector<T> m, std::vector<std::vector<T>*> *S) {
assert(m.size() > 0);
T idx = m[0];
assert(idx>=(T)0 && idx < (T)S->size());
if ( (*S)[idx] != NULL) {
assert( *((*S)[idx]) == m );
// The vectors should be the same. Otherwise this is a bug in the algorithm.
// It could either be a programming error or a deeper conceptual bug.
return NULL; // nothing was inserted.
} else {
std::vector<T> *ret = (*S)[idx] = new std::vector<T>(m); // New copy of m.
return ret; // was inserted.
}
}
// See definition of Closure(S) in item A(iii) in the comment above. it's the set of states
// that are reachable from S via sequences of arcs a such that i[a]=epsilon and n[a] is
// not problematic. We assume that the fst is sorted on input label (so epsilon arcs first)
// The algorithm is described in section (C) above. We use the same variable for S and T.
template<class Arc> void Closure(MutableFst<Arc> *fst, std::set<typename Arc::StateId> *S,
const std::vector<bool> &pVec) {
typedef typename Arc::StateId StateId;
std::vector<StateId> Q;
CopySetToVector(*S, &Q);
while (Q.size() != 0) {
StateId s = Q.back();
Q.pop_back();
for (ArcIterator<MutableFst<Arc> > aiter(*fst, s); ! aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel != 0) break; // Break from the loop: due to sorting there will be no
// more transitions with epsilons as input labels.
if (!pVec[arc.nextstate]) { // Next state is not problematic -> we can use this transition.
std::pair< typename std::set<StateId>::iterator, bool > p = S->insert(arc.nextstate);
if (p.second) { // True means: was inserted into S (wasn't already there).
Q.push_back(arc.nextstate);
}
}
}
}
} // end function Closure.
} // end namespace pre_determinize_helpers.
template<class Arc, class Int>
void PreDeterminize(MutableFst<Arc> *fst,
typename Arc::Label first_new_sym,
std::vector<Int> *symsOut) {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef size_t ArcId; // Our own typedef, not standard OpenFst. Use size_t
// for compatibility with argument of ArcIterator::Seek().
typedef typename Arc::Weight Weight;
assert(first_new_sym > 0);
assert(fst != NULL);
if (fst->Start() == kNoStateId) return; // for empty FST, nothing to do.
assert(symsOut != NULL && symsOut->size() == 0); // we will output the symbols we add into this.
{ // (D)(i)(a): check is trim (i.e. connected, in OpenFST parlance).
KALDI_VLOG(2) << "PreDeterminize: Checking FST properties";
uint64 props = fst->Properties(kAccessible|kCoAccessible, true); // true-> computes properties if unknown at time when called.
if (props != (kAccessible|kCoAccessible)) { // All states are not both accessible and co-accessible...
KALDI_ERR << "PreDeterminize: FST is not trim";
}
}
{ // (D)(i)(b): make single final state.
KALDI_VLOG(2) << "PreDeterminize: creating single final state";
CreateSuperFinal(fst);
}
{ // (D)(i)(c): sort arcs on input.
KALDI_VLOG(2) << "PreDeterminize: sorting arcs on input";
ILabelCompare<Arc> icomp;
ArcSort(fst, icomp);
}
StateId n_states = 0, max_state = 0; // Compute n_states, max_state = highest-numbered state.
{ // compute nStates, maxStates.
for (StateIterator<MutableFst<Arc> > iter(*fst); ! iter.Done(); iter.Next()) {
StateId state = iter.Value();
assert(state>=0);
n_states++;
if (state > max_state) max_state = state;
}
KALDI_VLOG(2) << "PreDeterminize: n_states = "<<(n_states)<<", max_state ="<<(max_state);
}
std::vector<bool> p_vec(max_state+1, false); // compute this next.
{ // D(ii): computing the array p. ["problematic states, i.e. states with >1 input transition,
// counting being the initial state as an input transition"].
std::vector<bool> seen_vec(max_state+1, false); // rather than counting incoming transitions we just have a bool that says we saw at least one.
seen_vec[fst->Start()] = true;
for (StateIterator<MutableFst<Arc> > siter(*fst); ! siter.Done(); siter.Next()) {
for (ArcIterator<MutableFst<Arc> > aiter(*fst, siter.Value()); ! aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
assert(arc.nextstate>=0&&arc.nextstate<max_state+1);
if (seen_vec[arc.nextstate])
p_vec[arc.nextstate] = true; // now have >1 transition in, so problematic.
else
seen_vec[arc.nextstate] = true;
}
}
}
// D(iii): set up m(a)
std::map<std::pair<StateId, ArcId>, size_t> m_map;
// This is the array m, indexed by arcs. It maps to the index of the symbol we add.
// WARNING: we should be sure to clean up this memory before exiting. Do not return
// or throw an exception from this function, later than this point, without cleaning up!
// Note that the vectors are shared between Q and S (they "belong to" S.
std::vector<std::vector<StateId>* > S(max_state+1, (std::vector<StateId>*)(void*)0);
std::vector<std::pair<std::vector<StateId>*, size_t> > Q;
// D(iv): initialize S and Q.
{
std::vector<StateId> all_seed_states; // all "problematic" states, plus initial state (if not problematic).
if (!p_vec[fst->Start()])
all_seed_states.push_back(fst->Start());
for (StateId s = 0;s<=max_state; s++)
if (p_vec[s]) all_seed_states.push_back(s);
for (size_t idx = 0;idx < all_seed_states.size(); idx++) {
StateId s = all_seed_states[idx];
std::set<StateId> closure_s;
closure_s.insert(s); // insert "seed" state.
pre_determinize_helpers::Closure(fst, &closure_s, p_vec); // follow epsilons to non-problematic states.
// Closure in this case whis will usually not add anything, for typical topologies in speech
std::vector<StateId> closure_s_vec;
pre_determinize_helpers::CopySetToVector(closure_s, &closure_s_vec);
KALDI_ASSERT(closure_s_vec.size() != 0);
std::vector<StateId> *ptr = pre_determinize_helpers::InsertMember(closure_s_vec, &S);
KALDI_ASSERT(ptr != NULL); // Or conceptual bug or programming error.
Q.push_back(std::pair<std::vector<StateId>*, size_t>(ptr, 0));
}
}
std::vector<bool> d_vec(max_state+1, false); // "done vector". Purely for debugging.
size_t num_extra_det_states = 0;
// (D)(v)
while (Q.size() != 0) {
// (D)(v)(a)
std::pair<std::vector<StateId>*, size_t> cur_pair(Q.back());
Q.pop_back();
const std::vector<StateId> &A(*cur_pair.first);
size_t n =cur_pair.second; // next special symbol to add.
// (D)(v)(b)
for (size_t idx = 0;idx < A.size(); idx++) {
assert(d_vec[A[idx]] == false && "This state has been seen before. Algorithm error.");
d_vec[A[idx]] = true;
}
// From here is (D)(v)(c). We work out S_\eps and S_t (for t\neq eps)
// simultaneously at first.
std::map<Label, std::set<std::pair<std::pair<StateId, ArcId>, StateId> > > arc_hash;
// arc_hash is a hash with info of all arcs from states in the set A to
// non-problematic states.
// It is a map from ilabel to pair(pair(start-state, arc-offset), end-state).
// Here, arc-offset reflects the order in which we accessed the arc using the
// ArcIterator (zero for the first arc).
{ // This block sets up arc_hash
for (size_t idx = 0;idx < A.size(); idx++) {
StateId s = A[idx];
assert(s>=0 && s<=max_state);
ArcId arc_id = 0;
for (ArcIterator<MutableFst<Arc> > aiter(*fst, s); ! aiter.Done(); aiter.Next(), ++arc_id) {
const Arc &arc = aiter.Value();
std::pair<std::pair<StateId, ArcId>, StateId>
this_pair(std::pair<StateId, ArcId>(s, arc_id), arc.nextstate);
bool inserted = (arc_hash[arc.ilabel].insert(this_pair)).second;
assert(inserted); // Otherwise we had a duplicate.
}
}
}
// (D)(v)(d)
if (arc_hash.count(0) == 1) { // We have epsilon transitions out.
std::set<std::pair<std::pair<StateId, ArcId>, StateId> > &eps_set = arc_hash[0];
typedef typename std::set<std::pair<std::pair<StateId, ArcId>, StateId> >::iterator set_iter_t;
for (set_iter_t siter = eps_set.begin(); siter != eps_set.end(); ++siter) {
const std::pair<std::pair<StateId, ArcId>, StateId> &this_pr = *siter;
if (p_vec[this_pr.second]) { // Eps-transition to problematic state.
assert(m_map.count(this_pr.first) == 0);
m_map[this_pr.first] = n;
n++;
}
}
}
// (D)(v)(e)
{
typedef typename std::map<Label, std::set<std::pair<std::pair<StateId, ArcId>, StateId> > >::iterator map_iter_t;
typedef typename std::set<std::pair<std::pair<StateId, ArcId>, StateId> >::iterator set_iter_t2;
for (map_iter_t miter = arc_hash.begin(); miter != arc_hash.end(); ++miter) {
Label t = miter->first;
std::set<std::pair<std::pair<StateId, ArcId>, StateId> > &S_t = miter->second;
if (t != 0) { // For t != epsilon,
std::set<StateId> V_t; // set of destination non-problem states. Will create this set now.
// exists_noproblem is true iff |U_t| > 0.
size_t k = 0;
// First loop "for each transition a in T_t" (i.e. transitions to problematic states)
// The if-statement if (|S_t|>1) is pushed inside the loop, as the loop also computes
// the set V_t.
for (set_iter_t2 siter = S_t.begin(); siter != S_t.end(); ++siter) {
const std::pair<std::pair<StateId, ArcId>, StateId> &this_pr = *siter;
if (p_vec[this_pr.second]) { // only consider problematic states (just set T_t)
if (S_t.size() > 1) { // This is where we pushed the if-statement in.
assert(m_map.count(this_pr.first) == 0);
m_map[this_pr.first] = k;
k++;
num_extra_det_states++;
}
} else { // Create the set V_t.
V_t.insert(this_pr.second);
}
}
if (V_t.size() != 0) {
pre_determinize_helpers::Closure(fst, &V_t, p_vec); // follow epsilons to non-problematic states.
std::vector<StateId> closure_V_t_vec;
pre_determinize_helpers::CopySetToVector(V_t, &closure_V_t_vec);
std::vector<StateId> *ptr = pre_determinize_helpers::InsertMember(closure_V_t_vec, &S);
if (ptr != NULL) { // was inserted.
Q.push_back(std::pair<std::vector<StateId>*, size_t>(ptr, k));
}
}
}
}
}
} // end while (Q.size() != 0)
{ // (D)(vi): Check that for each state in the FST, d(s) = true.
for (StateIterator<MutableFst<Arc> > siter(*fst); ! siter.Done(); siter.Next()) {
StateId val = siter.Value();
assert(d_vec[val] == true);
}
}
{ // (D)(vii): compute symbol-table ID's.
// sets up symsOut array.
int64 n = -1;
for (typename std::map<std::pair<StateId, ArcId>, size_t>::iterator m_iter = m_map.begin();
m_iter != m_map.end();
++m_iter) {
n = std::max(n, (int64) m_iter->second); // m_iter->second is of type size_t.
}
// At this point n is the highest symbol-id (type size_t) of symbols we must add.
n++; // This is now the number of symbols we must add.
for (size_t i = 0;static_cast<int64>(i)<n;i++) symsOut->push_back(first_new_sym + i);
}
// (D)(viii): set up hash.
std::map<std::pair<StateId, size_t>, StateId> h_map;
{ // D(ix): add extra symbols! This is where the work gets done.
// Core part of this is below, search for (*)
size_t n_states_added = 0;
for (typename std::map<std::pair<StateId, ArcId>, size_t>::iterator m_iter = m_map.begin();
m_iter != m_map.end();
++m_iter) {
StateId state = m_iter->first.first;
ArcId arcpos = m_iter->first.second;
size_t m_a = m_iter->second;
MutableArcIterator<MutableFst<Arc> > aiter(fst, state);
aiter.Seek(arcpos);
Arc arc = aiter.Value();
// (*) core part here.
if (arc.ilabel == 0)
arc.ilabel = (*symsOut)[m_a];
else {
std::pair<StateId, size_t> pr(arc.nextstate, m_a);
if (!h_map.count(pr)) {
n_states_added++;
StateId newstate = fst->AddState();
assert(newstate>=0);
Arc new_arc( (*symsOut)[m_a], (Label)0, Weight::One(), arc.nextstate);
fst->AddArc(newstate, new_arc);
h_map[pr] = newstate;
}
arc.nextstate = h_map[pr];
}
aiter.SetValue(arc);
}
KALDI_VLOG(2) << "Added " <<(n_states_added)<<" new states and added/changed "<<(m_map.size())<<" arcs";
}
// Now free up memory.
for (size_t i = 0;i < S.size();i++)
delete S[i];
} // end function PreDeterminize
template<class Label> void CreateNewSymbols(SymbolTable *input_sym_table, int nSym,
std::string prefix, std::vector<Label> *symsOut) {
// Creates nSym new symbols named (prefix)0, (prefix)1 and so on.
// Crashes if it cannot create them because one or more of them were in the symbol
// table already.
assert(symsOut && symsOut->size() == 0);
for (int i = 0;i < nSym;i++) {
std::stringstream ss; ss << prefix << i;
std::string str = ss.str();
if (input_sym_table->Find(str) != -1) { // should not be present.
}
assert(symsOut);
symsOut->push_back( (Label) input_sym_table->AddSymbol(str));
}
}
// see pre-determinize.h for documentation.
template<class Arc> void AddSelfLoops(MutableFst<Arc> *fst, std::vector<typename Arc::Label> &isyms,
std::vector<typename Arc::Label> &osyms) {
assert(fst != NULL);
assert(isyms.size() == osyms.size());
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
size_t n = isyms.size();
if (n == 0) return; // Nothing to do.
// {
// the following declarations and statements are for quick detection of these
// symbols, which is purely for debugging/checking purposes.
Label isyms_min = *std::min_element(isyms.begin(), isyms.end()),
isyms_max = *std::max_element(isyms.begin(), isyms.end()),
osyms_min = *std::min_element(osyms.begin(), osyms.end()),
osyms_max = *std::max_element(osyms.begin(), osyms.end());
std::set<Label> isyms_set, osyms_set;
for (size_t i = 0; i < isyms.size(); i++) {
assert(isyms[i] > 0 && osyms[i] > 0); // should not have epsilon or invalid symbols.
isyms_set.insert(isyms[i]);
osyms_set.insert(osyms[i]);
}
assert(isyms_set.size() == n && osyms_set.size() == n);
// } end block.
for (StateIterator<MutableFst<Arc> > siter(*fst); ! siter.Done(); siter.Next()) {
StateId state = siter.Value();
bool this_state_needs_self_loops = (fst->Final(state) != Weight::Zero());
for (ArcIterator<MutableFst<Arc> > aiter(*fst, state); ! aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
// If one of the following asserts fails, it means that the input FST already had the symbols
// we are inserting. This is contrary to the preconditions of this algorithm.
assert(!(arc.ilabel>=isyms_min && arc.ilabel<=isyms_max && isyms_set.count(arc.ilabel) != 0));
assert(!(arc.olabel>=osyms_min && arc.olabel<=osyms_max && osyms_set.count(arc.olabel) != 0));
if (arc.olabel != 0) // Has non-epsilon output label -> need self loops.
this_state_needs_self_loops = true;
}
if (this_state_needs_self_loops) {
for (size_t i = 0;i < n;i++) {
Arc arc;
arc.ilabel = isyms[i];
arc.olabel = osyms[i];
arc.weight = Weight::One();
arc.nextstate = state;
fst->AddArc(state, arc);
}
}
}
}
template<class Arc>
int64 DeleteISymbols(MutableFst<Arc> *fst, std::vector<typename Arc::Label> isyms) {
// We could do this using the Mapper concept, but this is much easier to understand.
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
int64 num_deleted = 0;
if (isyms.size() == 0) return 0;
Label isyms_min = *std::min_element(isyms.begin(), isyms.end()),
isyms_max = *std::max_element(isyms.begin(), isyms.end());
bool isyms_consecutive = (isyms_max+1-isyms_min == static_cast<Label>(isyms.size()));
std::set<Label> isyms_set;
if (!isyms_consecutive)
for (size_t i = 0;i < isyms.size();i++)
isyms_set.insert(isyms[i]);
for (StateIterator<MutableFst<Arc> > siter(*fst); ! siter.Done(); siter.Next()) {
StateId state = siter.Value();
for (MutableArcIterator<MutableFst<Arc> > aiter(fst, state); ! aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel >= isyms_min && arc.ilabel <= isyms_max) {
if (isyms_consecutive || isyms_set.count(arc.ilabel) != 0) {
num_deleted++;
Arc mod_arc (arc);
mod_arc.ilabel = 0; // change label to epsilon.
aiter.SetValue(mod_arc);
}
}
}
}
return num_deleted;
}
template<class Arc>
typename Arc::StateId CreateSuperFinal(MutableFst<Arc> *fst) {
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
assert(fst != NULL);
StateId num_states = fst->NumStates();
std::vector<StateId> final_states;
for (StateId s = 0; s < num_states; s++) {
if (fst->Final(s) != Weight::Zero()) {
final_states.push_back(s);
}
}
if (final_states.size() == 1) {
if (fst->Final(final_states[0]) == Weight::One()) {
ArcIterator<MutableFst<Arc> > iter(*fst, final_states[0]);
if (iter.Done()) {
// We already have a final state w/ no transitions out and unit weight.
// So we're done.
return final_states[0];
}
}
}
StateId final_state = fst->AddState();
fst->SetFinal(final_state, Weight::One());
for (size_t idx = 0; idx < final_states.size(); idx++) {
StateId s = final_states[idx];
Weight weight = fst->Final(s);
fst->SetFinal(s, Weight::Zero());
Arc arc;
arc.ilabel = 0;
arc.olabel = 0;
arc.nextstate = final_state;
arc.weight = weight;
fst->AddArc(s, arc);
}
return final_state;
}
} // namespace fst
#endif // KALDI_FSTEXT_PRE_DETERMINIZE_INL_H_
@@ -0,0 +1,220 @@
// fstext/pre-determinize-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-math.h"
#include "fstext/pre-determinize.h"
#include "fstext/fst-test-utils.h"
#include "fstext/fstext-utils.h"
// Just check that it compiles, for now.
namespace fst
{
using std::vector;
using std::cout;
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestPreDeterminize() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr = NULL;
vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++)
all_syms.push_back(i);
// Create states.
vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> *fst_copy_orig = new VectorFst<Arc>(*fst);
vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
typename Arc::Label highest_sym = HighestNumberedInputSymbol(*fst);
PreDeterminize(fst, highest_sym+1, &extra_syms);
}
std::cout <<" printing after predeterminization\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
{ // Remove epsilon. All default args.
bool connect = true;
Weight weight_threshold = Weight::Zero();
int64 nstate = -1; // Relates to pruning.
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
// I guess. But with no epsilon cycles, probably doensn't matter.
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
}
std::cout <<" printing after epsilon removal\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst;
DeterminizeOptions<Arc> opts; // Default options.
Determinize(*fst, &ofst, opts);
std::cout <<" printing after determinization\n";
{
FstPrinter<Arc> fstprinter(ofst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
int64 num_removed = DeleteISymbols(&ofst, extra_syms);
std::cout <<" printing after removing "<<num_removed<<" instances of extra symbols\n";
{
FstPrinter<Arc> fstprinter(ofst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
std::cout <<" Checking equivalent to original FST.\n";
// giving Rand() as a seed stops the random number generator from always being reset to
// the same point each time, while maintaining determinism of the test.
assert(RandEquivalent(ofst, *fst_copy_orig, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
delete fst_copy_orig;
}
template<class Arc> void TestAddSelfLoops() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
SymbolTable *ilabels = new SymbolTable("my-symbol-table");
SymbolTable *olabels = new SymbolTable("my-symbol-table-2");
Label i0 = ilabels->AddSymbol("<eps>");
Label i1 = ilabels->AddSymbol("1");
Label i2 = ilabels->AddSymbol("2");
Label o0 = olabels->AddSymbol("<eps>");
Label o1 = olabels->AddSymbol("1");
assert(i0 == 0 && o0 == 0);
StateId s0 = fst->AddState(), s1 = fst->AddState(), s2 = fst->AddState();
fst->SetStart(s0);
assert(s0 == 0);
fst->SetFinal(s2, (Weight)2); // state 2 is final.
{
Arc arc;
arc.ilabel = i1;
arc.olabel = o0;
arc.nextstate = 1;
arc.weight = (Weight)1;
fst->AddArc(s0, arc); // arc from 0 to 1 with epsilon out.
}
{
Arc arc;
arc.ilabel = i2;
arc.olabel = o1;
arc.nextstate = 2;
arc.weight = (Weight)2;
fst->AddArc(s1, arc); // arc from 1 to 2 with "1" out.
}
std::cout <<" printing before adding self-loops\n";
{
FstPrinter<Arc> fstprinter(*fst, ilabels, olabels, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// So states 1 and 2 should have self-loops on.
size_t num_extra = kaldi::Rand() % 5;
vector<Label> extra_ilabels, extra_olabels;
CreateNewSymbols(ilabels, num_extra, "in#", &extra_ilabels);
CreateNewSymbols(olabels, num_extra, "out#", &extra_olabels);
AddSelfLoops(fst, extra_ilabels, extra_olabels);
assert(fst->NumArcs(0) == 1);
assert(fst->NumArcs(1) == 1 + num_extra);
assert(fst->NumArcs(2) == num_extra);
std::cout <<" printing after adding self-loops\n";
{
FstPrinter<Arc> fstprinter(*fst, ilabels, olabels, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
delete fst;
delete ilabels;
delete olabels;
}
} // end namespace fst.
int main() {
for (int i = 0;i < 10;i++) { // run it multiple times; it's a randomized testing algorithm.
fst::TestPreDeterminize<fst::StdArc>();
}
for (int i = 0;i < 5;i++) {
fst::TestAddSelfLoops<fst::StdArc>();
}
}
@@ -0,0 +1,96 @@
// fstext/pre-determinize.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_PRE_DETERMINIZE_H_
#define KALDI_FSTEXT_PRE_DETERMINIZE_H_
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "base/kaldi-common.h"
namespace fst {
/* PreDeterminize inserts extra symbols on the input side of an FST as necessary to
ensure that, after epsilon removal, it will be compactly determinizable by the
determinize* algorithm. By compactly determinizable we mean that
no original FST state is represented in more than one determinized state).
Caution: this code is now only used in testing.
The new symbols start from the value "first_new_symbol", which should be
higher than the largest-numbered symbol currently in the FST. The new
symbols added are put in the array syms_out, which should be empty at start.
*/
template<class Arc, class Int>
void PreDeterminize(MutableFst<Arc> *fst,
typename Arc::Label first_new_symbol,
std::vector<Int> *syms_out);
/* CreateNewSymbols is a helper function used inside PreDeterminize, and is also useful
when you need to add a number of extra symbols to a different vocabulary from the one
modified by PreDeterminize. */
template<class Label>
void CreateNewSymbols(SymbolTable *inputSymTable, int nSym,
std::string prefix, std::vector<Label> *syms_out);
/** AddSelfLoops is a function you will probably want to use alongside PreDeterminize,
to add self-loops to any FSTs that you compose on the left hand side of the one
modified by PreDeterminize.
This function inserts loops with "special symbols" [e.g. \#0, \#1] into an FST.
This is done at each final state and each state with non-epsilon output symbols on
at least one arc out of it. This is to ensure that these symbols, when inserted into
the input side of an FST we will compose with on the right, can "pass through" this
FST.
At input, isyms and osyms must be vectors of the same size n, corresponding
to symbols that currently do not exist in 'fst'. For each state in n that has
non-epsilon symbols on the output side of arcs leaving it, or which is a final state,
this function inserts n self-loops with unit weight and one of the n pairs
of symbols on its input and output.
*/
template<class Arc>
void AddSelfLoops(MutableFst<Arc> *fst, std::vector<typename Arc::Label> &isyms,
std::vector<typename Arc::Label> &osyms);
/* DeleteSymbols replaces any instances of symbols in the vector symsIn, appearing
on the input side, with epsilon. */
/* It returns the number of instances of symbols deleted. */
template<class Arc>
int64 DeleteISymbols(MutableFst<Arc> *fst, std::vector<typename Arc::Label> symsIn);
/* CreateSuperFinal takes an FST, and creates an equivalent FST with a single final
state with no transitions out and unit final weight, by inserting epsilon transitions
as necessary. */
template<class Arc>
typename Arc::StateId CreateSuperFinal(MutableFst<Arc> *fst);
} // end namespace fst
#include "fstext/pre-determinize-inl.h"
#endif
@@ -0,0 +1,173 @@
// fstext/prune-special-inl.h
// Copyright 2014 Johns Hopkins University (Author: Daniel Povey)
// Guoguo Chen
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_PRUNE_SPECIAL_INL_H_
#define KALDI_FSTEXT_PRUNE_SPECIAL_INL_H_
// Do not include this file directly. It is included by prune-special.h
#include "fstext/prune-special.h"
#include "base/kaldi-error.h"
namespace fst {
/// This class is used to implement the function PruneSpecial.
template<class Arc> class PruneSpecialClass {
public:
typedef typename Arc::StateId InputStateId;
typedef typename Arc::StateId OutputStateId;
typedef typename Arc::Weight Weight;
typedef typename Arc::Label Label;
PruneSpecialClass(const Fst<Arc> &ifst,
VectorFst<Arc> *ofst,
Weight beam,
size_t max_states):
ifst_(ifst), ofst_(ofst), beam_(beam), max_states_(max_states),
best_weight_(Weight::Zero()) {
KALDI_ASSERT(beam != Weight::One());
KALDI_ASSERT(queue_.size() == 0);
ofst_->DeleteStates(); // make sure it's empty.
if (ifst_.Start() == kNoStateId)
return;
ofst_->SetStart(ProcessState(ifst_.Start(), Weight::One()));
while (!queue_.empty()) {
Task task = queue_.top();
queue_.pop();
if (Done(task)) break;
else ProcessTask(task);
}
Connect(ofst);
if (beam_ != Weight::One())
Prune(ofst, beam_);
}
struct Task {
InputStateId istate;
OutputStateId ostate; // could be looked up; this is for speed.
size_t position; // arc position, or -1 if final-prob.
Weight weight;
Task(InputStateId istate, OutputStateId ostate, size_t position,
Weight weight): istate(istate), ostate(ostate), position(position),
weight(weight) { }
bool operator < (const Task &other) const {
return Compare(weight, other.weight) < 0;
}
};
bool Done(const Task &task) {
if (beam_ != Weight::One() && best_weight_ != Weight::Zero() &&
Compare(task.weight, Times(best_weight_, beam_)) < 0)
return true;
if (max_states_ > 0 &&
static_cast<size_t>(ofst_->NumStates()) > max_states_)
return true;
return false;
}
// This function assumes "state" has not been seen before, so we need to
// create a new output-state for it and add tasks. It returns the
// output-state id. "weight" is the best cost from the start-state to this
// state.
inline OutputStateId ProcessState(InputStateId istate, const Weight &weight) {
OutputStateId ostate = ofst_->AddState();
state_map_[istate] = ostate;
for (ArcIterator<Fst<Arc> > aiter(ifst_, istate); !aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
Task new_task(istate, ostate, aiter.Position(),
Times(weight, arc.weight));
KALDI_ASSERT(Compare(arc.weight, Weight::One()) != 1);
queue_.push(new_task);
}
Weight final = ifst_.Final(istate);
if (final != Weight::Zero()) {
Task final_task(istate, ostate, static_cast<size_t>(-1),
Times(weight, final));
KALDI_ASSERT(Compare(final, Weight::One()) != 1);
queue_.push(final_task);
}
return ostate;
}
// Returns the output-state id corresponding to "istate". This assumes we are
// processing a task corresponding to an arc to "istate", and the cost from
// the start-state to this state is "weight". Since we process tasks in
// order, if this is the first time we see this istate, then this is the best
// cost from the start-state to this state, and it can be used in setting the
// priority costs in ProcessState().
inline OutputStateId GetOutputStateId(InputStateId istate,
const Weight &weight) {
typedef typename unordered_map<InputStateId, OutputStateId>::iterator IterType;
IterType iter = state_map_.find(istate);
if (iter == state_map_.end())
return ProcessState(istate, weight);
else
return iter->second;
}
void ProcessTask(const Task &task) {
if (task.position == static_cast<size_t>(-1)) {
ofst_->SetFinal(task.ostate, ifst_.Final(task.istate));
if (best_weight_ == Weight::Zero())
best_weight_ = task.weight; // best-path cost through FST, used for
// beam-pruning.
} else {
ArcIterator<Fst<Arc> > aiter(ifst_, task.istate);
aiter.Seek(task.position); // if we spend most of our time here, we may
// need to store the arc in the Task.
const Arc &arc = aiter.Value();
InputStateId next_istate = arc.nextstate;
OutputStateId next_ostate = GetOutputStateId(next_istate, task.weight);
Arc oarc(arc.ilabel, arc.olabel, arc.weight, next_ostate);
ofst_->AddArc(task.ostate, oarc);
}
}
private:
const Fst<Arc> &ifst_;
VectorFst<Arc> *ofst_;
Weight beam_;
size_t max_states_;
unordered_map<InputStateId, OutputStateId> state_map_;
std::priority_queue<Task> queue_;
Weight best_weight_; // if not Zero(), then we have now processed a successful path
// through ifst_, and this is the weight.
};
template<class Arc>
void PruneSpecial(const Fst<Arc> &ifst,
VectorFst<Arc> *ofst,
typename Arc::Weight beam,
size_t max_states) {
PruneSpecialClass<Arc> c(ifst, ofst, beam, max_states);
}
}
#endif
@@ -0,0 +1,79 @@
// fstext/prune-special-test.cc
// Copyright 2014 Guoguo Chen
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/prune-special.h"
#include "fstext/rand-fst.h"
#include "fstext/fstext-utils.h"
namespace fst {
static void TestPruneSpecial() {
typedef StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
RandFstOptions opts;
opts.acyclic = false;
VectorFst<Arc> *ifst = RandFst<StdArc>(opts);
float beam = 0.55;
{
FstPrinter<Arc> fstprinter(*ifst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
std::cout << std::endl;
}
// Do the special pruning.
VectorFst<Arc> ofst1;
PruneSpecial<StdArc>(*ifst, &ofst1, beam);
{
FstPrinter<Arc> fstprinter(ofst1, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
std::cout << std::endl;
}
// Do the normal pruning.
VectorFst<Arc> ofst2;
Prune(*ifst, &ofst2, beam);
{
FstPrinter<Arc> fstprinter(ofst2, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
std::cout << std::endl;
}
KALDI_ASSERT(RandEquivalent(ofst1, ofst2,
5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/,
100/*path length-- max?*/));
delete ifst;
}
} // namespace fst
int main() {
kaldi::g_kaldi_verbose_level = 4;
using namespace fst;
for (int i = 0; i < 25; i++) {
TestPruneSpecial();
}
}
@@ -0,0 +1,60 @@
// fstext/prune-special.h
// Copyright 2014 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_PRUNE_SPECIAL_H_
#define KALDI_FSTEXT_PRUNE_SPECIAL_H_
#include "fst/fstlib.h"
#include "fstext/lattice-weight.h"
#include "fstext/factor.h"
namespace fst {
/**
The function PruneSpecial is like the standard OpenFst function "prune",
except it does not expand the entire "ifst"- this is useful for cases where
ifst is an on-demand FST such as a ComposeFst and we don't want to visit
it all. It supports pruning either to a specified beam (if beam is
not One()), or to a specified max_states (if max_states is > 0). One of the
two must be specified.
Requirements:
- Costs must be non-negative (equivalently, weights must not be greater than One()).
- There must be a Compare(a, b) function that compares two weights and returns (-1,0,1)
if (a<b, a=b, a>b). We define this in Kaldi, for TropicalWeight, LogWeight (I think),
and LatticeWeight... also CompactLatticeWeight, but we doubt that will be used here;
better to use PruneCompactLattice().
*/
template<class Arc>
void PruneSpecial(const Fst<Arc> &ifst,
VectorFst<Arc> *ofst,
typename Arc::Weight beam,
size_t max_states = 0);
} // end namespace fst
#include "fstext/prune-special-inl.h"
#endif // KALDI_FSTEXT_PRUNE_SPECIAL_H_
@@ -0,0 +1,81 @@
// fstext/push-special-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/push-special.h"
#include "fstext/rand-fst.h"
#include "fstext/fstext-utils.h"
#include "base/kaldi-math.h"
namespace fst
{
// Don't instantiate with log semiring, as RandEquivalent may fail.
static void TestPushSpecial() {
typedef StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
VectorFst<Arc> *fst = RandFst<StdArc>();
{
FstPrinter<Arc> fstprinter(*fst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> fst_copy(*fst);
float delta = kDelta;
PushSpecial(&fst_copy, delta);
Weight min, max;
float delta_dontcare = 0.1;
IsStochasticFstInLog(fst_copy, delta_dontcare, &min, &max);
// the per-state normalizers are allowed to deviate from the average by delta
// up and down, so the difference from the min to max weight should be 2*delta
// or less. We give it a bit of wiggle room (->2.5) due to numerical roundoff.
{
FstPrinter<Arc> fstprinter(fst_copy, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
KALDI_LOG << "Min value is " << min.Value() << ", max value is " << max.Value();
// below, should be <= delta but different pieces of code compute this in this
// part vs. push-special, so the roundoff may be different.
KALDI_ASSERT(std::abs(min.Value() - max.Value()) <= 1.2 * delta);
KALDI_ASSERT(RandEquivalent(*fst, fst_copy,
5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
delete fst;
}
} // namespace fst
int main() {
kaldi::g_kaldi_verbose_level = 4;
using namespace fst;
for (int i = 0; i < 25; i++) {
TestPushSpecial();
}
}
@@ -0,0 +1,269 @@
// fstext/push-special.cc
// Copyright 2012 Johns Hopkins University (author: Daniel Povey)
// 2012 Ehsan Variani, Pegah Ghahrmani
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/push-special.h"
#include "base/kaldi-error.h"
#include "base/kaldi-math.h"
namespace fst {
/*
This algorithm was briefly described in "COMBINING FORWARD AND BACKWARD SEARCH
IN DECODING" by Hannemann and Povey, ICASSP 2013,
http://www.danielpovey.com/files/2013_icassp_pingpong.pdf
Below is the most relevant excerpt of the LaTeX source.
Real backoff language models represented as WFSTs (\cite{Mohri:08}) will not
exactly sum to one because the backoff structure leads to duplicate paths for
some word sequences. In fact, such language models cannot be pushed at all in
the general case, because the total weight of the entire WFST may not be finite.
For our language model reversal we need a suitable pushing operation that will
always succeed.
Our solution is to require a modified pushing operation such that each state
``sums to'' the same quantity.
We were able to find an iterative algorithm that does this very efficiently in practice;
it is based on the power method for finding the top eigenvalue of a matrix.
Both for the math and the implementation, we find it more convenient to
use the probability semiring, i.e. we represent the transition-probabilities
as actual probabilities, not negative logs.
Let the transitions be written as a sparse matrix $\mathbf{P}$,
where $p_{ij}$ is the sum of all the probabilities of transitions between state $i$ and state $j$.
As a special case, if $j$ is the initial state,
then $p_{ij}$ is the final-probability of state $i$.
In our method we find the dominant eigenvector $\mathbf{v}$ of the matrix $\mathbf{P}$,
by starting from a random positive vector and iterating with the power method:
each time we let $\mathbf{v} \leftarrow \mathbf{P} \mathbf{v}$
and then renormalize the length of $\mathbf{v}$.
It is convenient to renormalize $\mathbf{v}$ so that $v_I$ is 1,
where $I$ is the initial state of the WFST\footnote{Note: in order to correctly
deal with the case of linear WFSTs, which have different eigenvalues
with the same magnitude but different complex phase,
we modify the iteration to $\mathbf{v} \leftarrow \mathbf{P} \mathbf{v} + 0.1 \mathbf{v}$.}.
This generally converges within several tens of iterations.
At the end we have a vector $\mathbf{v}$ with $v_I = 1$, and a scalar $\lambda > 0$, such that
\begin{equation}
\lambda \mathbf{v} = \mathbf{P} \mathbf{v} . \label{eqn:lambdav}
\end{equation}
Suppose we compute a modified transition matrix $\mathbf{P}'$, by letting
\begin{equation}
p'_{ij} = p_{ij} v_j / v_i .
\end{equation}
Then it is easy to show each row of $\mathbf{P}'$ sums to $\lambda$:
writing one element of Eq.~\ref{eqn:lambdav} as
\begin{equation}
\lambda v_i = \sum_j p_{ij} v_j,
\end{equation}
it easily follows that $\lambda = \sum_j p'_{ij}$.
We need to perform a similar transformation on the transition-probabilities and
final-probabilities of the WFST; the details are quite obvious, and the
equivalence with the original WFST is easy to show. Our algorithm is in
practice an order of magnitude faster than the more generic algorithm for
conventional weight-pushing of \cite{Mohri:02}, when applied to cyclic WFSTs.
*/
class PushSpecialClass {
typedef StdArc Arc;
typedef Arc::Weight Weight;
typedef Arc::StateId StateId;
public:
// Everything happens in the initializer.
PushSpecialClass(VectorFst<StdArc> *fst,
float delta): fst_(fst) {
num_states_ = fst_->NumStates();
initial_state_ = fst_->Start();
occ_.resize(num_states_, 1.0 / sqrt(num_states_)); // unit length
pred_.resize(num_states_);
for (StateId s = 0; s < num_states_; s++) {
for (ArcIterator<VectorFst<StdArc> > aiter(*fst, s);
!aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
StateId t = arc.nextstate;
double weight = kaldi::Exp(-arc.weight.Value());
pred_[t].push_back(std::make_pair(s, weight));
}
double final = kaldi::Exp(-fst_->Final(s).Value());
if (final != 0.0)
pred_[initial_state_].push_back(std::make_pair(s, final));
}
Iterate(delta);
ModifyFst();
}
private:
double TestAccuracy() { // returns the error (the difference
// between the min and max weights).
double min_sum = 0, max_sum = 0;
for (StateId s = 0; s < num_states_; s++) {
double sum = 0.0;
for (ArcIterator<VectorFst<StdArc> > aiter(*fst_, s);
!aiter.Done(); aiter.Next()) {
const Arc &arc = aiter.Value();
StateId t = arc.nextstate;
sum += kaldi::Exp(-arc.weight.Value()) * occ_[t] / occ_[s];
}
sum += kaldi::Exp(-(fst_->Final(s).Value())) * occ_[initial_state_] / occ_[s];
if (s == 0) {
min_sum = sum;
max_sum = sum;
} else {
min_sum = std::min(min_sum, sum);
max_sum = std::max(max_sum, sum);
}
}
KALDI_VLOG(4) << "min,max is " << min_sum << " " << max_sum;
return kaldi::Log(max_sum / min_sum); // In FST world we'll actually
// dealing with logs, so the log of the ratio is more suitable
// to compare with delta (makes testing the algorithm easier).
}
void Iterate(float delta) {
// This is like the power method to find the top eigenvalue of a matrix.
// We limit it to 200 iters max, just in case something unanticipated
// happens, but we should exit due to the "delta" thing, usually after
// several tens of iterations.
int iter, max_iter = 200;
for (iter = 0; iter < max_iter; iter++) {
std::vector<double> new_occ(num_states_);
// We initialize new_occ to 0.1 * occ. A simpler algorithm would
// initialize them to zero, so it's like the pure power method. This is
// like the power method on (M + 0.1 I), and we do it this way to avoid a
// problem we encountered with certain very simple linear FSTs where the
// eigenvalues of the weight matrix (including negative and imaginary
// ones) all have the same magnitude.
for (int i = 0; i < num_states_; i++)
new_occ[i] = 0.1 * occ_[i];
for (int i = 0; i < num_states_; i++) {
std::vector<std::pair<StateId, double> >::const_iterator iter,
end = pred_[i].end();
for (iter = pred_[i].begin(); iter != end; ++iter) {
StateId j = iter->first;
double p = iter->second;
new_occ[j] += occ_[i] * p;
}
}
double sumsq = 0.0;
for (int i = 0; i < num_states_; i++) sumsq += new_occ[i] * new_occ[i];
lambda_ = std::sqrt(sumsq);
double inv_lambda = 1.0 / lambda_;
for (int i = 0; i < num_states_; i++) occ_[i] = new_occ[i] * inv_lambda;
KALDI_VLOG(4) << "Lambda is " << lambda_;
if (iter % 5 == 0 && iter > 0 && TestAccuracy() <= delta) {
KALDI_VLOG(3) << "Weight-pushing converged after " << iter
<< " iterations.";
return;
}
}
KALDI_WARN << "push-special: finished " << iter
<< " iterations without converging. Output will be inaccurate.";
}
// Modifies the FST weights and the final-prob to take account of these potentials.
void ModifyFst() {
// First get the potentials as negative-logs, like the values
// in the FST.
for (StateId s = 0; s < num_states_; s++) {
occ_[s] = -kaldi::Log(occ_[s]);
if (KALDI_ISNAN(occ_[s]) || KALDI_ISINF(occ_[s]))
KALDI_WARN << "NaN or inf found: " << occ_[s];
}
for (StateId s = 0; s < num_states_; s++) {
for (MutableArcIterator<VectorFst<StdArc> > aiter(fst_, s);
!aiter.Done(); aiter.Next()) {
Arc arc = aiter.Value();
StateId t = arc.nextstate;
arc.weight = Weight(arc.weight.Value() + occ_[t] - occ_[s]);
aiter.SetValue(arc);
}
fst_->SetFinal(s, Times(fst_->Final(s).Value(),
Weight(occ_[initial_state_] - occ_[s])));
}
}
private:
StateId num_states_;
StateId initial_state_;
std::vector<double> occ_; // the top eigenvector of (matrix of weights) transposed.
double lambda_; // our current estimate of the top eigenvalue.
std::vector<std::vector<std::pair<StateId, double> > > pred_; // List of transitions
// into each state. For the start state, this list consists of the list of
// states with final-probs, each with their final prob.
VectorFst<StdArc> *fst_;
};
void PushSpecial(VectorFst<StdArc> *fst, float delta) {
if (fst->NumStates() > 0)
PushSpecialClass c(fst, delta); // all the work
// gets done in the initializer.
}
} // end namespace fst.
/*
Note: in testing an earlier, simpler
version of this method (without the 0.1 * old_occ) we had a problem with the following FST.
0 2 3 3 0
1 3 1 4 0.5
2 1 0 0 0.5
3 0.25
Corresponds to the following matrix [or maybe its transpose, doesn't matter
probably]
a=exp(-0.5)
b=exp(-0.25)
M = [ 0 1 0 0
0 0 a 0
0 0 0 a
b 0 0 0 ]
eigs(M)
eigs(M)
ans =
-0.0000 - 0.7316i
-0.0000 + 0.7316i
0.7316
-0.7316
OK, so the issue appears to be that all the eigenvalues of this matrix
have the same magnitude. The solution is to work with the eigenvalues
of M + alpha I, for some small alpha such as 0.1 (so as not to slow down
convergence in the normal case).
*/
@@ -0,0 +1,42 @@
// fstext/push-special.h
// Copyright 2012-2015 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_PUSH_SPECIAL_H_
#define KALDI_FSTEXT_PUSH_SPECIAL_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "util/const-integer-set.h"
namespace fst {
/*
This function does weight-pushing, in the log semiring,
but in a special way, such that any "leftover weight" after pushing
gets distributed evenly along the FST, and doesn't end up either
at the start or at the end. Basically it pushes the weights such
that the total weight of each state (i.e. the sum of the arc
probabilities plus the final-prob) is the same for all states.
*/
void PushSpecial(VectorFst<StdArc> *fst,
float delta = kDelta);
}
#endif
+162
View File
@@ -0,0 +1,162 @@
// fstext/rand-fst.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_RAND_FST_H_
#define KALDI_FSTEXT_RAND_FST_H_
#include <sstream>
#include <string>
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
#include "base/kaldi-math.h"
namespace fst {
// Note: all weights are constructed from nonnegative floats.
// (so no "negative costs").
struct RandFstOptions {
size_t n_syms;
size_t n_states;
size_t n_arcs;
size_t n_final;
bool allow_empty;
bool acyclic;
float weight_multiplier;
RandFstOptions() { // Initializes the options randomly.
n_syms = 2 + kaldi::Rand() % 5;
n_states = 3 + kaldi::Rand() % 10;
n_arcs = 5 + kaldi::Rand() % 30;
n_final = 1 + kaldi::Rand()%3;
allow_empty = true;
acyclic = false;
weight_multiplier = 0.25;
}
};
/// Returns a random FST. Useful for randomized algorithm testing.
/// Only works if weight can be constructed from float.
template<class Arc> VectorFst<Arc>* RandFst(RandFstOptions opts = RandFstOptions() ) {
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
start:
// Create states.
std::vector<StateId> all_states;
for (size_t i = 0;i < (size_t)opts.n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)opts.n_final;j++) {
StateId id = all_states[kaldi::Rand() % opts.n_states];
Weight weight = (Weight)(opts.weight_multiplier*(kaldi::Rand() % 5));
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)opts.n_arcs;i++) {
Arc a;
StateId start_state;
if(!opts.acyclic) { // no restriction on arcs.
start_state = all_states[kaldi::Rand() % opts.n_states];
a.nextstate = all_states[kaldi::Rand() % opts.n_states];
} else {
start_state = all_states[kaldi::Rand() % (opts.n_states-1)];
a.nextstate = start_state + 1 + (kaldi::Rand() % (opts.n_states-start_state-1));
}
a.ilabel = kaldi::Rand() % opts.n_syms;
a.olabel = kaldi::Rand() % opts.n_syms; // same input+output vocab.
a.weight = (Weight) (opts.weight_multiplier*(kaldi::Rand() % 4));
fst->AddArc(start_state, a);
}
// Trim resulting FST.
Connect(fst);
if (opts.acyclic)
assert(fst->Properties(kAcyclic, true) & kAcyclic);
if (fst->Start() == kNoStateId && !opts.allow_empty) {
goto start;
}
return fst;
}
/// Returns a random FST. Useful for randomized algorithm testing.
/// Only works if weight can be constructed from a pair of floats
template<class Arc> VectorFst<Arc>* RandPairFst(RandFstOptions opts = RandFstOptions() ) {
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
start:
// Create states.
std::vector<StateId> all_states;
for (size_t i = 0;i < (size_t)opts.n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0; j < (size_t)opts.n_final;j++) {
StateId id = all_states[kaldi::Rand() % opts.n_states];
Weight weight (opts.weight_multiplier*(kaldi::Rand() % 5), opts.weight_multiplier*(kaldi::Rand() % 5));
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)opts.n_arcs;i++) {
Arc a;
StateId start_state;
if(!opts.acyclic) { // no restriction on arcs.
start_state = all_states[kaldi::Rand() % opts.n_states];
a.nextstate = all_states[kaldi::Rand() % opts.n_states];
} else {
start_state = all_states[kaldi::Rand() % (opts.n_states-1)];
a.nextstate = start_state + 1 + (kaldi::Rand() % (opts.n_states-start_state-1));
}
a.ilabel = kaldi::Rand() % opts.n_syms;
a.olabel = kaldi::Rand() % opts.n_syms; // same input+output vocab.
a.weight = Weight (opts.weight_multiplier*(kaldi::Rand() % 4), opts.weight_multiplier*(kaldi::Rand() % 4));
fst->AddArc(start_state, a);
}
// Trim resulting FST.
Connect(fst);
if (opts.acyclic)
assert(fst->Properties(kAcyclic, true) & kAcyclic);
if (fst->Start() == kNoStateId && !opts.allow_empty) {
goto start;
}
return fst;
}
} // end namespace fst.
#endif
@@ -0,0 +1,321 @@
// fstext/remove-eps-local-inl.h
// Copyright 2009-2011 Microsoft Corporation
// 2014 Johns Hopkins University (author: Daniel Povey
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_REMOVE_EPS_LOCAL_INL_H_
#define KALDI_FSTEXT_REMOVE_EPS_LOCAL_INL_H_
namespace fst {
template<class Weight>
struct ReweightPlusDefault {
inline Weight operator () (const Weight &a, const Weight &b) {
return Plus(a, b);
}
};
struct ReweightPlusLogArc {
inline TropicalWeight operator () (const TropicalWeight &a,
const TropicalWeight &b) {
LogWeight a_log(a.Value()), b_log(b.Value());
return TropicalWeight(Plus(a_log, b_log).Value());
}
};
template<class Arc, class ReweightPlus = ReweightPlusDefault<typename Arc::Weight> >
class RemoveEpsLocalClass {
typedef typename Arc::StateId StateId;
typedef typename Arc::Label Label;
typedef typename Arc::Weight Weight;
public:
RemoveEpsLocalClass(MutableFst<Arc> *fst):
fst_(fst) {
if (fst_->Start() == kNoStateId) return; // empty.
non_coacc_state_ = fst_->AddState();
InitNumArcs();
StateId num_states = fst_->NumStates();
for (StateId s = 0; s < num_states; s++)
for (size_t pos = 0; pos < fst_->NumArcs(s); pos++)
RemoveEps(s, pos);
assert(CheckNumArcs());
Connect(fst); // remove inaccessible states.
}
private:
MutableFst<Arc> *fst_;
StateId non_coacc_state_; // use this to delete arcs: make it nextstate
std::vector<StateId> num_arcs_in_; // The number of arcs into the state, plus one
// if it's the start state.
std::vector<StateId> num_arcs_out_; // The number of arcs out of the state, plus
// one if it's a final state.
ReweightPlus reweight_plus_;
bool CanCombineArcs(const Arc &a, const Arc &b, Arc *c) {
if (a.ilabel != 0 && b.ilabel != 0) return false;
if (a.olabel != 0 && b.olabel != 0) return false;
c->weight = Times(a.weight, b.weight);
c->ilabel = (a.ilabel != 0 ? a.ilabel : b.ilabel);
c->olabel = (a.olabel != 0 ? a.olabel : b.olabel);
c->nextstate = b.nextstate;
return true;
}
static bool CanCombineFinal(const Arc &a, Weight final_prob, Weight *final_prob_out) {
if (a.ilabel != 0 || a.olabel != 0) return false;
else {
*final_prob_out = Times(a.weight, final_prob);
return true;
}
}
void InitNumArcs() { // init num transitions in/out of each state.
StateId num_states = fst_->NumStates();
num_arcs_in_.resize(num_states);
num_arcs_out_.resize(num_states);
num_arcs_in_[fst_->Start()]++; // count start as trans in.
for (StateId s = 0; s < num_states; s++) {
if (fst_->Final(s) != Weight::Zero())
num_arcs_out_[s]++; // count final as transition.
for (ArcIterator<MutableFst<Arc> > aiter(*fst_, s); !aiter.Done(); aiter.Next()) {
num_arcs_in_[aiter.Value().nextstate]++;
num_arcs_out_[s]++;
}
}
}
bool CheckNumArcs() { // check num arcs in/out of each state, at end. Debug.
num_arcs_in_[fst_->Start()]--; // count start as trans in.
StateId num_states = fst_->NumStates();
for (StateId s = 0; s < num_states; s++) {
if (s == non_coacc_state_) continue;
if (fst_->Final(s) != Weight::Zero())
num_arcs_out_[s]--; // count final as transition.
for (ArcIterator<MutableFst<Arc> > aiter(*fst_, s); !aiter.Done(); aiter.Next()) {
if (aiter.Value().nextstate == non_coacc_state_) continue;
num_arcs_in_[aiter.Value().nextstate]--;
num_arcs_out_[s]--;
}
}
for (StateId s = 0; s < num_states; s++) {
assert(num_arcs_in_[s] == 0);
assert(num_arcs_out_[s] == 0);
}
return true; // always does this. so we can assert it w/o warnings.
}
inline void GetArc(StateId s, size_t pos, Arc *arc) const {
ArcIterator<MutableFst<Arc> > aiter(*fst_, s);
aiter.Seek(pos);
*arc = aiter.Value();
}
inline void SetArc(StateId s, size_t pos, const Arc &arc) {
MutableArcIterator<MutableFst<Arc> > aiter(fst_, s);
aiter.Seek(pos);
aiter.SetValue(arc);
}
void Reweight(StateId s, size_t pos, Weight reweight) {
// Reweight is called from RemoveEpsPattern1; it is a step we
// do to preserve stochasticity. This function multiplies the
// arc at (s, pos) by reweight and divides all the arcs [+final-prob]
// out of the next state by the same. This is only valid if
// the next state has only one arc in and is not the start state.
assert(reweight != Weight::Zero());
MutableArcIterator<MutableFst<Arc> > aiter(fst_, s);
aiter.Seek(pos);
Arc arc = aiter.Value();
assert(num_arcs_in_[arc.nextstate] == 1);
arc.weight = Times(arc.weight, reweight);
aiter.SetValue(arc);
for (MutableArcIterator<MutableFst<Arc> > aiter_next(fst_, arc.nextstate);
!aiter_next.Done();
aiter_next.Next()) {
Arc nextarc = aiter_next.Value();
if (nextarc.nextstate != non_coacc_state_) {
nextarc.weight = Divide(nextarc.weight, reweight, DIVIDE_LEFT);
aiter_next.SetValue(nextarc);
}
}
Weight final = fst_->Final(arc.nextstate);
if (final != Weight::Zero()) {
fst_->SetFinal(arc.nextstate, Divide(final, reweight, DIVIDE_LEFT));
}
}
// RemoveEpsPattern1 applies where this arc, which is not a
// self-loop, enters a state which has only one input transition
// [and is not the start state], and has multiple output
// transitions [counting being the final-state as a final-transition].
void RemoveEpsPattern1(StateId s, size_t pos, Arc arc) {
const StateId nextstate = arc.nextstate;
Weight total_removed = Weight::Zero(),
total_kept = Weight::Zero(); // totals out of nextstate.
std::vector<Arc> arcs_to_add; // to add to state s.
for (MutableArcIterator<MutableFst<Arc> > aiter_next(fst_, nextstate);
!aiter_next.Done();
aiter_next.Next()) {
Arc nextarc = aiter_next.Value();
if (nextarc.nextstate == non_coacc_state_) continue; // deleted.
Arc combined;
if (CanCombineArcs(arc, nextarc, &combined)) {
total_removed = reweight_plus_(total_removed, nextarc.weight);
num_arcs_out_[nextstate]--;
num_arcs_in_[nextarc.nextstate]--;
nextarc.nextstate = non_coacc_state_;
aiter_next.SetValue(nextarc);
arcs_to_add.push_back(combined);
} else {
total_kept = reweight_plus_(total_kept, nextarc.weight);
}
}
{ // now final-state.
Weight next_final = fst_->Final(nextstate);
if (next_final != Weight::Zero()) {
Weight new_final;
if (CanCombineFinal(arc, next_final, &new_final)) {
total_removed = reweight_plus_(total_removed, next_final);
if (fst_->Final(s) == Weight::Zero())
num_arcs_out_[s]++; // final is counted as arc.
fst_->SetFinal(s, Plus(fst_->Final(s), new_final));
num_arcs_out_[nextstate]--;
fst_->SetFinal(nextstate, Weight::Zero());
} else {
total_kept = reweight_plus_(total_kept, next_final);
}
}
}
if (total_removed != Weight::Zero()) { // did something...
if (total_kept == Weight::Zero()) { // removed everything: remove arc.
num_arcs_out_[s]--;
num_arcs_in_[arc.nextstate]--;
arc.nextstate = non_coacc_state_;
SetArc(s, pos, arc);
} else {
// Have to reweight.
Weight total = reweight_plus_(total_removed, total_kept);
Weight reweight = Divide(total_kept, total, DIVIDE_LEFT); // <=1
Reweight(s, pos, reweight);
}
}
// Now add the arcs we were going to add.
for (size_t i = 0; i < arcs_to_add.size(); i++) {
num_arcs_out_[s]++;
num_arcs_in_[arcs_to_add[i].nextstate]++;
fst_->AddArc(s, arcs_to_add[i]);
}
}
void RemoveEpsPattern2(StateId s, size_t pos, Arc arc) {
// Pattern 2 is where "nextstate" has only one arc out, counting
// being-the-final-state as an arc, but possibly multiple arcs in.
// Also, nextstate != s.
const StateId nextstate = arc.nextstate;
bool can_delete_next = (num_arcs_in_[nextstate] == 1); // if
// we combine, can delete the corresponding out-arc/final-prob
// of nextstate.
bool delete_arc = false; // set to true if this arc to be deleted.
Weight next_final = fst_->Final(arc.nextstate);
if (next_final != Weight::Zero()) { // nextstate has no actual arcs out, only final-prob.
Weight new_final;
if (CanCombineFinal(arc, next_final, &new_final)) {
if (fst_->Final(s) == Weight::Zero())
num_arcs_out_[s]++; // final is counted as arc.
fst_->SetFinal(s, Plus(fst_->Final(s), new_final));
delete_arc = true; // will delete "arc".
if (can_delete_next) {
num_arcs_out_[nextstate]--;
fst_->SetFinal(nextstate, Weight::Zero());
}
}
} else { // has an arc but no final prob.
MutableArcIterator<MutableFst<Arc> > aiter_next(fst_, nextstate);
assert(!aiter_next.Done());
while (aiter_next.Value().nextstate == non_coacc_state_) {
aiter_next.Next();
assert(!aiter_next.Done());
}
// now aiter_next points to a real arc out of nextstate.
Arc nextarc = aiter_next.Value();
Arc combined;
if (CanCombineArcs(arc, nextarc, &combined)) {
delete_arc = true;
if (can_delete_next) { // do it before we invalidate iterators
num_arcs_out_[nextstate]--;
num_arcs_in_[nextarc.nextstate]--;
nextarc.nextstate = non_coacc_state_;
aiter_next.SetValue(nextarc);
}
num_arcs_out_[s]++;
num_arcs_in_[combined.nextstate]++;
fst_->AddArc(s, combined);
}
}
if (delete_arc) {
num_arcs_out_[s]--;
num_arcs_in_[nextstate]--;
arc.nextstate = non_coacc_state_;
SetArc(s, pos, arc);
}
}
void RemoveEps(StateId s, size_t pos) {
// Tries to do local epsilon-removal for arc sequences starting with this arc
Arc arc;
GetArc(s, pos, &arc);
StateId nextstate = arc.nextstate;
if (nextstate == non_coacc_state_) return; // deleted arc.
if (nextstate == s) return; // don't handle self-loops: too complex.
if (num_arcs_in_[nextstate] == 1 && num_arcs_out_[nextstate] > 1) {
RemoveEpsPattern1(s, pos, arc);
} else if (num_arcs_out_[nextstate] == 1) {
RemoveEpsPattern2(s, pos, arc);
}
}
};
template<class Arc>
void RemoveEpsLocal(MutableFst<Arc> *fst) {
RemoveEpsLocalClass<Arc> c(fst); // work gets done in initializer.
}
void RemoveEpsLocalSpecial(MutableFst<StdArc> *fst) {
// work gets done in initializer.
RemoveEpsLocalClass<StdArc, ReweightPlusLogArc> c(fst);
}
} // end namespace fst.
#endif
@@ -0,0 +1,178 @@
// fstext/remove-eps-local-test.cc
// Copyright 2009-2012 Microsoft Corporation Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/remove-eps-local.h"
#include "fstext/fstext-utils.h"
#include "fstext/fst-test-utils.h"
#include "base/kaldi-math.h"
namespace fst
{
using std::vector;
using std::cout;
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> static void TestRemoveEpsLocal() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> fst;
int n_syms = 2 + kaldi::Rand() % 5, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%10;
SymbolTable symtab("my-symbol-table"), *sptr = &symtab;
vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++) {
std::stringstream ss;
if (i == 0) ss << "<eps>";
else ss<<i;
Label cur_lab = sptr->AddSymbol(ss.str());
assert(cur_lab == (Label)i);
all_syms.push_back(cur_lab);
}
assert(all_syms[0] == 0);
fst.AddState();
int cur_num_states = 1;
for (int i = 0; i < n_arcs; i++) {
StateId src_state = kaldi::Rand() % cur_num_states;
StateId dst_state;
if (kaldi::RandUniform() < 0.1) dst_state = kaldi::Rand() % cur_num_states;
else {
dst_state = cur_num_states++; fst.AddState();
}
Arc arc;
if (kaldi::RandUniform() < 0.3) arc.ilabel = all_syms[kaldi::Rand()%all_syms.size()];
else arc.ilabel = 0;
if (kaldi::RandUniform() < 0.3) arc.olabel = all_syms[kaldi::Rand()%all_syms.size()];
else arc.olabel = 0;
arc.weight = (Weight) (0 + 0.1*(kaldi::Rand() % 5));
arc.nextstate = dst_state;
fst.AddArc(src_state, arc);
}
for (int i = 0; i < n_final; i++) {
fst.SetFinal(kaldi::Rand() % cur_num_states, (Weight) (0 + 0.1*(kaldi::Rand() % 5)));
}
if (kaldi::RandUniform() < 0.8) fst.SetStart(0); // usually leads to nicer examples.
else fst.SetStart(kaldi::Rand() % cur_num_states);
Connect(&fst);
if (fst.Start() == kNoStateId) return; // "Connect" made it empty.
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> fst_copy1(fst);
RemoveEpsLocal(&fst_copy1);
{
std::cout << "copy1 = \n";
FstPrinter<Arc> fstprinter(fst_copy1, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
int num_states_0 = fst.NumStates();
int num_states_1 = fst_copy1.NumStates();
std::cout << "Number of states 0 = "<<num_states_0<<", 1 = "<<num_states_1<<'\n';
assert(RandEquivalent(fst, fst_copy1, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
}
static void TestRemoveEpsLocalSpecial() {
// test that RemoveEpsLocalSpecial preserves equivalence in tropical while
// maintaining stochasticity in log.
typedef VectorFst<LogArc> Fst;
typedef LogArc::Weight Weight;
typedef LogArc::StateId StateId;
typedef LogArc Arc;
VectorFst<LogArc> *logfst = RandFst<LogArc>();
{ // Make the FST stochastic.
for (StateId s = 0; s < logfst->NumStates(); s++) {
Weight w = logfst->Final(s);
for (ArcIterator<Fst> aiter(*logfst, s); !aiter.Done(); aiter.Next()) {
w = Plus(w, aiter.Value().weight);
}
if (w != Weight::Zero()) {
logfst->SetFinal(s, Divide(logfst->Final(s), w, DIVIDE_ANY));
for (MutableArcIterator<Fst> aiter(logfst, s); !aiter.Done(); aiter.Next()) {
Arc a = aiter.Value();
a.weight = Divide(a.weight, w, DIVIDE_ANY);
aiter.SetValue(a);
}
}
}
}
#ifndef _MSC_VER
assert(IsStochasticFst(*logfst, kDelta*10));
#endif
{
std::cout << "logfst = \n";
FstPrinter<LogArc> fstprinter(*logfst, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<StdArc> fst;
Cast(*logfst, &fst);
VectorFst<StdArc> fst_copy(fst);
RemoveEpsLocalSpecial(&fst); // removes eps in std-arc but keep stochastic in log-arc
// make sure equivalent.
assert(RandEquivalent(fst, fst_copy, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
VectorFst<LogArc> logfst2;
Cast(fst, &logfst2);
{
std::cout << "logfst2 = \n";
FstPrinter<LogArc> fstprinter(logfst2, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
if (ApproxEqual(ShortestDistance(*logfst), ShortestDistance(logfst2))) {
// make sure we preserved stochasticity in cases where doing so was
// possible... if the log-semiring total weight changed, then it is
// not possible so don't assert this.
assert(IsStochasticFst(logfst2, kDelta*10));
}
delete logfst;
}
} // namespace fst
int main() {
using namespace fst;
for (int i = 0; i < 10; i++) {
TestRemoveEpsLocal<fst::StdArc>();
TestRemoveEpsLocalSpecial();
}
}
@@ -0,0 +1,60 @@
// fstext/remove-eps-local.h
// Copyright 2009-2011 Microsoft Corporation
// 2014 Johns Hopkins University (author: Daniel Povey)
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_REMOVE_EPS_LOCAL_H_
#define KALDI_FSTEXT_REMOVE_EPS_LOCAL_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
namespace fst {
/// RemoveEpsLocal remove some (but not necessarily all) epsilons in an FST,
/// using an algorithm that is guaranteed to never increase the number of arcs
/// in the FST (and will also never increase the number of states). The
/// algorithm is not optimal but is reasonably clever. It does not just remove
/// epsilon arcs;it also combines pairs of input-epsilon and output-epsilon arcs
/// into one.
/// The algorithm preserves equivalence and stochasticity in the given semiring.
/// If you want to preserve stochasticity in a different semiring (e.g. log),
/// then use RemoveEpsLocalSpecial, which only works for StdArc but which
/// preserves stochasticity, where possible (*) in the LogArc sense. The reason that we can't
/// just cast to a different semiring is that in that case we would no longer
/// be able to guarantee equivalence in the original semiring (this arises from
/// what happens when we combine identical arcs).
/// (*) by "where possible".. there are situations where we wouldn't be able to
/// preserve stochasticity in the LogArc sense while maintaining equivalence in
/// the StdArc sense, so in these situations we maintain equivalence.
template<class Arc>
void RemoveEpsLocal(MutableFst<Arc> *fst);
/// As RemoveEpsLocal but takes care to preserve stochasticity
/// when cast to LogArc.
inline void RemoveEpsLocalSpecial(MutableFst<StdArc> *fst);
} // namespace fst
#include "remove-eps-local-inl.h"
#endif
@@ -0,0 +1,257 @@
// fstext/table-matcher-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "fstext/table-matcher.h"
#include "fstext/fst-test-utils.h"
#include "base/kaldi-math.h"
namespace fst{
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestTableMatcher(bool connect, bool left) {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst1 = RandFst<Arc>();
VectorFst<Arc> *fst2 = RandFst<Arc>();
ILabelCompare<Arc> ilabel_comp;
OLabelCompare<Arc> olabel_comp;
TableComposeOptions opts;
if (left) opts.table_match_type = MATCH_OUTPUT;
else opts.table_match_type = MATCH_INPUT;
opts.min_table_size = 1 + kaldi::Rand() % 5;
opts.table_ratio = 0.25 * (kaldi::Rand() % 5);
opts.connect = connect;
ArcSort(fst1, olabel_comp);
ArcSort(fst2, ilabel_comp);
VectorFst<Arc> composed;
TableCompose(*fst1, *fst2, &composed, opts);
if (!connect) Connect(&composed);
VectorFst<Arc> composed_baseline;
Compose(*fst1, *fst2, &composed_baseline);
std::cout << "Connect = "<< (connect?"True\n":"False\n");
std::cout <<"Table-Composed FST\n";
{
FstPrinter<Arc> fstprinter(composed, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
std::cout <<" Baseline-Composed FST\n";
{
FstPrinter<Arc> fstprinter(composed_baseline, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
if ( !RandEquivalent(composed, composed_baseline, 3/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 20/*path length-- max?*/)) {
VectorFst<Arc> diff1;
Difference(composed, composed_baseline, &diff1);
std::cout <<" Diff1 (composed - baseline) \n";
{
FstPrinter<Arc> fstprinter(diff1, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> diff2;
Difference(composed_baseline, composed, &diff2);
std::cout <<" Diff2 (baseline - composed) \n";
{
FstPrinter<Arc> fstprinter(diff2, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(0);
}
delete fst1;
delete fst2;
}
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestTableMatcherCacheLeft(bool connect) {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst1 = RandFst<Arc>();
TableComposeOptions opts;
opts.table_match_type = MATCH_OUTPUT;
opts.min_table_size = 1 + kaldi::Rand() % 5;
opts.table_ratio = 0.25 * (kaldi::Rand() % 5);
opts.connect = connect;
TableComposeCache<Fst<Arc> > cache(opts);
for (size_t i = 0; i < 3; i++) {
VectorFst<Arc> *fst2 = RandFst<Arc>();
ILabelCompare<Arc> ilabel_comp;
OLabelCompare<Arc> olabel_comp;
ArcSort(fst1, olabel_comp);
ArcSort(fst2, ilabel_comp);
VectorFst<Arc> composed;
TableCompose(*fst1, *fst2, &composed, &cache);
if (!connect) Connect(&composed);
VectorFst<Arc> composed_baseline;
Compose(*fst1, *fst2, &composed_baseline);
std::cout << "Connect = "<< (connect?"True\n":"False\n");
if ( !RandEquivalent(composed, composed_baseline, 3/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/)) {
VectorFst<Arc> diff1;
Difference(composed, composed_baseline, &diff1);
std::cout <<" Diff1 (composed - baseline) \n";
{
FstPrinter<Arc> fstprinter(diff1, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> diff2;
Difference(composed_baseline, composed, &diff2);
std::cout <<" Diff2 (baseline - composed) \n";
{
FstPrinter<Arc> fstprinter(diff2, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(0);
}
delete fst2;
}
delete fst1;
}
template<class Arc> void TestTableMatcherCacheRight(bool connect) {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst2 = RandFst<Arc>();
ILabelCompare<Arc> ilabel_comp;
ArcSort(fst2, ilabel_comp);
TableComposeOptions opts;
opts.table_match_type = MATCH_INPUT;
opts.min_table_size = 1 + kaldi::Rand() % 5;
opts.table_ratio = 0.25 * (kaldi::Rand() % 5);
opts.connect = connect;
TableComposeCache<Fst<Arc> > cache(opts);
for (size_t i = 0; i < 2; i++) {
VectorFst<Arc> *fst1 = RandFst<Arc>();
OLabelCompare<Arc> olabel_comp;
ArcSort(fst1, olabel_comp);
VectorFst<Arc> composed;
TableCompose(*fst1, *fst2, &composed, &cache);
if (!connect) Connect(&composed);
VectorFst<Arc> composed_baseline;
Compose(*fst1, *fst2, &composed_baseline);
std::cout << "Connect = "<< (connect?"True\n":"False\n");
if ( !RandEquivalent(composed, composed_baseline, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 20/*path length-- max?*/)) {
VectorFst<Arc> diff1;
Difference(composed, composed_baseline, &diff1);
std::cout <<" Diff1 (composed - baseline) \n";
{
FstPrinter<Arc> fstprinter(diff1, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> diff2;
Difference(composed_baseline, composed, &diff2);
std::cout <<" Diff2 (baseline - composed) \n";
{
FstPrinter<Arc> fstprinter(diff2, NULL, NULL, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
assert(0);
}
delete fst1;
}
delete fst2;
}
} // namespace fst
int main() {
using namespace fst;
for (int i = 0;i < 1;i++) {
TestTableMatcher<fst::StdArc>(true, true);
TestTableMatcher<fst::StdArc>(false, true);
TestTableMatcher<fst::StdArc>(true, false);
TestTableMatcher<fst::StdArc>(false, false);
TestTableMatcherCacheLeft<fst::StdArc>(true);
TestTableMatcherCacheLeft<fst::StdArc>(false);
TestTableMatcherCacheRight<fst::StdArc>(true);
TestTableMatcherCacheRight<fst::StdArc>(false);
}
}
@@ -0,0 +1,376 @@
// fstext/table-matcher.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#ifndef KALDI_FSTEXT_TABLE_MATCHER_H_
#define KALDI_FSTEXT_TABLE_MATCHER_H_
#include <fst/fstlib.h>
#include <fst/fst-decl.h>
namespace fst {
/// TableMatcher is a matcher specialized for the case where the output
/// side of the left FST always has either all-epsilons coming out of
/// a state, or a majority of the symbol table. Therefore we can
/// either store nothing (for the all-epsilon case) or store a lookup
/// table from Labels to arc offsets. Since the TableMatcher has to
/// iterate over all arcs in each left-hand state the first time it sees
/// it, this matcher type is not efficient if you compose with
/// something very small on the right-- unless you do it multiple
/// times and keep the matcher around. To do this requires using the
/// most advanced form of ComposeFst in Compose.h, that initializes
/// with ComposeFstImplOptions.
struct TableMatcherOptions {
float table_ratio; // we construct the table if it would be at least this full.
int min_table_size;
TableMatcherOptions(): table_ratio(0.25), min_table_size(4) { }
};
// Introducing an "impl" class for TableMatcher because
// we need to do a shallow copy of the Matcher for when
// we want to cache tables for multiple compositions.
template<class F, class BackoffMatcher = SortedMatcher<F> >
class TableMatcherImpl : public MatcherBase<typename F::Arc> {
public:
typedef F FST;
typedef typename F::Arc Arc;
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef StateId ArcId; // Use this type to store arc offsets [it's actually size_t
// in the Seek function of ArcIterator, but StateId should be big enough].
typedef typename Arc::Weight Weight;
public:
TableMatcherImpl(const FST &fst, MatchType match_type,
const TableMatcherOptions &opts = TableMatcherOptions()):
match_type_(match_type),
fst_(fst.Copy()),
loop_(match_type == MATCH_INPUT ?
Arc(kNoLabel, 0, Weight::One(), kNoStateId) :
Arc(0, kNoLabel, Weight::One(), kNoStateId)),
aiter_(NULL),
s_(kNoStateId), opts_(opts),
backoff_matcher_(fst, match_type)
{
assert(opts_.min_table_size > 0);
if (match_type == MATCH_INPUT)
assert(fst_->Properties(kILabelSorted, true) == kILabelSorted);
else if (match_type == MATCH_OUTPUT)
assert(fst_->Properties(kOLabelSorted, true) == kOLabelSorted);
else
assert(0 && "Invalid FST properties");
}
virtual const FST &GetFst() const { return *fst_; }
virtual ~TableMatcherImpl() {
std::vector<ArcId> *const empty = ((std::vector<ArcId>*)(NULL)) + 1; // special marker.
for (size_t i = 0; i < tables_.size(); i++) {
if (tables_[i] != NULL && tables_[i] != empty)
delete tables_[i];
}
delete aiter_;
delete fst_;
}
virtual MatchType Type(bool test) const {
return match_type_;
}
void SetState(StateId s) {
if (aiter_) {
delete aiter_;
aiter_ = NULL;
}
if (match_type_ == MATCH_NONE)
LOG(FATAL) << "TableMatcher: bad match type";
s_ = s;
std::vector<ArcId> *const empty = ((std::vector<ArcId>*)(NULL)) + 1; // special marker.
if (static_cast<size_t>(s) >= tables_.size()) {
assert(s>=0);
tables_.resize(s+1, NULL);
}
std::vector<ArcId>* &this_table_ = tables_[s]; // note: ref to ptr.
if (this_table_ == empty) {
backoff_matcher_.SetState(s);
return;
} else if (this_table_ == NULL) { // NULL means has not been set.
ArcId num_arcs = fst_->NumArcs(s);
if (num_arcs == 0 || num_arcs < opts_.min_table_size) {
this_table_ = empty;
backoff_matcher_.SetState(s);
return;
}
ArcIterator<FST> aiter(*fst_, s);
aiter.SetFlags(kArcNoCache|(match_type_ == MATCH_OUTPUT?kArcOLabelValue:kArcILabelValue),
kArcNoCache|kArcValueFlags);
// the statement above, says: "Don't cache stuff; and I only need the ilabel/olabel
// to be computed.
aiter.Seek(num_arcs - 1);
Label highest_label = (match_type_ == MATCH_OUTPUT ?
aiter.Value().olabel : aiter.Value().ilabel);
if ((highest_label+1) * opts_.table_ratio > num_arcs) {
this_table_ = empty;
backoff_matcher_.SetState(s);
return; // table would be too sparse.
}
// OK, now we are creating the table.
this_table_ = new std::vector<ArcId> (highest_label+1, kNoStateId);
ArcId pos = 0;
for (aiter.Seek(0); !aiter.Done(); aiter.Next(), pos++) {
Label label = (match_type_ == MATCH_OUTPUT ?
aiter.Value().olabel : aiter.Value().ilabel);
assert((size_t)label <= (size_t)highest_label); // also checks >= 0.
if ((*this_table_)[label] == kNoStateId) (*this_table_)[label] = pos;
// set this_table_[label] to first position where arc has this
// label.
}
}
// At this point in the code, this_table_ != NULL and != empty.
aiter_ = new ArcIterator<FST>(*fst_, s);
aiter_->SetFlags(kArcNoCache, kArcNoCache); // don't need to cache arcs as may only
// need a small subset.
loop_.nextstate = s;
// aiter_ = NULL;
// backoff_matcher_.SetState(s);
}
bool Find(Label match_label) {
if (!aiter_) return backoff_matcher_.Find(match_label);
else {
match_label_ = match_label;
current_loop_ = (match_label == 0);
// kNoLabel means the implicit loop on the other FST --
// matches real epsilons but not the self-loop.
match_label_ = (match_label_ == kNoLabel ? 0 : match_label_);
if (static_cast<size_t>(match_label_) < tables_[s_]->size() &&
(*(tables_[s_]))[match_label_] != kNoStateId) {
aiter_->Seek( (*(tables_[s_]))[match_label_] ); // label exists.
return true;
}
return current_loop_;
}
}
const Arc& Value() const {
if (aiter_)
return current_loop_ ? loop_ : aiter_->Value();
else
return backoff_matcher_.Value();
}
void Next() {
if (aiter_) {
if (current_loop_)
current_loop_ = false;
else
aiter_->Next();
} else
backoff_matcher_.Next();
}
bool Done() const {
if (aiter_ != NULL) {
if (current_loop_)
return false;
if (aiter_->Done())
return true;
Label label = (match_type_ == MATCH_OUTPUT ?
aiter_->Value().olabel : aiter_->Value().ilabel);
return (label != match_label_);
} else
return backoff_matcher_.Done();
}
const Arc &Value() {
if (aiter_ != NULL) {
return (current_loop_ ? loop_ : aiter_->Value() );
} else
return backoff_matcher_.Value();
}
virtual TableMatcherImpl<FST> *Copy(bool safe = false) const {
assert(0); // shouldn't be called. This is not a "real" matcher,
// although we derive from MatcherBase for convenience.
return NULL;
}
virtual uint64 Properties(uint64 props) const { return props; } // simple matcher that does
// not change its FST, so properties are properties of FST it is applied to
private:
virtual void SetState_(StateId s) { SetState(s); }
virtual bool Find_(Label label) { return Find(label); }
virtual bool Done_() const { return Done(); }
virtual const Arc& Value_() const { return Value(); }
virtual void Next_() { Next(); }
MatchType match_type_;
FST *fst_;
bool current_loop_;
Label match_label_;
Arc loop_;
ArcIterator<FST> *aiter_;
StateId s_;
std::vector<std::vector<ArcId> *> tables_;
TableMatcherOptions opts_;
BackoffMatcher backoff_matcher_;
};
template<class F, class BackoffMatcher = SortedMatcher<F> >
class TableMatcher : public MatcherBase<typename F::Arc> {
public:
typedef F FST;
typedef typename F::Arc Arc;
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef StateId ArcId; // Use this type to store arc offsets [it's actually size_t
// in the Seek function of ArcIterator, but StateId should be big enough].
typedef typename Arc::Weight Weight;
typedef TableMatcherImpl<F, BackoffMatcher> Impl;
TableMatcher(const FST &fst, MatchType match_type,
const TableMatcherOptions &opts = TableMatcherOptions())
: impl_(std::make_shared<Impl>(fst, match_type, opts)) { }
TableMatcher(const TableMatcher<FST, BackoffMatcher> &matcher,
bool safe = false)
: impl_(matcher.impl_) {
if (safe == true) {
LOG(FATAL) << "TableMatcher: Safe copy not supported";
}
}
virtual const FST &GetFst() const { return impl_->GetFst(); }
virtual MatchType Type(bool test) const { return impl_->Type(test); }
void SetState(StateId s) { return impl_->SetState(s); }
bool Find(Label match_label) { return impl_->Find(match_label); }
const Arc& Value() const { return impl_->Value(); }
void Next() { return impl_->Next(); }
bool Done() const { return impl_->Done(); }
const Arc &Value() { return impl_->Value(); }
virtual TableMatcher<FST, BackoffMatcher> *Copy(bool safe = false) const {
return new TableMatcher<FST, BackoffMatcher> (*this, safe);
}
virtual uint64 Properties(uint64 props) const { return impl_->Properties(props); } // simple matcher that does
// not change its FST, so properties are properties of FST it is applied to
private:
std::shared_ptr<Impl> impl_;
virtual void SetState_(StateId s) { impl_->SetState(s); }
virtual bool Find_(Label label) { return impl_->Find(label); }
virtual bool Done_() const { return impl_->Done(); }
virtual const Arc& Value_() const { return impl_->Value(); }
virtual void Next_() { impl_->Next(); }
TableMatcher &operator=(const TableMatcher &) = delete;
};
struct TableComposeOptions: public TableMatcherOptions {
bool connect; // Connect output
ComposeFilter filter_type; // Which pre-defined filter to use
MatchType table_match_type;
explicit TableComposeOptions(const TableMatcherOptions &mo,
bool c = true, ComposeFilter ft = SEQUENCE_FILTER,
MatchType tms = MATCH_OUTPUT)
: TableMatcherOptions(mo), connect(c), filter_type(ft), table_match_type(tms) { }
TableComposeOptions() : connect(true), filter_type(SEQUENCE_FILTER),
table_match_type(MATCH_OUTPUT) { }
};
template<class Arc>
void TableCompose(const Fst<Arc> &ifst1, const Fst<Arc> &ifst2,
MutableFst<Arc> *ofst,
const TableComposeOptions &opts = TableComposeOptions()) {
typedef Fst<Arc> F;
CacheOptions nopts;
nopts.gc_limit = 0; // Cache only the last state for fastest copy.
if (opts.table_match_type == MATCH_OUTPUT) {
// ComposeFstImplOptions templated on matcher for fst1, matcher for fst2.
ComposeFstImplOptions<TableMatcher<F>, SortedMatcher<F> > impl_opts(nopts);
impl_opts.matcher1 = new TableMatcher<F>(ifst1, MATCH_OUTPUT, opts);
*ofst = ComposeFst<Arc>(ifst1, ifst2, impl_opts);
} else {
assert(opts.table_match_type == MATCH_INPUT) ;
// ComposeFstImplOptions templated on matcher for fst1, matcher for fst2.
ComposeFstImplOptions<SortedMatcher<F>, TableMatcher<F> > impl_opts(nopts);
impl_opts.matcher2 = new TableMatcher<F>(ifst2, MATCH_INPUT, opts);
*ofst = ComposeFst<Arc>(ifst1, ifst2, impl_opts);
}
if (opts.connect) Connect(ofst);
}
/// TableComposeCache lets us do multiple compositions while caching the same
/// matcher.
template<class F>
struct TableComposeCache {
TableMatcher<F> *matcher;
TableComposeOptions opts;
TableComposeCache(const TableComposeOptions &opts = TableComposeOptions()): matcher (NULL), opts(opts) {}
~TableComposeCache() { delete(matcher); }
};
template<class Arc>
void TableCompose(const Fst<Arc> &ifst1, const Fst<Arc> &ifst2,
MutableFst<Arc> *ofst,
TableComposeCache<Fst<Arc> > *cache) {
typedef Fst<Arc> F;
assert(cache != NULL);
CacheOptions nopts;
nopts.gc_limit = 0; // Cache only the last state for fastest copy.
if (cache->opts.table_match_type == MATCH_OUTPUT) {
ComposeFstImplOptions<TableMatcher<F>, SortedMatcher<F> > impl_opts(nopts);
if (cache->matcher == NULL)
cache->matcher = new TableMatcher<F>(ifst1, MATCH_OUTPUT, cache->opts);
impl_opts.matcher1 = cache->matcher->Copy(); // not passing "safe": may not
// be thread-safe-- anway I don't understand this part.
*ofst = ComposeFst<Arc>(ifst1, ifst2, impl_opts);
} else {
assert(cache->opts.table_match_type == MATCH_INPUT) ;
ComposeFstImplOptions<SortedMatcher<F>, TableMatcher<F> > impl_opts(nopts);
if (cache->matcher == NULL)
cache->matcher = new TableMatcher<F>(ifst2, MATCH_INPUT, cache->opts);
impl_opts.matcher2 = cache->matcher->Copy();
*ofst = ComposeFst<Arc>(ifst1, ifst2, impl_opts);
}
if (cache->opts.connect) Connect(ofst);
}
} // end namespace fst
#endif
@@ -0,0 +1,214 @@
// fstext/trivial-factor-weight-test.cc
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
#include "base/kaldi-math.h"
#include "fstext/pre-determinize.h"
#include "fstext/determinize-star.h"
#include "fstext/trivial-factor-weight.h"
#include "fstext/fst-test-utils.h"
// Just check that it compiles, for now.
namespace fst
{
using std::cout;
using std::vector;
// Don't instantiate with log semiring, as RandEquivalent may fail.
template<class Arc> void TestFactor() {
typedef typename Arc::Label Label;
typedef typename Arc::StateId StateId;
typedef typename Arc::Weight Weight;
VectorFst<Arc> *fst = new VectorFst<Arc>();
int n_syms = 2 + kaldi::Rand() % 5, n_states = 3 + kaldi::Rand() % 10, n_arcs = 5 + kaldi::Rand() % 30, n_final = 1 + kaldi::Rand()%3; // Up to 2 unique symbols.
cout << "Testing pre-determinize with "<<n_syms<<" symbols, "<<n_states<<" states and "<<n_arcs<<" arcs and "<<n_final<<" final states.\n";
SymbolTable *sptr = NULL;
vector<Label> all_syms; // including epsilon.
// Put symbols in the symbol table from 1..n_syms-1.
for (size_t i = 0;i < (size_t)n_syms;i++)
all_syms.push_back(i);
// Create states.
vector<StateId> all_states;
for (size_t i = 0;i < (size_t)n_states;i++) {
StateId this_state = fst->AddState();
if (i == 0) fst->SetStart(i);
all_states.push_back(this_state);
}
// Set final states.
for (size_t j = 0;j < (size_t)n_final;j++) {
StateId id = all_states[kaldi::Rand() % n_states];
Weight weight = (Weight)(0.33*(kaldi::Rand() % 5) );
printf("calling SetFinal with %d and %f\n", id, weight.Value());
fst->SetFinal(id, weight);
}
// Create arcs.
for (size_t i = 0;i < (size_t)n_arcs;i++) {
Arc a;
a.nextstate = all_states[kaldi::Rand() % n_states];
a.ilabel = all_syms[kaldi::Rand() % n_syms];
a.olabel = all_syms[kaldi::Rand() % n_syms]; // same input+output vocab.
a.weight = (Weight) (0.33*(kaldi::Rand() % 2));
StateId start_state = all_states[kaldi::Rand() % n_states];
fst->AddArc(start_state, a);
}
std::cout <<" printing before trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Trim resulting FST.
Connect(fst);
std::cout <<" printing after trimming\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
vector<Label> extra_syms;
if (fst->Start() != kNoStateId) { // "Connect" did not make it empty....
PreDeterminize(fst, 1000, &extra_syms);
}
std::cout <<" printing after predeterminization\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
{ // Remove epsilon. All default args.
bool connect = true;
Weight weight_threshold = Weight::Zero();
int64 nstate = -1; // Relates to pruning.
double delta = kDelta; // I think a small weight value. Relates to some kind of pruning,
// I guess. But with no epsilon cycles, probably doensn't matter.
RmEpsilon(fst, connect, weight_threshold, nstate, delta);
}
std::cout <<" printing after double-epsilon removal\n";
{
FstPrinter<Arc> fstprinter(*fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<Arc> ofst_star;
{
printf("Converting to Gallic semiring");
VectorFst<GallicArc<Arc> > gallic_fst;
VectorFst<GallicArc<Arc> > gallic_fst_noeps;
VectorFst<GallicArc<Arc> > gallic_fst_det;
{
printf("Determinizing with DeterminizeStar, converting to Gallic\n");
DeterminizeStar(*fst, &gallic_fst);
}
{
std::cout <<" printing gallic FST\n";
FstPrinter<GallicArc<Arc> > fstprinter(gallic_fst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
// Map(ofst_star, &gallic_fst, ToGallicMapper<Arc, STRING_LEFT>());
printf("Converting gallic back to regular\n");
TrivialFactorWeightFst< GallicArc<Arc, GALLIC_LEFT>, GallicFactor<typename Arc::Label,
typename Arc::Weight, GALLIC_LEFT> > fwfst(gallic_fst);
{
std::cout <<" printing factor-weight FST\n";
FstPrinter<GallicArc<Arc> > fstprinter(fwfst, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
Map(fwfst, &ofst_star, FromGallicMapper<Arc, GALLIC_LEFT>());
{
std::cout <<" printing after converting back to regular FST\n";
FstPrinter<Arc> fstprinter(ofst_star, sptr, sptr, NULL, false, true, "\t");
fstprinter.Print(&std::cout, "standard output");
}
VectorFst<GallicArc<Arc> > new_gallic_fst;
Map(ofst_star, &new_gallic_fst, ToGallicMapper<Arc, GALLIC_LEFT>());
assert(RandEquivalent(gallic_fst, new_gallic_fst, 5/*paths*/, 0.01/*delta*/, kaldi::Rand()/*seed*/, 100/*path length-- max?*/));
}
delete fst;
}
template<class Arc, class inttype> void TestStringRepository() {
typedef typename Arc::Label Label;
StringRepository<Label, inttype> sr;
int N = 1000;
if (sizeof(inttype) == 1) N = 64;
vector<vector<Label> > strings(N);
vector<inttype> ids(N);
for (size_t i = 0;i < N;i++) {
size_t len = kaldi::Rand() % 4;
vector<Label> vec;
for (size_t j = 0;j < len;j++) vec.push_back( (kaldi::Rand()%10) + 150*(kaldi::Rand()%2)); // make it have reasonable range.
if (i < 500 && vec.size() == 0) ids[i] = sr.IdOfEmpty();
else if (i < 500 && vec.size() == 1) ids[i] = sr.IdOfLabel(vec[0]);
else ids[i] = sr.IdOfSeq(vec);
strings[i] = vec;
}
for (size_t i = 0;i < N;i++) {
vector<Label> tmpv;
tmpv.push_back(10); // just put in garbage.
sr.SeqOfId(ids[i], &tmpv);
assert(tmpv == strings[i]);
assert(sr.IdOfSeq(strings[i]) == ids[i]);
if (strings[i].size() == 0) assert(ids[i] == sr.IdOfEmpty());
if (strings[i].size() == 1) assert(ids[i] == sr.IdOfLabel(strings[i][0]));
if (sizeof(inttype) != 1) {
size_t prefix_len = kaldi::Rand() % (strings[i].size() + 1);
inttype s2 = sr.RemovePrefix(ids[i], prefix_len);
vector<Label> vec2;
sr.SeqOfId(s2, &vec2);
for (size_t j = 0;j < strings[i].size()-prefix_len;j++) {
assert(vec2[j] == strings[i][j+prefix_len]);
}
}
}
}
} // end namespace fst
int main() {
for (int i = 0;i < 25;i++) {
fst::TestFactor<fst::StdArc>();
}
}
@@ -0,0 +1,401 @@
// fstext/trivial-factor-weight.h
// Copyright 2009-2011 Microsoft Corporation
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.
//
//
// This is a modified file from the OpenFST Library v1.2.7 available at
// http://www.openfst.org and released under the Apache License Version 2.0.
//
//
// See ../../COPYING for clarification regarding multiple authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Copyright 2005-2010 Google, Inc.
// Author: allauzen@google.com (Cyril Allauzen)
#ifndef KALDI_FSTEXT_TRIVIAL_FACTOR_WEIGHT_H_
#define KALDI_FSTEXT_TRIVIAL_FACTOR_WEIGHT_H_
// TrivialFactorWeight.h This is an extension to factor-weight.h in the OpenFst
// code. It is a version of FactorWeight that creates separate states (with
// input epsilons) rather than pushing the factors forward. This is for
// converting from Gallic FSTs, where you want the result to be a bit more
// trivial with input epsilons inserted where there are multiple output symbols.
// This has the advantage that it always works, for any input (also I just
// prefer this approach).
#include <unordered_map>
using std::unordered_map;
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <fst/cache.h>
#include <fst/test-properties.h>
namespace fst {
template <class Arc>
struct TrivialFactorWeightOptions : CacheOptions {
typedef typename Arc::Label Label;
float delta;
Label extra_ilabel; // input label of extra arcs
Label extra_olabel; // output label of extra arcs
TrivialFactorWeightOptions(const CacheOptions &opts, float d,
Label il = 0, Label ol = 0)
: CacheOptions(opts), delta(d), extra_ilabel(il), extra_olabel(ol) {}
explicit TrivialFactorWeightOptions(
float d, Label il = 0, Label ol = 0)
: delta(d), extra_ilabel(il), extra_olabel(ol) {}
TrivialFactorWeightOptions(): delta(kDelta), extra_ilabel(0), extra_olabel(0) {}
};
namespace internal {
// Implementation class for TrivialFactorWeight
template <class A, class F>
class TrivialFactorWeightFstImpl
: public CacheImpl<A> {
public:
using CacheImpl<A>::PushArc;
using FstImpl<A>::SetType;
using FstImpl<A>::SetProperties;
using FstImpl<A>::Properties;
using FstImpl<A>::SetInputSymbols;
using FstImpl<A>::SetOutputSymbols;
using CacheBaseImpl< CacheState<A> >::HasStart;
using CacheBaseImpl< CacheState<A> >::HasFinal;
using CacheBaseImpl< CacheState<A> >::HasArcs;
typedef A Arc;
typedef typename A::Label Label;
typedef typename A::Weight Weight;
typedef typename A::StateId StateId;
typedef F FactorIterator;
typedef DefaultCacheStore<A> Store;
typedef typename Store::State State;
struct Element {
Element() {}
Element(StateId s, Weight w) : state(s), weight(w) {}
StateId state; // Input state Id
Weight weight; // Residual weight
};
TrivialFactorWeightFstImpl(const Fst<A> &fst, const TrivialFactorWeightOptions<A> &opts)
: CacheImpl<A>(opts),
fst_(fst.Copy()),
delta_(opts.delta),
extra_ilabel_(opts.extra_ilabel),
extra_olabel_(opts.extra_olabel) {
SetType("factor-weight");
uint64 props = fst.Properties(kFstProperties, false);
SetProperties(FactorWeightProperties(props), kCopyProperties);
SetInputSymbols(fst.InputSymbols());
SetOutputSymbols(fst.OutputSymbols());
}
TrivialFactorWeightFstImpl(const TrivialFactorWeightFstImpl<A, F> &impl)
: CacheImpl<A>(impl),
fst_(impl.fst_->Copy(true)),
delta_(impl.delta_),
extra_ilabel_(impl.extra_ilabel_),
extra_olabel_(impl.extra_olabel_) {
SetType("factor-weight");
SetProperties(impl.Properties(), kCopyProperties);
SetInputSymbols(impl.InputSymbols());
SetOutputSymbols(impl.OutputSymbols());
}
StateId Start() {
if (!HasStart()) {
StateId s = fst_->Start();
if (s == kNoStateId)
return kNoStateId;
StateId start = this->FindState(Element(fst_->Start(), Weight::One()));
this->SetStart(start);
}
return CacheImpl<A>::Start();
}
Weight Final(StateId s) {
if (!HasFinal(s)) {
const Element &e = elements_[s];
Weight w;
if (e.state == kNoStateId) { // extra state inserted to represent final weights.
FactorIterator fit(e.weight);
if (fit.Done()) { // cannot be factored.
w = e.weight; // so it's final
} else {
w = Weight::Zero(); // need another transition.
}
} else {
if (e.weight != Weight::One()) { // Not a real state.
w = Weight::Zero();
} else { // corresponds to a "real" state.
w = fst_->Final(e.state);
FactorIterator fit(w);
if (!fit.Done()) // we would have intermediate states representing this final state.
w = Weight::Zero();
}
}
this->SetFinal(s, w);
return w;
} else {
return CacheImpl<A>::Final(s);
}
}
size_t NumArcs(StateId s) {
if (!HasArcs(s))
Expand(s);
return CacheImpl<A>::NumArcs(s);
}
size_t NumInputEpsilons(StateId s) {
if (!HasArcs(s))
Expand(s);
return CacheImpl<A>::NumInputEpsilons(s);
}
size_t NumOutputEpsilons(StateId s) {
if (!HasArcs(s))
Expand(s);
return CacheImpl<A>::NumOutputEpsilons(s);
}
void InitArcIterator(StateId s, ArcIteratorData<A> *data) {
if (!HasArcs(s))
Expand(s);
CacheImpl<A>::InitArcIterator(s, data);
}
// Find state corresponding to an element. Create new state
// if element not found.
StateId FindState(const Element &e) {
typename ElementMap::iterator eit = element_map_.find(e);
if (eit != element_map_.end()) {
return (*eit).second;
} else {
StateId s = elements_.size();
elements_.push_back(e);
element_map_.insert(std::pair<const Element, StateId>(e, s));
return s;
}
}
// Computes the outgoing transitions from a state, creating new destination
// states as needed.
void Expand(StateId s) {
CHECK(static_cast<size_t>(s) < elements_.size());
Element e = elements_[s];
if (e.weight != Weight::One()) {
FactorIterator fit(e.weight);
if (fit.Done()) { // Cannot be factored-> create a link to dest state directly
if (e.state != kNoStateId) {
StateId dest = FindState(Element(e.state, Weight::One()));
PushArc(s, Arc(extra_ilabel_, extra_olabel_, e.weight, dest));
} // else we're done. This is a final state.
} else { // Can be factored.
const std::pair<Weight, Weight> &p = fit.Value();
StateId dest = FindState(Element(e.state, p.second.Quantize(delta_)));
PushArc(s, Arc(extra_ilabel_, extra_olabel_, p.first, dest));
}
} else { // Unit weight. This corresponds to a "real" state.
CHECK(e.state != kNoStateId);
for (ArcIterator< Fst<A> > ait(*fst_, e.state);
!ait.Done();
ait.Next()) {
const A &arc = ait.Value();
FactorIterator fit(arc.weight);
if (fit.Done()) { // cannot be factored->just link directly to dest.
StateId dest = FindState(Element(arc.nextstate, Weight::One()));
PushArc(s, Arc(arc.ilabel, arc.olabel, arc.weight, dest));
} else {
const std::pair<Weight, Weight> &p = fit.Value();
StateId dest = FindState(Element(arc.nextstate, p.second.Quantize(delta_)));
PushArc(s, Arc(arc.ilabel, arc.olabel, p.first, dest));
}
}
// See if we have to add arcs for final-states [only if final-weight is factorable].
Weight final_w = fst_->Final(e.state);
if (final_w != Weight::Zero()) {
FactorIterator fit(final_w);
if (!fit.Done()) {
const std::pair<Weight, Weight> &p = fit.Value();
StateId dest = FindState(Element(kNoStateId, p.second.Quantize(delta_)));
PushArc(s, Arc(extra_ilabel_, extra_olabel_, p.first, dest));
}
}
}
this->SetArcs(s);
}
private:
// Equality function for Elements, assume weights have been quantized.
class ElementEqual {
public:
bool operator()(const Element &x, const Element &y) const {
return x.state == y.state && x.weight == y.weight;
}
};
// Hash function for Elements to Fst states.
class ElementKey {
public:
size_t operator()(const Element &x) const {
return static_cast<size_t>(x.state * kPrime + x.weight.Hash());
}
private:
static const int kPrime = 7853;
};
typedef unordered_map<Element, StateId, ElementKey, ElementEqual> ElementMap;
std::unique_ptr<const Fst<A>> fst_;
float delta_;
uint32 mode_; // factoring arc and/or final weights
Label extra_ilabel_; // ilabel of arc created when factoring final w's
Label extra_olabel_; // olabel of arc created when factoring final w's
std::vector<Element> elements_; // mapping Fst state to Elements
ElementMap element_map_; // mapping Elements to Fst state
};
} // namespace internal
/// TrivialFactorWeightFst takes as template parameter a FactorIterator as
/// defined above. The result of weight factoring is a transducer
/// equivalent to the input whose path weights have been factored
/// according to the FactorIterator. States and transitions will be
/// added as necessary.
/// This algorithm differs from the one implemented in FactorWeightFst
/// in that it does not attempt to push the extra weight forward to the
/// next state: it uses a sequence of "extra" intermediate state, and
/// outputs the remaining weight right away. This ensures that it will
/// always succeed, even for Gallic representations of FSTs that have cycles
/// with more output than input symbols.
/// Note that the code below was modified from factor-weight.h by just
/// search-and-replacing "FactorWeight" by "TrivialFactorWeight".
template <class A, class F>
class TrivialFactorWeightFst :
public ImplToFst<internal::TrivialFactorWeightFstImpl<A, F>> {
public:
friend class ArcIterator< TrivialFactorWeightFst<A, F> >;
friend class StateIterator< TrivialFactorWeightFst<A, F> >;
typedef A Arc;
typedef typename A::Weight Weight;
typedef typename A::StateId StateId;
typedef DefaultCacheStore<Arc> Store;
typedef typename Store::State State;
typedef internal::TrivialFactorWeightFstImpl<A, F> Impl;
explicit TrivialFactorWeightFst(const Fst<A> &fst)
: ImplToFst<Impl>(std::make_shared<Impl>(fst, TrivialFactorWeightOptions<A>())) {}
TrivialFactorWeightFst(const Fst<A> &fst, const TrivialFactorWeightOptions<A> &opts)
: ImplToFst<Impl>(std::make_shared<Impl>(fst, opts)) {}
// See Fst<>::Copy() for doc.
TrivialFactorWeightFst(const TrivialFactorWeightFst<A, F> &fst, bool copy)
: ImplToFst<Impl>(fst, copy) {}
// Get a copy of this TrivialFactorWeightFst. See Fst<>::Copy() for further doc.
TrivialFactorWeightFst<A, F> *Copy(bool copy = false) const override {
return new TrivialFactorWeightFst<A, F>(*this, copy);
}
inline void InitStateIterator(StateIteratorData<A> *data) const override;
void InitArcIterator(StateId s, ArcIteratorData<A> *data) const override {
GetMutableImpl()->InitArcIterator(s, data);
}
private:
using ImplToFst<Impl>::GetImpl;
using ImplToFst<Impl>::GetMutableImpl;
TrivialFactorWeightFst &operator=(const TrivialFactorWeightFst &fst) = delete;
};
// Specialization for TrivialFactorWeightFst.
template<class A, class F>
class StateIterator< TrivialFactorWeightFst<A, F> >
: public CacheStateIterator< TrivialFactorWeightFst<A, F> > {
public:
explicit StateIterator(const TrivialFactorWeightFst<A, F> &fst)
: CacheStateIterator< TrivialFactorWeightFst<A, F> >(fst, fst.GetMutableImpl()) {}
};
// Specialization for TrivialFactorWeightFst.
template <class A, class F>
class ArcIterator< TrivialFactorWeightFst<A, F> >
: public CacheArcIterator< TrivialFactorWeightFst<A, F> > {
public:
typedef typename A::StateId StateId;
ArcIterator(const TrivialFactorWeightFst<A, F> &fst, StateId s)
: CacheArcIterator< TrivialFactorWeightFst<A, F>>(fst.GetMutableImpl(), s) {
if (!fst.GetImpl()->HasArcs(s)) fst.GetMutableImpl()->Expand(s);
}
};
template <class A, class F>
inline void TrivialFactorWeightFst<A, F>::InitStateIterator(
StateIteratorData<A> *data) const {
data->base = new StateIterator< TrivialFactorWeightFst<A, F> >(*this);
}
} // namespace fst
#endif