Se hai giocato con i modelli recenti Abbracciare il visoè probabile che tu abbia incontrato un modello linguistico causale. Quando visualizzi la documentazione per a modello family, otterrai una pagina con “attività” come LlamaForCausalLM o LlamaForSequenceClassification.
Se sei come me, passare dalla documentazione alla messa a punto effettiva di un modello può creare un po' di confusione. Ci concentreremo su CausalLM, iniziando spiegando cos'è CausalLM in questo post seguito da un esempio pratico di come mettere a punto un modello CausalLM in un post successivo.
Background: codificatori e decodificatori
Molti dei migliori modelli oggi come LLAMA-2, GPT-2 o Falcon sono modelli “solo decoder”. Un modello solo decoder:
- richiede una sequenza di precedente token (ovvero un prompt)
- esegue questi token attraverso il modello (spesso creando incorporamenti dai token e facendoli passare attraverso i blocchi del trasformatore)
- restituisce un singolo output (solitamente la probabilità del token successivo).
Ciò è in contrasto con i modelli con architetture “solo codificatore” o ibride “codificatore-decodificatore” che inseriranno il intero sequenza, non solo precedente gettoni. Questa differenza dispone le due architetture verso compiti diversi. I modelli di decodificatore sono progettati per il compito generativo di scrivere nuovo testo. I modelli di codificatori sono progettati per attività che richiedono l'esame di una sequenza completa come la traduzione o la classificazione delle sequenze. Le cose diventano confuse perché puoi riutilizzare un modello solo decodificatore per eseguire la traduzione o utilizzare un modello solo codificatore per generare nuovo testo. Sebastian Raschka ha una bella guida se vuoi approfondire i codificatori e i decodificatori. C'è anche un articolo medio che approfondisce le differenze tra il modello linguistico mascherato e il modello linguistico causale.
Per i nostri scopi, tutto ciò che devi sapere è che:
- I modelli CausalLM sono generalmente modelli solo decodificatori
- I modelli solo decodificatori esaminano i token passati per prevedere il token successivo
Con i modelli linguistici basati solo sul decodificatore, possiamo pensare al successivo processo di previsione dei token come “modellazione linguistica causale” perché i token precedenti “causano” ogni token aggiuntivo.
AbbracciareFace CausaleLM
Nel mondo di HuggingFace, CausalLM (LM sta per Language Modeling) è una classe di modelli che accettano un suggerimento e prevedono nuovi token. In realtà, stiamo prevedendo un token alla volta, ma la classe elimina la noiosità di dover scorrere le sequenze un token alla volta. Durante l'inferenza, i CausalLM prediranno in modo iterativo i singoli token fino a quando non si verifica una condizione di arresto, a quel punto il modello restituisce i token concatenati finali.
Durante l'addestramento, accade qualcosa di simile quando diamo al modello una sequenza di token che vogliamo apprendere. Iniziamo prevedendo il secondo token dato il primo, poi il terzo token dati i primi due token e così via.
Pertanto, se vuoi imparare a prevedere la frase “al cane piace il cibo”, supponendo che ogni parola sia un segno, stai facendo 3 previsioni:
- “il” → cane,
- “il cane” → piace
- “al cane piace” → cibo
Durante l'addestramento, puoi considerare ciascuna delle tre istantanee della frase come tre osservazioni nel set di dati di addestramento. Dividere manualmente lunghe sequenze in singole righe per ciascun token in una sequenza sarebbe noioso, quindi HuggingFace lo gestisce per te.
Finché gli dai una sequenza di token, suddividerà quella sequenza in singole previsioni di token singoli dietro le quinte.
Puoi creare questa “sequenza di token” eseguendo una stringa regolare attraverso il tokenizzatore del modello. Il tokenizzatore genererà un oggetto simile a un dizionario con input_id e un attenzione_maschera come chiavi, come con qualsiasi normale modello HuggingFace.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bigscience/bloom-560m")
tokenizer("the dog likes food")
>>> {'input_ids': (5984, 35433, 114022, 17304), 'attention_mask': (1, 1, 1, 1)}
Con i modelli CausalLM, è presente un passaggio aggiuntivo in cui il modello prevede a etichette chiave. Durante l’allenamento utilizziamo il “precedente” input_id prevedere la “corrente” etichette gettone. Tuttavia, lo fai non voglio pensare alle etichette come a un modello di risposta a domande in cui il primo indice di etichette corrisponde alla risposta a input_id (vale a dire che il etichette dovrebbe essere concatenato alla fine del file input_id). Piuttosto, vuoi etichette E input_id specchiarsi a vicenda con forme identiche. In notazione algebrica, prevedere etichette token all'indice k, usiamo tutto il input_id attraverso l'indice k-1.
Se questo crea confusione, in pratica di solito puoi semplicemente farlo etichette una copia identica di input_id e chiudiamola qui. Se vuoi capire cosa sta succedendo, faremo un esempio.
Un esempio veloce e pratico
Torniamo a “al cane piace il cibo”. Per semplicità, lasciamo le parole come parole invece di assegnarle a numeri token, ma in pratica questi sarebbero numeri che puoi ricondurre alla loro vera rappresentazione di stringa utilizzando il tokenizzatore.
Il nostro input per un batch a singolo elemento sarebbe simile al seguente:
{
"input_ids": (("the", "dog", "likes", "food")),
"attention_mask": ((1, 1, 1, 1)),
"labels": (("the", "dog", "likes", "food")),
}
Le doppie parentesi indicano che tecnicamente la forma degli array per ciascuna chiave è batch_size x sequence_size. Per semplificare le cose, possiamo ignorare il raggruppamento e trattarli semplicemente come vettori unidimensionali.
Dietro le quinte, se il modello prevede il token k-esimo in una sequenza, lo farà in questo modo:
pred_token_k = model(input_ids(:k)*attention_mask(:k)^T)
Nota che questo è uno pseudocodice.
Possiamo ignorare la maschera di attenzione per i nostri scopi. Per i modelli CausalLM, di solito vogliamo che la maschera di attenzione sia tutta 1 perché vogliamo occuparci di tutti i token precedenti. Si noti inoltre che (:k) significa in realtà che utilizziamo l'indice 0 attraverso l'indice k-1 perché l'indice finale nell'affettamento è esclusivo.
Tenendo questo in mente, abbiamo:
pred_token_k = model(input_ids(:k))
La perdita verrebbe presa confrontando il valore reale di etichette(k) con pred_token_k.
In realtà, entrambi vengono rappresentati come vettori 1xv dove v è la dimensione del vocabolario. Ogni elemento rappresenta la probabilità di quel token. Per le previsioni (pred_token_k), queste sono le probabilità reali previste dal modello. Per la vera etichetta (etichette(k)), possiamo dargli artificialmente la forma corretta creando un vettore con 1 per il vero token effettivo e 0 per tutti gli altri token nel vocabolario.
Diciamo che stiamo prevedendo la seconda parola della nostra frase di esempio, che significa k=1 (stiamo indicizzando zero k). Il primo elemento del punto elenco è il contesto che utilizziamo per generare una previsione e il secondo elemento del punto elenco è il vero token etichetta che miriamo a prevedere.
k=1:
- Input_ids(:1) == (il)
- Etichette(1) == cane
k=2:
- Input_ids(:2) == (il, cane)
- Etichette(2) == Mi piace
k =3:
- Input_ids(:3) == (il, cane, mi piace)
- Etichette(3) == cibo
Diciamo k=3 e diamo in pasto al modello “(al, cane, piace)”. Il modello produce:
(P(dog)=10%, P(food)=60%,P(likes)=0%, P(the)=30%)
In altre parole, il modello pensa che ci sia il 10% di probabilità che il token successivo sia “cane”, il 60% di probabilità che il token successivo sia “cibo” e il 30% di probabilità che il token successivo sia “il”.
La vera etichetta potrebbe essere rappresentata come:
(P(dog)=0%, P(food)=100%, P(likes)=0%, P(the)=0%)
Nell'addestramento reale, utilizzeremmo una funzione di perdita come l'entropia incrociata. Per mantenerlo il più intuitivo possibile, utilizziamo semplicemente la differenza assoluta per avere una sensazione approssimativa della perdita. Per differenza assoluta intendo il valore assoluto della differenza tra la probabilità prevista e la nostra probabilità “vera”: es cane_diff_assoluto = |0,10–0,00| = 0,10.
Anche con questa semplice funzione di perdita, puoi vedere che per minimizzare la perdita vogliamo prevedere un'alta probabilità per l'etichetta effettiva (ad esempio il cibo) e una bassa probabilità per tutti gli altri token nel vocabolario.
Ad esempio, diciamo dopo l'addestramento, quando chiediamo al nostro modello di prevedere il prossimo token fornito (il cane, i Mi piace), i nostri risultati saranno simili ai seguenti:
Ora la nostra perdita è minore ora che abbiamo imparato a prevedere il “cibo” con un'elevata probabilità dati questi input.
L'addestramento consisterebbe semplicemente nel ripetere questo processo cercando di allineare le probabilità previste con il vero token successivo per tutti i token nelle sequenze di allenamento.
Conclusione
Si spera che tu abbia un'intuizione su cosa sta succedendo dietro le quinte per addestrare un modello CausalLM utilizzando HuggingFace. Potresti avere alcune domande come “perché abbiamo bisogno etichette come array separato quando potremmo semplicemente usare l'indice k-esimo di input_id direttamente ad ogni passo? C'è qualche caso in cui etichette sarebbe diverso da input_id?”
Ti lascerò pensare a queste domande e per ora mi fermerò qui. Riprenderemo con le risposte e il codice reale nel prossimo post!
Fonte: towardsdatascience.com