Rendere il riconoscimento automatico del parlato su file di grandi dimensioni con Wav2Vec2 in 🤗 Transformers

Riconoscimento automatico del parlato su file di grandi dimensioni con Wav2Vec2 in Transformers.

Tl;dr: Questo post spiega come utilizzare le specificità dell'architettura Connectionist Temporal Classification (CTC) per ottenere una qualità molto buona nel riconoscimento vocale automatico (ASR) anche su file arbitrariamente lunghi o durante l'inferenza in tempo reale.

Wav2Vec2 è un modello preaddestrato popolare per il riconoscimento vocale. Rilasciato nel settembre 2020 da Meta AI Research, l’architettura innovativa ha catalizzato i progressi nel preaddestramento auto-supervisionato per il riconoscimento vocale, ad esempio G. Ng et al., 2021, Chen et al., 2021, Hsu et al., 2021 e Babu et al., 2021. Sul portale Hugging Face Hub, il checkpoint preaddestrato più popolare di Wav2Vec2 viene scaricato mensilmente più di 250.000 volte.

Wav2Vec2 è principalmente un modello di transformers e una limitazione dei transformers è che di solito hanno una lunghezza di sequenza finita che possono gestire. Sia perché utilizzano positional encodings (non è il caso qui) o semplicemente perché il costo dell’attenzione nei transformers è effettivamente O(n²) in base alla lunghezza della sequenza, il che significa che utilizzare una lunghezza di sequenza molto grande esplode in complessità/memoria. Quindi non puoi eseguire Wav2Vec2 su un file di un’ora nemmeno con hardware sufficiente (come ad esempio una GPU molto grande come la A100). Il programma si bloccherà. Proviamo!

pip install transformers

from transformers import pipeline

# Questo funzionerà su uno qualsiasi dei migliaia di modelli su
# https://huggingface.co/models?pipeline_tag=automatic-speech-recognition
pipe = pipeline(model="facebook/wav2vec2-base-960h")


# Il file LibriVox di pubblico dominio utilizzato per il test
#!wget https://ia902600.us.archive.org/8/items/thecantervilleghostversion_2_1501_librivox/thecantervilleghostversion2_01_wilde_128kb.mp3 -o very_long_file.mp3

pipe("very_long_file.mp3")
# Blocco di memoria!

pipe("very_long_file.mp3", chunk_length_s=10)
# Questo funziona e stampa una stringa molto lunga!
# Questo intero post spiegherà come far funzionare le cose

Suddivisione semplice

Il modo più semplice per ottenere l’inferenza su file molto lunghi sarebbe suddividere l’audio iniziale in campioni più brevi, ad esempio 10 secondi ciascuno, eseguire l’inferenza su questi e ottenere una ricostruzione finale. Questo è efficiente dal punto di vista computazionale, ma di solito porta a risultati mediocri, perché per fare un’ottima inferenza, il modello ha bisogno di un certo contesto e quindi, intorno al confine di suddivisione, l’inferenza tende ad essere di scarsa qualità.

Osservate il seguente diagramma:

Ci sono modi per cercare di risolvere il problema in modo generale, ma non sono mai completamente robusti. È possibile provare a suddividere solo quando si incontra un silenzio, ma si potrebbe avere un audio non silenzioso per molto tempo (una canzone o un audio rumoroso di un caffè). È anche possibile provare a tagliare solo quando non c’è voce, ma ciò richiede un altro modello e questo non è un problema completamente risolto. Si potrebbe anche avere una voce continua per molto tempo.

Come si scopre, la struttura CTC, utilizzata da Wav2Vec2, può essere sfruttata per ottenere un riconoscimento vocale molto robusto anche su file molto lunghi senza cadere in tali insidie.

Suddivisione con stride

Wav2Vec2 utilizza l’algoritmo CTC, il che significa che ogni frame audio viene mappato su una singola previsione di lettera (logit).

Questa è la caratteristica principale che useremo per aggiungere uno stride. Questo link lo spiega nel contesto delle immagini, ma è lo stesso concetto per l’audio. Grazie a questa proprietà, possiamo:

  • Iniziare a fare inferenza su frammenti sovrapposti in modo che il modello abbia effettivamente un contesto adeguato al centro.
  • Eliminare i logit inferiti sui lati.
  • Concatenare i logit senza i loro lati eliminati per ottenere qualcosa di estremamente vicino a ciò che il modello avrebbe predetto sull’audio di lunghezza completa.

Questo non è tecnicamente esattamente la stessa cosa che eseguire il modello sull’intero file, quindi non è abilitato per impostazione predefinita, ma come hai visto nell’esempio precedente è sufficiente aggiungere chunk_length_s alla tua pipeline per farlo funzionare.

Nella pratica, abbiamo osservato che la maggior parte delle inferenze errate si trova all’interno delle strisce, che vengono eliminate prima dell’inferenza, consentendo un’inferenza corretta del testo completo.

È importante notare che è possibile scegliere ogni argomento di questa tecnica:

from transformers import pipeline

pipe = pipeline(model="facebook/wav2vec2-base-960h")
# stride_length_s è una tupla con la lunghezza di passo sinistra e destra.
# Con un solo numero, entrambi i lati ottengono lo stesso passo, per impostazione predefinita
# la lunghezza di passo su un lato è 1/6 della lunghezza di chunk_s
output = pipe("very_long_file.mp3", chunk_length_s=10, stride_length_s=(4, 2))

Chunking con passo su modelli LM potenziati

In transformers, abbiamo anche aggiunto il supporto per l’aggiunta di LM a Wav2Vec2 al fine di migliorare le performance WER dei modelli senza nemmeno il fine-tuning. Vedi questo eccellente post del blog che spiega come funziona.

Risulta che l’LM funziona direttamente sui logit stessi, quindi possiamo effettivamente applicare la stessa tecnica di prima senza alcuna modifica! Quindi, la suddivisione in chunk di file di grandi dimensioni su questi modelli potenziati da LM funziona ancora “out of the box”.

Inferenza in tempo reale

Un vantaggio molto interessante nell’utilizzare un modello CTC come Wav2vec2 è che è un modello a passaggio singolo, quindi è molto veloce. Soprattutto su GPU. Possiamo sfruttare questo per eseguire inferenze in tempo reale.

Il principio è esattamente lo stesso del passo regolare, ma questa volta possiamo alimentare i dati della pipeline man mano che arrivano e semplicemente utilizzare il passo su chunk completi di lunghezza 10s, ad esempio con un passo di 1s per ottenere un contesto adeguato.

Ciò richiede di eseguire molto più passaggi di inferenza rispetto alla suddivisione semplice dei file, ma può migliorare notevolmente l’esperienza in tempo reale perché il modello può stampare le cose mentre si sta parlando, senza dover aspettare X secondi prima di vedere qualcosa visualizzato.