menu
Paolo Avogadro

Pytorch 4 Feed Forward Neural Network

Posted on 15/10/2021, in Italiano. Reading time: 32 mins

Indice Globale degli argomenti tra i vari post:

 1.1. Indice degli argomenti.
 1.1. Introduzione e fonti.
 1.1. Lingo - Gergo utilizzato.
 1.2. Tensori in Pytorch.
 2.1. Chainrule e Autograd.
 2.2. Backpropagation.
 3.1. Loss e optimizer.
 3.2. Modelli di Pytorch.
 3.3. Dataset e Dataloader.
 3.4. Dataset Transforms.
 3.5. Softmax e Cross-Entropy Loss.
 3.6. Activation Function.
 4.1. Feed Forward Neural Network.
 5.1. Convolutional Neural Network.
 5.2. Transfer Learning.
 5.3. Tensorboard.
 6.1. I/O Saving and Loading Models.
 7.1. Recurrent Neural Networks.
 7.2. RNN, GRU e LSTM.
 8.1. Pytorch Lightning.
 8.2. LR Scheduler.
 9.1. Autoencoder.

Feed Forward Neural Network

Questo e’ il primo esempio di rete neurale funzionante. Nota che qui NON vengono fatti i passaggi preliminari per conoscere la struttura del datset, per vedere se le classi sono bilanciate, o per fare delle trasformazioni. L’ipotesi di partenza e’ che il dataset sia ok.

Scopo:

  • costruire una fully connected neural network con 1 hidden layer (Feed Forward: una volta fatto il passo il lavoro e’ finito).
  • per il riconoscimento di immagini del dataset MNIST (sono i numeri da 0 a 9 scritti a mano da varie persone, in immagini 28 x28 pixel con un solo canale di colore)

Metodo:

  1. Dataset e Dataloader:
    • Si importa un dataset, per esempio: torchvision.datasets.MNIST(...)
    • Si creano 2 dataloader, uno per il train e uno per il test, per esempio: torch.utils.data.DataLoader(...)
  2. Costruzione e istanziazione del modello:
    • si importa la classe nn.Model, a cui si devono poter passare come parametri le dimensioni dei tensori iniziali, hidden e output…
    • in nn.Model, all’interno del metodo __init__ si costruiscono i metodi e gli attributi che serviranno al grafo computazionale (per esempio self.l1= nn.Linear, self.n_output= n_output…)
    • in nn.Model si definiscono anche le ReLU, SoftMax, ecc self.relu= nn.ReLU()
    • eventualmente gli oggetti vengono mandati sul device: .to(device)
    • in nn.Model si costruisce il metodo forward() (il nome deve essere proprio forward) in cui si mettono i vari passaggi costruire il grafo computazionale
    • Finita la costruzione del modello se ne fa un’istanza passando come argomenti i valori corretti delle dimensioni dei tensori
    • si usa il metodo per mandare il modello sulla GPU: modello = NeuralNet(input_size,...).to(device) in modo che sia sulla GPU
  3. Scelta della funzione di loss (criterion) e del metodo di ottimizzazione (optimizer)
    • si sceglie una funzione di loss (che per esempio possiamo chiamare criterion) (occhio che la CrossEntropy include gia’ softmax)
    • si applica optimizer.zero_grad() per evitare che l’ottimizzatore entri nel grafo computazionale
    • ci si ricorda di
    • loss.backwards() costruisce i gradienti rispetto ai pesi (che hanno autograd =True) tramite la chain rule. Il gradiente della loss serve poi per l’ottimizzazione
    • si sceglie una funzione di ottimizzazione optimizer: SGD, Adam, … : per esempio: optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate) Nota che bisogna passare i parametri del modello che vengono ottenuti tramite model.parameters(), perche’ l’ottimizzatore sappia cosa deve modificare.
    • optimizer.step() fa un passo nella direzione dell’ottimizzazione in funzione del learning rate.
  4. Loop sulle epoche e sui batch.
    • si fa un loop sulle epoche (sceglo io quante).
    • si fa un loop per tutti i batch di ogni epoca.
    • con enumerate si spacchettano le immagini e le label dal dataloader.
    • si usa view o reshape per trasformare gli oggetti in 1D (il risultato e’ lo stesso)
    • NON chiaro, che differnza c’e’ tra passare un’immagine e un batch di immagini al modello? come viene gestito?
    • NON si chiama forward, ma si passano gli oggetti da classificare al modello (perche’ il modello nn.Model ha gia’ implementato un metodo __call__ e questo in pratica fa si che quando si chiami il modello come se fosse una funzione, allora si fa entrare in azione il metodo forward)
    • si crea una loss= criterion(output, labels) (posso comodamente cambiare la loss cambiando il criterio)
    • optimizer.zero_grad() ci si assicura che la backpropagation e l’ottimizzazione non modifichino i gradienti. Si evita quindi che entrino nel grafo computazionale.
    • si costruisce la chain rule con: loss.backward()
    • si ottimizzano i pesi usando un metodo del criterio di ottimizzazione: criterion.step()
  5. Si fa la validazione del modello tramite il test set
    • in questo caso si vuole evitare che i gradienti vengano fatti: with torch.no_grad()
    • si fanno i loop su tutti i batch (non ha senso sulle epoche) del test dataloader
    • si usano delle metriche, per esempio l’accuracy.

Note:

  • Per supporto GPU si costruisce un oggetto device come alla riga 8
  • piu’ tardi si fara’ un push to device, ottenuto sia per il modello che per i tensori tramite il metodo .to(device) (con l’oggetto device opportunamente definito prima, riga 8.
  • Occhio che non esiste “transform.ToTensor” ma “transforms”.ToTensor con una s dopo transform!!!! python mi dava errore: non esiste transform, ma l’errore era una semplice dimenticanza.
  • nota che la prima volta che carico il dataset ci mette un po’, la seconda volta MOLTO meno (lo mette da qualche parte in memoria forse)

All’inizio ci serve l’oggetto Module preso da nn. Questo oggetto e’ una specie di contenitore che connette le varie parti di una rete neurale.

Un layer fully connected da nn.Module ha bisogno di 3 parametri:

  • input size (numero di punti dell’immagine)
  • hidden size (numero di nodi nascosti)
  • output size (in questo caso sono le classi in uscita che voglio trovare).

Ricordiamoci di chiamare il metodo super. Questo mi consente di poter usare i metodi della superclass (la classe che ho ereditato) senza dover riscriverli da zero. Una buona spiegazione: https://realpython.com/python-super/

Il metodo forward ha 2 argomenti: self e x

  • self serve perche’ siamo dentro la classe e vogliamo accedere a tutti gli della istanza
  • x e’ invece l’immagine (il batch di immagini) che vogliamo catalogare

posso prendere invece che self.relu nn.ReLU ? (provare)

  • Non applico la softmax alla fine perche’ la cross entropy ha gia’ la softmax incorporata

Forward pass: ho un problema mi dice che non sono allineati gli oggetti su cpu e gpu, in particolare dice che :

  • out e’ su CPU
  • il tensore passato come argomento uno al modello e’ su CPU
  • ma lui se li aspetta sulla GPU

RISOLTO: dovevo usare il metodo to.(device) quando istanziavo il modello! (riga 17 del modello) Attento che lui nel video non lo faceva (probabilmente perche’ ha piu’ volte detto che il suo laptop non ha supporto GPU, quindi ovunque lui mandi qualcosa su device, resta su cpu)!

Nel seguito, prima spezzo in varie celle i passaggi, e poi per ultimo costruisco una cella con tutti i passaggi congiunti in modo da avere un luogo dove fare qualche esperimento.

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
%matplotlib inline

# device config
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')   # device, dove mandare i vari oggetti 

# Hyper parametri 
input_size = 28*28    #  =784 sono le dimensioni delle immagini
hidden_size  = 1000   #  scelto da me, e' la dimensione della hidden layer
num_classes = 10      #  devo classificare immagini di numeri da 0 a 9, quindi la dimensione dell'output =10
num_epochs = 2        #  faccio 2 giri completi sul dataset di train
batch_size = 100      #  questo no so come sia stato scelto, comunque posso modificare
learning_rate = 0.001 #  piccolo (potrei poi usare delle funzioni per cambiare questo parametro durante l'evoluzione)

#carico i dataset MNIST
#  root e' dove viene messo il dataset quando viene caricato 
#  gli diciamo poi che e' un dataset usato per il _training_
#  Occhio che poi quando facciamo train=False per costruire il dataset di test, lui splitta automaticamente
#  mi domando in quale percentuale. 
#  Trasformiamo le immagini facendo diventare le immagini in tensori
#  lo scarico se non e' gia' nella dir data

train_dataset = torchvision.datasets.MNIST(root= './data/', train= True, 
                                           transform =transforms.ToTensor(),
                                          download = True)

test_dataset = torchvision.datasets.MNIST(root= './data/', train= False, 
                                           transform =transforms.ToTensor(),
                                          download = False) # ho gia' scaricato tutto con il train_datast


# costruisco i dataloader:
#DataLoader necessita di almeno 2 parametri:
#  il nome del dataset
#  il la dimensione del batch
#  poi si fa shuffle = True/False

train_loader = torch.utils.data.DataLoader(dataset= train_dataset, batch_size = batch_size, shuffle=True)

test_loader = torch.utils.data.DataLoader (dataset=  test_dataset, batch_size = batch_size, shuffle=False)

## guardiamo un batch dei dati:

examples =  iter(train_loader)
samples, labels = examples.next()

print(samples.shape, labels.shape)
### nota che la dimensione dei sample e' la seguente
# 100  = numero di immagini nel batch (se non metti batch_size, di default vale 1)
#   1  = numero di canali solitamente i colori
#  28  =  numero di ingressi sull'asse delle x
#  28  = numero di ingressi sull'asse delle y
torch.Size([100, 1, 28, 28]) torch.Size([100])

qui disegno i sample, ricorda che subplot, indica la struttra, righe e colonne e poi l’indice dice quale di questi e’.

for i in range(6):
    plt.subplot(2,3,i+1)     # i primi parametri indicano 2 righe e 3 colonne, e poi il numero dell'immagine corrente.
    plt.imshow(samples[i][0], cmap= 'gray')  # occhio che c'e' solo samples[i][0], le label sono state messe in labels.

png

il Modello

# qui costruisco il modello: e' una classe

class NeuralNet(nn.Module):                                    # lo chiamo NeuralNet e lo importo da nn.Module
    def __init__(self, input_size, hidden_size, num_classes):  # il costruttore
        super(NeuralNet, self).__init__()                      # super per ereditare da nn.Module
        self.l1 = nn.Linear(input_size, hidden_size)           # L MAIUSCOLA per Linear
        self.relu = nn.ReLU()                                  # funzione di attivazione ReLU 
        self.l2 = nn.Linear(hidden_size, num_classes)          # secondo(ultimo) strato fully connected
       
    def forward (self, x):    # l'argomento self indica che viene lanciato su se stessi, la x e' il batch di immagini
        out = self.l1(x)      # sfrutto il metodo self.l1 a cui ho gia' dato dei parametri prima
        out = self.relu(out)  # ReLU non aveva bisogno di parametri: posso prendere nn.ReLU ?
        out = self.l2(out)
        return out            # DEVE restituire qualcosa: l'output
    

# qui creo una istanza del modello:
model = NeuralNet(input_size, hidden_size, num_classes).to(device)

Criterion e Optimizer

  • Qui sotto costruisco la funzione di LOSS
  • Poi costruisco l’optimizer, ovvero il metodo che mi “aggiusta” i pesi della rete neurale secondo determinati schemi. Attento, devo passare degli argomeni all’optimizer. In particolare c’e’ un metodo del modello che si chiama model.parameters()
criterion  = nn.CrossEntropyLoss()                                    # non ha bisogno di parametri
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)  # qui devo passare i parametri del modello!

Training loop

Qui sotto faccio il training che e’ composto da un loop esterno epoch e uno interno sui batch della singola epoch.

Osservazioni:

  • Il loop sui batches e’ fatto in modo interessante, con enumerate(dataloader). In questo modo ho un indice che mi dice in quale batch sono, inoltre ho creato una tupla contenente sia l’immagine che la corrispondente label (tra tonde)
  • devo mandare sia le immagini che le label sul device .to(device)
  • uso il modello, non uso il metodo del modello (se faccio model.forward() cosa succede? niente funziona allo stesso modo!)
n_total_steps = len(train_loader)

for epoch in range(num_epochs):                  # Loop sulle epochs
   # for steps in range(n_total_steps):           # loops sui batches 
   for i, (images,labels) in enumerate (train_loader):   # loop sui batch     
        # 100, 1, 28, 28  (batch, canali, x, y) forma del tensore images
        # 100, 28x28=784  forma voluta dall'hidden layer
        images = images.reshape(-1, 28*28).to(device)     #usando reshape con il -1
        labels = labels.to(device)
        
        # forward pass
        # print (images.is_cuda)        
        outputs = model (images)        #  non chiama il metodo forward: perche'?
        loss = criterion(outputs, labels)
                 
        # backward pass
        optimizer.zero_grad()   # non voglio che vengano inseriti nel backward pass
        loss.backward()          #  <========  fa tutto lui, calcola chain rule
        optimizer.step()         # aggiorna i pesi
        
        if (i+1) % 100 ==0:
            print(f'epoch {epoch+1} / {num_epochs}, step {i+1}/{n_total_steps}, loss= {loss.item():.4f}')        
epoch 1 / 2, step 100/600, loss= 0.3839
epoch 1 / 2, step 200/600, loss= 0.2682
epoch 1 / 2, step 300/600, loss= 0.1468
epoch 1 / 2, step 400/600, loss= 0.1664
epoch 1 / 2, step 500/600, loss= 0.0730
epoch 1 / 2, step 600/600, loss= 0.1700
epoch 2 / 2, step 100/600, loss= 0.0749
epoch 2 / 2, step 200/600, loss= 0.2193
epoch 2 / 2, step 300/600, loss= 0.1413
epoch 2 / 2, step 400/600, loss= 0.0539
epoch 2 / 2, step 500/600, loss= 0.0470
epoch 2 / 2, step 600/600, loss= 0.0669

Test Loop

qui calcolo l’accuracy con i dati di test.

  • La funzione torch.max restituisce: valori e l’indice. Noi vogliamo l’indice che e’ la classe.
  • per calcolare il numero totale di immagini processate, conto il numero di righe in labels.
  • per ogni predizione corretta aggiungo 1 “sum()” e estraggo dal tensore con item() (non chiarissimo il sum).
  • nota la scrittura con l’ ==, quindi facciamo una specie di if “online”
with torch.no_grad():    
    n_correct = 0         # numero di predizioni azzeccate
    n_samples = 0         # ? 
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        
        outputs = model(images)   # qui il modello e' gia' trainato!
        
        _, predictions = torch.max(outputs,1) # prendo la classe che ha il valore massimo
        n_samples += labels.shape[0]          # numero di samples nel batch corrente (nell'ultimo sono diversi spesso)
        n_correct += (predictions == labels).sum().item()
        
    acc = 100.0  * n_correct / n_samples # accuratezza in percentuale
    print(f'accuracy ={acc}')
accuracy =97.07

Prove sul modello

In questa cella faccio le mie varianti in modo da poter capire meglio i vari passaggi.

import numpy as np
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
%matplotlib inline

# device config
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#device = torch.device('cpu')

# Hyper parametri 
input_size = 28*28    #  =784 sono le dimensioni delle immagini
hidden_size  = 2000   #  scelto da me
num_classes = 10      #  devo classificare immagini di numeri
num_epochs = 1        #  quanti giri completi vengono fatti
batch_size = 100        #  questo no so come sia stato scelto
learning_rate = 0.001 #  piccolo

train_dataset = torchvision.datasets.MNIST(root= './data/', train= True, 
                                           transform =transforms.ToTensor(),
                                          download = True)

test_dataset = torchvision.datasets.MNIST(root= './data/', train= False, 
                                           transform =transforms.ToTensor(),
                                          download = False) # ho gia' scaricato tutto con il train_datast

train_loader = torch.utils.data.DataLoader(dataset= train_dataset, batch_size = batch_size, shuffle=True)

test_loader = torch.utils.data.DataLoader (dataset=  test_dataset, batch_size = batch_size, shuffle=False)

### nota che la dimensione dei sample e' la seguente
# 100  = numero di immagini nel batch (se non metti batch_size, di default vale 1)
#   1  = numero di canali solitamente i colori
#  28  =  numero di ingressi sull'asse delle x
#  28  = numero di ingressi sull'asse delle y

def myActiv(x):    # e' difficile costruire delle funzioni di attivazioni CUSTOM (vedi sopra)
    #return 1. +x/10. + 0.5*(x/10.)**2+1./6.*(x/10.)**3
    return  -3*x**2+4*x-1
        

############  MODELLO ####################
# qui costruisco il modello: e' una classe

class NeuralNet(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(NeuralNet, self).__init__()
        self.l1 = nn.Linear(input_size, hidden_size)  # L MAIUSCOLA
        self.relu = nn.ReLU()
        self.sigmoid= nn.Sigmoid()
        self.softplus = nn.Softplus()
        self.my = myActiv
        self.l2 = nn.Linear(hidden_size, num_classes)
       
    def forward (self, x):
        out = self.l1(x)      # sfrutto il metodo self.l1 a cui ho gia' dato dei parametri prima
        out = self.relu(out)  # ReLU non aveva bisogno di parametri: posso prendere nn.ReLU ?
        #out = self.sigmoid(out)
        #out = self.softplus(out)
        #out = self.my(out)
        out = self.l2(out)
        return out            # DEVE restituire qualcosa


############### ISTANZIO MODELLO ########
model = NeuralNet(input_size, hidden_size, num_classes).to(device)

############### CRITERION E OPTIMIZER ###
criterion  = nn.CrossEntropyLoss()   # non ha bisogno di parametri
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)       # qui devo passare i parametri del modello!

############### TRAINING LOOP ############
n_total_steps = len(train_loader)


tot =0 # PA
for epoch in range(num_epochs):                  # Loop sulle epoch  
   for i, (images,labels) in enumerate (train_loader):   # loop sui batch, uso enumerate cosi' so in che batch sono     
        # 100, 1, 28, 28  (batch, canali, x, y) forma del tensore images
        # 100, 28x28=784  forma voluta dall'hidden layer
        images = images.reshape(-1, 28*28).to(device)     #usando reshape con il -1
        labels = labels.to(device)
              
        #outputs = model (images)        #  non chiama il metodo forward: perche' nn.Model ha il __call__!
        outputs = model.forward(images)        #  non chiama il metodo forward: perche' nn.Model ha il __call__!
        
        loss = criterion(outputs, labels)

        tot = tot+1 # PA per fare delle modifiche sul Learning Rate
        if (tot%200 == 0):
            learning_rate = learning_rate *0.8
            print(f'Learning rate {learning_rate}')
            optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)
        
        
        # backward pass
        optimizer.zero_grad()   # non voglio che vengano inseriti nel backward pass
        loss.backward()          #  <========  fa tutto lui, calcola chain rule
        optimizer.step()         # aggiorna i pesi
        
        if (i+1) % 100 ==0:
            print(f'Epoch {epoch+1} / {num_epochs}, step {i+1}/{n_total_steps}, loss= {loss.item():.4f}')        

            
############ TEST LOOP #################
with torch.no_grad():    
    n_correct = 0         # numero di predizioni azzeccate
    n_samples = 0         # ? 
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        
        outputs = model(images)   # qui il modello e' gia' trainato!
        
        _, predictions = torch.max(outputs,1) # prendo la classe che ha il valore massimo
        n_samples += labels.shape[0]          # numero di samples nel batch corrente (nell'ultimo sono diversi spesso)
        n_correct += (predictions == labels).sum().item()
        
    acc = 100.0  * n_correct / n_samples # accuratezza in percentuale
    print(f'accuracy ={acc}')            
Epoch 1 / 1, step 100/600, loss= 0.2884
Learning rate 0.0008
Epoch 1 / 1, step 200/600, loss= 0.1714
Epoch 1 / 1, step 300/600, loss= 0.1254
Learning rate 0.00064
Epoch 1 / 1, step 400/600, loss= 0.1586
Epoch 1 / 1, step 500/600, loss= 0.1043
Learning rate 0.0005120000000000001
Epoch 1 / 1, step 600/600, loss= 0.0829
accuracy =97.06

Prova senza funzioni di attivazione: e’ un modello lineare!

Questi sono i risultati ottenuti quando elimino la activation function e lascio solo il modello lineare sottostante, dove c’e’ il primo passo la fully connected tra le immagini e lo strato hidden e dallo strato hidden all’output. I risultati sono in funzione di vari parametri variati. Con questo dataset i risultati non sono male, si hanno delle buone accuracy a condizione che il numero di neuroni sia paragonabile (anche 4 e’ sufficiente!) al numero di possibili output.

Nota pero’ che comunque ho la softmax finale per la cross entropy come loss.

  • senza attivazione: accuracy = 91.28 hidden_size =2000 epoch =1
  • senza attivazione: accuracy = 90.69 hidden_size =10 epoch =1
  • senza attivazione: accuracy = 92.54 hidden_size =10 epoch =4
  • senza attivazione: accuracy = 38.22 hidden_size =1 epoch =4
  • senza attivazione: accuracy = 41.95 hidden_size =1 epoch =14
  • senza attivazione: accuracy = 67.53 hidden_size =2 epoch =4
  • senza attivazione: accuracy = 86.04 hidden_size =4 epoch =4
import numpy as np
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
%matplotlib inline

# device config
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#device = torch.device('cpu')

# Hyper parametri 
input_size = 28*28    #  =784 sono le dimensioni delle immagini
hidden_size  = 4      #  scelto da me
num_classes = 10      #  devo classificare immagini di numeri
num_epochs = 4        #  quanti giri completi vengono fatti
batch_size = 100        #  questo no so come sia stato scelto
learning_rate = 0.001 #  piccolo

train_dataset = torchvision.datasets.MNIST(root= './data/', train= True, 
                                           transform =transforms.ToTensor(),
                                          download = True)

test_dataset = torchvision.datasets.MNIST(root= './data/', train= False, 
                                           transform =transforms.ToTensor(),
                                          download = False) # ho gia' scaricato tutto con il train_datast

train_loader = torch.utils.data.DataLoader(dataset= train_dataset, batch_size = batch_size, shuffle=True)

test_loader = torch.utils.data.DataLoader (dataset=  test_dataset, batch_size = batch_size, shuffle=False)

### nota che la dimensione dei sample e' la seguente
# 100  = numero di immagini nel batch (se non metti batch_size, di default vale 1)
#   1  = numero di canali solitamente i colori
#  28  =  numero di ingressi sull'asse delle x
#  28  = numero di ingressi sull'asse delle y

def myActiv(x):    # e' difficile costruire delle funzioni di attivazioni CUSTOM (vedi sopra)
    #return 1. +x/10. + 0.5*(x/10.)**2+1./6.*(x/10.)**3
    return  -3*x**2+4*x-1
        

############  MODELLO ####################
# qui costruisco il modello: e' una classe

class NeuralNet(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(NeuralNet, self).__init__()
        self.l1 = nn.Linear(input_size, hidden_size)  # L MAIUSCOLA
        self.l2 = nn.Linear(hidden_size, num_classes)
       
    def forward (self, x):
        out = self.l1(x)      # sfrutto il metodo self.l1 a cui ho gia' dato dei parametri prima
        #out = self.relu(out)  # ReLU non aveva bisogno di parametri: posso prendere nn.ReLU ?
        out = self.l2(out)
        return out            # DEVE restituire qualcosa


############### ISTANZIO MODELLO ########
model = NeuralNet(input_size, hidden_size, num_classes).to(device)

############### CRITERION E OPTIMIZER ###
criterion  = nn.CrossEntropyLoss()   # non ha bisogno di parametri
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)       # qui devo passare i parametri del modello!

############### TRAINING LOOP ############
n_total_steps = len(train_loader)

for epoch in range(num_epochs):                  # Loop sulle epoch  
   for i, (images,labels) in enumerate (train_loader):   # loop sui batch, uso enumerate cosi' so in che batch sono     
        # 100, 1, 28, 28  (batch, canali, x, y) forma del tensore images
        # 100, 28x28=784  forma voluta dall'hidden layer
        images = images.reshape(-1, 28*28).to(device)     #usando reshape con il -1
        labels = labels.to(device)
              
        outputs = model (images)        #  non chiama il metodo forward: perche'?
        loss = criterion(outputs, labels)
                 
        # backward pass
        optimizer.zero_grad()   # non voglio che vengano inseriti nel backward pass
        loss.backward()          #  <========  fa tutto lui, calcola chain rule
        optimizer.step()         # aggiorna i pesi
        
        if (i+1) % 100 ==0:
            print(f'epoch {epoch+1} / {num_epochs}, step {i+1}/{n_total_steps}, loss= {loss.item():.4f}')        

            
############ TEST LOOP #################
with torch.no_grad():    
    n_correct = 0         # numero di predizioni azzeccate
    n_samples = 0         # ? 
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        
        outputs = model(images)   # qui il modello e' gia' trainato!
        
        _, predictions = torch.max(outputs,1) # prendo la classe che ha il valore massimo
        n_samples += labels.shape[0]          # numero di samples nel batch corrente (nell'ultimo sono diversi spesso)
        n_correct += (predictions == labels).sum().item()
        
    acc = 100.0  * n_correct / n_samples # accuratezza in percentuale
    print(f'accuracy ={acc}')            
epoch 1 / 4, step 100/600, loss= 1.4573
epoch 1 / 4, step 200/600, loss= 1.0294
epoch 1 / 4, step 300/600, loss= 0.8547
epoch 1 / 4, step 400/600, loss= 0.6365
epoch 1 / 4, step 500/600, loss= 0.4938
epoch 1 / 4, step 600/600, loss= 0.6704
epoch 2 / 4, step 100/600, loss= 0.5902
epoch 2 / 4, step 200/600, loss= 0.4542
epoch 2 / 4, step 300/600, loss= 0.5579
epoch 2 / 4, step 400/600, loss= 0.7067
epoch 2 / 4, step 500/600, loss= 0.4833
epoch 2 / 4, step 600/600, loss= 0.6566
epoch 3 / 4, step 100/600, loss= 0.6494
epoch 3 / 4, step 200/600, loss= 0.4046
epoch 3 / 4, step 300/600, loss= 0.7297
epoch 3 / 4, step 400/600, loss= 0.3742
epoch 3 / 4, step 500/600, loss= 0.4305
epoch 3 / 4, step 600/600, loss= 0.4805
epoch 4 / 4, step 100/600, loss= 0.6506
epoch 4 / 4, step 200/600, loss= 0.3813
epoch 4 / 4, step 300/600, loss= 0.3637
epoch 4 / 4, step 400/600, loss= 0.6228
epoch 4 / 4, step 500/600, loss= 0.5557
epoch 4 / 4, step 600/600, loss= 0.5079
accuracy =86.55

Top