音声認識メモ(Kaldi)その23(トレーニング時のモデル更新 Dan's DNN(nnet2))
前回の続き。
mixup後に生成した12個のモデル(14.mdl〜25.mdl)から「final.mdl」を生成する過程を追ってみる。
nnet2では、結果が良かったモデルを1つだけ選ぶのではなく、スケールを掛けた上で足し合わせている。
生成は、「nnet2bin/nnet-combine-fast」コマンドを使ってモデルをcombine(合成)する。
nnet-combine-fast \ --minibatch-size=129 \ exp/nnet4c/14.mdl \ exp/nnet4c/15.mdl \ <snip> \ exp/nnet4c/25.mdl \ ark:exp/nnet4c/egs/combine.egs \ # <valid-examples-in> exp/nnet4c/final.mdl
今回のケースでは、モデルのComponent数が「9」、Updatable-Component数が「3」
infoコマンド(nnet2bin/nnet-am-info exp/nnet4c/14.mdl )
num-components 9 num-updatable-components 3 input-dim 40 output-dim 192 component 0 : SpliceComponent, input-dim=40, output-dim=360, ... component 1 : FixedAffineComponent, input-dim=360, output-dim=360, ... component 2 : AffineComponentPreconditionedOnline, input-dim=360, output-dim=375, ... component 3 : TanhComponent, input-dim=375, output-dim=375 component 4 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=375, ... component 5 : TanhComponent, input-dim=375, output-dim=375 component 6 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=453, ... component 7 : SoftmaxComponent, input-dim=453, output-dim=453 component 8 : SumGroupComponent, input-dim=453, output-dim=192
indexが2、4、6の「AffineComponentPreconditionedOnline」コンポーネント(Updatable-Componentに該当)が合成対象となる。
結果を先に書くと、各モデル、各コンポーネントに対して、以下のようなスケールが得られた。
各モデル、コンポーネントごとのスケール
スケールを求める過程を追ってみる。
更新における評価の尺度は、Propagateの値(Log値)となる(値が小さいものほど良)
同一のデータ(exp/nnet4c/egs/combine.egs)に対する各モデルの値は以下のとおりであった。
14.mdl : -0.0753596 15.mdl : -0.0679429 16.mdl : -0.0373454 17.mdl : -0.0325585 18.mdl : -0.0286209 19.mdl : -0.0237487 20.mdl : -0.0209156 21.mdl : -0.00755152 22.mdl : -0.0101945 23.mdl : -0.00779286 24.mdl : -0.00615664 25.mdl : -0.00456561
今回のケースでは、「25.mdl」の「-0.00456561」がもっとも良い値なので、この値を基準に、さらに良好な値を得られるようなスケールを求める。
スケールの最適解は準ニュートン法(L-BFGS)を使って求める。
ソースの抜粋(nnet2/combine-nnet-fast.cc)
OptimizeLbfgs<double> lbfgs(params_, lbfgs_options); // Loop 10 times for (int32 i = 0; i < config_.num_lbfgs_iters; i++) { // スケールをセット params_.CopyFromVec(lbfgs.GetProposedValue()); // Propagateの値とgradientを求める objf = ComputeObjfAndGradient(&gradient, ®ularizer_objf); if (i == 0) { initial_objf = objf; initial_regularizer_objf = regularizer_objf; } // 判定とスケールの更新 lbfgs.DoStep(objf, gradient); } // end of for(i)
各ループ段階におけるPropagateの値と判定は以下のとおりであった。
(i=0) -0.00456561 (i=1) -0.310829 action = decrease (i=2) -0.0201834 action = decrease (i=3) -0.00512826 action = decrease (i=4) -0.00410415 action = accept (i=5) -0.00352887 action = accept (i=6) -0.00315388 action = accept (i=7) -0.00228854 action = accept (i=8) -0.000916785 action = accept (i=9) -0.000539656 action = accept
ループカウンタが「9」の時の値「-0.000539656」がもっとも良いので、この時のスケールが最適解となる。
params_ = lbfgs.GetValue(&objf);
音声認識メモ(Kaldi)その22(アライメント)
decodeの過程を掘り下げてみる。
アライメントで出力される数値(インプットであるMFCC特徴量の各フレームに1対1で紐付けられる)は何を示しているか。
今回は、デコードシェル(egs/wsj/s5/steps/decode.sh)の内部でコールしているlattice生成コマンドのアウトプットを見てみる。
(追加オプションとして、「words-wspecifier」と「alignments-wspecifier」を指定)
gmm-latgen-faster \ --max-active=7000 \ --beam=13.0 \ --lattice-beam=6.0 \ --acoustic-scale=0.083333 \ --allow-partial=true \ --determinize-lattice=true \ --word-symbol-table=exp/mono/graph/words.txt \ exp/mono/final.mdl \ # model-in exp/mono/graph/HCLG.fst \ # fst-in ark:/tmp/utter_053.ark \ # features-rspecifier ark:/tmp/lat.ark \ # lattice-wspecifier ark:/tmp/word.ark \ # words-wspecifier ark:/tmp/ali.ark # alignments-wspecifier
インプット
音声(/tmp/utter_053.ark)
前回、検証用に使った、”禁煙席お願いします”という発話。
(フレーム数は「323」、識別子は"utterance_id_053"、39次元)
以下のコマンドで生成。
apply-cmvn \ --utt2spk=ark:data/test/split1/1/utt2spk \ scp:data/test/split1/1/cmvn.scp \ scp:data/test/split1/1/feats.scp \ ark:- | \ add-deltas \ ark:- \ ark:/tmp/hoge.ark
モデル
前回、作成したモノフォンのモデル(言語モデルは2-gramで作成)
exp/mono/graph/words.txt(抜粋)
<eps> 0 !SIL 1 <UNK> 2 あり 3 お 4 お願い 5 し 10 ます 23 席 45 禁煙 53 #0 62 <s> 63 </s> 64
exp/mono/graph/phones/align_lexicon.txt (抜粋)
<eps> <eps> sil 禁煙 禁煙 k_B i_I N_I e_I N_E 席 席 s_B e_I k_I i_E お願い お願い o_B n_I e_I g_I a_I i_E し し sh_B i_E ます ます m_B a_I s_I u_E
アウトプット
words(tmp/word.ark)
utterance_id_053 53 45 5 10 23
symbolに直すと、「禁煙(53) 席(45) お願い(5) し(10) ます(23)」
lattice (/tmp/lat.ark) ※説明用に一部加工
utterance_id_053 0 1 53 9.81125, 7750.02, 2_1_1_1_8_5_5_5_18_17_17_17_17_17_... 1 2 45 2.38995, 4952.66, 197_197_197_197_197_197_197_197_... 2 3 5 4.61863, 8155.52, 527_527_527_527_527_527_527_527_... 3 4 10 2.22007, 3096.31, 527_926_925_925_925_925_925_925_... 4 5 23 5.81478, 6656.75, 523_526_528_638_637_640_639_639_... 5 0, 0, 917_917_917_917_917_917_917_917_...
アライメント結果(/tmp/ali.ark)
utterance_id_053 2 1 1 1 8 5 5 5 18 17 17 17 17 17 17 17 17 17 17 ...
MFCC特徴量フレームと同数の「323」個ある
アライメントを音素に変換する。
ali-to-phones \ --write-lengths \ exp/mono/final.mdl \ ark:/tmp/ali.ark \ ark:/tmp/ali2phone.ark
アライメント結果(/tmp/ali2phone.ark) ※説明用に音素をsymbolに置き換え
utterance_id_053 sil 31 ; k_B 5 ; i_I 12 ; N_I 39 ; e_I 6 ; N_E 3 ; s_B 3 ; e_I 22 ; k_I 3 ; i_E 12 ; o_B 28 ; n_I 14 ; e_I 3 ; g_I 13 ; a_I 4 ; i_E 14 ; sh_B 28 ; i_E 5 ; m_B 38 ; a_I 14 ; s_I 21 ; u_E 5
symbolの後ろは出現数。例えば「sil 31」は"sil"が31回続いたことを示す
改めて今回のテーマ、アライメント時に出力されているものは何か。
「ali-to-phones」コマンドに渡しているインプットを見る限り、モデル(*.mdl)の情報から導出できる。
(アライメントから音素への変換だけなら、FSTのグラフは使わない)
モデル生成時のインプットとなる「phones.txt」の中身は全部で「171」個あり、
exp/mono/phones.txt
<eps> 0 sil 1 sil_B 2 sil_E 3 sil_I 4 sil_S 5 spn 6 spn_B 7 spn_E 8 spn_I 9 spn_S 10 N_B 11 N_E 12 N_I 13 N_S 14 a_B 15 a_E 16 a_I 17 a_S 18 ... z_E 164 z_I 165 z_S 166 #0 167 #1 168 #2 169 #3 170
この内、モデルの「TopologyEntry」として定義されるのは「166」個。
<ForPhones> 1 2 3 4 5 6 7 8 9 10 </ForPhones> <ForPhones> 11 12 13 14 15 16 17 18 ... 164 165 166 </ForPhones>
phone-idが1から10まで(silence phone)は「5」状態、11から166まで(non silence phone)は「3」状態となる。
3 state HMM
pdf-class数は「3」、遷移数は「6」
5 state HMM
pdf-class数は「5」、遷移数は「18」
音素数 x 状態の総数は「518」(5状態 x 10音素 + 3状態 x 156音素)
この「518」個の1つ1つにpdfを定義するわけではなく、似たような音素x状態はpdfを共有する。
状態間でpdfを共有し、pdfの総数は「127」になる。
<NUMPDFS> 127
状態遷移については、総数は「1116」になる(18遷移 x 10音素 + 6遷移 x 156音素)
(「LogProbs」エントリと同数)
<LogProbs> [ 0 -0.3281818 -1.378509 -4.027719 -4.60517 ... -1.386294 -0.2876821 -1.386294 ] </LogProbs>
アライメントで出力されているのは、状態遷移の識別子(transition-id)にあたる。
例えば、話し始め部分の「sil」については「2 1 1 1 8 5 5 5 18 17 17 17 17 17 17 17 17 17 17 ...」と並ぶ。
番号の振り方については、self-loopの遷移を後から追加するらしく、あるstateを見た時、self-loopの方がtransition-idが大きくなる。
音声認識メモ(Kaldi)その21(音韻モデル)
認識対象を孤立単語から発展させて、もう少し実用的な使い方を試してみる。
まず、トレーニング対象の発話を音素に分解する。
元の文
オススメの料理は何ですか
単語を音素列へ分解(「Julius」付属の「yomi2voca.pl」を使用)
オススメ o s u s u m e の n o 料理 ry o u r i は w a 何 n a N です d e s u か k a
音素については、無音(sp、sil)を除けば「40」種類。
音素表
(引用元)
http://winnie.kuis.kyoto-u.ac.jp/dictation/doc/phone_m.pdf
この内、「dy」(「ぢゃ」、「ぢゅ」、「ぢょ」)については対象外とし、
「39」種類の音素全部を使って20種類の文を作り、それぞれ3回ずつ音声データ60個分を用意した。
発話文(20種類)
オススメ の 料理 は 何 です か
o s u s u m e n o ry o u r i w a n a N d e s u k a
百 十 番 テーブル へ どうぞ
hy a k u j u u b a N t e: b u r u e d o u z o
ラーメン と 餃子 の セット を 1つ お願い し ます
r a: m e N t o gy o u z a n o s e q t o o h i t o ts u o n e g a i sh i m a s u
メニュー お願い し ます
m e ny u: o n e g a i sh i m a s u
禁煙 席 お願い し ます
k i N e N s e k i o n e g a i sh i m a s u
お 水 4つ お願い し ます
o m i z u y o q ts u o n e g a i sh i m a s u
フォーク 2つ お願い し ます
f o: k u f u t a ts u o n e g a i sh i m a s u
コーヒー は 食後 に お願い し ます
k o: h i: w a sh o k u g o n i o n e g a i sh i m a s u
ソフトドリンク は あり ます か
s o f u t o d o r i N k u w a a r i m a s u k a
持ち帰り に でき ます か
m o ch i k a e r i n i d e k i m a s u k a
別々 に でき ます か
b e ts u b e ts u n i d e k i m a s u k a
ごちそうさま でし た
g o ch i s o u s a m a d e sh i t a
牛肉 に し て ください
gy u u n i k u n i sh i t e k u d a s a i
キャベツ は お 代わり 自由 です
ky a b e ts u w a o k a w a r i j i y u u d e s u
サプライズ は でき ます か
s a p u r a i z u w a d e k i m a s u k a
シャンパン を ください
sh a N p a N o k u d a s a i
かんぴょう を ください
k a N py o u o k u d a s a i
食事 は ビュッフェ スタイル です
sh o k u j i w a by u q f e s u t a i r u d e s u
みょうが を 添え て ください
my o u g a o s o e t e k u d a s a i
コーヒー と 紅茶 どちら に し ます か
k o: h i: t o k o u ch a d o ch i r a n i sh i m a s u k a
(参考) data/lang/phones.txt
<eps> 0 sil 1 sil_B 2 sil_E 3 sil_I 4 sil_S 5 spn 6 spn_B 7 spn_E 8 spn_I 9 spn_S 10 N_B 11 N_E 12 N_I 13 N_S 14 a_B 15 a_E 16 a_I 17 a_S 18 <snip> z_B 163 z_E 164 z_I 165 z_S 166
「禁煙 席 お願い し ます」という音声データ1個分を検証用データ、
残り59個の音声データをトレーニング用として試したところ、各モデルでのデコード結果は以下のとおりとなった。
モノフォン(mono)
1-gram
utterance_id_053 禁煙 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -8.01345 over 323 frames.
2-gram
utterance_id_053 禁煙 席 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -7.97456 over 323 frames.
gmm-info実行結果
gmm-info exp/mono/final.mdl number of phones 166 number of pdfs 127 number of transition-ids 1116 number of transition-states 518 feature dimension 39 number of gaussians 1004
モデルのNUMPDFS(pdf-class数) : 127 ( 5hmm_state * 2phone + 3hmm_state * 29phone )
トライフォン(tri1)
1-gram
utterance_id_053 禁煙 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -7.9652 over 323 frames.
2-gram
utterance_id_053 禁煙 席 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -7.92976 over 323 frames.
gmm-info実行結果
gmm-info exp/tri1/final.mdl number of phones 166 number of pdfs 152 number of transition-ids 1740 number of transition-states 830 feature dimension 39 number of gaussians 977
トライフォン(tri2b、LDA+MLLT)
1-gram
utterance_id_053 禁煙 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -4.92207 over 323 frames.
2-gram
utterance_id_053 禁煙 お願い し ます LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -4.89113 over 323 frames.
gmm-info実行結果
gmm-info exp/tri2b/final.mdl number of phones 166 number of pdfs 168 number of transition-ids 2246 number of transition-states 1083 feature dimension 40 number of gaussians 970
DNN(nnet4c)
1-gram
utterance_id_053 禁煙 お願い し ます LOG (nnet-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance utterance_id_053 is -0.543315 over 323 frames.
nnet-am-info実行結果
nnet-am-info exp/nnet4c/final.mdl num-components 9 num-updatable-components 3 left-context 4 right-context 4 input-dim 40 output-dim 192 parameter-dim 446703 component 0 : SpliceComponent, input-dim=40, output-dim=360, context=-4 -3 -2 -1 0 1 2 3 4 component 1 : FixedAffineComponent, input-dim=360, output-dim=360, <snip> component 2 : AffineComponentPreconditionedOnline, input-dim=360, output-dim=375, <snip> component 3 : TanhComponent, input-dim=375, output-dim=375 component 4 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=375, <snip> component 5 : TanhComponent, input-dim=375, output-dim=375 component 6 : AffineComponentPreconditionedOnline, input-dim=375, output-dim=453, <snip> component 7 : SoftmaxComponent, input-dim=453, output-dim=453 component 8 : SumGroupComponent, input-dim=453, output-dim=192 prior dimension: 192, prior sum: 1, prior min: 1e-20
"禁煙"の後ろの"席"が欠落してしまっているケースがあるが、トレーニング用データ数や重みパラメータによって結果は変わってくると予想される。
また、今回、例えば「料理」という単語に関しては、「ry o u r i」としたが、「ry o: r i」(りょーり)でも認識できるようにすると良いと思われる。
このあたりは、話し言葉という領域の奥深さを感じる。
音声認識メモ(Kaldi)その20(トレーニング Dan's DNN(nnet2))
nnet2での学習の流れを追ってみる。
今回は、活性化関数に「tanh」を使った「nnet4c」を対象とした。
${KALDI_ROOT}/egs/rm/s5/local/nnet2/run_4c.sh
# for CPU only (with --use-gpu false). steps/nnet2/train_tanh_fast.sh \ --stage -10 \ --minibatch-size 128 \ --num-epochs 20 \ --add-layers-period 1 \ --num-hidden-layers 2 \ --mix-up 4000 \ --initial-learning-rate 0.02 \ --final-learning-rate 0.004 \ --hidden-layer-dim 375 \ data/train \ # <data> data/lang \ # <lang> exp/tri3b_ali \ # <ali-dir> exp/nnet4c_manual # <exp-dir>
内部処理の確認。
stage: -4
steps/nnet2/get_lda.sh \ --transform-dir exp/tri3b_ali \ --splice-width 4 \ data/train \ data/lang \ exp/tri3b_ali \ exp/nnet4c
stage: -3
トレーニングデータを"validation"用と"training"用に分ける。
steps/nnet2/get_egs.sh \ --transform-dir exp/tri3b_ali \ --splice-width 4 \ --stage 0 \ data/train \ data/lang \ exp/tri3b_ali \ exp/nnet4c
stage: -2
初期モデルを作成。
nnet-am-init \ exp/tri3b_ali/tree \ data/lang/topo \ 'nnet-init exp/nnet4c/nnet.config -|' \ exp/nnet4c/0.mdl
==>モデルのComponent数は「6」
Splice / FixedAffine / AffinePre / Tanh / AffinePre / Softmax
"AffinePre"は"AffineComponentPreconditionedOnline"の略
stage: -1
transition probabilities(遷移確率)の更新
nnet-train-transitions \ exp/nnet4c/0.mdl \ 'ark:gunzip -c exp/tri3b_ali/ali.*.gz|' \ exp/nnet4c/0.mdl
ここから、ループする。
ループ回数は「25」($num_epochs + $num_epochs_extra)。
- カウンタが「1」で隠れ層の追加
==> モデルのComponent数が「8」になる("Tanh"と"AffinePre"を追加)
Splice / FixedAffine / AffinePre / Tanh / AffinePre / Tanh / AffinePre / Softmax
- カウンタが「13」でmixup
==> モデルのComponent数が「9」になる("SumGroup"を追加)
Splice / Fixed Affine / AffinePre / Tanh / AffinePre / Tanh / AffinePre / Softmax / SumGroup
ループの各段階でやっていることを書き出してみると下表のとおり。
mixup後のモデルを使って「final.mdl」を生成する。
nnet-combine-fast \ exp/nnet4c/14.mdl \ exp/nnet4c/15.mdl \ <snip> \ exp/nnet4c/25.mdl \ ark:exp/nnet4c/egs/combine.egs \ # <valid-examples-in> exp/nnet4c/final.mdl
音声認識メモ(Kaldi)その19(Toolkitスクリプト(3))
前回の「Kaldi for Dummies tutorial」では、トライフォンの初期学習までであった。
TRI1 - simple triphone training (first triphone pass).
この後の処理を確認してみる。
「egs/rm/s5/RESULTS」には各実装(experiments)でのWERが出力されており、いくつかを書き並べてみると以下のとおり。
mono
Monophone, MFCC + delta + accel
tri1
MFCC + delta + accel
tri2a
MFCC + delta + accel (on top of better alignments)
tri2b
LDA + MLLT
tri3b
LDA + MLLT + SAT
tri3c
raw-fMLLR ( fMLLR on the raw MFCCs )
sgmm2_4[a-c]
SGMM2 is a new version of the code that has tying of the substates a bit like "state-clustered tied mixture" systems; and which has speaker-dependent mixture weights.
nnet4[a-e]
Deep neural net -- various types of hybrid system.
dnn4b
MFCC, LDA, fMLLR feaures, (Karel - 30.7.2015)
cnn4c
FBANK + pitch features, (Karel - 30.7.2015)
この中で、「nnet4d」(nnet2のプライマリレシピ)をターゲットとして、triphone初期モデル(tri1)までの流れを逆にたどってみる。
(GPUを使わない環境で試しているので、GPUを使用しない条件下で確認)
公式サイトの説明より、「rm/s5/local/run_nnet2.sh」が起点となるスクリプトであることを確認。
The first place to look to get a top level overview of the neural net training is probably the scripts. In the standard example scripts in egs/rm/s5, egs/wsj/s5 and egs/swbd/s5b, the top-level script is run.sh. This script calls (sometimes commented out) a script called local/run_nnet2.sh. This is the top-level example script for Dan's setup.
rm/s5/local/run_nnet2.shより抜粋
# **THIS IS THE PRIMARY RECIPE (40-dim + fMLLR + p-norm neural net)** local/nnet2/run_4d.sh --use-gpu false
egs/rm/s5/local/nnet2/run_4d.shより抜粋
steps/nnet2/train_pnorm_fast.sh data/train \ data/lang \ exp/tri3b_ali \ exp/nnet4d
トレーニングのアウトプット「exp/nnet4d」を作成するには、インプットとしてアライメントデータ「exp/tri3b_ali」が必要。
egs/rm/s5/run.shより抜粋
# Align all data with LDA+MLLT+SAT system (tri3b) steps/align_fmllr.sh \ --use-graphs true \ data/train \ data/lang \ exp/tri3b \ exp/tri3b_ali
アライメントのアウトプット「exp/tri3b_ali」を作成するには、インプットとして「exp/tri3b」が必要。
egs/rm/s5/run.shより抜粋
## Do LDA+MLLT+SAT steps/train_sat.sh \ 1800 \ # <#leaves> 9000 \ # <#gauss> data/train \ # <data> data/lang \ # <lang> exp/tri2b_ali \ # <ali-dir> exp/tri3b # <exp-dir>
トレーニングのアウトプット「exp/tri3b」を作成するには、インプットとしてアライメントデータ「exp/tri2b_ali」が必要。
egs/rm/s5/run.shより抜粋
# Align all data with LDA+MLLT system (tri2b) steps/align_si.sh \ --use-graphs true \ data/train \ data/lang \ exp/tri2b \ exp/tri2b_ali
アライメントデータ「exp/tri2b_ali」を作成するには、インプットとして「exp/tri2b」が必要。
egs/rm/s5/run.shより抜粋
# train and decode tri2b [LDA+MLLT] steps/train_lda_mllt.sh \ 1800 \ # <#leaves> 9000 \ # <#gauss> data/train \ # <data> data/lang \ # <lang> exp/tri1_ali \ # <ali-dir> exp/tri2b # <exp-dir>
トレーニングデータ「exp/tri2b」を作成するには、インプットとしてアライメントデータ「exp/tri1_ali」が必要。
egs/rm/s5/run.shより抜粋
# align tri1 steps/align_si.sh \ --use-graphs true \ data/train \ data/lang \ exp/tri1 \ exp/tri1_ali
アライメントデータ「exp/tri1_ali」を作成するには、インプットとしてトレーニングデータ「exp/tri1」が必要。
「exp/tri1」から[exp/nnet4d]までの流れを書き出してみると以下のとおり。
1. トライフォンモデル(MFCC + delta + accel)を使ったアライメント
アウトプットは「exp/tri1_ali」
2. トライフォンモデル(LDA + MLLT)の作成と学習
アウトプットは「exp/tri2b」
3. トライフォンモデル(LDA + MLLT)を使ったアライメント
アウトプットは「exp/tri2b_ali」
4. トライフォンモデル(LDA + MLLT + SAT)の作成と学習
アウトプットは「exp/tri3b」
5. トライフォンモデル(LDA + MLLT + SAT)を使ったアライメント
アウトプットは「exp/tri3b_ali」
6. Neural Networkモデルの作成と学習
アウトプットは「exp/tri4d」
音声認識メモ(Kaldi)その18(Toolkitスクリプト(2))
自前で用意した音声データを認識させる手順はKaldi for Dummies tutorialに説明されている。
"for Dummies"("サルでも分かる")という位だから、「yes/no」サンプル(前回の記事)の次に試すのはこれがいいのだろう。
流れを大まかに書き出してみると以下のとおり。
- Download Kaldi (GitHub から clone)
- Data preparation ( 音声データと言語データの準備 )
- Project finalization (Scoring scriptをコピー / SRILM インストール / Configファイル作成)
- Running scripts creation (cmd.sh / path.sh / run.sh 作成)
- Getting results (run.sh 実行)
言語モデルについては、Juliusの場合、連続単語なら「N-gram」か「DFA」(前回の記事)、孤立単語なら"-w"オプション(前回の記事)が用意されていたが、Kaldiの場合、「N-gram」択一の模様。
N-gramを作るための、言語モデルToolkitはいくつかある。
自然言語処理ツール
言語モデル構築Toolメモ - Negative/Positive Thinking
チュートリアルどおりにSRILMを使うとする。
最新バージョンは「1.7.2」(更新日は「9 November 2016」)
今回は、「Running scripts creation」項の「run.sh」の流れを追ってみる。
スクリプト内の流れを大まかに書き出してみると以下のとおり。
- 音声データ準備(発話と話者の紐付け(話者が一人だと警告が出る)、特徴量抽出)
- 言語モデル準備(WFST化、GrammarとLexicon)
- モノフォンモデルの作成と学習
- モノフォンモデルを使ったデコード
- モノフォンモデルを使ったアライメント(トライフォンモデル作成のインプットになる)
- トライフォンモデルの作成と学習
- トライフォンモデルを使ったデコード
「run.sh」を実行するにあたっては、以下のファイルが用意されていればOK。
% tree --charset C . |-- cmd.sh |-- conf | |-- decode.config | `-- mfcc.conf |-- data | |-- local | | |-- corpus.txt | | `-- dict | | |-- lexicon.txt | | |-- nonsilence_phones.txt | | |-- optional_silence.txt | | `-- silence_phones.txt | |-- test | | |-- text | | |-- utt2spk | | `-- wav.scp | `-- train | |-- text | |-- utt2spk | `-- wav.scp |-- local | `-- score.sh |-- path.sh |-- run.sh |-- steps -> ${KALDI_ROOT}/egs/wsj/s5/steps `-- utils -> ${$KALDI_ROOT}/egs/wsj/s5/utils
「data/train」と「data/test」が音声データ。
「data/train」が学習用、「data/test」が検証用で、今回は"もしもし"という発話を3回分、3ファイル用意した。
(学習用に2ファイル、テスト用に1ファイル)
「data/local」が言語データ。
"もしもし"に加え、同じ音素で表現できる単語2つ("もも"(桃)、"いも"(芋))を加えた3つ
「run.sh」内部の処理を順に見てみる。
1. 音声データ準備
# ===== PREPARING ACOUSTIC DATA ===== # Making spk2utt files utils/utt2spk_to_spk2utt.pl data/train/utt2spk > data/train/spk2utt utils/utt2spk_to_spk2utt.pl data/test/utt2spk > data/test/spk2utt # ===== FEATURES EXTRACTION ===== # Making feats.scp files steps/make_mfcc.sh data/train exp/make_mfcc/train $mfccdir steps/make_mfcc.sh data/test exp/make_mfcc/test $mfccdir # Making cmvn.scp files steps/compute_cmvn_stats.sh data/train exp/make_mfcc/train $mfccdir steps/compute_cmvn_stats.sh data/test exp/make_mfcc/test $mfccdir
2. 言語モデル準備
# ===== PREPARING LANGUAGE DATA ===== utils/prepare_lang.sh \ data/local/dict \ "<UNK>" \ data/local/lang \ data/lang # ===== MAKING lm.arpa ===== lm_order=1 # language model order (n-gram quantity) ngram-count \ -order $lm_order \ -write-vocab \ data/local/tmp/vocab-full.txt \ -wbdiscount \ -text data/local/corpus.txt \ -lm data/local/tmp/lm.arpa # ===== MAKING G.fst ===== arpa2fst \ --disambig-symbol=#0 \ --read-symbol-table=data/lang/words.txt \ data/local/tmp/lm.arpa \ data/lang/G.fst
Lexicon(L.fst)
3. モノフォンモデルの作成と学習
steps/train_mono.sh \ data/train \ # <data-dir> data/lang \ # <lang-dir> exp/mono # <exp-dir>
スクリプト内部でコールしているKaldiコマンドは以下のとおり。
"stage"という変数を持っており、途中から再開できるようにしている。
stage: -3
# Initialize monophone GMM gmmbin/gmm-init-mono
stage: -2
# Creates training graphs(without transition-probabilities, by default) bin/compile-train-graphs
stage: -1
均等アライメントをもとに統計量を作成
# Write an equally spaced alignment(for getting training started) bin/align-equal-compiled # Accumulate stats for GMM training gmmbin/gmm-acc-stats-ali
stage: 0
# Do Maximum Likelihood re-estimation of GMM-based acoustic model gmmbin/gmm-est
iteration (トレーニングの回数はデフォルトで40回)
# Modify GMM-based model to boost gmmbin/gmm-boost-silence # Align features given [GMM-based] models gmmbin/gmm-align-compiled # Accumulate stats for GMM training gmmbin/gmm-acc-stats-ali # (Above-mentioned ( stage 0 )) gmmbin/gmm-est
"exp/mono/final.mdl"がアウトプットとなる。
4. モノフォンモデルを使ったデコード
言語データ(”data/lang/L.fst"、"data/lang/G.fst"、他)をもとに、HMM stateがinputとなる単語グラフ"HCLG.fst"を生成する。
utils/mkgraph.sh \ --mono \ data/lang \ # <lang-dir> exp/mono \ # <model-dir> exp/mono/graph # <graphdir>
"--mono"オプションは廃止の模様。
Note: the --mono, --left-biphone and --quinphone options are now deprecated and will be ignored.
"gmmbin/gmm-latgen-faster"コマンドを使ってdecodeを実行。
音声データはテスト用のもの(学習時のものとは異なる)
steps/decode.sh \ --config conf/decode.config \ exp/mono/graph \ # <graph-dir> data/test \ # <data-dir> exp/mono/decode # <decode-dir>
5. モノフォンモデルを使ったアライメント
steps/align_si.sh \ data/train \ # <data-dir> data/lang \ # <lang-dir> exp/mono \ # <src-dir> exp/mono_ali # <align-dir>
アライメント結果(exp/mono_ali/ali.1.gz)
utterance_id_001 2 1 1 1 1 1 8 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 18 17 17 206 208 207 210 242 244 246 245 245 245 245 266 265 265 265 268 267 267 267 267 267 267 267 267 270 269 269 269 269 269 194 193 193 193 196 195 195 195 195 198 197 197 197 218 217 217 217 217 220 219 219 219 219 222 221 221 242 244 246 245 245 245 245 245 245 245 245 245 245 245 245 245 245 245 266 268 270 269 269 269 269 269 269 269 269 269 269 269 188 190 189 189 189 189 189 189 192 191 191 191 191 191 3 1 1 1 1 1 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 7 5 5 14 15 15 15 15 15 15 15 15 15 15 12 10 10 10 10 10 10 10 10 10 10 10 10 10 18 utterance_id_002 2 8 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 18 17 17 17 17 17 17 17 17 206 208 207 207 210 209 209 209 209 209 209 209 209 242 241 241 241 241 241 244 243 243 243 243 243 246 245 245 245 245 245 266 265 265 265 265 268 267 267 267 267 267 267 270 269 269 194 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 193 196 195 198 218 220 222 242 241 241 241 241 241 241 244 246 266 268 270 188 190 192 3 9 10 10 10 10 10 10 6 5 5 9 10 10 10 10 10 10 10 10 10 10 10 10 10 10 6 5 5 5 9 10 10 10 10 10 10 10 10 10 6 5 5 9 10 10 10 10 10 6 5 12 10 10 10 10 18
6. トライフォンモデルの作成と学習
steps/train_deltas.sh \ 2000 \ # <num-leaves> 11000 \ # <tot-gauss> data/train \ # <data-dir> data/lang \ # <lang-dir> exp/mono_ali \ # <alignment-dir> exp/tri1 # <exp-dir>
スクリプト内部でコールしているKaldiコマンドは以下のとおり。
stage: -3
# Accumulate statistics for phonetic-context tree building. bin/acc-tree-stats # Sum statistics for phonetic-context tree building. bin/sum-tree-stats
stage: -2
# Cluster phones (or sets of phones) into sets for various purposes bin/cluster-phones # Compile questions bin/compile-questions # Train decision tree bin/build-tree # Initialize GMM from decision tree and tree stats gmm-init-model # Does GMM mixing up (and Gaussian merging) gmmbin/gmm-mixup
stage: -1
# Convert alignments from one decision-tree/model to another bin/convert-ali
stage: 0
# Creates training graphs (without transition-probabilities, by default) bin/compile-train-graphs
iteration (トレーニングの回数はデフォルトで35回)
# Align features given [GMM-based] models. gmmbin/gmm-align-compiled # Accumulate stats for GMM training. gmmbin/gmm-acc-stats-ali # Do Maximum Likelihood re-estimation of GMM-based acoustic model gmmbin/gmm-est
"exp/tri1/final.mdl"がアウトプットとなる。
7. トライフォンモデルを使ったデコード
モノフォンと同様
utils/mkgraph.sh \ data/lang \ # <lang-dir> exp/tri1 \ # <model-dir> exp/tri1/graph # <graphdir> steps/decode.sh \ --config conf/decode.config \ exp/tri1/graph \ # <graph-dir> data/test \ # <data-dir> exp/tri1/decode # <decode-dir>
音声認識メモ(Kaldi)その17(Toolkitスクリプト)
Kaldiは、Bashスクリプトで実行するコマンドをコントロールしている。
今回はスクリプトについて確認してみる。
GitHubからダウンロードした一式のディレクトリ構成については以下のとおり。
詳細はkaldi公式サイトによる説明を参照("Kaldi directories structure"の項)
GitHub(kaldi公式)
「egs」配下に、各コーパスに対応したサンプルスクリプトが格納されている。
egs – example scripts allowing you to quickly build ASR systems for over 30 popular speech corporas (documentation is attached for each project),
自前で音声データを用意する場合には、どうするか。
Kaldi公式のチュートリアルを読むと、「egs/wsj/s5」配下を流用すればいい旨の説明がある。
Project finalization -> Tools attachment の項より抜粋
From kaldi-trunk/egs/wsj/s5 copy two folders (with the whole content) - utils and steps - and put them in your kaldi-trunk/egs/digits directory.
You can also create links to these directories.
「wsj」はWall Street Journal news textのコーパスらしい。
egs/wsj/README.txt より抜粋
About the Wall Street Journal corpus:
This is a corpus of read sentences from the Wall Street Journal, recorded under clean conditions.
The vocabulary is quite large. About 80 hours of training data.
Available from the LDC as either: [ catalog numbers LDC93S6A (WSJ0) and LDC94S13A (WSJ1) ]
or: [ catalog numbers LDC93S6B (WSJ0) and LDC94S13B (WSJ1) ]
....
他のコーパスのディレクトリ(例えば「egs/rm/steps」を見ても、「egs/wsj/steps」へのシンボリックリンクになっている。
/opt/kaldi/egs/rm/s5% ls -l steps lrwxrwxrwx 1 ichou1 ichou1 18 2月 5 19:46 steps -> ../../wsj/s5/steps /opt/kaldi/egs/rm/s5% file steps steps: symbolic link to `../../wsj/s5/steps' /opt/kaldi/egs/rm/s5%
コーパスは無いが、kaldiを試してみたい場合用に「egs/yesno」が用意されている。
これは音声データ(.wav)も格納されているので、すぐに試せる。
("YES"と"NO"のどちらかを8回、パターンを変えつつ発話。トレーニング用に31ファイル、検証用に29ファイル)
egs/yesno/README より抜粋
The "yesno" corpus is a very small dataset of recordings of one individual saying yes or no multiple times per recording, in Hebrew.
egs/yesno/s5/waves_yesno/README より抜粋
The archive "waves_yesno.tar.gz" contains 60 .wav files, sampled at 8 kHz.
All were recorded by the same male speaker, in English (although the individual is not a native speaker).
In each file, the individual says 8 words;
each word is either "yes" or "no", so each file is a random sequence of 8 yes-es or noes.
There is no separate transcription provided;
the sequence is encoded in the filename, with 1 for yes and 0 for no, for instance:
実行方法
cd egs/yesno/s5 ./run.sh
内部でやっていること
- Data preparation(データ準備)
--> 「local/prepare_dict.sh」、「local/prepare_dict.sh」、「utils/prepare_lang.sh」、「local/prepare_lm.sh」を実行
- Feature extraction(特徴量抽出)
--> 「steps/make_mfcc.sh」、「steps/compute_cmvn_stats.sh」、「utils/fix_data_dir.sh」を実行
(「steps」、「utils」は、「egs/wsj/s5/steps」、「egs/wsj/s5/utils」へのリンク)
- Mono training(モノフォン学習)
--> 「steps/train_mono.sh」を実行
- Graph compilation(グラフ作成)
--> 「utils/mkgraph.sh」を実行
- Decoding(認識)
--> 「steps/decode.sh」を実行
実行するとコンソール上には、WER(単語誤り率)が表示される。
decodeの結果は、ログ(egs/yesno/s5/exp/mono0a/decode_test_yesno/log/decode.1.log)で確認できる。
例)「egs/yesno/s5/waves_yesno/1_0_0_0_0_0_0_0.wav」の認識結果
1_0_0_0_0_0_0_0 YES NO NO NO NO NO NO NO
トップディレクトリが「/opt/kaldi」であるとして、docodeを直接実行する場合のコマンド(結果は標準出力にテキスト形式で出力)
decode(lattice無し)
/opt/kaldi/src/gmmbin/gmm-decode-faster \ --word-symbol-table=/opt/kaldi/egs/yesno/s5/exp/mono0a/graph_tgpr/words.txt \ /opt/kaldi/egs/yesno/s5/exp/mono0a/40.mdl \ /opt/kaldi/egs/yesno/s5/exp/mono0a/graph_tgpr/HCLG.fst \ "ark,s,cs:/opt/kaldi/src/featbin/apply-cmvn --utt2spk=ark:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/utt2spk scp:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/cmvn.scp scp:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/feats.scp ark:- | /opt/kaldi/src/featbin/add-deltas ark:- ark:- |" \ ark,t:-
渡しているパラメータについては前回の記事に記載。
結果(lattice無し)
1_0_0_0_0_0_0_0 3 2 2 2 2 2 2 2 1_0_0_0_0_0_0_0 YES NO NO NO NO NO NO NO LOG (gmm-decode-faster[5.3.106~1389-9e2d8]:main():gmm-decode-faster.cc:196) Log-like per frame for utterance 1_0_0_0_0_0_0_0 is -8.37946 over 668 frames.
「3」はwords.txt上の"YES"、「2」は"NO"に対応
decode(lattice有り)
/opt/kaldi/src/gmmbin/gmm-latgen-faster \ --word-symbol-table=/opt/kaldi/egs/yesno/s5/exp/mono0a/graph_tgpr/words.txt \ /opt/kaldi/egs/yesno/s5/exp/mono0a/final.mdl \ /opt/kaldi/egs/yesno/s5/exp/mono0a/graph_tgpr/HCLG.fst \ "ark,s,cs:/opt/kaldi/src/featbin/apply-cmvn --utt2spk=ark:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/utt2spk scp:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/cmvn.scp scp:/opt/kaldi/egs/yesno/s5/data/test_yesno/split1/1/feats.scp ark:- | /opt/kaldi/src/featbin/add-deltas ark:- ark:- |" \ ark,t:-
結果(lattice有り)
1_0_0_0_0_0_0_0 YES NO NO NO NO NO NO NO 1_0_0_0_0_0_0_0 0 1 3 9.34174, 10746.4, 4_1_1_1_1_1_16_18_<snip> 1 2 2 3.00029, 3604.42, 15_15_15_15_15_15_<snip> 2 3 2 3.75534, 460.406, 29_29 3 4 2 6.37105, 626.19, 4 5 2 5.32006, 589.474, 5 6 2 5.67636, 4377.79, 6 7 2 5.32006, 596.049, 7 8 2 4.3186, 6239.1, 8 9 2 5.85963, 5268.64, 29_29_29_29 8 9.50533, 28208.8, 29_29_29_29_4_1_1_1_<snip> 9 7.30095, 22958.9, 26_28_30_4_16_15_15_<snip> LOG (gmm-latgen-faster[5.3.106~1389-9e2d8]:DecodeUtteranceLatticeFaster():decoder-wrappers.cc:286) Log-like per frame for utterance 1_0_0_0_0_0_0_0 is -8.37946 over 668 frames.
モデル(egs/yesno/s5/exp/mono0a/final.mdl)をテキスト形式にしたもの
<TransitionModel> <Topology> <TopologyEntry> <ForPhones> 2 3 </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.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> <Triples> 11 1 0 0 1 1 1 1 2 2 1 3 3 1 4 4 2 0 5 2 1 6 2 2 7 3 0 8 3 1 9 3 2 10 </Triples> <LogProbs> [ 0 -0.3016863 -4.60517 -2.116771 -2.040137 -0.05096635 -4.60517 -3.516702 -4.60517 -4.60517 -0.09362812 -2.668062 -4.60517 -4.60517 -4.60517 -0.1123881 -2.449803 -0.04502614 -3.122941 -0.3431785 -1.236192 -0.1315082 -2.09372 -0.07189104 -2.668334 -0.1359556 -2.062634 -0.09793975 -2.371973 -0.04792399 -3.062005 ] </LogProbs> </TransitionModel> <DIMENSION> 39 <NUMPDFS> 11 <DiagGMM> <GCONSTS> [ -162.6711 -100.3258 -150.894 -774.145 <snip> ] <WEIGHTS> [ 0.02608728 0.03167231 0.03214631 0.03326807 0.01074118 <snip>] <MEANS_INVVARS> [ -3.798081 -5.357131 0.8406813 0.918729 1.014658 "snip" 0.5328674 1.181959 -0.6352269 -0.7017035 -0.06531551 "snip" ] <INV_VARS> [ 0.2399497 0.4042536 0.2387805 0.09193342 0.04029746 "snip" 0.282881 0.1213772 0.07582887 0.03232023 0.03635461 "snip" ] </DiagGMM> <DiagGMM> "snip" </DiagGMM> ( 10 times repeat )
「YES」(/jes/)を「j-e+s」 のように区切るのではなく、まとまりとして扱っている。
phone transcriptions(egs/yesno/s5/data/local/dict/lexicon.txt)
<SIL> SIL YES Y NO N
decision tree description (egs/yesno/s5/exp/mono0a/tree)