WaveNet vocoder をやってみましたので、その記録です / WaveNet: A Generative Model for Raw Audio [arXiv:1609.03499]

Summary

三行まとめ

  • Local / global conditioning を最低要件と考えて、WaveNet を実装しました
  • DeepVoice3 / Tacotron2 の一部として使えることを目標に作りました
  • PixelCNN++ の旨味を少し拝借し、16-bit linear PCMのscalarを入力として、(まぁまぁ)良い22.5kHzの音声を生成させるところまでできました

Tacotron2 は、あとはやればほぼできる感じですが、直近では僕の中で優先度が低めのため、しばらく実験をする予定はありません。興味のある方はやってみてください。

音声サンプル

左右どちらかが合成音声です^^

自分で書いた背景

WaveNetが発表されたのは、一年以上前 (記事) のことです。発表後すぐに、いくつかオープンソースの実装が出ていたように記憶しています。 一方で、僕が確認していた限りでは、local / global conditioningを十分にサポートした実装がなかったように思います。 例えば、Githubで一番スターが付いている ibab/tensorflow-wavenet では、いまだに十分にサポートされていません(#112)。 これはつまり、生成モデルとしては使えても、TTSには使えない、ということで、僕の要望を満たしてくれるものではありませんでした。また、ちょうど最近、Parallel WaveNetが発表されたのもあり、勉強も兼ねて、local / global conditioningを最低要件として置いて、自分で実装してみようと思った次第です。

実装を通して僕が一番知りたかった(体感したかった)のは、WaveNetで本当に自然音声並みの品質の音声を生成できるのか?ということなので、Parallel WaveNetで提案されているような推論を高速化するための工夫に関しては手を付けていませんので、あしからず。

実験を通して得た知見

  • Dropoutの有無については、WaveNetの論文に書いていませんが、僕は5%をゼロにする形で使いました。問題なく動いていそうです。PixelCNN++にはDropoutを使う旨が書かれていたので、WaveNetでも使われているのかなと推測しています。
  • Gradient clippingの有無は、両方試しましたが、なくてもあっても学習は安定していました。
  • 条件付けする特徴量と音声サンプルの時間解像度を合わせるのには、(少なくともLJSpeechを使う場合には)同じ値をduplicateするのではなく、Transposed convolutionを使うほうが良さそうです。 ref: r9y9/wavenet_vocoder/#1
  • 初期のWaveNetでは、音声サンプルを256階調にmu-law quantizeして入力します。僕もはじめそうしていたのですが、22.5kHzのLJSpeechのデータを扱っていた時、そもそもmulaw / inv-mulaw で明らかに品質が劣化していることに気づきました。512階調にすればまだましになりましたが、どうせならと思ってPixelCNN++で提案されているMixture of logistic distributionsを使った次第です。
  • Mixture of logistic distributionsを使う場合は、分散の下限を小さくするのが重要な気がしました (PixelCNN++でいうpixel_cnn_pp/nn.py#L54 の部分)。でないと、生成される音声がノイジーになりやすい印象を受けました。直感的には、external featureで条件付けする場合は特に、logistic distributionがかなりピーキー(分散がすごく小さく)なり得るので、そのピーキーな分布を十分表現できる必要があるのかなと思っています。生成時には確率分布からサンプリングすることになるので、分散の下限値を大きくとってしまった場合、ノイジーになりえるのは想像がつきます。 ref: r9y9/wavenet_vocoder/#7
  • WaveNetの実装は(比較的)簡単だったので、人のコード読むのツライ…という方は、(僕のコードを再利用なんてせずに)自分で実装するのも良いかなと思いました。勉強にもなりました。
  • WaveNetが発表された当時は、個人レベルの計算環境でやるのは無理なんじゃないかと思って手を出していなかったのですが、最近はそれが疑問に思えてきたので、実際にやってみました。僕のPCには1台しかGPUがついていませんが (GTX 1080 Ti)、個人でも可能だと示せたかと思います。
  • 実験をはじめた当初、バッチサイズ1でもGPUメモリ (12GB) を使いきってしまう…とつらまっていたのですが、Parallel WaveNetの論文でも言及されている通り、音声の一部を短く(7680サンプルとか)切り取って使っても、品質には影響しなさそうなことを確認しました。参考までに、この記事に貼ったサンプルは、バッチサイズ2、一音声あたりの長さ8000に制限して、実験して得たものです。学習時間は、パラメータを変えながら重ね重ねファインチューニングしていたので正確なことは言えないのですが、トータルでいえば10日くらい学習したかもしれません。ただ、1日くらいで、それなりにまともな音声はでます。

おわりに

  • WaveNetのすごさを実際に体感することができました。まだやりたいことは残っていますが、僕はそこそこ満足しました。
  • 今後のTODO及び過去/現在の進捗は、 r9y9/wavenet_vocoder/#1 にまとめています。海外の方との議論も見つかるので、興味のある方は見てください。
  • 実装をはじめた当初からコードを公開していたのですが、どうやら興味を持った方が複数いたようで、上記issueにて有益なコメントをたくさんもらいました。感謝感謝

参考にした論文

参考になったコード

参考になりそうなコード

※僕は参考にしませんでしたが、役に立つかもしれません

Ryuichi Yamamoto
Ryuichi Yamamoto
Engineer/Researcher

I am a software engineer / researcher passionate about speech synthesis. I love to write code and enjoy open-source collaboration on GitHub. Please feel free to reach out on Twitter and GitHub.

Related