Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +11 -2
- app.py +139 -19
- core.py +44 -13
- lscodec/__init__.py +1 -0
- lscodec/bin/__init__.py +0 -0
- lscodec/bin/decode_feat_prompt.py +178 -0
- lscodec/bin/decode_wav_prompt.py +190 -0
- lscodec/bin/encode.py +143 -0
- lscodec/bin/recon_with_prompt.py +235 -0
- lscodec/bin/train.py +994 -0
- lscodec/datasets/perturbs.py +308 -0
- lscodec/datasets/scp_dataset.py +381 -0
- lscodec/distributed/__init__.py +0 -0
- lscodec/distributed/distrib.py +124 -0
- lscodec/distributed/launch.py +163 -0
- lscodec/layers/__init__.py +6 -0
- lscodec/layers/causal_conv.py +66 -0
- lscodec/layers/pqmf.py +150 -0
- lscodec/layers/residual_block.py +222 -0
- lscodec/layers/residual_stack.py +85 -0
- lscodec/layers/tade_res_block.py +160 -0
- lscodec/layers/upsample.py +194 -0
- lscodec/models/__init__.py +3 -0
- lscodec/models/conformer/__init__.py +1 -0
- lscodec/models/conformer/argument.py +87 -0
- lscodec/models/conformer/attention.py +308 -0
- lscodec/models/conformer/contextual_block_encoder_layer.py +309 -0
- lscodec/models/conformer/convolution.py +79 -0
- lscodec/models/conformer/decoder.py +247 -0
- lscodec/models/conformer/decoder_layer.py +175 -0
- lscodec/models/conformer/embedding.py +244 -0
- lscodec/models/conformer/encoder.py +247 -0
- lscodec/models/conformer/encoder_layer.py +157 -0
- lscodec/models/conformer/layer_norm.py +42 -0
- lscodec/models/conformer/multi_layer_conv.py +105 -0
- lscodec/models/conformer/nets_utils.py +498 -0
- lscodec/models/conformer/positionwise_feed_forward.py +32 -0
- lscodec/models/conformer/repeat.py +33 -0
- lscodec/models/conformer/subsampling.py +257 -0
- lscodec/models/conformer/swish.py +18 -0
- lscodec/models/conformer/vgg2l.py +89 -0
- lscodec/models/ctx_v2w.py +108 -0
- lscodec/models/fairseq_modules/__init__.py +0 -0
- lscodec/models/fairseq_modules/fp32_group_norm.py +133 -0
- lscodec/models/fairseq_modules/gumbel_vector_quantizer.py +212 -0
- lscodec/models/fairseq_modules/kmeans_vector_quantizer.py +206 -0
- lscodec/models/fairseq_modules/layer_norm.py +48 -0
- lscodec/models/fairseq_modules/transpose_last.py +21 -0
- lscodec/models/feature_extractor.py +178 -0
- lscodec/models/hifigan.py +732 -0
README.md
CHANGED
|
@@ -8,7 +8,7 @@ sdk_version: 6.30.0
|
|
| 8 |
app_file: app.py
|
| 9 |
short_description: FocalCodec weights at int4–int32 vs fp4–fp32
|
| 10 |
python_version: "3.12"
|
| 11 |
-
startup_duration_timeout:
|
| 12 |
---
|
| 13 |
|
| 14 |
# ⚡ FocalCodec 12.5 Hz — weight quantization lab
|
|
@@ -28,7 +28,7 @@ The token stream is untouched, so the codec still runs at **0.1625 kbps**
|
|
| 28 |
| --- | --- |
|
| 29 |
| **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 levels |
|
| 30 |
| **Playground** | Encode → decode a single clip at one chosen level, with its metrics row |
|
| 31 |
-
| **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 8 levels, plus a summary chart |
|
| 32 |
|
| 33 |
## Method
|
| 34 |
|
|
@@ -37,6 +37,10 @@ absmax scale, round-to-nearest in the target format, dequantize back to fp32, th
|
|
| 37 |
the untouched FocalCodec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
|
| 38 |
and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
|
| 39 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
|
| 41 |
expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
|
| 42 |
[FocalCodec paper](https://arxiv.org/abs/2502.04465) reports SI-SNR ≈ −37 dB for the
|
|
@@ -48,3 +52,8 @@ untouched model. Use **STOI** (intelligibility, higher is better) and **LSD**
|
|
| 48 |
Model, method and example audio by
|
| 49 |
[Luca Della Libera](https://huggingface.co/lucadellalib) et al. — Apache-2.0.
|
| 50 |
[FocalCodec: Low-Bitrate Speech Coding via Focal Modulation Networks](https://arxiv.org/abs/2502.04465), NeurIPS 2025.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
short_description: FocalCodec weights at int4–int32 vs fp4–fp32
|
| 10 |
python_version: "3.12"
|
| 11 |
+
startup_duration_timeout: 1h
|
| 12 |
---
|
| 13 |
|
| 14 |
# ⚡ FocalCodec 12.5 Hz — weight quantization lab
|
|
|
|
| 28 |
| --- | --- |
|
| 29 |
| **Compare** | One reconstructed clip + input/reconstruction spectrograms, side by side for all 8 levels |
|
| 30 |
| **Playground** | Encode → decode a single clip at one chosen level, with its metrics row |
|
| 31 |
+
| **Benchmark** | SI-SDR, STOI, LSD, stored model size, bitrate and real-time factor for all 8 levels, plus an `lscodec-25hz` baseline row and a summary chart |
|
| 32 |
|
| 33 |
## Method
|
| 34 |
|
|
|
|
| 37 |
the untouched FocalCodec forward pass. `fp4` is IEEE-style E2M1, `fp8` is E4M3, `fp16`
|
| 38 |
and `fp32` are exact casts. Nothing is fine-tuned or calibrated on data.
|
| 39 |
|
| 40 |
+
The Benchmark tab also runs [LSCodec-25 Hz](https://github.com/X-LANCE/LSCodec-Inference)
|
| 41 |
+
(self-prompted with WavLM-Large) as a second codec for reference: it is a different model
|
| 42 |
+
at a different bitrate (0.25 kbps vs 0.1625 kbps), so read it as a floor, not a control.
|
| 43 |
+
|
| 44 |
⚠️ **On SI-SDR:** it is deeply negative at every level, including `fp32`. That is
|
| 45 |
expected — at 0.16 kbps FocalCodec does not preserve waveform phase, and the
|
| 46 |
[FocalCodec paper](https://arxiv.org/abs/2502.04465) reports SI-SNR ≈ −37 dB for the
|
|
|
|
| 52 |
Model, method and example audio by
|
| 53 |
[Luca Della Libera](https://huggingface.co/lucadellalib) et al. — Apache-2.0.
|
| 54 |
[FocalCodec: Low-Bitrate Speech Coding via Focal Modulation Networks](https://arxiv.org/abs/2502.04465), NeurIPS 2025.
|
| 55 |
+
|
| 56 |
+
The packed `int8` / `fp8` checkpoints produced by this lab live in
|
| 57 |
+
[`ALWOAL/focalcodec_12_5hz_quantized`](https://huggingface.co/ALWOAL/focalcodec_12_5hz_quantized).
|
| 58 |
+
The baseline codec is [LSCodec-Inference](https://github.com/X-LANCE/LSCodec-Inference) by
|
| 59 |
+
Yiwei Guo et al. — MIT.
|
app.py
CHANGED
|
@@ -3,17 +3,22 @@
|
|
| 3 |
import hashlib
|
| 4 |
import time
|
| 5 |
|
| 6 |
-
import spaces # must precede torch: patches torch.cuda for ZeroGPU
|
| 7 |
-
|
| 8 |
import gradio as gr
|
|
|
|
| 9 |
import soundfile as sf
|
|
|
|
| 10 |
import torch
|
|
|
|
| 11 |
|
| 12 |
import core
|
| 13 |
from core import QUANT_LEVELS
|
| 14 |
|
| 15 |
MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
|
| 16 |
HUB_REPO = "lucadellalib/focalcodec"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
MAX_SECONDS = 20
|
| 18 |
HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
|
| 19 |
DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
|
|
@@ -32,33 +37,125 @@ SAMPLE_RATE = codec.sample_rate_input
|
|
| 32 |
assert codec.sample_rate_output == SAMPLE_RATE, "demo assumes a 16/16 kHz codec"
|
| 33 |
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
def _estimate_duration(audio_path: str, levels: tuple) -> float:
|
| 36 |
try:
|
| 37 |
seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
|
| 38 |
-
except Exception:
|
| 39 |
seconds = float(MAX_SECONDS)
|
| 40 |
-
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
@spaces.GPU(duration=_estimate_duration)
|
| 44 |
def _encode_decode(audio_path: str, levels: tuple) -> dict:
|
| 45 |
"""Reconstruct `audio_path` once per weight-quantization level, scoring each result."""
|
| 46 |
ref, sr = core.load_audio(audio_path, SAMPLE_RATE, MAX_SECONDS)
|
| 47 |
-
|
| 48 |
-
with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
|
| 49 |
-
codec.sig_to_sig(sig)
|
| 50 |
rows, reconstructed = [], {}
|
| 51 |
-
for level in levels
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
torch.cuda.synchronize()
|
| 54 |
start = time.perf_counter()
|
| 55 |
-
|
| 56 |
-
rec = codec.sig_to_sig(sig).squeeze(0).float().cpu().numpy()
|
| 57 |
torch.cuda.synchronize()
|
| 58 |
-
rtf = (time.perf_counter() - start) /
|
| 59 |
-
rows.append(core.evaluate(ref, rec, sr, rtf,
|
| 60 |
-
reconstructed[level] = (sr, rec)
|
| 61 |
-
codec.load_state_dict(BASE_STATE)
|
| 62 |
return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
|
| 63 |
|
| 64 |
|
|
@@ -67,6 +164,7 @@ def _table(rows: list[dict]) -> list[list]:
|
|
| 67 |
|
| 68 |
|
| 69 |
_CACHE: dict[str, dict] = {}
|
|
|
|
| 70 |
|
| 71 |
|
| 72 |
def run(audio_path: str, levels: list | tuple) -> dict:
|
|
@@ -109,10 +207,23 @@ def playground(audio_path: str, level: str):
|
|
| 109 |
)
|
| 110 |
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
def benchmark(audio_path: str):
|
| 113 |
"""Score every quantization level on one input: metrics table plus summary chart."""
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
| 116 |
|
| 117 |
|
| 118 |
HEADER = f"""# ⚡ FocalCodec 12.5 Hz — weight quantization lab
|
|
@@ -127,7 +238,9 @@ The bitrate never changes — **{core.BITRATE_KBPS:g} kbps** (12.5 tokens/s × 1
|
|
| 127 |
"""
|
| 128 |
|
| 129 |
FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
|
| 130 |
-
(LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0.
|
|
|
|
|
|
|
| 131 |
|
| 132 |
with gr.Blocks(title="FocalCodec quantization lab") as demo:
|
| 133 |
gr.Markdown(HEADER)
|
|
@@ -183,13 +296,20 @@ with gr.Blocks(title="FocalCodec quantization lab") as demo:
|
|
| 183 |
sources=["upload", "microphone"], type="filepath", label="Input audio"
|
| 184 |
)
|
| 185 |
gr.Examples(examples=EXAMPLES, inputs=bm_input, label="Examples")
|
| 186 |
-
bm_button = gr.Button("Run all
|
| 187 |
with gr.Column(scale=3):
|
| 188 |
gr.Markdown(
|
| 189 |
"**STOI** ↑ intelligibility · **LSD** ↓ spectral error · "
|
| 190 |
"**RTF** = encode+decode time ÷ audio duration · size assumes "
|
| 191 |
"the stored weight format."
|
| 192 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
|
| 194 |
bm_plot = gr.Plot(label="Quality and size per level")
|
| 195 |
bm_button.click(benchmark, inputs=bm_input, outputs=[bm_table, bm_plot])
|
|
|
|
| 3 |
import hashlib
|
| 4 |
import time
|
| 5 |
|
|
|
|
|
|
|
| 6 |
import gradio as gr
|
| 7 |
+
import numpy as np
|
| 8 |
import soundfile as sf
|
| 9 |
+
import spaces # patches torch.cuda for ZeroGPU; must stay above `import torch`
|
| 10 |
import torch
|
| 11 |
+
from scipy.signal import resample_poly
|
| 12 |
|
| 13 |
import core
|
| 14 |
from core import QUANT_LEVELS
|
| 15 |
|
| 16 |
MODEL_REPO = "lucadellalib/focalcodec_12_5hz"
|
| 17 |
HUB_REPO = "lucadellalib/focalcodec"
|
| 18 |
+
LS_REPO = "cantabile-kwok/lscodec_25hz"
|
| 19 |
+
LS_LEVEL = "lscodec-25hz"
|
| 20 |
+
LS_SIZE_MB = 196.3 # encoder 70.4 + vocoder 125.6 + codebook 0.3 MB
|
| 21 |
+
LS_BITRATE_KBPS = 25 * 10 / 1000 # 25 tokens/s x 10-bit codebook
|
| 22 |
MAX_SECONDS = 20
|
| 23 |
HEADERS = ["Level", "SI-SDR (dB)", "STOI", "LSD (dB)", "Model size (MB)", "Bitrate (kbps)", "RTF"]
|
| 24 |
DATATYPES = ["str", "number", "number", "number", "number", "number", "number"]
|
|
|
|
| 37 |
assert codec.sample_rate_output == SAMPLE_RATE, "demo assumes a 16/16 kHz codec"
|
| 38 |
|
| 39 |
|
| 40 |
+
def _boot_lscodec() -> dict | None:
|
| 41 |
+
"""LSCodec-25 Hz baseline: WavLM-Large (first 6 layers) prompt encoder + encoder/vocoder."""
|
| 42 |
+
from scipy import signal
|
| 43 |
+
|
| 44 |
+
if not hasattr(signal, "kaiser"): # their pqmf.py imports scipy<1.15's top-level kaiser
|
| 45 |
+
from scipy.signal.windows import kaiser
|
| 46 |
+
|
| 47 |
+
signal.kaiser = kaiser
|
| 48 |
+
|
| 49 |
+
from pathlib import Path
|
| 50 |
+
|
| 51 |
+
import yaml
|
| 52 |
+
from huggingface_hub import hf_hub_download
|
| 53 |
+
from transformers import WavLMModel
|
| 54 |
+
|
| 55 |
+
from lscodec.utils import load_model, load_vocoder
|
| 56 |
+
|
| 57 |
+
files = {
|
| 58 |
+
name: hf_hub_download(LS_REPO, name, repo_type="model")
|
| 59 |
+
for name in (
|
| 60 |
+
"encoder_config.yml",
|
| 61 |
+
"vocoder_config.yml",
|
| 62 |
+
"codebook.npy",
|
| 63 |
+
"lscodec_encoder.pt",
|
| 64 |
+
"lscodec_vocoder.pt",
|
| 65 |
+
)
|
| 66 |
+
}
|
| 67 |
+
encoder_config = yaml.safe_load(Path(files["encoder_config.yml"]).read_text())
|
| 68 |
+
vocoder_config = yaml.safe_load(Path(files["vocoder_config.yml"]).read_text())
|
| 69 |
+
encoder_config["pretrain_codebook"] = files["codebook.npy"]
|
| 70 |
+
vocoder_config["vq_codebook"] = files["codebook.npy"]
|
| 71 |
+
|
| 72 |
+
enc = load_model(encoder_config, files["lscodec_encoder.pt"]).eval().requires_grad_(False)
|
| 73 |
+
dec = load_vocoder(vocoder_config, files["lscodec_vocoder.pt"]).eval().requires_grad_(False)
|
| 74 |
+
codebook = torch.as_tensor(np.load(files["codebook.npy"], allow_pickle=True))
|
| 75 |
+
books = torch.nn.ModuleList(
|
| 76 |
+
[torch.nn.Embedding.from_pretrained(b, freeze=True) for b in (codebook if codebook.ndim == 3 else [codebook])]
|
| 77 |
+
)
|
| 78 |
+
wavlm = WavLMModel.from_pretrained("microsoft/wavlm-large").eval().requires_grad_(False)
|
| 79 |
+
wavlm.encoder.layers = wavlm.encoder.layers[:6] # prompt features come from layers 0..5
|
| 80 |
+
for model in (enc, dec, books, wavlm):
|
| 81 |
+
model.cuda() # ZeroGPU wants module-level cuda placement (docs: model loading)
|
| 82 |
+
return {
|
| 83 |
+
"enc": enc,
|
| 84 |
+
"dec": dec,
|
| 85 |
+
"books": books,
|
| 86 |
+
"wavlm": wavlm,
|
| 87 |
+
"repeat": bool(vocoder_config.get("repeat_input_tokens")),
|
| 88 |
+
"out_sr": int(vocoder_config["sampling_rate"]),
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
LS = _boot_lscodec()
|
| 94 |
+
except Exception as exc: # noqa: BLE001 a third-party dep breaking must not take the demo down
|
| 95 |
+
LS = None
|
| 96 |
+
print(f"[lscodec] baseline disabled: {exc!r}")
|
| 97 |
+
|
| 98 |
+
BENCH_LEVELS = QUANT_LEVELS + ((LS_LEVEL,) if LS is not None else ())
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _lscodec_decode(ref: np.ndarray, sr: int) -> np.ndarray:
|
| 102 |
+
"""Self-prompted LSCodec-25 Hz reconstruction, resampled back to `sr`."""
|
| 103 |
+
audio = torch.from_numpy(ref).float().view(1, -1).cuda()
|
| 104 |
+
with torch.inference_mode():
|
| 105 |
+
prompt_input = torch.nn.functional.layer_norm(audio, audio.shape) # fairseq WavLM cfg.normalize
|
| 106 |
+
prompt = LS["wavlm"](prompt_input, output_hidden_states=True).hidden_states[6]
|
| 107 |
+
_, _, index = LS["enc"].encode(audio.unsqueeze(0))
|
| 108 |
+
if LS["repeat"]:
|
| 109 |
+
index = index.repeat_interleave(2, dim=0)
|
| 110 |
+
vqvec = torch.cat([book(index[:, i]) for i, book in enumerate(LS["books"])], dim=-1).unsqueeze(0)
|
| 111 |
+
rec = LS["dec"].inference(vqvec, prompt)[-1].view(-1).float().cpu().numpy()
|
| 112 |
+
out_sr = LS["out_sr"]
|
| 113 |
+
if out_sr != sr:
|
| 114 |
+
g = np.gcd(out_sr, sr)
|
| 115 |
+
rec = resample_poly(rec, sr // g, out_sr // g).astype(np.float32)
|
| 116 |
+
return rec
|
| 117 |
+
|
| 118 |
+
|
| 119 |
def _estimate_duration(audio_path: str, levels: tuple) -> float:
|
| 120 |
try:
|
| 121 |
seconds = min(sf.info(audio_path).duration, MAX_SECONDS)
|
| 122 |
+
except Exception: # noqa: BLE001 unreadable input falls back to the full cap
|
| 123 |
seconds = float(MAX_SECONDS)
|
| 124 |
+
# ZeroGPU rejects a request above the visitor's tier cap, so never ask for more than 60 s.
|
| 125 |
+
return min(60.0, 4.0 + 2.0 * len(levels) + 2.0 * seconds + (8.0 if LS_LEVEL in levels else 0.0))
|
| 126 |
|
| 127 |
|
| 128 |
@spaces.GPU(duration=_estimate_duration)
|
| 129 |
def _encode_decode(audio_path: str, levels: tuple) -> dict:
|
| 130 |
"""Reconstruct `audio_path` once per weight-quantization level, scoring each result."""
|
| 131 |
ref, sr = core.load_audio(audio_path, SAMPLE_RATE, MAX_SECONDS)
|
| 132 |
+
seconds = len(ref) / sr
|
|
|
|
|
|
|
| 133 |
rows, reconstructed = [], {}
|
| 134 |
+
focal_levels = tuple(level for level in levels if level in QUANT_LEVELS)
|
| 135 |
+
if focal_levels:
|
| 136 |
+
sig = torch.from_numpy(ref).unsqueeze(0).cuda()
|
| 137 |
+
with torch.inference_mode(): # untimed warmup so the first row's RTF isn't inflated
|
| 138 |
+
codec.sig_to_sig(sig)
|
| 139 |
+
for level in focal_levels:
|
| 140 |
+
codec.load_state_dict(BASE_STATE if level == "fp32" else core.quantize_state(BASE_STATE, level))
|
| 141 |
+
torch.cuda.synchronize()
|
| 142 |
+
start = time.perf_counter()
|
| 143 |
+
with torch.inference_mode():
|
| 144 |
+
rec = codec.sig_to_sig(sig).squeeze(0).float().cpu().numpy()
|
| 145 |
+
torch.cuda.synchronize()
|
| 146 |
+
rtf = (time.perf_counter() - start) / seconds
|
| 147 |
+
rows.append(core.evaluate(ref, rec, sr, rtf, SIZES[level], level))
|
| 148 |
+
reconstructed[level] = (sr, rec)
|
| 149 |
+
codec.load_state_dict(BASE_STATE)
|
| 150 |
+
if LS_LEVEL in levels:
|
| 151 |
+
with torch.inference_mode(): # untimed warmup, as above
|
| 152 |
+
_lscodec_decode(ref, sr)
|
| 153 |
torch.cuda.synchronize()
|
| 154 |
start = time.perf_counter()
|
| 155 |
+
rec = _lscodec_decode(ref, sr)
|
|
|
|
| 156 |
torch.cuda.synchronize()
|
| 157 |
+
rtf = (time.perf_counter() - start) / seconds
|
| 158 |
+
rows.append(core.evaluate(ref, rec, sr, rtf, LS_SIZE_MB, LS_LEVEL, LS_BITRATE_KBPS))
|
|
|
|
|
|
|
| 159 |
return {"input": (sr, ref), "rows": rows, "audio": reconstructed}
|
| 160 |
|
| 161 |
|
|
|
|
| 164 |
|
| 165 |
|
| 166 |
_CACHE: dict[str, dict] = {}
|
| 167 |
+
_LS_CACHE: dict[str, dict] = {}
|
| 168 |
|
| 169 |
|
| 170 |
def run(audio_path: str, levels: list | tuple) -> dict:
|
|
|
|
| 207 |
)
|
| 208 |
|
| 209 |
|
| 210 |
+
def _lscodec_row(audio_path: str) -> dict:
|
| 211 |
+
"""LSCodec-25 Hz score for one input, cached separately from the quantization sweep."""
|
| 212 |
+
with open(audio_path, "rb") as fh:
|
| 213 |
+
key = hashlib.md5(fh.read()).hexdigest()
|
| 214 |
+
if key not in _LS_CACHE:
|
| 215 |
+
if len(_LS_CACHE) >= 6:
|
| 216 |
+
_LS_CACHE.clear()
|
| 217 |
+
_LS_CACHE[key] = _encode_decode(audio_path, (LS_LEVEL,))["rows"][0]
|
| 218 |
+
return _LS_CACHE[key]
|
| 219 |
+
|
| 220 |
+
|
| 221 |
def benchmark(audio_path: str):
|
| 222 |
"""Score every quantization level on one input: metrics table plus summary chart."""
|
| 223 |
+
rows = run(audio_path, QUANT_LEVELS)["rows"]
|
| 224 |
+
if LS_LEVEL in BENCH_LEVELS:
|
| 225 |
+
rows = rows + [_lscodec_row(audio_path)]
|
| 226 |
+
return _table(rows), core.metrics_figure(rows)
|
| 227 |
|
| 228 |
|
| 229 |
HEADER = f"""# ⚡ FocalCodec 12.5 Hz — weight quantization lab
|
|
|
|
| 238 |
"""
|
| 239 |
|
| 240 |
FOOTER = """Audio examples from the [FocalCodec repository](https://github.com/lucadellalib/focalcodec)
|
| 241 |
+
(LibriSpeech, VoiceBank, MagicData). Model and method by Luca Della Libera et al., Apache-2.0.
|
| 242 |
+
The **lscodec-25hz** baseline is [LSCodec](https://github.com/X-LANCE/LSCodec-Inference) by
|
| 243 |
+
Yiwei Guo et al. (MIT), self-prompted with WavLM-Large from [`microsoft/wavlm-large`](https://huggingface.co/microsoft/wavlm-large)."""
|
| 244 |
|
| 245 |
with gr.Blocks(title="FocalCodec quantization lab") as demo:
|
| 246 |
gr.Markdown(HEADER)
|
|
|
|
| 296 |
sources=["upload", "microphone"], type="filepath", label="Input audio"
|
| 297 |
)
|
| 298 |
gr.Examples(examples=EXAMPLES, inputs=bm_input, label="Examples")
|
| 299 |
+
bm_button = gr.Button(f"Run all {len(BENCH_LEVELS)} levels", variant="primary")
|
| 300 |
with gr.Column(scale=3):
|
| 301 |
gr.Markdown(
|
| 302 |
"**STOI** ↑ intelligibility · **LSD** ↓ spectral error · "
|
| 303 |
"**RTF** = encode+decode time ÷ audio duration · size assumes "
|
| 304 |
"the stored weight format."
|
| 305 |
)
|
| 306 |
+
if LS_LEVEL in BENCH_LEVELS:
|
| 307 |
+
gr.Markdown(
|
| 308 |
+
f"**{LS_LEVEL}** is a second codec, not a weight format: it runs at "
|
| 309 |
+
f"**{LS_BITRATE_KBPS:g} kbps** (25 tokens/s × 10 bits) versus FocalCodec's "
|
| 310 |
+
f"**{core.BITRATE_KBPS:g} kbps**, and its size counts only the codec "
|
| 311 |
+
f"({LS_SIZE_MB:g} MB) — the WavLM-Large prompt encoder it shares is 1.2 GB."
|
| 312 |
+
)
|
| 313 |
bm_table = gr.Dataframe(headers=HEADERS, datatype=DATATYPES, interactive=False)
|
| 314 |
bm_plot = gr.Plot(label="Quality and size per level")
|
| 315 |
bm_button.click(benchmark, inputs=bm_input, outputs=[bm_table, bm_plot])
|
core.py
CHANGED
|
@@ -24,6 +24,7 @@ BITRATE_KBPS = TOKEN_RATE_HZ * BITS_PER_TOKEN / 1000.0
|
|
| 24 |
|
| 25 |
_BITS = {"int4": 4, "int8": 8, "int16": 16, "int32": 32, "fp4": 4, "fp8": 8, "fp16": 16, "fp32": 32}
|
| 26 |
_FP_FORMATS = {"fp4": (2, 1), "fp8": (4, 3)} # (exponent bits, mantissa bits) -> E2M1 / E4M3
|
|
|
|
| 27 |
|
| 28 |
|
| 29 |
def _ref_scale(w: torch.Tensor) -> torch.Tensor:
|
|
@@ -33,24 +34,40 @@ def _ref_scale(w: torch.Tensor) -> torch.Tensor:
|
|
| 33 |
return w.abs().amax()
|
| 34 |
|
| 35 |
|
| 36 |
-
def
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
| 39 |
qmax = 1 << (bits - 1)
|
| 40 |
-
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
-
def
|
|
|
|
|
|
|
| 45 |
bias = (1 << (e_bits - 1)) - 1
|
| 46 |
max_exp = (1 << e_bits) - 1 - bias
|
| 47 |
-
|
| 48 |
-
scale = (_ref_scale(w) / max_norm).clamp_min(1e-12)
|
| 49 |
-
x = w / scale
|
| 50 |
ax = x.abs()
|
| 51 |
exp = torch.floor(torch.log2(ax.clamp_min(1e-30))).clamp(1 - bias, max_exp)
|
| 52 |
step = torch.pow(2.0, exp - m_bits)
|
| 53 |
-
return torch.sign(x) * ((ax / step).round() * step)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
|
| 56 |
def quantize_state(state: dict, level: str) -> dict:
|
|
@@ -66,7 +83,7 @@ def quantize_state(state: dict, level: str) -> dict:
|
|
| 66 |
elif level.startswith("int"):
|
| 67 |
out[key] = _quant_int(value, int(level[3:]))
|
| 68 |
else:
|
| 69 |
-
out[key] = _quant_fp(value,
|
| 70 |
return out
|
| 71 |
|
| 72 |
|
|
@@ -104,16 +121,21 @@ def log_spectral_distance(ref: np.ndarray, rec: np.ndarray, sr: int) -> float:
|
|
| 104 |
return float(np.mean(np.sqrt(np.mean(diff**2, axis=0))))
|
| 105 |
|
| 106 |
|
| 107 |
-
def evaluate(
|
|
|
|
|
|
|
|
|
|
| 108 |
n = min(len(ref), len(rec))
|
| 109 |
ref, rec = ref[:n], rec[:n]
|
|
|
|
|
|
|
| 110 |
return {
|
| 111 |
"Level": level,
|
| 112 |
"SI-SDR (dB)": round(si_sdr(ref, rec), 2),
|
| 113 |
"STOI": round(float(stoi(ref, rec, sr, extended=False)), 3),
|
| 114 |
"LSD (dB)": round(log_spectral_distance(ref, rec, sr), 2),
|
| 115 |
"Model size (MB)": round(size_mb, 1),
|
| 116 |
-
"Bitrate (kbps)": round(
|
| 117 |
"RTF": round(rtf, 3),
|
| 118 |
}
|
| 119 |
|
|
@@ -150,9 +172,18 @@ if __name__ == "__main__":
|
|
| 150 |
q = quantize_state({"w": w}, level)["w"]
|
| 151 |
assert torch.isfinite(q).all(), f"{level} not finite"
|
| 152 |
assert q.abs().max() <= w.abs().max() * 1.01 + 1e-9, f"{level} broke the magnitude bound"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
x = np.random.randn(16000).astype(np.float32)
|
| 154 |
assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
|
| 155 |
assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
|
| 156 |
assert abs(evaluate(x, x, 16000, 1.0, 1.0, "fp32")["STOI"] - 1.0) < 1e-3
|
| 157 |
assert evaluate(x, x, 16000, 1.0, 1.0, "fp32")["Bitrate (kbps)"] == round(BITRATE_KBPS, 4)
|
|
|
|
| 158 |
print("core self-check ok")
|
|
|
|
| 24 |
|
| 25 |
_BITS = {"int4": 4, "int8": 8, "int16": 16, "int32": 32, "fp4": 4, "fp8": 8, "fp16": 16, "fp32": 32}
|
| 26 |
_FP_FORMATS = {"fp4": (2, 1), "fp8": (4, 3)} # (exponent bits, mantissa bits) -> E2M1 / E4M3
|
| 27 |
+
_FP_MAX_NORM = {"fp4": 6.0, "fp8": 448.0} # E2M1 max; E4M3-FN finite max (top encoding is NaN)
|
| 28 |
|
| 29 |
|
| 30 |
def _ref_scale(w: torch.Tensor) -> torch.Tensor:
|
|
|
|
| 34 |
return w.abs().amax()
|
| 35 |
|
| 36 |
|
| 37 |
+
def int_scale(w: torch.Tensor, bits: int) -> torch.Tensor:
|
| 38 |
+
return (_ref_scale(w) / (1 << (bits - 1))).clamp_min(1e-12)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def int_levels(w: torch.Tensor, bits: int) -> torch.Tensor:
|
| 42 |
+
"""Signed integer levels before rescaling (what an int-format export stores)."""
|
| 43 |
qmax = 1 << (bits - 1)
|
| 44 |
+
return (w / int_scale(w, bits)).round().clamp(-qmax, qmax - 1)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def fp_scale(w: torch.Tensor, level: str) -> torch.Tensor:
|
| 48 |
+
return (_ref_scale(w) / _FP_MAX_NORM[level]).clamp_min(1e-12)
|
| 49 |
|
| 50 |
|
| 51 |
+
def fp_levels(w: torch.Tensor, level: str) -> torch.Tensor:
|
| 52 |
+
"""Values on the format's own grid (what a float-format export stores, unscaled)."""
|
| 53 |
+
e_bits, m_bits = _FP_FORMATS[level]
|
| 54 |
bias = (1 << (e_bits - 1)) - 1
|
| 55 |
max_exp = (1 << e_bits) - 1 - bias
|
| 56 |
+
x = w / fp_scale(w, level)
|
|
|
|
|
|
|
| 57 |
ax = x.abs()
|
| 58 |
exp = torch.floor(torch.log2(ax.clamp_min(1e-30))).clamp(1 - bias, max_exp)
|
| 59 |
step = torch.pow(2.0, exp - m_bits)
|
| 60 |
+
return torch.sign(x) * ((ax / step).round() * step)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _quant_int(w: torch.Tensor, bits: int) -> torch.Tensor:
|
| 64 |
+
if bits >= 32:
|
| 65 |
+
return w
|
| 66 |
+
return int_levels(w, bits) * int_scale(w, bits)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _quant_fp(w: torch.Tensor, level: str) -> torch.Tensor:
|
| 70 |
+
return fp_levels(w, level) * fp_scale(w, level)
|
| 71 |
|
| 72 |
|
| 73 |
def quantize_state(state: dict, level: str) -> dict:
|
|
|
|
| 83 |
elif level.startswith("int"):
|
| 84 |
out[key] = _quant_int(value, int(level[3:]))
|
| 85 |
else:
|
| 86 |
+
out[key] = _quant_fp(value, level)
|
| 87 |
return out
|
| 88 |
|
| 89 |
|
|
|
|
| 121 |
return float(np.mean(np.sqrt(np.mean(diff**2, axis=0))))
|
| 122 |
|
| 123 |
|
| 124 |
+
def evaluate(
|
| 125 |
+
ref: np.ndarray, rec: np.ndarray, sr: int, rtf: float, size_mb: float, level: str,
|
| 126 |
+
bitrate_kbps: float | None = None,
|
| 127 |
+
) -> dict:
|
| 128 |
n = min(len(ref), len(rec))
|
| 129 |
ref, rec = ref[:n], rec[:n]
|
| 130 |
+
if bitrate_kbps is None:
|
| 131 |
+
bitrate_kbps = BITRATE_KBPS
|
| 132 |
return {
|
| 133 |
"Level": level,
|
| 134 |
"SI-SDR (dB)": round(si_sdr(ref, rec), 2),
|
| 135 |
"STOI": round(float(stoi(ref, rec, sr, extended=False)), 3),
|
| 136 |
"LSD (dB)": round(log_spectral_distance(ref, rec, sr), 2),
|
| 137 |
"Model size (MB)": round(size_mb, 1),
|
| 138 |
+
"Bitrate (kbps)": round(bitrate_kbps, 4),
|
| 139 |
"RTF": round(rtf, 3),
|
| 140 |
}
|
| 141 |
|
|
|
|
| 172 |
q = quantize_state({"w": w}, level)["w"]
|
| 173 |
assert torch.isfinite(q).all(), f"{level} not finite"
|
| 174 |
assert q.abs().max() <= w.abs().max() * 1.01 + 1e-9, f"{level} broke the magnitude bound"
|
| 175 |
+
# the packed levels a real export stores must reproduce the fake-quant demo exactly
|
| 176 |
+
assert torch.equal(int_levels(w, 8) * int_scale(w, 8), quantize_state({"w": w}, "int8")["w"])
|
| 177 |
+
assert torch.equal(fp_levels(w, "fp8") * fp_scale(w, "fp8"), quantize_state({"w": w}, "fp8")["w"])
|
| 178 |
+
# fp8 grid values are exactly float8_e4m3fn-representable, so no precision is lost on store
|
| 179 |
+
grid8 = fp_levels(w, "fp8")
|
| 180 |
+
assert torch.equal(grid8.to(torch.float8_e4m3fn).float(), grid8), "fp8 grid escapes E4M3-FN"
|
| 181 |
+
grid4 = fp_levels(w, "fp4")
|
| 182 |
+
assert grid4.abs().max() <= _FP_MAX_NORM["fp4"] * 1.0000001, "fp4 grid escapes E2M1"
|
| 183 |
x = np.random.randn(16000).astype(np.float32)
|
| 184 |
assert si_sdr(x, x) > 60, "SI-SDR of identical signals should be large"
|
| 185 |
assert si_sdr(x, 3 * x) > 60, "SI-SDR must be scale invariant"
|
| 186 |
assert abs(evaluate(x, x, 16000, 1.0, 1.0, "fp32")["STOI"] - 1.0) < 1e-3
|
| 187 |
assert evaluate(x, x, 16000, 1.0, 1.0, "fp32")["Bitrate (kbps)"] == round(BITRATE_KBPS, 4)
|
| 188 |
+
assert evaluate(x, x, 16000, 1.0, 1.0, "lscodec-25hz", 0.25)["Bitrate (kbps)"] == 0.25
|
| 189 |
print("core self-check ok")
|
lscodec/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
lscodec/bin/__init__.py
ADDED
|
File without changes
|
lscodec/bin/decode_feat_prompt.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 6 |
+
|
| 7 |
+
# MIT License
|
| 8 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 9 |
+
# This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
|
| 10 |
+
# Above is the original copyright notice.
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
"""Decode with Trained LSCodec Vocoder."""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import logging
|
| 17 |
+
import os
|
| 18 |
+
import time
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
import soundfile as sf
|
| 22 |
+
import torch
|
| 23 |
+
import yaml
|
| 24 |
+
import kaldiio
|
| 25 |
+
from tqdm import tqdm
|
| 26 |
+
|
| 27 |
+
from lscodec.utils import load_vocoder
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def main():
|
| 31 |
+
"""Run decoding process."""
|
| 32 |
+
parser = argparse.ArgumentParser(
|
| 33 |
+
description="Decode from LSCodec tokens to waveforms"
|
| 34 |
+
)
|
| 35 |
+
parser.add_argument(
|
| 36 |
+
"--feats-scp",
|
| 37 |
+
"--scp",
|
| 38 |
+
default=None,
|
| 39 |
+
type=str,
|
| 40 |
+
help="kaldi-style feats.scp file. "
|
| 41 |
+
"you need to specify either feats-scp or dumpdir.",
|
| 42 |
+
)
|
| 43 |
+
parser.add_argument(
|
| 44 |
+
"--prompt-scp",
|
| 45 |
+
default=None,
|
| 46 |
+
type=str
|
| 47 |
+
)
|
| 48 |
+
parser.add_argument(
|
| 49 |
+
"--outdir",
|
| 50 |
+
type=str,
|
| 51 |
+
required=True,
|
| 52 |
+
help="directory to save generated speech.",
|
| 53 |
+
)
|
| 54 |
+
# parser.add_argument(
|
| 55 |
+
# "--checkpoint",
|
| 56 |
+
# type=str,
|
| 57 |
+
# default='pretrained/lscodec_vocoder.pt',
|
| 58 |
+
# help="checkpoint file to be loaded.",
|
| 59 |
+
# )
|
| 60 |
+
# parser.add_argument(
|
| 61 |
+
# "--config",
|
| 62 |
+
# default='pretrained/vocoder_config.yml',
|
| 63 |
+
# type=str,
|
| 64 |
+
# help="yaml format configuration file."
|
| 65 |
+
# )
|
| 66 |
+
parser.add_argument(
|
| 67 |
+
'--pretrained-dir',
|
| 68 |
+
type=str,
|
| 69 |
+
default='pretrained',
|
| 70 |
+
help='directory of pretrained models. Should contain WavLM-Large.pt, lscodec_vocoder.pt, vocoder_config.yml',
|
| 71 |
+
)
|
| 72 |
+
parser.add_argument(
|
| 73 |
+
"--verbose",
|
| 74 |
+
type=int,
|
| 75 |
+
default=1,
|
| 76 |
+
help="logging level. higher is more logging. (default=1)",
|
| 77 |
+
)
|
| 78 |
+
args = parser.parse_args()
|
| 79 |
+
|
| 80 |
+
# set logger
|
| 81 |
+
if args.verbose > 1:
|
| 82 |
+
logging.basicConfig(
|
| 83 |
+
level=logging.DEBUG,
|
| 84 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 85 |
+
)
|
| 86 |
+
elif args.verbose > 0:
|
| 87 |
+
logging.basicConfig(
|
| 88 |
+
level=logging.INFO,
|
| 89 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 90 |
+
)
|
| 91 |
+
else:
|
| 92 |
+
logging.basicConfig(
|
| 93 |
+
level=logging.WARN,
|
| 94 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 95 |
+
)
|
| 96 |
+
logging.warning("Skip DEBUG/INFO messages")
|
| 97 |
+
|
| 98 |
+
# check directory existence
|
| 99 |
+
if not os.path.exists(args.outdir):
|
| 100 |
+
os.makedirs(args.outdir)
|
| 101 |
+
|
| 102 |
+
# load config
|
| 103 |
+
config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
|
| 104 |
+
checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
|
| 105 |
+
with open(config_path) as f:
|
| 106 |
+
config = yaml.load(f, Loader=yaml.Loader)
|
| 107 |
+
config.update(vars(args))
|
| 108 |
+
config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
|
| 109 |
+
|
| 110 |
+
# check arguments
|
| 111 |
+
if args.feats_scp is None:
|
| 112 |
+
raise ValueError("Please specify --feats-scp.")
|
| 113 |
+
|
| 114 |
+
# setup model
|
| 115 |
+
if torch.cuda.is_available():
|
| 116 |
+
device = torch.device("cuda")
|
| 117 |
+
logging.info("Using GPU.")
|
| 118 |
+
else:
|
| 119 |
+
device = torch.device("cpu")
|
| 120 |
+
logging.info("Using CPU.")
|
| 121 |
+
model = load_vocoder(checkpoint=checkpoint_path, config=config)
|
| 122 |
+
logging.info(f"Loaded model parameters from {checkpoint_path}.")
|
| 123 |
+
model.backend.remove_weight_norm()
|
| 124 |
+
model = model.eval().to(device)
|
| 125 |
+
|
| 126 |
+
# load vq codebook
|
| 127 |
+
feat_codebook = torch.tensor(np.load(config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
|
| 128 |
+
if feat_codebook.ndim == 2:
|
| 129 |
+
feat_codebook = feat_codebook.unsqueeze(0)
|
| 130 |
+
feat_codebook_numgroups = feat_codebook.shape[0]
|
| 131 |
+
feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
|
| 132 |
+
|
| 133 |
+
# start generation
|
| 134 |
+
total_rtf = 0.0
|
| 135 |
+
feats_scp = kaldiio.load_scp(args.feats_scp)
|
| 136 |
+
prompt_scp = kaldiio.load_scp(args.prompt_scp)
|
| 137 |
+
total_cnt = len(list(feats_scp.keys()))
|
| 138 |
+
logging.info(f"The number of features to be decoded = {total_cnt}.")
|
| 139 |
+
|
| 140 |
+
with torch.no_grad(), tqdm(feats_scp, desc="[decode]", total=total_cnt) as pbar:
|
| 141 |
+
for idx, utt_id in enumerate(pbar, 1):
|
| 142 |
+
c = feats_scp[utt_id]
|
| 143 |
+
prompt = prompt_scp[utt_id]
|
| 144 |
+
|
| 145 |
+
c = torch.tensor(c).to(device) # (L, D)
|
| 146 |
+
prompt = torch.tensor(prompt).unsqueeze(0).to(device) # (1, L', D')
|
| 147 |
+
|
| 148 |
+
vqidx = c.long()
|
| 149 |
+
if config.get("repeat_input_tokens", False):
|
| 150 |
+
vqidx = vqidx.repeat_interleave(2, dim=0)
|
| 151 |
+
vqvec = torch.cat([feat_codebook[i](vqidx[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
|
| 152 |
+
|
| 153 |
+
# generate
|
| 154 |
+
start = time.time()
|
| 155 |
+
y = model.inference(vqvec, prompt)[-1].view(-1)
|
| 156 |
+
rtf = (time.time() - start) / (len(y) / config["sampling_rate"])
|
| 157 |
+
pbar.set_postfix({"RTF": rtf})
|
| 158 |
+
total_rtf += rtf
|
| 159 |
+
|
| 160 |
+
tgt_dir = os.path.dirname(os.path.join(config["outdir"], f"{utt_id}.wav"))
|
| 161 |
+
os.makedirs(tgt_dir, exist_ok=True)
|
| 162 |
+
basename = os.path.basename(f"{utt_id}.wav")
|
| 163 |
+
# save as PCM 16 bit wav file
|
| 164 |
+
sf.write(
|
| 165 |
+
os.path.join(tgt_dir, basename),
|
| 166 |
+
y.cpu().numpy(),
|
| 167 |
+
config["sampling_rate"],
|
| 168 |
+
"PCM_16",
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
# report average RTF
|
| 172 |
+
logging.info(
|
| 173 |
+
f"Finished generation of {idx} utterances (RTF = {total_rtf / idx:.03f})."
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
if __name__ == "__main__":
|
| 178 |
+
main()
|
lscodec/bin/decode_wav_prompt.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 6 |
+
|
| 7 |
+
# MIT License
|
| 8 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 9 |
+
# This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
|
| 10 |
+
# Above is the original copyright notice.
|
| 11 |
+
|
| 12 |
+
"""Decode with Trained LSCodec Vocoder."""
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import logging
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
import soundfile as sf
|
| 21 |
+
import torch
|
| 22 |
+
import yaml
|
| 23 |
+
import kaldiio
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
import librosa
|
| 26 |
+
|
| 27 |
+
from lscodec.utils import load_vocoder, load_audio
|
| 28 |
+
from lscodec.ssl_models.wavlm_extractor import Extractor as WavLMExtractor
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def main():
|
| 32 |
+
"""Run decoding process."""
|
| 33 |
+
parser = argparse.ArgumentParser(
|
| 34 |
+
description="Decode from LSCodec tokens to waveforms"
|
| 35 |
+
)
|
| 36 |
+
parser.add_argument(
|
| 37 |
+
"--feats-scp",
|
| 38 |
+
"--scp",
|
| 39 |
+
default=None,
|
| 40 |
+
type=str,
|
| 41 |
+
help="kaldi-style feats.scp file. "
|
| 42 |
+
"you need to specify either feats-scp or dumpdir.",
|
| 43 |
+
)
|
| 44 |
+
parser.add_argument(
|
| 45 |
+
"--prompt-wav-scp",
|
| 46 |
+
default=None,
|
| 47 |
+
type=str,
|
| 48 |
+
help="kaldi-style prompt_wav.scp file."
|
| 49 |
+
)
|
| 50 |
+
parser.add_argument(
|
| 51 |
+
"--num-frames",
|
| 52 |
+
default=None,
|
| 53 |
+
type=str
|
| 54 |
+
)
|
| 55 |
+
parser.add_argument(
|
| 56 |
+
"--outdir",
|
| 57 |
+
type=str,
|
| 58 |
+
required=True,
|
| 59 |
+
help="directory to save generated speech.",
|
| 60 |
+
)
|
| 61 |
+
parser.add_argument(
|
| 62 |
+
'--pretrained-dir',
|
| 63 |
+
type=str,
|
| 64 |
+
default='pretrained',
|
| 65 |
+
help='directory to save pretrained models. Should contain WavLM-Large.pt, lscodec_vocoder.pt, vocoder_config.yml',
|
| 66 |
+
)
|
| 67 |
+
parser.add_argument(
|
| 68 |
+
"--verbose",
|
| 69 |
+
type=int,
|
| 70 |
+
default=1,
|
| 71 |
+
help="logging level. higher is more logging. (default=1)",
|
| 72 |
+
)
|
| 73 |
+
args = parser.parse_args()
|
| 74 |
+
|
| 75 |
+
# set logger
|
| 76 |
+
if args.verbose > 1:
|
| 77 |
+
logging.basicConfig(
|
| 78 |
+
level=logging.DEBUG,
|
| 79 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 80 |
+
)
|
| 81 |
+
elif args.verbose > 0:
|
| 82 |
+
logging.basicConfig(
|
| 83 |
+
level=logging.INFO,
|
| 84 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 85 |
+
)
|
| 86 |
+
else:
|
| 87 |
+
logging.basicConfig(
|
| 88 |
+
level=logging.WARN,
|
| 89 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 90 |
+
)
|
| 91 |
+
logging.warning("Skip DEBUG/INFO messages")
|
| 92 |
+
|
| 93 |
+
# check directory existence
|
| 94 |
+
if not os.path.exists(args.outdir):
|
| 95 |
+
os.makedirs(args.outdir)
|
| 96 |
+
|
| 97 |
+
# load config
|
| 98 |
+
config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
|
| 99 |
+
checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
|
| 100 |
+
wavlm_path = os.path.join(args.pretrained_dir, "WavLM-Large.pt")
|
| 101 |
+
with open(config_path) as f:
|
| 102 |
+
config = yaml.load(f, Loader=yaml.Loader)
|
| 103 |
+
config.update(vars(args))
|
| 104 |
+
config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
|
| 105 |
+
|
| 106 |
+
# check arguments
|
| 107 |
+
if args.feats_scp is None:
|
| 108 |
+
raise ValueError("Please specify --feats-scp.")
|
| 109 |
+
|
| 110 |
+
# setup model
|
| 111 |
+
if torch.cuda.is_available():
|
| 112 |
+
device = torch.device("cuda")
|
| 113 |
+
logging.info("Using GPU.")
|
| 114 |
+
else:
|
| 115 |
+
device = torch.device("cpu")
|
| 116 |
+
logging.info("Using CPU.")
|
| 117 |
+
model = load_vocoder(checkpoint=checkpoint_path, config=config)
|
| 118 |
+
logging.info(f"Loaded model parameters from {checkpoint_path}.")
|
| 119 |
+
model.backend.remove_weight_norm()
|
| 120 |
+
model = model.eval().to(device)
|
| 121 |
+
|
| 122 |
+
# load vq codebook
|
| 123 |
+
feat_codebook = torch.tensor(np.load(config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
|
| 124 |
+
if feat_codebook.ndim == 2:
|
| 125 |
+
feat_codebook = feat_codebook.unsqueeze(0)
|
| 126 |
+
feat_codebook_numgroups = feat_codebook.shape[0]
|
| 127 |
+
feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
|
| 128 |
+
|
| 129 |
+
# start generation
|
| 130 |
+
total_rtf = 0.0
|
| 131 |
+
feats_scp = kaldiio.load_scp(args.feats_scp)
|
| 132 |
+
prompt_wav_scp = dict()
|
| 133 |
+
with open(args.prompt_wav_scp, 'r') as fr:
|
| 134 |
+
for line in fr.readlines():
|
| 135 |
+
terms = line.strip().split()
|
| 136 |
+
if len(terms) != 2:
|
| 137 |
+
raise ValueError(f"Invalid line in prompt_wav_scp: {line.strip()}")
|
| 138 |
+
utt_id, audio_path = terms
|
| 139 |
+
prompt_wav_scp[utt_id] = audio_path
|
| 140 |
+
|
| 141 |
+
total_cnt = len(list(feats_scp.keys()))
|
| 142 |
+
logging.info(f"The number of features to be decoded = {total_cnt}.")
|
| 143 |
+
|
| 144 |
+
wavlm = WavLMExtractor(checkpoint=wavlm_path, device=device)
|
| 145 |
+
|
| 146 |
+
with torch.no_grad(), tqdm(feats_scp, desc="[decode]", total=total_cnt) as pbar:
|
| 147 |
+
for idx, utt_id in enumerate(pbar, 1):
|
| 148 |
+
c = feats_scp[utt_id]
|
| 149 |
+
# prompt = prompt_scp[utt_id]
|
| 150 |
+
prompt_path = prompt_wav_scp[utt_id]
|
| 151 |
+
prompt_audio, prompt_sr = load_audio(prompt_path)
|
| 152 |
+
|
| 153 |
+
if prompt_sr != 16000:
|
| 154 |
+
prompt_audio = librosa.resample(prompt_audio, orig_sr=prompt_sr, target_sr=16000)
|
| 155 |
+
|
| 156 |
+
c = torch.tensor(c).to(device) # (L, D)
|
| 157 |
+
|
| 158 |
+
prompt = wavlm.extract(prompt_audio).unsqueeze(0) # (1, L', D')
|
| 159 |
+
|
| 160 |
+
vqidx = c.long()
|
| 161 |
+
if config.get("repeat_input_tokens", False):
|
| 162 |
+
vqidx = vqidx.repeat_interleave(2, dim=0)
|
| 163 |
+
vqvec = torch.cat([feat_codebook[i](vqidx[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
|
| 164 |
+
|
| 165 |
+
# generate
|
| 166 |
+
start = time.time()
|
| 167 |
+
y = model.inference(vqvec, prompt)[-1].view(-1)
|
| 168 |
+
rtf = (time.time() - start) / (len(y) / config["sampling_rate"])
|
| 169 |
+
pbar.set_postfix({"RTF": rtf})
|
| 170 |
+
total_rtf += rtf
|
| 171 |
+
|
| 172 |
+
tgt_dir = os.path.dirname(os.path.join(config["outdir"], f"{utt_id}.wav"))
|
| 173 |
+
os.makedirs(tgt_dir, exist_ok=True)
|
| 174 |
+
basename = os.path.basename(f"{utt_id}.wav")
|
| 175 |
+
# save as PCM 16 bit wav file
|
| 176 |
+
sf.write(
|
| 177 |
+
os.path.join(tgt_dir, basename),
|
| 178 |
+
y.cpu().numpy(),
|
| 179 |
+
config["sampling_rate"],
|
| 180 |
+
"PCM_16",
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# report average RTF
|
| 184 |
+
logging.info(
|
| 185 |
+
f"Finished generation of {idx} utterances (RTF = {total_rtf / idx:.03f})."
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
if __name__ == "__main__":
|
| 190 |
+
main()
|
lscodec/bin/encode.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 6 |
+
|
| 7 |
+
# MIT License
|
| 8 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 9 |
+
# This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
|
| 10 |
+
# Above is the original copyright notice.
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
"""Encode with Trained LSCodec Encoder."""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import logging
|
| 17 |
+
import os
|
| 18 |
+
import time
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
import re
|
| 22 |
+
import librosa
|
| 23 |
+
|
| 24 |
+
import soundfile as sf
|
| 25 |
+
import torch
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
import yaml
|
| 28 |
+
|
| 29 |
+
from tqdm import tqdm
|
| 30 |
+
import kaldiio
|
| 31 |
+
|
| 32 |
+
from lscodec.utils import load_model, load_audio
|
| 33 |
+
from kaldiio import WriteHelper
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def main():
|
| 37 |
+
"""Run decoding process."""
|
| 38 |
+
parser = argparse.ArgumentParser(
|
| 39 |
+
description="Encode features with trained LSCodec"
|
| 40 |
+
)
|
| 41 |
+
parser.add_argument(
|
| 42 |
+
"--wav-scp",
|
| 43 |
+
"--scp",
|
| 44 |
+
default=None,
|
| 45 |
+
type=str,
|
| 46 |
+
help="kaldi-style wav.scp file. "
|
| 47 |
+
"you need to specify wav-scp.",
|
| 48 |
+
)
|
| 49 |
+
parser.add_argument(
|
| 50 |
+
"--outdir",
|
| 51 |
+
type=str,
|
| 52 |
+
required=True,
|
| 53 |
+
help="directory to save generated speech.",
|
| 54 |
+
)
|
| 55 |
+
parser.add_argument(
|
| 56 |
+
"--pretrained-dir",
|
| 57 |
+
type=str,
|
| 58 |
+
default="pretrained/",
|
| 59 |
+
help="directory to save pretrained model. (default=None). It will need to contain lscodec_encoder.pt and encoder_config.yml",
|
| 60 |
+
)
|
| 61 |
+
parser.add_argument(
|
| 62 |
+
"--verbose",
|
| 63 |
+
type=int,
|
| 64 |
+
default=1,
|
| 65 |
+
help="logging level. higher is more logging. (default=1)",
|
| 66 |
+
)
|
| 67 |
+
args = parser.parse_args()
|
| 68 |
+
|
| 69 |
+
# set logger
|
| 70 |
+
if args.verbose > 1:
|
| 71 |
+
logging.basicConfig(
|
| 72 |
+
level=logging.DEBUG,
|
| 73 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 74 |
+
)
|
| 75 |
+
elif args.verbose > 0:
|
| 76 |
+
logging.basicConfig(
|
| 77 |
+
level=logging.INFO,
|
| 78 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 79 |
+
)
|
| 80 |
+
else:
|
| 81 |
+
logging.basicConfig(
|
| 82 |
+
level=logging.WARN,
|
| 83 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 84 |
+
)
|
| 85 |
+
logging.warning("Skip DEBUG/INFO messages")
|
| 86 |
+
|
| 87 |
+
# check directory existence
|
| 88 |
+
if not os.path.exists(args.outdir):
|
| 89 |
+
os.makedirs(args.outdir)
|
| 90 |
+
|
| 91 |
+
# load config
|
| 92 |
+
config_path = os.path.join(args.pretrained_dir, "encoder_config.yml")
|
| 93 |
+
checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_encoder.pt")
|
| 94 |
+
with open(config_path) as f:
|
| 95 |
+
config = yaml.load(f, Loader=yaml.Loader)
|
| 96 |
+
config.update(vars(args))
|
| 97 |
+
config['pretrain_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
|
| 98 |
+
|
| 99 |
+
# check arguments
|
| 100 |
+
if args.wav_scp is None:
|
| 101 |
+
raise ValueError("Please specify --wav-scp.")
|
| 102 |
+
|
| 103 |
+
utt2path = dict()
|
| 104 |
+
with open(args.wav_scp, 'r') as fr:
|
| 105 |
+
for line in fr.readlines():
|
| 106 |
+
terms = line.strip().split()
|
| 107 |
+
utt2path[terms[0]] = terms[1]
|
| 108 |
+
logging.info(f"The number of features to be decoded = {len(utt2path)}.")
|
| 109 |
+
|
| 110 |
+
# setup model
|
| 111 |
+
if torch.cuda.is_available():
|
| 112 |
+
device = torch.device("cuda")
|
| 113 |
+
logging.info("Using GPU.")
|
| 114 |
+
else:
|
| 115 |
+
device = torch.device("cpu")
|
| 116 |
+
logging.info("Using CPU.")
|
| 117 |
+
model = load_model(config, checkpoint_path)
|
| 118 |
+
logging.info(f"Loaded model parameters from {checkpoint_path}.")
|
| 119 |
+
model = model.eval().to(device)
|
| 120 |
+
|
| 121 |
+
# start generation
|
| 122 |
+
outdir = str(Path(args.outdir).absolute())
|
| 123 |
+
with torch.no_grad(), tqdm(utt2path.items(), desc="[encode]") as pbar, WriteHelper(f"ark,scp:{outdir}/feats.ark,{outdir}/feats.scp") as writer:
|
| 124 |
+
for i, (utt_id, audio_path) in enumerate(pbar, 1):
|
| 125 |
+
audio, sr = load_audio(audio_path)
|
| 126 |
+
|
| 127 |
+
if sr != config['feature_extractor_params']['fs']:
|
| 128 |
+
audio_for_extractor = librosa.resample(audio, orig_sr=sr, target_sr=config['feature_extractor_params']['fs'])
|
| 129 |
+
else:
|
| 130 |
+
audio_for_extractor = audio
|
| 131 |
+
audio_for_extractor = torch.tensor(audio_for_extractor).float().to(device) # (L)
|
| 132 |
+
audio_for_extractor = audio_for_extractor.unsqueeze(0).unsqueeze(0) # (1, 1, L)
|
| 133 |
+
means, _, embed_index = model.encode(audio_for_extractor)
|
| 134 |
+
|
| 135 |
+
# now the embed_index is [L, 1] token tensor
|
| 136 |
+
if embed_index is not None:
|
| 137 |
+
writer(utt_id, embed_index.float().cpu().numpy())
|
| 138 |
+
else:
|
| 139 |
+
writer(utt_id, means.squeeze(0).cpu().numpy())
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
if __name__ == "__main__":
|
| 143 |
+
main()
|
lscodec/bin/recon_with_prompt.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 6 |
+
|
| 7 |
+
# MIT License
|
| 8 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 9 |
+
# This file is based on ParallelWaveGAN (https://github.com/kan-bayashi/ParallelWaveGAN).
|
| 10 |
+
# Above is the original copyright notice.
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
"""Decode with trained Parallel WaveGAN Generator."""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import logging
|
| 17 |
+
from kaldiio import WriteHelper
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
import os
|
| 20 |
+
import time
|
| 21 |
+
import re
|
| 22 |
+
|
| 23 |
+
import numpy as np
|
| 24 |
+
import librosa
|
| 25 |
+
|
| 26 |
+
import soundfile as sf
|
| 27 |
+
import torch
|
| 28 |
+
import yaml
|
| 29 |
+
|
| 30 |
+
from tqdm import tqdm
|
| 31 |
+
import kaldiio
|
| 32 |
+
from lscodec.ssl_models.wavlm_extractor import Extractor as WavLMExtractor
|
| 33 |
+
from lscodec.utils import load_model, load_vocoder, load_audio
|
| 34 |
+
|
| 35 |
+
def main():
|
| 36 |
+
"""Run decoding process."""
|
| 37 |
+
parser = argparse.ArgumentParser(
|
| 38 |
+
description="Decode dumped features with trained VQVAE "
|
| 39 |
+
)
|
| 40 |
+
parser.add_argument(
|
| 41 |
+
"--wav-scp",
|
| 42 |
+
"--scp",
|
| 43 |
+
default=None,
|
| 44 |
+
type=str,
|
| 45 |
+
help="kaldi-style wav.scp file. "
|
| 46 |
+
"you need to specify either wav-scp or dumpdir.",
|
| 47 |
+
)
|
| 48 |
+
parser.add_argument(
|
| 49 |
+
"--prompt-wav-scp",
|
| 50 |
+
default=None,
|
| 51 |
+
type=str,
|
| 52 |
+
help="kaldi-style feats.scp file. "
|
| 53 |
+
)
|
| 54 |
+
parser.add_argument(
|
| 55 |
+
"--outdir",
|
| 56 |
+
type=str,
|
| 57 |
+
required=True,
|
| 58 |
+
help="directory to save generated speech.",
|
| 59 |
+
)
|
| 60 |
+
# parser.add_argument(
|
| 61 |
+
# "--encoder-checkpoint",
|
| 62 |
+
# type=str,
|
| 63 |
+
# default="pretrained/lscodec_encoder.pt",
|
| 64 |
+
# help="checkpoint file to be loaded.",
|
| 65 |
+
# )
|
| 66 |
+
# parser.add_argument(
|
| 67 |
+
# "--encoder-config",
|
| 68 |
+
# default='pretrained/encoder_config.yml',
|
| 69 |
+
# type=str,
|
| 70 |
+
# help="yaml format configuration file. if not explicitly provided, "
|
| 71 |
+
# "it will be searched in the checkpoint directory. (default=None)",
|
| 72 |
+
# )
|
| 73 |
+
# parser.add_argument(
|
| 74 |
+
# "--vocoder-checkpoint",
|
| 75 |
+
# type=str,
|
| 76 |
+
# default="pretrained/lscodec_vocoder.pt",
|
| 77 |
+
# help="checkpoint file to be loaded.",
|
| 78 |
+
# )
|
| 79 |
+
# parser.add_argument(
|
| 80 |
+
# "--vocoder-config",
|
| 81 |
+
# default='pretrained/vocoder_config.yml',
|
| 82 |
+
# type=str,
|
| 83 |
+
# help="yaml format configuration file. if not explicitly provided, "
|
| 84 |
+
# "it will be searched in the checkpoint directory. (default=None)",
|
| 85 |
+
# )
|
| 86 |
+
# parser.add_argument(
|
| 87 |
+
# "--wavlm-path",
|
| 88 |
+
# type=str,
|
| 89 |
+
# default="pretrained/WavLM-Large.pt",
|
| 90 |
+
# help="path to WavLM checkpoint.",
|
| 91 |
+
# )
|
| 92 |
+
parser.add_argument(
|
| 93 |
+
"--pretrained-dir",
|
| 94 |
+
type=str,
|
| 95 |
+
default="pretrained/",
|
| 96 |
+
help="directory to save pretrained model. (default=None). It will need to contain WavLM-Large.pt, lscodec_encoder.pt, lscodec_vocoder.pt, encoder_config.yml, vocoder_config.yml and codebook.npy",
|
| 97 |
+
)
|
| 98 |
+
parser.add_argument(
|
| 99 |
+
"--verbose",
|
| 100 |
+
type=int,
|
| 101 |
+
default=1,
|
| 102 |
+
help="logging level. higher is more logging. (default=1)",
|
| 103 |
+
)
|
| 104 |
+
args = parser.parse_args()
|
| 105 |
+
|
| 106 |
+
# set logger
|
| 107 |
+
if args.verbose > 1:
|
| 108 |
+
logging.basicConfig(
|
| 109 |
+
level=logging.DEBUG,
|
| 110 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 111 |
+
)
|
| 112 |
+
elif args.verbose > 0:
|
| 113 |
+
logging.basicConfig(
|
| 114 |
+
level=logging.INFO,
|
| 115 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 116 |
+
)
|
| 117 |
+
else:
|
| 118 |
+
logging.basicConfig(
|
| 119 |
+
level=logging.WARN,
|
| 120 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 121 |
+
)
|
| 122 |
+
logging.warning("Skip DEBUG/INFO messages")
|
| 123 |
+
|
| 124 |
+
# check directory existence
|
| 125 |
+
if not os.path.exists(args.outdir):
|
| 126 |
+
os.makedirs(args.outdir)
|
| 127 |
+
|
| 128 |
+
# load encoder config
|
| 129 |
+
encoder_config_path = os.path.join(args.pretrained_dir, "encoder_config.yml")
|
| 130 |
+
encoder_checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_encoder.pt")
|
| 131 |
+
with open(encoder_config_path) as f:
|
| 132 |
+
encoder_config = yaml.load(f, Loader=yaml.Loader)
|
| 133 |
+
encoder_config.update(vars(args))
|
| 134 |
+
encoder_config['pretrain_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
|
| 135 |
+
|
| 136 |
+
# load vocoder config
|
| 137 |
+
vocoder_config_path = os.path.join(args.pretrained_dir, "vocoder_config.yml")
|
| 138 |
+
vocoder_checkpoint_path = os.path.join(args.pretrained_dir, "lscodec_vocoder.pt")
|
| 139 |
+
with open(vocoder_config_path) as f:
|
| 140 |
+
vocoder_config = yaml.load(f, Loader=yaml.Loader)
|
| 141 |
+
vocoder_config.update(vars(args))
|
| 142 |
+
vocoder_config['vq_codebook'] = os.path.join(args.pretrained_dir, "codebook.npy") # overwrite
|
| 143 |
+
|
| 144 |
+
# check arguments
|
| 145 |
+
if args.wav_scp is None or args.prompt_wav_scp is None:
|
| 146 |
+
raise ValueError("Please specify --wav-scp and --prompt-wav-scp.")
|
| 147 |
+
|
| 148 |
+
utt2path = dict()
|
| 149 |
+
with open(args.wav_scp, 'r') as fr:
|
| 150 |
+
for line in fr.readlines():
|
| 151 |
+
terms = line.strip().split()
|
| 152 |
+
if len(terms) != 2:
|
| 153 |
+
raise ValueError(f"Invalid line in wav_scp: {line.strip()}")
|
| 154 |
+
utt2path[terms[0]] = terms[1]
|
| 155 |
+
prompt_scp = dict()
|
| 156 |
+
with open(args.prompt_wav_scp, 'r') as fr:
|
| 157 |
+
for line in fr.readlines():
|
| 158 |
+
terms = line.strip().split()
|
| 159 |
+
if len(terms) != 2:
|
| 160 |
+
raise ValueError(f"Invalid line in prompt_wav_scp: {line.strip()}")
|
| 161 |
+
utt_id, audio_path = terms
|
| 162 |
+
prompt_scp[utt_id] = audio_path
|
| 163 |
+
|
| 164 |
+
logging.info(f"The number of features to be decoded = {len(utt2path)}.")
|
| 165 |
+
|
| 166 |
+
# setup model
|
| 167 |
+
if torch.cuda.is_available():
|
| 168 |
+
device = torch.device("cuda")
|
| 169 |
+
logging.info("Using GPU.")
|
| 170 |
+
else:
|
| 171 |
+
device = torch.device("cpu")
|
| 172 |
+
logging.info("Using CPU.")
|
| 173 |
+
model = load_model(encoder_config, encoder_checkpoint_path)
|
| 174 |
+
logging.info(f"Loaded encoder parameters from {encoder_checkpoint_path}.")
|
| 175 |
+
model = model.eval().to(device)
|
| 176 |
+
|
| 177 |
+
vocoder = load_vocoder(vocoder_config, vocoder_checkpoint_path)
|
| 178 |
+
logging.info(f"Loaded vocoder parameters from {vocoder_checkpoint_path}.")
|
| 179 |
+
vocoder = vocoder.eval().to(device)
|
| 180 |
+
# load vq codebook
|
| 181 |
+
feat_codebook = torch.tensor(np.load(vocoder_config["vq_codebook"], allow_pickle=True)).to(device) # (V, D)
|
| 182 |
+
if feat_codebook.ndim == 2:
|
| 183 |
+
feat_codebook = feat_codebook.unsqueeze(0)
|
| 184 |
+
feat_codebook_numgroups = feat_codebook.shape[0]
|
| 185 |
+
feat_codebook = torch.nn.ModuleList([torch.nn.Embedding.from_pretrained(feat_codebook[i], freeze=True) for i in range(feat_codebook_numgroups)])
|
| 186 |
+
|
| 187 |
+
wavlm_path = os.path.join(args.pretrained_dir, "WavLM-Large.pt")
|
| 188 |
+
wavlm = WavLMExtractor(checkpoint=wavlm_path, device=device)
|
| 189 |
+
|
| 190 |
+
# start generation
|
| 191 |
+
with torch.no_grad(), tqdm(utt2path.items(), desc="[prompted recon]") as pbar:
|
| 192 |
+
for idx, (utt_id, audio_path) in enumerate(pbar, 1):
|
| 193 |
+
prompt_path = prompt_scp[utt_id]
|
| 194 |
+
audio, sr = load_audio(audio_path)
|
| 195 |
+
prompt_audio, prompt_sr = load_audio(prompt_path)
|
| 196 |
+
|
| 197 |
+
if sr != encoder_config['feature_extractor_params']['fs']:
|
| 198 |
+
audio = librosa.resample(audio, orig_sr=sr, target_sr=encoder_config['feature_extractor_params']['fs'])
|
| 199 |
+
|
| 200 |
+
if prompt_sr != 16000:
|
| 201 |
+
prompt_audio = librosa.resample(prompt_audio, orig_sr=prompt_sr, target_sr=16000)
|
| 202 |
+
|
| 203 |
+
audio = torch.tensor(audio).float().to(device) # (L)
|
| 204 |
+
audio = audio.unsqueeze(0).unsqueeze(0) # (1, 1, L)
|
| 205 |
+
|
| 206 |
+
prompt = wavlm.extract(prompt_audio)
|
| 207 |
+
prompt = prompt.float().unsqueeze(0).to(device) # [1, L, D]
|
| 208 |
+
|
| 209 |
+
# generate
|
| 210 |
+
_, _, embed_index = model.encode(audio)
|
| 211 |
+
if vocoder_config.get("repeat_input_tokens", False):
|
| 212 |
+
# in LSCodec-25Hz, we still trained the vocoder on 50Hz input, so repeat_interleave is needed.
|
| 213 |
+
embed_index = embed_index.repeat_interleave(2, dim=0)
|
| 214 |
+
vqvec = torch.cat([feat_codebook[i](embed_index[:, i]) for i in range(feat_codebook_numgroups)], dim=-1).unsqueeze(0) # (1, L, D)
|
| 215 |
+
y = vocoder.inference(vqvec, prompt)[-1].view(-1)
|
| 216 |
+
|
| 217 |
+
tgt_dir = os.path.dirname(os.path.join(args.outdir, f"{utt_id}.wav"))
|
| 218 |
+
os.makedirs(tgt_dir, exist_ok=True)
|
| 219 |
+
basename = os.path.basename(f"{utt_id}.wav")
|
| 220 |
+
# save as PCM 16 bit wav file
|
| 221 |
+
sf.write(
|
| 222 |
+
os.path.join(tgt_dir, basename),
|
| 223 |
+
y.cpu().numpy(),
|
| 224 |
+
vocoder_config["sampling_rate"],
|
| 225 |
+
"PCM_16",
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
# report average RTF
|
| 229 |
+
logging.info(
|
| 230 |
+
f"Finished generation of {idx} utterances."
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
if __name__ == "__main__":
|
| 235 |
+
main()
|
lscodec/bin/train.py
ADDED
|
@@ -0,0 +1,994 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 6 |
+
|
| 7 |
+
# Portions of this code are a modification of the ParallelWaveGAN project.
|
| 8 |
+
# The original source code can be found at:
|
| 9 |
+
# https://github.com/kan-bayashi/ParallelWaveGAN/blob/master/parallel_wavegan/bin/train.py
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 12 |
+
# Licensed under the MIT license.
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import logging
|
| 16 |
+
import os
|
| 17 |
+
import sys
|
| 18 |
+
import random
|
| 19 |
+
|
| 20 |
+
from collections import defaultdict
|
| 21 |
+
|
| 22 |
+
import matplotlib
|
| 23 |
+
import numpy as np
|
| 24 |
+
import soundfile as sf
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
import yaml
|
| 28 |
+
import torch.multiprocessing as mp
|
| 29 |
+
from tensorboardX import SummaryWriter
|
| 30 |
+
from torch.utils.data import DataLoader
|
| 31 |
+
from tqdm import tqdm
|
| 32 |
+
|
| 33 |
+
import lscodec
|
| 34 |
+
import lscodec.models
|
| 35 |
+
from lscodec.models.quantization.core_vq import GroupVectorQuantization
|
| 36 |
+
import lscodec.optimizers
|
| 37 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 38 |
+
|
| 39 |
+
from lscodec.datasets.scp_dataset import AudioMelSCPDataset
|
| 40 |
+
from lscodec.utils.espnet_utils import pad_list, make_non_pad_mask
|
| 41 |
+
from lscodec.datasets.perturbs import Perturbs
|
| 42 |
+
|
| 43 |
+
# set to avoid matplotlib error in CLI environment
|
| 44 |
+
matplotlib.use("Agg")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def KL_loss(means, logvars, mask):
|
| 48 |
+
means = means.masked_select(mask)
|
| 49 |
+
logvars = logvars.masked_select(mask)
|
| 50 |
+
logvars = torch.clamp(logvars, -3, 3) # NOTE: added to ensure stability
|
| 51 |
+
loss = -0.5*logvars + 0.5*(logvars.exp() + means.pow(2)) - 0.5
|
| 52 |
+
# if torch.isnan(loss).any():
|
| 53 |
+
# logging.info("detected nan in kl_loss")
|
| 54 |
+
# raise ValueError()
|
| 55 |
+
# loss = torch.nan_to_num(loss, nan=10)
|
| 56 |
+
return loss.mean()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class Trainer(object):
|
| 60 |
+
"""Customized trainer module for Parallel WaveGAN training."""
|
| 61 |
+
|
| 62 |
+
def __init__(
|
| 63 |
+
self,
|
| 64 |
+
steps,
|
| 65 |
+
epochs,
|
| 66 |
+
data_loader,
|
| 67 |
+
sampler,
|
| 68 |
+
model,
|
| 69 |
+
optimizer,
|
| 70 |
+
scheduler,
|
| 71 |
+
config,
|
| 72 |
+
device=torch.device("cpu"),
|
| 73 |
+
):
|
| 74 |
+
"""Initialize trainer.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
steps (int): Initial global steps.
|
| 78 |
+
epochs (int): Initial global epochs.
|
| 79 |
+
data_loader (dict): Dict of data loaders. It must contain "train" and "dev" loaders.
|
| 80 |
+
model (dict): Dict of models. It must contain "generator" model.
|
| 81 |
+
optimizer (dict): Dict of optimizers. It must contain "generator" and optimizer.
|
| 82 |
+
scheduler (dict): Dict of schedulers. It must contain "generator" and schedulers.
|
| 83 |
+
config (dict): Config dict loaded from yaml format configuration file.
|
| 84 |
+
device (torch.deive): Pytorch device instance.
|
| 85 |
+
|
| 86 |
+
"""
|
| 87 |
+
self.steps = steps
|
| 88 |
+
self.epochs = epochs
|
| 89 |
+
self.data_loader = data_loader
|
| 90 |
+
self.sampler = sampler
|
| 91 |
+
self.model = model
|
| 92 |
+
self.optimizer = optimizer
|
| 93 |
+
self.scheduler = scheduler
|
| 94 |
+
self.config = config
|
| 95 |
+
self.device = device
|
| 96 |
+
self.writer = SummaryWriter(config["outdir"])
|
| 97 |
+
self.finish_train = False
|
| 98 |
+
self.total_train_loss = defaultdict(float)
|
| 99 |
+
self.total_eval_loss = defaultdict(float)
|
| 100 |
+
|
| 101 |
+
def run(self):
|
| 102 |
+
"""Run training."""
|
| 103 |
+
self.tqdm = tqdm(
|
| 104 |
+
initial=self.steps, total=self.config["train_max_steps"], desc="[train]"
|
| 105 |
+
)
|
| 106 |
+
while True:
|
| 107 |
+
# train one epoch
|
| 108 |
+
self._train_epoch()
|
| 109 |
+
|
| 110 |
+
# check whether training is finished
|
| 111 |
+
if self.finish_train:
|
| 112 |
+
break
|
| 113 |
+
|
| 114 |
+
self.tqdm.close()
|
| 115 |
+
logging.info("Finished training.")
|
| 116 |
+
|
| 117 |
+
def save_checkpoint(self, checkpoint_path):
|
| 118 |
+
"""Save checkpoint.
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
checkpoint_path (str): Checkpoint path to be saved.
|
| 122 |
+
|
| 123 |
+
"""
|
| 124 |
+
state_dict = {
|
| 125 |
+
"optimizer": {
|
| 126 |
+
"generator": self.optimizer["generator"].state_dict(),
|
| 127 |
+
},
|
| 128 |
+
"scheduler": {
|
| 129 |
+
"generator": self.scheduler["generator"].state_dict(),
|
| 130 |
+
},
|
| 131 |
+
"steps": self.steps,
|
| 132 |
+
"epochs": self.epochs,
|
| 133 |
+
}
|
| 134 |
+
if self.config["distributed"]:
|
| 135 |
+
state_dict["model"] = {
|
| 136 |
+
"generator": self.model["generator"].module.state_dict(),
|
| 137 |
+
}
|
| 138 |
+
else:
|
| 139 |
+
state_dict["model"] = {
|
| 140 |
+
"generator": self.model["generator"].state_dict(),
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
if not os.path.exists(os.path.dirname(checkpoint_path)):
|
| 144 |
+
os.makedirs(os.path.dirname(checkpoint_path))
|
| 145 |
+
torch.save(state_dict, checkpoint_path)
|
| 146 |
+
|
| 147 |
+
def load_checkpoint(self, checkpoint_path, load_only_params=False):
|
| 148 |
+
"""Load checkpoint.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
checkpoint_path (str): Checkpoint path to be loaded.
|
| 152 |
+
load_only_params (bool): Whether to load only model parameters.
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
state_dict = torch.load(checkpoint_path, map_location="cpu")
|
| 156 |
+
if self.config["distributed"]:
|
| 157 |
+
self.model["generator"].module.load_state_dict(
|
| 158 |
+
state_dict["model"]["generator"]
|
| 159 |
+
)
|
| 160 |
+
else:
|
| 161 |
+
self.model["generator"].load_state_dict(state_dict["model"]["generator"])
|
| 162 |
+
if not load_only_params:
|
| 163 |
+
self.steps = state_dict["steps"]
|
| 164 |
+
self.epochs = state_dict["epochs"]
|
| 165 |
+
self.optimizer["generator"].load_state_dict(
|
| 166 |
+
state_dict["optimizer"]["generator"]
|
| 167 |
+
)
|
| 168 |
+
self.scheduler["generator"].load_state_dict(
|
| 169 |
+
state_dict["scheduler"]["generator"]
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
def load_vae_params(self, checkpoint_path):
|
| 173 |
+
state_dict = torch.load(checkpoint_path, map_location="cpu")
|
| 174 |
+
generator = state_dict['model']['generator']
|
| 175 |
+
frontend_state_dict = {}
|
| 176 |
+
backend_state_dict = {}
|
| 177 |
+
for key in generator.keys():
|
| 178 |
+
if key.startswith("frontend"):
|
| 179 |
+
frontend_state_dict[".".join(key.split(".")[1:])] = generator[key]
|
| 180 |
+
elif key.startswith("backend"):
|
| 181 |
+
backend_state_dict[".".join(key.split(".")[1:])] = generator[key]
|
| 182 |
+
else:
|
| 183 |
+
raise NotImplementedError(f"Unknown key: {key}")
|
| 184 |
+
if self.config["distributed"]:
|
| 185 |
+
self.model['generator'].module.frontend.load_state_dict(frontend_state_dict)
|
| 186 |
+
self.model['generator'].module.backend.load_state_dict(backend_state_dict)
|
| 187 |
+
else:
|
| 188 |
+
self.model['generator'].frontend.load_state_dict(frontend_state_dict)
|
| 189 |
+
self.model['generator'].backend.load_state_dict(backend_state_dict)
|
| 190 |
+
|
| 191 |
+
def open_codebook_expire(self):
|
| 192 |
+
if self.config['distributed']:
|
| 193 |
+
for group_i in range(len(self.model['generator'].module.quantizer.groups)):
|
| 194 |
+
self.model['generator'].module.quantizer.groups[group_i]._codebook.perform_expire = True
|
| 195 |
+
else:
|
| 196 |
+
for group_i in range(len(self.model['generator'].quantizer.groups)):
|
| 197 |
+
self.model['generator'].quantizer.groups[group_i]._codebook.perform_expire = True
|
| 198 |
+
|
| 199 |
+
def open_codebook_ema(self):
|
| 200 |
+
if self.config['distributed']:
|
| 201 |
+
for group_i in range(len(self.model['generator'].module.quantizer.groups)):
|
| 202 |
+
self.model['generator'].module.quantizer.groups[group_i]._codebook.perform_ema_update = True
|
| 203 |
+
else:
|
| 204 |
+
for group_i in range(len(self.model['generator'].quantizer.groups)):
|
| 205 |
+
self.model['generator'].quantizer.groups[group_i]._codebook.perform_ema_update = True
|
| 206 |
+
|
| 207 |
+
def _train_step(self, batch):
|
| 208 |
+
"""Train model one step."""
|
| 209 |
+
if self.config['have_quantizer'] and self.config['quantizer_class'] == "KMeans":
|
| 210 |
+
if self.steps > self.config['quantizer_params']['perform_expire_after_steps']:
|
| 211 |
+
self.open_codebook_expire()
|
| 212 |
+
if self.steps > self.config['quantizer_params']['perform_ema_after_steps']:
|
| 213 |
+
self.open_codebook_ema()
|
| 214 |
+
if self.config['have_quantizer'] and self.config['quantizer_class'] == "Gumbel":
|
| 215 |
+
self.model['generator'].module.quantizer.set_num_updates(self.steps)
|
| 216 |
+
|
| 217 |
+
# parse batch
|
| 218 |
+
aux, target, idx, prompt, y_for_extractor, xlens, prompt_lens = batch
|
| 219 |
+
target = target.to(self.device)
|
| 220 |
+
prompt = prompt.to(self.device)
|
| 221 |
+
idx = idx.to(self.device)
|
| 222 |
+
y_for_extractor = y_for_extractor.unsqueeze(-2).to(self.device) # (B, 1, T')
|
| 223 |
+
|
| 224 |
+
# build mask
|
| 225 |
+
mask = make_non_pad_mask(xlens).to(self.device) # (B, L) True for valid values
|
| 226 |
+
prompt_mask = make_non_pad_mask(prompt_lens).to(self.device) # (B, L_prompt)
|
| 227 |
+
|
| 228 |
+
#######################
|
| 229 |
+
# Generator #
|
| 230 |
+
#######################
|
| 231 |
+
if self.config['distributed']:
|
| 232 |
+
tolerance = self.model['generator'].module.length_mismatch_tolerance
|
| 233 |
+
else:
|
| 234 |
+
tolerance = self.model['generator'].length_mismatch_tolerance
|
| 235 |
+
target_, idx_logits_, _, commitment_loss, mask, means, logvars = self.model["generator"](y_for_extractor, prompt, mask, prompt_mask)
|
| 236 |
+
# (B, L, 80)
|
| 237 |
+
|
| 238 |
+
if target.shape[1] != mask.shape[-1]:
|
| 239 |
+
if abs(target.shape[1] - mask.shape[-1]) <= tolerance:
|
| 240 |
+
min_len = min(target.shape[1], mask.shape[-1])
|
| 241 |
+
target = target[:, :min_len, :]
|
| 242 |
+
mask = mask[..., :min_len]
|
| 243 |
+
target_ = target_[:, :min_len, :]
|
| 244 |
+
idx = idx[:, :min_len, :]
|
| 245 |
+
idx_logits_ = idx_logits_[:, :min_len, :]
|
| 246 |
+
|
| 247 |
+
# initialize
|
| 248 |
+
gen_loss = 0.0
|
| 249 |
+
# frontend target prediction loss
|
| 250 |
+
frontend_target_pred_loss = F.l1_loss(torch.masked_select(target, mask.unsqueeze(-1)),
|
| 251 |
+
torch.masked_select(target_, mask.unsqueeze(-1)))
|
| 252 |
+
self.total_train_loss["train/frontend_target_pred_loss"] += frontend_target_pred_loss.item()
|
| 253 |
+
gen_loss += self.config["lambda_frontend_target_prediction"] * frontend_target_pred_loss
|
| 254 |
+
|
| 255 |
+
idx_criterion = torch.nn.CrossEntropyLoss()
|
| 256 |
+
V = idx_logits_.shape[-1]
|
| 257 |
+
frontend_idx_pred_loss = idx_criterion(torch.masked_select(idx_logits_, mask.unsqueeze(-1)).view(-1, V),
|
| 258 |
+
torch.masked_select(idx.squeeze(-1), mask))
|
| 259 |
+
self.total_train_loss["train/frontend_idx_pred_loss"] += frontend_idx_pred_loss.item()
|
| 260 |
+
gen_loss += self.config["lambda_frontend_idx_prediction"] * frontend_idx_pred_loss
|
| 261 |
+
|
| 262 |
+
if commitment_loss is None:
|
| 263 |
+
if self.config['quantizer_class'] != "Gumbel": # In this case, no additional loss.
|
| 264 |
+
# VQVAE KL loss
|
| 265 |
+
kl_loss = KL_loss(means, logvars, mask.unsqueeze(-1))
|
| 266 |
+
gen_loss += self.config['lambda_kl_loss'] * kl_loss
|
| 267 |
+
self.total_train_loss['train/kl_loss'] += kl_loss.item()
|
| 268 |
+
else:
|
| 269 |
+
# VQ loss (if kmeans)
|
| 270 |
+
if self.config['quantizer_class'] == "KMeans":
|
| 271 |
+
gen_loss += self.config['lambda_kmeans_loss'] * commitment_loss
|
| 272 |
+
self.total_train_loss['train/kmeans_loss'] += commitment_loss.item()
|
| 273 |
+
|
| 274 |
+
self.total_train_loss["train/generator_loss"] += gen_loss.item()
|
| 275 |
+
|
| 276 |
+
# update generator
|
| 277 |
+
self.optimizer["generator"].zero_grad()
|
| 278 |
+
# gen_loss = torch.nan_to_num(gen_loss, nan=100) # NOTE: will this ensure stability?
|
| 279 |
+
if torch.isnan(gen_loss):
|
| 280 |
+
logging.info(f"Find NAN in loss. Refusing to update.")
|
| 281 |
+
gen_loss = 0.
|
| 282 |
+
for p in self.model['generator'].parameters():
|
| 283 |
+
gen_loss = gen_loss + 0.0 * p.sum()
|
| 284 |
+
# gen_loss = torch.sum([0.0*p.sum() for p in self.model['generator'].parameters()]) # fake loss. Gradient=0
|
| 285 |
+
|
| 286 |
+
gen_loss.backward()
|
| 287 |
+
if self.config["generator_grad_norm"] > 0:
|
| 288 |
+
torch.nn.utils.clip_grad_norm_(
|
| 289 |
+
self.model["generator"].parameters(),
|
| 290 |
+
self.config["generator_grad_norm"],
|
| 291 |
+
)
|
| 292 |
+
self.optimizer["generator"].step()
|
| 293 |
+
self.scheduler["generator"].step()
|
| 294 |
+
# update counts
|
| 295 |
+
self.steps += 1
|
| 296 |
+
self.tqdm.update(1)
|
| 297 |
+
self._check_train_finish()
|
| 298 |
+
|
| 299 |
+
def _train_epoch(self):
|
| 300 |
+
"""Train model one epoch."""
|
| 301 |
+
for train_steps_per_epoch, batch in enumerate(self.data_loader["train"], 1):
|
| 302 |
+
# train one step
|
| 303 |
+
self._train_step(batch)
|
| 304 |
+
|
| 305 |
+
# check interval
|
| 306 |
+
if self.config["rank"] == 0:
|
| 307 |
+
self._check_log_interval()
|
| 308 |
+
self._check_eval_interval()
|
| 309 |
+
self._check_save_interval()
|
| 310 |
+
|
| 311 |
+
# check whether training is finished
|
| 312 |
+
if self.finish_train:
|
| 313 |
+
return
|
| 314 |
+
|
| 315 |
+
# update
|
| 316 |
+
self.epochs += 1
|
| 317 |
+
self.train_steps_per_epoch = train_steps_per_epoch
|
| 318 |
+
logging.info(
|
| 319 |
+
f"(Steps: {self.steps}) Finished {self.epochs} epoch training "
|
| 320 |
+
f"({self.train_steps_per_epoch} steps per epoch)."
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
# needed for shuffle in distributed training
|
| 324 |
+
if self.config["distributed"]:
|
| 325 |
+
self.sampler["train"].set_epoch(self.epochs)
|
| 326 |
+
|
| 327 |
+
@torch.no_grad()
|
| 328 |
+
def _eval_step(self, batch):
|
| 329 |
+
"""Evaluate model one step."""
|
| 330 |
+
# parse batch
|
| 331 |
+
aux, target, idx, prompt, y_for_extractor, xlens, prompt_lens = batch
|
| 332 |
+
target = target.to(self.device)
|
| 333 |
+
prompt = prompt.to(self.device)
|
| 334 |
+
idx = idx.to(self.device)
|
| 335 |
+
y_for_extractor = y_for_extractor.unsqueeze(-2).to(self.device) # (B, 1, T')
|
| 336 |
+
|
| 337 |
+
# build mask
|
| 338 |
+
mask = make_non_pad_mask(xlens).to(self.device) # (B, L)
|
| 339 |
+
prompt_mask = make_non_pad_mask(prompt_lens).to(self.device) # (B, L_prompt)
|
| 340 |
+
|
| 341 |
+
#######################
|
| 342 |
+
# Generator #
|
| 343 |
+
#######################
|
| 344 |
+
if self.config['distributed']:
|
| 345 |
+
tolerance = self.model['generator'].module.length_mismatch_tolerance
|
| 346 |
+
else:
|
| 347 |
+
tolerance = self.model['generator'].length_mismatch_tolerance
|
| 348 |
+
target_, idx_logits_, _, commitment_loss, mask, means, logvars = self.model["generator"](y_for_extractor, prompt, mask, prompt_mask) # (B, L, 80), (B, C, T)
|
| 349 |
+
if target.shape[1] != mask.shape[-1]:
|
| 350 |
+
if abs(target.shape[1] - mask.shape[-1]) <= tolerance:
|
| 351 |
+
min_len = min(target.shape[1], mask.shape[-1])
|
| 352 |
+
target = target[:, :min_len, :]
|
| 353 |
+
mask = mask[..., :min_len]
|
| 354 |
+
target_ = target_[:, :min_len, :]
|
| 355 |
+
idx = idx[:, :min_len, :]
|
| 356 |
+
idx_logits_ = idx_logits_[:, :min_len, :]
|
| 357 |
+
|
| 358 |
+
# frontend target prediction loss
|
| 359 |
+
frontend_target_pred_loss = F.l1_loss(torch.masked_select(target, mask.unsqueeze(-1)),
|
| 360 |
+
torch.masked_select(target_, mask.unsqueeze(-1)))
|
| 361 |
+
self.total_eval_loss["eval/frontend_target_pred_loss"] += frontend_target_pred_loss.item()
|
| 362 |
+
|
| 363 |
+
idx_criterion = torch.nn.CrossEntropyLoss()
|
| 364 |
+
V = idx_logits_.shape[-1]
|
| 365 |
+
frontend_idx_pred_loss = idx_criterion(torch.masked_select(idx_logits_, mask.unsqueeze(-1)).view(-1, V),
|
| 366 |
+
torch.masked_select(idx.squeeze(-1), mask))
|
| 367 |
+
self.total_eval_loss["eval/frontend_idx_pred_loss"] += frontend_idx_pred_loss.item()
|
| 368 |
+
|
| 369 |
+
if commitment_loss is None:
|
| 370 |
+
if self.config['quantizer_class'] != "Gumbel": # In this case, no additional loss.
|
| 371 |
+
# VAE KL loss
|
| 372 |
+
kl_loss = KL_loss(means, logvars, mask.unsqueeze(-1))
|
| 373 |
+
self.total_eval_loss['eval/kl_loss'] += kl_loss.item()
|
| 374 |
+
else:
|
| 375 |
+
# VQ loss (if kmeans)
|
| 376 |
+
if self.config['quantizer_class'] == "KMeans":
|
| 377 |
+
self.total_eval_loss['eval/kmeans_loss'] += commitment_loss.item()
|
| 378 |
+
|
| 379 |
+
def _eval_epoch(self):
|
| 380 |
+
"""Evaluate model one epoch."""
|
| 381 |
+
logging.info(f"(Steps: {self.steps}) Start evaluation.")
|
| 382 |
+
# change mode
|
| 383 |
+
for key in self.model.keys():
|
| 384 |
+
self.model[key].eval()
|
| 385 |
+
|
| 386 |
+
# calculate loss for each batch
|
| 387 |
+
for eval_steps_per_epoch, batch in enumerate(
|
| 388 |
+
tqdm(self.data_loader["dev"], desc="[eval]"), 1
|
| 389 |
+
):
|
| 390 |
+
# eval one step
|
| 391 |
+
self._eval_step(batch)
|
| 392 |
+
|
| 393 |
+
logging.info(
|
| 394 |
+
f"(Steps: {self.steps}) Finished evaluation "
|
| 395 |
+
f"({eval_steps_per_epoch} steps per epoch)."
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
# average loss
|
| 399 |
+
for key in self.total_eval_loss.keys():
|
| 400 |
+
self.total_eval_loss[key] /= eval_steps_per_epoch
|
| 401 |
+
logging.info(
|
| 402 |
+
f"(Steps: {self.steps}) {key} = {self.total_eval_loss[key]:.4f}."
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
+
# record
|
| 406 |
+
self._write_to_tensorboard(self.total_eval_loss)
|
| 407 |
+
|
| 408 |
+
# reset
|
| 409 |
+
self.total_eval_loss = defaultdict(float)
|
| 410 |
+
|
| 411 |
+
# restore mode
|
| 412 |
+
for key in self.model.keys():
|
| 413 |
+
self.model[key].train()
|
| 414 |
+
|
| 415 |
+
def _write_to_tensorboard(self, loss):
|
| 416 |
+
"""Write to tensorboard."""
|
| 417 |
+
for key, value in loss.items():
|
| 418 |
+
self.writer.add_scalar(key, value, self.steps)
|
| 419 |
+
|
| 420 |
+
def _check_save_interval(self):
|
| 421 |
+
if self.steps % self.config["save_interval_steps"] == 0:
|
| 422 |
+
self.save_checkpoint(
|
| 423 |
+
os.path.join(self.config["outdir"], f"checkpoint-{self.steps}steps.pkl")
|
| 424 |
+
)
|
| 425 |
+
logging.info(f"Successfully saved checkpoint @ {self.steps} steps.")
|
| 426 |
+
|
| 427 |
+
def _check_eval_interval(self):
|
| 428 |
+
if self.steps % self.config["eval_interval_steps"] == 0:
|
| 429 |
+
self._eval_epoch()
|
| 430 |
+
|
| 431 |
+
def _check_log_interval(self):
|
| 432 |
+
if self.steps % self.config["log_interval_steps"] == 0:
|
| 433 |
+
for key in self.total_train_loss.keys():
|
| 434 |
+
self.total_train_loss[key] /= self.config["log_interval_steps"]
|
| 435 |
+
logging.info(
|
| 436 |
+
f"(Steps: {self.steps}) {key} = {self.total_train_loss[key]:.4f}."
|
| 437 |
+
)
|
| 438 |
+
if self.config['have_quantizer'] and self.config['quantizer_class'] == "Gumbel":
|
| 439 |
+
logging.info(f"(Steps: {self.steps}) gumbel temperature = {self.model['generator'].module.quantizer.curr_temp}")
|
| 440 |
+
self._write_to_tensorboard(self.total_train_loss)
|
| 441 |
+
|
| 442 |
+
# reset
|
| 443 |
+
self.total_train_loss = defaultdict(float)
|
| 444 |
+
|
| 445 |
+
def _check_train_finish(self):
|
| 446 |
+
if self.steps >= self.config["train_max_steps"]:
|
| 447 |
+
self.finish_train = True
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
class Collator(object):
|
| 451 |
+
"""Customized collator for Pytorch DataLoader in training."""
|
| 452 |
+
|
| 453 |
+
def __init__(
|
| 454 |
+
self,
|
| 455 |
+
hop_size_for_extractor=256,
|
| 456 |
+
win_length_for_extractor=1024,
|
| 457 |
+
sampling_rate=16000,
|
| 458 |
+
n_mel=80,
|
| 459 |
+
force_from_start=False,
|
| 460 |
+
):
|
| 461 |
+
"""Initialize customized collator for PyTorch DataLoader.
|
| 462 |
+
|
| 463 |
+
Args:
|
| 464 |
+
hop_size_for_extractor (int): Hop size of features, in sampling points.
|
| 465 |
+
win_length_for_extractor (int): window length of features.
|
| 466 |
+
sampling_rate (int): sampling rate of waveform data
|
| 467 |
+
n_mel (int): number of mel spectrogram dimensions
|
| 468 |
+
"""
|
| 469 |
+
self.hop_size_for_extractor = hop_size_for_extractor
|
| 470 |
+
self.win_length_for_extractor = win_length_for_extractor
|
| 471 |
+
self.sampling_rate = sampling_rate
|
| 472 |
+
self.n_mel = n_mel
|
| 473 |
+
self.force_from_start = force_from_start
|
| 474 |
+
|
| 475 |
+
def construct_prompt(self, mel_lens):
|
| 476 |
+
prompt_lens = [random.randint(int(l / 3), int(l / 2)) for l in mel_lens]
|
| 477 |
+
prompt_starts = []
|
| 478 |
+
is_from_start = []
|
| 479 |
+
for ml, pl in zip(mel_lens, prompt_lens):
|
| 480 |
+
random_number = random.random()
|
| 481 |
+
if self.force_from_start:
|
| 482 |
+
random_number = 1
|
| 483 |
+
if random_number > 0.5:
|
| 484 |
+
# from start
|
| 485 |
+
prompt_start = random.randint(0, 1 * self.sampling_rate//self.hop_size_for_extractor)
|
| 486 |
+
is_from_start.append(True)
|
| 487 |
+
else:
|
| 488 |
+
# from ending
|
| 489 |
+
prompt_start = random.randint((ml - 1 * self.sampling_rate//self.hop_size_for_extractor), ml) - pl
|
| 490 |
+
is_from_start.append(False)
|
| 491 |
+
prompt_starts.append(prompt_start)
|
| 492 |
+
return prompt_lens, prompt_starts, is_from_start
|
| 493 |
+
|
| 494 |
+
def __call__(self, batch):
|
| 495 |
+
"""Convert into batch tensors.
|
| 496 |
+
|
| 497 |
+
Args:
|
| 498 |
+
batch (list): list of tuple of the pair of audio and features.
|
| 499 |
+
|
| 500 |
+
This collator will automatically determine the prompt segment (acoustic context) for each utterance.
|
| 501 |
+
The prompt is cut off from the current utterance, ranging from 2 to 3 seconds.
|
| 502 |
+
The starting point of the prompt lies between the first 0-1 second range.
|
| 503 |
+
Then, it parses the concatenated features into (3 dim auxiliary features, 2 dim VQ features, and 80 dim mel spectrograms)
|
| 504 |
+
|
| 505 |
+
Returns:
|
| 506 |
+
Tensor: Gaussian noise batch (B, 1, T).
|
| 507 |
+
Tensor: Auxiliary feature batch (B, C, T'), where
|
| 508 |
+
T = T' * hop_size.
|
| 509 |
+
Tensor: Target signal batch (B, 1, T).
|
| 510 |
+
|
| 511 |
+
"""
|
| 512 |
+
batch = batch[0]
|
| 513 |
+
|
| 514 |
+
# check length
|
| 515 |
+
batch = [
|
| 516 |
+
self._adjust_length(*b) for b in batch
|
| 517 |
+
]
|
| 518 |
+
ys_for_extractor, mels, auxs, targets, idxs = ([b[0] for b in batch], [b[1] for b in batch], [b[2] for b in batch], [b[3] for b in batch], [b[4] for b in batch])
|
| 519 |
+
|
| 520 |
+
batch_size = len(ys_for_extractor)
|
| 521 |
+
prompt_lengths, prompt_starts, is_from_starts = self.construct_prompt([len(m) for m in mels])
|
| 522 |
+
|
| 523 |
+
c_lengths = []
|
| 524 |
+
|
| 525 |
+
prompts = torch.zeros(batch_size, max(prompt_lengths), self.n_mel)
|
| 526 |
+
for i in range(batch_size):
|
| 527 |
+
# data_offset = prompt_offsets[i] + prompt_lengths[i]
|
| 528 |
+
prompts[i, :prompt_lengths[i]] = torch.tensor(mels[i][prompt_starts[i]:prompt_starts[i]+prompt_lengths[i], :])
|
| 529 |
+
if is_from_starts[i]:
|
| 530 |
+
targets[i] = targets[i][prompt_starts[i]+prompt_lengths[i]:]
|
| 531 |
+
idxs[i] = idxs[i][prompt_starts[i]+prompt_lengths[i]:]
|
| 532 |
+
auxs[i] = auxs[i][prompt_starts[i]+prompt_lengths[i]:]
|
| 533 |
+
ys_for_extractor[i] = ys_for_extractor[i][(prompt_starts[i]+prompt_lengths[i])*self.hop_size_for_extractor:]
|
| 534 |
+
else:
|
| 535 |
+
targets[i] = targets[i][:prompt_starts[i]]
|
| 536 |
+
idxs[i] = idxs[i][:prompt_starts[i]]
|
| 537 |
+
auxs[i] = auxs[i][:prompt_starts[i]]
|
| 538 |
+
ys_for_extractor[i] = ys_for_extractor[i][:prompt_starts[i]*self.hop_size_for_extractor]
|
| 539 |
+
c_lengths.append(len(targets[i]))
|
| 540 |
+
# NOTE: PAD_VALUES is set to 999 instead of 0, so that the code vectors to be quantized are not affected.
|
| 541 |
+
targets = pad_list([torch.tensor(c) for c in targets], pad_value=0) # (B, L, 80)
|
| 542 |
+
idxs = pad_list([torch.tensor(c) for c in idxs], pad_value=0) # (B, L, 80)
|
| 543 |
+
idxs = idxs.long()
|
| 544 |
+
auxs = pad_list([torch.tensor(c) for c in auxs], pad_value=0) # (B, L, 3)
|
| 545 |
+
ys_for_extractor = pad_list([torch.tensor(y, dtype=torch.float) for y in ys_for_extractor],
|
| 546 |
+
pad_value=0)[:, :targets.size(1) * self.hop_size_for_extractor]
|
| 547 |
+
assert targets.size(1) == auxs.size(1) == idxs.size(1)
|
| 548 |
+
assert ys_for_extractor.size(1) == targets.size(1) * self.hop_size_for_extractor == auxs.size(1) * self.hop_size_for_extractor
|
| 549 |
+
|
| 550 |
+
return auxs, targets, idxs, prompts, ys_for_extractor, c_lengths, prompt_lengths
|
| 551 |
+
|
| 552 |
+
def _adjust_length(self, x, c, *args):
|
| 553 |
+
"""Adjust the audio and feature lengths.
|
| 554 |
+
|
| 555 |
+
Note:
|
| 556 |
+
Basically we assume that the length of x and c are adjusted
|
| 557 |
+
through preprocessing stage, but if we use other library processed
|
| 558 |
+
features, this process will be needed.
|
| 559 |
+
|
| 560 |
+
"""
|
| 561 |
+
if len(x) > len(c) * self.hop_size_for_extractor:
|
| 562 |
+
x = x[(self.win_length_for_extractor - self.hop_size_for_extractor) // 2:]
|
| 563 |
+
x = x[:len(c) * self.hop_size_for_extractor]
|
| 564 |
+
|
| 565 |
+
# check the legnth is valid
|
| 566 |
+
assert len(x) == len(c) * self.hop_size_for_extractor, f"{len(x)}, {len(c)}, {self.hop_size_for_extractor}"
|
| 567 |
+
|
| 568 |
+
return x, c, *args
|
| 569 |
+
|
| 570 |
+
|
| 571 |
+
def main(rank, n_gpus):
|
| 572 |
+
"""Run training process."""
|
| 573 |
+
parser = argparse.ArgumentParser(
|
| 574 |
+
description="Train Parallel WaveGAN (See detail in ctx_vec2wav/bin/train.py)."
|
| 575 |
+
)
|
| 576 |
+
parser.add_argument(
|
| 577 |
+
"--train-wav-scp",
|
| 578 |
+
default=None,
|
| 579 |
+
type=str,
|
| 580 |
+
help="kaldi-style wav.scp file for training. "
|
| 581 |
+
)
|
| 582 |
+
parser.add_argument(
|
| 583 |
+
"--train-idx-scp",
|
| 584 |
+
default=None,
|
| 585 |
+
type=str,
|
| 586 |
+
help="kaldi-style feats.scp file for training. "
|
| 587 |
+
)
|
| 588 |
+
parser.add_argument(
|
| 589 |
+
"--train-prompt-scp",
|
| 590 |
+
default=None,
|
| 591 |
+
type=str,
|
| 592 |
+
help="kaldi-style feats.scp file for training. "
|
| 593 |
+
)
|
| 594 |
+
parser.add_argument(
|
| 595 |
+
"--train-target-scp",
|
| 596 |
+
default=None,
|
| 597 |
+
type=str,
|
| 598 |
+
help="kaldi-style feats.scp file for training. "
|
| 599 |
+
)
|
| 600 |
+
parser.add_argument(
|
| 601 |
+
"--train-aux-scp",
|
| 602 |
+
default=None,
|
| 603 |
+
type=str,
|
| 604 |
+
help="kaldi-style feats.scp file for training. "
|
| 605 |
+
)
|
| 606 |
+
parser.add_argument(
|
| 607 |
+
"--train-segments",
|
| 608 |
+
default=None,
|
| 609 |
+
type=str,
|
| 610 |
+
help="kaldi-style segments file for training.",
|
| 611 |
+
)
|
| 612 |
+
# parser.add_argument(
|
| 613 |
+
# "--train-xvector-scp",
|
| 614 |
+
# default=None,
|
| 615 |
+
# type=str,
|
| 616 |
+
# help="kaldi-style xvector.scp file for training.",
|
| 617 |
+
# )
|
| 618 |
+
parser.add_argument(
|
| 619 |
+
"--train-num-frames",
|
| 620 |
+
default=None,
|
| 621 |
+
type=str,
|
| 622 |
+
help="kaldi-style utt2num_frames file for training.",
|
| 623 |
+
)
|
| 624 |
+
parser.add_argument(
|
| 625 |
+
"--train-utt2spk",
|
| 626 |
+
default=None,
|
| 627 |
+
type=str,
|
| 628 |
+
help="kaldi-style utt2spk file for training.",
|
| 629 |
+
)
|
| 630 |
+
parser.add_argument(
|
| 631 |
+
"--train-spk2gender",
|
| 632 |
+
default=None,
|
| 633 |
+
type=str,
|
| 634 |
+
help="kaldi-style spk2gender file for training.",
|
| 635 |
+
)
|
| 636 |
+
parser.add_argument(
|
| 637 |
+
"--dev-wav-scp",
|
| 638 |
+
default=None,
|
| 639 |
+
type=str,
|
| 640 |
+
help="kaldi-style wav.scp file for validation. "
|
| 641 |
+
)
|
| 642 |
+
parser.add_argument(
|
| 643 |
+
"--dev-idx-scp",
|
| 644 |
+
default=None,
|
| 645 |
+
type=str,
|
| 646 |
+
help="kaldi-style feats.scp file for vaidation. "
|
| 647 |
+
)
|
| 648 |
+
parser.add_argument(
|
| 649 |
+
"--dev-prompt-scp",
|
| 650 |
+
default=None,
|
| 651 |
+
type=str,
|
| 652 |
+
help="kaldi-style feats.scp file for vaidation. "
|
| 653 |
+
)
|
| 654 |
+
parser.add_argument(
|
| 655 |
+
"--dev-target-scp",
|
| 656 |
+
default=None,
|
| 657 |
+
type=str,
|
| 658 |
+
help="kaldi-style feats.scp file for vaidation. "
|
| 659 |
+
)
|
| 660 |
+
parser.add_argument(
|
| 661 |
+
"--dev-aux-scp",
|
| 662 |
+
default=None,
|
| 663 |
+
type=str,
|
| 664 |
+
help="kaldi-style feats.scp file for vaidation. "
|
| 665 |
+
)
|
| 666 |
+
parser.add_argument(
|
| 667 |
+
"--dev-segments",
|
| 668 |
+
default=None,
|
| 669 |
+
type=str,
|
| 670 |
+
help="kaldi-style segments file for validation.",
|
| 671 |
+
)
|
| 672 |
+
# parser.add_argument(
|
| 673 |
+
# "--dev-xvector-scp",
|
| 674 |
+
# default=None,
|
| 675 |
+
# type=str,
|
| 676 |
+
# help="kaldi-style xvector.scp file for validation.",
|
| 677 |
+
# )
|
| 678 |
+
parser.add_argument(
|
| 679 |
+
"--dev-num-frames",
|
| 680 |
+
default=None,
|
| 681 |
+
type=str,
|
| 682 |
+
help="kaldi-style utt2num_frames file for validation.",
|
| 683 |
+
)
|
| 684 |
+
parser.add_argument(
|
| 685 |
+
"--dev-utt2spk",
|
| 686 |
+
default=None,
|
| 687 |
+
type=str,
|
| 688 |
+
help="kaldi-style utt2spk file for training.",
|
| 689 |
+
)
|
| 690 |
+
parser.add_argument(
|
| 691 |
+
"--dev-spk2gender",
|
| 692 |
+
default=None,
|
| 693 |
+
type=str,
|
| 694 |
+
help="kaldi-style spk2gender file for training.",
|
| 695 |
+
)
|
| 696 |
+
parser.add_argument(
|
| 697 |
+
"--outdir",
|
| 698 |
+
type=str,
|
| 699 |
+
required=True,
|
| 700 |
+
help="directory to save checkpoints.",
|
| 701 |
+
)
|
| 702 |
+
parser.add_argument(
|
| 703 |
+
"--config",
|
| 704 |
+
type=str,
|
| 705 |
+
required=True,
|
| 706 |
+
help="yaml format configuration file.",
|
| 707 |
+
)
|
| 708 |
+
parser.add_argument(
|
| 709 |
+
"--pretrain",
|
| 710 |
+
default="",
|
| 711 |
+
type=str,
|
| 712 |
+
nargs="?",
|
| 713 |
+
help='checkpoint file path to load pretrained params. (default="")',
|
| 714 |
+
)
|
| 715 |
+
# parser.add_argument("--pretrain-feature-extractor",
|
| 716 |
+
# default="",
|
| 717 |
+
# type=str,
|
| 718 |
+
# help="checkpoint file path to load pretrained params for CNN+trans feature extractor. Default=''")
|
| 719 |
+
parser.add_argument("--pretrain-vae",
|
| 720 |
+
default="",
|
| 721 |
+
type=str,
|
| 722 |
+
help="pretrained VAE (no VQ)")
|
| 723 |
+
parser.add_argument("--pretrain-codebook",
|
| 724 |
+
default="",
|
| 725 |
+
type=str,
|
| 726 |
+
help="checkpoint file path to load pretrained params for codebook. Default=''")
|
| 727 |
+
# parser.add_argument("--pretrain-ctx-vec2wav",
|
| 728 |
+
# default="",
|
| 729 |
+
# type=str,
|
| 730 |
+
# help="checkpoint file path to load pretrained params for ctx-vec2wav. Default=''")
|
| 731 |
+
parser.add_argument(
|
| 732 |
+
"--resume",
|
| 733 |
+
default="",
|
| 734 |
+
type=str,
|
| 735 |
+
nargs="?",
|
| 736 |
+
help='checkpoint file path to resume training. (default="")',
|
| 737 |
+
)
|
| 738 |
+
parser.add_argument(
|
| 739 |
+
"--verbose",
|
| 740 |
+
type=int,
|
| 741 |
+
default=1,
|
| 742 |
+
help="logging level. higher is more logging. (default=1)",
|
| 743 |
+
)
|
| 744 |
+
# parser.add_argument("--vq-codebook", default=None, type=str)
|
| 745 |
+
parser.add_argument("--sampling-rate", type=int)
|
| 746 |
+
parser.add_argument("--num-mels", type=int)
|
| 747 |
+
parser.add_argument("--hop-size", type=int)
|
| 748 |
+
parser.add_argument("--win-length", type=int)
|
| 749 |
+
args = parser.parse_args()
|
| 750 |
+
|
| 751 |
+
# init distributed training
|
| 752 |
+
device = torch.device("cuda")
|
| 753 |
+
# effective when using fixed size inputs
|
| 754 |
+
# see https://discuss.pytorch.org/t/what-does-torch-backends-cudnn-benchmark-do/5936
|
| 755 |
+
torch.backends.cudnn.benchmark = True
|
| 756 |
+
# setup for distributed training
|
| 757 |
+
# see example: https://github.com/NVIDIA/apex/tree/master/examples/simple/distributed
|
| 758 |
+
if n_gpus == 1:
|
| 759 |
+
assert rank == 0
|
| 760 |
+
|
| 761 |
+
# set logger
|
| 762 |
+
if args.verbose > 1:
|
| 763 |
+
logging.basicConfig(
|
| 764 |
+
level=logging.DEBUG,
|
| 765 |
+
stream=sys.stdout,
|
| 766 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 767 |
+
)
|
| 768 |
+
elif args.verbose > 0:
|
| 769 |
+
logging.basicConfig(
|
| 770 |
+
level=logging.INFO,
|
| 771 |
+
stream=sys.stdout,
|
| 772 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 773 |
+
)
|
| 774 |
+
else:
|
| 775 |
+
logging.basicConfig(
|
| 776 |
+
level=logging.WARN,
|
| 777 |
+
stream=sys.stdout,
|
| 778 |
+
format="%(asctime)s (%(module)s:%(lineno)d) %(levelname)s: %(message)s",
|
| 779 |
+
)
|
| 780 |
+
logging.warning("Skip DEBUG/INFO messages")
|
| 781 |
+
|
| 782 |
+
# check directory existence
|
| 783 |
+
if not os.path.exists(args.outdir):
|
| 784 |
+
os.makedirs(args.outdir)
|
| 785 |
+
|
| 786 |
+
# init process group
|
| 787 |
+
logging.info("Synchronizing between all workers.")
|
| 788 |
+
torch.distributed.init_process_group(backend="nccl", init_method="env://", world_size=n_gpus, rank=rank)
|
| 789 |
+
torch.cuda.set_device(rank)
|
| 790 |
+
logging.info("Finished init process group.")
|
| 791 |
+
|
| 792 |
+
# load and save config
|
| 793 |
+
with open(args.config) as f:
|
| 794 |
+
config = yaml.load(f, Loader=yaml.Loader)
|
| 795 |
+
config.update(vars(args))
|
| 796 |
+
config['rank'] = rank
|
| 797 |
+
config['distributed'] = True
|
| 798 |
+
config['world_size'] = n_gpus
|
| 799 |
+
# config["version"] = lscodec.__version__ # add version info
|
| 800 |
+
if rank == 0:
|
| 801 |
+
with open(os.path.join(args.outdir, "config.yml"), "w") as f:
|
| 802 |
+
yaml.dump(config, f, Dumper=yaml.Dumper)
|
| 803 |
+
for key, value in config.items():
|
| 804 |
+
logging.info(f"{key} = {value}")
|
| 805 |
+
|
| 806 |
+
# get perturber
|
| 807 |
+
perturber = Perturbs(
|
| 808 |
+
formant_f0_perturb_params=config.get("formant_f0_perturb_params", dict()),
|
| 809 |
+
speed_perturb_params=config.get("speed_perturb_params", dict()),
|
| 810 |
+
speed_perturb_prob=config.get("speed_perturb_prob", 1.0)
|
| 811 |
+
)
|
| 812 |
+
|
| 813 |
+
# get dataset
|
| 814 |
+
train_dataset = AudioMelSCPDataset(
|
| 815 |
+
wav_scp=args.train_wav_scp,
|
| 816 |
+
prompt_scp=args.train_prompt_scp,
|
| 817 |
+
target_scp=args.train_target_scp,
|
| 818 |
+
aux_scp=args.train_aux_scp,
|
| 819 |
+
idx_scp=args.train_idx_scp,
|
| 820 |
+
utt2num_frames=args.train_num_frames,
|
| 821 |
+
segments=args.train_segments,
|
| 822 |
+
batch_frames=config.get("batch_frames", None),
|
| 823 |
+
batch_size=config.get("batch_size", None),
|
| 824 |
+
min_num_frames=config.get("min_num_frames", None),
|
| 825 |
+
max_num_frames=config.get("max_num_frames", None),
|
| 826 |
+
allow_cache=config.get("allow_cache", False), # keep compatibility
|
| 827 |
+
length_tolerance=config.get("length_tolerance", 2),
|
| 828 |
+
prompt_min_length=config.get("prompt_min_length", 500),
|
| 829 |
+
extractor_fs=config['feature_extractor_params'].get("fs", 16000),
|
| 830 |
+
# speed_alpha_range=eval(config['speed_alpha_range'])
|
| 831 |
+
utt2spk=args.train_utt2spk,
|
| 832 |
+
spk2gender=args.train_spk2gender,
|
| 833 |
+
perturber=perturber
|
| 834 |
+
)
|
| 835 |
+
if rank == 0:
|
| 836 |
+
logging.info(f"The number of training files = {len(train_dataset)}.")
|
| 837 |
+
dev_dataset = AudioMelSCPDataset(
|
| 838 |
+
wav_scp=args.dev_wav_scp,
|
| 839 |
+
prompt_scp=args.dev_prompt_scp,
|
| 840 |
+
target_scp=args.dev_target_scp,
|
| 841 |
+
aux_scp=args.dev_aux_scp,
|
| 842 |
+
idx_scp=args.dev_idx_scp,
|
| 843 |
+
utt2num_frames=args.dev_num_frames,
|
| 844 |
+
segments=args.dev_segments,
|
| 845 |
+
min_num_frames=config.get("min_num_frames", None),
|
| 846 |
+
max_num_frames=config.get("max_num_frames", None),
|
| 847 |
+
allow_cache=config.get("allow_cache", False), # keep compatibility
|
| 848 |
+
length_tolerance=config.get("length_tolerance", 2),
|
| 849 |
+
prompt_min_length=config.get("prompt_min_length", 500),
|
| 850 |
+
extractor_fs=config['feature_extractor_params'].get("fs", 16000),
|
| 851 |
+
# speed_alpha_range=eval(config['speed_alpha_range'])
|
| 852 |
+
utt2spk=args.dev_utt2spk,
|
| 853 |
+
spk2gender=args.dev_spk2gender,
|
| 854 |
+
perturber=perturber
|
| 855 |
+
)
|
| 856 |
+
if rank == 0:
|
| 857 |
+
logging.info(f"The number of development files = {len(dev_dataset)}.")
|
| 858 |
+
dataset = {
|
| 859 |
+
"train": train_dataset,
|
| 860 |
+
"dev": dev_dataset,
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
# get data loader
|
| 864 |
+
collator = Collator(
|
| 865 |
+
hop_size_for_extractor=config['feature_extractor_params']['hop_size'],
|
| 866 |
+
win_length_for_extractor=config['feature_extractor_params']['win_length'],
|
| 867 |
+
sampling_rate=config["feature_extractor_params"]["fs"],
|
| 868 |
+
n_mel=1024,
|
| 869 |
+
force_from_start=config.get("force_from_start", False)
|
| 870 |
+
)
|
| 871 |
+
|
| 872 |
+
sampler = {
|
| 873 |
+
"train": DistributedSampler(
|
| 874 |
+
dataset=dataset["train"],
|
| 875 |
+
num_replicas=n_gpus,
|
| 876 |
+
rank=rank,
|
| 877 |
+
shuffle=True,
|
| 878 |
+
),
|
| 879 |
+
"dev": DistributedSampler(
|
| 880 |
+
dataset=dataset["dev"],
|
| 881 |
+
num_replicas=n_gpus,
|
| 882 |
+
rank=rank,
|
| 883 |
+
shuffle=False,
|
| 884 |
+
)}
|
| 885 |
+
data_loader = {
|
| 886 |
+
"train": DataLoader(
|
| 887 |
+
dataset=dataset["train"],
|
| 888 |
+
shuffle=False,
|
| 889 |
+
collate_fn=collator,
|
| 890 |
+
num_workers=config["num_workers"],
|
| 891 |
+
sampler=sampler["train"],
|
| 892 |
+
pin_memory=config["pin_memory"],
|
| 893 |
+
),
|
| 894 |
+
"dev": DataLoader(
|
| 895 |
+
dataset=dataset["dev"],
|
| 896 |
+
shuffle=False,
|
| 897 |
+
collate_fn=collator,
|
| 898 |
+
num_workers=config["num_workers"],
|
| 899 |
+
sampler=sampler["dev"],
|
| 900 |
+
pin_memory=config["pin_memory"],
|
| 901 |
+
),
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
from lscodec.utils import load_model
|
| 905 |
+
config['pretrain_codebook'] = args.pretrain_codebook
|
| 906 |
+
generator = load_model(config, None)
|
| 907 |
+
|
| 908 |
+
model = {
|
| 909 |
+
"generator": generator.to(device),
|
| 910 |
+
}
|
| 911 |
+
|
| 912 |
+
# define optimizers and schedulers
|
| 913 |
+
generator_optimizer_class = getattr(
|
| 914 |
+
lscodec.optimizers,
|
| 915 |
+
# keep compatibility
|
| 916 |
+
config.get("generator_optimizer_type", "RAdam"),
|
| 917 |
+
)
|
| 918 |
+
optimizer = {
|
| 919 |
+
"generator": generator_optimizer_class(
|
| 920 |
+
model["generator"].parameters(),
|
| 921 |
+
**config["generator_optimizer_params"],
|
| 922 |
+
),
|
| 923 |
+
}
|
| 924 |
+
generator_scheduler_class = getattr(
|
| 925 |
+
torch.optim.lr_scheduler,
|
| 926 |
+
# keep compatibility
|
| 927 |
+
config.get("generator_scheduler_type", "StepLR"),
|
| 928 |
+
)
|
| 929 |
+
scheduler = {
|
| 930 |
+
"generator": generator_scheduler_class(
|
| 931 |
+
optimizer=optimizer["generator"],
|
| 932 |
+
**config["generator_scheduler_params"],
|
| 933 |
+
),
|
| 934 |
+
}
|
| 935 |
+
from torch.nn.parallel import DistributedDataParallel
|
| 936 |
+
model["generator"] = DistributedDataParallel(model["generator"], device_ids=[rank], find_unused_parameters=True)
|
| 937 |
+
|
| 938 |
+
if rank == 0:
|
| 939 |
+
# show settings
|
| 940 |
+
logging.info(model["generator"])
|
| 941 |
+
logging.info(optimizer["generator"])
|
| 942 |
+
|
| 943 |
+
# define trainer
|
| 944 |
+
trainer = Trainer(
|
| 945 |
+
steps=0,
|
| 946 |
+
epochs=0,
|
| 947 |
+
data_loader=data_loader,
|
| 948 |
+
sampler=sampler,
|
| 949 |
+
model=model,
|
| 950 |
+
optimizer=optimizer,
|
| 951 |
+
scheduler=scheduler,
|
| 952 |
+
config=config,
|
| 953 |
+
device=device,
|
| 954 |
+
)
|
| 955 |
+
|
| 956 |
+
if len(args.pretrain_vae) != 0:
|
| 957 |
+
trainer.load_vae_params(args.pretrain_vae)
|
| 958 |
+
if rank == 0:
|
| 959 |
+
logging.info(f"Successfully load parameters from {args.pretrain_vae}.")
|
| 960 |
+
|
| 961 |
+
# load pretrained parameters from checkpoint
|
| 962 |
+
if len(args.pretrain) != 0:
|
| 963 |
+
trainer.load_checkpoint(args.pretrain, load_only_params=True)
|
| 964 |
+
if rank == 0:
|
| 965 |
+
logging.info(f"Successfully load parameters from {args.pretrain}.")
|
| 966 |
+
|
| 967 |
+
if len(args.resume) != 0:
|
| 968 |
+
trainer.load_checkpoint(args.resume)
|
| 969 |
+
if rank == 0:
|
| 970 |
+
logging.info(f"Successfully resumed from {args.resume}.")
|
| 971 |
+
|
| 972 |
+
# run training loop
|
| 973 |
+
try:
|
| 974 |
+
trainer.run()
|
| 975 |
+
finally:
|
| 976 |
+
if rank == 0:
|
| 977 |
+
trainer.save_checkpoint(
|
| 978 |
+
os.path.join(config["outdir"], f"checkpoint-{trainer.steps}steps.pkl")
|
| 979 |
+
)
|
| 980 |
+
logging.info(f"Successfully saved checkpoint @ {trainer.steps}steps.")
|
| 981 |
+
|
| 982 |
+
|
| 983 |
+
if __name__ == "__main__":
|
| 984 |
+
assert torch.cuda.is_available(), "CPU training is not allowed."
|
| 985 |
+
n_gpus = torch.cuda.device_count()
|
| 986 |
+
print(f"============> using {n_gpus} GPUS")
|
| 987 |
+
os.environ["MASTER_ADDR"] = "localhost"
|
| 988 |
+
os.environ["MASTER_PORT"] = "8001"
|
| 989 |
+
|
| 990 |
+
mp.spawn(
|
| 991 |
+
main,
|
| 992 |
+
nprocs=n_gpus,
|
| 993 |
+
args=(n_gpus,)
|
| 994 |
+
)
|
lscodec/datasets/perturbs.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Portions of this code are based on ContentVec.
|
| 4 |
+
# The original source code can be found at:
|
| 5 |
+
# https://github.com/auspicious3000/contentvec/blob/d746688a32940f4bee410ed7c87ec9cf8ff04f74/contentvec/data/audio/audio_utils_1.py
|
| 6 |
+
#
|
| 7 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 8 |
+
# Licensed under the MIT license.
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import numpy as np
|
| 12 |
+
from scipy.signal import sosfilt
|
| 13 |
+
from scipy import signal as sg
|
| 14 |
+
import sox
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def make_lowshelf(g, fc, Q, fs=44100):
|
| 18 |
+
"""Generate filter coefficients for 2nd order Lowshelf filter.
|
| 19 |
+
This function follows the code from the JUCE DSP library
|
| 20 |
+
which can be found in `juce_IIRFilter.cpp`.
|
| 21 |
+
|
| 22 |
+
The design equations are based upon those found in the Cookbook
|
| 23 |
+
formulae for audio equalizer biquad filter coefficients
|
| 24 |
+
by Robert Bristow-Johnson.
|
| 25 |
+
https://www.w3.org/2011/audio/audio-eq-cookbook.html
|
| 26 |
+
Args:
|
| 27 |
+
g (float): Gain factor in dB.
|
| 28 |
+
fc (float): Cutoff frequency in Hz.
|
| 29 |
+
Q (float): Q factor.
|
| 30 |
+
fs (float): Sampling frequency in Hz.
|
| 31 |
+
Returns:
|
| 32 |
+
tuple: (b, a) filter coefficients
|
| 33 |
+
"""
|
| 34 |
+
# convert gain from dB to linear
|
| 35 |
+
g = np.power(10,(g/20))
|
| 36 |
+
|
| 37 |
+
# initial values
|
| 38 |
+
A = np.max([0.0, np.sqrt(g)])
|
| 39 |
+
aminus1 = A - 1
|
| 40 |
+
aplus1 = A + 1
|
| 41 |
+
omega = (2 * np.pi * np.max([fc, 2.0])) / fs
|
| 42 |
+
coso = np.cos(omega)
|
| 43 |
+
beta = np.sin(omega) * np.sqrt(A) / Q
|
| 44 |
+
aminus1TimesCoso = aminus1 * coso
|
| 45 |
+
|
| 46 |
+
# coefs calculation
|
| 47 |
+
b0 = A * (aplus1 - aminus1TimesCoso + beta)
|
| 48 |
+
b1 = A * 2 * (aminus1 - aplus1 * coso)
|
| 49 |
+
b2 = A * (aplus1 - aminus1TimesCoso - beta)
|
| 50 |
+
a0 = aplus1 + aminus1TimesCoso + beta
|
| 51 |
+
a1 = -2 * (aminus1 + aplus1 * coso)
|
| 52 |
+
a2 = aplus1 + aminus1TimesCoso - beta
|
| 53 |
+
|
| 54 |
+
# output coefs
|
| 55 |
+
#b = np.array([b0/a0, b1/a0, b2/a0])
|
| 56 |
+
#a = np.array([a0/a0, a1/a0, a2/a0])
|
| 57 |
+
|
| 58 |
+
return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def make_highself(g, fc, Q, fs=44100):
|
| 63 |
+
"""Generate filter coefficients for 2nd order Highshelf filter.
|
| 64 |
+
This function follows the code from the JUCE DSP library
|
| 65 |
+
which can be found in `juce_IIRFilter.cpp`.
|
| 66 |
+
|
| 67 |
+
The design equations are based upon those found in the Cookbook
|
| 68 |
+
formulae for audio equalizer biquad filter coefficients
|
| 69 |
+
by Robert Bristow-Johnson.
|
| 70 |
+
https://www.w3.org/2011/audio/audio-eq-cookbook.html
|
| 71 |
+
Args:
|
| 72 |
+
g (float): Gain factor in dB.
|
| 73 |
+
fc (float): Cutoff frequency in Hz.
|
| 74 |
+
Q (float): Q factor.
|
| 75 |
+
fs (float): Sampling frequency in Hz.
|
| 76 |
+
Returns:
|
| 77 |
+
tuple: (b, a) filter coefficients
|
| 78 |
+
"""
|
| 79 |
+
# convert gain from dB to linear
|
| 80 |
+
g = np.power(10,(g/20))
|
| 81 |
+
|
| 82 |
+
# initial values
|
| 83 |
+
A = np.max([0.0, np.sqrt(g)])
|
| 84 |
+
aminus1 = A - 1
|
| 85 |
+
aplus1 = A + 1
|
| 86 |
+
omega = (2 * np.pi * np.max([fc, 2.0])) / fs
|
| 87 |
+
coso = np.cos(omega)
|
| 88 |
+
beta = np.sin(omega) * np.sqrt(A) / Q
|
| 89 |
+
aminus1TimesCoso = aminus1 * coso
|
| 90 |
+
|
| 91 |
+
# coefs calculation
|
| 92 |
+
b0 = A * (aplus1 + aminus1TimesCoso + beta)
|
| 93 |
+
b1 = A * -2 * (aminus1 + aplus1 * coso)
|
| 94 |
+
b2 = A * (aplus1 + aminus1TimesCoso - beta)
|
| 95 |
+
a0 = aplus1 - aminus1TimesCoso + beta
|
| 96 |
+
a1 = 2 * (aminus1 - aplus1 * coso)
|
| 97 |
+
a2 = aplus1 - aminus1TimesCoso - beta
|
| 98 |
+
|
| 99 |
+
# output coefs
|
| 100 |
+
#b = np.array([b0/a0, b1/a0, b2/a0])
|
| 101 |
+
#a = np.array([a0/a0, a1/a0, a2/a0])
|
| 102 |
+
|
| 103 |
+
return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def make_peaking(g, fc, Q, fs=44100):
|
| 108 |
+
"""Generate filter coefficients for 2nd order Peaking EQ.
|
| 109 |
+
This function follows the code from the JUCE DSP library
|
| 110 |
+
which can be found in `juce_IIRFilter.cpp`.
|
| 111 |
+
|
| 112 |
+
The design equations are based upon those found in the Cookbook
|
| 113 |
+
formulae for audio equalizer biquad filter coefficients
|
| 114 |
+
by Robert Bristow-Johnson.
|
| 115 |
+
https://www.w3.org/2011/audio/audio-eq-cookbook.html
|
| 116 |
+
Args:
|
| 117 |
+
g (float): Gain factor in dB.
|
| 118 |
+
fc (float): Cutoff frequency in Hz.
|
| 119 |
+
Q (float): Q factor.
|
| 120 |
+
fs (float): Sampling frequency in Hz.
|
| 121 |
+
Returns:
|
| 122 |
+
tuple: (b, a) filter coefficients
|
| 123 |
+
"""
|
| 124 |
+
# convert gain from dB to linear
|
| 125 |
+
g = np.power(10,(g/20))
|
| 126 |
+
|
| 127 |
+
# initial values
|
| 128 |
+
A = np.max([0.0, np.sqrt(g)])
|
| 129 |
+
omega = (2 * np.pi * np.max([fc, 2.0])) / fs
|
| 130 |
+
alpha = np.sin(omega) / (Q * 2)
|
| 131 |
+
c2 = -2 * np.cos(omega)
|
| 132 |
+
alphaTimesA = alpha * A
|
| 133 |
+
alphaOverA = alpha / A
|
| 134 |
+
|
| 135 |
+
# coefs calculation
|
| 136 |
+
b0 = 1 + alphaTimesA
|
| 137 |
+
b1 = c2
|
| 138 |
+
b2 = 1 - alphaTimesA
|
| 139 |
+
a0 = 1 + alphaOverA
|
| 140 |
+
a1 = c2
|
| 141 |
+
a2 = 1 - alphaOverA
|
| 142 |
+
|
| 143 |
+
# output coefs
|
| 144 |
+
#b = np.array([b0/a0, b1/a0, b2/a0])
|
| 145 |
+
#a = np.array([a0/a0, a1/a0, a2/a0])
|
| 146 |
+
|
| 147 |
+
return np.array([[b0/a0, b1/a0, b2/a0, 1.0, a1/a0, a2/a0]])
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def params2sos(G, Fc, Q, fs):
|
| 152 |
+
"""Convert 5 band EQ paramaters to 2nd order sections.
|
| 153 |
+
Takes a vector with shape (13,) of denormalized EQ parameters
|
| 154 |
+
and calculates filter coefficients for each of the 5 filters.
|
| 155 |
+
These coefficients (2nd order sections) are then stored into a
|
| 156 |
+
single (5,6) matrix. This matrix can be fed to `scipy.signal.sosfreqz()`
|
| 157 |
+
in order to determine the frequency response of the cascasd of
|
| 158 |
+
all five biquad filters.
|
| 159 |
+
Args:
|
| 160 |
+
x (float): Gain factor in dB.
|
| 161 |
+
fs (float): Sampling frequency in Hz.
|
| 162 |
+
Returns:
|
| 163 |
+
ndarray: filter coefficients for 5 band EQ stored in (5,6) matrix.
|
| 164 |
+
[[b1_0, b1_1, b1_2, a1_0, a1_1, a1_2], # lowshelf coefficients
|
| 165 |
+
[b2_0, b2_1, b2_2, a2_0, a2_1, a2_2], # first band coefficients
|
| 166 |
+
[b3_0, b3_1, b3_2, a3_0, a3_1, a3_2], # second band coefficients
|
| 167 |
+
[b4_0, b4_1, b4_2, a4_0, a4_1, a4_2], # third band coefficients
|
| 168 |
+
[b5_0, b5_1, b5_2, a5_0, a5_1, a5_2]] # highshelf coefficients
|
| 169 |
+
"""
|
| 170 |
+
# generate filter coefficients from eq params
|
| 171 |
+
c0 = make_lowshelf(G[0], Fc[0], Q[0], fs=fs)
|
| 172 |
+
c1 = make_peaking (G[1], Fc[1], Q[1], fs=fs)
|
| 173 |
+
c2 = make_peaking (G[2], Fc[2], Q[2], fs=fs)
|
| 174 |
+
c3 = make_peaking (G[3], Fc[3], Q[3], fs=fs)
|
| 175 |
+
c4 = make_peaking (G[4], Fc[4], Q[4], fs=fs)
|
| 176 |
+
c5 = make_peaking (G[5], Fc[5], Q[5], fs=fs)
|
| 177 |
+
c6 = make_peaking (G[6], Fc[6], Q[6], fs=fs)
|
| 178 |
+
c7 = make_peaking (G[7], Fc[7], Q[7], fs=fs)
|
| 179 |
+
c8 = make_peaking (G[8], Fc[8], Q[8], fs=fs)
|
| 180 |
+
c9 = make_highself(G[9], Fc[9], Q[9], fs=fs)
|
| 181 |
+
|
| 182 |
+
# stuff coefficients into second order sections structure
|
| 183 |
+
sos = np.concatenate([c0,c1,c2,c3,c4,c5,c6,c7,c8,c9], axis=0)
|
| 184 |
+
|
| 185 |
+
return sos
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
import parselmouth
|
| 189 |
+
def change_gender(x, fs, lo, hi, ratio_fs, ratio_ps, ratio_pr):
|
| 190 |
+
s = parselmouth.Sound(x, sampling_frequency=fs)
|
| 191 |
+
f0 = s.to_pitch_ac(pitch_floor=lo, pitch_ceiling=hi, time_step=0.8/lo)
|
| 192 |
+
f0_np = f0.selected_array['frequency']
|
| 193 |
+
f0_med = np.median(f0_np[f0_np!=0]).item()
|
| 194 |
+
ss = parselmouth.praat.call([s, f0], "Change gender", ratio_fs, f0_med*ratio_ps, ratio_pr, 1.0)
|
| 195 |
+
return ss.values.squeeze(0)
|
| 196 |
+
|
| 197 |
+
def change_gender_f0(x, fs, lo, hi, ratio_fs, new_f0_med, ratio_pr):
|
| 198 |
+
s = parselmouth.Sound(x, sampling_frequency=fs)
|
| 199 |
+
ss = parselmouth.praat.call(s, "Change gender", lo, hi, ratio_fs, new_f0_med, ratio_pr, 1.0)
|
| 200 |
+
return ss.values.squeeze(0)
|
| 201 |
+
|
| 202 |
+
def parse_range(range_str):
|
| 203 |
+
# (a,b)|(c,d)|p
|
| 204 |
+
|
| 205 |
+
terms = range_str.split("|")
|
| 206 |
+
assert len(terms)==3, range_str
|
| 207 |
+
prob = float(terms[-1])
|
| 208 |
+
ranges = [eval(terms[0]), eval(terms[1])]
|
| 209 |
+
return ranges, prob
|
| 210 |
+
|
| 211 |
+
class FormantF0Perturb:
|
| 212 |
+
def __init__(self, male_formant_range, female_formant_range, female_f0_range):
|
| 213 |
+
# male_formant_range: (0.9,0.95)|(1.05,1.3)|0.25
|
| 214 |
+
# first range, second range, prob of first range
|
| 215 |
+
|
| 216 |
+
self.Qmin = 2
|
| 217 |
+
self.Qmax = 5
|
| 218 |
+
self.male_formant_range, self.male_formant_prob = parse_range(male_formant_range)
|
| 219 |
+
self.female_formant_range, self.female_formant_prob = parse_range(female_formant_range)
|
| 220 |
+
self.female_f0_range, self.female_f0_prob = parse_range(female_f0_range)
|
| 221 |
+
|
| 222 |
+
def get_perturb_param(self, gender):
|
| 223 |
+
# formant shift and pitch shift.
|
| 224 |
+
if gender == "M":
|
| 225 |
+
if np.random.random()<=self.male_formant_prob:
|
| 226 |
+
ratio_fs = np.random.uniform(self.male_formant_range[0][0], self.male_formant_range[0][1])
|
| 227 |
+
else:
|
| 228 |
+
ratio_fs = np.random.uniform(self.male_formant_range[1][0], self.male_formant_range[1][1])
|
| 229 |
+
return 75, 250, ratio_fs, 1.0
|
| 230 |
+
else:
|
| 231 |
+
if np.random.random()<=self.female_formant_prob:
|
| 232 |
+
ratio_fs = np.random.uniform(self.female_formant_range[0][0], self.female_formant_range[0][1])
|
| 233 |
+
else:
|
| 234 |
+
ratio_fs = np.random.uniform(self.female_formant_range[1][0], self.female_formant_range[1][1])
|
| 235 |
+
if np.random.random()<=self.female_f0_prob:
|
| 236 |
+
ratio_ps = np.random.uniform(self.female_f0_range[0][0], self.female_f0_range[0][1])
|
| 237 |
+
else:
|
| 238 |
+
ratio_ps = np.random.uniform(self.female_f0_range[1][0], self.female_f0_range[1][1])
|
| 239 |
+
return 100, 400, ratio_fs, ratio_ps
|
| 240 |
+
|
| 241 |
+
def random_eq(self, wav, sr):
|
| 242 |
+
Fc = np.exp(np.linspace(np.log(60), np.log(7600), 10))
|
| 243 |
+
# rng = np.random.default_rng()
|
| 244 |
+
z = np.random.uniform(0, 1, size=(10,))
|
| 245 |
+
Q = self.Qmin * (self.Qmax / self.Qmin)**z
|
| 246 |
+
G = np.random.uniform(-12, 12, size=(10,))
|
| 247 |
+
sos = params2sos(G, Fc, Q, sr)
|
| 248 |
+
wav = sosfilt(sos, wav)
|
| 249 |
+
return wav
|
| 250 |
+
|
| 251 |
+
def __call__(self, wav, sr, gender):
|
| 252 |
+
# gender: "F" or "M"
|
| 253 |
+
|
| 254 |
+
lo, hi, ratio_fs, ratio_ps = self.get_perturb_param(gender)
|
| 255 |
+
# fs: formant shift. This controls timbre (higher -> more female). ps: pitch shift. This controls absolute pitch level
|
| 256 |
+
# We fix the pitch range parameter to 1. So that we don't change the pitch variations
|
| 257 |
+
ratio_pr = 1
|
| 258 |
+
|
| 259 |
+
ss = change_gender(wav, sr, lo, hi, ratio_fs, ratio_ps, ratio_pr)
|
| 260 |
+
|
| 261 |
+
return ss
|
| 262 |
+
|
| 263 |
+
class SpeedPerturb:
|
| 264 |
+
def __init__(self, male_range, female_range):
|
| 265 |
+
self.male_range, self.male_prob = parse_range(male_range)
|
| 266 |
+
self.female_range, self.female_prob = parse_range(female_range)
|
| 267 |
+
|
| 268 |
+
def __call__(self, wav, sr, gender):
|
| 269 |
+
|
| 270 |
+
if gender == "F":
|
| 271 |
+
ranges, prob = self.male_range, self.male_prob
|
| 272 |
+
else:
|
| 273 |
+
ranges, prob = self.female_range, self.female_prob
|
| 274 |
+
|
| 275 |
+
if np.random.random()<=prob:
|
| 276 |
+
speed_alpha = np.random.uniform(ranges[0][0], ranges[0][1])
|
| 277 |
+
else:
|
| 278 |
+
speed_alpha = np.random.uniform(ranges[1][0], ranges[1][1])
|
| 279 |
+
|
| 280 |
+
self.transformer = sox.Transformer()
|
| 281 |
+
self.transformer.set_globals(verbosity=1) # disable warnings.
|
| 282 |
+
self.transformer.speed(factor=speed_alpha)
|
| 283 |
+
self.transformer.tempo(factor=1/speed_alpha) # recover the original length. Only affects timbre and pitch.
|
| 284 |
+
wav_perturb = self.transformer.build_array(input_array=wav, sample_rate_in=sr)
|
| 285 |
+
return wav_perturb
|
| 286 |
+
|
| 287 |
+
class IdentityPerturb:
|
| 288 |
+
def __init__(self, ):
|
| 289 |
+
pass
|
| 290 |
+
def __call__(self,wav, sr, gender):
|
| 291 |
+
return wav
|
| 292 |
+
|
| 293 |
+
class Perturbs:
|
| 294 |
+
def __init__(self, formant_f0_perturb_params, speed_perturb_params, speed_perturb_prob):
|
| 295 |
+
self.speed_perturb_prob = speed_perturb_prob
|
| 296 |
+
if speed_perturb_prob!=0.0:
|
| 297 |
+
self.speed_perturber = SpeedPerturb(**speed_perturb_params)
|
| 298 |
+
else:
|
| 299 |
+
self.speed_perturber = IdentityPerturb()
|
| 300 |
+
if speed_perturb_prob!=1.0:
|
| 301 |
+
self.formant_perturber = FormantF0Perturb(**formant_f0_perturb_params)
|
| 302 |
+
else:
|
| 303 |
+
self.formant_perturber = IdentityPerturb()
|
| 304 |
+
def __call__(self, wav, sr, gender):
|
| 305 |
+
if np.random.random()<=self.speed_perturb_prob:
|
| 306 |
+
return self.speed_perturber(wav,sr,gender)
|
| 307 |
+
else:
|
| 308 |
+
return self.formant_perturber(wav,sr,gender)
|
lscodec/datasets/scp_dataset.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Copyright 2019 Tomoki Hayashi
|
| 4 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 5 |
+
|
| 6 |
+
# Portions of this code are a modification of the ParallelWaveGAN project.
|
| 7 |
+
# The original source code can be found at:
|
| 8 |
+
# https://github.com/kan-bayashi/ParallelWaveGAN/blob/master/parallel_wavegan/datasets/scp_dataset.py
|
| 9 |
+
#
|
| 10 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 11 |
+
# Licensed under the MIT license.
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
"""Dataset modules based on kaldi-style scp files."""
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import logging
|
| 18 |
+
import random
|
| 19 |
+
import copy
|
| 20 |
+
import sox
|
| 21 |
+
from multiprocessing import Manager
|
| 22 |
+
|
| 23 |
+
import kaldiio
|
| 24 |
+
import librosa
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
from torch.utils.data import Dataset
|
| 28 |
+
from tqdm import tqdm
|
| 29 |
+
from lscodec.utils import HDF5ScpLoader
|
| 30 |
+
from lscodec.utils import NpyScpLoader
|
| 31 |
+
from lscodec.datasets.perturbs import Perturbs
|
| 32 |
+
|
| 33 |
+
logging.getLogger("sox").setLevel(logging.ERROR)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _get_feats_scp_loader(feats_scp):
|
| 37 |
+
# read the first line of feats.scp file
|
| 38 |
+
with open(feats_scp) as f:
|
| 39 |
+
key, value = f.readlines()[0].replace("\n", "").split()
|
| 40 |
+
|
| 41 |
+
# check scp type
|
| 42 |
+
if ":" in value:
|
| 43 |
+
value_1, value_2 = value.split(":")
|
| 44 |
+
if value_1.endswith(".ark"):
|
| 45 |
+
# kaldi-ark case: utt_id_1 /path/to/utt_id_1.ark:index
|
| 46 |
+
return kaldiio.load_scp(feats_scp)
|
| 47 |
+
elif value_1.endswith(".h5"):
|
| 48 |
+
# hdf5 case with path in hdf5: utt_id_1 /path/to/utt_id_1.h5:feats
|
| 49 |
+
return HDF5ScpLoader(feats_scp)
|
| 50 |
+
else:
|
| 51 |
+
raise ValueError("Not supported feats.scp type.")
|
| 52 |
+
else:
|
| 53 |
+
if value.endswith(".h5"):
|
| 54 |
+
# hdf5 case without path in hdf5: utt_id_1 /path/to/utt_id_1.h5
|
| 55 |
+
return HDF5ScpLoader(feats_scp)
|
| 56 |
+
elif value.endswith(".npy"):
|
| 57 |
+
# npy case: utt_id_1 /path/to/utt_id_1.npy
|
| 58 |
+
return NpyScpLoader(feats_scp)
|
| 59 |
+
else:
|
| 60 |
+
raise ValueError("Not supported feats.scp type.")
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class AudioMelSCPDataset(Dataset):
|
| 64 |
+
"""PyTorch compatible audio and feat dataset based on kaldi-stype scp files."""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
wav_scp,
|
| 69 |
+
prompt_scp,
|
| 70 |
+
target_scp,
|
| 71 |
+
aux_scp,
|
| 72 |
+
idx_scp,
|
| 73 |
+
utt2num_frames=None,
|
| 74 |
+
segments=None,
|
| 75 |
+
batch_frames=None,
|
| 76 |
+
batch_size=None,
|
| 77 |
+
min_num_frames=None,
|
| 78 |
+
max_num_frames=None,
|
| 79 |
+
return_utt_id=False,
|
| 80 |
+
return_sampling_rate=False,
|
| 81 |
+
allow_cache=False,
|
| 82 |
+
length_tolerance=2,
|
| 83 |
+
prompt_min_length=500, # in terms of frames
|
| 84 |
+
extractor_fs=16000,
|
| 85 |
+
# speed_alpha_range=(0.8, 1.2),
|
| 86 |
+
perturber=None,
|
| 87 |
+
utt2spk=None,
|
| 88 |
+
spk2gender=None
|
| 89 |
+
):
|
| 90 |
+
"""Initialize dataset.
|
| 91 |
+
|
| 92 |
+
Args:
|
| 93 |
+
wav_scp (str): Kaldi-style wav.scp file.
|
| 94 |
+
prompt_scp (str): Kaldi-style fests.scp file.
|
| 95 |
+
aux_scp (str): Kaldi-style fests.scp file.
|
| 96 |
+
segments (str): Kaldi-style segments file.
|
| 97 |
+
min_num_frames (int): Threshold to remove short feature files.
|
| 98 |
+
max_num_frames (int): Threshold to remove long feature files.
|
| 99 |
+
return_utt_id (bool): Whether to return utterance id.
|
| 100 |
+
return_sampling_rate (bool): Whether to return sampling rate.
|
| 101 |
+
allow_cache (bool): Whether to allow cache of the loaded files.
|
| 102 |
+
|
| 103 |
+
"""
|
| 104 |
+
# load scp as lazy dict
|
| 105 |
+
self.audio_loader = kaldiio.load_scp(wav_scp, segments=segments)
|
| 106 |
+
self.prompt_loader = _get_feats_scp_loader(prompt_scp)
|
| 107 |
+
# with open(prompt_scp, 'r') as fr:
|
| 108 |
+
# self.prompt_loader = dict()
|
| 109 |
+
# for line in fr.readlines():
|
| 110 |
+
# terms = line.strip().split()
|
| 111 |
+
# utt, path = terms
|
| 112 |
+
# self.prompt_loader[utt] = path
|
| 113 |
+
|
| 114 |
+
self.target_loader = _get_feats_scp_loader(target_scp)
|
| 115 |
+
self.aux_loader = _get_feats_scp_loader(aux_scp)
|
| 116 |
+
self.idx_loader = _get_feats_scp_loader(idx_scp)
|
| 117 |
+
|
| 118 |
+
self.utt_ids = list(self.prompt_loader.keys())
|
| 119 |
+
self.return_utt_id = return_utt_id
|
| 120 |
+
self.return_sampling_rate = return_sampling_rate
|
| 121 |
+
self.allow_cache = allow_cache
|
| 122 |
+
self.extractor_fs = extractor_fs
|
| 123 |
+
|
| 124 |
+
utt2num_frames_loader = None
|
| 125 |
+
if utt2num_frames is not None:
|
| 126 |
+
with open(utt2num_frames, 'r') as f:
|
| 127 |
+
utt2num_frames_loader = dict([(x.split()[0], int(x.split()[1])) for x in f.readlines()])
|
| 128 |
+
else:
|
| 129 |
+
utt2num_frames_loader = dict([(k, mel.shape[0]) for k, mel in self.prompt_loader.items()])
|
| 130 |
+
self.utt2num_frames_loader = utt2num_frames_loader
|
| 131 |
+
# filter by threshold
|
| 132 |
+
if (min_num_frames or max_num_frames) is not None:
|
| 133 |
+
mel_lengths = [utt2num_frames_loader[key] for key in self.utt_ids]
|
| 134 |
+
idxs = [
|
| 135 |
+
idx
|
| 136 |
+
for idx in range(len(self.utt_ids))
|
| 137 |
+
if (min_num_frames and mel_lengths[idx] >= min_num_frames) and (max_num_frames and mel_lengths[idx] <= max_num_frames)
|
| 138 |
+
]
|
| 139 |
+
if len(self.utt_ids) != len(idxs):
|
| 140 |
+
logging.warning(
|
| 141 |
+
f"Some files are filtered by mel length threshold "
|
| 142 |
+
f"({len(self.utt_ids)} -> {len(idxs)})."
|
| 143 |
+
)
|
| 144 |
+
self.utt_ids = [self.utt_ids[idx] for idx in idxs]
|
| 145 |
+
|
| 146 |
+
# batchify
|
| 147 |
+
if batch_frames is not None:
|
| 148 |
+
self.batches = self.batchify(utt2num_frames_loader, batch_frames=batch_frames)
|
| 149 |
+
elif batch_size is not None:
|
| 150 |
+
self.batches = self.batchify(utt2num_frames_loader, batch_size=batch_size)
|
| 151 |
+
else:
|
| 152 |
+
self.batches = [[utt_id] for utt_id in self.utt_ids]
|
| 153 |
+
|
| 154 |
+
if allow_cache:
|
| 155 |
+
# NOTE(kan-bayashi): Manager is need to share memory in dataloader with num_workers > 0
|
| 156 |
+
self.manager = Manager()
|
| 157 |
+
self.caches = self.manager.dict()
|
| 158 |
+
self.length_tolerance = length_tolerance
|
| 159 |
+
self.prompt_min_length = prompt_min_length
|
| 160 |
+
|
| 161 |
+
# assert len(speed_alpha_range)==2, speed_alpha_range
|
| 162 |
+
# self.alpha_range = speed_alpha_range
|
| 163 |
+
self.perturber = perturber
|
| 164 |
+
|
| 165 |
+
if utt2spk is not None:
|
| 166 |
+
self.utt2spk = dict()
|
| 167 |
+
with open(utt2spk, 'r') as f:
|
| 168 |
+
for line in f.readlines():
|
| 169 |
+
terms = line.strip().split()
|
| 170 |
+
self.utt2spk[terms[0]] = terms[1]
|
| 171 |
+
assert spk2gender is not None
|
| 172 |
+
self.spk2gender = dict()
|
| 173 |
+
with open(spk2gender, 'r') as f:
|
| 174 |
+
for line in f.readlines():
|
| 175 |
+
terms = line.strip().split()
|
| 176 |
+
assert terms[1] in {"F", "M"}
|
| 177 |
+
self.spk2gender[terms[0]] = terms[1]
|
| 178 |
+
else:
|
| 179 |
+
self.utt2spk = None
|
| 180 |
+
|
| 181 |
+
def batchify(self, utt2num_frames_loader, batch_frames=None, batch_size=None, min_batch_size=1, drop_last=True):
|
| 182 |
+
|
| 183 |
+
assert batch_size is None or batch_size > min_batch_size
|
| 184 |
+
|
| 185 |
+
batches = []
|
| 186 |
+
batch = []
|
| 187 |
+
accum_num_frames = 0
|
| 188 |
+
utt_ids_set = set(self.utt_ids)
|
| 189 |
+
for utt_id, mel_length in tqdm(sorted(list(utt2num_frames_loader.items()), key=lambda x: x[1], reverse=True)):
|
| 190 |
+
if utt_id not in utt_ids_set:
|
| 191 |
+
continue
|
| 192 |
+
if (batch_frames is not None and accum_num_frames + mel_length > batch_frames and len(batch) > min_batch_size) or (batch_size is not None and len(batch) == batch_size):
|
| 193 |
+
batches.append(batch)
|
| 194 |
+
batch = []
|
| 195 |
+
accum_num_frames = 0
|
| 196 |
+
batch.append(utt_id)
|
| 197 |
+
accum_num_frames += mel_length
|
| 198 |
+
if len(batch) > min_batch_size and not drop_last:
|
| 199 |
+
batches.append(batch)
|
| 200 |
+
return batches
|
| 201 |
+
|
| 202 |
+
def __getitem__(self, idx):
|
| 203 |
+
"""Get specified idx items.
|
| 204 |
+
|
| 205 |
+
Args:
|
| 206 |
+
idx (int): Index of the item.
|
| 207 |
+
|
| 208 |
+
Returns:
|
| 209 |
+
str: Utterance id (only in return_utt_id = True).
|
| 210 |
+
ndarray or tuple: Audio signal (T,) or (w/ sampling rate if return_sampling_rate = True).
|
| 211 |
+
ndarray: Feature (T', C).
|
| 212 |
+
|
| 213 |
+
"""
|
| 214 |
+
batch = self.batches[idx]
|
| 215 |
+
batch_items = []
|
| 216 |
+
|
| 217 |
+
for utt_id in batch:
|
| 218 |
+
if self.allow_cache and self.caches.get(utt_id) is not None:
|
| 219 |
+
items = self.caches[utt_id]
|
| 220 |
+
else:
|
| 221 |
+
fs, audio = self.audio_loader[utt_id]
|
| 222 |
+
prompt = self.prompt_loader[utt_id]
|
| 223 |
+
# prompt = torch.load(prompt_path).numpy()
|
| 224 |
+
target = self.target_loader[utt_id]
|
| 225 |
+
ssl_idx = self.idx_loader[utt_id]
|
| 226 |
+
aux = self.aux_loader[utt_id]
|
| 227 |
+
|
| 228 |
+
min_len = min(len(prompt), len(aux), len(target), len(ssl_idx))
|
| 229 |
+
assert ((abs(len(prompt) - min_len) <= self.length_tolerance) and
|
| 230 |
+
(abs(len(target) - min_len) <= self.length_tolerance) and
|
| 231 |
+
(abs(len(ssl_idx) - min_len) <= self.length_tolerance) and
|
| 232 |
+
(abs(len(aux) - min_len) <= self.length_tolerance)), \
|
| 233 |
+
f"Audio feature lengths difference exceeds length tolerance for {utt_id}"
|
| 234 |
+
prompt, aux, target, ssl_idx = prompt[:min_len], aux[:min_len], target[:min_len], ssl_idx[:min_len]
|
| 235 |
+
|
| 236 |
+
if self.utt2spk is not None:
|
| 237 |
+
spk=self.utt2spk[utt_id]
|
| 238 |
+
gender=self.spk2gender[spk]
|
| 239 |
+
else:
|
| 240 |
+
gender = "M" # Default to male, so that not use pitch shift
|
| 241 |
+
|
| 242 |
+
# normalize audio signal to be [-1, 1]
|
| 243 |
+
audio = audio.astype(np.float32)
|
| 244 |
+
audio /= 1 << (16 - 1) # assume that wav is PCM 16 bit
|
| 245 |
+
|
| 246 |
+
if fs != self.extractor_fs:
|
| 247 |
+
audio_for_extractor = librosa.resample(audio, orig_sr=fs, target_sr=self.extractor_fs)
|
| 248 |
+
else:
|
| 249 |
+
audio_for_extractor = audio
|
| 250 |
+
|
| 251 |
+
# # perturb
|
| 252 |
+
# if self.alpha_range[0] == self.alpha_range[1] == 1.0:
|
| 253 |
+
# pass
|
| 254 |
+
# else:
|
| 255 |
+
# speed_alpha = random.random() * (self.alpha_range[1] - self.alpha_range[0]) + self.alpha_range[0]
|
| 256 |
+
# self.transformer = sox.Transformer()
|
| 257 |
+
# self.transformer.set_globals(verbosity=1) # disable warnings.
|
| 258 |
+
# self.transformer.speed(factor=speed_alpha)
|
| 259 |
+
# self.transformer.tempo(factor=1/speed_alpha) # recover the original length. Only affects timbre and pitch.
|
| 260 |
+
# audio_for_extractor = self.transformer.build_array(input_array=audio_for_extractor, sample_rate_in=self.extractor_fs)
|
| 261 |
+
audio_for_extractor = self.perturber(audio_for_extractor, self.extractor_fs, gender)
|
| 262 |
+
|
| 263 |
+
if self.return_utt_id:
|
| 264 |
+
items = utt_id, audio_for_extractor, prompt, aux, target, ssl_idx
|
| 265 |
+
else:
|
| 266 |
+
items = audio_for_extractor, prompt, aux, target, ssl_idx
|
| 267 |
+
|
| 268 |
+
if self.allow_cache:
|
| 269 |
+
self.caches[utt_id] = items
|
| 270 |
+
|
| 271 |
+
batch_items.append(items)
|
| 272 |
+
|
| 273 |
+
return batch_items
|
| 274 |
+
|
| 275 |
+
def __len__(self):
|
| 276 |
+
"""Return dataset length.
|
| 277 |
+
Returns:
|
| 278 |
+
int: The length of dataset.
|
| 279 |
+
"""
|
| 280 |
+
return len(self.batches)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class MelSCPDataset(Dataset):
|
| 284 |
+
"""PyTorch compatible feat dataset based on kaldi-stype scp files."""
|
| 285 |
+
|
| 286 |
+
def __init__(
|
| 287 |
+
self,
|
| 288 |
+
vqidx_scp,
|
| 289 |
+
prompt_scp,
|
| 290 |
+
utt2num_frames=None,
|
| 291 |
+
min_num_frames=None,
|
| 292 |
+
max_num_frames=None,
|
| 293 |
+
return_utt_id=False,
|
| 294 |
+
allow_cache=False,
|
| 295 |
+
):
|
| 296 |
+
"""Initialize dataset.
|
| 297 |
+
|
| 298 |
+
Args:
|
| 299 |
+
vqidx_scp (str): Kaldi-style fests.scp file.
|
| 300 |
+
prompt_scp (str): Kaldi-style scp file. In this file, every utt is associated with its prompt's mel-spectrogram.
|
| 301 |
+
min_num_frames (int): Threshold to remove short feature files.
|
| 302 |
+
max_num_frames (int): Threshold to remove long feature files.
|
| 303 |
+
return_utt_id (bool): Whether to return utterance id.
|
| 304 |
+
allow_cache (bool): Whether to allow cache of the loaded files.
|
| 305 |
+
"""
|
| 306 |
+
# load scp as lazy dict
|
| 307 |
+
vqidx_loader = _get_feats_scp_loader(vqidx_scp)
|
| 308 |
+
prompt_loader = _get_feats_scp_loader(prompt_scp)
|
| 309 |
+
vqidx_keys = list(set(prompt_loader.keys()) & set(vqidx_loader.keys()))
|
| 310 |
+
|
| 311 |
+
utt2num_frames_loader = None
|
| 312 |
+
if utt2num_frames is not None:
|
| 313 |
+
with open(utt2num_frames, 'r') as f:
|
| 314 |
+
utt2num_frames_loader = dict([(x.split()[0], int(x.split()[1])) for x in f.readlines()])
|
| 315 |
+
else:
|
| 316 |
+
utt2num_frames_loader = dict([(k, mel.shape[0]) for k, mel in vqidx_loader.items()])
|
| 317 |
+
|
| 318 |
+
# filter by threshold
|
| 319 |
+
if (min_num_frames or max_num_frames) is not None:
|
| 320 |
+
mel_lengths = [utt2num_frames_loader[key] for key in vqidx_keys]
|
| 321 |
+
idxs = [
|
| 322 |
+
idx
|
| 323 |
+
for idx in range(len(vqidx_keys))
|
| 324 |
+
if (min_num_frames and mel_lengths[idx] > min_num_frames) and (max_num_frames and mel_lengths[idx] < max_num_frames)
|
| 325 |
+
]
|
| 326 |
+
if len(vqidx_keys) != len(idxs):
|
| 327 |
+
logging.warning(
|
| 328 |
+
f"Some files are filtered by mel length threshold "
|
| 329 |
+
f"({len(vqidx_keys)} -> {len(idxs)})."
|
| 330 |
+
)
|
| 331 |
+
vqidx_keys = [vqidx_keys[idx] for idx in idxs]
|
| 332 |
+
|
| 333 |
+
self.vqidx_loader = vqidx_loader
|
| 334 |
+
self.prompt_loader = prompt_loader
|
| 335 |
+
self.utt_ids = vqidx_keys
|
| 336 |
+
self.return_utt_id = return_utt_id
|
| 337 |
+
self.allow_cache = allow_cache
|
| 338 |
+
|
| 339 |
+
if allow_cache:
|
| 340 |
+
# NOTE(kan-bayashi): Manager is need to share memory in dataloader with num_workers > 0
|
| 341 |
+
self.manager = Manager()
|
| 342 |
+
self.caches = self.manager.list()
|
| 343 |
+
self.caches += [() for _ in range(len(self.utt_ids))]
|
| 344 |
+
|
| 345 |
+
def __getitem__(self, idx):
|
| 346 |
+
"""Get specified idx items.
|
| 347 |
+
|
| 348 |
+
Args:
|
| 349 |
+
idx (int): Index of the item.
|
| 350 |
+
|
| 351 |
+
Returns:
|
| 352 |
+
str: Utterance id (only in return_utt_id = True).
|
| 353 |
+
ndarray: Feature (T', C).
|
| 354 |
+
|
| 355 |
+
"""
|
| 356 |
+
if self.allow_cache and len(self.caches[idx]) != 0:
|
| 357 |
+
return self.caches[idx]
|
| 358 |
+
|
| 359 |
+
utt_id = self.utt_ids[idx]
|
| 360 |
+
vqidx = self.vqidx_loader[utt_id].astype(int)
|
| 361 |
+
|
| 362 |
+
prompt = self.prompt_loader[utt_id].copy()
|
| 363 |
+
|
| 364 |
+
if self.return_utt_id:
|
| 365 |
+
items = utt_id, vqidx, prompt
|
| 366 |
+
else:
|
| 367 |
+
items = vqidx, prompt
|
| 368 |
+
|
| 369 |
+
if self.allow_cache:
|
| 370 |
+
self.caches[idx] = items
|
| 371 |
+
|
| 372 |
+
return items
|
| 373 |
+
|
| 374 |
+
def __len__(self):
|
| 375 |
+
"""Return dataset length.
|
| 376 |
+
|
| 377 |
+
Returns:
|
| 378 |
+
int: The length of dataset.
|
| 379 |
+
|
| 380 |
+
"""
|
| 381 |
+
return len(self.utt_ids)
|
lscodec/distributed/__init__.py
ADDED
|
File without changes
|
lscodec/distributed/distrib.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""Torch distributed utilities."""
|
| 8 |
+
|
| 9 |
+
import typing as tp
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def rank():
|
| 15 |
+
if torch.distributed.is_initialized():
|
| 16 |
+
return torch.distributed.get_rank()
|
| 17 |
+
else:
|
| 18 |
+
return 0
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def world_size():
|
| 22 |
+
if torch.distributed.is_initialized():
|
| 23 |
+
return torch.distributed.get_world_size()
|
| 24 |
+
else:
|
| 25 |
+
return 1
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def is_distributed():
|
| 29 |
+
return world_size() > 1
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def all_reduce(tensor: torch.Tensor, op=torch.distributed.ReduceOp.SUM):
|
| 33 |
+
if is_distributed():
|
| 34 |
+
return torch.distributed.all_reduce(tensor, op)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _is_complex_or_float(tensor):
|
| 38 |
+
return torch.is_floating_point(tensor) or torch.is_complex(tensor)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _check_number_of_params(params: tp.List[torch.Tensor]):
|
| 42 |
+
# utility function to check that the number of params in all workers is the same,
|
| 43 |
+
# and thus avoid a deadlock with distributed all reduce.
|
| 44 |
+
if not is_distributed() or not params:
|
| 45 |
+
return
|
| 46 |
+
tensor = torch.tensor([len(params)], device=params[0].device, dtype=torch.long)
|
| 47 |
+
all_reduce(tensor)
|
| 48 |
+
if tensor.item() != len(params) * world_size():
|
| 49 |
+
# If not all the workers have the same number, for at least one of them,
|
| 50 |
+
# this inequality will be verified.
|
| 51 |
+
raise RuntimeError(f"Mismatch in number of params: ours is {len(params)}, "
|
| 52 |
+
"at least one worker has a different one.")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def broadcast_tensors(tensors: tp.Iterable[torch.Tensor], src: int = 0):
|
| 56 |
+
"""Broadcast the tensors from the given parameters to all workers.
|
| 57 |
+
This can be used to ensure that all workers have the same model to start with.
|
| 58 |
+
"""
|
| 59 |
+
if not is_distributed():
|
| 60 |
+
return
|
| 61 |
+
tensors = [tensor for tensor in tensors if _is_complex_or_float(tensor)]
|
| 62 |
+
_check_number_of_params(tensors)
|
| 63 |
+
handles = []
|
| 64 |
+
for tensor in tensors:
|
| 65 |
+
handle = torch.distributed.broadcast(tensor.data, src=src, async_op=True)
|
| 66 |
+
handles.append(handle)
|
| 67 |
+
for handle in handles:
|
| 68 |
+
handle.wait()
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def sync_buffer(buffers, average=True):
|
| 72 |
+
"""
|
| 73 |
+
Sync grad for buffers. If average is False, broadcast instead of averaging.
|
| 74 |
+
"""
|
| 75 |
+
if not is_distributed():
|
| 76 |
+
return
|
| 77 |
+
handles = []
|
| 78 |
+
for buffer in buffers:
|
| 79 |
+
if torch.is_floating_point(buffer.data):
|
| 80 |
+
if average:
|
| 81 |
+
handle = torch.distributed.all_reduce(
|
| 82 |
+
buffer.data, op=torch.distributed.ReduceOp.SUM, async_op=True)
|
| 83 |
+
else:
|
| 84 |
+
handle = torch.distributed.broadcast(
|
| 85 |
+
buffer.data, src=0, async_op=True)
|
| 86 |
+
handles.append((buffer, handle))
|
| 87 |
+
for buffer, handle in handles:
|
| 88 |
+
handle.wait()
|
| 89 |
+
if average:
|
| 90 |
+
buffer.data /= world_size
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def sync_grad(params):
|
| 94 |
+
"""
|
| 95 |
+
Simpler alternative to DistributedDataParallel, that doesn't rely
|
| 96 |
+
on any black magic. For simple models it can also be as fast.
|
| 97 |
+
Just call this on your model parameters after the call to backward!
|
| 98 |
+
"""
|
| 99 |
+
if not is_distributed():
|
| 100 |
+
return
|
| 101 |
+
handles = []
|
| 102 |
+
for p in params:
|
| 103 |
+
if p.grad is not None:
|
| 104 |
+
handle = torch.distributed.all_reduce(
|
| 105 |
+
p.grad.data, op=torch.distributed.ReduceOp.SUM, async_op=True)
|
| 106 |
+
handles.append((p, handle))
|
| 107 |
+
for p, handle in handles:
|
| 108 |
+
handle.wait()
|
| 109 |
+
p.grad.data /= world_size()
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def average_metrics(metrics: tp.Dict[str, float], count=1.):
|
| 113 |
+
"""Average a dictionary of metrics across all workers, using the optional
|
| 114 |
+
`count` as unormalized weight.
|
| 115 |
+
"""
|
| 116 |
+
if not is_distributed():
|
| 117 |
+
return metrics
|
| 118 |
+
keys, values = zip(*metrics.items())
|
| 119 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 120 |
+
tensor = torch.tensor(list(values) + [1], device=device, dtype=torch.float32)
|
| 121 |
+
tensor *= count
|
| 122 |
+
all_reduce(tensor)
|
| 123 |
+
averaged = (tensor[:-1] / tensor[-1]).cpu().tolist()
|
| 124 |
+
return dict(zip(keys, averaged))
|
lscodec/distributed/launch.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
"""Distributed process launcher.
|
| 5 |
+
|
| 6 |
+
This code is modified from https://github.com/pytorch/pytorch/blob/v1.3.0/torch/distributed/launch.py.
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
import os
|
| 10 |
+
import subprocess
|
| 11 |
+
import sys
|
| 12 |
+
|
| 13 |
+
from argparse import ArgumentParser
|
| 14 |
+
from argparse import REMAINDER
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def parse_args():
|
| 18 |
+
"""Parse arguments."""
|
| 19 |
+
parser = ArgumentParser(
|
| 20 |
+
description="PyTorch distributed training launch "
|
| 21 |
+
"helper utilty that will spawn up "
|
| 22 |
+
"multiple distributed processes"
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# Optional arguments for the launch helper
|
| 26 |
+
parser.add_argument(
|
| 27 |
+
"--nnodes",
|
| 28 |
+
type=int,
|
| 29 |
+
default=1,
|
| 30 |
+
help="The number of nodes to use for distributed " "training",
|
| 31 |
+
)
|
| 32 |
+
parser.add_argument(
|
| 33 |
+
"--node_rank",
|
| 34 |
+
type=int,
|
| 35 |
+
default=0,
|
| 36 |
+
help="The rank of the node for multi-node distributed " "training",
|
| 37 |
+
)
|
| 38 |
+
parser.add_argument(
|
| 39 |
+
"--nproc_per_node",
|
| 40 |
+
type=int,
|
| 41 |
+
default=1,
|
| 42 |
+
help="The number of processes to launch on each node, "
|
| 43 |
+
"for GPU training, this is recommended to be set "
|
| 44 |
+
"to the number of GPUs in your system so that "
|
| 45 |
+
"each process can be bound to a single GPU.",
|
| 46 |
+
)
|
| 47 |
+
parser.add_argument(
|
| 48 |
+
"--master_addr",
|
| 49 |
+
default="127.0.0.1",
|
| 50 |
+
type=str,
|
| 51 |
+
help="Master node (rank 0)'s address, should be either "
|
| 52 |
+
"the IP address or the hostname of node 0, for "
|
| 53 |
+
"single node multi-proc training, the "
|
| 54 |
+
"--master_addr can simply be 127.0.0.1",
|
| 55 |
+
)
|
| 56 |
+
parser.add_argument(
|
| 57 |
+
"--master_port",
|
| 58 |
+
default=29500,
|
| 59 |
+
type=int,
|
| 60 |
+
help="Master node (rank 0)'s free port that needs to "
|
| 61 |
+
"be used for communciation during distributed "
|
| 62 |
+
"training",
|
| 63 |
+
)
|
| 64 |
+
parser.add_argument(
|
| 65 |
+
"--use_env",
|
| 66 |
+
default=False,
|
| 67 |
+
action="store_true",
|
| 68 |
+
help="Use environment variable to pass "
|
| 69 |
+
"'local rank'. For legacy reasons, the default value is False. "
|
| 70 |
+
"If set to True, the script will not pass "
|
| 71 |
+
"--local_rank as argument, and will instead set LOCAL_RANK.",
|
| 72 |
+
)
|
| 73 |
+
parser.add_argument(
|
| 74 |
+
"-m",
|
| 75 |
+
"--module",
|
| 76 |
+
default=False,
|
| 77 |
+
action="store_true",
|
| 78 |
+
help="Changes each process to interpret the launch script "
|
| 79 |
+
"as a python module, executing with the same behavior as"
|
| 80 |
+
"'python -m'.",
|
| 81 |
+
)
|
| 82 |
+
parser.add_argument(
|
| 83 |
+
"-c",
|
| 84 |
+
"--command",
|
| 85 |
+
default=False,
|
| 86 |
+
action="store_true",
|
| 87 |
+
help="Changes each process to interpret the launch script " "as a command.",
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
# positional
|
| 91 |
+
parser.add_argument(
|
| 92 |
+
"training_script",
|
| 93 |
+
type=str,
|
| 94 |
+
help="The full path to the single GPU training "
|
| 95 |
+
"program/script/command to be launched in parallel, "
|
| 96 |
+
"followed by all the arguments for the "
|
| 97 |
+
"training script",
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
# rest from the training program
|
| 101 |
+
parser.add_argument("training_script_args", nargs=REMAINDER)
|
| 102 |
+
return parser.parse_args()
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def main():
|
| 106 |
+
"""Launch distributed processes."""
|
| 107 |
+
args = parse_args()
|
| 108 |
+
|
| 109 |
+
# world size in terms of number of processes
|
| 110 |
+
dist_world_size = args.nproc_per_node * args.nnodes
|
| 111 |
+
|
| 112 |
+
# set PyTorch distributed related environmental variables
|
| 113 |
+
current_env = os.environ.copy()
|
| 114 |
+
current_env["MASTER_ADDR"] = args.master_addr
|
| 115 |
+
current_env["MASTER_PORT"] = str(args.master_port)
|
| 116 |
+
current_env["WORLD_SIZE"] = str(dist_world_size)
|
| 117 |
+
|
| 118 |
+
processes = []
|
| 119 |
+
|
| 120 |
+
if "OMP_NUM_THREADS" not in os.environ and args.nproc_per_node > 1:
|
| 121 |
+
current_env["OMP_NUM_THREADS"] = str(1)
|
| 122 |
+
print(
|
| 123 |
+
"*****************************************\n"
|
| 124 |
+
"Setting OMP_NUM_THREADS environment variable for each process "
|
| 125 |
+
"to be {} in default, to avoid your system being overloaded, "
|
| 126 |
+
"please further tune the variable for optimal performance in "
|
| 127 |
+
"your application as needed. \n"
|
| 128 |
+
"*****************************************".format(
|
| 129 |
+
current_env["OMP_NUM_THREADS"]
|
| 130 |
+
)
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
for local_rank in range(0, args.nproc_per_node):
|
| 134 |
+
# each process's rank
|
| 135 |
+
dist_rank = args.nproc_per_node * args.node_rank + local_rank
|
| 136 |
+
current_env["RANK"] = str(dist_rank)
|
| 137 |
+
current_env["LOCAL_RANK"] = str(local_rank)
|
| 138 |
+
|
| 139 |
+
# spawn the processes
|
| 140 |
+
if args.command:
|
| 141 |
+
cmd = [args.training_script]
|
| 142 |
+
else:
|
| 143 |
+
cmd = [sys.executable, "-u"]
|
| 144 |
+
if args.module:
|
| 145 |
+
cmd.append("-m")
|
| 146 |
+
cmd.append(args.training_script)
|
| 147 |
+
|
| 148 |
+
if not args.use_env:
|
| 149 |
+
cmd.append("--local_rank={}".format(local_rank))
|
| 150 |
+
|
| 151 |
+
cmd.extend(args.training_script_args)
|
| 152 |
+
|
| 153 |
+
process = subprocess.Popen(cmd, env=current_env)
|
| 154 |
+
processes.append(process)
|
| 155 |
+
|
| 156 |
+
for process in processes:
|
| 157 |
+
process.wait()
|
| 158 |
+
if process.returncode != 0:
|
| 159 |
+
raise subprocess.CalledProcessError(returncode=process.returncode, cmd=cmd)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
if __name__ == "__main__":
|
| 163 |
+
main()
|
lscodec/layers/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .causal_conv import * # NOQA
|
| 2 |
+
from .pqmf import * # NOQA
|
| 3 |
+
from .residual_block import * # NOQA
|
| 4 |
+
from .residual_stack import * # NOQA
|
| 5 |
+
from .tade_res_block import * # NOQA
|
| 6 |
+
from .upsample import * # NOQA
|
lscodec/layers/causal_conv.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Copyright 2020 Tomoki Hayashi
|
| 4 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 5 |
+
|
| 6 |
+
"""Causal convolusion layer modules."""
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class CausalConv1d(torch.nn.Module):
|
| 13 |
+
"""CausalConv1d module with customized initialization."""
|
| 14 |
+
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
in_channels,
|
| 18 |
+
out_channels,
|
| 19 |
+
kernel_size,
|
| 20 |
+
dilation=1,
|
| 21 |
+
bias=True,
|
| 22 |
+
pad="ConstantPad1d",
|
| 23 |
+
pad_params={"value": 0.0},
|
| 24 |
+
):
|
| 25 |
+
"""Initialize CausalConv1d module."""
|
| 26 |
+
super(CausalConv1d, self).__init__()
|
| 27 |
+
self.pad = getattr(torch.nn, pad)((kernel_size - 1) * dilation, **pad_params)
|
| 28 |
+
self.conv = torch.nn.Conv1d(
|
| 29 |
+
in_channels, out_channels, kernel_size, dilation=dilation, bias=bias
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
def forward(self, x):
|
| 33 |
+
"""Calculate forward propagation.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
x (Tensor): Input tensor (B, in_channels, T).
|
| 37 |
+
|
| 38 |
+
Returns:
|
| 39 |
+
Tensor: Output tensor (B, out_channels, T).
|
| 40 |
+
|
| 41 |
+
"""
|
| 42 |
+
return self.conv(self.pad(x))[:, :, : x.size(2)]
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class CausalConvTranspose1d(torch.nn.Module):
|
| 46 |
+
"""CausalConvTranspose1d module with customized initialization."""
|
| 47 |
+
|
| 48 |
+
def __init__(self, in_channels, out_channels, kernel_size, stride, bias=True):
|
| 49 |
+
"""Initialize CausalConvTranspose1d module."""
|
| 50 |
+
super(CausalConvTranspose1d, self).__init__()
|
| 51 |
+
self.deconv = torch.nn.ConvTranspose1d(
|
| 52 |
+
in_channels, out_channels, kernel_size, stride, bias=bias
|
| 53 |
+
)
|
| 54 |
+
self.stride = stride
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
"""Calculate forward propagation.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
x (Tensor): Input tensor (B, in_channels, T_in).
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
Tensor: Output tensor (B, out_channels, T_out).
|
| 64 |
+
|
| 65 |
+
"""
|
| 66 |
+
return self.deconv(x)[:, :, : -self.stride]
|
lscodec/layers/pqmf.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Copyright 2020 Tomoki Hayashi
|
| 4 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 5 |
+
|
| 6 |
+
"""Pseudo QMF modules."""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
|
| 12 |
+
from scipy.signal import kaiser
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def design_prototype_filter(taps=62, cutoff_ratio=0.142, beta=9.0):
|
| 16 |
+
"""Design prototype filter for PQMF.
|
| 17 |
+
|
| 18 |
+
This method is based on `A Kaiser window approach for the design of prototype
|
| 19 |
+
filters of cosine modulated filterbanks`_.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
taps (int): The number of filter taps.
|
| 23 |
+
cutoff_ratio (float): Cut-off frequency ratio.
|
| 24 |
+
beta (float): Beta coefficient for kaiser window.
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
ndarray: Impluse response of prototype filter (taps + 1,).
|
| 28 |
+
|
| 29 |
+
.. _`A Kaiser window approach for the design of prototype filters of cosine modulated filterbanks`:
|
| 30 |
+
https://ieeexplore.ieee.org/abstract/document/681427
|
| 31 |
+
|
| 32 |
+
"""
|
| 33 |
+
# check the arguments are valid
|
| 34 |
+
assert taps % 2 == 0, "The number of taps mush be even number."
|
| 35 |
+
assert 0.0 < cutoff_ratio < 1.0, "Cutoff ratio must be > 0.0 and < 1.0."
|
| 36 |
+
|
| 37 |
+
# make initial filter
|
| 38 |
+
omega_c = np.pi * cutoff_ratio
|
| 39 |
+
with np.errstate(invalid="ignore"):
|
| 40 |
+
h_i = np.sin(omega_c * (np.arange(taps + 1) - 0.5 * taps)) / (
|
| 41 |
+
np.pi * (np.arange(taps + 1) - 0.5 * taps)
|
| 42 |
+
)
|
| 43 |
+
h_i[taps // 2] = np.cos(0) * cutoff_ratio # fix nan due to indeterminate form
|
| 44 |
+
|
| 45 |
+
# apply kaiser window
|
| 46 |
+
w = kaiser(taps + 1, beta)
|
| 47 |
+
h = h_i * w
|
| 48 |
+
|
| 49 |
+
return h
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class PQMF(torch.nn.Module):
|
| 53 |
+
"""PQMF module.
|
| 54 |
+
|
| 55 |
+
This module is based on `Near-perfect-reconstruction pseudo-QMF banks`_.
|
| 56 |
+
|
| 57 |
+
.. _`Near-perfect-reconstruction pseudo-QMF banks`:
|
| 58 |
+
https://ieeexplore.ieee.org/document/258122
|
| 59 |
+
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def __init__(self, subbands=4, taps=62, cutoff_ratio=0.142, beta=9.0):
|
| 63 |
+
"""Initilize PQMF module.
|
| 64 |
+
|
| 65 |
+
The cutoff_ratio and beta parameters are optimized for #subbands = 4.
|
| 66 |
+
See dicussion in https://github.com/kan-bayashi/ParallelWaveGAN/issues/195.
|
| 67 |
+
|
| 68 |
+
Args:
|
| 69 |
+
subbands (int): The number of subbands.
|
| 70 |
+
taps (int): The number of filter taps.
|
| 71 |
+
cutoff_ratio (float): Cut-off frequency ratio.
|
| 72 |
+
beta (float): Beta coefficient for kaiser window.
|
| 73 |
+
|
| 74 |
+
"""
|
| 75 |
+
super(PQMF, self).__init__()
|
| 76 |
+
|
| 77 |
+
# build analysis & synthesis filter coefficients
|
| 78 |
+
h_proto = design_prototype_filter(taps, cutoff_ratio, beta)
|
| 79 |
+
h_analysis = np.zeros((subbands, len(h_proto)))
|
| 80 |
+
h_synthesis = np.zeros((subbands, len(h_proto)))
|
| 81 |
+
for k in range(subbands):
|
| 82 |
+
h_analysis[k] = (
|
| 83 |
+
2
|
| 84 |
+
* h_proto
|
| 85 |
+
* np.cos(
|
| 86 |
+
(2 * k + 1)
|
| 87 |
+
* (np.pi / (2 * subbands))
|
| 88 |
+
* (np.arange(taps + 1) - (taps / 2))
|
| 89 |
+
+ (-1) ** k * np.pi / 4
|
| 90 |
+
)
|
| 91 |
+
)
|
| 92 |
+
h_synthesis[k] = (
|
| 93 |
+
2
|
| 94 |
+
* h_proto
|
| 95 |
+
* np.cos(
|
| 96 |
+
(2 * k + 1)
|
| 97 |
+
* (np.pi / (2 * subbands))
|
| 98 |
+
* (np.arange(taps + 1) - (taps / 2))
|
| 99 |
+
- (-1) ** k * np.pi / 4
|
| 100 |
+
)
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# convert to tensor
|
| 104 |
+
analysis_filter = torch.from_numpy(h_analysis).float().unsqueeze(1)
|
| 105 |
+
synthesis_filter = torch.from_numpy(h_synthesis).float().unsqueeze(0)
|
| 106 |
+
|
| 107 |
+
# register coefficients as beffer
|
| 108 |
+
self.register_buffer("analysis_filter", analysis_filter)
|
| 109 |
+
self.register_buffer("synthesis_filter", synthesis_filter)
|
| 110 |
+
|
| 111 |
+
# filter for downsampling & upsampling
|
| 112 |
+
updown_filter = torch.zeros((subbands, subbands, subbands)).float()
|
| 113 |
+
for k in range(subbands):
|
| 114 |
+
updown_filter[k, k, 0] = 1.0
|
| 115 |
+
self.register_buffer("updown_filter", updown_filter)
|
| 116 |
+
self.subbands = subbands
|
| 117 |
+
|
| 118 |
+
# keep padding info
|
| 119 |
+
self.pad_fn = torch.nn.ConstantPad1d(taps // 2, 0.0)
|
| 120 |
+
|
| 121 |
+
def analysis(self, x):
|
| 122 |
+
"""Analysis with PQMF.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
x (Tensor): Input tensor (B, 1, T).
|
| 126 |
+
|
| 127 |
+
Returns:
|
| 128 |
+
Tensor: Output tensor (B, subbands, T // subbands).
|
| 129 |
+
|
| 130 |
+
"""
|
| 131 |
+
x = F.conv1d(self.pad_fn(x), self.analysis_filter)
|
| 132 |
+
return F.conv1d(x, self.updown_filter, stride=self.subbands)
|
| 133 |
+
|
| 134 |
+
def synthesis(self, x):
|
| 135 |
+
"""Synthesis with PQMF.
|
| 136 |
+
|
| 137 |
+
Args:
|
| 138 |
+
x (Tensor): Input tensor (B, subbands, T // subbands).
|
| 139 |
+
|
| 140 |
+
Returns:
|
| 141 |
+
Tensor: Output tensor (B, 1, T).
|
| 142 |
+
|
| 143 |
+
"""
|
| 144 |
+
# NOTE(kan-bayashi): Power will be dreased so here multipy by # subbands.
|
| 145 |
+
# Not sure this is the correct way, it is better to check again.
|
| 146 |
+
# TODO(kan-bayashi): Understand the reconstruction procedure
|
| 147 |
+
x = F.conv_transpose1d(
|
| 148 |
+
x, self.updown_filter * self.subbands, stride=self.subbands
|
| 149 |
+
)
|
| 150 |
+
return F.conv1d(self.pad_fn(x), self.synthesis_filter)
|
lscodec/layers/residual_block.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
"""Residual block modules.
|
| 4 |
+
|
| 5 |
+
References:
|
| 6 |
+
- https://github.com/r9y9/wavenet_vocoder
|
| 7 |
+
- https://github.com/jik876/hifi-gan
|
| 8 |
+
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import math
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Conv1d(torch.nn.Conv1d):
|
| 18 |
+
"""Conv1d module with customized initialization."""
|
| 19 |
+
|
| 20 |
+
def __init__(self, *args, **kwargs):
|
| 21 |
+
"""Initialize Conv1d module."""
|
| 22 |
+
super(Conv1d, self).__init__(*args, **kwargs)
|
| 23 |
+
|
| 24 |
+
def reset_parameters(self):
|
| 25 |
+
"""Reset parameters."""
|
| 26 |
+
torch.nn.init.kaiming_normal_(self.weight, nonlinearity="relu")
|
| 27 |
+
if self.bias is not None:
|
| 28 |
+
torch.nn.init.constant_(self.bias, 0.0)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class Conv1d1x1(Conv1d):
|
| 32 |
+
"""1x1 Conv1d with customized initialization."""
|
| 33 |
+
|
| 34 |
+
def __init__(self, in_channels, out_channels, bias):
|
| 35 |
+
"""Initialize 1x1 Conv1d module."""
|
| 36 |
+
super(Conv1d1x1, self).__init__(
|
| 37 |
+
in_channels, out_channels, kernel_size=1, padding=0, dilation=1, bias=bias
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class WaveNetResidualBlock(torch.nn.Module):
|
| 42 |
+
"""Residual block module in WaveNet."""
|
| 43 |
+
|
| 44 |
+
def __init__(
|
| 45 |
+
self,
|
| 46 |
+
kernel_size=3,
|
| 47 |
+
residual_channels=64,
|
| 48 |
+
gate_channels=128,
|
| 49 |
+
skip_channels=64,
|
| 50 |
+
aux_channels=80,
|
| 51 |
+
dropout=0.0,
|
| 52 |
+
dilation=1,
|
| 53 |
+
bias=True,
|
| 54 |
+
use_causal_conv=False,
|
| 55 |
+
):
|
| 56 |
+
"""Initialize WaveNetResidualBlock module.
|
| 57 |
+
|
| 58 |
+
Args:
|
| 59 |
+
kernel_size (int): Kernel size of dilation convolution layer.
|
| 60 |
+
residual_channels (int): Number of channels for residual connection.
|
| 61 |
+
skip_channels (int): Number of channels for skip connection.
|
| 62 |
+
aux_channels (int): Local conditioning channels i.e. auxiliary input dimension.
|
| 63 |
+
dropout (float): Dropout probability.
|
| 64 |
+
dilation (int): Dilation factor.
|
| 65 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 66 |
+
use_causal_conv (bool): Whether to use use_causal_conv or non-use_causal_conv convolution.
|
| 67 |
+
|
| 68 |
+
"""
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.dropout = dropout
|
| 71 |
+
# no future time stamps available
|
| 72 |
+
if use_causal_conv:
|
| 73 |
+
padding = (kernel_size - 1) * dilation
|
| 74 |
+
else:
|
| 75 |
+
assert (kernel_size - 1) % 2 == 0, "Not support even number kernel size."
|
| 76 |
+
padding = (kernel_size - 1) // 2 * dilation
|
| 77 |
+
self.use_causal_conv = use_causal_conv
|
| 78 |
+
|
| 79 |
+
# dilation conv
|
| 80 |
+
self.conv = Conv1d(
|
| 81 |
+
residual_channels,
|
| 82 |
+
gate_channels,
|
| 83 |
+
kernel_size,
|
| 84 |
+
padding=padding,
|
| 85 |
+
dilation=dilation,
|
| 86 |
+
bias=bias,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# local conditioning
|
| 90 |
+
if aux_channels > 0:
|
| 91 |
+
self.conv1x1_aux = Conv1d1x1(aux_channels, gate_channels, bias=False)
|
| 92 |
+
else:
|
| 93 |
+
self.conv1x1_aux = None
|
| 94 |
+
|
| 95 |
+
# conv output is split into two groups
|
| 96 |
+
gate_out_channels = gate_channels // 2
|
| 97 |
+
self.conv1x1_out = Conv1d1x1(gate_out_channels, residual_channels, bias=bias)
|
| 98 |
+
self.conv1x1_skip = Conv1d1x1(gate_out_channels, skip_channels, bias=bias)
|
| 99 |
+
|
| 100 |
+
def forward(self, x, c):
|
| 101 |
+
"""Calculate forward propagation.
|
| 102 |
+
|
| 103 |
+
Args:
|
| 104 |
+
x (Tensor): Input tensor (B, residual_channels, T).
|
| 105 |
+
c (Tensor): Local conditioning auxiliary tensor (B, aux_channels, T).
|
| 106 |
+
|
| 107 |
+
Returns:
|
| 108 |
+
Tensor: Output tensor for residual connection (B, residual_channels, T).
|
| 109 |
+
Tensor: Output tensor for skip connection (B, skip_channels, T).
|
| 110 |
+
|
| 111 |
+
"""
|
| 112 |
+
residual = x
|
| 113 |
+
x = F.dropout(x, p=self.dropout, training=self.training)
|
| 114 |
+
x = self.conv(x)
|
| 115 |
+
|
| 116 |
+
# remove future time steps if use_causal_conv conv
|
| 117 |
+
x = x[:, :, : residual.size(-1)] if self.use_causal_conv else x
|
| 118 |
+
|
| 119 |
+
# split into two part for gated activation
|
| 120 |
+
splitdim = 1
|
| 121 |
+
xa, xb = x.split(x.size(splitdim) // 2, dim=splitdim)
|
| 122 |
+
|
| 123 |
+
# local conditioning
|
| 124 |
+
if c is not None:
|
| 125 |
+
assert self.conv1x1_aux is not None
|
| 126 |
+
c = self.conv1x1_aux(c)
|
| 127 |
+
ca, cb = c.split(c.size(splitdim) // 2, dim=splitdim)
|
| 128 |
+
xa, xb = xa + ca, xb + cb
|
| 129 |
+
|
| 130 |
+
x = torch.tanh(xa) * torch.sigmoid(xb)
|
| 131 |
+
|
| 132 |
+
# for skip connection
|
| 133 |
+
s = self.conv1x1_skip(x)
|
| 134 |
+
|
| 135 |
+
# for residual connection
|
| 136 |
+
x = (self.conv1x1_out(x) + residual) * math.sqrt(0.5)
|
| 137 |
+
|
| 138 |
+
return x, s
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class HiFiGANResidualBlock(torch.nn.Module):
|
| 142 |
+
"""Residual block module in HiFiGAN."""
|
| 143 |
+
|
| 144 |
+
def __init__(
|
| 145 |
+
self,
|
| 146 |
+
kernel_size=3,
|
| 147 |
+
channels=512,
|
| 148 |
+
dilations=(1, 3, 5),
|
| 149 |
+
bias=True,
|
| 150 |
+
use_additional_convs=True,
|
| 151 |
+
nonlinear_activation="LeakyReLU",
|
| 152 |
+
nonlinear_activation_params={"negative_slope": 0.1},
|
| 153 |
+
):
|
| 154 |
+
"""Initialize HiFiGANResidualBlock module.
|
| 155 |
+
|
| 156 |
+
Args:
|
| 157 |
+
kernel_size (int): Kernel size of dilation convolution layer.
|
| 158 |
+
channels (int): Number of channels for convolution layer.
|
| 159 |
+
dilations (List[int]): List of dilation factors.
|
| 160 |
+
use_additional_convs (bool): Whether to use additional convolution layers.
|
| 161 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 162 |
+
nonlinear_activation (str): Activation function module name.
|
| 163 |
+
nonlinear_activation_params (dict): Hyperparameters for activation function.
|
| 164 |
+
|
| 165 |
+
"""
|
| 166 |
+
super().__init__()
|
| 167 |
+
self.use_additional_convs = use_additional_convs
|
| 168 |
+
self.convs1 = torch.nn.ModuleList()
|
| 169 |
+
if use_additional_convs:
|
| 170 |
+
self.convs2 = torch.nn.ModuleList()
|
| 171 |
+
assert kernel_size % 2 == 1, "Kernel size must be odd number."
|
| 172 |
+
for dilation in dilations:
|
| 173 |
+
self.convs1 += [
|
| 174 |
+
torch.nn.Sequential(
|
| 175 |
+
getattr(torch.nn, nonlinear_activation)(
|
| 176 |
+
**nonlinear_activation_params
|
| 177 |
+
),
|
| 178 |
+
torch.nn.Conv1d(
|
| 179 |
+
channels,
|
| 180 |
+
channels,
|
| 181 |
+
kernel_size,
|
| 182 |
+
1,
|
| 183 |
+
dilation=dilation,
|
| 184 |
+
bias=bias,
|
| 185 |
+
padding=(kernel_size - 1) // 2 * dilation,
|
| 186 |
+
),
|
| 187 |
+
)
|
| 188 |
+
]
|
| 189 |
+
if use_additional_convs:
|
| 190 |
+
self.convs2 += [
|
| 191 |
+
torch.nn.Sequential(
|
| 192 |
+
getattr(torch.nn, nonlinear_activation)(
|
| 193 |
+
**nonlinear_activation_params
|
| 194 |
+
),
|
| 195 |
+
torch.nn.Conv1d(
|
| 196 |
+
channels,
|
| 197 |
+
channels,
|
| 198 |
+
kernel_size,
|
| 199 |
+
1,
|
| 200 |
+
dilation=1,
|
| 201 |
+
bias=bias,
|
| 202 |
+
padding=(kernel_size - 1) // 2,
|
| 203 |
+
),
|
| 204 |
+
)
|
| 205 |
+
]
|
| 206 |
+
|
| 207 |
+
def forward(self, x):
|
| 208 |
+
"""Calculate forward propagation.
|
| 209 |
+
|
| 210 |
+
Args:
|
| 211 |
+
x (Tensor): Input tensor (B, channels, T).
|
| 212 |
+
|
| 213 |
+
Returns:
|
| 214 |
+
Tensor: Output tensor (B, channels, T).
|
| 215 |
+
|
| 216 |
+
"""
|
| 217 |
+
for idx in range(len(self.convs1)):
|
| 218 |
+
xt = self.convs1[idx](x)
|
| 219 |
+
if self.use_additional_convs:
|
| 220 |
+
xt = self.convs2[idx](xt)
|
| 221 |
+
x = xt + x
|
| 222 |
+
return x
|
lscodec/layers/residual_stack.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
# Copyright 2020 Tomoki Hayashi
|
| 4 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 5 |
+
|
| 6 |
+
"""Residual stack module in MelGAN."""
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from lscodec.layers import CausalConv1d
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ResidualStack(torch.nn.Module):
|
| 14 |
+
"""Residual stack module introduced in MelGAN."""
|
| 15 |
+
|
| 16 |
+
def __init__(
|
| 17 |
+
self,
|
| 18 |
+
kernel_size=3,
|
| 19 |
+
channels=32,
|
| 20 |
+
dilation=1,
|
| 21 |
+
bias=True,
|
| 22 |
+
nonlinear_activation="LeakyReLU",
|
| 23 |
+
nonlinear_activation_params={"negative_slope": 0.2},
|
| 24 |
+
pad="ReflectionPad1d",
|
| 25 |
+
pad_params={},
|
| 26 |
+
use_causal_conv=False,
|
| 27 |
+
):
|
| 28 |
+
"""Initialize ResidualStack module.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
kernel_size (int): Kernel size of dilation convolution layer.
|
| 32 |
+
channels (int): Number of channels of convolution layers.
|
| 33 |
+
dilation (int): Dilation factor.
|
| 34 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 35 |
+
nonlinear_activation (str): Activation function module name.
|
| 36 |
+
nonlinear_activation_params (dict): Hyperparameters for activation function.
|
| 37 |
+
pad (str): Padding function module name before dilated convolution layer.
|
| 38 |
+
pad_params (dict): Hyperparameters for padding function.
|
| 39 |
+
use_causal_conv (bool): Whether to use causal convolution.
|
| 40 |
+
|
| 41 |
+
"""
|
| 42 |
+
super(ResidualStack, self).__init__()
|
| 43 |
+
|
| 44 |
+
# defile residual stack part
|
| 45 |
+
if not use_causal_conv:
|
| 46 |
+
assert (kernel_size - 1) % 2 == 0, "Not support even number kernel size."
|
| 47 |
+
self.stack = torch.nn.Sequential(
|
| 48 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 49 |
+
getattr(torch.nn, pad)((kernel_size - 1) // 2 * dilation, **pad_params),
|
| 50 |
+
torch.nn.Conv1d(
|
| 51 |
+
channels, channels, kernel_size, dilation=dilation, bias=bias
|
| 52 |
+
),
|
| 53 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 54 |
+
torch.nn.Conv1d(channels, channels, 1, bias=bias),
|
| 55 |
+
)
|
| 56 |
+
else:
|
| 57 |
+
self.stack = torch.nn.Sequential(
|
| 58 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 59 |
+
CausalConv1d(
|
| 60 |
+
channels,
|
| 61 |
+
channels,
|
| 62 |
+
kernel_size,
|
| 63 |
+
dilation=dilation,
|
| 64 |
+
bias=bias,
|
| 65 |
+
pad=pad,
|
| 66 |
+
pad_params=pad_params,
|
| 67 |
+
),
|
| 68 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 69 |
+
torch.nn.Conv1d(channels, channels, 1, bias=bias),
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# defile extra layer for skip connection
|
| 73 |
+
self.skip_layer = torch.nn.Conv1d(channels, channels, 1, bias=bias)
|
| 74 |
+
|
| 75 |
+
def forward(self, c):
|
| 76 |
+
"""Calculate forward propagation.
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
c (Tensor): Input tensor (B, channels, T).
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
Tensor: Output tensor (B, chennels, T).
|
| 83 |
+
|
| 84 |
+
"""
|
| 85 |
+
return self.stack(c) + self.skip_layer(c)
|
lscodec/layers/tade_res_block.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2021 Tomoki Hayashi
|
| 2 |
+
# MIT License (https://opensource.org/licenses/MIT)
|
| 3 |
+
|
| 4 |
+
"""StyleMelGAN's TADEResBlock Modules."""
|
| 5 |
+
|
| 6 |
+
from functools import partial
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TADELayer(torch.nn.Module):
|
| 12 |
+
"""TADE Layer module."""
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
in_channels=64,
|
| 17 |
+
aux_channels=80,
|
| 18 |
+
kernel_size=9,
|
| 19 |
+
bias=True,
|
| 20 |
+
upsample_factor=2,
|
| 21 |
+
upsample_mode="nearest",
|
| 22 |
+
):
|
| 23 |
+
"""Initilize TADE layer."""
|
| 24 |
+
super().__init__()
|
| 25 |
+
self.norm = torch.nn.InstanceNorm1d(in_channels)
|
| 26 |
+
self.aux_conv = torch.nn.Sequential(
|
| 27 |
+
torch.nn.Conv1d(
|
| 28 |
+
aux_channels,
|
| 29 |
+
in_channels,
|
| 30 |
+
kernel_size,
|
| 31 |
+
1,
|
| 32 |
+
bias=bias,
|
| 33 |
+
padding=(kernel_size - 1) // 2,
|
| 34 |
+
),
|
| 35 |
+
# NOTE(kan-bayashi): Use non-linear activation?
|
| 36 |
+
)
|
| 37 |
+
self.gated_conv = torch.nn.Sequential(
|
| 38 |
+
torch.nn.Conv1d(
|
| 39 |
+
in_channels,
|
| 40 |
+
in_channels * 2,
|
| 41 |
+
kernel_size,
|
| 42 |
+
1,
|
| 43 |
+
bias=bias,
|
| 44 |
+
padding=(kernel_size - 1) // 2,
|
| 45 |
+
),
|
| 46 |
+
# NOTE(kan-bayashi): Use non-linear activation?
|
| 47 |
+
)
|
| 48 |
+
self.upsample = torch.nn.Upsample(
|
| 49 |
+
scale_factor=upsample_factor, mode=upsample_mode
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def forward(self, x, c):
|
| 53 |
+
"""Calculate forward propagation.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
x (Tensor): Input tensor (B, in_channels, T).
|
| 57 |
+
c (Tensor): Auxiliary input tensor (B, aux_channels, T').
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
Tensor: Output tensor (B, in_channels, T * in_upsample_factor).
|
| 61 |
+
Tensor: Upsampled aux tensor (B, in_channels, T * aux_upsample_factor).
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
x = self.norm(x)
|
| 65 |
+
c = self.upsample(c)
|
| 66 |
+
c = self.aux_conv(c)
|
| 67 |
+
cg = self.gated_conv(c)
|
| 68 |
+
cg1, cg2 = cg.split(cg.size(1) // 2, dim=1)
|
| 69 |
+
# NOTE(kan-bayashi): Use upsample for noise input here?
|
| 70 |
+
y = cg1 * self.upsample(x) + cg2
|
| 71 |
+
# NOTE(kan-bayashi): Return upsampled aux here?
|
| 72 |
+
return y, c
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class TADEResBlock(torch.nn.Module):
|
| 76 |
+
"""TADEResBlock module."""
|
| 77 |
+
|
| 78 |
+
def __init__(
|
| 79 |
+
self,
|
| 80 |
+
in_channels=64,
|
| 81 |
+
aux_channels=80,
|
| 82 |
+
kernel_size=9,
|
| 83 |
+
dilation=2,
|
| 84 |
+
bias=True,
|
| 85 |
+
upsample_factor=2,
|
| 86 |
+
upsample_mode="nearest",
|
| 87 |
+
gated_function="softmax",
|
| 88 |
+
):
|
| 89 |
+
"""Initialize TADEResBlock module."""
|
| 90 |
+
super().__init__()
|
| 91 |
+
self.tade1 = TADELayer(
|
| 92 |
+
in_channels=in_channels,
|
| 93 |
+
aux_channels=aux_channels,
|
| 94 |
+
kernel_size=kernel_size,
|
| 95 |
+
bias=bias,
|
| 96 |
+
# NOTE(kan-bayashi): Use upsample in the first TADE layer?
|
| 97 |
+
upsample_factor=1,
|
| 98 |
+
upsample_mode=upsample_mode,
|
| 99 |
+
)
|
| 100 |
+
self.gated_conv1 = torch.nn.Conv1d(
|
| 101 |
+
in_channels,
|
| 102 |
+
in_channels * 2,
|
| 103 |
+
kernel_size,
|
| 104 |
+
1,
|
| 105 |
+
bias=bias,
|
| 106 |
+
padding=(kernel_size - 1) // 2,
|
| 107 |
+
)
|
| 108 |
+
self.tade2 = TADELayer(
|
| 109 |
+
in_channels=in_channels,
|
| 110 |
+
aux_channels=in_channels,
|
| 111 |
+
kernel_size=kernel_size,
|
| 112 |
+
bias=bias,
|
| 113 |
+
upsample_factor=upsample_factor,
|
| 114 |
+
upsample_mode=upsample_mode,
|
| 115 |
+
)
|
| 116 |
+
self.gated_conv2 = torch.nn.Conv1d(
|
| 117 |
+
in_channels,
|
| 118 |
+
in_channels * 2,
|
| 119 |
+
kernel_size,
|
| 120 |
+
1,
|
| 121 |
+
bias=bias,
|
| 122 |
+
dilation=dilation,
|
| 123 |
+
padding=(kernel_size - 1) // 2 * dilation,
|
| 124 |
+
)
|
| 125 |
+
self.upsample = torch.nn.Upsample(
|
| 126 |
+
scale_factor=upsample_factor, mode=upsample_mode
|
| 127 |
+
)
|
| 128 |
+
if gated_function == "softmax":
|
| 129 |
+
self.gated_function = partial(torch.softmax, dim=1)
|
| 130 |
+
elif gated_function == "sigmoid":
|
| 131 |
+
self.gated_function = torch.sigmoid
|
| 132 |
+
else:
|
| 133 |
+
raise ValueError(f"{gated_function} is not supported.")
|
| 134 |
+
|
| 135 |
+
def forward(self, x, c):
|
| 136 |
+
"""Calculate forward propagation.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
x (Tensor): Input tensor (B, in_channels, T).
|
| 140 |
+
c (Tensor): Auxiliary input tensor (B, aux_channels, T').
|
| 141 |
+
|
| 142 |
+
Returns:
|
| 143 |
+
Tensor: Output tensor (B, in_channels, T * in_upsample_factor).
|
| 144 |
+
Tensor: Upsampled auxirialy tensor (B, in_channels, T * in_upsample_factor).
|
| 145 |
+
|
| 146 |
+
"""
|
| 147 |
+
residual = x
|
| 148 |
+
|
| 149 |
+
x, c = self.tade1(x, c)
|
| 150 |
+
x = self.gated_conv1(x)
|
| 151 |
+
xa, xb = x.split(x.size(1) // 2, dim=1)
|
| 152 |
+
x = self.gated_function(xa) * torch.tanh(xb)
|
| 153 |
+
|
| 154 |
+
x, c = self.tade2(x, c)
|
| 155 |
+
x = self.gated_conv2(x)
|
| 156 |
+
xa, xb = x.split(x.size(1) // 2, dim=1)
|
| 157 |
+
x = self.gated_function(xa) * torch.tanh(xb)
|
| 158 |
+
|
| 159 |
+
# NOTE(kan-bayashi): Return upsampled aux here?
|
| 160 |
+
return self.upsample(residual) + x, c
|
lscodec/layers/upsample.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
"""Upsampling module.
|
| 4 |
+
|
| 5 |
+
This code is modified from https://github.com/r9y9/wavenet_vocoder.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
|
| 13 |
+
from lscodec.layers import Conv1d
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class Stretch2d(torch.nn.Module):
|
| 17 |
+
"""Stretch2d module."""
|
| 18 |
+
|
| 19 |
+
def __init__(self, x_scale, y_scale, mode="nearest"):
|
| 20 |
+
"""Initialize Stretch2d module.
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
x_scale (int): X scaling factor (Time axis in spectrogram).
|
| 24 |
+
y_scale (int): Y scaling factor (Frequency axis in spectrogram).
|
| 25 |
+
mode (str): Interpolation mode.
|
| 26 |
+
|
| 27 |
+
"""
|
| 28 |
+
super(Stretch2d, self).__init__()
|
| 29 |
+
self.x_scale = x_scale
|
| 30 |
+
self.y_scale = y_scale
|
| 31 |
+
self.mode = mode
|
| 32 |
+
|
| 33 |
+
def forward(self, x):
|
| 34 |
+
"""Calculate forward propagation.
|
| 35 |
+
|
| 36 |
+
Args:
|
| 37 |
+
x (Tensor): Input tensor (B, C, F, T).
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
Tensor: Interpolated tensor (B, C, F * y_scale, T * x_scale),
|
| 41 |
+
|
| 42 |
+
"""
|
| 43 |
+
return F.interpolate(
|
| 44 |
+
x, scale_factor=(self.y_scale, self.x_scale), mode=self.mode
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class Conv2d(torch.nn.Conv2d):
|
| 49 |
+
"""Conv2d module with customized initialization."""
|
| 50 |
+
|
| 51 |
+
def __init__(self, *args, **kwargs):
|
| 52 |
+
"""Initialize Conv2d module."""
|
| 53 |
+
super(Conv2d, self).__init__(*args, **kwargs)
|
| 54 |
+
|
| 55 |
+
def reset_parameters(self):
|
| 56 |
+
"""Reset parameters."""
|
| 57 |
+
self.weight.data.fill_(1.0 / np.prod(self.kernel_size))
|
| 58 |
+
if self.bias is not None:
|
| 59 |
+
torch.nn.init.constant_(self.bias, 0.0)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class UpsampleNetwork(torch.nn.Module):
|
| 63 |
+
"""Upsampling network module."""
|
| 64 |
+
|
| 65 |
+
def __init__(
|
| 66 |
+
self,
|
| 67 |
+
upsample_scales,
|
| 68 |
+
nonlinear_activation=None,
|
| 69 |
+
nonlinear_activation_params={},
|
| 70 |
+
interpolate_mode="nearest",
|
| 71 |
+
freq_axis_kernel_size=1,
|
| 72 |
+
use_causal_conv=False,
|
| 73 |
+
):
|
| 74 |
+
"""Initialize upsampling network module.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
upsample_scales (list): List of upsampling scales.
|
| 78 |
+
nonlinear_activation (str): Activation function name.
|
| 79 |
+
nonlinear_activation_params (dict): Arguments for specified activation function.
|
| 80 |
+
interpolate_mode (str): Interpolation mode.
|
| 81 |
+
freq_axis_kernel_size (int): Kernel size in the direction of frequency axis.
|
| 82 |
+
|
| 83 |
+
"""
|
| 84 |
+
super(UpsampleNetwork, self).__init__()
|
| 85 |
+
self.use_causal_conv = use_causal_conv
|
| 86 |
+
self.up_layers = torch.nn.ModuleList()
|
| 87 |
+
for scale in upsample_scales:
|
| 88 |
+
# interpolation layer
|
| 89 |
+
stretch = Stretch2d(scale, 1, interpolate_mode)
|
| 90 |
+
self.up_layers += [stretch]
|
| 91 |
+
|
| 92 |
+
# conv layer
|
| 93 |
+
assert (
|
| 94 |
+
freq_axis_kernel_size - 1
|
| 95 |
+
) % 2 == 0, "Not support even number freq axis kernel size."
|
| 96 |
+
freq_axis_padding = (freq_axis_kernel_size - 1) // 2
|
| 97 |
+
kernel_size = (freq_axis_kernel_size, scale * 2 + 1)
|
| 98 |
+
if use_causal_conv:
|
| 99 |
+
padding = (freq_axis_padding, scale * 2)
|
| 100 |
+
else:
|
| 101 |
+
padding = (freq_axis_padding, scale)
|
| 102 |
+
conv = Conv2d(1, 1, kernel_size=kernel_size, padding=padding, bias=False)
|
| 103 |
+
self.up_layers += [conv]
|
| 104 |
+
|
| 105 |
+
# nonlinear
|
| 106 |
+
if nonlinear_activation is not None:
|
| 107 |
+
nonlinear = getattr(torch.nn, nonlinear_activation)(
|
| 108 |
+
**nonlinear_activation_params
|
| 109 |
+
)
|
| 110 |
+
self.up_layers += [nonlinear]
|
| 111 |
+
|
| 112 |
+
def forward(self, c):
|
| 113 |
+
"""Calculate forward propagation.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
c : Input tensor (B, C, T).
|
| 117 |
+
|
| 118 |
+
Returns:
|
| 119 |
+
Tensor: Upsampled tensor (B, C, T'), where T' = T * prod(upsample_scales).
|
| 120 |
+
|
| 121 |
+
"""
|
| 122 |
+
c = c.unsqueeze(1) # (B, 1, C, T)
|
| 123 |
+
for f in self.up_layers:
|
| 124 |
+
if self.use_causal_conv and isinstance(f, Conv2d):
|
| 125 |
+
c = f(c)[..., : c.size(-1)]
|
| 126 |
+
else:
|
| 127 |
+
c = f(c)
|
| 128 |
+
return c.squeeze(1) # (B, C, T')
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
class ConvInUpsampleNetwork(torch.nn.Module):
|
| 132 |
+
"""Convolution + upsampling network module."""
|
| 133 |
+
|
| 134 |
+
def __init__(
|
| 135 |
+
self,
|
| 136 |
+
upsample_scales,
|
| 137 |
+
nonlinear_activation=None,
|
| 138 |
+
nonlinear_activation_params={},
|
| 139 |
+
interpolate_mode="nearest",
|
| 140 |
+
freq_axis_kernel_size=1,
|
| 141 |
+
aux_channels=80,
|
| 142 |
+
aux_context_window=0,
|
| 143 |
+
use_causal_conv=False,
|
| 144 |
+
):
|
| 145 |
+
"""Initialize convolution + upsampling network module.
|
| 146 |
+
|
| 147 |
+
Args:
|
| 148 |
+
upsample_scales (list): List of upsampling scales.
|
| 149 |
+
nonlinear_activation (str): Activation function name.
|
| 150 |
+
nonlinear_activation_params (dict): Arguments for specified activation function.
|
| 151 |
+
mode (str): Interpolation mode.
|
| 152 |
+
freq_axis_kernel_size (int): Kernel size in the direction of frequency axis.
|
| 153 |
+
aux_channels (int): Number of channels of pre-convolutional layer.
|
| 154 |
+
aux_context_window (int): Context window size of the pre-convolutional layer.
|
| 155 |
+
use_causal_conv (bool): Whether to use causal structure.
|
| 156 |
+
|
| 157 |
+
"""
|
| 158 |
+
super(ConvInUpsampleNetwork, self).__init__()
|
| 159 |
+
self.aux_context_window = aux_context_window
|
| 160 |
+
self.use_causal_conv = use_causal_conv and aux_context_window > 0
|
| 161 |
+
# To capture wide-context information in conditional features
|
| 162 |
+
kernel_size = (
|
| 163 |
+
aux_context_window + 1 if use_causal_conv else 2 * aux_context_window + 1
|
| 164 |
+
)
|
| 165 |
+
# NOTE(kan-bayashi): Here do not use padding because the input is already padded
|
| 166 |
+
self.conv_in = Conv1d(
|
| 167 |
+
aux_channels, aux_channels, kernel_size=kernel_size, bias=False
|
| 168 |
+
)
|
| 169 |
+
self.upsample = UpsampleNetwork(
|
| 170 |
+
upsample_scales=upsample_scales,
|
| 171 |
+
nonlinear_activation=nonlinear_activation,
|
| 172 |
+
nonlinear_activation_params=nonlinear_activation_params,
|
| 173 |
+
interpolate_mode=interpolate_mode,
|
| 174 |
+
freq_axis_kernel_size=freq_axis_kernel_size,
|
| 175 |
+
use_causal_conv=use_causal_conv,
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
def forward(self, c):
|
| 179 |
+
"""Calculate forward propagation.
|
| 180 |
+
|
| 181 |
+
Args:
|
| 182 |
+
c : Input tensor (B, C, T').
|
| 183 |
+
|
| 184 |
+
Returns:
|
| 185 |
+
Tensor: Upsampled tensor (B, C, T),
|
| 186 |
+
where T = (T' - aux_context_window * 2) * prod(upsample_scales).
|
| 187 |
+
|
| 188 |
+
Note:
|
| 189 |
+
The length of inputs considers the context window size.
|
| 190 |
+
|
| 191 |
+
"""
|
| 192 |
+
c_ = self.conv_in(c)
|
| 193 |
+
c = c_[:, :, : -self.aux_context_window] if self.use_causal_conv else c_
|
| 194 |
+
return self.upsample(c)
|
lscodec/models/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .hifigan import * # NOQA
|
| 2 |
+
from .melgan import * # NOQA
|
| 3 |
+
from .lscodec import *
|
lscodec/models/conformer/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Initialize sub package."""
|
lscodec/models/conformer/argument.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2020 Hirofumi Inaguma
|
| 2 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 3 |
+
|
| 4 |
+
"""Conformer common arguments."""
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
from distutils.util import strtobool
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def add_arguments_conformer_common(group):
|
| 12 |
+
"""Add Transformer common arguments."""
|
| 13 |
+
group.add_argument(
|
| 14 |
+
"--transformer-encoder-pos-enc-layer-type",
|
| 15 |
+
type=str,
|
| 16 |
+
default="abs_pos",
|
| 17 |
+
choices=["abs_pos", "scaled_abs_pos", "rel_pos"],
|
| 18 |
+
help="Transformer encoder positional encoding layer type",
|
| 19 |
+
)
|
| 20 |
+
group.add_argument(
|
| 21 |
+
"--transformer-encoder-activation-type",
|
| 22 |
+
type=str,
|
| 23 |
+
default="swish",
|
| 24 |
+
choices=["relu", "hardtanh", "selu", "swish"],
|
| 25 |
+
help="Transformer encoder activation function type",
|
| 26 |
+
)
|
| 27 |
+
group.add_argument(
|
| 28 |
+
"--macaron-style",
|
| 29 |
+
default=False,
|
| 30 |
+
type=strtobool,
|
| 31 |
+
help="Whether to use macaron style for positionwise layer",
|
| 32 |
+
)
|
| 33 |
+
# Attention
|
| 34 |
+
group.add_argument(
|
| 35 |
+
"--zero-triu",
|
| 36 |
+
default=False,
|
| 37 |
+
type=strtobool,
|
| 38 |
+
help="If true, zero the uppper triangular part of attention matrix.",
|
| 39 |
+
)
|
| 40 |
+
# Relative positional encoding
|
| 41 |
+
group.add_argument(
|
| 42 |
+
"--rel-pos-type",
|
| 43 |
+
type=str,
|
| 44 |
+
default="legacy",
|
| 45 |
+
choices=["legacy", "latest"],
|
| 46 |
+
help="Whether to use the latest relative positional encoding or the legacy one."
|
| 47 |
+
"The legacy relative positional encoding will be deprecated in the future."
|
| 48 |
+
"More Details can be found in https://github.com/espnet/espnet/pull/2816.",
|
| 49 |
+
)
|
| 50 |
+
# CNN module
|
| 51 |
+
group.add_argument(
|
| 52 |
+
"--use-cnn-module",
|
| 53 |
+
default=False,
|
| 54 |
+
type=strtobool,
|
| 55 |
+
help="Use convolution module or not",
|
| 56 |
+
)
|
| 57 |
+
group.add_argument(
|
| 58 |
+
"--cnn-module-kernel",
|
| 59 |
+
default=31,
|
| 60 |
+
type=int,
|
| 61 |
+
help="Kernel size of convolution module.",
|
| 62 |
+
)
|
| 63 |
+
return group
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def verify_rel_pos_type(args):
|
| 67 |
+
"""Verify the relative positional encoding type for compatibility.
|
| 68 |
+
|
| 69 |
+
Args:
|
| 70 |
+
args (Namespace): original arguments
|
| 71 |
+
Returns:
|
| 72 |
+
args (Namespace): modified arguments
|
| 73 |
+
"""
|
| 74 |
+
rel_pos_type = getattr(args, "rel_pos_type", None)
|
| 75 |
+
if rel_pos_type is None or rel_pos_type == "legacy":
|
| 76 |
+
if args.transformer_encoder_pos_enc_layer_type == "rel_pos":
|
| 77 |
+
args.transformer_encoder_pos_enc_layer_type = "legacy_rel_pos"
|
| 78 |
+
logging.warning(
|
| 79 |
+
"Using legacy_rel_pos and it will be deprecated in the future."
|
| 80 |
+
)
|
| 81 |
+
if args.transformer_encoder_selfattn_layer_type == "rel_selfattn":
|
| 82 |
+
args.transformer_encoder_selfattn_layer_type = "legacy_rel_selfattn"
|
| 83 |
+
logging.warning(
|
| 84 |
+
"Using legacy_rel_selfattn and it will be deprecated in the future."
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
return args
|
lscodec/models/conformer/attention.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Multi-Head Attention layer definition."""
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
import numpy
|
| 12 |
+
import torch
|
| 13 |
+
from torch import nn
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class MultiHeadedAttention(nn.Module):
|
| 17 |
+
"""Multi-Head Attention layer.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
n_head (int): The number of heads.
|
| 21 |
+
n_feat (int): The number of features.
|
| 22 |
+
dropout_rate (float): Dropout rate.
|
| 23 |
+
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
def __init__(self, n_head, n_feat, dropout_rate):
|
| 27 |
+
"""Construct an MultiHeadedAttention object."""
|
| 28 |
+
super(MultiHeadedAttention, self).__init__()
|
| 29 |
+
assert n_feat % n_head == 0
|
| 30 |
+
# We assume d_v always equals d_k
|
| 31 |
+
self.d_k = n_feat // n_head
|
| 32 |
+
self.h = n_head
|
| 33 |
+
self.linear_q = nn.Linear(n_feat, n_feat)
|
| 34 |
+
self.linear_k = nn.Linear(n_feat, n_feat)
|
| 35 |
+
self.linear_v = nn.Linear(n_feat, n_feat)
|
| 36 |
+
self.linear_out = nn.Linear(n_feat, n_feat)
|
| 37 |
+
self.attn = None
|
| 38 |
+
self.dropout = nn.Dropout(p=dropout_rate)
|
| 39 |
+
|
| 40 |
+
def forward_qkv(self, query, key, value):
|
| 41 |
+
"""Transform query, key and value.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
query (torch.Tensor): Query tensor (#batch, time1, size).
|
| 45 |
+
key (torch.Tensor): Key tensor (#batch, time2, size).
|
| 46 |
+
value (torch.Tensor): Value tensor (#batch, time2, size).
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
torch.Tensor: Transformed query tensor (#batch, n_head, time1, d_k).
|
| 50 |
+
torch.Tensor: Transformed key tensor (#batch, n_head, time2, d_k).
|
| 51 |
+
torch.Tensor: Transformed value tensor (#batch, n_head, time2, d_k).
|
| 52 |
+
|
| 53 |
+
"""
|
| 54 |
+
n_batch = query.size(0)
|
| 55 |
+
q = self.linear_q(query).view(n_batch, -1, self.h, self.d_k)
|
| 56 |
+
k = self.linear_k(key).view(n_batch, -1, self.h, self.d_k)
|
| 57 |
+
v = self.linear_v(value).view(n_batch, -1, self.h, self.d_k)
|
| 58 |
+
q = q.transpose(1, 2) # (batch, head, time1, d_k)
|
| 59 |
+
k = k.transpose(1, 2) # (batch, head, time2, d_k)
|
| 60 |
+
v = v.transpose(1, 2) # (batch, head, time2, d_k)
|
| 61 |
+
|
| 62 |
+
return q, k, v
|
| 63 |
+
|
| 64 |
+
def forward_attention(self, value, scores, mask):
|
| 65 |
+
"""Compute attention context vector.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
value (torch.Tensor): Transformed value (#batch, n_head, time2, d_k).
|
| 69 |
+
scores (torch.Tensor): Attention score (#batch, n_head, time1, time2).
|
| 70 |
+
mask (torch.Tensor): Mask (#batch, 1, time2) or (#batch, time1, time2).
|
| 71 |
+
|
| 72 |
+
Returns:
|
| 73 |
+
torch.Tensor: Transformed value (#batch, time1, d_model)
|
| 74 |
+
weighted by the attention score (#batch, time1, time2).
|
| 75 |
+
|
| 76 |
+
"""
|
| 77 |
+
n_batch = value.size(0)
|
| 78 |
+
if mask is not None:
|
| 79 |
+
mask = mask.unsqueeze(1).eq(0) # (batch, 1, *, time2)
|
| 80 |
+
min_value = float(
|
| 81 |
+
numpy.finfo(torch.tensor(0, dtype=scores.dtype).numpy().dtype).min
|
| 82 |
+
)
|
| 83 |
+
scores = scores.masked_fill(mask, min_value)
|
| 84 |
+
self.attn = torch.softmax(scores, dim=-1).masked_fill(
|
| 85 |
+
mask, 0.0
|
| 86 |
+
) # (batch, head, time1, time2)
|
| 87 |
+
else:
|
| 88 |
+
self.attn = torch.softmax(scores, dim=-1) # (batch, head, time1, time2)
|
| 89 |
+
|
| 90 |
+
p_attn = self.dropout(self.attn)
|
| 91 |
+
x = torch.matmul(p_attn, value) # (batch, head, time1, d_k)
|
| 92 |
+
x = (
|
| 93 |
+
x.transpose(1, 2).contiguous().view(n_batch, -1, self.h * self.d_k)
|
| 94 |
+
) # (batch, time1, d_model)
|
| 95 |
+
|
| 96 |
+
return self.linear_out(x) # (batch, time1, d_model)
|
| 97 |
+
|
| 98 |
+
def forward(self, query, key, value, mask):
|
| 99 |
+
"""Compute scaled dot product attention.
|
| 100 |
+
|
| 101 |
+
Args:
|
| 102 |
+
query (torch.Tensor): Query tensor (#batch, time1, size).
|
| 103 |
+
key (torch.Tensor): Key tensor (#batch, time2, size).
|
| 104 |
+
value (torch.Tensor): Value tensor (#batch, time2, size).
|
| 105 |
+
mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
|
| 106 |
+
(#batch, time1, time2).
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
torch.Tensor: Output tensor (#batch, time1, d_model).
|
| 110 |
+
|
| 111 |
+
"""
|
| 112 |
+
q, k, v = self.forward_qkv(query, key, value)
|
| 113 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k)
|
| 114 |
+
return self.forward_attention(v, scores, mask)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class LegacyRelPositionMultiHeadedAttention(MultiHeadedAttention):
|
| 118 |
+
"""Multi-Head Attention layer with relative position encoding (old version).
|
| 119 |
+
|
| 120 |
+
Details can be found in https://github.com/espnet/espnet/pull/2816.
|
| 121 |
+
|
| 122 |
+
Paper: https://arxiv.org/abs/1901.02860
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
n_head (int): The number of heads.
|
| 126 |
+
n_feat (int): The number of features.
|
| 127 |
+
dropout_rate (float): Dropout rate.
|
| 128 |
+
zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
|
| 129 |
+
|
| 130 |
+
"""
|
| 131 |
+
|
| 132 |
+
def __init__(self, n_head, n_feat, dropout_rate, zero_triu=False):
|
| 133 |
+
"""Construct an RelPositionMultiHeadedAttention object."""
|
| 134 |
+
super().__init__(n_head, n_feat, dropout_rate)
|
| 135 |
+
self.zero_triu = zero_triu
|
| 136 |
+
# linear transformation for positional encoding
|
| 137 |
+
self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
|
| 138 |
+
# these two learnable bias are used in matrix c and matrix d
|
| 139 |
+
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
| 140 |
+
self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
| 141 |
+
self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
| 142 |
+
torch.nn.init.xavier_uniform_(self.pos_bias_u)
|
| 143 |
+
torch.nn.init.xavier_uniform_(self.pos_bias_v)
|
| 144 |
+
|
| 145 |
+
def rel_shift(self, x):
|
| 146 |
+
"""Compute relative positional encoding.
|
| 147 |
+
|
| 148 |
+
Args:
|
| 149 |
+
x (torch.Tensor): Input tensor (batch, head, time1, time2).
|
| 150 |
+
|
| 151 |
+
Returns:
|
| 152 |
+
torch.Tensor: Output tensor.
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
zero_pad = torch.zeros((*x.size()[:3], 1), device=x.device, dtype=x.dtype)
|
| 156 |
+
x_padded = torch.cat([zero_pad, x], dim=-1)
|
| 157 |
+
|
| 158 |
+
x_padded = x_padded.view(*x.size()[:2], x.size(3) + 1, x.size(2))
|
| 159 |
+
x = x_padded[:, :, 1:].view_as(x)
|
| 160 |
+
|
| 161 |
+
if self.zero_triu:
|
| 162 |
+
ones = torch.ones((x.size(2), x.size(3)))
|
| 163 |
+
x = x * torch.tril(ones, x.size(3) - x.size(2))[None, None, :, :]
|
| 164 |
+
|
| 165 |
+
return x
|
| 166 |
+
|
| 167 |
+
def forward(self, query, key, value, pos_emb, mask):
|
| 168 |
+
"""Compute 'Scaled Dot Product Attention' with rel. positional encoding.
|
| 169 |
+
|
| 170 |
+
Args:
|
| 171 |
+
query (torch.Tensor): Query tensor (#batch, time1, size).
|
| 172 |
+
key (torch.Tensor): Key tensor (#batch, time2, size).
|
| 173 |
+
value (torch.Tensor): Value tensor (#batch, time2, size).
|
| 174 |
+
pos_emb (torch.Tensor): Positional embedding tensor (#batch, time1, size).
|
| 175 |
+
mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
|
| 176 |
+
(#batch, time1, time2).
|
| 177 |
+
|
| 178 |
+
Returns:
|
| 179 |
+
torch.Tensor: Output tensor (#batch, time1, d_model).
|
| 180 |
+
|
| 181 |
+
"""
|
| 182 |
+
q, k, v = self.forward_qkv(query, key, value)
|
| 183 |
+
q = q.transpose(1, 2) # (batch, time1, head, d_k)
|
| 184 |
+
|
| 185 |
+
n_batch_pos = pos_emb.size(0)
|
| 186 |
+
p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
|
| 187 |
+
p = p.transpose(1, 2) # (batch, head, time1, d_k)
|
| 188 |
+
|
| 189 |
+
# (batch, head, time1, d_k)
|
| 190 |
+
q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
|
| 191 |
+
# (batch, head, time1, d_k)
|
| 192 |
+
q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
|
| 193 |
+
|
| 194 |
+
# compute attention score
|
| 195 |
+
# first compute matrix a and matrix c
|
| 196 |
+
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
| 197 |
+
# (batch, head, time1, time2)
|
| 198 |
+
matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
|
| 199 |
+
|
| 200 |
+
# compute matrix b and matrix d
|
| 201 |
+
# (batch, head, time1, time1)
|
| 202 |
+
matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
|
| 203 |
+
matrix_bd = self.rel_shift(matrix_bd)
|
| 204 |
+
|
| 205 |
+
scores = (matrix_ac + matrix_bd) / math.sqrt(
|
| 206 |
+
self.d_k
|
| 207 |
+
) # (batch, head, time1, time2)
|
| 208 |
+
|
| 209 |
+
return self.forward_attention(v, scores, mask)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class RelPositionMultiHeadedAttention(MultiHeadedAttention):
|
| 213 |
+
"""Multi-Head Attention layer with relative position encoding (new implementation).
|
| 214 |
+
|
| 215 |
+
Details can be found in https://github.com/espnet/espnet/pull/2816.
|
| 216 |
+
|
| 217 |
+
Paper: https://arxiv.org/abs/1901.02860
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
n_head (int): The number of heads.
|
| 221 |
+
n_feat (int): The number of features.
|
| 222 |
+
dropout_rate (float): Dropout rate.
|
| 223 |
+
zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
|
| 224 |
+
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
def __init__(self, n_head, n_feat, dropout_rate, zero_triu=False):
|
| 228 |
+
"""Construct an RelPositionMultiHeadedAttention object."""
|
| 229 |
+
super().__init__(n_head, n_feat, dropout_rate)
|
| 230 |
+
self.zero_triu = zero_triu
|
| 231 |
+
# linear transformation for positional encoding
|
| 232 |
+
self.linear_pos = nn.Linear(n_feat, n_feat, bias=False)
|
| 233 |
+
# these two learnable bias are used in matrix c and matrix d
|
| 234 |
+
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
| 235 |
+
self.pos_bias_u = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
| 236 |
+
self.pos_bias_v = nn.Parameter(torch.Tensor(self.h, self.d_k))
|
| 237 |
+
torch.nn.init.xavier_uniform_(self.pos_bias_u)
|
| 238 |
+
torch.nn.init.xavier_uniform_(self.pos_bias_v)
|
| 239 |
+
|
| 240 |
+
def rel_shift(self, x):
|
| 241 |
+
"""Compute relative positional encoding.
|
| 242 |
+
|
| 243 |
+
Args:
|
| 244 |
+
x (torch.Tensor): Input tensor (batch, head, time1, 2*time1-1).
|
| 245 |
+
time1 means the length of query vector.
|
| 246 |
+
|
| 247 |
+
Returns:
|
| 248 |
+
torch.Tensor: Output tensor.
|
| 249 |
+
|
| 250 |
+
"""
|
| 251 |
+
zero_pad = torch.zeros((*x.size()[:3], 1), device=x.device, dtype=x.dtype)
|
| 252 |
+
x_padded = torch.cat([zero_pad, x], dim=-1)
|
| 253 |
+
|
| 254 |
+
x_padded = x_padded.view(*x.size()[:2], x.size(3) + 1, x.size(2))
|
| 255 |
+
x = x_padded[:, :, 1:].view_as(x)[
|
| 256 |
+
:, :, :, : x.size(-1) // 2 + 1
|
| 257 |
+
] # only keep the positions from 0 to time2
|
| 258 |
+
|
| 259 |
+
if self.zero_triu:
|
| 260 |
+
ones = torch.ones((x.size(2), x.size(3)), device=x.device)
|
| 261 |
+
x = x * torch.tril(ones, x.size(3) - x.size(2))[None, None, :, :]
|
| 262 |
+
|
| 263 |
+
return x
|
| 264 |
+
|
| 265 |
+
def forward(self, query, key, value, pos_emb, mask):
|
| 266 |
+
"""Compute 'Scaled Dot Product Attention' with rel. positional encoding.
|
| 267 |
+
|
| 268 |
+
Args:
|
| 269 |
+
query (torch.Tensor): Query tensor (#batch, time1, size).
|
| 270 |
+
key (torch.Tensor): Key tensor (#batch, time2, size).
|
| 271 |
+
value (torch.Tensor): Value tensor (#batch, time2, size).
|
| 272 |
+
pos_emb (torch.Tensor): Positional embedding tensor
|
| 273 |
+
(#batch, 2*time1-1, size).
|
| 274 |
+
mask (torch.Tensor): Mask tensor (#batch, 1, time2) or
|
| 275 |
+
(#batch, time1, time2).
|
| 276 |
+
|
| 277 |
+
Returns:
|
| 278 |
+
torch.Tensor: Output tensor (#batch, time1, d_model).
|
| 279 |
+
|
| 280 |
+
"""
|
| 281 |
+
q, k, v = self.forward_qkv(query, key, value)
|
| 282 |
+
q = q.transpose(1, 2) # (batch, time1, head, d_k)
|
| 283 |
+
|
| 284 |
+
n_batch_pos = pos_emb.size(0)
|
| 285 |
+
p = self.linear_pos(pos_emb).view(n_batch_pos, -1, self.h, self.d_k)
|
| 286 |
+
p = p.transpose(1, 2) # (batch, head, 2*time1-1, d_k)
|
| 287 |
+
|
| 288 |
+
# (batch, head, time1, d_k)
|
| 289 |
+
q_with_bias_u = (q + self.pos_bias_u).transpose(1, 2)
|
| 290 |
+
# (batch, head, time1, d_k)
|
| 291 |
+
q_with_bias_v = (q + self.pos_bias_v).transpose(1, 2)
|
| 292 |
+
|
| 293 |
+
# compute attention score
|
| 294 |
+
# first compute matrix a and matrix c
|
| 295 |
+
# as described in https://arxiv.org/abs/1901.02860 Section 3.3
|
| 296 |
+
# (batch, head, time1, time2)
|
| 297 |
+
matrix_ac = torch.matmul(q_with_bias_u, k.transpose(-2, -1))
|
| 298 |
+
|
| 299 |
+
# compute matrix b and matrix d
|
| 300 |
+
# (batch, head, time1, 2*time1-1)
|
| 301 |
+
matrix_bd = torch.matmul(q_with_bias_v, p.transpose(-2, -1))
|
| 302 |
+
matrix_bd = self.rel_shift(matrix_bd)
|
| 303 |
+
|
| 304 |
+
scores = (matrix_ac + matrix_bd) / math.sqrt(
|
| 305 |
+
self.d_k
|
| 306 |
+
) # (batch, head, time1, time2)
|
| 307 |
+
|
| 308 |
+
return self.forward_attention(v, scores, mask)
|
lscodec/models/conformer/contextual_block_encoder_layer.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""
|
| 3 |
+
Created on Sat Aug 21 16:57:31 2021.
|
| 4 |
+
|
| 5 |
+
@author: Keqi Deng (UCAS)
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from espnet.nets.pytorch_backend.transformer.layer_norm import LayerNorm
|
| 9 |
+
import torch
|
| 10 |
+
from torch import nn
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ContextualBlockEncoderLayer(nn.Module):
|
| 14 |
+
"""Contexutal Block Encoder layer module.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
size (int): Input dimension.
|
| 18 |
+
self_attn (torch.nn.Module): Self-attention module instance.
|
| 19 |
+
`MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
|
| 20 |
+
can be used as the argument.
|
| 21 |
+
feed_forward (torch.nn.Module): Feed-forward module instance.
|
| 22 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 23 |
+
can be used as the argument.
|
| 24 |
+
feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
|
| 25 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 26 |
+
can be used as the argument.
|
| 27 |
+
conv_module (torch.nn.Module): Convolution module instance.
|
| 28 |
+
`ConvlutionModule` instance can be used as the argument.
|
| 29 |
+
dropout_rate (float): Dropout rate.
|
| 30 |
+
total_layer_num (int): Total number of layers
|
| 31 |
+
normalize_before (bool): Whether to use layer_norm before the first block.
|
| 32 |
+
concat_after (bool): Whether to concat attention layer's input and output.
|
| 33 |
+
if True, additional linear will be applied.
|
| 34 |
+
i.e. x -> x + linear(concat(x, att(x)))
|
| 35 |
+
if False, no additional linear will be applied. i.e. x -> x + att(x)
|
| 36 |
+
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def __init__(
|
| 40 |
+
self,
|
| 41 |
+
size,
|
| 42 |
+
self_attn,
|
| 43 |
+
feed_forward,
|
| 44 |
+
feed_forward_macaron,
|
| 45 |
+
conv_module,
|
| 46 |
+
dropout_rate,
|
| 47 |
+
total_layer_num,
|
| 48 |
+
normalize_before=True,
|
| 49 |
+
concat_after=False,
|
| 50 |
+
):
|
| 51 |
+
"""Construct an EncoderLayer object."""
|
| 52 |
+
super(ContextualBlockEncoderLayer, self).__init__()
|
| 53 |
+
self.self_attn = self_attn
|
| 54 |
+
self.feed_forward = feed_forward
|
| 55 |
+
self.feed_forward_macaron = feed_forward_macaron
|
| 56 |
+
self.conv_module = conv_module
|
| 57 |
+
self.norm1 = LayerNorm(size)
|
| 58 |
+
self.norm2 = LayerNorm(size)
|
| 59 |
+
if feed_forward_macaron is not None:
|
| 60 |
+
self.norm_ff_macaron = LayerNorm(size)
|
| 61 |
+
self.ff_scale = 0.5
|
| 62 |
+
else:
|
| 63 |
+
self.ff_scale = 1.0
|
| 64 |
+
if self.conv_module is not None:
|
| 65 |
+
self.norm_conv = LayerNorm(size) # for the CNN module
|
| 66 |
+
self.norm_final = LayerNorm(size) # for the final output of the block
|
| 67 |
+
self.dropout = nn.Dropout(dropout_rate)
|
| 68 |
+
self.size = size
|
| 69 |
+
self.normalize_before = normalize_before
|
| 70 |
+
self.concat_after = concat_after
|
| 71 |
+
self.total_layer_num = total_layer_num
|
| 72 |
+
if self.concat_after:
|
| 73 |
+
self.concat_linear = nn.Linear(size + size, size)
|
| 74 |
+
|
| 75 |
+
def forward(
|
| 76 |
+
self,
|
| 77 |
+
x,
|
| 78 |
+
mask,
|
| 79 |
+
infer_mode=False,
|
| 80 |
+
past_ctx=None,
|
| 81 |
+
next_ctx=None,
|
| 82 |
+
is_short_segment=False,
|
| 83 |
+
layer_idx=0,
|
| 84 |
+
cache=None,
|
| 85 |
+
):
|
| 86 |
+
"""Calculate forward propagation."""
|
| 87 |
+
if self.training or not infer_mode:
|
| 88 |
+
return self.forward_train(x, mask, past_ctx, next_ctx, layer_idx, cache)
|
| 89 |
+
else:
|
| 90 |
+
return self.forward_infer(
|
| 91 |
+
x, mask, past_ctx, next_ctx, is_short_segment, layer_idx, cache
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
def forward_train(
|
| 95 |
+
self, x, mask, past_ctx=None, next_ctx=None, layer_idx=0, cache=None
|
| 96 |
+
):
|
| 97 |
+
"""Compute encoded features.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
x_input (torch.Tensor): Input tensor (#batch, time, size).
|
| 101 |
+
mask (torch.Tensor): Mask tensor for the input (#batch, time).
|
| 102 |
+
past_ctx (torch.Tensor): Previous contexutal vector
|
| 103 |
+
next_ctx (torch.Tensor): Next contexutal vector
|
| 104 |
+
cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
|
| 105 |
+
|
| 106 |
+
Returns:
|
| 107 |
+
torch.Tensor: Output tensor (#batch, time, size).
|
| 108 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 109 |
+
cur_ctx (torch.Tensor): Current contexutal vector
|
| 110 |
+
next_ctx (torch.Tensor): Next contexutal vector
|
| 111 |
+
layer_idx (int): layer index number
|
| 112 |
+
|
| 113 |
+
"""
|
| 114 |
+
nbatch = x.size(0)
|
| 115 |
+
nblock = x.size(1)
|
| 116 |
+
|
| 117 |
+
if past_ctx is not None:
|
| 118 |
+
if next_ctx is None:
|
| 119 |
+
# store all context vectors in one tensor
|
| 120 |
+
next_ctx = past_ctx.new_zeros(
|
| 121 |
+
nbatch, nblock, self.total_layer_num, x.size(-1)
|
| 122 |
+
)
|
| 123 |
+
else:
|
| 124 |
+
x[:, :, 0] = past_ctx[:, :, layer_idx]
|
| 125 |
+
|
| 126 |
+
# reshape ( nbatch, nblock, block_size + 2, dim )
|
| 127 |
+
# -> ( nbatch * nblock, block_size + 2, dim )
|
| 128 |
+
x = x.view(-1, x.size(-2), x.size(-1))
|
| 129 |
+
if mask is not None:
|
| 130 |
+
mask = mask.view(-1, mask.size(-2), mask.size(-1))
|
| 131 |
+
|
| 132 |
+
# whether to use macaron style
|
| 133 |
+
if self.feed_forward_macaron is not None:
|
| 134 |
+
residual = x
|
| 135 |
+
if self.normalize_before:
|
| 136 |
+
x = self.norm_ff_macaron(x)
|
| 137 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
|
| 138 |
+
if not self.normalize_before:
|
| 139 |
+
x = self.norm_ff_macaron(x)
|
| 140 |
+
|
| 141 |
+
residual = x
|
| 142 |
+
if self.normalize_before:
|
| 143 |
+
x = self.norm1(x)
|
| 144 |
+
|
| 145 |
+
if cache is None:
|
| 146 |
+
x_q = x
|
| 147 |
+
else:
|
| 148 |
+
assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
|
| 149 |
+
x_q = x[:, -1:, :]
|
| 150 |
+
residual = residual[:, -1:, :]
|
| 151 |
+
mask = None if mask is None else mask[:, -1:, :]
|
| 152 |
+
|
| 153 |
+
if self.concat_after:
|
| 154 |
+
x_concat = torch.cat((x, self.self_attn(x_q, x, x, mask)), dim=-1)
|
| 155 |
+
x = residual + self.concat_linear(x_concat)
|
| 156 |
+
else:
|
| 157 |
+
x = residual + self.dropout(self.self_attn(x_q, x, x, mask))
|
| 158 |
+
if not self.normalize_before:
|
| 159 |
+
x = self.norm1(x)
|
| 160 |
+
|
| 161 |
+
# convolution module
|
| 162 |
+
if self.conv_module is not None:
|
| 163 |
+
residual = x
|
| 164 |
+
if self.normalize_before:
|
| 165 |
+
x = self.norm_conv(x)
|
| 166 |
+
x = residual + self.dropout(self.conv_module(x))
|
| 167 |
+
if not self.normalize_before:
|
| 168 |
+
x = self.norm_conv(x)
|
| 169 |
+
|
| 170 |
+
residual = x
|
| 171 |
+
if self.normalize_before:
|
| 172 |
+
x = self.norm2(x)
|
| 173 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
|
| 174 |
+
if not self.normalize_before:
|
| 175 |
+
x = self.norm2(x)
|
| 176 |
+
|
| 177 |
+
if self.conv_module is not None:
|
| 178 |
+
x = self.norm_final(x)
|
| 179 |
+
|
| 180 |
+
if cache is not None:
|
| 181 |
+
x = torch.cat([cache, x], dim=1)
|
| 182 |
+
|
| 183 |
+
layer_idx += 1
|
| 184 |
+
# reshape ( nbatch * nblock, block_size + 2, dim )
|
| 185 |
+
# -> ( nbatch, nblock, block_size + 2, dim )
|
| 186 |
+
x = x.view(nbatch, -1, x.size(-2), x.size(-1)).squeeze(1)
|
| 187 |
+
if mask is not None:
|
| 188 |
+
mask = mask.view(nbatch, -1, mask.size(-2), mask.size(-1)).squeeze(1)
|
| 189 |
+
|
| 190 |
+
if next_ctx is not None and layer_idx < self.total_layer_num:
|
| 191 |
+
next_ctx[:, 0, layer_idx, :] = x[:, 0, -1, :]
|
| 192 |
+
next_ctx[:, 1:, layer_idx, :] = x[:, 0:-1, -1, :]
|
| 193 |
+
|
| 194 |
+
return x, mask, False, next_ctx, next_ctx, layer_idx
|
| 195 |
+
|
| 196 |
+
def forward_infer(
|
| 197 |
+
self,
|
| 198 |
+
x,
|
| 199 |
+
mask,
|
| 200 |
+
past_ctx=None,
|
| 201 |
+
next_ctx=None,
|
| 202 |
+
is_short_segment=False,
|
| 203 |
+
layer_idx=0,
|
| 204 |
+
cache=None,
|
| 205 |
+
):
|
| 206 |
+
"""Compute encoded features.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
x_input (torch.Tensor): Input tensor (#batch, time, size).
|
| 210 |
+
mask (torch.Tensor): Mask tensor for the input (#batch, time).
|
| 211 |
+
past_ctx (torch.Tensor): Previous contexutal vector
|
| 212 |
+
next_ctx (torch.Tensor): Next contexutal vector
|
| 213 |
+
cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
torch.Tensor: Output tensor (#batch, time, size).
|
| 217 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 218 |
+
cur_ctx (torch.Tensor): Current contexutal vector
|
| 219 |
+
next_ctx (torch.Tensor): Next contexutal vector
|
| 220 |
+
layer_idx (int): layer index number
|
| 221 |
+
|
| 222 |
+
"""
|
| 223 |
+
nbatch = x.size(0)
|
| 224 |
+
nblock = x.size(1)
|
| 225 |
+
# if layer_idx == 0, next_ctx has to be None
|
| 226 |
+
if layer_idx == 0:
|
| 227 |
+
assert next_ctx is None
|
| 228 |
+
next_ctx = x.new_zeros(nbatch, self.total_layer_num, x.size(-1))
|
| 229 |
+
|
| 230 |
+
# reshape ( nbatch, nblock, block_size + 2, dim )
|
| 231 |
+
# -> ( nbatch * nblock, block_size + 2, dim )
|
| 232 |
+
x = x.view(-1, x.size(-2), x.size(-1))
|
| 233 |
+
if mask is not None:
|
| 234 |
+
mask = mask.view(-1, mask.size(-2), mask.size(-1))
|
| 235 |
+
|
| 236 |
+
# whether to use macaron style
|
| 237 |
+
if self.feed_forward_macaron is not None:
|
| 238 |
+
residual = x
|
| 239 |
+
if self.normalize_before:
|
| 240 |
+
x = self.norm_ff_macaron(x)
|
| 241 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
|
| 242 |
+
if not self.normalize_before:
|
| 243 |
+
x = self.norm_ff_macaron(x)
|
| 244 |
+
|
| 245 |
+
residual = x
|
| 246 |
+
if self.normalize_before:
|
| 247 |
+
x = self.norm1(x)
|
| 248 |
+
|
| 249 |
+
if cache is None:
|
| 250 |
+
x_q = x
|
| 251 |
+
else:
|
| 252 |
+
assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
|
| 253 |
+
x_q = x[:, -1:, :]
|
| 254 |
+
residual = residual[:, -1:, :]
|
| 255 |
+
mask = None if mask is None else mask[:, -1:, :]
|
| 256 |
+
|
| 257 |
+
if self.concat_after:
|
| 258 |
+
x_concat = torch.cat((x, self.self_attn(x_q, x, x, mask)), dim=-1)
|
| 259 |
+
x = residual + self.concat_linear(x_concat)
|
| 260 |
+
else:
|
| 261 |
+
x = residual + self.dropout(self.self_attn(x_q, x, x, mask))
|
| 262 |
+
if not self.normalize_before:
|
| 263 |
+
x = self.norm1(x)
|
| 264 |
+
|
| 265 |
+
# convolution module
|
| 266 |
+
if self.conv_module is not None:
|
| 267 |
+
residual = x
|
| 268 |
+
if self.normalize_before:
|
| 269 |
+
x = self.norm_conv(x)
|
| 270 |
+
x = residual + self.dropout(self.conv_module(x))
|
| 271 |
+
if not self.normalize_before:
|
| 272 |
+
x = self.norm_conv(x)
|
| 273 |
+
|
| 274 |
+
residual = x
|
| 275 |
+
if self.normalize_before:
|
| 276 |
+
x = self.norm2(x)
|
| 277 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
|
| 278 |
+
if not self.normalize_before:
|
| 279 |
+
x = self.norm2(x)
|
| 280 |
+
|
| 281 |
+
if self.conv_module is not None:
|
| 282 |
+
x = self.norm_final(x)
|
| 283 |
+
|
| 284 |
+
if cache is not None:
|
| 285 |
+
x = torch.cat([cache, x], dim=1)
|
| 286 |
+
|
| 287 |
+
# reshape ( nbatch * nblock, block_size + 2, dim )
|
| 288 |
+
# -> ( nbatch, nblock, block_size + 2, dim )
|
| 289 |
+
x = x.view(nbatch, nblock, x.size(-2), x.size(-1))
|
| 290 |
+
if mask is not None:
|
| 291 |
+
mask = mask.view(nbatch, nblock, mask.size(-2), mask.size(-1))
|
| 292 |
+
|
| 293 |
+
# Propagete context information (the last frame of each block)
|
| 294 |
+
# to the first frame
|
| 295 |
+
# of the next block
|
| 296 |
+
|
| 297 |
+
if not is_short_segment:
|
| 298 |
+
if past_ctx is None:
|
| 299 |
+
# First block of an utterance
|
| 300 |
+
x[:, 0, 0, :] = x[:, 0, -1, :]
|
| 301 |
+
else:
|
| 302 |
+
x[:, 0, 0, :] = past_ctx[:, layer_idx, :]
|
| 303 |
+
if nblock > 1:
|
| 304 |
+
x[:, 1:, 0, :] = x[:, 0:-1, -1, :]
|
| 305 |
+
next_ctx[:, layer_idx, :] = x[:, -1, -1, :]
|
| 306 |
+
else:
|
| 307 |
+
next_ctx = None
|
| 308 |
+
|
| 309 |
+
return x, mask, True, past_ctx, next_ctx, is_short_segment, layer_idx + 1
|
lscodec/models/conformer/convolution.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 5 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 6 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 7 |
+
|
| 8 |
+
"""ConvolutionModule definition."""
|
| 9 |
+
|
| 10 |
+
from torch import nn
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ConvolutionModule(nn.Module):
|
| 14 |
+
"""ConvolutionModule in Conformer model.
|
| 15 |
+
|
| 16 |
+
Args:
|
| 17 |
+
channels (int): The number of channels of conv layers.
|
| 18 |
+
kernel_size (int): Kernerl size of conv layers.
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self, channels, kernel_size, activation=nn.ReLU(), bias=True):
|
| 23 |
+
"""Construct an ConvolutionModule object."""
|
| 24 |
+
super(ConvolutionModule, self).__init__()
|
| 25 |
+
# kernerl_size should be a odd number for 'SAME' padding
|
| 26 |
+
assert (kernel_size - 1) % 2 == 0
|
| 27 |
+
|
| 28 |
+
self.pointwise_conv1 = nn.Conv1d(
|
| 29 |
+
channels,
|
| 30 |
+
2 * channels,
|
| 31 |
+
kernel_size=1,
|
| 32 |
+
stride=1,
|
| 33 |
+
padding=0,
|
| 34 |
+
bias=bias,
|
| 35 |
+
)
|
| 36 |
+
self.depthwise_conv = nn.Conv1d(
|
| 37 |
+
channels,
|
| 38 |
+
channels,
|
| 39 |
+
kernel_size,
|
| 40 |
+
stride=1,
|
| 41 |
+
padding=(kernel_size - 1) // 2,
|
| 42 |
+
groups=channels,
|
| 43 |
+
bias=bias,
|
| 44 |
+
)
|
| 45 |
+
self.norm = nn.BatchNorm1d(channels)
|
| 46 |
+
self.pointwise_conv2 = nn.Conv1d(
|
| 47 |
+
channels,
|
| 48 |
+
channels,
|
| 49 |
+
kernel_size=1,
|
| 50 |
+
stride=1,
|
| 51 |
+
padding=0,
|
| 52 |
+
bias=bias,
|
| 53 |
+
)
|
| 54 |
+
self.activation = activation
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
"""Compute convolution module.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
x (torch.Tensor): Input tensor (#batch, time, channels).
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
torch.Tensor: Output tensor (#batch, time, channels).
|
| 64 |
+
|
| 65 |
+
"""
|
| 66 |
+
# exchange the temporal dimension and the feature dimension
|
| 67 |
+
x = x.transpose(1, 2)
|
| 68 |
+
|
| 69 |
+
# GLU mechanism
|
| 70 |
+
x = self.pointwise_conv1(x) # (batch, 2*channel, dim)
|
| 71 |
+
x = nn.functional.glu(x, dim=1) # (batch, channel, dim)
|
| 72 |
+
|
| 73 |
+
# 1D Depthwise Conv
|
| 74 |
+
x = self.depthwise_conv(x)
|
| 75 |
+
x = self.activation(self.norm(x))
|
| 76 |
+
|
| 77 |
+
x = self.pointwise_conv2(x)
|
| 78 |
+
|
| 79 |
+
return x.transpose(1, 2)
|
lscodec/models/conformer/decoder.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 2 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 3 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 4 |
+
|
| 5 |
+
"""Encoder definition."""
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from lscodec.models.conformer.convolution import ConvolutionModule
|
| 11 |
+
from lscodec.models.conformer.decoder_layer import DecoderLayer
|
| 12 |
+
from lscodec.models.conformer.nets_utils import get_activation
|
| 13 |
+
from lscodec.models.conformer.vgg2l import VGG2L
|
| 14 |
+
from lscodec.models.conformer.attention import (
|
| 15 |
+
MultiHeadedAttention, # noqa: H301
|
| 16 |
+
RelPositionMultiHeadedAttention, # noqa: H301
|
| 17 |
+
LegacyRelPositionMultiHeadedAttention, # noqa: H301
|
| 18 |
+
)
|
| 19 |
+
from lscodec.models.conformer.embedding import (
|
| 20 |
+
PositionalEncoding, # noqa: H301
|
| 21 |
+
ScaledPositionalEncoding, # noqa: H301
|
| 22 |
+
RelPositionalEncoding, # noqa: H301
|
| 23 |
+
LegacyRelPositionalEncoding, # noqa: H301
|
| 24 |
+
)
|
| 25 |
+
from lscodec.models.conformer.layer_norm import LayerNorm
|
| 26 |
+
from lscodec.models.conformer.multi_layer_conv import Conv1dLinear, MultiLayeredConv1d
|
| 27 |
+
from lscodec.models.conformer.positionwise_feed_forward import (
|
| 28 |
+
PositionwiseFeedForward, # noqa: H301
|
| 29 |
+
)
|
| 30 |
+
from lscodec.models.conformer.repeat import repeat
|
| 31 |
+
from lscodec.models.conformer.subsampling import Conv2dSubsampling
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class Decoder(torch.nn.Module):
|
| 35 |
+
"""Conformer encoder module.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
idim (int): Input dimension.
|
| 39 |
+
attention_dim (int): Dimention of attention.
|
| 40 |
+
attention_heads (int): The number of heads of multi head attention.
|
| 41 |
+
linear_units (int): The number of units of position-wise feed forward.
|
| 42 |
+
num_blocks (int): The number of decoder blocks.
|
| 43 |
+
dropout_rate (float): Dropout rate.
|
| 44 |
+
positional_dropout_rate (float): Dropout rate after adding positional encoding.
|
| 45 |
+
attention_dropout_rate (float): Dropout rate in attention.
|
| 46 |
+
input_layer (Union[str, torch.nn.Module]): Input layer type.
|
| 47 |
+
normalize_before (bool): Whether to use layer_norm before the first block.
|
| 48 |
+
concat_after (bool): Whether to concat attention layer's input and output.
|
| 49 |
+
if True, additional linear will be applied.
|
| 50 |
+
i.e. x -> x + linear(concat(x, att(x)))
|
| 51 |
+
if False, no additional linear will be applied. i.e. x -> x + att(x)
|
| 52 |
+
positionwise_layer_type (str): "linear", "conv1d", or "conv1d-linear".
|
| 53 |
+
positionwise_conv_kernel_size (int): Kernel size of positionwise conv1d layer.
|
| 54 |
+
macaron_style (bool): Whether to use macaron style for positionwise layer.
|
| 55 |
+
pos_enc_layer_type (str): Encoder positional encoding layer type.
|
| 56 |
+
selfattention_layer_type (str): Encoder attention layer type.
|
| 57 |
+
activation_type (str): Encoder activation function type.
|
| 58 |
+
use_cnn_module (bool): Whether to use convolution module.
|
| 59 |
+
zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
|
| 60 |
+
cnn_module_kernel (int): Kernerl size of convolution module.
|
| 61 |
+
padding_idx (int): Padding idx for input_layer=embed.
|
| 62 |
+
|
| 63 |
+
"""
|
| 64 |
+
|
| 65 |
+
def __init__(
|
| 66 |
+
self,
|
| 67 |
+
idim,
|
| 68 |
+
attention_dim=256,
|
| 69 |
+
attention_heads=4,
|
| 70 |
+
linear_units=2048,
|
| 71 |
+
num_blocks=6,
|
| 72 |
+
dropout_rate=0.1,
|
| 73 |
+
positional_dropout_rate=0.1,
|
| 74 |
+
attention_dropout_rate=0.0,
|
| 75 |
+
input_layer="conv2d",
|
| 76 |
+
normalize_before=True,
|
| 77 |
+
concat_after=False,
|
| 78 |
+
positionwise_layer_type="linear",
|
| 79 |
+
positionwise_conv_kernel_size=1,
|
| 80 |
+
macaron_style=False,
|
| 81 |
+
pos_enc_layer_type="abs_pos",
|
| 82 |
+
selfattention_layer_type="selfattn",
|
| 83 |
+
activation_type="swish",
|
| 84 |
+
use_cnn_module=False,
|
| 85 |
+
zero_triu=False,
|
| 86 |
+
cnn_module_kernel=31,
|
| 87 |
+
padding_idx=-1,
|
| 88 |
+
):
|
| 89 |
+
"""Construct an Encoder object."""
|
| 90 |
+
super(Decoder, self).__init__()
|
| 91 |
+
|
| 92 |
+
activation = get_activation(activation_type)
|
| 93 |
+
if pos_enc_layer_type == "abs_pos":
|
| 94 |
+
pos_enc_class = PositionalEncoding
|
| 95 |
+
elif pos_enc_layer_type == "scaled_abs_pos":
|
| 96 |
+
pos_enc_class = ScaledPositionalEncoding
|
| 97 |
+
elif pos_enc_layer_type == "rel_pos":
|
| 98 |
+
assert selfattention_layer_type == "rel_selfattn"
|
| 99 |
+
pos_enc_class = RelPositionalEncoding
|
| 100 |
+
elif pos_enc_layer_type == "legacy_rel_pos":
|
| 101 |
+
pos_enc_class = LegacyRelPositionalEncoding
|
| 102 |
+
assert selfattention_layer_type == "legacy_rel_selfattn"
|
| 103 |
+
else:
|
| 104 |
+
raise ValueError("unknown pos_enc_layer: " + pos_enc_layer_type)
|
| 105 |
+
|
| 106 |
+
self.conv_subsampling_factor = 1
|
| 107 |
+
if input_layer == "linear":
|
| 108 |
+
self.embed = torch.nn.Sequential(
|
| 109 |
+
torch.nn.Linear(idim, attention_dim),
|
| 110 |
+
torch.nn.LayerNorm(attention_dim),
|
| 111 |
+
torch.nn.Dropout(dropout_rate),
|
| 112 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 113 |
+
)
|
| 114 |
+
elif input_layer == "conv2d":
|
| 115 |
+
self.embed = Conv2dSubsampling(
|
| 116 |
+
idim,
|
| 117 |
+
attention_dim,
|
| 118 |
+
dropout_rate,
|
| 119 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 120 |
+
)
|
| 121 |
+
self.conv_subsampling_factor = 4
|
| 122 |
+
elif input_layer == "vgg2l":
|
| 123 |
+
self.embed = VGG2L(idim, attention_dim)
|
| 124 |
+
self.conv_subsampling_factor = 4
|
| 125 |
+
elif input_layer == "embed":
|
| 126 |
+
self.embed = torch.nn.Sequential(
|
| 127 |
+
torch.nn.Embedding(idim, attention_dim, padding_idx=padding_idx),
|
| 128 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 129 |
+
)
|
| 130 |
+
elif isinstance(input_layer, torch.nn.Module):
|
| 131 |
+
self.embed = torch.nn.Sequential(
|
| 132 |
+
input_layer,
|
| 133 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 134 |
+
)
|
| 135 |
+
elif input_layer is None:
|
| 136 |
+
self.embed = torch.nn.Sequential(
|
| 137 |
+
pos_enc_class(attention_dim, positional_dropout_rate)
|
| 138 |
+
)
|
| 139 |
+
else:
|
| 140 |
+
raise ValueError("unknown input_layer: " + input_layer)
|
| 141 |
+
self.normalize_before = normalize_before
|
| 142 |
+
|
| 143 |
+
# self-attention module definition
|
| 144 |
+
if selfattention_layer_type == "selfattn":
|
| 145 |
+
logging.info("encoder self-attention layer type = self-attention")
|
| 146 |
+
encoder_selfattn_layer = MultiHeadedAttention
|
| 147 |
+
encoder_selfattn_layer_args = (
|
| 148 |
+
attention_heads,
|
| 149 |
+
attention_dim,
|
| 150 |
+
attention_dropout_rate,
|
| 151 |
+
)
|
| 152 |
+
elif selfattention_layer_type == "legacy_rel_selfattn":
|
| 153 |
+
assert pos_enc_layer_type == "legacy_rel_pos"
|
| 154 |
+
encoder_selfattn_layer = LegacyRelPositionMultiHeadedAttention
|
| 155 |
+
encoder_selfattn_layer_args = (
|
| 156 |
+
attention_heads,
|
| 157 |
+
attention_dim,
|
| 158 |
+
attention_dropout_rate,
|
| 159 |
+
)
|
| 160 |
+
elif selfattention_layer_type == "rel_selfattn":
|
| 161 |
+
logging.info("encoder self-attention layer type = relative self-attention")
|
| 162 |
+
assert pos_enc_layer_type == "rel_pos"
|
| 163 |
+
encoder_selfattn_layer = RelPositionMultiHeadedAttention
|
| 164 |
+
encoder_selfattn_layer_args = (
|
| 165 |
+
attention_heads,
|
| 166 |
+
attention_dim,
|
| 167 |
+
attention_dropout_rate,
|
| 168 |
+
zero_triu,
|
| 169 |
+
)
|
| 170 |
+
else:
|
| 171 |
+
raise ValueError("unknown encoder_attn_layer: " + selfattention_layer_type)
|
| 172 |
+
|
| 173 |
+
# feed-forward module definition
|
| 174 |
+
if positionwise_layer_type == "linear":
|
| 175 |
+
positionwise_layer = PositionwiseFeedForward
|
| 176 |
+
positionwise_layer_args = (
|
| 177 |
+
attention_dim,
|
| 178 |
+
linear_units,
|
| 179 |
+
dropout_rate,
|
| 180 |
+
activation,
|
| 181 |
+
)
|
| 182 |
+
elif positionwise_layer_type == "conv1d":
|
| 183 |
+
positionwise_layer = MultiLayeredConv1d
|
| 184 |
+
positionwise_layer_args = (
|
| 185 |
+
attention_dim,
|
| 186 |
+
linear_units,
|
| 187 |
+
positionwise_conv_kernel_size,
|
| 188 |
+
dropout_rate,
|
| 189 |
+
)
|
| 190 |
+
elif positionwise_layer_type == "conv1d-linear":
|
| 191 |
+
positionwise_layer = Conv1dLinear
|
| 192 |
+
positionwise_layer_args = (
|
| 193 |
+
attention_dim,
|
| 194 |
+
linear_units,
|
| 195 |
+
positionwise_conv_kernel_size,
|
| 196 |
+
dropout_rate,
|
| 197 |
+
)
|
| 198 |
+
else:
|
| 199 |
+
raise NotImplementedError("Support only linear or conv1d.")
|
| 200 |
+
|
| 201 |
+
# convolution module definition
|
| 202 |
+
convolution_layer = ConvolutionModule
|
| 203 |
+
convolution_layer_args = (attention_dim, cnn_module_kernel, activation)
|
| 204 |
+
|
| 205 |
+
self.decoders = repeat(
|
| 206 |
+
num_blocks,
|
| 207 |
+
lambda lnum: DecoderLayer(
|
| 208 |
+
attention_dim,
|
| 209 |
+
encoder_selfattn_layer(*encoder_selfattn_layer_args),
|
| 210 |
+
MultiHeadedAttention(
|
| 211 |
+
attention_heads, attention_dim, attention_dropout_rate
|
| 212 |
+
),
|
| 213 |
+
positionwise_layer(*positionwise_layer_args),
|
| 214 |
+
positionwise_layer(*positionwise_layer_args) if macaron_style else None,
|
| 215 |
+
convolution_layer(*convolution_layer_args) if use_cnn_module else None,
|
| 216 |
+
dropout_rate,
|
| 217 |
+
normalize_before,
|
| 218 |
+
concat_after,
|
| 219 |
+
),
|
| 220 |
+
)
|
| 221 |
+
if self.normalize_before:
|
| 222 |
+
self.after_norm = LayerNorm(attention_dim)
|
| 223 |
+
|
| 224 |
+
def forward(self, xs, masks, memory, memory_mask):
|
| 225 |
+
"""Encode input sequence.
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
xs (torch.Tensor): Input tensor (#batch, time, idim).
|
| 229 |
+
masks (torch.Tensor): Mask tensor (#batch, time).
|
| 230 |
+
|
| 231 |
+
Returns:
|
| 232 |
+
torch.Tensor: Output tensor (#batch, time, attention_dim).
|
| 233 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 234 |
+
|
| 235 |
+
"""
|
| 236 |
+
if isinstance(self.embed, (Conv2dSubsampling, VGG2L)):
|
| 237 |
+
xs, masks = self.embed(xs, masks)
|
| 238 |
+
else:
|
| 239 |
+
xs = self.embed(xs)
|
| 240 |
+
|
| 241 |
+
xs, masks, memory, memory_mask = self.decoders(xs, masks, memory, memory_mask)
|
| 242 |
+
if isinstance(xs, tuple):
|
| 243 |
+
xs = xs[0]
|
| 244 |
+
|
| 245 |
+
if self.normalize_before:
|
| 246 |
+
xs = self.after_norm(xs)
|
| 247 |
+
return xs, masks
|
lscodec/models/conformer/decoder_layer.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 5 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 6 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 7 |
+
|
| 8 |
+
"""Encoder self-attention layer definition."""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
from lscodec.models.conformer.layer_norm import LayerNorm
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DecoderLayer(nn.Module):
|
| 18 |
+
"""Encoder layer module.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
size (int): Input dimension.
|
| 22 |
+
self_attn (torch.nn.Module): Self-attention module instance.
|
| 23 |
+
`MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
|
| 24 |
+
can be used as the argument.
|
| 25 |
+
feed_forward (torch.nn.Module): Feed-forward module instance.
|
| 26 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 27 |
+
can be used as the argument.
|
| 28 |
+
feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
|
| 29 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 30 |
+
can be used as the argument.
|
| 31 |
+
conv_module (torch.nn.Module): Convolution module instance.
|
| 32 |
+
`ConvlutionModule` instance can be used as the argument.
|
| 33 |
+
dropout_rate (float): Dropout rate.
|
| 34 |
+
normalize_before (bool): Whether to use layer_norm before the first block.
|
| 35 |
+
concat_after (bool): Whether to concat attention layer's input and output.
|
| 36 |
+
if True, additional linear will be applied.
|
| 37 |
+
i.e. x -> x + linear(concat(x, att(x)))
|
| 38 |
+
if False, no additional linear will be applied. i.e. x -> x + att(x)
|
| 39 |
+
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
size,
|
| 45 |
+
self_attn,
|
| 46 |
+
src_attn,
|
| 47 |
+
feed_forward,
|
| 48 |
+
feed_forward_macaron,
|
| 49 |
+
conv_module,
|
| 50 |
+
dropout_rate,
|
| 51 |
+
normalize_before=True,
|
| 52 |
+
concat_after=False,
|
| 53 |
+
):
|
| 54 |
+
"""Construct an EncoderLayer object."""
|
| 55 |
+
super(DecoderLayer, self).__init__()
|
| 56 |
+
self.self_attn = self_attn
|
| 57 |
+
self.src_attn = src_attn
|
| 58 |
+
self.feed_forward = feed_forward
|
| 59 |
+
self.feed_forward_macaron = feed_forward_macaron
|
| 60 |
+
self.conv_module = conv_module
|
| 61 |
+
self.norm_ff = LayerNorm(size) # for the FNN module
|
| 62 |
+
self.norm_mha = LayerNorm(size) # for the MHA module
|
| 63 |
+
self.norm2 = LayerNorm(size) # for the MHA module
|
| 64 |
+
if feed_forward_macaron is not None:
|
| 65 |
+
self.norm_ff_macaron = LayerNorm(size)
|
| 66 |
+
self.ff_scale = 0.5
|
| 67 |
+
else:
|
| 68 |
+
self.ff_scale = 1.0
|
| 69 |
+
if self.conv_module is not None:
|
| 70 |
+
self.norm_conv = LayerNorm(size) # for the CNN module
|
| 71 |
+
self.norm_final = LayerNorm(size) # for the final output of the block
|
| 72 |
+
self.dropout = nn.Dropout(dropout_rate)
|
| 73 |
+
self.size = size
|
| 74 |
+
self.normalize_before = normalize_before
|
| 75 |
+
self.concat_after = concat_after
|
| 76 |
+
if self.concat_after:
|
| 77 |
+
self.concat_linear = nn.Linear(size + size, size)
|
| 78 |
+
self.concat_linear2 = nn.Linear(size + size, size)
|
| 79 |
+
|
| 80 |
+
def forward(self, x_input, mask, memory, memory_mask, cache=None):
|
| 81 |
+
"""Compute encoded features.
|
| 82 |
+
|
| 83 |
+
Args:
|
| 84 |
+
x_input (Union[Tuple, torch.Tensor]): Input tensor w/ or w/o pos emb.
|
| 85 |
+
- w/ pos emb: Tuple of tensors [(#batch, time, size), (1, time, size)].
|
| 86 |
+
- w/o pos emb: Tensor (#batch, time, size).
|
| 87 |
+
mask (torch.Tensor): Mask tensor for the input (#batch, time).
|
| 88 |
+
cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
torch.Tensor: Output tensor (#batch, time, size).
|
| 92 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 93 |
+
|
| 94 |
+
"""
|
| 95 |
+
if isinstance(x_input, tuple):
|
| 96 |
+
x, pos_emb = x_input[0], x_input[1]
|
| 97 |
+
else:
|
| 98 |
+
x, pos_emb = x_input, None
|
| 99 |
+
|
| 100 |
+
# whether to use macaron style
|
| 101 |
+
if self.feed_forward_macaron is not None:
|
| 102 |
+
residual = x
|
| 103 |
+
if self.normalize_before:
|
| 104 |
+
x = self.norm_ff_macaron(x)
|
| 105 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
|
| 106 |
+
if not self.normalize_before:
|
| 107 |
+
x = self.norm_ff_macaron(x)
|
| 108 |
+
|
| 109 |
+
# multi-headed self-attention module
|
| 110 |
+
residual = x
|
| 111 |
+
if self.normalize_before:
|
| 112 |
+
x = self.norm_mha(x)
|
| 113 |
+
|
| 114 |
+
if cache is None:
|
| 115 |
+
x_q = x
|
| 116 |
+
else:
|
| 117 |
+
assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
|
| 118 |
+
x_q = x[:, -1:, :]
|
| 119 |
+
residual = residual[:, -1:, :]
|
| 120 |
+
mask = None if mask is None else mask[:, -1:, :]
|
| 121 |
+
|
| 122 |
+
if pos_emb is not None:
|
| 123 |
+
x_att = self.self_attn(x_q, x, x, pos_emb, mask)
|
| 124 |
+
else:
|
| 125 |
+
x_att = self.self_attn(x_q, x, x, mask)
|
| 126 |
+
|
| 127 |
+
if self.concat_after:
|
| 128 |
+
x_concat = torch.cat((x, x_att), dim=-1)
|
| 129 |
+
x = residual + self.concat_linear(x_concat)
|
| 130 |
+
else:
|
| 131 |
+
x = residual + self.dropout(x_att)
|
| 132 |
+
if not self.normalize_before:
|
| 133 |
+
x = self.norm_mha(x)
|
| 134 |
+
|
| 135 |
+
# cross attention
|
| 136 |
+
residual = x
|
| 137 |
+
if self.normalize_before:
|
| 138 |
+
x = self.norm2(x)
|
| 139 |
+
if self.concat_after:
|
| 140 |
+
x_concat = torch.cat(
|
| 141 |
+
(x, self.src_attn(x, memory, memory, memory_mask)), dim=-1
|
| 142 |
+
)
|
| 143 |
+
x = residual + self.concat_linear2(x_concat)
|
| 144 |
+
else:
|
| 145 |
+
x = residual + self.dropout(self.src_attn(x, memory, memory, memory_mask))
|
| 146 |
+
if not self.normalize_before:
|
| 147 |
+
x = self.norm2(x)
|
| 148 |
+
|
| 149 |
+
# convolution module
|
| 150 |
+
if self.conv_module is not None:
|
| 151 |
+
residual = x
|
| 152 |
+
if self.normalize_before:
|
| 153 |
+
x = self.norm_conv(x)
|
| 154 |
+
x = residual + self.dropout(self.conv_module(x))
|
| 155 |
+
if not self.normalize_before:
|
| 156 |
+
x = self.norm_conv(x)
|
| 157 |
+
|
| 158 |
+
# feed forward module
|
| 159 |
+
residual = x
|
| 160 |
+
if self.normalize_before:
|
| 161 |
+
x = self.norm_ff(x)
|
| 162 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
|
| 163 |
+
if not self.normalize_before:
|
| 164 |
+
x = self.norm_ff(x)
|
| 165 |
+
|
| 166 |
+
if self.conv_module is not None:
|
| 167 |
+
x = self.norm_final(x)
|
| 168 |
+
|
| 169 |
+
if cache is not None:
|
| 170 |
+
x = torch.cat([cache, x], dim=1)
|
| 171 |
+
|
| 172 |
+
if pos_emb is not None:
|
| 173 |
+
return (x, pos_emb), mask, memory, memory_mask
|
| 174 |
+
|
| 175 |
+
return x, mask, memory, memory_mask
|
lscodec/models/conformer/embedding.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Positional Encoding Module."""
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _pre_hook(
|
| 15 |
+
state_dict,
|
| 16 |
+
prefix,
|
| 17 |
+
local_metadata,
|
| 18 |
+
strict,
|
| 19 |
+
missing_keys,
|
| 20 |
+
unexpected_keys,
|
| 21 |
+
error_msgs,
|
| 22 |
+
):
|
| 23 |
+
"""Perform pre-hook in load_state_dict for backward compatibility.
|
| 24 |
+
|
| 25 |
+
Note:
|
| 26 |
+
We saved self.pe until v.0.5.2 but we have omitted it later.
|
| 27 |
+
Therefore, we remove the item "pe" from `state_dict` for backward compatibility.
|
| 28 |
+
|
| 29 |
+
"""
|
| 30 |
+
k = prefix + "pe"
|
| 31 |
+
if k in state_dict:
|
| 32 |
+
state_dict.pop(k)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class PositionalEncoding(torch.nn.Module):
|
| 36 |
+
"""Positional encoding.
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
d_model (int): Embedding dimension.
|
| 40 |
+
dropout_rate (float): Dropout rate.
|
| 41 |
+
max_len (int): Maximum input length.
|
| 42 |
+
reverse (bool): Whether to reverse the input position. Only for
|
| 43 |
+
the class LegacyRelPositionalEncoding. We remove it in the current
|
| 44 |
+
class RelPositionalEncoding.
|
| 45 |
+
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(self, d_model, dropout_rate, max_len=5000, reverse=False):
|
| 49 |
+
"""Construct an PositionalEncoding object."""
|
| 50 |
+
super(PositionalEncoding, self).__init__()
|
| 51 |
+
self.d_model = d_model
|
| 52 |
+
self.reverse = reverse
|
| 53 |
+
self.xscale = math.sqrt(self.d_model)
|
| 54 |
+
self.dropout = torch.nn.Dropout(p=dropout_rate)
|
| 55 |
+
self.pe = None
|
| 56 |
+
self.extend_pe(torch.tensor(0.0).expand(1, max_len))
|
| 57 |
+
self._register_load_state_dict_pre_hook(_pre_hook)
|
| 58 |
+
|
| 59 |
+
def extend_pe(self, x):
|
| 60 |
+
"""Reset the positional encodings."""
|
| 61 |
+
if self.pe is not None:
|
| 62 |
+
if self.pe.size(1) >= x.size(1):
|
| 63 |
+
if self.pe.dtype != x.dtype or self.pe.device != x.device:
|
| 64 |
+
self.pe = self.pe.to(dtype=x.dtype, device=x.device)
|
| 65 |
+
return
|
| 66 |
+
pe = torch.zeros(x.size(1), self.d_model)
|
| 67 |
+
if self.reverse:
|
| 68 |
+
position = torch.arange(
|
| 69 |
+
x.size(1) - 1, -1, -1.0, dtype=torch.float32
|
| 70 |
+
).unsqueeze(1)
|
| 71 |
+
else:
|
| 72 |
+
position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
|
| 73 |
+
div_term = torch.exp(
|
| 74 |
+
torch.arange(0, self.d_model, 2, dtype=torch.float32)
|
| 75 |
+
* -(math.log(10000.0) / self.d_model)
|
| 76 |
+
)
|
| 77 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 78 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 79 |
+
pe = pe.unsqueeze(0)
|
| 80 |
+
self.pe = pe.to(device=x.device, dtype=x.dtype)
|
| 81 |
+
|
| 82 |
+
def forward(self, x: torch.Tensor):
|
| 83 |
+
"""Add positional encoding.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
x (torch.Tensor): Input tensor (batch, time, `*`).
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
torch.Tensor: Encoded tensor (batch, time, `*`).
|
| 90 |
+
|
| 91 |
+
"""
|
| 92 |
+
self.extend_pe(x)
|
| 93 |
+
x = x * self.xscale + self.pe[:, : x.size(1)]
|
| 94 |
+
return self.dropout(x)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class ScaledPositionalEncoding(PositionalEncoding):
|
| 98 |
+
"""Scaled positional encoding module.
|
| 99 |
+
|
| 100 |
+
See Sec. 3.2 https://arxiv.org/abs/1809.08895
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
d_model (int): Embedding dimension.
|
| 104 |
+
dropout_rate (float): Dropout rate.
|
| 105 |
+
max_len (int): Maximum input length.
|
| 106 |
+
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
def __init__(self, d_model, dropout_rate, max_len=5000):
|
| 110 |
+
"""Initialize class."""
|
| 111 |
+
super().__init__(d_model=d_model, dropout_rate=dropout_rate, max_len=max_len)
|
| 112 |
+
self.alpha = torch.nn.Parameter(torch.tensor(1.0))
|
| 113 |
+
|
| 114 |
+
def reset_parameters(self):
|
| 115 |
+
"""Reset parameters."""
|
| 116 |
+
self.alpha.data = torch.tensor(1.0)
|
| 117 |
+
|
| 118 |
+
def forward(self, x):
|
| 119 |
+
"""Add positional encoding.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
x (torch.Tensor): Input tensor (batch, time, `*`).
|
| 123 |
+
|
| 124 |
+
Returns:
|
| 125 |
+
torch.Tensor: Encoded tensor (batch, time, `*`).
|
| 126 |
+
|
| 127 |
+
"""
|
| 128 |
+
self.extend_pe(x)
|
| 129 |
+
x = x + self.alpha * self.pe[:, : x.size(1)]
|
| 130 |
+
return self.dropout(x)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class LegacyRelPositionalEncoding(PositionalEncoding):
|
| 134 |
+
"""Relative positional encoding module (old version).
|
| 135 |
+
|
| 136 |
+
Details can be found in https://github.com/espnet/espnet/pull/2816.
|
| 137 |
+
|
| 138 |
+
See : Appendix B in https://arxiv.org/abs/1901.02860
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
d_model (int): Embedding dimension.
|
| 142 |
+
dropout_rate (float): Dropout rate.
|
| 143 |
+
max_len (int): Maximum input length.
|
| 144 |
+
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
def __init__(self, d_model, dropout_rate, max_len=5000):
|
| 148 |
+
"""Initialize class."""
|
| 149 |
+
super().__init__(
|
| 150 |
+
d_model=d_model,
|
| 151 |
+
dropout_rate=dropout_rate,
|
| 152 |
+
max_len=max_len,
|
| 153 |
+
reverse=True,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
def forward(self, x):
|
| 157 |
+
"""Compute positional encoding.
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
x (torch.Tensor): Input tensor (batch, time, `*`).
|
| 161 |
+
|
| 162 |
+
Returns:
|
| 163 |
+
torch.Tensor: Encoded tensor (batch, time, `*`).
|
| 164 |
+
torch.Tensor: Positional embedding tensor (1, time, `*`).
|
| 165 |
+
|
| 166 |
+
"""
|
| 167 |
+
self.extend_pe(x)
|
| 168 |
+
x = x * self.xscale
|
| 169 |
+
pos_emb = self.pe[:, : x.size(1)]
|
| 170 |
+
return self.dropout(x), self.dropout(pos_emb)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class RelPositionalEncoding(torch.nn.Module):
|
| 174 |
+
"""Relative positional encoding module (new implementation).
|
| 175 |
+
|
| 176 |
+
Details can be found in https://github.com/espnet/espnet/pull/2816.
|
| 177 |
+
|
| 178 |
+
See : Appendix B in https://arxiv.org/abs/1901.02860
|
| 179 |
+
|
| 180 |
+
Args:
|
| 181 |
+
d_model (int): Embedding dimension.
|
| 182 |
+
dropout_rate (float): Dropout rate.
|
| 183 |
+
max_len (int): Maximum input length.
|
| 184 |
+
|
| 185 |
+
"""
|
| 186 |
+
|
| 187 |
+
def __init__(self, d_model, dropout_rate, max_len=5000):
|
| 188 |
+
"""Construct an PositionalEncoding object."""
|
| 189 |
+
super(RelPositionalEncoding, self).__init__()
|
| 190 |
+
self.d_model = d_model
|
| 191 |
+
self.xscale = math.sqrt(self.d_model)
|
| 192 |
+
self.dropout = torch.nn.Dropout(p=dropout_rate)
|
| 193 |
+
self.pe = None
|
| 194 |
+
self.extend_pe(torch.tensor(0.0).expand(1, max_len))
|
| 195 |
+
|
| 196 |
+
def extend_pe(self, x):
|
| 197 |
+
"""Reset the positional encodings."""
|
| 198 |
+
if self.pe is not None:
|
| 199 |
+
# self.pe contains both positive and negative parts
|
| 200 |
+
# the length of self.pe is 2 * input_len - 1
|
| 201 |
+
if self.pe.size(1) >= x.size(1) * 2 - 1:
|
| 202 |
+
if self.pe.dtype != x.dtype or self.pe.device != x.device:
|
| 203 |
+
self.pe = self.pe.to(dtype=x.dtype, device=x.device)
|
| 204 |
+
return
|
| 205 |
+
# Suppose `i` means to the position of query vecotr and `j` means the
|
| 206 |
+
# position of key vector. We use position relative positions when keys
|
| 207 |
+
# are to the left (i>j) and negative relative positions otherwise (i<j).
|
| 208 |
+
pe_positive = torch.zeros(x.size(1), self.d_model)
|
| 209 |
+
pe_negative = torch.zeros(x.size(1), self.d_model)
|
| 210 |
+
position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
|
| 211 |
+
div_term = torch.exp(
|
| 212 |
+
torch.arange(0, self.d_model, 2, dtype=torch.float32)
|
| 213 |
+
* -(math.log(10000.0) / self.d_model)
|
| 214 |
+
)
|
| 215 |
+
pe_positive[:, 0::2] = torch.sin(position * div_term)
|
| 216 |
+
pe_positive[:, 1::2] = torch.cos(position * div_term)
|
| 217 |
+
pe_negative[:, 0::2] = torch.sin(-1 * position * div_term)
|
| 218 |
+
pe_negative[:, 1::2] = torch.cos(-1 * position * div_term)
|
| 219 |
+
|
| 220 |
+
# Reserve the order of positive indices and concat both positive and
|
| 221 |
+
# negative indices. This is used to support the shifting trick
|
| 222 |
+
# as in https://arxiv.org/abs/1901.02860
|
| 223 |
+
pe_positive = torch.flip(pe_positive, [0]).unsqueeze(0)
|
| 224 |
+
pe_negative = pe_negative[1:].unsqueeze(0)
|
| 225 |
+
pe = torch.cat([pe_positive, pe_negative], dim=1)
|
| 226 |
+
self.pe = pe.to(device=x.device, dtype=x.dtype)
|
| 227 |
+
|
| 228 |
+
def forward(self, x: torch.Tensor):
|
| 229 |
+
"""Add positional encoding.
|
| 230 |
+
|
| 231 |
+
Args:
|
| 232 |
+
x (torch.Tensor): Input tensor (batch, time, `*`).
|
| 233 |
+
|
| 234 |
+
Returns:
|
| 235 |
+
torch.Tensor: Encoded tensor (batch, time, `*`).
|
| 236 |
+
|
| 237 |
+
"""
|
| 238 |
+
self.extend_pe(x)
|
| 239 |
+
x = x * self.xscale
|
| 240 |
+
pos_emb = self.pe[
|
| 241 |
+
:,
|
| 242 |
+
self.pe.size(1) // 2 - x.size(1) + 1 : self.pe.size(1) // 2 + x.size(1),
|
| 243 |
+
]
|
| 244 |
+
return self.dropout(x), self.dropout(pos_emb)
|
lscodec/models/conformer/encoder.py
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 2 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 3 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 4 |
+
|
| 5 |
+
"""Encoder definition."""
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import torch
|
| 9 |
+
from lscodec.models.conformer.convolution import ConvolutionModule
|
| 10 |
+
from lscodec.models.conformer.encoder_layer import EncoderLayer
|
| 11 |
+
from lscodec.utils.espnet_utils import get_activation
|
| 12 |
+
from lscodec.models.conformer.vgg2l import VGG2L
|
| 13 |
+
from lscodec.models.conformer.attention import (
|
| 14 |
+
MultiHeadedAttention, # noqa: H301
|
| 15 |
+
RelPositionMultiHeadedAttention, # noqa: H301
|
| 16 |
+
LegacyRelPositionMultiHeadedAttention, # noqa: H301
|
| 17 |
+
)
|
| 18 |
+
from lscodec.models.conformer.embedding import (
|
| 19 |
+
PositionalEncoding, # noqa: H301
|
| 20 |
+
ScaledPositionalEncoding, # noqa: H301
|
| 21 |
+
RelPositionalEncoding, # noqa: H301
|
| 22 |
+
LegacyRelPositionalEncoding, # noqa: H301
|
| 23 |
+
)
|
| 24 |
+
from lscodec.models.conformer.layer_norm import LayerNorm
|
| 25 |
+
from lscodec.models.conformer.multi_layer_conv import Conv1dLinear
|
| 26 |
+
from lscodec.models.conformer.multi_layer_conv import MultiLayeredConv1d
|
| 27 |
+
from lscodec.models.conformer.positionwise_feed_forward import (
|
| 28 |
+
PositionwiseFeedForward, # noqa: H301
|
| 29 |
+
)
|
| 30 |
+
from lscodec.models.conformer.repeat import repeat
|
| 31 |
+
from lscodec.models.conformer.subsampling import Conv2dSubsampling
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Encoder(torch.nn.Module):
|
| 36 |
+
"""Conformer encoder module.
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
idim (int): Input dimension.
|
| 40 |
+
attention_dim (int): Dimention of attention.
|
| 41 |
+
attention_heads (int): The number of heads of multi head attention.
|
| 42 |
+
linear_units (int): The number of units of position-wise feed forward.
|
| 43 |
+
num_blocks (int): The number of decoder blocks.
|
| 44 |
+
dropout_rate (float): Dropout rate.
|
| 45 |
+
positional_dropout_rate (float): Dropout rate after adding positional encoding.
|
| 46 |
+
attention_dropout_rate (float): Dropout rate in attention.
|
| 47 |
+
input_layer (Union[str, torch.nn.Module]): Input layer type.
|
| 48 |
+
normalize_before (bool): Whether to use layer_norm before the first block.
|
| 49 |
+
concat_after (bool): Whether to concat attention layer's input and output.
|
| 50 |
+
if True, additional linear will be applied.
|
| 51 |
+
i.e. x -> x + linear(concat(x, att(x)))
|
| 52 |
+
if False, no additional linear will be applied. i.e. x -> x + att(x)
|
| 53 |
+
positionwise_layer_type (str): "linear", "conv1d", or "conv1d-linear".
|
| 54 |
+
positionwise_conv_kernel_size (int): Kernel size of positionwise conv1d layer.
|
| 55 |
+
macaron_style (bool): Whether to use macaron style for positionwise layer.
|
| 56 |
+
pos_enc_layer_type (str): Encoder positional encoding layer type.
|
| 57 |
+
selfattention_layer_type (str): Encoder attention layer type.
|
| 58 |
+
activation_type (str): Encoder activation function type.
|
| 59 |
+
use_cnn_module (bool): Whether to use convolution module.
|
| 60 |
+
zero_triu (bool): Whether to zero the upper triangular part of attention matrix.
|
| 61 |
+
cnn_module_kernel (int): Kernerl size of convolution module.
|
| 62 |
+
padding_idx (int): Padding idx for input_layer=embed.
|
| 63 |
+
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
idim,
|
| 69 |
+
attention_dim=256,
|
| 70 |
+
attention_heads=4,
|
| 71 |
+
linear_units=2048,
|
| 72 |
+
num_blocks=6,
|
| 73 |
+
dropout_rate=0.1,
|
| 74 |
+
positional_dropout_rate=0.1,
|
| 75 |
+
attention_dropout_rate=0.0,
|
| 76 |
+
input_layer="conv2d",
|
| 77 |
+
normalize_before=True,
|
| 78 |
+
concat_after=False,
|
| 79 |
+
positionwise_layer_type="linear",
|
| 80 |
+
positionwise_conv_kernel_size=1,
|
| 81 |
+
macaron_style=False,
|
| 82 |
+
pos_enc_layer_type="abs_pos",
|
| 83 |
+
selfattention_layer_type="selfattn",
|
| 84 |
+
activation_type="swish",
|
| 85 |
+
use_cnn_module=False,
|
| 86 |
+
zero_triu=False,
|
| 87 |
+
cnn_module_kernel=31,
|
| 88 |
+
padding_idx=-1,
|
| 89 |
+
):
|
| 90 |
+
"""Construct an Encoder object."""
|
| 91 |
+
super(Encoder, self).__init__()
|
| 92 |
+
|
| 93 |
+
activation = get_activation(activation_type)
|
| 94 |
+
if pos_enc_layer_type == "abs_pos":
|
| 95 |
+
pos_enc_class = PositionalEncoding
|
| 96 |
+
elif pos_enc_layer_type == "scaled_abs_pos":
|
| 97 |
+
pos_enc_class = ScaledPositionalEncoding
|
| 98 |
+
elif pos_enc_layer_type == "rel_pos":
|
| 99 |
+
assert selfattention_layer_type == "rel_selfattn"
|
| 100 |
+
pos_enc_class = RelPositionalEncoding
|
| 101 |
+
elif pos_enc_layer_type == "legacy_rel_pos":
|
| 102 |
+
pos_enc_class = LegacyRelPositionalEncoding
|
| 103 |
+
assert selfattention_layer_type == "legacy_rel_selfattn"
|
| 104 |
+
else:
|
| 105 |
+
raise ValueError("unknown pos_enc_layer: " + pos_enc_layer_type)
|
| 106 |
+
|
| 107 |
+
self.conv_subsampling_factor = 1
|
| 108 |
+
if input_layer == "linear":
|
| 109 |
+
self.embed = torch.nn.Sequential(
|
| 110 |
+
torch.nn.Linear(idim, attention_dim),
|
| 111 |
+
torch.nn.LayerNorm(attention_dim),
|
| 112 |
+
torch.nn.Dropout(dropout_rate),
|
| 113 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 114 |
+
)
|
| 115 |
+
elif input_layer == "conv2d":
|
| 116 |
+
self.embed = Conv2dSubsampling(
|
| 117 |
+
idim,
|
| 118 |
+
attention_dim,
|
| 119 |
+
dropout_rate,
|
| 120 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 121 |
+
)
|
| 122 |
+
self.conv_subsampling_factor = 4
|
| 123 |
+
elif input_layer == "vgg2l":
|
| 124 |
+
self.embed = VGG2L(idim, attention_dim)
|
| 125 |
+
self.conv_subsampling_factor = 4
|
| 126 |
+
elif input_layer == "embed":
|
| 127 |
+
self.embed = torch.nn.Sequential(
|
| 128 |
+
torch.nn.Embedding(idim, attention_dim, padding_idx=padding_idx),
|
| 129 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 130 |
+
)
|
| 131 |
+
elif isinstance(input_layer, torch.nn.Module):
|
| 132 |
+
self.embed = torch.nn.Sequential(
|
| 133 |
+
input_layer,
|
| 134 |
+
pos_enc_class(attention_dim, positional_dropout_rate),
|
| 135 |
+
)
|
| 136 |
+
elif input_layer is None:
|
| 137 |
+
self.embed = torch.nn.Sequential(
|
| 138 |
+
pos_enc_class(attention_dim, positional_dropout_rate)
|
| 139 |
+
)
|
| 140 |
+
elif input_layer == "no_pos_emb":
|
| 141 |
+
self.embed = torch.nn.Identity()
|
| 142 |
+
else:
|
| 143 |
+
raise ValueError("unknown input_layer: " + input_layer)
|
| 144 |
+
self.normalize_before = normalize_before
|
| 145 |
+
|
| 146 |
+
# self-attention module definition
|
| 147 |
+
if selfattention_layer_type == "selfattn":
|
| 148 |
+
logging.info("encoder self-attention layer type = self-attention")
|
| 149 |
+
encoder_selfattn_layer = MultiHeadedAttention
|
| 150 |
+
encoder_selfattn_layer_args = (
|
| 151 |
+
attention_heads,
|
| 152 |
+
attention_dim,
|
| 153 |
+
attention_dropout_rate,
|
| 154 |
+
)
|
| 155 |
+
elif selfattention_layer_type == "legacy_rel_selfattn":
|
| 156 |
+
assert pos_enc_layer_type == "legacy_rel_pos"
|
| 157 |
+
encoder_selfattn_layer = LegacyRelPositionMultiHeadedAttention
|
| 158 |
+
encoder_selfattn_layer_args = (
|
| 159 |
+
attention_heads,
|
| 160 |
+
attention_dim,
|
| 161 |
+
attention_dropout_rate,
|
| 162 |
+
)
|
| 163 |
+
elif selfattention_layer_type == "rel_selfattn":
|
| 164 |
+
logging.info("encoder self-attention layer type = relative self-attention")
|
| 165 |
+
assert pos_enc_layer_type == "rel_pos"
|
| 166 |
+
encoder_selfattn_layer = RelPositionMultiHeadedAttention
|
| 167 |
+
encoder_selfattn_layer_args = (
|
| 168 |
+
attention_heads,
|
| 169 |
+
attention_dim,
|
| 170 |
+
attention_dropout_rate,
|
| 171 |
+
zero_triu,
|
| 172 |
+
)
|
| 173 |
+
else:
|
| 174 |
+
raise ValueError("unknown encoder_attn_layer: " + selfattention_layer_type)
|
| 175 |
+
|
| 176 |
+
# feed-forward module definition
|
| 177 |
+
if positionwise_layer_type == "linear":
|
| 178 |
+
positionwise_layer = PositionwiseFeedForward
|
| 179 |
+
positionwise_layer_args = (
|
| 180 |
+
attention_dim,
|
| 181 |
+
linear_units,
|
| 182 |
+
dropout_rate,
|
| 183 |
+
activation,
|
| 184 |
+
)
|
| 185 |
+
elif positionwise_layer_type == "conv1d":
|
| 186 |
+
positionwise_layer = MultiLayeredConv1d
|
| 187 |
+
positionwise_layer_args = (
|
| 188 |
+
attention_dim,
|
| 189 |
+
linear_units,
|
| 190 |
+
positionwise_conv_kernel_size,
|
| 191 |
+
dropout_rate,
|
| 192 |
+
)
|
| 193 |
+
elif positionwise_layer_type == "conv1d-linear":
|
| 194 |
+
positionwise_layer = Conv1dLinear
|
| 195 |
+
positionwise_layer_args = (
|
| 196 |
+
attention_dim,
|
| 197 |
+
linear_units,
|
| 198 |
+
positionwise_conv_kernel_size,
|
| 199 |
+
dropout_rate,
|
| 200 |
+
)
|
| 201 |
+
else:
|
| 202 |
+
raise NotImplementedError("Support only linear or conv1d.")
|
| 203 |
+
|
| 204 |
+
# convolution module definition
|
| 205 |
+
convolution_layer = ConvolutionModule
|
| 206 |
+
convolution_layer_args = (attention_dim, cnn_module_kernel, activation)
|
| 207 |
+
|
| 208 |
+
self.encoders = repeat(
|
| 209 |
+
num_blocks,
|
| 210 |
+
lambda lnum: EncoderLayer(
|
| 211 |
+
attention_dim,
|
| 212 |
+
encoder_selfattn_layer(*encoder_selfattn_layer_args),
|
| 213 |
+
positionwise_layer(*positionwise_layer_args),
|
| 214 |
+
positionwise_layer(*positionwise_layer_args) if macaron_style else None,
|
| 215 |
+
convolution_layer(*convolution_layer_args) if use_cnn_module else None,
|
| 216 |
+
dropout_rate,
|
| 217 |
+
normalize_before,
|
| 218 |
+
concat_after,
|
| 219 |
+
),
|
| 220 |
+
)
|
| 221 |
+
if self.normalize_before:
|
| 222 |
+
self.after_norm = LayerNorm(attention_dim)
|
| 223 |
+
|
| 224 |
+
def forward(self, xs, masks):
|
| 225 |
+
"""Encode input sequence.
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
xs (torch.Tensor): Input tensor (#batch, time, idim).
|
| 229 |
+
masks (torch.Tensor): Mask tensor (#batch, time).
|
| 230 |
+
|
| 231 |
+
Returns:
|
| 232 |
+
torch.Tensor: Output tensor (#batch, time, attention_dim).
|
| 233 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 234 |
+
|
| 235 |
+
"""
|
| 236 |
+
if isinstance(self.embed, (Conv2dSubsampling, VGG2L)):
|
| 237 |
+
xs, masks = self.embed(xs, masks)
|
| 238 |
+
else:
|
| 239 |
+
xs = self.embed(xs)
|
| 240 |
+
|
| 241 |
+
xs, masks = self.encoders(xs, masks)
|
| 242 |
+
if isinstance(xs, tuple):
|
| 243 |
+
xs = xs[0]
|
| 244 |
+
|
| 245 |
+
if self.normalize_before:
|
| 246 |
+
xs = self.after_norm(xs)
|
| 247 |
+
return xs, masks
|
lscodec/models/conformer/encoder_layer.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 5 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 6 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 7 |
+
|
| 8 |
+
"""Encoder self-attention layer definition."""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
from lscodec.models.conformer.layer_norm import LayerNorm
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class EncoderLayer(nn.Module):
|
| 18 |
+
"""Encoder layer module.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
size (int): Input dimension.
|
| 22 |
+
self_attn (torch.nn.Module): Self-attention module instance.
|
| 23 |
+
`MultiHeadedAttention` or `RelPositionMultiHeadedAttention` instance
|
| 24 |
+
can be used as the argument.
|
| 25 |
+
feed_forward (torch.nn.Module): Feed-forward module instance.
|
| 26 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 27 |
+
can be used as the argument.
|
| 28 |
+
feed_forward_macaron (torch.nn.Module): Additional feed-forward module instance.
|
| 29 |
+
`PositionwiseFeedForward`, `MultiLayeredConv1d`, or `Conv1dLinear` instance
|
| 30 |
+
can be used as the argument.
|
| 31 |
+
conv_module (torch.nn.Module): Convolution module instance.
|
| 32 |
+
`ConvlutionModule` instance can be used as the argument.
|
| 33 |
+
dropout_rate (float): Dropout rate.
|
| 34 |
+
normalize_before (bool): Whether to use layer_norm before the first block.
|
| 35 |
+
concat_after (bool): Whether to concat attention layer's input and output.
|
| 36 |
+
if True, additional linear will be applied.
|
| 37 |
+
i.e. x -> x + linear(concat(x, att(x)))
|
| 38 |
+
if False, no additional linear will be applied. i.e. x -> x + att(x)
|
| 39 |
+
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
size,
|
| 45 |
+
self_attn,
|
| 46 |
+
feed_forward,
|
| 47 |
+
feed_forward_macaron,
|
| 48 |
+
conv_module,
|
| 49 |
+
dropout_rate,
|
| 50 |
+
normalize_before=True,
|
| 51 |
+
concat_after=False,
|
| 52 |
+
):
|
| 53 |
+
"""Construct an EncoderLayer object."""
|
| 54 |
+
super(EncoderLayer, self).__init__()
|
| 55 |
+
self.self_attn = self_attn
|
| 56 |
+
self.feed_forward = feed_forward
|
| 57 |
+
self.feed_forward_macaron = feed_forward_macaron
|
| 58 |
+
self.conv_module = conv_module
|
| 59 |
+
self.norm_ff = LayerNorm(size) # for the FNN module
|
| 60 |
+
self.norm_mha = LayerNorm(size) # for the MHA module
|
| 61 |
+
if feed_forward_macaron is not None:
|
| 62 |
+
self.norm_ff_macaron = LayerNorm(size)
|
| 63 |
+
self.ff_scale = 0.5
|
| 64 |
+
else:
|
| 65 |
+
self.ff_scale = 1.0
|
| 66 |
+
if self.conv_module is not None:
|
| 67 |
+
self.norm_conv = LayerNorm(size) # for the CNN module
|
| 68 |
+
self.norm_final = LayerNorm(size) # for the final output of the block
|
| 69 |
+
self.dropout = nn.Dropout(dropout_rate)
|
| 70 |
+
self.size = size
|
| 71 |
+
self.normalize_before = normalize_before
|
| 72 |
+
self.concat_after = concat_after
|
| 73 |
+
if self.concat_after:
|
| 74 |
+
self.concat_linear = nn.Linear(size + size, size)
|
| 75 |
+
|
| 76 |
+
def forward(self, x_input, mask, cache=None):
|
| 77 |
+
"""Compute encoded features.
|
| 78 |
+
|
| 79 |
+
Args:
|
| 80 |
+
x_input (Union[Tuple, torch.Tensor]): Input tensor w/ or w/o pos emb.
|
| 81 |
+
- w/ pos emb: Tuple of tensors [(#batch, time, size), (1, time, size)].
|
| 82 |
+
- w/o pos emb: Tensor (#batch, time, size).
|
| 83 |
+
mask (torch.Tensor): Mask tensor for the input (#batch, time).
|
| 84 |
+
cache (torch.Tensor): Cache tensor of the input (#batch, time - 1, size).
|
| 85 |
+
|
| 86 |
+
Returns:
|
| 87 |
+
torch.Tensor: Output tensor (#batch, time, size).
|
| 88 |
+
torch.Tensor: Mask tensor (#batch, time).
|
| 89 |
+
|
| 90 |
+
"""
|
| 91 |
+
if isinstance(x_input, tuple):
|
| 92 |
+
x, pos_emb = x_input[0], x_input[1]
|
| 93 |
+
else:
|
| 94 |
+
x, pos_emb = x_input, None
|
| 95 |
+
|
| 96 |
+
# whether to use macaron style
|
| 97 |
+
if self.feed_forward_macaron is not None:
|
| 98 |
+
residual = x
|
| 99 |
+
if self.normalize_before:
|
| 100 |
+
x = self.norm_ff_macaron(x)
|
| 101 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward_macaron(x))
|
| 102 |
+
if not self.normalize_before:
|
| 103 |
+
x = self.norm_ff_macaron(x)
|
| 104 |
+
|
| 105 |
+
# multi-headed self-attention module
|
| 106 |
+
residual = x
|
| 107 |
+
if self.normalize_before:
|
| 108 |
+
x = self.norm_mha(x)
|
| 109 |
+
|
| 110 |
+
if cache is None:
|
| 111 |
+
x_q = x
|
| 112 |
+
else:
|
| 113 |
+
assert cache.shape == (x.shape[0], x.shape[1] - 1, self.size)
|
| 114 |
+
x_q = x[:, -1:, :]
|
| 115 |
+
residual = residual[:, -1:, :]
|
| 116 |
+
mask = None if mask is None else mask[:, -1:, :]
|
| 117 |
+
|
| 118 |
+
if pos_emb is not None:
|
| 119 |
+
x_att = self.self_attn(x_q, x, x, pos_emb, mask)
|
| 120 |
+
else:
|
| 121 |
+
x_att = self.self_attn(x_q, x, x, mask)
|
| 122 |
+
|
| 123 |
+
if self.concat_after:
|
| 124 |
+
x_concat = torch.cat((x, x_att), dim=-1)
|
| 125 |
+
x = residual + self.concat_linear(x_concat)
|
| 126 |
+
else:
|
| 127 |
+
x = residual + self.dropout(x_att)
|
| 128 |
+
if not self.normalize_before:
|
| 129 |
+
x = self.norm_mha(x)
|
| 130 |
+
|
| 131 |
+
# convolution module
|
| 132 |
+
if self.conv_module is not None:
|
| 133 |
+
residual = x
|
| 134 |
+
if self.normalize_before:
|
| 135 |
+
x = self.norm_conv(x)
|
| 136 |
+
x = residual + self.dropout(self.conv_module(x))
|
| 137 |
+
if not self.normalize_before:
|
| 138 |
+
x = self.norm_conv(x)
|
| 139 |
+
|
| 140 |
+
# feed forward module
|
| 141 |
+
residual = x
|
| 142 |
+
if self.normalize_before:
|
| 143 |
+
x = self.norm_ff(x)
|
| 144 |
+
x = residual + self.ff_scale * self.dropout(self.feed_forward(x))
|
| 145 |
+
if not self.normalize_before:
|
| 146 |
+
x = self.norm_ff(x)
|
| 147 |
+
|
| 148 |
+
if self.conv_module is not None:
|
| 149 |
+
x = self.norm_final(x)
|
| 150 |
+
|
| 151 |
+
if cache is not None:
|
| 152 |
+
x = torch.cat([cache, x], dim=1)
|
| 153 |
+
|
| 154 |
+
if pos_emb is not None:
|
| 155 |
+
return (x, pos_emb), mask
|
| 156 |
+
|
| 157 |
+
return x, mask
|
lscodec/models/conformer/layer_norm.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Layer normalization module."""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class LayerNorm(torch.nn.LayerNorm):
|
| 13 |
+
"""Layer normalization module.
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
nout (int): Output dim size.
|
| 17 |
+
dim (int): Dimension to be normalized.
|
| 18 |
+
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
def __init__(self, nout, dim=-1):
|
| 22 |
+
"""Construct an LayerNorm object."""
|
| 23 |
+
super(LayerNorm, self).__init__(nout, eps=1e-12)
|
| 24 |
+
self.dim = dim
|
| 25 |
+
|
| 26 |
+
def forward(self, x):
|
| 27 |
+
"""Apply layer normalization.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
x (torch.Tensor): Input tensor.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
torch.Tensor: Normalized tensor.
|
| 34 |
+
|
| 35 |
+
"""
|
| 36 |
+
if self.dim == -1:
|
| 37 |
+
return super(LayerNorm, self).forward(x)
|
| 38 |
+
return (
|
| 39 |
+
super(LayerNorm, self)
|
| 40 |
+
.forward(x.transpose(self.dim, -1))
|
| 41 |
+
.transpose(self.dim, -1)
|
| 42 |
+
)
|
lscodec/models/conformer/multi_layer_conv.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Tomoki Hayashi
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Layer modules for FFT block in FastSpeech (Feed-forward Transformer)."""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class MultiLayeredConv1d(torch.nn.Module):
|
| 13 |
+
"""Multi-layered conv1d for Transformer block.
|
| 14 |
+
|
| 15 |
+
This is a module of multi-leyered conv1d designed
|
| 16 |
+
to replace positionwise feed-forward network
|
| 17 |
+
in Transforner block, which is introduced in
|
| 18 |
+
`FastSpeech: Fast, Robust and Controllable Text to Speech`_.
|
| 19 |
+
|
| 20 |
+
.. _`FastSpeech: Fast, Robust and Controllable Text to Speech`:
|
| 21 |
+
https://arxiv.org/pdf/1905.09263.pdf
|
| 22 |
+
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self, in_chans, hidden_chans, kernel_size, dropout_rate):
|
| 26 |
+
"""Initialize MultiLayeredConv1d module.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
in_chans (int): Number of input channels.
|
| 30 |
+
hidden_chans (int): Number of hidden channels.
|
| 31 |
+
kernel_size (int): Kernel size of conv1d.
|
| 32 |
+
dropout_rate (float): Dropout rate.
|
| 33 |
+
|
| 34 |
+
"""
|
| 35 |
+
super(MultiLayeredConv1d, self).__init__()
|
| 36 |
+
self.w_1 = torch.nn.Conv1d(
|
| 37 |
+
in_chans,
|
| 38 |
+
hidden_chans,
|
| 39 |
+
kernel_size,
|
| 40 |
+
stride=1,
|
| 41 |
+
padding=(kernel_size - 1) // 2,
|
| 42 |
+
)
|
| 43 |
+
self.w_2 = torch.nn.Conv1d(
|
| 44 |
+
hidden_chans,
|
| 45 |
+
in_chans,
|
| 46 |
+
kernel_size,
|
| 47 |
+
stride=1,
|
| 48 |
+
padding=(kernel_size - 1) // 2,
|
| 49 |
+
)
|
| 50 |
+
self.dropout = torch.nn.Dropout(dropout_rate)
|
| 51 |
+
|
| 52 |
+
def forward(self, x):
|
| 53 |
+
"""Calculate forward propagation.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
x (torch.Tensor): Batch of input tensors (B, T, in_chans).
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
torch.Tensor: Batch of output tensors (B, T, hidden_chans).
|
| 60 |
+
|
| 61 |
+
"""
|
| 62 |
+
x = torch.relu(self.w_1(x.transpose(-1, 1))).transpose(-1, 1)
|
| 63 |
+
return self.w_2(self.dropout(x).transpose(-1, 1)).transpose(-1, 1)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Conv1dLinear(torch.nn.Module):
|
| 67 |
+
"""Conv1D + Linear for Transformer block.
|
| 68 |
+
|
| 69 |
+
A variant of MultiLayeredConv1d, which replaces second conv-layer to linear.
|
| 70 |
+
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
def __init__(self, in_chans, hidden_chans, kernel_size, dropout_rate):
|
| 74 |
+
"""Initialize Conv1dLinear module.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
in_chans (int): Number of input channels.
|
| 78 |
+
hidden_chans (int): Number of hidden channels.
|
| 79 |
+
kernel_size (int): Kernel size of conv1d.
|
| 80 |
+
dropout_rate (float): Dropout rate.
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
super(Conv1dLinear, self).__init__()
|
| 84 |
+
self.w_1 = torch.nn.Conv1d(
|
| 85 |
+
in_chans,
|
| 86 |
+
hidden_chans,
|
| 87 |
+
kernel_size,
|
| 88 |
+
stride=1,
|
| 89 |
+
padding=(kernel_size - 1) // 2,
|
| 90 |
+
)
|
| 91 |
+
self.w_2 = torch.nn.Linear(hidden_chans, in_chans)
|
| 92 |
+
self.dropout = torch.nn.Dropout(dropout_rate)
|
| 93 |
+
|
| 94 |
+
def forward(self, x):
|
| 95 |
+
"""Calculate forward propagation.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
x (torch.Tensor): Batch of input tensors (B, T, in_chans).
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
torch.Tensor: Batch of output tensors (B, T, hidden_chans).
|
| 102 |
+
|
| 103 |
+
"""
|
| 104 |
+
x = torch.relu(self.w_1(x.transpose(-1, 1))).transpose(-1, 1)
|
| 105 |
+
return self.w_2(self.dropout(x))
|
lscodec/models/conformer/nets_utils.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
"""Network related utility tools."""
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
from typing import Dict
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def to_device(m, x):
|
| 13 |
+
"""Send tensor into the device of the module.
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
m (torch.nn.Module): Torch module.
|
| 17 |
+
x (Tensor): Torch tensor.
|
| 18 |
+
|
| 19 |
+
Returns:
|
| 20 |
+
Tensor: Torch tensor located in the same place as torch module.
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
if isinstance(m, torch.nn.Module):
|
| 24 |
+
device = next(m.parameters()).device
|
| 25 |
+
elif isinstance(m, torch.Tensor):
|
| 26 |
+
device = m.device
|
| 27 |
+
else:
|
| 28 |
+
raise TypeError(
|
| 29 |
+
"Expected torch.nn.Module or torch.tensor, " f"bot got: {type(m)}"
|
| 30 |
+
)
|
| 31 |
+
return x.to(device)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def pad_list(xs, pad_value):
|
| 35 |
+
"""Perform padding for the list of tensors.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
xs (List): List of Tensors [(T_1, `*`), (T_2, `*`), ..., (T_B, `*`)].
|
| 39 |
+
pad_value (float): Value for padding.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
Tensor: Padded tensor (B, Tmax, `*`).
|
| 43 |
+
|
| 44 |
+
Examples:
|
| 45 |
+
>>> x = [torch.ones(4), torch.ones(2), torch.ones(1)]
|
| 46 |
+
>>> x
|
| 47 |
+
[tensor([1., 1., 1., 1.]), tensor([1., 1.]), tensor([1.])]
|
| 48 |
+
>>> pad_list(x, 0)
|
| 49 |
+
tensor([[1., 1., 1., 1.],
|
| 50 |
+
[1., 1., 0., 0.],
|
| 51 |
+
[1., 0., 0., 0.]])
|
| 52 |
+
|
| 53 |
+
"""
|
| 54 |
+
n_batch = len(xs)
|
| 55 |
+
max_len = max(x.size(0) for x in xs)
|
| 56 |
+
pad = xs[0].new(n_batch, max_len, *xs[0].size()[1:]).fill_(pad_value)
|
| 57 |
+
|
| 58 |
+
for i in range(n_batch):
|
| 59 |
+
pad[i, : xs[i].size(0)] = xs[i]
|
| 60 |
+
|
| 61 |
+
return pad
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def make_pad_mask(lengths, xs=None, length_dim=-1):
|
| 65 |
+
"""Make mask tensor containing indices of padded part.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
lengths (LongTensor or List): Batch of lengths (B,).
|
| 69 |
+
xs (Tensor, optional): The reference tensor.
|
| 70 |
+
If set, masks will be the same shape as this tensor.
|
| 71 |
+
length_dim (int, optional): Dimension indicator of the above tensor.
|
| 72 |
+
See the example.
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
Tensor: Mask tensor containing indices of padded part.
|
| 76 |
+
dtype=torch.uint8 in PyTorch 1.2-
|
| 77 |
+
dtype=torch.bool in PyTorch 1.2+ (including 1.2)
|
| 78 |
+
|
| 79 |
+
Examples:
|
| 80 |
+
With only lengths.
|
| 81 |
+
|
| 82 |
+
>>> lengths = [5, 3, 2]
|
| 83 |
+
>>> make_non_pad_mask(lengths)
|
| 84 |
+
masks = [[0, 0, 0, 0 ,0],
|
| 85 |
+
[0, 0, 0, 1, 1],
|
| 86 |
+
[0, 0, 1, 1, 1]]
|
| 87 |
+
|
| 88 |
+
With the reference tensor.
|
| 89 |
+
|
| 90 |
+
>>> xs = torch.zeros((3, 2, 4))
|
| 91 |
+
>>> make_pad_mask(lengths, xs)
|
| 92 |
+
tensor([[[0, 0, 0, 0],
|
| 93 |
+
[0, 0, 0, 0]],
|
| 94 |
+
[[0, 0, 0, 1],
|
| 95 |
+
[0, 0, 0, 1]],
|
| 96 |
+
[[0, 0, 1, 1],
|
| 97 |
+
[0, 0, 1, 1]]], dtype=torch.uint8)
|
| 98 |
+
>>> xs = torch.zeros((3, 2, 6))
|
| 99 |
+
>>> make_pad_mask(lengths, xs)
|
| 100 |
+
tensor([[[0, 0, 0, 0, 0, 1],
|
| 101 |
+
[0, 0, 0, 0, 0, 1]],
|
| 102 |
+
[[0, 0, 0, 1, 1, 1],
|
| 103 |
+
[0, 0, 0, 1, 1, 1]],
|
| 104 |
+
[[0, 0, 1, 1, 1, 1],
|
| 105 |
+
[0, 0, 1, 1, 1, 1]]], dtype=torch.uint8)
|
| 106 |
+
|
| 107 |
+
With the reference tensor and dimension indicator.
|
| 108 |
+
|
| 109 |
+
>>> xs = torch.zeros((3, 6, 6))
|
| 110 |
+
>>> make_pad_mask(lengths, xs, 1)
|
| 111 |
+
tensor([[[0, 0, 0, 0, 0, 0],
|
| 112 |
+
[0, 0, 0, 0, 0, 0],
|
| 113 |
+
[0, 0, 0, 0, 0, 0],
|
| 114 |
+
[0, 0, 0, 0, 0, 0],
|
| 115 |
+
[0, 0, 0, 0, 0, 0],
|
| 116 |
+
[1, 1, 1, 1, 1, 1]],
|
| 117 |
+
[[0, 0, 0, 0, 0, 0],
|
| 118 |
+
[0, 0, 0, 0, 0, 0],
|
| 119 |
+
[0, 0, 0, 0, 0, 0],
|
| 120 |
+
[1, 1, 1, 1, 1, 1],
|
| 121 |
+
[1, 1, 1, 1, 1, 1],
|
| 122 |
+
[1, 1, 1, 1, 1, 1]],
|
| 123 |
+
[[0, 0, 0, 0, 0, 0],
|
| 124 |
+
[0, 0, 0, 0, 0, 0],
|
| 125 |
+
[1, 1, 1, 1, 1, 1],
|
| 126 |
+
[1, 1, 1, 1, 1, 1],
|
| 127 |
+
[1, 1, 1, 1, 1, 1],
|
| 128 |
+
[1, 1, 1, 1, 1, 1]]], dtype=torch.uint8)
|
| 129 |
+
>>> make_pad_mask(lengths, xs, 2)
|
| 130 |
+
tensor([[[0, 0, 0, 0, 0, 1],
|
| 131 |
+
[0, 0, 0, 0, 0, 1],
|
| 132 |
+
[0, 0, 0, 0, 0, 1],
|
| 133 |
+
[0, 0, 0, 0, 0, 1],
|
| 134 |
+
[0, 0, 0, 0, 0, 1],
|
| 135 |
+
[0, 0, 0, 0, 0, 1]],
|
| 136 |
+
[[0, 0, 0, 1, 1, 1],
|
| 137 |
+
[0, 0, 0, 1, 1, 1],
|
| 138 |
+
[0, 0, 0, 1, 1, 1],
|
| 139 |
+
[0, 0, 0, 1, 1, 1],
|
| 140 |
+
[0, 0, 0, 1, 1, 1],
|
| 141 |
+
[0, 0, 0, 1, 1, 1]],
|
| 142 |
+
[[0, 0, 1, 1, 1, 1],
|
| 143 |
+
[0, 0, 1, 1, 1, 1],
|
| 144 |
+
[0, 0, 1, 1, 1, 1],
|
| 145 |
+
[0, 0, 1, 1, 1, 1],
|
| 146 |
+
[0, 0, 1, 1, 1, 1],
|
| 147 |
+
[0, 0, 1, 1, 1, 1]]], dtype=torch.uint8)
|
| 148 |
+
|
| 149 |
+
"""
|
| 150 |
+
if length_dim == 0:
|
| 151 |
+
raise ValueError("length_dim cannot be 0: {}".format(length_dim))
|
| 152 |
+
|
| 153 |
+
if not isinstance(lengths, list):
|
| 154 |
+
lengths = lengths.tolist()
|
| 155 |
+
bs = int(len(lengths))
|
| 156 |
+
if xs is None:
|
| 157 |
+
maxlen = int(max(lengths))
|
| 158 |
+
else:
|
| 159 |
+
maxlen = xs.size(length_dim)
|
| 160 |
+
|
| 161 |
+
seq_range = torch.arange(0, maxlen, dtype=torch.int64)
|
| 162 |
+
seq_range_expand = seq_range.unsqueeze(0).expand(bs, maxlen)
|
| 163 |
+
seq_length_expand = seq_range_expand.new(lengths).unsqueeze(-1)
|
| 164 |
+
mask = seq_range_expand >= seq_length_expand
|
| 165 |
+
|
| 166 |
+
if xs is not None:
|
| 167 |
+
assert xs.size(0) == bs, (xs.size(0), bs)
|
| 168 |
+
|
| 169 |
+
if length_dim < 0:
|
| 170 |
+
length_dim = xs.dim() + length_dim
|
| 171 |
+
# ind = (:, None, ..., None, :, , None, ..., None)
|
| 172 |
+
ind = tuple(
|
| 173 |
+
slice(None) if i in (0, length_dim) else None for i in range(xs.dim())
|
| 174 |
+
)
|
| 175 |
+
mask = mask[ind].expand_as(xs).to(xs.device)
|
| 176 |
+
return mask
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def make_non_pad_mask(lengths, xs=None, length_dim=-1):
|
| 180 |
+
"""Make mask tensor containing indices of non-padded part.
|
| 181 |
+
|
| 182 |
+
Args:
|
| 183 |
+
lengths (LongTensor or List): Batch of lengths (B,).
|
| 184 |
+
xs (Tensor, optional): The reference tensor.
|
| 185 |
+
If set, masks will be the same shape as this tensor.
|
| 186 |
+
length_dim (int, optional): Dimension indicator of the above tensor.
|
| 187 |
+
See the example.
|
| 188 |
+
|
| 189 |
+
Returns:
|
| 190 |
+
ByteTensor: mask tensor containing indices of padded part.
|
| 191 |
+
dtype=torch.uint8 in PyTorch 1.2-
|
| 192 |
+
dtype=torch.bool in PyTorch 1.2+ (including 1.2)
|
| 193 |
+
|
| 194 |
+
Examples:
|
| 195 |
+
With only lengths.
|
| 196 |
+
|
| 197 |
+
>>> lengths = [5, 3, 2]
|
| 198 |
+
>>> make_non_pad_mask(lengths)
|
| 199 |
+
masks = [[1, 1, 1, 1 ,1],
|
| 200 |
+
[1, 1, 1, 0, 0],
|
| 201 |
+
[1, 1, 0, 0, 0]]
|
| 202 |
+
|
| 203 |
+
With the reference tensor.
|
| 204 |
+
|
| 205 |
+
>>> xs = torch.zeros((3, 2, 4))
|
| 206 |
+
>>> make_non_pad_mask(lengths, xs)
|
| 207 |
+
tensor([[[1, 1, 1, 1],
|
| 208 |
+
[1, 1, 1, 1]],
|
| 209 |
+
[[1, 1, 1, 0],
|
| 210 |
+
[1, 1, 1, 0]],
|
| 211 |
+
[[1, 1, 0, 0],
|
| 212 |
+
[1, 1, 0, 0]]], dtype=torch.uint8)
|
| 213 |
+
>>> xs = torch.zeros((3, 2, 6))
|
| 214 |
+
>>> make_non_pad_mask(lengths, xs)
|
| 215 |
+
tensor([[[1, 1, 1, 1, 1, 0],
|
| 216 |
+
[1, 1, 1, 1, 1, 0]],
|
| 217 |
+
[[1, 1, 1, 0, 0, 0],
|
| 218 |
+
[1, 1, 1, 0, 0, 0]],
|
| 219 |
+
[[1, 1, 0, 0, 0, 0],
|
| 220 |
+
[1, 1, 0, 0, 0, 0]]], dtype=torch.uint8)
|
| 221 |
+
|
| 222 |
+
With the reference tensor and dimension indicator.
|
| 223 |
+
|
| 224 |
+
>>> xs = torch.zeros((3, 6, 6))
|
| 225 |
+
>>> make_non_pad_mask(lengths, xs, 1)
|
| 226 |
+
tensor([[[1, 1, 1, 1, 1, 1],
|
| 227 |
+
[1, 1, 1, 1, 1, 1],
|
| 228 |
+
[1, 1, 1, 1, 1, 1],
|
| 229 |
+
[1, 1, 1, 1, 1, 1],
|
| 230 |
+
[1, 1, 1, 1, 1, 1],
|
| 231 |
+
[0, 0, 0, 0, 0, 0]],
|
| 232 |
+
[[1, 1, 1, 1, 1, 1],
|
| 233 |
+
[1, 1, 1, 1, 1, 1],
|
| 234 |
+
[1, 1, 1, 1, 1, 1],
|
| 235 |
+
[0, 0, 0, 0, 0, 0],
|
| 236 |
+
[0, 0, 0, 0, 0, 0],
|
| 237 |
+
[0, 0, 0, 0, 0, 0]],
|
| 238 |
+
[[1, 1, 1, 1, 1, 1],
|
| 239 |
+
[1, 1, 1, 1, 1, 1],
|
| 240 |
+
[0, 0, 0, 0, 0, 0],
|
| 241 |
+
[0, 0, 0, 0, 0, 0],
|
| 242 |
+
[0, 0, 0, 0, 0, 0],
|
| 243 |
+
[0, 0, 0, 0, 0, 0]]], dtype=torch.uint8)
|
| 244 |
+
>>> make_non_pad_mask(lengths, xs, 2)
|
| 245 |
+
tensor([[[1, 1, 1, 1, 1, 0],
|
| 246 |
+
[1, 1, 1, 1, 1, 0],
|
| 247 |
+
[1, 1, 1, 1, 1, 0],
|
| 248 |
+
[1, 1, 1, 1, 1, 0],
|
| 249 |
+
[1, 1, 1, 1, 1, 0],
|
| 250 |
+
[1, 1, 1, 1, 1, 0]],
|
| 251 |
+
[[1, 1, 1, 0, 0, 0],
|
| 252 |
+
[1, 1, 1, 0, 0, 0],
|
| 253 |
+
[1, 1, 1, 0, 0, 0],
|
| 254 |
+
[1, 1, 1, 0, 0, 0],
|
| 255 |
+
[1, 1, 1, 0, 0, 0],
|
| 256 |
+
[1, 1, 1, 0, 0, 0]],
|
| 257 |
+
[[1, 1, 0, 0, 0, 0],
|
| 258 |
+
[1, 1, 0, 0, 0, 0],
|
| 259 |
+
[1, 1, 0, 0, 0, 0],
|
| 260 |
+
[1, 1, 0, 0, 0, 0],
|
| 261 |
+
[1, 1, 0, 0, 0, 0],
|
| 262 |
+
[1, 1, 0, 0, 0, 0]]], dtype=torch.uint8)
|
| 263 |
+
|
| 264 |
+
"""
|
| 265 |
+
return ~make_pad_mask(lengths, xs, length_dim)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def mask_by_length(xs, lengths, fill=0):
|
| 269 |
+
"""Mask tensor according to length.
|
| 270 |
+
|
| 271 |
+
Args:
|
| 272 |
+
xs (Tensor): Batch of input tensor (B, `*`).
|
| 273 |
+
lengths (LongTensor or List): Batch of lengths (B,).
|
| 274 |
+
fill (int or float): Value to fill masked part.
|
| 275 |
+
|
| 276 |
+
Returns:
|
| 277 |
+
Tensor: Batch of masked input tensor (B, `*`).
|
| 278 |
+
|
| 279 |
+
Examples:
|
| 280 |
+
>>> x = torch.arange(5).repeat(3, 1) + 1
|
| 281 |
+
>>> x
|
| 282 |
+
tensor([[1, 2, 3, 4, 5],
|
| 283 |
+
[1, 2, 3, 4, 5],
|
| 284 |
+
[1, 2, 3, 4, 5]])
|
| 285 |
+
>>> lengths = [5, 3, 2]
|
| 286 |
+
>>> mask_by_length(x, lengths)
|
| 287 |
+
tensor([[1, 2, 3, 4, 5],
|
| 288 |
+
[1, 2, 3, 0, 0],
|
| 289 |
+
[1, 2, 0, 0, 0]])
|
| 290 |
+
|
| 291 |
+
"""
|
| 292 |
+
assert xs.size(0) == len(lengths)
|
| 293 |
+
ret = xs.data.new(*xs.size()).fill_(fill)
|
| 294 |
+
for i, l in enumerate(lengths):
|
| 295 |
+
ret[i, :l] = xs[i, :l]
|
| 296 |
+
return ret
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def th_accuracy(pad_outputs, pad_targets, ignore_label):
|
| 300 |
+
"""Calculate accuracy.
|
| 301 |
+
|
| 302 |
+
Args:
|
| 303 |
+
pad_outputs (Tensor): Prediction tensors (B * Lmax, D).
|
| 304 |
+
pad_targets (LongTensor): Target label tensors (B, Lmax, D).
|
| 305 |
+
ignore_label (int): Ignore label id.
|
| 306 |
+
|
| 307 |
+
Returns:
|
| 308 |
+
float: Accuracy value (0.0 - 1.0).
|
| 309 |
+
|
| 310 |
+
"""
|
| 311 |
+
pad_pred = pad_outputs.view(
|
| 312 |
+
pad_targets.size(0), pad_targets.size(1), pad_outputs.size(1)
|
| 313 |
+
).argmax(2)
|
| 314 |
+
mask = pad_targets != ignore_label
|
| 315 |
+
numerator = torch.sum(
|
| 316 |
+
pad_pred.masked_select(mask) == pad_targets.masked_select(mask)
|
| 317 |
+
)
|
| 318 |
+
denominator = torch.sum(mask)
|
| 319 |
+
return float(numerator) / float(denominator)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
def to_torch_tensor(x):
|
| 323 |
+
"""Change to torch.Tensor or ComplexTensor from numpy.ndarray.
|
| 324 |
+
|
| 325 |
+
Args:
|
| 326 |
+
x: Inputs. It should be one of numpy.ndarray, Tensor, ComplexTensor, and dict.
|
| 327 |
+
|
| 328 |
+
Returns:
|
| 329 |
+
Tensor or ComplexTensor: Type converted inputs.
|
| 330 |
+
|
| 331 |
+
Examples:
|
| 332 |
+
>>> xs = np.ones(3, dtype=np.float32)
|
| 333 |
+
>>> xs = to_torch_tensor(xs)
|
| 334 |
+
tensor([1., 1., 1.])
|
| 335 |
+
>>> xs = torch.ones(3, 4, 5)
|
| 336 |
+
>>> assert to_torch_tensor(xs) is xs
|
| 337 |
+
>>> xs = {'real': xs, 'imag': xs}
|
| 338 |
+
>>> to_torch_tensor(xs)
|
| 339 |
+
ComplexTensor(
|
| 340 |
+
Real:
|
| 341 |
+
tensor([1., 1., 1.])
|
| 342 |
+
Imag;
|
| 343 |
+
tensor([1., 1., 1.])
|
| 344 |
+
)
|
| 345 |
+
|
| 346 |
+
"""
|
| 347 |
+
# If numpy, change to torch tensor
|
| 348 |
+
if isinstance(x, np.ndarray):
|
| 349 |
+
if x.dtype.kind == "c":
|
| 350 |
+
# Dynamically importing because torch_complex requires python3
|
| 351 |
+
from torch_complex.tensor import ComplexTensor
|
| 352 |
+
|
| 353 |
+
return ComplexTensor(x)
|
| 354 |
+
else:
|
| 355 |
+
return torch.from_numpy(x)
|
| 356 |
+
|
| 357 |
+
# If {'real': ..., 'imag': ...}, convert to ComplexTensor
|
| 358 |
+
elif isinstance(x, dict):
|
| 359 |
+
# Dynamically importing because torch_complex requires python3
|
| 360 |
+
from torch_complex.tensor import ComplexTensor
|
| 361 |
+
|
| 362 |
+
if "real" not in x or "imag" not in x:
|
| 363 |
+
raise ValueError("has 'real' and 'imag' keys: {}".format(list(x)))
|
| 364 |
+
# Relative importing because of using python3 syntax
|
| 365 |
+
return ComplexTensor(x["real"], x["imag"])
|
| 366 |
+
|
| 367 |
+
# If torch.Tensor, as it is
|
| 368 |
+
elif isinstance(x, torch.Tensor):
|
| 369 |
+
return x
|
| 370 |
+
|
| 371 |
+
else:
|
| 372 |
+
error = (
|
| 373 |
+
"x must be numpy.ndarray, torch.Tensor or a dict like "
|
| 374 |
+
"{{'real': torch.Tensor, 'imag': torch.Tensor}}, "
|
| 375 |
+
"but got {}".format(type(x))
|
| 376 |
+
)
|
| 377 |
+
try:
|
| 378 |
+
from torch_complex.tensor import ComplexTensor
|
| 379 |
+
except Exception:
|
| 380 |
+
# If PY2
|
| 381 |
+
raise ValueError(error)
|
| 382 |
+
else:
|
| 383 |
+
# If PY3
|
| 384 |
+
if isinstance(x, ComplexTensor):
|
| 385 |
+
return x
|
| 386 |
+
else:
|
| 387 |
+
raise ValueError(error)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def get_subsample(train_args, mode, arch):
|
| 391 |
+
"""Parse the subsampling factors from the args for the specified `mode` and `arch`.
|
| 392 |
+
|
| 393 |
+
Args:
|
| 394 |
+
train_args: argument Namespace containing options.
|
| 395 |
+
mode: one of ('asr', 'mt', 'st')
|
| 396 |
+
arch: one of ('rnn', 'rnn-t', 'rnn_mix', 'rnn_mulenc', 'transformer')
|
| 397 |
+
|
| 398 |
+
Returns:
|
| 399 |
+
np.ndarray / List[np.ndarray]: subsampling factors.
|
| 400 |
+
"""
|
| 401 |
+
if arch == "transformer":
|
| 402 |
+
return np.array([1])
|
| 403 |
+
|
| 404 |
+
elif mode == "mt" and arch == "rnn":
|
| 405 |
+
# +1 means input (+1) and layers outputs (train_args.elayer)
|
| 406 |
+
subsample = np.ones(train_args.elayers + 1, dtype=np.int)
|
| 407 |
+
logging.warning("Subsampling is not performed for machine translation.")
|
| 408 |
+
logging.info("subsample: " + " ".join([str(x) for x in subsample]))
|
| 409 |
+
return subsample
|
| 410 |
+
|
| 411 |
+
elif (
|
| 412 |
+
(mode == "asr" and arch in ("rnn", "rnn-t"))
|
| 413 |
+
or (mode == "mt" and arch == "rnn")
|
| 414 |
+
or (mode == "st" and arch == "rnn")
|
| 415 |
+
):
|
| 416 |
+
subsample = np.ones(train_args.elayers + 1, dtype=np.int)
|
| 417 |
+
if train_args.etype.endswith("p") and not train_args.etype.startswith("vgg"):
|
| 418 |
+
ss = train_args.subsample.split("_")
|
| 419 |
+
for j in range(min(train_args.elayers + 1, len(ss))):
|
| 420 |
+
subsample[j] = int(ss[j])
|
| 421 |
+
else:
|
| 422 |
+
logging.warning(
|
| 423 |
+
"Subsampling is not performed for vgg*. "
|
| 424 |
+
"It is performed in max pooling layers at CNN."
|
| 425 |
+
)
|
| 426 |
+
logging.info("subsample: " + " ".join([str(x) for x in subsample]))
|
| 427 |
+
return subsample
|
| 428 |
+
|
| 429 |
+
elif mode == "asr" and arch == "rnn_mix":
|
| 430 |
+
subsample = np.ones(
|
| 431 |
+
train_args.elayers_sd + train_args.elayers + 1, dtype=np.int
|
| 432 |
+
)
|
| 433 |
+
if train_args.etype.endswith("p") and not train_args.etype.startswith("vgg"):
|
| 434 |
+
ss = train_args.subsample.split("_")
|
| 435 |
+
for j in range(
|
| 436 |
+
min(train_args.elayers_sd + train_args.elayers + 1, len(ss))
|
| 437 |
+
):
|
| 438 |
+
subsample[j] = int(ss[j])
|
| 439 |
+
else:
|
| 440 |
+
logging.warning(
|
| 441 |
+
"Subsampling is not performed for vgg*. "
|
| 442 |
+
"It is performed in max pooling layers at CNN."
|
| 443 |
+
)
|
| 444 |
+
logging.info("subsample: " + " ".join([str(x) for x in subsample]))
|
| 445 |
+
return subsample
|
| 446 |
+
|
| 447 |
+
elif mode == "asr" and arch == "rnn_mulenc":
|
| 448 |
+
subsample_list = []
|
| 449 |
+
for idx in range(train_args.num_encs):
|
| 450 |
+
subsample = np.ones(train_args.elayers[idx] + 1, dtype=np.int)
|
| 451 |
+
if train_args.etype[idx].endswith("p") and not train_args.etype[
|
| 452 |
+
idx
|
| 453 |
+
].startswith("vgg"):
|
| 454 |
+
ss = train_args.subsample[idx].split("_")
|
| 455 |
+
for j in range(min(train_args.elayers[idx] + 1, len(ss))):
|
| 456 |
+
subsample[j] = int(ss[j])
|
| 457 |
+
else:
|
| 458 |
+
logging.warning(
|
| 459 |
+
"Encoder %d: Subsampling is not performed for vgg*. "
|
| 460 |
+
"It is performed in max pooling layers at CNN.",
|
| 461 |
+
idx + 1,
|
| 462 |
+
)
|
| 463 |
+
logging.info("subsample: " + " ".join([str(x) for x in subsample]))
|
| 464 |
+
subsample_list.append(subsample)
|
| 465 |
+
return subsample_list
|
| 466 |
+
|
| 467 |
+
else:
|
| 468 |
+
raise ValueError("Invalid options: mode={}, arch={}".format(mode, arch))
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def rename_state_dict(
|
| 472 |
+
old_prefix: str, new_prefix: str, state_dict: Dict[str, torch.Tensor]
|
| 473 |
+
):
|
| 474 |
+
"""Replace keys of old prefix with new prefix in state dict."""
|
| 475 |
+
# need this list not to break the dict iterator
|
| 476 |
+
old_keys = [k for k in state_dict if k.startswith(old_prefix)]
|
| 477 |
+
if len(old_keys) > 0:
|
| 478 |
+
logging.warning(f"Rename: {old_prefix} -> {new_prefix}")
|
| 479 |
+
for k in old_keys:
|
| 480 |
+
v = state_dict.pop(k)
|
| 481 |
+
new_k = k.replace(old_prefix, new_prefix)
|
| 482 |
+
state_dict[new_k] = v
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
def get_activation(act):
|
| 486 |
+
"""Return activation function."""
|
| 487 |
+
# Lazy load to avoid unused import
|
| 488 |
+
from lscodec.models.conformer.swish import Swish
|
| 489 |
+
|
| 490 |
+
activation_funcs = {
|
| 491 |
+
"hardtanh": torch.nn.Hardtanh,
|
| 492 |
+
"tanh": torch.nn.Tanh,
|
| 493 |
+
"relu": torch.nn.ReLU,
|
| 494 |
+
"selu": torch.nn.SELU,
|
| 495 |
+
"swish": Swish,
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
return activation_funcs[act]()
|
lscodec/models/conformer/positionwise_feed_forward.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Positionwise feed forward layer definition."""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class PositionwiseFeedForward(torch.nn.Module):
|
| 13 |
+
"""Positionwise feed forward layer.
|
| 14 |
+
|
| 15 |
+
Args:
|
| 16 |
+
idim (int): Input dimenstion.
|
| 17 |
+
hidden_units (int): The number of hidden units.
|
| 18 |
+
dropout_rate (float): Dropout rate.
|
| 19 |
+
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
def __init__(self, idim, hidden_units, dropout_rate, activation=torch.nn.ReLU()):
|
| 23 |
+
"""Construct an PositionwiseFeedForward object."""
|
| 24 |
+
super(PositionwiseFeedForward, self).__init__()
|
| 25 |
+
self.w_1 = torch.nn.Linear(idim, hidden_units)
|
| 26 |
+
self.w_2 = torch.nn.Linear(hidden_units, idim)
|
| 27 |
+
self.dropout = torch.nn.Dropout(dropout_rate)
|
| 28 |
+
self.activation = activation
|
| 29 |
+
|
| 30 |
+
def forward(self, x):
|
| 31 |
+
"""Forward funciton."""
|
| 32 |
+
return self.w_2(self.dropout(self.activation(self.w_1(x))))
|
lscodec/models/conformer/repeat.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Repeat the same layer definition."""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class MultiSequential(torch.nn.Sequential):
|
| 13 |
+
"""Multi-input multi-output torch.nn.Sequential."""
|
| 14 |
+
|
| 15 |
+
def forward(self, *args):
|
| 16 |
+
"""Repeat."""
|
| 17 |
+
for m in self:
|
| 18 |
+
args = m(*args)
|
| 19 |
+
return args
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def repeat(N, fn):
|
| 23 |
+
"""Repeat module N times.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
N (int): Number of repeat time.
|
| 27 |
+
fn (Callable): Function to generate module.
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
MultiSequential: Repeated model instance.
|
| 31 |
+
|
| 32 |
+
"""
|
| 33 |
+
return MultiSequential(*[fn(n) for n in range(N)])
|
lscodec/models/conformer/subsampling.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2019 Shigeki Karita
|
| 5 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 6 |
+
|
| 7 |
+
"""Subsampling layer definition."""
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
from lscodec.models.conformer.embedding import PositionalEncoding
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TooShortUttError(Exception):
|
| 15 |
+
"""Raised when the utt is too short for subsampling.
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
message (str): Message for error catch
|
| 19 |
+
actual_size (int): the short size that cannot pass the subsampling
|
| 20 |
+
limit (int): the limit size for subsampling
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, message, actual_size, limit):
|
| 25 |
+
"""Construct a TooShortUttError for error handler."""
|
| 26 |
+
super().__init__(message)
|
| 27 |
+
self.actual_size = actual_size
|
| 28 |
+
self.limit = limit
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def check_short_utt(ins, size):
|
| 32 |
+
"""Check if the utterance is too short for subsampling."""
|
| 33 |
+
if isinstance(ins, Conv2dSubsampling2) and size < 3:
|
| 34 |
+
return True, 3
|
| 35 |
+
if isinstance(ins, Conv2dSubsampling) and size < 7:
|
| 36 |
+
return True, 7
|
| 37 |
+
if isinstance(ins, Conv2dSubsampling6) and size < 11:
|
| 38 |
+
return True, 11
|
| 39 |
+
if isinstance(ins, Conv2dSubsampling8) and size < 15:
|
| 40 |
+
return True, 15
|
| 41 |
+
return False, -1
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class Conv2dSubsampling(torch.nn.Module):
|
| 45 |
+
"""Convolutional 2D subsampling (to 1/4 length).
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
idim (int): Input dimension.
|
| 49 |
+
odim (int): Output dimension.
|
| 50 |
+
dropout_rate (float): Dropout rate.
|
| 51 |
+
pos_enc (torch.nn.Module): Custom position encoding layer.
|
| 52 |
+
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(self, idim, odim, dropout_rate, pos_enc=None):
|
| 56 |
+
"""Construct an Conv2dSubsampling object."""
|
| 57 |
+
super(Conv2dSubsampling, self).__init__()
|
| 58 |
+
self.conv = torch.nn.Sequential(
|
| 59 |
+
torch.nn.Conv2d(1, odim, 3, 2),
|
| 60 |
+
torch.nn.ReLU(),
|
| 61 |
+
torch.nn.Conv2d(odim, odim, 3, 2),
|
| 62 |
+
torch.nn.ReLU(),
|
| 63 |
+
)
|
| 64 |
+
self.out = torch.nn.Sequential(
|
| 65 |
+
torch.nn.Linear(odim * (((idim - 1) // 2 - 1) // 2), odim),
|
| 66 |
+
pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
def forward(self, x, x_mask):
|
| 70 |
+
"""Subsample x.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
x (torch.Tensor): Input tensor (#batch, time, idim).
|
| 74 |
+
x_mask (torch.Tensor): Input mask (#batch, 1, time).
|
| 75 |
+
|
| 76 |
+
Returns:
|
| 77 |
+
torch.Tensor: Subsampled tensor (#batch, time', odim),
|
| 78 |
+
where time' = time // 4.
|
| 79 |
+
torch.Tensor: Subsampled mask (#batch, 1, time'),
|
| 80 |
+
where time' = time // 4.
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
x = x.unsqueeze(1) # (b, c, t, f)
|
| 84 |
+
x = self.conv(x)
|
| 85 |
+
b, c, t, f = x.size()
|
| 86 |
+
x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
|
| 87 |
+
if x_mask is None:
|
| 88 |
+
return x, None
|
| 89 |
+
return x, x_mask[:, :, :-2:2][:, :, :-2:2]
|
| 90 |
+
|
| 91 |
+
def __getitem__(self, key):
|
| 92 |
+
"""Get item.
|
| 93 |
+
|
| 94 |
+
When reset_parameters() is called, if use_scaled_pos_enc is used,
|
| 95 |
+
return the positioning encoding.
|
| 96 |
+
|
| 97 |
+
"""
|
| 98 |
+
if key != -1:
|
| 99 |
+
raise NotImplementedError("Support only `-1` (for `reset_parameters`).")
|
| 100 |
+
return self.out[key]
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class Conv2dSubsampling2(torch.nn.Module):
|
| 104 |
+
"""Convolutional 2D subsampling (to 1/2 length).
|
| 105 |
+
|
| 106 |
+
Args:
|
| 107 |
+
idim (int): Input dimension.
|
| 108 |
+
odim (int): Output dimension.
|
| 109 |
+
dropout_rate (float): Dropout rate.
|
| 110 |
+
pos_enc (torch.nn.Module): Custom position encoding layer.
|
| 111 |
+
|
| 112 |
+
"""
|
| 113 |
+
|
| 114 |
+
def __init__(self, idim, odim, dropout_rate, pos_enc=None):
|
| 115 |
+
"""Construct an Conv2dSubsampling2 object."""
|
| 116 |
+
super(Conv2dSubsampling2, self).__init__()
|
| 117 |
+
self.conv = torch.nn.Sequential(
|
| 118 |
+
torch.nn.Conv2d(1, odim, 3, 2),
|
| 119 |
+
torch.nn.ReLU(),
|
| 120 |
+
torch.nn.Conv2d(odim, odim, 3, 1),
|
| 121 |
+
torch.nn.ReLU(),
|
| 122 |
+
)
|
| 123 |
+
self.out = torch.nn.Sequential(
|
| 124 |
+
torch.nn.Linear(odim * (((idim - 1) // 2 - 2)), odim),
|
| 125 |
+
pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
def forward(self, x, x_mask):
|
| 129 |
+
"""Subsample x.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
x (torch.Tensor): Input tensor (#batch, time, idim).
|
| 133 |
+
x_mask (torch.Tensor): Input mask (#batch, 1, time).
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
torch.Tensor: Subsampled tensor (#batch, time', odim),
|
| 137 |
+
where time' = time // 2.
|
| 138 |
+
torch.Tensor: Subsampled mask (#batch, 1, time'),
|
| 139 |
+
where time' = time // 2.
|
| 140 |
+
|
| 141 |
+
"""
|
| 142 |
+
x = x.unsqueeze(1) # (b, c, t, f)
|
| 143 |
+
x = self.conv(x)
|
| 144 |
+
b, c, t, f = x.size()
|
| 145 |
+
x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
|
| 146 |
+
if x_mask is None:
|
| 147 |
+
return x, None
|
| 148 |
+
return x, x_mask[:, :, :-2:2][:, :, :-2:1]
|
| 149 |
+
|
| 150 |
+
def __getitem__(self, key):
|
| 151 |
+
"""Get item.
|
| 152 |
+
|
| 153 |
+
When reset_parameters() is called, if use_scaled_pos_enc is used,
|
| 154 |
+
return the positioning encoding.
|
| 155 |
+
|
| 156 |
+
"""
|
| 157 |
+
if key != -1:
|
| 158 |
+
raise NotImplementedError("Support only `-1` (for `reset_parameters`).")
|
| 159 |
+
return self.out[key]
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class Conv2dSubsampling6(torch.nn.Module):
|
| 163 |
+
"""Convolutional 2D subsampling (to 1/6 length).
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
idim (int): Input dimension.
|
| 167 |
+
odim (int): Output dimension.
|
| 168 |
+
dropout_rate (float): Dropout rate.
|
| 169 |
+
pos_enc (torch.nn.Module): Custom position encoding layer.
|
| 170 |
+
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
def __init__(self, idim, odim, dropout_rate, pos_enc=None):
|
| 174 |
+
"""Construct an Conv2dSubsampling6 object."""
|
| 175 |
+
super(Conv2dSubsampling6, self).__init__()
|
| 176 |
+
self.conv = torch.nn.Sequential(
|
| 177 |
+
torch.nn.Conv2d(1, odim, 3, 2),
|
| 178 |
+
torch.nn.ReLU(),
|
| 179 |
+
torch.nn.Conv2d(odim, odim, 5, 3),
|
| 180 |
+
torch.nn.ReLU(),
|
| 181 |
+
)
|
| 182 |
+
self.out = torch.nn.Sequential(
|
| 183 |
+
torch.nn.Linear(odim * (((idim - 1) // 2 - 2) // 3), odim),
|
| 184 |
+
pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
def forward(self, x, x_mask):
|
| 188 |
+
"""Subsample x.
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
x (torch.Tensor): Input tensor (#batch, time, idim).
|
| 192 |
+
x_mask (torch.Tensor): Input mask (#batch, 1, time).
|
| 193 |
+
|
| 194 |
+
Returns:
|
| 195 |
+
torch.Tensor: Subsampled tensor (#batch, time', odim),
|
| 196 |
+
where time' = time // 6.
|
| 197 |
+
torch.Tensor: Subsampled mask (#batch, 1, time'),
|
| 198 |
+
where time' = time // 6.
|
| 199 |
+
|
| 200 |
+
"""
|
| 201 |
+
x = x.unsqueeze(1) # (b, c, t, f)
|
| 202 |
+
x = self.conv(x)
|
| 203 |
+
b, c, t, f = x.size()
|
| 204 |
+
x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
|
| 205 |
+
if x_mask is None:
|
| 206 |
+
return x, None
|
| 207 |
+
return x, x_mask[:, :, :-2:2][:, :, :-4:3]
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
class Conv2dSubsampling8(torch.nn.Module):
|
| 211 |
+
"""Convolutional 2D subsampling (to 1/8 length).
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
idim (int): Input dimension.
|
| 215 |
+
odim (int): Output dimension.
|
| 216 |
+
dropout_rate (float): Dropout rate.
|
| 217 |
+
pos_enc (torch.nn.Module): Custom position encoding layer.
|
| 218 |
+
|
| 219 |
+
"""
|
| 220 |
+
|
| 221 |
+
def __init__(self, idim, odim, dropout_rate, pos_enc=None):
|
| 222 |
+
"""Construct an Conv2dSubsampling8 object."""
|
| 223 |
+
super(Conv2dSubsampling8, self).__init__()
|
| 224 |
+
self.conv = torch.nn.Sequential(
|
| 225 |
+
torch.nn.Conv2d(1, odim, 3, 2),
|
| 226 |
+
torch.nn.ReLU(),
|
| 227 |
+
torch.nn.Conv2d(odim, odim, 3, 2),
|
| 228 |
+
torch.nn.ReLU(),
|
| 229 |
+
torch.nn.Conv2d(odim, odim, 3, 2),
|
| 230 |
+
torch.nn.ReLU(),
|
| 231 |
+
)
|
| 232 |
+
self.out = torch.nn.Sequential(
|
| 233 |
+
torch.nn.Linear(odim * ((((idim - 1) // 2 - 1) // 2 - 1) // 2), odim),
|
| 234 |
+
pos_enc if pos_enc is not None else PositionalEncoding(odim, dropout_rate),
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
def forward(self, x, x_mask):
|
| 238 |
+
"""Subsample x.
|
| 239 |
+
|
| 240 |
+
Args:
|
| 241 |
+
x (torch.Tensor): Input tensor (#batch, time, idim).
|
| 242 |
+
x_mask (torch.Tensor): Input mask (#batch, 1, time).
|
| 243 |
+
|
| 244 |
+
Returns:
|
| 245 |
+
torch.Tensor: Subsampled tensor (#batch, time', odim),
|
| 246 |
+
where time' = time // 8.
|
| 247 |
+
torch.Tensor: Subsampled mask (#batch, 1, time'),
|
| 248 |
+
where time' = time // 8.
|
| 249 |
+
|
| 250 |
+
"""
|
| 251 |
+
x = x.unsqueeze(1) # (b, c, t, f)
|
| 252 |
+
x = self.conv(x)
|
| 253 |
+
b, c, t, f = x.size()
|
| 254 |
+
x = self.out(x.transpose(1, 2).contiguous().view(b, t, c * f))
|
| 255 |
+
if x_mask is None:
|
| 256 |
+
return x, None
|
| 257 |
+
return x, x_mask[:, :, :-2:2][:, :, :-2:2][:, :, :-2:2]
|
lscodec/models/conformer/swish.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
|
| 4 |
+
# Copyright 2020 Johns Hopkins University (Shinji Watanabe)
|
| 5 |
+
# Northwestern Polytechnical University (Pengcheng Guo)
|
| 6 |
+
# Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
| 7 |
+
|
| 8 |
+
"""Swish() activation function for Conformer."""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class Swish(torch.nn.Module):
|
| 14 |
+
"""Construct an Swish object."""
|
| 15 |
+
|
| 16 |
+
def forward(self, x):
|
| 17 |
+
"""Return Swich activation function."""
|
| 18 |
+
return x * torch.sigmoid(x)
|
lscodec/models/conformer/vgg2l.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""VGG2L module definition for transformer encoder."""
|
| 2 |
+
|
| 3 |
+
from typing import Tuple
|
| 4 |
+
from typing import Union
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class VGG2L(torch.nn.Module):
|
| 10 |
+
"""VGG2L module for custom encoder.
|
| 11 |
+
|
| 12 |
+
Args:
|
| 13 |
+
idim: Dimension of inputs
|
| 14 |
+
odim: Dimension of outputs
|
| 15 |
+
pos_enc: Positional encoding class
|
| 16 |
+
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __init__(self, idim: int, odim: int, pos_enc: torch.nn.Module = None):
|
| 20 |
+
"""Construct a VGG2L object."""
|
| 21 |
+
super().__init__()
|
| 22 |
+
|
| 23 |
+
self.vgg2l = torch.nn.Sequential(
|
| 24 |
+
torch.nn.Conv2d(1, 64, 3, stride=1, padding=1),
|
| 25 |
+
torch.nn.ReLU(),
|
| 26 |
+
torch.nn.Conv2d(64, 64, 3, stride=1, padding=1),
|
| 27 |
+
torch.nn.ReLU(),
|
| 28 |
+
torch.nn.MaxPool2d((3, 2)),
|
| 29 |
+
torch.nn.Conv2d(64, 128, 3, stride=1, padding=1),
|
| 30 |
+
torch.nn.ReLU(),
|
| 31 |
+
torch.nn.Conv2d(128, 128, 3, stride=1, padding=1),
|
| 32 |
+
torch.nn.ReLU(),
|
| 33 |
+
torch.nn.MaxPool2d((2, 2)),
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
if pos_enc is not None:
|
| 37 |
+
self.output = torch.nn.Sequential(
|
| 38 |
+
torch.nn.Linear(128 * ((idim // 2) // 2), odim), pos_enc
|
| 39 |
+
)
|
| 40 |
+
else:
|
| 41 |
+
self.output = torch.nn.Linear(128 * ((idim // 2) // 2), odim)
|
| 42 |
+
|
| 43 |
+
def forward(
|
| 44 |
+
self, x: torch.Tensor, x_mask: torch.Tensor
|
| 45 |
+
) -> Union[
|
| 46 |
+
Tuple[torch.Tensor, torch.Tensor],
|
| 47 |
+
Tuple[Tuple[torch.Tensor, torch.Tensor], torch.Tensor],
|
| 48 |
+
]:
|
| 49 |
+
"""VGG2L forward for x.
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
x: Input tensor (B, T, idim)
|
| 53 |
+
x_mask: Input mask (B, 1, T)
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
x: Output tensor (B, sub(T), odim)
|
| 57 |
+
or ((B, sub(T), odim), (B, sub(T), att_dim))
|
| 58 |
+
x_mask: Output mask (B, 1, sub(T))
|
| 59 |
+
|
| 60 |
+
"""
|
| 61 |
+
x = x.unsqueeze(1)
|
| 62 |
+
x = self.vgg2l(x)
|
| 63 |
+
|
| 64 |
+
b, c, t, f = x.size()
|
| 65 |
+
|
| 66 |
+
x = self.output(x.transpose(1, 2).contiguous().view(b, t, c * f))
|
| 67 |
+
|
| 68 |
+
if x_mask is not None:
|
| 69 |
+
x_mask = self.create_new_mask(x_mask)
|
| 70 |
+
|
| 71 |
+
return x, x_mask
|
| 72 |
+
|
| 73 |
+
def create_new_mask(self, x_mask: torch.Tensor) -> torch.Tensor:
|
| 74 |
+
"""Create a subsampled version of x_mask.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
x_mask: Input mask (B, 1, T)
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
x_mask: Output mask (B, 1, sub(T))
|
| 81 |
+
|
| 82 |
+
"""
|
| 83 |
+
x_t1 = x_mask.size(2) - (x_mask.size(2) % 3)
|
| 84 |
+
x_mask = x_mask[:, :, :x_t1][:, :, ::3]
|
| 85 |
+
|
| 86 |
+
x_t2 = x_mask.size(2) - (x_mask.size(2) % 2)
|
| 87 |
+
x_mask = x_mask[:, :, :x_t2][:, :, ::2]
|
| 88 |
+
|
| 89 |
+
return x_mask
|
lscodec/models/ctx_v2w.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# MIT License
|
| 3 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from typing import Union
|
| 7 |
+
from lscodec.models.conformer.decoder import Decoder as ConformerDecoder
|
| 8 |
+
|
| 9 |
+
from lscodec.utils import crop_seq
|
| 10 |
+
|
| 11 |
+
from lscodec.models.prompt_prenet import ConvPromptPrenet
|
| 12 |
+
from lscodec.models.hifigan import HiFiGANGenerator
|
| 13 |
+
import logging
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class CTXVEC2WAVFrontend(torch.nn.Module):
|
| 17 |
+
|
| 18 |
+
def __init__(self,
|
| 19 |
+
prompt_net_type,
|
| 20 |
+
num_mels,
|
| 21 |
+
vqvec_channels,
|
| 22 |
+
prompt_channels,
|
| 23 |
+
conformer_params):
|
| 24 |
+
|
| 25 |
+
super(CTXVEC2WAVFrontend, self).__init__()
|
| 26 |
+
|
| 27 |
+
if prompt_net_type == "ConvPromptPrenet":
|
| 28 |
+
self.prompt_prenet = ConvPromptPrenet(
|
| 29 |
+
embed=prompt_channels,
|
| 30 |
+
conv_layers=[(128, 3, 1, 1), (256, 5, 1, 2), (512, 5, 1, 2), (conformer_params["attention_dim"], 3, 1, 1)],
|
| 31 |
+
dropout=0.1,
|
| 32 |
+
skip_connections=True,
|
| 33 |
+
residual_scale=0.25,
|
| 34 |
+
non_affine_group_norm=False,
|
| 35 |
+
conv_bias=True,
|
| 36 |
+
activation=torch.nn.ReLU()
|
| 37 |
+
)
|
| 38 |
+
elif prompt_net_type == "Conv1d":
|
| 39 |
+
self.prompt_prenet = torch.nn.Conv1d(prompt_channels, conformer_params["attention_dim"], kernel_size=5, padding=2) # , padding=2)
|
| 40 |
+
else:
|
| 41 |
+
raise NotImplementedError
|
| 42 |
+
|
| 43 |
+
self.input_dim = vqvec_channels
|
| 44 |
+
self.encoder1 = ConformerDecoder(vqvec_channels, input_layer='linear', **conformer_params)
|
| 45 |
+
|
| 46 |
+
self.hidden_proj = torch.nn.Linear(conformer_params["attention_dim"], conformer_params["attention_dim"])
|
| 47 |
+
|
| 48 |
+
self.encoder2 = ConformerDecoder(0, input_layer=None, **conformer_params)
|
| 49 |
+
self.mel_proj = torch.nn.Linear(conformer_params["attention_dim"], num_mels)
|
| 50 |
+
|
| 51 |
+
def forward(self, vqvec, prompt, mask=None, prompt_mask=None):
|
| 52 |
+
"""
|
| 53 |
+
params:
|
| 54 |
+
vqvec: sequence of VQ-vectors.
|
| 55 |
+
prompt: sequence of mel-spectrogram prompt (acoustic context)
|
| 56 |
+
mask: mask of the vqvec. True or 1 stands for valid values.
|
| 57 |
+
prompt_mask: mask of the prompt.
|
| 58 |
+
vqvec and prompt are of shape [B, D, T]. All masks are of shape [B, T].
|
| 59 |
+
returns:
|
| 60 |
+
enc_out: the input to the CTX-vec2wav Generator (HifiGAN);
|
| 61 |
+
mel: the frontend predicted mel spectrogram (for faster convergence);
|
| 62 |
+
"""
|
| 63 |
+
prompt = self.prompt_prenet(prompt.transpose(1, 2)).transpose(1, 2)
|
| 64 |
+
|
| 65 |
+
if mask is not None:
|
| 66 |
+
mask = mask.unsqueeze(-2)
|
| 67 |
+
if prompt_mask is not None:
|
| 68 |
+
prompt_mask = prompt_mask.unsqueeze(-2)
|
| 69 |
+
enc_out, _ = self.encoder1(vqvec, mask, prompt, prompt_mask)
|
| 70 |
+
|
| 71 |
+
h = self.hidden_proj(enc_out)
|
| 72 |
+
|
| 73 |
+
enc_out, _ = self.encoder2(h, mask, prompt, prompt_mask)
|
| 74 |
+
mel = self.mel_proj(enc_out) # (B, L, 80)
|
| 75 |
+
|
| 76 |
+
return enc_out, mel
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class CTXVEC2WAVGenerator(torch.nn.Module):
|
| 80 |
+
|
| 81 |
+
def __init__(self, frontend: CTXVEC2WAVFrontend, backend: HiFiGANGenerator):
|
| 82 |
+
|
| 83 |
+
super(CTXVEC2WAVGenerator, self).__init__()
|
| 84 |
+
self.frontend = frontend
|
| 85 |
+
self.backend = backend
|
| 86 |
+
|
| 87 |
+
def forward(self, vqvec, prompt, mask=None, prompt_mask=None, crop_len=0, crop_offsets=None):
|
| 88 |
+
"""
|
| 89 |
+
:param vqvec: (torch.Tensor) The shape is (B, L, D). Sequence of VQ-vectors.
|
| 90 |
+
:param prompt: (torch.Tensor) The shape is (B, L', D'). Sequence of prompt features (acoustic context)
|
| 91 |
+
:param mask: (torch.Tensor) The dtype is torch.bool. The shape is (B, L). True or 1 stands for valid values in `vqvec`.
|
| 92 |
+
:param prompt_mask: (torch.Tensor) The dtype is torch.bool. The shape is (B, L'). True or 1 stands for valid values in `prompt`.
|
| 93 |
+
:return: frontend predicted mel spectrogram; reconstructed waveform.
|
| 94 |
+
"""
|
| 95 |
+
h, mel, _ = self.frontend(vqvec, prompt, mask=mask, prompt_mask=prompt_mask) # (B, L, adim), (B, L, 80)
|
| 96 |
+
if mask is not None:
|
| 97 |
+
h = h.masked_fill(~mask.unsqueeze(-1), 0)
|
| 98 |
+
h = h.transpose(1, 2)
|
| 99 |
+
if crop_len > 0:
|
| 100 |
+
h = crop_seq(h, crop_offsets, crop_len)
|
| 101 |
+
wav = self.backend(h) # (B, C, T)
|
| 102 |
+
return mel, None, wav
|
| 103 |
+
|
| 104 |
+
def inference(self, vqvec, prompt, **kwargs):
|
| 105 |
+
h, mel = self.frontend(vqvec, prompt)
|
| 106 |
+
wav = self.backend.inference(h, **kwargs)
|
| 107 |
+
|
| 108 |
+
return mel, None, wav
|
lscodec/models/fairseq_modules/__init__.py
ADDED
|
File without changes
|
lscodec/models/fairseq_modules/fp32_group_norm.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
"""
|
| 6 |
+
Layer norm done in fp32 (for fp16 training)
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
from math import ceil, floor
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def torch_quantile(
|
| 19 |
+
input: torch.Tensor,
|
| 20 |
+
q: float | torch.Tensor,
|
| 21 |
+
dim: int | None = None,
|
| 22 |
+
keepdim: bool = False,
|
| 23 |
+
*,
|
| 24 |
+
interpolation: str = "nearest",
|
| 25 |
+
out: torch.Tensor | None = None,
|
| 26 |
+
) -> torch.Tensor:
|
| 27 |
+
"""Better torch.quantile for one SCALAR quantile.
|
| 28 |
+
|
| 29 |
+
Using torch.kthvalue. Better than torch.quantile because:
|
| 30 |
+
- No 2**24 input size limit (pytorch/issues/67592),
|
| 31 |
+
- Much faster, at least on big input sizes.
|
| 32 |
+
|
| 33 |
+
Arguments:
|
| 34 |
+
input (torch.Tensor): See torch.quantile.
|
| 35 |
+
q (float): See torch.quantile. Supports only scalar input
|
| 36 |
+
currently.
|
| 37 |
+
dim (int | None): See torch.quantile.
|
| 38 |
+
keepdim (bool): See torch.quantile. Supports only False
|
| 39 |
+
currently.
|
| 40 |
+
interpolation: {"nearest", "lower", "higher"}
|
| 41 |
+
See torch.quantile.
|
| 42 |
+
out (torch.Tensor | None): See torch.quantile. Supports only
|
| 43 |
+
None currently.
|
| 44 |
+
"""
|
| 45 |
+
# Sanitization: q
|
| 46 |
+
try:
|
| 47 |
+
q = float(q)
|
| 48 |
+
assert 0 <= q <= 1
|
| 49 |
+
except Exception:
|
| 50 |
+
raise ValueError(f"Only scalar input 0<=q<=1 is currently supported (got {q})!")
|
| 51 |
+
|
| 52 |
+
# Sanitization: dim
|
| 53 |
+
# Because one cannot pass `dim=None` to `squeeze()` or `kthvalue()`
|
| 54 |
+
if dim_was_none := dim is None:
|
| 55 |
+
dim = 0
|
| 56 |
+
input = input.reshape((-1,) + (1,) * (input.ndim - 1))
|
| 57 |
+
|
| 58 |
+
# Sanitization: inteporlation
|
| 59 |
+
if interpolation == "nearest":
|
| 60 |
+
inter = round
|
| 61 |
+
elif interpolation == "lower":
|
| 62 |
+
inter = floor
|
| 63 |
+
elif interpolation == "higher":
|
| 64 |
+
inter = ceil
|
| 65 |
+
else:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
"Supported interpolations currently are {'nearest', 'lower', 'higher'} "
|
| 68 |
+
f"(got '{interpolation}')!"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Sanitization: out
|
| 72 |
+
if out is not None:
|
| 73 |
+
raise ValueError(f"Only None value is currently supported for out (got {out})!")
|
| 74 |
+
|
| 75 |
+
# Logic
|
| 76 |
+
k = inter(q * (input.shape[dim] - 1)) + 1
|
| 77 |
+
out = torch.kthvalue(input, k, dim, keepdim=True, out=out)[0]
|
| 78 |
+
|
| 79 |
+
# Rectification: keepdim
|
| 80 |
+
if keepdim:
|
| 81 |
+
return out
|
| 82 |
+
if dim_was_none:
|
| 83 |
+
return out.squeeze()
|
| 84 |
+
else:
|
| 85 |
+
return out.squeeze(dim)
|
| 86 |
+
|
| 87 |
+
return out
|
| 88 |
+
|
| 89 |
+
class Fp32GroupNorm(nn.GroupNorm):
|
| 90 |
+
def __init__(self, *args, **kwargs):
|
| 91 |
+
super().__init__(*args, **kwargs)
|
| 92 |
+
|
| 93 |
+
def forward(self, input):
|
| 94 |
+
# print("In FP32 GroupNorm", input[0].mean(), input[0].std(), input[0].min(), input[0].max())
|
| 95 |
+
output = F.group_norm(
|
| 96 |
+
input.float(),
|
| 97 |
+
self.num_groups,
|
| 98 |
+
self.weight.float() if self.weight is not None else None,
|
| 99 |
+
self.bias.float() if self.bias is not None else None,
|
| 100 |
+
self.eps,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# NOTE: dirty hack
|
| 104 |
+
# if input.max()<1000:
|
| 105 |
+
# output = F.group_norm(
|
| 106 |
+
# input.float(),
|
| 107 |
+
# self.num_groups,
|
| 108 |
+
# self.weight.float() if self.weight is not None else None,
|
| 109 |
+
# self.bias.float() if self.bias is not None else None,
|
| 110 |
+
# self.eps,
|
| 111 |
+
# )
|
| 112 |
+
# else:
|
| 113 |
+
# print("Will remove outliers in a dirty way in FP32GroupNorm")
|
| 114 |
+
# q1 = torch_quantile(input.view(-1), 0.25)
|
| 115 |
+
# q3 = torch_quantile(input.view(-1), 0.75)
|
| 116 |
+
# IQR = q3-q1
|
| 117 |
+
# upper_bound = q3 + 1.5 * IQR
|
| 118 |
+
# lower_bound = q1 - 1.5 * IQR
|
| 119 |
+
# # correct_mean = input[(lower_bound<input) & (input<upper_bound)].mean()
|
| 120 |
+
# # correct_std = input[(lower_bound<input) & (input<upper_bound)].std()
|
| 121 |
+
# # print(f"After removing outliers, the correct mean and std are {correct_mean}, {correct_std}")
|
| 122 |
+
# # output = (input-correct_mean)/(correct_std+self.eps) * self.weight.float().unsqueeze(0).unsqueeze(-1) + self.bias.unsqueeze(0).unsqueeze(-1)
|
| 123 |
+
|
| 124 |
+
# input[(lower_bound<input) & (input<upper_bound)] = 0.0
|
| 125 |
+
# output = F.group_norm(
|
| 126 |
+
# input.float(),
|
| 127 |
+
# self.num_groups,
|
| 128 |
+
# self.weight.float() if self.weight is not None else None,
|
| 129 |
+
# self.bias.float() if self.bias is not None else None,
|
| 130 |
+
# self.eps,
|
| 131 |
+
# )
|
| 132 |
+
|
| 133 |
+
return output.type_as(input)
|
lscodec/models/fairseq_modules/gumbel_vector_quantizer.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class GumbelVectorQuantizer(nn.Module):
|
| 12 |
+
def __init__(
|
| 13 |
+
self,
|
| 14 |
+
dim,
|
| 15 |
+
num_vars,
|
| 16 |
+
temp,
|
| 17 |
+
groups,
|
| 18 |
+
combine_groups,
|
| 19 |
+
vq_dim,
|
| 20 |
+
time_first,
|
| 21 |
+
activation=nn.GELU(),
|
| 22 |
+
weight_proj_depth=1,
|
| 23 |
+
weight_proj_factor=1,
|
| 24 |
+
hard=True,
|
| 25 |
+
std=0,
|
| 26 |
+
):
|
| 27 |
+
"""Vector quantization using gumbel softmax
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
dim: input dimension (channels)
|
| 31 |
+
num_vars: number of quantized vectors per group
|
| 32 |
+
temp: temperature for training. this should be a tuple of 3 elements: (start, stop, decay factor)
|
| 33 |
+
groups: number of groups for vector quantization
|
| 34 |
+
combine_groups: whether to use the vectors for all groups
|
| 35 |
+
vq_dim: dimensionality of the resulting quantized vector
|
| 36 |
+
time_first: if true, expect input in BxTxC format, otherwise in BxCxT
|
| 37 |
+
activation: what activation to use (should be a module). this is only used if weight_proj_depth is > 1
|
| 38 |
+
weight_proj_depth: number of layers (with activation in between) to project input before computing logits
|
| 39 |
+
weight_proj_factor: this is used only if weight_proj_depth is > 1. scales the inner dimensionality of
|
| 40 |
+
projections by this factor
|
| 41 |
+
"""
|
| 42 |
+
super().__init__()
|
| 43 |
+
|
| 44 |
+
self.groups = groups
|
| 45 |
+
self.combine_groups = combine_groups
|
| 46 |
+
self.input_dim = dim
|
| 47 |
+
self.num_vars = num_vars
|
| 48 |
+
self.time_first = time_first
|
| 49 |
+
self.hard = hard
|
| 50 |
+
|
| 51 |
+
assert (
|
| 52 |
+
vq_dim % groups == 0
|
| 53 |
+
), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
|
| 54 |
+
|
| 55 |
+
var_dim = vq_dim // groups
|
| 56 |
+
num_groups = groups if not combine_groups else 1
|
| 57 |
+
|
| 58 |
+
self.vars = nn.Parameter(torch.FloatTensor(1, num_groups * num_vars, var_dim))
|
| 59 |
+
if std == 0:
|
| 60 |
+
nn.init.uniform_(self.vars)
|
| 61 |
+
else:
|
| 62 |
+
nn.init.normal_(self.vars, mean=0, std=std)
|
| 63 |
+
|
| 64 |
+
if weight_proj_depth > 1:
|
| 65 |
+
|
| 66 |
+
def block(input_dim, output_dim):
|
| 67 |
+
return nn.Sequential(nn.Linear(input_dim, output_dim), activation)
|
| 68 |
+
|
| 69 |
+
inner_dim = self.input_dim * weight_proj_factor
|
| 70 |
+
self.weight_proj = nn.Sequential(
|
| 71 |
+
*[
|
| 72 |
+
block(self.input_dim if i == 0 else inner_dim, inner_dim)
|
| 73 |
+
for i in range(weight_proj_depth - 1)
|
| 74 |
+
],
|
| 75 |
+
nn.Linear(inner_dim, groups * num_vars),
|
| 76 |
+
)
|
| 77 |
+
else:
|
| 78 |
+
self.weight_proj = nn.Linear(self.input_dim, groups * num_vars)
|
| 79 |
+
nn.init.normal_(self.weight_proj.weight, mean=0, std=1)
|
| 80 |
+
nn.init.zeros_(self.weight_proj.bias)
|
| 81 |
+
|
| 82 |
+
if isinstance(temp, str):
|
| 83 |
+
import ast
|
| 84 |
+
|
| 85 |
+
temp = ast.literal_eval(temp)
|
| 86 |
+
assert len(temp) == 3, f"{temp}, {len(temp)}"
|
| 87 |
+
|
| 88 |
+
self.max_temp, self.min_temp, self.temp_decay = temp
|
| 89 |
+
self.curr_temp = self.max_temp
|
| 90 |
+
self.codebook_indices = None
|
| 91 |
+
|
| 92 |
+
def set_num_updates(self, num_updates):
|
| 93 |
+
self.curr_temp = max(
|
| 94 |
+
self.max_temp * self.temp_decay**num_updates, self.min_temp
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
def get_codebook_indices(self):
|
| 98 |
+
if self.codebook_indices is None:
|
| 99 |
+
from itertools import product
|
| 100 |
+
|
| 101 |
+
p = [range(self.num_vars)] * self.groups
|
| 102 |
+
inds = list(product(*p))
|
| 103 |
+
self.codebook_indices = torch.tensor(
|
| 104 |
+
inds, dtype=torch.long, device=self.vars.device
|
| 105 |
+
).flatten()
|
| 106 |
+
|
| 107 |
+
if not self.combine_groups:
|
| 108 |
+
self.codebook_indices = self.codebook_indices.view(
|
| 109 |
+
self.num_vars**self.groups, -1
|
| 110 |
+
)
|
| 111 |
+
for b in range(1, self.groups):
|
| 112 |
+
self.codebook_indices[:, b] += self.num_vars * b
|
| 113 |
+
self.codebook_indices = self.codebook_indices.flatten()
|
| 114 |
+
return self.codebook_indices
|
| 115 |
+
|
| 116 |
+
def codebook(self):
|
| 117 |
+
indices = self.get_codebook_indices()
|
| 118 |
+
return (
|
| 119 |
+
self.vars.squeeze(0)
|
| 120 |
+
.index_select(0, indices)
|
| 121 |
+
.view(self.num_vars**self.groups, -1)
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
def sample_from_codebook(self, b, n):
|
| 125 |
+
indices = self.get_codebook_indices()
|
| 126 |
+
indices = indices.view(-1, self.groups)
|
| 127 |
+
cb_size = indices.size(0)
|
| 128 |
+
assert (
|
| 129 |
+
n < cb_size
|
| 130 |
+
), f"sample size {n} is greater than size of codebook {cb_size}"
|
| 131 |
+
sample_idx = torch.randint(low=0, high=cb_size, size=(b * n,))
|
| 132 |
+
indices = indices[sample_idx]
|
| 133 |
+
|
| 134 |
+
z = self.vars.squeeze(0).index_select(0, indices.flatten()).view(b, n, -1)
|
| 135 |
+
return z
|
| 136 |
+
|
| 137 |
+
def to_codebook_index(self, indices):
|
| 138 |
+
res = indices.new_full(indices.shape[:-1], 0)
|
| 139 |
+
for i in range(self.groups):
|
| 140 |
+
exponent = self.groups - i - 1
|
| 141 |
+
res += indices[..., i] * (self.num_vars**exponent)
|
| 142 |
+
return res
|
| 143 |
+
|
| 144 |
+
def forward_idx(self, x):
|
| 145 |
+
res = self.forward(x, produce_targets=True)
|
| 146 |
+
return res["x"], res["targets"]
|
| 147 |
+
|
| 148 |
+
def forward(self, x, produce_targets=True):
|
| 149 |
+
|
| 150 |
+
result = {"num_vars": self.num_vars * self.groups}
|
| 151 |
+
|
| 152 |
+
# if not self.time_first:
|
| 153 |
+
# x = x.transpose(1, 2)
|
| 154 |
+
|
| 155 |
+
bsz_tsz, fsz = x.shape
|
| 156 |
+
# x = x.reshape(-1, fsz)
|
| 157 |
+
x = self.weight_proj(x)
|
| 158 |
+
x = x.view(bsz_tsz * self.groups, -1)
|
| 159 |
+
|
| 160 |
+
with torch.no_grad():
|
| 161 |
+
_, k = x.max(-1)
|
| 162 |
+
hard_x = (
|
| 163 |
+
x.new_zeros(*x.shape)
|
| 164 |
+
.scatter_(-1, k.view(-1, 1), 1.0)
|
| 165 |
+
.view(bsz_tsz, self.groups, -1)
|
| 166 |
+
)
|
| 167 |
+
hard_probs = torch.mean(hard_x.float(), dim=0)
|
| 168 |
+
result["code_perplexity"] = torch.exp(
|
| 169 |
+
-torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
|
| 170 |
+
).sum()
|
| 171 |
+
|
| 172 |
+
avg_probs = torch.softmax(
|
| 173 |
+
x.view(bsz_tsz, self.groups, -1).float(), dim=-1
|
| 174 |
+
).mean(dim=0)
|
| 175 |
+
result["prob_perplexity"] = torch.exp(
|
| 176 |
+
-torch.sum(avg_probs * torch.log(avg_probs + 1e-7), dim=-1)
|
| 177 |
+
).sum()
|
| 178 |
+
|
| 179 |
+
result["temp"] = self.curr_temp
|
| 180 |
+
|
| 181 |
+
if self.training:
|
| 182 |
+
x = F.gumbel_softmax(x.float(), tau=self.curr_temp, hard=self.hard).type_as(
|
| 183 |
+
x
|
| 184 |
+
)
|
| 185 |
+
else:
|
| 186 |
+
x = hard_x
|
| 187 |
+
|
| 188 |
+
x = x.view(bsz_tsz, -1)
|
| 189 |
+
|
| 190 |
+
vars = self.vars
|
| 191 |
+
if self.combine_groups:
|
| 192 |
+
vars = vars.repeat(1, self.groups, 1)
|
| 193 |
+
|
| 194 |
+
if produce_targets:
|
| 195 |
+
result["targets"] = (
|
| 196 |
+
x.view(bsz_tsz * self.groups, -1)
|
| 197 |
+
.argmax(dim=-1)
|
| 198 |
+
.detach()
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
x = x.unsqueeze(-1) * vars
|
| 202 |
+
x = x.view(bsz_tsz, self.groups, self.num_vars, -1)
|
| 203 |
+
x = x.sum(-2)
|
| 204 |
+
x = x.view(bsz_tsz, -1)
|
| 205 |
+
|
| 206 |
+
# if not self.time_first:
|
| 207 |
+
# x = x.transpose(1, 2) # BTC -> BCT
|
| 208 |
+
|
| 209 |
+
result["x"] = x
|
| 210 |
+
|
| 211 |
+
# return result
|
| 212 |
+
return x, result['targets'], None
|
lscodec/models/fairseq_modules/kmeans_vector_quantizer.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
from lscodec.models.fairseq_modules.fp32_group_norm import Fp32GroupNorm
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class KmeansVectorQuantizer(nn.Module):
|
| 12 |
+
def __init__(
|
| 13 |
+
self, dim, num_vars, groups, combine_groups, vq_dim, time_first, gamma=0.25
|
| 14 |
+
):
|
| 15 |
+
"""Vector quantization using straight pass-through estimator (i.e. kmeans)
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
dim: input dimension (channels)
|
| 19 |
+
num_vars: number of quantized vectors per group
|
| 20 |
+
groups: number of groups for vector quantization
|
| 21 |
+
combine_groups: whether to use the vectors for all groups
|
| 22 |
+
vq_dim: dimensionality of the resulting quantized vector
|
| 23 |
+
time_first: if true, expect input in BxTxC format, otherwise in BxCxT
|
| 24 |
+
gamma: commitment loss coefficient
|
| 25 |
+
"""
|
| 26 |
+
super().__init__()
|
| 27 |
+
|
| 28 |
+
self.groups = groups
|
| 29 |
+
self.combine_groups = combine_groups
|
| 30 |
+
self.input_dim = dim
|
| 31 |
+
self.num_vars = num_vars
|
| 32 |
+
self.vq_dim = vq_dim
|
| 33 |
+
self.time_first = time_first
|
| 34 |
+
|
| 35 |
+
assert (
|
| 36 |
+
vq_dim % groups == 0
|
| 37 |
+
), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
|
| 38 |
+
|
| 39 |
+
self.var_dim = vq_dim // groups
|
| 40 |
+
num_groups = groups if not combine_groups else 1
|
| 41 |
+
|
| 42 |
+
self.embedding = nn.Parameter(
|
| 43 |
+
0.01 * torch.randn(num_vars, num_groups, self.var_dim)
|
| 44 |
+
)
|
| 45 |
+
self.projection = nn.Sequential(
|
| 46 |
+
nn.Conv1d(dim, dim, kernel_size=1, groups=groups, bias=False),
|
| 47 |
+
Fp32GroupNorm(groups, dim),
|
| 48 |
+
)
|
| 49 |
+
self.gamma = gamma
|
| 50 |
+
self.mse_mean = nn.MSELoss(reduction="mean")
|
| 51 |
+
|
| 52 |
+
def _pass_grad(self, x, y):
|
| 53 |
+
"""Manually set gradient for backward pass.
|
| 54 |
+
for y = f(x), ensure that during the backward pass,
|
| 55 |
+
dL/dy = dL/dx regardless of f(x).
|
| 56 |
+
Returns:
|
| 57 |
+
y, with the gradient forced to be dL/dy = dL/dx.
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
return y.detach() + (x - x.detach())
|
| 61 |
+
|
| 62 |
+
@property
|
| 63 |
+
def expand_embedding(self):
|
| 64 |
+
if self.combine_groups:
|
| 65 |
+
return self.embedding.expand(self.num_vars, self.groups, self.var_dim)
|
| 66 |
+
return self.embedding
|
| 67 |
+
|
| 68 |
+
def forward_idx(self, x):
|
| 69 |
+
res = self.forward(x, produce_targets=True)
|
| 70 |
+
return res["x"], res["targets"]
|
| 71 |
+
|
| 72 |
+
def forward_idx_limited(self, x, valid_label2vqidx_mat):
|
| 73 |
+
# mask_mat = convert_valid_label2vqidx_to_mask_mat(valid_label2vqidx)
|
| 74 |
+
res = self.forward_group2(x, mask_mat=valid_label2vqidx_mat, produce_targets=True)
|
| 75 |
+
return res['x'], res['targets']
|
| 76 |
+
|
| 77 |
+
def forward(self, x, produce_targets=False):
|
| 78 |
+
|
| 79 |
+
result = {"num_vars": self.num_vars}
|
| 80 |
+
|
| 81 |
+
if self.time_first:
|
| 82 |
+
x = x.transpose(1, 2)
|
| 83 |
+
|
| 84 |
+
bsz, fsz, tsz = x.shape
|
| 85 |
+
|
| 86 |
+
ze = self.projection(x)
|
| 87 |
+
ze_ = ze.view(bsz, self.groups, self.var_dim, tsz).permute(0, 3, 1, 2)
|
| 88 |
+
d = (
|
| 89 |
+
(ze_.unsqueeze(0) - self.expand_embedding.unsqueeze(1).unsqueeze(1))
|
| 90 |
+
.view(self.num_vars, bsz, tsz, self.groups, -1)
|
| 91 |
+
.norm(dim=-1, p=2)
|
| 92 |
+
)
|
| 93 |
+
idx = d.argmin(dim=0)
|
| 94 |
+
zq = (
|
| 95 |
+
torch.stack(
|
| 96 |
+
[
|
| 97 |
+
self.expand_embedding[idx[..., group], group]
|
| 98 |
+
for group in range(self.groups)
|
| 99 |
+
],
|
| 100 |
+
dim=-2,
|
| 101 |
+
)
|
| 102 |
+
.view(bsz, tsz, self.groups * self.var_dim)
|
| 103 |
+
.permute(0, 2, 1)
|
| 104 |
+
)
|
| 105 |
+
assert ze.shape == zq.shape, (ze.shape, zq.shape)
|
| 106 |
+
x = self._pass_grad(ze, zq)
|
| 107 |
+
|
| 108 |
+
with torch.no_grad():
|
| 109 |
+
hard_x = (
|
| 110 |
+
idx.new_zeros(bsz * tsz * self.groups, self.num_vars)
|
| 111 |
+
.scatter_(-1, idx.view(-1, 1), 1.0)
|
| 112 |
+
.view(bsz * tsz, self.groups, -1)
|
| 113 |
+
)
|
| 114 |
+
hard_probs = torch.mean(hard_x.float(), dim=0)
|
| 115 |
+
result["code_perplexity"] = torch.exp(
|
| 116 |
+
-torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
|
| 117 |
+
).sum()
|
| 118 |
+
|
| 119 |
+
if produce_targets:
|
| 120 |
+
result["targets"] = idx
|
| 121 |
+
|
| 122 |
+
if self.time_first:
|
| 123 |
+
x = x.transpose(1, 2) # BCT -> BTC
|
| 124 |
+
result["x"] = x
|
| 125 |
+
|
| 126 |
+
ze = ze.float()
|
| 127 |
+
zq = zq.float()
|
| 128 |
+
latent_loss = self.mse_mean(zq, ze.detach())
|
| 129 |
+
commitment_loss = self.mse_mean(ze, zq.detach())
|
| 130 |
+
|
| 131 |
+
result["kmeans_loss"] = latent_loss + self.gamma * commitment_loss
|
| 132 |
+
|
| 133 |
+
return result
|
| 134 |
+
|
| 135 |
+
def forward_group2(self, x, mask_mat=None, produce_targets=False, inf=999999):
|
| 136 |
+
assert mask_mat is not None
|
| 137 |
+
|
| 138 |
+
result = {"num_vars": self.num_vars}
|
| 139 |
+
|
| 140 |
+
if self.time_first:
|
| 141 |
+
x = x.transpose(1, 2)
|
| 142 |
+
|
| 143 |
+
bsz, fsz, tsz = x.shape
|
| 144 |
+
|
| 145 |
+
ze = self.projection(x)
|
| 146 |
+
|
| 147 |
+
ze_ = ze.view(bsz, self.groups, self.var_dim, tsz).permute(0, 3, 1, 2)
|
| 148 |
+
ze_0 = ze_[:, :, 0, None, :]
|
| 149 |
+
ze_1 = ze_[:, :, 1, None, :] # 4 * 100 * 320 * 128
|
| 150 |
+
cb0_expand = self.expand_embedding[:, 0, :]
|
| 151 |
+
cb1_expand = self.expand_embedding[:, 1, :] # 320 * 128
|
| 152 |
+
dist_0 = ((ze_0 - cb0_expand) ** 2).sum(dim=-1)[:, :, :, None]
|
| 153 |
+
dist_1 = ((ze_1 - cb1_expand) ** 2).sum(dim=-1)[:, :, None, :]
|
| 154 |
+
res_0, res_1 = torch.broadcast_tensors(dist_0, dist_1)
|
| 155 |
+
mask_mat = (1 - mask_mat[None, None, :, :].to(res_0.device) * torch.ones_like(res_0)) * inf
|
| 156 |
+
# mask_mat = mask_mat.to(x.device)
|
| 157 |
+
d_flt = (res_0 + res_1 + mask_mat).view(bsz, tsz, -1)
|
| 158 |
+
idx_flt = torch.argmin(d_flt, dim=-1)
|
| 159 |
+
idx = torch.stack((idx_flt // self.num_vars, idx_flt % self.num_vars), dim=-1)
|
| 160 |
+
|
| 161 |
+
zq = (
|
| 162 |
+
torch.stack(
|
| 163 |
+
[
|
| 164 |
+
self.expand_embedding[idx[..., group], group]
|
| 165 |
+
for group in range(self.groups)
|
| 166 |
+
],
|
| 167 |
+
dim=-2,
|
| 168 |
+
)
|
| 169 |
+
.view(bsz, tsz, self.groups * self.var_dim)
|
| 170 |
+
.permute(0, 2, 1)
|
| 171 |
+
)
|
| 172 |
+
assert ze.shape == zq.shape, (ze.shape, zq.shape)
|
| 173 |
+
x = self._pass_grad(ze, zq)
|
| 174 |
+
|
| 175 |
+
with torch.no_grad():
|
| 176 |
+
hard_x = (
|
| 177 |
+
idx.new_zeros(bsz * tsz * self.groups, self.num_vars)
|
| 178 |
+
.scatter_(-1, idx.view(-1, 1), 1.0)
|
| 179 |
+
.view(bsz * tsz, self.groups, -1)
|
| 180 |
+
)
|
| 181 |
+
hard_probs = torch.mean(hard_x.float(), dim=0)
|
| 182 |
+
result["code_perplexity"] = torch.exp(
|
| 183 |
+
-torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
|
| 184 |
+
).sum()
|
| 185 |
+
|
| 186 |
+
if produce_targets:
|
| 187 |
+
result["targets"] = idx
|
| 188 |
+
|
| 189 |
+
if self.time_first:
|
| 190 |
+
x = x.transpose(1, 2) # BCT -> BTC
|
| 191 |
+
result["x"] = x
|
| 192 |
+
|
| 193 |
+
ze = ze.float()
|
| 194 |
+
zq = zq.float()
|
| 195 |
+
latent_loss = self.mse_mean(zq, ze.detach())
|
| 196 |
+
commitment_loss = self.mse_mean(ze, zq.detach())
|
| 197 |
+
|
| 198 |
+
result["kmeans_loss"] = latent_loss + self.gamma * commitment_loss
|
| 199 |
+
|
| 200 |
+
return result
|
| 201 |
+
|
| 202 |
+
if __name__ == "__main__":
|
| 203 |
+
quantizer = KmeansVectorQuantizer(dim=256, num_vars=320, groups=2, combine_groups=False, vq_dim=256, time_first=True)
|
| 204 |
+
x = torch.ones(4, 100, 256)
|
| 205 |
+
result = quantizer.forward_group2(x, mask_mat=torch.randint(0, 2, (320, 320)))
|
| 206 |
+
print(result)
|
lscodec/models/fairseq_modules/layer_norm.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
from apex.normalization import FusedLayerNorm as _FusedLayerNorm
|
| 12 |
+
|
| 13 |
+
has_fused_layernorm = True
|
| 14 |
+
|
| 15 |
+
class FusedLayerNorm(_FusedLayerNorm):
|
| 16 |
+
@torch.jit.unused
|
| 17 |
+
def forward(self, x):
|
| 18 |
+
if not x.is_cuda:
|
| 19 |
+
return super().forward(x)
|
| 20 |
+
else:
|
| 21 |
+
with torch.cuda.device(x.device):
|
| 22 |
+
return super().forward(x)
|
| 23 |
+
|
| 24 |
+
except ImportError:
|
| 25 |
+
has_fused_layernorm = False
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def LayerNorm(normalized_shape, eps=1e-5, elementwise_affine=True, export=False):
|
| 29 |
+
if torch.jit.is_scripting() or torch.jit.is_tracing():
|
| 30 |
+
export = True
|
| 31 |
+
if not export and torch.cuda.is_available() and has_fused_layernorm:
|
| 32 |
+
return FusedLayerNorm(normalized_shape, eps, elementwise_affine)
|
| 33 |
+
return torch.nn.LayerNorm(normalized_shape, eps, elementwise_affine)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class Fp32LayerNorm(nn.LayerNorm):
|
| 37 |
+
def __init__(self, *args, **kwargs):
|
| 38 |
+
super().__init__(*args, **kwargs)
|
| 39 |
+
|
| 40 |
+
def forward(self, input):
|
| 41 |
+
output = F.layer_norm(
|
| 42 |
+
input.float(),
|
| 43 |
+
self.normalized_shape,
|
| 44 |
+
self.weight.float() if self.weight is not None else None,
|
| 45 |
+
self.bias.float() if self.bias is not None else None,
|
| 46 |
+
self.eps,
|
| 47 |
+
)
|
| 48 |
+
return output.type_as(input)
|
lscodec/models/fairseq_modules/transpose_last.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
"""
|
| 6 |
+
transpose last 2 dimensions of the input
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TransposeLast(nn.Module):
|
| 13 |
+
def __init__(self, deconstruct_idx=None, tranpose_dim=-2):
|
| 14 |
+
super().__init__()
|
| 15 |
+
self.deconstruct_idx = deconstruct_idx
|
| 16 |
+
self.tranpose_dim = tranpose_dim
|
| 17 |
+
|
| 18 |
+
def forward(self, x):
|
| 19 |
+
if self.deconstruct_idx is not None:
|
| 20 |
+
x = x[self.deconstruct_idx]
|
| 21 |
+
return x.transpose(self.tranpose_dim, -1)
|
lscodec/models/feature_extractor.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
# Portions of this code are a modification of the fairseq project.
|
| 8 |
+
# The original source code can be found at:
|
| 9 |
+
# https://github.com/facebookresearch/fairseq/blob/d13e14a800bb588e5a77fb4e551f554ff9b24a72/fairseq/models/wav2vec/wav2vec.py
|
| 10 |
+
#
|
| 11 |
+
# Copyright (c) 2025 Yiwei Guo
|
| 12 |
+
# Licensed under the MIT license.
|
| 13 |
+
|
| 14 |
+
import math
|
| 15 |
+
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
from lscodec.models.fairseq_modules.fp32_group_norm import Fp32GroupNorm
|
| 18 |
+
from lscodec.models.fairseq_modules.layer_norm import Fp32LayerNorm
|
| 19 |
+
from lscodec.models.fairseq_modules.transpose_last import TransposeLast
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def norm_block(is_layer_norm, dim, affine=True):
|
| 24 |
+
if is_layer_norm:
|
| 25 |
+
mod = nn.Sequential(
|
| 26 |
+
TransposeLast(),
|
| 27 |
+
Fp32LayerNorm(dim, elementwise_affine=affine),
|
| 28 |
+
TransposeLast(),
|
| 29 |
+
)
|
| 30 |
+
else:
|
| 31 |
+
mod = Fp32GroupNorm(1, dim, affine=affine)
|
| 32 |
+
|
| 33 |
+
return mod
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class ZeroPad1d(nn.Module):
|
| 37 |
+
def __init__(self, pad_left, pad_right):
|
| 38 |
+
super().__init__()
|
| 39 |
+
self.pad_left = pad_left
|
| 40 |
+
self.pad_right = pad_right
|
| 41 |
+
|
| 42 |
+
def forward(self, x):
|
| 43 |
+
return nn.functional.pad(x, (self.pad_left, self.pad_right))
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class ConvFeatureExtractionModel(nn.Module):
|
| 47 |
+
def __init__(
|
| 48 |
+
self,
|
| 49 |
+
conv_layers,
|
| 50 |
+
dropout,
|
| 51 |
+
log_compression,
|
| 52 |
+
skip_connections,
|
| 53 |
+
residual_scale,
|
| 54 |
+
non_affine_group_norm,
|
| 55 |
+
activation,
|
| 56 |
+
):
|
| 57 |
+
super().__init__()
|
| 58 |
+
|
| 59 |
+
def block(n_in, n_out, k, stride):
|
| 60 |
+
return nn.Sequential(
|
| 61 |
+
nn.Conv1d(n_in, n_out, k, stride=stride, bias=False),
|
| 62 |
+
nn.Dropout(p=dropout),
|
| 63 |
+
norm_block(
|
| 64 |
+
is_layer_norm=False, dim=n_out, affine=not non_affine_group_norm
|
| 65 |
+
),
|
| 66 |
+
activation,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
in_d = 1
|
| 70 |
+
self.conv_layers = nn.ModuleList()
|
| 71 |
+
for dim, k, stride in conv_layers:
|
| 72 |
+
self.conv_layers.append(block(in_d, dim, k, stride))
|
| 73 |
+
in_d = dim
|
| 74 |
+
|
| 75 |
+
self.log_compression = log_compression
|
| 76 |
+
self.skip_connections = skip_connections
|
| 77 |
+
self.residual_scale = math.sqrt(residual_scale)
|
| 78 |
+
|
| 79 |
+
def forward(self, x): # x is audio of shape [B, 1, T]
|
| 80 |
+
# Bx1xT -> BxCxT
|
| 81 |
+
for conv in self.conv_layers:
|
| 82 |
+
residual = x
|
| 83 |
+
x = conv(x)
|
| 84 |
+
if self.skip_connections and x.size(1) == residual.size(1):
|
| 85 |
+
tsz = x.size(2)
|
| 86 |
+
r_tsz = residual.size(2)
|
| 87 |
+
residual = residual[..., :: r_tsz // tsz][..., :tsz]
|
| 88 |
+
x = (x + residual) * self.residual_scale
|
| 89 |
+
|
| 90 |
+
if self.log_compression:
|
| 91 |
+
x = x.abs()
|
| 92 |
+
x = x + 1
|
| 93 |
+
x = x.log()
|
| 94 |
+
|
| 95 |
+
return x
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class ConvAggregator(nn.Module):
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
conv_layers,
|
| 102 |
+
embed,
|
| 103 |
+
dropout,
|
| 104 |
+
skip_connections,
|
| 105 |
+
residual_scale,
|
| 106 |
+
non_affine_group_norm,
|
| 107 |
+
conv_bias,
|
| 108 |
+
zero_pad,
|
| 109 |
+
activation,
|
| 110 |
+
):
|
| 111 |
+
super().__init__()
|
| 112 |
+
|
| 113 |
+
def block(n_in, n_out, k, stride):
|
| 114 |
+
# padding dims only really make sense for stride = 1
|
| 115 |
+
ka = k // 2
|
| 116 |
+
kb = ka - 1 if k % 2 == 0 else ka
|
| 117 |
+
|
| 118 |
+
pad = (
|
| 119 |
+
ZeroPad1d(ka + kb, 0) if zero_pad else nn.ReplicationPad1d((ka + kb, 0))
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
return nn.Sequential(
|
| 123 |
+
pad,
|
| 124 |
+
nn.Conv1d(n_in, n_out, k, stride=stride, bias=conv_bias),
|
| 125 |
+
nn.Dropout(p=dropout),
|
| 126 |
+
norm_block(False, n_out, affine=not non_affine_group_norm),
|
| 127 |
+
activation,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
in_d = embed
|
| 131 |
+
self.conv_layers = nn.ModuleList()
|
| 132 |
+
self.residual_proj = nn.ModuleList()
|
| 133 |
+
for dim, k, stride in conv_layers:
|
| 134 |
+
if in_d != dim and skip_connections:
|
| 135 |
+
self.residual_proj.append(nn.Conv1d(in_d, dim, 1, bias=False))
|
| 136 |
+
else:
|
| 137 |
+
self.residual_proj.append(None)
|
| 138 |
+
|
| 139 |
+
self.conv_layers.append(block(in_d, dim, k, stride))
|
| 140 |
+
in_d = dim
|
| 141 |
+
self.conv_layers = nn.Sequential(*self.conv_layers)
|
| 142 |
+
self.skip_connections = skip_connections
|
| 143 |
+
self.residual_scale = math.sqrt(residual_scale)
|
| 144 |
+
|
| 145 |
+
def forward(self, x):
|
| 146 |
+
for rproj, conv in zip(self.residual_proj, self.conv_layers):
|
| 147 |
+
residual = x
|
| 148 |
+
x = conv(x)
|
| 149 |
+
if self.skip_connections:
|
| 150 |
+
if rproj is not None:
|
| 151 |
+
residual = rproj(residual)
|
| 152 |
+
x = (x + residual) * self.residual_scale
|
| 153 |
+
return x
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
class LSCodecEncoder(nn.Module):
|
| 157 |
+
def __init__(self, conv_extraction_model: ConvFeatureExtractionModel,
|
| 158 |
+
conv_aggregation_model: ConvAggregator,
|
| 159 |
+
dropout_p: float = 0.1):
|
| 160 |
+
super().__init__()
|
| 161 |
+
self.conv_models = nn.Sequential(conv_extraction_model, conv_aggregation_model)
|
| 162 |
+
|
| 163 |
+
self.dropout = nn.Dropout(dropout_p)
|
| 164 |
+
|
| 165 |
+
def forward(self, waveform, frame_mask, length_tolerance=3):
|
| 166 |
+
# waveform: [B, 1, T]. Frame mask is 1 at valid values.
|
| 167 |
+
conv_features = self.conv_models(waveform) # [B, 1024, L]
|
| 168 |
+
conv_features = self.dropout(conv_features)
|
| 169 |
+
B, D, L = conv_features.shape
|
| 170 |
+
if frame_mask is None:
|
| 171 |
+
frame_mask = torch.ones(size=(B, L)).bool().to(conv_features.device)
|
| 172 |
+
if (frame_mask is not None) and (0 < frame_mask.shape[-1] - L <= length_tolerance):
|
| 173 |
+
pad_tensor = torch.zeros(size=(B, D, frame_mask.shape[-1] - L)).to(conv_features.device)
|
| 174 |
+
# NOTE: we don't pad these values with 999, but instead treat those as valid data points
|
| 175 |
+
# NOTE: this is because the mask is longer than the valid data points, and changing the mask is inconvenient.
|
| 176 |
+
conv_features = torch.concat([conv_features, pad_tensor], dim=-1)
|
| 177 |
+
|
| 178 |
+
return conv_features
|
lscodec/models/hifigan.py
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
|
| 3 |
+
"""HiFi-GAN Modules.
|
| 4 |
+
|
| 5 |
+
This code is based on https://github.com/jik876/hifi-gan.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import copy
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
|
| 16 |
+
from lscodec.layers import HiFiGANResidualBlock as ResidualBlock
|
| 17 |
+
from lscodec.utils import read_hdf5
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class HiFiGANGenerator(torch.nn.Module):
|
| 21 |
+
"""HiFiGAN generator module."""
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
in_channels=80,
|
| 26 |
+
out_channels=1,
|
| 27 |
+
channels=512,
|
| 28 |
+
kernel_size=7,
|
| 29 |
+
upsample_scales=(8, 8, 2, 2),
|
| 30 |
+
upsample_kernel_sizes=(16, 16, 4, 4),
|
| 31 |
+
resblock_kernel_sizes=(3, 7, 11),
|
| 32 |
+
resblock_dilations=[(1, 3, 5), (1, 3, 5), (1, 3, 5)],
|
| 33 |
+
use_additional_convs=True,
|
| 34 |
+
bias=True,
|
| 35 |
+
nonlinear_activation="LeakyReLU",
|
| 36 |
+
nonlinear_activation_params={"negative_slope": 0.1},
|
| 37 |
+
use_weight_norm=True,
|
| 38 |
+
):
|
| 39 |
+
"""Initialize HiFiGANGenerator module.
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
in_channels (int): Number of input channels.
|
| 43 |
+
out_channels (int): Number of output channels.
|
| 44 |
+
channels (int): Number of hidden representation channels.
|
| 45 |
+
kernel_size (int): Kernel size of initial and final conv layer.
|
| 46 |
+
upsample_scales (list): List of upsampling scales.
|
| 47 |
+
upsample_kernel_sizes (list): List of kernel sizes for upsampling layers.
|
| 48 |
+
resblock_kernel_sizes (list): List of kernel sizes for residual blocks.
|
| 49 |
+
resblock_dilations (list): List of dilation list for residual blocks.
|
| 50 |
+
use_additional_convs (bool): Whether to use additional conv layers in residual blocks.
|
| 51 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 52 |
+
nonlinear_activation (str): Activation function module name.
|
| 53 |
+
nonlinear_activation_params (dict): Hyperparameters for activation function.
|
| 54 |
+
use_weight_norm (bool): Whether to use weight norm.
|
| 55 |
+
If set to true, it will be applied to all the conv layers.
|
| 56 |
+
|
| 57 |
+
"""
|
| 58 |
+
super().__init__()
|
| 59 |
+
|
| 60 |
+
# check hyperparameters are valid
|
| 61 |
+
assert kernel_size % 2 == 1, "Kernel size must be odd number."
|
| 62 |
+
assert len(upsample_scales) == len(upsample_kernel_sizes)
|
| 63 |
+
assert len(resblock_dilations) == len(resblock_kernel_sizes)
|
| 64 |
+
|
| 65 |
+
# define modules
|
| 66 |
+
self.num_upsamples = len(upsample_kernel_sizes)
|
| 67 |
+
self.num_blocks = len(resblock_kernel_sizes)
|
| 68 |
+
self.input_conv = torch.nn.Conv1d(
|
| 69 |
+
in_channels,
|
| 70 |
+
channels,
|
| 71 |
+
kernel_size,
|
| 72 |
+
1,
|
| 73 |
+
padding=(kernel_size - 1) // 2,
|
| 74 |
+
)
|
| 75 |
+
self.upsamples = torch.nn.ModuleList()
|
| 76 |
+
self.blocks = torch.nn.ModuleList()
|
| 77 |
+
for i in range(len(upsample_kernel_sizes)):
|
| 78 |
+
assert upsample_kernel_sizes[i] == 2 * upsample_scales[i]
|
| 79 |
+
self.upsamples += [
|
| 80 |
+
torch.nn.Sequential(
|
| 81 |
+
getattr(torch.nn, nonlinear_activation)(
|
| 82 |
+
**nonlinear_activation_params
|
| 83 |
+
),
|
| 84 |
+
torch.nn.ConvTranspose1d(
|
| 85 |
+
channels // (2 ** i),
|
| 86 |
+
channels // (2 ** (i + 1)),
|
| 87 |
+
upsample_kernel_sizes[i],
|
| 88 |
+
upsample_scales[i],
|
| 89 |
+
padding=upsample_scales[i] // 2 + upsample_scales[i] % 2,
|
| 90 |
+
output_padding=upsample_scales[i] % 2,
|
| 91 |
+
),
|
| 92 |
+
)
|
| 93 |
+
]
|
| 94 |
+
for j in range(len(resblock_kernel_sizes)):
|
| 95 |
+
self.blocks += [
|
| 96 |
+
ResidualBlock(
|
| 97 |
+
kernel_size=resblock_kernel_sizes[j],
|
| 98 |
+
channels=channels // (2 ** (i + 1)),
|
| 99 |
+
dilations=resblock_dilations[j],
|
| 100 |
+
bias=bias,
|
| 101 |
+
use_additional_convs=use_additional_convs,
|
| 102 |
+
nonlinear_activation=nonlinear_activation,
|
| 103 |
+
nonlinear_activation_params=nonlinear_activation_params,
|
| 104 |
+
)
|
| 105 |
+
]
|
| 106 |
+
self.output_conv = torch.nn.Sequential(
|
| 107 |
+
# NOTE(kan-bayashi): follow official implementation but why
|
| 108 |
+
# using different slope parameter here? (0.1 vs. 0.01)
|
| 109 |
+
torch.nn.LeakyReLU(),
|
| 110 |
+
torch.nn.Conv1d(
|
| 111 |
+
channels // (2 ** (i + 1)),
|
| 112 |
+
out_channels,
|
| 113 |
+
kernel_size,
|
| 114 |
+
1,
|
| 115 |
+
padding=(kernel_size - 1) // 2,
|
| 116 |
+
),
|
| 117 |
+
torch.nn.Tanh(),
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# apply weight norm
|
| 121 |
+
if use_weight_norm:
|
| 122 |
+
self.apply_weight_norm()
|
| 123 |
+
|
| 124 |
+
# reset parameters
|
| 125 |
+
self.reset_parameters()
|
| 126 |
+
|
| 127 |
+
def forward(self, c):
|
| 128 |
+
"""Calculate forward propagation.
|
| 129 |
+
|
| 130 |
+
Args:
|
| 131 |
+
c (Tensor): Input tensor (B, in_channels, T).
|
| 132 |
+
|
| 133 |
+
Returns:
|
| 134 |
+
Tensor: Output tensor (B, out_channels, T).
|
| 135 |
+
|
| 136 |
+
"""
|
| 137 |
+
c = self.input_conv(c)
|
| 138 |
+
for i in range(self.num_upsamples):
|
| 139 |
+
c = self.upsamples[i](c)
|
| 140 |
+
cs = 0.0 # initialize
|
| 141 |
+
for j in range(self.num_blocks):
|
| 142 |
+
cs += self.blocks[i * self.num_blocks + j](c)
|
| 143 |
+
c = cs / self.num_blocks
|
| 144 |
+
c = self.output_conv(c)
|
| 145 |
+
|
| 146 |
+
return c
|
| 147 |
+
|
| 148 |
+
def reset_parameters(self):
|
| 149 |
+
"""Reset parameters.
|
| 150 |
+
|
| 151 |
+
This initialization follows the official implementation manner.
|
| 152 |
+
https://github.com/jik876/hifi-gan/blob/master/models.py
|
| 153 |
+
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
def _reset_parameters(m):
|
| 157 |
+
if isinstance(m, (torch.nn.Conv1d, torch.nn.ConvTranspose1d)):
|
| 158 |
+
m.weight.data.normal_(0.0, 0.01)
|
| 159 |
+
logging.debug(f"Reset parameters in {m}.")
|
| 160 |
+
|
| 161 |
+
self.apply(_reset_parameters)
|
| 162 |
+
|
| 163 |
+
def remove_weight_norm(self):
|
| 164 |
+
"""Remove weight normalization module from all the layers."""
|
| 165 |
+
|
| 166 |
+
def _remove_weight_norm(m):
|
| 167 |
+
try:
|
| 168 |
+
logging.debug(f"Weight norm is removed from {m}.")
|
| 169 |
+
torch.nn.utils.remove_weight_norm(m)
|
| 170 |
+
except ValueError: # this module didn't have weight norm
|
| 171 |
+
return
|
| 172 |
+
|
| 173 |
+
self.apply(_remove_weight_norm)
|
| 174 |
+
|
| 175 |
+
def apply_weight_norm(self):
|
| 176 |
+
"""Apply weight normalization module from all the layers."""
|
| 177 |
+
|
| 178 |
+
def _apply_weight_norm(m):
|
| 179 |
+
if isinstance(m, torch.nn.Conv1d) or isinstance(
|
| 180 |
+
m, torch.nn.ConvTranspose1d
|
| 181 |
+
):
|
| 182 |
+
torch.nn.utils.weight_norm(m)
|
| 183 |
+
logging.debug(f"Weight norm is applied to {m}.")
|
| 184 |
+
|
| 185 |
+
self.apply(_apply_weight_norm)
|
| 186 |
+
|
| 187 |
+
def register_stats(self, stats):
|
| 188 |
+
"""Register stats for de-normalization as buffer.
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
stats (str): Path of statistics file (".npy" or ".h5").
|
| 192 |
+
|
| 193 |
+
"""
|
| 194 |
+
assert stats.endswith(".h5") or stats.endswith(".npy")
|
| 195 |
+
if stats.endswith(".h5"):
|
| 196 |
+
mean = read_hdf5(stats, "mean").reshape(-1)
|
| 197 |
+
scale = read_hdf5(stats, "scale").reshape(-1)
|
| 198 |
+
else:
|
| 199 |
+
mean = np.load(stats)[0].reshape(-1)
|
| 200 |
+
scale = np.load(stats)[1].reshape(-1)
|
| 201 |
+
self.register_buffer("mean", torch.from_numpy(mean).float())
|
| 202 |
+
self.register_buffer("scale", torch.from_numpy(scale).float())
|
| 203 |
+
logging.info("Successfully registered stats as buffer.")
|
| 204 |
+
|
| 205 |
+
def inference(self, c, normalize_before=False):
|
| 206 |
+
"""Perform inference.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
c (Union[Tensor, ndarray]): Input tensor (T, in_channels).
|
| 210 |
+
normalize_before (bool): Whether to perform normalization.
|
| 211 |
+
|
| 212 |
+
Returns:
|
| 213 |
+
Tensor: Output tensor (T ** prod(upsample_scales), out_channels).
|
| 214 |
+
|
| 215 |
+
"""
|
| 216 |
+
# if not isinstance(c, torch.Tensor):
|
| 217 |
+
# c = torch.tensor(c, dtype=torch.float).to(next(self.parameters()).device)
|
| 218 |
+
if normalize_before:
|
| 219 |
+
c = (c - self.mean) / self.scale
|
| 220 |
+
c = self.forward(c.transpose(1, 2))
|
| 221 |
+
return c.squeeze(0).transpose(1, 0)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
class HiFiGANPeriodDiscriminator(torch.nn.Module):
|
| 225 |
+
"""HiFiGAN period discriminator module."""
|
| 226 |
+
|
| 227 |
+
def __init__(
|
| 228 |
+
self,
|
| 229 |
+
in_channels=1,
|
| 230 |
+
out_channels=1,
|
| 231 |
+
period=3,
|
| 232 |
+
kernel_sizes=[5, 3],
|
| 233 |
+
channels=32,
|
| 234 |
+
downsample_scales=[3, 3, 3, 3, 1],
|
| 235 |
+
max_downsample_channels=1024,
|
| 236 |
+
bias=True,
|
| 237 |
+
nonlinear_activation="LeakyReLU",
|
| 238 |
+
nonlinear_activation_params={"negative_slope": 0.1},
|
| 239 |
+
use_weight_norm=True,
|
| 240 |
+
use_spectral_norm=False,
|
| 241 |
+
):
|
| 242 |
+
"""Initialize HiFiGANPeriodDiscriminator module.
|
| 243 |
+
|
| 244 |
+
Args:
|
| 245 |
+
in_channels (int): Number of input channels.
|
| 246 |
+
out_channels (int): Number of output channels.
|
| 247 |
+
period (int): Period.
|
| 248 |
+
kernel_sizes (list): Kernel sizes of initial conv layers and the final conv layer.
|
| 249 |
+
channels (int): Number of initial channels.
|
| 250 |
+
downsample_scales (list): List of downsampling scales.
|
| 251 |
+
max_downsample_channels (int): Number of maximum downsampling channels.
|
| 252 |
+
use_additional_convs (bool): Whether to use additional conv layers in residual blocks.
|
| 253 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 254 |
+
nonlinear_activation (str): Activation function module name.
|
| 255 |
+
nonlinear_activation_params (dict): Hyperparameters for activation function.
|
| 256 |
+
use_weight_norm (bool): Whether to use weight norm.
|
| 257 |
+
If set to true, it will be applied to all the conv layers.
|
| 258 |
+
use_spectral_norm (bool): Whether to use spectral norm.
|
| 259 |
+
If set to true, it will be applied to all the conv layers.
|
| 260 |
+
|
| 261 |
+
"""
|
| 262 |
+
super().__init__()
|
| 263 |
+
assert len(kernel_sizes) == 2
|
| 264 |
+
assert kernel_sizes[0] % 2 == 1, "Kernel size must be odd number."
|
| 265 |
+
assert kernel_sizes[1] % 2 == 1, "Kernel size must be odd number."
|
| 266 |
+
|
| 267 |
+
self.period = period
|
| 268 |
+
self.convs = torch.nn.ModuleList()
|
| 269 |
+
in_chs = in_channels
|
| 270 |
+
out_chs = channels
|
| 271 |
+
for downsample_scale in downsample_scales:
|
| 272 |
+
self.convs += [
|
| 273 |
+
torch.nn.Sequential(
|
| 274 |
+
torch.nn.Conv2d(
|
| 275 |
+
in_chs,
|
| 276 |
+
out_chs,
|
| 277 |
+
(kernel_sizes[0], 1),
|
| 278 |
+
(downsample_scale, 1),
|
| 279 |
+
padding=((kernel_sizes[0] - 1) // 2, 0),
|
| 280 |
+
),
|
| 281 |
+
getattr(torch.nn, nonlinear_activation)(
|
| 282 |
+
**nonlinear_activation_params
|
| 283 |
+
),
|
| 284 |
+
)
|
| 285 |
+
]
|
| 286 |
+
in_chs = out_chs
|
| 287 |
+
# NOTE(kan-bayashi): Use downsample_scale + 1?
|
| 288 |
+
out_chs = min(out_chs * 4, max_downsample_channels)
|
| 289 |
+
self.output_conv = torch.nn.Conv2d(
|
| 290 |
+
out_chs,
|
| 291 |
+
out_channels,
|
| 292 |
+
(kernel_sizes[1] - 1, 1),
|
| 293 |
+
1,
|
| 294 |
+
padding=((kernel_sizes[1] - 1) // 2, 0),
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
if use_weight_norm and use_spectral_norm:
|
| 298 |
+
raise ValueError("Either use use_weight_norm or use_spectral_norm.")
|
| 299 |
+
|
| 300 |
+
# apply weight norm
|
| 301 |
+
if use_weight_norm:
|
| 302 |
+
self.apply_weight_norm()
|
| 303 |
+
|
| 304 |
+
# apply spectral norm
|
| 305 |
+
if use_spectral_norm:
|
| 306 |
+
self.apply_spectral_norm()
|
| 307 |
+
|
| 308 |
+
def forward(self, x):
|
| 309 |
+
"""Calculate forward propagation.
|
| 310 |
+
|
| 311 |
+
Args:
|
| 312 |
+
c (Tensor): Input tensor (B, in_channels, T).
|
| 313 |
+
|
| 314 |
+
Returns:
|
| 315 |
+
list: List of each layer's tensors.
|
| 316 |
+
|
| 317 |
+
"""
|
| 318 |
+
# transform 1d to 2d -> (B, C, T/P, P)
|
| 319 |
+
b, c, t = x.shape
|
| 320 |
+
if t % self.period != 0:
|
| 321 |
+
n_pad = self.period - (t % self.period)
|
| 322 |
+
x = F.pad(x, (0, n_pad), "reflect")
|
| 323 |
+
t += n_pad
|
| 324 |
+
x = x.view(b, c, t // self.period, self.period)
|
| 325 |
+
|
| 326 |
+
# forward conv
|
| 327 |
+
outs = []
|
| 328 |
+
for layer in self.convs:
|
| 329 |
+
x = layer(x)
|
| 330 |
+
outs += [x]
|
| 331 |
+
x = self.output_conv(x)
|
| 332 |
+
x = torch.flatten(x, 1, -1)
|
| 333 |
+
outs += [x]
|
| 334 |
+
|
| 335 |
+
return outs
|
| 336 |
+
|
| 337 |
+
def apply_weight_norm(self):
|
| 338 |
+
"""Apply weight normalization module from all the layers."""
|
| 339 |
+
|
| 340 |
+
def _apply_weight_norm(m):
|
| 341 |
+
if isinstance(m, torch.nn.Conv2d):
|
| 342 |
+
torch.nn.utils.weight_norm(m)
|
| 343 |
+
logging.debug(f"Weight norm is applied to {m}.")
|
| 344 |
+
|
| 345 |
+
self.apply(_apply_weight_norm)
|
| 346 |
+
|
| 347 |
+
def apply_spectral_norm(self):
|
| 348 |
+
"""Apply spectral normalization module from all the layers."""
|
| 349 |
+
|
| 350 |
+
def _apply_spectral_norm(m):
|
| 351 |
+
if isinstance(m, torch.nn.Conv2d):
|
| 352 |
+
torch.nn.utils.spectral_norm(m)
|
| 353 |
+
logging.debug(f"Spectral norm is applied to {m}.")
|
| 354 |
+
|
| 355 |
+
self.apply(_apply_spectral_norm)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
class HiFiGANMultiPeriodDiscriminator(torch.nn.Module):
|
| 359 |
+
"""HiFiGAN multi-period discriminator module."""
|
| 360 |
+
|
| 361 |
+
def __init__(
|
| 362 |
+
self,
|
| 363 |
+
periods=[2, 3, 5, 7, 11],
|
| 364 |
+
discriminator_params={
|
| 365 |
+
"in_channels": 1,
|
| 366 |
+
"out_channels": 1,
|
| 367 |
+
"kernel_sizes": [5, 3],
|
| 368 |
+
"channels": 32,
|
| 369 |
+
"downsample_scales": [3, 3, 3, 3, 1],
|
| 370 |
+
"max_downsample_channels": 1024,
|
| 371 |
+
"bias": True,
|
| 372 |
+
"nonlinear_activation": "LeakyReLU",
|
| 373 |
+
"nonlinear_activation_params": {"negative_slope": 0.1},
|
| 374 |
+
"use_weight_norm": True,
|
| 375 |
+
"use_spectral_norm": False,
|
| 376 |
+
},
|
| 377 |
+
):
|
| 378 |
+
"""Initialize HiFiGANMultiPeriodDiscriminator module.
|
| 379 |
+
|
| 380 |
+
Args:
|
| 381 |
+
periods (list): List of periods.
|
| 382 |
+
discriminator_params (dict): Parameters for hifi-gan period discriminator module.
|
| 383 |
+
The period parameter will be overwritten.
|
| 384 |
+
|
| 385 |
+
"""
|
| 386 |
+
super().__init__()
|
| 387 |
+
self.discriminators = torch.nn.ModuleList()
|
| 388 |
+
for period in periods:
|
| 389 |
+
params = copy.deepcopy(discriminator_params)
|
| 390 |
+
params["period"] = period
|
| 391 |
+
self.discriminators += [HiFiGANPeriodDiscriminator(**params)]
|
| 392 |
+
|
| 393 |
+
def forward(self, x):
|
| 394 |
+
"""Calculate forward propagation.
|
| 395 |
+
|
| 396 |
+
Args:
|
| 397 |
+
x (Tensor): Input noise signal (B, 1, T).
|
| 398 |
+
|
| 399 |
+
Returns:
|
| 400 |
+
List: List of list of each discriminator outputs, which consists of each layer output tensors.
|
| 401 |
+
|
| 402 |
+
"""
|
| 403 |
+
outs = []
|
| 404 |
+
for f in self.discriminators:
|
| 405 |
+
outs += [f(x)]
|
| 406 |
+
|
| 407 |
+
return outs
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
class HiFiGANScaleDiscriminator(torch.nn.Module):
|
| 411 |
+
"""HiFi-GAN scale discriminator module."""
|
| 412 |
+
|
| 413 |
+
def __init__(
|
| 414 |
+
self,
|
| 415 |
+
in_channels=1,
|
| 416 |
+
out_channels=1,
|
| 417 |
+
kernel_sizes=[15, 41, 5, 3],
|
| 418 |
+
channels=128,
|
| 419 |
+
max_downsample_channels=1024,
|
| 420 |
+
max_groups=16,
|
| 421 |
+
bias=True,
|
| 422 |
+
downsample_scales=[2, 2, 4, 4, 1],
|
| 423 |
+
nonlinear_activation="LeakyReLU",
|
| 424 |
+
nonlinear_activation_params={"negative_slope": 0.1},
|
| 425 |
+
use_weight_norm=True,
|
| 426 |
+
use_spectral_norm=False,
|
| 427 |
+
):
|
| 428 |
+
"""Initilize HiFiGAN scale discriminator module.
|
| 429 |
+
|
| 430 |
+
Args:
|
| 431 |
+
in_channels (int): Number of input channels.
|
| 432 |
+
out_channels (int): Number of output channels.
|
| 433 |
+
kernel_sizes (list): List of four kernel sizes. The first will be used for the first conv layer,
|
| 434 |
+
and the second is for downsampling part, and the remaining two are for output layers.
|
| 435 |
+
channels (int): Initial number of channels for conv layer.
|
| 436 |
+
max_downsample_channels (int): Maximum number of channels for downsampling layers.
|
| 437 |
+
bias (bool): Whether to add bias parameter in convolution layers.
|
| 438 |
+
downsample_scales (list): List of downsampling scales.
|
| 439 |
+
nonlinear_activation (str): Activation function module name.
|
| 440 |
+
nonlinear_activation_params (dict): Hyperparameters for activation function.
|
| 441 |
+
use_weight_norm (bool): Whether to use weight norm.
|
| 442 |
+
If set to true, it will be applied to all the conv layers.
|
| 443 |
+
use_spectral_norm (bool): Whether to use spectral norm.
|
| 444 |
+
If set to true, it will be applied to all the conv layers.
|
| 445 |
+
|
| 446 |
+
"""
|
| 447 |
+
super().__init__()
|
| 448 |
+
self.layers = torch.nn.ModuleList()
|
| 449 |
+
|
| 450 |
+
# check kernel size is valid
|
| 451 |
+
assert len(kernel_sizes) == 4
|
| 452 |
+
for ks in kernel_sizes:
|
| 453 |
+
assert ks % 2 == 1
|
| 454 |
+
|
| 455 |
+
# add first layer
|
| 456 |
+
self.layers += [
|
| 457 |
+
torch.nn.Sequential(
|
| 458 |
+
torch.nn.Conv1d(
|
| 459 |
+
in_channels,
|
| 460 |
+
channels,
|
| 461 |
+
# NOTE(kan-bayashi): Use always the same kernel size
|
| 462 |
+
kernel_sizes[0],
|
| 463 |
+
bias=bias,
|
| 464 |
+
padding=(kernel_sizes[0] - 1) // 2,
|
| 465 |
+
),
|
| 466 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 467 |
+
)
|
| 468 |
+
]
|
| 469 |
+
|
| 470 |
+
# add downsample layers
|
| 471 |
+
in_chs = channels
|
| 472 |
+
out_chs = channels
|
| 473 |
+
# NOTE(kan-bayashi): Remove hard coding?
|
| 474 |
+
groups = 4
|
| 475 |
+
for downsample_scale in downsample_scales:
|
| 476 |
+
self.layers += [
|
| 477 |
+
torch.nn.Sequential(
|
| 478 |
+
torch.nn.Conv1d(
|
| 479 |
+
in_chs,
|
| 480 |
+
out_chs,
|
| 481 |
+
kernel_size=kernel_sizes[1],
|
| 482 |
+
stride=downsample_scale,
|
| 483 |
+
padding=(kernel_sizes[1] - 1) // 2,
|
| 484 |
+
groups=groups,
|
| 485 |
+
bias=bias,
|
| 486 |
+
),
|
| 487 |
+
getattr(torch.nn, nonlinear_activation)(
|
| 488 |
+
**nonlinear_activation_params
|
| 489 |
+
),
|
| 490 |
+
)
|
| 491 |
+
]
|
| 492 |
+
in_chs = out_chs
|
| 493 |
+
# NOTE(kan-bayashi): Remove hard coding?
|
| 494 |
+
out_chs = min(in_chs * 2, max_downsample_channels)
|
| 495 |
+
# NOTE(kan-bayashi): Remove hard coding?
|
| 496 |
+
groups = min(groups * 4, max_groups)
|
| 497 |
+
|
| 498 |
+
# add final layers
|
| 499 |
+
out_chs = min(in_chs * 2, max_downsample_channels)
|
| 500 |
+
self.layers += [
|
| 501 |
+
torch.nn.Sequential(
|
| 502 |
+
torch.nn.Conv1d(
|
| 503 |
+
in_chs,
|
| 504 |
+
out_chs,
|
| 505 |
+
kernel_size=kernel_sizes[2],
|
| 506 |
+
stride=1,
|
| 507 |
+
padding=(kernel_sizes[2] - 1) // 2,
|
| 508 |
+
bias=bias,
|
| 509 |
+
),
|
| 510 |
+
getattr(torch.nn, nonlinear_activation)(**nonlinear_activation_params),
|
| 511 |
+
)
|
| 512 |
+
]
|
| 513 |
+
self.layers += [
|
| 514 |
+
torch.nn.Conv1d(
|
| 515 |
+
out_chs,
|
| 516 |
+
out_channels,
|
| 517 |
+
kernel_size=kernel_sizes[3],
|
| 518 |
+
stride=1,
|
| 519 |
+
padding=(kernel_sizes[3] - 1) // 2,
|
| 520 |
+
bias=bias,
|
| 521 |
+
),
|
| 522 |
+
]
|
| 523 |
+
|
| 524 |
+
if use_weight_norm and use_spectral_norm:
|
| 525 |
+
raise ValueError("Either use use_weight_norm or use_spectral_norm.")
|
| 526 |
+
|
| 527 |
+
# apply weight norm
|
| 528 |
+
if use_weight_norm:
|
| 529 |
+
self.apply_weight_norm()
|
| 530 |
+
|
| 531 |
+
# apply spectral norm
|
| 532 |
+
if use_spectral_norm:
|
| 533 |
+
self.apply_spectral_norm()
|
| 534 |
+
|
| 535 |
+
def forward(self, x):
|
| 536 |
+
"""Calculate forward propagation.
|
| 537 |
+
|
| 538 |
+
Args:
|
| 539 |
+
x (Tensor): Input noise signal (B, 1, T).
|
| 540 |
+
|
| 541 |
+
Returns:
|
| 542 |
+
List: List of output tensors of each layer.
|
| 543 |
+
|
| 544 |
+
"""
|
| 545 |
+
outs = []
|
| 546 |
+
for f in self.layers:
|
| 547 |
+
x = f(x)
|
| 548 |
+
outs += [x]
|
| 549 |
+
|
| 550 |
+
return outs
|
| 551 |
+
|
| 552 |
+
def apply_weight_norm(self):
|
| 553 |
+
"""Apply weight normalization module from all the layers."""
|
| 554 |
+
|
| 555 |
+
def _apply_weight_norm(m):
|
| 556 |
+
if isinstance(m, torch.nn.Conv2d):
|
| 557 |
+
torch.nn.utils.weight_norm(m)
|
| 558 |
+
logging.debug(f"Weight norm is applied to {m}.")
|
| 559 |
+
|
| 560 |
+
self.apply(_apply_weight_norm)
|
| 561 |
+
|
| 562 |
+
def apply_spectral_norm(self):
|
| 563 |
+
"""Apply spectral normalization module from all the layers."""
|
| 564 |
+
|
| 565 |
+
def _apply_spectral_norm(m):
|
| 566 |
+
if isinstance(m, torch.nn.Conv2d):
|
| 567 |
+
torch.nn.utils.spectral_norm(m)
|
| 568 |
+
logging.debug(f"Spectral norm is applied to {m}.")
|
| 569 |
+
|
| 570 |
+
self.apply(_apply_spectral_norm)
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
class HiFiGANMultiScaleDiscriminator(torch.nn.Module):
|
| 574 |
+
"""HiFi-GAN multi-scale discriminator module."""
|
| 575 |
+
|
| 576 |
+
def __init__(
|
| 577 |
+
self,
|
| 578 |
+
scales=3,
|
| 579 |
+
downsample_pooling="AvgPool1d",
|
| 580 |
+
# follow the official implementation setting
|
| 581 |
+
downsample_pooling_params={
|
| 582 |
+
"kernel_size": 4,
|
| 583 |
+
"stride": 2,
|
| 584 |
+
"padding": 2,
|
| 585 |
+
},
|
| 586 |
+
discriminator_params={
|
| 587 |
+
"in_channels": 1,
|
| 588 |
+
"out_channels": 1,
|
| 589 |
+
"kernel_sizes": [15, 41, 5, 3],
|
| 590 |
+
"channels": 128,
|
| 591 |
+
"max_downsample_channels": 1024,
|
| 592 |
+
"max_groups": 16,
|
| 593 |
+
"bias": True,
|
| 594 |
+
"downsample_scales": [2, 2, 4, 4, 1],
|
| 595 |
+
"nonlinear_activation": "LeakyReLU",
|
| 596 |
+
"nonlinear_activation_params": {"negative_slope": 0.1},
|
| 597 |
+
},
|
| 598 |
+
follow_official_norm=False,
|
| 599 |
+
):
|
| 600 |
+
"""Initilize HiFiGAN multi-scale discriminator module.
|
| 601 |
+
|
| 602 |
+
Args:
|
| 603 |
+
scales (int): Number of multi-scales.
|
| 604 |
+
downsample_pooling (str): Pooling module name for downsampling of the inputs.
|
| 605 |
+
downsample_pooling_params (dict): Parameters for the above pooling module.
|
| 606 |
+
discriminator_params (dict): Parameters for hifi-gan scale discriminator module.
|
| 607 |
+
follow_official_norm (bool): Whether to follow the norm setting of the official
|
| 608 |
+
implementaion. The first discriminator uses spectral norm and the other
|
| 609 |
+
discriminators use weight norm.
|
| 610 |
+
|
| 611 |
+
"""
|
| 612 |
+
super().__init__()
|
| 613 |
+
self.discriminators = torch.nn.ModuleList()
|
| 614 |
+
|
| 615 |
+
# add discriminators
|
| 616 |
+
for i in range(scales):
|
| 617 |
+
params = copy.deepcopy(discriminator_params)
|
| 618 |
+
if follow_official_norm:
|
| 619 |
+
if i == 0:
|
| 620 |
+
params["use_weight_norm"] = False
|
| 621 |
+
params["use_spectral_norm"] = True
|
| 622 |
+
else:
|
| 623 |
+
params["use_weight_norm"] = True
|
| 624 |
+
params["use_spectral_norm"] = False
|
| 625 |
+
self.discriminators += [HiFiGANScaleDiscriminator(**params)]
|
| 626 |
+
self.pooling = getattr(torch.nn, downsample_pooling)(
|
| 627 |
+
**downsample_pooling_params
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
def forward(self, x):
|
| 631 |
+
"""Calculate forward propagation.
|
| 632 |
+
|
| 633 |
+
Args:
|
| 634 |
+
x (Tensor): Input noise signal (B, 1, T).
|
| 635 |
+
|
| 636 |
+
Returns:
|
| 637 |
+
List: List of list of each discriminator outputs, which consists of each layer output tensors.
|
| 638 |
+
|
| 639 |
+
"""
|
| 640 |
+
outs = []
|
| 641 |
+
for f in self.discriminators:
|
| 642 |
+
outs += [f(x)]
|
| 643 |
+
x = self.pooling(x)
|
| 644 |
+
|
| 645 |
+
return outs
|
| 646 |
+
|
| 647 |
+
|
| 648 |
+
class HiFiGANMultiScaleMultiPeriodDiscriminator(torch.nn.Module):
|
| 649 |
+
"""HiFi-GAN multi-scale + multi-period discriminator module."""
|
| 650 |
+
|
| 651 |
+
def __init__(
|
| 652 |
+
self,
|
| 653 |
+
# Multi-scale discriminator related
|
| 654 |
+
scales=3,
|
| 655 |
+
scale_downsample_pooling="AvgPool1d",
|
| 656 |
+
scale_downsample_pooling_params={
|
| 657 |
+
"kernel_size": 4,
|
| 658 |
+
"stride": 2,
|
| 659 |
+
"padding": 2,
|
| 660 |
+
},
|
| 661 |
+
scale_discriminator_params={
|
| 662 |
+
"in_channels": 1,
|
| 663 |
+
"out_channels": 1,
|
| 664 |
+
"kernel_sizes": [15, 41, 5, 3],
|
| 665 |
+
"channels": 128,
|
| 666 |
+
"max_downsample_channels": 1024,
|
| 667 |
+
"max_groups": 16,
|
| 668 |
+
"bias": True,
|
| 669 |
+
"downsample_scales": [2, 2, 4, 4, 1],
|
| 670 |
+
"nonlinear_activation": "LeakyReLU",
|
| 671 |
+
"nonlinear_activation_params": {"negative_slope": 0.1},
|
| 672 |
+
},
|
| 673 |
+
follow_official_norm=True,
|
| 674 |
+
# Multi-period discriminator related
|
| 675 |
+
periods=[2, 3, 5, 7, 11],
|
| 676 |
+
period_discriminator_params={
|
| 677 |
+
"in_channels": 1,
|
| 678 |
+
"out_channels": 1,
|
| 679 |
+
"kernel_sizes": [5, 3],
|
| 680 |
+
"channels": 32,
|
| 681 |
+
"downsample_scales": [3, 3, 3, 3, 1],
|
| 682 |
+
"max_downsample_channels": 1024,
|
| 683 |
+
"bias": True,
|
| 684 |
+
"nonlinear_activation": "LeakyReLU",
|
| 685 |
+
"nonlinear_activation_params": {"negative_slope": 0.1},
|
| 686 |
+
"use_weight_norm": True,
|
| 687 |
+
"use_spectral_norm": False,
|
| 688 |
+
},
|
| 689 |
+
):
|
| 690 |
+
"""Initilize HiFiGAN multiscale + multi-period discriminator module.
|
| 691 |
+
|
| 692 |
+
Args:
|
| 693 |
+
scales (int): Number of multi-scales.
|
| 694 |
+
scale_downsample_pooling (str): Pooling module name for downsampling of the inputs.
|
| 695 |
+
scale_downsample_pooling_params (dict): Parameters for the above pooling module.
|
| 696 |
+
scale_discriminator_params (dict): Parameters for hifi-gan scale discriminator module.
|
| 697 |
+
follow_official_norm (bool): Whether to follow the norm setting of the official
|
| 698 |
+
implementaion. The first discriminator uses spectral norm and the other
|
| 699 |
+
discriminators use weight norm.
|
| 700 |
+
periods (list): List of periods.
|
| 701 |
+
period_discriminator_params (dict): Parameters for hifi-gan period discriminator module.
|
| 702 |
+
The period parameter will be overwritten.
|
| 703 |
+
|
| 704 |
+
"""
|
| 705 |
+
super().__init__()
|
| 706 |
+
self.msd = HiFiGANMultiScaleDiscriminator(
|
| 707 |
+
scales=scales,
|
| 708 |
+
downsample_pooling=scale_downsample_pooling,
|
| 709 |
+
downsample_pooling_params=scale_downsample_pooling_params,
|
| 710 |
+
discriminator_params=scale_discriminator_params,
|
| 711 |
+
follow_official_norm=follow_official_norm,
|
| 712 |
+
)
|
| 713 |
+
self.mpd = HiFiGANMultiPeriodDiscriminator(
|
| 714 |
+
periods=periods,
|
| 715 |
+
discriminator_params=period_discriminator_params,
|
| 716 |
+
)
|
| 717 |
+
|
| 718 |
+
def forward(self, x):
|
| 719 |
+
"""Calculate forward propagation.
|
| 720 |
+
|
| 721 |
+
Args:
|
| 722 |
+
x (Tensor): Input noise signal (B, 1, T).
|
| 723 |
+
|
| 724 |
+
Returns:
|
| 725 |
+
List: List of list of each discriminator outputs,
|
| 726 |
+
which consists of each layer output tensors.
|
| 727 |
+
Multiscale and multi period ones are concatenated.
|
| 728 |
+
|
| 729 |
+
"""
|
| 730 |
+
msd_outs = self.msd(x)
|
| 731 |
+
mpd_outs = self.mpd(x)
|
| 732 |
+
return msd_outs + mpd_outs
|