Reti neurali convoluzionali
- 15 minuti
Nell'unità precedente si è appreso come definire una rete neurale generica a più livelli. In questa unità verranno fornite informazioni sulle reti neurali convoluzionali (CNN) progettate per la visione artificiale.
La visione artificiale è diversa dalla classificazione generica, perché quando si prova a trovare un determinato oggetto nell'immagine, si analizza l'immagine cercando alcuni modelli specifici e le relative combinazioni. Ad esempio, quando si cerca un gatto, prima si possono cercare linee orizzontali, che possono formare baffi, e poi una certa combinazione dei baffi ci può dire che è effettivamente un'immagine di un gatto. La posizione e la presenza di determinati modelli sono importanti. Per estrarre i modelli, si userà la nozione di filtri convoluzionali.
Prima di tutto, è possibile caricare tutte le dipendenze e definire le funzioni helper che verranno usate:
import keras
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
def plot_convolution(data, t, title=''):
t = tf.constant(t, dtype=tf.float32)
t = tf.reshape(t, [*t.shape, 1, 1])
fig, ax = plt.subplots(len(data), 2)
fig.suptitle(title, fontsize=16)
for i in range(len(data)):
d = tf.reshape(tf.constant(data[i], dtype=tf.float32), [1, *data[i].shape, 1])
ax[i][0].imshow(data[i])
ax[i][1].imshow(tf.nn.conv2d(d, t, [1, 1, 1, 1], 'SAME')[0, ..., 0])
def plot_results(hist):
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(hist.history['accuracy'], label='Training acc')
plt.plot(hist.history['val_accuracy'], label='Validation acc')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(hist.history['loss'], label='Training loss')
plt.plot(hist.history['val_loss'], label='Validation loss')
plt.legend()
def display_dataset(dataset, labels=None, n=10, classes=None):
fig, ax = plt.subplots(1, n)
for i in range(n):
ax[i].imshow(dataset[i])
ax[i].axis('off')
if labels is not None:
# Handle both scalar labels (e.g. MNIST) and array labels (e.g. CIFAR-10)
lbl = int(labels[i][0]) if np.ndim(labels[i]) > 0 else int(labels[i])
ax[i].set_title(classes[lbl] if classes is not None else str(lbl))
Caricare ora il set di dati MNIST:
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
Filtri convoluzionali
I filtri convoluzionali sono piccole finestre che vengono eseguite su ogni pixel dell'immagine e calcolano la media ponderata dei pixel adiacenti. Sono definiti da matrici di coefficienti di peso. Ecco gli esempi di applicazione di due filtri convoluzionali diversi sulle cifre scritte a mano MNIST:
plot_convolution(x_train[:5], [[-1., 0., 1.], [-1., 0., 1.], [-1., 0., 1.]], 'Vertical edge filter')
plot_convolution(x_train[:5], [[-1., -1., -1.], [0., 0., 0.], [1., 1., 1.]], 'Horizontal edge filter')
# Expected output: Two sets of images showing original digits alongside the result of applying vertical and horizontal edge filters
Il primo filtro è denominato filtro di arco verticale ed è definito dalla matrice seguente: $$ \left( \begin{matrix} -1 & 0 & 1 \ -1 & 0 & 1 \ -1 & 0 & 1 \ \end{matrix} \right) $$ Quando questo filtro scorre su un'area relativamente uniforme di pixel, la somma ponderata prodotta dalla convoluzione è vicina a 0, perché i pesi positivi e negativi si annullano. Quando rileva un bordo verticale (dove le intensità dei pixel cambiano bruscamente da sinistra a destra), la convoluzione produce un valore positivo o negativo elevato. Ecco perché nelle immagini sopra puoi vedere i bordi verticali evidenziati da valori alti e bassi, mentre i bordi orizzontali sono smussati.
Si verifica un'operazione opposta quando si applica il filtro orizzontale dei bordi: le linee orizzontali vengono amplificate e il valore verticale viene mediato.
Nella visione artificiale classica sono stati applicati più filtri all'immagine per generare funzionalità, che sono state quindi usate dall'algoritmo di Machine Learning per creare un classificatore. Nel Deep Learning vengono costruite reti che apprendono i migliori filtri convoluzionali per risolvere autonomamente il problema di classificazione.
A tale scopo, introduciamo livelli convoluzionali.
Livelli convoluzionali
I livelli convoluzionali vengono definiti usando Conv2D la classe . È necessario specificare quanto segue:
-
filters: numero di filtri da usare. Verranno usati 9 filtri diversi, che offriranno alla rete un sacco di opportunità per esplorare i filtri più adatti per il nostro scenario. -
kernel_sizeè la dimensione della finestra scorrevole. In genere vengono usati filtri 3x3 o 5x5.
La CNN più semplice contiene un solo livello convoluzionale. Data la dimensione di input 28x28, dopo aver applicato nove filtri 5x5, si otterrà un tensore di 24x24x9. La dimensione spaziale è più piccola perché l'impostazione predefinita padding='valid' indica che non viene aggiunta alcuna spaziatura interna e sono presenti solo 24 posizioni in cui una finestra scorrevole di dimensioni 5 può rientrare in 28 pixel (28 − 5 + 1 = 24). L'uso di padding='same' consente di mantenere le dimensioni spaziali aggiungendo zero-padding attorno all'input.
Dopo la convoluzione, appiattiamo il tensore 24×24×9 in un singolo vettore di dimensione 5184, quindi aggiungiamo uno strato denso per produrre 10 classi. La relu funzione di attivazione viene applicata dopo il livello convoluzionale per introdurre la nonlinearità.
model = keras.Sequential([
keras.layers.Input(shape=(28, 28, 1)),
keras.layers.Conv2D(filters=9, kernel_size=(5, 5), activation='relu'),
keras.layers.Flatten(),
keras.layers.Dense(10)
])
model.compile(loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])
model.summary()
# Expected output: Model summary showing a Conv2D layer, Flatten, and Dense(10) with ~52k parameters
È possibile notare che questa rete contiene circa 52.000 parametri sottoponibili a training (234 nel livello convoluzionale + 51.850 nel livello denso), rispetto a circa 80.000 in reti multi-layer completamente connesse. Le reti convoluzionali generalizzano meglio, consentendo di ottenere buoni risultati su set di dati più piccoli.
Annotazioni
Nella maggior parte dei casi pratici si vogliono applicare livelli convoluzionali alle immagini a colori. Pertanto, Conv2D il livello prevede che l'input sia della forma $H\times W\times C$, dove $H$ e $W$ sono altezza e larghezza dell'immagine e $C$ è il numero di canali di colore. Per le immagini in scala di grigi, è necessaria la stessa forma con $C=1$.
Prima di iniziare il training, è necessario modellare i dati:
x_train_c = np.expand_dims(x_train, 3)
x_test_c = np.expand_dims(x_test, 3)
hist = model.fit(x_train_c, y_train, validation_data=(x_test_c, y_test), epochs=3)
# Expected output: Training for 3 epochs showing loss and accuracy for training and validation sets
plot_results(hist)
# Expected output: Plots showing training and validation accuracy and loss over 3 epochs
Come si può notare, è possibile ottenere un'accuratezza più elevata con meno periodi rispetto alle reti completamente connesse dall'unità precedente. Tuttavia, il training stesso richiede più risorse e potrebbe essere più lento nei computer non GPU.
Visualizzazione dei livelli convoluzionali
È anche possibile visualizzare i pesi dei livelli convoluzionali sottoposti a training per provare a dare un po' di più senso a ciò che sta accadendo:
fig, ax = plt.subplots(1, 9)
l = model.layers[0].weights[0]
for i in range(9):
ax[i].imshow(l[..., 0, i])
ax[i].axis('off')
# Expected output: 9 small images showing the learned 5x5 convolutional filter weights
È possibile notare che alcuni di questi filtri sembrano in grado di riconoscere alcuni tratti obliqui, mentre altri sembrano casuali.
Reti CNN a più strati e strati di pooling
In primo luogo, i livelli convoluzionali cercano modelli primitivi, ad esempio linee orizzontali o verticali. È possibile applicare altri livelli convoluzionali sopra di essi per cercare modelli di livello superiore, ad esempio forme primitive. Più livelli convoluzionali possono quindi combinare tali forme in alcune parti dell'immagine, fino all'oggetto finale che si sta tentando di classificare.
In questo caso, è anche possibile applicare un trucco: ridurre le dimensioni spaziali dell'immagine. Una volta rilevato che c'è un tratto orizzontale all'interno di una finestra scorrevole 3x3, non è così importante in quale pixel esatto si è verificato. È quindi possibile "ridurre" le dimensioni dell'immagine, che viene eseguita usando uno dei livelli di pooling:
- Average Pooling utilizza una finestra scorrevole (ad esempio, 2x2 pixel) e calcola la media dei valori all'interno della finestra
- Max Pooling sostituisce la finestra con il valore massimo. L'idea alla base del max pooling è quella di rilevare la presenza di un determinato pattern all'interno della finestra scorrevole.
Pertanto, in una tipica CNN ci sarebbero diversi livelli convoluzionali, con i livelli di pooling tra di essi per ridurre le dimensioni dell'immagine. Aumenteremo anche il numero di filtri, perché man mano che i modelli diventano più avanzati, ci sono combinazioni più interessanti che dobbiamo cercare.
Questa architettura è detta anche architettura a piramide a causa della riduzione delle dimensioni spaziali e dell'aumento delle dimensioni di funzionalità/filtri.
model = keras.Sequential([
keras.layers.Input(shape=(28, 28, 1)),
keras.layers.Conv2D(filters=10, kernel_size=(5, 5), activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Conv2D(filters=20, kernel_size=(5, 5), activation='relu'),
keras.layers.MaxPooling2D(),
keras.layers.Flatten(),
keras.layers.Dense(10)
])
model.compile(loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])
model.summary()
# Expected output: Model summary showing Conv2D, MaxPooling, Conv2D, MaxPooling, Flatten, Dense with ~8.5k parameters
Si noti che il numero di parametri sottoponibili a training (~8,5K) è notevolmente inferiore rispetto ai casi precedenti. Ciò accade perché i livelli convoluzionali in generale hanno pochi parametri e la dimensionalità dell'immagine prima di applicare uno strato denso finale viene ridotta.
hist = model.fit(x_train_c, y_train, validation_data=(x_test_c, y_test), epochs=3)
# Expected output: Training for 3 epochs showing loss and accuracy for training and validation sets
plot_results(hist)
# Expected output: Plots showing training and validation accuracy and loss over 3 epochs
Si noti che è possibile ottenere un'accuratezza maggiore con più livelli e richiede un numero minore di periodi. Significa che un'architettura di rete più sofisticata richiede meno dati per capire cosa sta succedendo e per estrarre modelli generici dalle immagini. Tuttavia, ogni periodo comporta più calcoli a causa delle operazioni convoluzionali aggiuntive, quindi il training è più veloce con una GPU.
Lavorare con immagini reali dal set di dati CIFAR-10
Anche se il problema di riconoscimento delle cifre scritte a mano può sembrare un problema toy, siamo ora pronti a fare qualcosa di più serio. Si esaminerà ora un set di dati più avanzato di immagini di oggetti diversi, denominato CIFAR-10. Contiene 60k 32x32 immagini, suddivise in 10 classi.
Annotazioni
Da questo punto in poi si ricarica x_train, y_train, x_teste y_test con il set di dati CIFAR-10, sostituendo i dati MNIST usati in precedenza in questa unità. Le variabili a dimensioni espanse x_train_c e x_test_c non sono più necessarie.
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype(np.float32) / 255.0
x_test = x_test.astype(np.float32) / 255.0
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
display_dataset(x_train, y_train, classes=classes)
# Expected output: A row of 10 CIFAR-10 images with their class labels
Un'architettura CNN classica è LeNet, proposta da Yann LeCun originariamente per il riconoscimento delle cifre scritte a mano. Adattiamolo per CIFAR-10. Segue gli stessi principi descritti in precedenza, la differenza principale è 3 canali di colore di input anziché 1.
model = keras.Sequential([
keras.layers.Input(shape=(32, 32, 3)),
keras.layers.Conv2D(filters=6, kernel_size=5, strides=1, activation='relu'),
keras.layers.MaxPooling2D(pool_size=2, strides=2),
keras.layers.Conv2D(filters=16, kernel_size=5, strides=1, activation='relu'),
keras.layers.MaxPooling2D(pool_size=2, strides=2),
keras.layers.Flatten(),
keras.layers.Dense(120, activation='relu'),
keras.layers.Dense(84, activation='relu'),
keras.layers.Dense(10)
])
model.summary()
# Expected output: Model summary showing the LeNet architecture with Conv2D, MaxPooling, Dense layers
L'addestramento di questa rete richiede un corretto impiego di una quantità significativa di tempo e deve essere eseguito su sistemi di calcolo con GPU.
model.compile(optimizer='adam', loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])
hist = model.fit(x_train, y_train, validation_data=(x_test, y_test), epochs=10)
# Expected output: Training for 10 epochs showing loss and accuracy for training and validation sets
plot_results(hist)
# Expected output: Plots showing training and validation accuracy and loss over 10 epochs
La precisione che siamo stati in grado di raggiungere con solo pochi periodi di allenamento è solo ok. Tenere presente che il problema è notevolmente più difficile rispetto alla classificazione delle cifre MNIST. Ottenere oltre 60% accuratezza è un buon risultato in un tempo di training così breve, anche se i modelli all'avanguardia possono ottenere oltre 95% su CIFAR-10 usando architetture più approfondite, aumento dei dati e training più lungo.
Risultati
In questa unità si è appreso il concetto principale alla base delle reti neurali di visione artificiale, ovvero reti convoluzionali. Le architetture reali che alimentano la classificazione delle immagini, il rilevamento degli oggetti e persino le reti di generazione di immagini sono basate su reti CNN, solo con più livelli e alcuni trucchi di training aggiuntivi.
Verifica delle conoscenze
Commenti e suggerimenti
Questa pagina è stata utile?
No
Serve aiuto con questo argomento?
Provare a usare Ask Learn per chiarire o guidare l'utente in questo argomento?