I modelli di riconoscimento#
Quando pronunci la parola «casa», il microfono del telefono non registra quattro lettere: registra circa sedicimila numeri al secondo. Ogni numero è la pressione dell’aria misurata in un istante, si chiama campione, e messi in fila quei numeri raccontano come l’aria ha vibrato. Sedicimila è una scelta, non una legge: bastano a rendere una voce senza sprecare spazio, e chi registra musica ne usa quasi il triplo, perché lì servono anche gli acuti che nel parlato non ci sono.
Il compito del riconoscimento vocale automatico (Automatic Speech Recognition, ASR) è tradurre quel fiume di numeri in una manciata di caratteri. Sembra un problema di traduzione come un altro, ma nasconde una difficoltà tutta sua, che ha condizionato per decenni il modo in cui si costruiscono questi modelli.
Il problema dell’allineamento#
Prima di dare in pasto l’audio a una rete lo trasformiamo in uno spettrogramma: tagliamo il segnale in finestrelle di circa 25 millesimi di secondo, una nuova ogni 10, e per ciascuna misuriamo quanta energia c’è a ogni frequenza, cioè a ogni altezza sonora. È la finestra di Hann di Dal suono alle feature, con i bordi sfumati e il passo che la fa sovrapporre alla vicina. Ogni finestrella è un frame, e un secondo di parlato diventa così un centinaio di frame.
La trascrizione, invece, è lunga poche decine di caratteri. Due sequenze di lunghezza molto diversa, e nessuno ci dice quale frame corrisponde a quale lettera.
Sottotitolare un video a orecchio, senza avere i tempi. Chi parla lento, chi veloce; una vocale tenuta a lungo («caaasa») occupa molti fotogrammi ma resta una sola lettera; tra una parola e l’altra ci sono pause e respiri che non vanno scritti. Sai cosa è stato detto, ma non quando comincia e finisce ogni suono. Questo è l’allineamento: appaiare i tanti pezzetti di audio ai pochi caratteri del testo.
Una cosa però la sai già: si va nello stesso verso. Il video scorre, il testo scorre con lui, e quello che si sente prima è anche scritto prima; non capita mai di dover risalire il nastro per un suono che arriva dopo.
I tempi si potrebbero anche segnare a mano, cronometro alla mano, suono per suono: su un video si fa, su milioni di ore di registrazioni non lo farà nessuno. Restano le due cose che costa poco avere, la registrazione e la sua trascrizione, e da quelle due i tempi bisogna tirarli fuori da sé.
Abbiamo un input \(\mathbf{X} = (\mathbf{x}_1, \dots, \mathbf{x}_T)\) di \(T\) frame e un target \(y = (y_1, \dots, y_U)\) di \(U\) token (caratteri o sotto-parole): è la trascrizione che nella panoramica chiamavamo \(W\), vista qui come sequenza di simboli. Con \(T \gg U\), l’allineamento è monotono (l’audio scorre in avanti come il testo) ma sconosciuto: non abbiamo etichette frame per frame. Segmentare a mano milioni di ore per dire «da qui a qui c’è una a» è impraticabile. Serve un modello che impari l’allineamento da solo, dalla sola coppia (audio, trascrizione).
CTC: imparare ad allineare da soli#
La svolta arriva nel 2006, e l’idea è di Alex Graves e colleghi: aggiungere all’alfabeto un simbolo speciale, il «vuoto» (blank, \(\varnothing\)), che significa «qui non produco nessun carattere». Di mestieri ne fa due: sta dove non si pronuncia niente, nei silenzi e nei respiri, e tiene separate due lettere uguali di fila, che altrimenti si fonderebbero in una. Il metodo si chiama Connectionist Temporal Classification, un nome che non aiuta nessuno e che infatti si abbrevia sempre: CTC, e sono quelle tre lettere a contare.
Per ogni frame la rete non sceglie un simbolo secco. Distribuisce cento punti fra tutti i simboli dell’alfabeto, vuoto compreso: dieci alla «A», due alla «B», e così via fino a esaurirli. Sono percentuali, e quello che conta è che siano cento in tutto, cioè che tutta la fiducia della rete finisca da qualche parte. (In gergo si dice che i voti sommano a uno, perché il 100% si scrive anche «1», e che la rete emette una distribuzione, cioè un modo di spartire la fiducia fra più possibilità.)
In Fig. 23.2 è disegnato, per ogni frame, il simbolo che ha preso il voto più alto. Poi una regola di collasso ripulisce la sequenza: prima unisce i caratteri uguali consecutivi, poi elimina i vuoti.
Fig. 23.2 Il meccanismo della CTC, un passo alla volta. Per ogni frame è disegnato il simbolo più votato, e fra i candidati c’è anche il «vuoto» ∅; poi si uniscono i ripetuti consecutivi, e solo dopo si tolgono i vuoti. L’ordine decide tutto: invertendolo la doppia «L» si perde, ed è per impedirlo che il ∅ esiste. I frame disegnati sono sette perché ci stiano: per una parola come «palla» ne servirebbero una cinquantina, uno ogni dieci millesimi di secondo.#
Molti modi di etichettare i frame danno la stessa parola. Per «PALLA» va bene
P A A L ∅ L A, ma anche P P A L ∅ L A: entrambi, dopo aver unito i doppioni
e tolto i vuoti, diventano PALLA.
Questi modi hanno un nome, e da qui in avanti useremo quello: si chiamano allineamenti, perché ciascuno dice come i pezzi di suono si appaiano alle lettere.
Ogni allineamento ha una sua probabilità, cioè quanto la rete ci crede, e si ottiene moltiplicando fra loro i voti che la rete ha dato ai sette simboli di quella riga. Si moltiplica perché la rete vota ogni frame senza guardare che cosa ha votato negli altri: sette giudizi che non si parlano fra loro, come sette dadi tirati uno accanto all’altro, e allora la probabilità che escano tutti come vogliamo è il prodotto dei sette. Che i sette giudizi non si parlino è com’è fatta la CTC, non una legge di natura, e la sezione sul modello di linguaggio dirà quanto le costa.
Sette voti moltiplicati fra loro danno un numero piccolo; mettiamo che il primo allineamento valga il 3% e il secondo il 2% (sono numeri inventati per l’esempio). Tutti e due, ripuliti, danno «PALLA»: quindi finora «PALLA» vale il 5%, più di quanto valga ciascuno da solo. Dico «finora» perché di modi che danno «PALLA» ce ne sono altri, e vanno sommati anche quelli. Ecco cosa vuol dire «sommare gli allineamenti».
La CTC non sceglie dunque un allineamento giusto e non chiede alla rete di indovinarlo: li considera tutti insieme e somma le probabilità di quelli che danno la trascrizione corretta. L’addestramento non chiede altro che alzare quel totale, e come lo si alzi (spostando i voti su un modo o sull’altro) sono affari della rete.
Gli allineamenti sono tanti anche per una parola di cinque lettere, e quanti
siano non è una magia: si contano, e il conto si può rifare a mano. Il più
corto sta in sei frame, uno per lettera più il vuoto obbligatorio in mezzo
alle due «L»: P A L ∅ L A, e in sei frame non ce n’è nessun altro. Con sette
frame ne avanza uno, e si può spendere in due modi soltanto: tenere un simbolo
per due frame invece che per uno, e allora i modi sono sei, uno per ciascun
simbolo (P P A L ∅ L A, P A A L ∅ L A, e così via); oppure infilare un
vuoto in più, e i posti dove infilarlo sono sette, uno prima della «P», cinque
fra un simbolo e l’altro e uno dopo la «A» finale, ma i due attaccati al vuoto
che c’è già danno P A L ∅ ∅ L A, che è il vuoto tenuto per due frame, cioè
il caso di prima: restano cinque. Sei più cinque,
undici.
Con cinquanta frame, che sono quelli che «palla» occupa davvero, i modi
diventano quasi ventiquattro miliardi: a mano non si contano più, e a contarli
è la stessa tabella con cui si sommano. Sommarli tutti sembra un lavoro
impossibile, e invece si fa in fretta, perché i modi si somigliano: prendine
due che per i primi tre frame sono identici e si separano al quarto, e il
conto dei primi tre frame è lo stesso per tutti e due. Basta farlo una volta e
riusarlo. Messi insieme i modi che condividono l’inizio, il lavoro diventa
riempire una tabella lunga cinquanta colonne, una per frame, e alta quanto
«palla» scritta con i vuoti dentro e ai lati, ∅P∅A∅L∅L∅A∅: undici righe, e
cinquecentocinquanta caselle in tutto. È lo stesso risparmio del navigatore
di Viterbi di
POS tagging ed entità,
con una differenza che fra poco conterà: là in ogni casella sopravviveva il
cammino migliore, qui si sommano tutti.
C’è però un prezzo. La rete vota un frame alla volta, e ogni voto lo dà guardando il suono e nient’altro: non si rilegge mai quello che ha già scritto. Non sa, cioè, che dopo «c-a-n» in italiano viene molto più facilmente una «e» che una «q». È un’ignoranza che le costerà cara, e a cui qualcun altro dovrà rimediare al posto suo.
La probabilità di una trascrizione \(y\) è la somma su tutti i percorsi frame-level \(\pi\) che, collassati, la producono:
dove \(\pi = (\pi_1, \dots, \pi_T)\) è un allineamento a livello di frame,
\(p_t(\pi_t \mid \mathbf{X})\) è la probabilità che la rete assegna al simbolo
\(\pi_t\) al frame \(t\), e \(\mathcal{B}\) è la funzione di collasso. Quanti sono i
termini si conta con la stessa formula, mettendo tutte le \(p_t\) a uno: per
PALLA sono undici sui sette frame della figura, mille e uno su dieci frame,
quasi ventiquattro miliardi sui cinquanta che quella parola occupa davvero. La
somma si calcola comunque in tempo \(O(T \cdot U)\) (lineare nella
lunghezza dell’audio, a trascrizione fissata) con l’algoritmo di
programmazione dinamica forward-backward, che lavora sul reticolo dei
\(2U+1\) simboli della trascrizione estesa con i blank. Si addestra minimizzando
\(\mathcal{L} = -\log p(y \mid \mathbf{X})\).
Due limiti strutturali. Il primo è una conseguenza diretta della formula: la
CTC emette esattamente un simbolo per frame, quindi i frame devono bastare, e
la soglia è \(T \ge U + r\), dove \(r\) conta le coppie di simboli uguali
consecutivi in \(y\), ciascuna delle quali vuole in mezzo un vuoto che la
separi; sotto quella soglia \(\mathcal{B}^{-1}(y)\) è l’insieme vuoto e la loss
non è nemmeno definita. PALLA ha \(U = 5\) e \(r = 1\): in cinque frame non ci
sta, in sei ci sta in un modo solo (P A L ∅ L A), e da lì in poi i modi si
moltiplicano. È il motivo per cui il metodo serve all’ascolto e non alla
sintesi vocale, dove il testo in ingresso è più corto del suono in uscita
[Gra12]. Il secondo, il più citato, è invece un’ipotesi che
la formula presuppone invece di dedurla, e l’articolo del 2006 la dichiara
[GFernandezGS06]: il prodotto
\(\prod_t p_t(\pi_t \mid \mathbf{X})\) si può scrivere solo perché le uscite ai
vari frame sono condizionatamente indipendenti, e a garantirlo è un vincolo di
architettura, nessuna connessione di ritorno dallo strato di uscita a se
stesso. Nel prodotto non c’è quindi nessun fattore della forma
\(p(y_u \mid y_{<u})\); e togliere quel vincolo è esattamente la mossa del
trasduttore. Non è che la
CTC modelli «non bene» le dipendenze fra i caratteri in uscita: non ha il posto
dove metterle. Torneremo su questo punto parlando del modello di linguaggio,
perché è di lì che discende tutto il resto.
Quella tabella ha una forma, e conviene guardarla: Fig. 23.3 la disegna sui sette frame della parola d’esempio.
Fig. 23.3 Le righe sono «palla» con i vuoti dentro e ai lati, le colonne i frame. Da ogni casella si può restare dove si è, scendere di una riga o scenderne due scavalcando un vuoto, e il salto è l’unica mossa che ha un divieto: fra le due «L» il vuoto va attraversato, o le due lettere si fondono. Ogni cammino che arriva in fondo è un allineamento, e la somma di tutti si fa riempiendo le caselle una volta sola invece di seguire i cammini uno per uno.#
Dalla rete alla frase: la decodifica#
Fin qui abbiamo detto come si addestra un modello CTC, non come gli si fa scrivere una frase. Sono due cose diverse, e la differenza è più grossa di quanto sembri: il passaggio dai voti della rete alla trascrizione si chiama decodifica, ed è una storia a sé.
Il modo ovvio è prendere, frame per frame, il simbolo che ha ricevuto il voto più alto, e poi collassare la sequenza. Si chiama decodifica del percorso migliore (best path), costa niente, ed è quello che fa quasi tutto il codice di esempio che si trova in giro. Solo che risponde alla domanda sbagliata: cerca il percorso più probabile, non la trascrizione più probabile, e le due non coincidono.
Il caso più piccolo possibile: due frame soltanto, e un alfabeto di due simboli, il vuoto ∅ e la lettera «A». Su ciascuno dei due frame la rete dà il 60% al vuoto e il 40% alla «A». Ci sono quattro percorsi, e per ognuno la probabilità è il prodotto dei due voti:
percorso |
probabilità |
dopo la ripulitura |
|---|---|---|
∅ ∅ |
0,6 × 0,6 = 36% |
(niente) |
∅ A |
0,6 × 0,4 = 24% |
A |
A ∅ |
0,4 × 0,6 = 24% |
A |
A A |
0,4 × 0,4 = 16% |
A (le due si fondono) |
Il percorso più probabile è ∅ ∅, con il 36%, e non scrive niente. Ma la
lettera «A» esce da tre percorsi diversi, e insieme fanno 24 + 24 + 16 = 64%:
quasi il doppio. Prendendo il percorso migliore avremmo trascritto il
silenzio; sommando come fa la CTC, la risposta è «A».
E succede davvero, non solo negli esempi costruiti ad arte: capita ogni volta che una trascrizione è sostenuta da tanti percorsi mediocri e un’altra da un percorso solo, molto convinto; e di percorsi che danno la stessa parola ce ne sono a miliardi, come abbiamo contato per «palla».
Il best path massimizza il singolo allineamento,
mentre l’obiettivo che il modello è stato addestrato a massimizzare è
\(\arg\max_y p(y \mid \mathbf{X})\), cioè la somma su
\(\mathcal{B}^{-1}(y)\). Le due
quantità sono diverse perché \(\mathcal{B}\) non è iniettiva: molti percorsi
cadono sulla stessa etichettatura, e la loro massa può battere il massimo
puntuale. Il controesempio minimo ha \(T = 2\) e alfabeto
\(\{\varnothing, \texttt{A}\}\), con \(p_t(\varnothing) = 0{,}6\) su entrambi i
frame: il percorso migliore è \(\varnothing\varnothing\) e vale \(0{,}36\), ma
l’etichettatura A raccoglie i tre percorsi restanti e vale
\(0{,}24 + 0{,}24 + 0{,}16 = 0{,}64\).
Graves e colleghi lo scrivono già nel paper del 2006 [GFernandezGS06], nella sezione in cui costruiscono il classificatore: per l’\(\arg\max\) esatto «non conosciamo un algoritmo di decodifica trattabile in generale». Al suo posto propongono due metodi approssimati. Il primo è proprio il best path, che costa niente e non garantisce di trovare l’etichettatura più probabile. Il secondo è la prefix search decoding, che lavora sui prefissi invece che sui percorsi e, dato tempo a sufficienza, l’etichettatura più probabile la trova davvero. Il tempo però cresce in fretta, perché il numero di prefissi da espandere cresce esponenzialmente con la lunghezza dell’audio; gli autori osservano che se la distribuzione in uscita è abbastanza appuntita la ricerca finisce comunque in tempi ragionevoli, ma per il loro stesso esperimento servì un’euristica in più (spezzare la sequenza dove il vuoto è molto probabile). Quella che si usa oggi è la versione col freno a mano: una ricerca a fascio sui prefissi, che ne tiene aperti \(k\) e getta gli altri.
La ricerca a fascio (beam search) l’abbiamo già incontrata nella traduzione automatica, nel capitolo sul linguaggio naturale: invece di decidere subito, si tengono aperte le \(k\) ipotesi più promettenti e si va avanti qualche passo prima di scegliere. L’idea è la stessa, ma una cosa cambia, ed è proprio quella di prima: qui molti percorsi diversi danno la stessa identica parola. Nella traduzione le ipotesi competono: due strade diverse sono due frasi diverse, e alla fine ne resta una. Nel CTC no: due percorsi che si ripuliscono nello stesso testo sono la stessa ipotesi, e i loro punteggi vanno sommati invece di essere messi in concorrenza. Una beam search che se ne dimentica scarta la trascrizione giusta, esattamente come fa il percorso migliore.
Su questa ricerca lavorano due cose che la sezione sul modello di linguaggio, il correttore silenzioso, rimette in gioco. La prima è un modello di linguaggio, cioè un giudice che di suono non sa niente e sa soltanto quali sequenze di parole sono italiano plausibile: a ogni passo della ricerca aggiunge il proprio parere al punteggio. La seconda è la lista delle prime ipotesi, le migliori fra le \(k\) tenute aperte, che la ricerca sforna comunque e che si può riordinare a cose fatte.
Il caso dei due frame si rifà in poche righe di codice, elencando i quattro percorsi e sommandoli, ed è il modo più rapido di convincersene: il percorso più votato vale 0,36 e non scrive niente, la lettera «A» vale 0,64.
import itertools
VUOTO = "∅"
# i voti della rete: due frame, due simboli
voti = [{VUOTO: 0.6, "A": 0.4},
{VUOTO: 0.6, "A": 0.4}]
def collassa(percorso):
"""Prima unisce i simboli uguali consecutivi, poi toglie i vuoti."""
uniti = [s for i, s in enumerate(percorso)
if i == 0 or s != percorso[i - 1]]
return "".join(s for s in uniti if s != VUOTO)
def probabilita(percorso):
p = 1.0
for t, s in enumerate(percorso):
p *= voti[t][s]
return p
# tutte le trascrizioni, con la somma dei percorsi che le producono
totali = {}
for percorso in itertools.product(VUOTO + "A", repeat=len(voti)):
testo = collassa(percorso)
totali[testo] = totali.get(testo, 0.0) + probabilita(percorso)
# il percorso migliore: il simbolo più votato a ogni frame
migliore = tuple(max(v, key=v.get) for v in voti)
print("percorso migliore:", "".join(migliore),
"->", repr(collassa(migliore)), round(probabilita(migliore), 3))
for testo, p in sorted(totali.items(), key=lambda kv: -kv[1]):
print(f" p(y = {testo!r}) = {p:.2f}")
# la promessa del testo, resa eseguibile
assert totali["A"] > totali[collassa(migliore)]
percorso migliore: ∅∅ -> '' 0.36
p(y = 'A') = 0.64
p(y = '') = 0.36
Da qui in avanti, quando diremo che un sistema CTC «trascrive», intendiamo una ricerca di questo tipo, non il simbolo più votato frame per frame.
Ascoltare e attendere: i modelli con attenzione#
La CTC è una prima famiglia di modelli. Ce n’è una seconda, e il vuoto non ce l’ha proprio. Il lavoro qui è diviso fra due parti della stessa rete: una ascolta tutto l’audio e se lo riassume (si chiama encoder, «codificatore»), l’altra scrive il testo un carattere alla volta (il decoder), e ogni carattere lo sceglie tenendo conto di quelli che ha già scritto. In gergo si dice che procede in modo autoregressivo, cioè rileggendosi.
A ogni passo il decoder deve decidere quale pezzo di audio guardare, e la cosa che glielo fa decidere si chiama attenzione. È di nuovo l’allineamento da cui siamo partiti, e anche qui il modello se lo impara da solo; la differenza con la CTC è che la CTC è obbligata ad andare avanti frame per frame, mentre l’attenzione può guardare dove le pare, avanti o indietro. Un allineamento morbido, insomma, invece che a scatti. L’architettura di riferimento è Listen, Attend and Spell [CJLV16], ed è da lì che viene «ascoltare e attendere»: «attendere» traduce l’inglese attend, che non vuol dire aspettare ma «fare attenzione a».
Un interprete che traduce a discorso finito prima ascolta l’intera frase, poi la ridice parola per parola. Mentre pronuncia ogni parola, la sua attenzione torna al punto giusto di ciò che ha sentito. Il modello fa lo stesso: genera un carattere, si «riguarda» la porzione di audio più rilevante, genera il prossimo.
E l’interprete non riparte da zero a ogni parola: tiene il filo di quello che ha appena detto, e dopo «i bambini» dirà «giocano», non «gioca». Da questo rileggersi il modello ricava gratis un orecchio per la lingua, che nessuno gli ha insegnato: sa come continuano le frasi, e ci si appoggia quando il suono è confuso.
L’interprete, però, paga due prezzi. Il primo è che, per cominciare, deve aver ascoltato la frase fino in fondo: un modello così non può scrivere mentre uno sta ancora parlando. Il secondo è che niente gli impedisce di sbagliare punto. Può riguardare un pezzo di audio che ha già tradotto, o saltarne uno del tutto, e allora ripete una sillaba o si mangia una parola; nei casi peggiori si impunta e ripete la stessa cosa all’infinito. La CTC questo difetto non ce l’ha, perché va avanti frame per frame e indietro non torna mai.
Al passo \(i\) il decoder costruisce un vettore di contesto come media pesata degli stati dell’encoder \(\mathbf{h}_j\) (è la stessa formula dell’attenzione di Bahdanau vista nella traduzione automatica, con l’audio al posto della frase sorgente):
dove i pesi di attenzione \(\alpha_{ij}\) dicono quanto lo stato \(j\) conta per produrre il token \(i\)-esimo, e \(T_{\text{enc}} \le T\) è il numero di stati dell’encoder: l’encoder tipicamente sottocampiona l’asse temporale (in LAS di un fattore 8), proprio per rendere trattabile l’attenzione su sequenze lunghe. A differenza della CTC, il decoder condiziona ogni token su quelli già emessi: modella cioè le dipendenze del testo in uscita, ed è per questo che un modello del genere si porta dentro un modello di linguaggio senza che nessuno gliel’abbia messo.
Il prezzo si paga altrove, e non è la generazione sequenziale più lenta. È che nulla vincola \(\alpha_{ij}\) ad avanzare al crescere di \(i\): la CTC ha la monotonia per costruzione, l’attenzione la deve imparare, e niente le impedisce di saltare una porzione di audio, di riguardarne una già trascritta o di restare ferma dov’è. Da lì vengono le parole mancanti, le sillabe ripetute e i loop di ripetizione, che ritroveremo identici (stessa causa, altro compito) nella sintesi vocale. C’è poi un secondo prezzo, che conta appena si esce dal laboratorio: la somma corre su tutti gli stati dell’encoder, quindi il primo token non può uscire prima che sia arrivato l’ultimo frame. Un modello così non trascrive mentre ascolta.
Il trasduttore: tenersi tutte e due le cose#
Messe una accanto all’altra, le due famiglie sembrano costringere a una scelta. La CTC scorre l’audio in avanti e non torna mai indietro, quindi può scrivere mentre ascolta, purché la rete che dà i voti non abbia bisogno anche del suono che viene dopo. Ce ne sono che ne hanno bisogno: quelle che ripercorrono la registrazione anche all’indietro, per decidere un frame sapendo come va a finire, e la rete dell’articolo del 2006 era di quelle, tanto che in diretta non trascriveva. La CTC, però, non sa niente di cosa ha già scritto. Il decoder con attenzione sa benissimo cosa ha già scritto, ma per farlo deve aver ascoltato tutto, e per giunta può perdere il segno. Nella pratica quella scelta non esiste, perché esiste una terza famiglia che tiene le due cose insieme: il trasduttore neurale, proposto da Alex Graves nel 2012 [Gra12], cioè da chi aveva scritto la CTC sei anni prima. Nei testi si trova sempre con la sigla RNN-T, dove le prime tre lettere sono le reti ricorrenti del capitolo sul linguaggio naturale, quelle che leggono una sequenza un pezzo alla volta tenendosi in mente il pezzo di prima.
Quella data va guardata. Il trasduttore non arriva dopo i modelli con attenzione per rimediare ai loro difetti: precede di tre anni Listen, Attend and Spell, che è del 2015 e arriva in conferenza l’anno dopo, e di due anni il primo riconoscitore con attenzione. È nato dal lato della CTC, per togliere alla CTC il difetto che il suo autore le conosceva meglio di chiunque.
E se si scrivesse mentre l’altro parla? È il mestiere dello stenografo sotto dettatura: non aspetta la fine del discorso come l’interprete, scrive mentre ascolta, e non torna mai indietro sul nastro. Però ha sotto gli occhi il foglio, cioè quello che ha appena scritto, e quel foglio lo aiuta: se ha scritto «buon», la parola dopo sarà più probabilmente «giorno» che «gnorno».
Il trasduttore è questo. A ogni istante ha due mosse possibili: scrivere un carattere (e allora rilegge il foglio aggiornato, ma resta fermo sull’audio) o passare al frame successivo senza scrivere niente. Alternando le due mosse copre tutto l’audio e produce tutto il testo, senza mai tornare indietro e senza mai dimenticare quello che ha già messo giù.
Quando scrivere e quando spostarsi non gliel’ha detto nessuno. I ritmi possibili sono moltissimi, come i modi di etichettare i frame nella CTC, e in addestramento contano tutti insieme: quello che si spinge in alto è il totale, non un ritmo in particolare.
Restando fermo sull’audio, in teoria, potrebbe scrivere all’infinito: a fermarlo non c’è una regola, c’è il fatto che dopo aver scritto quello che quel pezzo di suono conteneva il vuoto diventa la mossa più votata, e allora si sposta. Se un modello mal addestrato si incaponisce a scrivere, infatti, esce proprio quello: una parola ripetuta finché qualcuno non stacca la spina.
E quel foglio, quello che lo stenografo si rilegge, dentro il trasduttore è una rete a sé: la prediction network, «la rete che prevede», il cui unico mestiere è guardare le lettere già scritte e dire cosa ci si aspetta dopo. È la cosa che alla CTC manca del tutto.
Il trasduttore accoppia tre reti: un encoder (o transcription network) che produce \(\mathbf{h}_t\) dai frame acustici, una prediction network che produce \(\mathbf{g}_u\) dai soli token già emessi \(y_{<u}\), e una piccola joint network che fonde le due e proietta sul vocabolario esteso col vuoto,
dove \(s\) è il simbolo candidato, \(\phi\) una non linearità (di solito una tangente iperbolica) e \(\mathbf{W}_o\) la proiezione sul vocabolario (il pedice la tiene distinta dal \(W\) della trascrizione). Nella formulazione originale del 2012 le due reti si sommavano direttamente nello spazio delle uscite; la joint network con la non linearità in mezzo è la forma che si è imposta dopo, ed è quella che si trova nelle librerie.
Lo spazio degli allineamenti non è più una sequenza di \(T\) etichette ma un
reticolo: emettere un token muove di uno in verticale, emettere il vuoto muove
di uno in orizzontale, e ogni cammino monotono che copre tutti i frame ed
emette tutti i token è un allineamento valido. L’ultimo simbolo è sempre un
vuoto, quello che chiude l’ultimo frame, quindi liberi da disporre restano
\(T-1\) vuoti e \(U\) token: i cammini sono \(\binom{T+U-1}{U}\), cioè \(3\,162\,510\)
per PALLA sui cinquanta frame di prima, e non i \(3\,478\,761\) di
\(\binom{T+U}{U}\), che è la forma che si scrive dimenticando il vuoto finale. La
probabilità
della trascrizione è ancora la somma su tutti i cammini, calcolata con un
forward-backward analogo a quello della CTC ma su questo reticolo, dove le
transizioni sono due e non tre, e la loss è ancora
\(-\log p(y \mid \mathbf{X})\).
Due conseguenze, ed è tutto il punto. La prediction network è a tutti gli effetti un modello di linguaggio interno, condizionato sui token già emessi: il trasduttore modella cioè le dipendenze uscita-uscita che la CTC non ha dove mettere. E \(\mathbf{h}_t\) dipende solo dai frame fino a \(t\) (con un encoder causale), quindi la decodifica è frame-sincrona e non ha bisogno della fine dell’audio: si trascrive mentre si ascolta.
È l’architettura su cui gira, dal 2019, la dettatura in tempo reale sui telefoni [HSP+19], dove la risposta deve arrivare mentre si parla e il modello deve stare dentro un dispositivo. Fra poco ci chiederemo a che cosa serva ancora, oggi, la vecchia catena a stadi.
Whisper e i Transformer end-to-end#
Nel settembre 2022 OpenAI rilascia Whisper, e la novità si dice in una riga: una rete sola, che riceve l’immagine a bande del suono e restituisce il testo, senza nessuno stadio in mezzo. La rete è un Transformer, diviso in encoder e decoder come i modelli con attenzione di poco fa; e l’immagine a bande è lo spettrogramma di sempre, in una versione che si chiama log-mel perché misura le altezze sonore («mel») e i volumi («log») come li sente l’orecchio, e non come li misurerebbe uno strumento.
Fig. 23.4 La catena di Whisper, tutta qui. Fra lo spettrogramma e il testo non c’è nessuno stadio con regole scritte a mano: encoder e decoder sono addestrati insieme, in un pezzo solo.#
Quello che manca in Fig. 23.4 conta quanto quello che c’è. Un riconoscitore a stadi ha tre pezzi da mettere a punto lingua per lingua, e la catena di montaggio del riconoscimento vocale (Fig. 23.1) ne mostrava due: il modello acustico, quello che giudica a quali suoni somiglia ogni frammento, e il modello di linguaggio. Il terzo sta fra quei due e non compare in nessuno dei due disegni: è il dizionario di pronuncia, un elenco compilato a mano che dice di quali suoni è fatta ogni parola.
Quei tre compiti restano anche qui, ma nessuno li ha più assegnati a un pezzo suo: sono sparsi nei pesi, cioè nei numeri che la rete ha imparato. Ed è per questo che un modello solo può coprire decine di lingue: non c’è più niente da compilare a mano lingua per lingua, il dizionario di pronuncia per primo, e aggiungerne una vuol dire darle altro audio con la sua trascrizione, non scriverle un pezzo su misura.
La sua forza, però, non è tanto l’architettura quanto i dati: 680.000 ore di audio, che sono settantotto anni di ascolto senza mai staccare, raccolte dal web con etichettatura debole, cioè trascrizioni già esistenti in rete, scritte da qualcuno per i propri scopi e non per addestrare un modello. «Debole» non vuol dire «non curata». Gli autori le passano al setaccio con filtri automatici, buttando via quelle prodotte da altri riconoscitori (imparare da un altro riconoscitore vuol dire ereditarne gli errori) e i duplicati; e ispezionano a mano le fonti che sbagliano di più, per eliminarle. Le coppie in cui la lingua parlata non è quella scritta le buttano via anche loro, con un’eccezione che conta: se il testo è in inglese la coppia resta, e diventa un esempio di traduzione. Sono 125.000 ore, e sono la ragione per cui lo stesso modello, oltre a trascrivere, traduce.
Quelle ore coprono quasi cento lingue, l’inglese e altre novantasei, ma non allo stesso modo: l’inglese se ne prende circa due terzi, e la maggior parte delle altre sta sotto le mille ore. È da qui che viene il salto di qualità che si sente passando all’italiano, e gli autori ne ricavano una stima, da una regressione fatta fra le lingue: il tasso di errore, cioè la quota di parole sbagliate, si dimezza ogni volta che le ore si moltiplicano per sedici. Vuol dire che fra una lingua da mille ore e una da sedicimila lo scarto atteso è di un fattore due, e che per un altro dimezzamento ne servirebbero duecentocinquantaseimila. Che poi portare quella lingua a sedicimila ore dimezzi davvero il suo errore è un’altra affermazione, e il paper non la misura. È una misura di quanto costa fare meglio e non una classifica fra lingue, e il costo cresce in fretta.
Con lo stesso modello Whisper trascrive, traduce verso l’inglese e riconosce la lingua. A dirgli quale dei tre mestieri fare sono delle istruzioni infilate nel decoder sotto forma di simboli che non si pronunciano (in gergo token speciali): uno dice in che lingua si sta parlando, un altro se il compito è trascrivere o tradurre.
Quello che gli autori rivendicano non è che Whisper sbagli meno di tutti, ed è una distinzione da tenere. Per confrontare i riconoscitori si usano dei benchmark, che sono prove d’esame standard: raccolte di registrazioni con accanto la trascrizione giusta, sempre le stesse per tutti. La grandezza che gli autori misurano è la robustezza zero-shot, cioè come se la cava Whisper su una prova su cui non si è mai allenato: ci va meglio di quanto la sua bravura altrove lascerebbe prevedere, e con il rumore di fondo che sale peggiora più lentamente dei quattordici modelli addestrati sul corpus di riferimento, soprattutto sui rumori naturali come il brusio di un locale. Sull’audio pulito, invece, i modelli allenati apposta per quella prova gli restavano davanti. È una misura di quanto si peggiora fuori casa, non di quanto si è bravi.
E ha un fianco scoperto, che il paper dichiara. I dati vengono dal web, e sul web stanno anche i benchmark: se le frasi dell’esame erano già dentro il materiale di studio, il voto è gonfiato. Gli autori il controllo l’hanno fatto, ma su una raccolta sola (TED-LIUM 3) e confrontando le trascrizioni scritte, non l’audio; il che vuol dire che una registrazione ripubblicata altrove con parole leggermente diverse sarebbe passata inosservata.
Due precisazioni, per non lasciare a Whisper meriti che non ha e difetti che non sono solo suoi.
La prima: non è Whisper ad aver mandato in pensione la catena a stadi. Il passaggio a una rete sola era cominciato anni prima, con la CTC e con i modelli ad attenzione di queste pagine; Whisper ne è la vetrina più visibile, non l’inizio. E la vecchia catena non è nemmeno sparita: dove le parole da riconoscere sono poche e note in anticipo (i comandi di un centralino telefonico, i codici letti ad alta voce in un magazzino) i sistemi a stadi restano in servizio, perché sono più piccoli e si lasciano obbligare a scegliere solo dentro un elenco di parole ammesse.
La seconda riguarda la trascrizione in diretta, e va detta perché Whisper è così famoso che si finisce per credere che faccia tutto. Questo no: prima di scrivere la prima parola deve aver ascoltato tutto quello che gli è stato dato, e gliene diamo trenta secondi alla volta, quindi trascrive a blocchi. La dettatura che compare sullo schermo mentre parli è un’altra cosa, ed è quella dei trasduttori di poco fa, che sono end-to-end come lui ma tengono il passo dell’audio frame per frame.
E da lì vengono anche i suoi limiti, che gli autori dichiarano onestamente. Il decoder non è obbligato a scorrere l’audio in avanti, e quando il legame fra testo e suono si allenta quello che resta è un modello di linguaggio molto bravo che continua a scrivere per conto proprio, senza più guardare il suono. Sull’audio lungo il guasto si aggrava, perché ogni finestra di trenta secondi comincia dove il modello stesso ha deciso che finiva la precedente: se ha sbagliato a decidere, l’errore passa alla finestra dopo. Il paper elenca tre sintomi: le prime o le ultime parole di un segmento che non vengono trascritte, le ripetizioni in loop, e il testo inventato di sana pianta (in gergo allucinato: il modello scrive parole che nessuno ha detto). È quello che si vede nei sottotitoli automatici quando riempiono di frasi un passaggio in cui non parla nessuno.
Ecco perché prendere sempre il boccone più grosso, cioè il simbolo più votato a ogni passo, non basta. In gergo si chiama decodifica ingorda, e assomiglia al percorso migliore della CTC nel gesto ma non nel guasto: là il problema era che tanti percorsi diversi danno la stessa frase, qui è che una scelta comoda adesso vincola tutte quelle dopo, e il modello si infila in un giro da cui non esce più. Gli autori usano al suo posto una ricerca a fascio a cinque ipotesi, e quando il testo prodotto insospettisce alzano la temperatura, la manopola che decide quanto il modello si tiene stretta la propria prima scelta: è la stessa dei grandi modelli linguistici.
Il sospetto funziona così, e non serve nessuno che ascolti. Un campanello suona se il testo prodotto si ripete troppo, e per accorgersene basta comprimerlo: un testo che si ripete si comprime moltissimo, e se si comprime troppo qualcosa non va. L’altro suona se la rete stessa, guardando i voti che ha dato, risulta poco convinta di quello che ha appena scritto. Quando uno dei due suona si rifà il pezzo a temperatura più alta, e siccome una temperatura alta rende il modello meno incaponito sulla parola che gli sembra ovvia, gli capiterà di provarne una diversa: è esattamente quello che serve per uscire da un loop, dove il modello continua a riscegliere la stessa cosa.
Il modello di linguaggio, il correttore silenzioso#
Quello che dice il suono, da solo, non basta mai. In italiano «l’ago» e «lago», «l’una» e «luna» si pronunciano allo stesso identico modo: a decidere è il contesto. Qui entra il modello di linguaggio (LM), che sa quali sequenze di parole sono frasi plausibili e sposta la trascrizione verso ciò che «suona» come italiano corretto.
Il modo di farlo entrare cambia con l’epoca. Nei sistemi classici il modello di linguaggio non si affiancava al riconoscitore. Quei sistemi, prima di ascoltare, si costruivano una specie di mappa stradale di tutto ciò che si poteva dire: dai suoni alle parole, dalle parole alle frasi, con un costo scritto su ogni strada. Trascrivere voleva dire attraversare quella mappa cercando il percorso più economico. Il modello di linguaggio non arrivava dopo: i suoi giudizi erano già scritti nei costi delle strade, insieme al dizionario di pronuncia, e una manopola regolava quanto contassero rispetto al parere dell’orecchio.
Nei modelli end-to-end quella mappa non si costruisce più, e lo stesso effetto si ottiene in due modi. O si somma il punteggio di un modello di linguaggio esterno a quello del riconoscitore a ogni passo della ricerca a fascio (si chiama shallow fusion, «fusione superficiale» [KWN+18]), o si lascia finire la ricerca e si riordinano con il modello di linguaggio le prime \(n\) ipotesi che ha prodotto.
Quanto serva, però, dipende da quale modello si sta usando, e le due famiglie non stanno affatto sulla stessa barca. Un modello che scrive rileggendosi, come Whisper o come il Listen, Attend and Spell di prima (in gergo: con decoder autoregressivo), un modello di linguaggio ce l’ha già dentro. Lo ha imparato senza volerlo, perché sceglie ogni pezzo di testo guardando quelli che ha già scritto. Un modello di linguaggio esterno gli serve comunque, e Kannan e colleghi lo misurano: la fusione superficiale porta un decoder con attenzione dal 10,3 al 6,9 per cento di parole sbagliate sul corpus del Wall Street Journal, dove si legge ad alta voce testo di giornale, e dal 7,7 al 7,0 sulle ricerche vocali. Ma il guadagno si assottiglia man mano che le trascrizioni viste in addestramento aumentano, perché il decoder diventa da sé un modello di linguaggio robusto, e quello che resta è soprattutto sui termini rari o di dominio (nomi propri, sigle, gergo medico). Un modello CTC è tutta un’altra faccenda: decide ogni frame per conto suo, guardando il suono e mai le lettere che ha già scritto, ed è quell’ignoranza già annunciata. Non è che modelli male il testo in uscita: non ha il posto dove metterlo, e un modello di linguaggio interno non ce l’ha. Per lui quello esterno non è un miglioramento marginale, è il pezzo che gli manca: senza, i caratteri escono quasi giusti ma sparpagliati su parole che non esistono. Il trasduttore sta in mezzo, e ora si capisce perché: la sua prediction network (il foglio che lo stenografo si rilegge) è il modello di linguaggio interno che alla CTC mancava.
Misurare gli errori: il Word Error Rate#
Come diciamo che una trascrizione è «buona»? La metrica standard è il Word Error Rate (WER), ed è la distanza di edit che portava da carta a casa, con le parole al posto delle lettere: il numero minimo di correzioni (cambia una parola, toglila, aggiungine una) che servono per trasformare la trascrizione prodotta in quella di riferimento.
dove \(S\) è il numero di sostituzioni, \(D\) le cancellazioni, \(I\) le inserzioni e \(N\) il numero di parole nel riferimento, che è anche \(S + D + C\) con \(C\) le parole indovinate. Attenzione ai nomi, perché sono dal punto di vista del sistema e non di chi corregge: una cancellazione è una parola che il sistema si è mangiato, un’inserzione è una parola che ha aggiunto di suo. Chi corregge fa il gesto opposto, ma l’errore si chiama così. Un WER di \(0\) è la trascrizione perfetta, e siccome \(N = S + D + C\) il rapporto supera \(1\) esattamente quando le parole aggiunte sono più di quelle indovinate, \(I > C\): non serve che il sistema ne aggiunga più di quante ce ne siano.
Un conto per intero, sull’esempio di carta e casa del capitolo sul linguaggio naturale. Il riferimento è «il gatto nero salta sul muro», sei parole; il sistema ha scritto «il gatto nemo salta muro». Gli errori sono due: ha scritto «nemo» al posto di «nero» (una sostituzione) e si è mangiato «sul» (una cancellazione). Due errori su sei parole, il WER è \(2/6 = 0{,}33\), cioè il 33%.
Trovare quei due errori a occhio è facile su sei parole e impossibile su seicento, perché le combinazioni sono tante e ne vogliamo il numero minimo. Si riempie la stessa tabella di allora, quella della distanza di Levenshtein.
import numpy as np
def wer(rif, ip):
r, h = rif.split(), ip.split()
# matrice di distanza di edit riempita per programmazione dinamica
D = np.zeros((len(r) + 1, len(h) + 1), dtype=int)
D[:, 0] = np.arange(len(r) + 1) # cancellazioni pure
D[0, :] = np.arange(len(h) + 1) # inserzioni pure
for i in range(1, len(r) + 1):
for j in range(1, len(h) + 1):
costo = 0 if r[i - 1] == h[j - 1] else 1
D[i, j] = min(D[i - 1, j] + 1, # cancellazione
D[i, j - 1] + 1, # inserzione
D[i - 1, j - 1] + costo) # sostituzione (o parola uguale)
return D[len(r), len(h)] / len(r)
print(round(wer("il gatto nero salta sul muro",
"il gatto nemo salta muro"), 3))
0.333
Il WER è comodo ma grezzo. Pesa allo stesso modo un errore grave e uno banale, e tratta male le lingue che attaccano le parole fra loro: in tedesco Geschwindigkeitsbegrenzung («limite di velocità») è una parola sola, quindi sbagliarne una sillaba conta come sbagliarla tutta, mentre in italiano lo stesso inciampo ne intaccherebbe una su tre. Per questo, accanto al WER, si riporta spesso il Character Error Rate (CER), che conta gli stessi errori a livello di carattere. Nessuna misura, però, cattura del tutto ciò che conta davvero: se la frase trascritta, letta da un essere umano, significa ancora la cosa giusta.
Tiriamo le fila.
Da ricordare
L’audio è lunghissimo e il testo è corto, e nessuno dice quale pezzetto di suono corrisponde a quale lettera: è il problema centrale del riconoscimento vocale.
La CTC lo aggira con il simbolo «vuoto»: prova tutti i modi di etichettare i frame e somma le probabilità di quelli che danno la parola giusta. I modelli con attenzione invece scrivono una lettera alla volta, rileggendosi ogni volta il pezzo di audio che serve; il trasduttore fa come uno stenografo, scrive mentre ascolta senza tornare indietro e con sotto gli occhi il foglio di quello che ha già messo giù.
Trovare la trascrizione non è prendere il simbolo più votato a ogni frame: quello è il percorso più probabile, che non è la parola più probabile. Si cerca a fascio, tenendo aperte più strade.
Whisper mette tutto in un modello solo, che fa molte lingue insieme. Se perde il filo fra suono e testo continua a scrivere per conto suo: sono le frasi inventate che ogni tanto compaiono nei sottotitoli automatici.
Il modello di linguaggio è il pezzo che sceglie fra «l’ago» e «lago», e serve soprattutto alla CTC, che di suo non sa niente delle lettere già scritte; il WER conta quante correzioni servono per rimettere a posto una trascrizione, diviso il numero di parole.
Da ricordare
Audio e testo hanno lunghezze diverse e l’allineamento non è dato: è il problema centrale dell’ASR.
La CTC lo risolve con il simbolo «vuoto» e sommando tutti gli allineamenti possibili, al prezzo dell’indipendenza condizionale fra i frame e del vincolo \(T \ge U + r\), che le vieta il verso opposto (la sintesi, dove il testo è più corto del suono); i modelli con attenzione lo imparano in modo morbido, un token alla volta, ma perdono monotonia e streaming; il trasduttore [Gra12] tiene il reticolo monotono e ci aggiunge una prediction network, cioè un LM interno.
Addestramento e decodifica non sono la stessa cosa: il best path non massimizza \(p(y \mid \mathbf{X})\), e la beam search del CTC somma i percorsi che collassano nello stesso prefisso invece di metterli in concorrenza.
I Transformer end-to-end come Whisper [RKX+23] uniscono tutto in un solo modello multilingue; zero-shot è il protocollo (nessuna messa a punto sulla prova d’esame), e ciò che si misura è di quanto si peggiora fuori casa, non quanto si è bravi; i loro loop nascono dall’allineamento testo-audio che si stacca.
Il modello di linguaggio disambigua gli omofoni, si integra per shallow fusion o per riordino delle \(n\) ipotesi, ed è indispensabile alla CTC proprio perché la CTC non ne ha uno implicito; il WER misura gli errori come distanza di edit fra parole.