From 6ca9c214fcc2f53a2ca8d62a8f41bee613bc217b Mon Sep 17 00:00:00 2001 From: litagin02 Date: Fri, 5 Jan 2024 16:16:07 +0900 Subject: [PATCH] Update (WIP) --- README.md | 1 + colab.ipynb | 35 +++++++++++----- slice.py | 1 + transcribe.py | 1 - webui_style_vectors.py | 95 +++++++++++++++++++++++------------------- 5 files changed, 79 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 004f41d..9b865a9 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ API仕様は起動後に`/docs`にて確認ください。 - [x] `server_fastapi.py`の対応、とくにAPIで使えるようになると嬉しい人が増えるのかもしれない - [x] モデルのマージで声音と感情表現を混ぜる機能の実装 - [ ] 英語等多言語対応? +- [ ] ONNX対応 ## 実験したいこと diff --git a/colab.ipynb b/colab.ipynb index d02670e..ee2edf4 100644 --- a/colab.ipynb +++ b/colab.ipynb @@ -40,7 +40,8 @@ "\n", "!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n", "%cd Style-Bert-VITS2/\n", - "!pip install -r requirements.txt" + "!pip install -r requirements.txt\n", + "!apt install libcublas11" ] }, { @@ -92,7 +93,6 @@ "\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)" ] }, @@ -100,9 +100,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", + "## 2. 学習に使うデータ準備\n", "\n", - "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、音声ファイルのみから以下の手順でデータセットを作成することができます。デフォルトではGoogle drive上の`Style-Bert-VITS2/inputs/`に音声ファイル(wavファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られます。" + "すでに音声ファイル(1ファイル2-12秒程度)とその書き起こしデータがある場合は2.2を、ない場合は2.1を実行してください。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", + "\n", + "音声ファイル(1ファイル2-12秒程度)とその書き起こしのデータセットを持っていない方は、(日本語の)音声ファイルのみから以下の手順でデータセットを作成することができます。Google drive上の`Style-Bert-VITS2/inputs/`フォルダに音声ファイル(wavファイル形式、1ファイルでも複数ファイルでも可)を置いて、下を実行すると、データセットが作られます。" ] }, { @@ -124,13 +133,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## 2. データの配置" + "成功したらそのまま3へ進んでください" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "### 音声ファイルと書き起こしデータがすでにある場合\n", + "\n", + "指示に従って適切にデータセットを配置してください。\n", + "\n", "次のセルを実行して、学習データをいれるフォルダ(1で設定した`dataset_root`)を作成します。" ] }, @@ -151,7 +164,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "次に、学習に必要なデータを、Google driveに作成されたStyle-Bert-VITS2/Dataフォルダに配置します。\n", + "次に、学習に必要なデータを、Google driveに作成された`Style-Bert-VITS2/Data`フォルダに配置します。\n", "\n", "### データセットの準備\n", "まず音声データ(wavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつか)と、書き起こしテキストを用意してください。\n", @@ -219,11 +232,11 @@ "# 保存頻度。何ステップごとにモデルを保存するか。分からなければデフォルトのままで。\n", "save_every_steps = 1000\n", "\n", - "# 音声ファイルの音量を正規化するかどうか。\n", - "normalize = True\n", + "# 音声ファイルの音量を正規化するかどうか。`True`もしくは`False`\n", + "normalize = False\n", "\n", "# 音声ファイルの開始・終了にある無音区間を削除するかどうか\n", - "trim = True" + "trim = False" ] }, { @@ -252,7 +265,7 @@ " batch_size=batch_size,\n", " epochs=epochs,\n", " save_every_steps=save_every_steps,\n", - " bf16_run=False, # colabの無料のやつではサポートされていないようです。\n", + " bf16_run=False,\n", " num_processes=2,\n", " normalize=normalize,\n", " trim=trim,\n", @@ -287,6 +300,7 @@ "# 上でつけたモデル名を入力。学習を途中からする場合はきちんとモデルが保存されているフォルダ名を入力。\n", "model_name = \"your_model_name\"\n", "\n", + "\n", "import yaml\n", "from webui_train import get_path\n", "\n", @@ -298,7 +312,6 @@ "with open(\"config.yml\", \"w\", encoding=\"utf-8\") as f:\n", " yaml.dump(yml_data, f, allow_unicode=True)\n", "\n", - "\n", "!python train_ms.py --config {config_path} --model {dataset_path} --assets_root {assets_root}" ] }, diff --git a/slice.py b/slice.py index df2e6cd..3b6e2d7 100644 --- a/slice.py +++ b/slice.py @@ -131,6 +131,7 @@ if __name__ == "__main__": wav_files = Path(input_dir).glob("**/*.wav") wav_files = list(wav_files) + logger.info(f"Found {len(wav_files)} wav files.") if os.path.exists(output_dir): logger.warning(f"Output directory {output_dir} already exists, deleting...") shutil.rmtree(output_dir) diff --git a/transcribe.py b/transcribe.py index 9a0f8c1..9244683 100644 --- a/transcribe.py +++ b/transcribe.py @@ -73,7 +73,6 @@ if __name__ == "__main__": language = Languages.ZH else: raise ValueError(f"{language} is not supported.") - logger.debug(f"Initial prompt: {initial_prompt}") with open(output_file, "w", encoding="utf-8") as f: for wav_file in tqdm(wav_files, file=SAFE_STDOUT): file_name = os.path.basename(wav_file) diff --git a/webui_style_vectors.py b/webui_style_vectors.py index 8eb144c..3efa192 100644 --- a/webui_style_vectors.py +++ b/webui_style_vectors.py @@ -4,6 +4,7 @@ import os import gradio as gr import matplotlib.pyplot as plt import numpy as np +from umap import UMAP from scipy.spatial.distance import cdist from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans from sklearn.manifold import TSNE @@ -17,16 +18,17 @@ MAX_AUDIO_NUM = 10 tsne = TSNE(n_components=2, random_state=42, metric="cosine") +umap = UMAP(n_components=2, random_state=42, metric="cosine") wav_files = [] x = np.array([]) -x_tsne = None +x_reduced = None mean = np.array([]) centroids = [] -def load(model_name): - global wav_files, x, x_tsne, mean +def load(model_name, reduction_method): + global wav_files, x, x_reduced, mean wavs_dir = os.path.join("Data", model_name, "wavs") style_vector_files = [ os.path.join(wavs_dir, f) for f in os.listdir(wavs_dir) if f.endswith(".npy") @@ -35,32 +37,34 @@ def load(model_name): style_vectors = [np.load(f) for f in style_vector_files] x = np.array(style_vectors) mean = np.mean(x, axis=0) - - x_tsne = tsne.fit_transform(x) - + if reduction_method == "t-SNE": + x_reduced = tsne.fit_transform(x) + elif reduction_method == "UMAP": + x_reduced = umap.fit_transform(x) + else: + raise ValueError("Invalid reduction method") + x_reduced = np.asarray(x_reduced) plt.figure(figsize=(6, 6)) - plt.scatter(x_tsne[:, 0], x_tsne[:, 1]) + plt.scatter(x_reduced[:, 0], x_reduced[:, 1]) return plt def do_clustering(n_clusters=4, method="KMeans"): - global centroids, x_tsne + global centroids, x_reduced if method == "KMeans": model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto") y_pred = model.fit_predict(x) elif method == "Agglomerative": model = AgglomerativeClustering(n_clusters=n_clusters) y_pred = model.fit_predict(x) - elif method == "KMeans after t-SNE": - if x_tsne is None: - x_tsne = tsne.fit_transform(x) + elif method == "KMeans after reduction": + assert x_reduced is not None model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto") - y_pred = model.fit_predict(x_tsne) - elif method == "Agglomerative after t-SNE": - if x_tsne is None: - x_tsne = tsne.fit_transform(x) + y_pred = model.fit_predict(x_reduced) + elif method == "Agglomerative after reduction": + assert x_reduced is not None model = AgglomerativeClustering(n_clusters=n_clusters) - y_pred = model.fit_predict(x_tsne) + y_pred = model.fit_predict(x_reduced) else: raise ValueError("Invalid method") @@ -72,11 +76,10 @@ def do_clustering(n_clusters=4, method="KMeans"): def do_dbscan(eps=2.5, min_samples=15): - global centroids, x_tsne + global centroids, x_reduced model = DBSCAN(eps=eps, min_samples=min_samples) - if x_tsne is None: - x_tsne = tsne.fit_transform(x) - y_pred = model.fit_predict(x_tsne) + assert x_reduced is not None + y_pred = model.fit_predict(x_reduced) n_clusters = max(y_pred) + 1 centroids = [] for i in range(n_clusters): @@ -84,11 +87,15 @@ def do_dbscan(eps=2.5, min_samples=15): return y_pred, centroids -def closest_wav_files(cluster_index, num_files=1, weight=5): +def closest_wav_files(cluster_index, num_files=1, weight=1): # centroidを強調した点からの距離が最も近い音声を選ぶ centroid_enhanced = mean + weight * (centroids - mean) # セントロイドと全ての点との距離を計算 - distances = cdist(centroid_enhanced[cluster_index : cluster_index + 1], x) + distances = cdist( + umap.transform(centroid_enhanced[cluster_index : cluster_index + 1]), + x_reduced, + metric="euclidean", + ) # 距離が小さい順にソートし、上位のインデックスを取得 closest_indices = np.argsort(distances[0])[:num_files] @@ -96,26 +103,25 @@ def closest_wav_files(cluster_index, num_files=1, weight=5): def do_dbscan_gradio(eps=2.5, min_samples=15): - global x_tsne, centroids + global x_reduced, centroids y_pred, centroids = do_dbscan(eps, min_samples) - if x_tsne is None: - x_tsne = tsne.fit_transform(x) + assert x_reduced is not None cmap = plt.get_cmap("tab10") plt.figure(figsize=(6, 6)) for i in range(max(y_pred) + 1): plt.scatter( - x_tsne[y_pred == i, 0], - x_tsne[y_pred == i, 1], + x_reduced[y_pred == i, 0], + x_reduced[y_pred == i, 1], color=cmap(i), label=f"Style {i + 1}", ) # Noise cluster (-1) is black plt.scatter( - x_tsne[y_pred == -1, 0], - x_tsne[y_pred == -1, 1], + x_reduced[y_pred == -1, 0], + x_reduced[y_pred == -1, 1], color="black", label="Noise", ) @@ -153,18 +159,16 @@ def closest_wav_files_gradio(cluster_index, num_files=1, weight=1): def do_clustering_gradio(n_clusters=4, method="KMeans"): - global x_tsne, centroids + global x_reduced, centroids y_pred, centroids = do_clustering(n_clusters, method) - if x_tsne is None: - x_tsne = tsne.fit_transform(x) - + assert x_reduced is not None cmap = plt.get_cmap("tab10") plt.figure(figsize=(6, 6)) for i in range(n_clusters): plt.scatter( - x_tsne[y_pred == i, 0], - x_tsne[y_pred == i, 1], + x_reduced[y_pred == i, 0], + x_reduced[y_pred == i, 1], color=cmap(i), label=f"Style {i + 1}", ) @@ -294,10 +298,17 @@ DBSCANという方法でスタイル分けを行います。 with gr.Blocks(theme="NoCrypt/miku") as app: gr.Markdown(initial_md) with gr.Row(): - model_name = gr.Textbox("your_model_name", label="モデル名") + model_name = gr.Textbox(placeholder="your_model_name", label="モデル名") + # 削減方法を選択:UMAP or t-SNE + reduction_method = gr.Radio( + choices=["UMAP", "t-SNE"], + label="次元削減方法", + info="v 1.3以前はt-SNEでしたがUMAPのほうがよい可能性もあります。", + value="UMAP", + ) load_button = gr.Button("スタイルベクトルを読み込む", variant="primary") output = gr.Plot(label="音声スタイルの可視化") - load_button.click(load, inputs=[model_name], outputs=[output]) + load_button.click(load, inputs=[model_name, reduction_method], outputs=[output]) with gr.Tab("方法1: スタイル分けを自動で行う"): with gr.Tab("スタイル分け1"): n_clusters = gr.Slider( @@ -310,14 +321,14 @@ with gr.Blocks(theme="NoCrypt/miku") as app: ) c_method = gr.Radio( choices=[ - "Agglomerative after t-SNE", - "KMeans after t-SNE", + "Agglomerative after reduction", + "KMeans after reduction", "Agglomerative", "KMeans", ], label="アルゴリズム", info="分類する(クラスタリング)アルゴリズムを選択します。いろいろ試してみてください。", - value="Agglomerative after t-SNE", + value="Agglomerative after reduction", ) c_button = gr.Button("スタイル分けを実行") with gr.Tab("スタイル分け2: DBSCAN"): @@ -325,14 +336,14 @@ with gr.Blocks(theme="NoCrypt/miku") as app: eps = gr.Slider( minimum=0.1, maximum=10, - step=0.1, + step=0.05, value=2.5, label="eps", info="小さいほどスタイル数が増える", ) min_samples = gr.Slider( minimum=1, - maximum=100, + maximum=50, step=1, value=15, label="min_samples",