音訊課程文件

微調 ASR 模型

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

微調 ASR 模型

在本節中,我們將逐步指導您如何在 Common Voice 13 資料集上微調 Whisper 進行語音識別。我們將使用模型的“small”版本和相對輕量級的資料集,使您能夠在任何具有 16GB+ GPU 且磁碟空間需求較低的裝置上快速執行微調,例如 Google Colab 免費層提供的 16GB T4 GPU。

如果您有較小的 GPU 或在訓練期間遇到記憶體問題,可以遵循所提供的建議來減少記憶體使用。相反,如果您可以使用更大的 GPU,則可以修改訓練引數以最大化吞吐量。因此,無論您的 GPU 規格如何,本指南都適用!

同樣,本指南概述瞭如何微調馬爾地夫語的 Whisper 模型。然而,此處介紹的步驟適用於 Common Voice 資料集中的任何語言,更普遍地適用於 Hugging Face Hub 上的任何 ASR 資料集。您可以調整程式碼以快速切換到您選擇的語言,並用您的母語微調 Whisper 模型 🌍

好的!現在,讓我們開始並啟動我們的微調管道!

準備環境

我們強烈建議您在訓練期間將模型檢查點直接上傳到 Hugging Face Hub。Hub 提供

  • 整合的版本控制:你可以確保在訓練過程中不會丟失任何模型檢查點。
  • Tensorboard 日誌:在訓練過程中跟蹤重要指標。
  • 模型卡片:記錄模型的功能及其預期用途。
  • 社群:一種與社群分享和協作的簡單方式!🤗

將筆記本連結到 Hub 很簡單 - 只需在提示時輸入您的 Hub 身份驗證令牌即可。在此處找到您的 Hub 身份驗證令牌:here 並在提示時輸入

from huggingface_hub import notebook_login

notebook_login()

輸出

Login successful
Your token has been saved to /root/.huggingface/token

載入資料集

Common Voice 13 包含大約十小時的馬爾地夫語標記資料,其中三小時是保留的測試資料。這對於微調來說資料量極少,因此我們將依靠利用 Whisper 在預訓練期間獲得的廣泛多語言 ASR 知識來處理資源稀缺的馬爾地夫語。

使用 🤗 Datasets,下載和準備資料非常簡單。我們只需一行程式碼即可下載和準備 Common Voice 13 分割。由於馬爾地夫語資源非常稀缺,我們將合併 `train` 和 `validation` 分割,以提供大約七小時的訓練資料。我們將使用三小時的 `test` 資料作為我們的保留測試集

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)

輸出

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 中所有可能的語言,請檢視 Hugging Face Hub 上的資料集卡片:https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0

大多數 ASR 資料集只提供輸入音訊樣本(`audio`)和相應的轉錄文字(`sentence`)。Common Voice 包含額外的元資料資訊,如 `accent` 和 `locale`,這些資訊我們可以忽略,因為它們與 ASR 無關。為了使筆記本儘可能通用,我們只考慮輸入音訊和轉錄文字進行微調,而忽略額外的元資料資訊

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

特徵提取器、分詞器和處理器

ASR 管道可分為三個階段:

  1. 特徵提取器,將原始音訊輸入預處理為對數梅爾頻譜圖。
  2. 執行序列到序列對映的模型。
  3. 分詞器,將預測的標記後處理為文字。

在 🤗 Transformers 中,Whisper 模型具有關聯的特徵提取器和分詞器,分別稱為 `WhisperFeatureExtractor``WhisperTokenizer`。為了簡化我們的工作,這兩個物件被封裝在一個名為 `WhisperProcessor` 的單一類中。我們可以呼叫 `WhisperProcessor` 來同時執行音訊預處理和文字標記後處理。這樣,在訓練過程中我們只需跟蹤兩個物件:處理器和模型。

執行多語言微調時,在例項化處理器時需要設定 `“language”` 和 `“task”`。`“language”` 應設定為源音訊語言,任務設定為 `“transcribe”` 用於語音識別或 `“translate”` 用於語音翻譯。這些引數會修改分詞器的行為,應正確設定以確保目標標籤正確編碼。

我們可以透過匯入語言列表來檢視 Whisper 支援的所有可能語言

from transformers.models.whisper.tokenization_whisper import TO_LANGUAGE_CODE

TO_LANGUAGE_CODE

如果您滾動瀏覽此列表,您會注意到許多語言都存在,但馬爾地夫語是少數不存在的語言之一!這意味著 Whisper 沒有在馬爾地夫語上進行預訓練。然而,這並不意味著我們不能在其上微調 Whisper。這樣做,我們將教 Whisper 一種新語言,一個預訓練檢查點不支援的語言。這很酷,對吧!

當您在新語言上微調 Whisper 時,它會很好地利用其在預訓練的另外 96 種語言方面的知識。總的來說,所有現代語言在語言學上都會與 Whisper 已知的 96 種語言中的至少一種相似,因此我們將落入這種跨語言知識表示的正規化。

要在新語言上微調 Whisper,我們需要找到 Whisper 在預訓練時所使用的**最相似**的語言。馬爾地夫語的維基百科文章指出,馬爾地夫語與斯里蘭卡的僧伽羅語密切相關。如果我們再次檢查語言程式碼,我們可以看到僧伽羅語存在於 Whisper 語言集中,因此我們可以安全地將語言引數設定為 `"sinhalese"`。

沒錯!我們將從預訓練檢查點載入處理器,並根據上述解釋將語言設定為 `"sinhalese"`,任務設定為 `"transcribe"`

from transformers import WhisperProcessor

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

值得重申的是,在大多數情況下,你會發現你想要微調的語言在預訓練語言集中,在這種情況下,你只需將語言直接設定為你的源音訊語言即可!請注意,對於僅限英語的微調,這兩個引數都應省略,因為語言(`“English”`)和任務(`“transcribe”`)只有一個選項。

預處理資料

讓我們看看資料集的特徵。特別注意 `“audio”` 列——這詳細說明了我們音訊輸入的取樣率。

common_voice["train"].features

輸出

{'audio': Audio(sampling_rate=48000, mono=True, decode=True, id=None),
 'sentence': Value(dtype='string', id=None)}

由於我們的輸入音訊以 48kHz 取樣,在將其傳遞給 Whisper 特徵提取器之前,我們需要將其**下采樣**至 16kHz,16kHz 是 Whisper 模型預期的取樣率。

我們將使用資料集的 `cast_column` 方法將音訊輸入設定為正確的取樣率。此操作不會就地更改音訊,而是向資料集發出訊號,以便在載入時動態重取樣音訊樣本

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. 我們透過使用分詞器將轉錄文字編碼為標籤 ID。
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

我們可以使用 🤗 Datasets 的 `.map` 方法將資料準備函式應用於所有訓練樣本。我們將從原始訓練資料(音訊和文字)中刪除列,只保留 `prepare_dataset` 函式返回的列

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

最後,我們過濾掉任何音訊樣本長度超過 30 秒的訓練資料。這些樣本否則會被 Whisper 特徵提取器截斷,這可能會影響訓練的穩定性。我們定義一個函式,對於小於 30 秒的樣本返回 `True`,對於更長的樣本返回 `False`。

max_input_length = 30.0


def is_audio_in_length_range(length):
    return length < max_input_length

我們透過 🤗 Datasets 的 `.filter` 方法將過濾器函式應用於訓練資料集的所有樣本。

common_voice["train"] = common_voice["train"].filter(
    is_audio_in_length_range,
    input_columns=["input_length"],
)

讓我們檢查一下透過此過濾步驟我們移除了多少訓練資料

common_voice["train"]

輸出

Dataset({
    features: ['input_features', 'labels', 'input_length'],
    num_rows: 4904
})

好的!在這種情況下,我們實際上擁有與之前相同數量的樣本,因此沒有樣本長度超過 30 秒。如果您切換語言,情況可能並非如此,因此最好保留此過濾步驟以增強穩健性。這樣,我們就為訓練完全準備好了資料!讓我們繼續看看如何使用這些資料來微調 Whisper。

訓練與評估

現在我們已經準備好資料,可以投入訓練流程了。 🤗 Trainer 將為我們承擔大部分繁重的工作。我們只需:

  • 定義一個數據收集器:資料收集器接收我們預處理的資料並準備好 PyTorch 張量供模型使用。

  • 評估指標:在評估期間,我們希望使用詞錯誤率 (WER) 指標評估模型。我們需要定義一個 `compute_metrics` 函式來處理此計算。

  • 載入預訓練檢查點:我們需要載入一個預訓練檢查點並對其進行正確配置以進行訓練。

  • 定義訓練引數:這些引數將由 🤗 Trainer 用於構建訓練計劃。

一旦我們微調了模型,我們將在測試資料上對其進行評估,以驗證我們已正確訓練它來轉錄馬爾地夫語語音。

定義資料整理器

序列到序列語音模型的資料整理器是獨特的,因為它獨立處理 `input_features` 和 `labels`:`input_features` 必須由特徵提取器處理,`labels` 必須由分詞器處理。

`input_features` 已填充到 30 秒並轉換為固定維度的對數梅爾譜圖,因此我們所要做的就是使用特徵提取器的 `.pad` 方法將其轉換為批處理的 PyTorch 張量,並設定 `return_tensors=pt`。請注意,這裡沒有應用額外的填充,因為輸入是固定維度的,`input_features` 只是簡單地轉換為 PyTorch 張量。

另一方面,`labels` 是未填充的。我們首先使用分詞器的 `.pad` 方法將序列填充到批次中的最大長度。然後,填充令牌被替換為 `-100`,以便在計算損失時**不**考慮這些令牌。然後,我們從標籤序列的開頭剪掉轉錄令牌的開頭,因為我們稍後在訓練期間會將其附加。

我們可以利用我們之前定義的 `WhisperProcessor` 來執行特徵提取器和分詞器操作。

import torch

from dataclasses import dataclass
from typing import Any, Dict, List, Union


@dataclass
class DataCollatorSpeechSeq2SeqWithPadding:
    processor: Any

    def __call__(
        self, features: List[Dict[str, Union[List[int], torch.Tensor]]]
    ) -> 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

現在我們可以初始化剛剛定義的資料收集器了。

data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor=processor)

前進!

評估指標

接下來,我們定義將在評估集上使用的評估指標。我們將使用在評估部分中介紹的詞錯誤率 (WER) 指標,這是評估 ASR 系統的“事實標準”指標。

我們將從 🤗 Evaluate 載入 WER 指標

import evaluate

metric = evaluate.load("wer")

然後,我們只需定義一個函式,該函式接受我們的模型預測並返回 WER 指標。這個名為 `compute_metrics` 的函式首先將 `label_ids` 中的 `-100` 替換為 `pad_token_id`(撤銷我們在資料整理器中應用的步驟,以正確忽略損失中的填充標記)。然後,它將預測和標籤 ID 解碼為字串。最後,它計算預測和參考標籤之間的 WER。在這裡,我們可以選擇使用“規範化”的轉錄和預測進行評估,這些轉錄和預測已刪除標點符號和大小寫。我們建議您遵循此操作,以從規範化轉錄獲得的 WER 改進中受益。

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,這非常簡單!

from transformers import WhisperForConditionalGeneration

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

我們將 `use_cache` 設定為 `False` 進行訓練,因為我們正在使用 梯度檢查點,兩者不相容。我們還將覆蓋兩個生成引數以控制模型在推理期間的行為:透過設定 `language` 和 `task` 引數強制在生成期間使用語言和任務標記,並重新啟用生成快取以加速推理時間

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 模型獲得顯著改進,同時確保在 Google Colab 免費層上大約 45 分鐘內完成微調。有關訓練引數的更多詳細資訊,請參閱 Seq2SeqTrainingArguments 文件

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,
)
如果您不想將模型檢查點上傳到 Hub,請設定 `push_to_hub=False`。

我們可以將訓練引數連同我們的模型、資料集、資料整理器和 `compute_metrics` 函式一起傳遞給 🤗 Trainer

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,
)

這樣,我們就可以開始訓練了!

訓練

要啟動訓練,只需執行

trainer.train()

訓練大約需要 45 分鐘,具體取決於您的 GPU 或分配給 Google Colab 的 GPU。根據您的 GPU,您在開始訓練時可能會遇到 CUDA “記憶體不足”錯誤。在這種情況下,您可以將 `per_device_train_batch_size` 逐步減小 2 倍,並採用 `gradient_accumulation_steps` 來補償。

輸出

訓練損失 輪次 步驟 驗證損失 正字法詞錯誤率 詞錯誤率 (Wer)
0.136 1.63 500 0.1727 63.8972 14.0661

我們最終的 WER 為 14.1% - 對於七小時的訓練資料和僅僅 500 步訓練來說,這還不錯!這相當於比預訓練模型提高了 112%!這意味著我們用不到一小時的時間,將一個以前對馬爾地夫語一無所知的模型微調到能夠以足夠準確的識別馬爾地夫語語音🤯

最大的問題是這與其他 ASR 系統相比如何。為此,我們可以檢視自動評估排行榜,該排行榜按語言和資料集對模型進行分類,然後根據其 WER 進行排名。

從排行榜上看,我們訓練了 500 步的模型,明顯優於我們在上一節中評估的預訓練 Whisper Small 檢查點。做得好 👏

我們看到有一些檢查點比我們訓練的更好。Hugging Face Hub 的美妙之處在於它是一個*協作*平臺——如果我們沒有時間或資源自己進行更長時間的訓練,我們可以載入社群中其他人訓練並樂於分享的檢查點(當然要感謝他們!)。您將能夠以與以前載入預訓練檢查點完全相同的方式使用 `pipeline` 類載入這些檢查點!因此,沒有任何東西可以阻止您選擇排行榜上最好的模型來完成您的任務!

我們可以透過將訓練結果推送到 Hub 時自動將檢查點提交到排行榜 - 我們只需設定適當的關鍵字引數(kwargs)。您可以更改這些值以匹配您的資料集、語言和模型名稱。

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",
}

訓練結果現在可以上傳到 Hub。為此,執行 `push_to_hub` 命令

trainer.push_to_hub(**kwargs)

這將把訓練日誌和模型權重儲存到 `“your-username/the-name-you-picked”` 下。對於這個例子,請檢視 `sanchit-gandhi/whisper-small-dv` 上的上傳。

儘管微調後的模型在 Common Voice 13 馬爾地夫語測試資料上取得了令人滿意的結果,但它絕不是最優的。本指南的目的是演示如何使用 🤗 Trainer 對 ASR 模型進行微調,以實現多語言語音識別。

如果您可以使用自己的 GPU 或訂閱了 Google Colab 付費計劃,您可以將 `max_steps` 增加到 4000 步,透過增加訓練步數進一步提高 WER。訓練 4000 步大約需要 3-5 小時,具體取決於您的 GPU,並且 WER 結果比訓練 500 步低約 3%。如果您決定訓練 4000 步,我們還建議將學習率排程器更改為*線性*排程(設定 `lr_scheduler_type="linear"`),因為這將在長時間訓練中帶來額外的效能提升。

透過最佳化訓練超引數,例如*學習率*和*dropout*,以及使用更大的預訓練檢查點(`medium` 或 `large`),結果可能會進一步改善。我們將其留作讀者的練習。

分享您的模型

現在,您可以使用 Hub 上的連結與任何人分享此模型。他們可以使用識別符號 `"your-username/the-name-you-picked"` 直接將其載入到 `pipeline()` 物件中。例如,要載入微調檢查點 “sanchit-gandhi/whisper-small-dv”

from transformers import pipeline

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

結論

在本節中,我們提供了使用 🤗 Datasets、Transformers 和 Hugging Face Hub 微調 Whisper 模型以進行語音識別的分步指南。我們首先載入 Common Voice 13 資料集的馬爾地夫語子集,並透過計算對數梅爾譜圖和文字分詞對其進行預處理。然後,我們定義了資料收集器、評估指標和訓練引數,最後使用 🤗 Trainer 訓練和評估了我們的模型。我們最終將微調後的模型上傳到 Hugging Face Hub,並展示瞭如何使用 `pipeline()` 類進行共享和使用。

如果您堅持到這一點,您現在應該擁有一個用於語音識別的微調檢查點,做得好!🥳 更重要的是,您已經掌握了在任何語音識別資料集或領域微調 Whisper 模型所需的所有工具。還在等什麼!選擇“選擇資料集”部分中涵蓋的一個數據集,或者選擇您自己的資料集,看看您能否獲得最先進的效能!排行榜正等著您……

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.