Pytorch 4 Feed Forward Neural Network
Posted on 15/10/2021, in Italiano. Reading time: 32 minsIndice 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
:
- 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(...)
- Si importa un dataset, per esempio:
- 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 esempioself.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 ilgrafo 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 sullaGPU
- si importa la classe
- 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 tramitemodel.parameters()
, perche’ l’ottimizzatore sappia cosa deve modificare. optimizer.step()
fa un passo nella direzione dell’ottimizzazione in funzione del learning rate.
- si sceglie una funzione di loss (che per esempio possiamo chiamare
- 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
oreshape
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 almodello
(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 metodoforward
) - 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()
- 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
.
- in questo caso si vuole evitare che i gradienti vengano fatti:
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.
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
Comments