This commit is contained in:
litagin02
2024-02-02 21:11:23 +09:00
parent 7a8477b9bc
commit e566ac62f3
17 changed files with 1535 additions and 1162 deletions

5
.gitignore vendored
View File

@@ -15,6 +15,11 @@ venv/
/pretrained/*.safetensors
/pretrained/*.pth
/pretrained_jp_extra/*.safetensors
/pretrained_jp_extra/*.pth
/slm/*/*.bin
/scripts/test/
*.zip
*.csv

View File

@@ -67,5 +67,5 @@
"use_spectral_norm": false,
"gin_channels": 256
},
"version": "1.3"
"version": "2.0"
}

View File

@@ -0,0 +1,77 @@
{
"train": {
"log_interval": 200,
"eval_interval": 1000,
"seed": 42,
"epochs": 1000,
"learning_rate": 0.0001,
"betas": [0.8, 0.99],
"eps": 1e-9,
"batch_size": 24,
"bf16_run": false,
"fp16_run": false,
"lr_decay": 0.99996,
"segment_size": 16384,
"init_lr_ratio": 1,
"warmup_epochs": 0,
"c_mel": 45,
"c_kl": 1.0,
"c_commit": 100,
"skip_optimizer": true,
"freeze_ZH_bert": false,
"freeze_JP_bert": false,
"freeze_EN_bert": false,
"freeze_emo": false,
"freeze_style": false
},
"data": {
"training_files": "filelists/train.list",
"validation_files": "filelists/val.list",
"max_wav_value": 32768.0,
"sampling_rate": 44100,
"filter_length": 2048,
"hop_length": 512,
"win_length": 2048,
"n_mel_channels": 128,
"mel_fmin": 0.0,
"mel_fmax": null,
"add_blank": true,
"n_speakers": 512,
"cleaned_text": true
},
"model": {
"use_spk_conditioned_encoder": true,
"use_noise_scaled_mas": true,
"use_mel_posterior_encoder": false,
"use_duration_discriminator": false,
"use_wavlm_discriminator": true,
"inter_channels": 192,
"hidden_channels": 192,
"filter_channels": 768,
"n_heads": 2,
"n_layers": 6,
"kernel_size": 3,
"p_dropout": 0.1,
"resblock": "1",
"resblock_kernel_sizes": [3, 7, 11],
"resblock_dilation_sizes": [
[1, 3, 5],
[1, 3, 5],
[1, 3, 5]
],
"upsample_rates": [8, 8, 2, 2, 2],
"upsample_initial_channel": 512,
"upsample_kernel_sizes": [16, 16, 8, 2, 2],
"n_layers_q": 3,
"use_spectral_norm": false,
"gin_channels": 512,
"slm": {
"model": "./slm/wavlm-base-plus",
"sr": 16000,
"hidden": 768,
"nlayers": 13,
"initial_channel": 64
}
},
"version": "2.0-JP-Extra"
}

View File

@@ -362,35 +362,9 @@ class TextEncoder(nn.Module):
self.language_emb = nn.Embedding(num_languages, hidden_channels)
nn.init.normal_(self.language_emb.weight, 0.0, hidden_channels**-0.5)
self.bert_proj = nn.Conv1d(1024, hidden_channels, 1)
#self.bert_pre_proj = nn.Conv1d(2048, 1024, 1)
# self.en_bert_proj = nn.Conv1d(1024, hidden_channels, 1)
self.in_feature_net = nn.Sequential(
# input is assumed to an already normalized embedding
nn.Linear(512, 1028, bias=False),
nn.GELU(),
nn.LayerNorm(1028),
*[Block(1028, 512) for _ in range(1)],
nn.Linear(1028, 512, bias=False),
# normalize before passing to VQ?
# nn.GELU(),
# nn.LayerNorm(512),
)
self.emo_vq = VectorQuantize(
dim=512,
# codebook_size=128,
codebook_size=256,
codebook_dim=16,
# codebook_dim=32,
commitment_weight=0.1,
decay=0.99,
heads=32,
kmeans_iters=20,
separate_codebook_per_head=True,
stochastic_sample_codes=True,
threshold_ema_dead_code=2,
use_cosine_sim = True,
)
self.out_feature_net = nn.Linear(512, hidden_channels)
# Remove emo_vq since it's not working well.
self.style_proj = nn.Linear(256, hidden_channels)
self.encoder = attentions.Encoder(
hidden_channels,
@@ -403,20 +377,15 @@ class TextEncoder(nn.Module):
)
self.proj = nn.Conv1d(hidden_channels, out_channels * 2, 1)
def forward(self, x, x_lengths, tone, language, bert, emo, g=None):
def forward(self, x, x_lengths, tone, language, bert, style_vec, g=None):
bert_emb = self.bert_proj(bert).transpose(1, 2)
# en_bert_emb = self.en_bert_proj(en_bert).transpose(1, 2)
emo_emb = self.in_feature_net(emo)
emo_emb, _, loss_commit = self.emo_vq(emo_emb.unsqueeze(1))
loss_commit = loss_commit.mean()
emo_emb = self.out_feature_net(emo_emb)
style_emb = self.style_proj(style_vec.unsqueeze(1))
x = (
self.emb(x)
+ self.tone_emb(tone)
+ self.language_emb(language)
+ bert_emb
# + en_bert_emb
+ emo_emb
+ style_emb
) * math.sqrt(
self.hidden_channels
) # [b, t, h]
@@ -429,7 +398,7 @@ class TextEncoder(nn.Module):
stats = self.proj(x) * x_mask
m, logs = torch.split(stats, self.out_channels, dim=1)
return x, m, logs, x_mask, loss_commit
return x, m, logs, x_mask
class ResidualCouplingBlock(nn.Module):
@@ -976,14 +945,14 @@ class SynthesizerTrn(nn.Module):
tone,
language,
bert,
emo,
style_vec,
):
if self.n_speakers > 0:
g = self.emb_g(sid).unsqueeze(-1) # [b, h, 1]
else:
g = self.ref_enc(y.transpose(1, 2)).unsqueeze(-1)
x, m_p, logs_p, x_mask, loss_commit = self.enc_p(
x, x_lengths, tone, language, bert, emo, g=g
x, m_p, logs_p, x_mask = self.enc_p(
x, x_lengths, tone, language, bert, style_vec, g=g
)
z, m_q, logs_q, y_mask = self.enc_q(y, y_lengths, g=g)
z_p = self.flow(z, y_mask, g=g)
@@ -1052,7 +1021,6 @@ class SynthesizerTrn(nn.Module):
(z, z_p, m_p, logs_p, m_q, logs_q),
(x, logw, logw_), # , logw_sdp),
g,
loss_commit,
)
def infer(
@@ -1063,7 +1031,7 @@ class SynthesizerTrn(nn.Module):
tone,
language,
bert,
emo,
style_vec,
noise_scale=0.667,
length_scale=1,
noise_scale_w=0.8,
@@ -1077,8 +1045,8 @@ class SynthesizerTrn(nn.Module):
g = self.emb_g(sid).unsqueeze(-1) # [b, h, 1]
else:
g = self.ref_enc(y.transpose(1, 2)).unsqueeze(-1)
x, m_p, logs_p, x_mask, _ = self.enc_p(
x, x_lengths, tone, language, bert, emo, g=g
x, m_p, logs_p, x_mask = self.enc_p(
x, x_lengths, tone, language, bert, style_vec, g=g
)
logw = self.sdp(x, x_mask, g=g, reverse=True, noise_scale=noise_scale_w) * (
sdp_ratio

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ preprocess_text_config = config.preprocess_text_config
@click.option("--max-val-total", default=preprocess_text_config.max_val_total)
@click.option("--clean/--no-clean", default=preprocess_text_config.clean)
@click.option("-y", "--yml_config")
@click.option("--use_jp_extra", is_flag=True)
def preprocess(
transcription_path: str,
cleaned_path: Optional[str],
@@ -43,6 +44,7 @@ def preprocess(
max_val_total: int,
clean: bool,
yml_config: str, # 这个不要删
use_jp_extra: bool,
):
if cleaned_path == "" or cleaned_path is None:
cleaned_path = transcription_path + ".cleaned"
@@ -53,7 +55,9 @@ def preprocess(
for line in tqdm(trans_file, file=SAFE_STDOUT):
try:
utt, spk, language, text = line.strip().split("|")
norm_text, phones, tones, word2ph = clean_text(text, language)
norm_text, phones, tones, word2ph = clean_text(
text, language, use_jp_extra
)
out_file.write(
"{}|{}|{}|{}|{}|{}|{}\n".format(
utt,

115
safetensors.ipynb Normal file
View File

@@ -0,0 +1,115 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import torch\n",
"\n",
"model_dir = \"pretrained-Japanese-Extra\"\n",
"step = 0\n",
"\n",
"g_model = torch.load(f\"{model_dir}/G_{step}.pth\", map_location=\"cpu\")\n",
"d_model = torch.load(f\"{model_dir}/D_{step}.pth\", map_location=\"cpu\")\n",
"wd_model = torch.load(f\"{model_dir}/WD_{step}.pth\", map_location=\"cpu\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"skip enc_p.in_feature_net.0.weight\n",
"skip enc_p.in_feature_net.2.weight\n",
"skip enc_p.in_feature_net.2.bias\n",
"skip enc_p.in_feature_net.3.norm.weight\n",
"skip enc_p.in_feature_net.3.norm.bias\n",
"skip enc_p.in_feature_net.3.mlp.c_fc1.weight\n",
"skip enc_p.in_feature_net.3.mlp.c_fc2.weight\n",
"skip enc_p.in_feature_net.3.mlp.c_proj.weight\n",
"skip enc_p.in_feature_net.4.weight\n",
"skip enc_p.emo_vq._codebook.initted\n",
"skip enc_p.emo_vq._codebook.cluster_size\n",
"skip enc_p.emo_vq._codebook.embed_avg\n",
"skip enc_p.emo_vq._codebook.embed\n",
"skip enc_p.out_feature_net.weight\n",
"skip enc_p.out_feature_net.bias\n"
]
}
],
"source": [
"g_dict = {}\n",
"skip_list = [\"enc_p.in_feature_net.\", \"enc_p.emo_vq.\", \"enc_p.out_feature_net.\"]\n",
"for key in g_model[\"model\"].keys():\n",
" if any([key.startswith(s) for s in skip_list]):\n",
" print(f\"skip {key}\")\n",
" continue\n",
" else:\n",
" g_dict[key] = g_model[\"model\"][key]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"d_dict = {}\n",
"for key in d_model[\"model\"].keys():\n",
" d_dict[key] = d_model[\"model\"][key]"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"wd_dict = {}\n",
"for key in wd_model[\"model\"].keys():\n",
" wd_dict[key] = wd_model[\"model\"][key]"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"from safetensors.torch import save_file\n",
"\n",
"\n",
"save_file(g_dict, f\"G_0.safetensors\")\n",
"save_file (d_dict, f\"D_0.safetensors\")\n",
"save_file (wd_dict, f\"WD_0.safetensors\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

27
slm/wavlm-base-plus/.gitattributes vendored Normal file
View File

@@ -0,0 +1,27 @@
*.7z filter=lfs diff=lfs merge=lfs -text
*.arrow filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.bin.* filter=lfs diff=lfs merge=lfs -text
*.bz2 filter=lfs diff=lfs merge=lfs -text
*.ftz filter=lfs diff=lfs merge=lfs -text
*.gz filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.joblib filter=lfs diff=lfs merge=lfs -text
*.lfs.* filter=lfs diff=lfs merge=lfs -text
*.model filter=lfs diff=lfs merge=lfs -text
*.msgpack filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
*.ot filter=lfs diff=lfs merge=lfs -text
*.parquet filter=lfs diff=lfs merge=lfs -text
*.pb filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.rar filter=lfs diff=lfs merge=lfs -text
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.tar.* filter=lfs diff=lfs merge=lfs -text
*.tflite filter=lfs diff=lfs merge=lfs -text
*.tgz filter=lfs diff=lfs merge=lfs -text
*.xz filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zstandard filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text

View File

@@ -0,0 +1,65 @@
---
language:
- en
datasets:
tags:
- speech
inference: false
---
# WavLM-Base-Plus
[Microsoft's WavLM](https://github.com/microsoft/unilm/tree/master/wavlm)
The base model pretrained on 16kHz sampled speech audio. When using the model, make sure that your speech input is also sampled at 16kHz.
**Note**: This model does not have a tokenizer as it was pretrained on audio alone. In order to use this model **speech recognition**, a tokenizer should be created and the model should be fine-tuned on labeled text data. Check out [this blog](https://huggingface.co/blog/fine-tune-wav2vec2-english) for more in-detail explanation of how to fine-tune the model.
The model was pre-trained on:
- 60,000 hours of [Libri-Light](https://arxiv.org/abs/1912.07875)
- 10,000 hours of [GigaSpeech](https://arxiv.org/abs/2106.06909)
- 24,000 hours of [VoxPopuli](https://arxiv.org/abs/2101.00390)
[Paper: WavLM: Large-Scale Self-Supervised Pre-Training for Full Stack Speech Processing](https://arxiv.org/abs/2110.13900)
Authors: Sanyuan Chen, Chengyi Wang, Zhengyang Chen, Yu Wu, Shujie Liu, Zhuo Chen, Jinyu Li, Naoyuki Kanda, Takuya Yoshioka, Xiong Xiao, Jian Wu, Long Zhou, Shuo Ren, Yanmin Qian, Yao Qian, Jian Wu, Michael Zeng, Furu Wei
**Abstract**
*Self-supervised learning (SSL) achieves great success in speech recognition, while limited exploration has been attempted for other speech processing tasks. As speech signal contains multi-faceted information including speaker identity, paralinguistics, spoken content, etc., learning universal representations for all speech tasks is challenging. In this paper, we propose a new pre-trained model, WavLM, to solve full-stack downstream speech tasks. WavLM is built based on the HuBERT framework, with an emphasis on both spoken content modeling and speaker identity preservation. We first equip the Transformer structure with gated relative position bias to improve its capability on recognition tasks. For better speaker discrimination, we propose an utterance mixing training strategy, where additional overlapped utterances are created unsupervisely and incorporated during model training. Lastly, we scale up the training dataset from 60k hours to 94k hours. WavLM Large achieves state-of-the-art performance on the SUPERB benchmark, and brings significant improvements for various speech processing tasks on their representative benchmarks.*
The original model can be found under https://github.com/microsoft/unilm/tree/master/wavlm.
# Usage
This is an English pre-trained speech model that has to be fine-tuned on a downstream task like speech recognition or audio classification before it can be
used in inference. The model was pre-trained in English and should therefore perform well only in English. The model has been shown to work well on the [SUPERB benchmark](https://superbbenchmark.org/).
**Note**: The model was pre-trained on phonemes rather than characters. This means that one should make sure that the input text is converted to a sequence
of phonemes before fine-tuning.
## Speech Recognition
To fine-tune the model for speech recognition, see [the official speech recognition example](https://github.com/huggingface/transformers/tree/master/examples/pytorch/speech-recognition).
## Speech Classification
To fine-tune the model for speech classification, see [the official audio classification example](https://github.com/huggingface/transformers/tree/master/examples/pytorch/audio-classification).
## Speaker Verification
TODO
## Speaker Diarization
TODO
# Contribution
The model was contributed by [cywang](https://huggingface.co/cywang) and [patrickvonplaten](https://huggingface.co/patrickvonplaten).
# License
The official license can be found [here](https://github.com/microsoft/UniSpeech/blob/main/LICENSE)
![design](https://raw.githubusercontent.com/patrickvonplaten/scientific_images/master/wavlm.png)

View File

@@ -0,0 +1,99 @@
{
"_name_or_path": "wavlm-base-plus",
"activation_dropout": 0.0,
"adapter_kernel_size": 3,
"adapter_stride": 2,
"add_adapter": false,
"apply_spec_augment": true,
"architectures": [
"WavLMModel"
],
"attention_dropout": 0.1,
"bos_token_id": 1,
"classifier_proj_size": 256,
"codevector_dim": 256,
"contrastive_logits_temperature": 0.1,
"conv_bias": false,
"conv_dim": [
512,
512,
512,
512,
512,
512,
512
],
"conv_kernel": [
10,
3,
3,
3,
3,
2,
2
],
"conv_stride": [
5,
2,
2,
2,
2,
2,
2
],
"ctc_loss_reduction": "sum",
"ctc_zero_infinity": false,
"diversity_loss_weight": 0.1,
"do_stable_layer_norm": false,
"eos_token_id": 2,
"feat_extract_activation": "gelu",
"feat_extract_norm": "group",
"feat_proj_dropout": 0.1,
"feat_quantizer_dropout": 0.0,
"final_dropout": 0.0,
"freeze_feat_extract_train": true,
"hidden_act": "gelu",
"hidden_dropout": 0.1,
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-05,
"layerdrop": 0.05,
"mask_channel_length": 10,
"mask_channel_min_space": 1,
"mask_channel_other": 0.0,
"mask_channel_prob": 0.0,
"mask_channel_selection": "static",
"mask_feature_length": 10,
"mask_feature_min_masks": 0,
"mask_feature_prob": 0.0,
"mask_time_length": 10,
"mask_time_min_masks": 2,
"mask_time_min_space": 1,
"mask_time_other": 0.0,
"mask_time_prob": 0.05,
"mask_time_selection": "static",
"model_type": "wavlm",
"no_mask_channel_overlap": false,
"no_mask_time_overlap": false,
"num_adapter_layers": 3,
"num_attention_heads": 12,
"num_buckets": 320,
"num_codevector_groups": 2,
"num_codevectors_per_group": 320,
"num_conv_pos_embedding_groups": 16,
"num_conv_pos_embeddings": 128,
"num_ctc_classes": 80,
"num_feat_extract_layers": 7,
"num_hidden_layers": 12,
"num_negatives": 100,
"output_hidden_size": 768,
"pad_token_id": 0,
"proj_codevector_dim": 256,
"replace_prob": 0.5,
"torch_dtype": "float32",
"transformers_version": "4.13.0.dev0",
"use_weighted_layer_sum": false,
"vocab_size": 32,
"tokenizer_class": "Wav2Vec2CTCTokenizer"
}

View File

@@ -0,0 +1,9 @@
{
"do_normalize": false,
"feature_extractor_type": "Wav2Vec2FeatureExtractor",
"feature_size": 1,
"padding_side": "right",
"padding_value": 0.0,
"return_attention_mask": true,
"sampling_rate": 16000
}

View File

@@ -4,10 +4,13 @@ from text import chinese, japanese, english, cleaned_text_to_sequence
language_module_map = {"ZH": chinese, "JP": japanese, "EN": english}
def clean_text(text, language):
def clean_text(text, language, use_jp_extra=True):
language_module = language_module_map[language]
norm_text = language_module.text_normalize(text)
phones, tones, word2ph = language_module.g2p(norm_text)
if language == "JP":
phones, tones, word2ph = language_module.g2p(norm_text, use_jp_extra)
else:
phones, tones, word2ph = language_module.g2p(norm_text)
return norm_text, phones, tones, word2ph

View File

@@ -23,8 +23,8 @@ COSONANTS = set(
]
)
# 母音の集合
VOWELS = {"a", "i", "u", "e", "o"}
# 母音の集合、便宜上「ん」を含める
VOWELS = {"a", "i", "u", "e", "o", "N"}
# 正規化で記号を変換するための辞書
@@ -62,7 +62,7 @@ rep_map = {
"": "-",
"": "-",
# "": "-", # これは長音記号「ー」として扱うよう変更
"~": "-",
# "~": "-", # これも長音記号「ー」として扱うよう変更
"": "'",
"": "'",
}
@@ -145,7 +145,9 @@ def japanese_convert_numbers_to_words(text: str) -> str:
return res
def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
def g2p(
norm_text: str, use_jp_extra: bool = True
) -> tuple[list[str], list[int], list[int]]:
"""
他で使われるメインの関数。`text_normalize()`で正規化された`norm_text`を受け取り、
- phones: 音素のリスト(ただし`!`や`,`や`.`等punctuationが含まれうる
@@ -153,6 +155,7 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
- word2ph: 元のテキストの各文字に音素が何個割り当てられるかを表すリスト
のタプルを返す。
ただし`phones`と`tones`の最初と終わりに`_`が入り、応じて`word2ph`の最初と最後に1が追加される。
use_jp_extra: Falseの場合、「ん」の音素を「N」ではなく「n」とする。
"""
# pyopenjtalkのフルコンテキストラベルを使ってアクセントを取り出すと、punctuationの位置が消えてしまい情報が失われてしまう
# 「こんにちは、世界。」と「こんにちは!世界。」と「こんにちは!!!???世界……。」は全て同じになる。
@@ -160,7 +163,7 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
# それとは別にpyopenjtalk.run_frontend()で得られる音素リストこちらはpunctuationが保持されるを使い、
# アクセント割当をしなおすことによってpunctuationを含めた音素とアクセントのリストを作る。
# punctuationがすべて消えた、音素とアクセントのタプルのリスト
# punctuationがすべて消えた、音素とアクセントのタプルのリスト「ん」は「N」
phone_tone_list_wo_punct = g2phone_tone_wo_punct(norm_text)
# sep_text: 単語単位の単語のリスト
@@ -185,7 +188,9 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
sep_tokenized: list[list[str]] = []
for i in sep_text:
if i not in punctuation:
sep_tokenized.append(tokenizer.tokenize(i)) # ここでおそらく`i`が文字単位に分割される
sep_tokenized.append(
tokenizer.tokenize(i)
) # ここでおそらく`i`が文字単位に分割される
else:
sep_tokenized.append([i])
@@ -205,11 +210,15 @@ def g2p(norm_text: str) -> tuple[list[str], list[int], list[int]]:
assert len(phones) == sum(word2ph), f"{len(phones)} != {sum(word2ph)}"
# 最後にuse_jp_extraでない場合は「N」を「n」に変換
if not use_jp_extra:
phones = [phone if phone != "N" else "n" for phone in phones]
return phones, tones, word2ph
def g2kata_tone(norm_text: str) -> list[tuple[str, int]]:
phones, tones, _ = g2p(norm_text)
phones, tones, _ = g2p(norm_text, use_jp_extra=True)
return phone_tone2kata_tone(list(zip(phones, tones)))
@@ -225,22 +234,12 @@ def phone_tone2kata_tone(phone_tone: list[tuple[str, int]]) -> list[tuple[str, i
if phone in punctuation:
result.append((phone, tone))
continue
if phone in COSONANTS and phone != "n": # n以外の子音の場合
if phone in COSONANTS: # n以外の子音の場合
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
assert tone == next_tone, f"Unexpected {phone} tone {tone} != {next_tone}"
current_mora = phone
elif phone == "n":
assert current_mora == "", f"Unexpected {phone} after {current_mora}"
if next_phone not in VOWELS: # 次の音素が母音でない場合
result.append(("", tone))
else:
# 子音のnの場合
assert (
tone == next_tone
), f"Unexpected {phone} tone {tone} != {next_tone}"
current_mora = "n"
else:
# phoneが母音
# phoneが母音もしくは「N」
current_mora += phone
result.append((mora_phonemes_to_mora_kata[current_mora], tone))
current_mora = ""
@@ -269,9 +268,9 @@ def g2phone_tone_wo_punct(text: str) -> list[tuple[str, int]]:
テキストに対して、音素とアクセント0か1のペアのリストを返す。
ただし「!」「.」「?」等の非音素記号(punctuation)は全て消える(ポーズ記号も残さない)。
非音素記号を含める処理は`align_tones()`で行われる。
また「っ」は「q」に、「ん」は「n」に変換される。
また「っ」は「q」に、「ん」は「N」に変換される。
例: "こんにちは、世界ー。。元気?!"
[('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)
# logger.debug(f"prosodies: {prosodies}")
@@ -306,8 +305,8 @@ def g2phone_tone_wo_punct(text: str) -> list[tuple[str, int]]:
else:
if letter == "cl": # 「っ」の処理
letter = "q"
elif letter == "N": # 「ん」の処理
letter = "n"
# elif letter == "N": # 「ん」の処理
# letter = "n"
current_phrase.append((letter, current_tone))
return result
@@ -364,7 +363,7 @@ def text2sep_kata(norm_text: str) -> tuple[list[str], list[str]]:
return sep_text, sep_kata
# ESPnetの実装から引用、変更点無し
# ESPnetの実装から引用、変更点無し。「ん」は「N」なことに注意。
# https://github.com/espnet/espnet/blob/master/espnet2/text/phoneme_tokenizer.py
def pyopenjtalk_g2p_prosody(text: str, drop_unvoiced_vowels: bool = True) -> list[str]:
"""Extract phoneme + prosoody symbol sequence from input full-context labels.

View File

@@ -2,6 +2,7 @@
VOICEVOXのソースコードからお借りして最低限に改造したコード。
https://github.com/VOICEVOX/voicevox_engine/blob/master/voicevox_engine/tts_pipeline/mora_list.py
"""
"""
以下のモーラ対応表はOpenJTalkのソースコードから取得し、
カタカナ表記とモーラが一対一対応するように改造した。
@@ -48,8 +49,8 @@ POSSIBILITY OF SUCH DAMAGE.
from typing import Optional
# (カタカナ, 子音, 母音)の順。子音がない場合はNoneを入れる。
# 但し「ン」と「ッ」は母音のみという扱いで、「ン」は「n」、「ッ」は「q」に変更
# (元々はそれぞれ「N」「cl」
# 但し「ン」と「ッ」は母音のみという扱いで、「ン」は「N」、「ッ」は「q」とする。
# (元々「ッ」は「cl」
# また「デェ = dy e」はpyopenjtalkの出力de eと合わないため削除
_mora_list_minimum: list[tuple[str, Optional[str], str]] = [
("ヴォ", "v", "o"),
@@ -57,7 +58,7 @@ _mora_list_minimum: list[tuple[str, Optional[str], str]] = [
("ヴィ", "v", "i"),
("ヴァ", "v", "a"),
("", "v", "u"),
("", None, "n"), # 「N」から「n」に変更
("", None, "N"),
("", "w", "a"),
("", "r", "o"),
("", "r", "e"),

View File

@@ -1,6 +1,5 @@
import argparse
import datetime
import gc
import os
import platform
@@ -299,9 +298,9 @@ def run():
utils.latest_checkpoint_path(model_dir, "DUR_*.pth"),
net_dur_disc,
optim_dur_disc,
skip_optimizer=hps.train.skip_optimizer
if "skip_optimizer" in hps.train
else True,
skip_optimizer=(
hps.train.skip_optimizer if "skip_optimizer" in hps.train else True
),
)
if not optim_dur_disc.param_groups[0].get("initial_lr"):
optim_dur_disc.param_groups[0]["initial_lr"] = dur_resume_lr
@@ -309,17 +308,17 @@ def run():
utils.latest_checkpoint_path(model_dir, "G_*.pth"),
net_g,
optim_g,
skip_optimizer=hps.train.skip_optimizer
if "skip_optimizer" in hps.train
else True,
skip_optimizer=(
hps.train.skip_optimizer if "skip_optimizer" in hps.train else True
),
)
_, optim_d, d_resume_lr, epoch_str = utils.load_checkpoint(
utils.latest_checkpoint_path(model_dir, "D_*.pth"),
net_d,
optim_d,
skip_optimizer=hps.train.skip_optimizer
if "skip_optimizer" in hps.train
else True,
skip_optimizer=(
hps.train.skip_optimizer if "skip_optimizer" in hps.train else True
),
)
if not optim_g.param_groups[0].get("initial_lr"):
optim_g.param_groups[0]["initial_lr"] = g_resume_lr

998
train_ms_jp_extra.py Normal file
View File

@@ -0,0 +1,998 @@
import argparse
import datetime
import os
import platform
import torch
import torch.distributed as dist
from torch.cuda.amp import GradScaler, autocast
from torch.nn import functional as F
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from tqdm import tqdm
# logging.getLogger("numba").setLevel(logging.WARNING)
import commons
import default_style
import utils
from common.log import logger
from common.stdout_wrapper import SAFE_STDOUT
from config import config
from data_utils import (
DistributedBucketSampler,
TextAudioSpeakerCollate,
TextAudioSpeakerLoader,
)
from losses import WavLMLoss, discriminator_loss, feature_loss, generator_loss, kl_loss
from mel_processing import mel_spectrogram_torch, spec_to_mel_torch
from models_jp_extra import (
DurationDiscriminator,
MultiPeriodDiscriminator,
SynthesizerTrn,
WavLMDiscriminator,
)
from text.symbols import symbols
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = (
True # If encontered training problem,please try to disable TF32.
)
torch.set_num_threads(1)
torch.set_float32_matmul_precision("medium")
torch.backends.cuda.sdp_kernel("flash")
torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(
True
) # Not available if torch version is lower than 2.0
global_step = 0
def run():
# Command line configuration is not recommended unless necessary, use config.yml
parser = argparse.ArgumentParser()
parser.add_argument(
"-c",
"--config",
type=str,
default=config.train_ms_config.config_path,
help="JSON file for configuration",
)
parser.add_argument(
"-m",
"--model",
type=str,
help="数据集文件夹路径,请注意,数据不再默认放在/logs文件夹下。如果需要用命令行配置请声明相对于根目录的路径",
default=config.dataset_path,
)
parser.add_argument(
"--assets_root",
type=str,
help="Root directory of model assets needed for inference.",
default=config.assets_root,
)
parser.add_argument(
"--skip_default_style",
action="store_true",
help="Skip saving default style config and mean vector.",
)
parser.add_argument(
"--no_progress_bar",
action="store_true",
help="Do not show the progress bar while training.",
)
args = parser.parse_args()
# Set log file
model_dir = os.path.join(args.model, config.train_ms_config.model_dir)
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
logger.add(os.path.join(args.model, f"train_{timestamp}.log"))
# Parsing environment variables
envs = config.train_ms_config.env
for env_name, env_value in envs.items():
if env_name not in os.environ.keys():
logger.info("Loading configuration from config {}".format(str(env_value)))
os.environ[env_name] = str(env_value)
logger.info(
"Loading environment variables \nMASTER_ADDR: {},\nMASTER_PORT: {},\nWORLD_SIZE: {},\nRANK: {},\nLOCAL_RANK: {}".format(
os.environ["MASTER_ADDR"],
os.environ["MASTER_PORT"],
os.environ["WORLD_SIZE"],
os.environ["RANK"],
os.environ["LOCAL_RANK"],
)
)
backend = "nccl"
if platform.system() == "Windows":
backend = "gloo" # If Windows,switch to gloo backend.
dist.init_process_group(
backend=backend,
init_method="env://",
timeout=datetime.timedelta(seconds=300),
) # Use torchrun instead of mp.spawn
rank = dist.get_rank()
local_rank = int(os.environ["LOCAL_RANK"])
n_gpus = dist.get_world_size()
hps = utils.get_hparams_from_file(args.config)
# This is needed because we have to pass `model_dir` to `train_and_evaluate()`
hps.model_dir = model_dir
# 比较路径是否相同
if os.path.realpath(args.config) != os.path.realpath(
config.train_ms_config.config_path
):
with open(args.config, "r", encoding="utf-8") as f:
data = f.read()
os.makedirs(os.path.dirname(config.train_ms_config.config_path), exist_ok=True)
with open(config.train_ms_config.config_path, "w", encoding="utf-8") as f:
f.write(data)
"""
Path constants are a bit complicated...
TODO: Refactor or rename these?
(Both `config.yml` and `config.json` are used, which is confusing I think.)
args.model: For saving all info needed for training.
default: `Data/{model_name}`.
hps.model_dir := model_dir: For saving checkpoints (for resuming training).
default: `Data/{model_name}/models`.
(Use `hps` since we have to pass `model_dir` to `train_and_evaluate()`.
args.assets_root: The root directory of model assets needed for inference.
default: config.assets_root == `model_assets`.
config.out_dir: The directory for model assets of this model (for inference).
default: `model_assets/{model_name}`.
"""
os.makedirs(config.out_dir, exist_ok=True)
if not args.skip_default_style:
# Save default style to out_dir
default_style.set_style_config(
args.config, os.path.join(config.out_dir, "config.json")
)
default_style.save_mean_vector(
os.path.join(args.model, "wavs"),
os.path.join(config.out_dir, "style_vectors.npy"),
)
torch.manual_seed(hps.train.seed)
torch.cuda.set_device(local_rank)
global global_step
if rank == 0:
# logger = utils.get_logger(hps.model_dir)
# logger.info(hps)
utils.check_git_hash(model_dir)
writer = SummaryWriter(log_dir=model_dir)
writer_eval = SummaryWriter(log_dir=os.path.join(model_dir, "eval"))
train_dataset = TextAudioSpeakerLoader(hps.data.training_files, hps.data)
train_sampler = DistributedBucketSampler(
train_dataset,
hps.train.batch_size,
[32, 300, 400, 500, 600, 700, 800, 900, 1000],
num_replicas=n_gpus,
rank=rank,
shuffle=True,
)
collate_fn = TextAudioSpeakerCollate()
train_loader = DataLoader(
train_dataset,
# num_workers=min(config.train_ms_config.num_workers, os.cpu_count() - 1),
# Slow and often freezes, so use only half of the cores.
num_workers=min(config.train_ms_config.num_workers, os.cpu_count() // 2),
shuffle=False,
pin_memory=True,
collate_fn=collate_fn,
batch_sampler=train_sampler,
persistent_workers=True,
prefetch_factor=6,
) # DataLoader config could be adjusted.
if rank == 0:
eval_dataset = TextAudioSpeakerLoader(hps.data.validation_files, hps.data)
eval_loader = DataLoader(
eval_dataset,
num_workers=0,
shuffle=False,
batch_size=1,
pin_memory=True,
drop_last=False,
collate_fn=collate_fn,
)
if (
"use_noise_scaled_mas" in hps.model.keys()
and hps.model.use_noise_scaled_mas is True
):
logger.info("Using noise scaled MAS for VITS2")
mas_noise_scale_initial = 0.01
noise_scale_delta = 2e-6
else:
logger.info("Using normal MAS for VITS1")
mas_noise_scale_initial = 0.0
noise_scale_delta = 0.0
if (
"use_duration_discriminator" in hps.model.keys()
and hps.model.use_duration_discriminator is True
):
logger.info("Using duration discriminator for VITS2")
net_dur_disc = DurationDiscriminator(
hps.model.hidden_channels,
hps.model.hidden_channels,
3,
0.1,
gin_channels=hps.model.gin_channels if hps.data.n_speakers != 0 else 0,
).cuda(local_rank)
else:
net_dur_disc = None
if (
"use_wavlm_discriminator" in hps.model.keys()
and hps.model.use_wavlm_discriminator is True
):
net_wd = WavLMDiscriminator(
hps.model.slm.hidden, hps.model.slm.nlayers, hps.model.slm.initial_channel
).cuda(local_rank)
else:
net_wd = None
if (
"use_spk_conditioned_encoder" in hps.model.keys()
and hps.model.use_spk_conditioned_encoder is True
):
if hps.data.n_speakers == 0:
raise ValueError(
"n_speakers must be > 0 when using spk conditioned encoder to train multi-speaker model"
)
else:
logger.info("Using normal encoder for VITS1")
net_g = SynthesizerTrn(
len(symbols),
hps.data.filter_length // 2 + 1,
hps.train.segment_size // hps.data.hop_length,
n_speakers=hps.data.n_speakers,
mas_noise_scale_initial=mas_noise_scale_initial,
noise_scale_delta=noise_scale_delta,
**hps.model,
).cuda(local_rank)
if getattr(hps.train, "freeze_JP_bert", False):
logger.info("Freezing (JP) bert encoder !!!")
for param in net_g.enc_p.bert_proj.parameters():
param.requires_grad = False
if getattr(hps.train, "freeze_style", False):
logger.info("Freezing style encoder !!!")
for param in net_g.enc_p.style_proj.parameters():
param.requires_grad = False
net_d = MultiPeriodDiscriminator(hps.model.use_spectral_norm).cuda(local_rank)
optim_g = torch.optim.AdamW(
filter(lambda p: p.requires_grad, net_g.parameters()),
hps.train.learning_rate,
betas=hps.train.betas,
eps=hps.train.eps,
)
optim_d = torch.optim.AdamW(
net_d.parameters(),
hps.train.learning_rate,
betas=hps.train.betas,
eps=hps.train.eps,
)
if net_dur_disc is not None:
optim_dur_disc = torch.optim.AdamW(
net_dur_disc.parameters(),
hps.train.learning_rate,
betas=hps.train.betas,
eps=hps.train.eps,
)
else:
optim_dur_disc = None
if net_wd is not None:
optim_wd = torch.optim.AdamW(
net_wd.parameters(),
hps.train.learning_rate,
betas=hps.train.betas,
eps=hps.train.eps,
)
else:
optim_wd = None
net_g = DDP(net_g, device_ids=[local_rank], bucket_cap_mb=512)
net_d = DDP(net_d, device_ids=[local_rank], bucket_cap_mb=512)
if net_dur_disc is not None:
net_dur_disc = DDP(
net_dur_disc,
device_ids=[local_rank],
bucket_cap_mb=512,
)
if net_wd is not None:
net_wd = DDP(net_wd, device_ids=[local_rank], bucket_cap_mb=512)
if utils.is_resuming(model_dir):
if net_dur_disc is not None:
try:
_, _, dur_resume_lr, epoch_str = utils.load_checkpoint(
utils.latest_checkpoint_path(model_dir, "DUR_*.pth"),
net_dur_disc,
optim_dur_disc,
skip_optimizer=(
hps.train.skip_optimizer
if "skip_optimizer" in hps.train
else True
),
)
if not optim_dur_disc.param_groups[0].get("initial_lr"):
optim_dur_disc.param_groups[0]["initial_lr"] = dur_resume_lr
except:
if not optim_dur_disc.param_groups[0].get("initial_lr"):
optim_dur_disc.param_groups[0]["initial_lr"] = dur_resume_lr
print("Initialize dur_disc")
if net_wd is not None:
try:
_, optim_wd, wd_resume_lr, epoch_str = utils.load_checkpoint(
utils.latest_checkpoint_path(model_dir, "WD_*.pth"),
net_wd,
optim_wd,
skip_optimizer=(
hps.train.skip_optimizer
if "skip_optimizer" in hps.train
else True
),
)
if not optim_wd.param_groups[0].get("initial_lr"):
optim_wd.param_groups[0]["initial_lr"] = wd_resume_lr
except:
if not optim_wd.param_groups[0].get("initial_lr"):
optim_wd.param_groups[0]["initial_lr"] = wd_resume_lr
logger.info("Initialize wavlm")
try:
_, optim_g, g_resume_lr, epoch_str = utils.load_checkpoint(
utils.latest_checkpoint_path(model_dir, "G_*.pth"),
net_g,
optim_g,
skip_optimizer=(
hps.train.skip_optimizer if "skip_optimizer" in hps.train else True
),
)
_, optim_d, d_resume_lr, epoch_str = utils.load_checkpoint(
utils.latest_checkpoint_path(model_dir, "D_*.pth"),
net_d,
optim_d,
skip_optimizer=(
hps.train.skip_optimizer if "skip_optimizer" in hps.train else True
),
)
if not optim_g.param_groups[0].get("initial_lr"):
optim_g.param_groups[0]["initial_lr"] = g_resume_lr
if not optim_d.param_groups[0].get("initial_lr"):
optim_d.param_groups[0]["initial_lr"] = d_resume_lr
epoch_str = max(epoch_str, 1)
# global_step = (epoch_str - 1) * len(train_loader)
global_step = int(
utils.get_steps(utils.latest_checkpoint_path(model_dir, "G_*.pth"))
)
logger.info(
f"******************Found the model. Current epoch is {epoch_str}, gloabl step is {global_step}*********************"
)
except Exception as e:
logger.warning(e)
logger.warning(
"It seems that you are not using the pretrained models, so we will train from scratch."
)
epoch_str = 1
global_step = 0
else:
try:
_ = utils.load_safetensors(
os.path.join(model_dir, "G_0.safetensors"), net_g
)
_ = utils.load_safetensors(
os.path.join(model_dir, "D_0.safetensors"), net_d
)
if net_dur_disc is not None:
_ = utils.load_safetensors(
os.path.join(model_dir, "DUR_0.safetensors"), net_dur_disc
)
logger.info("Loaded the pretrained models.")
except Exception as e:
logger.warning(e)
logger.warning(
"It seems that you are not using the pretrained models, so we will train from scratch."
)
finally:
epoch_str = 1
global_step = 0
def lr_lambda(epoch):
"""
Learning rate scheduler for warmup and exponential decay.
- During the warmup period, the learning rate increases linearly.
- After the warmup period, the learning rate decreases exponentially.
"""
if epoch < hps.train.warmup_epochs:
return float(epoch) / float(max(1, hps.train.warmup_epochs))
else:
return hps.train.lr_decay ** (epoch - hps.train.warmup_epochs)
scheduler_last_epoch = epoch_str - 2
scheduler_g = torch.optim.lr_scheduler.LambdaLR(
optim_g, lr_lambda=lr_lambda, last_epoch=scheduler_last_epoch
)
scheduler_d = torch.optim.lr_scheduler.LambdaLR(
optim_d, lr_lambda=lr_lambda, last_epoch=scheduler_last_epoch
)
if net_dur_disc is not None:
scheduler_dur_disc = torch.optim.lr_scheduler.LambdaLR(
optim_dur_disc, lr_lambda=lr_lambda, last_epoch=scheduler_last_epoch
)
else:
scheduler_dur_disc = None
if net_wd is not None:
scheduler_wd = torch.optim.lr_scheduler.LambdaLR(
optim_wd, lr_lambda=lr_lambda, last_epoch=scheduler_last_epoch
)
wl = WavLMLoss(
hps.model.slm.model,
net_wd,
hps.data.sampling_rate,
hps.model.slm.sr,
).to(local_rank)
else:
scheduler_wd = None
wl = None
scaler = GradScaler(enabled=hps.train.bf16_run)
logger.info("Start training.")
diff = abs(
epoch_str * len(train_loader) - (hps.train.epochs + 1) * len(train_loader)
)
pbar = None
if not args.no_progress_bar:
pbar = tqdm(
total=global_step + diff,
initial=global_step,
smoothing=0.05,
file=SAFE_STDOUT,
)
initial_step = global_step
for epoch in range(epoch_str, hps.train.epochs + 1):
if rank == 0:
train_and_evaluate(
rank,
local_rank,
epoch,
hps,
[net_g, net_d, net_dur_disc, net_wd, wl],
[optim_g, optim_d, optim_dur_disc, optim_wd],
[scheduler_g, scheduler_d, scheduler_dur_disc, scheduler_wd],
scaler,
[train_loader, eval_loader],
logger,
[writer, writer_eval],
pbar,
initial_step,
)
else:
train_and_evaluate(
rank,
local_rank,
epoch,
hps,
[net_g, net_d, net_dur_disc, net_wd, wl],
[optim_g, optim_d, optim_dur_disc, optim_wd],
[scheduler_g, scheduler_d, scheduler_dur_disc, scheduler_wd],
scaler,
[train_loader, None],
None,
None,
pbar,
initial_step,
)
scheduler_g.step()
scheduler_d.step()
if net_dur_disc is not None:
scheduler_dur_disc.step()
if net_wd is not None:
scheduler_wd.step()
if epoch == hps.train.epochs:
# Save the final models
utils.save_checkpoint(
net_g,
optim_g,
hps.train.learning_rate,
epoch,
os.path.join(model_dir, "G_{}.pth".format(global_step)),
)
utils.save_checkpoint(
net_d,
optim_d,
hps.train.learning_rate,
epoch,
os.path.join(model_dir, "D_{}.pth".format(global_step)),
)
if net_dur_disc is not None:
utils.save_checkpoint(
net_dur_disc,
optim_dur_disc,
hps.train.learning_rate,
epoch,
os.path.join(model_dir, "DUR_{}.pth".format(global_step)),
)
if net_wd is not None:
utils.save_checkpoint(
net_wd,
optim_wd,
hps.train.learning_rate,
epoch,
os.path.join(model_dir, "WD_{}.pth".format(global_step)),
)
utils.save_safetensors(
net_g,
epoch,
os.path.join(
config.out_dir,
f"{config.model_name}_e{epoch}_s{global_step}.safetensors",
),
for_infer=True,
)
if pbar is not None:
pbar.close()
def train_and_evaluate(
rank,
local_rank,
epoch,
hps,
nets,
optims,
schedulers,
scaler,
loaders,
logger,
writers,
pbar: tqdm,
initial_step: int,
):
net_g, net_d, net_dur_disc, net_wd, wl = nets
optim_g, optim_d, optim_dur_disc, optim_wd = optims
scheduler_g, scheduler_d, scheduler_dur_disc, scheduler_wd = schedulers
train_loader, eval_loader = loaders
if writers is not None:
writer, writer_eval = writers
train_loader.batch_sampler.set_epoch(epoch)
global global_step
net_g.train()
net_d.train()
if net_dur_disc is not None:
net_dur_disc.train()
if net_wd is not None:
net_wd.train()
for batch_idx, (
x,
x_lengths,
spec,
spec_lengths,
y,
y_lengths,
speakers,
tone,
language,
bert,
style_vec,
) in enumerate(tqdm(train_loader)):
if net_g.module.use_noise_scaled_mas:
current_mas_noise_scale = (
net_g.module.mas_noise_scale_initial
- net_g.module.noise_scale_delta * global_step
)
net_g.module.current_mas_noise_scale = max(current_mas_noise_scale, 0.0)
x, x_lengths = x.cuda(local_rank, non_blocking=True), x_lengths.cuda(
local_rank, non_blocking=True
)
spec, spec_lengths = spec.cuda(
local_rank, non_blocking=True
), spec_lengths.cuda(local_rank, non_blocking=True)
y, y_lengths = y.cuda(local_rank, non_blocking=True), y_lengths.cuda(
local_rank, non_blocking=True
)
speakers = speakers.cuda(local_rank, non_blocking=True)
tone = tone.cuda(local_rank, non_blocking=True)
language = language.cuda(local_rank, non_blocking=True)
bert = bert.cuda(local_rank, non_blocking=True)
style_vec = style_vec.cuda(local_rank, non_blocking=True)
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
(
y_hat,
l_length,
attn,
ids_slice,
x_mask,
z_mask,
(z, z_p, m_p, logs_p, m_q, logs_q),
(hidden_x, logw, logw_), # , logw_sdp),
g,
) = net_g(
x,
x_lengths,
spec,
spec_lengths,
speakers,
tone,
language,
bert,
style_vec,
)
mel = spec_to_mel_torch(
spec,
hps.data.filter_length,
hps.data.n_mel_channels,
hps.data.sampling_rate,
hps.data.mel_fmin,
hps.data.mel_fmax,
)
y_mel = commons.slice_segments(
mel, ids_slice, hps.train.segment_size // hps.data.hop_length
)
y_hat_mel = mel_spectrogram_torch(
y_hat.squeeze(1).float(),
hps.data.filter_length,
hps.data.n_mel_channels,
hps.data.sampling_rate,
hps.data.hop_length,
hps.data.win_length,
hps.data.mel_fmin,
hps.data.mel_fmax,
)
y = commons.slice_segments(
y, ids_slice * hps.data.hop_length, hps.train.segment_size
) # slice
# Discriminator
y_d_hat_r, y_d_hat_g, _, _ = net_d(y, y_hat.detach())
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
loss_disc, losses_disc_r, losses_disc_g = discriminator_loss(
y_d_hat_r, y_d_hat_g
)
loss_disc_all = loss_disc
if net_dur_disc is not None:
y_dur_hat_r, y_dur_hat_g = net_dur_disc(
hidden_x.detach(),
x_mask.detach(),
logw_.detach(),
logw.detach(),
g.detach(),
)
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
# TODO: I think need to mean using the mask, but for now, just mean all
(
loss_dur_disc,
losses_dur_disc_r,
losses_dur_disc_g,
) = discriminator_loss(y_dur_hat_r, y_dur_hat_g)
loss_dur_disc_all = loss_dur_disc
optim_dur_disc.zero_grad()
scaler.scale(loss_dur_disc_all).backward()
scaler.unscale_(optim_dur_disc)
# torch.nn.utils.clip_grad_norm_(
# parameters=net_dur_disc.parameters(), max_norm=5
# )
grad_norm_dur = commons.clip_grad_value_(
net_dur_disc.parameters(), None
)
scaler.step(optim_dur_disc)
if net_wd is not None:
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
loss_slm = wl.discriminator(
y.detach().squeeze(), y_hat.detach().squeeze()
).mean()
optim_wd.zero_grad()
scaler.scale(loss_slm).backward()
scaler.unscale_(optim_wd)
# torch.nn.utils.clip_grad_norm_(parameters=net_wd.parameters(), max_norm=200)
grad_norm_wd = commons.clip_grad_value_(net_wd.parameters(), None)
scaler.step(optim_wd)
optim_d.zero_grad()
scaler.scale(loss_disc_all).backward()
scaler.unscale_(optim_d)
if getattr(hps.train, "bf16_run", False):
torch.nn.utils.clip_grad_norm_(parameters=net_d.parameters(), max_norm=200)
grad_norm_d = commons.clip_grad_value_(net_d.parameters(), None)
scaler.step(optim_d)
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
# Generator
y_d_hat_r, y_d_hat_g, fmap_r, fmap_g = net_d(y, y_hat)
if net_dur_disc is not None:
_, y_dur_hat_g = net_dur_disc(hidden_x, x_mask, logw_, logw, g)
if net_wd is not None:
loss_lm = wl(y.detach().squeeze(), y_hat.squeeze()).mean()
loss_lm_gen = wl.generator(y_hat.squeeze())
with autocast(enabled=hps.train.bf16_run, dtype=torch.bfloat16):
loss_dur = torch.sum(l_length.float())
loss_mel = F.l1_loss(y_mel, y_hat_mel) * hps.train.c_mel
loss_kl = kl_loss(z_p, logs_q, m_p, logs_p, z_mask) * hps.train.c_kl
loss_fm = feature_loss(fmap_r, fmap_g)
loss_gen, losses_gen = generator_loss(y_d_hat_g)
# loss_commit = loss_commit * hps.train.c_commit
loss_gen_all = loss_gen + loss_fm + loss_mel + loss_dur + loss_kl
if net_dur_disc is not None:
loss_dur_gen, losses_dur_gen = generator_loss(y_dur_hat_g)
if net_wd is not None:
loss_gen_all += loss_dur_gen + loss_lm + loss_lm_gen
else:
loss_gen_all += loss_dur_gen
optim_g.zero_grad()
scaler.scale(loss_gen_all).backward()
scaler.unscale_(optim_g)
# if getattr(hps.train, "bf16_run", False):
torch.nn.utils.clip_grad_norm_(parameters=net_g.parameters(), max_norm=500)
grad_norm_g = commons.clip_grad_value_(net_g.parameters(), None)
scaler.step(optim_g)
scaler.update()
if rank == 0:
if global_step % hps.train.log_interval == 0:
lr = optim_g.param_groups[0]["lr"]
losses = [loss_disc, loss_gen, loss_fm, loss_mel, loss_dur, loss_kl]
# logger.info(
# "Train Epoch: {} [{:.0f}%]".format(
# epoch, 100.0 * batch_idx / len(train_loader)
# )
# )
# logger.info([x.item() for x in losses] + [global_step, lr])
scalar_dict = {
"loss/g/total": loss_gen_all,
"loss/d/total": loss_disc_all,
"learning_rate": lr,
"grad_norm_d": grad_norm_d,
"grad_norm_g": grad_norm_g,
}
scalar_dict.update(
{
"loss/g/fm": loss_fm,
"loss/g/mel": loss_mel,
"loss/g/dur": loss_dur,
"loss/g/kl": loss_kl,
}
)
scalar_dict.update(
{"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)}
)
scalar_dict.update(
{"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)}
)
scalar_dict.update(
{"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)}
)
if net_dur_disc is not None:
scalar_dict.update({"loss/dur_disc/total": loss_dur_disc_all})
scalar_dict.update(
{
"loss/dur_disc_g/{}".format(i): v
for i, v in enumerate(losses_dur_disc_g)
}
)
scalar_dict.update(
{
"loss/dur_disc_r/{}".format(i): v
for i, v in enumerate(losses_dur_disc_r)
}
)
scalar_dict.update({"loss/g/dur_gen": loss_dur_gen})
scalar_dict.update(
{
"loss/g/dur_gen_{}".format(i): v
for i, v in enumerate(losses_dur_gen)
}
)
if net_wd is not None:
scalar_dict.update(
{
"loss/wd/total": loss_slm,
"grad_norm_wd": grad_norm_wd,
"loss/g/lm": loss_lm,
"loss/g/lm_gen": loss_lm_gen,
}
)
image_dict = {
"slice/mel_org": utils.plot_spectrogram_to_numpy(
y_mel[0].data.cpu().numpy()
),
"slice/mel_gen": utils.plot_spectrogram_to_numpy(
y_hat_mel[0].data.cpu().numpy()
),
"all/mel": utils.plot_spectrogram_to_numpy(
mel[0].data.cpu().numpy()
),
"all/attn": utils.plot_alignment_to_numpy(
attn[0, 0].data.cpu().numpy()
),
}
utils.summarize(
writer=writer,
global_step=global_step,
images=image_dict,
scalars=scalar_dict,
)
if (
global_step % hps.train.eval_interval == 0
and global_step != 0
and initial_step != global_step
):
evaluate(hps, net_g, eval_loader, writer_eval)
utils.save_checkpoint(
net_g,
optim_g,
hps.train.learning_rate,
epoch,
os.path.join(hps.model_dir, "G_{}.pth".format(global_step)),
)
utils.save_checkpoint(
net_d,
optim_d,
hps.train.learning_rate,
epoch,
os.path.join(hps.model_dir, "D_{}.pth".format(global_step)),
)
if net_dur_disc is not None:
utils.save_checkpoint(
net_dur_disc,
optim_dur_disc,
hps.train.learning_rate,
epoch,
os.path.join(hps.model_dir, "DUR_{}.pth".format(global_step)),
)
if net_wd is not None:
utils.save_checkpoint(
net_wd,
optim_wd,
hps.train.learning_rate,
epoch,
os.path.join(hps.model_dir, "WD_{}.pth".format(global_step)),
)
keep_ckpts = config.train_ms_config.keep_ckpts
if keep_ckpts > 0:
utils.clean_checkpoints(
path_to_models=hps.model_dir,
n_ckpts_to_keep=keep_ckpts,
sort_by_time=True,
)
# Save safetensors (for inference) to `model_assets/{model_name}`
utils.save_safetensors(
net_g,
epoch,
os.path.join(
config.out_dir,
f"{config.model_name}_e{epoch}_s{global_step}.safetensors",
),
for_infer=True,
)
global_step += 1
if pbar is not None:
pbar.set_description(
"Epoch {}({:.0f}%)/{}".format(
epoch, 100.0 * batch_idx / len(train_loader), hps.train.epochs
)
)
pbar.update()
# 本家ではこれをスピードアップのために消すと書かれていたので、一応消してみる
# gc.collect()
# torch.cuda.empty_cache()
if pbar is None and rank == 0:
logger.info(f"====> Epoch: {epoch}, step: {global_step}")
def evaluate(hps, generator, eval_loader, writer_eval):
generator.eval()
image_dict = {}
audio_dict = {}
logger.info("Evaluating ...")
with torch.no_grad():
for batch_idx, (
x,
x_lengths,
spec,
spec_lengths,
y,
y_lengths,
speakers,
tone,
language,
bert,
style_vec,
) in enumerate(eval_loader):
x, x_lengths = x.cuda(), x_lengths.cuda()
spec, spec_lengths = spec.cuda(), spec_lengths.cuda()
y, y_lengths = y.cuda(), y_lengths.cuda()
speakers = speakers.cuda()
bert = bert.cuda()
tone = tone.cuda()
language = language.cuda()
style_vec = style_vec.cuda()
for use_sdp in [True, False]:
y_hat, attn, mask, *_ = generator.module.infer(
x,
x_lengths,
speakers,
tone,
language,
bert,
style_vec,
y=spec,
max_len=1000,
sdp_ratio=0.0 if not use_sdp else 1.0,
)
y_hat_lengths = mask.sum([1, 2]).long() * hps.data.hop_length
mel = spec_to_mel_torch(
spec,
hps.data.filter_length,
hps.data.n_mel_channels,
hps.data.sampling_rate,
hps.data.mel_fmin,
hps.data.mel_fmax,
)
y_hat_mel = mel_spectrogram_torch(
y_hat.squeeze(1).float(),
hps.data.filter_length,
hps.data.n_mel_channels,
hps.data.sampling_rate,
hps.data.hop_length,
hps.data.win_length,
hps.data.mel_fmin,
hps.data.mel_fmax,
)
image_dict.update(
{
f"gen/mel_{batch_idx}": utils.plot_spectrogram_to_numpy(
y_hat_mel[0].cpu().numpy()
)
}
)
audio_dict.update(
{
f"gen/audio_{batch_idx}_{use_sdp}": y_hat[
0, :, : y_hat_lengths[0]
]
}
)
image_dict.update(
{
f"gt/mel_{batch_idx}": utils.plot_spectrogram_to_numpy(
mel[0].cpu().numpy()
)
}
)
audio_dict.update({f"gt/audio_{batch_idx}": y[0, :, : y_lengths[0]]})
utils.summarize(
writer=writer_eval,
global_step=global_step,
images=image_dict,
audios=audio_dict,
audio_sampling_rate=hps.data.sampling_rate,
)
generator.train()
if __name__ == "__main__":
run()

View File

@@ -40,6 +40,7 @@ def initialize(
freeze_JP_bert,
freeze_ZH_bert,
freeze_style,
use_jp_extra,
):
global logger_handler
dataset_path, _, train_path, val_path, config_path = get_path(model_name)
@@ -52,14 +53,20 @@ def initialize(
logger_handler = logger.add(os.path.join(dataset_path, file_name))
logger.info(
f"Step 1: start initialization...\nmodel_name: {model_name}, batch_size: {batch_size}, epochs: {epochs}, save_every_steps: {save_every_steps}, bf16_run: {bf16_run}, freeze_ZH_bert: {freeze_ZH_bert}, freeze_JP_bert: {freeze_JP_bert}, freeze_EN_bert: {freeze_EN_bert}, freeze_style: {freeze_style}"
f"Step 1: start initialization...\nmodel_name: {model_name}, batch_size: {batch_size}, epochs: {epochs}, save_every_steps: {save_every_steps}, bf16_run: {bf16_run}, freeze_ZH_bert: {freeze_ZH_bert}, freeze_JP_bert: {freeze_JP_bert}, freeze_EN_bert: {freeze_EN_bert}, freeze_style: {freeze_style}, use_jp_extra: {use_jp_extra}"
)
if os.path.isfile(config_path):
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
else:
# Use default config
with open("configs/config.json", "r", encoding="utf-8") as f:
default_config_path = (
"configs/config.json"
if not use_jp_extra
else "configs/configs_jp_extra.json"
)
with open(default_config_path, "r", encoding="utf-8") as f:
config = json.load(f)
config["model_name"] = model_name
config["data"]["training_files"] = train_path
@@ -83,14 +90,15 @@ def initialize(
dirs_exist_ok=True,
)
shutil.rmtree(model_path)
pretrained_dir = "pretrained" if not use_jp_extra else "pretrained_jp_extra"
try:
shutil.copytree(
src="pretrained",
src=pretrained_dir,
dst=model_path,
)
except FileNotFoundError:
logger.error("Step 1: `pretrained` folder not found.")
return False, "Step 1, Error: pretrainedフォルダが見つかりません。"
logger.error(f"Step 1: {pretrained_dir} folder not found.")
return False, f"Step 1, Error: {pretrained_dir}フォルダが見つかりません。"
with open(config_path, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
@@ -138,7 +146,7 @@ def resample(model_name, normalize, trim, num_processes):
return True, "Step 2, Success: 音声ファイルの前処理が完了しました"
def preprocess_text(model_name):
def preprocess_text(model_name, use_jp_extra):
logger.info("Step 3: start preprocessing text...")
dataset_path, lbl_path, train_path, val_path, config_path = get_path(model_name)
try:
@@ -153,25 +161,32 @@ def preprocess_text(model_name):
"\\", "/"
)
f.writelines(f"{path}|{spk}|{language}|{text}\n")
success, message = run_script_with_log(
[
"preprocess_text.py",
"--config-path",
config_path,
"--transcription-path",
lbl_path,
"--train-path",
train_path,
"--val-path",
val_path,
]
)
cmd = [
"preprocess_text.py",
"--config-path",
config_path,
"--transcription-path",
lbl_path,
"--train-path",
train_path,
"--val-path",
val_path,
]
if use_jp_extra:
cmd.append("--use_jp_extra")
success, message = run_script_with_log(cmd)
if not success:
logger.error(f"Step 3: preprocessing text failed.")
return False, f"Step 3, Error: 書き起こしファイルの前処理に失敗しました:\n{message}"
return (
False,
f"Step 3, Error: 書き起こしファイルの前処理に失敗しました:\n{message}",
)
elif message:
logger.warning(f"Step 3: preprocessing text finished with stderr.")
return True, f"Step 3, Success: 書き起こしファイルの前処理が完了しました:\n{message}"
return (
True,
f"Step 3, Success: 書き起こしファイルの前処理が完了しました:\n{message}",
)
logger.success("Step 3: preprocessing text finished.")
return True, "Step 3, Success: 書き起こしファイルの前処理が完了しました"
@@ -193,7 +208,10 @@ def bert_gen(model_name):
return False, f"Step 4, Error: BERT特徴ファイルの生成に失敗しました:\n{message}"
elif message:
logger.warning(f"Step 4: bert_gen finished with stderr.")
return True, f"Step 4, Success: BERT特徴ファイルの生成が完了しました:\n{message}"
return (
True,
f"Step 4, Success: BERT特徴ファイルの生成が完了しました:\n{message}",
)
logger.success("Step 4: bert_gen finished.")
return True, "Step 4, Success: BERT特徴ファイルの生成が完了しました"
@@ -212,10 +230,16 @@ def style_gen(model_name, num_processes):
)
if not success:
logger.error(f"Step 5: style_gen failed.")
return False, f"Step 5, Error: スタイル特徴ファイルの生成に失敗しました:\n{message}"
return (
False,
f"Step 5, Error: スタイル特徴ファイルの生成に失敗しました:\n{message}",
)
elif message:
logger.warning(f"Step 5: style_gen finished with stderr.")
return True, f"Step 5, Success: スタイル特徴ファイルの生成が完了しました:\n{message}"
return (
True,
f"Step 5, Success: スタイル特徴ファイルの生成が完了しました:\n{message}",
)
logger.success("Step 5: style_gen finished.")
return True, "Step 5, Success: スタイル特徴ファイルの生成が完了しました"
@@ -233,6 +257,7 @@ def preprocess_all(
freeze_JP_bert,
freeze_ZH_bert,
freeze_style,
use_jp_extra,
):
if model_name == "":
return False, "Error: モデル名を入力してください"
@@ -246,13 +271,14 @@ def preprocess_all(
freeze_JP_bert,
freeze_ZH_bert,
freeze_style,
use_jp_extra,
)
if not success:
return False, message
success, message = resample(model_name, normalize, trim, num_processes)
if not success:
return False, message
success, message = preprocess_text(model_name)
success, message = preprocess_text(model_name, use_jp_extra)
if not success:
return False, message
success, message = bert_gen(model_name) # bert_genは重いのでプロセス数いじらない
@@ -262,10 +288,13 @@ def preprocess_all(
if not success:
return False, message
logger.success("Success: All preprocess finished!")
return True, "Success: 全ての前処理が完了しました。ターミナルを確認しておかしいところがないか確認するのをおすすめします。"
return (
True,
"Success: 全ての前処理が完了しました。ターミナルを確認しておかしいところがないか確認するのをおすすめします。",
)
def train(model_name, skip_style=False):
def train(model_name, skip_style=False, use_jp_extra=True):
dataset_path, _, _, _, config_path = get_path(model_name)
# 学習再開の場合は念のためconfig.ymlの名前等を更新
with open("config.yml", "r", encoding="utf-8") as f:
@@ -274,12 +303,12 @@ def train(model_name, skip_style=False):
yml_data["dataset_path"] = dataset_path
with open("config.yml", "w", encoding="utf-8") as f:
yaml.dump(yml_data, f, allow_unicode=True)
cmd = ["train_ms.py", "--config", config_path, "--model", dataset_path]
train_py = "train_ms.py" if not use_jp_extra else "train_ms_jp_extra.py"
cmd = [train_py, "--config", config_path, "--model", dataset_path]
if skip_style:
cmd.append("--skip_default_style")
success, message = run_script_with_log(
["train_ms.py", "--config", config_path, "--model", dataset_path]
)
success, message = run_script_with_log(cmd)
if not success:
logger.error(f"Train failed.")
return False, f"Error: 学習に失敗しました:\n{message}"
@@ -348,6 +377,10 @@ if __name__ == "__main__":
gr.Markdown("### 自動前処理")
with gr.Row(variant="panel"):
with gr.Column():
use_jp_extra = gr.Checkbox(
label="日本語特化版を使う(日本語の性能が上がるが英語と中国語は話せなくなる)",
value=False,
)
batch_size = gr.Slider(
label="バッチサイズ",
value=4,
@@ -412,12 +445,18 @@ if __name__ == "__main__":
)
with gr.Column():
preprocess_button = gr.Button(value="自動前処理を実行", variant="primary")
preprocess_button = gr.Button(
value="自動前処理を実行", variant="primary"
)
info_all = gr.Textbox(label="状況")
with gr.Accordion(open=False, label="手動前処理"):
with gr.Row(variant="panel"):
with gr.Column():
gr.Markdown(value="#### Step 1: 設定ファイルの生成")
use_jp_extra_manual = gr.Checkbox(
label="日本語特化版を使う",
value=False,
)
batch_size_manual = gr.Slider(
label="バッチサイズ",
value=4,
@@ -515,6 +554,10 @@ if __name__ == "__main__":
info="学習再開の場合の場合はチェックしてください",
value=False,
)
use_jp_extra_train = gr.Checkbox(
label="日本語特化版を使う",
value=True,
)
train_btn = gr.Button(value="学習を開始する", variant="primary")
info_train = gr.Textbox(label="状況")
@@ -533,6 +576,7 @@ if __name__ == "__main__":
freeze_JP_bert,
freeze_ZH_bert,
freeze_style,
use_jp_extra,
],
outputs=[info_all],
)
@@ -548,6 +592,7 @@ if __name__ == "__main__":
freeze_JP_bert_manual,
freeze_ZH_bert_manual,
freeze_style_manual,
use_jp_extra_manual,
],
outputs=[info_init],
)
@@ -577,7 +622,9 @@ if __name__ == "__main__":
outputs=[info_style],
)
train_btn.click(
second_elem_of(train), inputs=[model_name, skip_style], outputs=[info_train]
second_elem_of(train),
inputs=[model_name, skip_style, use_jp_extra_train],
outputs=[info_train],
)
parser = argparse.ArgumentParser()