天天看點

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

文章目錄

  • 互資訊
  • 最大似然估計MLE(缺點)
  • 區分性訓練DT和最大互資訊MMI
  • 區分性訓練缺點
  • Lattice
  • MMI的問題:
  • MMI代碼分析
    • 輸入資料的介紹,以及特征的轉化
    • lattice boost

搜集資料的思路:mmi -> DT -> mle -> ce -> 熵 -> 互資訊

互資訊

首先連接配接機器學習中的熵、條件熵、相對熵(KL散度)和交叉熵的概念:傳送門

資訊熵是衡量随機變量分布的混亂程度,是随機分布各事件發生的資訊量的期望值,随機變量的取值個數越多,狀态數也就越多,資訊熵就越大,混亂程度就越大。

當随機分布為均勻分布時,熵最大;資訊熵推廣到多元領域,則可得到聯合資訊熵;條件熵表示的是在 X 給定條件下,Y 的條件機率分布的熵對 X的期望。

相對熵可以用來衡量兩個機率分布之間的差異。

交叉熵可以來衡量在給定的真實分布下,使用非真實分布所指定的政策消除系統的不确定性所需要付出的努力的大小。

互資訊(mutual information):

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

mmi準則實際上就是最大化互資訊的縮寫

最大似然估計MLE(缺點)

最大似然估計,即假設一種分布,用現在已知的資料去估計這種分布的參數。

然後看交叉熵,我們用估計的分布算出一個交叉熵,然後讓交叉熵盡可能小達到估計真實的分布。是以我們之前的訓練是屬于MLE,用的是交叉熵。交叉熵也就是MLE的一種實作,同樣如果是KL散度,也是屬于MLE的實作,正如我們前面說的這樣。

我們語音識别的目的是最大化P(W∣O). w是words ,o是指觀測序列

P ( W ∣ O ) = P ( O ∣ W ) P ( W ) P ( O ) P(W|O)=\frac{P(O|W)P(W)}{P(O)} P(W∣O)=P(O)P(O∣W)P(W)​,其中 P ( O ) P(O) P(O)對于每個任務都是一樣的, P ( W ) P(W) P(W) 是由語言模型确定, P ( O ∣ W ) P(O∣W) P(O∣W)是由聲學模型确定,也稱之為後驗機率。

HMM也就是對 P ( O ∣ W ) P(O∣W) P(O∣W)進行的模組化,我們所用的神經網絡實際上也就是學習hmm的發射矩陣。

MLE缺點:

  • 我們所學習模拟的分布是已知的。要求模型假設必須正确。
  • 訓練時資料應趨向于無窮多。
  • 在解碼時語言模型是趨向于真實的語言分布。

對于第一點,我們是通過GMM去模拟語音的真實分布,因為我們認為GMM可以模拟出任意一種分布。然後在此基礎上去對齊訓練。但是如果GMM訓練的不到位呢。第二點第三點就不用再說了。

于是提出了區分性訓練

區分性訓練DT和最大互資訊MMI

區分性訓練

于是對于這些問題,就有了區分性訓練,區分性訓練實際上就是希望通過設定一個目标函數達到獎勵正确的同時處罰錯誤的這樣一個目的,來進行訓練。

它有幾種實作方法,其中一種是MMI,最大化互資訊,其他的還有BMMI/MPE/sMBR之類的。

我們前面說到,互資訊是描述兩個随機變量的關聯程度,于是在這裡就是描述觀測序列和文本的關聯程度。

再來看一下互資訊公式,這裡隻介紹一下主要的公式, I ( x , y ) = H ( x ) − H ( x ∣ y ) I(x,y)=H(x)−H(x∣y) I(x,y)=H(x)−H(x∣y)。是以最大化互資訊,就等于最小化條件熵 H ( x ∣ y ) H(x∣y) H(x∣y)。

MMI

公式:

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

MMI公式本身它可以看成,正确路徑得分與所有路徑得分的比值。當正确路徑得分提升的同時,錯誤路徑得分會減少,是以是一種區分性訓練。

實際上我們就可以看成

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

也就是我們是以 P ( W ∣ O ) P(W|O) P(W∣O)作為目标函數,而不是以 P ( O ∣ W ) P(O|W) P(O∣W)作為目标函數了。可以看到,實際上,MMI把語言模型的東西也考慮進去了。這樣做相對于MLE的好處就是,即使假設的分布不是很好,得到的結果也不會太差。

SDT(MMI/BMMI/MPE/sMBR)的詳細公式推理可以參考一下部落格:傳送門

區分性訓練缺點

訓練的資料泛化能力不強。 我的了解是,我們把一個正确的路徑學的太多了,導緻相對于這個訓練集的錯誤路徑的分數太低太低,是以為了增強它的泛化能力,我們适當增加錯誤路徑的得分。或者說讓聲學模型在其中更具主導性,而不是一味地讓語言模型進行主導。

MMI訓練過程

我們在對一些參數進行更新的時候,需要用到的是前向後向算法,并且需要對分子和分母分别進行前向後向算法。這個是hmm中使用的一種方法。

Lattice

對于分子來說,這個算法是可行的,但對于分母來說,這個算法的計算量就太大了。是以為了計算可行性,我們使用lattice來進行計算,lattice實際上就是詞格,詞網絡。我們通常使用的網絡是狀态級别的網絡,而這裡使用的詞網絡,這就是我們在訓練前要進行對齊lats。然後我們為了友善統計資訊,我們在每個詞節點中加入狀态資訊。

是以,我們在訓練chain-model的時候,使用的是wer,而不是cer。同時,值得注意的是,我們在實際使用的時候,wer也是更值得關注一些。

當然,對分母的優化隻有這一點是遠遠不夠的,在lf-mmi中會大量提及對分母的優化,包括hmm的優化,解碼圖的優化,以及各種tricks。這裡先隻提及從狀态級别的轉向lattice級别的優化。

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

注意:這裡要強調一點,正确的路徑可能不止一條,因為可能會有多音字的情況。

對于它的訓練:

  • 我們還是使用MLE(CE)準則訓練的模型去進行對齊得到lats。
  • 一般來說,在整個訓練過程中這些lats不會改變的,但也有随着訓練過程而變化的情況,這樣得到的效果也是不錯的。
  • 解碼圖,每個分子的解碼圖是根據目前句子定的,但分母的解碼圖都是一樣的。
  • 但實際上我們在kaldi訓練的過程中不是這樣的,kaldi中實作的是lf-mmi

MMI的問題:

對于MMI的一個問題就是,訓練的資料泛化能力不強。我的了解是,我們把一個正确的路徑學的太多了,導緻相對于這個訓練集的錯誤路徑的分數太低太低,是以為了增強它的泛化能力,我們适當增加錯誤路徑的得分。或者說讓聲學模型在其中更具主導性,而不是一味地讓語言模型進行主導。

是以有幾種解決方法,這裡隻列舉幾種:

  • 聲學參數k,增加聲學參數的影響
  • lat使用一個簡單的語言模型,一般來說,在分母的解碼圖的語言模型實際上使用的是一個word-level的bigram
  • boosted mmi
    區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

我們可以看到,和MMI相比,BMMI在分母上增加了一項, e x p ( − b A ( s , s r ) ) exp(-bA(s,sr)) exp(−bA(s,sr)) 其中A(s,sr)表示的是用來描述s和sr的準确性的一個參數。

  • 包括在lf-mmi中也有一些方法去解決泛化能力

MMI代碼分析

輸入資料的介紹,以及特征的轉化

train_mmi.sh:

輸入輸出檔案:

data/train_si84 輸入資料

data/lang 詞典等資訊

exp/tri2b_ali_si84 對齊

exp/tri2b_denlats_si84 lattice

exp/tri2b_mmi 輸出檔案

Usage: steps/train_mmi_sgmm2.sh <data> <lang> <ali> <denlats> <exp>
e.g.: steps/train_mmi_sgmm2.sh data/train_si84 
data/lang exp/tri2b_ali_si84 exp/tri2b_denlats_si84 exp/tri2b_mmi"
           

比較了兩個音子标号的文本是否是一樣的,不一樣的話就直接報錯停止

每個音子都唯一的對應于一個整數

utils/lang/check_phones_compatible.sh $lang/phones.txt $alidir/phones.txt || exit 1;
cp $lang/phones.txt $dir || exit 1;
           

data/feats.scp 每句話所對應的提取出來的二進制存儲的特征他的存放位置

alidir/{tree,final.mdl,ali.1.gz} 對齊時候的決策樹,模型,對齊的資訊。

denlatdir/lat.1.gz 存放詞圖

for f in $data/feats.scp $alidir/{tree,final.mdl,ali.1.gz} $denlatdir/lat.1.gz; do
  [ ! -f $f ] && echo "$0: no such file $f" && exit 1;
done
           

接下就是對于特征的轉換。

final.mat 是用來進行特征轉換的,

transform-feats,使用transform來進行特征轉換,為了解碼調用。之後可以對該步生成的ark檔案,進行解碼的操作,得到一個lattice檔案。可以參考部落格:傳送門

transform-feats final.mat ark:splice.ark ark:transform.ark

# Set up features

if [ -f $alidir/final.mat ]; then feat_type=lda; else feat_type=delta; fi
echo "$0: feature type is $feat_type"

case $feat_type in
  delta) feats="ark,s,cs:apply-cmvn $cmvn_opts --utt2spk=ark:$sdata/JOB/utt2spk scp:$sdata/JOB/cmvn.scp scp:$sdata/JOB/feats.scp ark:- | add-deltas ark:- ark:- |";;
  lda) feats="ark,s,cs:apply-cmvn $cmvn_opts --utt2spk=ark:$sdata/JOB/utt2spk scp:$sdata/JOB/cmvn.scp scp:$sdata/JOB/feats.scp ark:- | splice-feats $splice_opts ark:- ark:- | transform-feats $alidir/final.mat ark:- ark:- |"
    cp $alidir/final.mat $dir    
    ;;
  *) echo "Invalid feature type $feat_type" && exit 1;
esac

if [ ! -z "$transform_dir" ]; then
  echo "$0: using transforms from $ "
  [ ! -f $transform_dir/trans.1 ] && echo "$0: no such file $transform_dir/trans.1" \
    && exit 1;
  feats="$feats transform-feats --utt2spk=ark:$sdata/JOB/utt2spk ark,s,cs:$transform_dir/trans.JOB ark:- ark:- |"
else
  echo "$0: no fMLLR transforms."
fi
           

lattice boost

lats="ark:gunzip -c $denlatdir/lat.JOB.gz|"
if [[ "$boost" != "0.0" && "$boost" != 0 ]]; then
  lats="$lats lattice-boost-ali --b=$boost --silence-phones=$silphonelist $alidir/final.mdl ark:- 'ark,s,cs:gunzip -c $alidir/ali.JOB.gz|' ark:- |"
fi
           

lattice-boost-ali.cc

通過lattice中每個弧上的幀錯誤來提高圖的相似性(降低圖的成本)。

有助于鑒别訓練,例如Boost MMI。它主要是修改lattice

此版本采用對齊形式的引用。

需要模型(隻是transition)來将pdf_id轉換為音子

使用–silence phones選項,lattice中出現的這些靜音音子始終被指定為零錯誤。或者使用–max silence error選項,最多每幀的錯誤計數(–max silence error=1相當于未指定–silence phones)。

區分性訓練和mmi互資訊最大似然估計MLE(缺點)區分性訓練DT和最大互資訊MMI區分性訓練缺點LatticeMMI的問題:MMI代碼分析

其中這個檔案主要是調用 LatticeBoost()函數對lattice進行了修改

LatticeBoost()

傳送門

po.Register("b", &b, 
                 "Boosting factor (more -> more boosting of errors / larger margin)");
     po.Register("max-silence", &max_silence_error,
                 "Maximum error assigned to silence phones [c.f. --silence-phones option]."
                 "0.0 -> original BMMI paper, 1.0 -> no special silence treatment.");
     po.Register("silence-phones", &silence_phones_str,
                 "Colon-separated list of integer id's of silence phones, e.g. 46:47");
           
bool LatticeBoost	(	const TransitionModel & 	trans,
const std::vector< int32 > & 	alignment,
const std::vector< int32 > & 	silence_phones,
BaseFloat 	b,
BaseFloat 	max_silence_error,
Lattice * 	lat 
)	
           

提高 LM語言模型的機率,通過b,也就是将每幀的錯誤的數量乘以b,然後添加到lattice圖中弧的代價中去

如果特定幀上的特定transion_id所對應的音子與該幀alignment對齊的音子不比對的話,則存在幀錯誤。

參數中TransitionModel 的作用是将 在lattice輸入端中 transition-ids 映射成 phones;

在“silence_phones”中出現的phones被特殊處理,我們用minimum of f 或max_silence_error來替換一個幀的幀錯誤f(0或1)。

對于正常情況,max_silence_error将為零。成功時傳回true,如果存在某種不比對,則傳回false。輸入時,silence_phones必須分類和唯一。

{
   TopSortLatticeIfNeeded(lat);
 
   // get all stored properties (test==false means don't test if not known).
   uint64 props = lat->Properties(fst::kFstProperties,
                                  false);
   //對靜音的音子要確定裡面的音子沒有重複
   KALDI_ASSERT(IsSortedAndUniq(silence_phones));
   //max_silence_erroe的值為0-1之間
   KALDI_ASSERT(max_silence_error >= 0.0 && max_silence_error <= 1.0);
   vector<int32> state_times;
   int32 num_states = lat->NumStates();
   //num_frames 有多少幀,state_times數組指的是每個state對應的是哪一幀
   int32 num_frames = LatticeStateTimes(*lat, &state_times);
   KALDI_ASSERT(num_frames == static_cast<int32>(alignment.size()));
   for (int32 state = 0; state < num_states; state++) {
     int32 cur_time = state_times[state];
     for (fst::MutableArcIterator<Lattice> aiter(lat, state); !aiter.Done();
          aiter.Next()) {
       LatticeArc arc = aiter.Value();
       if (arc.ilabel != 0) {  // Non-epsilon arc
         if (arc.ilabel < 0 || arc.ilabel > trans.NumTransitionIds()) {
           KALDI_WARN << "Lattice has out-of-range transition-ids: "
                      << "lattice/model mismatch?";
           return false;
         }
         //phone 是指這條弧對應的輸入transition_id所對應的音子
         //ref_phone 是指目前state->目前的幀cur_time->transition_id->phone音子
         int32 phone = trans.TransitionIdToPhone(arc.ilabel),
             ref_phone = trans.TransitionIdToPhone(alignment[cur_time]);
         BaseFloat frame_error;
         //如果弧輸入對應的音子和目前state對應的音子一樣的話,就是正确的,error=0
         if (phone == ref_phone) {
           frame_error = 0.0;
         } else { // an error...
         //接下就是判斷是否是靜音音子,如果損失靜音音子錯誤的指就是預設的max_silence_error,否則就為1
           if (std::binary_search(silence_phones.begin(), silence_phones.end(), phone))
             frame_error = max_silence_error;
           else
             frame_error = 1.0;
         }
         BaseFloat delta_cost = -b * frame_error; // negative cost if
         // frame is wrong, to boost likelihood of arcs with errors on them.
         // Add this cost to the graph part.
         arc.weight.SetValue1(arc.weight.Value1() + delta_cost);
         aiter.SetValue(arc);
       }
     }
   }
   // All we changed is the weights, so any properties that were
   // known before, are still known, except for whether or not the
   // lattice was weighted.
   lat->SetProperties(props,
                      ~(fst::kWeighted|fst::kUnweighted));
 
   return true;
 }
           

接下是MMI的核心部分:

if [ $stage -le $x ]; then
    $cmd JOB=1:$nj  \
      //test指令用于檢查某個條件是否成立 -s 檔案名,如果檔案存在且至少有一個字元則為真
      test -s $dir/den_acc.$x.JOB.gz -a -s $dir/num_acc.$x.JOB.gz '||' \
      //使用一個新的模型去更改lattice上的聲學分數。
      sgmm2-rescore-lattice --speedup=true "$gselect_opt" $spkvecs_opt $dir/$x.mdl "$lats" "$feats" ark:- \| \
      lattice-to-post --acoustic-scale=$acwt ark:- ark:- \| \
      sum-post --drop-frames=$drop_frames --merge=$cancel --scale1=-1 \
      ark:- "ark,s,cs:gunzip -c $alidir/ali.JOB.gz | ali-to-post ark:- ark:- |" ark:- \| \
      sgmm2-acc-stats2 "$gselect_opt" $spkvecs_opt $dir/$x.mdl "$feats" ark,s,cs:- \
      "|gzip -c >$dir/num_acc.$x.JOB.gz" "|gzip -c >$dir/den_acc.$x.JOB.gz" || exit 1;

    n=`echo $dir/{num,den}_acc.$x.*.gz | wc -w`;
    [ "$n" -ne $[$nj*2] ] && \
      echo "Wrong number of MMI accumulators $n versus 2*$nj" && exit 1;
    num_acc_sum="sgmm2-sum-accs - ";
    den_acc_sum="sgmm2-sum-accs - ";
    for j in `seq $nj`; do 
      num_acc_sum="$num_acc_sum 'gunzip -c $dir/num_acc.$x.$j.gz|'"; 
      den_acc_sum="$den_acc_sum 'gunzip -c $dir/den_acc.$x.$j.gz|'"; 
    done
    $cmd $dir/log/update.$x.log \
     sgmm2-est-ebw $update_opts $dir/$x.mdl "$num_acc_sum |" "$den_acc_sum |" \
      $dir/$[$x+1].mdl || exit 1;
    rm $dir/*_acc.$x.*.gz 
  fi

           

sgmm2-rescore-lattice.cc :

Replace the acoustic scores on a lattice using a new model

使用一個新的模型去更改lattice上的聲學分數。

參數為模型,更改前的lattice,聲學特征,更改後的lattice

Usage: sgmm2-rescore-lattice [options] <model-in> <lattice-rspecifier> 
<feature-rspecifier> <lattice-wspecifier>
e.g.: sgmm2-rescore-lattice 1.mdl ark:1.lats scp:trn.scp ark:2.lats
           
kaldi::ParseOptions po(usage);
     po.Register("old-acoustic-scale", &old_acoustic_scale,
                 "Add the current acoustic scores with some scale.");
     po.Register("log-prune", &log_prune,
                 "Pruning beam used to reduce number of exp() evaluations.");
     po.Register("spk-vecs", &spkvecs_rspecifier, "Speaker vectors (rspecifier)");
     po.Register("utt2spk", &utt2spk_rspecifier,
                 "rspecifier for utterance to speaker map");
     po.Register("gselect", &gselect_rspecifier,
                 "Precomputed Gaussian indices (rspecifier)預計算高斯指數");
     po.Register("speedup", &speedup,
                 "If true, enable a faster version of the computation that "
                 "saves times when there is only one pdf-id on a single frame "
                 "by only sometimes (randomly) computing the probabilities, and "
                 "then scaling them up to preserve corpus-level diagnostics.");
           
std::string model_filename = po.GetArg(1),
         lats_rspecifier = po.GetArg(2),
         feature_rspecifier = po.GetArg(3),
         lats_wspecifier = po.GetArg(4);
 
     AmSgmm2 am_sgmm;
     TransitionModel trans_model;
     {
       bool binary;
       Input ki(model_filename, &binary);
       trans_model.Read(ki.Stream(), binary);
       am_sgmm.Read(ki.Stream(), binary);
     }
 
     RandomAccessInt32VectorVectorReader gselect_reader(gselect_rspecifier);
     RandomAccessBaseFloatVectorReaderMapped spkvecs_reader(spkvecs_rspecifier,
                                                            utt2spk_rspecifier);
     RandomAccessBaseFloatMatrixReader feature_reader(feature_rspecifier);
     // Read as compact lattice
     SequentialCompactLatticeReader compact_lattice_reader(lats_rspecifier);
     // Write as compact lattice.
     CompactLatticeWriter compact_lattice_writer(lats_wspecifier);
 
     int32 num_done = 0, num_err = 0;
     for (; !compact_lattice_reader.Done(); compact_lattice_reader.Next()) {
       std::string utt = compact_lattice_reader.Key();
       //查找這句所對應的特征
       if (!feature_reader.HasKey(utt)) {
         KALDI_WARN << "No feature found for utterance " << utt;
         num_err++;
         continue;
       }
 
       CompactLattice clat = compact_lattice_reader.Value();
       compact_lattice_reader.FreeCurrent();
       if (old_acoustic_scale != 1.0)
         //通過old_acoustic_scale這個聲學尺度,對clat這個lattice上的弧的權重以及final_weight進行縮放
         fst::ScaleLattice(fst::AcousticLatticeScale(old_acoustic_scale), &clat);
       //utt這句話所對應的特征
       const Matrix<BaseFloat> &feats = feature_reader.Value(utt);
 
       // Get speaker vectors
       Sgmm2PerSpkDerivedVars spk_vars;
       if (spkvecs_reader.IsOpen()) {
         if (spkvecs_reader.HasKey(utt)) {
           spk_vars.SetSpeakerVector(spkvecs_reader.Value(utt));
           am_sgmm.ComputePerSpkDerivedVars(&spk_vars);
         } else {
           KALDI_WARN << "Cannot find speaker vector for " << utt;
           num_err++;
           continue;
         }
       }  // else spk_vars is "empty"
      //從這裡可以看出高斯指數,特征的每一幀對應一個高斯指數
       if (!gselect_reader.HasKey(utt) ||
           gselect_reader.Value(utt).size() != feats.NumRows()) {
         KALDI_WARN << "No Gaussian-selection info available for utterance "
                    << utt << " (or wrong size)";
         num_err++;
         continue;
       }
       const std::vector<std::vector<int32> > &gselect =
           gselect_reader.Value(utt);
 
       DecodableAmSgmm2 sgmm2_decodable(am_sgmm, trans_model, feats,
                                        gselect, log_prune, &spk_vars);
 
       if (!speedup) {
         if (kaldi::RescoreCompactLattice(&sgmm2_decodable, &clat)) {
           compact_lattice_writer.Write(utt, clat);
           num_done++;
         } else num_err++;
       } else {
         BaseFloat speedup_factor = 100.0; 
         if (kaldi::RescoreCompactLatticeSpeedup(trans_model, speedup_factor,
                                                 &sgmm2_decodable,
                                                 &clat)) {
           compact_lattice_writer.Write(utt, clat);
           num_done++;
         } else num_err++;
       }        
     }
           

最後一次更新時間2019-11-14 ,之後繼續更新