音声認識メモ(HMM)その3(HTK HCopyコマンド(3))

"もしもし"というWAVE音声データをプロットしてみると以下のとおりであった。

f:id:ichou1:20171009113157p:plain

  • 発声の時間は2秒間で、サンプリングレートは16kHz(出力結果として得られるサンプル数は32,000)
  • 1サンプルは2バイト(C言語でのshort(−32,768〜 32,767))の範囲の値を取る
  • 横軸がサンプリング番号(1〜32000)、縦軸が10進数に変換した波形データ


このデータから、1フレーム分(400サンプル、25ms)を切り出すと以下のとおり。

フレーム番号:1(サンプル番号1〜400)

f:id:ichou1:20171009114440p:plain

フレーム番号:2(サンプル番号161〜560)

f:id:ichou1:20171009114608p:plain


フレーム番号「1」のデータに対して、MFCCが計算されるまでをトレースしてみる。

※以降の表は、「HTK」のプログラム内部で保持する配列データを表したもの(上段が配列のインデックス、下段が配列の値)

フレーム番号:1(サンプル番号1〜400)の配列

f:id:ichou1:20171009115618p:plain

プリエンファシス係数を「0.97」とした場合の変換後の状態

f:id:ichou1:20171009115855p:plain

変換は、隣り合う前後の差を求めたもの(↓)
波形の勾配が大きい(=高周波数)ほど値の絶対値が大きくなる

s[400] = s[400] - (s[399] * 0.97)
s[1]   = s[1] * (1.0 - 0.97)
フレーム番号:1(サンプル番号1〜400)(変換後)

f:id:ichou1:20171012203235p:plain

続けて、ハミング窓を掛けた後の状態

f:id:ichou1:20171009121152p:plain

ハミング窓は、中央の値を強調したもの(↓)
f:id:ichou1:20171009121029p:plain

続けてFFT(フーリエ変換)を適用する。

適用する前の状態(↓)、FFTのサイズは「512」、401〜512は0パディング
f:id:ichou1:20171009121935p:plain

適用した後の状態(↓)
f:id:ichou1:20171210121135p:plain
f:id:ichou1:20171210121151p:plain
ソース上、indexが2の時は「0」をセットしている。

続けて、フィルタバンクにより「24」次元(パラメータ"NUMCHANS"で指定)に変換する。

ソース部分は以下のとおり(HSigP.cのWave2FBank関数より抜粋し加工)

/* Fill filterbank channels */

float real;   		/* real parts */
float imag;   		/* imag parts */
float amplitude;   	/* amplitude of k'th fft channel */

/* fill bins */
/* info.klo:2, info.khi:256 */
for (k = info.klo; k <= info.khi; k++) {             

    real = fft[2*k-1];
    imag = fft[2*k];
  
    amplitude = sqrt(real*real + imag*imag);

    bin = info.loChan[k];
    /* bin range(0-24) */
    /* 
       bin: 0	k:  2 ->   3
       bin: 1	k:  4 ->   6
       bin: 2	k:  7 ->   8
       bin: 3	k:  9 ->  11
       bin: 4	k: 12 ->  15  
       bin: 5	k: 16 ->  19
       ...
       bin: 24	k:231 -> 256
     */

    /* apply lower channel weights */
    /* range: 0 < weight < 1 */
    amplitude2 = amplitude * info.loWeight[k];

    if (bin > 0){ fbank[bin] += amplitude2; }

    /* numChans:24 */
    if (bin < info.numChans){
        fbank[bin+1] += amplitude - amplitude2;
    }
}

f:id:ichou1:20171009130732p:plain

続けて、自然対数 (基数 e) を計算する(log( ))

f:id:ichou1:20171009122757p:plain

続けて、12次元(パラメータ"NUMCEPS"で指定)のCepstral領域に移す。

計算は離散コサイン変換(Discrete Cosine Transform)で行う。使う式は以下(↓)

http://www1.icsi.berkeley.edu/Speech/docs/HTKBook3.2/img171.png


Nはフィルタバンクの次元数(パラメータ"NUMCHANS"で指定した「24」)、mはフィルタバンクの配列

for (i=1; i<=12; i++)  {
    c[i] = 0.0;
    for (j=1; j<=24; j++){
        c[i] += fbank[j] * cos(i * PI / (float)24 * (j-0.5));
    }
    c[i] *= sqrt(2.0/(float)24);
}

計算後の状態(↓)
f:id:ichou1:20171009123451p:plain

続けて、重み付けする。使用する重み付けは以下のとおり。

f:id:ichou1:20171009131247p:plain

重み付けの係数(Cepstral liftering coef)は以下の式のカッコの部分。
http://www1.icsi.berkeley.edu/Speech/docs/HTKBook3.2/img162.png

L(パラメータCEPLIFTER)をデフォルトの「22」とした場合、C言語で書くと、

cepWin[n] = 1.0 + ( (22/2) * sin(n * (PI/22)) );

indexが"11"でピークになる(↓)
f:id:ichou1:20171010194030p:plain

重み付けをした結果

f:id:ichou1:20171009123556p:plain


これがMFCC1次の1〜12のデータに該当する(↓)
f:id:ichou1:20171009124515p:plain