音声認識メモ(Kaldi)その7(decode with Karel's DNN)

Deep Neural Network Featuresを使ったdecodeを試してみる。
公式サイトKaldi: Deep Neural Networks in Kaldiによると3種類あるらしい。

  1. nnet(Karel氏による)
  2. nnet2(Dan氏による)
  3. 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をつなぎ合わせたもの。
f:id:ichou1:20180329132230p:plain

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個分のインプットとアウトプットをプロットしたもの。
f:id:ichou1:20180330113546p:plain

続けて、線形変換、活性化関数適用を数回繰り返して、最後の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)になる。