Un esempio pratico di utilizzo di PyTorch
Questo esempio spiega come utilizzare una rete neurale tramite PyTorch per riconoscere il contenuto di una immagine.
Dove PyTorch è una libreria open source di apprendimento automatico (machine learning) del linguaggio Python. È ampiamente utilizzata per applicazioni come il deep learning e l'elaborazione del linguaggio naturale (NLP) tramite le reti neurali.
Per prima cosa occorre importare in Python un'immagine.
- import urllib.request
- url = 'https://upload.wikimedia.org/wikipedia/commons/9/9e/Ginger_european_cat.jpg'
- fpath = 'immagine.jpg'
- urllib.request.urlretrieve(url, fpath)
In questo caso si tratta di una foto di un gatto tratta da Wikipedia.
Qualsiasi altra foto può comunque andare bene.

Poi importiamo la libreria PIL (Python Imaging Library), conosciuta anche come Pillow, una libreria molto utile per aprire, manipolare e salvare un file in molti formati immagine differenti.
Al suo interno troviamo la funzione Image.open() che ci serve a caricare un'immagine dal file system in un oggetto immagine che può poi essere manipolato o visualizzato nel codice Python.
- from PIL import Image
- img = Image.open('immagine.jpg')
L'immagine ora risiede nella variabile img.
Adesso occorre preparare la foto ad essere processata dalla rete neurale (NN o Neural Network).
La preelaborazione (o preprocessing) è una delle fasi forse più noiose ma essenziali del processo e non può essere trascurata.
In particolar modo bisogna trasformare l'immagine digitale in un tensore con determinate caratteristiche tramite torchvision.
- from torchvision import transforms
- transform = transforms.Compose([
- transforms.Resize(256),
- transforms.CenterCrop(224),
- transforms.ToTensor(),
- transforms.Normalize(
- mean=[0.485, 0.456, 0.406],
- std=[0.229, 0.224, 0.225]
- )
- ])
- img_tensor=transform(img)
Il metodo Compose adatta l'immagine alle esigenze della rete neurale e la trasforma in un tensore tramite toTensor(). Dove un tensore è un array multidimensionale.
La funzione Normalize(), invece, converte ogni pixel dell'immagine in valori compresi tra 0 e 1.
Perché è necessario trasformare l'immagine? Oltre a rendere compatibile l'immagine con il modello di classificazione, la normalizzazione rende molto più accurata l'attività di classificazione che andremo ad eseguire.
Ora l'immagine è un tensore ed è memorizzato nella variabile img_tensor.
Nella variabile img_tensor viene registrato un oggetto della classe torch.Tensor, un tensore, che ha 3 canali e una dimensione di 224x224 pixel.
print(type(img_tensor), img_tensor.shape)
<class 'torch.Tensor'> torch.Size([3, 224, 224])
Nell'apprendimento automatico, specialmente nel trattamento di immagini, è comune processare i dati in "batches" (lotti).
Un modello spesso si aspetta un batch di dati anziché un singolo elemento.
Tuttavia, se disponiamo solo di una immagine e vogliamo adattarla al modello, dobbiamo creare un batch di dimensione 1.
- batch=img_tensor.unsqueeze(0)
Qui, img_tensor è il tensore che rappresenta la nostra immagine.
Usando la funzione unsqueeze() di PyTorch, aggiungiamo una dimensione al tensore, trasformandolo in un batch di dimensione 1.
print(batch.shape)
Di conseguenza, la forma del tensore diventa 1 × 3 × 224 × 224,
torch.Size([1, 3, 224, 224])
Dove
- 1 rappresenta la dimensione del batch (una sola immagine)
- 3 sta per il numero di canali (RGB, quindi Rosso, Verde, Blu)
- 224 × 224 sono le dimensioni dell'immagine in pixel.
A questo punto carichiamo il modello che dovrà processare l'immagine.
- from torchvision import models
- model = models.alexnet(pretrained=True)
- device = "cuda" if torch.cuda.is_available() else "cpu"
Configuriamo il modello ad analizzare i dati memorizzati a lotti nell'oggetto "batch".
La funzione model.eval() configura il modello AlexNet per l'inferenza.
Le funzioni model.to(device) invia il modello alla GPU, se disponibile, oppure alla CPU per il calcolo.
Infine, la funzione model() esegue l'elaborazione.
- model.eval()
- model.to(device)
- y=model(batch.to(device))
L'output viene memorizzato nella variabile y
A questo punto visualizziamo la dimensione della risposta con y.shape.
print(y.shape)
torch.Size([1, 1000])
Il primo valore 1 indica che l'input consiste in un solo batch ovvero l'immagine.
L'output, invece, consiste in mille uscite (1000), ogni uscita equivale a una classe. Quindi, il modello ha assegnato un valore di probabilità per ogni classe.
Selezioniamo la risposta che ha ottenuto la maggiore probabilità.
y_max, index = torch.max(y,1)
Infine, visualizziamo la classe associata al risultato.
print(index, y_max)
tensor([285]) tensor([18.0390], grad_fn=<MaxBackward0>)
Secondo la rete neurale l'immagine rientra probabilmente nella classe 285
Non resta altro che vedere di cosa si tratta.
print(classes[285])
285: 'Egyptian cat'
Quindi, secondo la rete neurale nell'immagine c'è un gatto egiziano 'Egyptian cat'.
Per vedere tutte le classi del risultato, quelle con maggiore probabilità, occorre aggiungere al programma un ulteriore blocco di codice.
- url='https://raw.githubusercontent.com/joe-papa/pytorch-book/main/files/imagenet_class_labels.txt'
- fpath ='imagenet_class_labels.txt'
- urllib.request.urlretrieve(url, fpath)
- with open('imagenet_class_labels.txt') as f:
- classes = [line.strip() for line in f.readlines()]
- prob = torch.nn.functional.softmax(y, dim=1)[0]*100
- _, indices = torch.sort(y, descending=True)
- for idx in indices[0][:5]:
- print(classes[idx], prob[idx].item())
Questo codice legge tutte le etichette delle classi del classificatore.
Poi ordina le classi estrapolate dal modello in ordine decrescente di probabilità e le stampa.
Ecco il risultato finale:
285: 'Egyptian cat', 59.271602630615234
281: 'tabby, tabby cat', 30.12925148010254
904: 'window screen', 2.8015429973602295
478: 'carton', 2.376310110092163
282: 'tiger cat', 1.066372036933899
Al 59.2% l'immagine viene riconosciuta come un gatto egiziano 'Egyptian cat' e al 30.1% come un 'tabby, tabby cat'
Quindi, il modello riconosce nella foto un gatto al 89.3% ossia 59.2%+30.1%