La guida completa dei Transformers Tutto ciò che devi sapere

La guida completa dei Transformers Tutto quello che devi sapere

Tutto quello che devi sapere sui Trasformatori e come implementarli

Immagine dell'autore

Perché un altro tutorial sui Trasformatori?

Probabilmente hai già sentito parlare dei Trasformatori e tutti ne parlano, quindi perché fare un nuovo articolo a riguardo?

Bene, io sono un ricercatore e questo richiede di avere una conoscenza approfondita degli strumenti che utilizzo (perché se non li capisci, come puoi identificare dove sono sbagliati e come migliorarli, giusto?).

Man mano che mi addentravo nel mondo dei Trasformatori, mi sono ritrovato sommerso da una montagna di risorse. Eppure, nonostante tutta quella lettura, mi sono ritrovato con una comprensione generale dell’architettura e una serie di domande che mi rimanevano.

In questa guida, ho l’intenzione di colmare questa lacuna di conoscenza. Una guida che ti darà una forte intuizione sui Trasformatori, un’immersione approfondita nell’architettura e l’implementazione da zero.

Ti consiglio vivamente di seguire il codice su Github:

awesome-ai-tutorials/NLP/007 – Transformers From Scratch at main ·…

La migliore raccolta di tutorial sull’Intelligenza Artificiale per farti diventare un esperto di Data Science! – awesome-ai-tutorials/NLP/007 – Transformers…

github.com

Goditi! 🤗

Un po’ di storia prima:

Molti attribuiscono il concetto di meccanismo di attenzione al rinomato articolo “Attention is All You Need” del team Google Brain. Tuttavia, questo è solo parte della storia.

Le radici del meccanismo di attenzione risalgono a un articolo precedente intitolato “Neural Machine Translation by Jointly Learning to Align and Translate” scritto da Dzmitry Bahdanau, KyungHyun Cho e Yoshua Bengio.

La sfida principale di Bahdanau era affrontare le limitazioni delle Reti Neurali Ricorrenti (RNN). In particolare, quando si codificavano frasi lunghe in vettori utilizzando le RNN, spesso si perdevano informazioni cruciali.

Tracciando un parallelo dagli esercizi di traduzione, dove spesso si rilegge la frase di origine durante la traduzione, Bahdanau ha cercato di assegnare pesi agli stati nascosti all’interno delle RNN. Questo approccio ha prodotto risultati impressionanti, come mostrato nel diagramma seguente.

Immagine da Neural machine translation by jointly learning to align and translate

Tuttavia, Bahdanau non fu l’unico ad affrontare questo problema. Prendendo spunto dal suo lavoro rivoluzionario, il team Google Brain avanzò un’idea audace:

“Perché non semplificare tutto e concentrarsi esclusivamente sul meccanismo di attenzione?”

Credettero che non fosse la RNN ma il meccanismo di attenzione che fosse il principale responsabile del successo.

Questa convinzione culminò nel loro articolo, intitolato saggiamente “Attention is All You Need”.

Affascinante, vero?

L’architettura del Trasformatore

1. Le primizie, gli addestramenti

Questo diagramma rappresenta l’architettura del Transformer. Non preoccuparti se non capisci nulla all’inizio, copriremo assolutamente tutto.

Addestramenti, Immagine dell'articolo modificata dall'autore

Dal testo ai vettori – Il processo di addestramento: Immagina che il nostro input sia una sequenza di parole, diciamo “Il gatto beve latte”. Questa sequenza ha una lunghezza definita come seq_len. Il nostro compito immediato è convertire queste parole in una forma che il modello possa capire, specificamente vettori. Ecco dove entra in gioco l’addestratore.

Ogni parola subisce una trasformazione per diventare un vettore. Questo processo di trasformazione è chiamato ‘addestramento’. Ciascuno di questi vettori o ‘addestramenti’ ha una dimensione di d_model = 512.

Ora, cos’è esattamente questo addestratore? Nel suo nucleo, l’addestratore è una mappatura lineare (matrice), indicata da E. Puoi visualizzarlo come una matrice di dimensioni (d_model, vocab_size), dove vocab_size è la dimensione del nostro vocabolario.

Dopo il processo di addestramento, otteniamo una collezione di vettori di dimensione d_model ciascuno. È fondamentale capire questo formato, poiché è un tema ricorrente, lo vedrai in varie fasi come l’input dell’encoder, l’output dell’encoder, e così via.

Scriviamo il codice di questa parte:

class Addestramenti(nn.Module):    def __init__(self, d_model, vocab):        super(Addestramenti, self).__init__()        self.lut = nn.Embedding(vocab, d_model)        self.d_model = d_model    def forward(self, x):        return self.lut(x) * math.sqrt(self.d_model)

Nota: moltiplichiamo per d_model per scopi di normalizzazione (spiegato in seguito)

Nota 2: Personalmente mi sono chiesto se avremmo usato un addestratore pre-addestrato, o almeno iniziato da uno pre-addestrato e raffinato. Ma no, l’addestramento è completamente appreso da zero e inizializzato casualmente.

Codifica Posizionale

Perché abbiamo bisogno della Codifica Posizionale?

Dato il nostro attuale setup, possediamo una lista di vettori che rappresentano le parole. Se alimentati così come sono a un modello di trasformazione, manca un elemento chiave: l’ordine sequenziale delle parole. Le parole nelle lingue naturali spesso traggono significato dalla loro posizione. “John ama Mary” porta un sentimento diverso da “Mary ama John”. Per assicurare che il nostro modello catturi questo ordine, introduciamo la Codifica Posizionale.

Ora, potresti chiederti: “Perché non semplicemente aggiungere un semplice incremento come +1 per la prima parola, +2 per la seconda, e così via?” Ci sono diverse sfide con questo approccio:

  1. Multidimensionalità: Ogni token è rappresentato in 512 dimensioni. Un semplice incremento non sarebbe sufficiente per catturare questo spazio complesso.
  2. Preoccupazioni di normalizzazione: Idealmente, vogliamo che i nostri valori siano compresi tra -1 e 1. Quindi, aggiungere direttamente numeri grandi (come +2000 per un testo lungo) sarebbe problematico.
  3. Dipendenza dalla lunghezza della sequenza: L’uso di incrementi diretti non è agnostico alla scala. Per un testo lungo, dove la posizione potrebbe essere +5000, questo numero non riflette veramente la posizione relativa del token nella sua frase associata. E il significato di una parola dipende più dalla sua posizione relativa in una frase che dalla sua posizione assoluta in un testo.

Se hai studiato matematica, l’idea delle coordinate circolari – specificamente, le funzioni seno e coseno – dovrebbe risuonare con la tua intuizione. Queste funzioni forniscono un modo unico per codificare la posizione che soddisfa le nostre esigenze.

Dato la nostra matrice di dimensioni (seq_len, d_model), il nostro obiettivo è aggiungere un’altra matrice, la Codifica Posizionale, della stessa dimensione.

Ecco il concetto principale:

  1. Per ogni token, gli autori suggeriscono di fornire una coordinata seno delle dimensioni a coppie (2k) e una coordinata coseno a (2k+1).
  2. Se fissiamo la posizione del token e spostiamo la dimensione, possiamo vedere che il seno/coseno diminuisce in frequenza.
  3. Se guardiamo a un token che è più lontano nel testo, questo fenomeno avviene più rapidamente (la frequenza aumenta)
Immagine dall'articolo

Tutto questo è riassunto nel seguente grafico (ma non preoccupatevi troppo di questo). L’aspetto chiave è che la Codifica Posizionale è una funzione matematica che consente al Transformer di mantenere un’idea dell’ordine dei token nella frase. Si tratta di un’area di ricerca molto attiva.

Embedding Posizionale, Immagine dell'autore
class PositionalEncoding(nn.Module):    "Implementa la funzione PE."    def __init__(self, d_model, dropout, max_len=5000):        super(PositionalEncoding, self).__init__()        self.dropout = nn.Dropout(p=dropout)        # Calcola le codifiche posizionali una volta nello spazio logaritmico.        pe = torch.zeros(max_len, d_model)        position = torch.arange(0, max_len).unsqueeze(1)        div_term = torch.exp(            torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)        )        pe[:, 0::2] = torch.sin(position * div_term)        pe[:, 1::2] = torch.cos(position * div_term)        pe = pe.unsqueeze(0)        self.register_buffer("pe", pe)    def forward(self, x):        x = x + self.pe[:, : x.size(1)].requires_grad_(False)        return self.dropout(x)

Il Meccanismo di Attenzione (Singola Testa)

Approfondiamo il concetto chiave del paper di Google: il Meccanismo di Attenzione

Intuizione di Alto Livello:

Alla base, il meccanismo di attenzione è un meccanismo di comunicazione tra vettori/token. Permette a un modello di focalizzarsi su parti specifiche dell’input durante la produzione di un output. Pensatelo come una luce puntata su determinate parti dei vostri dati di input. Questa “luce” può essere più intensa su parti più rilevanti (dando loro più attenzione) e meno intensa su parti meno rilevanti.

Per una frase, l’attenzione aiuta a determinare la relazione tra le parole. Alcune parole sono strettamente correlate tra loro in termini di significato o funzione all’interno di una frase, mentre altre no. Il meccanismo di attenzione quantifica queste relazioni.

Esempio:

Consideriamo la frase: “Lei gli ha dato il suo libro”.

Se ci concentriamo sulla parola “suo”, il meccanismo di attenzione potrebbe determinare che:

  • Ha una forte connessione con “libro” perché “suo” indica il possesso del “libro”.
  • Ha una connessione VoAGI con “Lei” perché “Lei” e “suo” si riferiscono probabilmente alla stessa entità.
  • Ha una connessione più debole con altre parole come “dato” o “gli”.

Approfondimento Tecnico sul meccanismo di attenzione

Attenzione dot-product scalata, immagine dall'articolo

Per ogni token, generiamo tre vettori:

  1. Query (Q):

Intuizione: Pensate alla query come a una “domanda” che un token pone. Rappresenta la parola corrente e cerca di capire quali parti della sequenza sono rilevanti per essa.

2. Key (K):

Intuizione: La chiave può essere considerata come un “identificatore” per ogni parola nella sequenza. Quando la query “fa” la sua domanda, la chiave aiuta a “rispondere” determinando quanto ogni parola nella sequenza sia rilevante per la query.

3. Valore (V):

Intuizione: Una volta determinata la rilevanza di ogni parola (tramite la sua chiave) rispetto alla query, abbiamo bisogno di informazioni reali o contenuti di quelle parole per assistere il token corrente. Qui entra in gioco il valore. Rappresenta il contenuto di ogni parola.

Come vengono generati Q, K, V?

Generazione Q, K, V, immagine dell'autore

La similarità tra una query e una chiave è un prodotto scalare (misura la similarità tra 2 vettori), diviso per la deviazione standard di questa variabile casuale, per normalizzare tutto.

Formula di attenzione, immagine dell'articolo

Illustreremo questo con un esempio:

Immaginiamo di avere una query e vogliamo trovare il risultato dell’attenzione con K e V:

Q, K, V, Immagine dell'autore

Ora calcoliamo le similitudini tra q1 e le chiavi:

Prodotto scalare, Immagine dell'autore

Anche se i numeri 3/2 e 1/8 potrebbero sembrare relativamente vicini, la natura esponenziale della funzione softmax amplificherà la loro differenza.

Pesi di attenzione, Immagine dell'autore

Questa differenza suggerisce che q1 ha una connessione più marcata con k1 rispetto a k2.

Ora guardiamo il risultato dell’attenzione, che è una combinazione pesata (pesi di attenzione) dei valori

Attenzione, Immagine dell'autore

Ottimo! Ripetendo questa operazione per ogni token (da q1 a qn) otteniamo una collezione di n vettori.

Nella pratica questa operazione viene vettorizzata in una moltiplicazione matriciale per una maggiore efficienza.

Codifichiamolo:

def attenzione(query, chiave, valore, maschera=None, dropout=None):    "Calcola l'attenzione con prodotto scalato dei punti"    d_k = query.size(-1)    punteggi = torch.matmul(query, chiave.transpose(-2, -1)) / math.sqrt(d_k)    if maschera is not None:        punteggi = punteggi.masked_fill(maschera == 0, -1e9)    p_attn = punteggi.softmax(dim=-1)    if dropout is not None:        p_attn = dropout(p_attn)    return torch.matmul(p_attn, valore), p_attn

Attenzione multipla

Cosa c’è di sbagliato nell’attenzione a singola testa?

Con l’approccio dell’attenzione a singola testa, ogni token ha la possibilità di porre solo una query. Questo si traduce generalmente nel creare una forte relazione con un solo altro token, dato che la funzione softmax tende a potenziare molto un valore e diminuire altri vicini allo zero. Tuttavia, quando si pensa al linguaggio e alle strutture delle frasi, una singola parola ha spesso connessioni con più altre parole, non solo una.

Per affrontare questa limitazione, introduciamo attività a più teste. L’idea principale? Permettiamo a ciascun token di porre contemporaneamente più domande (query) eseguendo il processo di attenzione in parallelo per ‘h’ volte. Il Transformer originale usa 8 teste.

Attività a più teste, immagine tratta dall'articolo

Una volta ottenuti i risultati delle 8 teste, li concateniamo in una matrice.

Attività a più teste, immagine tratta dall'articolo

Anche questo è semplice da codificare, basta fare attenzione alle dimensioni:

class MultiHeadedAttention(nn.Module):    def __init__(self, h, d_model, dropout=0.1):        "Prende in ingresso la dimensione del modello e il numero di teste."        super(MultiHeadedAttention, self).__init__()        assert d_model % h == 0        # Supponiamo che d_v sia sempre uguale a d_k        self.d_k = d_model // h        self.h = h        self.linears = clones(nn.Linear(d_model, d_model), 4)        self.attn = None        self.dropout = nn.Dropout(p=dropout)    def forward(self, query, key, value, mask=None):        "Implementa la Figura 2"        if mask is not None:            # La stessa maschera applicata a tutte le teste h.            mask = mask.unsqueeze(1)        nbatches = query.size(0)        # 1) Effettua tutte le proiezioni lineari in batch da d_model => h x d_k        query, key, value = [            lin(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2)            for lin, x in zip(self.linears, (query, key, value))        ]        # 2) Applica l'attenzione a tutti i vettori proiettati in batch.        x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout)        # 3) "Concatena" utilizzando una vista e applica una proiezione finale.        x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k)        del query        del key        del value        return self.linears[-1](x)

Dovresti iniziare a capire perché i Transformer sono così potenti, sfruttano appieno il parallelismo.

Assemblea le parti del Transformer

A livello elevato, un Transformer è la combinazione di 3 elementi: un Codificatore, un Decodificatore e un Generatore

Codificatore, Decodificatore, Generatore, Immagine tratta dall'articolo modificata dall'autore

1. Il Codificatore

  • Scopo: Convertire una sequenza di input in una nuova sequenza (di solito di dimensioni più ridotte) che cattura l’essenza dei dati originali.
  • Nota: Se hai sentito parlare del modello BERT, utilizza solo questa parte di codifica del Transformer.

2. Il Decodificatore

  • Scopo: Generare una sequenza di output utilizzando la sequenza codificata dal Codificatore.
  • Nota: Il decodificatore nel Transformer è diverso dal decodificatore tipico di un autoencoder. Nel Transformer, il decodificatore non solo guarda l’output codificato, ma considera anche i token generati finora.

3. Il Generatore

  • Scopo: Convertire un vettore in un token. Lo fa proiettando il vettore delle dimensioni del vocabolario e poi selezionando il token più probabile con la funzione softmax.

Sentiamo codice:

class EncoderDecoder(nn.Module):    """    Un'architettura standard Encoder-Decoder. Base per questo e molti    altri modelli.    """    def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):        super(EncoderDecoder, self).__init__()        self.encoder = encoder        self.decoder = decoder        self.src_embed = src_embed        self.tgt_embed = tgt_embed        self.generator = generator    def forward(self, src, tgt, src_mask, tgt_mask):        "Prende e elabora le sequenze sorgente e target mascherate."        return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)    def encode(self, src, src_mask):        return self.encoder(self.src_embed(src), src_mask)    def decode(self, memory, src_mask, tgt, tgt_mask):        return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)class Generator(nn.Module):    "Definisce il passaggio di generazione standard lineare + softmax."    def __init__(self, d_model, vocab):        super(Generator, self).__init__()        self.proj = nn.Linear(d_model, vocab)    def forward(self, x):        return log_softmax(self.proj(x), dim=-1)

Un commento qui: “src” si riferisce alla sequenza di input, e “target” si riferisce alla sequenza che viene generata. Ricordiamo che generiamo l’output in modo autoregressivo, token per token, quindi dobbiamo tenere traccia anche della sequenza target.

Stacking degli Encoder

L’Encoder del Transformer non è costituito da un singolo livello. In realtà è un insieme di N livelli. In particolare:

  • L’Encoder nel modello Transformer originale è composto da un insieme di N=6 livelli identici.

All’interno del livello Encoder, possiamo vedere che ci sono due blocchi Sublayer molto simili ((1) e (2)): una connessione residua seguita da una normalizzazione dei livelli.

  • Blocco (1) Meccanismo di Self-Attention: Aiuta l’encoder a concentrarsi su diverse parole nell’input durante la generazione della rappresentazione codificata.
  • Blocco (2) Rete neurale feed-forward: Una piccola rete neurale applicata indipendentemente ad ogni posizione.
Livello Encoder, connessioni residue e normalizzazione dei livelli, immagine dell'articolo modificata dall'autore

Adesso codifichiamo tutto ciò:

Prima SublayerConnection:

Seguiamo l’architettura generale, e possiamo cambiare “sublayer” con “self-attention” o “FFN”

class SublayerConnection(nn.Module):    """    Una connessione residua seguita da una normalizzazione dei livelli.    Nota per semplicità del codice, la normalizzazione viene effettuata prima anziché dopo.    """    def __init__(self, size, dropout):        super(SublayerConnection, self).__init__()        self.norm = nn.LayerNorm(size)  # Utilizza la LayerNorm di PyTorch        self.dropout = nn.Dropout(dropout)    def forward(self, x, sublayer):        "Applica una connessione residua a qualsiasi sublayer con la stessa dimensione."        return x + self.dropout(sublayer(self.norm(x)))

Ora possiamo definire il livello completo di Encoder:

class EncoderLayer(nn.Module):    "L'Encoder è composto da self-attn e feed forward (definito di seguito)"    def __init__(self, size, self_attn, feed_forward, dropout):        super(EncoderLayer, self).__init__()        self.self_attn = self_attn        self.feed_forward = feed_forward        self.sublayer = clones(SublayerConnection(size, dropout), 2)        self.size = size    def forward(self, x, mask):        # self attention, blocco 1        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask))        # feed forward, blocco 2        x = self.sublayer[1](x, self.feed_forward)        return x

Il livello Encoder è pronto, adesso creiamone una concatenazione per formare l’intero Encoder:

def clones(module, N):    "Produce N livelli identici."    return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])class Encoder(nn.Module):    "L'encoder principale è una pila di N livelli"    def __init__(self, layer, N):        super(Encoder, self).__init__()        self.layers = clones(layer, N)        self.norm = nn.LayerNorm(layer.size)    def forward(self, x, mask):        "Passa l'input (e la maschera) attraverso ogni livello a turno."        for layer in self.layers:            x = layer(x, mask)        return self.norm(x)

Decoder

Il Decoder, proprio come l’Encoder, è strutturato con più livelli identici sovrapposti l’uno sopra l’altro. Il numero di questi livelli è di solito 6 nel modello originale di Transformer.

In che modo il Decoder è diverso dall’Encoder?

Viene aggiunto un terzo sottolivello per interagire con l’encoder: si tratta di Cross-Attention

  • Il sottolivello (1) è lo stesso dell’Encoder. È il meccanismo di Self-Attention, il che significa che generiamo tutto (Q, K, V) dai token inseriti nel Decoder
  • Il sottolivello (2) è il nuovo meccanismo di comunicazione: Cross-Attention. Viene chiamato in questo modo perché utilizziamo l’output del (1) per generare le Query, e utilizziamo l’output dell’Encoder per generare le Chiavi e i Valori (K, V). In altre parole, per generare una frase dobbiamo guardare sia a ciò che abbiamo generato finora nel Decoder (self-attention), sia a ciò che abbiamo chiesto inizialmente nell’Encoder (cross-attention)
  • Il sottolivello (3) è identico a quello dell’Encoder.
Livello del Decoder, self attention, cross attention, Immagine dall'articolo modificata dall'autore

Ora codifichiamo il DecoderLayer. Se hai compreso il meccanismo nel DecoderLayer, questo dovrebbe essere piuttosto semplice.

class DecoderLayer(nn.Module):    "Il Decoder è composto da self-attn, src-attn, e feed forward (definiti di seguito)"    def __init__(self, size, self_attn, src_attn, feed_forward, dropout):        super(DecoderLayer, self).__init__()        self.size = size        self.self_attn = self_attn        self.src_attn = src_attn        self.feed_forward = feed_forward        self.sublayer = clones(SublayerConnection(size, dropout), 3)    def forward(self, x, memory, src_mask, tgt_mask):        "Segui la Figura 1 (a destra) per le connessioni."        m = memory        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask))        # Nuovo sottolivello (cross attention)        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask))        return self.sublayer[2](x, self.feed_forward)

E ora possiamo collegare i N=6 DecoderLayers per formare il Decoder:

class Decoder(nn.Module):    "Decoder a N strati generico con masking."    def __init__(self, layer, N):        super(Decoder, self).__init__()        self.layers = clones(layer, N)        self.norm = nn.LayerNorm(layer.size)    def forward(self, x, memory, src_mask, tgt_mask):        for layer in self.layers:            x = layer(x, memory, src_mask, tgt_mask)        return self.norm(x)

A questo punto hai compreso circa il 90% di ciò che è un Transformer. Ci sono ancora alcuni dettagli:

Dettagli del Modello Transformer

Padding:

  • In un tipico transformer, c’è una lunghezza massima per le sequenze (ad esempio, “max_len=5000”). Questo definisce la sequenza più lunga che il modello può gestire.
  • Tuttavia, le frasi del mondo reale possono variare in lunghezza. Per gestire frasi più corte, utilizziamo il padding.
  • Il padding consiste nell’aggiunta di speciali “token di padding” per rendere tutte le sequenze in un batch della stessa lunghezza.
Padding, immagine dell'autore

Masking

Il Masking assicura che durante il calcolo dell’attenzione, certi token vengano ignorati.

Due scenari per il Masking:

  • src_masking: Poiché abbiamo aggiunto token di padding alle sequenze, non vogliamo che il modello presti attenzione a questi token senza significato. Pertanto, li mascheriamo.
  • tgt_masking o Look-Ahead/Causal Masking: Nel Decoder, durante la generazione dei token in sequenza, ogni token dovrebbe essere influenzato solo dai token precedenti e non da quelli futuri. Ad esempio, durante la generazione della quinta parola in una frase, non dovrebbe conoscere la sesta parola. Ciò assicura una generazione sequenziale dei token.
Causal Masking/Look-Ahead masking, image by author

Usiamo poi questa maschera per aggiungere meno infinito in modo che il token corrispondente venga ignorato. Questo esempio dovrebbe chiarire le cose:

Mascheramento, un trucco nel softmax, immagine dell'autore

FFN: Rete forward feed

  • Il livello “Forward Feed” nel diagramma del Transformer è un po’ fuorviante. Non è solo un’operazione, ma una sequenza di esse.
  • La FFN è composta da due livelli lineari. Interessante notare che i dati di input, che potrebbero essere di dimensione d_model=512, vengono prima trasformati in una dimensione superiore d_ff=2048 e successivamente mappati di nuovo alla loro dimensione originale (d_model=512).
  • Questo può essere visualizzato come i dati che vengono “espansi” nel mezzo dell’operazione prima di essere “compressi” di nuovo alla loro dimensione originale.
Immagine modificata dall'articolo dell'autore

Questo è facile da codificare:

class PositionwiseFeedForward(nn.Module):    "Implementa l'equazione FFN."    def __init__(self, d_model, d_ff, dropout=0.1):        super(PositionwiseFeedForward, self).__init__()        self.w_1 = nn.Linear(d_model, d_ff)        self.w_2 = nn.Linear(d_ff, d_model)        self.dropout = nn.Dropout(dropout)    def forward(self, x):        return self.w_2(self.dropout(self.w_1(x).relu()))

Conclusione

Il successo senza eguali e la popolarità del modello Transformer possono essere attribuiti a diversi fattori chiave:

  1. Flessibilità. I Transformer possono lavorare con qualsiasi sequenza di vettori. Questi vettori possono essere embeddings per parole. È facile trasporre ciò alla Computer Vision convertendo un’immagine in diverse patch e svolgendo una patch in un vettore. Oppure anche nell’Audio, possiamo suddividere un audio in diverse parti e vettorizzarle.
  2. Generalità: Con un minimo bias induttivo, il Transformer è libero di catturare modelli intricati e sfumati nei dati, permettendogli di imparare e generalizzare meglio.
  3. Velocità ed efficienza: Sfruttando l’immensa potenza di calcolo delle GPU, i Transformer sono progettati per l’elaborazione parallela.

Grazie per la lettura! Prima di andare:

Puoi eseguire gli esperimenti con il mio Repository Github di Transformer.

Per altri tutorial fantastici, controlla la mia compilazione di tutorial sull’IA su Github

GitHub — FrancoisPorcher/awesome-ai-tutorials: La migliore collezione di tutorial sull’IA per renderti un…

La migliore collezione di tutorial sull’IA per renderti un professionista della Data Science! — GitHub …

github.com

Dovresti ricevere i miei articoli nella tua casella di posta. Iscriviti qui.

Se vuoi avere accesso ad articoli premium su VoAGI, ti basta una membership a $5 al mese. Se ti iscrivi con il mio link, mi sostieni con una parte della tua quota senza costi aggiuntivi.

Se hai trovato questo articolo interessante e utile, ti preghiamo di considerare di seguirmi e di lasciare un applauso per altri contenuti approfonditi! Il tuo supporto mi aiuta a continuare a produrre contenuti che contribuiscono alla nostra comprensione collettiva.

Riferimenti

Per approfondire

Anche con una guida completa, ci sono molte altre aree legate ai transformer che potresti voler esplorare:

  • Codifica posizionale: sono stati apportati miglioramenti significativi, potresti voler controllare “Codifica posizionale relativa” e “Embedding posizionale rotativo (RoPE)”
  • Layer Norm e le differenze con la batch norm, la group norm
  • Connessioni residue e il loro effetto nella levigatura del gradiente
  • Miglioramenti apportati a BERT (Roberta, ELECTRA, Camembert)
  • Distillazione di modelli più grandi in modelli più piccoli
  • Applicazioni di Transformer in altri domini (principalmente visione e audio)
  • Il collegamento tra i transformer e le reti neurali grafiche