Token Passing解码

1、Token Passing讲解视频

参考地址:Token passing

2、Token Passing(以Kaldi代码为例)

(1)取src/fstext/deterministic-fst-test.cc,描述了怎么创建fst。

StdVectorFst* CreateBackoffFst() {
  StdVectorFst *fst = new StdVectorFst();
  fst->AddState();   // state 0
  fst->SetStart(0);
  fst->AddArc(0, StdArc(10, 10, 0.0, 1));

  // AddArc(0, StdArc(10, 10, 0.0, 1))的5个数字的意思分别是:源节点、输入的transition-id、
  // 输出的id(可能是word-id、phones-id等)、权重、目标节点

  fst->AddState();    // state 1
  fst->AddArc(1, StdArc(12, 12, 0.0, 4));
  fst->AddArc(1, StdArc(0,0,  0.1,2));  // backoff from 1 to 2

  fst->AddState();    // state 2
  fst->AddArc(2, StdArc(13, 13, 0.2, 4));
  fst->AddArc(2, StdArc(0,0,  0.3,3));  // backoff from 2 to 3

  fst->AddState();     // state 3
  fst->AddArc(3, StdArc(14, 14, 0.4, 4));

  fst->AddState();    // state 4
  fst->AddArc(4, StdArc(15, 15, 0.5, 5));

  fst->AddState();     // state 5
  fst->SetFinal(5, 0.6);

  return fst;
}

该函数构造的fst图形如下:

(2)数据结构:

Token包含哪些内容?

  1. 边(输入标签、输出标签、代价cost、边指向的下一个状态);
  2. 前向指针指向的token(用来回溯,最优路径);
  3. 前向有多少指针;

     4.cost(新的token cost=前向指针指向的token的cost+边的cost+该token的cost);

中间变量:

1、一个map存储当前帧解码的current token(state-id、token);

2、一个map存储前一帧解码的previous token(state-id、token);


class SimpleDecoder {
 public:
  // StdArc : 边(输入、输出、代价)
  typedef fst::StdArc StdArc;
  typedef StdArc::Weight StdWeight;       // 代价
  typedef StdArc::Label Label;            // 输出
  typedef StdArc::StateId StateId;        // 输入

  // 构造方法
  SimpleDecoder(const fst::Fst &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);

  // 判断到没到终止节点
  // 对1和2有疑惑:
  //   1、是否到了解码图的终止节点
  //   2、一段语音分为好多帧,这个是判断一个utt-id音频分出来的帧是否到了最后一帧
  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】;否则就不删除前面的token
        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_.
  // 拓展实边(transitions-id不为0的)
  void ProcessEmitting(DecodableInterface *decodable);

  // 拓展实边(transitions-id为0的)
  void ProcessNonemitting();

  unordered_map cur_toks_;   // 当前解码的token
  unordered_map prev_toks_;  // 前一帧解码的token
  const fst::Fst &fst_;          // HCLG
  BaseFloat beam_;
  // Keep track of the number of frames decoded in the current file.
  int32 num_frames_decoded_;      // 已经解码了多少帧

  // 清除token
  static void ClearToks(unordered_map &toks);

  static void PruneToks(BaseFloat beam, unordered_map *toks);

  KALDI_DISALLOW_COPY_AND_ASSIGN(SimpleDecoder);
};

实现步骤:

(1)初始化(ProcessNonemitting):

[1.1]、初始化一个token(0,0,0,HCLG.fst的start state),将其加入到current token中。

[1.2]、新建一个queue队列,遍历current token中的每一个元素,将每个元素的state-id加入到queue中,并且找到该current token中的最小cost,最小cost+beam(beam为一个数值,由自己设定)作为一个剪枝的阈值cost。

[1.3]、遍历步骤[1.2]生成的队列queue,针对queue中的每一个元素start-id,得到该state-id对应的token(记为token1),在HCLG.fst中遍历它的所有出边。针对它的所有出边:

[1.3.1]如果出边的输入标签不为零,则跳过;

[1.3.2]如果出边的输入标签为零,则新建一个token(前一个token就是token1),比较新token的cost与阈值cost的大小,

[1.3.2.1]比阈值cost大时,就删除该token;

[1.3.2.2]比阈值cost小时:

[1.3.2.2.1].如果该出边指向的下一个state-id没有token,就把(start-id、新token)加入到current token中,把state-id加入到queue中。

[1.3.2.2.2]如果该出边指向的下一个state-id有token,比较新旧token cost,保存cost小的token,删除cost大的token(相当于此处会进行token的更新)。

[1.4]、遍历完queue,直至为空。步骤[3]中会生成一个current token的map1,map1中存放了初始化过程中遇到的所有state-id token。类似于一个二叉树广度优先搜索,但是只搜索满足条件的一些节点,并为每一个节点建了一个token,token中存放达到该节点的最小cost、和到达该节点cost最小的路径中的前一个节点。

(2)寻优(AdvanceDecoding):

[2.1]、以帧为单位循环遍历,每一个帧经过如下处理:1.清空previous token,交换previous token和current token、2.遍历出边不为零的state-id(ProcessEmitting)、3.遍历出边为零的state-id(ProcessNonemitting)、4.剪枝token。

[2.2]、清空previous token,交换previous token和current token。

[2.3]、遍历出边不为零的state-id(ProcessEmitting):

[2.3.1].初始化cost为无穷大,遍历previous token中的每一个元素,得到state-id和token2。

[2.3.2].遍历token1的所有出边:

[2.3.2.1].如果出边的输入标签为零,则跳过;

[2.3.2.2].如果到出边指向的下一个节点的cost比无穷大还大,则跳过;

[2.3.2.3].如果出边的输入标签不为零,则新建一个token(前一个token就是token2);

[2.3.2.3.1].如果该出边指向的下一个state-id没有token,就把(start-id、新token)加入到current token中。

[2.3.2.3.2].如果该出边指向的下一个state-id有token,比较新旧token cost,保存cost小的token,删除cost大的token(相当于此处会进行token的更新)。

[2.4]、遍历出边为零的state-id(ProcessNonemitting),和初始化一样。

[2.5]、剪枝token(处理current token)。

[2.5.1].遍历current token,找到cost的最小值。

[2.5.2].cost的最小值+beam得到一个阈值cost。

[2.5.3].再一次遍历current token,删除所有cost大于阈值cost的token,得到一个新的current token,用于第[6]中的计算。

[2.6]、循环步骤[2]、步骤[3]、步骤[4],步骤[5]。

Token Passing的步骤如上,是基于Kaldi简单解码得出的步骤。

总结1Token Passing类似于对一个二叉树进行广度优先搜索,但是只搜索满足条件的一些节点,并为每一个节点建了一个tokentoken中存放达到该节点路径的最小cost、和到达该节点cost最小的路径中的前一个节点的指针。其中token会在每次遍历过程中更新。Token Passing是以帧为单位进行遍历的,会有很多次的广度优先搜索。

总结2Token Passing遍历过程中,遍历输入标签为零的出边时,会进行广度优先遍历+深度优先遍历(会在遍历过程中添加遍历项);遍历输入标签不为零的出边时,只遍历固定的节点(上次遍历结束设定好的)。

// 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 

namespace kaldi {

SimpleDecoder::~SimpleDecoder() {
  ClearToks(cur_toks_);
  ClearToks(prev_toks_);
}

// 由gmm-decode-simple.cc中的decoder.Decode(&gmm_decodable);调用
bool SimpleDecoder::Decode(DecodableInterface *decodable) {
  std::cout<<"--simple decoder start."<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).
  // num_frames_decoded_ : 已经解码的帧数 【保证       音频的帧数大于等于当前解码的帧数】
  KALDI_ASSERT(num_frames_ready >= num_frames_decoded_);

  int32 target_frames_decoded = num_frames_ready;

  // max_num_frames为-1,该循环不走
  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_
    // 清除了prev token
    ClearToks(prev_toks_);
	// current toekn和prev token交换位置
    cur_toks_.swap(prev_toks_);
  
    ProcessEmitting(decodable);
    ProcessNonemitting();

    // 处理令牌(剪枝)
	PruneToks(beam_, &cur_toks_);
  }
}

bool SimpleDecoder::ReachedFinal() const {
  for (unordered_map::const_iterator iter = cur_toks_.begin();
       iter != cur_toks_.end();
       ++iter) {
    if (iter->second->cost_ != std::numeric_limits::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::infinity();
  if (cur_toks_.empty())
    return infinity;
  double best_cost = infinity,
      best_cost_with_final = infinity;
  for (unordered_map::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::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::infinity();
    double best_cost = infinity;
    for (unordered_map::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.

  // 由best token通过prev_指针,往回找,直到找到最初的开始点,形成一个链,保存在arcs_reverse中
  std::vector 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.

  // 把最优路径保存在fst_out中。
  StateId cur_state = fst_out->AddState();
  fst_out->SetStart(cur_state);
  for (ssize_t i = static_cast(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_.
  // 初始化剪枝cost为正无穷大
  double cutoff = std::numeric_limits::infinity();

  // 遍历prev token
  for (unordered_map::iterator iter = prev_toks_.begin();
       iter != prev_toks_.end();
       ++iter) {
    StateId state = iter->first;
    Token *tok = iter->second;
    KALDI_ASSERT(state == tok->arc_.nextstate);

	// 遍历当前token的出边【遍历从state-id节点出发的每一条弧】
    for (fst::ArcIterator > aiter(fst_, state);
         !aiter.Done(); aiter.Next()) {
      const StdArc &arc = aiter.Value();

	  // 如果出边的输入transition-id不为0时,
      if (arc.ilabel != 0) {  // propagate..
        // 
        BaseFloat acoustic_cost = -decodable->LogLikelihood(frame, arc.ilabel);

        // 总cost = token的cost(0) + 边的cost + 声学模型cost
	    double total_cost = tok->cost_ + arc.weight.Value() + acoustic_cost;

        // 剪枝去掉total cost大的出边
        if (total_cost >= cutoff)
		  continue;

		// 
        if (total_cost + beam_  < cutoff)
          cutoff = total_cost + beam_;   // 更新剪枝cost:cutoff

		// 新建一个token:规定走那条边,代价,上一个token
        Token *new_tok = new Token(arc, acoustic_cost, tok);

		// 同出边的目的state-id去找下一个token:find_iter
        unordered_map::iterator find_iter
            = cur_toks_.find(arc.nextstate);   // 查找这条边的下一个节点是否有令牌

	    // 如果没有令牌,就新建一个令牌,保存到cur_toks_中
        if (find_iter == cur_toks_.end()) {
          cur_toks_[arc.nextstate] = new_tok;
        } else {
          // 如果有令牌,比较cost的大小,如果新的cost小,就删除老的,更新称新的
          if ( *(find_iter->second) < *new_tok ) {
            Token::TokenDelete(find_iter->second);
            find_iter->second = new_tok;
          } else {
		  	// 如果新的cost大,就把新的删掉
            Token::TokenDelete(new_tok);
          }
        }
      }
    }
  }
  num_frames_decoded_++;
}

void SimpleDecoder::ProcessNonemitting() {
  // Processes nonemitting arcs for one frame.  Propagates within
  // cur_toks_.
  // 新建一个队列,局部变量
  std::vector queue;

  // 一开始的代价为无穷大
  double infinity = std::numeric_limits::infinity();
  double best_cost = infinity;

  // 遍历current tokens【cur_toks_是一个map,first为StateId,second为Token*】
  // 遍历current tokens,可以得到cur_token_中的所有节点,组成一个队列;然后再遍历这个节点的每一个出边
  for (unordered_map::iterator iter = cur_toks_.begin();
       iter != cur_toks_.end();
       ++iter) {
    // 将current token的state-id放在队列中
    queue.push_back(iter->first);
	// 找到最小的代价
    best_cost = std::min(best_cost, iter->second->cost_);
  }

  // 进行剪枝
  double cutoff = best_cost + beam_;

  // 遍历队列【队列中存放的是current token的state-id】
  while (!queue.empty()) {
    StateId state = queue.back();
    queue.pop_back();
    Token *tok = cur_toks_[state]; // 由state-id得到token
    KALDI_ASSERT(tok != NULL && state == tok->arc_.nextstate);

	// 遍历token的出边【通过HCLG】
    for (fst::ArcIterator > aiter(fst_, state);
         !aiter.Done();
         aiter.Next()) {
      const StdArc &arc = aiter.Value();

	  // 如果出边的输入transition-id为0时,
      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) {
		  // 如果新边的cost大于最大的剪枝cost,就把这个出边剪枝掉了
          Token::TokenDelete(new_tok);
        } else {
          unordered_map::iterator find_iter = cur_toks_.find(arc.nextstate);
		   用nextstate去找,发现没有找到,返回了end()。
          if (find_iter == cur_toks_.end()) { 
		  	// 表示如果下一个状态上还没有令牌token,那么就将此令牌放到下一个状态上。
            cur_toks_[arc.nextstate] = new_tok;
			// 更新队列,感觉就会打HCLG.fst中的所有non-emitting的全部遍历一遍
            queue.push_back(arc.nextstate);
          } else {
            if ( *(find_iter->second) < *new_tok ) {
			  // 如果下一个状态的令牌不比新令牌cost小,则用新令牌替换下一个状态的令牌
              Token::TokenDelete(find_iter->second);
              find_iter->second = new_tok;
              queue.push_back(arc.nextstate);
            } else {
			  // 如果下一个状态的令牌比新令牌cost小,则删除新令牌
              Token::TokenDelete(new_tok);
            }
          }
        }
      }
    }
  }
}

// static
void SimpleDecoder::ClearToks(unordered_map &toks) {
  for (unordered_map::iterator iter = toks.begin();
       iter != toks.end(); ++iter) {
    Token::TokenDelete(iter->second);
  }
  toks.clear();
}

// static
void SimpleDecoder::PruneToks(BaseFloat beam, unordered_map *toks) {
  if (toks->empty()) {
    KALDI_VLOG(2) <<  "No tokens to prune.\n";
    return;
  }
  double best_cost = std::numeric_limits::infinity();
  for (unordered_map::iterator iter = toks->begin();
       iter != toks->end(); ++iter)
	// 遍历当前token map,找到cost最小的那一个;入参toks为刚新生成的token map   	
    best_cost = std::min(best_cost, iter->second->cost_);

  std::vector retained;
  double cutoff = best_cost + beam;
  for (unordered_map::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 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";

  // 保留剪枝后的token map
  tmp.swap(*toks);
}

} // end namespace kaldi.

3、举例

假如HCLG如下图所示:

Token Passing解码_第1张图片

1、HCLG.fst的起始节点state-id=0,新建token1(输入标签:0,输出标签:0,cost:0,下一个state-id就是HCLG.fst的起始节点:0),则current token中就只有一个元素state-id:0 token1。

2、新建一个queue,保存current token中每一个元素的state-id;这里只有一个元素state-id:0。遍历queue的每一个元素,针对每一个元素在HCLG.fst中遍历它的所有输入标签为零的出边,这里没有,则遍历结束。最终current token中只有一个元素state-id=0 token1 cost=0。

3将current token中的元素全部放入previous token中,则previous中也只有一个元素:token1。遍历previous token中所有元素,则token1的所有输入标签不为零的出边,这里只有一个出边1(10,10,0,1),由于state-id=1上没有token,就新建了一个token2(10,10,0,token1),这里current token中只有一个元素state-id=1 token2 cost=0。

4新建一个queue,保存current token中每一个元素的state-id;这里只有一个元素state-id:1。遍历queue的每一个元素,针对每一个元素在HCLG.fst中遍历它的所有输入标签为零的出边,这里只有一个出边2(0,0,0.1,2)。由于state-id=2上没有token,就新建了一个token3(0,0,0.1,token2)cost=0.1,这里current token中就有两个元素state-id=1 token2 cost=0和state-id=2 token3 cost=0.1。然后将state-id=2加入到queue中,再次遍历queue中,由于state-id=2有一个输入标签为零的出边3(0,0,0.3,3),由于state-id=3上没有token,新建token4(0,0,0.4,token3),这里current token中就有三个元素state-id=1 token2 cost=0、state-id=2 token3 cost=0.1和state-id=3 token4 cost=0.4。然后将state-id=3加入到queue中,遍历queue的每一个state-id的所有输入标签为零的出边,这里state-id=3没有,则遍历结束。这时current token中就有三个元素state-id=1 token2 cost=0、state-id=2 token3 cost=0.1和state-id=3 token4 cost=0.4。

5剪枝current token,如果beam比较大,这里的token都满足条件;如果beam比较小,则state-id=2 token3和state-id=4 token4的cost比state-id=1 token2的cost大,会被剪枝掉。这里使用剪枝操作,后面会更好理解。

6将current token中的元素全部放入previous token中,则previous中也只有一个元素:state-id=1 token2 cost=0。遍历previous token中所有元素,则token1的所有输入标签不为零的出边,这里只有一个出边4(12,2,0,4),由于state-id=4上没有token,就新建了一个token5(12,2,0,token2),这里current token中只有一个元素state-id=4 token5 cost=0。

7新建一个queue,保存current token中每一个元素的state-id;这里只有一个元素state-id:4。遍历queue的每一个元素,针对每一个元素在HCLG.fst中遍历它的所有输入标签不为零的出边,这里没有,则遍历结束。最终current token中只有一个元素state-id=4 token5 cost=0。

8剪枝current token,这里不使用剪枝操作,后面会更好理解。

9将current token中的元素全部放入previous token中,则previous中也只有一个元素:token5。遍历previous token中所有元素,则token5的所有输入标签不为零的出边,这里只有一个出边5(15,15,0.5,5),由于state-id=5上没有token,就新建了一个token6(15,15,0.5,token5),这里current token中只有一个元素state-id=5 token6 cost=0.5。因为state-id=5是HCLG.fst的结束节点。遍历到这里就结束了。

10、路径中有token6((15,15,0.5),token5)、token5((0,0,0),token2)、token2((0,0,0),token1)、token1((0,0,0),NULL)。路径从结束节点往前回溯,找到最优路径上的每一个节点。

你可能感兴趣的:(语音,神经网络共同学习,语音识别,人工智能)