ichou1のブログ

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

音声認識メモ(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

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

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

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

続けて、メルフィルタバンクを適用する。

チャネル数はパラメータ"NUMCHANS"で指定する(ここでは「24」)
ソース部分は以下のとおり(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

続けて、対数をとる。

対数を取ることで、信号が掛け算から足し算になり、分離できる。
f:id:ichou1:20171009122757p:plain

続けて、離散コサイン変換(Discrete Cosine Transform)により、Cepstrum領域に移す。

使う式は以下。


\begin{eqnarray}
c_i &=& \sqrt{\frac{2}{N}}\displaystyle \sum_{ j = 1 }^N \ m_j \ cos \left( \frac{ \pi i }{ N } \left( j - 0.5 \right) \right)
\end{eqnarray}
HTK Book 「Cepstral Features」 式(5.14)
http://kom.aau.dk/~tb/kurser/HTKBook/node64_id.html

パラメータ"NUMCEPS"で指定した数の係数を取り出す。
ここでは「12」とする。
「N」はフィルタバンクの次元数(パラメータ"NUMCHANS"で指定した「24」)、「m」はフィルタバンクの配列
変数「i」は「1」から始まっていることに注目。

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

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

続けて、重み付けする。

使用する重み付け係数(Cepstral liftering coef)は以下の式のカッコ部分。
「n」がCepstral領域のindex(1から12)に該当する。


\begin{eqnarray}
c'_n &=& \left( 1 + \frac{L}{2} sin \frac{\pi \ n }{L} \right) c_n
\end{eqnarray}
HTK Book 「Linear Prediction Analysis」 式(5.12)
http://www1.icsi.berkeley.edu/Speech/docs/HTKBook/node283_id.html

L(パラメータCEPLIFTER)をデフォルトの「22」とした場合、indexが"11"でピークになる。
f:id:ichou1:20171009131247p:plain

f:id:ichou1:20200224114953p:plain

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

重み付けをした結果

f:id:ichou1:20171009123556p:plain


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