colab proを使ってみた
1ヶ月だけcolab proを使ってみたのでメモ
https://jsapachehtml.hatenablog.com/entry/2021/06/16/094213
こちらの記事で少し書いたが、学習に数日かかるものだったため再実行の頻度を下げようと思って一度使ってみることにした
colaboratoryの無料版、有料版の比較は既にweb上にたくさん情報があるので詳細な説明はそちらを見た方がよいが、今回は実際に使ってみた感想ということで自分な理に気づいた点をメモとして残しておく
まずはざっくりした感想としては、とても良いものでした、ということになる
理由としては
ほぼ途切れることなくgpuを利用可能
まず強制的に途切れるまでの利用時間が12h -> 24hと2倍になる。ただ、もっと大きなメリットはそもそも途切れなくなること。最近はinteractive利用のチェックが以前より厳しくなったようで、3時間くらい経つとチェックが入る(ログイン時のロボットじゃないですよね?のやつ)
これはjavascriptで定期的にボタンを押すようにしてあっても、定期的にページのリロードをするようにしておいても変わらず確認された(私が知らないだけで回避する方法はあるのかもしれない)
colab proにした場合、私が使ってる間はこんな感じだった
- interactive利用のための確認は一度も入らなかった
- pcがスリープに入ってしまったことがあったが復帰したらまだ実行中だった
- 少なくとも1時間以上は経ってた
- 半日以上経ってたことも一度あったさすがにそのときは切れていた
- スリープにならないようにした上でjavascriptから定期的にボタンを押すようにしておいたところ24h経つまで切れなかった
- その後すぐに初期化して再実行したところ、同じようにそのまま24h切れなかった
- 無料の場合、12hぶっ通しで使った後は少なくとも数時間使えなくなったはず
- 別のnotebook(gpuモード)も開いてそちらでもgpuを使って実行したところ、2~3日目のどこかで使用上限のため~のメッセージで切れた
- ただし、そのあと1時間くらいしたら実行できるようになった
使っていて思ったが使用上限の判定はおそらくこんな感じなのではないだろうか
- 使用可能時間が単位時間毎にn秒回復する
- 実際は秒単位とかで細かく見てるはずだが(gcpがそうなので)ここではわかりやすく1時間毎と考えておく
- colab proの場合、3600 <= n <= 7200となっているため2つ以上同時に使っている場合は枯渇する
- ただし、3600 <= n なのでまたすぐに使えるようになる
- 無料版の場合、n < 3600のためgpu一つの利用でも枯渇し12h使った後は回復の時間が必要になる
基本的に24h毎に再実行する前提ならば、それ以外は途切れることなく実行可能であるように見えた。ただし、24h実行しっぱなしを続けたのはせいぜい3~5日間で、別の学習を始める前は開始までにある程度間があいていた。なので本当に24hを途切れることなく続けた場合はどこかで上限にひっかかるときが来るかもしれない。
使えるリソースがスケールアップ
colab proの場合は無料版に比べて以下のように使えるリソースが良くなっている
- gpuは基本的にP100 or V100のどちらか
- 大抵P100でたまにV100というイメージ
- メモリとストレージは無料版の2倍分
なので大きめのデータセットを試したい場合はcolab proにすることで可能になるものがあるはず
ブラウザ上でcliによるアクセスが可能
無料版だとnotebookを実行している際は他の操作ができないため、以下のようなときに困ったことがあった
- コンソールには出してない別のログが確認したい
- 出力ファイルが上書きされていく実装の場合、現状のものを取り出してdriveに保存したい
colab proだと画面左下のアイコンからcliを起動できコマンドラインによる操作が別途可能だったため、上記のような際に簡単に解決できる
まとめ
上記を考えるとこれで$10であれば十分すぎると感じた
実際同じ用途をgcp上でノードを立てて行う場合、P100であればプリエンプティブの料金でも$0.43/hourなので1ヶ月動かしておけたとしてgpuだけで$0.43 x 24 x 30 = $309.6(この他にインスタンス料金などもかかる)
なので1 gpu、2 cpu、メモリとストレージ容量の制約で問題のない規模の学習を行うなら圧倒的にお得だし準備の手間もかからない。そして、おそらく学習を試してみるという程度であれば間違いなくこの規模に収まるので、ちょっと時間がかかる学習などやってみたい場合にとてもよい選択だと思った
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しておく
- wav, phoneme_cache, scale_stats.npyはダウンロードしてローカルから読むこと
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.の学習はそれ以上やらなかった
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から学習したもの以外はきれいに並んでいるように見える
学習にかかる時間
やってみるに際してどのくらいの規模になるかを知りたいところだが、意外とこういう情報は書かれてなかったりするためわかったことを書いておく。
今回やってみた際の条件
- 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が学習済みモデルも提供していて環境が整っているに見えたのでそちらも試して比較してみたい。
colaboratoryとGCSのregionを一致させる
https://jsapachehtml.hatenablog.com/entry/2021/04/25/142900
こちらの書いたことの補足として調べたことをメモ
サーバの位置を割り出すipinfo.ioを参照するのが簡単そうだった。
モダンなIPアドレス表示サービス「ipinfo.io」 - ソフトアンテナブログ
colabでも curl
するだけでわかりやすく表示されるためよい
https://colab.research.google.com/notebooks/empty.ipynb
こちらから使い捨てのcolab notebookを2つ開いて位置を確認してみた
!curl ipinfo.io { "ip": "35.238.215.102", "hostname": "102.215.238.35.bc.googleusercontent.com", "city": "Council Bluffs", "region": "Iowa", "country": "US", "loc": "41.2619,-95.8608", "org": "AS15169 Google LLC", "postal": "51502", "timezone": "America/Chicago", "readme": "https://ipinfo.io/missingauth" }
!curl ipinfo.io { "ip": "35.186.165.109", "hostname": "109.165.186.35.bc.googleusercontent.com", "city": "Washington", "region": "Washington, D.C.", "country": "US", "loc": "38.8951,-77.0364", "org": "AS15169 Google LLC", "postal": "20045", "timezone": "America/New_York", "readme": "https://ipinfo.io/missingauth" }
アイオワとワシントンだった
ちなみに何度か試してみたところ上記以外のregionも出てきたが、us以外から割り当てられたことはなかった。ただし、この部分は各地域のインスタンス稼働率に依ってくると思うのでasiaが出てくることがないとは言えない
gcpのregionのリストはこちら
https://cloud.google.com/compute/docs/regions-zones?hl=ja#available
アイオワはgcpのregionとしてあるのでわかりやすくus-central1、ワシントンというregionはgcpにはないものの、地図で調べればバージニア州のすぐ東隣なのでus-east4であろうことが推測できる
ちょうどus-centralにバケットを作ってあり2.5GBのzipファイルを上げてあったのでそれぞれ一回ずつgsutilでダウンロードしてみた結果がこちら
# アイオワ [1 files][ 2.5 GiB/ 2.5 GiB] 45.4 MiB/s
# ワシントン [1 files][ 2.5 GiB/ 2.5 GiB] 51.8 MiB/s
ほぼ同じ速度だった。当然ネットワーク環境に応じてブレがあるはずなので一回だけでは確かなことは何も言えないものの、同一であれば桁違いに速いとかではなさそう
この日はこれしかgcsにアクセスしなかったので後日課金額を調べてみたところ2.7円くらいとなっていた。おそらくこちらの料金体系だろうと考えていたのと一致する
https://cloud.google.com/storage/pricing?hl=ja#network-buckets
アイオワは同一regionなので無料、ワシントンはregion違いだが同一大陸なので$0.01/GBが課金されたと考えられる
ということで
- ipinfo.ioで位置を調べることでregionを特定することができる
- 同一regionだからといって桁違いに速いわけではなさそう
- 同一regionのインスタンスを使うことでgcsからのネットワーク料金を無料にできる
巨大なデータセットを使いたい場合(とはいってもcolabの場合は最大でもストレージが225GBだが)は同一のインスタンスに合わせて使うことでネットワーク料金を節約できる。
また、無料の範囲で活用する場合でもgoogle driveの容量を空けるのに役立ちそう。driveに5GB弱のデータセットを置いておくと他のものを置く余地が減って出力ファイルの整理を頻繁にやる必要が出てくるので意外と面倒。gcsのalways free分(5GB)を移すことでdriveの容量を空けられるのはなかなかありがたい。