Cos’è un Transformer?
Introduzione ai trasformatori e all'apprendimento da sequenza a sequenza per il machine learning
Prima di iniziare a leggere questo tutorial, se siete carenti sulle reti neurali, in particolare le Recurrent Neural Networks, vi consiglio di iscrivervi prima al nostro corso di Deep Learning per il Sequence modelling in modo da rafforzare le vostre conoscenze e comprendere meglio quanto andrò a spiegare.
I nuovi modelli di deep learning vengono introdotti a ritmo crescente e a volte è difficile tenere traccia di tutte le novità. Detto questo, un particolare modello di rete neurale si è dimostrato particolarmente efficace per le comuni attività di elaborazione del linguaggio naturale. Il modello si chiama Transformer e fa uso di diversi metodi e meccanismi che presenterò qui. I documenti a cui faccio riferimento nel post offrono una descrizione più dettagliata e quantitativa.
Apprendimento da sequenza a sequenza e attenzione
L’articolo “Attention Is All You Need” (L’attenzione è tutto ciò che serve) descrive i trasformatori e quella che viene definita un’architettura sequenza-sequenza. Sequence-to-Sequence (o Seq2Seq) è una rete neurale che trasforma una data sequenza di elementi, come la sequenza di parole in una frase, in un’altra sequenza. (Beh, questo potrebbe non sorprendervi visto il nome).
I modelli Seq2Seq sono particolarmente adatti alla traduzione, dove la sequenza di parole di una lingua viene trasformata in una sequenza di parole diverse in un’altra lingua. Una scelta popolare per questo tipo di modello è Seq2Seq. I modelli sono costituiti da un Encoder e da un Decoder. L’Encoder prende la sequenza in ingresso e la mappa in uno spazio dimensionale superiore (vettore n-dimensionale). Questo vettore astratto viene inserito nel Decoder che lo trasforma in una sequenza di uscita. La sequenza di uscita può essere in un altro linguaggio, in simboli, una copia dell’input, ecc. Immaginiamo che Encoder e Decoder siano traduttori umani in grado di parlare solo due lingue. La prima lingua è la lingua madre, diversa per entrambi (ad esempio, il tedesco e il francese), mentre la seconda è una lingua immaginaria che hanno in comune. Per tradurre il tedesco in francese, l’encoder converte la frase tedesca nell’altra lingua che conosce, quella immaginaria. Poiché il Decodificatore è in grado di leggere quella lingua immaginaria, può ora tradurre da quella lingua in francese. Insieme, il modello (composto da Encoder e Decoder) può tradurre il tedesco in francese!
Supponiamo che, inizialmente, né l’Encoder né il Decoder conoscano bene la lingua immaginaria. Per impararla, li addestriamo (il modello) su molti esempi.
Una scelta molto semplice per l’Encoder e il Decoder del modello Seq2Seq è un singolo LSTM per ciascuno di essi.
Vi state chiedendo quando il Transformer entrerà finalmente in gioco, vero?
Abbiamo bisogno di un ulteriore dettaglio tecnico per rendere i trasformatori più facili da capire: L’attenzione. Il meccanismo di attenzione esamina una sequenza di input e decide a ogni passo quali altre parti della sequenza sono importanti.
Un meccanismo di attenzione funziona in modo simile per una data sequenza. Per il nostro esempio con l’Encoder e il Decoder umani, immaginiamo che invece di scrivere solo la traduzione della frase nella lingua immaginaria, l’Encoder scriva anche delle parole chiave importanti per la semantica della frase, e le dia al Decoder in aggiunta alla normale traduzione. Queste nuove parole chiave rendono la traduzione molto più semplice per il Decodificatore, che sa quali parti della frase sono importanti e quali termini chiave forniscono il contesto della frase.
In altre parole, per ogni input che l’LSTM (Encoder) legge, il meccanismo di attenzione prende in considerazione diversi altri input allo stesso tempo e decide quali sono importanti attribuendo pesi diversi a tali input. Il decodificatore prende in input la frase codificata e i pesi forniti dal meccanismo di attenzione. Per saperne di più sull’attenzione, si veda questo articolo. E per un approccio più scientifico di quello fornito, leggete i diversi approcci basati sull’attenzione per i modelli Sequence-to-Sequence in questo ottimo documento intitolato “Approcci efficaci alla traduzione automatica neurale basata sull’attenzione”.
Il trasformatore
L’articolo “Attention Is All You Need” presenta un’architettura innovativa chiamata Transformer. Come indica il titolo, utilizza il meccanismo di attenzione che abbiamo visto in precedenza. Come LSTM, Transformer è un’architettura per trasformare una sequenza in un’altra con l’aiuto di due parti (Encoder e Decoder), ma si differenzia dai modelli sequenza-sequenza precedentemente descritti/esistenti perché non implica alcuna rete ricorrente (GRU, LSTM, ecc.).
Le reti ricorrenti sono state finora uno dei modi migliori per catturare le dipendenze puntuali nelle sequenze. Tuttavia, il team che ha presentato l’articolo ha dimostrato che un’architettura con i soli meccanismi di attenzione senza alcuna RNN (Recurrent Neural Networks) può migliorare i risultati nel compito di traduzione e in altri compiti!
Quindi, cos’è esattamente un Transformer?
L’Encoder è a sinistra e il Decoder a destra. Sia l’Encoder che il Decoder sono composti da moduli che possono essere impilati l’uno sull’altro più volte, il che è descritto da Nx nella figura. Si nota che i moduli sono costituiti principalmente da livelli di attenzione a più teste e da livelli Feed Forward. Gli input e gli output (frasi target) vengono prima inseriti in uno spazio n-dimensionale, poiché non possiamo usare direttamente le stringhe.
Una piccola ma importante parte del modello è la codifica posizionale delle diverse parole. Poiché non disponiamo di reti ricorrenti in grado di ricordare il modo in cui le sequenze vengono inserite nel modello, dobbiamo in qualche modo assegnare a ogni parola/parte della nostra sequenza una posizione relativa, poiché una sequenza dipende dall’ordine dei suoi elementi. Queste posizioni vengono aggiunte alla rappresentazione incorporata (vettore n-dimensionale) di ogni parola.
Diamo un’occhiata più da vicino a questi mattoncini di attenzione multi-testa nel modello:
Cominciamo con la descrizione a sinistra del meccanismo di attenzione. Non è molto complicato e può essere descritto dalla seguente equazione:
Q è una matrice che contiene la query (rappresentazione vettoriale di una parola della sequenza), K sono tutte le chiavi (rappresentazioni vettoriali di tutte le parole della sequenza) e V sono i valori, che sono di nuovo le rappresentazioni vettoriali di tutte le parole della sequenza. Per i moduli di attenzione multitesta del codificatore e del decodificatore, V è costituito dalla stessa sequenza di parole di Q. Tuttavia, per il modulo di attenzione che tiene conto delle sequenze del codificatore e del decodificatore, V è diverso dalla sequenza rappresentata da Q.
Per semplificare un po’, potremmo dire che i valori in V sono moltiplicati e sommati con alcuni pesi di attenzione a, dove i pesi sono definiti da:
Ciò significa che i pesi a sono definiti da come ogni parola della sequenza (rappresentata da Q) è influenzata da tutte le altre parole della sequenza (rappresentate da K). Inoltre, la funzione SoftMax viene applicata ai pesi a per avere una distribuzione tra 0 e 1. Questi pesi vengono poi applicati a tutte le parole della sequenza che vengono introdotte in V (gli stessi vettori di Q per il codificatore e il decodificatore, ma diversi per il modulo che ha ingressi di codifica e decodifica).
L’immagine di destra descrive come questo meccanismo di attenzione possa essere parallelizzato in più meccanismi che possono essere utilizzati uno accanto all’altro. Il meccanismo di attenzione viene ripetuto più volte con proiezioni lineari di Q, K e V. Ciò consente al sistema di apprendere da diverse rappresentazioni di Q, K e V, a vantaggio del modello. Queste rappresentazioni lineari sono realizzate moltiplicando Q, K e V per le matrici di peso W apprese durante l’addestramento.
Le matrici Q, K e V sono diverse per ogni posizione dei moduli di attenzione nella struttura, a seconda che si trovino nell’encoder, nel decoder o tra l’encoder e il decoder. Il motivo è che si vuole prestare attenzione all’intera sequenza di ingresso del codificatore o a una parte della sequenza di ingresso del decodificatore. Il modulo di attenzione multitesta che collega l’encoder e il decoder farà in modo che la sequenza di ingresso dell’encoder venga presa in considerazione insieme alla sequenza di ingresso del decoder fino a una determinata posizione.
Dopo le teste di attenzione multipla sia nell’encoder che nel decoder, abbiamo uno strato di feed-forward puntuale. Questa piccola rete feed-forward ha parametri identici per ogni posizione, che possono essere descritti come una trasformazione lineare separata e identica di ogni elemento della sequenza data.
L'addestramento
Come addestrare questa “bestia”? L’addestramento e l’inferenza dei modelli Seq2Seq è un po’ diverso dal solito problema di classificazione. Lo stesso vale per i trasformatori.
Sappiamo che per addestrare un modello per compiti di traduzione abbiamo bisogno di due frasi in lingue diverse che siano traduzioni l’una dell’altra. Una volta che abbiamo molte coppie di frasi, possiamo iniziare ad addestrare il nostro modello. Supponiamo di voler tradurre dal francese al tedesco. Il nostro input codificato sarà una frase francese e l’input per il decodificatore sarà una frase tedesca. Tuttavia, l’input del decodificatore sarà spostato a destra di una posizione. Aspetta, perché?
Una ragione è che non vogliamo che il nostro modello impari a copiare il nostro input di decodifica durante l’addestramento, ma vogliamo imparare che, data la sequenza di codifica e una particolare sequenza di decodifica, che è già stata vista dal modello, prevediamo la parola/il carattere successivo.
Se non spostiamo la sequenza di decodifica, il modello impara semplicemente a “copiare” l’ingresso del decodificatore, poiché la parola/il carattere target per la posizione i sarebbe la parola/il carattere i nell’ingresso del decodificatore. Pertanto, spostando l’ingresso del decodificatore di una posizione, il nostro modello deve prevedere la parola/il carattere target per la posizione i avendo visto solo le parole/i caratteri 1, …, i-1 nella sequenza di decodifica. Questo impedisce al modello di apprendere il compito di copia/incolla. Riempiamo la prima posizione dell’input del decodificatore con un token di inizio frase, poiché altrimenti sarebbe vuota a causa dello spostamento a destra. Allo stesso modo, aggiungiamo un token di fine frase alla sequenza di input del decodificatore per segnare la fine di quella sequenza, che viene aggiunta anche alla frase di output. Tra poco vedremo come questo sia utile per dedurre i risultati.
Questo vale per i modelli Seq2Seq e per il Transformer. Oltre allo spostamento a destra, il Transformer applica una maschera all’ingresso nel primo modulo di attenzione multitesta per evitare di vedere potenziali elementi di sequenza “futuri”. Questo è specifico dell’architettura del Transformer perché non abbiamo RNN in cui possiamo inserire la nostra sequenza in modo sequenziale. In questo caso, inseriamo tutto insieme e, se non ci fosse una maschera, l’attenzione multitesta considererebbe l’intera sequenza di input del decodificatore in ogni posizione.
Il processo di immissione della sequenza corretta spostata nel decodificatore si chiama anche Teacher-Forcing, come descritto in questo blog.
La sequenza target che vogliamo per il calcolo delle perdite è semplicemente l’input del decodificatore (frase tedesca) senza spostamento e con un token di fine sequenza alla fine.
Inferenza
L’inferenza con questi modelli è diversa dall’addestramento, il che ha senso perché alla fine vogliamo tradurre una frase francese senza avere la frase tedesca. Il trucco consiste nell’alimentare nuovamente il modello per ogni posizione della sequenza di output, finché non si incontra un token di fine frase.
Un metodo più graduale sarebbe il seguente:
– Inseriamo la sequenza completa del codificatore (frase francese) e come input del decodificatore prendiamo una sequenza vuota con solo un token di inizio frase nella prima posizione. Si ottiene così una sequenza in cui si prende solo il primo elemento.
– Questo elemento sarà riempito nella seconda posizione della sequenza di ingresso del decodificatore, che ora ha un token di inizio frase e una prima parola/carattere.
I dati
I dati disponibili ci forniscono il carico orario dell’intera area di controllo ERCOT. Ho utilizzato i dati degli anni dal 2003 al 2015 come set di addestramento e l’anno 2016 come set di test. Avendo a disposizione solo il valore del carico e il timestamp del carico, ho esteso il timestamp ad altre caratteristiche. Dal timestamp, ho estratto il giorno della settimana a cui corrisponde e l’ho codificato con un solo punto. Inoltre, ho utilizzato l’anno (2003, 2004, …, 2015) e l’ora corrispondente (1, 2, 3, …, 24) come valore stesso. In questo modo ho ottenuto 11 caratteristiche in totale per ogni ora del giorno. Per motivi di convergenza, ho anche normalizzato il carico ERCOT dividendolo per 1000.
Per prevedere una determinata sequenza, abbiamo bisogno di una sequenza del passato. Le dimensioni di queste finestre possono variare da caso a caso, ma nel nostro esempio ho utilizzato i dati orari delle 24 ore precedenti per prevedere le 12 ore successive. È utile poter regolare le dimensioni di queste finestre in base alle nostre esigenze. Ad esempio, possiamo passare ai dati giornalieri invece che a quelli orari.
Modifiche al modello rispetto al documento
Come primo passo, dobbiamo rimuovere gli embedding, poiché abbiamo già dei valori numerici in ingresso. Un embedding solitamente mappa un dato intero in uno spazio n-dimensionale. In questo caso, invece di usare l’embedding, ho semplicemente usato una trasformazione lineare per trasformare i dati a 11 dimensioni in uno spazio n-dimensionale. Questo è simile all’incorporazione con le parole.
Dobbiamo anche rimuovere il livello SoftMax dall’uscita del trasformatore, perché i nostri nodi di uscita non sono probabilità ma valori reali.
Dopo queste piccole modifiche, l’addestramento può iniziare!
Come già detto, per l’addestramento ho usato la forzatura del docente. Ciò significa che il codificatore riceve in ingresso una finestra di 24 punti di dati e il decodificatore una finestra di 12 punti di dati in cui il primo è un valore di “inizio sequenza” e i punti di dati successivi sono semplicemente la sequenza di destinazione. Avendo introdotto un valore di “inizio sequenza” all’inizio, ho spostato l’ingresso del decodificatore di una posizione rispetto alla sequenza target.
Ho utilizzato un vettore a 11 dimensioni con solo i -1 come valori di “inizio sequenza”. Naturalmente, questo può essere modificato e forse sarebbe utile utilizzare altri valori a seconda del caso d’uso, ma per questo esempio funziona poiché non abbiamo mai valori negativi in nessuna delle due dimensioni delle sequenze di ingresso/uscita.
La funzione di perdita per questo esempio è semplicemente l’errore quadratico medio.
I risultati
I due grafici seguenti mostrano i risultati. Ho preso il valore medio dei valori orari per giorno e l’ho confrontato con i valori corretti. Il primo grafico mostra le previsioni a 12 ore date le 24 ore precedenti. Nel secondo grafico, abbiamo previsto un’ora in base alle 24 ore precedenti. Si nota che il modello è in grado di cogliere molto bene alcune fluttuazioni. L’errore quadratico medio per il set di addestramento è di 859 e per il set di validazione è di 4.106 per le previsioni a 12 ore e di 2.583 per quelle a 1 ora. Ciò corrisponde a un errore percentuale medio assoluto della previsione del modello dell’8,4% per il primo grafico e del 5,1% per il secondo.
Sintesi
I risultati mostrano che sarebbe possibile utilizzare l’architettura Transformer per la previsione delle serie temporali. Tuttavia, durante la valutazione, è emerso che più passi si vogliono prevedere, più alto sarà l’errore. Il primo grafico (Figura 3) è stato ottenuto utilizzando le 24 ore per prevedere le 12 ore successive. Se prevediamo solo un’ora, i risultati sono molto migliori, come si vede nel secondo grafico (Figura 4).
C’è molto spazio per giocare con i parametri del Transformer, come il numero di strati di decodifica e codifica, ecc. Non si tratta di un modello perfetto e, con una migliore messa a punto e formazione, i risultati probabilmente migliorerebbero.
Può essere di grande aiuto accelerare l’addestramento utilizzando le GPU. Ho usato la piattaforma locale di Watson Studio per addestrare il mio modello con le GPU e ho lasciato che venisse eseguito lì piuttosto che sulla mia macchina locale. È anche possibile accelerare l’addestramento utilizzando le GPU di Watson per l’apprendimento automatico, che sono gratuite fino a una certa quantità di tempo di addestramento! Consultate il mio blog precedente per vedere come si può integrare facilmente nel vostro codice.
Vi ringrazio per aver letto questo articolo e spero di essere riuscito a chiarire alcune nozioni a chi sta iniziando ad avvicinarsi al Deep Learning!