chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# Speech-to-Text (S2T) Modeling
|
||||
|
||||
[https://www.aclweb.org/anthology/2020.aacl-demo.6](https://www.aclweb.org/anthology/2020.aacl-demo.6.pdf)
|
||||
|
||||
Speech recognition (ASR) and speech-to-text translation (ST) with fairseq.
|
||||
|
||||
## Data Preparation
|
||||
S2T modeling data consists of source speech features, target text and other optional information
|
||||
(source text, speaker id, etc.). Fairseq S2T uses per-dataset-split TSV manifest files
|
||||
to store these information. Each data field is represented by a column in the TSV file.
|
||||
|
||||
Unlike text token embeddings, speech features (e.g. log mel-scale filter banks) are usually fixed
|
||||
during model training and can be pre-computed. The manifest file contains the path to
|
||||
either the feature file in NumPy format or the WAV/FLAC audio file. For the latter,
|
||||
features will be extracted on-the-fly by fairseq S2T. Optionally, feature/audio files can be packed
|
||||
into uncompressed ZIP files (then accessed via byte offset and length) to improve I/O performance.
|
||||
|
||||
Fairseq S2T also employs a YAML file for data related configurations: tokenizer type and dictionary path
|
||||
for the target text, feature transforms such as CMVN (cepstral mean and variance normalization) and SpecAugment,
|
||||
temperature-based resampling, etc.
|
||||
|
||||
## Model Training & Evaluation
|
||||
Fairseq S2T uses the unified `fairseq-train`/`fairseq-generate` interface for model training and evaluation.
|
||||
It requires arguments `--task speech_to_text` and `--arch <model architecture in fairseq.models.speech_to_text.*>`.
|
||||
|
||||
|
||||
## Examples
|
||||
- [Speech Recognition (ASR) on LibriSpeech](docs/librispeech_example.md)
|
||||
|
||||
- [Speech-to-Text Translation (ST) on MuST-C](docs/mustc_example.md)
|
||||
|
||||
- [Speech-to-Text Translation (ST) on CoVoST 2](docs/covost_example.md)
|
||||
|
||||
## Updates
|
||||
- 01/08/2021: Several fixes for S2T Transformer model, inference-time de-tokenization, scorer configuration and data
|
||||
preparation scripts. We also add pre-trained models to the examples and revise the instructions.
|
||||
Breaking changes: the data preparation scripts now extract filterbank features without CMVN. CMVN is instead applied
|
||||
on-the-fly (defined in the config YAML).
|
||||
|
||||
## What's Next
|
||||
- We are migrating the old fairseq [ASR example](../speech_recognition) into this S2T framework and
|
||||
merging the features from both sides.
|
||||
- The following papers also base their experiments on fairseq S2T. We are adding more examples for replication.
|
||||
- [Improving Cross-Lingual Transfer Learning for End-to-End Speech Recognition with Speech Translation (Wang et al., 2020)](https://arxiv.org/abs/2006.05474)
|
||||
- [Self-Supervised Representations Improve End-to-End Speech Translation (Wu et al., 2020)](https://arxiv.org/abs/2006.12124)
|
||||
- [Self-Training for End-to-End Speech Translation (Pino et al., 2020)](https://arxiv.org/abs/2006.02490)
|
||||
- [CoVoST: A Diverse Multilingual Speech-To-Text Translation Corpus (Wang et al., 2020)](https://arxiv.org/abs/2002.01320)
|
||||
- [Harnessing Indirect Training Data for End-to-End Automatic Speech Translation: Tricks of the Trade (Pino et al., 2019)](https://arxiv.org/abs/1909.06515)
|
||||
|
||||
## Citation
|
||||
Please cite as:
|
||||
```
|
||||
@inproceedings{wang2020fairseqs2t,
|
||||
title = {fairseq S2T: Fast Speech-to-Text Modeling with fairseq},
|
||||
author = {Changhan Wang and Yun Tang and Xutai Ma and Anne Wu and Dmytro Okhonko and Juan Pino},
|
||||
booktitle = {Proceedings of the 2020 Conference of the Asian Chapter of the Association for Computational Linguistics (AACL): System Demonstrations},
|
||||
year = {2020},
|
||||
}
|
||||
|
||||
@inproceedings{ott2019fairseq,
|
||||
title = {fairseq: A Fast, Extensible Toolkit for Sequence Modeling},
|
||||
author = {Myle Ott and Sergey Edunov and Alexei Baevski and Angela Fan and Sam Gross and Nathan Ng and David Grangier and Michael Auli},
|
||||
booktitle = {Proceedings of NAACL-HLT 2019: Demonstrations},
|
||||
year = {2019},
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,309 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
from functools import reduce
|
||||
from multiprocessing import cpu_count
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import sentencepiece as sp
|
||||
from fairseq.data.audio.audio_utils import _get_kaldi_fbank, _get_torchaudio_fbank
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
UNK_TOKEN, UNK_TOKEN_ID = "<unk>", 3
|
||||
BOS_TOKEN, BOS_TOKEN_ID = "<s>", 0
|
||||
EOS_TOKEN, EOS_TOKEN_ID = "</s>", 2
|
||||
PAD_TOKEN, PAD_TOKEN_ID = "<pad>", 1
|
||||
|
||||
|
||||
def gen_vocab(
|
||||
input_path: Path, output_path_prefix: Path, model_type="bpe",
|
||||
vocab_size=1000, special_symbols: Optional[List[str]] = None
|
||||
):
|
||||
# Train SentencePiece Model
|
||||
arguments = [
|
||||
f"--input={input_path.as_posix()}",
|
||||
f"--model_prefix={output_path_prefix.as_posix()}",
|
||||
f"--model_type={model_type}",
|
||||
f"--vocab_size={vocab_size}",
|
||||
"--character_coverage=1.0",
|
||||
f"--num_threads={cpu_count()}",
|
||||
f"--unk_id={UNK_TOKEN_ID}",
|
||||
f"--bos_id={BOS_TOKEN_ID}",
|
||||
f"--eos_id={EOS_TOKEN_ID}",
|
||||
f"--pad_id={PAD_TOKEN_ID}",
|
||||
]
|
||||
if special_symbols is not None:
|
||||
_special_symbols = ",".join(special_symbols)
|
||||
arguments.append(f"--user_defined_symbols={_special_symbols}")
|
||||
sp.SentencePieceTrainer.Train(" ".join(arguments))
|
||||
# Export fairseq dictionary
|
||||
spm = sp.SentencePieceProcessor()
|
||||
spm.Load(output_path_prefix.as_posix() + ".model")
|
||||
vocab = {i: spm.IdToPiece(i) for i in range(spm.GetPieceSize())}
|
||||
assert (
|
||||
vocab.get(UNK_TOKEN_ID) == UNK_TOKEN
|
||||
and vocab.get(PAD_TOKEN_ID) == PAD_TOKEN
|
||||
and vocab.get(BOS_TOKEN_ID) == BOS_TOKEN
|
||||
and vocab.get(EOS_TOKEN_ID) == EOS_TOKEN
|
||||
)
|
||||
vocab = {
|
||||
i: s
|
||||
for i, s in vocab.items()
|
||||
if s not in {UNK_TOKEN, BOS_TOKEN, EOS_TOKEN, PAD_TOKEN}
|
||||
}
|
||||
with open(output_path_prefix.as_posix() + ".txt", "w") as f_out:
|
||||
for _, s in sorted(vocab.items(), key=lambda x: x[0]):
|
||||
f_out.write(f"{s} 1\n")
|
||||
|
||||
|
||||
def extract_fbank_features(
|
||||
waveform,
|
||||
sample_rate: int,
|
||||
output_path: Optional[Path] = None,
|
||||
n_mel_bins: int = 80,
|
||||
overwrite: bool = False,
|
||||
):
|
||||
if output_path is not None and output_path.is_file() and not overwrite:
|
||||
return
|
||||
|
||||
_waveform = waveform * (2 ** 15) # Kaldi compliance: 16-bit signed integers
|
||||
_waveform = _waveform.squeeze().numpy()
|
||||
|
||||
features = _get_kaldi_fbank(_waveform, sample_rate, n_mel_bins)
|
||||
if features is None:
|
||||
features = _get_torchaudio_fbank(_waveform, sample_rate, n_mel_bins)
|
||||
if features is None:
|
||||
raise ImportError(
|
||||
"Please install pyKaldi or torchaudio to enable fbank feature extraction"
|
||||
)
|
||||
|
||||
if output_path is not None:
|
||||
np.save(output_path.as_posix(), features)
|
||||
else:
|
||||
return features
|
||||
|
||||
|
||||
def create_zip(data_root: Path, zip_path: Path):
|
||||
paths = list(data_root.glob("*.npy"))
|
||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_STORED) as f:
|
||||
for path in tqdm(paths):
|
||||
f.write(path, arcname=path.name)
|
||||
|
||||
|
||||
def is_npy_data(data: bytes) -> bool:
|
||||
return data[0] == 147 and data[1] == 78
|
||||
|
||||
|
||||
def get_zip_manifest(zip_path: Path, zip_root: Optional[Path] = None):
|
||||
_zip_path = zip_path if zip_root is None else Path.joinpath(zip_root, zip_path)
|
||||
with zipfile.ZipFile(_zip_path, mode="r") as f:
|
||||
info = f.infolist()
|
||||
manifest = {}
|
||||
for i in tqdm(info):
|
||||
utt_id = Path(i.filename).stem
|
||||
offset, file_size = i.header_offset + 30 + len(i.filename), i.file_size
|
||||
manifest[utt_id] = f"{zip_path.as_posix()}:{offset}:{file_size}"
|
||||
with open(_zip_path, "rb") as f:
|
||||
f.seek(offset)
|
||||
data = f.read(file_size)
|
||||
assert len(data) > 1 and is_npy_data(data)
|
||||
return manifest
|
||||
|
||||
|
||||
def gen_config_yaml(
|
||||
manifest_root: Path,
|
||||
spm_filename: str,
|
||||
yaml_filename: str = "config.yaml",
|
||||
specaugment_policy: str = "lb",
|
||||
prepend_tgt_lang_tag: bool = False,
|
||||
sampling_alpha: float = 1.0,
|
||||
audio_root: str = ""
|
||||
):
|
||||
manifest_root = manifest_root.absolute()
|
||||
writer = S2TDataConfigWriter(manifest_root / yaml_filename)
|
||||
writer.set_vocab_filename(spm_filename.replace(".model", ".txt"))
|
||||
writer.set_input_channels(1)
|
||||
writer.set_input_feat_per_channel(80)
|
||||
specaugment_setters = {
|
||||
"lb": writer.set_specaugment_lb_policy,
|
||||
"ld": writer.set_specaugment_ld_policy,
|
||||
"sm": writer.set_specaugment_sm_policy,
|
||||
"ss": writer.set_specaugment_ss_policy,
|
||||
}
|
||||
specaugment_setter = specaugment_setters.get(specaugment_policy, None)
|
||||
if specaugment_setter is not None:
|
||||
specaugment_setter()
|
||||
writer.set_bpe_tokenizer(
|
||||
{
|
||||
"bpe": "sentencepiece",
|
||||
"sentencepiece_model": (manifest_root / spm_filename).as_posix(),
|
||||
}
|
||||
)
|
||||
if prepend_tgt_lang_tag:
|
||||
writer.set_prepend_tgt_lang_tag(True)
|
||||
writer.set_sampling_alpha(sampling_alpha)
|
||||
writer.set_feature_transforms("_train", ["utterance_cmvn", "specaugment"])
|
||||
writer.set_feature_transforms("*", ["utterance_cmvn"])
|
||||
if len(audio_root) > 0:
|
||||
writer.set_audio_root(audio_root)
|
||||
writer.flush()
|
||||
|
||||
|
||||
def load_df_from_tsv(path: Union[str, Path]):
|
||||
_path = path if isinstance(path, str) else path.as_posix()
|
||||
return pd.read_csv(
|
||||
_path,
|
||||
sep="\t",
|
||||
header=0,
|
||||
encoding="utf-8",
|
||||
escapechar="\\",
|
||||
quoting=csv.QUOTE_NONE,
|
||||
na_filter=False,
|
||||
)
|
||||
|
||||
|
||||
def save_df_to_tsv(dataframe, path: Union[str, Path]):
|
||||
_path = path if isinstance(path, str) else path.as_posix()
|
||||
dataframe.to_csv(
|
||||
_path,
|
||||
sep="\t",
|
||||
header=True,
|
||||
index=False,
|
||||
encoding="utf-8",
|
||||
escapechar="\\",
|
||||
quoting=csv.QUOTE_NONE,
|
||||
)
|
||||
|
||||
|
||||
def filter_manifest_df(
|
||||
df, is_train_split=False, extra_filters=None, min_n_frames=5, max_n_frames=3000
|
||||
):
|
||||
filters = {
|
||||
"no speech": df["audio"] == "",
|
||||
f"short speech (<{min_n_frames} frames)": df["n_frames"] < min_n_frames,
|
||||
"empty sentence": df["tgt_text"] == "",
|
||||
}
|
||||
if is_train_split:
|
||||
filters[f"long speech (>{max_n_frames} frames)"] = df["n_frames"] > max_n_frames
|
||||
if extra_filters is not None:
|
||||
filters.update(extra_filters)
|
||||
invalid = reduce(lambda x, y: x | y, filters.values())
|
||||
valid = ~invalid
|
||||
print(
|
||||
"| "
|
||||
+ ", ".join(f"{n}: {f.sum()}" for n, f in filters.items())
|
||||
+ f", total {invalid.sum()} filtered, {valid.sum()} remained."
|
||||
)
|
||||
return df[valid]
|
||||
|
||||
|
||||
class S2TDataConfigWriter(object):
|
||||
DEFAULT_VOCAB_FILENAME = "dict.txt"
|
||||
DEFAULT_INPUT_FEAT_PER_CHANNEL = 80
|
||||
DEFAULT_INPUT_CHANNELS = 1
|
||||
|
||||
def __init__(self, yaml_path: Path):
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("Please install PyYAML for S2T data config YAML files")
|
||||
self.yaml = yaml
|
||||
self.yaml_path = yaml_path
|
||||
self.config = {}
|
||||
|
||||
def flush(self):
|
||||
with open(self.yaml_path, "w") as f:
|
||||
self.yaml.dump(self.config, f)
|
||||
|
||||
def set_audio_root(self, audio_root=""):
|
||||
self.config["audio_root"] = audio_root
|
||||
|
||||
def set_vocab_filename(self, vocab_filename: str = "dict.txt"):
|
||||
self.config["vocab_filename"] = vocab_filename
|
||||
|
||||
def set_specaugment(
|
||||
self,
|
||||
time_wrap_w: int,
|
||||
freq_mask_n: int,
|
||||
freq_mask_f: int,
|
||||
time_mask_n: int,
|
||||
time_mask_t: int,
|
||||
time_mask_p: float,
|
||||
):
|
||||
self.config["specaugment"] = {
|
||||
"time_wrap_W": time_wrap_w,
|
||||
"freq_mask_N": freq_mask_n,
|
||||
"freq_mask_F": freq_mask_f,
|
||||
"time_mask_N": time_mask_n,
|
||||
"time_mask_T": time_mask_t,
|
||||
"time_mask_p": time_mask_p,
|
||||
}
|
||||
|
||||
def set_specaugment_lb_policy(self):
|
||||
self.set_specaugment(
|
||||
time_wrap_w=0,
|
||||
freq_mask_n=1,
|
||||
freq_mask_f=27,
|
||||
time_mask_n=1,
|
||||
time_mask_t=100,
|
||||
time_mask_p=1.0,
|
||||
)
|
||||
|
||||
def set_specaugment_ld_policy(self):
|
||||
self.set_specaugment(
|
||||
time_wrap_w=0,
|
||||
freq_mask_n=2,
|
||||
freq_mask_f=27,
|
||||
time_mask_n=2,
|
||||
time_mask_t=100,
|
||||
time_mask_p=1.0,
|
||||
)
|
||||
|
||||
def set_specaugment_sm_policy(self):
|
||||
self.set_specaugment(
|
||||
time_wrap_w=0,
|
||||
freq_mask_n=2,
|
||||
freq_mask_f=15,
|
||||
time_mask_n=2,
|
||||
time_mask_t=70,
|
||||
time_mask_p=0.2,
|
||||
)
|
||||
|
||||
def set_specaugment_ss_policy(self):
|
||||
self.set_specaugment(
|
||||
time_wrap_w=0,
|
||||
freq_mask_n=2,
|
||||
freq_mask_f=27,
|
||||
time_mask_n=2,
|
||||
time_mask_t=70,
|
||||
time_mask_p=0.2,
|
||||
)
|
||||
|
||||
def set_input_channels(self, input_channels: int = 1):
|
||||
self.config["input_channels"] = input_channels
|
||||
|
||||
def set_input_feat_per_channel(self, input_feat_per_channel: int = 80):
|
||||
self.config["input_feat_per_channel"] = input_feat_per_channel
|
||||
|
||||
def set_bpe_tokenizer(self, bpe_tokenizer: Dict[str, Any]):
|
||||
self.config["bpe_tokenizer"] = bpe_tokenizer
|
||||
|
||||
def set_feature_transforms(self, split: str, transforms: List[str]):
|
||||
if "transforms" not in self.config:
|
||||
self.config["transforms"] = {}
|
||||
self.config["transforms"][split] = transforms
|
||||
|
||||
def set_prepend_tgt_lang_tag(self, flag: bool = True):
|
||||
self.config["prepend_tgt_lang_tag"] = flag
|
||||
|
||||
def set_sampling_alpha(self, sampling_alpha: float = 1.0):
|
||||
self.config["sampling_alpha"] = sampling_alpha
|
||||
@@ -0,0 +1,93 @@
|
||||
[[Back]](..)
|
||||
|
||||
# S2T Example: ST on CoVoST
|
||||
We replicate the experiments in
|
||||
[CoVoST 2 and Massively Multilingual Speech-to-Text Translation (Wang et al., 2020)](https://arxiv.org/abs/2007.10310).
|
||||
|
||||
## Data Preparation
|
||||
[Download](https://commonvoice.mozilla.org/en/datasets) and unpack Common Voice v4 to a path
|
||||
`${COVOST_ROOT}/${SOURCE_LANG_ID}`, then preprocess it with
|
||||
```bash
|
||||
# additional Python packages for S2T data processing/model training
|
||||
pip install pandas torchaudio sentencepiece
|
||||
|
||||
# En ASR
|
||||
python examples/speech_to_text/prep_covost_data.py \
|
||||
--data-root ${COVOST_ROOT} --vocab-type char --src-lang en
|
||||
# ST
|
||||
python examples/speech_to_text/prep_covost_data.py \
|
||||
--data-root ${COVOST_ROOT} --vocab-type char \
|
||||
--src-lang fr --tgt-lang en
|
||||
```
|
||||
The generated files (manifest, features, vocabulary and data configuration) will be added to
|
||||
`${COVOST_ROOT}/${SOURCE_LANG_ID}`.
|
||||
|
||||
Download our vocabulary files if you want to use our pre-trained models:
|
||||
- ASR: [En](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_asr_vocab_char.zip)
|
||||
- ST: [Fr-En](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_fr_en_st_vocab_char.zip), [De-En](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_de_en_st_vocab_char.zip), [Es-En](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_es_en_st_vocab_char.zip), [Ca-En](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_ca_en_st_vocab_char.zip), [En-De](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_de_st_vocab_char.zip), [En-Ca](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_ca_st_vocab_char.zip), [En-Fa](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_fa_st_vocab_char.zip), [En-Et](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_et_st_vocab_char.zip)
|
||||
|
||||
## ASR
|
||||
#### Training
|
||||
We train an En ASR model for encoder pre-training of all ST models:
|
||||
```bash
|
||||
fairseq-train ${COVOST_ROOT}/en \
|
||||
--config-yaml config_asr_en.yaml --train-subset train_asr_en --valid-subset dev_asr_en \
|
||||
--save-dir ${ASR_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 60000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8
|
||||
```
|
||||
where `ASR_SAVE_DIR` is the checkpoint root path. We set `--update-freq 8` to simulate 8 GPUs with 1 GPU.
|
||||
You may want to update it accordingly when using more than 1 GPU.
|
||||
|
||||
#### Inference & Evaluation
|
||||
```bash
|
||||
CHECKPOINT_FILENAME=avg_last_10_checkpoint.pt
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${ASR_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
fairseq-generate ${COVOST_ROOT}/en \
|
||||
--config-yaml config_asr_en.yaml --gen-subset test_asr_en --task speech_to_text \
|
||||
--path ${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME} --max-tokens 50000 --beam 5 \
|
||||
--scoring wer --wer-tokenizer 13a --wer-lowercase --wer-remove-punct
|
||||
```
|
||||
#### Results
|
||||
| --arch | Params | En | Model |
|
||||
|---|---|---|---|
|
||||
| s2t_transformer_s | 31M | 25.6 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_asr_transformer_s.pt) |
|
||||
|
||||
## ST
|
||||
#### Training
|
||||
Fr-En as example:
|
||||
```bash
|
||||
fairseq-train ${COVOST_ROOT}/fr \
|
||||
--config-yaml config_st_fr_en.yaml --train-subset train_st_fr_en --valid-subset dev_st_fr_en \
|
||||
--save-dir ${ST_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 60000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8 \
|
||||
--load-pretrained-encoder-from ${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}
|
||||
```
|
||||
where `ST_SAVE_DIR` is the checkpoint root path. The ST encoder is pre-trained by En ASR for faster training and better
|
||||
performance: `--load-pretrained-encoder-from <ASR checkpoint path>`. We set `--update-freq 8` to simulate 8 GPUs with 1 GPU.
|
||||
You may want to update it accordingly when using more than 1 GPU.
|
||||
|
||||
#### Inference & Evaluation
|
||||
Average the last 10 checkpoints and evaluate on test split:
|
||||
```bash
|
||||
CHECKPOINT_FILENAME=avg_last_10_checkpoint.pt
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${ST_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${ST_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
fairseq-generate ${COVOST_ROOT}/fr \
|
||||
--config-yaml config_st_fr_en.yaml --gen-subset test_st_fr_en --task speech_to_text \
|
||||
--path ${ST_SAVE_DIR}/${CHECKPOINT_FILENAME} \
|
||||
--max-tokens 50000 --beam 5 --scoring sacrebleu
|
||||
```
|
||||
|
||||
#### Results
|
||||
| --arch | Params | Fr-En | De-En | Es-En | Ca-En | En-De | En-Ca | En-Fa | En-Et | Model |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| s2t_transformer_s | 31M | [26.3](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_fr_en_st_transformer_s.pt) | [17.1](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_de_en_st_transformer_s.pt) | [23.0](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_es_en_st_transformer_s.pt) | [18.8](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_ca_en_st_transformer_s.pt) | [16.3](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_de_st_transformer_s.pt) | [21.8](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_ca_st_transformer_s.pt) | [13.0](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_fa_st_transformer_s.pt) | [13.2](https://dl.fbaipublicfiles.com/fairseq/s2t/covost2_en_et_st_transformer_s.pt) | (<-Download) |
|
||||
|
||||
[[Back]](..)
|
||||
@@ -0,0 +1,61 @@
|
||||
[[Back]](..)
|
||||
|
||||
# S2T Example: Speech Recognition (ASR) on LibriSpeech
|
||||
[LibriSpeech](https://www.danielpovey.com/files/2015_icassp_librispeech.pdf) is a de-facto standard English ASR
|
||||
benchmark. We provide competitive
|
||||
vanilla [Transformer](https://papers.nips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf) baselines.
|
||||
|
||||
## Data preparation
|
||||
Download and preprocess LibriSpeech data with
|
||||
```bash
|
||||
# additional Python packages for S2T data processing/model training
|
||||
pip install pandas torchaudio sentencepiece
|
||||
|
||||
python examples/speech_to_text/prep_librispeech_data.py \
|
||||
--output-root ${LS_ROOT} --vocab-type unigram --vocab-size 10000
|
||||
```
|
||||
where `LS_ROOT` is the root path for downloaded data as well as generated files (manifest, features, vocabulary and
|
||||
data configuration).
|
||||
|
||||
[Download](https://dl.fbaipublicfiles.com/fairseq/s2t/librispeech_vocab_unigram10000.zip) our vocabulary files
|
||||
if you want to use our pre-trained models.
|
||||
|
||||
## Training
|
||||
```bash
|
||||
fairseq-train ${LS_ROOT} --save-dir ${SAVE_DIR} \
|
||||
--config-yaml config.yaml --train-subset train --valid-subset dev \
|
||||
--num-workers 4 --max-tokens 40000 --max-update 300000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --share-decoder-input-output-embed \
|
||||
--optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt --warmup-updates 10000 \
|
||||
--clip-norm 10.0 --seed 1 --update-freq 8
|
||||
```
|
||||
where `SAVE_DIR` is the checkpoint root path. Here we use `--arch s2t_transformer_s` (31M parameters) as example.
|
||||
For better performance, you may switch to `s2t_transformer_m` (71M, with `--lr 1e-3`) or `s2t_transformer_l`
|
||||
(268M, with `--lr 5e-4`). We set `--update-freq 8` to simulate 8 GPUs with 1 GPU. You may want to update it accordingly
|
||||
when using more than 1 GPU.
|
||||
|
||||
## Inference & Evaluation
|
||||
Average the last 10 checkpoints and evaluate on the 4 splits
|
||||
(`dev-clean`, `dev-other`, `test-clean` and `test-other`):
|
||||
```bash
|
||||
CHECKPOINT_FILENAME=avg_last_10_checkpoint.pt
|
||||
python scripts/average_checkpoints.py --inputs ${SAVE_DIR} \
|
||||
--num-epoch-checkpoints 10 \
|
||||
--output "${SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
for SUBSET in dev-clean dev-other test-clean test-other; do
|
||||
fairseq-generate ${LS_ROOT} --config-yaml config.yaml --gen-subset ${SUBSET} \
|
||||
--task speech_to_text --path ${SAVE_DIR}/${CHECKPOINT_FILENAME} \
|
||||
--max-tokens 50000 --beam 5 --scoring wer
|
||||
done
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
| --arch | Params | dev-clean | dev-other | test-clean | test-other | Model |
|
||||
|---|---|---|---|---|---|---|
|
||||
| s2t_transformer_s | 30M | 3.8 | 8.9 | 4.4 | 9.0 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/librispeech_transformer_s.pt) |
|
||||
| s2t_transformer_m | 71M | 3.2 | 8.0 | 3.4 | 7.9 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/librispeech_transformer_m.pt) |
|
||||
| s2t_transformer_l | 268M | 3.0 | 7.5 | 3.2 | 7.5 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/librispeech_transformer_l.pt) |
|
||||
|
||||
[[Back]](..)
|
||||
@@ -0,0 +1,155 @@
|
||||
[[Back]](..)
|
||||
|
||||
# S2T Example: Speech Translation (ST) on MuST-C
|
||||
|
||||
[MuST-C](https://www.aclweb.org/anthology/N19-1202) is multilingual speech-to-text translation corpus with
|
||||
8-language translations on English TED talks. We match the state-of-the-art performance in
|
||||
[ESPNet-ST](https://arxiv.org/pdf/2004.10234.pdf) with a simpler model training pipeline.
|
||||
|
||||
## Data Preparation
|
||||
[Download](https://ict.fbk.eu/must-c) and unpack MuST-C data to a path
|
||||
`${MUSTC_ROOT}/en-${TARGET_LANG_ID}`, then preprocess it with
|
||||
```bash
|
||||
# additional Python packages for S2T data processing/model training
|
||||
pip install pandas torchaudio sentencepiece
|
||||
|
||||
# Generate TSV manifests, features, vocabulary
|
||||
# and configuration for each language
|
||||
python examples/speech_to_text/prep_mustc_data.py \
|
||||
--data-root ${MUSTC_ROOT} --task asr \
|
||||
--vocab-type unigram --vocab-size 5000
|
||||
python examples/speech_to_text/prep_mustc_data.py \
|
||||
--data-root ${MUSTC_ROOT} --task st \
|
||||
--vocab-type unigram --vocab-size 8000
|
||||
|
||||
# Add vocabulary and configuration for joint data
|
||||
# (based on the manifests and features generated above)
|
||||
python examples/speech_to_text/prep_mustc_data.py \
|
||||
--data-root ${MUSTC_ROOT} --task asr --joint \
|
||||
--vocab-type unigram --vocab-size 10000
|
||||
python examples/speech_to_text/prep_mustc_data.py \
|
||||
--data-root ${MUSTC_ROOT} --task st --joint \
|
||||
--vocab-type unigram --vocab-size 10000
|
||||
```
|
||||
The generated files (manifest, features, vocabulary and data configuration) will be added to
|
||||
`${MUSTC_ROOT}/en-${TARGET_LANG_ID}` (per-language data) and `MUSTC_ROOT` (joint data).
|
||||
|
||||
Download our vocabulary files if you want to use our pre-trained models:
|
||||
- ASR: [En-De](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_de_asr_vocab_unigram5000.zip), [En-Nl](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_nl_asr_vocab_unigram5000.zip), [En-Es](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_es_asr_vocab_unigram5000.zip), [En-Fr](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_fr_asr_vocab_unigram5000.zip), [En-It](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_it_asr_vocab_unigram5000.zip), [En-Pt](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_pt_asr_vocab_unigram5000.zip), [En-Ro](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ro_asr_vocab_unigram5000.zip), [En-Ru](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ru_asr_vocab_unigram5000.zip), [Joint](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_joint_asr_vocab_unigram10000.zip)
|
||||
- ST: [En-De](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_de_st_vocab_unigram8000.zip), [En-Nl](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_nl_st_vocab_unigram8000.zip), [En-Es](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_es_st_vocab_unigram8000.zip), [En-Fr](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_fr_st_vocab_unigram8000.zip), [En-It](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_it_st_vocab_unigram8000.zip), [En-Pt](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_pt_st_vocab_unigram8000.zip), [En-Ro](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ro_st_vocab_unigram8000.zip), [En-Ru](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ru_st_vocab_unigram8000.zip), [Multilingual](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_multilingual_st_vocab_unigram10000.zip)
|
||||
|
||||
## ASR
|
||||
#### Training
|
||||
En-De as example:
|
||||
```bash
|
||||
fairseq-train ${MUSTC_ROOT}/en-de \
|
||||
--config-yaml config_asr.yaml --train-subset train_asr --valid-subset dev_asr \
|
||||
--save-dir ${ASR_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 100000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --optimizer adam --lr 1e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8
|
||||
```
|
||||
For joint model (using ASR data from all 8 directions):
|
||||
```bash
|
||||
fairseq-train ${MUSTC_ROOT} \
|
||||
--config-yaml config_asr.yaml \
|
||||
--train-subset train_de_asr,train_nl_asr,train_es_asr,train_fr_asr,train_it_asr,train_pt_asr,train_ro_asr,train_ru_asr \
|
||||
--valid-subset dev_de_asr,dev_nl_asr,dev_es_asr,dev_fr_asr,dev_it_asr,dev_pt_asr,dev_ro_asr,dev_ru_asr \
|
||||
--save-dir ${JOINT_ASR_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 100000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --optimizer adam --lr 1e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8
|
||||
```
|
||||
where `ASR_SAVE_DIR` (`JOINT_ASR_SAVE_DIR`) is the checkpoint root path. We set `--update-freq 8` to simulate 8 GPUs
|
||||
with 1 GPU. You may want to update it accordingly when using more than 1 GPU.
|
||||
|
||||
#### Inference & Evaluation
|
||||
```bash
|
||||
CHECKPOINT_FILENAME=avg_last_10_checkpoint.pt
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${ASR_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
fairseq-generate ${MUSTC_ROOT}/en-de \
|
||||
--config-yaml config_asr.yaml --gen-subset tst-COMMON_asr --task speech_to_text \
|
||||
--path ${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME} --max-tokens 50000 --beam 5 \
|
||||
--scoring wer --wer-tokenizer 13a --wer-lowercase --wer-remove-punct
|
||||
|
||||
# For models trained on joint data
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${JOINT_ASR_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${JOINT_ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
for LANG in de nl es fr it pt ro ru; do
|
||||
fairseq-generate ${MUSTC_ROOT} \
|
||||
--config-yaml config_asr.yaml --gen-subset tst-COMMON_${LANG}_asr --task speech_to_text \
|
||||
--path ${JOINT_ASR_SAVE_DIR}/${CHECKPOINT_FILENAME} --max-tokens 50000 --beam 5 \
|
||||
--scoring wer --wer-tokenizer 13a --wer-lowercase --wer-remove-punct
|
||||
done
|
||||
```
|
||||
#### Results
|
||||
| Data | --arch | Params | En-De | En-Nl | En-Es | En-Fr | En-It | En-Pt | En-Ro | En-Ru | Model |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Single | s2t_transformer_s | 31M | [18.2](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_de_asr_transformer_s.pt) | [17.6](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_nl_asr_transformer_s.pt) | [17.7](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_es_asr_transformer_s.pt) | [17.2](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_fr_asr_transformer_s.pt) | [17.9](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_it_asr_transformer_s.pt) | [19.1](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_pt_asr_transformer_s.pt) | [18.1](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ro_asr_transformer_s.pt) | [17.7](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ru_asr_transformer_s.pt) | (<-Download) |
|
||||
| Joint | s2t_transformer_m | 76M | 16.8 | 16.7 | 16.9 | 16.9 | 17.0 | 17.4 | 17.0 | 16.9 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_joint_asr_transformer_m.pt) |
|
||||
|
||||
## ST
|
||||
#### Training
|
||||
En-De as example:
|
||||
```bash
|
||||
fairseq-train ${MUSTC_ROOT}/en-de \
|
||||
--config-yaml config_st.yaml --train-subset train_st --valid-subset dev_st \
|
||||
--save-dir ${ST_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 100000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8 \
|
||||
--load-pretrained-encoder-from ${ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}
|
||||
```
|
||||
For multilingual model (all 8 directions):
|
||||
```bash
|
||||
fairseq-train ${MUSTC_ROOT} \
|
||||
--config-yaml config_st.yaml \
|
||||
--train-subset train_de_st,train_nl_st,train_es_st,train_fr_st,train_it_st,train_pt_st,train_ro_st,train_ru_st \
|
||||
--valid-subset dev_de_st,dev_nl_st,dev_es_st,dev_fr_st,dev_it_st,dev_pt_st,dev_ro_st,dev_ru_st \
|
||||
--save-dir ${MULTILINGUAL_ST_SAVE_DIR} --num-workers 4 --max-tokens 40000 --max-update 100000 \
|
||||
--task speech_to_text --criterion label_smoothed_cross_entropy --report-accuracy \
|
||||
--arch s2t_transformer_s --ignore-prefix-size 1 --optimizer adam --lr 2e-3 --lr-scheduler inverse_sqrt \
|
||||
--warmup-updates 10000 --clip-norm 10.0 --seed 1 --update-freq 8 \
|
||||
--load-pretrained-encoder-from ${JOINT_ASR_SAVE_DIR}/${CHECKPOINT_FILENAME}
|
||||
```
|
||||
where `ST_SAVE_DIR` (`MULTILINGUAL_ST_SAVE_DIR`) is the checkpoint root path. The ST encoder is pre-trained by ASR
|
||||
for faster training and better performance: `--load-pretrained-encoder-from <(JOINT_)ASR checkpoint path>`. We set
|
||||
`--update-freq 8` to simulate 8 GPUs with 1 GPU. You may want to update it accordingly when using more than 1 GPU.
|
||||
For multilingual models, we prepend target language ID token as target BOS, which should be excluded from
|
||||
the training loss via `--ignore-prefix-size 1`.
|
||||
|
||||
#### Inference & Evaluation
|
||||
Average the last 10 checkpoints and evaluate on the `tst-COMMON` split:
|
||||
```bash
|
||||
CHECKPOINT_FILENAME=avg_last_10_checkpoint.pt
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${ST_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${ST_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
fairseq-generate ${MUSTC_ROOT}/en-de \
|
||||
--config-yaml config_st.yaml --gen-subset tst-COMMON_st --task speech_to_text \
|
||||
--path ${ST_SAVE_DIR}/${CHECKPOINT_FILENAME} \
|
||||
--max-tokens 50000 --beam 5 --scoring sacrebleu
|
||||
|
||||
# For multilingual models
|
||||
python scripts/average_checkpoints.py \
|
||||
--inputs ${MULTILINGUAL_ST_SAVE_DIR} --num-epoch-checkpoints 10 \
|
||||
--output "${MULTILINGUAL_ST_SAVE_DIR}/${CHECKPOINT_FILENAME}"
|
||||
for LANG in de nl es fr it pt ro ru; do
|
||||
fairseq-generate ${MUSTC_ROOT} \
|
||||
--config-yaml config_st.yaml --gen-subset tst-COMMON_${LANG}_st --task speech_to_text \
|
||||
--prefix-size 1 --path ${MULTILINGUAL_ST_SAVE_DIR}/${CHECKPOINT_FILENAME} \
|
||||
--max-tokens 50000 --beam 5 --scoring sacrebleu
|
||||
done
|
||||
```
|
||||
For multilingual models, we force decoding from the target language ID token (as BOS) via `--prefix-size 1`.
|
||||
|
||||
#### Results
|
||||
| Data | --arch | Params | En-De | En-Nl | En-Es | En-Fr | En-It | En-Pt | En-Ro | En-Ru | Model |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| Bilingual | s2t_transformer_s | 31M | [22.7](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_de_st_transformer_s.pt) | [27.3](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_nl_st_transformer_s.pt) | [27.2](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_es_st_transformer_s.pt) | [32.9](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_fr_st_transformer_s.pt) | [22.7](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_it_st_transformer_s.pt) | [28.1](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_pt_st_transformer_s.pt) | [21.9](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ro_st_transformer_s.pt) | [15.3](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_ru_st_transformer_s.pt) | (<-Download) |
|
||||
| Multilingual | s2t_transformer_m | 76M | 24.5 | 28.6 | 28.2 | 34.9 | 24.6 | 31.1 | 23.8 | 16.0 | [Download](https://dl.fbaipublicfiles.com/fairseq/s2t/mustc_multilingual_st_transformer_m.pt) |
|
||||
|
||||
[[Back]](..)
|
||||
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
import torchaudio
|
||||
from examples.speech_to_text.data_utils import (
|
||||
create_zip,
|
||||
extract_fbank_features,
|
||||
filter_manifest_df,
|
||||
gen_config_yaml,
|
||||
gen_vocab,
|
||||
get_zip_manifest,
|
||||
load_df_from_tsv,
|
||||
save_df_to_tsv,
|
||||
)
|
||||
from torch import Tensor
|
||||
from torch.utils.data import Dataset
|
||||
from torchaudio.datasets.utils import download_url, extract_archive
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
MANIFEST_COLUMNS = ["id", "audio", "n_frames", "tgt_text", "speaker"]
|
||||
|
||||
|
||||
class CoVoST(Dataset):
|
||||
"""Create a Dataset for CoVoST (https://github.com/facebookresearch/covost).
|
||||
|
||||
Args:
|
||||
root (str): root path to the dataset and generated manifests/features
|
||||
source_language (str): source (audio) language
|
||||
target_language (str, optional): target (text) language,
|
||||
None for no translation (default: None)
|
||||
version (int, optional): CoVoST version. (default: 2)
|
||||
download (bool, optional): Whether to download the dataset if it is not
|
||||
found at root path. (default: ``False``).
|
||||
"""
|
||||
|
||||
COVOST_URL_TEMPLATE = (
|
||||
"https://dl.fbaipublicfiles.com/covost/"
|
||||
"covost_v2.{src_lang}_{tgt_lang}.tsv.tar.gz"
|
||||
)
|
||||
|
||||
VERSIONS = {2}
|
||||
SPLITS = ["train", "dev", "test"]
|
||||
|
||||
XX_EN_LANGUAGES = {
|
||||
1: ["fr", "de", "nl", "ru", "es", "it", "tr", "fa", "sv-SE", "mn", "zh-CN"],
|
||||
2: [
|
||||
"fr",
|
||||
"de",
|
||||
"es",
|
||||
"ca",
|
||||
"it",
|
||||
"ru",
|
||||
"zh-CN",
|
||||
"pt",
|
||||
"fa",
|
||||
"et",
|
||||
"mn",
|
||||
"nl",
|
||||
"tr",
|
||||
"ar",
|
||||
"sv-SE",
|
||||
"lv",
|
||||
"sl",
|
||||
"ta",
|
||||
"ja",
|
||||
"id",
|
||||
"cy",
|
||||
],
|
||||
}
|
||||
EN_XX_LANGUAGES = {
|
||||
1: [],
|
||||
2: [
|
||||
"de",
|
||||
"tr",
|
||||
"fa",
|
||||
"sv-SE",
|
||||
"mn",
|
||||
"zh-CN",
|
||||
"cy",
|
||||
"ca",
|
||||
"sl",
|
||||
"et",
|
||||
"id",
|
||||
"ar",
|
||||
"ta",
|
||||
"lv",
|
||||
"ja",
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root: str,
|
||||
split: str,
|
||||
source_language: str,
|
||||
target_language: Optional[str] = None,
|
||||
version: int = 2,
|
||||
) -> None:
|
||||
assert version in self.VERSIONS and split in self.SPLITS
|
||||
assert source_language is not None
|
||||
self.no_translation = target_language is None
|
||||
if not self.no_translation:
|
||||
assert "en" in {source_language, target_language}
|
||||
if source_language == "en":
|
||||
assert target_language in self.EN_XX_LANGUAGES[version]
|
||||
else:
|
||||
assert source_language in self.XX_EN_LANGUAGES[version]
|
||||
else:
|
||||
# Hack here so that we can get "split" column from CoVoST TSV.
|
||||
# Note that we use CoVoST train split for ASR which is an extension
|
||||
# to Common Voice train split.
|
||||
target_language = "de" if source_language == "en" else "en"
|
||||
|
||||
self.root: Path = Path(root)
|
||||
|
||||
cv_tsv_path = self.root / "validated.tsv"
|
||||
assert cv_tsv_path.is_file()
|
||||
|
||||
covost_url = self.COVOST_URL_TEMPLATE.format(
|
||||
src_lang=source_language, tgt_lang=target_language
|
||||
)
|
||||
covost_archive = self.root / Path(covost_url).name
|
||||
if not covost_archive.is_file():
|
||||
download_url(covost_url, self.root.as_posix(), hash_value=None)
|
||||
extract_archive(covost_archive.as_posix())
|
||||
|
||||
cv_tsv = load_df_from_tsv(cv_tsv_path)
|
||||
covost_tsv = load_df_from_tsv(
|
||||
self.root / Path(covost_url).name.replace(".tar.gz", "")
|
||||
)
|
||||
df = pd.merge(
|
||||
left=cv_tsv[["path", "sentence", "client_id"]],
|
||||
right=covost_tsv[["path", "translation", "split"]],
|
||||
how="inner",
|
||||
on="path",
|
||||
)
|
||||
if split == "train":
|
||||
df = df[(df["split"] == split) | (df["split"] == f"{split}_covost")]
|
||||
else:
|
||||
df = df[df["split"] == split]
|
||||
data = df.to_dict(orient="index").items()
|
||||
data = [v for k, v in sorted(data, key=lambda x: x[0])]
|
||||
self.data = []
|
||||
for e in data:
|
||||
try:
|
||||
path = self.root / "clips" / e["path"]
|
||||
_ = torchaudio.info(path.as_posix())
|
||||
self.data.append(e)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def __getitem__(
|
||||
self, n: int
|
||||
) -> Tuple[Tensor, int, str, str, Optional[str], str, str]:
|
||||
"""Load the n-th sample from the dataset.
|
||||
|
||||
Args:
|
||||
n (int): The index of the sample to be loaded
|
||||
|
||||
Returns:
|
||||
tuple: ``(waveform, sample_rate, sentence, translation, speaker_id,
|
||||
sample_id)``
|
||||
"""
|
||||
data = self.data[n]
|
||||
path = self.root / "clips" / data["path"]
|
||||
waveform, sample_rate = torchaudio.load(path)
|
||||
sentence = data["sentence"]
|
||||
translation = None if self.no_translation else data["translation"]
|
||||
speaker_id = data["client_id"]
|
||||
_id = data["path"].replace(".mp3", "")
|
||||
return waveform, sample_rate, sentence, translation, speaker_id, _id
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.data)
|
||||
|
||||
|
||||
def process(args):
|
||||
root = Path(args.data_root).absolute() / args.src_lang
|
||||
if not root.is_dir():
|
||||
raise NotADirectoryError(f"{root} does not exist")
|
||||
# Extract features
|
||||
feature_root = root / "fbank80"
|
||||
feature_root.mkdir(exist_ok=True)
|
||||
for split in CoVoST.SPLITS:
|
||||
print(f"Fetching split {split}...")
|
||||
dataset = CoVoST(root, split, args.src_lang, args.tgt_lang)
|
||||
print("Extracting log mel filter bank features...")
|
||||
for waveform, sample_rate, _, _, _, utt_id in tqdm(dataset):
|
||||
extract_fbank_features(
|
||||
waveform, sample_rate, feature_root / f"{utt_id}.npy"
|
||||
)
|
||||
# Pack features into ZIP
|
||||
zip_path = root / "fbank80.zip"
|
||||
print("ZIPing features...")
|
||||
create_zip(feature_root, zip_path)
|
||||
print("Fetching ZIP manifest...")
|
||||
zip_manifest = get_zip_manifest(zip_path)
|
||||
# Generate TSV manifest
|
||||
print("Generating manifest...")
|
||||
train_text = []
|
||||
task = f"asr_{args.src_lang}"
|
||||
if args.tgt_lang is not None:
|
||||
task = f"st_{args.src_lang}_{args.tgt_lang}"
|
||||
for split in CoVoST.SPLITS:
|
||||
manifest = {c: [] for c in MANIFEST_COLUMNS}
|
||||
dataset = CoVoST(root, split, args.src_lang, args.tgt_lang)
|
||||
for wav, sr, src_utt, tgt_utt, speaker_id, utt_id in tqdm(dataset):
|
||||
manifest["id"].append(utt_id)
|
||||
manifest["audio"].append(zip_manifest[utt_id])
|
||||
duration_ms = int(wav.size(1) / sr * 1000)
|
||||
manifest["n_frames"].append(int(1 + (duration_ms - 25) / 10))
|
||||
manifest["tgt_text"].append(src_utt if args.tgt_lang is None else tgt_utt)
|
||||
manifest["speaker"].append(speaker_id)
|
||||
is_train_split = split.startswith("train")
|
||||
if is_train_split:
|
||||
train_text.extend(manifest["tgt_text"])
|
||||
df = pd.DataFrame.from_dict(manifest)
|
||||
df = filter_manifest_df(df, is_train_split=is_train_split)
|
||||
save_df_to_tsv(df, root / f"{split}_{task}.tsv")
|
||||
# Generate vocab
|
||||
vocab_size_str = "" if args.vocab_type == "char" else str(args.vocab_size)
|
||||
spm_filename_prefix = f"spm_{args.vocab_type}{vocab_size_str}_{task}"
|
||||
with NamedTemporaryFile(mode="w") as f:
|
||||
for t in train_text:
|
||||
f.write(t + "\n")
|
||||
gen_vocab(
|
||||
Path(f.name),
|
||||
root / spm_filename_prefix,
|
||||
args.vocab_type,
|
||||
args.vocab_size
|
||||
)
|
||||
# Generate config YAML
|
||||
gen_config_yaml(
|
||||
root,
|
||||
spm_filename_prefix + ".model",
|
||||
yaml_filename=f"config_{task}.yaml",
|
||||
specaugment_policy="lb",
|
||||
)
|
||||
# Clean up
|
||||
shutil.rmtree(feature_root)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--data-root", "-d", required=True, type=str,
|
||||
help="data root with sub-folders for each language <root>/<src_lang>"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vocab-type",
|
||||
default="unigram",
|
||||
required=True,
|
||||
type=str,
|
||||
choices=["bpe", "unigram", "char"],
|
||||
),
|
||||
parser.add_argument("--vocab-size", default=1000, type=int)
|
||||
parser.add_argument("--src-lang", "-s", required=True, type=str)
|
||||
parser.add_argument("--tgt-lang", "-t", type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
process(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import pandas as pd
|
||||
from examples.speech_to_text.data_utils import (
|
||||
create_zip,
|
||||
extract_fbank_features,
|
||||
gen_config_yaml,
|
||||
gen_vocab,
|
||||
get_zip_manifest,
|
||||
save_df_to_tsv,
|
||||
)
|
||||
from torchaudio.datasets import LIBRISPEECH
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
SPLITS = [
|
||||
"train-clean-100",
|
||||
"train-clean-360",
|
||||
"train-other-500",
|
||||
"dev-clean",
|
||||
"dev-other",
|
||||
"test-clean",
|
||||
"test-other",
|
||||
]
|
||||
|
||||
MANIFEST_COLUMNS = ["id", "audio", "n_frames", "tgt_text", "speaker"]
|
||||
|
||||
|
||||
def process(args):
|
||||
out_root = Path(args.output_root).absolute()
|
||||
out_root.mkdir(exist_ok=True)
|
||||
# Extract features
|
||||
feature_root = out_root / "fbank80"
|
||||
feature_root.mkdir(exist_ok=True)
|
||||
for split in SPLITS:
|
||||
print(f"Fetching split {split}...")
|
||||
dataset = LIBRISPEECH(out_root.as_posix(), url=split, download=True)
|
||||
print("Extracting log mel filter bank features...")
|
||||
for wav, sample_rate, _, spk_id, chapter_no, utt_no in tqdm(dataset):
|
||||
sample_id = f"{spk_id}-{chapter_no}-{utt_no}"
|
||||
extract_fbank_features(
|
||||
wav, sample_rate, feature_root / f"{sample_id}.npy"
|
||||
)
|
||||
# Pack features into ZIP
|
||||
zip_path = out_root / "fbank80.zip"
|
||||
print("ZIPing features...")
|
||||
create_zip(feature_root, zip_path)
|
||||
print("Fetching ZIP manifest...")
|
||||
zip_manifest = get_zip_manifest(zip_path)
|
||||
# Generate TSV manifest
|
||||
print("Generating manifest...")
|
||||
train_text = []
|
||||
for split in SPLITS:
|
||||
manifest = {c: [] for c in MANIFEST_COLUMNS}
|
||||
dataset = LIBRISPEECH(out_root.as_posix(), url=split)
|
||||
for wav, sample_rate, utt, spk_id, chapter_no, utt_no in tqdm(dataset):
|
||||
sample_id = f"{spk_id}-{chapter_no}-{utt_no}"
|
||||
manifest["id"].append(sample_id)
|
||||
manifest["audio"].append(zip_manifest[sample_id])
|
||||
duration_ms = int(wav.size(1) / sample_rate * 1000)
|
||||
manifest["n_frames"].append(int(1 + (duration_ms - 25) / 10))
|
||||
manifest["tgt_text"].append(utt)
|
||||
manifest["speaker"].append(spk_id)
|
||||
save_df_to_tsv(
|
||||
pd.DataFrame.from_dict(manifest), out_root / f"{split}.tsv"
|
||||
)
|
||||
if split.startswith("train"):
|
||||
train_text.extend(manifest["tgt_text"])
|
||||
# Generate vocab
|
||||
vocab_size = "" if args.vocab_type == "char" else str(args.vocab_size)
|
||||
spm_filename_prefix = f"spm_{args.vocab_type}{vocab_size}"
|
||||
with NamedTemporaryFile(mode="w") as f:
|
||||
for t in train_text:
|
||||
f.write(t + "\n")
|
||||
gen_vocab(
|
||||
Path(f.name),
|
||||
out_root / spm_filename_prefix,
|
||||
args.vocab_type,
|
||||
args.vocab_size,
|
||||
)
|
||||
# Generate config YAML
|
||||
gen_config_yaml(
|
||||
out_root, spm_filename_prefix + ".model", specaugment_policy="ld"
|
||||
)
|
||||
# Clean up
|
||||
shutil.rmtree(feature_root)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output-root", "-o", required=True, type=str)
|
||||
parser.add_argument(
|
||||
"--vocab-type",
|
||||
default="unigram",
|
||||
required=True,
|
||||
type=str,
|
||||
choices=["bpe", "unigram", "char"],
|
||||
),
|
||||
parser.add_argument("--vocab-size", default=10000, type=int)
|
||||
args = parser.parse_args()
|
||||
|
||||
process(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from itertools import groupby
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import Tuple
|
||||
|
||||
import pandas as pd
|
||||
import torchaudio
|
||||
from examples.speech_to_text.data_utils import (
|
||||
create_zip,
|
||||
extract_fbank_features,
|
||||
filter_manifest_df,
|
||||
gen_config_yaml,
|
||||
gen_vocab,
|
||||
get_zip_manifest,
|
||||
load_df_from_tsv,
|
||||
save_df_to_tsv,
|
||||
)
|
||||
from torch import Tensor
|
||||
from torch.utils.data import Dataset
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
MANIFEST_COLUMNS = ["id", "audio", "n_frames", "tgt_text", "speaker"]
|
||||
|
||||
|
||||
class MUSTC(Dataset):
|
||||
"""
|
||||
Create a Dataset for MuST-C. Each item is a tuple of the form:
|
||||
waveform, sample_rate, source utterance, target utterance, speaker_id,
|
||||
utterance_id
|
||||
"""
|
||||
|
||||
SPLITS = ["train", "dev", "tst-COMMON", "tst-HE"]
|
||||
LANGUAGES = ["de", "es", "fr", "it", "nl", "pt", "ro", "ru"]
|
||||
|
||||
def __init__(self, root: str, lang: str, split: str) -> None:
|
||||
assert split in self.SPLITS and lang in self.LANGUAGES
|
||||
_root = Path(root) / f"en-{lang}" / "data" / split
|
||||
wav_root, txt_root = _root / "wav", _root / "txt"
|
||||
assert _root.is_dir() and wav_root.is_dir() and txt_root.is_dir()
|
||||
# Load audio segments
|
||||
try:
|
||||
import yaml
|
||||
except ImportError:
|
||||
print("Please install PyYAML to load the MuST-C YAML files")
|
||||
with open(txt_root / f"{split}.yaml") as f:
|
||||
segments = yaml.load(f, Loader=yaml.BaseLoader)
|
||||
# Load source and target utterances
|
||||
for _lang in ["en", lang]:
|
||||
with open(txt_root / f"{split}.{_lang}") as f:
|
||||
utterances = [r.strip() for r in f]
|
||||
assert len(segments) == len(utterances)
|
||||
for i, u in enumerate(utterances):
|
||||
segments[i][_lang] = u
|
||||
# Gather info
|
||||
self.data = []
|
||||
for wav_filename, _seg_group in groupby(segments, lambda x: x["wav"]):
|
||||
wav_path = wav_root / wav_filename
|
||||
sample_rate = torchaudio.info(wav_path.as_posix())[0].rate
|
||||
seg_group = sorted(_seg_group, key=lambda x: x["offset"])
|
||||
for i, segment in enumerate(seg_group):
|
||||
offset = int(float(segment["offset"]) * sample_rate)
|
||||
n_frames = int(float(segment["duration"]) * sample_rate)
|
||||
_id = f"{wav_path.stem}_{i}"
|
||||
self.data.append(
|
||||
(
|
||||
wav_path.as_posix(),
|
||||
offset,
|
||||
n_frames,
|
||||
sample_rate,
|
||||
segment["en"],
|
||||
segment[lang],
|
||||
segment["speaker_id"],
|
||||
_id,
|
||||
)
|
||||
)
|
||||
|
||||
def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str, str, str]:
|
||||
wav_path, offset, n_frames, sr, src_utt, tgt_utt, spk_id, utt_id = self.data[n]
|
||||
waveform, _ = torchaudio.load(wav_path, offset=offset, num_frames=n_frames)
|
||||
return waveform, sr, src_utt, tgt_utt, spk_id, utt_id
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.data)
|
||||
|
||||
|
||||
def process(args):
|
||||
root = Path(args.data_root).absolute()
|
||||
for lang in MUSTC.LANGUAGES:
|
||||
cur_root = root / f"en-{lang}"
|
||||
if not cur_root.is_dir():
|
||||
print(f"{cur_root.as_posix()} does not exist. Skipped.")
|
||||
continue
|
||||
# Extract features
|
||||
feature_root = cur_root / "fbank80"
|
||||
feature_root.mkdir(exist_ok=True)
|
||||
for split in MUSTC.SPLITS:
|
||||
print(f"Fetching split {split}...")
|
||||
dataset = MUSTC(root.as_posix(), lang, split)
|
||||
print("Extracting log mel filter bank features...")
|
||||
for waveform, sample_rate, _, _, _, utt_id in tqdm(dataset):
|
||||
extract_fbank_features(
|
||||
waveform, sample_rate, feature_root / f"{utt_id}.npy"
|
||||
)
|
||||
# Pack features into ZIP
|
||||
zip_path = cur_root / "fbank80.zip"
|
||||
print("ZIPing features...")
|
||||
create_zip(feature_root, zip_path)
|
||||
print("Fetching ZIP manifest...")
|
||||
zip_manifest = get_zip_manifest(zip_path)
|
||||
# Generate TSV manifest
|
||||
print("Generating manifest...")
|
||||
train_text = []
|
||||
for split in MUSTC.SPLITS:
|
||||
is_train_split = split.startswith("train")
|
||||
manifest = {c: [] for c in MANIFEST_COLUMNS}
|
||||
dataset = MUSTC(args.data_root, lang, split)
|
||||
for wav, sr, src_utt, tgt_utt, speaker_id, utt_id in tqdm(dataset):
|
||||
manifest["id"].append(utt_id)
|
||||
manifest["audio"].append(zip_manifest[utt_id])
|
||||
duration_ms = int(wav.size(1) / sr * 1000)
|
||||
manifest["n_frames"].append(int(1 + (duration_ms - 25) / 10))
|
||||
manifest["tgt_text"].append(src_utt if args.task == "asr" else tgt_utt)
|
||||
manifest["speaker"].append(speaker_id)
|
||||
if is_train_split:
|
||||
train_text.extend(manifest["tgt_text"])
|
||||
df = pd.DataFrame.from_dict(manifest)
|
||||
df = filter_manifest_df(df, is_train_split=is_train_split)
|
||||
save_df_to_tsv(df, cur_root / f"{split}_{args.task}.tsv")
|
||||
# Generate vocab
|
||||
v_size_str = "" if args.vocab_type == "char" else str(args.vocab_size)
|
||||
spm_filename_prefix = f"spm_{args.vocab_type}{v_size_str}_{args.task}"
|
||||
with NamedTemporaryFile(mode="w") as f:
|
||||
for t in train_text:
|
||||
f.write(t + "\n")
|
||||
gen_vocab(
|
||||
Path(f.name),
|
||||
cur_root / spm_filename_prefix,
|
||||
args.vocab_type,
|
||||
args.vocab_size,
|
||||
)
|
||||
# Generate config YAML
|
||||
gen_config_yaml(
|
||||
cur_root,
|
||||
spm_filename_prefix + ".model",
|
||||
yaml_filename=f"config_{args.task}.yaml",
|
||||
specaugment_policy="lb",
|
||||
)
|
||||
# Clean up
|
||||
shutil.rmtree(feature_root)
|
||||
|
||||
|
||||
def process_joint(args):
|
||||
cur_root = Path(args.data_root)
|
||||
assert all((cur_root / f"en-{lang}").is_dir() for lang in MUSTC.LANGUAGES), \
|
||||
"do not have downloaded data available for all 8 languages"
|
||||
# Generate vocab
|
||||
vocab_size_str = "" if args.vocab_type == "char" else str(args.vocab_size)
|
||||
spm_filename_prefix = f"spm_{args.vocab_type}{vocab_size_str}_{args.task}"
|
||||
with NamedTemporaryFile(mode="w") as f:
|
||||
for lang in MUSTC.LANGUAGES:
|
||||
tsv_path = cur_root / f"en-{lang}" / f"train_{args.task}.tsv"
|
||||
df = load_df_from_tsv(tsv_path)
|
||||
for t in df["tgt_text"]:
|
||||
f.write(t + "\n")
|
||||
special_symbols = None
|
||||
if args.task == 'st':
|
||||
special_symbols = [f'<lang:{lang}>' for lang in MUSTC.LANGUAGES]
|
||||
gen_vocab(
|
||||
Path(f.name),
|
||||
cur_root / spm_filename_prefix,
|
||||
args.vocab_type,
|
||||
args.vocab_size,
|
||||
special_symbols=special_symbols
|
||||
)
|
||||
# Generate config YAML
|
||||
gen_config_yaml(
|
||||
cur_root,
|
||||
spm_filename_prefix + ".model",
|
||||
yaml_filename=f"config_{args.task}.yaml",
|
||||
specaugment_policy="ld",
|
||||
prepend_tgt_lang_tag=(args.task == "st"),
|
||||
)
|
||||
# Make symbolic links to manifests
|
||||
for lang in MUSTC.LANGUAGES:
|
||||
for split in MUSTC.SPLITS:
|
||||
src_path = cur_root / f"en-{lang}" / f"{split}_{args.task}.tsv"
|
||||
desc_path = cur_root / f"{split}_{lang}_{args.task}.tsv"
|
||||
if not desc_path.is_symlink():
|
||||
os.symlink(src_path, desc_path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--data-root", "-d", required=True, type=str)
|
||||
parser.add_argument(
|
||||
"--vocab-type",
|
||||
default="unigram",
|
||||
required=True,
|
||||
type=str,
|
||||
choices=["bpe", "unigram", "char"],
|
||||
),
|
||||
parser.add_argument("--vocab-size", default=8000, type=int)
|
||||
parser.add_argument("--task", type=str, choices=["asr", "st"])
|
||||
parser.add_argument("--joint", action="store_true", help="")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.joint:
|
||||
process_joint(args)
|
||||
else:
|
||||
process(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user