coqui-ai/ttsで日本語音声合成を試す

いままで音声の生成はやってみたことがないため勉強のためにやってみたのでメモ。

目的としてはどういうことを行っているのか理解したいというのと、TTSを学習させてみた場合にどの程度のコストがかかってどのくらいの音声が生成できるのかというのが気になったというのもある。

TTSの学習・推論処理をまとめたrepositoryはいくつかあるが、coqui-ai/ttsを使ってみることにした
https://github.com/coqui-ai/TTS

理由としては

ちなみにcoqui-ai/ttsは、readmeに書いてあることが同じかつ開発者も同じ方なのでmozilla/ttsと同じコードベースだと思われる。mozilla/ttsはmasterの最終コミットが2021/02に対してcoquiは数日前なので後継となるrepoという扱いっぽい。

データセット

日本語の音声データということでsingle speakerでデータ量も多いJSUTを使う
https://sites.google.com/site/shinnosuketakamichi/publication/jsut

このコーパスは日本語テキストと読み上げ音声からなります.音声データは48kHzでサンプリングされ,無響室で収録されました.一人の日本語女性話者の音声を収録しました.このコーパスは,10時間の音声 を含み,以下のデータからなります.

notebookで公開されている英語学習済みのモデルで使われているデータセットがLJSpeechで、そちらは24時間分のデータなので比較すると少なめ。

ちなみにJSUTを作成された方は他にも日本語の音声データセットをいろいろ公開している
https://sites.google.com/site/shinnosuketakamichi/publication/corpus?authuser=0

データセット準備

まずJSUTをLJSpeechの形に直す。LJSpeechの形についてはこちらで説明してくれている
https://qiita.com/tset-tset-tset/items/7b388b0536fcc774b2ad#112-ljspeech-%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88

こんな感じで簡単に変換できる

#!/bin/bash

# jsutのzipを展開した際のroot
ROOT_PATH=$1

mkdir -p $ROOT_PATH/wavs
find $ROOT_PATH -name '*.wav' -type f | xargs -I@ cp @ $ROOT_PATH/wavs/
find $ROOT_PATH -name transcript_utf8.txt -type f | xargs cat | sed -e 's/:/|/' > $ROOT_PATH/metadata.csv

metadataはカタカナに直したものを使うことにする。これについても同じqiitaの著者が書いてくれたこちらの処理を使わせてもらう(一番下のセルの処理)
https://github.com/tset-tset-tset/hoge/blob/8ef0a1c/JSUT.ipynb

また、変換しそこねた漢字はできるだけ除きたいので削除しつつtrain/valのデータを分ける(ただし、これでも完全に削除しきれるわけではないので注意)

実際に使ったスクリプトはこちら
https://github.com/y-kamiya/machine-learning-samples/pull/24

ちなみにvalidation用のデータ数を160件しか用意してないが本来はもっと多く用意した方がよいと思われる。ここではまずやってみるということで、なるべく学習完了までの時間を短くするために少なめの数にした。また、品質チェックはattentionの並びと生成された音声ファイルを聞いて確認するのがと書いてあったためというのもある。

モデル

以下のモデルで試す

  • Text-to-Spectrogram
    • GlowTTS
  • Vocoder
    • MultiBandMelGAN

理由はtacotron2&waveglowなどに比べて学習・推論が速いらしいためというのと、vocoderについては推論用のnotebookを見るとよくMultiBandMelGANが使われているため
https://github.com/coqui-ai/TTS/wiki/%F0%9F%90%B8-TTS-Notebooks,-Examples-and-Tutorials

英語での推論はこちらのnotebookで試せる https://colab.research.google.com/drive/1NC4eQJFvVEqD8L4Rd8CVK25_Z-ypaBHD?usp=sharing

ちなみにMultiBandMelGANの学習済みステップ数が145Kと書いてあるが、これは1450Kの間違いっぽいので比較する場合は注意。

学習

ljspeechをtacotron2で学習するcolab notebookを元にして学習する
https://gist.github.com/erogol/97516ad65b44dbddb8cd694953187c5b

学習に使ったnotebookはこちら
https://github.com/y-kamiya/machine-learning-samples/blob/0cb64ec7be5ea5165d3a2424ade2f6948e61fdda/scripts/tts/TTS_jsut_glowtts.ipynb
うまくいった場合の参考として出力は一部残してある

また、ljspeechの学習済みモデルからの転移学習も試してみたいので、こちらのコミットをcheckoutして使うことにした
https://github.com/coqui-ai/TTS/tree/4132240

学習済みモデルのリストはこちらにある
https://github.com/coqui-ai/TTS/wiki/Experimental-Released-Models

カタカナのテキストを発音記号に変換するためにespeak-ngを使うが、こちらに書かれた修正済みのものを使う
Mozilla TTS (Tacotron2) を使って日本語音声合成 - Qiita

パラメータについてはqittaを参考にしつつ必要そうなものを足していった。精度向上のためにはもっと最適化していく必要があると思われるが一旦日本語の音声が聞ける範囲で学習できる状態にはなった

# glow tts用のconfigを元にする
CONFIG = load_config('/content/TTS/TTS/tts/configs/glow_tts_ljspeech.json') 

# 使うデータセットの設定
CONFIG['datasets'][0]['path'] = <データセットのroot>
CONFIG['datasets'][0]['meta_file_train'] = <rootからtrainデータへのpath>
CONFIG['datasets'][0]['meta_file_val'] = <rootからvalデータへのpath>
CONFIG['phoneme_language'] = 'ja'
CONFIG['text_cleaner'] = 'basic_cleaners'
# 事前にphoneme_cacheを事前作成してダウンロードしておく(高速化のため)
CONFIG['phoneme_cache_path'] = "/content/phoneme_cache"
# 0から学習なら必須
CONFIG['audio']['stats_path'] = "/content/scale_stats.npy"  
CONFIG['audio']['signal_norm'] = True


# 出力先とテスト用データ
CONFIG['output_path'] = <google drive上のpath指定するとよい>
CONFIG['test_sentences_file'] = <テスト用テキストを入れたファイルへのpath>

# 学習環境に応じて都合のよい値を入れる
CONFIG['num_loader_workers'] = 2
CONFIG['num_val_loader_workers'] = 1
CONFIG['print_step'] = 1000
CONFIG['save_step'] = 5000
# これを指定するとevaluationが飛ばされるので学習は速く済むが、その部分のログがtensorboard上に残らなくなるので見づらい
#CONFIG['test_delay_epochs'] = 250

上書きの元となるljspeech用のconfigはこちら
https://github.com/coqui-ai/TTS/blob/41322408331207093538c473891d1306785dc923/TTS/tts/configs/glow_tts_ljspeech.json

注意点としては

  • phoneme_cacheは事前に生成
    • テキストを発音記号に変換したデータでこれが実際のモデルへの入力になる
    • 生成自体に時間がかかる(30分くらい)ので毎回やるのは無駄
  • scale_stats.npyを事前に生成して指定する
    • これなしで0から学習を試してみたが声として聞こえる形にはならなかった
  • wavは事前にresampleしておく
    • resample=trueと設定することで学習時に変換してくれるが重いの処理なので時間の無駄
      • resample済みwavを読む場合のavg_load_time: 0.01秒程度
      • resampleして読む場合のavg_load_time: 5秒程度
  • wav, phoneme_cache, scale_stats.npyはダウンロードしてローカルから読むこと
    • driveの置いたものを参照するとネットワーク経由で毎回取得するので当然ではあるがとても時間がかかる
      • wavをlocalから読む場合のavg_load_time: 0.01秒程度
      • wavをdriveから読む場合のavg_load_time: 20秒程度

scale_stats.npyの生成は専用のスクリプトが用意されている
https://github.com/coqui-ai/TTS/blob/41322408331207093538c473891d1306785dc923/TTS/bin/compute_statistics.py

# 上記のconfigをロードした上で
python TTS/bin/compute_statistics.py --config_path config.json --out_path  <出力先>

phoneme_cacheは学習開始時にファイルが存在しなければ生成される。なのでそのデータセットで最初の学習開始時だけキャッシュのpathをdrive上などに設定しておく

# 上記のconfigのうち以下の設定を出力したい場所に変える
CONFIG['phoneme_cache_path'] = <出力先>

次回からはそれをローカルにダウンロードした上でそのpathを設定しておく

wavのresampleも専用のスクリプトが用意されている(今回checkoutしたversionにはなかったがmainには含まれていた)
https://github.com/coqui-ai/TTS/blob/87d674a/TTS/bin/resample.py

python TTS/bin/resample.py  --n_jobs 4 --input_dir jsut_ver1.1 --output_dir <出力先>

学習の出力をdriveに置く場合、save_stepで指定したstep毎にcheckpointが作られることになるが、一つ328MBあるのでいっぱいにならないよう注意。また、tensorboardのログにはspectrogramと音声ファイルが記録されるため見やすい反面サイズが大きいので注意(300K stepで2GBくらいになる)

結果

結果確認のおすすめ方法はこちらに書かれている
https://github.com/coqui-ai/TTS/wiki/Training-and-Testing-TTS#inspecting-training

  • validationを行うようにした上でtensorboardの出力を見るのがよい
    • まずはvalidation lossが下がっていること確認
    • 次にattentionが対角線上に並んでいることを確認
    • 最後にもっとも重要な指標としてtest用の出力音声を聞いて確認
      • train, evalの出力音声は学習データによるバイアスがかかっているためtestで確認すべき

TODOとなっている部分も多いがtensorboardの見方についての解説はこちら
https://github.com/coqui-ai/TTS/wiki/Tensorboard-Logs

以下の4つを比べてみた 1. scale_statsなしで0から学習 2. scale_statsありで0から学習 3. LJSpeechのscale_statsを指定してljspeechの学習済みモデルに追加学習 4. JSUTのscale_statsを指定してljspeechの学習済みモデルに追加学習

まず1.について、250K程度学習されたモデルで確認したところ指定した文をしゃべっているようには聞こえる状態にならなかった

statsありのモデルでは少なくとも100Kの段階で喋るように聞こえる状態になっていたため、1.の学習はそれ以上やらなかった

f:id:y-kamiya:20210613204851p:plain

lossの計算はこちら
https://github.com/coqui-ai/TTS/blob/41322408331207093538c473891d1306785dc923/TTS/tts/layers/losses.py#L402

avg_lossは他の2つのlossの和。log_mleがメインの損失(テキストからmelspectrogramへの変換を学習するためのもの)であり、loss_durがalignmentを学習すための補助的な損失(入力テキストのtoken毎の音の長さに対する損失)

この辺は論文を読んだものの説明できるほど理解できてないのでもう少しちゃんと勉強したいところ。

水色と赤のラインが学習済みモデルに追加学習を行ったものであり、300Kくらいから始まっているのはそのため。

ピンクのラインがstatsありで0から学習したモデルだが、直線部分が多くなっているのはevalを行わない設定で学習したときが何回かあったため。このように見づらくなってしまうため、スキップせずにevalを実行しつつ学習した方がやってみる分にはよさそう。

水色(LJSpeechのstats+学習済みモデル)は330K以降は右肩あがりで過学習気味、その他2つはほぼ水平なラインに見えるがわずかにlossが小さくなっていっている。

400K時点での各モデルが生成した音声はこちら(eval時にtestデータから生成されるものでvocoderはGriffin-LIm)

scale_statsありで0から学習

LJSpeechのscale_statsを指定してljspeechの学習済みモデルに追加学習

JSUTのscale_statsを指定してljspeechの学習済みモデルに追加学習

ある程度聞けるようになったものの、流暢とは言えないレベル。それぞれの結果はだいたい似たようなものだがイントネーションが微妙に違って面白い。ロスを見るとJSUTのstatsを適用して学習してるものはわずかずつだがevalデータに対するロスが下がっていっている。聞いた感じでも若干その方が自然な発音に聞こえる(気がする)

alignmentはこんな感じでstatsなしで0から学習したもの以外はきれいに並んでいるように見える f:id:y-kamiya:20210614091722p:plain

学習にかかる時間

やってみるに際してどのくらいの規模になるかを知りたいところだが、意外とこういう情報は書かれてなかったりするためわかったことを書いておく。

今回やってみた際の条件

  • batch_size: 32
  • mixed_precision: true
  • gpuはP100
  • 学習データは約7500件、評価データは160件
    • ちなみにJSUTの全データ数は7696件で音声10時間分

1 epochあたりほぼちょうど3分というところで、100K回すのに約22時間弱くらいだった。notebookには学習時のログも残してあるので参考までに
https://github.com/y-kamiya/machine-learning-samples/blob/0cb64ec7be5ea5165d3a2424ade2f6948e61fdda/scripts/tts/TTS_jsut_glowtts.ipynb

ちなみにP100を基準に書いてあるのはcolab proを使ったため。今回学習を試すにあたって最初は有料プランを使ってなかったが少しやってみたところで

  • 数日単位で回す必要があることがわかった
  • かなりの頻度で止まってしまう
    • 以前に比べてちゃんと判定されているようで3時間くらい経つと人が操作していることの確認が出てきた
    • 5分おきにボタンを押すようにしたり、windowを更新したりというスクリプトを入れておいても同様

ので試しに1ヶ月使ってみることにした。少し前から一度使ってみようとは思っていたのでちょうどいい機会だったが、使ってみた感想としては制限がかなり緩和されるためかなり使いやすかった(これで1000円でいいのかと思うくらい)。ただ、話がそれるのでcolab proについては次の記事として書くことにする。

ちなみに1 epochにかかる時間として、V100の場合だと2分半程度、T4なら3分半程度だった(と思う)

まとめ

生成したmelspectrogramをMultiBandMelGANで音声に変換したものも比較する予定だったが、長くなったので一旦ここまでにしてまとめ。

coqui-ttsは

  • 学習済みモデルに日本語は存在しないが、espeak-ngを用いることで日本語のテキストから発音への変換を行って学習することが可能
  • 英語モデルの学習・推論用のcolab notebookは公開されているため同じ形でやることで比較的スムーズに学習開始できた
    • 冒頭で挙げたqiitaで学習を試した際の工夫や結果が書かれているのでなおやりやすい

学習を行う場合に思ったこととしては

  • 学習中に出力されるログやcheckpointで結構容量を食うのでdriveを使ってる場合は注意
  • フリーのcolaboratoryなら100Kまで学習させて結果を比較するくらいにするのが良さそう(ちょうど1日分くらいで済む)

これをやるなかで調べてみたが、日本語のttsであればespnetが学習済みモデルも提供していて環境が整っているに見えたのでそちらも試して比較してみたい。