mirror of
https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI.git
synced 2026-08-29 10:09:32 +02:00
Import RVC 20260716 Nvidia 50x0 v2bb
This commit is contained in:
517
train/data_utils.py
Normal file
517
train/data_utils.py
Normal file
@@ -0,0 +1,517 @@
|
||||
import os
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.utils.data
|
||||
|
||||
from train.mel_processing import spectrogram_torch
|
||||
from train.utils import load_filepaths_and_text, load_wav_to_torch
|
||||
|
||||
|
||||
class TextAudioLoaderMultiNSFsid(torch.utils.data.Dataset):
|
||||
"""
|
||||
1) loads audio, text pairs
|
||||
2) normalizes text and converts them to sequences of integers
|
||||
3) computes spectrograms from audio files.
|
||||
"""
|
||||
|
||||
def __init__(self, audiopaths_and_text, hparams):
|
||||
self.audiopaths_and_text = load_filepaths_and_text(audiopaths_and_text)
|
||||
self.max_wav_value = hparams.max_wav_value
|
||||
self.sampling_rate = hparams.sampling_rate
|
||||
self.filter_length = hparams.filter_length
|
||||
self.hop_length = hparams.hop_length
|
||||
self.win_length = hparams.win_length
|
||||
self.sampling_rate = hparams.sampling_rate
|
||||
self.min_text_len = getattr(hparams, "min_text_len", 1)
|
||||
self.max_text_len = getattr(hparams, "max_text_len", 5000)
|
||||
self._filter()
|
||||
|
||||
def _filter(self):
|
||||
"""
|
||||
Filter text & store spec lengths
|
||||
"""
|
||||
# Store spectrogram lengths for Bucketing
|
||||
# wav_length ~= file_size / (wav_channels * Bytes per dim) = file_size / (1 * 2)
|
||||
# spec_length = wav_length // hop_length
|
||||
audiopaths_and_text_new = []
|
||||
lengths = []
|
||||
for audiopath, text, pitch, pitchf, dv in self.audiopaths_and_text:
|
||||
if self.min_text_len <= len(text) and len(text) <= self.max_text_len:
|
||||
audiopaths_and_text_new.append([audiopath, text, pitch, pitchf, dv])
|
||||
lengths.append(os.path.getsize(audiopath) // (3 * self.hop_length))
|
||||
self.audiopaths_and_text = audiopaths_and_text_new
|
||||
self.lengths = lengths
|
||||
|
||||
def get_sid(self, sid):
|
||||
sid = torch.LongTensor([int(sid)])
|
||||
return sid
|
||||
|
||||
def get_audio_text_pair(self, audiopath_and_text):
|
||||
# separate filename and text
|
||||
file = audiopath_and_text[0]
|
||||
phone = audiopath_and_text[1]
|
||||
pitch = audiopath_and_text[2]
|
||||
pitchf = audiopath_and_text[3]
|
||||
dv = audiopath_and_text[4]
|
||||
|
||||
phone, pitch, pitchf = self.get_labels(phone, pitch, pitchf)
|
||||
spec, wav = self.get_audio(file)
|
||||
dv = self.get_sid(dv)
|
||||
|
||||
len_phone = phone.size()[0]
|
||||
len_spec = spec.size()[-1]
|
||||
# print(123,phone.shape,pitch.shape,spec.shape)
|
||||
if len_phone != len_spec:
|
||||
len_min = min(len_phone, len_spec)
|
||||
# amor
|
||||
len_wav = len_min * self.hop_length
|
||||
|
||||
spec = spec[:, :len_min]
|
||||
wav = wav[:, :len_wav]
|
||||
|
||||
phone = phone[:len_min, :]
|
||||
pitch = pitch[:len_min]
|
||||
pitchf = pitchf[:len_min]
|
||||
|
||||
return (spec, wav, phone, pitch, pitchf, dv)
|
||||
|
||||
def get_labels(self, phone, pitch, pitchf):
|
||||
phone = np.load(phone)
|
||||
phone = np.repeat(phone, 2, axis=0)
|
||||
pitch = np.load(pitch)
|
||||
pitchf = np.load(pitchf)
|
||||
n_num = min(phone.shape[0], 900) # DistributedBucketSampler
|
||||
# print(234,phone.shape,pitch.shape)
|
||||
phone = phone[:n_num, :]
|
||||
pitch = pitch[:n_num]
|
||||
pitchf = pitchf[:n_num]
|
||||
phone = torch.FloatTensor(phone)
|
||||
pitch = torch.LongTensor(pitch)
|
||||
pitchf = torch.FloatTensor(pitchf)
|
||||
return phone, pitch, pitchf
|
||||
|
||||
def get_audio(self, filename):
|
||||
audio, sampling_rate = load_wav_to_torch(filename)
|
||||
if sampling_rate != self.sampling_rate:
|
||||
raise ValueError(
|
||||
"{} SR doesn't match target {} SR".format(
|
||||
sampling_rate, self.sampling_rate
|
||||
)
|
||||
)
|
||||
audio_norm = audio
|
||||
# audio_norm = audio / self.max_wav_value
|
||||
# audio_norm = audio / np.abs(audio).max()
|
||||
|
||||
audio_norm = audio_norm.unsqueeze(0)
|
||||
spec_filename = filename.replace(".wav", ".spec.pt")
|
||||
if os.path.exists(spec_filename):
|
||||
try:
|
||||
spec = torch.load(spec_filename)
|
||||
except:
|
||||
logger.warning("%s %s", spec_filename, traceback.format_exc())
|
||||
spec = spectrogram_torch(
|
||||
audio_norm,
|
||||
self.filter_length,
|
||||
self.sampling_rate,
|
||||
self.hop_length,
|
||||
self.win_length,
|
||||
center=False,
|
||||
)
|
||||
spec = torch.squeeze(spec, 0)
|
||||
torch.save(spec, spec_filename, _use_new_zipfile_serialization=False)
|
||||
else:
|
||||
spec = spectrogram_torch(
|
||||
audio_norm,
|
||||
self.filter_length,
|
||||
self.sampling_rate,
|
||||
self.hop_length,
|
||||
self.win_length,
|
||||
center=False,
|
||||
)
|
||||
spec = torch.squeeze(spec, 0)
|
||||
torch.save(spec, spec_filename, _use_new_zipfile_serialization=False)
|
||||
return spec, audio_norm
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.get_audio_text_pair(self.audiopaths_and_text[index])
|
||||
|
||||
def __len__(self):
|
||||
return len(self.audiopaths_and_text)
|
||||
|
||||
|
||||
class TextAudioCollateMultiNSFsid:
|
||||
"""Zero-pads model inputs and targets"""
|
||||
|
||||
def __init__(self, return_ids=False):
|
||||
self.return_ids = return_ids
|
||||
|
||||
def __call__(self, batch):
|
||||
"""Collate's training batch from normalized text and aduio
|
||||
PARAMS
|
||||
------
|
||||
batch: [text_normalized, spec_normalized, wav_normalized]
|
||||
"""
|
||||
# Right zero-pad all one-hot text sequences to max input length
|
||||
_, ids_sorted_decreasing = torch.sort(
|
||||
torch.LongTensor([x[0].size(1) for x in batch]), dim=0, descending=True
|
||||
)
|
||||
|
||||
max_spec_len = max([x[0].size(1) for x in batch])
|
||||
max_wave_len = max([x[1].size(1) for x in batch])
|
||||
spec_lengths = torch.LongTensor(len(batch))
|
||||
wave_lengths = torch.LongTensor(len(batch))
|
||||
spec_padded = torch.FloatTensor(len(batch), batch[0][0].size(0), max_spec_len)
|
||||
wave_padded = torch.FloatTensor(len(batch), 1, max_wave_len)
|
||||
spec_padded.zero_()
|
||||
wave_padded.zero_()
|
||||
|
||||
max_phone_len = max([x[2].size(0) for x in batch])
|
||||
phone_lengths = torch.LongTensor(len(batch))
|
||||
phone_padded = torch.FloatTensor(
|
||||
len(batch), max_phone_len, batch[0][2].shape[1]
|
||||
) # (spec, wav, phone, pitch)
|
||||
pitch_padded = torch.LongTensor(len(batch), max_phone_len)
|
||||
pitchf_padded = torch.FloatTensor(len(batch), max_phone_len)
|
||||
phone_padded.zero_()
|
||||
pitch_padded.zero_()
|
||||
pitchf_padded.zero_()
|
||||
# dv = torch.FloatTensor(len(batch), 256)#gin=256
|
||||
sid = torch.LongTensor(len(batch))
|
||||
|
||||
for i in range(len(ids_sorted_decreasing)):
|
||||
row = batch[ids_sorted_decreasing[i]]
|
||||
|
||||
spec = row[0]
|
||||
spec_padded[i, :, : spec.size(1)] = spec
|
||||
spec_lengths[i] = spec.size(1)
|
||||
|
||||
wave = row[1]
|
||||
wave_padded[i, :, : wave.size(1)] = wave
|
||||
wave_lengths[i] = wave.size(1)
|
||||
|
||||
phone = row[2]
|
||||
phone_padded[i, : phone.size(0), :] = phone
|
||||
phone_lengths[i] = phone.size(0)
|
||||
|
||||
pitch = row[3]
|
||||
pitch_padded[i, : pitch.size(0)] = pitch
|
||||
pitchf = row[4]
|
||||
pitchf_padded[i, : pitchf.size(0)] = pitchf
|
||||
|
||||
# dv[i] = row[5]
|
||||
sid[i] = row[5]
|
||||
|
||||
return (
|
||||
phone_padded,
|
||||
phone_lengths,
|
||||
pitch_padded,
|
||||
pitchf_padded,
|
||||
spec_padded,
|
||||
spec_lengths,
|
||||
wave_padded,
|
||||
wave_lengths,
|
||||
# dv
|
||||
sid,
|
||||
)
|
||||
|
||||
|
||||
class TextAudioLoader(torch.utils.data.Dataset):
|
||||
"""
|
||||
1) loads audio, text pairs
|
||||
2) normalizes text and converts them to sequences of integers
|
||||
3) computes spectrograms from audio files.
|
||||
"""
|
||||
|
||||
def __init__(self, audiopaths_and_text, hparams):
|
||||
self.audiopaths_and_text = load_filepaths_and_text(audiopaths_and_text)
|
||||
self.max_wav_value = hparams.max_wav_value
|
||||
self.sampling_rate = hparams.sampling_rate
|
||||
self.filter_length = hparams.filter_length
|
||||
self.hop_length = hparams.hop_length
|
||||
self.win_length = hparams.win_length
|
||||
self.sampling_rate = hparams.sampling_rate
|
||||
self.min_text_len = getattr(hparams, "min_text_len", 1)
|
||||
self.max_text_len = getattr(hparams, "max_text_len", 5000)
|
||||
self._filter()
|
||||
|
||||
def _filter(self):
|
||||
"""
|
||||
Filter text & store spec lengths
|
||||
"""
|
||||
# Store spectrogram lengths for Bucketing
|
||||
# wav_length ~= file_size / (wav_channels * Bytes per dim) = file_size / (1 * 2)
|
||||
# spec_length = wav_length // hop_length
|
||||
audiopaths_and_text_new = []
|
||||
lengths = []
|
||||
for audiopath, text, dv in self.audiopaths_and_text:
|
||||
if self.min_text_len <= len(text) and len(text) <= self.max_text_len:
|
||||
audiopaths_and_text_new.append([audiopath, text, dv])
|
||||
lengths.append(os.path.getsize(audiopath) // (3 * self.hop_length))
|
||||
self.audiopaths_and_text = audiopaths_and_text_new
|
||||
self.lengths = lengths
|
||||
|
||||
def get_sid(self, sid):
|
||||
sid = torch.LongTensor([int(sid)])
|
||||
return sid
|
||||
|
||||
def get_audio_text_pair(self, audiopath_and_text):
|
||||
# separate filename and text
|
||||
file = audiopath_and_text[0]
|
||||
phone = audiopath_and_text[1]
|
||||
dv = audiopath_and_text[2]
|
||||
|
||||
phone = self.get_labels(phone)
|
||||
spec, wav = self.get_audio(file)
|
||||
dv = self.get_sid(dv)
|
||||
|
||||
len_phone = phone.size()[0]
|
||||
len_spec = spec.size()[-1]
|
||||
if len_phone != len_spec:
|
||||
len_min = min(len_phone, len_spec)
|
||||
len_wav = len_min * self.hop_length
|
||||
spec = spec[:, :len_min]
|
||||
wav = wav[:, :len_wav]
|
||||
phone = phone[:len_min, :]
|
||||
return (spec, wav, phone, dv)
|
||||
|
||||
def get_labels(self, phone):
|
||||
phone = np.load(phone)
|
||||
phone = np.repeat(phone, 2, axis=0)
|
||||
n_num = min(phone.shape[0], 900) # DistributedBucketSampler
|
||||
phone = phone[:n_num, :]
|
||||
phone = torch.FloatTensor(phone)
|
||||
return phone
|
||||
|
||||
def get_audio(self, filename):
|
||||
audio, sampling_rate = load_wav_to_torch(filename)
|
||||
if sampling_rate != self.sampling_rate:
|
||||
raise ValueError(
|
||||
"{} SR doesn't match target {} SR".format(
|
||||
sampling_rate, self.sampling_rate
|
||||
)
|
||||
)
|
||||
audio_norm = audio
|
||||
# audio_norm = audio / self.max_wav_value
|
||||
# audio_norm = audio / np.abs(audio).max()
|
||||
|
||||
audio_norm = audio_norm.unsqueeze(0)
|
||||
spec_filename = filename.replace(".wav", ".spec.pt")
|
||||
if os.path.exists(spec_filename):
|
||||
try:
|
||||
spec = torch.load(spec_filename)
|
||||
except:
|
||||
logger.warning("%s %s", spec_filename, traceback.format_exc())
|
||||
spec = spectrogram_torch(
|
||||
audio_norm,
|
||||
self.filter_length,
|
||||
self.sampling_rate,
|
||||
self.hop_length,
|
||||
self.win_length,
|
||||
center=False,
|
||||
)
|
||||
spec = torch.squeeze(spec, 0)
|
||||
torch.save(spec, spec_filename, _use_new_zipfile_serialization=False)
|
||||
else:
|
||||
spec = spectrogram_torch(
|
||||
audio_norm,
|
||||
self.filter_length,
|
||||
self.sampling_rate,
|
||||
self.hop_length,
|
||||
self.win_length,
|
||||
center=False,
|
||||
)
|
||||
spec = torch.squeeze(spec, 0)
|
||||
torch.save(spec, spec_filename, _use_new_zipfile_serialization=False)
|
||||
return spec, audio_norm
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.get_audio_text_pair(self.audiopaths_and_text[index])
|
||||
|
||||
def __len__(self):
|
||||
return len(self.audiopaths_and_text)
|
||||
|
||||
|
||||
class TextAudioCollate:
|
||||
"""Zero-pads model inputs and targets"""
|
||||
|
||||
def __init__(self, return_ids=False):
|
||||
self.return_ids = return_ids
|
||||
|
||||
def __call__(self, batch):
|
||||
"""Collate's training batch from normalized text and aduio
|
||||
PARAMS
|
||||
------
|
||||
batch: [text_normalized, spec_normalized, wav_normalized]
|
||||
"""
|
||||
# Right zero-pad all one-hot text sequences to max input length
|
||||
_, ids_sorted_decreasing = torch.sort(
|
||||
torch.LongTensor([x[0].size(1) for x in batch]), dim=0, descending=True
|
||||
)
|
||||
|
||||
max_spec_len = max([x[0].size(1) for x in batch])
|
||||
max_wave_len = max([x[1].size(1) for x in batch])
|
||||
spec_lengths = torch.LongTensor(len(batch))
|
||||
wave_lengths = torch.LongTensor(len(batch))
|
||||
spec_padded = torch.FloatTensor(len(batch), batch[0][0].size(0), max_spec_len)
|
||||
wave_padded = torch.FloatTensor(len(batch), 1, max_wave_len)
|
||||
spec_padded.zero_()
|
||||
wave_padded.zero_()
|
||||
|
||||
max_phone_len = max([x[2].size(0) for x in batch])
|
||||
phone_lengths = torch.LongTensor(len(batch))
|
||||
phone_padded = torch.FloatTensor(
|
||||
len(batch), max_phone_len, batch[0][2].shape[1]
|
||||
)
|
||||
phone_padded.zero_()
|
||||
sid = torch.LongTensor(len(batch))
|
||||
|
||||
for i in range(len(ids_sorted_decreasing)):
|
||||
row = batch[ids_sorted_decreasing[i]]
|
||||
|
||||
spec = row[0]
|
||||
spec_padded[i, :, : spec.size(1)] = spec
|
||||
spec_lengths[i] = spec.size(1)
|
||||
|
||||
wave = row[1]
|
||||
wave_padded[i, :, : wave.size(1)] = wave
|
||||
wave_lengths[i] = wave.size(1)
|
||||
|
||||
phone = row[2]
|
||||
phone_padded[i, : phone.size(0), :] = phone
|
||||
phone_lengths[i] = phone.size(0)
|
||||
|
||||
sid[i] = row[3]
|
||||
|
||||
return (
|
||||
phone_padded,
|
||||
phone_lengths,
|
||||
spec_padded,
|
||||
spec_lengths,
|
||||
wave_padded,
|
||||
wave_lengths,
|
||||
sid,
|
||||
)
|
||||
|
||||
|
||||
class DistributedBucketSampler(torch.utils.data.distributed.DistributedSampler):
|
||||
"""
|
||||
Maintain similar input lengths in a batch.
|
||||
Length groups are specified by boundaries.
|
||||
Ex) boundaries = [b1, b2, b3] -> any batch is included either {x | b1 < length(x) <=b2} or {x | b2 < length(x) <= b3}.
|
||||
|
||||
It removes samples which are not included in the boundaries.
|
||||
Ex) boundaries = [b1, b2, b3] -> any x s.t. length(x) <= b1 or length(x) > b3 are discarded.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dataset,
|
||||
batch_size,
|
||||
boundaries,
|
||||
num_replicas=None,
|
||||
rank=None,
|
||||
shuffle=True,
|
||||
):
|
||||
super().__init__(dataset, num_replicas=num_replicas, rank=rank, shuffle=shuffle)
|
||||
self.lengths = dataset.lengths
|
||||
self.batch_size = batch_size
|
||||
self.boundaries = boundaries
|
||||
|
||||
self.buckets, self.num_samples_per_bucket = self._create_buckets()
|
||||
self.total_size = sum(self.num_samples_per_bucket)
|
||||
self.num_samples = self.total_size // self.num_replicas
|
||||
|
||||
def _create_buckets(self):
|
||||
buckets = [[] for _ in range(len(self.boundaries) - 1)]
|
||||
for i in range(len(self.lengths)):
|
||||
length = self.lengths[i]
|
||||
idx_bucket = self._bisect(length)
|
||||
if idx_bucket != -1:
|
||||
buckets[idx_bucket].append(i)
|
||||
|
||||
for i in range(len(buckets) - 1, -1, -1): #
|
||||
if len(buckets[i]) == 0:
|
||||
buckets.pop(i)
|
||||
self.boundaries.pop(i + 1)
|
||||
|
||||
num_samples_per_bucket = []
|
||||
for i in range(len(buckets)):
|
||||
len_bucket = len(buckets[i])
|
||||
total_batch_size = self.num_replicas * self.batch_size
|
||||
rem = (
|
||||
total_batch_size - (len_bucket % total_batch_size)
|
||||
) % total_batch_size
|
||||
num_samples_per_bucket.append(len_bucket + rem)
|
||||
return buckets, num_samples_per_bucket
|
||||
|
||||
def __iter__(self):
|
||||
# deterministically shuffle based on epoch
|
||||
g = torch.Generator()
|
||||
g.manual_seed(self.epoch)
|
||||
|
||||
indices = []
|
||||
if self.shuffle:
|
||||
for bucket in self.buckets:
|
||||
indices.append(torch.randperm(len(bucket), generator=g).tolist())
|
||||
else:
|
||||
for bucket in self.buckets:
|
||||
indices.append(list(range(len(bucket))))
|
||||
|
||||
batches = []
|
||||
for i in range(len(self.buckets)):
|
||||
bucket = self.buckets[i]
|
||||
len_bucket = len(bucket)
|
||||
ids_bucket = indices[i]
|
||||
num_samples_bucket = self.num_samples_per_bucket[i]
|
||||
|
||||
# add extra samples to make it evenly divisible
|
||||
rem = num_samples_bucket - len_bucket
|
||||
ids_bucket = (
|
||||
ids_bucket
|
||||
+ ids_bucket * (rem // len_bucket)
|
||||
+ ids_bucket[: (rem % len_bucket)]
|
||||
)
|
||||
|
||||
# subsample
|
||||
ids_bucket = ids_bucket[self.rank :: self.num_replicas]
|
||||
|
||||
# batching
|
||||
for j in range(len(ids_bucket) // self.batch_size):
|
||||
batch = [
|
||||
bucket[idx]
|
||||
for idx in ids_bucket[
|
||||
j * self.batch_size : (j + 1) * self.batch_size
|
||||
]
|
||||
]
|
||||
batches.append(batch)
|
||||
|
||||
if self.shuffle:
|
||||
batch_ids = torch.randperm(len(batches), generator=g).tolist()
|
||||
batches = [batches[i] for i in batch_ids]
|
||||
self.batches = batches
|
||||
|
||||
assert len(self.batches) * self.batch_size == self.num_samples
|
||||
return iter(self.batches)
|
||||
|
||||
def _bisect(self, x, lo=0, hi=None):
|
||||
if hi is None:
|
||||
hi = len(self.boundaries) - 1
|
||||
|
||||
if hi > lo:
|
||||
mid = (hi + lo) // 2
|
||||
if self.boundaries[mid] < x and x <= self.boundaries[mid + 1]:
|
||||
return mid
|
||||
elif x <= self.boundaries[mid]:
|
||||
return self._bisect(x, lo, mid)
|
||||
else:
|
||||
return self._bisect(x, mid + 1, hi)
|
||||
else:
|
||||
return -1
|
||||
|
||||
def __len__(self):
|
||||
return self.num_samples // self.batch_size
|
||||
228
train/dataset/extract_f0.py
Normal file
228
train/dataset/extract_f0.py
Normal file
@@ -0,0 +1,228 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import parselmouth
|
||||
|
||||
import logging
|
||||
|
||||
import numpy as np
|
||||
|
||||
from infer.audio import load_audio
|
||||
from i18n.i18n import I18nAuto
|
||||
from tools.progress import should_report
|
||||
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
logging.getLogger("numba").setLevel(logging.WARNING)
|
||||
from multiprocessing import Process
|
||||
|
||||
mode = sys.argv[1].lower()
|
||||
if mode == "cpu":
|
||||
exp_dir = sys.argv[2]
|
||||
n_p = int(sys.argv[3])
|
||||
f0method = sys.argv[4]
|
||||
device = "cpu"
|
||||
is_half = False
|
||||
elif mode == "cuda":
|
||||
n_part = int(sys.argv[2])
|
||||
i_part = int(sys.argv[3])
|
||||
i_gpu = sys.argv[4]
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu)
|
||||
exp_dir = sys.argv[5]
|
||||
is_half = sys.argv[6].lower() == "true"
|
||||
f0method = "rmvpe"
|
||||
device = "cuda"
|
||||
elif mode in ("dml", "directml"):
|
||||
exp_dir = sys.argv[2]
|
||||
f0method = "rmvpe"
|
||||
is_half = False
|
||||
import torch_directml
|
||||
|
||||
device = torch_directml.device(torch_directml.default_device())
|
||||
else:
|
||||
raise ValueError("Unsupported F0 extraction mode: %s" % mode)
|
||||
|
||||
f = open("%s/extract_f0_feature.log" % exp_dir, "a", encoding="utf8")
|
||||
|
||||
|
||||
def printt(strr):
|
||||
print(strr)
|
||||
f.write("%s\n" % strr)
|
||||
f.flush()
|
||||
class FeatureInput(object):
|
||||
def __init__(self, samplerate=16000, hop_size=160):
|
||||
self.fs = samplerate
|
||||
self.hop = hop_size
|
||||
|
||||
self.f0_bin = 256
|
||||
self.f0_max = 1100.0
|
||||
self.f0_min = 50.0
|
||||
self.f0_mel_min = 1127 * np.log(1 + self.f0_min / 700)
|
||||
self.f0_mel_max = 1127 * np.log(1 + self.f0_max / 700)
|
||||
|
||||
def compute_f0(self, path, f0_method):
|
||||
if f0_method not in ("pm", "rmvpe"):
|
||||
raise ValueError(i18n("仅支持pm和rmvpe音高提取算法"))
|
||||
x = load_audio(path, self.fs)
|
||||
p_len = x.shape[0] // self.hop
|
||||
if f0_method == "pm":
|
||||
time_step = 160 / 16000 * 1000
|
||||
f0_min = 50
|
||||
f0_max = 1100
|
||||
f0 = (
|
||||
parselmouth.Sound(x, self.fs)
|
||||
.to_pitch_ac(
|
||||
time_step=time_step / 1000,
|
||||
voicing_threshold=0.6,
|
||||
pitch_floor=f0_min,
|
||||
pitch_ceiling=f0_max,
|
||||
)
|
||||
.selected_array["frequency"]
|
||||
)
|
||||
pad_size = (p_len - len(f0) + 1) // 2
|
||||
if pad_size > 0 or p_len - len(f0) - pad_size > 0:
|
||||
f0 = np.pad(
|
||||
f0, [[pad_size, p_len - len(f0) - pad_size]], mode="constant"
|
||||
)
|
||||
elif f0_method == "rmvpe":
|
||||
if hasattr(self, "model_rmvpe") == False:
|
||||
from infer.rmvpe import RMVPE
|
||||
|
||||
printt(i18n("正在加载RMVPE模型"))
|
||||
self.model_rmvpe = RMVPE(
|
||||
"assets/rmvpe/rmvpe.pt", is_half=is_half, device=device
|
||||
)
|
||||
f0 = self.model_rmvpe.infer_from_audio(x, thred=0.03)
|
||||
f0 = np.asarray(f0)
|
||||
try:
|
||||
uv = f0 == 0
|
||||
f0[uv] = np.interp(np.where(uv)[0], np.where(~uv)[0], f0[~uv])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return None
|
||||
return f0
|
||||
|
||||
def coarse_f0(self, f0):
|
||||
f0_mel = 1127 * np.log(1 + f0 / 700)
|
||||
f0_mel[f0_mel > 0] = (f0_mel[f0_mel > 0] - self.f0_mel_min) * (
|
||||
self.f0_bin - 2
|
||||
) / (self.f0_mel_max - self.f0_mel_min) + 1
|
||||
|
||||
# use 0 or 1
|
||||
f0_mel[f0_mel <= 1] = 1
|
||||
f0_mel[f0_mel > self.f0_bin - 1] = self.f0_bin - 1
|
||||
f0_coarse = np.rint(f0_mel).astype(int)
|
||||
assert f0_coarse.max() <= 255 and f0_coarse.min() >= 1, (
|
||||
f0_coarse.max(),
|
||||
f0_coarse.min(),
|
||||
)
|
||||
return f0_coarse
|
||||
|
||||
def go(self, paths, f0_method, max_updates=5):
|
||||
success = 0
|
||||
skipped = 0
|
||||
failed = 0
|
||||
if len(paths) == 0:
|
||||
printt(i18n("[F0提取] 无待处理音频,已全部跳过"))
|
||||
else:
|
||||
printt(i18n("[F0提取] 待处理:%s") % len(paths))
|
||||
for idx, (inp_path, opt_path1, opt_path2) in enumerate(paths):
|
||||
try:
|
||||
if (
|
||||
os.path.exists(opt_path1 + ".npy") == True
|
||||
and os.path.exists(opt_path2 + ".npy") == True
|
||||
):
|
||||
skipped += 1
|
||||
continue
|
||||
featur_pit = self.compute_f0(inp_path, f0_method)
|
||||
if featur_pit is None:
|
||||
skipped += 1
|
||||
printt(i18n("音高全部为0,该音频无意义,跳过:%s") % inp_path)
|
||||
continue
|
||||
np.save(
|
||||
opt_path2,
|
||||
featur_pit,
|
||||
allow_pickle=False,
|
||||
) # nsf
|
||||
coarse_pit = self.coarse_f0(featur_pit)
|
||||
np.save(
|
||||
opt_path1,
|
||||
coarse_pit,
|
||||
allow_pickle=False,
|
||||
) # ori
|
||||
success += 1
|
||||
if should_report(idx, len(paths), max_updates):
|
||||
printt(
|
||||
i18n("[F0提取] 进度:%s/%s | 成功:%s | 跳过:%s | %s")
|
||||
% (idx + 1, len(paths), success, skipped, os.path.basename(inp_path))
|
||||
)
|
||||
except Exception:
|
||||
failed += 1
|
||||
printt(
|
||||
i18n("[F0提取][失败] %s\n%s")
|
||||
% (inp_path, traceback.format_exc())
|
||||
)
|
||||
printt(
|
||||
i18n("[F0提取] 完成 | 成功:%s | 跳过:%s | 失败:%s")
|
||||
% (success, skipped, failed)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# exp_dir=r"E:\codes\py39\dataset\mi-test"
|
||||
# n_p=16
|
||||
featureInput = FeatureInput()
|
||||
paths = []
|
||||
inp_root = "%s/1_16k_wavs" % (exp_dir)
|
||||
opt_root1 = "%s/2a_f0" % (exp_dir)
|
||||
opt_root2 = "%s/2b-f0nsf" % (exp_dir)
|
||||
|
||||
os.makedirs(opt_root1, exist_ok=True)
|
||||
os.makedirs(opt_root2, exist_ok=True)
|
||||
for name in sorted(list(os.listdir(inp_root))):
|
||||
inp_path = "%s/%s" % (inp_root, name)
|
||||
if "spec" in inp_path:
|
||||
continue
|
||||
opt_path1 = "%s/%s" % (opt_root1, name)
|
||||
opt_path2 = "%s/%s" % (opt_root2, name)
|
||||
if os.path.exists(opt_path1 + ".npy") and os.path.exists(
|
||||
opt_path2 + ".npy"
|
||||
):
|
||||
continue
|
||||
paths.append([inp_path, opt_path1, opt_path2])
|
||||
|
||||
if mode == "cpu":
|
||||
if not paths:
|
||||
featureInput.go([], f0method, 1)
|
||||
else:
|
||||
worker_count = min(max(1, n_p), len(paths))
|
||||
ps = []
|
||||
for i in range(worker_count):
|
||||
p = Process(
|
||||
target=featureInput.go,
|
||||
args=(
|
||||
paths[i::worker_count],
|
||||
f0method,
|
||||
max(1, (12 + worker_count - 1) // worker_count),
|
||||
),
|
||||
)
|
||||
ps.append(p)
|
||||
p.start()
|
||||
for p in ps:
|
||||
p.join()
|
||||
elif mode == "cuda":
|
||||
try:
|
||||
featureInput.go(
|
||||
paths[i_part::n_part],
|
||||
"rmvpe",
|
||||
max(1, (12 + n_part - 1) // n_part),
|
||||
)
|
||||
except Exception:
|
||||
printt(i18n("[F0提取][失败] %s") % traceback.format_exc())
|
||||
else:
|
||||
try:
|
||||
featureInput.go(paths, "rmvpe", 5)
|
||||
except Exception:
|
||||
printt(i18n("[F0提取][失败] %s") % traceback.format_exc())
|
||||
154
train/dataset/extract_hubert_feature.py
Normal file
154
train/dataset/extract_hubert_feature.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
device = sys.argv[1]
|
||||
n_part = int(sys.argv[2])
|
||||
i_part = int(sys.argv[3])
|
||||
if len(sys.argv) == 7:
|
||||
exp_dir = sys.argv[4]
|
||||
version = sys.argv[5]
|
||||
is_half = sys.argv[6].lower() == "true"
|
||||
else:
|
||||
i_gpu = sys.argv[4]
|
||||
exp_dir = sys.argv[5]
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = str(i_gpu)
|
||||
version = sys.argv[6]
|
||||
is_half = sys.argv[7].lower() == "true"
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from configs.config import get_device_dtype_sm
|
||||
from infer.hubert import (
|
||||
HUBERT_MODEL_PATH,
|
||||
extract_hubert_features,
|
||||
hubert_audio_requires_normalization,
|
||||
load_hubert_model,
|
||||
)
|
||||
from i18n.i18n import I18nAuto
|
||||
from tools.progress import should_report
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
if "privateuseone" not in device:
|
||||
device = "cpu"
|
||||
if torch.cuda.is_available():
|
||||
selected_device, selected_dtype, _, _ = get_device_dtype_sm(0)
|
||||
device = str(selected_device)
|
||||
is_half = is_half and selected_dtype == torch.float16
|
||||
else:
|
||||
import torch_directml
|
||||
|
||||
device = torch_directml.device(torch_directml.default_device())
|
||||
|
||||
|
||||
f = open("%s/extract_f0_feature.log" % exp_dir, "a", encoding="utf8")
|
||||
|
||||
|
||||
def printt(strr):
|
||||
print(strr)
|
||||
f.write("%s\n" % strr)
|
||||
f.flush()
|
||||
|
||||
|
||||
model_path = str(HUBERT_MODEL_PATH)
|
||||
wavPath = "%s/1_16k_wavs" % exp_dir
|
||||
outPath = (
|
||||
"%s/3_feature256" % exp_dir if version == "v1" else "%s/3_feature768" % exp_dir
|
||||
)
|
||||
os.makedirs(outPath, exist_ok=True)
|
||||
|
||||
|
||||
# wave must be 16k, hop_size=320
|
||||
def readwave(wav_path, normalize=False):
|
||||
wav, sr = sf.read(wav_path)
|
||||
assert sr == 16000
|
||||
feats = torch.from_numpy(wav).float()
|
||||
if feats.dim() == 2: # double channels
|
||||
feats = feats.mean(-1)
|
||||
assert feats.dim() == 1, feats.dim()
|
||||
if normalize:
|
||||
with torch.no_grad():
|
||||
feats = F.layer_norm(feats, feats.shape)
|
||||
feats = feats.view(1, -1)
|
||||
return feats
|
||||
|
||||
|
||||
assigned_files = [
|
||||
file
|
||||
for file in sorted(os.listdir(wavPath))[i_part::n_part]
|
||||
if file.endswith(".wav")
|
||||
]
|
||||
todo = [
|
||||
file
|
||||
for file in assigned_files
|
||||
if not os.path.exists(
|
||||
"%s/%s.npy" % (outPath, os.path.splitext(file)[0])
|
||||
)
|
||||
]
|
||||
skipped = len(assigned_files) - len(todo)
|
||||
if len(todo) == 0:
|
||||
printt(i18n("[HuBERT特征] 无待处理音频,已全部跳过:%s") % skipped)
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
printt(i18n("[HuBERT特征] 正在加载模型:%s") % model_path)
|
||||
if os.access(model_path, os.F_OK) == False:
|
||||
printt(
|
||||
i18n("[HuBERT特征][失败] 模型不存在:%s")
|
||||
% model_path
|
||||
)
|
||||
raise SystemExit(1)
|
||||
model = load_hubert_model(device, is_half and device != "cpu")
|
||||
normalize_audio = hubert_audio_requires_normalization()
|
||||
printt(
|
||||
i18n("[HuBERT特征] 设备:%s | 待处理:%s | 已跳过:%s")
|
||||
% (device, len(todo), skipped)
|
||||
)
|
||||
|
||||
success = 0
|
||||
failed = 0
|
||||
for idx, file in enumerate(todo):
|
||||
try:
|
||||
wav_path = "%s/%s" % (wavPath, file)
|
||||
out_path = "%s/%s.npy" % (outPath, os.path.splitext(file)[0])
|
||||
if os.path.exists(out_path):
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
feats = readwave(wav_path, normalize=normalize_audio)
|
||||
padding_mask = torch.BoolTensor(feats.shape).fill_(False)
|
||||
source = (
|
||||
feats.half().to(device)
|
||||
if is_half and device != "cpu"
|
||||
else feats.to(device)
|
||||
)
|
||||
with torch.no_grad():
|
||||
feats = extract_hubert_features(
|
||||
model,
|
||||
source,
|
||||
version,
|
||||
padding_mask=padding_mask.to(device),
|
||||
)
|
||||
|
||||
feats = feats.squeeze(0).float().cpu().numpy()
|
||||
if np.isnan(feats).sum() == 0:
|
||||
np.save(out_path, feats, allow_pickle=False)
|
||||
success += 1
|
||||
if should_report(idx, len(todo), max(1, (12 + n_part - 1) // n_part)):
|
||||
printt(
|
||||
i18n("[HuBERT特征] 进度:%s/%s | 成功:%s | 失败:%s | %s | %s")
|
||||
% (idx + 1, len(todo), success, failed, file, feats.shape)
|
||||
)
|
||||
else:
|
||||
failed += 1
|
||||
printt(i18n("[HuBERT特征][失败] %s 包含NaN") % file)
|
||||
except Exception:
|
||||
failed += 1
|
||||
printt(i18n("[HuBERT特征][失败] %s\n%s") % (file, traceback.format_exc()))
|
||||
printt(
|
||||
i18n("[HuBERT特征] 完成 | 成功:%s | 跳过:%s | 失败:%s")
|
||||
% (success, skipped, failed)
|
||||
)
|
||||
260
train/dataset/slicer2.py
Normal file
260
train/dataset/slicer2.py
Normal file
@@ -0,0 +1,260 @@
|
||||
import numpy as np
|
||||
|
||||
|
||||
# This function is obtained from librosa.
|
||||
def get_rms(
|
||||
y,
|
||||
frame_length=2048,
|
||||
hop_length=512,
|
||||
pad_mode="constant",
|
||||
):
|
||||
padding = (int(frame_length // 2), int(frame_length // 2))
|
||||
y = np.pad(y, padding, mode=pad_mode)
|
||||
|
||||
axis = -1
|
||||
# put our new within-frame axis at the end for now
|
||||
out_strides = y.strides + tuple([y.strides[axis]])
|
||||
# Reduce the shape on the framing axis
|
||||
x_shape_trimmed = list(y.shape)
|
||||
x_shape_trimmed[axis] -= frame_length - 1
|
||||
out_shape = tuple(x_shape_trimmed) + tuple([frame_length])
|
||||
xw = np.lib.stride_tricks.as_strided(y, shape=out_shape, strides=out_strides)
|
||||
if axis < 0:
|
||||
target_axis = axis - 1
|
||||
else:
|
||||
target_axis = axis + 1
|
||||
xw = np.moveaxis(xw, -1, target_axis)
|
||||
# Downsample along the target axis
|
||||
slices = [slice(None)] * xw.ndim
|
||||
slices[axis] = slice(0, None, hop_length)
|
||||
x = xw[tuple(slices)]
|
||||
|
||||
# Calculate power
|
||||
power = np.mean(np.abs(x) ** 2, axis=-2, keepdims=True)
|
||||
|
||||
return np.sqrt(power)
|
||||
|
||||
|
||||
class Slicer:
|
||||
def __init__(
|
||||
self,
|
||||
sr,
|
||||
threshold = -40.0,
|
||||
min_length = 5000,
|
||||
min_interval = 300,
|
||||
hop_size = 20,
|
||||
max_sil_kept = 5000,
|
||||
):
|
||||
if not min_length >= min_interval >= hop_size:
|
||||
raise ValueError(
|
||||
"The following condition must be satisfied: min_length >= min_interval >= hop_size"
|
||||
)
|
||||
if not max_sil_kept >= hop_size:
|
||||
raise ValueError(
|
||||
"The following condition must be satisfied: max_sil_kept >= hop_size"
|
||||
)
|
||||
min_interval = sr * min_interval / 1000
|
||||
self.threshold = 10 ** (threshold / 20.0)
|
||||
self.hop_size = round(sr * hop_size / 1000)
|
||||
self.win_size = min(round(min_interval), 4 * self.hop_size)
|
||||
self.min_length = round(sr * min_length / 1000 / self.hop_size)
|
||||
self.min_interval = round(min_interval / self.hop_size)
|
||||
self.max_sil_kept = round(sr * max_sil_kept / 1000 / self.hop_size)
|
||||
|
||||
def _apply_slice(self, waveform, begin, end):
|
||||
if len(waveform.shape) > 1:
|
||||
return waveform[
|
||||
:, begin * self.hop_size : min(waveform.shape[1], end * self.hop_size)
|
||||
]
|
||||
else:
|
||||
return waveform[
|
||||
begin * self.hop_size : min(waveform.shape[0], end * self.hop_size)
|
||||
]
|
||||
|
||||
# @timeit
|
||||
def slice(self, waveform):
|
||||
if len(waveform.shape) > 1:
|
||||
samples = waveform.mean(axis=0)
|
||||
else:
|
||||
samples = waveform
|
||||
if samples.shape[0] <= self.min_length:
|
||||
return [waveform]
|
||||
rms_list = get_rms(
|
||||
y=samples, frame_length=self.win_size, hop_length=self.hop_size
|
||||
).squeeze(0)
|
||||
sil_tags = []
|
||||
silence_start = None
|
||||
clip_start = 0
|
||||
for i, rms in enumerate(rms_list):
|
||||
# Keep looping while frame is silent.
|
||||
if rms < self.threshold:
|
||||
# Record start of silent frames.
|
||||
if silence_start is None:
|
||||
silence_start = i
|
||||
continue
|
||||
# Keep looping while frame is not silent and silence start has not been recorded.
|
||||
if silence_start is None:
|
||||
continue
|
||||
# Clear recorded silence start if interval is not enough or clip is too short
|
||||
is_leading_silence = silence_start == 0 and i > self.max_sil_kept
|
||||
need_slice_middle = (
|
||||
i - silence_start >= self.min_interval
|
||||
and i - clip_start >= self.min_length
|
||||
)
|
||||
if not is_leading_silence and not need_slice_middle:
|
||||
silence_start = None
|
||||
continue
|
||||
# Need slicing. Record the range of silent frames to be removed.
|
||||
if i - silence_start <= self.max_sil_kept:
|
||||
pos = rms_list[silence_start : i + 1].argmin() + silence_start
|
||||
if silence_start == 0:
|
||||
sil_tags.append((0, pos))
|
||||
else:
|
||||
sil_tags.append((pos, pos))
|
||||
clip_start = pos
|
||||
elif i - silence_start <= self.max_sil_kept * 2:
|
||||
pos = rms_list[
|
||||
i - self.max_sil_kept : silence_start + self.max_sil_kept + 1
|
||||
].argmin()
|
||||
pos += i - self.max_sil_kept
|
||||
pos_l = (
|
||||
rms_list[
|
||||
silence_start : silence_start + self.max_sil_kept + 1
|
||||
].argmin()
|
||||
+ silence_start
|
||||
)
|
||||
pos_r = (
|
||||
rms_list[i - self.max_sil_kept : i + 1].argmin()
|
||||
+ i
|
||||
- self.max_sil_kept
|
||||
)
|
||||
if silence_start == 0:
|
||||
sil_tags.append((0, pos_r))
|
||||
clip_start = pos_r
|
||||
else:
|
||||
sil_tags.append((min(pos_l, pos), max(pos_r, pos)))
|
||||
clip_start = max(pos_r, pos)
|
||||
else:
|
||||
pos_l = (
|
||||
rms_list[
|
||||
silence_start : silence_start + self.max_sil_kept + 1
|
||||
].argmin()
|
||||
+ silence_start
|
||||
)
|
||||
pos_r = (
|
||||
rms_list[i - self.max_sil_kept : i + 1].argmin()
|
||||
+ i
|
||||
- self.max_sil_kept
|
||||
)
|
||||
if silence_start == 0:
|
||||
sil_tags.append((0, pos_r))
|
||||
else:
|
||||
sil_tags.append((pos_l, pos_r))
|
||||
clip_start = pos_r
|
||||
silence_start = None
|
||||
# Deal with trailing silence.
|
||||
total_frames = rms_list.shape[0]
|
||||
if (
|
||||
silence_start is not None
|
||||
and total_frames - silence_start >= self.min_interval
|
||||
):
|
||||
silence_end = min(total_frames, silence_start + self.max_sil_kept)
|
||||
pos = rms_list[silence_start : silence_end + 1].argmin() + silence_start
|
||||
sil_tags.append((pos, total_frames + 1))
|
||||
# Apply and return slices.
|
||||
if len(sil_tags) == 0:
|
||||
return [waveform]
|
||||
else:
|
||||
chunks = []
|
||||
if sil_tags[0][0] > 0:
|
||||
chunks.append(self._apply_slice(waveform, 0, sil_tags[0][0]))
|
||||
for i in range(len(sil_tags) - 1):
|
||||
chunks.append(
|
||||
self._apply_slice(waveform, sil_tags[i][1], sil_tags[i + 1][0])
|
||||
)
|
||||
if sil_tags[-1][1] < total_frames:
|
||||
chunks.append(
|
||||
self._apply_slice(waveform, sil_tags[-1][1], total_frames)
|
||||
)
|
||||
return chunks
|
||||
|
||||
|
||||
def main():
|
||||
import os.path
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import librosa
|
||||
import soundfile
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("audio", type=str, help="The audio to be sliced")
|
||||
parser.add_argument(
|
||||
"--out", type=str, help="Output directory of the sliced audio clips"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--db_thresh",
|
||||
type=float,
|
||||
required=False,
|
||||
default=-40,
|
||||
help="The dB threshold for silence detection",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min_length",
|
||||
type=int,
|
||||
required=False,
|
||||
default=5000,
|
||||
help="The minimum milliseconds required for each sliced audio clip",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min_interval",
|
||||
type=int,
|
||||
required=False,
|
||||
default=300,
|
||||
help="The minimum milliseconds for a silence part to be sliced",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hop_size",
|
||||
type=int,
|
||||
required=False,
|
||||
default=10,
|
||||
help="Frame length in milliseconds",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max_sil_kept",
|
||||
type=int,
|
||||
required=False,
|
||||
default=500,
|
||||
help="The maximum silence length kept around the sliced clip, presented in milliseconds",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
out = args.out
|
||||
if out is None:
|
||||
out = os.path.dirname(os.path.abspath(args.audio))
|
||||
audio, sr = librosa.load(args.audio, sr=None, mono=False)
|
||||
slicer = Slicer(
|
||||
sr=sr,
|
||||
threshold=args.db_thresh,
|
||||
min_length=args.min_length,
|
||||
min_interval=args.min_interval,
|
||||
hop_size=args.hop_size,
|
||||
max_sil_kept=args.max_sil_kept,
|
||||
)
|
||||
chunks = slicer.slice(audio)
|
||||
if not os.path.exists(out):
|
||||
os.makedirs(out)
|
||||
for i, chunk in enumerate(chunks):
|
||||
if len(chunk.shape) > 1:
|
||||
chunk = chunk.T
|
||||
soundfile.write(
|
||||
os.path.join(
|
||||
out,
|
||||
f"%s_%d.wav"
|
||||
% (os.path.basename(args.audio).rsplit(".", maxsplit=1)[0], i),
|
||||
),
|
||||
chunk,
|
||||
sr,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
train/losses.py
Normal file
58
train/losses.py
Normal file
@@ -0,0 +1,58 @@
|
||||
import torch
|
||||
|
||||
|
||||
def feature_loss(fmap_r, fmap_g):
|
||||
loss = 0
|
||||
for dr, dg in zip(fmap_r, fmap_g):
|
||||
for rl, gl in zip(dr, dg):
|
||||
rl = rl.float().detach()
|
||||
gl = gl.float()
|
||||
loss += torch.mean(torch.abs(rl - gl))
|
||||
|
||||
return loss * 2
|
||||
|
||||
|
||||
def discriminator_loss(disc_real_outputs, disc_generated_outputs):
|
||||
loss = 0
|
||||
r_losses = []
|
||||
g_losses = []
|
||||
for dr, dg in zip(disc_real_outputs, disc_generated_outputs):
|
||||
dr = dr.float()
|
||||
dg = dg.float()
|
||||
r_loss = torch.mean((1 - dr) ** 2)
|
||||
g_loss = torch.mean(dg**2)
|
||||
loss += r_loss + g_loss
|
||||
r_losses.append(r_loss.item())
|
||||
g_losses.append(g_loss.item())
|
||||
|
||||
return loss, r_losses, g_losses
|
||||
|
||||
|
||||
def generator_loss(disc_outputs):
|
||||
loss = 0
|
||||
gen_losses = []
|
||||
for dg in disc_outputs:
|
||||
dg = dg.float()
|
||||
l = torch.mean((1 - dg) ** 2)
|
||||
gen_losses.append(l)
|
||||
loss += l
|
||||
|
||||
return loss, gen_losses
|
||||
|
||||
|
||||
def kl_loss(z_p, logs_q, m_p, logs_p, z_mask):
|
||||
"""
|
||||
z_p, logs_q: [b, h, t_t]
|
||||
m_p, logs_p: [b, h, t_t]
|
||||
"""
|
||||
z_p = z_p.float()
|
||||
logs_q = logs_q.float()
|
||||
m_p = m_p.float()
|
||||
logs_p = logs_p.float()
|
||||
z_mask = z_mask.float()
|
||||
|
||||
kl = logs_p - logs_q - 0.5
|
||||
kl += 0.5 * ((z_p - m_p) ** 2) * torch.exp(-2.0 * logs_p)
|
||||
kl = torch.sum(kl * z_mask)
|
||||
l = kl / torch.sum(z_mask)
|
||||
return l
|
||||
127
train/mel_processing.py
Normal file
127
train/mel_processing.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import torch
|
||||
import torch.utils.data
|
||||
from librosa.filters import mel as librosa_mel_fn
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MAX_WAV_VALUE = 32768.0
|
||||
|
||||
|
||||
def dynamic_range_compression_torch(x, C=1, clip_val=2e-6):
|
||||
"""
|
||||
PARAMS
|
||||
------
|
||||
C: compression factor
|
||||
"""
|
||||
return torch.log(torch.clamp(x, min=clip_val) * C)
|
||||
|
||||
|
||||
def dynamic_range_decompression_torch(x, C=1):
|
||||
"""
|
||||
PARAMS
|
||||
------
|
||||
C: compression factor used to compress
|
||||
"""
|
||||
return torch.exp(x) / C
|
||||
|
||||
|
||||
def spectral_normalize_torch(magnitudes):
|
||||
return dynamic_range_compression_torch(magnitudes)
|
||||
|
||||
|
||||
def spectral_de_normalize_torch(magnitudes):
|
||||
return dynamic_range_decompression_torch(magnitudes)
|
||||
|
||||
|
||||
# Reusable banks
|
||||
mel_basis = {}
|
||||
hann_window = {}
|
||||
|
||||
|
||||
def spectrogram_torch(y, n_fft, sampling_rate, hop_size, win_size, center=False):
|
||||
"""Convert waveform into Linear-frequency Linear-amplitude spectrogram.
|
||||
|
||||
Args:
|
||||
y :: (B, T) - Audio waveforms
|
||||
n_fft
|
||||
sampling_rate
|
||||
hop_size
|
||||
win_size
|
||||
center
|
||||
Returns:
|
||||
:: (B, Freq, Frame) - Linear-frequency Linear-amplitude spectrogram
|
||||
"""
|
||||
|
||||
# Window - Cache if needed
|
||||
global hann_window
|
||||
dtype_device = str(y.dtype) + "_" + str(y.device)
|
||||
wnsize_dtype_device = str(win_size) + "_" + dtype_device
|
||||
if wnsize_dtype_device not in hann_window:
|
||||
hann_window[wnsize_dtype_device] = torch.hann_window(win_size).to(
|
||||
dtype=y.dtype, device=y.device
|
||||
)
|
||||
|
||||
# Padding
|
||||
y = torch.nn.functional.pad(
|
||||
y.unsqueeze(1),
|
||||
(int((n_fft - hop_size) / 2), int((n_fft - hop_size) / 2)),
|
||||
mode="reflect",
|
||||
)
|
||||
y = y.squeeze(1)
|
||||
|
||||
# Complex Spectrogram :: (B, T) -> (B, Freq, Frame, RealComplex=2)
|
||||
spec = torch.stft(
|
||||
y,
|
||||
n_fft,
|
||||
hop_length=hop_size,
|
||||
win_length=win_size,
|
||||
window=hann_window[wnsize_dtype_device],
|
||||
center=center,
|
||||
pad_mode="reflect",
|
||||
normalized=False,
|
||||
onesided=True,
|
||||
return_complex=True,
|
||||
)
|
||||
|
||||
# Linear-frequency Linear-amplitude spectrogram :: (B, Freq, Frame, RealComplex=2) -> (B, Freq, Frame)
|
||||
spec = torch.sqrt(spec.real.pow(2) + spec.imag.pow(2) + 2e-7)
|
||||
return spec
|
||||
|
||||
|
||||
def spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax):
|
||||
# MelBasis - Cache if needed
|
||||
global mel_basis
|
||||
dtype_device = str(spec.dtype) + "_" + str(spec.device)
|
||||
fmax_dtype_device = str(fmax) + "_" + dtype_device
|
||||
if fmax_dtype_device not in mel_basis:
|
||||
mel = librosa_mel_fn(
|
||||
sr=sampling_rate, n_fft=n_fft, n_mels=num_mels, fmin=fmin, fmax=fmax
|
||||
)
|
||||
mel_basis[fmax_dtype_device] = torch.from_numpy(mel).to(
|
||||
dtype=spec.dtype, device=spec.device
|
||||
)
|
||||
|
||||
# Mel-frequency Log-amplitude spectrogram :: (B, Freq=num_mels, Frame)
|
||||
melspec = torch.matmul(mel_basis[fmax_dtype_device], spec)
|
||||
melspec = spectral_normalize_torch(melspec)
|
||||
return melspec
|
||||
|
||||
|
||||
def mel_spectrogram_torch(
|
||||
y, n_fft, num_mels, sampling_rate, hop_size, win_size, fmin, fmax, center=False
|
||||
):
|
||||
"""Convert waveform into Mel-frequency Log-amplitude spectrogram.
|
||||
|
||||
Args:
|
||||
y :: (B, T) - Waveforms
|
||||
Returns:
|
||||
melspec :: (B, Freq, Frame) - Mel-frequency Log-amplitude spectrogram
|
||||
"""
|
||||
# Linear-frequency Linear-amplitude spectrogram :: (B, T) -> (B, Freq, Frame)
|
||||
spec = spectrogram_torch(y, n_fft, sampling_rate, hop_size, win_size, center)
|
||||
|
||||
# Mel-frequency Log-amplitude spectrogram :: (B, Freq, Frame) -> (B, Freq=num_mels, Frame)
|
||||
melspec = spec_to_mel_torch(spec, n_fft, num_mels, sampling_rate, fmin, fmax)
|
||||
|
||||
return melspec
|
||||
169
train/preprocess.py
Normal file
169
train/preprocess.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
|
||||
from scipy import signal
|
||||
|
||||
inp_root = sys.argv[1]
|
||||
sr = int(sys.argv[2])
|
||||
n_p = int(sys.argv[3])
|
||||
exp_dir = sys.argv[4]
|
||||
noparallel = sys.argv[5] == "True"
|
||||
per = float(sys.argv[6])
|
||||
import os
|
||||
import traceback
|
||||
|
||||
import librosa
|
||||
import numpy as np
|
||||
from scipy.io import wavfile
|
||||
|
||||
from infer.audio import load_audio
|
||||
from train.dataset.slicer2 import Slicer
|
||||
from i18n.i18n import I18nAuto
|
||||
from tools.progress import should_report
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
f = open("%s/preprocess.log" % exp_dir, "a", encoding="utf8")
|
||||
|
||||
|
||||
def println(strr):
|
||||
print(strr)
|
||||
f.write("%s\n" % strr)
|
||||
f.flush()
|
||||
|
||||
|
||||
class PreProcess:
|
||||
def __init__(self, sr, exp_dir, per=3.7):
|
||||
self.slicer = Slicer(
|
||||
sr=sr,
|
||||
threshold=-42,
|
||||
min_length=1500,
|
||||
min_interval=400,
|
||||
hop_size=15,
|
||||
max_sil_kept=500,
|
||||
)
|
||||
self.sr = sr
|
||||
self.bh, self.ah = signal.butter(N=5, Wn=48, btype="high", fs=self.sr)
|
||||
self.per = per
|
||||
self.overlap = 0.3
|
||||
self.tail = self.per + self.overlap
|
||||
self.max = 0.9
|
||||
self.alpha = 0.75
|
||||
self.exp_dir = exp_dir
|
||||
self.gt_wavs_dir = "%s/0_gt_wavs" % exp_dir
|
||||
self.wavs16k_dir = "%s/1_16k_wavs" % exp_dir
|
||||
os.makedirs(self.exp_dir, exist_ok=True)
|
||||
os.makedirs(self.gt_wavs_dir, exist_ok=True)
|
||||
os.makedirs(self.wavs16k_dir, exist_ok=True)
|
||||
|
||||
def norm_write(self, tmp_audio, idx0, idx1):
|
||||
tmp_max = np.abs(tmp_audio).max()
|
||||
if not np.isfinite(tmp_max) or tmp_max <= 0 or tmp_max > 2.5:
|
||||
println(
|
||||
i18n("[数据切分][跳过] 无效或异常音频片段:%s_%s | 峰值:%s")
|
||||
% (idx0, idx1, tmp_max)
|
||||
)
|
||||
return False
|
||||
tmp_audio = (tmp_audio / tmp_max * (self.max * self.alpha)) + (
|
||||
1 - self.alpha
|
||||
) * tmp_audio
|
||||
wavfile.write(
|
||||
"%s/%s_%s.wav" % (self.gt_wavs_dir, idx0, idx1),
|
||||
self.sr,
|
||||
tmp_audio.astype(np.float32),
|
||||
)
|
||||
tmp_audio = librosa.resample(
|
||||
tmp_audio, orig_sr=self.sr, target_sr=16000
|
||||
) # , res_type="soxr_vhq"
|
||||
wavfile.write(
|
||||
"%s/%s_%s.wav" % (self.wavs16k_dir, idx0, idx1),
|
||||
16000,
|
||||
tmp_audio.astype(np.float32),
|
||||
)
|
||||
return True
|
||||
|
||||
def pipeline(self, path, idx0, total):
|
||||
try:
|
||||
audio = load_audio(path, self.sr)
|
||||
# zero phased digital filter cause pre-ringing noise...
|
||||
# audio = signal.filtfilt(self.bh, self.ah, audio)
|
||||
audio = signal.lfilter(self.bh, self.ah, audio)
|
||||
|
||||
idx1 = 0
|
||||
for audio in self.slicer.slice(audio):
|
||||
i = 0
|
||||
while 1:
|
||||
start = int(self.sr * (self.per - self.overlap) * i)
|
||||
i += 1
|
||||
if len(audio[start:]) > self.tail * self.sr:
|
||||
tmp_audio = audio[start : start + int(self.per * self.sr)]
|
||||
self.norm_write(tmp_audio, idx0, idx1)
|
||||
idx1 += 1
|
||||
else:
|
||||
tmp_audio = audio[start:]
|
||||
idx1 += 1
|
||||
break
|
||||
self.norm_write(tmp_audio, idx0, idx1)
|
||||
if should_report(idx0, total):
|
||||
println(
|
||||
i18n("[数据切分] 进度:%s/%s | %s")
|
||||
% (idx0 + 1, total, os.path.basename(path))
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
println(
|
||||
i18n("[数据切分][失败] %s\n%s")
|
||||
% (path, traceback.format_exc())
|
||||
)
|
||||
return False
|
||||
|
||||
def pipeline_mp(self, infos):
|
||||
success = 0
|
||||
failed = 0
|
||||
for path, idx0, total in infos:
|
||||
if self.pipeline(path, idx0, total):
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
if infos:
|
||||
println(
|
||||
i18n("[数据切分] 子任务完成 | 成功:%s | 失败:%s")
|
||||
% (success, failed)
|
||||
)
|
||||
|
||||
def pipeline_mp_inp_dir(self, inp_root, n_p):
|
||||
try:
|
||||
names = sorted(os.listdir(inp_root))
|
||||
total = len(names)
|
||||
infos = [
|
||||
("%s/%s" % (inp_root, name), idx, total)
|
||||
for idx, name in enumerate(names)
|
||||
]
|
||||
println(i18n("[数据切分] 待处理:%s | 进程数:%s") % (total, n_p))
|
||||
if noparallel:
|
||||
for i in range(n_p):
|
||||
self.pipeline_mp(infos[i::n_p])
|
||||
else:
|
||||
ps = []
|
||||
for i in range(n_p):
|
||||
p = multiprocessing.Process(
|
||||
target=self.pipeline_mp, args=(infos[i::n_p],)
|
||||
)
|
||||
ps.append(p)
|
||||
p.start()
|
||||
for i in range(n_p):
|
||||
ps[i].join()
|
||||
except Exception:
|
||||
println(i18n("[数据切分][失败] %s") % traceback.format_exc())
|
||||
|
||||
|
||||
def preprocess_trainset(inp_root, sr, n_p, exp_dir, per):
|
||||
pp = PreProcess(sr, exp_dir, per)
|
||||
println(i18n("[数据切分] 开始"))
|
||||
pp.pipeline_mp_inp_dir(inp_root, n_p)
|
||||
println(i18n("[数据切分] 完成"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
preprocess_trainset(inp_root, sr, n_p, exp_dir, per)
|
||||
261
train/process_ckpt.py
Normal file
261
train/process_ckpt.py
Normal file
@@ -0,0 +1,261 @@
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from collections import OrderedDict
|
||||
|
||||
import torch
|
||||
|
||||
from i18n.i18n import I18nAuto
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
|
||||
def savee(ckpt, sr, if_f0, name, epoch, version, hps):
|
||||
try:
|
||||
opt = OrderedDict()
|
||||
opt["weight"] = {}
|
||||
for key in ckpt.keys():
|
||||
if "enc_q" in key:
|
||||
continue
|
||||
opt["weight"][key] = ckpt[key].half()
|
||||
opt["config"] = [
|
||||
hps.data.filter_length // 2 + 1,
|
||||
32,
|
||||
hps.model.inter_channels,
|
||||
hps.model.hidden_channels,
|
||||
hps.model.filter_channels,
|
||||
hps.model.n_heads,
|
||||
hps.model.n_layers,
|
||||
hps.model.kernel_size,
|
||||
hps.model.p_dropout,
|
||||
hps.model.resblock,
|
||||
hps.model.resblock_kernel_sizes,
|
||||
hps.model.resblock_dilation_sizes,
|
||||
hps.model.upsample_rates,
|
||||
hps.model.upsample_initial_channel,
|
||||
hps.model.upsample_kernel_sizes,
|
||||
hps.model.spk_embed_dim,
|
||||
hps.model.gin_channels,
|
||||
hps.data.sampling_rate,
|
||||
]
|
||||
opt["info"] = "%sepoch" % epoch
|
||||
opt["sr"] = sr
|
||||
opt["f0"] = if_f0
|
||||
opt["version"] = version
|
||||
torch.save(opt, "assets/weights/%s.pth" % name)
|
||||
return i18n("成功")
|
||||
except:
|
||||
return traceback.format_exc()
|
||||
|
||||
|
||||
def show_info(path):
|
||||
try:
|
||||
a = torch.load(path, map_location="cpu")
|
||||
return i18n("模型信息:%s\n采样率:%s\n是否使用音高引导:%s\n版本:%s") % (
|
||||
a.get("info", "None"),
|
||||
a.get("sr", "None"),
|
||||
a.get("f0", "None"),
|
||||
a.get("version", "None"),
|
||||
)
|
||||
except:
|
||||
return traceback.format_exc()
|
||||
|
||||
|
||||
def extract_small_model(path, name, sr, if_f0, info, version):
|
||||
try:
|
||||
ckpt = torch.load(path, map_location="cpu")
|
||||
if "model" in ckpt:
|
||||
ckpt = ckpt["model"]
|
||||
opt = OrderedDict()
|
||||
opt["weight"] = {}
|
||||
for key in ckpt.keys():
|
||||
if "enc_q" in key:
|
||||
continue
|
||||
opt["weight"][key] = ckpt[key].half()
|
||||
if sr == "40k":
|
||||
opt["config"] = [
|
||||
1025,
|
||||
32,
|
||||
192,
|
||||
192,
|
||||
768,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
"1",
|
||||
[3, 7, 11],
|
||||
[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
|
||||
[10, 10, 2, 2],
|
||||
512,
|
||||
[16, 16, 4, 4],
|
||||
109,
|
||||
256,
|
||||
40000,
|
||||
]
|
||||
elif sr == "48k":
|
||||
if version == "v1":
|
||||
opt["config"] = [
|
||||
1025,
|
||||
32,
|
||||
192,
|
||||
192,
|
||||
768,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
"1",
|
||||
[3, 7, 11],
|
||||
[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
|
||||
[10, 6, 2, 2, 2],
|
||||
512,
|
||||
[16, 16, 4, 4, 4],
|
||||
109,
|
||||
256,
|
||||
48000,
|
||||
]
|
||||
else:
|
||||
opt["config"] = [
|
||||
1025,
|
||||
32,
|
||||
192,
|
||||
192,
|
||||
768,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
"1",
|
||||
[3, 7, 11],
|
||||
[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
|
||||
[12, 10, 2, 2],
|
||||
512,
|
||||
[24, 20, 4, 4],
|
||||
109,
|
||||
256,
|
||||
48000,
|
||||
]
|
||||
elif sr == "32k":
|
||||
if version == "v1":
|
||||
opt["config"] = [
|
||||
513,
|
||||
32,
|
||||
192,
|
||||
192,
|
||||
768,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
"1",
|
||||
[3, 7, 11],
|
||||
[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
|
||||
[10, 4, 2, 2, 2],
|
||||
512,
|
||||
[16, 16, 4, 4, 4],
|
||||
109,
|
||||
256,
|
||||
32000,
|
||||
]
|
||||
else:
|
||||
opt["config"] = [
|
||||
513,
|
||||
32,
|
||||
192,
|
||||
192,
|
||||
768,
|
||||
2,
|
||||
6,
|
||||
3,
|
||||
0,
|
||||
"1",
|
||||
[3, 7, 11],
|
||||
[[1, 3, 5], [1, 3, 5], [1, 3, 5]],
|
||||
[10, 8, 2, 2],
|
||||
512,
|
||||
[20, 16, 4, 4],
|
||||
109,
|
||||
256,
|
||||
32000,
|
||||
]
|
||||
if info == "":
|
||||
info = i18n("从训练检查点提取的模型")
|
||||
opt["info"] = info
|
||||
opt["version"] = version
|
||||
opt["sr"] = sr
|
||||
opt["f0"] = int(if_f0)
|
||||
torch.save(opt, "assets/weights/%s.pth" % name)
|
||||
return i18n("成功")
|
||||
except:
|
||||
return traceback.format_exc()
|
||||
|
||||
|
||||
def change_info(path, info, name):
|
||||
try:
|
||||
ckpt = torch.load(path, map_location="cpu")
|
||||
ckpt["info"] = info
|
||||
if name == "":
|
||||
name = os.path.basename(path)
|
||||
torch.save(ckpt, "assets/weights/%s" % name)
|
||||
return i18n("成功")
|
||||
except:
|
||||
return traceback.format_exc()
|
||||
|
||||
|
||||
def merge(path1, path2, alpha1, sr, f0, info, name, version):
|
||||
try:
|
||||
|
||||
def extract(ckpt):
|
||||
a = ckpt["model"]
|
||||
opt = OrderedDict()
|
||||
opt["weight"] = {}
|
||||
for key in a.keys():
|
||||
if "enc_q" in key:
|
||||
continue
|
||||
opt["weight"][key] = a[key]
|
||||
return opt
|
||||
|
||||
ckpt1 = torch.load(path1, map_location="cpu")
|
||||
ckpt2 = torch.load(path2, map_location="cpu")
|
||||
cfg = ckpt1["config"]
|
||||
if "model" in ckpt1:
|
||||
ckpt1 = extract(ckpt1)
|
||||
else:
|
||||
ckpt1 = ckpt1["weight"]
|
||||
if "model" in ckpt2:
|
||||
ckpt2 = extract(ckpt2)
|
||||
else:
|
||||
ckpt2 = ckpt2["weight"]
|
||||
if sorted(list(ckpt1.keys())) != sorted(list(ckpt2.keys())):
|
||||
return i18n("模型融合失败:两个模型的结构不一致")
|
||||
opt = OrderedDict()
|
||||
opt["weight"] = {}
|
||||
for key in ckpt1.keys():
|
||||
# try:
|
||||
if key == "emb_g.weight" and ckpt1[key].shape != ckpt2[key].shape:
|
||||
min_shape0 = min(ckpt1[key].shape[0], ckpt2[key].shape[0])
|
||||
opt["weight"][key] = (
|
||||
alpha1 * (ckpt1[key][:min_shape0].float())
|
||||
+ (1 - alpha1) * (ckpt2[key][:min_shape0].float())
|
||||
).half()
|
||||
else:
|
||||
opt["weight"][key] = (
|
||||
alpha1 * (ckpt1[key].float()) + (1 - alpha1) * (ckpt2[key].float())
|
||||
).half()
|
||||
# except:
|
||||
# pdb.set_trace()
|
||||
opt["config"] = cfg
|
||||
"""
|
||||
if(sr=="40k"):opt["config"] = [1025, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10, 10, 2, 2], 512, [16, 16, 4, 4,4], 109, 256, 40000]
|
||||
elif(sr=="48k"):opt["config"] = [1025, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10,6,2,2,2], 512, [16, 16, 4, 4], 109, 256, 48000]
|
||||
elif(sr=="32k"):opt["config"] = [513, 32, 192, 192, 768, 2, 6, 3, 0, "1", [3, 7, 11], [[1, 3, 5], [1, 3, 5], [1, 3, 5]], [10, 4, 2, 2, 2], 512, [16, 16, 4, 4,4], 109, 256, 32000]
|
||||
"""
|
||||
opt["sr"] = sr
|
||||
opt["f0"] = 1 if f0 == i18n("是") else 0
|
||||
opt["version"] = version
|
||||
opt["info"] = info
|
||||
torch.save(opt, "assets/weights/%s.pth" % name)
|
||||
return i18n("成功")
|
||||
except:
|
||||
return traceback.format_exc()
|
||||
657
train/train.py
Normal file
657
train/train.py
Normal file
@@ -0,0 +1,657 @@
|
||||
import os
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message="`torch.nn.utils.weight_norm` is deprecated.*",
|
||||
category=FutureWarning,
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message="`torch.cuda.amp.GradScaler.*is deprecated.*",
|
||||
category=FutureWarning,
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message="`torch.cuda.amp.autocast.*is deprecated.*",
|
||||
category=FutureWarning,
|
||||
)
|
||||
warnings.filterwarnings(
|
||||
"ignore",
|
||||
message="Grad strides do not match bucket view strides.*",
|
||||
category=UserWarning,
|
||||
)
|
||||
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import datetime
|
||||
|
||||
from train import utils
|
||||
|
||||
hps = utils.get_hparams()
|
||||
os.environ["CUDA_VISIBLE_DEVICES"] = hps.gpus.replace("-", ",")
|
||||
n_gpus = len(hps.gpus.split("-"))
|
||||
from random import randint, shuffle
|
||||
|
||||
import torch
|
||||
|
||||
from configs.config import get_training_dtype
|
||||
from i18n.i18n import I18nAuto
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
training_dtype = get_training_dtype()
|
||||
training_is_half = training_dtype == torch.float16
|
||||
|
||||
from torch.cuda.amp import GradScaler, autocast
|
||||
|
||||
torch.backends.cudnn.deterministic = False
|
||||
torch.backends.cudnn.benchmark = False
|
||||
from time import sleep
|
||||
from time import time as ttime
|
||||
|
||||
import torch.distributed as dist
|
||||
import torch.multiprocessing as mp
|
||||
from torch.nn import functional as F
|
||||
from torch.nn.parallel import DistributedDataParallel as DDP
|
||||
from torch.utils.data import DataLoader
|
||||
from torch.utils.tensorboard import SummaryWriter
|
||||
|
||||
from infer.module import commons
|
||||
from train.data_utils import (
|
||||
DistributedBucketSampler,
|
||||
TextAudioCollate,
|
||||
TextAudioCollateMultiNSFsid,
|
||||
TextAudioLoader,
|
||||
TextAudioLoaderMultiNSFsid,
|
||||
)
|
||||
|
||||
if hps.version == "v1":
|
||||
from infer.module.models import MultiPeriodDiscriminator
|
||||
from infer.module.models import SynthesizerTrnMs256NSFsid as RVC_Model_f0
|
||||
from infer.module.models import (
|
||||
SynthesizerTrnMs256NSFsid_nono as RVC_Model_nof0,
|
||||
)
|
||||
else:
|
||||
from infer.module.models import (
|
||||
SynthesizerTrnMs768NSFsid as RVC_Model_f0,
|
||||
SynthesizerTrnMs768NSFsid_nono as RVC_Model_nof0,
|
||||
MultiPeriodDiscriminatorV2 as MultiPeriodDiscriminator,
|
||||
)
|
||||
|
||||
from train.losses import (
|
||||
discriminator_loss,
|
||||
feature_loss,
|
||||
generator_loss,
|
||||
kl_loss,
|
||||
)
|
||||
from train.mel_processing import mel_spectrogram_torch, spec_to_mel_torch
|
||||
from train.process_ckpt import savee
|
||||
|
||||
global_step = 0
|
||||
|
||||
|
||||
class EpochRecorder:
|
||||
def __init__(self):
|
||||
self.last_time = ttime()
|
||||
|
||||
def record(self):
|
||||
now_time = ttime()
|
||||
elapsed_time = now_time - self.last_time
|
||||
self.last_time = now_time
|
||||
elapsed_time_str = str(datetime.timedelta(seconds=elapsed_time))
|
||||
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
return f"[{current_time}] | ({elapsed_time_str})"
|
||||
|
||||
|
||||
def main():
|
||||
n_gpus = torch.cuda.device_count()
|
||||
single_cuda = torch.cuda.is_available() and n_gpus == 1
|
||||
|
||||
if n_gpus < 1:
|
||||
# patch to unblock people without gpus. there is probably a better way.
|
||||
print(i18n("未检测到可用显卡,将使用CPU训练,耗时可能较长"))
|
||||
n_gpus = 1
|
||||
logger = utils.get_logger(hps.model_dir)
|
||||
logger.info(i18n("训练设备规则选择的精度:%s"), training_dtype)
|
||||
if single_cuda:
|
||||
run(0, 1, hps, logger, False)
|
||||
return
|
||||
os.environ["MASTER_ADDR"] = "localhost"
|
||||
os.environ["MASTER_PORT"] = str(randint(20000, 55555))
|
||||
children = []
|
||||
for i in range(n_gpus):
|
||||
subproc = mp.Process(
|
||||
target=run,
|
||||
args=(i, n_gpus, hps, logger, True),
|
||||
)
|
||||
children.append(subproc)
|
||||
subproc.start()
|
||||
|
||||
for i in range(n_gpus):
|
||||
children[i].join()
|
||||
|
||||
|
||||
def run(rank, n_gpus, hps, logger, use_ddp):
|
||||
global global_step
|
||||
if rank == 0:
|
||||
# logger = utils.get_logger(hps.model_dir)
|
||||
logger.info(hps)
|
||||
# utils.check_git_hash(hps.model_dir)
|
||||
writer = SummaryWriter(log_dir=hps.model_dir)
|
||||
writer_eval = SummaryWriter(log_dir=os.path.join(hps.model_dir, "eval"))
|
||||
|
||||
if use_ddp:
|
||||
dist.init_process_group(
|
||||
backend="gloo", init_method="env://", world_size=n_gpus, rank=rank
|
||||
)
|
||||
torch.manual_seed(hps.train.seed)
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.set_device(rank)
|
||||
|
||||
if hps.if_f0 == 1:
|
||||
train_dataset = TextAudioLoaderMultiNSFsid(hps.data.training_files, hps.data)
|
||||
else:
|
||||
train_dataset = TextAudioLoader(hps.data.training_files, hps.data)
|
||||
train_sampler = DistributedBucketSampler(
|
||||
train_dataset,
|
||||
hps.train.batch_size * n_gpus,
|
||||
# [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1200,1400], # 16s
|
||||
[100, 200, 300, 400, 500, 600, 700, 800, 900], # 16s
|
||||
num_replicas=n_gpus,
|
||||
rank=rank,
|
||||
shuffle=True,
|
||||
)
|
||||
# It is possible that dataloader's workers are out of shared memory. Please try to raise your shared memory limit.
|
||||
# num_workers=8 -> num_workers=4
|
||||
if hps.if_f0 == 1:
|
||||
collate_fn = TextAudioCollateMultiNSFsid()
|
||||
else:
|
||||
collate_fn = TextAudioCollate()
|
||||
train_loader = DataLoader(
|
||||
train_dataset,
|
||||
num_workers=4,
|
||||
shuffle=False,
|
||||
pin_memory=True,
|
||||
collate_fn=collate_fn,
|
||||
batch_sampler=train_sampler,
|
||||
persistent_workers=True,
|
||||
prefetch_factor=8,
|
||||
)
|
||||
if hps.if_f0 == 1:
|
||||
net_g = RVC_Model_f0(
|
||||
hps.data.filter_length // 2 + 1,
|
||||
hps.train.segment_size // hps.data.hop_length,
|
||||
**hps.model,
|
||||
is_half=training_is_half,
|
||||
sr=hps.sample_rate,
|
||||
)
|
||||
else:
|
||||
net_g = RVC_Model_nof0(
|
||||
hps.data.filter_length // 2 + 1,
|
||||
hps.train.segment_size // hps.data.hop_length,
|
||||
**hps.model,
|
||||
is_half=training_is_half,
|
||||
)
|
||||
if torch.cuda.is_available():
|
||||
net_g = net_g.cuda(rank)
|
||||
net_d = MultiPeriodDiscriminator(hps.model.use_spectral_norm)
|
||||
if torch.cuda.is_available():
|
||||
net_d = net_d.cuda(rank)
|
||||
optim_g = torch.optim.AdamW(
|
||||
net_g.parameters(),
|
||||
hps.train.learning_rate,
|
||||
betas=hps.train.betas,
|
||||
eps=hps.train.eps,
|
||||
)
|
||||
optim_d = torch.optim.AdamW(
|
||||
net_d.parameters(),
|
||||
hps.train.learning_rate,
|
||||
betas=hps.train.betas,
|
||||
eps=hps.train.eps,
|
||||
)
|
||||
# net_g = DDP(net_g, device_ids=[rank], find_unused_parameters=True)
|
||||
# net_d = DDP(net_d, device_ids=[rank], find_unused_parameters=True)
|
||||
if use_ddp:
|
||||
if torch.cuda.is_available():
|
||||
net_g = DDP(net_g, device_ids=[rank])
|
||||
net_d = DDP(net_d, device_ids=[rank])
|
||||
else:
|
||||
net_g = DDP(net_g)
|
||||
net_d = DDP(net_d)
|
||||
|
||||
try: # 如果能加载自动resume
|
||||
_, _, _, epoch_str = utils.load_checkpoint(
|
||||
utils.latest_checkpoint_path(hps.model_dir, "D_*.pth"), net_d, optim_d
|
||||
) # D多半加载没事
|
||||
if rank == 0:
|
||||
logger.info(i18n("已恢复判别器检查点"))
|
||||
# _, _, _, epoch_str = utils.load_checkpoint(utils.latest_checkpoint_path(hps.model_dir, "G_*.pth"), net_g, optim_g,load_opt=0)
|
||||
_, _, _, epoch_str = utils.load_checkpoint(
|
||||
utils.latest_checkpoint_path(hps.model_dir, "G_*.pth"), net_g, optim_g
|
||||
)
|
||||
global_step = (epoch_str - 1) * len(train_loader)
|
||||
# epoch_str = 1
|
||||
# global_step = 0
|
||||
except Exception: # 如果首次不能加载,加载pretrain
|
||||
# traceback.print_exc()
|
||||
epoch_str = 1
|
||||
global_step = 0
|
||||
if hps.pretrainG != "":
|
||||
if rank == 0:
|
||||
logger.info(i18n("已加载生成器预训练模型:%s") % hps.pretrainG)
|
||||
if hasattr(net_g, "module"):
|
||||
logger.info(
|
||||
net_g.module.load_state_dict(
|
||||
torch.load(hps.pretrainG, map_location="cpu")["model"]
|
||||
)
|
||||
) ##测试不加载优化器
|
||||
else:
|
||||
logger.info(
|
||||
net_g.load_state_dict(
|
||||
torch.load(hps.pretrainG, map_location="cpu")["model"]
|
||||
)
|
||||
) ##测试不加载优化器
|
||||
if hps.pretrainD != "":
|
||||
if rank == 0:
|
||||
logger.info(i18n("已加载判别器预训练模型:%s") % hps.pretrainD)
|
||||
if hasattr(net_d, "module"):
|
||||
logger.info(
|
||||
net_d.module.load_state_dict(
|
||||
torch.load(hps.pretrainD, map_location="cpu")["model"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
net_d.load_state_dict(
|
||||
torch.load(hps.pretrainD, map_location="cpu")["model"]
|
||||
)
|
||||
)
|
||||
|
||||
scheduler_g = torch.optim.lr_scheduler.ExponentialLR(
|
||||
optim_g, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2
|
||||
)
|
||||
scheduler_d = torch.optim.lr_scheduler.ExponentialLR(
|
||||
optim_d, gamma=hps.train.lr_decay, last_epoch=epoch_str - 2
|
||||
)
|
||||
|
||||
scaler = GradScaler(enabled=training_is_half)
|
||||
|
||||
cache = []
|
||||
for epoch in range(epoch_str, hps.train.epochs + 1):
|
||||
if rank == 0:
|
||||
train_and_evaluate(
|
||||
rank,
|
||||
epoch,
|
||||
hps,
|
||||
[net_g, net_d],
|
||||
[optim_g, optim_d],
|
||||
[scheduler_g, scheduler_d],
|
||||
scaler,
|
||||
[train_loader, None],
|
||||
logger,
|
||||
[writer, writer_eval],
|
||||
cache,
|
||||
)
|
||||
else:
|
||||
train_and_evaluate(
|
||||
rank,
|
||||
epoch,
|
||||
hps,
|
||||
[net_g, net_d],
|
||||
[optim_g, optim_d],
|
||||
[scheduler_g, scheduler_d],
|
||||
scaler,
|
||||
[train_loader, None],
|
||||
None,
|
||||
None,
|
||||
cache,
|
||||
)
|
||||
scheduler_g.step()
|
||||
scheduler_d.step()
|
||||
|
||||
|
||||
def train_and_evaluate(
|
||||
rank, epoch, hps, nets, optims, schedulers, scaler, loaders, logger, writers, cache
|
||||
):
|
||||
net_g, net_d = nets
|
||||
optim_g, optim_d = optims
|
||||
train_loader, eval_loader = loaders
|
||||
if writers is not None:
|
||||
writer, writer_eval = writers
|
||||
|
||||
train_loader.batch_sampler.set_epoch(epoch)
|
||||
global global_step
|
||||
|
||||
net_g.train()
|
||||
net_d.train()
|
||||
|
||||
# Prepare data iterator
|
||||
if hps.if_cache_data_in_gpu == True:
|
||||
# Use Cache
|
||||
data_iterator = cache
|
||||
if cache == []:
|
||||
# Make new cache
|
||||
for batch_idx, info in enumerate(train_loader):
|
||||
# Unpack
|
||||
if hps.if_f0 == 1:
|
||||
(
|
||||
phone,
|
||||
phone_lengths,
|
||||
pitch,
|
||||
pitchf,
|
||||
spec,
|
||||
spec_lengths,
|
||||
wave,
|
||||
wave_lengths,
|
||||
sid,
|
||||
) = info
|
||||
else:
|
||||
(
|
||||
phone,
|
||||
phone_lengths,
|
||||
spec,
|
||||
spec_lengths,
|
||||
wave,
|
||||
wave_lengths,
|
||||
sid,
|
||||
) = info
|
||||
# Load on CUDA
|
||||
if torch.cuda.is_available():
|
||||
phone = phone.cuda(rank, non_blocking=True)
|
||||
phone_lengths = phone_lengths.cuda(rank, non_blocking=True)
|
||||
if hps.if_f0 == 1:
|
||||
pitch = pitch.cuda(rank, non_blocking=True)
|
||||
pitchf = pitchf.cuda(rank, non_blocking=True)
|
||||
sid = sid.cuda(rank, non_blocking=True)
|
||||
spec = spec.cuda(rank, non_blocking=True)
|
||||
spec_lengths = spec_lengths.cuda(rank, non_blocking=True)
|
||||
wave = wave.cuda(rank, non_blocking=True)
|
||||
wave_lengths = wave_lengths.cuda(rank, non_blocking=True)
|
||||
# Cache on list
|
||||
if hps.if_f0 == 1:
|
||||
cache.append(
|
||||
(
|
||||
batch_idx,
|
||||
(
|
||||
phone,
|
||||
phone_lengths,
|
||||
pitch,
|
||||
pitchf,
|
||||
spec,
|
||||
spec_lengths,
|
||||
wave,
|
||||
wave_lengths,
|
||||
sid,
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
cache.append(
|
||||
(
|
||||
batch_idx,
|
||||
(
|
||||
phone,
|
||||
phone_lengths,
|
||||
spec,
|
||||
spec_lengths,
|
||||
wave,
|
||||
wave_lengths,
|
||||
sid,
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Load shuffled cache
|
||||
shuffle(cache)
|
||||
else:
|
||||
# Loader
|
||||
data_iterator = enumerate(train_loader)
|
||||
|
||||
# Run steps
|
||||
epoch_recorder = EpochRecorder()
|
||||
for batch_idx, info in data_iterator:
|
||||
# Data
|
||||
## Unpack
|
||||
if hps.if_f0 == 1:
|
||||
(
|
||||
phone,
|
||||
phone_lengths,
|
||||
pitch,
|
||||
pitchf,
|
||||
spec,
|
||||
spec_lengths,
|
||||
wave,
|
||||
wave_lengths,
|
||||
sid,
|
||||
) = info
|
||||
else:
|
||||
phone, phone_lengths, spec, spec_lengths, wave, wave_lengths, sid = info
|
||||
## Load on CUDA
|
||||
if (hps.if_cache_data_in_gpu == False) and torch.cuda.is_available():
|
||||
phone = phone.cuda(rank, non_blocking=True)
|
||||
phone_lengths = phone_lengths.cuda(rank, non_blocking=True)
|
||||
if hps.if_f0 == 1:
|
||||
pitch = pitch.cuda(rank, non_blocking=True)
|
||||
pitchf = pitchf.cuda(rank, non_blocking=True)
|
||||
sid = sid.cuda(rank, non_blocking=True)
|
||||
spec = spec.cuda(rank, non_blocking=True)
|
||||
spec_lengths = spec_lengths.cuda(rank, non_blocking=True)
|
||||
wave = wave.cuda(rank, non_blocking=True)
|
||||
# wave_lengths = wave_lengths.cuda(rank, non_blocking=True)
|
||||
|
||||
# Calculate
|
||||
with autocast(enabled=training_is_half):
|
||||
if hps.if_f0 == 1:
|
||||
(
|
||||
y_hat,
|
||||
ids_slice,
|
||||
x_mask,
|
||||
z_mask,
|
||||
(z, z_p, m_p, logs_p, m_q, logs_q),
|
||||
) = net_g(phone, phone_lengths, pitch, pitchf, spec, spec_lengths, sid)
|
||||
else:
|
||||
(
|
||||
y_hat,
|
||||
ids_slice,
|
||||
x_mask,
|
||||
z_mask,
|
||||
(z, z_p, m_p, logs_p, m_q, logs_q),
|
||||
) = net_g(phone, phone_lengths, spec, spec_lengths, sid)
|
||||
mel = spec_to_mel_torch(
|
||||
spec,
|
||||
hps.data.filter_length,
|
||||
hps.data.n_mel_channels,
|
||||
hps.data.sampling_rate,
|
||||
hps.data.mel_fmin,
|
||||
hps.data.mel_fmax,
|
||||
)
|
||||
y_mel = commons.slice_segments(
|
||||
mel, ids_slice, hps.train.segment_size // hps.data.hop_length
|
||||
)
|
||||
with autocast(enabled=False):
|
||||
y_hat_mel = mel_spectrogram_torch(
|
||||
y_hat.float().squeeze(1),
|
||||
hps.data.filter_length,
|
||||
hps.data.n_mel_channels,
|
||||
hps.data.sampling_rate,
|
||||
hps.data.hop_length,
|
||||
hps.data.win_length,
|
||||
hps.data.mel_fmin,
|
||||
hps.data.mel_fmax,
|
||||
)
|
||||
if training_is_half:
|
||||
y_hat_mel = y_hat_mel.half()
|
||||
wave = commons.slice_segments(
|
||||
wave, ids_slice * hps.data.hop_length, hps.train.segment_size
|
||||
) # slice
|
||||
|
||||
# Discriminator
|
||||
y_d_hat_r, y_d_hat_g, _, _ = net_d(wave, y_hat.detach())
|
||||
with autocast(enabled=False):
|
||||
loss_disc, losses_disc_r, losses_disc_g = discriminator_loss(
|
||||
y_d_hat_r, y_d_hat_g
|
||||
)
|
||||
optim_d.zero_grad()
|
||||
scaler.scale(loss_disc).backward()
|
||||
scaler.unscale_(optim_d)
|
||||
grad_norm_d = commons.clip_grad_value_(net_d.parameters(), None)
|
||||
scaler.step(optim_d)
|
||||
|
||||
with autocast(enabled=training_is_half):
|
||||
# Generator
|
||||
y_d_hat_r, y_d_hat_g, fmap_r, fmap_g = net_d(wave, y_hat)
|
||||
with autocast(enabled=False):
|
||||
loss_mel = F.l1_loss(y_mel, y_hat_mel) * hps.train.c_mel
|
||||
loss_kl = kl_loss(z_p, logs_q, m_p, logs_p, z_mask) * hps.train.c_kl
|
||||
loss_fm = feature_loss(fmap_r, fmap_g)
|
||||
loss_gen, losses_gen = generator_loss(y_d_hat_g)
|
||||
loss_gen_all = loss_gen + loss_fm + loss_mel + loss_kl
|
||||
optim_g.zero_grad()
|
||||
scaler.scale(loss_gen_all).backward()
|
||||
scaler.unscale_(optim_g)
|
||||
grad_norm_g = commons.clip_grad_value_(net_g.parameters(), None)
|
||||
scaler.step(optim_g)
|
||||
scaler.update()
|
||||
|
||||
if rank == 0:
|
||||
if global_step % hps.train.log_interval == 0:
|
||||
lr = optim_g.param_groups[0]["lr"]
|
||||
logger.info(
|
||||
i18n("训练轮次:{} [{:.0f}%]").format(
|
||||
epoch, 100.0 * batch_idx / len(train_loader)
|
||||
)
|
||||
)
|
||||
# Amor For Tensorboard display
|
||||
if loss_mel > 75:
|
||||
loss_mel = 75
|
||||
if loss_kl > 9:
|
||||
loss_kl = 9
|
||||
|
||||
logger.info([global_step, lr])
|
||||
logger.info(
|
||||
f"loss_disc={loss_disc:.3f}, loss_gen={loss_gen:.3f}, loss_fm={loss_fm:.3f},loss_mel={loss_mel:.3f}, loss_kl={loss_kl:.3f}"
|
||||
)
|
||||
scalar_dict = {
|
||||
"loss/g/total": loss_gen_all,
|
||||
"loss/d/total": loss_disc,
|
||||
"learning_rate": lr,
|
||||
"grad_norm_d": grad_norm_d,
|
||||
"grad_norm_g": grad_norm_g,
|
||||
}
|
||||
scalar_dict.update(
|
||||
{
|
||||
"loss/g/fm": loss_fm,
|
||||
"loss/g/mel": loss_mel,
|
||||
"loss/g/kl": loss_kl,
|
||||
}
|
||||
)
|
||||
|
||||
scalar_dict.update(
|
||||
{"loss/g/{}".format(i): v for i, v in enumerate(losses_gen)}
|
||||
)
|
||||
scalar_dict.update(
|
||||
{"loss/d_r/{}".format(i): v for i, v in enumerate(losses_disc_r)}
|
||||
)
|
||||
scalar_dict.update(
|
||||
{"loss/d_g/{}".format(i): v for i, v in enumerate(losses_disc_g)}
|
||||
)
|
||||
image_dict = {
|
||||
"slice/mel_org": utils.plot_spectrogram_to_numpy(
|
||||
y_mel[0].data.cpu().numpy()
|
||||
),
|
||||
"slice/mel_gen": utils.plot_spectrogram_to_numpy(
|
||||
y_hat_mel[0].data.cpu().numpy()
|
||||
),
|
||||
"all/mel": utils.plot_spectrogram_to_numpy(
|
||||
mel[0].data.cpu().numpy()
|
||||
),
|
||||
}
|
||||
utils.summarize(
|
||||
writer=writer,
|
||||
global_step=global_step,
|
||||
images=image_dict,
|
||||
scalars=scalar_dict,
|
||||
)
|
||||
global_step += 1
|
||||
# /Run steps
|
||||
|
||||
if epoch % hps.save_every_epoch == 0 and rank == 0:
|
||||
if hps.if_latest == 0:
|
||||
utils.save_checkpoint(
|
||||
net_g,
|
||||
optim_g,
|
||||
hps.train.learning_rate,
|
||||
epoch,
|
||||
os.path.join(hps.model_dir, "G_{}.pth".format(global_step)),
|
||||
)
|
||||
utils.save_checkpoint(
|
||||
net_d,
|
||||
optim_d,
|
||||
hps.train.learning_rate,
|
||||
epoch,
|
||||
os.path.join(hps.model_dir, "D_{}.pth".format(global_step)),
|
||||
)
|
||||
else:
|
||||
utils.save_checkpoint(
|
||||
net_g,
|
||||
optim_g,
|
||||
hps.train.learning_rate,
|
||||
epoch,
|
||||
os.path.join(hps.model_dir, "G_{}.pth".format(2333333)),
|
||||
)
|
||||
utils.save_checkpoint(
|
||||
net_d,
|
||||
optim_d,
|
||||
hps.train.learning_rate,
|
||||
epoch,
|
||||
os.path.join(hps.model_dir, "D_{}.pth".format(2333333)),
|
||||
)
|
||||
if rank == 0 and hps.save_every_weights == "1":
|
||||
if hasattr(net_g, "module"):
|
||||
ckpt = net_g.module.state_dict()
|
||||
else:
|
||||
ckpt = net_g.state_dict()
|
||||
logger.info(
|
||||
i18n("正在保存检查点 %s_e%s:%s")
|
||||
% (
|
||||
hps.name,
|
||||
epoch,
|
||||
savee(
|
||||
ckpt,
|
||||
hps.sample_rate,
|
||||
hps.if_f0,
|
||||
hps.name + "_e%s_s%s" % (epoch, global_step),
|
||||
epoch,
|
||||
hps.version,
|
||||
hps,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if rank == 0:
|
||||
logger.info(i18n("====> 轮次:{} {}").format(epoch, epoch_recorder.record()))
|
||||
if epoch >= hps.total_epoch and rank == 0:
|
||||
logger.info(i18n("训练已完成,正在保存最终模型"))
|
||||
|
||||
if hasattr(net_g, "module"):
|
||||
ckpt = net_g.module.state_dict()
|
||||
else:
|
||||
ckpt = net_g.state_dict()
|
||||
logger.info(
|
||||
i18n("正在保存最终检查点:%s")
|
||||
% (
|
||||
savee(
|
||||
ckpt, hps.sample_rate, hps.if_f0, hps.name, epoch, hps.version, hps
|
||||
)
|
||||
)
|
||||
)
|
||||
sleep(1)
|
||||
os._exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
torch.multiprocessing.set_start_method("spawn")
|
||||
main()
|
||||
174
train/train_index.py
Normal file
174
train/train_index.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
import glob
|
||||
|
||||
import faiss
|
||||
import numpy as np
|
||||
from sklearn.cluster import MiniBatchKMeans
|
||||
from i18n.i18n import I18nAuto
|
||||
from tools.progress import should_report
|
||||
|
||||
|
||||
i18n = I18nAuto()
|
||||
|
||||
|
||||
exp_name = sys.argv[1]
|
||||
version = sys.argv[2]
|
||||
outside_index_root = sys.argv[3]
|
||||
n_cpu = int(sys.argv[4])
|
||||
exp_dir = os.path.join("logs", exp_name)
|
||||
feature_dir = os.path.join(
|
||||
exp_dir, "3_feature256" if version == "v1" else "3_feature768"
|
||||
)
|
||||
log_path = os.path.join(exp_dir, "train_index.log")
|
||||
os.makedirs(exp_dir, exist_ok=True)
|
||||
|
||||
|
||||
def log(message):
|
||||
print(message, flush=True)
|
||||
with open(log_path, "a", encoding="utf8") as f:
|
||||
f.write(str(message) + "\n")
|
||||
|
||||
|
||||
with open(log_path, "w", encoding="utf8"):
|
||||
pass
|
||||
|
||||
|
||||
def newest_index(pattern):
|
||||
paths = [path for path in glob.glob(pattern) if os.path.isfile(path)]
|
||||
return max(paths, key=os.path.getmtime) if paths else ""
|
||||
|
||||
|
||||
def link_added_index(added_path):
|
||||
added_name = os.path.basename(added_path)
|
||||
try:
|
||||
os.makedirs(outside_index_root, exist_ok=True)
|
||||
source = os.path.abspath(added_path)
|
||||
outside_root = os.path.abspath(outside_index_root)
|
||||
if os.path.commonpath([source, outside_root]) == outside_root:
|
||||
log(i18n("[索引训练] 外部索引链接已存在:%s") % source)
|
||||
return
|
||||
target = os.path.abspath(
|
||||
os.path.join(outside_index_root, "%s_%s" % (exp_name, added_name))
|
||||
)
|
||||
if os.path.lexists(target):
|
||||
try:
|
||||
if os.path.samefile(source, target):
|
||||
log(i18n("[索引训练] 外部索引链接已存在:%s") % target)
|
||||
return
|
||||
except (FileNotFoundError, OSError):
|
||||
pass
|
||||
os.unlink(target)
|
||||
if platform.system() == "Windows":
|
||||
os.link(source, target)
|
||||
else:
|
||||
os.symlink(source, target)
|
||||
log(i18n("[索引训练] 已链接索引到外部目录:%s") % outside_index_root)
|
||||
except Exception:
|
||||
log(
|
||||
i18n("[索引训练][失败] 无法链接索引到外部目录:%s\n%s")
|
||||
% (outside_index_root, traceback.format_exc())
|
||||
)
|
||||
|
||||
|
||||
existing_trained_path = newest_index(
|
||||
os.path.join(
|
||||
exp_dir,
|
||||
"trained_IVF*_Flat_nprobe_*_%s_%s.index" % (exp_name, version),
|
||||
)
|
||||
)
|
||||
existing_added_path = newest_index(
|
||||
os.path.join(exp_dir, "added_IVF*_Flat_nprobe_*_%s_%s.index" % (exp_name, version))
|
||||
)
|
||||
if not existing_added_path:
|
||||
existing_added_path = newest_index(
|
||||
os.path.join(
|
||||
outside_index_root,
|
||||
"%s_*added_IVF*_Flat_nprobe_*_%s_%s.index"
|
||||
% (exp_name, exp_name, version),
|
||||
)
|
||||
)
|
||||
if existing_added_path:
|
||||
if existing_trained_path:
|
||||
log(
|
||||
i18n("[索引训练][跳过] trained索引已存在:%s")
|
||||
% os.path.basename(existing_trained_path)
|
||||
)
|
||||
log(
|
||||
i18n("[索引训练][跳过] added索引已存在:%s")
|
||||
% os.path.basename(existing_added_path)
|
||||
)
|
||||
link_added_index(existing_added_path)
|
||||
raise SystemExit(0)
|
||||
|
||||
if not os.path.isdir(feature_dir) or not os.listdir(feature_dir):
|
||||
log(i18n("[索引训练][失败] 请先进行特征提取"))
|
||||
raise SystemExit(1)
|
||||
|
||||
features = []
|
||||
for name in sorted(os.listdir(feature_dir)):
|
||||
features.append(np.load(os.path.join(feature_dir, name)))
|
||||
|
||||
big_npy = np.concatenate(features, 0)
|
||||
big_npy = big_npy[np.random.permutation(big_npy.shape[0])]
|
||||
if big_npy.shape[0] > 200000:
|
||||
log(i18n("[索引训练] 正在将%s条特征聚类为10000个中心") % big_npy.shape[0])
|
||||
try:
|
||||
big_npy = MiniBatchKMeans(
|
||||
n_clusters=10000,
|
||||
verbose=False,
|
||||
batch_size=256 * n_cpu,
|
||||
compute_labels=False,
|
||||
init="random",
|
||||
).fit(big_npy).cluster_centers_
|
||||
except Exception:
|
||||
log(i18n("[索引训练][失败] 聚类失败,将使用原始特征继续\n%s") % traceback.format_exc())
|
||||
|
||||
n_ivf = max(1, min(int(16 * np.sqrt(big_npy.shape[0])), big_npy.shape[0] // 39))
|
||||
log(i18n("[索引训练] 特征形状:%s | IVF数量:%s") % (big_npy.shape, n_ivf))
|
||||
if existing_trained_path:
|
||||
trained_path = existing_trained_path
|
||||
index = faiss.read_index(trained_path)
|
||||
index_ivf = faiss.extract_index_ivf(index)
|
||||
index_ivf.nprobe = 1
|
||||
log(
|
||||
i18n("[索引训练][跳过] trained索引已存在:%s")
|
||||
% os.path.basename(trained_path)
|
||||
)
|
||||
else:
|
||||
index = faiss.index_factory(
|
||||
256 if version == "v1" else 768, "IVF%s,Flat" % n_ivf
|
||||
)
|
||||
index_ivf = faiss.extract_index_ivf(index)
|
||||
index_ivf.nprobe = 1
|
||||
trained_path = os.path.join(
|
||||
exp_dir,
|
||||
"trained_IVF%s_Flat_nprobe_%s_%s_%s.index"
|
||||
% (n_ivf, index_ivf.nprobe, exp_name, version),
|
||||
)
|
||||
log(i18n("[索引训练] 正在训练索引"))
|
||||
index.train(big_npy)
|
||||
faiss.write_index(index, trained_path)
|
||||
|
||||
log(i18n("[索引训练] 正在写入特征向量"))
|
||||
starts = list(range(0, big_npy.shape[0], 8192))
|
||||
for batch_index, start in enumerate(starts):
|
||||
index.add(big_npy[start : start + 8192])
|
||||
if should_report(batch_index, len(starts), 10):
|
||||
log(
|
||||
i18n("[索引训练] 写入进度:%s/%s")
|
||||
% (batch_index + 1, len(starts))
|
||||
)
|
||||
|
||||
added_name = "added_IVF%s_Flat_nprobe_%s_%s_%s.index" % (
|
||||
n_ivf,
|
||||
index_ivf.nprobe,
|
||||
exp_name,
|
||||
version,
|
||||
)
|
||||
added_path = os.path.join(exp_dir, added_name)
|
||||
faiss.write_index(index, added_path)
|
||||
log(i18n("[索引训练] 成功构建索引:%s") % added_name)
|
||||
link_added_index(added_path)
|
||||
479
train/utils.py
Normal file
479
train/utils.py
Normal file
@@ -0,0 +1,479 @@
|
||||
import argparse
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from scipy.io.wavfile import read
|
||||
from tools.file_io import read_text
|
||||
|
||||
MATPLOTLIB_FLAG = False
|
||||
|
||||
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
||||
logger = logging
|
||||
|
||||
|
||||
def load_checkpoint_d(checkpoint_path, combd, sbd, optimizer=None, load_opt=1):
|
||||
assert os.path.isfile(checkpoint_path)
|
||||
checkpoint_dict = torch.load(checkpoint_path, map_location="cpu")
|
||||
|
||||
##################
|
||||
def go(model, bkey):
|
||||
saved_state_dict = checkpoint_dict[bkey]
|
||||
if hasattr(model, "module"):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
new_state_dict = {}
|
||||
for k, v in state_dict.items(): # 模型需要的shape
|
||||
try:
|
||||
new_state_dict[k] = saved_state_dict[k]
|
||||
if saved_state_dict[k].shape != state_dict[k].shape:
|
||||
logger.warning(
|
||||
"shape-%s-mismatch. need: %s, get: %s",
|
||||
k,
|
||||
state_dict[k].shape,
|
||||
saved_state_dict[k].shape,
|
||||
) #
|
||||
raise KeyError
|
||||
except:
|
||||
# logger.info(traceback.format_exc())
|
||||
logger.info("%s is not in the checkpoint", k) # pretrain缺失的
|
||||
new_state_dict[k] = v # 模型自带的随机值
|
||||
if hasattr(model, "module"):
|
||||
model.module.load_state_dict(new_state_dict, strict=False)
|
||||
else:
|
||||
model.load_state_dict(new_state_dict, strict=False)
|
||||
return model
|
||||
|
||||
go(combd, "combd")
|
||||
model = go(sbd, "sbd")
|
||||
#############
|
||||
logger.info("Loaded model weights")
|
||||
|
||||
iteration = checkpoint_dict["iteration"]
|
||||
learning_rate = checkpoint_dict["learning_rate"]
|
||||
if (
|
||||
optimizer is not None and load_opt == 1
|
||||
): ###加载不了,如果是空的的话,重新初始化,可能还会影响lr时间表的更新,因此在train文件最外围catch
|
||||
# try:
|
||||
optimizer.load_state_dict(checkpoint_dict["optimizer"])
|
||||
# except:
|
||||
# traceback.print_exc()
|
||||
logger.info("Loaded checkpoint '{}' (epoch {})".format(checkpoint_path, iteration))
|
||||
return model, optimizer, learning_rate, iteration
|
||||
|
||||
|
||||
# def load_checkpoint(checkpoint_path, model, optimizer=None):
|
||||
# assert os.path.isfile(checkpoint_path)
|
||||
# checkpoint_dict = torch.load(checkpoint_path, map_location='cpu')
|
||||
# iteration = checkpoint_dict['iteration']
|
||||
# learning_rate = checkpoint_dict['learning_rate']
|
||||
# if optimizer is not None:
|
||||
# optimizer.load_state_dict(checkpoint_dict['optimizer'])
|
||||
# # print(1111)
|
||||
# saved_state_dict = checkpoint_dict['model']
|
||||
# # print(1111)
|
||||
#
|
||||
# if hasattr(model, 'module'):
|
||||
# state_dict = model.module.state_dict()
|
||||
# else:
|
||||
# state_dict = model.state_dict()
|
||||
# new_state_dict= {}
|
||||
# for k, v in state_dict.items():
|
||||
# try:
|
||||
# new_state_dict[k] = saved_state_dict[k]
|
||||
# except:
|
||||
# logger.info("%s is not in the checkpoint" % k)
|
||||
# new_state_dict[k] = v
|
||||
# if hasattr(model, 'module'):
|
||||
# model.module.load_state_dict(new_state_dict)
|
||||
# else:
|
||||
# model.load_state_dict(new_state_dict)
|
||||
# logger.info("Loaded checkpoint '{}' (epoch {})" .format(
|
||||
# checkpoint_path, iteration))
|
||||
# return model, optimizer, learning_rate, iteration
|
||||
def load_checkpoint(checkpoint_path, model, optimizer=None, load_opt=1):
|
||||
assert os.path.isfile(checkpoint_path)
|
||||
checkpoint_dict = torch.load(checkpoint_path, map_location="cpu")
|
||||
|
||||
saved_state_dict = checkpoint_dict["model"]
|
||||
if hasattr(model, "module"):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
new_state_dict = {}
|
||||
for k, v in state_dict.items(): # 模型需要的shape
|
||||
try:
|
||||
new_state_dict[k] = saved_state_dict[k]
|
||||
if saved_state_dict[k].shape != state_dict[k].shape:
|
||||
logger.warning(
|
||||
"shape-%s-mismatch|need-%s|get-%s",
|
||||
k,
|
||||
state_dict[k].shape,
|
||||
saved_state_dict[k].shape,
|
||||
) #
|
||||
raise KeyError
|
||||
except:
|
||||
# logger.info(traceback.format_exc())
|
||||
logger.info("%s is not in the checkpoint", k) # pretrain缺失的
|
||||
new_state_dict[k] = v # 模型自带的随机值
|
||||
if hasattr(model, "module"):
|
||||
model.module.load_state_dict(new_state_dict, strict=False)
|
||||
else:
|
||||
model.load_state_dict(new_state_dict, strict=False)
|
||||
logger.info("Loaded model weights")
|
||||
|
||||
iteration = checkpoint_dict["iteration"]
|
||||
learning_rate = checkpoint_dict["learning_rate"]
|
||||
if (
|
||||
optimizer is not None and load_opt == 1
|
||||
): ###加载不了,如果是空的的话,重新初始化,可能还会影响lr时间表的更新,因此在train文件最外围catch
|
||||
# try:
|
||||
optimizer.load_state_dict(checkpoint_dict["optimizer"])
|
||||
# except:
|
||||
# traceback.print_exc()
|
||||
logger.info("Loaded checkpoint '{}' (epoch {})".format(checkpoint_path, iteration))
|
||||
return model, optimizer, learning_rate, iteration
|
||||
|
||||
|
||||
def save_checkpoint(model, optimizer, learning_rate, iteration, checkpoint_path):
|
||||
logger.info(
|
||||
"Saving model and optimizer state at epoch {} to {}".format(
|
||||
iteration, checkpoint_path
|
||||
)
|
||||
)
|
||||
if hasattr(model, "module"):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
torch.save(
|
||||
{
|
||||
"model": state_dict,
|
||||
"iteration": iteration,
|
||||
"optimizer": optimizer.state_dict(),
|
||||
"learning_rate": learning_rate,
|
||||
},
|
||||
checkpoint_path,
|
||||
)
|
||||
|
||||
|
||||
def save_checkpoint_d(combd, sbd, optimizer, learning_rate, iteration, checkpoint_path):
|
||||
logger.info(
|
||||
"Saving model and optimizer state at epoch {} to {}".format(
|
||||
iteration, checkpoint_path
|
||||
)
|
||||
)
|
||||
if hasattr(combd, "module"):
|
||||
state_dict_combd = combd.module.state_dict()
|
||||
else:
|
||||
state_dict_combd = combd.state_dict()
|
||||
if hasattr(sbd, "module"):
|
||||
state_dict_sbd = sbd.module.state_dict()
|
||||
else:
|
||||
state_dict_sbd = sbd.state_dict()
|
||||
torch.save(
|
||||
{
|
||||
"combd": state_dict_combd,
|
||||
"sbd": state_dict_sbd,
|
||||
"iteration": iteration,
|
||||
"optimizer": optimizer.state_dict(),
|
||||
"learning_rate": learning_rate,
|
||||
},
|
||||
checkpoint_path,
|
||||
)
|
||||
|
||||
|
||||
def summarize(
|
||||
writer,
|
||||
global_step,
|
||||
scalars={},
|
||||
histograms={},
|
||||
images={},
|
||||
audios={},
|
||||
audio_sampling_rate=22050,
|
||||
):
|
||||
for k, v in scalars.items():
|
||||
writer.add_scalar(k, v, global_step)
|
||||
for k, v in histograms.items():
|
||||
writer.add_histogram(k, v, global_step)
|
||||
for k, v in images.items():
|
||||
writer.add_image(k, v, global_step, dataformats="HWC")
|
||||
for k, v in audios.items():
|
||||
writer.add_audio(k, v, global_step, audio_sampling_rate)
|
||||
|
||||
|
||||
def latest_checkpoint_path(dir_path, regex="G_*.pth"):
|
||||
f_list = glob.glob(os.path.join(dir_path, regex))
|
||||
f_list.sort(key=lambda f: int("".join(filter(str.isdigit, f))))
|
||||
x = f_list[-1]
|
||||
logger.debug(x)
|
||||
return x
|
||||
|
||||
|
||||
def figure_to_rgb_array(fig):
|
||||
import numpy as np
|
||||
|
||||
fig.canvas.draw()
|
||||
if hasattr(fig.canvas, "buffer_rgba"):
|
||||
return np.asarray(fig.canvas.buffer_rgba())[..., :3].copy()
|
||||
width, height = fig.canvas.get_width_height()
|
||||
return np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8).reshape(
|
||||
height, width, 3
|
||||
)
|
||||
|
||||
|
||||
def plot_spectrogram_to_numpy(spectrogram):
|
||||
global MATPLOTLIB_FLAG
|
||||
if not MATPLOTLIB_FLAG:
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
MATPLOTLIB_FLAG = True
|
||||
mpl_logger = logging.getLogger("matplotlib")
|
||||
mpl_logger.setLevel(logging.WARNING)
|
||||
import matplotlib.pylab as plt
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 2))
|
||||
im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none")
|
||||
plt.colorbar(im, ax=ax)
|
||||
plt.xlabel("Frames")
|
||||
plt.ylabel("Channels")
|
||||
plt.tight_layout()
|
||||
|
||||
data = figure_to_rgb_array(fig)
|
||||
plt.close()
|
||||
return data
|
||||
|
||||
|
||||
def plot_alignment_to_numpy(alignment, info=None):
|
||||
global MATPLOTLIB_FLAG
|
||||
if not MATPLOTLIB_FLAG:
|
||||
import matplotlib
|
||||
|
||||
matplotlib.use("Agg")
|
||||
MATPLOTLIB_FLAG = True
|
||||
mpl_logger = logging.getLogger("matplotlib")
|
||||
mpl_logger.setLevel(logging.WARNING)
|
||||
import matplotlib.pylab as plt
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 4))
|
||||
im = ax.imshow(
|
||||
alignment.transpose(), aspect="auto", origin="lower", interpolation="none"
|
||||
)
|
||||
fig.colorbar(im, ax=ax)
|
||||
xlabel = "Decoder timestep"
|
||||
if info is not None:
|
||||
xlabel += "\n\n" + info
|
||||
plt.xlabel(xlabel)
|
||||
plt.ylabel("Encoder timestep")
|
||||
plt.tight_layout()
|
||||
|
||||
data = figure_to_rgb_array(fig)
|
||||
plt.close()
|
||||
return data
|
||||
|
||||
|
||||
def load_wav_to_torch(full_path):
|
||||
sampling_rate, data = read(full_path)
|
||||
return torch.FloatTensor(data.astype(np.float32)), sampling_rate
|
||||
|
||||
|
||||
def load_filepaths_and_text(filename, split="|"):
|
||||
return [line.strip().split(split) for line in read_text(filename).splitlines()]
|
||||
|
||||
|
||||
def get_hparams(init=True):
|
||||
"""
|
||||
todo:
|
||||
结尾七人组:
|
||||
保存频率、总epoch done
|
||||
bs done
|
||||
pretrainG、pretrainD done
|
||||
卡号:os.en["CUDA_VISIBLE_DEVICES"] done
|
||||
if_latest done
|
||||
模型:if_f0 done
|
||||
采样率:自动选择config done
|
||||
是否缓存数据集进GPU:if_cache_data_in_gpu done
|
||||
|
||||
-m:
|
||||
自动决定training_files路径,改掉train_nsf_load_pretrain.py里的hps.data.training_files done
|
||||
-c不要了
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-se",
|
||||
"--save_every_epoch",
|
||||
type=int,
|
||||
required=True,
|
||||
help="checkpoint save frequency (epoch)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-te", "--total_epoch", type=int, required=True, help="total_epoch"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-pg", "--pretrainG", type=str, default="", help="Pretrained Generator path"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-pd", "--pretrainD", type=str, default="", help="Pretrained Discriminator path"
|
||||
)
|
||||
parser.add_argument("-g", "--gpus", type=str, default="0", help="split by -")
|
||||
parser.add_argument(
|
||||
"-bs", "--batch_size", type=int, required=True, help="batch size"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e", "--experiment_dir", type=str, required=True, help="experiment dir"
|
||||
) # -m
|
||||
parser.add_argument(
|
||||
"-sr", "--sample_rate", type=str, required=True, help="sample rate, 32k/40k/48k"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-sw",
|
||||
"--save_every_weights",
|
||||
type=str,
|
||||
default="0",
|
||||
help="save the extracted model in weights directory when saving checkpoints",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v", "--version", type=str, required=True, help="model version"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f0",
|
||||
"--if_f0",
|
||||
type=int,
|
||||
required=True,
|
||||
help="use f0 as one of the inputs of the model, 1 or 0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--if_latest",
|
||||
type=int,
|
||||
required=True,
|
||||
help="if only save the latest G/D pth file, 1 or 0",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--if_cache_data_in_gpu",
|
||||
type=int,
|
||||
required=True,
|
||||
help="if caching the dataset in GPU memory, 1 or 0",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
name = args.experiment_dir
|
||||
experiment_dir = os.path.join("./logs", args.experiment_dir)
|
||||
|
||||
config_save_path = os.path.join(experiment_dir, "config.json")
|
||||
config = json.loads(read_text(config_save_path))
|
||||
|
||||
hparams = HParams(**config)
|
||||
hparams.model_dir = hparams.experiment_dir = experiment_dir
|
||||
hparams.save_every_epoch = args.save_every_epoch
|
||||
hparams.name = name
|
||||
hparams.total_epoch = args.total_epoch
|
||||
hparams.pretrainG = args.pretrainG
|
||||
hparams.pretrainD = args.pretrainD
|
||||
hparams.version = args.version
|
||||
hparams.gpus = args.gpus
|
||||
hparams.train.batch_size = args.batch_size
|
||||
hparams.sample_rate = args.sample_rate
|
||||
hparams.if_f0 = args.if_f0
|
||||
hparams.if_latest = args.if_latest
|
||||
hparams.save_every_weights = args.save_every_weights
|
||||
hparams.if_cache_data_in_gpu = args.if_cache_data_in_gpu
|
||||
hparams.data.training_files = "%s/filelist.txt" % experiment_dir
|
||||
return hparams
|
||||
|
||||
|
||||
def get_hparams_from_dir(model_dir):
|
||||
config_save_path = os.path.join(model_dir, "config.json")
|
||||
config = json.loads(read_text(config_save_path))
|
||||
|
||||
hparams = HParams(**config)
|
||||
hparams.model_dir = model_dir
|
||||
return hparams
|
||||
|
||||
|
||||
def get_hparams_from_file(config_path):
|
||||
config = json.loads(read_text(config_path))
|
||||
|
||||
hparams = HParams(**config)
|
||||
return hparams
|
||||
|
||||
|
||||
def check_git_hash(model_dir):
|
||||
source_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
if not os.path.exists(os.path.join(source_dir, ".git")):
|
||||
logger.warning(
|
||||
"{} is not a git repository, therefore hash value comparison will be ignored.".format(
|
||||
source_dir
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
cur_hash = subprocess.getoutput("git rev-parse HEAD")
|
||||
|
||||
path = os.path.join(model_dir, "githash")
|
||||
if os.path.exists(path):
|
||||
saved_hash = read_text(path)
|
||||
if saved_hash != cur_hash:
|
||||
logger.warning(
|
||||
"git hash values are different. {}(saved) != {}(current)".format(
|
||||
saved_hash[:8], cur_hash[:8]
|
||||
)
|
||||
)
|
||||
else:
|
||||
with open(path, "w", encoding="utf8") as f:
|
||||
f.write(cur_hash)
|
||||
|
||||
|
||||
def get_logger(model_dir, filename="train.log"):
|
||||
global logger
|
||||
logger = logging.getLogger(os.path.basename(model_dir))
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
formatter = logging.Formatter("%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s")
|
||||
if not os.path.exists(model_dir):
|
||||
os.makedirs(model_dir)
|
||||
h = logging.FileHandler(os.path.join(model_dir, filename), encoding="utf8")
|
||||
h.setLevel(logging.DEBUG)
|
||||
h.setFormatter(formatter)
|
||||
logger.addHandler(h)
|
||||
return logger
|
||||
|
||||
|
||||
class HParams:
|
||||
def __init__(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
if type(v) == dict:
|
||||
v = HParams(**v)
|
||||
self[k] = v
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def values(self):
|
||||
return self.__dict__.values()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return setattr(self, key, value)
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__dict__
|
||||
|
||||
def __repr__(self):
|
||||
return self.__dict__.__repr__()
|
||||
Reference in New Issue
Block a user