2019년 7월 13일 토요일

음성인식 메모(kaldi) 7 - decode with Karel's DNN

「原作者へ」

連絡先を存じ上げませんでしたので、不本意ながら無断で翻訳しました。 
正式に翻訳を許可されたいです。 
gogyzzz@gmail.comでご連絡ください。

아래 포스트의 번역입니다.

http://work-in-progress.hatenablog.com/entry/2018/03/29/124545


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에서는 feature vector 파일(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 ]

각 프레임의 column 수는 「6」(GMM-HMM에 있어서 pdf수는 「6」)、row를 덧붙이면 1이 된다.

지금은 흐름을 파악하는 것이 목적이므로, 학습을 생략한 초기 상태의 Neural Network를 전달한다.

input이 되는 파일을 생성하는 커맨드는 다음과 같다.

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> 

nnetdbndnn.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에 대해 아래의 순서로 feature transform을 수행한다.

  • Splice
  • AddShift
  • Rescale

여기서, MFCC를 1프레임마다 13차원, splice를 5로 하면 Splice의 결과는 1프레임마다 143차원이 된다.

( 2 * splice + 1) * feat_dim
= ( 2 * 5 + 1) *13
= 11 * 13
= 143

splice는 앞뒤 프레임의 차원을 이어붙인 것

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) activation (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}

이에 대해 activation function, 실제 소스에서는 표준 sigmoid 함수(y=1/(1+e^(-x)))를 적용한다.

아래 그림은 프레임 1에 대해 앞 50개의 input과 output을 플롯한 것.

이어서, 선형변환, activation 적용을 몇번 반복하여 마지막 Affine변환을 통해 2048차원에서 6차원으로 변환한다.

그 후 activation으로 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을 곱한 것이 출력 결과(mosimosiloglikes.ark)가 된다.

댓글 없음:

댓글 쓰기