Files
storyteller-ml/voice_conversion/softvc/hifigan/train.py
T
2022-12-07 19:59:01 -05:00

348 lines
11 KiB
Python

import argparse
import logging
from pathlib import Path
import torch
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
import torch.distributed as dist
from torch.utils.data.distributed import DistributedSampler
import torch.multiprocessing as mp
from torch.nn.parallel import DistributedDataParallel as DDP
from hifigan.generator import HifiganGenerator
from hifigan.discriminator import (
HifiganDiscriminator,
feature_loss,
discriminator_loss,
generator_loss,
)
from hifigan.dataset import MelDataset, LogMelSpectrogram
from hifigan.utils import load_checkpoint, save_checkpoint, plot_spectrogram
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
BATCH_SIZE = 8
SEGMENT_LENGTH = 8320
HOP_LENGTH = 160
SAMPLE_RATE = 16000
BASE_LEARNING_RATE = 2e-4
FINETUNE_LEARNING_RATE = 1e-4
BETAS = (0.8, 0.99)
LEARNING_RATE_DECAY = 0.999
WEIGHT_DECAY = 1e-5
EPOCHS = 3100
LOG_INTERVAL = 5
VALIDATION_INTERVAL = 1000
NUM_GENERATED_EXAMPLES = 10
CHECKPOINT_INTERVAL = 5000
def train_model(rank, world_size, args):
dist.init_process_group(
"nccl",
rank=rank,
world_size=world_size,
init_method="tcp://localhost:54321",
)
log_dir = args.checkpoint_dir / "logs"
log_dir.mkdir(exist_ok=True, parents=True)
if rank == 0:
logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(log_dir / f"{args.checkpoint_dir.stem}.log")
handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] %(message)s", datefmt="%m/%d/%Y %I:%M:%S"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
else:
logger.setLevel(logging.ERROR)
writer = SummaryWriter(log_dir) if rank == 0 else None
generator = HifiganGenerator().to(rank)
discriminator = HifiganDiscriminator().to(rank)
generator = DDP(generator, device_ids=[rank])
discriminator = DDP(discriminator, device_ids=[rank])
optimizer_generator = optim.AdamW(
generator.parameters(),
lr=BASE_LEARNING_RATE if not args.finetune else FINETUNE_LEARNING_RATE,
betas=BETAS,
weight_decay=WEIGHT_DECAY,
)
optimizer_discriminator = optim.AdamW(
discriminator.parameters(),
lr=BASE_LEARNING_RATE if not args.finetune else FINETUNE_LEARNING_RATE,
betas=BETAS,
weight_decay=WEIGHT_DECAY,
)
scheduler_generator = optim.lr_scheduler.ExponentialLR(
optimizer_generator, gamma=LEARNING_RATE_DECAY
)
scheduler_discriminator = optim.lr_scheduler.ExponentialLR(
optimizer_discriminator, gamma=LEARNING_RATE_DECAY
)
train_dataset = MelDataset(
root=args.dataset_dir,
segment_length=SEGMENT_LENGTH,
sample_rate=SAMPLE_RATE,
hop_length=HOP_LENGTH,
train=True,
finetune=args.finetune,
)
train_sampler = DistributedSampler(train_dataset, drop_last=True)
train_loader = DataLoader(
train_dataset,
batch_size=BATCH_SIZE,
sampler=train_sampler,
num_workers=8,
pin_memory=True,
shuffle=False,
drop_last=True,
)
validation_dataset = MelDataset(
root=args.dataset_dir,
segment_length=SEGMENT_LENGTH,
sample_rate=SAMPLE_RATE,
hop_length=HOP_LENGTH,
train=False,
finetune=args.finetune,
)
validation_loader = DataLoader(
validation_dataset,
batch_size=1,
shuffle=False,
num_workers=8,
pin_memory=True,
)
melspectrogram = LogMelSpectrogram().to(rank)
if args.resume is not None:
global_step, best_loss = load_checkpoint(
load_path=args.resume,
generator=generator,
discriminator=discriminator,
optimizer_generator=optimizer_generator,
optimizer_discriminator=optimizer_discriminator,
scheduler_generator=scheduler_generator,
scheduler_discriminator=scheduler_discriminator,
rank=rank,
logger=logger,
finetune=args.finetune,
)
else:
global_step, best_loss = 0, float("inf")
if args.finetune:
global_step, best_loss = 0, float("inf")
n_epochs = EPOCHS
start_epoch = global_step // len(train_loader) + 1
logger.info("**" * 40)
logger.info(f"batch size: {BATCH_SIZE}")
logger.info(f"iterations per epoch: {len(train_loader)}")
logger.info(f"total of epochs: {n_epochs}")
logger.info(f"started at epoch: {start_epoch}")
logger.info("**" * 40 + "\n")
for epoch in range(start_epoch, n_epochs + 1):
train_sampler.set_epoch(epoch)
generator.train()
discriminator.train()
average_loss_mel = average_loss_discriminator = average_loss_generator = 0
for i, (wavs, mels, tgts) in enumerate(train_loader, 1):
wavs, mels, tgts = wavs.to(rank), mels.to(rank), tgts.to(rank)
# Discriminator
optimizer_discriminator.zero_grad()
wavs_ = generator(mels.squeeze(1))
mels_ = melspectrogram(wavs_)
scores, _ = discriminator(wavs)
scores_, _ = discriminator(wavs_.detach())
loss_discriminator, _, _ = discriminator_loss(scores, scores_)
loss_discriminator.backward()
optimizer_discriminator.step()
# Generator
optimizer_generator.zero_grad()
scores, features = discriminator(wavs)
scores_, features_ = discriminator(wavs_)
loss_mel = F.l1_loss(mels_, tgts)
loss_features = feature_loss(features, features_)
loss_generator_adversarial, _ = generator_loss(scores_)
loss_generator = 45 * loss_mel + loss_features + loss_generator_adversarial
loss_generator.backward()
optimizer_generator.step()
global_step += 1
average_loss_mel += (loss_mel.item() - average_loss_mel) / i
average_loss_discriminator += (
loss_discriminator.item() - average_loss_discriminator
) / i
average_loss_generator += (
loss_generator.item() - average_loss_generator
) / i
if rank == 0:
if global_step % LOG_INTERVAL == 0:
writer.add_scalar(
"train/loss_mel",
loss_mel.item(),
global_step,
)
writer.add_scalar(
"train/loss_generator",
loss_generator.item(),
global_step,
)
writer.add_scalar(
"train/loss_discriminator",
loss_discriminator.item(),
global_step,
)
if global_step % VALIDATION_INTERVAL == 0:
generator.eval()
average_validation_loss = 0
for j, (wavs, mels, tgts) in enumerate(validation_loader, 1):
wavs, mels, tgts = wavs.to(rank), mels.to(rank), tgts.to(rank)
with torch.no_grad():
wavs_ = generator(mels.squeeze(1))
mels_ = melspectrogram(wavs_)
length = min(mels_.size(-1), tgts.size(-1))
loss_mel = F.l1_loss(mels_[..., :length], tgts[..., :length])
average_validation_loss += (
loss_mel.item() - average_validation_loss
) / j
if rank == 0:
if j <= NUM_GENERATED_EXAMPLES:
writer.add_audio(
f"generated/wav_{j}",
wavs_.squeeze(0),
global_step,
sample_rate=16000,
)
writer.add_figure(
f"generated/mel_{j}",
plot_spectrogram(mels_.squeeze().cpu().numpy()),
global_step,
)
generator.train()
discriminator.train()
if rank == 0:
writer.add_scalar(
"validation/mel_loss", average_validation_loss, global_step
)
logger.info(
f"valid -- epoch: {epoch}, mel loss: {average_validation_loss:.4f}"
)
new_best = best_loss > average_validation_loss
if new_best or global_step % CHECKPOINT_INTERVAL == 0:
if new_best:
logger.info("-------- new best model found!")
best_loss = average_validation_loss
if rank == 0:
save_checkpoint(
checkpoint_dir=args.checkpoint_dir,
generator=generator,
discriminator=discriminator,
optimizer_generator=optimizer_generator,
optimizer_discriminator=optimizer_discriminator,
scheduler_generator=scheduler_generator,
scheduler_discriminator=scheduler_discriminator,
step=global_step,
loss=average_validation_loss,
best=new_best,
logger=logger,
)
scheduler_discriminator.step()
scheduler_generator.step()
logger.info(
f"train -- epoch: {epoch}, mel loss: {average_loss_mel:.4f}, generator loss: {average_loss_generator:.4f}, discriminator loss: {average_loss_discriminator:.4f}"
)
dist.destroy_process_group()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Train or finetune HiFi-GAN.")
parser.add_argument(
"dataset_dir",
metavar="dataset-dir",
help="path to the preprocessed data directory",
type=Path,
)
parser.add_argument(
"checkpoint_dir",
metavar="checkpoint-dir",
help="path to the checkpoint directory",
type=Path,
)
parser.add_argument(
"--resume",
help="path to the checkpoint to resume from",
type=Path,
)
parser.add_argument(
"--finetune",
help="whether to finetune (note that a resume path must be given)",
action="store_true",
)
args = parser.parse_args()
# display training setup info
logger.info(f"PyTorch version: {torch.__version__}")
logger.info(f"CUDA version: {torch.version.cuda}")
logger.info(f"CUDNN version: {torch.backends.cudnn.version()}")
logger.info(f"CUDNN enabled: {torch.backends.cudnn.enabled}")
logger.info(f"CUDNN deterministic: {torch.backends.cudnn.deterministic}")
logger.info(f"CUDNN benchmark: {torch.backends.cudnn.benchmark}")
logger.info(f"# of GPUS: {torch.cuda.device_count()}")
# clear handlers
logger.handlers.clear()
world_size = torch.cuda.device_count()
mp.spawn(
train_model,
args=(world_size, args),
nprocs=world_size,
join=True,
)