ichou1のブログ

主に音声認識、時々、データ分析のことを書く

音声認識メモ(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に該当)が合成対象となる。

結果を先に書くと、各モデル、各コンポーネントに対して、以下のようなスケールが得られた。

各モデル、コンポーネントごとのスケール

f:id:ichou1:20180812105103p:plain

スケールを求める過程を追ってみる。


更新における評価の尺度は、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, &regularizer_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」

f:id:ichou1:20180729094226p:plain
f:id:ichou1:20180729095443p:plain

5 state HMM

pdf-class数は「5」、遷移数は「18」

f:id:ichou1:20180729094210p:plain

素数 x 状態の総数は「518」(5状態 x 10音素 + 3状態 x 156音素)
この「518」個の1つ1つにpdfを定義するわけではなく、似たような音素x状態はpdfを共有する。

f:id:ichou1:20180729100930p:plain

状態間で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(音韻モデル)

認識対象を孤立単語から発展させて、もう少し実用的な使い方を試してみる。

まず、トレーニング対象の発話を音素に分解する。

元の文
オススメの料理は何ですか
文を単語へ分解(分かち書き、「MeCab」を使用)
オススメ の 料理 は 何 です か 
単語を音素列へ分解(「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」種類。

音素表

f:id:ichou1:20180722113500p:plain
(引用元)
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

ループの各段階でやっていることを書き出してみると下表のとおり。
f:id:ichou1:20180715130237p:plain

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」サンプル(前回の記事)の次に試すのはこれがいいのだろう。

流れを大まかに書き出してみると以下のとおり。

  1. Download Kaldi (GitHub から clone)
  2. Data preparation ( 音声データと言語データの準備 )
  3. Project finalization (Scoring scriptをコピー / SRILM インストール / Configファイル作成)
  4. Running scripts creation (cmd.sh / path.sh / run.sh 作成)
  5. 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」の流れを追ってみる。

スクリプト内の流れを大まかに書き出してみると以下のとおり。

  1. 音声データ準備(発話と話者の紐付け(話者が一人だと警告が出る)、特徴量抽出)
  2. 言語モデル準備(WFST化、GrammarとLexicon)
  3. モノフォンモデルの作成と学習
  4. モノフォンモデルを使ったデコード
  5. モノフォンモデルを使ったアライメント(トライフォンモデル作成のインプットになる)
  6. トライフォンモデルの作成と学習
  7. トライフォンモデルを使ったデコード

「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)

f:id:ichou1:20180701110517j:plain

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"を生成する。

f:id:ichou1:20180702081326p:plain

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公式)

github.com

「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)

f:id:ichou1:20180630125611p:plain