# Дообучение модели ASR

В этом разделе мы рассмотрим пошаговое руководство по дообучению модели Whisper для распознавания речи на наборе данных Common
Voice 13. Мы будем использовать версию модели 'small' и относительно небольшой набор данных, что позволит вам провести настройку
довольно быстро на любом GPU объемом памяти 16 ГБ и более с небольшими требованиями к дисковому пространству, на таком GPU как T4
объемом 16 ГБ, предоставляемый в бесплатной версии Google Colab.

Если у вас GPU с меньшим объемом памяти или возникают проблемы с памятью во время обучения, вы можете следовать предложенным советам
по снижению использования памяти. Если у вас есть доступ к GPU с бОльшим объемом памяти, вы можете изменить аргументы обучения для
максимизации производительности. Таким образом, это руководство доступно независимо от спецификаций вашего GPU!

Так же это руководство описывает, как дообучить модель Whisper для языка Дивехи. Однако шаги, описанные здесь, обобщаются на любой
язык в наборе данных Common Voice и будут работать для любого набора данных распознавания речи на Hub Hugging Face. Вы можете изменить
код, чтобы быстро переключиться на необходимый вам язык и дообучить модель Whisper на вашем родном языке 🌍

Отлично! Теперь, когда мы разобрались с этим, давайте начнем и запустим нашу процедуру дообучения модели!

## Подготовка окружения

Мы настоятельно рекомендуем вам загружать контрольные точки модели непосредственно на [Hugging Face Hub](https://huggingface.co/) во время обучения.
Hugging Face Hub предоставляет следующие возможности:
- Встроенный контроль версий: вы можете быть уверены, что ни одна контрольная точка модели не будет потеряна в процессе обучения.
- Журналы Tensorboard: отслеживание важных показателей в процессе обучения.
- Карты моделей: документирование того, что делает модель, и предполагаемых вариантов ее использования.
- Сообщество: простой способ обмена информацией и сотрудничества с сообществом! 🤗

Связать блокнот с Hub Hugging Face довольно просто - вам просто нужно ввести свой токен аутентификации Hugging Face Hub, когда вас
попросят. Найдите свой токен аутентификации Hugging Face Hub [здесь](https://huggingface.co/settings/tokens) и введите его, когда вас попросят:

```python
from huggingface_hub import notebook_login

notebook_login()
```

**Output:**
```bash
Login successful
Your token has been saved to /root/.huggingface/token
```

## Загрузка набора данных

[Common Voice 13](https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0) содержит примерно десять часов
размеченных данных для Дивехи, из которых три часа являются тестовой частью данных. Этих данных крайне мало для дообучения модели,
поэтому мы будем опираться на обширные многоязыковые знания в области распознавания речи, накопленные Whisper во время предварительного
обучения, для низкоресурсного языка Дивехи.

Используя 🤗 Datasets, загрузка и подготовка данных чрезвычайно просты. Мы можем загрузить и подготовить разделения набора данных Common
Voice 13 всего за одну строку кода. Так как Дивехи имеет очень небольшой объем данных, мы объединим выборки `train` и `validation`, чтобы
получить примерно семь часов данных для обучения. Мы будем использовать три часа данных выборки `test` в качестве нашего тестового набора данных:

```python
from datasets import load_dataset, DatasetDict

common_voice = DatasetDict()

common_voice["train"] = load_dataset(
    "mozilla-foundation/common_voice_13_0", "dv", split="train+validation"
)
common_voice["test"] = load_dataset(
    "mozilla-foundation/common_voice_13_0", "dv", split="test"
)

print(common_voice)
```

**Output:**
```
DatasetDict({
    train: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment', 'variant'],
        num_rows: 4904
    })
    test: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment', 'variant'],
        num_rows: 2212
    })
})
```

    Вы можете изменить идентификатор языка с `"dv"` на идентификатор языка по вашему выбору. Чтобы увидеть все возможные
    языки в Common Voice 13, ознакомьтесь с карточкой набора данных на Hub Hugging Face: https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0

Большинство наборов данных для распознавания речи предоставляют только аудиофайлы для ввода (`audio`) и соответствующий
транскрибированный текст (`sentence`). В наборе данных Common Voice есть дополнительная метаинформация, такая как `accent`
и `locale`, которую мы можем не учитывать для ASR. Сохраняя блокнот максимально общим, мы будем рассматривать только входное
аудио и транскрибированный текст для дообучения модели, отбрасывая дополнительную метаинформацию:

```python
common_voice = common_voice.select_columns(["audio", "sentence"])
```

## Извлечение признаков, токенизатор и обработчик

Конвеер для задачи распознавания речи можно разделить на три этапа:

1. Извлекатель признаков, который предварительно обрабатывает сырой аудиоввод и создает логарифмические мел-спектрограммы
2. Модель, выполняющая отображение последовательности в последовательность
3. Токенизатор, который выполняет постобработку предсказанных токенов в текст

В библиотеке 🤗 Transformers для модели Whisper существуют связанные извлекатель признаков и токенизатор, называемые
[WhisperFeatureExtractor](https://huggingface.co/docs/transformers/main/model_doc/whisper#transformers.WhisperFeatureExtractor)
и [WhisperTokenizer](https://huggingface.co/docs/transformers/main/model_doc/whisper#transformers.WhisperTokenizer) соответственно.
Для упрощения работы с ними, эти два объекта объединены в одном классе, названном [WhisperProcessor](https://huggingface.co/docs/transformers/model_doc/whisper#transformers.WhisperProcessor).
Мы можем вызвать WhisperProcessor для выполнения как предварительной обработки звука, так и постобработки текстового токена.
При этом во время обучения нам нужно отслеживать только два объекта: процессор и модель.

При выполнении многоязыкового дообучения модели нам необходимо установить параметры `"language"` и `"task"` при создании процессора.
Параметр `"language"` должен быть установлен на язык исходного аудио, а параметр `"task"` на `"transcribe"` для распознавания речи или
на `"translate"` для перевода речи. Эти аргументы изменяют поведение токенизатора и должны быть правильно установлены, чтобы обеспечить
правильное кодирование целевых меток.

Мы можем увидеть все возможные поддерживаемые языки моделью Whisper, импортировав список языков:

```python
from transformers.models.whisper.tokenization_whisper import TO_LANGUAGE_CODE

TO_LANGUAGE_CODE
```

Если вы прокрутите этот список, вы заметите, что многие языки присутствуют, но Дивехи в нем нет! Это означает, что модель
Whisper не была предварительно обучена на Дивехи. Однако это не означает, что мы не можем дообучить Whisper на этом языке.
Таким образом, мы будем обучать Whisper новому языку, который не поддерживается предварительно обученной контрольной точкой.
Это довольно круто, не так ли?

При дообучения модели на новый язык Whisper хорошо использует свои знания о других 96 языках, на которых эта модель была
предварительно обучена. В целом, все современные языки будут лингвистически похожи хотя бы на один из 96 языков, которые Whisper
уже знает, и мы будем попадать под эту парадигму кросс-языкового представления знаний.

Для дообучения модели Whisper на новый язык нам нужно найти **самый похожий** язык, на котором Whisper был предварительно обучен.
Статья в Википедии о Дивехи утверждает, что Дивехи тесно связан с Сингальским языком Шри-Ланки. Если мы снова проверим коды языков,
мы увидим, что Сингальский присутствует в наборе языков Whisper, поэтому мы можем безопасно установить аргумент "language" в значение `"sinhalese"`.

Отлично! Мы загрузим наш процессор из предварительно обученной контрольной точки, установив язык на `"sinhalese"` и задачу на `"transcribe"`,
как объяснено выше:

```python
from transformers import WhisperProcessor

processor = WhisperProcessor.from_pretrained(
    "openai/whisper-small", language="sinhalese", task="transcribe"
)
```

Стоит отметить, что в большинстве случаев вы обнаружите, что язык, на котором вы хотите дообучить модель, есть в наборе языков
предобученной модели, в этом случае вы можете просто установить язык напрямую как язык вашего аудиоввода! Обратите внимание,
что оба эти аргумента следует исключить для дообучения только на английском языке, когда есть только один вариант для языка (`"English"`)
и задачи (`"transcribe"`).

## Предварительная обработка данных

Давайте посмотрим на характеристики набора данных. Особое внимание уделяем колонке `"audio"` - это подробности о частоте
дискретизации наших аудиовходов:

```python
common_voice["train"].features
```
**Output:**
```
{'audio': Audio(sampling_rate=48000, mono=True, decode=True, id=None),
 'sentence': Value(dtype='string', id=None)}
```

Поскольку наше входной звук дискретизируется на частоте 48 кГц, нам нужно уменьшить частоту дискретизации до 16 кГц
перед передачей его в извлекатель признаков Whisper, так как модель Whisper ожидает аудио с частотой дискретизации 16 кГц.

Мы установим необходимую частоту дискретизации аудиовводов набора данных с помощью метода [`cast_column`](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.cast_column).
Эта операция не изменяет звук непосредственно, а сигнализирует набору данных о пересемплировании аудиообразцов при их загрузке на лету:

```python
from datasets import Audio

sampling_rate = processor.feature_extractor.sampling_rate
common_voice = common_voice.cast_column("audio", Audio(sampling_rate=sampling_rate))
```

Теперь мы можем написать функцию для подготовки наших данных к использованию моделью:

1. Мы загружаем и выполняем пересемплирование аудиоданных на основе выборки, вызывая sample["audio"]. Как уже было объяснено, библиотека
🤗 Datasets выполняет все необходимые операции пересемплирования на лету.
2. Мы используем извлекатель признаков для вычисления входных признаков в виде логарифмической мел-спектрограммы из нашего одномерного аудио-массива.
3. Мы кодируем транскрипции в идентификаторы меток с помощью токенизатора.

```python
def prepare_dataset(example):
    audio = example["audio"]

    example = processor(
        audio=audio["array"],
        sampling_rate=audio["sampling_rate"],
        text=example["sentence"],
    )

    # compute input length of audio sample in seconds
    example["input_length"] = len(audio["array"]) / audio["sampling_rate"]

    return example
```

Мы можем применить функцию подготовки данных ко всем нашим обучающим примерам, используя метод `.map` библиотеки 🤗 Datasets.
Мы удалим столбцы из исходных данных обучения (аудио и текст), оставив только столбцы, возвращаемые функцией `prepare_dataset`:

```python
common_voice = common_voice.map(
    prepare_dataset, remove_columns=common_voice.column_names["train"], num_proc=1
)
```

Наконец, мы фильтруем любые обучающие данные с аудиообразцами длиннее 30 секунд. В противном случае эти образцы могли бы быть усечены
извлекателем признаков Whisper, что может повлиять на стабильность обучения. Мы определяем функцию, которая возвращает `True` для образцов,
которые короче 30 секунд, и `False` для тех, что длиннее:

```python
max_input_length = 30.0

def is_audio_in_length_range(length):
    return length  Dict[str, torch.Tensor]:
        # split inputs and labels since they have to be of different lengths and need different padding methods
        # first treat the audio inputs by simply returning torch tensors
        input_features = [
            {"input_features": feature["input_features"][0]} for feature in features
        ]
        batch = self.processor.feature_extractor.pad(input_features, return_tensors="pt")

        # get the tokenized label sequences
        label_features = [{"input_ids": feature["labels"]} for feature in features]
        # pad the labels to max length
        labels_batch = self.processor.tokenizer.pad(label_features, return_tensors="pt")

        # replace padding with -100 to ignore loss correctly
        labels = labels_batch["input_ids"].masked_fill(
            labels_batch.attention_mask.ne(1), -100
        )

        # if bos token is appended in previous tokenization step,
        # cut bos token here as it's append later anyways
        if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all().cpu().item():
            labels = labels[:, 1:]

        batch["labels"] = labels

        return batch
```

Теперь мы можем инициализировать только что определенный сборщик данных:

```python
data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor=processor)
```

Продолжаем!

### Метрики оценки

Далее мы определяем метрику оценки, которую будем использовать на нашем наборе для оценки. Мы будем использовать метрику
Word Error Rate (WER), представленную в разделе [Оценка](evaluation), это "де-факто" метрика для оценки систем автоматического распознавания речи.

Давайте загрузим метрику WER из библиотеки 🤗 Evaluate:

```python
import evaluate

metric = evaluate.load("wer")
```

Затем нам просто нужно определить функцию, которая принимает предсказания нашей модели и возвращает метрику WER. Эта функция,
называемая `compute_metrics`, сначала заменяет `-100` на `pad_token_id` в `label_ids` (отменяя шаг, который мы применили в
коллекторе данных, чтобы правильно игнорировать заполненные токены при вычислении потерь). Затем он декодирует предсказанные
идентификаторы и идентификаторы меток в строки. Наконец, она вычисляет WER между предсказаниями и эталонными метками.
Здесь у нас есть возможность оценить "нормализованные" транскрипции и предсказания, у которых удалены знаки препинания и регистр.
Мы рекомендуем следовать этому совету, чтобы получить преимущество от улучшения WER, полученного путем нормализации транскрипций.

```python
from transformers.models.whisper.english_normalizer import BasicTextNormalizer

normalizer = BasicTextNormalizer()

def compute_metrics(pred):
    pred_ids = pred.predictions
    label_ids = pred.label_ids

    # replace -100 with the pad_token_id
    label_ids[label_ids == -100] = processor.tokenizer.pad_token_id

    # we do not want to group tokens when computing the metrics
    pred_str = processor.batch_decode(pred_ids, skip_special_tokens=True)
    label_str = processor.batch_decode(label_ids, skip_special_tokens=True)

    # compute orthographic wer
    wer_ortho = 100 * metric.compute(predictions=pred_str, references=label_str)

    # compute normalised WER
    pred_str_norm = [normalizer(pred) for pred in pred_str]
    label_str_norm = [normalizer(label) for label in label_str]
    # filtering step to only evaluate the samples that correspond to non-zero references:
    pred_str_norm = [
        pred_str_norm[i] for i in range(len(pred_str_norm)) if len(label_str_norm[i]) > 0
    ]
    label_str_norm = [
        label_str_norm[i]
        for i in range(len(label_str_norm))
        if len(label_str_norm[i]) > 0
    ]

    wer = 100 * metric.compute(predictions=pred_str_norm, references=label_str_norm)

    return {"wer_ortho": wer_ortho, "wer": wer}
```

### Загружаем предобученную контрольную точку

Теперь давайте загрузим предварительно обученную контрольную точку Whisper small. Опять же, это делается с помощью 🤗 Transformers без труда!

```python
from transformers import WhisperForConditionalGeneration

model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small")
```

Мы установим `use_cache` в значение `False` для обучения, так как мы используем [проверку градиента](https://huggingface.co/docs/transformers/v4.18.0/en/performance#gradient-checkpointing),
и эти две опции несовместимы. Мы также переопределим два аргумента для генерации, чтобы контролировать поведение модели во время вывода:
мы принудительно зададим токены языка и задачи во время генерации, установив аргументы `language` и `task`, и также снова включим
кэш для генерации, чтобы ускорить время вывода:

```python
from functools import partial

# disable cache during training since it's incompatible with gradient checkpointing
model.config.use_cache = False

# set language and task for generation and re-enable cache
model.generate = partial(
    model.generate, language="sinhalese", task="transcribe", use_cache=True
)
```

## Определение конфигурации обучения

На последнем этапе мы определяем все параметры, связанные с обучением. Здесь мы устанавливаем количество шагов обучения на 500.
Этого количества шагов достаточно, чтобы увидеть большое улучшение WER по сравнению с предварительно обученной моделью Whisper,
при этом обеспечивая возможность выполнения дообучения в течение примерно 45 минут в бесплатном тарифе Google Colab.
Для получения более подробной информации о параметрах обучения обратитесь к документации по Seq2SeqTrainingArguments [здесь](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.Seq2SeqTrainingArguments).

```python
from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="./whisper-small-dv",  # name on the HF Hub
    per_device_train_batch_size=16,
    gradient_accumulation_steps=1,  # increase by 2x for every 2x decrease in batch size
    learning_rate=1e-5,
    lr_scheduler_type="constant_with_warmup",
    warmup_steps=50,
    max_steps=500,  # increase to 4000 if you have your own GPU or a Colab paid plan
    gradient_checkpointing=True,
    fp16=True,
    fp16_full_eval=True,
    evaluation_strategy="steps",
    per_device_eval_batch_size=16,
    predict_with_generate=True,
    generation_max_length=225,
    save_steps=500,
    eval_steps=500,
    logging_steps=25,
    report_to=["tensorboard"],
    load_best_model_at_end=True,
    metric_for_best_model="wer",
    greater_is_better=False,
    push_to_hub=True,
)
```

    Если вы не хотите загружать контрольные точки модели на Hugging Face Hub, установите `push_to_hub=False`.

Мы можем передать аргументы обучения в 🤗 Trainer вместе с нашей моделью, набором данных, сборщиком данных и функцией `compute_metrics`:

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    args=training_args,
    model=model,
    train_dataset=common_voice["train"],
    eval_dataset=common_voice["test"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
    tokenizer=processor,
)
```

И теперь мы готовы приступить к обучению!

### Обучение

Для запуска обучения просто выполните:

```python
trainer.train()
```

Обучение займет примерно 45 минут в зависимости от вашего графического ускорителя или того, который выделен в Google Colab.
В зависимости от вашего GPU возможно, что при запуске обучения вы столкнетесь с ошибкой CUDA "out-of-memory". В этом случае
вы можете уменьшать `per_device_train_batch_size` постепенно вдвое и использовать [`gradient_accumulation_steps`](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.Seq2SeqTrainingArguments.gradient_accumulation_steps)
для компенсации.

**Output:**

| Training Loss | Epoch | Step | Validation Loss | Wer Ortho | Wer     |
|:-------------:|:-----:|:----:|:---------------:|:---------:|:-------:|
| 0.136         | 1.63  | 500  | 0.1727          | 63.8972   | 14.0661 |

Наш окончательный WER составляет 14,1% - не плохо для семи часов данных обучения и всего 500 шагов обучения! Это означает
улучшение на 112% по сравнению с предварительно обученной моделью! Это означает, что мы взяли модель, которая ранее не имела знаний
о Дивехи, и успешно дообучили ее распознавать речь на Дивехи с достаточной точностью менее чем за один час 🤯

Большой вопрос заключается в том, как это сравнивать с другими системами распознавания речи. Для этого мы можем посмотреть на [доску лидеров autoevaluate](https://huggingface.co/spaces/autoevaluate/leaderboards?dataset=mozilla-foundation%2Fcommon_voice_13_0&only_verified=0&task=automatic-speech-recognition&config=dv&split=test&metric=wer),
которая классифицирует модели по языку и набору данных, а затем ранжирует их по их WER.

Посмотрев на доску лидеров, мы видим, что наша модель, обученная за 500 шагов, убедительно обгоняет предварительно обученный [Whisper Small](https://huggingface.co/openai/whisper-small),
который мы оценивали в предыдущем разделе. Отличная работа 👏

Мы видим, что есть несколько контрольных точек, которые показывают лучший результат, чем тот, который мы обучили. Прелесть платформы
Hugging Face Hub заключается в том, что это *коллаборативная* платформа - если у нас нет времени или ресурсов для проведения более
длительного обучения самостоятельно, мы можем загрузить контрольную точку, которую кто-то из сообщества обучил и любезно поделился ей
(не забудьте поблагодарить его за это!). Вы сможете загрузить эти контрольные точки так же, как и предварительно обученные модели, используя
класс `pipeline`, как мы делали ранее! Так что нет ничего, что бы могло помешать вам выбрать лучшую модель из доски лидеров для использования в вашей задаче!

Мы можем автоматически отправить нашу контрольную точку на доску лидеров, когда мы публикуем результаты обучения на Hugging Face Hub - нам
просто нужно установить соответствующие аргументы (kwargs). Вы можете изменить эти значения, чтобы они соответствовали вашему набору данных,
языку и имени модели:

```python
kwargs = {
    "dataset_tags": "mozilla-foundation/common_voice_13_0",
    "dataset": "Common Voice 13",  # a 'pretty' name for the training dataset
    "language": "dv",
    "model_name": "Whisper Small Dv - Sanchit Gandhi",  # a 'pretty' name for your model
    "finetuned_from": "openai/whisper-small",
    "tasks": "automatic-speech-recognition",
}
```

Теперь результаты обучения можно загрузить на Hugging Face Hub. Для этого выполните команду push_to_hub:

```python
trainer.push_to_hub(**kwargs)
```

Это сохранит журналы обучения и веса модели в папке `"ваше-имя-пользователя/имя-которое-вы-выбрали"`. В этом примере посмотрите
загрузку по адресу `sanchit-gandhi/whisper-small-dv`.

Хотя дообученная модель дает удовлетворительные результаты на тестовых данных Common Voice 13 на Дивехи, это далеко не оптимальный результат.
Цель данного руководства - показать, как настроить модель ASR с использованием 🤗 Trainer для многоязычного распознавания речи.

Если у вас есть доступ к собственному GPU или вы подписаны на платный план Google Colab, вы можете увеличить max_steps до 4000, чтобы
дополнительно улучшить WER, обучаясь больше шагов. Обучение на 4000 шагов займет примерно 3-5 часов в зависимости от вашего GPU и приведет
к результатам WER примерно на 3% ниже, чем при обучении на 500 шагах. Если вы решите обучить на 4000 шагов, мы также рекомендуем изменить
планировщик скорости обучения на *линейный* (установите `lr_scheduler_type="linear"`), так как это также принесет дополнительный прирост
производительности при длительных обучающихся сессиях.

Результаты могут быть дальше улучшены путем оптимизации гиперпараметров обучения, таких как _скорость обучения_ (_learning rate_) и _dropout_,
а также использования более крупной предварительно обученной контрольной точки (`medium` или `large`). Мы оставляем это как упражнение для читателя.

## Совместный доступ к модели

Теперь вы можете поделиться этой моделью с любым пользователем, используя ссылку на Hugging Face Hub. Они могут загрузить ее с идентификатором
`"your-username/the-name-you-picked"` непосредственно в объект `pipeline()`. Например, для загрузки дообученной контрольной
точки ["sanchit-gandhi/whisper-small-dv"](https://huggingface.co/sanchit-gandhi/whisper-small-dv):

```python
from transformers import pipeline

pipe = pipeline("automatic-speech-recognition", model="sanchit-gandhi/whisper-small-dv")
```

## Заключение

В этом разделе мы рассмотрели пошаговое руководство по дообучению модели Whisper для распознавания речи с использованием 🤗 Datasets,
Transformers и Hugging Face Hub. Сначала мы загрузили набор данных Dhivehi из Common Voice 13 и предобработали его, вычислив лог-мел-спектрограммы
и токенизировав текст. Затем мы определили способ обработки данных, метрику оценки и параметры обучения, прежде чем использовать 🤗 Trainer
для обучения и оценки нашей модели. Мы закончили, загрузив настроенную модель на Hugging Face Hub, и показали, как поделиться и использовать ее
с помощью класса `pipeline()`.

Если вы следовали этому руководству до этой точки, у вас теперь есть дообученная контрольная точка для распознавания речи, отличная работа! 🥳
Еще более важно то, что вы экипированы всеми инструментами, необходимыми для дообучения модели Whisper на любом наборе данных или в любой предметной
области. Так что чего вы ждете! Выберите один из наборов данных, охваченных в разделе [Выбор набора данных](choosing_dataset), или выберите свой
собственный набор данных и посмотрите, сможете ли вы достичь современной производительности (state-of-the-art performance)! Лидерборд ждет вас...

