chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:10 +08:00
commit c397331b1e
3684 changed files with 990993 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
all:
EXTRA_CXXFLAGS = -Wno-sign-compare
include ../kaldi.mk
TESTFILES =
OBJFILES = training-graph-compiler.o lattice-simple-decoder.o lattice-faster-decoder.o \
lattice-faster-online-decoder.o simple-decoder.o faster-decoder.o \
decoder-wrappers.o grammar-fst.o decodable-matrix.o \
lattice-incremental-decoder.o lattice-incremental-online-decoder.o
LIBNAME = kaldi-decoder
ADDLIBS = ../lat/kaldi-lat.a ../fstext/kaldi-fstext.a ../hmm/kaldi-hmm.a \
../transform/kaldi-transform.a ../gmm/kaldi-gmm.a \
../tree/kaldi-tree.a ../util/kaldi-util.a ../matrix/kaldi-matrix.a \
../base/kaldi-base.a
include ../makefiles/default_rules.mk
@@ -0,0 +1,502 @@
// decoder/biglm-faster-decoder.h
// Copyright 2009-2011 Microsoft Corporation, 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.
#ifndef KALDI_DECODER_BIGLM_FASTER_DECODER_H_
#define KALDI_DECODER_BIGLM_FASTER_DECODER_H_
#include "util/stl-utils.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "lat/kaldi-lattice.h" // for CompactLatticeArc
#include "decoder/faster-decoder.h" // for options class
#include "fstext/deterministic-fst.h"
namespace kaldi {
struct BiglmFasterDecoderOptions: public FasterDecoderOptions {
BiglmFasterDecoderOptions() {
min_active = 200;
}
};
/** This is as FasterDecoder, but does online composition between
HCLG and the "difference language model", which is a deterministic
FST that represents the difference between the language model you want
and the language model you compiled HCLG with. The class
DeterministicOnDemandFst follows through the epsilons in G for you
(assuming G is a standard backoff language model) and makes it look
like a determinized FST. Actually, in practice,
DeterministicOnDemandFst operates in a mode where it composes two
G's together; one has negated likelihoods and works by removing the
LM probabilities that you made HCLG with, and one is the language model
you want to use.
*/
class BiglmFasterDecoder {
public:
typedef fst::StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
typedef uint64 PairId;
typedef Arc::Weight Weight;
// This constructor is the same as for FasterDecoder, except the second
// argument (lm_diff_fst) is new; it's an FST (actually, a
// DeterministicOnDemandFst) that represents the difference in LM scores
// between the LM we want and the LM the decoding-graph "fst" was built with.
// See e.g. gmm-decode-biglm-faster.cc for an example of how this is called.
// Basically, we are using fst o lm_diff_fst (where o is composition)
// as the decoding graph. Instead of having everything indexed by the state in
// "fst", we now index by the pair of states in (fst, lm_diff_fst).
// Whenever we cross a word, we need to propagate the state within
// lm_diff_fst.
BiglmFasterDecoder(const fst::Fst<fst::StdArc> &fst,
const BiglmFasterDecoderOptions &opts,
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst):
fst_(fst), lm_diff_fst_(lm_diff_fst), opts_(opts), warned_noarc_(false) {
KALDI_ASSERT(opts_.hash_ratio >= 1.0); // less doesn't make much sense.
KALDI_ASSERT(opts_.max_active > 1);
KALDI_ASSERT(fst.Start() != fst::kNoStateId &&
lm_diff_fst->Start() != fst::kNoStateId);
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
}
void SetOptions(const BiglmFasterDecoderOptions &opts) { opts_ = opts; }
~BiglmFasterDecoder() {
ClearToks(toks_.Clear());
}
void Decode(DecodableInterface *decodable) {
// clean up from last time:
ClearToks(toks_.Clear());
PairId start_pair = ConstructPair(fst_.Start(), lm_diff_fst_->Start());
Arc dummy_arc(0, 0, Weight::One(), fst_.Start()); // actually, the last element of
// the Arcs (fst_.Start(), here) is never needed.
toks_.Insert(start_pair, new Token(dummy_arc, NULL));
ProcessNonemitting(std::numeric_limits<float>::max());
for (int32 frame = 0; !decodable->IsLastFrame(frame-1); frame++) {
BaseFloat weight_cutoff = ProcessEmitting(decodable, frame);
ProcessNonemitting(weight_cutoff);
}
}
bool ReachedFinal() {
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
PairId state_pair = e->key;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
Weight this_weight =
Times(e->val->weight_,
Times(fst_.Final(state), lm_diff_fst_->Final(lm_state)));
if (this_weight != Weight::Zero())
return true;
}
return false;
}
bool GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
bool use_final_probs = true) {
// GetBestPath gets the decoding output. If "use_final_probs" is true
// AND we reached a final state, it limits itself to final states;
// otherwise it gets the most likely token not taking into
// account final-probs. fst_out will be empty (Start() == kNoStateId) if
// nothing was available. It returns true if it got output (thus, fst_out
// will be nonempty).
fst_out->DeleteStates();
Token *best_tok = NULL;
Weight best_final = Weight::Zero(); // set only if is_final == true. The
// final-prob corresponding to the best final token (i.e. the one with best
// weight best_weight, below).
bool is_final = ReachedFinal();
if (!is_final) {
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
if (best_tok == NULL || *best_tok < *(e->val) )
best_tok = e->val;
} else {
Weight best_weight = Weight::Zero();
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
Weight fst_final = fst_.Final(PairToState(e->key)),
lm_final = lm_diff_fst_->Final(PairToLmState(e->key)),
final = Times(fst_final, lm_final);
Weight this_weight = Times(e->val->weight_, final);
if (this_weight != Weight::Zero() &&
this_weight.Value() < best_weight.Value()) {
best_weight = this_weight;
best_final = final;
best_tok = e->val;
}
}
}
if (best_tok == NULL) return false; // No output.
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_) {
BaseFloat tot_cost = tok->weight_.Value() -
(tok->prev_ ? tok->prev_->weight_.Value() : 0.0),
graph_cost = tok->arc_.weight.Value(),
ac_cost = tot_cost - graph_cost;
LatticeArc l_arc(tok->arc_.ilabel,
tok->arc_.olabel,
LatticeWeight(graph_cost, ac_cost),
tok->arc_.nextstate);
arcs_reverse.push_back(l_arc);
}
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
StateId cur_state = fst_out->AddState();
fst_out->SetStart(cur_state);
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
LatticeArc arc = arcs_reverse[i];
arc.nextstate = fst_out->AddState();
fst_out->AddArc(cur_state, arc);
cur_state = arc.nextstate;
}
if (is_final && use_final_probs) {
fst_out->SetFinal(cur_state, LatticeWeight(best_final.Value(), 0.0));
} else {
fst_out->SetFinal(cur_state, LatticeWeight::One());
}
RemoveEpsLocal(fst_out);
return true;
}
private:
inline PairId ConstructPair(StateId fst_state, StateId lm_state) {
return static_cast<PairId>(fst_state) + (static_cast<PairId>(lm_state) << 32);
}
static inline StateId PairToState(PairId state_pair) {
return static_cast<StateId>(static_cast<uint32>(state_pair));
}
static inline StateId PairToLmState(PairId state_pair) {
return static_cast<StateId>(static_cast<uint32>(state_pair >> 32));
}
class Token {
public:
Arc arc_; // contains only the graph part of the cost,
// including the part in "fst" (== HCLG) plus lm_diff_fst.
// We can work out the acoustic part from difference between
// "weight_" and prev->weight_.
Token *prev_;
int32 ref_count_;
Weight weight_; // weight up to current point.
inline Token(const Arc &arc, Weight &ac_weight, Token *prev):
arc_(arc), prev_(prev), ref_count_(1) {
if (prev) {
prev->ref_count_++;
weight_ = Times(Times(prev->weight_, arc.weight), ac_weight);
} else {
weight_ = Times(arc.weight, ac_weight);
}
}
inline Token(const Arc &arc, Token *prev):
arc_(arc), prev_(prev), ref_count_(1) {
if (prev) {
prev->ref_count_++;
weight_ = Times(prev->weight_, arc.weight);
} else {
weight_ = arc.weight;
}
}
inline bool operator < (const Token &other) {
return weight_.Value() > other.weight_.Value();
// This makes sense for log + tropical semiring.
}
inline ~Token() {
KALDI_ASSERT(ref_count_ == 1);
if (prev_ != NULL) TokenDelete(prev_);
}
inline static void TokenDelete(Token *tok) {
if (tok->ref_count_ == 1) {
delete tok;
} else {
tok->ref_count_--;
}
}
};
typedef HashList<PairId, Token*>::Elem Elem;
/// Gets the weight cutoff. Also counts the active tokens.
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
BaseFloat *adaptive_beam, Elem **best_elem) {
BaseFloat best_weight = 1.0e+10; // positive == high cost == bad.
size_t count = 0;
if (opts_.max_active == std::numeric_limits<int32>::max() &&
opts_.min_active == 0) {
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
BaseFloat w = static_cast<BaseFloat>(e->val->weight_.Value());
if (w < best_weight) {
best_weight = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
if (adaptive_beam != NULL) *adaptive_beam = opts_.beam;
return best_weight + opts_.beam;
} else {
tmp_array_.clear();
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
BaseFloat w = e->val->weight_.Value();
tmp_array_.push_back(w);
if (w < best_weight) {
best_weight = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
BaseFloat beam_cutoff = best_weight + opts_.beam,
min_active_cutoff = std::numeric_limits<BaseFloat>::infinity(),
max_active_cutoff = std::numeric_limits<BaseFloat>::infinity();
if (tmp_array_.size() > static_cast<size_t>(opts_.max_active)) {
std::nth_element(tmp_array_.begin(),
tmp_array_.begin() + opts_.max_active,
tmp_array_.end());
max_active_cutoff = tmp_array_[opts_.max_active];
}
if (tmp_array_.size() > static_cast<size_t>(opts_.min_active)) {
if (opts_.min_active == 0) min_active_cutoff = best_weight;
else {
std::nth_element(tmp_array_.begin(),
tmp_array_.begin() + opts_.min_active,
tmp_array_.size() > static_cast<size_t>(opts_.max_active) ?
tmp_array_.begin() + opts_.max_active :
tmp_array_.end());
min_active_cutoff = tmp_array_[opts_.min_active];
}
}
if (max_active_cutoff < beam_cutoff) { // max_active is tighter than beam.
if (adaptive_beam)
*adaptive_beam = max_active_cutoff - best_weight + opts_.beam_delta;
return max_active_cutoff;
} else if (min_active_cutoff > beam_cutoff) { // min_active is looser than beam.
if (adaptive_beam)
*adaptive_beam = min_active_cutoff - best_weight + opts_.beam_delta;
return min_active_cutoff;
} else {
*adaptive_beam = opts_.beam;
return beam_cutoff;
}
}
}
void PossiblyResizeHash(size_t num_toks) {
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
* opts_.hash_ratio);
if (new_sz > toks_.Size()) {
toks_.SetSize(new_sz);
}
}
inline StateId PropagateLm(StateId lm_state,
Arc *arc) { // returns new LM state.
if (arc->olabel == 0) {
return lm_state; // no change in LM state if no word crossed.
} else { // Propagate in the LM-diff FST.
Arc lm_arc;
bool ans = lm_diff_fst_->GetArc(lm_state, arc->olabel, &lm_arc);
if (!ans) { // this case is unexpected for statistical LMs.
if (!warned_noarc_) {
warned_noarc_ = true;
KALDI_WARN << "No arc available in LM (unlikely to be correct "
"if a statistical language model); will not warn again";
}
arc->weight = Weight::Zero();
return lm_state; // doesn't really matter what we return here; will
// be pruned.
} else {
arc->weight = Times(arc->weight, lm_arc.weight);
arc->olabel = lm_arc.olabel; // probably will be the same.
return lm_arc.nextstate; // return the new LM state.
}
}
}
// ProcessEmitting returns the likelihood cutoff used.
BaseFloat ProcessEmitting(DecodableInterface *decodable, int frame) {
Elem *last_toks = toks_.Clear();
size_t tok_cnt;
BaseFloat adaptive_beam;
Elem *best_elem = NULL;
BaseFloat weight_cutoff = GetCutoff(last_toks, &tok_cnt,
&adaptive_beam, &best_elem);
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
// This is the cutoff we use after adding in the log-likes (i.e.
// for the next frame). This is a bound on the cutoff we will use
// on the next frame.
BaseFloat next_weight_cutoff = 1.0e+10;
// First process the best token to get a hopefully
// reasonably tight bound on the next cutoff.
if (best_elem) {
PairId state_pair = best_elem->key;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
Token *tok = best_elem->val;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0) { // we'd propagate..
PropagateLm(lm_state, &arc); // may affect "arc.weight".
// We don't need the return value (the new LM state).
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel),
new_weight = arc.weight.Value() + tok->weight_.Value() + ac_cost;
if (new_weight + adaptive_beam < next_weight_cutoff)
next_weight_cutoff = new_weight + adaptive_beam;
}
}
}
// the tokens are now owned here, in last_toks, and the hash is empty.
// 'owned' is a complex thing here; the point is we need to call toks_.Delete(e)
// on each elem 'e' to let toks_ know we're done with them.
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) { // loop this way
// because we delete "e" as we go.
PairId state_pair = e->key;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
Token *tok = e->val;
if (tok->weight_.Value() < weight_cutoff) { // not pruned.
KALDI_ASSERT(state == tok->arc_.nextstate);
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0) { // propagate.
StateId next_lm_state = PropagateLm(lm_state, &arc);
Weight ac_weight(-decodable->LogLikelihood(frame, arc.ilabel));
BaseFloat new_weight = arc.weight.Value() + tok->weight_.Value()
+ ac_weight.Value();
if (new_weight < next_weight_cutoff) { // not pruned..
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
Token *new_tok = new Token(arc, ac_weight, tok);
Elem *e_found = toks_.Insert(next_pair, new_tok);
if (new_weight + adaptive_beam < next_weight_cutoff)
next_weight_cutoff = new_weight + adaptive_beam;
if (e_found->val != new_tok) {
if (*(e_found->val) < *new_tok) {
Token::TokenDelete(e_found->val);
e_found->val = new_tok;
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
}
e_tail = e->tail;
Token::TokenDelete(e->val);
toks_.Delete(e);
}
return next_weight_cutoff;
}
// TODO: first time we go through this, could avoid using the queue.
void ProcessNonemitting(BaseFloat cutoff) {
// Processes nonemitting arcs for one frame.
KALDI_ASSERT(queue_.empty());
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
queue_.push_back(e);
while (!queue_.empty()) {
const Elem *e = queue_.back();
queue_.pop_back();
PairId state_pair = e->key;
Token *tok = e->val; // would segfault if state not
// in toks_ but this can't happen.
if (tok->weight_.Value() > cutoff) { // Don't bother processing successors.
continue;
}
KALDI_ASSERT(tok != NULL);
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc_ref = aiter.Value();
if (arc_ref.ilabel == 0) { // propagate nonemitting only...
Arc arc(arc_ref);
StateId next_lm_state = PropagateLm(lm_state, &arc);
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
Token *new_tok = new Token(arc, tok);
if (new_tok->weight_.Value() > cutoff) { // prune
Token::TokenDelete(new_tok);
} else {
Elem *e_found = toks_.Insert(next_pair, new_tok);
if (e_found->val == new_tok) {
queue_.push_back(e_found);
} else {
if ( *(e_found->val) < *new_tok ) {
Token::TokenDelete(e_found->val);
e_found->val = new_tok;
queue_.push_back(e_found);
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
}
}
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
// more than one list (e.g. for current and previous frames), but only one of
// them at a time can be indexed by PairId.
HashList<PairId, Token*> toks_;
const fst::Fst<fst::StdArc> &fst_;
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst_;
BiglmFasterDecoderOptions opts_;
bool warned_noarc_;
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
// make it class member to avoid internal new/delete.
// It might seem unclear why we call ClearToks(toks_.Clear()).
// There are two separate cleanup tasks we need to do at when we start a new file.
// one is to delete the Token objects in the list; the other is to delete
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
// this way for convenience in propagating tokens from one frame to the next.
void ClearToks(Elem *list) {
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
Token::TokenDelete(e->val);
e_tail = e->tail;
toks_.Delete(e);
}
}
KALDI_DISALLOW_COPY_AND_ASSIGN(BiglmFasterDecoder);
};
} // end namespace kaldi.
#endif
@@ -0,0 +1,69 @@
// decoder/decodable-mapped.h
// Copyright 2009-2011 Saarland University; Microsoft Corporation;
// Lukas Burget
// 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_DECODER_DECODABLE_MAPPED_H_
#define KALDI_DECODER_DECODABLE_MAPPED_H_
#include <vector>
#include "base/kaldi-common.h"
#include "itf/decodable-itf.h"
namespace kaldi {
// The DecodableMapped object is initialized by a normal decodable object,
// and a vector that maps indices. The "pdf index" into this decodable object
// is the index into the vector, and the value it finds there is used
// to index into the base decodable object.
class DecodableMapped: public DecodableInterface {
public:
DecodableMapped(const std::vector<int32> &index_map, DecodableInterface *d):
index_map_(index_map), decodable_(d) { }
// Note, frames are numbered from zero. But state_index is numbered
// from one (this routine is called by FSTs).
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
KALDI_ASSERT(static_cast<size_t>(state_index) < index_map_.size());
return decodable_->LogLikelihood(frame, index_map_[state_index]);
}
// note: indices are assumed to be numbered from one, so
// NumIndices() will be the same as the largest index.
virtual int32 NumIndices() const { return static_cast<int32>(index_map_.size()) - 1; }
virtual bool IsLastFrame(int32 frame) const {
// We require all the decodables have the same #frames. We don't check this though.
return decodable_->IsLastFrame(frame);
}
private:
std::vector<int32> index_map_;
DecodableInterface *decodable_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMapped);
};
} // namespace kaldi
#endif // KALDI_DECODER_DECODABLE_MAPPED_H_
@@ -0,0 +1,112 @@
// decoder/decodable-matrix.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 "decoder/decodable-matrix.h"
namespace kaldi {
DecodableMatrixMapped::DecodableMatrixMapped(
const TransitionInformation &tm,
const MatrixBase<BaseFloat> &likes,
int32 frame_offset):
trans_model_(tm),
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
likes_(&likes), likes_to_delete_(NULL),
frame_offset_(frame_offset) {
stride_ = likes.Stride();
raw_data_ = likes.Data() - (stride_ * frame_offset);
if (likes.NumCols() != tm.NumPdfs())
KALDI_ERR << "Mismatch, matrix has "
<< likes.NumCols() << " cols but transition-model has "
<< tm.NumPdfs() << " pdf-ids.";
}
DecodableMatrixMapped::DecodableMatrixMapped(
const TransitionInformation &tm, const Matrix<BaseFloat> *likes,
int32 frame_offset):
trans_model_(tm),
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
likes_(likes), likes_to_delete_(likes),
frame_offset_(frame_offset) {
stride_ = likes->Stride();
raw_data_ = likes->Data() - (stride_ * frame_offset_);
if (likes->NumCols() != tm.NumPdfs())
KALDI_ERR << "Mismatch, matrix has "
<< likes->NumCols() << " cols but transition-model has "
<< tm.NumPdfs() << " pdf-ids.";
}
BaseFloat DecodableMatrixMapped::LogLikelihood(int32 frame, int32 tid) {
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
int32 pdf_id = tid_to_pdf_[tid];
#ifdef KALDI_PARANOID
return (*likes_)(frame - frame_offset_, pdf_id);
#else
return raw_data_[frame * stride_ + pdf_id];
#endif
}
int32 DecodableMatrixMapped::NumFramesReady() const {
return frame_offset_ + likes_->NumRows();
}
bool DecodableMatrixMapped::IsLastFrame(int32 frame) const {
KALDI_ASSERT(frame < NumFramesReady());
return (frame == NumFramesReady() - 1);
}
// Indices are one-based! This is for compatibility with OpenFst.
int32 DecodableMatrixMapped::NumIndices() const {
return trans_model_.NumTransitionIds();
}
DecodableMatrixMapped::~DecodableMatrixMapped() {
delete likes_to_delete_;
}
void DecodableMatrixMappedOffset::AcceptLoglikes(
Matrix<BaseFloat> *loglikes, int32 frames_to_discard) {
if (loglikes->NumRows() == 0) return;
KALDI_ASSERT(loglikes->NumCols() == trans_model_.NumPdfs());
KALDI_ASSERT(frames_to_discard <= loglikes_.NumRows() &&
frames_to_discard >= 0);
if (frames_to_discard == loglikes_.NumRows()) {
loglikes_.Swap(loglikes);
loglikes->Resize(0, 0);
} else {
int32 old_rows_kept = loglikes_.NumRows() - frames_to_discard,
new_num_rows = old_rows_kept + loglikes->NumRows();
Matrix<BaseFloat> new_loglikes(new_num_rows, loglikes->NumCols());
new_loglikes.RowRange(0, old_rows_kept).CopyFromMat(
loglikes_.RowRange(frames_to_discard, old_rows_kept));
new_loglikes.RowRange(old_rows_kept, loglikes->NumRows()).CopyFromMat(
*loglikes);
loglikes_.Swap(&new_loglikes);
}
frame_offset_ += frames_to_discard;
stride_ = loglikes_.Stride();
raw_data_ = loglikes_.Data() - (frame_offset_ * stride_);
}
} // end namespace kaldi.
@@ -0,0 +1,253 @@
// decoder/decodable-matrix.h
// Copyright 2009-2011 Microsoft Corporation
// 2013 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_DECODER_DECODABLE_MATRIX_H_
#define KALDI_DECODER_DECODABLE_MATRIX_H_
#include <vector>
#include "base/kaldi-common.h"
#include "itf/decodable-itf.h"
#include "itf/transition-information.h"
#include "matrix/kaldi-matrix.h"
namespace kaldi {
class DecodableMatrixScaledMapped: public DecodableInterface {
public:
// This constructor creates an object that will not delete "likes" when done.
DecodableMatrixScaledMapped(const TransitionInformation &tm,
const Matrix<BaseFloat> &likes,
BaseFloat scale): trans_model_(tm), likes_(&likes),
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
scale_(scale), delete_likes_(false) {
if (likes.NumCols() != tm.NumPdfs())
KALDI_ERR << "DecodableMatrixScaledMapped: mismatch, matrix has "
<< likes.NumCols() << " cols but transition-model has "
<< tm.NumPdfs() << " pdf-ids.";
}
// This constructor creates an object that will delete "likes"
// when done.
DecodableMatrixScaledMapped(const TransitionInformation &tm,
BaseFloat scale,
const Matrix<BaseFloat> *likes):
trans_model_(tm), likes_(likes),
tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
scale_(scale), delete_likes_(true) {
if (likes->NumCols() != tm.NumPdfs())
KALDI_ERR << "DecodableMatrixScaledMapped: mismatch, matrix has "
<< likes->NumCols() << " cols but transition-model has "
<< tm.NumPdfs() << " pdf-ids.";
}
virtual int32 NumFramesReady() const { return likes_->NumRows(); }
virtual bool IsLastFrame(int32 frame) const {
KALDI_ASSERT(frame < NumFramesReady());
return (frame == NumFramesReady() - 1);
}
// Note, frames are numbered from zero.
virtual BaseFloat LogLikelihood(int32 frame, int32 tid) {
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
return scale_ * (*likes_)(frame, tid_to_pdf_[tid]);
}
// Indices are one-based! This is for compatibility with OpenFst.
virtual int32 NumIndices() const { return trans_model_.NumTransitionIds(); }
virtual ~DecodableMatrixScaledMapped() {
if (delete_likes_) delete likes_;
}
private:
const TransitionInformation &trans_model_; // for tid to pdf mapping
const Matrix<BaseFloat> *likes_;
const std::vector<int32> &tid_to_pdf_;
BaseFloat scale_;
bool delete_likes_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixScaledMapped);
};
/**
This is like DecodableMatrixScaledMapped, but it doesn't support an acoustic
scale, and it does support a frame offset, whereby you can state that the
first row of 'likes' is actually the n'th row of the matrix of available
log-likelihoods. It's useful if the neural net output comes in chunks for
different frame ranges.
Note: DecodableMatrixMappedOffset solves the same problem in a slightly
different way, where you use the same decodable object. This one, unlike
DecodableMatrixMappedOffset, is compatible with when the loglikes are in a
SubMatrix.
*/
class DecodableMatrixMapped: public DecodableInterface {
public:
// This constructor creates an object that will not delete "likes" when done.
// the frame_offset is the frame the row 0 of 'likes' corresponds to, would be
// greater than one if this is not the first chunk of likelihoods.
DecodableMatrixMapped(const TransitionInformation &tm,
const MatrixBase<BaseFloat> &likes,
int32 frame_offset = 0);
// This constructor creates an object that will delete "likes"
// when done.
DecodableMatrixMapped(const TransitionInformation &tm,
const Matrix<BaseFloat> *likes,
int32 frame_offset = 0);
virtual int32 NumFramesReady() const;
virtual bool IsLastFrame(int32 frame) const;
virtual BaseFloat LogLikelihood(int32 frame, int32 tid);
// Note: these indices are 1-based.
virtual int32 NumIndices() const;
virtual ~DecodableMatrixMapped();
private:
const TransitionInformation &trans_model_; // for tid to pdf mapping
const std::vector<int32>& tid_to_pdf_;
const MatrixBase<BaseFloat> *likes_;
const Matrix<BaseFloat> *likes_to_delete_;
int32 frame_offset_;
// raw_data_ and stride_ are a kind of fast look-aside for 'likes_', to be
// used when KALDI_PARANOID is false.
const BaseFloat *raw_data_;
int32 stride_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixMapped);
};
/**
This decodable class returns log-likes stored in a matrix; it supports
repeatedly writing to the matrix and setting a time-offset representing the
frame-index of the first row of the matrix. It's intended for use in
multi-threaded decoding; mutex and semaphores are not included. External
code will call SetLoglikes() each time more log-likelihods are available.
If you try to access a log-likelihood that's no longer available because
the frame index is less than the current offset, it is of course an error.
See also DecodableMatrixMapped, which supports the same type of thing but
with a different interface where you are expected to re-construct the
object each time you want to decode.
*/
class DecodableMatrixMappedOffset: public DecodableInterface {
public:
DecodableMatrixMappedOffset(const TransitionInformation &tm):
trans_model_(tm), tid_to_pdf_(trans_model_.TransitionIdToPdfArray()),
frame_offset_(0), input_is_finished_(false) { }
// this is not part of the generic Decodable interface.
int32 FirstAvailableFrame() const { return frame_offset_; }
// Logically, this function appends 'loglikes' (interpreted as newly available
// frames) to the log-likelihoods stored in the class.
//
// This function is destructive of the input "loglikes" because it may
// under some circumstances do a shallow copy using Swap(). This function
// appends loglikes to any existing likelihoods you've previously supplied.
void AcceptLoglikes(Matrix<BaseFloat> *loglikes,
int32 frames_to_discard);
void InputIsFinished() { input_is_finished_ = true; }
virtual int32 NumFramesReady() const {
return loglikes_.NumRows() + frame_offset_;
}
virtual bool IsLastFrame(int32 frame) const {
KALDI_ASSERT(frame < NumFramesReady());
return (frame == NumFramesReady() - 1 && input_is_finished_);
}
virtual BaseFloat LogLikelihood(int32 frame, int32 tid) {
KALDI_PARANOID_ASSERT(tid >= 1 && tid < tid_to_pdf_.size());
int32 pdf_id = tid_to_pdf_[tid];
#ifdef KALDI_PARANOID
return loglikes_(frame - frame_offset_, pdf_id);
#else
// This does no checking, so will be faster.
return raw_data_[frame * stride_ + pdf_id];
#endif
}
virtual int32 NumIndices() const { return trans_model_.NumTransitionIds(); }
// nothing special to do in destructor.
virtual ~DecodableMatrixMappedOffset() { }
private:
const TransitionInformation &trans_model_; // for tid to pdf mapping
const std::vector<int32>& tid_to_pdf_;
Matrix<BaseFloat> loglikes_;
int32 frame_offset_;
bool input_is_finished_;
// 'raw_data_' and 'stride_' are intended as a fast look-aside which is an
// alternative to accessing data_. raw_data_ is a faked version of
// data_->Data() as if it started from frame zero rather than frame_offset_.
// This simplifies the code of LogLikelihood(), in cases where KALDI_PARANOID
// is not defined.
BaseFloat *raw_data_;
int32 stride_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixMappedOffset);
};
class DecodableMatrixScaled: public DecodableInterface {
public:
DecodableMatrixScaled(const Matrix<BaseFloat> &likes,
BaseFloat scale):
likes_(likes), scale_(scale) { }
virtual int32 NumFramesReady() const { return likes_.NumRows(); }
virtual bool IsLastFrame(int32 frame) const {
KALDI_ASSERT(frame < NumFramesReady());
return (frame == NumFramesReady() - 1);
}
// Note, frames are numbered from zero.
virtual BaseFloat LogLikelihood(int32 frame, int32 index) {
if (index > likes_.NumCols() || index <= 0 ||
frame < 0 || frame >= likes_.NumRows())
KALDI_ERR << "Invalid (frame, index - 1) = ("
<< frame << ", " << index - 1 << ") for matrix of size "
<< likes_.NumRows() << " x " << likes_.NumCols();
return scale_ * likes_(frame, index - 1);
}
// Indices are one-based! This is for compatibility with OpenFst.
virtual int32 NumIndices() const { return likes_.NumCols(); }
private:
const Matrix<BaseFloat> &likes_;
BaseFloat scale_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableMatrixScaled);
};
} // namespace kaldi
#endif // KALDI_DECODER_DECODABLE_MATRIX_H_
@@ -0,0 +1,109 @@
// decoder/decodable-sum.h
// Copyright 2009-2011 Saarland University; Microsoft Corporation;
// Lukas Burget, Pawel Swietojanski
// 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_DECODER_DECODABLE_SUM_H_
#define KALDI_DECODER_DECODABLE_SUM_H_
#include <vector>
#include <utility>
#include "base/kaldi-common.h"
#include "itf/decodable-itf.h"
namespace kaldi {
// The DecodableSum object is a very simple object that just sums
// scores over a number of Decodable objects. They must all have
// the same dimensions.
class DecodableSum: public DecodableInterface {
public:
// Does not take ownership of pointers! They are just
// pointers because they are non-const.
DecodableSum(DecodableInterface *d1, BaseFloat w1,
DecodableInterface *d2, BaseFloat w2) {
decodables_.push_back(std::make_pair(d1, w1));
decodables_.push_back(std::make_pair(d2, w2));
CheckSizes();
}
// Does not take ownership of pointers!
DecodableSum(
const std::vector<std::pair<DecodableInterface*, BaseFloat> > &decodables) :
decodables_(decodables) { CheckSizes(); }
void CheckSizes() const {
KALDI_ASSERT(decodables_.size() >= 1
&& decodables_[0].first != NULL);
for (size_t i = 1; i < decodables_.size(); i++)
KALDI_ASSERT(decodables_[i].first != NULL &&
decodables_[i].first->NumIndices() ==
decodables_[0].first->NumIndices());
}
// Note, frames are numbered from zero. But state_index is numbered
// from one (this routine is called by FSTs).
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
BaseFloat sum = 0.0;
// int32 i=1;
for (std::vector<std::pair<DecodableInterface*, BaseFloat> >::iterator iter = decodables_.begin();
iter != decodables_.end();
++iter) {
sum += iter->first->LogLikelihood(frame, state_index) * iter->second;
}
return sum;
}
virtual int32 NumIndices() const { return decodables_[0].first->NumIndices(); }
virtual bool IsLastFrame(int32 frame) const {
// We require all the decodables have the same #frames. We don't check this though.
return decodables_[0].first->IsLastFrame(frame);
}
private:
std::vector<std::pair<DecodableInterface*, BaseFloat> > decodables_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableSum);
};
class DecodableSumScaled : public DecodableSum {
public:
DecodableSumScaled(DecodableInterface *d1, BaseFloat w1,
DecodableInterface *d2, BaseFloat w2,
BaseFloat scale)
: DecodableSum(d1, w1, d2, w2), scale_(scale) {}
DecodableSumScaled(const std::vector<std::pair<DecodableInterface*, BaseFloat> > &decodables,
BaseFloat scale)
: DecodableSum(decodables), scale_(scale) {}
virtual BaseFloat LogLikelihood(int32 frame, int32 state_index) {
return scale_ * DecodableSum::LogLikelihood(frame, state_index);
}
private:
BaseFloat scale_;
KALDI_DISALLOW_COPY_AND_ASSIGN(DecodableSumScaled);
};
} // namespace kaldi
#endif // KALDI_DECODER_DECODABLE_SUM_H_
@@ -0,0 +1,665 @@
// decoder/decoder-wrappers.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 "decoder/decoder-wrappers.h"
#include "decoder/faster-decoder.h"
#include "decoder/lattice-faster-decoder.h"
#include "decoder/grammar-fst.h"
#include "lat/lattice-functions.h"
namespace kaldi {
DecodeUtteranceLatticeFasterClass::DecodeUtteranceLatticeFasterClass(
LatticeFasterDecoder *decoder,
DecodableInterface *decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
const std::string &utt,
BaseFloat acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignments_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_sum, // on success, adds likelihood to this.
int64 *frame_sum, // on success, adds #frames to this.
int32 *num_done, // on success (including partial decode), increments this.
int32 *num_err, // on failure, increments this.
int32 *num_partial): // If partial decode (final-state not reached), increments this.
decoder_(decoder), decodable_(decodable), trans_model_(&trans_model),
word_syms_(word_syms), utt_(utt), acoustic_scale_(acoustic_scale),
determinize_(determinize), allow_partial_(allow_partial),
alignments_writer_(alignments_writer),
words_writer_(words_writer),
compact_lattice_writer_(compact_lattice_writer),
lattice_writer_(lattice_writer),
like_sum_(like_sum), frame_sum_(frame_sum),
num_done_(num_done), num_err_(num_err),
num_partial_(num_partial),
computed_(false), success_(false), partial_(false),
clat_(NULL), lat_(NULL) { }
void DecodeUtteranceLatticeFasterClass::operator () () {
// Decoding and lattice determinization happens here.
computed_ = true; // Just means this function was called-- a check on the
// calling code.
success_ = true;
using fst::VectorFst;
if (!decoder_->Decode(decodable_)) {
KALDI_WARN << "Failed to decode utterance with id " << utt_;
success_ = false;
}
if (!decoder_->ReachedFinal()) {
if (allow_partial_) {
KALDI_WARN << "Outputting partial output for utterance " << utt_
<< " since no final-state reached\n";
partial_ = true;
} else {
KALDI_WARN << "Not producing output for utterance " << utt_
<< " since no final-state reached and "
<< "--allow-partial=false.\n";
success_ = false;
}
}
if (!success_) return;
// Get lattice, and do determinization if requested.
lat_ = new Lattice;
decoder_->GetRawLattice(lat_);
if (lat_->NumStates() == 0)
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt_;
fst::Connect(lat_);
if (determinize_) {
clat_ = new CompactLattice;
if (!DeterminizeLatticePhonePrunedWrapper(
*trans_model_,
lat_,
decoder_->GetOptions().lattice_beam,
clat_,
decoder_->GetOptions().det_opts))
KALDI_WARN << "Determinization finished earlier than the beam for "
<< "utterance " << utt_;
delete lat_;
lat_ = NULL;
// We'll write the lattice without acoustic scaling.
if (acoustic_scale_ != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale_), clat_);
} else {
// We'll write the lattice without acoustic scaling.
if (acoustic_scale_ != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale_), lat_);
}
}
DecodeUtteranceLatticeFasterClass::~DecodeUtteranceLatticeFasterClass() {
if (!computed_)
KALDI_ERR << "Destructor called without operator (), error in calling code.";
if (!success_) {
if (num_err_ != NULL) (*num_err_)++;
} else { // successful decode.
// Getting the one-best output is lightweight enough that we can do it in
// the destructor (easier than adding more variables to the class, and
// will rarely slow down the main thread.)
double likelihood;
LatticeWeight weight;
int32 num_frames;
{ // First do some stuff with word-level traceback...
// This is basically for diagnostics.
fst::VectorFst<LatticeArc> decoded;
decoder_->GetBestPath(&decoded);
if (decoded.NumStates() == 0) {
// Shouldn't really reach this point as already checked success.
KALDI_ERR << "Failed to get traceback for utterance " << utt_;
}
std::vector<int32> alignment;
std::vector<int32> words;
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
num_frames = alignment.size();
if (words_writer_->IsOpen())
words_writer_->Write(utt_, words);
if (alignments_writer_->IsOpen())
alignments_writer_->Write(utt_, alignment);
if (word_syms_ != NULL) {
std::cerr << utt_ << ' ';
for (size_t i = 0; i < words.size(); i++) {
std::string s = word_syms_->Find(words[i]);
if (s == "")
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
std::cerr << s << ' ';
}
std::cerr << '\n';
}
likelihood = -(weight.Value1() + weight.Value2());
}
// Ouptut the lattices.
if (determinize_) { // CompactLattice output.
KALDI_ASSERT(compact_lattice_writer_ != NULL && clat_ != NULL);
if (clat_->NumStates() == 0) {
KALDI_WARN << "Empty lattice for utterance " << utt_;
} else {
compact_lattice_writer_->Write(utt_, *clat_);
}
delete clat_;
clat_ = NULL;
} else {
KALDI_ASSERT(lattice_writer_ != NULL && lat_ != NULL);
if (lat_->NumStates() == 0) {
KALDI_WARN << "Empty lattice for utterance " << utt_;
} else {
lattice_writer_->Write(utt_, *lat_);
}
delete lat_;
lat_ = NULL;
}
// Print out logging information.
KALDI_LOG << "Log-like per frame for utterance " << utt_ << " is "
<< (likelihood / num_frames) << " over "
<< num_frames << " frames.";
KALDI_VLOG(2) << "Cost for utterance " << utt_ << " is "
<< weight.Value1() << " + " << weight.Value2();
// Now output the various diagnostic variables.
if (like_sum_ != NULL) *like_sum_ += likelihood;
if (frame_sum_ != NULL) *frame_sum_ += num_frames;
if (num_done_ != NULL) (*num_done_)++;
if (partial_ && num_partial_ != NULL) (*num_partial_)++;
}
// We were given ownership of these two objects that were passed in in
// the initializer.
delete decoder_;
delete decodable_;
}
template <typename FST>
bool DecodeUtteranceLatticeIncremental(
LatticeIncrementalDecoderTpl<FST> &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr) { // puts utterance's like in like_ptr on success.
using fst::VectorFst;
if (!decoder.Decode(&decodable)) {
KALDI_WARN << "Failed to decode utterance with id " << utt;
return false;
}
if (!decoder.ReachedFinal()) {
if (allow_partial) {
KALDI_WARN << "Outputting partial output for utterance " << utt
<< " since no final-state reached\n";
} else {
KALDI_WARN << "Not producing output for utterance " << utt
<< " since no final-state reached and "
<< "--allow-partial=false.\n";
return false;
}
}
// Get lattice
CompactLattice clat = decoder.GetLattice(decoder.NumFramesDecoded(), true);
if (clat.NumStates() == 0)
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
double likelihood;
LatticeWeight weight;
int32 num_frames;
{ // First do some stuff with word-level traceback...
CompactLattice decoded_clat;
CompactLatticeShortestPath(clat, &decoded_clat);
Lattice decoded;
fst::ConvertLattice(decoded_clat, &decoded);
if (decoded.Start() == fst::kNoStateId)
// Shouldn't really reach this point as already checked success.
KALDI_ERR << "Failed to get traceback for utterance " << utt;
std::vector<int32> alignment;
std::vector<int32> words;
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
num_frames = alignment.size();
KALDI_ASSERT(num_frames == decoder.NumFramesDecoded());
if (words_writer->IsOpen())
words_writer->Write(utt, words);
if (alignment_writer->IsOpen())
alignment_writer->Write(utt, alignment);
if (word_syms != NULL) {
std::cerr << utt << ' ';
for (size_t i = 0; i < words.size(); i++) {
std::string s = word_syms->Find(words[i]);
if (s == "")
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
std::cerr << s << ' ';
}
std::cerr << '\n';
}
likelihood = -(weight.Value1() + weight.Value2());
}
// We'll write the lattice without acoustic scaling.
if (acoustic_scale != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
Connect(&clat);
compact_lattice_writer->Write(utt, clat);
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
<< (likelihood / num_frames) << " over "
<< num_frames << " frames.";
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
<< weight.Value1() << " + " << weight.Value2();
*like_ptr = likelihood;
return true;
}
// Takes care of output. Returns true on success.
template <typename FST>
bool DecodeUtteranceLatticeFaster(
LatticeFasterDecoderTpl<FST> &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr) { // puts utterance's like in like_ptr on success.
using fst::VectorFst;
if (!decoder.Decode(&decodable)) {
KALDI_WARN << "Failed to decode utterance with id " << utt;
return false;
}
if (!decoder.ReachedFinal()) {
if (allow_partial) {
KALDI_WARN << "Outputting partial output for utterance " << utt
<< " since no final-state reached\n";
} else {
KALDI_WARN << "Not producing output for utterance " << utt
<< " since no final-state reached and "
<< "--allow-partial=false.\n";
return false;
}
}
double likelihood;
LatticeWeight weight;
int32 num_frames;
{ // First do some stuff with word-level traceback...
VectorFst<LatticeArc> decoded;
if (!decoder.GetBestPath(&decoded))
// Shouldn't really reach this point as already checked success.
KALDI_ERR << "Failed to get traceback for utterance " << utt;
std::vector<int32> alignment;
std::vector<int32> words;
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
num_frames = alignment.size();
if (words_writer->IsOpen())
words_writer->Write(utt, words);
if (alignment_writer->IsOpen())
alignment_writer->Write(utt, alignment);
if (word_syms != NULL) {
std::cerr << utt << ' ';
for (size_t i = 0; i < words.size(); i++) {
std::string s = word_syms->Find(words[i]);
if (s == "")
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
std::cerr << s << ' ';
}
std::cerr << '\n';
}
likelihood = -(weight.Value1() + weight.Value2());
}
// Get lattice, and do determinization if requested.
Lattice lat;
decoder.GetRawLattice(&lat);
if (lat.NumStates() == 0)
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
fst::Connect(&lat);
if (determinize) {
CompactLattice clat;
if (!DeterminizeLatticePhonePrunedWrapper(
trans_model,
&lat,
decoder.GetOptions().lattice_beam,
&clat,
decoder.GetOptions().det_opts))
KALDI_WARN << "Determinization finished earlier than the beam for "
<< "utterance " << utt;
// We'll write the lattice without acoustic scaling.
if (acoustic_scale != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
compact_lattice_writer->Write(utt, clat);
} else {
// We'll write the lattice without acoustic scaling.
if (acoustic_scale != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &lat);
lattice_writer->Write(utt, lat);
}
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
<< (likelihood / num_frames) << " over "
<< num_frames << " frames.";
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
<< weight.Value1() << " + " << weight.Value2();
*like_ptr = likelihood;
return true;
}
// Instantiate the template above for the two required FST types.
template bool DecodeUtteranceLatticeIncremental(
LatticeIncrementalDecoderTpl<fst::Fst<fst::StdArc> > &decoder,
DecodableInterface &decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr);
template bool DecodeUtteranceLatticeIncremental(
LatticeIncrementalDecoderTpl<fst::ConstGrammarFst > &decoder,
DecodableInterface &decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr);
template bool DecodeUtteranceLatticeFaster(
LatticeFasterDecoderTpl<fst::Fst<fst::StdArc> > &decoder,
DecodableInterface &decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr);
template bool DecodeUtteranceLatticeFaster(
LatticeFasterDecoderTpl<fst::ConstGrammarFst > &decoder,
DecodableInterface &decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr);
// Takes care of output. Returns true on success.
bool DecodeUtteranceLatticeSimple(
LatticeSimpleDecoder &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignment_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr) { // puts utterance's like in like_ptr on success.
using fst::VectorFst;
if (!decoder.Decode(&decodable)) {
KALDI_WARN << "Failed to decode utterance with id " << utt;
return false;
}
if (!decoder.ReachedFinal()) {
if (allow_partial) {
KALDI_WARN << "Outputting partial output for utterance " << utt
<< " since no final-state reached\n";
} else {
KALDI_WARN << "Not producing output for utterance " << utt
<< " since no final-state reached and "
<< "--allow-partial=false.\n";
return false;
}
}
double likelihood;
LatticeWeight weight = LatticeWeight::Zero();
int32 num_frames;
{ // First do some stuff with word-level traceback...
VectorFst<LatticeArc> decoded;
if (!decoder.GetBestPath(&decoded))
// Shouldn't really reach this point as already checked success.
KALDI_ERR << "Failed to get traceback for utterance " << utt;
std::vector<int32> alignment;
std::vector<int32> words;
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
num_frames = alignment.size();
if (words_writer->IsOpen())
words_writer->Write(utt, words);
if (alignment_writer->IsOpen())
alignment_writer->Write(utt, alignment);
if (word_syms != NULL) {
std::cerr << utt << ' ';
for (size_t i = 0; i < words.size(); i++) {
std::string s = word_syms->Find(words[i]);
if (s == "")
KALDI_ERR << "Word-id " << words[i] << " not in symbol table.";
std::cerr << s << ' ';
}
std::cerr << '\n';
}
likelihood = -(weight.Value1() + weight.Value2());
}
// Get lattice, and do determinization if requested.
Lattice lat;
if (!decoder.GetRawLattice(&lat))
KALDI_ERR << "Unexpected problem getting lattice for utterance " << utt;
fst::Connect(&lat);
if (determinize) {
CompactLattice clat;
if (!DeterminizeLatticePhonePrunedWrapper(
trans_model,
&lat,
decoder.GetOptions().lattice_beam,
&clat,
decoder.GetOptions().det_opts))
KALDI_WARN << "Determinization finished earlier than the beam for "
<< "utterance " << utt;
// We'll write the lattice without acoustic scaling.
if (acoustic_scale != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &clat);
compact_lattice_writer->Write(utt, clat);
} else {
// We'll write the lattice without acoustic scaling.
if (acoustic_scale != 0.0)
fst::ScaleLattice(fst::AcousticLatticeScale(1.0 / acoustic_scale), &lat);
lattice_writer->Write(utt, lat);
}
KALDI_LOG << "Log-like per frame for utterance " << utt << " is "
<< (likelihood / num_frames) << " over "
<< num_frames << " frames.";
KALDI_VLOG(2) << "Cost for utterance " << utt << " is "
<< weight.Value1() << " + " << weight.Value2();
*like_ptr = likelihood;
return true;
}
// see comment in header.
void ModifyGraphForCarefulAlignment(
fst::VectorFst<fst::StdArc> *fst) {
typedef fst::StdArc Arc;
typedef Arc::StateId StateId;
typedef Arc::Label Label;
typedef Arc::Weight Weight;
StateId num_states = fst->NumStates();
if (num_states == 0) {
KALDI_WARN << "Empty FST input.";
return;
}
Weight zero = Weight::Zero();
// fst_rhs will be the right hand side of the Concat operation.
fst::VectorFst<fst::StdArc> fst_rhs(*fst);
// first remove the final-probs from fst_rhs.
for (StateId state = 0; state < num_states; state++)
fst_rhs.SetFinal(state, zero);
StateId pre_initial = fst_rhs.AddState();
Arc to_initial(0, 0, Weight::One(), fst_rhs.Start());
fst_rhs.AddArc(pre_initial, to_initial);
fst_rhs.SetStart(pre_initial);
// make the pre_initial state final with probability one;
// this is equivalent to keeping the final-probs of the first
// FST when we do concat (otherwise they would get deleted).
fst_rhs.SetFinal(pre_initial, Weight::One());
fst::VectorFst<fst::StdArc> fst_concat;
fst::Concat(fst, fst_rhs);
}
void AlignUtteranceWrapper(
const AlignConfig &config,
const std::string &utt,
BaseFloat acoustic_scale, // affects scores written to scores_writer, if
// present
fst::VectorFst<fst::StdArc> *fst, // non-const in case config.careful ==
// true.
DecodableInterface *decodable, // not const but is really an input.
Int32VectorWriter *alignment_writer,
BaseFloatWriter *scores_writer,
int32 *num_done,
int32 *num_error,
int32 *num_retried,
double *tot_like,
int64 *frame_count,
BaseFloatVectorWriter *per_frame_acwt_writer) {
if ((config.retry_beam != 0 && config.retry_beam <= config.beam) ||
config.beam <= 0.0) {
KALDI_ERR << "Beams do not make sense: beam " << config.beam
<< ", retry-beam " << config.retry_beam;
}
if (fst->Start() == fst::kNoStateId) {
KALDI_WARN << "Empty decoding graph for " << utt;
if (num_error != NULL) (*num_error)++;
return;
}
if (config.careful)
ModifyGraphForCarefulAlignment(fst);
FasterDecoderOptions decode_opts;
decode_opts.beam = config.beam;
FasterDecoder decoder(*fst, decode_opts);
decoder.Decode(decodable);
bool ans = decoder.ReachedFinal(); // consider only final states.
if (!ans && config.retry_beam != 0.0) {
if (num_retried != NULL) (*num_retried)++;
KALDI_WARN << "Retrying utterance " << utt << " with beam "
<< config.retry_beam;
decode_opts.beam = config.retry_beam;
decoder.SetOptions(decode_opts);
decoder.Decode(decodable);
ans = decoder.ReachedFinal();
}
if (!ans) { // Still did not reach final state.
KALDI_WARN << "Did not successfully decode file " << utt << ", len = "
<< decodable->NumFramesReady();
if (num_error != NULL) (*num_error)++;
return;
}
fst::VectorFst<LatticeArc> decoded; // linear FST.
decoder.GetBestPath(&decoded);
if (decoded.NumStates() == 0) {
KALDI_WARN << "Error getting best path from decoder (likely a bug)";
if (num_error != NULL) (*num_error)++;
return;
}
std::vector<int32> alignment;
std::vector<int32> words;
LatticeWeight weight;
GetLinearSymbolSequence(decoded, &alignment, &words, &weight);
BaseFloat like = -(weight.Value1()+weight.Value2()) / acoustic_scale;
if (num_done != NULL) (*num_done)++;
if (tot_like != NULL) (*tot_like) += like;
if (frame_count != NULL) (*frame_count) += decodable->NumFramesReady();
if (alignment_writer != NULL && alignment_writer->IsOpen())
alignment_writer->Write(utt, alignment);
if (scores_writer != NULL && scores_writer->IsOpen())
scores_writer->Write(utt, -(weight.Value1()+weight.Value2()));
Vector<BaseFloat> per_frame_loglikes;
if (per_frame_acwt_writer != NULL && per_frame_acwt_writer->IsOpen()) {
GetPerFrameAcousticCosts(decoded, &per_frame_loglikes);
per_frame_loglikes.Scale(-1 / acoustic_scale);
per_frame_acwt_writer->Write(utt, per_frame_loglikes);
}
}
} // end namespace kaldi.
@@ -0,0 +1,221 @@
// decoder/decoder-wrappers.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_DECODER_DECODER_WRAPPERS_H_
#define KALDI_DECODER_DECODER_WRAPPERS_H_
#include "itf/options-itf.h"
#include "decoder/lattice-faster-decoder.h"
#include "decoder/lattice-incremental-decoder.h"
#include "decoder/lattice-simple-decoder.h"
// This header contains declarations from various convenience functions that are called
// from binary-level programs such as gmm-decode-faster.cc, gmm-align-compiled.cc, and
// so on.
namespace kaldi {
struct AlignConfig {
BaseFloat beam;
BaseFloat retry_beam;
bool careful;
AlignConfig(): beam(200.0), retry_beam(0.0), careful(false) { }
void Register(OptionsItf *opts) {
opts->Register("beam", &beam, "Decoding beam used in alignment");
opts->Register("retry-beam", &retry_beam,
"Decoding beam for second try at alignment");
opts->Register("careful", &careful,
"If true, do 'careful' alignment, which is better at detecting "
"alignment failure (involves loop to start of decoding graph).");
}
};
/// AlignUtteranceWapper is a wrapper for alignment code used in training, that
/// is called from many different binaries, e.g. gmm-align, gmm-align-compiled,
/// sgmm-align, etc. The writers for alignments and words will only be written
/// to if they are open. The num_done, num_error, num_retried, tot_like and
/// frame_count pointers will (if non-NULL) be incremented or added to, not set,
/// by this function.
void AlignUtteranceWrapper(
const AlignConfig &config,
const std::string &utt,
BaseFloat acoustic_scale, // affects scores written to scores_writer, if
// present
fst::VectorFst<fst::StdArc> *fst, // non-const in case config.careful ==
// true, we add loop.
DecodableInterface *decodable, // not const but is really an input.
Int32VectorWriter *alignment_writer,
BaseFloatWriter *scores_writer,
int32 *num_done,
int32 *num_error,
int32 *num_retried,
double *tot_like,
int64 *frame_count,
BaseFloatVectorWriter *per_frame_acwt_writer = NULL);
/// This function modifies the decoding graph for what we call "careful
/// alignment". The problem we are trying to solve is that if the decoding eats
/// up the words in the graph too fast, it can get stuck at the end, and produce
/// what looks like a valid alignment even though there was really a failure.
/// So what we want to do is to introduce, after the final-states of the graph,
/// a "blind alley" with no final-probs reachable, where the decoding can go to
/// get lost. Our basic idea is to append the decoding-graph to itself using
/// the fst Concat operation; but in order that there should be final-probs at the end of
/// the first but not the second FST, we modify the right-hand argument to the
/// Concat operation so that it has none of the original final-probs, and add
/// a "pre-initial" state that is final.
void ModifyGraphForCarefulAlignment(
fst::VectorFst<fst::StdArc> *fst);
/// TODO
template <typename FST>
bool DecodeUtteranceLatticeIncremental(
LatticeIncrementalDecoderTpl<FST> &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignments_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
/// This function DecodeUtteranceLatticeFaster is used in several decoders, and
/// we have moved it here. Note: this is really "binary-level" code as it
/// involves table readers and writers; we've just put it here as there is no
/// other obvious place to put it. If determinize == false, it writes to
/// lattice_writer, else to compact_lattice_writer. The writers for
/// alignments and words will only be written to if they are open.
///
/// Caution: this will only link correctly if FST is either fst::Fst<fst::StdArc>,
/// or fst::GrammarFst, as the template function is defined in the .cc file and
/// only instantiated for those two types.
template <typename FST>
bool DecodeUtteranceLatticeFaster(
LatticeFasterDecoderTpl<FST> &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignments_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
/// This class basically does the same job as the function
/// DecodeUtteranceLatticeFaster, but in a way that allows us
/// to build a multi-threaded command line program more easily.
/// The main computation takes place in operator (), and the output
/// happens in the destructor.
class DecodeUtteranceLatticeFasterClass {
public:
// Initializer sets various variables.
// NOTE: we "take ownership" of "decoder" and "decodable". These
// are deleted by the destructor. On error, "num_err" is incremented.
DecodeUtteranceLatticeFasterClass(
LatticeFasterDecoder *decoder,
DecodableInterface *decodable,
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
const std::string &utt,
BaseFloat acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignments_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_sum, // on success, adds likelihood to this.
int64 *frame_sum, // on success, adds #frames to this.
int32 *num_done, // on success (including partial decode), increments this.
int32 *num_err, // on failure, increments this.
int32 *num_partial); // If partial decode (final-state not reached), increments this.
void operator () (); // The decoding happens here.
~DecodeUtteranceLatticeFasterClass(); // Output happens here.
private:
// The following variables correspond to inputs:
LatticeFasterDecoder *decoder_;
DecodableInterface *decodable_;
const TransitionInformation *trans_model_;
const fst::SymbolTable *word_syms_;
std::string utt_;
BaseFloat acoustic_scale_;
bool determinize_;
bool allow_partial_;
Int32VectorWriter *alignments_writer_;
Int32VectorWriter *words_writer_;
CompactLatticeWriter *compact_lattice_writer_;
LatticeWriter *lattice_writer_;
double *like_sum_;
int64 *frame_sum_;
int32 *num_done_;
int32 *num_err_;
int32 *num_partial_;
// The following variables are stored by the computation.
bool computed_; // operator () was called.
bool success_; // decoding succeeded (possibly partial)
bool partial_; // decoding was partial.
CompactLattice *clat_; // Stored output, if determinize_ == true.
Lattice *lat_; // Stored output, if determinize_ == false.
};
// This function DecodeUtteranceLatticeSimple is used in several decoders, and
// we have moved it here. Note: this is really "binary-level" code as it
// involves table readers and writers; we've just put it here as there is no
// other obvious place to put it. If determinize == false, it writes to
// lattice_writer, else to compact_lattice_writer. The writers for
// alignments and words will only be written to if they are open.
bool DecodeUtteranceLatticeSimple(
LatticeSimpleDecoder &decoder, // not const but is really an input.
DecodableInterface &decodable, // not const but is really an input.
const TransitionInformation &trans_model,
const fst::SymbolTable *word_syms,
std::string utt,
double acoustic_scale,
bool determinize,
bool allow_partial,
Int32VectorWriter *alignments_writer,
Int32VectorWriter *words_writer,
CompactLatticeWriter *compact_lattice_writer,
LatticeWriter *lattice_writer,
double *like_ptr); // puts utterance's likelihood in like_ptr on success.
} // end namespace kaldi.
#endif
@@ -0,0 +1,351 @@
// decoder/faster-decoder.cc
// Copyright 2009-2011 Microsoft Corporation
// 2012-2013 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 "decoder/faster-decoder.h"
namespace kaldi {
FasterDecoder::FasterDecoder(const fst::Fst<fst::StdArc> &fst,
const FasterDecoderOptions &opts):
fst_(fst), config_(opts), num_frames_decoded_(-1) {
KALDI_ASSERT(config_.hash_ratio >= 1.0); // less doesn't make much sense.
KALDI_ASSERT(config_.max_active > 1);
KALDI_ASSERT(config_.min_active >= 0 && config_.min_active < config_.max_active);
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
}
void FasterDecoder::InitDecoding() {
// clean up from last time:
ClearToks(toks_.Clear());
StateId start_state = fst_.Start();
KALDI_ASSERT(start_state != fst::kNoStateId);
Arc dummy_arc(0, 0, Weight::One(), start_state);
toks_.Insert(start_state, new Token(dummy_arc, NULL));
ProcessNonemitting(std::numeric_limits<float>::max());
num_frames_decoded_ = 0;
}
void FasterDecoder::Decode(DecodableInterface *decodable) {
InitDecoding();
AdvanceDecoding(decodable);
}
void FasterDecoder::AdvanceDecoding(DecodableInterface *decodable,
int32 max_num_frames) {
KALDI_ASSERT(num_frames_decoded_ >= 0 &&
"You must call InitDecoding() before AdvanceDecoding()");
int32 num_frames_ready = decodable->NumFramesReady();
// num_frames_ready must be >= num_frames_decoded, or else
// the number of frames ready must have decreased (which doesn't
// make sense) or the decodable object changed between calls
// (which isn't allowed).
KALDI_ASSERT(num_frames_ready >= num_frames_decoded_);
int32 target_frames_decoded = num_frames_ready;
if (max_num_frames >= 0)
target_frames_decoded = std::min(target_frames_decoded,
num_frames_decoded_ + max_num_frames);
while (num_frames_decoded_ < target_frames_decoded) {
// note: ProcessEmitting() increments num_frames_decoded_
double weight_cutoff = ProcessEmitting(decodable);
ProcessNonemitting(weight_cutoff);
}
}
bool FasterDecoder::ReachedFinal() const {
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
if (e->val->cost_ != std::numeric_limits<double>::infinity() &&
fst_.Final(e->key) != Weight::Zero())
return true;
}
return false;
}
bool FasterDecoder::GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
bool use_final_probs) {
// GetBestPath gets the decoding output. If "use_final_probs" is true
// AND we reached a final state, it limits itself to final states;
// otherwise it gets the most likely token not taking into
// account final-probs. fst_out will be empty (Start() == kNoStateId) if
// nothing was available. It returns true if it got output (thus, fst_out
// will be nonempty).
fst_out->DeleteStates();
Token *best_tok = NULL;
bool is_final = ReachedFinal();
if (!is_final) {
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
if (best_tok == NULL || *best_tok < *(e->val) )
best_tok = e->val;
} else {
double infinity = std::numeric_limits<double>::infinity(),
best_cost = infinity;
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
double this_cost = e->val->cost_ + fst_.Final(e->key).Value();
if (this_cost < best_cost && this_cost != infinity) {
best_cost = this_cost;
best_tok = e->val;
}
}
}
if (best_tok == NULL) return false; // No output.
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_) {
BaseFloat tot_cost = tok->cost_ -
(tok->prev_ ? tok->prev_->cost_ : 0.0),
graph_cost = tok->arc_.weight.Value(),
ac_cost = tot_cost - graph_cost;
LatticeArc l_arc(tok->arc_.ilabel,
tok->arc_.olabel,
LatticeWeight(graph_cost, ac_cost),
tok->arc_.nextstate);
arcs_reverse.push_back(l_arc);
}
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
StateId cur_state = fst_out->AddState();
fst_out->SetStart(cur_state);
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
LatticeArc arc = arcs_reverse[i];
arc.nextstate = fst_out->AddState();
fst_out->AddArc(cur_state, arc);
cur_state = arc.nextstate;
}
if (is_final && use_final_probs) {
Weight final_weight = fst_.Final(best_tok->arc_.nextstate);
fst_out->SetFinal(cur_state, LatticeWeight(final_weight.Value(), 0.0));
} else {
fst_out->SetFinal(cur_state, LatticeWeight::One());
}
RemoveEpsLocal(fst_out);
return true;
}
// Gets the weight cutoff. Also counts the active tokens.
double FasterDecoder::GetCutoff(Elem *list_head, size_t *tok_count,
BaseFloat *adaptive_beam, Elem **best_elem) {
double best_cost = std::numeric_limits<double>::infinity();
size_t count = 0;
if (config_.max_active == std::numeric_limits<int32>::max() &&
config_.min_active == 0) {
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
double w = e->val->cost_;
if (w < best_cost) {
best_cost = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
if (adaptive_beam != NULL) *adaptive_beam = config_.beam;
return best_cost + config_.beam;
} else {
tmp_array_.clear();
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
double w = e->val->cost_;
tmp_array_.push_back(w);
if (w < best_cost) {
best_cost = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
double beam_cutoff = best_cost + config_.beam,
min_active_cutoff = std::numeric_limits<double>::infinity(),
max_active_cutoff = std::numeric_limits<double>::infinity();
if (tmp_array_.size() > static_cast<size_t>(config_.max_active)) {
std::nth_element(tmp_array_.begin(),
tmp_array_.begin() + config_.max_active,
tmp_array_.end());
max_active_cutoff = tmp_array_[config_.max_active];
}
if (max_active_cutoff < beam_cutoff) { // max_active is tighter than beam.
if (adaptive_beam)
*adaptive_beam = max_active_cutoff - best_cost + config_.beam_delta;
return max_active_cutoff;
}
if (tmp_array_.size() > static_cast<size_t>(config_.min_active)) {
if (config_.min_active == 0) min_active_cutoff = best_cost;
else {
std::nth_element(tmp_array_.begin(),
tmp_array_.begin() + config_.min_active,
tmp_array_.size() > static_cast<size_t>(config_.max_active) ?
tmp_array_.begin() + config_.max_active :
tmp_array_.end());
min_active_cutoff = tmp_array_[config_.min_active];
}
}
if (min_active_cutoff > beam_cutoff) { // min_active is looser than beam.
if (adaptive_beam)
*adaptive_beam = min_active_cutoff - best_cost + config_.beam_delta;
return min_active_cutoff;
} else {
*adaptive_beam = config_.beam;
return beam_cutoff;
}
}
}
void FasterDecoder::PossiblyResizeHash(size_t num_toks) {
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
* config_.hash_ratio);
if (new_sz > toks_.Size()) {
toks_.SetSize(new_sz);
}
}
// ProcessEmitting returns the likelihood cutoff used.
double FasterDecoder::ProcessEmitting(DecodableInterface *decodable) {
int32 frame = num_frames_decoded_;
Elem *last_toks = toks_.Clear();
size_t tok_cnt;
BaseFloat adaptive_beam;
Elem *best_elem = NULL;
double weight_cutoff = GetCutoff(last_toks, &tok_cnt,
&adaptive_beam, &best_elem);
KALDI_VLOG(3) << tok_cnt << " tokens active.";
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
// This is the cutoff we use after adding in the log-likes (i.e.
// for the next frame). This is a bound on the cutoff we will use
// on the next frame.
double next_weight_cutoff = std::numeric_limits<double>::infinity();
// First process the best token to get a hopefully
// reasonably tight bound on the next cutoff.
if (best_elem) {
StateId state = best_elem->key;
Token *tok = best_elem->val;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel != 0) { // we'd propagate..
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel);
double new_weight = arc.weight.Value() + tok->cost_ + ac_cost;
if (new_weight + adaptive_beam < next_weight_cutoff)
next_weight_cutoff = new_weight + adaptive_beam;
}
}
}
// int32 n = 0, np = 0;
// the tokens are now owned here, in last_toks, and the hash is empty.
// 'owned' is a complex thing here; the point is we need to call TokenDelete
// on each elem 'e' to let toks_ know we're done with them.
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) { // loop this way
// n++;
// because we delete "e" as we go.
StateId state = e->key;
Token *tok = e->val;
if (tok->cost_ < weight_cutoff) { // not pruned.
// np++;
KALDI_ASSERT(state == tok->arc_.nextstate);
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0) { // propagate..
BaseFloat ac_cost = - decodable->LogLikelihood(frame, arc.ilabel);
double new_weight = arc.weight.Value() + tok->cost_ + ac_cost;
if (new_weight < next_weight_cutoff) { // not pruned..
Token *new_tok = new Token(arc, ac_cost, tok);
Elem *e_found = toks_.Insert(arc.nextstate, new_tok);
if (new_weight + adaptive_beam < next_weight_cutoff)
next_weight_cutoff = new_weight + adaptive_beam;
if (e_found->val != new_tok) {
if (*(e_found->val) < *new_tok) {
Token::TokenDelete(e_found->val);
e_found->val = new_tok;
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
}
e_tail = e->tail;
Token::TokenDelete(e->val);
toks_.Delete(e);
}
num_frames_decoded_++;
return next_weight_cutoff;
}
// TODO: first time we go through this, could avoid using the queue.
void FasterDecoder::ProcessNonemitting(double cutoff) {
// Processes nonemitting arcs for one frame.
KALDI_ASSERT(queue_.empty());
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail)
queue_.push_back(e);
while (!queue_.empty()) {
const Elem* e = queue_.back();
queue_.pop_back();
StateId state = e->key;
Token *tok = e->val; // would segfault if state not
// in toks_ but this can't happen.
if (tok->cost_ > cutoff) { // Don't bother processing successors.
continue;
}
KALDI_ASSERT(tok != NULL && state == tok->arc_.nextstate);
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel == 0) { // propagate nonemitting only...
Token *new_tok = new Token(arc, tok);
if (new_tok->cost_ > cutoff) { // prune
Token::TokenDelete(new_tok);
} else {
Elem *e_found = toks_.Insert(arc.nextstate, new_tok);
if (e_found->val == new_tok) {
queue_.push_back(e_found);
} else {
if (*(e_found->val) < *new_tok) {
Token::TokenDelete(e_found->val);
e_found->val = new_tok;
queue_.push_back(e_found);
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
}
}
void FasterDecoder::ClearToks(Elem *list) {
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
Token::TokenDelete(e->val);
e_tail = e->tail;
toks_.Delete(e);
}
}
} // end namespace kaldi.
@@ -0,0 +1,195 @@
// decoder/faster-decoder.h
// Copyright 2009-2011 Microsoft Corporation
// 2013 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_DECODER_FASTER_DECODER_H_
#define KALDI_DECODER_FASTER_DECODER_H_
#include "util/stl-utils.h"
#include "itf/options-itf.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "lat/kaldi-lattice.h" // for CompactLatticeArc
namespace kaldi {
struct FasterDecoderOptions {
BaseFloat beam;
int32 max_active;
int32 min_active;
BaseFloat beam_delta;
BaseFloat hash_ratio;
FasterDecoderOptions(): beam(16.0),
max_active(std::numeric_limits<int32>::max()),
min_active(20), // This decoder mostly used for
// alignment, use small default.
beam_delta(0.5),
hash_ratio(2.0) { }
void Register(OptionsItf *opts, bool full) { /// if "full", use obscure
/// options too.
/// Depends on program.
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
opts->Register("max-active", &max_active, "Decoder max active states. Larger->slower; "
"more accurate");
opts->Register("min-active", &min_active,
"Decoder min active states (don't prune if #active less than this).");
if (full) {
opts->Register("beam-delta", &beam_delta,
"Increment used in decoder [obscure setting]");
opts->Register("hash-ratio", &hash_ratio,
"Setting used in decoder to control hash behavior");
}
}
};
class FasterDecoder {
public:
typedef fst::StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
FasterDecoder(const fst::Fst<fst::StdArc> &fst,
const FasterDecoderOptions &config);
void SetOptions(const FasterDecoderOptions &config) { config_ = config; }
~FasterDecoder() { ClearToks(toks_.Clear()); }
void Decode(DecodableInterface *decodable);
/// Returns true if a final state was active on the last frame.
bool ReachedFinal() const;
/// GetBestPath gets the decoding traceback. If "use_final_probs" is true
/// AND we reached a final state, it limits itself to final states;
/// otherwise it gets the most likely token not taking into account
/// final-probs. Returns true if the output best path was not the empty
/// FST (will only return false in unusual circumstances where
/// no tokens survived).
bool GetBestPath(fst::MutableFst<LatticeArc> *fst_out,
bool use_final_probs = true);
/// As a new alternative to Decode(), you can call InitDecoding
/// and then (possibly multiple times) AdvanceDecoding().
void InitDecoding();
/// This will decode until there are no more frames ready in the decodable
/// object, but if max_num_frames is >= 0 it will decode no more than
/// that many frames.
void AdvanceDecoding(DecodableInterface *decodable,
int32 max_num_frames = -1);
/// Returns the number of frames already decoded.
int32 NumFramesDecoded() const { return num_frames_decoded_; }
protected:
class Token {
public:
Arc arc_; // contains only the graph part of the cost;
// we can work out the acoustic part from difference between
// "cost_" and prev->cost_.
Token *prev_;
int32 ref_count_;
// if you are looking for weight_ here, it was removed and now we just have
// cost_, which corresponds to ConvertToCost(weight_).
double cost_;
inline Token(const Arc &arc, BaseFloat ac_cost, Token *prev):
arc_(arc), prev_(prev), ref_count_(1) {
if (prev) {
prev->ref_count_++;
cost_ = prev->cost_ + arc.weight.Value() + ac_cost;
} else {
cost_ = arc.weight.Value() + ac_cost;
}
}
inline Token(const Arc &arc, Token *prev):
arc_(arc), prev_(prev), ref_count_(1) {
if (prev) {
prev->ref_count_++;
cost_ = prev->cost_ + arc.weight.Value();
} else {
cost_ = arc.weight.Value();
}
}
inline bool operator < (const Token &other) {
return cost_ > other.cost_;
}
inline static void TokenDelete(Token *tok) {
while (--tok->ref_count_ == 0) {
Token *prev = tok->prev_;
delete tok;
if (prev == NULL) return;
else tok = prev;
}
#ifdef KALDI_PARANOID
KALDI_ASSERT(tok->ref_count_ > 0);
#endif
}
};
typedef HashList<StateId, Token*>::Elem Elem;
/// Gets the weight cutoff. Also counts the active tokens.
double GetCutoff(Elem *list_head, size_t *tok_count,
BaseFloat *adaptive_beam, Elem **best_elem);
void PossiblyResizeHash(size_t num_toks);
// ProcessEmitting returns the likelihood cutoff used.
// It decodes the frame num_frames_decoded_ of the decodable object
// and then increments num_frames_decoded_
double ProcessEmitting(DecodableInterface *decodable);
// TODO: first time we go through this, could avoid using the queue.
void ProcessNonemitting(double cutoff);
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
// more than one list (e.g. for current and previous frames), but only one of
// them at a time can be indexed by StateId.
HashList<StateId, Token*> toks_;
const fst::Fst<fst::StdArc> &fst_;
FasterDecoderOptions config_;
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
// make it class member to avoid internal new/delete.
// Keep track of the number of frames decoded in the current file.
int32 num_frames_decoded_;
// It might seem unclear why we call ClearToks(toks_.Clear()).
// There are two separate cleanup tasks we need to do at when we start a new file.
// one is to delete the Token objects in the list; the other is to delete
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
// this way for convenience in propagating tokens from one frame to the next.
void ClearToks(Elem *list);
KALDI_DISALLOW_COPY_AND_ASSIGN(FasterDecoder);
};
} // end namespace kaldi.
#endif
@@ -0,0 +1,886 @@
// decoder/lattice-biglm-faster-decoder.h
// Copyright 2009-2011 Microsoft Corporation, Mirko Hannemann,
// 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.
#ifndef KALDI_DECODER_LATTICE_BIGLM_FASTER_DECODER_H_
#define KALDI_DECODER_LATTICE_BIGLM_FASTER_DECODER_H_
#include "util/stl-utils.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "fstext/fstext-lib.h"
#include "lat/kaldi-lattice.h"
#include "decoder/lattice-faster-decoder.h" // for options.
namespace kaldi {
// The options are the same as for lattice-faster-decoder.h for now.
typedef LatticeFasterDecoderConfig LatticeBiglmFasterDecoderConfig;
/** This is as LatticeFasterDecoder, but does online composition between
HCLG and the "difference language model", which is a deterministic
FST that represents the difference between the language model you want
and the language model you compiled HCLG with. The class
DeterministicOnDemandFst follows through the epsilons in G for you
(assuming G is a standard backoff language model) and makes it look
like a determinized FST.
*/
class LatticeBiglmFasterDecoder {
public:
typedef fst::StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
typedef uint64 PairId;
typedef Arc::Weight Weight;
// instantiate this class once for each thing you have to decode.
LatticeBiglmFasterDecoder(
const fst::Fst<fst::StdArc> &fst,
const LatticeBiglmFasterDecoderConfig &config,
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst):
fst_(fst), lm_diff_fst_(lm_diff_fst), config_(config),
warned_noarc_(false), num_toks_(0) {
config.Check();
KALDI_ASSERT(fst.Start() != fst::kNoStateId &&
lm_diff_fst->Start() != fst::kNoStateId);
toks_.SetSize(1000); // just so on the first frame we do something reasonable.
}
void SetOptions(const LatticeBiglmFasterDecoderConfig &config) { config_ = config; }
LatticeBiglmFasterDecoderConfig GetOptions() { return config_; }
~LatticeBiglmFasterDecoder() {
DeleteElems(toks_.Clear());
ClearActiveTokens();
}
// Returns true if any kind of traceback is available (not necessarily from
// a final state).
bool Decode(DecodableInterface *decodable) {
// clean up from last time:
DeleteElems(toks_.Clear());
ClearActiveTokens();
warned_ = false;
final_active_ = false;
final_costs_.clear();
num_toks_ = 0;
PairId start_pair = ConstructPair(fst_.Start(), lm_diff_fst_->Start());
active_toks_.resize(1);
Token *start_tok = new Token(0.0, 0.0, NULL, NULL);
active_toks_[0].toks = start_tok;
toks_.Insert(start_pair, start_tok);
num_toks_++;
ProcessNonemitting(0);
// We use 1-based indexing for frames in this decoder (if you view it in
// terms of features), but note that the decodable object uses zero-based
// numbering, which we have to correct for when we call it.
for (int32 frame = 1; !decodable->IsLastFrame(frame-2); frame++) {
active_toks_.resize(frame+1); // new column
ProcessEmitting(decodable, frame);
ProcessNonemitting(frame);
if (decodable->IsLastFrame(frame-1))
PruneActiveTokensFinal(frame);
else if (frame % config_.prune_interval == 0)
PruneActiveTokens(frame, config_.lattice_beam * 0.1); // use larger delta.
}
// Returns true if we have any kind of traceback available (not necessarily
// to the end state; query ReachedFinal() for that).
return !final_costs_.empty();
}
/// says whether a final-state was active on the last frame. If it was not, the
/// lattice (or traceback) will end with states that are not final-states.
bool ReachedFinal() const { return final_active_; }
// Outputs an FST corresponding to the single best path
// through the lattice.
bool GetBestPath(fst::MutableFst<LatticeArc> *ofst,
bool use_final_probs = true) const {
fst::VectorFst<LatticeArc> fst;
if (!GetRawLattice(&fst, use_final_probs)) return false;
// std::cout << "Raw lattice is:\n";
// fst::FstPrinter<LatticeArc> fstprinter(fst, NULL, NULL, NULL, false, true);
// fstprinter.Print(&std::cout, "standard output");
ShortestPath(fst, ofst);
return true;
}
// Outputs an FST corresponding to the raw, state-level
// tracebacks.
bool GetRawLattice(fst::MutableFst<LatticeArc> *ofst,
bool use_final_probs = true) const {
typedef LatticeArc Arc;
typedef Arc::StateId StateId;
// A PairId will be constructed as: (StateId in fst) + (StateId in lm_diff_fst) << 32;
typedef uint64 PairId;
typedef Arc::Weight Weight;
typedef Arc::Label Label;
ofst->DeleteStates();
// num-frames plus one (since frames are one-based, and we have
// an extra frame for the start-state).
int32 num_frames = active_toks_.size() - 1;
KALDI_ASSERT(num_frames > 0);
unordered_map<Token*, StateId> tok_map(num_toks_/2 + 3); // bucket count
// First create all states.
for (int32 f = 0; f <= num_frames; f++) {
if (active_toks_[f].toks == NULL) {
KALDI_WARN << "GetRawLattice: no tokens active on frame " << f
<< ": not producing lattice.\n";
return false;
}
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next)
tok_map[tok] = ofst->AddState();
// The next statement sets the start state of the output FST.
// Because we always add new states to the head of the list
// active_toks_[f].toks, and the start state was the first one
// added, it will be the last one added to ofst.
if (f == 0 && ofst->NumStates() > 0)
ofst->SetStart(ofst->NumStates()-1);
}
KALDI_VLOG(3) << "init:" << num_toks_/2 + 3 << " buckets:"
<< tok_map.bucket_count() << " load:" << tok_map.load_factor()
<< " max:" << tok_map.max_load_factor();
// Now create all arcs.
StateId cur_state = 0; // we rely on the fact that we numbered these
// consecutively (AddState() returns the numbers in order..)
for (int32 f = 0; f <= num_frames; f++) {
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next,
cur_state++) {
for (ForwardLink *l = tok->links;
l != NULL;
l = l->next) {
unordered_map<Token*, StateId>::const_iterator iter =
tok_map.find(l->next_tok);
StateId nextstate = iter->second;
KALDI_ASSERT(iter != tok_map.end());
Arc arc(l->ilabel, l->olabel,
Weight(l->graph_cost, l->acoustic_cost),
nextstate);
ofst->AddArc(cur_state, arc);
}
if (f == num_frames) {
if (use_final_probs && !final_costs_.empty()) {
std::map<Token*, BaseFloat>::const_iterator iter =
final_costs_.find(tok);
if (iter != final_costs_.end())
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
} else {
ofst->SetFinal(cur_state, LatticeWeight::One());
}
}
}
}
KALDI_ASSERT(cur_state == ofst->NumStates());
return (cur_state != 0);
}
// This function is now deprecated, since now we do determinization from
// outside the LatticeBiglmFasterDecoder class.
// Outputs an FST corresponding to the lattice-determinized
// lattice (one path per word sequence).
bool GetLattice(fst::MutableFst<CompactLatticeArc> *ofst,
bool use_final_probs = true) const {
Lattice raw_fst;
if (!GetRawLattice(&raw_fst, use_final_probs)) return false;
Invert(&raw_fst); // make it so word labels are on the input.
if (!TopSort(&raw_fst)) // topological sort makes lattice-determinization more efficient
KALDI_WARN << "Topological sorting of state-level lattice failed "
"(probably your lexicon has empty words or your LM has epsilon cycles; this "
" is a bad idea.)";
// (in phase where we get backward-costs).
fst::ILabelCompare<LatticeArc> ilabel_comp;
ArcSort(&raw_fst, ilabel_comp); // sort on ilabel; makes
// lattice-determinization more efficient.
fst::DeterminizeLatticePrunedOptions lat_opts;
lat_opts.max_mem = config_.det_opts.max_mem;
DeterminizeLatticePruned(raw_fst, config_.lattice_beam, ofst, lat_opts);
raw_fst.DeleteStates(); // Free memory-- raw_fst no longer needed.
Connect(ofst); // Remove unreachable states... there might be
// a small number of these, in some cases.
return true;
}
private:
inline PairId ConstructPair(StateId fst_state, StateId lm_state) {
return static_cast<PairId>(fst_state) + (static_cast<PairId>(lm_state) << 32);
}
static inline StateId PairToState(PairId state_pair) {
return static_cast<StateId>(static_cast<uint32>(state_pair));
}
static inline StateId PairToLmState(PairId state_pair) {
return static_cast<StateId>(static_cast<uint32>(state_pair >> 32));
}
struct Token;
// ForwardLinks are the links from a token to a token on the next frame.
// or sometimes on the current frame (for input-epsilon links).
struct ForwardLink {
Token *next_tok; // the next token [or NULL if represents final-state]
Label ilabel; // ilabel on link.
Label olabel; // olabel on link.
BaseFloat graph_cost; // graph cost of traversing link (contains LM, etc.)
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing link
ForwardLink *next; // next in singly-linked list of forward links from a
// token.
inline ForwardLink(Token *next_tok, Label ilabel, Label olabel,
BaseFloat graph_cost, BaseFloat acoustic_cost,
ForwardLink *next):
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
next(next) { }
};
// Token is what's resident in a particular state at a particular time.
// In this decoder a Token actually contains *forward* links.
// When first created, a Token just has the (total) cost. We add forward
// links to it when we process the next frame.
struct Token {
BaseFloat tot_cost; // would equal weight.Value()... cost up to this point.
BaseFloat extra_cost; // >= 0. After calling PruneForwardLinks, this equals
// the minimum difference between the cost of the best path, and the cost of
// this is on, and the cost of the absolute best path, under the assumption
// that any of the currently active states at the decoding front may
// eventually succeed (e.g. if you were to take the currently active states
// one by one and compute this difference, and then take the minimum).
ForwardLink *links; // Head of singly linked list of ForwardLinks
Token *next; // Next in list of tokens for this frame.
inline Token(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLink *links,
Token *next): tot_cost(tot_cost), extra_cost(extra_cost),
links(links), next(next) { }
inline void DeleteForwardLinks() {
ForwardLink *l = links, *m;
while (l != NULL) {
m = l->next;
delete l;
l = m;
}
links = NULL;
}
};
// head and tail of per-frame list of Tokens (list is in topological order),
// and something saying whether we ever pruned it using PruneForwardLinks.
struct TokenList {
Token *toks;
bool must_prune_forward_links;
bool must_prune_tokens;
TokenList(): toks(NULL), must_prune_forward_links(true),
must_prune_tokens(true) { }
};
typedef HashList<PairId, Token*>::Elem Elem;
void PossiblyResizeHash(size_t num_toks) {
size_t new_sz = static_cast<size_t>(static_cast<BaseFloat>(num_toks)
* config_.hash_ratio);
if (new_sz > toks_.Size()) {
toks_.SetSize(new_sz);
}
}
// FindOrAddToken either locates a token in hash of toks_,
// or if necessary inserts a new, empty token (i.e. with no forward links)
// for the current frame. [note: it's inserted if necessary into hash toks_
// and also into the singly linked list of tokens active on this frame
// (whose head is at active_toks_[frame]).
inline Elem *FindOrAddToken(PairId state_pair, int32 frame,
BaseFloat tot_cost, bool emitting, bool *changed) {
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
// if the token was newly created or the cost changed.
KALDI_ASSERT(frame < active_toks_.size());
Token *&toks = active_toks_[frame].toks;
Elem *e_found = toks_.Insert(state_pair, NULL);
if (e_found->val == NULL) { // no such token presently.
const BaseFloat extra_cost = 0.0;
// tokens on the currently final frame have zero extra_cost
// as any of them could end up
// on the winning path.
Token *new_tok = new Token (tot_cost, extra_cost, NULL, toks);
// NULL: no forward links yet
toks = new_tok;
num_toks_++;
e_found->val = new_tok;
if (changed) *changed = true;
return e_found;
} else {
Token *tok = e_found->val; // There is an existing Token for this state.
if (tok->tot_cost > tot_cost) { // replace old token
tok->tot_cost = tot_cost;
// we don't allocate a new token, the old stays linked in active_toks_
// we only replace the tot_cost
// in the current frame, there are no forward links (and no extra_cost)
// only in ProcessNonemitting we have to delete forward links
// in case we visit a state for the second time
// those forward links, that lead to this replaced token before:
// they remain and will hopefully be pruned later (PruneForwardLinks...)
if (changed) *changed = true;
} else {
if (changed) *changed = false;
}
return e_found;
}
}
// prunes outgoing links for all tokens in active_toks_[frame]
// it's called by PruneActiveTokens
// all links, that have link_extra_cost > lattice_beam are pruned
void PruneForwardLinks(int32 frame, bool *extra_costs_changed,
bool *links_pruned,
BaseFloat delta) {
// delta is the amount by which the extra_costs must change
// If delta is larger, we'll tend to go back less far
// toward the beginning of the file.
// extra_costs_changed is set to true if extra_cost was changed for any token
// links_pruned is set to true if any link in any token was pruned
*extra_costs_changed = false;
*links_pruned = false;
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
if (active_toks_[frame].toks == NULL ) { // empty list; should not happen.
if (!warned_) {
KALDI_WARN << "No tokens alive [doing pruning].. warning first "
"time only for each utterance\n";
warned_ = true;
}
}
// We have to iterate until there is no more change, because the links
// are not guaranteed to be in topological order.
bool changed = true; // difference new minus old extra cost >= delta ?
while (changed) {
changed = false;
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
ForwardLink *link, *prev_link=NULL;
// will recompute tok_extra_cost for tok.
BaseFloat tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
// tok_extra_cost is the best (min) of link_extra_cost of outgoing links
for (link = tok->links; link != NULL; ) {
// See if we need to excise this link...
Token *next_tok = link->next_tok;
BaseFloat link_extra_cost = next_tok->extra_cost +
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
- next_tok->tot_cost); // difference in brackets is >= 0
// link_exta_cost is the difference in score between the best paths
// through link source state and through link destination state
KALDI_ASSERT(link_extra_cost == link_extra_cost); // check for NaN
if (link_extra_cost > config_.lattice_beam) { // excise link
ForwardLink *next_link = link->next;
if (prev_link != NULL) prev_link->next = next_link;
else tok->links = next_link;
delete link;
link = next_link; // advance link but leave prev_link the same.
*links_pruned = true;
} else { // keep the link and update the tok_extra_cost if needed.
if (link_extra_cost < 0.0) { // this is just a precaution.
if (link_extra_cost < -0.01)
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
link_extra_cost = 0.0;
}
if (link_extra_cost < tok_extra_cost)
tok_extra_cost = link_extra_cost;
prev_link = link; // move to next link
link = link->next;
}
} // for all outgoing links
if (fabs(tok_extra_cost - tok->extra_cost) > delta)
changed = true; // difference new minus old is bigger than delta
tok->extra_cost = tok_extra_cost;
// will be +infinity or <= lattice_beam_.
// infinity indicates, that no forward link survived pruning
} // for all Token on active_toks_[frame]
if (changed) *extra_costs_changed = true;
// Note: it's theoretically possible that aggressive compiler
// optimizations could cause an infinite loop here for small delta and
// high-dynamic-range scores.
} // while changed
}
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
// on the final frame. If there are final tokens active, it uses
// the final-probs for pruning, otherwise it treats all tokens as final.
void PruneForwardLinksFinal(int32 frame) {
KALDI_ASSERT(static_cast<size_t>(frame+1) == active_toks_.size());
if (active_toks_[frame].toks == NULL ) // empty list; should not happen.
KALDI_WARN << "No tokens alive at end of file\n";
// First go through, working out the best token (do it in parallel
// including final-probs and not including final-probs; we'll take
// the one with final-probs if it's valid).
const BaseFloat infinity = std::numeric_limits<BaseFloat>::infinity();
BaseFloat best_cost_final = infinity,
best_cost_nofinal = infinity;
unordered_map<Token*, BaseFloat> tok_to_final_cost;
Elem *cur_toks = toks_.Clear(); // swapping prev_toks_ / cur_toks_
for (Elem *e = cur_toks, *e_tail; e != NULL; e = e_tail) {
PairId state_pair = e->key;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
Token *tok = e->val;
BaseFloat final_cost = fst_.Final(state).Value() +
lm_diff_fst_->Final(lm_state).Value();
tok_to_final_cost[tok] = final_cost;
best_cost_final = std::min(best_cost_final, tok->tot_cost + final_cost);
best_cost_nofinal = std::min(best_cost_nofinal, tok->tot_cost);
e_tail = e->tail;
toks_.Delete(e);
}
final_active_ = (best_cost_final != infinity);
// Now go through tokens on this frame, pruning forward links... may have
// to iterate a few times until there is no more change, because the list is
// not in topological order.
bool changed = true;
BaseFloat delta = 1.0e-05;
while (changed) {
changed = false;
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
ForwardLink *link, *prev_link=NULL;
// will recompute tok_extra_cost. It has a term in it that corresponds
// to the "final-prob", so instead of initializing tok_extra_cost to infinity
// below we set it to the difference between the (score+final_prob) of this token,
// and the best such (score+final_prob).
BaseFloat tok_extra_cost;
if (final_active_) {
BaseFloat final_cost = tok_to_final_cost[tok];
tok_extra_cost = (tok->tot_cost + final_cost) - best_cost_final;
} else
tok_extra_cost = tok->tot_cost - best_cost_nofinal;
for (link = tok->links; link != NULL; ) {
// See if we need to excise this link...
Token *next_tok = link->next_tok;
BaseFloat link_extra_cost = next_tok->extra_cost +
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
- next_tok->tot_cost);
if (link_extra_cost > config_.lattice_beam) { // excise link
ForwardLink *next_link = link->next;
if (prev_link != NULL) prev_link->next = next_link;
else tok->links = next_link;
delete link;
link = next_link; // advance link but leave prev_link the same.
} else { // keep the link and update the tok_extra_cost if needed.
if (link_extra_cost < 0.0) { // this is just a precaution.
if (link_extra_cost < -0.01)
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
link_extra_cost = 0.0;
}
if (link_extra_cost < tok_extra_cost)
tok_extra_cost = link_extra_cost;
prev_link = link;
link = link->next;
}
}
// prune away tokens worse than lattice_beam above best path. This step
// was not necessary in the non-final case because then, this case
// showed up as having no forward links. Here, the tok_extra_cost has
// an extra component relating to the final-prob.
if (tok_extra_cost > config_.lattice_beam)
tok_extra_cost = infinity;
// to be pruned in PruneTokensForFrame
if (!ApproxEqual(tok->extra_cost, tok_extra_cost, delta))
changed = true;
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
}
} // while changed
// Now put surviving Tokens in the final_costs_ hash, which is a class
// member (unlike tok_to_final_costs).
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
if (tok->extra_cost != infinity) {
// If the token was not pruned away,
if (final_active_) {
BaseFloat final_cost = tok_to_final_cost[tok];
if (final_cost != infinity)
final_costs_[tok] = final_cost;
} else {
final_costs_[tok] = 0;
}
}
}
}
// Prune away any tokens on this frame that have no forward links.
// [we don't do this in PruneForwardLinks because it would give us
// a problem with dangling pointers].
// It's called by PruneActiveTokens if any forward links have been pruned
void PruneTokensForFrame(int32 frame) {
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
Token *&toks = active_toks_[frame].toks;
if (toks == NULL)
KALDI_WARN << "No tokens alive [doing pruning]\n";
Token *tok, *next_tok, *prev_tok = NULL;
for (tok = toks; tok != NULL; tok = next_tok) {
next_tok = tok->next;
if (tok->extra_cost == std::numeric_limits<BaseFloat>::infinity()) {
// token is unreachable from end of graph; (no forward links survived)
// excise tok from list and delete tok.
if (prev_tok != NULL) prev_tok->next = tok->next;
else toks = tok->next;
delete tok;
num_toks_--;
} else { // fetch next Token
prev_tok = tok;
}
}
}
// Go backwards through still-alive tokens, pruning them. note: cur_frame is
// where hash toks_ are (so we do not want to mess with it because these tokens
// don't yet have forward pointers), but we do all previous frames, unless we
// know that we can safely ignore them because the frame after them was unchanged.
// delta controls when it considers a cost to have changed enough to continue
// going backward and propagating the change.
// for a larger delta, we will recurse less far back
void PruneActiveTokens(int32 cur_frame, BaseFloat delta) {
int32 num_toks_begin = num_toks_;
for (int32 frame = cur_frame-1; frame >= 0; frame--) {
// Reason why we need to prune forward links in this situation:
// (1) we have never pruned them (new TokenList)
// (2) we have not yet pruned the forward links to the next frame,
// after any of those tokens have changed their extra_cost.
if (active_toks_[frame].must_prune_forward_links) {
bool extra_costs_changed = false, links_pruned = false;
PruneForwardLinks(frame, &extra_costs_changed, &links_pruned, delta);
if (extra_costs_changed && frame > 0) // any token has changed extra_cost
active_toks_[frame-1].must_prune_forward_links = true;
if (links_pruned) // any link was pruned
active_toks_[frame].must_prune_tokens = true;
active_toks_[frame].must_prune_forward_links = false; // job done
}
if (frame+1 < cur_frame && // except for last frame (no forward links)
active_toks_[frame+1].must_prune_tokens) {
PruneTokensForFrame(frame+1);
active_toks_[frame+1].must_prune_tokens = false;
}
}
KALDI_VLOG(3) << "PruneActiveTokens: pruned tokens from " << num_toks_begin
<< " to " << num_toks_;
}
// Version of PruneActiveTokens that we call on the final frame.
// Takes into account the final-prob of tokens.
void PruneActiveTokensFinal(int32 cur_frame) {
// returns true if there were final states active
// else returns false and treats all states as final while doing the pruning
// (this can be useful if you want partial lattice output,
// although it can be dangerous, depending what you want the lattices for).
// final_active_ and final_probs_ (a hash) are set internally
// by PruneForwardLinksFinal
int32 num_toks_begin = num_toks_;
PruneForwardLinksFinal(cur_frame); // prune final frame (with final-probs)
// sets final_active_ and final_probs_
for (int32 frame = cur_frame-1; frame >= 0; frame--) {
bool b1, b2; // values not used.
BaseFloat dontcare = 0.0; // delta of zero means we must always update
PruneForwardLinks(frame, &b1, &b2, dontcare);
PruneTokensForFrame(frame+1);
}
PruneTokensForFrame(0);
KALDI_VLOG(3) << "PruneActiveTokensFinal: pruned tokens from " << num_toks_begin
<< " to " << num_toks_;
}
/// Gets the weight cutoff. Also counts the active tokens.
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
BaseFloat *adaptive_beam, Elem **best_elem) {
BaseFloat best_weight = std::numeric_limits<BaseFloat>::infinity();
// positive == high cost == bad.
size_t count = 0;
if (config_.max_active == std::numeric_limits<int32>::max()) {
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
BaseFloat w = static_cast<BaseFloat>(e->val->tot_cost);
if (w < best_weight) {
best_weight = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
if (adaptive_beam != NULL) *adaptive_beam = config_.beam;
return best_weight + config_.beam;
} else {
tmp_array_.clear();
for (Elem *e = list_head; e != NULL; e = e->tail, count++) {
BaseFloat w = e->val->tot_cost;
tmp_array_.push_back(w);
if (w < best_weight) {
best_weight = w;
if (best_elem) *best_elem = e;
}
}
if (tok_count != NULL) *tok_count = count;
if (tmp_array_.size() <= static_cast<size_t>(config_.max_active)) {
if (adaptive_beam) *adaptive_beam = config_.beam;
return best_weight + config_.beam;
} else {
// the lowest elements (lowest costs, highest likes)
// will be put in the left part of tmp_array.
std::nth_element(tmp_array_.begin(),
tmp_array_.begin()+config_.max_active,
tmp_array_.end());
// return the tighter of the two beams.
BaseFloat ans = std::min(best_weight + config_.beam,
*(tmp_array_.begin()+config_.max_active));
if (adaptive_beam)
*adaptive_beam = std::min(config_.beam,
ans - best_weight + config_.beam_delta);
return ans;
}
}
}
inline StateId PropagateLm(StateId lm_state,
Arc *arc) { // returns new LM state.
if (arc->olabel == 0) {
return lm_state; // no change in LM state if no word crossed.
} else { // Propagate in the LM-diff FST.
Arc lm_arc;
bool ans = lm_diff_fst_->GetArc(lm_state, arc->olabel, &lm_arc);
if (!ans) { // this case is unexpected for statistical LMs.
if (!warned_noarc_) {
warned_noarc_ = true;
KALDI_WARN << "No arc available in LM (unlikely to be correct "
"if a statistical language model); will not warn again";
}
arc->weight = Weight::Zero();
return lm_state; // doesn't really matter what we return here; will
// be pruned.
} else {
arc->weight = Times(arc->weight, lm_arc.weight);
arc->olabel = lm_arc.olabel; // probably will be the same.
return lm_arc.nextstate; // return the new LM state.
}
}
}
void ProcessEmitting(DecodableInterface *decodable, int32 frame) {
// Processes emitting arcs for one frame. Propagates from prev_toks_ to cur_toks_.
Elem *last_toks = toks_.Clear(); // swapping prev_toks_ / cur_toks_
Elem *best_elem = NULL;
BaseFloat adaptive_beam;
size_t tok_cnt;
BaseFloat cur_cutoff = GetCutoff(last_toks, &tok_cnt, &adaptive_beam, &best_elem);
PossiblyResizeHash(tok_cnt); // This makes sure the hash is always big enough.
BaseFloat next_cutoff = std::numeric_limits<BaseFloat>::infinity();
// pruning "online" before having seen all tokens
// First process the best token to get a hopefully
// reasonably tight bound on the next cutoff.
if (best_elem) {
PairId state_pair = best_elem->key;
StateId state = PairToState(state_pair), // state in "fst"
lm_state = PairToLmState(state_pair);
Token *tok = best_elem->val;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
Arc arc = aiter.Value();
if (arc.ilabel != 0) { // propagate..
PropagateLm(lm_state, &arc); // may affect "arc.weight".
// We don't need the return value (the new LM state).
arc.weight = Times(arc.weight,
Weight(-decodable->LogLikelihood(frame-1, arc.ilabel)));
BaseFloat new_weight = arc.weight.Value() + tok->tot_cost;
if (new_weight + adaptive_beam < next_cutoff)
next_cutoff = new_weight + adaptive_beam;
}
}
}
// the tokens are now owned here, in last_toks, and the hash is empty.
// 'owned' is a complex thing here; the point is we need to call DeleteElem
// on each elem 'e' to let toks_ know we're done with them.
for (Elem *e = last_toks, *e_tail; e != NULL; e = e_tail) {
// loop this way because we delete "e" as we go.
PairId state_pair = e->key;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
Token *tok = e->val;
if (tok->tot_cost <= cur_cutoff) {
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc_ref = aiter.Value();
if (arc_ref.ilabel != 0) { // propagate..
Arc arc(arc_ref);
StateId next_lm_state = PropagateLm(lm_state, &arc);
BaseFloat ac_cost = -decodable->LogLikelihood(frame-1, arc.ilabel),
graph_cost = arc.weight.Value(),
cur_cost = tok->tot_cost,
tot_cost = cur_cost + ac_cost + graph_cost;
if (tot_cost >= next_cutoff) continue;
else if (tot_cost + adaptive_beam < next_cutoff)
next_cutoff = tot_cost + adaptive_beam; // prune by best current token
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
Elem *e_next = FindOrAddToken(next_pair, frame, tot_cost, true, NULL);
// true: emitting, NULL: no change indicator needed
// Add ForwardLink from tok to next_tok (put on head of list tok->links)
tok->links = new ForwardLink(e_next->val, arc.ilabel, arc.olabel,
graph_cost, ac_cost, tok->links);
}
} // for all arcs
}
e_tail = e->tail;
toks_.Delete(e); // delete Elem
}
}
void ProcessNonemitting(int32 frame) {
// note: "frame" is the same as emitting states just processed.
// Processes nonemitting arcs for one frame. Propagates within toks_.
// Note-- this queue structure is is not very optimal as
// it may cause us to process states unnecessarily (e.g. more than once),
// but in the baseline code, turning this vector into a set to fix this
// problem did not improve overall speed.
KALDI_ASSERT(queue_.empty());
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
for (const Elem *e = toks_.GetList(); e != NULL; e = e->tail) {
queue_.push_back(e);
// for pruning with current best token
best_cost = std::min(best_cost, static_cast<BaseFloat>(e->val->tot_cost));
}
if (queue_.empty()) {
if (!warned_) {
KALDI_ERR << "Error in ProcessNonemitting: no surviving tokens: frame is "
<< frame;
warned_ = true;
}
}
BaseFloat cutoff = best_cost + config_.beam;
while (!queue_.empty()) {
const Elem *e = queue_.back();
queue_.pop_back();
PairId state_pair = e->key;
Token *tok = e->val; // would segfault if state not in
// toks_ but this can't happen.
BaseFloat cur_cost = tok->tot_cost;
if (cur_cost >= cutoff) // Don't bother processing successors.
continue;
StateId state = PairToState(state_pair),
lm_state = PairToLmState(state_pair);
// If "tok" has any existing forward links, delete them,
// because we're about to regenerate them. This is a kind
// of non-optimality (remember, this is the simple decoder),
// but since most states are emitting it's not a huge issue.
tok->DeleteForwardLinks(); // necessary when re-visiting
tok->links = NULL;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc_ref = aiter.Value();
if (arc_ref.ilabel == 0) { // propagate nonemitting only...
Arc arc(arc_ref);
StateId next_lm_state = PropagateLm(lm_state, &arc);
BaseFloat graph_cost = arc.weight.Value(),
tot_cost = cur_cost + graph_cost;
if (tot_cost < cutoff) {
bool changed;
PairId next_pair = ConstructPair(arc.nextstate, next_lm_state);
Elem *e_new = FindOrAddToken(next_pair, frame, tot_cost,
false, &changed); // false: non-emit
tok->links = new ForwardLink(e_new->val, 0, arc.olabel,
graph_cost, 0, tok->links);
// "changed" tells us whether the new token has a different
// cost from before, or is new [if so, add into queue].
if (changed) queue_.push_back(e_new);
}
}
} // for all arcs
} // while queue not empty
}
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
// more than one list (e.g. for current and previous frames), but only one of
// them at a time can be indexed by StateId.
HashList<PairId, Token*> toks_;
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
// frame (members of TokenList are toks, must_prune_forward_links,
// must_prune_tokens).
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
// make it class member to avoid internal new/delete.
const fst::Fst<fst::StdArc> &fst_;
fst::DeterministicOnDemandFst<fst::StdArc> *lm_diff_fst_;
LatticeBiglmFasterDecoderConfig config_;
bool warned_noarc_;
int32 num_toks_; // current total #toks allocated...
bool warned_;
bool final_active_; // use this to say whether we found active final tokens
// on the last frame.
std::map<Token*, BaseFloat> final_costs_; // A cache of final-costs
// of tokens on the last frame-- it's just convenient to store it this way.
// It might seem unclear why we call DeleteElems(toks_.Clear()).
// There are two separate cleanup tasks we need to do at when we start a new file.
// one is to delete the Token objects in the list; the other is to delete
// the Elem objects. toks_.Clear() just clears them from the hash and gives ownership
// to the caller, who then has to call toks_.Delete(e) for each one. It was designed
// this way for convenience in propagating tokens from one frame to the next.
void DeleteElems(Elem *list) {
for (Elem *e = list, *e_tail; e != NULL; e = e_tail) {
e_tail = e->tail;
toks_.Delete(e);
}
toks_.Clear();
}
void ClearActiveTokens() { // a cleanup routine, at utt end/begin
for (size_t i = 0; i < active_toks_.size(); i++) {
// Delete all tokens alive on this frame, and any forward
// links they may have.
for (Token *tok = active_toks_[i].toks; tok != NULL; ) {
tok->DeleteForwardLinks();
Token *next_tok = tok->next;
delete tok;
num_toks_--;
tok = next_tok;
}
}
active_toks_.clear();
KALDI_ASSERT(num_toks_ == 0);
}
};
} // end namespace kaldi.
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,597 @@
// decoder/lattice-faster-decoder.h
// Copyright 2009-2013 Microsoft Corporation; Mirko Hannemann;
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
// 2014 Guoguo Chen
// 2018 Zhehuai 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_DECODER_LATTICE_FASTER_DECODER_H_
#define KALDI_DECODER_LATTICE_FASTER_DECODER_H_
//#include "decoder/grammar-fst.h"
#include "fst/fstlib.h"
#include "fst/memory.h"
#include "fstext/fstext-lib.h"
#include "itf/decodable-itf.h"
#include "lat/determinize-lattice-pruned.h"
#include "lat/kaldi-lattice.h"
#include "util/hash-list.h"
#include "util/stl-utils.h"
#include "bias-lm.h"
namespace kaldi {
struct LatticeFasterDecoderConfig {
BaseFloat beam;
int32 max_active;
int32 min_active;
BaseFloat lattice_beam;
int32 prune_interval;
bool determinize_lattice; // not inspected by this class... used in
// command-line program.
BaseFloat beam_delta;
BaseFloat hash_ratio;
// Note: we don't make prune_scale configurable on the command line, it's not
// a very important parameter. It affects the algorithm that prunes the
// tokens as we go.
BaseFloat prune_scale;
// Number of elements in the block for Token and ForwardLink memory
// pool allocation.
int32 memory_pool_tokens_block_size;
int32 memory_pool_links_block_size;
// Most of the options inside det_opts are not actually queried by the
// LatticeFasterDecoder class itself, but by the code that calls it, for
// example in the function DecodeUtteranceLatticeFaster.
fst::DeterminizeLatticePhonePrunedOptions det_opts;
LatticeFasterDecoderConfig(float glob_beam, float lat_beam)
: beam(glob_beam),
max_active(std::numeric_limits<int32>::max()),
min_active(200),
lattice_beam(lat_beam),
prune_interval(25),
determinize_lattice(true),
beam_delta(0.5),
hash_ratio(2.0),
prune_scale(0.1),
memory_pool_tokens_block_size(1 << 8),
memory_pool_links_block_size(1 << 8) {}
LatticeFasterDecoderConfig()
: beam(3.0),
max_active(std::numeric_limits<int32>::max()),
min_active(200),
lattice_beam(3.0),
prune_interval(25),
determinize_lattice(true),
beam_delta(0.5),
hash_ratio(2.0),
prune_scale(0.1),
memory_pool_tokens_block_size(1 << 8),
memory_pool_links_block_size(1 << 8) {}
void Register(OptionsItf *opts) {
det_opts.Register(opts);
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
opts->Register("max-active", &max_active, "Decoder max active states. Larger->slower; "
"more accurate");
opts->Register("min-active", &min_active, "Decoder minimum #active states.");
opts->Register("lattice-beam", &lattice_beam, "Lattice generation beam. Larger->slower, "
"and deeper lattices");
opts->Register("prune-interval", &prune_interval, "Interval (in frames) at "
"which to prune tokens");
opts->Register("determinize-lattice", &determinize_lattice, "If true, "
"determinize the lattice (lattice-determinization, keeping only "
"best pdf-sequence for each word-sequence).");
opts->Register("beam-delta", &beam_delta, "Increment used in decoding-- this "
"parameter is obscure and relates to a speedup in the way the "
"max-active constraint is applied. Larger is more accurate.");
opts->Register("hash-ratio", &hash_ratio, "Setting used in decoder to "
"control hash behavior");
opts->Register("memory-pool-tokens-block-size", &memory_pool_tokens_block_size,
"Memory pool block size suggestion for storing tokens (in elements). "
"Smaller uses less memory but increases cache misses.");
opts->Register("memory-pool-links-block-size", &memory_pool_links_block_size,
"Memory pool block size suggestion for storing links (in elements). "
"Smaller uses less memory but increases cache misses.");
}
void Check() const {
KALDI_ASSERT(beam > 0.0 && max_active > 1 && lattice_beam > 0.0
&& min_active <= max_active
&& prune_interval > 0 && beam_delta > 0.0 && hash_ratio >= 1.0
&& prune_scale > 0.0 && prune_scale < 1.0);
}
};
namespace decoder {
// We will template the decoder on the token type as well as the FST type; this
// is a mechanism so that we can use the same underlying decoder code for
// versions of the decoder that support quickly getting the best path
// (LatticeFasterOnlineDecoder, see lattice-faster-online-decoder.h) and also
// those that do not (LatticeFasterDecoder).
// ForwardLinks are the links from a token to a token on the next frame.
// or sometimes on the current frame (for input-epsilon links).
template <typename Token>
struct ForwardLink {
using Label = fst::StdArc::Label;
Token *next_tok; // the next token [or NULL if represents final-state]
Label ilabel; // ilabel on arc
Label olabel; // olabel on arc
BaseFloat graph_cost; // graph cost of traversing arc (contains LM, etc.)
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing arc
ForwardLink *next; // next in singly-linked list of forward arcs (arcs
// in the state-level lattice) from a token.
inline ForwardLink(Token *next_tok, Label ilabel, Label olabel,
BaseFloat graph_cost, BaseFloat acoustic_cost,
ForwardLink *next):
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
next(next) { }
};
struct StdToken {
using ForwardLinkT = ForwardLink<StdToken>;
using Token = StdToken;
// Standard token type for LatticeFasterDecoder. Each active HCLG
// (decoding-graph) state on each frame has one token.
// tot_cost is the total (LM + acoustic) cost from the beginning of the
// utterance up to this point. (but see cost_offset_, which is subtracted
// to keep it in a good numerical range).
BaseFloat tot_cost;
// exta_cost is >= 0. After calling PruneForwardLinks, this equals the
// minimum difference between the cost of the best path that this link is a
// part of, and the cost of the absolute best path, under the assumption that
// any of the currently active states at the decoding front may eventually
// succeed (e.g. if you were to take the currently active states one by one
// and compute this difference, and then take the minimum).
BaseFloat extra_cost;
// 'links' is the head of singly-linked list of ForwardLinks, which is what we
// use for lattice generation.
ForwardLinkT *links;
//'next' is the next in the singly-linked list of tokens for this frame.
Token *next;
// bias_lm_state is used to record the state of tokens in the bias lm network
LatticeArc::StateId bias_lm_state;
// This function does nothing and should be optimized out; it's needed
// so we can share the regular LatticeFasterDecoderTpl code and the code
// for LatticeFasterOnlineDecoder that supports fast traceback.
inline void SetBackpointer (Token *backpointer) { }
// This constructor just ignores the 'backpointer' argument. That argument is
// needed so that we can use the same decoder code for LatticeFasterDecoderTpl
// and LatticeFasterOnlineDecoderTpl (which needs backpointers to support a
// fast way to obtain the best path).
inline StdToken(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLinkT *links,
Token *next, Token *backpointer):
tot_cost(tot_cost), extra_cost(extra_cost), links(links), next(next), bias_lm_state(0) { }
inline void GetLabelSeq(Token *tok, std::vector<int> &phn_id) {}
};
struct BackpointerToken {
using ForwardLinkT = ForwardLink<BackpointerToken>;
using Token = BackpointerToken;
// BackpointerToken is like Token but also
// Standard token type for LatticeFasterDecoder. Each active HCLG
// (decoding-graph) state on each frame has one token.
// tot_cost is the total (LM + acoustic) cost from the beginning of the
// utterance up to this point. (but see cost_offset_, which is subtracted
// to keep it in a good numerical range).
BaseFloat tot_cost;
// exta_cost is >= 0. After calling PruneForwardLinks, this equals
// the minimum difference between the cost of the best path, and the cost of
// this is on, and the cost of the absolute best path, under the assumption
// that any of the currently active states at the decoding front may
// eventually succeed (e.g. if you were to take the currently active states
// one by one and compute this difference, and then take the minimum).
BaseFloat extra_cost;
// 'links' is the head of singly-linked list of ForwardLinks, which is what we
// use for lattice generation.
ForwardLinkT *links;
//'next' is the next in the singly-linked list of tokens for this frame.
BackpointerToken *next;
// Best preceding BackpointerToken (could be a on this frame, connected to
// this via an epsilon transition, or on a previous frame). This is only
// required for an efficient GetBestPath function in
// LatticeFasterOnlineDecoderTpl; it plays no part in the lattice generation
// (the "links" list is what stores the forward links, for that).
Token *backpointer;
// bias_lm_state is used to record the state of tokens in the bias lm network
LatticeArc::StateId bias_lm_state;
inline void SetBackpointer (Token *backpointer) {
this->backpointer = backpointer;
}
inline BackpointerToken(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLinkT *links,
Token *next, Token *backpointer):
tot_cost(tot_cost), extra_cost(extra_cost), links(links), next(next),
backpointer(backpointer), bias_lm_state(0) { }
inline void GetLabelSeq(Token *token, std::vector<int> &phn_id) {
ForwardLinkT* link;
Token *tok = token;
while (tok && tok->backpointer) {
for (link = tok->backpointer->links; link != NULL; link = link->next) {
if (link->next_tok == tok) {
phn_id.push_back(link->ilabel - 1);
break;
}
}
tok = tok->backpointer;
}
}
};
} // namespace decoder
/** This is the "normal" lattice-generating decoder.
See \ref lattices_generation \ref decoders_faster and \ref decoders_simple
for more information.
The decoder is templated on the FST type and the token type. The token type
will normally be StdToken, but also may be BackpointerToken which is to support
quick lookup of the current best path (see lattice-faster-online-decoder.h)
The FST you invoke this decoder which is expected to equal
Fst::Fst<fst::StdArc>, a.k.a. StdFst, or GrammarFst. If you invoke it with
FST == StdFst and it notices that the actual FST type is
fst::VectorFst<fst::StdArc> or fst::ConstFst<fst::StdArc>, the decoder object
will internally cast itself to one that is templated on those more specific
types; this is an optimization for speed.
*/
template <typename FST, typename Token = decoder::StdToken>
class LatticeFasterDecoderTpl {
public:
using Arc = typename FST::Arc;
using Label = typename Arc::Label;
using StateId = typename Arc::StateId;
using Weight = typename Arc::Weight;
using ForwardLinkT = decoder::ForwardLink<Token>;
// Instantiate this class once for each thing you have to decode.
// This version of the constructor does not take ownership of
// 'fst'.
LatticeFasterDecoderTpl(const FST &fst,
const LatticeFasterDecoderConfig &config);
// This version of the constructor takes ownership of the fst, and will delete
// it when this object is destroyed.
LatticeFasterDecoderTpl(const LatticeFasterDecoderConfig &config,
FST *fst);
//LatticeFasterDecoderTpl() { }
void SetOptions(const LatticeFasterDecoderConfig &config) {
config_ = config;
}
const LatticeFasterDecoderConfig &GetOptions() const {
return config_;
}
~LatticeFasterDecoderTpl();
/// Decodes until there are no more frames left in the "decodable" object..
/// note, this may block waiting for input if the "decodable" object blocks.
/// Returns true if any kind of traceback is available (not necessarily from a
/// final state).
bool Decode(DecodableInterface *decodable);
/// says whether a final-state was active on the last frame. If it was not, the
/// lattice (or traceback) will end with states that are not final-states.
bool ReachedFinal() const {
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
}
/// Outputs an FST corresponding to the single best path through the lattice.
/// Returns true if result is nonempty (using the return status is deprecated,
/// it will become void). If "use_final_probs" is true AND we reached the
/// final-state of the graph then it will include those as final-probs, else
/// it will treat all final-probs as one. Note: this just calls GetRawLattice()
/// and figures out the shortest path.
bool GetBestPath(Lattice *ofst,
bool use_final_probs = true) const;
/// Outputs an FST corresponding to the raw, state-level
/// tracebacks. Returns true if result is nonempty.
/// If "use_final_probs" is true AND we reached the final-state
/// of the graph then it will include those as final-probs, else
/// it will treat all final-probs as one.
/// The raw lattice will be topologically sorted.
///
/// See also GetRawLatticePruned in lattice-faster-online-decoder.h,
/// which also supports a pruning beam, in case for some reason
/// you want it pruned tighter than the regular lattice beam.
/// We could put that here in future needed.
bool GetRawLattice(Lattice *ofst, bool use_final_probs = true) const;
/// [Deprecated, users should now use GetRawLattice and determinize it
/// themselves, e.g. using DeterminizeLatticePhonePrunedWrapper].
/// Outputs an FST corresponding to the lattice-determinized
/// lattice (one path per word sequence). Returns true if result is nonempty.
/// If "use_final_probs" is true AND we reached the final-state of the graph
/// then it will include those as final-probs, else it will treat all
/// final-probs as one.
bool GetLattice(CompactLattice *ofst,
bool use_final_probs = true) const;
/// InitDecoding initializes the decoding, and should only be used if you
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need to
/// call this. You can also call InitDecoding if you have already decoded an
/// utterance and want to start with a new utterance.
void InitDecoding();
/// This will decode until there are no more frames ready in the decodable
/// object. You can keep calling it each time more frames become available.
/// If max_num_frames is specified, it specifies the maximum number of frames
/// the function will decode before returning.
void AdvanceDecoding(DecodableInterface *decodable,
int32 max_num_frames = -1);
/// This function may be optionally called after AdvanceDecoding(), when you
/// do not plan to decode any further. It does an extra pruning step that
/// will help to prune the lattices output by GetLattice and (particularly)
/// GetRawLattice more completely, particularly toward the end of the
/// utterance. If you call this, you cannot call AdvanceDecoding again (it
/// will fail), and you cannot call GetLattice() and related functions with
/// use_final_probs = false. Used to be called PruneActiveTokensFinal().
void FinalizeDecoding();
/// FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
/// more information. It returns the difference between the best (final-cost
/// plus cost) of any token on the final frame, and the best cost of any token
/// on the final frame. If it is infinity it means no final-states were
/// present on the final frame. It will usually be nonnegative. If it not
/// too positive (e.g. < 5 is my first guess, but this is not tested) you can
/// take it as a good indication that we reached the final-state with
/// reasonable likelihood.
BaseFloat FinalRelativeCost() const;
// Returns the number of frames decoded so far. The value returned changes
// whenever we call ProcessEmitting().
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
std::string GetTokResult(Token *tok);
void SetBiasLm(std::shared_ptr<funasr::BiasLm> &bias_lm) {
bias_lm_ = bias_lm;
}
void ClearBiasLm() {
bias_lm_.reset();
}
protected:
// we make things protected instead of private, as code in
// LatticeFasterOnlineDecoderTpl, which inherits from this, also uses the
// internals.
// Deletes the elements of the singly linked list tok->links.
void DeleteForwardLinks(Token *tok);
// head of per-frame list of Tokens (list is in topological order),
// and something saying whether we ever pruned it using PruneForwardLinks.
struct TokenList {
Token *toks;
bool must_prune_forward_links;
bool must_prune_tokens;
TokenList(): toks(NULL), must_prune_forward_links(true),
must_prune_tokens(true) { }
};
using Elem = typename HashList<StateId, Token*>::Elem;
// Equivalent to:
// struct Elem {
// StateId key;
// Token *val;
// Elem *tail;
// };
void PossiblyResizeHash(size_t num_toks);
// FindOrAddToken either locates a token in hash of toks_, or if necessary
// inserts a new, empty token (i.e. with no forward links) for the current
// frame. [note: it's inserted if necessary into hash toks_ and also into the
// singly linked list of tokens active on this frame (whose head is at
// active_toks_[frame]). The frame_plus_one argument is the acoustic frame
// index plus one, which is used to index into the active_toks_ array.
// Returns the Token pointer. Sets "changed" (if non-NULL) to true if the
// token was newly created or the cost changed.
// If Token == StdToken, the 'backpointer' argument has no purpose (and will
// hopefully be optimized out).
inline Elem *FindOrAddToken(StateId state, int32 frame_plus_one,
BaseFloat tot_cost, Token *backpointer,
bool *changed, StateId bias_lm_state = 0);
// prunes outgoing links for all tokens in active_toks_[frame]
// it's called by PruneActiveTokens
// all links, that have link_extra_cost > lattice_beam are pruned
// delta is the amount by which the extra_costs must change
// before we set *extra_costs_changed = true.
// If delta is larger, we'll tend to go back less far
// toward the beginning of the file.
// extra_costs_changed is set to true if extra_cost was changed for any token
// links_pruned is set to true if any link in any token was pruned
void PruneForwardLinks(int32 frame_plus_one, bool *extra_costs_changed,
bool *links_pruned,
BaseFloat delta);
// This function computes the final-costs for tokens active on the final
// frame. It outputs to final-costs, if non-NULL, a map from the Token*
// pointer to the final-prob of the corresponding state, for all Tokens
// that correspond to states that have final-probs. This map will be
// empty if there were no final-probs. It outputs to
// final_relative_cost, if non-NULL, the difference between the best
// forward-cost including the final-prob cost, and the best forward-cost
// without including the final-prob cost (this will usually be positive), or
// infinity if there were no final-probs. [c.f. FinalRelativeCost(), which
// outputs this quanitity]. It outputs to final_best_cost, if
// non-NULL, the lowest for any token t active on the final frame, of
// forward-cost[t] + final-cost[t], where final-cost[t] is the final-cost in
// the graph of the state corresponding to token t, or the best of
// forward-cost[t] if there were no final-probs active on the final frame.
// You cannot call this after FinalizeDecoding() has been called; in that
// case you should get the answer from class-member variables.
void ComputeFinalCosts(unordered_map<Token*, BaseFloat> *final_costs,
BaseFloat *final_relative_cost,
BaseFloat *final_best_cost) const;
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
// on the final frame. If there are final tokens active, it uses
// the final-probs for pruning, otherwise it treats all tokens as final.
void PruneForwardLinksFinal();
// Prune away any tokens on this frame that have no forward links.
// [we don't do this in PruneForwardLinks because it would give us
// a problem with dangling pointers].
// It's called by PruneActiveTokens if any forward links have been pruned
void PruneTokensForFrame(int32 frame_plus_one);
// Go backwards through still-alive tokens, pruning them if the
// forward+backward cost is more than lat_beam away from the best path. It's
// possible to prove that this is "correct" in the sense that we won't lose
// anything outside of lat_beam, regardless of what happens in the future.
// delta controls when it considers a cost to have changed enough to continue
// going backward and propagating the change. larger delta -> will recurse
// less far.
void PruneActiveTokens(BaseFloat delta);
/// Gets the weight cutoff. Also counts the active tokens.
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count,
BaseFloat *adaptive_beam, Elem **best_elem);
/// Processes emitting arcs for one frame. Propagates from prev_toks_ to
/// cur_toks_. Returns the cost cutoff for subsequent ProcessNonemitting() to
/// use.
BaseFloat ProcessEmitting(DecodableInterface *decodable);
/// Processes nonemitting (epsilon) arcs for one frame. Called after
/// ProcessEmitting() on each frame. The cost cutoff is computed by the
/// preceding ProcessEmitting().
void ProcessNonemitting(BaseFloat cost_cutoff);
// HashList defined in ../util/hash-list.h. It actually allows us to maintain
// more than one list (e.g. for current and previous frames), but only one of
// them at a time can be indexed by StateId. It is indexed by frame-index
// plus one, where the frame-index is zero-based, as used in decodable object.
// That is, the emitting probs of frame t are accounted for in tokens at
// toks_[t+1]. The zeroth frame is for nonemitting transition at the start of
// the graph.
HashList<StateId, Token*> toks_;
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
// frame (members of TokenList are toks, must_prune_forward_links,
// must_prune_tokens).
std::vector<const Elem* > queue_; // temp variable used in ProcessNonemitting,
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
// fst_ is a pointer to the FST we are decoding from.
const FST *fst_;
// delete_fst_ is true if the pointer fst_ needs to be deleted when this
// object is destroyed.
bool delete_fst_;
std::vector<BaseFloat> cost_offsets_; // This contains, for each
// frame, an offset that was added to the acoustic log-likelihoods on that
// frame in order to keep everything in a nice dynamic range i.e. close to
// zero, to reduce roundoff errors.
LatticeFasterDecoderConfig config_;
int32 num_toks_; // current total #toks allocated...
bool warned_;
/// decoding_finalized_ is true if someone called FinalizeDecoding(). [note,
/// calling this is optional]. If true, it's forbidden to decode more. Also,
/// if this is set, then the output of ComputeFinalCosts() is in the next
/// three variables. The reason we need to do this is that after
/// FinalizeDecoding() calls PruneTokensForFrame() for the final frame, some
/// of the tokens on the last frame are freed, so we free the list from toks_
/// to avoid having dangling pointers hanging around.
bool decoding_finalized_;
/// For the meaning of the next 3 variables, see the comment for
/// decoding_finalized_ above., and ComputeFinalCosts().
unordered_map<Token*, BaseFloat> final_costs_;
BaseFloat final_relative_cost_;
BaseFloat final_best_cost_;
// Memory pools for storing tokens and forward links.
// We use it to decrease the work put on allocator and to move some of data
// together. Too small block sizes will result in more work to allocator but
// bigger ones increase the memory usage.
fst::MemoryPool<Token> token_pool_;
fst::MemoryPool<ForwardLinkT> forward_link_pool_;
// There are various cleanup tasks... the toks_ structure contains
// singly linked lists of Token pointers, where Elem is the list type.
// It also indexes them in a hash, indexed by state (this hash is only
// maintained for the most recent frame). toks_.Clear()
// deletes them from the hash and returns the list of Elems. The
// function DeleteElems calls toks_.Delete(elem) for each elem in
// the list, which returns ownership of the Elem to the toks_ structure
// for reuse, but does not delete the Token pointer. The Token pointers
// are reference-counted and are ultimately deleted in PruneTokensForFrame,
// but are also linked together on each frame by their own linked-list,
// using the "next" pointer. We delete them manually.
void DeleteElems(Elem *list);
// This function takes a singly linked list of tokens for a single frame, and
// outputs a list of them in topological order (it will crash if no such order
// can be found, which will typically be due to decoding graphs with epsilon
// cycles, which are not allowed). Note: the output list may contain NULLs,
// which the caller should pass over; it just happens to be more efficient for
// the algorithm to output a list that contains NULLs.
static void TopSortTokens(Token *tok_list,
std::vector<Token*> *topsorted_list);
void ClearActiveTokens();
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeFasterDecoderTpl);
std::shared_ptr<funasr::BiasLm> bias_lm_ = nullptr;
};
typedef LatticeFasterDecoderTpl<fst::StdFst, decoder::StdToken> LatticeFasterDecoder;
} // end namespace kaldi.
#endif
@@ -0,0 +1,285 @@
// decoder/lattice-faster-online-decoder.cc
// Copyright 2009-2012 Microsoft Corporation Mirko Hannemann
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
// 2014 Guoguo Chen
// 2014 IMSL, PKU-HKUST (author: Wei Shi)
// 2018 Zhehuai 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.
// see note at the top of lattice-faster-decoder.cc, about how to maintain this
// file in sync with lattice-faster-decoder.cc
#include "decoder/lattice-faster-online-decoder.h"
#include "lat/lattice-functions.h"
namespace kaldi {
template <typename FST>
bool LatticeFasterOnlineDecoderTpl<FST>::TestGetBestPath(
bool use_final_probs) const {
Lattice lat1;
{
Lattice raw_lat;
this->GetRawLattice(&raw_lat, use_final_probs);
ShortestPath(raw_lat, &lat1);
}
Lattice lat2;
GetBestPath(&lat2, use_final_probs);
BaseFloat delta = 0.1;
int32 num_paths = 1;
if (!fst::RandEquivalent(lat1, lat2, num_paths, delta, rand())) {
KALDI_WARN << "Best-path test failed";
return false;
} else {
return true;
}
}
// Outputs an FST corresponding to the single best path through the lattice.
template <typename FST>
bool LatticeFasterOnlineDecoderTpl<FST>::GetBestPath(Lattice *olat,
bool use_final_probs) const {
olat->DeleteStates();
BaseFloat final_graph_cost;
BestPathIterator iter = BestPathEnd(use_final_probs, &final_graph_cost);
if (iter.Done())
return false; // would have printed warning.
StateId state = olat->AddState();
olat->SetFinal(state, LatticeWeight(final_graph_cost, 0.0));
while (!iter.Done()) {
LatticeArc arc;
iter = TraceBackBestPath(iter, &arc);
arc.nextstate = state;
StateId new_state = olat->AddState();
olat->AddArc(new_state, arc);
state = new_state;
}
olat->SetStart(state);
return true;
}
template <typename FST>
typename LatticeFasterOnlineDecoderTpl<FST>::BestPathIterator LatticeFasterOnlineDecoderTpl<FST>::BestPathEnd(
bool use_final_probs,
BaseFloat *final_cost_out) const {
if (this->decoding_finalized_ && !use_final_probs)
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
<< "BestPathEnd() with use_final_probs == false";
KALDI_ASSERT(this->NumFramesDecoded() > 0 &&
"You cannot call BestPathEnd if no frames were decoded.");
unordered_map<Token*, BaseFloat> final_costs_local;
const unordered_map<Token*, BaseFloat> &final_costs =
(this->decoding_finalized_ ? this->final_costs_ :final_costs_local);
if (!this->decoding_finalized_ && use_final_probs)
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
// Singly linked list of tokens on last frame (access list through "next"
// pointer).
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
BaseFloat best_final_cost = 0;
Token *best_tok = NULL;
for (Token *tok = this->active_toks_.back().toks;
tok != NULL; tok = tok->next) {
BaseFloat cost = tok->tot_cost, final_cost = 0.0;
if (use_final_probs && !final_costs.empty()) {
// if we are instructed to use final-probs, and any final tokens were
// active on final frame, include the final-prob in the cost of the token.
typename unordered_map<Token*, BaseFloat>::const_iterator
iter = final_costs.find(tok);
if (iter != final_costs.end()) {
final_cost = iter->second;
cost += final_cost;
} else {
cost = std::numeric_limits<BaseFloat>::infinity();
}
}
if (cost < best_cost) {
best_cost = cost;
best_tok = tok;
best_final_cost = final_cost;
}
}
if (best_tok == NULL) { // this should not happen, and is likely a code error or
// caused by infinities in likelihoods, but I'm not making
// it a fatal error for now.
KALDI_WARN << "No final token found.";
}
if (final_cost_out)
*final_cost_out = best_final_cost;
return BestPathIterator(best_tok, this->NumFramesDecoded() - 1);
}
template <typename FST>
typename LatticeFasterOnlineDecoderTpl<FST>::BestPathIterator LatticeFasterOnlineDecoderTpl<FST>::TraceBackBestPath(
BestPathIterator iter, LatticeArc *oarc) const {
KALDI_ASSERT(!iter.Done() && oarc != NULL);
Token *tok = static_cast<Token*>(iter.tok);
int32 cur_t = iter.frame, step_t = 0;
if (tok->backpointer != NULL) {
// retrieve the correct forward link(with the best link cost)
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
ForwardLinkT *link;
for (link = tok->backpointer->links;
link != NULL; link = link->next) {
if (link->next_tok == tok) { // this is a link to "tok"
BaseFloat graph_cost = link->graph_cost,
acoustic_cost = link->acoustic_cost;
BaseFloat cost = graph_cost + acoustic_cost;
if (cost < best_cost) {
oarc->ilabel = link->ilabel;
oarc->olabel = link->olabel;
if (link->ilabel != 0) {
KALDI_ASSERT(static_cast<size_t>(cur_t) < this->cost_offsets_.size());
acoustic_cost -= this->cost_offsets_[cur_t];
step_t = -1;
} else {
step_t = 0;
}
oarc->weight = LatticeWeight(graph_cost, acoustic_cost);
best_cost = cost;
}
}
}
if (link == NULL &&
best_cost == std::numeric_limits<BaseFloat>::infinity()) { // Did not find correct link.
KALDI_ERR << "Error tracing best-path back (likely "
<< "bug in token-pruning algorithm)";
}
} else {
oarc->ilabel = 0;
oarc->olabel = 0;
oarc->weight = LatticeWeight::One(); // zero costs.
}
return BestPathIterator(tok->backpointer, cur_t + step_t);
}
template <typename FST>
bool LatticeFasterOnlineDecoderTpl<FST>::GetRawLatticePruned(
Lattice *ofst,
bool use_final_probs,
BaseFloat beam) const {
typedef LatticeArc Arc;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
typedef Arc::Label Label;
// Note: you can't use the old interface (Decode()) if you want to
// get the lattice with use_final_probs = false. You'd have to do
// InitDecoding() and then AdvanceDecoding().
if (this->decoding_finalized_ && !use_final_probs)
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
<< "GetRawLattice() with use_final_probs == false";
unordered_map<Token*, BaseFloat> final_costs_local;
const unordered_map<Token*, BaseFloat> &final_costs =
(this->decoding_finalized_ ? this->final_costs_ : final_costs_local);
if (!this->decoding_finalized_ && use_final_probs)
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
ofst->DeleteStates();
// num-frames plus one (since frames are one-based, and we have
// an extra frame for the start-state).
int32 num_frames = this->active_toks_.size() - 1;
KALDI_ASSERT(num_frames > 0);
for (int32 f = 0; f <= num_frames; f++) {
if (this->active_toks_[f].toks == NULL) {
KALDI_WARN << "No tokens active on frame " << f
<< ": not producing lattice.\n";
return false;
}
}
unordered_map<Token*, StateId> tok_map;
std::queue<std::pair<Token*, int32> > tok_queue;
// First initialize the queue and states. Put the initial state on the queue;
// this is the last token in the list active_toks_[0].toks.
for (Token *tok = this->active_toks_[0].toks;
tok != NULL; tok = tok->next) {
if (tok->next == NULL) {
tok_map[tok] = ofst->AddState();
ofst->SetStart(tok_map[tok]);
std::pair<Token*, int32> tok_pair(tok, 0); // #frame = 0
tok_queue.push(tok_pair);
}
}
// Next create states for "good" tokens
while (!tok_queue.empty()) {
std::pair<Token*, int32> cur_tok_pair = tok_queue.front();
tok_queue.pop();
Token *cur_tok = cur_tok_pair.first;
int32 cur_frame = cur_tok_pair.second;
KALDI_ASSERT(cur_frame >= 0 &&
cur_frame <= this->cost_offsets_.size());
typename unordered_map<Token*, StateId>::const_iterator iter =
tok_map.find(cur_tok);
KALDI_ASSERT(iter != tok_map.end());
StateId cur_state = iter->second;
for (ForwardLinkT *l = cur_tok->links;
l != NULL;
l = l->next) {
Token *next_tok = l->next_tok;
if (next_tok->extra_cost < beam) {
// so both the current and the next token are good; create the arc
int32 next_frame = l->ilabel == 0 ? cur_frame : cur_frame + 1;
StateId nextstate;
if (tok_map.find(next_tok) == tok_map.end()) {
nextstate = tok_map[next_tok] = ofst->AddState();
tok_queue.push(std::pair<Token*, int32>(next_tok, next_frame));
} else {
nextstate = tok_map[next_tok];
}
BaseFloat cost_offset = (l->ilabel != 0 ?
this->cost_offsets_[cur_frame] : 0);
Arc arc(l->ilabel, l->olabel,
Weight(l->graph_cost, l->acoustic_cost - cost_offset),
nextstate);
ofst->AddArc(cur_state, arc);
}
}
if (cur_frame == num_frames) {
if (use_final_probs && !final_costs.empty()) {
typename unordered_map<Token*, BaseFloat>::const_iterator iter =
final_costs.find(cur_tok);
if (iter != final_costs.end())
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
} else {
ofst->SetFinal(cur_state, LatticeWeight::One());
}
}
}
return (ofst->NumStates() != 0);
}
// Instantiate the template for the FST types that we'll need.
template class LatticeFasterOnlineDecoderTpl<fst::Fst<fst::StdArc> >;
template class LatticeFasterOnlineDecoderTpl<fst::VectorFst<fst::StdArc> >;
template class LatticeFasterOnlineDecoderTpl<fst::ConstFst<fst::StdArc> >;
//template class LatticeFasterOnlineDecoderTpl<fst::ConstGrammarFst >;
//template class LatticeFasterOnlineDecoderTpl<fst::VectorGrammarFst >;
} // end namespace kaldi.
@@ -0,0 +1,146 @@
// decoder/lattice-faster-online-decoder.h
// Copyright 2009-2013 Microsoft Corporation; Mirko Hannemann;
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
// 2014 Guoguo Chen
// 2018 Zhehuai 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.
// see note at the top of lattice-faster-decoder.h, about how to maintain this
// file in sync with lattice-faster-decoder.h
#ifndef KALDI_DECODER_LATTICE_FASTER_ONLINE_DECODER_H_
#define KALDI_DECODER_LATTICE_FASTER_ONLINE_DECODER_H_
#include "util/stl-utils.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "fstext/fstext-lib.h"
#include "lat/determinize-lattice-pruned.h"
#include "lat/kaldi-lattice.h"
#include "decoder/lattice-faster-decoder.h"
namespace kaldi {
/** LatticeFasterOnlineDecoderTpl is as LatticeFasterDecoderTpl but also
supports an efficient way to get the best path (see the function
BestPathEnd()), which is useful in endpointing and in situations where you
might want to frequently access the best path.
This is only templated on the FST type, since the Token type is required to
be BackpointerToken. Actually it only makes sense to instantiate
LatticeFasterDecoderTpl with Token == BackpointerToken if you do so indirectly via
this child class.
*/
template <typename FST>
class LatticeFasterOnlineDecoderTpl:
public LatticeFasterDecoderTpl<FST, decoder::BackpointerToken> {
public:
using Arc = typename FST::Arc;
using Label = typename Arc::Label;
using StateId = typename Arc::StateId;
using Weight = typename Arc::Weight;
using Token = decoder::BackpointerToken;
using ForwardLinkT = decoder::ForwardLink<Token>;
// Instantiate this class once for each thing you have to decode.
// This version of the constructor does not take ownership of
// 'fst'.
LatticeFasterOnlineDecoderTpl(const FST &fst,
const LatticeFasterDecoderConfig &config):
LatticeFasterDecoderTpl<FST, Token>(fst, config) { }
// This version of the initializer takes ownership of 'fst', and will delete
// it when this object is destroyed.
LatticeFasterOnlineDecoderTpl(const LatticeFasterDecoderConfig &config, FST *fst):
LatticeFasterDecoderTpl<FST, Token>(config, fst) { }
//LatticeFasterOnlineDecoderTpl() {}
struct BestPathIterator {
void *tok;
int32 frame;
// note, "frame" is the frame-index of the frame you'll get the
// transition-id for next time, if you call TraceBackBestPath on this
// iterator (assuming it's not an epsilon transition). Note that this
// is one less than you might reasonably expect, e.g. it's -1 for
// the nonemitting transitions before the first frame.
BestPathIterator(void *t, int32 f): tok(t), frame(f) { }
bool Done() const { return tok == NULL; }
};
/// Outputs an FST corresponding to the single best path through the lattice.
/// This is quite efficient because it doesn't get the entire raw lattice and find
/// the best path through it; instead, it uses the BestPathEnd and BestPathIterator
/// so it basically traces it back through the lattice.
/// Returns true if result is nonempty (using the return status is deprecated,
/// it will become void). If "use_final_probs" is true AND we reached the
/// final-state of the graph then it will include those as final-probs, else
/// it will treat all final-probs as one.
bool GetBestPath(Lattice *ofst,
bool use_final_probs = true) const;
/// This function does a self-test of GetBestPath(). Returns true on
/// success; returns false and prints a warning on failure.
bool TestGetBestPath(bool use_final_probs = true) const;
/// This function returns an iterator that can be used to trace back
/// the best path. If use_final_probs == true and at least one final state
/// survived till the end, it will use the final-probs in working out the best
/// final Token, and will output the final cost to *final_cost (if non-NULL),
/// else it will use only the forward likelihood, and will put zero in
/// *final_cost (if non-NULL).
/// Requires that NumFramesDecoded() > 0.
BestPathIterator BestPathEnd(bool use_final_probs,
BaseFloat *final_cost = NULL) const;
/// This function can be used in conjunction with BestPathEnd() to trace back
/// the best path one link at a time (e.g. this can be useful in endpoint
/// detection). By "link" we mean a link in the graph; not all links cross
/// frame boundaries, but each time you see a nonzero ilabel you can interpret
/// that as a frame. The return value is the updated iterator. It outputs
/// the ilabel and olabel, and the (graph and acoustic) weight to the "arc" pointer,
/// while leaving its "nextstate" variable unchanged.
BestPathIterator TraceBackBestPath(
BestPathIterator iter, LatticeArc *arc) const;
/// Behaves the same as GetRawLattice but only processes tokens whose
/// extra_cost is smaller than the best-cost plus the specified beam.
/// It is only worthwhile to call this function if beam is less than
/// the lattice_beam specified in the config; otherwise, it would
/// return essentially the same thing as GetRawLattice, but more slowly.
bool GetRawLatticePruned(Lattice *ofst,
bool use_final_probs,
BaseFloat beam) const;
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeFasterOnlineDecoderTpl);
};
typedef LatticeFasterOnlineDecoderTpl<fst::StdFst> LatticeFasterOnlineDecoder;
} // end namespace kaldi.
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,734 @@
// decoder/lattice-incremental-decoder.h
// Copyright 2019 Zhehuai Chen, 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_DECODER_LATTICE_INCREMENTAL_DECODER_H_
#define KALDI_DECODER_LATTICE_INCREMENTAL_DECODER_H_
#include "util/stl-utils.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "fstext/fstext-lib.h"
#include "lat/determinize-lattice-pruned.h"
#include "lat/kaldi-lattice.h"
#include "decoder/grammar-fst.h"
#include "decoder/lattice-faster-decoder.h"
namespace kaldi {
/**
The normal decoder, lattice-faster-decoder.h, sometimes has an issue when
doing real-time applications with long utterances, that each time you get the
lattice the lattice determinization can take a considerable amount of time;
this introduces latency. This version of the decoder spreads the work of
lattice determinization out throughout the decoding process.
NOTE:
Please see https://www.danielpovey.com/files/ *TBD* .pdf for a technical
explanation of what is going on here.
GLOSSARY OF TERMS:
chunk: We do the determinization on chunks of frames; these
may coincide with the chunks on which the user calls
AdvanceDecoding(). The basic idea is to extract chunks
of the raw lattice and determinize them individually, but
it gets much more complicated than that. The chunks
should normally be at least as long as a word (let's say,
at least 20 frames), or the overhead of this algorithm
might become excessive and affect RTF.
raw lattice chunk: A chunk of raw (i.e. undeterminized) lattice
that we will determinize. In the paper this corresponds
to the FST B that is described in Section 5.2.
token_label, state_label / token-label, state-label:
In the paper these are both referred to as `state labels` (these are
special, large integer id's that refer to states in the undeterminized
lattice and in the the determinized lattice); but we use two separate
terms here, for more clarity, when referring to the undeterminized
vs. determinized lattice.
token_label conceptually refers to states in the
raw lattice, but we don't materialize the entire
raw lattice as a physical FST and and these tokens
are actually tokens (template type Token) held by
the decoder
state_label when used in this code refers specifically
to labels that identify states in the determinized
lattice (i.e. state indexes in lat_).
token-final state
A state in a raw lattice or in a determinized chunk that has an arc
entering it that has a `token-label` on it (as defined above).
These states will have nonzero final-probs.
redeterminized-non-splice-state, aka ns_redet:
A redeterminized state which is not also a splice state;
refer to the paper for explanation. In the already-determinized
part this means a redeterminized state which is not final.
canonical appended lattice: This is the appended compact lattice
that we conceptually have (i.e. what we described in the paper).
The difference from the "actual appended lattice" stored
in LatticeIncrementalDeterminizer::clat_ is that the
actual appended lattice has all its final-arcs replaced with
final-probs, and we keep the real final-arcs "on the side" in a
separate data structure. The final-probs in clat_ aren't
necessarily related to the costs on the final-arcs; instead
they can have arbitrary values passed in by the user (e.g.
if we want to include final-probs). This means that the
clat_ can be returned without modification to the user who wants
a partially determinized result.
final-arc: An arc in the canonical appended CompactLattice which
goes to a final-state. These arcs will have `state-labels` as
their labels.
*/
struct LatticeIncrementalDecoderConfig {
// All the configuration values until det_opts are the same as in
// LatticeFasterDecoder. For clarity we repeat them rather than inheriting.
BaseFloat beam;
int32 max_active;
int32 min_active;
BaseFloat lattice_beam;
int32 prune_interval;
BaseFloat beam_delta; // has nothing to do with beam_ratio
BaseFloat hash_ratio;
BaseFloat prune_scale; // Note: we don't make this configurable on the command line,
// it's not a very important parameter. It affects the
// algorithm that prunes the tokens as we go.
// Most of the options inside det_opts are not actually queried by the
// LatticeIncrementalDecoder class itself, but by the code that calls it, for
// example in the function DecodeUtteranceLatticeIncremental.
fst::DeterminizeLatticePhonePrunedOptions det_opts;
// The configuration values from this point on are specific to the
// incremental determinization. See where they are registered for
// explanation.
// Caution: these are only inspected in UpdateLatticeDeterminization().
// If you call
int32 determinize_max_delay;
int32 determinize_min_chunk_size;
int32 determinize_max_active;
LatticeIncrementalDecoderConfig()
: beam(16.0),
max_active(std::numeric_limits<int32>::max()),
min_active(200),
lattice_beam(10.0),
prune_interval(25),
beam_delta(0.5),
hash_ratio(2.0),
prune_scale(0.01),
determinize_max_delay(60),
determinize_min_chunk_size(20),
determinize_max_active(200) {
det_opts.minimize = false;
}
void Register(OptionsItf *opts) {
det_opts.Register(opts);
opts->Register("beam", &beam, "Decoding beam. Larger->slower, more accurate.");
opts->Register("max-active", &max_active,
"Decoder max active states. Larger->slower; "
"more accurate");
opts->Register("min-active", &min_active, "Decoder minimum #active states.");
opts->Register("lattice-beam", &lattice_beam,
"Lattice generation beam. Larger->slower, "
"and deeper lattices");
opts->Register("prune-interval", &prune_interval,
"Interval (in frames) at "
"which to prune tokens");
opts->Register("beam-delta", &beam_delta,
"Increment used in decoding-- this "
"parameter is obscure and relates to a speedup in the way the "
"max-active constraint is applied. Larger is more accurate.");
opts->Register("hash-ratio", &hash_ratio,
"Setting used in decoder to "
"control hash behavior");
opts->Register("determinize-max-delay", &determinize_max_delay,
"Maximum frames of delay between decoding a frame and "
"determinizing it");
opts->Register("determinize-min-chunk-size", &determinize_min_chunk_size,
"Minimum chunk size used in determinization");
opts->Register("determinize-max-active", &determinize_max_active,
"Maximum number of active tokens to update determinization");
}
void Check() const {
if (!(beam > 0.0 && max_active > 1 && lattice_beam > 0.0 &&
min_active <= max_active && prune_interval > 0 &&
beam_delta > 0.0 && hash_ratio >= 1.0 &&
prune_scale > 0.0 && prune_scale < 1.0 &&
determinize_max_delay > determinize_min_chunk_size &&
determinize_min_chunk_size > 0 &&
determinize_max_active >= 0))
KALDI_ERR << "Invalid options given to decoder";
/* Minimization of the chunks is not compatible withour algorithm (or at
least, would require additional complexity to implement.) */
if (det_opts.minimize || !det_opts.word_determinize)
KALDI_ERR << "Invalid determinization options given to decoder.";
}
};
/**
This class is used inside LatticeIncrementalDecoderTpl; it handles
some of the details of incremental determinization.
https://www.danielpovey.com/files/ *TBD*.pdf for the paper.
*/
class LatticeIncrementalDeterminizer {
public:
using Label = typename LatticeArc::Label; /* Actualy the same labels appear
in both lattice and compact
lattice, so we don't use the
specific type all the time but
just say 'Label' */
LatticeIncrementalDeterminizer(
const TransitionInformation &trans_model,
const LatticeIncrementalDecoderConfig &config):
trans_model_(trans_model), config_(config) { }
// Resets the lattice determinization data for new utterance
void Init();
// Returns the current determinized lattice.
const CompactLattice &GetDeterminizedLattice() const { return clat_; }
/**
Starts the process of creating a raw lattice chunk. (Search the glossary
for "raw lattice chunk"). This just sets up the initial states and
redeterminized-states in the chunk. Relates to sec. 5.2 in the paper,
specifically the initial-state i and the redeterminized-states.
After calling this, the caller would add the remaining arcs and states
to `olat` and then call AcceptRawLatticeChunk() with the result.
@param [out] olat The lattice to be (partially) created
@param [out] token_label2state This function outputs to here
a map from `token-label` to the state we created for
it in *olat. See glossary for `token-label`.
The keys actually correspond to the .nextstate fields
in the arcs in final_arcs_; values are states in `olat`.
See the last bullet point before Sec. 5.3 in the paper.
*/
void InitializeRawLatticeChunk(
Lattice *olat,
unordered_map<Label, LatticeArc::StateId> *token_label2state);
/**
This function accepts the raw FST (state-level lattice) corresponding to a
single chunk of the lattice, determinizes it and appends it to this->clat_.
Unless this was the
Note: final-probs in `raw_fst` are treated specially: they are used to
guide the pruned determinization, but when you call GetLattice() it will be
-- except for pruning effects-- as if all nonzero final-probs in `raw_fst`
were: One() if final_costs == NULL; else the value present in `final_costs`.
@param [in] raw_fst (Consumed destructively). The input
raw (state-level) lattice. Would correspond to the
FST A in the paper if first_frame == 0, and B
otherwise.
@return returns false if determinization finished earlier than the beam
or the determinized lattice was empty; true otherwise.
NOTE: if this is not the final chunk, you will probably want to call
SetFinalCosts() directly after calling this.
*/
bool AcceptRawLatticeChunk(Lattice *raw_fst);
/*
Sets final-probs in `clat_`. Must only be called if the final chunk
has not been processed. (The final chunk is whenever GetLattice() is
called with finalize == true).
The reason this is a separate function from AcceptRawLatticeChunk() is that
there may be situations where a user wants to get the latice with
final-probs in it, after previously getting it without final-probs; or
vice versa. By final-probs, we mean the Final() probabilities in the
HCLG (decoding graph; this->fst_).
@param [in] token_label2final_cost A map from the token-label
corresponding to Tokens active on the final frame of the
lattice in the object, to the final-cost we want to use for
those tokens. If NULL, it means all Tokens should be treated
as final with probability One(). If non-NULL, and a particular
token-label is not a key of this map, it means that Token
corresponded to a state that was not final in HCLG; and
such tokens will be treated as non-final. However,
if this would result in no states in the lattice being final,
we will treat all Tokens as final with probability One(),
a warning will be printed (this should not happen.)
*/
void SetFinalCosts(const unordered_map<Label, BaseFloat> *token_label2final_cost = NULL);
const CompactLattice &GetLattice() { return clat_; }
// kStateLabelOffset is what we add to state-ids in clat_ to produce labels
// to identify them in the raw lattice chunk
// kTokenLabelOffset is where we start allocating labels corresponding to Tokens
// (these correspond with raw lattice states);
enum { kStateLabelOffset = (int)1e8, kTokenLabelOffset = (int)2e8, kMaxTokenLabel = (int)3e8 };
private:
// [called from AcceptRawLatticeChunk()]
// Gets the final costs from token-final states in the raw lattice (see
// glossary for definition). These final costs will be subtracted after
// determinization; in the normal case they are `temporaries` used to guide
// pruning. NOTE: the index of the array is not the FST state that is final,
// but the label on arcs entering it (these will be `token-labels`). Each
// token-final state will have the same label on all arcs entering it.
//
// `old_final_costs` is assumed to be empty at entry.
void GetRawLatticeFinalCosts(const Lattice &raw_fst,
std::unordered_map<Label, BaseFloat> *old_final_costs);
// Sets up non_final_redet_states_. See documentation for that variable.
void GetNonFinalRedetStates();
/** [called from AcceptRawLatticeChunk()] Processes arcs that leave the
start-state of `chunk_clat` (if this is not the first chunk); does nothing
if this is the first chunk. This includes using the `state-labels` to
work out which states in clat_ these states correspond to, and writing
that mapping to `state_map`.
Also modifies forward_costs_, because it has to do a kind of reweighting
of the clat states that are the values it puts in `state_map`, to take
account of the probabilities on the arcs from the start state of
chunk_clat to the states corresponding to those redeterminized-states
(i.e. the states in clat corresponding to the values it puts in
`*state_map`). It also modifies arcs_in_, mostly because there
are rare cases when we end up `merging` sets of those redeterminized-states,
because the determinization process mapped them to a single state,
and that means we need to reroute the arcs into members of that
set into one single member (which will appear as a value in
`*state_map`).
@param [in] chunk_clat The determinized chunk of lattice we are
processing
@param [out] state_map Mapping from states in chunk_clat to
the state in clat_ they correspond to.
@return Returns true if this is the first chunk.
*/
bool ProcessArcsFromChunkStartState(
const CompactLattice &chunk_clat,
std::unordered_map<CompactLattice::StateId, CompactLattice::StateId> *state_map);
/**
This function, called from AcceptRawLatticeChunk(), transfers arcs from
`chunk_clat` to clat_. For those arcs that have `token-labels` on them,
they don't get written to clat_ but instead are stored in the arcs_ array.
@param [in] chunk_clat The determinized lattice for the chunk
we are processing; this is the source of the arcs
we are moving.
@param [in] is_first_chunk True if this is the first chunk in the
utterance; it's needed because if it is, we
will also transfer arcs from the start state of
chunk_clat.
@param [in] state_map Map from state-ids in chunk_clat to state-ids
in clat_.
@param [in] chunk_state_to_token Map from `token-final states`
(see glossary) in chunk_clat, to the token-label
on arcs entering those states.
@param [in] old_final_costs Map from token-label to the
final-costs that were on the corresponding
token-final states in the undeterminized lattice;
these final-costs need to be removed when
we record the weights in final_arcs_, because
they were just temporary.
*/
void TransferArcsToClat(
const CompactLattice &chunk_clat,
bool is_first_chunk,
const std::unordered_map<CompactLattice::StateId, CompactLattice::StateId> &state_map,
const std::unordered_map<CompactLattice::StateId, Label> &chunk_state_to_token,
const std::unordered_map<Label, BaseFloat> &old_final_costs);
/**
Adds one arc to `clat_`. It's like clat_.AddArc(state, arc), except
it also modifies arcs_in_ and forward_costs_.
*/
void AddArcToClat(CompactLattice::StateId state,
const CompactLatticeArc &arc);
CompactLattice::StateId AddStateToClat();
// Identifies token-final states in `chunk_clat`; see glossary above for
// definition of `token-final`. This function outputs a map from such states
// in chunk_clat, to the `token-label` on arcs entering them. (It is not
// possible that the same state would have multiple arcs entering it with
// different token-labels, or some arcs entering with one token-label and some
// another, or be both initial and have such arcs; this is true due to how we
// construct the raw lattice.)
void IdentifyTokenFinalStates(
const CompactLattice &chunk_clat,
std::unordered_map<CompactLattice::StateId, CompactLatticeArc::Label> *token_map) const;
// trans_model_ is needed by DeterminizeLatticePhonePrunedWrapper() which this
// class calls.
const TransitionInformation &trans_model_;
// config_ is needed by DeterminizeLatticePhonePrunedWrapper() which this
// class calls.
const LatticeIncrementalDecoderConfig &config_;
// Contains the set of redeterminized-states which are not final in the
// canonical appended lattice. Since the final ones don't physically appear
// in clat_, this means the set of redeterminized-states which are physically
// in clat_. In code terms, this means set of .first elements in final_arcs,
// plus whatever other states in clat_ are reachable from such states.
std::unordered_set<CompactLattice::StateId> non_final_redet_states_;
// clat_ is the appended lattice (containing all chunks processed so
// far), except its `final-arcs` (i.e. arcs which in the canonical
// lattice would go to final-states) are not present (they are stored
// separately in final_arcs_) and states which in the canonical lattice
// should have final-arcs leaving them will instead have a final-prob.
CompactLattice clat_;
// arcs_in_ is indexed by (state-id in clat_), and is a list of
// arcs that come into this state, in the form (prev-state,
// arc-index). CAUTION: not all these input-arc records will always
// be valid (some may be out-of-date, and may refer to an out-of-range
// arc or an arc that does not point to this state). But all
// input arcs will always be listed.
std::vector<std::vector<std::pair<CompactLattice::StateId, int32> > > arcs_in_;
// final_arcs_ contains arcs which would appear in the canonical appended
// lattice but for implementation reasons are not physically present in clat_.
// These are arcs to final states in the canonical appended lattice. The
// .first elements are the source states in clat_ (these will all be elements
// of non_final_redet_states_); the .nextstate elements of the arcs does not
// contain a physical state, but contain state-labels allocated by
// AllocateNewStateLabel().
std::vector<CompactLatticeArc> final_arcs_;
// forward_costs_, indexed by the state-id in clat_, stores the alpha
// (forward) costs, i.e. the minimum cost from the start state to each state
// in clat_. This is relevant for pruned determinization. The BaseFloat can
// be thought of as the sum of a Value1() + Value2() in a LatticeWeight.
std::vector<BaseFloat> forward_costs_;
// temporary used in a function, kept here to avoid excessive reallocation.
std::unordered_set<int32> temp_;
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalDeterminizer);
};
/** This is an extention to the "normal" lattice-generating decoder.
See \ref lattices_generation \ref decoders_faster and \ref decoders_simple
for more information.
The main difference is the incremental determinization which will be
discussed in the function GetLattice(). This means that the work of determinizatin
isn't done all at once at the end of the file, but incrementally while decoding.
See the comment at the top of this file for more explanation.
The decoder is templated on the FST type and the token type. The token type
will normally be StdToken, but also may be BackpointerToken which is to support
quick lookup of the current best path (see lattice-faster-online-decoder.h)
The FST you invoke this decoder with is expected to be of type
Fst::Fst<fst::StdArc>, a.k.a. StdFst, or GrammarFst. If you invoke it with
FST == StdFst and it notices that the actual FST type is
fst::VectorFst<fst::StdArc> or fst::ConstFst<fst::StdArc>, the decoder object
will internally cast itself to one that is templated on those more specific
types; this is an optimization for speed.
*/
template <typename FST, typename Token = decoder::StdToken>
class LatticeIncrementalDecoderTpl {
public:
using Arc = typename FST::Arc;
using Label = typename Arc::Label;
using StateId = typename Arc::StateId;
using Weight = typename Arc::Weight;
using ForwardLinkT = decoder::ForwardLink<Token>;
// Instantiate this class once for each thing you have to decode.
// This version of the constructor does not take ownership of
// 'fst'.
LatticeIncrementalDecoderTpl(const FST &fst, const TransitionInformation &trans_model,
const LatticeIncrementalDecoderConfig &config);
// This version of the constructor takes ownership of the fst, and will delete
// it when this object is destroyed.
LatticeIncrementalDecoderTpl(const LatticeIncrementalDecoderConfig &config,
FST *fst, const TransitionInformation &trans_model);
void SetOptions(const LatticeIncrementalDecoderConfig &config) { config_ = config; }
const LatticeIncrementalDecoderConfig &GetOptions() const { return config_; }
~LatticeIncrementalDecoderTpl();
/**
CAUTION: it's unlikely that you will ever want to call this function. In a
scenario where you have the entire file and just want to decode it, there
is no point using this decoder.
An example of how to do decoding together with incremental
determinization. It decodes until there are no more frames left in the
"decodable" object.
In this example, config_.determinize_max_delay, config_.determinize_min_chunk_size
and config_.determinize_max_active are used to determine the time to
call GetLattice().
Users will probably want to use appropriate combinations of
AdvanceDecoding() and GetLattice() to build their application; this just
gives you some idea how.
The function returns true if any kind of traceback is available (not
necessarily from a final state).
*/
bool Decode(DecodableInterface *decodable);
/// says whether a final-state was active on the last frame. If it was not,
/// the lattice (or traceback) will end with states that are not final-states.
bool ReachedFinal() const {
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
}
/**
This decoder has no GetBestPath() function.
If you need that functionality you should probably use lattice-incremental-online-decoder.h,
which makes it very efficient to obtain the best path. */
/**
This GetLattice() function returns the lattice containing
`num_frames_to_decode` frames; this will be all frames decoded so
far, if you let num_frames_to_decode == NumFramesDecoded(),
but it will generally be better to make it a few frames less than
that to avoid the lattice having too many active states at
the end.
@param [in] num_frames_to_include The number of frames that you want
to be included in the lattice. Must be >=
NumFramesInLattice() and <= NumFramesDecoded().
@param [in] use_final_probs True if you want the final-probs
of HCLG to be included in the output lattice. Must not
be set to true if num_frames_to_include !=
NumFramesDecoded(). Must be set to true if you have
previously called FinalizeDecoding().
(If no state was final on frame `num_frames_to_include`, the
final-probs won't be included regardless of
`use_final_probs`; you can test whether this
was the case by calling ReachedFinal().
@return clat The CompactLattice representing what has been decoded
up until `num_frames_to_include` (e.g., LatticeStateTimes()
on this lattice would return `num_frames_to_include`).
See also UpdateLatticeDeterminizaton(). Caution: this const ref
is only valid until the next time you call AdvanceDecoding() or
GetLattice().
CAUTION: the lattice may contain disconnnected states; you should
call Connect() on the output before writing it out.
*/
const CompactLattice &GetLattice(int32 num_frames_to_include,
bool use_final_probs = false);
/*
Returns the number of frames in the currently-determinized part of the
lattice which will be a number in [0, NumFramesDecoded()]. It will
be the largest number that GetLattice() was called with, but note
that GetLattice() may be called from UpdateLatticeDeterminization().
Made available in case the user wants to give that same number to
GetLattice().
*/
int NumFramesInLattice() const { return num_frames_in_lattice_; }
/**
InitDecoding initializes the decoding, and should only be used if you
intend to call AdvanceDecoding(). If you call Decode(), you don't need to
call this. You can also call InitDecoding if you have already decoded an
utterance and want to start with a new utterance.
*/
void InitDecoding();
/**
This will decode until there are no more frames ready in the decodable
object. You can keep calling it each time more frames become available
(this is the normal pattern in a real-time/online decoding scenario).
If max_num_frames is specified, it specifies the maximum number of frames
the function will decode before returning.
*/
void AdvanceDecoding(DecodableInterface *decodable, int32 max_num_frames = -1);
/** FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
more information. It returns the difference between the best (final-cost
plus cost) of any token on the final frame, and the best cost of any token
on the final frame. If it is infinity it means no final-states were
present on the final frame. It will usually be nonnegative. If it not
too positive (e.g. < 5 is my first guess, but this is not tested) you can
take it as a good indication that we reached the final-state with
reasonable likelihood. */
BaseFloat FinalRelativeCost() const;
/** Returns the number of frames decoded so far. */
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
/**
Finalizes the decoding, doing an extra pruning step on the last frame
that uses the final-probs. May be called only once.
*/
void FinalizeDecoding();
protected:
/* Some protected things are needed in LatticeIncrementalOnlineDecoderTpl. */
/** NOTE: for parts the internal implementation that are shared with LatticeFasterDecoer,
we have removed the comments.*/
inline static void DeleteForwardLinks(Token *tok);
struct TokenList {
Token *toks;
bool must_prune_forward_links;
bool must_prune_tokens;
int32 num_toks; /* Note: you can only trust `num_toks` if must_prune_tokens
* == false, because it is only set in
* PruneTokensForFrame(). */
TokenList()
: toks(NULL), must_prune_forward_links(true), must_prune_tokens(true),
num_toks(-1) {}
};
using Elem = typename HashList<StateId, Token *>::Elem;
void PossiblyResizeHash(size_t num_toks);
inline Token *FindOrAddToken(StateId state, int32 frame_plus_one,
BaseFloat tot_cost, Token *backpointer, bool *changed);
void PruneForwardLinks(int32 frame_plus_one, bool *extra_costs_changed,
bool *links_pruned, BaseFloat delta);
void ComputeFinalCosts(unordered_map<Token *, BaseFloat> *final_costs,
BaseFloat *final_relative_cost,
BaseFloat *final_best_cost) const;
void PruneForwardLinksFinal();
void PruneTokensForFrame(int32 frame_plus_one);
void PruneActiveTokens(BaseFloat delta);
BaseFloat GetCutoff(Elem *list_head, size_t *tok_count, BaseFloat *adaptive_beam,
Elem **best_elem);
BaseFloat ProcessEmitting(DecodableInterface *decodable);
void ProcessNonemitting(BaseFloat cost_cutoff);
HashList<StateId, Token *> toks_;
std::vector<TokenList> active_toks_; // indexed by frame.
std::vector<StateId> queue_; // temp variable used in ProcessNonemitting,
std::vector<BaseFloat> tmp_array_; // used in GetCutoff.
const FST *fst_;
bool delete_fst_;
std::vector<BaseFloat> cost_offsets_;
int32 num_toks_;
bool warned_;
bool decoding_finalized_;
unordered_map<Token *, BaseFloat> final_costs_;
BaseFloat final_relative_cost_;
BaseFloat final_best_cost_;
/***********************
Variables below this point relate to the incremental
determinization.
*********************/
LatticeIncrementalDecoderConfig config_;
/** Much of the the incremental determinization algorithm is encapsulated in
the determinize_ object. */
LatticeIncrementalDeterminizer determinizer_;
/* Just a temporary used in a function; stored here to avoid reallocation. */
unordered_map<Token*, StateId> temp_token_map_;
/** num_frames_in_lattice_ is the highest `num_frames_to_include_` argument
for any prior call to GetLattice(). */
int32 num_frames_in_lattice_;
// A map from Token to its token_label. Will contain an entry for
// each Token in active_toks_[num_frames_in_lattice_].
unordered_map<Token*, Label> token2label_map_;
// A temporary used in a function, kept here to avoid reallocation.
unordered_map<Token*, Label> token2label_map_temp_;
// we allocate a unique id for each Token
Label next_token_label_;
inline Label AllocateNewTokenLabel() { return next_token_label_++; }
// There are various cleanup tasks... the the toks_ structure contains
// singly linked lists of Token pointers, where Elem is the list type.
// It also indexes them in a hash, indexed by state (this hash is only
// maintained for the most recent frame). toks_.Clear()
// deletes them from the hash and returns the list of Elems. The
// function DeleteElems calls toks_.Delete(elem) for each elem in
// the list, which returns ownership of the Elem to the toks_ structure
// for reuse, but does not delete the Token pointer. The Token pointers
// are reference-counted and are ultimately deleted in PruneTokensForFrame,
// but are also linked together on each frame by their own linked-list,
// using the "next" pointer. We delete them manually.
void DeleteElems(Elem *list);
void ClearActiveTokens();
// Returns the number of active tokens on frame `frame`. Can be used as part
// of a heuristic to decide which frame to determinize until, if you are not
// at the end of an utterance.
int32 GetNumToksForFrame(int32 frame);
/**
UpdateLatticeDeterminization() ensures the work of determinization is kept
up to date so that when you do need the lattice you can get it fast. It
uses the configuration values `determinize_max_delay`, `determinize_min_chunk_size`
and `determinize_max_active`, to decide whether and when to call
GetLattice(). You can safely call this as often as you want (e.g. after
each time you call AdvanceDecoding(); it won't do subtantially more work if
it is called frequently.
*/
void UpdateLatticeDeterminization();
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalDecoderTpl);
};
typedef LatticeIncrementalDecoderTpl<fst::StdFst, decoder::StdToken>
LatticeIncrementalDecoder;
} // end namespace kaldi.
#endif
@@ -0,0 +1,158 @@
// decoder/lattice-incremental-online-decoder.cc
// Copyright 2019 Zhehuai 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.
// see note at the top of lattice-faster-decoder.cc, about how to maintain this
// file in sync with lattice-faster-decoder.cc
#include "decoder/lattice-incremental-decoder.h"
#include "decoder/lattice-incremental-online-decoder.h"
#include "lat/lattice-functions.h"
#include "base/timer.h"
namespace kaldi {
// Outputs an FST corresponding to the single best path through the lattice.
template <typename FST>
bool LatticeIncrementalOnlineDecoderTpl<FST>::GetBestPath(Lattice *olat,
bool use_final_probs) const {
olat->DeleteStates();
BaseFloat final_graph_cost;
BestPathIterator iter = BestPathEnd(use_final_probs, &final_graph_cost);
if (iter.Done())
return false; // would have printed warning.
StateId state = olat->AddState();
olat->SetFinal(state, LatticeWeight(final_graph_cost, 0.0));
while (!iter.Done()) {
LatticeArc arc;
iter = TraceBackBestPath(iter, &arc);
arc.nextstate = state;
StateId new_state = olat->AddState();
olat->AddArc(new_state, arc);
state = new_state;
}
olat->SetStart(state);
return true;
}
template <typename FST>
typename LatticeIncrementalOnlineDecoderTpl<FST>::BestPathIterator LatticeIncrementalOnlineDecoderTpl<FST>::BestPathEnd(
bool use_final_probs,
BaseFloat *final_cost_out) const {
if (this->decoding_finalized_ && !use_final_probs)
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
<< "BestPathEnd() with use_final_probs == false";
KALDI_ASSERT(this->NumFramesDecoded() > 0 &&
"You cannot call BestPathEnd if no frames were decoded.");
unordered_map<Token*, BaseFloat> final_costs_local;
const unordered_map<Token*, BaseFloat> &final_costs =
(this->decoding_finalized_ ? this->final_costs_ :final_costs_local);
if (!this->decoding_finalized_ && use_final_probs)
this->ComputeFinalCosts(&final_costs_local, NULL, NULL);
// Singly linked list of tokens on last frame (access list through "next"
// pointer).
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
BaseFloat best_final_cost = 0;
Token *best_tok = NULL;
for (Token *tok = this->active_toks_.back().toks;
tok != NULL; tok = tok->next) {
BaseFloat cost = tok->tot_cost, final_cost = 0.0;
if (use_final_probs && !final_costs.empty()) {
// if we are instructed to use final-probs, and any final tokens were
// active on final frame, include the final-prob in the cost of the token.
typename unordered_map<Token*, BaseFloat>::const_iterator
iter = final_costs.find(tok);
if (iter != final_costs.end()) {
final_cost = iter->second;
cost += final_cost;
} else {
cost = std::numeric_limits<BaseFloat>::infinity();
}
}
if (cost < best_cost) {
best_cost = cost;
best_tok = tok;
best_final_cost = final_cost;
}
}
if (best_tok == NULL) { // this should not happen, and is likely a code error or
// caused by infinities in likelihoods, but I'm not making
// it a fatal error for now.
KALDI_WARN << "No final token found.";
}
if (final_cost_out != NULL)
*final_cost_out = best_final_cost;
return BestPathIterator(best_tok, this->NumFramesDecoded() - 1);
}
template <typename FST>
typename LatticeIncrementalOnlineDecoderTpl<FST>::BestPathIterator LatticeIncrementalOnlineDecoderTpl<FST>::TraceBackBestPath(
BestPathIterator iter, LatticeArc *oarc) const {
KALDI_ASSERT(!iter.Done() && oarc != NULL);
Token *tok = static_cast<Token*>(iter.tok);
int32 cur_t = iter.frame, step_t = 0;
if (tok->backpointer != NULL) {
// retrieve the correct forward link(with the best link cost)
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
ForwardLinkT *link;
for (link = tok->backpointer->links;
link != NULL; link = link->next) {
if (link->next_tok == tok) { // this is the a to "tok"
BaseFloat graph_cost = link->graph_cost,
acoustic_cost = link->acoustic_cost;
BaseFloat cost = graph_cost + acoustic_cost;
if (cost < best_cost) {
oarc->ilabel = link->ilabel;
oarc->olabel = link->olabel;
if (link->ilabel != 0) {
KALDI_ASSERT(static_cast<size_t>(cur_t) < this->cost_offsets_.size());
acoustic_cost -= this->cost_offsets_[cur_t];
step_t = -1;
} else {
step_t = 0;
}
oarc->weight = LatticeWeight(graph_cost, acoustic_cost);
best_cost = cost;
}
}
}
if (link == NULL &&
best_cost == std::numeric_limits<BaseFloat>::infinity()) { // Did not find correct link.
KALDI_ERR << "Error tracing best-path back (likely "
<< "bug in token-pruning algorithm)";
}
} else {
oarc->ilabel = 0;
oarc->olabel = 0;
oarc->weight = LatticeWeight::One(); // zero costs.
}
return BestPathIterator(tok->backpointer, cur_t + step_t);
}
// Instantiate the template for the FST types that we'll need.
template class LatticeIncrementalOnlineDecoderTpl<fst::Fst<fst::StdArc> >;
template class LatticeIncrementalOnlineDecoderTpl<fst::VectorFst<fst::StdArc> >;
template class LatticeIncrementalOnlineDecoderTpl<fst::ConstFst<fst::StdArc> >;
template class LatticeIncrementalOnlineDecoderTpl<fst::ConstGrammarFst >;
template class LatticeIncrementalOnlineDecoderTpl<fst::VectorGrammarFst >;
} // end namespace kaldi.
@@ -0,0 +1,132 @@
// decoder/lattice-incremental-online-decoder.h
// Copyright 2019 Zhehuai 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.
// see note at the top of lattice-faster-decoder.h, about how to maintain this
// file in sync with lattice-faster-decoder.h
#ifndef KALDI_DECODER_LATTICE_INCREMENTAL_ONLINE_DECODER_H_
#define KALDI_DECODER_LATTICE_INCREMENTAL_ONLINE_DECODER_H_
#include "util/stl-utils.h"
#include "util/hash-list.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "fstext/fstext-lib.h"
#include "lat/determinize-lattice-pruned.h"
#include "lat/kaldi-lattice.h"
#include "decoder/lattice-incremental-decoder.h"
namespace kaldi {
/** LatticeIncrementalOnlineDecoderTpl is as LatticeIncrementalDecoderTpl but also
supports an efficient way to get the best path (see the function
BestPathEnd()), which is useful in endpointing and in situations where you
might want to frequently access the best path.
This is only templated on the FST type, since the Token type is required to
be BackpointerToken. Actually it only makes sense to instantiate
LatticeIncrementalDecoderTpl with Token == BackpointerToken if you do so indirectly via
this child class.
*/
template <typename FST>
class LatticeIncrementalOnlineDecoderTpl:
public LatticeIncrementalDecoderTpl<FST, decoder::BackpointerToken> {
public:
using Arc = typename FST::Arc;
using Label = typename Arc::Label;
using StateId = typename Arc::StateId;
using Weight = typename Arc::Weight;
using Token = decoder::BackpointerToken;
using ForwardLinkT = decoder::ForwardLink<Token>;
// Instantiate this class once for each thing you have to decode.
// This version of the constructor does not take ownership of
// 'fst'.
LatticeIncrementalOnlineDecoderTpl(const FST &fst,
const TransitionInformation &trans_model,
const LatticeIncrementalDecoderConfig &config):
LatticeIncrementalDecoderTpl<FST, Token>(fst, trans_model, config) { }
// This version of the initializer takes ownership of 'fst', and will delete
// it when this object is destroyed.
LatticeIncrementalOnlineDecoderTpl(const LatticeIncrementalDecoderConfig &config,
FST *fst,
const TransitionInformation &trans_model):
LatticeIncrementalDecoderTpl<FST, Token>(config, fst, trans_model) { }
struct BestPathIterator {
void *tok;
int32 frame;
// note, "frame" is the frame-index of the frame you'll get the
// transition-id for next time, if you call TraceBackBestPath on this
// iterator (assuming it's not an epsilon transition). Note that this
// is one less than you might reasonably expect, e.g. it's -1 for
// the nonemitting transitions before the first frame.
BestPathIterator(void *t, int32 f): tok(t), frame(f) { }
bool Done() { return tok == NULL; }
};
/// Outputs an FST corresponding to the single best path through the lattice.
/// This is quite efficient because it doesn't get the entire raw lattice and find
/// the best path through it; instead, it uses the BestPathEnd and BestPathIterator
/// so it basically traces it back through the lattice.
/// Returns true if result is nonempty (using the return status is deprecated,
/// it will become void). If "use_final_probs" is true AND we reached the
/// final-state of the graph then it will include those as final-probs, else
/// it will treat all final-probs as one.
bool GetBestPath(Lattice *ofst,
bool use_final_probs = true) const;
/// This function returns an iterator that can be used to trace back
/// the best path. If use_final_probs == true and at least one final state
/// survived till the end, it will use the final-probs in working out the best
/// final Token, and will output the final cost to *final_cost (if non-NULL),
/// else it will use only the forward likelihood, and will put zero in
/// *final_cost (if non-NULL).
/// Requires that NumFramesDecoded() > 0.
BestPathIterator BestPathEnd(bool use_final_probs,
BaseFloat *final_cost = NULL) const;
/// This function can be used in conjunction with BestPathEnd() to trace back
/// the best path one link at a time (e.g. this can be useful in endpoint
/// detection). By "link" we mean a link in the graph; not all links cross
/// frame boundaries, but each time you see a nonzero ilabel you can interpret
/// that as a frame. The return value is the updated iterator. It outputs
/// the ilabel and olabel, and the (graph and acoustic) weight to the "arc" pointer,
/// while leaving its "nextstate" variable unchanged.
BestPathIterator TraceBackBestPath(
BestPathIterator iter, LatticeArc *arc) const;
KALDI_DISALLOW_COPY_AND_ASSIGN(LatticeIncrementalOnlineDecoderTpl);
};
typedef LatticeIncrementalOnlineDecoderTpl<fst::StdFst> LatticeIncrementalOnlineDecoder;
} // end namespace kaldi.
#endif
@@ -0,0 +1,666 @@
// decoder/lattice-simple-decoder.cc
// Copyright 2009-2012 Microsoft Corporation
// 2013-2014 Johns Hopkins University (Author: Daniel Povey)
// 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 "decoder/lattice-simple-decoder.h"
namespace kaldi {
void LatticeSimpleDecoder::InitDecoding() {
// clean up from last time:
cur_toks_.clear();
prev_toks_.clear();
ClearActiveTokens();
warned_ = false;
decoding_finalized_ = false;
final_costs_.clear();
num_toks_ = 0;
StateId start_state = fst_.Start();
KALDI_ASSERT(start_state != fst::kNoStateId);
active_toks_.resize(1);
Token *start_tok = new Token(0.0, 0.0, NULL, NULL);
active_toks_[0].toks = start_tok;
cur_toks_[start_state] = start_tok;
num_toks_++;
ProcessNonemitting();
}
bool LatticeSimpleDecoder::Decode(DecodableInterface *decodable) {
InitDecoding();
while (!decodable->IsLastFrame(NumFramesDecoded() - 1)) {
if (NumFramesDecoded() % config_.prune_interval == 0)
PruneActiveTokens(config_.lattice_beam * config_.prune_scale);
ProcessEmitting(decodable);
// Important to call PruneCurrentTokens before ProcessNonemitting, or we
// would get dangling forward pointers. Anyway, ProcessNonemitting uses the
// beam.
PruneCurrentTokens(config_.beam, &cur_toks_);
ProcessNonemitting();
}
FinalizeDecoding();
// Returns true if we have any kind of traceback available (not necessarily
// to the end state; query ReachedFinal() for that).
return !final_costs_.empty();
}
// Outputs an FST corresponding to the single best path
// through the lattice.
bool LatticeSimpleDecoder::GetBestPath(Lattice *ofst,
bool use_final_probs) const {
fst::VectorFst<LatticeArc> fst;
GetRawLattice(&fst, use_final_probs);
ShortestPath(fst, ofst);
return (ofst->NumStates() > 0);
}
// Outputs an FST corresponding to the raw, state-level
// tracebacks.
bool LatticeSimpleDecoder::GetRawLattice(Lattice *ofst,
bool use_final_probs) const {
typedef LatticeArc Arc;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
typedef Arc::Label Label;
// Note: you can't use the old interface (Decode()) if you want to
// get the lattice with use_final_probs = false. You'd have to do
// InitDecoding() and then AdvanceDecoding().
if (decoding_finalized_ && !use_final_probs)
KALDI_ERR << "You cannot call FinalizeDecoding() and then call "
<< "GetRawLattice() with use_final_probs == false";
unordered_map<Token*, BaseFloat> final_costs_local;
const unordered_map<Token*, BaseFloat> &final_costs =
(decoding_finalized_ ? final_costs_ : final_costs_local);
if (!decoding_finalized_ && use_final_probs)
ComputeFinalCosts(&final_costs_local, NULL, NULL);
ofst->DeleteStates();
int32 num_frames = NumFramesDecoded();
KALDI_ASSERT(num_frames > 0);
const int32 bucket_count = num_toks_/2 + 3;
unordered_map<Token*, StateId> tok_map(bucket_count);
// First create all states.
for (int32 f = 0; f <= num_frames; f++) {
if (active_toks_[f].toks == NULL) {
KALDI_WARN << "GetRawLattice: no tokens active on frame " << f
<< ": not producing lattice.\n";
return false;
}
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next)
tok_map[tok] = ofst->AddState();
// The next statement sets the start state of the output FST.
// Because we always add new states to the head of the list
// active_toks_[f].toks, and the start state was the first one
// added, it will be the last one added to ofst.
if (f == 0 && ofst->NumStates() > 0)
ofst->SetStart(ofst->NumStates()-1);
}
StateId cur_state = 0; // we rely on the fact that we numbered these
// consecutively (AddState() returns the numbers in order..)
for (int32 f = 0; f <= num_frames; f++) {
for (Token *tok = active_toks_[f].toks; tok != NULL; tok = tok->next,
cur_state++) {
for (ForwardLink *l = tok->links;
l != NULL;
l = l->next) {
unordered_map<Token*, StateId>::const_iterator iter =
tok_map.find(l->next_tok);
StateId nextstate = iter->second;
KALDI_ASSERT(iter != tok_map.end());
Arc arc(l->ilabel, l->olabel,
Weight(l->graph_cost, l->acoustic_cost),
nextstate);
ofst->AddArc(cur_state, arc);
}
if (f == num_frames) {
if (use_final_probs && !final_costs.empty()) {
unordered_map<Token*, BaseFloat>::const_iterator iter =
final_costs.find(tok);
if (iter != final_costs.end())
ofst->SetFinal(cur_state, LatticeWeight(iter->second, 0));
} else {
ofst->SetFinal(cur_state, LatticeWeight::One());
}
}
}
}
KALDI_ASSERT(cur_state == ofst->NumStates());
return (cur_state != 0);
}
// This function is now deprecated, since now we do determinization from outside
// the LatticeSimpleDecoder class.
// Outputs an FST corresponding to the lattice-determinized
// lattice (one path per word sequence).
bool LatticeSimpleDecoder::GetLattice(
CompactLattice *ofst,
bool use_final_probs) const {
Lattice raw_fst;
GetRawLattice(&raw_fst, use_final_probs);
Invert(&raw_fst); // make it so word labels are on the input.
if (!TopSort(&raw_fst)) // topological sort makes lattice-determinization more efficient
KALDI_WARN << "Topological sorting of state-level lattice failed "
"(probably your lexicon has empty words or your LM has epsilon cycles; this "
" is a bad idea.)";
// (in phase where we get backward-costs).
fst::ILabelCompare<LatticeArc> ilabel_comp;
ArcSort(&raw_fst, ilabel_comp); // sort on ilabel; makes
// lattice-determinization more efficient.
fst::DeterminizeLatticePrunedOptions lat_opts;
lat_opts.max_mem = config_.det_opts.max_mem;
DeterminizeLatticePruned(raw_fst, config_.lattice_beam, ofst, lat_opts);
raw_fst.DeleteStates(); // Free memory-- raw_fst no longer needed.
Connect(ofst); // Remove unreachable states... there might be
// a small number of these, in some cases.
// Note: if something went wrong and the raw lattice was empty,
// we should still get to this point in the code without warnings or failures.
return (ofst->NumStates() != 0);
}
// FindOrAddToken either locates a token in cur_toks_, or if necessary inserts a new,
// empty token (i.e. with no forward links) for the current frame. [note: it's
// inserted if necessary into cur_toks_ and also into the singly linked list
// of tokens active on this frame (whose head is at active_toks_[frame]).
//
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
// if the token was newly created or the cost changed.
inline LatticeSimpleDecoder::Token *LatticeSimpleDecoder::FindOrAddToken(
StateId state, int32 frame, BaseFloat tot_cost,
bool emitting, bool *changed) {
KALDI_ASSERT(frame < active_toks_.size());
Token *&toks = active_toks_[frame].toks;
unordered_map<StateId, Token*>::iterator find_iter = cur_toks_.find(state);
if (find_iter == cur_toks_.end()) { // no such token presently.
// Create one.
const BaseFloat extra_cost = 0.0;
// tokens on the currently final frame have zero extra_cost
// as any of them could end up
// on the winning path.
Token *new_tok = new Token (tot_cost, extra_cost, NULL, toks);
toks = new_tok;
num_toks_++;
cur_toks_[state] = new_tok;
if (changed) *changed = true;
return new_tok;
} else {
Token *tok = find_iter->second; // There is an existing Token for this state.
if (tok->tot_cost > tot_cost) {
tok->tot_cost = tot_cost;
if (changed) *changed = true;
} else {
if (changed) *changed = false;
}
return tok;
}
}
// delta is the amount by which the extra_costs must
// change before it sets "extra_costs_changed" to true. If delta is larger,
// we'll tend to go back less far toward the beginning of the file.
void LatticeSimpleDecoder::PruneForwardLinks(
int32 frame, bool *extra_costs_changed,
bool *links_pruned, BaseFloat delta) {
// We have to iterate until there is no more change, because the links
// are not guaranteed to be in topological order.
*extra_costs_changed = false;
*links_pruned = false;
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
if (active_toks_[frame].toks == NULL ) { // empty list; this should
// not happen.
if (!warned_) {
KALDI_WARN << "No tokens alive [doing pruning].. warning first "
"time only for each utterance\n";
warned_ = true;
}
}
bool changed = true;
while (changed) {
changed = false;
for (Token *tok = active_toks_[frame].toks; tok != NULL; tok = tok->next) {
ForwardLink *link, *prev_link = NULL;
// will recompute tok_extra_cost.
BaseFloat tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
for (link = tok->links; link != NULL; ) {
// See if we need to excise this link...
Token *next_tok = link->next_tok;
BaseFloat link_extra_cost = next_tok->extra_cost +
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
- next_tok->tot_cost);
KALDI_ASSERT(link_extra_cost == link_extra_cost); // check for NaN
if (link_extra_cost > config_.lattice_beam) { // excise link
ForwardLink *next_link = link->next;
if (prev_link != NULL) prev_link->next = next_link;
else tok->links = next_link;
delete link;
link = next_link; // advance link but leave prev_link the same.
*links_pruned = true;
} else { // keep the link and update the tok_extra_cost if needed.
if (link_extra_cost < 0.0) { // this is just a precaution.
if (link_extra_cost < -0.01)
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
link_extra_cost = 0.0;
}
if (link_extra_cost < tok_extra_cost)
tok_extra_cost = link_extra_cost;
prev_link = link;
link = link->next;
}
}
if (fabs(tok_extra_cost - tok->extra_cost) > delta)
changed = true;
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
}
if (changed) *extra_costs_changed = true;
// Note: it's theoretically possible that aggressive compiler
// optimizations could cause an infinite loop here for small delta and
// high-dynamic-range scores.
}
}
void LatticeSimpleDecoder::ComputeFinalCosts(
unordered_map<Token*, BaseFloat> *final_costs,
BaseFloat *final_relative_cost,
BaseFloat *final_best_cost) const {
KALDI_ASSERT(!decoding_finalized_);
if (final_costs != NULL)
final_costs->clear();
BaseFloat infinity = std::numeric_limits<BaseFloat>::infinity();
BaseFloat best_cost = infinity,
best_cost_with_final = infinity;
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
iter != cur_toks_.end(); ++iter) {
StateId state = iter->first;
Token *tok = iter->second;
BaseFloat final_cost = fst_.Final(state).Value();
BaseFloat cost = tok->tot_cost,
cost_with_final = cost + final_cost;
best_cost = std::min(cost, best_cost);
best_cost_with_final = std::min(cost_with_final, best_cost_with_final);
if (final_costs != NULL && final_cost != infinity)
(*final_costs)[tok] = final_cost;
}
if (final_relative_cost != NULL) {
if (best_cost == infinity && best_cost_with_final == infinity) {
// Likely this will only happen if there are no tokens surviving.
// This seems the least bad way to handle it.
*final_relative_cost = infinity;
} else {
*final_relative_cost = best_cost_with_final - best_cost;
}
}
if (final_best_cost != NULL) {
if (best_cost_with_final != infinity) { // final-state exists.
*final_best_cost = best_cost_with_final;
} else { // no final-state exists.
*final_best_cost = best_cost;
}
}
}
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
// on the final frame. If there are final tokens active, it uses the final-probs
// for pruning, otherwise it treats all tokens as final.
void LatticeSimpleDecoder::PruneForwardLinksFinal() {
KALDI_ASSERT(!active_toks_.empty());
int32 frame_plus_one = active_toks_.size() - 1;
if (active_toks_[frame_plus_one].toks == NULL) // empty list; should not happen.
KALDI_WARN << "No tokens alive at end of file\n";
typedef unordered_map<Token*, BaseFloat>::const_iterator IterType;
ComputeFinalCosts(&final_costs_, &final_relative_cost_, &final_best_cost_);
decoding_finalized_ = true;
// We're about to delete some of the tokens active on the final frame, so we
// clear cur_toks_ because otherwise it would then contain dangling pointers.
cur_toks_.clear();
// Now go through tokens on this frame, pruning forward links... may have to
// iterate a few times until there is no more change, because the list is not
// in topological order. This is a modified version of the code in
// PruneForwardLinks, but here we also take account of the final-probs.
bool changed = true;
BaseFloat delta = 1.0e-05;
while (changed) {
changed = false;
for (Token *tok = active_toks_[frame_plus_one].toks;
tok != NULL; tok = tok->next) {
ForwardLink *link, *prev_link=NULL;
// will recompute tok_extra_cost. It has a term in it that corresponds
// to the "final-prob", so instead of initializing tok_extra_cost to infinity
// below we set it to the difference between the (score+final_prob) of this token,
// and the best such (score+final_prob).
BaseFloat final_cost;
if (final_costs_.empty()) {
final_cost = 0.0;
} else {
IterType iter = final_costs_.find(tok);
if (iter != final_costs_.end())
final_cost = iter->second;
else
final_cost = std::numeric_limits<BaseFloat>::infinity();
}
BaseFloat tok_extra_cost = tok->tot_cost + final_cost - final_best_cost_;
// tok_extra_cost will be a "min" over either directly being final, or
// being indirectly final through other links, and the loop below may
// decrease its value:
for (link = tok->links; link != NULL; ) {
// See if we need to excise this link...
Token *next_tok = link->next_tok;
BaseFloat link_extra_cost = next_tok->extra_cost +
((tok->tot_cost + link->acoustic_cost + link->graph_cost)
- next_tok->tot_cost);
if (link_extra_cost > config_.lattice_beam) { // excise link
ForwardLink *next_link = link->next;
if (prev_link != NULL) prev_link->next = next_link;
else tok->links = next_link;
delete link;
link = next_link; // advance link but leave prev_link the same.
} else { // keep the link and update the tok_extra_cost if needed.
if (link_extra_cost < 0.0) { // this is just a precaution.
if (link_extra_cost < -0.01)
KALDI_WARN << "Negative extra_cost: " << link_extra_cost;
link_extra_cost = 0.0;
}
if (link_extra_cost < tok_extra_cost)
tok_extra_cost = link_extra_cost;
prev_link = link;
link = link->next;
}
}
// prune away tokens worse than lattice_beam above best path. This step
// was not necessary in the non-final case because then, this case
// showed up as having no forward links. Here, the tok_extra_cost has
// an extra component relating to the final-prob.
if (tok_extra_cost > config_.lattice_beam)
tok_extra_cost = std::numeric_limits<BaseFloat>::infinity();
// to be pruned in PruneTokensForFrame
if (!ApproxEqual(tok->extra_cost, tok_extra_cost, delta))
changed = true;
tok->extra_cost = tok_extra_cost; // will be +infinity or <= lattice_beam_.
}
} // while changed
}
BaseFloat LatticeSimpleDecoder::FinalRelativeCost() const {
if (!decoding_finalized_) {
BaseFloat relative_cost;
ComputeFinalCosts(NULL, &relative_cost, NULL);
return relative_cost;
} else {
// we're not allowed to call that function if FinalizeDecoding() has
// been called; return a cached value.
return final_relative_cost_;
}
}
// Prune away any tokens on this frame that have no forward links. [we don't do
// this in PruneForwardLinks because it would give us a problem with dangling
// pointers].
void LatticeSimpleDecoder::PruneTokensForFrame(int32 frame) {
KALDI_ASSERT(frame >= 0 && frame < active_toks_.size());
Token *&toks = active_toks_[frame].toks;
if (toks == NULL)
KALDI_WARN << "No tokens alive [doing pruning]";
Token *tok, *next_tok, *prev_tok = NULL;
for (tok = toks; tok != NULL; tok = next_tok) {
next_tok = tok->next;
if (tok->extra_cost == std::numeric_limits<BaseFloat>::infinity()) {
// Next token is unreachable from end of graph; excise tok from list
// and delete tok.
if (prev_tok != NULL) prev_tok->next = tok->next;
else toks = tok->next;
delete tok;
num_toks_--;
} else {
prev_tok = tok;
}
}
}
// Go backwards through still-alive tokens, pruning them, starting not from
// the current frame (where we want to keep all tokens) but from the frame before
// that. We go backwards through the frames and stop when we reach a point
// where the delta-costs are not changing (and the delta controls when we consider
// a cost to have "not changed").
void LatticeSimpleDecoder::PruneActiveTokens(BaseFloat delta) {
int32 cur_frame_plus_one = NumFramesDecoded();
int32 num_toks_begin = num_toks_;
// The index "f" below represents a "frame plus one", i.e. you'd have to subtract
// one to get the corresponding index for the decodable object.
for (int32 f = cur_frame_plus_one - 1; f >= 0; f--) {
// Reason why we need to prune forward links in this situation:
// (1) we have never pruned them
// (2) we never pruned the forward links on the next frame, which
//
if (active_toks_[f].must_prune_forward_links) {
bool extra_costs_changed = false, links_pruned = false;
PruneForwardLinks(f, &extra_costs_changed, &links_pruned, delta);
if (extra_costs_changed && f > 0)
active_toks_[f-1].must_prune_forward_links = true;
if (links_pruned)
active_toks_[f].must_prune_tokens = true;
active_toks_[f].must_prune_forward_links = false;
}
if (f+1 < cur_frame_plus_one &&
active_toks_[f+1].must_prune_tokens) {
PruneTokensForFrame(f+1);
active_toks_[f+1].must_prune_tokens = false;
}
}
KALDI_VLOG(3) << "PruneActiveTokens: pruned tokens from " << num_toks_begin
<< " to " << num_toks_;
}
// FinalizeDecoding() is a version of PruneActiveTokens that we call
// (optionally) on the final frame. Takes into account the final-prob of
// tokens. This function used to be called PruneActiveTokensFinal().
void LatticeSimpleDecoder::FinalizeDecoding() {
int32 final_frame_plus_one = NumFramesDecoded();
int32 num_toks_begin = num_toks_;
PruneForwardLinksFinal();
for (int32 f = final_frame_plus_one - 1; f >= 0; f--) {
bool b1, b2; // values not used.
BaseFloat dontcare = 0.0;
PruneForwardLinks(f, &b1, &b2, dontcare);
PruneTokensForFrame(f + 1);
}
PruneTokensForFrame(0);
KALDI_VLOG(3) << "pruned tokens from " << num_toks_begin
<< " to " << num_toks_;
}
void LatticeSimpleDecoder::ProcessEmitting(DecodableInterface *decodable) {
int32 frame = active_toks_.size() - 1; // frame is the frame-index
// (zero-based) used to get likelihoods
// from the decodable object.
active_toks_.resize(active_toks_.size() + 1);
prev_toks_.clear();
cur_toks_.swap(prev_toks_);
// Processes emitting arcs for one frame. Propagates from
// prev_toks_ to cur_toks_.
BaseFloat cutoff = std::numeric_limits<BaseFloat>::infinity();
for (unordered_map<StateId, Token*>::iterator iter = prev_toks_.begin();
iter != prev_toks_.end();
++iter) {
StateId state = iter->first;
Token *tok = iter->second;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel != 0) { // propagate..
BaseFloat ac_cost = -decodable->LogLikelihood(frame, arc.ilabel),
graph_cost = arc.weight.Value(),
cur_cost = tok->tot_cost,
tot_cost = cur_cost + ac_cost + graph_cost;
if (tot_cost >= cutoff) continue;
else if (tot_cost + config_.beam < cutoff)
cutoff = tot_cost + config_.beam;
// AddToken adds the next_tok to cur_toks_ (if not already present).
Token *next_tok = FindOrAddToken(arc.nextstate, frame + 1, tot_cost,
true, NULL);
// Add ForwardLink from tok to next_tok (put on head of list tok->links)
tok->links = new ForwardLink(next_tok, arc.ilabel, arc.olabel,
graph_cost, ac_cost, tok->links);
}
}
}
}
void LatticeSimpleDecoder::ProcessNonemitting() {
KALDI_ASSERT(!active_toks_.empty());
int32 frame = static_cast<int32>(active_toks_.size()) - 2;
// Note: "frame" is the time-index we just processed, or -1 if
// we are processing the nonemitting transitions before the
// first frame (called from InitDecoding()).
// Processes nonemitting arcs for one frame. Propagates within
// cur_toks_. Note-- this queue structure is is not very optimal as
// it may cause us to process states unnecessarily (e.g. more than once),
// but in the baseline code, turning this vector into a set to fix this
// problem did not improve overall speed.
std::vector<StateId> queue;
BaseFloat best_cost = std::numeric_limits<BaseFloat>::infinity();
for (unordered_map<StateId, Token*>::iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter) {
StateId state = iter->first;
if (fst_.NumInputEpsilons(state) != 0)
queue.push_back(state);
best_cost = std::min(best_cost, iter->second->tot_cost);
}
if (queue.empty()) {
if (!warned_) {
KALDI_ERR << "Error in ProcessEmitting: no surviving tokens: frame is "
<< frame;
warned_ = true;
}
}
BaseFloat cutoff = best_cost + config_.beam;
while (!queue.empty()) {
StateId state = queue.back();
queue.pop_back();
Token *tok = cur_toks_[state];
// If "tok" has any existing forward links, delete them,
// because we're about to regenerate them. This is a kind
// of non-optimality (remember, this is the simple decoder),
// but since most states are emitting it's not a huge issue.
tok->DeleteForwardLinks();
tok->links = NULL;
for (fst::ArcIterator<fst::Fst<Arc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const Arc &arc = aiter.Value();
if (arc.ilabel == 0) { // propagate nonemitting only...
BaseFloat graph_cost = arc.weight.Value(),
cur_cost = tok->tot_cost,
tot_cost = cur_cost + graph_cost;
if (tot_cost < cutoff) {
bool changed;
Token *new_tok = FindOrAddToken(arc.nextstate, frame + 1, tot_cost,
false, &changed);
tok->links = new ForwardLink(new_tok, 0, arc.olabel,
graph_cost, 0, tok->links);
// "changed" tells us whether the new token has a different
// cost from before, or is new [if so, add into queue].
if (changed && fst_.NumInputEpsilons(arc.nextstate) != 0)
queue.push_back(arc.nextstate);
}
}
}
}
}
void LatticeSimpleDecoder::ClearActiveTokens() { // a cleanup routine, at utt end/begin
for (size_t i = 0; i < active_toks_.size(); i++) {
// Delete all tokens alive on this frame, and any forward
// links they may have.
for (Token *tok = active_toks_[i].toks; tok != NULL; ) {
tok->DeleteForwardLinks();
Token *next_tok = tok->next;
delete tok;
num_toks_--;
tok = next_tok;
}
}
active_toks_.clear();
KALDI_ASSERT(num_toks_ == 0);
}
// PruneCurrentTokens deletes the tokens from the "toks" map, but not
// from the active_toks_ list, which could cause dangling forward pointers
// (will delete it during regular pruning operation).
void LatticeSimpleDecoder::PruneCurrentTokens(BaseFloat beam, unordered_map<StateId, Token*> *toks) {
if (toks->empty()) {
KALDI_VLOG(2) << "No tokens to prune.\n";
return;
}
BaseFloat best_cost = 1.0e+10; // positive == high cost == bad.
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
iter != toks->end(); ++iter) {
best_cost =
std::min(best_cost,
static_cast<BaseFloat>(iter->second->tot_cost));
}
std::vector<StateId> retained;
BaseFloat cutoff = best_cost + beam;
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
iter != toks->end(); ++iter) {
if (iter->second->tot_cost < cutoff)
retained.push_back(iter->first);
}
unordered_map<StateId, Token*> tmp;
for (size_t i = 0; i < retained.size(); i++) {
tmp[retained[i]] = (*toks)[retained[i]];
}
KALDI_VLOG(2) << "Pruned to "<<(retained.size())<<" toks.\n";
tmp.swap(*toks);
}
} // end namespace kaldi.
@@ -0,0 +1,318 @@
// decoder/lattice-simple-decoder.h
// Copyright 2009-2012 Microsoft Corporation
// 2012-2014 Johns Hopkins University (Author: Daniel Povey)
// 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.
#ifndef KALDI_DECODER_LATTICE_SIMPLE_DECODER_H_
#define KALDI_DECODER_LATTICE_SIMPLE_DECODER_H_
#include "util/stl-utils.h"
#include "fst/fstlib.h"
#include "itf/decodable-itf.h"
#include "fstext/fstext-lib.h"
#include "lat/determinize-lattice-pruned.h"
#include "lat/kaldi-lattice.h"
#include <algorithm>
namespace kaldi {
struct LatticeSimpleDecoderConfig {
BaseFloat beam;
BaseFloat lattice_beam;
int32 prune_interval;
bool determinize_lattice; // not inspected by this class... used in
// command-line program.
bool prune_lattice;
BaseFloat beam_ratio;
BaseFloat prune_scale; // Note: we don't make this configurable on the command line,
// it's not a very important parameter. It affects the
// algorithm that prunes the tokens as we go.
fst::DeterminizeLatticePhonePrunedOptions det_opts;
LatticeSimpleDecoderConfig(): beam(16.0),
lattice_beam(10.0),
prune_interval(25),
determinize_lattice(true),
beam_ratio(0.9),
prune_scale(0.1) { }
void Register(OptionsItf *opts) {
det_opts.Register(opts);
opts->Register("beam", &beam, "Decoding beam.");
opts->Register("lattice-beam", &lattice_beam, "Lattice generation beam");
opts->Register("prune-interval", &prune_interval, "Interval (in frames) at "
"which to prune tokens");
opts->Register("determinize-lattice", &determinize_lattice, "If true, "
"determinize the lattice (in a special sense, keeping only "
"best pdf-sequence for each word-sequence).");
}
void Check() const {
KALDI_ASSERT(beam > 0.0 && lattice_beam > 0.0 && prune_interval > 0);
}
};
/** Simplest possible decoder, included largely for didactic purposes and as a
means to debug more highly optimized decoders. See \ref decoders_simple
for more information.
*/
class LatticeSimpleDecoder {
public:
typedef fst::StdArc Arc;
typedef Arc::Label Label;
typedef Arc::StateId StateId;
typedef Arc::Weight Weight;
// instantiate this class once for each thing you have to decode.
LatticeSimpleDecoder(const fst::Fst<fst::StdArc> &fst,
const LatticeSimpleDecoderConfig &config):
fst_(fst), config_(config), num_toks_(0) { config.Check(); }
~LatticeSimpleDecoder() { ClearActiveTokens(); }
const LatticeSimpleDecoderConfig &GetOptions() const {
return config_;
}
// Returns true if any kind of traceback is available (not necessarily from
// a final state).
bool Decode(DecodableInterface *decodable);
/// says whether a final-state was active on the last frame. If it was not, the
/// lattice (or traceback) will end with states that are not final-states.
bool ReachedFinal() const {
return FinalRelativeCost() != std::numeric_limits<BaseFloat>::infinity();
}
/// InitDecoding initializes the decoding, and should only be used if you
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need
/// to call this. You can call InitDecoding if you have already decoded an
/// utterance and want to start with a new utterance.
void InitDecoding();
/// This function may be optionally called after AdvanceDecoding(), when you
/// do not plan to decode any further. It does an extra pruning step that
/// will help to prune the lattices output by GetLattice and (particularly)
/// GetRawLattice more accurately, particularly toward the end of the
/// utterance. It does this by using the final-probs in pruning (if any
/// final-state survived); it also does a final pruning step that visits all
/// states (the pruning that is done during decoding may fail to prune states
/// that are within kPruningScale = 0.1 outside of the beam). If you call
/// this, you cannot call AdvanceDecoding again (it will fail), and you
/// cannot call GetLattice() and related functions with use_final_probs =
/// false.
/// Used to be called PruneActiveTokensFinal().
void FinalizeDecoding();
/// FinalRelativeCost() serves the same purpose as ReachedFinal(), but gives
/// more information. It returns the difference between the best (final-cost
/// plus cost) of any token on the final frame, and the best cost of any token
/// on the final frame. If it is infinity it means no final-states were
/// present on the final frame. It will usually be nonnegative. If it not
/// too positive (e.g. < 5 is my first guess, but this is not tested) you can
/// take it as a good indication that we reached the final-state with
/// reasonable likelihood.
BaseFloat FinalRelativeCost() const;
// Outputs an FST corresponding to the single best path
// through the lattice. Returns true if result is nonempty
// (using the return status is deprecated, it will become void).
// If "use_final_probs" is true AND we reached the final-state
// of the graph then it will include those as final-probs, else
// it will treat all final-probs as one.
bool GetBestPath(Lattice *lat,
bool use_final_probs = true) const;
// Outputs an FST corresponding to the raw, state-level
// tracebacks. Returns true if result is nonempty
// (using the return status is deprecated, it will become void).
// If "use_final_probs" is true AND we reached the final-state
// of the graph then it will include those as final-probs, else
// it will treat all final-probs as one.
bool GetRawLattice(Lattice *lat,
bool use_final_probs = true) const;
// This function is now deprecated, since now we do determinization from
// outside the LatticeTrackingDecoder class.
// Outputs an FST corresponding to the lattice-determinized
// lattice (one path per word sequence). [will become deprecated,
// users should determinize themselves.]
bool GetLattice(CompactLattice *clat,
bool use_final_probs = true) const;
inline int32 NumFramesDecoded() const { return active_toks_.size() - 1; }
private:
struct Token;
// ForwardLinks are the links from a token to a token on the next frame.
// or sometimes on the current frame (for input-epsilon links).
struct ForwardLink {
Token *next_tok; // the next token [or NULL if represents final-state]
Label ilabel; // ilabel on link.
Label olabel; // olabel on link.
BaseFloat graph_cost; // graph cost of traversing link (contains LM, etc.)
BaseFloat acoustic_cost; // acoustic cost (pre-scaled) of traversing link
ForwardLink *next; // next in singly-linked list of forward links from a
// token.
ForwardLink(Token *next_tok, Label ilabel, Label olabel,
BaseFloat graph_cost, BaseFloat acoustic_cost,
ForwardLink *next):
next_tok(next_tok), ilabel(ilabel), olabel(olabel),
graph_cost(graph_cost), acoustic_cost(acoustic_cost),
next(next) { }
};
// Token is what's resident in a particular state at a particular time.
// In this decoder a Token actually contains *forward* links.
// When first created, a Token just has the (total) cost. We add forward
// links from it when we process the next frame.
struct Token {
BaseFloat tot_cost; // would equal weight.Value()... cost up to this point.
BaseFloat extra_cost; // >= 0. After calling PruneForwardLinks, this equals
// the minimum difference between the cost of the best path this is on,
// and the cost of the absolute best path, under the assumption
// that any of the currently active states at the decoding front may
// eventually succeed (e.g. if you were to take the currently active states
// one by one and compute this difference, and then take the minimum).
ForwardLink *links; // Head of singly linked list of ForwardLinks
Token *next; // Next in list of tokens for this frame.
Token(BaseFloat tot_cost, BaseFloat extra_cost, ForwardLink *links,
Token *next): tot_cost(tot_cost), extra_cost(extra_cost), links(links),
next(next) { }
Token() {}
void DeleteForwardLinks() {
ForwardLink *l = links, *m;
while (l != NULL) {
m = l->next;
delete l;
l = m;
}
links = NULL;
}
};
// head and tail of per-frame list of Tokens (list is in topological order),
// and something saying whether we ever pruned it using PruneForwardLinks.
struct TokenList {
Token *toks;
bool must_prune_forward_links;
bool must_prune_tokens;
TokenList(): toks(NULL), must_prune_forward_links(true),
must_prune_tokens(true) { }
};
// FindOrAddToken either locates a token in cur_toks_, or if necessary inserts a new,
// empty token (i.e. with no forward links) for the current frame. [note: it's
// inserted if necessary into cur_toks_ and also into the singly linked list
// of tokens active on this frame (whose head is at active_toks_[frame]).
//
// Returns the Token pointer. Sets "changed" (if non-NULL) to true
// if the token was newly created or the cost changed.
inline Token *FindOrAddToken(StateId state, int32 frame_plus_one,
BaseFloat tot_cost, bool emitting, bool *changed);
// delta is the amount by which the extra_costs must
// change before it sets "extra_costs_changed" to true. If delta is larger,
// we'll tend to go back less far toward the beginning of the file.
void PruneForwardLinks(int32 frame, bool *extra_costs_changed,
bool *links_pruned,
BaseFloat delta);
// PruneForwardLinksFinal is a version of PruneForwardLinks that we call
// on the final frame. If there are final tokens active, it uses the final-probs
// for pruning, otherwise it treats all tokens as final.
void PruneForwardLinksFinal();
// Prune away any tokens on this frame that have no forward links. [we don't do
// this in PruneForwardLinks because it would give us a problem with dangling
// pointers].
void PruneTokensForFrame(int32 frame);
// Go backwards through still-alive tokens, pruning them if the
// forward+backward cost is more than lat_beam away from the best path. It's
// possible to prove that this is "correct" in the sense that we won't lose
// anything outside of lat_beam, regardless of what happens in the future.
// delta controls when it considers a cost to have changed enough to continue
// going backward and propagating the change. larger delta -> will recurse
// less far.
void PruneActiveTokens(BaseFloat delta);
void ProcessEmitting(DecodableInterface *decodable);
void ProcessNonemitting();
void ClearActiveTokens(); // a cleanup routine, at utt end/begin
// This function computes the final-costs for tokens active on the final
// frame. It outputs to final-costs, if non-NULL, a map from the Token*
// pointer to the final-prob of the corresponding state, or zero for all states if
// none were final. It outputs to final_relative_cost, if non-NULL, the
// difference between the best forward-cost including the final-prob cost, and
// the best forward-cost without including the final-prob cost (this will
// usually be positive), or infinity if there were no final-probs. It outputs
// to final_best_cost, if non-NULL, the lowest for any token t active on the
// final frame, of t + final-cost[t], where final-cost[t] is the final-cost
// in the graph of the state corresponding to token t, or zero if there
// were no final-probs active on the final frame.
// You cannot call this after FinalizeDecoding() has been called; in that
// case you should get the answer from class-member variables.
void ComputeFinalCosts(unordered_map<Token*, BaseFloat> *final_costs,
BaseFloat *final_relative_cost,
BaseFloat *final_best_cost) const;
// PruneCurrentTokens deletes the tokens from the "toks" map, but not
// from the active_toks_ list, which could cause dangling forward pointers
// (will delete it during regular pruning operation).
void PruneCurrentTokens(BaseFloat beam, unordered_map<StateId, Token*> *toks);
unordered_map<StateId, Token*> cur_toks_;
unordered_map<StateId, Token*> prev_toks_;
std::vector<TokenList> active_toks_; // Lists of tokens, indexed by
// frame_plus_one
const fst::Fst<fst::StdArc> &fst_;
LatticeSimpleDecoderConfig config_;
int32 num_toks_; // current total #toks allocated...
bool warned_;
/// decoding_finalized_ is true if someone called FinalizeDecoding(). [note,
/// calling this is optional]. If true, it's forbidden to decode more. Also,
/// if this is set, then the output of ComputeFinalCosts() is in the next
/// three variables. The reason we need to do this is that after
/// FinalizeDecoding() calls PruneTokensForFrame() for the final frame, some
/// of the tokens on the last frame are freed, so we free the list from
/// cur_toks_ to avoid having dangling pointers hanging around.
bool decoding_finalized_;
/// For the meaning of the next 3 variables, see the comment for
/// decoding_finalized_ above., and ComputeFinalCosts().
unordered_map<Token*, BaseFloat> final_costs_;
BaseFloat final_relative_cost_;
BaseFloat final_best_cost_;
};
} // end namespace kaldi.
#endif
@@ -0,0 +1,293 @@
// decoder/simple-decoder.cc
// Copyright 2009-2011 Microsoft Corporation
// 2012-2013 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 "decoder/simple-decoder.h"
#include "fstext/remove-eps-local.h"
#include <algorithm>
namespace kaldi {
SimpleDecoder::~SimpleDecoder() {
ClearToks(cur_toks_);
ClearToks(prev_toks_);
}
bool SimpleDecoder::Decode(DecodableInterface *decodable) {
InitDecoding();
AdvanceDecoding(decodable);
return (!cur_toks_.empty());
}
void SimpleDecoder::InitDecoding() {
// clean up from last time:
ClearToks(cur_toks_);
ClearToks(prev_toks_);
// initialize decoding:
StateId start_state = fst_.Start();
KALDI_ASSERT(start_state != fst::kNoStateId);
StdArc dummy_arc(0, 0, StdWeight::One(), start_state);
cur_toks_[start_state] = new Token(dummy_arc, 0.0, NULL);
num_frames_decoded_ = 0;
ProcessNonemitting();
}
void SimpleDecoder::AdvanceDecoding(DecodableInterface *decodable,
int32 max_num_frames) {
KALDI_ASSERT(num_frames_decoded_ >= 0 &&
"You must call InitDecoding() before AdvanceDecoding()");
int32 num_frames_ready = decodable->NumFramesReady();
// num_frames_ready must be >= num_frames_decoded, or else
// the number of frames ready must have decreased (which doesn't
// make sense) or the decodable object changed between calls
// (which isn't allowed).
KALDI_ASSERT(num_frames_ready >= num_frames_decoded_);
int32 target_frames_decoded = num_frames_ready;
if (max_num_frames >= 0)
target_frames_decoded = std::min(target_frames_decoded,
num_frames_decoded_ + max_num_frames);
while (num_frames_decoded_ < target_frames_decoded) {
// note: ProcessEmitting() increments num_frames_decoded_
ClearToks(prev_toks_);
cur_toks_.swap(prev_toks_);
ProcessEmitting(decodable);
ProcessNonemitting();
PruneToks(beam_, &cur_toks_);
}
}
bool SimpleDecoder::ReachedFinal() const {
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter) {
if (iter->second->cost_ != std::numeric_limits<BaseFloat>::infinity() &&
fst_.Final(iter->first) != StdWeight::Zero())
return true;
}
return false;
}
BaseFloat SimpleDecoder::FinalRelativeCost() const {
// as a special case, if there are no active tokens at all (e.g. some kind of
// pruning failure), return infinity.
double infinity = std::numeric_limits<double>::infinity();
if (cur_toks_.empty())
return infinity;
double best_cost = infinity,
best_cost_with_final = infinity;
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter) {
// Note: Plus is taking the minimum cost, since we're in the tropical
// semiring.
best_cost = std::min(best_cost, iter->second->cost_);
best_cost_with_final = std::min(best_cost_with_final,
iter->second->cost_ +
fst_.Final(iter->first).Value());
}
BaseFloat extra_cost = best_cost_with_final - best_cost;
if (extra_cost != extra_cost) { // NaN. This shouldn't happen; it indicates some
// kind of error, most likely.
KALDI_WARN << "Found NaN (likely search failure in decoding)";
return infinity;
}
// Note: extra_cost will be infinity if no states were final.
return extra_cost;
}
// Outputs an FST corresponding to the single best path
// through the lattice.
bool SimpleDecoder::GetBestPath(Lattice *fst_out, bool use_final_probs) const {
fst_out->DeleteStates();
Token *best_tok = NULL;
bool is_final = ReachedFinal();
if (!is_final) {
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter)
if (best_tok == NULL || *best_tok < *(iter->second) )
best_tok = iter->second;
} else {
double infinity =std::numeric_limits<double>::infinity(),
best_cost = infinity;
for (unordered_map<StateId, Token*>::const_iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter) {
double this_cost = iter->second->cost_ + fst_.Final(iter->first).Value();
if (this_cost != infinity && this_cost < best_cost) {
best_cost = this_cost;
best_tok = iter->second;
}
}
}
if (best_tok == NULL) return false; // No output.
std::vector<LatticeArc> arcs_reverse; // arcs in reverse order.
for (Token *tok = best_tok; tok != NULL; tok = tok->prev_)
arcs_reverse.push_back(tok->arc_);
KALDI_ASSERT(arcs_reverse.back().nextstate == fst_.Start());
arcs_reverse.pop_back(); // that was a "fake" token... gives no info.
StateId cur_state = fst_out->AddState();
fst_out->SetStart(cur_state);
for (ssize_t i = static_cast<ssize_t>(arcs_reverse.size())-1; i >= 0; i--) {
LatticeArc arc = arcs_reverse[i];
arc.nextstate = fst_out->AddState();
fst_out->AddArc(cur_state, arc);
cur_state = arc.nextstate;
}
if (is_final && use_final_probs)
fst_out->SetFinal(cur_state,
LatticeWeight(fst_.Final(best_tok->arc_.nextstate).Value(),
0.0));
else
fst_out->SetFinal(cur_state, LatticeWeight::One());
fst::RemoveEpsLocal(fst_out);
return true;
}
void SimpleDecoder::ProcessEmitting(DecodableInterface *decodable) {
int32 frame = num_frames_decoded_;
// Processes emitting arcs for one frame. Propagates from
// prev_toks_ to cur_toks_.
double cutoff = std::numeric_limits<BaseFloat>::infinity();
for (unordered_map<StateId, Token*>::iterator iter = prev_toks_.begin();
iter != prev_toks_.end();
++iter) {
StateId state = iter->first;
Token *tok = iter->second;
KALDI_ASSERT(state == tok->arc_.nextstate);
for (fst::ArcIterator<fst::Fst<StdArc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const StdArc &arc = aiter.Value();
if (arc.ilabel != 0) { // propagate..
BaseFloat acoustic_cost = -decodable->LogLikelihood(frame, arc.ilabel);
double total_cost = tok->cost_ + arc.weight.Value() + acoustic_cost;
if (total_cost >= cutoff) continue;
if (total_cost + beam_ < cutoff)
cutoff = total_cost + beam_;
Token *new_tok = new Token(arc, acoustic_cost, tok);
unordered_map<StateId, Token*>::iterator find_iter
= cur_toks_.find(arc.nextstate);
if (find_iter == cur_toks_.end()) {
cur_toks_[arc.nextstate] = new_tok;
} else {
if ( *(find_iter->second) < *new_tok ) {
Token::TokenDelete(find_iter->second);
find_iter->second = new_tok;
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
num_frames_decoded_++;
}
void SimpleDecoder::ProcessNonemitting() {
// Processes nonemitting arcs for one frame. Propagates within
// cur_toks_.
std::vector<StateId> queue;
double infinity = std::numeric_limits<double>::infinity();
double best_cost = infinity;
for (unordered_map<StateId, Token*>::iterator iter = cur_toks_.begin();
iter != cur_toks_.end();
++iter) {
queue.push_back(iter->first);
best_cost = std::min(best_cost, iter->second->cost_);
}
double cutoff = best_cost + beam_;
while (!queue.empty()) {
StateId state = queue.back();
queue.pop_back();
Token *tok = cur_toks_[state];
KALDI_ASSERT(tok != NULL && state == tok->arc_.nextstate);
for (fst::ArcIterator<fst::Fst<StdArc> > aiter(fst_, state);
!aiter.Done();
aiter.Next()) {
const StdArc &arc = aiter.Value();
if (arc.ilabel == 0) { // propagate nonemitting only...
const BaseFloat acoustic_cost = 0.0;
Token *new_tok = new Token(arc, acoustic_cost, tok);
if (new_tok->cost_ > cutoff) {
Token::TokenDelete(new_tok);
} else {
unordered_map<StateId, Token*>::iterator find_iter
= cur_toks_.find(arc.nextstate);
if (find_iter == cur_toks_.end()) {
cur_toks_[arc.nextstate] = new_tok;
queue.push_back(arc.nextstate);
} else {
if ( *(find_iter->second) < *new_tok ) {
Token::TokenDelete(find_iter->second);
find_iter->second = new_tok;
queue.push_back(arc.nextstate);
} else {
Token::TokenDelete(new_tok);
}
}
}
}
}
}
}
// static
void SimpleDecoder::ClearToks(unordered_map<StateId, Token*> &toks) {
for (unordered_map<StateId, Token*>::iterator iter = toks.begin();
iter != toks.end(); ++iter) {
Token::TokenDelete(iter->second);
}
toks.clear();
}
// static
void SimpleDecoder::PruneToks(BaseFloat beam, unordered_map<StateId, Token*> *toks) {
if (toks->empty()) {
KALDI_VLOG(2) << "No tokens to prune.\n";
return;
}
double best_cost = std::numeric_limits<double>::infinity();
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
iter != toks->end(); ++iter)
best_cost = std::min(best_cost, iter->second->cost_);
std::vector<StateId> retained;
double cutoff = best_cost + beam;
for (unordered_map<StateId, Token*>::iterator iter = toks->begin();
iter != toks->end(); ++iter) {
if (iter->second->cost_ < cutoff)
retained.push_back(iter->first);
else
Token::TokenDelete(iter->second);
}
unordered_map<StateId, Token*> tmp;
for (size_t i = 0; i < retained.size(); i++) {
tmp[retained[i]] = (*toks)[retained[i]];
}
KALDI_VLOG(2) << "Pruned to " << (retained.size()) << " toks.\n";
tmp.swap(*toks);
}
} // end namespace kaldi.
@@ -0,0 +1,156 @@
// decoder/simple-decoder.h
// Copyright 2009-2013 Microsoft Corporation; Lukas Burget;
// Saarland University (author: Arnab Ghoshal);
// 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_DECODER_SIMPLE_DECODER_H_
#define KALDI_DECODER_SIMPLE_DECODER_H_
#include "util/stl-utils.h"
#include "fst/fstlib.h"
#include "lat/kaldi-lattice.h"
#include "itf/decodable-itf.h"
namespace kaldi {
/** Simplest possible decoder, included largely for didactic purposes and as a
means to debug more highly optimized decoders. See \ref decoders_simple
for more information.
*/
class SimpleDecoder {
public:
typedef fst::StdArc StdArc;
typedef StdArc::Weight StdWeight;
typedef StdArc::Label Label;
typedef StdArc::StateId StateId;
SimpleDecoder(const fst::Fst<fst::StdArc> &fst, BaseFloat beam): fst_(fst), beam_(beam) { }
~SimpleDecoder();
/// Decode this utterance.
/// Returns true if any tokens reached the end of the file (regardless of
/// whether they are in a final state); query ReachedFinal() after Decode()
/// to see whether we reached a final state.
bool Decode(DecodableInterface *decodable);
bool ReachedFinal() const;
// GetBestPath gets the decoding traceback. If "use_final_probs" is true
// AND we reached a final state, it limits itself to final states;
// otherwise it gets the most likely token not taking into account final-probs.
// fst_out will be empty (Start() == kNoStateId) if nothing was available due to
// search error.
// If Decode() returned true, it is safe to assume GetBestPath will return true.
// It returns true if the output lattice was nonempty (i.e. had states in it);
// using the return value is deprecated.
bool GetBestPath(Lattice *fst_out, bool use_final_probs = true) const;
/// *** The next functions are from the "new interface". ***
/// FinalRelativeCost() serves the same function as ReachedFinal(), but gives
/// more information. It returns the difference between the best (final-cost plus
/// cost) of any token on the final frame, and the best cost of any token
/// on the final frame. If it is infinity it means no final-states were present
/// on the final frame. It will usually be nonnegative.
BaseFloat FinalRelativeCost() const;
/// InitDecoding initializes the decoding, and should only be used if you
/// intend to call AdvanceDecoding(). If you call Decode(), you don't need
/// to call this. You can call InitDecoding if you have already decoded an
/// utterance and want to start with a new utterance.
void InitDecoding();
/// This will decode until there are no more frames ready in the decodable
/// object, but if max_num_frames is >= 0 it will decode no more than
/// that many frames. If it returns false, then no tokens are alive,
/// which is a kind of error state.
void AdvanceDecoding(DecodableInterface *decodable,
int32 max_num_frames = -1);
/// Returns the number of frames already decoded.
int32 NumFramesDecoded() const { return num_frames_decoded_; }
private:
class Token {
public:
LatticeArc arc_; // We use LatticeArc so that we can separately
// store the acoustic and graph cost, in case
// we need to produce lattice-formatted output.
Token *prev_;
int32 ref_count_;
double cost_; // accumulated total cost up to this point.
Token(const StdArc &arc,
BaseFloat acoustic_cost,
Token *prev): prev_(prev), ref_count_(1) {
arc_.ilabel = arc.ilabel;
arc_.olabel = arc.olabel;
arc_.weight = LatticeWeight(arc.weight.Value(), acoustic_cost);
arc_.nextstate = arc.nextstate;
if (prev) {
prev->ref_count_++;
cost_ = prev->cost_ + (arc.weight.Value() + acoustic_cost);
} else {
cost_ = arc.weight.Value() + acoustic_cost;
}
}
bool operator < (const Token &other) {
return cost_ > other.cost_;
}
static void TokenDelete(Token *tok) {
while (--tok->ref_count_ == 0) {
Token *prev = tok->prev_;
delete tok;
if (prev == NULL) return;
else tok = prev;
}
#ifdef KALDI_PARANOID
KALDI_ASSERT(tok->ref_count_ > 0);
#endif
}
};
// ProcessEmitting decodes the frame num_frames_decoded_ of the
// decodable object, then increments num_frames_decoded_.
void ProcessEmitting(DecodableInterface *decodable);
void ProcessNonemitting();
unordered_map<StateId, Token*> cur_toks_;
unordered_map<StateId, Token*> prev_toks_;
const fst::Fst<fst::StdArc> &fst_;
BaseFloat beam_;
// Keep track of the number of frames decoded in the current file.
int32 num_frames_decoded_;
static void ClearToks(unordered_map<StateId, Token*> &toks);
static void PruneToks(BaseFloat beam, unordered_map<StateId, Token*> *toks);
KALDI_DISALLOW_COPY_AND_ASSIGN(SimpleDecoder);
};
} // end namespace kaldi.
#endif
@@ -0,0 +1,182 @@
// decoder/training-graph-compiler.cc
// Copyright 2009-2011 Microsoft Corporation
// 2018 Johns Hopkins University (author: Daniel Povey)
// 2021 Xiaomi Corporation (Author: Junbo Zhang)
// 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 "decoder/training-graph-compiler.h"
#include "hmm/hmm-utils.h" // for GetHTransducer
namespace kaldi {
TrainingGraphCompiler::TrainingGraphCompiler(const TransitionModel &trans_model,
const ContextDependency &ctx_dep, // Does not maintain reference to this.
fst::VectorFst<fst::StdArc> *lex_fst,
const std::vector<int32> &disambig_syms,
const TrainingGraphCompilerOptions &opts):
trans_model_(trans_model), ctx_dep_(ctx_dep), lex_fst_(lex_fst),
disambig_syms_(disambig_syms), opts_(opts) {
using namespace fst;
const std::vector<int32> &phone_syms = trans_model_.GetPhones(); // needed to create context fst.
KALDI_ASSERT(!phone_syms.empty());
KALDI_ASSERT(IsSortedAndUniq(phone_syms));
SortAndUniq(&disambig_syms_);
for (int32 i = 0; i < disambig_syms_.size(); i++)
if (std::binary_search(phone_syms.begin(), phone_syms.end(),
disambig_syms_[i]))
KALDI_ERR << "Disambiguation symbol " << disambig_syms_[i]
<< " is also a phone.";
subsequential_symbol_ = 1 + phone_syms.back();
if (!disambig_syms_.empty() && subsequential_symbol_ <= disambig_syms_.back())
subsequential_symbol_ = 1 + disambig_syms_.back();
if (lex_fst == NULL) return;
{
int32 N = ctx_dep.ContextWidth(),
P = ctx_dep.CentralPosition();
if (P != N-1)
AddSubsequentialLoop(subsequential_symbol_, lex_fst_); // This is needed for
// systems with right-context or we will not successfully compose
// with C.
}
{ // make sure lexicon is olabel sorted.
fst::OLabelCompare<fst::StdArc> olabel_comp;
fst::ArcSort(lex_fst_, olabel_comp);
}
}
bool TrainingGraphCompiler::CompileGraphFromText(
const std::vector<int32> &transcript,
fst::VectorFst<fst::StdArc> *out_fst) {
using namespace fst;
VectorFst<StdArc> word_fst;
MakeLinearAcceptor(transcript, &word_fst);
return CompileGraph(word_fst, out_fst);
}
bool TrainingGraphCompiler::CompileGraphFromLG(const fst::VectorFst<fst::StdArc> &phone2word_fst,
fst::VectorFst<fst::StdArc> *out_fst) {
using namespace fst;
KALDI_ASSERT(phone2word_fst.Start() != kNoStateId);
const std::vector<int32> &phone_syms = trans_model_.GetPhones(); // needed to create context fst.
// inv_cfst will be expanded on the fly, as needed.
InverseContextFst inv_cfst(subsequential_symbol_,
phone_syms,
disambig_syms_,
ctx_dep_.ContextWidth(),
ctx_dep_.CentralPosition());
VectorFst<StdArc> ctx2word_fst;
ComposeDeterministicOnDemandInverse(phone2word_fst, &inv_cfst, &ctx2word_fst);
// now ctx2word_fst is C * LG, assuming phone2word_fst is written as LG.
KALDI_ASSERT(ctx2word_fst.Start() != kNoStateId);
HTransducerConfig h_cfg;
h_cfg.transition_scale = opts_.transition_scale;
std::vector<int32> disambig_syms_h; // disambiguation symbols on
// input side of H.
VectorFst<StdArc> *H = GetHTransducer(inv_cfst.IlabelInfo(),
ctx_dep_,
trans_model_,
h_cfg,
&disambig_syms_h);
VectorFst<StdArc> &trans2word_fst = *out_fst; // transition-id to word.
TableCompose(*H, ctx2word_fst, &trans2word_fst);
KALDI_ASSERT(trans2word_fst.Start() != kNoStateId);
// Epsilon-removal and determinization combined. This will fail if not determinizable.
DeterminizeStarInLog(&trans2word_fst);
if (!disambig_syms_h.empty()) {
RemoveSomeInputSymbols(disambig_syms_h, &trans2word_fst);
// we elect not to remove epsilons after this phase, as it is
// a little slow.
if (opts_.rm_eps)
RemoveEpsLocal(&trans2word_fst);
}
// Encoded minimization.
MinimizeEncoded(&trans2word_fst);
std::vector<int32> disambig;
bool check_no_self_loops = true;
AddSelfLoops(trans_model_,
disambig,
opts_.self_loop_scale,
opts_.reorder,
check_no_self_loops,
&trans2word_fst);
delete H;
return true;
}
bool TrainingGraphCompiler::CompileGraph(const fst::VectorFst<fst::StdArc> &word_fst,
fst::VectorFst<fst::StdArc> *out_fst) {
using namespace fst;
KALDI_ASSERT(lex_fst_ !=NULL);
KALDI_ASSERT(out_fst != NULL);
VectorFst<StdArc> phone2word_fst;
// TableCompose more efficient than compose.
TableCompose(*lex_fst_, word_fst, &phone2word_fst, &lex_cache_);
return CompileGraphFromLG(phone2word_fst, out_fst);
}
bool TrainingGraphCompiler::CompileGraphsFromText(
const std::vector<std::vector<int32> > &transcripts,
std::vector<fst::VectorFst<fst::StdArc>*> *out_fsts) {
using namespace fst;
std::vector<const VectorFst<StdArc>* > word_fsts(transcripts.size());
for (size_t i = 0; i < transcripts.size(); i++) {
VectorFst<StdArc> *word_fst = new VectorFst<StdArc>();
MakeLinearAcceptor(transcripts[i], word_fst);
word_fsts[i] = word_fst;
}
bool ans = CompileGraphs(word_fsts, out_fsts);
for (size_t i = 0; i < transcripts.size(); i++)
delete word_fsts[i];
return ans;
}
bool TrainingGraphCompiler::CompileGraphs(
const std::vector<const fst::VectorFst<fst::StdArc>* > &word_fsts,
std::vector<fst::VectorFst<fst::StdArc>* > *out_fsts) {
out_fsts->resize(word_fsts.size(), NULL);
for (size_t i = 0; i < word_fsts.size(); i++) {
fst::VectorFst<fst::StdArc> trans2word_fst;
if (!CompileGraph(*(word_fsts[i]), &trans2word_fst)) return false;
(*out_fsts)[i] = trans2word_fst.Copy();
}
return true;
}
} // end namespace kaldi
@@ -0,0 +1,117 @@
// decoder/training-graph-compiler.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.
#ifndef KALDI_DECODER_TRAINING_GRAPH_COMPILER_H_
#define KALDI_DECODER_TRAINING_GRAPH_COMPILER_H_
#include "base/kaldi-common.h"
#include "hmm/transition-model.h"
#include "fst/fstlib.h"
#include "fstext/fstext-lib.h"
#include "tree/context-dep.h"
namespace kaldi {
struct TrainingGraphCompilerOptions {
BaseFloat transition_scale;
BaseFloat self_loop_scale;
bool rm_eps;
bool reorder; // (Dan-style graphs)
explicit TrainingGraphCompilerOptions(BaseFloat transition_scale = 1.0,
BaseFloat self_loop_scale = 1.0,
bool b = true) :
transition_scale(transition_scale),
self_loop_scale(self_loop_scale),
rm_eps(false),
reorder(b) { }
void Register(OptionsItf *opts) {
opts->Register("transition-scale", &transition_scale, "Scale of transition "
"probabilities (excluding self-loops)");
opts->Register("self-loop-scale", &self_loop_scale, "Scale of self-loop vs. "
"non-self-loop probability mass ");
opts->Register("reorder", &reorder, "Reorder transition ids for greater decoding efficiency.");
opts->Register("rm-eps", &rm_eps, "Remove [most] epsilons before minimization (only applicable "
"if disambig symbols present)");
}
};
class TrainingGraphCompiler {
public:
TrainingGraphCompiler(const TransitionModel &trans_model, // Maintains reference to this object.
const ContextDependency &ctx_dep, // And this.
fst::VectorFst<fst::StdArc> *lex_fst, // Takes ownership of this object.
// It should not contain disambiguation symbols or subsequential symbol,
// but it should contain optional silence.
const std::vector<int32> &disambig_syms, // disambig symbols in phone symbol table.
const TrainingGraphCompilerOptions &opts);
// CompileGraph compiles a single training graph its input is a
// weighted acceptor (G) at the word level, its output is HCLG.
// Note: G could actually be a transducer, it would also work.
// This function is not const for technical reasons involving the cache.
// if not for "table_compose" we could make it const.
bool CompileGraph(const fst::VectorFst<fst::StdArc> &word_grammar,
fst::VectorFst<fst::StdArc> *out_fst);
// Same as `CompileGraph`, but uses an external LG fst.
bool CompileGraphFromLG(const fst::VectorFst<fst::StdArc> &phone2word_fst,
fst::VectorFst<fst::StdArc> * out_fst);
// CompileGraphs allows you to compile a number of graphs at the same
// time. This consumes more memory but is faster.
bool CompileGraphs(
const std::vector<const fst::VectorFst<fst::StdArc> *> &word_fsts,
std::vector<fst::VectorFst<fst::StdArc> *> *out_fsts);
// This version creates an FST from the text and calls CompileGraph.
bool CompileGraphFromText(const std::vector<int32> &transcript,
fst::VectorFst<fst::StdArc> *out_fst);
// This function creates FSTs from the text and calls CompileGraphs.
bool CompileGraphsFromText(
const std::vector<std::vector<int32> > &word_grammar,
std::vector<fst::VectorFst<fst::StdArc> *> *out_fsts);
~TrainingGraphCompiler() { delete lex_fst_; }
private:
const TransitionModel &trans_model_;
const ContextDependency &ctx_dep_;
fst::VectorFst<fst::StdArc> *lex_fst_; // lexicon FST (an input; we take
// ownership as we need to modify it).
std::vector<int32> disambig_syms_; // disambig symbols (if any) in the phone
int32 subsequential_symbol_; // search in ../fstext/context-fst.h for more info.
// symbol table.
fst::TableComposeCache<fst::Fst<fst::StdArc> > lex_cache_; // stores matcher..
// this is one of Dan's extensions.
TrainingGraphCompilerOptions opts_;
};
} // end namespace kaldi.
#endif