Corso
Immagina di essere allo zoo e di dover riconoscere se un certo animale è un ghepardo o un leopardo. Come essere umano, il tuo cervello può analizzare senza sforzo caratteristiche del corpo e del viso per arrivare a una conclusione valida. Allo stesso modo, le Convolutional Neural Networks (CNN) possono essere addestrate a svolgere lo stesso compito di riconoscimento, indipendentemente da quanto complessi siano i pattern. Questo le rende potenti nel campo della computer vision.
Questo tutorial concettuale sulle CNN inizierà fornendo una panoramica su cosa sono le CNN e sulla loro importanza nel machine learning. Poi ti guiderà in un’implementazione passo passo di una CNN nel framework TensorFlow 2.
Che cos’è una CNN?
Una Convolutional Neural Network (CNN o ConvNet) è un algoritmo di deep learning progettato specificamente per qualsiasi compito in cui il riconoscimento di oggetti è cruciale, come classificazione, rilevamento e segmentazione di immagini. Molte applicazioni reali, come auto a guida autonoma, telecamere di sorveglianza e altro, usano le CNN.
L’importanza delle CNN
Ci sono diversi motivi per cui le CNN sono importanti, evidenziati di seguito:
- A differenza dei modelli tradizionali di machine learning come SVM e alberi decisionali che richiedono l’estrazione manuale delle caratteristiche, le CNN possono eseguire l’estrazione automatica delle feature su larga scala, risultando efficienti.
- I layer di convoluzione rendono le CNN traslazionalmente invarianti, cioè in grado di riconoscere pattern dai dati ed estrarre feature indipendentemente dalla loro posizione, anche se l’immagine è ruotata, scalata o traslata.
- Numerosi modelli CNN preaddestrati come VGG-16, ResNet50, Inceptionv3 ed EfficientNet hanno raggiunto risultati allo stato dell’arte e possono essere adattati a nuovi compiti usando una quantità relativamente ridotta di dati.
- Le CNN possono essere usate anche per problemi di classificazione non di immagini e non si limitano all’elaborazione del linguaggio naturale, all’analisi di serie temporali e al riconoscimento vocale.
Architettura di una CNN
L’architettura delle CNN cerca di imitare la struttura dei neuroni nel sistema visivo umano, composta da più layer, ciascuno responsabile del rilevamento di una specifica caratteristica nei dati. Come illustrato nell’immagine qui sotto, una tipica CNN è formata da una combinazione di quattro layer principali:
- Layer di convoluzione
- Rectified Linear Unit (in breve ReLU)
- Layer di pooling
- Layer completamente connessi
Vediamo come funziona ciascuno di questi layer usando il seguente esempio di classificazione delle cifre scritte a mano.

Layer di convoluzione
Questo è il primo mattone di una CNN. Come suggerisce il nome, il principale compito matematico svolto è chiamato convoluzione, ovvero l’applicazione di una funzione a finestra scorrevole a una matrice di pixel che rappresenta un’immagine. La funzione scorrevole applicata alla matrice si chiama kernel o filtro, e i due termini possono essere usati in modo intercambiabile.
Nel layer di convoluzione si applicano diversi filtri di uguali dimensioni, e ciascun filtro viene usato per riconoscere uno specifico pattern dall’immagine, come la curvatura delle cifre, i bordi, l’intera forma delle cifre e altro.
Consideriamo questa immagine in scala di grigi 32x32 di una cifra scritta a mano. I valori nella matrice sono forniti a scopo illustrativo.
![]()
Consideriamo anche il kernel usato per la convoluzione. È una matrice con dimensione 3x3. I pesi di ciascun elemento del kernel sono rappresentati nella griglia. I pesi zero sono rappresentati nelle celle nere e gli uno nelle celle bianche.
Dobbiamo trovare manualmente questi pesi?
Nella pratica, i pesi dei kernel vengono determinati durante il processo di addestramento della rete neurale.
Usando queste due matrici, possiamo eseguire l’operazione di convoluzione applicando il prodotto scalare, e procedere come segue:
- Applica la matrice kernel dall’angolo in alto a sinistra verso destra.
- Esegui la moltiplicazione elemento per elemento.
- Somma i valori dei prodotti.
- Il valore risultante corrisponde al primo valore (angolo in alto a sinistra) nella matrice convoluta.
- Sposta il kernel verso il basso in base alla dimensione della finestra scorrevole.
- Ripeti dal punto 1 al 5 finché l’intera matrice dell’immagine non è coperta.
La dimensione della matrice convoluta dipende dalla dimensione della finestra scorrevole. Più grande è la finestra, più piccola sarà la dimensione.

Un altro nome associato al kernel in letteratura è rilevatore di feature, perché i pesi possono essere ottimizzati per rilevare specifiche caratteristiche nell’immagine di input.
Per esempio:
- Un kernel che fa la media dei pixel adiacenti può essere usato per sfocare l’immagine di input.
- Un kernel che sottrae i vicini è usato per eseguire il rilevamento dei bordi.
Più layer di convoluzione ha la rete, meglio i layer riescono a rilevare feature via via più astratte.
Funzione di attivazione
Dopo ogni operazione di convoluzione si applica una funzione di attivazione ReLU. Questa funzione aiuta la rete a imparare relazioni non lineari tra le feature nell’immagine, rendendo la rete più robusta nell’identificare pattern diversi. Aiuta anche a mitigare il problema del vanishing gradient.
Layer di pooling
L’obiettivo del layer di pooling è estrarre le feature più significative dalla matrice convoluta. Questo si ottiene applicando alcune operazioni di aggregazione, che riducono la dimensione della feature map (matrice convoluta), riducendo così la memoria usata durante l’addestramento della rete. Il pooling è utile anche per mitigare l’overfitting.
Le funzioni di aggregazione più comuni che si possono applicare sono:
- Max pooling, che prende il valore massimo della feature map
- Sum pooling, che corrisponde alla somma di tutti i valori della feature map
- Average pooling, che è la media di tutti i valori.
Di seguito un’illustrazione di ciascuno dei precedenti esempi:

Inoltre, la dimensione della feature map diventa più piccola man mano che si applica la funzione di pooling.
L’ultimo layer di pooling appiattisce la sua feature map in modo che possa essere processata dal layer completamente connesso.
Layer completamente connessi
Questi layer si trovano nella parte finale della rete neurale convoluzionale, e i loro input corrispondono alla matrice monodimensionale appiattita generata dall’ultimo layer di pooling. A essi si applicano funzioni di attivazione ReLU per introdurre non linearità.
Infine, si usa un layer di previsione softmax per generare valori di probabilità per ciascuna delle possibili etichette di output, e l’etichetta finale predetta è quella con il punteggio di probabilità più alto.
Dropout
Il dropout è una tecnica di regolarizzazione applicata per migliorare la capacità di generalizzazione delle reti neurali con un gran numero di parametri. Consiste nell’eliminare casualmente alcuni neuroni durante il processo di addestramento, costringendo i neuroni rimanenti a imparare nuove feature dai dati di input.
Poiché l’implementazione tecnica verrà eseguita usando TensorFlow 2, la prossima sezione mira a fornire una panoramica completa dei diversi componenti di questo framework per costruire in modo efficiente modelli di deep learning.
Che cos’è il framework TensorFlow?
Google ha sviluppato TensorFlow nel novembre 2015. Lo definisce un framework open source di machine learning per tutti, per diversi motivi.

- Open source: rilasciato sotto la licenza open source Apache 2.0. Questo consente a ricercatori, organizzazioni e sviluppatori di contribuire alla libreria costruendoci sopra senza restrizioni.
- Framework di machine learning: significa che include un insieme di librerie e strumenti che supportano il processo di costruzione di modelli di machine learning.
- Per tutti: usare TensorFlow rende più semplice l’implementazione di modelli di machine learning tramite linguaggi di programmazione comuni come Python. Inoltre, librerie integrate come Keras rendono ancora più facile creare modelli di deep learning robusti.
Tutte queste funzionalità rendono TensorFlow un buon candidato per costruire reti neurali.
Inoltre, installare TensorFlow 2 è semplice e può essere fatto come segue usando il gestore di pacchetti Python pip come spiegato nella documentazione ufficiale.
Dopo l’installazione, possiamo vedere che la versione in uso è la 2.9.1
import tensorflow as tf
print("TensorFlow version:", tf.__version__)
Ora esploriamo più a fondo i componenti principali per creare queste reti.
Cosa sono i tensori?
Quando costruiamo modelli di machine learning e deep learning lavoriamo principalmente con dati ad alta dimensionalità. I tensori sono array multidimensionali con un tipo uniforme usati per rappresentare diverse caratteristiche dei dati.
Di seguito è riportata la rappresentazione grafica dei diversi tipi di dimensioni dei tensori.

- Un tensore a 0 dimensioni contiene un singolo valore.
- Un tensore a 1 dimensione, noto anche come tensore di “rank-1”, è un elenco di valori.
- Un tensore a 2 dimensioni è un tensore di “rank-2”.
- Infine, possiamo avere un tensore a N dimensioni, dove N rappresenta il numero di dimensioni all’interno del tensore. Nei casi precedenti, N è rispettivamente 0, 1 e 2.
Di seguito un’illustrazione da un tensore a zero fino a uno a 3 dimensioni. Ogni tensore è creato usando la funzione constant() di TensorFlow.
# Zero dimensional tensor
zero_dim_tensor = tf.constant(20)
print(zero_dim_tensor)
# One dimensional tensor
one_dim_tensor = tf.constant([12, 20, 53, 26, 11, 56])
print(one_dim_tensor)
# Two dimensional tensor
two_dim_array = [[3, 6, 7, 5],
[9, 2, 3, 4],
[7, 1, 10,6],
[0, 8, 11,2]]
two_dim_tensor = tf.constant(two_dim_array)
print(two_dim_tensor)
Un’esecuzione corretta del codice precedente dovrebbe generare gli output qui sotto, e possiamo notare la parola chiave “tf.Tensor”, a indicare che il risultato è un tensore. Ha tre parametri:
- Il valore effettivo del tensore.
- La shape() del tensore, che è rispettivamente 0, 6 per 1 e 4 per 4 per il primo, il secondo e il terzo tensore.
- Il tipo di dato rappresentato dall’attributo dtype, e tutti i tensori sono int32.

Il nostro Tutorial su TensorFlow per principianti fornisce una panoramica completa di TensorFlow e insegna come costruire e addestrare modelli.
Tensori vs matrici: differenze
Molte persone confondono tensori e matrici. Anche se questi due oggetti possono sembrare simili, hanno proprietà completamente diverse. Questa sezione offre una migliore comprensione della differenza tra matrici e tensori.
- Possiamo pensare a una matrice come a un tensore con sole due dimensioni.
- I tensori, d’altra parte, sono un formato più generale che può avere un qualsiasi numero di dimensioni.
A differenza delle matrici, i tensori sono più adatti ai problemi di deep learning per i seguenti motivi:
- Possono gestire un qualsiasi numero di dimensioni, il che li rende più adatti a dati multidimensionali.
- La capacità dei tensori di essere compatibili con un’ampia gamma di tipi di dati, forme e dimensioni li rende più versatili delle matrici.
- TensorFlow fornisce supporto GPU e TPU per velocizzare i calcoli. Usando i tensori, gli ingegneri di machine learning possono sfruttare automaticamente questi vantaggi.
- I tensori supportano nativamente il broadcasting, che consiste nell’eseguire operazioni aritmetiche tra tensori di forme diverse, cosa non sempre possibile quando si lavora con le matrici.
TensorFlow: costanti, variabili e placeholder
Le costanti non sono gli unici tipi di tensori. Esistono anche variabili e placeholder, che sono tutti blocchi costitutivi di un grafo computazionale.
Un grafo computazionale è, in sostanza, una rappresentazione di una sequenza di operazioni e del flusso di dati tra di esse.
Vediamo ora la differenza tra questi tipi di tensori.
Costanti
Le costanti sono tensori i cui valori non cambiano durante l’esecuzione del grafo computazionale. Si creano usando la funzione tf.constant() e sono utilizzate principalmente per memorizzare parametri fissi che non richiedono cambiamenti durante l’addestramento del modello.
Variabili
Le variabili sono tensori il cui valore può cambiare durante l’esecuzione del grafo computazionale e si creano usando la funzione tf.Variable(). Per esempio, nel caso delle reti neurali, pesi e bias possono essere definiti come variabili poiché devono essere aggiornati durante l’addestramento.
Placeholder
Questi venivano usati nella prima versione di TensorFlow come contenitori vuoti privi di valori specifici. Servivano solo a riservare uno spazio per dati da usare in futuro. Questo dava agli utenti la libertà di usare dataset e dimensioni di batch diversi durante l’addestramento e la validazione del modello.
Nella versione 2 di TensorFlow, i placeholder sono stati sostituiti dalla funzione tf.function(), che è un approccio più pitonico e dinamico per fornire dati al grafo computazionale.
Implementazione passo passo di una CNN
Mettiamo in pratica tutto ciò che abbiamo imparato. Questa sezione illustra l’implementazione end-to-end di una rete neurale convoluzionale in TensorFlow applicata al dataset CIFAR-10, un dataset integrato con le seguenti proprietà:
- Contiene 60.000 immagini a colori 32 per 32
- Il dataset ha 10 classi diverse
- Ogni classe ha 6000 immagini
- Ci sono complessivamente 50.000 immagini di training
- E complessivamente 10.000 immagini di test
Il codice sorgente dell’articolo è disponibile nello spazio di lavoro di DataCamp
Architettura della rete
Prima di entrare nell’implementazione tecnica, capiamo prima l’architettura complessiva della rete che stiamo implementando.

- L’input del modello è un tensore 32x32x3, rispettivamente per larghezza, altezza e canali.
- Avremo due layer di convoluzione. Il primo layer applica 32 filtri di dimensione 3x3 ciascuno e una funzione di attivazione ReLU. Il secondo applica 64 filtri di dimensione 3x3
- Il primo layer di pooling applicherà un max pooling 2x2
- Anche il secondo layer di pooling applicherà un max pooling 2x2
- Il layer completamente connesso avrà 128 unità e una funzione di attivazione ReLU
- Infine, l’output avrà 10 unità corrispondenti alle 10 classi, e la funzione di attivazione è una softmax per generare le distribuzioni di probabilità.
Caricamento del dataset
Il dataset integrato viene caricato da keras.datasets() come segue:
(train_images, train_labels), (test_images, test_labels) = cf10.load_data()
Analisi esplorativa dei dati
In questa sezione ci concentreremo esclusivamente sulla visualizzazione di alcune immagini di esempio, dato che conosciamo già la proporzione di ciascuna classe sia nei dati di training che in quelli di test.
La funzione di supporto show_images() mostra per impostazione predefinita un totale di 12 immagini e accetta tre parametri principali:
- Le immagini di training
- I nomi delle classi
- E le etichette di training.
import matplotlib.pyplot as plt
def show_images(train_images,
class_names,
train_labels,
nb_samples = 12, nb_row = 4):
plt.figure(figsize=(12, 12))
for i in range(nb_samples):
plt.subplot(nb_row, nb_row, i + 1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(train_images[i], cmap=plt.cm.binary)
plt.xlabel(class_names[train_labels[i][0]])
plt.show()
Ora possiamo chiamare la funzione con i parametri richiesti.
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
show_images(train_images, class_names, train_labels)
Un’esecuzione corretta del codice precedente genera le immagini qui sotto.

Preprocessing dei dati
Prima di addestrare il modello, dobbiamo normalizzare i valori dei pixel dei dati nello stesso intervallo (ad es. da 0 a 1). Questo è un passaggio di preprocessing comune quando si lavora con immagini per garantire l’invarianza di scala e una convergenza più rapida durante l’addestramento.
max_pixel_value = 255
train_images = train_images / max_pixel_value
test_images = test_images / max_pixel_value
Notiamo inoltre che le etichette sono rappresentate in formato categoriale come cat, horse, bird e così via. Dobbiamo convertirle in un formato numerico in modo che possano essere facilmente elaborate dalla rete neurale.
from tensorflow.keras.utils import to_categorical
train_labels = to_categorical(train_labels, len(class_names))
test_labels = to_categorical(test_labels, len(class_names))
Implementazione dell’architettura del modello
Il passo successivo è implementare l’architettura della rete in base alla descrizione precedente.
Per prima cosa definiamo il modello usando la classe Sequential() e aggiungiamo ciascun layer al modello con la funzione add().
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
# Variables
INPUT_SHAPE = (32, 32, 3)
FILTER1_SIZE = 32
FILTER2_SIZE = 64
FILTER_SHAPE = (3, 3)
POOL_SHAPE = (2, 2)
FULLY_CONNECT_NUM = 128
NUM_CLASSES = len(class_names)
# Model architecture implementation
model = Sequential()
model.add(Conv2D(FILTER1_SIZE, FILTER_SHAPE, activation='relu', input_shape=INPUT_SHAPE))
model.add(MaxPooling2D(POOL_SHAPE))
model.add(Conv2D(FILTER2_SIZE, FILTER_SHAPE, activation='relu'))
model.add(MaxPooling2D(POOL_SHAPE))
model.add(Flatten())
model.add(Dense(FULLY_CONNECT_NUM, activation='relu'))
model.add(Dense(NUM_CLASSES, activation='softmax'))
Dopo aver applicato la funzione summary() al modello, otteniamo un riepilogo completo dell’architettura con informazioni su ciascun layer, il suo tipo, la forma dell’output e il numero totale di parametri addestrabili.

Addestramento del modello
Finalmente abbiamo tutte le risorse per configurare e avviare l’addestramento del modello. Questo avviene rispettivamente con le funzioni compile() e fit(), che accettano i seguenti parametri:
- L’ottimizzatore è responsabile dell’aggiornamento di pesi e bias del modello. Nel nostro caso usiamo l’ottimizzatore Adam.
- La funzione di loss misura gli errori di classificazione e usiamo la categorical_crossentropy.
- Infine, le metriche misurano le prestazioni del modello; nel nostro caso verranno visualizzate accuracy, precision e recall.
from tensorflow.keras.metrics import Precision, Recall
BATCH_SIZE = 32
EPOCHS = 30
METRICS = metrics=['accuracy',
Precision(name='precision'),
Recall(name='recall')]
model.compile(optimizer='adam',
loss='categorical_crossentropy',
metrics = METRICS)
# Train the model
training_history = model.fit(train_images, train_labels,
epochs=EPOCHS, batch_size=BATCH_SIZE,
validation_data=(test_images, test_labels))
Valutazione del modello
Dopo l’addestramento del modello, possiamo confrontarne le prestazioni sia sul dataset di training sia su quello di test tracciando le metriche sopra usando la funzione di supporto show_performance_curve() in due dimensioni.
- L’asse orizzontale (x) è il numero di epoche
- Quello verticale (y) è la prestazione del modello.
- La curva rappresenta il valore della metrica a una specifica epoca.
Per una visualizzazione migliore, una linea rossa verticale viene tracciata nel punto di intersezione tra i valori di prestazione di training e validazione insieme al valore ottimale.
def show_performance_curve(training_result, metric, metric_label):
train_perf = training_result.history[str(metric)]
validation_perf = training_result.history['val_'+str(metric)]
intersection_idx = np.argwhere(np.isclose(train_perf,
validation_perf, atol=1e-2)).flatten()[0]
intersection_value = train_perf[intersection_idx]
plt.plot(train_perf, label=metric_label)
plt.plot(validation_perf, label = 'val_'+str(metric))
plt.axvline(x=intersection_idx, color='r', linestyle='--', label='Intersection')
plt.annotate(f'Optimal Value: {intersection_value:.4f}',
xy=(intersection_idx, intersection_value),
xycoords='data',
fontsize=10,
color='green')
plt.xlabel('Epoch')
plt.ylabel(metric_label)
plt.legend(loc='lower right')
Quindi, la funzione viene applicata sia all’accuracy sia alla precision del modello.
show_performance_curve(training_history, 'accuracy', 'accuracy')

show_performance_curve(training_history, 'precision', 'precision')

Dopo aver addestrato il modello senza alcun fine-tuning e preprocessing, otteniamo:
- Un punteggio di accuratezza del 67,09%, il che significa che il modello classifica correttamente 67 campioni ogni 100.
- E una precision del 76,55%, il che significa che su 100 previsioni positive, quasi 77 sono veri positivi e i restanti 23 sono falsi positivi.
- Questi punteggi sono raggiunti rispettivamente alla terza e alla seconda epoca per accuratezza e precisione.
Queste due metriche forniscono una comprensione globale del comportamento del modello.
E se volessimo sapere, per ciascuna classe, quali il modello predice bene e con quali ha difficoltà?
Questo si può ottenere dalla matrice di confusione, che mostra per ogni classe il numero di previsioni corrette e sbagliate. L’implementazione è riportata di seguito. Iniziamo facendo previsioni sui dati di test, quindi calcoliamo la matrice di confusione e mostriamo il risultato finale.
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
test_predictions = model.predict(test_images)
test_predicted_labels = np.argmax(test_predictions, axis=1)
test_true_labels = np.argmax(test_labels, axis=1)
cm = confusion_matrix(test_true_labels, test_predicted_labels)
cmd = ConfusionMatrixDisplay(confusion_matrix=cm)
cmd.plot(include_values=True, cmap='viridis', ax=None, xticks_rotation='horizontal')
plt.show()

- Le classi 0, 1, 6, 7, 8, 9, rispettivamente airplane, automobile, frog, horse, ship, e truck hanno i valori più alti sulla diagonale. Questo significa che il modello è più bravo a prevedere queste classi.
- D’altra parte, sembra faticare con le restanti classi:
- Le classi con i valori fuori diagonale più alti sono quelle con cui il modello confonde le classi buone. Per esempio, confonde i birds (classe 2) con un airplane e automobile con trucks (classe 9).
Scopri di più sulla matrice di confusione dal nostro tutorial capire la matrice di confusione in R, che riprende materiale dal corso Machine Learning toolbox di DataCamp.
Questo modello può essere migliorato con attività aggiuntive come:
- Augmentation delle immagini
- Transfer learning usando modelli preaddestrati come ResNet, MobileNet o VGG. Il nostro tutorial sul transfer learning spiega cos’è il transfer learning e alcune delle sue applicazioni nella vita reale.
- Applicazione di diverse tecniche di regolarizzazione come L1, L2 o dropout.
- Fine-tuning di diversi iperparametri come learning rate, dimensione del batch, numero di layer nella rete.
Conclusione
Questo articolo ha fornito una panoramica completa delle CNN in TensorFlow, con dettagli su ciascun layer dell’architettura delle CNN. Ha inoltre introdotto brevemente TensorFlow e come aiuta ingegneri e ricercatori di machine learning a costruire reti neurali sofisticate.
Abbiamo applicato tutte queste competenze a uno scenario reale relativo a un compito di classificazione multiclasse.
La nostra guida per principianti al rilevamento degli oggetti potrebbe essere un ottimo prossimo passo per approfondire l’apprendimento sulla computer vision. Esplora i componenti chiave del rilevamento degli oggetti e spiega come implementare SSD e Faster RCNN disponibili in TensorFlow.

