音声認識メモ(Kaldi)その7(decode with Karel's DNN)
Deep Neural Network Featuresを使ったdecodeを試してみる。
公式サイトKaldi: Deep Neural Networks in Kaldiによると3種類あるらしい。
- nnet(Karel氏による)
- nnet2(Dan氏による)
- nnet3(Dan氏による)
今回はKarel氏によるバージョンを試してみる。
結果から。
実行コマンド
bin/decode-faster-mapped \ --word-symbol-table=lang/words.txt \ tri/final.mdl \ HCLG.fst \ ark:mosimosi_loglikes.ark \ ark,t:-
GMM-HMMを使ったdecodeでは特徴量ファイル(13次元、198フレーム)を渡していた部分が、DNNを通したもの(6次元、198フレーム)に置き換わる。
出力結果
utterance_id_001 2 utterance_id_001 MOSIMOSI LOG (decode-faster-mapped[5.3.106~1389-9e2d8]:main():decode-faster-mapped.cc:143) Log-like per frame for utterance utterance_id_001 is 0.67109 over 198 frames. LOG (decode-faster-mapped[5.3.106~1389-9e2d8]:main():decode-faster-mapped.cc:155) Time taken [excluding initialization] 0.0379519s: real-time factor assuming 100 frames/sec is 0.0191676 LOG (decode-faster-mapped[5.3.106~1389-9e2d8]:main():decode-faster-mapped.cc:158) Done 1 utterances, failed for 0 LOG (decode-faster-mapped[5.3.106~1389-9e2d8]:main():decode-faster-mapped.cc:160) Overall log-likelihood per frame is 0.67109 over 198 frames.
mosimosi_loglikes.ark
utterance_id_001 [ 0.1099651 0.09116774 0.081482 0.04679191 0.01571052 0.6548827 0.1098814 0.09139811 0.08160141 0.0467133 0.01567704 0.6547288 0.1108724 0.09039295 0.0817743 0.04703647 0.0159712 0.6539527 (途中省略、194フレーム分) 0.1097786 0.09063407 0.08120951 0.04691367 0.01570774 0.6557564 ]
各フレームのカラム数は「6」(GMM-HMMにおけるpdf数は「6」)、行を足し合わせると「1」になる。
インプットとなるファイルを生成するコマンドは以下を実行。
今回は、流れを確認するのが目的のため、学習を省略した初期状態のNeural Networkを渡している。
nnetbin/nnet-forward \ --feature-transform=final.feature_transform \ nnet_dbn_dnn.init \ ark:mosimosi.ark \ ark:mosimosi_loglikes.ark
final.feature_transform
<Nnet> <Splice> 143 13 [ -5 -4 -3 -2 -1 0 1 2 3 4 5 ] <!EndOfComponent> <AddShift> 143 143 <LearnRateCoef> 0 [ -65.69167 9.436182 10.43176 -2.286287 2.41689 10.67416 -4.945405 0.9329144 5.600904 -9.443085 8.796977 -5.058812 0.7505272 (以降130個は省略) ] <!EndOfComponent> <Rescale> 143 143 <LearnRateCoef> 0 [ 0.05615381 0.05059185 0.07485399 0.1185114 0.09485025 0.07829515 0.07323452 0.09640685 0.08708434 0.07876774 0.09747799 0.1328274 0.1090247 (以降130個は省略)] <!EndOfComponent> </Nnet>
nnet_dbn_dnn.init
<Nnet> <AffineTransform> 2048 143 <LearnRateCoef> 1 <BiasLearnRateCoef> 1 <MaxNorm> 0 [ 0.02285465 -0.005800713 -0.03342053 0.0009499519 0.001076195 0.001254448 -0.005117053 -0.005607297 -0.005614388 -0.003707627 0.03109564 -0.01724246 -0.006429902 0.0242731 (以降130個は省略) ...(以降、2047行省略) ] <-- (2048行 x 143列) [ -9.79805e-05 -0.0001669982 -0.0001459182 -0.0001764622 -0.0001836212 -0.0001546516 -0.0001855577 -9.09675e-05 -0.0001460401 -0.0002300229 -0.0002415479 -0.0001545625 -9.22261e-05(以降2035個は省略) ] <-- (1行 x 2048列) <!EndOfComponent> <Sigmoid> 2048 2048 <!EndOfComponent> ...(途中省略、<AffineTransform>、<Sigmoid>の繰り返し) <AffineTransform> 6 2048 <LearnRateCoef> 1 <BiasLearnRateCoef> 1 <MaxNorm> 0 [ (途中省略) ] <-- (6行 x 2048列) [ 0 0 0 0 0 0 ] <-- (1行 x 6列) <!EndOfComponent> <Softmax> 6 6 <!EndOfComponent> </Nnet>
MFCCに対して以下の順で特徴量変換を実行する。
- Splice
- AddShift
- Rescale
ここで、MFCCは1フレームあたり、「13」次元、spliceは「5」とすると、
spliceの結果、1フレームあたり「143」次元になる。
( 2 * splice + 1) * feat_dim = ( 2 * 5 + 1) * 13 = 11 * 13 = 143
spliceは、前後のフレームのdimentionをつなぎ合わせたもの。
Shiftは足し算、ReScaleは掛け算を実行する(いずれも143次元で実行)。
上記の処理が、ソース中の以下の箇所
// fwd-pass, feature transform, nnet_transf.Feedforward(feats, &feats_transf);
変換結果
(gdb) p feats_transf $334 = {<kaldi::CuMatrixBase<float>> = {data_ = 0x8291440, num_cols_ = 143, num_rows_ = 198, stride_ = 144}, <No data fields>}
変換後のデータ
-0.181488395, 0.827578247, 0.543785274, 0.0556436256, -0.128215984, 0.213598549, 0.592510521, 0.838311195, 0.0181505159, -0.334542274, -1.33981669, -0.902492762, -1.08390939,(以降130個省略) ...(以降197行省略)
続けて、線形変換(AffineTransform)、活性化関数(Sigmoid function)を適用する。
最初のAffine変換で、143次元から2048次元に変換する。
Nnet nnet; nnet.Read(model_filename); (途中省略) // fwd-pass, nnet, nnet.Feedforward(feats_transf, &nnet_out);
1回目のAffineTransform(matrix/kaldi-matrix.cc)
cblas_Xgemm(alpha, // 1 transA, // kaldi::kNoTrans A.data_, // Matrix A(198rows x 143cols) A.num_rows_, // 198 rows A.num_cols_, // 143 cols A.stride_, // 144 transB, // kaldi::kTrans B.data_, // MatrixB(2048rows x 143cols) B.stride_, // 144 beta, // 1 data_, // MatrixC(198rows x 2048cols) num_rows_, // 198 num_cols_, // 2048 stride_ // 2048 );
内部でcblas_dgemmをコールしている。
matrix/cblas-wrappers.h
// C := alpha * AB + beta * C cblas_dgemm(CblasRowMajor, static_cast<CBLAS_TRANSPOSE>(transA), // No-Transpose static_cast<CBLAS_TRANSPOSE>(transB), // Transpose num_rows, // m (MatrixA rows) --> 198 num_cols, // n (MatrixB cols) --> 144 a_num_cols, // k (MatA cols, MatB rows) -->2048 alpha, // alpha --> 1 Adata, // Matrix A --> (198rows x 144cols) a_stride, // k (MatA cols, MatB rows) -->2048 Bdata, // Matrix B --> (2048rows x 144cols) b_stride, // n (MatrixB cols) --> 144 beta, // beta --> 1 Cdata, // Matrix C --> (198rows x 2048cols) stride // n (MatB(transpose) cols) --> 2048 );
結果は198行x2048列の行列になる。
$358 = {data_ = 0xb6ef4020, num_cols_ = 2048, num_rows_ = 198, stride_ = 2048}
これに対して、活性化関数、本実装では標準シグモイド関数(y=1/(1+e^(-x)))を適用する。
下図は、フレーム1に対して先頭50個分のインプットとアウトプットをプロットしたもの。
続けて、線形変換、活性化関数適用を数回繰り返して、最後のAffine変換で2048次元から6次元に変換する。
その後、活性化関数としてsoftmaxを適用する。
matrix/kaldi-vector.cc
template<typename Real> Real VectorBase<Real>::ApplySoftMax() { Real max = this->Max(), sum = 0.0; // data_ = // {1.58165777, 1.39419591, 1.28187716, 0.727205336, -0.364174455, 3.36595106} for (MatrixIndexT i = 0; i < dim_; i++) { sum += (data_[i] = Exp(data_[i] - max)); } // data_ = {0.167915687, 0.13921231, 0.124422282, 0.0714508295, 0.0239898264, 1} // sum = 1.52699089 this->Scale(1.0 / sum); // => 0.6548827544085741 // max = 3.36595106 // Log(sum) = 0.423299074 // max + Log(sum) = 3.78925014 return max + Log(sum); }
data_に対してScaleを掛けたものが出力結果(mosimosi_loglikes.ark)になる。