// fstext/context-fst-inl.h // Copyright 2009-2011 Microsoft Corporation; Jan Silovsky // 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_CONTEXT_FST_INL_H_ #define KALDI_FSTEXT_CONTEXT_FST_INL_H_ #include "base/kaldi-common.h" #include "fstext/fstext-utils.h" // Do not include this file directly. It is included by context-fst.h. namespace fst { /// \addtogroup context_fst_group /// @{ template typename ContextFstImpl::StateId ContextFstImpl::FindState(const vector &seq) { // Finds state-id corresponding to this vector of phones. Inserts it if // necessary. assert(static_cast(seq.size()) == N_-1); VectorToStateIter iter = state_map_.find(seq); if (iter == state_map_.end()) { // Not already in map. StateId this_state_id = (StateId)state_seqs_.size(); StateId this_state_id_check = CacheImpl::AddState(); // goes back to VectorFstBaseImpl, inherited via CacheFst assert(this_state_id == this_state_id_check); state_seqs_.push_back(seq); state_map_[seq] = this_state_id; return this_state_id; } else { return iter->second; } } template typename ContextFstImpl::Label ContextFstImpl::FindLabel(const vector &label_vec) { // Finds ilabel corresponding to this information.. Creates new ilabel if necessary. VectorToLabelIter 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; } } template typename ContextFstImpl::StateId ContextFstImpl::Start() { if (! CacheImpl::HasStart()) { vector vec(N_-1, 0); // Vector of N_-1 epsilons. [e.g. N = 3]. StateId s = FindState(vec); assert(s == 0); this->SetStart(s); } return CacheImpl::Start(); } template ContextFstImpl::ContextFstImpl(const ContextFstImpl &other): phone_syms_(other.phone_syms_), disambig_syms_(other.disambig_syms_) { std::cerr << "ContextFst copying not yet supported [not hard, but would have to test.]"; exit(1); } template ContextFstImpl::ContextFstImpl(Label subsequential_symbol, // epsilon not allowed. const vector &phone_syms, // on output side of ifst. const vector &disambig_syms, // on output int N, int P): phone_syms_(phone_syms), disambig_syms_(disambig_syms), subsequential_symbol_(subsequential_symbol) , N_(N), P_(P) { { // This block checks the inputs. 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."; assert(phone_syms_.count(0) == 0); assert(disambig_syms_.count(0) == 0); for (size_t i = 0; i < phone_syms.size(); i++) assert(disambig_syms_.count(phone_syms[i]) == 0); assert(N>0 && P>=0 && P eps_vec; // empty vec. // Make sure the symbol that equates to epsilon is zero in our numbering. Label eps_id = FindLabel(eps_vec); // this function will add it to the input // symbol table, if necessary. assert(eps_id == 0); // doing this in the initializer should guarantee it is zero. if (N > P+1 && !disambig_syms_.empty()) { // We add in 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 pseudo_eps_vec; pseudo_eps_vec.push_back(0); pseudo_eps_symbol_= FindLabel(pseudo_eps_vec); // this function will add it to the input // symbol table, if necessary. assert(pseudo_eps_symbol_ == 1); } else pseudo_eps_symbol_ = 0; // use actual epsilon. } template typename ContextFstImpl::Weight ContextFstImpl::Final(StateId s) { assert(static_cast(s) < state_seqs_.size()); // make sure state exists already. if (!this->HasFinal(s)) { // Work out final-state weight. const vector &seq = state_seqs_[s]; bool final_ok; assert(static_cast(seq.size()) == N_-1); if (P_ < N_ - 1) { /* Note that P_ (in zero based indexing) is the "central position", and for arcs out of this state the thing at P_ will be the one we expand. If this is the subsequential symbol, it means we will output nothing (and will obviously never output anything). Thus we make this state the final state. */ final_ok = (seq[P_] == subsequential_symbol_); } else { /* If P_ == N_-1, then the "central phone" is the last one in the list (we have a left-context system). In this case everything is output immediately and there is no need for a subsequential symbol. Here, any state in the FST can be the final state. */ final_ok = true; } Weight w = final_ok ? Weight::One() : Weight::Zero(); this->SetFinal(s, w); return w; } return CacheImpl::Final(s); } // Warning! Not tested for correctness. Does not really matter, the way // this function is being used so far. Note: could possibly be wrong, template size_t ContextFstImpl::NumArcs(StateId s) { if (this->HasArcs(s)) { return CacheImpl::NumArcs(s); } KALDI_ASSERT(s >= 0 && s < state_seqs_.size()); const vector &seq = state_seqs_[s]; KALDI_ASSERT(seq.size() == N_ - 1); if (!seq.empty() && seq.back() == subsequential_symbol_) { // State is not a "normal" state because it just saw the subsequential symbol, // hence it cannot accept phones. if (P_ == N_ - 1 || seq[P_] == subsequential_symbol_) { // don't // accept subsequential symbol.. c.f. logic in CreateArc(). return disambig_syms_.size(); } else { return disambig_syms_.size() + 1; // Accept disambig syms and // subsequential symbol. } } else { // For normal states, in general there is potentially an arc for each phone and an arc // for each disambiguation symbol, plus one for the subsequential symbol. return phone_syms_.size() + disambig_syms_.size() + 1; } } template size_t ContextFstImpl::NumInputEpsilons(StateId s) { if (!this->HasArcs(s)) Expand(s); return CacheImpl::NumInputEpsilons(s); } template void ContextFstImpl::InitArcIterator(StateId s, ArcIteratorData *data) { if (!this->HasArcs(s)) Expand(s); CacheImpl::InitArcIterator(s, data); } template void ContextFstImpl::CreateDisambigArc(StateId s, Label olabel, Arc *oarc) { // called from CreateArc. // Creates a self-loop arc corresponding to the disambiguation symbol. vector label_info; // (olabel); label_info.push_back(-olabel); // olabel is a disambiguation symbol. Use its negative // so we can easily distinguish them. Label ilabel = FindLabel(label_info); oarc->ilabel = ilabel; oarc->olabel = olabel; oarc->weight = Weight::One(); oarc->nextstate = s; // self-loop. } template bool ContextFstImpl::CreatePhoneOrEpsArc(StateId src, StateId dst, Label olabel, const vector &phone_seq, Arc *oarc) { // called from CreateArc. // creates the arc with a phone's state on its input labels (or epsilon). // returns true if it created the arc. // returns false if it could not create an arc due to the decision-tree returning false // [this only happens if opts_.behavior_on_failure == ContextFstOptions::kNoArc]. assert(phone_seq[P_] != subsequential_symbol_); // would be coding error. if (phone_seq[P_] == 0) { // this can happen at the beginning of the graph. // we don't output a real phone. Epsilon arc (but sometimes we need to // use a special disambiguation symbol instead of epsilon). *oarc = Arc(pseudo_eps_symbol_, olabel, Weight::One(), dst); // This 1 is a "special" disambiguation symbol (#-1 in printed form) that // we use to represent epsilons. return true; } else { // have a phone in central position. Label ilabel = FindLabel(phone_seq); *oarc = Arc(ilabel, olabel, Weight::One(), dst); return true; } } // This function is specific to ContextFst. It's not part of the Fst // interface but it's called (indirectly)by the special matcher. It // attempts to create an arc out of state s, with output label // "olabel" [it works out the input label from the value of "olabel". // It returns true if it is able to create an arc, and false // otherwise. template bool ContextFstImpl::CreateArc(StateId s, Label olabel, Arc *oarc) { // Returns true to indicate the arc exists. if (olabel == 0) return false; // No epsilon-output arcs in this FST. const vector &seq = state_seqs_[s]; if (IsDisambigSymbol(olabel)) { // Disambiguation-symbol arcs.. create self-loop. CreateDisambigArc(s, olabel, oarc); return true; } else if (IsPhoneSymbol(olabel) || olabel == subsequential_symbol_) { // If all is OK, we shift the old sequence left by 1 and push on the new phone. if (olabel != subsequential_symbol_ && !seq.empty() && seq.back() == subsequential_symbol_) { return false; // Phone not allowed to follow subsequential symbol. } if (olabel == subsequential_symbol_ && (P_ == N_-1 || seq[P_] == 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; } vector newseq(N_-1); // seq shifted left by 1. for (int i = 0;i < N_-2;i++) newseq[i] = seq[i+1]; if (N_ > 1) newseq[N_-2] = olabel; vector phoneseq(seq); // copy it before FindState which // possibly changes the address. StateId nextstate = FindState(newseq); phoneseq.push_back(olabel); // Now it's the full context window of size N_. for (int i = 1; i < N_ ; i++) if (phoneseq[i] == subsequential_symbol_) phoneseq[i] = 0; // don't put subseq. symbol on // the output arcs, just 0. return CreatePhoneOrEpsArc(s, nextstate, olabel, phoneseq, oarc); } else { std::cerr << "ContextFst: CreateArc, invalid olabel supplied [confusion about phone list or disambig symbols?]: "<<(olabel); exit(1); } return false; // won't get here. suppress compiler error. } // Note that Expand is not called if we do the composition using // ContextMatcher, which is the normal case. template void ContextFstImpl::Expand(StateId s) { // expands arcs only [not final state weight]. assert(static_cast(s) < state_seqs_.size()); // make sure state exists already. // We just try adding all possible symbols on the output side. Arc arc; if (this->CreateArc(s, subsequential_symbol_, &arc)) this->AddArc(s, arc); for (typename kaldi::ConstIntegerSet