Ubuntu Weekly Recipe

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

この記事を読むのに必要な時間:およそ 6 分

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

先週のレシピに対するお詫び

まず先週の内容に関して2点お詫びがあります。

1点目はMbrolaの「フリーなライセンス」という言及です。オープンソースソフトウェアにおいてフリーとは「自由な」ことを指しますから,ソースが公開されておらず改良する自由を持たないmbrolaは,フリーなライセンスが適用されたソフトウェアではありません。筆者の意図は,⁠広く使うことができるように配慮されている」というところにあり,それはMbrolaプロジェクトの目的の一つでもあります。

2点目は,MeCabの解析結果には表示されていない情報もあり,それを利用するとより自然な音声を目指すことができるという点です。確かにMeCabには表示されないパラメーターがありますが,自然な音声を目指してそれを利用してもあまり意味はありません。筆者の意図としては単語間の文法的,意味的な関係のパラメーターを取得し,それをピッチに反映するというところにありましたが,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が役に立つでしょう。

注1
筆者の個人的な体験ですが,ラテン文字キャラクターを扱う文化圏にいる人と話をした際,日本語のようなキャラクターを処理する際のこのような注意点を説明しても,すんなり理解してもらえないことがありました。⁠日本語のキャラクターは一体いくつあるんだい?」と質問されて「うーん,数えきれないよ。万とかかな?」と答えて目を丸くされたのを覚えています。

著者プロフィール

坂本貴史(さかもとたかし)

Ubuntuのマルチメディア編集環境であるUbuntu Studioのユーザ。主にUbuntu日本コミュニティとUbuntu Studioコミュニティで活動。いつかユーザ同士で合作するのが夢。

コメント

コメントの記入