from local
12
egs/LJSpeech/ASR/add_alignments.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eou pipefail
|
||||||
|
|
||||||
|
alignments_dir=data/alignment
|
||||||
|
cuts_in_dir=data/fbank
|
||||||
|
cuts_out_dir=data/fbank_ali
|
||||||
|
|
||||||
|
python3 ./local/add_alignment_librispeech.py \
|
||||||
|
--alignments-dir $alignments_dir \
|
||||||
|
--cuts-in-dir $cuts_in_dir \
|
||||||
|
--cuts-out-dir $cuts_out_dir
|
||||||
207
egs/LJSpeech/ASR/distillation_with_hubert.sh
Executable file
@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# A short introduction about distillation framework.
|
||||||
|
#
|
||||||
|
# A typical traditional distillation method is
|
||||||
|
# Loss(teacher embedding, student embedding).
|
||||||
|
#
|
||||||
|
# Comparing to these, the proposed distillation framework contains two mainly steps:
|
||||||
|
# codebook indexes = quantizer.encode(teacher embedding)
|
||||||
|
# Loss(codebook indexes, student embedding)
|
||||||
|
#
|
||||||
|
# Things worth to meantion:
|
||||||
|
# 1. The float type teacher embedding is quantized into a sequence of
|
||||||
|
# 8-bit integer codebook indexes.
|
||||||
|
# 2. a middle layer 36(1-based) out of total 48 layers is used to extract
|
||||||
|
# teacher embeddings.
|
||||||
|
# 3. a middle layer 6(1-based) out of total 6 layers is used to extract
|
||||||
|
# student embeddings.
|
||||||
|
#
|
||||||
|
# To directly download the extracted codebook indexes for model distillation, you can
|
||||||
|
# set stage=2, stop_stage=4, use_extracted_codebook=True
|
||||||
|
#
|
||||||
|
# To start from scratch, you can
|
||||||
|
# set stage=0, stop_stage=4, use_extracted_codebook=False
|
||||||
|
|
||||||
|
stage=0
|
||||||
|
stop_stage=4
|
||||||
|
|
||||||
|
# Set the GPUs available.
|
||||||
|
# This script requires at least one GPU.
|
||||||
|
# You MUST set environment variable "CUDA_VISIBLE_DEVICES",
|
||||||
|
# even you only have ONE GPU. It needed by CodebookIndexExtractor to determine numbert of jobs to extract codebook indexes parallelly.
|
||||||
|
|
||||||
|
# Suppose only one GPU exists:
|
||||||
|
# export CUDA_VISIBLE_DEVICES="0"
|
||||||
|
#
|
||||||
|
# Suppose GPU 2,3,4,5 are available.
|
||||||
|
export CUDA_VISIBLE_DEVICES="0,1,2,3"
|
||||||
|
|
||||||
|
exp_dir=./pruned_transducer_stateless6/exp
|
||||||
|
mkdir -p $exp_dir
|
||||||
|
|
||||||
|
# full_libri can be "True" or "False"
|
||||||
|
# "True" -> use full librispeech dataset for distillation
|
||||||
|
# "False" -> use train-clean-100 subset for distillation
|
||||||
|
full_libri=False
|
||||||
|
|
||||||
|
# use_extracted_codebook can be "True" or "False"
|
||||||
|
# "True" -> stage 0 and stage 1 would be skipped,
|
||||||
|
# and directly download the extracted codebook indexes for distillation
|
||||||
|
# "False" -> start from scratch
|
||||||
|
use_extracted_codebook=False
|
||||||
|
|
||||||
|
# teacher_model_id can be one of
|
||||||
|
# "hubert_xtralarge_ll60k_finetune_ls960" -> fine-tuned model, it is the one we currently use.
|
||||||
|
# "hubert_xtralarge_ll60k" -> pretrained model without fintuing
|
||||||
|
teacher_model_id=hubert_xtralarge_ll60k_finetune_ls960
|
||||||
|
|
||||||
|
log() {
|
||||||
|
# This function is from espnet
|
||||||
|
local fname=${BASH_SOURCE[1]##*/}
|
||||||
|
echo -e "$(date '+%Y-%m-%d %H:%M:%S') (${fname}:${BASH_LINENO[0]}:${FUNCNAME[1]}) $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $stage -le 0 ] && [ $stop_stage -ge 0 ] && [ ! "$use_extracted_codebook" == "True" ]; then
|
||||||
|
log "Stage 0: Download HuBERT model"
|
||||||
|
# Preparation stage.
|
||||||
|
|
||||||
|
# Install fairseq according to:
|
||||||
|
# https://github.com/pytorch/fairseq
|
||||||
|
# when testing this code:
|
||||||
|
# commit 806855bf660ea748ed7ffb42fe8dcc881ca3aca0 is used.
|
||||||
|
has_fairseq=$(python3 -c "import importlib; print(importlib.util.find_spec('fairseq') is not None)")
|
||||||
|
if [ $has_fairseq == 'False' ]; then
|
||||||
|
log "Please install fairseq before running following stages"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install quantization toolkit:
|
||||||
|
# pip install git+https://github.com/k2-fsa/multi_quantization.git
|
||||||
|
# or
|
||||||
|
# pip install multi_quantization
|
||||||
|
|
||||||
|
has_quantization=$(python3 -c "import importlib; print(importlib.util.find_spec('multi_quantization') is not None)")
|
||||||
|
if [ $has_quantization == 'False' ]; then
|
||||||
|
log "Please install multi_quantization before running following stages"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Download HuBERT model."
|
||||||
|
# Parameters about model.
|
||||||
|
hubert_model_dir=${exp_dir}/hubert_models
|
||||||
|
hubert_model=${hubert_model_dir}/${teacher_model_id}.pt
|
||||||
|
mkdir -p ${hubert_model_dir}
|
||||||
|
# For more models refer to: https://github.com/pytorch/fairseq/tree/main/examples/hubert
|
||||||
|
if [ -f ${hubert_model} ]; then
|
||||||
|
log "HuBERT model alread exists."
|
||||||
|
else
|
||||||
|
wget -c https://dl.fbaipublicfiles.com/hubert/${teacher_model_id}.pt -P ${hubert_model_dir}
|
||||||
|
wget -c wget https://dl.fbaipublicfiles.com/fairseq/wav2vec/dict.ltr.txt -P ${hubert_model_dir}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d ./data/fbank ]; then
|
||||||
|
log "This script assumes ./data/fbank is already generated by prepare.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stage -le 1 ] && [ $stop_stage -ge 1 ] && [ ! "$use_extracted_codebook" == "True" ]; then
|
||||||
|
log "Stage 1: Verify that the downloaded HuBERT model is correct."
|
||||||
|
# This stage is not directly used by codebook indexes extraction.
|
||||||
|
# It is a method to "prove" that the downloaed hubert model
|
||||||
|
# is inferenced in an correct way if WERs look like normal.
|
||||||
|
# Expect WERs:
|
||||||
|
# [test-clean-ctc_greedy_search] %WER 2.04% [1075 / 52576, 92 ins, 104 del, 879 sub ]
|
||||||
|
# [test-other-ctc_greedy_search] %WER 3.71% [1942 / 52343, 152 ins, 126 del, 1664 sub ]
|
||||||
|
./pruned_transducer_stateless6/hubert_decode.py --exp-dir $exp_dir
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stage -le 2 ] && [ $stop_stage -ge 2 ]; then
|
||||||
|
# Analysis of disk usage:
|
||||||
|
# With num_codebooks==8, each teacher embedding is quantized into
|
||||||
|
# a sequence of eight 8-bit integers, i.e. only eight bytes are needed.
|
||||||
|
# Training dataset including clean-100h with speed perturb 0.9 and 1.1 has 300 hours.
|
||||||
|
# The output frame rates of Hubert is 50 per second.
|
||||||
|
# Theoretically, 412M = 300 * 3600 * 50 * 8 / 1024 / 1024 is needed.
|
||||||
|
# The actual size of all "*.h5" files storaging codebook index is 450M.
|
||||||
|
# I think the extra "48M" usage is some meta information.
|
||||||
|
|
||||||
|
# Time consumption analysis:
|
||||||
|
# For quantizer training data(teacher embedding) extraction, only 1000 utts from clean-100 are used.
|
||||||
|
# Together with quantizer training, no more than 20 minutes will be used.
|
||||||
|
#
|
||||||
|
# For codebook indexes extraction,
|
||||||
|
# with two pieces of NVIDIA A100 gpus, around three hours needed to process 300 hours training data,
|
||||||
|
# i.e. clean-100 with speed purteb 0.9 and 1.1.
|
||||||
|
|
||||||
|
# GPU usage:
|
||||||
|
# During quantizer's training data(teacher embedding) and it's training,
|
||||||
|
# only the first ONE GPU is used.
|
||||||
|
# During codebook indexes extraction, ALL GPUs set by CUDA_VISIBLE_DEVICES are used.
|
||||||
|
|
||||||
|
if [ "$use_extracted_codebook" == "True" ]; then
|
||||||
|
if [ ! "$teacher_model_id" == "hubert_xtralarge_ll60k_finetune_ls960" ]; then
|
||||||
|
log "Currently we only uploaded codebook indexes from teacher model hubert_xtralarge_ll60k_finetune_ls960"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p $exp_dir/vq
|
||||||
|
codebook_dir=$exp_dir/vq/$teacher_model_id
|
||||||
|
mkdir -p codebook_dir
|
||||||
|
codebook_download_dir=$exp_dir/download_codebook
|
||||||
|
if [ -d $codebook_download_dir ]; then
|
||||||
|
log "$codebook_download_dir exists, you should remove it first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
log "Downloading extracted codebook indexes to $codebook_download_dir"
|
||||||
|
# Make sure you have git-lfs installed (https://git-lfs.github.com)
|
||||||
|
git lfs install
|
||||||
|
git clone https://huggingface.co/Zengwei/pruned_transducer_stateless6_hubert_xtralarge_ll60k_finetune_ls960 $codebook_download_dir
|
||||||
|
|
||||||
|
mkdir -p data/vq_fbank
|
||||||
|
mv $codebook_download_dir/*.jsonl.gz data/vq_fbank/
|
||||||
|
mkdir -p $codebook_dir/splits4
|
||||||
|
mv $codebook_download_dir/*.h5 $codebook_dir/splits4/
|
||||||
|
log "Remove $codebook_download_dir"
|
||||||
|
rm -rf $codebook_download_dir
|
||||||
|
fi
|
||||||
|
|
||||||
|
./pruned_transducer_stateless6/extract_codebook_index.py \
|
||||||
|
--full-libri $full_libri \
|
||||||
|
--exp-dir $exp_dir \
|
||||||
|
--embedding-layer 36 \
|
||||||
|
--num-utts 1000 \
|
||||||
|
--num-codebooks 8 \
|
||||||
|
--max-duration 100 \
|
||||||
|
--teacher-model-id $teacher_model_id \
|
||||||
|
--use-extracted-codebook $use_extracted_codebook
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stage -le 3 ] && [ $stop_stage -ge 3 ]; then
|
||||||
|
# Example training script.
|
||||||
|
# Note: it's better to set spec-aug-time-warpi-factor=-1
|
||||||
|
WORLD_SIZE=$(echo ${CUDA_VISIBLE_DEVICES} | awk '{n=split($1, _, ","); print n}')
|
||||||
|
./pruned_transducer_stateless6/train.py \
|
||||||
|
--manifest-dir ./data/vq_fbank \
|
||||||
|
--master-port 12359 \
|
||||||
|
--full-libri $full_libri \
|
||||||
|
--spec-aug-time-warp-factor -1 \
|
||||||
|
--max-duration 300 \
|
||||||
|
--world-size ${WORLD_SIZE} \
|
||||||
|
--num-epochs 20 \
|
||||||
|
--exp-dir $exp_dir \
|
||||||
|
--enable-distillation True
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stage -le 4 ] && [ $stop_stage -ge 4 ]; then
|
||||||
|
# Results should be similar to:
|
||||||
|
# errs-test-clean-beam_size_4-epoch-20-avg-10-beam-4.txt:%WER = 5.67
|
||||||
|
# errs-test-other-beam_size_4-epoch-20-avg-10-beam-4.txt:%WER = 15.60
|
||||||
|
./pruned_transducer_stateless6/decode.py \
|
||||||
|
--decoding-method "modified_beam_search" \
|
||||||
|
--epoch 20 \
|
||||||
|
--avg 10 \
|
||||||
|
--max-duration 200 \
|
||||||
|
--exp-dir $exp_dir \
|
||||||
|
--enable-distillation True
|
||||||
|
fi
|
||||||
20
egs/LJSpeech/ASR/generate-lm.sh
Executable file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
lang_dir=data/lang_bpe_500
|
||||||
|
|
||||||
|
for ngram in 2 3 5; do
|
||||||
|
if [ ! -f $lang_dir/${ngram}gram.arpa ]; then
|
||||||
|
./shared/make_kn_lm.py \
|
||||||
|
-ngram-order ${ngram} \
|
||||||
|
-text $lang_dir/transcript_tokens.txt \
|
||||||
|
-lm $lang_dir/${ngram}gram.arpa
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f $lang_dir/${ngram}gram.fst.txt ]; then
|
||||||
|
python3 -m kaldilm \
|
||||||
|
--read-symbol-table="$lang_dir/tokens.txt" \
|
||||||
|
--disambig-symbol='#0' \
|
||||||
|
--max-order=${ngram} \
|
||||||
|
$lang_dir/${ngram}gram.arpa > $lang_dir/${ngram}gram.fst.txt
|
||||||
|
fi
|
||||||
|
done
|
||||||
0
egs/LJSpeech/ASR/local/__init__.py
Executable file
190
egs/LJSpeech/ASR/local/add_alignment_librispeech.py
Executable file
@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2022 Xiaomi Corp. (authors: Zengwei Yao)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file adds alignments from https://github.com/CorentinJ/librispeech-alignments # noqa
|
||||||
|
to the existing fbank features dir (e.g., data/fbank)
|
||||||
|
and save cuts to a new dir (e.g., data/fbank_ali).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import zipfile
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from lhotse import CutSet, load_manifest_lazy
|
||||||
|
from lhotse.recipes.librispeech import parse_alignments
|
||||||
|
from lhotse.utils import is_module_available
|
||||||
|
|
||||||
|
LIBRISPEECH_ALIGNMENTS_URL = (
|
||||||
|
"https://drive.google.com/uc?id=1WYfgr31T-PPwMcxuAq09XZfHQO5Mw8fE"
|
||||||
|
)
|
||||||
|
|
||||||
|
DATASET_PARTS = [
|
||||||
|
"dev-clean",
|
||||||
|
"dev-other",
|
||||||
|
"test-clean",
|
||||||
|
"test-other",
|
||||||
|
"train-clean-100",
|
||||||
|
"train-clean-360",
|
||||||
|
"train-other-500",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--alignments-dir",
|
||||||
|
type=str,
|
||||||
|
default="data/alignment",
|
||||||
|
help="The dir to save alignments.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--cuts-in-dir",
|
||||||
|
type=str,
|
||||||
|
default="data/fbank",
|
||||||
|
help="The dir of the existing cuts without alignments.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--cuts-out-dir",
|
||||||
|
type=str,
|
||||||
|
default="data/fbank_ali",
|
||||||
|
help="The dir to save the new cuts with alignments",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def download_alignments(
|
||||||
|
target_dir: str, alignments_url: str = LIBRISPEECH_ALIGNMENTS_URL
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Download and extract the alignments.
|
||||||
|
|
||||||
|
Note: If you can not access drive.google.com, you could download the file
|
||||||
|
`LibriSpeech-Alignments.zip` from huggingface:
|
||||||
|
https://huggingface.co/Zengwei/librispeech-alignments
|
||||||
|
and extract the zip file manually.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target_dir:
|
||||||
|
The dir to save alignments.
|
||||||
|
alignments_url:
|
||||||
|
The URL of alignments.
|
||||||
|
"""
|
||||||
|
"""Modified from https://github.com/lhotse-speech/lhotse/blob/master/lhotse/recipes/librispeech.py""" # noqa
|
||||||
|
target_dir = Path(target_dir)
|
||||||
|
target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
completed_detector = target_dir / ".ali_completed"
|
||||||
|
if completed_detector.is_file():
|
||||||
|
logging.info("The alignment files already exist.")
|
||||||
|
return
|
||||||
|
|
||||||
|
ali_zip_path = target_dir / "LibriSpeech-Alignments.zip"
|
||||||
|
if not ali_zip_path.is_file():
|
||||||
|
assert is_module_available(
|
||||||
|
"gdown"
|
||||||
|
), 'To download LibriSpeech alignments, please install "pip install gdown"' # noqa
|
||||||
|
import gdown
|
||||||
|
|
||||||
|
gdown.download(alignments_url, output=str(ali_zip_path))
|
||||||
|
|
||||||
|
with zipfile.ZipFile(str(ali_zip_path)) as f:
|
||||||
|
f.extractall(path=target_dir)
|
||||||
|
completed_detector.touch()
|
||||||
|
|
||||||
|
|
||||||
|
def add_alignment(
|
||||||
|
alignments_dir: str,
|
||||||
|
cuts_in_dir: str = "data/fbank",
|
||||||
|
cuts_out_dir: str = "data/fbank_ali",
|
||||||
|
dataset_parts: List[str] = DATASET_PARTS,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Add alignment info to existing cuts.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alignments_dir:
|
||||||
|
The dir of the alignments.
|
||||||
|
cuts_in_dir:
|
||||||
|
The dir of the existing cuts.
|
||||||
|
cuts_out_dir:
|
||||||
|
The dir to save the new cuts with alignments.
|
||||||
|
dataset_parts:
|
||||||
|
Librispeech parts to add alignments.
|
||||||
|
"""
|
||||||
|
alignments_dir = Path(alignments_dir)
|
||||||
|
cuts_in_dir = Path(cuts_in_dir)
|
||||||
|
cuts_out_dir = Path(cuts_out_dir)
|
||||||
|
cuts_out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for part in dataset_parts:
|
||||||
|
logging.info(f"Processing {part}")
|
||||||
|
|
||||||
|
cuts_in_path = cuts_in_dir / f"librispeech_cuts_{part}.jsonl.gz"
|
||||||
|
if not cuts_in_path.is_file():
|
||||||
|
logging.info(f"{cuts_in_path} does not exist - skipping.")
|
||||||
|
continue
|
||||||
|
cuts_out_path = cuts_out_dir / f"librispeech_cuts_{part}.jsonl.gz"
|
||||||
|
if cuts_out_path.is_file():
|
||||||
|
logging.info(f"{part} already exists - skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# parse alignments
|
||||||
|
alignments = {}
|
||||||
|
part_ali_dir = alignments_dir / "LibriSpeech" / part
|
||||||
|
for ali_path in part_ali_dir.rglob("*.alignment.txt"):
|
||||||
|
ali = parse_alignments(ali_path)
|
||||||
|
alignments.update(ali)
|
||||||
|
logging.info(f"{part} has {len(alignments.keys())} cuts with alignments.")
|
||||||
|
|
||||||
|
# add alignment attribute and write out
|
||||||
|
cuts_in = load_manifest_lazy(cuts_in_path)
|
||||||
|
with CutSet.open_writer(cuts_out_path) as writer:
|
||||||
|
for cut in cuts_in:
|
||||||
|
for idx, subcut in enumerate(cut.supervisions):
|
||||||
|
origin_id = subcut.id.split("_")[0]
|
||||||
|
if origin_id in alignments:
|
||||||
|
ali = alignments[origin_id]
|
||||||
|
else:
|
||||||
|
logging.info(f"Warning: {origin_id} does not have alignment.")
|
||||||
|
ali = []
|
||||||
|
subcut.alignment = {"word": ali}
|
||||||
|
writer.write(cut, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
parser = get_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
|
||||||
|
download_alignments(args.alignments_dir)
|
||||||
|
add_alignment(args.alignments_dir, args.cuts_in_dir, args.cuts_out_dir)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
166
egs/LJSpeech/ASR/local/compile_hlg.py
Executable file
@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script takes as input lang_dir and generates HLG from
|
||||||
|
|
||||||
|
- H, the ctc topology, built from tokens contained in lang_dir/lexicon.txt
|
||||||
|
- L, the lexicon, built from lang_dir/L_disambig.pt
|
||||||
|
|
||||||
|
Caution: We use a lexicon that contains disambiguation symbols
|
||||||
|
|
||||||
|
- G, the LM, built from data/lm/G_3_gram.fst.txt
|
||||||
|
|
||||||
|
The generated HLG is saved in $lang_dir/HLG.pt
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from icefall.lexicon import Lexicon
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lm",
|
||||||
|
type=str,
|
||||||
|
default="G_3_gram",
|
||||||
|
help="""Stem name for LM used in HLG compiling.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compile_HLG(lang_dir: str, lm: str = "G_3_gram") -> k2.Fsa:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
lang_dir:
|
||||||
|
The language directory, e.g., data/lang_phone or data/lang_bpe_5000.
|
||||||
|
lm:
|
||||||
|
The language stem base name.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
An FSA representing HLG.
|
||||||
|
"""
|
||||||
|
lexicon = Lexicon(lang_dir)
|
||||||
|
max_token_id = max(lexicon.tokens)
|
||||||
|
logging.info(f"Building ctc_topo. max_token_id: {max_token_id}")
|
||||||
|
H = k2.ctc_topo(max_token_id)
|
||||||
|
L = k2.Fsa.from_dict(torch.load(f"{lang_dir}/L_disambig.pt"))
|
||||||
|
|
||||||
|
if Path(f"data/lm/{lm}.pt").is_file():
|
||||||
|
logging.info(f"Loading pre-compiled {lm}")
|
||||||
|
d = torch.load(f"data/lm/{lm}.pt")
|
||||||
|
G = k2.Fsa.from_dict(d)
|
||||||
|
else:
|
||||||
|
logging.info(f"Loading {lm}.fst.txt")
|
||||||
|
with open(f"data/lm/{lm}.fst.txt") as f:
|
||||||
|
G = k2.Fsa.from_openfst(f.read(), acceptor=False)
|
||||||
|
torch.save(G.as_dict(), f"data/lm/{lm}.pt")
|
||||||
|
|
||||||
|
first_token_disambig_id = lexicon.token_table["#0"]
|
||||||
|
first_word_disambig_id = lexicon.word_table["#0"]
|
||||||
|
|
||||||
|
L = k2.arc_sort(L)
|
||||||
|
G = k2.arc_sort(G)
|
||||||
|
|
||||||
|
logging.info("Intersecting L and G")
|
||||||
|
LG = k2.compose(L, G)
|
||||||
|
logging.info(f"LG shape: {LG.shape}")
|
||||||
|
|
||||||
|
logging.info("Connecting LG")
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
logging.info(f"LG shape after k2.connect: {LG.shape}")
|
||||||
|
|
||||||
|
logging.info(type(LG.aux_labels))
|
||||||
|
logging.info("Determinizing LG")
|
||||||
|
|
||||||
|
LG = k2.determinize(LG)
|
||||||
|
logging.info(type(LG.aux_labels))
|
||||||
|
|
||||||
|
logging.info("Connecting LG after k2.determinize")
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
|
||||||
|
logging.info("Removing disambiguation symbols on LG")
|
||||||
|
|
||||||
|
LG.labels[LG.labels >= first_token_disambig_id] = 0
|
||||||
|
# See https://github.com/k2-fsa/k2/issues/874
|
||||||
|
# for why we need to set LG.properties to None
|
||||||
|
LG.__dict__["_properties"] = None
|
||||||
|
|
||||||
|
assert isinstance(LG.aux_labels, k2.RaggedTensor)
|
||||||
|
LG.aux_labels.values[LG.aux_labels.values >= first_word_disambig_id] = 0
|
||||||
|
|
||||||
|
LG = k2.remove_epsilon(LG)
|
||||||
|
logging.info(f"LG shape after k2.remove_epsilon: {LG.shape}")
|
||||||
|
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
LG.aux_labels = LG.aux_labels.remove_values_eq(0)
|
||||||
|
|
||||||
|
logging.info("Arc sorting LG")
|
||||||
|
LG = k2.arc_sort(LG)
|
||||||
|
|
||||||
|
logging.info("Composing H and LG")
|
||||||
|
# CAUTION: The name of the inner_labels is fixed
|
||||||
|
# to `tokens`. If you want to change it, please
|
||||||
|
# also change other places in icefall that are using
|
||||||
|
# it.
|
||||||
|
HLG = k2.compose(H, LG, inner_labels="tokens")
|
||||||
|
|
||||||
|
logging.info("Connecting LG")
|
||||||
|
HLG = k2.connect(HLG)
|
||||||
|
|
||||||
|
logging.info("Arc sorting LG")
|
||||||
|
HLG = k2.arc_sort(HLG)
|
||||||
|
logging.info(f"HLG.shape: {HLG.shape}")
|
||||||
|
|
||||||
|
return HLG
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
|
||||||
|
if (lang_dir / "HLG.pt").is_file():
|
||||||
|
logging.info(f"{lang_dir}/HLG.pt already exists - skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info(f"Processing {lang_dir}")
|
||||||
|
|
||||||
|
HLG = compile_HLG(lang_dir, args.lm)
|
||||||
|
logging.info(f"Saving HLG.pt to {lang_dir}")
|
||||||
|
torch.save(HLG.as_dict(), f"{lang_dir}/HLG.pt")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
139
egs/LJSpeech/ASR/local/compile_lg.py
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang, Wei Kang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script takes as input lang_dir and generates LG from
|
||||||
|
|
||||||
|
- L, the lexicon, built from lang_dir/L_disambig.pt
|
||||||
|
|
||||||
|
Caution: We use a lexicon that contains disambiguation symbols
|
||||||
|
|
||||||
|
- G, the LM, built from data/lm/G_3_gram.fst.txt
|
||||||
|
|
||||||
|
The generated LG is saved in $lang_dir/LG.pt
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from icefall.lexicon import Lexicon
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compile_LG(lang_dir: str) -> k2.Fsa:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
lang_dir:
|
||||||
|
The language directory, e.g., data/lang_phone or data/lang_bpe_5000.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
An FSA representing LG.
|
||||||
|
"""
|
||||||
|
lexicon = Lexicon(lang_dir)
|
||||||
|
L = k2.Fsa.from_dict(torch.load(f"{lang_dir}/L_disambig.pt"))
|
||||||
|
|
||||||
|
if Path("data/lm/G_3_gram.pt").is_file():
|
||||||
|
logging.info("Loading pre-compiled G_3_gram")
|
||||||
|
d = torch.load("data/lm/G_3_gram.pt")
|
||||||
|
G = k2.Fsa.from_dict(d)
|
||||||
|
else:
|
||||||
|
logging.info("Loading G_3_gram.fst.txt")
|
||||||
|
with open("data/lm/G_3_gram.fst.txt") as f:
|
||||||
|
G = k2.Fsa.from_openfst(f.read(), acceptor=False)
|
||||||
|
torch.save(G.as_dict(), "data/lm/G_3_gram.pt")
|
||||||
|
|
||||||
|
first_token_disambig_id = lexicon.token_table["#0"]
|
||||||
|
first_word_disambig_id = lexicon.word_table["#0"]
|
||||||
|
|
||||||
|
L = k2.arc_sort(L)
|
||||||
|
G = k2.arc_sort(G)
|
||||||
|
|
||||||
|
logging.info("Intersecting L and G")
|
||||||
|
LG = k2.compose(L, G)
|
||||||
|
logging.info(f"LG shape: {LG.shape}")
|
||||||
|
|
||||||
|
logging.info("Connecting LG")
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
logging.info(f"LG shape after k2.connect: {LG.shape}")
|
||||||
|
|
||||||
|
logging.info(type(LG.aux_labels))
|
||||||
|
logging.info("Determinizing LG")
|
||||||
|
|
||||||
|
LG = k2.determinize(LG, k2.DeterminizeWeightPushingType.kLogWeightPushing)
|
||||||
|
logging.info(type(LG.aux_labels))
|
||||||
|
|
||||||
|
logging.info("Connecting LG after k2.determinize")
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
|
||||||
|
logging.info("Removing disambiguation symbols on LG")
|
||||||
|
|
||||||
|
LG.labels[LG.labels >= first_token_disambig_id] = 0
|
||||||
|
# See https://github.com/k2-fsa/k2/issues/874
|
||||||
|
# for why we need to set LG.properties to None
|
||||||
|
LG.__dict__["_properties"] = None
|
||||||
|
|
||||||
|
assert isinstance(LG.aux_labels, k2.RaggedTensor)
|
||||||
|
LG.aux_labels.values[LG.aux_labels.values >= first_word_disambig_id] = 0
|
||||||
|
|
||||||
|
LG = k2.remove_epsilon(LG)
|
||||||
|
logging.info(f"LG shape after k2.remove_epsilon: {LG.shape}")
|
||||||
|
|
||||||
|
LG = k2.connect(LG)
|
||||||
|
LG.aux_labels = LG.aux_labels.remove_values_eq(0)
|
||||||
|
|
||||||
|
logging.info("Arc sorting LG")
|
||||||
|
LG = k2.arc_sort(LG)
|
||||||
|
|
||||||
|
return LG
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
|
||||||
|
if (lang_dir / "LG.pt").is_file():
|
||||||
|
logging.info(f"{lang_dir}/LG.pt already exists - skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info(f"Processing {lang_dir}")
|
||||||
|
|
||||||
|
LG = compile_LG(lang_dir)
|
||||||
|
logging.info(f"Saving LG.pt to {lang_dir}")
|
||||||
|
torch.save(LG.as_dict(), f"{lang_dir}/LG.pt")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
140
egs/LJSpeech/ASR/local/compute_fbank_LJSpeech.py
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file computes fbank features of the LJSpeech dataset.
|
||||||
|
It looks for manifests in the directory data/manifests.
|
||||||
|
|
||||||
|
The generated fbank features are saved in data/fbank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
from filter_cuts import filter_cuts
|
||||||
|
from lhotse import CutSet, Fbank, FbankConfig, LilcomChunkyWriter
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
from icefall.utils import get_executor
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=str,
|
||||||
|
help="""Path to the bpe.model. If not None, we will remove short and
|
||||||
|
long utterances before extracting features""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--data-dir",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="""Path to data""",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_LJSpeech(bpe_model: Optional[str] = None):
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
num_jobs = min(15, os.cpu_count())
|
||||||
|
num_mel_bins = 80
|
||||||
|
|
||||||
|
if bpe_model:
|
||||||
|
logging.info(f"Loading {bpe_model}")
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(bpe_model)
|
||||||
|
|
||||||
|
data_dir = args.data_dir
|
||||||
|
if data_dir is None:
|
||||||
|
raise NotImplementedError("need data directory")
|
||||||
|
|
||||||
|
directory = data_dir + '/wavs'
|
||||||
|
|
||||||
|
parts = ['train', 'dev', 'test']
|
||||||
|
prefix = "LJSpeech"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
extractor = Fbank(FbankConfig(num_mel_bins=num_mel_bins))
|
||||||
|
|
||||||
|
with get_executor() as ex: # Initialize the executor only once.
|
||||||
|
for partition, m in manifests.items():
|
||||||
|
cuts_filename = f"{prefix}_cuts_{partition}.{suffix}"
|
||||||
|
if (output_dir / cuts_filename).is_file():
|
||||||
|
logging.info(f"{partition} already exists - skipping.")
|
||||||
|
continue
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
cut_set = CutSet.from_manifests(
|
||||||
|
recordings=m["recordings"],
|
||||||
|
supervisions=m["supervisions"],
|
||||||
|
)
|
||||||
|
if bpe_model:
|
||||||
|
cut_set = filter_cuts(cut_set, sp)
|
||||||
|
|
||||||
|
if "train" in partition:
|
||||||
|
cut_set = (
|
||||||
|
cut_set + cut_set.perturb_speed(0.9) + cut_set.perturb_speed(1.1)
|
||||||
|
)
|
||||||
|
cut_set = cut_set.compute_and_store_features(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/{prefix}_feats_{partition}",
|
||||||
|
# when an executor is specified, make more partitions
|
||||||
|
num_jobs=num_jobs if ex is None else 80,
|
||||||
|
executor=ex,
|
||||||
|
storage_type=LilcomChunkyWriter,
|
||||||
|
)
|
||||||
|
cut_set.to_file(output_dir / cuts_filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
args = get_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
compute_fbank_LJSpeech(bpe_model=args.bpe_model)
|
||||||
140
egs/LJSpeech/ASR/local/compute_fbank_LJSpeech_pseudo.py
Executable file
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file computes fbank features of the LJSpeech dataset.
|
||||||
|
It looks for manifests in the directory data/manifests.
|
||||||
|
|
||||||
|
The generated fbank features are saved in data/fbank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
from filter_cuts import filter_cuts
|
||||||
|
from lhotse import CutSet, Fbank, FbankConfig, LilcomChunkyWriter
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
from icefall.utils import get_executor
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=str,
|
||||||
|
help="""Path to the bpe.model. If not None, we will remove short and
|
||||||
|
long utterances before extracting features""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--data-dir",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="""Path to data""",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_LJSpeech(bpe_model: Optional[str] = None):
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
num_jobs = min(15, os.cpu_count())
|
||||||
|
num_mel_bins = 80
|
||||||
|
|
||||||
|
if bpe_model:
|
||||||
|
logging.info(f"Loading {bpe_model}")
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(bpe_model)
|
||||||
|
|
||||||
|
data_dir = args.data_dir
|
||||||
|
if data_dir is None:
|
||||||
|
raise NotImplementedError("need data directory")
|
||||||
|
|
||||||
|
directory = data_dir + '/wavs'
|
||||||
|
|
||||||
|
parts = ['train', 'dev', 'test']
|
||||||
|
prefix = "LJSpeech_pseudo"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
extractor = Fbank(FbankConfig(num_mel_bins=num_mel_bins))
|
||||||
|
|
||||||
|
with get_executor() as ex: # Initialize the executor only once.
|
||||||
|
for partition, m in manifests.items():
|
||||||
|
cuts_filename = f"{prefix}_cuts_{partition}.{suffix}"
|
||||||
|
if (output_dir / cuts_filename).is_file():
|
||||||
|
logging.info(f"{partition} already exists - skipping.")
|
||||||
|
continue
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
cut_set = CutSet.from_manifests(
|
||||||
|
recordings=m["recordings"],
|
||||||
|
supervisions=m["supervisions"],
|
||||||
|
)
|
||||||
|
if bpe_model:
|
||||||
|
cut_set = filter_cuts(cut_set, sp)
|
||||||
|
|
||||||
|
if "train" in partition:
|
||||||
|
cut_set = (
|
||||||
|
cut_set + cut_set.perturb_speed(0.9) + cut_set.perturb_speed(1.1)
|
||||||
|
)
|
||||||
|
cut_set = cut_set.compute_and_store_features(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/{prefix}_feats_{partition}",
|
||||||
|
# when an executor is specified, make more partitions
|
||||||
|
num_jobs=num_jobs if ex is None else 80,
|
||||||
|
executor=ex,
|
||||||
|
storage_type=LilcomChunkyWriter,
|
||||||
|
)
|
||||||
|
cut_set.to_file(output_dir / cuts_filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
args = get_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
compute_fbank_LJSpeech(bpe_model=args.bpe_model)
|
||||||
90
egs/LJSpeech/ASR/local/compute_fbank_gigaspeech_dev_test.py
Executable file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Johns Hopkins University (Piotr Żelasko)
|
||||||
|
# Copyright 2021 Xiaomi Corp. (Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from lhotse import CutSet, KaldifeatFbank, KaldifeatFbankConfig
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_gigaspeech_dev_test():
|
||||||
|
in_out_dir = Path("data/fbank")
|
||||||
|
# number of workers in dataloader
|
||||||
|
num_workers = 20
|
||||||
|
|
||||||
|
# number of seconds in a batch
|
||||||
|
batch_duration = 600
|
||||||
|
|
||||||
|
subsets = ("DEV", "TEST")
|
||||||
|
|
||||||
|
device = torch.device("cpu")
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
device = torch.device("cuda", 0)
|
||||||
|
extractor = KaldifeatFbank(KaldifeatFbankConfig(device=device))
|
||||||
|
|
||||||
|
logging.info(f"device: {device}")
|
||||||
|
|
||||||
|
prefix = "gigaspeech"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
|
||||||
|
for partition in subsets:
|
||||||
|
cuts_path = in_out_dir / f"{prefix}_cuts_{partition}.{suffix}"
|
||||||
|
if cuts_path.is_file():
|
||||||
|
logging.info(f"{cuts_path} exists - skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_cuts_path = in_out_dir / f"{prefix}_cuts_{partition}_raw.{suffix}"
|
||||||
|
|
||||||
|
logging.info(f"Loading {raw_cuts_path}")
|
||||||
|
cut_set = CutSet.from_file(raw_cuts_path)
|
||||||
|
|
||||||
|
logging.info("Computing features")
|
||||||
|
|
||||||
|
cut_set = cut_set.compute_and_store_features_batch(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{in_out_dir}/{prefix}_feats_{partition}",
|
||||||
|
num_workers=num_workers,
|
||||||
|
batch_duration=batch_duration,
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
cut_set = cut_set.trim_to_supervisions(
|
||||||
|
keep_overlapping=False, min_duration=None
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Saving to {cuts_path}")
|
||||||
|
cut_set.to_file(cuts_path)
|
||||||
|
logging.info(f"Saved to {cuts_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
compute_fbank_gigaspeech_dev_test()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
170
egs/LJSpeech/ASR/local/compute_fbank_gigaspeech_splits.py
Executable file
@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Johns Hopkins University (Piotr Żelasko)
|
||||||
|
# Copyright 2021 Xiaomi Corp. (Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from lhotse import CutSet, KaldifeatFbank, KaldifeatFbankConfig
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--num-workers",
|
||||||
|
type=int,
|
||||||
|
default=20,
|
||||||
|
help="Number of dataloading workers used for reading the audio.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--batch-duration",
|
||||||
|
type=float,
|
||||||
|
default=600.0,
|
||||||
|
help="The maximum number of audio seconds in a batch."
|
||||||
|
"Determines batch size dynamically.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--num-splits",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="The number of splits of the XL subset",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--start",
|
||||||
|
type=int,
|
||||||
|
default=0,
|
||||||
|
help="Process pieces starting from this number (inclusive).",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--stop",
|
||||||
|
type=int,
|
||||||
|
default=-1,
|
||||||
|
help="Stop processing pieces until this number (exclusive).",
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_gigaspeech_splits(args):
|
||||||
|
num_splits = args.num_splits
|
||||||
|
output_dir = f"data/fbank/gigaspeech_XL_split_{num_splits}"
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
assert output_dir.exists(), f"{output_dir} does not exist!"
|
||||||
|
|
||||||
|
num_digits = len(str(num_splits))
|
||||||
|
|
||||||
|
start = args.start
|
||||||
|
stop = args.stop
|
||||||
|
if stop < start:
|
||||||
|
stop = num_splits
|
||||||
|
|
||||||
|
stop = min(stop, num_splits)
|
||||||
|
|
||||||
|
device = torch.device("cpu")
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
device = torch.device("cuda", 0)
|
||||||
|
extractor = KaldifeatFbank(KaldifeatFbankConfig(device=device))
|
||||||
|
logging.info(f"device: {device}")
|
||||||
|
|
||||||
|
prefix = "gigaspeech"
|
||||||
|
|
||||||
|
num_digits = 8 # num_digits is fixed by lhotse split-lazy
|
||||||
|
for i in range(start, stop):
|
||||||
|
idx = f"{i + 1}".zfill(num_digits)
|
||||||
|
logging.info(f"Processing {idx}/{num_splits}")
|
||||||
|
|
||||||
|
cuts_path = output_dir / f"{prefix}_cuts_XL.{idx}.jsonl.gz"
|
||||||
|
if cuts_path.is_file():
|
||||||
|
logging.info(f"{cuts_path} exists - skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_cuts_path = output_dir / f"{prefix}_cuts_XL_raw.{idx}.jsonl.gz"
|
||||||
|
if not raw_cuts_path.is_file():
|
||||||
|
logging.info(f"{raw_cuts_path} does not exist - skipping it")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info(f"Loading {raw_cuts_path}")
|
||||||
|
cut_set = CutSet.from_file(raw_cuts_path)
|
||||||
|
|
||||||
|
logging.info("Computing features")
|
||||||
|
if (output_dir / f"{prefix}_feats_XL_{idx}.lca").exists():
|
||||||
|
logging.info(f"Removing {output_dir}/{prefix}_feats_XL_{idx}.lca")
|
||||||
|
os.remove(output_dir / f"{prefix}_feats_XL_{idx}.lca")
|
||||||
|
|
||||||
|
cut_set = cut_set.compute_and_store_features_batch(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/{prefix}_feats_XL_{idx}",
|
||||||
|
num_workers=args.num_workers,
|
||||||
|
batch_duration=args.batch_duration,
|
||||||
|
overwrite=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info("About to split cuts into smaller chunks.")
|
||||||
|
cut_set = cut_set.trim_to_supervisions(
|
||||||
|
keep_overlapping=False, min_duration=None
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Saving to {cuts_path}")
|
||||||
|
cut_set.to_file(cuts_path)
|
||||||
|
logging.info(f"Saved to {cuts_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
now = datetime.now()
|
||||||
|
date_time = now.strftime("%Y-%m-%d-%H-%M-%S")
|
||||||
|
|
||||||
|
log_filename = "log-compute_fbank_gigaspeech_splits"
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
log_filename = f"{log_filename}-{date_time}"
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=log_filename,
|
||||||
|
format=formatter,
|
||||||
|
level=logging.INFO,
|
||||||
|
filemode="w",
|
||||||
|
)
|
||||||
|
|
||||||
|
console = logging.StreamHandler()
|
||||||
|
console.setLevel(logging.INFO)
|
||||||
|
console.setFormatter(logging.Formatter(formatter))
|
||||||
|
logging.getLogger("").addHandler(console)
|
||||||
|
|
||||||
|
parser = get_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
|
||||||
|
compute_fbank_gigaspeech_splits(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
134
egs/LJSpeech/ASR/local/compute_fbank_librispeech.py
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file computes fbank features of the LibriSpeech dataset.
|
||||||
|
It looks for manifests in the directory data/manifests.
|
||||||
|
|
||||||
|
The generated fbank features are saved in data/fbank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
from filter_cuts import filter_cuts
|
||||||
|
from lhotse import CutSet, Fbank, FbankConfig, LilcomChunkyWriter
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
from icefall.utils import get_executor
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=str,
|
||||||
|
help="""Path to the bpe.model. If not None, we will remove short and
|
||||||
|
long utterances before extracting features""",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_librispeech(bpe_model: Optional[str] = None):
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
num_jobs = min(15, os.cpu_count())
|
||||||
|
num_mel_bins = 80
|
||||||
|
|
||||||
|
if bpe_model:
|
||||||
|
logging.info(f"Loading {bpe_model}")
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(bpe_model)
|
||||||
|
|
||||||
|
dataset_parts = (
|
||||||
|
"dev-clean",
|
||||||
|
"dev-other",
|
||||||
|
"test-clean",
|
||||||
|
"test-other",
|
||||||
|
"train-clean-100",
|
||||||
|
"train-clean-360",
|
||||||
|
"train-other-500",
|
||||||
|
)
|
||||||
|
prefix = "librispeech"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=dataset_parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(dataset_parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(dataset_parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
dataset_parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
extractor = Fbank(FbankConfig(num_mel_bins=num_mel_bins))
|
||||||
|
|
||||||
|
with get_executor() as ex: # Initialize the executor only once.
|
||||||
|
for partition, m in manifests.items():
|
||||||
|
cuts_filename = f"{prefix}_cuts_{partition}.{suffix}"
|
||||||
|
if (output_dir / cuts_filename).is_file():
|
||||||
|
logging.info(f"{partition} already exists - skipping.")
|
||||||
|
continue
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
cut_set = CutSet.from_manifests(
|
||||||
|
recordings=m["recordings"],
|
||||||
|
supervisions=m["supervisions"],
|
||||||
|
)
|
||||||
|
if bpe_model:
|
||||||
|
cut_set = filter_cuts(cut_set, sp)
|
||||||
|
|
||||||
|
if "train" in partition:
|
||||||
|
cut_set = (
|
||||||
|
cut_set + cut_set.perturb_speed(0.9) + cut_set.perturb_speed(1.1)
|
||||||
|
)
|
||||||
|
cut_set = cut_set.compute_and_store_features(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/{prefix}_feats_{partition}",
|
||||||
|
# when an executor is specified, make more partitions
|
||||||
|
num_jobs=num_jobs if ex is None else 80,
|
||||||
|
executor=ex,
|
||||||
|
storage_type=LilcomChunkyWriter,
|
||||||
|
)
|
||||||
|
cut_set.to_file(output_dir / cuts_filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
args = get_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
compute_fbank_librispeech(bpe_model=args.bpe_model)
|
||||||
105
egs/LJSpeech/ASR/local/compute_fbank_musan.py
Executable file
@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file computes fbank features of the musan dataset.
|
||||||
|
It looks for manifests in the directory data/manifests.
|
||||||
|
|
||||||
|
The generated fbank features are saved in data/fbank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from lhotse import CutSet, Fbank, FbankConfig, LilcomChunkyWriter, combine
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
from icefall.utils import get_executor
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_musan():
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
num_jobs = min(15, os.cpu_count())
|
||||||
|
num_mel_bins = 80
|
||||||
|
|
||||||
|
dataset_parts = (
|
||||||
|
"music",
|
||||||
|
"speech",
|
||||||
|
"noise",
|
||||||
|
)
|
||||||
|
prefix = "musan"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=dataset_parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(dataset_parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(dataset_parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
dataset_parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
musan_cuts_path = output_dir / "musan_cuts.jsonl.gz"
|
||||||
|
|
||||||
|
if musan_cuts_path.is_file():
|
||||||
|
logging.info(f"{musan_cuts_path} already exists - skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info("Extracting features for Musan")
|
||||||
|
|
||||||
|
extractor = Fbank(FbankConfig(num_mel_bins=num_mel_bins))
|
||||||
|
|
||||||
|
with get_executor() as ex: # Initialize the executor only once.
|
||||||
|
# create chunks of Musan with duration 5 - 10 seconds
|
||||||
|
musan_cuts = (
|
||||||
|
CutSet.from_manifests(
|
||||||
|
recordings=combine(part["recordings"] for part in manifests.values())
|
||||||
|
)
|
||||||
|
.cut_into_windows(10.0)
|
||||||
|
.filter(lambda c: c.duration > 5)
|
||||||
|
.compute_and_store_features(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/musan_feats",
|
||||||
|
num_jobs=num_jobs if ex is None else 80,
|
||||||
|
executor=ex,
|
||||||
|
storage_type=LilcomChunkyWriter,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
musan_cuts.to_file(musan_cuts_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
compute_fbank_musan()
|
||||||
131
egs/LJSpeech/ASR/local/compute_fbank_userlibri.py
Executable file
@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file computes fbank features of the UserLibri dataset.
|
||||||
|
It looks for manifests in the directory data/manifests.
|
||||||
|
|
||||||
|
The generated fbank features are saved in data/fbank.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
from filter_cuts import filter_cuts
|
||||||
|
from lhotse import CutSet, Fbank, FbankConfig, LilcomChunkyWriter
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
from icefall.utils import get_executor
|
||||||
|
|
||||||
|
# Torch's multithreaded behavior needs to be disabled or
|
||||||
|
# it wastes a lot of CPU and slow things down.
|
||||||
|
# Do this outside of main() in case it needs to take effect
|
||||||
|
# even when we are not invoking the main (e.g. when spawning subprocesses).
|
||||||
|
torch.set_num_threads(1)
|
||||||
|
torch.set_num_interop_threads(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=str,
|
||||||
|
help="""Path to the bpe.model. If not None, we will remove short and
|
||||||
|
long utterances before extracting features""",
|
||||||
|
)
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def compute_fbank_userlibri(bpe_model: Optional[str] = None):
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
num_jobs = min(15, os.cpu_count())
|
||||||
|
num_mel_bins = 80
|
||||||
|
|
||||||
|
if bpe_model:
|
||||||
|
logging.info(f"Loading {bpe_model}")
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(bpe_model)
|
||||||
|
|
||||||
|
directory = "/DB/UserLibri/audio_data/speaker-wise-test"
|
||||||
|
spks_parts = os.listdir(directory)
|
||||||
|
directory = "/DB/UserLibri/audio_data/book-wise-test"
|
||||||
|
books_parts = os.listdir(directory)
|
||||||
|
|
||||||
|
dataset_parts = spks_parts + books_parts
|
||||||
|
prefix = "userlibri"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=dataset_parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(dataset_parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(dataset_parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
dataset_parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
extractor = Fbank(FbankConfig(num_mel_bins=num_mel_bins))
|
||||||
|
|
||||||
|
with get_executor() as ex: # Initialize the executor only once.
|
||||||
|
for partition, m in manifests.items():
|
||||||
|
cuts_filename = f"{prefix}_cuts_{partition}.{suffix}"
|
||||||
|
if (output_dir / cuts_filename).is_file():
|
||||||
|
logging.info(f"{partition} already exists - skipping.")
|
||||||
|
continue
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
cut_set = CutSet.from_manifests(
|
||||||
|
recordings=m["recordings"],
|
||||||
|
supervisions=m["supervisions"],
|
||||||
|
)
|
||||||
|
if bpe_model:
|
||||||
|
cut_set = filter_cuts(cut_set, sp)
|
||||||
|
|
||||||
|
if "train" in partition:
|
||||||
|
cut_set = (
|
||||||
|
cut_set + cut_set.perturb_speed(0.9) + cut_set.perturb_speed(1.1)
|
||||||
|
)
|
||||||
|
cut_set = cut_set.compute_and_store_features(
|
||||||
|
extractor=extractor,
|
||||||
|
storage_path=f"{output_dir}/{prefix}_feats_{partition}",
|
||||||
|
# when an executor is specified, make more partitions
|
||||||
|
num_jobs=num_jobs if ex is None else 80,
|
||||||
|
executor=ex,
|
||||||
|
storage_type=LilcomChunkyWriter,
|
||||||
|
)
|
||||||
|
cut_set.to_file(output_dir / cuts_filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
args = get_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
compute_fbank_userlibri(bpe_model=args.bpe_model)
|
||||||
103
egs/LJSpeech/ASR/local/convert_transcript_words_to_tokens.py
Executable file
@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright 2021 Xiaomi Corporation (Author: Fangjun Kuang)
|
||||||
|
"""
|
||||||
|
Convert a transcript file containing words to a corpus file containing tokens
|
||||||
|
for LM training with the help of a lexicon.
|
||||||
|
|
||||||
|
If the lexicon contains phones, the resulting LM will be a phone LM; If the
|
||||||
|
lexicon contains word pieces, the resulting LM will be a word piece LM.
|
||||||
|
|
||||||
|
If a word has multiple pronunciations, the one that appears first in the lexicon
|
||||||
|
is kept; others are removed.
|
||||||
|
|
||||||
|
If the input transcript is:
|
||||||
|
|
||||||
|
hello zoo world hello
|
||||||
|
world zoo
|
||||||
|
foo zoo world hellO
|
||||||
|
|
||||||
|
and if the lexicon is
|
||||||
|
|
||||||
|
<UNK> SPN
|
||||||
|
hello h e l l o 2
|
||||||
|
hello h e l l o
|
||||||
|
world w o r l d
|
||||||
|
zoo z o o
|
||||||
|
|
||||||
|
Then the output is
|
||||||
|
|
||||||
|
h e l l o 2 z o o w o r l d h e l l o 2
|
||||||
|
w o r l d z o o
|
||||||
|
SPN z o o w o r l d SPN
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
from generate_unique_lexicon import filter_multiple_pronunications
|
||||||
|
|
||||||
|
from icefall.lexicon import read_lexicon
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--transcript",
|
||||||
|
type=str,
|
||||||
|
help="The input transcript file."
|
||||||
|
"We assume that the transcript file consists of "
|
||||||
|
"lines. Each line consists of space separated words.",
|
||||||
|
)
|
||||||
|
parser.add_argument("--lexicon", type=str, help="The input lexicon file.")
|
||||||
|
parser.add_argument("--oov", type=str, default="<UNK>", help="The OOV word.")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def process_line(lexicon: Dict[str, List[str]], line: str, oov_token: str) -> None:
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
A dict containing pronunciations. Its keys are words and values
|
||||||
|
are pronunciations (i.e., tokens).
|
||||||
|
line:
|
||||||
|
A line of transcript consisting of space(s) separated words.
|
||||||
|
oov_token:
|
||||||
|
The pronunciation of the oov word if a word in `line` is not present
|
||||||
|
in the lexicon.
|
||||||
|
Returns:
|
||||||
|
Return None.
|
||||||
|
"""
|
||||||
|
s = ""
|
||||||
|
words = line.strip().split()
|
||||||
|
for i, w in enumerate(words):
|
||||||
|
tokens = lexicon.get(w, oov_token)
|
||||||
|
s += " ".join(tokens)
|
||||||
|
s += " "
|
||||||
|
print(s.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
assert Path(args.lexicon).is_file()
|
||||||
|
assert Path(args.transcript).is_file()
|
||||||
|
assert len(args.oov) > 0
|
||||||
|
|
||||||
|
# Only the first pronunciation of a word is kept
|
||||||
|
lexicon = filter_multiple_pronunications(read_lexicon(args.lexicon))
|
||||||
|
|
||||||
|
lexicon = dict(lexicon)
|
||||||
|
|
||||||
|
assert args.oov in lexicon
|
||||||
|
|
||||||
|
oov_token = lexicon[args.oov]
|
||||||
|
|
||||||
|
with open(args.transcript) as f:
|
||||||
|
for line in f:
|
||||||
|
process_line(lexicon=lexicon, line=line, oov_token=oov_token)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
215
egs/LJSpeech/ASR/local/display_manifest_statistics.py
Executable file
@ -0,0 +1,215 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file displays duration statistics of utterances in a manifest.
|
||||||
|
You can use the displayed value to choose minimum/maximum duration
|
||||||
|
to remove short and long utterances during the training.
|
||||||
|
|
||||||
|
See the function `remove_short_and_long_utt()` in transducer/train.py
|
||||||
|
for usage.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from lhotse import load_manifest_lazy
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# path = "./data/fbank/librispeech_cuts_train-clean-100.jsonl.gz"
|
||||||
|
# path = "./data/fbank/librispeech_cuts_train-clean-360.jsonl.gz"
|
||||||
|
# path = "./data/fbank/librispeech_cuts_train-other-500.jsonl.gz"
|
||||||
|
# path = "./data/fbank/librispeech_cuts_dev-clean.jsonl.gz"
|
||||||
|
# path = "./data/fbank/librispeech_cuts_dev-other.jsonl.gz"
|
||||||
|
# path = "./data/fbank/librispeech_cuts_test-clean.jsonl.gz"
|
||||||
|
path = "./data/fbank/librispeech_cuts_test-other.jsonl.gz"
|
||||||
|
|
||||||
|
cuts = load_manifest_lazy(path)
|
||||||
|
cuts.describe()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
"""
|
||||||
|
## train-clean-100
|
||||||
|
Cuts count: 85617
|
||||||
|
Total duration (hours): 303.8
|
||||||
|
Speech duration (hours): 303.8 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 12.8
|
||||||
|
std 3.8
|
||||||
|
min 1.3
|
||||||
|
0.1% 1.9
|
||||||
|
0.5% 2.2
|
||||||
|
1% 2.5
|
||||||
|
5% 4.2
|
||||||
|
10% 6.4
|
||||||
|
25% 11.4
|
||||||
|
50% 13.8
|
||||||
|
75% 15.3
|
||||||
|
90% 16.7
|
||||||
|
95% 17.3
|
||||||
|
99% 18.1
|
||||||
|
99.5% 18.4
|
||||||
|
99.9% 18.8
|
||||||
|
max 27.2
|
||||||
|
|
||||||
|
## train-clean-360
|
||||||
|
Cuts count: 312042
|
||||||
|
Total duration (hours): 1098.2
|
||||||
|
Speech duration (hours): 1098.2 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 12.7
|
||||||
|
std 3.8
|
||||||
|
min 1.0
|
||||||
|
0.1% 1.8
|
||||||
|
0.5% 2.2
|
||||||
|
1% 2.5
|
||||||
|
5% 4.2
|
||||||
|
10% 6.2
|
||||||
|
25% 11.2
|
||||||
|
50% 13.7
|
||||||
|
75% 15.3
|
||||||
|
90% 16.6
|
||||||
|
95% 17.3
|
||||||
|
99% 18.1
|
||||||
|
99.5% 18.4
|
||||||
|
99.9% 18.8
|
||||||
|
max 33.0
|
||||||
|
|
||||||
|
## train-other 500
|
||||||
|
Cuts count: 446064
|
||||||
|
Total duration (hours): 1500.6
|
||||||
|
Speech duration (hours): 1500.6 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 12.1
|
||||||
|
std 4.2
|
||||||
|
min 0.8
|
||||||
|
0.1% 1.7
|
||||||
|
0.5% 2.1
|
||||||
|
1% 2.3
|
||||||
|
5% 3.5
|
||||||
|
10% 5.0
|
||||||
|
25% 9.8
|
||||||
|
50% 13.4
|
||||||
|
75% 15.1
|
||||||
|
90% 16.5
|
||||||
|
95% 17.2
|
||||||
|
99% 18.1
|
||||||
|
99.5% 18.4
|
||||||
|
99.9% 18.9
|
||||||
|
max 31.0
|
||||||
|
|
||||||
|
## dev-clean
|
||||||
|
Cuts count: 2703
|
||||||
|
Total duration (hours): 5.4
|
||||||
|
Speech duration (hours): 5.4 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 7.2
|
||||||
|
std 4.7
|
||||||
|
min 1.4
|
||||||
|
0.1% 1.6
|
||||||
|
0.5% 1.8
|
||||||
|
1% 1.9
|
||||||
|
5% 2.4
|
||||||
|
10% 2.7
|
||||||
|
25% 3.8
|
||||||
|
50% 5.9
|
||||||
|
75% 9.3
|
||||||
|
90% 13.3
|
||||||
|
95% 16.4
|
||||||
|
99% 23.8
|
||||||
|
99.5% 28.5
|
||||||
|
99.9% 32.3
|
||||||
|
max 32.6
|
||||||
|
|
||||||
|
## dev-other
|
||||||
|
Cuts count: 2864
|
||||||
|
Total duration (hours): 5.1
|
||||||
|
Speech duration (hours): 5.1 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 6.4
|
||||||
|
std 4.3
|
||||||
|
min 1.1
|
||||||
|
0.1% 1.3
|
||||||
|
0.5% 1.7
|
||||||
|
1% 1.8
|
||||||
|
5% 2.2
|
||||||
|
10% 2.6
|
||||||
|
25% 3.5
|
||||||
|
50% 5.3
|
||||||
|
75% 7.9
|
||||||
|
90% 12.0
|
||||||
|
95% 15.0
|
||||||
|
99% 22.2
|
||||||
|
99.5% 27.1
|
||||||
|
99.9% 32.4
|
||||||
|
max 35.2
|
||||||
|
|
||||||
|
## test-clean
|
||||||
|
Cuts count: 2620
|
||||||
|
Total duration (hours): 5.4
|
||||||
|
Speech duration (hours): 5.4 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 7.4
|
||||||
|
std 5.2
|
||||||
|
min 1.3
|
||||||
|
0.1% 1.6
|
||||||
|
0.5% 1.8
|
||||||
|
1% 2.0
|
||||||
|
5% 2.3
|
||||||
|
10% 2.7
|
||||||
|
25% 3.7
|
||||||
|
50% 5.8
|
||||||
|
75% 9.6
|
||||||
|
90% 14.6
|
||||||
|
95% 17.8
|
||||||
|
99% 25.5
|
||||||
|
99.5% 28.4
|
||||||
|
99.9% 32.8
|
||||||
|
max 35.0
|
||||||
|
|
||||||
|
## test-other
|
||||||
|
Cuts count: 2939
|
||||||
|
Total duration (hours): 5.3
|
||||||
|
Speech duration (hours): 5.3 (100.0%)
|
||||||
|
***
|
||||||
|
Duration statistics (seconds):
|
||||||
|
mean 6.5
|
||||||
|
std 4.4
|
||||||
|
min 1.2
|
||||||
|
0.1% 1.5
|
||||||
|
0.5% 1.8
|
||||||
|
1% 1.9
|
||||||
|
5% 2.3
|
||||||
|
10% 2.6
|
||||||
|
25% 3.4
|
||||||
|
50% 5.2
|
||||||
|
75% 8.2
|
||||||
|
90% 12.6
|
||||||
|
95% 15.8
|
||||||
|
99% 21.4
|
||||||
|
99.5% 23.8
|
||||||
|
99.9% 33.5
|
||||||
|
max 34.5
|
||||||
|
"""
|
||||||
97
egs/LJSpeech/ASR/local/download_lm.py
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file downloads the following LibriSpeech LM files:
|
||||||
|
|
||||||
|
- 3-gram.pruned.1e-7.arpa.gz
|
||||||
|
- 4-gram.arpa.gz
|
||||||
|
- librispeech-vocab.txt
|
||||||
|
- librispeech-lexicon.txt
|
||||||
|
- librispeech-lm-norm.txt.gz
|
||||||
|
|
||||||
|
from http://www.openslr.org/resources/11
|
||||||
|
and save them in the user provided directory.
|
||||||
|
|
||||||
|
Files are not re-downloaded if they already exist.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
./local/download_lm.py --out-dir ./download/lm
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import gzip
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from lhotse.utils import urlretrieve_progress
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--out-dir", type=str, help="Output directory.")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main(out_dir: str):
|
||||||
|
url = "http://www.openslr.org/resources/11"
|
||||||
|
out_dir = Path(out_dir)
|
||||||
|
|
||||||
|
files_to_download = (
|
||||||
|
"3-gram.pruned.1e-7.arpa.gz",
|
||||||
|
"4-gram.arpa.gz",
|
||||||
|
"librispeech-vocab.txt",
|
||||||
|
"librispeech-lexicon.txt",
|
||||||
|
"librispeech-lm-norm.txt.gz",
|
||||||
|
)
|
||||||
|
|
||||||
|
for f in tqdm(files_to_download, desc="Downloading LibriSpeech LM files"):
|
||||||
|
filename = out_dir / f
|
||||||
|
if filename.is_file() is False:
|
||||||
|
urlretrieve_progress(
|
||||||
|
f"{url}/{f}",
|
||||||
|
filename=filename,
|
||||||
|
desc=f"Downloading {filename}",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.info(f"{filename} already exists - skipping")
|
||||||
|
|
||||||
|
if ".gz" in str(filename):
|
||||||
|
unzipped = Path(os.path.splitext(filename)[0])
|
||||||
|
if unzipped.is_file() is False:
|
||||||
|
with gzip.open(filename, "rb") as f_in:
|
||||||
|
with open(unzipped, "wb") as f_out:
|
||||||
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
else:
|
||||||
|
logging.info(f"{unzipped} already exist - skipping")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
args = get_args()
|
||||||
|
logging.info(f"out_dir: {args.out_dir}")
|
||||||
|
|
||||||
|
main(out_dir=args.out_dir)
|
||||||
160
egs/LJSpeech/ASR/local/filter_cuts.py
Executable file
@ -0,0 +1,160 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2022 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""
|
||||||
|
This script removes short and long utterances from a cutset.
|
||||||
|
|
||||||
|
Caution:
|
||||||
|
You may need to tune the thresholds for your own dataset.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
python3 ./local/filter_cuts.py \
|
||||||
|
--bpe-model data/lang_bpe_500/bpe.model \
|
||||||
|
--in-cuts data/fbank/librispeech_cuts_test-clean.jsonl.gz \
|
||||||
|
--out-cuts data/fbank-filtered/librispeech_cuts_test-clean.jsonl.gz
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
from lhotse import CutSet, load_manifest_lazy
|
||||||
|
from lhotse.cut import Cut
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the bpe.model",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--in-cuts",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the input cutset",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--out-cuts",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the output cutset",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def filter_cuts(cut_set: CutSet, sp: spm.SentencePieceProcessor):
|
||||||
|
total = 0 # number of total utterances before removal
|
||||||
|
removed = 0 # number of removed utterances
|
||||||
|
|
||||||
|
def remove_short_and_long_utterances(c: Cut):
|
||||||
|
"""Return False to exclude the input cut"""
|
||||||
|
nonlocal removed, total
|
||||||
|
# Keep only utterances with duration between 1 second and 20 seconds
|
||||||
|
#
|
||||||
|
# Caution: There is a reason to select 20.0 here. Please see
|
||||||
|
# ./display_manifest_statistics.py
|
||||||
|
#
|
||||||
|
# You should use ./display_manifest_statistics.py to get
|
||||||
|
# an utterance duration distribution for your dataset to select
|
||||||
|
# the threshold
|
||||||
|
total += 1
|
||||||
|
if c.duration < 1.0 or c.duration > 20.0:
|
||||||
|
logging.warning(
|
||||||
|
f"Exclude cut with ID {c.id} from training. Duration: {c.duration}"
|
||||||
|
)
|
||||||
|
removed += 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
# In pruned RNN-T, we require that T >= S
|
||||||
|
# where T is the number of feature frames after subsampling
|
||||||
|
# and S is the number of tokens in the utterance
|
||||||
|
|
||||||
|
# In ./pruned_transducer_stateless2/conformer.py, the
|
||||||
|
# conv module uses the following expression
|
||||||
|
# for subsampling
|
||||||
|
if c.num_frames is None:
|
||||||
|
num_frames = c.duration * 100 # approximate
|
||||||
|
else:
|
||||||
|
num_frames = c.num_frames
|
||||||
|
|
||||||
|
T = ((num_frames - 1) // 2 - 1) // 2
|
||||||
|
# Note: for ./lstm_transducer_stateless/lstm.py, the formula is
|
||||||
|
# T = ((num_frames - 3) // 2 - 1) // 2
|
||||||
|
|
||||||
|
# Note: for ./pruned_transducer_stateless7/zipformer.py, the formula is
|
||||||
|
# T = ((num_frames - 7) // 2 + 1) // 2
|
||||||
|
|
||||||
|
tokens = sp.encode(c.supervisions[0].text, out_type=str)
|
||||||
|
|
||||||
|
if T < len(tokens):
|
||||||
|
logging.warning(
|
||||||
|
f"Exclude cut with ID {c.id} from training. "
|
||||||
|
f"Number of frames (before subsampling): {c.num_frames}. "
|
||||||
|
f"Number of frames (after subsampling): {T}. "
|
||||||
|
f"Text: {c.supervisions[0].text}. "
|
||||||
|
f"Tokens: {tokens}. "
|
||||||
|
f"Number of tokens: {len(tokens)}"
|
||||||
|
)
|
||||||
|
removed += 1
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# We use to_eager() here so that we can print out the value of total
|
||||||
|
# and removed below.
|
||||||
|
ans = cut_set.filter(remove_short_and_long_utterances).to_eager()
|
||||||
|
ratio = removed / total * 100
|
||||||
|
logging.info(
|
||||||
|
f"Removed {removed} cuts from {total} cuts. {ratio:.3f}% data is removed."
|
||||||
|
)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
logging.info(vars(args))
|
||||||
|
|
||||||
|
if args.out_cuts.is_file():
|
||||||
|
logging.info(f"{args.out_cuts} already exists - skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
assert args.in_cuts.is_file(), f"{args.in_cuts} does not exist"
|
||||||
|
assert args.bpe_model.is_file(), f"{args.bpe_model} does not exist"
|
||||||
|
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(str(args.bpe_model))
|
||||||
|
|
||||||
|
cut_set = load_manifest_lazy(args.in_cuts)
|
||||||
|
assert isinstance(cut_set, CutSet)
|
||||||
|
|
||||||
|
cut_set = filter_cuts(cut_set, sp)
|
||||||
|
logging.info(f"Saving to {args.out_cuts}")
|
||||||
|
args.out_cuts.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
cut_set.to_file(args.out_cuts)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
98
egs/LJSpeech/ASR/local/generate_unique_lexicon.py
Executable file
@ -0,0 +1,98 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file takes as input a lexicon.txt and output a new lexicon,
|
||||||
|
in which each word has a unique pronunciation.
|
||||||
|
|
||||||
|
The way to do this is to keep only the first pronunciation of a word
|
||||||
|
in lexicon.txt.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from icefall.lexicon import read_lexicon, write_lexicon
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
It should contain a file lexicon.txt.
|
||||||
|
This file will generate a new file uniq_lexicon.txt
|
||||||
|
in it.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def filter_multiple_pronunications(
|
||||||
|
lexicon: List[Tuple[str, List[str]]]
|
||||||
|
) -> List[Tuple[str, List[str]]]:
|
||||||
|
"""Remove multiple pronunciations of words from a lexicon.
|
||||||
|
|
||||||
|
If a word has more than one pronunciation in the lexicon, only
|
||||||
|
the first one is kept, while other pronunciations are removed
|
||||||
|
from the lexicon.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
The input lexicon, containing a list of (word, [p1, p2, ..., pn]),
|
||||||
|
where "p1, p2, ..., pn" are the pronunciations of the "word".
|
||||||
|
Returns:
|
||||||
|
Return a new lexicon where each word has a unique pronunciation.
|
||||||
|
"""
|
||||||
|
seen = set()
|
||||||
|
ans = []
|
||||||
|
|
||||||
|
for word, tokens in lexicon:
|
||||||
|
if word in seen:
|
||||||
|
continue
|
||||||
|
seen.add(word)
|
||||||
|
ans.append((word, tokens))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
|
||||||
|
lexicon_filename = lang_dir / "lexicon.txt"
|
||||||
|
|
||||||
|
in_lexicon = read_lexicon(lexicon_filename)
|
||||||
|
|
||||||
|
out_lexicon = filter_multiple_pronunications(in_lexicon)
|
||||||
|
|
||||||
|
write_lexicon(lang_dir / "uniq_lexicon.txt", out_lexicon)
|
||||||
|
|
||||||
|
logging.info(f"Number of entries in lexicon.txt: {len(in_lexicon)}")
|
||||||
|
logging.info(f"Number of entries in uniq_lexicon.txt: {len(out_lexicon)}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
179
egs/LJSpeech/ASR/local/prepare_LJSpeech.py
Executable file
@ -0,0 +1,179 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
import zipfile
|
||||||
|
from concurrent.futures.thread import ThreadPoolExecutor
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
from lhotse import validate_recordings_and_supervisions
|
||||||
|
from lhotse.audio import Recording, RecordingSet
|
||||||
|
from lhotse.recipes.utils import manifests_exist, read_manifests_if_cached
|
||||||
|
from lhotse.supervision import AlignmentItem, SupervisionSegment, SupervisionSet
|
||||||
|
from lhotse.utils import (
|
||||||
|
Pathlike,
|
||||||
|
is_module_available,
|
||||||
|
safe_extract,
|
||||||
|
urlretrieve_progress,
|
||||||
|
)
|
||||||
|
|
||||||
|
# LIBRISPEECH_ALIGNMENTS_URL = (
|
||||||
|
# "https://drive.google.com/uc?id=1WYfgr31T-PPwMcxuAq09XZfHQO5Mw8fE"
|
||||||
|
# )
|
||||||
|
|
||||||
|
def prepare_LJSpeech(
|
||||||
|
corpus_dir: str,
|
||||||
|
dataset_parts: str = "auto",
|
||||||
|
output_dir: str = None,
|
||||||
|
num_jobs: int = 1,
|
||||||
|
) -> Dict[str, Dict[str, Union[RecordingSet, SupervisionSet]]]:
|
||||||
|
"""
|
||||||
|
Returns the manifests which consist of the Recordings and Supervisions.
|
||||||
|
When all the manifests are available in the ``output_dir``, it will simply read and return them.
|
||||||
|
:param corpus_dir: Pathlike, the path of the data dir.
|
||||||
|
:param dataset_parts: string or sequence of strings representing dataset part names, e.g. 'train-clean-100', 'train-clean-5', 'dev-clean'.
|
||||||
|
By default we will infer which parts are available in ``corpus_dir``.
|
||||||
|
:param output_dir: Pathlike, the path where to write the manifests.
|
||||||
|
:return: a Dict whose key is the dataset part, and the value is Dicts with the keys 'audio' and 'supervisions'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert os.path.exists(corpus_dir), f"{corpus_dir} does not exist"
|
||||||
|
|
||||||
|
# wav_dir = Path(corpus_dir + "/wavs")
|
||||||
|
# wavs = os.listdir(wav_dir)
|
||||||
|
|
||||||
|
# text_dir = Path(corpus_dir + "/wavs")
|
||||||
|
# texts = os.listdir(text_dir)
|
||||||
|
|
||||||
|
# wavs_parts = (
|
||||||
|
# set(wavs)
|
||||||
|
# )
|
||||||
|
# books_parts = (
|
||||||
|
# set(texts)
|
||||||
|
# )
|
||||||
|
|
||||||
|
manifests = {}
|
||||||
|
|
||||||
|
dataset_parts = ["train", "dev", "test"]
|
||||||
|
if output_dir is not None:
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
import glob
|
||||||
|
|
||||||
|
futures = []
|
||||||
|
for part in tqdm(dataset_parts, desc="Dataset parts"):
|
||||||
|
logging.info(f"Processing LJSpeech subset: {part}")
|
||||||
|
if manifests_exist(part=part, output_dir=output_dir):
|
||||||
|
logging.info(f"LJSpeech subset: {part} already prepared - skipping.")
|
||||||
|
continue
|
||||||
|
recordings = []
|
||||||
|
supervisions = []
|
||||||
|
part_path = Path(os.path.join(corpus_dir, "wavs", part))
|
||||||
|
part_file_names = list(map(lambda x: x.strip('.wav'),os.listdir(part_path)))
|
||||||
|
txt_path = os.path.join(corpus_dir, "texts")
|
||||||
|
futures = []
|
||||||
|
|
||||||
|
for trans_path in tqdm(
|
||||||
|
glob.iglob(str(txt_path) + "/*.txt"), desc="Distributing tasks", leave=False
|
||||||
|
):
|
||||||
|
alignments = {}
|
||||||
|
with open(trans_path) as f:
|
||||||
|
cur_file_name = trans_path.split('/')[-1].replace('.txt', '')
|
||||||
|
if cur_file_name not in part_file_names:
|
||||||
|
continue
|
||||||
|
for line in f:
|
||||||
|
futures.append(
|
||||||
|
parse_utterance(part_path, trans_path + ' ' + line, alignments)
|
||||||
|
)
|
||||||
|
|
||||||
|
for future in tqdm(futures, desc="Processing", leave=False):
|
||||||
|
result = future
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
recording, segment = result
|
||||||
|
recordings.append(recording)
|
||||||
|
supervisions.append(segment)
|
||||||
|
|
||||||
|
recording_set = RecordingSet.from_recordings(recordings)
|
||||||
|
supervision_set = SupervisionSet.from_segments(supervisions)
|
||||||
|
|
||||||
|
validate_recordings_and_supervisions(recording_set, supervision_set)
|
||||||
|
|
||||||
|
if output_dir is not None:
|
||||||
|
supervision_set.to_file(
|
||||||
|
output_dir / f"LJSpeech_supervisions_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
recording_set.to_file(
|
||||||
|
output_dir / f"LJSpeech_recordings_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
|
||||||
|
manifests[part] = {
|
||||||
|
"recordings": recording_set,
|
||||||
|
"supervisions": supervision_set,
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifests
|
||||||
|
|
||||||
|
|
||||||
|
def parse_utterance(
|
||||||
|
dataset_split_path: Path,
|
||||||
|
line: str,
|
||||||
|
alignments: Dict[str, List[AlignmentItem]],
|
||||||
|
) -> Optional[Tuple[Recording, SupervisionSegment]]:
|
||||||
|
recording_id, text = line.strip().split(maxsplit=1)
|
||||||
|
recording_id = recording_id.split('/')[-1].split('.txt')[0]
|
||||||
|
|
||||||
|
# Create the Recording first
|
||||||
|
audio_path = (
|
||||||
|
dataset_split_path / f"{recording_id}.wav"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(audio_path):
|
||||||
|
logging.warning(f"No such file: {audio_path}")
|
||||||
|
return None
|
||||||
|
recording = Recording.from_file(audio_path, recording_id=recording_id)
|
||||||
|
# Then, create the corresponding supervisions
|
||||||
|
segment = SupervisionSegment(
|
||||||
|
id=recording_id,
|
||||||
|
recording_id=recording_id,
|
||||||
|
start=0.0,
|
||||||
|
duration=recording.duration,
|
||||||
|
channel=0,
|
||||||
|
language="English",
|
||||||
|
speaker=re.sub(r"-.*", r"", recording.id),
|
||||||
|
text=text.strip(),
|
||||||
|
alignment={"word": alignments[recording_id]}
|
||||||
|
if recording_id in alignments
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
return recording, segment
|
||||||
|
|
||||||
|
|
||||||
|
def parse_alignments(ali_path: Pathlike) -> Dict[str, List[AlignmentItem]]:
|
||||||
|
alignments = {}
|
||||||
|
for line in Path(ali_path).read_text().splitlines():
|
||||||
|
utt_id, words, timestamps = line.split()
|
||||||
|
words = words.replace('"', "").split(",")
|
||||||
|
timestamps = [0.0] + list(map(float, timestamps.replace('"', "").split(",")))
|
||||||
|
alignments[utt_id] = [
|
||||||
|
AlignmentItem(
|
||||||
|
symbol=word, start=start, duration=round(end - start, ndigits=8)
|
||||||
|
)
|
||||||
|
for word, start, end in zip(words, timestamps, timestamps[1:])
|
||||||
|
]
|
||||||
|
return alignments
|
||||||
|
|
||||||
|
def main(corpus_dir):
|
||||||
|
nj = 15
|
||||||
|
output_dir = "data/manifests"
|
||||||
|
|
||||||
|
prepare_LJSpeech(corpus_dir, "auto", output_dir, nj)
|
||||||
|
|
||||||
|
corpus_dir = sys.argv[1]
|
||||||
|
main(corpus_dir)
|
||||||
179
egs/LJSpeech/ASR/local/prepare_LJSpeech_pseudo.py
Executable file
@ -0,0 +1,179 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
import zipfile
|
||||||
|
from concurrent.futures.thread import ThreadPoolExecutor
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
from lhotse import validate_recordings_and_supervisions
|
||||||
|
from lhotse.audio import Recording, RecordingSet
|
||||||
|
from lhotse.recipes.utils import manifests_exist, read_manifests_if_cached
|
||||||
|
from lhotse.supervision import AlignmentItem, SupervisionSegment, SupervisionSet
|
||||||
|
from lhotse.utils import (
|
||||||
|
Pathlike,
|
||||||
|
is_module_available,
|
||||||
|
safe_extract,
|
||||||
|
urlretrieve_progress,
|
||||||
|
)
|
||||||
|
|
||||||
|
# LIBRISPEECH_ALIGNMENTS_URL = (
|
||||||
|
# "https://drive.google.com/uc?id=1WYfgr31T-PPwMcxuAq09XZfHQO5Mw8fE"
|
||||||
|
# )
|
||||||
|
|
||||||
|
def prepare_LJSpeech(
|
||||||
|
corpus_dir: str,
|
||||||
|
dataset_parts: str = "auto",
|
||||||
|
output_dir: str = None,
|
||||||
|
num_jobs: int = 1,
|
||||||
|
) -> Dict[str, Dict[str, Union[RecordingSet, SupervisionSet]]]:
|
||||||
|
"""
|
||||||
|
Returns the manifests which consist of the Recordings and Supervisions.
|
||||||
|
When all the manifests are available in the ``output_dir``, it will simply read and return them.
|
||||||
|
:param corpus_dir: Pathlike, the path of the data dir.
|
||||||
|
:param dataset_parts: string or sequence of strings representing dataset part names, e.g. 'train-clean-100', 'train-clean-5', 'dev-clean'.
|
||||||
|
By default we will infer which parts are available in ``corpus_dir``.
|
||||||
|
:param output_dir: Pathlike, the path where to write the manifests.
|
||||||
|
:return: a Dict whose key is the dataset part, and the value is Dicts with the keys 'audio' and 'supervisions'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert os.path.exists(corpus_dir), f"{corpus_dir} does not exist"
|
||||||
|
|
||||||
|
# wav_dir = Path(corpus_dir + "/wavs")
|
||||||
|
# wavs = os.listdir(wav_dir)
|
||||||
|
|
||||||
|
# text_dir = Path(corpus_dir + "/wavs")
|
||||||
|
# texts = os.listdir(text_dir)
|
||||||
|
|
||||||
|
# wavs_parts = (
|
||||||
|
# set(wavs)
|
||||||
|
# )
|
||||||
|
# books_parts = (
|
||||||
|
# set(texts)
|
||||||
|
# )
|
||||||
|
|
||||||
|
manifests = {}
|
||||||
|
|
||||||
|
dataset_parts = ["train", "dev", "test"]
|
||||||
|
if output_dir is not None:
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
import glob
|
||||||
|
|
||||||
|
futures = []
|
||||||
|
for part in tqdm(dataset_parts, desc="Dataset parts"):
|
||||||
|
logging.info(f"Processing LJSpeech subset: {part}")
|
||||||
|
if manifests_exist(part=part, output_dir=output_dir):
|
||||||
|
logging.info(f"LJSpeech subset: {part} already prepared - skipping.")
|
||||||
|
continue
|
||||||
|
recordings = []
|
||||||
|
supervisions = []
|
||||||
|
part_path = Path(os.path.join(corpus_dir, "wavs", part))
|
||||||
|
part_file_names = list(map(lambda x: x.strip('.wav'),os.listdir(part_path)))
|
||||||
|
txt_path = os.path.join(corpus_dir, "texts")
|
||||||
|
futures = []
|
||||||
|
|
||||||
|
for trans_path in tqdm(
|
||||||
|
glob.iglob(str(txt_path) + "/*.txt"), desc="Distributing tasks", leave=False
|
||||||
|
):
|
||||||
|
alignments = {}
|
||||||
|
with open(trans_path) as f:
|
||||||
|
cur_file_name = trans_path.split('/')[-1].replace('.txt', '')
|
||||||
|
if cur_file_name not in part_file_names:
|
||||||
|
continue
|
||||||
|
for line in f:
|
||||||
|
futures.append(
|
||||||
|
parse_utterance(part_path, trans_path + ' ' + line, alignments)
|
||||||
|
)
|
||||||
|
|
||||||
|
for future in tqdm(futures, desc="Processing", leave=False):
|
||||||
|
result = future
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
recording, segment = result
|
||||||
|
recordings.append(recording)
|
||||||
|
supervisions.append(segment)
|
||||||
|
|
||||||
|
recording_set = RecordingSet.from_recordings(recordings)
|
||||||
|
supervision_set = SupervisionSet.from_segments(supervisions)
|
||||||
|
|
||||||
|
validate_recordings_and_supervisions(recording_set, supervision_set)
|
||||||
|
|
||||||
|
if output_dir is not None:
|
||||||
|
supervision_set.to_file(
|
||||||
|
output_dir / f"LJSpeech_pseudo_supervisions_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
recording_set.to_file(
|
||||||
|
output_dir / f"LJSpeech_pseudo_recordings_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
|
||||||
|
manifests[part] = {
|
||||||
|
"recordings": recording_set,
|
||||||
|
"supervisions": supervision_set,
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifests
|
||||||
|
|
||||||
|
|
||||||
|
def parse_utterance(
|
||||||
|
dataset_split_path: Path,
|
||||||
|
line: str,
|
||||||
|
alignments: Dict[str, List[AlignmentItem]],
|
||||||
|
) -> Optional[Tuple[Recording, SupervisionSegment]]:
|
||||||
|
recording_id, text = line.strip().split(maxsplit=1)
|
||||||
|
recording_id = recording_id.split('/')[-1].split('.txt')[0]
|
||||||
|
|
||||||
|
# Create the Recording first
|
||||||
|
audio_path = (
|
||||||
|
dataset_split_path / f"{recording_id}.wav"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.path.exists(audio_path):
|
||||||
|
logging.warning(f"No such file: {audio_path}")
|
||||||
|
return None
|
||||||
|
recording = Recording.from_file(audio_path, recording_id=recording_id)
|
||||||
|
# Then, create the corresponding supervisions
|
||||||
|
segment = SupervisionSegment(
|
||||||
|
id=recording_id,
|
||||||
|
recording_id=recording_id,
|
||||||
|
start=0.0,
|
||||||
|
duration=recording.duration,
|
||||||
|
channel=0,
|
||||||
|
language="English",
|
||||||
|
speaker=re.sub(r"-.*", r"", recording.id),
|
||||||
|
text=text.strip(),
|
||||||
|
alignment={"word": alignments[recording_id]}
|
||||||
|
if recording_id in alignments
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
return recording, segment
|
||||||
|
|
||||||
|
|
||||||
|
def parse_alignments(ali_path: Pathlike) -> Dict[str, List[AlignmentItem]]:
|
||||||
|
alignments = {}
|
||||||
|
for line in Path(ali_path).read_text().splitlines():
|
||||||
|
utt_id, words, timestamps = line.split()
|
||||||
|
words = words.replace('"', "").split(",")
|
||||||
|
timestamps = [0.0] + list(map(float, timestamps.replace('"', "").split(",")))
|
||||||
|
alignments[utt_id] = [
|
||||||
|
AlignmentItem(
|
||||||
|
symbol=word, start=start, duration=round(end - start, ndigits=8)
|
||||||
|
)
|
||||||
|
for word, start, end in zip(words, timestamps, timestamps[1:])
|
||||||
|
]
|
||||||
|
return alignments
|
||||||
|
|
||||||
|
def main(corpus_dir):
|
||||||
|
nj = 15
|
||||||
|
output_dir = "data/manifests"
|
||||||
|
|
||||||
|
prepare_LJSpeech(corpus_dir, "auto", output_dir, nj)
|
||||||
|
|
||||||
|
corpus_dir = sys.argv[1]
|
||||||
|
main(corpus_dir)
|
||||||
35
egs/LJSpeech/ASR/local/prepare_LJSpeech_text.py
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
metafile = sys.argv[1]
|
||||||
|
outdir = "texts"
|
||||||
|
save_dir = "/".join(metafile.split('/')[:-1])
|
||||||
|
save_dir = os.path.join(save_dir, outdir)
|
||||||
|
|
||||||
|
if not os.path.exists(save_dir):
|
||||||
|
os.makedirs(save_dir)
|
||||||
|
|
||||||
|
with open(metafile, 'r') as f:
|
||||||
|
strings = f.readlines()
|
||||||
|
|
||||||
|
for string in strings:
|
||||||
|
|
||||||
|
# Split the string into parts
|
||||||
|
parts = string.split("|")
|
||||||
|
|
||||||
|
# Assign the parts to variables
|
||||||
|
filename = parts[0]
|
||||||
|
text1 = parts[1]
|
||||||
|
try:
|
||||||
|
text2 = parts[2]
|
||||||
|
except:
|
||||||
|
text2 = text1
|
||||||
|
|
||||||
|
text2 = text2.upper()
|
||||||
|
text2 = re.sub(r"[^A-Z ']", "", text2)
|
||||||
|
|
||||||
|
# Create a new text file with the filename and write text2 to it
|
||||||
|
filename = os.path.join(save_dir, filename)
|
||||||
|
with open(f"{filename}.txt", "w") as file:
|
||||||
|
file.write(text2)
|
||||||
413
egs/LJSpeech/ASR/local/prepare_lang.py
Executable file
@ -0,0 +1,413 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script takes as input a lexicon file "data/lang_phone/lexicon.txt"
|
||||||
|
consisting of words and tokens (i.e., phones) and does the following:
|
||||||
|
|
||||||
|
1. Add disambiguation symbols to the lexicon and generate lexicon_disambig.txt
|
||||||
|
|
||||||
|
2. Generate tokens.txt, the token table mapping a token to a unique integer.
|
||||||
|
|
||||||
|
3. Generate words.txt, the word table mapping a word to a unique integer.
|
||||||
|
|
||||||
|
4. Generate L.pt, in k2 format. It can be loaded by
|
||||||
|
|
||||||
|
d = torch.load("L.pt")
|
||||||
|
lexicon = k2.Fsa.from_dict(d)
|
||||||
|
|
||||||
|
5. Generate L_disambig.pt, in k2 format.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
from collections import defaultdict
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, List, Tuple
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from icefall.lexicon import read_lexicon, write_lexicon
|
||||||
|
from icefall.utils import str2bool
|
||||||
|
|
||||||
|
Lexicon = List[Tuple[str, List[str]]]
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
It should contain a file lexicon.txt.
|
||||||
|
Generated files by this script are saved into this directory.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
type=str2bool,
|
||||||
|
default=False,
|
||||||
|
help="""True for debugging, which will generate
|
||||||
|
a visualization of the lexicon FST.
|
||||||
|
|
||||||
|
Caution: If your lexicon contains hundreds of thousands
|
||||||
|
of lines, please set it to False!
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def write_mapping(filename: str, sym2id: Dict[str, int]) -> None:
|
||||||
|
"""Write a symbol to ID mapping to a file.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
No need to implement `read_mapping` as it can be done
|
||||||
|
through :func:`k2.SymbolTable.from_file`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename:
|
||||||
|
Filename to save the mapping.
|
||||||
|
sym2id:
|
||||||
|
A dict mapping symbols to IDs.
|
||||||
|
Returns:
|
||||||
|
Return None.
|
||||||
|
"""
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
for sym, i in sym2id.items():
|
||||||
|
f.write(f"{sym} {i}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def get_tokens(lexicon: Lexicon) -> List[str]:
|
||||||
|
"""Get tokens from a lexicon.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
It is the return value of :func:`read_lexicon`.
|
||||||
|
Returns:
|
||||||
|
Return a list of unique tokens.
|
||||||
|
"""
|
||||||
|
ans = set()
|
||||||
|
for _, tokens in lexicon:
|
||||||
|
ans.update(tokens)
|
||||||
|
sorted_ans = sorted(list(ans))
|
||||||
|
return sorted_ans
|
||||||
|
|
||||||
|
|
||||||
|
def get_words(lexicon: Lexicon) -> List[str]:
|
||||||
|
"""Get words from a lexicon.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
It is the return value of :func:`read_lexicon`.
|
||||||
|
Returns:
|
||||||
|
Return a list of unique words.
|
||||||
|
"""
|
||||||
|
ans = set()
|
||||||
|
for word, _ in lexicon:
|
||||||
|
ans.add(word)
|
||||||
|
sorted_ans = sorted(list(ans))
|
||||||
|
return sorted_ans
|
||||||
|
|
||||||
|
|
||||||
|
def add_disambig_symbols(lexicon: Lexicon) -> Tuple[Lexicon, int]:
|
||||||
|
"""It adds pseudo-token disambiguation symbols #1, #2 and so on
|
||||||
|
at the ends of tokens to ensure that all pronunciations are different,
|
||||||
|
and that none is a prefix of another.
|
||||||
|
|
||||||
|
See also add_lex_disambig.pl from kaldi.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
It is returned by :func:`read_lexicon`.
|
||||||
|
Returns:
|
||||||
|
Return a tuple with two elements:
|
||||||
|
|
||||||
|
- The output lexicon with disambiguation symbols
|
||||||
|
- The ID of the max disambiguation symbol that appears
|
||||||
|
in the lexicon
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (1) Work out the count of each token-sequence in the
|
||||||
|
# lexicon.
|
||||||
|
count = defaultdict(int)
|
||||||
|
for _, tokens in lexicon:
|
||||||
|
count[" ".join(tokens)] += 1
|
||||||
|
|
||||||
|
# (2) For each left sub-sequence of each token-sequence, note down
|
||||||
|
# that it exists (for identifying prefixes of longer strings).
|
||||||
|
issubseq = defaultdict(int)
|
||||||
|
for _, tokens in lexicon:
|
||||||
|
tokens = tokens.copy()
|
||||||
|
tokens.pop()
|
||||||
|
while tokens:
|
||||||
|
issubseq[" ".join(tokens)] = 1
|
||||||
|
tokens.pop()
|
||||||
|
|
||||||
|
# (3) For each entry in the lexicon:
|
||||||
|
# if the token sequence is unique and is not a
|
||||||
|
# prefix of another word, no disambig symbol.
|
||||||
|
# Else output #1, or #2, #3, ... if the same token-seq
|
||||||
|
# has already been assigned a disambig symbol.
|
||||||
|
ans = []
|
||||||
|
|
||||||
|
# We start with #1 since #0 has its own purpose
|
||||||
|
first_allowed_disambig = 1
|
||||||
|
max_disambig = first_allowed_disambig - 1
|
||||||
|
last_used_disambig_symbol_of = defaultdict(int)
|
||||||
|
|
||||||
|
for word, tokens in lexicon:
|
||||||
|
tokenseq = " ".join(tokens)
|
||||||
|
assert tokenseq != ""
|
||||||
|
if issubseq[tokenseq] == 0 and count[tokenseq] == 1:
|
||||||
|
ans.append((word, tokens))
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur_disambig = last_used_disambig_symbol_of[tokenseq]
|
||||||
|
if cur_disambig == 0:
|
||||||
|
cur_disambig = first_allowed_disambig
|
||||||
|
else:
|
||||||
|
cur_disambig += 1
|
||||||
|
|
||||||
|
if cur_disambig > max_disambig:
|
||||||
|
max_disambig = cur_disambig
|
||||||
|
last_used_disambig_symbol_of[tokenseq] = cur_disambig
|
||||||
|
tokenseq += f" #{cur_disambig}"
|
||||||
|
ans.append((word, tokenseq.split()))
|
||||||
|
return ans, max_disambig
|
||||||
|
|
||||||
|
|
||||||
|
def generate_id_map(symbols: List[str]) -> Dict[str, int]:
|
||||||
|
"""Generate ID maps, i.e., map a symbol to a unique ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
symbols:
|
||||||
|
A list of unique symbols.
|
||||||
|
Returns:
|
||||||
|
A dict containing the mapping between symbols and IDs.
|
||||||
|
"""
|
||||||
|
return {sym: i for i, sym in enumerate(symbols)}
|
||||||
|
|
||||||
|
|
||||||
|
def add_self_loops(
|
||||||
|
arcs: List[List[Any]], disambig_token: int, disambig_word: int
|
||||||
|
) -> List[List[Any]]:
|
||||||
|
"""Adds self-loops to states of an FST to propagate disambiguation symbols
|
||||||
|
through it. They are added on each state with non-epsilon output symbols
|
||||||
|
on at least one arc out of the state.
|
||||||
|
|
||||||
|
See also fstaddselfloops.pl from Kaldi. One difference is that
|
||||||
|
Kaldi uses OpenFst style FSTs and it has multiple final states.
|
||||||
|
This function uses k2 style FSTs and it does not need to add self-loops
|
||||||
|
to the final state.
|
||||||
|
|
||||||
|
The input label of a self-loop is `disambig_token`, while the output
|
||||||
|
label is `disambig_word`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
arcs:
|
||||||
|
A list-of-list. The sublist contains
|
||||||
|
`[src_state, dest_state, label, aux_label, score]`
|
||||||
|
disambig_token:
|
||||||
|
It is the token ID of the symbol `#0`.
|
||||||
|
disambig_word:
|
||||||
|
It is the word ID of the symbol `#0`.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
Return new `arcs` containing self-loops.
|
||||||
|
"""
|
||||||
|
states_needs_self_loops = set()
|
||||||
|
for arc in arcs:
|
||||||
|
src, dst, ilabel, olabel, score = arc
|
||||||
|
if olabel != 0:
|
||||||
|
states_needs_self_loops.add(src)
|
||||||
|
|
||||||
|
ans = []
|
||||||
|
for s in states_needs_self_loops:
|
||||||
|
ans.append([s, s, disambig_token, disambig_word, 0])
|
||||||
|
|
||||||
|
return arcs + ans
|
||||||
|
|
||||||
|
|
||||||
|
def lexicon_to_fst(
|
||||||
|
lexicon: Lexicon,
|
||||||
|
token2id: Dict[str, int],
|
||||||
|
word2id: Dict[str, int],
|
||||||
|
sil_token: str = "SIL",
|
||||||
|
sil_prob: float = 0.5,
|
||||||
|
need_self_loops: bool = False,
|
||||||
|
) -> k2.Fsa:
|
||||||
|
"""Convert a lexicon to an FST (in k2 format) with optional silence at
|
||||||
|
the beginning and end of each word.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
The input lexicon. See also :func:`read_lexicon`
|
||||||
|
token2id:
|
||||||
|
A dict mapping tokens to IDs.
|
||||||
|
word2id:
|
||||||
|
A dict mapping words to IDs.
|
||||||
|
sil_token:
|
||||||
|
The silence token.
|
||||||
|
sil_prob:
|
||||||
|
The probability for adding a silence at the beginning and end
|
||||||
|
of the word.
|
||||||
|
need_self_loops:
|
||||||
|
If True, add self-loop to states with non-epsilon output symbols
|
||||||
|
on at least one arc out of the state. The input label for this
|
||||||
|
self loop is `token2id["#0"]` and the output label is `word2id["#0"]`.
|
||||||
|
Returns:
|
||||||
|
Return an instance of `k2.Fsa` representing the given lexicon.
|
||||||
|
"""
|
||||||
|
assert sil_prob > 0.0 and sil_prob < 1.0
|
||||||
|
# CAUTION: we use score, i.e, negative cost.
|
||||||
|
sil_score = math.log(sil_prob)
|
||||||
|
no_sil_score = math.log(1.0 - sil_prob)
|
||||||
|
|
||||||
|
start_state = 0
|
||||||
|
loop_state = 1 # words enter and leave from here
|
||||||
|
sil_state = 2 # words terminate here when followed by silence; this state
|
||||||
|
# has a silence transition to loop_state.
|
||||||
|
next_state = 3 # the next un-allocated state, will be incremented as we go.
|
||||||
|
arcs = []
|
||||||
|
|
||||||
|
assert token2id["<eps>"] == 0
|
||||||
|
assert word2id["<eps>"] == 0
|
||||||
|
|
||||||
|
eps = 0
|
||||||
|
|
||||||
|
sil_token = token2id[sil_token]
|
||||||
|
|
||||||
|
arcs.append([start_state, loop_state, eps, eps, no_sil_score])
|
||||||
|
arcs.append([start_state, sil_state, eps, eps, sil_score])
|
||||||
|
arcs.append([sil_state, loop_state, sil_token, eps, 0])
|
||||||
|
|
||||||
|
for word, tokens in lexicon:
|
||||||
|
assert len(tokens) > 0, f"{word} has no pronunciations"
|
||||||
|
cur_state = loop_state
|
||||||
|
|
||||||
|
word = word2id[word]
|
||||||
|
tokens = [token2id[i] for i in tokens]
|
||||||
|
|
||||||
|
for i in range(len(tokens) - 1):
|
||||||
|
w = word if i == 0 else eps
|
||||||
|
arcs.append([cur_state, next_state, tokens[i], w, 0])
|
||||||
|
|
||||||
|
cur_state = next_state
|
||||||
|
next_state += 1
|
||||||
|
|
||||||
|
# now for the last token of this word
|
||||||
|
# It has two out-going arcs, one to the loop state,
|
||||||
|
# the other one to the sil_state.
|
||||||
|
i = len(tokens) - 1
|
||||||
|
w = word if i == 0 else eps
|
||||||
|
arcs.append([cur_state, loop_state, tokens[i], w, no_sil_score])
|
||||||
|
arcs.append([cur_state, sil_state, tokens[i], w, sil_score])
|
||||||
|
|
||||||
|
if need_self_loops:
|
||||||
|
disambig_token = token2id["#0"]
|
||||||
|
disambig_word = word2id["#0"]
|
||||||
|
arcs = add_self_loops(
|
||||||
|
arcs,
|
||||||
|
disambig_token=disambig_token,
|
||||||
|
disambig_word=disambig_word,
|
||||||
|
)
|
||||||
|
|
||||||
|
final_state = next_state
|
||||||
|
arcs.append([loop_state, final_state, -1, -1, 0])
|
||||||
|
arcs.append([final_state])
|
||||||
|
|
||||||
|
arcs = sorted(arcs, key=lambda arc: arc[0])
|
||||||
|
arcs = [[str(i) for i in arc] for arc in arcs]
|
||||||
|
arcs = [" ".join(arc) for arc in arcs]
|
||||||
|
arcs = "\n".join(arcs)
|
||||||
|
|
||||||
|
fsa = k2.Fsa.from_str(arcs, acceptor=False)
|
||||||
|
return fsa
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
lexicon_filename = lang_dir / "lexicon.txt"
|
||||||
|
sil_token = "SIL"
|
||||||
|
sil_prob = 0.5
|
||||||
|
|
||||||
|
lexicon = read_lexicon(lexicon_filename)
|
||||||
|
tokens = get_tokens(lexicon)
|
||||||
|
words = get_words(lexicon)
|
||||||
|
|
||||||
|
lexicon_disambig, max_disambig = add_disambig_symbols(lexicon)
|
||||||
|
|
||||||
|
for i in range(max_disambig + 1):
|
||||||
|
disambig = f"#{i}"
|
||||||
|
assert disambig not in tokens
|
||||||
|
tokens.append(f"#{i}")
|
||||||
|
|
||||||
|
assert "<eps>" not in tokens
|
||||||
|
tokens = ["<eps>"] + tokens
|
||||||
|
|
||||||
|
assert "<eps>" not in words
|
||||||
|
assert "#0" not in words
|
||||||
|
assert "<s>" not in words
|
||||||
|
assert "</s>" not in words
|
||||||
|
|
||||||
|
words = ["<eps>"] + words + ["#0", "<s>", "</s>"]
|
||||||
|
|
||||||
|
token2id = generate_id_map(tokens)
|
||||||
|
word2id = generate_id_map(words)
|
||||||
|
|
||||||
|
write_mapping(lang_dir / "tokens.txt", token2id)
|
||||||
|
write_mapping(lang_dir / "words.txt", word2id)
|
||||||
|
write_lexicon(lang_dir / "lexicon_disambig.txt", lexicon_disambig)
|
||||||
|
|
||||||
|
L = lexicon_to_fst(
|
||||||
|
lexicon,
|
||||||
|
token2id=token2id,
|
||||||
|
word2id=word2id,
|
||||||
|
sil_token=sil_token,
|
||||||
|
sil_prob=sil_prob,
|
||||||
|
)
|
||||||
|
|
||||||
|
L_disambig = lexicon_to_fst(
|
||||||
|
lexicon_disambig,
|
||||||
|
token2id=token2id,
|
||||||
|
word2id=word2id,
|
||||||
|
sil_token=sil_token,
|
||||||
|
sil_prob=sil_prob,
|
||||||
|
need_self_loops=True,
|
||||||
|
)
|
||||||
|
torch.save(L.as_dict(), lang_dir / "L.pt")
|
||||||
|
torch.save(L_disambig.as_dict(), lang_dir / "L_disambig.pt")
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
labels_sym = k2.SymbolTable.from_file(lang_dir / "tokens.txt")
|
||||||
|
aux_labels_sym = k2.SymbolTable.from_file(lang_dir / "words.txt")
|
||||||
|
|
||||||
|
L.labels_sym = labels_sym
|
||||||
|
L.aux_labels_sym = aux_labels_sym
|
||||||
|
L.draw(f"{lang_dir / 'L.svg'}", title="L.pt")
|
||||||
|
|
||||||
|
L_disambig.labels_sym = labels_sym
|
||||||
|
L_disambig.aux_labels_sym = aux_labels_sym
|
||||||
|
L_disambig.draw(f"{lang_dir / 'L_disambig.svg'}", title="L_disambig.pt")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
259
egs/LJSpeech/ASR/local/prepare_lang_bpe.py
Executable file
@ -0,0 +1,259 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (c) 2021 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
This script takes as input `lang_dir`, which should contain::
|
||||||
|
|
||||||
|
- lang_dir/bpe.model,
|
||||||
|
- lang_dir/words.txt
|
||||||
|
|
||||||
|
and generates the following files in the directory `lang_dir`:
|
||||||
|
|
||||||
|
- lexicon.txt
|
||||||
|
- lexicon_disambig.txt
|
||||||
|
- L.pt
|
||||||
|
- L_disambig.pt
|
||||||
|
- tokens.txt
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
from prepare_lang import (
|
||||||
|
Lexicon,
|
||||||
|
add_disambig_symbols,
|
||||||
|
add_self_loops,
|
||||||
|
write_lexicon,
|
||||||
|
write_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
from icefall.utils import str2bool
|
||||||
|
|
||||||
|
|
||||||
|
def lexicon_to_fst_no_sil(
|
||||||
|
lexicon: Lexicon,
|
||||||
|
token2id: Dict[str, int],
|
||||||
|
word2id: Dict[str, int],
|
||||||
|
need_self_loops: bool = False,
|
||||||
|
) -> k2.Fsa:
|
||||||
|
"""Convert a lexicon to an FST (in k2 format).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lexicon:
|
||||||
|
The input lexicon. See also :func:`read_lexicon`
|
||||||
|
token2id:
|
||||||
|
A dict mapping tokens to IDs.
|
||||||
|
word2id:
|
||||||
|
A dict mapping words to IDs.
|
||||||
|
need_self_loops:
|
||||||
|
If True, add self-loop to states with non-epsilon output symbols
|
||||||
|
on at least one arc out of the state. The input label for this
|
||||||
|
self loop is `token2id["#0"]` and the output label is `word2id["#0"]`.
|
||||||
|
Returns:
|
||||||
|
Return an instance of `k2.Fsa` representing the given lexicon.
|
||||||
|
"""
|
||||||
|
loop_state = 0 # words enter and leave from here
|
||||||
|
next_state = 1 # the next un-allocated state, will be incremented as we go
|
||||||
|
|
||||||
|
arcs = []
|
||||||
|
|
||||||
|
# The blank symbol <blk> is defined in local/train_bpe_model.py
|
||||||
|
assert token2id["<blk>"] == 0
|
||||||
|
assert word2id["<eps>"] == 0
|
||||||
|
|
||||||
|
eps = 0
|
||||||
|
|
||||||
|
for word, pieces in lexicon:
|
||||||
|
assert len(pieces) > 0, f"{word} has no pronunciations"
|
||||||
|
cur_state = loop_state
|
||||||
|
|
||||||
|
word = word2id[word]
|
||||||
|
pieces = [token2id[i] for i in pieces]
|
||||||
|
|
||||||
|
for i in range(len(pieces) - 1):
|
||||||
|
w = word if i == 0 else eps
|
||||||
|
arcs.append([cur_state, next_state, pieces[i], w, 0])
|
||||||
|
|
||||||
|
cur_state = next_state
|
||||||
|
next_state += 1
|
||||||
|
|
||||||
|
# now for the last piece of this word
|
||||||
|
i = len(pieces) - 1
|
||||||
|
w = word if i == 0 else eps
|
||||||
|
arcs.append([cur_state, loop_state, pieces[i], w, 0])
|
||||||
|
|
||||||
|
if need_self_loops:
|
||||||
|
disambig_token = token2id["#0"]
|
||||||
|
disambig_word = word2id["#0"]
|
||||||
|
arcs = add_self_loops(
|
||||||
|
arcs,
|
||||||
|
disambig_token=disambig_token,
|
||||||
|
disambig_word=disambig_word,
|
||||||
|
)
|
||||||
|
|
||||||
|
final_state = next_state
|
||||||
|
arcs.append([loop_state, final_state, -1, -1, 0])
|
||||||
|
arcs.append([final_state])
|
||||||
|
|
||||||
|
arcs = sorted(arcs, key=lambda arc: arc[0])
|
||||||
|
arcs = [[str(i) for i in arc] for arc in arcs]
|
||||||
|
arcs = [" ".join(arc) for arc in arcs]
|
||||||
|
arcs = "\n".join(arcs)
|
||||||
|
|
||||||
|
fsa = k2.Fsa.from_str(arcs, acceptor=False)
|
||||||
|
return fsa
|
||||||
|
|
||||||
|
|
||||||
|
def generate_lexicon(
|
||||||
|
model_file: str, words: List[str]
|
||||||
|
) -> Tuple[Lexicon, Dict[str, int]]:
|
||||||
|
"""Generate a lexicon from a BPE model.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_file:
|
||||||
|
Path to a sentencepiece model.
|
||||||
|
words:
|
||||||
|
A list of strings representing words.
|
||||||
|
Returns:
|
||||||
|
Return a tuple with two elements:
|
||||||
|
- A dict whose keys are words and values are the corresponding
|
||||||
|
word pieces.
|
||||||
|
- A dict representing the token symbol, mapping from tokens to IDs.
|
||||||
|
"""
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(str(model_file))
|
||||||
|
|
||||||
|
# Convert word to word piece IDs instead of word piece strings
|
||||||
|
# to avoid OOV tokens.
|
||||||
|
words_pieces_ids: List[List[int]] = sp.encode(words, out_type=int)
|
||||||
|
|
||||||
|
# Now convert word piece IDs back to word piece strings.
|
||||||
|
words_pieces: List[List[str]] = [sp.id_to_piece(ids) for ids in words_pieces_ids]
|
||||||
|
|
||||||
|
lexicon = []
|
||||||
|
for word, pieces in zip(words, words_pieces):
|
||||||
|
lexicon.append((word, pieces))
|
||||||
|
|
||||||
|
# The OOV word is <UNK>
|
||||||
|
lexicon.append(("<UNK>", [sp.id_to_piece(sp.unk_id())]))
|
||||||
|
|
||||||
|
token2id: Dict[str, int] = dict()
|
||||||
|
for i in range(sp.vocab_size()):
|
||||||
|
token2id[sp.id_to_piece(i)] = i
|
||||||
|
|
||||||
|
return lexicon, token2id
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
It should contain the bpe.model and words.txt
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
type=str2bool,
|
||||||
|
default=False,
|
||||||
|
help="""True for debugging, which will generate
|
||||||
|
a visualization of the lexicon FST.
|
||||||
|
|
||||||
|
Caution: If your lexicon contains hundreds of thousands
|
||||||
|
of lines, please set it to False!
|
||||||
|
|
||||||
|
See "test/test_bpe_lexicon.py" for usage.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
model_file = lang_dir / "bpe.model"
|
||||||
|
|
||||||
|
word_sym_table = k2.SymbolTable.from_file(lang_dir / "words.txt")
|
||||||
|
|
||||||
|
words = word_sym_table.symbols
|
||||||
|
|
||||||
|
excluded = ["<eps>", "!SIL", "<SPOKEN_NOISE>", "<UNK>", "#0", "<s>", "</s>"]
|
||||||
|
for w in excluded:
|
||||||
|
if w in words:
|
||||||
|
words.remove(w)
|
||||||
|
|
||||||
|
lexicon, token_sym_table = generate_lexicon(model_file, words)
|
||||||
|
|
||||||
|
lexicon_disambig, max_disambig = add_disambig_symbols(lexicon)
|
||||||
|
|
||||||
|
next_token_id = max(token_sym_table.values()) + 1
|
||||||
|
for i in range(max_disambig + 1):
|
||||||
|
disambig = f"#{i}"
|
||||||
|
assert disambig not in token_sym_table
|
||||||
|
token_sym_table[disambig] = next_token_id
|
||||||
|
next_token_id += 1
|
||||||
|
|
||||||
|
word_sym_table.add("#0")
|
||||||
|
word_sym_table.add("<s>")
|
||||||
|
word_sym_table.add("</s>")
|
||||||
|
|
||||||
|
write_mapping(lang_dir / "tokens.txt", token_sym_table)
|
||||||
|
|
||||||
|
write_lexicon(lang_dir / "lexicon.txt", lexicon)
|
||||||
|
write_lexicon(lang_dir / "lexicon_disambig.txt", lexicon_disambig)
|
||||||
|
|
||||||
|
L = lexicon_to_fst_no_sil(
|
||||||
|
lexicon,
|
||||||
|
token2id=token_sym_table,
|
||||||
|
word2id=word_sym_table,
|
||||||
|
)
|
||||||
|
|
||||||
|
L_disambig = lexicon_to_fst_no_sil(
|
||||||
|
lexicon_disambig,
|
||||||
|
token2id=token_sym_table,
|
||||||
|
word2id=word_sym_table,
|
||||||
|
need_self_loops=True,
|
||||||
|
)
|
||||||
|
torch.save(L.as_dict(), lang_dir / "L.pt")
|
||||||
|
torch.save(L_disambig.as_dict(), lang_dir / "L_disambig.pt")
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
labels_sym = k2.SymbolTable.from_file(lang_dir / "tokens.txt")
|
||||||
|
aux_labels_sym = k2.SymbolTable.from_file(lang_dir / "words.txt")
|
||||||
|
|
||||||
|
L.labels_sym = labels_sym
|
||||||
|
L.aux_labels_sym = aux_labels_sym
|
||||||
|
L.draw(f"{lang_dir / 'L.svg'}", title="L.pt")
|
||||||
|
|
||||||
|
L_disambig.labels_sym = labels_sym
|
||||||
|
L_disambig.aux_labels_sym = aux_labels_sym
|
||||||
|
L_disambig.draw(f"{lang_dir / 'L_disambig.svg'}", title="L_disambig.pt")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
167
egs/LJSpeech/ASR/local/prepare_lm_training_data.py
Executable file
@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2021 Xiaomi Corporation (authors: Daniel Povey
|
||||||
|
# Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This script takes a `bpe.model` and a text file such as
|
||||||
|
./download/lm/librispeech-lm-norm.txt
|
||||||
|
and outputs the LM training data to a supplied directory such
|
||||||
|
as data/lm_training_bpe_500. The format is as follows:
|
||||||
|
|
||||||
|
It creates a PyTorch archive (.pt file), say data/lm_training.pt, which is a
|
||||||
|
representation of a dict with the following format:
|
||||||
|
|
||||||
|
'words' -> a k2.RaggedTensor of two axes [word][token] with dtype torch.int32
|
||||||
|
containing the BPE representations of each word, indexed by
|
||||||
|
integer word ID. (These integer word IDS are present in
|
||||||
|
'lm_data'). The sentencepiece object can be used to turn the
|
||||||
|
words and BPE units into string form.
|
||||||
|
'sentences' -> a k2.RaggedTensor of two axes [sentence][word] with dtype
|
||||||
|
torch.int32 containing all the sentences, as word-ids (we don't
|
||||||
|
output the string form of this directly but it can be worked out
|
||||||
|
together with 'words' and the bpe.model).
|
||||||
|
'sentence_lengths' -> a 1-D torch.Tensor of dtype torch.int32, containing
|
||||||
|
number of BPE tokens of each sentence.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import sentencepiece as spm
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
type=str,
|
||||||
|
help="Input BPE model, e.g. data/bpe_500/bpe.model",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--lm-data",
|
||||||
|
type=str,
|
||||||
|
help="""Input LM training data as text, e.g.
|
||||||
|
download/pb.train.txt""",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--lm-archive",
|
||||||
|
type=str,
|
||||||
|
help="""Path to output archive, e.g. data/bpe_500/lm_data.pt;
|
||||||
|
look at the source of this script to see the format.""",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
|
||||||
|
if Path(args.lm_archive).exists():
|
||||||
|
logging.warning(f"{args.lm_archive} exists - skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(args.bpe_model)
|
||||||
|
|
||||||
|
# word2index is a dictionary from words to integer ids. No need to reserve
|
||||||
|
# space for epsilon, etc.; the words are just used as a convenient way to
|
||||||
|
# compress the sequences of BPE pieces.
|
||||||
|
word2index = dict()
|
||||||
|
|
||||||
|
word2bpe = [] # Will be a list-of-list-of-int, representing BPE pieces.
|
||||||
|
sentences = [] # Will be a list-of-list-of-int, representing word-ids.
|
||||||
|
|
||||||
|
if "librispeech-lm-norm" in args.lm_data:
|
||||||
|
num_lines_in_total = 40418261.0
|
||||||
|
step = 5000000
|
||||||
|
elif "valid" in args.lm_data:
|
||||||
|
num_lines_in_total = 5567.0
|
||||||
|
step = 3000
|
||||||
|
elif "test" in args.lm_data:
|
||||||
|
num_lines_in_total = 5559.0
|
||||||
|
step = 3000
|
||||||
|
else:
|
||||||
|
num_lines_in_total = None
|
||||||
|
step = None
|
||||||
|
|
||||||
|
processed = 0
|
||||||
|
|
||||||
|
with open(args.lm_data) as f:
|
||||||
|
while True:
|
||||||
|
line = f.readline()
|
||||||
|
if line == "":
|
||||||
|
break
|
||||||
|
|
||||||
|
if step and processed % step == 0:
|
||||||
|
logging.info(
|
||||||
|
f"Processed number of lines: {processed} "
|
||||||
|
f"({processed/num_lines_in_total*100: .3f}%)"
|
||||||
|
)
|
||||||
|
processed += 1
|
||||||
|
|
||||||
|
line_words = line.split()
|
||||||
|
for w in line_words:
|
||||||
|
if w not in word2index:
|
||||||
|
w_bpe = sp.encode(w)
|
||||||
|
word2index[w] = len(word2bpe)
|
||||||
|
word2bpe.append(w_bpe)
|
||||||
|
sentences.append([word2index[w] for w in line_words])
|
||||||
|
|
||||||
|
logging.info("Constructing ragged tensors")
|
||||||
|
words = k2.ragged.RaggedTensor(word2bpe)
|
||||||
|
sentences = k2.ragged.RaggedTensor(sentences)
|
||||||
|
|
||||||
|
output = dict(words=words, sentences=sentences)
|
||||||
|
|
||||||
|
num_sentences = sentences.dim0
|
||||||
|
logging.info(f"Computing sentence lengths, num_sentences: {num_sentences}")
|
||||||
|
sentence_lengths = [0] * num_sentences
|
||||||
|
for i in range(num_sentences):
|
||||||
|
if step and i % step == 0:
|
||||||
|
logging.info(
|
||||||
|
f"Processed number of lines: {i} ({i/num_sentences*100: .3f}%)"
|
||||||
|
)
|
||||||
|
|
||||||
|
word_ids = sentences[i]
|
||||||
|
|
||||||
|
# NOTE: If word_ids is a tensor with only 1 entry,
|
||||||
|
# token_ids is a torch.Tensor
|
||||||
|
token_ids = words[word_ids]
|
||||||
|
if isinstance(token_ids, k2.RaggedTensor):
|
||||||
|
token_ids = token_ids.values
|
||||||
|
|
||||||
|
# token_ids is a 1-D tensor containing the BPE tokens
|
||||||
|
# of the current sentence
|
||||||
|
|
||||||
|
sentence_lengths[i] = token_ids.numel()
|
||||||
|
|
||||||
|
output["sentence_lengths"] = torch.tensor(sentence_lengths, dtype=torch.int32)
|
||||||
|
|
||||||
|
torch.save(output, args.lm_archive)
|
||||||
|
logging.info(f"Saved to {args.lm_archive}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
255
egs/LJSpeech/ASR/local/prepare_userlibri.py
Executable file
@ -0,0 +1,255 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import tarfile
|
||||||
|
import zipfile
|
||||||
|
from concurrent.futures.thread import ThreadPoolExecutor
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
from lhotse import validate_recordings_and_supervisions
|
||||||
|
from lhotse.audio import Recording, RecordingSet
|
||||||
|
from lhotse.recipes.utils import manifests_exist, read_manifests_if_cached
|
||||||
|
from lhotse.supervision import AlignmentItem, SupervisionSegment, SupervisionSet
|
||||||
|
from lhotse.utils import (
|
||||||
|
Pathlike,
|
||||||
|
is_module_available,
|
||||||
|
safe_extract,
|
||||||
|
urlretrieve_progress,
|
||||||
|
)
|
||||||
|
|
||||||
|
# LIBRISPEECH_ALIGNMENTS_URL = (
|
||||||
|
# "https://drive.google.com/uc?id=1WYfgr31T-PPwMcxuAq09XZfHQO5Mw8fE"
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# def download_librispeech(
|
||||||
|
# target_dir: Pathlike = ".",
|
||||||
|
# dataset_parts: Optional[Union[str, Sequence[str]]] = "mini_librispeech",
|
||||||
|
# force_download: bool = False,
|
||||||
|
# alignments: bool = False,
|
||||||
|
# base_url: str = "http://www.openslr.org/resources",
|
||||||
|
# alignments_url: str = LIBRISPEECH_ALIGNMENTS_URL,
|
||||||
|
# ) -> Path:
|
||||||
|
# """
|
||||||
|
# Download and untar the dataset, supporting both LibriSpeech and MiniLibrispeech
|
||||||
|
# :param target_dir: Pathlike, the path of the dir to storage the dataset.
|
||||||
|
# :param dataset_parts: "librispeech", "mini_librispeech",
|
||||||
|
# or a list of splits (e.g. "dev-clean") to download.
|
||||||
|
# :param force_download: Bool, if True, download the tars no matter if the tars exist.
|
||||||
|
# :param alignments: should we download the alignments. The original source is:
|
||||||
|
# https://github.com/CorentinJ/librispeech-alignments
|
||||||
|
# :param base_url: str, the url of the OpenSLR resources.
|
||||||
|
# :param alignments_url: str, the url of LibriSpeech word alignments
|
||||||
|
# :return: the path to downloaded and extracted directory with data.
|
||||||
|
# """
|
||||||
|
# target_dir = Path(target_dir)
|
||||||
|
# corpus_dir = target_dir / "LibriSpeech"
|
||||||
|
# target_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# if dataset_parts == "librispeech":
|
||||||
|
# dataset_parts = LIBRISPEECH
|
||||||
|
# elif dataset_parts == "mini_librispeech":
|
||||||
|
# dataset_parts = MINI_LIBRISPEECH
|
||||||
|
# elif isinstance(dataset_parts, str):
|
||||||
|
# dataset_parts = [dataset_parts]
|
||||||
|
|
||||||
|
# for part in tqdm(dataset_parts, desc="Downloading LibriSpeech parts"):
|
||||||
|
# logging.info(f"Processing split: {part}")
|
||||||
|
# # Determine the valid URL for a given split.
|
||||||
|
# if part in LIBRISPEECH:
|
||||||
|
# url = f"{base_url}/12"
|
||||||
|
# elif part in MINI_LIBRISPEECH:
|
||||||
|
# url = f"{base_url}/31"
|
||||||
|
# else:
|
||||||
|
# logging.warning(f"Invalid dataset part name: {part}")
|
||||||
|
# continue
|
||||||
|
# # Split directory exists and seem valid? Skip this split.
|
||||||
|
# part_dir = corpus_dir / part
|
||||||
|
# completed_detector = part_dir / ".completed"
|
||||||
|
# if completed_detector.is_file():
|
||||||
|
# logging.info(f"Skipping {part} because {completed_detector} exists.")
|
||||||
|
# continue
|
||||||
|
# # Maybe-download the archive.
|
||||||
|
# tar_name = f"{part}.tar.gz"
|
||||||
|
# tar_path = target_dir / tar_name
|
||||||
|
# if force_download or not tar_path.is_file():
|
||||||
|
# urlretrieve_progress(
|
||||||
|
# f"{url}/{tar_name}", filename=tar_path, desc=f"Downloading {tar_name}"
|
||||||
|
# )
|
||||||
|
# # Remove partial unpacked files, if any, and unpack everything.
|
||||||
|
# shutil.rmtree(part_dir, ignore_errors=True)
|
||||||
|
# with tarfile.open(tar_path) as tar:
|
||||||
|
# safe_extract(tar, path=target_dir)
|
||||||
|
# completed_detector.touch()
|
||||||
|
|
||||||
|
# if alignments:
|
||||||
|
# completed_detector = target_dir / ".ali_completed"
|
||||||
|
# if completed_detector.is_file() and not force_download:
|
||||||
|
# return corpus_dir
|
||||||
|
# assert is_module_available(
|
||||||
|
# "gdown"
|
||||||
|
# ), 'To download LibriSpeech alignments, please install "pip install gdown"'
|
||||||
|
# import gdown
|
||||||
|
|
||||||
|
# ali_zip_path = str(target_dir / "LibriSpeech-Alignments.zip")
|
||||||
|
# gdown.download(alignments_url, output=ali_zip_path)
|
||||||
|
# with zipfile.ZipFile(ali_zip_path) as f:
|
||||||
|
# f.extractall(path=target_dir)
|
||||||
|
# completed_detector.touch()
|
||||||
|
|
||||||
|
# return corpus_dir
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_userlibri(
|
||||||
|
corpus_dir: str,
|
||||||
|
dataset_parts: str = "auto",
|
||||||
|
output_dir: str = None,
|
||||||
|
num_jobs: int = 1,
|
||||||
|
) -> Dict[str, Dict[str, Union[RecordingSet, SupervisionSet]]]:
|
||||||
|
"""
|
||||||
|
Returns the manifests which consist of the Recordings and Supervisions.
|
||||||
|
When all the manifests are available in the ``output_dir``, it will simply read and return them.
|
||||||
|
:param corpus_dir: Pathlike, the path of the data dir.
|
||||||
|
:param dataset_parts: string or sequence of strings representing dataset part names, e.g. 'train-clean-100', 'train-clean-5', 'dev-clean'.
|
||||||
|
By default we will infer which parts are available in ``corpus_dir``.
|
||||||
|
:param output_dir: Pathlike, the path where to write the manifests.
|
||||||
|
:return: a Dict whose key is the dataset part, and the value is Dicts with the keys 'audio' and 'supervisions'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# corpus_audio_dir = Path(corpus_dir + "/audio_data")
|
||||||
|
# corpus_lm_dir = Path(corpus_dir + "/lm_data")
|
||||||
|
# corpus_dir = Path(corpus_dir)
|
||||||
|
corpus_dir = Path(corpus_dir + "/audio_data")
|
||||||
|
assert corpus_dir.is_dir(), f"No such directory: {corpus_dir}"
|
||||||
|
|
||||||
|
spkwise_parent = corpus_dir / "speaker-wise-test"
|
||||||
|
spks = os.listdir(spkwise_parent)
|
||||||
|
|
||||||
|
bookwise_parent = corpus_dir / "book-wise-test"
|
||||||
|
books = os.listdir(bookwise_parent)
|
||||||
|
|
||||||
|
spks_parts = (
|
||||||
|
set(spks)
|
||||||
|
)
|
||||||
|
books_parts = (
|
||||||
|
set(books)
|
||||||
|
)
|
||||||
|
|
||||||
|
manifests = {}
|
||||||
|
|
||||||
|
for s_or_b, dataset_parts in zip(["speaker-wise-test", "book-wise-test"], [spks_parts, books_parts]):
|
||||||
|
if output_dir is not None:
|
||||||
|
output_dir = Path(output_dir)
|
||||||
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Maybe the manifests already exist: we can read them and save a bit of preparation time.
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=dataset_parts, output_dir=output_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(num_jobs) as ex:
|
||||||
|
for part in tqdm(dataset_parts, desc="Dataset parts"):
|
||||||
|
logging.info(f"Processing UserLibri subset: {part}")
|
||||||
|
if manifests_exist(part=part, output_dir=output_dir):
|
||||||
|
logging.info(f"UserLibri subset: {part} already prepared - skipping.")
|
||||||
|
continue
|
||||||
|
recordings = []
|
||||||
|
supervisions = []
|
||||||
|
part_path = corpus_dir / s_or_b / part
|
||||||
|
futures = []
|
||||||
|
for trans_path in tqdm(
|
||||||
|
part_path.rglob("*.trans.txt"), desc="Distributing tasks", leave=False
|
||||||
|
):
|
||||||
|
alignments = {}
|
||||||
|
with open(trans_path) as f:
|
||||||
|
for line in f:
|
||||||
|
futures.append(
|
||||||
|
ex.submit(parse_utterance, trans_path.parent, line, alignments)
|
||||||
|
)
|
||||||
|
|
||||||
|
for future in tqdm(futures, desc="Processing", leave=False):
|
||||||
|
result = future.result()
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
recording, segment = result
|
||||||
|
recordings.append(recording)
|
||||||
|
supervisions.append(segment)
|
||||||
|
|
||||||
|
recording_set = RecordingSet.from_recordings(recordings)
|
||||||
|
supervision_set = SupervisionSet.from_segments(supervisions)
|
||||||
|
|
||||||
|
validate_recordings_and_supervisions(recording_set, supervision_set)
|
||||||
|
|
||||||
|
if output_dir is not None:
|
||||||
|
supervision_set.to_file(
|
||||||
|
output_dir / f"userlibri_supervisions_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
recording_set.to_file(
|
||||||
|
output_dir / f"userlibri_recordings_{part}.jsonl.gz"
|
||||||
|
)
|
||||||
|
|
||||||
|
manifests[part] = {
|
||||||
|
"recordings": recording_set,
|
||||||
|
"supervisions": supervision_set,
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifests
|
||||||
|
|
||||||
|
|
||||||
|
def parse_utterance(
|
||||||
|
dataset_split_path: Path,
|
||||||
|
line: str,
|
||||||
|
alignments: Dict[str, List[AlignmentItem]],
|
||||||
|
) -> Optional[Tuple[Recording, SupervisionSegment]]:
|
||||||
|
recording_id, text = line.strip().split(maxsplit=1)
|
||||||
|
# Create the Recording first
|
||||||
|
audio_path = (
|
||||||
|
dataset_split_path
|
||||||
|
/ f"{recording_id}.flac"
|
||||||
|
)
|
||||||
|
if not audio_path.is_file():
|
||||||
|
logging.warning(f"No such file: {audio_path}")
|
||||||
|
return None
|
||||||
|
recording = Recording.from_file(audio_path, recording_id=recording_id)
|
||||||
|
# Then, create the corresponding supervisions
|
||||||
|
segment = SupervisionSegment(
|
||||||
|
id=recording_id,
|
||||||
|
recording_id=recording_id,
|
||||||
|
start=0.0,
|
||||||
|
duration=recording.duration,
|
||||||
|
channel=0,
|
||||||
|
language="English",
|
||||||
|
speaker=re.sub(r"-.*", r"", recording.id),
|
||||||
|
text=text.strip(),
|
||||||
|
alignment={"word": alignments[recording_id]}
|
||||||
|
if recording_id in alignments
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
return recording, segment
|
||||||
|
|
||||||
|
|
||||||
|
def parse_alignments(ali_path: Pathlike) -> Dict[str, List[AlignmentItem]]:
|
||||||
|
alignments = {}
|
||||||
|
for line in Path(ali_path).read_text().splitlines():
|
||||||
|
utt_id, words, timestamps = line.split()
|
||||||
|
words = words.replace('"', "").split(",")
|
||||||
|
timestamps = [0.0] + list(map(float, timestamps.replace('"', "").split(",")))
|
||||||
|
alignments[utt_id] = [
|
||||||
|
AlignmentItem(
|
||||||
|
symbol=word, start=start, duration=round(end - start, ndigits=8)
|
||||||
|
)
|
||||||
|
for word, start, end in zip(words, timestamps, timestamps[1:])
|
||||||
|
]
|
||||||
|
return alignments
|
||||||
|
|
||||||
|
def main():
|
||||||
|
nj = 15
|
||||||
|
output_dir = "data/manifests"
|
||||||
|
corpus_dir = "/DB/UserLibri"
|
||||||
|
|
||||||
|
prepare_userlibri(corpus_dir, "auto", output_dir, nj)
|
||||||
|
|
||||||
|
main()
|
||||||
129
egs/LJSpeech/ASR/local/preprocess_gigaspeech.py
Executable file
@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Johns Hopkins University (Piotr Żelasko)
|
||||||
|
# Copyright 2021 Xiaomi Corp. (Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from lhotse import CutSet, SupervisionSegment
|
||||||
|
from lhotse.recipes.utils import read_manifests_if_cached
|
||||||
|
|
||||||
|
# Similar text filtering and normalization procedure as in:
|
||||||
|
# https://github.com/SpeechColab/GigaSpeech/blob/main/toolkits/kaldi/gigaspeech_data_prep.sh
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_text(
|
||||||
|
utt: str,
|
||||||
|
punct_pattern=re.compile(r"<(COMMA|PERIOD|QUESTIONMARK|EXCLAMATIONPOINT)>"),
|
||||||
|
whitespace_pattern=re.compile(r"\s\s+"),
|
||||||
|
) -> str:
|
||||||
|
return whitespace_pattern.sub(" ", punct_pattern.sub("", utt))
|
||||||
|
|
||||||
|
|
||||||
|
def has_no_oov(
|
||||||
|
sup: SupervisionSegment,
|
||||||
|
oov_pattern=re.compile(r"<(SIL|MUSIC|NOISE|OTHER)>"),
|
||||||
|
) -> bool:
|
||||||
|
return oov_pattern.search(sup.text) is None
|
||||||
|
|
||||||
|
|
||||||
|
def preprocess_giga_speech():
|
||||||
|
src_dir = Path("data/manifests")
|
||||||
|
output_dir = Path("data/fbank")
|
||||||
|
output_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
dataset_parts = (
|
||||||
|
"DEV",
|
||||||
|
"TEST",
|
||||||
|
"XS",
|
||||||
|
"S",
|
||||||
|
"M",
|
||||||
|
"L",
|
||||||
|
"XL",
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info("Loading manifest (may take 4 minutes)")
|
||||||
|
prefix = "gigaspeech"
|
||||||
|
suffix = "jsonl.gz"
|
||||||
|
manifests = read_manifests_if_cached(
|
||||||
|
dataset_parts=dataset_parts,
|
||||||
|
output_dir=src_dir,
|
||||||
|
prefix=prefix,
|
||||||
|
suffix=suffix,
|
||||||
|
)
|
||||||
|
assert manifests is not None
|
||||||
|
|
||||||
|
assert len(manifests) == len(dataset_parts), (
|
||||||
|
len(manifests),
|
||||||
|
len(dataset_parts),
|
||||||
|
list(manifests.keys()),
|
||||||
|
dataset_parts,
|
||||||
|
)
|
||||||
|
|
||||||
|
for partition, m in manifests.items():
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
raw_cuts_path = output_dir / f"{prefix}_cuts_{partition}_raw.{suffix}"
|
||||||
|
if raw_cuts_path.is_file():
|
||||||
|
logging.info(f"{partition} already exists - skipping")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Note this step makes the recipe different than LibriSpeech:
|
||||||
|
# We must filter out some utterances and remove punctuation
|
||||||
|
# to be consistent with Kaldi.
|
||||||
|
logging.info("Filtering OOV utterances from supervisions")
|
||||||
|
m["supervisions"] = m["supervisions"].filter(has_no_oov)
|
||||||
|
logging.info(f"Normalizing text in {partition}")
|
||||||
|
for sup in m["supervisions"]:
|
||||||
|
sup.text = normalize_text(sup.text)
|
||||||
|
sup.custom = {"origin": "giga"}
|
||||||
|
|
||||||
|
# Create long-recording cut manifests.
|
||||||
|
logging.info(f"Processing {partition}")
|
||||||
|
cut_set = CutSet.from_manifests(
|
||||||
|
recordings=m["recordings"],
|
||||||
|
supervisions=m["supervisions"],
|
||||||
|
)
|
||||||
|
# Run data augmentation that needs to be done in the
|
||||||
|
# time domain.
|
||||||
|
# if partition not in ["DEV", "TEST"]:
|
||||||
|
# logging.info(
|
||||||
|
# f"Speed perturb for {partition} with factors 0.9 and 1.1 "
|
||||||
|
# "(Perturbing may take 8 minutes and saving may"
|
||||||
|
# " take 20 minutes)"
|
||||||
|
# )
|
||||||
|
# cut_set = (
|
||||||
|
# cut_set
|
||||||
|
# + cut_set.perturb_speed(0.9)
|
||||||
|
# + cut_set.perturb_speed(1.1)
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# Note: No need to perturb the training subset as not all of the
|
||||||
|
# data is going to be used in the training.
|
||||||
|
logging.info(f"Saving to {raw_cuts_path}")
|
||||||
|
cut_set.to_file(raw_cuts_path)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
preprocess_giga_speech()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
141
egs/LJSpeech/ASR/local/sort_lm_training_data.py
Executable file
@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Copyright (c) 2021 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file takes as input the filename of LM training data
|
||||||
|
generated by ./local/prepare_lm_training_data.py and sorts
|
||||||
|
it by sentence length.
|
||||||
|
|
||||||
|
Sentence length equals to the number of BPE tokens in a sentence.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import k2
|
||||||
|
import numpy as np
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--in-lm-data",
|
||||||
|
type=str,
|
||||||
|
help="Input LM training data, e.g., data/bpe_500/lm_data.pt",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--out-lm-data",
|
||||||
|
type=str,
|
||||||
|
help="Input LM training data, e.g., data/bpe_500/sorted_lm_data.pt",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--out-statistics",
|
||||||
|
type=str,
|
||||||
|
help="Statistics about LM training data., data/bpe_500/statistics.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
in_lm_data = Path(args.in_lm_data)
|
||||||
|
out_lm_data = Path(args.out_lm_data)
|
||||||
|
assert in_lm_data.is_file(), f"{in_lm_data}"
|
||||||
|
if out_lm_data.is_file():
|
||||||
|
logging.warning(f"{out_lm_data} exists - skipping")
|
||||||
|
return
|
||||||
|
data = torch.load(in_lm_data)
|
||||||
|
words2bpe = data["words"]
|
||||||
|
sentences = data["sentences"]
|
||||||
|
sentence_lengths = data["sentence_lengths"]
|
||||||
|
|
||||||
|
num_sentences = sentences.dim0
|
||||||
|
assert num_sentences == sentence_lengths.numel(), (
|
||||||
|
num_sentences,
|
||||||
|
sentence_lengths.numel(),
|
||||||
|
)
|
||||||
|
|
||||||
|
indices = torch.argsort(sentence_lengths, descending=True)
|
||||||
|
|
||||||
|
sorted_sentences = sentences[indices.to(torch.int32)]
|
||||||
|
sorted_sentence_lengths = sentence_lengths[indices]
|
||||||
|
|
||||||
|
# Check that sentences are ordered by length
|
||||||
|
assert num_sentences == sorted_sentences.dim0, (
|
||||||
|
num_sentences,
|
||||||
|
sorted_sentences.dim0,
|
||||||
|
)
|
||||||
|
|
||||||
|
cur = None
|
||||||
|
for i in range(num_sentences):
|
||||||
|
word_ids = sorted_sentences[i]
|
||||||
|
token_ids = words2bpe[word_ids]
|
||||||
|
if isinstance(token_ids, k2.RaggedTensor):
|
||||||
|
token_ids = token_ids.values
|
||||||
|
if cur is not None:
|
||||||
|
assert cur >= token_ids.numel(), (cur, token_ids.numel())
|
||||||
|
|
||||||
|
cur = token_ids.numel()
|
||||||
|
assert cur == sorted_sentence_lengths[i]
|
||||||
|
|
||||||
|
data["sentences"] = sorted_sentences
|
||||||
|
data["sentence_lengths"] = sorted_sentence_lengths
|
||||||
|
torch.save(data, args.out_lm_data)
|
||||||
|
logging.info(f"Saved to {args.out_lm_data}")
|
||||||
|
|
||||||
|
statistics = Path(args.out_statistics)
|
||||||
|
|
||||||
|
# Write statistics
|
||||||
|
num_words = sorted_sentences.numel()
|
||||||
|
num_tokens = sentence_lengths.sum().item()
|
||||||
|
max_sentence_length = sentence_lengths[indices[0]]
|
||||||
|
min_sentence_length = sentence_lengths[indices[-1]]
|
||||||
|
|
||||||
|
step = 10
|
||||||
|
hist, bins = np.histogram(
|
||||||
|
sentence_lengths.numpy(),
|
||||||
|
bins=np.arange(1, max_sentence_length + step, step),
|
||||||
|
)
|
||||||
|
|
||||||
|
histogram = np.stack((bins[:-1], hist)).transpose()
|
||||||
|
|
||||||
|
with open(statistics, "w") as f:
|
||||||
|
f.write(f"num_sentences: {num_sentences}\n")
|
||||||
|
f.write(f"num_words: {num_words}\n")
|
||||||
|
f.write(f"num_tokens: {num_tokens}\n")
|
||||||
|
f.write(f"max_sentence_length: {max_sentence_length}\n")
|
||||||
|
f.write(f"min_sentence_length: {min_sentence_length}\n")
|
||||||
|
f.write("histogram:\n")
|
||||||
|
f.write(" bin count percent\n")
|
||||||
|
for row in histogram:
|
||||||
|
f.write(
|
||||||
|
f"{int(row[0]):>5} {int(row[1]):>5} "
|
||||||
|
f"{100.*row[1]/num_sentences:.3f}%\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
main()
|
||||||
51
egs/LJSpeech/ASR/local/test_load_XL_split.py
Executable file
@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2022 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This file can be used to check if any split is corrupted.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import re
|
||||||
|
|
||||||
|
import lhotse
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
d = "data/fbank/XL_split_2000"
|
||||||
|
filenames = list(glob.glob(f"{d}/cuts_XL.*.jsonl.gz"))
|
||||||
|
|
||||||
|
pattern = re.compile(r"cuts_XL.([0-9]+).jsonl.gz")
|
||||||
|
|
||||||
|
idx_filenames = [(int(pattern.search(c).group(1)), c) for c in filenames]
|
||||||
|
|
||||||
|
idx_filenames = sorted(idx_filenames, key=lambda x: x[0])
|
||||||
|
|
||||||
|
print(f"Loading {len(idx_filenames)} splits")
|
||||||
|
|
||||||
|
s = 0
|
||||||
|
for i, f in idx_filenames:
|
||||||
|
cuts = lhotse.load_manifest_lazy(f)
|
||||||
|
print(i, "filename", f)
|
||||||
|
for i, c in enumerate(cuts):
|
||||||
|
s += c.features.load().shape[0]
|
||||||
|
if i > 5:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
104
egs/LJSpeech/ASR/local/test_prepare_lang.py
Executable file
@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright (c) 2021 Xiaomi Corporation (authors: Fangjun Kuang)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import k2
|
||||||
|
from prepare_lang import (
|
||||||
|
add_disambig_symbols,
|
||||||
|
generate_id_map,
|
||||||
|
get_phones,
|
||||||
|
get_words,
|
||||||
|
lexicon_to_fst,
|
||||||
|
read_lexicon,
|
||||||
|
write_lexicon,
|
||||||
|
write_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_lexicon_file() -> str:
|
||||||
|
fd, filename = tempfile.mkstemp()
|
||||||
|
os.close(fd)
|
||||||
|
s = """
|
||||||
|
!SIL SIL
|
||||||
|
<SPOKEN_NOISE> SPN
|
||||||
|
<UNK> SPN
|
||||||
|
f f
|
||||||
|
a a
|
||||||
|
foo f o o
|
||||||
|
bar b a r
|
||||||
|
bark b a r k
|
||||||
|
food f o o d
|
||||||
|
food2 f o o d
|
||||||
|
fo f o
|
||||||
|
""".strip()
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
f.write(s)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_lexicon(filename: str):
|
||||||
|
lexicon = read_lexicon(filename)
|
||||||
|
phones = get_phones(lexicon)
|
||||||
|
words = get_words(lexicon)
|
||||||
|
print(lexicon)
|
||||||
|
print(phones)
|
||||||
|
print(words)
|
||||||
|
lexicon_disambig, max_disambig = add_disambig_symbols(lexicon)
|
||||||
|
print(lexicon_disambig)
|
||||||
|
print("max disambig:", f"#{max_disambig}")
|
||||||
|
|
||||||
|
phones = ["<eps>", "SIL", "SPN"] + phones
|
||||||
|
for i in range(max_disambig + 1):
|
||||||
|
phones.append(f"#{i}")
|
||||||
|
words = ["<eps>"] + words
|
||||||
|
|
||||||
|
phone2id = generate_id_map(phones)
|
||||||
|
word2id = generate_id_map(words)
|
||||||
|
|
||||||
|
print(phone2id)
|
||||||
|
print(word2id)
|
||||||
|
|
||||||
|
write_mapping("phones.txt", phone2id)
|
||||||
|
write_mapping("words.txt", word2id)
|
||||||
|
|
||||||
|
write_lexicon("a.txt", lexicon)
|
||||||
|
write_lexicon("a_disambig.txt", lexicon_disambig)
|
||||||
|
|
||||||
|
fsa = lexicon_to_fst(lexicon, phone2id=phone2id, word2id=word2id)
|
||||||
|
fsa.labels_sym = k2.SymbolTable.from_file("phones.txt")
|
||||||
|
fsa.aux_labels_sym = k2.SymbolTable.from_file("words.txt")
|
||||||
|
fsa.draw("L.pdf", title="L")
|
||||||
|
|
||||||
|
fsa_disambig = lexicon_to_fst(lexicon_disambig, phone2id=phone2id, word2id=word2id)
|
||||||
|
fsa_disambig.labels_sym = k2.SymbolTable.from_file("phones.txt")
|
||||||
|
fsa_disambig.aux_labels_sym = k2.SymbolTable.from_file("words.txt")
|
||||||
|
fsa_disambig.draw("L_disambig.pdf", title="L_disambig")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
filename = generate_lexicon_file()
|
||||||
|
test_read_lexicon(filename)
|
||||||
|
os.remove(filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
97
egs/LJSpeech/ASR/local/train_bpe_model.py
Executable file
@ -0,0 +1,97 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2021 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
# You can install sentencepiece via:
|
||||||
|
#
|
||||||
|
# pip install sentencepiece
|
||||||
|
#
|
||||||
|
# Due to an issue reported in
|
||||||
|
# https://github.com/google/sentencepiece/pull/642#issuecomment-857972030
|
||||||
|
#
|
||||||
|
# Please install a version >=0.1.96
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--lang-dir",
|
||||||
|
type=str,
|
||||||
|
help="""Input and output directory.
|
||||||
|
The generated bpe.model is saved to this directory.
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--transcript",
|
||||||
|
type=str,
|
||||||
|
help="Training transcript.",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--vocab-size",
|
||||||
|
type=int,
|
||||||
|
help="Vocabulary size for BPE training",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
vocab_size = args.vocab_size
|
||||||
|
lang_dir = Path(args.lang_dir)
|
||||||
|
|
||||||
|
model_type = "unigram"
|
||||||
|
|
||||||
|
model_prefix = f"{lang_dir}/{model_type}_{vocab_size}"
|
||||||
|
train_text = args.transcript
|
||||||
|
character_coverage = 1.0
|
||||||
|
input_sentence_size = 100000000
|
||||||
|
|
||||||
|
user_defined_symbols = ["<blk>", "<sos/eos>"]
|
||||||
|
unk_id = len(user_defined_symbols)
|
||||||
|
# Note: unk_id is fixed to 2.
|
||||||
|
# If you change it, you should also change other
|
||||||
|
# places that are using it.
|
||||||
|
|
||||||
|
model_file = Path(model_prefix + ".model")
|
||||||
|
if not model_file.is_file():
|
||||||
|
spm.SentencePieceTrainer.train(
|
||||||
|
input=train_text,
|
||||||
|
vocab_size=vocab_size,
|
||||||
|
model_type=model_type,
|
||||||
|
model_prefix=model_prefix,
|
||||||
|
input_sentence_size=input_sentence_size,
|
||||||
|
character_coverage=character_coverage,
|
||||||
|
user_defined_symbols=user_defined_symbols,
|
||||||
|
unk_id=unk_id,
|
||||||
|
bos_id=-1,
|
||||||
|
eos_id=-1,
|
||||||
|
)
|
||||||
|
|
||||||
|
shutil.copyfile(model_file, f"{lang_dir}/bpe.model")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
77
egs/LJSpeech/ASR/local/validate_bpe_lexicon.py
Executable file
@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2022 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""
|
||||||
|
This script checks that there are no OOV tokens in the BPE-based lexicon.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
python3 ./local/validate_bpe_lexicon.py \
|
||||||
|
--lexicon /path/to/lexicon.txt \
|
||||||
|
--bpe-model /path/to/bpe.model
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
import sentencepiece as spm
|
||||||
|
|
||||||
|
from icefall.lexicon import read_lexicon
|
||||||
|
|
||||||
|
# Map word to word pieces
|
||||||
|
Lexicon = List[Tuple[str, List[str]]]
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--lexicon",
|
||||||
|
required=True,
|
||||||
|
type=Path,
|
||||||
|
help="Path to lexicon.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--bpe-model",
|
||||||
|
required=True,
|
||||||
|
type=Path,
|
||||||
|
help="Path to bpe.model",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
assert args.lexicon.is_file(), args.lexicon
|
||||||
|
assert args.bpe_model.is_file(), args.bpe_model
|
||||||
|
|
||||||
|
lexicon = read_lexicon(args.lexicon)
|
||||||
|
|
||||||
|
sp = spm.SentencePieceProcessor()
|
||||||
|
sp.load(str(args.bpe_model))
|
||||||
|
|
||||||
|
word_pieces = set(sp.id_to_piece(list(range(sp.vocab_size()))))
|
||||||
|
for word, pieces in lexicon:
|
||||||
|
for p in pieces:
|
||||||
|
if p not in word_pieces:
|
||||||
|
raise ValueError(f"The word {word} contains an OOV token {p}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
93
egs/LJSpeech/ASR/local/validate_manifest.py
Executable file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright 2022 Xiaomi Corp. (authors: Fangjun Kuang)
|
||||||
|
#
|
||||||
|
# See ../../../../LICENSE for clarification regarding multiple authors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
"""
|
||||||
|
This script checks the following assumptions of the generated manifest:
|
||||||
|
|
||||||
|
- Single supervision per cut
|
||||||
|
- Supervision time bounds are within cut time bounds
|
||||||
|
|
||||||
|
We will add more checks later if needed.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
|
||||||
|
python3 ./local/validate_manifest.py \
|
||||||
|
./data/fbank/librispeech_cuts_train-clean-100.jsonl.gz
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from lhotse import CutSet, load_manifest_lazy
|
||||||
|
from lhotse.cut import Cut
|
||||||
|
|
||||||
|
|
||||||
|
def get_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"manifest",
|
||||||
|
type=Path,
|
||||||
|
help="Path to the manifest file",
|
||||||
|
)
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_one_supervision_per_cut(c: Cut):
|
||||||
|
if len(c.supervisions) != 1:
|
||||||
|
raise ValueError(f"{c.id} has {len(c.supervisions)} supervisions")
|
||||||
|
|
||||||
|
|
||||||
|
def validate_supervision_and_cut_time_bounds(c: Cut):
|
||||||
|
s = c.supervisions[0]
|
||||||
|
if s.start < c.start:
|
||||||
|
raise ValueError(
|
||||||
|
f"{c.id}: Supervision start time {s.start} is less "
|
||||||
|
f"than cut start time {c.start}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if s.end > c.end:
|
||||||
|
raise ValueError(
|
||||||
|
f"{c.id}: Supervision end time {s.end} is larger "
|
||||||
|
f"than cut end time {c.end}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = get_args()
|
||||||
|
|
||||||
|
manifest = args.manifest
|
||||||
|
logging.info(f"Validating {manifest}")
|
||||||
|
|
||||||
|
assert manifest.is_file(), f"{manifest} does not exist"
|
||||||
|
cut_set = load_manifest_lazy(manifest)
|
||||||
|
|
||||||
|
assert isinstance(cut_set, CutSet)
|
||||||
|
|
||||||
|
for c in cut_set:
|
||||||
|
validate_one_supervision_per_cut(c)
|
||||||
|
validate_supervision_and_cut_time_bounds(c)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
|
||||||
|
|
||||||
|
logging.basicConfig(format=formatter, level=logging.INFO)
|
||||||
|
|
||||||
|
main()
|
||||||
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_0.png
Executable file
|
After Width: | Height: | Size: 30 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_1.png
Executable file
|
After Width: | Height: | Size: 27 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_2.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_3.png
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_4.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_5.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_6.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_7.png
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_8.png
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_org/plot_9.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_0.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_1.png
Executable file
|
After Width: | Height: | Size: 27 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_2.png
Executable file
|
After Width: | Height: | Size: 28 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_3.png
Executable file
|
After Width: | Height: | Size: 25 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_4.png
Executable file
|
After Width: | Height: | Size: 23 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_5.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_6.png
Executable file
|
After Width: | Height: | Size: 22 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_7.png
Executable file
|
After Width: | Height: | Size: 21 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_8.png
Executable file
|
After Width: | Height: | Size: 19 KiB |
BIN
egs/LJSpeech/ASR/outputs/bpe_histogram_pseudo/plot_9.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
egs/LJSpeech/ASR/outputs/density_plot.png
Executable file
|
After Width: | Height: | Size: 24 KiB |
BIN
egs/LJSpeech/ASR/outputs/phone_histogram_org/plot.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
egs/LJSpeech/ASR/outputs/phone_histogram_pseudo/plot.png
Executable file
|
After Width: | Height: | Size: 18 KiB |
BIN
egs/LJSpeech/ASR/outputs/word_histogram_org/plot.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
egs/LJSpeech/ASR/outputs/word_histogram_pseudo/plot.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.bias/abs_diff.png
Executable file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.bias/cos_sim.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.bias/rel_diff.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.weight/abs_diff.png
Executable file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.weight/cos_sim.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/ctc_output.1.weight/rel_diff.png
Executable file
|
After Width: | Height: | Size: 11 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.conv.weight/abs_diff.png
Executable file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.conv.weight/cos_sim.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.conv.weight/rel_diff.png
Executable file
|
After Width: | Height: | Size: 10 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.embedding.weight/abs_diff.png
Executable file
|
After Width: | Height: | Size: 14 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.embedding.weight/cos_sim.png
Executable file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
egs/LJSpeech/ASR/outputs_sim/decoder.embedding.weight/rel_diff.png
Executable file
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 12 KiB |