chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
+886
@@ -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
|
||||
+285
@@ -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.
|
||||
+146
@@ -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
|
||||
+1730
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
|
||||
+158
@@ -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.
|
||||
+132
@@ -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
|
||||
Reference in New Issue
Block a user