Comprendere le reti Transformer e il meccanismo di Self-Attention

Un diagramma tecnico dell'architettura Transformer che mostra le connessioni di self-attention

Nel 2017, il panorama dell’intelligenza artificiale è cambiato per sempre con la pubblicazione del seminale articolo “Attention Is All You Need” di Vaswani et al. L’articolo ha introdotto il Transformer, una rivoluzionaria architettura di rete neurale che ha abbandonato completamente la ricorrenza (RNN, LSTM), optando invece per l’elaborazione in parallelo di dati sequenziali tramite il Meccanismo di Self-Attention.

Oggi, i Transformer alimentano quasi tutti i modelli linguistici di grandi dimensioni (LLM) all’avanguardia, tra cui GPT-4, Gemini, Claude e Llama. Questo blog demistifica la rete Transformer e spiega come il meccanismo di self-attention viene implementato matematicamente e praticamente.


1. Il collo di bottiglia dell’elaborazione sequenziale (RNN vs. Transformer)

Prima dei Transformer, i modelli come le reti neurali ricorrenti (RNN) e le reti Long Short-Term Memory (LSTM) erano lo standard per la modellazione delle sequenze. Tuttavia, le RNN elaborano i token in modo sequenziale, una parola alla volta. Per calcolare lo stato nascosto per la decima parola, il modello deve prima calcolare gli stati nascosti per le parole da 1 a 9.

Questa natura sequenziale introduce due gravi limitazioni:

  1. Nessuna parallelizzazione: Le GPU moderne non possono essere utilizzate in modo efficiente perché i calcoli devono attendere il completamento del passaggio precedente.
  2. Gradienti che svaniscono o esplodono: Le informazioni provenienti dall’inizio di una lunga sequenza vengono compresse e vanno perse nel momento in cui il modello raggiunge la fine (il problema del collo di bottiglia).

I Transformer risolvono entrambi i problemi. Sostituendo la ricorrenza con la Self-Attention, un Transformer procesa l’intera sequenza di input contemporaneamente, consentendo una parallelizzazione massiccia e un percorso diretto tra due token qualsiasi in una sequenza, indipendentemente dalla distanza.


2. Cos’è il meccanismo di Self-Attention?

La self-attention (auto-attenzione) consente al modello di valutare la relazione tra parole diverse nella stessa sequenza. Invece di elaborare una parola in isolamento, il modello rappresenta ogni parola prendendo il contesto da tutte le altre parole nella frase.

Ad esempio, nelle frasi:

  • “La pesca sull’albero è matura.”
  • “La pesca sportiva richiede pazienza.”

La parola “pesca” ha significati diversi a seconda del contesto. La self-attention consente al modello di guardare “albero” nella prima frase e “sportiva” nella seconda per regolare correttamente la rappresentazione di “pesca”.

L’analogia con il database: Query, Key e Value

La formulazione matematica della self-attention è modellata sulle ricerche di recupero delle informazioni (database). Per ogni token di input, proiettiamo tre rappresentazioni vettoriali:

  1. Query ($Q$): Ciò che il token corrente sta cercando.
  2. Key ($K$): L’etichetta o il profilo dei token nella sequenza.
  3. Value ($V$): Il contenuto reale o le informazioni dei token.

Il meccanismo di attenzione calcola un punteggio di somiglianza tra una Query e tutte le Key, normalizza questi punteggi in pesi e restituisce una somma ponderata dei Value.


3. Procedura matematica della Scaled Dot-Product Attention

La formula standard per la self-attention si chiama Scaled Dot-Product Attention:

$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$

Ecco la scomposizione matematica passo dopo passo di come viene eseguita questa formula:

Passaggio 1: Calcolare le matrici di proiezione

Per una matrice di sequenza di input $X \in \mathbb{R}^{T \times d_{\text{model}}}$, moltiplichiamo per matrici di peso apprendibili $W_Q, W_K, W_V$ per ottenere Query ($Q$), Key ($K$) e Value ($V$): $$Q = X W_Q, \quad K = X W_K, \quad V = X W_V$$

Passaggio 2: Calcolare i punteggi di somiglianza (Prodotto scalare)

Calcoliamo il prodotto scalare della matrice Query $Q$ con la trasposta della matrice Key $K^T$ per misurare l’allineamento/rilevanza tra tutte le coppie di token: $$\text{Scores} = QK^T$$ La matrice risultante ha dimensioni $T \times T$, dove la voce $(i, j)$ rappresenta quanta attenzione il token $i$ dovrebbe prestare al token $j$.

Passaggio 3: Scalare i punteggi

I punteggi vengono divisi per la radice quadrata della dimensione delle chiavi ($d_k$): $$\text{Scaled Scores} = \frac{QK^T}{\sqrt{d_k}}$$ Perché scalare? Se $d_k$ è grande, i prodotti scalari crescono molto in termini di grandezza, spingendo la funzione softmax in regioni con gradienti estremamente piccoli (problema del gradiente svanente). La scalabilità per $\sqrt{d_k}$ stabilizza il processo di addestramento.

Passaggio 4: Applicare Softmax (Pesi di attenzione)

Applichiamo una funzione softmax lungo ciascuna riga per normalizzare i punteggi in una distribuzione di probabilità (valori compresi tra 0 e 1 che sommano a 1): $$\text{Attention Weights} = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)$$

Passaggio 5: Somma ponderata dei Value

Infine, moltiplichiamo i pesi di attenzione per la matrice Value $V$: $$\text{Output} = \text{Attention Weights} \times V$$ Questo passaggio aggrega le informazioni, consentendo alla rappresentazione di output di ciascun token di essere fortemente influenzata dai token a cui ha “prestato attenzione”.


4. Multi-Head Attention

Invece di eseguire la self-attention una sola volta, il Transformer utilizza la Multi-Head Attention (attenzione multi-testa). Suddivide i vettori Query, Key e Value in $h$ dimensioni più piccole (teste), esegue l’attenzione su ciascun sottospazio in modo indipendente e in parallelo, quindi concatena i risultati.

Ciò è fondamentale perché consente al modello di prestare attenzione a diversi tipi di relazioni contemporaneamente. Ad esempio, una testa potrebbe concentrarsi sull’accordo soggetto-verbo, mentre un’altra si concentra sulla risoluzione dei pronomi o sui riferimenti temporali.


5. Implementazione Python/NumPy dell’Attenzione

Per comprendere l’implementazione, scriviamo una simulazione Python semplice e autosufficiente della Scaled Dot-Product Attention e della Multi-Head Attention utilizzando NumPy:

import numpy as np

def softmax(x):
    # Softmax stabilizzato per evitare l'overflow
    exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
    return exp_x / np.sum(exp_x, axis=-1, keepdims=True)

def scaled_dot_product_attention(Q, K, V, mask=None):
    """
    Calcola la Scaled Dot-Product Attention.
    Q: [batch_size, seq_len, d_k]
    K: [batch_size, seq_len, d_k]
    V: [batch_size, seq_len, d_v]
    mask: Maschera binaria opzionale [batch_size, seq_len, seq_len]
    """
    d_k = Q.shape[-1]
    
    # Passaggio 2 & 3: Calcolo del prodotto scalare e scala
    scores = np.matmul(Q, K.swapaxes(-2, -1)) / np.sqrt(d_k)
    
    # Mascheramento opzionale (es. Mascheramento causale nei decoder)
    if mask is not None:
        scores = np.where(mask == 0, -1e9, scores)
        
    # Passaggio 4: Applicazione del softmax per ottenere i pesi di attenzione
    attention_weights = softmax(scores)
    
    # Passaggio 5: Somma ponderata dei value
    output = np.matmul(attention_weights, V)
    
    return output, attention_weights

# --- Esempio di esecuzione ---
if __name__ == "__main__":
    np.random.seed(42)
    batch_size = 1
    seq_len = 4  
    d_k = 8
    d_v = 8
    
    # Generazione di vettori Query, Key e Value casuali
    Q = np.random.randn(batch_size, seq_len, d_k)
    K = np.random.randn(batch_size, seq_len, d_k)
    V = np.random.randn(batch_size, seq_len, d_v)
    
    output, weights = scaled_dot_product_attention(Q, K, V)
    
    print("Matrice dei pesi di attenzione (Lunghezza sequenza x Lunghezza sequenza):")
    print(np.round(weights[0], 4))
    print("\nShape dell'output dell'attenzione:", output.shape)

6. Confronto Architetturale

Caratteristica RNN / LSTM Transformer
Elaborazione sequenziale Sì (token per token) No (sequenza parallelizzata)
Complessità computazionale $O(T)$ sequenziale $O(1)$ sequenziale, $O(T^2)$ operazioni totali
Dipendenze a lungo raggio Scarsa (la memoria svanisce nei passaggi) Eccellente (collegamento diretto indipendentemente dalla distanza)
Parallelizzazione Impossibile lungo l’asse temporale Parallelizzazione nativa
Consapevolezza posizionale Implicita (insita nel passaggio sequenziale) Esplicita (richiede la codifica posizionale)

7. Componenti aggiuntivi del blocco Transformer

Per far funzionare la self-attention in uno stack completo, l’architettura Transformer include diversi livelli cruciali in ogni blocco:

  1. Positional Encoding: Poiché i Transformer elaborano tutti i token contemporaneamente, non hanno un senso intrinseco dell’ordine. Iniettiamo vettori di codifica posicionali direttamente negli embedding di input per rappresentare l’ordine dei token.
  2. Residual Connections: Le connessioni skip attorno a ciascun sotto-livello (Attenzione e Feed-Forward) aiutano i gradienti a propagarsi attraverso reti molto profonde senza svanire.
  3. Layer Normalization: Normalizza le attivazioni di ciascun livello, stabilizzando e velocizzando l’addestramento.
  4. Feed-Forward Networks (FFN): Una MLP applicata a ciascun token in modo indipendente, aggiungendo capacità di rappresentazione non lineare.

Conclusione

Il passaggio del Transformer dalla ricorrenza alla self-attention parallelizzata ha sbloccato le leggi di scala dell’IA moderna. Comprendendo query, key e value, vediamo come i modelli possono collegare dinamicamente i concetti e costruire il significato in tempo reale, gettando le basi per le capacità cognitive dei moderni LLM.


Esplora altre prospettive tecnologiche sul Blog di Ghaznix →