This commit is contained in:
litagin02
2024-01-04 11:30:14 +09:00
parent b03f68ef57
commit c7571a7c39
12 changed files with 213 additions and 54 deletions

View File

@@ -87,14 +87,16 @@ model_assets
- デフォルトスタイル「Neutral」以外のスタイルを使いたい人向けです。
- `Style.bat`をダブルクリックか`python webui_style_vectors.py`するとWebUIが起動します。
- 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます。
- スタイルについての詳細は[clustering.ipynb](clustering.ipynb)を参照してください。
- 学習とは独立しているので、学習中でもできるし、学習が終わっても何度もやりなおせます(前処理は終わらせている必要があります)
- スタイルについての仕様の詳細は[clustering.ipynb](clustering.ipynb)を参照してください。
### データセット作り
- `Dataset.bat`をダブルクリックか`python webui_dataset.py`すると、音声ファイルからデータセットを作るためのWebUIが起動します。音声ファイルのみからでもこれを使って学習できます。
注意: データセットの手動修正やノイズ除去や、より高品質なデータセットを作りたい場合は[Aivis](https://github.com/tsukumijima/Aivis)や、そのデータセット部分のWindows対応版 [Aivis Dataset](https://github.com/litagin02/Aivis-Dataset) を使うのをおすすめします。
注意: データセットの手動修正やノイズ除去等、細かい修正を行いたい場合は[Aivis](https://github.com/tsukumijima/Aivis)や、そのデータセット部分のWindows対応版 [Aivis Dataset](https://github.com/litagin02/Aivis-Dataset) を使うといいかもしれません。ですがファイル数が多い場合などは、このツールで簡易的に切り出してデータセットを作るだけでも十分という気もしています。
データセットがどのようなものがいいかは各自試行錯誤中してください。
### API Server

View File

@@ -54,11 +54,23 @@
"!python initialize.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Google driveを使う方はこちらを実行してください。\n",
"\n",
"from google.colab import drive\n",
"drive.mount(\"/content/drive\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 初期設定\n",
"## 1. 初期設定(とデータセット作成)\n",
"\n",
"学習とその結果を保存するディレクトリ名を指定します。\n",
"Google driveの場合はそのまま実行、カスタマイズしたい方は変更して実行してください。"
@@ -70,12 +82,17 @@
"metadata": {},
"outputs": [],
"source": [
"# 学習に必要なファイルや途中経過が保存されるディレクトリ\n",
"dataset_root = \"/content/drive/MyDrive/Style-Bert-VITS2/Data\"\n",
"\n",
"# 学習結果(音声合成に必要なファイルたち)が保存されるディレクトリ\n",
"assets_root = \"/content/drive/MyDrive/Style-Bert-VITS2/model_assets\"\n",
"\n",
"import yaml\n",
"\n",
"\n",
"with open(\"configs/paths.yml\", \"w\", encoding=\"utf-8\") as f:\n",
"\n",
" yaml.dump({\"dataset_root\": dataset_root, \"assets_root\": assets_root}, f)"
]
},
@@ -83,25 +100,31 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Google Driveとの連携とデータの配置\n",
"### 音声ファイルからのデータセットの作成(ある人はスキップ可)\n",
"\n",
"Google driveにデータを保存する方は、次のセルを実行して、Google driveと連携します。"
"音声ファイル1ファイル2-12秒程度とその書き起こしのデータセットを持っていない方は、音声ファイルのみから以下の手順でデータセットを作成することができます。デフォルトではGoogle drive上の`Style-Bert-VITS2/inputs/`に音声ファイルwavファイル形式、1ファイルでも複数ファイルでも可を置いて、下を実行すると、データセットが作られます。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "xzbmRmVfP29m",
"outputId": "bd492fba-3cba-4002-b6d9-f144f701fce6"
},
"metadata": {},
"outputs": [],
"source": [
"from google.colab import drive\n",
"drive.mount(\"/content/drive\")"
"# 元となる音声ファイルwav形式を入れるディレクトリ\n",
"input_dir = \"/content/drive/MyDrive/Style-Bert-VITS2/inputs\"\n",
"# モデル名(話者名)を入力\n",
"model_name = \"your_model_name\"\n",
"\n",
"!python slice.py -i {input_dir} -o {dataset_root}/{model_name}/raw\n",
"!python transcribe.py -i {dataset_root}/{model_name}/raw -o {dataset_root}/{model_name}/esd.list --speaker_name {model_name} --compute_type float16"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. データの配置"
]
},
{
@@ -186,7 +209,7 @@
"# 上でつけたフォルダの名前`Data/{model_name}/`\n",
"model_name = \"your_model_name\"\n",
"\n",
"# 学習のバッチサイズ。4ぐらいが最適か。VRAMのはみ出具合に応じて調整してください。\n",
"# 学習のバッチサイズ。VRAMのはみ出具合に応じて調整してください。\n",
"batch_size = 4\n",
"\n",
"# 学習のエポック数(データセットを合計何周するか)。\n",

View File

@@ -66,5 +66,5 @@
"use_spectral_norm": false,
"gin_channels": 256
},
"version": "1.2"
"version": "1.3"
}

View File

@@ -1,9 +1,20 @@
# Changelog
## v1.3
- `Dataset.bat`の音声スライスと書き起こしをよりカスタマイズできるように秒数指定や書き起こしのWhisperモデル指定や言語指定等
- `Style.bat`のスタイル作成で、新しい方法DBSCANを追加こちらのほうがスタイル数を指定できない代わりに特徴がよく出るかもしれません
- クラウド実行等の際にパスの指定をこちらでできるように、パスの設定を`configs/paths.yml`にまとめましたcolabの[ノートブック](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb)もそれに伴って変えたので新しいものを使ってください)。デフォルトは`dataset_root: Data``assets_root: model_assets`です。クラウド等でやる方はここを変更してください。
- どのステップ数の出力がよいかの「一つの」指標として [SpeechMOS](https://github.com/tarepan/SpeechMOS) を使うスクリプトを追加:
```bash
python speech_mos.py -m <model_name> [-o <output_csv_file>]
```
ステップごとの自然性評価が表示されるはず。読み上げさせたい文章を変えたかったら中のファイルを弄って各自調整してください。またあくまで目安のひとつなので、実際に読み上げさせて選別するのが一番だと思います。
## v1.2 (2023-12-31)
- グラボがないユーザーでの音声合成をサポート、`Install-Style-Bert-VITS2-CPU.bat`でインストール。
- Google Colabでの学習をサポート、[ノートブック](../colab.ipynb)を追加
- Google Colabでの学習をサポート、[ノートブック](http://colab.research.google.com/github/litagin02/Style-Bert-VITS2/blob/master/colab.ipynb)を追加
- 音声合成のAPIサーバーを追加、`python server_fastapi.py`で起動します。API仕様は起動後に`/docs`にて確認ください。( @darai0512 様によるPRです、ありがとうございます
- 学習時に自動的にデフォルトスタイル Neutral を生成するように。特にスタイル指定が必要のない方は、学習したらそのまま音声合成を試せます。これまで通りスタイルを自分で作ることもできます。
- マージ機能の新規追加: `Merge.bat`, `webui_merge.py`

56
docs/CLI.md Normal file
View File

@@ -0,0 +1,56 @@
# CLI
## Dataset
`Dataset.bat` webui (`python webui_dataset.py`) consists of **slice audio** and **transcribe wavs**.
### Slice audio
```bash
python slice.py -i <input_dir> -o <output_dir> -m <min_sec> -M <max_sec>
```
Required:
- `input_dir`: Path to the directory containing the audio files to slice.
- `output_dir`: Path to the directory where the sliced audio files will be saved.
Optional:
- `min_sec`: Minimum duration of the sliced audio files in seconds (default 2).
- `max_sec`: Maximum duration of the sliced audio files in seconds (default 12).
### Transcribe wavs
```bash
python transcribe.py -i <input_dir> -o <output_file> --speaker_name <speaker_name>
```
Required:
- `input_dir`: Path to the directory containing the audio files to transcribe.
- `output_file`: Path to the file where the transcriptions will be saved.
- `speaker_name`: Name of the speaker.
Optional
- `--initial_prompt`: Initial prompt to use for the transcription (default value is specific to Japanese).
- `--device`: `cuda` or `cpu` (default: `cuda`).
- `--language`: `jp`, `en`, or `en` (default: `jp`).
- `--model`: Whisper model, default: `large-v3`
- `--compute_type`: default: `bfloat16`
## Train
`Train.bat` webui (`python webui_train.py`) consists of the following.
### Preprocess audio
```bash
python resample.py -i <input_dir> -o <output_dir> [--normalize] [--trim]
```
Required:
- `input_dir`: Path to the directory containing the audio files to preprocess.
- `output_dir`: Path to the directory where the preprocessed audio files will be saved.
TO BE WRITTEN (WIP)
これいる?

View File

@@ -1 +0,0 @@
To be written.

View File

@@ -56,12 +56,14 @@ if __name__ == "__main__":
)
parser.add_argument(
"--in_dir",
"-i",
type=str,
default=config.resample_config.in_dir,
help="path to source dir",
)
parser.add_argument(
"--out_dir",
"-o",
type=str,
default=config.resample_config.out_dir,
help="path to target dir",

View File

@@ -9,7 +9,6 @@ from tqdm import tqdm
from common.log import logger
from common.stdout_wrapper import SAFE_STDOUT
from resample import normalize_audio
vad_model, utils = torch.hub.load(
repo_or_dir="snakers4/silero-vad",
@@ -59,7 +58,6 @@ def split_wav(
min_sec=2,
max_sec=12,
min_silence_dur_ms=700,
normalize=False,
):
margin = 200 # ミリ秒単位で、音声の前後に余裕を持たせる
speech_timestamps = get_stamps(
@@ -87,9 +85,6 @@ def split_wav(
end_sample = int(end_ms / 1000 * sr)
segment = data[start_sample:end_sample]
if normalize:
segment = normalize_audio(segment, sr)
sf.write(os.path.join(target_dir, f"{file_name}-{i}.wav"), segment, sr)
total_time_ms += end_ms - start_ms
@@ -113,14 +108,11 @@ if __name__ == "__main__":
)
parser.add_argument(
"--output_dir",
"-t",
"-o",
type=str,
default="raw",
help="Directory of output wav files",
)
parser.add_argument(
"--normalize", action="store_true", help="Whether to normalize loudness"
)
parser.add_argument(
"--min_silence_dur_ms",
"-s",
@@ -135,7 +127,6 @@ if __name__ == "__main__":
min_sec = args.min_sec
max_sec = args.max_sec
min_silence_dur_ms = args.min_silence_dur_ms
normalize = args.normalize
wav_files = Path(input_dir).glob("**/*.wav")
wav_files = list(wav_files)
@@ -151,7 +142,6 @@ if __name__ == "__main__":
min_sec=min_sec,
max_sec=max_sec,
min_silence_dur_ms=min_silence_dur_ms,
normalize=normalize,
)
total_sec += time_sec

View File

@@ -7,6 +7,7 @@ import matplotlib.pyplot as plt
import pandas as pd
import torch
from tqdm import tqdm
import numpy as np
from common.log import logger
from common.tts_model import Model
@@ -35,8 +36,7 @@ predictor = torch.hub.load(
parser = argparse.ArgumentParser()
parser.add_argument("--model_name", "-m", type=str, required=True)
parser.add_argument("--device", "-d", type=str, default="cuda")
parser.add_argument("--output", "-o", type=str, default="mos.csv")
parser.add_argument("--device", "-d", type=str, default="cuda"))
args = parser.parse_args()
@@ -84,12 +84,14 @@ results = sorted(results, key=lambda x: x[2][-1], reverse=True)
for model_file, step, scores in results:
logger.info(f"{model_file}: {scores[-1]}")
with open(args.output, "w", encoding="utf-8", newline="") as f:
with open(f"mos_{model_name}.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["model_path"] + ["step"] + test_texts + ["mean"])
for model_file, step, scores in results:
writer.writerow([model_file] + [step] + scores)
logger.info(f"mos_{model_name}.csv has been saved.")
# step countと各MOSの値を格納するリストを初期化
steps = []
mos_values = []
@@ -117,8 +119,24 @@ plt.title("TTS Model Naturalness MOS")
plt.xlabel("Step Count")
plt.ylabel("MOS")
# ステップ数の軸ラベルを1000単位で表示するように調整
plt.xticks(
ticks=np.arange(0, max(steps) + 1000, 1000),
labels=[f"{int(x/1000)}k" for x in np.arange(0, max(steps) + 1000, 5000)],
)
# 縦の補助線を追加
plt.grid(True, axis="x")
# 凡例をグラフの外側に配置
plt.legend(loc="center left", bbox_to_anchor=(1, 0.5))
# 凡例を表示
plt.legend()
# グラフを表示
plt.show()
plt.savefig(f"mos_{model_name}.png", bbox_inches="tight")
logger.info(f"mos_{model_name}.png has been saved.")

View File

@@ -19,10 +19,10 @@ def transcribe(wav_path, initial_prompt=None, language="ja"):
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--input_dir", type=str, default="raw")
parser.add_argument("--output_file", type=str, default="esd.list")
parser.add_argument("--input_dir", "-i", type=str, default="raw")
parser.add_argument("--output_file", "-o", type=str, default="esd.list")
parser.add_argument(
"--initial_prompt", type=str, default="こんにちは。元気、ですかー?私は……ちゃんと元気だよ!"
"--initial_prompt", type=str, default="こんにちは。元気、ですかー?ふふっ、私は……ちゃんと元気だよ!"
)
parser.add_argument(
"--language", type=str, default="ja", choices=["ja", "en", "zh"]
@@ -43,13 +43,15 @@ if __name__ == "__main__":
device = args.device
compute_type = args.compute_type
os.makedirs(os.path.dirname(output_file), exist_ok=True)
logger.info(
f"Loading Whisper model ({args.model}) with compute_type={compute_type}"
)
try:
model = WhisperModel(args.model, device=device, compute_type=compute_type)
except ValueError as e:
logger.warning(f"Failed to load model: {e}")
logger.warning(f"Failed to load model, so use `auto` compute_type: {e}")
model = WhisperModel(args.model, device=device)
wav_files = [

View File

@@ -13,9 +13,16 @@ with open(os.path.join("configs", "paths.yml"), "r", encoding="utf-8") as f:
# assets_root = path_config["assets_root"]
def do_slice(model_name: str, min_sec: float, max_sec: float, input_dir="inputs"):
def do_slice(
model_name: str,
min_sec: float,
max_sec: float,
min_silence_dur_ms: int,
input_dir: str,
):
if model_name == "":
return "Error: モデル名を入力してください。"
logger.info("Start slicing...")
input_dir = "inputs"
output_dir = os.path.join(dataset_root, model_name, "raw")
cmd = [
"slice.py",
@@ -27,20 +34,28 @@ def do_slice(model_name: str, min_sec: float, max_sec: float, input_dir="inputs"
str(min_sec),
"--max_sec",
str(max_sec),
"--min_silence_dur_ms",
str(min_silence_dur_ms),
]
# onnxの警告が出るので無視する
success, message = run_script_with_log(cmd, ignore_warning=True)
if not success:
return f"Error: {message}"
return "音声のスライスが完了しました。"
def do_transcribe(model_name, whisper_model, compute_type, language, initial_prompt):
def do_transcribe(
model_name, whisper_model, compute_type, language, initial_prompt, input_dir, device
):
if model_name == "":
return "Error: モデル名を入力してください。"
if initial_prompt == "":
initial_prompt = "こんにちは。元気、ですかー?私は……ちゃんと元気だよ!"
initial_prompt = "こんにちは。元気、ですかー?私は……ふふっ、ちゃんと元気だよ!"
logger.debug(f"initial_prompt: {initial_prompt}")
if input_dir == "":
input_dir = os.path.join(dataset_root, model_name, "raw")
output_file = os.path.join(dataset_root, model_name, "esd.list")
result = run_script_with_log(
success, message = run_script_with_log(
[
"transcribe.py",
"--input_dir",
@@ -53,29 +68,44 @@ def do_transcribe(model_name, whisper_model, compute_type, language, initial_pro
whisper_model,
"--compute_type",
compute_type,
"--device",
device,
"--language",
language,
"--initial_prompt",
f'"{initial_prompt}"',
]
)
if not success:
return f"Error: {message}"
return "音声の文字起こしが完了しました。"
initial_md = """
# 簡易学習用データセット作成ツール
Style-Bert-VITS2の学習用データセットを作成するためのツールです。与えられた音声からちょうどいい長さの発話区間を切り取りスライスし、それぞれの音声に対して文字起こしを行います。
Style-Bert-VITS2の学習用データセットを作成するためのツールです。以下の2つからなります。
- 与えられた音声からちょうどいい長さの発話区間を切り取りスライス
- 音声に対して文字起こし
このうち両方を使ってもよいし、スライスする必要がない場合は後者のみを使ってもよいです。
## 必要なもの
学習したい音声が入ったwavファイルいくつか。
合計時間がある程度はあったほうがいいかも、10分とかでも大丈夫だったとの報告あり。単一ファイルでも良いし複数ファイルでもよい。
## 使い方
1. `inputs`フォルダ直下にwavファイルをすべて入れる
2. `モデル名`を入力して、`音声のスライス`ボタンを押す
3. 完了したら、`音声の文字起こし`ボタンを押す
4. 出来上がった音声ファイルたちは`Data/{モデル名}/raw`に、書き起こしファイルは`Data/{モデル名}/esd.list`に保存されます。
## スライス使い方
1. `inputs`フォルダにwavファイルをすべて入れる
2. `モデル名`を入力して、設定を必要なら調整して`音声のスライス`ボタンを押す
3. 出来上がった音声ファイルたちは`Data/{モデル名}/raw`に保存される
## 書き起こし使い方
1. 書き起こしたい音声ファイルのあるフォルダを指定(デフォルトは`Data/{モデル名}/raw`なのでスライス後に行う場合は省略してよい)
2. 設定を必要なら調整してボタンを押す
3. 書き起こしファイルは`Data/{モデル名}/esd.list`に保存される
## 注意
@@ -90,16 +120,31 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
with gr.Accordion("音声のスライス"):
with gr.Row():
with gr.Column():
input_dir = gr.Textbox(
label="入力フォルダ名デフォルトはinputs",
placeholder="inputs",
info="inputsフォルダにwavファイルを入れておいてください",
)
min_sec = gr.Slider(
minimum=0, maximum=10, value=2, step=0.5, label="この秒数未満は切り捨てる"
)
max_sec = gr.Slider(
minimum=0, maximum=15, value=12, step=0.5, label="この秒数以上は切り捨てる"
)
min_silence_dur_ms = gr.Slider(
minimum=0,
maximum=2000,
value=700,
step=100,
label="無音とみなして区切る最小の無音の長さms",
)
slice_button = gr.Button("スライスを実行")
result1 = gr.Textbox(label="結果")
with gr.Row():
with gr.Column():
raw_dir = gr.Textbox(
label="書き起こしたい音声ファイルが入っているフォルダ(スライスした場合など、`Data/{モデル名}/raw`の場合は省略可",
)
whisper_model = gr.Dropdown(
["tiny", "base", "small", "medium", "large", "large-v2", "large-v3"],
label="Whisperモデル",
@@ -119,20 +164,31 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
label="計算精度",
value="bfloat16",
)
device = gr.Radio(["cuda", "cpu"], label="デバイス", value="cuda")
language = gr.Dropdown(["ja", "en", "zh"], value="ja", label="言語")
initial_prompt = gr.Textbox(
label="初期プロンプト(日本語の場合は省略可)", placeholder="こんにちは。元気、ですかー?私は……ちゃんと元気だよ!"
label="初期プロンプト",
placeholder="こんにちは。元気、ですかー?ふふっ、私は……ちゃんと元気だよ!",
info="このように書き起こしてほしいという例文、日本語なら省略可、英語等なら書いてください",
)
transcribe_button = gr.Button("音声の文字起こし")
result2 = gr.Textbox(label="結果")
slice_button.click(
do_slice,
inputs=[model_name, min_sec, max_sec],
inputs=[model_name, min_sec, max_sec, min_silence_dur_ms, input_dir],
outputs=[result1],
)
transcribe_button.click(
do_transcribe,
inputs=[model_name, whisper_model, compute_type, language, initial_prompt],
inputs=[
model_name,
whisper_model,
compute_type,
language,
initial_prompt,
raw_dir,
device,
],
outputs=[result2],
)

View File

@@ -138,7 +138,7 @@ def do_dbscan_gradio(eps=2.5, min_samples=15):
f"クラスタが数が0です。パラメータを変えてみてください。",
] + [gr.Audio(visible=False)] * MAX_AUDIO_NUM
return [plt, gr.Slider(maximum=n_clusters), n_clusters] + [
return [plt, gr.Slider(maximum=n_clusters, value=1), n_clusters] + [
gr.Audio(visible=False)
] * MAX_AUDIO_NUM
@@ -170,7 +170,7 @@ def do_clustering_gradio(n_clusters=4, method="KMeans"):
)
plt.legend()
return [plt.gcf(), gr.Slider(maximum=n_clusters)] + [
return [plt.gcf(), gr.Slider(maximum=n_clusters, value=1)] + [
gr.Audio(visible=False)
] * MAX_AUDIO_NUM