Open JTalk のほぼ定型文のための辞書類のサイズの調査

日本語音声合成システム Open JTalk で数値などの部分的な可変部分を含む定型文を読み上げる場合の辞書のサイズを調査してみた。

今回の作業内容を備忘録としてまとめておき、いつになるかわからない次回の作業に備える。

背景

M5Stack Atom を使ってNetatmo のCO2濃度をGoogle Home にしゃべらせる」で、Google のサービスを利用して文章を音声ファイルに変換していたが、失敗する場合が多く(現在は変換に成功しない?)、外部サービスに依存しない形での音声合成ができないかと考えた。

部屋のCO2濃度は、1000ppmです。窓を開けて換気してください」のような文のうち、「部屋」の部分にいくつかのバリエーションがあり、数値「1000」が変わるというほぼ定型文の音声合成がしたい。数値部分がなければ、音声合成エンジンで文を読み上げた音声ファイルをいくつか用意しておき、それを使うだけで良いが、数値部分を別に扱う必要があり大変そうである。

そこで、資源の制限の制約が強い m5stack などの組み込み用のシステムで使える音声合成システムを調べてみた。

AquesTalk

組み込み用としてはAquesTalkがあり、商用利用には有料ライセンスが必要だが、個人利用は一定の条件の下で無償で使用できるとのこと。開発ライセンス(有料)を取得すれば開発できる。ESP32用のAquesTalk pico も用意されているようで、m5stackでそのまま使えそうである。ただし、オープンソースではないので、他のものをさらに探すことにした。

Open JTalk

他に利用できそうなものを探したところ名古屋工業大学で開発された Open JTalk がオープンソース(修正BSDライセンス)で使えそうなことがわかった。しかし、少し調べたところ、利用する辞書類(音声合成に必要なファイル)が大きく(システム辞書 sys.dic が78MB以上)、そのままではm5stack の4MBや16MBのフラッシュメモリに収まらない。

自由文を対象とする Open JTalk を m5stack に組み込むのは無理なのはわかったが、ほぼ定型文の場合には辞書を小さくすることが可能ではないかと思い、ほぼ定型文のための辞書生成を試してみて、サイズを調べることにした(たとえ辞書類をフラッシュメモリに収めることができても、RAM量の制約で動作しないかもしれない)。

まず、Open JTalkをビルドし、それを基に定型文から辞書データを生成した。基本的にWebでの記事を基に作業をしたので、新しい部分はあまりないが、備忘録として残しておく。

Open JTalk のダウンロードとビルド

Windows 10で Open JTalk のビルドを行った。

基本的に「Windowsで音声合成Open JTalk」と「音声合成Open JTalkのインストール[On Windows 10]」を基に作業した。

準備

Visual Studio Community – Visual Studio の 2019 をインストール済みとする。

環境変数などの設定

Command Prompt を利用してビルドする。

Visual C++ を利用できるように、次の呼び出しをしておく。

Command Prompt を呼び出すたびに環境変数の設定が必要である。

hts_engine_APIのダウンロードとビルド

SourceForge.net から音声合成のための HTSエンジン (HMM/DNN-based Speech Synthesis System) をダウンロードする( https://sourceforge.net/projects/hts-engine/ ) 。

hts_engine_API-1.10.tar.gz をダウンロードし、展開した。

展開したところで (hts_engine_API-1.10\)、次のコマンドを実行し、ビルドした。

うまくできていれば、次のファイルができているはず。

  • hts_engine_API-1.10\lib\hts_engine_API.lib
  • hts_engine_API-1.10\bin\hts_engine.exe

次のコマンドで、C:\hts_engine_API にインストールがされる。

このディレクトリは、この後の open_jtalk-1.11\bin\open_jtalk.exe のビルドの際に参照されるので、インストールディレクトリを変更した場合には、open_jtalk-1.11\bin\Makefile.mak の中で  C:\hts_engine_API\lib\hts_engine_API.lib を参照している部分も修正する必要がある。

Open JTalk と辞書のダウンロード

SourceForge.net から open_jtalk-1.10.tar.gz をダウンロードして ( https://sourceforge.net/projects/open-jtalk/ )、展開した。

コンパイル済み辞書

辞書を自分でビルドしないなら、コンパイル済みのShift JISの辞書 open_jtalk_dic_shift_jis-1.11.tar.gz をダウンロードして利用する。以下では辞書もビルドするのでダウンロード不要。

不具合修正のためのパッチ

辞書ビルド時のエラー解消のパッチ

まず、「音声合成Open JTalkのインストール[On Windows 10]」に従って、辞書のビルドの際のエラーを解消するために、open_jtalk-1.11\mecab-naist-jdic の Makefile.mak に以下のパッチをあてた。

音声の不具合の解消のパッチ

また、文章校正のためにOpen JTalkを使って日本語テキストを読み上げる に音声がところどころで間延びしてしまう問題の解消方法が記載されていたので、次のパッチをopen_jtalk-1.11\jpcommon\jpcommon_label.c にあてた。自分では不具合やその解消がどの程度されているかを確認していない。

最初の改行までの入力制限を外す

open_jtalk.exe は、fgets によって1行だけを入力して処理するコードになっている。あまり大きな入力を扱わないようにわざとそのようにしているようだが、次のパッチを open_jtalk-1.11\bin\open_jtalk.c にあてて、入力バッファが許す限り(MAXBUFLEN=1024バイト)複数行を入力できるようにした。

open_jtalk.exe の動作確認のためだけならこのパッチは不要である。入力ファイルに1行だけで書くようにすれば良い。

Open JTalk と辞書のビルド

open_jtalk-1.11\ で次のコマンドを実行することで、ビルドを行う。

うまくできていれば、以下のファイルができているはず。

  • open_jtalk-1.11\bin\open_jtalk.exe
    • 合成音声を生成するためのコマンド
  • open_jtalk-1.11\mecab\src\mecab-dict-index.exe
    • mecabの辞書を生成するためのコマンド
  • open_jtalk-1.11\mecab-naist-jdic\matrix.bin
    • matrix.def をコンパイルしたバイナリファイル
  • open_jtalk-1.11\mecab-naist-jdic\sys.dic
    • unidic-csj.csv、naist-jdic.csv から生成したシステム辞書
  • open_jtalk-1.11\mecab-naist-jdic\char.bin
    • char.def をコンパイルした文字カテゴリマップのバイナリファイル
  • open_jtalk-1.11\mecab-naist-jdic\unk.dic
    • unk.def から生成した未定義語の品詞等の辞書

さらに次のコマンドを実行することで、C:\open_jtalk にインストールが行われるようになっている。

Open JTalk の動作確認

辞書類の設定

open_jtalk-1.11\mecab-naist-jdic\matrix.bin
open_jtalk-1.11\mecab-naist-jdic\sys.dic
open_jtalk-1.11\mecab-naist-jdic\char.bin
open_jtalk-1.11\mecab-naist-jdic\unk.dic

たとえば、open_jtalk-1.11\bin\dic\ フォルダを作成し、その下に上記の辞書を入れておく。次のファイルがあるようにする。

open_jtalk-1.11\bin\dic\matrix.bin
open_jtalk-1.11\bin\dic\sys.dic
open_jtalk-1.11\bin\dic\char.bin
open_jtalk-1.11\bin\dic\unk.dic

Voiceのダウンロード

SourceForge.netのOpen JTalk のプロジェクトのhttps://sourceforge.net/projects/open-jtalk/files/HTS%20voice/ から

hts_voice_nitech_jp_atr503_m001-1.05/hts_voice_nitech_jp_atr503_m001-1.05.tar.gz をダウンロードし、展開する。展開された中にnitech_jp_atr503_m001.htsvoiceがあるはず。

たとえば、open_jtalk-1.11\bin\voice\ フォルダを作成し、その中に nitech_jp_atr503_m001.htsvoice を入れておく。

MMDAgentのVoiceの利用

MMDAgent – Toolkit for Building Voice Interaction Systemsのボイスも利用できるようである。
SourceForge.net の MMDAgent の  "Sample Script" の Source code ( MMDAgent_Example-1.8.zip )をダウンロードし、展開する。

MMDAgent_Example-1.8\Voice 以下の .htsvoice を必要に応じて、open_jtalk-1.11\bin\voice\ フォルダに入れる。たとえば、MMDAgent_Example-1.8\Voice\mei を open_jtalk-1.11\bin\voice\mei にコピーする。この場合には、open_jtalk-1.11\bin\voice\mei\mei_normal.htsvoice などが使えるようになる。

合成音声ファイルの生成

たとえば、次のようなテキスト input.txt を用意して、合成音声ファイルを生成する。音声合成のための文字コードを Shift-JIS にして音声合成コマンドをビルドしているので、Shift-JIS でなければならない(ちなみに、辞書の入力 .def ファイルの文字コードは UTF-8 である)。

前述の「最初の改行までの入力制限を外す」のパッチをあてていない場合には、最初のLF (0x0A)までしか入力しないようになっているので、1行で記述する。

open_jtalk-1.11\bin ディレクトリで次のコマンドを実行することで、合成音声ファイル output.wav が生成される。

-m でvoice ファイルを指定し、-x で辞書が含まれるディレクトリを指定する。カレントディレクトリに辞書が含まれるなら -x . を指定する。-ow で出力ファイル名を指定する。

定型文用の辞書を生成する

定型文に含まれる語彙のみをもった辞書を作成する。

char.def、unk.def は変わらないので、そのままである。matrix.def は変えた方が良いかもしれないが、今回はそのまま使うことにした。

このため、作成すべきものは、naist-jdic.csv や unidic-csj.csv に相当する語彙のcsvファイルである。

MeCabを利用して定型文の語彙だけのcsvファイルを作成する。しかし、Open JTalkには通常の MeCab のコマンドが含まれていない。

次のコードを加えて MeCab のコマンドを使えるようにした。

次の open_jtalk-1.11\mecab\src\mecab-do.cpp を追加した(mecab.cpp は既にあるので、mecab-do.cpp というファイル名にしている)。

mecab-do.cpp から mecab.exe を作成するための記述を open_jtalk-1.11\mecab\src\Makefile.mak に追加した。

この後、open_jtalk-1.11\で ビルドしておく。

open_jtalk-1.11\mecab\src\mecab.exe が生成されるので、 open_jtalk-1.11\bin に mecab.exe を入れておく。open_jtalk-1.11\mecab\src で nmake -f Makefile.mak install を実行してもよい。

文例からの辞書入力データ small-jdic.csv の生成

open_jtalk-1.11\binで次のコマンドを実行して small-jdic.csv を作成する。

input1.txt には、すべての数字が含まれるようにする。たとえば次のようにする。

すべての数字を含めておかないと含まれていない数字が発話されないことになってしまう。

また、記号、英字、数字を含めてすべていわゆる全角で表現する必要がある。open_jtalk の入力ファイルは半角が含まれていても自動的にすべて全角に変換されるが、mecab の入力は変換がされない。mecab で半角を使うと辞書に半角で登録されるが、open_jtalkではすべて全角に変換された後に辞書が検索されるのでマッチするものがなく警告が出て、正しく音声合成がされなくなるためである。

mecabに全角への変換処理を含めればよいが、open_jtalkとは処理が異なるためにコードの修正部分が多くなるので省いている。

input1.txt から生成した small-jdic.csv は次のようになる。

「て」が重複しているなど無駄があるが、現時点では冗長な部分を削除するなどの処理はしていない。

small-jdic.csv からの辞書データ生成

open_jtalk-1.11\mecab-naist-jdic 全体をコピーして open_jtalk-1.11\mecab-small-jdic を作成し、その中の naist-jdic.csv と unidic-csj.csv を削除する。残っていると、自動的に読み込まれて sys.dic が小さくならない。

small-jdic.csv を open_jtalk-1.11\mecab-small-jdic に入れる。

open_jtalk-1.11\mecab-small-jdic で次のコマンドを実行することでバイナリの小規模辞書が作成されるはず。

この時、このsmall-jdic.csv の文字コードは Shift-JISなので、-f オプションで入力ファイルの文字コードを sjis にしている(mecab-naist-jdic の csvファイルは UTF-8 であり、異なるので注意が必要)。

以下のバイナリの小規模辞書が作成されるはず。

open_jtalk-1.11\mecab-small-jdic\matrix.bin
open_jtalk-1.11\mecab-small-jdic\sys.dic
open_jtalk-1.11\mecab-small-jdic\char.bin
open_jtalk-1.11\mecab-small-jdic\unk.dic

open_jtalk-1.11\bin\dic.small\ フォルダを作成し、その下に上記のバイナリ小規模辞書を入れておく。以下のファイルがあるようになる。

open_jtalk-1.11\bin\dic.small\matrix.bin
open_jtalk-1.11\bin\dic.small\sys.dic
open_jtalk-1.11\bin\dic.small\char.bin
open_jtalk-1.11\bin\dic.small\unk.dic

Makefile.mak

次のような Makefile.mak を open_jtalk-1.11\mecab-small-jdic に作成し、その後に示すnmake を実行してもよい。

コマンドとしては open_jtalk-1.11\mecab-small-jdic で次を実行することで小規模辞書を作成し、それを ..\bin にインストールする。

小規模辞書での音声合成の確認

数値部分が変わっても大丈夫か確認するために、次の入力 input2.txt を音声合成する。

open_jtalk の入力の input2.txt には半角が含まれていてもよい。内部で全角に変換されて処理される。

open_jtalk-1.11\bin ディレクトリで次のコマンドを実行することで、合成音声ファイル small.wav が生成される(open_jtalk-1.11\bin\voice\mei\mei_normal.htsvoice があるとして)。

文章が正しく読み上げられていることを確認する。

当然のことながら、small-jdic.csv を作成する際に利用した input1.txt に含まれない単語は発話されない。

また、最初、辞書データのcsvファイルの文字コードと全角半角の違いを理解せず小規模辞書を作成して試していて、読み上げ時に欠けてしまう部分があり、原因を調べるのに手間取った。

辞書ファイルサイズの比較

辞書ファイルのサイズを次の表にまとめる。

サイズ(kB) mecab-naist-jdic mecab-small-jdic
char.bin 257 257
matrix.bin 3,704 3,704
sys.dic 78,490 7
unk.dic 6 6

必要最小限の語彙にすることで sys.dic のサイズは十分に小さくなった。

matrix.bin のサイズは変わっておらず、m5stack だと フラッシュが4MBのほとんどの機種では依然として入らない。16MBの Core 2 などならフラッシュには収めることができそうである。

課題

たとえ、必要な辞書ファイルがフラッシュに収まったとしても RAM が不足しないかはっきりしない。

また、MeCabのプログラムでは、matrix.bin などを mmap システムコール でメモリにマップしているようである。ESP32 にもフラッシュを read only でメモリ空間にマップする機能があるようだが、簡単にプログラムを修正できるのかわからない。

さらに、matrix.bin にもほぼ定型文の音声合成の際には不要な情報が多く含まれていると思われる。それらを省いて小さくできるかも確認する必要がある。

まとめると、Open JTalk を「ほぼ定型文」の音声合成に使う場合に m5stack で動作するようにできる可能性はあるが、確認すべきことが多数あり、先は長い感じである。