1. 程式人生 > >kaldi 學習筆記-三音素訓練1(Decision Tree)

kaldi 學習筆記-三音素訓練1(Decision Tree)

開始介紹kaldi三音素訓練大致流程。本文主要介紹決策樹(Decision Tree)部分。

1. acc-tree-stats

Usage:  acc-tree-stats [options] <model-in> <features rspecifier> <alignments-rspecifier> <tree-accs-out>

輸入:模型,特徵,對齊序列
輸出:決策樹的統計量

首先讀入Transition Model,從 contex_indep.csl中讀取要訓練的所有三音素, 儲存在 AccumulateTreeStatsInfo 物件的ci_phones中。然後對於每幀語音特徵和與其相應的對齊序列,進行統計量的計算。呼叫以下函式:

        AccumulateTreeStats(trans_model,
                            acc_tree_stats_info,
                            alignment,
                            mat,
                            &tree_stats);

該函式累計統計量,儲存在BuildTreeStatsType中。
BuildTreeStatsType也就是 vector <EventType, GaussClusterable*> stats


EventTpye也是一個vector,裡面存的是一個pair<EventKeyType, EventValueType>, EventKeyType是一個int,表示三音素位置(0,1,2)或者HMM的state(-1),EventKeyValue也是int,表示三音素的phone-ID或者對應HMM(-1)的state-id(通常為0,1,2 )。
例如: EventType e = { (-1, 1), (0, 10), (1, 11), (2,12) }中,表示三音素為(10,11,12)中第一個HMM state。
對於每幀語音,由其對齊序列可以得知transition-id,也就知道了對應哪個phone。因此我們可以統計phone出現的次數以及該條特徵的均值方差等統計量存放在GaussClusterable的結構體中。因此,對所有的特徵資料、對齊資料執行這個函式後,我們得到了所有的EventType和每個EventType對應的統計量,我們之後用它來進行決策樹,問題集的構建。

2. cluster-phones

Usage:  cluster-phones [options] <tree-stats-in> <phone-sets-in> <clustered-phones-out>

輸入:決策樹統計量,音素集
輸出:問題的文字表示

該命令的核心函式如下,根據統計量stats,問題集phones,輸出問題集phone_sets_out。pdf-class-list和P表示用於過濾的phone或者state。

AutomaticallyObtainQuestions(stats,
                             phone_sets,
                             pdf_class_list,
                             P,
                             &phone_sets_out);

pdf-class-list = 1表示只用中間狀態的統計量,其他都拋棄。

  BuildTreeStatsType retained_stats;
  FilterStatsByKey(stats, kPdfClass, all_pdf_classes,
                   true,  // retain only the listed positions
                   &retained_stats);  

P = 1表示中間音素,我們累加某一固定中間音素的所有三音素的統計量
得到split_stats

std::vector<BuildTreeStatsType> split_stats;  // split by phone.
SplitStatsByKey(retained_stats, P, &split_stats);

依照sets.int 音素集檔案將每行共享的統計量累加,再呼叫TreeCluster用Kmeans演算法依照統計量對所有三音素進行聚類。

 TreeCluster(summed_stats_per_set,
              summed_stats_per_set.size(),  // max-#clust is all of the points.
              NULL,  // don't need the clusters out.
              &assignments,
              &clust_assignments,
              &num_leaves,
              topts);

最後如下用ObtainSetsOfPhones輸出樹中每個節點所含的phones。這裡包含葉子節點和它的parent(非葉子節點),因此同一個phone可能在多個節點(多個問題)出現。注意kaldi裡一個問題就是一個phone set。

  ObtainSetsOfPhones(phone_sets,
                     assignments,
                     clust_assignments,
                     num_leaves,
                     questions_out);

3. compile-question

Usage:  compile-questions [options] <topo> <questions-text-file> <questions-out>

輸入:拓撲結構topo,question.txt
輸出:question.qst

分為兩個部分,音素位置相關的問題 和 HMM狀態位置的問題。

當key為0,1,2時,問題是基於音素位置的問題,即對三音素中的每個音素分別問問題.

kaldi裡用Questions 和 QuestionsForKey 這兩個類來表示。Questions裡面為音素位置和QuestionsForKey,QuestionsForKey為問題即一些音素集,初始化均為question.txt。

當key為-1時,問題是基於HMM的某個狀態的,這和HMM的狀態數目有關,通常為三個,得到的問題集為[[0],[0,1]], 如果為5個,則為[[0],[0,1],[0,1,2], [0,1,2,3]].

4. build-tree

Usage:  build-tree [options] <tree-stats-in> <roots-file> <questions-file> <topo-file> <tree-out>

輸入:關於tree的統計量,root檔案,問題集,topo檔案
輸出:決策樹tree

主要呼叫如下函式

   to_pdf = BuildTree(qo,
                       phone_sets,
                       phone2num_pdf_classes,
                       is_shared_root,
                       is_split_root,
                       stats,
                       thresh,
                       max_leaves,
                       cluster_thresh,
                       P);

qo是問題集,phone_set是由roots檔案得到,roots檔案裡說明了是否要share相同決策樹根結點和是否需要進一步進行劃分的的phone。

該函式由roots檔案中的所有音素集首先用GetStubMap()遞迴構初始的決策樹,總的來說GetStubMap()對每一個音素集建立一個初始的葉子結點,一個音素集就是roots.int中的一行中的音素的集合,每個節點其實都是一個小決策樹的樹根,之後會進一步由這個葉子節點劃分。

EventMap *tree_split = SplitDecisionTree(*tree_stub,
filtered_stats, q_opts, thresh, max_leaves,&num_leaves,&impr, &smallest_split);

首先對於EvenType的每一個key(-1,0,1,2),在該key對應的問題集中(由q_opts給出每個key的問題集)找到一個問題,使得對葉子結點劃分後獲得的似然提升最大。