Instructions to use deepsafe/deepsafe-services with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use deepsafe/deepsafe-services with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("deepsafe/deepsafe-services", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
chore: remove non-weight files (batch 2)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- audio/safeear/safeear_repo/fairseq_ours/examples/simultaneous_translation/utils/monotonic_attention.py +0 -180
- audio/safeear/safeear_repo/fairseq_ours/examples/simultaneous_translation/utils/p_choose_strategy.py +0 -127
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/README.md +0 -83
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/__init__.py +0 -1
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/ASG_loss.py +0 -170
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/__init__.py +0 -16
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/cross_entropy_acc.py +0 -130
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/__init__.py +0 -10
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/asr_dataset.py +0 -122
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/collaters.py +0 -131
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/data_utils.py +0 -100
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/replabels.py +0 -70
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/datasets/asr_prep_json.py +0 -124
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/datasets/prepare-librispeech.sh +0 -88
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/infer.py +0 -435
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/__init__.py +0 -0
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/add-self-loop-simple.cc +0 -94
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/config/kaldi_initializer.yaml +0 -8
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/kaldi_decoder.py +0 -243
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/kaldi_initializer.py +0 -698
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/__init__.py +0 -7
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/vggtransformer.py +0 -1020
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/w2l_conv_glu_enc.py +0 -176
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/README.md +0 -43
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/__init__.py +0 -0
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/conf/hydra/sweeper/ax.yaml +0 -29
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/conf/infer.yaml +0 -25
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/__init__.py +0 -0
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/base_decoder.py +0 -62
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/decoder.py +0 -32
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/decoder_config.py +0 -69
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/flashlight_decoder.py +0 -429
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/viterbi_decoder.py +0 -24
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/infer.py +0 -474
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/tasks/__init__.py +0 -7
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/tasks/speech_recognition.py +0 -157
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/utils/wer_utils.py +0 -380
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/w2l_decoder.py +0 -491
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/README.md +0 -38
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/__init__.py +0 -4
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/data_utils.py +0 -347
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/common_voice_example.md +0 -67
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/ljspeech_example.md +0 -137
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/vctk_example.md +0 -61
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/__init__.py +0 -4
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_asr.py +0 -128
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_f0.py +0 -267
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_sp.py +0 -130
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/get_eval_manifest.py +0 -64
- audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/generate_waveform.py +0 -191
audio/safeear/safeear_repo/fairseq_ours/examples/simultaneous_translation/utils/monotonic_attention.py
DELETED
|
@@ -1,180 +0,0 @@
|
|
| 1 |
-
from typing import Optional
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
from examples.simultaneous_translation.utils.functions import (
|
| 5 |
-
exclusive_cumprod,
|
| 6 |
-
moving_sum,
|
| 7 |
-
prob_check,
|
| 8 |
-
)
|
| 9 |
-
from torch import Tensor
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
def expected_alignment_from_p_choose(
|
| 13 |
-
p_choose: Tensor,
|
| 14 |
-
padding_mask: Optional[Tensor] = None,
|
| 15 |
-
eps: float = 1e-6
|
| 16 |
-
):
|
| 17 |
-
"""
|
| 18 |
-
Calculating expected alignment for from stepwise probability
|
| 19 |
-
|
| 20 |
-
Reference:
|
| 21 |
-
Online and Linear-Time Attention by Enforcing Monotonic Alignments
|
| 22 |
-
https://arxiv.org/pdf/1704.00784.pdf
|
| 23 |
-
|
| 24 |
-
q_ij = (1 − p_{ij−1})q_{ij−1} + a+{i−1j}
|
| 25 |
-
a_ij = p_ij q_ij
|
| 26 |
-
|
| 27 |
-
Parallel solution:
|
| 28 |
-
ai = p_i * cumprod(1 − pi) * cumsum(a_i / cumprod(1 − pi))
|
| 29 |
-
|
| 30 |
-
============================================================
|
| 31 |
-
Expected input size
|
| 32 |
-
p_choose: bsz, tgt_len, src_len
|
| 33 |
-
"""
|
| 34 |
-
prob_check(p_choose)
|
| 35 |
-
|
| 36 |
-
# p_choose: bsz, tgt_len, src_len
|
| 37 |
-
bsz, tgt_len, src_len = p_choose.size()
|
| 38 |
-
dtype = p_choose.dtype
|
| 39 |
-
|
| 40 |
-
p_choose = p_choose.float()
|
| 41 |
-
|
| 42 |
-
if padding_mask is not None:
|
| 43 |
-
p_choose = p_choose.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 44 |
-
|
| 45 |
-
if p_choose.is_cuda:
|
| 46 |
-
p_choose = p_choose.contiguous()
|
| 47 |
-
from alignment_train_cuda_binding import alignment_train_cuda as alignment_train
|
| 48 |
-
else:
|
| 49 |
-
from alignment_train_cpu_binding import alignment_train_cpu as alignment_train
|
| 50 |
-
|
| 51 |
-
alpha = p_choose.new_zeros([bsz, tgt_len, src_len])
|
| 52 |
-
alignment_train(p_choose, alpha, eps)
|
| 53 |
-
|
| 54 |
-
# Mix precision to prevent overflow for fp16
|
| 55 |
-
alpha = alpha.type(dtype)
|
| 56 |
-
|
| 57 |
-
prob_check(alpha)
|
| 58 |
-
|
| 59 |
-
return alpha
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
def expected_soft_attention(
|
| 63 |
-
alpha: Tensor,
|
| 64 |
-
soft_energy: Tensor,
|
| 65 |
-
padding_mask: Optional[Tensor] = None,
|
| 66 |
-
chunk_size: Optional[int] = None,
|
| 67 |
-
eps: float = 1e-10
|
| 68 |
-
):
|
| 69 |
-
"""
|
| 70 |
-
Function to compute expected soft attention for
|
| 71 |
-
monotonic infinite lookback attention from
|
| 72 |
-
expected alignment and soft energy.
|
| 73 |
-
|
| 74 |
-
Reference:
|
| 75 |
-
Monotonic Chunkwise Attention
|
| 76 |
-
https://arxiv.org/abs/1712.05382
|
| 77 |
-
|
| 78 |
-
Monotonic Infinite Lookback Attention for Simultaneous Machine Translation
|
| 79 |
-
https://arxiv.org/abs/1906.05218
|
| 80 |
-
|
| 81 |
-
alpha: bsz, tgt_len, src_len
|
| 82 |
-
soft_energy: bsz, tgt_len, src_len
|
| 83 |
-
padding_mask: bsz, src_len
|
| 84 |
-
left_padding: bool
|
| 85 |
-
"""
|
| 86 |
-
if padding_mask is not None:
|
| 87 |
-
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 88 |
-
soft_energy = soft_energy.masked_fill(
|
| 89 |
-
padding_mask.unsqueeze(1), -float("inf")
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
-
prob_check(alpha)
|
| 93 |
-
|
| 94 |
-
dtype = alpha.dtype
|
| 95 |
-
|
| 96 |
-
alpha = alpha.float()
|
| 97 |
-
soft_energy = soft_energy.float()
|
| 98 |
-
|
| 99 |
-
soft_energy = soft_energy - soft_energy.max(dim=2, keepdim=True)[0]
|
| 100 |
-
exp_soft_energy = torch.exp(soft_energy) + eps
|
| 101 |
-
|
| 102 |
-
if chunk_size is not None:
|
| 103 |
-
# Chunkwise
|
| 104 |
-
beta = (
|
| 105 |
-
exp_soft_energy
|
| 106 |
-
* moving_sum(
|
| 107 |
-
alpha / (eps + moving_sum(exp_soft_energy, chunk_size, 1)),
|
| 108 |
-
1, chunk_size
|
| 109 |
-
)
|
| 110 |
-
)
|
| 111 |
-
else:
|
| 112 |
-
# Infinite lookback
|
| 113 |
-
# Notice that infinite lookback is a special case of chunkwise
|
| 114 |
-
# where chunksize = inf
|
| 115 |
-
inner_items = alpha / (eps + torch.cumsum(exp_soft_energy, dim=2))
|
| 116 |
-
|
| 117 |
-
beta = (
|
| 118 |
-
exp_soft_energy
|
| 119 |
-
* torch.cumsum(inner_items.flip(dims=[2]), dim=2)
|
| 120 |
-
.flip(dims=[2])
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
if padding_mask is not None:
|
| 124 |
-
beta = beta.masked_fill(
|
| 125 |
-
padding_mask.unsqueeze(1).to(torch.bool), 0.0)
|
| 126 |
-
|
| 127 |
-
# Mix precision to prevent overflow for fp16
|
| 128 |
-
beta = beta.type(dtype)
|
| 129 |
-
|
| 130 |
-
beta = beta.clamp(0, 1)
|
| 131 |
-
|
| 132 |
-
prob_check(beta)
|
| 133 |
-
|
| 134 |
-
return beta
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
def mass_preservation(
|
| 138 |
-
alpha: Tensor,
|
| 139 |
-
padding_mask: Optional[Tensor] = None,
|
| 140 |
-
left_padding: bool = False
|
| 141 |
-
):
|
| 142 |
-
"""
|
| 143 |
-
Function to compute the mass perservation for alpha.
|
| 144 |
-
This means that the residual weights of alpha will be assigned
|
| 145 |
-
to the last token.
|
| 146 |
-
|
| 147 |
-
Reference:
|
| 148 |
-
Monotonic Infinite Lookback Attention for Simultaneous Machine Translation
|
| 149 |
-
https://arxiv.org/abs/1906.05218
|
| 150 |
-
|
| 151 |
-
alpha: bsz, tgt_len, src_len
|
| 152 |
-
padding_mask: bsz, src_len
|
| 153 |
-
left_padding: bool
|
| 154 |
-
"""
|
| 155 |
-
|
| 156 |
-
prob_check(alpha)
|
| 157 |
-
|
| 158 |
-
if padding_mask is not None:
|
| 159 |
-
if not left_padding:
|
| 160 |
-
assert not padding_mask[:, 0].any(), (
|
| 161 |
-
"Find padding on the beginning of the sequence."
|
| 162 |
-
)
|
| 163 |
-
alpha = alpha.masked_fill(padding_mask.unsqueeze(1), 0.0)
|
| 164 |
-
|
| 165 |
-
if left_padding or padding_mask is None:
|
| 166 |
-
residuals = 1 - alpha[:, :, :-1].sum(dim=-1).clamp(0, 1)
|
| 167 |
-
alpha[:, :, -1] = residuals
|
| 168 |
-
else:
|
| 169 |
-
# right padding
|
| 170 |
-
_, tgt_len, src_len = alpha.size()
|
| 171 |
-
residuals = 1 - alpha.sum(dim=-1, keepdim=True).clamp(0, 1)
|
| 172 |
-
src_lens = src_len - padding_mask.sum(dim=1, keepdim=True)
|
| 173 |
-
src_lens = src_lens.expand(-1, tgt_len).contiguous()
|
| 174 |
-
# add back the last value
|
| 175 |
-
residuals += alpha.gather(2, src_lens.unsqueeze(2) - 1)
|
| 176 |
-
alpha = alpha.scatter(2, src_lens.unsqueeze(2) - 1, residuals)
|
| 177 |
-
|
| 178 |
-
prob_check(alpha)
|
| 179 |
-
|
| 180 |
-
return alpha
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/simultaneous_translation/utils/p_choose_strategy.py
DELETED
|
@@ -1,127 +0,0 @@
|
|
| 1 |
-
from typing import Dict, Optional
|
| 2 |
-
|
| 3 |
-
import torch
|
| 4 |
-
from torch import Tensor
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
def waitk_p_choose(
|
| 8 |
-
tgt_len: int,
|
| 9 |
-
src_len: int,
|
| 10 |
-
bsz: int,
|
| 11 |
-
waitk_lagging: int,
|
| 12 |
-
key_padding_mask: Optional[Tensor] = None,
|
| 13 |
-
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None
|
| 14 |
-
):
|
| 15 |
-
|
| 16 |
-
max_src_len = src_len
|
| 17 |
-
if incremental_state is not None:
|
| 18 |
-
# Retrieve target length from incremental states
|
| 19 |
-
# For inference the length of query is always 1
|
| 20 |
-
max_tgt_len = incremental_state["steps"]["tgt"]
|
| 21 |
-
assert max_tgt_len is not None
|
| 22 |
-
max_tgt_len = int(max_tgt_len)
|
| 23 |
-
else:
|
| 24 |
-
max_tgt_len = tgt_len
|
| 25 |
-
|
| 26 |
-
if max_src_len < waitk_lagging:
|
| 27 |
-
if incremental_state is not None:
|
| 28 |
-
max_tgt_len = 1
|
| 29 |
-
return torch.zeros(
|
| 30 |
-
bsz, max_tgt_len, max_src_len
|
| 31 |
-
)
|
| 32 |
-
|
| 33 |
-
# Assuming the p_choose looks like this for wait k=3
|
| 34 |
-
# src_len = 6, max_tgt_len = 5
|
| 35 |
-
# [0, 0, 1, 0, 0, 0, 0]
|
| 36 |
-
# [0, 0, 0, 1, 0, 0, 0]
|
| 37 |
-
# [0, 0, 0, 0, 1, 0, 0]
|
| 38 |
-
# [0, 0, 0, 0, 0, 1, 0]
|
| 39 |
-
# [0, 0, 0, 0, 0, 0, 1]
|
| 40 |
-
# linearize the p_choose matrix:
|
| 41 |
-
# [0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0...]
|
| 42 |
-
# The indices of linearized matrix that equals 1 is
|
| 43 |
-
# 2 + 6 * 0
|
| 44 |
-
# 3 + 6 * 1
|
| 45 |
-
# ...
|
| 46 |
-
# n + src_len * n + k - 1 = n * (src_len + 1) + k - 1
|
| 47 |
-
# n from 0 to max_tgt_len - 1
|
| 48 |
-
#
|
| 49 |
-
# First, generate the indices (activate_indices_offset: bsz, max_tgt_len)
|
| 50 |
-
# Second, scatter a zeros tensor (bsz, max_tgt_len * src_len)
|
| 51 |
-
# with activate_indices_offset
|
| 52 |
-
# Third, resize the tensor to (bsz, max_tgt_len, src_len)
|
| 53 |
-
|
| 54 |
-
activate_indices_offset = (
|
| 55 |
-
(
|
| 56 |
-
torch.arange(max_tgt_len) * (max_src_len + 1)
|
| 57 |
-
+ waitk_lagging - 1
|
| 58 |
-
)
|
| 59 |
-
.unsqueeze(0)
|
| 60 |
-
.expand(bsz, max_tgt_len)
|
| 61 |
-
.long()
|
| 62 |
-
)
|
| 63 |
-
|
| 64 |
-
if key_padding_mask is not None:
|
| 65 |
-
if key_padding_mask[:, 0].any():
|
| 66 |
-
# Left padding
|
| 67 |
-
activate_indices_offset += (
|
| 68 |
-
key_padding_mask.sum(dim=1, keepdim=True)
|
| 69 |
-
)
|
| 70 |
-
|
| 71 |
-
# Need to clamp the indices that are too large
|
| 72 |
-
activate_indices_offset = (
|
| 73 |
-
activate_indices_offset
|
| 74 |
-
.clamp(
|
| 75 |
-
0,
|
| 76 |
-
min(
|
| 77 |
-
[
|
| 78 |
-
max_tgt_len,
|
| 79 |
-
max_src_len - waitk_lagging + 1
|
| 80 |
-
]
|
| 81 |
-
) * max_src_len - 1
|
| 82 |
-
)
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
p_choose = torch.zeros(bsz, max_tgt_len * max_src_len)
|
| 86 |
-
|
| 87 |
-
p_choose = p_choose.scatter(
|
| 88 |
-
1,
|
| 89 |
-
activate_indices_offset,
|
| 90 |
-
1.0
|
| 91 |
-
).view(bsz, max_tgt_len, max_src_len)
|
| 92 |
-
|
| 93 |
-
if key_padding_mask is not None:
|
| 94 |
-
p_choose = p_choose.to(key_padding_mask)
|
| 95 |
-
p_choose = p_choose.masked_fill(key_padding_mask.unsqueeze(1), 0)
|
| 96 |
-
|
| 97 |
-
if incremental_state is not None:
|
| 98 |
-
p_choose = p_choose[:, -1:]
|
| 99 |
-
|
| 100 |
-
return p_choose.float()
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
def learnable_p_choose(
|
| 104 |
-
energy,
|
| 105 |
-
noise_mean: float = 0.0,
|
| 106 |
-
noise_var: float = 0.0,
|
| 107 |
-
training: bool = True
|
| 108 |
-
):
|
| 109 |
-
"""
|
| 110 |
-
Calculating step wise prob for reading and writing
|
| 111 |
-
1 to read, 0 to write
|
| 112 |
-
energy: bsz, tgt_len, src_len
|
| 113 |
-
"""
|
| 114 |
-
|
| 115 |
-
noise = 0
|
| 116 |
-
if training:
|
| 117 |
-
# add noise here to encourage discretness
|
| 118 |
-
noise = (
|
| 119 |
-
torch.normal(noise_mean, noise_var, energy.size())
|
| 120 |
-
.type_as(energy)
|
| 121 |
-
.to(energy.device)
|
| 122 |
-
)
|
| 123 |
-
|
| 124 |
-
p_choose = torch.sigmoid(energy + noise)
|
| 125 |
-
|
| 126 |
-
# p_choose: bsz * self.num_heads, tgt_len, src_len
|
| 127 |
-
return p_choose
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/README.md
DELETED
|
@@ -1,83 +0,0 @@
|
|
| 1 |
-
### 2021 Update: We are merging this example into the [S2T framework](../speech_to_text), which supports more generic speech-to-text tasks (e.g. speech translation) and more flexible data processing pipelines. Please stay tuned.
|
| 2 |
-
|
| 3 |
-
# Speech Recognition
|
| 4 |
-
`examples/speech_recognition` is implementing ASR task in Fairseq, along with needed features, datasets, models and loss functions to train and infer model described in [Transformers with convolutional context for ASR (Abdelrahman Mohamed et al., 2019)](https://arxiv.org/abs/1904.11660).
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
## Additional dependencies
|
| 8 |
-
On top of main fairseq dependencies there are couple more additional requirements.
|
| 9 |
-
|
| 10 |
-
1) Please follow the instructions to install [torchaudio](https://github.com/pytorch/audio). This is required to compute audio fbank features.
|
| 11 |
-
2) [Sclite](http://www1.icsi.berkeley.edu/Speech/docs/sctk-1.2/sclite.htm#sclite_name_0) is used to measure WER. Sclite can be downloaded and installed from source from sctk package [here](http://www.openslr.org/4/). Training and inference doesn't require Sclite dependency.
|
| 12 |
-
3) [sentencepiece](https://github.com/google/sentencepiece) is required in order to create dataset with word-piece targets.
|
| 13 |
-
|
| 14 |
-
## Preparing librispeech data
|
| 15 |
-
```
|
| 16 |
-
./examples/speech_recognition/datasets/prepare-librispeech.sh $DIR_TO_SAVE_RAW_DATA $DIR_FOR_PREPROCESSED_DATA
|
| 17 |
-
```
|
| 18 |
-
|
| 19 |
-
## Training librispeech data
|
| 20 |
-
```
|
| 21 |
-
python train.py $DIR_FOR_PREPROCESSED_DATA --save-dir $MODEL_PATH --max-epoch 80 --task speech_recognition --arch vggtransformer_2 --optimizer adadelta --lr 1.0 --adadelta-eps 1e-8 --adadelta-rho 0.95 --clip-norm 10.0 --max-tokens 5000 --log-format json --log-interval 1 --criterion cross_entropy_acc --user-dir examples/speech_recognition/
|
| 22 |
-
```
|
| 23 |
-
|
| 24 |
-
## Inference for librispeech
|
| 25 |
-
`$SET` can be `test_clean` or `test_other`
|
| 26 |
-
Any checkpoint in `$MODEL_PATH` can be selected. In this example we are working with `checkpoint_last.pt`
|
| 27 |
-
```
|
| 28 |
-
python examples/speech_recognition/infer.py $DIR_FOR_PREPROCESSED_DATA --task speech_recognition --max-tokens 25000 --nbest 1 --path $MODEL_PATH/checkpoint_last.pt --beam 20 --results-path $RES_DIR --batch-size 40 --gen-subset $SET --user-dir examples/speech_recognition/
|
| 29 |
-
```
|
| 30 |
-
|
| 31 |
-
## Inference for librispeech
|
| 32 |
-
```
|
| 33 |
-
sclite -r ${RES_DIR}/ref.word-checkpoint_last.pt-${SET}.txt -h ${RES_DIR}/hypo.word-checkpoint_last.pt-${SET}.txt -i rm -o all stdout > $RES_REPORT
|
| 34 |
-
```
|
| 35 |
-
`Sum/Avg` row from first table of the report has WER
|
| 36 |
-
|
| 37 |
-
## Using flashlight (previously called [wav2letter](https://github.com/facebookresearch/wav2letter)) components
|
| 38 |
-
[flashlight](https://github.com/facebookresearch/flashlight) now has integration with fairseq. Currently this includes:
|
| 39 |
-
|
| 40 |
-
* AutoSegmentationCriterion (ASG)
|
| 41 |
-
* flashlight-style Conv/GLU model
|
| 42 |
-
* flashlight's beam search decoder
|
| 43 |
-
|
| 44 |
-
To use these, follow the instructions on [this page](https://github.com/facebookresearch/flashlight/tree/master/bindings/python) to install python bindings.
|
| 45 |
-
|
| 46 |
-
## Training librispeech data (flashlight style, Conv/GLU + ASG loss)
|
| 47 |
-
Training command:
|
| 48 |
-
```
|
| 49 |
-
python train.py $DIR_FOR_PREPROCESSED_DATA --save-dir $MODEL_PATH --max-epoch 100 --task speech_recognition --arch w2l_conv_glu_enc --batch-size 4 --optimizer sgd --lr 0.3,0.8 --momentum 0.8 --clip-norm 0.2 --max-tokens 50000 --log-format json --log-interval 100 --num-workers 0 --sentence-avg --criterion asg_loss --asg-transitions-init 5 --max-replabel 2 --linseg-updates 8789 --user-dir examples/speech_recognition
|
| 50 |
-
```
|
| 51 |
-
|
| 52 |
-
Note that ASG loss currently doesn't do well with word-pieces. You should prepare a dataset with character targets by setting `nbpe=31` in `prepare-librispeech.sh`.
|
| 53 |
-
|
| 54 |
-
## Inference for librispeech (flashlight decoder, n-gram LM)
|
| 55 |
-
Inference command:
|
| 56 |
-
```
|
| 57 |
-
python examples/speech_recognition/infer.py $DIR_FOR_PREPROCESSED_DATA --task speech_recognition --seed 1 --nbest 1 --path $MODEL_PATH/checkpoint_last.pt --gen-subset $SET --results-path $RES_DIR --w2l-decoder kenlm --kenlm-model $KENLM_MODEL_PATH --lexicon $LEXICON_PATH --beam 200 --beam-threshold 15 --lm-weight 1.5 --word-score 1.5 --sil-weight -0.3 --criterion asg_loss --max-replabel 2 --user-dir examples/speech_recognition
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
`$KENLM_MODEL_PATH` should be a standard n-gram language model file. `$LEXICON_PATH` should be a flashlight-style lexicon (list of known words and their spellings). For ASG inference, a lexicon line should look like this (note the repetition labels):
|
| 61 |
-
```
|
| 62 |
-
doorbell D O 1 R B E L 1 ▁
|
| 63 |
-
```
|
| 64 |
-
For CTC inference with word-pieces, repetition labels are not used and the lexicon should have most common spellings for each word (one can use sentencepiece's `NBestEncodeAsPieces` for this):
|
| 65 |
-
```
|
| 66 |
-
doorbell ▁DOOR BE LL
|
| 67 |
-
doorbell ▁DOOR B E LL
|
| 68 |
-
doorbell ▁DO OR BE LL
|
| 69 |
-
doorbell ▁DOOR B EL L
|
| 70 |
-
doorbell ▁DOOR BE L L
|
| 71 |
-
doorbell ▁DO OR B E LL
|
| 72 |
-
doorbell ▁DOOR B E L L
|
| 73 |
-
doorbell ▁DO OR B EL L
|
| 74 |
-
doorbell ▁DO O R BE LL
|
| 75 |
-
doorbell ▁DO OR BE L L
|
| 76 |
-
```
|
| 77 |
-
Lowercase vs. uppercase matters: the *word* should match the case of the n-gram language model (i.e. `$KENLM_MODEL_PATH`), while the *spelling* should match the case of the token dictionary (i.e. `$DIR_FOR_PREPROCESSED_DATA/dict.txt`).
|
| 78 |
-
|
| 79 |
-
## Inference for librispeech (flashlight decoder, viterbi only)
|
| 80 |
-
Inference command:
|
| 81 |
-
```
|
| 82 |
-
python examples/speech_recognition/infer.py $DIR_FOR_PREPROCESSED_DATA --task speech_recognition --seed 1 --nbest 1 --path $MODEL_PATH/checkpoint_last.pt --gen-subset $SET --results-path $RES_DIR --w2l-decoder viterbi --criterion asg_loss --max-replabel 2 --user-dir examples/speech_recognition
|
| 83 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
from . import criterions, models, tasks # noqa
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/ASG_loss.py
DELETED
|
@@ -1,170 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
import torch
|
| 9 |
-
from examples.speech_recognition.data.replabels import pack_replabels
|
| 10 |
-
from fairseq import utils
|
| 11 |
-
from fairseq.criterions import FairseqCriterion, register_criterion
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
@register_criterion("asg_loss")
|
| 15 |
-
class ASGCriterion(FairseqCriterion):
|
| 16 |
-
@staticmethod
|
| 17 |
-
def add_args(parser):
|
| 18 |
-
group = parser.add_argument_group("ASG Loss")
|
| 19 |
-
group.add_argument(
|
| 20 |
-
"--asg-transitions-init",
|
| 21 |
-
help="initial diagonal value of transition matrix",
|
| 22 |
-
type=float,
|
| 23 |
-
default=0.0,
|
| 24 |
-
)
|
| 25 |
-
group.add_argument(
|
| 26 |
-
"--max-replabel", help="maximum # of replabels", type=int, default=2
|
| 27 |
-
)
|
| 28 |
-
group.add_argument(
|
| 29 |
-
"--linseg-updates",
|
| 30 |
-
help="# of training updates to use LinSeg initialization",
|
| 31 |
-
type=int,
|
| 32 |
-
default=0,
|
| 33 |
-
)
|
| 34 |
-
group.add_argument(
|
| 35 |
-
"--hide-linseg-messages",
|
| 36 |
-
help="hide messages about LinSeg initialization",
|
| 37 |
-
action="store_true",
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
def __init__(
|
| 41 |
-
self,
|
| 42 |
-
task,
|
| 43 |
-
silence_token,
|
| 44 |
-
asg_transitions_init,
|
| 45 |
-
max_replabel,
|
| 46 |
-
linseg_updates,
|
| 47 |
-
hide_linseg_messages,
|
| 48 |
-
):
|
| 49 |
-
from flashlight.lib.sequence.criterion import ASGLoss, CriterionScaleMode
|
| 50 |
-
|
| 51 |
-
super().__init__(task)
|
| 52 |
-
self.tgt_dict = task.target_dictionary
|
| 53 |
-
self.eos = self.tgt_dict.eos()
|
| 54 |
-
self.silence = (
|
| 55 |
-
self.tgt_dict.index(silence_token)
|
| 56 |
-
if silence_token in self.tgt_dict
|
| 57 |
-
else None
|
| 58 |
-
)
|
| 59 |
-
self.max_replabel = max_replabel
|
| 60 |
-
|
| 61 |
-
num_labels = len(self.tgt_dict)
|
| 62 |
-
self.asg = ASGLoss(num_labels, scale_mode=CriterionScaleMode.TARGET_SZ_SQRT)
|
| 63 |
-
self.asg.trans = torch.nn.Parameter(
|
| 64 |
-
asg_transitions_init * torch.eye(num_labels), requires_grad=True
|
| 65 |
-
)
|
| 66 |
-
|
| 67 |
-
self.linseg_progress = torch.nn.Parameter(
|
| 68 |
-
torch.tensor([0], dtype=torch.int), requires_grad=False
|
| 69 |
-
)
|
| 70 |
-
self.linseg_maximum = linseg_updates
|
| 71 |
-
self.linseg_message_state = "none" if hide_linseg_messages else "start"
|
| 72 |
-
|
| 73 |
-
@classmethod
|
| 74 |
-
def build_criterion(cls, args, task):
|
| 75 |
-
return cls(
|
| 76 |
-
task,
|
| 77 |
-
args.silence_token,
|
| 78 |
-
args.asg_transitions_init,
|
| 79 |
-
args.max_replabel,
|
| 80 |
-
args.linseg_updates,
|
| 81 |
-
args.hide_linseg_messages,
|
| 82 |
-
)
|
| 83 |
-
|
| 84 |
-
def linseg_step(self):
|
| 85 |
-
if not self.training:
|
| 86 |
-
return False
|
| 87 |
-
if self.linseg_progress.item() < self.linseg_maximum:
|
| 88 |
-
if self.linseg_message_state == "start":
|
| 89 |
-
print("| using LinSeg to initialize ASG")
|
| 90 |
-
self.linseg_message_state = "finish"
|
| 91 |
-
self.linseg_progress.add_(1)
|
| 92 |
-
return True
|
| 93 |
-
elif self.linseg_message_state == "finish":
|
| 94 |
-
print("| finished LinSeg initialization")
|
| 95 |
-
self.linseg_message_state = "none"
|
| 96 |
-
return False
|
| 97 |
-
|
| 98 |
-
def replace_eos_with_silence(self, tgt):
|
| 99 |
-
if tgt[-1] != self.eos:
|
| 100 |
-
return tgt
|
| 101 |
-
elif self.silence is None or (len(tgt) > 1 and tgt[-2] == self.silence):
|
| 102 |
-
return tgt[:-1]
|
| 103 |
-
else:
|
| 104 |
-
return tgt[:-1] + [self.silence]
|
| 105 |
-
|
| 106 |
-
def forward(self, model, sample, reduce=True):
|
| 107 |
-
"""Compute the loss for the given sample.
|
| 108 |
-
|
| 109 |
-
Returns a tuple with three elements:
|
| 110 |
-
1) the loss
|
| 111 |
-
2) the sample size, which is used as the denominator for the gradient
|
| 112 |
-
3) logging outputs to display while training
|
| 113 |
-
"""
|
| 114 |
-
|
| 115 |
-
net_output = model(**sample["net_input"])
|
| 116 |
-
emissions = net_output["encoder_out"].transpose(0, 1).contiguous()
|
| 117 |
-
B = emissions.size(0)
|
| 118 |
-
T = emissions.size(1)
|
| 119 |
-
device = emissions.device
|
| 120 |
-
|
| 121 |
-
target = torch.IntTensor(B, T)
|
| 122 |
-
target_size = torch.IntTensor(B)
|
| 123 |
-
using_linseg = self.linseg_step()
|
| 124 |
-
|
| 125 |
-
for b in range(B):
|
| 126 |
-
initial_target_size = sample["target_lengths"][b].item()
|
| 127 |
-
if initial_target_size == 0:
|
| 128 |
-
raise ValueError("target size cannot be zero")
|
| 129 |
-
|
| 130 |
-
tgt = sample["target"][b, :initial_target_size].tolist()
|
| 131 |
-
tgt = self.replace_eos_with_silence(tgt)
|
| 132 |
-
tgt = pack_replabels(tgt, self.tgt_dict, self.max_replabel)
|
| 133 |
-
tgt = tgt[:T]
|
| 134 |
-
|
| 135 |
-
if using_linseg:
|
| 136 |
-
tgt = [tgt[t * len(tgt) // T] for t in range(T)]
|
| 137 |
-
|
| 138 |
-
target[b][: len(tgt)] = torch.IntTensor(tgt)
|
| 139 |
-
target_size[b] = len(tgt)
|
| 140 |
-
|
| 141 |
-
loss = self.asg.forward(emissions, target.to(device), target_size.to(device))
|
| 142 |
-
|
| 143 |
-
if reduce:
|
| 144 |
-
loss = torch.sum(loss)
|
| 145 |
-
|
| 146 |
-
sample_size = (
|
| 147 |
-
sample["target"].size(0) if self.args.sentence_avg else sample["ntokens"]
|
| 148 |
-
)
|
| 149 |
-
logging_output = {
|
| 150 |
-
"loss": utils.item(loss.data) if reduce else loss.data,
|
| 151 |
-
"ntokens": sample["ntokens"],
|
| 152 |
-
"nsentences": sample["target"].size(0),
|
| 153 |
-
"sample_size": sample_size,
|
| 154 |
-
}
|
| 155 |
-
return loss, sample_size, logging_output
|
| 156 |
-
|
| 157 |
-
@staticmethod
|
| 158 |
-
def aggregate_logging_outputs(logging_outputs):
|
| 159 |
-
"""Aggregate logging outputs from data parallel training."""
|
| 160 |
-
loss_sum = sum(log.get("loss", 0) for log in logging_outputs)
|
| 161 |
-
ntokens = sum(log.get("ntokens", 0) for log in logging_outputs)
|
| 162 |
-
nsentences = sum(log.get("nsentences", 0) for log in logging_outputs)
|
| 163 |
-
sample_size = sum(log.get("sample_size", 0) for log in logging_outputs)
|
| 164 |
-
agg_output = {
|
| 165 |
-
"loss": loss_sum / nsentences,
|
| 166 |
-
"ntokens": ntokens,
|
| 167 |
-
"nsentences": nsentences,
|
| 168 |
-
"sample_size": sample_size,
|
| 169 |
-
}
|
| 170 |
-
return agg_output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/__init__.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
import importlib
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
# ASG loss requires flashlight bindings
|
| 5 |
-
files_to_skip = set()
|
| 6 |
-
try:
|
| 7 |
-
import flashlight.lib.sequence.criterion
|
| 8 |
-
except ImportError:
|
| 9 |
-
files_to_skip.add("ASG_loss.py")
|
| 10 |
-
|
| 11 |
-
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 12 |
-
if file.endswith(".py") and not file.startswith("_") and file not in files_to_skip:
|
| 13 |
-
criterion_name = file[: file.find(".py")]
|
| 14 |
-
importlib.import_module(
|
| 15 |
-
"examples.speech_recognition.criterions." + criterion_name
|
| 16 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/criterions/cross_entropy_acc.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
| 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 |
-
from __future__ import absolute_import, division, print_function, unicode_literals
|
| 7 |
-
|
| 8 |
-
import logging
|
| 9 |
-
import math
|
| 10 |
-
|
| 11 |
-
import torch
|
| 12 |
-
import torch.nn.functional as F
|
| 13 |
-
from fairseq import utils
|
| 14 |
-
from fairseq.criterions import FairseqCriterion, register_criterion
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
@register_criterion("cross_entropy_acc")
|
| 18 |
-
class CrossEntropyWithAccCriterion(FairseqCriterion):
|
| 19 |
-
def __init__(self, task, sentence_avg):
|
| 20 |
-
super().__init__(task)
|
| 21 |
-
self.sentence_avg = sentence_avg
|
| 22 |
-
|
| 23 |
-
def compute_loss(self, model, net_output, target, reduction, log_probs):
|
| 24 |
-
# N, T -> N * T
|
| 25 |
-
target = target.view(-1)
|
| 26 |
-
lprobs = model.get_normalized_probs(net_output, log_probs=log_probs)
|
| 27 |
-
if not hasattr(lprobs, "batch_first"):
|
| 28 |
-
logging.warning(
|
| 29 |
-
"ERROR: we need to know whether "
|
| 30 |
-
"batch first for the net output; "
|
| 31 |
-
"you need to set batch_first attribute for the return value of "
|
| 32 |
-
"model.get_normalized_probs. Now, we assume this is true, but "
|
| 33 |
-
"in the future, we will raise exception instead. "
|
| 34 |
-
)
|
| 35 |
-
batch_first = getattr(lprobs, "batch_first", True)
|
| 36 |
-
if not batch_first:
|
| 37 |
-
lprobs = lprobs.transpose(0, 1)
|
| 38 |
-
|
| 39 |
-
# N, T, D -> N * T, D
|
| 40 |
-
lprobs = lprobs.view(-1, lprobs.size(-1))
|
| 41 |
-
loss = F.nll_loss(
|
| 42 |
-
lprobs, target, ignore_index=self.padding_idx, reduction=reduction
|
| 43 |
-
)
|
| 44 |
-
return lprobs, loss
|
| 45 |
-
|
| 46 |
-
def get_logging_output(self, sample, target, lprobs, loss):
|
| 47 |
-
target = target.view(-1)
|
| 48 |
-
mask = target != self.padding_idx
|
| 49 |
-
correct = torch.sum(
|
| 50 |
-
lprobs.argmax(1).masked_select(mask) == target.masked_select(mask)
|
| 51 |
-
)
|
| 52 |
-
total = torch.sum(mask)
|
| 53 |
-
sample_size = (
|
| 54 |
-
sample["target"].size(0) if self.sentence_avg else sample["ntokens"]
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
logging_output = {
|
| 58 |
-
"loss": utils.item(loss.data), # * sample['ntokens'],
|
| 59 |
-
"ntokens": sample["ntokens"],
|
| 60 |
-
"nsentences": sample["target"].size(0),
|
| 61 |
-
"sample_size": sample_size,
|
| 62 |
-
"correct": utils.item(correct.data),
|
| 63 |
-
"total": utils.item(total.data),
|
| 64 |
-
"nframes": torch.sum(sample["net_input"]["src_lengths"]).item(),
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
return sample_size, logging_output
|
| 68 |
-
|
| 69 |
-
def forward(self, model, sample, reduction="sum", log_probs=True):
|
| 70 |
-
"""Computes the cross entropy with accuracy metric for the given sample.
|
| 71 |
-
|
| 72 |
-
This is similar to CrossEntropyCriterion in fairseq, but also
|
| 73 |
-
computes accuracy metrics as part of logging
|
| 74 |
-
|
| 75 |
-
Args:
|
| 76 |
-
logprobs (Torch.tensor) of shape N, T, D i.e.
|
| 77 |
-
batchsize, timesteps, dimensions
|
| 78 |
-
targets (Torch.tensor) of shape N, T i.e batchsize, timesteps
|
| 79 |
-
|
| 80 |
-
Returns:
|
| 81 |
-
tuple: With three elements:
|
| 82 |
-
1) the loss
|
| 83 |
-
2) the sample size, which is used as the denominator for the gradient
|
| 84 |
-
3) logging outputs to display while training
|
| 85 |
-
|
| 86 |
-
TODO:
|
| 87 |
-
* Currently this Criterion will only work with LSTMEncoderModels or
|
| 88 |
-
FairseqModels which have decoder, or Models which return TorchTensor
|
| 89 |
-
as net_output.
|
| 90 |
-
We need to make a change to support all FairseqEncoder models.
|
| 91 |
-
"""
|
| 92 |
-
net_output = model(**sample["net_input"])
|
| 93 |
-
target = model.get_targets(sample, net_output)
|
| 94 |
-
lprobs, loss = self.compute_loss(
|
| 95 |
-
model, net_output, target, reduction, log_probs
|
| 96 |
-
)
|
| 97 |
-
sample_size, logging_output = self.get_logging_output(
|
| 98 |
-
sample, target, lprobs, loss
|
| 99 |
-
)
|
| 100 |
-
return loss, sample_size, logging_output
|
| 101 |
-
|
| 102 |
-
@staticmethod
|
| 103 |
-
def aggregate_logging_outputs(logging_outputs):
|
| 104 |
-
"""Aggregate logging outputs from data parallel training."""
|
| 105 |
-
correct_sum = sum(log.get("correct", 0) for log in logging_outputs)
|
| 106 |
-
total_sum = sum(log.get("total", 0) for log in logging_outputs)
|
| 107 |
-
loss_sum = sum(log.get("loss", 0) for log in logging_outputs)
|
| 108 |
-
ntokens = sum(log.get("ntokens", 0) for log in logging_outputs)
|
| 109 |
-
nsentences = sum(log.get("nsentences", 0) for log in logging_outputs)
|
| 110 |
-
sample_size = sum(log.get("sample_size", 0) for log in logging_outputs)
|
| 111 |
-
nframes = sum(log.get("nframes", 0) for log in logging_outputs)
|
| 112 |
-
agg_output = {
|
| 113 |
-
"loss": loss_sum / sample_size / math.log(2) if sample_size > 0 else 0.0,
|
| 114 |
-
# if args.sentence_avg, then sample_size is nsentences, then loss
|
| 115 |
-
# is per-sentence loss; else sample_size is ntokens, the loss
|
| 116 |
-
# becomes per-output token loss
|
| 117 |
-
"ntokens": ntokens,
|
| 118 |
-
"nsentences": nsentences,
|
| 119 |
-
"nframes": nframes,
|
| 120 |
-
"sample_size": sample_size,
|
| 121 |
-
"acc": correct_sum * 100.0 / total_sum if total_sum > 0 else 0.0,
|
| 122 |
-
"correct": correct_sum,
|
| 123 |
-
"total": total_sum,
|
| 124 |
-
# total is the number of validate tokens
|
| 125 |
-
}
|
| 126 |
-
if sample_size != ntokens:
|
| 127 |
-
agg_output["nll_loss"] = loss_sum / ntokens / math.log(2)
|
| 128 |
-
# loss: per output token loss
|
| 129 |
-
# nll_loss: per sentence loss
|
| 130 |
-
return agg_output
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/__init__.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 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 |
-
from .asr_dataset import AsrDataset
|
| 7 |
-
|
| 8 |
-
__all__ = [
|
| 9 |
-
"AsrDataset",
|
| 10 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/asr_dataset.py
DELETED
|
@@ -1,122 +0,0 @@
|
|
| 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 os
|
| 7 |
-
|
| 8 |
-
import numpy as np
|
| 9 |
-
from fairseq.data import FairseqDataset
|
| 10 |
-
|
| 11 |
-
from . import data_utils
|
| 12 |
-
from .collaters import Seq2SeqCollater
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class AsrDataset(FairseqDataset):
|
| 16 |
-
"""
|
| 17 |
-
A dataset representing speech and corresponding transcription.
|
| 18 |
-
|
| 19 |
-
Args:
|
| 20 |
-
aud_paths: (List[str]): A list of str with paths to audio files.
|
| 21 |
-
aud_durations_ms (List[int]): A list of int containing the durations of
|
| 22 |
-
audio files.
|
| 23 |
-
tgt (List[torch.LongTensor]): A list of LongTensors containing the indices
|
| 24 |
-
of target transcriptions.
|
| 25 |
-
tgt_dict (~fairseq.data.Dictionary): target vocabulary.
|
| 26 |
-
ids (List[str]): A list of utterance IDs.
|
| 27 |
-
speakers (List[str]): A list of speakers corresponding to utterances.
|
| 28 |
-
num_mel_bins (int): Number of triangular mel-frequency bins (default: 80)
|
| 29 |
-
frame_length (float): Frame length in milliseconds (default: 25.0)
|
| 30 |
-
frame_shift (float): Frame shift in milliseconds (default: 10.0)
|
| 31 |
-
"""
|
| 32 |
-
|
| 33 |
-
def __init__(
|
| 34 |
-
self,
|
| 35 |
-
aud_paths,
|
| 36 |
-
aud_durations_ms,
|
| 37 |
-
tgt,
|
| 38 |
-
tgt_dict,
|
| 39 |
-
ids,
|
| 40 |
-
speakers,
|
| 41 |
-
num_mel_bins=80,
|
| 42 |
-
frame_length=25.0,
|
| 43 |
-
frame_shift=10.0,
|
| 44 |
-
):
|
| 45 |
-
assert frame_length > 0
|
| 46 |
-
assert frame_shift > 0
|
| 47 |
-
assert all(x > frame_length for x in aud_durations_ms)
|
| 48 |
-
self.frame_sizes = [
|
| 49 |
-
int(1 + (d - frame_length) / frame_shift) for d in aud_durations_ms
|
| 50 |
-
]
|
| 51 |
-
|
| 52 |
-
assert len(aud_paths) > 0
|
| 53 |
-
assert len(aud_paths) == len(aud_durations_ms)
|
| 54 |
-
assert len(aud_paths) == len(tgt)
|
| 55 |
-
assert len(aud_paths) == len(ids)
|
| 56 |
-
assert len(aud_paths) == len(speakers)
|
| 57 |
-
self.aud_paths = aud_paths
|
| 58 |
-
self.tgt_dict = tgt_dict
|
| 59 |
-
self.tgt = tgt
|
| 60 |
-
self.ids = ids
|
| 61 |
-
self.speakers = speakers
|
| 62 |
-
self.num_mel_bins = num_mel_bins
|
| 63 |
-
self.frame_length = frame_length
|
| 64 |
-
self.frame_shift = frame_shift
|
| 65 |
-
|
| 66 |
-
self.s2s_collater = Seq2SeqCollater(
|
| 67 |
-
0,
|
| 68 |
-
1,
|
| 69 |
-
pad_index=self.tgt_dict.pad(),
|
| 70 |
-
eos_index=self.tgt_dict.eos(),
|
| 71 |
-
move_eos_to_beginning=True,
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
def __getitem__(self, index):
|
| 75 |
-
import torchaudio
|
| 76 |
-
import torchaudio.compliance.kaldi as kaldi
|
| 77 |
-
|
| 78 |
-
tgt_item = self.tgt[index] if self.tgt is not None else None
|
| 79 |
-
|
| 80 |
-
path = self.aud_paths[index]
|
| 81 |
-
if not os.path.exists(path):
|
| 82 |
-
raise FileNotFoundError("Audio file not found: {}".format(path))
|
| 83 |
-
sound, sample_rate = torchaudio.load_wav(path)
|
| 84 |
-
output = kaldi.fbank(
|
| 85 |
-
sound,
|
| 86 |
-
num_mel_bins=self.num_mel_bins,
|
| 87 |
-
frame_length=self.frame_length,
|
| 88 |
-
frame_shift=self.frame_shift,
|
| 89 |
-
)
|
| 90 |
-
output_cmvn = data_utils.apply_mv_norm(output)
|
| 91 |
-
|
| 92 |
-
return {"id": index, "data": [output_cmvn.detach(), tgt_item]}
|
| 93 |
-
|
| 94 |
-
def __len__(self):
|
| 95 |
-
return len(self.aud_paths)
|
| 96 |
-
|
| 97 |
-
def collater(self, samples):
|
| 98 |
-
"""Merge a list of samples to form a mini-batch.
|
| 99 |
-
|
| 100 |
-
Args:
|
| 101 |
-
samples (List[int]): sample indices to collate
|
| 102 |
-
|
| 103 |
-
Returns:
|
| 104 |
-
dict: a mini-batch suitable for forwarding with a Model
|
| 105 |
-
"""
|
| 106 |
-
return self.s2s_collater.collate(samples)
|
| 107 |
-
|
| 108 |
-
def num_tokens(self, index):
|
| 109 |
-
return self.frame_sizes[index]
|
| 110 |
-
|
| 111 |
-
def size(self, index):
|
| 112 |
-
"""Return an example's size as a float or tuple. This value is used when
|
| 113 |
-
filtering a dataset with ``--max-positions``."""
|
| 114 |
-
return (
|
| 115 |
-
self.frame_sizes[index],
|
| 116 |
-
len(self.tgt[index]) if self.tgt is not None else 0,
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
def ordered_indices(self):
|
| 120 |
-
"""Return an ordered list of indices. Batches will be constructed based
|
| 121 |
-
on this order."""
|
| 122 |
-
return np.arange(len(self))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/collaters.py
DELETED
|
@@ -1,131 +0,0 @@
|
|
| 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 |
-
This module contains collection of classes which implement
|
| 7 |
-
collate functionalities for various tasks.
|
| 8 |
-
|
| 9 |
-
Collaters should know what data to expect for each sample
|
| 10 |
-
and they should pack / collate them into batches
|
| 11 |
-
"""
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
from __future__ import absolute_import, division, print_function, unicode_literals
|
| 15 |
-
|
| 16 |
-
import numpy as np
|
| 17 |
-
import torch
|
| 18 |
-
from fairseq.data import data_utils as fairseq_data_utils
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
class Seq2SeqCollater(object):
|
| 22 |
-
"""
|
| 23 |
-
Implements collate function mainly for seq2seq tasks
|
| 24 |
-
This expects each sample to contain feature (src_tokens) and
|
| 25 |
-
targets.
|
| 26 |
-
This collator is also used for aligned training task.
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
def __init__(
|
| 30 |
-
self,
|
| 31 |
-
feature_index=0,
|
| 32 |
-
label_index=1,
|
| 33 |
-
pad_index=1,
|
| 34 |
-
eos_index=2,
|
| 35 |
-
move_eos_to_beginning=True,
|
| 36 |
-
):
|
| 37 |
-
self.feature_index = feature_index
|
| 38 |
-
self.label_index = label_index
|
| 39 |
-
self.pad_index = pad_index
|
| 40 |
-
self.eos_index = eos_index
|
| 41 |
-
self.move_eos_to_beginning = move_eos_to_beginning
|
| 42 |
-
|
| 43 |
-
def _collate_frames(self, frames):
|
| 44 |
-
"""Convert a list of 2d frames into a padded 3d tensor
|
| 45 |
-
Args:
|
| 46 |
-
frames (list): list of 2d frames of size L[i]*f_dim. Where L[i] is
|
| 47 |
-
length of i-th frame and f_dim is static dimension of features
|
| 48 |
-
Returns:
|
| 49 |
-
3d tensor of size len(frames)*len_max*f_dim where len_max is max of L[i]
|
| 50 |
-
"""
|
| 51 |
-
len_max = max(frame.size(0) for frame in frames)
|
| 52 |
-
f_dim = frames[0].size(1)
|
| 53 |
-
res = frames[0].new(len(frames), len_max, f_dim).fill_(0.0)
|
| 54 |
-
|
| 55 |
-
for i, v in enumerate(frames):
|
| 56 |
-
res[i, : v.size(0)] = v
|
| 57 |
-
|
| 58 |
-
return res
|
| 59 |
-
|
| 60 |
-
def collate(self, samples):
|
| 61 |
-
"""
|
| 62 |
-
utility function to collate samples into batch for speech recognition.
|
| 63 |
-
"""
|
| 64 |
-
if len(samples) == 0:
|
| 65 |
-
return {}
|
| 66 |
-
|
| 67 |
-
# parse samples into torch tensors
|
| 68 |
-
parsed_samples = []
|
| 69 |
-
for s in samples:
|
| 70 |
-
# skip invalid samples
|
| 71 |
-
if s["data"][self.feature_index] is None:
|
| 72 |
-
continue
|
| 73 |
-
source = s["data"][self.feature_index]
|
| 74 |
-
if isinstance(source, (np.ndarray, np.generic)):
|
| 75 |
-
source = torch.from_numpy(source)
|
| 76 |
-
target = s["data"][self.label_index]
|
| 77 |
-
if isinstance(target, (np.ndarray, np.generic)):
|
| 78 |
-
target = torch.from_numpy(target).long()
|
| 79 |
-
elif isinstance(target, list):
|
| 80 |
-
target = torch.LongTensor(target)
|
| 81 |
-
|
| 82 |
-
parsed_sample = {"id": s["id"], "source": source, "target": target}
|
| 83 |
-
parsed_samples.append(parsed_sample)
|
| 84 |
-
samples = parsed_samples
|
| 85 |
-
|
| 86 |
-
id = torch.LongTensor([s["id"] for s in samples])
|
| 87 |
-
frames = self._collate_frames([s["source"] for s in samples])
|
| 88 |
-
# sort samples by descending number of frames
|
| 89 |
-
frames_lengths = torch.LongTensor([s["source"].size(0) for s in samples])
|
| 90 |
-
frames_lengths, sort_order = frames_lengths.sort(descending=True)
|
| 91 |
-
id = id.index_select(0, sort_order)
|
| 92 |
-
frames = frames.index_select(0, sort_order)
|
| 93 |
-
|
| 94 |
-
target = None
|
| 95 |
-
target_lengths = None
|
| 96 |
-
prev_output_tokens = None
|
| 97 |
-
if samples[0].get("target", None) is not None:
|
| 98 |
-
ntokens = sum(len(s["target"]) for s in samples)
|
| 99 |
-
target = fairseq_data_utils.collate_tokens(
|
| 100 |
-
[s["target"] for s in samples],
|
| 101 |
-
self.pad_index,
|
| 102 |
-
self.eos_index,
|
| 103 |
-
left_pad=False,
|
| 104 |
-
move_eos_to_beginning=False,
|
| 105 |
-
)
|
| 106 |
-
target = target.index_select(0, sort_order)
|
| 107 |
-
target_lengths = torch.LongTensor(
|
| 108 |
-
[s["target"].size(0) for s in samples]
|
| 109 |
-
).index_select(0, sort_order)
|
| 110 |
-
prev_output_tokens = fairseq_data_utils.collate_tokens(
|
| 111 |
-
[s["target"] for s in samples],
|
| 112 |
-
self.pad_index,
|
| 113 |
-
self.eos_index,
|
| 114 |
-
left_pad=False,
|
| 115 |
-
move_eos_to_beginning=self.move_eos_to_beginning,
|
| 116 |
-
)
|
| 117 |
-
prev_output_tokens = prev_output_tokens.index_select(0, sort_order)
|
| 118 |
-
else:
|
| 119 |
-
ntokens = sum(len(s["source"]) for s in samples)
|
| 120 |
-
|
| 121 |
-
batch = {
|
| 122 |
-
"id": id,
|
| 123 |
-
"ntokens": ntokens,
|
| 124 |
-
"net_input": {"src_tokens": frames, "src_lengths": frames_lengths},
|
| 125 |
-
"target": target,
|
| 126 |
-
"target_lengths": target_lengths,
|
| 127 |
-
"nsentences": len(samples),
|
| 128 |
-
}
|
| 129 |
-
if prev_output_tokens is not None:
|
| 130 |
-
batch["net_input"]["prev_output_tokens"] = prev_output_tokens
|
| 131 |
-
return batch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/data_utils.py
DELETED
|
@@ -1,100 +0,0 @@
|
|
| 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 |
-
|
| 8 |
-
|
| 9 |
-
def calc_mean_invstddev(feature):
|
| 10 |
-
if len(feature.size()) != 2:
|
| 11 |
-
raise ValueError("We expect the input feature to be 2-D tensor")
|
| 12 |
-
mean = feature.mean(0)
|
| 13 |
-
var = feature.var(0)
|
| 14 |
-
# avoid division by ~zero
|
| 15 |
-
eps = 1e-8
|
| 16 |
-
if (var < eps).any():
|
| 17 |
-
return mean, 1.0 / (torch.sqrt(var) + eps)
|
| 18 |
-
return mean, 1.0 / torch.sqrt(var)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def apply_mv_norm(features):
|
| 22 |
-
# If there is less than 2 spectrograms, the variance cannot be computed (is NaN)
|
| 23 |
-
# and normalization is not possible, so return the item as it is
|
| 24 |
-
if features.size(0) < 2:
|
| 25 |
-
return features
|
| 26 |
-
mean, invstddev = calc_mean_invstddev(features)
|
| 27 |
-
res = (features - mean) * invstddev
|
| 28 |
-
return res
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
def lengths_to_encoder_padding_mask(lengths, batch_first=False):
|
| 32 |
-
"""
|
| 33 |
-
convert lengths (a 1-D Long/Int tensor) to 2-D binary tensor
|
| 34 |
-
|
| 35 |
-
Args:
|
| 36 |
-
lengths: a (B, )-shaped tensor
|
| 37 |
-
|
| 38 |
-
Return:
|
| 39 |
-
max_length: maximum length of B sequences
|
| 40 |
-
encoder_padding_mask: a (max_length, B) binary mask, where
|
| 41 |
-
[t, b] = 0 for t < lengths[b] and 1 otherwise
|
| 42 |
-
|
| 43 |
-
TODO:
|
| 44 |
-
kernelize this function if benchmarking shows this function is slow
|
| 45 |
-
"""
|
| 46 |
-
max_lengths = torch.max(lengths).item()
|
| 47 |
-
bsz = lengths.size(0)
|
| 48 |
-
encoder_padding_mask = torch.arange(
|
| 49 |
-
max_lengths
|
| 50 |
-
).to( # a (T, ) tensor with [0, ..., T-1]
|
| 51 |
-
lengths.device
|
| 52 |
-
).view( # move to the right device
|
| 53 |
-
1, max_lengths
|
| 54 |
-
).expand( # reshape to (1, T)-shaped tensor
|
| 55 |
-
bsz, -1
|
| 56 |
-
) >= lengths.view( # expand to (B, T)-shaped tensor
|
| 57 |
-
bsz, 1
|
| 58 |
-
).expand(
|
| 59 |
-
-1, max_lengths
|
| 60 |
-
)
|
| 61 |
-
if not batch_first:
|
| 62 |
-
return encoder_padding_mask.t(), max_lengths
|
| 63 |
-
else:
|
| 64 |
-
return encoder_padding_mask, max_lengths
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
def encoder_padding_mask_to_lengths(
|
| 68 |
-
encoder_padding_mask, max_lengths, batch_size, device
|
| 69 |
-
):
|
| 70 |
-
"""
|
| 71 |
-
convert encoder_padding_mask (2-D binary tensor) to a 1-D tensor
|
| 72 |
-
|
| 73 |
-
Conventionally, encoder output contains a encoder_padding_mask, which is
|
| 74 |
-
a 2-D mask in a shape (T, B), whose (t, b) element indicate whether
|
| 75 |
-
encoder_out[t, b] is a valid output (=0) or not (=1). Occasionally, we
|
| 76 |
-
need to convert this mask tensor to a 1-D tensor in shape (B, ), where
|
| 77 |
-
[b] denotes the valid length of b-th sequence
|
| 78 |
-
|
| 79 |
-
Args:
|
| 80 |
-
encoder_padding_mask: a (T, B)-shaped binary tensor or None; if None,
|
| 81 |
-
indicating all are valid
|
| 82 |
-
Return:
|
| 83 |
-
seq_lengths: a (B,)-shaped tensor, where its (b, )-th element is the
|
| 84 |
-
number of valid elements of b-th sequence
|
| 85 |
-
|
| 86 |
-
max_lengths: maximum length of all sequence, if encoder_padding_mask is
|
| 87 |
-
not None, max_lengths must equal to encoder_padding_mask.size(0)
|
| 88 |
-
|
| 89 |
-
batch_size: batch size; if encoder_padding_mask is
|
| 90 |
-
not None, max_lengths must equal to encoder_padding_mask.size(1)
|
| 91 |
-
|
| 92 |
-
device: which device to put the result on
|
| 93 |
-
"""
|
| 94 |
-
if encoder_padding_mask is None:
|
| 95 |
-
return torch.Tensor([max_lengths] * batch_size).to(torch.int32).to(device)
|
| 96 |
-
|
| 97 |
-
assert encoder_padding_mask.size(0) == max_lengths, "max_lengths does not match"
|
| 98 |
-
assert encoder_padding_mask.size(1) == batch_size, "batch_size does not match"
|
| 99 |
-
|
| 100 |
-
return max_lengths - torch.sum(encoder_padding_mask, dim=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/data/replabels.py
DELETED
|
@@ -1,70 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
"""
|
| 9 |
-
Replabel transforms for use with flashlight's ASG criterion.
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
def replabel_symbol(i):
|
| 14 |
-
"""
|
| 15 |
-
Replabel symbols used in flashlight, currently just "1", "2", ...
|
| 16 |
-
This prevents training with numeral tokens, so this might change in the future
|
| 17 |
-
"""
|
| 18 |
-
return str(i)
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
def pack_replabels(tokens, dictionary, max_reps):
|
| 22 |
-
"""
|
| 23 |
-
Pack a token sequence so that repeated symbols are replaced by replabels
|
| 24 |
-
"""
|
| 25 |
-
if len(tokens) == 0 or max_reps <= 0:
|
| 26 |
-
return tokens
|
| 27 |
-
|
| 28 |
-
replabel_value_to_idx = [0] * (max_reps + 1)
|
| 29 |
-
for i in range(1, max_reps + 1):
|
| 30 |
-
replabel_value_to_idx[i] = dictionary.index(replabel_symbol(i))
|
| 31 |
-
|
| 32 |
-
result = []
|
| 33 |
-
prev_token = -1
|
| 34 |
-
num_reps = 0
|
| 35 |
-
for token in tokens:
|
| 36 |
-
if token == prev_token and num_reps < max_reps:
|
| 37 |
-
num_reps += 1
|
| 38 |
-
else:
|
| 39 |
-
if num_reps > 0:
|
| 40 |
-
result.append(replabel_value_to_idx[num_reps])
|
| 41 |
-
num_reps = 0
|
| 42 |
-
result.append(token)
|
| 43 |
-
prev_token = token
|
| 44 |
-
if num_reps > 0:
|
| 45 |
-
result.append(replabel_value_to_idx[num_reps])
|
| 46 |
-
return result
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def unpack_replabels(tokens, dictionary, max_reps):
|
| 50 |
-
"""
|
| 51 |
-
Unpack a token sequence so that replabels are replaced by repeated symbols
|
| 52 |
-
"""
|
| 53 |
-
if len(tokens) == 0 or max_reps <= 0:
|
| 54 |
-
return tokens
|
| 55 |
-
|
| 56 |
-
replabel_idx_to_value = {}
|
| 57 |
-
for i in range(1, max_reps + 1):
|
| 58 |
-
replabel_idx_to_value[dictionary.index(replabel_symbol(i))] = i
|
| 59 |
-
|
| 60 |
-
result = []
|
| 61 |
-
prev_token = -1
|
| 62 |
-
for token in tokens:
|
| 63 |
-
try:
|
| 64 |
-
for _ in range(replabel_idx_to_value[token]):
|
| 65 |
-
result.append(prev_token)
|
| 66 |
-
prev_token = -1
|
| 67 |
-
except KeyError:
|
| 68 |
-
result.append(token)
|
| 69 |
-
prev_token = token
|
| 70 |
-
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/datasets/asr_prep_json.py
DELETED
|
@@ -1,124 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 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 |
-
from __future__ import absolute_import, division, print_function, unicode_literals
|
| 8 |
-
|
| 9 |
-
import argparse
|
| 10 |
-
import concurrent.futures
|
| 11 |
-
import json
|
| 12 |
-
import multiprocessing
|
| 13 |
-
import os
|
| 14 |
-
from collections import namedtuple
|
| 15 |
-
from itertools import chain
|
| 16 |
-
|
| 17 |
-
import sentencepiece as spm
|
| 18 |
-
from fairseq.data import Dictionary
|
| 19 |
-
|
| 20 |
-
MILLISECONDS_TO_SECONDS = 0.001
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def process_sample(aud_path, lable, utt_id, sp, tgt_dict):
|
| 24 |
-
import torchaudio
|
| 25 |
-
|
| 26 |
-
input = {}
|
| 27 |
-
output = {}
|
| 28 |
-
si, ei = torchaudio.info(aud_path)
|
| 29 |
-
input["length_ms"] = int(
|
| 30 |
-
si.length / si.channels / si.rate / MILLISECONDS_TO_SECONDS
|
| 31 |
-
)
|
| 32 |
-
input["path"] = aud_path
|
| 33 |
-
|
| 34 |
-
token = " ".join(sp.EncodeAsPieces(lable))
|
| 35 |
-
ids = tgt_dict.encode_line(token, append_eos=False)
|
| 36 |
-
output["text"] = lable
|
| 37 |
-
output["token"] = token
|
| 38 |
-
output["tokenid"] = ", ".join(map(str, [t.tolist() for t in ids]))
|
| 39 |
-
return {utt_id: {"input": input, "output": output}}
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def main():
|
| 43 |
-
parser = argparse.ArgumentParser()
|
| 44 |
-
parser.add_argument(
|
| 45 |
-
"--audio-dirs",
|
| 46 |
-
nargs="+",
|
| 47 |
-
default=["-"],
|
| 48 |
-
required=True,
|
| 49 |
-
help="input directories with audio files",
|
| 50 |
-
)
|
| 51 |
-
parser.add_argument(
|
| 52 |
-
"--labels",
|
| 53 |
-
required=True,
|
| 54 |
-
help="aggregated input labels with format <ID LABEL> per line",
|
| 55 |
-
type=argparse.FileType("r", encoding="UTF-8"),
|
| 56 |
-
)
|
| 57 |
-
parser.add_argument(
|
| 58 |
-
"--spm-model",
|
| 59 |
-
required=True,
|
| 60 |
-
help="sentencepiece model to use for encoding",
|
| 61 |
-
type=argparse.FileType("r", encoding="UTF-8"),
|
| 62 |
-
)
|
| 63 |
-
parser.add_argument(
|
| 64 |
-
"--dictionary",
|
| 65 |
-
required=True,
|
| 66 |
-
help="file to load fairseq dictionary from",
|
| 67 |
-
type=argparse.FileType("r", encoding="UTF-8"),
|
| 68 |
-
)
|
| 69 |
-
parser.add_argument("--audio-format", choices=["flac", "wav"], default="wav")
|
| 70 |
-
parser.add_argument(
|
| 71 |
-
"--output",
|
| 72 |
-
required=True,
|
| 73 |
-
type=argparse.FileType("w"),
|
| 74 |
-
help="path to save json output",
|
| 75 |
-
)
|
| 76 |
-
args = parser.parse_args()
|
| 77 |
-
|
| 78 |
-
sp = spm.SentencePieceProcessor()
|
| 79 |
-
sp.Load(args.spm_model.name)
|
| 80 |
-
|
| 81 |
-
tgt_dict = Dictionary.load(args.dictionary)
|
| 82 |
-
|
| 83 |
-
labels = {}
|
| 84 |
-
for line in args.labels:
|
| 85 |
-
(utt_id, label) = line.split(" ", 1)
|
| 86 |
-
labels[utt_id] = label
|
| 87 |
-
if len(labels) == 0:
|
| 88 |
-
raise Exception("No labels found in ", args.labels_path)
|
| 89 |
-
|
| 90 |
-
Sample = namedtuple("Sample", "aud_path utt_id")
|
| 91 |
-
samples = []
|
| 92 |
-
for path, _, files in chain.from_iterable(
|
| 93 |
-
os.walk(path) for path in args.audio_dirs
|
| 94 |
-
):
|
| 95 |
-
for f in files:
|
| 96 |
-
if f.endswith(args.audio_format):
|
| 97 |
-
if len(os.path.splitext(f)) != 2:
|
| 98 |
-
raise Exception("Expect <utt_id.extension> file name. Got: ", f)
|
| 99 |
-
utt_id = os.path.splitext(f)[0]
|
| 100 |
-
if utt_id not in labels:
|
| 101 |
-
continue
|
| 102 |
-
samples.append(Sample(os.path.join(path, f), utt_id))
|
| 103 |
-
|
| 104 |
-
utts = {}
|
| 105 |
-
num_cpu = multiprocessing.cpu_count()
|
| 106 |
-
with concurrent.futures.ThreadPoolExecutor(max_workers=num_cpu) as executor:
|
| 107 |
-
future_to_sample = {
|
| 108 |
-
executor.submit(
|
| 109 |
-
process_sample, s.aud_path, labels[s.utt_id], s.utt_id, sp, tgt_dict
|
| 110 |
-
): s
|
| 111 |
-
for s in samples
|
| 112 |
-
}
|
| 113 |
-
for future in concurrent.futures.as_completed(future_to_sample):
|
| 114 |
-
try:
|
| 115 |
-
data = future.result()
|
| 116 |
-
except Exception as exc:
|
| 117 |
-
print("generated an exception: ", exc)
|
| 118 |
-
else:
|
| 119 |
-
utts.update(data)
|
| 120 |
-
json.dump({"utts": utts}, args.output, indent=4)
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
if __name__ == "__main__":
|
| 124 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/datasets/prepare-librispeech.sh
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env bash
|
| 2 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 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 |
-
# Prepare librispeech dataset
|
| 8 |
-
|
| 9 |
-
base_url=www.openslr.org/resources/12
|
| 10 |
-
train_dir=train_960
|
| 11 |
-
|
| 12 |
-
if [ "$#" -ne 2 ]; then
|
| 13 |
-
echo "Usage: $0 <download_dir> <out_dir>"
|
| 14 |
-
echo "e.g.: $0 /tmp/librispeech_raw/ ~/data/librispeech_final"
|
| 15 |
-
exit 1
|
| 16 |
-
fi
|
| 17 |
-
|
| 18 |
-
download_dir=${1%/}
|
| 19 |
-
out_dir=${2%/}
|
| 20 |
-
|
| 21 |
-
fairseq_root=~/fairseq-py/
|
| 22 |
-
mkdir -p ${out_dir}
|
| 23 |
-
cd ${out_dir} || exit
|
| 24 |
-
|
| 25 |
-
nbpe=5000
|
| 26 |
-
bpemode=unigram
|
| 27 |
-
|
| 28 |
-
if [ ! -d "$fairseq_root" ]; then
|
| 29 |
-
echo "$0: Please set correct fairseq_root"
|
| 30 |
-
exit 1
|
| 31 |
-
fi
|
| 32 |
-
|
| 33 |
-
echo "Data Download"
|
| 34 |
-
for part in dev-clean test-clean dev-other test-other train-clean-100 train-clean-360 train-other-500; do
|
| 35 |
-
url=$base_url/$part.tar.gz
|
| 36 |
-
if ! wget -P $download_dir $url; then
|
| 37 |
-
echo "$0: wget failed for $url"
|
| 38 |
-
exit 1
|
| 39 |
-
fi
|
| 40 |
-
if ! tar -C $download_dir -xvzf $download_dir/$part.tar.gz; then
|
| 41 |
-
echo "$0: error un-tarring archive $download_dir/$part.tar.gz"
|
| 42 |
-
exit 1
|
| 43 |
-
fi
|
| 44 |
-
done
|
| 45 |
-
|
| 46 |
-
echo "Merge all train packs into one"
|
| 47 |
-
mkdir -p ${download_dir}/LibriSpeech/${train_dir}/
|
| 48 |
-
for part in train-clean-100 train-clean-360 train-other-500; do
|
| 49 |
-
mv ${download_dir}/LibriSpeech/${part}/* $download_dir/LibriSpeech/${train_dir}/
|
| 50 |
-
done
|
| 51 |
-
echo "Merge train text"
|
| 52 |
-
find ${download_dir}/LibriSpeech/${train_dir}/ -name '*.txt' -exec cat {} \; >> ${download_dir}/LibriSpeech/${train_dir}/text
|
| 53 |
-
|
| 54 |
-
# Use combined dev-clean and dev-other as validation set
|
| 55 |
-
find ${download_dir}/LibriSpeech/dev-clean/ ${download_dir}/LibriSpeech/dev-other/ -name '*.txt' -exec cat {} \; >> ${download_dir}/LibriSpeech/valid_text
|
| 56 |
-
find ${download_dir}/LibriSpeech/test-clean/ -name '*.txt' -exec cat {} \; >> ${download_dir}/LibriSpeech/test-clean/text
|
| 57 |
-
find ${download_dir}/LibriSpeech/test-other/ -name '*.txt' -exec cat {} \; >> ${download_dir}/LibriSpeech/test-other/text
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
dict=data/lang_char/${train_dir}_${bpemode}${nbpe}_units.txt
|
| 61 |
-
encoded=data/lang_char/${train_dir}_${bpemode}${nbpe}_encoded.txt
|
| 62 |
-
fairseq_dict=data/lang_char/${train_dir}_${bpemode}${nbpe}_fairseq_dict.txt
|
| 63 |
-
bpemodel=data/lang_char/${train_dir}_${bpemode}${nbpe}
|
| 64 |
-
echo "dictionary: ${dict}"
|
| 65 |
-
echo "Dictionary preparation"
|
| 66 |
-
mkdir -p data/lang_char/
|
| 67 |
-
echo "<unk> 3" > ${dict}
|
| 68 |
-
echo "</s> 2" >> ${dict}
|
| 69 |
-
echo "<pad> 1" >> ${dict}
|
| 70 |
-
cut -f 2- -d" " ${download_dir}/LibriSpeech/${train_dir}/text > data/lang_char/input.txt
|
| 71 |
-
spm_train --input=data/lang_char/input.txt --vocab_size=${nbpe} --model_type=${bpemode} --model_prefix=${bpemodel} --input_sentence_size=100000000 --unk_id=3 --eos_id=2 --pad_id=1 --bos_id=-1 --character_coverage=1
|
| 72 |
-
spm_encode --model=${bpemodel}.model --output_format=piece < data/lang_char/input.txt > ${encoded}
|
| 73 |
-
cat ${encoded} | tr ' ' '\n' | sort | uniq | awk '{print $0 " " NR+3}' >> ${dict}
|
| 74 |
-
cat ${encoded} | tr ' ' '\n' | sort | uniq -c | awk '{print $2 " " $1}' > ${fairseq_dict}
|
| 75 |
-
wc -l ${dict}
|
| 76 |
-
|
| 77 |
-
echo "Prepare train and test jsons"
|
| 78 |
-
for part in train_960 test-other test-clean; do
|
| 79 |
-
python ${fairseq_root}/examples/speech_recognition/datasets/asr_prep_json.py --audio-dirs ${download_dir}/LibriSpeech/${part} --labels ${download_dir}/LibriSpeech/${part}/text --spm-model ${bpemodel}.model --audio-format flac --dictionary ${fairseq_dict} --output ${part}.json
|
| 80 |
-
done
|
| 81 |
-
# fairseq expects to find train.json and valid.json during training
|
| 82 |
-
mv train_960.json train.json
|
| 83 |
-
|
| 84 |
-
echo "Prepare valid json"
|
| 85 |
-
python ${fairseq_root}/examples/speech_recognition/datasets/asr_prep_json.py --audio-dirs ${download_dir}/LibriSpeech/dev-clean ${download_dir}/LibriSpeech/dev-other --labels ${download_dir}/LibriSpeech/valid_text --spm-model ${bpemodel}.model --audio-format flac --dictionary ${fairseq_dict} --output valid.json
|
| 86 |
-
|
| 87 |
-
cp ${fairseq_dict} ./dict.txt
|
| 88 |
-
cp ${bpemodel}.model ./spm.model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/infer.py
DELETED
|
@@ -1,435 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3 -u
|
| 2 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 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 |
-
"""
|
| 8 |
-
Run inference for pre-processed data with a trained model.
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import ast
|
| 12 |
-
import logging
|
| 13 |
-
import math
|
| 14 |
-
import os
|
| 15 |
-
import sys
|
| 16 |
-
|
| 17 |
-
import editdistance
|
| 18 |
-
import numpy as np
|
| 19 |
-
import torch
|
| 20 |
-
from fairseq import checkpoint_utils, options, progress_bar, tasks, utils
|
| 21 |
-
from fairseq.data.data_utils import post_process
|
| 22 |
-
from fairseq.logging.meters import StopwatchMeter, TimeMeter
|
| 23 |
-
|
| 24 |
-
logging.basicConfig()
|
| 25 |
-
logging.root.setLevel(logging.INFO)
|
| 26 |
-
logging.basicConfig(level=logging.INFO)
|
| 27 |
-
logger = logging.getLogger(__name__)
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def add_asr_eval_argument(parser):
|
| 31 |
-
parser.add_argument("--kspmodel", default=None, help="sentence piece model")
|
| 32 |
-
parser.add_argument(
|
| 33 |
-
"--wfstlm", default=None, help="wfstlm on dictonary output units"
|
| 34 |
-
)
|
| 35 |
-
parser.add_argument(
|
| 36 |
-
"--rnnt_decoding_type",
|
| 37 |
-
default="greedy",
|
| 38 |
-
help="wfstlm on dictonary\
|
| 39 |
-
output units",
|
| 40 |
-
)
|
| 41 |
-
try:
|
| 42 |
-
parser.add_argument(
|
| 43 |
-
"--lm-weight",
|
| 44 |
-
"--lm_weight",
|
| 45 |
-
type=float,
|
| 46 |
-
default=0.2,
|
| 47 |
-
help="weight for lm while interpolating with neural score",
|
| 48 |
-
)
|
| 49 |
-
except:
|
| 50 |
-
pass
|
| 51 |
-
parser.add_argument(
|
| 52 |
-
"--rnnt_len_penalty", default=-0.5, help="rnnt length penalty on word level"
|
| 53 |
-
)
|
| 54 |
-
parser.add_argument(
|
| 55 |
-
"--w2l-decoder",
|
| 56 |
-
choices=["viterbi", "kenlm", "fairseqlm"],
|
| 57 |
-
help="use a w2l decoder",
|
| 58 |
-
)
|
| 59 |
-
parser.add_argument("--lexicon", help="lexicon for w2l decoder")
|
| 60 |
-
parser.add_argument("--unit-lm", action="store_true", help="if using a unit lm")
|
| 61 |
-
parser.add_argument("--kenlm-model", "--lm-model", help="lm model for w2l decoder")
|
| 62 |
-
parser.add_argument("--beam-threshold", type=float, default=25.0)
|
| 63 |
-
parser.add_argument("--beam-size-token", type=float, default=100)
|
| 64 |
-
parser.add_argument("--word-score", type=float, default=1.0)
|
| 65 |
-
parser.add_argument("--unk-weight", type=float, default=-math.inf)
|
| 66 |
-
parser.add_argument("--sil-weight", type=float, default=0.0)
|
| 67 |
-
parser.add_argument(
|
| 68 |
-
"--dump-emissions",
|
| 69 |
-
type=str,
|
| 70 |
-
default=None,
|
| 71 |
-
help="if present, dumps emissions into this file and exits",
|
| 72 |
-
)
|
| 73 |
-
parser.add_argument(
|
| 74 |
-
"--dump-features",
|
| 75 |
-
type=str,
|
| 76 |
-
default=None,
|
| 77 |
-
help="if present, dumps features into this file and exits",
|
| 78 |
-
)
|
| 79 |
-
parser.add_argument(
|
| 80 |
-
"--load-emissions",
|
| 81 |
-
type=str,
|
| 82 |
-
default=None,
|
| 83 |
-
help="if present, loads emissions from this file",
|
| 84 |
-
)
|
| 85 |
-
return parser
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
def check_args(args):
|
| 89 |
-
# assert args.path is not None, "--path required for generation!"
|
| 90 |
-
# assert args.results_path is not None, "--results_path required for generation!"
|
| 91 |
-
assert (
|
| 92 |
-
not args.sampling or args.nbest == args.beam
|
| 93 |
-
), "--sampling requires --nbest to be equal to --beam"
|
| 94 |
-
assert (
|
| 95 |
-
args.replace_unk is None or args.raw_text
|
| 96 |
-
), "--replace-unk requires a raw text dataset (--raw-text)"
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
def get_dataset_itr(args, task, models):
|
| 100 |
-
return task.get_batch_iterator(
|
| 101 |
-
dataset=task.dataset(args.gen_subset),
|
| 102 |
-
max_tokens=args.max_tokens,
|
| 103 |
-
max_sentences=args.batch_size,
|
| 104 |
-
max_positions=(sys.maxsize, sys.maxsize),
|
| 105 |
-
ignore_invalid_inputs=args.skip_invalid_size_inputs_valid_test,
|
| 106 |
-
required_batch_size_multiple=args.required_batch_size_multiple,
|
| 107 |
-
num_shards=args.num_shards,
|
| 108 |
-
shard_id=args.shard_id,
|
| 109 |
-
num_workers=args.num_workers,
|
| 110 |
-
data_buffer_size=args.data_buffer_size,
|
| 111 |
-
).next_epoch_itr(shuffle=False)
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
def process_predictions(
|
| 115 |
-
args, hypos, sp, tgt_dict, target_tokens, res_files, speaker, id
|
| 116 |
-
):
|
| 117 |
-
for hypo in hypos[: min(len(hypos), args.nbest)]:
|
| 118 |
-
hyp_pieces = tgt_dict.string(hypo["tokens"].int().cpu())
|
| 119 |
-
|
| 120 |
-
if "words" in hypo:
|
| 121 |
-
hyp_words = " ".join(hypo["words"])
|
| 122 |
-
else:
|
| 123 |
-
hyp_words = post_process(hyp_pieces, args.post_process)
|
| 124 |
-
|
| 125 |
-
if res_files is not None:
|
| 126 |
-
print(
|
| 127 |
-
"{} ({}-{})".format(hyp_pieces, speaker, id),
|
| 128 |
-
file=res_files["hypo.units"],
|
| 129 |
-
)
|
| 130 |
-
print(
|
| 131 |
-
"{} ({}-{})".format(hyp_words, speaker, id),
|
| 132 |
-
file=res_files["hypo.words"],
|
| 133 |
-
)
|
| 134 |
-
|
| 135 |
-
tgt_pieces = tgt_dict.string(target_tokens)
|
| 136 |
-
tgt_words = post_process(tgt_pieces, args.post_process)
|
| 137 |
-
|
| 138 |
-
if res_files is not None:
|
| 139 |
-
print(
|
| 140 |
-
"{} ({}-{})".format(tgt_pieces, speaker, id),
|
| 141 |
-
file=res_files["ref.units"],
|
| 142 |
-
)
|
| 143 |
-
print(
|
| 144 |
-
"{} ({}-{})".format(tgt_words, speaker, id), file=res_files["ref.words"]
|
| 145 |
-
)
|
| 146 |
-
|
| 147 |
-
if not args.quiet:
|
| 148 |
-
logger.info("HYPO:" + hyp_words)
|
| 149 |
-
logger.info("TARGET:" + tgt_words)
|
| 150 |
-
logger.info("___________________")
|
| 151 |
-
|
| 152 |
-
hyp_words = hyp_words.split()
|
| 153 |
-
tgt_words = tgt_words.split()
|
| 154 |
-
return editdistance.eval(hyp_words, tgt_words), len(tgt_words)
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
def prepare_result_files(args):
|
| 158 |
-
def get_res_file(file_prefix):
|
| 159 |
-
if args.num_shards > 1:
|
| 160 |
-
file_prefix = f"{args.shard_id}_{file_prefix}"
|
| 161 |
-
path = os.path.join(
|
| 162 |
-
args.results_path,
|
| 163 |
-
"{}-{}-{}.txt".format(
|
| 164 |
-
file_prefix, os.path.basename(args.path), args.gen_subset
|
| 165 |
-
),
|
| 166 |
-
)
|
| 167 |
-
return open(path, "w", buffering=1)
|
| 168 |
-
|
| 169 |
-
if not args.results_path:
|
| 170 |
-
return None
|
| 171 |
-
|
| 172 |
-
return {
|
| 173 |
-
"hypo.words": get_res_file("hypo.word"),
|
| 174 |
-
"hypo.units": get_res_file("hypo.units"),
|
| 175 |
-
"ref.words": get_res_file("ref.word"),
|
| 176 |
-
"ref.units": get_res_file("ref.units"),
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
def optimize_models(args, use_cuda, models):
|
| 181 |
-
"""Optimize ensemble for generation"""
|
| 182 |
-
for model in models:
|
| 183 |
-
model.make_generation_fast_(
|
| 184 |
-
beamable_mm_beam_size=None if args.no_beamable_mm else args.beam,
|
| 185 |
-
need_attn=args.print_alignment,
|
| 186 |
-
)
|
| 187 |
-
if args.fp16:
|
| 188 |
-
model.half()
|
| 189 |
-
if use_cuda:
|
| 190 |
-
model.cuda()
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
def apply_half(t):
|
| 194 |
-
if t.dtype is torch.float32:
|
| 195 |
-
return t.to(dtype=torch.half)
|
| 196 |
-
return t
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
class ExistingEmissionsDecoder(object):
|
| 200 |
-
def __init__(self, decoder, emissions):
|
| 201 |
-
self.decoder = decoder
|
| 202 |
-
self.emissions = emissions
|
| 203 |
-
|
| 204 |
-
def generate(self, models, sample, **unused):
|
| 205 |
-
ids = sample["id"].cpu().numpy()
|
| 206 |
-
try:
|
| 207 |
-
emissions = np.stack(self.emissions[ids])
|
| 208 |
-
except:
|
| 209 |
-
print([x.shape for x in self.emissions[ids]])
|
| 210 |
-
raise Exception("invalid sizes")
|
| 211 |
-
emissions = torch.from_numpy(emissions)
|
| 212 |
-
return self.decoder.decode(emissions)
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
def main(args, task=None, model_state=None):
|
| 216 |
-
check_args(args)
|
| 217 |
-
|
| 218 |
-
use_fp16 = args.fp16
|
| 219 |
-
if args.max_tokens is None and args.batch_size is None:
|
| 220 |
-
args.max_tokens = 4000000
|
| 221 |
-
logger.info(args)
|
| 222 |
-
|
| 223 |
-
use_cuda = torch.cuda.is_available() and not args.cpu
|
| 224 |
-
|
| 225 |
-
logger.info("| decoding with criterion {}".format(args.criterion))
|
| 226 |
-
|
| 227 |
-
task = tasks.setup_task(args)
|
| 228 |
-
|
| 229 |
-
# Load ensemble
|
| 230 |
-
if args.load_emissions:
|
| 231 |
-
models, criterions = [], []
|
| 232 |
-
task.load_dataset(args.gen_subset)
|
| 233 |
-
else:
|
| 234 |
-
logger.info("| loading model(s) from {}".format(args.path))
|
| 235 |
-
models, saved_cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
| 236 |
-
utils.split_paths(args.path, separator="\\"),
|
| 237 |
-
arg_overrides=ast.literal_eval(args.model_overrides),
|
| 238 |
-
task=task,
|
| 239 |
-
suffix=args.checkpoint_suffix,
|
| 240 |
-
strict=(args.checkpoint_shard_count == 1),
|
| 241 |
-
num_shards=args.checkpoint_shard_count,
|
| 242 |
-
state=model_state,
|
| 243 |
-
)
|
| 244 |
-
optimize_models(args, use_cuda, models)
|
| 245 |
-
task.load_dataset(args.gen_subset, task_cfg=saved_cfg.task)
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
# Set dictionary
|
| 249 |
-
tgt_dict = task.target_dictionary
|
| 250 |
-
|
| 251 |
-
logger.info(
|
| 252 |
-
"| {} {} {} examples".format(
|
| 253 |
-
args.data, args.gen_subset, len(task.dataset(args.gen_subset))
|
| 254 |
-
)
|
| 255 |
-
)
|
| 256 |
-
|
| 257 |
-
# hack to pass transitions to W2lDecoder
|
| 258 |
-
if args.criterion == "asg_loss":
|
| 259 |
-
raise NotImplementedError("asg_loss is currently not supported")
|
| 260 |
-
# trans = criterions[0].asg.trans.data
|
| 261 |
-
# args.asg_transitions = torch.flatten(trans).tolist()
|
| 262 |
-
|
| 263 |
-
# Load dataset (possibly sharded)
|
| 264 |
-
itr = get_dataset_itr(args, task, models)
|
| 265 |
-
|
| 266 |
-
# Initialize generator
|
| 267 |
-
gen_timer = StopwatchMeter()
|
| 268 |
-
|
| 269 |
-
def build_generator(args):
|
| 270 |
-
w2l_decoder = getattr(args, "w2l_decoder", None)
|
| 271 |
-
if w2l_decoder == "viterbi":
|
| 272 |
-
from examples.speech_recognition.w2l_decoder import W2lViterbiDecoder
|
| 273 |
-
|
| 274 |
-
return W2lViterbiDecoder(args, task.target_dictionary)
|
| 275 |
-
elif w2l_decoder == "kenlm":
|
| 276 |
-
from examples.speech_recognition.w2l_decoder import W2lKenLMDecoder
|
| 277 |
-
|
| 278 |
-
return W2lKenLMDecoder(args, task.target_dictionary)
|
| 279 |
-
elif w2l_decoder == "fairseqlm":
|
| 280 |
-
from examples.speech_recognition.w2l_decoder import W2lFairseqLMDecoder
|
| 281 |
-
|
| 282 |
-
return W2lFairseqLMDecoder(args, task.target_dictionary)
|
| 283 |
-
else:
|
| 284 |
-
print(
|
| 285 |
-
"only flashlight decoders with (viterbi, kenlm, fairseqlm) options are supported at the moment"
|
| 286 |
-
)
|
| 287 |
-
|
| 288 |
-
# please do not touch this unless you test both generate.py and infer.py with audio_pretraining task
|
| 289 |
-
generator = build_generator(args)
|
| 290 |
-
|
| 291 |
-
if args.load_emissions:
|
| 292 |
-
generator = ExistingEmissionsDecoder(
|
| 293 |
-
generator, np.load(args.load_emissions, allow_pickle=True)
|
| 294 |
-
)
|
| 295 |
-
logger.info("loaded emissions from " + args.load_emissions)
|
| 296 |
-
|
| 297 |
-
num_sentences = 0
|
| 298 |
-
|
| 299 |
-
if args.results_path is not None and not os.path.exists(args.results_path):
|
| 300 |
-
os.makedirs(args.results_path)
|
| 301 |
-
|
| 302 |
-
max_source_pos = (
|
| 303 |
-
utils.resolve_max_positions(
|
| 304 |
-
task.max_positions(), *[model.max_positions() for model in models]
|
| 305 |
-
),
|
| 306 |
-
)
|
| 307 |
-
|
| 308 |
-
if max_source_pos is not None:
|
| 309 |
-
max_source_pos = max_source_pos[0]
|
| 310 |
-
if max_source_pos is not None:
|
| 311 |
-
max_source_pos = max_source_pos[0] - 1
|
| 312 |
-
|
| 313 |
-
if args.dump_emissions:
|
| 314 |
-
emissions = {}
|
| 315 |
-
if args.dump_features:
|
| 316 |
-
features = {}
|
| 317 |
-
models[0].bert.proj = None
|
| 318 |
-
else:
|
| 319 |
-
res_files = prepare_result_files(args)
|
| 320 |
-
errs_t = 0
|
| 321 |
-
lengths_t = 0
|
| 322 |
-
with progress_bar.build_progress_bar(args, itr) as t:
|
| 323 |
-
wps_meter = TimeMeter()
|
| 324 |
-
for sample in t:
|
| 325 |
-
sample = utils.move_to_cuda(sample) if use_cuda else sample
|
| 326 |
-
if use_fp16:
|
| 327 |
-
sample = utils.apply_to_sample(apply_half, sample)
|
| 328 |
-
if "net_input" not in sample:
|
| 329 |
-
continue
|
| 330 |
-
|
| 331 |
-
prefix_tokens = None
|
| 332 |
-
if args.prefix_size > 0:
|
| 333 |
-
prefix_tokens = sample["target"][:, : args.prefix_size]
|
| 334 |
-
|
| 335 |
-
gen_timer.start()
|
| 336 |
-
if args.dump_emissions:
|
| 337 |
-
with torch.no_grad():
|
| 338 |
-
encoder_out = models[0](**sample["net_input"])
|
| 339 |
-
emm = models[0].get_normalized_probs(encoder_out, log_probs=True)
|
| 340 |
-
emm = emm.transpose(0, 1).cpu().numpy()
|
| 341 |
-
for i, id in enumerate(sample["id"]):
|
| 342 |
-
emissions[id.item()] = emm[i]
|
| 343 |
-
continue
|
| 344 |
-
elif args.dump_features:
|
| 345 |
-
with torch.no_grad():
|
| 346 |
-
encoder_out = models[0](**sample["net_input"])
|
| 347 |
-
feat = encoder_out["encoder_out"].transpose(0, 1).cpu().numpy()
|
| 348 |
-
for i, id in enumerate(sample["id"]):
|
| 349 |
-
padding = (
|
| 350 |
-
encoder_out["encoder_padding_mask"][i].cpu().numpy()
|
| 351 |
-
if encoder_out["encoder_padding_mask"] is not None
|
| 352 |
-
else None
|
| 353 |
-
)
|
| 354 |
-
features[id.item()] = (feat[i], padding)
|
| 355 |
-
continue
|
| 356 |
-
hypos = task.inference_step(generator, models, sample, prefix_tokens)
|
| 357 |
-
num_generated_tokens = sum(len(h[0]["tokens"]) for h in hypos)
|
| 358 |
-
gen_timer.stop(num_generated_tokens)
|
| 359 |
-
|
| 360 |
-
for i, sample_id in enumerate(sample["id"].tolist()):
|
| 361 |
-
speaker = None
|
| 362 |
-
# id = task.dataset(args.gen_subset).ids[int(sample_id)]
|
| 363 |
-
id = sample_id
|
| 364 |
-
toks = (
|
| 365 |
-
sample["target"][i, :]
|
| 366 |
-
if "target_label" not in sample
|
| 367 |
-
else sample["target_label"][i, :]
|
| 368 |
-
)
|
| 369 |
-
target_tokens = utils.strip_pad(toks, tgt_dict.pad()).int().cpu()
|
| 370 |
-
# Process top predictions
|
| 371 |
-
errs, length = process_predictions(
|
| 372 |
-
args,
|
| 373 |
-
hypos[i],
|
| 374 |
-
None,
|
| 375 |
-
tgt_dict,
|
| 376 |
-
target_tokens,
|
| 377 |
-
res_files,
|
| 378 |
-
speaker,
|
| 379 |
-
id,
|
| 380 |
-
)
|
| 381 |
-
errs_t += errs
|
| 382 |
-
lengths_t += length
|
| 383 |
-
|
| 384 |
-
wps_meter.update(num_generated_tokens)
|
| 385 |
-
t.log({"wps": round(wps_meter.avg)})
|
| 386 |
-
num_sentences += (
|
| 387 |
-
sample["nsentences"] if "nsentences" in sample else sample["id"].numel()
|
| 388 |
-
)
|
| 389 |
-
|
| 390 |
-
wer = None
|
| 391 |
-
if args.dump_emissions:
|
| 392 |
-
emm_arr = []
|
| 393 |
-
for i in range(len(emissions)):
|
| 394 |
-
emm_arr.append(emissions[i])
|
| 395 |
-
np.save(args.dump_emissions, emm_arr)
|
| 396 |
-
logger.info(f"saved {len(emissions)} emissions to {args.dump_emissions}")
|
| 397 |
-
elif args.dump_features:
|
| 398 |
-
feat_arr = []
|
| 399 |
-
for i in range(len(features)):
|
| 400 |
-
feat_arr.append(features[i])
|
| 401 |
-
np.save(args.dump_features, feat_arr)
|
| 402 |
-
logger.info(f"saved {len(features)} emissions to {args.dump_features}")
|
| 403 |
-
else:
|
| 404 |
-
if lengths_t > 0:
|
| 405 |
-
wer = errs_t * 100.0 / lengths_t
|
| 406 |
-
logger.info(f"WER: {wer}")
|
| 407 |
-
|
| 408 |
-
logger.info(
|
| 409 |
-
"| Processed {} sentences ({} tokens) in {:.1f}s ({:.2f}"
|
| 410 |
-
"sentences/s, {:.2f} tokens/s)".format(
|
| 411 |
-
num_sentences,
|
| 412 |
-
gen_timer.n,
|
| 413 |
-
gen_timer.sum,
|
| 414 |
-
num_sentences / gen_timer.sum,
|
| 415 |
-
1.0 / gen_timer.avg,
|
| 416 |
-
)
|
| 417 |
-
)
|
| 418 |
-
logger.info("| Generate {} with beam={}".format(args.gen_subset, args.beam))
|
| 419 |
-
return task, wer
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
def make_parser():
|
| 423 |
-
parser = options.get_generation_parser()
|
| 424 |
-
parser = add_asr_eval_argument(parser)
|
| 425 |
-
return parser
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
def cli_main():
|
| 429 |
-
parser = make_parser()
|
| 430 |
-
args = options.parse_args_and_arch(parser)
|
| 431 |
-
main(args)
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
if __name__ == "__main__":
|
| 435 |
-
cli_main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/__init__.py
DELETED
|
File without changes
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/add-self-loop-simple.cc
DELETED
|
@@ -1,94 +0,0 @@
|
|
| 1 |
-
/*
|
| 2 |
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
| 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 |
-
|
| 8 |
-
#include <iostream>
|
| 9 |
-
#include "fstext/fstext-lib.h" // @manual
|
| 10 |
-
#include "util/common-utils.h" // @manual
|
| 11 |
-
|
| 12 |
-
/*
|
| 13 |
-
* This program is to modify a FST without self-loop by:
|
| 14 |
-
* for each incoming arc with non-eps input symbol, add a self-loop arc
|
| 15 |
-
* with that non-eps symbol as input and eps as output.
|
| 16 |
-
*
|
| 17 |
-
* This is to make sure the resultant FST can do deduplication for repeated
|
| 18 |
-
* symbols, which is very common in acoustic model
|
| 19 |
-
*
|
| 20 |
-
*/
|
| 21 |
-
namespace {
|
| 22 |
-
int32 AddSelfLoopsSimple(fst::StdVectorFst* fst) {
|
| 23 |
-
typedef fst::MutableArcIterator<fst::StdVectorFst> IterType;
|
| 24 |
-
|
| 25 |
-
int32 num_states_before = fst->NumStates();
|
| 26 |
-
fst::MakePrecedingInputSymbolsSame(false, fst);
|
| 27 |
-
int32 num_states_after = fst->NumStates();
|
| 28 |
-
KALDI_LOG << "There are " << num_states_before
|
| 29 |
-
<< " states in the original FST; "
|
| 30 |
-
<< " after MakePrecedingInputSymbolsSame, there are "
|
| 31 |
-
<< num_states_after << " states " << std::endl;
|
| 32 |
-
|
| 33 |
-
auto weight_one = fst::StdArc::Weight::One();
|
| 34 |
-
|
| 35 |
-
int32 num_arc_added = 0;
|
| 36 |
-
|
| 37 |
-
fst::StdArc self_loop_arc;
|
| 38 |
-
self_loop_arc.weight = weight_one;
|
| 39 |
-
|
| 40 |
-
int32 num_states = fst->NumStates();
|
| 41 |
-
std::vector<std::set<int32>> incoming_non_eps_label_per_state(num_states);
|
| 42 |
-
|
| 43 |
-
for (int32 state = 0; state < num_states; state++) {
|
| 44 |
-
for (IterType aiter(fst, state); !aiter.Done(); aiter.Next()) {
|
| 45 |
-
fst::StdArc arc(aiter.Value());
|
| 46 |
-
if (arc.ilabel != 0) {
|
| 47 |
-
incoming_non_eps_label_per_state[arc.nextstate].insert(arc.ilabel);
|
| 48 |
-
}
|
| 49 |
-
}
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
for (int32 state = 0; state < num_states; state++) {
|
| 53 |
-
if (!incoming_non_eps_label_per_state[state].empty()) {
|
| 54 |
-
auto& ilabel_set = incoming_non_eps_label_per_state[state];
|
| 55 |
-
for (auto it = ilabel_set.begin(); it != ilabel_set.end(); it++) {
|
| 56 |
-
self_loop_arc.ilabel = *it;
|
| 57 |
-
self_loop_arc.olabel = 0;
|
| 58 |
-
self_loop_arc.nextstate = state;
|
| 59 |
-
fst->AddArc(state, self_loop_arc);
|
| 60 |
-
num_arc_added++;
|
| 61 |
-
}
|
| 62 |
-
}
|
| 63 |
-
}
|
| 64 |
-
return num_arc_added;
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
void print_usage() {
|
| 68 |
-
std::cout << "add-self-loop-simple usage:\n"
|
| 69 |
-
"\tadd-self-loop-simple <in-fst> <out-fst> \n";
|
| 70 |
-
}
|
| 71 |
-
} // namespace
|
| 72 |
-
|
| 73 |
-
int main(int argc, char** argv) {
|
| 74 |
-
if (argc != 3) {
|
| 75 |
-
print_usage();
|
| 76 |
-
exit(1);
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
auto input = argv[1];
|
| 80 |
-
auto output = argv[2];
|
| 81 |
-
|
| 82 |
-
auto fst = fst::ReadFstKaldi(input);
|
| 83 |
-
auto num_states = fst->NumStates();
|
| 84 |
-
KALDI_LOG << "Loading FST from " << input << " with " << num_states
|
| 85 |
-
<< " states." << std::endl;
|
| 86 |
-
|
| 87 |
-
int32 num_arc_added = AddSelfLoopsSimple(fst);
|
| 88 |
-
KALDI_LOG << "Adding " << num_arc_added << " self-loop arcs " << std::endl;
|
| 89 |
-
|
| 90 |
-
fst::WriteFstKaldi(*fst, std::string(output));
|
| 91 |
-
KALDI_LOG << "Writing FST to " << output << std::endl;
|
| 92 |
-
|
| 93 |
-
delete fst;
|
| 94 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/config/kaldi_initializer.yaml
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
# @package _group_
|
| 2 |
-
|
| 3 |
-
data_dir: ???
|
| 4 |
-
fst_dir: ???
|
| 5 |
-
in_labels: ???
|
| 6 |
-
kaldi_root: ???
|
| 7 |
-
lm_arpa: ???
|
| 8 |
-
blank_symbol: <s>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/kaldi_decoder.py
DELETED
|
@@ -1,243 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
import logging
|
| 9 |
-
import os
|
| 10 |
-
import warnings
|
| 11 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 12 |
-
from dataclasses import dataclass
|
| 13 |
-
from typing import Optional
|
| 14 |
-
|
| 15 |
-
import torch
|
| 16 |
-
from fairseq.dataclass import FairseqDataclass
|
| 17 |
-
from omegaconf import MISSING
|
| 18 |
-
|
| 19 |
-
from .kaldi_initializer import KaldiInitializerConfig, initalize_kaldi
|
| 20 |
-
|
| 21 |
-
logger = logging.getLogger(__name__)
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@dataclass
|
| 25 |
-
class KaldiDecoderConfig(FairseqDataclass):
|
| 26 |
-
hlg_graph_path: Optional[str] = None
|
| 27 |
-
output_dict: str = MISSING
|
| 28 |
-
|
| 29 |
-
kaldi_initializer_config: Optional[KaldiInitializerConfig] = None
|
| 30 |
-
|
| 31 |
-
acoustic_scale: float = 0.5
|
| 32 |
-
max_active: int = 10000
|
| 33 |
-
beam_delta: float = 0.5
|
| 34 |
-
hash_ratio: float = 2.0
|
| 35 |
-
|
| 36 |
-
is_lattice: bool = False
|
| 37 |
-
lattice_beam: float = 10.0
|
| 38 |
-
prune_interval: int = 25
|
| 39 |
-
determinize_lattice: bool = True
|
| 40 |
-
prune_scale: float = 0.1
|
| 41 |
-
max_mem: int = 0
|
| 42 |
-
phone_determinize: bool = True
|
| 43 |
-
word_determinize: bool = True
|
| 44 |
-
minimize: bool = True
|
| 45 |
-
|
| 46 |
-
num_threads: int = 1
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
class KaldiDecoder(object):
|
| 50 |
-
def __init__(
|
| 51 |
-
self,
|
| 52 |
-
cfg: KaldiDecoderConfig,
|
| 53 |
-
beam: int,
|
| 54 |
-
nbest: int = 1,
|
| 55 |
-
):
|
| 56 |
-
try:
|
| 57 |
-
from kaldi.asr import FasterRecognizer, LatticeFasterRecognizer
|
| 58 |
-
from kaldi.base import set_verbose_level
|
| 59 |
-
from kaldi.decoder import (
|
| 60 |
-
FasterDecoder,
|
| 61 |
-
FasterDecoderOptions,
|
| 62 |
-
LatticeFasterDecoder,
|
| 63 |
-
LatticeFasterDecoderOptions,
|
| 64 |
-
)
|
| 65 |
-
from kaldi.fstext import SymbolTable, read_fst_kaldi
|
| 66 |
-
from kaldi.lat.functions import DeterminizeLatticePhonePrunedOptions
|
| 67 |
-
except:
|
| 68 |
-
warnings.warn(
|
| 69 |
-
"pykaldi is required for this functionality. Please install from https://github.com/pykaldi/pykaldi"
|
| 70 |
-
)
|
| 71 |
-
|
| 72 |
-
# set_verbose_level(2)
|
| 73 |
-
|
| 74 |
-
self.acoustic_scale = cfg.acoustic_scale
|
| 75 |
-
self.nbest = nbest
|
| 76 |
-
|
| 77 |
-
if cfg.hlg_graph_path is None:
|
| 78 |
-
assert (
|
| 79 |
-
cfg.kaldi_initializer_config is not None
|
| 80 |
-
), "Must provide hlg graph path or kaldi initializer config"
|
| 81 |
-
cfg.hlg_graph_path = initalize_kaldi(cfg.kaldi_initializer_config)
|
| 82 |
-
|
| 83 |
-
assert os.path.exists(cfg.hlg_graph_path), cfg.hlg_graph_path
|
| 84 |
-
|
| 85 |
-
if cfg.is_lattice:
|
| 86 |
-
self.dec_cls = LatticeFasterDecoder
|
| 87 |
-
opt_cls = LatticeFasterDecoderOptions
|
| 88 |
-
self.rec_cls = LatticeFasterRecognizer
|
| 89 |
-
else:
|
| 90 |
-
assert self.nbest == 1, "nbest > 1 requires lattice decoder"
|
| 91 |
-
self.dec_cls = FasterDecoder
|
| 92 |
-
opt_cls = FasterDecoderOptions
|
| 93 |
-
self.rec_cls = FasterRecognizer
|
| 94 |
-
|
| 95 |
-
self.decoder_options = opt_cls()
|
| 96 |
-
self.decoder_options.beam = beam
|
| 97 |
-
self.decoder_options.max_active = cfg.max_active
|
| 98 |
-
self.decoder_options.beam_delta = cfg.beam_delta
|
| 99 |
-
self.decoder_options.hash_ratio = cfg.hash_ratio
|
| 100 |
-
|
| 101 |
-
if cfg.is_lattice:
|
| 102 |
-
self.decoder_options.lattice_beam = cfg.lattice_beam
|
| 103 |
-
self.decoder_options.prune_interval = cfg.prune_interval
|
| 104 |
-
self.decoder_options.determinize_lattice = cfg.determinize_lattice
|
| 105 |
-
self.decoder_options.prune_scale = cfg.prune_scale
|
| 106 |
-
det_opts = DeterminizeLatticePhonePrunedOptions()
|
| 107 |
-
det_opts.max_mem = cfg.max_mem
|
| 108 |
-
det_opts.phone_determinize = cfg.phone_determinize
|
| 109 |
-
det_opts.word_determinize = cfg.word_determinize
|
| 110 |
-
det_opts.minimize = cfg.minimize
|
| 111 |
-
self.decoder_options.det_opts = det_opts
|
| 112 |
-
|
| 113 |
-
self.output_symbols = {}
|
| 114 |
-
with open(cfg.output_dict, "r") as f:
|
| 115 |
-
for line in f:
|
| 116 |
-
items = line.rstrip().split()
|
| 117 |
-
assert len(items) == 2
|
| 118 |
-
self.output_symbols[int(items[1])] = items[0]
|
| 119 |
-
|
| 120 |
-
logger.info(f"Loading FST from {cfg.hlg_graph_path}")
|
| 121 |
-
self.fst = read_fst_kaldi(cfg.hlg_graph_path)
|
| 122 |
-
self.symbol_table = SymbolTable.read_text(cfg.output_dict)
|
| 123 |
-
|
| 124 |
-
self.executor = ThreadPoolExecutor(max_workers=cfg.num_threads)
|
| 125 |
-
|
| 126 |
-
def generate(self, models, sample, **unused):
|
| 127 |
-
"""Generate a batch of inferences."""
|
| 128 |
-
# model.forward normally channels prev_output_tokens into the decoder
|
| 129 |
-
# separately, but SequenceGenerator directly calls model.encoder
|
| 130 |
-
encoder_input = {
|
| 131 |
-
k: v for k, v in sample["net_input"].items() if k != "prev_output_tokens"
|
| 132 |
-
}
|
| 133 |
-
emissions, padding = self.get_emissions(models, encoder_input)
|
| 134 |
-
return self.decode(emissions, padding)
|
| 135 |
-
|
| 136 |
-
def get_emissions(self, models, encoder_input):
|
| 137 |
-
"""Run encoder and normalize emissions"""
|
| 138 |
-
model = models[0]
|
| 139 |
-
|
| 140 |
-
all_encoder_out = [m(**encoder_input) for m in models]
|
| 141 |
-
|
| 142 |
-
if len(all_encoder_out) > 1:
|
| 143 |
-
|
| 144 |
-
if "encoder_out" in all_encoder_out[0]:
|
| 145 |
-
encoder_out = {
|
| 146 |
-
"encoder_out": sum(e["encoder_out"] for e in all_encoder_out)
|
| 147 |
-
/ len(all_encoder_out),
|
| 148 |
-
"encoder_padding_mask": all_encoder_out[0]["encoder_padding_mask"],
|
| 149 |
-
}
|
| 150 |
-
padding = encoder_out["encoder_padding_mask"]
|
| 151 |
-
else:
|
| 152 |
-
encoder_out = {
|
| 153 |
-
"logits": sum(e["logits"] for e in all_encoder_out)
|
| 154 |
-
/ len(all_encoder_out),
|
| 155 |
-
"padding_mask": all_encoder_out[0]["padding_mask"],
|
| 156 |
-
}
|
| 157 |
-
padding = encoder_out["padding_mask"]
|
| 158 |
-
else:
|
| 159 |
-
encoder_out = all_encoder_out[0]
|
| 160 |
-
padding = (
|
| 161 |
-
encoder_out["padding_mask"]
|
| 162 |
-
if "padding_mask" in encoder_out
|
| 163 |
-
else encoder_out["encoder_padding_mask"]
|
| 164 |
-
)
|
| 165 |
-
|
| 166 |
-
if hasattr(model, "get_logits"):
|
| 167 |
-
emissions = model.get_logits(encoder_out, normalize=True)
|
| 168 |
-
else:
|
| 169 |
-
emissions = model.get_normalized_probs(encoder_out, log_probs=True)
|
| 170 |
-
|
| 171 |
-
return (
|
| 172 |
-
emissions.cpu().float().transpose(0, 1),
|
| 173 |
-
padding.cpu() if padding is not None and padding.any() else None,
|
| 174 |
-
)
|
| 175 |
-
|
| 176 |
-
def decode_one(self, logits, padding):
|
| 177 |
-
from kaldi.matrix import Matrix
|
| 178 |
-
|
| 179 |
-
decoder = self.dec_cls(self.fst, self.decoder_options)
|
| 180 |
-
asr = self.rec_cls(
|
| 181 |
-
decoder, self.symbol_table, acoustic_scale=self.acoustic_scale
|
| 182 |
-
)
|
| 183 |
-
|
| 184 |
-
if padding is not None:
|
| 185 |
-
logits = logits[~padding]
|
| 186 |
-
|
| 187 |
-
mat = Matrix(logits.numpy())
|
| 188 |
-
|
| 189 |
-
out = asr.decode(mat)
|
| 190 |
-
|
| 191 |
-
if self.nbest > 1:
|
| 192 |
-
from kaldi.fstext import shortestpath
|
| 193 |
-
from kaldi.fstext.utils import (
|
| 194 |
-
convert_compact_lattice_to_lattice,
|
| 195 |
-
convert_lattice_to_std,
|
| 196 |
-
convert_nbest_to_list,
|
| 197 |
-
get_linear_symbol_sequence,
|
| 198 |
-
)
|
| 199 |
-
|
| 200 |
-
lat = out["lattice"]
|
| 201 |
-
|
| 202 |
-
sp = shortestpath(lat, nshortest=self.nbest)
|
| 203 |
-
|
| 204 |
-
sp = convert_compact_lattice_to_lattice(sp)
|
| 205 |
-
sp = convert_lattice_to_std(sp)
|
| 206 |
-
seq = convert_nbest_to_list(sp)
|
| 207 |
-
|
| 208 |
-
results = []
|
| 209 |
-
for s in seq:
|
| 210 |
-
_, o, w = get_linear_symbol_sequence(s)
|
| 211 |
-
words = list(self.output_symbols[z] for z in o)
|
| 212 |
-
results.append(
|
| 213 |
-
{
|
| 214 |
-
"tokens": words,
|
| 215 |
-
"words": words,
|
| 216 |
-
"score": w.value,
|
| 217 |
-
"emissions": logits,
|
| 218 |
-
}
|
| 219 |
-
)
|
| 220 |
-
return results
|
| 221 |
-
else:
|
| 222 |
-
words = out["text"].split()
|
| 223 |
-
return [
|
| 224 |
-
{
|
| 225 |
-
"tokens": words,
|
| 226 |
-
"words": words,
|
| 227 |
-
"score": out["likelihood"],
|
| 228 |
-
"emissions": logits,
|
| 229 |
-
}
|
| 230 |
-
]
|
| 231 |
-
|
| 232 |
-
def decode(self, emissions, padding):
|
| 233 |
-
if padding is None:
|
| 234 |
-
padding = [None] * len(emissions)
|
| 235 |
-
|
| 236 |
-
ret = list(
|
| 237 |
-
map(
|
| 238 |
-
lambda e, p: self.executor.submit(self.decode_one, e, p),
|
| 239 |
-
emissions,
|
| 240 |
-
padding,
|
| 241 |
-
)
|
| 242 |
-
)
|
| 243 |
-
return ret
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/kaldi/kaldi_initializer.py
DELETED
|
@@ -1,698 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
import logging
|
| 9 |
-
import os
|
| 10 |
-
import os.path as osp
|
| 11 |
-
import subprocess
|
| 12 |
-
from dataclasses import dataclass
|
| 13 |
-
from pathlib import Path
|
| 14 |
-
from typing import Optional
|
| 15 |
-
|
| 16 |
-
import hydra
|
| 17 |
-
from fairseq.data.dictionary import Dictionary
|
| 18 |
-
from fairseq.dataclass import FairseqDataclass
|
| 19 |
-
from hydra.core.config_store import ConfigStore
|
| 20 |
-
from omegaconf import MISSING, OmegaConf
|
| 21 |
-
|
| 22 |
-
script_dir = Path(__file__).resolve().parent
|
| 23 |
-
config_path = script_dir / "config"
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
logger = logging.getLogger(__name__)
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
@dataclass
|
| 30 |
-
class KaldiInitializerConfig(FairseqDataclass):
|
| 31 |
-
data_dir: str = MISSING
|
| 32 |
-
fst_dir: Optional[str] = None
|
| 33 |
-
in_labels: str = MISSING
|
| 34 |
-
out_labels: Optional[str] = None
|
| 35 |
-
wav2letter_lexicon: Optional[str] = None
|
| 36 |
-
lm_arpa: str = MISSING
|
| 37 |
-
kaldi_root: str = MISSING
|
| 38 |
-
blank_symbol: str = "<s>"
|
| 39 |
-
silence_symbol: Optional[str] = None
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def create_units(fst_dir: Path, in_labels: str, vocab: Dictionary) -> Path:
|
| 43 |
-
in_units_file = fst_dir / f"kaldi_dict.{in_labels}.txt"
|
| 44 |
-
if not in_units_file.exists():
|
| 45 |
-
|
| 46 |
-
logger.info(f"Creating {in_units_file}")
|
| 47 |
-
|
| 48 |
-
with open(in_units_file, "w") as f:
|
| 49 |
-
print("<eps> 0", file=f)
|
| 50 |
-
i = 1
|
| 51 |
-
for symb in vocab.symbols[vocab.nspecial :]:
|
| 52 |
-
if not symb.startswith("madeupword"):
|
| 53 |
-
print(f"{symb} {i}", file=f)
|
| 54 |
-
i += 1
|
| 55 |
-
return in_units_file
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def create_lexicon(
|
| 59 |
-
cfg: KaldiInitializerConfig,
|
| 60 |
-
fst_dir: Path,
|
| 61 |
-
unique_label: str,
|
| 62 |
-
in_units_file: Path,
|
| 63 |
-
out_words_file: Path,
|
| 64 |
-
) -> (Path, Path):
|
| 65 |
-
|
| 66 |
-
disambig_in_units_file = fst_dir / f"kaldi_dict.{cfg.in_labels}_disambig.txt"
|
| 67 |
-
lexicon_file = fst_dir / f"kaldi_lexicon.{unique_label}.txt"
|
| 68 |
-
disambig_lexicon_file = fst_dir / f"kaldi_lexicon.{unique_label}_disambig.txt"
|
| 69 |
-
if (
|
| 70 |
-
not lexicon_file.exists()
|
| 71 |
-
or not disambig_lexicon_file.exists()
|
| 72 |
-
or not disambig_in_units_file.exists()
|
| 73 |
-
):
|
| 74 |
-
logger.info(f"Creating {lexicon_file} (in units file: {in_units_file})")
|
| 75 |
-
|
| 76 |
-
assert cfg.wav2letter_lexicon is not None or cfg.in_labels == cfg.out_labels
|
| 77 |
-
|
| 78 |
-
if cfg.wav2letter_lexicon is not None:
|
| 79 |
-
lm_words = set()
|
| 80 |
-
with open(out_words_file, "r") as lm_dict_f:
|
| 81 |
-
for line in lm_dict_f:
|
| 82 |
-
lm_words.add(line.split()[0])
|
| 83 |
-
|
| 84 |
-
num_skipped = 0
|
| 85 |
-
total = 0
|
| 86 |
-
with open(cfg.wav2letter_lexicon, "r") as w2l_lex_f, open(
|
| 87 |
-
lexicon_file, "w"
|
| 88 |
-
) as out_f:
|
| 89 |
-
for line in w2l_lex_f:
|
| 90 |
-
items = line.rstrip().split("\t")
|
| 91 |
-
assert len(items) == 2, items
|
| 92 |
-
if items[0] in lm_words:
|
| 93 |
-
print(items[0], items[1], file=out_f)
|
| 94 |
-
else:
|
| 95 |
-
num_skipped += 1
|
| 96 |
-
logger.debug(
|
| 97 |
-
f"Skipping word {items[0]} as it was not found in LM"
|
| 98 |
-
)
|
| 99 |
-
total += 1
|
| 100 |
-
if num_skipped > 0:
|
| 101 |
-
logger.warning(
|
| 102 |
-
f"Skipped {num_skipped} out of {total} words as they were not found in LM"
|
| 103 |
-
)
|
| 104 |
-
else:
|
| 105 |
-
with open(in_units_file, "r") as in_f, open(lexicon_file, "w") as out_f:
|
| 106 |
-
for line in in_f:
|
| 107 |
-
symb = line.split()[0]
|
| 108 |
-
if symb != "<eps>" and symb != "<ctc_blank>" and symb != "<SIL>":
|
| 109 |
-
print(symb, symb, file=out_f)
|
| 110 |
-
|
| 111 |
-
lex_disambig_path = (
|
| 112 |
-
Path(cfg.kaldi_root) / "egs/wsj/s5/utils/add_lex_disambig.pl"
|
| 113 |
-
)
|
| 114 |
-
res = subprocess.run(
|
| 115 |
-
[lex_disambig_path, lexicon_file, disambig_lexicon_file],
|
| 116 |
-
check=True,
|
| 117 |
-
capture_output=True,
|
| 118 |
-
)
|
| 119 |
-
ndisambig = int(res.stdout)
|
| 120 |
-
disamib_path = Path(cfg.kaldi_root) / "egs/wsj/s5/utils/add_disambig.pl"
|
| 121 |
-
res = subprocess.run(
|
| 122 |
-
[disamib_path, "--include-zero", in_units_file, str(ndisambig)],
|
| 123 |
-
check=True,
|
| 124 |
-
capture_output=True,
|
| 125 |
-
)
|
| 126 |
-
with open(disambig_in_units_file, "wb") as f:
|
| 127 |
-
f.write(res.stdout)
|
| 128 |
-
|
| 129 |
-
return disambig_lexicon_file, disambig_in_units_file
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
def create_G(
|
| 133 |
-
kaldi_root: Path, fst_dir: Path, lm_arpa: Path, arpa_base: str
|
| 134 |
-
) -> (Path, Path):
|
| 135 |
-
|
| 136 |
-
out_words_file = fst_dir / f"kaldi_dict.{arpa_base}.txt"
|
| 137 |
-
grammar_graph = fst_dir / f"G_{arpa_base}.fst"
|
| 138 |
-
if not grammar_graph.exists() or not out_words_file.exists():
|
| 139 |
-
logger.info(f"Creating {grammar_graph}")
|
| 140 |
-
arpa2fst = kaldi_root / "src/lmbin/arpa2fst"
|
| 141 |
-
subprocess.run(
|
| 142 |
-
[
|
| 143 |
-
arpa2fst,
|
| 144 |
-
"--disambig-symbol=#0",
|
| 145 |
-
f"--write-symbol-table={out_words_file}",
|
| 146 |
-
lm_arpa,
|
| 147 |
-
grammar_graph,
|
| 148 |
-
],
|
| 149 |
-
check=True,
|
| 150 |
-
)
|
| 151 |
-
return grammar_graph, out_words_file
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def create_L(
|
| 155 |
-
kaldi_root: Path,
|
| 156 |
-
fst_dir: Path,
|
| 157 |
-
unique_label: str,
|
| 158 |
-
lexicon_file: Path,
|
| 159 |
-
in_units_file: Path,
|
| 160 |
-
out_words_file: Path,
|
| 161 |
-
) -> Path:
|
| 162 |
-
lexicon_graph = fst_dir / f"L.{unique_label}.fst"
|
| 163 |
-
|
| 164 |
-
if not lexicon_graph.exists():
|
| 165 |
-
logger.info(f"Creating {lexicon_graph} (in units: {in_units_file})")
|
| 166 |
-
make_lex = kaldi_root / "egs/wsj/s5/utils/make_lexicon_fst.pl"
|
| 167 |
-
fstcompile = kaldi_root / "tools/openfst-1.6.7/bin/fstcompile"
|
| 168 |
-
fstaddselfloops = kaldi_root / "src/fstbin/fstaddselfloops"
|
| 169 |
-
fstarcsort = kaldi_root / "tools/openfst-1.6.7/bin/fstarcsort"
|
| 170 |
-
|
| 171 |
-
def write_disambig_symbol(file):
|
| 172 |
-
with open(file, "r") as f:
|
| 173 |
-
for line in f:
|
| 174 |
-
items = line.rstrip().split()
|
| 175 |
-
if items[0] == "#0":
|
| 176 |
-
out_path = str(file) + "_disamig"
|
| 177 |
-
with open(out_path, "w") as out_f:
|
| 178 |
-
print(items[1], file=out_f)
|
| 179 |
-
return out_path
|
| 180 |
-
|
| 181 |
-
return None
|
| 182 |
-
|
| 183 |
-
in_disambig_sym = write_disambig_symbol(in_units_file)
|
| 184 |
-
assert in_disambig_sym is not None
|
| 185 |
-
out_disambig_sym = write_disambig_symbol(out_words_file)
|
| 186 |
-
assert out_disambig_sym is not None
|
| 187 |
-
|
| 188 |
-
try:
|
| 189 |
-
with open(lexicon_graph, "wb") as out_f:
|
| 190 |
-
res = subprocess.run(
|
| 191 |
-
[make_lex, lexicon_file], capture_output=True, check=True
|
| 192 |
-
)
|
| 193 |
-
assert len(res.stderr) == 0, res.stderr.decode("utf-8")
|
| 194 |
-
res = subprocess.run(
|
| 195 |
-
[
|
| 196 |
-
fstcompile,
|
| 197 |
-
f"--isymbols={in_units_file}",
|
| 198 |
-
f"--osymbols={out_words_file}",
|
| 199 |
-
"--keep_isymbols=false",
|
| 200 |
-
"--keep_osymbols=false",
|
| 201 |
-
],
|
| 202 |
-
input=res.stdout,
|
| 203 |
-
capture_output=True,
|
| 204 |
-
)
|
| 205 |
-
assert len(res.stderr) == 0, res.stderr.decode("utf-8")
|
| 206 |
-
res = subprocess.run(
|
| 207 |
-
[fstaddselfloops, in_disambig_sym, out_disambig_sym],
|
| 208 |
-
input=res.stdout,
|
| 209 |
-
capture_output=True,
|
| 210 |
-
check=True,
|
| 211 |
-
)
|
| 212 |
-
res = subprocess.run(
|
| 213 |
-
[fstarcsort, "--sort_type=olabel"],
|
| 214 |
-
input=res.stdout,
|
| 215 |
-
capture_output=True,
|
| 216 |
-
check=True,
|
| 217 |
-
)
|
| 218 |
-
out_f.write(res.stdout)
|
| 219 |
-
except subprocess.CalledProcessError as e:
|
| 220 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 221 |
-
os.remove(lexicon_graph)
|
| 222 |
-
raise
|
| 223 |
-
except AssertionError:
|
| 224 |
-
os.remove(lexicon_graph)
|
| 225 |
-
raise
|
| 226 |
-
|
| 227 |
-
return lexicon_graph
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
def create_LG(
|
| 231 |
-
kaldi_root: Path,
|
| 232 |
-
fst_dir: Path,
|
| 233 |
-
unique_label: str,
|
| 234 |
-
lexicon_graph: Path,
|
| 235 |
-
grammar_graph: Path,
|
| 236 |
-
) -> Path:
|
| 237 |
-
lg_graph = fst_dir / f"LG.{unique_label}.fst"
|
| 238 |
-
|
| 239 |
-
if not lg_graph.exists():
|
| 240 |
-
logger.info(f"Creating {lg_graph}")
|
| 241 |
-
|
| 242 |
-
fsttablecompose = kaldi_root / "src/fstbin/fsttablecompose"
|
| 243 |
-
fstdeterminizestar = kaldi_root / "src/fstbin/fstdeterminizestar"
|
| 244 |
-
fstminimizeencoded = kaldi_root / "src/fstbin/fstminimizeencoded"
|
| 245 |
-
fstpushspecial = kaldi_root / "src/fstbin/fstpushspecial"
|
| 246 |
-
fstarcsort = kaldi_root / "tools/openfst-1.6.7/bin/fstarcsort"
|
| 247 |
-
|
| 248 |
-
try:
|
| 249 |
-
with open(lg_graph, "wb") as out_f:
|
| 250 |
-
res = subprocess.run(
|
| 251 |
-
[fsttablecompose, lexicon_graph, grammar_graph],
|
| 252 |
-
capture_output=True,
|
| 253 |
-
check=True,
|
| 254 |
-
)
|
| 255 |
-
res = subprocess.run(
|
| 256 |
-
[
|
| 257 |
-
fstdeterminizestar,
|
| 258 |
-
"--use-log=true",
|
| 259 |
-
],
|
| 260 |
-
input=res.stdout,
|
| 261 |
-
capture_output=True,
|
| 262 |
-
)
|
| 263 |
-
res = subprocess.run(
|
| 264 |
-
[fstminimizeencoded],
|
| 265 |
-
input=res.stdout,
|
| 266 |
-
capture_output=True,
|
| 267 |
-
check=True,
|
| 268 |
-
)
|
| 269 |
-
res = subprocess.run(
|
| 270 |
-
[fstpushspecial],
|
| 271 |
-
input=res.stdout,
|
| 272 |
-
capture_output=True,
|
| 273 |
-
check=True,
|
| 274 |
-
)
|
| 275 |
-
res = subprocess.run(
|
| 276 |
-
[fstarcsort, "--sort_type=ilabel"],
|
| 277 |
-
input=res.stdout,
|
| 278 |
-
capture_output=True,
|
| 279 |
-
check=True,
|
| 280 |
-
)
|
| 281 |
-
out_f.write(res.stdout)
|
| 282 |
-
except subprocess.CalledProcessError as e:
|
| 283 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 284 |
-
os.remove(lg_graph)
|
| 285 |
-
raise
|
| 286 |
-
|
| 287 |
-
return lg_graph
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
def create_H(
|
| 291 |
-
kaldi_root: Path,
|
| 292 |
-
fst_dir: Path,
|
| 293 |
-
disambig_out_units_file: Path,
|
| 294 |
-
in_labels: str,
|
| 295 |
-
vocab: Dictionary,
|
| 296 |
-
blk_sym: str,
|
| 297 |
-
silence_symbol: Optional[str],
|
| 298 |
-
) -> (Path, Path, Path):
|
| 299 |
-
h_graph = (
|
| 300 |
-
fst_dir / f"H.{in_labels}{'_' + silence_symbol if silence_symbol else ''}.fst"
|
| 301 |
-
)
|
| 302 |
-
h_out_units_file = fst_dir / f"kaldi_dict.h_out.{in_labels}.txt"
|
| 303 |
-
disambig_in_units_file_int = Path(str(h_graph) + "isym_disambig.int")
|
| 304 |
-
disambig_out_units_file_int = Path(str(disambig_out_units_file) + ".int")
|
| 305 |
-
if (
|
| 306 |
-
not h_graph.exists()
|
| 307 |
-
or not h_out_units_file.exists()
|
| 308 |
-
or not disambig_in_units_file_int.exists()
|
| 309 |
-
):
|
| 310 |
-
logger.info(f"Creating {h_graph}")
|
| 311 |
-
eps_sym = "<eps>"
|
| 312 |
-
|
| 313 |
-
num_disambig = 0
|
| 314 |
-
osymbols = []
|
| 315 |
-
|
| 316 |
-
with open(disambig_out_units_file, "r") as f, open(
|
| 317 |
-
disambig_out_units_file_int, "w"
|
| 318 |
-
) as out_f:
|
| 319 |
-
for line in f:
|
| 320 |
-
symb, id = line.rstrip().split()
|
| 321 |
-
if line.startswith("#"):
|
| 322 |
-
num_disambig += 1
|
| 323 |
-
print(id, file=out_f)
|
| 324 |
-
else:
|
| 325 |
-
if len(osymbols) == 0:
|
| 326 |
-
assert symb == eps_sym, symb
|
| 327 |
-
osymbols.append((symb, id))
|
| 328 |
-
|
| 329 |
-
i_idx = 0
|
| 330 |
-
isymbols = [(eps_sym, 0)]
|
| 331 |
-
|
| 332 |
-
imap = {}
|
| 333 |
-
|
| 334 |
-
for i, s in enumerate(vocab.symbols):
|
| 335 |
-
i_idx += 1
|
| 336 |
-
isymbols.append((s, i_idx))
|
| 337 |
-
imap[s] = i_idx
|
| 338 |
-
|
| 339 |
-
fst_str = []
|
| 340 |
-
|
| 341 |
-
node_idx = 0
|
| 342 |
-
root_node = node_idx
|
| 343 |
-
|
| 344 |
-
special_symbols = [blk_sym]
|
| 345 |
-
if silence_symbol is not None:
|
| 346 |
-
special_symbols.append(silence_symbol)
|
| 347 |
-
|
| 348 |
-
for ss in special_symbols:
|
| 349 |
-
fst_str.append("{} {} {} {}".format(root_node, root_node, ss, eps_sym))
|
| 350 |
-
|
| 351 |
-
for symbol, _ in osymbols:
|
| 352 |
-
if symbol == eps_sym or symbol.startswith("#"):
|
| 353 |
-
continue
|
| 354 |
-
|
| 355 |
-
node_idx += 1
|
| 356 |
-
# 1. from root to emitting state
|
| 357 |
-
fst_str.append("{} {} {} {}".format(root_node, node_idx, symbol, symbol))
|
| 358 |
-
# 2. from emitting state back to root
|
| 359 |
-
fst_str.append("{} {} {} {}".format(node_idx, root_node, eps_sym, eps_sym))
|
| 360 |
-
# 3. from emitting state to optional blank state
|
| 361 |
-
pre_node = node_idx
|
| 362 |
-
node_idx += 1
|
| 363 |
-
for ss in special_symbols:
|
| 364 |
-
fst_str.append("{} {} {} {}".format(pre_node, node_idx, ss, eps_sym))
|
| 365 |
-
# 4. from blank state back to root
|
| 366 |
-
fst_str.append("{} {} {} {}".format(node_idx, root_node, eps_sym, eps_sym))
|
| 367 |
-
|
| 368 |
-
fst_str.append("{}".format(root_node))
|
| 369 |
-
|
| 370 |
-
fst_str = "\n".join(fst_str)
|
| 371 |
-
h_str = str(h_graph)
|
| 372 |
-
isym_file = h_str + ".isym"
|
| 373 |
-
|
| 374 |
-
with open(isym_file, "w") as f:
|
| 375 |
-
for sym, id in isymbols:
|
| 376 |
-
f.write("{} {}\n".format(sym, id))
|
| 377 |
-
|
| 378 |
-
with open(h_out_units_file, "w") as f:
|
| 379 |
-
for sym, id in osymbols:
|
| 380 |
-
f.write("{} {}\n".format(sym, id))
|
| 381 |
-
|
| 382 |
-
with open(disambig_in_units_file_int, "w") as f:
|
| 383 |
-
disam_sym_id = len(isymbols)
|
| 384 |
-
for _ in range(num_disambig):
|
| 385 |
-
f.write("{}\n".format(disam_sym_id))
|
| 386 |
-
disam_sym_id += 1
|
| 387 |
-
|
| 388 |
-
fstcompile = kaldi_root / "tools/openfst-1.6.7/bin/fstcompile"
|
| 389 |
-
fstaddselfloops = kaldi_root / "src/fstbin/fstaddselfloops"
|
| 390 |
-
fstarcsort = kaldi_root / "tools/openfst-1.6.7/bin/fstarcsort"
|
| 391 |
-
|
| 392 |
-
try:
|
| 393 |
-
with open(h_graph, "wb") as out_f:
|
| 394 |
-
res = subprocess.run(
|
| 395 |
-
[
|
| 396 |
-
fstcompile,
|
| 397 |
-
f"--isymbols={isym_file}",
|
| 398 |
-
f"--osymbols={h_out_units_file}",
|
| 399 |
-
"--keep_isymbols=false",
|
| 400 |
-
"--keep_osymbols=false",
|
| 401 |
-
],
|
| 402 |
-
input=str.encode(fst_str),
|
| 403 |
-
capture_output=True,
|
| 404 |
-
check=True,
|
| 405 |
-
)
|
| 406 |
-
res = subprocess.run(
|
| 407 |
-
[
|
| 408 |
-
fstaddselfloops,
|
| 409 |
-
disambig_in_units_file_int,
|
| 410 |
-
disambig_out_units_file_int,
|
| 411 |
-
],
|
| 412 |
-
input=res.stdout,
|
| 413 |
-
capture_output=True,
|
| 414 |
-
check=True,
|
| 415 |
-
)
|
| 416 |
-
res = subprocess.run(
|
| 417 |
-
[fstarcsort, "--sort_type=olabel"],
|
| 418 |
-
input=res.stdout,
|
| 419 |
-
capture_output=True,
|
| 420 |
-
check=True,
|
| 421 |
-
)
|
| 422 |
-
out_f.write(res.stdout)
|
| 423 |
-
except subprocess.CalledProcessError as e:
|
| 424 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 425 |
-
os.remove(h_graph)
|
| 426 |
-
raise
|
| 427 |
-
return h_graph, h_out_units_file, disambig_in_units_file_int
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
def create_HLGa(
|
| 431 |
-
kaldi_root: Path,
|
| 432 |
-
fst_dir: Path,
|
| 433 |
-
unique_label: str,
|
| 434 |
-
h_graph: Path,
|
| 435 |
-
lg_graph: Path,
|
| 436 |
-
disambig_in_words_file_int: Path,
|
| 437 |
-
) -> Path:
|
| 438 |
-
hlga_graph = fst_dir / f"HLGa.{unique_label}.fst"
|
| 439 |
-
|
| 440 |
-
if not hlga_graph.exists():
|
| 441 |
-
logger.info(f"Creating {hlga_graph}")
|
| 442 |
-
|
| 443 |
-
fsttablecompose = kaldi_root / "src/fstbin/fsttablecompose"
|
| 444 |
-
fstdeterminizestar = kaldi_root / "src/fstbin/fstdeterminizestar"
|
| 445 |
-
fstrmsymbols = kaldi_root / "src/fstbin/fstrmsymbols"
|
| 446 |
-
fstrmepslocal = kaldi_root / "src/fstbin/fstrmepslocal"
|
| 447 |
-
fstminimizeencoded = kaldi_root / "src/fstbin/fstminimizeencoded"
|
| 448 |
-
|
| 449 |
-
try:
|
| 450 |
-
with open(hlga_graph, "wb") as out_f:
|
| 451 |
-
res = subprocess.run(
|
| 452 |
-
[
|
| 453 |
-
fsttablecompose,
|
| 454 |
-
h_graph,
|
| 455 |
-
lg_graph,
|
| 456 |
-
],
|
| 457 |
-
capture_output=True,
|
| 458 |
-
check=True,
|
| 459 |
-
)
|
| 460 |
-
res = subprocess.run(
|
| 461 |
-
[fstdeterminizestar, "--use-log=true"],
|
| 462 |
-
input=res.stdout,
|
| 463 |
-
capture_output=True,
|
| 464 |
-
check=True,
|
| 465 |
-
)
|
| 466 |
-
res = subprocess.run(
|
| 467 |
-
[fstrmsymbols, disambig_in_words_file_int],
|
| 468 |
-
input=res.stdout,
|
| 469 |
-
capture_output=True,
|
| 470 |
-
check=True,
|
| 471 |
-
)
|
| 472 |
-
res = subprocess.run(
|
| 473 |
-
[fstrmepslocal],
|
| 474 |
-
input=res.stdout,
|
| 475 |
-
capture_output=True,
|
| 476 |
-
check=True,
|
| 477 |
-
)
|
| 478 |
-
res = subprocess.run(
|
| 479 |
-
[fstminimizeencoded],
|
| 480 |
-
input=res.stdout,
|
| 481 |
-
capture_output=True,
|
| 482 |
-
check=True,
|
| 483 |
-
)
|
| 484 |
-
out_f.write(res.stdout)
|
| 485 |
-
except subprocess.CalledProcessError as e:
|
| 486 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 487 |
-
os.remove(hlga_graph)
|
| 488 |
-
raise
|
| 489 |
-
|
| 490 |
-
return hlga_graph
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
def create_HLa(
|
| 494 |
-
kaldi_root: Path,
|
| 495 |
-
fst_dir: Path,
|
| 496 |
-
unique_label: str,
|
| 497 |
-
h_graph: Path,
|
| 498 |
-
l_graph: Path,
|
| 499 |
-
disambig_in_words_file_int: Path,
|
| 500 |
-
) -> Path:
|
| 501 |
-
hla_graph = fst_dir / f"HLa.{unique_label}.fst"
|
| 502 |
-
|
| 503 |
-
if not hla_graph.exists():
|
| 504 |
-
logger.info(f"Creating {hla_graph}")
|
| 505 |
-
|
| 506 |
-
fsttablecompose = kaldi_root / "src/fstbin/fsttablecompose"
|
| 507 |
-
fstdeterminizestar = kaldi_root / "src/fstbin/fstdeterminizestar"
|
| 508 |
-
fstrmsymbols = kaldi_root / "src/fstbin/fstrmsymbols"
|
| 509 |
-
fstrmepslocal = kaldi_root / "src/fstbin/fstrmepslocal"
|
| 510 |
-
fstminimizeencoded = kaldi_root / "src/fstbin/fstminimizeencoded"
|
| 511 |
-
|
| 512 |
-
try:
|
| 513 |
-
with open(hla_graph, "wb") as out_f:
|
| 514 |
-
res = subprocess.run(
|
| 515 |
-
[
|
| 516 |
-
fsttablecompose,
|
| 517 |
-
h_graph,
|
| 518 |
-
l_graph,
|
| 519 |
-
],
|
| 520 |
-
capture_output=True,
|
| 521 |
-
check=True,
|
| 522 |
-
)
|
| 523 |
-
res = subprocess.run(
|
| 524 |
-
[fstdeterminizestar, "--use-log=true"],
|
| 525 |
-
input=res.stdout,
|
| 526 |
-
capture_output=True,
|
| 527 |
-
check=True,
|
| 528 |
-
)
|
| 529 |
-
res = subprocess.run(
|
| 530 |
-
[fstrmsymbols, disambig_in_words_file_int],
|
| 531 |
-
input=res.stdout,
|
| 532 |
-
capture_output=True,
|
| 533 |
-
check=True,
|
| 534 |
-
)
|
| 535 |
-
res = subprocess.run(
|
| 536 |
-
[fstrmepslocal],
|
| 537 |
-
input=res.stdout,
|
| 538 |
-
capture_output=True,
|
| 539 |
-
check=True,
|
| 540 |
-
)
|
| 541 |
-
res = subprocess.run(
|
| 542 |
-
[fstminimizeencoded],
|
| 543 |
-
input=res.stdout,
|
| 544 |
-
capture_output=True,
|
| 545 |
-
check=True,
|
| 546 |
-
)
|
| 547 |
-
out_f.write(res.stdout)
|
| 548 |
-
except subprocess.CalledProcessError as e:
|
| 549 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 550 |
-
os.remove(hla_graph)
|
| 551 |
-
raise
|
| 552 |
-
|
| 553 |
-
return hla_graph
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
def create_HLG(
|
| 557 |
-
kaldi_root: Path,
|
| 558 |
-
fst_dir: Path,
|
| 559 |
-
unique_label: str,
|
| 560 |
-
hlga_graph: Path,
|
| 561 |
-
prefix: str = "HLG",
|
| 562 |
-
) -> Path:
|
| 563 |
-
hlg_graph = fst_dir / f"{prefix}.{unique_label}.fst"
|
| 564 |
-
|
| 565 |
-
if not hlg_graph.exists():
|
| 566 |
-
logger.info(f"Creating {hlg_graph}")
|
| 567 |
-
|
| 568 |
-
add_self_loop = script_dir / "add-self-loop-simple"
|
| 569 |
-
kaldi_src = kaldi_root / "src"
|
| 570 |
-
kaldi_lib = kaldi_src / "lib"
|
| 571 |
-
|
| 572 |
-
try:
|
| 573 |
-
if not add_self_loop.exists():
|
| 574 |
-
fst_include = kaldi_root / "tools/openfst-1.6.7/include"
|
| 575 |
-
add_self_loop_src = script_dir / "add-self-loop-simple.cc"
|
| 576 |
-
|
| 577 |
-
subprocess.run(
|
| 578 |
-
[
|
| 579 |
-
"c++",
|
| 580 |
-
f"-I{kaldi_src}",
|
| 581 |
-
f"-I{fst_include}",
|
| 582 |
-
f"-L{kaldi_lib}",
|
| 583 |
-
add_self_loop_src,
|
| 584 |
-
"-lkaldi-base",
|
| 585 |
-
"-lkaldi-fstext",
|
| 586 |
-
"-o",
|
| 587 |
-
add_self_loop,
|
| 588 |
-
],
|
| 589 |
-
check=True,
|
| 590 |
-
)
|
| 591 |
-
|
| 592 |
-
my_env = os.environ.copy()
|
| 593 |
-
my_env["LD_LIBRARY_PATH"] = f"{kaldi_lib}:{my_env['LD_LIBRARY_PATH']}"
|
| 594 |
-
|
| 595 |
-
subprocess.run(
|
| 596 |
-
[
|
| 597 |
-
add_self_loop,
|
| 598 |
-
hlga_graph,
|
| 599 |
-
hlg_graph,
|
| 600 |
-
],
|
| 601 |
-
check=True,
|
| 602 |
-
capture_output=True,
|
| 603 |
-
env=my_env,
|
| 604 |
-
)
|
| 605 |
-
except subprocess.CalledProcessError as e:
|
| 606 |
-
logger.error(f"cmd: {e.cmd}, err: {e.stderr.decode('utf-8')}")
|
| 607 |
-
raise
|
| 608 |
-
|
| 609 |
-
return hlg_graph
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
def initalize_kaldi(cfg: KaldiInitializerConfig) -> Path:
|
| 613 |
-
if cfg.fst_dir is None:
|
| 614 |
-
cfg.fst_dir = osp.join(cfg.data_dir, "kaldi")
|
| 615 |
-
if cfg.out_labels is None:
|
| 616 |
-
cfg.out_labels = cfg.in_labels
|
| 617 |
-
|
| 618 |
-
kaldi_root = Path(cfg.kaldi_root)
|
| 619 |
-
data_dir = Path(cfg.data_dir)
|
| 620 |
-
fst_dir = Path(cfg.fst_dir)
|
| 621 |
-
fst_dir.mkdir(parents=True, exist_ok=True)
|
| 622 |
-
|
| 623 |
-
arpa_base = osp.splitext(osp.basename(cfg.lm_arpa))[0]
|
| 624 |
-
unique_label = f"{cfg.in_labels}.{arpa_base}"
|
| 625 |
-
|
| 626 |
-
with open(data_dir / f"dict.{cfg.in_labels}.txt", "r") as f:
|
| 627 |
-
vocab = Dictionary.load(f)
|
| 628 |
-
|
| 629 |
-
in_units_file = create_units(fst_dir, cfg.in_labels, vocab)
|
| 630 |
-
|
| 631 |
-
grammar_graph, out_words_file = create_G(
|
| 632 |
-
kaldi_root, fst_dir, Path(cfg.lm_arpa), arpa_base
|
| 633 |
-
)
|
| 634 |
-
|
| 635 |
-
disambig_lexicon_file, disambig_L_in_units_file = create_lexicon(
|
| 636 |
-
cfg, fst_dir, unique_label, in_units_file, out_words_file
|
| 637 |
-
)
|
| 638 |
-
|
| 639 |
-
h_graph, h_out_units_file, disambig_in_units_file_int = create_H(
|
| 640 |
-
kaldi_root,
|
| 641 |
-
fst_dir,
|
| 642 |
-
disambig_L_in_units_file,
|
| 643 |
-
cfg.in_labels,
|
| 644 |
-
vocab,
|
| 645 |
-
cfg.blank_symbol,
|
| 646 |
-
cfg.silence_symbol,
|
| 647 |
-
)
|
| 648 |
-
lexicon_graph = create_L(
|
| 649 |
-
kaldi_root,
|
| 650 |
-
fst_dir,
|
| 651 |
-
unique_label,
|
| 652 |
-
disambig_lexicon_file,
|
| 653 |
-
disambig_L_in_units_file,
|
| 654 |
-
out_words_file,
|
| 655 |
-
)
|
| 656 |
-
lg_graph = create_LG(
|
| 657 |
-
kaldi_root, fst_dir, unique_label, lexicon_graph, grammar_graph
|
| 658 |
-
)
|
| 659 |
-
hlga_graph = create_HLGa(
|
| 660 |
-
kaldi_root, fst_dir, unique_label, h_graph, lg_graph, disambig_in_units_file_int
|
| 661 |
-
)
|
| 662 |
-
hlg_graph = create_HLG(kaldi_root, fst_dir, unique_label, hlga_graph)
|
| 663 |
-
|
| 664 |
-
# for debugging
|
| 665 |
-
# hla_graph = create_HLa(kaldi_root, fst_dir, unique_label, h_graph, lexicon_graph, disambig_in_units_file_int)
|
| 666 |
-
# hl_graph = create_HLG(kaldi_root, fst_dir, unique_label, hla_graph, prefix="HL_looped")
|
| 667 |
-
# create_HLG(kaldi_root, fst_dir, "phnc", h_graph, prefix="H_looped")
|
| 668 |
-
|
| 669 |
-
return hlg_graph
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
@hydra.main(config_path=config_path, config_name="kaldi_initializer")
|
| 673 |
-
def cli_main(cfg: KaldiInitializerConfig) -> None:
|
| 674 |
-
container = OmegaConf.to_container(cfg, resolve=True, enum_to_str=True)
|
| 675 |
-
cfg = OmegaConf.create(container)
|
| 676 |
-
OmegaConf.set_struct(cfg, True)
|
| 677 |
-
initalize_kaldi(cfg)
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
if __name__ == "__main__":
|
| 681 |
-
|
| 682 |
-
logging.root.setLevel(logging.INFO)
|
| 683 |
-
logging.basicConfig(level=logging.INFO)
|
| 684 |
-
|
| 685 |
-
try:
|
| 686 |
-
from hydra._internal.utils import ( # pylint: disable=import-outside-toplevel
|
| 687 |
-
get_args,
|
| 688 |
-
)
|
| 689 |
-
|
| 690 |
-
cfg_name = get_args().config_name or "kaldi_initializer"
|
| 691 |
-
except ImportError:
|
| 692 |
-
logger.warning("Failed to get config name from hydra args")
|
| 693 |
-
cfg_name = "kaldi_initializer"
|
| 694 |
-
|
| 695 |
-
cs = ConfigStore.instance()
|
| 696 |
-
cs.store(name=cfg_name, node=KaldiInitializerConfig)
|
| 697 |
-
|
| 698 |
-
cli_main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/__init__.py
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
import importlib
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 5 |
-
if file.endswith(".py") and not file.startswith("_"):
|
| 6 |
-
model_name = file[: file.find(".py")]
|
| 7 |
-
importlib.import_module("examples.speech_recognition.models." + model_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/vggtransformer.py
DELETED
|
@@ -1,1020 +0,0 @@
|
|
| 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 argparse
|
| 7 |
-
import math
|
| 8 |
-
from collections.abc import Iterable
|
| 9 |
-
|
| 10 |
-
import torch
|
| 11 |
-
import torch.nn as nn
|
| 12 |
-
from examples.speech_recognition.data.data_utils import lengths_to_encoder_padding_mask
|
| 13 |
-
from fairseq import utils
|
| 14 |
-
from fairseq.models import (
|
| 15 |
-
FairseqEncoder,
|
| 16 |
-
FairseqEncoderDecoderModel,
|
| 17 |
-
FairseqEncoderModel,
|
| 18 |
-
FairseqIncrementalDecoder,
|
| 19 |
-
register_model,
|
| 20 |
-
register_model_architecture,
|
| 21 |
-
)
|
| 22 |
-
from fairseq.modules import (
|
| 23 |
-
LinearizedConvolution,
|
| 24 |
-
TransformerDecoderLayer,
|
| 25 |
-
TransformerEncoderLayer,
|
| 26 |
-
VGGBlock,
|
| 27 |
-
)
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
@register_model("asr_vggtransformer")
|
| 31 |
-
class VGGTransformerModel(FairseqEncoderDecoderModel):
|
| 32 |
-
"""
|
| 33 |
-
Transformers with convolutional context for ASR
|
| 34 |
-
https://arxiv.org/abs/1904.11660
|
| 35 |
-
"""
|
| 36 |
-
|
| 37 |
-
def __init__(self, encoder, decoder):
|
| 38 |
-
super().__init__(encoder, decoder)
|
| 39 |
-
|
| 40 |
-
@staticmethod
|
| 41 |
-
def add_args(parser):
|
| 42 |
-
"""Add model-specific arguments to the parser."""
|
| 43 |
-
parser.add_argument(
|
| 44 |
-
"--input-feat-per-channel",
|
| 45 |
-
type=int,
|
| 46 |
-
metavar="N",
|
| 47 |
-
help="encoder input dimension per input channel",
|
| 48 |
-
)
|
| 49 |
-
parser.add_argument(
|
| 50 |
-
"--vggblock-enc-config",
|
| 51 |
-
type=str,
|
| 52 |
-
metavar="EXPR",
|
| 53 |
-
help="""
|
| 54 |
-
an array of tuples each containing the configuration of one vggblock:
|
| 55 |
-
[(out_channels,
|
| 56 |
-
conv_kernel_size,
|
| 57 |
-
pooling_kernel_size,
|
| 58 |
-
num_conv_layers,
|
| 59 |
-
use_layer_norm), ...])
|
| 60 |
-
""",
|
| 61 |
-
)
|
| 62 |
-
parser.add_argument(
|
| 63 |
-
"--transformer-enc-config",
|
| 64 |
-
type=str,
|
| 65 |
-
metavar="EXPR",
|
| 66 |
-
help=""""
|
| 67 |
-
a tuple containing the configuration of the encoder transformer layers
|
| 68 |
-
configurations:
|
| 69 |
-
[(input_dim,
|
| 70 |
-
num_heads,
|
| 71 |
-
ffn_dim,
|
| 72 |
-
normalize_before,
|
| 73 |
-
dropout,
|
| 74 |
-
attention_dropout,
|
| 75 |
-
relu_dropout), ...]')
|
| 76 |
-
""",
|
| 77 |
-
)
|
| 78 |
-
parser.add_argument(
|
| 79 |
-
"--enc-output-dim",
|
| 80 |
-
type=int,
|
| 81 |
-
metavar="N",
|
| 82 |
-
help="""
|
| 83 |
-
encoder output dimension, can be None. If specified, projecting the
|
| 84 |
-
transformer output to the specified dimension""",
|
| 85 |
-
)
|
| 86 |
-
parser.add_argument(
|
| 87 |
-
"--in-channels",
|
| 88 |
-
type=int,
|
| 89 |
-
metavar="N",
|
| 90 |
-
help="number of encoder input channels",
|
| 91 |
-
)
|
| 92 |
-
parser.add_argument(
|
| 93 |
-
"--tgt-embed-dim",
|
| 94 |
-
type=int,
|
| 95 |
-
metavar="N",
|
| 96 |
-
help="embedding dimension of the decoder target tokens",
|
| 97 |
-
)
|
| 98 |
-
parser.add_argument(
|
| 99 |
-
"--transformer-dec-config",
|
| 100 |
-
type=str,
|
| 101 |
-
metavar="EXPR",
|
| 102 |
-
help="""
|
| 103 |
-
a tuple containing the configuration of the decoder transformer layers
|
| 104 |
-
configurations:
|
| 105 |
-
[(input_dim,
|
| 106 |
-
num_heads,
|
| 107 |
-
ffn_dim,
|
| 108 |
-
normalize_before,
|
| 109 |
-
dropout,
|
| 110 |
-
attention_dropout,
|
| 111 |
-
relu_dropout), ...]
|
| 112 |
-
""",
|
| 113 |
-
)
|
| 114 |
-
parser.add_argument(
|
| 115 |
-
"--conv-dec-config",
|
| 116 |
-
type=str,
|
| 117 |
-
metavar="EXPR",
|
| 118 |
-
help="""
|
| 119 |
-
an array of tuples for the decoder 1-D convolution config
|
| 120 |
-
[(out_channels, conv_kernel_size, use_layer_norm), ...]""",
|
| 121 |
-
)
|
| 122 |
-
|
| 123 |
-
@classmethod
|
| 124 |
-
def build_encoder(cls, args, task):
|
| 125 |
-
return VGGTransformerEncoder(
|
| 126 |
-
input_feat_per_channel=args.input_feat_per_channel,
|
| 127 |
-
vggblock_config=eval(args.vggblock_enc_config),
|
| 128 |
-
transformer_config=eval(args.transformer_enc_config),
|
| 129 |
-
encoder_output_dim=args.enc_output_dim,
|
| 130 |
-
in_channels=args.in_channels,
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
-
@classmethod
|
| 134 |
-
def build_decoder(cls, args, task):
|
| 135 |
-
return TransformerDecoder(
|
| 136 |
-
dictionary=task.target_dictionary,
|
| 137 |
-
embed_dim=args.tgt_embed_dim,
|
| 138 |
-
transformer_config=eval(args.transformer_dec_config),
|
| 139 |
-
conv_config=eval(args.conv_dec_config),
|
| 140 |
-
encoder_output_dim=args.enc_output_dim,
|
| 141 |
-
)
|
| 142 |
-
|
| 143 |
-
@classmethod
|
| 144 |
-
def build_model(cls, args, task):
|
| 145 |
-
"""Build a new model instance."""
|
| 146 |
-
# make sure that all args are properly defaulted
|
| 147 |
-
# (in case there are any new ones)
|
| 148 |
-
base_architecture(args)
|
| 149 |
-
|
| 150 |
-
encoder = cls.build_encoder(args, task)
|
| 151 |
-
decoder = cls.build_decoder(args, task)
|
| 152 |
-
return cls(encoder, decoder)
|
| 153 |
-
|
| 154 |
-
def get_normalized_probs(self, net_output, log_probs, sample=None):
|
| 155 |
-
# net_output['encoder_out'] is a (B, T, D) tensor
|
| 156 |
-
lprobs = super().get_normalized_probs(net_output, log_probs, sample)
|
| 157 |
-
lprobs.batch_first = True
|
| 158 |
-
return lprobs
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
DEFAULT_ENC_VGGBLOCK_CONFIG = ((32, 3, 2, 2, False),) * 2
|
| 162 |
-
DEFAULT_ENC_TRANSFORMER_CONFIG = ((256, 4, 1024, True, 0.2, 0.2, 0.2),) * 2
|
| 163 |
-
# 256: embedding dimension
|
| 164 |
-
# 4: number of heads
|
| 165 |
-
# 1024: FFN
|
| 166 |
-
# True: apply layerNorm before (dropout + resiaul) instead of after
|
| 167 |
-
# 0.2 (dropout): dropout after MultiheadAttention and second FC
|
| 168 |
-
# 0.2 (attention_dropout): dropout in MultiheadAttention
|
| 169 |
-
# 0.2 (relu_dropout): dropout after ReLu
|
| 170 |
-
DEFAULT_DEC_TRANSFORMER_CONFIG = ((256, 2, 1024, True, 0.2, 0.2, 0.2),) * 2
|
| 171 |
-
DEFAULT_DEC_CONV_CONFIG = ((256, 3, True),) * 2
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
# TODO: repace transformer encoder config from one liner
|
| 175 |
-
# to explicit args to get rid of this transformation
|
| 176 |
-
def prepare_transformer_encoder_params(
|
| 177 |
-
input_dim,
|
| 178 |
-
num_heads,
|
| 179 |
-
ffn_dim,
|
| 180 |
-
normalize_before,
|
| 181 |
-
dropout,
|
| 182 |
-
attention_dropout,
|
| 183 |
-
relu_dropout,
|
| 184 |
-
):
|
| 185 |
-
args = argparse.Namespace()
|
| 186 |
-
args.encoder_embed_dim = input_dim
|
| 187 |
-
args.encoder_attention_heads = num_heads
|
| 188 |
-
args.attention_dropout = attention_dropout
|
| 189 |
-
args.dropout = dropout
|
| 190 |
-
args.activation_dropout = relu_dropout
|
| 191 |
-
args.encoder_normalize_before = normalize_before
|
| 192 |
-
args.encoder_ffn_embed_dim = ffn_dim
|
| 193 |
-
return args
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
def prepare_transformer_decoder_params(
|
| 197 |
-
input_dim,
|
| 198 |
-
num_heads,
|
| 199 |
-
ffn_dim,
|
| 200 |
-
normalize_before,
|
| 201 |
-
dropout,
|
| 202 |
-
attention_dropout,
|
| 203 |
-
relu_dropout,
|
| 204 |
-
):
|
| 205 |
-
args = argparse.Namespace()
|
| 206 |
-
args.encoder_embed_dim = None
|
| 207 |
-
args.decoder_embed_dim = input_dim
|
| 208 |
-
args.decoder_attention_heads = num_heads
|
| 209 |
-
args.attention_dropout = attention_dropout
|
| 210 |
-
args.dropout = dropout
|
| 211 |
-
args.activation_dropout = relu_dropout
|
| 212 |
-
args.decoder_normalize_before = normalize_before
|
| 213 |
-
args.decoder_ffn_embed_dim = ffn_dim
|
| 214 |
-
return args
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
class VGGTransformerEncoder(FairseqEncoder):
|
| 218 |
-
"""VGG + Transformer encoder"""
|
| 219 |
-
|
| 220 |
-
def __init__(
|
| 221 |
-
self,
|
| 222 |
-
input_feat_per_channel,
|
| 223 |
-
vggblock_config=DEFAULT_ENC_VGGBLOCK_CONFIG,
|
| 224 |
-
transformer_config=DEFAULT_ENC_TRANSFORMER_CONFIG,
|
| 225 |
-
encoder_output_dim=512,
|
| 226 |
-
in_channels=1,
|
| 227 |
-
transformer_context=None,
|
| 228 |
-
transformer_sampling=None,
|
| 229 |
-
):
|
| 230 |
-
"""constructor for VGGTransformerEncoder
|
| 231 |
-
|
| 232 |
-
Args:
|
| 233 |
-
- input_feat_per_channel: feature dim (not including stacked,
|
| 234 |
-
just base feature)
|
| 235 |
-
- in_channel: # input channels (e.g., if stack 8 feature vector
|
| 236 |
-
together, this is 8)
|
| 237 |
-
- vggblock_config: configuration of vggblock, see comments on
|
| 238 |
-
DEFAULT_ENC_VGGBLOCK_CONFIG
|
| 239 |
-
- transformer_config: configuration of transformer layer, see comments
|
| 240 |
-
on DEFAULT_ENC_TRANSFORMER_CONFIG
|
| 241 |
-
- encoder_output_dim: final transformer output embedding dimension
|
| 242 |
-
- transformer_context: (left, right) if set, self-attention will be focused
|
| 243 |
-
on (t-left, t+right)
|
| 244 |
-
- transformer_sampling: an iterable of int, must match with
|
| 245 |
-
len(transformer_config), transformer_sampling[i] indicates sampling
|
| 246 |
-
factor for i-th transformer layer, after multihead att and feedfoward
|
| 247 |
-
part
|
| 248 |
-
"""
|
| 249 |
-
super().__init__(None)
|
| 250 |
-
|
| 251 |
-
self.num_vggblocks = 0
|
| 252 |
-
if vggblock_config is not None:
|
| 253 |
-
if not isinstance(vggblock_config, Iterable):
|
| 254 |
-
raise ValueError("vggblock_config is not iterable")
|
| 255 |
-
self.num_vggblocks = len(vggblock_config)
|
| 256 |
-
|
| 257 |
-
self.conv_layers = nn.ModuleList()
|
| 258 |
-
self.in_channels = in_channels
|
| 259 |
-
self.input_dim = input_feat_per_channel
|
| 260 |
-
self.pooling_kernel_sizes = []
|
| 261 |
-
|
| 262 |
-
if vggblock_config is not None:
|
| 263 |
-
for _, config in enumerate(vggblock_config):
|
| 264 |
-
(
|
| 265 |
-
out_channels,
|
| 266 |
-
conv_kernel_size,
|
| 267 |
-
pooling_kernel_size,
|
| 268 |
-
num_conv_layers,
|
| 269 |
-
layer_norm,
|
| 270 |
-
) = config
|
| 271 |
-
self.conv_layers.append(
|
| 272 |
-
VGGBlock(
|
| 273 |
-
in_channels,
|
| 274 |
-
out_channels,
|
| 275 |
-
conv_kernel_size,
|
| 276 |
-
pooling_kernel_size,
|
| 277 |
-
num_conv_layers,
|
| 278 |
-
input_dim=input_feat_per_channel,
|
| 279 |
-
layer_norm=layer_norm,
|
| 280 |
-
)
|
| 281 |
-
)
|
| 282 |
-
self.pooling_kernel_sizes.append(pooling_kernel_size)
|
| 283 |
-
in_channels = out_channels
|
| 284 |
-
input_feat_per_channel = self.conv_layers[-1].output_dim
|
| 285 |
-
|
| 286 |
-
transformer_input_dim = self.infer_conv_output_dim(
|
| 287 |
-
self.in_channels, self.input_dim
|
| 288 |
-
)
|
| 289 |
-
# transformer_input_dim is the output dimension of VGG part
|
| 290 |
-
|
| 291 |
-
self.validate_transformer_config(transformer_config)
|
| 292 |
-
self.transformer_context = self.parse_transformer_context(transformer_context)
|
| 293 |
-
self.transformer_sampling = self.parse_transformer_sampling(
|
| 294 |
-
transformer_sampling, len(transformer_config)
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
self.transformer_layers = nn.ModuleList()
|
| 298 |
-
|
| 299 |
-
if transformer_input_dim != transformer_config[0][0]:
|
| 300 |
-
self.transformer_layers.append(
|
| 301 |
-
Linear(transformer_input_dim, transformer_config[0][0])
|
| 302 |
-
)
|
| 303 |
-
self.transformer_layers.append(
|
| 304 |
-
TransformerEncoderLayer(
|
| 305 |
-
prepare_transformer_encoder_params(*transformer_config[0])
|
| 306 |
-
)
|
| 307 |
-
)
|
| 308 |
-
|
| 309 |
-
for i in range(1, len(transformer_config)):
|
| 310 |
-
if transformer_config[i - 1][0] != transformer_config[i][0]:
|
| 311 |
-
self.transformer_layers.append(
|
| 312 |
-
Linear(transformer_config[i - 1][0], transformer_config[i][0])
|
| 313 |
-
)
|
| 314 |
-
self.transformer_layers.append(
|
| 315 |
-
TransformerEncoderLayer(
|
| 316 |
-
prepare_transformer_encoder_params(*transformer_config[i])
|
| 317 |
-
)
|
| 318 |
-
)
|
| 319 |
-
|
| 320 |
-
self.encoder_output_dim = encoder_output_dim
|
| 321 |
-
self.transformer_layers.extend(
|
| 322 |
-
[
|
| 323 |
-
Linear(transformer_config[-1][0], encoder_output_dim),
|
| 324 |
-
LayerNorm(encoder_output_dim),
|
| 325 |
-
]
|
| 326 |
-
)
|
| 327 |
-
|
| 328 |
-
def forward(self, src_tokens, src_lengths, **kwargs):
|
| 329 |
-
"""
|
| 330 |
-
src_tokens: padded tensor (B, T, C * feat)
|
| 331 |
-
src_lengths: tensor of original lengths of input utterances (B,)
|
| 332 |
-
"""
|
| 333 |
-
bsz, max_seq_len, _ = src_tokens.size()
|
| 334 |
-
x = src_tokens.view(bsz, max_seq_len, self.in_channels, self.input_dim)
|
| 335 |
-
x = x.transpose(1, 2).contiguous()
|
| 336 |
-
# (B, C, T, feat)
|
| 337 |
-
|
| 338 |
-
for layer_idx in range(len(self.conv_layers)):
|
| 339 |
-
x = self.conv_layers[layer_idx](x)
|
| 340 |
-
|
| 341 |
-
bsz, _, output_seq_len, _ = x.size()
|
| 342 |
-
|
| 343 |
-
# (B, C, T, feat) -> (B, T, C, feat) -> (T, B, C, feat) -> (T, B, C * feat)
|
| 344 |
-
x = x.transpose(1, 2).transpose(0, 1)
|
| 345 |
-
x = x.contiguous().view(output_seq_len, bsz, -1)
|
| 346 |
-
|
| 347 |
-
input_lengths = src_lengths.clone()
|
| 348 |
-
for s in self.pooling_kernel_sizes:
|
| 349 |
-
input_lengths = (input_lengths.float() / s).ceil().long()
|
| 350 |
-
|
| 351 |
-
encoder_padding_mask, _ = lengths_to_encoder_padding_mask(
|
| 352 |
-
input_lengths, batch_first=True
|
| 353 |
-
)
|
| 354 |
-
if not encoder_padding_mask.any():
|
| 355 |
-
encoder_padding_mask = None
|
| 356 |
-
|
| 357 |
-
subsampling_factor = int(max_seq_len * 1.0 / output_seq_len + 0.5)
|
| 358 |
-
attn_mask = self.lengths_to_attn_mask(input_lengths, subsampling_factor)
|
| 359 |
-
|
| 360 |
-
transformer_layer_idx = 0
|
| 361 |
-
|
| 362 |
-
for layer_idx in range(len(self.transformer_layers)):
|
| 363 |
-
|
| 364 |
-
if isinstance(self.transformer_layers[layer_idx], TransformerEncoderLayer):
|
| 365 |
-
x = self.transformer_layers[layer_idx](
|
| 366 |
-
x, encoder_padding_mask, attn_mask
|
| 367 |
-
)
|
| 368 |
-
|
| 369 |
-
if self.transformer_sampling[transformer_layer_idx] != 1:
|
| 370 |
-
sampling_factor = self.transformer_sampling[transformer_layer_idx]
|
| 371 |
-
x, encoder_padding_mask, attn_mask = self.slice(
|
| 372 |
-
x, encoder_padding_mask, attn_mask, sampling_factor
|
| 373 |
-
)
|
| 374 |
-
|
| 375 |
-
transformer_layer_idx += 1
|
| 376 |
-
|
| 377 |
-
else:
|
| 378 |
-
x = self.transformer_layers[layer_idx](x)
|
| 379 |
-
|
| 380 |
-
# encoder_padding_maks is a (T x B) tensor, its [t, b] elements indicate
|
| 381 |
-
# whether encoder_output[t, b] is valid or not (valid=0, invalid=1)
|
| 382 |
-
|
| 383 |
-
return {
|
| 384 |
-
"encoder_out": x, # (T, B, C)
|
| 385 |
-
"encoder_padding_mask": encoder_padding_mask.t()
|
| 386 |
-
if encoder_padding_mask is not None
|
| 387 |
-
else None,
|
| 388 |
-
# (B, T) --> (T, B)
|
| 389 |
-
}
|
| 390 |
-
|
| 391 |
-
def infer_conv_output_dim(self, in_channels, input_dim):
|
| 392 |
-
sample_seq_len = 200
|
| 393 |
-
sample_bsz = 10
|
| 394 |
-
x = torch.randn(sample_bsz, in_channels, sample_seq_len, input_dim)
|
| 395 |
-
for i, _ in enumerate(self.conv_layers):
|
| 396 |
-
x = self.conv_layers[i](x)
|
| 397 |
-
x = x.transpose(1, 2)
|
| 398 |
-
mb, seq = x.size()[:2]
|
| 399 |
-
return x.contiguous().view(mb, seq, -1).size(-1)
|
| 400 |
-
|
| 401 |
-
def validate_transformer_config(self, transformer_config):
|
| 402 |
-
for config in transformer_config:
|
| 403 |
-
input_dim, num_heads = config[:2]
|
| 404 |
-
if input_dim % num_heads != 0:
|
| 405 |
-
msg = (
|
| 406 |
-
"ERROR in transformer config {}: ".format(config)
|
| 407 |
-
+ "input dimension {} ".format(input_dim)
|
| 408 |
-
+ "not dividable by number of heads {}".format(num_heads)
|
| 409 |
-
)
|
| 410 |
-
raise ValueError(msg)
|
| 411 |
-
|
| 412 |
-
def parse_transformer_context(self, transformer_context):
|
| 413 |
-
"""
|
| 414 |
-
transformer_context can be the following:
|
| 415 |
-
- None; indicates no context is used, i.e.,
|
| 416 |
-
transformer can access full context
|
| 417 |
-
- a tuple/list of two int; indicates left and right context,
|
| 418 |
-
any number <0 indicates infinite context
|
| 419 |
-
* e.g., (5, 6) indicates that for query at x_t, transformer can
|
| 420 |
-
access [t-5, t+6] (inclusive)
|
| 421 |
-
* e.g., (-1, 6) indicates that for query at x_t, transformer can
|
| 422 |
-
access [0, t+6] (inclusive)
|
| 423 |
-
"""
|
| 424 |
-
if transformer_context is None:
|
| 425 |
-
return None
|
| 426 |
-
|
| 427 |
-
if not isinstance(transformer_context, Iterable):
|
| 428 |
-
raise ValueError("transformer context must be Iterable if it is not None")
|
| 429 |
-
|
| 430 |
-
if len(transformer_context) != 2:
|
| 431 |
-
raise ValueError("transformer context must have length 2")
|
| 432 |
-
|
| 433 |
-
left_context = transformer_context[0]
|
| 434 |
-
if left_context < 0:
|
| 435 |
-
left_context = None
|
| 436 |
-
|
| 437 |
-
right_context = transformer_context[1]
|
| 438 |
-
if right_context < 0:
|
| 439 |
-
right_context = None
|
| 440 |
-
|
| 441 |
-
if left_context is None and right_context is None:
|
| 442 |
-
return None
|
| 443 |
-
|
| 444 |
-
return (left_context, right_context)
|
| 445 |
-
|
| 446 |
-
def parse_transformer_sampling(self, transformer_sampling, num_layers):
|
| 447 |
-
"""
|
| 448 |
-
parsing transformer sampling configuration
|
| 449 |
-
|
| 450 |
-
Args:
|
| 451 |
-
- transformer_sampling, accepted input:
|
| 452 |
-
* None, indicating no sampling
|
| 453 |
-
* an Iterable with int (>0) as element
|
| 454 |
-
- num_layers, expected number of transformer layers, must match with
|
| 455 |
-
the length of transformer_sampling if it is not None
|
| 456 |
-
|
| 457 |
-
Returns:
|
| 458 |
-
- A tuple with length num_layers
|
| 459 |
-
"""
|
| 460 |
-
if transformer_sampling is None:
|
| 461 |
-
return (1,) * num_layers
|
| 462 |
-
|
| 463 |
-
if not isinstance(transformer_sampling, Iterable):
|
| 464 |
-
raise ValueError(
|
| 465 |
-
"transformer_sampling must be an iterable if it is not None"
|
| 466 |
-
)
|
| 467 |
-
|
| 468 |
-
if len(transformer_sampling) != num_layers:
|
| 469 |
-
raise ValueError(
|
| 470 |
-
"transformer_sampling {} does not match with the number "
|
| 471 |
-
"of layers {}".format(transformer_sampling, num_layers)
|
| 472 |
-
)
|
| 473 |
-
|
| 474 |
-
for layer, value in enumerate(transformer_sampling):
|
| 475 |
-
if not isinstance(value, int):
|
| 476 |
-
raise ValueError("Invalid value in transformer_sampling: ")
|
| 477 |
-
if value < 1:
|
| 478 |
-
raise ValueError(
|
| 479 |
-
"{} layer's subsampling is {}.".format(layer, value)
|
| 480 |
-
+ " This is not allowed! "
|
| 481 |
-
)
|
| 482 |
-
return transformer_sampling
|
| 483 |
-
|
| 484 |
-
def slice(self, embedding, padding_mask, attn_mask, sampling_factor):
|
| 485 |
-
"""
|
| 486 |
-
embedding is a (T, B, D) tensor
|
| 487 |
-
padding_mask is a (B, T) tensor or None
|
| 488 |
-
attn_mask is a (T, T) tensor or None
|
| 489 |
-
"""
|
| 490 |
-
embedding = embedding[::sampling_factor, :, :]
|
| 491 |
-
if padding_mask is not None:
|
| 492 |
-
padding_mask = padding_mask[:, ::sampling_factor]
|
| 493 |
-
if attn_mask is not None:
|
| 494 |
-
attn_mask = attn_mask[::sampling_factor, ::sampling_factor]
|
| 495 |
-
|
| 496 |
-
return embedding, padding_mask, attn_mask
|
| 497 |
-
|
| 498 |
-
def lengths_to_attn_mask(self, input_lengths, subsampling_factor=1):
|
| 499 |
-
"""
|
| 500 |
-
create attention mask according to sequence lengths and transformer
|
| 501 |
-
context
|
| 502 |
-
|
| 503 |
-
Args:
|
| 504 |
-
- input_lengths: (B, )-shape Int/Long tensor; input_lengths[b] is
|
| 505 |
-
the length of b-th sequence
|
| 506 |
-
- subsampling_factor: int
|
| 507 |
-
* Note that the left_context and right_context is specified in
|
| 508 |
-
the input frame-level while input to transformer may already
|
| 509 |
-
go through subsampling (e.g., the use of striding in vggblock)
|
| 510 |
-
we use subsampling_factor to scale the left/right context
|
| 511 |
-
|
| 512 |
-
Return:
|
| 513 |
-
- a (T, T) binary tensor or None, where T is max(input_lengths)
|
| 514 |
-
* if self.transformer_context is None, None
|
| 515 |
-
* if left_context is None,
|
| 516 |
-
* attn_mask[t, t + right_context + 1:] = 1
|
| 517 |
-
* others = 0
|
| 518 |
-
* if right_context is None,
|
| 519 |
-
* attn_mask[t, 0:t - left_context] = 1
|
| 520 |
-
* others = 0
|
| 521 |
-
* elsif
|
| 522 |
-
* attn_mask[t, t - left_context: t + right_context + 1] = 0
|
| 523 |
-
* others = 1
|
| 524 |
-
"""
|
| 525 |
-
if self.transformer_context is None:
|
| 526 |
-
return None
|
| 527 |
-
|
| 528 |
-
maxT = torch.max(input_lengths).item()
|
| 529 |
-
attn_mask = torch.zeros(maxT, maxT)
|
| 530 |
-
|
| 531 |
-
left_context = self.transformer_context[0]
|
| 532 |
-
right_context = self.transformer_context[1]
|
| 533 |
-
if left_context is not None:
|
| 534 |
-
left_context = math.ceil(self.transformer_context[0] / subsampling_factor)
|
| 535 |
-
if right_context is not None:
|
| 536 |
-
right_context = math.ceil(self.transformer_context[1] / subsampling_factor)
|
| 537 |
-
|
| 538 |
-
for t in range(maxT):
|
| 539 |
-
if left_context is not None:
|
| 540 |
-
st = 0
|
| 541 |
-
en = max(st, t - left_context)
|
| 542 |
-
attn_mask[t, st:en] = 1
|
| 543 |
-
if right_context is not None:
|
| 544 |
-
st = t + right_context + 1
|
| 545 |
-
st = min(st, maxT - 1)
|
| 546 |
-
attn_mask[t, st:] = 1
|
| 547 |
-
|
| 548 |
-
return attn_mask.to(input_lengths.device)
|
| 549 |
-
|
| 550 |
-
def reorder_encoder_out(self, encoder_out, new_order):
|
| 551 |
-
encoder_out["encoder_out"] = encoder_out["encoder_out"].index_select(
|
| 552 |
-
1, new_order
|
| 553 |
-
)
|
| 554 |
-
if encoder_out["encoder_padding_mask"] is not None:
|
| 555 |
-
encoder_out["encoder_padding_mask"] = encoder_out[
|
| 556 |
-
"encoder_padding_mask"
|
| 557 |
-
].index_select(1, new_order)
|
| 558 |
-
return encoder_out
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
class TransformerDecoder(FairseqIncrementalDecoder):
|
| 562 |
-
"""
|
| 563 |
-
Transformer decoder consisting of *args.decoder_layers* layers. Each layer
|
| 564 |
-
is a :class:`TransformerDecoderLayer`.
|
| 565 |
-
Args:
|
| 566 |
-
args (argparse.Namespace): parsed command-line arguments
|
| 567 |
-
dictionary (~fairseq.data.Dictionary): decoding dictionary
|
| 568 |
-
embed_tokens (torch.nn.Embedding): output embedding
|
| 569 |
-
no_encoder_attn (bool, optional): whether to attend to encoder outputs.
|
| 570 |
-
Default: ``False``
|
| 571 |
-
left_pad (bool, optional): whether the input is left-padded. Default:
|
| 572 |
-
``False``
|
| 573 |
-
"""
|
| 574 |
-
|
| 575 |
-
def __init__(
|
| 576 |
-
self,
|
| 577 |
-
dictionary,
|
| 578 |
-
embed_dim=512,
|
| 579 |
-
transformer_config=DEFAULT_ENC_TRANSFORMER_CONFIG,
|
| 580 |
-
conv_config=DEFAULT_DEC_CONV_CONFIG,
|
| 581 |
-
encoder_output_dim=512,
|
| 582 |
-
):
|
| 583 |
-
|
| 584 |
-
super().__init__(dictionary)
|
| 585 |
-
vocab_size = len(dictionary)
|
| 586 |
-
self.padding_idx = dictionary.pad()
|
| 587 |
-
self.embed_tokens = Embedding(vocab_size, embed_dim, self.padding_idx)
|
| 588 |
-
|
| 589 |
-
self.conv_layers = nn.ModuleList()
|
| 590 |
-
for i in range(len(conv_config)):
|
| 591 |
-
out_channels, kernel_size, layer_norm = conv_config[i]
|
| 592 |
-
if i == 0:
|
| 593 |
-
conv_layer = LinearizedConv1d(
|
| 594 |
-
embed_dim, out_channels, kernel_size, padding=kernel_size - 1
|
| 595 |
-
)
|
| 596 |
-
else:
|
| 597 |
-
conv_layer = LinearizedConv1d(
|
| 598 |
-
conv_config[i - 1][0],
|
| 599 |
-
out_channels,
|
| 600 |
-
kernel_size,
|
| 601 |
-
padding=kernel_size - 1,
|
| 602 |
-
)
|
| 603 |
-
self.conv_layers.append(conv_layer)
|
| 604 |
-
if layer_norm:
|
| 605 |
-
self.conv_layers.append(nn.LayerNorm(out_channels))
|
| 606 |
-
self.conv_layers.append(nn.ReLU())
|
| 607 |
-
|
| 608 |
-
self.layers = nn.ModuleList()
|
| 609 |
-
if conv_config[-1][0] != transformer_config[0][0]:
|
| 610 |
-
self.layers.append(Linear(conv_config[-1][0], transformer_config[0][0]))
|
| 611 |
-
self.layers.append(
|
| 612 |
-
TransformerDecoderLayer(
|
| 613 |
-
prepare_transformer_decoder_params(*transformer_config[0])
|
| 614 |
-
)
|
| 615 |
-
)
|
| 616 |
-
|
| 617 |
-
for i in range(1, len(transformer_config)):
|
| 618 |
-
if transformer_config[i - 1][0] != transformer_config[i][0]:
|
| 619 |
-
self.layers.append(
|
| 620 |
-
Linear(transformer_config[i - 1][0], transformer_config[i][0])
|
| 621 |
-
)
|
| 622 |
-
self.layers.append(
|
| 623 |
-
TransformerDecoderLayer(
|
| 624 |
-
prepare_transformer_decoder_params(*transformer_config[i])
|
| 625 |
-
)
|
| 626 |
-
)
|
| 627 |
-
self.fc_out = Linear(transformer_config[-1][0], vocab_size)
|
| 628 |
-
|
| 629 |
-
def forward(self, prev_output_tokens, encoder_out=None, incremental_state=None):
|
| 630 |
-
"""
|
| 631 |
-
Args:
|
| 632 |
-
prev_output_tokens (LongTensor): previous decoder outputs of shape
|
| 633 |
-
`(batch, tgt_len)`, for input feeding/teacher forcing
|
| 634 |
-
encoder_out (Tensor, optional): output from the encoder, used for
|
| 635 |
-
encoder-side attention
|
| 636 |
-
incremental_state (dict): dictionary used for storing state during
|
| 637 |
-
:ref:`Incremental decoding`
|
| 638 |
-
Returns:
|
| 639 |
-
tuple:
|
| 640 |
-
- the last decoder layer's output of shape `(batch, tgt_len,
|
| 641 |
-
vocab)`
|
| 642 |
-
- the last decoder layer's attention weights of shape `(batch,
|
| 643 |
-
tgt_len, src_len)`
|
| 644 |
-
"""
|
| 645 |
-
target_padding_mask = (
|
| 646 |
-
(prev_output_tokens == self.padding_idx).to(prev_output_tokens.device)
|
| 647 |
-
if incremental_state is None
|
| 648 |
-
else None
|
| 649 |
-
)
|
| 650 |
-
|
| 651 |
-
if incremental_state is not None:
|
| 652 |
-
prev_output_tokens = prev_output_tokens[:, -1:]
|
| 653 |
-
|
| 654 |
-
# embed tokens
|
| 655 |
-
x = self.embed_tokens(prev_output_tokens)
|
| 656 |
-
|
| 657 |
-
# B x T x C -> T x B x C
|
| 658 |
-
x = self._transpose_if_training(x, incremental_state)
|
| 659 |
-
|
| 660 |
-
for layer in self.conv_layers:
|
| 661 |
-
if isinstance(layer, LinearizedConvolution):
|
| 662 |
-
x = layer(x, incremental_state)
|
| 663 |
-
else:
|
| 664 |
-
x = layer(x)
|
| 665 |
-
|
| 666 |
-
# B x T x C -> T x B x C
|
| 667 |
-
x = self._transpose_if_inference(x, incremental_state)
|
| 668 |
-
|
| 669 |
-
# decoder layers
|
| 670 |
-
for layer in self.layers:
|
| 671 |
-
if isinstance(layer, TransformerDecoderLayer):
|
| 672 |
-
x, *_ = layer(
|
| 673 |
-
x,
|
| 674 |
-
(encoder_out["encoder_out"] if encoder_out is not None else None),
|
| 675 |
-
(
|
| 676 |
-
encoder_out["encoder_padding_mask"].t()
|
| 677 |
-
if encoder_out["encoder_padding_mask"] is not None
|
| 678 |
-
else None
|
| 679 |
-
),
|
| 680 |
-
incremental_state,
|
| 681 |
-
self_attn_mask=(
|
| 682 |
-
self.buffered_future_mask(x)
|
| 683 |
-
if incremental_state is None
|
| 684 |
-
else None
|
| 685 |
-
),
|
| 686 |
-
self_attn_padding_mask=(
|
| 687 |
-
target_padding_mask if incremental_state is None else None
|
| 688 |
-
),
|
| 689 |
-
)
|
| 690 |
-
else:
|
| 691 |
-
x = layer(x)
|
| 692 |
-
|
| 693 |
-
# T x B x C -> B x T x C
|
| 694 |
-
x = x.transpose(0, 1)
|
| 695 |
-
|
| 696 |
-
x = self.fc_out(x)
|
| 697 |
-
|
| 698 |
-
return x, None
|
| 699 |
-
|
| 700 |
-
def buffered_future_mask(self, tensor):
|
| 701 |
-
dim = tensor.size(0)
|
| 702 |
-
if (
|
| 703 |
-
not hasattr(self, "_future_mask")
|
| 704 |
-
or self._future_mask is None
|
| 705 |
-
or self._future_mask.device != tensor.device
|
| 706 |
-
):
|
| 707 |
-
self._future_mask = torch.triu(
|
| 708 |
-
utils.fill_with_neg_inf(tensor.new(dim, dim)), 1
|
| 709 |
-
)
|
| 710 |
-
if self._future_mask.size(0) < dim:
|
| 711 |
-
self._future_mask = torch.triu(
|
| 712 |
-
utils.fill_with_neg_inf(self._future_mask.resize_(dim, dim)), 1
|
| 713 |
-
)
|
| 714 |
-
return self._future_mask[:dim, :dim]
|
| 715 |
-
|
| 716 |
-
def _transpose_if_training(self, x, incremental_state):
|
| 717 |
-
if incremental_state is None:
|
| 718 |
-
x = x.transpose(0, 1)
|
| 719 |
-
return x
|
| 720 |
-
|
| 721 |
-
def _transpose_if_inference(self, x, incremental_state):
|
| 722 |
-
if incremental_state:
|
| 723 |
-
x = x.transpose(0, 1)
|
| 724 |
-
return x
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
@register_model("asr_vggtransformer_encoder")
|
| 728 |
-
class VGGTransformerEncoderModel(FairseqEncoderModel):
|
| 729 |
-
def __init__(self, encoder):
|
| 730 |
-
super().__init__(encoder)
|
| 731 |
-
|
| 732 |
-
@staticmethod
|
| 733 |
-
def add_args(parser):
|
| 734 |
-
"""Add model-specific arguments to the parser."""
|
| 735 |
-
parser.add_argument(
|
| 736 |
-
"--input-feat-per-channel",
|
| 737 |
-
type=int,
|
| 738 |
-
metavar="N",
|
| 739 |
-
help="encoder input dimension per input channel",
|
| 740 |
-
)
|
| 741 |
-
parser.add_argument(
|
| 742 |
-
"--vggblock-enc-config",
|
| 743 |
-
type=str,
|
| 744 |
-
metavar="EXPR",
|
| 745 |
-
help="""
|
| 746 |
-
an array of tuples each containing the configuration of one vggblock
|
| 747 |
-
[(out_channels, conv_kernel_size, pooling_kernel_size,num_conv_layers), ...]
|
| 748 |
-
""",
|
| 749 |
-
)
|
| 750 |
-
parser.add_argument(
|
| 751 |
-
"--transformer-enc-config",
|
| 752 |
-
type=str,
|
| 753 |
-
metavar="EXPR",
|
| 754 |
-
help="""
|
| 755 |
-
a tuple containing the configuration of the Transformer layers
|
| 756 |
-
configurations:
|
| 757 |
-
[(input_dim,
|
| 758 |
-
num_heads,
|
| 759 |
-
ffn_dim,
|
| 760 |
-
normalize_before,
|
| 761 |
-
dropout,
|
| 762 |
-
attention_dropout,
|
| 763 |
-
relu_dropout), ]""",
|
| 764 |
-
)
|
| 765 |
-
parser.add_argument(
|
| 766 |
-
"--enc-output-dim",
|
| 767 |
-
type=int,
|
| 768 |
-
metavar="N",
|
| 769 |
-
help="encoder output dimension, projecting the LSTM output",
|
| 770 |
-
)
|
| 771 |
-
parser.add_argument(
|
| 772 |
-
"--in-channels",
|
| 773 |
-
type=int,
|
| 774 |
-
metavar="N",
|
| 775 |
-
help="number of encoder input channels",
|
| 776 |
-
)
|
| 777 |
-
parser.add_argument(
|
| 778 |
-
"--transformer-context",
|
| 779 |
-
type=str,
|
| 780 |
-
metavar="EXPR",
|
| 781 |
-
help="""
|
| 782 |
-
either None or a tuple of two ints, indicating left/right context a
|
| 783 |
-
transformer can have access to""",
|
| 784 |
-
)
|
| 785 |
-
parser.add_argument(
|
| 786 |
-
"--transformer-sampling",
|
| 787 |
-
type=str,
|
| 788 |
-
metavar="EXPR",
|
| 789 |
-
help="""
|
| 790 |
-
either None or a tuple of ints, indicating sampling factor in each layer""",
|
| 791 |
-
)
|
| 792 |
-
|
| 793 |
-
@classmethod
|
| 794 |
-
def build_model(cls, args, task):
|
| 795 |
-
"""Build a new model instance."""
|
| 796 |
-
base_architecture_enconly(args)
|
| 797 |
-
encoder = VGGTransformerEncoderOnly(
|
| 798 |
-
vocab_size=len(task.target_dictionary),
|
| 799 |
-
input_feat_per_channel=args.input_feat_per_channel,
|
| 800 |
-
vggblock_config=eval(args.vggblock_enc_config),
|
| 801 |
-
transformer_config=eval(args.transformer_enc_config),
|
| 802 |
-
encoder_output_dim=args.enc_output_dim,
|
| 803 |
-
in_channels=args.in_channels,
|
| 804 |
-
transformer_context=eval(args.transformer_context),
|
| 805 |
-
transformer_sampling=eval(args.transformer_sampling),
|
| 806 |
-
)
|
| 807 |
-
return cls(encoder)
|
| 808 |
-
|
| 809 |
-
def get_normalized_probs(self, net_output, log_probs, sample=None):
|
| 810 |
-
# net_output['encoder_out'] is a (T, B, D) tensor
|
| 811 |
-
lprobs = super().get_normalized_probs(net_output, log_probs, sample)
|
| 812 |
-
# lprobs is a (T, B, D) tensor
|
| 813 |
-
# we need to transoose to get (B, T, D) tensor
|
| 814 |
-
lprobs = lprobs.transpose(0, 1).contiguous()
|
| 815 |
-
lprobs.batch_first = True
|
| 816 |
-
return lprobs
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
class VGGTransformerEncoderOnly(VGGTransformerEncoder):
|
| 820 |
-
def __init__(
|
| 821 |
-
self,
|
| 822 |
-
vocab_size,
|
| 823 |
-
input_feat_per_channel,
|
| 824 |
-
vggblock_config=DEFAULT_ENC_VGGBLOCK_CONFIG,
|
| 825 |
-
transformer_config=DEFAULT_ENC_TRANSFORMER_CONFIG,
|
| 826 |
-
encoder_output_dim=512,
|
| 827 |
-
in_channels=1,
|
| 828 |
-
transformer_context=None,
|
| 829 |
-
transformer_sampling=None,
|
| 830 |
-
):
|
| 831 |
-
super().__init__(
|
| 832 |
-
input_feat_per_channel=input_feat_per_channel,
|
| 833 |
-
vggblock_config=vggblock_config,
|
| 834 |
-
transformer_config=transformer_config,
|
| 835 |
-
encoder_output_dim=encoder_output_dim,
|
| 836 |
-
in_channels=in_channels,
|
| 837 |
-
transformer_context=transformer_context,
|
| 838 |
-
transformer_sampling=transformer_sampling,
|
| 839 |
-
)
|
| 840 |
-
self.fc_out = Linear(self.encoder_output_dim, vocab_size)
|
| 841 |
-
|
| 842 |
-
def forward(self, src_tokens, src_lengths, **kwargs):
|
| 843 |
-
"""
|
| 844 |
-
src_tokens: padded tensor (B, T, C * feat)
|
| 845 |
-
src_lengths: tensor of original lengths of input utterances (B,)
|
| 846 |
-
"""
|
| 847 |
-
|
| 848 |
-
enc_out = super().forward(src_tokens, src_lengths)
|
| 849 |
-
x = self.fc_out(enc_out["encoder_out"])
|
| 850 |
-
# x = F.log_softmax(x, dim=-1)
|
| 851 |
-
# Note: no need this line, because model.get_normalized_prob will call
|
| 852 |
-
# log_softmax
|
| 853 |
-
return {
|
| 854 |
-
"encoder_out": x, # (T, B, C)
|
| 855 |
-
"encoder_padding_mask": enc_out["encoder_padding_mask"], # (T, B)
|
| 856 |
-
}
|
| 857 |
-
|
| 858 |
-
def max_positions(self):
|
| 859 |
-
"""Maximum input length supported by the encoder."""
|
| 860 |
-
return (1e6, 1e6) # an arbitrary large number
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
def Embedding(num_embeddings, embedding_dim, padding_idx):
|
| 864 |
-
m = nn.Embedding(num_embeddings, embedding_dim, padding_idx=padding_idx)
|
| 865 |
-
# nn.init.uniform_(m.weight, -0.1, 0.1)
|
| 866 |
-
# nn.init.constant_(m.weight[padding_idx], 0)
|
| 867 |
-
return m
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
def Linear(in_features, out_features, bias=True, dropout=0):
|
| 871 |
-
"""Linear layer (input: N x T x C)"""
|
| 872 |
-
m = nn.Linear(in_features, out_features, bias=bias)
|
| 873 |
-
# m.weight.data.uniform_(-0.1, 0.1)
|
| 874 |
-
# if bias:
|
| 875 |
-
# m.bias.data.uniform_(-0.1, 0.1)
|
| 876 |
-
return m
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
def LinearizedConv1d(in_channels, out_channels, kernel_size, dropout=0, **kwargs):
|
| 880 |
-
"""Weight-normalized Conv1d layer optimized for decoding"""
|
| 881 |
-
m = LinearizedConvolution(in_channels, out_channels, kernel_size, **kwargs)
|
| 882 |
-
std = math.sqrt((4 * (1.0 - dropout)) / (m.kernel_size[0] * in_channels))
|
| 883 |
-
nn.init.normal_(m.weight, mean=0, std=std)
|
| 884 |
-
nn.init.constant_(m.bias, 0)
|
| 885 |
-
return nn.utils.weight_norm(m, dim=2)
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
def LayerNorm(embedding_dim):
|
| 889 |
-
m = nn.LayerNorm(embedding_dim)
|
| 890 |
-
return m
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
# seq2seq models
|
| 894 |
-
def base_architecture(args):
|
| 895 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 40)
|
| 896 |
-
args.vggblock_enc_config = getattr(
|
| 897 |
-
args, "vggblock_enc_config", DEFAULT_ENC_VGGBLOCK_CONFIG
|
| 898 |
-
)
|
| 899 |
-
args.transformer_enc_config = getattr(
|
| 900 |
-
args, "transformer_enc_config", DEFAULT_ENC_TRANSFORMER_CONFIG
|
| 901 |
-
)
|
| 902 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 512)
|
| 903 |
-
args.in_channels = getattr(args, "in_channels", 1)
|
| 904 |
-
args.tgt_embed_dim = getattr(args, "tgt_embed_dim", 128)
|
| 905 |
-
args.transformer_dec_config = getattr(
|
| 906 |
-
args, "transformer_dec_config", DEFAULT_ENC_TRANSFORMER_CONFIG
|
| 907 |
-
)
|
| 908 |
-
args.conv_dec_config = getattr(args, "conv_dec_config", DEFAULT_DEC_CONV_CONFIG)
|
| 909 |
-
args.transformer_context = getattr(args, "transformer_context", "None")
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
@register_model_architecture("asr_vggtransformer", "vggtransformer_1")
|
| 913 |
-
def vggtransformer_1(args):
|
| 914 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 80)
|
| 915 |
-
args.vggblock_enc_config = getattr(
|
| 916 |
-
args, "vggblock_enc_config", "[(64, 3, 2, 2, True), (128, 3, 2, 2, True)]"
|
| 917 |
-
)
|
| 918 |
-
args.transformer_enc_config = getattr(
|
| 919 |
-
args,
|
| 920 |
-
"transformer_enc_config",
|
| 921 |
-
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 14",
|
| 922 |
-
)
|
| 923 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 1024)
|
| 924 |
-
args.tgt_embed_dim = getattr(args, "tgt_embed_dim", 128)
|
| 925 |
-
args.conv_dec_config = getattr(args, "conv_dec_config", "((256, 3, True),) * 4")
|
| 926 |
-
args.transformer_dec_config = getattr(
|
| 927 |
-
args,
|
| 928 |
-
"transformer_dec_config",
|
| 929 |
-
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 4",
|
| 930 |
-
)
|
| 931 |
-
|
| 932 |
-
|
| 933 |
-
@register_model_architecture("asr_vggtransformer", "vggtransformer_2")
|
| 934 |
-
def vggtransformer_2(args):
|
| 935 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 80)
|
| 936 |
-
args.vggblock_enc_config = getattr(
|
| 937 |
-
args, "vggblock_enc_config", "[(64, 3, 2, 2, True), (128, 3, 2, 2, True)]"
|
| 938 |
-
)
|
| 939 |
-
args.transformer_enc_config = getattr(
|
| 940 |
-
args,
|
| 941 |
-
"transformer_enc_config",
|
| 942 |
-
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 16",
|
| 943 |
-
)
|
| 944 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 1024)
|
| 945 |
-
args.tgt_embed_dim = getattr(args, "tgt_embed_dim", 512)
|
| 946 |
-
args.conv_dec_config = getattr(args, "conv_dec_config", "((256, 3, True),) * 4")
|
| 947 |
-
args.transformer_dec_config = getattr(
|
| 948 |
-
args,
|
| 949 |
-
"transformer_dec_config",
|
| 950 |
-
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 6",
|
| 951 |
-
)
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
@register_model_architecture("asr_vggtransformer", "vggtransformer_base")
|
| 955 |
-
def vggtransformer_base(args):
|
| 956 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 80)
|
| 957 |
-
args.vggblock_enc_config = getattr(
|
| 958 |
-
args, "vggblock_enc_config", "[(64, 3, 2, 2, True), (128, 3, 2, 2, True)]"
|
| 959 |
-
)
|
| 960 |
-
args.transformer_enc_config = getattr(
|
| 961 |
-
args, "transformer_enc_config", "((512, 8, 2048, True, 0.15, 0.15, 0.15),) * 12"
|
| 962 |
-
)
|
| 963 |
-
|
| 964 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 512)
|
| 965 |
-
args.tgt_embed_dim = getattr(args, "tgt_embed_dim", 512)
|
| 966 |
-
args.conv_dec_config = getattr(args, "conv_dec_config", "((256, 3, True),) * 4")
|
| 967 |
-
args.transformer_dec_config = getattr(
|
| 968 |
-
args, "transformer_dec_config", "((512, 8, 2048, True, 0.15, 0.15, 0.15),) * 6"
|
| 969 |
-
)
|
| 970 |
-
# Size estimations:
|
| 971 |
-
# Encoder:
|
| 972 |
-
# - vggblock param: 64*1*3*3 + 64*64*3*3 + 128*64*3*3 + 128*128*3 = 258K
|
| 973 |
-
# Transformer:
|
| 974 |
-
# - input dimension adapter: 2560 x 512 -> 1.31M
|
| 975 |
-
# - transformer_layers (x12) --> 37.74M
|
| 976 |
-
# * MultiheadAttention: 512*512*3 (in_proj) + 512*512 (out_proj) = 1.048M
|
| 977 |
-
# * FFN weight: 512*2048*2 = 2.097M
|
| 978 |
-
# - output dimension adapter: 512 x 512 -> 0.26 M
|
| 979 |
-
# Decoder:
|
| 980 |
-
# - LinearizedConv1d: 512 * 256 * 3 + 256 * 256 * 3 * 3
|
| 981 |
-
# - transformer_layer: (x6) --> 25.16M
|
| 982 |
-
# * MultiheadAttention (self-attention): 512*512*3 + 512*512 = 1.048M
|
| 983 |
-
# * MultiheadAttention (encoder-attention): 512*512*3 + 512*512 = 1.048M
|
| 984 |
-
# * FFN: 512*2048*2 = 2.097M
|
| 985 |
-
# Final FC:
|
| 986 |
-
# - FC: 512*5000 = 256K (assuming vocab size 5K)
|
| 987 |
-
# In total:
|
| 988 |
-
# ~65 M
|
| 989 |
-
|
| 990 |
-
|
| 991 |
-
# CTC models
|
| 992 |
-
def base_architecture_enconly(args):
|
| 993 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 40)
|
| 994 |
-
args.vggblock_enc_config = getattr(
|
| 995 |
-
args, "vggblock_enc_config", "[(32, 3, 2, 2, True)] * 2"
|
| 996 |
-
)
|
| 997 |
-
args.transformer_enc_config = getattr(
|
| 998 |
-
args, "transformer_enc_config", "((256, 4, 1024, True, 0.2, 0.2, 0.2),) * 2"
|
| 999 |
-
)
|
| 1000 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 512)
|
| 1001 |
-
args.in_channels = getattr(args, "in_channels", 1)
|
| 1002 |
-
args.transformer_context = getattr(args, "transformer_context", "None")
|
| 1003 |
-
args.transformer_sampling = getattr(args, "transformer_sampling", "None")
|
| 1004 |
-
|
| 1005 |
-
|
| 1006 |
-
@register_model_architecture("asr_vggtransformer_encoder", "vggtransformer_enc_1")
|
| 1007 |
-
def vggtransformer_enc_1(args):
|
| 1008 |
-
# vggtransformer_1 is the same as vggtransformer_enc_big, except the number
|
| 1009 |
-
# of layers is increased to 16
|
| 1010 |
-
# keep it here for backward compatiablity purpose
|
| 1011 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 80)
|
| 1012 |
-
args.vggblock_enc_config = getattr(
|
| 1013 |
-
args, "vggblock_enc_config", "[(64, 3, 2, 2, True), (128, 3, 2, 2, True)]"
|
| 1014 |
-
)
|
| 1015 |
-
args.transformer_enc_config = getattr(
|
| 1016 |
-
args,
|
| 1017 |
-
"transformer_enc_config",
|
| 1018 |
-
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 16",
|
| 1019 |
-
)
|
| 1020 |
-
args.enc_output_dim = getattr(args, "enc_output_dim", 1024)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/models/w2l_conv_glu_enc.py
DELETED
|
@@ -1,176 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
import math
|
| 9 |
-
|
| 10 |
-
import torch
|
| 11 |
-
import torch.nn as nn
|
| 12 |
-
import torch.nn.functional as F
|
| 13 |
-
from fairseq.models import (
|
| 14 |
-
FairseqEncoder,
|
| 15 |
-
FairseqEncoderModel,
|
| 16 |
-
register_model,
|
| 17 |
-
register_model_architecture,
|
| 18 |
-
)
|
| 19 |
-
from fairseq.modules.fairseq_dropout import FairseqDropout
|
| 20 |
-
|
| 21 |
-
default_conv_enc_config = """[
|
| 22 |
-
(400, 13, 170, 0.2),
|
| 23 |
-
(440, 14, 0, 0.214),
|
| 24 |
-
(484, 15, 0, 0.22898),
|
| 25 |
-
(532, 16, 0, 0.2450086),
|
| 26 |
-
(584, 17, 0, 0.262159202),
|
| 27 |
-
(642, 18, 0, 0.28051034614),
|
| 28 |
-
(706, 19, 0, 0.30014607037),
|
| 29 |
-
(776, 20, 0, 0.321156295296),
|
| 30 |
-
(852, 21, 0, 0.343637235966),
|
| 31 |
-
(936, 22, 0, 0.367691842484),
|
| 32 |
-
(1028, 23, 0, 0.393430271458),
|
| 33 |
-
(1130, 24, 0, 0.42097039046),
|
| 34 |
-
(1242, 25, 0, 0.450438317792),
|
| 35 |
-
(1366, 26, 0, 0.481969000038),
|
| 36 |
-
(1502, 27, 0, 0.51570683004),
|
| 37 |
-
(1652, 28, 0, 0.551806308143),
|
| 38 |
-
(1816, 29, 0, 0.590432749713),
|
| 39 |
-
]"""
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
@register_model("asr_w2l_conv_glu_encoder")
|
| 43 |
-
class W2lConvGluEncoderModel(FairseqEncoderModel):
|
| 44 |
-
def __init__(self, encoder):
|
| 45 |
-
super().__init__(encoder)
|
| 46 |
-
|
| 47 |
-
@staticmethod
|
| 48 |
-
def add_args(parser):
|
| 49 |
-
"""Add model-specific arguments to the parser."""
|
| 50 |
-
parser.add_argument(
|
| 51 |
-
"--input-feat-per-channel",
|
| 52 |
-
type=int,
|
| 53 |
-
metavar="N",
|
| 54 |
-
help="encoder input dimension per input channel",
|
| 55 |
-
)
|
| 56 |
-
parser.add_argument(
|
| 57 |
-
"--in-channels",
|
| 58 |
-
type=int,
|
| 59 |
-
metavar="N",
|
| 60 |
-
help="number of encoder input channels",
|
| 61 |
-
)
|
| 62 |
-
parser.add_argument(
|
| 63 |
-
"--conv-enc-config",
|
| 64 |
-
type=str,
|
| 65 |
-
metavar="EXPR",
|
| 66 |
-
help="""
|
| 67 |
-
an array of tuples each containing the configuration of one conv layer
|
| 68 |
-
[(out_channels, kernel_size, padding, dropout), ...]
|
| 69 |
-
""",
|
| 70 |
-
)
|
| 71 |
-
|
| 72 |
-
@classmethod
|
| 73 |
-
def build_model(cls, args, task):
|
| 74 |
-
"""Build a new model instance."""
|
| 75 |
-
conv_enc_config = getattr(args, "conv_enc_config", default_conv_enc_config)
|
| 76 |
-
encoder = W2lConvGluEncoder(
|
| 77 |
-
vocab_size=len(task.target_dictionary),
|
| 78 |
-
input_feat_per_channel=args.input_feat_per_channel,
|
| 79 |
-
in_channels=args.in_channels,
|
| 80 |
-
conv_enc_config=eval(conv_enc_config),
|
| 81 |
-
)
|
| 82 |
-
return cls(encoder)
|
| 83 |
-
|
| 84 |
-
def get_normalized_probs(self, net_output, log_probs, sample=None):
|
| 85 |
-
lprobs = super().get_normalized_probs(net_output, log_probs, sample)
|
| 86 |
-
lprobs.batch_first = False
|
| 87 |
-
return lprobs
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
class W2lConvGluEncoder(FairseqEncoder):
|
| 91 |
-
def __init__(
|
| 92 |
-
self, vocab_size, input_feat_per_channel, in_channels, conv_enc_config
|
| 93 |
-
):
|
| 94 |
-
super().__init__(None)
|
| 95 |
-
|
| 96 |
-
self.input_dim = input_feat_per_channel
|
| 97 |
-
if in_channels != 1:
|
| 98 |
-
raise ValueError("only 1 input channel is currently supported")
|
| 99 |
-
|
| 100 |
-
self.conv_layers = nn.ModuleList()
|
| 101 |
-
self.linear_layers = nn.ModuleList()
|
| 102 |
-
self.dropouts = []
|
| 103 |
-
cur_channels = input_feat_per_channel
|
| 104 |
-
|
| 105 |
-
for out_channels, kernel_size, padding, dropout in conv_enc_config:
|
| 106 |
-
layer = nn.Conv1d(cur_channels, out_channels, kernel_size, padding=padding)
|
| 107 |
-
layer.weight.data.mul_(math.sqrt(3)) # match wav2letter init
|
| 108 |
-
self.conv_layers.append(nn.utils.weight_norm(layer))
|
| 109 |
-
self.dropouts.append(
|
| 110 |
-
FairseqDropout(dropout, module_name=self.__class__.__name__)
|
| 111 |
-
)
|
| 112 |
-
if out_channels % 2 != 0:
|
| 113 |
-
raise ValueError("odd # of out_channels is incompatible with GLU")
|
| 114 |
-
cur_channels = out_channels // 2 # halved by GLU
|
| 115 |
-
|
| 116 |
-
for out_channels in [2 * cur_channels, vocab_size]:
|
| 117 |
-
layer = nn.Linear(cur_channels, out_channels)
|
| 118 |
-
layer.weight.data.mul_(math.sqrt(3))
|
| 119 |
-
self.linear_layers.append(nn.utils.weight_norm(layer))
|
| 120 |
-
cur_channels = out_channels // 2
|
| 121 |
-
|
| 122 |
-
def forward(self, src_tokens, src_lengths, **kwargs):
|
| 123 |
-
|
| 124 |
-
"""
|
| 125 |
-
src_tokens: padded tensor (B, T, C * feat)
|
| 126 |
-
src_lengths: tensor of original lengths of input utterances (B,)
|
| 127 |
-
"""
|
| 128 |
-
B, T, _ = src_tokens.size()
|
| 129 |
-
x = src_tokens.transpose(1, 2).contiguous() # (B, feat, T) assuming C == 1
|
| 130 |
-
|
| 131 |
-
for layer_idx in range(len(self.conv_layers)):
|
| 132 |
-
x = self.conv_layers[layer_idx](x)
|
| 133 |
-
x = F.glu(x, dim=1)
|
| 134 |
-
x = self.dropouts[layer_idx](x)
|
| 135 |
-
|
| 136 |
-
x = x.transpose(1, 2).contiguous() # (B, T, 908)
|
| 137 |
-
x = self.linear_layers[0](x)
|
| 138 |
-
x = F.glu(x, dim=2)
|
| 139 |
-
x = self.dropouts[-1](x)
|
| 140 |
-
x = self.linear_layers[1](x)
|
| 141 |
-
|
| 142 |
-
assert x.size(0) == B
|
| 143 |
-
assert x.size(1) == T
|
| 144 |
-
|
| 145 |
-
encoder_out = x.transpose(0, 1) # (T, B, vocab_size)
|
| 146 |
-
|
| 147 |
-
# need to debug this -- find a simpler/elegant way in pytorch APIs
|
| 148 |
-
encoder_padding_mask = (
|
| 149 |
-
torch.arange(T).view(1, T).expand(B, -1).to(x.device)
|
| 150 |
-
>= src_lengths.view(B, 1).expand(-1, T)
|
| 151 |
-
).t() # (B x T) -> (T x B)
|
| 152 |
-
|
| 153 |
-
return {
|
| 154 |
-
"encoder_out": encoder_out, # (T, B, vocab_size)
|
| 155 |
-
"encoder_padding_mask": encoder_padding_mask, # (T, B)
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
def reorder_encoder_out(self, encoder_out, new_order):
|
| 159 |
-
encoder_out["encoder_out"] = encoder_out["encoder_out"].index_select(
|
| 160 |
-
1, new_order
|
| 161 |
-
)
|
| 162 |
-
encoder_out["encoder_padding_mask"] = encoder_out[
|
| 163 |
-
"encoder_padding_mask"
|
| 164 |
-
].index_select(1, new_order)
|
| 165 |
-
return encoder_out
|
| 166 |
-
|
| 167 |
-
def max_positions(self):
|
| 168 |
-
"""Maximum input length supported by the encoder."""
|
| 169 |
-
return (1e6, 1e6) # an arbitrary large number
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
@register_model_architecture("asr_w2l_conv_glu_encoder", "w2l_conv_glu_enc")
|
| 173 |
-
def w2l_conv_glu_enc(args):
|
| 174 |
-
args.input_feat_per_channel = getattr(args, "input_feat_per_channel", 80)
|
| 175 |
-
args.in_channels = getattr(args, "in_channels", 1)
|
| 176 |
-
args.conv_enc_config = getattr(args, "conv_enc_config", default_conv_enc_config)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/README.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
| 1 |
-
# Flashlight Decoder
|
| 2 |
-
|
| 3 |
-
This script runs decoding for pre-trained speech recognition models.
|
| 4 |
-
|
| 5 |
-
## Usage
|
| 6 |
-
|
| 7 |
-
Assuming a few variables:
|
| 8 |
-
|
| 9 |
-
```bash
|
| 10 |
-
checkpoint=<path-to-checkpoint>
|
| 11 |
-
data=<path-to-data-directory>
|
| 12 |
-
lm_model=<path-to-language-model>
|
| 13 |
-
lexicon=<path-to-lexicon>
|
| 14 |
-
```
|
| 15 |
-
|
| 16 |
-
Example usage for decoding a fine-tuned Wav2Vec model:
|
| 17 |
-
|
| 18 |
-
```bash
|
| 19 |
-
python $FAIRSEQ_ROOT/examples/speech_recognition/new/infer.py --multirun \
|
| 20 |
-
task=audio_pretraining \
|
| 21 |
-
task.data=$data \
|
| 22 |
-
task.labels=ltr \
|
| 23 |
-
common_eval.path=$checkpoint \
|
| 24 |
-
decoding.type=kenlm \
|
| 25 |
-
decoding.lexicon=$lexicon \
|
| 26 |
-
decoding.lmpath=$lm_model \
|
| 27 |
-
dataset.gen_subset=dev_clean,dev_other,test_clean,test_other
|
| 28 |
-
```
|
| 29 |
-
|
| 30 |
-
Example usage for using Ax to sweep WER parameters (requires `pip install hydra-ax-sweeper`):
|
| 31 |
-
|
| 32 |
-
```bash
|
| 33 |
-
python $FAIRSEQ_ROOT/examples/speech_recognition/new/infer.py --multirun \
|
| 34 |
-
hydra/sweeper=ax \
|
| 35 |
-
task=audio_pretraining \
|
| 36 |
-
task.data=$data \
|
| 37 |
-
task.labels=ltr \
|
| 38 |
-
common_eval.path=$checkpoint \
|
| 39 |
-
decoding.type=kenlm \
|
| 40 |
-
decoding.lexicon=$lexicon \
|
| 41 |
-
decoding.lmpath=$lm_model \
|
| 42 |
-
dataset.gen_subset=dev_other
|
| 43 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/__init__.py
DELETED
|
File without changes
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/conf/hydra/sweeper/ax.yaml
DELETED
|
@@ -1,29 +0,0 @@
|
|
| 1 |
-
# @package hydra.sweeper
|
| 2 |
-
_target_: hydra_plugins.hydra_ax_sweeper.ax_sweeper.AxSweeper
|
| 3 |
-
max_batch_size: null
|
| 4 |
-
ax_config:
|
| 5 |
-
max_trials: 128
|
| 6 |
-
early_stop:
|
| 7 |
-
minimize: true
|
| 8 |
-
max_epochs_without_improvement: 10
|
| 9 |
-
epsilon: 0.025
|
| 10 |
-
experiment:
|
| 11 |
-
name: ${dataset.gen_subset}
|
| 12 |
-
objective_name: wer
|
| 13 |
-
minimize: true
|
| 14 |
-
parameter_constraints: null
|
| 15 |
-
outcome_constraints: null
|
| 16 |
-
status_quo: null
|
| 17 |
-
client:
|
| 18 |
-
verbose_logging: false
|
| 19 |
-
random_seed: null
|
| 20 |
-
params:
|
| 21 |
-
decoding.lmweight:
|
| 22 |
-
type: range
|
| 23 |
-
bounds: [0.0, 5.0]
|
| 24 |
-
decoding.wordscore:
|
| 25 |
-
type: range
|
| 26 |
-
bounds: [-5.0, 5.0]
|
| 27 |
-
decoding.silweight:
|
| 28 |
-
type: range
|
| 29 |
-
bounds: [ -8.0, 0.0 ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/conf/infer.yaml
DELETED
|
@@ -1,25 +0,0 @@
|
|
| 1 |
-
# @package _group_
|
| 2 |
-
|
| 3 |
-
defaults:
|
| 4 |
-
- task: null
|
| 5 |
-
- model: null
|
| 6 |
-
|
| 7 |
-
hydra:
|
| 8 |
-
run:
|
| 9 |
-
dir: ${common_eval.results_path}/${dataset.gen_subset}
|
| 10 |
-
sweep:
|
| 11 |
-
dir: /checkpoint/${env:USER}/${env:PREFIX}/${common_eval.results_path}
|
| 12 |
-
subdir: ${dataset.gen_subset}
|
| 13 |
-
common_eval:
|
| 14 |
-
results_path: null
|
| 15 |
-
path: null
|
| 16 |
-
post_process: letter
|
| 17 |
-
quiet: true
|
| 18 |
-
dataset:
|
| 19 |
-
max_tokens: 3000000
|
| 20 |
-
gen_subset: test
|
| 21 |
-
distributed_training:
|
| 22 |
-
distributed_world_size: 1
|
| 23 |
-
decoding:
|
| 24 |
-
beam: 5
|
| 25 |
-
type: viterbi
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/__init__.py
DELETED
|
File without changes
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/base_decoder.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
| 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 itertools as it
|
| 7 |
-
from typing import Any, Dict, List
|
| 8 |
-
|
| 9 |
-
import torch
|
| 10 |
-
from fairseq.data.dictionary import Dictionary
|
| 11 |
-
from fairseq.models.fairseq_model import FairseqModel
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
class BaseDecoder:
|
| 15 |
-
def __init__(self, tgt_dict: Dictionary) -> None:
|
| 16 |
-
self.tgt_dict = tgt_dict
|
| 17 |
-
self.vocab_size = len(tgt_dict)
|
| 18 |
-
|
| 19 |
-
self.blank = (
|
| 20 |
-
tgt_dict.index("<ctc_blank>")
|
| 21 |
-
if "<ctc_blank>" in tgt_dict.indices
|
| 22 |
-
else tgt_dict.bos()
|
| 23 |
-
)
|
| 24 |
-
if "<sep>" in tgt_dict.indices:
|
| 25 |
-
self.silence = tgt_dict.index("<sep>")
|
| 26 |
-
elif "|" in tgt_dict.indices:
|
| 27 |
-
self.silence = tgt_dict.index("|")
|
| 28 |
-
else:
|
| 29 |
-
self.silence = tgt_dict.eos()
|
| 30 |
-
|
| 31 |
-
def generate(
|
| 32 |
-
self, models: List[FairseqModel], sample: Dict[str, Any], **unused
|
| 33 |
-
) -> List[List[Dict[str, torch.LongTensor]]]:
|
| 34 |
-
encoder_input = {
|
| 35 |
-
k: v for k, v in sample["net_input"].items() if k != "prev_output_tokens"
|
| 36 |
-
}
|
| 37 |
-
emissions = self.get_emissions(models, encoder_input)
|
| 38 |
-
return self.decode(emissions)
|
| 39 |
-
|
| 40 |
-
def get_emissions(
|
| 41 |
-
self,
|
| 42 |
-
models: List[FairseqModel],
|
| 43 |
-
encoder_input: Dict[str, Any],
|
| 44 |
-
) -> torch.FloatTensor:
|
| 45 |
-
model = models[0]
|
| 46 |
-
encoder_out = model(**encoder_input)
|
| 47 |
-
if hasattr(model, "get_logits"):
|
| 48 |
-
emissions = model.get_logits(encoder_out)
|
| 49 |
-
else:
|
| 50 |
-
emissions = model.get_normalized_probs(encoder_out, log_probs=True)
|
| 51 |
-
return emissions.transpose(0, 1).float().cpu().contiguous()
|
| 52 |
-
|
| 53 |
-
def get_tokens(self, idxs: torch.IntTensor) -> torch.LongTensor:
|
| 54 |
-
idxs = (g[0] for g in it.groupby(idxs))
|
| 55 |
-
idxs = filter(lambda x: x != self.blank, idxs)
|
| 56 |
-
return torch.LongTensor(list(idxs))
|
| 57 |
-
|
| 58 |
-
def decode(
|
| 59 |
-
self,
|
| 60 |
-
emissions: torch.FloatTensor,
|
| 61 |
-
) -> List[List[Dict[str, torch.LongTensor]]]:
|
| 62 |
-
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/decoder.py
DELETED
|
@@ -1,32 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
from typing import Union
|
| 9 |
-
|
| 10 |
-
from fairseq.data.dictionary import Dictionary
|
| 11 |
-
|
| 12 |
-
from .base_decoder import BaseDecoder
|
| 13 |
-
from .decoder_config import DecoderConfig, FlashlightDecoderConfig
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
def Decoder(
|
| 17 |
-
cfg: Union[DecoderConfig, FlashlightDecoderConfig], tgt_dict: Dictionary
|
| 18 |
-
) -> BaseDecoder:
|
| 19 |
-
|
| 20 |
-
if cfg.type == "viterbi":
|
| 21 |
-
from .viterbi_decoder import ViterbiDecoder
|
| 22 |
-
|
| 23 |
-
return ViterbiDecoder(tgt_dict)
|
| 24 |
-
if cfg.type == "kenlm":
|
| 25 |
-
from .flashlight_decoder import KenLMDecoder
|
| 26 |
-
|
| 27 |
-
return KenLMDecoder(cfg, tgt_dict)
|
| 28 |
-
if cfg.type == "fairseqlm":
|
| 29 |
-
from .flashlight_decoder import FairseqLMDecoder
|
| 30 |
-
|
| 31 |
-
return FairseqLMDecoder(cfg, tgt_dict)
|
| 32 |
-
raise NotImplementedError(f"Invalid decoder name: {cfg.name}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/decoder_config.py
DELETED
|
@@ -1,69 +0,0 @@
|
|
| 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 math
|
| 7 |
-
from dataclasses import dataclass, field
|
| 8 |
-
from typing import Optional
|
| 9 |
-
|
| 10 |
-
from fairseq.dataclass.configs import FairseqDataclass
|
| 11 |
-
from fairseq.dataclass.constants import ChoiceEnum
|
| 12 |
-
from omegaconf import MISSING
|
| 13 |
-
|
| 14 |
-
DECODER_CHOICES = ChoiceEnum(["viterbi", "kenlm", "fairseqlm"])
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
@dataclass
|
| 18 |
-
class DecoderConfig(FairseqDataclass):
|
| 19 |
-
type: DECODER_CHOICES = field(
|
| 20 |
-
default="viterbi",
|
| 21 |
-
metadata={"help": "The type of decoder to use"},
|
| 22 |
-
)
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
@dataclass
|
| 26 |
-
class FlashlightDecoderConfig(FairseqDataclass):
|
| 27 |
-
nbest: int = field(
|
| 28 |
-
default=1,
|
| 29 |
-
metadata={"help": "Number of decodings to return"},
|
| 30 |
-
)
|
| 31 |
-
unitlm: bool = field(
|
| 32 |
-
default=False,
|
| 33 |
-
metadata={"help": "If set, use unit language model"},
|
| 34 |
-
)
|
| 35 |
-
lmpath: str = field(
|
| 36 |
-
default=MISSING,
|
| 37 |
-
metadata={"help": "Language model for KenLM decoder"},
|
| 38 |
-
)
|
| 39 |
-
lexicon: Optional[str] = field(
|
| 40 |
-
default=None,
|
| 41 |
-
metadata={"help": "Lexicon for Flashlight decoder"},
|
| 42 |
-
)
|
| 43 |
-
beam: int = field(
|
| 44 |
-
default=50,
|
| 45 |
-
metadata={"help": "Number of beams to use for decoding"},
|
| 46 |
-
)
|
| 47 |
-
beamthreshold: float = field(
|
| 48 |
-
default=50.0,
|
| 49 |
-
metadata={"help": "Threshold for beam search decoding"},
|
| 50 |
-
)
|
| 51 |
-
beamsizetoken: Optional[int] = field(
|
| 52 |
-
default=None, metadata={"help": "Beam size to use"}
|
| 53 |
-
)
|
| 54 |
-
wordscore: float = field(
|
| 55 |
-
default=-1,
|
| 56 |
-
metadata={"help": "Word score for KenLM decoder"},
|
| 57 |
-
)
|
| 58 |
-
unkweight: float = field(
|
| 59 |
-
default=-math.inf,
|
| 60 |
-
metadata={"help": "Unknown weight for KenLM decoder"},
|
| 61 |
-
)
|
| 62 |
-
silweight: float = field(
|
| 63 |
-
default=0,
|
| 64 |
-
metadata={"help": "Silence weight for KenLM decoder"},
|
| 65 |
-
)
|
| 66 |
-
lmweight: float = field(
|
| 67 |
-
default=2,
|
| 68 |
-
metadata={"help": "Weight for LM while interpolating score"},
|
| 69 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/flashlight_decoder.py
DELETED
|
@@ -1,429 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
import gc
|
| 9 |
-
import os.path as osp
|
| 10 |
-
import warnings
|
| 11 |
-
from collections import deque, namedtuple
|
| 12 |
-
from typing import Any, Dict, List, Tuple
|
| 13 |
-
|
| 14 |
-
import numpy as np
|
| 15 |
-
import torch
|
| 16 |
-
from fairseq import tasks
|
| 17 |
-
from fairseq.data.dictionary import Dictionary
|
| 18 |
-
from fairseq.dataclass.utils import convert_namespace_to_omegaconf
|
| 19 |
-
from fairseq.models.fairseq_model import FairseqModel
|
| 20 |
-
from fairseq.utils import apply_to_sample
|
| 21 |
-
from omegaconf import OmegaConf, open_dict
|
| 22 |
-
|
| 23 |
-
from .base_decoder import BaseDecoder
|
| 24 |
-
from .decoder_config import FlashlightDecoderConfig
|
| 25 |
-
|
| 26 |
-
try:
|
| 27 |
-
from flashlight.lib.text.decoder import (
|
| 28 |
-
LM,
|
| 29 |
-
CriterionType,
|
| 30 |
-
DecodeResult,
|
| 31 |
-
KenLM,
|
| 32 |
-
LexiconDecoder,
|
| 33 |
-
LexiconDecoderOptions,
|
| 34 |
-
LexiconFreeDecoder,
|
| 35 |
-
LexiconFreeDecoderOptions,
|
| 36 |
-
LMState,
|
| 37 |
-
SmearingMode,
|
| 38 |
-
Trie,
|
| 39 |
-
)
|
| 40 |
-
from flashlight.lib.text.dictionary import create_word_dict, load_words
|
| 41 |
-
except ImportError:
|
| 42 |
-
warnings.warn(
|
| 43 |
-
"flashlight python bindings are required to use this functionality. "
|
| 44 |
-
"Please install from "
|
| 45 |
-
"https://github.com/facebookresearch/flashlight/tree/master/bindings/python"
|
| 46 |
-
)
|
| 47 |
-
LM = object
|
| 48 |
-
LMState = object
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
class KenLMDecoder(BaseDecoder):
|
| 52 |
-
def __init__(self, cfg: FlashlightDecoderConfig, tgt_dict: Dictionary) -> None:
|
| 53 |
-
super().__init__(tgt_dict)
|
| 54 |
-
|
| 55 |
-
self.nbest = cfg.nbest
|
| 56 |
-
self.unitlm = cfg.unitlm
|
| 57 |
-
|
| 58 |
-
if cfg.lexicon:
|
| 59 |
-
self.lexicon = load_words(cfg.lexicon)
|
| 60 |
-
self.word_dict = create_word_dict(self.lexicon)
|
| 61 |
-
self.unk_word = self.word_dict.get_index("<unk>")
|
| 62 |
-
|
| 63 |
-
self.lm = KenLM(cfg.lmpath, self.word_dict)
|
| 64 |
-
self.trie = Trie(self.vocab_size, self.silence)
|
| 65 |
-
|
| 66 |
-
start_state = self.lm.start(False)
|
| 67 |
-
for word, spellings in self.lexicon.items():
|
| 68 |
-
word_idx = self.word_dict.get_index(word)
|
| 69 |
-
_, score = self.lm.score(start_state, word_idx)
|
| 70 |
-
for spelling in spellings:
|
| 71 |
-
spelling_idxs = [tgt_dict.index(token) for token in spelling]
|
| 72 |
-
assert (
|
| 73 |
-
tgt_dict.unk() not in spelling_idxs
|
| 74 |
-
), f"{word} {spelling} {spelling_idxs}"
|
| 75 |
-
self.trie.insert(spelling_idxs, word_idx, score)
|
| 76 |
-
self.trie.smear(SmearingMode.MAX)
|
| 77 |
-
|
| 78 |
-
self.decoder_opts = LexiconDecoderOptions(
|
| 79 |
-
beam_size=cfg.beam,
|
| 80 |
-
beam_size_token=cfg.beamsizetoken or len(tgt_dict),
|
| 81 |
-
beam_threshold=cfg.beamthreshold,
|
| 82 |
-
lm_weight=cfg.lmweight,
|
| 83 |
-
word_score=cfg.wordscore,
|
| 84 |
-
unk_score=cfg.unkweight,
|
| 85 |
-
sil_score=cfg.silweight,
|
| 86 |
-
log_add=False,
|
| 87 |
-
criterion_type=CriterionType.CTC,
|
| 88 |
-
)
|
| 89 |
-
|
| 90 |
-
self.decoder = LexiconDecoder(
|
| 91 |
-
self.decoder_opts,
|
| 92 |
-
self.trie,
|
| 93 |
-
self.lm,
|
| 94 |
-
self.silence,
|
| 95 |
-
self.blank,
|
| 96 |
-
self.unk_word,
|
| 97 |
-
[],
|
| 98 |
-
self.unitlm,
|
| 99 |
-
)
|
| 100 |
-
else:
|
| 101 |
-
assert self.unitlm, "Lexicon-free decoding requires unit LM"
|
| 102 |
-
|
| 103 |
-
d = {w: [[w]] for w in tgt_dict.symbols}
|
| 104 |
-
self.word_dict = create_word_dict(d)
|
| 105 |
-
self.lm = KenLM(cfg.lmpath, self.word_dict)
|
| 106 |
-
self.decoder_opts = LexiconFreeDecoderOptions(
|
| 107 |
-
beam_size=cfg.beam,
|
| 108 |
-
beam_size_token=cfg.beamsizetoken or len(tgt_dict),
|
| 109 |
-
beam_threshold=cfg.beamthreshold,
|
| 110 |
-
lm_weight=cfg.lmweight,
|
| 111 |
-
sil_score=cfg.silweight,
|
| 112 |
-
log_add=False,
|
| 113 |
-
criterion_type=CriterionType.CTC,
|
| 114 |
-
)
|
| 115 |
-
self.decoder = LexiconFreeDecoder(
|
| 116 |
-
self.decoder_opts, self.lm, self.silence, self.blank, []
|
| 117 |
-
)
|
| 118 |
-
|
| 119 |
-
def get_timesteps(self, token_idxs: List[int]) -> List[int]:
|
| 120 |
-
"""Returns frame numbers corresponding to every non-blank token.
|
| 121 |
-
|
| 122 |
-
Parameters
|
| 123 |
-
----------
|
| 124 |
-
token_idxs : List[int]
|
| 125 |
-
IDs of decoded tokens.
|
| 126 |
-
|
| 127 |
-
Returns
|
| 128 |
-
-------
|
| 129 |
-
List[int]
|
| 130 |
-
Frame numbers corresponding to every non-blank token.
|
| 131 |
-
"""
|
| 132 |
-
timesteps = []
|
| 133 |
-
for i, token_idx in enumerate(token_idxs):
|
| 134 |
-
if token_idx == self.blank:
|
| 135 |
-
continue
|
| 136 |
-
if i == 0 or token_idx != token_idxs[i-1]:
|
| 137 |
-
timesteps.append(i)
|
| 138 |
-
return timesteps
|
| 139 |
-
|
| 140 |
-
def decode(
|
| 141 |
-
self,
|
| 142 |
-
emissions: torch.FloatTensor,
|
| 143 |
-
) -> List[List[Dict[str, torch.LongTensor]]]:
|
| 144 |
-
B, T, N = emissions.size()
|
| 145 |
-
hypos = []
|
| 146 |
-
for b in range(B):
|
| 147 |
-
emissions_ptr = emissions.data_ptr() + 4 * b * emissions.stride(0)
|
| 148 |
-
results = self.decoder.decode(emissions_ptr, T, N)
|
| 149 |
-
|
| 150 |
-
nbest_results = results[: self.nbest]
|
| 151 |
-
hypos.append(
|
| 152 |
-
[
|
| 153 |
-
{
|
| 154 |
-
"tokens": self.get_tokens(result.tokens),
|
| 155 |
-
"score": result.score,
|
| 156 |
-
"timesteps": self.get_timesteps(result.tokens),
|
| 157 |
-
"words": [
|
| 158 |
-
self.word_dict.get_entry(x) for x in result.words if x >= 0
|
| 159 |
-
],
|
| 160 |
-
}
|
| 161 |
-
for result in nbest_results
|
| 162 |
-
]
|
| 163 |
-
)
|
| 164 |
-
return hypos
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
FairseqLMState = namedtuple(
|
| 168 |
-
"FairseqLMState",
|
| 169 |
-
[
|
| 170 |
-
"prefix",
|
| 171 |
-
"incremental_state",
|
| 172 |
-
"probs",
|
| 173 |
-
],
|
| 174 |
-
)
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
class FairseqLM(LM):
|
| 178 |
-
def __init__(self, dictionary: Dictionary, model: FairseqModel) -> None:
|
| 179 |
-
super().__init__()
|
| 180 |
-
|
| 181 |
-
self.dictionary = dictionary
|
| 182 |
-
self.model = model
|
| 183 |
-
self.unk = self.dictionary.unk()
|
| 184 |
-
|
| 185 |
-
self.save_incremental = False # this currently does not work properly
|
| 186 |
-
self.max_cache = 20_000
|
| 187 |
-
|
| 188 |
-
if torch.cuda.is_available():
|
| 189 |
-
model.cuda()
|
| 190 |
-
model.eval()
|
| 191 |
-
model.make_generation_fast_()
|
| 192 |
-
|
| 193 |
-
self.states = {}
|
| 194 |
-
self.stateq = deque()
|
| 195 |
-
|
| 196 |
-
def start(self, start_with_nothing: bool) -> LMState:
|
| 197 |
-
state = LMState()
|
| 198 |
-
prefix = torch.LongTensor([[self.dictionary.eos()]])
|
| 199 |
-
incremental_state = {} if self.save_incremental else None
|
| 200 |
-
with torch.no_grad():
|
| 201 |
-
res = self.model(prefix.cuda(), incremental_state=incremental_state)
|
| 202 |
-
probs = self.model.get_normalized_probs(res, log_probs=True, sample=None)
|
| 203 |
-
|
| 204 |
-
if incremental_state is not None:
|
| 205 |
-
incremental_state = apply_to_sample(lambda x: x.cpu(), incremental_state)
|
| 206 |
-
self.states[state] = FairseqLMState(
|
| 207 |
-
prefix.numpy(), incremental_state, probs[0, -1].cpu().numpy()
|
| 208 |
-
)
|
| 209 |
-
self.stateq.append(state)
|
| 210 |
-
|
| 211 |
-
return state
|
| 212 |
-
|
| 213 |
-
def score(
|
| 214 |
-
self,
|
| 215 |
-
state: LMState,
|
| 216 |
-
token_index: int,
|
| 217 |
-
no_cache: bool = False,
|
| 218 |
-
) -> Tuple[LMState, int]:
|
| 219 |
-
"""
|
| 220 |
-
Evaluate language model based on the current lm state and new word
|
| 221 |
-
Parameters:
|
| 222 |
-
-----------
|
| 223 |
-
state: current lm state
|
| 224 |
-
token_index: index of the word
|
| 225 |
-
(can be lexicon index then you should store inside LM the
|
| 226 |
-
mapping between indices of lexicon and lm, or lm index of a word)
|
| 227 |
-
Returns:
|
| 228 |
-
--------
|
| 229 |
-
(LMState, float): pair of (new state, score for the current word)
|
| 230 |
-
"""
|
| 231 |
-
curr_state = self.states[state]
|
| 232 |
-
|
| 233 |
-
def trim_cache(targ_size: int) -> None:
|
| 234 |
-
while len(self.stateq) > targ_size:
|
| 235 |
-
rem_k = self.stateq.popleft()
|
| 236 |
-
rem_st = self.states[rem_k]
|
| 237 |
-
rem_st = FairseqLMState(rem_st.prefix, None, None)
|
| 238 |
-
self.states[rem_k] = rem_st
|
| 239 |
-
|
| 240 |
-
if curr_state.probs is None:
|
| 241 |
-
new_incremental_state = (
|
| 242 |
-
curr_state.incremental_state.copy()
|
| 243 |
-
if curr_state.incremental_state is not None
|
| 244 |
-
else None
|
| 245 |
-
)
|
| 246 |
-
with torch.no_grad():
|
| 247 |
-
if new_incremental_state is not None:
|
| 248 |
-
new_incremental_state = apply_to_sample(
|
| 249 |
-
lambda x: x.cuda(), new_incremental_state
|
| 250 |
-
)
|
| 251 |
-
elif self.save_incremental:
|
| 252 |
-
new_incremental_state = {}
|
| 253 |
-
|
| 254 |
-
res = self.model(
|
| 255 |
-
torch.from_numpy(curr_state.prefix).cuda(),
|
| 256 |
-
incremental_state=new_incremental_state,
|
| 257 |
-
)
|
| 258 |
-
probs = self.model.get_normalized_probs(
|
| 259 |
-
res, log_probs=True, sample=None
|
| 260 |
-
)
|
| 261 |
-
|
| 262 |
-
if new_incremental_state is not None:
|
| 263 |
-
new_incremental_state = apply_to_sample(
|
| 264 |
-
lambda x: x.cpu(), new_incremental_state
|
| 265 |
-
)
|
| 266 |
-
|
| 267 |
-
curr_state = FairseqLMState(
|
| 268 |
-
curr_state.prefix, new_incremental_state, probs[0, -1].cpu().numpy()
|
| 269 |
-
)
|
| 270 |
-
|
| 271 |
-
if not no_cache:
|
| 272 |
-
self.states[state] = curr_state
|
| 273 |
-
self.stateq.append(state)
|
| 274 |
-
|
| 275 |
-
score = curr_state.probs[token_index].item()
|
| 276 |
-
|
| 277 |
-
trim_cache(self.max_cache)
|
| 278 |
-
|
| 279 |
-
outstate = state.child(token_index)
|
| 280 |
-
if outstate not in self.states and not no_cache:
|
| 281 |
-
prefix = np.concatenate(
|
| 282 |
-
[curr_state.prefix, torch.LongTensor([[token_index]])], -1
|
| 283 |
-
)
|
| 284 |
-
incr_state = curr_state.incremental_state
|
| 285 |
-
|
| 286 |
-
self.states[outstate] = FairseqLMState(prefix, incr_state, None)
|
| 287 |
-
|
| 288 |
-
if token_index == self.unk:
|
| 289 |
-
score = float("-inf")
|
| 290 |
-
|
| 291 |
-
return outstate, score
|
| 292 |
-
|
| 293 |
-
def finish(self, state: LMState) -> Tuple[LMState, int]:
|
| 294 |
-
"""
|
| 295 |
-
Evaluate eos for language model based on the current lm state
|
| 296 |
-
Returns:
|
| 297 |
-
--------
|
| 298 |
-
(LMState, float): pair of (new state, score for the current word)
|
| 299 |
-
"""
|
| 300 |
-
return self.score(state, self.dictionary.eos())
|
| 301 |
-
|
| 302 |
-
def empty_cache(self) -> None:
|
| 303 |
-
self.states = {}
|
| 304 |
-
self.stateq = deque()
|
| 305 |
-
gc.collect()
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
class FairseqLMDecoder(BaseDecoder):
|
| 309 |
-
def __init__(self, cfg: FlashlightDecoderConfig, tgt_dict: Dictionary) -> None:
|
| 310 |
-
super().__init__(tgt_dict)
|
| 311 |
-
|
| 312 |
-
self.nbest = cfg.nbest
|
| 313 |
-
self.unitlm = cfg.unitlm
|
| 314 |
-
|
| 315 |
-
self.lexicon = load_words(cfg.lexicon) if cfg.lexicon else None
|
| 316 |
-
self.idx_to_wrd = {}
|
| 317 |
-
|
| 318 |
-
checkpoint = torch.load(cfg.lmpath, map_location="cpu")
|
| 319 |
-
|
| 320 |
-
if "cfg" in checkpoint and checkpoint["cfg"] is not None:
|
| 321 |
-
lm_args = checkpoint["cfg"]
|
| 322 |
-
else:
|
| 323 |
-
lm_args = convert_namespace_to_omegaconf(checkpoint["args"])
|
| 324 |
-
|
| 325 |
-
if not OmegaConf.is_dict(lm_args):
|
| 326 |
-
lm_args = OmegaConf.create(lm_args)
|
| 327 |
-
|
| 328 |
-
with open_dict(lm_args.task):
|
| 329 |
-
lm_args.task.data = osp.dirname(cfg.lmpath)
|
| 330 |
-
|
| 331 |
-
task = tasks.setup_task(lm_args.task)
|
| 332 |
-
model = task.build_model(lm_args.model)
|
| 333 |
-
model.load_state_dict(checkpoint["model"], strict=False)
|
| 334 |
-
|
| 335 |
-
self.trie = Trie(self.vocab_size, self.silence)
|
| 336 |
-
|
| 337 |
-
self.word_dict = task.dictionary
|
| 338 |
-
self.unk_word = self.word_dict.unk()
|
| 339 |
-
self.lm = FairseqLM(self.word_dict, model)
|
| 340 |
-
|
| 341 |
-
if self.lexicon:
|
| 342 |
-
start_state = self.lm.start(False)
|
| 343 |
-
for i, (word, spellings) in enumerate(self.lexicon.items()):
|
| 344 |
-
if self.unitlm:
|
| 345 |
-
word_idx = i
|
| 346 |
-
self.idx_to_wrd[i] = word
|
| 347 |
-
score = 0
|
| 348 |
-
else:
|
| 349 |
-
word_idx = self.word_dict.index(word)
|
| 350 |
-
_, score = self.lm.score(start_state, word_idx, no_cache=True)
|
| 351 |
-
|
| 352 |
-
for spelling in spellings:
|
| 353 |
-
spelling_idxs = [tgt_dict.index(token) for token in spelling]
|
| 354 |
-
assert (
|
| 355 |
-
tgt_dict.unk() not in spelling_idxs
|
| 356 |
-
), f"{spelling} {spelling_idxs}"
|
| 357 |
-
self.trie.insert(spelling_idxs, word_idx, score)
|
| 358 |
-
self.trie.smear(SmearingMode.MAX)
|
| 359 |
-
|
| 360 |
-
self.decoder_opts = LexiconDecoderOptions(
|
| 361 |
-
beam_size=cfg.beam,
|
| 362 |
-
beam_size_token=cfg.beamsizetoken or len(tgt_dict),
|
| 363 |
-
beam_threshold=cfg.beamthreshold,
|
| 364 |
-
lm_weight=cfg.lmweight,
|
| 365 |
-
word_score=cfg.wordscore,
|
| 366 |
-
unk_score=cfg.unkweight,
|
| 367 |
-
sil_score=cfg.silweight,
|
| 368 |
-
log_add=False,
|
| 369 |
-
criterion_type=CriterionType.CTC,
|
| 370 |
-
)
|
| 371 |
-
|
| 372 |
-
self.decoder = LexiconDecoder(
|
| 373 |
-
self.decoder_opts,
|
| 374 |
-
self.trie,
|
| 375 |
-
self.lm,
|
| 376 |
-
self.silence,
|
| 377 |
-
self.blank,
|
| 378 |
-
self.unk_word,
|
| 379 |
-
[],
|
| 380 |
-
self.unitlm,
|
| 381 |
-
)
|
| 382 |
-
else:
|
| 383 |
-
assert self.unitlm, "Lexicon-free decoding requires unit LM"
|
| 384 |
-
|
| 385 |
-
d = {w: [[w]] for w in tgt_dict.symbols}
|
| 386 |
-
self.word_dict = create_word_dict(d)
|
| 387 |
-
self.lm = KenLM(cfg.lmpath, self.word_dict)
|
| 388 |
-
self.decoder_opts = LexiconFreeDecoderOptions(
|
| 389 |
-
beam_size=cfg.beam,
|
| 390 |
-
beam_size_token=cfg.beamsizetoken or len(tgt_dict),
|
| 391 |
-
beam_threshold=cfg.beamthreshold,
|
| 392 |
-
lm_weight=cfg.lmweight,
|
| 393 |
-
sil_score=cfg.silweight,
|
| 394 |
-
log_add=False,
|
| 395 |
-
criterion_type=CriterionType.CTC,
|
| 396 |
-
)
|
| 397 |
-
self.decoder = LexiconFreeDecoder(
|
| 398 |
-
self.decoder_opts, self.lm, self.silence, self.blank, []
|
| 399 |
-
)
|
| 400 |
-
|
| 401 |
-
def decode(
|
| 402 |
-
self,
|
| 403 |
-
emissions: torch.FloatTensor,
|
| 404 |
-
) -> List[List[Dict[str, torch.LongTensor]]]:
|
| 405 |
-
B, T, N = emissions.size()
|
| 406 |
-
hypos = []
|
| 407 |
-
|
| 408 |
-
def make_hypo(result: DecodeResult) -> Dict[str, Any]:
|
| 409 |
-
hypo = {
|
| 410 |
-
"tokens": self.get_tokens(result.tokens),
|
| 411 |
-
"score": result.score,
|
| 412 |
-
}
|
| 413 |
-
if self.lexicon:
|
| 414 |
-
hypo["words"] = [
|
| 415 |
-
self.idx_to_wrd[x] if self.unitlm else self.word_dict[x]
|
| 416 |
-
for x in result.words
|
| 417 |
-
if x >= 0
|
| 418 |
-
]
|
| 419 |
-
return hypo
|
| 420 |
-
|
| 421 |
-
for b in range(B):
|
| 422 |
-
emissions_ptr = emissions.data_ptr() + 4 * b * emissions.stride(0)
|
| 423 |
-
results = self.decoder.decode(emissions_ptr, T, N)
|
| 424 |
-
|
| 425 |
-
nbest_results = results[: self.nbest]
|
| 426 |
-
hypos.append([make_hypo(result) for result in nbest_results])
|
| 427 |
-
self.lm.empty_cache()
|
| 428 |
-
|
| 429 |
-
return hypos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/decoders/viterbi_decoder.py
DELETED
|
@@ -1,24 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
from typing import Dict, List
|
| 9 |
-
|
| 10 |
-
import torch
|
| 11 |
-
|
| 12 |
-
from .base_decoder import BaseDecoder
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
class ViterbiDecoder(BaseDecoder):
|
| 16 |
-
def decode(
|
| 17 |
-
self,
|
| 18 |
-
emissions: torch.FloatTensor,
|
| 19 |
-
) -> List[List[Dict[str, torch.LongTensor]]]:
|
| 20 |
-
def get_pred(e):
|
| 21 |
-
toks = e.argmax(dim=-1).unique_consecutive()
|
| 22 |
-
return toks[toks != self.blank]
|
| 23 |
-
|
| 24 |
-
return [[{"tokens": get_pred(x), "score": 0}] for x in emissions]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/new/infer.py
DELETED
|
@@ -1,474 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python -u
|
| 2 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 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 |
-
import ast
|
| 8 |
-
import hashlib
|
| 9 |
-
import logging
|
| 10 |
-
import os
|
| 11 |
-
import shutil
|
| 12 |
-
import sys
|
| 13 |
-
from dataclasses import dataclass, field, is_dataclass
|
| 14 |
-
from pathlib import Path
|
| 15 |
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
| 16 |
-
|
| 17 |
-
import editdistance
|
| 18 |
-
import hydra
|
| 19 |
-
import torch
|
| 20 |
-
import torch.distributed as dist
|
| 21 |
-
from examples.speech_recognition.new.decoders.decoder import Decoder
|
| 22 |
-
from examples.speech_recognition.new.decoders.decoder_config import (
|
| 23 |
-
DecoderConfig,
|
| 24 |
-
FlashlightDecoderConfig,
|
| 25 |
-
)
|
| 26 |
-
from fairseq import checkpoint_utils, distributed_utils, progress_bar, tasks, utils
|
| 27 |
-
from fairseq.data.data_utils import post_process
|
| 28 |
-
from fairseq.dataclass.configs import (
|
| 29 |
-
CheckpointConfig,
|
| 30 |
-
CommonConfig,
|
| 31 |
-
CommonEvalConfig,
|
| 32 |
-
DatasetConfig,
|
| 33 |
-
DistributedTrainingConfig,
|
| 34 |
-
FairseqDataclass,
|
| 35 |
-
)
|
| 36 |
-
from fairseq.logging.meters import StopwatchMeter, TimeMeter
|
| 37 |
-
from fairseq.logging.progress_bar import BaseProgressBar
|
| 38 |
-
from fairseq.models.fairseq_model import FairseqModel
|
| 39 |
-
from hydra.core.config_store import ConfigStore
|
| 40 |
-
from omegaconf import OmegaConf
|
| 41 |
-
|
| 42 |
-
logging.root.setLevel(logging.INFO)
|
| 43 |
-
logging.basicConfig(level=logging.INFO)
|
| 44 |
-
logger = logging.getLogger(__name__)
|
| 45 |
-
|
| 46 |
-
config_path = Path(__file__).resolve().parent / "conf"
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
@dataclass
|
| 50 |
-
class DecodingConfig(DecoderConfig, FlashlightDecoderConfig):
|
| 51 |
-
unique_wer_file: bool = field(
|
| 52 |
-
default=False,
|
| 53 |
-
metadata={"help": "If set, use a unique file for storing WER"},
|
| 54 |
-
)
|
| 55 |
-
results_path: Optional[str] = field(
|
| 56 |
-
default=None,
|
| 57 |
-
metadata={
|
| 58 |
-
"help": "If set, write hypothesis and reference sentences into this directory"
|
| 59 |
-
},
|
| 60 |
-
)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
@dataclass
|
| 64 |
-
class InferConfig(FairseqDataclass):
|
| 65 |
-
task: Any = None
|
| 66 |
-
decoding: DecodingConfig = DecodingConfig()
|
| 67 |
-
common: CommonConfig = CommonConfig()
|
| 68 |
-
common_eval: CommonEvalConfig = CommonEvalConfig()
|
| 69 |
-
checkpoint: CheckpointConfig = CheckpointConfig()
|
| 70 |
-
distributed_training: DistributedTrainingConfig = DistributedTrainingConfig()
|
| 71 |
-
dataset: DatasetConfig = DatasetConfig()
|
| 72 |
-
is_ax: bool = field(
|
| 73 |
-
default=False,
|
| 74 |
-
metadata={
|
| 75 |
-
"help": "if true, assumes we are using ax for tuning and returns a tuple for ax to consume"
|
| 76 |
-
},
|
| 77 |
-
)
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
def reset_logging():
|
| 81 |
-
root = logging.getLogger()
|
| 82 |
-
for handler in root.handlers:
|
| 83 |
-
root.removeHandler(handler)
|
| 84 |
-
root.setLevel(os.environ.get("LOGLEVEL", "INFO").upper())
|
| 85 |
-
handler = logging.StreamHandler(sys.stdout)
|
| 86 |
-
handler.setFormatter(
|
| 87 |
-
logging.Formatter(
|
| 88 |
-
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
| 89 |
-
datefmt="%Y-%m-%d %H:%M:%S",
|
| 90 |
-
)
|
| 91 |
-
)
|
| 92 |
-
root.addHandler(handler)
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
class InferenceProcessor:
|
| 96 |
-
cfg: InferConfig
|
| 97 |
-
|
| 98 |
-
def __init__(self, cfg: InferConfig) -> None:
|
| 99 |
-
self.cfg = cfg
|
| 100 |
-
self.task = tasks.setup_task(cfg.task)
|
| 101 |
-
|
| 102 |
-
models, saved_cfg = self.load_model_ensemble()
|
| 103 |
-
self.models = models
|
| 104 |
-
self.saved_cfg = saved_cfg
|
| 105 |
-
self.tgt_dict = self.task.target_dictionary
|
| 106 |
-
|
| 107 |
-
self.task.load_dataset(
|
| 108 |
-
self.cfg.dataset.gen_subset,
|
| 109 |
-
task_cfg=saved_cfg.task,
|
| 110 |
-
)
|
| 111 |
-
self.generator = Decoder(cfg.decoding, self.tgt_dict)
|
| 112 |
-
self.gen_timer = StopwatchMeter()
|
| 113 |
-
self.wps_meter = TimeMeter()
|
| 114 |
-
self.num_sentences = 0
|
| 115 |
-
self.total_errors = 0
|
| 116 |
-
self.total_length = 0
|
| 117 |
-
|
| 118 |
-
self.hypo_words_file = None
|
| 119 |
-
self.hypo_units_file = None
|
| 120 |
-
self.ref_words_file = None
|
| 121 |
-
self.ref_units_file = None
|
| 122 |
-
|
| 123 |
-
self.progress_bar = self.build_progress_bar()
|
| 124 |
-
|
| 125 |
-
def __enter__(self) -> "InferenceProcessor":
|
| 126 |
-
if self.cfg.decoding.results_path is not None:
|
| 127 |
-
self.hypo_words_file = self.get_res_file("hypo.word")
|
| 128 |
-
self.hypo_units_file = self.get_res_file("hypo.units")
|
| 129 |
-
self.ref_words_file = self.get_res_file("ref.word")
|
| 130 |
-
self.ref_units_file = self.get_res_file("ref.units")
|
| 131 |
-
return self
|
| 132 |
-
|
| 133 |
-
def __exit__(self, *exc) -> bool:
|
| 134 |
-
if self.cfg.decoding.results_path is not None:
|
| 135 |
-
self.hypo_words_file.close()
|
| 136 |
-
self.hypo_units_file.close()
|
| 137 |
-
self.ref_words_file.close()
|
| 138 |
-
self.ref_units_file.close()
|
| 139 |
-
return False
|
| 140 |
-
|
| 141 |
-
def __iter__(self) -> Any:
|
| 142 |
-
for sample in self.progress_bar:
|
| 143 |
-
if not self.cfg.common.cpu:
|
| 144 |
-
sample = utils.move_to_cuda(sample)
|
| 145 |
-
|
| 146 |
-
# Happens on the last batch.
|
| 147 |
-
if "net_input" not in sample:
|
| 148 |
-
continue
|
| 149 |
-
yield sample
|
| 150 |
-
|
| 151 |
-
def log(self, *args, **kwargs):
|
| 152 |
-
self.progress_bar.log(*args, **kwargs)
|
| 153 |
-
|
| 154 |
-
def print(self, *args, **kwargs):
|
| 155 |
-
self.progress_bar.print(*args, **kwargs)
|
| 156 |
-
|
| 157 |
-
def get_res_file(self, fname: str) -> None:
|
| 158 |
-
fname = os.path.join(self.cfg.decoding.results_path, fname)
|
| 159 |
-
if self.data_parallel_world_size > 1:
|
| 160 |
-
fname = f"{fname}.{self.data_parallel_rank}"
|
| 161 |
-
return open(fname, "w", buffering=1)
|
| 162 |
-
|
| 163 |
-
def merge_shards(self) -> None:
|
| 164 |
-
"""Merges all shard files into shard 0, then removes shard suffix."""
|
| 165 |
-
|
| 166 |
-
shard_id = self.data_parallel_rank
|
| 167 |
-
num_shards = self.data_parallel_world_size
|
| 168 |
-
|
| 169 |
-
if self.data_parallel_world_size > 1:
|
| 170 |
-
|
| 171 |
-
def merge_shards_with_root(fname: str) -> None:
|
| 172 |
-
fname = os.path.join(self.cfg.decoding.results_path, fname)
|
| 173 |
-
logger.info("Merging %s on shard %d", fname, shard_id)
|
| 174 |
-
base_fpath = Path(f"{fname}.0")
|
| 175 |
-
with open(base_fpath, "a") as out_file:
|
| 176 |
-
for s in range(1, num_shards):
|
| 177 |
-
shard_fpath = Path(f"{fname}.{s}")
|
| 178 |
-
with open(shard_fpath, "r") as in_file:
|
| 179 |
-
for line in in_file:
|
| 180 |
-
out_file.write(line)
|
| 181 |
-
shard_fpath.unlink()
|
| 182 |
-
shutil.move(f"{fname}.0", fname)
|
| 183 |
-
|
| 184 |
-
dist.barrier() # ensure all shards finished writing
|
| 185 |
-
if shard_id == (0 % num_shards):
|
| 186 |
-
merge_shards_with_root("hypo.word")
|
| 187 |
-
if shard_id == (1 % num_shards):
|
| 188 |
-
merge_shards_with_root("hypo.units")
|
| 189 |
-
if shard_id == (2 % num_shards):
|
| 190 |
-
merge_shards_with_root("ref.word")
|
| 191 |
-
if shard_id == (3 % num_shards):
|
| 192 |
-
merge_shards_with_root("ref.units")
|
| 193 |
-
dist.barrier()
|
| 194 |
-
|
| 195 |
-
def optimize_model(self, model: FairseqModel) -> None:
|
| 196 |
-
model.make_generation_fast_()
|
| 197 |
-
if self.cfg.common.fp16:
|
| 198 |
-
model.half()
|
| 199 |
-
if not self.cfg.common.cpu:
|
| 200 |
-
model.cuda()
|
| 201 |
-
|
| 202 |
-
def load_model_ensemble(self) -> Tuple[List[FairseqModel], FairseqDataclass]:
|
| 203 |
-
arg_overrides = ast.literal_eval(self.cfg.common_eval.model_overrides)
|
| 204 |
-
models, saved_cfg = checkpoint_utils.load_model_ensemble(
|
| 205 |
-
utils.split_paths(self.cfg.common_eval.path, separator="\\"),
|
| 206 |
-
arg_overrides=arg_overrides,
|
| 207 |
-
task=self.task,
|
| 208 |
-
suffix=self.cfg.checkpoint.checkpoint_suffix,
|
| 209 |
-
strict=(self.cfg.checkpoint.checkpoint_shard_count == 1),
|
| 210 |
-
num_shards=self.cfg.checkpoint.checkpoint_shard_count,
|
| 211 |
-
)
|
| 212 |
-
for model in models:
|
| 213 |
-
self.optimize_model(model)
|
| 214 |
-
return models, saved_cfg
|
| 215 |
-
|
| 216 |
-
def get_dataset_itr(self, disable_iterator_cache: bool = False) -> None:
|
| 217 |
-
return self.task.get_batch_iterator(
|
| 218 |
-
dataset=self.task.dataset(self.cfg.dataset.gen_subset),
|
| 219 |
-
max_tokens=self.cfg.dataset.max_tokens,
|
| 220 |
-
max_sentences=self.cfg.dataset.batch_size,
|
| 221 |
-
max_positions=(sys.maxsize, sys.maxsize),
|
| 222 |
-
ignore_invalid_inputs=self.cfg.dataset.skip_invalid_size_inputs_valid_test,
|
| 223 |
-
required_batch_size_multiple=self.cfg.dataset.required_batch_size_multiple,
|
| 224 |
-
seed=self.cfg.common.seed,
|
| 225 |
-
num_shards=self.data_parallel_world_size,
|
| 226 |
-
shard_id=self.data_parallel_rank,
|
| 227 |
-
num_workers=self.cfg.dataset.num_workers,
|
| 228 |
-
data_buffer_size=self.cfg.dataset.data_buffer_size,
|
| 229 |
-
disable_iterator_cache=disable_iterator_cache,
|
| 230 |
-
).next_epoch_itr(shuffle=False)
|
| 231 |
-
|
| 232 |
-
def build_progress_bar(
|
| 233 |
-
self,
|
| 234 |
-
epoch: Optional[int] = None,
|
| 235 |
-
prefix: Optional[str] = None,
|
| 236 |
-
default_log_format: str = "tqdm",
|
| 237 |
-
) -> BaseProgressBar:
|
| 238 |
-
return progress_bar.progress_bar(
|
| 239 |
-
iterator=self.get_dataset_itr(),
|
| 240 |
-
log_format=self.cfg.common.log_format,
|
| 241 |
-
log_interval=self.cfg.common.log_interval,
|
| 242 |
-
epoch=epoch,
|
| 243 |
-
prefix=prefix,
|
| 244 |
-
tensorboard_logdir=self.cfg.common.tensorboard_logdir,
|
| 245 |
-
default_log_format=default_log_format,
|
| 246 |
-
)
|
| 247 |
-
|
| 248 |
-
@property
|
| 249 |
-
def data_parallel_world_size(self):
|
| 250 |
-
if self.cfg.distributed_training.distributed_world_size == 1:
|
| 251 |
-
return 1
|
| 252 |
-
return distributed_utils.get_data_parallel_world_size()
|
| 253 |
-
|
| 254 |
-
@property
|
| 255 |
-
def data_parallel_rank(self):
|
| 256 |
-
if self.cfg.distributed_training.distributed_world_size == 1:
|
| 257 |
-
return 0
|
| 258 |
-
return distributed_utils.get_data_parallel_rank()
|
| 259 |
-
|
| 260 |
-
def process_sentence(
|
| 261 |
-
self,
|
| 262 |
-
sample: Dict[str, Any],
|
| 263 |
-
hypo: Dict[str, Any],
|
| 264 |
-
sid: int,
|
| 265 |
-
batch_id: int,
|
| 266 |
-
) -> Tuple[int, int]:
|
| 267 |
-
speaker = None # Speaker can't be parsed from dataset.
|
| 268 |
-
|
| 269 |
-
if "target_label" in sample:
|
| 270 |
-
toks = sample["target_label"]
|
| 271 |
-
else:
|
| 272 |
-
toks = sample["target"]
|
| 273 |
-
toks = toks[batch_id, :]
|
| 274 |
-
|
| 275 |
-
# Processes hypothesis.
|
| 276 |
-
hyp_pieces = self.tgt_dict.string(hypo["tokens"].int().cpu())
|
| 277 |
-
if "words" in hypo:
|
| 278 |
-
hyp_words = " ".join(hypo["words"])
|
| 279 |
-
else:
|
| 280 |
-
hyp_words = post_process(hyp_pieces, self.cfg.common_eval.post_process)
|
| 281 |
-
|
| 282 |
-
# Processes target.
|
| 283 |
-
target_tokens = utils.strip_pad(toks, self.tgt_dict.pad())
|
| 284 |
-
tgt_pieces = self.tgt_dict.string(target_tokens.int().cpu())
|
| 285 |
-
tgt_words = post_process(tgt_pieces, self.cfg.common_eval.post_process)
|
| 286 |
-
|
| 287 |
-
if self.cfg.decoding.results_path is not None:
|
| 288 |
-
print(f"{hyp_pieces} ({speaker}-{sid})", file=self.hypo_units_file)
|
| 289 |
-
print(f"{hyp_words} ({speaker}-{sid})", file=self.hypo_words_file)
|
| 290 |
-
print(f"{tgt_pieces} ({speaker}-{sid})", file=self.ref_units_file)
|
| 291 |
-
print(f"{tgt_words} ({speaker}-{sid})", file=self.ref_words_file)
|
| 292 |
-
|
| 293 |
-
if not self.cfg.common_eval.quiet:
|
| 294 |
-
logger.info(f"HYPO: {hyp_words}")
|
| 295 |
-
logger.info(f"REF: {tgt_words}")
|
| 296 |
-
logger.info("---------------------")
|
| 297 |
-
|
| 298 |
-
hyp_words, tgt_words = hyp_words.split(), tgt_words.split()
|
| 299 |
-
|
| 300 |
-
return editdistance.eval(hyp_words, tgt_words), len(tgt_words)
|
| 301 |
-
|
| 302 |
-
def process_sample(self, sample: Dict[str, Any]) -> None:
|
| 303 |
-
self.gen_timer.start()
|
| 304 |
-
hypos = self.task.inference_step(
|
| 305 |
-
generator=self.generator,
|
| 306 |
-
models=self.models,
|
| 307 |
-
sample=sample,
|
| 308 |
-
)
|
| 309 |
-
num_generated_tokens = sum(len(h[0]["tokens"]) for h in hypos)
|
| 310 |
-
self.gen_timer.stop(num_generated_tokens)
|
| 311 |
-
self.wps_meter.update(num_generated_tokens)
|
| 312 |
-
|
| 313 |
-
for batch_id, sample_id in enumerate(sample["id"].tolist()):
|
| 314 |
-
errs, length = self.process_sentence(
|
| 315 |
-
sample=sample,
|
| 316 |
-
sid=sample_id,
|
| 317 |
-
batch_id=batch_id,
|
| 318 |
-
hypo=hypos[batch_id][0],
|
| 319 |
-
)
|
| 320 |
-
self.total_errors += errs
|
| 321 |
-
self.total_length += length
|
| 322 |
-
|
| 323 |
-
self.log({"wps": round(self.wps_meter.avg)})
|
| 324 |
-
if "nsentences" in sample:
|
| 325 |
-
self.num_sentences += sample["nsentences"]
|
| 326 |
-
else:
|
| 327 |
-
self.num_sentences += sample["id"].numel()
|
| 328 |
-
|
| 329 |
-
def log_generation_time(self) -> None:
|
| 330 |
-
logger.info(
|
| 331 |
-
"Processed %d sentences (%d tokens) in %.1fs %.2f "
|
| 332 |
-
"sentences per second, %.2f tokens per second)",
|
| 333 |
-
self.num_sentences,
|
| 334 |
-
self.gen_timer.n,
|
| 335 |
-
self.gen_timer.sum,
|
| 336 |
-
self.num_sentences / (self.gen_timer.sum + 1e-6),
|
| 337 |
-
1.0 / (self.gen_timer.avg + 1e-6),
|
| 338 |
-
)
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
def parse_wer(wer_file: Path) -> float:
|
| 342 |
-
with open(wer_file, "r") as f:
|
| 343 |
-
return float(f.readline().strip().split(" ")[1])
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
def get_wer_file(cfg: InferConfig) -> Path:
|
| 347 |
-
"""Hashes the decoding parameters to a unique file ID."""
|
| 348 |
-
base_path = "wer"
|
| 349 |
-
if cfg.decoding.results_path is not None:
|
| 350 |
-
base_path = os.path.join(cfg.decoding.results_path, base_path)
|
| 351 |
-
|
| 352 |
-
if cfg.decoding.unique_wer_file:
|
| 353 |
-
yaml_str = OmegaConf.to_yaml(cfg.decoding)
|
| 354 |
-
fid = int(hashlib.md5(yaml_str.encode("utf-8")).hexdigest(), 16)
|
| 355 |
-
return Path(f"{base_path}.{fid % 1000000}")
|
| 356 |
-
else:
|
| 357 |
-
return Path(base_path)
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
def main(cfg: InferConfig) -> float:
|
| 361 |
-
"""Entry point for main processing logic.
|
| 362 |
-
|
| 363 |
-
Args:
|
| 364 |
-
cfg: The inferance configuration to use.
|
| 365 |
-
wer: Optional shared memory pointer for returning the WER. If not None,
|
| 366 |
-
the final WER value will be written here instead of being returned.
|
| 367 |
-
|
| 368 |
-
Returns:
|
| 369 |
-
The final WER if `wer` is None, otherwise None.
|
| 370 |
-
"""
|
| 371 |
-
|
| 372 |
-
yaml_str, wer_file = OmegaConf.to_yaml(cfg.decoding), get_wer_file(cfg)
|
| 373 |
-
|
| 374 |
-
# Validates the provided configuration.
|
| 375 |
-
if cfg.dataset.max_tokens is None and cfg.dataset.batch_size is None:
|
| 376 |
-
cfg.dataset.max_tokens = 4000000
|
| 377 |
-
if not cfg.common.cpu and not torch.cuda.is_available():
|
| 378 |
-
raise ValueError("CUDA not found; set `cpu=True` to run without CUDA")
|
| 379 |
-
|
| 380 |
-
logger.info(cfg.common_eval.path)
|
| 381 |
-
|
| 382 |
-
with InferenceProcessor(cfg) as processor:
|
| 383 |
-
for sample in processor:
|
| 384 |
-
processor.process_sample(sample)
|
| 385 |
-
|
| 386 |
-
processor.log_generation_time()
|
| 387 |
-
|
| 388 |
-
if cfg.decoding.results_path is not None:
|
| 389 |
-
processor.merge_shards()
|
| 390 |
-
|
| 391 |
-
errs_t, leng_t = processor.total_errors, processor.total_length
|
| 392 |
-
|
| 393 |
-
if cfg.common.cpu:
|
| 394 |
-
logger.warning("Merging WER requires CUDA.")
|
| 395 |
-
elif processor.data_parallel_world_size > 1:
|
| 396 |
-
stats = torch.LongTensor([errs_t, leng_t]).cuda()
|
| 397 |
-
dist.all_reduce(stats, op=dist.ReduceOp.SUM)
|
| 398 |
-
errs_t, leng_t = stats[0].item(), stats[1].item()
|
| 399 |
-
|
| 400 |
-
wer = errs_t * 100.0 / leng_t
|
| 401 |
-
|
| 402 |
-
if distributed_utils.is_master(cfg.distributed_training):
|
| 403 |
-
with open(wer_file, "w") as f:
|
| 404 |
-
f.write(
|
| 405 |
-
(
|
| 406 |
-
f"WER: {wer}\n"
|
| 407 |
-
f"err / num_ref_words = {errs_t} / {leng_t}\n\n"
|
| 408 |
-
f"{yaml_str}"
|
| 409 |
-
)
|
| 410 |
-
)
|
| 411 |
-
|
| 412 |
-
return wer
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
@hydra.main(config_path=config_path, config_name="infer")
|
| 416 |
-
def hydra_main(cfg: InferConfig) -> Union[float, Tuple[float, Optional[float]]]:
|
| 417 |
-
container = OmegaConf.to_container(cfg, resolve=True, enum_to_str=True)
|
| 418 |
-
cfg = OmegaConf.create(container)
|
| 419 |
-
OmegaConf.set_struct(cfg, True)
|
| 420 |
-
|
| 421 |
-
if cfg.common.reset_logging:
|
| 422 |
-
reset_logging()
|
| 423 |
-
|
| 424 |
-
utils.import_user_module(cfg.common)
|
| 425 |
-
|
| 426 |
-
# logger.info("Config:\n%s", OmegaConf.to_yaml(cfg))
|
| 427 |
-
wer = float("inf")
|
| 428 |
-
|
| 429 |
-
try:
|
| 430 |
-
if cfg.common.profile:
|
| 431 |
-
with torch.cuda.profiler.profile():
|
| 432 |
-
with torch.autograd.profiler.emit_nvtx():
|
| 433 |
-
distributed_utils.call_main(cfg, main)
|
| 434 |
-
else:
|
| 435 |
-
distributed_utils.call_main(cfg, main)
|
| 436 |
-
|
| 437 |
-
wer = parse_wer(get_wer_file(cfg))
|
| 438 |
-
except BaseException as e: # pylint: disable=broad-except
|
| 439 |
-
if not cfg.common.suppress_crashes:
|
| 440 |
-
raise
|
| 441 |
-
else:
|
| 442 |
-
logger.error("Crashed! %s", str(e))
|
| 443 |
-
|
| 444 |
-
logger.info("Word error rate: %.4f", wer)
|
| 445 |
-
if cfg.is_ax:
|
| 446 |
-
return wer, None
|
| 447 |
-
|
| 448 |
-
return wer
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
def cli_main() -> None:
|
| 452 |
-
try:
|
| 453 |
-
from hydra._internal.utils import ( # pylint: disable=import-outside-toplevel
|
| 454 |
-
get_args,
|
| 455 |
-
)
|
| 456 |
-
|
| 457 |
-
cfg_name = get_args().config_name or "infer"
|
| 458 |
-
except ImportError:
|
| 459 |
-
logger.warning("Failed to get config name from hydra args")
|
| 460 |
-
cfg_name = "infer"
|
| 461 |
-
|
| 462 |
-
cs = ConfigStore.instance()
|
| 463 |
-
cs.store(name=cfg_name, node=InferConfig)
|
| 464 |
-
|
| 465 |
-
for k in InferConfig.__dataclass_fields__:
|
| 466 |
-
if is_dataclass(InferConfig.__dataclass_fields__[k].type):
|
| 467 |
-
v = InferConfig.__dataclass_fields__[k].default
|
| 468 |
-
cs.store(name=k, node=v)
|
| 469 |
-
|
| 470 |
-
hydra_main() # pylint: disable=no-value-for-parameter
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
if __name__ == "__main__":
|
| 474 |
-
cli_main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/tasks/__init__.py
DELETED
|
@@ -1,7 +0,0 @@
|
|
| 1 |
-
import importlib
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
for file in sorted(os.listdir(os.path.dirname(__file__))):
|
| 5 |
-
if file.endswith(".py") and not file.startswith("_"):
|
| 6 |
-
task_name = file[: file.find(".py")]
|
| 7 |
-
importlib.import_module("examples.speech_recognition.tasks." + task_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/tasks/speech_recognition.py
DELETED
|
@@ -1,157 +0,0 @@
|
|
| 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 json
|
| 7 |
-
import os
|
| 8 |
-
import re
|
| 9 |
-
import sys
|
| 10 |
-
|
| 11 |
-
import torch
|
| 12 |
-
from examples.speech_recognition.data import AsrDataset
|
| 13 |
-
from examples.speech_recognition.data.replabels import replabel_symbol
|
| 14 |
-
from fairseq.data import Dictionary
|
| 15 |
-
from fairseq.tasks import LegacyFairseqTask, register_task
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
def get_asr_dataset_from_json(data_json_path, tgt_dict):
|
| 19 |
-
"""
|
| 20 |
-
Parse data json and create dataset.
|
| 21 |
-
See scripts/asr_prep_json.py which pack json from raw files
|
| 22 |
-
|
| 23 |
-
Json example:
|
| 24 |
-
{
|
| 25 |
-
"utts": {
|
| 26 |
-
"4771-29403-0025": {
|
| 27 |
-
"input": {
|
| 28 |
-
"length_ms": 170,
|
| 29 |
-
"path": "/tmp/file1.flac"
|
| 30 |
-
},
|
| 31 |
-
"output": {
|
| 32 |
-
"text": "HELLO \n",
|
| 33 |
-
"token": "HE LLO",
|
| 34 |
-
"tokenid": "4815, 861"
|
| 35 |
-
}
|
| 36 |
-
},
|
| 37 |
-
"1564-142299-0096": {
|
| 38 |
-
...
|
| 39 |
-
}
|
| 40 |
-
}
|
| 41 |
-
"""
|
| 42 |
-
if not os.path.isfile(data_json_path):
|
| 43 |
-
raise FileNotFoundError("Dataset not found: {}".format(data_json_path))
|
| 44 |
-
with open(data_json_path, "rb") as f:
|
| 45 |
-
data_samples = json.load(f)["utts"]
|
| 46 |
-
assert len(data_samples) != 0
|
| 47 |
-
sorted_samples = sorted(
|
| 48 |
-
data_samples.items(),
|
| 49 |
-
key=lambda sample: int(sample[1]["input"]["length_ms"]),
|
| 50 |
-
reverse=True,
|
| 51 |
-
)
|
| 52 |
-
aud_paths = [s[1]["input"]["path"] for s in sorted_samples]
|
| 53 |
-
ids = [s[0] for s in sorted_samples]
|
| 54 |
-
speakers = []
|
| 55 |
-
for s in sorted_samples:
|
| 56 |
-
m = re.search("(.+?)-(.+?)-(.+?)", s[0])
|
| 57 |
-
speakers.append(m.group(1) + "_" + m.group(2))
|
| 58 |
-
frame_sizes = [s[1]["input"]["length_ms"] for s in sorted_samples]
|
| 59 |
-
tgt = [
|
| 60 |
-
[int(i) for i in s[1]["output"]["tokenid"].split(", ")]
|
| 61 |
-
for s in sorted_samples
|
| 62 |
-
]
|
| 63 |
-
# append eos
|
| 64 |
-
tgt = [[*t, tgt_dict.eos()] for t in tgt]
|
| 65 |
-
return AsrDataset(aud_paths, frame_sizes, tgt, tgt_dict, ids, speakers)
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
@register_task("speech_recognition")
|
| 69 |
-
class SpeechRecognitionTask(LegacyFairseqTask):
|
| 70 |
-
"""
|
| 71 |
-
Task for training speech recognition model.
|
| 72 |
-
"""
|
| 73 |
-
|
| 74 |
-
@staticmethod
|
| 75 |
-
def add_args(parser):
|
| 76 |
-
"""Add task-specific arguments to the parser."""
|
| 77 |
-
parser.add_argument("data", help="path to data directory")
|
| 78 |
-
parser.add_argument(
|
| 79 |
-
"--silence-token", default="\u2581", help="token for silence (used by w2l)"
|
| 80 |
-
)
|
| 81 |
-
parser.add_argument(
|
| 82 |
-
"--max-source-positions",
|
| 83 |
-
default=sys.maxsize,
|
| 84 |
-
type=int,
|
| 85 |
-
metavar="N",
|
| 86 |
-
help="max number of frames in the source sequence",
|
| 87 |
-
)
|
| 88 |
-
parser.add_argument(
|
| 89 |
-
"--max-target-positions",
|
| 90 |
-
default=1024,
|
| 91 |
-
type=int,
|
| 92 |
-
metavar="N",
|
| 93 |
-
help="max number of tokens in the target sequence",
|
| 94 |
-
)
|
| 95 |
-
|
| 96 |
-
def __init__(self, args, tgt_dict):
|
| 97 |
-
super().__init__(args)
|
| 98 |
-
self.tgt_dict = tgt_dict
|
| 99 |
-
|
| 100 |
-
@classmethod
|
| 101 |
-
def setup_task(cls, args, **kwargs):
|
| 102 |
-
"""Setup the task (e.g., load dictionaries)."""
|
| 103 |
-
dict_path = os.path.join(args.data, "dict.txt")
|
| 104 |
-
if not os.path.isfile(dict_path):
|
| 105 |
-
raise FileNotFoundError("Dict not found: {}".format(dict_path))
|
| 106 |
-
tgt_dict = Dictionary.load(dict_path)
|
| 107 |
-
|
| 108 |
-
if args.criterion == "ctc_loss":
|
| 109 |
-
tgt_dict.add_symbol("<ctc_blank>")
|
| 110 |
-
elif args.criterion == "asg_loss":
|
| 111 |
-
for i in range(1, args.max_replabel + 1):
|
| 112 |
-
tgt_dict.add_symbol(replabel_symbol(i))
|
| 113 |
-
|
| 114 |
-
print("| dictionary: {} types".format(len(tgt_dict)))
|
| 115 |
-
return cls(args, tgt_dict)
|
| 116 |
-
|
| 117 |
-
def load_dataset(self, split, combine=False, **kwargs):
|
| 118 |
-
"""Load a given dataset split.
|
| 119 |
-
|
| 120 |
-
Args:
|
| 121 |
-
split (str): name of the split (e.g., train, valid, test)
|
| 122 |
-
"""
|
| 123 |
-
data_json_path = os.path.join(self.args.data, "{}.json".format(split))
|
| 124 |
-
self.datasets[split] = get_asr_dataset_from_json(data_json_path, self.tgt_dict)
|
| 125 |
-
|
| 126 |
-
def build_generator(self, models, args, **unused):
|
| 127 |
-
w2l_decoder = getattr(args, "w2l_decoder", None)
|
| 128 |
-
if w2l_decoder == "viterbi":
|
| 129 |
-
from examples.speech_recognition.w2l_decoder import W2lViterbiDecoder
|
| 130 |
-
|
| 131 |
-
return W2lViterbiDecoder(args, self.target_dictionary)
|
| 132 |
-
elif w2l_decoder == "kenlm":
|
| 133 |
-
from examples.speech_recognition.w2l_decoder import W2lKenLMDecoder
|
| 134 |
-
|
| 135 |
-
return W2lKenLMDecoder(args, self.target_dictionary)
|
| 136 |
-
elif w2l_decoder == "fairseqlm":
|
| 137 |
-
from examples.speech_recognition.w2l_decoder import W2lFairseqLMDecoder
|
| 138 |
-
|
| 139 |
-
return W2lFairseqLMDecoder(args, self.target_dictionary)
|
| 140 |
-
else:
|
| 141 |
-
return super().build_generator(models, args)
|
| 142 |
-
|
| 143 |
-
@property
|
| 144 |
-
def target_dictionary(self):
|
| 145 |
-
"""Return the :class:`~fairseq.data.Dictionary` for the language
|
| 146 |
-
model."""
|
| 147 |
-
return self.tgt_dict
|
| 148 |
-
|
| 149 |
-
@property
|
| 150 |
-
def source_dictionary(self):
|
| 151 |
-
"""Return the source :class:`~fairseq.data.Dictionary` (if applicable
|
| 152 |
-
for this task)."""
|
| 153 |
-
return None
|
| 154 |
-
|
| 155 |
-
def max_positions(self):
|
| 156 |
-
"""Return the max speech and sentence length allowed by the task."""
|
| 157 |
-
return (self.args.max_source_positions, self.args.max_target_positions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/utils/wer_utils.py
DELETED
|
@@ -1,380 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
from __future__ import absolute_import, division, print_function, unicode_literals
|
| 9 |
-
|
| 10 |
-
import re
|
| 11 |
-
from collections import deque
|
| 12 |
-
from enum import Enum
|
| 13 |
-
|
| 14 |
-
import numpy as np
|
| 15 |
-
|
| 16 |
-
"""
|
| 17 |
-
Utility modules for computation of Word Error Rate,
|
| 18 |
-
Alignments, as well as more granular metrics like
|
| 19 |
-
deletion, insersion and substitutions.
|
| 20 |
-
"""
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
class Code(Enum):
|
| 24 |
-
match = 1
|
| 25 |
-
substitution = 2
|
| 26 |
-
insertion = 3
|
| 27 |
-
deletion = 4
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
class Token(object):
|
| 31 |
-
def __init__(self, lbl="", st=np.nan, en=np.nan):
|
| 32 |
-
if np.isnan(st):
|
| 33 |
-
self.label, self.start, self.end = "", 0.0, 0.0
|
| 34 |
-
else:
|
| 35 |
-
self.label, self.start, self.end = lbl, st, en
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
class AlignmentResult(object):
|
| 39 |
-
def __init__(self, refs, hyps, codes, score):
|
| 40 |
-
self.refs = refs # std::deque<int>
|
| 41 |
-
self.hyps = hyps # std::deque<int>
|
| 42 |
-
self.codes = codes # std::deque<Code>
|
| 43 |
-
self.score = score # float
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
def coordinate_to_offset(row, col, ncols):
|
| 47 |
-
return int(row * ncols + col)
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
def offset_to_row(offset, ncols):
|
| 51 |
-
return int(offset / ncols)
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def offset_to_col(offset, ncols):
|
| 55 |
-
return int(offset % ncols)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def trimWhitespace(str):
|
| 59 |
-
return re.sub(" +", " ", re.sub(" *$", "", re.sub("^ *", "", str)))
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
def str2toks(str):
|
| 63 |
-
pieces = trimWhitespace(str).split(" ")
|
| 64 |
-
toks = []
|
| 65 |
-
for p in pieces:
|
| 66 |
-
toks.append(Token(p, 0.0, 0.0))
|
| 67 |
-
return toks
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
class EditDistance(object):
|
| 71 |
-
def __init__(self, time_mediated):
|
| 72 |
-
self.time_mediated_ = time_mediated
|
| 73 |
-
self.scores_ = np.nan # Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>
|
| 74 |
-
self.backtraces_ = (
|
| 75 |
-
np.nan
|
| 76 |
-
) # Eigen::Matrix<size_t, Eigen::Dynamic, Eigen::Dynamic> backtraces_;
|
| 77 |
-
self.confusion_pairs_ = {}
|
| 78 |
-
|
| 79 |
-
def cost(self, ref, hyp, code):
|
| 80 |
-
if self.time_mediated_:
|
| 81 |
-
if code == Code.match:
|
| 82 |
-
return abs(ref.start - hyp.start) + abs(ref.end - hyp.end)
|
| 83 |
-
elif code == Code.insertion:
|
| 84 |
-
return hyp.end - hyp.start
|
| 85 |
-
elif code == Code.deletion:
|
| 86 |
-
return ref.end - ref.start
|
| 87 |
-
else: # substitution
|
| 88 |
-
return abs(ref.start - hyp.start) + abs(ref.end - hyp.end) + 0.1
|
| 89 |
-
else:
|
| 90 |
-
if code == Code.match:
|
| 91 |
-
return 0
|
| 92 |
-
elif code == Code.insertion or code == Code.deletion:
|
| 93 |
-
return 3
|
| 94 |
-
else: # substitution
|
| 95 |
-
return 4
|
| 96 |
-
|
| 97 |
-
def get_result(self, refs, hyps):
|
| 98 |
-
res = AlignmentResult(refs=deque(), hyps=deque(), codes=deque(), score=np.nan)
|
| 99 |
-
|
| 100 |
-
num_rows, num_cols = self.scores_.shape
|
| 101 |
-
res.score = self.scores_[num_rows - 1, num_cols - 1]
|
| 102 |
-
|
| 103 |
-
curr_offset = coordinate_to_offset(num_rows - 1, num_cols - 1, num_cols)
|
| 104 |
-
|
| 105 |
-
while curr_offset != 0:
|
| 106 |
-
curr_row = offset_to_row(curr_offset, num_cols)
|
| 107 |
-
curr_col = offset_to_col(curr_offset, num_cols)
|
| 108 |
-
|
| 109 |
-
prev_offset = self.backtraces_[curr_row, curr_col]
|
| 110 |
-
|
| 111 |
-
prev_row = offset_to_row(prev_offset, num_cols)
|
| 112 |
-
prev_col = offset_to_col(prev_offset, num_cols)
|
| 113 |
-
|
| 114 |
-
res.refs.appendleft(curr_row - 1) # Note: this was .push_front() in C++
|
| 115 |
-
res.hyps.appendleft(curr_col - 1)
|
| 116 |
-
if curr_row - 1 == prev_row and curr_col == prev_col:
|
| 117 |
-
res.codes.appendleft(Code.deletion)
|
| 118 |
-
elif curr_row == prev_row and curr_col - 1 == prev_col:
|
| 119 |
-
res.codes.appendleft(Code.insertion)
|
| 120 |
-
else:
|
| 121 |
-
# assert(curr_row - 1 == prev_row and curr_col - 1 == prev_col)
|
| 122 |
-
ref_str = refs[res.refs[0]].label
|
| 123 |
-
hyp_str = hyps[res.hyps[0]].label
|
| 124 |
-
|
| 125 |
-
if ref_str == hyp_str:
|
| 126 |
-
res.codes.appendleft(Code.match)
|
| 127 |
-
else:
|
| 128 |
-
res.codes.appendleft(Code.substitution)
|
| 129 |
-
|
| 130 |
-
confusion_pair = "%s -> %s" % (ref_str, hyp_str)
|
| 131 |
-
if confusion_pair not in self.confusion_pairs_:
|
| 132 |
-
self.confusion_pairs_[confusion_pair] = 1
|
| 133 |
-
else:
|
| 134 |
-
self.confusion_pairs_[confusion_pair] += 1
|
| 135 |
-
|
| 136 |
-
curr_offset = prev_offset
|
| 137 |
-
|
| 138 |
-
return res
|
| 139 |
-
|
| 140 |
-
def align(self, refs, hyps):
|
| 141 |
-
if len(refs) == 0 and len(hyps) == 0:
|
| 142 |
-
return np.nan
|
| 143 |
-
|
| 144 |
-
# NOTE: we're not resetting the values in these matrices because every value
|
| 145 |
-
# will be overridden in the loop below. If this assumption doesn't hold,
|
| 146 |
-
# be sure to set all entries in self.scores_ and self.backtraces_ to 0.
|
| 147 |
-
self.scores_ = np.zeros((len(refs) + 1, len(hyps) + 1))
|
| 148 |
-
self.backtraces_ = np.zeros((len(refs) + 1, len(hyps) + 1))
|
| 149 |
-
|
| 150 |
-
num_rows, num_cols = self.scores_.shape
|
| 151 |
-
|
| 152 |
-
for i in range(num_rows):
|
| 153 |
-
for j in range(num_cols):
|
| 154 |
-
if i == 0 and j == 0:
|
| 155 |
-
self.scores_[i, j] = 0.0
|
| 156 |
-
self.backtraces_[i, j] = 0
|
| 157 |
-
continue
|
| 158 |
-
|
| 159 |
-
if i == 0:
|
| 160 |
-
self.scores_[i, j] = self.scores_[i, j - 1] + self.cost(
|
| 161 |
-
None, hyps[j - 1], Code.insertion
|
| 162 |
-
)
|
| 163 |
-
self.backtraces_[i, j] = coordinate_to_offset(i, j - 1, num_cols)
|
| 164 |
-
continue
|
| 165 |
-
|
| 166 |
-
if j == 0:
|
| 167 |
-
self.scores_[i, j] = self.scores_[i - 1, j] + self.cost(
|
| 168 |
-
refs[i - 1], None, Code.deletion
|
| 169 |
-
)
|
| 170 |
-
self.backtraces_[i, j] = coordinate_to_offset(i - 1, j, num_cols)
|
| 171 |
-
continue
|
| 172 |
-
|
| 173 |
-
# Below here both i and j are greater than 0
|
| 174 |
-
ref = refs[i - 1]
|
| 175 |
-
hyp = hyps[j - 1]
|
| 176 |
-
best_score = self.scores_[i - 1, j - 1] + (
|
| 177 |
-
self.cost(ref, hyp, Code.match)
|
| 178 |
-
if (ref.label == hyp.label)
|
| 179 |
-
else self.cost(ref, hyp, Code.substitution)
|
| 180 |
-
)
|
| 181 |
-
|
| 182 |
-
prev_row = i - 1
|
| 183 |
-
prev_col = j - 1
|
| 184 |
-
ins = self.scores_[i, j - 1] + self.cost(None, hyp, Code.insertion)
|
| 185 |
-
if ins < best_score:
|
| 186 |
-
best_score = ins
|
| 187 |
-
prev_row = i
|
| 188 |
-
prev_col = j - 1
|
| 189 |
-
|
| 190 |
-
delt = self.scores_[i - 1, j] + self.cost(ref, None, Code.deletion)
|
| 191 |
-
if delt < best_score:
|
| 192 |
-
best_score = delt
|
| 193 |
-
prev_row = i - 1
|
| 194 |
-
prev_col = j
|
| 195 |
-
|
| 196 |
-
self.scores_[i, j] = best_score
|
| 197 |
-
self.backtraces_[i, j] = coordinate_to_offset(
|
| 198 |
-
prev_row, prev_col, num_cols
|
| 199 |
-
)
|
| 200 |
-
|
| 201 |
-
return self.get_result(refs, hyps)
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
class WERTransformer(object):
|
| 205 |
-
def __init__(self, hyp_str, ref_str, verbose=True):
|
| 206 |
-
self.ed_ = EditDistance(False)
|
| 207 |
-
self.id2oracle_errs_ = {}
|
| 208 |
-
self.utts_ = 0
|
| 209 |
-
self.words_ = 0
|
| 210 |
-
self.insertions_ = 0
|
| 211 |
-
self.deletions_ = 0
|
| 212 |
-
self.substitutions_ = 0
|
| 213 |
-
|
| 214 |
-
self.process(["dummy_str", hyp_str, ref_str])
|
| 215 |
-
|
| 216 |
-
if verbose:
|
| 217 |
-
print("'%s' vs '%s'" % (hyp_str, ref_str))
|
| 218 |
-
self.report_result()
|
| 219 |
-
|
| 220 |
-
def process(self, input): # std::vector<std::string>&& input
|
| 221 |
-
if len(input) < 3:
|
| 222 |
-
print(
|
| 223 |
-
"Input must be of the form <id> ... <hypo> <ref> , got ",
|
| 224 |
-
len(input),
|
| 225 |
-
" inputs:",
|
| 226 |
-
)
|
| 227 |
-
return None
|
| 228 |
-
|
| 229 |
-
# Align
|
| 230 |
-
# std::vector<Token> hyps;
|
| 231 |
-
# std::vector<Token> refs;
|
| 232 |
-
|
| 233 |
-
hyps = str2toks(input[-2])
|
| 234 |
-
refs = str2toks(input[-1])
|
| 235 |
-
|
| 236 |
-
alignment = self.ed_.align(refs, hyps)
|
| 237 |
-
if alignment is None:
|
| 238 |
-
print("Alignment is null")
|
| 239 |
-
return np.nan
|
| 240 |
-
|
| 241 |
-
# Tally errors
|
| 242 |
-
ins = 0
|
| 243 |
-
dels = 0
|
| 244 |
-
subs = 0
|
| 245 |
-
for code in alignment.codes:
|
| 246 |
-
if code == Code.substitution:
|
| 247 |
-
subs += 1
|
| 248 |
-
elif code == Code.insertion:
|
| 249 |
-
ins += 1
|
| 250 |
-
elif code == Code.deletion:
|
| 251 |
-
dels += 1
|
| 252 |
-
|
| 253 |
-
# Output
|
| 254 |
-
row = input
|
| 255 |
-
row.append(str(len(refs)))
|
| 256 |
-
row.append(str(ins))
|
| 257 |
-
row.append(str(dels))
|
| 258 |
-
row.append(str(subs))
|
| 259 |
-
# print(row)
|
| 260 |
-
|
| 261 |
-
# Accumulate
|
| 262 |
-
kIdIndex = 0
|
| 263 |
-
kNBestSep = "/"
|
| 264 |
-
|
| 265 |
-
pieces = input[kIdIndex].split(kNBestSep)
|
| 266 |
-
|
| 267 |
-
if len(pieces) == 0:
|
| 268 |
-
print(
|
| 269 |
-
"Error splitting ",
|
| 270 |
-
input[kIdIndex],
|
| 271 |
-
" on '",
|
| 272 |
-
kNBestSep,
|
| 273 |
-
"', got empty list",
|
| 274 |
-
)
|
| 275 |
-
return np.nan
|
| 276 |
-
|
| 277 |
-
id = pieces[0]
|
| 278 |
-
if id not in self.id2oracle_errs_:
|
| 279 |
-
self.utts_ += 1
|
| 280 |
-
self.words_ += len(refs)
|
| 281 |
-
self.insertions_ += ins
|
| 282 |
-
self.deletions_ += dels
|
| 283 |
-
self.substitutions_ += subs
|
| 284 |
-
self.id2oracle_errs_[id] = [ins, dels, subs]
|
| 285 |
-
else:
|
| 286 |
-
curr_err = ins + dels + subs
|
| 287 |
-
prev_err = np.sum(self.id2oracle_errs_[id])
|
| 288 |
-
if curr_err < prev_err:
|
| 289 |
-
self.id2oracle_errs_[id] = [ins, dels, subs]
|
| 290 |
-
|
| 291 |
-
return 0
|
| 292 |
-
|
| 293 |
-
def report_result(self):
|
| 294 |
-
# print("---------- Summary ---------------")
|
| 295 |
-
if self.words_ == 0:
|
| 296 |
-
print("No words counted")
|
| 297 |
-
return
|
| 298 |
-
|
| 299 |
-
# 1-best
|
| 300 |
-
best_wer = (
|
| 301 |
-
100.0
|
| 302 |
-
* (self.insertions_ + self.deletions_ + self.substitutions_)
|
| 303 |
-
/ self.words_
|
| 304 |
-
)
|
| 305 |
-
|
| 306 |
-
print(
|
| 307 |
-
"\tWER = %0.2f%% (%i utts, %i words, %0.2f%% ins, "
|
| 308 |
-
"%0.2f%% dels, %0.2f%% subs)"
|
| 309 |
-
% (
|
| 310 |
-
best_wer,
|
| 311 |
-
self.utts_,
|
| 312 |
-
self.words_,
|
| 313 |
-
100.0 * self.insertions_ / self.words_,
|
| 314 |
-
100.0 * self.deletions_ / self.words_,
|
| 315 |
-
100.0 * self.substitutions_ / self.words_,
|
| 316 |
-
)
|
| 317 |
-
)
|
| 318 |
-
|
| 319 |
-
def wer(self):
|
| 320 |
-
if self.words_ == 0:
|
| 321 |
-
wer = np.nan
|
| 322 |
-
else:
|
| 323 |
-
wer = (
|
| 324 |
-
100.0
|
| 325 |
-
* (self.insertions_ + self.deletions_ + self.substitutions_)
|
| 326 |
-
/ self.words_
|
| 327 |
-
)
|
| 328 |
-
return wer
|
| 329 |
-
|
| 330 |
-
def stats(self):
|
| 331 |
-
if self.words_ == 0:
|
| 332 |
-
stats = {}
|
| 333 |
-
else:
|
| 334 |
-
wer = (
|
| 335 |
-
100.0
|
| 336 |
-
* (self.insertions_ + self.deletions_ + self.substitutions_)
|
| 337 |
-
/ self.words_
|
| 338 |
-
)
|
| 339 |
-
stats = dict(
|
| 340 |
-
{
|
| 341 |
-
"wer": wer,
|
| 342 |
-
"utts": self.utts_,
|
| 343 |
-
"numwords": self.words_,
|
| 344 |
-
"ins": self.insertions_,
|
| 345 |
-
"dels": self.deletions_,
|
| 346 |
-
"subs": self.substitutions_,
|
| 347 |
-
"confusion_pairs": self.ed_.confusion_pairs_,
|
| 348 |
-
}
|
| 349 |
-
)
|
| 350 |
-
return stats
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
def calc_wer(hyp_str, ref_str):
|
| 354 |
-
t = WERTransformer(hyp_str, ref_str, verbose=0)
|
| 355 |
-
return t.wer()
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
def calc_wer_stats(hyp_str, ref_str):
|
| 359 |
-
t = WERTransformer(hyp_str, ref_str, verbose=0)
|
| 360 |
-
return t.stats()
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
def get_wer_alignment_codes(hyp_str, ref_str):
|
| 364 |
-
"""
|
| 365 |
-
INPUT: hypothesis string, reference string
|
| 366 |
-
OUTPUT: List of alignment codes (intermediate results from WER computation)
|
| 367 |
-
"""
|
| 368 |
-
t = WERTransformer(hyp_str, ref_str, verbose=0)
|
| 369 |
-
return t.ed_.align(str2toks(ref_str), str2toks(hyp_str)).codes
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
def merge_counts(x, y):
|
| 373 |
-
# Merge two hashes which have 'counts' as their values
|
| 374 |
-
# This can be used for example to merge confusion pair counts
|
| 375 |
-
# conf_pairs = merge_counts(conf_pairs, stats['confusion_pairs'])
|
| 376 |
-
for k, v in y.items():
|
| 377 |
-
if k not in x:
|
| 378 |
-
x[k] = 0
|
| 379 |
-
x[k] += v
|
| 380 |
-
return x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_recognition/w2l_decoder.py
DELETED
|
@@ -1,491 +0,0 @@
|
|
| 1 |
-
#!/usr/bin/env python3
|
| 2 |
-
|
| 3 |
-
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
-
#
|
| 5 |
-
# This source code is licensed under the MIT license found in the
|
| 6 |
-
# LICENSE file in the root directory of this source tree.
|
| 7 |
-
|
| 8 |
-
"""
|
| 9 |
-
Flashlight decoders.
|
| 10 |
-
"""
|
| 11 |
-
|
| 12 |
-
import gc
|
| 13 |
-
import itertools as it
|
| 14 |
-
import os.path as osp
|
| 15 |
-
import warnings
|
| 16 |
-
from collections import deque, namedtuple
|
| 17 |
-
from typing import List
|
| 18 |
-
|
| 19 |
-
import numpy as np
|
| 20 |
-
import torch
|
| 21 |
-
from examples.speech_recognition.data.replabels import unpack_replabels
|
| 22 |
-
from fairseq import tasks
|
| 23 |
-
from fairseq.dataclass.utils import convert_namespace_to_omegaconf
|
| 24 |
-
from fairseq.utils import apply_to_sample
|
| 25 |
-
from omegaconf import open_dict
|
| 26 |
-
|
| 27 |
-
try:
|
| 28 |
-
from flashlight.lib.sequence.criterion import CpuViterbiPath, get_data_ptr_as_bytes
|
| 29 |
-
from flashlight.lib.text.decoder import (
|
| 30 |
-
LM,
|
| 31 |
-
CriterionType,
|
| 32 |
-
KenLM,
|
| 33 |
-
LexiconDecoder,
|
| 34 |
-
LexiconDecoderOptions,
|
| 35 |
-
LMState,
|
| 36 |
-
SmearingMode,
|
| 37 |
-
Trie,
|
| 38 |
-
)
|
| 39 |
-
from flashlight.lib.text.dictionary import create_word_dict, load_words
|
| 40 |
-
except:
|
| 41 |
-
warnings.warn(
|
| 42 |
-
"flashlight python bindings are required to use this functionality. Please install from https://github.com/facebookresearch/flashlight/tree/master/bindings/python"
|
| 43 |
-
)
|
| 44 |
-
LM = object
|
| 45 |
-
LMState = object
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
class W2lDecoder(object):
|
| 49 |
-
def __init__(self, args, tgt_dict):
|
| 50 |
-
self.tgt_dict = tgt_dict
|
| 51 |
-
self.vocab_size = len(tgt_dict)
|
| 52 |
-
self.nbest = args.nbest
|
| 53 |
-
|
| 54 |
-
# criterion-specific init
|
| 55 |
-
self.criterion_type = CriterionType.CTC
|
| 56 |
-
self.blank = (
|
| 57 |
-
tgt_dict.index("<ctc_blank>")
|
| 58 |
-
if "<ctc_blank>" in tgt_dict.indices
|
| 59 |
-
else tgt_dict.bos()
|
| 60 |
-
)
|
| 61 |
-
if "<sep>" in tgt_dict.indices:
|
| 62 |
-
self.silence = tgt_dict.index("<sep>")
|
| 63 |
-
elif "|" in tgt_dict.indices:
|
| 64 |
-
self.silence = tgt_dict.index("|")
|
| 65 |
-
else:
|
| 66 |
-
self.silence = tgt_dict.eos()
|
| 67 |
-
self.asg_transitions = None
|
| 68 |
-
|
| 69 |
-
def generate(self, models, sample, **unused):
|
| 70 |
-
"""Generate a batch of inferences."""
|
| 71 |
-
# model.forward normally channels prev_output_tokens into the decoder
|
| 72 |
-
# separately, but SequenceGenerator directly calls model.encoder
|
| 73 |
-
encoder_input = {
|
| 74 |
-
k: v for k, v in sample["net_input"].items() if k != "prev_output_tokens"
|
| 75 |
-
}
|
| 76 |
-
emissions = self.get_emissions(models, encoder_input)
|
| 77 |
-
return self.decode(emissions)
|
| 78 |
-
|
| 79 |
-
def get_emissions(self, models, encoder_input):
|
| 80 |
-
"""Run encoder and normalize emissions"""
|
| 81 |
-
model = models[0]
|
| 82 |
-
encoder_out = model(**encoder_input)
|
| 83 |
-
if hasattr(model, "get_logits"):
|
| 84 |
-
emissions = model.get_logits(encoder_out) # no need to normalize emissions
|
| 85 |
-
else:
|
| 86 |
-
emissions = model.get_normalized_probs(encoder_out, log_probs=True)
|
| 87 |
-
return emissions.transpose(0, 1).float().cpu().contiguous()
|
| 88 |
-
|
| 89 |
-
def get_tokens(self, idxs):
|
| 90 |
-
"""Normalize tokens by handling CTC blank, ASG replabels, etc."""
|
| 91 |
-
idxs = (g[0] for g in it.groupby(idxs))
|
| 92 |
-
idxs = filter(lambda x: x != self.blank, idxs)
|
| 93 |
-
return torch.LongTensor(list(idxs))
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
class W2lViterbiDecoder(W2lDecoder):
|
| 97 |
-
def __init__(self, args, tgt_dict):
|
| 98 |
-
super().__init__(args, tgt_dict)
|
| 99 |
-
|
| 100 |
-
def decode(self, emissions):
|
| 101 |
-
B, T, N = emissions.size()
|
| 102 |
-
hypos = []
|
| 103 |
-
if self.asg_transitions is None:
|
| 104 |
-
transitions = torch.FloatTensor(N, N).zero_()
|
| 105 |
-
else:
|
| 106 |
-
transitions = torch.FloatTensor(self.asg_transitions).view(N, N)
|
| 107 |
-
viterbi_path = torch.IntTensor(B, T)
|
| 108 |
-
workspace = torch.ByteTensor(CpuViterbiPath.get_workspace_size(B, T, N))
|
| 109 |
-
CpuViterbiPath.compute(
|
| 110 |
-
B,
|
| 111 |
-
T,
|
| 112 |
-
N,
|
| 113 |
-
get_data_ptr_as_bytes(emissions),
|
| 114 |
-
get_data_ptr_as_bytes(transitions),
|
| 115 |
-
get_data_ptr_as_bytes(viterbi_path),
|
| 116 |
-
get_data_ptr_as_bytes(workspace),
|
| 117 |
-
)
|
| 118 |
-
return [
|
| 119 |
-
[{"tokens": self.get_tokens(viterbi_path[b].tolist()), "score": 0}]
|
| 120 |
-
for b in range(B)
|
| 121 |
-
]
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
class W2lKenLMDecoder(W2lDecoder):
|
| 125 |
-
def __init__(self, args, tgt_dict):
|
| 126 |
-
super().__init__(args, tgt_dict)
|
| 127 |
-
|
| 128 |
-
self.unit_lm = getattr(args, "unit_lm", False)
|
| 129 |
-
|
| 130 |
-
if args.lexicon:
|
| 131 |
-
self.lexicon = load_words(args.lexicon)
|
| 132 |
-
self.word_dict = create_word_dict(self.lexicon)
|
| 133 |
-
self.unk_word = self.word_dict.get_index("<unk>")
|
| 134 |
-
|
| 135 |
-
self.lm = KenLM(args.kenlm_model, self.word_dict)
|
| 136 |
-
self.trie = Trie(self.vocab_size, self.silence)
|
| 137 |
-
|
| 138 |
-
start_state = self.lm.start(False)
|
| 139 |
-
for i, (word, spellings) in enumerate(self.lexicon.items()):
|
| 140 |
-
word_idx = self.word_dict.get_index(word)
|
| 141 |
-
_, score = self.lm.score(start_state, word_idx)
|
| 142 |
-
for spelling in spellings:
|
| 143 |
-
spelling_idxs = [tgt_dict.index(token) for token in spelling]
|
| 144 |
-
assert (
|
| 145 |
-
tgt_dict.unk() not in spelling_idxs
|
| 146 |
-
), f"{spelling} {spelling_idxs}"
|
| 147 |
-
self.trie.insert(spelling_idxs, word_idx, score)
|
| 148 |
-
self.trie.smear(SmearingMode.MAX)
|
| 149 |
-
|
| 150 |
-
self.decoder_opts = LexiconDecoderOptions(
|
| 151 |
-
beam_size=args.beam,
|
| 152 |
-
beam_size_token=int(getattr(args, "beam_size_token", len(tgt_dict))),
|
| 153 |
-
beam_threshold=args.beam_threshold,
|
| 154 |
-
lm_weight=args.lm_weight,
|
| 155 |
-
word_score=args.word_score,
|
| 156 |
-
unk_score=args.unk_weight,
|
| 157 |
-
sil_score=args.sil_weight,
|
| 158 |
-
log_add=False,
|
| 159 |
-
criterion_type=self.criterion_type,
|
| 160 |
-
)
|
| 161 |
-
|
| 162 |
-
if self.asg_transitions is None:
|
| 163 |
-
N = 768
|
| 164 |
-
# self.asg_transitions = torch.FloatTensor(N, N).zero_()
|
| 165 |
-
self.asg_transitions = []
|
| 166 |
-
|
| 167 |
-
self.decoder = LexiconDecoder(
|
| 168 |
-
self.decoder_opts,
|
| 169 |
-
self.trie,
|
| 170 |
-
self.lm,
|
| 171 |
-
self.silence,
|
| 172 |
-
self.blank,
|
| 173 |
-
self.unk_word,
|
| 174 |
-
self.asg_transitions,
|
| 175 |
-
self.unit_lm,
|
| 176 |
-
)
|
| 177 |
-
else:
|
| 178 |
-
assert args.unit_lm, "lexicon free decoding can only be done with a unit language model"
|
| 179 |
-
from flashlight.lib.text.decoder import (
|
| 180 |
-
LexiconFreeDecoder,
|
| 181 |
-
LexiconFreeDecoderOptions,
|
| 182 |
-
)
|
| 183 |
-
|
| 184 |
-
d = {w: [[w]] for w in tgt_dict.symbols}
|
| 185 |
-
self.word_dict = create_word_dict(d)
|
| 186 |
-
self.lm = KenLM(args.kenlm_model, self.word_dict)
|
| 187 |
-
self.decoder_opts = LexiconFreeDecoderOptions(
|
| 188 |
-
beam_size=args.beam,
|
| 189 |
-
beam_size_token=int(getattr(args, "beam_size_token", len(tgt_dict))),
|
| 190 |
-
beam_threshold=args.beam_threshold,
|
| 191 |
-
lm_weight=args.lm_weight,
|
| 192 |
-
sil_score=args.sil_weight,
|
| 193 |
-
log_add=False,
|
| 194 |
-
criterion_type=self.criterion_type,
|
| 195 |
-
)
|
| 196 |
-
self.decoder = LexiconFreeDecoder(
|
| 197 |
-
self.decoder_opts, self.lm, self.silence, self.blank, []
|
| 198 |
-
)
|
| 199 |
-
|
| 200 |
-
def get_timesteps(self, token_idxs: List[int]) -> List[int]:
|
| 201 |
-
"""Returns frame numbers corresponding to every non-blank token.
|
| 202 |
-
|
| 203 |
-
Parameters
|
| 204 |
-
----------
|
| 205 |
-
token_idxs : List[int]
|
| 206 |
-
IDs of decoded tokens.
|
| 207 |
-
|
| 208 |
-
Returns
|
| 209 |
-
-------
|
| 210 |
-
List[int]
|
| 211 |
-
Frame numbers corresponding to every non-blank token.
|
| 212 |
-
"""
|
| 213 |
-
timesteps = []
|
| 214 |
-
for i, token_idx in enumerate(token_idxs):
|
| 215 |
-
if token_idx == self.blank:
|
| 216 |
-
continue
|
| 217 |
-
if i == 0 or token_idx != token_idxs[i-1]:
|
| 218 |
-
timesteps.append(i)
|
| 219 |
-
return timesteps
|
| 220 |
-
|
| 221 |
-
def decode(self, emissions):
|
| 222 |
-
B, T, N = emissions.size()
|
| 223 |
-
hypos = []
|
| 224 |
-
for b in range(B):
|
| 225 |
-
emissions_ptr = emissions.data_ptr() + 4 * b * emissions.stride(0)
|
| 226 |
-
results = self.decoder.decode(emissions_ptr, T, N)
|
| 227 |
-
|
| 228 |
-
nbest_results = results[: self.nbest]
|
| 229 |
-
hypos.append(
|
| 230 |
-
[
|
| 231 |
-
{
|
| 232 |
-
"tokens": self.get_tokens(result.tokens),
|
| 233 |
-
"score": result.score,
|
| 234 |
-
"timesteps": self.get_timesteps(result.tokens),
|
| 235 |
-
"words": [
|
| 236 |
-
self.word_dict.get_entry(x) for x in result.words if x >= 0
|
| 237 |
-
],
|
| 238 |
-
}
|
| 239 |
-
for result in nbest_results
|
| 240 |
-
]
|
| 241 |
-
)
|
| 242 |
-
return hypos
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
FairseqLMState = namedtuple("FairseqLMState", ["prefix", "incremental_state", "probs"])
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
class FairseqLM(LM):
|
| 249 |
-
def __init__(self, dictionary, model):
|
| 250 |
-
LM.__init__(self)
|
| 251 |
-
self.dictionary = dictionary
|
| 252 |
-
self.model = model
|
| 253 |
-
self.unk = self.dictionary.unk()
|
| 254 |
-
|
| 255 |
-
self.save_incremental = False # this currently does not work properly
|
| 256 |
-
self.max_cache = 20_000
|
| 257 |
-
|
| 258 |
-
model.cuda()
|
| 259 |
-
model.eval()
|
| 260 |
-
model.make_generation_fast_()
|
| 261 |
-
|
| 262 |
-
self.states = {}
|
| 263 |
-
self.stateq = deque()
|
| 264 |
-
|
| 265 |
-
def start(self, start_with_nothing):
|
| 266 |
-
state = LMState()
|
| 267 |
-
prefix = torch.LongTensor([[self.dictionary.eos()]])
|
| 268 |
-
incremental_state = {} if self.save_incremental else None
|
| 269 |
-
with torch.no_grad():
|
| 270 |
-
res = self.model(prefix.cuda(), incremental_state=incremental_state)
|
| 271 |
-
probs = self.model.get_normalized_probs(res, log_probs=True, sample=None)
|
| 272 |
-
|
| 273 |
-
if incremental_state is not None:
|
| 274 |
-
incremental_state = apply_to_sample(lambda x: x.cpu(), incremental_state)
|
| 275 |
-
self.states[state] = FairseqLMState(
|
| 276 |
-
prefix.numpy(), incremental_state, probs[0, -1].cpu().numpy()
|
| 277 |
-
)
|
| 278 |
-
self.stateq.append(state)
|
| 279 |
-
|
| 280 |
-
return state
|
| 281 |
-
|
| 282 |
-
def score(self, state: LMState, token_index: int, no_cache: bool = False):
|
| 283 |
-
"""
|
| 284 |
-
Evaluate language model based on the current lm state and new word
|
| 285 |
-
Parameters:
|
| 286 |
-
-----------
|
| 287 |
-
state: current lm state
|
| 288 |
-
token_index: index of the word
|
| 289 |
-
(can be lexicon index then you should store inside LM the
|
| 290 |
-
mapping between indices of lexicon and lm, or lm index of a word)
|
| 291 |
-
|
| 292 |
-
Returns:
|
| 293 |
-
--------
|
| 294 |
-
(LMState, float): pair of (new state, score for the current word)
|
| 295 |
-
"""
|
| 296 |
-
curr_state = self.states[state]
|
| 297 |
-
|
| 298 |
-
def trim_cache(targ_size):
|
| 299 |
-
while len(self.stateq) > targ_size:
|
| 300 |
-
rem_k = self.stateq.popleft()
|
| 301 |
-
rem_st = self.states[rem_k]
|
| 302 |
-
rem_st = FairseqLMState(rem_st.prefix, None, None)
|
| 303 |
-
self.states[rem_k] = rem_st
|
| 304 |
-
|
| 305 |
-
if curr_state.probs is None:
|
| 306 |
-
new_incremental_state = (
|
| 307 |
-
curr_state.incremental_state.copy()
|
| 308 |
-
if curr_state.incremental_state is not None
|
| 309 |
-
else None
|
| 310 |
-
)
|
| 311 |
-
with torch.no_grad():
|
| 312 |
-
if new_incremental_state is not None:
|
| 313 |
-
new_incremental_state = apply_to_sample(
|
| 314 |
-
lambda x: x.cuda(), new_incremental_state
|
| 315 |
-
)
|
| 316 |
-
elif self.save_incremental:
|
| 317 |
-
new_incremental_state = {}
|
| 318 |
-
|
| 319 |
-
res = self.model(
|
| 320 |
-
torch.from_numpy(curr_state.prefix).cuda(),
|
| 321 |
-
incremental_state=new_incremental_state,
|
| 322 |
-
)
|
| 323 |
-
probs = self.model.get_normalized_probs(
|
| 324 |
-
res, log_probs=True, sample=None
|
| 325 |
-
)
|
| 326 |
-
|
| 327 |
-
if new_incremental_state is not None:
|
| 328 |
-
new_incremental_state = apply_to_sample(
|
| 329 |
-
lambda x: x.cpu(), new_incremental_state
|
| 330 |
-
)
|
| 331 |
-
|
| 332 |
-
curr_state = FairseqLMState(
|
| 333 |
-
curr_state.prefix, new_incremental_state, probs[0, -1].cpu().numpy()
|
| 334 |
-
)
|
| 335 |
-
|
| 336 |
-
if not no_cache:
|
| 337 |
-
self.states[state] = curr_state
|
| 338 |
-
self.stateq.append(state)
|
| 339 |
-
|
| 340 |
-
score = curr_state.probs[token_index].item()
|
| 341 |
-
|
| 342 |
-
trim_cache(self.max_cache)
|
| 343 |
-
|
| 344 |
-
outstate = state.child(token_index)
|
| 345 |
-
if outstate not in self.states and not no_cache:
|
| 346 |
-
prefix = np.concatenate(
|
| 347 |
-
[curr_state.prefix, torch.LongTensor([[token_index]])], -1
|
| 348 |
-
)
|
| 349 |
-
incr_state = curr_state.incremental_state
|
| 350 |
-
|
| 351 |
-
self.states[outstate] = FairseqLMState(prefix, incr_state, None)
|
| 352 |
-
|
| 353 |
-
if token_index == self.unk:
|
| 354 |
-
score = float("-inf")
|
| 355 |
-
|
| 356 |
-
return outstate, score
|
| 357 |
-
|
| 358 |
-
def finish(self, state: LMState):
|
| 359 |
-
"""
|
| 360 |
-
Evaluate eos for language model based on the current lm state
|
| 361 |
-
|
| 362 |
-
Returns:
|
| 363 |
-
--------
|
| 364 |
-
(LMState, float): pair of (new state, score for the current word)
|
| 365 |
-
"""
|
| 366 |
-
return self.score(state, self.dictionary.eos())
|
| 367 |
-
|
| 368 |
-
def empty_cache(self):
|
| 369 |
-
self.states = {}
|
| 370 |
-
self.stateq = deque()
|
| 371 |
-
gc.collect()
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
class W2lFairseqLMDecoder(W2lDecoder):
|
| 375 |
-
def __init__(self, args, tgt_dict):
|
| 376 |
-
super().__init__(args, tgt_dict)
|
| 377 |
-
|
| 378 |
-
self.unit_lm = getattr(args, "unit_lm", False)
|
| 379 |
-
|
| 380 |
-
self.lexicon = load_words(args.lexicon) if args.lexicon else None
|
| 381 |
-
self.idx_to_wrd = {}
|
| 382 |
-
|
| 383 |
-
checkpoint = torch.load(args.kenlm_model, map_location="cpu")
|
| 384 |
-
|
| 385 |
-
if "cfg" in checkpoint and checkpoint["cfg"] is not None:
|
| 386 |
-
lm_args = checkpoint["cfg"]
|
| 387 |
-
else:
|
| 388 |
-
lm_args = convert_namespace_to_omegaconf(checkpoint["args"])
|
| 389 |
-
|
| 390 |
-
with open_dict(lm_args.task):
|
| 391 |
-
lm_args.task.data = osp.dirname(args.kenlm_model)
|
| 392 |
-
|
| 393 |
-
task = tasks.setup_task(lm_args.task)
|
| 394 |
-
model = task.build_model(lm_args.model)
|
| 395 |
-
model.load_state_dict(checkpoint["model"], strict=False)
|
| 396 |
-
|
| 397 |
-
self.trie = Trie(self.vocab_size, self.silence)
|
| 398 |
-
|
| 399 |
-
self.word_dict = task.dictionary
|
| 400 |
-
self.unk_word = self.word_dict.unk()
|
| 401 |
-
self.lm = FairseqLM(self.word_dict, model)
|
| 402 |
-
|
| 403 |
-
if self.lexicon:
|
| 404 |
-
start_state = self.lm.start(False)
|
| 405 |
-
for i, (word, spellings) in enumerate(self.lexicon.items()):
|
| 406 |
-
if self.unit_lm:
|
| 407 |
-
word_idx = i
|
| 408 |
-
self.idx_to_wrd[i] = word
|
| 409 |
-
score = 0
|
| 410 |
-
else:
|
| 411 |
-
word_idx = self.word_dict.index(word)
|
| 412 |
-
_, score = self.lm.score(start_state, word_idx, no_cache=True)
|
| 413 |
-
|
| 414 |
-
for spelling in spellings:
|
| 415 |
-
spelling_idxs = [tgt_dict.index(token) for token in spelling]
|
| 416 |
-
assert (
|
| 417 |
-
tgt_dict.unk() not in spelling_idxs
|
| 418 |
-
), f"{spelling} {spelling_idxs}"
|
| 419 |
-
self.trie.insert(spelling_idxs, word_idx, score)
|
| 420 |
-
self.trie.smear(SmearingMode.MAX)
|
| 421 |
-
|
| 422 |
-
self.decoder_opts = LexiconDecoderOptions(
|
| 423 |
-
beam_size=args.beam,
|
| 424 |
-
beam_size_token=int(getattr(args, "beam_size_token", len(tgt_dict))),
|
| 425 |
-
beam_threshold=args.beam_threshold,
|
| 426 |
-
lm_weight=args.lm_weight,
|
| 427 |
-
word_score=args.word_score,
|
| 428 |
-
unk_score=args.unk_weight,
|
| 429 |
-
sil_score=args.sil_weight,
|
| 430 |
-
log_add=False,
|
| 431 |
-
criterion_type=self.criterion_type,
|
| 432 |
-
)
|
| 433 |
-
|
| 434 |
-
self.decoder = LexiconDecoder(
|
| 435 |
-
self.decoder_opts,
|
| 436 |
-
self.trie,
|
| 437 |
-
self.lm,
|
| 438 |
-
self.silence,
|
| 439 |
-
self.blank,
|
| 440 |
-
self.unk_word,
|
| 441 |
-
[],
|
| 442 |
-
self.unit_lm,
|
| 443 |
-
)
|
| 444 |
-
else:
|
| 445 |
-
assert args.unit_lm, "lexicon free decoding can only be done with a unit language model"
|
| 446 |
-
from flashlight.lib.text.decoder import (
|
| 447 |
-
LexiconFreeDecoder,
|
| 448 |
-
LexiconFreeDecoderOptions,
|
| 449 |
-
)
|
| 450 |
-
|
| 451 |
-
d = {w: [[w]] for w in tgt_dict.symbols}
|
| 452 |
-
self.word_dict = create_word_dict(d)
|
| 453 |
-
self.lm = KenLM(args.kenlm_model, self.word_dict)
|
| 454 |
-
self.decoder_opts = LexiconFreeDecoderOptions(
|
| 455 |
-
beam_size=args.beam,
|
| 456 |
-
beam_size_token=int(getattr(args, "beam_size_token", len(tgt_dict))),
|
| 457 |
-
beam_threshold=args.beam_threshold,
|
| 458 |
-
lm_weight=args.lm_weight,
|
| 459 |
-
sil_score=args.sil_weight,
|
| 460 |
-
log_add=False,
|
| 461 |
-
criterion_type=self.criterion_type,
|
| 462 |
-
)
|
| 463 |
-
self.decoder = LexiconFreeDecoder(
|
| 464 |
-
self.decoder_opts, self.lm, self.silence, self.blank, []
|
| 465 |
-
)
|
| 466 |
-
|
| 467 |
-
def decode(self, emissions):
|
| 468 |
-
B, T, N = emissions.size()
|
| 469 |
-
hypos = []
|
| 470 |
-
|
| 471 |
-
def idx_to_word(idx):
|
| 472 |
-
if self.unit_lm:
|
| 473 |
-
return self.idx_to_wrd[idx]
|
| 474 |
-
else:
|
| 475 |
-
return self.word_dict[idx]
|
| 476 |
-
|
| 477 |
-
def make_hypo(result):
|
| 478 |
-
hypo = {"tokens": self.get_tokens(result.tokens), "score": result.score}
|
| 479 |
-
if self.lexicon:
|
| 480 |
-
hypo["words"] = [idx_to_word(x) for x in result.words if x >= 0]
|
| 481 |
-
return hypo
|
| 482 |
-
|
| 483 |
-
for b in range(B):
|
| 484 |
-
emissions_ptr = emissions.data_ptr() + 4 * b * emissions.stride(0)
|
| 485 |
-
results = self.decoder.decode(emissions_ptr, T, N)
|
| 486 |
-
|
| 487 |
-
nbest_results = results[: self.nbest]
|
| 488 |
-
hypos.append([make_hypo(result) for result in nbest_results])
|
| 489 |
-
self.lm.empty_cache()
|
| 490 |
-
|
| 491 |
-
return hypos
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/README.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
| 1 |
-
Speech Synthesis (S^2)
|
| 2 |
-
===
|
| 3 |
-
[https://arxiv.org/abs/2109.06912](https://arxiv.org/abs/2109.06912)
|
| 4 |
-
|
| 5 |
-
Speech synthesis with fairseq.
|
| 6 |
-
|
| 7 |
-
## Features
|
| 8 |
-
|
| 9 |
-
- Autoregressive and non-autoregressive models
|
| 10 |
-
- Multi-speaker synthesis
|
| 11 |
-
- Audio preprocessing (denoising, VAD, etc.) for less curated data
|
| 12 |
-
- Automatic metrics for model development
|
| 13 |
-
- Similar data configuration as [S2T](../speech_to_text/README.md)
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
## Examples
|
| 17 |
-
- [Single-speaker synthesis on LJSpeech](docs/ljspeech_example.md)
|
| 18 |
-
- [Multi-speaker synthesis on VCTK](docs/vctk_example.md)
|
| 19 |
-
- [Multi-speaker synthesis on Common Voice](docs/common_voice_example.md)
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
## Citation
|
| 23 |
-
Please cite as:
|
| 24 |
-
```
|
| 25 |
-
@article{wang2021fairseqs2,
|
| 26 |
-
title={fairseq S\^{} 2: A Scalable and Integrable Speech Synthesis Toolkit},
|
| 27 |
-
author={Wang, Changhan and Hsu, Wei-Ning and Adi, Yossi and Polyak, Adam and Lee, Ann and Chen, Peng-Jen and Gu, Jiatao and Pino, Juan},
|
| 28 |
-
journal={arXiv preprint arXiv:2109.06912},
|
| 29 |
-
year={2021}
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
@inproceedings{ott2019fairseq,
|
| 33 |
-
title = {fairseq: A Fast, Extensible Toolkit for Sequence Modeling},
|
| 34 |
-
author = {Myle Ott and Sergey Edunov and Alexei Baevski and Angela Fan and Sam Gross and Nathan Ng and David Grangier and Michael Auli},
|
| 35 |
-
booktitle = {Proceedings of NAACL-HLT 2019: Demonstrations},
|
| 36 |
-
year = {2019},
|
| 37 |
-
}
|
| 38 |
-
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/__init__.py
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/data_utils.py
DELETED
|
@@ -1,347 +0,0 @@
|
|
| 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 io
|
| 7 |
-
import os
|
| 8 |
-
import tempfile
|
| 9 |
-
import zipfile
|
| 10 |
-
from dataclasses import dataclass
|
| 11 |
-
from itertools import groupby
|
| 12 |
-
from pathlib import Path
|
| 13 |
-
from typing import Dict, List, Optional
|
| 14 |
-
|
| 15 |
-
import numpy as np
|
| 16 |
-
import torch
|
| 17 |
-
import torch.nn.functional as F
|
| 18 |
-
from examples.speech_to_text.data_utils import load_tsv_to_dicts
|
| 19 |
-
from fairseq.data.audio.audio_utils import (
|
| 20 |
-
TTSMelScale,
|
| 21 |
-
TTSSpectrogram,
|
| 22 |
-
is_npy_data,
|
| 23 |
-
parse_path,
|
| 24 |
-
read_from_stored_zip,
|
| 25 |
-
)
|
| 26 |
-
from tqdm import tqdm
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def trim_or_pad_to_target_length(
|
| 30 |
-
data_1d_or_2d: np.ndarray, target_length: int
|
| 31 |
-
) -> np.ndarray:
|
| 32 |
-
assert len(data_1d_or_2d.shape) in {1, 2}
|
| 33 |
-
delta = data_1d_or_2d.shape[0] - target_length
|
| 34 |
-
if delta >= 0: # trim if being longer
|
| 35 |
-
data_1d_or_2d = data_1d_or_2d[: target_length]
|
| 36 |
-
else: # pad if being shorter
|
| 37 |
-
if len(data_1d_or_2d.shape) == 1:
|
| 38 |
-
data_1d_or_2d = np.concatenate(
|
| 39 |
-
[data_1d_or_2d, np.zeros(-delta)], axis=0
|
| 40 |
-
)
|
| 41 |
-
else:
|
| 42 |
-
data_1d_or_2d = np.concatenate(
|
| 43 |
-
[data_1d_or_2d, np.zeros((-delta, data_1d_or_2d.shape[1]))],
|
| 44 |
-
axis=0
|
| 45 |
-
)
|
| 46 |
-
return data_1d_or_2d
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
def extract_logmel_spectrogram(
|
| 50 |
-
waveform: torch.Tensor, sample_rate: int,
|
| 51 |
-
output_path: Optional[Path] = None, win_length: int = 1024,
|
| 52 |
-
hop_length: int = 256, n_fft: int = 1024,
|
| 53 |
-
win_fn: callable = torch.hann_window, n_mels: int = 80,
|
| 54 |
-
f_min: float = 0., f_max: float = 8000, eps: float = 1e-5,
|
| 55 |
-
overwrite: bool = False, target_length: Optional[int] = None
|
| 56 |
-
):
|
| 57 |
-
if output_path is not None and output_path.is_file() and not overwrite:
|
| 58 |
-
return
|
| 59 |
-
|
| 60 |
-
spectrogram_transform = TTSSpectrogram(
|
| 61 |
-
n_fft=n_fft, win_length=win_length, hop_length=hop_length,
|
| 62 |
-
window_fn=win_fn
|
| 63 |
-
)
|
| 64 |
-
mel_scale_transform = TTSMelScale(
|
| 65 |
-
n_mels=n_mels, sample_rate=sample_rate, f_min=f_min, f_max=f_max,
|
| 66 |
-
n_stft=n_fft // 2 + 1
|
| 67 |
-
)
|
| 68 |
-
spectrogram = spectrogram_transform(waveform)
|
| 69 |
-
mel_spec = mel_scale_transform(spectrogram)
|
| 70 |
-
logmel_spec = torch.clamp(mel_spec, min=eps).log()
|
| 71 |
-
assert len(logmel_spec.shape) == 3 and logmel_spec.shape[0] == 1
|
| 72 |
-
logmel_spec = logmel_spec.squeeze().t() # D x T -> T x D
|
| 73 |
-
if target_length is not None:
|
| 74 |
-
logmel_spec = trim_or_pad_to_target_length(logmel_spec, target_length)
|
| 75 |
-
|
| 76 |
-
if output_path is not None:
|
| 77 |
-
np.save(output_path.as_posix(), logmel_spec)
|
| 78 |
-
else:
|
| 79 |
-
return logmel_spec
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def extract_pitch(
|
| 83 |
-
waveform: torch.Tensor, sample_rate: int,
|
| 84 |
-
output_path: Optional[Path] = None, hop_length: int = 256,
|
| 85 |
-
log_scale: bool = True, phoneme_durations: Optional[List[int]] = None
|
| 86 |
-
):
|
| 87 |
-
if output_path is not None and output_path.is_file():
|
| 88 |
-
return
|
| 89 |
-
|
| 90 |
-
try:
|
| 91 |
-
import pyworld
|
| 92 |
-
except ImportError:
|
| 93 |
-
raise ImportError("Please install PyWORLD: pip install pyworld")
|
| 94 |
-
|
| 95 |
-
_waveform = waveform.squeeze(0).double().numpy()
|
| 96 |
-
pitch, t = pyworld.dio(
|
| 97 |
-
_waveform, sample_rate, frame_period=hop_length / sample_rate * 1000
|
| 98 |
-
)
|
| 99 |
-
pitch = pyworld.stonemask(_waveform, pitch, t, sample_rate)
|
| 100 |
-
|
| 101 |
-
if phoneme_durations is not None:
|
| 102 |
-
pitch = trim_or_pad_to_target_length(pitch, sum(phoneme_durations))
|
| 103 |
-
try:
|
| 104 |
-
from scipy.interpolate import interp1d
|
| 105 |
-
except ImportError:
|
| 106 |
-
raise ImportError("Please install SciPy: pip install scipy")
|
| 107 |
-
nonzero_ids = np.where(pitch != 0)[0]
|
| 108 |
-
if len(nonzero_ids) == 0:
|
| 109 |
-
print((f"{output_path} has all empty values in the pitch contour"))
|
| 110 |
-
return
|
| 111 |
-
elif len(nonzero_ids) == 1:
|
| 112 |
-
print((f"{output_path} has only one non-zero values in the pitch contour"))
|
| 113 |
-
return
|
| 114 |
-
else:
|
| 115 |
-
interp_fn = interp1d(
|
| 116 |
-
nonzero_ids,
|
| 117 |
-
pitch[nonzero_ids],
|
| 118 |
-
fill_value=(pitch[nonzero_ids[0]], pitch[nonzero_ids[-1]]),
|
| 119 |
-
bounds_error=False,
|
| 120 |
-
)
|
| 121 |
-
pitch = interp_fn(np.arange(0, len(pitch)))
|
| 122 |
-
d_cumsum = np.cumsum(np.concatenate([np.array([0]), phoneme_durations]))
|
| 123 |
-
pitch = np.array(
|
| 124 |
-
[
|
| 125 |
-
np.mean(pitch[d_cumsum[i-1]: d_cumsum[i]])
|
| 126 |
-
for i in range(1, len(d_cumsum))
|
| 127 |
-
]
|
| 128 |
-
)
|
| 129 |
-
assert len(pitch) == len(phoneme_durations)
|
| 130 |
-
|
| 131 |
-
if log_scale:
|
| 132 |
-
pitch = np.log(pitch + 1)
|
| 133 |
-
|
| 134 |
-
if output_path is not None:
|
| 135 |
-
np.save(output_path.as_posix(), pitch)
|
| 136 |
-
else:
|
| 137 |
-
return pitch
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
def extract_energy(
|
| 141 |
-
waveform: torch.Tensor, output_path: Optional[Path] = None,
|
| 142 |
-
hop_length: int = 256, n_fft: int = 1024, log_scale: bool = True,
|
| 143 |
-
phoneme_durations: Optional[List[int]] = None
|
| 144 |
-
):
|
| 145 |
-
if output_path is not None and output_path.is_file():
|
| 146 |
-
return
|
| 147 |
-
|
| 148 |
-
assert len(waveform.shape) == 2 and waveform.shape[0] == 1
|
| 149 |
-
waveform = waveform.view(1, 1, waveform.shape[1])
|
| 150 |
-
waveform = F.pad(
|
| 151 |
-
waveform.unsqueeze(1), [n_fft // 2, n_fft // 2, 0, 0],
|
| 152 |
-
mode="reflect"
|
| 153 |
-
)
|
| 154 |
-
waveform = waveform.squeeze(1)
|
| 155 |
-
|
| 156 |
-
fourier_basis = np.fft.fft(np.eye(n_fft))
|
| 157 |
-
cutoff = int((n_fft / 2 + 1))
|
| 158 |
-
fourier_basis = np.vstack(
|
| 159 |
-
[np.real(fourier_basis[:cutoff, :]),
|
| 160 |
-
np.imag(fourier_basis[:cutoff, :])]
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
forward_basis = torch.FloatTensor(fourier_basis[:, None, :])
|
| 164 |
-
forward_transform = F.conv1d(
|
| 165 |
-
waveform, forward_basis, stride=hop_length, padding=0
|
| 166 |
-
)
|
| 167 |
-
|
| 168 |
-
real_part = forward_transform[:, :cutoff, :]
|
| 169 |
-
imag_part = forward_transform[:, cutoff:, :]
|
| 170 |
-
magnitude = torch.sqrt(real_part ** 2 + imag_part ** 2)
|
| 171 |
-
energy = torch.norm(magnitude, dim=1).squeeze(0).numpy()
|
| 172 |
-
|
| 173 |
-
if phoneme_durations is not None:
|
| 174 |
-
energy = trim_or_pad_to_target_length(energy, sum(phoneme_durations))
|
| 175 |
-
d_cumsum = np.cumsum(np.concatenate([np.array([0]), phoneme_durations]))
|
| 176 |
-
energy = np.array(
|
| 177 |
-
[
|
| 178 |
-
np.mean(energy[d_cumsum[i - 1]: d_cumsum[i]])
|
| 179 |
-
for i in range(1, len(d_cumsum))
|
| 180 |
-
]
|
| 181 |
-
)
|
| 182 |
-
assert len(energy) == len(phoneme_durations)
|
| 183 |
-
|
| 184 |
-
if log_scale:
|
| 185 |
-
energy = np.log(energy + 1)
|
| 186 |
-
|
| 187 |
-
if output_path is not None:
|
| 188 |
-
np.save(output_path.as_posix(), energy)
|
| 189 |
-
else:
|
| 190 |
-
return energy
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
def get_global_cmvn(feature_root: Path, output_path: Optional[Path] = None):
|
| 194 |
-
mean_x, mean_x2, n_frames = None, None, 0
|
| 195 |
-
feature_paths = feature_root.glob("*.npy")
|
| 196 |
-
for p in tqdm(feature_paths):
|
| 197 |
-
with open(p, 'rb') as f:
|
| 198 |
-
frames = np.load(f).squeeze()
|
| 199 |
-
|
| 200 |
-
n_frames += frames.shape[0]
|
| 201 |
-
|
| 202 |
-
cur_mean_x = frames.sum(axis=0)
|
| 203 |
-
if mean_x is None:
|
| 204 |
-
mean_x = cur_mean_x
|
| 205 |
-
else:
|
| 206 |
-
mean_x += cur_mean_x
|
| 207 |
-
|
| 208 |
-
cur_mean_x2 = (frames ** 2).sum(axis=0)
|
| 209 |
-
if mean_x2 is None:
|
| 210 |
-
mean_x2 = cur_mean_x2
|
| 211 |
-
else:
|
| 212 |
-
mean_x2 += cur_mean_x2
|
| 213 |
-
|
| 214 |
-
mean_x /= n_frames
|
| 215 |
-
mean_x2 /= n_frames
|
| 216 |
-
var_x = mean_x2 - mean_x ** 2
|
| 217 |
-
std_x = np.sqrt(np.maximum(var_x, 1e-10))
|
| 218 |
-
|
| 219 |
-
if output_path is not None:
|
| 220 |
-
with open(output_path, 'wb') as f:
|
| 221 |
-
np.savez(f, mean=mean_x, std=std_x)
|
| 222 |
-
else:
|
| 223 |
-
return {"mean": mean_x, "std": std_x}
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
def ipa_phonemize(text, lang="en-us", use_g2p=False):
|
| 227 |
-
if use_g2p:
|
| 228 |
-
assert lang == "en-us", "g2pE phonemizer only works for en-us"
|
| 229 |
-
try:
|
| 230 |
-
from g2p_en import G2p
|
| 231 |
-
g2p = G2p()
|
| 232 |
-
return " ".join("|" if p == " " else p for p in g2p(text))
|
| 233 |
-
except ImportError:
|
| 234 |
-
raise ImportError(
|
| 235 |
-
"Please install phonemizer: pip install g2p_en"
|
| 236 |
-
)
|
| 237 |
-
else:
|
| 238 |
-
try:
|
| 239 |
-
from phonemizer import phonemize
|
| 240 |
-
from phonemizer.separator import Separator
|
| 241 |
-
return phonemize(
|
| 242 |
-
text, backend='espeak', language=lang,
|
| 243 |
-
separator=Separator(word="| ", phone=" ")
|
| 244 |
-
)
|
| 245 |
-
except ImportError:
|
| 246 |
-
raise ImportError(
|
| 247 |
-
"Please install phonemizer: pip install phonemizer"
|
| 248 |
-
)
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
@dataclass
|
| 252 |
-
class ForceAlignmentInfo(object):
|
| 253 |
-
tokens: List[str]
|
| 254 |
-
frame_durations: List[int]
|
| 255 |
-
start_sec: Optional[float]
|
| 256 |
-
end_sec: Optional[float]
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
def get_mfa_alignment_by_sample_id(
|
| 260 |
-
textgrid_zip_path: str, sample_id: str, sample_rate: int,
|
| 261 |
-
hop_length: int, silence_phones: List[str] = ("sil", "sp", "spn")
|
| 262 |
-
) -> ForceAlignmentInfo:
|
| 263 |
-
try:
|
| 264 |
-
import tgt
|
| 265 |
-
except ImportError:
|
| 266 |
-
raise ImportError("Please install TextGridTools: pip install tgt")
|
| 267 |
-
|
| 268 |
-
filename = f"{sample_id}.TextGrid"
|
| 269 |
-
out_root = Path(tempfile.gettempdir())
|
| 270 |
-
tgt_path = out_root / filename
|
| 271 |
-
with zipfile.ZipFile(textgrid_zip_path) as f_zip:
|
| 272 |
-
f_zip.extract(filename, path=out_root)
|
| 273 |
-
textgrid = tgt.io.read_textgrid(tgt_path.as_posix())
|
| 274 |
-
os.remove(tgt_path)
|
| 275 |
-
|
| 276 |
-
phones, frame_durations = [], []
|
| 277 |
-
start_sec, end_sec, end_idx = 0, 0, 0
|
| 278 |
-
for t in textgrid.get_tier_by_name("phones")._objects:
|
| 279 |
-
s, e, p = t.start_time, t.end_time, t.text
|
| 280 |
-
# Trim leading silences
|
| 281 |
-
if len(phones) == 0:
|
| 282 |
-
if p in silence_phones:
|
| 283 |
-
continue
|
| 284 |
-
else:
|
| 285 |
-
start_sec = s
|
| 286 |
-
phones.append(p)
|
| 287 |
-
if p not in silence_phones:
|
| 288 |
-
end_sec = e
|
| 289 |
-
end_idx = len(phones)
|
| 290 |
-
r = sample_rate / hop_length
|
| 291 |
-
frame_durations.append(int(np.round(e * r) - np.round(s * r)))
|
| 292 |
-
# Trim tailing silences
|
| 293 |
-
phones = phones[:end_idx]
|
| 294 |
-
frame_durations = frame_durations[:end_idx]
|
| 295 |
-
|
| 296 |
-
return ForceAlignmentInfo(
|
| 297 |
-
tokens=phones, frame_durations=frame_durations, start_sec=start_sec,
|
| 298 |
-
end_sec=end_sec
|
| 299 |
-
)
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
def get_mfa_alignment(
|
| 303 |
-
textgrid_zip_path: str, sample_ids: List[str], sample_rate: int,
|
| 304 |
-
hop_length: int
|
| 305 |
-
) -> Dict[str, ForceAlignmentInfo]:
|
| 306 |
-
return {
|
| 307 |
-
i: get_mfa_alignment_by_sample_id(
|
| 308 |
-
textgrid_zip_path, i, sample_rate, hop_length
|
| 309 |
-
) for i in tqdm(sample_ids)
|
| 310 |
-
}
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
def get_unit_alignment(
|
| 314 |
-
id_to_unit_tsv_path: str, sample_ids: List[str]
|
| 315 |
-
) -> Dict[str, ForceAlignmentInfo]:
|
| 316 |
-
id_to_units = {
|
| 317 |
-
e["id"]: e["units"] for e in load_tsv_to_dicts(id_to_unit_tsv_path)
|
| 318 |
-
}
|
| 319 |
-
id_to_units = {i: id_to_units[i].split() for i in sample_ids}
|
| 320 |
-
id_to_units_collapsed = {
|
| 321 |
-
i: [uu for uu, _ in groupby(u)] for i, u in id_to_units.items()
|
| 322 |
-
}
|
| 323 |
-
id_to_durations = {
|
| 324 |
-
i: [len(list(g)) for _, g in groupby(u)] for i, u in id_to_units.items()
|
| 325 |
-
}
|
| 326 |
-
|
| 327 |
-
return {
|
| 328 |
-
i: ForceAlignmentInfo(
|
| 329 |
-
tokens=id_to_units_collapsed[i], frame_durations=id_to_durations[i],
|
| 330 |
-
start_sec=None, end_sec=None
|
| 331 |
-
)
|
| 332 |
-
for i in sample_ids
|
| 333 |
-
}
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
def get_feature_value_min_max(feature_paths: List[str]):
|
| 337 |
-
v_min, v_max = 1e-8, -1e-8
|
| 338 |
-
for p in tqdm(feature_paths):
|
| 339 |
-
_path, slice_ptr = parse_path(p)
|
| 340 |
-
assert len(slice_ptr) == 2
|
| 341 |
-
byte_data = read_from_stored_zip(_path, slice_ptr[0], slice_ptr[1])
|
| 342 |
-
assert is_npy_data(byte_data)
|
| 343 |
-
path_or_fp = io.BytesIO(byte_data)
|
| 344 |
-
features = np.load(path_or_fp).squeeze()
|
| 345 |
-
v_min = min(v_min, features.min().item())
|
| 346 |
-
v_max = max(v_max, features.max().item())
|
| 347 |
-
return v_min, v_max
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/common_voice_example.md
DELETED
|
@@ -1,67 +0,0 @@
|
|
| 1 |
-
[[Back]](..)
|
| 2 |
-
|
| 3 |
-
# Common Voice
|
| 4 |
-
|
| 5 |
-
[Common Voice](https://commonvoice.mozilla.org/en/datasets) is a public domain speech corpus with 11.2K hours of read
|
| 6 |
-
speech in 76 languages (the latest version 7.0). We provide examples for building
|
| 7 |
-
[Transformer](https://arxiv.org/abs/1809.08895) models on this dataset.
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
## Data preparation
|
| 11 |
-
[Download](https://commonvoice.mozilla.org/en/datasets) and unpack Common Voice v4 to a path `${DATA_ROOT}/${LANG_ID}`.
|
| 12 |
-
Create splits and generate audio manifests with
|
| 13 |
-
```bash
|
| 14 |
-
python -m examples.speech_synthesis.preprocessing.get_common_voice_audio_manifest \
|
| 15 |
-
--data-root ${DATA_ROOT} \
|
| 16 |
-
--lang ${LANG_ID} \
|
| 17 |
-
--output-manifest-root ${AUDIO_MANIFEST_ROOT} --convert-to-wav
|
| 18 |
-
```
|
| 19 |
-
|
| 20 |
-
To denoise audio and trim leading/trailing silence using signal processing based VAD, run
|
| 21 |
-
```bash
|
| 22 |
-
for SPLIT in dev test train; do
|
| 23 |
-
python -m examples.speech_synthesis.preprocessing.denoise_and_vad_audio \
|
| 24 |
-
--audio-manifest ${AUDIO_MANIFEST_ROOT}/${SPLIT}.audio.tsv \
|
| 25 |
-
--output-dir ${PROCESSED_DATA_ROOT} \
|
| 26 |
-
--denoise --vad --vad-agg-level 2
|
| 27 |
-
done
|
| 28 |
-
```
|
| 29 |
-
|
| 30 |
-
which generates a new audio TSV manifest under `${PROCESSED_DATA_ROOT}` with updated path to the processed audio and
|
| 31 |
-
a new column for SNR.
|
| 32 |
-
|
| 33 |
-
To do filtering by CER, follow the [Automatic Evaluation](../docs/ljspeech_example.md#automatic-evaluation) section to
|
| 34 |
-
run ASR model (add `--eval-target` to `get_eval_manifest` for evaluation on the reference audio; add `--err-unit char`
|
| 35 |
-
to `eval_asr` to compute CER instead of WER). The example-level CER is saved to
|
| 36 |
-
`${EVAL_OUTPUT_ROOT}/uer_cer.${SPLIT}.tsv`.
|
| 37 |
-
|
| 38 |
-
Then, extract log-Mel spectrograms, generate feature manifest and create data configuration YAML with
|
| 39 |
-
```bash
|
| 40 |
-
python -m examples.speech_synthesis.preprocessing.get_feature_manifest \
|
| 41 |
-
--audio-manifest-root ${AUDIO_MANIFEST_ROOT} \
|
| 42 |
-
--output-root ${FEATURE_MANIFEST_ROOT} \
|
| 43 |
-
--ipa-vocab --lang ${LANG_ID} \
|
| 44 |
-
--snr-threshold 15 \
|
| 45 |
-
--cer-threshold 0.1 --cer-tsv-path ${EVAL_OUTPUT_ROOT}/uer_cer.${SPLIT}.tsv
|
| 46 |
-
```
|
| 47 |
-
where we use phoneme inputs (`--ipa-vocab`) as example. For sample filtering, we set the SNR and CER threshold
|
| 48 |
-
to 15 and 10%, respectively.
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
## Training
|
| 52 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#transformer).)
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
## Inference
|
| 56 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#inference).)
|
| 57 |
-
|
| 58 |
-
## Automatic Evaluation
|
| 59 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#automatic-evaluation).)
|
| 60 |
-
|
| 61 |
-
## Results
|
| 62 |
-
|
| 63 |
-
| Language | Speakers | --arch | Params | Test MCD | Model |
|
| 64 |
-
|---|---|---|---|---|---|
|
| 65 |
-
| English | 200 | tts_transformer | 54M | 3.8 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2/cv4_en200_transformer_phn.tar) |
|
| 66 |
-
|
| 67 |
-
[[Back]](..)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/ljspeech_example.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
[[Back]](..)
|
| 2 |
-
|
| 3 |
-
# LJSpeech
|
| 4 |
-
|
| 5 |
-
[LJSpeech](https://keithito.com/LJ-Speech-Dataset) is a public domain TTS
|
| 6 |
-
corpus with around 24 hours of English speech sampled at 22.05kHz. We provide examples for building
|
| 7 |
-
[Transformer](https://arxiv.org/abs/1809.08895) and [FastSpeech 2](https://arxiv.org/abs/2006.04558)
|
| 8 |
-
models on this dataset.
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
## Data preparation
|
| 12 |
-
|
| 13 |
-
Download data, create splits and generate audio manifests with
|
| 14 |
-
```bash
|
| 15 |
-
python -m examples.speech_synthesis.preprocessing.get_ljspeech_audio_manifest \
|
| 16 |
-
--output-data-root ${AUDIO_DATA_ROOT} \
|
| 17 |
-
--output-manifest-root ${AUDIO_MANIFEST_ROOT}
|
| 18 |
-
```
|
| 19 |
-
|
| 20 |
-
Then, extract log-Mel spectrograms, generate feature manifest and create data configuration YAML with
|
| 21 |
-
```bash
|
| 22 |
-
python -m examples.speech_synthesis.preprocessing.get_feature_manifest \
|
| 23 |
-
--audio-manifest-root ${AUDIO_MANIFEST_ROOT} \
|
| 24 |
-
--output-root ${FEATURE_MANIFEST_ROOT} \
|
| 25 |
-
--ipa-vocab --use-g2p
|
| 26 |
-
```
|
| 27 |
-
where we use phoneme inputs (`--ipa-vocab --use-g2p`) as example.
|
| 28 |
-
|
| 29 |
-
FastSpeech 2 additionally requires frame durations, pitch and energy as auxiliary training targets.
|
| 30 |
-
Add `--add-fastspeech-targets` to include these fields in the feature manifests. We get frame durations either from
|
| 31 |
-
phoneme-level force-alignment or frame-level pseudo-text unit sequence. They should be pre-computed and specified via:
|
| 32 |
-
- `--textgrid-zip ${TEXT_GRID_ZIP_PATH}` for a ZIP file, inside which there is one
|
| 33 |
-
[TextGrid](https://www.fon.hum.uva.nl/praat/manual/TextGrid.html) file per sample to provide force-alignment info.
|
| 34 |
-
- `--id-to-units-tsv ${ID_TO_UNIT_TSV}` for a TSV file, where there are 2 columns for sample ID and
|
| 35 |
-
space-delimited pseudo-text unit sequence, respectively.
|
| 36 |
-
|
| 37 |
-
For your convenience, we provide pre-computed
|
| 38 |
-
[force-alignment](https://dl.fbaipublicfiles.com/fairseq/s2/ljspeech_mfa.zip) from
|
| 39 |
-
[Montreal Forced Aligner](https://github.com/MontrealCorpusTools/Montreal-Forced-Aligner) and
|
| 40 |
-
[pseudo-text units](s3://dl.fbaipublicfiles.com/fairseq/s2/ljspeech_hubert.tsv) from
|
| 41 |
-
[HuBERT](https://github.com/pytorch/fairseq/tree/main/examples/hubert). You can also generate them by yourself using
|
| 42 |
-
a different software or model.
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
## Training
|
| 46 |
-
#### Transformer
|
| 47 |
-
```bash
|
| 48 |
-
fairseq-train ${FEATURE_MANIFEST_ROOT} --save-dir ${SAVE_DIR} \
|
| 49 |
-
--config-yaml config.yaml --train-subset train --valid-subset dev \
|
| 50 |
-
--num-workers 4 --max-tokens 30000 --max-update 200000 \
|
| 51 |
-
--task text_to_speech --criterion tacotron2 --arch tts_transformer \
|
| 52 |
-
--clip-norm 5.0 --n-frames-per-step 4 --bce-pos-weight 5.0 \
|
| 53 |
-
--dropout 0.1 --attention-dropout 0.1 --activation-dropout 0.1 \
|
| 54 |
-
--encoder-normalize-before --decoder-normalize-before \
|
| 55 |
-
--optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
| 56 |
-
--seed 1 --update-freq 8 --eval-inference --best-checkpoint-metric mcd_loss
|
| 57 |
-
```
|
| 58 |
-
where `SAVE_DIR` is the checkpoint root path. We set `--update-freq 8` to simulate 8 GPUs with 1 GPU. You may want to
|
| 59 |
-
update it accordingly when using more than 1 GPU.
|
| 60 |
-
|
| 61 |
-
#### FastSpeech2
|
| 62 |
-
```bash
|
| 63 |
-
fairseq-train ${FEATURE_MANIFEST_ROOT} --save-dir ${SAVE_DIR} \
|
| 64 |
-
--config-yaml config.yaml --train-subset train --valid-subset dev \
|
| 65 |
-
--num-workers 4 --max-sentences 6 --max-update 200000 \
|
| 66 |
-
--task text_to_speech --criterion fastspeech2 --arch fastspeech2 \
|
| 67 |
-
--clip-norm 5.0 --n-frames-per-step 1 \
|
| 68 |
-
--dropout 0.1 --attention-dropout 0.1 \
|
| 69 |
-
--optimizer adam --lr 5e-4 --lr-scheduler inverse_sqrt --warmup-updates 4000 \
|
| 70 |
-
--seed 1 --update-freq 8 --eval-inference --best-checkpoint-metric mcd_loss
|
| 71 |
-
```
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
## Inference
|
| 75 |
-
Average the last 5 checkpoints, generate the test split spectrogram and waveform using the default Griffin-Lim vocoder:
|
| 76 |
-
```bash
|
| 77 |
-
SPLIT=test
|
| 78 |
-
CHECKPOINT_NAME=avg_last_5
|
| 79 |
-
CHECKPOINT_PATH=${SAVE_DIR}/checkpoint_${CHECKPOINT_NAME}.pt
|
| 80 |
-
python scripts/average_checkpoints.py --inputs ${SAVE_DIR} \
|
| 81 |
-
--num-epoch-checkpoints 5 \
|
| 82 |
-
--output ${CHECKPOINT_PATH}
|
| 83 |
-
|
| 84 |
-
python -m examples.speech_synthesis.generate_waveform ${FEATURE_MANIFEST_ROOT} \
|
| 85 |
-
--config-yaml config.yaml --gen-subset ${SPLIT} --task text_to_speech \
|
| 86 |
-
--path ${CHECKPOINT_PATH} --max-tokens 50000 --spec-bwd-max-iter 32 \
|
| 87 |
-
--dump-waveforms
|
| 88 |
-
```
|
| 89 |
-
which dumps files (waveform, feature, attention plot, etc.) to `${SAVE_DIR}/generate-${CHECKPOINT_NAME}-${SPLIT}`. To
|
| 90 |
-
re-synthesize target waveforms for automatic evaluation, add `--dump-target`.
|
| 91 |
-
|
| 92 |
-
## Automatic Evaluation
|
| 93 |
-
To start with, generate the manifest for synthetic speech, which will be taken as inputs by evaluation scripts.
|
| 94 |
-
```bash
|
| 95 |
-
python -m examples.speech_synthesis.evaluation.get_eval_manifest \
|
| 96 |
-
--generation-root ${SAVE_DIR}/generate-${CHECKPOINT_NAME}-${SPLIT} \
|
| 97 |
-
--audio-manifest ${AUDIO_MANIFEST_ROOT}/${SPLIT}.audio.tsv \
|
| 98 |
-
--output-path ${EVAL_OUTPUT_ROOT}/eval.tsv \
|
| 99 |
-
--vocoder griffin_lim --sample-rate 22050 --audio-format flac \
|
| 100 |
-
--use-resynthesized-target
|
| 101 |
-
```
|
| 102 |
-
Speech recognition (ASR) models usually operate at lower sample rates (e.g. 16kHz). For the WER/CER metric,
|
| 103 |
-
you may need to resample the audios accordingly --- add `--output-sample-rate 16000` for `generate_waveform.py` and
|
| 104 |
-
use `--sample-rate 16000` for `get_eval_manifest.py`.
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
#### WER/CER metric
|
| 108 |
-
We use wav2vec 2.0 ASR model as example. [Download](https://github.com/pytorch/fairseq/tree/main/examples/wav2vec)
|
| 109 |
-
the model checkpoint and dictionary, then compute WER/CER with
|
| 110 |
-
```bash
|
| 111 |
-
python -m examples.speech_synthesis.evaluation.eval_asr \
|
| 112 |
-
--audio-header syn --text-header text --err-unit char --split ${SPLIT} \
|
| 113 |
-
--w2v-ckpt ${WAV2VEC2_CHECKPOINT_PATH} --w2v-dict-dir ${WAV2VEC2_DICT_DIR} \
|
| 114 |
-
--raw-manifest ${EVAL_OUTPUT_ROOT}/eval_16khz.tsv --asr-dir ${EVAL_OUTPUT_ROOT}/asr
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
#### MCD/MSD metric
|
| 118 |
-
```bash
|
| 119 |
-
python -m examples.speech_synthesis.evaluation.eval_sp \
|
| 120 |
-
${EVAL_OUTPUT_ROOT}/eval.tsv --mcd --msd
|
| 121 |
-
```
|
| 122 |
-
|
| 123 |
-
#### F0 metrics
|
| 124 |
-
```bash
|
| 125 |
-
python -m examples.speech_synthesis.evaluation.eval_f0 \
|
| 126 |
-
${EVAL_OUTPUT_ROOT}/eval.tsv --gpe --vde --ffe
|
| 127 |
-
```
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
## Results
|
| 131 |
-
|
| 132 |
-
| --arch | Params | Test MCD | Model |
|
| 133 |
-
|---|---|---|---|
|
| 134 |
-
| tts_transformer | 54M | 3.8 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2/ljspeech_transformer_phn.tar) |
|
| 135 |
-
| fastspeech2 | 41M | 3.8 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2/ljspeech_fastspeech2_phn.tar) |
|
| 136 |
-
|
| 137 |
-
[[Back]](..)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/docs/vctk_example.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
[[Back]](..)
|
| 2 |
-
|
| 3 |
-
# VCTK
|
| 4 |
-
|
| 5 |
-
[VCTK](https://datashare.ed.ac.uk/handle/10283/3443) is an open English speech corpus. We provide examples
|
| 6 |
-
for building [Transformer](https://arxiv.org/abs/1809.08895) models on this dataset.
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
## Data preparation
|
| 10 |
-
Download data, create splits and generate audio manifests with
|
| 11 |
-
```bash
|
| 12 |
-
python -m examples.speech_synthesis.preprocessing.get_vctk_audio_manifest \
|
| 13 |
-
--output-data-root ${AUDIO_DATA_ROOT} \
|
| 14 |
-
--output-manifest-root ${AUDIO_MANIFEST_ROOT}
|
| 15 |
-
```
|
| 16 |
-
|
| 17 |
-
To denoise audio and trim leading/trailing silence using signal processing based VAD, run
|
| 18 |
-
```bash
|
| 19 |
-
for SPLIT in dev test train; do
|
| 20 |
-
python -m examples.speech_synthesis.preprocessing.denoise_and_vad_audio \
|
| 21 |
-
--audio-manifest ${AUDIO_MANIFEST_ROOT}/${SPLIT}.audio.tsv \
|
| 22 |
-
--output-dir ${PROCESSED_DATA_ROOT} \
|
| 23 |
-
--denoise --vad --vad-agg-level 3
|
| 24 |
-
done
|
| 25 |
-
```
|
| 26 |
-
which generates a new audio TSV manifest under `${PROCESSED_DATA_ROOT}` with updated path to the processed audio and
|
| 27 |
-
a new column for SNR.
|
| 28 |
-
|
| 29 |
-
To do filtering by CER, follow the [Automatic Evaluation](../docs/ljspeech_example.md#automatic-evaluation) section to
|
| 30 |
-
run ASR model (add `--eval-target` to `get_eval_manifest` for evaluation on the reference audio; add `--err-unit char`
|
| 31 |
-
to `eval_asr` to compute CER instead of WER). The example-level CER is saved to
|
| 32 |
-
`${EVAL_OUTPUT_ROOT}/uer_cer.${SPLIT}.tsv`.
|
| 33 |
-
|
| 34 |
-
Then, extract log-Mel spectrograms, generate feature manifest and create data configuration YAML with
|
| 35 |
-
```bash
|
| 36 |
-
python -m examples.speech_synthesis.preprocessing.get_feature_manifest \
|
| 37 |
-
--audio-manifest-root ${PROCESSED_DATA_ROOT} \
|
| 38 |
-
--output-root ${FEATURE_MANIFEST_ROOT} \
|
| 39 |
-
--ipa-vocab --use-g2p \
|
| 40 |
-
--snr-threshold 15 \
|
| 41 |
-
--cer-threshold 0.1 --cer-tsv-path ${EVAL_OUTPUT_ROOT}/uer_cer.${SPLIT}.tsv
|
| 42 |
-
```
|
| 43 |
-
where we use phoneme inputs (`--ipa-vocab --use-g2p`) as example. For sample filtering, we set the SNR and CER threshold
|
| 44 |
-
to 15 and 10%, respectively.
|
| 45 |
-
|
| 46 |
-
## Training
|
| 47 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#transformer).)
|
| 48 |
-
|
| 49 |
-
## Inference
|
| 50 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#inference).)
|
| 51 |
-
|
| 52 |
-
## Automatic Evaluation
|
| 53 |
-
(Please refer to [the LJSpeech example](../docs/ljspeech_example.md#automatic-evaluation).)
|
| 54 |
-
|
| 55 |
-
## Results
|
| 56 |
-
|
| 57 |
-
| --arch | Params | Test MCD | Model |
|
| 58 |
-
|---|---|---|---|
|
| 59 |
-
| tts_transformer | 54M | 3.4 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2/vctk_transformer_phn.tar) |
|
| 60 |
-
|
| 61 |
-
[[Back]](..)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/__init__.py
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_asr.py
DELETED
|
@@ -1,128 +0,0 @@
|
|
| 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 argparse
|
| 7 |
-
import re
|
| 8 |
-
import shutil
|
| 9 |
-
import subprocess
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
|
| 12 |
-
import editdistance
|
| 13 |
-
import soundfile as sf
|
| 14 |
-
from examples.speech_to_text.data_utils import load_tsv_to_dicts
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
def preprocess_text(text):
|
| 18 |
-
text = "|".join(re.sub(r"[^A-Z' ]", " ", text.upper()).split())
|
| 19 |
-
text = " ".join(text)
|
| 20 |
-
return text
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def prepare_w2v_data(
|
| 24 |
-
dict_dir, sample_rate, label, audio_paths, texts, split, data_dir
|
| 25 |
-
):
|
| 26 |
-
data_dir.mkdir(parents=True, exist_ok=True)
|
| 27 |
-
shutil.copyfile(
|
| 28 |
-
dict_dir / f"dict.{label}.txt",
|
| 29 |
-
data_dir / f"dict.{label}.txt"
|
| 30 |
-
)
|
| 31 |
-
with open(data_dir / f"{split}.tsv", "w") as f:
|
| 32 |
-
f.write("/\n")
|
| 33 |
-
for audio_path in audio_paths:
|
| 34 |
-
wav, sr = sf.read(audio_path)
|
| 35 |
-
assert sr == sample_rate, f"{sr} != sample_rate"
|
| 36 |
-
nsample = len(wav)
|
| 37 |
-
f.write(f"{audio_path}\t{nsample}\n")
|
| 38 |
-
with open(data_dir / f"{split}.{label}", "w") as f:
|
| 39 |
-
for text in texts:
|
| 40 |
-
text = preprocess_text(text)
|
| 41 |
-
f.write(f"{text}\n")
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def run_asr(asr_dir, split, w2v_ckpt, w2v_label, res_dir):
|
| 45 |
-
"""
|
| 46 |
-
results will be saved at
|
| 47 |
-
{res_dir}/{ref,hypo}.word-{w2v_ckpt.filename}-{split}.txt
|
| 48 |
-
"""
|
| 49 |
-
cmd = ["python", "-m", "examples.speech_recognition.infer"]
|
| 50 |
-
cmd += [str(asr_dir.resolve())]
|
| 51 |
-
cmd += ["--task", "audio_finetuning", "--nbest", "1", "--quiet"]
|
| 52 |
-
cmd += ["--w2l-decoder", "viterbi", "--criterion", "ctc"]
|
| 53 |
-
cmd += ["--post-process", "letter", "--max-tokens", "4000000"]
|
| 54 |
-
cmd += ["--path", str(w2v_ckpt.resolve()), "--labels", w2v_label]
|
| 55 |
-
cmd += ["--gen-subset", split, "--results-path", str(res_dir.resolve())]
|
| 56 |
-
|
| 57 |
-
print(f"running cmd:\n{' '.join(cmd)}")
|
| 58 |
-
subprocess.run(cmd, check=True)
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
def compute_error_rate(hyp_wrd_path, ref_wrd_path, unit="word"):
|
| 62 |
-
"""each line is "<text> (None-<index>)" """
|
| 63 |
-
tokenize_line = {
|
| 64 |
-
"word": lambda x: re.sub(r" \(.*\)$", "", x.rstrip()).split(),
|
| 65 |
-
"char": lambda x: list(re.sub(r" \(.*\)$", "", x.rstrip()))
|
| 66 |
-
}.get(unit)
|
| 67 |
-
if tokenize_line is None:
|
| 68 |
-
raise ValueError(f"{unit} not supported")
|
| 69 |
-
|
| 70 |
-
inds = [int(re.sub(r"\D*(\d*)\D*", r"\1", line))
|
| 71 |
-
for line in open(hyp_wrd_path)]
|
| 72 |
-
hyps = [tokenize_line(line) for line in open(hyp_wrd_path)]
|
| 73 |
-
refs = [tokenize_line(line) for line in open(ref_wrd_path)]
|
| 74 |
-
assert(len(hyps) == len(refs))
|
| 75 |
-
err_rates = [
|
| 76 |
-
editdistance.eval(hyp, ref) / len(ref) for hyp, ref in zip(hyps, refs)
|
| 77 |
-
]
|
| 78 |
-
ind_to_err_rates = {i: e for i, e in zip(inds, err_rates)}
|
| 79 |
-
return ind_to_err_rates
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def main(args):
|
| 83 |
-
samples = load_tsv_to_dicts(args.raw_manifest)
|
| 84 |
-
ids = [
|
| 85 |
-
sample[args.id_header] if args.id_header else "" for sample in samples
|
| 86 |
-
]
|
| 87 |
-
audio_paths = [sample[args.audio_header] for sample in samples]
|
| 88 |
-
texts = [sample[args.text_header] for sample in samples]
|
| 89 |
-
|
| 90 |
-
prepare_w2v_data(
|
| 91 |
-
args.w2v_dict_dir,
|
| 92 |
-
args.w2v_sample_rate,
|
| 93 |
-
args.w2v_label,
|
| 94 |
-
audio_paths,
|
| 95 |
-
texts,
|
| 96 |
-
args.split,
|
| 97 |
-
args.asr_dir
|
| 98 |
-
)
|
| 99 |
-
run_asr(args.asr_dir, args.split, args.w2v_ckpt, args.w2v_label, args.asr_dir)
|
| 100 |
-
ind_to_err_rates = compute_error_rate(
|
| 101 |
-
args.asr_dir / f"hypo.word-{args.w2v_ckpt.name}-{args.split}.txt",
|
| 102 |
-
args.asr_dir / f"ref.word-{args.w2v_ckpt.name}-{args.split}.txt",
|
| 103 |
-
args.err_unit,
|
| 104 |
-
)
|
| 105 |
-
|
| 106 |
-
uer_path = args.asr_dir / f"uer_{args.err_unit}.{args.split}.tsv"
|
| 107 |
-
with open(uer_path, "w") as f:
|
| 108 |
-
f.write("id\taudio\tuer\n")
|
| 109 |
-
for ind, (id_, audio_path) in enumerate(zip(ids, audio_paths)):
|
| 110 |
-
f.write(f"{id_}\t{audio_path}\t{ind_to_err_rates[ind]:.4f}\n")
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
if __name__ == "__main__":
|
| 114 |
-
parser = argparse.ArgumentParser()
|
| 115 |
-
parser.add_argument("--raw-manifest", required=True, type=Path)
|
| 116 |
-
parser.add_argument("--asr-dir", required=True, type=Path)
|
| 117 |
-
parser.add_argument("--id-header", default="id", type=str)
|
| 118 |
-
parser.add_argument("--audio-header", default="audio", type=str)
|
| 119 |
-
parser.add_argument("--text-header", default="src_text", type=str)
|
| 120 |
-
parser.add_argument("--split", default="raw", type=str)
|
| 121 |
-
parser.add_argument("--w2v-ckpt", required=True, type=Path)
|
| 122 |
-
parser.add_argument("--w2v-dict-dir", required=True, type=Path)
|
| 123 |
-
parser.add_argument("--w2v-sample-rate", default=16000, type=int)
|
| 124 |
-
parser.add_argument("--w2v-label", default="ltr", type=str)
|
| 125 |
-
parser.add_argument("--err-unit", default="word", type=str)
|
| 126 |
-
args = parser.parse_args()
|
| 127 |
-
|
| 128 |
-
main(args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_f0.py
DELETED
|
@@ -1,267 +0,0 @@
|
|
| 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 |
-
"""
|
| 7 |
-
Signal processing-based evaluation using waveforms
|
| 8 |
-
"""
|
| 9 |
-
import os.path as op
|
| 10 |
-
|
| 11 |
-
import numpy as np
|
| 12 |
-
import torchaudio
|
| 13 |
-
import tqdm
|
| 14 |
-
from examples.speech_synthesis.evaluation.eval_sp import load_eval_spec
|
| 15 |
-
from examples.speech_synthesis.utils import (
|
| 16 |
-
f0_frame_error,
|
| 17 |
-
gross_pitch_error,
|
| 18 |
-
voicing_decision_error,
|
| 19 |
-
)
|
| 20 |
-
from tabulate import tabulate
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def difference_function(x, n, tau_max):
|
| 24 |
-
"""
|
| 25 |
-
Compute difference function of data x. This solution is implemented directly
|
| 26 |
-
with Numpy fft.
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
:param x: audio data
|
| 30 |
-
:param n: length of data
|
| 31 |
-
:param tau_max: integration window size
|
| 32 |
-
:return: difference function
|
| 33 |
-
:rtype: list
|
| 34 |
-
"""
|
| 35 |
-
|
| 36 |
-
x = np.array(x, np.float64)
|
| 37 |
-
w = x.size
|
| 38 |
-
tau_max = min(tau_max, w)
|
| 39 |
-
x_cumsum = np.concatenate((np.array([0.]), (x * x).cumsum()))
|
| 40 |
-
size = w + tau_max
|
| 41 |
-
p2 = (size // 32).bit_length()
|
| 42 |
-
nice_numbers = (16, 18, 20, 24, 25, 27, 30, 32)
|
| 43 |
-
size_pad = min(x * 2 ** p2 for x in nice_numbers if x * 2 ** p2 >= size)
|
| 44 |
-
fc = np.fft.rfft(x, size_pad)
|
| 45 |
-
conv = np.fft.irfft(fc * fc.conjugate())[:tau_max]
|
| 46 |
-
return x_cumsum[w:w - tau_max:-1] + x_cumsum[w] - x_cumsum[:tau_max] - \
|
| 47 |
-
2 * conv
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
def cumulative_mean_normalized_difference_function(df, n):
|
| 51 |
-
"""
|
| 52 |
-
Compute cumulative mean normalized difference function (CMND).
|
| 53 |
-
|
| 54 |
-
:param df: Difference function
|
| 55 |
-
:param n: length of data
|
| 56 |
-
:return: cumulative mean normalized difference function
|
| 57 |
-
:rtype: list
|
| 58 |
-
"""
|
| 59 |
-
|
| 60 |
-
# scipy method
|
| 61 |
-
cmn_df = df[1:] * range(1, n) / np.cumsum(df[1:]).astype(float)
|
| 62 |
-
return np.insert(cmn_df, 0, 1)
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def get_pitch(cmdf, tau_min, tau_max, harmo_th=0.1):
|
| 66 |
-
"""
|
| 67 |
-
Return fundamental period of a frame based on CMND function.
|
| 68 |
-
|
| 69 |
-
:param cmdf: Cumulative Mean Normalized Difference function
|
| 70 |
-
:param tau_min: minimum period for speech
|
| 71 |
-
:param tau_max: maximum period for speech
|
| 72 |
-
:param harmo_th: harmonicity threshold to determine if it is necessary to
|
| 73 |
-
compute pitch frequency
|
| 74 |
-
:return: fundamental period if there is values under threshold, 0 otherwise
|
| 75 |
-
:rtype: float
|
| 76 |
-
"""
|
| 77 |
-
tau = tau_min
|
| 78 |
-
while tau < tau_max:
|
| 79 |
-
if cmdf[tau] < harmo_th:
|
| 80 |
-
while tau + 1 < tau_max and cmdf[tau + 1] < cmdf[tau]:
|
| 81 |
-
tau += 1
|
| 82 |
-
return tau
|
| 83 |
-
tau += 1
|
| 84 |
-
|
| 85 |
-
return 0 # if unvoiced
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
def compute_yin(sig, sr, w_len=512, w_step=256, f0_min=100, f0_max=500,
|
| 89 |
-
harmo_thresh=0.1):
|
| 90 |
-
"""
|
| 91 |
-
|
| 92 |
-
Compute the Yin Algorithm. Return fundamental frequency and harmonic rate.
|
| 93 |
-
|
| 94 |
-
https://github.com/NVIDIA/mellotron adaption of
|
| 95 |
-
https://github.com/patriceguyot/Yin
|
| 96 |
-
|
| 97 |
-
:param sig: Audio signal (list of float)
|
| 98 |
-
:param sr: sampling rate (int)
|
| 99 |
-
:param w_len: size of the analysis window (samples)
|
| 100 |
-
:param w_step: size of the lag between two consecutives windows (samples)
|
| 101 |
-
:param f0_min: Minimum fundamental frequency that can be detected (hertz)
|
| 102 |
-
:param f0_max: Maximum fundamental frequency that can be detected (hertz)
|
| 103 |
-
:param harmo_thresh: Threshold of detection. The yalgorithmù return the
|
| 104 |
-
first minimum of the CMND function below this threshold.
|
| 105 |
-
|
| 106 |
-
:returns:
|
| 107 |
-
|
| 108 |
-
* pitches: list of fundamental frequencies,
|
| 109 |
-
* harmonic_rates: list of harmonic rate values for each fundamental
|
| 110 |
-
frequency value (= confidence value)
|
| 111 |
-
* argmins: minimums of the Cumulative Mean Normalized DifferenceFunction
|
| 112 |
-
* times: list of time of each estimation
|
| 113 |
-
:rtype: tuple
|
| 114 |
-
"""
|
| 115 |
-
|
| 116 |
-
tau_min = int(sr / f0_max)
|
| 117 |
-
tau_max = int(sr / f0_min)
|
| 118 |
-
|
| 119 |
-
# time values for each analysis window
|
| 120 |
-
time_scale = range(0, len(sig) - w_len, w_step)
|
| 121 |
-
times = [t/float(sr) for t in time_scale]
|
| 122 |
-
frames = [sig[t:t + w_len] for t in time_scale]
|
| 123 |
-
|
| 124 |
-
pitches = [0.0] * len(time_scale)
|
| 125 |
-
harmonic_rates = [0.0] * len(time_scale)
|
| 126 |
-
argmins = [0.0] * len(time_scale)
|
| 127 |
-
|
| 128 |
-
for i, frame in enumerate(frames):
|
| 129 |
-
# Compute YIN
|
| 130 |
-
df = difference_function(frame, w_len, tau_max)
|
| 131 |
-
cm_df = cumulative_mean_normalized_difference_function(df, tau_max)
|
| 132 |
-
p = get_pitch(cm_df, tau_min, tau_max, harmo_thresh)
|
| 133 |
-
|
| 134 |
-
# Get results
|
| 135 |
-
if np.argmin(cm_df) > tau_min:
|
| 136 |
-
argmins[i] = float(sr / np.argmin(cm_df))
|
| 137 |
-
if p != 0: # A pitch was found
|
| 138 |
-
pitches[i] = float(sr / p)
|
| 139 |
-
harmonic_rates[i] = cm_df[p]
|
| 140 |
-
else: # No pitch, but we compute a value of the harmonic rate
|
| 141 |
-
harmonic_rates[i] = min(cm_df)
|
| 142 |
-
|
| 143 |
-
return pitches, harmonic_rates, argmins, times
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
def extract_f0(samples):
|
| 147 |
-
f0_samples = []
|
| 148 |
-
for sample in tqdm.tqdm(samples):
|
| 149 |
-
if not op.isfile(sample["ref"]) or not op.isfile(sample["syn"]):
|
| 150 |
-
f0_samples.append(None)
|
| 151 |
-
continue
|
| 152 |
-
|
| 153 |
-
# assume single channel
|
| 154 |
-
yref, sr = torchaudio.load(sample["ref"])
|
| 155 |
-
ysyn, _sr = torchaudio.load(sample["syn"])
|
| 156 |
-
yref, ysyn = yref[0], ysyn[0]
|
| 157 |
-
assert sr == _sr, f"{sr} != {_sr}"
|
| 158 |
-
|
| 159 |
-
yref_f0 = compute_yin(yref, sr)
|
| 160 |
-
ysyn_f0 = compute_yin(ysyn, sr)
|
| 161 |
-
|
| 162 |
-
f0_samples += [
|
| 163 |
-
{
|
| 164 |
-
"ref": yref_f0,
|
| 165 |
-
"syn": ysyn_f0
|
| 166 |
-
}
|
| 167 |
-
]
|
| 168 |
-
|
| 169 |
-
return f0_samples
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
def eval_f0_error(samples, distortion_fn):
|
| 173 |
-
results = []
|
| 174 |
-
for sample in tqdm.tqdm(samples):
|
| 175 |
-
if sample is None:
|
| 176 |
-
results.append(None)
|
| 177 |
-
continue
|
| 178 |
-
# assume single channel
|
| 179 |
-
yref_f, _, _, yref_t = sample["ref"]
|
| 180 |
-
ysyn_f, _, _, ysyn_t = sample["syn"]
|
| 181 |
-
|
| 182 |
-
yref_f = np.array(yref_f)
|
| 183 |
-
yref_t = np.array(yref_t)
|
| 184 |
-
ysyn_f = np.array(ysyn_f)
|
| 185 |
-
ysyn_t = np.array(ysyn_t)
|
| 186 |
-
|
| 187 |
-
distortion = distortion_fn(yref_t, yref_f, ysyn_t, ysyn_f)
|
| 188 |
-
results.append((distortion.item(),
|
| 189 |
-
len(yref_f),
|
| 190 |
-
len(ysyn_f)
|
| 191 |
-
))
|
| 192 |
-
return results
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
def eval_gross_pitch_error(samples):
|
| 196 |
-
return eval_f0_error(samples, gross_pitch_error)
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
def eval_voicing_decision_error(samples):
|
| 200 |
-
return eval_f0_error(samples, voicing_decision_error)
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
def eval_f0_frame_error(samples):
|
| 204 |
-
return eval_f0_error(samples, f0_frame_error)
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
def print_results(results, show_bin):
|
| 208 |
-
results = np.array(list(filter(lambda x: x is not None, results)))
|
| 209 |
-
|
| 210 |
-
np.set_printoptions(precision=3)
|
| 211 |
-
|
| 212 |
-
def _print_result(results):
|
| 213 |
-
res = {
|
| 214 |
-
"nutt": len(results),
|
| 215 |
-
"error": results[:, 0].mean(),
|
| 216 |
-
"std": results[:, 0].std(),
|
| 217 |
-
"dur_ref": int(results[:, 1].sum()),
|
| 218 |
-
"dur_syn": int(results[:, 2].sum()),
|
| 219 |
-
}
|
| 220 |
-
print(tabulate([res.values()], res.keys(), floatfmt=".4f"))
|
| 221 |
-
|
| 222 |
-
print(">>>> ALL")
|
| 223 |
-
_print_result(results)
|
| 224 |
-
|
| 225 |
-
if show_bin:
|
| 226 |
-
edges = [0, 200, 400, 600, 800, 1000, 2000, 4000]
|
| 227 |
-
for i in range(1, len(edges)):
|
| 228 |
-
mask = np.logical_and(results[:, 1] >= edges[i-1],
|
| 229 |
-
results[:, 1] < edges[i])
|
| 230 |
-
if not mask.any():
|
| 231 |
-
continue
|
| 232 |
-
bin_results = results[mask]
|
| 233 |
-
print(f">>>> ({edges[i-1]}, {edges[i]})")
|
| 234 |
-
_print_result(bin_results)
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
def main(eval_f0, gpe, vde, ffe, show_bin):
|
| 238 |
-
samples = load_eval_spec(eval_f0)
|
| 239 |
-
if gpe or vde or ffe:
|
| 240 |
-
f0_samples = extract_f0(samples)
|
| 241 |
-
|
| 242 |
-
if gpe:
|
| 243 |
-
print("===== Evaluate Gross Pitch Error =====")
|
| 244 |
-
results = eval_gross_pitch_error(f0_samples)
|
| 245 |
-
print_results(results, show_bin)
|
| 246 |
-
if vde:
|
| 247 |
-
print("===== Evaluate Voicing Decision Error =====")
|
| 248 |
-
results = eval_voicing_decision_error(f0_samples)
|
| 249 |
-
print_results(results, show_bin)
|
| 250 |
-
if ffe:
|
| 251 |
-
print("===== Evaluate F0 Frame Error =====")
|
| 252 |
-
results = eval_f0_frame_error(f0_samples)
|
| 253 |
-
print_results(results, show_bin)
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
if __name__ == "__main__":
|
| 257 |
-
import argparse
|
| 258 |
-
|
| 259 |
-
parser = argparse.ArgumentParser()
|
| 260 |
-
parser.add_argument("eval_f0")
|
| 261 |
-
parser.add_argument("--gpe", action="store_true")
|
| 262 |
-
parser.add_argument("--vde", action="store_true")
|
| 263 |
-
parser.add_argument("--ffe", action="store_true")
|
| 264 |
-
parser.add_argument("--show-bin", action="store_true")
|
| 265 |
-
args = parser.parse_args()
|
| 266 |
-
|
| 267 |
-
main(args.eval_f0, args.gpe, args.vde, args.ffe, args.show_bin)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/eval_sp.py
DELETED
|
@@ -1,130 +0,0 @@
|
|
| 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 |
-
|
| 7 |
-
"""
|
| 8 |
-
Signal processing-based evaluation using waveforms
|
| 9 |
-
"""
|
| 10 |
-
|
| 11 |
-
import csv
|
| 12 |
-
import os.path as op
|
| 13 |
-
|
| 14 |
-
import numpy as np
|
| 15 |
-
import torch
|
| 16 |
-
import torchaudio
|
| 17 |
-
import tqdm
|
| 18 |
-
from examples.speech_synthesis.utils import batch_mel_spectral_distortion
|
| 19 |
-
from fairseq.tasks.text_to_speech import batch_mel_cepstral_distortion
|
| 20 |
-
from tabulate import tabulate
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def load_eval_spec(path):
|
| 24 |
-
with open(path) as f:
|
| 25 |
-
reader = csv.DictReader(f, delimiter='\t')
|
| 26 |
-
samples = list(reader)
|
| 27 |
-
return samples
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
def eval_distortion(samples, distortion_fn, device="cuda"):
|
| 31 |
-
nmiss = 0
|
| 32 |
-
results = []
|
| 33 |
-
for sample in tqdm.tqdm(samples):
|
| 34 |
-
if not op.isfile(sample["ref"]) or not op.isfile(sample["syn"]):
|
| 35 |
-
nmiss += 1
|
| 36 |
-
results.append(None)
|
| 37 |
-
continue
|
| 38 |
-
# assume single channel
|
| 39 |
-
yref, sr = torchaudio.load(sample["ref"])
|
| 40 |
-
ysyn, _sr = torchaudio.load(sample["syn"])
|
| 41 |
-
yref, ysyn = yref[0].to(device), ysyn[0].to(device)
|
| 42 |
-
assert sr == _sr, f"{sr} != {_sr}"
|
| 43 |
-
|
| 44 |
-
distortion, extra = distortion_fn([yref], [ysyn], sr, None)[0]
|
| 45 |
-
_, _, _, _, _, pathmap = extra
|
| 46 |
-
nins = torch.sum(pathmap.sum(dim=1) - 1) # extra frames in syn
|
| 47 |
-
ndel = torch.sum(pathmap.sum(dim=0) - 1) # missing frames from syn
|
| 48 |
-
results.append(
|
| 49 |
-
(distortion.item(), # path distortion
|
| 50 |
-
pathmap.size(0), # yref num frames
|
| 51 |
-
pathmap.size(1), # ysyn num frames
|
| 52 |
-
pathmap.sum().item(), # path length
|
| 53 |
-
nins.item(), # insertion
|
| 54 |
-
ndel.item(), # deletion
|
| 55 |
-
)
|
| 56 |
-
)
|
| 57 |
-
return results
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
def eval_mel_cepstral_distortion(samples, device="cuda"):
|
| 61 |
-
return eval_distortion(samples, batch_mel_cepstral_distortion, device)
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
def eval_mel_spectral_distortion(samples, device="cuda"):
|
| 65 |
-
return eval_distortion(samples, batch_mel_spectral_distortion, device)
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
def print_results(results, show_bin):
|
| 69 |
-
results = np.array(list(filter(lambda x: x is not None, results)))
|
| 70 |
-
|
| 71 |
-
np.set_printoptions(precision=3)
|
| 72 |
-
|
| 73 |
-
def _print_result(results):
|
| 74 |
-
dist, dur_ref, dur_syn, dur_ali, nins, ndel = results.sum(axis=0)
|
| 75 |
-
res = {
|
| 76 |
-
"nutt": len(results),
|
| 77 |
-
"dist": dist,
|
| 78 |
-
"dur_ref": int(dur_ref),
|
| 79 |
-
"dur_syn": int(dur_syn),
|
| 80 |
-
"dur_ali": int(dur_ali),
|
| 81 |
-
"dist_per_ref_frm": dist/dur_ref,
|
| 82 |
-
"dist_per_syn_frm": dist/dur_syn,
|
| 83 |
-
"dist_per_ali_frm": dist/dur_ali,
|
| 84 |
-
"ins": nins/dur_ref,
|
| 85 |
-
"del": ndel/dur_ref,
|
| 86 |
-
}
|
| 87 |
-
print(tabulate(
|
| 88 |
-
[res.values()],
|
| 89 |
-
res.keys(),
|
| 90 |
-
floatfmt=".4f"
|
| 91 |
-
))
|
| 92 |
-
|
| 93 |
-
print(">>>> ALL")
|
| 94 |
-
_print_result(results)
|
| 95 |
-
|
| 96 |
-
if show_bin:
|
| 97 |
-
edges = [0, 200, 400, 600, 800, 1000, 2000, 4000]
|
| 98 |
-
for i in range(1, len(edges)):
|
| 99 |
-
mask = np.logical_and(results[:, 1] >= edges[i-1],
|
| 100 |
-
results[:, 1] < edges[i])
|
| 101 |
-
if not mask.any():
|
| 102 |
-
continue
|
| 103 |
-
bin_results = results[mask]
|
| 104 |
-
print(f">>>> ({edges[i-1]}, {edges[i]})")
|
| 105 |
-
_print_result(bin_results)
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
def main(eval_spec, mcd, msd, show_bin):
|
| 109 |
-
samples = load_eval_spec(eval_spec)
|
| 110 |
-
device = "cpu"
|
| 111 |
-
if mcd:
|
| 112 |
-
print("===== Evaluate Mean Cepstral Distortion =====")
|
| 113 |
-
results = eval_mel_cepstral_distortion(samples, device)
|
| 114 |
-
print_results(results, show_bin)
|
| 115 |
-
if msd:
|
| 116 |
-
print("===== Evaluate Mean Spectral Distortion =====")
|
| 117 |
-
results = eval_mel_spectral_distortion(samples, device)
|
| 118 |
-
print_results(results, show_bin)
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
if __name__ == "__main__":
|
| 122 |
-
import argparse
|
| 123 |
-
parser = argparse.ArgumentParser()
|
| 124 |
-
parser.add_argument("eval_spec")
|
| 125 |
-
parser.add_argument("--mcd", action="store_true")
|
| 126 |
-
parser.add_argument("--msd", action="store_true")
|
| 127 |
-
parser.add_argument("--show-bin", action="store_true")
|
| 128 |
-
args = parser.parse_args()
|
| 129 |
-
|
| 130 |
-
main(args.eval_spec, args.mcd, args.msd, args.show_bin)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/evaluation/get_eval_manifest.py
DELETED
|
@@ -1,64 +0,0 @@
|
|
| 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 |
-
|
| 7 |
-
import csv
|
| 8 |
-
from pathlib import Path
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
def main(args):
|
| 12 |
-
"""
|
| 13 |
-
`uid syn ref text`
|
| 14 |
-
"""
|
| 15 |
-
in_root = Path(args.generation_root).resolve()
|
| 16 |
-
ext = args.audio_format
|
| 17 |
-
with open(args.audio_manifest) as f, open(args.output_path, "w") as f_out:
|
| 18 |
-
reader = csv.DictReader(
|
| 19 |
-
f, delimiter="\t", quotechar=None, doublequote=False,
|
| 20 |
-
lineterminator="\n", quoting=csv.QUOTE_NONE
|
| 21 |
-
)
|
| 22 |
-
header = ["id", "syn", "ref", "text", "speaker"]
|
| 23 |
-
f_out.write("\t".join(header) + "\n")
|
| 24 |
-
for row in reader:
|
| 25 |
-
dir_name = f"{ext}_{args.sample_rate}hz_{args.vocoder}"
|
| 26 |
-
id_ = row["id"]
|
| 27 |
-
syn = (in_root / dir_name / f"{id_}.{ext}").as_posix()
|
| 28 |
-
ref = row["audio"]
|
| 29 |
-
if args.use_resynthesized_target:
|
| 30 |
-
ref = (in_root / f"{dir_name}_tgt" / f"{id_}.{ext}").as_posix()
|
| 31 |
-
if args.eval_target:
|
| 32 |
-
syn = row["audio"]
|
| 33 |
-
sample = [id_, syn, ref, row["tgt_text"], row["speaker"]]
|
| 34 |
-
f_out.write("\t".join(sample) + "\n")
|
| 35 |
-
print(f"wrote evaluation file to {args.output_path}")
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
if __name__ == "__main__":
|
| 39 |
-
import argparse
|
| 40 |
-
parser = argparse.ArgumentParser()
|
| 41 |
-
parser.add_argument(
|
| 42 |
-
"--generation-root", help="output directory for generate_waveform.py"
|
| 43 |
-
)
|
| 44 |
-
parser.add_argument(
|
| 45 |
-
"--audio-manifest",
|
| 46 |
-
help="used to determine the original utterance ID and text"
|
| 47 |
-
)
|
| 48 |
-
parser.add_argument(
|
| 49 |
-
"--output-path", help="path to output evaluation spec file"
|
| 50 |
-
)
|
| 51 |
-
parser.add_argument(
|
| 52 |
-
"--use-resynthesized-target", action="store_true",
|
| 53 |
-
help="use resynthesized reference instead of the original audio"
|
| 54 |
-
)
|
| 55 |
-
parser.add_argument(
|
| 56 |
-
"--eval-target", action="store_true",
|
| 57 |
-
help="evaluate reference instead of model prediction"
|
| 58 |
-
)
|
| 59 |
-
parser.add_argument("--vocoder", type=str, default="griffin_lim")
|
| 60 |
-
parser.add_argument("--sample-rate", type=int, default=22_050)
|
| 61 |
-
parser.add_argument("--audio-format", type=str, default="wav")
|
| 62 |
-
args = parser.parse_args()
|
| 63 |
-
|
| 64 |
-
main(args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audio/safeear/safeear_repo/fairseq_ours/examples/speech_synthesis/generate_waveform.py
DELETED
|
@@ -1,191 +0,0 @@
|
|
| 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 ast
|
| 7 |
-
import logging
|
| 8 |
-
import sys
|
| 9 |
-
from pathlib import Path
|
| 10 |
-
|
| 11 |
-
import matplotlib.pyplot as plt
|
| 12 |
-
import numpy as np
|
| 13 |
-
import soundfile as sf
|
| 14 |
-
import torch
|
| 15 |
-
import torchaudio
|
| 16 |
-
from fairseq import checkpoint_utils, options, tasks, utils
|
| 17 |
-
from fairseq.data.audio.text_to_speech_dataset import TextToSpeechDataset
|
| 18 |
-
from fairseq.logging import progress_bar
|
| 19 |
-
from fairseq.tasks.text_to_speech import plot_tts_output
|
| 20 |
-
|
| 21 |
-
logging.basicConfig()
|
| 22 |
-
logging.root.setLevel(logging.INFO)
|
| 23 |
-
logging.basicConfig(level=logging.INFO)
|
| 24 |
-
logger = logging.getLogger(__name__)
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def make_parser():
|
| 28 |
-
parser = options.get_speech_generation_parser()
|
| 29 |
-
parser.add_argument("--dump-features", action="store_true")
|
| 30 |
-
parser.add_argument("--dump-waveforms", action="store_true")
|
| 31 |
-
parser.add_argument("--dump-attentions", action="store_true")
|
| 32 |
-
parser.add_argument("--dump-eos-probs", action="store_true")
|
| 33 |
-
parser.add_argument("--dump-plots", action="store_true")
|
| 34 |
-
parser.add_argument("--dump-target", action="store_true")
|
| 35 |
-
parser.add_argument("--output-sample-rate", default=22050, type=int)
|
| 36 |
-
parser.add_argument("--teacher-forcing", action="store_true")
|
| 37 |
-
parser.add_argument(
|
| 38 |
-
"--audio-format", type=str, default="wav", choices=["wav", "flac"]
|
| 39 |
-
)
|
| 40 |
-
return parser
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
def postprocess_results(
|
| 44 |
-
dataset: TextToSpeechDataset, sample, hypos, resample_fn, dump_target
|
| 45 |
-
):
|
| 46 |
-
def to_np(x):
|
| 47 |
-
return None if x is None else x.detach().cpu().numpy()
|
| 48 |
-
|
| 49 |
-
sample_ids = [dataset.ids[i] for i in sample["id"].tolist()]
|
| 50 |
-
texts = sample["src_texts"] if "src_texts" in sample else [""] * len(hypos)
|
| 51 |
-
attns = [to_np(hypo["attn"]) for hypo in hypos]
|
| 52 |
-
eos_probs = [to_np(hypo.get("eos_prob", None)) for hypo in hypos]
|
| 53 |
-
feat_preds = [to_np(hypo["feature"]) for hypo in hypos]
|
| 54 |
-
wave_preds = [to_np(resample_fn(h["waveform"])) for h in hypos]
|
| 55 |
-
if dump_target:
|
| 56 |
-
feat_targs = [to_np(hypo["targ_feature"]) for hypo in hypos]
|
| 57 |
-
wave_targs = [to_np(resample_fn(h["targ_waveform"])) for h in hypos]
|
| 58 |
-
else:
|
| 59 |
-
feat_targs = [None for _ in hypos]
|
| 60 |
-
wave_targs = [None for _ in hypos]
|
| 61 |
-
|
| 62 |
-
return zip(sample_ids, texts, attns, eos_probs, feat_preds, wave_preds,
|
| 63 |
-
feat_targs, wave_targs)
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
def dump_result(
|
| 67 |
-
is_na_model,
|
| 68 |
-
args,
|
| 69 |
-
vocoder,
|
| 70 |
-
sample_id,
|
| 71 |
-
text,
|
| 72 |
-
attn,
|
| 73 |
-
eos_prob,
|
| 74 |
-
feat_pred,
|
| 75 |
-
wave_pred,
|
| 76 |
-
feat_targ,
|
| 77 |
-
wave_targ,
|
| 78 |
-
):
|
| 79 |
-
sample_rate = args.output_sample_rate
|
| 80 |
-
out_root = Path(args.results_path)
|
| 81 |
-
if args.dump_features:
|
| 82 |
-
feat_dir = out_root / "feat"
|
| 83 |
-
feat_dir.mkdir(exist_ok=True, parents=True)
|
| 84 |
-
np.save(feat_dir / f"{sample_id}.npy", feat_pred)
|
| 85 |
-
if args.dump_target:
|
| 86 |
-
feat_tgt_dir = out_root / "feat_tgt"
|
| 87 |
-
feat_tgt_dir.mkdir(exist_ok=True, parents=True)
|
| 88 |
-
np.save(feat_tgt_dir / f"{sample_id}.npy", feat_targ)
|
| 89 |
-
if args.dump_attentions:
|
| 90 |
-
attn_dir = out_root / "attn"
|
| 91 |
-
attn_dir.mkdir(exist_ok=True, parents=True)
|
| 92 |
-
np.save(attn_dir / f"{sample_id}.npy", attn.numpy())
|
| 93 |
-
if args.dump_eos_probs and not is_na_model:
|
| 94 |
-
eos_dir = out_root / "eos"
|
| 95 |
-
eos_dir.mkdir(exist_ok=True, parents=True)
|
| 96 |
-
np.save(eos_dir / f"{sample_id}.npy", eos_prob)
|
| 97 |
-
|
| 98 |
-
if args.dump_plots:
|
| 99 |
-
images = [feat_pred.T] if is_na_model else [feat_pred.T, attn]
|
| 100 |
-
names = ["output"] if is_na_model else ["output", "alignment"]
|
| 101 |
-
if feat_targ is not None:
|
| 102 |
-
images = [feat_targ.T] + images
|
| 103 |
-
names = [f"target (idx={sample_id})"] + names
|
| 104 |
-
if is_na_model:
|
| 105 |
-
plot_tts_output(images, names, attn, "alignment", suptitle=text)
|
| 106 |
-
else:
|
| 107 |
-
plot_tts_output(images, names, eos_prob, "eos prob", suptitle=text)
|
| 108 |
-
plot_dir = out_root / "plot"
|
| 109 |
-
plot_dir.mkdir(exist_ok=True, parents=True)
|
| 110 |
-
plt.savefig(plot_dir / f"{sample_id}.png")
|
| 111 |
-
plt.close()
|
| 112 |
-
|
| 113 |
-
if args.dump_waveforms:
|
| 114 |
-
ext = args.audio_format
|
| 115 |
-
if wave_pred is not None:
|
| 116 |
-
wav_dir = out_root / f"{ext}_{sample_rate}hz_{vocoder}"
|
| 117 |
-
wav_dir.mkdir(exist_ok=True, parents=True)
|
| 118 |
-
sf.write(wav_dir / f"{sample_id}.{ext}", wave_pred, sample_rate)
|
| 119 |
-
if args.dump_target and wave_targ is not None:
|
| 120 |
-
wav_tgt_dir = out_root / f"{ext}_{sample_rate}hz_{vocoder}_tgt"
|
| 121 |
-
wav_tgt_dir.mkdir(exist_ok=True, parents=True)
|
| 122 |
-
sf.write(wav_tgt_dir / f"{sample_id}.{ext}", wave_targ, sample_rate)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
def main(args):
|
| 126 |
-
assert(args.dump_features or args.dump_waveforms or args.dump_attentions
|
| 127 |
-
or args.dump_eos_probs or args.dump_plots)
|
| 128 |
-
if args.max_tokens is None and args.batch_size is None:
|
| 129 |
-
args.max_tokens = 8000
|
| 130 |
-
logger.info(args)
|
| 131 |
-
|
| 132 |
-
use_cuda = torch.cuda.is_available() and not args.cpu
|
| 133 |
-
task = tasks.setup_task(args)
|
| 134 |
-
models, saved_cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
| 135 |
-
[args.path],
|
| 136 |
-
task=task,
|
| 137 |
-
arg_overrides=ast.literal_eval(args.model_overrides),
|
| 138 |
-
)
|
| 139 |
-
model = models[0].cuda() if use_cuda else models[0]
|
| 140 |
-
# use the original n_frames_per_step
|
| 141 |
-
task.args.n_frames_per_step = saved_cfg.task.n_frames_per_step
|
| 142 |
-
task.load_dataset(args.gen_subset, task_cfg=saved_cfg.task)
|
| 143 |
-
|
| 144 |
-
data_cfg = task.data_cfg
|
| 145 |
-
sample_rate = data_cfg.config.get("features", {}).get("sample_rate", 22050)
|
| 146 |
-
resample_fn = {
|
| 147 |
-
False: lambda x: x,
|
| 148 |
-
True: lambda x: torchaudio.sox_effects.apply_effects_tensor(
|
| 149 |
-
x.detach().cpu().unsqueeze(0), sample_rate,
|
| 150 |
-
[['rate', str(args.output_sample_rate)]]
|
| 151 |
-
)[0].squeeze(0)
|
| 152 |
-
}.get(args.output_sample_rate != sample_rate)
|
| 153 |
-
if args.output_sample_rate != sample_rate:
|
| 154 |
-
logger.info(f"resampling to {args.output_sample_rate}Hz")
|
| 155 |
-
|
| 156 |
-
generator = task.build_generator([model], args)
|
| 157 |
-
itr = task.get_batch_iterator(
|
| 158 |
-
dataset=task.dataset(args.gen_subset),
|
| 159 |
-
max_tokens=args.max_tokens,
|
| 160 |
-
max_sentences=args.batch_size,
|
| 161 |
-
max_positions=(sys.maxsize, sys.maxsize),
|
| 162 |
-
ignore_invalid_inputs=args.skip_invalid_size_inputs_valid_test,
|
| 163 |
-
required_batch_size_multiple=args.required_batch_size_multiple,
|
| 164 |
-
num_shards=args.num_shards,
|
| 165 |
-
shard_id=args.shard_id,
|
| 166 |
-
num_workers=args.num_workers,
|
| 167 |
-
data_buffer_size=args.data_buffer_size,
|
| 168 |
-
).next_epoch_itr(shuffle=False)
|
| 169 |
-
|
| 170 |
-
Path(args.results_path).mkdir(exist_ok=True, parents=True)
|
| 171 |
-
is_na_model = getattr(model, "NON_AUTOREGRESSIVE", False)
|
| 172 |
-
dataset = task.dataset(args.gen_subset)
|
| 173 |
-
vocoder = task.args.vocoder
|
| 174 |
-
with progress_bar.build_progress_bar(args, itr) as t:
|
| 175 |
-
for sample in t:
|
| 176 |
-
sample = utils.move_to_cuda(sample) if use_cuda else sample
|
| 177 |
-
hypos = generator.generate(model, sample, has_targ=args.dump_target)
|
| 178 |
-
for result in postprocess_results(
|
| 179 |
-
dataset, sample, hypos, resample_fn, args.dump_target
|
| 180 |
-
):
|
| 181 |
-
dump_result(is_na_model, args, vocoder, *result)
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
def cli_main():
|
| 185 |
-
parser = make_parser()
|
| 186 |
-
args = options.parse_args_and_arch(parser)
|
| 187 |
-
main(args)
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
if __name__ == "__main__":
|
| 191 |
-
cli_main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|