OpenJTalk の解析資料

2018/07/30(初版)

はじめに

OpenJTalk は名古屋工業大学で開発され BSD ライセンスで公開されている「入力された日本語テキストに基づいて自由な音声を生成する HMM テキスト音声合成システム」です。

ここには MikuMikuJTalk や NeGiTalk を開発するために調べた内容が、自分でも年月とともに自分でも分からなくなるのを防ぐために、公開されています。情報は独自の解析によるため、基本的に「~と思われる」という語尾が省略されている物と思って下さい。

OpenJTalk の構造

OpenJTalk は主に 4 つのライブラリから構成されてます。
  1. 形態素解析エンジン MeCab
  2. njd
  3. JPCommon
  4. HMM/DNN による音声合成システム HTS(いつのまにやら DNN がくっついてる?w)
MeCab,njd,JPCommon は日本語から HTS に渡す合成パラメータを作るのに使われています。

MeCab

MeCab は「京都大学情報学研究科−日本電信電話株式会社コミュニケーション科学基礎研究所共同研究ユニットプロジェクトを通じて開発されたオープンソース形態素解析エンジン」です。

つまり「ありもの」の流用ですw。ただし、OpenJTalk の MeCab は、一般に配布されている MeCab とは「辞書」が異なり、OpenJTalk に付属する辞書には「アクセント関連情報」が含まれています。実は長年それが分からず、どこでアクセント情報を作り出してるのかが謎だった時期がありますw。

MeCab が出力する情報は全て辞書に登録された「単なる内容不問の CSV 文字列」です。ぶっちゃけ辞書からそれらをすべて削除しても形態素解析できます。形態素解析で必要な部分は「MeCab の辞書構造と汎用テキスト変換ツールとしての利用」あたりを読むと分かりますが「表層形(単語)」+「数字3つ(左右文脈ID+コスト)」と「連接表(matrix.bin)」だけです。表層形は Double Array という物に変換されますので、sys.dic をダンプして文字として読める部分(後半)は本当に出力用としてだけ存在します。

そして、この「辞書に何を入れても良い」という利点を生かして、OpenJTalk に内包されている MeCab の辞書には、アクセント情報が追加されてます。実際、例えば「歌う」の場合
[一般のMeCabの辞書]
歌う,817,817,7077,動詞,自立,*,*,五段・ワ行促音便,基本形,歌う,ウタウ,ウタウ
[OpenJTalkのMeCabの辞書]
歌う,854,854,6898,動詞,自立,*,*,五段・ワ行促音便,基本形,歌う,ウタウ,ウタウ,0/3,C2
と 2 項目余計な情報が増えています。この 2 つが OpenJTalk で追加されているアクセント関連情報です(詳細は別途)。

ちなみに MeCab は、単語自体のコストと、2 つの文脈 ID の並び順に対するコストを、可能な全ての単語の組み合わせで計算し、一番コストパフォーマンスが良い物を出力します。例えば「本日は」という日本語は辞書から「本日+は」と「本+日+は」の可能性が考えられるのですが、コスト計算の結果、前者が選ばれます。ちなみに「は」は助詞以外にも名詞や動詞としても登録されてますが、助動詞の「は」がコスト計算でちゃんと選ばれます。

なお、「ほんじつ」というひらがな記述は、辞書に載っていなければ「本日」とは判定されません。実際には「ほんじつ」は登録されてますが「本じつ」や「ほん日」は流石にありません。MeCab は活用形もそうですが「辞書に載ってる形(表層形)が全て」なのが弱点でもあります。話し言葉に弱いのもありますが、低学年向けの中途半端な漢字文章なども苦手なはずです。

さらに言えば、OpenJTalk の MeCab の(IPA)辞書を調べた限り「左文脈ID」と「右文脈ID」が一致しないケースがありませんでした(分けてる意味がない)。純正の MeCab では Jumman 辞書、Unidic 辞書という、別の種類の辞書も使えるので、それらでは有用になっているのかもしれませんが謎です。なので、NeGiTalk では統合しています。

njd

njd が何の略なのはか不明ですが、実は結構重要な役割をしているのが njd です。njd には以下の処理コードが存在します。基本的には、日本語の暗黙のルールをコード化処理するものです。
  1. njd_set_pronunciation … 意味のない(辞書にない)仮名やアルファベットの読みを生成
  2. njd_set_digit … 二桁以上の数字や助数詞、単位の読みを生成
  3. njd_set_accent_phrase … 品詞によるアクセントの結合処理
  4. njd_set_accent_type … タイプによるアクセントの結合処理
  5. njd_set_unvoiced_vowel … 無声音化を処理
  6. njd_set_long_vowel … 長音化処理
1.によって「TIF」を「ティーアイエフ」と発音できるようになります。「ティフ」と発音させるには、MeCab への辞書登録が必要です。
2.によって「3939」が「さんきゅうさんきゅう」から「さんぜんきゅうひゃくさんじゅうきゅう」に変換されます。
6.によって、「エイ」を「エー」に変換するなど、「イ」で終わる一部の読みが長音化されます。

5.は無声音化です。njd_set_unvoiced_vowel_rule_*.h のコメントによると
無声子音: k ky s sh t ty ch ts h f hy p py
Rule 1 助動詞の「です」と「ます」の「す」が無声化
Rule 2 続けて無声化しない
Rule 3 アクセント核で無声化しない
Rule 4 無声子音(k ky s sh t ty ch ts h f hy p py)に囲まれた「i」と「u」が無声化
という処理になっています。無声音化に関しては「これで完璧」というルールは存在しません。というか njd として実装される処理はどれも「実際に使われてる日本語から法則を(日本語研究者が)ひねり出した例が元」なので、違う考え、違う処理も十分あり得る領域になります。

「アクセントの結合処理」というのは、まずは 3. の njd_set_accent_phrase_rule_*.h のコメントに書かれたルールを見てみると
Rule 01 デフォルトはくっつける
Rule 02 「名詞」の連続はくっつける
Rule 03 「形容詞」の後に「名詞」がきたら別のアクセント句に
Rule 04 「名詞,形容動詞語幹」の後に「名詞」がきたら別のアクセント句に
Rule 05 「動詞」の後に「形容詞」or「名詞」がきたら別のアクセント句に
Rule 06 「副詞」,「接続詞」,「連体詞」は単独のアクセント句に
Rule 07 「名詞,副詞可能」(すべて,など)は単独のアクセント句に
Rule 08 「助詞」or「助動詞」(付属語)は前にくっつける
Rule 09 「助詞」or「助動詞」(付属語)の後の「助詞」,「助動詞」以外(自立語)は別のアクセント句に
Rule 10 「*,接尾」の後の「名詞」は別のアクセント句に
Rule 11 「形容詞,非自立」は「動詞,連用*」or「形容詞,連用*」or「助詞,接続助詞,て」or「助詞,接続助詞,で」に接続する場合に前にくっつける
Rule 12 「動詞,非自立」は「動詞,連用*」or「名詞,サ変接続」or「助詞,接続助詞,て」or「助詞,接続助詞,で」に接続する場合に前にくっつける
Rule 13 「記号」は単独のアクセント句に
となってます。単語毎にアクセントを付けるのではなく、できるだけ長いひと固まり(アクセント句)にアクセントを付けることで、全体の抑揚として表現できるようにしよう、ということです。たとえば Rule 02 は「複数の名詞が連続したら、それは一つの複合名詞にしよう」ということです。

では、結合したら、どこにアクセントを付けるのでしょうか。それを解決するのが 4. です。これは
匂坂芳典,佐藤大和「日本語単語連鎖のアクセント規則」電子通信学会論文誌 vol.J66-D, no.7, pp.847–856, 1983
という論文に基づいているものと思われます。私は現物を見てないのですが、その内容を紹介してる別の論文で確認しています。そして、この処理で必要になるのが、先の MeCab の項で説明した「追加されたアクセント関連情報」のうちの 2 つ目の項です。たとえば先の「歌う」の場合「C2」ですが、この「C2」は「後続語が 2 モーラ以上で、かつアクセント核を持たない、あるいは最終音節内に核を持つ場合は結合し、その際、先行語のアクセント核が消え、後続要素の先頭モーラにアクセント核がくる」タイプということになりますw。詳細は論文を見て下さい。

ただ njd_set_accent_type.c を見ると「C2 は無条件で後続要素の先頭モーラにアクセント核がくる」実装になっているようです。NeGiTalk では論文を元に条件を厳密にしています(効果は不明)

ちなみに、「追加されたアクセント関連情報」の最初の方は、「<アクセント核>/<モーラ数>」です。なお、私が使ったバージョンの辞書には「モーラ数が実際と一致してないもの」がありました(辞書のバグ)

JPCommon

JPCommon は「HTS へ渡すためのデータ構造に変換するもの」くらいの理解で十分でしょう。前後の関係を事前に展開列挙することで、HTS での処理を簡略化してるのだと思います(かなり無駄に冗長です)。

一応、私の解析メモを貼っておきます(無説明w)。ちなみに「フレーズ」というのは njd の 3. で結合された文節のグループ(アクセント句)です。
A:0の位置が最高音高位置 -2(低い),-1,0(高い),1,2,3(低い) + モーラ位置 + 逆モーラ位置

B:前の文節    品詞+活用形+活用型
C:今の文節の品詞 品詞+活用形+活用型
D:次の文節の品詞 品詞+活用形+活用型

E:前のフレーズ
F:今のフレーズ モーラ数_アクセント・タイプ # 感情 _  xx @ フレーズ番号 _ 逆フレーズ番号 | 先頭モーラ位置 _ 逆先頭モーラ位置
G:次のフレーズ

H:前の文
I:今の文 フレーズ数 - 音素数 @ 文番号 + 逆文番号 & 先頭モーラ位置 - 逆先頭モーラ位置
J:次の文

K: 全文数 + 全フレーズ数 - 全モーラ数

HTS

実際に音声を合成する部分です。調べてませんし、難しすぎて調べられませんw。