解碼圖構建方法(實驗部分)
這裏我們一步一步地解釋圖的構建方法,以及與之相關的某些數據準備階段。
這種方法的大部分細節不會硬編碼到我們的工具中; 我們正在解釋它目前是如何做的。 如果這個部分很混亂,那麼最好的補救辦法可能就是閱讀Mohri等人的“Speech Recognition with Weighted Finite-State Transducers”。 要注意:這張紙是相當長的,閱讀它至少需要幾個小時,對於那些尚不熟悉FST的人。 另一個好的資源是OpenFst網站,它將提供更多相關信息比如符號表。
準備初始符號表
我們需要準備OpenFst符號表word.txt和phones.txt。
這些將整數id分配給系統中的所有單詞和phone。
請注意,OpenFst對epsilon的保留符號爲零。
示例WSJ任的符號表是:
## head words.txt
<eps> 0
!SIL 1
<s> 2
</s> 3
<SPOKEN_NOISE> 4
<UNK> 5
<NOISE> 6
!EXCLAMATION-POINT 7
"CLOSE-QUOTE 8
## tail -2 words.txt
}RIGHT-BRACE 123683
#0 123684
## head data/phones.txt
<eps> 0
SIL 1
SPN 2
NSN 3
AA 4
AA_B 5
word.txt文件包含單個消歧符號“#0”(用於G.fst輸入上的epsilon)。 這是最後編號的單詞。 如果您的詞典包含單詞“#0”,請小心。 phones.txt文件不包含消歧符號,但在創建L.fst之後,我們將創建一個文件phones_disambig.txt,該文件具有消歧符號(這對調試很有用)。
準備詞典L
首先,我們創建一個文本格式的詞典,最初沒有消歧符號。我們的C ++工具永遠不會與此交互,它只會被創建詞典FST的腳本使用。 我們的WSJ詞彙的一小部分是:
## head data/lexicon.txt
!SIL SIL
<s>
</s>
<SPOKEN_NOISE> SPN
<UNK> SPN
<NOISE> NSN
!EXCLAMATION-POINT EH2_B K S K L AH0 M EY1 SH AH0 N P OY2 N T_E
"CLOSE-QUOTE K_B L OW1 Z K W OW1 T_E
phone上的開始,結束和重音標記(例如T_E或AH0)是我們的WSJ方法特有的,就我們的工具包而言,它們被視爲不同的phone(然而,對於這個設置我們做特殊處理;在樹的構建過程中讀取roots文件)。
請注意,我們允許有空語音表示的單詞。 該詞典將用於在訓練中創建L.fst(不含消歧符號)。 我們還創建一個消歧符號的詞典,用於解碼圖創建。 此文件的摘錄在這裏:
# [from data/lexicon_disambig.txt]
!SIL SIL
<s> #1
</s> #2
<SPOKEN_NOISE> SPN #3
<UNK> SPN #4
<NOISE> NSN
...
{BRACE B_B R EY1 S_E #4
{LEFT-BRACE L_B EH1 F T B R EY1 S_E #4
此文件由腳本創建; 該腳本輸出它不得不添加的消歧符號,並用於創建符號表phones_disambig.txt。 這與phones.txt是一樣的,但它也包含消歧符號#0,#1,#2等的整數id(#0是來自G.fst的特殊消歧符號,但將被“passed through”L.fst通過自循環)。 文件phone_disambig.txt中間的一部分是:
ZH_E 338
ZH_S 339
#0 340
#1 341
#2 342
#3 343
數字是如此之大,因爲在這個(WSJ)語聊中,我們向phone添加了重音和位置信息。
注意,用於空字(即<s>和</s>)的消歧符號必須與用於正常詞的那些不同,因此本例中的“正常”消歧符號從#3開始。
將沒有消歧符號的詞典轉換爲FST的命令是:
scripts/make_lexicon_fst.pl data/lexicon.txt 0.5 SIL | \
fstcompile --isymbols=data/phones.txt --osymbols=data/words.txt \
--keep_isymbols=false --keep_osymbols=false | \
fstarcsort --sort_type=olabel > data/L.fst
這裏,腳本make_lexicon_fst.pl創建FST的文本表示。 0.5是靜音概率(即在句子開始處,每個單詞之後,我們以概率0.5輸出靜音;分配給無靜音的概率質量爲1.0-0.5 = 0.5,本示例中其餘的命令涉及 將FST轉換爲編譯格式; fstarcsort是必要的,因爲我們稍後將作compose。
詞典的結構大致如人們期望的那樣。 有一個狀態(“循環狀態”)是終止狀態。 有一個起始狀態有兩個過渡到循環狀態:一個有靜默和一個沒有。 從循環狀態,對應於每個單詞都有一個轉換,該字是轉換時的輸出符號; 輸入符號是該單詞的第一個phone。 對於組合的效率和最小化的有效性,重要的是輸出符號應儘可能早(即開始時不是單詞的結尾)。 在每個單詞的末尾,爲了處理可選的靜音,對應於最後一個phone的轉換有兩種形式:一種到循環狀態,一種轉換爲“靜默狀態”,它們轉換到循環狀態。 我們不要在靜音中放置可選的靜音,我們將它定義爲只有一個phone是靜音phone的話。
用消歧符號創建詞典只是稍微複雜一點。 問題是我們必須將自循環添加到詞典中,以便可以通過詞典傳遞來自G.fst的消歧符號#0。 我們使用程序fstaddselfloops(c.f.添加和刪除消歧義符號)來執行此操作,儘管我們可以在腳本make_lexicon_fst.pl中輕鬆完成“手動”操作。
phone_disambig_symbol=`grep \#0 data/phones_disambig.txt | awk '{print $2}'`
word_disambig_symbol=`grep \#0 data/words.txt | awk '{print $2}'`
scripts/make_lexicon_fst.pl data/lexicon_disambig.txt 0.5 SIL | \
fstcompile --isymbols=data/phones_disambig.txt --osymbols=data/words.txt \
--keep_isymbols=false --keep_osymbols=false | \
fstaddselfloops "echo $phone_disambig_symbol |" "echo $word_disambig_symbol |" | \
fstarcsort --sort_type=olabel > data/L_disambig.fst
程序fstaddselfloops不是原始的OpenFst命令行工具之一,它是我們自己的(我們有一些這樣的程序)之一。
準備語言模型G
語言模型G大部分是一個接受者(即每個圓弧上的輸入和輸出符號相同),其中以字爲其符號。
除了是消歧符號#0,它只出現在輸入端。 假設輸入是Arpa文件,我們使用Kaldi程序arpa2fst將其轉換爲FST。
程序arpa2fst輸出帶嵌入符號的FST。 在Kaldi,我們通常使用沒有嵌入符號的FST(即我們使用單獨的符號表)。
只需運行arpa2fst,我們必須採取的步驟如下:
我們必須從FST中刪除嵌入的符號(並依賴於磁盤上的符號表)。
我們必須確保在語言模型中沒有超出詞彙量的單詞
我們必須刪除起始和結尾符號的“非法”序列,例如
<s>後跟</ s>,因爲這些導致L o G不可確定。
我們必須用特殊的消歧符號#0替換輸入端的epsilons。
實際腳本的略簡化版本如下所示:
gunzip -c data_prep/lm.arpa.gz | \
arpa2fst --disambig-symbol=#0 \
--read-symbol-table=data/words.txt - data/G.fst
最後一個命令(fstisstochastic)是一個診斷步驟(請參閱Preserving stochasticity and testing it)。 在一個典型的例子中,它打印出數字:
9.14233e-05 -0.259833
第一個數字很小,所以它證實沒有狀態的弧的概率加上最終狀態顯着小於1。 第二個數字是重要的,這意味着有些狀態具有“太多”的概率(FST中權重的數值通常可以解釋爲負對數概率)。 對於具有回退的語言模型的FST表示,有一些具有“太多”概率的狀態是正常的。 在後續的構圖步驟中,我們將驗證這一非隨機性並沒有比開始時更糟糕。
所得到的FST G.fst當然只用於測試時間。 在訓練時間內,我們使用從訓練單詞序列生成的線性FST,但這是在Kaldi程序中完成的,而不是在腳本級別。
準備LG
當用G組合L時,我們概括地遵循一個相當標準的方法,即我們計算min(det(L o G))。 命令行如下:
fsttablecompose data/L_disambig.fst data/G.fst | \
fstdeterminizestar --use-log=true | \
fstminimizeencoded | fstpushspecial | \
fstarcsort --sort-type=ilabel > somedir/LG.fst
這裏OpenFst的算法有一些小的區別。 我們使用我們的命令行工具“fsttablecompose”實現的更有效的組合算法(參見Composition)。 我們的確定是一種也可以通過命令行程序fstdeterminizestar實現的算法,同時去空邊。 選項-use-log = true要求程序首先將FST映射到log半環; 這保留隨機性(在log半環); Preserving stochasticity and testing it。
我們通過程序“fstminimizeencoded”進行最小化。 這與OpenFst最小化算法的版本大體相同,適用於加權接收器; 唯一相關的變化是避免pushing weights,從而保持隨機性(詳見Minimization)。
程序“fstpushspecial”類似於OpenFst的“fstpush”程序,但如果權重不等於1,則可以確保所有狀態“總計”爲相同的值(可能與一不同),而不是嘗試推送 “額外”權重到圖表的開頭或結尾。 這具有永遠不會失敗的優點(“Fstpush”可以失敗或循環很長時間,如果FST“總和爲”無窮大“); 它也快得多。 有關更詳細的文檔,請參閱push-special.cc。
“fstarcsort”階段對弧進行排序以有助於稍後組合操作更快速。
準備CLG
爲了獲得輸入爲上下文相關phone的轉換器,我們需要準備一個叫做CLG的FST,它與C o L o G相同,其中L和G是詞典和語言模型,C代表語音上下文。 對於三音系統,C的輸入符號將爲a / b / c(即三元phone),輸出符號將是單個phone(例如a或b或c)。 有關語音上下文窗口的更多信息,請參閱Phonetic context windows,以及我們如何產生爲不同的上下文大小。 首先,我們將描述如何創建上下文FST C,如果我們自己做它並且正常組合(爲了效率和可擴展性的原因,我們的腳本實際上不是這樣工作)。
製作上下文轉換器
在本節中,我們將解釋如何將C作爲獨立的FST獲得。
C的基本結構是它對所有可能的大小爲N-1的phone窗口(c.f.語音上下文窗口,三音素中N = 3)的狀態。 第一個狀態,意思是開始的話語,只會對應於N-1個子句。 每個狀態都有每個phone的轉換(現在讓我們忘記自循環)。 作爲通用示例,狀態a / b在輸出上具有c的轉換,並且輸入上的a / b / c轉換到狀態b / c。 話語開始和結束時都有特殊情況。
在話語開始時,假設狀態爲<eps> / <eps>,輸出符號爲a。 通常,輸入符號將是<eps> / <eps> / a。 但這並不代表phone,因爲(假設P = 1),中心元素是<eps>,不是phone。 在這種情況下,我們讓弧的輸入符號爲#-1,這是我們爲此目的引入的特殊符號(我們在這裏不使用epsilon作爲標準方法,因爲當有空字時可能會導致不可確定性)。
話語結束有點複雜。上下文FST在右側(其輸出端)具有在語句結尾處出現的特殊符號$。考慮三元phone的情況。在句子結束後,看到所有符號後,我們需要刷出最後一個三元phone(例如a / b / <eps>,其中<eps>表示未定義的上下文)。這樣做的自然方法是在其輸入上具有/ b / <eps>的轉換,其輸出上的<eps>,從狀態a / b到最終狀態(例如b / <eps>或a特殊最終狀態)。但是,這對於組合來說是低效的,因爲如果不是話語的結束,我們必須先探索這些轉變,才能將它們修剪掉。相反,我們使用$作爲終止符號,並確保它在LG的每個路徑的末尾出現一次。然後我們用C的輸出$替換<eps>。一般來說,$的重複次數等於N-P-1。爲了避免麻煩,必須確定要添加多少個後續符號到LG,我們只允許它在話語結束時接受任何數量的這種符號。這通過函數AddSubsequentialLoop()和命令行程序fstaddsubsequentialloop來實現。
如果我們自己想要C,我們首先需要一個消歧符號清單; 我們還需要找出我們可以用於後續符號的未使用的符號id,如下所示:
grep '#' data/phones_disambig.txt | awk '{print $2}' > $dir/disambig_phones.list
subseq_sym=`tail -1 data/phones_disambig.txt | awk '{print $2+1;}'
然後,我們可以使用以下命令創建C(但是,關於fstcomposecontext,請參見下文),因爲實踐中不執行此操作,因爲效率低下)。
fstmakecontextfst --read-disambig-syms=$dir/disambig_phones.list \
--write-disambig-syms=$dir/disambig_ilabels.list data/phones.txt $subseq_sym \
$dir/ilabels | fstarcsort --sort_type=olabel > $dir/C.fst
程序fstmakecontextfst需要phone列表,消歧符號列表和後續符號的標識。 除了C.fst之外,它還會寫出解釋C.fst左邊符號的文件“ilabels”(請參閱The ilabel_info object)。 與LG的組合可以做到如下:
fstaddsubsequentialloop $subseq_sym $dir/LG.fst | \
fsttablecompose $dir/C.fst - > $dir/CLG.fst
爲了打印出C.fst和使用相同符號索引“ilabels”的任何內容,我們可以使用以下命令創建一個合適的符號表:
fstmakecontextsyms data/phones.txt $dir/ilabels > $dir/context_syms.txt
This command knows about the "ilabels" format (The ilabel_info object). An example random path through the CLG fst (for Resource Management), printed out with this symbol table, is as follows:
該命令知道“ilabels”格式(The ilabel_info object)。 通過CLG fst(用於資源管理),示例隨機路徑與該符號表打印出來,如下所示:
## fstrandgen --select=log_prob $dir/CLG.fst | \
fstprint --isymbols=$dir/context_syms.txt --osymbols=data/words.txt -
0 1 #-1 <eps>
1 2 <eps>/s/ax SUPPLIES
2 3 s/ax/p <eps>
3 4 ax/p/l <eps>
4 5 p/l/ay <eps>
5 6 l/ay/z <eps>
6 7 ay/z/sil <eps>
7 8 z/sil/<eps> <eps>
8
動態組合C
在正常構圖中,我們使用一個程序fstcomposecontext,它動態地創建所需的狀態和C的弧,而不浪費地創建它。 命令行是:
fstcomposecontext --read-disambig-syms=$dir/disambig_phones.list \
--write-disambig-syms=$dir/disambig_ilabels.list \
$dir/ilabels < $dir/LG.fst >$dir/CLG.fst
If we had different context parameters N and P than the defaults (3 and 1), we would supply extra options to this program. This program writes the file "ilabels" (see ) which interprets the input symbols of CLG.fst. The first few lines of an ilabels file from the recipe are as follows:
相比於默認值(3和1),如果我們有不同的上下文參數N和P,我們將爲此程序提供額外的選項。 該程序寫入解釋CLG.fst的輸入符號的文件“ilabels”(請參閱“The ilabel_info object”)。 Resource Management中的ilabels文件的前幾行如下:
65028 [ ]
[ 0 ]
[ -49 ]
[ -50 ]
[ -51 ]
[ 0 1 0 ]
[ 0 1 1 ]
[ 0 1 2 ]
...
數字65028是文件中的元素數。 像[-49]這樣的就是消歧符號; 像[0 1 2]這樣的行表示聲音上下文窗口; 前兩個條目是用於epsilon(從未使用)的[],[0]是用於在C開頭代替epsilon使用打印形式#-1的特殊消歧符號,以確保可確定性。
減少上下文相關輸入符號的數量
在創建CLG.fst之後,有一個可選的構圖階段可以減小其大小。 我們使用從決策樹和HMM拓撲信息中得出的程序make-ilabel-transducer,他是上下文相關phone的子集將對應於相同的編譯圖,因此可以合併(我們選擇任何一個子集的元素,並且轉換所有上下文窗口到那個窗口)。 這是一個類似於HTK邏輯到物理映射的概念。 命令是:
make-ilabel-transducer --write-disambig-syms=$dir/disambig_ilabels_remapped.list \
$dir/ilabels $tree $model $dir/ilabels.remapped > $dir/ilabel_map.fst
這個程序需要樹和模型; 它輸出一個名爲“ilabels.remapped”的新的ilabel_info對象; 這與原始“ilabels”文件的格式相同,但行數較少。 FST“ilabel_map.fst”和CLG.fst做組合,並重新映射標籤。 做到這一點後,我們確定化和最小化,所以我們可以馬上實現任何尺寸的縮減:
fstcompose $dir/ilabel_map.fst $dir/CLG.fst | \
fstdeterminizestar --use-log=true | \
fstminimizeencoded > $dir/CLG2.fst
對於一般的設置,這個階段實際上並沒有減少圖的大小(通常爲5%到20%的減少),在任何情況下,它只是通過這種機制減少的中間構圖階段的大小。 但是,對於具有更廣泛背景的系統,節省可能會變得重要。
製作H轉換器
在傳統的FST製作中中,H轉換器是在其輸出上具有上下文相關phone,並且在其輸入上具有表示聲學狀態的符號。在我們的例子中,H(或HCLG)的輸入上的符號不是聲學狀態(在我們的術語中是pdf-id),而是我們稱之爲transition-id的東西(參見Integer identifiers used by TransitionModel)。transition-id是pdf-id加上一些包括phone在內的其他信息進行編碼。每個transition-id可以映射到pdf-id。我們創建的H轉換器不對自環進行編碼。這些稍後由單獨的程序添加。 H轉換器具有初始和最終狀態,並且從該狀態到每個條目都有一個轉換,但ilabel_info對象中的第零個(the ilabels file,見上文)。上下文相關phone的轉換轉到相應HMM的結構(缺少自環),然後返回到開始狀態。對於正常拓撲,HMM的這些結構只是三個弧的線性序列。對於每個消歧符號(#-1,#0,#1,#2,#3等),H也在初始狀態上具有自循環。
H轉換器的腳本部分(我們稱之爲Ha,因爲它在這一點上缺少自循環),是:
make-h-transducer --disambig-syms-out=$dir/disambig_tstate.list \
--transition-scale=1.0 $dir/ilabels.remapped \
$tree $model > $dir/Ha.fst
有一個可選參數來設置轉移因子; 在我們目前的訓練腳本中,這個因子是1.0。 這個比例隻影響到與自循環概率無關的轉換部分,而在正常拓撲(Bakis模型)中它完全沒有影響; 請參閱Scaling of transition and acoustic probabilities以獲得更多的解釋。 除了FST之外,該程序還寫入一個消歧符號列表,稍後必須刪除。
製作HCLG
製作最終圖HCLG的第一步是製作缺少自循環的HCLG。 我們當前腳本中的命令如下所示:
fsttablecompose $dir/Ha.fst $dir/CLG2.fst | \
fstdeterminizestar --use-log=true | \
fstrmsymbols $dir/disambig_tstate.list | \
fstrmepslocal | fstminimizeencoded > $dir/HCLGa.fst
在這裏,CLG2.fst是CLG的版本,縮略符號集(“邏輯”三音素,HTK術語)。 我們刪除消歧符號和任何easy-to-remove epsilons(請參閱Removing epsilons),在最小化之前; 我們的最小化算法是避免推送符號和權重(從而保留隨機性),並接受非確定性輸入(見Minimization)。
向HCLG添加自環
通過以下命令完成向HCLG添加自環:
add-self-loops --self-loop-scale=0.1 \
--reorder=true $model < $dir/HCLGa.fst > $dir/HCLG.fst
有關如何應用0.1 self-loop-scale的說明,請參見Scaling of transition and acoustic probabilities(注意它也影響非自環概率)。有關“重新排序”選項的說明,請參閱Reordering transitions; “重新排序”選項提高了解碼速度,但與kaldi解碼器不兼容。 add-self-loops程序不僅添加自環;它可能還必須複製狀態並添加epsilon轉換,以確保以一致的方式添加自環。在重新排序轉換中,在Reordering transitions詳述這個問題。這是唯一不保存隨機性的構圖階段;它不保存它,因爲自環的因子不是1,所以程序fstisstochastic應該給所有的G.fst,LG.fst,CLG.fst和HCLGa.fst相同的輸出,但不是HCLG.fst 。在自環階段之後我們不再確定了;這樣會失敗,因爲我們已經刪除了消歧符號。無論如何,這很慢,我們認爲在這一點上確定和最小化沒有什麼進一步的。