kaldi中的數據準備

數據準備

譯者:([email protected])  水平有限,如有錯誤請多包涵。   @wbglearn校對。

介紹

在運行完示例腳本後(見Kaldi tutorial),你可能會想用自己的數據在Kaldi上跑一下。本節主要講述如何準備相關數據。我們假設本頁的讀者使用的是最新版本的示例腳本(即在腳本目錄下被命名爲s5的那些,例如egs/rm/s5)。另外,除了閱讀本頁所述內容外,你還可以查看腳本目錄下的那些數據準備相關的腳本。(譯者:結合起來看更易理解。) 在頂層的run.sh 腳本(例如 egs/rm/s5/run.sh)中,最前面的幾行命令都是和數據準備相關的,代表數據準備的不同步驟。子目錄local/下的腳本都是和數據集相關的。例如,Resource Management(RM) 數據集相應的腳本就是local/rm_data_prep.sh。對RM數據集來說,這幾行數據準備的命令爲:

local/rm_data_prep.sh /export/corpora5/LDC/LDC93S3A/rm_comp || exit 1;

utils/prepare_lang.sh data/local/dict '!SIL' data/local/lang data/lang || exit 1;

local/rm_prepare_grammar.sh || exit 1;

而對於WSJ來說,命令爲:

wsj0=/export/corpora5/LDC/LDC93S6B

wsj1=/export/corpora5/LDC/LDC94S13B

local/wsj_data_prep.sh $wsj0/??-{?,??}.? $wsj1/??-{?,??}.?  || exit 1;

local/wsj_prepare_dict.sh || exit 1;

utils/prepare_lang.sh data/local/dict "<SPOKEN_NOISE>" data/local/lang_tmp data/lang || exit 1;

 

local/wsj_format_data.sh || exit 1;

在WSJ的示例腳本中,上述命令之後還有一些訓練語言模型的命令(根據標註重新訓練語言模型,而不是使用LDC提供的), 但是上述幾條命令是最重要的。

數據準備階段的輸出包含兩部分。一部分與“數據”相關(保存在諸如data/train/之類的目錄下),另一部分則與“語言”相關(保存在諸如data/lang/之類的目錄下)。“數據”部分與數據集的錄音相關,而“語言”部分則與語言本身更相關的內容,例如發音字典、音素集合以及其他Kaldi需要的關於音素的額外信息。如果你想用已有的識別系統和語言模型對你的數據進行解碼,那麼你只需要重寫“數據”部分。

數據準備-- 數據部分.

舉個數據準備階段中的關於“數據”部分例子,請查看任何一個示例腳本目錄下的“data/train”目錄(假設你已經運行過一遍這些腳本了)。注意:目錄名字“data/train”本身沒有什麼特別的。一些被命名爲其他名字的目錄,如“data/eval2000”(爲一個測試集建立的),有幾乎差不多的目錄結構和文件格式(說“幾乎”是因爲在測試集的目錄下,可能含有“sgm”和“glm”文件,用於sclite評分)。我們以Switchboard數據爲例,對應腳本在egs/swbd/s5下

s5# ls data/train

cmvn.scp  feats.scp  reco2file_and_channel  segments  spk2utt  text  utt2spk  wav.scp

不是所有的文件都同等重要。如果要設置簡單點,分段(segmentation)信息是不必要的(即一個文件裏只有一段發音),你只需要自己創建“utt2spk”、“text”和“wav.scp”,“segments”和“reco2file_and_channel”是可選的, 根據實際需要決定是否創建。剩下的就都交給標準腳本。

下面我們會詳細描述該目錄下的這些文件。首先從那些需要你手動創建的文件開始。

需要手動創建的文件

文件“text”包含每段發音的標註。

s5# head -3 data/train/text

sw02001-A_000098-001156 HI UM YEAH I'D LIKE TO TALK ABOUT HOW YOU DRESS FOR WORK AND 

sw02001-A_001980-002131 UM-HUM

sw02001-A_002736-002893 AND IS

每行的第一項是發音編號(utterance-id),可以是任意的文本字符串,但是如果在你的設置中還包含說話人信息, 你應該把說話人編號(speaker-id)作爲發音編號的前綴。這對於音頻文件的排序非常重要。發音編號後面跟着的是每段發音的標註。你不用保證這裏出現的每一個字都出現在你的詞彙表中。詞彙表之外的詞會被映射到data/lang/oov.txt中。注意:儘管在這個特別的例子中,我們用下劃線分割了發音編號中的“說話人”和“發音”部分,但是通常用破折號(“-”)會更安全一點。這是因爲破折號的ASCII值更小。有人向我指出說,如果使用下劃線,並且說話人編號的長度不一,在某些特殊的情況下, 如果使用標準"C"語言風格對字符串進行排序,說話人編號和對應的發音編號會被排成不同的順序。另外一個很重要的文件是wav.scp。在Switchboard例子中,

s5# head -3 data/train/wav.scp

sw02001-A /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 1 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |

sw02001-B /home/dpovey/kaldi-trunk/tools/sph2pipe_v2.5/sph2pipe -f wav -p -c 2 /export/corpora3/LDC/LDC97S62/swb1/sw02001.sph |

這個文件的格式是:

<recording-id> <extended-filename>

其中,“extended-filename”可能是一個實際的文件名,或者就像本例中所述那樣,是一段提取wav格式文件的命令。 extended-filename末尾的管道符號表明,整個命令應該被解釋爲一個管道。等會我們會解釋什麼是“recording-id”, 但是首先,我們需要指出,如果“segments”文件不存在,“wav.scp”每一行的第一項就是發音編號。

在Switchboard設置中,我們有“segments”文件,所以下面我們就討論一下這個文件。

s5# head -3 data/train/segments

sw02001-A_000098-001156 sw02001-A 0.98 11.56

sw02001-A_001980-002131 sw02001-A 19.8 21.31

sw02001-A_002736-002893 sw02001-A 27.36 28.93

"segments"文件的格式是:

<utterance-id> <recording-id> <segment-begin> <segment-end>

其中,segment-begin和segment-end以秒爲單位。它們指明瞭一段發音在一段錄音中的時間偏移量。“recording-id” 和在“wav.scp”中使用的是同一個標識字符串。再次聲明一下,這只是一個任意的標識字符串,你可以隨便指定。文件"reco2file_and_channel"只是在你用NIST的sclite工具對結果進行評分(計算錯誤率)的時候使用:

s5# head -3 data/train/reco2file_and_channel 

sw02001-A sw02001 A

sw02001-B sw02001 B

sw02005-A sw02005 A

格式是:

<recording-id> <filename> <recording-side (A or B)>

filename通常是.sph文件的名字,當然需要去掉sph這個後綴;但是也可以是任何其他你在“stm”文件中使用的標識字符串。 “錄音方”(recording side)則是一個電話對話中兩方通話(A或者B)的概念。如果不是兩方通話,那麼爲保險起見最好使用“A”。如果你並沒有“stm”文件,或者你根本不知道這些都是什麼東西,那麼你可能就不需要reco2file_and_channel"文件。

最後一個需要你手動創建的文件是“utt2spk”。該文件指明某一段發音是哪一個說話人發出的。

s5# head -3 data/train/utt2spk

sw02001-A_000098-001156 2001-A

sw02001-A_001980-002131 2001-A

sw02001-A_002736-002893 2001-A

格式是:

<utterance-id> <speaker-id>

注意一點,說話人編號並不需要與說話人實際的名字完全一致——只需要大概能夠猜出來就行。在這種情況下,我們假定每一個說話方(電話對話的每一方)對應一個說話人。這可能不完全正確—— 有時一個說話人會把電話交給另外一個說話人,或者同一個說話人會在不同的對話中出現——但是上述假定對我們來說也足夠用了。如果你完全沒有關於說話人的信息,你可以把發音編號當做說話人編號。那麼對應的文件格式就變爲<utterance-id> <utterance-id>。

在一些示例腳本中還出現了另外一個文件,它在Kaldi的識別系統的建立過程中只是偶爾使用。在Resource Management

(RM)設置中該文件是這樣的:

s5# head -3 ../../rm/s5/data/train/spk2gender

adg0 f

ahh0 m

ajp0 m

這個文件根據說話人的性別,將每個說話人編號映射爲“m”或者“f”。

上述所有文件都應該被排序。如果沒有排序,你在運行腳本的時候就會出現錯誤。在 The Table concept 中我們解釋了爲什麼需要這樣。這與(Kaldi的)I/O框架有關,歸根到底是因爲排序後的文件可以在一些不支持 fseek()的流中,例如,含有管道的命令,提供類似於隨機存取查找的功能。許多Kaldi程序都會從其他Kaldi命令中讀取多個管道流,讀入各種不同類型的對象,然後對不同輸入做一些類似於“合併然後排序”的處理。既然要合併排序, 當然需要輸入是經過排序的。小心確保你的shell環境變量LC_ALL定義爲“C”。例如,在bash中,你需要這樣做:

export LC_ALL=C

如果你不這樣做,這些文件的排序方式會與C++排序字符串的方式不一樣,Kaldi就會崩潰。這一點我已經再三強調過了!

如果你的數據中包含NIST提供的測試集,其中有“stm”和“glm”文件可以用作計算WER,那麼你可以直接把這些文件拷貝到數據目錄下,並分別命名爲“stm”和“glm”。注意,我們把評分腳本score.sh(可以計算WER)放到local/下, 這意味着該腳本是與數據集相關的。不是所有的示例設置下的評分腳本都能識別stm和glm文件。能夠使用這些文件的一個例子在Switchboard設置裏,即i.e.egs/swbd/s5/local/score_sclite.sh。如果檢測到你有stm和glm文件該腳本會被頂層的評分腳本egs/swbd/s5/local/score.sh調用。

 

不需要手動創建的文件

數據目錄下的其他文件可以由前述你提供的文件所生成。你可以用如下的一條命令創建“spk2utt”文件( 這是一條從egs/rm/s5/local/rm_data_prep.sh中摘取的命令):

utils/utt2spk_to_spk2utt.pl data/train/utt2spk > data/train/spk2utt

這是因爲utt2spkspk2utt文件中包含的信息是一樣的。spk2utt文件的格式是:

<speaker-id> <utterance-id1> <utterance-id2> ...

 

下面我們講一講 feats.scp文件.

s5# head -3 data/train/feats.scp

sw02001-A_000098-001156 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24

sw02001-A_001980-002131 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:54975

sw02001-A_002736-002893 /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:62762

這個文件指向已經提取好的特徵——在這個例子中我們所使用的是MFCCfeats.scp文件的格式是:

<utterance-id> <extended-filename-of-features>

每一個特徵文件保存的都是Kaldi格式的矩陣。在這個例子中,矩陣的維度是13(譯者注:即列數;行數則和你的文件長度有關,標準情況下幀長20ms,幀移10ms,所以一行特徵數據對應10ms的音頻數據。但在Kaldi中,實際返回的幀數可能比你預想的要小一點。例如,你的音頻是5.68s,返回的通常是565幀而不是568。這和Kaldi中處理不足以分幀的剩餘數據的方式有關,目前這種方式是兼容HTK的。你需要一直向下傳遞window sizeframe shift的值,才能準確算出每一幀的timing。這種方式在實際使用中有不少問題。)。第一行的“extended filename”, 

/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark:24,意思是,打開存檔(archive)文件

/home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/raw_mfcc_train.1.ark, fseek()定位到24(字節),然後開始讀數據。

  

feats.scp文件由如下命令創建:

steps/make_mfcc.sh --nj 20 --cmd "$train_cmd" data/train exp/make_mfcc/train $mfccdir 

該句被頂層的“run.sh”腳本調用。命令中一些shell變量的定義,請查閱對應run.sh

$mfccdir .ark文件將被寫入的目錄,由用戶自定義。

 

data/train下最後一個未講到的文件是“cmvn.scp”。該文件包含了倒譜均值和方差歸一化的統計量,以說話人編號爲索引。每個統計量集合都是一個矩陣,在本例中是2乘以14維。在我們的例子中,有:

s5# head -3 data/train/cmvn.scp 

2001-A /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:7

2001-B /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:253

2005-A /home/dpovey/kaldi-trunk/egs/swbd/s5/mfcc/cmvn_train.ark:499

feats.scp不同,這個scp文件是以說話人編號爲索引,而不是發音編號。該文件由如下的命令創建:

steps/compute_cmvn_stats.sh data/train exp/make_mfcc/train $mfccdir 

(這個例句來自egs/swbd/s5/run.sh).

因爲數據準備階段的錯誤會影響後續腳本的運行,所以我們有一個腳本來判斷數據目錄的文件格式是否正確。運行如下的例子:

utils/validate_data_dir.sh data/train

你可能會發現下面這個命令也很有用:

utils/fix_data_dir.sh data/train

(當然可對任何數據目錄使用該命令,而不只是data/train)。該腳本會修復排序錯誤,並會移除那些被指明需要特徵數據或標註,但是卻找不到被需要的數據的那些發音(utterances)。

數據準備-- lang”目錄

現在我們關注一下數據準備的“lang”這個目錄。

s5# ls data/lang

L.fst  L_disambig.fst  oov.int  oov.txt  phones  phones.txt  topo  words.txt

data/lang,可能還有其他目錄擁有相似的文件格式:例如有個目錄被命名爲“data/lang_test”,其中包含和data/lang完全一樣的信息,但是要多一個G.fst文件。該文件是一個FST形式的語言模型:

s5# ls data/lang_test

G.fst  L.fst  L_disambig.fst  oov.int  oov.txt  phones  phones.txt  topo  words.txt

注意,lang_test/ 由拷貝lang/目錄而來,並加入了G.fst。每個這樣的目錄都似乎只包含爲數不多的幾個文件。但事實上不止如此,因爲其中phones是一個目錄而不是文件:

s5# ls data/lang/phones

context_indep.csl  disambig.txt         nonsilence.txt        roots.txt    silence.txt

context_indep.int  extra_questions.int  optional_silence.csl  sets.int     word_boundary.int

context_indep.txt  extra_questions.txt  optional_silence.int  sets.txt     word_boundary.txt

disambig.csl       nonsilence.csl       optional_silence.txt  silence.csl

phones目錄下有許多關於音素集的信息。同一類信息可能有三種不同的格式,分別以.csl.int.txt結尾。幸運的是,作爲一個Kaldi用戶,你沒有必要去一一手動創建所有這些文件,因爲我們有一個腳本“utils/prepare_lang.sh”能夠根據更簡單的輸入爲你創建所有這些文件。在講述該腳本和所謂更簡單的輸入之前,有必要先解釋一下“lang”目錄下到底有些什麼內容。之後我們將解釋如何輕鬆創建該目錄。如果用戶不需要理解Kaldi是如何工作的,而是秉着快速建立識別系統的目的,那麼可以跳過下面的Creating the "lang" directory 這一節 

 

“lang”目錄下的內容

首先是有文件hones.txtwords.txt。這些都是符號表(symbol-table)文件,符合OpenFst的格式定義。其中每一行首先是一個文本項,接着是一個數字項:

s5# head -3 data/lang/phones.txt

<eps> 0

SIL 1

SIL_B 2

s5# head -3 data/lang/words.txt

<eps> 0

!SIL 1

-'S 2

Kaldi中,這些文件被用於在這些音素符號的文本形式和數字形式之間進行轉換。

大多數情況下,只有腳本utils/int2sym.plutils/sym2int.plOpenFst中的程序

fstcompilefstprint會讀取這些文件。

 

文件L.fst FST形式的發音字典(L,見<http://www.cs.nyu.edu/~mohri/pub/hbka.pdf> "Speech Recognition with Weighted Finite-State Transducers" by Mohri, Pereira and Riley, in Springer Handbook on SpeechProcessing and Speech Communication, 2008),

其中,輸入是音素,輸出是詞。文件 L_disambig.fst也是發音字典,但是還包含了爲消歧而引入的符號,諸如#1#2 之類,以及爲自環(self-loop)而引入的 #0 #0 能讓消岐符號“通過”(pass through)整個語法(譯者注:n元語法,即我們的語言模型。另外前面這句話我實在不知道該怎麼翻譯)。更多解釋見Disambiguation symbols 。但是不管明白與否,你其實不用自己手動去引入這些符號。

 

文件 data/lang/oov.txt 僅僅只有一行:

s5# cat data/lang/oov.txt

<UNK>

在訓練過程中,所有詞彙表以外的詞都會被映射爲這個詞(譯者注:UNKunknown)。“<UNK>”本身並沒有特殊的地方,也不一定非要用這個詞。重要的是需要保證這個詞的發音只包含一個被指定爲“垃圾音素”(garbage phone)的音素。該音素會與各種口語噪聲對齊。在我們的這個特別設置中,該音素被稱爲<SPN>,就是“spoken noise”的縮寫:

s5# grep -w UNK data/local/dict/lexicon.txt 

<UNK> SPN

文件oov.int 包含<UNK>的整數形式(從words.txt中提取的),在本設置中是221。你可能已經注意到了,在Resource Management設置中,oov.txt裏有一個靜音詞,在RM設置中被稱爲“!SIL”。在這種情況下,我們從詞彙表中任意選一個詞(放入oov.txt——因爲訓練集中沒有oov詞,所以選哪個都不起作用。

 

文件data/lang/topo則含有如下數據:

s5# cat data/lang/topo

<Topology>

<TopologyEntry>

<ForPhones>

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188

</ForPhones>

<State> 0 <PdfClass> 0 <Transition> 0 0.75 <Transition> 1 0.25 </State>

<State> 1 <PdfClass> 1 <Transition> 1 0.75 <Transition> 2 0.25 </State>

<State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State>

<State> 3 </State>

</TopologyEntry>

<TopologyEntry>

<ForPhones>

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

</ForPhones>

<State> 0 <PdfClass> 0 <Transition> 0 0.25 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 </State>

<State> 1 <PdfClass> 1 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>

<State> 2 <PdfClass> 2 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>

<State> 3 <PdfClass> 3 <Transition> 1 0.25 <Transition> 2 0.25 <Transition> 3 0.25 <Transition> 4 0.25 </State>

<State> 4 <PdfClass> 4 <Transition> 4 0.75 <Transition> 5 0.25 </State>

<State> 5 </State>

</TopologyEntry>

</Topology>

這個文件指明瞭我們所用HMM模型的拓撲結構。在這個例子中,一個“真正”的音素內含3個發射狀態,呈標準的三狀態從左到右拓撲結構——即“Bakis”模型。(發射狀態即能“發射”特徵矢量的狀態,與之對應的就是那些“假”的僅用於連接其他狀態的非發射狀態)。音素120是各種靜音和噪聲。之所以會有這麼多,是因爲對詞中的不同位置的同一音素進行了區分(word-position-dependency)。這種情況下,實際上這裏的靜音和噪聲音素大多數都用不上。不考慮在詞位話,應該只有5個代表靜音和噪聲的音素。所謂靜音音素(silence phones)有更復雜的拓撲結構。每個靜音音素都有一個起始發射狀態和一個結束髮射狀態,中間還有另外三個發射狀態。你不用手動創建data/lang/topo

data/lang/phones/下有一系列的文件,指明瞭音素集合的各種信息。這些文件大多數有三個不同版本:一個 “.txt”形式,如:

s5# head -3 data/lang/phones/context_indep.txt 

SIL

SIL_B

SIL_E

一個“.int”形式,如:

s5# head -3 data/lang/phones/context_indep.int

1

2

3

以及一個“.csl”形式,內含一個冒號分割的列表:

s5# cat data/lang/phones/context_indep.csl 

1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20

三種形式的文件包含的是相同的信息,所以我們只關注人們更易閱讀的“.txt”形式。文件"context_indep.txt"包含一個音素列表,用於建立文本無關的模型。也就是說,對這些音素,我們不會建立需要參考左右音素的上下文決策樹。實際上,我們建立的是更小的決策樹,只參考中心音素和HMM狀態。這依賴於“roots.txt”,下面將會介紹到。關於決策樹的更深入討論見 How decision trees are used in Kaldi 。

 

文件 context_indep.txt包含所有音素,包括那些所謂“假”音素:例如靜音(SIL),口語噪聲(SPN),非口語噪聲(NSN)和笑聲(LAU):

# cat data/lang/phones/context_indep.txt

SIL

SIL_B

SIL_E

SIL_I

SIL_S

SPN

SPN_B

SPN_E

SPN_I

SPN_S

NSN

NSN_B

NSN_E

NSN_I

NSN_S

LAU

LAU_B

LAU_E

LAU_I

LAU_S

因爲考慮了詞位,這些音素都有許多變體。不是所有的變體都會被實際使用。這裏, SIL代表靜音詞,會被插入到發音字典中(是一個詞而不是一個詞的一部分,可選的);SIL_B則代表一個靜音音素,應該出現在一個詞的開端(這種情況應該永不出現);SIL_I代表詞內靜音(也很少存在);SIL_E代表詞末靜音(不應該存在);而SIL_S則表示一種被視爲“單獨詞”(singleton word)的靜音,意指這個音素只對應一個詞——當你的發音字典中有“靜音詞”且標註中有明確的靜音段時會有用。

 

silence.txtonsilence.txt分別包含靜音音素列表和非靜音音素列表。這兩個集合是互斥的,且如果合併在一起,應該是音素的總集。在本例中,silence.txtcontext_indep.txt的內容完全一致。我們說“非靜音”音素,是指我們將要估計各種線性變換的音素。所謂線性變換是指全局變換,如LDAMLLT,以及說話人自適應變換,如fMLLR。 根據之前的實驗,我們相信,加入靜音對這些變換沒有影響。我們的經驗是,把噪聲和發聲噪聲都列爲“靜音”音素,而其他傳統的音素則是“非靜音”音素。在Kaldi中我們還沒有通過實驗找到一個最佳的方法來這樣做。

s5# head -3 data/lang/phones/silence.txt 

SIL

SIL_B

SIL_E

s5# head -3 data/lang/phones/nonsilence.txt 

IY_B

IY_E

IY_I

disambig.txt包含一個“消岐符號”列表 (見 Disambiguation symbols):

s5# head -3 data/lang/phones/disambig.txt 

#0

#1

#2

這些符號會出現在phones.txt 中,被當做音素使用。

optional_silence.txt只含有一個音素。該音素可在需要的時候出現在詞之間:

s5# cat data/lang/phones/optional_silence.txt 

SIL

(可選靜音列表中的)音素出現在詞之間的機制是,在發音字典的FST中,可選地讓該音素 出現在每個詞的詞尾(以及每段發音的段首)。該音素必須在phones/中指明而不是僅僅出現在L.fst中。這個原因比較複雜,這裏就不講了。

 

文件sets.txt包含一系列的音素集,在聚類音素時被分組(被當做同一個音素),以便建立文本相關問題集(在Kaldi中,建立決策樹時使用自動生成的問題集,而不是具有語言語義的問題集)。本設置中,sets.txt將每個音素的所有不同詞位的變體組合爲一行:

s5# head -3 data/lang/phones/sets.txt 

SIL SIL_B SIL_E SIL_I SIL_S

SPN SPN_B SPN_E SPN_I SPN_S

NSN NSN_B NSN_E NSN_I NSN_S

文件extra_questions.txt包含那些自動產生的問題集之外的一些問題:

s5# cat data/lang/phones/extra_questions.txt 

IY_B B_B D_B F_B G_B K_B SH_B L_B M_B N_B OW_B AA_B TH_B P_B OY_B R_B UH_B AE_B S_B T_B AH_B V_B W_B Y_B Z_B CH_B AO_B DH_B UW_B ZH_B EH_B AW_B AX_B EL_B AY_B EN_B HH_B ER_B IH_B JH_B EY_B NG_B 

IY_E B_E D_E F_E G_E K_E SH_E L_E M_E N_E OW_E AA_E TH_E P_E OY_E R_E UH_E AE_E S_E T_E AH_E V_E W_E Y_E Z_E CH_E AO_E DH_E UW_E ZH_E EH_E AW_E AX_E EL_E AY_E EN_E HH_E ER_E IH_E JH_E EY_E NG_E 

IY_I B_I D_I F_I G_I K_I SH_I L_I M_I N_I OW_I AA_I TH_I P_I OY_I R_I UH_I AE_I S_I T_I AH_I V_I W_I Y_I Z_I CH_I AO_I DH_I UW_I ZH_I EH_I AW_I AX_I EL_I AY_I EN_I HH_I ER_I IH_I JH_I EY_I NG_I 

IY_S B_S D_S F_S G_S K_S SH_S L_S M_S N_S OW_S AA_S TH_S P_S OY_S R_S UH_S AE_S S_S T_S AH_S V_S W_S Y_S Z_S CH_S AO_S DH_S UW_S ZH_S EH_S AW_S AX_S EL_S AY_S EN_S HH_S ER_S IH_S JH_S EY_S NG_S 

SIL SPN NSN LAU 

SIL_B SPN_B NSN_B LAU_B 

SIL_E SPN_E NSN_E LAU_E 

SIL_I SPN_I NSN_I LAU_I 

SIL_S SPN_S NSN_S LAU_S 

你可以看到,所謂一個問題就是一組音素。前4個問題是關於普通音素的詞位信息,後面五個則是關於“靜音音素”的。 “靜音”音素也可能沒有像_B 這樣的後綴,比如SIL。這些可被作爲發音字典中可選的靜音詞的表示,即不會出現在某個詞中,而是單獨成詞。在具有語調和語氣的設置中,extra_questions.txt 可以包含與之相關的問題集。

word_boundary.txt解釋了這些音素與詞位的關聯情況:

s5# head  data/lang/phones/word_boundary.txt 

SIL nonword

SIL_B begin

SIL_E end

SIL_I internal

SIL_S singleton

SPN nonword

SPN_B begin

這和音素中的後綴(_B等等)是相同的信息,但我們並不想給音素的文本形式附加這樣的強制限制——記住一件事,Kaldi的可執行程序從不使用音素的文本形式,而是整數形式。(譯者注:即Kaldi內部使用的都是音素的整數標號來表示和傳遞音素)。所以我們使用文件word_boundary.txt來指明各音素與詞位間的對應關係。 建立這種對應關係的原因是因爲我們需要這些信息從音素網格中恢復詞的邊界(例如,lattice-align-words需要讀取word_boundary.txt的整數版本word_boundary.int)。找出詞的邊界是有用的,其中之一是用作NISTsclite評分,該工具需要詞的時間標記。還有其他的後續處理需要這些信息。

roots.txt文件包含如何建立音素上下文決策樹的信息:

head data/lang/phones/roots.txt 

shared split SIL SIL_B SIL_E SIL_I SIL_S

shared split SPN SPN_B SPN_E SPN_I SPN_S

shared split NSN NSN_B NSN_E NSN_I NSN_S

shared split LAU LAU_B LAU_E LAU_I LAU_S

...

shared split B_B B_E B_I B_S

暫時你可以忽略”shared“和”split——這些與我們建立決策樹時的具體選項有關(更多信息見How decision trees are used in Kaldi ). 像SIL SIL_B SIL_E SIL_I SIL_S這樣,幾個音素出現在同一行的意義是,在決策樹中它們都有同一個“共享根”(shared root),因此狀態可在這些音素間共享。對於帶語氣語調的系統,通常所有與語氣和語調相關的音素變體都會出現在同一行。此外,一個HMM中的3個狀態(對靜音來說有5個狀態)共享一個根,且決策樹的建立過程需要知道狀態(的共享情況)。 HMM狀態間共享決策樹根節點,這就是roots文件中“shared”代表的意思。

建立"lang"目錄

data/lang/目錄下有很多不同的文件,所以我們提供了一個腳本爲你創建這個目錄,你只需要提供一些相對簡單的輸入信息:

utils/prepare_lang.sh data/local/dict "<UNK>" data/local/lang data/lang

這裏,輸入目錄是data/local/dict/<UNK>需要在字典中,是標註中所有OOV詞的映射詞(映射情況會寫入data/lang/oov.txt中)。data/local/lang/只是腳本使用的一個臨時目錄,data/lang/纔是輸出文件將會寫入的地方。

 

作爲數據準備者,你需要做的事就是創建data/local/dict/這個目錄。該

目錄包含以下信息:

s5# ls data/local/dict

extra_questions.txt  lexicon.txt nonsilence_phones.txt  optional_silence.txt  silence_phones.txt

(實際上還有一些文件我們沒有列出來,但那都是在創建目錄時所遺留下的臨時文件,可以忽略)。下面的這些命令可以讓你知道這些文件中大概都有些什麼:

s5# head -3 data/local/dict/nonsilence_phones.txt 

IY

B

D

s5# cat data/local/dict/silence_phones.txt 

SIL

SPN

NSN

LAU

s5# cat data/local/dict/extra_questions.txt 

s5# head -5 data/local/dict/lexicon.txt 

!SIL SIL

-'S S

-'S Z

-'T K UH D EN T

-1K W AH N K EY

正如你看到的,本設置(Switchboard)中,這個目錄下的內容都非常簡單。我們只是分別列出了“真正”的音素和“靜音”音素,一個叫extra_questions.txt的空文件,以及一個有如下格式的lexicon.txt

<word> <phone1> <phone2> ...

注意:lexicon.txt中,如果一個詞有不同發音,則會在不同行中出現多次。如果你想使用發音概率,你需要建立exiconp.txt而不是 lexicon.txtlexiconp.txt中第二域就是概率值。

注意,一個通常的作法是,對發音概率進行歸一化,使最大的那個概率值爲1,而不是使同一個詞的所有發音概率加起來等於1。 這樣可能會得到更好的結果。 如果想在頂層腳本中找一個與發音概率相關的腳本,請在egs/wsj/s5/run.sh目錄下搜索pp

 

需要注意的是,在這些輸入中,沒有詞位信息,即沒有像_B_E<這樣的後綴。

這是因爲腳本prepare_lang.sh會添加這些後綴。

 

從空的extra_questions.txt件中你會發現,可能還有些潛在的功能我們沒有利用。

這其中就包括重音和語調標記。 對具有不同重音和語調的同一音素,你可能會想用不同的標記去表示。爲展示如何這樣做,我們看看在另外一個設置egs/wsj/s5/的這些文件。結果如下:

s5# cat data/local/dict/silence_phones.txt 

SIL

SPN

NSN

s5# head data/local/dict/nonsilence_phones.txt 

UW UW0 UW1 UW2 

AO AO0 AO1 AO2 

AY AY0 AY1 AY2 

SH 

s5# head -6 data/local/dict/lexicon.txt 

!SIL SIL

<SPOKEN_NOISE> SPN

<UNK> SPN

<NOISE> NSN

!EXCLAMATION-POINT  EH2 K S K L AH0 M EY1 SH AH0 N P OY2 N T

"CLOSE-QUOTE  K L OW1 Z K W OW1 T

s5# cat data/local/dict/extra_questions.txt 

SIL SPN NSN 

S UW T N K Y Z AO AY SH W NG EY B CH OY JH D ZH G UH F V ER AA IH M DH L AH P OW AW HH AE R TH IY EH 

UW1 AO1 AY1 EY1 OY1 UH1 ER1 AA1 IH1 AH1 OW1 AW1 AE1 IY1 EH1 

UW0 AO0 AY0 EY0 OY0 UH0 ER0 AA0 IH0 AH0 OW0 AW0 AE0 IY0 EH0 

UW2 AO2 AY2 EY2 OY2 UH2 ER2 AA2 IH2 AH2 OW2 AW2 AE2 IY2 EH2 

s5# 

你可能已經注意到了,nonsilence_phones.txt中的某些行,一行中

有多個音素。這些是同一元音的與重音相關的不同表示。 注意,在CMU版的字典中,

每個音素有4種表示:例如,UW UW0 UW1 UW2。基於某些原因。其中一種表示沒有數字後綴。行中音素的順序沒有關係。通常,我們建議將每個“真實音素”的不同形式都組織在單獨的一行中。我們使用CMU字典中的重音標記。文件extra_questions.txt中只有一個問題

包含所有的“靜音”音素(實際上這是不必要的,只是腳本prepare_lang.sh 會添加這麼一個問題),以及一個涉及不同重音標記的問題。

這些問題對利用重音標記信息來說是必要的,因爲在nonsilence_phones.txt中每個音素的不同重音表示都在同一行,這確保了他們在 data/lang/phones/roots.txt 

data/lang/phones/sets.txt也屬同一行,這又反過來確保了它們共享同一個(決策)樹

根,並且不會有決策問題弄混它們。因此,我們需要提供一個特別的問題,能爲決策樹的建立過程提供一種區分音素的方法。 注意:我們在sets.txtroots.txt中將音素分組放在一起的原因是,這些同一音素的不同重音變體可能缺乏足夠的數據去穩健地估計一個單獨的決策樹,或者是產生問題集時需要的聚類信息。 像這樣把它們組合在一起,我們可以確保當數據不足以對它們分別估計決策樹時,這些變體能在決策樹的建立過程中“聚集在一起”(stay together)。

 

寫到這裏我們需要提一點,腳本utils/prepare_lang.sh支持很多選項。下面是該腳本的用法,可讓你們瞭解這些選項都有哪些:

usage: utils/prepare_lang.sh <dict-src-dir> <oov-dict-entry> <tmp-dir> <lang-dir>

e.g.: utils/prepare_lang.sh data/local/dict <SPOKEN_NOISE> data/local/lang data/lang

options: 

     --num-sil-states <number of states>             # default: 5, #states in silence models.

     --num-nonsil-states <number of states>          # default: 3, #states in non-silence models.

     --position-dependent-phones (true|false)        # default: true; if true, use _B, _E, _S & _I

                                                     # markers on phones to indicate word-internal positions.

     --share-silence-phones (true|false)             # default: false; if true, share pdfs of

                                                     # all non-silence phones.

     --sil-prob <probability of silence>             # default: 0.5 [must have 0 < silprob < 1]

一個可能的重要選項是--share-silence-phones。該選項默認是false。 如果該選項被設爲true, 所有靜音音素——如靜音、發聲噪聲、噪聲和笑聲——的概率密度函數(PDF,高斯混合模型)都會共享,只有模型中的轉移概率不同。現在還不清楚爲什麼這樣做有用,但我們發現這對IARPABABEL項目中的廣東話數據集非常有效。該數據集非常亂,其中有很長的未標註的部分,我們試着將其與一個特別標記的音素對齊。我們懷疑訓練數據可能沒能成功正確對齊,而且基於某些不明原因,將上述選項設置爲true則改變了結果。

 

另外一個可能的重要選項是“--sil-prob”。 通常,對這些選項我們所作的實驗都不多,所以對具體如何設置也不能給出非常詳細的建議。

 

創建語言模型或者語法文件

 

前面的關於如何創建lang/目錄的教程沒有涉及如何產生G.fst文件。該文件是語言模型——或者稱爲語法——的有限狀態轉換器格式的表示,我們解碼時需要它。 實際上,在一些設置中,爲做不同的測試,我們可能會有許多“lang”目錄。這些目錄中有不同的語言模型和字典。以華爾街日報(WSJ)的設置爲例:

s5# echo data/lang*

data/lang data/lang_test_bd_fg data/lang_test_bd_tg data/lang_test_bd_tgpr data/lang_test_bg \

 data/lang_test_bg_5k data/lang_test_tg data/lang_test_tg_5k data/lang_test_tgpr data/lang_test_tgpr_5k

根據我們使用的語言模型的不同——是統計語言模型還是別的種類的語法形式——生成G.fst的步驟會不同。 在RM設置中,使用的是二元語法,只允許某些詞對。我們將總概率值1分配給所有向外的弧,以確保每個語法狀態的概率和爲1。在 local/rm_data_prep.sh中有這樣一句代碼:

local/make_rm_lm.pl $RMROOT/rm1_audio1/rm1/doc/wp_gram.txt  > $tmpdir/G.txt || exit 1;

腳本local/make_rm_lm.pl會建立一個FST格式的語法文件(文本格式,不是二進制格式)。

該文件包含如下形式的行:

s5# head data/local/tmp/G.txt 

0    1    ADD    ADD    5.19849703126583

0    2    AJAX+S    AJAX+S    5.19849703126583

0    3    APALACHICOLA+S    APALACHICOLA+S    5.19849703126583

 www.openfst.org 上查閱更多關於OpenFst的信息(他們有一個很詳細的教程)。 腳本local/rm_prepare_grammar.sh會將文本格式的語法文件轉換爲二進制文件G.fst。所用命令如下:

fstcompile --isymbols=data/lang/words.txt --osymbols=data/lang/words.txt --keep_isymbols=false \

    --keep_osymbols=false $tmpdir/G.txt > data/lang/G.fst 

如果你要建立自己的語法文件,你也應做類似的事。

注意:這種過程只適用於一類語法:用上述方法你不能創建上下文無關的語法,因爲這類語法不能被表示爲OpenFst格式。 在WFST框架下還是有辦法這麼做(見Mike Riley最近關於push down transducers的研究工作),但是在Kaldi中我們還沒實現這些功能。

 

WSJ設置中,我們使用了一個統計語言模型。腳本 local/wsj_format_data.shWSJ數據庫提供的ARPA格式的語言模型文件轉換爲OpenFst格式的。 腳本中關鍵的命令如下:

  gunzip -c $lmdir/lm_${lm_suffix}.arpa.gz | \

   utils/find_arpa_oovs.pl $test/words.txt  > $tmpdir/oovs_${lm_suffix}.txt

 ...

  gunzip -c $lmdir/lm_${lm_suffix}.arpa.gz | \

    grep -v '<s> <s>' | \

    grep -v '</s> <s>' | \

    grep -v '</s> </s>' | \

    arpa2fst - | fstprint | \

    utils/remove_oovs.pl $tmpdir/oovs_${lm_suffix}.txt | \

    utils/eps2disambig.pl | utils/s2eps.pl | fstcompile --isymbols=$test/words.txt \

      --osymbols=$test/words.txt  --keep_isymbols=false --keep_osymbols=false | \

     fstrmepsilon > $test/G.fst

這裏,變量$test的值形如data/lang_test_tg。最重要的一條命令是arpa2fst, 這是一個Kaldi程序。該程序將ARPA格式的語言模型轉換爲一個加權有限狀態轉換器(實際上是一個接收器)。grep命令移除語言模型中“不可用”的N元語法。 程序remove_oovs.pl

移除包含集外詞的N元語法(如果不移除會引起fstcompile崩潰)。eps2disambig.pl

將回退弧上的<eps>ε)符號轉換爲一個特殊的符號#0,以保證語法文件是確定的(determinizable),見 Disambiguation symbols. 如果你不知道“回退弧”是什麼,

你可以參考關於回退N元語法的文獻,例如Goodman“A bit of progress in language modeling”,以及我們前面引用的Mohri的論文。 命令s2eps.pl 將句首和句末

符號<s> 和 </s> 轉換爲epsilon<eps>), 意即“沒有符號”。fstcompile 是一個OpenFst命令,可將文本形式的FST轉換爲二進制形式的。 fstrmepsilon也是一個OpenFst命令,可將FST中由<s> 和 </s> 替換而來的少量的<eps>符號移除掉。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章