Update (WIP)

This commit is contained in:
litagin02
2024-01-05 16:16:07 +09:00
parent 7ba7d52f50
commit 6ca9c214fc
5 changed files with 79 additions and 54 deletions

View File

@@ -135,6 +135,7 @@ API仕様は起動後に`/docs`にて確認ください。
- [x] `server_fastapi.py`の対応、とくにAPIで使えるようになると嬉しい人が増えるのかもしれない - [x] `server_fastapi.py`の対応、とくにAPIで使えるようになると嬉しい人が増えるのかもしれない
- [x] モデルのマージで声音と感情表現を混ぜる機能の実装 - [x] モデルのマージで声音と感情表現を混ぜる機能の実装
- [ ] 英語等多言語対応? - [ ] 英語等多言語対応?
- [ ] ONNX対応
## 実験したいこと ## 実験したいこと

View File

@@ -40,7 +40,8 @@
"\n", "\n",
"!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n", "!git clone https://github.com/litagin02/Style-Bert-VITS2.git\n",
"%cd Style-Bert-VITS2/\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",
"\n", "\n",
"with open(\"configs/paths.yml\", \"w\", encoding=\"utf-8\") as f:\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)" " yaml.dump({\"dataset_root\": dataset_root, \"assets_root\": assets_root}, f)"
] ]
}, },
@@ -100,9 +100,18 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### 音声ファイルからのデータセットの作成(ある人はスキップ可)\n", "## 2. 学習に使うデータ準備\n",
"\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", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"## 2. データの配置" "成功したらそのまま3へ進んでください"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"### 音声ファイルと書き起こしデータがすでにある場合\n",
"\n",
"指示に従って適切にデータセットを配置してください。\n",
"\n",
"次のセルを実行して、学習データをいれるフォルダ1で設定した`dataset_root`)を作成します。" "次のセルを実行して、学習データをいれるフォルダ1で設定した`dataset_root`)を作成します。"
] ]
}, },
@@ -151,7 +164,7 @@
"cell_type": "markdown", "cell_type": "markdown",
"metadata": {}, "metadata": {},
"source": [ "source": [
"次に、学習に必要なデータを、Google driveに作成されたStyle-Bert-VITS2/Dataフォルダに配置します。\n", "次に、学習に必要なデータを、Google driveに作成された`Style-Bert-VITS2/Data`フォルダに配置します。\n",
"\n", "\n",
"### データセットの準備\n", "### データセットの準備\n",
"まず音声データwavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつかと、書き起こしテキストを用意してください。\n", "まず音声データwavファイルで1ファイルが2-12秒程度の、長すぎず短すぎない発話のものをいくつかと、書き起こしテキストを用意してください。\n",
@@ -219,11 +232,11 @@
"# 保存頻度。何ステップごとにモデルを保存するか。分からなければデフォルトのままで。\n", "# 保存頻度。何ステップごとにモデルを保存するか。分からなければデフォルトのままで。\n",
"save_every_steps = 1000\n", "save_every_steps = 1000\n",
"\n", "\n",
"# 音声ファイルの音量を正規化するかどうか。\n", "# 音声ファイルの音量を正規化するかどうか。`True`もしくは`False`\n",
"normalize = True\n", "normalize = False\n",
"\n", "\n",
"# 音声ファイルの開始・終了にある無音区間を削除するかどうか\n", "# 音声ファイルの開始・終了にある無音区間を削除するかどうか\n",
"trim = True" "trim = False"
] ]
}, },
{ {
@@ -252,7 +265,7 @@
" batch_size=batch_size,\n", " batch_size=batch_size,\n",
" epochs=epochs,\n", " epochs=epochs,\n",
" save_every_steps=save_every_steps,\n", " save_every_steps=save_every_steps,\n",
" bf16_run=False, # colabの無料のやつではサポートされていないようです。\n", " bf16_run=False,\n",
" num_processes=2,\n", " num_processes=2,\n",
" normalize=normalize,\n", " normalize=normalize,\n",
" trim=trim,\n", " trim=trim,\n",
@@ -287,6 +300,7 @@
"# 上でつけたモデル名を入力。学習を途中からする場合はきちんとモデルが保存されているフォルダ名を入力。\n", "# 上でつけたモデル名を入力。学習を途中からする場合はきちんとモデルが保存されているフォルダ名を入力。\n",
"model_name = \"your_model_name\"\n", "model_name = \"your_model_name\"\n",
"\n", "\n",
"\n",
"import yaml\n", "import yaml\n",
"from webui_train import get_path\n", "from webui_train import get_path\n",
"\n", "\n",
@@ -298,7 +312,6 @@
"with open(\"config.yml\", \"w\", encoding=\"utf-8\") as f:\n", "with open(\"config.yml\", \"w\", encoding=\"utf-8\") as f:\n",
" yaml.dump(yml_data, f, allow_unicode=True)\n", " yaml.dump(yml_data, f, allow_unicode=True)\n",
"\n", "\n",
"\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}"
] ]
}, },

View File

@@ -131,6 +131,7 @@ if __name__ == "__main__":
wav_files = Path(input_dir).glob("**/*.wav") wav_files = Path(input_dir).glob("**/*.wav")
wav_files = list(wav_files) wav_files = list(wav_files)
logger.info(f"Found {len(wav_files)} wav files.")
if os.path.exists(output_dir): if os.path.exists(output_dir):
logger.warning(f"Output directory {output_dir} already exists, deleting...") logger.warning(f"Output directory {output_dir} already exists, deleting...")
shutil.rmtree(output_dir) shutil.rmtree(output_dir)

View File

@@ -73,7 +73,6 @@ if __name__ == "__main__":
language = Languages.ZH language = Languages.ZH
else: else:
raise ValueError(f"{language} is not supported.") raise ValueError(f"{language} is not supported.")
logger.debug(f"Initial prompt: {initial_prompt}")
with open(output_file, "w", encoding="utf-8") as f: with open(output_file, "w", encoding="utf-8") as f:
for wav_file in tqdm(wav_files, file=SAFE_STDOUT): for wav_file in tqdm(wav_files, file=SAFE_STDOUT):
file_name = os.path.basename(wav_file) file_name = os.path.basename(wav_file)

View File

@@ -4,6 +4,7 @@ import os
import gradio as gr import gradio as gr
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import numpy as np import numpy as np
from umap import UMAP
from scipy.spatial.distance import cdist from scipy.spatial.distance import cdist
from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans from sklearn.cluster import DBSCAN, AgglomerativeClustering, KMeans
from sklearn.manifold import TSNE from sklearn.manifold import TSNE
@@ -17,16 +18,17 @@ MAX_AUDIO_NUM = 10
tsne = TSNE(n_components=2, random_state=42, metric="cosine") tsne = TSNE(n_components=2, random_state=42, metric="cosine")
umap = UMAP(n_components=2, random_state=42, metric="cosine")
wav_files = [] wav_files = []
x = np.array([]) x = np.array([])
x_tsne = None x_reduced = None
mean = np.array([]) mean = np.array([])
centroids = [] centroids = []
def load(model_name): def load(model_name, reduction_method):
global wav_files, x, x_tsne, mean global wav_files, x, x_reduced, mean
wavs_dir = os.path.join("Data", model_name, "wavs") wavs_dir = os.path.join("Data", model_name, "wavs")
style_vector_files = [ style_vector_files = [
os.path.join(wavs_dir, f) for f in os.listdir(wavs_dir) if f.endswith(".npy") 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] style_vectors = [np.load(f) for f in style_vector_files]
x = np.array(style_vectors) x = np.array(style_vectors)
mean = np.mean(x, axis=0) mean = np.mean(x, axis=0)
if reduction_method == "t-SNE":
x_tsne = tsne.fit_transform(x) 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.figure(figsize=(6, 6))
plt.scatter(x_tsne[:, 0], x_tsne[:, 1]) plt.scatter(x_reduced[:, 0], x_reduced[:, 1])
return plt return plt
def do_clustering(n_clusters=4, method="KMeans"): def do_clustering(n_clusters=4, method="KMeans"):
global centroids, x_tsne global centroids, x_reduced
if method == "KMeans": if method == "KMeans":
model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto") model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto")
y_pred = model.fit_predict(x) y_pred = model.fit_predict(x)
elif method == "Agglomerative": elif method == "Agglomerative":
model = AgglomerativeClustering(n_clusters=n_clusters) model = AgglomerativeClustering(n_clusters=n_clusters)
y_pred = model.fit_predict(x) y_pred = model.fit_predict(x)
elif method == "KMeans after t-SNE": elif method == "KMeans after reduction":
if x_tsne is None: assert x_reduced is not None
x_tsne = tsne.fit_transform(x)
model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto") model = KMeans(n_clusters=n_clusters, random_state=42, n_init="auto")
y_pred = model.fit_predict(x_tsne) y_pred = model.fit_predict(x_reduced)
elif method == "Agglomerative after t-SNE": elif method == "Agglomerative after reduction":
if x_tsne is None: assert x_reduced is not None
x_tsne = tsne.fit_transform(x)
model = AgglomerativeClustering(n_clusters=n_clusters) model = AgglomerativeClustering(n_clusters=n_clusters)
y_pred = model.fit_predict(x_tsne) y_pred = model.fit_predict(x_reduced)
else: else:
raise ValueError("Invalid method") 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): 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) model = DBSCAN(eps=eps, min_samples=min_samples)
if x_tsne is None: assert x_reduced is not None
x_tsne = tsne.fit_transform(x) y_pred = model.fit_predict(x_reduced)
y_pred = model.fit_predict(x_tsne)
n_clusters = max(y_pred) + 1 n_clusters = max(y_pred) + 1
centroids = [] centroids = []
for i in range(n_clusters): for i in range(n_clusters):
@@ -84,11 +87,15 @@ def do_dbscan(eps=2.5, min_samples=15):
return y_pred, centroids 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を強調した点からの距離が最も近い音声を選ぶ
centroid_enhanced = mean + weight * (centroids - mean) 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] 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): 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) y_pred, centroids = do_dbscan(eps, min_samples)
if x_tsne is None: assert x_reduced is not None
x_tsne = tsne.fit_transform(x)
cmap = plt.get_cmap("tab10") cmap = plt.get_cmap("tab10")
plt.figure(figsize=(6, 6)) plt.figure(figsize=(6, 6))
for i in range(max(y_pred) + 1): for i in range(max(y_pred) + 1):
plt.scatter( plt.scatter(
x_tsne[y_pred == i, 0], x_reduced[y_pred == i, 0],
x_tsne[y_pred == i, 1], x_reduced[y_pred == i, 1],
color=cmap(i), color=cmap(i),
label=f"Style {i + 1}", label=f"Style {i + 1}",
) )
# Noise cluster (-1) is black # Noise cluster (-1) is black
plt.scatter( plt.scatter(
x_tsne[y_pred == -1, 0], x_reduced[y_pred == -1, 0],
x_tsne[y_pred == -1, 1], x_reduced[y_pred == -1, 1],
color="black", color="black",
label="Noise", 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"): 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) y_pred, centroids = do_clustering(n_clusters, method)
if x_tsne is None: assert x_reduced is not None
x_tsne = tsne.fit_transform(x)
cmap = plt.get_cmap("tab10") cmap = plt.get_cmap("tab10")
plt.figure(figsize=(6, 6)) plt.figure(figsize=(6, 6))
for i in range(n_clusters): for i in range(n_clusters):
plt.scatter( plt.scatter(
x_tsne[y_pred == i, 0], x_reduced[y_pred == i, 0],
x_tsne[y_pred == i, 1], x_reduced[y_pred == i, 1],
color=cmap(i), color=cmap(i),
label=f"Style {i + 1}", label=f"Style {i + 1}",
) )
@@ -294,10 +298,17 @@ DBSCANという方法でスタイル分けを行います。
with gr.Blocks(theme="NoCrypt/miku") as app: with gr.Blocks(theme="NoCrypt/miku") as app:
gr.Markdown(initial_md) gr.Markdown(initial_md)
with gr.Row(): 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") load_button = gr.Button("スタイルベクトルを読み込む", variant="primary")
output = gr.Plot(label="音声スタイルの可視化") 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: スタイル分けを自動で行う"):
with gr.Tab("スタイル分け1"): with gr.Tab("スタイル分け1"):
n_clusters = gr.Slider( n_clusters = gr.Slider(
@@ -310,14 +321,14 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
) )
c_method = gr.Radio( c_method = gr.Radio(
choices=[ choices=[
"Agglomerative after t-SNE", "Agglomerative after reduction",
"KMeans after t-SNE", "KMeans after reduction",
"Agglomerative", "Agglomerative",
"KMeans", "KMeans",
], ],
label="アルゴリズム", label="アルゴリズム",
info="分類する(クラスタリング)アルゴリズムを選択します。いろいろ試してみてください。", info="分類する(クラスタリング)アルゴリズムを選択します。いろいろ試してみてください。",
value="Agglomerative after t-SNE", value="Agglomerative after reduction",
) )
c_button = gr.Button("スタイル分けを実行") c_button = gr.Button("スタイル分けを実行")
with gr.Tab("スタイル分け2: DBSCAN"): with gr.Tab("スタイル分け2: DBSCAN"):
@@ -325,14 +336,14 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
eps = gr.Slider( eps = gr.Slider(
minimum=0.1, minimum=0.1,
maximum=10, maximum=10,
step=0.1, step=0.05,
value=2.5, value=2.5,
label="eps", label="eps",
info="小さいほどスタイル数が増える", info="小さいほどスタイル数が増える",
) )
min_samples = gr.Slider( min_samples = gr.Slider(
minimum=1, minimum=1,
maximum=100, maximum=50,
step=1, step=1,
value=15, value=15,
label="min_samples", label="min_samples",