Le reti neurali convoluzionali, un modo per vedere bene al minimo costo

Le reti neurali convoluzionali, un modo per vedere bene al minimo costo

Condividi con i tuoi amici...

Questo articolo mostrerà cosa sono le rete neurali convoluzionali (CNN) e come, ancora una volta, i creatori di questa tecnologia si siano ispirati alla natura ed in particolare alle funzioni della visione naturale degli animali e dell’uomo. Nel progettare i modelli di AI ci si avvale sempre di uno schema preesistente naturale che rende questi modelli qualcosa di più di semplici strumenti realizzati artificiosamente per assolvere delle funzioni specifiche, essi imitano gli schemi del cervello umano e animale ponendosi in qualche modo sul nostro stesso livello di complessità.

Al di là di queste pertinenti riflessioni filosofiche cerchiamo di capire cosa sono e come funzionano le reti neurali convoluzionali.

-> Consigliamo la lettura dell’articolo “Chiarezza sulle reti neurali

Anticipiamo immediatamente che l’uso principale che si fa di queste reti riguarda la visione artificiale.

Immaginate un’orchestra. Ciascuno strumentista, con il suo strumento, contribuisce a creare un intricato coro di suoni, abilmente orchestrato dal direttore. Ogni strumentista “comprende” la sua parte, chi suona le percussioni non deve preoccuparsi di cosa sta facendo il violino. Ciò non significa che le percussioni siano meno importanti del violino, è l’unione del lavoro di tutti che permette di creare l’armonia della musica.

Una rete convoluzionale funziona in modo molto simile. Come in un’orchestra, ci sono molteplici “strumentisti” o neuroni artificiali, ognuno dei quali è specializzato nell’identificazione di un particolare pattern o caratteristica. Questi “strumentisti” non lavorano da soli, ma sono interconnessi in uno schema complesso e meticolosamente organizzato, il quale permette alla rete convoluzionale di “vedere” e “comprendere” le immagini.

Ma cosa significa realmente “vedere” o “comprendere” per un algoritmo di intelligenza artificiale? È come se ogni immagine avesse un suo codice unico, una serie di pattern e caratteristiche che la rete convoluzionale impara a identificare e catalogare.

Consideriamo, ad esempio, l’immagine di un gatto. Alcuni neuroni saranno allenati a riconoscere i bordi delle figure, alcuni riconosceranno i pattern della pelliccia, altri ancora saranno specializzati nel distinguere le forme, come quella delle orecchie o dei baffi. Così come in un puzzle, ogni pezzo concorre a ricreare l’immagine finale. Ciò che permette a una rete convoluzionale di “vedere” un gatto non è solo il baffo o l’orecchio, ma l’armoniosa unione di tutte queste caratteristiche.

La rete neurale “classica” completamente connessa

In una rete neurale completamente connessa, ogni neurone è collegato a tutti gli altri neuroni nei layer adiacenti. Per esempio, se abbiamo un’immagine di input di dimensione 100×100 pixel e un primo layer da 10 neuroni, ci sono quindi 100x100x10=100,000 connessioni e di conseguenza altrettanti pesi da addestrare.

Parte di una rete neurale completamente connessa

D’altra parte, una CNN (rete neurale convoluzionale) utilizza una serie di filtri più piccoli (ad esempio 5×5) che vengono “fatti scorerre” sull’intera superficie dell’immagine per formare una mappa delle caratteristiche. Il peso dei filtri viene condiviso tra tutti i punti dell’immagine, il che significa che il numero totale di pesi da addestrare è molto inferiore. In questo caso avremmo solo 5x5x10=250 pesi da addestrare, un incredibile risparmio rispetto a centomila.

Questo è uno dei principali vantaggi delle CNN: utilizzano meno parametri, il che riduce la complessità del modello (facendo risparmiare tempo e risorse computazionali) e migliora la generalizzazione (riducendo il rischio di sovradattamento).

In una rete neurale convoluzionale (CNN), i neuroni non sono connessi individualmente ad ogni singolo neurone nel livello interno, come può avvenire in una rete neurale completamente connessa, ma condividono pesi e bias con un piccolo gruppo di neuroni nello stesso livello. Questo è definito come un filtro o kernel.

Se volessimo schematizzare ciò, potremmo immaginarlo come una piccola matrice (il filtro) che viene applicata sull’immagine di input o sulla mappa delle caratteristiche del livello precedente.

Ad esempio, immaginiamo un filtro di dimensione 2×2 applicato su 4 neuroni di input. Ciò significa che lo stesso filtro sarà applicato in sequenza su 4 diversi gruppi di neuroni in input, ognuno di dimensione 2×2, e ciascuna applicazione creerà un singolo neurone nel livello successivo. Il risultato sarà una mappa di caratteristiche di dimensione ridotta, dove ogni neurone è determinato da un gruppo locale di neuroni nel livello precedente.

Quindi, non stiamo collegando singoli neuroni di input a singoli neuroni nel livello interno, ma piuttosto stiamo condividendo pesi e bias con gruppi di neuroni. Questo è il motivo per cui le CNN sono molto efficienti in termini di parametri.

Un aspetto critico delle CNN è che a causa di questa condivisione dei pesi, sono ottimizzate per lavorare con dati che presentano una “località” spaziale o temporale, come le immagini o i segnali audio. Non lavorano così bene con dati dove questa località non è presente. Inoltre, la selezione e la progettazione di questi filtri richiedono un certo livello di comprensione e di esperienza, il che può essere ostacolo per i principianti.

Approfondiamo…

Le reti neurali convoluzionali sono ispirate dalla biologia. Le CNN sono state modellate ispirandosi alla visione biologica, e particolarmente al concetto dei campi recettivi delle cellule gangliari della retina, introdotto per la prima volta da Stephen Kuffler nel 1953. Un campo recettivo in neuroscienza è la regione dello spazio visivo che stimola l’attività di una determinata cellula nervosa. Kuffler osservò che le cellule gangliari retiniche erano sensibili più ad alcune regioni dello spazio visivo rispetto ad altre. In particolare, è riuscito a mappare queste aree visive e dimostrare che le cellule gangliari rispondono a stimoli di luce in un particolare “campo recettivo”.

In termini semplici, possiamo pensare a un campo recettivo come una porzione del mondo visivo stimolata ed elaborata da specifiche cellule gangliari della retina. È come se ogni cellula gangliare avesse una “finestra” privilegiata da cui osservare il mondo esterno. Ma, come ci ha mostrato Kuffler, non tutte le “finestre” sono uguali. Alcune sono più sensibili di altre a stimoli luminosi presenti in specifiche aree dello spazio visivo.

Un esempio scientifico può contribuire a chiarire le cose. Supponiamo di avere una griglia composta da molteplici rettangoli illuminabili. Ogni rettangolo rappresenta un’area dello spazio visivo e ogni cellula gangliare della retina è come un osservatore che tiene d’occhio uno o più rettangoli. Quando un rettangolo (o area del campo recettivo) viene illuminato, l’osservatore (la cellula gangliare) avverte la luce e risponde.

La scoperta dei campi recettivi ha aperto nuovi orizzonti nell’indagine della neuroscienza sulla comprensione del meccanismo di elaborazione visiva nel cervello. La teoria prevalente suggerisce che i campi recettivi hanno forme e dimensioni specifiche e variano considerevolmente tra le diverse cellule nervose. Questa variabilità consente un’elaborazione più sofisticata e dettagliata delle informazioni visive.

Questa stessa idea è stata applicata alle CNN: invece di avere neuroni che rispondono a tutto l’input, abbiamo neuroni che rispondono a piccole parti dell’input.

Questa idea viene realizzata attraverso due operazioni principali:

la convoluzione e

il pooling.

In una operazione convoluzionale, il CNN prende una piccola porzione dell’input (il campo recettivo) e applica un filtro (o kernel). Il filtro è una piccola matrice di pesi che viene moltiplicata per l’input. Il risultato di questa operazione viene poi sommato per produrre una singola uscita. L’operazione viene ripetuta applicando il filtro su tutto l’input, ottenendo una mappa delle caratteristiche o feature map. Ogni feature map viene prodotta utilizzando un filtro diverso: potremmo avere una feature map per rilevare i bordi, un’altra per rilevare i cerchi e così via. Questo è un modo per ridurre il numero di parametri: invece di avere un peso per ogni input per ogni neurone, abbiamo un filtro per ogni feature map.

Esempio matematico

Ecco un semplice esempio di come funziona una operazione convoluzionale, un’operazione fondamentale in una Convolutional Neural Network (CNN) che serve per creare le mappe delle caratteristiche (feature maps).

Supponiamo che il nostro input sia una piccola immagine in bianco e nero di 5×5 pixel. Questa immagine può essere rappresentata da una matrice di valori di pixel di dimensione 5×5. Ad esempio:

1  1  1  0  0
0  1  1  1  0
0  0  1  1  1
0  0  1  1  0
0  1  1  0  0

Supponiamo che il filtro con cui vogliamo convolvere l’immagine sia una piccola matrice 3×3. Un esempio di filtro potrebbe essere il seguente:

1  0  1
0  1  0
1  0  1

In implementazioni reali, i filtri sono realizati mediante apprendimento durante la formazione della CNN. Adesso, per fare la convoluzione, si sposta il filtro attraverso l’immagine di input, moltiplicando ogni valore del filtro con il corrispondente valore dell’immagine sotto il filtro, e sommando i risultati in un singolo numero che forma la mappa delle caratteristiche.

Ad esempio, per ottenere il primo valore della mappa delle caratteristiche, moltiplicheremmo e sommeremmo i valori così:

(1*1) + (1*0) + (1*1) +
(0*1) + (1*1) + (0*0) +
(1*0) + (0*1) + (1*1) = 4

Questo ci darebbe il valore di 4. Questo valore sarebbe il primo valore della nostra mappa delle caratteristiche.

Spostiamo poi il nostro filtro di una posizione verso destra (o verso il basso una volta che abbiamo raggiunto l’estrema destra) e ripetiamo l’operazione per ottenere l’intera mappa delle caratteristiche.

Questa operazione rileverà le caratteristiche o pattern nell’immagine di input che corrispondono al filtro. Durante una convoluzione si passano molti filtri sull’immagine di input. Ogni filtro è progettato per rilevare un tipo di caratteristica o pattern, come bordi verticali, bordi orizzontali, curve, texture, e così via. Quando un filtro è convoluto con l’immagine di input, esso crea una mappa delle caratteristiche (o feature map), che è essenzialmente un’immagine trasformata che enfatizza le caratteristiche cercate dal filtro.

in una rete convoluzionale (CNN) i filtri svolgono il ruolo dei pesi. Durante l’addestramento della CNN, i valori di queste matrici-filtro vengono aggiustati mediante il processo di retropropagazione dell’errore.

Durante la fase di forward pass, l’input viene convoluto con i filtri per generare delle mappe delle caratteristiche. Successivamente, queste mappe vengono passate attraverso una funzione di attivazione. Durante il backwards pass, l’errore viene propagato all’indietro attraverso la rete, fino ai filtri. Durante questa fase, i gradienti calcolati vengono utilizzati per aggiornare i valori dei filtri (pesi), in modo da minimizzare l’errore finale.

Le feature map possono essere ulteriormente passate attraverso strati convoluzionali aggiuntivi, permettendo alla rete di rilevare caratteristiche sempre più complesse. Ad esempio, mentre i primi strati potrebbero rilevare bordi o curve, gli strati successivi potrebbero rilevare parti di oggetti (come le ruote di una macchina) o interi oggetti.

Il pooling

L’operazione di pooling riduce ulteriormente la dimensione dell’input, prendendo un gruppo di neuroni vicini e scegliendo il massimo (max pooling) o la media (average pooling) dell’attività dei neuroni. Questo rende il modello più robusto a piccole variazioni dell’input.

Una CNN generalmente consiste di molteplici strati di operazioni convoluzionali e di pooling che rilevano caratteristiche a diversi livelli di astrazione. Ad esempio, i primi strati potrebbero rilevare piccoli bordi e i colori, mentre gli strati più profondi potrebbero rilevare parti dell’oggetto o intero oggetti.

Consideriamo un esempio di pooling (o sottocampionamento) dopo l’operazione di convoluzione in una rete neurale convoluzionale.

Supponiamo di avere una mappa delle caratteristiche di 4×4 risultante da un’operazione di convoluzione con un filtro. La mappa delle caratteristiche potrebbe essere la seguente:

Un’operazione di pooling viene utilizzata per ridurre le dimensioni di questa mappa delle caratteristiche. Esistono vari tipi di pooling, ma uno dei più comuni è il max pooling, che seleziona il valore massimo da una regione della mappa delle caratteristiche.

Se applichiamo un’operazione di max pooling con una finestra di 2×2, dividiamo la mappa delle caratteristiche in regioni di 2×2 e selezioniamo il valore massimo da ciascuna regione:

Il valore massimo della prima regione è 80, e della seconda regione è 90.
Il valore massimo della terza regione è 130, e della quarta regione è 140.

Riunendo questi valori massimi, otteniamo la mappa delle caratteristiche ridotta sopraindicata.

Il pooling ha ridotto le dimensioni della mappa delle caratteristiche originale della metà, mantenendo comunque il valore più importante (la caratteristica o il pattern rilevato con maggiore intensità) all’interno di ogni regione sottocampionata. Il max pooling è comunemente utilizzato nelle CNN poiché fornisce una certa invarianza a piccole traslazioni, consentendo al modello di riconoscere la caratteristica indipendentemente dalla sua posizione.

Alla fine…

Verso la fine della rete, le feature map sono spesso elaborate da uno o più strati completamente connessi per effettuare attività come la classificazione dell’immagine. Per esempio, i neuroni negli strati completamente connessi possono usare le feature map per determinare se un’immagine contiene o meno un certo oggetto.

In genere, una rete neurale convoluzionale cercherà di apprendere durante la fase di addestramento quali filtri (e pertanto, quali feature map) sono più utili per l’attività da eseguire, come il riconoscimento degli oggetti in un’immagine.

Perché queste reti si definiscono “convoluzionali”?

La convoluzione è un’operazione matematica usata per combinare o mescolare due funzioni, spesso per analizzare come una funzione influisce sull’altra. Questo concetto è centrale in molte aree dell’ingegneria e delle matematiche, come l’elaborazione dei segnali, l’analisi delle immagini, ed è alla base del funzionamento delle reti neurali convoluzionali, un tipo comune di rete neurale usata per l’apprendimento automatico.

Matematicamente, l’operazione di convoluzione di due funzioni f(t) e g(t) è di solito definita come l’integrale nel tempo di un prodotto di due funzioni, dove una funzione è invertita e traslata. Si scrive come (f * g)(t) e si calcola come:

(f * g)(t) = ∫ f(τ)g(t – τ) dτ

dove il simbolo ∫ rappresenta l’integrale e τ è una variabile di integrazione che va da -∞ a +∞.

Sia f(τ) sia g(t – τ) sono funzioni del parametro τ, che a sua volta è una variabile che scorre l’intero dominio delle funzioni f e g. Nota che g è scritta come g(t – τ) anziché come g(τ) perché g è capovolta e traslata per t.

In termini di fisica, immaginiamo f come una sorta di segnale di input e g come risposta di un sistema a un impulso. Ad esempio, in un sistema acustico, f potrebbe essere un input audio e g la risposta di un altoparlante. La convoluzione di f e g poi ci dà la risposta complessiva del sistema (l’output audio risultante) ad un certo istante t.

Ora rinominiamo le funzioni in modo da ridurci al caso particolare dei segnali.

Dato un impulso come input del sistema esso genera un segnale di risposta h(t). Che tipo di output y(t) si ottiene dallo stesso sistema se si invia un segnale di input x(t)?

La chiave per capire questa formulazione è che un segnale di ingresso generale x(t) può essere pensato come una serie infinita di impulsi diffusi nel tempo. In virtù della proprietà di linearità, la risposta totale del sistema è semplicemente la sovrapposizione (somma) delle risposte a ciascuno di questi impulsi.

Nel calcolo del prodotto di convoluzione, x(τ) rappresenta l’intensità di un impulso “fittizio” al tempo τ, e h(t – τ) rappresenta la risposta del sistema all’impulso che si è verificato al tempo τ, visto dal vantaggio del tempo successivo t.

Quando si integrano tutti questi contributi nella variabile τ, si ottiene la risposta totale del sistema y(t) al tempo t.

y(t) = (x * h)(t) = ∫ x(τ)h(t – τ) dτ

Fissato un istante t l’integrale da la somma dei contributi di tutte le risposte h(t – τ) per τ tra +inf e -inf al segnale di output y(t). Questi contributi sono dati da x(τ)h(t – τ) (il segnale di input al tempo τ per la risposta h(t – τ) all’impulso)

Esempio prodotto da Code interpreter (GPT4)

Come tutto ciò riguarda le reti neurali convoluzionali?

In una Convolutional Neural Network (CNN), il filtro (o kernel) svolge un ruolo molto simile alla funzione di risposta all’impulso h(t) nell’analisi dei sistemi lineari.

Per le immagini e le reti neurali convoluzionali, parliamo di convoluzioni discrete anziché continue. Nel contesto delle CNN, l’operazione convoluzionale è una forma semplificata di convoluzione, in cui si utilizza una piccola matrice (o “filtro”) che viene mossa sull’immagine di input. Ciascun pixel dell’input è moltiplicato per il valore corrispondente nel filtro e i prodotti sono sommati per dare un singolo valore output, formando così una mappa delle caratteristiche.

In questo senso, si può pensare a un filtro come a una funzione di risposta all’impulso, in cui l’impulso è l’immagine sotto il filtro. La funzione di risposta all’impulso in questo caso non risponde a una singola “esplosione” di segnale, come in un sistema lineare classico, ma piuttosto a un certo pattern o caratteristica nel campo visivo del filtro.

Allo stesso modo, si può pensare all‘immagine di input o alla mappa delle caratteristiche come a un analogo di un segnale d’ingresso x(t), in cui ora t è l’indirizzo del pixel sul piano dell’immagine.

Esempio di algoritmo (python) che addestra una CNN

Mostriamo un codice python che usa delle immagini estratte da una cartella per addestrare una rete CNN

import os
import cv2
import numpy as np
import tensorflow as tf
from tensorflow.keras import layers

# Specificare la cartella contenente le immagini di addestramento
train_folder = "path/cartella_immagini"

# Elenco delle classi di immagini
class_names = os.listdir(train_folder)
num_classes = len(class_names)

# Creazione di una lista di immagini e relative etichette
x_train = []
y_train = []

# Caricamento delle immagini dalle cartelle
for idx, class_name in enumerate(class_names):
    class_folder = os.path.join(train_folder, class_name)
    for image_name in os.listdir(class_folder):
        image_path = os.path.join(class_folder, image_name)
        image = cv2.imread(image_path)
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (28, 28))
        x_train.append(image)
        y_train.append(idx)

# Conversione delle liste in array numpy
x_train = np.array(x_train)
y_train = np.array(y_train)

# Normalizzazione dei pixel delle immagini
x_train = x_train.astype("float32") / 255.0

# One-hot encoding delle etichette
y_train = tf.keras.utils.to_categorical(y_train, num_classes)

# Definizione della struttura della CNN
model = tf.keras.Sequential()
model.add(layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 3)))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(num_classes, activation='softmax'))

# Compilazione del modello
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

# Addestramento del modello
model.fit(x_train, y_train, batch_size=128, epochs=10)

Il cuore del programma è costituito dalle linee che definiscono la struttura della CNN

model = tf.keras.Sequential()
model.add(layers.Conv2D(32, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 3)))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(num_classes, activation='softmax'))

Stiamo creando una CNN con due strati convoluzionali (Conv2D) seguiti da un layer di pooling (MaxPooling2D). Successivamente, abbiamo un layer Flatten che converte i dati in un vettore unidimensionale per essere utilizzato da un layer denso (Dense) con funzione di attivazione Softmax

  • 32: rappresenta il numero di filtri convoluzionali. Ogni filtro apprende diverse caratteristiche dell’input e produce una mappa di attivazioni in output.
  • kernel_size=(3, 3): rappresenta la dimensione della finestra di convoluzione, ovvero la dimensione del filtro che si sposta sull’input per calcolare le convoluzioni. In questo caso, stiamo utilizzando un filtro 3×3.
  • activation='relu': specifica la funzione di attivazione da applicare dopo l’operazione di convoluzione. In questo caso, viene utilizzata la funzione di attivazione ReLU (Rectified Linear Unit), che è una funzione non lineare che introduce la non linearità nel modello.
  • input_shape=(28, 28, 1): rappresenta la forma dell’input atteso dal layer convoluzionale. In questo caso, l’input è un’immagine 2D di dimensioni 28×28 con un singolo canale (scala di grigi). L’ultima dimensione 1 indica il numero di canali dell’immagine.

Ricorda che i filtri in una rete neurale convoluzionale (CNN) sono rappresentati come matrici. Le dimensioni di queste matrici sono determinate dalla dimensione del kernel specificata nel layer convoluzionale.

I valori all’interno di queste matrici (filtraggio) vengono impostati inizialmente in modo casuale e successivamente ottimizzati durante il processo di addestramento della CNN attraverso la retropropagazione del gradiente (discesa del gradiente stocastico (SGD) o varianti come Adam o RMSProp). Il modello apprende i valori dei filtri durante l’addestramento per rilevare estraendo le caratteristiche significative dai dati di input.

La scelta di come inizializzare i valori dei filtri può influire sulle prestazioni del modello. Una pratica comune è l’inizializzazione casuale utilizzando una distribuzione normale o uniforme. Alcuni algoritmi di inizializzazione dei pesi, come l’inizializzazione di Xavier o l’inizializzazione di He, possono essere utilizzati per garantire che i pesi siano inizializzati in modo appropriato per la propagazione corretta del gradiente.