Ver 2.7.0 maybe

This commit is contained in:
litagin02
2025-08-24 11:27:27 +09:00
parent 4859d64e9f
commit c51c80fe1a
24 changed files with 173 additions and 33 deletions

View File

@@ -15,6 +15,7 @@ You can install via `pip install style-bert-vits2` (inference only), see [librar
- [Zennの解説記事](https://zenn.dev/litagin/articles/034819a5256ff4) - [Zennの解説記事](https://zenn.dev/litagin/articles/034819a5256ff4)
- [**リリースページ**](https://github.com/litagin02/Style-Bert-VITS2/releases/)、[更新履歴](/docs/CHANGELOG.md) - [**リリースページ**](https://github.com/litagin02/Style-Bert-VITS2/releases/)、[更新履歴](/docs/CHANGELOG.md)
- 2025-08-24: Ver 2.7.0: 外部ライブラリ [Aivis Project](https://aivis-project.com/) 等との連携のため、ONNX変換のGUI追加、また音声認識モデルとして `litagin/anime-whisper` の追加等
- 2024-09-09: Ver 2.6.1: Google colabでうまく学習できない等のバグ修正のみ - 2024-09-09: Ver 2.6.1: Google colabでうまく学習できない等のバグ修正のみ
- 2024-06-16: Ver 2.6.0 (モデルの差分マージ・加重マージ・ヌルモデルマージの追加、使い道については[この記事](https://zenn.dev/litagin/articles/1297b1dc7bdc79)参照) - 2024-06-16: Ver 2.6.0 (モデルの差分マージ・加重マージ・ヌルモデルマージの追加、使い道については[この記事](https://zenn.dev/litagin/articles/1297b1dc7bdc79)参照)
- 2024-06-14: Ver 2.5.1 (利用規約をお願いへ変更したのみ) - 2024-06-14: Ver 2.5.1 (利用規約をお願いへ変更したのみ)
@@ -155,6 +156,10 @@ API仕様は起動後に`/docs`にて確認ください。
2つのモデルを、「声質」「声の高さ」「感情表現」「テンポ」の4点で混ぜ合わせて、新しいモデルを作ったり、また「あるモデルに、別の2つのモデルの差分を足す」等の操作ができます。 2つのモデルを、「声質」「声の高さ」「感情表現」「テンポ」の4点で混ぜ合わせて、新しいモデルを作ったり、また「あるモデルに、別の2つのモデルの差分を足す」等の操作ができます。
`App.bat`をダブルクリックか`python app.py`して開くWebUIの「マージ」タブから、2つのモデルを選択してマージすることができます。または`Merge.bat`をダブルクリックでもその単独タブが開きます。 `App.bat`をダブルクリックか`python app.py`して開くWebUIの「マージ」タブから、2つのモデルを選択してマージすることができます。または`Merge.bat`をダブルクリックでもその単独タブが開きます。
### ONNX変換
タブの「ONNX変換」から、学習済みsafetensorsファイルをONNX形式に変換することができます。これは外部ライブラリ等でONNX形式ファイルが必要な場合に使えます。例えば [Aivis Project](https://aivis-project.com/) では [AIVM Generator](https://aivm-generator.aivis-project.com/) を使って、safetensorsファイルとONNXファイルからAivis Speech用のモデルを作成できます。
### 自然性評価 ### 自然性評価
学習結果のうちどのステップ数がいいかの「一つの」指標として、[SpeechMOS](https://github.com/tarepan/SpeechMOS) を使うスクリプトを用意しています: 学習結果のうちどのステップ数がいいかの「一つの」指標として、[SpeechMOS](https://github.com/tarepan/SpeechMOS) を使うスクリプトを用意しています:

8
app.py
View File

@@ -5,6 +5,7 @@ import gradio as gr
import torch import torch
from config import get_path_config from config import get_path_config
from gradio_tabs.convert_onnx import create_onnx_app
from gradio_tabs.dataset import create_dataset_app from gradio_tabs.dataset import create_dataset_app
from gradio_tabs.inference import create_inference_app from gradio_tabs.inference import create_inference_app
from gradio_tabs.merge import create_merge_app from gradio_tabs.merge import create_merge_app
@@ -42,7 +43,10 @@ if device == "cuda" and not torch.cuda.is_available():
path_config = get_path_config() path_config = get_path_config()
model_holder = TTSModelHolder( model_holder = TTSModelHolder(
Path(path_config.assets_root), device, torch_device_to_onnx_providers(device) Path(path_config.assets_root),
device,
torch_device_to_onnx_providers(device),
ignore_onnx=True,
) )
with gr.Blocks(theme=GRADIO_THEME) as app: with gr.Blocks(theme=GRADIO_THEME) as app:
@@ -58,6 +62,8 @@ with gr.Blocks(theme=GRADIO_THEME) as app:
create_style_vectors_app() create_style_vectors_app()
with gr.Tab("マージ"): with gr.Tab("マージ"):
create_merge_app(model_holder=model_holder) create_merge_app(model_holder=model_holder)
with gr.Tab("ONNX変換"):
create_onnx_app(model_holder=model_holder)
app.launch( app.launch(
server_name=args.host, server_name=args.host,

View File

@@ -90,6 +90,7 @@ if __name__ == "__main__":
executor.map(process_line, zip(lines, add_blank)), executor.map(process_line, zip(lines, add_blank)),
total=len(lines), total=len(lines),
file=SAFE_STDOUT, file=SAFE_STDOUT,
dynamic_ncols=True,
) )
) )

View File

@@ -425,6 +425,16 @@
"# 学習結果を試す・マージ・スタイル分けはこちらから\n", "# 学習結果を試す・マージ・スタイル分けはこちらから\n",
"!python app.py --share" "!python app.py --share"
] ]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ONNX変換は、変換したいsafetensorsファイルを指定してこのセルを実行してください。\n",
"!python convert_onnx.py --model \"Data/your_model/your_model_e100_s10000.safetensors\""
]
} }
], ],
"metadata": { "metadata": {

View File

@@ -68,7 +68,7 @@ class TextAudioSpeakerLoader(torch.utils.data.Dataset):
skipped = 0 skipped = 0
logger.info("Init dataset...") logger.info("Init dataset...")
for _id, spk, language, text, phones, tone, word2ph in tqdm( for _id, spk, language, text, phones, tone, word2ph in tqdm(
self.audiopaths_sid_text, file=sys.stdout self.audiopaths_sid_text, file=sys.stdout, dynamic_ncols=True
): ):
audiopath = f"{_id}" audiopath = f"{_id}"
# if self.min_text_len <= len(phones) and len(phones) <= self.max_text_len: # if self.min_text_len <= len(phones) and len(phones) <= self.max_text_len:

View File

@@ -1,5 +1,11 @@
# Changelog # Changelog
## v2.7.0 (2025-08-24)
- [AivisProject](https://aivis-project.com/) 等との連携のため、ONNX変換のGUI追加 (Gradioタブの一つとして)
- また音声認識モデルとして `litagin/anime-whisper` の追加
- その他軽微な修正等
## v2.6.1 (2024-09-09) ## v2.6.1 (2024-09-09)
- Google colabで、torchのバージョン由来でエラーが発生する不具合の修正たぶん - Google colabで、torchのバージョン由来でエラーが発生する不具合の修正たぶん

101
gradio_tabs/convert_onnx.py Normal file
View File

@@ -0,0 +1,101 @@
from pathlib import Path
import gradio as gr
from style_bert_vits2.constants import GRADIO_THEME
from style_bert_vits2.logging import logger
from style_bert_vits2.tts_model import NullModelParam, TTSModelHolder
from style_bert_vits2.utils.subprocess import run_script_with_log
def call_convert_onnx(
model: str,
):
if model == "":
return "Error: モデル名を入力してください。"
logger.info("Start converting model to onnx...")
cmd = [
"convert_onnx.py",
"--model",
model,
]
success, message = run_script_with_log(cmd, ignore_warning=True)
if not success:
return f"Error: {message}"
return "ONNX変換が完了しました。"
initial_md = """
safetensors形式のモデルをONNX形式に変換します。
このONNXモデルは、[AIVM Generator](https://aivm-generator.aivis-project.com/) 等でさらにAIVM形式・AIVMX形式に変換して[AivisSpeech](https://aivis-project.com/)で利用できます。
変換には5分以上ほどの時間がかかります。進捗状況はターミナルのログを参照してください。
"""
def create_onnx_app(model_holder: TTSModelHolder) -> gr.Blocks:
def get_model_files(model_name: str):
return [str(f) for f in model_holder.model_files_dict[model_name]]
model_names = model_holder.model_names
if len(model_names) == 0:
logger.error(
f"モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。"
)
with gr.Blocks() as app:
gr.Markdown(
f"Error: モデルが見つかりませんでした。{model_holder.root_dir}にモデルを置いてください。"
)
return app
initial_id = 0
initial_pth_files = get_model_files(model_names[initial_id])
with gr.Blocks(theme=GRADIO_THEME) as app:
gr.Markdown(initial_md)
with gr.Row():
with gr.Column():
model_name = gr.Dropdown(
label="モデル一覧",
choices=model_names,
value=model_names[initial_id],
)
model_path = gr.Dropdown(
label="モデルファイル",
choices=initial_pth_files,
value=initial_pth_files[0],
)
refresh_button = gr.Button("更新")
convert_button = gr.Button("ONNX形式に変換", variant="primary")
info = gr.Textbox(label="情報")
model_name.change(
model_holder.update_model_files_for_gradio,
inputs=[model_name],
outputs=[model_path],
)
def refresh_fn() -> tuple[gr.Dropdown, gr.Dropdown]:
names, files, _ = model_holder.update_model_names_for_gradio()
return names, files
refresh_button.click(
refresh_fn,
outputs=[model_name, model_path],
)
convert_button.click(
call_convert_onnx,
inputs=[model_path],
outputs=[info],
)
return app
if __name__ == "__main__":
from config import get_path_config
path_config = get_path_config()
assets_root = path_config.assets_root
model_holder = TTSModelHolder(assets_root, "cpu", "", ignore_onnx=True)
app = create_onnx_app(model_holder)
app.launch(inbrowser=True)

View File

@@ -51,6 +51,11 @@ def do_transcribe(
): ):
if model_name == "": if model_name == "":
return "Error: モデル名を入力してください。" return "Error: モデル名を入力してください。"
if hf_repo_id == "litagin/anime-whisper":
logger.info(
"Since litagin/anime-whisper does not support initial prompt, it will be ignored."
)
initial_prompt = ""
cmd = [ cmd = [
"transcribe.py", "transcribe.py",
@@ -159,34 +164,30 @@ def create_dataset_app() -> gr.Blocks:
result1 = gr.Textbox(label="結果") result1 = gr.Textbox(label="結果")
with gr.Row(): with gr.Row():
with gr.Column(): with gr.Column():
use_hf_whisper = gr.Checkbox(
label="HuggingFaceのWhisperを使う速度が速いがVRAMを多く使う",
value=False,
)
whisper_model = gr.Dropdown( whisper_model = gr.Dropdown(
[ [
"tiny",
"base",
"small",
"medium",
"large", "large",
"large-v2", "large-v2",
"large-v3", "large-v3",
], ],
label="Whisperモデル", label="Whisperモデル",
value="large-v3", value="large-v3",
) visible=True,
use_hf_whisper = gr.Checkbox(
label="HuggingFaceのWhisperを使う速度が速いがVRAMを多く使う",
value=False,
) )
hf_repo_id = gr.Dropdown( hf_repo_id = gr.Dropdown(
[ [
"openai/whisper-large-v3-turbo", "openai/whisper-large-v3-turbo",
"openai/whisper-large-v3", "openai/whisper-large-v3",
"openai/whisper-large-v2", "openai/whisper-large-v2",
"kotoba-tech/kotoba-whisper-v1.1",
"kotoba-tech/kotoba-whisper-v2.1", "kotoba-tech/kotoba-whisper-v2.1",
"litagin/anime-whisper", "litagin/anime-whisper",
], ],
label="HuggingFaceのWhisper repo_id", label="HuggingFaceのWhisper repo_id",
value="openai/whisper-large-v3", value="openai/whisper-large-v3-turbo",
visible=False, visible=False,
) )
compute_type = gr.Dropdown( compute_type = gr.Dropdown(
@@ -258,12 +259,13 @@ def create_dataset_app() -> gr.Blocks:
) )
use_hf_whisper.change( use_hf_whisper.change(
lambda x: ( lambda x: (
gr.update(visible=not x),
gr.update(visible=x), gr.update(visible=x),
gr.update(visible=x), gr.update(visible=x),
gr.update(visible=not x), gr.update(visible=not x),
), ),
inputs=[use_hf_whisper], inputs=[use_hf_whisper],
outputs=[hf_repo_id, batch_size, compute_type], outputs=[whisper_model, hf_repo_id, batch_size, compute_type],
) )
return app return app

View File

@@ -322,6 +322,7 @@ def save_style_vectors_by_dirs(model_name: str, audio_dir_str: str):
total=len(audio_files), total=len(audio_files),
file=SAFE_STDOUT, file=SAFE_STDOUT,
desc="Generating style vectors", desc="Generating style vectors",
dynamic_ncols=True,
) )
) )

View File

@@ -98,7 +98,7 @@ def preprocess(
transcription_path.open("r", encoding="utf-8") as trans_file, transcription_path.open("r", encoding="utf-8") as trans_file,
cleaned_path.open("w", encoding="utf-8") as out_file, cleaned_path.open("w", encoding="utf-8") as out_file,
): ):
for line in tqdm(trans_file, file=SAFE_STDOUT, total=total_lines): for line in tqdm(trans_file, file=SAFE_STDOUT, total=total_lines, dynamic_ncols=True):
try: try:
processed_line = process_line( processed_line = process_line(
line, line,

View File

@@ -27,7 +27,7 @@ pypinyin
pyworld-prebuilt pyworld-prebuilt
# stable_ts # stable_ts
# tensorboard # tensorboard
torch torch<2.4
torchaudio torchaudio<2.4
transformers transformers
umap-learn umap-learn

View File

@@ -27,7 +27,7 @@ pypinyin
pyworld-prebuilt pyworld-prebuilt
stable_ts stable_ts
tensorboard tensorboard
torch torch<2.4
torchaudio torchaudio<2.4
transformers transformers
umap-learn umap-learn

View File

@@ -140,7 +140,7 @@ if __name__ == "__main__":
for file in original_files for file in original_files
] ]
for future in tqdm( for future in tqdm(
as_completed(futures), total=len(original_files), file=SAFE_STDOUT as_completed(futures), total=len(original_files), file=SAFE_STDOUT, dynamic_ncols=True
): ):
pass pass

View File

@@ -202,7 +202,9 @@ if args.preload_onnx_bert:
) )
onnx_bert_models.load_tokenizer(Languages.JP) onnx_bert_models.load_tokenizer(Languages.JP)
model_holder = TTSModelHolder(model_dir, device, torch_device_to_onnx_providers(device)) model_holder = TTSModelHolder(
model_dir, device, torch_device_to_onnx_providers(device), ignore_onnx=True
)
if len(model_holder.model_names) == 0: if len(model_holder.model_names) == 0:
logger.error(f"Models not found in {model_dir}.") logger.error(f"Models not found in {model_dir}.")
sys.exit(1) sys.exit(1)

View File

@@ -230,7 +230,7 @@ if __name__ == "__main__":
for t in threads: for t in threads:
t.start() t.start()
pbar = tqdm(total=len(audio_files), file=SAFE_STDOUT) pbar = tqdm(total=len(audio_files), file=SAFE_STDOUT, dynamic_ncols=True)
for file in audio_files: for file in audio_files:
q.put(file) q.put(file)

View File

@@ -70,7 +70,7 @@ safetensors_files = list(safetensors_files)
logger.info(f"There are {len(safetensors_files)} models.") logger.info(f"There are {len(safetensors_files)} models.")
for model_file in tqdm(safetensors_files): for model_file in tqdm(safetensors_files, dynamic_ncols=True):
# `test_e10_s1000.safetensors`` -> 1000を取り出す # `test_e10_s1000.safetensors`` -> 1000を取り出す
match = re.search(r"_s(\d+)\.safetensors$", model_file.name) match = re.search(r"_s(\d+)\.safetensors$", model_file.name)
if match: if match:

View File

@@ -11,10 +11,8 @@ import torch
from numpy.typing import NDArray from numpy.typing import NDArray
from style_bert_vits2.logging import logger from style_bert_vits2.logging import logger
from style_bert_vits2.models.utils import ( from style_bert_vits2.models.utils import checkpoints # type: ignore # noqa: F401
checkpoints, # type: ignore # noqa: F401 from style_bert_vits2.models.utils import safetensors # type: ignore # noqa: F401
safetensors, # type: ignore # noqa: F401
)
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@@ -121,7 +121,7 @@ def normalize_text(text: str) -> str:
# 結合文字の濁点・半濁点を削除 # 結合文字の濁点・半濁点を削除
# 通常の「ば」等はそのままのこされる、「あ゛」は上で「あ゙」になりここで「あ」になる # 通常の「ば」等はそのままのこされる、「あ゛」は上で「あ゙」になりここで「あ」になる
res = res.replace("\u3099", "") # 結合文字の濁点を削除、る゙ → る res = res.replace("\u3099", "") # 結合文字の濁点を削除、る゙ → る
res = res.replace("\u309A", "") # 結合文字の半濁点を削除、な゚ → な res = res.replace("\u309a", "") # 結合文字の半濁点を削除、な゚ → な
return res return res

View File

@@ -41,7 +41,7 @@ def run_script_with_log(
def second_elem_of( def second_elem_of(
original_function: Callable[..., tuple[Any, Any]] original_function: Callable[..., tuple[Any, Any]],
) -> Callable[..., Any]: ) -> Callable[..., Any]:
""" """
与えられた関数をラップし、その戻り値の 2 番目の要素のみを返す関数を生成する。 与えられた関数をラップし、その戻り値の 2 番目の要素のみを返す関数を生成する。

View File

@@ -80,6 +80,7 @@ if __name__ == "__main__":
executor.map(process_line, training_lines), executor.map(process_line, training_lines),
total=len(training_lines), total=len(training_lines),
file=SAFE_STDOUT, file=SAFE_STDOUT,
dynamic_ncols=True,
) )
) )
ok_training_lines = [line for line, error in training_results if error is None] ok_training_lines = [line for line, error in training_results if error is None]
@@ -102,6 +103,7 @@ if __name__ == "__main__":
executor.map(process_line, val_lines), executor.map(process_line, val_lines),
total=len(val_lines), total=len(val_lines),
file=SAFE_STDOUT, file=SAFE_STDOUT,
dynamic_ncols=True,
) )
) )
ok_val_lines = [line for line, error in val_results if error is None] ok_val_lines = [line for line, error in val_results if error is None]

View File

@@ -485,6 +485,7 @@ def run():
initial=global_step, initial=global_step,
smoothing=0.05, smoothing=0.05,
file=SAFE_STDOUT, file=SAFE_STDOUT,
dynamic_ncols=True,
) )
initial_step = global_step initial_step = global_step

View File

@@ -562,6 +562,7 @@ def run():
initial=global_step, initial=global_step,
smoothing=0.05, smoothing=0.05,
file=SAFE_STDOUT, file=SAFE_STDOUT,
dynamic_ncols=True,
) )
initial_step = global_step initial_step = global_step

View File

@@ -73,8 +73,8 @@ def transcribe_files_with_hf_whisper(
max_new_tokens=128, max_new_tokens=128,
chunk_length_s=30, chunk_length_s=30,
batch_size=batch_size, batch_size=batch_size,
torch_dtype=torch.float16, torch_dtype=torch.float16 if device == "cuda" else torch.float32,
device="cuda", device=device,
trust_remote_code=True, trust_remote_code=True,
# generate_kwargs=generate_kwargs, # generate_kwargs=generate_kwargs,
) )
@@ -154,6 +154,10 @@ if __name__ == "__main__":
wav_files = [f for f in input_dir.rglob("*.wav") if f.is_file()] wav_files = [f for f in input_dir.rglob("*.wav") if f.is_file()]
wav_files = sorted(wav_files, key=lambda x: str(x)) wav_files = sorted(wav_files, key=lambda x: str(x))
logger.info(f"Found {len(wav_files)} WAV files")
if len(wav_files) == 0:
logger.warning(f"No WAV files found in {input_dir}")
sys.exit(1)
if output_file.exists(): if output_file.exists():
logger.warning(f"{output_file} exists, backing up to {output_file}.bak") logger.warning(f"{output_file} exists, backing up to {output_file}.bak")
@@ -183,7 +187,7 @@ if __name__ == "__main__":
except ValueError as e: except ValueError as e:
logger.warning(f"Failed to load model, so use `auto` compute_type: {e}") logger.warning(f"Failed to load model, so use `auto` compute_type: {e}")
model = WhisperModel(args.model, device=device) model = WhisperModel(args.model, device=device)
for wav_file in tqdm(wav_files, file=SAFE_STDOUT): for wav_file in tqdm(wav_files, file=SAFE_STDOUT, dynamic_ncols=True):
text = transcribe_with_faster_whisper( text = transcribe_with_faster_whisper(
model=model, model=model,
audio_file=wav_file, audio_file=wav_file,
@@ -198,7 +202,7 @@ if __name__ == "__main__":
else: else:
model_id = args.hf_repo_id model_id = args.hf_repo_id
logger.info(f"Loading HF Whisper model ({model_id})") logger.info(f"Loading HF Whisper model ({model_id})")
pbar = tqdm(total=len(wav_files), file=SAFE_STDOUT) pbar = tqdm(total=len(wav_files), file=SAFE_STDOUT, dynamic_ncols=True)
results = transcribe_files_with_hf_whisper( results = transcribe_files_with_hf_whisper(
audio_files=wav_files, audio_files=wav_files,
model_id=model_id, model_id=model_id,