From 60bd7f202e3415a938162a0256eea34a73e966cf Mon Sep 17 00:00:00 2001 From: jiamingkong Date: Mon, 22 May 2023 17:24:05 +0800 Subject: [PATCH] Code clean up according to comments in https://github.com/PaddlePaddle/PaddleSpeech/pull/3242 --- examples/librispeech/asr5/README.md | 40 +- examples/librispeech/asr5/RESULTS.md | 6 +- examples/librispeech/asr5/avg_model.py | 18 - examples/librispeech/asr5/test.profile | Bin 182839 -> 0 bytes paddlespeech/s2t/exps/wavlm/bin/test.py | 46 +- paddlespeech/s2t/exps/wavlm/bin/train.py | 48 +- .../s2t/models/wavlm/modules/conv_layers.py | 0 .../s2t/models/wavlm/processing/__init__.py | 0 .../wavlm/processing/signal_processing.py | 251 ----- .../wavlm/processing/speech_augmentation.py | 901 ------------------ 10 files changed, 25 insertions(+), 1285 deletions(-) delete mode 100644 examples/librispeech/asr5/avg_model.py delete mode 100644 examples/librispeech/asr5/test.profile delete mode 100644 paddlespeech/s2t/models/wavlm/modules/conv_layers.py delete mode 100644 paddlespeech/s2t/models/wavlm/processing/__init__.py delete mode 100644 paddlespeech/s2t/models/wavlm/processing/signal_processing.py delete mode 100644 paddlespeech/s2t/models/wavlm/processing/speech_augmentation.py diff --git a/examples/librispeech/asr5/README.md b/examples/librispeech/asr5/README.md index 064a7f16..c684ca54 100644 --- a/examples/librispeech/asr5/README.md +++ b/examples/librispeech/asr5/README.md @@ -1,5 +1,5 @@ -# Hubert2ASR with Librispeech -This example contains code used to finetune [hubert](https://arxiv.org/abs/2106.07447) model with [Librispeech dataset](http://www.openslr.org/resources/12) +# WavLM2ASR with Librispeech +This example contains code used to finetune [WavLM](https://arxiv.org/abs/2110.13900) model with [Librispeech dataset](http://www.openslr.org/resources/12) ## Overview All the scripts you need are in `run.sh`. There are several stages in `run.sh`, and each stage has its function. | Stage | Function | @@ -42,7 +42,7 @@ Some local variables are set in `run.sh`. `conf_path` denotes the config path of the model. `avg_num` denotes the number K of top-K models you want to average to get the final model. `audio file` denotes the file path of the single file you want to infer in stage 5 -`ckpt` denotes the checkpoint prefix of the model, e.g. "hubertASR" +`ckpt` denotes the checkpoint prefix of the model, e.g. "WavLMASR" You can set the local variables (except `ckpt`) when you use `run.sh` @@ -89,10 +89,10 @@ data/ `-- train.meta ``` -Stage 0 also downloads the pre-trained [hubert](https://paddlespeech.bj.bcebos.com/hubert/hubert-large-lv60.pdparams) model. +Stage 0 also downloads the pre-trained [wavlm](https://paddlespeech.bj.bcebos.com/wavlm/wavlm-base-plus.pdparams) model. ```bash -mkdir -p exp/hubert -wget -P exp/hubert https://paddlespeech.bj.bcebos.com/hubert/hubert-large-lv60.pdparams +mkdir -p exp/wavlm +wget -P exp/wavlm https://paddlespeech.bj.bcebos.com/wavlm/wavlm-base-plus.pdparams ``` ## Stage 1: Model Training If you want to train the model. you can use stage 1 in `run.sh`. The code is shown below. @@ -111,10 +111,10 @@ or you can run these scripts in the command line (only use CPU). . ./path.sh . ./cmd.sh bash ./local/data.sh -CUDA_VISIBLE_DEVICES= ./local/train.sh conf/hubertASR.yaml hubertASR +CUDA_VISIBLE_DEVICES= ./local/train.sh conf/wavlmASR.yaml wavlmASR ``` ## Stage 2: Top-k Models Averaging -After training the model, we need to get the final model for testing and inference. In every epoch, the model checkpoint is saved, so we can choose the best model from them based on the validation loss or we can sort them and average the parameters of the top-k models to get the final model. We can use stage 2 to do this, and the code is shown below. Note: We only train one epoch for hubertASR, thus the `avg_num` is set to 1. +After training the model, we need to get the final model for testing and inference. In every epoch, the model checkpoint is saved, so we can choose the best model from them based on the validation loss or we can sort them and average the parameters of the top-k models to get the final model. We can use stage 2 to do this, and the code is shown below. Note: We only train one epoch for wavlmASR, thus the `avg_num` is set to 1. ```bash if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then # avg n best model @@ -132,8 +132,8 @@ or you can run these scripts in the command line (only use CPU). . ./path.sh . ./cmd.sh bash ./local/data.sh -CUDA_VISIBLE_DEVICES= ./local/train.sh conf/hubertASR.yaml hubertASR -avg.sh best exp/hubertASR/checkpoints 1 +CUDA_VISIBLE_DEVICES= ./local/train.sh conf/wavlmASR.yaml wavlmASR +avg.sh best exp/wavlmASR/checkpoints 1 ``` ## Stage 3: Model Testing The test stage is to evaluate the model performance. The code of test stage is shown below: @@ -152,24 +152,24 @@ or you can run these scripts in the command line (only use CPU). . ./path.sh . ./cmd.sh bash ./local/data.sh -CUDA_VISIBLE_DEVICES= ./local/train.sh conf/hubertASR.yaml hubertASR -avg.sh best exp/hubertASR/checkpoints 1 -CUDA_VISIBLE_DEVICES= ./local/test.sh conf/hubertASR.yaml conf/tuning/decode.yaml exp/hubertASR/checkpoints/avg_1 +CUDA_VISIBLE_DEVICES= ./local/train.sh conf/wavlmASR.yaml wavlmASR +avg.sh best exp/wavlmASR/checkpoints 1 +CUDA_VISIBLE_DEVICES= ./local/test.sh conf/wavlmASR.yaml conf/tuning/decode.yaml exp/wavlmASR/checkpoints/avg_1 ``` ## Pretrained Model -You can get the pretrained hubertASR from [this](../../../docs/source/released_model.md). +You can get the pretrained wavlmASR from [this](../../../docs/source/released_model.md). using the `tar` scripts to unpack the model and then you can use the script to test the model. For example: ```bash -wget https://paddlespeech.bj.bcebos.com/hubert/hubertASR-large-100h-librispeech_ckpt_1.4.0.model.tar.gz -tar xzvf hubertASR-large-100h-librispeech_ckpt_1.4.0.model.tar.gz +wget https://paddlespeech.bj.bcebos.com/wavlm/wavlmASR-base-100h-librispeech_ckpt_1.4.0.model.tar.gz +tar xzvf wavlmASR-base-100h-librispeech_ckpt_1.4.0.model.tar.gz source path.sh # If you have process the data and get the manifest fileļ¼Œ you can skip the following 2 steps bash local/data.sh --stage -1 --stop_stage -1 bash local/data.sh --stage 2 --stop_stage 2 -CUDA_VISIBLE_DEVICES= ./local/test.sh conf/hubertASR.yaml conf/tuning/decode.yaml exp/hubertASR/checkpoints/avg_1 +CUDA_VISIBLE_DEVICES= ./local/test.sh conf/wavlmASR.yaml conf/tuning/decode.yaml exp/wavlmASR/checkpoints/avg_1 ``` The performance of the released models are shown in [here](./RESULTS.md). @@ -184,8 +184,8 @@ In some situations, you want to use the trained model to do the inference for th ``` you can train the model by yourself using ```bash run.sh --stage 0 --stop_stage 3```, or you can download the pretrained model through the script below: ```bash -wget https://paddlespeech.bj.bcebos.com/hubert/hubertASR-large-100h-librispeech_ckpt_1.4.0.model.tar.gz -tar xzvf hubertASR-large-100h-librispeech_ckpt_1.4.0.model.tar.gz +wget https://paddlespeech.bj.bcebos.com/wavlm/wavlmASR-base-100h-librispeech_ckpt_1.4.0.model.tar.gz +tar xzvf wavlmASR-base-100h-librispeech_ckpt_1.4.0.model.tar.gz ``` You can download the audio demo: ```bash @@ -193,5 +193,5 @@ wget -nc https://paddlespeech.bj.bcebos.com/datasets/single_wav/en/demo_002_en.w ``` You need to prepare an audio file or use the audio demo above, please confirm the sample rate of the audio is 16K. You can get the result of the audio demo by running the script below. ```bash -CUDA_VISIBLE_DEVICES= ./local/test_wav.sh conf/hubertASR.yaml conf/tuning/decode.yaml exp/hubertASR/checkpoints/avg_1 data/demo_002_en.wav +CUDA_VISIBLE_DEVICES= ./local/test_wav.sh conf/wavlmASR.yaml conf/tuning/decode.yaml exp/wavlmASR/checkpoints/avg_1 data/demo_002_en.wav ``` diff --git a/examples/librispeech/asr5/RESULTS.md b/examples/librispeech/asr5/RESULTS.md index 81ce6ee9..806b39a1 100644 --- a/examples/librispeech/asr5/RESULTS.md +++ b/examples/librispeech/asr5/RESULTS.md @@ -1,9 +1,9 @@ # LibriSpeech -## hubertASR +## WavLMASR Fintuning on train-clean-100 -train: Epoch 3, 1*V100-32G, batchsize: 4, accum_grad: 8 +train: Epoch 16, 4*A800-80G, batchsize: 16, accum_grad: 8 | Model | Params | Config | Augmentation| Test set | Decode method | WER | | --- | --- | --- | --- | --- | --- | --- | -| hubertASR | 326.16M | conf/hubertASR.yaml | spec_aug | test-clean | greedy search | 0.05868 | +| WavLMASR | 326.16M | conf/wavlmasr.yaml | spec_aug | test-clean | greedy search | 0.0561 | diff --git a/examples/librispeech/asr5/avg_model.py b/examples/librispeech/asr5/avg_model.py deleted file mode 100644 index 039ea626..00000000 --- a/examples/librispeech/asr5/avg_model.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. -# -# 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. -from paddlespeech.dataset.s2t import avg_ckpts_main - -if __name__ == '__main__': - avg_ckpts_main() diff --git a/examples/librispeech/asr5/test.profile b/examples/librispeech/asr5/test.profile deleted file mode 100644 index c22dacf89e2b55adc3562bf38f63566141e1ec7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182839 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 Mh_=uc!n$<%f1H_O$p8QV diff --git a/paddlespeech/s2t/exps/wavlm/bin/test.py b/paddlespeech/s2t/exps/wavlm/bin/test.py index 8bb0421d..f56b418b 100644 --- a/paddlespeech/s2t/exps/wavlm/bin/test.py +++ b/paddlespeech/s2t/exps/wavlm/bin/test.py @@ -18,51 +18,7 @@ from yacs.config import CfgNode from paddlespeech.s2t.exps.wavlm.model import WavLMASRTester as Tester from paddlespeech.s2t.training.cli import default_argument_parser -# from paddlespeech.utils.argparse import print_arguments -import distutils.util - -def add_arguments(argname, type, default, help, argparser, **kwargs): - """Add argparse's argument. - - Usage: - - .. code-block:: python - - parser = argparse.ArgumentParser() - add_argument("name", str, "Jonh", "User name.", parser) - args = parser.parse_args() - """ - type = distutils.util.strtobool if type == bool else type - argparser.add_argument( - "--" + argname, - default=default, - type=type, - help=help + ' Default: %(default)s.', - **kwargs) - -def print_arguments(args, info=None): - """Print argparse's arguments. - - Usage: - - .. code-block:: python - - parser = argparse.ArgumentParser() - parser.add_argument("name", default="Jonh", type=str, help="User name.") - args = parser.parse_args() - print_arguments(args) - - :param args: Input argparse.Namespace for printing. - :type args: argparse.Namespace - """ - filename = "" - if info: - filename = info["__file__"] - filename = os.path.basename(filename) - print(f"----------- {filename} Configuration Arguments -----------") - for arg, value in sorted(vars(args).items()): - print("%s: %s" % (arg, value)) - print("-----------------------------------------------------------") +from paddlespeech.utils.argparse import print_arguments, add_arguments def main_sp(config, args): diff --git a/paddlespeech/s2t/exps/wavlm/bin/train.py b/paddlespeech/s2t/exps/wavlm/bin/train.py index 20e7455f..4ad966b7 100644 --- a/paddlespeech/s2t/exps/wavlm/bin/train.py +++ b/paddlespeech/s2t/exps/wavlm/bin/train.py @@ -19,53 +19,7 @@ from yacs.config import CfgNode from paddlespeech.s2t.exps.wavlm.model import WavLMASRTrainer as Trainer from paddlespeech.s2t.training.cli import default_argument_parser -# from paddlespeech.utils.argparse import print_arguments - -import distutils.util - -def add_arguments(argname, type, default, help, argparser, **kwargs): - """Add argparse's argument. - - Usage: - - .. code-block:: python - - parser = argparse.ArgumentParser() - add_argument("name", str, "Jonh", "User name.", parser) - args = parser.parse_args() - """ - type = distutils.util.strtobool if type == bool else type - argparser.add_argument( - "--" + argname, - default=default, - type=type, - help=help + ' Default: %(default)s.', - **kwargs) - -def print_arguments(args, info=None): - """Print argparse's arguments. - - Usage: - - .. code-block:: python - - parser = argparse.ArgumentParser() - parser.add_argument("name", default="Jonh", type=str, help="User name.") - args = parser.parse_args() - print_arguments(args) - - :param args: Input argparse.Namespace for printing. - :type args: argparse.Namespace - """ - filename = "" - if info: - filename = info["__file__"] - filename = os.path.basename(filename) - print(f"----------- {filename} Configuration Arguments -----------") - for arg, value in sorted(vars(args).items()): - print("%s: %s" % (arg, value)) - print("-----------------------------------------------------------") - +from paddlespeech.utils.argparse import print_arguments, add_arguments def main_sp(config, args): diff --git a/paddlespeech/s2t/models/wavlm/modules/conv_layers.py b/paddlespeech/s2t/models/wavlm/modules/conv_layers.py deleted file mode 100644 index e69de29b..00000000 diff --git a/paddlespeech/s2t/models/wavlm/processing/__init__.py b/paddlespeech/s2t/models/wavlm/processing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/paddlespeech/s2t/models/wavlm/processing/signal_processing.py b/paddlespeech/s2t/models/wavlm/processing/signal_processing.py deleted file mode 100644 index 7267e221..00000000 --- a/paddlespeech/s2t/models/wavlm/processing/signal_processing.py +++ /dev/null @@ -1,251 +0,0 @@ -# Copyright (c) 2023 speechbrain Authors. All Rights Reserved. -# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. -# -# 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. -# Modified from speechbrain 2023 (https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/processing/signal_processing.py) -""" -Low level signal processing utilities -Authors - * Peter Plantinga 2020 - * Francois Grondin 2020 - * William Aris 2020 - * Samuele Cornell 2020 - * Sarthak Yadav 2022 -""" -import numpy as np -import paddle - - -def blackman_window(window_length, periodic=True): - """Blackman window function. - Arguments - --------- - window_length : int - Controlling the returned window size. - periodic : bool - Determines whether the returned window trims off the - last duplicate value from the symmetric window - - Returns - ------- - A 1-D tensor of size (window_length) containing the window - """ - if window_length == 0: - return [] - if window_length == 1: - return paddle.ones([1]) - if periodic: - window_length += 1 - window = paddle.arange(window_length) * (np.pi / (window_length - 1)) - window = 0.08 * paddle.cos(window * 4) - 0.5 * paddle.cos(window * 2) + 0.42 - return window[:-1] if periodic else window - - -def compute_amplitude(waveforms, lengths=None, amp_type="avg", scale="linear"): - """Compute amplitude of a batch of waveforms. - Arguments - --------- - waveform : tensor - The waveforms used for computing amplitude. - Shape should be `[time]` or `[batch, time]` or - `[batch, time, channels]`. - lengths : tensor - The lengths of the waveforms excluding the padding. - Shape should be a single dimension, `[batch]`. - amp_type : str - Whether to compute "avg" average or "peak" amplitude. - Choose between ["avg", "peak"]. - scale : str - Whether to compute amplitude in "dB" or "linear" scale. - Choose between ["linear", "dB"]. - Returns - ------- - The average amplitude of the waveforms. - Example - ------- - >>> signal = paddle.sin(paddle.arange(16000.0)).unsqueeze(0) - >>> compute_amplitude(signal, signal.size(1)) - tensor([[0.6366]]) - """ - if len(waveforms.shape) == 1: - waveforms = waveforms.unsqueeze(0) - - assert amp_type in ["avg", "peak"] - assert scale in ["linear", "dB"] - - if amp_type == "avg": - if lengths is None: - out = paddle.mean(paddle.abs(waveforms), axis=1, keepdim=True) - else: - wav_sum = paddle.sum(paddle.abs(waveforms), axis=1, keepdim=True) - out = wav_sum / lengths - elif amp_type == "peak": - out = paddle.max(paddle.abs(waveforms), axis=1, keepdim=True)[0] - else: - raise NotImplementedError - - if scale == "linear": - return out - elif scale == "dB": - return paddle.clip(20 * paddle.log10(out), min=-80) # clamp zeros - else: - raise NotImplementedError - - -def convolve1d( - waveform, - kernel, - padding=0, - pad_type="constant", - stride=1, - groups=1, - use_fft=False, - rotation_index=0, ): - """Use paddle.nn.functional to perform 1d padding and conv. - Arguments - --------- - waveform : tensor - The tensor to perform operations on. - kernel : tensor - The filter to apply during convolution. - padding : int or tuple - The padding (pad_left, pad_right) to apply. - If an integer is passed instead, this is passed - to the conv1d function and pad_type is ignored. - pad_type : str - The type of padding to use. Passed directly to - `paddle.nn.functional.pad`, see Paddle documentation - for available options. - stride : int - The number of units to move each time convolution is applied. - Passed to conv1d. Has no effect if `use_fft` is True. - groups : int - This option is passed to `conv1d` to split the input into groups for - convolution. Input channels should be divisible by the number of groups. - use_fft : bool - When `use_fft` is passed `True`, then compute the convolution in the - spectral domain using complex multiply. This is more efficient on CPU - when the size of the kernel is large (e.g. reverberation). WARNING: - Without padding, circular convolution occurs. This makes little - difference in the case of reverberation, but may make more difference - with different kernels. - rotation_index : int - This option only applies if `use_fft` is true. If so, the kernel is - rolled by this amount before convolution to shift the output location. - Returns - ------- - The convolved waveform. - Example - ------- - >>> from speechbrain.dataio.dataio import read_audio - >>> signal = read_audio('tests/samples/single-mic/example1.wav') - >>> signal = signal.unsqueeze(0).unsqueeze(2) - >>> kernel = paddle.rand([1, 10, 1]) - >>> signal = convolve1d(signal, kernel, padding=(9, 0)) - """ - if len(waveform.shape) != 3: - raise ValueError("Convolve1D expects a 3-dimensional tensor") - - # Move time dimension last, which pad and fft and conv expect. - waveform = waveform.transpose([0, 2, 1]) - kernel = kernel.transpose([0, 2, 1]) - # Padding can be a tuple (left_pad, right_pad) or an int - if isinstance(padding, tuple): - waveform = paddle.nn.functional.pad( - x=waveform, pad=padding, mode=pad_type, data_format='NCL') - - # This approach uses FFT, which is more efficient if the kernel is large - if use_fft: - # Pad kernel to same length as signal, ensuring correct alignment - zero_length = waveform.shape[-1] - kernel.shape[-1] - - # Handle case where signal is shorter - if zero_length < 0: - kernel = kernel[..., :zero_length] - zero_length = 0 - - # Perform rotation to ensure alignment - zeros = paddle.zeros( - [kernel.shape[0], kernel.shape[1], zero_length], dtype=kernel.dtype) - after_index = kernel[..., rotation_index:] - before_index = kernel[..., :rotation_index] - kernel = paddle.concat((after_index, zeros, before_index), axis=-1) - - # Multiply in frequency domain to convolve in time domain - import paddle.fft as fft - - result = fft.rfft(waveform) * fft.rfft(kernel) - convolved = fft.irfft(result, n=waveform.shape[-1]) - - # Use the implementation given by paddle, which should be efficient on GPU - else: - convolved = paddle.nn.functional.conv1d( - x=waveform, - weight=kernel, - stride=stride, - groups=groups, - padding=padding if not isinstance(padding, tuple) else 0, ) - - # Return time dimension to the second dimension. - return convolved.transpose([0, 2, 1]) - - -def notch_filter(notch_freq, filter_width=101, notch_width=0.05): - """Returns a notch filter constructed from a high-pass and low-pass filter. - (from https://tomroelandts.com/articles/ - how-to-create-simple-band-pass-and-band-reject-filters) - Arguments - --------- - notch_freq : float - frequency to put notch as a fraction of the - sampling rate / 2. The range of possible inputs is 0 to 1. - filter_width : int - Filter width in samples. Longer filters have - smaller transition bands, but are more inefficient. - notch_width : float - Width of the notch, as a fraction of the sampling_rate / 2. - """ - - # Check inputs - assert 0 < notch_freq <= 1 - assert filter_width % 2 != 0 - pad = filter_width // 2 - inputs = paddle.arange(filter_width) - pad - - # Avoid frequencies that are too low - notch_freq += notch_width - - # Define sinc function, avoiding division by zero - def sinc(x): - "Computes the sinc function." - - def _sinc(x): - return paddle.sin(x) / x - - # The zero is at the middle index - return paddle.concat( - [_sinc(x[:pad]), paddle.ones([1]), _sinc(x[pad + 1:])]) - - # Compute a low-pass filter with cutoff frequency notch_freq. - hlpf = sinc(3 * (notch_freq - notch_width) * inputs) - hlpf *= blackman_window(filter_width) - hlpf /= paddle.sum(hlpf) - - # Compute a high-pass filter with cutoff frequency notch_freq. - hhpf = sinc(3 * (notch_freq + notch_width) * inputs) - hhpf *= blackman_window(filter_width) - hhpf /= -paddle.sum(hhpf) - hhpf[pad] += 1 - - # Adding filters creates notch filter - return (hlpf + hhpf).view(1, -1, 1) diff --git a/paddlespeech/s2t/models/wavlm/processing/speech_augmentation.py b/paddlespeech/s2t/models/wavlm/processing/speech_augmentation.py deleted file mode 100644 index ee5c51f5..00000000 --- a/paddlespeech/s2t/models/wavlm/processing/speech_augmentation.py +++ /dev/null @@ -1,901 +0,0 @@ -# Copyright (c) 2023 speechbrain Authors. All Rights Reserved. -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# 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. -# Modified from speechbrain(https://github.com/speechbrain/speechbrain/blob/develop/speechbrain/processing/speech_augmentation.py) -"""Classes for mutating speech data for data augmentation. -This module provides classes that produce realistic distortions of speech -data for the purpose of training speech processing models. The list of -distortions includes adding noise, adding reverberation, changing speed, -and more. All the classes are of type `torch.nn.Module`. This gives the -possibility to have end-to-end differentiability and -backpropagate the gradient through them. In addition, all operations -are expected to be performed on the GPU (where available) for efficiency. - -Authors - * Peter Plantinga 2020 -""" -import math - -import paddle -import paddle.nn as nn - -from .signal_processing import compute_amplitude -from .signal_processing import convolve1d -from .signal_processing import notch_filter - - -class SpeedPerturb(nn.Layer): - """Slightly speed up or slow down an audio signal. - Resample the audio signal at a rate that is similar to the original rate, - to achieve a slightly slower or slightly faster signal. This technique is - outlined in the paper: "Audio Augmentation for Speech Recognition" - Arguments - --------- - orig_freq : int - The frequency of the original signal. - speeds : list - The speeds that the signal should be changed to, as a percentage of the - original signal (i.e. `speeds` is divided by 100 to get a ratio). - perturb_prob : float - The chance that the batch will be speed- - perturbed. By default, every batch is perturbed. - Example - ------- - >>> from speechbrain.dataio.dataio import read_audio - >>> signal = read_audio('tests/samples/single-mic/example1.wav') - >>> perturbator = SpeedPerturb(orig_freq=16000, speeds=[90]) - >>> clean = signal.unsqueeze(0) - >>> perturbed = perturbator(clean) - >>> clean.shape - paddle.shape([1, 52173]) - >>> perturbed.shape - paddle.shape([1, 46956]) - """ - - def __init__( - self, - orig_freq, - speeds=[90, 100, 110], - perturb_prob=1.0, ): - super().__init__() - self.orig_freq = orig_freq - self.speeds = speeds - self.perturb_prob = perturb_prob - - # Initialize index of perturbation - self.samp_index = 0 - # Initialize resamplers - self.resamplers = [] - for speed in self.speeds: - config = { - "orig_freq": self.orig_freq, - "new_freq": self.orig_freq * speed // 100, - } - self.resamplers.append(Resample(**config)) - - def forward(self, waveform): - """ - Arguments - --------- - waveforms : tensor - Shape should be `[batch, time]` or `[batch, time, channels]`. - lengths : tensor - Shape should be a single dimension, `[batch]`. - Returns - ------- - Tensor of shape `[batch, time]` or `[batch, time, channels]`. - """ - - # Don't perturb (return early) 1-`perturb_prob` portion of the batches - if paddle.rand([1]) > self.perturb_prob: - return waveform.clone() - # Perform a random perturbation - self.samp_index = paddle.randint(len(self.speeds), shape=(1, ))[0] - perturbed_waveform = self.resamplers[self.samp_index](waveform) - - return perturbed_waveform - - -class Resample(nn.Layer): - """This class resamples an audio signal using sinc-based interpolation. - - It is a modification of the `resample` function from torchaudio - (https://pytorch.org/audio/stable/tutorials/audio_resampling_tutorial.html) - - Arguments - --------- - orig_freq : int - the sampling frequency of the input signal. - new_freq : int - the new sampling frequency after this operation is performed. - lowpass_filter_width : int - Controls the sharpness of the filter, larger numbers result in a - sharper filter, but they are less efficient. Values from 4 to 10 are allowed. - """ - - def __init__( - self, - orig_freq=16000, - new_freq=16000, - lowpass_filter_width=6, ): - super().__init__() - self.orig_freq = orig_freq - self.new_freq = new_freq - self.lowpass_filter_width = lowpass_filter_width - - # Compute rate for striding - self._compute_strides() - assert self.orig_freq % self.conv_stride == 0 - assert self.new_freq % self.conv_transpose_stride == 0 - - def _compute_strides(self): - """Compute the phases in polyphase filter. - - (almost directly from torchaudio.compliance.kaldi) - """ - - # Compute new unit based on ratio of in/out frequencies - base_freq = math.gcd(self.orig_freq, self.new_freq) - input_samples_in_unit = self.orig_freq // base_freq - self.output_samples = self.new_freq // base_freq - - # Store the appropriate stride based on the new units - self.conv_stride = input_samples_in_unit - self.conv_transpose_stride = self.output_samples - - def forward(self, waveforms): - """ - Arguments - --------- - waveforms : tensor - Shape should be `[batch, time]` or `[batch, time, channels]`. - lengths : tensor - Shape should be a single dimension, `[batch]`. - - Returns - ------- - Tensor of shape `[batch, time]` or `[batch, time, channels]`. - """ - - if not hasattr(self, "first_indices"): - self._indices_and_weights(waveforms) - - # Don't do anything if the frequencies are the same - if self.orig_freq == self.new_freq: - return waveforms - unsqueezed = False - if len(waveforms.shape) == 2: - waveforms = waveforms.unsqueeze(1) - unsqueezed = True - elif len(waveforms.shape) == 3: - waveforms = waveforms.transpose([0, 2, 1]) - else: - raise ValueError("Input must be 2 or 3 dimensions") - - # Do resampling - resampled_waveform = self._perform_resample(waveforms) - - if unsqueezed: - resampled_waveform = resampled_waveform.squeeze(1) - else: - resampled_waveform = resampled_waveform.transpose([0, 2, 1]) - - return resampled_waveform - - def _perform_resample(self, waveforms): - """Resamples the waveform at the new frequency. - - This matches Kaldi's OfflineFeatureTpl ResampleWaveform which uses a - LinearResample (resample a signal at linearly spaced intervals to - up/downsample a signal). LinearResample (LR) means that the output - signal is at linearly spaced intervals (i.e the output signal has a - frequency of `new_freq`). It uses sinc/bandlimited interpolation to - upsample/downsample the signal. - - (almost directly from torchaudio.compliance.kaldi) - - https://ccrma.stanford.edu/~jos/resample/ - Theory_Ideal_Bandlimited_Interpolation.html - - https://github.com/kaldi-asr/kaldi/blob/master/src/feat/resample.h#L56 - - Arguments - --------- - waveforms : tensor - The batch of audio signals to resample. - - Returns - ------- - The waveforms at the new frequency. - """ - - # Compute output size and initialize - batch_size, num_channels, wave_len = waveforms.shape - window_size = self.weights.shape[1] - tot_output_samp = self._output_samples(wave_len) - resampled_waveform = paddle.zeros( - (batch_size, num_channels, tot_output_samp)) - # self.weights = self.weights.to(waveforms.device) - - # Check weights are on correct device - # if waveforms.device != self.weights.device: - # self.weights = self.weights.to(waveforms.device) - - # eye size: (num_channels, num_channels, 1) - eye = paddle.eye(num_channels).unsqueeze(2) - - # Iterate over the phases in the polyphase filter - for i in range(self.first_indices.shape[0]): - wave_to_conv = waveforms - first_index = int(self.first_indices[i].item()) - if first_index >= 0: - # trim the signal as the filter will not be applied - # before the first_index - wave_to_conv = wave_to_conv[..., first_index:] - - # pad the right of the signal to allow partial convolutions - # meaning compute values for partial windows (e.g. end of the - # window is outside the signal length) - max_index = (tot_output_samp - 1) // self.output_samples - end_index = max_index * self.conv_stride + window_size - current_wave_len = wave_len - first_index - right_padding = max(0, end_index + 1 - current_wave_len) - left_padding = max(0, -first_index) - wave_to_conv = paddle.nn.functional.pad( - wave_to_conv, (left_padding, right_padding), data_format='NCL') - conv_wave = paddle.nn.functional.conv1d( - x=wave_to_conv, - weight=self.weights[i].repeat(num_channels, 1, 1), - stride=self.conv_stride, - groups=num_channels, ) - - # we want conv_wave[:, i] to be at - # output[:, i + n*conv_transpose_stride] - dilated_conv_wave = paddle.nn.functional.conv1d_transpose( - conv_wave, eye, stride=self.conv_transpose_stride) - - # pad dilated_conv_wave so it reaches the output length if needed. - left_padding = i - previous_padding = left_padding + dilated_conv_wave.shape[-1] - right_padding = max(0, tot_output_samp - previous_padding) - dilated_conv_wave = paddle.nn.functional.pad( - dilated_conv_wave, (left_padding, right_padding), - data_format='NCL') - dilated_conv_wave = dilated_conv_wave[..., :tot_output_samp] - - resampled_waveform += dilated_conv_wave - - return resampled_waveform - - def _output_samples(self, input_num_samp): - """Based on LinearResample::GetNumOutputSamples. - - LinearResample (LR) means that the output signal is at - linearly spaced intervals (i.e the output signal has a - frequency of ``new_freq``). It uses sinc/bandlimited - interpolation to upsample/downsample the signal. - - (almost directly from torchaudio.compliance.kaldi) - - Arguments - --------- - input_num_samp : int - The number of samples in each example in the batch. - - Returns - ------- - Number of samples in the output waveform. - """ - - # For exact computation, we measure time in "ticks" of 1.0 / tick_freq, - # where tick_freq is the least common multiple of samp_in and - # samp_out. - samp_in = int(self.orig_freq) - samp_out = int(self.new_freq) - - tick_freq = abs(samp_in * samp_out) // math.gcd(samp_in, samp_out) - ticks_per_input_period = tick_freq // samp_in - - # work out the number of ticks in the time interval - # [ 0, input_num_samp/samp_in ). - interval_length = input_num_samp * ticks_per_input_period - if interval_length <= 0: - return 0 - ticks_per_output_period = tick_freq // samp_out - - # Get the last output-sample in the closed interval, - # i.e. replacing [ ) with [ ]. Note: integer division rounds down. - # See http://en.wikipedia.org/wiki/Interval_(mathematics) for an - # explanation of the notation. - last_output_samp = interval_length // ticks_per_output_period - - # We need the last output-sample in the open interval, so if it - # takes us to the end of the interval exactly, subtract one. - if last_output_samp * ticks_per_output_period == interval_length: - last_output_samp -= 1 - - # First output-sample index is zero, so the number of output samples - # is the last output-sample plus one. - num_output_samp = last_output_samp + 1 - - return num_output_samp - - def _indices_and_weights(self, waveforms): - """Based on LinearResample::SetIndexesAndWeights - - Retrieves the weights for resampling as well as the indices in which - they are valid. LinearResample (LR) means that the output signal is at - linearly spaced intervals (i.e the output signal has a frequency - of ``new_freq``). It uses sinc/bandlimited interpolation to - upsample/downsample the signal. - - Returns - ------- - - the place where each filter should start being applied - - the filters to be applied to the signal for resampling - """ - - # Lowpass filter frequency depends on smaller of two frequencies - min_freq = min(self.orig_freq, self.new_freq) - lowpass_cutoff = 0.99 * 0.5 * min_freq - - assert lowpass_cutoff * 2 <= min_freq - window_width = self.lowpass_filter_width / (2.0 * lowpass_cutoff) - - assert lowpass_cutoff < min(self.orig_freq, self.new_freq) / 2 - output_t = paddle.arange(start=0.0, end=self.output_samples) - output_t /= self.new_freq - min_t = output_t - window_width - max_t = output_t + window_width - - min_input_index = paddle.ceil(min_t * self.orig_freq) - max_input_index = paddle.floor(max_t * self.orig_freq) - num_indices = max_input_index - min_input_index + 1 - - max_weight_width = num_indices.max() - j = paddle.arange(max_weight_width) - input_index = min_input_index.unsqueeze(1) + j.unsqueeze(0) - delta_t = (input_index / self.orig_freq) - output_t.unsqueeze(1) - - weights = paddle.zeros_like(delta_t) - - inside_window_indices = delta_t.abs() < (window_width) - # raised-cosine (Hanning) window with width `window_width` - weights[inside_window_indices] = 0.5 * (1 + paddle.cos( - 2 * math.pi * lowpass_cutoff / self.lowpass_filter_width * - delta_t[inside_window_indices])) - t_eq_zero_indices = delta_t == 0.0 - t_not_eq_zero_indices = ~t_eq_zero_indices - - # sinc filter function - weights[t_not_eq_zero_indices] *= paddle.sin( - 2 * math.pi * lowpass_cutoff * delta_t[t_not_eq_zero_indices]) / ( - math.pi * delta_t[t_not_eq_zero_indices]) - - # limit of the function at t = 0 - weights[t_eq_zero_indices] *= 2 * lowpass_cutoff - - # size (output_samples, max_weight_width) - weights /= self.orig_freq - - self.first_indices = min_input_index - self.weights = weights - - -class DropFreq(nn.Layer): - """This class drops a random frequency from the signal. - The purpose of this class is to teach models to learn to rely on all parts - of the signal, not just a few frequency bands. - Arguments - --------- - drop_freq_low : float - The low end of frequencies that can be dropped, - as a fraction of the sampling rate / 2. - drop_freq_high : float - The high end of frequencies that can be - dropped, as a fraction of the sampling rate / 2. - drop_count_low : int - The low end of number of frequencies that could be dropped. - drop_count_high : int - The high end of number of frequencies that could be dropped. - drop_width : float - The width of the frequency band to drop, as - a fraction of the sampling_rate / 2. - drop_prob : float - The probability that the batch of signals will have a frequency - dropped. By default, every batch has frequencies dropped. - Example - ------- - >>> from speechbrain.dataio.dataio import read_audio - >>> dropper = DropFreq() - >>> signal = read_audio('tests/samples/single-mic/example1.wav') - >>> dropped_signal = dropper(signal.unsqueeze(0)) - """ - - def __init__( - self, - drop_freq_low=1e-14, - drop_freq_high=1, - drop_count_low=1, - drop_count_high=2, - drop_width=0.05, - drop_prob=1, ): - super().__init__() - self.drop_freq_low = drop_freq_low - self.drop_freq_high = drop_freq_high - self.drop_count_low = drop_count_low - self.drop_count_high = drop_count_high - self.drop_width = drop_width - self.drop_prob = drop_prob - - def forward(self, waveforms): - """ - Arguments - --------- - waveforms : tensor - Shape should be `[batch, time]` or `[batch, time, channels]`. - Returns - ------- - Tensor of shape `[batch, time]` or `[batch, time, channels]`. - """ - - # Don't drop (return early) 1-`drop_prob` portion of the batches - dropped_waveform = waveforms.clone() - if paddle.rand([1]) > self.drop_prob: - return dropped_waveform - - # Add channels dimension - if len(waveforms.shape) == 2: - dropped_waveform = dropped_waveform.unsqueeze(-1) - - # Pick number of frequencies to drop - drop_count = paddle.randint( - low=self.drop_count_low, - high=self.drop_count_high + 1, - shape=(1, ), ) - - # Filter parameters - filter_length = 101 - pad = filter_length // 2 - - # Start with delta function - drop_filter = paddle.zeros([1, filter_length, 1]) - drop_filter[0, pad, 0] = 1 - - if drop_count.shape == 0: - # Pick a frequency to drop - drop_range = self.drop_freq_high - self.drop_freq_low - drop_frequency = ( - paddle.rand(drop_count) * drop_range + self.drop_freq_low) - # Subtract each frequency - for frequency in drop_frequency: - notch_kernel = notch_filter( - frequency, - filter_length, - self.drop_width, ) - drop_filter = convolve1d(drop_filter, notch_kernel, pad) - - # Apply filter - dropped_waveform = convolve1d(dropped_waveform, drop_filter, pad) - - # Remove channels dimension if added - return dropped_waveform.squeeze(-1) - - -class DropChunk(nn.Layer): - """This class drops portions of the input signal. - Using `DropChunk` as an augmentation strategy helps a models learn to rely - on all parts of the signal, since it can't expect a given part to be - present. - Arguments - --------- - drop_length_low : int - The low end of lengths for which to set the - signal to zero, in samples. - drop_length_high : int - The high end of lengths for which to set the - signal to zero, in samples. - drop_count_low : int - The low end of number of times that the signal - can be dropped to zero. - drop_count_high : int - The high end of number of times that the signal - can be dropped to zero. - drop_start : int - The first index for which dropping will be allowed. - drop_end : int - The last index for which dropping will be allowed. - drop_prob : float - The probability that the batch of signals will - have a portion dropped. By default, every batch - has portions dropped. - noise_factor : float - The factor relative to average amplitude of an utterance - to use for scaling the white noise inserted. 1 keeps - the average amplitude the same, while 0 inserts all 0's. - Example - ------- - >>> from speechbrain.dataio.dataio import read_audio - >>> dropper = DropChunk(drop_start=100, drop_end=200, noise_factor=0.) - >>> signal = read_audio('tests/samples/single-mic/example1.wav') - >>> signal = signal.unsqueeze(0) # [batch, time, channels] - >>> length = paddle.ones([1]) - >>> dropped_signal = dropper(signal, length) - >>> float(dropped_signal[:, 150]) - 0.0 - """ - - def __init__( - self, - drop_length_low=100, - drop_length_high=1000, - drop_count_low=1, - drop_count_high=10, - drop_start=0, - drop_end=None, - drop_prob=1, - noise_factor=0.0, ): - super().__init__() - self.drop_length_low = drop_length_low - self.drop_length_high = drop_length_high - self.drop_count_low = drop_count_low - self.drop_count_high = drop_count_high - self.drop_start = drop_start - self.drop_end = drop_end - self.drop_prob = drop_prob - self.noise_factor = noise_factor - - # Validate low < high - if drop_length_low > drop_length_high: - raise ValueError("Low limit must not be more than high limit") - if drop_count_low > drop_count_high: - raise ValueError("Low limit must not be more than high limit") - - # Make sure the length doesn't exceed end - start - if drop_end is not None and drop_end >= 0: - if drop_start > drop_end: - raise ValueError("Low limit must not be more than high limit") - - drop_range = drop_end - drop_start - self.drop_length_low = min(drop_length_low, drop_range) - self.drop_length_high = min(drop_length_high, drop_range) - - def forward(self, waveforms, lengths): - """ - Arguments - --------- - waveforms : tensor - Shape should be `[batch, time]` or `[batch, time, channels]`. - lengths : tensor - Shape should be a single dimension, `[batch]`. - Returns - ------- - Tensor of shape `[batch, time]` or - `[batch, time, channels]` - """ - - # Reading input list - lengths = (lengths * waveforms.shape[1]).long() - batch_size = waveforms.shape[0] - dropped_waveform = waveforms.clone() - - # Don't drop (return early) 1-`drop_prob` portion of the batches - if paddle.rand([1]) > self.drop_prob: - return dropped_waveform - - # Store original amplitude for computing white noise amplitude - clean_amplitude = compute_amplitude(waveforms, lengths.unsqueeze(1)) - - # Pick a number of times to drop - drop_times = paddle.randint( - low=self.drop_count_low, - high=self.drop_count_high + 1, - shape=(batch_size, ), ) - - # Iterate batch to set mask - for i in range(batch_size): - if drop_times[i] == 0: - continue - - # Pick lengths - length = paddle.randint( - low=self.drop_length_low, - high=self.drop_length_high + 1, - shape=(drop_times[i], ), ) - - # Compute range of starting locations - start_min = self.drop_start - if start_min < 0: - start_min += lengths[i] - start_max = self.drop_end - if start_max is None: - start_max = lengths[i] - if start_max < 0: - start_max += lengths[i] - start_max = max(0, start_max - length.max()) - - # Pick starting locations - start = paddle.randint( - low=start_min, - high=start_max + 1, - shape=(drop_times[i], ), ) - - end = start + length - - # Update waveform - if not self.noise_factor: - for j in range(drop_times[i]): - dropped_waveform[i, start[j]:end[j]] = 0.0 - else: - # Uniform distribution of -2 to +2 * avg amplitude should - # preserve the average for normalization - noise_max = 2 * clean_amplitude[i] * self.noise_factor - for j in range(drop_times[i]): - # zero-center the noise distribution - noise_vec = paddle.rand([length[j]]) - noise_vec = 2 * noise_max * noise_vec - noise_max - dropped_waveform[i, start[j]:end[j]] = noise_vec - - return dropped_waveform - - -class SpecAugment(paddle.nn.Layer): - """An implementation of the SpecAugment algorithm. - Reference: - https://arxiv.org/abs/1904.08779 - Arguments - --------- - time_warp : bool - Whether applying time warping. - time_warp_window : int - Time warp window. - time_warp_mode : str - Interpolation mode for time warping (default "bicubic"). - freq_mask : bool - Whether applying freq mask. - freq_mask_width : int or tuple - Freq mask width range. - n_freq_mask : int - Number of freq mask. - time_mask : bool - Whether applying time mask. - time_mask_width : int or tuple - Time mask width range. - n_time_mask : int - Number of time mask. - replace_with_zero : bool - If True, replace masked value with 0, else replace masked value with mean of the input tensor. - Example - ------- - >>> aug = SpecAugment() - >>> a = paddle.rand([8, 120, 80]) - >>> a = aug(a) - >>> print(a.shape) - paddle.Size([8, 120, 80]) - """ - - def __init__( - self, - time_warp=True, - time_warp_window=5, - time_warp_mode="bicubic", - freq_mask=True, - freq_mask_width=(0, 20), - n_freq_mask=2, - time_mask=True, - time_mask_width=(0, 100), - n_time_mask=2, - replace_with_zero=True, ): - super().__init__() - assert ( - time_warp or freq_mask or time_mask - ), "at least one of time_warp, time_mask, or freq_mask should be applied" - - self.apply_time_warp = time_warp - self.time_warp_window = time_warp_window - self.time_warp_mode = time_warp_mode - - self.freq_mask = freq_mask - if isinstance(freq_mask_width, int): - freq_mask_width = (0, freq_mask_width) - self.freq_mask_width = freq_mask_width - self.n_freq_mask = n_freq_mask - - self.time_mask = time_mask - if isinstance(time_mask_width, int): - time_mask_width = (0, time_mask_width) - self.time_mask_width = time_mask_width - self.n_time_mask = n_time_mask - - self.replace_with_zero = replace_with_zero - - def forward(self, x): - """Takes in input a tensors and returns an augmented one.""" - if self.apply_time_warp: - x = self.time_warp(x) - if self.freq_mask: - x = self.mask_along_axis(x, dim=2) - if self.time_mask: - x = self.mask_along_axis(x, dim=1) - return x - - def time_warp(self, x): - """Time warping with paddle.nn.functional.interpolate""" - original_size = x.shape - window = self.time_warp_window - - # 2d interpolation requires 4D or higher dimension tensors - # x: (Batch, Time, Freq) -> (Batch, 1, Time, Freq) - if x.dim() == 3: - x = x.unsqueeze(1) - - time = x.shape[2] - if time - window <= window: - return x.view(*original_size) - - # compute center and corresponding window - c = paddle.randint(window, time - window, (1, ))[0] - w = paddle.randint(c - window, c + window, (1, ))[0] + 1 - - left = paddle.nn.functional.interpolate( - x[:, :, :c], - (w, x.shape[3]), - mode=self.time_warp_mode, - align_corners=True, ) - right = paddle.nn.functional.interpolate( - x[:, :, c:], - (time - w, x.shape[3]), - mode=self.time_warp_mode, - align_corners=True, ) - - x[:, :, :w] = left - x[:, :, w:] = right - return x.view(*original_size) - - def mask_along_axis(self, x, dim): - """Mask along time or frequency axis. - Arguments - --------- - x : tensor - Input tensor. - dim : int - Corresponding dimension to mask. - """ - original_size = x.shape - if x.dim() == 4: - x = x.view(-1, x.shape[2], x.shape[3]) - - batch, time, fea = x.shape - - if dim == 1: - D = time - n_mask = self.n_time_mask - width_range = self.time_mask_width - else: - D = fea - n_mask = self.n_freq_mask - width_range = self.freq_mask_width - - mask_len = paddle.randint(width_range[0], width_range[1], - (batch, n_mask)).unsqueeze(2) - - mask_pos = paddle.randint(0, max(1, D - mask_len.max()), - (batch, n_mask)).unsqueeze(2) - - # compute masks - arange = paddle.arange(end=D).view(1, 1, -1) - mask = (mask_pos <= arange) * (arange < (mask_pos + mask_len)) - mask = mask.any(axis=1) - - if dim == 1: - mask = mask.unsqueeze(2) - else: - mask = mask.unsqueeze(1) - - if self.replace_with_zero: - val = 0.0 - else: - val = x.mean() - # same to x.masked_fill_(mask, val) - y = paddle.full(x.shape, val, x.dtype) - x = paddle.where(mask, y, x) - return x.view(*original_size) - - -class TimeDomainSpecAugment(nn.Layer): - """A time-domain approximation of the SpecAugment algorithm. - This augmentation module implements three augmentations in - the time-domain. - 1. Drop chunks of the audio (zero amplitude or white noise) - 2. Drop frequency bands (with band-drop filters) - 3. Speed peturbation (via resampling to slightly different rate) - Arguments - --------- - perturb_prob : float from 0 to 1 - The probability that a batch will have speed perturbation applied. - drop_freq_prob : float from 0 to 1 - The probability that a batch will have frequencies dropped. - drop_chunk_prob : float from 0 to 1 - The probability that a batch will have chunks dropped. - speeds : list of ints - A set of different speeds to use to perturb each batch. - See ``speechbrain.processing.speech_augmentation.SpeedPerturb`` - sample_rate : int - Sampling rate of the input waveforms. - drop_freq_count_low : int - Lowest number of frequencies that could be dropped. - drop_freq_count_high : int - Highest number of frequencies that could be dropped. - drop_chunk_count_low : int - Lowest number of chunks that could be dropped. - drop_chunk_count_high : int - Highest number of chunks that could be dropped. - drop_chunk_length_low : int - Lowest length of chunks that could be dropped. - drop_chunk_length_high : int - Highest length of chunks that could be dropped. - drop_chunk_noise_factor : float - The noise factor used to scale the white noise inserted, relative to - the average amplitude of the utterance. Default 0 (no noise inserted). - Example - ------- - >>> inputs = paddle.randn([10, 16000]) - >>> feature_maker = TimeDomainSpecAugment(speeds=[80]) - >>> feats = feature_maker(inputs, paddle.ones(10)) - >>> feats.shape - paddle.shape([10, 12800]) - """ - - def __init__( - self, - perturb_prob=1.0, - drop_freq_prob=1.0, - drop_chunk_prob=1.0, - speeds=[95, 100, 105], - sample_rate=16000, - drop_freq_count_low=0, - drop_freq_count_high=3, - drop_chunk_count_low=0, - drop_chunk_count_high=5, - drop_chunk_length_low=1000, - drop_chunk_length_high=2000, - drop_chunk_noise_factor=0, ): - super().__init__() - self.speed_perturb = SpeedPerturb( - perturb_prob=perturb_prob, orig_freq=sample_rate, speeds=speeds) - self.drop_freq = DropFreq( - drop_prob=drop_freq_prob, - drop_count_low=drop_freq_count_low, - drop_count_high=drop_freq_count_high, ) - self.drop_chunk = DropChunk( - drop_prob=drop_chunk_prob, - drop_count_low=drop_chunk_count_low, - drop_count_high=drop_chunk_count_high, - drop_length_low=drop_chunk_length_low, - drop_length_high=drop_chunk_length_high, - noise_factor=drop_chunk_noise_factor, ) - - def forward(self, waveforms, lengths): - """Returns the distorted waveforms. - Arguments - --------- - waveforms : tensor - The waveforms to distort - """ - # Augmentation - with paddle.no_grad(): - waveforms = self.speed_perturb(waveforms) - waveforms = self.drop_freq(waveforms) - waveforms = self.drop_chunk(waveforms, lengths) - return waveforms