kaldi的語音識別數據timit例子詳解

本文將以 kaldi 中 timit 的例程來看整個 run.sh 腳本的執行過程。

數據準備

請先進入 kaldi\egs\timit\s5\ 這個目錄。

運行環境

由於 kaldi 可以在本地運行,也可以在 Oracle GridEngine 上運行,因此,請修改 cmd.sh

如果你是在本地運行,請輸入

export train_cmd="run.pl --max-jobs-run 10"
export decode_cmd="run.pl --max-jobs-run 10"
export cuda_cmd="run.pl --max-jobs-run 2"
export mkgraph_cmd="run.pl --max-jobs-run 10"

其中 max-jobs-run 限定了最大的運行線程數,如果你的機器CPU不是特別多,請一定加上這個條件,防止內存被耗盡。

如果你是在 Oracle GridEngine 上運行,那麼請修改成類似的內容

export train_cmd="queue.pl --mem 4G"
export decode_cmd="queue.pl --mem 4G"
export mkgraph_cmd="queue.pl --mem 8G"
export cuda_cmd="queue.pl --gpu 1"

準備數據

timit 下的 run.sh 前面 5 條命令都是和數據準備有關的。

# 數據路徑
timit=/mnt/b/huanglu/timit

# 準備數據
local/timit_data_prep.sh $timit || exit 1

# 準備詞典
local/timit_prepare_dict.sh

# 準備語言文件
utils/prepare_lang.sh --sil-prob 0.0 --position-dependent-phones false --num-sil-states 3 \
 data/local/dict "sil" data/local/lang_tmp data/lang

# 準備訓練集、開發集合測試集
local/timit_format_data.sh

在執行完上面的腳本之後,進入 data 目錄,會看到下面一些文件夾

文件夾 內容
dev 開發集數據
lang 包含語言文件
lang_test_bg 用於測試的語言文件
local 包含了原始數據的信息,以及詞典
test 測試集
train 訓練集

詞典

$ cd data/local/dict
$ head lexicon.txt
$ head nonsilence_phones.txt
$ head silence_phones.txt
文件 內容
lexicon.txt 詞典
nonsilence_phones.txt 包含哪些音素是靜音的信息
silence_phones.txt 包含哪些音素不是靜音的信息

訓練(測試、開發)數據

$ cd data/train
$ head text
$ head spk2gender
$ head spk2utt
$ head utt2spk
$ head wav.scp
文件 內容
spk2gender 包含說話人的性別信息
spk2utt 包含說話人編號和說話人的語音編號的信息
text 包含語音和語音編號之間的關係
utt2spk 語音編號和說話人編號之間的關係
wav.scp 包含了原始語音的路徑信息等

語言文件

$ cd data/lang
$ head words.txt
$ head phones.txt
$ cd phones
$ head silence.csl
$ head nonsilence.csl
$ cd ..
$ ls -hl

其中 words.txt 和 phones.txt 是 OpenFst 格式的符號表,表示音素到整數的映射。

而 *.csl 文件則則包括了靜音、非靜音和音素的整數 id 列表。

文件 L.fst 是FST 格式的編譯詞典。爲了查看其代表的信息,可以執行

fstprint --isymbols=data/lang/phones.txt --osymbols=data/lang/words.txt data/lang/L.fst | head

如果顯示 No command 'fstprint' found,則可以執行

. ./path.sh

輸出結果如下:

0	0	aa	aa
0	0	ae	ae
0	0	ah	ah
0	0	ao	ao
0	0	aw	aw
0	0	ax	ax
0	0	ay	ay
0	0	b	b
0	0	ch	ch
0	0	cl	cl

特徵提取

有關特徵提取的代碼主要是下面幾行:

mfccdir=mfcc
for x in train dev test; do 
  steps/make_mfcc.sh --cmd "$train_cmd" --nj $feats_nj data/$x exp/make_mfcc/$x $mfccdir
  steps/compute_cmvn_stats.sh data/$x exp/make_mfcc/$x $mfccdir
done

執行這段代碼之前需要執行

. ./cmd.sh
feats_nj=4
train_nj=4
decode_nj=1

執行前面的代碼,可以生成了兩個文件夾:mfcc 和 exp/make_mfcc,其中 mfcc 裏主要保存了提取的特徵,而 exp/make_mfcc 裏保存了日誌,即 .log 文件。

在 steps/make_mfcc.sh 裏用到的最主要的命令就是 compute-mfcc-feats 和 copy-feats,其在 src 裏編譯好的。

mfcc 目錄裏主要是 .ark 和 .scp 文件,其中 .scp 文件裏的內容是語音段和特徵對應,而真正的特徵保存在 .ark 文件裏。用下面的命令可以看清楚

$ head raw_mfcc_train.1.scp
faem0_si1392 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:13
faem0_si2022 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:6313
faem0_si762 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:9349
faem0_sx132 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:13048
faem0_sx222 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:16916
faem0_sx312 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:20537
faem0_sx402 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:25549
faem0_sx42 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:29767
fajw0_si1263 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:32804
fajw0_si1893 /home/huanglu/kaldi/egs/timit/s5/mfcc/raw_mfcc_train.1.ark:39403

scripts 和 archives 的底層是 Table 的概念。一個 Table 基本上是由唯一字符串(例如話語標識符)索引的項目(例如特徵文件)的有序集合。.scp 格式是一個純文本格式,每一行有一個 key,然後一個“擴展文件名”,告訴 Kaldi 在哪裏可以找到數據。archive 格式可以是文本或二進制。 格式是:key(例如 utterance id),然後一個空格,然後是對象數據。

除了 MFCC 特徵以外,文件夾 mfcc 裏還有 CMVN 特徵。

關於 scripts 和 archives 兩種格式,有以下論述

  1. A string that specifies how to read a Table (archive or script) is called an rspecifier; for example “ark:gunzip -c my/dir/foo.ark.gzl”.
  2. A string that specifies how to write a Table (archive or script) is called a wspecifier; for example “ark,t:foo.ark”.
  3. Archives can be concatenated together and still be valid archives (there is no “central index” in them).
  4. The code can read both scripts and archives either sequentially or via random access. The user-level code only knows whether it’s iterating or doing lookup; it doesn’t know whether it’s accessing a script or an archive.
  5. Kaldi doesn’t attempt to represent the object type in the archive; you have to know the object type in advance.
  6. Archives and script files can’t contain mixtures of types.
  7. Reading archives via random access can be memory-inefficient as the code may have to cache the objects in memory.
  8. For efficient random access to an archive, you can write out a corresponding script file using the “ark,scp” writing mechanism (e.g., used in writing the mfcc features to disk). You would then access it via the scp file.
  9. Another way to avoid the code having to cache a bunch of stuff in memory when doing random access on archives is to tell the code that the archive is sorted and will be called in sorted order (e.g. “ark,s,cs:-“).
  10. Types that read and write archives are templated on a Holder type, which is a type that “knows how” to read and write the object in question.

至於如何通過管道使用 scripts 和 archives 兩種格式的文件,可以參考下面的命令

$ head -1 mfcc/raw_mfcc_train.1.scp | copy-feats scp:- ark:- | copy-feats ark:- ark,t:- | head -n 2
# 示例輸出
copy-feats ark:- ark,t:-
copy-feats scp:- ark:-
LOG (copy-feats:main():copy-feats.cc:120) Copied 1 feature matrices.
faem0_si1392  [
  38.3508 -31.04888 -10.46318 -9.166654 -11.21832 -1.998698 18.50948 3.447642 6.247809 -5.24096 -4.613473 -14.82091 -12.65494
  38.3508 -29.88511 -2.339327 -2.430964 -3.292726 2.997352 5.760813 4.516098 14.78676 7.313382 -6.224341 -16.88169 -8.839561

單音素訓練和解碼

單音素訓練

下面的命令可以訓練單音素模型(monophone model)

steps/train_mono.sh  --nj "$train_nj" --cmd "$train_cmd" data/train data/lang exp/mono

訓練過程的輸出大致如下

steps/train_mono.sh --nj 4 --cmd run.pl --max-jobs-run 10 data/train data/lang exp/mono
steps/train_mono.sh: Initializing monophone system.
steps/train_mono.sh: Compiling training graphs
steps/train_mono.sh: Aligning data equally (pass 0)
steps/train_mono.sh: Pass 1
steps/train_mono.sh: Aligning data
························
steps/train_mono.sh: Aligning data
steps/train_mono.sh: Pass 36
steps/train_mono.sh: Pass 37
steps/train_mono.sh: Pass 38
steps/train_mono.sh: Aligning data
steps/train_mono.sh: Pass 39
steps/diagnostic/analyze_alignments.sh --cmd run.pl --max-jobs-run 10 data/lang exp/mono
steps/diagnostic/analyze_alignments.sh: see stats in exp/mono/log/analyze_alignments.log
2 warnings in exp/mono/log/align.*.*.log
exp/mono: nj=4 align prob=-99.15 over 3.12h [retry=0.0%, fail=0.0%] states=144 gauss=985

之後會在 exp 文件夾下產生一個 mono 的目錄,裏面以 .mdl 結尾的就保存了模型的參數。使用下面的命令可以查看模型的內容。

$ gmm-copy --binary=false exp/mono/0.mdl - | less
# 示例輸出
<TransitionModel>
<Topology>
<TopologyEntry>
<ForPhones>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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
</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
</ForPhones>
<State> 0 <PdfClass> 0 <Transition> 0 0.5 <Transition> 1 0.5 </State>
<State> 1 <PdfClass> 1 <Transition> 1 0.5 <Transition> 2 0.5 </State>
<State> 2 <PdfClass> 2 <Transition> 2 0.75 <Transition> 3 0.25 </State>
<State> 3 </State>
</TopologyEntry>
</Topology>
<Triples> 144
1 0 0
1 1 1
1 2 2
2 0 3
2 1 4
2 2 5
3 0 6
3 1 7
3 2 8
4 0 9
············

有關上下文的信息可以查看 tree 文件

$ copy-tree --binary=false exp/mono/tree - | less
# 示例輸出
copy-tree --binary=false exp/mono/tree -
LOG (copy-tree:main():copy-tree.cc:55) Copied tree
ContextDependency 1 0 ToPdf TE 0 49 ( NULL TE -1 3 ( CE 0 CE 1 CE 2 )
TE -1 3 ( CE 3 CE 4 CE 5 )
TE -1 3 ( CE 6 CE 7 CE 8 )
TE -1 3 ( CE 9 CE 10 CE 11 )
TE -1 3 ( CE 12 CE 13 CE 14 )
TE -1 3 ( CE 15 CE 16 CE 17 )
TE -1 3 ( CE 18 CE 19 CE 20 )
TE -1 3 ( CE 21 CE 22 CE 23 )
TE -1 3 ( CE 24 CE 25 CE 26 )
TE -1 3 ( CE 27 CE 28 CE 29 )
TE -1 3 ( CE 30 CE 31 CE 32 )
TE -1 3 ( CE 33 CE 34 CE 35 )
···················
EndContextDependency

下面查看訓練數據 Veterbi 對齊,ali.*.gz 的每一行是對應於每一個訓練文件的。

$ copy-int-vector "ark:gunzip -c exp/mono/ali.1.gz|" ark,t:- | head -n 2
# 示例輸出
faem0_si1392 2 4 3 3 3 3 3 3 6 5 5 5 5 5 38 37 37 40 42 218 217 217 217 217 217 217 217 217 217 217 217 220 219 222 221 221 221 248 247 247 247 247 247 250 252 176 175 178 177 177 177 177 177 180 122 121 121 121 121 121 121 121 124 123 123 126 125 26 28 27 30 29 29 212 211 214 216 215 146 148 150 260 262 264 263 263 278 277 277 277 280 282 281 281 281 14 13 13 13 13 16 15 18 17 176 175 178 180 62 64 66 206 208 210 242 241 244 246 170 169 169 169 169 172 171 174 173 173 173 173 173 38 37 37 37 40 42 41 218 217 217 217 217 217 217 217 217 217 220 219 222 221 146 145 148 150 62 64 66 56 55 55 55 55 55 55 58 60 59 59 248 250 249 252 251 251 251 251 251 116 115 115 115 115 118 117 117 120 119 224 223 223 223 223 223 223 226 225 225 228 98 97 97 100 99 102 101 266 265 265 265 265 268 267 267 267 270 269 269 86 88 90 110 109 112 111 114 122 121 121 121 121 121 121 121 121 121 124 123 126 125 8 7 7 7 7 7 7 7 7 10 9 9 12 212 214 216 176 175 175 178 177 180 134 133 133 133 136 135 138 137 86 88 90 89 278 277 277 277 280 282 281 281 38 37 40 42 62 64 66 65 206 208 207 207 207 207 207 210 209 14 13 13 16 15 15 18 17 17 17 62 61 64 66 164 166 168 167 167 152 154 156 188 190 189 189 192 191 191 224 223 223 223 223 223 223 223 223 223 226 225 225 228 227 86 85 85 85 85 85 85 85 85 85 85 88 87 90 260 259 262 264 263 68 70 72 71 71 71 2 4 3 3 6 5 5 5 5 5 5 14 13 13 16 15 15 15 15 15 18 17 17 182 181 184 186 260 262 264 68 70 72 122 121 121 121 121 121 121 124 123 123 126 152 151 151 151 151 154 153 153 153 156 155 155 155 155 155 155 170 169 169 169 169 169 169 172 171 174 173 260 259 262 261 264 263 263 263 218 217 217 217 217 217 217 217 217 217 220 219 222 221 221 2 1 4 3 3 3 3 3 3 3 6
faem0_si2022 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 3 3 3 3 3 6 5 5 5 266 265 265 265 265 265 265 268 267 270 269 269 20 22 24 80 82 81 84 83 32 31 34 33 33 33 33 36 35 35 35 35 35 35 122 121 121 121 121 121 121 121 121 124 123 126 125 146 145 148 147 150 62 64 63 66 65 65 65 65 65 65 68 70 72 71 242 241 244 246 245 224 223 223 223 223 223 223 223 223 223 223 223 226 225 225 228 152 154 153 156 260 262 264 263 68 70 72 71 212 211 211 211 214 213 213 216 215 215 215 215 44 43 46 45 45 45 48 47 47 254 256 258 122 121 121 121 121 121 121 121 121 121 121 124 123 126 125 125 26 25 25 28 27 27 27 27 27 30 29 29 29 2 1 1 1 1 4 3 3 3 3 3 3 6 5

可以使用 show-transitions 命令來查看有關 transition-id 的信息。

$ show-transitions data/lang/phones.txt exp/mono/0.mdl
# 示例輸出
Transition-state 1: phone = sil hmm-state = 0 pdf = 0
 Transition-id = 1 p = 0.5 [self-loop]
 Transition-id = 2 p = 0.5 [0 -> 1]
Transition-state 2: phone = sil hmm-state = 1 pdf = 1
 Transition-id = 3 p = 0.5 [self-loop]
 Transition-id = 4 p = 0.5 [1 -> 2]
···········

構建解碼圖

在解碼之前,我們需要構建解碼圖。輸入下面的命令

$ utils/mkgraph.sh --mono data/lang_test_bg exp/mono exp/mono/graph
# 示例輸出
tree-info exp/mono/tree
tree-info exp/mono/tree
fsttablecompose data/lang_test_bg/L_disambig.fst data/lang_test_bg/G.fst
fstdeterminizestar --use-log=true
fstpushspecial
fstminimizeencoded
fstisstochastic data/lang_test_bg/tmp/LG.fst
-0.00841335 -0.00928529
fstcomposecontext --context-size=1 --central-position=0 --read-disambig-syms=data/lang_test_bg/phones/disambig.int --write-disambig-syms=data/lang_test_bg/tmp/disambig_ilabels_1_0.int data/lang_test_bg/tmp/ilabels_1_0
fstisstochastic data/lang_test_bg/tmp/CLG_1_0.fst
-0.00841335 -0.00928515
make-h-transducer --disambig-syms-out=exp/mono/graph/disambig_tid.int --transition-scale=1.0 data/lang_test_bg/tmp/ilabels_1_0 exp/mono/tree exp/mono/final.mdl
fstminimizeencoded
fstdeterminizestar --use-log=true
fsttablecompose exp/mono/graph/Ha.fst data/lang_test_bg/tmp/CLG_1_0.fst
fstrmsymbols exp/mono/graph/disambig_tid.int
fstrmepslocal
fstisstochastic exp/mono/graph/HCLGa.fst
0.000381767 -0.00951546
add-self-loops --self-loop-scale=0.1 --reorder=true exp/mono/final.mdl

解碼

接下來我們就可以解碼了,分別針對開發集和測試集解碼。

steps/decode.sh --nj "$decode_nj" --cmd "$decode_cmd" \
 exp/mono/graph data/dev exp/mono/decode_dev
 
steps/decode.sh --nj "$decode_nj" --cmd "$decode_cmd" \
 exp/mono/graph data/test exp/mono/decode_test

解碼的日誌會保存在 exp/mono/decode_dev/log 和 exp/mono/decode_test/log 裏。下面是開發集的解碼輸出。

steps/decode.sh --nj 1 --cmd run.pl --max-jobs-run 10 exp/mono/graph data/dev exp/mono/decode_dev
decode.sh: feature type is delta
steps/diagnostic/analyze_lats.sh --cmd run.pl --max-jobs-run 10 exp/mono/graph exp/mono/decode_dev
steps/diagnostic/analyze_lats.sh: see stats in exp/mono/decode_dev/log/analyze_alignments.log
Overall, lattice depth (10,50,90-percentile)=(5,26,120) and mean=61.6
steps/diagnostic/analyze_lats.sh: see stats in exp/mono/decode_dev/log/analyze_lattice_depth_stats.log

緊接着你可以完成其他訓練和解碼。

查看結果

輸入下面的命令來查看結果

#!/bin/bash
for x in exp/{mono,tri,sgmm,dnn,combine}*/decode*; do [ -d $x ] && echo $x | grep "${1:-.*}" >/dev/null && grep WER $x/wer_* 2>/dev/null | utils/best_wer.sh; done
for x in exp/{mono,tri,sgmm,dnn,combine}*/decode*; do [ -d $x ] && echo $x | grep "${1:-.*}" >/dev/null && grep Sum $x/score_*/*.sys 2>/dev/null | utils/best_wer.sh; done

我的解碼結果如下:

%WER 31.8 | 400 15057 | 71.7 19.4 8.9 3.5 31.8 100.0 | -0.496 | exp/mono/decode_dev/score_5/ctm_39phn.filt.sys
%WER 32.2 | 192 7215 | 70.5 19.4 10.0 2.8 32.2 100.0 | -0.211 | exp/mono/decode_test/score_6/ctm_39phn.filt.sys

開發集和測試集的詞錯率(WER)分別是 31.8% 和 32.2%。

發佈了7 篇原創文章 · 獲贊 35 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章