Semplificazione dei Transformers NLP all’avanguardia utilizzando parole comprensibili – parte 3 – Attenzione

Semplificazione dei Transformers NLP utilizzando parole comprensibili - parte 3 - Attenzione

Approfondimento sulla tecnica principale delle LLMs – attenzione

I transformer hanno avuto un impatto significativo nel campo dell’IA, forse in tutto il mondo. Questa architettura è composta da diversi componenti, ma come suggerisce il titolo del paper originale “L’attenzione è tutto ciò di cui hai bisogno”, è evidente che il meccanismo di attenzione riveste particolare importanza. La parte 3 di questa serie si concentrerà principalmente sull’attenzione e sulle funzionalità ad essa associate che assicurano che il filarmonico del Transformer suoni bene insieme.

Immagine tratta dal paper originale di Vaswani, A. et al.

Attenzione

Nel contesto dei transformer, l’attenzione si riferisce a un meccanismo che consente al modello di concentrarsi sulle parti rilevanti dell’input durante l’elaborazione. Immagina una torcia che illumina parti specifiche di una frase, consentendo al modello di attribuire maggiore o minore importanza a seconda del contesto. Credo che gli esempi siano più efficaci delle definizioni perché sono una sorta di enigma per il cervello, offrendo la possibilità di colmare le lacune e comprendere il concetto autonomamente.

Quando viene presentata la frase “L’uomo ha preso la sedia e è scomparso”, assegni naturalmente gradi di importanza diversi (ad esempio, attenzione) a diverse parti della frase. Sorprendentemente, se rimuoviamo parole specifiche, il significato rimane principalmente intatto: “uomo ha preso sedia scomparso”. Anche se questa versione è un inglese errato, rispetto alla frase originale è comunque possibile comprendere l’essenza del messaggio. È interessante notare che tre parole (“the”, “the” e “and”) rappresentano il 43% delle parole della frase ma non contribuiscono significativamente al significato complessivo. Questa osservazione era probabilmente chiara a ogni berlinese che si è imbattuto nel mio incredibile tedesco mentre viveva lì (si può imparare il tedesco o essere felici, è una decisione che devi prendere), ma è molto meno evidente per i modelli di apprendimento automatico.

In passato, architetture precedenti come le RNN (Reti Neurali Ricorrenti) hanno affrontato una sfida significativa: hanno faticato a “ricordare” parole che apparivano molto indietro nella sequenza di input, di solito oltre 20 parole. Come già sai, questi modelli si basano essenzialmente su operazioni matematiche per elaborare i dati. Purtroppo, le operazioni matematiche utilizzate nelle architetture precedenti non erano sufficientemente efficienti per trasportare adeguatamente le rappresentazioni delle parole nel futuro lontano della sequenza.

Questa limitazione nella dipendenza a lungo termine ha ostacolato la capacità delle RNN di mantenere informazioni contestuali per periodi prolungati, influenzando compiti come la traduzione di linguaggi o l’analisi dei sentimenti in cui è cruciale comprendere l’intera sequenza di input. Tuttavia, i transformer, con il loro meccanismo di attenzione e i meccanismi di auto-attenzione, affrontano in modo più efficace questa problematica. Possono catturare efficientemente le dipendenze su lunghe distanze nell’input, consentendo al modello di conservare contesto e associazioni anche per le parole che appaiono molto prima nella sequenza. Di conseguenza, i transformer sono diventati una soluzione innovativa per superare i limiti delle architetture precedenti e hanno notevolmente migliorato le prestazioni di vari compiti di elaborazione del linguaggio naturale.

Per creare prodotti eccezionali come i chatbot avanzati che incontriamo oggi, è essenziale dotare il modello della capacità di distinguere tra parole di alto e basso valore e di conservare informazioni contestuali su lunghe distanze nell’input. Il meccanismo introdotto nell’architettura dei transformer per affrontare queste sfide è noto come attenzione.

*Gli esseri umani sviluppano da molto tempo tecniche per discriminare tra esseri umani, ma, per quanto siano ispiratrici, non le useremo qui.

Prodotto scalare

Come può un modello discernere teoricamente l’importanza di diverse parole? Quando analizziamo una frase, cerchiamo di identificare le parole che hanno relazioni più forti tra loro. Poiché le parole sono rappresentate come vettori (di numeri), abbiamo bisogno di una misura per la similarità tra i numeri. Il termine matematico per misurare la similarità tra vettori è “Prodotto scalare”. Coinvolge la moltiplicazione degli elementi di due vettori e la produzione di un valore scalare (ad esempio, 2, 16, -4,43), che funge da rappresentazione della loro similarità. L’apprendimento automatico si basa su varie operazioni matematiche e tra queste il prodotto scalare riveste particolare importanza. Pertanto, prenderò il tempo per approfondire questo concetto.

IntuizioneImmagina di avere rappresentazioni reali (embeddings) per 5 parole: “florida”, “california”, “texas”, “politica” e “verità”. Poiché gli embeddings sono solo numeri, potremmo teoricamente rappresentarli in un grafico. Tuttavia, a causa della loro alta dimensionalità (il numero di numeri utilizzati per rappresentare la parola), che può variare facilmente da 100 a 1000, non possiamo rappresentarli così come sono. Non possiamo rappresentare un vettore a 100 dimensioni su uno schermo di computer/telefono a 2D. Inoltre, il cervello umano trova difficile comprendere qualcosa al di là delle 3 dimensioni. Come appare un vettore a 4 dimensioni? Non lo so.

Per superare questo problema, utilizziamo l’Analisi delle Componenti Principali (PCA), una tecnica che riduce il numero di dimensioni. Applicando la PCA, possiamo proiettare le embeddings su uno spazio bidimensionale (coordinate x,y). Questa riduzione delle dimensioni ci aiuta a visualizzare i dati su un grafico. Sebbene si perda alcune informazioni a causa della riduzione, speriamo che questi vettori ridotti conservino comunque sufficienti similarità alle embeddings originali, consentendoci di ottenere intuizioni e comprendere le relazioni tra le parole.

Questi numeri si basano sulle Embedding GloVe.

florida = [-2.40062016, 0.00478901]california = [-2.54245794, -0.37579669]texas = [-2.24764634, -0.12963368]politics = [3.02004564, 2.88826688]truth = [4.17067881, -2.38762552]

Forse puoi notare un certo pattern nei numeri, ma rappresenteremo i numeri per semplificarti la vita.

5 vettori 2D

In questa visualizzazione, vediamo cinque vettori 2D (coordinate x,y), che rappresentano 5 parole diverse. Come puoi vedere, il grafico suggerisce che alcune parole sono molto più correlate ad altre.

matematicaIl corrispondente matematico della visualizzazione dei vettori può essere espresso attraverso un’equazione semplice. Se non ti piace particolarmente la matematica e ricordi la descrizione degli autori dell’architettura Transformers come una “architettura di rete semplice”, probabilmente pensi che sia così che accade alle persone del ML, diventano strane. È probabilmente vero, ma non in questo caso, questo è semplice. Spiegherò:

Formula prodotto scalare

Il simbolo ||a|| indica la magnitudine del vettore “a”, che rappresenta la distanza tra l’origine (punto 0,0) e la punta del vettore. Il calcolo della magnitudine è il seguente:

Formula magnitudine vettore

Il risultato di questo calcolo è un numero, come ad esempio 4 o 12.4. Theta (θ) si riferisce all’angolo tra i vettori (guarda la visualizzazione). Il coseno di theta, indicato come cos(θ), è semplicemente il risultato dell’applicazione della funzione coseno a tale angolo.

codiceUtilizzando l’algoritmo GloVe, i ricercatori dell’Università di Stanford hanno generato le embeddings per le parole effettive, come abbiamo discusso in precedenza. Sebbene abbiano una tecnica specifica per creare queste embeddings, il concetto sottostante rimane lo stesso di quello discusso nella parte precedente della serie. Come esempio, ho preso 4 parole, ridotto la loro dimensionalità a 2 e poi ho rappresentato i loro vettori come coordinate x e y.

Per far funzionare correttamente questo processo, è necessario scaricare le embeddings GloVe.

* Parte del codice, in particolare la prima casella, è ispirata a un codice che ho visto, ma non riesco a trovare la fonte.

import pandas as pdpath_to_glove_embds = 'glove.6B.100d.txt'glove = pd.read_csv(path_to_glove_embds, sep=" ", header=None, index_col=0)glove_embedding = {key: val.values for key, val in glove.T.items()}

words = ['florida', 'california', 'texas', 'politics', 'truth']word_embeddings = [glove_embedding[word] for word in words]print(word_embeddings[0]).shape # 100 numeri per rappresentare ogni parola.---------------------output:(100,)

pca = PCA(n_components=2) # riduci la dimensionalità da 100 a 2.word_embeddings_pca = pca.fit_transform(word_embeddings)

for i in range(5):    print(word_embeddings_pca[i])---------------------output:[-2.40062016  0.00478901] # florida[-2.54245794 -0.37579669] # california[-2.24764634 -0.12963368] # texas[3.02004564 2.88826688] # politics[ 4.17067881 -2.38762552] # truth

Ora possediamo una rappresentazione autentica di tutte e 5 le parole. Il nostro prossimo passo è quello di effettuare i calcoli del prodotto scalare.

Magnitudine del vettore:

import numpy as npflorida_vector = [-2.40062016,  0.00478901]florida_vector_magnitude = np.linalg.norm(florida_vector)print(florida_vector_magnitude)---------------------output:2.4006249368060817 # La magnitudine del vettore "florida" è 2.4.

Prodotto scalare tra due vettori simili.

import numpy as npflorida_vector = [-2.40062016,  0.00478901]texas_vector = [-2.24764634 -0.12963368]print(np.dot(florida_vector, texas_vector))---------------------output:5.395124299364358

Prodotto scalare tra due vettori diversi.

import numpy as npflorida_vector = [-2.40062016,  0.00478901]truth_vector = [4.17067881, -2.38762552]print(np.dot(florida_vector, truth_vector))---------------------output:-10.023649994662344

Come si evince dal calcolo del prodotto scalare, sembra catturare e riflettere una comprensione delle somiglianze tra concetti diversi.

Attenzione del prodotto scalato

intuizione Ora che abbiamo compreso il Prodotto Scalare, possiamo approfondire l’attenzione. In particolare, il meccanismo di auto-attenzione. Utilizzando l’auto-attenzione, il modello è in grado di determinare l’importanza di ogni parola, indipendentemente dalla sua “prossimità” fisica con la parola. Questo consente al modello di prendere decisioni informate basate sulla rilevanza contestuale di ogni parola, portando a una migliore comprensione.

Per raggiungere questo ambizioso obiettivo, creiamo 3 matrici composte da parametri apprendibili (!), conosciute come Query, Key e Value (Q, K, V). La matrice di query può essere immaginata come una matrice di query contenente le parole per le quali l’utente si interroga o richiede informazioni (ad esempio, quando si chiede a chatGPT se: “Dio è disponibile oggi alle 17:00?” quella è la query). La matrice delle chiavi include tutte le altre parole nella sequenza. Calcolando il prodotto scalare tra queste matrici, otteniamo il grado di correlazione tra ogni parola e la parola che stiamo esaminando al momento (ad esempio, traducendo o producendo la risposta alla query).

La matrice dei valori fornisce la rappresentazione “pulita” per ogni parola nella sequenza. Perché la chiamo “pulita” mentre le altre due matrici sono formate in modo simile? Perché la matrice dei valori rimane nella sua forma originale, non la utilizziamo dopo la moltiplicazione per un’altra matrice o la normalizziamo per qualche valore. Questa distinzione differenzia la matrice dei valori, garantendo che conservi gli embedding originali, liberi da calcoli o trasformazioni aggiuntive.

Tutte e 3 le matrici sono costruite con una dimensione di word_embedding (512). Tuttavia, sono divise in “heads”. Nel paper gli autori hanno utilizzato 8 heads, il che fa sì che ogni matrice abbia una dimensione di sequence_length per 64. Potresti chiederti perché viene eseguita la stessa operazione 8 volte con 1/8 dei dati anziché una volta con tutti i dati. La ragione dietro questo approccio è che eseguendo la stessa operazione 8 volte con 8 diversi insiemi di pesi (che, come accennato, sono apprendibili), possiamo sfruttare la diversità intrinseca nei dati. Ogni head può concentrarsi su un aspetto specifico all’interno dell’input e, in totale, ciò può portare a una migliore performance.

* Nella maggior parte delle implementazioni non dividiamo realmente la matrice principale in 8. La divisione viene ottenuta tramite indicizzazione, consentendo un’elaborazione parallela per ogni parte. Tuttavia, questi sono solo dettagli di implementazione. Teoricamente, avremmo potuto fare praticamente la stessa cosa utilizzando 8 matrici.

Le matrici Q e K vengono moltiplicate (prodotto scalare) e poi normalizzate per la radice quadrata del numero di dimensioni. Passiamo il risultato attraverso una funzione Softmax e il risultato viene quindi moltiplicato per la matrice V. La ragione per la normalizzazione dei risultati è che le matrici Q e K sono generate in modo più o meno casuale. Le loro dimensioni potrebbero essere completamente non correlate (indipendenti) e le moltiplicazioni tra matrici indipendenti potrebbero produrre numeri molto grandi che possono danneggiare l’apprendimento, come spiegherò più avanti in questa parte. Quindi utilizziamo una trasformazione non lineare chiamata Softmax per rendere tutti i numeri compresi tra 0 e 1 e che sommino a 1. Il risultato è simile a una distribuzione di probabilità (in quanto ci sono numeri da 0 a 1 che sommano a 1). Questi numeri esemplificano la rilevanza di ogni parola rispetto a ogni altra parola nella sequenza. Infine, moltiplichiamo il risultato per la matrice V e, miracolo dei miracoli, otteniamo il punteggio di auto-attenzione.

*L’encoder è effettivamente costruito da N (nel paper, N=6) livelli identici, ognuno di questi livelli riceve in input il livello precedente e fa la stessa cosa. L’ultimo livello passa i dati sia al Decoder (di cui parleremo in una parte successiva di questa serie) che ai livelli superiori dell’Encoder.

Ecco una visualizzazione dell’auto-attenzione. È come gruppi di amici in una classe. Alcune persone sono più connesse ad alcune persone. Alcune persone non sono molto connesse a nessuno.

Immagine tratta dal paper originale di Vaswani, A. et al.

matematica Le matrici Q, K e V sono derivate attraverso una trasformazione lineare della matrice di incorporamento. Le trasformazioni lineari sono importanti nell’apprendimento automatico e se sei interessato a diventare un professionista di ML, ti consiglio di approfondirle ulteriormente. Non approfondirò molto, ma dirò che la trasformazione lineare è un’operazione matematica che sposta un vettore (o una matrice) da uno spazio a un altro spazio. Sembra più complicato di quanto non sia. Immagina una freccia che punta in una direzione e poi si sposta di 30 gradi verso destra. Questo illustra una trasformazione lineare. Ci sono alcune condizioni perché tale operazione sia considerata lineare, ma non sono davvero importanti per ora. La cosa principale è che conserva molte delle proprietà del vettore originale.

L’intero calcolo dei livelli di auto-attenzione viene eseguito applicando la seguente formula:

Attenzione a prodotto punto scalato - Immagine tratta dal paper originale di Vaswani, A. et al.

Il processo di calcolo si svolge come segue:1. Moltiplichiamo Q per K trasposto (ribaltato). 2. Dividiamo il risultato per la radice quadrata della dimensionalità della matrice K.3. Ora abbiamo le “matrici di attenzione” che descrivono quanto ogni parola sia simile a ogni altra parola. Passiamo ogni riga a una trasformazione Softmax (non lineare). Softmax fa tre cose interessanti:a. Scala tutti i numeri in modo che siano compresi tra 0 e 1.b. Fa sì che la somma di tutti i numeri sia 1.c. Accentua le differenze, rendendo le cose leggermente più importanti molto più importanti. Di conseguenza, ora possiamo distinguere facilmente i diversi gradi con cui il modello percepisce la connessione tra le parole x1 e x2, x3, x4, e così via.4. Moltiplichiamo il punteggio per la matrice V. Questo è il risultato finale dell’operazione di auto-attenzione.

Mascheramento

Nel capitolo precedente di questa serie, ho spiegato che utilizziamo token fittizi per trattare le occorrenze speciali nella frase, come la prima parola nella frase, l’ultima parola, ecc. Uno di questi token, indicato come <PADDING>, indica che non ci sono dati effettivi eppure è necessario mantenere dimensioni di matrice coerenti durante l’intero processo. Per assicurarsi che il modello comprenda che si tratta di token fittizi e che quindi non devono essere considerati durante il calcolo di auto-attenzione, rappresentiamo questi token come meno infinito (ad esempio, un numero negativo molto grande, ad esempio -153513871339). I valori di mascheramento vengono aggiunti al risultato della moltiplicazione tra Q per K. Softmax trasforma quindi questi numeri in 0. Ciò ci consente di ignorare efficacemente i token fittizi durante il meccanismo di attenzione, preservando l’integrità dei calcoli.

Dropout

Dopo il livello di auto-attenzione, viene applicata un’operazione di dropout. Il dropout è una tecnica di regolarizzazione ampiamente utilizzata nell’apprendimento automatico. Lo scopo della regolarizzazione è imporre vincoli al modello durante l’addestramento, rendendo più difficile per il modello fare affidamento pesante su dettagli di input specifici. Di conseguenza, il modello impara in modo più robusto e migliora la sua capacità di generalizzazione. L’implementazione effettiva prevede la scelta casuale di alcune attivazioni (i numeri che escono da diversi livelli) e annullarne il valore. In ogni passaggio dello stesso livello, diverse attivazioni saranno annullate, impedendo al modello di trovare soluzioni specifiche per i dati che riceve. In sostanza, il dropout aiuta a migliorare la capacità del modello di gestire input diversi e rende più difficile per il modello essere adattato a pattern specifici nei dati.

Connessione di salto

Un’altra importante operazione eseguita nell’architettura del Transformer è chiamata connessione di salto.

Immagine tratta dall'articolo originale di Vaswani, A. et al.

La connessione di salto è un modo per passare l’input senza sottoporlo a alcuna trasformazione. Per illustrare, immagina che io riporti al mio responsabile che lo riporta al suo responsabile. Anche con intenzioni molto pure di rendere il rapporto più utile, l’input passa ora attraverso alcune modifiche quando viene elaborato da un altro essere umano (o dal livello di ML). In questa analogia, la connessione di salto sarei io, che riporto direttamente al responsabile del mio responsabile. Di conseguenza, il responsabile superiore riceve l’input sia attraverso il mio responsabile (dati elaborati) e direttamente da me (non elaborati). Il responsabile senior può quindi prendere una decisione migliore. La logica alla base dell’utilizzo delle connessioni di salto è affrontare potenziali problemi come i gradienti che tendono a scomparire, che spiegherò nella sezione seguente.

Livello di aggiunta e normalizzazione

IntuizioneIl livello di “Aggiunta e Normalizzazione” esegue l’addizione e la normalizzazione. Inizierò con l’addizione perché è più semplice. Fondamentalmente, aggiungiamo l’output del livello di autoattenzione all’input originale (ricevuto dalla connessione di salto). Questa addizione viene eseguita elemento per elemento (ogni numero al suo numero corrispondente). Il risultato viene quindi normalizzato.

La ragione per cui normalizziamo, ancora una volta, è che ogni livello esegue numerosi calcoli. Moltiplicare i numeri molte volte può portare a scenari non desiderati. Ad esempio, se prendo una frazione, come 0,3, e la moltiplico per un’altra frazione, come 0,9, ottengo 0,27 che è più piccolo rispetto a dove è iniziato. Se faccio questo molte volte, potrei finire con qualcosa di molto vicino a 0. Questo potrebbe portare a un problema nell’apprendimento profondo chiamato gradienti che tendono a scomparire. Non approfondirò troppo in questo momento in modo che l’articolo non impieghi un’eternità per essere letto, ma l’idea è che se i numeri diventano molto vicini a 0, il modello non sarà in grado di apprendere. La base dell’ML moderno consiste nel calcolare i gradienti e regolare i pesi utilizzando quei gradienti (e alcuni altri ingredienti). Se quei gradienti sono vicini a 0, sarà molto difficile per il modello imparare in modo efficace.

Al contrario, può verificarsi il fenomeno opposto, chiamato gradienti che tendono ad esplodere, quando i numeri che non sono frazioni vengono moltiplicati per numeri non frazionari, causando l’aumento eccessivo dei valori. Di conseguenza, il modello affronta difficoltà nell’apprendimento a causa dei notevoli cambiamenti nei pesi e nelle attivazioni, che possono portare a instabilità e divergenza durante il processo di addestramento.

I modelli di ML sono un po’ come un bambino piccolo, hanno bisogno di protezione. Uno dei modi per proteggere questi modelli da numeri troppo grandi o troppo piccoli è la normalizzazione.

MatematicaL’operazione di normalizzazione del livello sembra spaventosa (come sempre), ma è in realtà relativamente semplice.

Immagine di Pytorch, tratta da qui

Nell’operazione di normalizzazione del livello, seguiamo questi semplici passaggi per ogni input:

  1. Sottrai la sua media dall’input.
  2. Dividi per la radice quadrata della varianza e aggiungi un epsilon (un piccolo numero), utilizzato per evitare la divisione per zero.
  3. Moltiplica il punteggio risultante per un parametro apprendibile chiamato gamma (γ).
  4. Aggiungi un altro parametro apprendibile chiamato beta (β).

Questi passaggi assicurano che la media sia vicina a 0 e la deviazione standard vicina a 1. Il processo di normalizzazione migliora la stabilità, la velocità e le prestazioni complessive dell’addestramento.

Codice

# x è l'input.(x - mean(x)) / sqrt(variance(x) + epsilon) * gamma + beta

Sommario:

A questo punto, abbiamo una solida comprensione del funzionamento interno principale dell’Encoder. Inoltre, abbiamo esplorato le connessioni di salto, una tecnica puramente tecnica (e importante) nell’ambito dell’ML che migliora la capacità del modello di apprendere.

Anche se questa sezione è un po’ complicata, hai già acquisito una comprensione sostanziale dell’architettura dei Transformers nel suo complesso. Man mano che procediamo nella serie, questa comprensione ti servirà per comprendere le parti rimanenti. Ricorda, questo è lo stato dell’arte in un campo complicato. Non è facile. Anche se non capisci ancora tutto al 100%, complimenti per aver compiuto grandi progressi!

La prossima parte riguarderà un concetto fondamentale (e più semplice) nel Machine Learning, la Rete Neurale Feed Forward.

Immagine tratta dal paper originale di Vaswani, A. et al.