簡単にできるHTKを使った音声認識（数字の単語認識）実験

# はじめに
HTK(Hidden Markov Model Toolkit)は、隠れマルコフモデル(HMMs)を組み立てるためのツールキットです。
これを使って、数字の音声認識をやりながら、音声認識の仕組みを理解していきたいと思います。

[フリーソフトで作る音声認識システム](https://www.amazon.co.jp/フリーソフトでつくる音声認識システム-パターン認識・機械学習の初歩から対話システムまで-荒木-雅弘/dp/4627847122)
という本を参考にしています。音声認識に関してかなりググってみたのですが、何から手を付けていいかいまいちわからず本を買ってみたところ、自分にとって予想以上に良書でした。

# 環境
Windows10
HKT 3.4.1
WaveSurfer 1.8.8p5-1701260009

# セットアップ
まず、下記サイトからダウンロードします。
http://htk.eng.cam.ac.uk/download.shtml

ダウンロードする際はユーザ登録が必要です。
下記はWindowsの手順です。Linux等の場合は、READMEの該当箇所の手順に従ってください。
Windows用をダウンロードしました。

`HTK-3.4.1.zip`がダウンロードされるので、解凍します。
直下にあるREADMEを参考にコンパイル、インストールします。

1. コマンドプロンプトを開き、ktkフォルダに移動します。

```
>cd /d d:\Programs\htk
```
※解凍先を`d:\Programs\htk`ということで話を進めます。

2. ライブラリとツール用のディレクトリを作成します。

```
>mkdir bin.win32
```
3. VCVA32.batを実行し、Visual Studioビルド環境をロードします。

```
>"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat"
```
※場所は適宜読み替えてください

4. HTK Libraryをビルドします。

```
>cd HTKLib
>nmake /f htk_htklib_nt.mkf all
```
なんやかんやワーニングが出ますが、ビルドが完了します。

5. HTK Toolsをビルドします。

```
>cd ..
>cd HTKTools
>nmake /f htk_htktools_nt.mkf all
>cd ..
>cd HLMLib
>nmake /f htk_hlmlib_nt.mkf all
>cd ..
>cd HLMTools
>nmake /f htk_hlmtools_nt.mkf all
>cd ..
```
これで、先ほど作った、`bin.win32`フォルダにビルドして出来上がったHTK Toolsが入っています。必要に応じてPATHを通したりしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/95d2b1fd-26a8-da9a-3dcf-f8d9ba9e0588.png)

# 音声の録音とラベル付け
音声と録音とラベル付けは、WaveSurferというツールを使います。以下からダウンロードしてください。
http://www.speech.kth.se/wavesurfer/

### 数字音声の保存
WaveSurferの録音機能で、０～９までの音声をそれぞれ3回ずつ保存します。

まずは、設定で、New sound default channelsに1を、New Sound default rateに16000を指定してください。
この16000という数字は、標本化定理に基づいて指定する値です。音声を認識するには8kHz程度の情報が取れればよく、その倍の16kHzを指定すれば標本化定理により元の音を完全に再現できる指定にできます。

「File]-「New」で「Speech analysis」を選んで録音します。1音ごとに「Save As」で保存してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/311ee20f-1ac8-f0fe-8678-59202f8299b6.png)
上記は「１」を録音したところです。

### 音声のラベル付け
録音した音声にラベル付けします。
先ほど録音した音声を「File」-「Open」で開きます。その際、「HTK transcription」を選択することで、ラベルを付けることができるようになります。

無音部分を「sil」、数値部分を「zero」～「nine」というラベルを付けます。
下記はdata0-1.wavでゼロのラベル付けをした例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/74d707d7-9c4a-59e8-df97-ae600ad08e6d.png)
そうしたら、ラベルのところで右クリックをして表示されたポップアップで「Save Transcription As...」を選択し、ラベルを保存します。
これをすべての音声で行います。

data0-1.labには以下のような内容が保存されます。

```text
0 5312810 sil
5312810 9533267 zero
9533267 12487587 sil
```

# 特徴の抽出
`HCopy`コマンドで特徴量を抽出します。
コマンドは以下の形式で指定します。

```bash:HCopy
HCopy [オプション] 音声ファイル名 特徴ファイル名 -C 構成ファイル名 -S スクリプトファイル名
```
### 構成ファイル
構成ファイルには、音声ファイルの形式や、どのような特徴を指定するかを指定します。

```text:config.hcopy
SOURCEFORMAT = WAV
SOURCEKIND = WAVEFORM
SOURCERATE = 625
TARGETKIND = MFCC_0_D_A
TARGETRATE = 100000.0
WINDOWSIZE = 250000.0
USEHAMMING = T
PREEMCOEF = 0.97
NUMCHANS = 24
NUMCEPS = 12
```

SOURCERATEは、標本化周期である、1/16000[s]を指定してます。TARGETKINDには、抽出パラメータがMFCCで、０次元目のデータとしてフレームの平均パワーをとり、Dで特徴の１次差分を、Aで２次差分をとることを指定しています。
TARGETRATE はフレーム 周期を 100ns を単位として表した数値，WINDOWSIZE は分析窓長を 100ns を単位として 表した数値で，例では 10ms のフレーム周期で，25ms 長の窓を使うように指定しています。
USEHAMMING は T とすることで分析窓としてハミング窓を使うように指定しています。
PREEMCOEF にはプリエンファシスの係数を指定します。
NUMCHANS にはメルフィルタバン クのフィルタ数を、CEPLIFTER には求めたケプストラムに掛ける等価リフタの次数を指定します。
NUMCEPS には最終的に高次係数を切り捨てて求める MFCC の次数を指定します。

### スクリプトファイル
スクリプトファイルには、特徴抽出の入力ファイルと出力ファイルの対応を記述します。
区切り文字はタブです。

```text:script.hcopy
data0-1.wav	data0-1.mfc
data1-1.wav	data1-1.mfc
data2-1.wav	data2-1.mfc
data3-1.wav	data3-1.mfc
data4-1.wav	data4-1.mfc
data5-1.wav	data5-1.mfc
data6-1.wav	data6-1.mfc
data7-1.wav	data7-1.mfc
data8-1.wav	data8-1.mfc
data9-1.wav	data9-1.mfc
data0-2.wav	data0-2.mfc
data1-2.wav	data1-2.mfc
data2-2.wav	data2-2.mfc
data3-2.wav	data3-2.mfc
data4-2.wav	data4-2.mfc
data5-2.wav	data5-2.mfc
data6-2.wav	data6-2.mfc
data7-2.wav	data7-2.mfc
data8-2.wav	data8-2.mfc
data9-2.wav	data9-2.mfc
data0-3.wav	data0-3.mfc
data1-3.wav	data1-3.mfc
data2-3.wav	data2-3.mfc
data3-3.wav	data3-3.mfc
data4-3.wav	data4-3.mfc
data5-3.wav	data5-3.mfc
data6-3.wav	data6-3.mfc
data7-3.wav	data7-3.mfc
data8-3.wav	data8-3.mfc
data9-3.wav	data9-3.mfc
```

### HCopy実行
構成ファイルとスクリプトファイルを指定して、HCopyコマンドを実行します。

```
>HCopy.exe -C config.hcopy -S script.hcopy
```
mfcファイルができたら、mfccというフォルダを作ってそこに移動させます。

# 初期モデル作成
HMM(Hidden Markov Model)の構造を定義します。今回は以下の構成とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/7df8a694-334c-ef3b-ac75-cc85d1ee898a.png)
この構成をファイルに表現すると、以下のようになります。

```text:zero.hmm
~o <VecSize> 39 <MFCC_0_D_A>
~h "zero"
<BeginHMM>
<NumStates> 5
<State> 2
<Mean> 39
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
<Variance> 39
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
<State> 3
<Mean> 39
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
<Variance> 39
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
<State> 4
<Mean> 39
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
<Variance> 39
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
<TransP> 5
0.0 1.0 0.0 0.0 0.0
0.0 0.5 0.5 0.0 0.0
0.0 0.0 0.5 0.5 0.0
0.0 0.0 0.0 0.5 0.5
0.0 0.0 0.0 0.0 0.0
<EndHMM>
```
状態１と5は、特徴ベクトルを出力しないので、クラス分布関数の定義は不要となります。ほかの状態に関しては、クラス分布関数の平均ベクトル（Mean）と共分散行列（Variance）を記述します。
共分散行列は対角行列であると仮定し、その成分だけを指定しています。
<TransP>は状態遷移の定義をしています。今回は5状態なので5$\times$5で指定しています。例えば一行目は、2カラム目が1.0なので、状態１からは状態２に確率１で遷移することが指定されています。2行目は、2カラム目と3カラム目が0.5なので、状態２からは、状態２自身へ0.5、状態３へ0.5の確率で遷移することが指定されています。
このファイルをone.hmm～nine.hmmという名前でコピーし、「~h」の名前をそれぞれ書き換えて保存してください。

#HMMパラメータの初期値の設定
HMMのパラメータはそれぞれの状態でのクラス分布関数や状態遷移確率を定義しています。それらが均一で学習が始まると、局所最適解に陥る可能性があるため、HInitコマンドで初期値を設定します。初期値はビタビアルゴリズムで設定されます。

```bash:HInit
HInit [オプション] HMM名
```
オプションは以下の通りです。

```
-T Num  出力トレースレベルを指定する
-S listFile  学習データリスト名を指定する
-M folderName  初期HMMフォルダ名を指定する
-H hmmFile  HMM構成ファイル名を指定する
-l String  ラベル名を指定する
-L folderName  ラベルフォルダ名を指定する
```

オプションの-Sに指定する学習データリストを作成します。
HCopyで作成したmfcファイルのファイル名をすべて指定します。

```text:trainlist.txt
mfcc/data0-1.mfc
mfcc/data1-1.mfc
mfcc/data2-1.mfc
mfcc/data3-1.mfc
mfcc/data4-1.mfc
mfcc/data5-1.mfc
mfcc/data6-1.mfc
mfcc/data7-1.mfc
mfcc/data8-1.mfc
mfcc/data9-1.mfc
mfcc/data0-2.mfc
mfcc/data1-2.mfc
mfcc/data2-2.mfc
mfcc/data3-2.mfc
mfcc/data4-2.mfc
mfcc/data5-2.mfc
mfcc/data6-2.mfc
mfcc/data7-2.mfc
mfcc/data8-2.mfc
mfcc/data9-2.mfc
mfcc/data0-3.mfc
mfcc/data1-3.mfc
mfcc/data2-3.mfc
mfcc/data3-3.mfc
mfcc/data4-3.mfc
mfcc/data5-3.mfc
mfcc/data6-3.mfc
mfcc/data7-3.mfc
mfcc/data8-3.mfc
mfcc/data9-3.mfc
```

作成したら以下のコマンドを実行します。

```bash:HInit
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/zero.hmm -l zero -L label zero
```
以下のようなメッセージが出力されて処理が終了します。

```bash
Initialising  HMM zero . . .
 States   :   2  3  4 (width)
 Mixes  s1:   1  1  1 ( 39  )
 Num Using:   0  0  0
 Parm Kind:  MFCC_D_A_0
 Number of owners = 1
 SegLab   :  zero
 maxIter  :  20
 epsilon  :  0.000100
 minSeg   :  3
 Updating :  Means Variances MixWeights/DProbs TransProbs

 - system is PLAIN
3 Observation Sequences Loaded
Starting Estimation Process
Iteration 1: Average LogP = -3215.04102
Iteration 2: Average LogP = -3161.16675  Change =    53.87427
Iteration 3: Average LogP = -3147.54492  Change =    13.62183
Iteration 4: Average LogP = -3142.60547  Change =     4.93945
Iteration 5: Average LogP = -3137.84692  Change =     4.75854
Iteration 6: Average LogP = -3137.84692  Change =     0.00000
Estimation converged at iteration 7
Output written to directory hmm0
```
-Mで指定したディレクトリhmm0に結果が出力されています。クラス分布関数の平均ベクトルや、共分散行列の対角成分の初期値が設定されています。

```text:hmm0/zero.hmm
~o
<STREAMINFO> 1 39
<VECSIZE> 39<NULLD><MFCC_D_A_0><DIAGC>
~h "zero"
<BEGINHMM>
<NUMSTATES> 5
<STATE> 2
<MEAN> 39
 -1.188843e+01 3.477678e+00 3.675378e+00 -3.583102e-01 1.305101e+00 -8.195998e+00 1.164498e+00 -1.343422e+00 -5.572519e-01 -1.641809e+00 4.538788e-01 -3.993519e+00 6.202705e+01 1.258294e-01 -1.618993e+00 -2.157246e-01 -1.482081e+00 -1.093997e+00 -2.445189e+00 -7.862397e-01 5.881087e-01 -7.339767e-01 7.656317e-01 5.172008e-01 -7.833375e-02 2.365453e+00 2.523896e-01 -1.967793e-01 1.320107e-01 -2.817445e-01 -1.703041e-01 -1.349682e-01 -1.753984e-03 7.186008e-02 -4.457561e-02 -1.149647e-02 -1.150950e-01 -1.925533e-01 -1.014521e-01
<VARIANCE> 39
 8.134451e+01 2.500010e+01 2.850899e+01 3.407345e+01 3.633406e+01 1.519371e+02 2.309261e+01 1.219199e+01 2.605530e+01 1.669540e+01 7.526879e+00 1.242349e+01 6.291047e+01 1.676000e+01 2.441002e+00 5.652246e+00 3.159509e+00 4.312917e+00 1.023630e+01 1.497224e+00 1.609096e+00 2.166575e+00 1.569044e+00 1.058919e+00 2.794152e+00 1.761050e+00 3.050326e+00 5.784694e-01 1.052930e+00 3.828032e-01 5.059460e-01 2.157578e+00 3.394441e-01 3.012945e-01 5.481936e-01 3.527757e-01 2.134150e-01 3.832273e-01 3.550204e-01
<GCONST> 1.216047e+02
<STATE> 3
<MEAN> 39
 -7.309173e-01 -1.048546e+01 2.659467e+00 -1.338165e+01 -8.673465e+00 -2.305907e+01 -5.547618e+00 4.851469e+00 -1.124947e+00 1.360517e+00 -6.829199e-01 -8.349416e+00 7.364520e+01 2.837810e-01 -2.149490e-01 -8.765711e-01 -1.177059e-01 3.576238e-01 9.940961e-01 -1.604020e-01 -3.220538e-01 3.907898e-01 -5.969828e-02 4.719580e-01 8.681932e-02 7.714530e-02 -4.246662e-02 2.011100e-01 4.601711e-03 -4.502578e-02 1.839996e-01 1.834370e-01 1.421157e-01 -1.526191e-01 -1.234164e-01 -4.772357e-02 1.755387e-01 8.219465e-02 -8.779123e-02
<VARIANCE> 39
 8.108775e+00 1.834256e+00 3.121191e+01 4.376693e+01 8.521482e+00 2.422301e+01 1.186177e+01 1.053487e+01 2.263748e+01 7.743262e+00 1.453198e+01 1.808433e+01 6.427885e+00 5.204114e-01 4.328600e-01 9.790024e-01 6.985510e+00 1.159161e+00 2.183934e+00 1.309209e+00 1.069459e+00 1.907738e+00 9.514236e-01 3.414848e+00 2.245418e+00 1.005552e+00 1.177312e-01 1.024540e-01 2.142989e-01 1.328135e+00 1.833571e-01 2.062124e-01 3.222685e-01 8.448422e-02 2.971713e-01 2.330663e-01 3.594871e-01 4.487212e-01 2.456534e-01
<GCONST> 9.023790e+01
<STATE> 4
<MEAN> 39
 2.184474e+00 -5.833115e+00 -5.189268e+00 -2.197799e+01 -2.865063e+00 -7.106102e-01 5.807543e+00 -3.738693e+00 -1.543543e+00 1.309784e+00 1.105161e+01 -2.566302e+00 6.105293e+01 -5.572022e-01 4.472832e-01 3.408478e-01 2.388481e-01 1.331146e-01 5.106670e-01 5.467352e-01 -5.777531e-02 2.071937e-01 -1.611491e-01 -4.072005e-01 5.497755e-01 -1.642518e+00 -5.422572e-02 -5.406429e-02 6.275102e-02 2.781438e-01 -5.356923e-02 -1.684144e-01 -1.708611e-01 1.139488e-01 1.112158e-01 -1.089825e-02 -2.177670e-01 -5.934791e-03 1.551733e-02
<VARIANCE> 39
 2.196372e+01 1.054869e+01 9.056970e+00 4.348963e+01 1.065004e+01 1.313244e+01 2.535172e+01 9.383027e+00 1.420171e+01 1.470503e+01 1.630399e+01 2.157364e+01 1.038608e+02 1.358972e+00 8.380147e-01 1.497663e+00 6.104476e+00 1.432893e+00 1.276902e+00 3.454984e+00 1.076520e+00 1.620838e+00 1.897942e+00 1.662103e+00 5.682069e-01 1.261803e+00 1.517102e-01 1.698475e-01 2.700142e-01 5.574468e-01 2.880678e-01 1.503949e-01 2.872775e-01 2.300916e-01 2.548315e-01 3.564199e-01 2.485757e-01 1.144796e-01 2.613516e-01
<GCONST> 9.621025e+01
<TRANSP> 5
 0.000000e+00 1.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
 0.000000e+00 9.062500e-01 9.375000e-02 0.000000e+00 0.000000e+00
 0.000000e+00 0.000000e+00 9.411765e-01 5.882353e-02 0.000000e+00
 0.000000e+00 0.000000e+00 0.000000e+00 9.423077e-01 5.769231e-02
 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
<ENDHMM>
```

# HMMの学習
HRestを使用して学習します。学習アルゴリズムは、Baum-Welchアルゴリズムで行われます。HRestコマンドは以下のように定義されています。

```bash:HRest
HRest [オプション] HMM名
```
オプションは以下の通りです。

```
-T Num  出力トレースレベルを指定する
-S listFile  学習データリスト名を指定する
-M folderName  学習後のHMMフォルダ名を指定する
-H hmmFile  HMM構成ファイル名を指定する
-l String  ラベル名を指定する
-L folderName  ラベルフォルダ名を指定する
```
以下のコマンドを実行します。

```bash:HRest
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/zero.hmm -l zero -L label zero
```
コマンドの実行結果として、以下のようなメッセージが出力されます。

```
Reestimating HMM zero . . .
 States   :   2  3  4 (width)
 Mixes  s1:   1  1  1 ( 39  )
 Num Using:   0  0  0
 Parm Kind:  MFCC_D_A_0
 Number of owners = 1
 SegLab   :  zero
 MaxIter  :  20
 Epsilon  :  0.000100
 Updating :  Transitions Means Variances

 - system is PLAIN
3 Examples loaded, Max length = 48, Min length = 43
Ave LogProb at iter 1 = -3137.81421 using 3 examples
Ave LogProb at iter 2 = -3137.77124 using 3 examples  change =    0.04297
Ave LogProb at iter 3 = -3137.59253 using 3 examples  change =    0.17871
Ave LogProb at iter 4 = -3136.82495 using 3 examples  change =    0.76758
Ave LogProb at iter 5 = -3136.63086 using 3 examples  change =    0.19409
Ave LogProb at iter 6 = -3136.62720 using 3 examples  change =    0.00366
Ave LogProb at iter 7 = -3136.62695 using 3 examples  change =    0.00024
Ave LogProb at iter 8 = -3136.62695 using 3 examples  change =    0.00000
Estimation converged at iteration 8
```

changeの値がEpsilonの値以下になると処理が終了します。今回は、7回の繰り返しでchangeが0.00000になり、閾値（Epsilon）以下になったため処理が終了しています。hmm1には以下のようなファイルが出力されています。

```text:hmm1/zero.hmm
~o
<STREAMINFO> 1 39
<VECSIZE> 39<NULLD><MFCC_D_A_0><DIAGC>
~h "zero"
<BEGINHMM>
<NUMSTATES> 5
<STATE> 2
<MEAN> 39
 -1.217227e+01 3.842913e+00 3.533644e+00 1.472756e-01 1.648457e+00 -7.514535e+00 1.386567e+00 -1.436491e+00 -3.542405e-01 -1.861192e+00 4.071339e-01 -3.814390e+00 6.159630e+01 1.141078e-01 -1.565576e+00 -1.964019e-01 -1.410935e+00 -1.062007e+00 -2.575727e+00 -8.264967e-01 5.880688e-01 -7.851771e-01 7.808987e-01 5.518509e-01 -4.987149e-03 2.402865e+00 3.024479e-01 -2.217661e-01 1.633340e-01 -3.199436e-01 -1.993721e-01 -2.153498e-01 -2.831185e-02 8.236580e-02 -7.390657e-02 7.967819e-03 -9.346215e-02 -2.085769e-01 -8.045533e-02
<VARIANCE> 39
 8.169442e+01 2.182631e+01 2.887436e+01 2.753969e+01 3.400240e+01 1.431584e+02 2.239446e+01 1.232833e+01 2.568959e+01 1.581773e+01 7.711698e+00 1.187034e+01 5.946759e+01 1.733037e+01 2.438734e+00 5.834223e+00 3.114666e+00 4.429817e+00 1.007105e+01 1.498500e+00 1.663006e+00 2.161969e+00 1.613664e+00 1.056348e+00 2.725916e+00 1.779367e+00 3.079589e+00 5.794673e-01 1.059528e+00 3.519869e-01 4.966784e-01 2.036953e+00 3.298393e-01 3.083083e-01 5.409558e-01 3.534068e-01 2.059905e-01 3.885967e-01 3.535298e-01
<GCONST> 1.208999e+02
<STATE> 3
<MEAN> 39
 -7.906535e-01 -1.041732e+01 2.766007e+00 -1.341644e+01 -8.674352e+00 -2.316298e+01 -5.542252e+00 4.780799e+00 -1.234228e+00 1.429336e+00 -6.335033e-01 -8.366649e+00 7.366433e+01 2.875266e-01 -2.754755e-01 -8.747131e-01 -1.874051e-01 3.088014e-01 1.001068e+00 -1.495176e-01 -3.033022e-01 3.986614e-01 -5.198545e-02 4.518696e-01 3.981968e-02 1.016811e-01 -6.619872e-02 2.078234e-01 -1.152948e-02 -2.717815e-02 1.940995e-01 2.248549e-01 1.548931e-01 -1.542719e-01 -1.044020e-01 -5.847171e-02 1.568739e-01 8.602078e-02 -1.004811e-01
<VARIANCE> 39
 8.117218e+00 2.026274e+00 3.109494e+01 4.293981e+01 8.363704e+00 2.419632e+01 1.162494e+01 1.057546e+01 2.276046e+01 7.815707e+00 1.434899e+01 1.773967e+01 6.322599e+00 5.111328e-01 5.991596e-01 9.597199e-01 7.079209e+00 1.249156e+00 2.143095e+00 1.288905e+00 1.065011e+00 1.870619e+00 9.363415e-01 3.364549e+00 2.307021e+00 1.013867e+00 1.423830e-01 1.025852e-01 2.221691e-01 1.316545e+00 1.851659e-01 2.836960e-01 3.236317e-01 8.290850e-02 3.086341e-01 2.339674e-01 3.694269e-01 4.403385e-01 2.486919e-01
<GCONST> 9.116265e+01
<STATE> 4
<MEAN> 39
 2.184827e+00 -5.833997e+00 -5.189749e+00 -2.197671e+01 -2.865399e+00 -7.114503e-01 5.805620e+00 -3.737938e+00 -1.543275e+00 1.310084e+00 1.105157e+01 -2.567401e+00 6.105442e+01 -5.570874e-01 4.473169e-01 3.408257e-01 2.383701e-01 1.333299e-01 5.111523e-01 5.469086e-01 -5.796339e-02 2.068227e-01 -1.610779e-01 -4.066597e-01 5.497510e-01 -1.642352e+00 -5.422474e-02 -5.401250e-02 6.285939e-02 2.781239e-01 -5.359057e-02 -1.685091e-01 -1.707469e-01 1.139030e-01 1.112104e-01 -1.098443e-02 -2.178366e-01 -5.832497e-03 1.543439e-02
<VARIANCE> 39
 2.196155e+01 1.055286e+01 9.057457e+00 4.349560e+01 1.064939e+01 1.313586e+01 2.537523e+01 9.385870e+00 1.420036e+01 1.470367e+01 1.630179e+01 2.157984e+01 1.038633e+02 1.358882e+00 8.379121e-01 1.497464e+00 6.105309e+00 1.433039e+00 1.278445e+00 3.454720e+00 1.076635e+00 1.621630e+00 1.897718e+00 1.664000e+00 5.681398e-01 1.261837e+00 1.516891e-01 1.698446e-01 2.700627e-01 5.573735e-01 2.880323e-01 1.504418e-01 2.873333e-01 2.300760e-01 2.547979e-01 3.564235e-01 2.485776e-01 1.145405e-01 2.613668e-01
<GCONST> 9.621539e+01
<TRANSP> 5
 0.000000e+00 1.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
 0.000000e+00 9.030307e-01 9.696929e-02 0.000000e+00 0.000000e+00
 0.000000e+00 0.000000e+00 9.423689e-01 5.763115e-02 0.000000e+00
 0.000000e+00 0.000000e+00 0.000000e+00 9.423156e-01 5.768438e-02
 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
<ENDHMM>
```
hmm0内の初期値から値が変更されています。この、HMMの初期値の設定と学習をほかのone～nineのファイルに対して繰り返します。

```bash
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/one.hmm -l one -L label one
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/one.hmm -l one -L label one
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/two.hmm -l two -L label two
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/two.hmm -l two -L label two
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/three.hmm -l three -L label three
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/three.hmm -l three -L label three
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/four.hmm -l four -L label four
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/four.hmm -l four -L label four
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/five.hmm -l five -L label five
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/five.hmm -l five -L label five
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/six.hmm -l six -L label six
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/six.hmm -l six -L label six
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/seven.hmm -l seven -L label seven
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/seven.hmm -l seven -L label seven
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/eight.hmm -l eight -L label eight
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/eight.hmm -l eight -L label eight
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/nine.hmm -l nine -L label nine
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/nine.hmm -l nine -L label nine
>HInit -T 1 -S trainlist.txt -M hmm0 -H proto/sil.hmm -l sil -L label sil
>HRest -T 1 -S trainlist.txt -M hmm1 -H hmm0/sil.hmm -l sil -L label sil
```

終了したら、hmm1フォルダのファイルを結合し、hmmdefs.hmmというファイル名でspeechフォルダに保存します。
Windowsだと、以下のようなコマンドで結合できます。

```bash
>copy /b hmm1\*.hmm hmmdefs.hmm
```

作成したHMMのHMM名の一覧のファイルも作成します。

```text:hmmlist.txt
zero
one
two
three
four
five
six
seven
eight
nine
sil
```

# 数字の認識
いよいよ、数字を認識してみます。
認識の対象として、「無音」＋「数字」＋「無音」という並びを対象にして、一つの数字を認識してみます。
以下のように文法定義ファイルを準備します。

```text:grammar.txt
$WORD = ZERO | ONE | TWO | THREE | FOUR | FIVE | SIX | SEVEN | EIGHT | NINE;
({SIL} [$WORD] {SIL})
```
１行目で変数の定義をし、2行目でその定義を使った正規表現を指定しています。
SILは無音を表します。{SIL}は無音が0回以上繰り返される指定です。[\$WORD]は、$WORDが0回か1回出現することを指定しています。

この文法定義からHParseを使って認識のためのHMMネットワークを作成します。

```text:HParse
HParse 文法ファイル名 ネットワークファイル名
```
ここでは、以下のコマンドを実行します。

```bash
>HParse grammar.txt net.slf
```
net.slfを確認してみます。

```text:net.slf
VERSION=1.0
N=16   L=28   
I=0    W=SIL                 
I=1    W=!NULL               
I=2    W=NINE                
I=3    W=EIGHT               
I=4    W=SEVEN               
I=5    W=SIX                 
I=6    W=FIVE                
I=7    W=FOUR                
I=8    W=THREE               
I=9    W=TWO                 
I=10   W=ONE                 
I=11   W=ZERO                
I=12   W=SIL                 
I=13   W=!NULL               
I=14   W=!NULL               
I=15   W=!NULL               
J=0     S=1    E=0    
J=1     S=13   E=0    
J=2     S=0    E=1    
J=3     S=2    E=1    
J=4     S=3    E=1    
J=5     S=4    E=1    
J=6     S=5    E=1    
J=7     S=6    E=1    
J=8     S=7    E=1    
J=9     S=8    E=1    
J=10    S=9    E=1    
J=11    S=10   E=1    
J=12    S=11   E=1    
J=13    S=13   E=2    
J=14    S=13   E=3    
J=15    S=13   E=4    
J=16    S=13   E=5    
J=17    S=13   E=6    
J=18    S=13   E=7    
J=19    S=13   E=8    
J=20    S=13   E=9    
J=21    S=13   E=10   
J=22    S=13   E=11   
J=23    S=13   E=12   
J=24    S=12   E=13   
J=25    S=15   E=13   
J=26    S=1    E=14   
J=27    S=13   E=14   
```
次に、単語辞書を作成します。単語辞書は、単語 出力文字列 HMM名 という順番で記述します。

```text:voca.txt
ZERO [zero] zero
ONE [one] one
TWO [two] two
THREE [three] three
FOUR [four] four
FIVE [five] five
SIX [six] six
SEVEN [seven] seven
EIGHT [eight] eight
NINE [nine] nine
SIL [sil] sil
```
以上で準備完了です。HViteで認識してみましょう。

```
HVite [オプション] 単語辞書ファイル名 HMMリスト名 入力ファイル名
```
オプションは以下を指定します。

```
-T Num  出力トレースレベルを指定する
-H hmmFile  HMM構成ファイル名を指定する
-i resultFile 認識結果ファイル名を指定する
-w networkFile ネットワークファイル名を指定する
```

以下のコマンドを実行します。

```bash:HVite
>HVite -T 1 -H hmmdefs.hmm -i reco.mlf -w net.slf voca.txt hmmlist.txt mfcc/data0-1.mfc
```
コマンドラインには以下のように出力されました。

```text:commandline
Read 11 physical / 11 logical HMMs
Read lattice with 16 nodes / 28 arcs
Created network with 30 nodes / 42 links
File: mfcc/data0-1.mfc
SIL SIL SIL ZERO SIL  ==  [123 frames] -75.9633 [Ac=-9343.5 LM=0.0] (Act=27.7)
```
reco.mlfには以下のように出力されました。

```text:reco.mlf
#!MLF!#
"mfcc/data0-1.rec"
0 1100000 sil -836.290894
1100000 3100000 sil -1489.876709
3100000 4600000 sil -1255.839355
4600000 9400000 zero -3558.776611
9400000 12300000 sil -2202.708740
.
```
0.46秒から0.94秒の間で「zero」が認識されています。ラベル付けしたものを確認してみると、大体そのあたりで「zero」が発生されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/74d707d7-9c4a-59e8-df97-ae600ad08e6d.png)
これ面白いです。

# 新しい音声での認識実験
上の実験では、学習に使用した音声を使った認識実験でしたので、新しく音声を録音してきちんと認識できるかを実験してみます。
WaveSurferで「６」の音声を新たに録音しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/653ba4e9-0178-b6b5-2b29-0c4de105a93d.png)
これで、認識してみましょう

まずは、録音した音声の特徴量を抽出します。
testフォルダ配下に以下のファイルを作成します。区切り文字はタブでしたね。

```text:script.hcopy
test/test6-1.wav	test/test6-1.mfc
```
用意ができたら、HCopyコマンドで特徴を抽出する。

```bash:HCopy
>HCopy.exe -C config.hcopy -S test/script.hcopy
```
コマンド実行が成功したら、testフォルダ内にtest6-1.mfcという特徴量のファイルが出来上がります。
HViteで認識してみましょう。

```bash:HVite
>HVite -T 1 -H hmmdefs.hmm -i reco.mlf -w net.slf voca.txt hmmlist.txt test/test6-1.mfc
```
以下のようなログが出力されました。６と認識されています！

```text
Read 11 physical / 11 logical HMMs
Read lattice with 16 nodes / 28 arcs
Created network with 30 nodes / 42 links
File: test/test6-1.mfc
SIL SIL SIL SIX SIL SIL  ==  [167 frames] -70.3251 [Ac=-11744.3 LM=0.0] (Act=27.8)
```
これがどのくらい６と認識されたのか、認識率の評価をしてみます。
認識率の評価は、HResltコマンドを使用します。

```text:HResults
HResults [オプション] HMMリスト名 認識結果ファイル名
```
オプションは以下の通りです。

```
-e label1 label2 ラベル2をラベル1に置き換える
-I refFile 正解ファイル名を指定
-L labelFolder ラベルファイルのフォルダを指定
```
まずは、実験用の音声にラベル付けをして、test/ref.mlfに保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508288/0926db9a-f033-94e6-1021-965e1be674e8.png)

正解ファイル名のリストを作成します。

```text:test/ref.mlf
#!MLF!#
"label"
test6-1.lab
```

下記のコマンドで認識率を出力してみます。

```bash:HResults
>HResults -T 1 -e "???" sil -I test/ref.mlf -L test hmmlist.txt reco.mlf
```
-e "???" sil で、silが正解の集計に影響なしとするように指定しています。"???"はラベルなしの意味です。
以下の結果が出力されました。

```text
====================== HTK Results Analysis =======================
  Date: Sat Dec 14 11:59:55 2019
  Ref : test/ref.mlf
  Rec : reco.mlf
------------------------ Overall Results --------------------------
SENT: %Correct=100.00 [H=1, S=0, N=1]
WORD: %Corr=100.00, Acc=100.00 [H=1, D=0, S=0, I=0, N=1]
===================================================================
```
100パーセントの認識率です。大成功です。

# 動的音声認識
動的な音声認識もできます。
まずは、以下の設定ファイルを用意します。

```text:config.hvite-mic
SOURCEFORMAT = HTK
SOURCEKIND = HAUDIO
SOURCERATE = 625
TARGETKIND = MFCC_0_D_A
TARGETRATE = 100000.0
WINDOWSIZE = 250000.0
USEHAMMING = T
PREEMCOEF = 0.97
NUMCHANS = 24
NUMCEPS = 12
USESILDET = T
MEASURESIL = F
OUTSILWARN = T
ENORMALISE = F
```
下記のコマンドで、動的認識が開始しますので、マイクに向かって数字を言ってみましょう。

```bash:HVite
>HVite -C config.hvite-mic -H hmmdefs.hmm -i reco.mlf -w net.slf voca.txt hmmlist.txt
```
下記のような結果が出力されました。

```text
Please speak sentence - measuring levels
Level measurement completed
SIL  ==  [67 frames] -63.5110 [Ac=-4255.2 LM=0.0] (Act=27.5)

READY[2]>
SIL SIL SIX SIL SIL SIL  ==  [237 frames] -67.8191 [Ac=-16073.1 LM=0.0] (Act=27.9)

READY[3]>
SIL SIX SIL SIL SIL SIL  ==  [244 frames] -68.0448 [Ac=-16602.9 LM=0.0] (Act=27.9)

READY[4]>
SIL SIL SIL EIGHT SIL SIL SIL SIL SIL  ==  [271 frames] -69.0263 [Ac=-18706.1 LM=0.0] (Act=27.9)

READY[5]>
SIL FOUR SIL  ==  [111 frames] -69.6964 [Ac=-7736.3 LM=0.0] (Act=27.7)

READY[6]>
SIL THREE SIL SIL SIL SIL  ==  [221 frames] -65.9460 [Ac=-14574.1 LM=0.0] (Act=27.9)

READY[7]>
SIL NINE SIL SIL  ==  [129 frames] -69.9172 [Ac=-9019.3 LM=0.0] (Act=27.8)
```
動的認識がきちんとなされています。

# 終わりに
今回は、数字1文字の認識でしたが、HTKを使うと連続数字の認識も可能ですので、やってみてください。
この本でも演習問題として連続数字の認識が課題となってます。おそらく、grammar.txtを以下のように変えればできると思います。

```text:grammar.txt
$WORD = ZERO | ONE | TWO | THREE | FOUR | FIVE | SIX | SEVEN | EIGHT | NINE;
({SIL} <$WORD SIL> {SIL})
```
\$WORD と SILを繰り返すように指定しています。\$WORDと\$WORDの間に無音声が入らない場合は、

```text:grammar.txt
$WORD = ZERO | ONE | TWO | THREE | FOUR | FIVE | SIX | SEVEN | EIGHT | NINE;
({SIL} <$WORD [SIL]> {SIL})
```
とすればいいと思います。もっといい方法があったら教えてください。

もし、音声や各設定ファイルの需要がありましたらGitにアップしますので言ってください。私の声でよければ・・・ｗ。

# 参考
[フリーソフトで作る音声認識システム](https://www.amazon.co.jp/フリーソフトでつくる音声認識システム-パターン認識・機械学習の初歩から対話システムまで-荒木-雅弘/dp/4627847122)