Update
This commit is contained in:
7
app.py
7
app.py
@@ -287,10 +287,11 @@ if __name__ == "__main__":
|
|||||||
step=0.1,
|
step=0.1,
|
||||||
label="分けた場合に挟む無音の長さ(秒)",
|
label="分けた場合に挟む無音の長さ(秒)",
|
||||||
)
|
)
|
||||||
tone = gr.Textbox(label="アクセント指定")
|
tone = gr.Textbox(
|
||||||
use_tone = gr.Checkbox(
|
label="アクセント指定",
|
||||||
label="アクセント指定を使う", info="改行で分けない場合のみ使えます", value=False
|
info="改行で分けない場合のみ使えます",
|
||||||
)
|
)
|
||||||
|
use_tone = gr.Checkbox(label="アクセント指定を使う", value=False)
|
||||||
language = gr.Dropdown(choices=languages, value="JP", label="Language")
|
language = gr.Dropdown(choices=languages, value="JP", label="Language")
|
||||||
with gr.Accordion(label="詳細設定", open=False):
|
with gr.Accordion(label="詳細設定", open=False):
|
||||||
sdp_ratio = gr.Slider(
|
sdp_ratio = gr.Slider(
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
## v1.3
|
## v1.3
|
||||||
|
|
||||||
### 大きい変更
|
### 大きい変更
|
||||||
- 日本語の発音・アクセント処理部分のバグを大幅に改良
|
- 元々のBert-VITS2に存在した、日本語の発音・アクセント処理部分のバグを修正
|
||||||
- `車両`が`シャリヨオ`や`見つける`が`ミッケル`と発音・学習されており、その単語のアクセント情報が全て死んでいた
|
- `車両`が`シャリヨオ`、`思う`が`オモオ`、`見つける`が`ミッケル`等に発音・学習されており、その単語以降のアクセント情報が全て死んでいた
|
||||||
- `私はそれを見る`のアクセントが`ワ➚タシ➘ワ ソ➚レ➘オ ミ➘ル`だったのを`ワ➚タシワ ソ➚レ オ ミ➘ル`に修正
|
- `私はそれを見る`のアクセントが`ワ➚タシ➘ワ ソ➚レ➘オ ミ➘ル`だったのを`ワ➚タシワ ソ➚レオ ミ➘ル`に修正
|
||||||
- アクセントの誤りを修正して音声合成できるように(万能制御ではない)。
|
- アクセントの誤りを修正して音声合成できるように(完全に制御できるわけではないが改善される場合がある)。
|
||||||
- これまでのモデルにも使える。アクセントや発音等が改善される可能性あり。学習し直すとよりよくなる可能性もある。
|
|
||||||
|
これまでのモデルもこれまで通り使え、アクセントや発音等が改善される可能性があります。新しいバージョンで学習し直すとより良くなる可能性もあります。
|
||||||
|
|
||||||
### 改善
|
### 改善
|
||||||
- `Dataset.bat`の音声スライスと書き起こしをよりカスタマイズできるように(秒数指定や書き起こしのWhisperモデル指定や言語指定等)
|
- `Dataset.bat`の音声スライスと書き起こしをよりカスタマイズできるように(秒数指定や書き起こしのWhisperモデル指定や言語指定等)
|
||||||
|
|||||||
@@ -25,4 +25,5 @@ safetensors
|
|||||||
scipy
|
scipy
|
||||||
tensorboard
|
tensorboard
|
||||||
torch # For users without GPU
|
torch # For users without GPU
|
||||||
transformers
|
transformers
|
||||||
|
umap-learn
|
||||||
@@ -218,8 +218,6 @@ def phone_tone2kata_tone(phone_tone: list[tuple[str, int]]) -> list[tuple[str, i
|
|||||||
phone_tone = phone_tone[1:] # 最初の("_", 0)を無視
|
phone_tone = phone_tone[1:] # 最初の("_", 0)を無視
|
||||||
phones = [phone for phone, _ in phone_tone]
|
phones = [phone for phone, _ in phone_tone]
|
||||||
tones = [tone for _, tone in phone_tone]
|
tones = [tone for _, tone in phone_tone]
|
||||||
logger.debug(f"phones: {phones}")
|
|
||||||
logger.debug(f"tones: {tones}")
|
|
||||||
result: list[tuple[str, int]] = []
|
result: list[tuple[str, int]] = []
|
||||||
current_mora = ""
|
current_mora = ""
|
||||||
for phone, next_phone, tone, next_tone in zip(phones, phones[1:], tones, tones[1:]):
|
for phone, next_phone, tone, next_tone in zip(phones, phones[1:], tones, tones[1:]):
|
||||||
@@ -232,8 +230,6 @@ def phone_tone2kata_tone(phone_tone: list[tuple[str, int]]) -> list[tuple[str, i
|
|||||||
assert tone == next_tone, f"Unexpected {phone} tone {tone} != {next_tone}"
|
assert tone == next_tone, f"Unexpected {phone} tone {tone} != {next_tone}"
|
||||||
current_mora = phone
|
current_mora = phone
|
||||||
elif phone == "n":
|
elif phone == "n":
|
||||||
logger.debug(f"current_mora: {current_mora}")
|
|
||||||
logger.debug(f"next_phone: {next_phone}")
|
|
||||||
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
|
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
|
||||||
if next_phone not in VOWELS: # 次の音素が母音でない場合
|
if next_phone not in VOWELS: # 次の音素が母音でない場合
|
||||||
result.append(("ン", tone))
|
result.append(("ン", tone))
|
||||||
@@ -248,7 +244,6 @@ def phone_tone2kata_tone(phone_tone: list[tuple[str, int]]) -> list[tuple[str, i
|
|||||||
current_mora += phone
|
current_mora += phone
|
||||||
result.append((mora_phonemes_to_mora_kata[current_mora], tone))
|
result.append((mora_phonemes_to_mora_kata[current_mora], tone))
|
||||||
current_mora = ""
|
current_mora = ""
|
||||||
logger.debug(f"result: {result}")
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -279,7 +274,7 @@ def g2phone_tone_wo_punct(text: str) -> list[tuple[str, int]]:
|
|||||||
[('k', 0), ('o', 0), ('n', 1), ('n', 1), ('i', 1), ('ch', 1), ('i', 1), ('w', 1), ('a', 1), ('s', 1), ('e', 1), ('k', 0), ('a', 0), ('i', 0), ('i', 0), ('g', 1), ('e', 1), ('n', 0), ('k', 0), ('i', 0)]
|
[('k', 0), ('o', 0), ('n', 1), ('n', 1), ('i', 1), ('ch', 1), ('i', 1), ('w', 1), ('a', 1), ('s', 1), ('e', 1), ('k', 0), ('a', 0), ('i', 0), ('i', 0), ('g', 1), ('e', 1), ('n', 0), ('k', 0), ('i', 0)]
|
||||||
"""
|
"""
|
||||||
prosodies = pyopenjtalk_g2p_prosody(text, drop_unvoiced_vowels=True)
|
prosodies = pyopenjtalk_g2p_prosody(text, drop_unvoiced_vowels=True)
|
||||||
logger.debug(f"prosodies: {prosodies}")
|
# logger.debug(f"prosodies: {prosodies}")
|
||||||
result: list[tuple[str, int]] = []
|
result: list[tuple[str, int]] = []
|
||||||
current_phrase: list[tuple[str, int]] = []
|
current_phrase: list[tuple[str, int]] = []
|
||||||
current_tone = 0
|
current_tone = 0
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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 umap import UMAP
|
||||||
from scipy.spatial.distance import cdist
|
from scipy.spatial.distance import pdist, squareform
|
||||||
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,12 +17,12 @@ MAX_CLUSTER_NUM = 10
|
|||||||
MAX_AUDIO_NUM = 10
|
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", n_jobs=1, min_dist=0.0)
|
||||||
umap = UMAP(n_components=2, random_state=42, metric="cosine")
|
|
||||||
|
|
||||||
wav_files = []
|
wav_files = []
|
||||||
x = np.array([])
|
x = np.array([])
|
||||||
x_reduced = None
|
x_reduced = None
|
||||||
|
y_pred = np.array([])
|
||||||
mean = np.array([])
|
mean = np.array([])
|
||||||
centroids = []
|
centroids = []
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ def load(model_name, reduction_method):
|
|||||||
|
|
||||||
|
|
||||||
def do_clustering(n_clusters=4, method="KMeans"):
|
def do_clustering(n_clusters=4, method="KMeans"):
|
||||||
global centroids, x_reduced
|
global centroids, x_reduced, y_pred
|
||||||
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)
|
||||||
@@ -76,7 +76,7 @@ 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_reduced
|
global centroids, x_reduced, y_pred
|
||||||
model = DBSCAN(eps=eps, min_samples=min_samples)
|
model = DBSCAN(eps=eps, min_samples=min_samples)
|
||||||
assert x_reduced is not None
|
assert x_reduced is not None
|
||||||
y_pred = model.fit_predict(x_reduced)
|
y_pred = model.fit_predict(x_reduced)
|
||||||
@@ -87,19 +87,22 @@ 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=1):
|
def representative_wav_files(cluster_id, num_files=1):
|
||||||
# centroidを強調した点からの距離が最も近い音声を選ぶ
|
# y_predの中でcluster_indexに関するメドイドを探す
|
||||||
centroid_enhanced = mean + weight * (centroids - mean)
|
cluster_indices = np.where(y_pred == cluster_id)[0]
|
||||||
# セントロイドと全ての点との距離を計算
|
cluster_vectors = x[cluster_indices]
|
||||||
distances = cdist(
|
# クラスタ内の全ベクトル間の距離を計算
|
||||||
umap.transform(centroid_enhanced[cluster_index : cluster_index + 1]),
|
distances = pdist(cluster_vectors)
|
||||||
x_reduced,
|
distance_matrix = squareform(distances)
|
||||||
metric="euclidean",
|
|
||||||
)
|
|
||||||
# 距離が小さい順にソートし、上位のインデックスを取得
|
|
||||||
closest_indices = np.argsort(distances[0])[:num_files]
|
|
||||||
|
|
||||||
return closest_indices
|
# 各ベクトルと他の全ベクトルとの平均距離を計算
|
||||||
|
mean_distances = distance_matrix.mean(axis=1)
|
||||||
|
|
||||||
|
# 平均距離が最も小さい順にnum_files個のインデックスを取得
|
||||||
|
closest_indices = np.argsort(mean_distances)[:num_files]
|
||||||
|
|
||||||
|
# 最も近いメドイドの元のインデックスを取得
|
||||||
|
return cluster_indices[closest_indices]
|
||||||
|
|
||||||
|
|
||||||
def do_dbscan_gradio(eps=2.5, min_samples=15):
|
def do_dbscan_gradio(eps=2.5, min_samples=15):
|
||||||
@@ -149,9 +152,9 @@ def do_dbscan_gradio(eps=2.5, min_samples=15):
|
|||||||
] * MAX_AUDIO_NUM
|
] * MAX_AUDIO_NUM
|
||||||
|
|
||||||
|
|
||||||
def closest_wav_files_gradio(cluster_index, num_files=1, weight=1):
|
def representative_wav_files_gradio(cluster_id, num_files=1):
|
||||||
cluster_index = cluster_index - 1 # UIでは1から始まるので0からにする
|
cluster_id = cluster_id - 1 # UIでは1から始まるので0からにする
|
||||||
closest_indices = closest_wav_files(cluster_index, num_files, weight)
|
closest_indices = representative_wav_files(cluster_id, num_files)
|
||||||
return [
|
return [
|
||||||
gr.Audio(wav_files[i], visible=True, label=wav_files[i])
|
gr.Audio(wav_files[i], visible=True, label=wav_files[i])
|
||||||
for i in closest_indices
|
for i in closest_indices
|
||||||
@@ -179,7 +182,7 @@ def do_clustering_gradio(n_clusters=4, method="KMeans"):
|
|||||||
] * MAX_AUDIO_NUM
|
] * MAX_AUDIO_NUM
|
||||||
|
|
||||||
|
|
||||||
def save_style_vectors(model_name, style_names: str):
|
def save_style_vectors_from_clustering(model_name, style_names: str):
|
||||||
"""centerとcentroidsを保存する"""
|
"""centerとcentroidsを保存する"""
|
||||||
result_dir = os.path.join(config.assets_root, model_name)
|
result_dir = os.path.join(config.assets_root, model_name)
|
||||||
os.makedirs(result_dir, exist_ok=True)
|
os.makedirs(result_dir, exist_ok=True)
|
||||||
@@ -286,20 +289,23 @@ method1 = """
|
|||||||
|
|
||||||
dbscan_md = """
|
dbscan_md = """
|
||||||
DBSCANという方法でスタイル分けを行います。
|
DBSCANという方法でスタイル分けを行います。
|
||||||
こちらの方が方法1よりも特徴がより良く出るものができるかもしれません。
|
こちらの方が方法1よりも特徴がはっきり出るもののみを取り出せ、よいスタイルベクトルが作れるかもしれません。
|
||||||
|
|
||||||
ただし事前にスタイル数は指定できません。
|
ただし事前にスタイル数は指定できません。
|
||||||
|
|
||||||
パラメータ:
|
パラメータ:
|
||||||
- eps: この値より近い点同士をどんどん繋げて同じスタイル分類とする。小さいほどスタイル数が増え、大きいほどスタイル数が減る。
|
- eps: この値より近い点同士をどんどん繋げて同じスタイル分類とする。小さいほどスタイル数が増え、大きいほどスタイル数が減る傾向。
|
||||||
- min_samples: クラスタとみなすために必要な点の数。小さいほどスタイル数が増え、大きいほどスタイル数が減る。
|
- min_samples: ある点をスタイルの核となる点とみなすために必要な近傍の点の数。小さいほどスタイル数が増え、大きいほどスタイル数が減る傾向。
|
||||||
|
|
||||||
|
UMAPの場合はepsは0.3くらい、t-SNEの場合は2.5くらいがいいかもしれません。min_samplesはデータ数に依存するのでいろいろ試してみてください。
|
||||||
|
|
||||||
|
詳細:
|
||||||
|
https://ja.wikipedia.org/wiki/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(placeholder="your_model_name", label="モデル名")
|
model_name = gr.Textbox(placeholder="your_model_name", label="モデル名")
|
||||||
# 削減方法を選択:UMAP or t-SNE
|
|
||||||
reduction_method = gr.Radio(
|
reduction_method = gr.Radio(
|
||||||
choices=["UMAP", "t-SNE"],
|
choices=["UMAP", "t-SNE"],
|
||||||
label="次元削減方法",
|
label="次元削減方法",
|
||||||
@@ -336,8 +342,8 @@ 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.05,
|
step=0.01,
|
||||||
value=2.5,
|
value=0.3,
|
||||||
label="eps",
|
label="eps",
|
||||||
info="小さいほどスタイル数が増える",
|
info="小さいほどスタイル数が増える",
|
||||||
)
|
)
|
||||||
@@ -377,9 +383,7 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
|
|||||||
with gr.Row():
|
with gr.Row():
|
||||||
audio_list = []
|
audio_list = []
|
||||||
for i in range(MAX_AUDIO_NUM):
|
for i in range(MAX_AUDIO_NUM):
|
||||||
audio_list.append(
|
audio_list.append(gr.Audio(visible=False, show_label=True))
|
||||||
gr.Audio(visible=False, scale=1, show_label=True)
|
|
||||||
)
|
|
||||||
c_button.click(
|
c_button.click(
|
||||||
do_clustering_gradio,
|
do_clustering_gradio,
|
||||||
inputs=[n_clusters, c_method],
|
inputs=[n_clusters, c_method],
|
||||||
@@ -391,7 +395,7 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
|
|||||||
outputs=[gr_plot, cluster_index, num_styles_result] + audio_list,
|
outputs=[gr_plot, cluster_index, num_styles_result] + audio_list,
|
||||||
)
|
)
|
||||||
get_audios_button.click(
|
get_audios_button.click(
|
||||||
closest_wav_files_gradio,
|
representative_wav_files_gradio,
|
||||||
inputs=[cluster_index, num_files],
|
inputs=[cluster_index, num_files],
|
||||||
outputs=audio_list,
|
outputs=audio_list,
|
||||||
)
|
)
|
||||||
@@ -402,11 +406,13 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
|
|||||||
info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。",
|
info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。",
|
||||||
)
|
)
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
save_button = gr.Button("スタイルベクトルを保存", variant="primary")
|
save_button1 = gr.Button("スタイルベクトルを保存", variant="primary")
|
||||||
info2 = gr.Textbox(label="保存結果")
|
info2 = gr.Textbox(label="保存結果")
|
||||||
|
|
||||||
save_button.click(
|
save_button1.click(
|
||||||
save_style_vectors, inputs=[model_name, style_names], outputs=[info2]
|
save_style_vectors_from_clustering,
|
||||||
|
inputs=[model_name, style_names],
|
||||||
|
outputs=[info2],
|
||||||
)
|
)
|
||||||
with gr.Tab("方法2: 手動でスタイルを選ぶ"):
|
with gr.Tab("方法2: 手動でスタイルを選ぶ"):
|
||||||
gr.Markdown("下のテキスト欄に、各スタイルの代表音声のファイル名を`,`区切りで、その横に対応するスタイル名を`,`区切りで入力してください。")
|
gr.Markdown("下のテキスト欄に、各スタイルの代表音声のファイル名を`,`区切りで、その横に対応するスタイル名を`,`区切りで入力してください。")
|
||||||
@@ -422,21 +428,11 @@ with gr.Blocks(theme="NoCrypt/miku") as app:
|
|||||||
label="スタイル名", placeholder="Angry, Sad, Happy"
|
label="スタイル名", placeholder="Angry, Sad, Happy"
|
||||||
)
|
)
|
||||||
with gr.Row():
|
with gr.Row():
|
||||||
save_button3 = gr.Button("スタイルベクトルを保存", variant="primary")
|
save_button2 = gr.Button("スタイルベクトルを保存", variant="primary")
|
||||||
info3 = gr.Textbox(label="保存結果")
|
info2 = gr.Textbox(label="保存結果")
|
||||||
save_button3.click(
|
save_button2.click(
|
||||||
save_style_vectors_from_files,
|
save_style_vectors_from_files,
|
||||||
inputs=[model_name, audio_files_text, style_names_text],
|
inputs=[model_name, audio_files_text, style_names_text],
|
||||||
outputs=[info3],
|
outputs=[info2],
|
||||||
)
|
)
|
||||||
gr.Markdown("結果が良さそうなら、これを保存します。")
|
|
||||||
style_names2 = gr.Textbox(
|
|
||||||
"Angry, Sad, Happy",
|
|
||||||
label="スタイルの名前",
|
|
||||||
info=f"スタイルの名前を`,`で区切って入力してください(日本語可)。例: `Angry, Sad, Happy`や`怒り, 悲しみ, 喜び`など。平均音声は{DEFAULT_STYLE}として自動的に保存されます。",
|
|
||||||
)
|
|
||||||
with gr.Row():
|
|
||||||
save_button2 = gr.Button("スタイルベクトルを保存", variant="primary")
|
|
||||||
info4 = gr.Textbox(label="保存結果")
|
|
||||||
|
|
||||||
app.launch(inbrowser=True)
|
app.launch(inbrowser=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user