menu
Paolo Avogadro

Pytorch 5 Convolutional Neural Network

Posted on 15/10/2021, in Italiano. Reading time: 43 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.

Convolutional Neural Network

Video-Lezione del MIT sulle CNN (Inglese).

Una video-lezione estesa sugli stessi argomenti e dagli stessi autori qui

Materiale della lezione: http://introtodeeplearning.com​

qui vediamo degli esempi specifici riguardo le reti convoluzionali.

  • L’esempio e’ basato sul dataset CIFAR-10.
  • 10 classi, 6000 immagini per classe.
  • ogni immagine sono 32 x 32
  • ogni immagine ha 3 canali
  • 50 000 immagini di training e 10 000 immagini di test
  • Il dataset sembra gia’ essere diviso in batches

Problema ho implementato fino a criterion ma ho un errore quando istanzio il modello (prima di averlo riempito… forse e’ per quello). 'ConvNet' object has no attribute '_modules'

-Domanda: Non mi e’ chiaro come vengano gestiti i casi di 3 canali di colore. Come faccio ad associarli a una singola immagine? -Risposta. Posso pensare i 3 canli come un’immagine una sopra l’altra. Le convoluzioni applicano in modo indipendente ai 3 canali, ma alla fine quando faccio un flatten li metto tutti in un unico tensore 1D (forse)

Regola del numero di punti restanti in funzione di una convoluzione/ pooling,

  • w = larghezza dell’immagine.
  • F = larghezza del filtro
  • P = larghezza del padding
  • S = stride

Risultato (minuto 14:00 del tutorial 14): \(\frac{W-F+2P}{S}+1\) (in questo caso P =0 dato che non ho padding, e S=1 in quanto il kernel si muove di un quadretto per volta) drawing (nota che i quadrati colorati successivi sono un po’ piu’ piccoli per evitare sovrapposizioni ma riguardano tutti i punti delle celle che toccano) Ora viene implementata una rete neurale con la seguente (immagine da internet, non penso sia di Loeber) struttura:

drawing

  • conv + relu
  • pooling
  • conv + relu
  • pooling
  • fully connected
  • fully connected
  • fully connected

Come misurare le dimensioni dei tensori in uscita

Quando si hanno delle convoluzioni o dei pooling, la dimensione dei tensori in uscita non e’ facilissima da calcolare al volo, fortunatamente esiste una formula semplice.

  1. In torch le convoluzioni nn.Conv2d(3,6,5) hanno 3 parametri:
  • il primo parametro e’ il numero di canali in ingresso (per esempio 3, associati a r g b).
  • il secondo parametro e’ il numero di canali in USCITA, nell’esempio sopra 6. Cosa significa? significa che ho preso 6 kernel differenti (riempiti con valori random) e li ho applicati tutti e 6 (ipotesi mia)!
  • il terzo parametro e’ la dimensione del kernel

  • ci sono i canali (in entrata di solito sono i colori, in uscita non hanno questo significato, in quanto possono cambiare.
  1. I max pooling hanno 2 parametri, per esempio: nn.MaxPool2d(2,2):
    • la dimensione del kernel
    • lo stride

Riassumendo si usa la formula (w - f +2p )/s +1 sia per convoluzione che per pooling:

  • prima convoluzione (kernel 5 x 5) passo da 3 canali 32 x 32 a : (32-5-0)/1+1 = 28 x 28 ( 6 canali ,scelto da me)
  • primo pooling (kernel 2 x 2) con stride 2 passo da 6 canali 28 x 28 -> (28-2-0)/2 + 1 = 14 x 14 ( 6 canali, non modificabile)
  • seconda convoluzione (kernel 5 x5) passo da 6 canali 14 x 14 a -> (14 - 5 -0)/1+1 = 10 x 10 (16 canali, scelto da me)
  • secondo pooling (kernel 2 x 2) passp da 16 canali 10 x 10 a -> (10-2 -0)/2 +1 = 5 x 5 (16 canali non modificabile)

Quindi quando faccio un flatten alla fine ottengo: 5 x 5 x 16 esatto come nel video!

In grassetto metto i valori che scelgo io (per esempio i canali di ouput con la convolione)

operazione canali input canali output kernel Stride figura input figura output
I convoluzione 3 6 5 x 5 1 32 x 32 (32-5-0)/1+1 =28
I max pooling 6 6 2 x 2 2 28 x 28 (28-2-0)/2+1 =14
II convoluzione 6 16 5 x 5 1 14 x 14 (14-5-0)/1+1 =10
II max pooling 16 16 2 x 2 2 10 x 10 (10-2-0)/2+1 = 5

Osservazione

  • alla funzione di convoluzione vengono passati come argomenti solo: i canali di ingresso, canali di uscita e la dimensione del kernel, per esempio: nn.Conv2d(3,6,5)
  • Non si passa la dimensione delle figure, viene presa in automatico!
  • alla funzione di max pool invece si passano solo: la dimensione del kernel e dimensione della stride:nn.MaxPool2d(2,2)

Problemi:

  • ho provato ha mettere dei valori custom degli strati finali fully connected, non mi prende hidden_size2, ma se metto un valore preciso lo prende… perche? Il motivo era che avevo messo queste quantita’ dentro la classe indentando. Se le metto fuori, vengono prese come variabili globali!
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
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')

# Hyper parametri 
input_size = 32*32    #  =1024 sono le dimensioni delle immagini
num_classes = 10      #  devo classificare immagini di numeri
num_epochs = 6        #  quanti giri completi vengono fatti
batch_size = 4        #  questo no so come sia stato scelto
learning_rate = 0.001 #  piccolo
hidden_size1  = 220   #  scelto da me
hidden_size2  = 184   #  scelto da me 

transform =  transforms.Compose( [transforms.ToTensor(), transforms.Normalize( (0.5,0.5,0.5), (0.5,0.5,0.5)  )  ] )


train_dataset = torchvision.datasets.CIFAR10(root= './data/', train= True, 
                                           transform =transform,            # uso le trasformazioni indicate sopra
                                          download = True)

test_dataset = torchvision.datasets.CIFAR10(root= './data/', train= False, 
                                           transform =transform,
                                          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)

# Pytorch lavora con numeri, qui assegno i nomi corrispondenti
#             0       1       2       3      4      5       6       7       8        9 
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

def imshow(img):
    img = img /2 + 0.5 # "denormalizza" forse per avere dei colori migliori
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1,2,0)))


######### MODELLO ###############

class ConvNet(nn.Module):

    
    def __init__(self):
        super(ConvNet, self).__init__()
        self.conv1  = nn.Conv2d(3, 6, 5)     # input channel size, output channel size,  Kernel size 5 (5x5)
        self.pool   = nn.MaxPool2d(2,2)      # kernel size, stride  (e' un quadrato 4x4 diviso in 4 parti 2x2)
        self.conv2  = nn.Conv2d(6, 16, 5)    # input channel size = out di prima = 6, scelgo l'out= 16 e kernel size 5 (5x5)  
        self.fc1    = nn.Linear(16*5*5, hidden_size1) # fully connected (spiegato dopo) ingresso, e 120 in uscita
        self.fc2    = nn.Linear(hidden_size1, hidden_size2)     # in ingresso prende l'uscita del precedente e in output un tensor 1D con 84 ingressi
        self.fc3    = nn.Linear(hidden_size2, 10)      # in uscita ho solo le 10 classi di oggetti (riga 38)            
        
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))  #  MaxPool(ReLU(convoluzione(immagine))
        x = self.pool(F.relu(self.conv2(x)))  #  MaxPool(ReLU(convoluzione( ))
        x = x.view(-1, 16*5*5)                #  metto l'immagine processata in un vettore 1D
        x = F.relu(self.fc1(x))               #  ReLU(fc())
        x = F.relu(self.fc2(x))               #  ReLU(fc())
        x = self.fc3(x)                       #  qui non faccio la ReLU, perche' poi nella cross entropy c'e' softmax
        return x



model = ConvNet().to(device)                  # metto sul device    
    
criterion = nn.CrossEntropyLoss()             # serve per fare la LOSS, include la softmax per l'ultimo strato
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate)
    
n_total_steps  = len(train_loader)
for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        # forma [4, 3, 32, 32] -> [4, 3, 1024]       4 immagini, 3 canali di colore, 32 x 32 
        # input layer: 3 canali di input, 6 canali di uscita, 5 kernel size
        images = images.to(device)
        labels = labels.to(device)
        
        # forward
        outputs = model(images)
        loss = criterion(outputs, labels)
        
        # backward e ottimizzazione dei pesi
        optimizer.zero_grad()
        loss.backward()              # chain rule
        optimizer.step()             # step di ottimizzazione dato il learning rate
        
        if (i+1) % 1000 ==0:
            print(f'epoch {epoch+1} / {num_epochs}, step {i+1}/{n_total_steps}, loss= {loss.item():.4f}')     
print("Training Terminato")

# qui faccio la fase di testing:

with torch.no_grad():
    n_correct = 0
    n_samples = 0
    n_class_correct = [0 for i in range(10)]  # e' una lista uso una list-comprehension
    n_class_samples = [0 for i in range(10)]  # non chiaro cosa sia questo
    
    for images, labels in test_loader:
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)
        
        _, predicted = torch.max(outputs, 1)
        n_samples += labels.size(0)      # non serve una quadra?
        n_correct += (predicted == labels).sum().item()       # questa da capire bene 
        
        for i in range (batch_size):
            label = labels[i]        # gira nel batch, label della singola immagine
            pred = predicted[i]      # guarda la riga 90
            
            if (label == pred):
                n_class_correct[label] += 1  # fa un istogramma
            n_class_samples [label] +=1      # ne ho trovsata una un piu' della classe label
    
    acc = 100.0 * n_correct / n_samples      # percentuale di corrette sul totale dei sample
    print(f'Accuracy  = {acc}:.4f')
        
    for i in range(10):
        acc = 100.0 *n_class_correct[i]/ n_class_samples[i]  # accuracy della singola classe
        print(f'Accuracy della classe {classes[i]}: {acc} %'  )
    
        
Files already downloaded and verified
epoch 1 / 6, step 1000/12500, loss= 2.3000
epoch 1 / 6, step 2000/12500, loss= 2.3080
epoch 1 / 6, step 3000/12500, loss= 2.3117
epoch 1 / 6, step 4000/12500, loss= 2.2834
epoch 1 / 6, step 5000/12500, loss= 2.2788
epoch 1 / 6, step 6000/12500, loss= 2.2987
epoch 1 / 6, step 7000/12500, loss= 2.2965
epoch 1 / 6, step 8000/12500, loss= 2.3122
epoch 1 / 6, step 9000/12500, loss= 2.1770
epoch 1 / 6, step 10000/12500, loss= 2.2453
epoch 1 / 6, step 11000/12500, loss= 1.9151
epoch 1 / 6, step 12000/12500, loss= 2.1131
epoch 2 / 6, step 1000/12500, loss= 2.2575
epoch 2 / 6, step 2000/12500, loss= 1.6527
epoch 2 / 6, step 3000/12500, loss= 1.3318
epoch 2 / 6, step 4000/12500, loss= 1.4340
epoch 2 / 6, step 5000/12500, loss= 2.3746
epoch 2 / 6, step 6000/12500, loss= 1.9364
epoch 2 / 6, step 7000/12500, loss= 2.1739
epoch 2 / 6, step 8000/12500, loss= 2.1157
epoch 2 / 6, step 9000/12500, loss= 1.5521
epoch 2 / 6, step 10000/12500, loss= 1.7367
epoch 2 / 6, step 11000/12500, loss= 1.6836
epoch 2 / 6, step 12000/12500, loss= 1.8475
epoch 3 / 6, step 1000/12500, loss= 1.7431
epoch 3 / 6, step 2000/12500, loss= 2.2294
epoch 3 / 6, step 3000/12500, loss= 2.5908
epoch 3 / 6, step 4000/12500, loss= 1.4687
epoch 3 / 6, step 5000/12500, loss= 1.6939
epoch 3 / 6, step 6000/12500, loss= 1.4162
epoch 3 / 6, step 7000/12500, loss= 1.5874
epoch 3 / 6, step 8000/12500, loss= 1.5107
epoch 3 / 6, step 9000/12500, loss= 1.7396
epoch 3 / 6, step 10000/12500, loss= 1.6814
epoch 3 / 6, step 11000/12500, loss= 2.7111
epoch 3 / 6, step 12000/12500, loss= 1.9274
epoch 4 / 6, step 1000/12500, loss= 1.4081
epoch 4 / 6, step 2000/12500, loss= 1.0557
epoch 4 / 6, step 3000/12500, loss= 1.9581
epoch 4 / 6, step 4000/12500, loss= 1.2325
epoch 4 / 6, step 5000/12500, loss= 2.0073
epoch 4 / 6, step 6000/12500, loss= 1.1686
epoch 4 / 6, step 7000/12500, loss= 2.1211
epoch 4 / 6, step 8000/12500, loss= 1.8252
epoch 4 / 6, step 9000/12500, loss= 1.3711
epoch 4 / 6, step 10000/12500, loss= 0.7326
epoch 4 / 6, step 11000/12500, loss= 1.5150
epoch 4 / 6, step 12000/12500, loss= 1.2301
epoch 5 / 6, step 1000/12500, loss= 1.7059
epoch 5 / 6, step 2000/12500, loss= 1.4075
epoch 5 / 6, step 3000/12500, loss= 1.5126
epoch 5 / 6, step 4000/12500, loss= 1.4504
epoch 5 / 6, step 5000/12500, loss= 1.7903
epoch 5 / 6, step 6000/12500, loss= 1.4010
epoch 5 / 6, step 7000/12500, loss= 1.5074
epoch 5 / 6, step 8000/12500, loss= 1.0711
epoch 5 / 6, step 9000/12500, loss= 0.9101
epoch 5 / 6, step 10000/12500, loss= 1.4603
epoch 5 / 6, step 11000/12500, loss= 1.5229
epoch 5 / 6, step 12000/12500, loss= 1.2879
epoch 6 / 6, step 1000/12500, loss= 0.9052
epoch 6 / 6, step 2000/12500, loss= 1.2526
epoch 6 / 6, step 3000/12500, loss= 1.0965
epoch 6 / 6, step 4000/12500, loss= 1.1149
epoch 6 / 6, step 5000/12500, loss= 1.2192
epoch 6 / 6, step 6000/12500, loss= 1.1820
epoch 6 / 6, step 7000/12500, loss= 1.4098
epoch 6 / 6, step 8000/12500, loss= 1.8098
epoch 6 / 6, step 9000/12500, loss= 1.0243
epoch 6 / 6, step 10000/12500, loss= 1.3008
epoch 6 / 6, step 11000/12500, loss= 0.9658
epoch 6 / 6, step 12000/12500, loss= 1.0129
Training Terminato
Accuracy  = 52.13:.4f
Accuracy della classe plane: 49.5 %
Accuracy della classe car: 71.5 %
Accuracy della classe bird: 49.9 %
Accuracy della classe cat: 33.7 %
Accuracy della classe deer: 29.7 %
Accuracy della classe dog: 32.7 %
Accuracy della classe frog: 78.4 %
Accuracy della classe horse: 50.6 %
Accuracy della classe ship: 69.7 %
Accuracy della classe truck: 55.6 %

Transfer Learning

In questo paragrafro viene spiegato come usare parzialmente dei modelli gia’ “trainati” per fare dei nuovi compiti simili. Curiosamente questo sembra funzionare molto bene. Dico curiosamente perche’ non e’ chiaro il motivo per cui una rete neurale che ha un training su degli oggetti possa andare bene anche su degli oggetti differenti. A meno che, in qualche modo, si siano imparate delle caratteristiche generali dal primo training.

Quello che e’ davvero potente e’ che modificando l’ultimo layer posso cambiare il numero di valori in uscita. Con Resnet18 ci sono 1000 classi in uscita. Cambio l’ultimo layer e me ne servono solo 2 in uscita: no problem!

  • per esempio faccio il training per un modello che classifica uccelli e gatti
  • con una piccola modifica gli faro’ classificare api e cani

Osservazione

  • gli uccelli hanno caratteristiche in comune alle api (non mi pare scelto a caso!)
  • i cani hanno caratteristiche in comune ai gatti (anche questo non mi pare a caso)

Quindi il transfer learning, intuitivamente, funziona bene quando le modifiche da fare sono piccole (e’ un pensiero mio).

Si cambia solo l’ultimo strato fully connected (rispetto al caso precedente gli ultimi 3 strati), questo e’ infatti lo strato di classificazione. Gli strati precedenti tramite le convoluzioni e i pooling dovrebbero estrarre le caratteristiche dei vari oggetti.

  • Intuitivamente mi viene da dire che gli strati convoluzionali prendono i “pezzi” (p.es le ali, le zampe, ecc, ma nella realta’ prendono dettagli molto piu’ piccoli)
  • gli strati finali mettono insieme questi pezzi elementari.
  • quindi mi viene da dire che dovrebbe bastare 1 strato fully connected.
  • in realta’ nel video di 3blue1brown si vede che non e’ molto chiaro cosa venga preso nei vari passaggi.

In questo caso vediamo anche come caricare un modello gia’ “trainato” (devo trovare un nome migliore…ma dire con i pesi ottimizzati e’ lungo e anche modello ottimizzato non mi piace):
Resnet18NN

  • basato sul Imagenet database (con piu’ di 1 000 000 immagni)
  • ha 18 strati
  • classifica oggetti di 1000 categorie.

Attenzione che qui fa una cosa leggemente diversa da quanto fatto finora, dove si costruisce il modello e lo si lancia da un loop.

In questo caso costruisce una funzione

Image Folder

Costruire un folder dove mettere le immagini che devono servire per train e test.
Il folder che contiene le immagini che vogliamo usare come nuovo training ha il seguente formato:

  • train
    • ants
    • bees
  • val
    • ants
    • bees

Quindi se la struttura e’ questa chiamando datasets.Imagefolder posso leggere e trasformare in un dataset. Posso inoltre usare l’attributo classes.

Scheduler

  • E’ una funzione che modifica il learning rate durante il training per ottimizzare la discesa dei gradienti.

Fine Tuninig

tengo tutto il modello pre-trainato ma modifico solo l’ultimo layer

Domande

per come e’ scritto il codice sembra che in varie epoche i modelli siano diversi, non ci sia un aggiornamento continuo. In pratica fa una deepcopy del miglior modello (ma io dovrei salvare, altrimenti perdo tutto e devo rifare)

Non riesco a trovare il modello!? dove lo ha caricato? Non lo ha ancora caricato!

  • model.train() metodo che fa il training?

Tempo

  • se freezo il train tranne l’ultimo layer ci mette 1:42 s
  • se re-train tutto il modello ci ha messo circa 2:14 s

(ricorda che partiva gia’ trainato)

Train() e Eval()

sono due metodi associati al modello che precedentemente non avevo usato

# prendo e modifico il codice di prima secondo quanto scritto nella lezione 14
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.optim import lr_scheduler  # nuovo non ancora  caricato prima
import torchvision
#import torchvision.transforms as transforms
from torchvision import datasets, models, transforms  # 
import matplotlib.pyplot as plt
import time                      # per controllare l'orario
import os                        # per interagire con l'os
import copy                      # per fare una deep copy del modello che e' un oggetto complesso (non vogliamo shallow copy)
import sys                       # per bloccare (c'e' una funzione tipo  stop del fortran)
%matplotlib inline

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

mean = np.array([0.485, 0.456, 0.406])   # queste non so come sono state scelte
std = np.array([0.229, 0.224, 0.225])    # idem


# Hyper parametri 
input_size = 32*32    #  =1024 sono le dimensioni delle immagini
num_classes = 10      #  devo classificare immagini di numeri
num_epochs = 6        #  quanti giri completi vengono fatti
batch_size = 4        #  questo no so come sia stato scelto
learning_rate = 0.001 #  piccolo
hidden_size1  = 220   #  scelto da me
hidden_size2  = 184   #  scelto da me 


# qui faccio un dizionario che contiene le trasformazioni da applicare
data_transforms = {
    'train': transforms.Compose([transforms.RandomResizedCrop(224), 
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean,std)])
    ,
    'val': transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize(mean,std)
    ])
}

# import data
data_dir = 'data/hymenoptera_data'         # dove ci sono le api? Imenotteri
sets  = ['train', 'val']                   # lista che contiene mi sa che poi non lo usa


# Questo sotto e' da pensare un momento
# e' una list comprehension (in un dictionary)
# dove in realta' ci sono 2 directory, poteva mettere il path... e forse scriveva meno.
# in ogni caso la parte che non mi e' chiarissima sono i : dopo la x
# no e' banale: e' un dictionary e la x sono le chiavi e le datasets.Image... sono i valori
# tutti ottenuti con una list comprehension finale
image_datasets= {x:datasets.ImageFolder( os.path.join(data_dir, x), data_transforms[x] )
                 for x in ['train', 'val'] }

dataloaders  = {x: torch.utils.data.DataLoader(image_datasets[x],
                    batch_size = batch_size,
                    shuffle= True,
                    num_workers= 0) for x in  ['train', 'val']}


dataset_sizes  = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names  = image_datasets['train'].classes      # .classes e' un attributo 

print(class_names)
#sys.exit()                      # per fermare qui


######### funzione che contiene il training loop  ##############

def train_model(model, criterion, optimizer, scheduler, num_epochs=25): 
    since = time.time()     # penso chen questo sia il momento d'inizio

    best_model_wts = copy.deepcopy(model.state_dict())  # fa una deep copy del modello
    best_acc = 0.0                                      # 
    
    for epoch in range(num_epochs):
        print(f'Epoch {epoch}/{num_epochs-1}')
        print('-'* 10)                             # disegna una riga orizzontale
        
        # In ogni epoca si ha una fase di training e validation:
        for phase in ['train', 'val']:
            if phase== 'train':
                model.train()              # set model to train mode  #######################
            else:
                model.eval()               # set model to evaluation mode
            
            running_loss = 0.0             # in qualche modo voglio vedere live se il training va bene
            running_corrects =0
            

            for inputs, labels in dataloaders[phase]:       # distinguo tra le fasi train/val
                inputs = inputs.to(device)
                labels = labels.to(device)
            
                ################  forward pass
                with torch.set_grad_enabled(phase == 'train'):
                    outputs =  model(inputs)
                    _, preds = torch.max(outputs, 1)
                    loss = criterion(outputs,labels)
            
                ################ backward pass
                    if phase == 'train':
                        optimizer.zero_grad()      # non mi seve quando faccio l'ottimizzazione
                        loss.backward()            # chain rule
                        optimizer.step()           # mi muovo in funzione del gradiente
                 
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds==labels.data)  
            
            if phase =='train':
                scheduler.step()    # =========================  questo aggiorna il learning_rate
            
            epoch_loss =running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]
            
            print(f'{phase} Loss: {epoch_loss:.4f}  Acc: {epoch_acc:.4f} ')

            # deep copy model
            
            if phase =='val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())   # questo copia il dizionario associato al miglior modello
        
        print()
    time_elapsed = time.time() -since
    print(f'Training completato in {time_elapsed//60:.0f}  minuti {time_elapsed%60:.0f}s ')
           
    model.load_state_dict(best_model_wts)    
    return model    
        
######### importiamo il MODELLO ############### 

model = models.resnet18(pretrained=True )       # qui il modello e' importato da torchvision e lo mette nella cache

# se voglio bloccare tutti i parametri tranne quelli dell'ultimo layer basta che 
# li blocco:!

for param in model.parameters():
    param.requires_grad = False     # non fa il gradiente!

num_ftrs = model.fc.in_features                 # ho preso il layer fully connected (ultimo) e queste sono le input features?

# ora creo un nuovo layer e lo assegno come ultimo layer. 
# come faccio a sapere che l'ultimo layer si chiama `fc`?

model.fc = nn.Linear(num_ftrs, 2)  # ho solo 2 classi in uscita.  DI DEFAULT ha requires_grad = True
model.to(device)                    

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001 )   # occhio che lui aveva importato in modo diverso e chiama solo optim.SGD

############ SCHEDULER che fa una update del lr #######

step_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1) # ogni 7 epoch lr =lr*gamma (diventa 1/10)

#for epoch in range(100):
#    train()   # optimizer.step()
#    evaluate()
#    scheduler.Step()
    
model = train_model(model, criterion, optimizer, step_lr_scheduler, num_epochs=20)

# qui alla fine ho trovato il modello ottimale, e potrei salvarlo volendo.

['ants', 'bees']
Epoch 0/19
----------
train Loss: 0.6456  Acc: 0.6230 
val Loss: 0.5187  Acc: 0.8039 

Epoch 1/19
----------
train Loss: 0.5606  Acc: 0.7049 
val Loss: 0.4390  Acc: 0.8627 

Epoch 2/19
----------
train Loss: 0.5181  Acc: 0.7623 
val Loss: 0.4065  Acc: 0.8301 

Epoch 3/19
----------
train Loss: 0.4886  Acc: 0.7910 
val Loss: 0.3304  Acc: 0.8954 

Epoch 4/19
----------
train Loss: 0.4584  Acc: 0.8197 
val Loss: 0.3134  Acc: 0.8954 

Epoch 5/19
----------
train Loss: 0.4822  Acc: 0.7623 
val Loss: 0.2991  Acc: 0.9085 

Epoch 6/19
----------
train Loss: 0.4221  Acc: 0.8115 
val Loss: 0.2734  Acc: 0.9216 

Epoch 7/19
----------
train Loss: 0.4034  Acc: 0.8156 
val Loss: 0.2741  Acc: 0.9216 

Epoch 8/19
----------
train Loss: 0.3629  Acc: 0.8852 
val Loss: 0.2734  Acc: 0.9216 

Epoch 9/19
----------
train Loss: 0.4139  Acc: 0.8197 
val Loss: 0.2570  Acc: 0.9216 

Epoch 10/19
----------
train Loss: 0.4058  Acc: 0.8402 
val Loss: 0.2794  Acc: 0.9085 

Epoch 11/19
----------
train Loss: 0.4000  Acc: 0.8238 
val Loss: 0.2611  Acc: 0.9346 

Epoch 12/19
----------
train Loss: 0.4265  Acc: 0.8156 
val Loss: 0.2510  Acc: 0.9346 

Epoch 13/19
----------
train Loss: 0.3911  Acc: 0.8648 
val Loss: 0.2729  Acc: 0.9085 

Epoch 14/19
----------
train Loss: 0.4748  Acc: 0.7787 
val Loss: 0.2668  Acc: 0.9216 

Epoch 15/19
----------
train Loss: 0.3790  Acc: 0.8443 
val Loss: 0.2865  Acc: 0.9020 

Epoch 16/19
----------
train Loss: 0.3815  Acc: 0.8730 
val Loss: 0.2611  Acc: 0.9412 

Epoch 17/19
----------
train Loss: 0.3946  Acc: 0.8197 
val Loss: 0.2722  Acc: 0.9020 

Epoch 18/19
----------
train Loss: 0.4166  Acc: 0.8525 
val Loss: 0.2828  Acc: 0.9085 

Epoch 19/19
----------
train Loss: 0.3790  Acc: 0.8484 
val Loss: 0.2649  Acc: 0.9216 

Training completato in 1  minuti 42s 

Tensorboard

https://pytorch.org/docs/stable/tensorboard.html

installazione, io ho usato conda: conda install -c conda-forge/label/cf202003 tensorboard

Tensorboard e’ un metodo per visualizzare tramite delle dashboard visibili da browser:

  • il grafo computazionale
  • l’evoluzione dei parametri (Loss, accuracy, ecc…)
  • nota che e’ sviluppato per Tensorflow (ma funge anche con Pytorch)
  • visualizzare istogrammi dei pesi e dei bias
  • visualizzare immagini, testi, audio (eh si lavora anche con quelli)
  • fare un profiling dei programmi di TensorFlow
  • Project embeddings in lower dimensional spaces ?????

Viene usato il codice del tutorial numero 13 (il riconoscimento delle immagini dei numeri da 0 a 9). Da quel codice ho tolto quasi tutti i commenti in modo che i commenti rimanenti siano solo per l’utilizzo di tensorboard

  • Quando si lancia Tensorboard bisogna specificare il path dove verranno salvati i logfile. Di default vengono messi nella directory chiamata run (suppongo che sia una sottodir dell’installazione di Tensorboard

  • per fare partire Tensorboad (dalla dir dove si e’ lanciato il Python):
    tensorboard --logdir=runs
    a questo punto si apre un browser alla pagina:
    http://localhost:6006

(CTRL+C = quit)

  • La prima cosa che si fa e’ essenzialmente istanziare un writer: writer=SummaryWriter('runs/mnist') (riga 12). Il writer in pratica scrive dei valori in formato JSON (controllare) con specifiche che tensorboard va a leggere nella dir runs e, note le chiavi le mette nella dashboard.

In pratica a quanto capisco, il writer scrive i dati in un formato particolare che viene poi letto da tensorboard e mandato nella dashboard al localhost:6006. Questo e’ il motivo per cui l’oggetto fondamentale e’ un writer che ha dei vari metodi in grado di scrivere le informazioni dei vari oggetti in modo che Tensorboard le interpreti correttamente.

I Esempio di utilizzo: visualizzo i dati nel browser, consiste di 3 passi (piu’ l’istanza del writer appena fatta):

  1. Costruisco una griglia di immagini img_grid = torchvision.utils.make_grid(example_data) (usando le utility di torchvision)
  2. Do in pasto la griglia al writer, tramite il metodo add_image: writer.add_image('Immagini di Mnist', img_grid), il primo argomento una label per la image grid, il secondo e’ la griglia stessa
  3. uso il metodo writer.close() per assicurarmi che tutti i dati siano mandati

II Esempio di utilizzo: visualizzo il grafo computazionale. Consiste di 2 passi.

  1. uso il metodo writer.add_graph(model, examples_data.reshape(-1,28*28).to(device)),quindi il primo argomento e’ il modello e il secondo sono ancora gli esempio (che ho flattenato). Attento, dato che sto mettendo tutto sul device, devo farlo anche per quanto riguarda examples_data, nel video non viene fatto! (questo perche’ il notebook usato non ha una scheda grafica dedicata e quindi sono comunque tutti sull’host.
  2. chiudo il writer per assicurarmi che tutti i dati vengano passati: writer.close()

Nota che dopo avere scritto l’esempio II, appare una seconda scelta in tensorboard (in alto), chiamata graph. Si vede quindi il grafo computazionale che viene visualizzato. Con dei doppi click si aprono nel dettaglio i grafi!

III Esempio di utilizzo: mando la loss e la accuracy durante l’esecuzione. Consiste di 2 passi. Vogliamo la loss media durante il training, quindi aggiungo delle variabili al mdoello.

  1. uso un nuovo metodo: writer.add_scalar('Training loss', running_loss/100, epoch* n_total_steps + i) Nota che ho definito prima delle nuove quantita’ da mandare al writer tramite add_scalar

Attento se continuo a fare girare la rete neurale nel notebook, i dati in Tensorboard si accumulano a quelli dei run precedenti. Devo trovare un modo per azzerare i dati. Soluzione:
devi rinominare la dir dove vengono salvati i dati per ogni run diverso. In questo modo si avranno delle righe di colore diverso per ogni run. Altrimenti provo ad andare a cancellare il contenuto della dir runs/mnist (occhio che e’ una cartella che viene creata nella stessa dir di dove gira il python!) (magari esiste un metodo migliore devo investigare). Ho cancellato tutto ma non funge… ok funge, li teneva in memoria! ho riavviato tensorboard ed e’ sparito tutto. La cosa curiosa e’ che aprendo gli eventi sembrano vuoti… Solo il primo contiene molti valori, il resto sono gli incrementi sul primo direi.

Attento quando ci sono degli scalari (dei grafici) di default Tensorboard aggiunge una linea di smoothing (e’ nella modale(?) subito a sinistra).

IV Eempio di utilizzo: aggiungere una precision e recall curve (riguarda le note sulla confusoin matrix per le definizioni esatte). Esiste un metodo apposta per aggiungere la precision. Guarda il link: pytorch.org/docs/stable/tensorboard.html

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

from torch.utils.tensorboard import SummaryWriter   ##############  TENSORBOARD
import sys 
import torch.nn.functional as F

##### costruisco un writer ####################
writer  =SummaryWriter('runs/mnist') # come argomento serve la dir dove vanno salvati i file
#####    fine writer   ########################


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

input_size = 28*28    
hidden_size  = 100    
num_classes = 10      
num_epochs = 2        
batch_size = 100      
learning_rate = 0.001 

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) # 

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)

examples = iter(test_loader)
examples_data, examples_targets = examples.next()
#for i in range(6):
#    plt.subplot(2,3,i+1)
#    plt.imshow(example_data[i][0], cmap='gray')

    
img_grid  = torchvision.utils.make_grid(examples_data)
writer.add_image('Immagini di Mnist' , img_grid)    
writer.close()     # questo assicura che tutti gli output sono flushati
#sys.exit()    # per non dover fare tutto il training    

############  MODELLO ####################
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)  
        self.relu = nn.ReLU()
        self.l2 = nn.Linear(hidden_size, num_classes)
       
    def forward (self, x):
        out = self.l1(x)       
        out = self.relu(out)   
        out = self.l2(out)
        return out             

############### ISTANZIO MODELLO, Back e Forw ########
model = NeuralNet(input_size, hidden_size, num_classes).to(device)
criterion  = nn.CrossEntropyLoss()   # non ha bisogno di parametri
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)        


######  Aggiungo un altro grafo alla dashboard di Tensorboard,
###### ora uso il metodo     add_graph   (prima ho usato add_image)
writer.add_graph(model, examples_data.reshape(-1,28*28).to(device))
writer.close()
#sys.exit()

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


running_loss = 0.0      # questo e' il valore aggiornato in tempo reale
running_correct = 0     # idem

for epoch in range(num_epochs):                           
   for i, (images,labels) in enumerate (train_loader):         
        images = images.reshape(-1, 28*28).to(device)     
        labels = labels.to(device)
              
        outputs = model (images)                           
        loss = criterion(outputs, labels)
                 
        optimizer.zero_grad()     
        loss.backward()            
        optimizer.step()          
        
################ da mandare a TENSORBOARD ####################        
        running_loss += loss.item()  # aggiorno il totale
        _, predicted  =torch.max(outputs.data, 1)
        running_correct += (predicted == labels).sum().item()
################ ##################### ########################        
        
        
        if (i+1) % 100 ==0:
            #print(f'epoch {epoch+1} / {num_epochs}, step {i+1}/{n_total_steps}, loss= {loss.item():.4f}')        

############## qui aggiungo alla dashboard un nuovo oggetto TENSORBOARD ######
            writer.add_scalar('Training loss', running_loss/100, epoch* n_total_steps + i)
            writer.add_scalar('Accuracy', running_correct/100, epoch* n_total_steps + i) 
            running_loss = 0.0     # riazzero
            running_correct = 0    # riazzero
            writer.close()
######################################################            
            
            

        
        
        
################ per Tensorboard ########################
labels2 = []   #occhio che aveva gia' definito labels sotto, ho messo un 2 Tensorboard
preds = []
#########################################################
        
############ TEST LOOP #################
with torch.no_grad():    
    n_correct = 0         
    n_samples = 0           
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        
        outputs = model(images)    
        
        _, predictions = torch.max(outputs,1)  
        n_samples += labels.shape[0]           
        n_correct += (predictions == labels).sum().item()
        
        class_predictions = [F.softmax(output, dim=0) for output in outputs]  # ho bisogno di probabilita', quindi serve softmax
        preds.append(class_predictions)
        labels2.append(predicted)   # per TENSORBOARD
        
    preds = torch.cat ([torch.stack(batch) for batch in preds ]) # tensorboard 2D oggetto 1000, 1
    labels2 = torch.cat(labels2)   # Tensorboard concateno le liste in un oggetto 1D        1000
    
    classes = range(10)   # tutte le possibili classi  0-9  Tensorboard
    for i in classes:
        labels_i = labels2 == i    # Tensorboard    non chiaro cosa fa
        preds_i = preds[:,i]      # Tensorboard 
        writer.add_pr_curve(str(i), labels_i, preds_i, global_step =0) # TEnsorboard
        writer.close()       # chiudo il writer
    
    
    acc = 100.0  * n_correct / n_samples       
    print(f'accuracy ={acc}')            
accuracy =95.03

Top