DNN統計的音声合成ツールキット Merlin の中身を理解をする
この記事では、音声合成ツールキットであるMerlinが、具体的に何をしているのか(特徴量の正規化、無音区間の削除、ポストフィルタなど、コードを読まないとわからないこと)、その中身を僕が理解した範囲でまとめます。 なお、HMM音声合成について簡単に理解していること(HMMとは、状態とは、フルコンテキストラベルとは、くらい)を前提とします。
はじめに
Merlinの概要については以下をご覧ください。
- Wu, Zhizheng, Oliver Watts, and Simon King. “Merlin: An open source neural network speech synthesis system.” Proc. SSW, Sunnyvale, USA (2016).
- “A Demonstration of the Merlin Open Source Neural Network Speech Synthesis System”
- 公式ドキュメント
Merlinにはデモスクリプトがついています。基本的にユーザが使うインタフェースはrun_merlin.pyというコマンドラインスクリプトで、 デモスクリプトではrun_merlin.pyに用途に応じた設定ファイルを与えることで、継続長モデルの学習/音響モデルの学習/パラメータ生成など、音声合成に必要なステップを実現しています。
デモスクリプトを実行すると、音声データ (wav) と言語特徴量(HTSのフルコンテキストラベル)から、変換音声が合成されるところまでまるっとやってくれるのですが、それだけでは内部で何をやっているのか、理解することはできません。 ツールキットを使う目的が、自分が用意したデータセットで音声合成器を作りたい、といった場合には、特に内部を知る必要はありません。 また、設定ファイルをちょこっといじるだけでこと済むのであれば、知る必要はないかもしれません。 しかし、モデル構造を変えたい、学習アルゴリズムを変えたい、ポストフィルタを入れたい、といったように、少し進んだ使い方をしようとすれば、内部構造を理解しないとできないことも多いと思います。
run_merlin.py はあらゆる処理 (具体的にはあとで述べます) のエントリーポイントになっているがゆえに、コードはなかなかに複雑になっています1。この記事では、run_merlin.pyがいったい何をしているのかを読み解いた結果をまとめます。
Merlinでは提供しないこと
Merlinが何を提供してくれるのかを理解する前に、何を提供しないのか、をざっくりと整理します。以下のとおりです。
- Text-processing (Frontend)
- Speech analysis/synthesis (Backend)
HTSと同様に、frontend, backendといった部分は提供していません。Merlinの論文にもあるように、HTSの影響を受けているようです。
Frontendには、英語ならFestival、BackendにはWORLDやSTRAIGHTを使ってよろしくやってね、というスタンスです。 Backendに関しては、Merlinのインストールガイドにあるように、WOLRDをインストールするように促されます。
デモスクリプトでは、Frontendによって生成されたフルコンテキストラベル(HTS書式)が事前に同梱されているので、Festivalをインストールする必要はありません。 misc以下に、Festivalを使ってフルコンテキストラベルを作るスクリプト (make_labels) があるので、デモデータ以外のデータセットを使う場合は、それを使います。
Steps
本編です。slt_arcticのデモスクリプトに従い、いくらかのステップに分けて、詳細に見ていきます。なお、以下デモスクリプトと書いた際には、slt_arcticのデモスクリプトを指すものとします。
- 継続長モデルの学習
- 音響モデルの学習
- 変換音声の合成
なお、Merlinのスクリプトによってはかれるデータは、基本的に
x.astype(np.float32).tofile("foobar.bin")
といった感じで、32bit浮動小数点のnumpyの配列がヘッダなしのバイナリフォーマットで保存されています。デバッグ時には、
np.fromfile("foobar.bin", dtype=np.float32)
として、ファイルを読み込んでインスペクトするのが便利です。注意事項として、ややこしいことに、拡張子は信頼できません。.lab
という拡張子であっても、フルコンテキストラベルのテキストファイルである場合もあれば、上述のようにバイナリフォーマットである可能性もあります。つらいですね!
継続長モデルの学習
継続長モデルとは、言語特徴量から、継続長を予測するモデルです。Merlinでは、phone-level / state-level のどちらかを選択可能です。Merlinの提供するDNN音声合成では、継続長の予測→音響特徴量の予測→合成、といったスタイルをとります。 デフォルトでは、state-levelで継続長(具体的には一状態当たりの継続フレーム数)を予測します。状態レベルのアライメントのほうが、時間解像度の高いコンテキストを得られ、結果音声合成の品質が良くなるので、デフォルトになっているのだと思います。 https://github.com/CSTR-Edinburgh/merlin/issues/18 に少し議論があります。
デモスクリプトを実行すると、 experiments/slt_arctic_demo/duration_model/
以下に継続長モデル用のデータがは出力されます。いくつか重要なものについて、以下に示します。
data
label_phone_align
: 音素レベルでのフルコンテキストラベルですdur
: 状態別継続長です。正確には、T
をフルコンテキストラベル中の音素数として、(T, 5)
の配列が発話ごとに保存されます。5は音素あたりのHMMの状態数で、慣例的に?5が使用されているような気がします。
inter_module
中間結果のファイル群です
binary_label_416/
: HTS形式の質問ファイルを元に生成した、言語特徴量行列です。デモスクリプトでは、416個の質問があるので、一状態あたり416次元の特徴ベクトルになります。binaryな特徴量(母音か否か)と連続的な特徴量(単語中のsylalbleの数等)があります。(T, 416)
の行列が、発話ごとに保存されています。label_norm_HTS_416.dat
: 416次元の特徴ベクトルの正規化に必要な情報です。デモスクリプトでは、言語特徴量に関してはmin/max正規化が行われるので、minおよびmaxの416次元のベクトル(計416*2次元)が保存されています。nn_dur_5/
: 無音区間が除去された、状態別継続長です。フォルダ名からは察することは難しいですが、無音区間が除去されています。nn_no_silence_lab_416/
: 無音区間が除去された、言語特徴量行列です。nn_no_silence_lab_norm_416/
: 無音区間が除去された、min/max正規化された言語特徴量行列です。nn_norm_dur_5/
無音区間が除去された、mean/variance正規化された状態別継続長です。norm_info_dur_5_MVN.dat
: 継続長の正規化に必要な情報です。具体的には、Mean-variance正規化(N(0, 1)になるようにする)が行われるので、平均と標準偏差(not 分散)が入っています。状態レベルでのアライメントを使用する場合は、5*2で計10次元のベクトルです。ref_data/
: RMSEなどの評価基準を計算する際に使われる継続長のテストデータです。data/dur
ディレクトリの継続長データを元に、無音区間が除去されたものですvar/
: 継続長の分散(not 標準偏差)です。パラメータ生成 (MLPG) に使われる想定のデータです
けっこうたくさんありますね。これだけでも、いかに多くのことがrun_merlin.pyによってなされているか、わかるかと思います。
入力/出力
中間ファイルがたくさんあってややこしいですが、整理すると、ネットワーク学習に用いる入力と出力は以下になります。
- 入力:
nn_no_silence_lab_norm_416
, 一発話あたりの特徴量のshape:(T, 416)
- 出力:
nn_norm_dur_5
, 一発話あたりの特徴量のshape:(T, 5)
学習されたモデルは、 nnets_model
というフォルダに保存されます。
音響モデルの学習
音響モデルとは、言語特徴量からメルケプストラム、F0、非周期性成分などの音響特徴量を予測するモデルです。Merlinのデモスクリプトでは、
- メルケプストラム: 60次元(動的特徴量を合わせると、180次元)
- 対数F0: 1次元(動的特徴量を合わせると、3次元)
- 有声 or 無声フラグ (voiced/unvoiced; vuv): 1次元
- 非周期性成分: 1次元(動的特徴量を合わせると、3次元)
の計187次元の音響特徴量を予測するモデルを考えます。継続長モデルのときと同様に、出力されるファイルについていくらか説明します。
data
bap
: 発話毎に計算された非周期性成分が入っています。bapはband averaged aperiodicityの略です(専門家の人にとっては当たり前かと思いますが、一応label_phone_align
: phone-levelでアライメントされたHTSのコンテキストラベルが入っています。デフォルトの設定では使いません。label_state_align
: state-levelでアライメントされたHTSのコンテキストラベルが入っていますlf0
: 対数F0です。なお、WORLDではかれるF0は無声区間で0を取りますが、無声区間の部分を線形補間することによって、非ゼロの値で補完しています。mgc
: メルケプストラムです(フォルダ名は、慣習的にメル一般化ケプストラムを表すmgc
となっていますが、デモスクリプトでは実際にはメルケプストラムです)
inter_module
binary_label_425/
: 言語特徴量の行列です。継続長モデルの場合と違って、フレーム単位で生成されているのと、フレーム単位ならではの特徴量(音素中の何フレーム目なのか、等)が追加されています。フレーム数をT
として、(T, 425)
の配列が発話ごとに保存されています。label_norm_HTS_425.dat
: 言語特徴量のmin/max正規化に必要なmin/maxのベクトルです。nn_mgc_lf0_vuv_bap_187/
: mgc, lf0, vuv, bapを結合した音響特徴量です。よくcmp (composed featureから来ていると思われる) と表されるものです。ディレクトリ名からは判別が付きませんが、無音区間は削除されています。ややこしいnn_no_silence_lab_425/
:binary_label_425
の言語特徴量から無音区間を削除したものですnn_no_silence_lab_norm_425/
: それをさらにmin/max正規化したものですnn_norm_mgc_lf0_vuv_bap_187/
:nn_mgc_lf0_vuv_bap_187/
の音響特徴量をN(0, 1)になるようにmean/variance正規化したものですnorm_info_mgc_lf0_vuv_bap_187_MVN.dat
: 音響特徴量の正規化に必要な、平均と標準偏差ですvar/
: mgc, lf0, bap, vuvそれぞれの分散です。このうちvuvは、パラメータ生成時にMLPGを行いませんが、保存はされています。
入力/出力
継続長モデルの場合と同様の中間特徴量が出力されています。改めて整理すると、音響モデルの学習に使用する入力と出力は、以下のとおりです。
- 入力:
nn_no_silence_lab_norm_425/
, 一発話あたりの特徴量のshape:(T, 425)
- 出力:
nn_norm_mgc_lf0_vuv_bap_187
, 一発話あたりの特徴量のshape:(T, 187)
学習されたモデルは、 nnets_model
というフォルダに保存されます。
波形生成
得られた継続長モデルと音響モデルから、波形を生成する処理は、大雑把にいって以下の手順で行われます。
- フルコンテキストラベルから得られる言語特徴量を元に、継続長モデルを使って継続長を予測する
- 予測された継続長を使って、フルコンテキストラベルを書き換える。より具体的には、状態毎の start_time, end_time の部分を書き換える。
- 書き換えられたフルコンテキストラベルから、音響モデル用のフレームレベルの言語特徴量を計算し、音響モデルを使って音響特徴量を予測する
- 予測された音響特徴量(static + delta + delta-delta) から、静的特徴量をMLPGによって生成する。MLPGによって生成するのは、mgc, lf0, bapのみで、vuvについてはそのまま使う。波形合成にはvuvを直接使うのではなく、vuv < 0.5以下のf0を0として扱う。
- 生成されたメルケプストラムに対して、Merlinお手製ポストフィルタを掛ける
- 得られた音響特徴量 (mgc, f0, bap) から、WORLDを使って波形合成をする
以上です。Merlinの良い所の一つに、ログをたくさんはいてくれるというのがあります。しかし、このうちポストフィルタ(デフォルトでONです)に関しては一切(デフォルトでは)ログがはかれず、気づくのに時間がかかりました。2
また、個人的な感覚ですが、このポストフィルタの影響は絶大に思いました。コードを見て、何をしているのか僕には理解できませんでしたが、ヒューリスティックな方法も含んでいるように思いました。興味のある方は、 波形合成用のconfファイルを開いて、
[Waveform]
do_post_filtering: False
のように、[Waveform]
セクションに do_post_filtering
という項目を加えて、生成結果を聴き比べてみることをおすすめします。ポストフィルタによって劇的に音質が改善されているのがわかると思います。さらに興味のある方は、コードを読んでみてください。参考文献も探しましたが、僕には見つかりませんでした。ご存知の方がいれば教えていただきたいです。
おわりに
Merlin、最初は使いにくいなと思っていましたが、頑張って読んでみれば、とても勉強になりました(使いやすいとは言っていない)。後半はだれて、適当なまとめになってしまったかもしれません、すいません。もろもろの不満から新しいツールを作りましたが、それはまた別の機会に紹介したいと思います。ありがとうございました。
-
http://jrmeyer.github.io/merlin/2017/02/14/Installing-Merlin.html によれば、This is a very clearly written Python script だそうです…。僕に読解力がないだけの可能性があります ↩︎
-
自分で作ったモデルが、どうしてもmerlinに勝てない、なぜだ、と悩んでいたとき、Merlinに言及している論文の一つに、ポストフィルタを使っているとの記述があり、探ってみるとたしかにあった、という感じでした。 ↩︎