Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

音声合成時に読み上げテキストの読みを表す音素列を指定する機能を追加 + 様々な改善 #118

Merged
merged 10 commits into from
May 14, 2024

Conversation

tsukumijima
Copy link

@tsukumijima tsukumijima commented Apr 19, 2024

概要

TTSModel.infer() にオプションの given_phone 引数を追加し、ライブラリユーザーが音声合成時の読みを手動で設定できるようにします。

ライブラリユーザーが given_phone 引数を用意すれば、音声合成時の読み上げテキストを変更することなく、読み方が複数ある熟語の読み方を変更できます (「明日」を「アシタ」ではなく「アス」と読ませるなど) 。
現在も辞書登録を行えば読み方が複数ある熟語の読みを変更できますが、一回限りの場合は辞書登録が面倒ですし、他の単語と被るために辞書登録できないケースもあります。そうした際に有用です。

具体的には、ライブラリとしての style_bert_vits2 を活用したエディターで、難読地名や固有名詞など読みを正しく取得できない場合に、ユーザー側で当該単語を正しい読みに変更した上で再度音声合成するために必要です。

また、上記変更に関連して、g2p() 実行時かつ raise_yomi_error=False が指定されている場合に「読めない文字が抜けたように扱う」のではなく、「読めない文字は全て『ん』として扱う」ように変更しました。
上記のエディターで単語の読みを編集する際、読めない文字が(最初から存在しなかったかのように)欠損していると、アクセント指定を行う際に支障するためです。

よくなること

  • ライブラリとしての style_bert_vits2 利用時の調声の幅が広がり、より実用的になる

わるくなること

  • g2p.py のコード量が若干増える
    • 新規追加された adjust_word2ph() 関数に、「与えられた given_phone」と「読み上げテキストから自動生成された generated_phone」を使って差分を取り、「word2ph」内の値の合計値を「given_phone」の長さ (=音素数) に一致させるための複雑怪奇なアルゴリズムが実装されている
      • GPT-4 を最大限活用しながら数日丸々頭を捻り出してどうにか編み出したロジック (自分でもなぜ上手くいっているのかが分からん…)
      • 「word2ph」のうち、「generated_phone」と「given_phone」の間で読み方が変更された部分だけを変更し、両者で共通する部分の値は維持することで、音声合成時の発声やアクセントへの影響をなるべく避けつつ、一部単語の読み方だけを変更できる
      • 私も苦し紛れに実装したものなので現在のロジックがベストとは限らないし、適切な「word2ph」を生成するより良い方法があれば全然変えてしまって構わない
  • raise_yomi_error=Falseg2p() を呼んだ時、pyopenjtalk で読みを取得できない文字の扱い方が若干変わること
    • 現状この挙動 (読めない文字が出たら消えた扱いになる) に依存している内部コードや外部ソフトウェアはないとは思うが、もし不備があれば教えてほしい
    • 上記挙動に依存していなければ、このプルリクを取り込むことによる一般ユーザーへの副作用は起こらないはず
    • raise_yomi_error=Trueg2p() を呼んだ時は影響しない
    • 学習時は Web UI の「書き起こしが読めないファイルの扱い」で 読めないファイルも無理やり読んで学習に使う を選択している場合のみ影響する
      • ただしこのオプションを選択した場合はどのみち変な対応関係で学習されそうなので、あまり差はないのでは

…ng of the text to be read during speech synthesis

With this feature, it is possible to specify individual readings for homonyms such as "明日 (ashita/asu)" while maintaining the Kanji representation of the read text.
@litagin02
Copy link
Owner

discordの議論で、「ん」ではなく「'」等を割り当てるのがよいかもという意見がありましたが、それについてはどうお考えですか?

@tsukumijima
Copy link
Author

discordの議論で、「ん」ではなく「'」等を割り当てるのがよいかもという意見がありましたが、それについてはどうお考えですか?

ここはちょっと迷ってて、学習時に『読めないファイルを無理やり読み込んで使う』を考慮しないのであれば「ん」でも問題ない気はしてます。最終的にはそちらの判断にお任せします。
ただ「'」より「ん」のほうが音声合成時に一応音が入るので、不自然感は減らせるかもしれません (ここもどこまで考慮するかによる) 。

@tsukumijima
Copy link
Author

@litagin02 ついでになりますが、28f254f にてライブラリ利用時に Gradio に依存しないようにしました。

というのも、Gradio は実は import 文の実行に2秒以上かかるなど、かなり重いライブラリです (実測値) 。
ライブラリとして利用する際は Gradio を使わない用途も多いはずですが、使わないにも関わらず Gradio が依存関係に入っていると依存関係が複雑になるだけでなく、コード全体の起動が遅くなる問題も出てきます。

ライブラリとしての style_bert_vits2 内で Gradio に依存している箇所は、tts_model.py 内の TTSModelHolder が持つメソッドのうち、_for_gradio() 系の内部の Gradio WebUI から呼んでいるメソッドだけでした。
これらのメソッドの中で import gradio as gr を呼ぶことで、tts_model.py のモジュール全体を import する際に Gradio に依存しないようにしています。

Note

一度同一プロセス内で Gradio が import されていればその後の Gradio の import 文実行時に再利用されるので、何度も import 文を通る分だけ再 import が発生して重くなる…といったことはないはずです。

…t_vits2 as a library

librosa: Originally not used under style_bert_vits2/.
pyannote.audio: Required only when using the "infer style vector from audio" function during inference, but it is currently almost unused.
scipy: Because it was used in only one utility function that was not needed outside of training.
@tsukumijima
Copy link
Author

@litagin02 さらについでになりますが、ライブラリとしての style_bert_vits2 の依存関係のスリム化を行いました。
具体的には、下記のライブラリを依存関係から削除しています。

  • librosa: 元々 style_bert_vits2 以下では使われておらず不要
  • pyannote.audio: 「音声からスタイルベクトルを推論する」機能を使う (reference_audio_path を指定する) 際にのみ使われるが、pyannote.audio 自体が scikit-learn を始め大量のライブラリに依存しており重い
    • 当該機能自体が現在ではほとんど使われていないため、大半のライブラリユーザーにとって不要な pyannote.audio を依存関係から外せるのは大きい
    • もし当該機能が必要なユーザーが万が一いた場合も、別途 pyannote.audio をインストールしておけば動作する
  • scipy: style_bert_vits2 以下では、ユーティリティのうち学習時にのみ使われる関数でのみ使われていた
    • 学習時しか使わないにも関わらずモジュールのトップレベルでインポートすると依存関係として必要になってしまうため、当該関数内部で scipy をインポートするようにした

ライブラリ利用者としては、ほとんど使われていない余分な依存関係が削除できるので、site-packages 以下がより軽くなるほか、PyInstaller で exe 化する際の問題が減るなどメリットが大きいです。

@litagin02 litagin02 changed the base branch from master to dev April 27, 2024 03:24
tsukumijima and others added 5 commits May 1, 2024 10:56
…configs/default_paths.yml when running initialize.py

If configs/paths.yml itself is included in version control, differences will occur when it is changed in each environment, which is troublesome.
@tsukumijima
Copy link
Author

@litagin02

discordの議論で、「ん」ではなく「'」等を割り当てるのがよいかもという意見がありましたが、それについてはどうお考えですか?

この件ですが、結局学習時に『読めないファイルを無理やり読み込んで使う』際に「ん」の音素に誤った音が割り当てられる可能性を鑑み、代わりに「'」を割り当てるように変更しました。
事前学習モデルやファインチューニングで「'」を他用途 (非言語音) への割り当てに使っている場合もなくもないようですが、少なくとも JP-Extra モデルでは「'」には何の音も割り当てられていないようですので、十分許容範囲かなと。

Note

本来『読めないファイルを無理やり読み込んで使う』は学習後の品質を鑑みると望ましくないですし、わざわざ書き起こし文中で「'」に非言語音を割り当てている方は書き起こし文自体の品質が高いことが予想されるため、ほとんどのケースで問題は起こらないはずです。


さらについでになりますが、いくつか細かな改善を行いました。

  1. TTSModel() の初期化時にハイパーパラメータ HyperParameters とスタイルベクトル NDArray を直接指定できるように変更
    • ファイルパスではなく値を直接指定できれば、ハイパーパラメータやスタイルベクトルを RDB などファイル以外の形で保存できるようになり、ライブラリ利用時の利便性が高まる
      • 現状 config.json*.safetensorsstyle_vectors.npy の3ファイル全てをストレージ上に保管する必要があり、微妙に煩雑
    • モデルファイルはサイズ的にファイル以外の形での保管が難しいため、引き続きファイルパスのみの対応とした
  2. configs/paths.ymlconfigs/default_paths.yml に移動し、configs/paths.yml をバージョン管理対象から外す
    • 現状 configs.paths.yml はバージョン管理に含まれているため、各環境に合わせて変更しただけで Git の追跡対象となり、差分が生じて非常に面倒
      • 環境依存の設定ファイルなのだから、config.yml 同様にデフォルト設定ファイルだけをバージョン管理に含め、後でコピーする形が理想的
    • initialize.py 実行時、まだ configs/paths.yml が存在しない場合のみ configs/default_paths.yml の内容をコピーするように変更した
      • 特に推論のみの利用など大半のユーザーは paths.yml を変更していないと考えられるため、バージョンアップ時に再度 initialize.py を実行して paths.yml を生成させる必要が出てくる点には注意が必要

Improve performance by pre-compiling regular expressions that are executed many times.
@tsukumijima tsukumijima changed the title 音声合成時に読み上げテキストの読みを表す音素列を指定する機能を追加 音声合成時に読み上げテキストの読みを表す音素列を指定する機能を追加 + 様々な改善 May 12, 2024
@tsukumijima
Copy link
Author

@litagin02
#124 のプルリクエストに触発され、日本語自然言語処理部分のパフォーマンス改善を行いました。
具体的には、関数内で何度も実行される計算コストが比較的高い定数や正規表現をグローバルスコープ (関数定義の外) で事前定義 (正規表現は事前コンパイル) することで、その後何度も呼び出される関数の実行パフォーマンスを向上させています。

style_bert_vits2/ 以下の NLP 系関数は比較的呼び出し回数が多いため、(パフォーマンス比較は行えていませんが) 一定の効果があるものと思います。
推論にはどうしても時間が掛かるので、それ以外の部分で簡単に処理時間を削減できるのならやっておいて損はないかなと。

@litagin02 litagin02 changed the base branch from dev to master May 14, 2024 22:34
@litagin02 litagin02 merged commit 837c439 into litagin02:master May 14, 2024
@tsukumijima tsukumijima deleted the dev branch May 15, 2024 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants