diff --git a/README.md b/README.md index 1d558a2..0d6522b 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Pythonライブラリとしてのpipでのインストールや使用例は[libr Windowsを前提としています。 -1. [このzipファイル](https://github.com/litagin02/Style-Bert-VITS2/releases/download/2.5.0/sbv2.zip)を**パスに日本語や空白が含まれない場所に**ダウンロードして展開します。 +1. [このzipファイル](https://github.com/litagin02/Style-Bert-VITS2/releases/download/2.6.0/sbv2.zip)を**パスに日本語や空白が含まれない場所に**ダウンロードして展開します。 - グラボがある方は、`Install-Style-Bert-VITS2.bat`をダブルクリックします。 - グラボがない方は、`Install-Style-Bert-VITS2-CPU.bat`をダブルクリックします。CPU版では学習はできませんが、音声合成とマージは可能です。 2. 待つと自動で必要な環境がインストールされます。 @@ -92,7 +92,7 @@ python initialize.py # 必要なモデルとデフォルトTTSモデルをダ エディター部分は[別リポジトリ](https://github.com/litagin02/Style-Bert-VITS2-Editor)に分かれています。 -バージョン2.2以前での音声合成WebUIは、`App.bat`をダブルクリックか、`python app.py`するとWebUIが起動します。 +バージョン2.2以前での音声合成WebUIは、`App.bat`をダブルクリックか、`python app.py`するとWebUIが起動します。または`Inference.bat`でも音声合成単独タブが開きます。 音声合成に必要なモデルファイルたちの構造は以下の通りです(手動で配置する必要はありません)。 ``` @@ -123,18 +123,18 @@ model_assets #### データセット作り -- `App.bat`をダブルクリックか`python app.py`したところの「データセット作成」タブから、音声ファイルを適切な長さにスライスし、その後に文字の書き起こしを自動で行えます。 +- `App.bat`をダブルクリックか`python app.py`したところの「データセット作成」タブから、音声ファイルを適切な長さにスライスし、その後に文字の書き起こしを自動で行えます。または`Dataset.bat`をダブルクリックでもその単独タブが開きます。 - 指示に従った後、下の「学習」タブでそのまま学習を行うことができます。 #### 学習WebUI -- `App.bat`をダブルクリックか`python app.py`して開くWebUIの「学習」タブから指示に従ってください。 +- `App.bat`をダブルクリックか`python app.py`して開くWebUIの「学習」タブから指示に従ってください。または`Train.bat`をダブルクリックでもその単独タブが開きます。 ### スタイルの生成 - デフォルトでは、デフォルトスタイル「Neutral」の他、学習フォルダのフォルダ分けに応じたスタイルが生成されます。 - それ以外の方法で手動でスタイルを作成したい人向けです。 -- `App.bat`をダブルクリックか`python app.py`して開くWebUIの「スタイル作成」タブから、音声ファイルを使ってスタイルを生成できます。 +- `App.bat`をダブルクリックか`python app.py`して開くWebUIの「スタイル作成」タブから、音声ファイルを使ってスタイルを生成できます。または`StyleVectors.bat`をダブルクリックでもその単独タブが開きます。 - 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます(前処理は終わらせている必要があります)。 ### API Server @@ -151,8 +151,8 @@ API仕様は起動後に`/docs`にて確認ください。 ### マージ -2つのモデルを、「声質」「声の高さ」「感情表現」「テンポ」の4点で混ぜ合わせて、新しいモデルを作ることが出来ます。 -`App.bat`をダブルクリックか`python app.py`して開くWebUIの「マージ」タブから、2つのモデルを選択してマージすることができます。 +2つのモデルを、「声質」「声の高さ」「感情表現」「テンポ」の4点で混ぜ合わせて、新しいモデルを作ったり、また「あるモデルに、別の2つのモデルの差分を足す」等の操作ができます。 +`App.bat`をダブルクリックか`python app.py`して開くWebUIの「マージ」タブから、2つのモデルを選択してマージすることができます。または`Merge.bat`をダブルクリックでもその単独タブが開きます。 ### 自然性評価 diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4b1cd16..b928be2 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,9 +4,11 @@ ### 新機能 モデルのマージ時に、今までの `new = (1 - weight) * A + weight * B` の他に、次を追加 + - `new = A + weight * (B - C)`: 差分マージ - `new = a * A + b * B + c * C`: 加重和マージ - `new = A + weight * B`: ヌルモデルのマージ + 差分マージは、例えばBを「Cと同じ話者だけど囁いているモデル」とすると、`B - C`が囁きベクトル的なものだと思えるので、それをAに足すことで、Aの話者が囁いているような音声を生成できるようになります。 また、加重和で`new = A - B`を作って、それをヌルモデルマージで別のモデルに足せば、実質差分マージを実現できます。また謎に`new = -A`や`new = 41 * A`等のモデルも作ることができます。 @@ -15,8 +17,9 @@ 囁きについて実験的に作ったヌルモデルを[こちら](https://huggingface.co/litagin/sbv2_null_models)に置いています。これをヌルモデルマージで使うことで、任意のモデルを囁きモデルにある程度は変換できます。 -### 改善? +### 改善 +- スタイルベクトルのマージ部分のUIの改善 - WebUIの`App.bat`の起動が少し重いので、それぞれの機能を分割した`Dataset.bat`, `Inference.bat`, `Merge.bat`, `StyleVectors.bat`, `Train.bat`を追加 (今までの`App.bat`もこれまで通り使えます) ## v2.5.1 (2024-06-14) diff --git a/docs/FAQ.md b/docs/FAQ.md index 950affa..63e7ad9 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -5,6 +5,10 @@ Google Colabのノートブックは以前のバージョンのノートブックのコピーを使っていませんか? Colabノートブックは最新のバージョンに合ったノートブックで動かすことを前提としています。ノートブック記載のバージョンを確認して、[最新のcolabノートブック](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb)(を必要ならコピーして)から使うようにしてください。 +## `ModuleNotFoundError: No module named '_socket'`と出る + +フォルダ名をインストールした時から変えていませんか?フォルダ名を変えるとパスが変わってしまい、インストール時に指定したパスと異なるためにエラーが出ます。フォルダ名を元に戻してください。 + ## 学習に時間がかかりすぎる デフォルトの100エポックは音声データ量によっては過剰な場合があります。デフォルトでは1000ステップごとにモデルが保存されるはずなので、途中で学習を中断してみて途中のもので試してみてもいいでしょう。 diff --git a/gradio_tabs/inference.py b/gradio_tabs/inference.py index 53393be..ab8bb1e 100644 --- a/gradio_tabs/inference.py +++ b/gradio_tabs/inference.py @@ -1,6 +1,6 @@ import datetime import json -from typing import Optional +from typing import Optional, Any, Union import gradio as gr @@ -185,7 +185,10 @@ style_md = f""" - どのくらいに強さがいいかはモデルやスタイルによって異なるようです。 - 音声ファイルを入力する場合は、学習データと似た声音の話者(特に同じ性別)でないとよい効果が出ないかもしれません。 """ - +voice_keys = ["dec"] +voice_pitch_keys = ["flow"] +speech_style_keys = ["enc_p"] +tempo_keys = ["sdp", "dp"] def make_interactive(): return gr.update(interactive=True, value="音声合成") @@ -201,6 +204,26 @@ def gr_util(item): else: return (gr.update(visible=False), gr.update(visible=True)) +null_models_frame = 0 +def change_null_model_row(null_model_index:int, null_model_name:str, null_model_path:str,null_voice_weights:float, + null_voice_pitch_weights:float, null_speech_style_weights:float,null_tempo_weights:float, + null_models:dict[int,dict[str, Any]]): + #logger.debug("change_null_model_row:sta"+str(null_models)) + mid_result={} + mid_result["name"]=null_model_name + mid_result["path"]=null_model_path + mid_result["weight"]=null_tempo_weights + mid_result["pitch"]=null_voice_pitch_weights + mid_result["style"]=null_speech_style_weights + mid_result["tempo"]=null_tempo_weights + null_models[null_model_index] = mid_result + #logger.debug("decreasing:"+str(null_models_frame)+":"+str(len(null_models.keys()))) + if null_models_frame < len(null_models.keys()): + for i in range(null_models_frame ,len(null_models.keys())): + _ = null_models.pop(i, None) + result = null_models + #logger.debug("change_null_model_row:res"+str(null_models)) + return result, True def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: def tts_fn( @@ -225,6 +248,8 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: speaker, pitch_scale, intonation_scale, + null_models:dict[int, dict[str, Union[str, float]]], + force_reload_model:bool ): model_holder.get_model(model_name, model_path) assert model_holder.current_model is not None @@ -282,6 +307,8 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: speaker_id=speaker_id, pitch_scale=pitch_scale, intonation_scale=intonation_scale, + null_model_params = null_models, + force_reload_model = force_reload_model ) except InvalidToneError as e: logger.error(f"Tone error: {e}") @@ -303,7 +330,7 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: message = f"Success, time: {duration} seconds." if wrong_tone_message != "": message = wrong_tone_message + "\n" + message - return message, (sr, audio), kata_tone_json_str + return message, (sr, audio), kata_tone_json_str, False model_names = model_holder.model_names if len(model_names) == 0: @@ -323,6 +350,8 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown(initial_md) gr.Markdown(terms_of_use_md) + null_models = gr.State({}) + force_reload_model = gr.State(False) with gr.Accordion(label="使い方", open=False): gr.Markdown(how_to_md) with gr.Row(): @@ -436,6 +465,142 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: inputs=[use_assist_text], outputs=[assist_text, assist_text_weight], ) + with gr.Accordion(label="ヌルモデル", open=False): + with gr.Row() as null_row: + null_models_count = gr.Number(label="ヌルモデルの数", value=0, step=1) + with gr.Column(variant="panel") as null_column: + @gr.render( + inputs=[ + null_models_count, + ] + ) + def render_style( + null_models_count:int, + ): + global null_models_frame + null_models_frame = null_models_count + for i in range(0, null_models_count): + with gr.Row(): + null_model_index = gr.Number( + value=i, + key=f"null_model_index_{i}", + visible=False + ) + null_model_name = gr.Dropdown( + label="モデル一覧", + choices=model_names, + key=f"null_model_name_{i}", + value=model_names[initial_id], + interactive=True + ) + if i in null_models.value: + logger.debug(f"null model parameter exists in index {i}") + null_model_name.value=null_models.value[i]["name"] + null_model_path = gr.Dropdown( + label="モデルファイル", + choices=initial_pth_files, + key=f"null_model_path_{i}", + value=initial_pth_files[0], + interactive=True + ) + if i in null_models.value: + #null_model_path.choices = #ToDo + null_model_path.value=null_models.value[i]["path"] + null_voice_weights = gr.Slider( + minimum=0, + maximum=1, + value=1, + step=0.1, + key=f"null_voice_weights_{i}", + label="声質", + interactive=True + ) + if i in null_models.value: + null_voice_weights.value=null_models.value[i]["weight"] + null_voice_pitch_weights = gr.Slider( + minimum=0, + maximum=1, + value=1, + step=0.1, + key=f"null_voice_pitch_weights_{i}", + label="声の高さ", + interactive=True + ) + if i in null_models.value: + null_voice_pitch_weights.value=null_models.value[i]["pitch"] + null_speech_style_weights = gr.Slider( + minimum=0, + maximum=1, + value=1, + step=0.1, + key=f"null_speech_style_weights_{i}", + label="話し方", + interactive=True + ) + if i in null_models.value: + null_speech_style_weights.value=null_models.value[i]["style"] + null_tempo_weights = gr.Slider( + minimum=0, + maximum=1, + value=1, + step=0.1, + key=f"null_tempo_weights_{i}", + label="テンポ", + interactive=True + ) + if i in null_models.value: + null_tempo_weights.value=null_models.value[i]["tempo"] + null_model_name.change( + model_holder.update_model_files_for_gradio, + inputs=[null_model_name], + outputs=[null_model_path], + ) + #null_model_name.change(model_holder.refresh, outputs=[]) + null_model_path.change(make_non_interactive, outputs=[tts_button]) + #愚直すぎるのでもう少しなんとかしたい + null_model_path.change(change_null_model_row, + inputs=[null_model_index, null_model_name, null_model_path,null_voice_weights, + null_voice_pitch_weights, null_speech_style_weights,null_tempo_weights, + null_models], + outputs=[null_models,force_reload_model] + ) + null_voice_weights.change(change_null_model_row, + inputs=[null_model_index, null_model_name, null_model_path,null_voice_weights, + null_voice_pitch_weights, null_speech_style_weights,null_tempo_weights, + null_models], + outputs=[null_models,force_reload_model] + ) + null_voice_pitch_weights.change(change_null_model_row, + inputs=[null_model_index, null_model_name, null_model_path,null_voice_weights, + null_voice_pitch_weights, null_speech_style_weights,null_tempo_weights, + null_models], + outputs=[null_models,force_reload_model] + ) + null_speech_style_weights.change(change_null_model_row, + inputs=[null_model_index, null_model_name, null_model_path,null_voice_weights, + null_voice_pitch_weights, null_speech_style_weights,null_tempo_weights, + null_models], + outputs=[null_models,force_reload_model] + ) + null_tempo_weights.change(change_null_model_row, + inputs=[null_model_index, null_model_name, null_model_path,null_voice_weights, + null_voice_pitch_weights, null_speech_style_weights,null_tempo_weights, + null_models], + outputs=[null_models,force_reload_model] + ) + add_btn = gr.Button("ヌルモデルを増やす") + del_btn = gr.Button("ヌルモデルを減らす") + add_btn.click( + lambda x: x + 1, + inputs=[null_models_count], + outputs=[null_models_count], + ) + del_btn.click( + lambda x: x - 1 if x > 0 else 0, + inputs=[null_models_count], + outputs=[null_models_count], + ) + with gr.Column(): with gr.Accordion("スタイルについて詳細", open=False): gr.Markdown(style_md) @@ -493,8 +658,10 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: speaker, pitch_scale, intonation_scale, + null_models, + force_reload_model ], - outputs=[text_output, audio_output, tone], + outputs=[text_output, audio_output, tone, force_reload_model], ) model_name.change( @@ -524,7 +691,6 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: return app - if __name__ == "__main__": from config import get_path_config import torch diff --git a/requirements-colab.txt b/requirements-colab.txt index 160c9e5..532edb2 100644 --- a/requirements-colab.txt +++ b/requirements-colab.txt @@ -1,11 +1,12 @@ cmudict cn2an g2p_en -gradio +gradio>=4.32 jieba librosa==0.9.2 loguru num2words +numpy<2 onnxruntime pyannote.audio>=3.1.0 pyloudnorm diff --git a/requirements-infer.txt b/requirements-infer.txt index 6dc1dd4..dda14dc 100644 --- a/requirements-infer.txt +++ b/requirements-infer.txt @@ -8,6 +8,7 @@ jieba # librosa==0.9.2 loguru num2words +numpy<2 # protobuf==4.25 psutil # punctuators diff --git a/requirements.txt b/requirements.txt index 3ae7567..8704ac3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,12 @@ cn2an faster-whisper==0.10.1 g2p_en GPUtil -gradio +gradio>=4.32 jieba librosa==0.9.2 loguru num2words +numpy<2 protobuf==4.25 psutil punctuators diff --git a/style_bert_vits2/tts_model.py b/style_bert_vits2/tts_model.py index 6df8394..77db3dd 100644 --- a/style_bert_vits2/tts_model.py +++ b/style_bert_vits2/tts_model.py @@ -61,6 +61,7 @@ class TTSModel: self.model_path: Path = model_path self.device: str = device + self.null_model_params: dict[int, dict[str,Union[float, str]]] = {} # ハイパーパラメータの Pydantic モデルが直接指定された if isinstance(config_path, HyperParameters): @@ -113,6 +114,36 @@ class TTSModel: device=self.device, hps=self.hyper_parameters, ) + if(len(self.null_model_params.keys())==0): + return + + for index, null_model in enumerate(self.null_model_params.keys()): + null_model_add = get_net_g( + model_path=str(self.null_model_params[index]["path"]), + version=self.hyper_parameters.version, + device=self.device, + hps=self.hyper_parameters, + ) + #愚直。もっと上手い方法ありそう + print(str(self.null_model_params[index]["weight"])) + params = zip(self.__net_g.dec.parameters(), null_model_add.dec.parameters()) + for v in params: + v[0].data.add_(v[1].data,alpha=float(self.null_model_params[index]["weight"])) + params = zip(self.__net_g.flow.parameters(), null_model_add.flow.parameters()) + for v in params: + v[0].data.add_(v[1].data,alpha=float(self.null_model_params[index]["pitch"])) + + params = zip(self.__net_g.enc_p.parameters(), null_model_add.enc_p.parameters()) + for v in params: + v[0].data.add_(v[1].data,alpha=float(self.null_model_params[index]["style"])) + #テンポはsdpとdp二つあるからとりあえずどっちも足す + params = zip(self.__net_g.sdp.parameters(), null_model_add.sdp.parameters()) + for v in params: + v[0].data.add_(v[1].data,alpha=float(self.null_model_params[index]["tempo"])) + params = zip(self.__net_g.dp.parameters(), null_model_add.dp.parameters()) + for v in params: + v[0].data.add_(v[1].data,alpha=float(self.null_model_params[index]["tempo"])) + def __get_style_vector(self, style_id: int, weight: float = 1.0) -> NDArray[Any]: """ @@ -227,6 +258,8 @@ class TTSModel: given_tone: Optional[list[int]] = None, pitch_scale: float = 1.0, intonation_scale: float = 1.0, + null_model_params: dict[int,dict[str,Union[str, float]]] = {}, + force_reload_model:bool = False ) -> tuple[int, NDArray[Any]]: """ テキストから音声を合成する。 @@ -251,7 +284,7 @@ class TTSModel: given_tone (Optional[list[int]], optional): アクセントのトーンのリスト. Defaults to None. pitch_scale (float, optional): ピッチの高さ (1.0 から変更すると若干音質が低下する). Defaults to 1.0. intonation_scale (float, optional): 抑揚の平均からの変化幅 (1.0 から変更すると若干音質が低下する). Defaults to 1.0. - + null_model_params(dict[int,dict[str,Union[str,float]],optional):推論時に使用するヌルモデルの名前、適用割合のdictが入ったdict。 Returns: tuple[int, NDArray[Any]]: サンプリングレートと音声データ (16bit PCM) """ @@ -265,7 +298,12 @@ class TTSModel: reference_audio_path = None if assist_text == "" or not use_assist_text: assist_text = None - + if null_model_params is not {}: + self.null_model_params = null_model_params + else: + self.null_model_params = {} + if force_reload_model is True: + self.__net_g = None if self.__net_g is None: self.load() assert self.__net_g is not None