{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"**Wav2Vec2** is a pretrained model for Automatic Speech Recognition (ASR) and was released in [September 2020](https://ai.facebook.com/blog/wav2vec-20-learning-the-structure-of-speech-from-raw-audio/) by *Alexei Baevski, Michael Auli, and Alex Conneau*.  Soon after the superior performance of Wav2Vec2 was demonstrated on one of the most popular English datasets for ASR, called [LibriSpeech](https://huggingface.co/datasets/librispeech_asr), *Facebook AI* presented a multi-lingual version of Wav2Vec2, called [XLSR](https://arxiv.org/abs/2006.13979). XLSR stands for *cross-lingual speech representations* and refers to model's ability to learn speech representations that are useful across multiple languages.\n\nXLSR's successor, simply called **XLS-R** (refering to the [*''XLM-R*](https://ai.facebook.com/blog/-xlm-r-state-of-the-art-cross-lingual-understanding-through-self-supervision/) *for Speech''*), was released in [November 2021](https://ai.facebook.com/blog/xls-r-self-supervised-speech-processing-for-128-languages) by *Arun Babu, Changhan Wang, Andros Tjandra, et al.* XLS-R used almost **half a million** hours of audio data in 128 languages for self-supervised pre-training and comes in sizes ranging from 300 milion up to **two billion** parameters. You can find the pretrained checkpoints on the 🤗 Hub:\n\n- [**Wav2Vec2-XLS-R-300M**](https://huggingface.co/facebook/wav2vec2-xls-r-300m)\n- [**Wav2Vec2-XLS-R-1B**](https://huggingface.co/facebook/wav2vec2-xls-r-1b)\n- [**Wav2Vec2-XLS-R-2B**](https://huggingface.co/facebook/wav2vec2-xls-r-2b)\n\nSimilar to [BERT's masked language modeling objective](http://jalammar.github.io/illustrated-bert/), XLS-R learns contextualized speech representations by randomly masking feature vectors before passing them to a transformer network during self-supervised pre-training (*i.e.* diagram on the left below). \n\nFor fine-tuning, a single linear layer is added on top of the pre-trained network to train the model on labeled data of audio downstream tasks such as speech recognition, speech translation and audio classification (*i.e.* diagram on the right below).\n\n![wav2vec2_structure](https://raw.githubusercontent.com/patrickvonplaten/scientific_images/master/xls_r.png)\n\nXLS-R shows impressive improvements over previous state-of-the-art results on both speech recognition, speech translation and speaker/language identification, *cf.* with Table 3-6, Table 7-10, and Table 11-12 respectively of the official [paper](https://ai.facebook.com/blog/xls-r-self-supervised-speech-processing-for-128-languages).","metadata":{"id":"V7YOT2mnUiea"}},{"cell_type":"code","source":"gpu_info = !nvidia-smi\ngpu_info = '\\n'.join(gpu_info)\nif gpu_info.find('failed') >= 0:\n    print('Not connected to a GPU')\nelse:\n    print(gpu_info)","metadata":{"id":"YELVqGxMxnbG","outputId":"1ab7eb67-409d-4371-b99e-7eb1171cb5fb","execution":{"iopub.status.busy":"2022-07-02T00:58:22.7191Z","iopub.execute_input":"2022-07-02T00:58:22.719485Z","iopub.status.idle":"2022-07-02T00:58:22.788573Z","shell.execute_reply.started":"2022-07-02T00:58:22.719449Z","shell.execute_reply":"2022-07-02T00:58:22.787444Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%%capture\n!pip install datasets\n!pip install transformers\n!pip install torchaudio\n!pip install jiwer","metadata":{"id":"c8eh87Hoee5d","execution":{"iopub.status.busy":"2022-07-02T00:58:22.790106Z","iopub.execute_input":"2022-07-02T00:58:22.790759Z","iopub.status.idle":"2022-07-02T00:58:59.461879Z","shell.execute_reply.started":"2022-07-02T00:58:22.79072Z","shell.execute_reply":"2022-07-02T00:58:59.460676Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare Data, Tokenizer, Feature Extractor","metadata":{"id":"0mW-C1Nt-j7k"}},{"cell_type":"markdown","source":"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. \n\nIn 🤗 Transformers, the XLS-R model is thus accompanied by both a tokenizer, called [Wav2Vec2CTCTokenizer](https://huggingface.co/transformers/master/model_doc/wav2vec2.html#wav2vec2ctctokenizer), and a feature extractor, called [Wav2Vec2FeatureExtractor](https://huggingface.co/transformers/master/model_doc/wav2vec2.html#wav2vec2featureextractor).\n\nLet's start by creating the tokenizer to decode the predicted output classes to the output transcription.","metadata":{"id":"BeBosnY9BH3e"}},{"cell_type":"markdown","source":"### Create `Wav2Vec2CTCTokenizer`","metadata":{"id":"sEXEWEJGQPqD"}},{"cell_type":"markdown","source":"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\"`. ","metadata":{"id":"bee4g9rpLxll"}},{"cell_type":"code","source":"from datasets import load_dataset, load_metric, Audio\ndataset=load_dataset(\"bengaliAI/cvbn\", \"bn\")","metadata":{"id":"2MMXcWFFgCXU","outputId":"00961862-9e79-4e0e-c887-db62adafa553","execution":{"iopub.status.busy":"2022-07-02T00:58:59.46533Z","iopub.execute_input":"2022-07-02T00:58:59.46603Z","iopub.status.idle":"2022-07-02T01:12:38.943652Z","shell.execute_reply.started":"2022-07-02T00:58:59.465993Z","shell.execute_reply":"2022-07-02T01:12:38.942543Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"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. Keeping the notebook as general as possible, we only consider the transcribed text for fine-tuning.\n\n","metadata":{"id":"ri5y5N_HMANq"}},{"cell_type":"code","source":"common_voice_train=dataset[\"train\"]\ncommon_voice_test=dataset[\"validation\"]","metadata":{"execution":{"iopub.status.busy":"2022-07-02T01:12:38.945525Z","iopub.execute_input":"2022-07-02T01:12:38.946517Z","iopub.status.idle":"2022-07-02T01:12:38.951562Z","shell.execute_reply.started":"2022-07-02T01:12:38.946476Z","shell.execute_reply":"2022-07-02T01:12:38.950246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"common_voice_train = common_voice_train.remove_columns([\"accent\", \"age\", \"client_id\", \"down_votes\", \"gender\", \"locale\", \"segment\", \"up_votes\"])\ncommon_voice_test = common_voice_test.remove_columns([\"accent\", \"age\", \"client_id\", \"down_votes\", \"gender\", \"locale\", \"segment\", \"up_votes\"])","metadata":{"id":"kbyq6lDgQc2a","execution":{"iopub.status.busy":"2022-07-02T01:12:38.953256Z","iopub.execute_input":"2022-07-02T01:12:38.953916Z","iopub.status.idle":"2022-07-02T01:12:39.039191Z","shell.execute_reply.started":"2022-07-02T01:12:38.953878Z","shell.execute_reply":"2022-07-02T01:12:39.038135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"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.","metadata":{"id":"Go9Hq4e4NDT9"}},{"cell_type":"code","source":"from datasets import ClassLabel\nimport random\nimport pandas as pd\nfrom IPython.display import display, HTML\n\ndef show_random_elements(dataset, num_examples=10):\n    assert num_examples <= len(dataset), \"Can't pick more elements than there are in the dataset.\"\n    picks = []\n    for _ in range(num_examples):\n        pick = random.randint(0, len(dataset)-1)\n        while pick in picks:\n            pick = random.randint(0, len(dataset)-1)\n        picks.append(pick)\n    \n    df = pd.DataFrame(dataset[picks])\n    display(HTML(df.to_html()))","metadata":{"id":"72737oog2F6U","execution":{"iopub.status.busy":"2022-07-02T01:14:42.633792Z","iopub.execute_input":"2022-07-02T01:14:42.634157Z","iopub.status.idle":"2022-07-02T01:14:42.642558Z","shell.execute_reply.started":"2022-07-02T01:14:42.634127Z","shell.execute_reply":"2022-07-02T01:14:42.641331Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_random_elements(common_voice_train.remove_columns([\"path\", \"audio\"]), num_examples=10)","metadata":{"id":"K_JUmf3G3b9S","outputId":"e3a0d9c8-4b68-4255-fd29-8dba65632a24","execution":{"iopub.status.busy":"2022-07-02T01:14:45.914015Z","iopub.execute_input":"2022-07-02T01:14:45.914368Z","iopub.status.idle":"2022-07-02T01:14:45.984098Z","shell.execute_reply.started":"2022-07-02T01:14:45.914338Z","shell.execute_reply":"2022-07-02T01:14:45.983107Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import re\nchars_to_remove_regex = '[\\,\\?\\.\\!\\-\\;\\:\\\"\\“\\%\\‘\\”\\�\\']'\n\ndef remove_special_characters(batch):\n    batch[\"sentence\"] = re.sub(chars_to_remove_regex, '', batch[\"sentence\"]).lower()\n    return batch","metadata":{"id":"svKzVJ_hQGK6","execution":{"iopub.status.busy":"2022-07-02T01:14:49.231284Z","iopub.execute_input":"2022-07-02T01:14:49.231665Z","iopub.status.idle":"2022-07-02T01:14:49.237539Z","shell.execute_reply.started":"2022-07-02T01:14:49.231634Z","shell.execute_reply":"2022-07-02T01:14:49.236625Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"common_voice_train = common_voice_train.map(remove_special_characters)\ncommon_voice_test = common_voice_test.map(remove_special_characters)","metadata":{"id":"XIHocAuTQbBR","outputId":"32b09453-6f60-42c4-e417-4e87edad58dd","execution":{"iopub.status.busy":"2022-07-02T01:14:53.838064Z","iopub.execute_input":"2022-07-02T01:14:53.83902Z","iopub.status.idle":"2022-07-02T01:16:28.956807Z","shell.execute_reply.started":"2022-07-02T01:14:53.83898Z","shell.execute_reply":"2022-07-02T01:16:28.955761Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In CTC, it is common to classify speech chunks into letters, so we will do the same here. \nLet's extract all distinct letters of the training and test data and build our vocabulary from this set of letters.\n\nWe write a mapping function that concatenates all transcriptions into one long transcription and then transforms the string into a set of chars. \nIt is important to pass the argument `batched=True` to the `map(...)` function so that the mapping function has access to all transcriptions at once.","metadata":{"id":"3ORHDb2Th2TW"}},{"cell_type":"code","source":"def extract_all_chars(batch):\n    all_text = \" \".join(batch[\"sentence\"])\n    vocab = list(set(all_text))\n    return {\"vocab\": [vocab], \"all_text\": [all_text]}","metadata":{"id":"LwCshNbbeRZR","execution":{"iopub.status.busy":"2022-07-02T01:16:32.127575Z","iopub.execute_input":"2022-07-02T01:16:32.128182Z","iopub.status.idle":"2022-07-02T01:16:32.133695Z","shell.execute_reply.started":"2022-07-02T01:16:32.128144Z","shell.execute_reply":"2022-07-02T01:16:32.132836Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vocab_train = common_voice_train.map(extract_all_chars, batched=True, batch_size=-1, keep_in_memory=False, remove_columns=common_voice_train.column_names)\nvocab_test = common_voice_test.map(extract_all_chars, batched=True, batch_size=-1, keep_in_memory=False, remove_columns=common_voice_test.column_names)","metadata":{"id":"_m6uUjjcfbjH","outputId":"67fa96dd-2df9-4393-f551-4d599c42efff","execution":{"iopub.status.busy":"2022-07-02T01:16:34.749909Z","iopub.execute_input":"2022-07-02T01:16:34.750264Z","iopub.status.idle":"2022-07-02T01:17:22.0006Z","shell.execute_reply.started":"2022-07-02T01:16:34.750233Z","shell.execute_reply":"2022-07-02T01:17:21.999445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"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.","metadata":{"id":"7oVgE8RZSJNP"}},{"cell_type":"code","source":"vocab_list = list(set(vocab_train[\"vocab\"][0]) | set(vocab_test[\"vocab\"][0]))","metadata":{"id":"aQfneNsmlJI0","execution":{"iopub.status.busy":"2022-07-02T01:17:22.004789Z","iopub.execute_input":"2022-07-02T01:17:22.005057Z","iopub.status.idle":"2022-07-02T01:17:22.012708Z","shell.execute_reply.started":"2022-07-02T01:17:22.005033Z","shell.execute_reply":"2022-07-02T01:17:22.01003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vocab_dict = {v: k for k, v in enumerate(sorted(vocab_list))}\nvocab_dict","metadata":{"id":"_0kRndSvqaKk","outputId":"63440dfc-3f58-42c1-db78-3369f96f103f","execution":{"iopub.status.busy":"2022-07-02T01:17:22.015314Z","iopub.execute_input":"2022-07-02T01:17:22.01572Z","iopub.status.idle":"2022-07-02T01:17:22.113041Z","shell.execute_reply.started":"2022-07-02T01:17:22.015687Z","shell.execute_reply":"2022-07-02T01:17:22.111844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"vocab_dict[\"|\"] = vocab_dict[\" \"]\ndel vocab_dict[\" \"]","metadata":{"id":"npbIbBoLgaFX","execution":{"iopub.status.busy":"2022-07-02T01:17:22.115644Z","iopub.execute_input":"2022-07-02T01:17:22.116757Z","iopub.status.idle":"2022-07-02T01:17:22.121731Z","shell.execute_reply.started":"2022-07-02T01:17:22.116718Z","shell.execute_reply":"2022-07-02T01:17:22.120484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"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](https://distill.pub/2017/ctc/).","metadata":{"id":"_9yCWg4Sd0cb"}},{"cell_type":"code","source":"vocab_dict[\"[UNK]\"] = len(vocab_dict)\nvocab_dict[\"[PAD]\"] = len(vocab_dict)\nlen(vocab_dict)","metadata":{"id":"znF0bNunsjbl","outputId":"314ca989-0587-485e-d7d2-3e6efe2158ee","execution":{"iopub.status.busy":"2022-07-02T01:17:22.123221Z","iopub.execute_input":"2022-07-02T01:17:22.12382Z","iopub.status.idle":"2022-07-02T01:17:22.134839Z","shell.execute_reply.started":"2022-07-02T01:17:22.123782Z","shell.execute_reply":"2022-07-02T01:17:22.133774Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's now save the vocabulary as a json file.","metadata":{"id":"1CujRgBNVRaD"}},{"cell_type":"code","source":"import json\nwith open('vocab.json', 'w') as vocab_file:\n    json.dump(vocab_dict, vocab_file)","metadata":{"id":"ehyUoh9vk191","execution":{"iopub.status.busy":"2022-07-02T01:17:22.136585Z","iopub.execute_input":"2022-07-02T01:17:22.136833Z","iopub.status.idle":"2022-07-02T01:17:22.144604Z","shell.execute_reply.started":"2022-07-02T01:17:22.136811Z","shell.execute_reply":"2022-07-02T01:17:22.143698Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In a final step, we use the json file to load the vocabulary into an instance of the `Wav2Vec2CTCTokenizer` class.","metadata":{"id":"SHJDaKlIVVim"}},{"cell_type":"code","source":"from transformers import Wav2Vec2CTCTokenizer\n\ntokenizer = Wav2Vec2CTCTokenizer.from_pretrained(\"./\", unk_token=\"[UNK]\", pad_token=\"[PAD]\", word_delimiter_token=\"|\")","metadata":{"id":"xriFGEWQkO4M","outputId":"95e57a04-c48e-4748-8d77-bac71dd2750e","execution":{"iopub.status.busy":"2022-07-02T01:17:22.146098Z","iopub.execute_input":"2022-07-02T01:17:22.146582Z","iopub.status.idle":"2022-07-02T01:17:22.563873Z","shell.execute_reply.started":"2022-07-02T01:17:22.146542Z","shell.execute_reply":"2022-07-02T01:17:22.562982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create `Wav2Vec2FeatureExtractor`","metadata":{"id":"mYcIiR2FQ96i"}},{"cell_type":"markdown","source":"Speech is a continuous signal and to be treated by computers, it first has to be discretized, which is usually called **sampling**. The sampling rate hereby plays an important role in that it defines how many data points of the speech signal are measured per second. Therefore, sampling with a higher sampling rate results in a better approximation of the *real* speech signal but also necessitates more values per second.\n\nA pretrained checkpoint expects its input data to have been sampled more or less from the same distribution as the data it was trained on. The same speech signals sampled at two different rates have a very different distribution, *e.g.*, doubling the sampling rate results in data points being twice as long. Thus, \nbefore fine-tuning a pretrained checkpoint of an ASR model, it is crucial to verify that the sampling rate of the data that was used to pretrain the model matches the sampling rate of the dataset used to fine-tune the model.\n\nXLS-R was pretrained on audio data of [Babel](http://www.reading.ac.uk/AcaDepts/ll/speechlab/babel/r), \n[Multilingual LibriSpeech (MLS)](https://huggingface.co/datasets/multilingual_librispeech), [Common Voice](https://huggingface.co/datasets/common_voice), [VoxPopuli](https://arxiv.org/abs/2101.00390), and [VoxLingua107](https://arxiv.org/abs/2011.12998) at a sampling rate of 16kHz. Common Voice, in its original form, has a sampling rate of 48kHz, thus we will have to downsample the fine-tuning data to 16kHz in the following.\n\n","metadata":{"id":"Y6mDEyW719rx"}},{"cell_type":"markdown","source":"A `Wav2Vec2FeatureExtractor` object requires the following parameters to be instantiated:\n\n- `feature_size`: Speech models take a sequence of feature vectors as an input. While the length of this sequence obviously varies, the feature size should not. In the case of Wav2Vec2, the feature size is 1 because the model was trained on the raw speech signal ${}^2$.\n- `sampling_rate`: The sampling rate at which the model is trained on.\n- `padding_value`: For batched inference, shorter inputs need to be padded with a specific value\n- `do_normalize`: Whether the input should be *zero-mean-unit-variance* normalized or not. Usually, speech models perform better when normalizing the input\n- `return_attention_mask`: Whether the model should make use of an `attention_mask` for batched inference. In general, XLS-R models checkpoints should **always** use the `attention_mask`.","metadata":{"id":"KuUbPW7oV-B5"}},{"cell_type":"code","source":"from transformers import Wav2Vec2FeatureExtractor\n\nfeature_extractor = Wav2Vec2FeatureExtractor(feature_size=1, sampling_rate=16000, padding_value=0.0, do_normalize=True, return_attention_mask=True)","metadata":{"id":"kAR0-2KLkopp","execution":{"iopub.status.busy":"2022-07-02T01:43:17.573013Z","iopub.execute_input":"2022-07-02T01:43:17.573382Z","iopub.status.idle":"2022-07-02T01:43:17.584668Z","shell.execute_reply.started":"2022-07-02T01:43:17.573353Z","shell.execute_reply":"2022-07-02T01:43:17.583718Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, XLS-R's feature extraction pipeline is thereby fully defined!\n\nFor improved user-friendliness, the feature extractor and tokenizer are *wrapped* into a single `Wav2Vec2Processor` class so that one only needs a `model` and `processor` object.","metadata":{"id":"qUETetgqYC3W"}},{"cell_type":"code","source":"from transformers import Wav2Vec2Processor\n\nprocessor = Wav2Vec2Processor(feature_extractor=feature_extractor, tokenizer=tokenizer)","metadata":{"id":"KYZtoW-tlZgl","execution":{"iopub.status.busy":"2022-07-02T01:43:20.315203Z","iopub.execute_input":"2022-07-02T01:43:20.315558Z","iopub.status.idle":"2022-07-02T01:43:20.326157Z","shell.execute_reply.started":"2022-07-02T01:43:20.315529Z","shell.execute_reply":"2022-07-02T01:43:20.325236Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next, we can prepare the dataset.","metadata":{"id":"DrKnYuvDIoOO"}},{"cell_type":"markdown","source":"### Preprocess Data\n\nSo 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.\n","metadata":{"id":"YFmShnl7RE35"}},{"cell_type":"code","source":"common_voice_train[0][\"path\"]","metadata":{"id":"TTCS7W6XJ9BG","outputId":"d4b375f8-a536-4be9-b995-578fd321acfe","execution":{"iopub.status.busy":"2022-07-02T01:43:25.739052Z","iopub.execute_input":"2022-07-02T01:43:25.739427Z","iopub.status.idle":"2022-07-02T01:43:26.130141Z","shell.execute_reply.started":"2022-07-02T01:43:25.739377Z","shell.execute_reply":"2022-07-02T01:43:26.129242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"XLS-R 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.\n\n Thankfully, `datasets` does this automatically by calling the other column `audio`. Let try it out. ","metadata":{"id":"T6ndIjHGFp0W"}},{"cell_type":"code","source":"common_voice_train[0][\"audio\"]","metadata":{"id":"qj_z5Zc3GAs9","outputId":"6b0dca9c-aadc-493f-f754-b01d0f35fe28","execution":{"iopub.status.busy":"2022-07-02T01:43:28.948265Z","iopub.execute_input":"2022-07-02T01:43:28.949297Z","iopub.status.idle":"2022-07-02T01:43:28.97438Z","shell.execute_reply.started":"2022-07-02T01:43:28.949246Z","shell.execute_reply":"2022-07-02T01:43:28.973387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, we can see that the audio file has automatically been loaded. This is thanks to the new [`\"Audio\"` feature](https://huggingface.co/docs/datasets/package_reference/main_classes.html?highlight=audio#datasets.Audio) introduced in `datasets == 4.13.3`, which loads and resamples audio files on-the-fly upon calling.\n\nIn 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`](https://huggingface.co/docs/datasets/package_reference/main_classes.html?highlight=cast_column#datasets.DatasetDict.cast_column):","metadata":{"id":"WUUTgI1bGHW-"}},{"cell_type":"code","source":"common_voice_train = common_voice_train.cast_column(\"audio\", Audio(sampling_rate=16_000))\ncommon_voice_test = common_voice_test.cast_column(\"audio\", Audio(sampling_rate=16_000))","metadata":{"id":"rrv65aj7G95i","execution":{"iopub.status.busy":"2022-07-02T01:43:36.698313Z","iopub.execute_input":"2022-07-02T01:43:36.699011Z","iopub.status.idle":"2022-07-02T01:43:36.713175Z","shell.execute_reply.started":"2022-07-02T01:43:36.698975Z","shell.execute_reply":"2022-07-02T01:43:36.712062Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's take a look at `\"audio\"` again.","metadata":{"id":"PcnO4x-NGBEi"}},{"cell_type":"code","source":"common_voice_train[0][\"audio\"]","metadata":{"id":"aKtkc1o_HWHC","outputId":"c2c1375e-4812-4112-d843-9e3da6dd327d","execution":{"iopub.status.busy":"2022-07-02T01:43:40.012956Z","iopub.execute_input":"2022-07-02T01:43:40.01332Z","iopub.status.idle":"2022-07-02T01:43:40.048486Z","shell.execute_reply.started":"2022-07-02T01:43:40.013289Z","shell.execute_reply":"2022-07-02T01:43:40.047463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"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. \n\n**Note**: *You can click the following cell a couple of times to listen to different speech samples.*","metadata":{"id":"SOckzFd4Mbzq"}},{"cell_type":"code","source":"import IPython.display as ipd\nimport numpy as np\nimport random\n\nrand_int = random.randint(0, len(common_voice_train)-1)\n\nprint(common_voice_train[rand_int][\"sentence\"])\nipd.Audio(data=common_voice_train[rand_int][\"audio\"][\"array\"], autoplay=True, rate=16000)","metadata":{"id":"dueM6U7Ev0OA","outputId":"1a771d0a-085d-441b-cc1b-f05438425ce4","execution":{"iopub.status.busy":"2022-07-02T01:43:44.282817Z","iopub.execute_input":"2022-07-02T01:43:44.283163Z","iopub.status.idle":"2022-07-02T01:43:44.402438Z","shell.execute_reply.started":"2022-07-02T01:43:44.283134Z","shell.execute_reply":"2022-07-02T01:43:44.401487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rand_int = random.randint(0, len(common_voice_train)-1)\n\nprint(\"Target text:\", common_voice_train[rand_int][\"sentence\"])\nprint(\"Input array shape:\", common_voice_train[rand_int][\"audio\"][\"array\"].shape)\nprint(\"Sampling rate:\", common_voice_train[rand_int][\"audio\"][\"sampling_rate\"])","metadata":{"id":"1Po2g7YPuRTx","outputId":"63478ae9-2927-4ec1-c13c-41fb3754e18e","execution":{"iopub.status.busy":"2022-07-02T01:43:52.017783Z","iopub.execute_input":"2022-07-02T01:43:52.018142Z","iopub.status.idle":"2022-07-02T01:43:52.079481Z","shell.execute_reply.started":"2022-07-02T01:43:52.018111Z","shell.execute_reply":"2022-07-02T01:43:52.078458Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Finally, we can leverage `Wav2Vec2Processor` to process the data to the format expected by `Wav2Vec2ForCTC` for training. To do so let's make use of Dataset's [`map(...)`](https://huggingface.co/docs/datasets/package_reference/main_classes.html?highlight=map#datasets.DatasetDict.map) function.\n\nFirst, we load and resample the audio data, simply by calling `batch[\"audio\"]`.\nSecond, 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](https://en.wikipedia.org/wiki/Mel-frequency_cepstrum). \nThird, we encode the transcriptions to label ids.\n\n**Note**: This mapping function is a good example of how the `Wav2Vec2Processor` class should be used. In \"normal\" context, calling `processor(...)` is redirected to `Wav2Vec2FeatureExtractor`'s call method. When wrapping the processor into the `as_target_processor` context, however, the same method is redirected to `Wav2Vec2CTCTokenizer`'s call method.\nFor more information please check the [docs](https://huggingface.co/transformers/master/model_doc/wav2vec2.html#transformers.Wav2Vec2Processor.__call__).","metadata":{"id":"k3Pbn5WvOYZF"}},{"cell_type":"code","source":"def prepare_dataset(batch):\n    audio = batch[\"audio\"]\n\n    # batched output is \"un-batched\"\n    batch[\"input_values\"] = processor(audio[\"array\"], sampling_rate=audio[\"sampling_rate\"]).input_values[0]\n    batch[\"input_length\"] = len(batch[\"input_values\"])\n    \n    with processor.as_target_processor():\n        batch[\"labels\"] = processor(batch[\"sentence\"]).input_ids\n    return batch","metadata":{"id":"eJY7I0XAwe9p","execution":{"iopub.status.busy":"2022-07-02T01:44:02.95715Z","iopub.execute_input":"2022-07-02T01:44:02.957513Z","iopub.status.idle":"2022-07-02T01:44:02.963222Z","shell.execute_reply.started":"2022-07-02T01:44:02.957483Z","shell.execute_reply":"2022-07-02T01:44:02.962289Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's apply the data preparation function to all examples.","metadata":{"id":"q6Pg_WR3OGAP"}},{"cell_type":"markdown","source":"### Sample Training Example\ntrain samples=10000\nval samples=1000","metadata":{}},{"cell_type":"code","source":"common_voice_train=common_voice_train.select(range(10000))\ncommon_voice_test=common_voice_test.select(range(1000))","metadata":{"execution":{"iopub.status.busy":"2022-07-02T01:44:08.414227Z","iopub.execute_input":"2022-07-02T01:44:08.414582Z","iopub.status.idle":"2022-07-02T01:44:08.432388Z","shell.execute_reply.started":"2022-07-02T01:44:08.414552Z","shell.execute_reply":"2022-07-02T01:44:08.431442Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"common_voice_train = common_voice_train.map(prepare_dataset, remove_columns=common_voice_train.column_names)\ncommon_voice_test = common_voice_test.map(prepare_dataset, remove_columns=common_voice_test.column_names)","metadata":{"id":"-np9xYK-wl8q","outputId":"00d6940a-a7bf-4128-896b-76bc289e5b7f","execution":{"iopub.status.busy":"2022-07-02T01:44:11.459279Z","iopub.execute_input":"2022-07-02T01:44:11.459946Z","iopub.status.idle":"2022-07-02T01:46:31.98268Z","shell.execute_reply.started":"2022-07-02T01:44:11.45991Z","shell.execute_reply":"2022-07-02T01:46:31.981349Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Note**: Currently `datasets` make use of [`torchaudio`](https://pytorch.org/audio/stable/index.html) and [`librosa`](https://librosa.org/doc/latest/index.html) 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.","metadata":{"id":"nKcEWHvKI1by"}},{"cell_type":"markdown","source":"Long input sequences require a lot of memory. XLS-R is based on `self-attention` the memory requirement scales quadratically with the input length for long input sequences (*cf.* with [this](https://www.reddit.com/r/MachineLearning/comments/genjvb/d_why_is_the_maximum_input_sequence_length_of/) reddit post). In case this demo crashes with an \"Out-of-memory\" error for you, you might want to uncomment the following lines to filter all sequences that are longer than 5 seconds for training.","metadata":{"id":"24CxHd5ewI4T"}},{"cell_type":"code","source":"#max_input_length_in_sec = 5.0\n#common_voice_train = common_voice_train.filter(lambda x: x < max_input_length_in_sec * processor.feature_extractor.sampling_rate, input_columns=[\"input_length\"])","metadata":{"id":"tdHfbUJ_09iA","execution":{"iopub.status.busy":"2022-07-01T23:43:19.237266Z","iopub.execute_input":"2022-07-01T23:43:19.240524Z","iopub.status.idle":"2022-07-01T23:43:19.247887Z","shell.execute_reply.started":"2022-07-01T23:43:19.240478Z","shell.execute_reply":"2022-07-01T23:43:19.246332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Awesome, now we are ready to start training!","metadata":{"id":"1ZWDCCKqwcfS"}},{"cell_type":"markdown","source":"## Training\n\nThe data is processed so that we are ready to start setting up the training pipeline. We will make use of 🤗's [Trainer](https://huggingface.co/transformers/master/main_classes/trainer.html?highlight=trainer) for which we essentially need to do the following:\n\n- Define a data collator. In contrast to most NLP models, XLS-R has a much larger input length than output length. *E.g.*, a sample of input length 50000 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 XLS-R requires a special padding data collator, which we will define below\n\n- Evaluation metric. During training, the model should be evaluated on the word error rate. We should define a `compute_metrics` function accordingly\n\n- Load a pretrained checkpoint. We need to load a pretrained checkpoint and configure it correctly for training.\n\n- Define the training configuration.\n\nAfter 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.","metadata":{"id":"gYlQkKVoRUos"}},{"cell_type":"markdown","source":"### Set-up Trainer\n\nLet's start by defining the data collator. The code for the data collator was copied from [this example](https://github.com/huggingface/transformers/blob/7e61d56a45c19284cfda0cee8995fb552f6b1f4e/examples/pytorch/speech-recognition/run_speech_recognition_ctc.py#L219).\n\nWithout 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 (again making use of XLS-R processor's context manager). 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.\nAnalogous 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.","metadata":{"id":"Slk403unUS91"}},{"cell_type":"code","source":"import torch\n\nfrom dataclasses import dataclass, field\nfrom typing import Any, Dict, List, Optional, Union\n\n@dataclass\nclass DataCollatorCTCWithPadding:\n    \"\"\"\n    Data collator that will dynamically pad the inputs received.\n    Args:\n        processor (:class:`~transformers.Wav2Vec2Processor`)\n            The processor used for proccessing the data.\n        padding (:obj:`bool`, :obj:`str` or :class:`~transformers.tokenization_utils_base.PaddingStrategy`, `optional`, defaults to :obj:`True`):\n            Select a strategy to pad the returned sequences (according to the model's padding side and padding index)\n            among:\n            * :obj:`True` or :obj:`'longest'`: Pad to the longest sequence in the batch (or no padding if only a single\n              sequence if provided).\n            * :obj:`'max_length'`: Pad to a maximum length specified with the argument :obj:`max_length` or to the\n              maximum acceptable input length for the model if that argument is not provided.\n            * :obj:`False` or :obj:`'do_not_pad'` (default): No padding (i.e., can output a batch with sequences of\n              different lengths).\n    \"\"\"\n\n    processor: Wav2Vec2Processor\n    padding: Union[bool, str] = True\n\n    def __call__(self, features: List[Dict[str, Union[List[int], torch.Tensor]]]) -> Dict[str, torch.Tensor]:\n        # split inputs and labels since they have to be of different lenghts and need\n        # different padding methods\n        input_features = [{\"input_values\": feature[\"input_values\"]} for feature in features]\n        label_features = [{\"input_ids\": feature[\"labels\"]} for feature in features]\n\n        batch = self.processor.pad(\n            input_features,\n            padding=self.padding,\n            return_tensors=\"pt\",\n        )\n        with self.processor.as_target_processor():\n            labels_batch = self.processor.pad(\n                label_features,\n                padding=self.padding,\n                return_tensors=\"pt\",\n            )\n\n        # replace padding with -100 to ignore loss correctly\n        labels = labels_batch[\"input_ids\"].masked_fill(labels_batch.attention_mask.ne(1), -100)\n\n        batch[\"labels\"] = labels\n\n        return batch","metadata":{"id":"tborvC9hx88e","execution":{"iopub.status.busy":"2022-07-02T01:46:43.4471Z","iopub.execute_input":"2022-07-02T01:46:43.447904Z","iopub.status.idle":"2022-07-02T01:46:43.462404Z","shell.execute_reply.started":"2022-07-02T01:46:43.447867Z","shell.execute_reply":"2022-07-02T01:46:43.461446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"data_collator = DataCollatorCTCWithPadding(processor=processor, padding=True)","metadata":{"id":"lbQf5GuZyQ4_","execution":{"iopub.status.busy":"2022-07-02T01:46:46.130482Z","iopub.execute_input":"2022-07-02T01:46:46.131111Z","iopub.status.idle":"2022-07-02T01:46:46.13631Z","shell.execute_reply.started":"2022-07-02T01:46:46.131058Z","shell.execute_reply":"2022-07-02T01:46:46.135102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Next, the evaluation metric is defined. As mentioned earlier, the \npredominant metric in ASR is the word error rate (WER), hence we will use it in this notebook as well.","metadata":{"id":"xO-Zdj-5cxXp"}},{"cell_type":"code","source":"wer_metric = load_metric(\"wer\")","metadata":{"id":"9Xsux2gmyXso","outputId":"18ceeb9e-1a0d-4ee8-f511-a12ad3608bf1","execution":{"iopub.status.busy":"2022-07-02T01:46:49.209912Z","iopub.execute_input":"2022-07-02T01:46:49.210814Z","iopub.status.idle":"2022-07-02T01:46:50.114728Z","shell.execute_reply.started":"2022-07-02T01:46:49.210764Z","shell.execute_reply":"2022-07-02T01:46:50.113749Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The model will return a sequence of logit vectors:\n$\\mathbf{y}_1, \\ldots, \\mathbf{y}_m$ with $\\mathbf{y}_1 = f_{\\theta}(x_1, \\ldots, x_n)[0]$ and $n >> m$.\n\nA logit vector $\\mathbf{y}_1$ contains the log-odds for each word in the vocabulary we defined earlier, thus $\\text{len}(\\mathbf{y}_i) =$ `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 ${}^1$.","metadata":{"id":"E1qZU5p-deqB"}},{"cell_type":"code","source":"def compute_metrics(pred):\n    pred_logits = pred.predictions\n    pred_ids = np.argmax(pred_logits, axis=-1)\n\n    pred.label_ids[pred.label_ids == -100] = processor.tokenizer.pad_token_id\n\n    pred_str = processor.batch_decode(pred_ids)\n    # we do not want to group tokens when computing the metrics\n    label_str = processor.batch_decode(pred.label_ids, group_tokens=False)\n\n    wer = wer_metric.compute(predictions=pred_str, references=label_str)\n\n    return {\"wer\": wer}","metadata":{"id":"1XZ-kjweyTy_","execution":{"iopub.status.busy":"2022-07-02T01:46:57.783975Z","iopub.execute_input":"2022-07-02T01:46:57.78489Z","iopub.status.idle":"2022-07-02T01:46:57.792698Z","shell.execute_reply.started":"2022-07-02T01:46:57.784836Z","shell.execute_reply":"2022-07-02T01:46:57.790854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now, we can load the pretrained checkpoint of [Wav2Vec2-XLS-R-300M](https://huggingface.co/facebook/wav2vec2-xls-r-300m). The tokenizer's `pad_token_id` must be to define the model's `pad_token_id` or in the case of `Wav2Vec2ForCTC` also CTC's *blank token* ${}^2$. To save GPU memory, we enable PyTorch's [gradient checkpointing](https://pytorch.org/docs/stable/checkpoint.html) and also set the loss reduction to \"*mean*\".\n\n**Note**: When using this notebook the hyper-parameter settings might not work very well. Feel free to adapt those depending on your use case. ","metadata":{"id":"Xmgrx4bRwLIH"}},{"cell_type":"code","source":"from transformers import Wav2Vec2ForCTC\n\nmodel = Wav2Vec2ForCTC.from_pretrained(\n    \"facebook/wav2vec2-xls-r-300m\", \n    attention_dropout=0.0,\n    hidden_dropout=0.0,\n    feat_proj_dropout=0.0,\n    mask_time_prob=0.05,\n    layerdrop=0.0,\n    ctc_loss_reduction=\"mean\", \n    pad_token_id=processor.tokenizer.pad_token_id,\n    vocab_size=len(processor.tokenizer),\n)","metadata":{"id":"e7cqAWIayn6w","outputId":"3e6cebea-78ef-45df-87b0-f63b78ba9644","execution":{"iopub.status.busy":"2022-07-02T01:47:09.483264Z","iopub.execute_input":"2022-07-02T01:47:09.483888Z","iopub.status.idle":"2022-07-02T01:48:12.269051Z","shell.execute_reply.started":"2022-07-02T01:47:09.483849Z","shell.execute_reply":"2022-07-02T01:48:12.267939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The first component of XLS-R 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](https://arxiv.org/pdf/2006.13979.pdf) does not need to be fine-tuned anymore. \nThus, we can set the `requires_grad` to `False` for all parameters of the *feature extraction* part.","metadata":{"id":"1DwR3XLSzGDD"}},{"cell_type":"code","source":"model.freeze_feature_extractor()","metadata":{"id":"oGI8zObtZ3V0","execution":{"iopub.status.busy":"2022-07-02T02:01:47.753811Z","iopub.execute_input":"2022-07-02T02:01:47.754647Z","iopub.status.idle":"2022-07-02T02:01:47.762862Z","shell.execute_reply.started":"2022-07-02T02:01:47.754608Z","shell.execute_reply":"2022-07-02T02:01:47.761473Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In a final step, we define all parameters related to training. \nTo give more explanation on some of the parameters:\n- `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 model\n- `learning_rate` and `weight_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.\n\nFor more explanations on other parameters, one can take a look at the [docs](https://huggingface.co/transformers/master/main_classes/trainer.html?highlight=trainer#trainingarguments).\n","metadata":{"id":"lD4aGhQM0K-D"}},{"cell_type":"code","source":"from transformers import TrainingArguments\n\nstep_n = 20 # Change it to 400 while training.\n\ntraining_args = TrainingArguments(\n  output_dir=\"./\",\n  group_by_length=True,\n  per_device_train_batch_size=16,\n  gradient_accumulation_steps=2,\n  evaluation_strategy=\"steps\",\n  num_train_epochs=30,\n  gradient_checkpointing=True,\n  fp16=True,\n  save_steps=step_n,\n  eval_steps=step_n,\n  logging_steps=step_n,\n  learning_rate=3e-4,\n  warmup_steps=500,\n  save_total_limit=2,\n  push_to_hub=False,\n)","metadata":{"id":"KbeKSV7uzGPP","execution":{"iopub.status.busy":"2022-07-02T02:01:53.127593Z","iopub.execute_input":"2022-07-02T02:01:53.128777Z","iopub.status.idle":"2022-07-02T02:01:53.27295Z","shell.execute_reply.started":"2022-07-02T02:01:53.128727Z","shell.execute_reply":"2022-07-02T02:01:53.271962Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now, all instances can be passed to Trainer and we are ready to start training!","metadata":{"id":"OsW-WZcL1ZtN"}},{"cell_type":"code","source":"from transformers import Trainer\n\ntrainer = Trainer(\n    model=model,\n    data_collator=data_collator,\n    args=training_args,\n    compute_metrics=compute_metrics,\n    train_dataset=common_voice_train,\n    eval_dataset=common_voice_test,\n    tokenizer=processor.feature_extractor,\n)","metadata":{"id":"rY7vBmFCPFgC","outputId":"c47ecc78-5259-44db-c121-5bd7945defb8","execution":{"iopub.status.busy":"2022-07-02T02:02:00.446722Z","iopub.execute_input":"2022-07-02T02:02:00.447097Z","iopub.status.idle":"2022-07-02T02:02:07.74064Z","shell.execute_reply.started":"2022-07-02T02:02:00.447066Z","shell.execute_reply":"2022-07-02T02:02:07.739576Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Training","metadata":{"id":"rpvZHM1xReIW"}},{"cell_type":"code","source":"import os\nos.environ[\"WANDB_DISABLED\"] = \"true\"","metadata":{"execution":{"iopub.status.busy":"2022-07-02T02:02:07.744227Z","iopub.execute_input":"2022-07-02T02:02:07.7451Z","iopub.status.idle":"2022-07-02T02:02:07.751777Z","shell.execute_reply.started":"2022-07-02T02:02:07.74507Z","shell.execute_reply":"2022-07-02T02:02:07.74985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainer.train()","metadata":{"id":"9fRr9TG5pGBl","outputId":"122ea040-7b24-452a-c7d4-7e72e1c46973","execution":{"iopub.status.busy":"2022-07-02T02:02:16.786975Z","iopub.execute_input":"2022-07-02T02:02:16.787366Z","iopub.status.idle":"2022-07-02T02:24:47.539267Z","shell.execute_reply.started":"2022-07-02T02:02:16.787334Z","shell.execute_reply":"2022-07-02T02:24:47.536887Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Resources:\n* blog: https://huggingface.co/blog/fine-tune-xlsr-wav2vec2","metadata":{}},{"cell_type":"code","source":"!ls","metadata":{},"execution_count":null,"outputs":[]}]}