Ottimizza Whisper per ASR multilingue con 🤗 Transformers

Ottimizza Whisper per ASR multilingue con Transformers.

In questo blog, presentiamo una guida passo-passo per il fine-tuning di Whisper su qualsiasi dataset ASR multilingue utilizzando Hugging Face 🤗 Transformers. Questo blog fornisce spiegazioni dettagliate sul modello Whisper, sul dataset Common Voice e sulla teoria alla base del fine-tuning, con codice di esempio per eseguire le fasi di preparazione dei dati e di fine-tuning. Per una versione più snella del notebook con meno spiegazioni ma tutto il codice, consultare il Google Colab allegato.

Indice

  1. Introduzione
  2. Fine-tuning di Whisper in un Google Colab
    1. Preparazione dell’Ambiente
    2. Caricamento del Dataset
    3. Preparazione di Feature Extractor, Tokenizer e Dati
    4. Allenamento e Valutazione
    5. Creazione di una Demo
  3. Osservazioni Finali

Introduzione

Whisper è un modello pre-trained per il riconoscimento automatico della parola (ASR) pubblicato a settembre 2022 dagli autori Alec Radford et al. di OpenAI. A differenza di molti dei suoi predecessori, come Wav2Vec 2.0, che sono pre-trained su dati audio non etichettati, Whisper è pre-trained su una vasta quantità di dati di audio-trascrizione etichettati, per la precisione 680.000 ore. Questo è un ordine di grandezza di dati in più rispetto ai dati audio non etichettati utilizzati per addestrare Wav2Vec 2.0 (60.000 ore). Inoltre, 117.000 ore di questi dati di pre-training sono dati ASR multilingue. Ciò porta alla creazione di checkpoint che possono essere applicati a oltre 96 lingue, molte delle quali sono considerate a bassa risorsa.

Questa quantità di dati etichettati consente a Whisper di essere pre-trained direttamente sul compito supervisionato del riconoscimento della parola, apprendendo una mappatura da audio-trascrizione etichettata a testo 1 {}^1 1. Di conseguenza, Whisper richiede poco fine-tuning aggiuntivo per ottenere un modello ASR performante. Questo è in contrasto con Wav2Vec 2.0, che è pre-trained sul compito non supervisionato della predizione mascherata. Qui, il modello è addestrato per apprendere una mappatura intermedia da audio a stati nascosti solo dai dati audio non etichettati. Sebbene il pre-training non supervisionato produca rappresentazioni di alta qualità della parola, non apprende una mappatura da audio a testo. Questa mappatura viene appresa solo durante il fine-tuning, richiedendo quindi più fine-tuning per ottenere prestazioni competitive.

Quando scalati a 680.000 ore di dati di pre-training etichettati, i modelli Whisper dimostrano una forte capacità di generalizzazione su molti dataset e domini. I checkpoint pre-trained raggiungono risultati competitivi rispetto ai sistemi ASR allo stato dell’arte, con un tasso di errore di parola (WER) di circa il 3% sul sottoinsieme di test-clean di LibriSpeech ASR e un nuovo stato dell’arte su TED-LIUM con un WER del 4,7% ( c.f. Tabella 8 del paper di Whisper ). La conoscenza ASR multilingue acquisita da Whisper durante il pre-training può essere sfruttata per altre lingue a bassa risorsa; attraverso il fine-tuning, i checkpoint pre-trained possono essere adattati a dataset e lingue specifiche per migliorare ulteriormente questi risultati.

Whisper è un modello encoder-decoder basato su Transformer, anche chiamato modello sequence-to-sequence. Mappa una sequenza di feature di spettrogramma audio a una sequenza di token di testo. Innanzitutto, gli input audio grezzi vengono convertiti in uno spettrogramma log-Mel tramite l’estrattore di feature. L’encoder Transformer codifica quindi lo spettrogramma per formare una sequenza di stati nascosti dell’encoder. Infine, il decoder predice in modo autoregressivo i token di testo, condizionati sia ai token precedenti che agli stati nascosti dell’encoder. La Figura 1 riassume il modello Whisper.

Figura 1: Modello Whisper. L'architettura segue lo standard del modello encoder-decoder basato su Transformer. Uno spettrogramma log-Mel viene inserito nell'encoder. Gli ultimi stati nascosti dell'encoder vengono inseriti nel decoder tramite meccanismi di cross-attention. Il decoder predice in modo autoregressivo i token di testo, condizionati sia agli stati nascosti dell'encoder che ai token previsti in precedenza. Fonte della figura: OpenAI Whisper Blog.

In un modello di sequenza-a-sequenza, l’encoder trasforma i segnali audio in un insieme di rappresentazioni di stato nascoste, estraendo caratteristiche importanti dal discorso pronunciato. Il decoder svolge il ruolo di un modello di linguaggio, elaborando le rappresentazioni di stato nascoste e generando le corrispondenti trascrizioni di testo. L’incorporazione di un modello di linguaggio internamente nell’architettura del sistema è chiamata fusione profonda. Questo è in contrasto con la fusione superficiale, in cui un modello di linguaggio viene combinato esternamente con un encoder, ad esempio con CTC + n-grammi (vedi Internal Language Model Estimation). Con la fusione profonda, l’intero sistema può essere addestrato end-to-end con gli stessi dati di addestramento e la stessa funzione di perdita, offrendo una maggiore flessibilità e prestazioni generalmente superiori (vedi ESB Benchmark).

Whisper è pre-addestrato e raffinato utilizzando la funzione obiettivo dell’entropia incrociata, una funzione obiettivo standard per addestrare sistemi di sequenza-a-sequenza su compiti di classificazione. Qui, il sistema viene addestrato per classificare correttamente il token di testo target da un vocabolario predefinito di token di testo.

I checkpoint di Whisper sono disponibili in cinque configurazioni di dimensioni di modelli diverse. I quattro più piccoli sono addestrati solo su dati in inglese o multilingue. Il checkpoint più grande è solo multilingue. Tutti e nove i checkpoint pre-addestrati sono disponibili su Hugging Face Hub. I checkpoint sono riassunti nella seguente tabella con link ai modelli su Hub:

A scopo dimostrativo, raffineremo la versione multilingue del checkpoint small con 244M parametri (~= 1GB). Per quanto riguarda i nostri dati, addestreremo e valuteremo il nostro sistema su una lingua a risorse limitate tratta dal dataset Common Voice. Mostreremo che con soli 8 ore di dati di raffinamento, possiamo ottenere una forte performance in questa lingua.


1 {}^1 1 Il nome Whisper deriva dall’acronimo “WSPSR”, che sta per “Web-scale Supervised Pre-training for Speech Recognition”.

Raffinamento di Whisper in un Google Colab

Preparare l’ambiente

Useremo diversi pacchetti Python popolari per raffinare il modello Whisper. Utilizzeremo datasets per scaricare e preparare i nostri dati di addestramento e transformers per caricare e addestrare il nostro modello Whisper. Sarà inoltre necessario il pacchetto soundfile per pre-elaborare i file audio, evaluate e jiwer per valutare le prestazioni del nostro modello. Infine, useremo gradio per creare una demo accattivante del nostro modello raffinato.

!pip install datasets>=2.6.1
!pip install git+https://github.com/huggingface/transformers
!pip install librosa
!pip install evaluate>=0.30
!pip install jiwer
!pip install gradio

Consigliamo vivamente di caricare i checkpoint dei modelli direttamente su Hugging Face Hub durante l’addestramento. Il Hub fornisce:

  • Controllo integrato delle versioni: puoi essere sicuro che nessun checkpoint del modello venga perso durante l’addestramento.
  • Registri di Tensorboard: traccia metriche importanti nel corso dell’addestramento.
  • Carte dei modelli: documenta ciò che fa un modello e i casi d’uso previsti.
  • Comunità: un modo facile per condividere e collaborare con la comunità!

Collegare il notebook al Hub è semplice: basta inserire il proprio token di autenticazione Hub quando richiesto. Trova il tuo token di autenticazione Hub qui :

from huggingface_hub import notebook_login

notebook_login()

Output della stampa:

Login riuscito
Il tuo token è stato salvato in /root/.huggingface/token

Caricare il dataset

Common Voice è una serie di dataset generati dal crowd in cui i locutori registrano testi da Wikipedia in varie lingue. Utilizzeremo l’ultima versione del dataset Common Voice (versione 11). Per quanto riguarda la nostra lingua, affineremo il nostro modello sull’hindi, una lingua indo-ariana parlata nell’India settentrionale, centrale, orientale e occidentale. Common Voice 11.0 contiene circa 12 ore di dati etichettati in hindi, di cui 4 sono dati di test riservati.

Andiamo al Hub e visualizziamo la pagina del dataset di Common Voice: mozilla-foundation/common_voice_11_0.

La prima volta che visualizziamo questa pagina, ci verrà chiesto di accettare i termini d’uso. Dopo di che, avremo pieno accesso al dataset.

Una volta che abbiamo fornito l’autenticazione per utilizzare il dataset, ci verrà presentata un’anteprima del dataset. L’anteprima del dataset ci mostra i primi 100 campioni del dataset. Inoltre, è caricata con campioni audio pronti per essere ascoltati in tempo reale. Possiamo selezionare il sottoinsieme in hindi di Common Voice impostando il sottoinsieme su hi utilizzando il menu a tendina (hi è il codice identificativo della lingua hindi):

Se facciamo clic sul pulsante di riproduzione del primo campione, possiamo ascoltare l’audio e vedere il testo corrispondente. Scorri i campioni per i set di addestramento e di test per avere una migliore comprensione dei dati audio e testuali con cui stiamo lavorando. Puoi capire dall’intonazione e dallo stile che le registrazioni sono tratte da discorsi narrati. Noterai anche la grande variazione di speaker e qualità di registrazione, una caratteristica comune dei dati ottenuti in modo collaborativo.

Utilizzando 🤗 Datasets, il download e la preparazione dei dati è estremamente semplice. Possiamo scaricare e preparare le divisioni di Common Voice in una sola riga di codice. Poiché l’hindi ha poche risorse, combineremo le divisioni train e validation per ottenere circa 8 ore di dati di addestramento. Utilizzeremo le 4 ore di dati di test come set di test separato:

from datasets import load_dataset, DatasetDict

common_voice = DatasetDict()

common_voice["train"] = load_dataset("mozilla-foundation/common_voice_11_0", "hi", split="train+validation", use_auth_token=True)
common_voice["test"] = load_dataset("mozilla-foundation/common_voice_11_0", "hi", split="test", use_auth_token=True)

print(common_voice)

Output di stampa:

DatasetDict({
    train: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment'],
        num_rows: 6540
    })
    test: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment'],
        num_rows: 2894
    })
})

La maggior parte dei dataset ASR fornisce solo campioni audio di input ( audio ) e il testo trascritto corrispondente ( sentence ). Common Voice contiene informazioni aggiuntive sui metadati, come accent e locale , che possiamo trascurare per ASR. Mantenendo il notebook il più generale possibile, consideriamo solo l’audio di input e il testo trascritto per il fine-tuning, ignorando le informazioni aggiuntive sui metadati:

common_voice = common_voice.remove_columns(["accent", "age", "client_id", "down_votes", "gender", "locale", "path", "segment", "up_votes"])

Common Voice è solo uno dei dataset multilingue ASR che possiamo scaricare dal Hub – ce ne sono molti altri disponibili! Per visualizzare la gamma di dataset disponibili per il riconoscimento vocale, segui il link: Dataset ASR sul Hub .

Prepara il Feature Extractor, il Tokenizer e i Dati

Il pipeline ASR può essere decomposto in tre componenti:

  1. Un feature extractor che preelabora gli input audio grezzi
  2. Il modello che esegue il mapping sequenza-sequenza
  3. Un tokenizer che post-elabora gli output del modello nel formato di testo

In 🤗 Transformers, il modello Whisper ha un feature extractor e un tokenizer associati, chiamati rispettivamente WhisperFeatureExtractor e WhisperTokenizer.

Scorreremo i dettagli del feature extractor e del tokenizer uno per uno!

Carica WhisperFeatureExtractor

La voce è rappresentata da un array unidimensionale che varia nel tempo. Il valore dell’array in un determinato istante di tempo è l’ampiezza del segnale in quel punto. Dalle informazioni sull’ampiezza da sole, possiamo ricostruire lo spettro di frequenza dell’audio e recuperare tutte le caratteristiche acustiche.

Dato che la voce è continua, contiene un numero infinito di valori di ampiezza. Ciò pone problemi ai dispositivi informatici che si aspettano array finiti. Pertanto, discretizziamo il segnale vocale campionando i valori dal segnale a intervalli di tempo fissi. L’intervallo con cui campioniamo l’audio è noto come frequenza di campionamento ed è solitamente misurato in campioni/secondo o Hertz (Hz) . Campionare con una frequenza di campionamento più alta produce una migliore approssimazione del segnale vocale continuo, ma richiede anche di memorizzare più valori al secondo.

È fondamentale che la frequenza di campionamento dei nostri input audio corrisponda alla frequenza di campionamento prevista dal nostro modello, poiché i segnali audio con diverse frequenze di campionamento hanno distribuzioni molto diverse. Gli esempi audio dovrebbero essere elaborati solo con la frequenza di campionamento corretta. Non farlo può portare a risultati inaspettati! Ad esempio, prendere un esempio audio con una frequenza di campionamento di 16 kHz e ascoltarlo con una frequenza di campionamento di 8 kHz farà sembrare l’audio come se fosse a metà velocità. Allo stesso modo, passare audio con la frequenza di campionamento errata può compromettere un modello ASR che si aspetta una frequenza di campionamento e ne riceve un’altra. L’estrattore di caratteristiche Whisper si aspetta input audio con una frequenza di campionamento di 16 kHz, quindi dobbiamo abbinare i nostri input a questo valore. Non vogliamo addestrare involontariamente un sistema ASR con un discorso rallentato!

L’estrattore di caratteristiche Whisper esegue due operazioni. Prima di tutto, aggiunge/tronca un batch di esempi audio in modo che tutti gli esempi abbiano una lunghezza di 30 secondi. Gli esempi più brevi di 30 secondi vengono riempiti fino a 30 secondi aggiungendo zeri alla fine della sequenza (zeri in un segnale audio corrispondenti a nessun segnale o silenzio). Gli esempi più lunghi di 30 secondi vengono troncati a 30 secondi. Poiché tutti gli elementi del batch vengono riempiti/troncati a una lunghezza massima nello spazio di input, non è necessario un mascheramento dell’attenzione durante l’inoltro degli input audio al modello Whisper. Whisper è unico a questo proposito: con la maggior parte dei modelli audio, è possibile fornire un mascheramento dell’attenzione che indica dove le sequenze sono state riempite e quindi dove devono essere ignorate nel meccanismo di auto-attenzione. Whisper è addestrato per funzionare senza un mascheramento dell’attenzione e inferire direttamente dai segnali vocali dove ignorare gli input.

La seconda operazione che l’estrattore di caratteristiche Whisper esegue è la conversione delle matrici audio riempite in spettrogrammi log-Mel. Questi spettrogrammi sono una rappresentazione visiva delle frequenze di un segnale, simile a una trasformata di Fourier. Un esempio di spettrogramma è mostrato nella Figura 2. Lungo l’asse y ci sono i canali Mel, che corrispondono a particolari intervalli di frequenza. Lungo l’asse x c’è il tempo. Il colore di ogni pixel corrisponde all’intensità logaritmica di quel canale di frequenza in un dato momento. Lo spettrogramma log-Mel è la forma di input prevista dal modello Whisper.

I canali Mel (intervalli di frequenza) sono standard nell’elaborazione del parlato e scelti per approssimare l’intervallo uditivo umano. Tutto ciò che dobbiamo sapere per il fine-tuning di Whisper è che lo spettrogramma è una rappresentazione visiva delle frequenze nel segnale vocale. Per maggiori dettagli sui canali Mel, fare riferimento al cepstrum di Mel-frequency.

Figura 2: Conversione di un array audio campionato in uno spettrogramma log-Mel. Sinistra: segnale audio campionato a 1 dimensione. Destra: corrispondente spettrogramma log-Mel. Fonte figura: Blog Google SpecAugment .

Per nostra fortuna, l’estrattore di caratteristiche Whisper di 🤗 Transformers esegue sia il riempimento che la conversione dello spettrogramma in una sola riga di codice! Andiamo avanti e carichiamo l’estrattore di caratteristiche dal checkpoint pre-addestrato per avere pronto per i nostri dati audio:

from transformers import WhisperFeatureExtractor

feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-small")

Carica WhisperTokenizer

Ora vediamo come caricare un tokenizzatore Whisper. Il modello Whisper restituisce token di testo che indicano l’indice del testo predetto tra i vocaboli del dizionario. Il tokenizzatore mappa una sequenza di token di testo nella stringa di testo effettiva (ad esempio [1169, 3797, 3332] -> “il gatto sedeva”).

Tradicionalmente, quando si utilizzano modelli solo encoder per ASR, si decodifica utilizzando la Connectionist Temporal Classification (CTC). Qui siamo tenuti ad addestrare un tokenizzatore CTC per ciascun dataset che utilizziamo. Uno dei vantaggi dell’utilizzo di un’architettura encoder-decoder è che possiamo sfruttare direttamente il tokenizzatore del modello pre-addestrato.

Il tokenizzatore Whisper è pre-addestrato sulle trascrizioni per le 96 lingue di pre-addestramento. Di conseguenza, ha una rappresentazione byte-pair estesa che è appropriata per quasi tutte le applicazioni ASR multilingue. Per l’hindi, possiamo caricare il tokenizzatore e usarlo per il fine-tuning senza ulteriori modifiche. Dobbiamo semplicemente specificare la lingua di destinazione e il compito. Questi argomenti informano il tokenizzatore di anteporre i token di lingua e compito all’inizio delle sequenze di etichette codificate:

from transformers import WhisperTokenizer

tokenizer = WhisperTokenizer.from_pretrained("openai/whisper-small", language="Hindi", task="transcribe")

Possiamo verificare che il tokenizer codifichi correttamente i caratteri hindi codificando e decodificando il primo campione del dataset Common Voice. Durante la codifica delle trascrizioni, il tokenizer aggiunge ‘token speciali’ all’inizio e alla fine della sequenza, inclusi i token di inizio/fine trascrizione, il token di lingua e i token di task (come specificato dagli argomenti nel passaggio precedente). Durante la decodifica degli ID dei label, abbiamo l’opzione di ‘saltare’ questi token speciali, consentendoci di ottenere una stringa nella forma di input originale:

input_str = common_voice["train"][0]["sentence"]
labels = tokenizer(input_str).input_ids
decoded_with_special = tokenizer.decode(labels, skip_special_tokens=False)
decoded_str = tokenizer.decode(labels, skip_special_tokens=True)

print(f"Input:                 {input_str}")
print(f"Decodificato con speciali:    {decoded_with_special}")
print(f"Decodificato senza speciali: {decoded_str}")
print(f"Sono uguali:             {input_str == decoded_str}")

Output della stampa:

Input:                 खीर की मिठास पर गरमाई बिहार की सियासत, कुशवाहा ने दी सफाई
Decodificato con speciali:    <|startoftranscript|><|hi|><|transcribe|><|notimestamps|>खीर की मिठास पर गरमाई बिहार की सियासत, कुशवाहा ने दी सफाई<|endoftext|>
Decodificato senza speciali: खीर की मिठास पर गरमाई बिहार की सियासत, कुशवाहा ने दी सफाई
Sono uguali:             True

Combina per creare un WhisperProcessor

Per semplificare l’utilizzo dell’estrattore di caratteristiche e del tokenizer, possiamo avvolgerli entrambi in una singola classe WhisperProcessor. Questo oggetto processor eredita da WhisperFeatureExtractor e WhisperProcessor e può essere utilizzato per gli input audio e le previsioni del modello come richiesto. In questo modo, durante l’addestramento, è sufficiente tenere traccia di due oggetti: il processor e il modello:

from transformers import WhisperProcessor

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

Preparazione dei dati

Stampiamo il primo esempio del dataset Common Voice per vedere in che forma sono i dati:

print(common_voice["train"][0])

Output della stampa:

{'audio': {'path': '/home/sanchit_huggingface_co/.cache/huggingface/datasets/downloads/extracted/607848c7e74a89a3b5225c0fa5ffb9470e39b7f11112db614962076a847f3abf/cv-corpus-11.0-2022-09-21/hi/clips/common_voice_hi_25998259.mp3', 
           'array': array([0.0000000e+00, 0.0000000e+00, 0.0000000e+00, ..., 9.6724887e-07,
       1.5334779e-06, 1.0415988e-06], dtype=float32), 
           'sampling_rate': 48000},
 'sentence': 'खीर की मिठास पर गरमाई बिहार की सियासत, कुशवाहा ने दी सफाई'}

Possiamo vedere che abbiamo un array audio di input unidimensionale e la trascrizione di destinazione corrispondente. Abbiamo parlato molto dell’importanza del tasso di campionamento e del fatto che abbiamo bisogno di abbinare il tasso di campionamento del nostro audio a quello del modello Whisper (16kHz). Poiché il nostro audio di input è campionato a 48kHz, dobbiamo ridimensionarlo a 16kHz prima di passarlo all’estrattore di caratteristiche di Whisper.

Imposteremo gli input audio alla corretta frequenza di campionamento utilizzando il metodo cast_column del dataset. Questa operazione non modifica l’audio direttamente, ma segnala a datasets di eseguire il campionamento dei campioni audio al volo la prima volta che vengono caricati:

from datasets import Audio

common_voice = common_voice.cast_column("audio", Audio(sampling_rate=16000))

Ricaricando il primo campione audio nel dataset Common Voice, verrà eseguito il campionamento alla frequenza di campionamento desiderata:

print(common_voice["train"][0])

Output della stampa:

{'audio': {'path': '/home/sanchit_huggingface_co/.cache/huggingface/datasets/downloads/extracted/607848c7e74a89a3b5225c0fa5ffb9470e39b7f11112db614962076a847f3abf/cv-corpus-11.0-2022-09-21/hi/clips/common_voice_hi_25998259.mp3', 
           'array': array([ 0.0000000e+00,  0.0000000e+00,  0.0000000e+00, ...,
       -3.4206650e-07,  3.2979898e-07,  1.0042874e-06], dtype=float32),
           'sampling_rate': 16000},
 'sentence': 'खीर की मिठास पर गरमाई बिहार की सियासत, कुशवाहा ने दी सफाई'}

Perfetto! Possiamo vedere che la frequenza di campionamento è stata ridotta a 16kHz. I valori dell’array sono anche diversi, poiché ora abbiamo approssimativamente un valore di ampiezza per ogni tre che avevamo prima.

Ora possiamo scrivere una funzione per preparare i nostri dati pronti per il modello:

  1. Carichiamo e campioniamo i dati audio chiamando batch["audio"]. Come spiegato sopra, 🤗 Datasets esegue tutte le operazioni di campionamento necessarie al volo.
  2. Utilizziamo l’estrattore di caratteristiche per calcolare le feature di input dello spettrogramma log-Mel dalla nostra matrice audio unidimensionale.
  3. Codifichiamo le trascrizioni in identificatori di etichetta attraverso l’uso del tokenizer.
def prepare_dataset(batch):
    # carica e campiona i dati audio da 48 a 16kHz
    audio = batch["audio"]

    # calcola le feature di input dello spettrogramma log-Mel dalla matrice audio di input
    batch["input_features"] = feature_extractor(audio["array"], sampling_rate=audio["sampling_rate"]).input_features[0]

    # codifica il testo target in identificatori di etichetta
    batch["labels"] = tokenizer(batch["sentence"]).input_ids
    return batch

Possiamo applicare la funzione di preparazione dei dati a tutti i nostri esempi di addestramento utilizzando il metodo .map del dataset:

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

Bene! Con questo abbiamo preparato completamente i nostri dati per l’addestramento! Continuiamo e vediamo come possiamo utilizzare questi dati per il fine-tuning di Whisper.

Nota: Attualmente datasets utilizza sia torchaudio che librosa per il caricamento e il campionamento dell’audio. Se desideri implementare il tuo caricamento/campionamento personalizzato dei dati, puoi utilizzare la colonna "path" per ottenere il percorso del file audio e ignorare la colonna "audio".

Addestramento e Valutazione

Ora che abbiamo preparato i nostri dati, siamo pronti per entrare nel processo di addestramento. Il 🤗 Trainer farà gran parte del lavoro pesante per noi. Tutto ciò che dobbiamo fare è:

  • Definire un collatore di dati: il collatore di dati prende i nostri dati pre-elaborati e prepara i tensori PyTorch pronti per il modello.

  • Metriche di valutazione: durante la valutazione, vogliamo valutare il modello utilizzando la metrica del tasso di errore delle parole (WER). Dobbiamo definire una funzione compute_metrics che gestisca questo calcolo.

  • Caricare un checkpoint pre-addestrato: dobbiamo caricare un checkpoint pre-addestrato e configurarlo correttamente per l’addestramento.

  • Definire gli argomenti di addestramento: questi verranno utilizzati dal 🤗 Trainer per costruire il programma di addestramento.

Una volta che abbiamo ottimizzato il modello, lo valuteremo sui dati di test per verificare di averlo addestrato correttamente a trascrivere il discorso in hindi.

Definisci un Data Collator

Il data collator per un modello di discorso sequenziale è unico nel senso che tratta le input_features e le labels in modo indipendente: le input_features devono essere gestite dall’estrattore di caratteristiche e le labels dal tokenizzatore.

Le input_features sono già riempite a 30s e convertite in uno spettrogramma log-Mel di dimensione fissa, quindi tutto ciò che dobbiamo fare è convertirle in tensori batch di PyTorch. Lo facciamo utilizzando il metodo .pad dell’estrattore di caratteristiche con return_tensors=pt. Si noti che qui non viene applicato alcun riempimento aggiuntivo poiché gli input hanno una dimensione fissa, le input_features vengono semplicemente convertite in tensori PyTorch.

D’altra parte, le labels non sono riempite. Prima riempiamo le sequenze alla lunghezza massima nel batch utilizzando il metodo .pad del tokenizzatore. I token di riempimento vengono quindi sostituiti da -100 in modo che questi token non vengano presi in considerazione durante il calcolo della perdita. Quindi tagliamo il token di inizio del trascritto dall’inizio della sequenza di etichette poiché lo aggiungiamo successivamente durante l’addestramento.

Possiamo sfruttare il WhisperProcessor che abbiamo definito in precedenza per eseguire sia l’estrazione delle caratteristiche che le operazioni del tokenizzatore:

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]:
        # dividiamo input e labels poiché devono avere lunghezze diverse e necessitano di metodi di riempimento diversi
        # trattiamo prima gli input audio restituendo semplicemente tensori torch
        input_features = [{"input_features": feature["input_features"]} for feature in features]
        batch = self.processor.feature_extractor.pad(input_features, return_tensors="pt")

        # otteniamo le sequenze di etichette tokenizzate
        label_features = [{"input_ids": feature["labels"]} for feature in features]
        # riempiamo le etichette fino alla lunghezza massima
        labels_batch = self.processor.tokenizer.pad(label_features, return_tensors="pt")

        # sostituiamo il riempimento con -100 per ignorare correttamente la perdita
        labels = labels_batch["input_ids"].masked_fill(labels_batch.attention_mask.ne(1), -100)

        # se il token bos è stato aggiunto nella fase di tokenizzazione precedente,
        # tagliamo il token bos qui poiché viene aggiunto successivamente comunque
        if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all().cpu().item():
            labels = labels[:, 1:]

        batch["labels"] = labels

        return batch

Inizializziamo il data collator appena definito:

data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor=processor)

Metriche di valutazione

Successivamente, definiamo la metrica di valutazione che utilizzeremo sul nostro set di valutazione. Utilizzeremo la metrica di Word Error Rate (WER), la metrica ‘de-facto’ per valutare i sistemi ASR. Per ulteriori informazioni, consultare la documentazione di WER. Caricheremo la metrica WER da 🤗 Evaluate:

import evaluate

metric = evaluate.load("wer")

Successivamente, dobbiamo semplicemente definire una funzione che prende le previsioni del nostro modello e restituisce la metrica WER. Questa funzione, chiamata compute_metrics, sostituisce prima -100 con pad_token_id in label_ids (annullando il passaggio applicato nel data collator per ignorare correttamente i token di riempimento nella perdita). Quindi decodifica gli id previsti e di etichette in stringhe. Infine, calcola la WER tra le previsioni e le etichette di riferimento:

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

    # sostituiamo -100 con pad_token_id
    label_ids[label_ids == -100] = tokenizer.pad_token_id

    # non vogliamo raggruppare i token durante il calcolo delle metriche
    pred_str = tokenizer.batch_decode(pred_ids, skip_special_tokens=True)
    label_str = tokenizer.batch_decode(label_ids, skip_special_tokens=True)

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

    return {"wer": wer}

Carica un checkpoint pre-addestrato

Ora carichiamo il checkpoint pre-addestrato di Whisper small. Anche in questo caso, è banale grazie all’utilizzo di 🤗 Transformers!

from transformers import WhisperForConditionalGeneration

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

Il modello Whisper ha degli ID di token che vengono forzati come output del modello prima dell’inizio della generazione autoregressiva (forced_decoder_ids). Questi ID di token controllano la lingua della trascrizione e il compito per ASR a zero spiazzamento. Per il fine-tuning, impostiamo questi ID a None, poiché addestreremo il modello per prevedere la lingua corretta (Hindi) e il compito (trascrizione). Ci sono anche token che vengono completamente soppressi durante la generazione (suppress_tokens). Questi token hanno le loro probabilità logaritmiche impostate a -inf, quindi non verranno mai campionati. Sovrascriveremo questi token con una lista vuota, il che significa che nessun token verrà soppresso:

model.config.forced_decoder_ids = None
model.config.suppress_tokens = []

Definisci gli argomenti di addestramento

Nell’ultimo passaggio, definiamo tutti i parametri relativi all’addestramento. Di seguito vengono spiegati alcuni dei parametri:

  • output_dir: directory locale in cui salvare i pesi del modello. Questo sarà anche il nome del repository su Hugging Face Hub.
  • generation_max_length: numero massimo di token da generare in modo autoregressivo durante la valutazione.
  • save_steps: durante l’addestramento, i checkpoint intermedi verranno salvati e caricati in modo asincrono su Hub ogni save_steps passi di addestramento.
  • eval_steps: durante l’addestramento, verranno eseguite valutazioni dei checkpoint intermedi ogni eval_steps passi di addestramento.
  • report_to: dove salvare i log di addestramento. Le piattaforme supportate sono "azure_ml", "comet_ml", "mlflow", "neptune", "tensorboard" e "wandb". Scegli la tua preferita o lascia "tensorboard" per registrare su Hub.

Per ulteriori dettagli sugli altri argomenti di addestramento, consulta la documentazione di Seq2SeqTrainingArguments.

from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="./whisper-small-hi",  # cambia con un nome di repository a tua scelta
    per_device_train_batch_size=16,
    gradient_accumulation_steps=1,  # aumenta di 2 volte per ogni riduzione di 2 volte della dimensione del batch
    learning_rate=1e-5,
    warmup_steps=500,
    max_steps=4000,
    gradient_checkpointing=True,
    fp16=True,
    evaluation_strategy="steps",
    per_device_eval_batch_size=8,
    predict_with_generate=True,
    generation_max_length=225,
    save_steps=1000,
    eval_steps=1000,
    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,
)

Nota: se non si desidera caricare i checkpoint del modello su Hub, impostare push_to_hub=False.

Possiamo passare gli argomenti di addestramento al 🤗 Trainer insieme al nostro modello, dataset, data collator e alla funzione compute_metrics:

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

E con questo siamo pronti per iniziare l’addestramento!

Addestramento

Per avviare l’addestramento, esegui semplicemente:

trainer.train()

L’addestramento richiederà circa 5-10 ore a seconda della tua GPU o di quella allocata a Google Colab. A seconda della tua GPU, è possibile che si verifichi un errore CUDA "out-of-memory" quando si avvia l’addestramento. In questo caso, è possibile ridurre incrementalmente la per_device_train_batch_size di un fattore di 2 e utilizzare gradient_accumulation_steps per compensare.

Output di stampa:

Il nostro miglior WER è del 32,0% – niente male per 8 ore di dati di addestramento! La grande domanda è come si confronta con altri sistemi ASR. Per questo, possiamo visualizzare il hf-speech-bench, una classifica che categorizza i modelli per lingua e dataset, e successivamente li classifica in base al loro WER.

Il nostro modello addestrato presenta un miglioramento significativo rispetto alle prestazioni zero-shot del checkpoint Whisper small, evidenziando le forti capacità di trasferimento di apprendimento di Whisper.

Possiamo inviare automaticamente il nostro checkpoint alla classifica quando inviamo i risultati dell’addestramento all’Hub – dobbiamo semplicemente impostare gli opportuni argomenti chiave (kwargs). Puoi cambiare questi valori in base al tuo dataset, lingua e nome del modello:

kwargs = {
    "dataset_tags": "mozilla-foundation/common_voice_11_0",
    "dataset": "Common Voice 11.0",  # un nome "carino" per il dataset di addestramento
    "dataset_args": "config: hi, split: test",
    "language": "hi",
    "model_name": "Whisper Small Hi - Sanchit Gandhi",  # un nome "carino" per il tuo modello
    "finetuned_from": "openai/whisper-small",
    "tasks": "automatic-speech-recognition",
    "tags": "hf-asr-leaderboard",
}

I risultati dell’addestramento possono ora essere caricati sull’Hub. Per farlo, esegui il comando push_to_hub:

trainer.push_to_hub(**kwargs)

Ora puoi condividere questo modello con chiunque utilizzando il link sull’Hub. Possono anche caricarlo con l’identificatore "your-username/the-name-you-picked", ad esempio:

from transformers import WhisperForConditionalGeneration, WhisperProcessor

model = WhisperForConditionalGeneration.from_pretrained("sanchit-gandhi/whisper-small-hi")
processor = WhisperProcessor.from_pretrained("sanchit-gandhi/whisper-small-hi")

Anche se il modello addestrato presenta risultati soddisfacenti sui dati di test di Common Voice Hindi, non è ottimale. Lo scopo di questo notebook è quello di dimostrare come i checkpoint pre-addestrati di Whisper possono essere addestrati anche su altri dataset multilingue ASR. I risultati potrebbero essere migliorati ottimizzando gli iperparametri di addestramento, come il tasso di apprendimento e il dropout, e utilizzando un checkpoint pre-addestrato più grande (VoAGI o large).

Creazione di una demo

Ora che abbiamo addestrato il nostro modello, possiamo creare una demo per mostrare le sue capacità ASR! Utilizzeremo il pipeline di 🤗 Transformers, che si occuperà di tutto il processo ASR, dalla pre-elaborazione degli input audio alla decodifica delle previsioni del modello. Costruiremo la nostra demo interattiva con Gradio. Gradio è probabilmente il modo più semplice per creare demo di machine learning; con Gradio, possiamo costruire una demo in pochi minuti!

Eseguendo l’esempio qui sotto verrà generata una demo Gradio in cui possiamo registrare un audio tramite il microfono del nostro computer e inserirlo nel nostro modello Whisper addestrato per trascrivere il testo corrispondente:

from transformers import pipeline
import gradio as gr

pipe = pipeline(model="sanchit-gandhi/whisper-small-hi")  # cambia con "your-username/the-name-you-picked"

def transcribe(audio):
    text = pipe(audio)["text"]
    return text

iface = gr.Interface(
    fn=transcribe, 
    inputs=gr.Audio(source="microphone", type="filepath"), 
    outputs="text",
    title="Whisper Small Hindi",
    description="Demo in tempo reale per il riconoscimento del parlato in hindi utilizzando un modello Whisper small addestrato.",
)

iface.launch()

Considerazioni finali

In questo blog, abbiamo coperto una guida passo-passo sull’addestramento di Whisper per l’ASR multilingue utilizzando 🤗 Datasets, Transformers e l’Hugging Face Hub. Fai riferimento a Google Colab se desideri provare l’addestramento tu stesso. Se sei interessato ad addestrare altri modelli Transformers, sia per l’ASR in inglese che multilingue, assicurati di dare un’occhiata agli script di esempio in examples/pytorch/speech-recognition.