Ubuntu Weekly Recipe

第251回Ubuntuに日本語を話してもらおう(後編)

先週のレシピでは、mbrolaを使った音声合成と、MeCabを使った形態素解析のレシピをお届けしました。

今回は予告どおり、一連の流れを自動化するPythonスクリプトを作成してみます。

なお、Pythonを選択した理由は2つあります。ひとつはMeCabのPythonバインディングのパッケージが提供されていて都合が良かったこと、もうひとつは日本語文字の扱いが簡単そうなことです。後者を先に簡単に説明します。

Pythonで日本語処理

Mecabを通じて辞書から取得した「読み」をmbrolaの音素リストに変換するためには、日本語を1文字1文字処理する必要があります。例えば冒頭の例「八戸」の読みである「ハチノヘ」を音素リストに変換するには、次を行う必要があります。

  1. 「ハチノヘ」「ハ⁠⁠、⁠チ⁠⁠、⁠ノ⁠⁠、⁠ヘ」に切り分けたリストを得る
  2. リストのそれぞれの要素に対して置換処理を行い、⁠h⁠⁠、⁠a⁠⁠、⁠tS⁠⁠、⁠i⁠⁠、⁠n⁠⁠、⁠o⁠⁠、⁠h⁠⁠、⁠e」のリストを得る

これらの処理は注意して行わなければなりません。なぜかというと、日本語文字(以下、日本語キャラクターと呼んでいきます)は1バイトでは表せないからです。もし命令が1バイトづつ処理するように実装されていたら、日本語キャラクターはうまく処理されず、予期しない結果が返ってきます。

具体例を挙げて説明しましょう。例えば文字列「aiueo」はたいていの場合、5バイト(40ビット)長のビット列となり、16進数で表記すると「0x61 69 75 65 6F」となります。命令が1バイト(8ビット)づつ処理するよう実装されている場合、⁠0x61⁠⁠、⁠0x69⁠⁠、⁠0x75⁠⁠、⁠0x65⁠⁠、⁠0x6F」と1バイトづつとり出されても「a⁠⁠、⁠i⁠⁠、⁠u⁠⁠、⁠e⁠⁠、⁠o」となり、特に問題がありません。

ではこの処理系に「あいうえお」を与えてみましょう。文字列「あいうえお」をビット列にする方法はいくつもありますが、UTF-8を用いると15バイト(120ビット)長のビット列となり、16進数で表記すると「0xE38182 E38184 E38186 E38188 E3818A」となります。上記の処理系でこれを扱うと「0xE3⁠⁠、⁠0x81⁠⁠、⁠0x82⁠⁠・⁠以下略)と取り出され、⁠あ⁠⁠、⁠い⁠⁠、⁠う⁠⁠、⁠え⁠⁠、⁠お」に対応するビット列にはなりません。それでは3バイトずつ取り出すようにすればよいというわけでもなく、UTF-8はキャラクターに応じて1バイトから4バイトで表現するるため、ビット幅固定の処理そのものが不適切な実装です。

歴史的経緯により、アルファベットなどラテン文字のキャラクターは1バイトで表現できるように定められているのが一般的です。そのため、1バイトずつ処理する処理系で十分でした。しかし日本語や中国語、韓国語などは、256キャラクターしか表現できない1バイトでは到底足りません。そこで複数のバイトを使って表現するわけですが、このような実装は上記の1バイト処理系では適切に扱うことができません。日本語を処理するにあたりこれでは困ります[1]⁠。

Pythonではこの問題に対し、インタープリター内で文字列をUnicodeによるビット列で表現し、このビット列を操作する専用の命令をオブジェクトのメソッドとして提供することで対応しています。Python2と3で違いがあり、Python2ではUnicode型のインスタンスを使う必要がありますが、Python3では文字列のオブジェクト自体がデフォルトでUnicodeとなります。そのため、Python2とPython3でスクリプトの書き方を変える必要があります。

Python2でUnicode型のインスタンスを作成する場合、⁠u""」「U""」のリテラルを用います。また、文字列型オブジェクトをUnicode型に変換するには、文字列型の文字符号化方式を引数にunicode()関数を実行します。例えば、任意のstringからUnicode型のインスタンスを作成する場合、⁠result = unicode(string, "utf-8")」とします。この際、文字列型stringがどのような文字符号化方式のビット列となっているかを、あらかじめ知っておく必要がありますので注意してください。

こうして作成したUnicode型オブジェクトのインスタンスは、正しくシーケンス処理をすることができます。例えば一文字づつ取り出すなら次のようになります。

for character in result:
    print(character)

今回は前後の要素をチェックするために添字付きのアクセスをする必要があったので、次のコードを多用しています。

for (i,character) in iterate(result):
    print(result[i])

PythonのUnicode型に関してより深く知りたい場合は、Python2マニュアルのUnicode HOWTOPython3のUnicode HOWTOが役に立つでしょう。

MeCabのPythonバインディングのインストール

先週扱ったMeCabのPythonバインディングですが、⁠python-mecab」パッケージとして提供されています。Ubuntuソフトウェアセンターを使いインストールしてください。

図1 Ubuntuソフトウェアセンターでpython-mecabを検索
図1 Ubuntuソフトウェアセンターでpython-mecabを検索

この「python-mecab」パッケージはPython2用のモジュールを提供します。Ubuntuの開発方針として「すぐにでもPython3をデフォルトとしよう」というものがありますが、今回はこのモジュールの制約を受けて、Python2用のスクリプトを作りました。

AlternativesシステムでMeCabのデフォルト辞書を変更する

MeCabから取得できる形態素情報は使う辞書によって異なります。今回はnaist-jdicの使用を前提とします。もし複数の辞書をインストールしている場合は、DebianのAlternativesシステムを使って切り替えておきます。

Alternativeシステムとは、類似の機能を持つプログラムやバージョンが異なるプログラムを共存させるための仕組みです。例えば「システムにemacs23がインストールされているけど、emacs24も使いたい」というとき、インストールしたemacs24をAlternativesシステムの「emacs」に登録します。以降はAlternativesシステム上で「emacs」に対して「emacs23」「emacs24」のどちらかを選択できるようになり、選択したものを「emacs」というコマンドで実行することができるようになります。

他の例も挙げてみましょう。何らかのソフトウェアをビルドする際にその過程で、あるコマンドが実行されるとします。そのコマンドには複数の実装があり、デフォルトでインストールされている実装ではソフトウェアのビルドに失敗するとします。適した実装をインストールして使わなければならないわけですが、余計なトラブルを防ぐために、なるべくデフォルトのものには手をつけたくありません。

このようなトラブルの解決するのがAlternativesシステムです。Alternativesシステムは実はシンボリックリンクを管理しています。先ほどの「emacs」は実行バイナリへのシンボリックリンクになっていて、Alternativesシステムはこのシンボリックを設定に応じて「張り替え」ることで、切り替えを柔軟にしています。ソフトウェアをビルドする際は一時的にコマンドがbetuna 実装を指すようにし、ビルドが無事に終了したら元に戻します。

さてMeCabのデフォルト辞書もまた、このAlternativesによって管理されています。確実にnaist-jdicをデフォルトにするために、次のように入力して再設定を行います。

$ sudo update-alternatives --config mecab-dictionary

alternative mecab-dictionary (/var/lib/mecab/dic/debian を提供) には 6 個の選択肢があります。

  選択肢    パス                               優先度  状態
------------------------------------------------------------
  0            /var/lib/mecab/dic/naist-jdic         100       自動モード
  1            /var/lib/mecab/dic/ipadic             70        手動モード
  2            /var/lib/mecab/dic/ipadic-utf8        80        手動モード
  3            /var/lib/mecab/dic/juman              30        手動モード
  4            /var/lib/mecab/dic/juman-utf8         40        手動モード
* 5            /var/lib/mecab/dic/naist-jdic         100       手動モード
  6            /var/lib/mecab/dic/naist-jdic-eucjp   90        手動モード

現在の選択 [*] を保持するには Enter、さもなければ選択肢の番号のキーを押してください:

この例では、5を選択することになります。

スクリプトの概要

今回作成したスクリプトは、次からダウンロードできます。

このスクリプトは、PythonのMeCabモジュールとnaist-jdicを利用して入力文字列を分かち書きし、その結果得られたの読みや品詞の情報を利用してmbrola用の一時ファイルを作成します。その後、スクリプトはmbrolaを実行して音声を合成します。合成した音声をシェルのパイプ機能を用いてaplayerに標準入力し、aplayerはPulseAudioとALSAサウンドサブシステムを用いてシステム音声を出力します。

mbrolaデータベースとしてjp1を用います。スクリプト内にデータベースへの相対パス「./jp1/jp1」が記述してありますので、jp1データベースの配置先に注意してください。

スクリプトを実行するには、端末で次のように実行します。なお、この例ではスクリプトのファイル名を「mecabrola.py」としています。

$ python2 ./mecabrola.py
何を喋りたい?:

実行すると入力待ちとなりますので、喋らせたい文を入力してエンターを押してください。

$ python2 ./mecabrola.py
何を喋りたい?: あらゆる現実をすべて自分の方へねじ曲げたのだ。
再生中 Sparc オーディオ 'stdin' : Signed 16 bit Big Endian, レート 22050 Hz, モノラル

スクリプトの制約として、naist-jdic辞書にない単語、あるいは辞書から読みを取得できない単語は発音できず、無音となります。この制約により、次の例では「ぴゅんとつばを」が無音となります。

$ python2 ./mecabrola.py
何を喋りたい?: アフリカ人は、実に巧みにぴゅんとつばを吐く。
再生中 Sparc オーディオ 'stdin' : Signed 16 bit Big Endian, レート 22050 Hz, モノラル
$ echo "アフリカ人は、実に巧みにぴゅんとつばを吐く。" | mecab
アフリカ    名詞,固有名詞,地域,一般,*,*,アフリカ,アフリカ,アフリカ,,
人      名詞,接尾,一般,*,*,*,人,ジン,ジン,,
は      助詞,係助詞,*,*,*,*,は,ハ,ワ,,
、      記号,読点,*,*,*,*,、,、,、,,
実に    副詞,一般,*,*,*,*,実に,ジツニ,ジツニ,,
巧み    名詞,形容動詞語幹,*,*,*,*,巧み,タクミ,タクミ,,
に      助詞,副詞化,*,*,*,*,に,ニ,ニ,,
ぴゅんとつばを    名詞,一般,*,*,*,*,*
吐く    動詞,自立,*,*,五段・カ行イ音便,基本形,吐く,ハク,ハク,,
。      記号,句点,*,*,*,*,。,。,。,,
EOS

さらに、促音や拗音から始まるような単語は処理のロジックから外れるため、まるまる無音としました。英数字は辞書にあるもののみ発音できます。例えば「C言語」が辞書に収録されています。

mbrola用ファイルのダンプ

コマンドライン引数を与えてスクリプトを実行すると、mbrolaに与えるファイルの内容をダンプしてから発声します。引数は何であっても構いません。

$ python2 ./mecabrola.py dump
何を喋りたい?: 河童の川流れ
k      75      0      205
a      75      0      205
Qp     150     0      190
a      75      0      190
n      75      0      205
o      75      0      205
k      75      0      205
a      75      0      205
w      75      0      205
a      75      0      205
n      75      0      205
a      75      0      205
g      75      0      205
a      75      0      205
rr     75      0      220
e      75      0      220
_      90      

再生中 Sparc オーディオ 'stdin' : Signed 16 bit Big Endian, レート 22050 Hz, モノラル

ファイルにコピーし手動でmbrolaに読み込ませるという用途に使えると思います。

jp1/jp3の制約

jp1とjp3は作成者が同じ人物ということもあり、含んでいるデータセットで対応可能な音素セットはほぼ同じです。この2つのデータベースの持つ制限として、促音が関係するデータが欠けていることが挙げられます。これにより、例えば「骨子」「ヘッダ⁠⁠、⁠ぼっち」といった語が、そのままでは発音できません。

対してjp2はjp1/jp3の倍以上のデータを含んでおり、促音が関係するデータも充実しています。しかしjp1/jp3とは異なる音素セットとなっており、互換性はありません。

どちらを選ぶか悩みましたが、今回は男声と女声が発音可能なように、jp1とjp3データベースを選択しました。欠けているデータへの対応ですが、本来促音が発音するはずだった時間が無音となるように処理することで、それっぽく聞こえるようにしました。

$ python2 ./mecabrola.py dump
何を喋りたい?: 一人ぼっちは寂しいもんな。
h      75      0      205
i      75      0      205
t      75      0      205
o      75      0      205
rr     75      0      205
i      75      0      205
b      75      0      205
o      75      0      205
_      75      0      205
_      75      0      205
tS     75      0      190
i      75      0      190
w      75      0      205
a      75      0      205
s      75      0      205
a      75      0      205
b      75      0      205
i      75      0      205
S      75      0      205
i      75      0      205
i      150     0      190
m      75      0      205
o      75      0      205
N      150     0      190
n      75      0      205
a      75      0      205
_      150     0      205
_      90      

再生中 Sparc オーディオ 'stdin' : Signed 16 bit Big Endian, レート 22050 Hz, モノラル

なおmbrolaは、音素リスト内に合成できない組み合わせを見つけると音声合成を中止し、その時点でエラーを出力します。スクリプトはこのエラーをそのまま表示します。ダンプ出力と組み合わせて、スクリプトの改良に役立ててください。次の例では、⁠_-Qt」に対応するデータがないため、エラーとなっています。

$ python2 ./mecabrola.py a
何を喋りたい?: 「一般的な文系」って・・・。
i      150     0      205
Qp     150     0      205
a      75      0      205
N      150     0      190
t      75      0      205
e      75      0      205
k      75      0      190
i      75      0      190
n      75      0      205
a      75      0      205
b      75      0      205
u      75      0      205
N      150     0      205
k      75      0      205
e      75      0      205
i      150     0      190
_      150     0      205
Qt     150     0      205
e      75      0      205
_      37      0      205
_      37      0      205
_      37      0      205
_      37      0      205
_      90      

再生中 Sparc オーディオ 'stdin' : Signed 16 bit Big Endian, レート 22050 Hz, モノラル
Fatal error: Unkown recovery for _-Qt segment

オープンソースなテキスト音声合成ソフトウェア

さてmbrolaですが、他のソフトウェアのエンジンとしても使われています。eSpeakは音声合成のバックエンドをmbolaから選択することができます。gespeakはeSpeakのGUIフロントエンドです。festivalもmbrolaをバックエンドの一つとして使うことができます。

eSpeakとFestivalはspeech-dispatcherというスピーチシンセサイザーフレームワークのエンジンのひとつとなっています。これらはUbuntuのパッケージとしても提供されていますが、mbrolaの日本語データベースのパッケージが提供されておらず、また日本語特有の処理が含まれていないため、日本語のテキストから音声合成することはできません。今回紹介した内容を実装することで、日本語のテキスト音声合成をすることができる可能性があると思います。

また、日本語テキスト音声合成専用のオープンソースなソフトウェアとして、名古屋工業大学のOpen Jtalkがあります。mbrolaとは異なり、テキスト入力から音声合成までを含むシステムです。Ubuntuでは12.04からパッケージが提供されていますので、すぐに試すことができます。


今回のレシピはこれで終わりとなります。実際に音声合成してみると、全体的に抑揚がないことがわかります。試行錯誤してみましたが、同じ品詞の単語は同じピッチになってしまいます。今回利用したソフトウェアだけでは文章構造の情報が足りないからです。テキスト音声合成システムは音声合成だけでは完結せず、音声学や言語学も含めた様々な研究成果から成り立っていることが実感できます。

おすすめ記事

記事・ニュース一覧