From ceb431192c2e779f9f557a82c64811aab4ecd13f Mon Sep 17 00:00:00 2001 From: litagin02 Date: Tue, 28 May 2024 18:37:44 +0900 Subject: [PATCH] colab --- colab.ipynb => colab_ipynb.ipynb | 190 +++++++++++++++++++++---------- default_style.py | 2 +- gradio_tabs/train.py | 12 +- requirements-colab.txt | 15 +++ requirements.txt | 1 - resample.py | 1 + vad_filter.py | 92 +++++++++++++++ 7 files changed, 245 insertions(+), 68 deletions(-) rename colab.ipynb => colab_ipynb.ipynb (66%) create mode 100644 requirements-colab.txt create mode 100644 vad_filter.py diff --git a/colab.ipynb b/colab_ipynb.ipynb similarity index 66% rename from colab.ipynb rename to colab_ipynb.ipynb index 41e2002..de46741 100644 --- a/colab.ipynb +++ b/colab_ipynb.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "F7aJhsgLAWvO" + }, "source": [ "# Style-Bert-VITS2 (ver 2.5.0) のGoogle Colabでの学習\n", "\n", @@ -22,34 +24,52 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "L-gAIubBAWvQ" + }, "source": [ "## 0. 環境構築\n", "\n", - "Style-Bert-VITS2の環境をcolab上に構築します。グラボモードが有効になっていることを確認し、以下のセルを順に実行してください。\n", - "\n", - "**最近のcolabのアップデートにより、エラーダイアログ「WARNING: The following packages were previously imported in this runtime: [pydevd_plugins]」が出るが、「キャンセル」を選択して続行してください。**" + "Style-Bert-VITS2の環境をcolab上に構築します。ランタイムがT4等のGPUバックエンドになっていることを確認し、実行してください。" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0GNj8JyDAlm2", + "outputId": "d8be4a1a-e52d-46f8-8675-3f1a24bc9a51" + }, "outputs": [], "source": [ - "# このセルを実行して環境構築してください。\n", - "# エラーダイアログ「WARNING: The following packages were previously imported in this runtime: [pydevd_plugins]」が出るが「キャンセル」を選択して続行してください。\n", + "import os\n", "\n", + "\n", + "os.environ[\"PATH\"] += \":/root/.cargo/bin\"\n", + "\n", + "!curl -LsSf https://astral.sh/uv/install.sh | sh\n", "!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n", "%cd Style-Bert-VITS2/\n", - "!pip install -r requirements.txt\n", - "!python initialize.py --skip_jvnv" + "# 後で消す!!!\n", + "!git checkout dev\n", + "# 後で消す!!!\n", + "!uv pip install --system -r requirements-colab.txt\n", + "!python initialize.py --skip_default_models" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "o5z1nzkvAWvR", + "outputId": "cd87f053-18e0-4dbb-f904-d5230d1fa7ef" + }, "outputs": [], "source": [ "# Google driveを使う方はこちらを実行してください。\n", @@ -61,7 +81,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "WU9apXzcAWvR" + }, "source": [ "## 1. 初期設定\n", "\n", @@ -71,8 +93,10 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, + "execution_count": null, + "metadata": { + "id": "gO3OwZV1AWvR" + }, "outputs": [], "source": [ "# 学習に必要なファイルや途中経過が保存されるディレクトリ\n", @@ -90,7 +114,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "dA_yLeezAWvS" + }, "source": [ "## 2. 学習に使うデータ準備\n", "\n", @@ -99,17 +125,27 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "8s9gOnTCAWvS" + }, "source": [ "### 2.1 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", "\n", - "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、(日本語の)音声ファイルのみから以下の手順でデータセットを作成することができます。Google drive上の`Style-Bert-VITS2/inputs/`フォルダに音声ファイル(wavファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られ、自動的に正しい場所へ配置されます。" + "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、(日本語の)音声ファイルのみから以下の手順でデータセットを作成することができます。Google drive上の`Style-Bert-VITS2/inputs/`フォルダに音声ファイル(wavやmp3等の通常の音声ファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られ、自動的に正しい場所へ配置されます。\n", + "\n", + "**2024-05-27のVer 2.5以降**、`inputs/`フォルダにサブフォルダを2個以上作ってそこへ音声ファイルをスタイルに応じて振り分けて置くと、学習の際にサブディレクトリに応じたスタイルが自動的に作成されます。デフォルトスタイルのみでよい場合や手動でスタイルを後で作成する場合は`inputs/`直下へ入れれば大丈夫です。" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_fXCTPuiAWvS", + "outputId": "47abd55b-efe5-48e2-f6fa-8e2016efe0ec" + }, "outputs": [], "source": [ "# 元となる音声ファイル(wav形式)を入れるディレクトリ\n", @@ -126,14 +162,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "j7vEWewoAWvS" + }, "source": [ "成功したらそのまま3へ進んでください" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "Z3AC-3zpAWvS" + }, "source": [ "### 2.2 音声ファイルと書き起こしデータがすでにある場合\n", "\n", @@ -144,7 +184,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": { "id": "esCNJl704h52" }, @@ -157,39 +197,58 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "aaDgJCjCAWvT" + }, "source": [ - "次に、学習に必要なデータを、Google driveに作成された`Style-Bert-VITS2/Data`フォルダに配置します。\n", + "まず音声データと、書き起こしテキストを用意してください。\n", + "\n", + "それを次のように配置します。\n", + "```\n", + "├── Data/\n", + "│ ├── {モデルの名前}\n", + "│ │ ├── esd.list\n", + "│ │ ├── raw/\n", + "│ │ │ ├── foo.wav\n", + "│ │ │ ├── bar.mp3\n", + "│ │ │ ├── style1/\n", + "│ │ │ │ ├── baz.wav\n", + "│ │ │ │ ├── qux.wav\n", + "│ │ │ ├── style2/\n", + "│ │ │ │ ├── corge.wav\n", + "│ │ │ │ ├── grault.wav\n", + "...\n", + "```\n", + "\n", + "### 配置の仕方\n", + "- 上のように配置すると、`style1/`と`style2/`フォルダの内部(直下以外も含む)に入っている音声ファイルたちから、自動的にデフォルトスタイルに加えて`style1`と`style2`というスタイルが作成されます\n", + "- 特にスタイルを作る必要がない場合や、スタイル分類機能等でスタイルを作る場合は、`raw/`フォルダ直下に全てを配置してください。このように`raw/`のサブディレクトリの個数が0または1の場合は、スタイルはデフォルトスタイルのみが作成されます。\n", + "- 音声ファイルのフォーマットはwav形式以外にもmp3等の多くの音声ファイルに対応しています\n", + "\n", + "### 書き起こしファイル`esd.list`\n", + "\n", + "`Data/{モデルの名前}/esd.list` ファイルには、以下のフォーマットで各音声ファイルの情報を記述してください。\n", "\n", - "まず音声データ(wavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。wavファイル名やモデルの名前は空白を含まない半角で、wavファイルの拡張子は小文字`.wav`である必要があります。\n", "\n", - "書き起こしテキストは、次の形式で記述してください。\n", "```\n", - "****.wav|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト}\n", + "path/to/audio.wav(wavファイル以外でもこう書く)|{話者名}|{言語ID、ZHかJPかEN}|{書き起こしテキスト}\n", "```\n", "\n", + "- ここで、最初の`path/to/audio.wav`は、`raw/`からの相対パスです。つまり、`raw/foo.wav`の場合は`foo.wav`、`raw/style1/bar.wav`の場合は`style1/bar.wav`となります。\n", + "- 拡張子がwavでない場合でも、`esd.list`には`wav`と書いてください、つまり、`raw/bar.mp3`の場合でも`bar.wav`と書いてください。\n", + "\n", + "\n", "例:\n", "```\n", - "wav_number1.wav|hanako|JP|こんにちは、聞こえて、いますか?\n", - "wav_next.wav|taro|JP|はい、聞こえています……。\n", + "foo.wav|hanako|JP|こんにちは、元気ですか?\n", + "bar.wav|taro|JP|はい、聞こえています……。何か用ですか?\n", + "style1/baz.wav|hanako|JP|今日はいい天気ですね。\n", + "style1/qux.wav|taro|JP|はい、そうですね。\n", + "...\n", "english_teacher.wav|Mary|EN|How are you? I'm fine, thank you, and you?\n", "...\n", "```\n", - "日本語話者の単一話者データセットで構いません。\n", - "\n", - "### データセットの配置\n", - "\n", - "次にモデルの名前を適当に決めてください(空白を含まない半角英数字がよいです)。\n", - "そして、書き起こしファイルを`esd.list`という名前で保存し、またwavファイルも`raw`というフォルダを作成し、あなたのGoogle Driveの中の(上で自動的に作られるはずの)`Data`フォルダのなかに、次のように配置します。\n", - "```\n", - "├── Data\n", - "│ ├── {モデルの名前}\n", - "│ │ ├── esd.list\n", - "│ │ ├── raw\n", - "│ │ │ ├── ****.wav\n", - "│ │ │ ├── ****.wav\n", - "│ │ │ ├── ...\n", - "```" + "もちろん日本語話者の単一話者データセットでも構いません。" ] }, { @@ -205,7 +264,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": { "id": "CXR7kjuF5GlE" }, @@ -240,7 +299,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "BFZdLTtpAWvT" + }, "source": [ "上のセルが実行されたら、次のセルを実行して学習の前処理を行います。" ] @@ -253,7 +314,7 @@ "base_uri": "https://localhost:8080/" }, "id": "xMVaOIPLabV5", - "outputId": "15fac868-9132-45d9-9f5f-365b6aeb67b0" + "outputId": "36b1c2b2-6df0-4d00-d86a-519a0fc0af63" }, "outputs": [], "source": [ @@ -271,7 +332,7 @@ " freeze_JP_bert=False,\n", " freeze_ZH_bert=False,\n", " freeze_style=False,\n", - " freeze_decoder=False, # ここをTrueにするともしかしたら違う結果になるかもしれません。\n", + " freeze_decoder=False,\n", " use_jp_extra=use_jp_extra,\n", " val_per_lang=0,\n", " log_interval=200,\n", @@ -281,7 +342,9 @@ }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "id": "sVhwI5C-AWvT" + }, "source": [ "## 4. 学習\n", "\n", @@ -296,11 +359,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "laieKrbEb6Ij", - "outputId": "72238c88-f294-4ed9-84f6-84c1c17999ca" + "id": "laieKrbEb6Ij" }, "outputs": [], "source": [ @@ -311,7 +370,9 @@ "import yaml\n", "from gradio_tabs.train import get_path\n", "\n", - "dataset_path, _, _, _, config_path = get_path(model_name)\n", + "paths = get_path(model_name)\n", + "dataset_path = str(paths.dataset_path)\n", + "config_path = str(paths.config_path)\n", "\n", "with open(\"default_config.yml\", \"r\", encoding=\"utf-8\") as f:\n", " yml_data = yaml.safe_load(f)\n", @@ -323,21 +384,30 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "colab": { + "background_save": true, + "base_uri": "https://localhost:8080/" + }, + "id": "JqGeHNabAWvT", + "outputId": "c51b422c-728b-420b-fa92-b787fa058adf" + }, "outputs": [], "source": [ "# 日本語特化版を「使う」場合\n", - "!python train_ms_jp_extra.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" + "!python train_ms_jp_extra.py --config {config_path} --model {dataset_path} --assets_root {assets_root} --use_custom_batch_sampler" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "id": "rVbjh-WPAWvU" + }, "outputs": [], "source": [ "# 日本語特化版を「使わない」場合\n", - "!python train_ms.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" + "!python train_ms.py --config {config_path} --model {dataset_path} --assets_root {assets_root} --use_custom_batch_sampler" ] }, { @@ -348,12 +418,12 @@ "base_uri": "https://localhost:8080/" }, "id": "c7g0hrdeP1Tl", - "outputId": "94f9a6f6-027f-4554-ce0c-60ac56251c22" + "outputId": "4bb9d21e-50df-4ba5-a547-daa78a4b63dc" }, "outputs": [], "source": [ "# 学習結果を試す・マージ・スタイル分けはこちらから\n", - "!python app.py --share" + "!python app.py --share --skip_default_models" ] } ], diff --git a/default_style.py b/default_style.py index 9671db2..7a2076a 100644 --- a/default_style.py +++ b/default_style.py @@ -15,7 +15,7 @@ def set_style_config(json_path: Path, output_path: Path): json_dict["data"]["style2id"] = {DEFAULT_STYLE: 0} with open(output_path, "w", encoding="utf-8") as f: json.dump(json_dict, f, indent=2, ensure_ascii=False) - logger.info(f"Save style config (only {DEFAULT_STYLE}) to {output_path}") + logger.info(f"Saving style config (only {DEFAULT_STYLE}) to {output_path}...") def save_neutral_vector(wav_dir: Union[Path, str], output_path: Union[Path, str]): diff --git a/gradio_tabs/train.py b/gradio_tabs/train.py index 4107845..d0411e8 100644 --- a/gradio_tabs/train.py +++ b/gradio_tabs/train.py @@ -329,7 +329,7 @@ def train( skip_style: bool = False, use_jp_extra: bool = True, speedup: bool = False, - use_custom_batch_sampler: bool = False, + not_use_custom_batch_sampler: bool = False, ): paths = get_path(model_name) # 学習再開の場合を考えて念のためconfig.ymlの名前等を更新 @@ -352,7 +352,7 @@ def train( cmd.append("--skip_default_style") if speedup: cmd.append("--speedup") - if use_custom_batch_sampler: + if not not_use_custom_batch_sampler: cmd.append("--use_custom_batch_sampler") success, message = run_script_with_log(cmd, ignore_warning=True) if not success: @@ -723,9 +723,9 @@ def create_train_app(): label="JP-Extra版を使う", value=True, ) - use_custom_batch_sampler = gr.Checkbox( - label="カスタムバッチサンプラーを使う", - info="Ver 2.5以降にうまく学習できなかったりVRAMが足りない場合に試してみてください", + not_use_custom_batch_sampler = gr.Checkbox( + label="カスタムバッチサンプラーを使わない", + info="VRAMに余裕がある場合にチェックすると、長い音声ファイルも学習に使われるようになります", value=False, ) speedup = gr.Checkbox( @@ -820,7 +820,7 @@ def create_train_app(): skip_style, use_jp_extra_train, speedup, - use_custom_batch_sampler, + not_use_custom_batch_sampler, ], outputs=[info_train], ) diff --git a/requirements-colab.txt b/requirements-colab.txt new file mode 100644 index 0000000..2f92ef1 --- /dev/null +++ b/requirements-colab.txt @@ -0,0 +1,15 @@ +cmudict +cn2an +g2p_en +gradio +jieba +librosa==0.9.2 +loguru +num2words +pyannote.audio>=3.1.0 +pyloudnorm +pyopenjtalk-dict +pypinyin +pyworld-prebuilt +transformers +umap-learn diff --git a/requirements.txt b/requirements.txt index 1c702be..6695153 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,6 @@ g2p_en GPUtil gradio jieba -langid librosa==0.9.2 loguru num2words diff --git a/resample.py b/resample.py index 050ce9a..285105c 100644 --- a/resample.py +++ b/resample.py @@ -62,6 +62,7 @@ def resample( if trim: wav, _ = librosa.effects.trim(wav, top_db=30) relative_path = file.relative_to(input_dir) + # ここで拡張子が.wav以外でも.wavに置き換えられる output_path = output_dir / relative_path.with_suffix(".wav") output_path.parent.mkdir(parents=True, exist_ok=True) soundfile.write(output_path, wav, sr) diff --git a/vad_filter.py b/vad_filter.py new file mode 100644 index 0000000..1259051 --- /dev/null +++ b/vad_filter.py @@ -0,0 +1,92 @@ +import argparse +import os +import shutil +import sys +from pathlib import Path + +import pandas as pd +import torch +from tqdm import tqdm + +from style_bert_vits2.logging import logger + + +vad_model, utils = torch.hub.load( + repo_or_dir="litagin02/silero-vad", + model="silero_vad", + onnx=True, + trust_repo=True, +) + +(get_speech_timestamps, _, read_audio, *_) = utils + + +def get_speech_ratio(audio_file): + sampling_rate = 16000 + + wav = read_audio(audio_file, sampling_rate=sampling_rate) + speech_timestamps = get_speech_timestamps( + wav, vad_model, sampling_rate=sampling_rate + ) + + speech_dur_ms = 0 + + for ts in speech_timestamps: + start_ms = ts["start"] / 16 + end_ms = ts["end"] / 16 + speech_dur_ms += end_ms - start_ms + + total_dur_ms = len(wav) / sampling_rate * 1000 + return speech_dur_ms / total_dur_ms + + +def process(file: Path): + speech_ratio = get_speech_ratio(file) + return file, speech_ratio + + +def main(): + parser = argparse.ArgumentParser(description="Calculate speech ratio.") + parser.add_argument( + "-i", "--input", help="Directory containing audio files", required=True + ) + args = parser.parse_args() + + if os.path.exists(os.path.join(args.input, "low_speech_ratio")): + logger.info("Low speech ratio directory already exists, skipping...") + exit(0) + + data_dir = Path(args.input) + wav_files = list(data_dir.glob("*.wav")) + wav_files.sort() + + if len(wav_files) < 100: + logger.warning("Too few files, skipping...") + exit(0) + + logger.info(f"Start VAD filtering for {data_dir}...") + + results = [] + + for wav_file in tqdm(wav_files, file=sys.stdout): + speech_ratio = get_speech_ratio(wav_file) + results.append((wav_file, speech_ratio)) + + results_df = pd.DataFrame(results, columns=["file", "speech_ratio"]) + results_df.to_csv(os.path.join(data_dir, "speech_ratio.csv"), index=False) + + logger.info(f"Speech ratio stats:\n{results_df['speech_ratio'].describe()}") + threshold = 0.5 + + low_speech_ratio_dir = os.path.join(data_dir, "low_speech_ratio") + os.makedirs(low_speech_ratio_dir, exist_ok=True) + + low_speech_files = results_df[results_df["speech_ratio"] < threshold]["file"] + logger.info(f"Moving {len(low_speech_files)} files to {low_speech_ratio_dir}...") + for low_speech_file in low_speech_files: + shutil.move(low_speech_file, low_speech_ratio_dir) + logger.success("VAD filtering completed.") + + +if __name__ == "__main__": + main()