From 14b209c7e9b9ba336d65152ddf45482ea47b2408 Mon Sep 17 00:00:00 2001 From: Edresson Date: Sat, 5 Jun 2021 03:12:17 -0300 Subject: [PATCH 01/28] Create a batch for more fast inference on LSTM Speaker Encoder --- TTS/speaker_encoder/models/lstm.py | 33 +++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/TTS/speaker_encoder/models/lstm.py b/TTS/speaker_encoder/models/lstm.py index 05a56675..fadada70 100644 --- a/TTS/speaker_encoder/models/lstm.py +++ b/TTS/speaker_encoder/models/lstm.py @@ -1,4 +1,5 @@ import torch +import numpy as np from torch import nn @@ -70,24 +71,32 @@ class LSTMSpeakerEncoder(nn.Module): d = torch.nn.functional.normalize(d, p=2, dim=1) return d - def compute_embedding(self, x, num_frames=160, overlap=0.5): + def compute_embedding(self, x, num_frames=250, num_eval=10, return_mean=True): """ Generate embeddings for a batch of utterances x: 1xTxD """ - num_overlap = int(num_frames * overlap) max_len = x.shape[1] - embed = None - cur_iter = 0 - for offset in range(0, max_len, num_frames - num_overlap): - cur_iter += 1 - end_offset = min(x.shape[1], offset + num_frames) + + if max_len < num_frames: + num_frames = max_len + + offsets = np.linspace(0, max_len-num_frames, num=num_eval) + + frames_batch = [] + for offset in offsets: + offset = int(offset) + end_offset = int(offset+num_frames) frames = x[:, offset:end_offset] - if embed is None: - embed = self.inference(frames) - else: - embed += self.inference(frames) - return embed / cur_iter + frames_batch.append(frames) + + frames_batch = torch.cat(frames_batch, dim=0) + embeddings = self.inference(frames_batch) + + if return_mean: + embeddings = torch.mean(embeddings, dim=0, keepdim=True) + + return embeddings def batch_compute_embedding(self, x, seq_lens, num_frames=160, overlap=0.5): """ From d0cef713cf19fa75011bd7ab53c20c168cfd3d93 Mon Sep 17 00:00:00 2001 From: Sadam Hussain Memon Date: Thu, 10 Jun 2021 05:51:30 +0500 Subject: [PATCH 02/28] `mixed_precision` set to false Change default value of `"mixed_precision" : false` as when it is set true it leads to `raise RuntimeError(f" [!] NaN loss with {key}.") RuntimeError: [!] NaN loss with decoder_loss.` --- recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json b/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json index 9cdbbd3b..dd3b71db 100644 --- a/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json +++ b/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json @@ -41,7 +41,7 @@ "run_description": "tacotron2 with double decoder consistency.", "batch_size": 64, "eval_batch_size": 16, - "mixed_precision": true, + "mixed_precision": false, "loss_masking": true, "decoder_loss_alpha": 0.25, "postnet_loss_alpha": 0.25, @@ -88,4 +88,4 @@ "phoneme_cache_path": "DEFINE THIS", "use_phonemes": false, "phoneme_language": "en-us" -} \ No newline at end of file +} From b0aa18934870cb0120703346c766325af81135bc Mon Sep 17 00:00:00 2001 From: Adam Froghyar Date: Mon, 14 Jun 2021 10:44:00 +0200 Subject: [PATCH 03/28] Forcing do_trim_silence to False in the extract TTS script --- TTS/bin/extract_tts_spectrograms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/TTS/bin/extract_tts_spectrograms.py b/TTS/bin/extract_tts_spectrograms.py index ace7464a..4eb79d76 100755 --- a/TTS/bin/extract_tts_spectrograms.py +++ b/TTS/bin/extract_tts_spectrograms.py @@ -299,4 +299,5 @@ if __name__ == "__main__": args = parser.parse_args() c = load_config(args.config_path) + c.audio['do_trim_silence'] = False # IMPORTANT!!!!!!!!!!!!!!! disable to align mel main(args) From d85ee901d57b4a08301ef569d3c48dd032508ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Tue, 15 Jun 2021 10:53:53 +0200 Subject: [PATCH 04/28] Fix #571 --- TTS/bin/extract_tts_spectrograms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/TTS/bin/extract_tts_spectrograms.py b/TTS/bin/extract_tts_spectrograms.py index ace7464a..2be9d760 100755 --- a/TTS/bin/extract_tts_spectrograms.py +++ b/TTS/bin/extract_tts_spectrograms.py @@ -299,4 +299,5 @@ if __name__ == "__main__": args = parser.parse_args() c = load_config(args.config_path) + C.audio['do_trim_silence'] = False main(args) From b74b510d3c9d7dd31e16c1dc5379ea85948e771f Mon Sep 17 00:00:00 2001 From: Edresson Date: Fri, 18 Jun 2021 14:04:49 -0300 Subject: [PATCH 05/28] Compute embeddings and find characters using config file --- TTS/bin/compute_embeddings.py | 67 ++++++++++++---------------------- TTS/bin/find_unique_chars.py | 25 +++++++------ TTS/tts/datasets/preprocess.py | 34 +++++++++++++---- 3 files changed, 63 insertions(+), 63 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 003da1e5..9ed459a2 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -3,71 +3,44 @@ import glob import os import torch +import numpy as np from tqdm import tqdm from TTS.speaker_encoder.utils.generic_utils import setup_model from TTS.tts.datasets.preprocess import load_meta_data from TTS.tts.utils.speakers import SpeakerManager from TTS.utils.audio import AudioProcessor -from TTS.config import load_config, BaseDatasetConfig +from TTS.config import load_config parser = argparse.ArgumentParser( - description='Compute embedding vectors for each wav file in a dataset. If "target_dataset" is defined, it generates "speakers.json" necessary for training a multi-speaker model.' + description='Compute embedding vectors for each wav file in a dataset.' ) -parser.add_argument("model_path", type=str, help="Path to model outputs (checkpoint, tensorboard etc.).") +parser.add_argument("model_path", type=str, help="Path to model checkpoint file.") parser.add_argument( "config_path", type=str, - help="Path to config file for training.", + help="Path to model config file.", ) -parser.add_argument("data_path", type=str, help="Data path for wav files - directory or CSV file") -parser.add_argument("output_path", type=str, help="path for output speakers.json.") + parser.add_argument( - "--target_dataset", + "config_dataset_path", type=str, - default="", - help="Target dataset to pick a processor from TTS.tts.dataset.preprocess. Necessary to create a speakers.json file.", + help="Path to dataset config file.", ) +parser.add_argument("output_path", type=str, help="path for output speakers.json and/or speakers.npy.") parser.add_argument("--use_cuda", type=bool, help="flag to set cuda.", default=True) -parser.add_argument("--separator", type=str, help="Separator used in file if CSV is passed for data_path", default="|") +parser.add_argument("--save_npy", type=bool, help="flag to set cuda.", default=False) args = parser.parse_args() c = load_config(args.config_path) +c_dataset = load_config(args.config_dataset_path) + ap = AudioProcessor(**c["audio"]) -data_path = args.data_path -split_ext = os.path.splitext(data_path) -sep = args.separator +train_files, dev_files = load_meta_data(c_dataset.datasets, eval_split=True, ignore_generated_eval=True) -if args.target_dataset != "": - # if target dataset is defined - dataset_config = [ - BaseDatasetConfig(name=args.target_dataset, path=args.data_path, meta_file_train=None, meta_file_val=None), - ] - wav_files, _ = load_meta_data(dataset_config, eval_split=False) -else: - # if target dataset is not defined - if len(split_ext) > 0 and split_ext[1].lower() == ".csv": - # Parse CSV - print(f"CSV file: {data_path}") - with open(data_path) as f: - wav_path = os.path.join(os.path.dirname(data_path), "wavs") - wav_files = [] - print(f"Separator is: {sep}") - for line in f: - components = line.split(sep) - if len(components) != 2: - print("Invalid line") - continue - wav_file = os.path.join(wav_path, components[0] + ".wav") - # print(f'wav_file: {wav_file}') - if os.path.exists(wav_file): - wav_files.append(wav_file) - print(f"Count of wavs imported: {len(wav_files)}") - else: - # Parse all wav files in data_path - wav_files = glob.glob(data_path + "/**/*.wav", recursive=True) +wav_files = train_files + dev_files # define Encoder model model = setup_model(c) @@ -100,11 +73,19 @@ for idx, wav_file in enumerate(tqdm(wav_files)): if speaker_mapping: # save speaker_mapping if target dataset is defined - if '.json' not in args.output_path: + if '.json' not in args.output_path and '.npy' not in args.output_path: mapping_file_path = os.path.join(args.output_path, "speakers.json") + mapping_npy_file_path = os.path.join(args.output_path, "speakers.npy") else: - mapping_file_path = args.output_path + mapping_file_path = args.output_path.replace(".npy", ".json") + mapping_npy_file_path = mapping_file_path.replace(".json", ".npy") + os.makedirs(os.path.dirname(mapping_file_path), exist_ok=True) + + if args.save_npy: + np.save(mapping_npy_file_path, speaker_mapping) + print("Speaker embeddings saved at:", mapping_npy_file_path) + speaker_manager = SpeakerManager() # pylint: disable=W0212 speaker_manager._save_json(mapping_file_path, speaker_mapping) diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index 7891d65a..8fbc8f8e 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -2,40 +2,41 @@ import argparse import os from argparse import RawTextHelpFormatter - -from TTS.tts.datasets.preprocess import get_preprocessor_by_name +from TTS.tts.datasets.preprocess import load_meta_data +from TTS.config import load_config def main(): # pylint: disable=bad-option-value parser = argparse.ArgumentParser( description="""Find all the unique characters or phonemes in a dataset.\n\n""" - """Target dataset must be defined in TTS.tts.datasets.preprocess\n\n""" + """\n\n""" """ Example runs: - python TTS/bin/find_unique_chars.py --dataset ljspeech --meta_file /path/to/LJSpeech/metadata.csv + python TTS/bin/find_unique_chars.py --config_path config.json """, formatter_class=RawTextHelpFormatter, ) - parser.add_argument( - "--dataset", type=str, default="", help="One of the target dataset names in TTS.tts.datasets.preprocess." + "--config_path", type=str, help="Path to dataset config file.", required=True ) - - parser.add_argument("--meta_file", type=str, default=None, help="Path to the transcriptions file of the dataset.") - args = parser.parse_args() - preprocessor = get_preprocessor_by_name(args.dataset) - items = preprocessor(os.path.dirname(args.meta_file), os.path.basename(args.meta_file)) + c = load_config(args.config_path) + # load all datasets + train_items, dev_items = load_meta_data(c.datasets, eval_split=True, ignore_generated_eval=True) + items = train_items + dev_items + texts = "".join(item[0] for item in items) chars = set(texts) lower_chars = filter(lambda c: c.islower(), chars) + chars_force_lower = set([c.lower() for c in chars]) + print(f" > Number of unique characters: {len(chars)}") print(f" > Unique characters: {''.join(sorted(chars))}") print(f" > Unique lower characters: {''.join(sorted(lower_chars))}") - + print(f" > Unique all forced to lower characters: {''.join(sorted(chars_force_lower))}") if __name__ == "__main__": main() diff --git a/TTS/tts/datasets/preprocess.py b/TTS/tts/datasets/preprocess.py index 72ab160e..7fbc01b8 100644 --- a/TTS/tts/datasets/preprocess.py +++ b/TTS/tts/datasets/preprocess.py @@ -37,7 +37,7 @@ def split_dataset(items): return items[:eval_split_size], items[eval_split_size:] -def load_meta_data(datasets, eval_split=True): +def load_meta_data(datasets, eval_split=True, ignore_generated_eval=False): meta_data_train_all = [] meta_data_eval_all = [] if eval_split else None for dataset in datasets: @@ -54,9 +54,11 @@ def load_meta_data(datasets, eval_split=True): if eval_split: if meta_file_val: meta_data_eval = preprocessor(root_path, meta_file_val) - else: + meta_data_eval_all += meta_data_eval + elif not ignore_generated_eval: meta_data_eval, meta_data_train = split_dataset(meta_data_train) - meta_data_eval_all += meta_data_eval + meta_data_eval_all += meta_data_eval + meta_data_train_all += meta_data_train # load attention masks for duration predictor training if dataset.meta_file_attn_mask: @@ -270,16 +272,20 @@ def libri_tts(root_path, meta_files=None): items = [] if meta_files is None: meta_files = glob(f"{root_path}/**/*trans.tsv", recursive=True) + else: + if isinstance(meta_files, str): + meta_files = [os.path.join(root_path, meta_files)] + for meta_file in meta_files: _meta_file = os.path.basename(meta_file).split(".")[0] - speaker_name = _meta_file.split("_")[0] - chapter_id = _meta_file.split("_")[1] - _root_path = os.path.join(root_path, f"{speaker_name}/{chapter_id}") with open(meta_file, "r") as ttf: for line in ttf: cols = line.split("\t") - wav_file = os.path.join(_root_path, cols[0] + ".wav") - text = cols[1] + file_name = cols[0] + speaker_name, chapter_id, *_ = cols[0].split("_") + _root_path = os.path.join(root_path, f"{speaker_name}/{chapter_id}") + wav_file = os.path.join(_root_path, file_name + ".wav") + text = cols[2] items.append([text, wav_file, "LTTS_" + speaker_name]) for item in items: assert os.path.exists(item[1]), f" [!] wav files don't exist - {item[1]}" @@ -355,6 +361,18 @@ def vctk_slim(root_path, meta_files=None, wavs_path="wav48"): return items +def mls(root_path, meta_files=None): + """http://www.openslr.org/94/""" + items = [] + with open(os.path.join(root_path, meta_files), "r") as meta: + isTrain = "train" in meta_files + for line in meta: + file, text = line.split('\t') + text = text[:-1] + speaker, book, no = file.split('_') + wav_file = os.path.join(root_path, "train" if isTrain else "dev", 'audio', speaker, book, file + ".wav") + items.append([text, wav_file, "MLS_" + speaker]) + return items # ======================================== VOX CELEB =========================================== def voxceleb2(root_path, meta_file=None): From 83644056e368d17e69eaf00b9ef7999bd6d3cfd5 Mon Sep 17 00:00:00 2001 From: Edresson Date: Fri, 18 Jun 2021 14:32:28 -0300 Subject: [PATCH 06/28] fix Lint checks --- TTS/bin/compute_embeddings.py | 4 ++-- TTS/bin/find_unique_chars.py | 4 ++-- TTS/tts/datasets/preprocess.py | 5 ++--- tests/test_speaker_encoder.py | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 9ed459a2..ab5754f7 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -1,16 +1,16 @@ import argparse -import glob import os import torch import numpy as np from tqdm import tqdm +from TTS.config import load_config from TTS.speaker_encoder.utils.generic_utils import setup_model from TTS.tts.datasets.preprocess import load_meta_data from TTS.tts.utils.speakers import SpeakerManager from TTS.utils.audio import AudioProcessor -from TTS.config import load_config + parser = argparse.ArgumentParser( description='Compute embedding vectors for each wav file in a dataset.' diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index 8fbc8f8e..fccbc311 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -1,6 +1,5 @@ """Find all the unique characters in a dataset""" import argparse -import os from argparse import RawTextHelpFormatter from TTS.tts.datasets.preprocess import load_meta_data from TTS.config import load_config @@ -31,7 +30,8 @@ def main(): texts = "".join(item[0] for item in items) chars = set(texts) lower_chars = filter(lambda c: c.islower(), chars) - chars_force_lower = set([c.lower() for c in chars]) + chars_force_lower = [c.lower() for c in chars]) + chars_force_lower = set(chars_force_lower) print(f" > Number of unique characters: {len(chars)}") print(f" > Unique characters: {''.join(sorted(chars))}") diff --git a/TTS/tts/datasets/preprocess.py b/TTS/tts/datasets/preprocess.py index 7fbc01b8..23d3f3c1 100644 --- a/TTS/tts/datasets/preprocess.py +++ b/TTS/tts/datasets/preprocess.py @@ -365,12 +365,11 @@ def mls(root_path, meta_files=None): """http://www.openslr.org/94/""" items = [] with open(os.path.join(root_path, meta_files), "r") as meta: - isTrain = "train" in meta_files for line in meta: file, text = line.split('\t') text = text[:-1] - speaker, book, no = file.split('_') - wav_file = os.path.join(root_path, "train" if isTrain else "dev", 'audio', speaker, book, file + ".wav") + speaker, book, *_ = file.split('_') + wav_file = os.path.join(root_path, os.path.dirname(meta_files), 'audio', speaker, book, file + ".wav") items.append([text, wav_file, "MLS_" + speaker]) return items diff --git a/tests/test_speaker_encoder.py b/tests/test_speaker_encoder.py index f56a9577..cecbd493 100644 --- a/tests/test_speaker_encoder.py +++ b/tests/test_speaker_encoder.py @@ -34,7 +34,7 @@ class LSTMSpeakerEncoderTests(unittest.TestCase): assert abs(assert_diff) < 1e-4, f" [!] output_norm has wrong values - {assert_diff}" # compute d for a given batch dummy_input = T.rand(1, 240, 80) # B x T x D - output = model.compute_embedding(dummy_input, num_frames=160, overlap=0.5) + output = model.compute_embedding(dummy_input, num_frames=160, num_eval=5) assert output.shape[0] == 1 assert output.shape[1] == 256 assert len(output.shape) == 2 From 99d40e98d99ca9c7e848c3b5b19fea1d067c8788 Mon Sep 17 00:00:00 2001 From: Edresson Date: Fri, 18 Jun 2021 14:59:01 -0300 Subject: [PATCH 07/28] fix Lint checks --- TTS/bin/compute_embeddings.py | 1 - TTS/bin/find_unique_chars.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 00a20bdf..5332123d 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -6,7 +6,6 @@ import numpy as np from tqdm import tqdm from TTS.config import load_config -from TTS.config import BaseDatasetConfig, load_config from TTS.speaker_encoder.utils.generic_utils import setup_model from TTS.tts.datasets.preprocess import load_meta_data from TTS.tts.utils.speakers import SpeakerManager diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index fccbc311..8ac73235 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -30,7 +30,7 @@ def main(): texts = "".join(item[0] for item in items) chars = set(texts) lower_chars = filter(lambda c: c.islower(), chars) - chars_force_lower = [c.lower() for c in chars]) + chars_force_lower = [c.lower() for c in chars] chars_force_lower = set(chars_force_lower) print(f" > Number of unique characters: {len(chars)}") From 1c4e806f549923169056ae90c51795ffae772f65 Mon Sep 17 00:00:00 2001 From: Edresson Date: Sun, 27 Jun 2021 03:35:34 -0300 Subject: [PATCH 08/28] use speaker manager on compute embeddings script --- TTS/bin/compute_embeddings.py | 39 +++++----------------------- TTS/speaker_encoder/models/lstm.py | 4 ++- TTS/speaker_encoder/models/resnet.py | 9 +++++++ TTS/tts/utils/speakers.py | 12 ++++++--- 4 files changed, 27 insertions(+), 37 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 5332123d..e843150b 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -1,15 +1,11 @@ import argparse import os -import torch -import numpy as np from tqdm import tqdm from TTS.config import load_config -from TTS.speaker_encoder.utils.generic_utils import setup_model from TTS.tts.datasets.preprocess import load_meta_data from TTS.tts.utils.speakers import SpeakerManager -from TTS.utils.audio import AudioProcessor parser = argparse.ArgumentParser( description='Compute embedding vectors for each wav file in a dataset.' @@ -28,25 +24,14 @@ parser.add_argument( ) parser.add_argument("output_path", type=str, help="path for output speakers.json and/or speakers.npy.") parser.add_argument("--use_cuda", type=bool, help="flag to set cuda.", default=True) -parser.add_argument("--save_npy", type=bool, help="flag to set cuda.", default=False) args = parser.parse_args() - -c = load_config(args.config_path) c_dataset = load_config(args.config_dataset_path) -ap = AudioProcessor(**c["audio"]) - train_files, dev_files = load_meta_data(c_dataset.datasets, eval_split=True, ignore_generated_eval=True) - wav_files = train_files + dev_files -# define Encoder model -model = setup_model(c) -model.load_state_dict(torch.load(args.model_path)["model"]) -model.eval() -if args.use_cuda: - model.cuda() +speaker_manager = SpeakerManager(encoder_model_path=args.model_path, encoder_config_path=args.config_path, use_cuda=args.use_cuda) # compute speaker embeddings speaker_mapping = {} @@ -57,36 +42,24 @@ for idx, wav_file in enumerate(tqdm(wav_files)): else: speaker_name = None - mel_spec = ap.melspectrogram(ap.load_wav(wav_file, sr=ap.sample_rate)).T - mel_spec = torch.FloatTensor(mel_spec[None, :, :]) - if args.use_cuda: - mel_spec = mel_spec.cuda() - embedd = model.compute_embedding(mel_spec) - embedd = embedd.detach().cpu().numpy() + # extract the embedding + embedd = speaker_manager.compute_x_vector_from_clip(wav_file) # create speaker_mapping if target dataset is defined wav_file_name = os.path.basename(wav_file) speaker_mapping[wav_file_name] = {} speaker_mapping[wav_file_name]["name"] = speaker_name - speaker_mapping[wav_file_name]["embedding"] = embedd.flatten().tolist() + speaker_mapping[wav_file_name]["embedding"] = embedd if speaker_mapping: # save speaker_mapping if target dataset is defined - if '.json' not in args.output_path and '.npy' not in args.output_path: - + if '.json' not in args.output_path: mapping_file_path = os.path.join(args.output_path, "speakers.json") - mapping_npy_file_path = os.path.join(args.output_path, "speakers.npy") else: - mapping_file_path = args.output_path.replace(".npy", ".json") - mapping_npy_file_path = mapping_file_path.replace(".json", ".npy") + mapping_file_path = args.output_path os.makedirs(os.path.dirname(mapping_file_path), exist_ok=True) - if args.save_npy: - np.save(mapping_npy_file_path, speaker_mapping) - print("Speaker embeddings saved at:", mapping_npy_file_path) - - speaker_manager = SpeakerManager() # pylint: disable=W0212 speaker_manager._save_json(mapping_file_path, speaker_mapping) print("Speaker embeddings saved at:", mapping_file_path) diff --git a/TTS/speaker_encoder/models/lstm.py b/TTS/speaker_encoder/models/lstm.py index fadada70..21439d6b 100644 --- a/TTS/speaker_encoder/models/lstm.py +++ b/TTS/speaker_encoder/models/lstm.py @@ -119,9 +119,11 @@ class LSTMSpeakerEncoder(nn.Module): return embed / num_iters # pylint: disable=unused-argument, redefined-builtin - def load_checkpoint(self, config: dict, checkpoint_path: str, eval: bool = False): + def load_checkpoint(self, config: dict, checkpoint_path: str, eval: bool = False, use_cuda: bool = False): state = torch.load(checkpoint_path, map_location=torch.device("cpu")) self.load_state_dict(state["model"]) + if use_cuda: + self.cuda() if eval: self.eval() assert not self.training diff --git a/TTS/speaker_encoder/models/resnet.py b/TTS/speaker_encoder/models/resnet.py index ce86b01f..29f3ae61 100644 --- a/TTS/speaker_encoder/models/resnet.py +++ b/TTS/speaker_encoder/models/resnet.py @@ -199,3 +199,12 @@ class ResNetSpeakerEncoder(nn.Module): embeddings = torch.mean(embeddings, dim=0, keepdim=True) return embeddings + + def load_checkpoint(self, config: dict, checkpoint_path: str, eval: bool = False, use_cuda: bool = False): + state = torch.load(checkpoint_path, map_location=torch.device("cpu")) + self.load_state_dict(state["model"]) + if use_cuda: + self.cuda() + if eval: + self.eval() + assert not self.training \ No newline at end of file diff --git a/TTS/tts/utils/speakers.py b/TTS/tts/utils/speakers.py index 84da1f72..1b8c054d 100755 --- a/TTS/tts/utils/speakers.py +++ b/TTS/tts/utils/speakers.py @@ -133,6 +133,7 @@ class SpeakerManager: speaker_id_file_path: str = "", encoder_model_path: str = "", encoder_config_path: str = "", + use_cuda: bool = False, ): self.x_vectors = None @@ -140,6 +141,7 @@ class SpeakerManager: self.clip_ids = None self.speaker_encoder = None self.speaker_encoder_ap = None + self.use_cuda = use_cuda if x_vectors_file_path: self.load_x_vectors_file(x_vectors_file_path) @@ -215,17 +217,19 @@ class SpeakerManager: def init_speaker_encoder(self, model_path: str, config_path: str) -> None: self.speaker_encoder_config = load_config(config_path) self.speaker_encoder = setup_model(self.speaker_encoder_config) - self.speaker_encoder.load_checkpoint(config_path, model_path, True) + self.speaker_encoder.load_checkpoint(config_path, model_path, eval=True, use_cuda=self.use_cuda) self.speaker_encoder_ap = AudioProcessor(**self.speaker_encoder_config.audio) # normalize the input audio level and trim silences - self.speaker_encoder_ap.do_sound_norm = True - self.speaker_encoder_ap.do_trim_silence = True + # self.speaker_encoder_ap.do_sound_norm = True + # self.speaker_encoder_ap.do_trim_silence = True def compute_x_vector_from_clip(self, wav_file: Union[str, list]) -> list: def _compute(wav_file: str): waveform = self.speaker_encoder_ap.load_wav(wav_file, sr=self.speaker_encoder_ap.sample_rate) spec = self.speaker_encoder_ap.melspectrogram(waveform) spec = torch.from_numpy(spec.T) + if self.use_cuda: + spec = spec.cuda() spec = spec.unsqueeze(0) x_vector = self.speaker_encoder.compute_embedding(spec) return x_vector @@ -248,6 +252,8 @@ class SpeakerManager: feats = torch.from_numpy(feats) if feats.ndim == 2: feats = feats.unsqueeze(0) + if self.use_cuda: + feats = feats.cuda() return self.speaker_encoder.compute_embedding(feats) def run_umap(self): From 6e3e6d57563a33e221a672598c9d8d45dc75fd7c Mon Sep 17 00:00:00 2001 From: Aloento <11802769+Aloento@users.noreply.github.com> Date: Thu, 8 Jul 2021 09:53:13 +0200 Subject: [PATCH 09/28] Change to _get_preprocessor_by_name --- TTS/bin/find_unique_chars.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index 75169569..9e62657f 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -3,7 +3,7 @@ import argparse import os from argparse import RawTextHelpFormatter -from TTS.tts.datasets.formatters import get_preprocessor_by_name +from TTS.tts.datasets import _get_preprocessor_by_name def main(): @@ -27,7 +27,7 @@ def main(): args = parser.parse_args() - preprocessor = get_preprocessor_by_name(args.dataset) + preprocessor = _get_preprocessor_by_name(args.dataset) items = preprocessor(os.path.dirname(args.meta_file), os.path.basename(args.meta_file)) texts = "".join(item[0] for item in items) chars = set(texts) From 4eac1c4651f0e9adf3d0618cfac10ea4d4e8bd01 Mon Sep 17 00:00:00 2001 From: Edresson Date: Sun, 11 Jul 2021 12:00:39 -0300 Subject: [PATCH 10/28] bug fix on train_encoder and unit tests --- TTS/bin/train_encoder.py | 2 +- tests/test_speaker_encoder_train.py | 49 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/TTS/bin/train_encoder.py b/TTS/bin/train_encoder.py index 38902a18..2bb5bfc7 100644 --- a/TTS/bin/train_encoder.py +++ b/TTS/bin/train_encoder.py @@ -164,7 +164,7 @@ def main(args): # pylint: disable=redefined-outer-name elif c.loss == "angleproto": criterion = AngleProtoLoss() elif c.loss == "softmaxproto": - criterion = SoftmaxAngleProtoLoss(c.model["proj_dim"], num_speakers) + criterion = SoftmaxAngleProtoLoss(c.model_params["proj_dim"], num_speakers) else: raise Exception("The %s not is a loss supported" % c.loss) diff --git a/tests/test_speaker_encoder_train.py b/tests/test_speaker_encoder_train.py index 21b12074..4419a00f 100644 --- a/tests/test_speaker_encoder_train.py +++ b/tests/test_speaker_encoder_train.py @@ -6,7 +6,18 @@ from tests import get_device_id, get_tests_output_path, run_cli from TTS.config.shared_configs import BaseAudioConfig from TTS.speaker_encoder.speaker_encoder_config import SpeakerEncoderConfig -config_path = os.path.join(get_tests_output_path(), "test_model_config.json") +def run_test_train(): + command = ( + f"CUDA_VISIBLE_DEVICES='{get_device_id()}' python TTS/bin/train_encoder.py --config_path {config_path} " + f"--coqpit.output_path {output_path} " + "--coqpit.datasets.0.name ljspeech " + "--coqpit.datasets.0.meta_file_train metadata.csv " + "--coqpit.datasets.0.meta_file_val metadata.csv " + "--coqpit.datasets.0.path tests/data/ljspeech " + ) + run_cli(command) + +config_path = os.path.join(get_tests_output_path(), "test_speaker_encoder_config.json") output_path = os.path.join(get_tests_output_path(), "train_outputs") config = SpeakerEncoderConfig( @@ -24,16 +35,9 @@ config.audio.do_trim_silence = True config.audio.trim_db = 60 config.save_json(config_path) +print(config) # train the model for one epoch -command_train = ( - f"CUDA_VISIBLE_DEVICES='{get_device_id()}' python TTS/bin/train_encoder.py --config_path {config_path} " - f"--coqpit.output_path {output_path} " - "--coqpit.datasets.0.name ljspeech " - "--coqpit.datasets.0.meta_file_train metadata.csv " - "--coqpit.datasets.0.meta_file_val metadata.csv " - "--coqpit.datasets.0.path tests/data/ljspeech " -) -run_cli(command_train) +run_test_train() # Find latest folder continue_path = max(glob.glob(os.path.join(output_path, "*/")), key=os.path.getmtime) @@ -50,15 +54,7 @@ config.model_params["model_name"] = "resnet" config.save_json(config_path) # train the model for one epoch -command_train = ( - f"CUDA_VISIBLE_DEVICES='{get_device_id()}' python TTS/bin/train_encoder.py --config_path {config_path} " - f"--coqpit.output_path {output_path} " - "--coqpit.datasets.0.name ljspeech " - "--coqpit.datasets.0.meta_file_train metadata.csv " - "--coqpit.datasets.0.meta_file_val metadata.csv " - "--coqpit.datasets.0.path tests/data/ljspeech " -) -run_cli(command_train) +run_test_train() # Find latest folder continue_path = max(glob.glob(os.path.join(output_path, "*/")), key=os.path.getmtime) @@ -69,3 +65,18 @@ command_train = ( ) run_cli(command_train) shutil.rmtree(continue_path) + +# test model with ge2e loss function +config.loss = "ge2e" +config.save_json(config_path) +run_test_train() + +# test model with angleproto loss function +config.loss = "angleproto" +config.save_json(config_path) +run_test_train() + +# test model with softmaxproto loss function +config.loss = "softmaxproto" +config.save_json(config_path) +run_test_train() From d906fea08cf96c95d78fa01cdc935fc6a87c6685 Mon Sep 17 00:00:00 2001 From: Edresson Date: Tue, 13 Jul 2021 02:15:31 -0300 Subject: [PATCH 11/28] lint fix and eval as argparse in extract tts spectrograms --- TTS/bin/compute_embeddings.py | 5 +---- TTS/bin/extract_tts_spectrograms.py | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 19bfbe3a..7719318a 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -1,13 +1,10 @@ import argparse -import glob import os -import torch from tqdm import tqdm -from TTS.config import BaseDatasetConfig, load_config -from TTS.speaker_encoder.utils.generic_utils import setup_model +from TTS.config import load_config from TTS.tts.datasets import load_meta_data from TTS.tts.utils.speakers import SpeakerManager diff --git a/TTS/bin/extract_tts_spectrograms.py b/TTS/bin/extract_tts_spectrograms.py index 0bd84db1..0e783c2f 100755 --- a/TTS/bin/extract_tts_spectrograms.py +++ b/TTS/bin/extract_tts_spectrograms.py @@ -227,7 +227,7 @@ def main(args): # pylint: disable=redefined-outer-name ap = AudioProcessor(**c.audio) # load data instances - meta_data_train, meta_data_eval = load_meta_data(c.datasets, eval_split=True, ignore_generated_eval=True) + meta_data_train, meta_data_eval = load_meta_data(c.datasets, eval_split=args.eval, ignore_generated_eval=True) # use eval and training partitions meta_data = meta_data_train + meta_data_eval @@ -271,6 +271,7 @@ if __name__ == "__main__": parser.add_argument("--debug", default=False, action="store_true", help="Save audio files for debug") parser.add_argument("--save_audio", default=False, action="store_true", help="Save audio files") parser.add_argument("--quantized", action="store_true", help="Save quantized audio files") + parser.add_argument("--eval", type=bool, help="compute eval.", default=True) args = parser.parse_args() c = load_config(args.config_path) From 32974dd6a9f30fad1eb3652a6637bcc607bbc763 Mon Sep 17 00:00:00 2001 From: WeberJulian Date: Tue, 13 Jul 2021 16:04:42 +0200 Subject: [PATCH 12/28] Fix test sentences synthesis --- TTS/trainer.py | 6 +++--- TTS/tts/models/base_tts.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index c56be140..bbd9665a 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -764,11 +764,11 @@ class Trainer: """Run test and log the results. Test run must be defined by the model. Model must return figures and audios to be logged by the Tensorboard.""" if hasattr(self.model, "test_run"): - if hasattr(self.eval_loader.load_test_samples): + if hasattr(self.eval_loader, "load_test_samples"): samples = self.eval_loader.load_test_samples(1) figures, audios = self.model.test_run(samples) else: - figures, audios = self.model.test_run() + figures, audios = self.model.test_run(use_cuda=self.use_cuda, ap=self.ap) self.tb_logger.tb_test_audios(self.total_steps_done, audios, self.config.audio["sample_rate"]) self.tb_logger.tb_test_figures(self.total_steps_done, figures) @@ -790,7 +790,7 @@ class Trainer: self.train_epoch() if self.config.run_eval: self.eval_epoch() - if epoch >= self.config.test_delay_epochs and self.args.rank < 0: + if epoch >= self.config.test_delay_epochs and self.args.rank <= 0: self.test_run() self.c_logger.print_epoch_end( epoch, self.keep_avg_eval.avg_values if self.config.run_eval else self.keep_avg_train.avg_values diff --git a/TTS/tts/models/base_tts.py b/TTS/tts/models/base_tts.py index 2ec268d6..64c0ba6f 100644 --- a/TTS/tts/models/base_tts.py +++ b/TTS/tts/models/base_tts.py @@ -200,7 +200,7 @@ class BaseTTS(BaseModel): ) return loader - def test_run(self) -> Tuple[Dict, Dict]: + def test_run(self, use_cuda=True, ap=None) -> Tuple[Dict, Dict]: """Generic test run for `tts` models used by `Trainer`. You can override this for a different behaviour. @@ -212,14 +212,14 @@ class BaseTTS(BaseModel): test_audios = {} test_figures = {} test_sentences = self.config.test_sentences - aux_inputs = self._get_aux_inputs() + aux_inputs = self.get_aux_input() for idx, sen in enumerate(test_sentences): wav, alignment, model_outputs, _ = synthesis( - self.model, + self, sen, self.config, - self.use_cuda, - self.ap, + use_cuda, + ap, speaker_id=aux_inputs["speaker_id"], d_vector=aux_inputs["d_vector"], style_wav=aux_inputs["style_wav"], @@ -229,6 +229,6 @@ class BaseTTS(BaseModel): ).values() test_audios["{}-audio".format(idx)] = wav - test_figures["{}-prediction".format(idx)] = plot_spectrogram(model_outputs, self.ap, output_fig=False) + test_figures["{}-prediction".format(idx)] = plot_spectrogram(model_outputs, ap, output_fig=False) test_figures["{}-alignment".format(idx)] = plot_alignment(alignment, output_fig=False) return test_figures, test_audios From 7d92b309465b58005450b1cc6459ef6e119c8453 Mon Sep 17 00:00:00 2001 From: WeberJulian Date: Tue, 13 Jul 2021 23:00:34 +0200 Subject: [PATCH 13/28] Fix tests --- TTS/trainer.py | 11 +++++++---- TTS/tts/models/base_tts.py | 4 ++-- TTS/vocoder/models/wavegrad.py | 5 ++++- TTS/vocoder/models/wavernn.py | 10 ++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index bbd9665a..b2494bad 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -22,6 +22,7 @@ from torch.utils.data import DataLoader from TTS.config import load_config, register_config from TTS.tts.datasets import load_meta_data from TTS.tts.models import setup_model as setup_tts_model +from TTS.vocoder.models.wavegrad import Wavegrad from TTS.tts.utils.text.symbols import parse_symbols from TTS.utils.audio import AudioProcessor from TTS.utils.callbacks import TrainerCallback @@ -764,11 +765,13 @@ class Trainer: """Run test and log the results. Test run must be defined by the model. Model must return figures and audios to be logged by the Tensorboard.""" if hasattr(self.model, "test_run"): - if hasattr(self.eval_loader, "load_test_samples"): - samples = self.eval_loader.load_test_samples(1) - figures, audios = self.model.test_run(samples) + if isinstance(self.model, Wavegrad): + return None # TODO: Fix inference on WaveGrad + elif hasattr(self.eval_loader.dataset, "load_test_samples"): + samples = self.eval_loader.dataset.load_test_samples(1) + figures, audios = self.model.test_run(self.ap, samples, None, self.use_cuda) else: - figures, audios = self.model.test_run(use_cuda=self.use_cuda, ap=self.ap) + figures, audios = self.model.test_run(self.ap, self.use_cuda) self.tb_logger.tb_test_audios(self.total_steps_done, audios, self.config.audio["sample_rate"]) self.tb_logger.tb_test_figures(self.total_steps_done, figures) diff --git a/TTS/tts/models/base_tts.py b/TTS/tts/models/base_tts.py index 64c0ba6f..a30c5f02 100644 --- a/TTS/tts/models/base_tts.py +++ b/TTS/tts/models/base_tts.py @@ -70,7 +70,7 @@ class BaseTTS(BaseModel): def get_aux_input(self, **kwargs) -> Dict: """Prepare and return `aux_input` used by `forward()`""" - pass + return {"speaker_id": None, "style_wav": None, "d_vector": None} def format_batch(self, batch: Dict) -> Dict: """Generic batch formatting for `TTSDataset`. @@ -200,7 +200,7 @@ class BaseTTS(BaseModel): ) return loader - def test_run(self, use_cuda=True, ap=None) -> Tuple[Dict, Dict]: + def test_run(self, ap, use_cuda) -> Tuple[Dict, Dict]: """Generic test run for `tts` models used by `Trainer`. You can override this for a different behaviour. diff --git a/TTS/vocoder/models/wavegrad.py b/TTS/vocoder/models/wavegrad.py index 03d5160e..7781b5f5 100644 --- a/TTS/vocoder/models/wavegrad.py +++ b/TTS/vocoder/models/wavegrad.py @@ -261,13 +261,16 @@ class Wavegrad(BaseModel): def eval_log(self, ap: AudioProcessor, batch: Dict, outputs: Dict) -> Tuple[Dict, np.ndarray]: return None, None - def test_run(self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict): # pylint: disable=unused-argument + def test_run(self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict, use_cuda): # pylint: disable=unused-argument # setup noise schedule and inference noise_schedule = self.config["test_noise_schedule"] betas = np.linspace(noise_schedule["min_val"], noise_schedule["max_val"], noise_schedule["num_steps"]) self.compute_noise_level(betas) for sample in samples: + sample = self.format_batch(sample) x = sample["input"] + if use_cuda: + x = x.cuda() y = sample["waveform"] # compute voice y_pred = self.inference(x) diff --git a/TTS/vocoder/models/wavernn.py b/TTS/vocoder/models/wavernn.py index a5d89d5a..12a29a72 100644 --- a/TTS/vocoder/models/wavernn.py +++ b/TTS/vocoder/models/wavernn.py @@ -322,7 +322,7 @@ class Wavernn(BaseVocoder): with torch.no_grad(): if isinstance(mels, np.ndarray): - mels = torch.FloatTensor(mels).type_as(mels) + mels = torch.FloatTensor(mels) if mels.ndim == 2: mels = mels.unsqueeze(0) @@ -571,12 +571,14 @@ class Wavernn(BaseVocoder): @torch.no_grad() def test_run( - self, ap: AudioProcessor, samples: List[Dict], output: Dict # pylint: disable=unused-argument + self, ap: AudioProcessor, samples: List[Dict], output: Dict, use_cuda # pylint: disable=unused-argument ) -> Tuple[Dict, Dict]: figures = {} audios = {} for idx, sample in enumerate(samples): - x = sample["input"] + x = torch.FloatTensor(sample[0]) + if use_cuda: + x = x.cuda() y_hat = self.inference(x, self.config.batched, self.config.target_samples, self.config.overlap_samples) x_hat = ap.melspectrogram(y_hat) figures.update( @@ -585,7 +587,7 @@ class Wavernn(BaseVocoder): f"test_{idx}/prediction": plot_spectrogram(x_hat.T), } ) - audios.update({f"test_{idx}/audio", y_hat}) + audios.update({f"test_{idx}/audio": y_hat}) return figures, audios @staticmethod From c79a82ed07b79f0b802cc3f08dd2594da4a81547 Mon Sep 17 00:00:00 2001 From: WeberJulian Date: Tue, 13 Jul 2021 23:12:18 +0200 Subject: [PATCH 14/28] refix linter --- TTS/trainer.py | 7 ++++--- TTS/tts/models/glow_tts.py | 2 +- TTS/vocoder/models/__init__.py | 4 ++-- TTS/vocoder/models/wavegrad.py | 4 +++- TTS/vocoder/models/wavernn.py | 2 +- tests/test_speaker_encoder_train.py | 2 ++ 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index b2494bad..fd316e78 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -22,7 +22,6 @@ from torch.utils.data import DataLoader from TTS.config import load_config, register_config from TTS.tts.datasets import load_meta_data from TTS.tts.models import setup_model as setup_tts_model -from TTS.vocoder.models.wavegrad import Wavegrad from TTS.tts.utils.text.symbols import parse_symbols from TTS.utils.audio import AudioProcessor from TTS.utils.callbacks import TrainerCallback @@ -41,6 +40,7 @@ from TTS.utils.logging import ConsoleLogger, TensorboardLogger from TTS.utils.trainer_utils import get_optimizer, get_scheduler, is_apex_available, setup_torch_training_env from TTS.vocoder.datasets.preprocess import load_wav_data, load_wav_feat_data from TTS.vocoder.models import setup_model as setup_vocoder_model +from TTS.vocoder.models.wavegrad import Wavegrad if platform.system() != "Windows": # https://github.com/pytorch/pytorch/issues/973 @@ -766,14 +766,15 @@ class Trainer: Model must return figures and audios to be logged by the Tensorboard.""" if hasattr(self.model, "test_run"): if isinstance(self.model, Wavegrad): - return None # TODO: Fix inference on WaveGrad - elif hasattr(self.eval_loader.dataset, "load_test_samples"): + return None # TODO: Fix inference on WaveGrad + if hasattr(self.eval_loader.dataset, "load_test_samples"): samples = self.eval_loader.dataset.load_test_samples(1) figures, audios = self.model.test_run(self.ap, samples, None, self.use_cuda) else: figures, audios = self.model.test_run(self.ap, self.use_cuda) self.tb_logger.tb_test_audios(self.total_steps_done, audios, self.config.audio["sample_rate"]) self.tb_logger.tb_test_figures(self.total_steps_done, figures) + return None def _fit(self) -> None: """🏃 train -> evaluate -> test for the number of epochs.""" diff --git a/TTS/tts/models/glow_tts.py b/TTS/tts/models/glow_tts.py index 9f235fad..b3bceb09 100755 --- a/TTS/tts/models/glow_tts.py +++ b/TTS/tts/models/glow_tts.py @@ -113,7 +113,7 @@ class GlowTTS(BaseTTS): @staticmethod def compute_outputs(attn, o_mean, o_log_scale, x_mask): - """ Compute and format the mode outputs with the given alignment map""" + """Compute and format the mode outputs with the given alignment map""" y_mean = torch.matmul(attn.squeeze(1).transpose(1, 2), o_mean.transpose(1, 2)).transpose( 1, 2 ) # [b, t', t], [b, t, d] -> [b, d, t'] diff --git a/TTS/vocoder/models/__init__.py b/TTS/vocoder/models/__init__.py index 9479095e..7c209af4 100644 --- a/TTS/vocoder/models/__init__.py +++ b/TTS/vocoder/models/__init__.py @@ -31,7 +31,7 @@ def setup_model(config: Coqpit): def setup_generator(c): - """ TODO: use config object as arguments""" + """TODO: use config object as arguments""" print(" > Generator Model: {}".format(c.generator_model)) MyModel = importlib.import_module("TTS.vocoder.models." + c.generator_model.lower()) MyModel = getattr(MyModel, to_camel(c.generator_model)) @@ -94,7 +94,7 @@ def setup_generator(c): def setup_discriminator(c): - """ TODO: use config objekt as arguments""" + """TODO: use config objekt as arguments""" print(" > Discriminator Model: {}".format(c.discriminator_model)) if "parallel_wavegan" in c.discriminator_model: MyModel = importlib.import_module("TTS.vocoder.models.parallel_wavegan_discriminator") diff --git a/TTS/vocoder/models/wavegrad.py b/TTS/vocoder/models/wavegrad.py index 7781b5f5..01b47a20 100644 --- a/TTS/vocoder/models/wavegrad.py +++ b/TTS/vocoder/models/wavegrad.py @@ -261,7 +261,9 @@ class Wavegrad(BaseModel): def eval_log(self, ap: AudioProcessor, batch: Dict, outputs: Dict) -> Tuple[Dict, np.ndarray]: return None, None - def test_run(self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict, use_cuda): # pylint: disable=unused-argument + def test_run( + self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict, use_cuda + ): # pylint: disable=unused-argument # setup noise schedule and inference noise_schedule = self.config["test_noise_schedule"] betas = np.linspace(noise_schedule["min_val"], noise_schedule["max_val"], noise_schedule["num_steps"]) diff --git a/TTS/vocoder/models/wavernn.py b/TTS/vocoder/models/wavernn.py index 12a29a72..90eee58e 100644 --- a/TTS/vocoder/models/wavernn.py +++ b/TTS/vocoder/models/wavernn.py @@ -571,7 +571,7 @@ class Wavernn(BaseVocoder): @torch.no_grad() def test_run( - self, ap: AudioProcessor, samples: List[Dict], output: Dict, use_cuda # pylint: disable=unused-argument + self, ap: AudioProcessor, samples: List[Dict], output: Dict, use_cuda # pylint: disable=unused-argument ) -> Tuple[Dict, Dict]: figures = {} audios = {} diff --git a/tests/test_speaker_encoder_train.py b/tests/test_speaker_encoder_train.py index 4419a00f..7901fe5a 100644 --- a/tests/test_speaker_encoder_train.py +++ b/tests/test_speaker_encoder_train.py @@ -6,6 +6,7 @@ from tests import get_device_id, get_tests_output_path, run_cli from TTS.config.shared_configs import BaseAudioConfig from TTS.speaker_encoder.speaker_encoder_config import SpeakerEncoderConfig + def run_test_train(): command = ( f"CUDA_VISIBLE_DEVICES='{get_device_id()}' python TTS/bin/train_encoder.py --config_path {config_path} " @@ -17,6 +18,7 @@ def run_test_train(): ) run_cli(command) + config_path = os.path.join(get_tests_output_path(), "test_speaker_encoder_config.json") output_path = os.path.join(get_tests_output_path(), "train_outputs") From 98d88b88bd1263da14b8382a96c08423fd158dee Mon Sep 17 00:00:00 2001 From: ravi maithrey Date: Wed, 14 Jul 2021 18:16:27 +0530 Subject: [PATCH 15/28] added information to ask for model contributions --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 831eddd5..89138e47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ This repository is governed by [the Contributor Covenant Code of Conduct](https: ## Where to start. We welcome everyone who likes to contribute to 🐸TTS. + You can contribute not only with code but with bug reports, comments, questions, answers, or just a simple tweet to spread the word. If you like to contribute code, squash a bug but if you don't know where to start, here are some pointers. @@ -25,6 +26,16 @@ If you like to contribute code, squash a bug but if you don't know where to star We list all the target improvements for the next version. You can pick one of them and start contributing. - Also feel free to suggest new features, ideas and models. We're always open for new things. +#####Call for sharing language models +If possible, please consider sharing your pre-trained models in any language (if the licences allow for you to do so). We will include them in our model catalogue for public use and give the proper attribution, whether it be your name, company, website or any other source specified. + +This model can be shared in two ways: +1. Share the model files with us and we serve them with the next 🐸 TTS release. +2. Upload your models on GDrive and share the link. + +Models are served under `.models.json` file and any model is available under TTS CLI or Server end points. + +Either way you choose, please make sure you send the models [here](https://github.com/coqui-ai/TTS/issues/380). ## Sending a ✨**PR**✨ If you have a new feature, a model to implement, or a bug to squash, go ahead and send a ✨**PR**✨. From b1620d1f3f517821507991e1a85a412ab636c4ce Mon Sep 17 00:00:00 2001 From: Edresson Date: Thu, 15 Jul 2021 03:34:28 -0300 Subject: [PATCH 16/28] remove ignore generate eval flag --- TTS/bin/compute_embeddings.py | 4 ++-- TTS/bin/extract_tts_spectrograms.py | 2 +- TTS/bin/find_unique_chars.py | 2 +- TTS/tts/datasets/__init__.py | 8 +++----- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 7719318a..7ea1e4f9 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -32,8 +32,8 @@ args = parser.parse_args() c_dataset = load_config(args.config_dataset_path) -train_files, dev_files = load_meta_data(c_dataset.datasets, eval_split=args.eval, ignore_generated_eval=True) -wav_files = train_files + dev_files +meta_data_train, meta_data_eval = load_meta_data(c_dataset.datasets, eval_split=args.eval) +wav_files = meta_data_train + meta_data_eval speaker_manager = SpeakerManager(encoder_model_path=args.model_path, encoder_config_path=args.config_path, use_cuda=args.use_cuda) diff --git a/TTS/bin/extract_tts_spectrograms.py b/TTS/bin/extract_tts_spectrograms.py index 0e783c2f..1cbc5516 100755 --- a/TTS/bin/extract_tts_spectrograms.py +++ b/TTS/bin/extract_tts_spectrograms.py @@ -227,7 +227,7 @@ def main(args): # pylint: disable=redefined-outer-name ap = AudioProcessor(**c.audio) # load data instances - meta_data_train, meta_data_eval = load_meta_data(c.datasets, eval_split=args.eval, ignore_generated_eval=True) + meta_data_train, meta_data_eval = load_meta_data(c.datasets, eval_split=args.eval) # use eval and training partitions meta_data = meta_data_train + meta_data_eval diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index 6273b752..c7c25d80 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -24,7 +24,7 @@ def main(): c = load_config(args.config_path) # load all datasets - train_items, eval_items = load_meta_data(c.datasets, eval_split=True, ignore_generated_eval=True) + train_items, eval_items = load_meta_data(c.datasets, eval_split=True) items = train_items + eval_items texts = "".join(item[0] for item in items) diff --git a/TTS/tts/datasets/__init__.py b/TTS/tts/datasets/__init__.py index 736d6ed4..cbae78a7 100644 --- a/TTS/tts/datasets/__init__.py +++ b/TTS/tts/datasets/__init__.py @@ -30,7 +30,7 @@ def split_dataset(items): return items[:eval_split_size], items[eval_split_size:] -def load_meta_data(datasets, eval_split=True, ignore_generated_eval=False): +def load_meta_data(datasets, eval_split=True): meta_data_train_all = [] meta_data_eval_all = [] if eval_split else None for dataset in datasets: @@ -47,11 +47,9 @@ def load_meta_data(datasets, eval_split=True, ignore_generated_eval=False): if eval_split: if meta_file_val: meta_data_eval = preprocessor(root_path, meta_file_val) - meta_data_eval_all += meta_data_eval - elif not ignore_generated_eval: + else: meta_data_eval, meta_data_train = split_dataset(meta_data_train) - meta_data_eval_all += meta_data_eval - + meta_data_eval_all += meta_data_eval meta_data_train_all += meta_data_train # load attention masks for duration predictor training if dataset.meta_file_attn_mask: From 25832eb97bad61e56e15299eec2462c08b2798c5 Mon Sep 17 00:00:00 2001 From: WeberJulian Date: Thu, 15 Jul 2021 11:38:45 +0200 Subject: [PATCH 17/28] Changes for review --- TTS/trainer.py | 4 ++-- TTS/tts/models/base_tts.py | 4 ++-- TTS/vocoder/models/wavegrad.py | 7 ++----- TTS/vocoder/models/wavernn.py | 7 +++---- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index fd316e78..12f43563 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -769,9 +769,9 @@ class Trainer: return None # TODO: Fix inference on WaveGrad if hasattr(self.eval_loader.dataset, "load_test_samples"): samples = self.eval_loader.dataset.load_test_samples(1) - figures, audios = self.model.test_run(self.ap, samples, None, self.use_cuda) + figures, audios = self.model.test_run(self.ap, samples, None) else: - figures, audios = self.model.test_run(self.ap, self.use_cuda) + figures, audios = self.model.test_run(self.ap) self.tb_logger.tb_test_audios(self.total_steps_done, audios, self.config.audio["sample_rate"]) self.tb_logger.tb_test_figures(self.total_steps_done, figures) return None diff --git a/TTS/tts/models/base_tts.py b/TTS/tts/models/base_tts.py index a30c5f02..561b76fb 100644 --- a/TTS/tts/models/base_tts.py +++ b/TTS/tts/models/base_tts.py @@ -200,7 +200,7 @@ class BaseTTS(BaseModel): ) return loader - def test_run(self, ap, use_cuda) -> Tuple[Dict, Dict]: + def test_run(self, ap) -> Tuple[Dict, Dict]: """Generic test run for `tts` models used by `Trainer`. You can override this for a different behaviour. @@ -218,7 +218,7 @@ class BaseTTS(BaseModel): self, sen, self.config, - use_cuda, + "cuda" in str(next(self.parameters()).device), ap, speaker_id=aux_inputs["speaker_id"], d_vector=aux_inputs["d_vector"], diff --git a/TTS/vocoder/models/wavegrad.py b/TTS/vocoder/models/wavegrad.py index 01b47a20..9249f81c 100644 --- a/TTS/vocoder/models/wavegrad.py +++ b/TTS/vocoder/models/wavegrad.py @@ -261,9 +261,7 @@ class Wavegrad(BaseModel): def eval_log(self, ap: AudioProcessor, batch: Dict, outputs: Dict) -> Tuple[Dict, np.ndarray]: return None, None - def test_run( - self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict, use_cuda - ): # pylint: disable=unused-argument + def test_run(self, ap: AudioProcessor, samples: List[Dict], ouputs: Dict): # pylint: disable=unused-argument # setup noise schedule and inference noise_schedule = self.config["test_noise_schedule"] betas = np.linspace(noise_schedule["min_val"], noise_schedule["max_val"], noise_schedule["num_steps"]) @@ -271,8 +269,7 @@ class Wavegrad(BaseModel): for sample in samples: sample = self.format_batch(sample) x = sample["input"] - if use_cuda: - x = x.cuda() + x = x.to(next(self.parameters()).device) y = sample["waveform"] # compute voice y_pred = self.inference(x) diff --git a/TTS/vocoder/models/wavernn.py b/TTS/vocoder/models/wavernn.py index 90eee58e..c2e47120 100644 --- a/TTS/vocoder/models/wavernn.py +++ b/TTS/vocoder/models/wavernn.py @@ -322,7 +322,7 @@ class Wavernn(BaseVocoder): with torch.no_grad(): if isinstance(mels, np.ndarray): - mels = torch.FloatTensor(mels) + mels = torch.FloatTensor(mels).to(str(next(self.parameters()).device)) if mels.ndim == 2: mels = mels.unsqueeze(0) @@ -571,14 +571,13 @@ class Wavernn(BaseVocoder): @torch.no_grad() def test_run( - self, ap: AudioProcessor, samples: List[Dict], output: Dict, use_cuda # pylint: disable=unused-argument + self, ap: AudioProcessor, samples: List[Dict], output: Dict # pylint: disable=unused-argument ) -> Tuple[Dict, Dict]: figures = {} audios = {} for idx, sample in enumerate(samples): x = torch.FloatTensor(sample[0]) - if use_cuda: - x = x.cuda() + x = x.to(next(self.parameters()).device) y_hat = self.inference(x, self.config.batched, self.config.target_samples, self.config.overlap_samples) x_hat = ap.melspectrogram(y_hat) figures.update( From 58cc414477df5dfdd897c56a860bcfb308002b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Fri, 16 Jul 2021 13:02:25 +0200 Subject: [PATCH 18/28] Fix WaveGrad `test_run` --- TTS/trainer.py | 2 -- TTS/vocoder/datasets/wavegrad_dataset.py | 15 ++++++++++++++- TTS/vocoder/models/wavegrad.py | 19 ++++++++++++------- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index 12f43563..f3f45ebd 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -765,8 +765,6 @@ class Trainer: """Run test and log the results. Test run must be defined by the model. Model must return figures and audios to be logged by the Tensorboard.""" if hasattr(self.model, "test_run"): - if isinstance(self.model, Wavegrad): - return None # TODO: Fix inference on WaveGrad if hasattr(self.eval_loader.dataset, "load_test_samples"): samples = self.eval_loader.dataset.load_test_samples(1) figures, audios = self.model.test_run(self.ap, samples, None) diff --git a/TTS/vocoder/datasets/wavegrad_dataset.py b/TTS/vocoder/datasets/wavegrad_dataset.py index d99fc417..05e0fae8 100644 --- a/TTS/vocoder/datasets/wavegrad_dataset.py +++ b/TTS/vocoder/datasets/wavegrad_dataset.py @@ -2,6 +2,7 @@ import glob import os import random from multiprocessing import Manager +from typing import List, Tuple import numpy as np import torch @@ -67,7 +68,19 @@ class WaveGradDataset(Dataset): item = self.load_item(idx) return item - def load_test_samples(self, num_samples): + def load_test_samples(self, num_samples: int) -> List[Tuple]: + """Return test samples. + + Args: + num_samples (int): Number of samples to return. + + Returns: + List[Tuple]: melspectorgram and audio. + + Shapes: + - melspectrogram (Tensor): :math:`[C, T]` + - audio (Tensor): :math:`[T_audio]` + """ samples = [] return_segments = self.return_segments self.return_segments = False diff --git a/TTS/vocoder/models/wavegrad.py b/TTS/vocoder/models/wavegrad.py index 9249f81c..22d2a015 100644 --- a/TTS/vocoder/models/wavegrad.py +++ b/TTS/vocoder/models/wavegrad.py @@ -124,11 +124,16 @@ class Wavegrad(BaseModel): @torch.no_grad() def inference(self, x, y_n=None): - """x: B x D X T""" + """ + Shapes: + x: :math:`[B, C , T]` + y_n: :math:`[B, 1, T]` + """ if y_n is None: - y_n = torch.randn(x.shape[0], 1, self.hop_len * x.shape[-1], dtype=torch.float32).to(x) + y_n = torch.randn(x.shape[0], 1, self.hop_len * x.shape[-1]) else: - y_n = torch.FloatTensor(y_n).unsqueeze(0).unsqueeze(0).to(x) + y_n = torch.FloatTensor(y_n).unsqueeze(0).unsqueeze(0) + y_n = y_n.type_as(x) sqrt_alpha_hat = self.noise_level.to(x) for n in range(len(self.alpha) - 1, -1, -1): y_n = self.c1[n] * (y_n - self.c2[n] * self.forward(y_n, x, sqrt_alpha_hat[n].repeat(x.shape[0]))) @@ -267,10 +272,10 @@ class Wavegrad(BaseModel): betas = np.linspace(noise_schedule["min_val"], noise_schedule["max_val"], noise_schedule["num_steps"]) self.compute_noise_level(betas) for sample in samples: - sample = self.format_batch(sample) - x = sample["input"] - x = x.to(next(self.parameters()).device) - y = sample["waveform"] + x = sample[0] + x = x[None, : , :].to(next(self.parameters()).device) + y = sample[1] + y = y[None, :] # compute voice y_pred = self.inference(x) # compute spectrograms From 05c75aa9d5358065f86fc321a2c843b0f41add38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Fri, 16 Jul 2021 13:37:38 +0200 Subject: [PATCH 19/28] Fix linter issues --- TTS/trainer.py | 2 -- TTS/vocoder/models/wavegrad.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/TTS/trainer.py b/TTS/trainer.py index f3f45ebd..903aee5f 100644 --- a/TTS/trainer.py +++ b/TTS/trainer.py @@ -40,7 +40,6 @@ from TTS.utils.logging import ConsoleLogger, TensorboardLogger from TTS.utils.trainer_utils import get_optimizer, get_scheduler, is_apex_available, setup_torch_training_env from TTS.vocoder.datasets.preprocess import load_wav_data, load_wav_feat_data from TTS.vocoder.models import setup_model as setup_vocoder_model -from TTS.vocoder.models.wavegrad import Wavegrad if platform.system() != "Windows": # https://github.com/pytorch/pytorch/issues/973 @@ -772,7 +771,6 @@ class Trainer: figures, audios = self.model.test_run(self.ap) self.tb_logger.tb_test_audios(self.total_steps_done, audios, self.config.audio["sample_rate"]) self.tb_logger.tb_test_figures(self.total_steps_done, figures) - return None def _fit(self) -> None: """🏃 train -> evaluate -> test for the number of epochs.""" diff --git a/TTS/vocoder/models/wavegrad.py b/TTS/vocoder/models/wavegrad.py index 22d2a015..d2983be2 100644 --- a/TTS/vocoder/models/wavegrad.py +++ b/TTS/vocoder/models/wavegrad.py @@ -273,7 +273,7 @@ class Wavegrad(BaseModel): self.compute_noise_level(betas) for sample in samples: x = sample[0] - x = x[None, : , :].to(next(self.parameters()).device) + x = x[None, :, :].to(next(self.parameters()).device) y = sample[1] y = y[None, :] # compute voice From d5adc35fdfd87a417431a549358711220ad971e1 Mon Sep 17 00:00:00 2001 From: Edresson Casanova Date: Wed, 21 Jul 2021 07:16:10 -0300 Subject: [PATCH 20/28] Add docstring to compute_embeddings script --- TTS/bin/compute_embeddings.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index 7ea1e4f9..f485514a 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -4,13 +4,18 @@ import os from tqdm import tqdm +from argparse import RawTextHelpFormatter from TTS.config import load_config from TTS.tts.datasets import load_meta_data from TTS.tts.utils.speakers import SpeakerManager - parser = argparse.ArgumentParser( - description='Compute embedding vectors for each wav file in a dataset.' + description="""Compute embedding vectors for each wav file in a dataset.\n\n""" + """ + Example runs: + python TTS/bin/compute_embeddings.py speaker_encoder_model.pth.tar speaker_encoder_config.json dataset_config.json embeddings_output_path/ + """, + formatter_class=RawTextHelpFormatter, ) parser.add_argument("model_path", type=str, help="Path to model checkpoint file.") parser.add_argument( From e1f0ea5487782365e28c1e281ef476625e0d31d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Thu, 8 Jul 2021 01:30:21 +0200 Subject: [PATCH 21/28] Fix #618 --- hubconf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hubconf.py b/hubconf.py index 96f12b5f..0c9c5930 100644 --- a/hubconf.py +++ b/hubconf.py @@ -1,5 +1,5 @@ dependencies = [ - 'torch', 'gdown', 'pysbd', 'gruut', 'anyascii', 'pypinyin', 'coqpit', 'mecab-python3', 'unidic-lite` + 'torch', 'gdown', 'pysbd', 'gruut', 'anyascii', 'pypinyin', 'coqpit', 'mecab-python3', 'unidic-lite' ] import torch From 377b379f1e168f4e56e41c8e1dd1229abc5694dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Thu, 8 Jul 2021 01:55:02 +0200 Subject: [PATCH 22/28] Update dataset URL --- docs/source/tts_datasets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tts_datasets.md b/docs/source/tts_datasets.md index 6075bc95..852ccd37 100644 --- a/docs/source/tts_datasets.md +++ b/docs/source/tts_datasets.md @@ -11,6 +11,6 @@ Some of the known public datasets that we successfully applied 🐸TTS: - [Spanish](https://drive.google.com/file/d/1Sm_zyBo67XHkiFhcRSQ4YaHPYM0slO_e/view?usp=sharing) - thx! @carlfm01 - [German - Thorsten OGVD](https://github.com/thorstenMueller/deep-learning-german-tts) - [Japanese - Kokoro](https://www.kaggle.com/kaiida/kokoro-speech-dataset-v11-small/version/1) -- [Chinese](https://www.data-baker.com/open_source.html) +- [Chinese](https://www.data-baker.com/data/index/source/) Let us know if you use 🐸TTS on a different dataset. \ No newline at end of file From d7a99653898e0c254b584fcc51927f8443ed39e7 Mon Sep 17 00:00:00 2001 From: ravi maithrey Date: Wed, 14 Jul 2021 18:16:27 +0530 Subject: [PATCH 23/28] added information to ask for model contributions --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 831eddd5..89138e47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ This repository is governed by [the Contributor Covenant Code of Conduct](https: ## Where to start. We welcome everyone who likes to contribute to 🐸TTS. + You can contribute not only with code but with bug reports, comments, questions, answers, or just a simple tweet to spread the word. If you like to contribute code, squash a bug but if you don't know where to start, here are some pointers. @@ -25,6 +26,16 @@ If you like to contribute code, squash a bug but if you don't know where to star We list all the target improvements for the next version. You can pick one of them and start contributing. - Also feel free to suggest new features, ideas and models. We're always open for new things. +#####Call for sharing language models +If possible, please consider sharing your pre-trained models in any language (if the licences allow for you to do so). We will include them in our model catalogue for public use and give the proper attribution, whether it be your name, company, website or any other source specified. + +This model can be shared in two ways: +1. Share the model files with us and we serve them with the next 🐸 TTS release. +2. Upload your models on GDrive and share the link. + +Models are served under `.models.json` file and any model is available under TTS CLI or Server end points. + +Either way you choose, please make sure you send the models [here](https://github.com/coqui-ai/TTS/issues/380). ## Sending a ✨**PR**✨ If you have a new feature, a model to implement, or a bug to squash, go ahead and send a ✨**PR**✨. From fc0c4600bdf6e69e090bfcb9befe811df18985bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Tue, 20 Jul 2021 17:34:42 +0200 Subject: [PATCH 24/28] Fix stopnet training --- TTS/tts/layers/losses.py | 15 ++++++++------- TTS/tts/models/base_tts.py | 4 +++- TTS/tts/models/tacotron.py | 2 ++ TTS/tts/models/tacotron2.py | 2 ++ TTS/tts/utils/data.py | 15 ++++++++++++--- tests/data_tests/test_loader.py | 2 +- 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/TTS/tts/layers/losses.py b/TTS/tts/layers/losses.py index 86d34c30..07b58974 100644 --- a/TTS/tts/layers/losses.py +++ b/TTS/tts/layers/losses.py @@ -246,9 +246,9 @@ class Huber(nn.Module): class TacotronLoss(torch.nn.Module): """Collection of Tacotron set-up based on provided config.""" - def __init__(self, c, stopnet_pos_weight=10, ga_sigma=0.4): + def __init__(self, c, ga_sigma=0.4): super().__init__() - self.stopnet_pos_weight = stopnet_pos_weight + self.stopnet_pos_weight = c.stopnet_pos_weight self.ga_alpha = c.ga_alpha self.decoder_diff_spec_alpha = c.decoder_diff_spec_alpha self.postnet_diff_spec_alpha = c.postnet_diff_spec_alpha @@ -274,7 +274,7 @@ class TacotronLoss(torch.nn.Module): self.criterion_ssim = SSIMLoss() # stopnet loss # pylint: disable=not-callable - self.criterion_st = BCELossMasked(pos_weight=torch.tensor(stopnet_pos_weight)) if c.stopnet else None + self.criterion_st = BCELossMasked(pos_weight=torch.tensor(self.stopnet_pos_weight)) if c.stopnet else None def forward( self, @@ -284,6 +284,7 @@ class TacotronLoss(torch.nn.Module): linear_input, stopnet_output, stopnet_target, + stop_target_length, output_lens, decoder_b_output, alignments, @@ -315,12 +316,12 @@ class TacotronLoss(torch.nn.Module): return_dict["decoder_loss"] = decoder_loss return_dict["postnet_loss"] = postnet_loss - # stopnet loss stop_loss = ( - self.criterion_st(stopnet_output, stopnet_target, output_lens) if self.config.stopnet else torch.zeros(1) + self.criterion_st(stopnet_output, stopnet_target, stop_target_length) + if self.config.stopnet + else torch.zeros(1) ) - if not self.config.separate_stopnet and self.config.stopnet: - loss += stop_loss + loss += stop_loss return_dict["stopnet_loss"] = stop_loss # backward decoder loss (if enabled) diff --git a/TTS/tts/models/base_tts.py b/TTS/tts/models/base_tts.py index 561b76fb..b36ed106 100644 --- a/TTS/tts/models/base_tts.py +++ b/TTS/tts/models/base_tts.py @@ -119,9 +119,10 @@ class BaseTTS(BaseModel): ), f" [!] total duration {dur.sum()} vs spectrogram length {mel_lengths[idx]}" durations[idx, : text_lengths[idx]] = dur - # set stop targets view, we predict a single stop token per iteration. + # set stop targets wrt reduction factor stop_targets = stop_targets.view(text_input.shape[0], stop_targets.size(1) // self.config.r, -1) stop_targets = (stop_targets.sum(2) > 0.0).unsqueeze(2).float().squeeze(2) + stop_target_lengths = torch.divide(mel_lengths, self.config.r).ceil_() return { "text_input": text_input, @@ -131,6 +132,7 @@ class BaseTTS(BaseModel): "mel_lengths": mel_lengths, "linear_input": linear_input, "stop_targets": stop_targets, + "stop_target_lengths": stop_target_lengths, "attn_mask": attn_mask, "durations": durations, "speaker_ids": speaker_ids, diff --git a/TTS/tts/models/tacotron.py b/TTS/tts/models/tacotron.py index 95b4a358..7949ddf9 100644 --- a/TTS/tts/models/tacotron.py +++ b/TTS/tts/models/tacotron.py @@ -219,6 +219,7 @@ class Tacotron(BaseTacotron): mel_lengths = batch["mel_lengths"] linear_input = batch["linear_input"] stop_targets = batch["stop_targets"] + stop_target_lengths = batch["stop_target_lengths"] speaker_ids = batch["speaker_ids"] d_vectors = batch["d_vectors"] @@ -250,6 +251,7 @@ class Tacotron(BaseTacotron): linear_input, outputs["stop_tokens"], stop_targets, + stop_target_lengths, mel_lengths, outputs["decoder_outputs_backward"], outputs["alignments"], diff --git a/TTS/tts/models/tacotron2.py b/TTS/tts/models/tacotron2.py index eaca3ff8..19619662 100644 --- a/TTS/tts/models/tacotron2.py +++ b/TTS/tts/models/tacotron2.py @@ -224,6 +224,7 @@ class Tacotron2(BaseTacotron): mel_lengths = batch["mel_lengths"] linear_input = batch["linear_input"] stop_targets = batch["stop_targets"] + stop_target_lengths = batch["stop_target_lengths"] speaker_ids = batch["speaker_ids"] d_vectors = batch["d_vectors"] @@ -255,6 +256,7 @@ class Tacotron2(BaseTacotron): linear_input, outputs["stop_tokens"], stop_targets, + stop_target_lengths, mel_lengths, outputs["decoder_outputs_backward"], outputs["alignments"], diff --git a/TTS/tts/utils/data.py b/TTS/tts/utils/data.py index 3ff52195..887f4376 100644 --- a/TTS/tts/utils/data.py +++ b/TTS/tts/utils/data.py @@ -27,10 +27,19 @@ def prepare_tensor(inputs, out_steps): return np.stack([_pad_tensor(x, pad_len) for x in inputs]) -def _pad_stop_target(x, length): - _pad = 0.0 +def _pad_stop_target(x: np.ndarray, length: int, pad_val=1) -> np.ndarray: + """Pad stop target array. + + Args: + x (np.ndarray): Stop target array. + length (int): Length after padding. + pad_val (int, optional): Padding value. Defaults to 1. + + Returns: + np.ndarray: Padded stop target array. + """ assert x.ndim == 1 - return np.pad(x, (0, length - x.shape[0]), mode="constant", constant_values=_pad) + return np.pad(x, (0, length - x.shape[0]), mode="constant", constant_values=pad_val) def prepare_stop_target(inputs, out_steps): diff --git a/tests/data_tests/test_loader.py b/tests/data_tests/test_loader.py index 9bc70ddd..3fd3eaef 100644 --- a/tests/data_tests/test_loader.py +++ b/tests/data_tests/test_loader.py @@ -207,7 +207,7 @@ class TestTTSDataset(unittest.TestCase): assert linear_input[1 - idx, -1].sum() == 0 assert mel_input[1 - idx, -1].sum() == 0 assert stop_target[1, mel_lengths[1] - 1] == 1 - assert stop_target[1, mel_lengths[1] :].sum() == 0 + assert stop_target[1, mel_lengths[1] :].sum() == stop_target.shape[1] - mel_lengths[1] assert len(mel_lengths.shape) == 1 # check batch zero-frame conditions (zero-frame disabled) From d435fd7981f096692c02ae33b106767cac1a6ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Sat, 24 Jul 2021 11:27:44 +0200 Subject: [PATCH 25/28] Update `max_decoder_steps` in tacotron recipes --- recipes/ljspeech/tacotron2-DCA/tacotron2-DCA.json | 2 +- recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/recipes/ljspeech/tacotron2-DCA/tacotron2-DCA.json b/recipes/ljspeech/tacotron2-DCA/tacotron2-DCA.json index c5b6fa52..73bb8ae3 100644 --- a/recipes/ljspeech/tacotron2-DCA/tacotron2-DCA.json +++ b/recipes/ljspeech/tacotron2-DCA/tacotron2-DCA.json @@ -50,7 +50,7 @@ "stopnet_pos_weight": 15.0, "run_eval": true, "test_delay_epochs": 10, - "max_decoder_steps": 50, + "max_decoder_steps": 1000, "noam_schedule": true, "grad_clip": 0.05, "epochs": 1000, diff --git a/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json b/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json index d787c138..339e65b8 100644 --- a/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json +++ b/recipes/ljspeech/tacotron2-DDC/tacotron2-DDC.json @@ -56,7 +56,7 @@ "run_eval": true, "test_delay_epochs": 10, "test_sentences_file": null, - "max_decoder_steps": 50, + "max_decoder_steps": 1000, "noam_schedule": true, "grad_clip": 0.05, "epochs": 1000, From 764f684e1b17d7f0475f7af11679d10972e5ba3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Mon, 26 Jul 2021 14:52:32 +0200 Subject: [PATCH 26/28] Fix `server.py` for multi-speaker models --- TTS/server/server.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TTS/server/server.py b/TTS/server/server.py index dc025b32..e90d93e6 100644 --- a/TTS/server/server.py +++ b/TTS/server/server.py @@ -103,7 +103,8 @@ synthesizer = Synthesizer( model_path, config_path, speakers_file_path, vocoder_path, vocoder_config_path, use_cuda=args.use_cuda ) -use_multi_speaker = synthesizer.speaker_manager is not None +use_multi_speaker = synthesizer.tts_model.speaker_manager is not None and synthesizer.tts_model.num_speakers > 1 +speaker_manager = synthesizer.tts_model.speaker_manager if hasattr(synthesizer.tts_model, "speaker_manager") else None # TODO: set this from SpeakerManager use_gst = synthesizer.tts_config.get("use_gst", False) app = Flask(__name__) @@ -134,7 +135,7 @@ def index(): "index.html", show_details=args.show_details, use_multi_speaker=use_multi_speaker, - speaker_ids=synthesizer.speaker_manager.speaker_ids if synthesizer.speaker_manager else None, + speaker_ids=speaker_manager.speaker_ids if speaker_manager is not None else None, use_gst=use_gst, ) From 4b7b88dd3d1c8377544648a1ce81d73e8d61a45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Mon, 26 Jul 2021 14:56:05 +0200 Subject: [PATCH 27/28] Add fullband-melgan DE vocoder --- TTS/.models.json | 5 +++++ TTS/bin/compute_embeddings.py | 13 +++++++------ TTS/bin/find_unique_chars.py | 8 ++++---- TTS/speaker_encoder/models/lstm.py | 6 +++--- TTS/speaker_encoder/models/resnet.py | 2 +- TTS/tts/datasets/formatters.py | 8 +++++--- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/TTS/.models.json b/TTS/.models.json index 73204db6..d46237b9 100644 --- a/TTS/.models.json +++ b/TTS/.models.json @@ -230,6 +230,11 @@ "github_rls_url": "https://github.com/coqui-ai/TTS/releases/download/v0.0.11/vocoder_models--de--thorsten--wavegrad.zip", "author": "@thorstenMueller", "commit": "unknown" + }, + "fullband-melgan":{ + "github_rls_url": "https://github.com/coqui-ai/TTS/releases/download/v0.1.3/vocoder_models--de--thorsten--fullband-melgan.zip", + "author": "@thorstenMueller", + "commit": "unknown" } } } diff --git a/TTS/bin/compute_embeddings.py b/TTS/bin/compute_embeddings.py index f485514a..8c4d275f 100644 --- a/TTS/bin/compute_embeddings.py +++ b/TTS/bin/compute_embeddings.py @@ -1,21 +1,20 @@ - import argparse import os +from argparse import RawTextHelpFormatter from tqdm import tqdm -from argparse import RawTextHelpFormatter from TTS.config import load_config from TTS.tts.datasets import load_meta_data from TTS.tts.utils.speakers import SpeakerManager parser = argparse.ArgumentParser( description="""Compute embedding vectors for each wav file in a dataset.\n\n""" - """ + """ Example runs: python TTS/bin/compute_embeddings.py speaker_encoder_model.pth.tar speaker_encoder_config.json dataset_config.json embeddings_output_path/ """, - formatter_class=RawTextHelpFormatter, + formatter_class=RawTextHelpFormatter, ) parser.add_argument("model_path", type=str, help="Path to model checkpoint file.") parser.add_argument( @@ -40,7 +39,9 @@ c_dataset = load_config(args.config_dataset_path) meta_data_train, meta_data_eval = load_meta_data(c_dataset.datasets, eval_split=args.eval) wav_files = meta_data_train + meta_data_eval -speaker_manager = SpeakerManager(encoder_model_path=args.model_path, encoder_config_path=args.config_path, use_cuda=args.use_cuda) +speaker_manager = SpeakerManager( + encoder_model_path=args.model_path, encoder_config_path=args.config_path, use_cuda=args.use_cuda +) # compute speaker embeddings speaker_mapping = {} @@ -62,7 +63,7 @@ for idx, wav_file in enumerate(tqdm(wav_files)): if speaker_mapping: # save speaker_mapping if target dataset is defined - if '.json' not in args.output_path: + if ".json" not in args.output_path: mapping_file_path = os.path.join(args.output_path, "speakers.json") else: mapping_file_path = args.output_path diff --git a/TTS/bin/find_unique_chars.py b/TTS/bin/find_unique_chars.py index c7c25d80..16768e43 100644 --- a/TTS/bin/find_unique_chars.py +++ b/TTS/bin/find_unique_chars.py @@ -1,8 +1,9 @@ """Find all the unique characters in a dataset""" import argparse from argparse import RawTextHelpFormatter -from TTS.tts.datasets import load_meta_data + from TTS.config import load_config +from TTS.tts.datasets import load_meta_data def main(): @@ -16,9 +17,7 @@ def main(): """, formatter_class=RawTextHelpFormatter, ) - parser.add_argument( - "--config_path", type=str, help="Path to dataset config file.", required=True - ) + parser.add_argument("--config_path", type=str, help="Path to dataset config file.", required=True) args = parser.parse_args() c = load_config(args.config_path) @@ -38,5 +37,6 @@ def main(): print(f" > Unique lower characters: {''.join(sorted(lower_chars))}") print(f" > Unique all forced to lower characters: {''.join(sorted(chars_force_lower))}") + if __name__ == "__main__": main() diff --git a/TTS/speaker_encoder/models/lstm.py b/TTS/speaker_encoder/models/lstm.py index 21439d6b..7e39087a 100644 --- a/TTS/speaker_encoder/models/lstm.py +++ b/TTS/speaker_encoder/models/lstm.py @@ -1,5 +1,5 @@ -import torch import numpy as np +import torch from torch import nn @@ -81,12 +81,12 @@ class LSTMSpeakerEncoder(nn.Module): if max_len < num_frames: num_frames = max_len - offsets = np.linspace(0, max_len-num_frames, num=num_eval) + offsets = np.linspace(0, max_len - num_frames, num=num_eval) frames_batch = [] for offset in offsets: offset = int(offset) - end_offset = int(offset+num_frames) + end_offset = int(offset + num_frames) frames = x[:, offset:end_offset] frames_batch.append(frames) diff --git a/TTS/speaker_encoder/models/resnet.py b/TTS/speaker_encoder/models/resnet.py index 29f3ae61..f52bb4d5 100644 --- a/TTS/speaker_encoder/models/resnet.py +++ b/TTS/speaker_encoder/models/resnet.py @@ -207,4 +207,4 @@ class ResNetSpeakerEncoder(nn.Module): self.cuda() if eval: self.eval() - assert not self.training \ No newline at end of file + assert not self.training diff --git a/TTS/tts/datasets/formatters.py b/TTS/tts/datasets/formatters.py index ef5299cb..c057c51e 100644 --- a/TTS/tts/datasets/formatters.py +++ b/TTS/tts/datasets/formatters.py @@ -291,18 +291,20 @@ def vctk_slim(root_path, meta_files=None, wavs_path="wav48"): return items + def mls(root_path, meta_files=None): """http://www.openslr.org/94/""" items = [] with open(os.path.join(root_path, meta_files), "r") as meta: for line in meta: - file, text = line.split('\t') + file, text = line.split("\t") text = text[:-1] - speaker, book, *_ = file.split('_') - wav_file = os.path.join(root_path, os.path.dirname(meta_files), 'audio', speaker, book, file + ".wav") + speaker, book, *_ = file.split("_") + wav_file = os.path.join(root_path, os.path.dirname(meta_files), "audio", speaker, book, file + ".wav") items.append([text, wav_file, "MLS_" + speaker]) return items + # ======================================== VOX CELEB =========================================== def voxceleb2(root_path, meta_file=None): """ From febd6105b5b4214f340794a45d73912209cd2cf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eren=20G=C3=B6lge?= Date: Mon, 26 Jul 2021 16:08:52 +0200 Subject: [PATCH 28/28] Update default vocoder for de-thorsten --- TTS/.models.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TTS/.models.json b/TTS/.models.json index d46237b9..3c2ad8dc 100644 --- a/TTS/.models.json +++ b/TTS/.models.json @@ -132,7 +132,7 @@ "thorsten":{ "tacotron2-DCA":{ "github_rls_url": "https://github.com/coqui-ai/TTS/releases/download/v0.0.11/tts_models--de--thorsten--tacotron2-DCA.zip", - "default_vocoder": "vocoder_models/de/thorsten/wavegrad", + "default_vocoder": "vocoder_models/de/thorsten/fullband-melgan", "author": "@thorstenMueller", "commit": "unknown" }