2018-01-22 09:48:59 +00:00
|
|
|
import os
|
|
|
|
import numpy as np
|
|
|
|
import collections
|
2018-02-02 15:18:16 +00:00
|
|
|
import torch
|
2018-09-20 09:08:12 +00:00
|
|
|
import random
|
2018-01-22 09:48:59 +00:00
|
|
|
from torch.utils.data import Dataset
|
|
|
|
|
2019-07-19 13:17:35 +00:00
|
|
|
from utils.text import text_to_sequence, phoneme_to_sequence, pad_with_eos_bos
|
2019-07-19 06:46:23 +00:00
|
|
|
from utils.data import prepare_data, prepare_tensor, prepare_stop_target
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
|
2018-07-25 17:14:07 +00:00
|
|
|
class MyDataset(Dataset):
|
2018-08-02 14:34:17 +00:00
|
|
|
def __init__(self,
|
|
|
|
outputs_per_step,
|
|
|
|
text_cleaner,
|
|
|
|
ap,
|
2019-07-15 13:38:58 +00:00
|
|
|
meta_data,
|
2018-09-20 09:08:12 +00:00
|
|
|
batch_group_size=0,
|
2018-12-17 15:32:45 +00:00
|
|
|
min_seq_len=0,
|
|
|
|
max_seq_len=float("inf"),
|
2019-01-16 12:07:03 +00:00
|
|
|
use_phonemes=True,
|
|
|
|
phoneme_cache_path=None,
|
2019-02-27 08:50:52 +00:00
|
|
|
phoneme_language="en-us",
|
2019-04-12 14:12:15 +00:00
|
|
|
enable_eos_bos=False,
|
2019-02-27 08:50:52 +00:00
|
|
|
verbose=False):
|
2018-12-17 15:32:45 +00:00
|
|
|
"""
|
|
|
|
Args:
|
|
|
|
outputs_per_step (int): number of time frames predicted per step.
|
|
|
|
text_cleaner (str): text cleaner used for the dataset.
|
|
|
|
ap (TTS.utils.AudioProcessor): audio processor object.
|
2019-07-15 13:38:58 +00:00
|
|
|
meta_data (list): list of dataset instances.
|
2019-07-19 06:46:23 +00:00
|
|
|
batch_group_size (int): (0) range of batch randomization after sorting
|
|
|
|
sequences by length.
|
|
|
|
min_seq_len (int): (0) minimum sequence length to be processed
|
2018-12-17 15:32:45 +00:00
|
|
|
by the loader.
|
|
|
|
max_seq_len (int): (float("inf")) maximum sequence length.
|
2019-01-16 12:07:03 +00:00
|
|
|
use_phonemes (bool): (true) if true, text converted to phonemes.
|
2019-07-19 06:46:23 +00:00
|
|
|
phoneme_cache_path (str): path to cache phoneme features.
|
|
|
|
phoneme_language (str): one the languages from
|
2019-01-16 12:07:03 +00:00
|
|
|
https://github.com/bootphon/phonemizer#languages
|
2019-04-12 14:12:15 +00:00
|
|
|
enable_eos_bos (bool): enable end of sentence and beginning of sentences characters.
|
2019-02-27 08:50:52 +00:00
|
|
|
verbose (bool): print diagnostic information.
|
2018-12-17 15:32:45 +00:00
|
|
|
"""
|
2018-09-20 09:08:12 +00:00
|
|
|
self.batch_group_size = batch_group_size
|
2019-07-15 13:38:58 +00:00
|
|
|
self.items = meta_data
|
2018-01-22 09:48:59 +00:00
|
|
|
self.outputs_per_step = outputs_per_step
|
2018-07-20 14:04:29 +00:00
|
|
|
self.sample_rate = ap.sample_rate
|
2018-01-24 16:04:25 +00:00
|
|
|
self.cleaners = text_cleaner
|
2018-03-09 17:51:32 +00:00
|
|
|
self.min_seq_len = min_seq_len
|
2018-12-17 15:32:45 +00:00
|
|
|
self.max_seq_len = max_seq_len
|
2018-07-20 14:04:29 +00:00
|
|
|
self.ap = ap
|
2019-01-16 12:07:03 +00:00
|
|
|
self.use_phonemes = use_phonemes
|
2019-01-15 14:51:13 +00:00
|
|
|
self.phoneme_cache_path = phoneme_cache_path
|
2019-01-16 12:07:03 +00:00
|
|
|
self.phoneme_language = phoneme_language
|
2019-04-12 14:12:15 +00:00
|
|
|
self.enable_eos_bos = enable_eos_bos
|
2019-02-27 08:50:52 +00:00
|
|
|
self.verbose = verbose
|
2019-02-25 17:34:06 +00:00
|
|
|
if use_phonemes and not os.path.isdir(phoneme_cache_path):
|
2019-02-28 16:49:42 +00:00
|
|
|
os.makedirs(phoneme_cache_path, exist_ok=True)
|
2019-02-27 08:50:52 +00:00
|
|
|
if self.verbose:
|
|
|
|
print("\n > DataLoader initialization")
|
|
|
|
print(" | > Use phonemes: {}".format(self.use_phonemes))
|
|
|
|
if use_phonemes:
|
|
|
|
print(" | > phoneme language: {}".format(phoneme_language))
|
|
|
|
print(" | > Number of instances : {}".format(len(self.items)))
|
2018-11-02 15:13:51 +00:00
|
|
|
self.sort_items()
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
def load_wav(self, filename):
|
2019-04-28 12:04:49 +00:00
|
|
|
audio = self.ap.load_wav(filename)
|
|
|
|
return audio
|
2018-01-22 09:48:59 +00:00
|
|
|
|
2019-07-19 06:46:23 +00:00
|
|
|
@staticmethod
|
|
|
|
def load_np(filename):
|
2018-12-17 15:32:45 +00:00
|
|
|
data = np.load(filename).astype('float32')
|
|
|
|
return data
|
|
|
|
|
2019-07-19 13:17:35 +00:00
|
|
|
def _generate_and_cache_phoneme_sequence(self, text, cache_path):
|
|
|
|
"""generate a phoneme sequence from text.
|
|
|
|
|
|
|
|
since the usage is for subsequent caching, we never add bos and
|
|
|
|
eos chars here. Instead we add those dynamically later; based on the
|
|
|
|
config option."""
|
|
|
|
phonemes = phoneme_to_sequence(text, [self.cleaners],
|
|
|
|
language=self.phoneme_language,
|
|
|
|
enable_eos_bos=False)
|
|
|
|
phonemes = np.asarray(phonemes, dtype=np.int32)
|
|
|
|
np.save(cache_path, phonemes)
|
|
|
|
return phonemes
|
|
|
|
|
|
|
|
def _load_or_generate_phoneme_sequence(self, wav_file, text):
|
2019-01-16 12:07:03 +00:00
|
|
|
file_name = os.path.basename(wav_file).split('.')[0]
|
2019-07-19 13:17:35 +00:00
|
|
|
cache_path = os.path.join(self.phoneme_cache_path,
|
|
|
|
file_name + '_phoneme.npy')
|
|
|
|
try:
|
|
|
|
phonemes = np.load(cache_path)
|
|
|
|
except FileNotFoundError:
|
|
|
|
phonemes = self._generate_and_cache_phoneme_sequence(text,
|
|
|
|
cache_path)
|
|
|
|
except (ValueError, IOError):
|
|
|
|
print(" > ERROR: failed loading phonemes for {}. "
|
|
|
|
"Recomputing.".format(wav_file))
|
|
|
|
phonemes = self._generate_and_cache_phoneme_sequence(text,
|
2019-07-19 14:26:36 +00:00
|
|
|
cache_path)
|
2019-07-19 13:17:35 +00:00
|
|
|
if self.enable_eos_bos:
|
|
|
|
phonemes = pad_with_eos_bos(phonemes)
|
2019-07-26 11:40:58 +00:00
|
|
|
phonemes = np.asarray(phonemes, dtype=np.int32)
|
2019-07-19 13:17:35 +00:00
|
|
|
|
|
|
|
return phonemes
|
2019-01-16 12:07:03 +00:00
|
|
|
|
2018-12-17 15:32:45 +00:00
|
|
|
def load_data(self, idx):
|
2019-06-26 10:59:14 +00:00
|
|
|
text, wav_file, speaker_name = self.items[idx]
|
2019-04-29 09:07:04 +00:00
|
|
|
wav = np.asarray(self.load_wav(wav_file), dtype=np.float32)
|
2019-03-06 12:10:05 +00:00
|
|
|
|
2019-01-16 12:07:03 +00:00
|
|
|
if self.use_phonemes:
|
2019-07-19 13:17:35 +00:00
|
|
|
text = self._load_or_generate_phoneme_sequence(wav_file, text)
|
2019-03-06 12:10:05 +00:00
|
|
|
else:
|
2019-01-16 12:07:03 +00:00
|
|
|
text = np.asarray(
|
|
|
|
text_to_sequence(text, [self.cleaners]), dtype=np.int32)
|
2019-04-18 14:25:04 +00:00
|
|
|
|
|
|
|
assert text.size > 0, self.items[idx][1]
|
|
|
|
assert wav.size > 0, self.items[idx][1]
|
|
|
|
|
2019-03-06 12:10:05 +00:00
|
|
|
sample = {
|
|
|
|
'text': text,
|
|
|
|
'wav': wav,
|
2019-06-26 10:59:14 +00:00
|
|
|
'item_idx': self.items[idx][1],
|
|
|
|
'speaker_name': speaker_name
|
2019-03-06 12:10:05 +00:00
|
|
|
}
|
2018-12-17 15:32:45 +00:00
|
|
|
return sample
|
|
|
|
|
2018-11-02 15:13:51 +00:00
|
|
|
def sort_items(self):
|
2018-12-17 15:32:45 +00:00
|
|
|
r"""Sort instances based on text length in ascending order"""
|
2018-11-02 15:13:51 +00:00
|
|
|
lengths = np.array([len(ins[0]) for ins in self.items])
|
2019-07-19 06:46:23 +00:00
|
|
|
|
2018-03-07 14:58:51 +00:00
|
|
|
idxs = np.argsort(lengths)
|
2018-11-02 15:13:51 +00:00
|
|
|
new_items = []
|
2018-03-09 17:46:47 +00:00
|
|
|
ignored = []
|
2018-03-07 14:58:51 +00:00
|
|
|
for i, idx in enumerate(idxs):
|
2018-03-09 17:46:47 +00:00
|
|
|
length = lengths[idx]
|
2018-12-17 15:32:45 +00:00
|
|
|
if length < self.min_seq_len or length > self.max_seq_len:
|
2018-03-09 17:46:47 +00:00
|
|
|
ignored.append(idx)
|
2018-03-09 17:49:18 +00:00
|
|
|
else:
|
2018-11-02 15:13:51 +00:00
|
|
|
new_items.append(self.items[idx])
|
2018-09-20 09:08:12 +00:00
|
|
|
# shuffle batch groups
|
|
|
|
if self.batch_group_size > 0:
|
2018-11-02 15:13:51 +00:00
|
|
|
for i in range(len(new_items) // self.batch_group_size):
|
2018-09-20 09:08:12 +00:00
|
|
|
offset = i * self.batch_group_size
|
|
|
|
end_offset = offset + self.batch_group_size
|
2019-03-06 12:10:05 +00:00
|
|
|
temp_items = new_items[offset:end_offset]
|
2018-11-02 15:13:51 +00:00
|
|
|
random.shuffle(temp_items)
|
2019-03-06 12:10:05 +00:00
|
|
|
new_items[offset:end_offset] = temp_items
|
2018-11-02 15:13:51 +00:00
|
|
|
self.items = new_items
|
2018-04-03 10:24:57 +00:00
|
|
|
|
2019-02-27 08:50:52 +00:00
|
|
|
if self.verbose:
|
|
|
|
print(" | > Max length sequence: {}".format(np.max(lengths)))
|
|
|
|
print(" | > Min length sequence: {}".format(np.min(lengths)))
|
|
|
|
print(" | > Avg length sequence: {}".format(np.mean(lengths)))
|
2019-07-19 06:46:23 +00:00
|
|
|
print(" | > Num. instances discarded by max-min (max={}, min={}) seq limits: {}".format(
|
|
|
|
self.max_seq_len, self.min_seq_len, len(ignored)))
|
2019-02-27 08:50:52 +00:00
|
|
|
print(" | > Batch group size: {}.".format(self.batch_group_size))
|
2019-07-19 06:46:23 +00:00
|
|
|
|
2018-01-22 09:48:59 +00:00
|
|
|
def __len__(self):
|
2018-11-02 15:13:51 +00:00
|
|
|
return len(self.items)
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
def __getitem__(self, idx):
|
2018-12-17 15:32:45 +00:00
|
|
|
return self.load_data(idx)
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
def collate_fn(self, batch):
|
2018-03-07 14:58:51 +00:00
|
|
|
r"""
|
|
|
|
Perform preprocessing and create a final data batch:
|
|
|
|
1. PAD sequences with the longest sequence in the batch
|
|
|
|
2. Convert Audio signal to Spectrograms.
|
|
|
|
3. PAD sequences that can be divided by r.
|
|
|
|
4. Convert Numpy to Torch tensors.
|
|
|
|
"""
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
# Puts each data field into a tensor with outer dimension batch size
|
|
|
|
if isinstance(batch[0], collections.Mapping):
|
|
|
|
|
2019-03-06 12:10:05 +00:00
|
|
|
text_lenghts = np.array([len(d["text"]) for d in batch])
|
|
|
|
text_lenghts, ids_sorted_decreasing = torch.sort(
|
|
|
|
torch.LongTensor(text_lenghts), dim=0, descending=True)
|
2018-02-08 13:57:43 +00:00
|
|
|
|
2019-03-06 12:10:05 +00:00
|
|
|
wav = [batch[idx]['wav'] for idx in ids_sorted_decreasing]
|
|
|
|
item_idxs = [
|
|
|
|
batch[idx]['item_idx'] for idx in ids_sorted_decreasing
|
|
|
|
]
|
|
|
|
text = [batch[idx]['text'] for idx in ids_sorted_decreasing]
|
2019-06-26 10:59:14 +00:00
|
|
|
speaker_name = [batch[idx]['speaker_name']
|
2019-07-19 06:46:23 +00:00
|
|
|
for idx in ids_sorted_decreasing]
|
2018-01-22 09:48:59 +00:00
|
|
|
|
2019-04-29 09:26:01 +00:00
|
|
|
mel = [self.ap.melspectrogram(w).astype('float32') for w in wav]
|
|
|
|
linear = [self.ap.spectrogram(w).astype('float32') for w in wav]
|
|
|
|
|
2018-04-03 10:24:57 +00:00
|
|
|
mel_lengths = [m.shape[1] + 1 for m in mel] # +1 for zero-frame
|
|
|
|
|
2018-03-22 21:06:33 +00:00
|
|
|
# compute 'stop token' targets
|
2018-08-02 14:34:17 +00:00
|
|
|
stop_targets = [
|
|
|
|
np.array([0.] * (mel_len - 1)) for mel_len in mel_lengths
|
|
|
|
]
|
2018-04-03 10:24:57 +00:00
|
|
|
|
2018-03-22 21:06:33 +00:00
|
|
|
# PAD stop targets
|
2018-08-02 14:34:17 +00:00
|
|
|
stop_targets = prepare_stop_target(stop_targets,
|
|
|
|
self.outputs_per_step)
|
2018-03-22 19:34:16 +00:00
|
|
|
|
2018-01-22 09:48:59 +00:00
|
|
|
# PAD sequences with largest length of the batch
|
|
|
|
text = prepare_data(text).astype(np.int32)
|
|
|
|
wav = prepare_data(wav)
|
|
|
|
|
2018-03-26 17:43:36 +00:00
|
|
|
# PAD features with largest length + a zero frame
|
|
|
|
linear = prepare_tensor(linear, self.outputs_per_step)
|
|
|
|
mel = prepare_tensor(mel, self.outputs_per_step)
|
2018-02-08 13:57:43 +00:00
|
|
|
assert mel.shape[2] == linear.shape[2]
|
2018-04-03 10:24:57 +00:00
|
|
|
timesteps = mel.shape[2]
|
2018-01-22 09:48:59 +00:00
|
|
|
|
2018-03-22 20:46:52 +00:00
|
|
|
# B x T x D
|
2018-02-08 13:57:43 +00:00
|
|
|
linear = linear.transpose(0, 2, 1)
|
2018-01-22 14:58:12 +00:00
|
|
|
mel = mel.transpose(0, 2, 1)
|
|
|
|
|
2018-02-09 13:39:58 +00:00
|
|
|
# convert things to pytorch
|
2018-02-04 16:25:00 +00:00
|
|
|
text_lenghts = torch.LongTensor(text_lenghts)
|
|
|
|
text = torch.LongTensor(text)
|
2018-11-20 13:56:19 +00:00
|
|
|
linear = torch.FloatTensor(linear).contiguous()
|
|
|
|
mel = torch.FloatTensor(mel).contiguous()
|
2018-03-22 20:46:52 +00:00
|
|
|
mel_lengths = torch.LongTensor(mel_lengths)
|
2018-03-22 19:34:16 +00:00
|
|
|
stop_targets = torch.FloatTensor(stop_targets)
|
2018-04-03 10:24:57 +00:00
|
|
|
|
2019-06-26 10:59:14 +00:00
|
|
|
return text, text_lenghts, speaker_name, linear, mel, mel_lengths, \
|
|
|
|
stop_targets, item_idxs
|
2018-01-22 09:48:59 +00:00
|
|
|
|
|
|
|
raise TypeError(("batch must contain tensors, numbers, dicts or lists;\
|
2018-08-02 14:34:17 +00:00
|
|
|
found {}".format(type(batch[0]))))
|