Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.
Path: blob/main/examples/multi_lingual_speech_recognition.ipynb
Views: 2535
Fine-tuning Multi-Lingual Speech Model with 🤗 Transformers
This notebook shows how to fine-tune multi-lingual pretrained speech models for Automatic Speech Recognition.
This notebook is built to run on the Common Voice dataset with any multi-lingual speech model checkpoint from the Model Hub as long as that model has a version with a Connectionist Temporal Classification (CTC) head. Depending on the model and the GPU you are using, you might need to adjust the batch size to avoid out-of-memory errors. Set those two parameters, then the rest of the notebook should run smoothly:
For a more in-detail explanation of how multi-lingual pretrained speech models function, please take a look at the 🤗 Blog.
Before we start, let's install both datasets
and transformers
from master. Also, we need the torchaudio
and librosa
package to load audio files and the jiwer
to evaluate our fine-tuned model using the word error rate (WER) metric .
Next we strongly suggest to upload your training checkpoints directly to the 🤗 Hub while training. The 🤗 Hub has integrated version control so you can be sure that no model checkpoint is getting lost during training.
To do so you have to store your authentication token from the Hugging Face website (sign up here if you haven't already!)
Then you need to install Git-LFS to upload your model checkpoints:
We also quickly upload some telemetry - this tells us which examples and software versions are getting used so we know where to prioritize our maintenance efforts. We don't collect (or care about) any personally identifiable information, but if you'd prefer not to be counted, feel free to skip this step or delete this cell entirely.
In the paper, the model was evaluated using the phoneme error rate (PER), but by far the most common metric in ASR is the word error rate (WER). To keep this notebook as general as possible we decided to evaluate the model using WER.
Prepare Data, Tokenizer, Feature Extractor
ASR models transcribe speech to text, which means that we both need a feature extractor that processes the speech signal to the model's input format, e.g. a feature vector, and a tokenizer that processes the model's output format to text.
In 🤗 Transformers, speech recognition models are thus accompanied by both a tokenizer, and a feature extractor.
Let's start by creating the tokenizer responsible for decoding the model's predictions.
Create Tokenizer for Speech Recognition
First, let's go to Common Voice and pick a language to fine-tune XLSR-Wav2Vec2 on. For this notebook, we will use Turkish.
For each language-specific dataset, you can find a language code corresponding to your chosen language. On Common Voice, look for the field "Version". The language code then corresponds to the prefix before the underscore. For Turkish, e.g. the language code is "tr"
.
Great, now we can use 🤗 Datasets' simple API to download the data. The dataset name will be "common_voice"
, the config name corresponds to the language code - "tr"
in our case.
Common Voice has many different splits including invalidated
, which refers to data that was not rated as "clean enough" to be considered useful. In this notebook, we will only make use of the splits "train"
, "validation"
and "test"
.
Because the Turkish dataset is so small, we will merge both the validation and training data into a training dataset and simply use the test data for validation.
Many ASR datasets only provide the target text, 'sentence'
for each audio array 'audio'
and file 'path'
. Common Voice actually provides much more information about each audio file, such as the 'accent'
, etc. However, we want to keep the notebook as general as possible, so that we will only consider the transcribed text for fine-tuning.
Let's write a short function to display some random samples of the dataset and run it a couple of times to get a feeling for the transcriptions.
Alright! The transcriptions look fairly clean. Having translated the transcribed sentences, it seems that the language corresponds more to written-out text than noisy dialogue. This makes sense considering that Common Voice is a crowd-sourced read speech corpus.
We can see that the transcriptions contain some special characters, such as ,.?!;:
. Without a language model, it is much harder to classify speech chunks to such special characters because they don't really correspond to a characteristic sound unit. E.g., the letter "s"
has a more or less clear sound, whereas the special character "."
does not. Also in order to understand the meaning of a speech signal, it is usually not necessary to include special characters in the transcription.
In addition, we normalize the text to only have lower case letters and append a word separator token at the end.
Good! This looks better. We have removed most special characters from transcriptions and normalized them to lower-case only.
In CTC, it is common to classify speech chunks into letters, so we will do the same here. Let's extract all distinct letters of the training and test data and build our vocabulary from this set of letters.
We write a mapping function that concatenates all transcriptions into one long transcription and then transforms the string into a set of chars. It is important to pass the argument batched=True
to the map(...)
function so that the mapping function has access to all transcriptions at once.
Now, we create the union of all distinct letters in the training dataset and test dataset and convert the resulting list into an enumerated dictionary.
Cool, we see that all letters of the alphabet occur in the dataset (which is not really surprising) and we also extracted the special characters " "
and '
. Note that we did not exclude those special characters because:
The model has to learn to predict when a word is finished or else the model prediction would always be a sequence of chars which would make it impossible to separate words from each other.
From the transcriptions above it seems that words that include an apostrophe, such as
maktouf'un
do exist in Turkish, so I decided to keep the apostrophe in the dataset. This might be a wrong assumption though.
One should always keep in mind that the data-preprocessing is a very important step before training your model. E.g., we don't want our model to differentiate between a
and A
just because we forgot to normalize the data. The difference between a
and A
does not depend on the "sound" of the letter at all, but more on grammatical rules - e.g. use a capitalized letter at the beginning of the sentence. So it is sensible to remove the difference between capitalized and non-capitalized letters so that the model has an easier time learning to transcribe speech.
It is always advantageous to get help from a native speaker of the language you would like to transcribe to verify whether the assumptions you made are sensible, e.g. I should have made sure that keeping '
, but removing other special characters is a sensible choice for Turkish.
To make it clearer to the reader that " "
has its own token class, we give it a more visible character |
. In addition, we also add an "unknown" token so that the model can later deal with characters not encountered in Common Voice's training set.
Finally, we also add a padding token that corresponds to CTC's "blank token". The "blank token" is a core component of the CTC algorithm. For more information, please take a look at the "Alignment" section here.
Cool, now our vocabulary is complete and consists of 40 tokens, which means that the linear layer that we will add on top of the pretrained XLSR-Wav2Vec2 checkpoint will have an output dimension of 40.
Let's now save the vocabulary as a json file.
In a final step, we use the json file to instantiate a tokenizer object with the just created vocabulary file. The correct tokenizer_type
can be retrieved from the model configuration. If a tokenizer_class
is defined in the config, we can use it, else we assume the tokenizer_type
corresponds to the model_type
.
Now we can instantiate a tokenizer using AutoTokenizer
. Additionally, we set the tokenizer's special tokens.
If one wants to re-use the just created tokenizer with the fine-tuned model of this notebook, it is strongly advised to upload the tokenizer
to the 🤗 Hub. Let's call the repo to which we will upload the files "wav2vec2-large-xlsr-turkish-demo-colab"
:
and upload the tokenizer to the 🤗 Hub.
Great, you can see the just created repository under https://huggingface.co/<your-username>/wav2vec2-large-xlsr-turkish-demo-colab
.
Preprocess Data
So far, we have not looked at the actual values of the speech signal but just the transcription. In addition to sentence
, our datasets include two more column names path
and audio
. path
states the absolute path of the audio file. Let's take a look.
XLSR-Wav2Vec2
expects the input in the format of a 1-dimensional array of 16 kHz. This means that the audio file has to be loaded and resampled.
Thankfully, datasets
does this automatically by calling the other column audio
. Let try it out.
Great, we can see that the audio file has automatically been loaded. This is thanks to the new "Audio"
feature introduced in datasets == 1.13.3
, which loads and resamples audio files on-the-fly upon calling.
In the example above we can see that the audio data is loaded with a sampling rate of 48kHz whereas 16kHz are expected by the model. We can set the audio feature to the correct sampling rate by making use of cast_column
:
Let's take a look at "audio"
again.
This seemed to have worked! Let's listen to a couple of audio files to better understand the dataset and verify that the audio was correctly loaded.
Note: You can click the following cell a couple of times to listen to different speech samples.
It seems like the data is now correctly loaded and resampled.
It can be heard, that the speakers change along with their speaking rate, accent, and background environment, etc. Overall, the recordings sound acceptably clear though, which is to be expected from a crowd-sourced read speech corpus.
Let's do a final check that the data is correctly prepared, by printing the shape of the speech input, its transcription, and the corresponding sampling rate.
Note: You can click the following cell a couple of times to verify multiple samples.
Good! Everything looks fine - the data is a 1-dimensional array, the sampling rate always corresponds to 16kHz, and the target text is normalized.
Next, we should process the data with the model's feature extractor. Let's load the feature extractor
and wrap it into a Wav2Vec2Processor
together with the tokenizer.
Finally, we can leverage Wav2Vec2Processor
to process the data to the format expected by the model for training. To do so let's make use of Dataset's map(...)
function.
First, we load and resample the audio data, simply by calling batch["audio"]
. Second, we extract the input_values
from the loaded audio file. In our case, the Wav2Vec2Processor
only normalizes the data. For other speech models, however, this step can include more complex feature extraction, such as Log-Mel feature extraction. Third, we encode the transcriptions to label ids.
Let's apply the data preparation function to all examples.
Note: Currently datasets
make use of torchaudio
and librosa
for audio loading and resampling. If you wish to implement your own costumized data loading/sampling, feel free to just make use of the "path"
column instead and disregard the "audio"
column.
Long input sequences require a lot of memory. Since speech models in transformers
are based on self-attention
the memory requirement scales quadratically with the input length for long input sequences (cf. with this reddit post). For this demo, let's filter all sequences that are longer than 5 seconds out of the training dataset.
Awesome, now we are ready to start training!
Training
The data is processed so that we are ready to start setting up the training pipeline. We will make use of 🤗's Trainer for which we essentially need to do the following:
Define a data collator. In contrast to most NLP models, speech models usually have a much larger input length than output length. E.g., a sample of input length 50000 for XLSR-Wav2Vec2 has an output length of no more than 100. Given the large input sizes, it is much more efficient to pad the training batches dynamically meaning that all training samples should only be padded to the longest sample in their batch and not the overall longest sample. Therefore, fine-tuning speech models requires a special padding data collator, which we will define below
Evaluation metric. During training, the model should be evaluated on the word error rate. We should define a
compute_metrics
function accordinglyLoad a pretrained checkpoint. We need to load a pretrained checkpoint and configure it correctly for training.
Define the training configuration.
After having fine-tuned the model, we will correctly evaluate it on the test data and verify that it has indeed learned to correctly transcribe speech.
Set-up Trainer
Let's start by defining the data collator. The code for the data collator was copied from this example.
Without going into too many details, in contrast to the common data collators, this data collator treats the input_values
and labels
differently and thus applies to separate padding functions on them. This is necessary because in speech input and output are of different modalities meaning that they should not be treated by the same padding function. Analogous to the common data collators, the padding tokens in the labels with -100
so that those tokens are not taken into account when computing the loss.
Next, the evaluation metric is defined. As mentioned earlier, the predominant metric in ASR is the word error rate (WER), hence we will use it in this notebook as well.
The model will return a sequence of logit vectors: with and .
A logit vector contains the log-odds for each word in the vocabulary we defined earlier, thus config.vocab_size
. We are interested in the most likely prediction of the model and thus take the argmax(...)
of the logits. Also, we transform the encoded labels back to the original string by replacing -100
with the pad_token_id
and decoding the ids while making sure that consecutive tokens are not grouped to the same token in CTC style .
Now, we can load the pretrained speech checkpoint. The tokenizer's pad_token_id
must be to define the model's pad_token_id
or in the case of a CTC speech model also CTC's blank token .
Because the dataset is quite small (~6h of training data) and because Common Voice is quite noisy, fine-tuning might require some hyper-parameter tuning, which is why a couple of hyperparameters are set in the following.
Note: When using this notebook to train speech models on another language of Common Voice those hyper-parameter settings might not work very well. Feel free to adapt those depending on your use case.
The first component of most transformer-based speech models consists of a stack of CNN layers that are used to extract acoustically meaningful - but contextually independent - features from the raw speech signal. This part of the model has already been sufficiently trained during pretraining and as stated in the paper does not need to be fine-tuned anymore. Thus, we can set the requires_grad
to False
for all parameters of the feature extraction part.
In a final step, we define all parameters related to training. To give more explanation on some of the parameters:
group_by_length
makes training more efficient by grouping training samples of similar input length into one batch. This can significantly speed up training time by heavily reducing the overall number of useless padding tokens that are passed through the modellearning_rate
andweight_decay
were heuristically tuned until fine-tuning has become stable. Note that those parameters strongly depend on the Common Voice dataset and might be suboptimal for other speech datasets.
For more explanations on other parameters, one can take a look at the docs.
During training, a checkpoint will be uploaded asynchronously to the hub every 400 training steps. It allows you to also play around with the demo widget even while your model is still training.
Note: If one does not want to upload the model checkpoints to the hub, simply set push_to_hub=False
.
Now, all instances can be passed to Trainer and we are ready to start training!
To allow models to become independent of the speaker rate, in CTC, consecutive tokens that are identical are simply grouped as a single token. However, the encoded labels should not be grouped when decoding since they don't correspond to the predicted tokens of the model, which is why the group_tokens=False
parameter has to be passed. If we wouldn't pass this parameter a word like "hello"
would incorrectly be encoded, and decoded as "helo"
.
The blank token allows the model to predict a word, such as "hello"
by forcing it to insert the blank token between the two l's. A CTC-conform prediction of "hello"
of our model would be [PAD] [PAD] "h" "e" "e" "l" "l" [PAD] "l" "o" "o" [PAD]
.
Training
Training will take multiple hours depending on the GPU allocated to this notebook. Every save_steps
, the current checkpoint will be uploaded to the Hub.
You can now upload the final result of the training to the Hub. Just execute this instruction:
You can now share this model with all your friends, family, favorite pets: they can all load it with the identifier "your-username/the-name-you-picked" so for instance:
To fine-tune larger models on larger datasets using CTC loss, one should take a look at the official speech-recognition examples here 🤗.