walston's picture
Upload folder using huggingface_hub
66b8580 verified
Raw
History Blame Contribute Delete
63.1 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2019 Tomoki Hayashi
# MIT License (https://opensource.org/licenses/MIT)
"""Train Parallel WaveGAN."""
import argparse
import logging
import os
import sys
from collections import defaultdict
import matplotlib
import numpy as np
import soundfile as sf
import torch
import yaml
from tensorboardX import SummaryWriter
from torch.utils.data import DataLoader
from tqdm import tqdm
import parallel_wavegan
import parallel_wavegan.models
import parallel_wavegan.optimizers
from parallel_wavegan.datasets import (
AudioDataset,
AudioMelDataset,
AudioMelF0ExcitationDataset,
AudioMelSCPDataset,
AudioSCPDataset,
)
from parallel_wavegan.layers import PQMF
# from parallel_wavegan.layers import ISTFT
from parallel_wavegan.losses import (
DiscriminatorAdversarialLoss,
DurationPredictorLoss,
FeatureMatchLoss,
GeneratorAdversarialLoss,
MelSpectrogramLoss,
MultiResolutionSTFTLoss,
)
from parallel_wavegan.utils import read_hdf5
# set to avoid matplotlib error in CLI environment
matplotlib.use("Agg")
class Trainer(object):
"""Customized trainer module for Parallel WaveGAN training."""
def __init__(
self,
steps,
epochs,
data_loader,
sampler,
model,
criterion,
optimizer,
scheduler,
config,
device=torch.device("cpu"),
):
"""Initialize trainer.
Args:
steps (int): Initial global steps.
epochs (int): Initial global epochs.
data_loader (dict): Dict of data loaders. It must contrain "train" and "dev" loaders.
model (dict): Dict of models. It must contrain "generator" and "discriminator" models.
criterion (dict): Dict of criterions. It must contrain "stft" and "mse" criterions.
optimizer (dict): Dict of optimizers. It must contrain "generator" and "discriminator" optimizers.
scheduler (dict): Dict of schedulers. It must contrain "generator" and "discriminator" schedulers.
config (dict): Config dict loaded from yaml format configuration file.
device (torch.deive): Pytorch device instance.
"""
self.steps = steps
self.epochs = epochs
self.data_loader = data_loader
self.sampler = sampler
self.model = model
self.criterion = criterion
self.optimizer = optimizer
self.scheduler = scheduler
self.config = config
self.device = device
self.writer = SummaryWriter(config["outdir"])
self.finish_train = False
self.total_train_loss = defaultdict(float)
self.total_eval_loss = defaultdict(float)
self.is_vq = "VQVAE" in config.get("generator_type", "ParallelWaveGANGenerator")
self.use_duration_prediction = "Duration" in config.get(
"generator_type", "ParallelWaveGANGenerator"
)
def run(self):
"""Run training."""
self.tqdm = tqdm(
initial=self.steps, total=self.config["train_max_steps"], desc="[train]"
)
while True:
# train one epoch
self._train_epoch()
# check whether training is finished
if self.finish_train:
break
self.tqdm.close()
logging.info("Finished training.")
def save_checkpoint(self, checkpoint_path):
"""Save checkpoint.
Args:
checkpoint_path (str): Checkpoint path to be saved.
"""
state_dict = {
"optimizer": {
"generator": self.optimizer["generator"].state_dict(),
"discriminator": self.optimizer["discriminator"].state_dict(),
},
"scheduler": {
"generator": self.scheduler["generator"].state_dict(),
"discriminator": self.scheduler["discriminator"].state_dict(),
},
"steps": self.steps,
"epochs": self.epochs,
}
if self.config["distributed"]:
state_dict["model"] = {
"generator": self.model["generator"].module.state_dict(),
"discriminator": self.model["discriminator"].module.state_dict(),
}
else:
state_dict["model"] = {
"generator": self.model["generator"].state_dict(),
"discriminator": self.model["discriminator"].state_dict(),
}
if not os.path.exists(os.path.dirname(checkpoint_path)):
os.makedirs(os.path.dirname(checkpoint_path))
torch.save(state_dict, checkpoint_path)
def load_checkpoint(self, checkpoint_path, load_only_params=False):
"""Load checkpoint.
Args:
checkpoint_path (str): Checkpoint path to be loaded.
load_only_params (bool): Whether to load only model parameters.
"""
state_dict = torch.load(checkpoint_path, map_location="cpu")
if self.config["distributed"]:
self.model["generator"].module.load_state_dict(
state_dict["model"]["generator"],
)
self.model["discriminator"].module.load_state_dict(
state_dict["model"]["discriminator"],
strict=False,
)
else:
self.model["generator"].load_state_dict(
state_dict["model"]["generator"],
)
self.model["discriminator"].load_state_dict(
state_dict["model"]["discriminator"],
strict=False,
)
if not load_only_params:
self.steps = state_dict["steps"]
self.epochs = state_dict["epochs"]
self.optimizer["generator"].load_state_dict(
state_dict["optimizer"]["generator"]
)
self.optimizer["discriminator"].load_state_dict(
state_dict["optimizer"]["discriminator"]
)
self.scheduler["generator"].load_state_dict(
state_dict["scheduler"]["generator"]
)
self.scheduler["discriminator"].load_state_dict(
state_dict["scheduler"]["discriminator"]
)
def _train_step(self, batch):
"""Train model one step."""
# parse batch and send to device
if self.use_duration_prediction:
x, y, ds = self._parse_batch(batch)
else:
x, y = self._parse_batch(batch)
#######################
# Generator #
#######################
if self.steps > self.config.get("generator_train_start_steps", 0):
# initialize
gen_loss = 0.0
if self.is_vq:
# vq case
if self.config["generator_params"]["in_channels"] == 1:
y_, z_e, z_q = self.model["generator"](y, *x)
else:
y_mb = self.criterion["pqmf"].analysis(y)
y_, z_e, z_q = self.model["generator"](y_mb, *x)
quantize_loss = self.criterion["mse"](z_q, z_e.detach())
commit_loss = self.criterion["mse"](z_e, z_q.detach())
self.total_train_loss["train/quantization_loss"] += quantize_loss.item()
self.total_train_loss["train/commitment_loss"] += commit_loss.item()
gen_loss += quantize_loss + self.config["lambda_commit"] * commit_loss
elif self.use_duration_prediction:
assert ds is not None
y_, ds_ = self.model["generator"](x, ds)
duration_loss = self.criterion["duration"](ds_, ds)
self.total_train_loss["train/duration_loss"] += duration_loss.item()
gen_loss += duration_loss
else:
y_ = self.model["generator"](*x)
# print('generator output y_: (out_channels=4)', y_.shape)
# reconstruct the signal from multi-band signal
if self.config["generator_params"]["out_channels"] > 1:
y_mb_ = y_
y_ = self.criterion["pqmf"].synthesis(y_mb_)
# print('y_mb_ (y_) before pqmf, and y_ after pqmf:', y_mb_.shape, y_.shape)
# multi-resolution sfft loss
if self.config["use_stft_loss"]:
# print("use_stft_loss: y_.shape, y.shape:", y_.shape, y.shape)
sc_loss, mag_loss = self.criterion["stft"](y_, y)
gen_loss += sc_loss + mag_loss
self.total_train_loss[
"train/spectral_convergence_loss"
] += sc_loss.item()
self.total_train_loss[
"train/log_stft_magnitude_loss"
] += mag_loss.item()
# subband multi-resolution stft loss
if self.config["use_subband_stft_loss"]:
gen_loss *= 0.5 # for balancing with subband stft loss
if not self.is_vq:
y_mb = self.criterion["pqmf"].analysis(y)
# print("use_subband_stft_loss: y_mb_.shape, y_mb.shape:", y_mb_.shape, y_mb.shape)
sub_sc_loss, sub_mag_loss = self.criterion["sub_stft"](y_mb_, y_mb)
gen_loss += 0.5 * (sub_sc_loss + sub_mag_loss)
self.total_train_loss[
"train/sub_spectral_convergence_loss"
] += sub_sc_loss.item()
self.total_train_loss[
"train/sub_log_stft_magnitude_loss"
] += sub_mag_loss.item()
# mel spectrogram loss
if self.config["use_mel_loss"]:
mel_loss = self.criterion["mel"](y_, y)
gen_loss += mel_loss
self.total_train_loss["train/mel_loss"] += mel_loss.item()
# weighting aux loss
gen_loss *= self.config.get("lambda_aux", 1.0)
# adversarial loss
if self.steps > self.config["discriminator_train_start_steps"]:
if self.config["discriminator_type"] == "MultibandParallelWaveGANDiscriminator":
p_, p_low_, p_mid_, p_high_ = self.model["discriminator"](y_)
adv_loss = self.criterion["gen_adv"](p_)
adv_loss += self.criterion["gen_adv"](p_low_)
adv_loss += self.criterion["gen_adv"](p_mid_)
adv_loss += self.criterion["gen_adv"](p_high_)
elif self.config["discriminator_type"] == "LowbandParallelWaveGANDiscriminator":
p_, p_low_ = self.model["discriminator"](y_)
adv_loss = self.criterion["gen_adv"](p_)
adv_loss_low = self.criterion["gen_adv"](p_low_)
adv_loss += self.criterion["gen_adv"](p_low_)
self.total_train_loss["train/adversarial_loss_low"] += adv_loss_low.item()
else:
p_ = self.model["discriminator"](y_)
adv_loss = self.criterion["gen_adv"](p_)
self.total_train_loss["train/adversarial_loss"] += adv_loss.item()
# feature matching loss
if self.config["use_feat_match_loss"]:
# no need to track gradients
with torch.no_grad():
p_, p_low_ = self.model["discriminator"](y_)
p, p_low = self.model["discriminator"](y)
fm_loss = self.criterion["feat_match"](p_, p)
fm_loss_low = self.criterion["feat_match"](p_low_, p_low)
self.total_train_loss[
"train/feature_matching_loss"
] += fm_loss
self.total_train_loss[
"train/feature_matching_loss_low"
] += fm_loss_low
adv_loss += self.config["lambda_feat_match"] * fm_loss + self.config["lambda_feat_match_low"] * fm_loss_low
# add adversarial loss to generator loss
gen_loss += self.config["lambda_adv"] * adv_loss
# istft
self.total_train_loss["train/generator_loss"] += gen_loss.item()
# update generator
self.optimizer["generator"].zero_grad()
gen_loss.backward()
if self.config["generator_grad_norm"] > 0:
torch.nn.utils.clip_grad_norm_(
self.model["generator"].parameters(),
self.config["generator_grad_norm"],
)
self.optimizer["generator"].step()
self.scheduler["generator"].step()
#######################
# Discriminator #
#######################
if self.steps > self.config["discriminator_train_start_steps"]:
if self.config.get("update_prediction_after_generator_update", True):
# re-compute y_ which leads better quality
with torch.no_grad():
if self.is_vq:
if self.config["generator_params"]["in_channels"] == 1:
y_, _, _ = self.model["generator"](y, *x)
else:
y_, _, _ = self.model["generator"](y_mb, *x)
elif self.use_duration_prediction:
assert ds is not None
y_, _ = self.model["generator"](x, ds)
else:
y_ = self.model["generator"](*x)
if self.config["generator_params"]["out_channels"] > 1:
y_ = self.criterion["pqmf"].synthesis(y_)
# discriminator loss
if self.config["discriminator_type"] == "MultibandParallelWaveGANDiscriminator":
p_, p_low_, p_mid_, p_high_ = self.model["discriminator"](y_.detach())
p, p_low, p_mid, p_high = self.model["discriminator"](y)
real_loss, fake_loss = self.criterion["dis_adv"](p_, p)
real_loss_low, fake_loss_low = self.criterion["dis_adv"](p_low_, p_low)
real_loss_mid, fake_loss_mid = self.criterion["dis_adv"](p_mid_, p_mid)
real_loss_high, fake_loss_high = self.criterion["dis_adv"](p_high_, p_high)
real_loss += real_loss_low + real_loss_mid + real_loss_high
fake_loss += fake_loss_low + fake_loss_mid + fake_loss_high
elif self.config["discriminator_type"] == "LowbandParallelWaveGANDiscriminator":
p_, p_low_ = self.model["discriminator"](y_.detach())
p, p_low = self.model["discriminator"](y)
real_loss, fake_loss = self.criterion["dis_adv"](p_, p)
real_loss_low, fake_loss_low = self.criterion["dis_adv"](p_low_, p_low)
real_loss += real_loss_low
fake_loss += fake_loss_low
self.total_train_loss["train/real_loss_low"] += real_loss_low.item()
self.total_train_loss["train/fake_loss_low"] += fake_loss_low.item()
else:
p = self.model["discriminator"](y)
p_ = self.model["discriminator"](y_.detach())
real_loss, fake_loss = self.criterion["dis_adv"](p_, p)
dis_loss = real_loss + fake_loss
self.total_train_loss["train/real_loss"] += real_loss.item()
self.total_train_loss["train/fake_loss"] += fake_loss.item()
self.total_train_loss["train/discriminator_loss"] += dis_loss.item()
# update discriminator
self.optimizer["discriminator"].zero_grad()
dis_loss.backward()
if self.config["discriminator_grad_norm"] > 0:
torch.nn.utils.clip_grad_norm_(
self.model["discriminator"].parameters(),
self.config["discriminator_grad_norm"],
)
self.optimizer["discriminator"].step()
self.scheduler["discriminator"].step()
# update counts
self.steps += 1
self.tqdm.update(1)
self._check_train_finish()
def _train_epoch(self):
"""Train model one epoch."""
for train_steps_per_epoch, batch in enumerate(self.data_loader["train"], 1):
# train one step
self._train_step(batch)
# check interval
if self.config["rank"] == 0:
self._check_log_interval()
self._check_eval_interval()
self._check_save_interval()
# check whether training is finished
if self.finish_train:
return
# update
self.epochs += 1
self.train_steps_per_epoch = train_steps_per_epoch
logging.info(
f"(Steps: {self.steps}) Finished {self.epochs} epoch training "
f"({self.train_steps_per_epoch} steps per epoch)."
)
# needed for shuffle in distributed training
if self.config["distributed"]:
self.sampler["train"].set_epoch(self.epochs)
@torch.no_grad()
def _eval_step(self, batch):
"""Evaluate model one step."""
# parse batch and send to device
if self.use_duration_prediction:
x, y, ds = self._parse_batch(batch)
else:
x, y = self._parse_batch(batch)
#######################
# Generator #
#######################
if self.is_vq:
if self.config["generator_params"]["in_channels"] == 1:
y_, z_e, z_q = self.model["generator"](y, *x)
else:
y_mb = self.criterion["pqmf"].analysis(y)
y_, z_e, z_q = self.model["generator"](y_mb, *x)
quantize_loss = self.criterion["mse"](z_q, z_e.detach())
commit_loss = self.criterion["mse"](z_e, z_q.detach())
elif self.use_duration_prediction:
assert ds is not None
y_, ds_ = self.model["generator"](x, ds)
duration_loss = self.criterion["duration"](ds_, torch.log(ds))
else:
y_ = self.model["generator"](*x)
if self.config["generator_params"]["out_channels"] > 1:
y_mb_ = y_
y_ = self.criterion["pqmf"].synthesis(y_mb_)
# initialize
aux_loss = 0.0
# multi-resolution stft loss
if self.config["use_stft_loss"]:
sc_loss, mag_loss = self.criterion["stft"](y_, y)
aux_loss += sc_loss + mag_loss
self.total_eval_loss["eval/spectral_convergence_loss"] += sc_loss.item()
self.total_eval_loss["eval/log_stft_magnitude_loss"] += mag_loss.item()
# subband multi-resolution stft loss
if self.config.get("use_subband_stft_loss", False):
aux_loss *= 0.5 # for balancing with subband stft loss
if not self.is_vq:
y_mb = self.criterion["pqmf"].analysis(y)
sub_sc_loss, sub_mag_loss = self.criterion["sub_stft"](y_mb_, y_mb)
self.total_eval_loss[
"eval/sub_spectral_convergence_loss"
] += sub_sc_loss.item()
self.total_eval_loss[
"eval/sub_log_stft_magnitude_loss"
] += sub_mag_loss.item()
aux_loss += 0.5 * (sub_sc_loss + sub_mag_loss)
# mel spectrogram loss
if self.config["use_mel_loss"]:
mel_loss = self.criterion["mel"](y_, y)
aux_loss += mel_loss
self.total_eval_loss["eval/mel_loss"] += mel_loss.item()
# weighting stft loss
aux_loss *= self.config.get("lambda_aux", 1.0)
# adversarial loss
p_ = self.model["discriminator"](y_)
adv_loss = self.criterion["gen_adv"](p_)
gen_loss = aux_loss + self.config["lambda_adv"] * adv_loss
# feature matching loss
if self.config["use_feat_match_loss"]:
p = self.model["discriminator"](y)
fm_loss = self.criterion["feat_match"](p_, p)
self.total_eval_loss["eval/feature_matching_loss"] += fm_loss
gen_loss += (
self.config["lambda_adv"] * self.config["lambda_feat_match"] * fm_loss
)
#######################
# Discriminator #
#######################
p = self.model["discriminator"](y)
p_ = self.model["discriminator"](y_)
# discriminator loss
real_loss, fake_loss = self.criterion["dis_adv"](p_, p)
dis_loss = real_loss + fake_loss
# add to total eval loss
self.total_eval_loss["eval/adversarial_loss"] += adv_loss.item()
self.total_eval_loss["eval/generator_loss"] += gen_loss.item()
self.total_eval_loss["eval/real_loss"] += real_loss.item()
self.total_eval_loss["eval/fake_loss"] += fake_loss.item()
self.total_eval_loss["eval/discriminator_loss"] += dis_loss.item()
if self.is_vq:
self.total_eval_loss["eval/quantization_loss"] += quantize_loss.item()
self.total_eval_loss["eval/commitment_loss"] += commit_loss.item()
if self.use_duration_prediction:
self.total_eval_loss["eval/duration_loss"] += duration_loss.item()
def _eval_epoch(self):
"""Evaluate model one epoch."""
logging.info(f"(Steps: {self.steps}) Start evaluation.")
# change mode
for key in self.model.keys():
self.model[key].eval()
# calculate loss for each batch
for eval_steps_per_epoch, batch in enumerate(
tqdm(self.data_loader["dev"], desc="[eval]"), 1
):
# eval one step
self._eval_step(batch)
# save intermediate result
if eval_steps_per_epoch == 1:
self._genearete_and_save_intermediate_result(batch)
logging.info(
f"(Steps: {self.steps}) Finished evaluation "
f"({eval_steps_per_epoch} steps per epoch)."
)
# average loss
for key in self.total_eval_loss.keys():
self.total_eval_loss[key] /= eval_steps_per_epoch
logging.info(
f"(Steps: {self.steps}) {key} = {self.total_eval_loss[key]:.4f}."
)
# record
self._write_to_tensorboard(self.total_eval_loss)
# reset
self.total_eval_loss = defaultdict(float)
# restore mode
for key in self.model.keys():
self.model[key].train()
@torch.no_grad()
def _genearete_and_save_intermediate_result(self, batch):
"""Generate and save intermediate result."""
# delayed import to avoid error related backend error
import matplotlib.pyplot as plt
# parse batch and send to device
if self.use_duration_prediction:
x_batch, y_batch, _ = self._parse_batch(batch)
else:
x_batch, y_batch = self._parse_batch(batch)
# generate
if self.is_vq:
if self.config["generator_params"]["in_channels"] == 1:
y_batch_, _, _ = self.model["generator"](y_batch, *x_batch)
else:
y_batch_, _, _ = self.model["generator"](
self.criterion["pqmf"].analysis(y_batch), *x_batch
)
elif self.use_duration_prediction:
y_batch_, _ = self.model["generator"].synthesis(x_batch)
else:
y_batch_ = self.model["generator"](*x_batch)
if self.config["generator_params"]["out_channels"] > 1:
y_batch_ = self.criterion["pqmf"].synthesis(y_batch_)
# check directory
dirname = os.path.join(self.config["outdir"], f"predictions/{self.steps}steps")
if not os.path.exists(dirname):
os.makedirs(dirname)
for idx, (y, y_) in enumerate(zip(y_batch, y_batch_), 1):
# convert to ndarray
y, y_ = y.view(-1).cpu().numpy(), y_.view(-1).cpu().numpy()
# plot figure and save it
figname = os.path.join(dirname, f"{idx}.png")
plt.subplot(2, 1, 1)
plt.plot(y)
plt.title("groundtruth speech")
plt.subplot(2, 1, 2)
plt.plot(y_)
plt.title(f"generated speech @ {self.steps} steps")
plt.tight_layout()
plt.savefig(figname)
plt.close()
# save as wavfile
y = np.clip(y, -1, 1)
y_ = np.clip(y_, -1, 1)
sf.write(
figname.replace(".png", "_ref.wav"),
y,
self.config["sampling_rate"],
"PCM_16",
)
sf.write(
figname.replace(".png", "_gen.wav"),
y_,
self.config["sampling_rate"],
"PCM_16",
)
if idx >= self.config["num_save_intermediate_results"]:
break
def _parse_batch(self, batch):
"""Parse batch and send to the device."""
# parse batch
if self.use_duration_prediction:
inputs, targets, durations = batch
else:
inputs, targets = batch
# send inputs to device
if isinstance(inputs, torch.Tensor):
x = inputs.to(self.device)
elif isinstance(inputs, (tuple, list)):
x = [None if x is None else x.to(self.device) for x in inputs]
else:
raise ValueError(f"Not supported type ({type(inputs)}).")
# send targets to device
if isinstance(targets, torch.Tensor):
y = targets.to(self.device)
elif isinstance(targets, (tuple, list)):
y = [None if y is None else y.to(self.device) for y in targets]
else:
raise ValueError(f"Not supported type ({type(targets)}).")
if self.use_duration_prediction:
# send durations to device (for model with duration prediction only)
if isinstance(durations, torch.Tensor):
ds = durations.to(self.device)
elif isinstance(durations, (tuple, list)):
ds = [None if d is None else d.to(self.device) for d in durations]
else:
raise ValueError(f"Not supported type ({type(durations)}).")
return x, y, ds
return x, y
def _write_to_tensorboard(self, loss):
"""Write to tensorboard."""
for key, value in loss.items():
self.writer.add_scalar(key, value, self.steps)
def _check_save_interval(self):
if self.steps % self.config["save_interval_steps"] == 0:
self.save_checkpoint(
os.path.join(self.config["outdir"], f"checkpoint-{self.steps}steps.pkl")
)
logging.info(f"Successfully saved checkpoint @ {self.steps} steps.")
def _check_eval_interval(self):
if self.steps % self.config["eval_interval_steps"] == 0:
self._eval_epoch()
def _check_log_interval(self):
if self.steps % self.config["log_interval_steps"] == 0:
for key in self.total_train_loss.keys():
self.total_train_loss[key] /= self.config["log_interval_steps"]
logging.info(
f"(Steps: {self.steps}) {key} = {self.total_train_loss[key]:.4f}."
)
self._write_to_tensorboard(self.total_train_loss)
# reset
self.total_train_loss = defaultdict(float)
def _check_train_finish(self):
if self.steps >= self.config["train_max_steps"]:
self.finish_train = True
class Collater(object):
"""Customized collater for Pytorch DataLoader in training."""
def __init__(
self,
batch_max_steps=20480,
hop_size=256,
aux_context_window=2,
use_noise_input=False,
use_f0_and_excitation=False,
use_aux_input=True,
use_duration=False,
use_global_condition=False,
use_local_condition=False,
pad_value=0,
):
"""Initialize customized collater for PyTorch DataLoader.
Args:
batch_max_steps (int): The maximum length of input signal in batch.
hop_size (int): Hop size of auxiliary features.
aux_context_window (int): Context window size for auxiliary feature conv.
use_noise_input (bool): Whether to use noise input.
use_f0_and_excitation (bool): Whether to use f0 and ext. input.
use_aux_input (bool): Whether to use auxiliary input.
use_duration (bool): Whether to use duration for duration prediction.
use_global_condition (bool): Whether to use global conditioning.
use_local_condition (bool): Whether to use local conditioning.
"""
if hop_size is not None:
if batch_max_steps % hop_size != 0:
batch_max_steps += -(batch_max_steps % hop_size)
assert batch_max_steps % hop_size == 0
self.hop_size = hop_size
self.batch_max_frames = batch_max_steps // hop_size
self.batch_max_steps = batch_max_steps
self.aux_context_window = aux_context_window
self.use_noise_input = use_noise_input
self.use_f0_and_excitation = use_f0_and_excitation
self.use_aux_input = use_aux_input
self.use_duration = use_duration
self.use_global_condition = use_global_condition
self.use_local_condition = use_local_condition
self.pad_value = pad_value
if not self.use_aux_input:
assert not self.use_noise_input, "Not supported."
assert not self.use_duration, "Not supported."
if self.use_noise_input:
assert not self.use_duration, "Not supported."
if self.use_local_condition:
assert not self.use_aux_input and not self.use_duration, "Not supported."
if self.use_global_condition:
assert not self.use_aux_input and not self.use_duration, "Not supported."
# set useful values in random cutting
if self.use_aux_input or self.use_local_condition:
self.start_offset = aux_context_window
self.end_offset = -(self.batch_max_frames + aux_context_window)
self.mel_threshold = self.batch_max_frames + 2 * aux_context_window
else:
self.start_offset = 0
self.end_offset = -self.batch_max_steps
self.audio_threshold = self.batch_max_steps
def __call__(self, batch):
"""Convert into batch tensors.
Args:
batch (list): list of tuple of the pair of audio and features.
Returns:
Tuple: Tuple of Gaussian noise batch (B, 1, T) and auxiliary feature
batch (B, C, T'), where T = (T' - 2 * aux_context_window) * hop_size.
If use_noise_input = False, Gaussian noise batch is not included.
If use_aux_input = False, auxiliary feature batch is not included.
If both use_noise_input and use_aux_input to False, this tuple is
not returned.
Tensor: Target signal batch (B, 1, T).
"""
if self.use_aux_input:
#################################
# MEL2WAV CASE #
#################################
# check length
batch = [
self._adjust_length(*b) for b in batch if len(b[1]) > self.mel_threshold
]
xs, cs = [b[0] for b in batch], [b[1] for b in batch]
if self.use_f0_and_excitation:
fs, es = [b[2] for b in batch], [b[3] for b in batch]
# make batch with random cut
c_lengths = [len(c) for c in cs]
start_frames = np.array(
[
np.random.randint(self.start_offset, cl + self.end_offset)
for cl in c_lengths
]
)
x_starts = start_frames * self.hop_size
x_ends = x_starts + self.batch_max_steps
c_starts = start_frames - self.aux_context_window
c_ends = start_frames + self.batch_max_frames + self.aux_context_window
y_batch = [x[start:end] for x, start, end in zip(xs, x_starts, x_ends)]
c_batch = [c[start:end] for c, start, end in zip(cs, c_starts, c_ends)]
# convert each batch to tensor, asuume that each item in batch has the same length
y_batch, c_batch = np.array(y_batch), np.array(c_batch)
y_batch = torch.tensor(y_batch, dtype=torch.float).unsqueeze(1) # (B, 1, T)
if self.use_f0_and_excitation:
f_batch = [f[start:end] for f, start, end in zip(fs, c_starts, c_ends)]
e_batch = [e[start:end] for e, start, end in zip(es, c_starts, c_ends)]
f_batch, e_batch = np.array(f_batch), np.array(e_batch)
f_batch = torch.tensor(f_batch, dtype=torch.float).unsqueeze(
1
) # (B, 1, T')
e_batch = torch.tensor(e_batch, dtype=torch.float) # (B, 1, T', C')
e_batch = e_batch.reshape(e_batch.shape[0], 1, -1) # (B, 1, T' * C')
# duration calculation and return with duration information
if self.use_duration:
updated_c_batch, d_batch = [], []
for c in c_batch:
# NOTE(jiatong): assume 0 is the discrete symbol
# (refer to cvss_c/local/preprocess_hubert.py)
code, d = torch.unique_consecutive(
torch.tensor(c, dtype=torch.long), return_counts=True, dim=0
)
updated_c_batch.append(code)
d_batch.append(d)
c_batch = self._pad_list(updated_c_batch, self.pad_value).transpose(
2, 1
) # (B, C, T')
d_batch = self._pad_list(d_batch, 0)
return c_batch, y_batch, d_batch
# process data without duration prediction
c_batch = torch.tensor(c_batch, dtype=torch.float).transpose(
2, 1
) # (B, C, T')
input_items = (c_batch,)
if self.use_noise_input:
# make input noise signal batch tensor
z_batch = torch.randn(y_batch.size()) # (B, 1, T)
input_items = (z_batch,) + input_items
if self.use_f0_and_excitation:
input_items = input_items + (f_batch, e_batch)
return input_items, y_batch
else:
#################################
# VQ-WAV2WAV CASE #
#################################
if self.use_local_condition:
# check length
batch_idx = [
idx
for idx, b in enumerate(batch)
if len(b[1]) >= self.mel_threshold
]
# fix length
batch_ = [
self._adjust_length(batch[idx][0], batch[idx][1])
for idx in batch_idx
]
# decide random index
l_lengths = [len(b[1]) for b in batch_]
l_starts = np.array(
[
np.random.randint(self.start_offset, ll + self.end_offset)
for ll in l_lengths
]
)
l_ends = l_starts + self.batch_max_frames
y_starts = l_starts * self.hop_size
y_ends = y_starts + self.batch_max_steps
# make random batch
y_batch = [
b[0][start:end] for b, start, end in zip(batch_, y_starts, y_ends)
]
l_batch = [
b[1][start:end] for b, start, end in zip(batch_, l_starts, l_ends)
]
if self.use_global_condition:
g_batch = [batch[idx][2].reshape(1) for idx in batch_idx]
else:
# check length
if self.use_global_condition:
batch = [b for b in batch if len(b[0]) >= self.audio_threshold]
else:
batch = [(b,) for b in batch if len(b) >= self.audio_threshold]
# decide random index
y_lengths = [len(b[0]) for b in batch]
y_starts = np.array(
[
np.random.randint(self.start_offset, yl + self.end_offset)
for yl in y_lengths
]
)
y_ends = y_starts + self.batch_max_steps
# make random batch
y_batch = [
b[0][start:end] for b, start, end in zip(batch, y_starts, y_ends)
]
if self.use_global_condition:
g_batch = [b[1].reshape(1) for b in batch]
# convert each batch to tensor, asuume that each item in batch has the same length
y_batch = torch.tensor(y_batch, dtype=torch.float).unsqueeze(1) # (B, 1, T)
if self.use_local_condition:
l_batch = torch.tensor(l_batch, dtype=torch.float).transpose(
2, 1
) # (B, C' T')
else:
l_batch = None
if self.use_global_condition:
g_batch = torch.tensor(g_batch, dtype=torch.long).view(-1) # (B,)
else:
g_batch = None
# NOTE(kan-bayashi): Always return "l" and "g" since VQ-VAE can accept None
return (l_batch, g_batch), y_batch
def _adjust_length(self, x, c, f0=None, excitation=None):
"""Adjust the audio and feature lengths.
Note:
Basically we assume that the length of x and c are adjusted
through preprocessing stage, but if we use other library processed
features, this process will be needed.
"""
if len(x) < len(c) * self.hop_size:
x = np.pad(x, (0, len(c) * self.hop_size - len(x)), mode="edge")
# check the legnth is valid
assert len(x) == len(c) * self.hop_size
if f0 is not None and excitation is not None:
return x, c, f0, excitation
else:
return x, c
def _pad_list(self, xs, pad_value):
"""Perform padding for the list of tensors.
Args:
xs (List): List of Tensors [(T_1, `*`), (T_2, `*`), ..., (T_B, `*`)].
pad_value (float): Value for padding.
Returns:
Tensor: Padded tensor (B, Tmax, `*`).
Examples:
>>> x = [torch.ones(4), torch.ones(2), torch.ones(1)]
>>> x
[tensor([1., 1., 1., 1.]), tensor([1., 1.]), tensor([1.])]
>>> pad_list(x, 0)
tensor([[1., 1., 1., 1.],
[1., 1., 0., 0.],
[1., 0., 0., 0.]])
"""
n_batch = len(xs)
max_len = max(x.size(0) for x in xs)
pad = xs[0].new(n_batch, max_len, *xs[0].size()[1:]).fill_(pad_value)
for i in range(n_batch):
pad[i, : xs[i].size(0)] = xs[i]
return pad
def main():
"""Run training process."""
parser = argparse.ArgumentParser(
description=(
"Train Parallel WaveGAN (See detail in parallel_wavegan/bin/train.py)."
)
)
parser.add_argument(
"--train-wav-scp",
default=None,
type=str,
help=(
"kaldi-style wav.scp file for training. "
"you need to specify either train-*-scp or train-dumpdir."
),
)
parser.add_argument(
"--train-feats-scp",
default=None,
type=str,
help=(
"kaldi-style feats.scp file for training. "
"you need to specify either train-*-scp or train-dumpdir."
),
)
parser.add_argument(
"--train-segments",
default=None,
type=str,
help="kaldi-style segments file for training.",
)
parser.add_argument(
"--train-dumpdir",
default=None,
type=str,
help=(
"directory including training data. "
"you need to specify either train-*-scp or train-dumpdir."
),
)
parser.add_argument(
"--dev-wav-scp",
default=None,
type=str,
help=(
"kaldi-style wav.scp file for validation. "
"you need to specify either dev-*-scp or dev-dumpdir."
),
)
parser.add_argument(
"--dev-feats-scp",
default=None,
type=str,
help=(
"kaldi-style feats.scp file for vaidation. "
"you need to specify either dev-*-scp or dev-dumpdir."
),
)
parser.add_argument(
"--dev-segments",
default=None,
type=str,
help="kaldi-style segments file for validation.",
)
parser.add_argument(
"--dev-dumpdir",
default=None,
type=str,
help=(
"directory including development data. "
"you need to specify either dev-*-scp or dev-dumpdir."
),
)
parser.add_argument(
"--outdir",
type=str,
required=True,
help="directory to save checkpoints.",
)
parser.add_argument(
"--config",
type=str,
required=True,
help="yaml format configuration file.",
)
parser.add_argument(
"--pretrain",
default="",
type=str,
nargs="?",
help='checkpoint file path to load pretrained params. (default="")',
)
parser.add_argument(
"--resume",
default="",
type=str,
nargs="?",
help='checkpoint file path to resume training. (default="")',
)
parser.add_argument(
"--verbose",
type=int,
default=1,
help="logging level. higher is more logging. (default=1)",
)
parser.add_argument(
"--rank",
"--local_rank",
default=0,
type=int,
help="rank for distributed training. no need to explictly specify.",
)
args = parser.parse_args()
args.distributed = False
if not torch.cuda.is_available():
device = torch.device("cpu")
else:
device = torch.device("cuda")
# effective when using fixed size inputs
# see https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936
torch.backends.cudnn.benchmark = True
torch.cuda.set_device(args.rank)
# setup for distributed training
# see example: https://github.com/NVIDIA/apex/tree/master/examples/simple/distributed
if "WORLD_SIZE" in os.environ:
args.world_size = int(os.environ["WORLD_SIZE"])
args.distributed = args.world_size > 1
if args.distributed:
torch.distributed.init_process_group(backend="nccl", init_method="env://")
# suppress logging for distributed training
if args.rank != 0:
sys.stdout = open(os.devnull, "w")
# set logger
if args.verbose > 1:
logging.basicConfig(
level=logging.DEBUG,
stream=sys.stdout,
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
)
elif args.verbose > 0:
logging.basicConfig(
level=logging.INFO,
stream=sys.stdout,
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
)
else:
logging.basicConfig(
level=logging.WARN,
stream=sys.stdout,
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
)
logging.warning("Skip DEBUG/INFO messages")
# check directory existence
if not os.path.exists(args.outdir):
os.makedirs(args.outdir)
# check arguments
if (args.train_feats_scp is not None and args.train_dumpdir is not None) or (
args.train_feats_scp is None and args.train_dumpdir is None
):
raise ValueError("Please specify either --train-dumpdir or --train-*-scp.")
if (args.dev_feats_scp is not None and args.dev_dumpdir is not None) or (
args.dev_feats_scp is None and args.dev_dumpdir is None
):
raise ValueError("Please specify either --dev-dumpdir or --dev-*-scp.")
# load and save config
with open(args.config) as f:
config = yaml.load(f, Loader=yaml.Loader)
config.update(vars(args))
config["version"] = parallel_wavegan.__version__ # add version info
with open(os.path.join(args.outdir, "config.yml"), "w") as f:
yaml.dump(config, f, Dumper=yaml.Dumper)
for key, value in config.items():
logging.info(f"{key} = {value}")
# get configuration
generator_type = config.get("generator_type", "ParallelWaveGANGenerator")
use_aux_input = "VQVAE" not in generator_type
use_noise_input = (
"ParallelWaveGAN" in generator_type and "VQVAE" not in generator_type
)
use_duration = "Duration" in generator_type
use_local_condition = config.get("use_local_condition", False)
use_global_condition = config.get("use_global_condition", False)
use_f0_and_excitation = generator_type == "UHiFiGANGenerator"
# setup query and load function
if args.train_wav_scp is None or args.dev_wav_scp is None:
local_query = None
local_load_fn = None
global_query = None
global_load_fn = None
if config["format"] == "hdf5":
audio_query, mel_query = "*.h5", "*.h5"
audio_load_fn = lambda x: read_hdf5(x, "wave") # NOQA
mel_load_fn = lambda x: read_hdf5(x, "feats") # NOQA
if use_f0_and_excitation:
f0_query, excitation_query = "*.h5", "*.h5"
f0_load_fn = lambda x: read_hdf5(x, "f0") # NOQA
excitation_load_fn = lambda x: read_hdf5(x, "excitation") # NOQA
if use_local_condition:
local_query = "*.h5"
local_load_fn = lambda x: read_hdf5(x, "local") # NOQA
if use_global_condition:
global_query = "*.h5"
global_load_fn = lambda x: read_hdf5(x, "global") # NOQA
elif config["format"] == "npy":
audio_query, mel_query = "*-wave.npy", "*-feats.npy"
audio_load_fn = np.load
mel_load_fn = np.load
if use_f0_and_excitation:
f0_query, excitation_query = "*-f0.npy", "*-excitation.npy"
f0_load_fn = np.load
excitation_load_fn = np.load
if use_local_condition:
local_query = "*-local.npy"
local_load_fn = np.load
if use_global_condition:
global_query = "*-global.npy"
global_load_fn = np.load
else:
raise ValueError("support only hdf5 or npy format.")
# setup length threshold
if config["remove_short_samples"]:
audio_length_threshold = config["batch_max_steps"]
mel_length_threshold = config["batch_max_steps"] // config[
"hop_size"
] + 2 * config["generator_params"].get("aux_context_window", 0)
else:
mel_length_threshold = None
audio_length_threshold = None
# define dataset for training data
if args.train_dumpdir is not None:
if not use_f0_and_excitation:
if use_aux_input:
train_dataset = AudioMelDataset(
root_dir=args.train_dumpdir,
audio_query=audio_query,
audio_load_fn=audio_load_fn,
mel_query=mel_query,
mel_load_fn=mel_load_fn,
local_query=local_query,
local_load_fn=local_load_fn,
global_query=global_query,
global_load_fn=global_load_fn,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
train_dataset = AudioDataset(
root_dir=args.train_dumpdir,
audio_query=audio_query,
audio_load_fn=audio_load_fn,
local_query=local_query,
local_load_fn=local_load_fn,
global_query=global_query,
global_load_fn=global_load_fn,
audio_length_threshold=audio_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
train_dataset = AudioMelF0ExcitationDataset(
root_dir=args.train_dumpdir,
audio_query=audio_query,
mel_query=mel_query,
f0_query=f0_query,
excitation_query=excitation_query,
audio_load_fn=audio_load_fn,
mel_load_fn=mel_load_fn,
f0_load_fn=f0_load_fn,
excitation_load_fn=excitation_load_fn,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
if use_f0_and_excitation:
raise NotImplementedError(
"SCP format is not supported for f0 and excitation."
)
if use_local_condition:
raise NotImplementedError("Not supported.")
if use_global_condition:
raise NotImplementedError("Not supported.")
if use_aux_input:
train_dataset = AudioMelSCPDataset(
wav_scp=args.train_wav_scp,
feats_scp=args.train_feats_scp,
segments=args.train_segments,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
train_dataset = AudioSCPDataset(
wav_scp=args.train_wav_scp,
segments=args.train_segments,
audio_length_threshold=audio_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
# define dataset for validation
if args.dev_dumpdir is not None:
if not use_f0_and_excitation:
if use_aux_input:
dev_dataset = AudioMelDataset(
root_dir=args.dev_dumpdir,
audio_query=audio_query,
audio_load_fn=audio_load_fn,
mel_query=mel_query,
mel_load_fn=mel_load_fn,
local_query=local_query,
local_load_fn=local_load_fn,
global_query=global_query,
global_load_fn=global_load_fn,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
dev_dataset = AudioDataset(
root_dir=args.dev_dumpdir,
audio_query=audio_query,
audio_load_fn=audio_load_fn,
local_query=local_query,
local_load_fn=local_load_fn,
global_query=global_query,
global_load_fn=global_load_fn,
audio_length_threshold=audio_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
dev_dataset = AudioMelF0ExcitationDataset(
root_dir=args.dev_dumpdir,
audio_query=audio_query,
mel_query=mel_query,
f0_query=f0_query,
excitation_query=excitation_query,
audio_load_fn=audio_load_fn,
mel_load_fn=mel_load_fn,
f0_load_fn=f0_load_fn,
excitation_load_fn=excitation_load_fn,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
if use_f0_and_excitation:
raise NotImplementedError(
"SCP format is not supported for f0 and excitation."
)
if use_local_condition:
raise NotImplementedError("Not supported.")
if use_global_condition:
raise NotImplementedError("Not supported.")
if use_aux_input:
dev_dataset = AudioMelSCPDataset(
wav_scp=args.dev_wav_scp,
feats_scp=args.dev_feats_scp,
segments=args.dev_segments,
mel_length_threshold=mel_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
else:
dev_dataset = AudioSCPDataset(
wav_scp=args.dev_wav_scp,
segments=args.dev_segments,
audio_length_threshold=audio_length_threshold,
allow_cache=config.get("allow_cache", False), # keep compatibility
)
# store into dataset dict
dataset = {
"train": train_dataset,
"dev": dev_dataset,
}
logging.info(f"The number of training files = {len(train_dataset)}.")
logging.info(f"The number of development files = {len(dev_dataset)}.")
# get data loader
collater = Collater(
batch_max_steps=config["batch_max_steps"],
hop_size=config.get("hop_size", None),
aux_context_window=config["generator_params"].get("aux_context_window", 0),
use_f0_and_excitation=use_f0_and_excitation,
use_noise_input=use_noise_input,
use_aux_input=use_aux_input,
use_duration=use_duration,
use_global_condition=use_global_condition,
use_local_condition=use_local_condition,
pad_value=config["generator_params"].get(
"num_embs", 0
), # assume 0-based discrete symbol
)
sampler = {"train": None, "dev": None}
if args.distributed:
# setup sampler for distributed training
from torch.utils.data.distributed import DistributedSampler
sampler["train"] = DistributedSampler(
dataset=dataset["train"],
num_replicas=args.world_size,
rank=args.rank,
shuffle=True,
)
sampler["dev"] = DistributedSampler(
dataset=dataset["dev"],
num_replicas=args.world_size,
rank=args.rank,
shuffle=False,
)
data_loader = {
"train": DataLoader(
dataset=dataset["train"],
shuffle=False if args.distributed else True,
collate_fn=collater,
batch_size=config["batch_size"],
num_workers=config["num_workers"],
sampler=sampler["train"],
pin_memory=config["pin_memory"],
),
"dev": DataLoader(
dataset=dataset["dev"],
shuffle=False if args.distributed else True,
collate_fn=collater,
batch_size=config["batch_size"],
num_workers=config["num_workers"],
sampler=sampler["dev"],
pin_memory=config["pin_memory"],
),
}
# define models
generator_class = getattr(
parallel_wavegan.models,
# keep compatibility
config.get("generator_type", "ParallelWaveGANGenerator"),
)
discriminator_class = getattr(
parallel_wavegan.models,
# keep compatibility
config.get("discriminator_type", "ParallelWaveGANDiscriminator"),
)
model = {
"generator": generator_class(
**config["generator_params"],
).to(device),
"discriminator": discriminator_class(
**config["discriminator_params"],
).to(device),
}
generator_params = sum(p.numel() for p in model["generator"].parameters())
discriminator_params = sum(p.numel() for p in model["discriminator"].parameters())
logging.info("number of generator parameters:", generator_params)
logging.info("number of discriminator parameters:", discriminator_params)
# define criterions
criterion = {
"gen_adv": GeneratorAdversarialLoss(
# keep compatibility
**config.get("generator_adv_loss_params", {})
).to(device),
"dis_adv": DiscriminatorAdversarialLoss(
# keep compatibility
**config.get("discriminator_adv_loss_params", {})
).to(device),
"mse": torch.nn.MSELoss().to(device),
}
if config.get("use_stft_loss", True): # keep compatibility
config["use_stft_loss"] = True
criterion["stft"] = MultiResolutionSTFTLoss(
**config["stft_loss_params"],
).to(device)
if config.get("use_subband_stft_loss", False): # keep compatibility
assert config["generator_params"]["out_channels"] > 1
criterion["sub_stft"] = MultiResolutionSTFTLoss(
**config["subband_stft_loss_params"],
).to(device)
else:
config["use_subband_stft_loss"] = False
if config.get("use_feat_match_loss", False): # keep compatibility
criterion["feat_match"] = FeatureMatchLoss(
# keep compatibility
**config.get("feat_match_loss_params", {}),
).to(device)
else:
config["use_feat_match_loss"] = False
if config.get("use_mel_loss", False): # keep compatibility
if config.get("mel_loss_params", None) is None:
criterion["mel"] = MelSpectrogramLoss(
fs=config["sampling_rate"],
fft_size=config["fft_size"],
hop_size=config["hop_size"],
win_length=config["win_length"],
window=config["window"],
num_mels=config["num_mels"],
fmin=config["fmin"],
fmax=config["fmax"],
).to(device)
else:
criterion["mel"] = MelSpectrogramLoss(
**config["mel_loss_params"],
).to(device)
else:
config["use_mel_loss"] = False
if config.get("use_duration_loss", False): # keep compatibility
if config.get("duration_loss_params", None) is None:
criterion["duration"] = DurationPredictorLoss(
offset=config["offset"],
reduction=config["reduction"],
).to(device)
else:
criterion["duration"] = DurationPredictorLoss(
**config["duration_loss_params"],
).to(device)
else:
config["use_duration_loss"] = False
# define special module for subband processing
if config["generator_params"]["out_channels"] > 1:
criterion["pqmf"] = PQMF(
subbands=config["generator_params"]["out_channels"],
# keep compatibility
**config.get("pqmf_params", {}),
).to(device)
# if config["use_istft"]:
# criterion["istft"] = ISTFT(
# config["fft_size"], config["hop_size"], config["win_size"], device=device)
# define optimizers and schedulers
generator_optimizer_class = getattr(
parallel_wavegan.optimizers,
# keep compatibility
config.get("generator_optimizer_type", "RAdam"),
)
discriminator_optimizer_class = getattr(
parallel_wavegan.optimizers,
# keep compatibility
config.get("discriminator_optimizer_type", "RAdam"),
)
optimizer = {
"generator": generator_optimizer_class(
model["generator"].parameters(),
**config["generator_optimizer_params"],
),
"discriminator": discriminator_optimizer_class(
model["discriminator"].parameters(),
**config["discriminator_optimizer_params"],
),
}
generator_scheduler_class = getattr(
torch.optim.lr_scheduler,
# keep compatibility
config.get("generator_scheduler_type", "StepLR"),
)
discriminator_scheduler_class = getattr(
torch.optim.lr_scheduler,
# keep compatibility
config.get("discriminator_scheduler_type", "StepLR"),
)
scheduler = {
"generator": generator_scheduler_class(
optimizer=optimizer["generator"],
**config["generator_scheduler_params"],
),
"discriminator": discriminator_scheduler_class(
optimizer=optimizer["discriminator"],
**config["discriminator_scheduler_params"],
),
}
if args.distributed:
# wrap model for distributed training
# try:
# from apex.parallel import DistributedDataParallel
# except ImportError:
# raise ImportError(
# "apex is not installed. please check https://github.com/NVIDIA/apex."
# )
from torch.nn.parallel import DistributedDataParallel
model["generator"] = DistributedDataParallel(model["generator"],find_unused_parameters=True)
model["discriminator"] = DistributedDataParallel(model["discriminator"],find_unused_parameters=True)
# show settings
logging.info(model["generator"])
logging.info(model["discriminator"])
logging.info(optimizer["generator"])
logging.info(optimizer["discriminator"])
logging.info(scheduler["generator"])
logging.info(scheduler["discriminator"])
for criterion_ in criterion.values():
logging.info(criterion_)
# define trainer
trainer = Trainer(
steps=0,
epochs=0,
data_loader=data_loader,
sampler=sampler,
model=model,
criterion=criterion,
optimizer=optimizer,
scheduler=scheduler,
config=config,
device=device,
)
# load pretrained parameters from checkpoint
if len(args.pretrain) != 0:
trainer.load_checkpoint(args.pretrain, load_only_params=True)
logging.info(f"Successfully load parameters from {args.pretrain}.")
# resume from checkpoint
if len(args.resume) != 0:
trainer.load_checkpoint(args.resume)
logging.info(f"Successfully resumed from {args.resume}.")
# run training loop
try:
trainer.run()
finally:
trainer.save_checkpoint(
os.path.join(config["outdir"], f"checkpoint-{trainer.steps}steps.pkl")
)
logging.info(f"Successfully saved checkpoint @ {trainer.steps}steps.")
if __name__ == "__main__":
main()