This commit is contained in:
litagin02
2024-01-08 21:06:18 +09:00
parent 7358f5e426
commit cafb7a13c4
5 changed files with 58 additions and 64 deletions

7
app.py
View File

@@ -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(

View File

@@ -3,11 +3,12 @@
## v1.3 ## v1.3
### 大きい変更 ### 大きい変更
- 日本語の発音・アクセント処理部分のバグを大幅に改良 - 元々のBert-VITS2に存在した、日本語の発音・アクセント処理部分のバグを修正
- `車両``シャリヨオ``見つける``ミッケル`発音・学習されており、その単語のアクセント情報が全て死んでいた - `車両``シャリヨオ``思う``オモオ``見つける``ミッケル`等に発音・学習されており、その単語以降のアクセント情報が全て死んでいた
- `私はそれを見る`のアクセントが`ワ➚タシ➘ワ ソ➚レ➘オ ミ➘ル`だったのを`ワ➚タシワ ソ➚レ オ ミ➘ル`に修正 - `私はそれを見る`のアクセントが`ワ➚タシ➘ワ ソ➚レ➘オ ミ➘ル`だったのを`ワ➚タシワ ソ➚レオ ミ➘ル`に修正
- アクセントの誤りを修正して音声合成できるように(万能制御ではない)。 - アクセントの誤りを修正して音声合成できるように(完全に制御できるわけではないが改善される場合がある)。
- これまでのモデルにも使える。アクセントや発音等が改善される可能性あり。学習し直すとよりよくなる可能性もある。
これまでのモデルもこれまで通り使え、アクセントや発音等が改善される可能性があります。新しいバージョンで学習し直すとより良くなる可能性もあります。
### 改善 ### 改善
- `Dataset.bat`の音声スライスと書き起こしをよりカスタマイズできるように秒数指定や書き起こしのWhisperモデル指定や言語指定等 - `Dataset.bat`の音声スライスと書き起こしをよりカスタマイズできるように秒数指定や書き起こしのWhisperモデル指定や言語指定等

View File

@@ -25,4 +25,5 @@ safetensors
scipy scipy
tensorboard tensorboard
torch # For users without GPU torch # For users without GPU
transformers transformers
umap-learn

View File

@@ -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

View File

@@ -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)