Modelli linguistici per il completamento delle frasi |  di Dhruv Matani |  Settembre 2023

 | Intelligenza-Artificiale

Costruiamo un semplice modello LSTM e addestriamolo per prevedere il token successivo dato un prefisso di token. Ora potresti chiederti cos’è un token.

Tokenizzazione

Tipicamente per i modelli linguistici, un token può significare

  1. Un singolo carattere (o un singolo byte)
  2. Una parola intera nella lingua di destinazione
  3. Qualcosa tra 1 e 2. Di solito è chiamata sottoparola

Mappare un singolo carattere (o byte) su un token è molto restrittivo poiché stiamo sovraccaricando quel token per contenere molto contesto su dove si trova. Questo perché il carattere “c”, ad esempio, ricorre in molte parole diverse e per prevedere il carattere successivo dopo aver visto il carattere “c” dobbiamo guardare attentamente il contesto principale.

Anche mappare una singola parola su un token è problematico poiché l’inglese stesso ha un numero compreso tra 250.000 e 1 milione di parole. Inoltre, cosa succede quando una nuova parola viene aggiunta alla lingua? Dobbiamo tornare indietro e riqualificare l’intero modello per tenere conto di questa nuova parola?

La tokenizzazione delle sottoparole è considerata lo standard del settore nel 2023. Assegna sottostringhe di byte che si verificano frequentemente insieme a token univoci. In genere, i modelli linguistici contengono da poche migliaia (diciamo 4.000) a decine di migliaia (diciamo 60.000) di token univoci. L’algoritmo per determinare cosa costituisce un token è determinato da Algoritmo BPE (codifica coppia di byte).M.

Per scegliere il numero di token univoci nel nostro vocabolario (chiamato dimensione del vocabolario), dobbiamo tenere conto di alcune cose:

  1. Se scegliamo troppo pochi gettoni, torniamo al regime di un gettone per personaggio, ed è difficile per il modello imparare qualcosa di utile.
  2. Se scegliamo troppi token, ci ritroveremo in una situazione in cui le tabelle di incorporamento del modello oscurano il resto del peso del modello e diventa difficile distribuire il modello in un ambiente vincolato. La dimensione della tabella di incorporamento dipenderà dal numero di dimensioni che utilizziamo per ciascun token. Non è raro utilizzare una dimensione di 256, 512, 786, ecc… Se utilizziamo una dimensione di incorporamento di token di 512 e abbiamo 100.000 token, ci ritroveremo con una tabella di incorporamento che utilizza 200 MiB di memoria.

Pertanto, dobbiamo trovare un equilibrio quando scegliamo la dimensione del vocabolario. In questo esempio, scegliamo 6600 token e addestriamo il nostro tokenizzatore con una dimensione del vocabolario di 6600. Successivamente, diamo un’occhiata alla definizione del modello stesso.

Il modello PyTorch

Il modello in sé è piuttosto semplice. Abbiamo i seguenti strati:

  1. Incorporamento token (dimensione vocab=6600, dim incorporamento=512), per una dimensione totale di circa 15 MiB (assumendo 4 byte float32 come tipo di dati della tabella di incorporamento)
  2. LSTM (num layer=1, dimensione nascosta=786) per una dimensione totale di circa 16MiB
  3. Perceptron multistrato (da 786 a 3144 a 6600 dimensioni) per una dimensione totale di circa 93MiB

Il modello completo ha circa 31M di parametri addestrabili per una dimensione totale di circa 120MiB.

Ecco il codice PyTorch per il modello.

class WordPredictionLSTMModel(nn.Module):
def __init__(self, num_embed, embed_dim, pad_idx, lstm_hidden_dim, lstm_num_layers, output_dim, dropout):
super().__init__()
self.vocab_size = num_embed
self.embed = nn.Embedding(num_embed, embed_dim, pad_idx)
self.lstm = nn.LSTM(embed_dim, lstm_hidden_dim, lstm_num_layers, batch_first=True, dropout=dropout)
self.fc = nn.Sequential(
nn.Linear(lstm_hidden_dim, lstm_hidden_dim * 4),
nn.LayerNorm(lstm_hidden_dim * 4),
nn.LeakyReLU(),
nn.Dropout(p=dropout),

nn.Linear(lstm_hidden_dim * 4, output_dim),
)
#

def forward(self, x):
x = self.embed(x)
x, _ = self.lstm(x)
x = self.fc(x)
x = x.permute(0, 2, 1)
return x
#
#

Ecco il riepilogo del modello utilizzando torchinfo.

Riepilogo del modello LSTM

=================================================================
Layer (type:depth-idx) Param #
=================================================================
WordPredictionLSTMModel -
├─Embedding: 1–1 3,379,200
├─LSTM: 1–2 4,087,200
├─Sequential: 1–3 -
│ └─Linear: 2–1 2,474,328
│ └─LayerNorm: 2–2 6,288
│ └─LeakyReLU: 2–3 -
│ └─Dropout: 2–4 -
│ └─Linear: 2–5 20,757,000
=================================================================
Total params: 30,704,016
Trainable params: 30,704,016
Non-trainable params: 0
=================================================================

Interpretare l’accuratezza: Dopo aver addestrato questo modello su 12 milioni di frasi in lingua inglese per circa 8 ore su una GPU P100, abbiamo ottenuto una perdita di 4,03, una precisione nella top 1 del 29% e una precisione nella top 5 del 49%. Ciò significa che nel 29% dei casi il modello è stato in grado di prevedere correttamente il token successivo e nel 49% dei casi il token successivo nel set di training era una delle 5 principali previsioni del modello.

Quale dovrebbe essere la nostra metrica di successo? Anche se i numeri di precisione primi 1 e primi 5 per il nostro modello non sono impressionanti, non sono così importanti per il nostro problema. Le nostre parole candidate sono un piccolo insieme di possibili parole che si adattano allo schema di scorrimento. Ciò che vogliamo dal nostro modello è essere in grado di selezionare un candidato ideale per completare la frase in modo che sia sintatticamente e semanticamente coerente. Poiché il nostro modello apprende il natura del linguaggio attraverso i dati di training, ci aspettiamo che assegni una probabilità maggiore a frasi coerenti. Ad esempio, se abbiamo la frase “Il giocatore di baseball” e possibili candidati al completamento (“corse”, “nuotò”, “nascosto”), allora la parola “corse” è una parola successiva migliore delle altre due. Quindi, se il nostro modello prevede la parola corso con una probabilità più alta rispetto agli altri, per noi funziona.

Interpretare la perdita: Una perdita di 4,03 significa che la log-verosimiglianza negativa della previsione è 4,03, il che significa che la probabilità di prevedere correttamente il token successivo è e^-4,03 = 0,0178 o 1/56. Un modello inizializzato in modo casuale ha in genere una perdita di circa 8,8 che è -log_e(1/6600), poiché il modello prevede in modo casuale 1/6600 token (6600 è la dimensione del vocabolario). Sebbene una perdita di 4,03 possa non sembrare eccezionale, è importante ricordare che il modello addestrato è circa 120 volte migliore di un modello non addestrato (o inizializzato casualmente).

Successivamente, diamo un’occhiata a come possiamo utilizzare questo modello per migliorare i suggerimenti dalla nostra tastiera a scorrimento.

Utilizzo del modello per eliminare suggerimenti non validi

Diamo un’occhiata a un esempio reale. Supponiamo di avere una frase parziale “Penso”e l’utente esegue lo schema di scorrimento mostrato in blu di seguito, iniziando da “o”, passando tra le lettere “c” e “v” e terminando tra le lettere “e” e “v”.

Alcune possibili parole che potrebbero essere rappresentate da questo schema di scorrimento sono

  1. Sopra
  2. Ott (abbreviazione di ottobre)
  3. Ghiaccio
  4. Ho (con l’apostrofo implicito)

Di questi suggerimenti, probabilmente quello più probabile sarà “Io ho”. Inseriamo questi suggerimenti nel nostro modello e vediamo cosa ne esce.

(I think) (I've) = 0.00087
(I think) (over) = 0.00051
(I think) (ice) = 0.00001
(I think) (Oct) = 0.00000

Il valore dopo il segno = è la probabilità che la parola sia un completamento valido del prefisso della frase. In questo caso, vediamo che alla parola “I’ve” è stata assegnata la probabilità più alta. Quindi, è la parola che più probabilmente segue il prefisso della frase “I think”.

La prossima domanda che potresti avere è come possiamo calcolare queste probabilità della parola successiva. Diamo un’occhiata.

Calcolo della probabilità della parola successiva

Per calcolare la probabilità che una parola sia un completamento valido di un prefisso di frase, eseguiamo il modello in modalità eval (inferenza) e inseriamo il prefisso di frase tokenizzato. Inoltre tokenizziamo la parola dopo aver aggiunto un prefisso di spazio bianco alla parola. Questo viene fatto perché il pre-tokenizzatore di HuggingFace divide le parole con spazi all’inizio della parola, quindi vogliamo assicurarci che i nostri input siano coerenti con la strategia di tokenizzazione utilizzata dai tokenizzatori di HuggingFace.

Supponiamo che la parola candidata sia composta da 3 token T0, T1 e T2.

  1. Per prima cosa eseguiamo il modello con il prefisso della frase tokenizzata originale. Per l’ultimo token, controlliamo la probabilità di prevedere il token T0. Lo aggiungiamo all’elenco dei “problemi”.
  2. Successivamente, eseguiamo una previsione sul prefisso+T0 e controlliamo la probabilità del token T1. Aggiungiamo questa probabilità all’elenco dei “probabili”.
  3. Successivamente, eseguiamo una previsione sul prefisso+T0+T1 e controlliamo la probabilità del token T2. Aggiungiamo questa probabilità all’elenco dei “probabili”.

L’elenco “probs” contiene le probabilità individuali di generare i token T0, T1 e T2 in sequenza. Poiché questi token corrispondono alla tokenizzazione della parola candidata, possiamo moltiplicare queste probabilità per ottenere la probabilità combinata che il candidato sia un completamento del prefisso della frase.

Il codice per calcolare le probabilità di completamento è mostrato di seguito.

 def get_completion_probability(self, input, completion, tok):
self.model.eval()
ids = tok.encode(input).ids
ids = torch.tensor(ids, device=self.device).unsqueeze(0)
completion_ids = torch.tensor(tok.encode(completion).ids, device=self.device).unsqueeze(0)
probs = ()
for i in range(completion_ids.size(1)):
y = self.model(ids)
y = y(0,:,-1).softmax(dim=0)
# prob is the probability of this completion.
prob = y(completion_ids(0,i))
probs.append(prob)
ids = torch.cat((ids, completion_ids(:,i:i+1)), dim=1)
#
return torch.tensor(probs)
#

Di seguito possiamo vedere altri esempi.

(That ice-cream looks) (really) = 0.00709
(That ice-cream looks) (delicious) = 0.00264
(That ice-cream looks) (absolutely) = 0.00122
(That ice-cream looks) (real) = 0.00031
(That ice-cream looks) (fish) = 0.00004
(That ice-cream looks) (paper) = 0.00001
(That ice-cream looks) (atrocious) = 0.00000

(Since we're heading) (toward) = 0.01052
(Since we're heading) (away) = 0.00344
(Since we're heading) (against) = 0.00035
(Since we're heading) (both) = 0.00009
(Since we're heading) (death) = 0.00000
(Since we're heading) (bubble) = 0.00000
(Since we're heading) (birth) = 0.00000

(Did I make) (a) = 0.22704
(Did I make) (the) = 0.06622
(Did I make) (good) = 0.00190
(Did I make) (food) = 0.00020
(Did I make) (color) = 0.00007
(Did I make) (house) = 0.00006
(Did I make) (colour) = 0.00002
(Did I make) (pencil) = 0.00001
(Did I make) (flower) = 0.00000

(We want a candidate) (with) = 0.03209
(We want a candidate) (that) = 0.02145
(We want a candidate) (experience) = 0.00097
(We want a candidate) (which) = 0.00094
(We want a candidate) (more) = 0.00010
(We want a candidate) (less) = 0.00007
(We want a candidate) (school) = 0.00003

(This is the definitive guide to the) (the) = 0.00089
(This is the definitive guide to the) (complete) = 0.00047
(This is the definitive guide to the) (sentence) = 0.00006
(This is the definitive guide to the) (rapper) = 0.00001
(This is the definitive guide to the) (illustrated) = 0.00001
(This is the definitive guide to the) (extravagant) = 0.00000
(This is the definitive guide to the) (wrapper) = 0.00000
(This is the definitive guide to the) (miniscule) = 0.00000

(Please can you) (check) = 0.00502
(Please can you) (confirm) = 0.00488
(Please can you) (cease) = 0.00002
(Please can you) (cradle) = 0.00000
(Please can you) (laptop) = 0.00000
(Please can you) (envelope) = 0.00000
(Please can you) (options) = 0.00000
(Please can you) (cordon) = 0.00000
(Please can you) (corolla) = 0.00000

(I think) (I've) = 0.00087
(I think) (over) = 0.00051
(I think) (ice) = 0.00001
(I think) (Oct) = 0.00000

(Please) (can) = 0.00428
(Please) (cab) = 0.00000

(I've scheduled this) (meeting) = 0.00077
(I've scheduled this) (messing) = 0.00000

Questi esempi mostrano la probabilità che la parola completi la frase precedente. I candidati sono ordinati in ordine decrescente di probabilità.

Poiché i Transformer stanno lentamente sostituendo i modelli LSTM e RNN per le attività basate su sequenze, diamo un’occhiata a come sarebbe un modello Transformer per lo stesso obiettivo.

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *