top of page

Introduzione a PyTorch per principianti

Se sei interessato al deep learning, probabilmente hai già sentito parlare di TensorFlow e PyTorch, i due framework più famosi per questa tecnologia. Ma se sei alla ricerca di un'introduzione pratica al framework PyTorch, sei nel posto giusto.


In questo articolo, esploreremo PyTorch dal punto di vista della programmazione applicata, concentrandoci sull'addestramento del tuo primo modello di classificazione delle immagini. Non ci immergeremo in matematica complessa, ma piuttosto forniremo un approccio pratico per iniziare a utilizzare PyTorch come strumento.


Assumiamo che tu abbia già una conoscenza intermedia di Python, compresi i concetti di classi e programmazione orientata agli oggetti, e che tu sia familiare con le nozioni di base del deep learning. Non importa quale sia la tua esperienza con PyTorch, questo articolo ti darà gli strumenti necessari per creare modelli di deep learning efficaci.


Come sempre partiamo dalle basi per i principianti, COSA E' PYTHORCH ?


Introduzione a PyTorch per principianti
Introduzione a PyTorch per principianti

Introduzione a PyTorch : Cosa è ?

PyTorch è un framework open source di deep learning che viene utilizzato principalmente per lo sviluppo di modelli di apprendimento automatico. È stato creato da Facebook AI Research (FAIR) e si basa sul linguaggio di programmazione Python. PyTorch è noto per la sua facilità d'uso, flessibilità e velocità.


Una delle caratteristiche distintive di PyTorch è l'utilizzo di un'architettura dinamica dei grafi computazionali. Ciò significa che, a differenza di altri framework come TensorFlow, PyTorch consente agli utenti di definire e modificare i grafi computazionali durante l'esecuzione del codice, invece di doverli definire in modo statico prima dell'esecuzione.


Ciò rende PyTorch particolarmente utile per la sperimentazione rapida e l'ottimizzazione dei modelli di deep learning, poiché consente agli utenti di iterare rapidamente e testare diverse architetture di rete. Inoltre, PyTorch offre anche un'ampia gamma di funzionalità per la gestione dei tensori, come ad esempio operazioni matematiche avanzate, operazioni di riduzione, broadcasting e indeces slicing.


Per installare PyTorch, è possibile utilizzare pip, il gestore di pacchetti Python predefinito. Tuttavia, è importante notare che PyTorch richiede l'utilizzo di una specifica versione di Python e di alcune dipendenze aggiuntive. Per installare PyTorch, è possibile seguire i seguenti passaggi:

  1. Apri un terminale (o un prompt dei comandi) sul tuo computer.

  2. Utilizza il seguente comando per installare PyTorch con supporto per la tua versione di Python: "pip install torch torchvision" Se stai usando una versione di Python differente da quella predefinita, potresti dover specificare il percorso dell'interprete Python corretto.

  3. Attendi il completamento dell'installazione. Questo potrebbe richiedere alcuni minuti, a seconda delle dimensioni del pacchetto e della velocità della tua connessione Internet.

Una volta installato PyTorch, puoi importare il pacchetto e iniziare a utilizzare le sue funzionalità all'interno del tuo codice Python.



Introduzione a PyTorch : Cosa sono i Tensori?

I tensori sono una struttura dati fondamentale nel deep learning, poiché consentono di eseguire operazioni matematiche su grandi insiemi di dati in modo efficiente. I tensori sono molto simili ad array e matrici, ma possono essere rappresentati come un vettore, uno scalare o un array di dimensioni superiori.


Un tensore può essere rappresentato come un semplice array contenente scalari o altri array. Su PyTorch, i tensori sono una struttura molto simile a un ndarray, con la differenza che possono funzionare su una GPU, il che accelera notevolmente il processo computazionale.

I tensori possono essere creati utilizzando la libreria PyTorch. Ecco un esempio di come creare un tensore in PyTorch:

import torch

# Creazione di un tensore vuoto di dimensioni 3x3
tensor_vuoto = torch.empty(3, 3)

# Creazione di un tensore di interi casuali di dimensioni 2x2
tensor_interi = torch.randint(10, (2, 2))

# Creazione di un tensore di float di dimensioni 1x3
tensor_float = torch.tensor([1.0, 2.0, 3.0])

In questo esempio, abbiamo creato tre tensori di dimensioni diverse utilizzando PyTorch. Il primo tensore tensor_vuoto è stato creato come un tensore vuoto di dimensioni 3x3 utilizzando la funzione torch.empty(). Il secondo tensore tensor_interi è stato creato come un tensore di interi casuali di dimensioni 2x2 utilizzando la funzione torch.randint(). Infine, il terzo tensore tensor_float è stato creato come un tensore di float di dimensioni 1x3 utilizzando la funzione torch.tensor().


È possibile eseguire diverse operazioni sui tensori. Ecco alcuni esempi di come eseguire operazioni aritmetiche sui tensori in PyTorch:

import torch

# Creazione di due tensori di dimensioni 2x2
tensor_1 = torch.tensor([[1, 2], [3, 4]])
tensor_2 = torch.tensor([[5, 6], [7, 8]])

# Somma dei due tensori
tensor_sum = tensor_1 + tensor_2

# Prodotto dei due tensori
tensor_product = torch.mm(tensor_1, tensor_2)

# Stampa dei risultati
print("Somma dei tensori:")
print(tensor_sum)
print("Prodotto dei tensori:")
print(tensor_product)

In questo esempio, abbiamo creato due tensori tensor_1 e tensor_2 di dimensioni 2x2 utilizzando la funzione torch.tensor(). Successivamente, abbiamo eseguito la somma dei due tensori utilizzando l'operatore + e il prodotto utilizzando la funzione torch.mm(). Infine, abbiamo stampato i risultati utilizzando la funzione print().


Oltre alla creazione e manipolazione dei tensori, PyTorch offre anche molte funzionalità utili per il deep learning. Di seguito, ne vedremo alcune importanti.


AutoGrad di Pytorch

Una delle funzionalità più utili di PyTorch è il sistema di calcolo differenziale automatico, chiamato AutoGrad. Questo sistema permette di calcolare automaticamente i gradienti di una funzione rispetto ai suoi parametri, senza doverli calcolare manualmente. In pratica, ciò significa che PyTorch può essere utilizzato per implementare algoritmi di apprendimento automatico che richiedono la minimizzazione di una funzione obiettivo attraverso la discesa del gradiente.


Di seguito, vediamo un esempio di come usare il modulo di autograd per calcolare i gradienti di una funzione.



import torch

x = torch.tensor(2.0, requires_grad=True)
y = x**2 + 3*x + 1# Calcoliamo i gradienti rispetto a x
y.backward()

# Ora possiamo accedere al gradienteprint(x.grad)

In questo esempio, abbiamo creato un tensore x con il valore 2.0 e impostato il parametro requires_grad=True, il che indica a PyTorch di tracciare il calcolo dei gradienti rispetto a x. Poi abbiamo definito una funzione y come una combinazione di operazioni tensoriali, e abbiamo chiamato il metodo backward() su y per calcolare i gradienti rispetto a tutti i tensori che hanno requires_grad=True. Infine, abbiamo acceduto al gradiente di x con la proprietà grad del tensore.


Moduli e ottimizzatori di Pytorch

PyTorch offre anche una vasta gamma di moduli predefiniti per la costruzione di reti neurali. Ogni modulo rappresenta un layer della rete, ad esempio un layer convoluzionale o un layer completamente connesso. Questi moduli possono essere combinati per creare reti neurali complesse.

Ad esempio, possiamo creare una rete neurale semplice con un layer lineare come segue:


import torch
import torch.nn as nn

# Definiamo la reteclass SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.linear = nn.Linear(10, 1)

    def forward(self, x):
        out = self.linear(x)
        return out

# Creiamo un'istanza della rete
net = SimpleNet()

# Definiamo la funzione obiettivo e l'ottimizzatore
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.01)

# Addestramento della rete
for i in range(100):
    # Generiamo dei dati casuali
    x = torch.randn(10, 10)
    y = torch.randn(10, 1)

    # Azzeriamo i gradienti
    optimizer.zero_grad()

    # Calcoliamo l'output della rete
    output = net(x)

    # Calcoliamo la loss
    loss = criterion(output, y)

    # Calcoliamo i gradienti
    loss.backward()

    # Aggiorniamo i pesi
    optimizer.step()

Queste operazioni sono solo una frazione di ciò che PyTorch può fare. Tuttavia, lo scopo di questo articolo non è coprire ciascuno di essi, ma dare un'idea generale di come funzionano. Se vuoi saperne di più, PyTorch ha una documentazione completa .



Introduzione a PyTorch per principianti
Introduzione a PyTorch per principianti

Introduzione a PyTorch , il tuo primo classificiatore di Immagini step by step

PyTorch viene fornito con un modulo integrato che fornisce set di dati pronti all'uso per molte applicazioni di deep learning, come la visione artificiale, il riconoscimento vocale e l'elaborazione del linguaggio naturale. Ciò significa che è possibile costruire la propria rete neurale senza la necessità di raccogliere ed elaborare i dati autonomamente.


Ad esempio, scaricheremo il set di dati MNIST. Il MNIST è un dataset di immagini di cifre scritte a mano, contenente 60mila campioni e un set di test di 10mila immagini.



Useremo il modulo datasets da torchvision per scaricare i dati:


from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
import torch

training_data = datasets.MNIST(root=".", train=True, download=True, transform=ToTensor())
test_data = datasets.MNIST(root=".", train=False, download=True, transform=ToTensor())

All'interno della funzione di download, abbiamo i seguenti parametri:

  1. root : la directory in cui verranno salvati i dati. Puoi passare una stringa con il percorso della directory. Un punto (come mostrato nell'esempio) salverà i file nella stessa directory in cui ti trovi.

  2. train : utilizzato per informare PyTorch se stai scaricando il treno o il set di test.

  3. download : se scaricare i dati se non sono già disponibili nel percorso specificato.

  4. transform : trasformare i dati. Nel nostro codice, selezioniamo tensore.

Se stampiamo il primo elemento del treno, vedremo quanto segue:

training_data[0]

(tensor([[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000], .......... .............. ................... ............. [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,0.0000, 0.0000, 0.0000, 0.0000],]]]))


Il tensore sopra è solo una piccola parte dell'intero elemento, poiché sarebbe troppo grande per essere visualizzato.


Questo gruppo di numeri potrebbe non significare nulla per noi e poiché rappresentano immagini, possiamo utilizzare matplotlib per visualizzarli come immagini reali:

figure = plt.figure(figsize=(8, 8))
cols, rows = 5, 5
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()


Introduzione a PyTorch per principianti
Introduzione a PyTorch per principianti




Possiamo anche usare l' attributo classes per vedere le classi all'interno dei dati:

training_data.classes
['0 - zero','1 - one','2 - two','3 - three','4 - four','5 - five','6 - six','7 - seven','8 - eight','9 - nine']

Quando il modello viene addestrato, può ricevere nuovi input, quindi classificarsi come una di queste classi.


Ora che abbiamo scaricato i dati, utilizzeremo il file DataLoader. Ciò ci consente di scorrere il set di dati in mini-batch invece di un'osservazione alla volta e di mescolare i dati durante l'addestramento dei modelli. Ecco il codice:

from torch.utils.data import DataLoader

loaded_train = DataLoader(training_data, batch_size=64, shuffle=True)

loaded_test = DataLoader(test_data, batch_size=64, shuffle=True)


Creiamo la rete neurale con Pythorch

Nel deep learning, una rete neurale è un tipo di algoritmo utilizzato per modellare i dati con schemi complessi. Una rete neurale tenta di simulare il funzionamento del cervello umano attraverso più strati collegati da nodi di elaborazione, che si comportano come i neuroni umani. Questi livelli collegati da nodi creano una rete complessa in grado di elaborare e comprendere enormi quantità di dati complessi.


In PyTorch, tutto ciò che riguarda le reti neurali viene creato utilizzando il torch.nnmodulo. La rete stessa è scritta come una classe che eredita da nn.Modulee, all'interno della classe, la useremo nnper costruire i livelli. Quella che segue è una semplice implementazione presa dalla documentazione di PyTorch :

from torch import nn

class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

Sebbene non rientri nell'ambito di questo articolo approfondire cosa sono i livelli, come funzionano e come implementarli, facciamo un rapido tuffo in ciò che fa il codice sopra.

  • È nn.Flaten il responsabile della trasformazione dei dati da multidimensionali a una sola dimensione.

  • nn.Sequential è il contenitore che crea una sequenza di livelli all'interno della rete.

  • All'interno del contenitore, abbiamo i livelli. Ogni tipo di livello trasforma i dati in modo diverso e ci sono numerosi modi per implementare i livelli in una rete neurale.

  • La funzione forward è la funzione chiamata quando il modello viene eseguito; tuttavia, non dovremmo chiamarlo direttamente.

La riga seguente istanzia il nostro modello:

model = NeuralNetwork()
print(model)
NeuralNetwork(
(flatten): Flatten(start_dim=1, end_dim=-1)
(linear_relu_stack): 
Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Linear(in_features=512, out_features=512, bias=True)
(3): ReLU()
(4): Linear(in_features=512, out_features=10, bias=True)))


Allenare la rete neurale con Pythorch

Ora che abbiamo definito la nostra rete neurale, possiamo utilizzarla. Prima di iniziare l'allenamento, dovremmo prima impostare una funzione di perdita.


La funzione di perdita misura quanto è lontano il nostro modello dai risultati corretti ed è ciò che cercheremo di minimizzare durante l'addestramento della rete. L'entropia incrociata è una funzione di perdita comune utilizzata per compiti di classificazione, ed è quella che useremo. Dovremmo inizializzare la funzione:

loss_function = nn.CrossEntropyLoss()

Un ultimo passaggio prima dell'addestramento consiste nell'impostare un algoritmo di ottimizzazione. Tale algoritmo sarà incaricato di aggiustare il modello durante il processo di addestramento al fine di minimizzare l'errore misurato dalla funzione di perdita che abbiamo scelto sopra. Una scelta comune per questo tipo di attività è l'algoritmo di discesa del gradiente stocastico. PyTorch, tuttavia, ha molte altre possibilità con cui puoi familiarizzare qui . Di seguito il codice:

optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

Il lrparametro è il tasso di apprendimento, che rappresenta la velocità con cui i parametri del modello verranno aggiornati durante ogni iterazione del training.


Infine, è il momento di addestrare e testare la rete. Per ognuna di queste attività, implementeremo una funzione. La funzione train consiste nell'eseguire il ciclo dei dati un batch alla volta, utilizzando l'ottimizzatore per regolare il modello e calcolando la previsione e la perdita. Questa è l'implementazione standard di PyTorch:


def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    for batch, (X, y) in enumerate(dataloader):
        pred = model(X)
        loss = loss_fn(pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if batch % 1000 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

Si noti che per ogni iterazione otteniamo i dati per alimentare il modello, ma teniamo anche traccia del numero del batch in modo da poter stampare la perdita e il batch corrente ogni 100 iterazioni.


E poi abbiamo la funzione di test, che calcola l'accuratezza e la perdita, questa volta utilizzando il set di test:


def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

Quindi impostiamo il numero di epoche per addestrare il nostro modello. Un'epoca consiste in un'iterazione sul set di dati. Ad esempio, se impostiamo epochs=5 , significa che esamineremo l'intero set di dati 5 volte con l'addestramento e il test della rete neurale. Più ci alleniamo, migliori saranno i risultati.


Questa è l'implementazione di PyTorch e l'output di tale ciclo:


epochs = 5
for t in range(epochs):
print(f"Epoch {t+1}\n-------------------------------")
    train(loaded_train, model, loss_function, optimizer)
    test(loaded_test, model, loss_function)
print("Done!")
Epoch 1-------------------------------
loss: 2.296232  [    0/60000]
Test Error:
 Accuracy: 47.3%, Avg loss: 2.254638
Epoch 2-------------------------------
loss: 2.260034  [    0/60000]
Test Error:
 Accuracy: 63.2%, Avg loss: 2.183432
Epoch 3-------------------------------
loss: 2.173747  [    0/60000]
Test Error:
 Accuracy: 66.9%, Avg loss: 2.062604
Epoch 4-------------------------------
loss: 2.078938  [    0/60000]
Test Error:
 Accuracy: 72.4%, Avg loss: 1.859960
Epoch 5-------------------------------
loss: 1.871736  [    0/60000]
Test Error:
 Accuracy: 75.8%, Avg loss: 1.562622
Done!

Si noti che in ogni epoca, stampiamo la funzione di perdita ogni 100 batch nel ciclo di addestramento e continua a diminuire. Inoltre, dopo ogni epoca, possiamo vedere che la precisione aumenta man mano che la perdita media diminuisce.


Se avessimo impostato più epoche, diciamo 10, 50 o anche 100, è probabile che vedremmo risultati ancora migliori, ma i risultati sarebbero molto più lunghi e molto più difficili da visualizzare e comprendere.


Con il nostro modello finalmente addestrato, è facile salvarlo e caricarlo quando necessario:

torch.save(model, "model.pth")
model = torch.load("model.pth")



Conclusione su Pytorch

In conclusione, abbiamo visto che PyTorch è uno dei framework più utilizzati per il deep learning, grazie alla sua semplicità d'uso e alla sua efficacia nel calcolo su GPU. Abbiamo appreso come utilizzare i tensori per rappresentare i dati, come preparare i dati per l'addestramento del modello, come definire una rete neurale e come addestrarla per la classificazione delle immagini. Inoltre, abbiamo visto come salvare e caricare i modelli per l'utilizzo futuro. Con queste conoscenze di base, siamo pronti per esplorare ulteriormente il mondo del deep learning con PyTorch. Presto seguiranno nuovi tutorial :)


Condividi l'articolo per supportarci 🎁

Se vuoi scaricare gratis il codice del progetto, clicca quì !












1 Comment

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Guest
Mar 06, 2023

Non so come fate a pubblicare tutti questi articoli, così interessanti e con questa frequenza. Ma devo dire che si percepisce quanta qualità c'è dietro ogni lavoro che fate. Ormai sono stupefatto da quante informazioni si possono trovare su questo sito, dagli articoli, alle notizie, ai progetti, ai dataset veramente un patrimonio ITALIANO.


Complimenti a chiunque gestisce tale impero ❤

Like
PCR (5).gif
PCR (4).gif
PCR.gif
PCR.gif