479 lines
25 KiB
Plaintext
479 lines
25 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Reti neurali ricorrenti\n",
|
|
"\n",
|
|
"Nel modulo precedente, abbiamo utilizzato rappresentazioni semantiche ricche del testo e un semplice classificatore lineare sopra gli embeddings. Questa architettura cattura il significato aggregato delle parole in una frase, ma non tiene conto dell'**ordine** delle parole, poiché l'operazione di aggregazione sugli embeddings elimina questa informazione dal testo originale. Poiché questi modelli non sono in grado di modellare l'ordine delle parole, non possono risolvere compiti più complessi o ambigui come la generazione di testo o la risposta alle domande.\n",
|
|
"\n",
|
|
"Per catturare il significato della sequenza di testo, dobbiamo utilizzare un'altra architettura di rete neurale, chiamata **rete neurale ricorrente**, o RNN. In una RNN, passiamo la nostra frase attraverso la rete un simbolo alla volta, e la rete produce uno **stato**, che poi passiamo nuovamente alla rete insieme al simbolo successivo.\n",
|
|
"\n",
|
|
"Dato la sequenza di token $X_0,\\dots,X_n$, la RNN crea una sequenza di blocchi di rete neurale e addestra questa sequenza end-to-end utilizzando la retropropagazione. Ogni blocco di rete prende una coppia $(X_i,S_i)$ come input e produce $S_{i+1}$ come risultato. Lo stato finale $S_n$ o l'output $X_n$ viene inviato a un classificatore lineare per produrre il risultato. Tutti i blocchi di rete condividono gli stessi pesi e vengono addestrati end-to-end con un unico passaggio di retropropagazione.\n",
|
|
"\n",
|
|
"Poiché i vettori di stato $S_0,\\dots,S_n$ vengono passati attraverso la rete, essa è in grado di apprendere le dipendenze sequenziali tra le parole. Ad esempio, quando la parola *not* appare da qualche parte nella sequenza, può imparare a negare determinati elementi all'interno del vettore di stato, risultando in una negazione.\n",
|
|
"\n",
|
|
"> Poiché i pesi di tutti i blocchi RNN nell'immagine sono condivisi, la stessa immagine può essere rappresentata come un unico blocco (a destra) con un ciclo di feedback ricorrente, che passa lo stato di output della rete nuovamente all'input.\n",
|
|
"\n",
|
|
"Vediamo come le reti neurali ricorrenti possono aiutarci a classificare il nostro dataset di notizie.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"Loading dataset...\n",
|
|
"Building vocab...\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import torch\n",
|
|
"import torchtext\n",
|
|
"from torchnlp import *\n",
|
|
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
|
"vocab_size = len(vocab)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Classificatore RNN semplice\n",
|
|
"\n",
|
|
"Nel caso di un RNN semplice, ogni unità ricorrente è una rete lineare semplice, che prende un vettore di input concatenato e un vettore di stato, e produce un nuovo vettore di stato. PyTorch rappresenta questa unità con la classe `RNNCell`, e una rete di tali celle - come livello `RNN`.\n",
|
|
"\n",
|
|
"Per definire un classificatore RNN, applicheremo prima un livello di embedding per ridurre la dimensionalità del vocabolario di input, e poi aggiungeremo un livello RNN sopra di esso:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class RNNClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,h = self.rnn(x)\n",
|
|
" return self.fc(x.mean(dim=1))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Nota:** Usiamo un livello di embedding non addestrato qui per semplicità, ma per ottenere risultati ancora migliori possiamo utilizzare un livello di embedding pre-addestrato con embedding Word2Vec o GloVe, come descritto nell'unità precedente. Per una migliore comprensione, potresti voler adattare questo codice per funzionare con embedding pre-addestrati.\n",
|
|
"\n",
|
|
"Nel nostro caso, utilizzeremo un data loader con padding, quindi ogni batch avrà un certo numero di sequenze con padding della stessa lunghezza. Il livello RNN prenderà la sequenza di tensori di embedding e produrrà due output: \n",
|
|
"* $x$ è una sequenza di output delle celle RNN a ogni passo \n",
|
|
"* $h$ è lo stato nascosto finale per l'ultimo elemento della sequenza \n",
|
|
"\n",
|
|
"Applichiamo quindi un classificatore lineare completamente connesso per ottenere il numero di classi.\n",
|
|
"\n",
|
|
"> **Nota:** Gli RNN sono piuttosto difficili da addestrare, perché una volta che le celle RNN vengono \"srotolate\" lungo la lunghezza della sequenza, il numero risultante di livelli coinvolti nella retropropagazione è piuttosto elevato. Pertanto, è necessario selezionare un tasso di apprendimento basso e addestrare la rete su un dataset più grande per ottenere buoni risultati. Può richiedere molto tempo, quindi è preferibile utilizzare una GPU.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.3090625\n",
|
|
"6400: acc=0.38921875\n",
|
|
"9600: acc=0.4590625\n",
|
|
"12800: acc=0.511953125\n",
|
|
"16000: acc=0.5506875\n",
|
|
"19200: acc=0.57921875\n",
|
|
"22400: acc=0.6070089285714285\n",
|
|
"25600: acc=0.6304296875\n",
|
|
"28800: acc=0.6484027777777778\n",
|
|
"32000: acc=0.66509375\n",
|
|
"35200: acc=0.6790056818181818\n",
|
|
"38400: acc=0.6929166666666666\n",
|
|
"41600: acc=0.7035817307692308\n",
|
|
"44800: acc=0.7137276785714286\n",
|
|
"48000: acc=0.72225\n",
|
|
"51200: acc=0.73001953125\n",
|
|
"54400: acc=0.7372794117647059\n",
|
|
"57600: acc=0.7436631944444444\n",
|
|
"60800: acc=0.7503947368421052\n",
|
|
"64000: acc=0.75634375\n",
|
|
"67200: acc=0.7615773809523809\n",
|
|
"70400: acc=0.7662642045454545\n",
|
|
"73600: acc=0.7708423913043478\n",
|
|
"76800: acc=0.7751822916666666\n",
|
|
"80000: acc=0.7790625\n",
|
|
"83200: acc=0.7825\n",
|
|
"86400: acc=0.7858564814814815\n",
|
|
"89600: acc=0.7890513392857142\n",
|
|
"92800: acc=0.7920474137931034\n",
|
|
"96000: acc=0.7952708333333334\n",
|
|
"99200: acc=0.7982258064516129\n",
|
|
"102400: acc=0.80099609375\n",
|
|
"105600: acc=0.8037594696969697\n",
|
|
"108800: acc=0.8060569852941176\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)\n",
|
|
"net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Long Short Term Memory (LSTM)\n",
|
|
"\n",
|
|
"Uno dei principali problemi delle RNN classiche è il cosiddetto problema dei **vanishing gradients**. Poiché le RNN vengono addestrate end-to-end in un unico passaggio di back-propagation, hanno difficoltà a propagare l'errore ai primi strati della rete, e di conseguenza la rete non riesce a imparare le relazioni tra token distanti. Uno dei modi per evitare questo problema è introdurre una **gestione esplicita dello stato** utilizzando i cosiddetti **gates**. Esistono due architetture più conosciute di questo tipo: **Long Short Term Memory** (LSTM) e **Gated Relay Unit** (GRU).\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"La rete LSTM è organizzata in modo simile a una RNN, ma ci sono due stati che vengono passati da uno strato all'altro: lo stato effettivo $c$ e il vettore nascosto $h$. In ogni unità, il vettore nascosto $h_i$ viene concatenato con l'input $x_i$, e insieme controllano cosa accade allo stato $c$ tramite i **gates**. Ogni gate è una rete neurale con attivazione sigmoide (output nell'intervallo $[0,1]$), che può essere considerata come una maschera bitwise quando moltiplicata per il vettore di stato. I gates sono i seguenti (da sinistra a destra nell'immagine sopra):\n",
|
|
"* **forget gate** prende il vettore nascosto e determina quali componenti del vettore $c$ dobbiamo dimenticare e quali far passare.\n",
|
|
"* **input gate** prende alcune informazioni dall'input e dal vettore nascosto, e le inserisce nello stato.\n",
|
|
"* **output gate** trasforma lo stato tramite un layer lineare con attivazione $\\tanh$, quindi seleziona alcune delle sue componenti utilizzando il vettore nascosto $h_i$ per produrre il nuovo stato $c_{i+1}$.\n",
|
|
"\n",
|
|
"Le componenti dello stato $c$ possono essere considerate come dei flag che possono essere attivati o disattivati. Ad esempio, quando incontriamo il nome *Alice* in una sequenza, potremmo voler assumere che si riferisca a un personaggio femminile, e attivare il flag nello stato che indica la presenza di un sostantivo femminile nella frase. Quando successivamente incontriamo la frase *e Tom*, attiveremo il flag che indica la presenza di un sostantivo plurale. In questo modo, manipolando lo stato, possiamo teoricamente tenere traccia delle proprietà grammaticali delle parti della frase.\n",
|
|
"\n",
|
|
"> **Nota**: Una risorsa eccellente per comprendere i dettagli interni delle LSTM è questo fantastico articolo [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) di Christopher Olah.\n",
|
|
"\n",
|
|
"Sebbene la struttura interna di una cella LSTM possa sembrare complessa, PyTorch nasconde questa implementazione all'interno della classe `LSTMCell` e fornisce l'oggetto `LSTM` per rappresentare l'intero layer LSTM. Pertanto, l'implementazione di un classificatore LSTM sarà molto simile a quella della semplice RNN che abbiamo visto sopra:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 4,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" x,(h,c) = self.rnn(x)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.259375\n",
|
|
"6400: acc=0.25859375\n",
|
|
"9600: acc=0.26177083333333334\n",
|
|
"12800: acc=0.2784375\n",
|
|
"16000: acc=0.313\n",
|
|
"19200: acc=0.3528645833333333\n",
|
|
"22400: acc=0.3965625\n",
|
|
"25600: acc=0.4385546875\n",
|
|
"28800: acc=0.4752777777777778\n",
|
|
"32000: acc=0.505375\n",
|
|
"35200: acc=0.5326704545454546\n",
|
|
"38400: acc=0.5557552083333334\n",
|
|
"41600: acc=0.5760817307692307\n",
|
|
"44800: acc=0.5954910714285714\n",
|
|
"48000: acc=0.6118333333333333\n",
|
|
"51200: acc=0.62681640625\n",
|
|
"54400: acc=0.6404779411764706\n",
|
|
"57600: acc=0.6520138888888889\n",
|
|
"60800: acc=0.662828947368421\n",
|
|
"64000: acc=0.673546875\n",
|
|
"67200: acc=0.6831547619047619\n",
|
|
"70400: acc=0.6917897727272727\n",
|
|
"73600: acc=0.6997146739130434\n",
|
|
"76800: acc=0.707109375\n",
|
|
"80000: acc=0.714075\n",
|
|
"83200: acc=0.7209134615384616\n",
|
|
"86400: acc=0.727037037037037\n",
|
|
"89600: acc=0.7326674107142858\n",
|
|
"92800: acc=0.7379633620689655\n",
|
|
"96000: acc=0.7433645833333333\n",
|
|
"99200: acc=0.7479032258064516\n",
|
|
"102400: acc=0.752119140625\n",
|
|
"105600: acc=0.7562405303030303\n",
|
|
"108800: acc=0.76015625\n",
|
|
"112000: acc=0.7641339285714286\n",
|
|
"115200: acc=0.7677777777777778\n",
|
|
"118400: acc=0.7711233108108108\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.03487814127604167, 0.7728)"
|
|
]
|
|
},
|
|
"execution_count": 5,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch(net,train_loader, lr=0.001)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Sequenze compattate\n",
|
|
"\n",
|
|
"Nel nostro esempio, abbiamo dovuto riempire tutte le sequenze nel minibatch con vettori di zeri. Sebbene ciò comporti un certo spreco di memoria, con le RNN è ancora più critico il fatto che vengano create celle RNN aggiuntive per gli elementi di input riempiti, che partecipano all'addestramento ma non contengono informazioni significative. Sarebbe molto meglio addestrare la RNN solo sulla dimensione effettiva della sequenza.\n",
|
|
"\n",
|
|
"Per fare ciò, in PyTorch viene introdotto un formato speciale per la memorizzazione delle sequenze riempite. Supponiamo di avere un minibatch di input riempito che appare così:\n",
|
|
"```\n",
|
|
"[[1,2,3,4,5],\n",
|
|
" [6,7,8,0,0],\n",
|
|
" [9,0,0,0,0]]\n",
|
|
"```\n",
|
|
"Qui 0 rappresenta i valori riempiti, e il vettore delle lunghezze effettive delle sequenze di input è `[5,3,1]`.\n",
|
|
"\n",
|
|
"Per addestrare efficacemente una RNN con sequenze riempite, vogliamo iniziare l'addestramento del primo gruppo di celle RNN con un grande minibatch (`[1,6,9]`), ma poi terminare l'elaborazione della terza sequenza e continuare l'addestramento con minibatch più piccoli (`[2,7]`, `[3,8]`), e così via. Pertanto, una sequenza compattata è rappresentata come un unico vettore - nel nostro caso `[1,6,9,2,7,3,8,4,5]`, e un vettore delle lunghezze (`[5,3,1]`), dal quale possiamo facilmente ricostruire il minibatch originale riempito.\n",
|
|
"\n",
|
|
"Per produrre una sequenza compattata, possiamo utilizzare la funzione `torch.nn.utils.rnn.pack_padded_sequence`. Tutti i livelli ricorrenti, inclusi RNN, LSTM e GRU, supportano le sequenze compattate come input e producono un output compattato, che può essere decodificato utilizzando `torch.nn.utils.rnn.pad_packed_sequence`.\n",
|
|
"\n",
|
|
"Per poter produrre una sequenza compattata, dobbiamo passare il vettore delle lunghezze alla rete, e quindi abbiamo bisogno di una funzione diversa per preparare i minibatch:\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 6,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def pad_length(b):\n",
|
|
" # build vectorized sequence\n",
|
|
" v = [encode(x[1]) for x in b]\n",
|
|
" # compute max length of a sequence in this minibatch and length sequence itself\n",
|
|
" len_seq = list(map(len,v))\n",
|
|
" l = max(len_seq)\n",
|
|
" return ( # tuple of three tensors - labels, padded features, length sequence\n",
|
|
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
|
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),\n",
|
|
" torch.tensor(len_seq)\n",
|
|
" )\n",
|
|
"\n",
|
|
"train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"La rete effettiva sarebbe molto simile a `LSTMClassifier` sopra, ma il passaggio `forward` riceverà sia il minibatch con padding che il vettore delle lunghezze delle sequenze. Dopo aver calcolato l'embedding, calcoliamo la sequenza impacchettata, la passiamo al livello LSTM e poi scompattiamo il risultato.\n",
|
|
"\n",
|
|
"> **Nota**: In realtà non utilizziamo il risultato scompattato `x`, perché usiamo l'output dei livelli nascosti nelle computazioni successive. Pertanto, possiamo rimuovere del tutto lo scompattamento da questo codice. Il motivo per cui lo includiamo qui è per permetterti di modificare facilmente questo codice, nel caso in cui dovessi aver bisogno di utilizzare l'output della rete in ulteriori computazioni.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 7,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class LSTMPackClassifier(torch.nn.Module):\n",
|
|
" def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):\n",
|
|
" super().__init__()\n",
|
|
" self.hidden_dim = hidden_dim\n",
|
|
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
|
" self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5\n",
|
|
" self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)\n",
|
|
" self.fc = torch.nn.Linear(hidden_dim, num_class)\n",
|
|
"\n",
|
|
" def forward(self, x, lengths):\n",
|
|
" batch_size = x.size(0)\n",
|
|
" x = self.embedding(x)\n",
|
|
" pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)\n",
|
|
" pad_x,(h,c) = self.rnn(pad_x)\n",
|
|
" x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)\n",
|
|
" return self.fc(h[-1])"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {
|
|
"scrolled": true
|
|
},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"3200: acc=0.285625\n",
|
|
"6400: acc=0.33359375\n",
|
|
"9600: acc=0.3876041666666667\n",
|
|
"12800: acc=0.44078125\n",
|
|
"16000: acc=0.4825\n",
|
|
"19200: acc=0.5235416666666667\n",
|
|
"22400: acc=0.5559821428571429\n",
|
|
"25600: acc=0.58609375\n",
|
|
"28800: acc=0.6116666666666667\n",
|
|
"32000: acc=0.63340625\n",
|
|
"35200: acc=0.6525284090909091\n",
|
|
"38400: acc=0.668515625\n",
|
|
"41600: acc=0.6822596153846154\n",
|
|
"44800: acc=0.6948214285714286\n",
|
|
"48000: acc=0.7052708333333333\n",
|
|
"51200: acc=0.71521484375\n",
|
|
"54400: acc=0.7239889705882353\n",
|
|
"57600: acc=0.7315277777777778\n",
|
|
"60800: acc=0.7388486842105263\n",
|
|
"64000: acc=0.74571875\n",
|
|
"67200: acc=0.7518303571428572\n",
|
|
"70400: acc=0.7576988636363636\n",
|
|
"73600: acc=0.7628940217391305\n",
|
|
"76800: acc=0.7681510416666667\n",
|
|
"80000: acc=0.7728125\n",
|
|
"83200: acc=0.7772235576923077\n",
|
|
"86400: acc=0.7815393518518519\n",
|
|
"89600: acc=0.7857700892857142\n",
|
|
"92800: acc=0.7895043103448276\n",
|
|
"96000: acc=0.7930520833333333\n",
|
|
"99200: acc=0.7959072580645161\n",
|
|
"102400: acc=0.798994140625\n",
|
|
"105600: acc=0.802064393939394\n",
|
|
"108800: acc=0.8051378676470589\n",
|
|
"112000: acc=0.8077857142857143\n",
|
|
"115200: acc=0.8104600694444445\n",
|
|
"118400: acc=0.8128293918918919\n"
|
|
]
|
|
},
|
|
{
|
|
"data": {
|
|
"text/plain": [
|
|
"(0.029785829671223958, 0.8138166666666666)"
|
|
]
|
|
},
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"output_type": "execute_result"
|
|
}
|
|
],
|
|
"source": [
|
|
"net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)\n",
|
|
"train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"> **Nota:** Potresti aver notato il parametro `use_pack_sequence` che passiamo alla funzione di training. Attualmente, la funzione `pack_padded_sequence` richiede che il tensore della sequenza di lunghezza sia sul dispositivo CPU e, di conseguenza, la funzione di training deve evitare di spostare i dati della sequenza di lunghezza su GPU durante il training. Puoi esaminare l'implementazione della funzione `train_emb` nel file [`torchnlp.py`](../../../../../lessons/5-NLP/16-RNN/torchnlp.py).\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Reti neurali ricorrenti bidirezionali e multilivello\n",
|
|
"\n",
|
|
"Nei nostri esempi, tutte le reti ricorrenti operavano in una sola direzione, dall'inizio di una sequenza alla fine. Sembra naturale, perché somiglia al modo in cui leggiamo e ascoltiamo il parlato. Tuttavia, poiché in molti casi pratici abbiamo accesso casuale alla sequenza di input, potrebbe avere senso eseguire il calcolo ricorrente in entrambe le direzioni. Queste reti sono chiamate **RNN bidirezionali**, e possono essere create passando il parametro `bidirectional=True` al costruttore di RNN/LSTM/GRU.\n",
|
|
"\n",
|
|
"Quando si lavora con una rete bidirezionale, avremo bisogno di due vettori di stato nascosto, uno per ciascuna direzione. PyTorch codifica questi vettori come un unico vettore di dimensione doppia, il che è piuttosto conveniente, perché normalmente si passa lo stato nascosto risultante a un livello lineare completamente connesso, e basta tenere conto di questo aumento di dimensione quando si crea il livello.\n",
|
|
"\n",
|
|
"Una rete ricorrente, sia unidirezionale che bidirezionale, cattura determinati schemi all'interno di una sequenza e può memorizzarli nel vettore di stato o passarli all'output. Come con le reti convoluzionali, possiamo costruire un altro livello ricorrente sopra il primo per catturare schemi di livello superiore, costruiti a partire dagli schemi di basso livello estratti dal primo livello. Questo ci porta al concetto di **RNN multilivello**, che consiste in due o più reti ricorrenti, dove l'output del livello precedente viene passato al livello successivo come input.\n",
|
|
"\n",
|
|
"\n",
|
|
"\n",
|
|
"*Immagine tratta da [questo fantastico articolo](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) di Fernando López*\n",
|
|
"\n",
|
|
"PyTorch rende la costruzione di tali reti un compito semplice, perché basta passare il parametro `num_layers` al costruttore di RNN/LSTM/GRU per costruire automaticamente diversi livelli di ricorrenza. Questo significa anche che la dimensione del vettore di stato nascosto aumenterà proporzionalmente, e sarà necessario tenerne conto quando si gestisce l'output dei livelli ricorrenti.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"## RNN per altri compiti\n",
|
|
"\n",
|
|
"In questa unità, abbiamo visto che le RNN possono essere utilizzate per la classificazione di sequenze, ma in realtà possono gestire molti altri compiti, come la generazione di testo, la traduzione automatica e altro ancora. Considereremo questi compiti nella prossima unità.\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"metadata": {},
|
|
"source": [
|
|
"\n---\n\n**Disclaimer**: \nQuesto documento è stato tradotto utilizzando il servizio di traduzione automatica [Co-op Translator](https://github.com/Azure/co-op-translator). Sebbene ci impegniamo per garantire l'accuratezza, si prega di notare che le traduzioni automatiche possono contenere errori o imprecisioni. Il documento originale nella sua lingua nativa dovrebbe essere considerato la fonte autorevole. Per informazioni critiche, si raccomanda una traduzione professionale effettuata da un traduttore umano. Non siamo responsabili per eventuali incomprensioni o interpretazioni errate derivanti dall'uso di questa traduzione.\n"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"interpreter": {
|
|
"hash": "16af2a8bbb083ea23e5e41c7f5787656b2ce26968575d8763f2c4b17f9cd711f"
|
|
},
|
|
"kernelspec": {
|
|
"display_name": "Python 3.8.12 ('py38')",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.8.12"
|
|
},
|
|
"coopTranslator": {
|
|
"original_hash": "522ee52ae3d5ae933e283286254e9a55",
|
|
"translation_date": "2025-08-28T14:23:54+00:00",
|
|
"source_file": "lessons/5-NLP/16-RNN/RNNPyTorch.ipynb",
|
|
"language_code": "it"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
} |