Juliusの独自辞書を使って音声を認識させる

## はじめに
[Raspberry Pi＋Juliusで音声を認識する](https://qiita.com/mininobu/items/609aed76f5767f0aa99b)では、Julius公式のディクテーションキットを使用してマイクから入力した音声を認識させることができました。
何回か音声認識を試した方はお気づきかもしれませんが、ディクテーションキットではかなりの確率で誤認識されます。
以下、「おはよう」と話してみた結果です。

```shell:「おはよう」と話しかけた結果
pass1_best:  奥 さん 。
pass1_best_wordseq: 
 奥+名詞 さん+接尾辞 

pass1_best_phonemeseq: silB | o k u | s a N | silE
pass1_best_score: -3925.215576
### Recognition: 2nd pass (RL heuristic best-first)
STAT: 00 _default: 33457 generated, 3126 pushed, 335 nodes popped in 159
sentence1:  アン オン 。
wseq1: 
 アン+名詞 オン+名詞 

phseq1: silB | a N | o N | silE
cmscore1: 0.066 0.184 0.004 1.000
score1: -3971.063965
```

「奥さん」、「アン オン」と認識されました。いずれも「おはよう」とはほど遠い認識です。
この原因はディクテーションキットに含まれている辞書の精度によるものです。提供されている辞書は、日本語を認識する「最低限の情報」程度のものであり、Google Cloud Speech APIのような音声認識サービスが使用している辞書とは雲泥の差があります。
精度を上げるためには辞書のデータを充実させることが必要ですが、あらゆる日本語を認識できる辞書を個人で作成するのは到底できません。が、「特定の言葉だけ」を認識するだけの辞書をつくることはそう難しくありません。
Juliusには独自の辞書ファイルを作成するツールが同梱されています。今回はそれらツールを使用して、いくつかの言葉だけを認識する辞書を作成し、音声認識をさせてみましょう。

## 辞書ファイルの作成に必要な情報
辞書ファイルを作成するためには、「語彙」「音素」「構文」の３つの情報が必要です。
Juliusにおけるこれら情報の位置づけについて説明します。

### 語彙
Juliusに認識させたい言葉（単語）を定義します。

### 音素（音素列）
語彙の「よみ」を定義します。

### 構文
認識する文章の構成を定義します。

## 辞書を作成する
今回は、次の語彙を認識する辞書を作成してみます。

* おはよう
* おはようございます
* こんにちは
* こんばんは

単純な単語を認識できる辞書です。構文の設定次第では複雑な文章を認識することも可能ですが、まずはシンプルな単語の認識から始めましょう。辞書ファイルおよび辞書の作成に必要なファイルは、``~/julius/dict``に格納するものとします。

### 読みファイルを作成する
辞書に登録したい語彙に対して、読みの情報を定義したファイルを作成します。
具体的には下記の内容になります。

```text:greeting.yomi
おはよう    おはよう
ございます ございます
こんにちは こんにちわ
こんばんは こんばんわ
```

読みファイル作成のポイントは次の点です。

* 読みは「ひらがな」で定義する。
* 重複する語彙は1語にまとめる。  
 * 今回の場合、「おはよう」と「おはようございます」は「"おはよう"」の部分を構文定義で同一視させるため1語にまとめる。
* 読みは「発音」で設定する
 * 「こんにち"は"」は、読みでは「こんにち"わ"」と発音するため、読みを「こんにちわ」と設定します。

### 音素ファイルを作成する
juliusに含まれているスクリプト（yomi2voca.pl）を使用し、読みファイルから音素ファイルを作成します。

```shell
iconv -f utf8 -t eucjp ~/julius/dict/greeting.yomi | ~/julius/julius-4.4.2.1/gramtools/yomi2voca/yomi2voca.pl | iconv -f eucjp -t utf8 > ~/julius/dict/greeting.phone
```

yomi2voca.plを使用する場合、インプットとなる文字列の文字コードはEUC-JPである必要があります。（Linux版Juliusの仕様です）
そのため、読みファイルの内容をiconvコマンドでEUC-JPに変換してからyomi2voca.plを実行します。その後、再度文字コードをUTF-8に戻してから、ファイルに保存します（greeting.phone）。

作成された音素ファイルの内容が下記のようになっていれば成功です。

```text:greeting.phone
おはよう    o h a y o u
ございます g o z a i m a s u
こんにちは k o N n i ch i w a
こんばんは k o N b a N w a
```

### 構文ファイルを作成する
構文定義を設定した構文ファイルを作成します。
具体的には下記の内容になります。

```text:greeting.grammar
S : NS_B GREET NS_E
GREET : OHAYOU
GREET : OHAYOU GOZAIMASU
GREET : KONNICHIWA
GREET : KONBANWA
```

1行目の``S``の箇所が、認識する文章（構文定義）を表します。
``NS_B``が文章の開始、``NS_E``が文章の終了という意味です。
``NS_B``と``NS_E``の``GREET``が認識させてい単語です。２行目以降に``GREET``で認識させる読みの文字列を設定します。設定する文字列は、音素ファイルで出力された文字列で文章を組み立てます。

### 語彙ファイルを作成する
語彙ファイルは先程作成した音素ファイルを使って作成すると楽です。
各音素に対して、構文で割り振った読み（OHAYOUなど）と、開始、終了を表す```NS_B```、```NS_E```を設定します。

```ssh:greeting.voca
% OHAYOU
おはよう        o h a y o u
% GOZAIMASU
ございます      g o z a i m a s u
% KONNICHIWA
こんにちは      k o N n i ch i w a
% KONBANWA
こんばんは      k o N b a N w a
% NS_B
[s]             silB
% NS_E
[/s]            silE
```

### 辞書ファイルを作成する
音素ファイル、構文ファイル、語彙ファイルとjuliusのスクリプト（mkdfa.pl）を使用して辞書ファイルを作成します。
作成にあたり、注意があります。
grammar-kit-v4.1 及び julius-4.3.1/gramtools/mkdfa/ に付属している mkdfa.plは正常に動作しません。正常に動作するスクリプトが``gramtools/``に含まれているため、スクリプト(mkfa、dfa_minimize)を差し替えてから辞書ファイルの作成を実行します。


```shell
cp -b ~/julius/julius-4.4.2.1/gramtools/mkdfa/mkfa-1.44-flex/mkfa ~/julius/julius-4.4.2.1/gramtools/mkdfa/mkfa
cp -b ~/julius/julius-4.4.2.1/gramtools/dfa_minimize/dfa_minimize ~/julius/julius-4.4.2.1/gramtools/mkdfa/dfa_minimize
~/julius/julius-4.4.2.1/gramtools/mkdfa/mkdfa.pl ~/julius/dict/greeting
```

## 作成した辞書で音声を認識する
次の通りjuliusコマンドを実行してみましょう。

```shell
~/julius/julius-4.4.2.1/julius/julius -C ~/julius/julius-kit/dictation-kit-v4.4/am-gmm.jconf -nostrip -gram ~/julius/dict/greeting -input mic
```

いかがでしょうか。ディクテーションキットを使用した場合に比べて、認識率が向上していると思います。
あわせて応答速度も早くなっていないでしょうか？
これは辞書ファイルが小さくなった分、辞書情報の探索範囲が減ったために、応答速度も早くなっています。

## おわりに
認識したい単語が限られている場合は、精度、性能、両方の観点からみて独自辞書の作成は有効な手段だと思いました。辞書の作成は一手間ありますが、精度に悩んでいる方は、ぜひチャレンジしてみてください。