From 8f2a815d7d20e41ae3bffa1aeecc890843d32dca Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 7 Dec 2022 19:59:01 -0500 Subject: [PATCH] Add models for voice conversion --- voice_conversion/softvc/README.md | 22 + voice_conversion/softvc/__init__.py | 0 .../softvc/acoustic-model/.gitignore | 132 +++++ .../softvc/acoustic-model/LICENSE | 21 + .../softvc/acoustic-model/README.md | 180 +++++++ .../softvc/acoustic-model/acoustic-model.png | Bin 0 -> 83890 bytes .../acoustic-model/acoustic/__init__.py | 1 + .../softvc/acoustic-model/acoustic/dataset.py | 55 ++ .../softvc/acoustic-model/acoustic/model.py | 168 ++++++ .../softvc/acoustic-model/acoustic/utils.py | 99 ++++ .../softvc/acoustic-model/generate.py | 51 ++ .../softvc/acoustic-model/hubconf.py | 3 + .../softvc/acoustic-model/mels.py | 64 +++ .../softvc/acoustic-model/train.py | 311 +++++++++++ voice_conversion/softvc/hifigan/.gitignore | 132 +++++ voice_conversion/softvc/hifigan/LICENSE | 21 + voice_conversion/softvc/hifigan/README.md | 139 +++++ voice_conversion/softvc/hifigan/generate.py | 50 ++ .../softvc/hifigan/hifigan/__init__.py | 1 + .../softvc/hifigan/hifigan/dataset.py | 126 +++++ .../softvc/hifigan/hifigan/discriminator.py | 262 +++++++++ .../softvc/hifigan/hifigan/generator.py | 282 ++++++++++ .../softvc/hifigan/hifigan/utils.py | 84 +++ voice_conversion/softvc/hifigan/hubconf.py | 3 + .../softvc/hifigan/requirements.txt | 4 + voice_conversion/softvc/hifigan/resample.py | 56 ++ voice_conversion/softvc/hifigan/train.py | 347 ++++++++++++ voice_conversion/softvc/hifigan/vocoder.png | Bin 0 -> 84132 bytes voice_conversion/softvc/hubert/.gitignore | 132 +++++ voice_conversion/softvc/hubert/LICENSE | 21 + voice_conversion/softvc/hubert/README.md | 163 ++++++ voice_conversion/softvc/hubert/cluster.py | 66 +++ .../softvc/hubert/content-encoder.png | Bin 0 -> 84584 bytes voice_conversion/softvc/hubert/encode.py | 56 ++ voice_conversion/softvc/hubert/hubconf.py | 3 + .../softvc/hubert/hubert/__init__.py | 8 + .../softvc/hubert/hubert/dataset.py | 91 ++++ .../softvc/hubert/hubert/model.py | 293 ++++++++++ .../softvc/hubert/hubert/utils.py | 58 ++ voice_conversion/softvc/hubert/train.py | 453 ++++++++++++++++ voice_conversion/softvc/softvctraceandonnx.py | 508 ++++++++++++++++++ 41 files changed, 4466 insertions(+) create mode 100644 voice_conversion/softvc/README.md create mode 100644 voice_conversion/softvc/__init__.py create mode 100644 voice_conversion/softvc/acoustic-model/.gitignore create mode 100644 voice_conversion/softvc/acoustic-model/LICENSE create mode 100644 voice_conversion/softvc/acoustic-model/README.md create mode 100644 voice_conversion/softvc/acoustic-model/acoustic-model.png create mode 100644 voice_conversion/softvc/acoustic-model/acoustic/__init__.py create mode 100644 voice_conversion/softvc/acoustic-model/acoustic/dataset.py create mode 100644 voice_conversion/softvc/acoustic-model/acoustic/model.py create mode 100644 voice_conversion/softvc/acoustic-model/acoustic/utils.py create mode 100644 voice_conversion/softvc/acoustic-model/generate.py create mode 100644 voice_conversion/softvc/acoustic-model/hubconf.py create mode 100644 voice_conversion/softvc/acoustic-model/mels.py create mode 100644 voice_conversion/softvc/acoustic-model/train.py create mode 100644 voice_conversion/softvc/hifigan/.gitignore create mode 100644 voice_conversion/softvc/hifigan/LICENSE create mode 100644 voice_conversion/softvc/hifigan/README.md create mode 100644 voice_conversion/softvc/hifigan/generate.py create mode 100644 voice_conversion/softvc/hifigan/hifigan/__init__.py create mode 100644 voice_conversion/softvc/hifigan/hifigan/dataset.py create mode 100644 voice_conversion/softvc/hifigan/hifigan/discriminator.py create mode 100644 voice_conversion/softvc/hifigan/hifigan/generator.py create mode 100644 voice_conversion/softvc/hifigan/hifigan/utils.py create mode 100644 voice_conversion/softvc/hifigan/hubconf.py create mode 100644 voice_conversion/softvc/hifigan/requirements.txt create mode 100644 voice_conversion/softvc/hifigan/resample.py create mode 100644 voice_conversion/softvc/hifigan/train.py create mode 100644 voice_conversion/softvc/hifigan/vocoder.png create mode 100644 voice_conversion/softvc/hubert/.gitignore create mode 100644 voice_conversion/softvc/hubert/LICENSE create mode 100644 voice_conversion/softvc/hubert/README.md create mode 100644 voice_conversion/softvc/hubert/cluster.py create mode 100644 voice_conversion/softvc/hubert/content-encoder.png create mode 100644 voice_conversion/softvc/hubert/encode.py create mode 100644 voice_conversion/softvc/hubert/hubconf.py create mode 100644 voice_conversion/softvc/hubert/hubert/__init__.py create mode 100644 voice_conversion/softvc/hubert/hubert/dataset.py create mode 100644 voice_conversion/softvc/hubert/hubert/model.py create mode 100644 voice_conversion/softvc/hubert/hubert/utils.py create mode 100644 voice_conversion/softvc/hubert/train.py create mode 100644 voice_conversion/softvc/softvctraceandonnx.py diff --git a/voice_conversion/softvc/README.md b/voice_conversion/softvc/README.md new file mode 100644 index 0000000..0ae7a93 --- /dev/null +++ b/voice_conversion/softvc/README.md @@ -0,0 +1,22 @@ +FakeYou SoftVC +============== + +This work is based off research by Michael and Paul, leveraging bshall's SoftVC: + +https://bshall.github.io/soft-vc/ + +These are the repos contained herein: + +* https://github.com/bshall/hubert +* https://github.com/bshall/acoustic-model +* https://github.com/bshall/hifigan + +And this is our initial notebook: + +* [Colab Notebook](https://colab.research.google.com/drive/1g0YsQQY8fzXHmZUWAMzQO60c6OUlyKBl#scrollTo=Sf_rjYw0P83I) + + + + + + diff --git a/voice_conversion/softvc/__init__.py b/voice_conversion/softvc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/voice_conversion/softvc/acoustic-model/.gitignore b/voice_conversion/softvc/acoustic-model/.gitignore new file mode 100644 index 0000000..0202868 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/.gitignore @@ -0,0 +1,132 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# VSCode project settings +.vscode + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/voice_conversion/softvc/acoustic-model/LICENSE b/voice_conversion/softvc/acoustic-model/LICENSE new file mode 100644 index 0000000..6eb2af0 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Benjamin van Niekerk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/voice_conversion/softvc/acoustic-model/README.md b/voice_conversion/softvc/acoustic-model/README.md new file mode 100644 index 0000000..fcc1daf --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/README.md @@ -0,0 +1,180 @@ +

+ + Open In Colab + +

+ +# Acoustic-Model + +Training and inference scripts for the acoustic models in [A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion](https://ieeexplore.ieee.org/abstract/document/9746484). For more details see [soft-vc](https://github.com/bshall/soft-vc). Audio samples can be found [here](https://bshall.github.io/soft-vc/). Colab demo can be found [here](https://colab.research.google.com/github/bshall/soft-vc/blob/main/soft-vc-demo.ipynb). + +
+ Soft-VC +
+
+ + Fig 1: Architecture of the voice conversion system. a) The discrete content encoder clusters audio features to produce a sequence of discrete speech units. b) The soft content encoder is trained to predict the discrete units. The acoustic model transforms the discrete/soft speech units into a target spectrogram. The vocoder converts the spectrogram into an audio waveform. + +
+ +## Example Usage + +### Programmatic Usage + +```python +import torch +import numpy as np + +# Load checkpoint (either hubert_soft or hubert_discrete) +acoustic = torch.hub.load("bshall/acoustic-model:main", "hubert_soft").cuda() + +# Load speech units +units = torch.from_numpy(np.load("path/to/units")) + +# Generate mel-spectrogram +mel = acoustic.generate(units) +``` + +### Script-Based Usage + +``` +usage: generate.py [-h] {soft,discrete} in-dir out-dir + +Generate spectrograms from input speech units (discrete or soft). + +positional arguments: + {soft,discrete} available models (HuBERT-Soft or HuBERT-Discrete) + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit +``` + +## Training + +### Step 1: Dataset Preparation + +Download and extract the [LJSpeech](https://keithito.com/LJ-Speech-Dataset/) dataset. The training script expects the following tree structure for the dataset directory: + +``` +└───wavs + ├───dev + │ ├───LJ001-0001.wav + │ ├───... + │ └───LJ050-0278.wav + └───train + ├───LJ002-0332.wav + ├───... + └───LJ047-0007.wav +``` + +The `train` and `dev` directories should contain the training and validation splits respectively. The splits used for the paper can be found [here](https://github.com/bshall/acoustic-model/releases/tag/v0.1). + +### Step 2: Extract Spectrograms + +Extract mel-spectrograms using the `mel.py` script: + +``` +usage: mels.py [-h] in-dir out-dir + +Extract mel-spectrograms for an audio dataset. + +positional arguments: + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit +``` + +for example: + +``` +python mel.py path/to/LJSpeech-1.1/wavs path/to/LJSpeech-1.1/mels +``` + +At this point the directory tree should look like: + +``` +├───mels +│ ├───... +└───wavs + ├───... +``` + +### Step 3: Extract Discrete or Soft Speech Units + +Use the HuBERT-Soft or HuBERT-Discrete content encoders to extract speech units. First clone the [content encoder repo](https://github.com/bshall/hubert) and then run `encode.py` (see the repo for details): + +``` +usage: encode.py [-h] [--extension EXTENSION] {soft,discrete} in-dir out-dir + +Encode an audio dataset. + +positional arguments: + {soft,discrete} available models (HuBERT-Soft or HuBERT-Discrete) + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit + --extension EXTENSION + extension of the audio files (defaults to .flac). +``` + +for example: + +``` +python encode.py soft path/to/LJSpeech-1.1/wavs path/to/LJSpeech-1.1/soft --extension .wav +``` + +At this point the directory tree should look like: + +``` +├───mels +│ ├───... +├───soft/discrete +│ ├───... +└───wavs + ├───... +``` + +### Step 4: Train the Acoustic-Model + +``` +usage: train.py [-h] [--resume RESUME] [--discrete] dataset-dir checkpoint-dir + +Train the acoustic model. + +positional arguments: + dataset-dir path to the data directory. + checkpoint-dir path to the checkpoint directory. + +optional arguments: + -h, --help show this help message and exit + --resume RESUME path to the checkpoint to resume from. + --discrete Use discrete units. +``` + +## Links + +- [Soft-VC repo](https://github.com/bshall/soft-vc) +- [Soft-VC paper](https://ieeexplore.ieee.org/abstract/document/9746484) +- [HuBERT content encoders](https://github.com/bshall/hubert) +- [HiFiGAN vocoder](https://github.com/bshall/hifigan) + +## Citation + +If you found this work helpful please consider citing our paper: + +``` +@inproceedings{ + soft-vc-2022, + author={van Niekerk, Benjamin and Carbonneau, Marc-André and Zaïdi, Julian and Baas, Matthew and Seuté, Hugo and Kamper, Herman}, + booktitle={ICASSP}, + title={A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion}, + year={2022} +} +``` \ No newline at end of file diff --git a/voice_conversion/softvc/acoustic-model/acoustic-model.png b/voice_conversion/softvc/acoustic-model/acoustic-model.png new file mode 100644 index 0000000000000000000000000000000000000000..41cc8227f919755262633ebfbff6d489ab6d9ec7 GIT binary patch literal 83890 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 literal 0 HcmV?d00001 diff --git a/voice_conversion/softvc/acoustic-model/acoustic/__init__.py b/voice_conversion/softvc/acoustic-model/acoustic/__init__.py new file mode 100644 index 0000000..38186d0 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/acoustic/__init__.py @@ -0,0 +1 @@ +from .model import AcousticModel, hubert_discrete, hubert_soft diff --git a/voice_conversion/softvc/acoustic-model/acoustic/dataset.py b/voice_conversion/softvc/acoustic-model/acoustic/dataset.py new file mode 100644 index 0000000..97cdaa6 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/acoustic/dataset.py @@ -0,0 +1,55 @@ +from pathlib import Path +import numpy as np + +import torch +import torch.nn.functional as F +from torch.utils.data import Dataset +from torch.nn.utils.rnn import pad_sequence + + +class MelDataset(Dataset): + def __init__(self, root: Path, train: bool = True, discrete: bool = False): + self.discrete = discrete + self.mels_dir = root / "mels" + self.units_dir = root / "discrete" if discrete else root / "soft" + + pattern = "train/**/*.npy" if train else "dev/**/*.npy" + self.metadata = [ + path.relative_to(self.mels_dir).with_suffix("") + for path in self.mels_dir.rglob(pattern) + ] + + def __len__(self): + return len(self.metadata) + + def __getitem__(self, index): + path = self.metadata[index] + mel_path = self.mels_dir / path + units_path = self.units_dir / path + + mel = np.load(mel_path.with_suffix(".npy")).T + units = np.load(units_path.with_suffix(".npy")) + + length = 2 * units.shape[0] + + mel = torch.from_numpy(mel[:length, :]) + mel = F.pad(mel, (0, 0, 1, 0)) + units = torch.from_numpy(units) + if self.discrete: + units = units.long() + return mel, units + + def pad_collate(self, batch): + mels, units = zip(*batch) + + mels, units = list(mels), list(units) + + mels_lengths = torch.tensor([x.size(0) - 1 for x in mels]) + units_lengths = torch.tensor([x.size(0) for x in units]) + + mels = pad_sequence(mels, batch_first=True) + units = pad_sequence( + units, batch_first=True, padding_value=100 if self.discrete else 0 + ) + + return mels, mels_lengths, units, units_lengths diff --git a/voice_conversion/softvc/acoustic-model/acoustic/model.py b/voice_conversion/softvc/acoustic-model/acoustic/model.py new file mode 100644 index 0000000..00b2054 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/acoustic/model.py @@ -0,0 +1,168 @@ +import torch +import torch.nn as nn +from torch.nn.modules.utils import consume_prefix_in_state_dict_if_present + +URLS = { + "hubert-discrete": "https://github.com/bshall/acoustic-model/releases/download/v0.1/hubert-discrete-d49e1c77.pt", + "hubert-soft": "https://github.com/bshall/acoustic-model/releases/download/v0.1/hubert-soft-0321fd7e.pt", +} + + +class AcousticModel(nn.Module): + def __init__(self, discrete: bool = False, upsample: bool = True): + super().__init__() + self.encoder = Encoder(discrete, upsample) + self.decoder = Decoder() + + def forward(self, x: torch.Tensor, mels: torch.Tensor) -> torch.Tensor: + x = self.encoder(x) + return self.decoder(x, mels) + + @torch.inference_mode() + def generate(self, x: torch.Tensor) -> torch.Tensor: + x = self.encoder(x) + return self.decoder.generate(x) + + +class Encoder(nn.Module): + def __init__(self, discrete: bool = False, upsample: bool = True): + super().__init__() + self.embedding = nn.Embedding(100 + 1, 256) if discrete else None + self.prenet = PreNet(256, 256, 256) + self.convs = nn.Sequential( + nn.Conv1d(256, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + nn.ConvTranspose1d(512, 512, 4, 2, 1) if upsample else nn.Identity(), + nn.Conv1d(512, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + nn.Conv1d(512, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.embedding is not None: + x = self.embedding(x) + x = self.prenet(x) + x = self.convs(x.transpose(1, 2)) + return x.transpose(1, 2) + + +class Decoder(nn.Module): + def __init__(self): + super().__init__() + self.prenet = PreNet(128, 256, 256) + self.lstm1 = nn.LSTM(512 + 256, 768, batch_first=True) + self.lstm2 = nn.LSTM(768, 768, batch_first=True) + self.lstm3 = nn.LSTM(768, 768, batch_first=True) + self.proj = nn.Linear(768, 128, bias=False) + + def forward(self, x: torch.Tensor, mels: torch.Tensor) -> torch.Tensor: + mels = self.prenet(mels) + x, _ = self.lstm1(torch.cat((x, mels), dim=-1)) + res = x + x, _ = self.lstm2(x) + x = res + x + res = x + x, _ = self.lstm3(x) + x = res + x + return self.proj(x) + + @torch.inference_mode() + def generate(self, xs: torch.Tensor) -> torch.Tensor: + m = torch.zeros(xs.size(0), 128, device=xs.device) + h1 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c1 = torch.zeros(1, xs.size(0), 768, device=xs.device) + h2 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c2 = torch.zeros(1, xs.size(0), 768, device=xs.device) + h3 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c3 = torch.zeros(1, xs.size(0), 768, device=xs.device) + + mel = [] + for x in torch.unbind(xs, dim=1): + m = self.prenet(m) + x = torch.cat((x, m), dim=1).unsqueeze(1) + x1, (h1, c1) = self.lstm1(x, (h1, c1)) + x2, (h2, c2) = self.lstm2(x1, (h2, c2)) + x = x1 + x2 + x3, (h3, c3) = self.lstm3(x, (h3, c3)) + x = x + x3 + m = self.proj(x).squeeze(1) + mel.append(m) + return torch.stack(mel, dim=1) + + +class PreNet(nn.Module): + def __init__( + self, + input_size: int, + hidden_size: int, + output_size: int, + dropout: float = 0.5, + ): + super().__init__() + self.net = nn.Sequential( + nn.Linear(input_size, hidden_size), + nn.ReLU(), + nn.Dropout(dropout), + nn.Linear(hidden_size, output_size), + nn.ReLU(), + nn.Dropout(dropout), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.net(x) + + +def _acoustic( + name: str, + discrete: bool, + upsample: bool, + pretrained: bool = True, + progress: bool = True, +) -> AcousticModel: + acoustic = AcousticModel(discrete, upsample) + if pretrained: + checkpoint = torch.hub.load_state_dict_from_url(URLS[name], progress=progress) + consume_prefix_in_state_dict_if_present(checkpoint["acoustic-model"], "module.") + acoustic.load_state_dict(checkpoint["acoustic-model"]) + acoustic.eval() + return acoustic + + +def hubert_discrete( + pretrained: bool = True, + progress: bool = True, +) -> AcousticModel: + r"""HuBERT-Discrete acoustic model from `"A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion"`. + Args: + pretrained (bool): load pretrained weights into the model + progress (bool): show progress bar when downloading model + """ + return _acoustic( + "hubert-discrete", + discrete=True, + upsample=True, + pretrained=pretrained, + progress=progress, + ) + + +def hubert_soft( + pretrained: bool = True, + progress: bool = True, +) -> AcousticModel: + r"""HuBERT-Soft acoustic model from `"A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion"`. + Args: + pretrained (bool): load pretrained weights into the model + progress (bool): show progress bar when downloading model + """ + return _acoustic( + "hubert-soft", + discrete=False, + upsample=True, + pretrained=pretrained, + progress=progress, + ) diff --git a/voice_conversion/softvc/acoustic-model/acoustic/utils.py b/voice_conversion/softvc/acoustic-model/acoustic/utils.py new file mode 100644 index 0000000..ad505fe --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/acoustic/utils.py @@ -0,0 +1,99 @@ +import torch +import torch.nn.functional as F +import matplotlib + +import torchaudio.transforms as transforms + +matplotlib.use("Agg") +import matplotlib.pylab as plt + + +class Metric: + def __init__(self): + self.steps = 0 + self.value = 0 + + def update(self, value): + self.steps += 1 + self.value += (value - self.value) / self.steps + return self.value + + def reset(self): + self.steps = 0 + self.value = 0 + + +class LogMelSpectrogram(torch.nn.Module): + def __init__(self): + super().__init__() + self.melspctrogram = transforms.MelSpectrogram( + sample_rate=16000, + n_fft=1024, + win_length=1024, + hop_length=160, + center=False, + power=1.0, + norm="slaney", + onesided=True, + n_mels=128, + mel_scale="slaney", + ) + + def forward(self, wav): + padding = (1024 - 160) // 2 + wav = F.pad(wav, (padding, padding), "reflect") + mel = self.melspctrogram(wav) + logmel = torch.log(torch.clamp(mel, min=1e-5)) + return logmel + + +def save_checkpoint( + checkpoint_dir, + acoustic, + optimizer, + step, + loss, + best, + logger, +): + state = { + "acoustic-model": acoustic.state_dict(), + "optimizer": optimizer.state_dict(), + "step": step, + "loss": loss, + } + checkpoint_dir.mkdir(exist_ok=True, parents=True) + checkpoint_path = checkpoint_dir / f"model-{step}.pt" + torch.save(state, checkpoint_path) + if best: + best_path = checkpoint_dir / "model-best.pt" + torch.save(state, best_path) + logger.info(f"Saved checkpoint: {checkpoint_path.stem}") + + +def load_checkpoint( + load_path, + acoustic, + optimizer, + rank, + logger, +): + logger.info(f"Loading checkpoint from {load_path}") + checkpoint = torch.load(load_path, map_location={"cuda:0": f"cuda:{rank}"}) + acoustic.load_state_dict(checkpoint["acoustic-model"]) + if "optimizer" in checkpoint: + optimizer.load_state_dict(checkpoint["optimizer"]) + step = checkpoint.get("step", 0) + loss = checkpoint.get("loss", float("inf")) + return step, loss + + +def plot_spectrogram(spectrogram): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig diff --git a/voice_conversion/softvc/acoustic-model/generate.py b/voice_conversion/softvc/acoustic-model/generate.py new file mode 100644 index 0000000..0696b6c --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/generate.py @@ -0,0 +1,51 @@ +import argparse + +from pathlib import Path +import numpy as np +from tqdm import tqdm + +import torch + + +def generate(args): + print("Loading acoustic model checkpoint") + acoustic = torch.hub.load("bshall/acoustic-model:main", f"hubert_{args.model}").cuda() + + print(f"Generating from {args.in_dir} -> {args.out_dir}") + for path in tqdm(list(args.in_dir.rglob("*.npy"))): + units = np.load(path) + units_dtype = torch.long if args.model == "discrete" else torch.float + units = torch.tensor(units, dtype=units_dtype).cuda() + + with torch.inference_mode(): + mel_ = acoustic.generate(units) + mel_ = mel_.transpose(1, 2) + + out_path = args.out_dir / path.relative_to(args.in_dir) + out_path.parent.mkdir(exist_ok=True, parents=True) + np.save(out_path.with_suffix(".npy"), mel_.squeeze().cpu().numpy()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate spectrograms from input speech units (discrete or soft)." + ) + parser.add_argument( + "model", + help="available models (HuBERT-Soft or HuBERT-Discrete)", + choices=["soft", "discrete"], + ) + parser.add_argument( + "in_dir", + metavar="in-dir", + help="path to the dataset directory.", + type=Path, + ) + parser.add_argument( + "out_dir", + metavar="out-dir", + help="path to the output directory.", + type=Path, + ) + args = parser.parse_args() + generate(args) diff --git a/voice_conversion/softvc/acoustic-model/hubconf.py b/voice_conversion/softvc/acoustic-model/hubconf.py new file mode 100644 index 0000000..8d3ea3a --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/hubconf.py @@ -0,0 +1,3 @@ +dependencies = ["torch", "torchaudio"] + +from acoustic import hubert_discrete, hubert_soft diff --git a/voice_conversion/softvc/acoustic-model/mels.py b/voice_conversion/softvc/acoustic-model/mels.py new file mode 100644 index 0000000..7f06cc1 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/mels.py @@ -0,0 +1,64 @@ +import argparse +from pathlib import Path +from concurrent.futures import ProcessPoolExecutor +from multiprocessing import cpu_count + +import numpy as np +from tqdm import tqdm + +import torchaudio +from torchaudio.functional import resample + +from acoustic.utils import LogMelSpectrogram + + +melspectrogram = LogMelSpectrogram() + + +def process_wav(in_path, out_path): + wav, sr = torchaudio.load(in_path) + wav = resample(wav, sr, 16000) + + logmel = melspectrogram(wav.unsqueeze(0)) + + np.save(out_path.with_suffix(".npy"), logmel.squeeze().numpy()) + return out_path, logmel.shape[-1] + + +def preprocess_dataset(args): + args.out_dir.mkdir(parents=True, exist_ok=True) + + futures = [] + executor = ProcessPoolExecutor(max_workers=cpu_count()) + print(f"Extracting features for {args.in_dir}") + for in_path in args.in_dir.rglob("*.wav"): + relative_path = in_path.relative_to(args.in_dir) + out_path = args.out_dir / relative_path.with_suffix("") + out_path.parent.mkdir(parents=True, exist_ok=True) + futures.append(executor.submit(process_wav, in_path, out_path)) + + results = [future.result() for future in tqdm(futures)] + + lengths = {path.stem: length for path, length in results} + frames = sum(lengths.values()) + frame_shift_ms = 160 / 16000 + hours = frames * frame_shift_ms / 3600 + print(f"Wrote {len(lengths)} utterances, {frames} frames ({hours:.2f} hours)") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Extract mel-spectrograms for an audio dataset.") + parser.add_argument( + "in_dir", + metavar="in-dir", + help="path to the dataset directory.", + type=Path, + ) + parser.add_argument( + "out_dir", + metavar="out-dir", + help="path to the output directory.", + type=Path, + ) + args = parser.parse_args() + preprocess_dataset(args) diff --git a/voice_conversion/softvc/acoustic-model/train.py b/voice_conversion/softvc/acoustic-model/train.py new file mode 100644 index 0000000..88972a3 --- /dev/null +++ b/voice_conversion/softvc/acoustic-model/train.py @@ -0,0 +1,311 @@ +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 acoustic import AcousticModel +from acoustic.dataset import MelDataset +from acoustic.utils import Metric, save_checkpoint, load_checkpoint, plot_spectrogram + + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) + +######################################################################################## +# Define hyperparameters for training: +######################################################################################## + +BATCH_SIZE = 32 +LEARNING_RATE = 4e-4 +BETAS = (0.8, 0.99) +WEIGHT_DECAY = 1e-5 +STEPS = 80000 +LOG_INTERVAL = 5 +VALIDATION_INTERVAL = 1000 +CHECKPOINT_INTERVAL = 1000 +BACKEND = "nccl" +INIT_METHOD = "tcp://localhost:54321" + + +def train(rank, world_size, args): + dist.init_process_group( + BACKEND, + rank=rank, + world_size=world_size, + init_method=INIT_METHOD, + ) + + #################################################################################### + # Setup logging utilities: + #################################################################################### + + log_dir = args.checkpoint_dir / "logs" + log_dir.mkdir(exist_ok=True, parents=True) + + if rank == 0: + logger.setLevel(logging.INFO) + handler = logging.FileHandler(log_dir / f"{args.checkpoint_dir.stem}.log") + handler.setLevel(logging.INFO) + 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 + + #################################################################################### + # Initialize models and optimizer + #################################################################################### + + acoustic = AcousticModel().to(rank) + + acoustic = DDP(acoustic, device_ids=[rank]) + + optimizer = optim.AdamW( + acoustic.parameters(), + lr=LEARNING_RATE, + betas=BETAS, + weight_decay=WEIGHT_DECAY, + ) + + #################################################################################### + # Initialize datasets and dataloaders + #################################################################################### + + train_dataset = MelDataset( + root=args.dataset_dir, + train=True, + discrete=args.discrete, + ) + train_sampler = DistributedSampler(train_dataset, drop_last=True) + train_loader = DataLoader( + train_dataset, + batch_size=BATCH_SIZE, + sampler=train_sampler, + collate_fn=train_dataset.pad_collate, + num_workers=8, + pin_memory=True, + shuffle=False, + drop_last=True, + ) + + validation_dataset = MelDataset( + root=args.dataset_dir, + train=False, + ) + validation_loader = DataLoader( + validation_dataset, + batch_size=1, + shuffle=False, + num_workers=8, + pin_memory=True, + ) + + #################################################################################### + # Load checkpoint if args.resume is set + #################################################################################### + + if args.resume is not None: + global_step, best_loss = load_checkpoint( + load_path=args.resume, + acoustic=acoustic, + optimizer=optimizer, + rank=rank, + logger=logger, + ) + else: + global_step, best_loss = 0, float("inf") + + # =================================================================================# + # Start training loop + # =================================================================================# + + n_epochs = STEPS // len(train_loader) + 1 + start_epoch = global_step // len(train_loader) + 1 + + logger.info("**" * 40) + 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()}") + logger.info(f"batch size: {BATCH_SIZE}") + logger.info(f"iterations per epoch: {len(train_loader)}") + logger.info(f"# of epochs: {n_epochs}") + logger.info(f"started at epoch: {start_epoch}") + logger.info("**" * 40 + "\n") + + average_loss = Metric() + epoch_loss = Metric() + + validation_loss = Metric() + + for epoch in range(start_epoch, n_epochs + 1): + train_sampler.set_epoch(epoch) + + acoustic.train() + epoch_loss.reset() + + for mels, mels_lengths, units, units_lengths in train_loader: + mels, mels_lengths = mels.to(rank), mels_lengths.to(rank) + units, units_lengths = units.to(rank), units_lengths.to(rank) + + ############################################################################ + # Compute training loss + ############################################################################ + + optimizer.zero_grad() + + mels_ = acoustic(units, mels[:, :-1, :]) + + loss = F.l1_loss(mels_, mels[:, 1:, :], reduction="none") + loss = torch.sum(loss, dim=(1, 2)) / (mels_.size(-1) * mels_lengths) + loss = torch.mean(loss) + + loss.backward() + optimizer.step() + + global_step += 1 + + ############################################################################ + # Update and log training metrics + ############################################################################ + + average_loss.update(loss.item()) + epoch_loss.update(loss.item()) + + if rank == 0 and global_step % LOG_INTERVAL == 0: + writer.add_scalar( + "train/loss", + average_loss.value, + global_step, + ) + average_loss.reset() + + # --------------------------------------------------------------------------# + # Start validation loop + # --------------------------------------------------------------------------# + + if global_step % VALIDATION_INTERVAL == 0: + acoustic.eval() + validation_loss.reset() + + for i, (mels, units) in enumerate(validation_loader, 1): + mels, units = mels.to(rank), units.to(rank) + + with torch.no_grad(): + mels_ = acoustic(units, mels[:, :-1, :]) + loss = F.l1_loss(mels_, mels[:, 1:, :]) + + #################################################################### + # Update validation metrics and log generated mels + #################################################################### + + validation_loss.update(loss.item()) + + if rank == 0: + writer.add_figure( + f"generated/mel_{i}", + plot_spectrogram( + mels_.squeeze().transpose(0, 1).cpu().numpy() + ), + global_step, + ) + + acoustic.train() + + ############################################################################ + # Log validation metrics + ############################ + + if rank == 0: + writer.add_scalar( + "validation/loss", + validation_loss.value, + global_step, + ) + logger.info( + f"valid -- epoch: {epoch}, loss: {validation_loss.value:.4f}" + ) + + new_best = best_loss > validation_loss.value + if new_best or global_step % CHECKPOINT_INTERVAL: + if new_best: + logger.info("-------- new best model found!") + best_loss = validation_loss.value + + if rank == 0: + save_checkpoint( + checkpoint_dir=args.checkpoint_dir, + acoustic=acoustic, + optimizer=optimizer, + step=global_step, + loss=validation_loss.value, + best=new_best, + logger=logger, + ) + + # -----------------------------------------------------------------------------# + # End validation loop + # -----------------------------------------------------------------------------# + + #################################################################################### + # Log training metrics + #################################################################################### + + logger.info(f"train -- epoch: {epoch}, loss: {epoch_loss.value:.4f}") + + # =================================================================================# + # End training loop + # ==================================================================================# + + dist.destroy_process_group() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Train the acoustic model.") + parser.add_argument( + "dataset_dir", + metavar="dataset-dir", + help="path to the 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( + "--discrete", + action="store_true", + help="use discrete units.", + ) + args = parser.parse_args() + + world_size = torch.cuda.device_count() + mp.spawn( + train, + args=(world_size, args), + nprocs=world_size, + join=True, + ) diff --git a/voice_conversion/softvc/hifigan/.gitignore b/voice_conversion/softvc/hifigan/.gitignore new file mode 100644 index 0000000..0202868 --- /dev/null +++ b/voice_conversion/softvc/hifigan/.gitignore @@ -0,0 +1,132 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# VSCode project settings +.vscode + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/voice_conversion/softvc/hifigan/LICENSE b/voice_conversion/softvc/hifigan/LICENSE new file mode 100644 index 0000000..6eb2af0 --- /dev/null +++ b/voice_conversion/softvc/hifigan/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Benjamin van Niekerk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/voice_conversion/softvc/hifigan/README.md b/voice_conversion/softvc/hifigan/README.md new file mode 100644 index 0000000..d963302 --- /dev/null +++ b/voice_conversion/softvc/hifigan/README.md @@ -0,0 +1,139 @@ +

+ + Open In Colab + +

+ +# HiFi-GAN + +Training and inference scripts for the vocoder models in [A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion](https://ieeexplore.ieee.org/abstract/document/9746484). For more details see [soft-vc](https://github.com/bshall/soft-vc). Audio samples can be found [here](https://bshall.github.io/soft-vc/). Colab demo can be found [here](https://colab.research.google.com/github/bshall/soft-vc/blob/main/soft-vc-demo.ipynb). + +
+ Soft-VC +
+
+ + Fig 1: Architecture of the voice conversion system. a) The discrete content encoder clusters audio features to produce a sequence of discrete speech units. b) The soft content encoder is trained to predict the discrete units. The acoustic model transforms the discrete/soft speech units into a target spectrogram. The vocoder converts the spectrogram into an audio waveform. + +
+ +## Example Usage + +### Programmatic Usage + +```python +import torch +import numpy as np + +# Load checkpoint +hifigan = torch.hub.load("bshall/hifigan:main", "hifigan_hubert_soft").cuda() +# Load mel-spectrogram +mel = torch.from_numpy(np.load("path/to/mel")).unsqueeze(0).cuda() +# Generate +wav, sr = hifigan.generate(mel) +``` + +### Script-Based Usage + +``` +usage: generate.py [-h] {soft,discrete,base} in-dir out-dir + +Generate audio for a directory of mel-spectrogams using HiFi-GAN. + +positional arguments: + {soft,discrete,base} available models (HuBERT-Soft, HuBERT-Discrete, or + Base). + in-dir path to input directory containing the mel- + spectrograms. + out-dir path to output directory. + +optional arguments: + -h, --help show this help message and exit +``` + +## Training + +### Step 1: Dataset Preparation + +Download and extract the [LJSpeech](https://keithito.com/LJ-Speech-Dataset/) dataset. The training script expects the following tree structure for the dataset directory: + +``` +└───wavs + ├───dev + │ ├───LJ001-0001.wav + │ ├───... + │ └───LJ050-0278.wav + └───train + ├───LJ002-0332.wav + ├───... + └───LJ047-0007.wav +``` + +The `train` and `dev` directories should contain the training and validation splits respectively. The splits used for the paper can be found [here](https://github.com/bshall/hifigan/releases/tag/v0.1). + +### Step 2: Resample the Audio + +Resample the audio to 16kHz using the `resample.py` script: + +``` +usage: resample.py [-h] [--sample-rate SAMPLE_RATE] in-dir out-dir + +Resample an audio dataset. + +positional arguments: + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit + --sample-rate SAMPLE_RATE + target sample rate (default 16kHz) +``` + +for example: + +``` +python reample.py path/to/LJSpeech-1.1/ path/to/LJSpeech-Resampled/ +``` + +### Step 3: Train HifiGAN + +``` +usage: train.py [-h] [--resume RESUME] [--finetune] dataset-dir checkpoint-dir + +Train or finetune HiFi-GAN. + +positional arguments: + dataset-dir path to the preprocessed data directory + checkpoint-dir path to the checkpoint directory + +optional arguments: + -h, --help show this help message and exit + --resume RESUME path to the checkpoint to resume from + --finetune whether to finetune (note that a resume path must be given) +``` + +## Links + +- [Soft-VC repo](https://github.com/bshall/soft-vc) +- [Soft-VC paper](https://ieeexplore.ieee.org/abstract/document/9746484) +- [HuBERT content encoders](https://github.com/bshall/hubert) +- [Acoustic models](https://github.com/bshall/acoustic-model) + +## Citation + +If you found this work helpful please consider citing our paper: + +``` +@inproceedings{ + soft-vc-2022, + author={van Niekerk, Benjamin and Carbonneau, Marc-André and Zaïdi, Julian and Baas, Matthew and Seuté, Hugo and Kamper, Herman}, + booktitle={ICASSP}, + title={A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion}, + year={2022} +} +``` + +## Acknowledgements +This repo is based heavily on [https://github.com/jik876/hifi-gan](https://github.com/jik876/hifi-gan). \ No newline at end of file diff --git a/voice_conversion/softvc/hifigan/generate.py b/voice_conversion/softvc/hifigan/generate.py new file mode 100644 index 0000000..22c58cf --- /dev/null +++ b/voice_conversion/softvc/hifigan/generate.py @@ -0,0 +1,50 @@ +from pathlib import Path +import numpy as np +import argparse +import torch +import torchaudio +from tqdm import tqdm + + +def generate(args): + print("Loading checkpoint") + model_name = f"hifigan_hubert_{args.model}" if args.model != "base" else "hifigan" + hifigan = torch.hub.load("bshall/hifigan:main", model_name).cuda() + + print(f"Generating audio from {args.in_dir}") + for path in tqdm(list(args.in_dir.rglob("*.npy"))): + mel = torch.from_numpy(np.load(path)) + mel = mel.unsqueeze(0).cuda() + + wav, sr = hifigan.generate(mel) + wav = wav.squeeze(0).cpu() + + out_path = args.out_dir / path.relative_to(args.in_dir) + out_path.parent.mkdir(exist_ok=True, parents=True) + torchaudio.save(out_path.with_suffix(".wav"), wav, sr) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate audio for a directory of mel-spectrogams using HiFi-GAN." + ) + parser.add_argument( + "model", + help="available models (HuBERT-Soft, HuBERT-Discrete, or Base).", + choices=["soft", "discrete", "base"], + ) + parser.add_argument( + "in_dir", + metavar="in-dir", + help="path to input directory containing the mel-spectrograms.", + type=Path, + ) + parser.add_argument( + "out_dir", + metavar="out-dir", + help="path to output directory.", + type=Path, + ) + args = parser.parse_args() + + generate(args) diff --git a/voice_conversion/softvc/hifigan/hifigan/__init__.py b/voice_conversion/softvc/hifigan/hifigan/__init__.py new file mode 100644 index 0000000..e594d66 --- /dev/null +++ b/voice_conversion/softvc/hifigan/hifigan/__init__.py @@ -0,0 +1 @@ +from .generator import hifigan, hifigan_hubert_discrete, hifigan_hubert_soft \ No newline at end of file diff --git a/voice_conversion/softvc/hifigan/hifigan/dataset.py b/voice_conversion/softvc/hifigan/hifigan/dataset.py new file mode 100644 index 0000000..0142cd0 --- /dev/null +++ b/voice_conversion/softvc/hifigan/hifigan/dataset.py @@ -0,0 +1,126 @@ +from pathlib import Path +import math +import random +import numpy as np +import torch +import torch.nn.functional as F + +from torch.utils.data import Dataset + +import torchaudio +import torchaudio.transforms as transforms + + +class LogMelSpectrogram(torch.nn.Module): + def __init__(self): + super().__init__() + self.melspctrogram = transforms.MelSpectrogram( + sample_rate=16000, + n_fft=1024, + win_length=1024, + hop_length=160, + center=False, + power=1.0, + norm="slaney", + onesided=True, + n_mels=128, + mel_scale="slaney", + ) + + def forward(self, wav): + wav = F.pad(wav, ((1024 - 160) // 2, (1024 - 160) // 2), "reflect") + mel = self.melspctrogram(wav) + logmel = torch.log(torch.clamp(mel, min=1e-5)) + return logmel + + +class MelDataset(Dataset): + def __init__( + self, + root: Path, + segment_length: int, + sample_rate: int, + hop_length: int, + train: bool = True, + finetune: bool = False, + ): + self.wavs_dir = root / "wavs" + self.mels_dir = root / "mels" + self.data_dir = self.wavs_dir if not finetune else self.mels_dir + + self.segment_length = segment_length + self.sample_rate = sample_rate + self.hop_length = hop_length + self.train = train + self.finetune = finetune + + suffix = ".wav" if not finetune else ".npy" + pattern = f"train/**/*{suffix}" if train else "dev/**/*{suffix}" + + self.metadata = [ + path.relative_to(self.data_dir).with_suffix("") + for path in self.data_dir.rglob(pattern) + ] + + self.logmel = LogMelSpectrogram() + + def __len__(self): + return len(self.metadata) + + def __getitem__(self, index): + path = self.metadata[index] + wav_path = self.wavs_dir / path + + info = torchaudio.info(wav_path.with_suffix(".wav")) + if info.sample_rate != self.sample_rate: + raise ValueError( + f"Sample rate {info.sample_rate} doesn't match target of {self.sample_rate}" + ) + + if self.finetune: + mel_path = self.mels_dir / path + src_logmel = torch.from_numpy(np.load(mel_path.with_suffix(".npy"))) + src_logmel = src_logmel.unsqueeze(0) + + mel_frames_per_segment = math.ceil(self.segment_length / self.hop_length) + mel_diff = src_logmel.size(-1) - mel_frames_per_segment if self.train else 0 + mel_offset = random.randint(0, max(mel_diff, 0)) + + frame_offset = self.hop_length * mel_offset + else: + frame_diff = info.num_frames - self.segment_length + frame_offset = random.randint(0, max(frame_diff, 0)) + + wav, _ = torchaudio.load( + filepath=wav_path.with_suffix(".wav"), + frame_offset=frame_offset if self.train else 0, + num_frames=self.segment_length if self.train else -1, + ) + + if wav.size(-1) < self.segment_length: + wav = F.pad(wav, (0, self.segment_length - wav.size(-1))) + + if not self.finetune and self.train: + gain = random.random() * (0.99 - 0.4) + 0.4 + flip = -1 if random.random() > 0.5 else 1 + wav = flip * gain * wav / wav.abs().max() + + tgt_logmel = self.logmel(wav.unsqueeze(0)).squeeze(0) + + if self.finetune: + if self.train: + src_logmel = src_logmel[ + :, :, mel_offset : mel_offset + mel_frames_per_segment + ] + + if src_logmel.size(-1) < mel_frames_per_segment: + src_logmel = F.pad( + src_logmel, + (0, mel_frames_per_segment - src_logmel.size(-1)), + "constant", + src_logmel.min(), + ) + else: + src_logmel = tgt_logmel.clone() + + return wav, src_logmel, tgt_logmel diff --git a/voice_conversion/softvc/hifigan/hifigan/discriminator.py b/voice_conversion/softvc/hifigan/hifigan/discriminator.py new file mode 100644 index 0000000..8f53c3d --- /dev/null +++ b/voice_conversion/softvc/hifigan/hifigan/discriminator.py @@ -0,0 +1,262 @@ +# adopted from https://github.com/jik876/hifi-gan/blob/master/models.py +import torch +import torch.nn as nn +import torch.nn.functional as F +from typing import Tuple, List + +from hifigan.utils import get_padding + + +LRELU_SLOPE = 0.1 + + +class PeriodDiscriminator(torch.nn.Module): + """HiFiGAN Period Discriminator""" + + def __init__( + self, + period: int, + kernel_size: int = 5, + stride: int = 3, + use_spectral_norm: bool = False, + ) -> None: + super().__init__() + self.period = period + norm_f = nn.utils.spectral_norm if use_spectral_norm else nn.utils.weight_norm + self.convs = nn.ModuleList( + [ + norm_f( + nn.Conv2d( + 1, + 32, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + nn.Conv2d( + 32, + 128, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + nn.Conv2d( + 128, + 512, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f( + nn.Conv2d( + 512, + 1024, + (kernel_size, 1), + (stride, 1), + padding=(get_padding(5, 1), 0), + ) + ), + norm_f(nn.Conv2d(1024, 1024, (kernel_size, 1), 1, padding=(2, 0))), + ] + ) + self.conv_post = norm_f(nn.Conv2d(1024, 1, (3, 1), 1, padding=(1, 0))) + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + """ + Args: + x (Tensor): input waveform. + Returns: + [Tensor]: discriminator scores per sample in the batch. + [List[Tensor]]: list of features from each convolutional layer. + """ + feat = [] + + # 1d to 2d + b, c, t = x.shape + if t % self.period != 0: # pad first + n_pad = self.period - (t % self.period) + x = F.pad(x, (0, n_pad), "reflect") + t = t + n_pad + x = x.view(b, c, t // self.period, self.period) + + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, LRELU_SLOPE) + feat.append(x) + x = self.conv_post(x) + feat.append(x) + x = torch.flatten(x, 1, -1) + + return x, feat + + +class MultiPeriodDiscriminator(torch.nn.Module): + """HiFiGAN Multi-Period Discriminator (MPD)""" + + def __init__(self): + super().__init__() + self.discriminators = nn.ModuleList( + [ + PeriodDiscriminator(2), + PeriodDiscriminator(3), + PeriodDiscriminator(5), + PeriodDiscriminator(7), + PeriodDiscriminator(11), + ] + ) + + def forward( + self, x: torch.Tensor + ) -> Tuple[List[torch.Tensor], List[List[torch.Tensor]]]: + """ + Args: + x (Tensor): input waveform. + Returns: + [List[Tensor]]: list of scores from each discriminator. + [List[List[Tensor]]]: list of features from each discriminator's convolutional layers. + """ + scores = [] + feats = [] + for _, d in enumerate(self.discriminators): + score, feat = d(x) + scores.append(score) + feats.append(feat) + return scores, feats + + +class ScaleDiscriminator(torch.nn.Module): + """HiFiGAN Scale Discriminator.""" + + def __init__(self, use_spectral_norm: bool = False) -> None: + super().__init__() + norm_f = nn.utils.spectral_norm if use_spectral_norm else nn.utils.weight_norm + self.convs = nn.ModuleList( + [ + norm_f(nn.Conv1d(1, 128, 15, 1, padding=7)), + norm_f(nn.Conv1d(128, 128, 41, 2, groups=4, padding=20)), + norm_f(nn.Conv1d(128, 256, 41, 2, groups=16, padding=20)), + norm_f(nn.Conv1d(256, 512, 41, 4, groups=16, padding=20)), + norm_f(nn.Conv1d(512, 1024, 41, 4, groups=16, padding=20)), + norm_f(nn.Conv1d(1024, 1024, 41, 1, groups=16, padding=20)), + norm_f(nn.Conv1d(1024, 1024, 5, 1, padding=2)), + ] + ) + self.conv_post = norm_f(nn.Conv1d(1024, 1, 3, 1, padding=1)) + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, List[torch.Tensor]]: + """ + Args: + x (Tensor): input waveform. + Returns: + Tensor: discriminator scores. + List[Tensor]: list of features from the convolutional layers. + """ + feat = [] + for l in self.convs: + x = l(x) + x = F.leaky_relu(x, LRELU_SLOPE) + feat.append(x) + x = self.conv_post(x) + feat.append(x) + x = torch.flatten(x, 1, -1) + return x, feat + + +class MultiScaleDiscriminator(torch.nn.Module): + """HiFiGAN Multi-Scale Discriminator.""" + + def __init__(self): + super().__init__() + self.discriminators = nn.ModuleList( + [ + ScaleDiscriminator(use_spectral_norm=True), + ScaleDiscriminator(), + ScaleDiscriminator(), + ] + ) + self.meanpools = nn.ModuleList( + [nn.AvgPool1d(4, 2, padding=2), nn.AvgPool1d(4, 2, padding=2)] + ) + + def forward( + self, x: torch.Tensor + ) -> Tuple[List[torch.Tensor], List[List[torch.Tensor]]]: + """ + Args: + x (Tensor): input waveform. + Returns: + List[Tensor]: discriminator scores. + List[List[Tensor]]: list of features from each discriminator's convolutional layers. + """ + scores = [] + feats = [] + for i, d in enumerate(self.discriminators): + if i != 0: + x = self.meanpools[i - 1](x) + score, feat = d(x) + scores.append(score) + feats.append(feat) + return scores, feats + + +class HifiganDiscriminator(nn.Module): + """HiFiGAN discriminator""" + + def __init__(self): + super().__init__() + self.mpd = MultiPeriodDiscriminator() + self.msd = MultiScaleDiscriminator() + + def forward( + self, x: torch.Tensor + ) -> Tuple[List[torch.Tensor], List[List[torch.Tensor]]]: + """ + Args: + x (Tensor): input waveform. + Returns: + List[Tensor]: discriminator scores. + List[List[Tensor]]: list of features from from each discriminator's convolutional layers. + """ + scores, feats = self.mpd(x) + scores_, feats_ = self.msd(x) + return scores + scores_, feats + feats_ + + +def feature_loss( + features_real: List[List[torch.Tensor]], features_generate: List[List[torch.Tensor]] +) -> float: + loss = 0 + for r, g in zip(features_real, features_generate): + for rl, gl in zip(r, g): + loss += torch.mean(torch.abs(rl - gl)) + return loss * 2 + + +def discriminator_loss(real, generated): + loss = 0 + real_losses = [] + generated_losses = [] + for r, g in zip(real, generated): + r_loss = torch.mean((1 - r) ** 2) + g_loss = torch.mean(g ** 2) + loss += r_loss + g_loss + real_losses.append(r_loss.item()) + generated_losses.append(g_loss.item()) + + return loss, real_losses, generated_losses + + +def generator_loss(discriminator_outputs): + loss = 0 + generator_losses = [] + for x in discriminator_outputs: + l = torch.mean((1 - x) ** 2) + generator_losses.append(l) + loss += l + + return loss, generator_losses diff --git a/voice_conversion/softvc/hifigan/hifigan/generator.py b/voice_conversion/softvc/hifigan/hifigan/generator.py new file mode 100644 index 0000000..17282c3 --- /dev/null +++ b/voice_conversion/softvc/hifigan/hifigan/generator.py @@ -0,0 +1,282 @@ +# adapted from https://github.com/jik876/hifi-gan/blob/master/models.py +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn.utils import remove_weight_norm, weight_norm +from torch.nn.modules.utils import consume_prefix_in_state_dict_if_present +from typing import Tuple + +from hifigan.utils import get_padding + + +URLS = { + "hifigan": "https://github.com/bshall/hifigan/releases/download/v0.1/hifigan-67926ec6.pt", + "hifigan-hubert-soft": "https://github.com/bshall/hifigan/releases/download/v0.1/hifigan-hubert-discrete-bbad3043.pt", + "hifigan-hubert-discrete": "https://github.com/bshall/hifigan/releases/download/v0.1/hifigan-hubert-soft-65f03469.pt", +} + +LRELU_SLOPE = 0.1 + + +class HifiganGenerator(torch.nn.Module): + def __init__( + self, + in_channels: int = 128, + resblock_dilation_sizes: Tuple[Tuple[int, ...], ...] = ( + (1, 3, 5), + (1, 3, 5), + (1, 3, 5), + ), + resblock_kernel_sizes: Tuple[int, ...] = (3, 7, 11), + upsample_kernel_sizes: Tuple[int, ...] = (20, 8, 4, 4), + upsample_initial_channel: int = 512, + upsample_factors: int = (10, 4, 2, 2), + inference_padding: int = 5, + sample_rate: int = 16000, + ) -> None: + r"""HiFiGAN Generator + Args: + in_channels (int): number of input channels. + resblock_dilation_sizes (Tuple[Tuple[int, ...], ...]): list of dilation values in each layer of a `ResBlock`. + resblock_kernel_sizes (Tuple[int, ...]): list of kernel sizes for each `ResBlock`. + upsample_kernel_sizes (Tuple[int, ...]): list of kernel sizes for each transposed convolution. + upsample_initial_channel (int): number of channels for the first upsampling layer. This is divided by 2 + for each consecutive upsampling layer. + upsample_factors (Tuple[int, ...]): upsampling factors (stride) for each upsampling layer. + inference_padding (int): constant padding applied to the input at inference time. + sample_rate (int): sample rate of the generated audio. + """ + super().__init__() + self.inference_padding = inference_padding + self.num_kernels = len(resblock_kernel_sizes) + self.num_upsamples = len(upsample_factors) + self.sample_rate = sample_rate + # initial upsampling layers + self.conv_pre = weight_norm( + nn.Conv1d(in_channels, upsample_initial_channel, 7, 1, padding=3) + ) + + # upsampling layers + self.ups = nn.ModuleList() + for i, (u, k) in enumerate(zip(upsample_factors, upsample_kernel_sizes)): + self.ups.append( + weight_norm( + nn.ConvTranspose1d( + upsample_initial_channel // (2 ** i), + upsample_initial_channel // (2 ** (i + 1)), + k, + u, + padding=(k - u) // 2, + ) + ) + ) + # MRF blocks + self.resblocks = nn.ModuleList() + for i in range(len(self.ups)): + ch = upsample_initial_channel // (2 ** (i + 1)) + for _, (k, d) in enumerate( + zip(resblock_kernel_sizes, resblock_dilation_sizes) + ): + self.resblocks.append(ResBlock1(ch, k, d)) + # post convolution layer + self.conv_post = weight_norm(nn.Conv1d(ch, 1, 7, 1, padding=3)) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + o = self.conv_pre(x) + for i in range(self.num_upsamples): + o = F.leaky_relu(o, LRELU_SLOPE) + o = self.ups[i](o) + z_sum = None + for j in range(self.num_kernels): + if z_sum is None: + z_sum = self.resblocks[i * self.num_kernels + j](o) + else: + z_sum += self.resblocks[i * self.num_kernels + j](o) + o = z_sum / self.num_kernels + o = F.leaky_relu(o) + o = self.conv_post(o) + o = torch.tanh(o) + return o + + @torch.no_grad() + def generate(self, x: torch.Tensor) -> torch.Tensor: + x = F.pad(x, (self.inference_padding, self.inference_padding), "replicate") + return self(x), self.sample_rate + + def remove_weight_norm(self): + print("Removing weight norm...") + for l in self.ups: + remove_weight_norm(l) + for l in self.resblocks: + l.remove_weight_norm() + remove_weight_norm(self.conv_pre) + remove_weight_norm(self.conv_post) + + +class ResBlock1(torch.nn.Module): + def __init__( + self, channels: int, kernel_size: int = 3, dilation: Tuple[int, ...] = (1, 3, 5) + ) -> None: + super().__init__() + self.convs1 = nn.ModuleList( + [ + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[2], + padding=get_padding(kernel_size, dilation[2]), + ) + ), + ] + ) + + self.convs2 = nn.ModuleList( + [ + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=1, + padding=get_padding(kernel_size, 1), + ) + ), + ] + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + for c1, c2 in zip(self.convs1, self.convs2): + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c1(xt) + xt = F.leaky_relu(xt, LRELU_SLOPE) + xt = c2(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs1: + remove_weight_norm(l) + for l in self.convs2: + remove_weight_norm(l) + + +class ResBlock2(torch.nn.Module): + def __init__( + self, channels: int, kernel_size: int = 3, dilation: Tuple[int, ...] = (1, 3) + ) -> None: + super().__init__() + self.convs = nn.ModuleList( + [ + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[0], + padding=get_padding(kernel_size, dilation[0]), + ) + ), + weight_norm( + nn.Conv1d( + channels, + channels, + kernel_size, + 1, + dilation=dilation[1], + padding=get_padding(kernel_size, dilation[1]), + ) + ), + ] + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + for c in self.convs: + xt = F.leaky_relu(x, LRELU_SLOPE) + xt = c(xt) + x = xt + x + return x + + def remove_weight_norm(self): + for l in self.convs: + remove_weight_norm(l) + + +def _hifigan( + name: str, + pretrained: bool = True, + progress: bool = True, + map_location=None, +) -> HifiganGenerator: + hifigan = HifiganGenerator() + if pretrained: + checkpoint = torch.hub.load_state_dict_from_url( + URLS[name], map_location=map_location, progress=progress + ) + consume_prefix_in_state_dict_if_present(checkpoint, "module.") + hifigan.load_state_dict(checkpoint) + hifigan.eval() + hifigan.remove_weight_norm() + return hifigan + + +def hifigan( + pretrained: bool = True, progress: bool = True, map_location=None +) -> HifiganGenerator: + return _hifigan("hifigan", pretrained, progress, map_location) + + +def hifigan_hubert_soft( + pretrained: bool = True, progress: bool = True, map_location=None +) -> HifiganGenerator: + return _hifigan("hifigan-hubert-soft", pretrained, progress, map_location=None) + + +def hifigan_hubert_discrete( + pretrained: bool = True, progress: bool = True, map_location=None +) -> HifiganGenerator: + return _hifigan("hifigan-hubert-discrete", pretrained, progress, map_location=None) diff --git a/voice_conversion/softvc/hifigan/hifigan/utils.py b/voice_conversion/softvc/hifigan/hifigan/utils.py new file mode 100644 index 0000000..3585663 --- /dev/null +++ b/voice_conversion/softvc/hifigan/hifigan/utils.py @@ -0,0 +1,84 @@ +import torch +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pylab as plt + + +def get_padding(k, d): + return int((k * d - d) / 2) + + +def plot_spectrogram(spectrogram): + fig, ax = plt.subplots(figsize=(10, 2)) + im = ax.imshow(spectrogram, aspect="auto", origin="lower", interpolation="none") + plt.colorbar(im, ax=ax) + + fig.canvas.draw() + plt.close() + + return fig + + +def save_checkpoint( + checkpoint_dir, + generator, + discriminator, + optimizer_generator, + optimizer_discriminator, + scheduler_generator, + scheduler_discriminator, + step, + loss, + best, + logger, +): + state = { + "generator": { + "model": generator.state_dict(), + "optimizer": optimizer_generator.state_dict(), + "scheduler": scheduler_generator.state_dict(), + }, + "discriminator": { + "model": discriminator.state_dict(), + "optimizer": optimizer_discriminator.state_dict(), + "scheduler": scheduler_discriminator.state_dict(), + }, + "step": step, + "loss": loss, + } + checkpoint_dir.mkdir(exist_ok=True, parents=True) + checkpoint_path = checkpoint_dir / f"model-{step}.pt" + torch.save(state, checkpoint_path) + if best: + best_path = checkpoint_dir / "model-best.pt" + torch.save(state, best_path) + logger.info(f"Saved checkpoint: {checkpoint_path.stem}") + + +def load_checkpoint( + load_path, + generator, + discriminator, + optimizer_generator, + optimizer_discriminator, + scheduler_generator, + scheduler_discriminator, + rank, + logger, + finetune=False, +): + logger.info(f"Loading checkpoint from {load_path}") + checkpoint = torch.load(load_path, map_location={"cuda:0": f"cuda:{rank}"}) + generator.load_state_dict(checkpoint["generator"]["model"]) + discriminator.load_state_dict(checkpoint["discriminator"]["model"]) + if not finetune: + optimizer_generator.load_state_dict(checkpoint["generator"]["optimizer"]) + scheduler_generator.load_state_dict(checkpoint["generator"]["scheduler"]) + optimizer_discriminator.load_state_dict( + checkpoint["discriminator"]["optimizer"] + ) + scheduler_discriminator.load_state_dict( + checkpoint["discriminator"]["scheduler"] + ) + return checkpoint["step"], checkpoint["loss"] diff --git a/voice_conversion/softvc/hifigan/hubconf.py b/voice_conversion/softvc/hifigan/hubconf.py new file mode 100644 index 0000000..38dbd31 --- /dev/null +++ b/voice_conversion/softvc/hifigan/hubconf.py @@ -0,0 +1,3 @@ +dependencies = ["torch", "torchaudio"] + +from hifigan import hifigan, hifigan_hubert_discrete, hifigan_hubert_soft diff --git a/voice_conversion/softvc/hifigan/requirements.txt b/voice_conversion/softvc/hifigan/requirements.txt new file mode 100644 index 0000000..20dfcf8 --- /dev/null +++ b/voice_conversion/softvc/hifigan/requirements.txt @@ -0,0 +1,4 @@ +tensorboard==2.7.0 +torch==1.9.1 +torchaudio==0.9.1 +tqdm==4.62.3 \ No newline at end of file diff --git a/voice_conversion/softvc/hifigan/resample.py b/voice_conversion/softvc/hifigan/resample.py new file mode 100644 index 0000000..f6b46da --- /dev/null +++ b/voice_conversion/softvc/hifigan/resample.py @@ -0,0 +1,56 @@ +import argparse +from pathlib import Path +from concurrent.futures import ProcessPoolExecutor +from multiprocessing import cpu_count + +import torchaudio +from torchaudio.functional import resample + +from tqdm import tqdm + + +def process_wav(in_path, out_path, sample_rate): + wav, sr = torchaudio.load(in_path) + wav = resample(wav, sr, sample_rate) + torchaudio.save(out_path, wav, sample_rate) + return out_path, wav.size(-1) / sample_rate + + +def preprocess_dataset(args): + args.out_dir.mkdir(parents=True, exist_ok=True) + + futures = [] + executor = ProcessPoolExecutor(max_workers=cpu_count()) + print(f"Resampling audio in {args.in_dir}") + for in_path in args.in_dir.rglob("*.wav"): + relative_path = in_path.relative_to(args.in_dir) + out_path = args.out_dir / relative_path + out_path.parent.mkdir(parents=True, exist_ok=True) + futures.append( + executor.submit(process_wav, in_path, out_path, args.sample_rate) + ) + + results = [future.result() for future in tqdm(futures)] + + lengths = {path.stem: length for path, length in results} + seconds = sum(lengths.values()) + hours = seconds / 3600 + print(f"Wrote {len(lengths)} utterances ({hours:.2f} hours)") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Resample an audio dataset.") + parser.add_argument( + "in_dir", metavar="in-dir", help="path to the dataset directory.", type=Path + ) + parser.add_argument( + "out_dir", metavar="out-dir", help="path to the output directory.", type=Path + ) + parser.add_argument( + "--sample-rate", + help="target sample rate (default 16kHz)", + type=int, + default=16000, + ) + args = parser.parse_args() + preprocess_dataset(args) diff --git a/voice_conversion/softvc/hifigan/train.py b/voice_conversion/softvc/hifigan/train.py new file mode 100644 index 0000000..0ff27ad --- /dev/null +++ b/voice_conversion/softvc/hifigan/train.py @@ -0,0 +1,347 @@ +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, + ) diff --git a/voice_conversion/softvc/hifigan/vocoder.png b/voice_conversion/softvc/hifigan/vocoder.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed2d54e24bb725c16de35738d625494978b75a5 GIT binary patch literal 84132 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 T;D+E=z(Ynt5n3f?6!iZ9fn + + Open In Colab + +

+ +# HuBERT + +Training and inference scripts for the HuBERT content encoders in [A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion](https://ieeexplore.ieee.org/abstract/document/9746484). +For more details see [soft-vc](https://github.com/bshall/soft-vc). Audio samples can be found [here](https://bshall.github.io/soft-vc/). Colab demo can be found [here](https://colab.research.google.com/github/bshall/soft-vc/blob/main/soft-vc-demo.ipynb). + +
+ Soft-VC +
+
+ + Fig 1: Architecture of the voice conversion system. a) The discrete content encoder clusters audio features to produce a sequence of discrete speech units. b) The soft content encoder is trained to predict the discrete units. The acoustic model transforms the discrete/soft speech units into a target spectrogram. The vocoder converts the spectrogram into an audio waveform. + +
+ +## Example Usage + +### Programmatic Usage + +```python +import torch, torchaudio + +# Load checkpoint (either hubert_soft or hubert_discrete) +hubert = torch.hub.load("bshall/hubert:main", "hubert_soft").cuda() + +# Load audio +wav, sr = torchaudio.load("path/to/wav") +assert sr == 16000 +wav = wav.unsqueeze(0).cuda() + +# Extract speech units +units = hubert.units(x) +``` + +### Script-Based Usage + +``` +usage: encode.py [-h] [--extension EXTENSION] {soft,discrete} in-dir out-dir + +Encode an audio dataset. + +positional arguments: + {soft,discrete} available models (HuBERT-Soft or HuBERT-Discrete) + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit + --extension EXTENSION + extension of the audio files (defaults to .flac). +``` + +## Training + +### Step 1: Dataset Preparation + +Download and extract the [LibriSpeech](https://www.openslr.org/12) corpus. The training script expects the following tree structure for the dataset directory: + +``` +│ lengths.json +│ +└───wavs + ├───dev-* + │ ├───84 + │ ├───... + │ └───8842 + └───train-* + ├───19 + ├───... + └───8975 +``` + +The `train-*` and `dev-*` directories should contain the training and validation splits respectively. Note that there can be multiple `train` and `dev` folders e.g., `train-clean-100`, `train-other-500`, etc. Finally, the `lengths.json` file should contain key-value pairs with the file path and number of samples: + +```json +{ + "dev-clean/1272/128104/1272-128104-0000": 93680, + "dev-clean/1272/128104/1272-128104-0001": 77040, +} +``` + +### Step 2: Extract Discrete Speech Units + +Encode LibriSpeech using the HuBERT-Discrete model and `encode.py` script: + +``` +usage: encode.py [-h] [--extension EXTENSION] {soft,discrete} in-dir out-dir + +Encode an audio dataset. + +positional arguments: + {soft,discrete} available models (HuBERT-Soft or HuBERT-Discrete) + in-dir path to the dataset directory. + out-dir path to the output directory. + +optional arguments: + -h, --help show this help message and exit + --extension EXTENSION + extension of the audio files (defaults to .flac). +``` + +for example: + +``` +python encode.py discrete path/to/LibriSpeech/wavs path/to/LibriSpeech/units +``` + +At this point the directory tree should look like: + +``` +│ lengths.json +│ +├───discrete +│ ├───... +└───wavs + ├───... +``` + +### Step 3: Train the HuBERT-Soft Content Encoder + +``` +usage: train.py [-h] [--resume RESUME] [--warmstart] [--mask] [--alpha ALPHA] dataset-dir checkpoint-dir + +Train HuBERT soft content encoder. + +positional arguments: + dataset-dir path to the data directory. + checkpoint-dir path to the checkpoint directory. + +optional arguments: + -h, --help show this help message and exit + --resume RESUME path to the checkpoint to resume from. + --warmstart whether to initialize from the fairseq HuBERT checkpoint. + --mask whether to use input masking. + --alpha ALPHA weight for the masked loss. +``` + +## Links + +- [Soft-VC repo](https://github.com/bshall/soft-vc) +- [Soft-VC paper](https://ieeexplore.ieee.org/abstract/document/9746484) +- [Official HuBERT repo](https://github.com/pytorch/fairseq) +- [HuBERT paper](https://arxiv.org/abs/2106.07447) + +## Citation + +If you found this work helpful please consider citing our paper: + +``` +@inproceedings{ + soft-vc-2022, + author={van Niekerk, Benjamin and Carbonneau, Marc-André and Zaïdi, Julian and Baas, Matthew and Seuté, Hugo and Kamper, Herman}, + booktitle={ICASSP}, + title={A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion}, + year={2022} +} +``` \ No newline at end of file diff --git a/voice_conversion/softvc/hubert/cluster.py b/voice_conversion/softvc/hubert/cluster.py new file mode 100644 index 0000000..18b754c --- /dev/null +++ b/voice_conversion/softvc/hubert/cluster.py @@ -0,0 +1,66 @@ +from pathlib import Path +import logging +import argparse + +import torch +import numpy as np +from sklearn.cluster import KMeans + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +def cluster(args): + with open(args.subset) as file: + subset = [line.strip() for line in file] + + logger.info(f"Loading features from {args.in_dir}") + features = [] + for path in subset: + in_path = args.in_dir / path + features.append(np.load(in_path.with_suffix(".npy"))) + features = np.concatenate(features, axis=0) + + logger.info(f"Clustering features of shape: {features.shape}") + kmeans = KMeans(n_clusters=args.n_clusters).fit(features) + + checkpoint_path = args.checkpoint_dir / f"kmeans_{args.n_clusters}.pt" + checkpoint_path.parent.mkdir(exist_ok=True, parents=True) + torch.save( + checkpoint_path, + { + "n_features_in_": kmeans.n_features_in_, + "_n_threads": kmeans._n_threads, + "cluster_centers_": kmeans.cluster_centers_, + }, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Cluster speech features features.") + parser.add_argument( + "in_dir", + metavar="in-dir", + help="path to the encoded dataset", + type=Path, + ) + parser.add_argument( + "subset", + matavar="subset", + help="path to the .txt file containing the list of files to cluster", + type=Path, + ) + parser.add_argument( + "checkpoint_dir", + metavar="checkpoint-dir", + help="path to the checkpoint directory", + type=Path, + ) + parser.add_argument( + "--n-clusters", + help="number of clusters", + type=int, + default=100, + ) + args = parser.parse_args() + cluster(args) diff --git a/voice_conversion/softvc/hubert/content-encoder.png b/voice_conversion/softvc/hubert/content-encoder.png new file mode 100644 index 0000000000000000000000000000000000000000..fc59d538a9383896cf0c36e1d4a3f5030fce38fe GIT binary patch literal 84584 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 elMyXIehiQ min_samples + ] + + self.sample_rate = sample_rate + self.label_rate = label_rate + self.min_samples = min_samples + self.max_samples = max_samples + self.train = train + + def __len__(self): + return len(self.metadata) + + def __getitem__(self, index): + wav_path = self.metadata[index] + units_path = self.units_dir / wav_path.relative_to(self.wavs_dir) + + wav, _ = torchaudio.load(wav_path) + wav = F.pad(wav, ((400 - 320) // 2, (400 - 320) // 2)) + codes = np.load(units_path.with_suffix(".npy")) + + return wav, torch.from_numpy(codes).long() + + def collate(self, batch): + wavs, codes = zip(*batch) + wavs, codes = list(wavs), list(codes) + + wav_lengths = [wav.size(-1) for wav in wavs] + code_lengths = [code.size(-1) for code in codes] + + wav_frames = min(self.max_samples, *wav_lengths) + + collated_wavs, wav_offsets = [], [] + for wav in wavs: + wav_diff = wav.size(-1) - wav_frames + wav_offset = random.randint(0, wav_diff) + wav = wav[:, wav_offset : wav_offset + wav_frames] + + collated_wavs.append(wav) + wav_offsets.append(wav_offset) + + rate = self.label_rate / self.sample_rate + code_offsets = [round(wav_offset * rate) for wav_offset in wav_offsets] + code_frames = round(wav_frames * rate) + remaining_code_frames = [ + length - offset for length, offset in zip(code_lengths, code_offsets) + ] + code_frames = min(code_frames, *remaining_code_frames) + + collated_codes = [] + for code, code_offset in zip(codes, code_offsets): + code = code[code_offset : code_offset + code_frames] + collated_codes.append(code) + + wavs = torch.stack(collated_wavs, dim=0) + codes = torch.stack(collated_codes, dim=0) + + return wavs, codes diff --git a/voice_conversion/softvc/hubert/hubert/model.py b/voice_conversion/softvc/hubert/hubert/model.py new file mode 100644 index 0000000..f275b1c --- /dev/null +++ b/voice_conversion/softvc/hubert/hubert/model.py @@ -0,0 +1,293 @@ +import copy +from typing import Optional, Tuple +import random + +from sklearn.cluster import KMeans + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn.modules.utils import consume_prefix_in_state_dict_if_present + +URLS = { + "hubert-discrete": "https://github.com/bshall/hubert/releases/download/v0.1/hubert-discrete-e9416457.pt", + "hubert-soft": "https://github.com/bshall/hubert/releases/download/v0.1/hubert-soft-0d54a1f4.pt", + "kmeans100": "https://github.com/bshall/hubert/releases/download/v0.1/kmeans100-50f36a95.pt", +} + + +class Hubert(nn.Module): + def __init__(self, num_label_embeddings: int = 100, mask: bool = True): + super().__init__() + self._mask = mask + self.feature_extractor = FeatureExtractor() + self.feature_projection = FeatureProjection() + self.positional_embedding = PositionalConvEmbedding() + self.norm = nn.LayerNorm(768) + self.dropout = nn.Dropout(0.1) + self.encoder = TransformerEncoder( + nn.TransformerEncoderLayer( + 768, 12, 3072, activation="gelu", batch_first=True + ), + 12, + ) + self.proj = nn.Linear(768, 256) + + self.masked_spec_embed = nn.Parameter(torch.FloatTensor(768).uniform_()) + self.label_embedding = nn.Embedding(num_label_embeddings, 256) + + def mask(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + mask = None + if self.training and self._mask: + mask = _compute_mask((x.size(0), x.size(1)), 0.8, 10, x.device, 2) + x[mask] = self.masked_spec_embed.to(x.dtype) + return x, mask + + def encode( + self, x: torch.Tensor, layer: Optional[int] = None + ) -> Tuple[torch.Tensor, torch.Tensor]: + x = self.feature_extractor(x) + x = self.feature_projection(x.transpose(1, 2)) + x, mask = self.mask(x) + x = x + self.positional_embedding(x) + x = self.dropout(self.norm(x)) + x = self.encoder(x, output_layer=layer) + return x, mask + + def logits(self, x: torch.Tensor) -> torch.Tensor: + logits = torch.cosine_similarity( + x.unsqueeze(2), + self.label_embedding.weight.unsqueeze(0).unsqueeze(0), + dim=-1, + ) + return logits / 0.1 + + def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: + x, mask = self.encode(x) + x = self.proj(x) + logits = self.logits(x) + return logits, mask + + +class HubertSoft(Hubert): + def __init__(self): + super().__init__() + + @torch.inference_mode() + def units(self, wav: torch.Tensor) -> torch.Tensor: + wav = F.pad(wav, ((400 - 320) // 2, (400 - 320) // 2)) + x, _ = self.encode(wav) + return self.proj(x) + + +class HubertDiscrete(Hubert): + def __init__(self, kmeans): + super().__init__(504) + self.kmeans = kmeans + + @torch.inference_mode() + def units(self, wav: torch.Tensor) -> torch.LongTensor: + wav = F.pad(wav, ((400 - 320) // 2, (400 - 320) // 2)) + x, _ = self.encode(wav, layer=7) + x = self.kmeans.predict(x.squeeze().cpu().numpy()) + return torch.tensor(x, dtype=torch.long, device=wav.device) + + +class FeatureExtractor(nn.Module): + def __init__(self): + super().__init__() + self.conv0 = nn.Conv1d(1, 512, 10, 5, bias=False) + self.norm0 = nn.GroupNorm(512, 512) + self.conv1 = nn.Conv1d(512, 512, 3, 2, bias=False) + self.conv2 = nn.Conv1d(512, 512, 3, 2, bias=False) + self.conv3 = nn.Conv1d(512, 512, 3, 2, bias=False) + self.conv4 = nn.Conv1d(512, 512, 3, 2, bias=False) + self.conv5 = nn.Conv1d(512, 512, 2, 2, bias=False) + self.conv6 = nn.Conv1d(512, 512, 2, 2, bias=False) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = F.gelu(self.norm0(self.conv0(x))) + x = F.gelu(self.conv1(x)) + x = F.gelu(self.conv2(x)) + x = F.gelu(self.conv3(x)) + x = F.gelu(self.conv4(x)) + x = F.gelu(self.conv5(x)) + x = F.gelu(self.conv6(x)) + return x + + +class FeatureProjection(nn.Module): + def __init__(self): + super().__init__() + self.norm = nn.LayerNorm(512) + self.projection = nn.Linear(512, 768) + self.dropout = nn.Dropout(0.1) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.norm(x) + x = self.projection(x) + x = self.dropout(x) + return x + + +class PositionalConvEmbedding(nn.Module): + def __init__(self): + super().__init__() + self.conv = nn.Conv1d( + 768, + 768, + kernel_size=128, + padding=128 // 2, + groups=16, + ) + self.conv = nn.utils.weight_norm(self.conv, name="weight", dim=2) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.conv(x.transpose(1, 2)) + x = F.gelu(x[:, :, :-1]) + return x.transpose(1, 2) + + +class TransformerEncoder(nn.Module): + def __init__( + self, encoder_layer: nn.TransformerEncoderLayer, num_layers: int + ) -> None: + super(TransformerEncoder, self).__init__() + self.layers = nn.ModuleList( + [copy.deepcopy(encoder_layer) for _ in range(num_layers)] + ) + self.num_layers = num_layers + + def forward( + self, + src: torch.Tensor, + mask: torch.Tensor = None, + src_key_padding_mask: torch.Tensor = None, + output_layer: Optional[int] = None, + ) -> torch.Tensor: + output = src + for layer in self.layers[:output_layer]: + output = layer( + output, src_mask=mask, src_key_padding_mask=src_key_padding_mask + ) + return output + + +def _compute_mask( + shape: Tuple[int, int], + mask_prob: float, + mask_length: int, + device: torch.device, + min_masks: int = 0, +) -> torch.Tensor: + batch_size, sequence_length = shape + + if mask_length < 1: + raise ValueError("`mask_length` has to be bigger than 0.") + + if mask_length > sequence_length: + raise ValueError( + f"`mask_length` has to be smaller than `sequence_length`, but got `mask_length`: {mask_length} and `sequence_length`: {sequence_length}`" + ) + + # compute number of masked spans in batch + num_masked_spans = int(mask_prob * sequence_length / mask_length + random.random()) + num_masked_spans = max(num_masked_spans, min_masks) + + # make sure num masked indices <= sequence_length + if num_masked_spans * mask_length > sequence_length: + num_masked_spans = sequence_length // mask_length + + # SpecAugment mask to fill + mask = torch.zeros((batch_size, sequence_length), device=device, dtype=torch.bool) + + # uniform distribution to sample from, make sure that offset samples are < sequence_length + uniform_dist = torch.ones( + (batch_size, sequence_length - (mask_length - 1)), device=device + ) + + # get random indices to mask + mask_indices = torch.multinomial(uniform_dist, num_masked_spans) + + # expand masked indices to masked spans + mask_indices = ( + mask_indices.unsqueeze(dim=-1) + .expand((batch_size, num_masked_spans, mask_length)) + .reshape(batch_size, num_masked_spans * mask_length) + ) + offsets = ( + torch.arange(mask_length, device=device)[None, None, :] + .expand((batch_size, num_masked_spans, mask_length)) + .reshape(batch_size, num_masked_spans * mask_length) + ) + mask_idxs = mask_indices + offsets + + # scatter indices to mask + mask = mask.scatter(1, mask_idxs, True) + + return mask + + +def hubert_discrete( + pretrained: bool = True, + progress: bool = True, +) -> HubertDiscrete: + r"""HuBERT-Discrete from `"A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion"`. + Args: + pretrained (bool): load pretrained weights into the model + progress (bool): show progress bar when downloading model + """ + kmeans = kmeans100(pretrained=pretrained, progress=progress) + hubert = HubertDiscrete(kmeans) + if pretrained: + checkpoint = torch.hub.load_state_dict_from_url( + URLS["hubert-discrete"], progress=progress + ) + consume_prefix_in_state_dict_if_present(checkpoint, "module.") + hubert.load_state_dict(checkpoint) + hubert.eval() + return hubert + + +def hubert_soft( + pretrained: bool = True, + progress: bool = True, +) -> HubertSoft: + r"""HuBERT-Soft from `"A Comparison of Discrete and Soft Speech Units for Improved Voice Conversion"`. + Args: + pretrained (bool): load pretrained weights into the model + progress (bool): show progress bar when downloading model + """ + hubert = HubertSoft() + if pretrained: + checkpoint = torch.hub.load_state_dict_from_url( + URLS["hubert-soft"], progress=progress + ) + consume_prefix_in_state_dict_if_present(checkpoint, "module.") + hubert.load_state_dict(checkpoint) + hubert.eval() + return hubert + + +def _kmeans( + num_clusters: int, pretrained: bool = True, progress: bool = True +) -> KMeans: + kmeans = KMeans(num_clusters) + if pretrained: + checkpoint = torch.hub.load_state_dict_from_url( + URLS[f"kmeans{num_clusters}"], progress=progress + ) + kmeans.__dict__["n_features_in_"] = checkpoint["n_features_in_"] + kmeans.__dict__["_n_threads"] = checkpoint["_n_threads"] + kmeans.__dict__["cluster_centers_"] = checkpoint["cluster_centers_"].numpy() + return kmeans + + +def kmeans100(pretrained: bool = True, progress: bool = True) -> KMeans: + r""" + k-means checkpoint for HuBERT-Discrete with 100 clusters. + Args: + pretrained (bool): load pretrained weights into the model + progress (bool): show progress bar when downloading model + """ + return _kmeans(100, pretrained, progress) diff --git a/voice_conversion/softvc/hubert/hubert/utils.py b/voice_conversion/softvc/hubert/hubert/utils.py new file mode 100644 index 0000000..8519e7a --- /dev/null +++ b/voice_conversion/softvc/hubert/hubert/utils.py @@ -0,0 +1,58 @@ +import torch + + +class Metric: + def __init__(self): + self.steps = 0 + self.value = 0 + + def update(self, value): + self.steps += 1 + self.value += (value - self.value) / self.steps + return self.value + + def reset(self): + self.steps = 0 + self.value = 0 + + +def save_checkpoint( + checkpoint_dir, + hubert, + optimizer, + scaler, + step, + loss, + best, + logger, +): + state = { + "hubert": hubert.state_dict(), + "optimizer": optimizer.state_dict(), + "scaler": scaler.state_dict(), + "step": step, + "loss": loss, + } + checkpoint_dir.mkdir(exist_ok=True, parents=True) + checkpoint_path = checkpoint_dir / f"model-{step}.pt" + torch.save(state, checkpoint_path) + if best: + best_path = checkpoint_dir / "model-best.pt" + torch.save(state, best_path) + logger.info(f"Saved checkpoint: {checkpoint_path.stem}") + + +def load_checkpoint( + load_path, + hubert, + optimizer, + scaler, + rank, + logger, +): + logger.info(f"Loading checkpoint from {load_path}") + checkpoint = torch.load(load_path, map_location={"cuda:0": f"cuda:{rank}"}) + hubert.load_state_dict(checkpoint["hubert"]) + scaler.load_state_dict(checkpoint["scaler"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + return checkpoint["step"], checkpoint["loss"] diff --git a/voice_conversion/softvc/hubert/train.py b/voice_conversion/softvc/hubert/train.py new file mode 100644 index 0000000..fc01575 --- /dev/null +++ b/voice_conversion/softvc/hubert/train.py @@ -0,0 +1,453 @@ +import argparse +import logging +from pathlib import Path + +import torch +import torch.cuda.amp as amp +import torch.nn as nn +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 torch.nn.modules.utils import consume_prefix_in_state_dict_if_present + +from hubert.model import Hubert, URLS +from hubert.dataset import AcousticUnitsDataset +from hubert.utils import Metric, save_checkpoint, load_checkpoint + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +######################################################################################## +# Define hyperparameters for training: +######################################################################################## + +BATCH_SIZE = 64 +LEARNING_RATE = 2e-5 +BETAS = (0.9, 0.98) +EPS = 1e-06 +WEIGHT_DECAY = 1e-2 +MAX_NORM = 10 +STEPS = 25000 +LOG_INTERVAL = 5 +VALIDATION_INTERVAL = 1000 +CHECKPOINT_INTERVAL = 1000 +BACKEND = "nccl" +INIT_METHOD = "tcp://localhost:54321" + + +def train(rank, world_size, args): + dist.init_process_group( + BACKEND, + rank=rank, + world_size=world_size, + init_method=INIT_METHOD, + ) + + #################################################################################### + # Setup logging utilities: + #################################################################################### + + log_dir = args.checkpoint_dir / "logs" + log_dir.mkdir(exist_ok=True, parents=True) + + if rank == 0: + logger.setLevel(logging.INFO) + handler = logging.FileHandler(log_dir / f"{args.checkpoint_dir.stem}.log") + handler.setLevel(logging.INFO) + 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 + + #################################################################################### + # Initialize models + #################################################################################### + + hubert = Hubert(mask=args.mask).to(rank) + + if args.warmstart: + checkpoint = torch.hub.load_state_dict_from_url( + URLS["hubert-discrete"], map_location={"cuda:0": f"cuda:{rank}"} + ) + consume_prefix_in_state_dict_if_present(checkpoint, "module.") + hubert.load_state_dict(checkpoint, strict=False) + + hubert = DDP(hubert, device_ids=[rank]) + + #################################################################################### + # Initialze optimizer and grad scaler + #################################################################################### + + optimizer = optim.AdamW( + hubert.parameters(), + lr=LEARNING_RATE, + betas=BETAS, + eps=EPS, + weight_decay=WEIGHT_DECAY, + ) + scaler = amp.GradScaler() + + #################################################################################### + # Initialize datasets and dataloaders + #################################################################################### + + train_dataset = AcousticUnitsDataset( + root=args.dataset_dir, + train=True, + ) + train_sampler = DistributedSampler(train_dataset, drop_last=True) + train_loader = DataLoader( + train_dataset, + collate_fn=train_dataset.collate, + batch_size=BATCH_SIZE, + sampler=train_sampler, + num_workers=8, + pin_memory=True, + shuffle=False, + drop_last=True, + ) + + validation_dataset = AcousticUnitsDataset( + root=args.dataset_dir, + train=False, + ) + validation_loader = DataLoader( + validation_dataset, + batch_size=1, + shuffle=False, + num_workers=8, + pin_memory=True, + ) + + #################################################################################### + # Load checkpoint if args.resume is set + #################################################################################### + + if args.resume is not None: + global_step, best_loss = load_checkpoint( + load_path=args.resume, + hubert=hubert, + optimizer=optimizer, + scaler=scaler, + rank=rank, + logger=logger, + ) + else: + global_step, best_loss = 0, float("inf") + + # =================================================================================# + # Start training loop + # =================================================================================# + + n_epochs = STEPS // len(train_loader) + 1 + start_epoch = global_step // len(train_loader) + 1 + + logger.info("**" * 40) + 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()}") + logger.info(f"batch size: {BATCH_SIZE}") + logger.info(f"iterations per epoch: {len(train_loader)}") + logger.info(f"# of epochs: {n_epochs}") + logger.info(f"started at epoch: {start_epoch}") + logger.info("**" * 40 + "\n") + + if args.mask: + average_masked_loss = Metric() + average_unmasked_loss = Metric() + average_masked_accuracy = Metric() + average_unmasked_accuracy = Metric() + + epoch_masked_loss = Metric() + epoch_unmasked_loss = Metric() + epoch_masked_accuracy = Metric() + epoch_unmasked_accuracy = Metric() + else: + average_loss = Metric() + average_accuracy = Metric() + + epoch_loss = Metric() + epoch_accuracy = Metric() + + validation_loss = Metric() + validation_accuracy = Metric() + + for epoch in range(start_epoch, n_epochs + 1): + train_sampler.set_epoch(epoch) + + hubert.train() + if args.mask: + epoch_masked_loss.reset() + epoch_unmasked_loss.reset() + epoch_masked_accuracy.reset() + epoch_unmasked_accuracy.reset() + else: + epoch_loss.reset() + epoch_accuracy.reset() + + for wavs, codes in train_loader: + global_step += 1 + wavs, codes = wavs.to(rank), codes.to(rank) + + ############################################################################ + # Compute training loss + ############################################################################ + + optimizer.zero_grad() + + with amp.autocast(): + logits, mask = hubert(wavs) + length = min( + mask.size(-1) if args.mask else float("inf"), codes.size(-1) + ) + logits = logits[:, :length, :] + codes = codes[:, :length] + if args.mask: + mask = mask[:, :length] + + if args.mask: + masked_loss = F.cross_entropy(logits[mask], codes[mask]) + unmasked_loss = F.cross_entropy(logits[~mask], codes[~mask]) + loss = args.alpha * masked_loss + (1 - args.alpha) * unmasked_loss + else: + loss = F.cross_entropy(logits.transpose(1, 2), codes) + + scaler.scale(loss).backward() + scaler.unscale_(optimizer) + + nn.utils.clip_grad_norm_(hubert.parameters(), MAX_NORM) + + scaler.step(optimizer) + scaler.update() + + if args.mask: + masked_accuracy = logits[mask].argmax(dim=-1) == codes[mask] + masked_accuracy = torch.mean(masked_accuracy.float()) + + unmasked_accuracy = logits[~mask].argmax(dim=-1) == codes[~mask] + unmasked_accuracy = torch.mean(unmasked_accuracy.float()) + else: + accuracy = logits.argmax(dim=-1) == codes + accuracy = torch.mean(accuracy.float()) + + ############################################################################ + # Update and log training metrics + ############################################################################ + + if args.mask: + average_masked_loss.update(masked_loss.item()) + average_unmasked_loss.update(unmasked_loss.item()) + average_masked_accuracy.update(masked_accuracy.item()) + average_unmasked_accuracy.update(unmasked_accuracy.item()) + + epoch_masked_loss.update(masked_loss.item()) + epoch_unmasked_loss.update(unmasked_loss.item()) + epoch_masked_accuracy.update(masked_accuracy.item()) + epoch_unmasked_accuracy.update(unmasked_accuracy.item()) + else: + average_loss.update(loss.item()) + average_accuracy.update(accuracy.item()) + + epoch_loss.update(loss.item()) + epoch_accuracy.update(accuracy.item()) + + if rank == 0 and global_step % LOG_INTERVAL == 0: + if args.mask: + writer.add_scalar( + "train/masked_loss", + average_masked_loss.value, + global_step, + ) + writer.add_scalar( + "train/unmasked_loss", + average_unmasked_loss.value, + global_step, + ) + writer.add_scalar( + "train/masked_accuracy", + average_masked_accuracy.value * 100, + global_step, + ) + writer.add_scalar( + "train/unmasked_accuracy", + average_unmasked_accuracy.value * 100, + global_step, + ) + average_masked_loss.reset() + average_unmasked_loss.reset() + average_masked_accuracy.reset() + average_unmasked_accuracy.reset() + else: + writer.add_scalar( + "train/loss", + average_loss.value, + global_step, + ) + writer.add_scalar( + "train/accuracy", + average_accuracy.value, + global_step, + ) + average_loss.reset() + average_accuracy.reset() + + # --------------------------------------------------------------------------# + # Start validation loop + # --------------------------------------------------------------------------# + + if global_step % VALIDATION_INTERVAL == 0: + hubert.eval() + validation_loss.reset() + validation_accuracy.reset() + for wavs, codes in validation_loader: + wavs, codes = wavs.to(rank), codes.to(rank) + + with torch.no_grad(): + logits, _ = hubert(wavs) + logits = logits.transpose(1, 2) + + loss = F.cross_entropy(logits, codes) + + accuracy = logits.argmax(dim=1) == codes + accuracy = torch.mean(accuracy.float()) + + #################################################################### + # Update validation metrics + #################################################################### + + validation_loss.update(loss.item()) + validation_accuracy.update(accuracy.item()) + + hubert.train() + + ############################################################################ + # Log validation metrics + ############################################################################ + + if rank == 0: + writer.add_scalar( + "validation/unit_loss", + validation_loss.value, + global_step, + ) + writer.add_scalar( + "validation/unit_accuracy", + validation_accuracy.value * 100, + global_step, + ) + logger.info( + f"valid -- epoch: {epoch}, loss: {validation_loss.value:.4f}, accuracy: {validation_accuracy.value * 100:.2f}" + ) + + ############################################################################ + # Save model checkpoint + ############################################################################ + + new_best = best_loss > validation_loss.value + if new_best or global_step % CHECKPOINT_INTERVAL == 0: + if new_best: + logger.info("-------- new best model found!") + best_loss = validation_loss.value + + if rank == 0: + save_checkpoint( + checkpoint_dir=args.checkpoint_dir, + hubert=hubert, + optimizer=optimizer, + scaler=scaler, + step=global_step, + loss=validation_loss.value, + best=new_best, + logger=logger, + ) + + # -----------------------------------------------------------------------------# + # End validation loop + # -----------------------------------------------------------------------------# + + #################################################################################### + # Log training metrics + #################################################################################### + + logger.info( + f""" + train -- epoch: {epoch}, masked loss: {epoch_masked_loss.value:.4f}, unmasked loss: {epoch_unmasked_loss.value:.4f}, + masked accuracy: {epoch_masked_accuracy.value * 100:.2f}, umasked accuracy: {epoch_unmasked_accuracy.value * 100:.2f} + """ + ) + + # ==================================================================================# + # End training loop + # ==================================================================================# + + dist.destroy_process_group() + + +def train_hubert(args): + world_size = torch.cuda.device_count() + mp.spawn( + train, + args=(world_size, args), + nprocs=world_size, + join=True, + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Train HuBERT soft content encoder.") + parser.add_argument( + "dataset_dir", + metavar="dataset-dir", + help="path to the 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( + "--warmstart", + help="whether to initialize from the fairseq HuBERT checkpoint.", + action="store_true", + ) + parser.add_argument( + "--mask", + help="whether to use input masking.", + action="store_true", + ) + parser.add_argument( + "--alpha", + help="weight for the masked loss.", + default=1, + type=float, + ) + args = parser.parse_args() + + world_size = torch.cuda.device_count() + mp.spawn( + train, + args=(world_size, args), + nprocs=world_size, + join=True, + ) diff --git a/voice_conversion/softvc/softvctraceandonnx.py b/voice_conversion/softvc/softvctraceandonnx.py new file mode 100644 index 0000000..bea11ee --- /dev/null +++ b/voice_conversion/softvc/softvctraceandonnx.py @@ -0,0 +1,508 @@ +# -*- coding: utf-8 -*- +"""SoftVCTraceAndOnnx.ipynb + +Automatically generated by Colaboratory. + +Original file is located at + https://colab.research.google.com/drive/1g0YsQQY8fzXHmZUWAMzQO60c6OUlyKBl +""" + +!pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu116 +!pip3 install sklearn +!pip3 install soundfile +!pip3 install matplotlib + +!git clone https://github.com/bshall/hubert +!git clone https://github.com/bshall/acoustic-model +!git clone https://github.com/bshall/hifigan + +!nvidia-smi + +from google.colab import drive +drive.mount('/content/drive') + +# Torch Script Inference Hubert +import copy +from typing import Optional, Tuple +import random + +from sklearn.cluster import KMeans + +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn.modules.utils import consume_prefix_in_state_dict_if_present +import torch.nn.utils as utils + +from torch.nn.utils.weight_norm import WeightNorm +from typing import Dict, Any +from collections import OrderedDict + +def remove_weight_norm(module): + module_list = [mod for mod in module.children()] + if len(module_list) == 0: + for k, hook in module._forward_pre_hooks.items(): + if isinstance(hook, WeightNorm): + hook.remove(module) + del module._forward_pre_hooks[k] + else: + for mod in module_list: + remove_weight_norm(mod) + + +def modify_model(state_dict:Dict[str,Any]): + new_state_dict = OrderedDict() + for k in state_dict['acoustic-model'].keys(): + value = state_dict['acoustic-model'][k] + name = k[7:] # remove `module.` because they used ddp training to save + new_state_dict[name] = value + return new_state_dict + +# @torch.jit.export + # def generate(self, x: torch.Tensor) -> torch.Tensor: + # x = self.encoder(x) + # return self.decoder.generate(x) + class AcousticModelScripted(nn.Module): + def __init__(self, discrete: bool = False, upsample: bool = True): + super().__init__() + self.encoder = Encoder(discrete, upsample) + self.decoder = Decoder() + + def forward(self, x: torch.Tensor, mels: torch.Tensor) -> torch.Tensor: + x = self.encoder(x) + return self.decoder(x, mels) + + @torch.jit.export + def generate(self, x: torch.Tensor) -> torch.Tensor: + x = self.encoder(x) + return self.decoder.generate(x) + + + class Encoder(nn.Module): + def __init__(self, discrete: bool = False, upsample: bool = True): + super().__init__() + self.embedding = nn.Embedding(100 + 1, 256) if discrete else None + self.prenet = PreNet(256, 256, 256) + self.convs = nn.Sequential( + nn.Conv1d(256, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + nn.ConvTranspose1d(512, 512, 4, 2, 1) if upsample else nn.Identity(), + nn.Conv1d(512, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + nn.Conv1d(512, 512, 5, 1, 2), + nn.ReLU(), + nn.InstanceNorm1d(512), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + if self.embedding is not None: + x = self.embedding(x) + x = self.prenet(x) + x = self.convs(x.transpose(1, 2)) + return x.transpose(1, 2) + + + class Decoder(nn.Module): + def __init__(self): + super().__init__() + self.prenet = PreNet(128, 256, 256) + self.lstm1 = nn.LSTM(512 + 256, 768, batch_first=True) + self.lstm2 = nn.LSTM(768, 768, batch_first=True) + self.lstm3 = nn.LSTM(768, 768, batch_first=True) + self.proj = nn.Linear(768, 128, bias=False) + + def forward(self, x: torch.Tensor, mels: torch.Tensor) -> torch.Tensor: + mels = self.prenet(mels) + x, _ = self.lstm1(torch.cat((x, mels), dim=-1)) + res = x + x, _ = self.lstm2(x) + x = res + x + res = x + x, _ = self.lstm3(x) + x = res + x + return self.proj(x) + + @torch.inference_mode() + def generate(self, xs: torch.Tensor) -> torch.Tensor: + m = torch.zeros(xs.size(0), 128, device=xs.device) + h1 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c1 = torch.zeros(1, xs.size(0), 768, device=xs.device) + h2 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c2 = torch.zeros(1, xs.size(0), 768, device=xs.device) + h3 = torch.zeros(1, xs.size(0), 768, device=xs.device) + c3 = torch.zeros(1, xs.size(0), 768, device=xs.device) + + mel = [] + for x in torch.unbind(xs, dim=1): + m = self.prenet(m) + x = torch.cat((x, m), dim=1).unsqueeze(1) + x1, (h1, c1) = self.lstm1(x, (h1, c1)) + x2, (h2, c2) = self.lstm2(x1, (h2, c2)) + x = x1 + x2 + x3, (h3, c3) = self.lstm3(x, (h3, c3)) + x = x + x3 + m = self.proj(x).squeeze(1) + mel.append(m) + return torch.stack(mel, dim=1) + + + class PreNet(nn.Module): + def __init__( + self, + input_size: int, + hidden_size: int, + output_size: int, + dropout: float = 0.5, + ): + super().__init__() + self.net = nn.Sequential( + nn.Linear(input_size, hidden_size), + nn.ReLU(), + nn.Dropout(dropout), + nn.Linear(hidden_size, output_size), + nn.ReLU(), + nn.Dropout(dropout), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.net(x) + +import IPython.display as display +import torch, torchaudio +import torch.nn as nn +import sys +import soundfile as sf + +from time import time +import torch.jit +sys.path.insert(0, './hubert/hubert') +from model import HubertSoft + +sys.path.insert(0, './acoustic-model/') +from acoustic.model import AcousticModel + +#@title select input wav +file_name = "/content/drive/MyDrive/VCModels/Source/brandon1.wav"#@param { type: "string"} + +source, sr = torchaudio.load(f"{file_name}") +source = torchaudio.functional.resample(source, sr, 16000) +source = source.unsqueeze(0).cuda() +print(f"Wav Shape and Type {source.shape} {source.dtype}") +if source.shape[1] == 2: # remove channel + source = torch.mean(source, dim=0).unsqueeze(0) + print(f"Removing Channel to mono Wav Shape and Type {source.shape} {source.dtype}") + +display.Audio(source.squeeze().cpu(), rate=16000) + +import datetime + +hubert_soft = torch.hub.load("bshall/hubert:main", "hubert_soft").cuda() +hubert_soft.eval() + +script_acoustic = True #@param {type: "boolean"} +print(script_acoustic) + +# Load the acoustic model (either hubert_soft or hubert_discrete) +input_acoustic_model_file = "/content/drive/MyDrive/VCModels/Padme/model-24000.pt" #@param {type: "string"} +acoustic_dict = torch.load(input_acoustic_model_file) # this used ddp ## REPLACE THIS! <-- ! +if script_acoustic == True: + acoustic = AcousticModelScripted().cuda() +else: + acoustic = AcousticModel().cuda() + +acoustic.eval() + +new_state_dict = modify_model(state_dict=acoustic_dict) +acoustic.load_state_dict(new_state_dict) + +import importlib.util +import sys + +spec = importlib.util.spec_from_file_location("hifigan.utils", "/content/hifigan/hifigan/utils.py") +u = importlib.util.module_from_spec(spec) +sys.modules["hifigan.utils"] = u +spec.loader.exec_module(u) + +spec = importlib.util.spec_from_file_location("hifigan.generator", "/content/hifigan/hifigan/generator.py") +g = importlib.util.module_from_spec(spec) +sys.modules["hifigan.generator"] = g +spec.loader.exec_module(g) + +new_state_dict = modify_model(state_dict=acoustic_dict) +acoustic.load_state_dict(new_state_dict) +input_hifigan_checkpoint_path = "/content/drive/MyDrive/VCModels/Hifigan/Padme.pt" #@param {type: "string"} +if input_hifigan_checkpoint_path: + checkpoint = torch.load(input_hifigan_checkpoint_path)["generator"]["model"] + hifigan = g.HifiganGenerator() + consume_prefix_in_state_dict_if_present(checkpoint, "module.") + hifigan.load_state_dict(checkpoint) + hifigan.remove_weight_norm() +else: + hifigan = torch.hub.load("bshall/hifigan:main", "hifigan_hubert_soft") + +hifigan.cuda() +hifigan.eval() + +#utils.remove_weight_norm(hubert_soft.positional_embedding.conv) +# Convert to the target speaker +units = None +mel = None + +unit_start_time = 0.0 +unit_end_time = 0.0 + +acoustic_start_time = 0.0 +acoustic_end_time = 0.0 + +hifi_start_time = 0.0 +hifi_end_time = 0.0 + +with torch.inference_mode(): + unit_start_time = datetime.datetime.now() + units = hubert_soft.units(source) + print(f"Units and Type {units.shape} {units.dtype}") + unit_end_time = datetime.datetime.now() + + hubert_soft_script = torch.jit.trace_module(hubert_soft,{"units":source}) + torch.jit.save(hubert_soft_script, 'hubert_soft.jit') + + print(f"Unit Generation Time {(unit_end_time - unit_start_time).total_seconds() * 1000}") + + print("Hubert Soft Exported") + + acoustic_start_time = datetime.datetime.now() + mel = acoustic.generate(units).transpose(1, 2) + print(f"Mel and Type {mel.shape} {mel.dtype}") + acoustic_end_time = datetime.datetime.now() + print(f"Acoustic Time {(acoustic_end_time - acoustic_start_time).total_seconds() * 1000}") + if script_acoustic == True: + output_acoustic_scripted = '/content/drive/MyDrive/VCModels/FinishedModels/Padme.recast' #@param {type: "string"} + print("Exporting Scripted") + acoustic_model_script = torch.jit.script(acoustic) + torch.jit.save(acoustic_model_script, output_acoustic_scripted) + else: + print("Exporting Traced") + acoustic_model_script = torch.jit.trace_module(acoustic,{"generate":units}) + output_acoustic_traced = '' #@param {type: "string"} + torch.jit.save(acoustic_model_script, output_acoustic_traced) + + print("Acoustic Model Exported") + hifi_start_time = datetime.datetime.now() + + target = hifigan(mel) + print(f"Wavform and Type {target.shape} {target.dtype}") + hifi_end_time = datetime.datetime.now() + print(f"MEL {target.shape}") + print(f"Hifi Gan Time {(hifi_end_time - hifi_start_time).total_seconds() * 1000}") + + hifi_gan_script = torch.jit.trace_module(hifigan,{"forward":mel}) + output_hifigan_jit = '/content/drive/MyDrive/VCModels/FinishedModels/Padme-hifigan-jit.pt' #@param {type: "string"} + torch.jit.save(hifi_gan_script, output_hifigan_jit) + print("Hifi Gan Model Exported") + +import datetime + +jit_unit_start_time = 0.0 +jit_unit_end_time = 0.0 +jit_unit_final = 0.0 + +jit_acoustic_start_time = 0.0 +jit_acoustic_end_time = 0.0 +jit_acoustic_final = 0.0 + +jit_hifi_start_time = 0.0 +jit_hifi_end_time = 0.0 +jit_hifi_final = 0.0 + +jit_model = torch.jit.load('hubert_soft.jit').cuda() +jit_model.eval() +with torch.no_grad(): + jit_unit_start_time = datetime.datetime.now() + jit_tensor = jit_model.units(source) + jit_unit_end_time = datetime.datetime.now() + jit_unit_final = jit_unit_end_time - jit_unit_start_time +print(jit_unit_final.total_seconds() * 1000) +print(jit_tensor.shape) +assert torch.allclose(jit_tensor,units) + +print(script_acoustic) +if script_acoustic == True: + acoustic_jit_model = torch.jit.load(output_acoustic_scripted).cuda() + print("Using Scripted Model") +else: + acoustic_jit_model = torch.jit.load(output_acoustic_traced).cuda() +acoustic_jit_model.eval() +with torch.no_grad(): + if script_acoustic == True: + print("Using Scripted Model") + jit_acoustic_start_time = datetime.datetime.now() + jit_mel = acoustic_jit_model.generate(jit_tensor) + else: + jit_acoustic_start_time = datetime.datetime.now() + jit_mel = acoustic_jit_model.generate(jit_tensor) + jit_acoustic_end_time = datetime.datetime.now() + jit_acoustic_final = jit_acoustic_end_time - jit_acoustic_start_time + print(jit_acoustic_final.total_seconds() * 1000) + +jit_mel = jit_mel.transpose(1,2) +print(jit_mel.shape) +assert torch.allclose(mel,jit_mel) + +hifi_gan = torch.jit.load(output_hifigan_jit).cuda() +hifi_gan.eval() +with torch.no_grad(): + jit_hifi_start_time = datetime.datetime.now() + jit_target = hifi_gan(jit_mel) + jit_hifi_end_time = datetime.datetime.now() + jit_hifi_final = jit_hifi_end_time - jit_hifi_start_time +print(jit_hifi_final.total_seconds() * 1000) +assert torch.allclose(target,jit_target) + +sf.write(f'./sample_jit.wav', jit_target.squeeze().detach().cpu().numpy(), samplerate=16000) +sf.write(f'./sample.wav', target.squeeze().cpu().detach().numpy(), samplerate=16000) + +#uploaded = files.upload() +import datetime + +jit_unit_start_time = 0.0 +jit_unit_end_time = 0.0 +jit_unit_final = 0.0 + +jit_acoustic_start_time = 0.0 +jit_acoustic_end_time = 0.0 +jit_acoustic_final = 0.0 + +jit_hifi_start_time = 0.0 +jit_hifi_end_time = 0.0 +jit_hifi_final = 0.0 + + +source, sr = torchaudio.load(f"{file_name}") +source = torchaudio.functional.resample(source, sr, 16000) +source = source.unsqueeze(0).cuda() +print(f"Wav Shape and Type {source.shape} {source.dtype}") +if source.shape[1] == 2: # remove channel + source = torch.mean(source, dim=1).unsqueeze(0) + print(f"Removing Channel to mono Wav Shape and Type {source.shape} {source.dtype}") + +display.Audio(source.squeeze().cpu(), rate=16000) + +jit_model = torch.jit.load('hubert_soft.jit').cuda() +jit_model.eval() + +if script_acoustic == True: + acoustic_jit_model = torch.jit.load(output_acoustic_scripted).cuda() + print("Loading Scripted Model") +else: + acoustic_jit_model = torch.jit.load(output_acoustic_traced).cuda() + print("Loading Tracing Model") + +acoustic_jit_model.eval() + +hifi_gan = torch.jit.load(output_hifigan_jit).cuda() +hifi_gan.eval() +print("Loaded Models") + +# Warm up the model to get the real time +for i in range(0,5): + with torch.no_grad(): + jit_unit_start_time = datetime.datetime.now() + jit_tensor = jit_model.units(source) + jit_unit_end_time = datetime.datetime.now() + + if script_acoustic == True: + print("Using Scripted Model TT2") + jit_acoustic_start_time = datetime.datetime.now() + jit_mel = acoustic_jit_model.generate(jit_tensor) + else: + print("Using Traced Model TT2") + jit_acoustic_start_time = datetime.datetime.now() + jit_mel = acoustic_jit_model.generate(jit_tensor) + jit_mel = jit_mel.transpose(1, 2) + + jit_acoustic_end_time = datetime.datetime.now() + + jit_hifi_start_time = datetime.datetime.now() + jit_target = hifi_gan(jit_mel) + jit_hifi_end_time = datetime.datetime.now() + + +jit_unit_final = jit_unit_end_time - jit_unit_start_time +jit_acoustic_final = jit_acoustic_end_time - jit_acoustic_start_time +jit_hifi_final = jit_hifi_end_time - jit_hifi_start_time + +print(f"16 Khz Wav Shape {source.shape}") + +print(f"Jit Tensor Shape for Unit Inputs: {jit_tensor.shape}") +print(f"Hubert Runtime : {jit_unit_final.total_seconds() * 1000}") + +print(f"Tactron 2 Runtime Mel Output Shape: {jit_acoustic_final.total_seconds() * 1000}") +print(f"Jit Tensor Shape for Mel output: {jit_mel.shape}") + +print(f"Hifi Runtime Mel Output Shape: {jit_hifi_final.total_seconds() * 1000}") +print(f"Jit Tensor Shape for Mel output: {jit_target.shape}") + +sf.write(f'./{file_name}_jit.wav', jit_target.squeeze().detach().cpu().numpy(), samplerate=16000) +display.Audio(jit_target.squeeze().cpu(), rate=16000) + +# silero vad +import datetime + + +source, sr = torchaudio.load(f"{file_name}") +source = torchaudio.functional.resample(source, sr, 16000) + +print(source.shape) + +input_vad_file = "/content/drive/MyDrive/tts/softvc-checkpoints/silero_vad.jit" #@param {type: "string"} + +class VADIterator: + # You will only need to verify the 100 ms chunks to see this checks a minute of audio + # sound_sample_size run vad in 30 ms, 60 ms or 100 ms + def __init__(self,threshold: float = 0.5, sampling_rate: int = 16000,sound_sample_size:float = 100): + self.silero_jit = torch.jit.load(input_vad_file).cuda() + self.silero_jit.eval() + self.sampling_rate = sampling_rate + self.sound_sample_size = sound_sample_size + + jit_silero_start_time = 0.0 + jit_silero_end_time = 0.0 + jit_silero_final = 0.0 + + def operating_frame_size(self,sampling_rate:int,ms_of_audio:float): + return int((ms_of_audio * sampling_rate) / 1000) # number of frames + + def check_voice_activity(self,source_sample) -> float: + probs_over_time = [] + # formula we know that we can get 1600 operating frames / 16000 sr * 1000 = 100 ms of audio from 1600 frames. + # this gives you the operating frames given the audio size you want. + op_frame_size = self.operating_frame_size(sampling_rate=self.sampling_rate,ms_of_audio=self.sound_sample_size) + print(op_frame_size) + iters = int(source_sample.shape[-1] / op_frame_size) # get the chunks + #print(iters) + for i in range(0,iters): # get sound_sample_size ms chunks + + #print(window_size) + chunk = source_sample[i * op_frame_size:(i + 1) * op_frame_size] + with torch.no_grad():# slide window across using VAD 100ms + jit_silero_start_time = datetime.datetime.now() + try: + print(f"Current {i}") + prob = self.silero_jit(chunk.cuda(),sr=self.sampling_rate).item() # input shape is [1*] just an array of samples. 1 = false + jit_silero_end_time = datetime.datetime.now() + print(f"Probability: {prob}") + jit_silero_final = jit_silero_end_time - jit_silero_start_time + #print(f"Silero VAD Time: {jit_silero_final.total_seconds() * 1000}") + probs_over_time.append(prob) + except Exception as e: + print(e) + + return probs_over_time + +iterator = VADIterator(sound_sample_size=100) +source_sample = source_sample=source.squeeze().squeeze() +print(source_sample.shape) +probs = iterator.check_voice_activity(source_sample=source_sample) +#print(probs) \ No newline at end of file