From 0f30b148f4cbf88158d344de1f3d6b118fe7a03c Mon Sep 17 00:00:00 2001 From: litagin02 Date: Sun, 2 Jun 2024 15:22:40 +0900 Subject: [PATCH 1/7] Feat: add difference (experimental) --- gradio_tabs/merge.py | 272 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 243 insertions(+), 29 deletions(-) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 750f310..7ee3928 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -28,7 +28,7 @@ def merge_style( model_name_b: str, weight: float, output_name: str, - style_triple_list: list[tuple[str, str, str]], + style_triple_list: list[tuple[str, ...]], ) -> tuple[Path, list[str]]: """ style_triple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)] @@ -94,6 +94,85 @@ def merge_style( return output_style_path, list(new_style2id.keys()) +def merge_style_add_diff( + model_name_a: str, + model_name_b: str, + model_name_c: str, + weight: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +) -> tuple[Path, list[str]]: + """ + new = A + weight * (B - C) + """ + if any(triple[3] == DEFAULT_STYLE for triple in style_tuple_list): + # 存在する場合、リストをソート + sorted_list = sorted(style_tuple_list, key=lambda x: x[3] != DEFAULT_STYLE) + else: + # 存在しない場合、エラーを発生 + raise ValueError(f"No element with {DEFAULT_STYLE} output style name found.") + + style_vectors_a = np.load( + assets_root / model_name_a / "style_vectors.npy" + ) # (style_num_a, 256) + style_vectors_b = np.load( + assets_root / model_name_b / "style_vectors.npy" + ) # (style_num_b, 256) + style_vectors_c = np.load( + assets_root / model_name_c / "style_vectors.npy" + ) # (style_num_c, 256) + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: + config_a = json.load(f) + with open(assets_root / model_name_b / "config.json", encoding="utf-8") as f: + config_b = json.load(f) + with open(assets_root / model_name_c / "config.json", encoding="utf-8") as f: + config_c = json.load(f) + style2id_a = config_a["data"]["style2id"] + style2id_b = config_b["data"]["style2id"] + style2id_c = config_c["data"]["style2id"] + new_style_vecs = [] + new_style2id = {} + for style_a, style_b, style_c, style_out in sorted_list: + if style_a not in style2id_a: + logger.error(f"{style_a} is not in {model_name_a}.") + raise ValueError(f"{style_a} は {model_name_a} にありません。") + if style_b not in style2id_b: + logger.error(f"{style_b} is not in {model_name_b}.") + raise ValueError(f"{style_b} は {model_name_b} にありません。") + if style_c not in style2id_c: + logger.error(f"{style_c} is not in {model_name_c}.") + raise ValueError(f"{style_c} は {model_name_c} にありません。") + new_style = style_vectors_a[style2id_a[style_a]] + weight * ( + style_vectors_b[style2id_b[style_b]] - style_vectors_c[style2id_c[style_c]] + ) + new_style_vecs.append(new_style) + new_style2id[style_out] = len(new_style_vecs) - 1 + new_style_vecs = np.array(new_style_vecs) + + output_style_path = assets_root / output_name / "style_vectors.npy" + np.save(output_style_path, new_style_vecs) + + new_config = config_a.copy() + new_config["data"]["num_styles"] = len(new_style2id) + new_config["data"]["style2id"] = new_style2id + new_config["model_name"] = output_name + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: + json.dump(new_config, f, indent=2, ensure_ascii=False) + + # recipe.jsonを読み込んで、style_tuple_listを追記 + info_path = assets_root / output_name / "recipe.json" + if info_path.exists(): + with open(info_path, encoding="utf-8") as f: + info = json.load(f) + else: + info = {} + info["style_tuple_list"] = style_tuple_list + with open(info_path, "w", encoding="utf-8") as f: + json.dump(info, f, indent=2, ensure_ascii=False) + + return output_style_path, list(new_style2id.keys()) + + def lerp_tensors(t: float, v0: torch.Tensor, v1: torch.Tensor): return v0 * (1 - t) + v1 * t @@ -175,11 +254,77 @@ def merge_models( return merged_model_path +def merge_models_add_diff( + model_path_a: str, + model_path_b: str, + model_path_c: str, + voice_weight: float, + voice_pitch_weight: float, + speech_style_weight: float, + tempo_weight: float, + output_name: str, +): + """ + new = A + weight * (B - C) + """ + model_a_weight: dict[str, torch.Tensor] = {} + with safe_open(model_path_a, framework="pt", device="cpu") as f: + for k in f.keys(): + model_a_weight[k] = f.get_tensor(k) + + model_b_weight: dict[str, torch.Tensor] = {} + with safe_open(model_path_b, framework="pt", device="cpu") as f: + for k in f.keys(): + model_b_weight[k] = f.get_tensor(k) + + model_c_weight: dict[str, torch.Tensor] = {} + with safe_open(model_path_c, framework="pt", device="cpu") as f: + for k in f.keys(): + model_c_weight[k] = f.get_tensor(k) + + merged_model_weight = model_a_weight.copy() + + for key in model_a_weight: + if any([key.startswith(prefix) for prefix in voice_keys]): + weight = voice_weight + elif any([key.startswith(prefix) for prefix in voice_pitch_keys]): + weight = voice_pitch_weight + elif any([key.startswith(prefix) for prefix in speech_style_keys]): + weight = speech_style_weight + elif any([key.startswith(prefix) for prefix in tempo_keys]): + weight = tempo_weight + else: + continue + merged_model_weight[key] = model_a_weight[key] + weight * ( + model_b_weight[key] - model_c_weight[key] + ) + + merged_model_path = assets_root / output_name / f"{output_name}.safetensors" + merged_model_path.parent.mkdir(parents=True, exist_ok=True) + save_file(merged_model_weight, merged_model_path) + + info = { + "model_a": model_path_a, + "model_b": model_path_b, + "model_c": model_path_c, + "voice_weight": voice_weight, + "voice_pitch_weight": voice_pitch_weight, + "speech_style_weight": speech_style_weight, + "tempo_weight": tempo_weight, + } + with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: + json.dump(info, f, indent=2, ensure_ascii=False) + return merged_model_path + + def merge_models_gr( model_name_a: str, model_path_a: str, model_name_b: str, model_path_b: str, + model_name_c: str, + model_path_c: str, + use_add_diff: bool, output_name: str, voice_weight: float, voice_pitch_weight: float, @@ -189,48 +334,86 @@ def merge_models_gr( ): if output_name == "": return "Error: 新しいモデル名を入力してください。" - merged_model_path = merge_models( - model_path_a, - model_path_b, - voice_weight, - voice_pitch_weight, - speech_style_weight, - tempo_weight, - output_name, - use_slerp_instead_of_lerp, - ) + if not use_add_diff: + merged_model_path = merge_models( + model_path_a, + model_path_b, + voice_weight, + voice_pitch_weight, + speech_style_weight, + tempo_weight, + output_name, + use_slerp_instead_of_lerp, + ) + else: + merged_model_path = merge_models_add_diff( + model_path_a, + model_path_b, + model_path_c, + voice_weight, + voice_pitch_weight, + speech_style_weight, + tempo_weight, + output_name, + ) return f"Success: モデルを{merged_model_path}に保存しました。" def merge_style_gr( model_name_a: str, model_name_b: str, + model_name_c: str, + use_add_diff: bool, weight: float, output_name: str, - style_triple_list_str: str, + style_tuple_list_str: str, ): if output_name == "": return "Error: 新しいモデル名を入力してください。", None - style_triple_list = [] - for line in style_triple_list_str.split("\n"): + style_tuple_list: list[tuple[str, ...]] = [] + for line in style_tuple_list_str.split("\n"): if not line: continue style_triple = line.split(",") - if len(style_triple) != 3: - logger.error(f"Invalid style triple: {line}") - return ( - f"Error: スタイルを3つのカンマ区切りで入力してください:\n{line}", - None, - ) - style_a, style_b, style_out = style_triple - style_a = style_a.strip() - style_b = style_b.strip() - style_out = style_out.strip() - style_triple_list.append((style_a, style_b, style_out)) + if not use_add_diff: + if len(style_triple) != 3: + logger.error(f"Invalid style triple: {line}") + return ( + f"Error: スタイルを3つのカンマ区切りで入力してください:\n{line}", + None, + ) + style_a, style_b, style_out = style_triple + style_a = style_a.strip() + style_b = style_b.strip() + style_out = style_out.strip() + style_tuple_list.append((style_a, style_b, style_out)) + else: + if len(style_triple) != 4: + logger.error(f"Invalid style triple: {line}") + return ( + f"Error: スタイルを4つのカンマ区切りで入力してください:\n{line}", + None, + ) + style_a, style_b, style_c, style_out = style_triple + style_a = style_a.strip() + style_b = style_b.strip() + style_c = style_c.strip() + style_out = style_out.strip() + style_tuple_list.append((style_a, style_b, style_c, style_out)) try: - new_style_path, new_styles = merge_style( - model_name_a, model_name_b, weight, output_name, style_triple_list - ) + if not use_add_diff: + new_style_path, new_styles = merge_style( + model_name_a, model_name_b, weight, output_name, style_tuple_list + ) + else: + new_style_path, new_styles = merge_style_add_diff( + model_name_a, + model_name_b, + model_name_c, + weight, + output_name, + style_tuple_list, + ) except ValueError as e: return f"Error: {e}" return f"Success: スタイルを{new_style_path}に保存しました。", gr.Dropdown( @@ -342,6 +525,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) with gr.Accordion(label="使い方", open=False): gr.Markdown(initial_md) + use_add_diff = gr.Checkbox(label="差分マージ", value=False) with gr.Row(): with gr.Column(scale=3): model_name_a = gr.Dropdown( @@ -365,7 +549,23 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: choices=initial_model_files, value=initial_model_files[0], ) + with gr.Column(scale=3): + model_name_c = gr.Dropdown( + label="モデルC", + choices=model_names, + value=model_names[initial_id], + visible=False, + ) + model_path_c = gr.Dropdown( + label="モデルファイル", + choices=initial_model_files, + value=initial_model_files[0], + visible=False, + ) refresh_button = gr.Button("更新", scale=1, visible=True) + gr.Markdown( + "通常マージの場合、`new = (1 - weight) * A + weight * B`、差分マージの場合、`new = A + weight * (B - C)`" + ) with gr.Column(variant="panel"): new_name = gr.Textbox(label="新しいモデル名", placeholder="new_model") with gr.Row(): @@ -438,7 +638,11 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) tts_button = gr.Button("音声合成", variant="primary") audio_output = gr.Audio(label="結果") - + use_add_diff.change( + lambda x: (gr.Dropdown(visible=x), gr.Dropdown(visible=x)), + inputs=[use_add_diff], + outputs=[model_name_c, model_path_c], + ) model_name_a.change( model_holder.update_model_files_for_gradio, inputs=[model_name_a], @@ -449,6 +653,11 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: inputs=[model_name_b], outputs=[model_path_b], ) + model_name_c.change( + model_holder.update_model_files_for_gradio, + inputs=[model_name_c], + outputs=[model_path_c], + ) refresh_button.click( lambda: update_two_model_names_dropdown(model_holder), @@ -468,6 +677,9 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: model_path_a, model_name_b, model_path_b, + model_name_c, + model_path_c, + use_add_diff, new_name, voice_slider, voice_pitch_slider, @@ -483,6 +695,8 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: inputs=[ model_name_a, model_name_b, + model_name_c, + use_add_diff, speech_style_slider, new_name, style_triple_list, From 64ddece1639332a70f8157d3580a46719c6df78e Mon Sep 17 00:00:00 2001 From: litagin02 Date: Wed, 5 Jun 2024 07:14:18 +0900 Subject: [PATCH 2/7] Fix --- gradio_tabs/merge.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 7ee3928..3c2bd58 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -432,9 +432,9 @@ def simple_tts( return model.infer(text, style=style, style_weight=style_weight) -def update_two_model_names_dropdown(model_holder: TTSModelHolder): +def update_three_model_names_dropdown(model_holder: TTSModelHolder): new_names, new_files, _ = model_holder.update_model_names_for_gradio() - return new_names, new_files, new_names, new_files + return new_names, new_files, new_names, new_files, new_names, new_files def load_styles_gr(model_name_a: str, model_name_b: str): @@ -660,8 +660,15 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) refresh_button.click( - lambda: update_two_model_names_dropdown(model_holder), - outputs=[model_name_a, model_path_a, model_name_b, model_path_b], + lambda: update_three_model_names_dropdown(model_holder), + outputs=[ + model_name_a, + model_path_a, + model_name_b, + model_path_b, + model_name_c, + model_path_c, + ], ) load_style_button.click( From d7b93dbd1890c0c6147956c843564bc14b6f6970 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Fri, 14 Jun 2024 18:05:37 +0900 Subject: [PATCH 3/7] Feat: new merge method: triple weighted sum, add zero merge --- gradio_tabs/merge.py | 473 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 409 insertions(+), 64 deletions(-) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 3c2bd58..979d6a3 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -1,5 +1,6 @@ import json from pathlib import Path +from typing import Union import gradio as gr import numpy as np @@ -23,6 +24,14 @@ path_config = get_path_config() assets_root = path_config.assets_root +def load_safetensors(model_path: Union[str, Path]) -> dict[str, torch.Tensor]: + result: dict[str, torch.Tensor] = {} + with safe_open(model_path, framework="pt", device="cpu") as f: + for k in f.keys(): + result[k] = f.get_tensor(k) + return result + + def merge_style( model_name_a: str, model_name_b: str, @@ -208,17 +217,11 @@ def merge_models( output_name: str, use_slerp_instead_of_lerp: bool, ): - """model Aを起点に、model Bの各要素を重み付けしてマージする。 - safetensors形式を前提とする。""" - model_a_weight: dict[str, torch.Tensor] = {} - with safe_open(model_path_a, framework="pt", device="cpu") as f: - for k in f.keys(): - model_a_weight[k] = f.get_tensor(k) - - model_b_weight: dict[str, torch.Tensor] = {} - with safe_open(model_path_b, framework="pt", device="cpu") as f: - for k in f.keys(): - model_b_weight[k] = f.get_tensor(k) + """ + new = (1 - weight) * A + weight * B + """ + model_a_weight = load_safetensors(model_path_a) + model_b_weight = load_safetensors(model_path_b) merged_model_weight = model_a_weight.copy() @@ -242,15 +245,43 @@ def merge_models( save_file(merged_model_weight, merged_model_path) info = { + "method": "usual", "model_a": model_path_a, "model_b": model_path_b, "voice_weight": voice_weight, "voice_pitch_weight": voice_pitch_weight, "speech_style_weight": speech_style_weight, "tempo_weight": tempo_weight, + "use_slerp_instead_of_lerp": use_slerp_instead_of_lerp, } with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: json.dump(info, f, indent=2, ensure_ascii=False) + + # Default style merge only using Neutral style + model_name_a = Path(model_path_a).parent.name + model_name_b = Path(model_path_b).parent.name + style_vectors_a = np.load( + assets_root / model_name_a / "style_vectors.npy" + ) # (style_num_a, 256) + style_vectors_b = np.load( + assets_root / model_name_b / "style_vectors.npy" + ) # (style_num_b, 256) + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: + new_config = json.load(f) + + new_config["model_name"] = output_name + new_config["data"]["num_styles"] = 1 + new_config["data"]["style2id"] = {DEFAULT_STYLE: 0} + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: + json.dump(new_config, f, indent=2, ensure_ascii=False) + + neutral_vector_a = style_vectors_a[0] + neutral_vector_b = style_vectors_b[0] + weight = speech_style_weight + new_neutral_vector = (1 - weight) * neutral_vector_a + weight * neutral_vector_b + new_style_vectors = np.array([new_neutral_vector]) + new_style_path = assets_root / output_name / "style_vectors.npy" + np.save(new_style_path, new_style_vectors) return merged_model_path @@ -267,20 +298,9 @@ def merge_models_add_diff( """ new = A + weight * (B - C) """ - model_a_weight: dict[str, torch.Tensor] = {} - with safe_open(model_path_a, framework="pt", device="cpu") as f: - for k in f.keys(): - model_a_weight[k] = f.get_tensor(k) - - model_b_weight: dict[str, torch.Tensor] = {} - with safe_open(model_path_b, framework="pt", device="cpu") as f: - for k in f.keys(): - model_b_weight[k] = f.get_tensor(k) - - model_c_weight: dict[str, torch.Tensor] = {} - with safe_open(model_path_c, framework="pt", device="cpu") as f: - for k in f.keys(): - model_c_weight[k] = f.get_tensor(k) + model_a_weight = load_safetensors(model_path_a) + model_b_weight = load_safetensors(model_path_b) + model_c_weight = load_safetensors(model_path_c) merged_model_weight = model_a_weight.copy() @@ -304,6 +324,7 @@ def merge_models_add_diff( save_file(merged_model_weight, merged_model_path) info = { + "method": "add_diff", "model_a": model_path_a, "model_b": model_path_b, "model_c": model_path_c, @@ -314,17 +335,199 @@ def merge_models_add_diff( } with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: json.dump(info, f, indent=2, ensure_ascii=False) + + # Default style merge only using Neutral style + model_name_a = Path(model_path_a).parent.name + model_name_b = Path(model_path_b).parent.name + model_name_c = Path(model_path_c).parent.name + + style_vectors_a = np.load( + assets_root / model_name_a / "style_vectors.npy" + ) # (style_num_a, 256) + style_vectors_b = np.load( + assets_root / model_name_b / "style_vectors.npy" + ) # (style_num_b, 256) + style_vectors_c = np.load( + assets_root / model_name_c / "style_vectors.npy" + ) # (style_num_c, 256) + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: + new_config = json.load(f) + + new_config["model_name"] = output_name + new_config["data"]["num_styles"] = 1 + new_config["data"]["style2id"] = {DEFAULT_STYLE: 0} + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: + json.dump(new_config, f, indent=2, ensure_ascii=False) + + neutral_vector_a = style_vectors_a[0] + neutral_vector_b = style_vectors_b[0] + neutral_vector_c = style_vectors_c[0] + weight = speech_style_weight + new_neutral_vector = neutral_vector_a + weight * ( + neutral_vector_b - neutral_vector_c + ) + new_style_vectors = np.array([new_neutral_vector]) + new_style_path = assets_root / output_name / "style_vectors.npy" + np.save(new_style_path, new_style_vectors) + return merged_model_path + + +def merge_models_weighted_sum( + model_path_a: str, + model_path_b: str, + model_path_c: str, + model_a_coeff: float, + model_b_coeff: float, + model_c_coeff: float, + output_name: str, +): + model_a_weight = load_safetensors(model_path_a) + model_b_weight = load_safetensors(model_path_b) + model_c_weight = load_safetensors(model_path_c) + + merged_model_weight = model_a_weight.copy() + + for key in model_a_weight: + merged_model_weight[key] = ( + model_a_coeff * model_a_weight[key] + + model_b_coeff * model_b_weight[key] + + model_c_coeff * model_c_weight[key] + ) + + merged_model_path = assets_root / output_name / f"{output_name}.safetensors" + merged_model_path.parent.mkdir(parents=True, exist_ok=True) + save_file(merged_model_weight, merged_model_path) + + info = { + "method": "weighted_sum", + "model_a": model_path_a, + "model_b": model_path_b, + "model_c": model_path_c, + "model_a_coeff": model_a_coeff, + "model_b_coeff": model_b_coeff, + "model_c_coeff": model_c_coeff, + } + with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: + json.dump(info, f, indent=2, ensure_ascii=False) + + # Default style merge only using Neutral style + model_name_a = Path(model_path_a).parent.name + model_name_b = Path(model_path_b).parent.name + model_name_c = Path(model_path_c).parent.name + + style_vectors_a = np.load( + assets_root / model_name_a / "style_vectors.npy" + ) # (style_num_a, 256) + style_vectors_b = np.load( + assets_root / model_name_b / "style_vectors.npy" + ) # (style_num_b, 256) + style_vectors_c = np.load( + assets_root / model_name_c / "style_vectors.npy" + ) # (style_num_c, 256) + + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: + new_config = json.load(f) + + new_config["model_name"] = output_name + new_config["data"]["num_styles"] = 1 + new_config["data"]["style2id"] = {DEFAULT_STYLE: 0} + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: + json.dump(new_config, f, indent=2, ensure_ascii=False) + + neutral_vector_a = style_vectors_a[0] + neutral_vector_b = style_vectors_b[0] + neutral_vector_c = style_vectors_c[0] + new_neutral_vector = ( + model_a_coeff * neutral_vector_a + + model_b_coeff * neutral_vector_b + + model_c_coeff * neutral_vector_c + ) + new_style_vectors = np.array([new_neutral_vector]) + new_style_path = assets_root / output_name / "style_vectors.npy" + np.save(new_style_path, new_style_vectors) + return merged_model_path + + +def merge_models_add_zero( + model_path_a: str, + model_path_b: str, + voice_weight: float, + voice_pitch_weight: float, + speech_style_weight: float, + tempo_weight: float, + output_name: str, +): + model_a_weight = load_safetensors(model_path_a) + model_b_weight = load_safetensors(model_path_b) + + merged_model_weight = model_a_weight.copy() + + for key in model_a_weight: + if any([key.startswith(prefix) for prefix in voice_keys]): + weight = voice_weight + elif any([key.startswith(prefix) for prefix in voice_pitch_keys]): + weight = voice_pitch_weight + elif any([key.startswith(prefix) for prefix in speech_style_keys]): + weight = speech_style_weight + elif any([key.startswith(prefix) for prefix in tempo_keys]): + weight = tempo_weight + else: + continue + merged_model_weight[key] = model_a_weight[key] + weight * model_b_weight[key] + + merged_model_path = assets_root / output_name / f"{output_name}.safetensors" + merged_model_path.parent.mkdir(parents=True, exist_ok=True) + save_file(merged_model_weight, merged_model_path) + + info = { + "method": "add_zero", + "model_a": model_path_a, + "model_b": model_path_b, + "voice_weight": voice_weight, + "voice_pitch_weight": voice_pitch_weight, + "speech_style_weight": speech_style_weight, + "tempo_weight": tempo_weight, + } + with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: + json.dump(info, f, indent=2, ensure_ascii=False) + + # Default style merge only using Neutral style + model_name_a = Path(model_path_a).parent.name + model_name_b = Path(model_path_b).parent.name + + style_vectors_a = np.load( + assets_root / model_name_a / "style_vectors.npy" + ) # (style_num_a, 256) + style_vectors_b = np.load( + assets_root / model_name_b / "style_vectors.npy" + ) # (style_num_b, 256) + with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: + new_config = json.load(f) + + new_config["model_name"] = output_name + new_config["data"]["num_styles"] = 1 + new_config["data"]["style2id"] = {DEFAULT_STYLE: 0} + with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: + json.dump(new_config, f, indent=2, ensure_ascii=False) + + neutral_vector_a = style_vectors_a[0] + neutral_vector_b = style_vectors_b[0] + weight = speech_style_weight + new_neutral_vector = neutral_vector_a + weight * neutral_vector_b + new_style_vectors = np.array([new_neutral_vector]) + new_style_path = assets_root / output_name / "style_vectors.npy" + np.save(new_style_path, new_style_vectors) return merged_model_path def merge_models_gr( - model_name_a: str, model_path_a: str, - model_name_b: str, model_path_b: str, - model_name_c: str, model_path_c: str, - use_add_diff: bool, + model_a_coeff: float, + model_b_coeff: float, + model_c_coeff: float, + method: str, output_name: str, voice_weight: float, voice_pitch_weight: float, @@ -334,7 +537,13 @@ def merge_models_gr( ): if output_name == "": return "Error: 新しいモデル名を入力してください。" - if not use_add_diff: + assert method in [ + "usual", + "add_diff", + "weighted_sum", + "add_zero", + ], f"Invalid method: {method}" + if method == "usual": merged_model_path = merge_models( model_path_a, model_path_b, @@ -345,7 +554,7 @@ def merge_models_gr( output_name, use_slerp_instead_of_lerp, ) - else: + elif method == "add_diff": merged_model_path = merge_models_add_diff( model_path_a, model_path_b, @@ -356,6 +565,26 @@ def merge_models_gr( tempo_weight, output_name, ) + elif method == "weighted_sum": + merged_model_path = merge_models_weighted_sum( + model_path_a, + model_path_b, + model_path_c, + model_a_coeff, + model_b_coeff, + model_c_coeff, + output_name, + ) + else: # add_zero + merged_model_path = merge_models_add_zero( + model_path_a, + model_path_b, + voice_weight, + voice_pitch_weight, + speech_style_weight, + tempo_weight, + output_name, + ) return f"Success: モデルを{merged_model_path}に保存しました。" @@ -363,7 +592,7 @@ def merge_style_gr( model_name_a: str, model_name_b: str, model_name_c: str, - use_add_diff: bool, + method: str, weight: float, output_name: str, style_tuple_list_str: str, @@ -374,38 +603,35 @@ def merge_style_gr( for line in style_tuple_list_str.split("\n"): if not line: continue - style_triple = line.split(",") - if not use_add_diff: - if len(style_triple) != 3: + style_tuple = line.split(",") + if method == "usual": + if len(style_tuple) != 3: logger.error(f"Invalid style triple: {line}") return ( f"Error: スタイルを3つのカンマ区切りで入力してください:\n{line}", None, ) - style_a, style_b, style_out = style_triple + style_a, style_b, style_out = style_tuple style_a = style_a.strip() style_b = style_b.strip() style_out = style_out.strip() style_tuple_list.append((style_a, style_b, style_out)) + new_style_path, new_styles = merge_style( + model_name_a, model_name_b, weight, output_name, style_tuple_list + ) else: - if len(style_triple) != 4: + if len(style_tuple) != 4: logger.error(f"Invalid style triple: {line}") return ( f"Error: スタイルを4つのカンマ区切りで入力してください:\n{line}", None, ) - style_a, style_b, style_c, style_out = style_triple + style_a, style_b, style_c, style_out = style_tuple style_a = style_a.strip() style_b = style_b.strip() style_c = style_c.strip() style_out = style_out.strip() style_tuple_list.append((style_a, style_b, style_c, style_out)) - try: - if not use_add_diff: - new_style_path, new_styles = merge_style( - model_name_a, model_name_b, weight, output_name, style_tuple_list - ) - else: new_style_path, new_styles = merge_style_add_diff( model_name_a, model_name_b, @@ -414,8 +640,6 @@ def merge_style_gr( output_name, style_tuple_list, ) - except ValueError as e: - return f"Error: {e}" return f"Success: スタイルを{new_style_path}に保存しました。", gr.Dropdown( choices=new_styles, value=new_styles[0] ) @@ -501,6 +725,95 @@ Happy, Surprise, HappySurprise - 構造上の相性の関係で、スタイルベクトルを混ぜる重みは、上の「話し方」と同じ比率で混ぜられます。例えば「話し方」が0のときはモデルAのみしか使われません。 """ +usual_md = """ +`weight` を下の各スライダーで定める数値とすると、各要素ごとに、 +``` +new_model = (1 - weight) * A + weight * B +``` +としてマージされます。 +""" + +add_diff_md = """ +`weight` を下の各スライダーで定める数値とすると、各要素ごとに、 +``` +new_model = A + weight * (B - C) +``` +としてマージされます。 +""" + +weighted_sum_md = """ +モデルの係数をそれぞれ `a`, `b`, `c` とすると、 **全要素に対して**、 +``` +new_model = a * A + b * B + c * C +``` +としてマージされます。 + +TIPS: + +- A, B, C が全て通常モデルの場合は、`a + b + c = 1`となるようにするのがよいと思います。 +- `a + b + c = 0` とすると(たとえば `A - B`)、話者性を持たないゼロモデルを作ることができ、「ゼロモデルとの和」で結果を使うことが出来ます(差分マージなど) +""" + +add_zero_md = """ +「ゼロモデル」を、いくつかのモデルの加重和であってその係数の和が0であるようなものとします(例えば `C - D` など)。 + +そうして作ったゼロモデルBと通常モデルAに対して、`weight` を下の各スライダーで定める数値とすると、各要素ごとに、 +``` +new_model = A + weight * B +``` +としてマージされます。 +""" + + +def method_change(x: str): + assert x in [ + "usual", + "add_diff", + "weighted_sum", + "add_zero", + ], f"Invalid method: {x}" + # model_desc, c_col, model_a_coeff, model_b_coeff, model_c_coeff, weight_row, use_slerp_instead_of_lerp + if x == "usual": + return ( + gr.Markdown(usual_md), + gr.Column(visible=False), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Row(visible=True), + gr.Checkbox(visible=True), + ) + elif x == "add_diff": + return ( + gr.Markdown(add_diff_md), + gr.Column(visible=True), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Row(visible=True), + gr.Checkbox(visible=False), + ) + elif x == "add_zero": + return ( + gr.Markdown(add_zero_md), + gr.Column(visible=False), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Number(visible=False), + gr.Row(visible=True), + gr.Checkbox(visible=False), + ) + else: # weighted_sum + return ( + gr.Markdown(weighted_sum_md), + gr.Column(visible=True), + gr.Number(visible=True), + gr.Number(visible=True), + gr.Number(visible=True), + gr.Row(visible=False), + gr.Checkbox(visible=False), + ) + def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: model_names = model_holder.model_names @@ -525,7 +838,16 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) with gr.Accordion(label="使い方", open=False): gr.Markdown(initial_md) - use_add_diff = gr.Checkbox(label="差分マージ", value=False) + method = gr.Radio( + label="マージ方法", + choices=[ + ("通常マージ", "usual"), + ("差分マージ", "add_diff"), + ("加重和", "weighted_sum"), + ("ゼロモデルマージ", "add_zero"), + ], + value="usual", + ) with gr.Row(): with gr.Column(scale=3): model_name_a = gr.Dropdown( @@ -538,6 +860,12 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: choices=initial_model_files, value=initial_model_files[0], ) + model_a_coeff = gr.Number( + label="モデルAの係数", + value=1.0, + step=0.1, + visible=False, + ) with gr.Column(scale=3): model_name_b = gr.Dropdown( label="モデルB", @@ -549,26 +877,34 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: choices=initial_model_files, value=initial_model_files[0], ) - with gr.Column(scale=3): + model_b_coeff = gr.Number( + label="モデルBの係数", + value=-1.0, + step=0.1, + visible=False, + ) + with gr.Column(scale=3, visible=False) as c_col: model_name_c = gr.Dropdown( label="モデルC", choices=model_names, value=model_names[initial_id], - visible=False, ) model_path_c = gr.Dropdown( label="モデルファイル", choices=initial_model_files, value=initial_model_files[0], + ) + model_c_coeff = gr.Number( + label="モデルCの係数", + value=0.0, + step=0.1, visible=False, ) refresh_button = gr.Button("更新", scale=1, visible=True) - gr.Markdown( - "通常マージの場合、`new = (1 - weight) * A + weight * B`、差分マージの場合、`new = A + weight * (B - C)`" - ) + method_desc = gr.Markdown(usual_md) with gr.Column(variant="panel"): new_name = gr.Textbox(label="新しいモデル名", placeholder="new_model") - with gr.Row(): + with gr.Row() as weight_row: voice_slider = gr.Slider( label="声質", value=0, @@ -600,6 +936,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: use_slerp_instead_of_lerp = gr.Checkbox( label="線形補完のかわりに球面線形補完を使う", value=False, + visible=True, ) with gr.Column(variant="panel"): gr.Markdown("## モデルファイル(safetensors)のマージ") @@ -626,8 +963,8 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) style = gr.Dropdown( label="スタイル", - choices=["スタイルをマージしてください"], - value="スタイルをマージしてください", + choices=[DEFAULT_STYLE], + value=DEFAULT_STYLE, ) emotion_weight = gr.Slider( minimum=0, @@ -638,10 +975,18 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) tts_button = gr.Button("音声合成", variant="primary") audio_output = gr.Audio(label="結果") - use_add_diff.change( - lambda x: (gr.Dropdown(visible=x), gr.Dropdown(visible=x)), - inputs=[use_add_diff], - outputs=[model_name_c, model_path_c], + method.change( + method_change, + inputs=[method], + outputs=[ + method_desc, + c_col, + model_a_coeff, + model_b_coeff, + model_c_coeff, + weight_row, + use_slerp_instead_of_lerp, + ], ) model_name_a.change( model_holder.update_model_files_for_gradio, @@ -680,13 +1025,13 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: model_merge_button.click( merge_models_gr, inputs=[ - model_name_a, model_path_a, - model_name_b, model_path_b, - model_name_c, model_path_c, - use_add_diff, + model_a_coeff, + model_b_coeff, + model_c_coeff, + method, new_name, voice_slider, voice_pitch_slider, @@ -703,7 +1048,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: model_name_a, model_name_b, model_name_c, - use_add_diff, + method, speech_style_slider, new_name, style_triple_list, From f16b80ab1fbf72e9e9bfb79f4a4e9803717f1d03 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Sun, 16 Jun 2024 16:48:50 +0900 Subject: [PATCH 4/7] Feat: style vector merge for new merge methods, better UI --- gradio_tabs/merge.py | 968 +++++++++++++++++++++++++++++++++---------- 1 file changed, 748 insertions(+), 220 deletions(-) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 979d6a3..782168d 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -1,6 +1,6 @@ import json from pathlib import Path -from typing import Union +from typing import Union, Any import gradio as gr import numpy as np @@ -32,39 +32,60 @@ def load_safetensors(model_path: Union[str, Path]) -> dict[str, torch.Tensor]: return result -def merge_style( +def load_config(model_name: str) -> dict[str, Any]: + with open(assets_root / model_name / "config.json", encoding="utf-8") as f: + config = json.load(f) + return config + + +def save_config(config: dict[str, Any], model_name: str): + with open(assets_root / model_name / "config.json", "w", encoding="utf-8") as f: + json.dump(config, f, indent=2, ensure_ascii=False) + + +def load_recipe(model_name: str) -> dict[str, Any]: + receipe_path = assets_root / model_name / "recipe.json" + if receipe_path.exists(): + with open(receipe_path, encoding="utf-8") as f: + recipe = json.load(f) + else: + recipe = {} + return recipe + + +def save_recipe(recipe: dict[str, Any], model_name: str): + with open(assets_root / model_name / "recipe.json", "w", encoding="utf-8") as f: + json.dump(recipe, f, indent=2, ensure_ascii=False) + + +def load_style_vectors(model_name: str) -> np.ndarray: + return np.load(assets_root / model_name / "style_vectors.npy") + + +def save_style_vectors(style_vectors: np.ndarray, model_name: str): + np.save(assets_root / model_name / "style_vectors.npy", style_vectors) + + +def merge_style_usual( model_name_a: str, model_name_b: str, weight: float, output_name: str, - style_triple_list: list[tuple[str, ...]], -) -> tuple[Path, list[str]]: + style_tuple_list: list[tuple[str, ...]], +) -> list[str]: """ + new = (1 - weight) * A + weight * B style_triple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)] """ - # 新スタイル名リストにNeutralが含まれているか確認し、Neutralを先頭に持ってくる - if any(triple[2] == DEFAULT_STYLE for triple in style_triple_list): - # 存在する場合、リストをソート - sorted_list = sorted(style_triple_list, key=lambda x: x[2] != DEFAULT_STYLE) - else: - # 存在しない場合、エラーを発生 - raise ValueError(f"No element with {DEFAULT_STYLE} output style name found.") - - style_vectors_a = np.load( - assets_root / model_name_a / "style_vectors.npy" - ) # (style_num_a, 256) - style_vectors_b = np.load( - assets_root / model_name_b / "style_vectors.npy" - ) # (style_num_b, 256) - with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: - config_a = json.load(f) - with open(assets_root / model_name_b / "config.json", encoding="utf-8") as f: - config_b = json.load(f) + style_vectors_a = load_style_vectors(model_name_a) + style_vectors_b = load_style_vectors(model_name_b) + config_a = load_config(model_name_a) + config_b = load_config(model_name_b) style2id_a = config_a["data"]["style2id"] style2id_b = config_b["data"]["style2id"] new_style_vecs = [] new_style2id = {} - for style_a, style_b, style_out in sorted_list: + for style_a, style_b, style_out in style_tuple_list: if style_a not in style2id_a: logger.error(f"{style_a} is not in {model_name_a}.") raise ValueError(f"{style_a} は {model_name_a} にありません。") @@ -78,29 +99,19 @@ def merge_style( new_style_vecs.append(new_style) new_style2id[style_out] = len(new_style_vecs) - 1 new_style_vecs = np.array(new_style_vecs) - - output_style_path = assets_root / output_name / "style_vectors.npy" - np.save(output_style_path, new_style_vecs) + save_style_vectors(new_style_vecs, output_name) new_config = config_a.copy() new_config["data"]["num_styles"] = len(new_style2id) new_config["data"]["style2id"] = new_style2id new_config["model_name"] = output_name - with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: - json.dump(new_config, f, indent=2, ensure_ascii=False) + save_config(new_config, output_name) - # recipe.jsonを読み込んで、style_triple_listを追記 - info_path = assets_root / output_name / "recipe.json" - if info_path.exists(): - with open(info_path, encoding="utf-8") as f: - info = json.load(f) - else: - info = {} - info["style_triple_list"] = style_triple_list - with open(info_path, "w", encoding="utf-8") as f: - json.dump(info, f, indent=2, ensure_ascii=False) + receipe = load_recipe(output_name) + receipe["style_tuple_list"] = style_tuple_list + save_recipe(receipe, output_name) - return output_style_path, list(new_style2id.keys()) + return list(new_style2id.keys()) def merge_style_add_diff( @@ -110,38 +121,23 @@ def merge_style_add_diff( weight: float, output_name: str, style_tuple_list: list[tuple[str, ...]], -) -> tuple[Path, list[str]]: +) -> list[str]: """ new = A + weight * (B - C) + style_tuple_list: list[(model_aでのスタイル名, model_bでのスタイル名, model_cでのスタイル名, 出力するスタイル名)] """ - if any(triple[3] == DEFAULT_STYLE for triple in style_tuple_list): - # 存在する場合、リストをソート - sorted_list = sorted(style_tuple_list, key=lambda x: x[3] != DEFAULT_STYLE) - else: - # 存在しない場合、エラーを発生 - raise ValueError(f"No element with {DEFAULT_STYLE} output style name found.") - - style_vectors_a = np.load( - assets_root / model_name_a / "style_vectors.npy" - ) # (style_num_a, 256) - style_vectors_b = np.load( - assets_root / model_name_b / "style_vectors.npy" - ) # (style_num_b, 256) - style_vectors_c = np.load( - assets_root / model_name_c / "style_vectors.npy" - ) # (style_num_c, 256) - with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: - config_a = json.load(f) - with open(assets_root / model_name_b / "config.json", encoding="utf-8") as f: - config_b = json.load(f) - with open(assets_root / model_name_c / "config.json", encoding="utf-8") as f: - config_c = json.load(f) + style_vectors_a = load_style_vectors(model_name_a) + style_vectors_b = load_style_vectors(model_name_b) + style_vectors_c = load_style_vectors(model_name_c) + config_a = load_config(model_name_a) + config_b = load_config(model_name_b) + config_c = load_config(model_name_c) style2id_a = config_a["data"]["style2id"] style2id_b = config_b["data"]["style2id"] style2id_c = config_c["data"]["style2id"] new_style_vecs = [] new_style2id = {} - for style_a, style_b, style_c, style_out in sorted_list: + for style_a, style_b, style_c, style_out in style_tuple_list: if style_a not in style2id_a: logger.error(f"{style_a} is not in {model_name_a}.") raise ValueError(f"{style_a} は {model_name_a} にありません。") @@ -158,28 +154,127 @@ def merge_style_add_diff( new_style2id[style_out] = len(new_style_vecs) - 1 new_style_vecs = np.array(new_style_vecs) - output_style_path = assets_root / output_name / "style_vectors.npy" - np.save(output_style_path, new_style_vecs) + save_style_vectors(new_style_vecs, output_name) new_config = config_a.copy() new_config["data"]["num_styles"] = len(new_style2id) new_config["data"]["style2id"] = new_style2id new_config["model_name"] = output_name - with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: - json.dump(new_config, f, indent=2, ensure_ascii=False) + save_config(new_config, output_name) - # recipe.jsonを読み込んで、style_tuple_listを追記 - info_path = assets_root / output_name / "recipe.json" - if info_path.exists(): - with open(info_path, encoding="utf-8") as f: - info = json.load(f) - else: - info = {} - info["style_tuple_list"] = style_tuple_list - with open(info_path, "w", encoding="utf-8") as f: - json.dump(info, f, indent=2, ensure_ascii=False) + receipe = load_recipe(output_name) + receipe["style_tuple_list"] = style_tuple_list + save_recipe(receipe, output_name) - return output_style_path, list(new_style2id.keys()) + return list(new_style2id.keys()) + + +def merge_style_weighted_sum( + model_name_a: str, + model_name_b: str, + model_name_c: str, + model_a_coeff: float, + model_b_coeff: float, + model_c_coeff: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +) -> list[str]: + """ + new = A * model_a_coeff + B * model_b_coeff + C * model_c_coeff + style_tuple_list: list[(model_aでのスタイル名, model_bでのスタイル名, model_cでのスタイル名, 出力するスタイル名)] + """ + style_vectors_a = load_style_vectors(model_name_a) + style_vectors_b = load_style_vectors(model_name_b) + style_vectors_c = load_style_vectors(model_name_c) + config_a = load_config(model_name_a) + config_b = load_config(model_name_b) + config_c = load_config(model_name_c) + style2id_a = config_a["data"]["style2id"] + style2id_b = config_b["data"]["style2id"] + style2id_c = config_c["data"]["style2id"] + new_style_vecs = [] + new_style2id = {} + for style_a, style_b, style_c, style_out in style_tuple_list: + if style_a not in style2id_a: + logger.error(f"{style_a} is not in {model_name_a}.") + raise ValueError(f"{style_a} は {model_name_a} にありません。") + if style_b not in style2id_b: + logger.error(f"{style_b} is not in {model_name_b}.") + raise ValueError(f"{style_b} は {model_name_b} にありません。") + if style_c not in style2id_c: + logger.error(f"{style_c} is not in {model_name_c}.") + raise ValueError(f"{style_c} は {model_name_c} にありません。") + new_style = ( + style_vectors_a[style2id_a[style_a]] * model_a_coeff + + style_vectors_b[style2id_b[style_b]] * model_b_coeff + + style_vectors_c[style2id_c[style_c]] * model_c_coeff + ) + new_style_vecs.append(new_style) + new_style2id[style_out] = len(new_style_vecs) - 1 + new_style_vecs = np.array(new_style_vecs) + + save_style_vectors(new_style_vecs, output_name) + + new_config = config_a.copy() + new_config["data"]["num_styles"] = len(new_style2id) + new_config["data"]["style2id"] = new_style2id + new_config["model_name"] = output_name + save_config(new_config, output_name) + + receipe = load_recipe(output_name) + receipe["style_tuple_list"] = style_tuple_list + save_recipe(receipe, output_name) + + return list(new_style2id.keys()) + + +def merge_style_add_zero( + model_name_a: str, + model_name_b: str, + weight: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +) -> list[str]: + """ + new = A + weight * B + style_tuple_list: list[(model_aでのスタイル名, model_bでのスタイル名, 出力するスタイル名)] + """ + style_vectors_a = load_style_vectors(model_name_a) + style_vectors_b = load_style_vectors(model_name_b) + config_a = load_config(model_name_a) + config_b = load_config(model_name_b) + style2id_a = config_a["data"]["style2id"] + style2id_b = config_b["data"]["style2id"] + new_style_vecs = [] + new_style2id = {} + for style_a, style_b, style_out in style_tuple_list: + if style_a not in style2id_a: + logger.error(f"{style_a} is not in {model_name_a}.") + raise ValueError(f"{style_a} は {model_name_a} にありません。") + if style_b not in style2id_b: + logger.error(f"{style_b} is not in {model_name_b}.") + raise ValueError(f"{style_b} は {model_name_b} にありません。") + new_style = ( + style_vectors_a[style2id_a[style_a]] + + weight * style_vectors_b[style2id_b[style_b]] + ) + new_style_vecs.append(new_style) + new_style2id[style_out] = len(new_style_vecs) - 1 + new_style_vecs = np.array(new_style_vecs) + + save_style_vectors(new_style_vecs, output_name) + + new_config = config_a.copy() + new_config["data"]["num_styles"] = len(new_style2id) + new_config["data"]["style2id"] = new_style2id + new_config["model_name"] = output_name + save_config(new_config, output_name) + + receipe = load_recipe(output_name) + receipe["style_tuple_list"] = style_tuple_list + save_recipe(receipe, output_name) + + return list(new_style2id.keys()) def lerp_tensors(t: float, v0: torch.Tensor, v1: torch.Tensor): @@ -207,7 +302,7 @@ def slerp_tensors( ).to(device) -def merge_models( +def merge_models_usual( model_path_a: str, model_path_b: str, voice_weight: float, @@ -244,7 +339,7 @@ def merge_models( merged_model_path.parent.mkdir(parents=True, exist_ok=True) save_file(merged_model_weight, merged_model_path) - info = { + receipe = { "method": "usual", "model_a": model_path_a, "model_b": model_path_b, @@ -254,34 +349,26 @@ def merge_models( "tempo_weight": tempo_weight, "use_slerp_instead_of_lerp": use_slerp_instead_of_lerp, } - with open(assets_root / output_name / "recipe.json", "w", encoding="utf-8") as f: - json.dump(info, f, indent=2, ensure_ascii=False) + save_recipe(receipe, output_name) - # Default style merge only using Neutral style + # Merge default Neutral style vectors and save model_name_a = Path(model_path_a).parent.name model_name_b = Path(model_path_b).parent.name - style_vectors_a = np.load( - assets_root / model_name_a / "style_vectors.npy" - ) # (style_num_a, 256) - style_vectors_b = np.load( - assets_root / model_name_b / "style_vectors.npy" - ) # (style_num_b, 256) - with open(assets_root / model_name_a / "config.json", encoding="utf-8") as f: - new_config = json.load(f) + style_vectors_a = load_style_vectors(model_name_a) + style_vectors_b = load_style_vectors(model_name_b) + new_config = load_config(model_name_a) new_config["model_name"] = output_name new_config["data"]["num_styles"] = 1 new_config["data"]["style2id"] = {DEFAULT_STYLE: 0} - with open(assets_root / output_name / "config.json", "w", encoding="utf-8") as f: - json.dump(new_config, f, indent=2, ensure_ascii=False) + save_config(new_config, output_name) neutral_vector_a = style_vectors_a[0] neutral_vector_b = style_vectors_b[0] weight = speech_style_weight new_neutral_vector = (1 - weight) * neutral_vector_a + weight * neutral_vector_b new_style_vectors = np.array([new_neutral_vector]) - new_style_path = assets_root / output_name / "style_vectors.npy" - np.save(new_style_path, new_style_vectors) + save_style_vectors(new_style_vectors, output_name) return merged_model_path @@ -543,8 +630,13 @@ def merge_models_gr( "weighted_sum", "add_zero", ], f"Invalid method: {method}" + model_a_name = Path(model_path_a).parent.name + model_b_name = Path(model_path_b).parent.name + model_c_name = Path(model_path_c).parent.name if method == "usual": - merged_model_path = merge_models( + if output_name in [model_a_name, model_b_name]: + return "Error: マージ元のモデル名と同じ名前は使用できません。", None + merged_model_path = merge_models_usual( model_path_a, model_path_b, voice_weight, @@ -555,6 +647,8 @@ def merge_models_gr( use_slerp_instead_of_lerp, ) elif method == "add_diff": + if output_name in [model_a_name, model_b_name, model_c_name]: + return "Error: マージ元のモデル名と同じ名前は使用できません。", None merged_model_path = merge_models_add_diff( model_path_a, model_path_b, @@ -566,6 +660,8 @@ def merge_models_gr( output_name, ) elif method == "weighted_sum": + if output_name in [model_a_name, model_b_name, model_c_name]: + return "Error: マージ元のモデル名と同じ名前は使用できません。", None merged_model_path = merge_models_weighted_sum( model_path_a, model_path_b, @@ -576,6 +672,8 @@ def merge_models_gr( output_name, ) else: # add_zero + if output_name in [model_a_name, model_b_name]: + return "Error: マージ元のモデル名と同じ名前は使用できません。", None merged_model_path = merge_models_add_zero( model_path_a, model_path_b, @@ -585,75 +683,179 @@ def merge_models_gr( tempo_weight, output_name, ) - return f"Success: モデルを{merged_model_path}に保存しました。" + return f"Success: モデルを{merged_model_path}に保存しました。", gr.Dropdown( + choices=[DEFAULT_STYLE], value=DEFAULT_STYLE + ) -def merge_style_gr( +def merge_style_usual_gr( model_name_a: str, model_name_b: str, - model_name_c: str, - method: str, weight: float, output_name: str, - style_tuple_list_str: str, + style_tuple_list: list[tuple[str, ...]], ): if output_name == "": return "Error: 新しいモデル名を入力してください。", None - style_tuple_list: list[tuple[str, ...]] = [] - for line in style_tuple_list_str.split("\n"): - if not line: - continue - style_tuple = line.split(",") - if method == "usual": - if len(style_tuple) != 3: - logger.error(f"Invalid style triple: {line}") - return ( - f"Error: スタイルを3つのカンマ区切りで入力してください:\n{line}", - None, - ) - style_a, style_b, style_out = style_tuple - style_a = style_a.strip() - style_b = style_b.strip() - style_out = style_out.strip() - style_tuple_list.append((style_a, style_b, style_out)) - new_style_path, new_styles = merge_style( - model_name_a, model_name_b, weight, output_name, style_tuple_list - ) - else: - if len(style_tuple) != 4: - logger.error(f"Invalid style triple: {line}") - return ( - f"Error: スタイルを4つのカンマ区切りで入力してください:\n{line}", - None, - ) - style_a, style_b, style_c, style_out = style_tuple - style_a = style_a.strip() - style_b = style_b.strip() - style_c = style_c.strip() - style_out = style_out.strip() - style_tuple_list.append((style_a, style_b, style_c, style_out)) - new_style_path, new_styles = merge_style_add_diff( - model_name_a, - model_name_b, - model_name_c, - weight, - output_name, - style_tuple_list, - ) - return f"Success: スタイルを{new_style_path}に保存しました。", gr.Dropdown( + new_styles = merge_style_usual( + model_name_a, + model_name_b, + weight, + output_name, + style_tuple_list, + ) + return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( choices=new_styles, value=new_styles[0] ) +def merge_style_add_diff_gr( + model_name_a: str, + model_name_b: str, + model_name_c: str, + weight: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +): + if output_name == "": + return "Error: 新しいモデル名を入力してください。", None + new_styles = merge_style_add_diff( + model_name_a, + model_name_b, + model_name_c, + weight, + output_name, + style_tuple_list, + ) + return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( + choices=new_styles, value=new_styles[0] + ) + + +def merge_style_weighted_sum_gr( + model_name_a: str, + model_name_b: str, + model_name_c: str, + model_a_coeff: float, + model_b_coeff: float, + model_c_coeff: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +): + if output_name == "": + return "Error: 新しいモデル名を入力してください。", None + new_styles = merge_style_weighted_sum( + model_name_a, + model_name_b, + model_name_c, + model_a_coeff, + model_b_coeff, + model_c_coeff, + output_name, + style_tuple_list, + ) + return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( + choices=new_styles, value=new_styles[0] + ) + + +def merge_style_add_zero_gr( + model_name_a: str, + model_name_b: str, + weight: float, + output_name: str, + style_tuple_list: list[tuple[str, ...]], +): + if output_name == "": + return "Error: 新しいモデル名を入力してください。", None + new_styles = merge_style_add_zero( + model_name_a, + model_name_b, + weight, + output_name, + style_tuple_list, + ) + return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( + choices=new_styles, value=new_styles[0] + ) + + +# def merge_style_gr( +# model_name_a: str, +# model_name_b: str, +# model_name_c: str, +# model_a_coeff: float, +# model_b_coeff: float, +# model_c_coeff: float, +# method: str, +# weight: float, +# output_name: str, +# style_tuple_list: list[tuple[str, ...]], +# ): +# if output_name == "": +# return "Error: 新しいモデル名を入力してください。", None +# assert method in [ +# "usual", +# "add_diff", +# "weighted_sum", +# "add_zero", +# ], f"Invalid method: {method}" +# if method == "usual": +# new_styles = merge_style_usual( +# model_name_a, +# model_name_b, +# weight, +# output_name, +# style_tuple_list, +# ) +# elif method == "add_diff": +# new_styles = merge_style_add_diff( +# model_name_a, +# model_name_b, +# model_name_c, +# weight, +# output_name, +# style_tuple_list, +# ) +# elif method == "weighted_sum": +# new_styles = merge_style_weighted_sum( +# model_name_a, +# model_name_b, +# model_name_c, +# model_a_coeff, +# model_b_coeff, +# model_c_coeff, +# output_name, +# style_tuple_list, +# ) +# else: # add_zero +# new_styles = merge_style_add_zero( +# model_name_a, +# model_name_b, +# weight, +# output_name, +# style_tuple_list, +# ) +# return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( +# choices=new_styles, value=new_styles[0] +# ) + + def simple_tts( model_name: str, text: str, style: str = DEFAULT_STYLE, style_weight: float = 1.0 ): + if model_name == "": + return "Error: モデル名を入力してください。", None model_path = assets_root / model_name / f"{model_name}.safetensors" config_path = assets_root / model_name / "config.json" style_vec_path = assets_root / model_name / "style_vectors.npy" model = TTSModel(model_path, config_path, style_vec_path, device) - return model.infer(text, style=style, style_weight=style_weight) + + return ( + "Success: 音声を生成しました。", + model.infer(text, style=style, style_weight=style_weight), + ) def update_three_model_names_dropdown(model_holder: TTSModelHolder): @@ -661,6 +863,18 @@ def update_three_model_names_dropdown(model_holder: TTSModelHolder): return new_names, new_files, new_names, new_files, new_names, new_files +def get_styles(model_name: str): + config_path = assets_root / model_name / "config.json" + with open(config_path, encoding="utf-8") as f: + config = json.load(f) + styles = list(config["data"]["style2id"].keys()) + return styles + + +def get_triple_styles(model_name_a: str, model_name_b: str, model_name_c: str): + return get_styles(model_name_a), get_styles(model_name_b), get_styles(model_name_c) + + def load_styles_gr(model_name_a: str, model_name_b: str): config_path_a = assets_root / model_name_a / "config.json" with open(config_path_a, encoding="utf-8") as f: @@ -690,11 +904,30 @@ def load_styles_gr(model_name_a: str, model_name_b: str): initial_md = """ ## 使い方 -1. マージしたい2つのモデルを選択してください(`model_assets`フォルダの中から選ばれます)。 -2. マージ後のモデルの名前を入力してください。 -3. マージ後のモデルの声質・話し方・話す速さを調整してください。 -4. 「モデルファイルのマージ」ボタンを押してください(safetensorsファイルがマージされる)。 -5. スタイルベクトルファイルも生成する必要があるので、指示に従ってマージ方法を入力後、「スタイルのマージ」ボタンを押してください。 +### マージ方法の選択 + +マージの方法には4つの方法があります。 +- 通常のマージ `new = (1 - weight) * A + weight * B`: AとBのモデルを指定して、要素ごとに比率を指定して混ぜる + - 単純にAとBの二人の話し方や声音を混ぜたいとき +- 差分マージ `new = A + weight * (B - C)`: AとBとCのモデルを指定して、「Bの要素からCの要素を引いたもの」をAに足す + - 例えば、Bが「Cと同じ人だけど囁いているモデル」とすると、`B - C`は「囁きを表すベクトル」だと思えるので、それをAに足すことで、Aの声のままで囁き声を出すモデルができたりする + - 他にも活用例はいろいろありそう +- 重み付き和 `new = a * A + b * B + c * C`: AとBとCのモデルを指定して、各モデルの係数を指定して混ぜる + - 例えば`new = A - B` としておくと、結果としてできたモデルを別のモデルと「ゼロモデルの加算」で使うことで、差分マージが実現できる + - 他にも何らかの活用法があるかもしれない +- ゼロモデルの加算 `new = A + weight * B`: AとBのモデルを指定して、Bのモデルに要素ごとに比率をかけたものをAに足す + - Bのモデルは重み付き和などで `C - D` などとして作っている場合を想定している + - 他にも何らかの活用法があるかもしれない + + +### マージの手順 + +1. マージ元のモデルたちを選択(`model_assets`フォルダの中から選ばれます) +2. マージ後のモデルの名前を入力 +3. 指示に従って重みや係数を入力 +4. 「モデルファイルのマージ」ボタンを押す (safetensorsファイルがマージされる) +5. 結果を簡易音声合成で確認 +6. 必要に応じてスタイルベクトルのマージを行う 以上でマージは完了で、`model_assets/マージ後のモデル名`にマージ後のモデルが保存され、音声合成のときに使えます。 @@ -703,26 +936,23 @@ initial_md = """ 一番下にマージしたモデルによる簡易的な音声合成機能もつけています。 ## 注意 -1.x系と2.x-JP-Extraのモデルマージは失敗するようです。 + +- 1.x系と2.x-JP-Extraのモデルマージは失敗するようです。 +- 話者数が違うモデル同士はおそらくマージできません。 """ style_merge_md = f""" -## スタイルベクトルのマージ +## 3. スタイルベクトルのマージ -1行に「モデルAのスタイル名, モデルBのスタイル名, 左の2つを混ぜて出力するスタイル名」 -という形式で入力してください。例えば、 -``` -{DEFAULT_STYLE}, {DEFAULT_STYLE}, {DEFAULT_STYLE} -Happy, Surprise, HappySurprise -``` -と入力すると、マージ後のスタイルベクトルは、 -- `{DEFAULT_STYLE}`: モデルAの`{DEFAULT_STYLE}`とモデルBの`{DEFAULT_STYLE}`を混ぜたもの -- `HappySurprise`: モデルAの`Happy`とモデルBの`Surprise`を混ぜたもの -の2つになります。 +1. マージ後のモデルにいくつスタイルを追加したいかを「作りたいスタイル数」で指定 +2. マージ前のモデルのスタイルを「各モデルのスタイルを取得」ボタンで取得 +3. どのスタイルたちから新しいスタイルを作るかを下の欄で入力 +4. 「スタイルのマージ」をクリック -### 注意 -- 必ず「{DEFAULT_STYLE}」という名前のスタイルを作ってください。これは、マージ後のモデルの平均スタイルになります。 -- 構造上の相性の関係で、スタイルベクトルを混ぜる重みは、上の「話し方」と同じ比率で混ぜられます。例えば「話し方」が0のときはモデルAのみしか使われません。 +### スタイルベクトルの混ぜられ方 + +- 構造上の相性の関係で、スタイルベクトルを混ぜる重みは、加重和以外の場合は、上の「話し方」と同じ比率で混ぜられます。例えば「話し方」が0のときはモデルAのみしか使われません。 +- 加重和の場合は、AとBとCの係数によって混ぜられます。 """ usual_md = """ @@ -731,6 +961,8 @@ usual_md = """ new_model = (1 - weight) * A + weight * B ``` としてマージされます。 + +つまり、`weight = 0` のときはモデルA、`weight = 1` のときはモデルBになります。 """ add_diff_md = """ @@ -748,10 +980,11 @@ new_model = a * A + b * B + c * C ``` としてマージされます。 -TIPS: +## TIPS -- A, B, C が全て通常モデルの場合は、`a + b + c = 1`となるようにするのがよいと思います。 -- `a + b + c = 0` とすると(たとえば `A - B`)、話者性を持たないゼロモデルを作ることができ、「ゼロモデルとの和」で結果を使うことが出来ます(差分マージなど) +- A, B, C が全て通常モデルで、通常モデルを作りたい場合は、`a + b + c = 1`となるようにするのがよいと思います。 +- `a + b + c = 0` とすると(たとえば `A - B`)、話者性を持たないゼロモデルを作ることができ、「ゼロモデルとの和」で結果を使うことが出来ます(差分マージの材料などに) +- 他にも、`a = 0.5, b = c = 0`などでモデルAを謎に小さくしたり大きくしたり負にしたりできるので、実験に使ってください。 """ add_zero_md = """ @@ -764,6 +997,12 @@ new_model = A + weight * B としてマージされます。 """ +tts_md = f""" +## 2. 結果のテスト + +マージ後のモデルで音声合成を行います。ただし、デフォルトではスタイルは`{DEFAULT_STYLE}`しか使えないので、他のスタイルを使いたい場合は、下の「スタイルベクトルのマージ」を行ってください。 +""" + def method_change(x: str): assert x in [ @@ -827,7 +1066,6 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) return app initial_id = 0 - # initial_model_files = model_holder.model_files_dict[model_names[initial_id]] initial_model_files = [ str(f) for f in model_holder.model_files_dict[model_names[initial_id]] ] @@ -938,43 +1176,325 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: value=False, visible=True, ) - with gr.Column(variant="panel"): - gr.Markdown("## モデルファイル(safetensors)のマージ") + with gr.Column(variant="panel"): + gr.Markdown("## 1. モデルファイル (safetensors) のマージ") + with gr.Row(): model_merge_button = gr.Button( "モデルファイルのマージ", variant="primary" ) info_model_merge = gr.Textbox(label="情報") - with gr.Column(variant="panel"): - gr.Markdown(style_merge_md) - with gr.Row(): - load_style_button = gr.Button("スタイル一覧をロード", scale=1) - styles_a = gr.Textbox(label="モデルAのスタイル一覧") - styles_b = gr.Textbox(label="モデルBのスタイル一覧") - style_triple_list = gr.TextArea( - label="スタイルのマージリスト", - placeholder=f"{DEFAULT_STYLE}, {DEFAULT_STYLE},{DEFAULT_STYLE}\nAngry, Angry, Angry", - value=f"{DEFAULT_STYLE}, {DEFAULT_STYLE}, {DEFAULT_STYLE}", - ) - style_merge_button = gr.Button("スタイルのマージ", variant="primary") - info_style_merge = gr.Textbox(label="情報") + with gr.Column(variant="panel"): + gr.Markdown(tts_md) + text_input = gr.TextArea( + label="テキスト", value="これはテストです。聞こえていますか?" + ) + with gr.Row(): + with gr.Column(): + style = gr.Dropdown( + label="スタイル", + choices=[DEFAULT_STYLE], + value=DEFAULT_STYLE, + ) + emotion_weight = gr.Slider( + minimum=0, + maximum=50, + value=1, + step=0.1, + label="スタイルの強さ", + ) + tts_button = gr.Button("音声合成", variant="primary") + tts_info = gr.Textbox(label="情報") + audio_output = gr.Audio(label="結果") + with gr.Column(variant="panel"): + gr.Markdown(style_merge_md) + style_a_list = gr.State([DEFAULT_STYLE]) + style_b_list = gr.State([DEFAULT_STYLE]) + style_c_list = gr.State([DEFAULT_STYLE]) + gr.Markdown("Hello world!") + with gr.Row(): + style_count = gr.Number(label="作るスタイルの数", value=1, step=1) + + get_style_btn = gr.Button("各モデルのスタイルを取得", variant="primary") + get_style_btn.click( + get_triple_styles, + inputs=[model_name_a, model_name_b, model_name_c], + outputs=[style_a_list, style_b_list, style_c_list], + ) + + def join_names(*args): + if all(arg == DEFAULT_STYLE for arg in args): + return DEFAULT_STYLE + return "_".join(args) + + @gr.render( + inputs=[ + style_count, + style_a_list, + style_b_list, + style_c_list, + method, + ] + ) + def render_style( + style_count, style_a_list, style_b_list, style_c_list, method + ): + print( + f"{style_count=}, {method=}, {style_a_list=}, {style_b_list=}, {style_c_list=}" + ) + a_components = [] + b_components = [] + c_components = [] + out_components = [] + if method in ["usual", "add_zero"]: + for i in range(style_count): + with gr.Row(): + style_a = gr.Dropdown( + label="モデルAのスタイル名", + key=f"style_a_{i}", + choices=style_a_list, + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_b = gr.Dropdown( + label="モデルBのスタイル名", + key=f"style_b_{i}", + choices=style_b_list, + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_out = gr.Textbox( + label="出力スタイル名", + key=f"style_out_{i}", + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_a.change( + join_names, + inputs=[style_a, style_b], + outputs=[style_out], + ) + style_b.change( + join_names, + inputs=[style_a, style_b], + outputs=[style_out], + ) + a_components.append(style_a) + b_components.append(style_b) + out_components.append(style_out) + if method == "usual": + + def _merge_usual(data): + style_tuple_list = [ + (data[a], data[b], data[out]) + for a, b, out in zip( + a_components, b_components, out_components + ) + ] + return merge_style_usual_gr( + data[model_name_a], + data[model_name_b], + data[speech_style_slider], + data[new_name], + style_tuple_list, + ) + + style_merge_btn.click( + _merge_usual, + inputs=set( + a_components + + b_components + + out_components + + [ + model_name_a, + model_name_b, + speech_style_slider, + new_name, + ] + ), + outputs=[info_style_merge, style], + ) + else: # add_zero + + def _merge_add_zero(data): + print("Method is add_zero") + style_tuple_list = [ + (data[a], data[b], data[out]) + for a, b, out in zip( + a_components, b_components, out_components + ) + ] + return merge_style_add_zero_gr( + data[model_name_a], + data[model_name_b], + data[speech_style_slider], + data[new_name], + style_tuple_list, + ) + + style_merge_btn.click( + _merge_add_zero, + inputs=set( + a_components + + b_components + + out_components + + [ + model_name_a, + model_name_b, + speech_style_slider, + new_name, + ] + ), + outputs=[info_style_merge, style], + ) + + elif method in ["add_diff", "weighted_sum"]: + for i in range(style_count): + with gr.Row(): + style_a = gr.Dropdown( + label="モデルAのスタイル名", + key=f"style_a_{i}", + choices=style_a_list, + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_b = gr.Dropdown( + label="モデルBのスタイル名", + key=f"style_b_{i}", + choices=style_b_list, + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_c = gr.Dropdown( + label="モデルCのスタイル名", + key=f"style_c_{i}", + choices=style_c_list, + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_out = gr.Textbox( + label="出力スタイル名", + key=f"style_out_{i}", + value=DEFAULT_STYLE, + interactive=i != 0, + ) + style_a.change( + join_names, + inputs=[style_a, style_b, style_c], + outputs=[style_out], + ) + style_b.change( + join_names, + inputs=[style_a, style_b, style_c], + outputs=[style_out], + ) + style_c.change( + join_names, + inputs=[style_a, style_b, style_c], + outputs=[style_out], + ) + + a_components.append(style_a) + b_components.append(style_b) + c_components.append(style_c) + out_components.append(style_out) + if method == "add_diff": + + def _merge_add_diff(data): + style_tuple_list = [ + (data[a], data[b], data[c], data[out]) + for a, b, c, out in zip( + a_components, + b_components, + c_components, + out_components, + ) + ] + return merge_style_add_diff_gr( + data[model_name_a], + data[model_name_b], + data[model_name_c], + data[speech_style_slider], + data[new_name], + style_tuple_list, + ) + + style_merge_btn.click( + _merge_add_diff, + inputs=set( + a_components + + b_components + + c_components + + out_components + + [ + model_name_a, + model_name_b, + model_name_c, + speech_style_slider, + new_name, + ] + ), + outputs=[info_style_merge, style], + ) + else: # weighted_sum + + def _merge_weighted_sum(data): + style_tuple_list = [ + (data[a], data[b], data[c], data[out]) + for a, b, c, out in zip( + a_components, + b_components, + c_components, + out_components, + ) + ] + return merge_style_weighted_sum_gr( + data[model_name_a], + data[model_name_b], + data[model_name_c], + data[model_a_coeff], + data[model_b_coeff], + data[model_c_coeff], + data[new_name], + style_tuple_list, + ) + + style_merge_btn.click( + _merge_weighted_sum, + inputs=set( + a_components + + b_components + + c_components + + out_components + + [ + model_name_a, + model_name_b, + model_name_c, + model_a_coeff, + model_b_coeff, + model_c_coeff, + new_name, + ] + ), + outputs=[info_style_merge, style], + ) + + with gr.Row(): + add_btn = gr.Button("スタイルを増やす") + del_btn = gr.Button("スタイルを減らす") + add_btn.click( + lambda x: x + 1, + inputs=[style_count], + outputs=[style_count], + ) + del_btn.click( + lambda x: x - 1 if x > 1 else 1, + inputs=[style_count], + outputs=[style_count], + ) + style_merge_btn = gr.Button("スタイルのマージ", variant="primary") + + info_style_merge = gr.Textbox(label="情報") - text_input = gr.TextArea( - label="テキスト", value="これはテストです。聞こえていますか?" - ) - style = gr.Dropdown( - label="スタイル", - choices=[DEFAULT_STYLE], - value=DEFAULT_STYLE, - ) - emotion_weight = gr.Slider( - minimum=0, - maximum=50, - value=1, - step=0.1, - label="スタイルの強さ", - ) - tts_button = gr.Button("音声合成", variant="primary") - audio_output = gr.Audio(label="結果") method.change( method_change, inputs=[method], @@ -1016,11 +1536,11 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ], ) - load_style_button.click( - load_styles_gr, - inputs=[model_name_a, model_name_b], - outputs=[styles_a, styles_b, style_triple_list], - ) + # load_style_button.click( + # load_styles_gr, + # inputs=[model_name_a, model_name_b], + # outputs=[styles_a, styles_b, style_triple_list], + # ) model_merge_button.click( merge_models_gr, @@ -1039,27 +1559,35 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: tempo_slider, use_slerp_instead_of_lerp, ], - outputs=[info_model_merge], + outputs=[info_model_merge, style], ) - style_merge_button.click( - merge_style_gr, - inputs=[ - model_name_a, - model_name_b, - model_name_c, - method, - speech_style_slider, - new_name, - style_triple_list, - ], - outputs=[info_style_merge, style], - ) + # style_merge_button.click( + # merge_style_gr, + # inputs=[ + # model_name_a, + # model_name_b, + # model_name_c, + # method, + # speech_style_slider, + # new_name, + # style_triple_list, + # ], + # outputs=[info_style_merge, style], + # ) tts_button.click( simple_tts, inputs=[new_name, text_input, style, emotion_weight], - outputs=[audio_output], + outputs=[tts_info, audio_output], ) return app + + +if __name__ == "__main__": + model_holder = TTSModelHolder( + assets_root, device="cuda" if torch.cuda.is_available() else "cpu" + ) + app = create_merge_app(model_holder) + app.launch() From 3472983050a3b4c87c683521dd289dd5904cb888 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Sun, 16 Jun 2024 16:50:47 +0900 Subject: [PATCH 5/7] Clean --- gradio_tabs/merge.py | 69 +------------------------------------------- 1 file changed, 1 insertion(+), 68 deletions(-) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index 782168d..abcf5f0 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -1,6 +1,6 @@ import json from pathlib import Path -from typing import Union, Any +from typing import Any, Union import gradio as gr import numpy as np @@ -780,67 +780,6 @@ def merge_style_add_zero_gr( ) -# def merge_style_gr( -# model_name_a: str, -# model_name_b: str, -# model_name_c: str, -# model_a_coeff: float, -# model_b_coeff: float, -# model_c_coeff: float, -# method: str, -# weight: float, -# output_name: str, -# style_tuple_list: list[tuple[str, ...]], -# ): -# if output_name == "": -# return "Error: 新しいモデル名を入力してください。", None -# assert method in [ -# "usual", -# "add_diff", -# "weighted_sum", -# "add_zero", -# ], f"Invalid method: {method}" -# if method == "usual": -# new_styles = merge_style_usual( -# model_name_a, -# model_name_b, -# weight, -# output_name, -# style_tuple_list, -# ) -# elif method == "add_diff": -# new_styles = merge_style_add_diff( -# model_name_a, -# model_name_b, -# model_name_c, -# weight, -# output_name, -# style_tuple_list, -# ) -# elif method == "weighted_sum": -# new_styles = merge_style_weighted_sum( -# model_name_a, -# model_name_b, -# model_name_c, -# model_a_coeff, -# model_b_coeff, -# model_c_coeff, -# output_name, -# style_tuple_list, -# ) -# else: # add_zero -# new_styles = merge_style_add_zero( -# model_name_a, -# model_name_b, -# weight, -# output_name, -# style_tuple_list, -# ) -# return f"Success: {output_name}のスタイルを保存しました。", gr.Dropdown( -# choices=new_styles, value=new_styles[0] -# ) - - def simple_tts( model_name: str, text: str, style: str = DEFAULT_STYLE, style_weight: float = 1.0 ): @@ -1536,12 +1475,6 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ], ) - # load_style_button.click( - # load_styles_gr, - # inputs=[model_name_a, model_name_b], - # outputs=[styles_a, styles_b, style_triple_list], - # ) - model_merge_button.click( merge_models_gr, inputs=[ From 048d0b6c4211b8f4718170bde56ae0c56b8f25d7 Mon Sep 17 00:00:00 2001 From: litagin02 Date: Sun, 16 Jun 2024 17:24:50 +0900 Subject: [PATCH 6/7] Change zero_model to null_model, docs --- README.md | 1 + colab.ipynb | 2 +- configs/config.json | 2 +- configs/config_jp_extra.json | 2 +- docs/CHANGELOG.md | 13 +++++++++ gradio_tabs/merge.py | 55 +++++++++++++++++------------------ style_bert_vits2/constants.py | 2 +- 7 files changed, 45 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 1837f24..1d558a2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ You can install via `pip install style-bert-vits2` (inference only), see [librar - [Zennの解説記事](https://zenn.dev/litagin/articles/034819a5256ff4) - [**リリースページ**](https://github.com/litagin02/Style-Bert-VITS2/releases/)、[更新履歴](/docs/CHANGELOG.md) + - 2024-06-16: Ver 2.6.0 (モデルの差分マージ・加重マージ・ヌルモデルマージの追加) - 2024-06-14: Ver 2.5.1 (利用規約をお願いへ変更したのみ) - 2024-06-02: Ver 2.5.0 (**[利用規約](/docs/TERMS_OF_USE.md)の追加**、フォルダ分けからのスタイル生成、小春音アミ・あみたろモデルの追加、インストールの高速化等) - 2024-03-16: ver 2.4.1 (**batファイルによるインストール方法の変更**) diff --git a/colab.ipynb b/colab.ipynb index 4f8d939..b617a28 100644 --- a/colab.ipynb +++ b/colab.ipynb @@ -6,7 +6,7 @@ "id": "F7aJhsgLAWvO" }, "source": [ - "# Style-Bert-VITS2 (ver 2.5.1) のGoogle Colabでの学習\n", + "# Style-Bert-VITS2 (ver 2.6.0) のGoogle Colabでの学習\n", "\n", "Google Colab上でStyle-Bert-VITS2の学習を行うことができます。\n", "\n", diff --git a/configs/config.json b/configs/config.json index a360f66..abaae81 100644 --- a/configs/config.json +++ b/configs/config.json @@ -69,5 +69,5 @@ "use_spectral_norm": false, "gin_channels": 256 }, - "version": "2.5.1" + "version": "2.6.0" } diff --git a/configs/config_jp_extra.json b/configs/config_jp_extra.json index c478b8f..b6edffb 100644 --- a/configs/config_jp_extra.json +++ b/configs/config_jp_extra.json @@ -76,5 +76,5 @@ "initial_channel": 64 } }, - "version": "2.5.1-JP-Extra" + "version": "2.6.0-JP-Extra" } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f87de27..b7bd5de 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## v2.6.0 (2024-06-16) + +### 新機能 +モデルのマージ時に、今までの `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`等のモデルも作ることができます。 + +これらのマージの活用法については各自いろいろ考えて実験してみて、面白い使い方があればぜひ共有してください。 + ## v2.5.1 (2024-06-14) ライセンスとのコンフリクトから、[利用規約](/docs/TERMS_OF_USE.md)を[開発陣からのお願いとデフォルトモデルの利用規約](/docs/TERMS_OF_USE.md)に変更しました。 diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index abcf5f0..a73937e 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -228,7 +228,7 @@ def merge_style_weighted_sum( return list(new_style2id.keys()) -def merge_style_add_zero( +def merge_style_add_null( model_name_a: str, model_name_b: str, weight: float, @@ -535,7 +535,7 @@ def merge_models_weighted_sum( return merged_model_path -def merge_models_add_zero( +def merge_models_add_null( model_path_a: str, model_path_b: str, voice_weight: float, @@ -567,7 +567,7 @@ def merge_models_add_zero( save_file(merged_model_weight, merged_model_path) info = { - "method": "add_zero", + "method": "add_null", "model_a": model_path_a, "model_b": model_path_b, "voice_weight": voice_weight, @@ -628,7 +628,7 @@ def merge_models_gr( "usual", "add_diff", "weighted_sum", - "add_zero", + "add_null", ], f"Invalid method: {method}" model_a_name = Path(model_path_a).parent.name model_b_name = Path(model_path_b).parent.name @@ -671,10 +671,10 @@ def merge_models_gr( model_c_coeff, output_name, ) - else: # add_zero + else: # add_null if output_name in [model_a_name, model_b_name]: return "Error: マージ元のモデル名と同じ名前は使用できません。", None - merged_model_path = merge_models_add_zero( + merged_model_path = merge_models_add_null( model_path_a, model_path_b, voice_weight, @@ -759,7 +759,7 @@ def merge_style_weighted_sum_gr( ) -def merge_style_add_zero_gr( +def merge_style_add_null_gr( model_name_a: str, model_name_b: str, weight: float, @@ -768,7 +768,7 @@ def merge_style_add_zero_gr( ): if output_name == "": return "Error: 新しいモデル名を入力してください。", None - new_styles = merge_style_add_zero( + new_styles = merge_style_add_null( model_name_a, model_name_b, weight, @@ -852,9 +852,9 @@ initial_md = """ - 例えば、Bが「Cと同じ人だけど囁いているモデル」とすると、`B - C`は「囁きを表すベクトル」だと思えるので、それをAに足すことで、Aの声のままで囁き声を出すモデルができたりする - 他にも活用例はいろいろありそう - 重み付き和 `new = a * A + b * B + c * C`: AとBとCのモデルを指定して、各モデルの係数を指定して混ぜる - - 例えば`new = A - B` としておくと、結果としてできたモデルを別のモデルと「ゼロモデルの加算」で使うことで、差分マージが実現できる + - 例えば`new = A - B` としておくと、結果としてできたモデルを別のモデルと「ヌルモデルの加算」で使うことで、差分マージが実現できる - 他にも何らかの活用法があるかもしれない -- ゼロモデルの加算 `new = A + weight * B`: AとBのモデルを指定して、Bのモデルに要素ごとに比率をかけたものをAに足す +- ヌルモデルの加算 `new = A + weight * B`: AとBのモデルを指定して、Bのモデルに要素ごとに比率をかけたものをAに足す - Bのモデルは重み付き和などで `C - D` などとして作っている場合を想定している - 他にも何らかの活用法があるかもしれない @@ -922,18 +922,20 @@ new_model = a * A + b * B + c * C ## TIPS - A, B, C が全て通常モデルで、通常モデルを作りたい場合は、`a + b + c = 1`となるようにするのがよいと思います。 -- `a + b + c = 0` とすると(たとえば `A - B`)、話者性を持たないゼロモデルを作ることができ、「ゼロモデルとの和」で結果を使うことが出来ます(差分マージの材料などに) +- `a + b + c = 0` とすると(たとえば `A - B`)、話者性を持たないヌルモデルを作ることができ、「ヌルモデルとの和」で結果を使うことが出来ます(差分マージの材料などに) - 他にも、`a = 0.5, b = c = 0`などでモデルAを謎に小さくしたり大きくしたり負にしたりできるので、実験に使ってください。 """ -add_zero_md = """ -「ゼロモデル」を、いくつかのモデルの加重和であってその係数の和が0であるようなものとします(例えば `C - D` など)。 +add_null_md = """ +「ヌルモデル」を、いくつかのモデルの加重和であってその係数の和が0であるようなものとします(例えば `C - D` など)。 -そうして作ったゼロモデルBと通常モデルAに対して、`weight` を下の各スライダーで定める数値とすると、各要素ごとに、 +そうして作ったヌルモデルBと通常モデルAに対して、`weight` を下の各スライダーで定める数値とすると、各要素ごとに、 ``` new_model = A + weight * B ``` としてマージされます。 + +実際にはヌルモデルでないBに対しても使えますが、その場合はおそらく音声が正常に生成されないモデルができる気がします。が、もしかしたら何かに使えるかもしれません。 """ tts_md = f""" @@ -948,7 +950,7 @@ def method_change(x: str): "usual", "add_diff", "weighted_sum", - "add_zero", + "add_null", ], f"Invalid method: {x}" # model_desc, c_col, model_a_coeff, model_b_coeff, model_c_coeff, weight_row, use_slerp_instead_of_lerp if x == "usual": @@ -971,9 +973,9 @@ def method_change(x: str): gr.Row(visible=True), gr.Checkbox(visible=False), ) - elif x == "add_zero": + elif x == "add_null": return ( - gr.Markdown(add_zero_md), + gr.Markdown(add_null_md), gr.Column(visible=False), gr.Number(visible=False), gr.Number(visible=False), @@ -1011,7 +1013,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown( - "2つのStyle-Bert-VITS2モデルから、声質・話し方・話す速さを取り替えたり混ぜたりできます。" + "複数のStyle-Bert-VITS2モデルから、声質・話し方・話す速さを取り替えたり混ぜたり引いたりして新しいモデルを作成できます。" ) with gr.Accordion(label="使い方", open=False): gr.Markdown(initial_md) @@ -1021,7 +1023,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ("通常マージ", "usual"), ("差分マージ", "add_diff"), ("加重和", "weighted_sum"), - ("ゼロモデルマージ", "add_zero"), + ("ヌルモデルマージ", "add_null"), ], value="usual", ) @@ -1177,14 +1179,11 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: def render_style( style_count, style_a_list, style_b_list, style_c_list, method ): - print( - f"{style_count=}, {method=}, {style_a_list=}, {style_b_list=}, {style_c_list=}" - ) a_components = [] b_components = [] c_components = [] out_components = [] - if method in ["usual", "add_zero"]: + if method in ["usual", "add_null"]: for i in range(style_count): with gr.Row(): style_a = gr.Dropdown( @@ -1252,17 +1251,17 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ), outputs=[info_style_merge, style], ) - else: # add_zero + else: # add_null - def _merge_add_zero(data): - print("Method is add_zero") + def _merge_add_null(data): + print("Method is add_null") style_tuple_list = [ (data[a], data[b], data[out]) for a, b, out in zip( a_components, b_components, out_components ) ] - return merge_style_add_zero_gr( + return merge_style_add_null_gr( data[model_name_a], data[model_name_b], data[speech_style_slider], @@ -1271,7 +1270,7 @@ def create_merge_app(model_holder: TTSModelHolder) -> gr.Blocks: ) style_merge_btn.click( - _merge_add_zero, + _merge_add_null, inputs=set( a_components + b_components diff --git a/style_bert_vits2/constants.py b/style_bert_vits2/constants.py index 15c2611..c0df22b 100644 --- a/style_bert_vits2/constants.py +++ b/style_bert_vits2/constants.py @@ -4,7 +4,7 @@ from style_bert_vits2.utils.strenum import StrEnum # Style-Bert-VITS2 のバージョン -VERSION = "2.5.1" +VERSION = "2.6.0" # Style-Bert-VITS2 のベースディレクトリ BASE_DIR = Path(__file__).parent.parent From 7e7ac679063e9b01bdc1a37d75fb14a95608d33d Mon Sep 17 00:00:00 2001 From: litagin02 Date: Sun, 16 Jun 2024 17:56:32 +0900 Subject: [PATCH 7/7] Feat: each tab executables --- Dataset.bat | 11 +++++++++++ Inference.bat | 11 +++++++++++ Merge.bat | 11 +++++++++++ StyleVectors.bat | 11 +++++++++++ Train.bat | 11 +++++++++++ docs/CHANGELOG.md | 6 ++++++ gradio_tabs/dataset.py | 8 +++++++- gradio_tabs/inference.py | 12 ++++++++++++ gradio_tabs/merge.py | 8 +++++++- gradio_tabs/style_vectors.py | 10 ++++++++++ gradio_tabs/train.py | 8 +++++++- 11 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 Dataset.bat create mode 100644 Inference.bat create mode 100644 Merge.bat create mode 100644 StyleVectors.bat create mode 100644 Train.bat diff --git a/Dataset.bat b/Dataset.bat new file mode 100644 index 0000000..0dfc3ef --- /dev/null +++ b/Dataset.bat @@ -0,0 +1,11 @@ +chcp 65001 > NUL +@echo off + +pushd %~dp0 +echo Running gradio_tabs/dataset.py... +venv\Scripts\python gradio_tabs/dataset.py + +if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) + +popd +pause \ No newline at end of file diff --git a/Inference.bat b/Inference.bat new file mode 100644 index 0000000..833bd8e --- /dev/null +++ b/Inference.bat @@ -0,0 +1,11 @@ +chcp 65001 > NUL +@echo off + +pushd %~dp0 +echo Running gradio_tabs/inference.py... +venv\Scripts\python gradio_tabs/inference.py + +if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) + +popd +pause \ No newline at end of file diff --git a/Merge.bat b/Merge.bat new file mode 100644 index 0000000..523ef47 --- /dev/null +++ b/Merge.bat @@ -0,0 +1,11 @@ +chcp 65001 > NUL +@echo off + +pushd %~dp0 +echo Running gradio_tabs/merge.py... +venv\Scripts\python gradio_tabs/merge.py + +if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) + +popd +pause \ No newline at end of file diff --git a/StyleVectors.bat b/StyleVectors.bat new file mode 100644 index 0000000..6f6a4c3 --- /dev/null +++ b/StyleVectors.bat @@ -0,0 +1,11 @@ +chcp 65001 > NUL +@echo off + +pushd %~dp0 +echo Running gradio_tabs/style_vectors.py... +venv\Scripts\python gradio_tabs/style_vectors.py + +if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) + +popd +pause \ No newline at end of file diff --git a/Train.bat b/Train.bat new file mode 100644 index 0000000..f7adce3 --- /dev/null +++ b/Train.bat @@ -0,0 +1,11 @@ +chcp 65001 > NUL +@echo off + +pushd %~dp0 +echo Running gradio_tabs/train.py... +venv\Scripts\python gradio_tabs/train.py + +if %errorlevel% neq 0 ( pause & popd & exit /b %errorlevel% ) + +popd +pause \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b7bd5de..4b1cd16 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,12 @@ これらのマージの活用法については各自いろいろ考えて実験してみて、面白い使い方があればぜひ共有してください。 +囁きについて実験的に作ったヌルモデルを[こちら](https://huggingface.co/litagin/sbv2_null_models)に置いています。これをヌルモデルマージで使うことで、任意のモデルを囁きモデルにある程度は変換できます。 + +### 改善? + +- WebUIの`App.bat`の起動が少し重いので、それぞれの機能を分割した`Dataset.bat`, `Inference.bat`, `Merge.bat`, `StyleVectors.bat`, `Train.bat`を追加 (今までの`App.bat`もこれまで通り使えます) + ## v2.5.1 (2024-06-14) ライセンスとのコンフリクトから、[利用規約](/docs/TERMS_OF_USE.md)を[開発陣からのお願いとデフォルトモデルの利用規約](/docs/TERMS_OF_USE.md)に変更しました。 diff --git a/gradio_tabs/dataset.py b/gradio_tabs/dataset.py index a905caf..5404595 100644 --- a/gradio_tabs/dataset.py +++ b/gradio_tabs/dataset.py @@ -1,5 +1,6 @@ import gradio as gr +from style_bert_vits2.constants import GRADIO_THEME from style_bert_vits2.logging import logger from style_bert_vits2.utils.subprocess import run_script_with_log @@ -109,7 +110,7 @@ Style-Bert-VITS2の学習用データセットを作成するためのツール def create_dataset_app() -> gr.Blocks: - with gr.Blocks() as app: + with gr.Blocks(theme=GRADIO_THEME) as app: gr.Markdown( "**既に1ファイル2-12秒程度の音声ファイル集とその書き起こしデータがある場合は、このタブは使用せずに学習できます。**" ) @@ -257,3 +258,8 @@ def create_dataset_app() -> gr.Blocks: ) return app + + +if __name__ == "__main__": + app = create_dataset_app() + app.launch(inbrowser=True) diff --git a/gradio_tabs/inference.py b/gradio_tabs/inference.py index 5935786..53393be 100644 --- a/gradio_tabs/inference.py +++ b/gradio_tabs/inference.py @@ -523,3 +523,15 @@ def create_inference_app(model_holder: TTSModelHolder) -> gr.Blocks: ) return app + + +if __name__ == "__main__": + from config import get_path_config + import torch + + path_config = get_path_config() + assets_root = path_config.assets_root + device = "cuda" if torch.cuda.is_available() else "cpu" + model_holder = TTSModelHolder(assets_root, device) + app = create_inference_app(model_holder) + app.launch(inbrowser=True) diff --git a/gradio_tabs/merge.py b/gradio_tabs/merge.py index a73937e..b4c9204 100644 --- a/gradio_tabs/merge.py +++ b/gradio_tabs/merge.py @@ -910,6 +910,8 @@ add_diff_md = """ new_model = A + weight * (B - C) ``` としてマージされます。 + +通常のマージと違い、**重みを1にしてもAの要素はそのまま保たれます**。 """ weighted_sum_md = """ @@ -935,7 +937,11 @@ new_model = A + weight * B ``` としてマージされます。 +通常のマージと違い、**重みを1にしてもAの要素はそのまま保たれます**。 + 実際にはヌルモデルでないBに対しても使えますが、その場合はおそらく音声が正常に生成されないモデルができる気がします。が、もしかしたら何かに使えるかもしれません。 + +囁きについて実験的に作ったヌルモデルを[こちら](https://huggingface.co/litagin/sbv2_null_models)に置いています。これを `B` に使うことで、任意のモデルを囁きモデルにある程度は変換できます。 """ tts_md = f""" @@ -1522,4 +1528,4 @@ if __name__ == "__main__": assets_root, device="cuda" if torch.cuda.is_available() else "cpu" ) app = create_merge_app(model_holder) - app.launch() + app.launch(inbrowser=True) diff --git a/gradio_tabs/style_vectors.py b/gradio_tabs/style_vectors.py index 4618efb..f6ba949 100644 --- a/gradio_tabs/style_vectors.py +++ b/gradio_tabs/style_vectors.py @@ -1,3 +1,8 @@ +""" +TODO: +importが重いので、WebUI全般が重くなっている。どうにかしたい。 +""" + import json import shutil from pathlib import Path @@ -568,3 +573,8 @@ def create_style_vectors_app(): ) return app + + +if __name__ == "__main__": + app = create_style_vectors_app() + app.launch(inbrowser=True) diff --git a/gradio_tabs/train.py b/gradio_tabs/train.py index e77b761..fbf8522 100644 --- a/gradio_tabs/train.py +++ b/gradio_tabs/train.py @@ -14,6 +14,7 @@ import gradio as gr import yaml from config import get_path_config +from style_bert_vits2.constants import GRADIO_THEME from style_bert_vits2.logging import logger from style_bert_vits2.utils.stdout_wrapper import SAFE_STDOUT from style_bert_vits2.utils.subprocess import run_script_with_log, second_elem_of @@ -484,7 +485,7 @@ english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you? def create_train_app(): - with gr.Blocks().queue() as app: + with gr.Blocks(theme=GRADIO_THEME).queue() as app: gr.Markdown(change_log_md) with gr.Accordion("使い方", open=False): gr.Markdown(how_to_md) @@ -840,3 +841,8 @@ def create_train_app(): ) return app + + +if __name__ == "__main__": + app = create_train_app() + app.launch(inbrowser=True)