Files
storyteller-ml/neo-tts/tacotron2-cac/train.py
T
ZDisket 456fc04e63 Add Tacotron2-CAC
- Tacotron2 based on https://github.com/keonlee9420/Comprehensive-Tacotron2
- Removed DeepSpeaker and Tensorflow dependency
- Added ARPA function
- Replaced guided attention loss with Convolutional Attention Consistency
2023-03-16 16:45:49 -03:00

220 lines
7.5 KiB
Python

import argparse
import os
import json
import torch
import yaml
import torch.nn as nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from tqdm import tqdm
from utils.model import get_model, get_vocoder, get_param_num
from utils.tools import get_configs_of, to_device, log, synth_one_sample
from model import Tacotron2Loss
from dataset import Dataset
import logging
numba_logger = logging.getLogger('numba')
numba_logger.setLevel(logging.WARNING)
from evaluate import evaluate
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
def main(args, configs):
print("Prepare training ...")
preprocess_config, model_config, train_config = configs
# Get dataset
dataset = Dataset(
"train.txt", preprocess_config, model_config, train_config, sort=True, drop_last=True
)
batch_size = train_config["optimizer"]["batch_size"]
group_size = 4 # Set this larger than 1 to enable sorting in Dataset
assert batch_size * group_size < len(dataset)
loader = DataLoader(
dataset,
batch_size=batch_size * group_size,
shuffle=True,
collate_fn=dataset.collate_fn,
)
# Prepare model
model, optimizer = get_model(args, configs, device, train=True)
if len(args.pretrained):
print(f"Warm starting from checkpoint {args.pretrained}")
ckpt = torch.load(args.pretrained)
model.load_state_dict(ckpt["model"])
model = nn.DataParallel(model)
num_param = get_param_num(model)
Loss = Tacotron2Loss(preprocess_config, model_config, train_config).to(device)
print("Number of Tacotron2 Parameters:", num_param)
# Load vocoder
vocoder = get_vocoder(model_config, device)
# Init logger
for p in train_config["path"].values():
os.makedirs(p, exist_ok=True)
train_log_path = os.path.join(train_config["path"]["log_path"], "train")
val_log_path = os.path.join(train_config["path"]["log_path"], "val")
os.makedirs(train_log_path, exist_ok=True)
os.makedirs(val_log_path, exist_ok=True)
train_logger = SummaryWriter(train_log_path)
val_logger = SummaryWriter(val_log_path)
# Training
step = args.restore_step + 1
epoch = 1
grad_acc_step = train_config["optimizer"]["grad_acc_step"]
grad_clip_thresh = train_config["optimizer"]["grad_clip_thresh"]
total_step = train_config["step"]["total_step"]
log_step = train_config["step"]["log_step"]
save_step = train_config["step"]["save_step"]
synth_step = train_config["step"]["synth_step"]
val_step = train_config["step"]["val_step"]
normalize = preprocess_config["preprocessing"]["mel"]["normalize"]
with open(
os.path.join(preprocess_config["path"]["preprocessed_path"], "stats.json")
) as f:
stats = json.load(f)
mel_stats = stats["mel"]
outer_bar = tqdm(total=total_step, desc="Training", position=0)
outer_bar.n = args.restore_step
outer_bar.update()
while True:
inner_bar = tqdm(total=len(loader), desc="Epoch {}".format(epoch), position=1)
for batchs in loader:
for batch in batchs:
batch = to_device(batch, device, mel_stats if normalize else None)
# Forward
output = model(*(batch[2:]))
# Cal Loss
losses = Loss(batch, output)
total_loss = losses[0]
# Backward
total_loss = total_loss / grad_acc_step
total_loss.backward()
if step % grad_acc_step == 0:
# Clipping gradients to avoid gradient explosion
grad_norm = nn.utils.clip_grad_norm_(model.parameters(), grad_clip_thresh)
# Update weights
optimizer.step_and_update_lr()
optimizer.zero_grad()
if step % log_step == 0:
losses = [l.item() for l in losses]
message1 = "Step {}/{}, ".format(step, total_step)
message2 = "Total Loss: {:.4f}, Mel Loss: {:.4f}, Gate Loss: {:.4f}, Forward Sum Loss: {:.4f}, Guided Attention Loss: {:.4f}".format(
*losses
)
with open(os.path.join(train_log_path, "log.txt"), "a") as f:
f.write(message1 + message2 + "\n")
outer_bar.write(message1 + message2)
log(train_logger, step, losses=losses, grad_norm=grad_norm)
if step % synth_step == 0:
fig, gate_fig, wav_reconstruction, wav_prediction, tag = synth_one_sample(
batch,
output,
vocoder,
mel_stats,
model_config,
preprocess_config,
step,
)
log(
train_logger,
fig=fig,
tag="Training/step_{}_{}".format(step, tag),
)
log(
train_logger,
step=step,
fig=gate_fig,
tag="Gates/training",
)
sampling_rate = preprocess_config["preprocessing"]["audio"][
"sampling_rate"
]
log(
train_logger,
audio=wav_reconstruction,
sampling_rate=sampling_rate,
tag="Training/step_{}_{}_reconstructed".format(step, tag),
)
log(
train_logger,
audio=wav_prediction,
sampling_rate=sampling_rate,
tag="Training/step_{}_{}_synthesized".format(step, tag),
)
if step % val_step == 0:
model.eval()
message = evaluate(model, step, configs, mel_stats, val_logger, vocoder, len(losses))
with open(os.path.join(val_log_path, "log.txt"), "a") as f:
f.write(message + "\n")
outer_bar.write(message)
model.train()
if step % save_step == 0:
torch.save(
{
"model": model.module.state_dict(),
"optimizer": optimizer._optimizer.state_dict(),
},
os.path.join(
train_config["path"]["ckpt_path"],
"{}.pth.tar".format(step),
),
)
if step == total_step:
quit()
step += 1
outer_bar.update(1)
inner_bar.update(1)
epoch += 1
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--restore_step", type=int, default=0)
parser.add_argument(
"--dataset",
type=str,
required=True,
help="name of dataset",
)
parser.add_argument(
"--pretrained",
type=str,
required=False,
help="Path to pretrained checkpoint file to finetune from",
default="",
)
args = parser.parse_args()
# Read Config
configs = get_configs_of(args.dataset)
main(args, configs)