HomeAboutcontatti
← TORNA ALLA HOME

Classificazione binaria di immagini con Tensorflow

Classificazione
Tensorflow
13/5/2022
|
Computer Vision
|
Andrea D'Agostino
Machine Learning
NLP
Computer Vision
Deep Learning
Carriera
Progetti
Classificare immagini di cani e gatti utilizzando una rete neurale convoluzionale in Tensorflow

In questo post vedremo come costruire un modello di classificazione binaria con Tensorflow per differenziare tra cani e gatti in immagini. Prendendo spunto da una famosa competizione su Kaggle e il relativo dataset, useremo questo compito per imparare come

  • importare dal web un dataset compresso
  • costruire un modello di classificazione con strati di convoluzione e max pooling
  • creare un generatore di immagini con ImageDataGenerator per gestire efficacemente le immagini di addestramento e di validazione
  • compilare e addestrare il modello
  • visualizzare le trasformazioni applicate alle immagini nei vari strati della rete neurale
  • fare delle previsioni su immagini mai viste prima

Poiché fare deep learning non è alla portata di qualsiasi PC di uso domestico, useremo Google Colab con runtime impostato su GPU.

Importare il dataset compresso dal web

Useremo un dataset ridotto di 3000 immagini di cani e gatti presi dal famoso dataset di Kaggle formato da 25000 immagini. Il dataset completo pesa più di 500 MB e caricarli/scaricarli su Colab può essere frustrante. Useremo questa versione ridotta che in ogni caso ci permetterà di testare efficacemente il nostro modello.

La URL al dataset è questa:

https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip

Usiamo il comando wget per scaricare il file compresso nel nostro file system:

Usando il comando wget e i pacchetti os e zipfile siamo in grado di scaricare e organizzare i nostri file di addestramento in maniera efficiente. Abbiamo ora un modo per puntare ai nostri file con delle variabili specifiche, che useremo in ImageDataGenerator di Tensorflow.

Diamo una occhiata ad un set di immagini così da farci una idea su quello che andremo a classificare

Esempio di immagini di cani e gatti che andremo a classificare

Vediamo come le immagini siano molto diverse tra loro e come a volte siano presenti anche entità estranee come esseri umani o altri oggetti. Costruiremo un modello di deep learning in grado di differenziare efficacemente tra cani e gatti nonostante questi elementi non inerenti.

Se usiamo len(os.listdir(train_cats_dir)) per contare il numero di immagini nelle varie cartelle vediamo che il loro numero ammonta a 3000, con 1000 immagini di cani e di gatti nel training e 500 immagini rispettivamente per la validazione.

Breve introduzione a convoluzioni e pooling

Nel modello che vedremo a breve useremo strati di convoluzioni e di max pooling. Entrambi gli strati sono ampiamente usati in compiti di computer vision per via delle trasformazioni che applicano sulla immagine in input e beneficiano la rete neurale perché la aiutano nell'identificazione dei pattern andando a enfatizzare delle caratteristiche essenziali presenti in esse.

Convoluzione

Una convoluzione è essenzialmente un filtro che viene applicato ad una immagine. Ragionando a livello di pixel, che sono le entità che andremo a trasformare quando si parla di immagini, una convoluzione guarda il suo valore e quelli dei suoi pixel vicini e trasforma il pixel target usando una griglia di valori mappati ad ogni pixel considerato. ‍

Se per esempio usiamo una griglia 3x3, allora considereremo tutti i pixel vicini del nostro pixel target. Quando applichiamo la convoluzione, il pixel target viene trasformato e assume il valore corrispondente alla moltiplicazione del valore originale di ogni pixel considerato e del rispettivo valore nella griglia di convoluzione. Il valore finale corrisponde alla somma di ogni prodotto. Vediamo con una immagine.

Applicazione di una convoluzione su un pixel. Screenshot preso da Coursera.org e rielaborato dall'autore.

‍

Considerando il pixel target con valore 192, allora una convoluzione applicato su di esso considererà tutti i pixel intorno ad esso come vicini e il suo nuovo valore sarà il seguente:

L'idea alla base di una convoluzione è quella di far emergere alcune caratteristiche di una immagine, come ad esempio rendere bordi e contorni più salienti rispetto al background.

Una convoluzione che fa risaltare i bordi verticali degli elementi nell'immagine. Screenshot preso da Coursera.org.

Le convoluzioni vengono spesso accompagnate dal pooling (in italiano, aggregazione, compressione), che permette alla rete neurale di comprimere l'immagine e estrarre gli elementi davvero salienti della stessa.

In Tensorflow, un tipico strato di convoluzione viene applicato con tf.keras.layers.Conv2D(filters, kernel_size, activation, **kwargs). In filters inseriremo il numero di filtri di convoluzione da applicare, invece con kernel_size indicheremo la grandezza della griglia. Con activation specificheremo invece la funzione di attivazione. I parametri sono molti e consiglio al lettore di studiare meglio il materiale sulla documentazione ufficiale di Tensorflow.

Pooling

Fare pooling vuol dire applicare una compressione all'immagine. Se per esempio volessimo applicare uno strato di pooling 2D con Tensorflow, questo significherebbe prendere il pixel di riferimento, quello sotto di esso e i due al suo lato sinistro, in modo da formare una griglia di quattro valori. Di questi valori si conserva solo il valore più grande.

Funzionamento di un meccanismo di pooling 2D. Screenshot preso da Coursera.org.

Guardando attentamente questa immagine vediamo come fare pooling riduce una immagine di 16 pixel in una da 4, andando proprio a prendere i pixel dal valore più grande in blocchi di 4 e ripetendo il processo.

Questo meccanismo viene applicato dopo la convoluzione, andando così a preservare le caratteristiche messe in risalto dalla stessa e amplificando ancora di più questo effetto. Il pooling riduce anche la dimensione dell'immagine, velocizzando quindi l'addestramento negli strati più avanzati di una rete neurale.

Il pooling è solitamente applicato applicato prendendo il valore massimo, ma ci sono anche altre logiche, come ad esempio quelle basate sulla media e somma.

In Tensorflow, un tipico strato di pooling viene applicato con tf.keras.layers.MaxPooling2D(pool_size, **kwargs). In pool_size inseriremo la grandezza della griglia. I parametri sono molti e consiglio al lettore di studiare meglio il materiale sulla documentazione ufficiale di Keras.

Creazione del modello con Tensorflow

Ora che è un po' più chiaro cosa siano convoluzione e aggregazione, procediamo con la creazione di un modello di classificazione binaria con Tensorflow in grado di sfruttare le caratteristiche che rendono cani e gatti identificabili. Useremo l'API sequenziale di Tensorflow perché è facile da comprendere e da implementare.

Una nota sulla input_shape

È importante notare che dovremmo fornire al modello immagini dalle dimensioni uniformi. Questa dimensione è arbitraria e per questo modello useremo una dimensione di 150x150 pixel. Ogni immagine verrà quindi ridimensionata da Tensorflow in modo da essere quadrata. Poiché stiamo usando immagini a colori, dovremmo anche fornire questa informazione. La input_shape sarà quindi (150, 150, 3), dove 3 sta proprio per i tre bit di informazione che codificano il colore. Vedremo tra poco come assicurarci che le nostre immagini siano di questa dimensione quando sfrutteremo ImageDataGenerator.

Vediamo come implementare l'architettura della rete neurale.

Come menzionato, convoluzioni e aggregazioni vanno spesso insieme. Il loro numero però è arbitrario e va testato dall'analista. Magari aumentando o diminuendo questo numero di strati la performance aumenta. L'unico modo per capirlo è di sperimentare.

L'output dell'ultimo neurone viene infine sottoposto alla funzione di attivazione sigmoide che restituisce 0 oppure 1.

Utilizziamo ora model.summary() per comprendere come il dato viene trasformato dalla rete neurale e come questo venga convertito in una classe binaria.

Risultato di model.summary()

Vediamo come, a cascata, la nostra immagine venga ridotta dalla convoluzione e successivamente compressa ulteriormente dal pooling. Dobbiamo porre particolare attenzione alla colonna Output Shape, in quanto ci mostra proprio il percorso del dato nella rete. Vediamo come nel primo strato conv2d l'output shape sia 148, 148, 64. Analizziamo un attimo meglio questa informazione. Come mai se le nostre immagini sono 150x150, la rete neurale prende in input una immagine 148x148? La risposta è perché la convoluzione che stiamo usando utilizza una griglia 3x3. I primi pixel intorno all'immagine non hanno dei pixel vicini per permettere la sovrapposizione del filtro. Viene quindi rimosso un pixel sull'asse X e Y, riducendo il margine dell'immagine proprio di 1 pixel. Il 64 sta per il numero di convoluzioni applicati all'immagine.

La convoluzione rimuove un margine esterno di 1 pixel dalla immagine. Screenshot preso da Coursera.org.

‍

In seguito alla prima convoluzione vediamo come lo strato di max pooling vada a ridurre la dimensione dell'immagine, riducendolo esattamente della metà. Il processo continua fino a che non arriviamo allo strato flatten, che prende l'output arrivato a quel punto e lo appiattisce in un singolo vettore. Questo viene fornito ad uno strato denso di 512 neuroni e poi si arriva alla fine della rete con l'output singolo, 0 oppure 1.

Per dire a Tensorflow che l'architettura del modello è conclusa dobbiamo usare il comando compile. Useremo l'ottimizzatore Adam, una loss function di crossentropia binaria e l'accuratezza come metrica di performance.

Procediamo ora con lo scrivere la pipeline di pre-processing delle immagini da fornire al modello.

Preprocessing e consegna delle immagini al modello

Il prossimo step è quello di fare preprocessing sulle immagini per assicuraci che siano adatte al nostro modello. Esse verranno ridimensionate a prescindere dalla dimensione originale, convertite in float64 e associate alla loro etichetta (cane o gatto). Queste informazioni verranno poi consegnate al modello. Creeremo due generatori: uno per l'addestramento e uno per la validazione. Ognuno di questi, inoltre, convertirà le immagini in valori numerici normalizzati tra 0 e 255. 255 è il valore massimo di un pixel, quindi un pixel di intensità 255 diventerà 1 mentre un pixel "spento" sarà 0 e ogni valore intermedio sarà proprio compreso tra 0 e 1.

In Tensorflow tutto questo viene fatto con ImageDataGenerator. Una delle particolarità che rende ImageDataGenerator così potente è che genera etichette per le nostre immagini automaticamente, basandosi sulla gerarchia e nomenclatura delle cartelle che contengono le immagini.

ImageDataGenerator assegna automaticamente delle label alle nostre immagini in base alla loro posizione nelle cartelle dedicate. Screenshot preso da Coursera.org.

Vediamo come implementare i generatori in Python. Questi ora verranno usati per addestrare il modello, ma non dovremmo preoccuparci di riscalare manualmente le immagini o di fare labeling. Il lavoro sporco lo fa tutto Tensorflow ;)

Addestramento del modello

Addestreremo il modello su 2000 immagini e lo valideremo su 1000. Faremo questo per 15 epoche.

Steps_per_epoch denota il numero di batches da selezionare per un'epoca. Se vengono selezionati 500 steps, la rete userà 500 batch per completare un'epoca. Vediamo le performance del modello durante il training.

Risultati dell'addestramento del modello.

Vediamo come l'accuracy del nostro modello sia intorno al 71% sul set di validazione. Non male ma nemmeno benissimo - su un dataset così piccolo 71% è soddisfacente a mio avviso! Aumentare il numero di immagini darebbe sicuramente risultati più solidi.

Visualizzare le rappresentazioni neurali

Una delle cose più interessanti è vedere come una rete neurale convoluzionale estragga le informazioni salienti dalle immagini e le rappresenti mentre passano tra i vari strati. Useremo un modello di Keras per fare ciò, e gli passeremo gli input del modello convoluzionale addestrato precedentemente. Questa porzione di codice è un po' avanzata, quindi sentitevi liberi di saltarla oppure eseguirla meramente per l'output (che è molto interessante!)

Ecco il risultato

Rappresentazioni intermedie delle convoluzioni e aggregazioni in una rete neurale

Vediamo come le feature più salienti vengano passate di strato in strato e che rendono tale il cane preso in esempio. Vediamo come spiccano le orecchie, gli occhi e il muso. Queste feature vengono mantenute attraverso tutte (o quasi) le rappresentazioni negli strati e servono a far comprendere alla rete neurale com'è fatto un cane. Molto interessante!

Questa tecnica di visualizzare le rappresentazioni della rete neurale è utile perché ci aiuta a comprendere cosa mettono in risalto le convoluzioni e le aggregazioni. Se ci sono cose che non vanno questo è il primo luogo dove andare a guardare. Ad esempio, la rete potrebbe mettere in risalto feature non inerenti che la portano a sbagliare la predizione. In questo caso una analisi manuale è d'obbligo e dovremmo agire sull'architettura della rete.

Valutazione del modello

Prima di passare alla previsione di immagini nuove, vediamo come scrivere il codice che permette mostrare su grafico l'andamento di loss e di accuracy nel set di addestramento e di validazione.

Ecco i risultati - si nota dell'overfitting nel training set. Questo è dovuto alle dimensioni ridotte del dataset, come menzionato. Si nota come nel training la accuracy raggiunga velocemente, già dopo la seconda epoca, una accuracy tra il 95-99%. L'overfitting si verifica quando un modello esposto a un numero insufficiente di esempi apprende pattern che non si generalizzano a nuovi dati, ovvero quando il modello inizia a utilizzare feature irrilevanti per fare previsioni.

L'overfitting è IL problema numero uno nel machine learning, ed è un termine che leggerete parecchie volte in questo blog. Da analisti, il nostro primo obiettivo è quello di evitare l'overfitting e di rendere un modello quanto più generalizzabile possibile.

‍

Andamento della loss e della accuracy nel training e validation set

Predizioni su immagini nuove

Siamo arrivati alla conclusione di questo articolo. Grazie per la tua attenzione! Ricordati di lasciare un commento o di condividere questo post con un collega se hai voglia :) Vediamo ora come caricare una immagine su Colab e usarla per effettuare una classificazione usando il nostro modello predittivo.

Useremo questa immagine di un cucciolo di Labrador per testare il modello.

Immagine per testare il modello

Ecco il codice

E infine ecco la predizione corretta del nostro modello!

L'immagine è stata correttamente classificata come un cane
Andrea D'Agostino
Ciao, sono Andrea D'Agostino e sono un data scientist con 6 anni di esperienza nel campo della business intelligence. Applico tecniche statistiche e di machine learning per aiutare i clienti a trovare e risolvere problemi nei loro asset digitali e a sfruttare le debolezze dei competitor a loro vantaggio.

Sono il fondatore e l'autore di questo blog, il cui obiettivo è raccogliere le informazioni più importanti che ho imparato durante il mio percorso lavorativo e accademico al fine di poter aiutare il lettore a migliorare le sue analisi.

Post correlati

Prevedere il prossimo punto in una serie temporale con Tensorflow e Keras
Scritto
il
18/6/2022
Da
Andrea D'Agostino
in
Deep Learning

Previsione delle serie temporali con LSTM in Tensorflow

Prevedere il prossimo punto in una serie temporale con Tensorflow e Keras

Template per la tokenizzazione e gestione delle sequenze di testo con Tensorflow
Scritto
il
6/6/2022
Da
Andrea D'Agostino
in
NLP

Come tokenizzare e fare padding di sequenze in Tensorflow

Template per la tokenizzazione e gestione delle sequenze di testo con Tensorflow

Come usare una callback per stoppare il training a performance adeguata
Scritto
il
7/5/2022
Da
Andrea D'Agostino
in
Deep Learning

Controllare il training di una rete neurale in Tensorflow

Come usare una callback per stoppare il training a performance adeguata

Privacy policy
Contatti
© Andrea D'Agostino 2022