Ci sono molti strumenti che permettono di identificare un oggetto in un’immagine, come ad esempio il modello bag of visual words, tramite i descrittori di feature SIFT (scale-invariant feature transform) o SURF (speeded up robust feature), oppure l’utilizzo delle macchine a vettori di supporto (SVM, support vector machine); tuttavia, in questi ultimi anni, le reti neurali profonde (DNN, deep neural networks) hanno contribuito con un nuovo impulso alla ricerca e sono, pertanto, sempre più utilizzate. Infatti, l’abilità delle DNN nell’apprendere, a partire da grandi insiemi di esempi, funzioni complesse, non-lineari e ad alta dimensionalità, le rende perfette candidate per i compiti di riconoscimento di immagini. Un tipo particolare di DNN sono le reti neurali convoluzionali (CNN, convolutional neural networks) usate con grande successo per problemi di riconoscimento automatico di pattern bidimensionali come la rivelazione di oggetti, facce e loghi nelle immagini. In questo post introduco alcune specifiche implementazioni di CNN in grado di offrire un’elaborazione cognitiva delle immagini che è molto prossima allo stato dell’arte. Il codice esemplificativo è basato sull’impiego del framework MXNet in combinazione con il linguaggio Python.


MXNet è un progetto open source, distribuito con Apache License Version 2.0, frutto della collaborazione di gruppi di ricerca e sviluppo facenti capo ad importanti istituti quali CMU, NYU, NUS e MIT. MXNet è un progetto del DMLC: un gruppo di aziende, laboratori e università impegnate in realizzazioni che contribuiscono da anni a definire lo stato dell’arte in materia di machine learning.

MXNet è essenzilamente un framework per l’implementazione di soluzioni di apprendimento profondo (deep learning), è sviluppato nativamente in C++ e include interfacce verso i linguaggi e ambienti più diffusi (p.e. Python, Go, R, Matlab, ecc.). Esso può essere eseguito efficientemente sia su CPU sia su sistemi ad alte prestazioni basati su GPU/Cuda, può scalare su architetture distribuite, è sufficientemente leggero per funzionare su dispositivi mobile. E’ anche parte integrante di altri framework come per esempio Turi.

MXnet si fonda su un connubbio tra due modelli di programmazione: la programmazione imperativa (imperative programming) e la programmazione dichiarativa o simbolica (symbolic programming). La programmazione dichiarativa consente di implementare facilmente prototipi svincoltati dalla particolare natura del dato grezzo conferendo maggiore genericità ai modelli risultanti. Un’introduzione a MXnet è fornita nel documento MXNet: A Flexible and Efficient Machine Learning Library for Heterogeneous Distributed System.

Nell’arsenale di MXNet sono incluse molte implementazioni per la creazione di CNN (in parte utilizzate anche in questo post).


Prima di tutto: i dati!

Una collezione di riferimento, liberamente accessibile, per lo sviluppo di algoritmi di processazione di immagini è ImageNet. Questa collezione ha una dimensione che è di gran lunga più grande di qualsiasi altra cosa disponibile nella comunità della computer vision e sta contribuendo allo sviluppo di algoritmi di riconoscimento finemente addestrati che non avrebbero potuto essere altrimenti prodotti.
ImageNet è infatti un imponente archivio di immagini catalogate in categorie e sottocategorie che raccoglie oltre 14 milioni di immagini diverse indicizzate in 21K+ categorie. Per organizzare questa enorme collezione di immagini è usato il database di vocaboli inglesi WordNet.1 La catalogazione delle immagini è eseguita manualmente attraverso il sistema Mechanical Turk di Amazon che consente di organizzare migliaia di esseri umani per eseguire compiti di piccola entità.

Ogni anno la competizione ILSVRC (ImageNet Large Scale Visual Recognition Competition) invita le persone che lavorano nel campo della computer vision a misurare i progressi fatti nel riconoscimento e classificazione automatica delle immagini. I sistemi in competizione sono prima collaudati su sottoinsiemi arbitrariamente grandi di immagini già correttamente etichettate (provenienti da ImageNet) e poi sono chiamati ad etichettarne altre mai viste, infine, durante un workshop che si svolge dopo la competizione, i vincitori condividono e discutono le loro tecniche. Nel 2010 il sistema che vinse era in grado di etichettare correttamente il 72% delle immagini (per gli esseri umani la media era del 95%). Nel 2012 un’équipe guidata da Geoffrey Hinton, dell’università di Toronto, ha raggiunto un livello di precisione dell’84% grazie all’apprendimento profondo. Quest’innovazione ha prodotto rapidi miglioramenti e un’accuratezza del 96% nell’ImageNet Challenge del 2015, l’edizione in cui le macchine hanno superato per la prima volta gli esseri umani.

Il gruppo DMLC rende disponibili reti CNN già addestrate su subset di ImageNet. Come vedremo, questi modelli sono usabili anche con MXNet e ciò può essere un grande vantaggio in quanto l’addestramento su copiose collezioni di immagini è sempre un compito oneroso in termini computazionali, temporali ed economici.


Prossimi passi

Per semplificare, ho organizzato questo post in due sezioni. Nella prima sezione illustro come utilizzare i modelli pre-addestrati di DMLC ottenendo, con minimo sforzo, sistemi di classificazione delle immagini che sono allo stato dell’arte. Nella seconda sezione accenno alle caratteristiche architetturali delle reti CNN e propongo un’implementazione from scratch di una rete CNN basilare in grado di riconoscere immagini appartenenti a due categorie specifiche: cat e airplane.


Caricamento di modelli pre-addestrati allo stato dell’arte

Nel blocco seguente: carico il modello model_21k che è pre-addestrato sull’intero dataset ImageNet (14M+ immagini in 21K+ categorie).

Il caricamento è avviato eseguendo la funzione mx.model.FeedForward.load e specificando i parametri prefix che è una stringa arbitraria usata per generare dinamicamente i nomi dei file (p.e. prefix-symbol.json e prefix-{epoch}.params) in cui sono memorizzati i parametri da caricare e iteration che è il numero dell’epoca (epoch) in cui sono stati generati i parametri. Un’epoca è semplicemente un’unità di misura per l’allenamento di una rete neurale. Si può pensare di allenare la propria rete per un certo numero di epoche e verificare al termine se l’allenamento ha prodotto buoni risultati o meno. Solitamente, durante l’addestramento di una rete neurale, i parametri appresi sono memorizzati periodicamente su disco (p.e. al termine di ogni epoca, ogni 10 epoche, ecc.). Per la rete in esame, DMLC fornisce l’addestramento all’epoca nona, quindi, imposto num_round = 9 per caricare il modello all’epoca data.

Per poter ottenere delle classificazioni human-readable carico in memoria anche la lista synset che associa l’indice numerico progressivo assegnato ad ogni categoria ad un elenco di parole che la descrivono. Questa lista è usata per tradurre l’output della rete (un elenco di indici numerici corrispondenti alle classi con relativa probabilità) in un elenco pesato di parole comprensibili.

Le immagini da riconoscere devono soddisfare specifici requisiti dettati dalla rete e sono quindi elaborate mediante un’apposita funzione di pre-processazione preprocessing.PreprocessImage_21k.

La predizione è eseguita invocando la funzione model.predict.

# import mxnet
import mxnet as mx
# import image preprocessing functions
import preprocessing

# pretrained model file settings
prefix = "./datasets/preloaded/model_21k/Inception"

# get the ninth epoch
num_round = 9

# load model
model = mx.model.FeedForward.load(prefix=prefix, iteration=num_round)

# load textual tags for each classes
# used below to translate the predicted classes in a human-readable way
synset = [l.strip() for l in open('./datasets/preloaded/model_21k/synset.txt').readlines()]

# test image preprocessing
batch = preprocessing.PreprocessImage_21k('./test-images/candle.jpg', True)
# batch = preprocessing.PreprocessImage_21k('./test-images/funny-cat.jpg', True)

# get prediction probability of 1000 classes from model
prob = model.predict(batch)[0]
# argsort, get prediction index from largest prob to lowest
pred = np.argsort(prob)[::-1]
# get top1 label
top1 = synset[pred[0]]
print("Top1: ", top1)
# get top5 label
top5 = [synset[pred[i]] for i in range(5)]
print("Top5: ", top5)

Per stimolare i modelli pre-addestrati utilizzo le seguenti immagini:

Risultato della classificazione dell’immagine della candela (model_21k):

('Top1: ', 'n02948072 Candle, taper, wax light')
('Top5: ', ['n02948072 Candle, taper, wax light', 'n04581829 Wick, taper', 'n04534895 Vigil light, vigil candle', 'n03005515 Chandlery', 'n02948557 Candlestick, candle holder'])

Risultato della classificazione dell’immagine del gatto (model_21k):

('Top1: ', 'n01318894 Pet')
('Top5: ', ['n01318894 Pet', 'n02122878 Tabby, queen', 'n02122725 Tom, tomcat', 'n02123045 Tabby, tabby cat', 'n02122948 Kitten, kitty'])

Altri modelli pre-addestrati, model_bn e model_v3, forniti dal DMLC, possono essere caricati introducendo piccole variazioni al codice precedente, come riportato negli estratti seguenti.

Secondo modello pre-addestrato model_bn:

...
# pretrained model file settings
prefix = "./datasets/preloaded/model_bn/Inception_BN"
num_round = 39
...

# read textual tags for each classes
# used below to translate the predicted classes in a human-readable way
synset = [l.strip() for l in open('./datasets/preloaded/model_bn/synset.txt').readlines()]

# preprocessing of an image
batch = preprocessing.PreprocessImage_BN('./test-images/candle.jpg', True)
# or batch = preprocessing.PreprocessImage_BN('./test-images/funny-cat.jpg', True)
...

Risultato della classificazione dell’immagine della candela (model_bn):

('Top1: ', 'n02948072 candle, taper, wax light')
('Top5: ', ['n02948072 candle, taper, wax light', 'n03666591 lighter, light, igniter, ignitor', 'n04456115 torch', 'n03729826 matchstick', 'n04266014 space shuttle'])

Risultato della classificazione dell’immagine del gatto (model_bn):

('Top1: ', 'n02123045 tabby, tabby cat')
('Top5: ', ['n02123045 tabby, tabby cat', 'n02124075 Egyptian cat', 'n02123159 tiger cat', 'n03126707 crane', 'n03649909 lawn mower, mower'])

Terzo modello pre-addestrato model_v3:

...
# pretrained model file settings
prefix = "./datasets/preloaded/model_v3/Inception-7"
num_round = 1
...

# read textual tags for each classes
# used below to translate the predicted classes in a human-readable way
synset = [l.strip() for l in open('./datasets/preloaded/model_v3/synset.txt').readlines()]

# preprocessing of an image
batch = preprocessing.PreprocessImage_V3('./test-images/candle.jpg', True)
# or batch = preprocessing.PreprocessImage_V3('./test-images/funny-cat.jpg', True)
...

Risultato della classificazione dell’immagine della candela (model_v3):

('Top1: ', 'n02948072 candle, taper, wax light')
('Top5: ', ['n02948072 candle, taper, wax light', 'n02699494 altar', 'n03666591 lighter, light, igniter, ignitor', 'n03729826 matchstick', 'n04456115 torch'])

Risultato della classificazione dell’immagine del gatto (model_v3):

('Top1: ', 'n03126707 crane')
('Top5: ', ['n03126707 crane', 'n04467665 trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi', 'n02123045 tabby, tabby cat', 'n02123159 tiger cat', 'n04252225 snowplow, snowplough'])

Tutti i modelli pre-addestrati si basano su configurazioni di reti CNN di varia complessità e profondità (la profondità è il numero degli strati che le compongono). La profondità influenza la capacità della rete di riconoscere ed estrarre automaticamente le caratteristiche che compongono un’immagine (le features).

Le prestazioni delle reti pre-addestrate fornite da DMLC sono state misurate sul validation set di ILVRC-2012. Per ottenere risultati più pertinenti con il contenuto dell’immagine, le classificazioni sono solitamente limitate a 1000 classi e si considerano i primi 5 o 20 risultati (Top 5 o Top 20) e più raramente il Top 1. Nel caso della rete model_21k si è misurata una validation performance del 68.3% su Top 1, 89.0% su Top 5 e 96.0% su Top 20 (ossia su 100 immagini estratte dal validation set, 96 sono state riconosciute correttamente). Se estendessimo la classificazione alla totalità delle classi (27K+), si noterebbe un sensibile calo delle prestazioni e in particolare: 41.9% su Top 1, 69.6% su Top 5 e 83.6% su Top 20. Il secondo modello model_bn ha una validation performance dell’89.5% su Top 5. Il terzo modello model_v3 ha un validation perfomance del 76.88% su Top 1 e 93.34% su Top 5. Il secondo e terzo modello sono addestrati sul training set di ILVRC-2012 che consiste di 10 milioni di immagini su circa 10000 classi (è un sottoinsieme dell’archivio ImageNet).

Normalmente, dopo aver preparato un dataset dove potenzialmente ci possono essere immagini di tutte le dimensioni, si procede con semplici operazioni di resize di ciascuna immagine, crop centrale ed altre manipolazioni. Questo trattamento è ovviamente esterno, fatto cioè prima di avviare l’allenamento della rete. Una volta pre-trattate, le immagini sono pronte per essere date in pasto alla rete. Ciascuno dei modelli pre-addestrati richiede un tipo specifico di trattamento delle immagini. Per semplificare ho raccolto le varie funzioni di trattamento nel modulo preprocessing che puoi richiedermi compilando la form di contatto.

Convoluzione

Prima di addentrarci nell’implementazione di un classificatore di immagini basato su CNN, vorrei riassumere alcuni argomenti basilari. Il primo argomento è quello della convoluzione.

Come noto, solitamente ad un’immagine reale viene associata una griglia composta da un elevato numero di piccoli quadrati detti pixel. La figura seguente propone, per esempio, un’immagine in bianco e nero a cui è associata una griglia di dimensioni 5×5 pixel.

cnnimagetest

La rappresentazione matematica più indicata per una griglia siffata è una matrice di pari dimensioni (5×5), tale matrice è chiamata matrice di pixel o più in generale matrice di input. Ogni elemento della matrice corrisponde ad un pixel e, nel caso di immagini in bianco e nero, può assumere il valore 1 associato al nero o il valore 0 associato al bianco. Nel caso di un’immagine in tonalità del grigio, la scala di valori ammessi per ogni elemento della matrice è nell’intervallo intero [0,255], dove 0 è associato al nero e 255 al bianco. I valori interi specificano l’intensità luminosa. Le immagini a colori sono rappresentate in modo più complesso perchè l’informazione è multidimensionale. Per rappresentare un colore si usa un determinato spazio definito scegliendo un insieme di colori base. Lo spazio di rappresentazione usato più comunemente è quello RGB (Red-Green-Blue). Un’immagine codificata con tre colori è rappresentata da un gruppo di 3 matrici ciascuna corrispondente ad un canale di colore (R,G,B). Ogni elemento di ciascuna matrice può variare su un intervallo intero [0,255] e specifica l’intensità del colore fondamentale (o colore base).

Utilizzando lo spazio RGB in cui ad ogni colore viene associata una terna di valori, che indicano le quantità di rosso, di verde e di blu che consentono di ottenerlo, l’immagine viene rappresentata come una matrice trimensionale (ovvero un tensore2) composta da tre matrici bidimensionali (ognuna delle quali è associata ad un colore fondamentale).

Nella figura sottostante è mostrata una matrice di input che rappresenta l’immagine precedentemente introdotta3.

convolution_schematic

Come mostrato nella figura, supponiamo di far scorrere (dall’alto in basso e da sinistra a destra) sulla matrice di input, una seconda matrice di dimensioni inferiori, per esempio 3×3, così definita:

\begin{bmatrix}1 & 0 & 1\\0 & 1 & 0\\1 & 0 & 1\end{bmatrix}

La matrice che scorre è chiamata kernel o filtro (filter) o feature detector. Man mano che si sposta il filtro lungo l’area della matrice di input, si effettua un’operazione di prodotto membro a membro, cioè un prodotto scalare, fra i valori del filtro e quelli della porzione della matrice al quale è applicato. Il risultato dello scorrimento del filtro si traduce nella generazione di una matrice di convoluzione come quella illustrata in figura (convolved feature). In questo caso la matrice di convoluzione ha dimensioni inferiori alla matrice di input, tuttavia, come vedremo tra breve, con alcuni accorgimenti (p.e. zero-padding) è possibile conservare le dimensioni originali come nella figura seguente.

paddedconv

Nell’ambito della computer grafica esiste una estesa varietà di filtri applicabili ad un’immagine per evidenziarne specifiche particolarità, per esempio con riferimento alla figura seguente (da sinistra a destra): filtro di nitidezza (sharpening) teso a recuperare/aumentare la nitidezza dell’immagine, filtro di sfocatura (blur), filtro di rilevamento contorni (edge detection) e filtro di rilievo (emboss).

filterscomputergraphics

Cenni sull’architettura delle reti convoluzionali

Le reti CNN sono di fatto delle reti neurali artificiali. Esattamente come queste ultime, infatti, anche le CNN sono costituite da neuroni collegati fra loro tramite dei rami pesati; parametri allenabili delle reti sono sempre quindi i pesi. Tutto quanto noto per le reti neurali ordinarie, cioè forward/backward propagation e aggiornamento dei pesi, vale anche in questo contesto; inoltre un’intera CNN utilizza sempre una singola funzione di costo differenziabile. Tuttavia le CNN muovono dall’assunzione che ogni loro input abbia una precisa struttura da riconoscere ossia l’input non è una sequenza pre-elaborata di dati, ma il dato nella sua forma originale come ad esempio un’immagine. Prendendo come esempio la matrice di input 5×5 precedentemente mostrata, una CNN consisterà certamente di uno strato di ingresso composto da 25 neuroni (=5×5) il cui compito è acquisire il valore di input corrispondente ad ogni pixel e trasferirlo allo strato nascosto successivo. Nel caso di una rete multistrato tradizionale le uscite di tutti i neuroni dello strato di ingresso sarebbero connesse ad ogni singolo neurone dello strato nascosto contribuendo così a definire un’architettura completamente connessa (FC, fully connected). Nel caso delle reti CNN, invece, lo schema delle connessioni è significativamente diverso.

Per esempio, assumendo di utilizzare un filtro 3×3 come quello mostrato in precedenza e traslando di un pixel per volta (il passo di traslazione è chiamato stride), è come se suddividessimo la matrice di input in 9 aree di uguale dimensione (questa operazione di suddivisione è anche chiamata piastrellatura dell’immagine). Ciascuna di queste aree definisce un campo ricettivo (receptive field) di un neurone dello strato nascosto. Nello strato nascosto ci saranno 9 neuroni ciascuno avente come input i valori di uno dei 9 campi ricettivi; in particolare per un neurone gli ingressi sono 9 tanti quanti i pixel coperti dal filtro più un ulteriore ingresso (il bias)4.

receptivefield

Ad ogni ingresso è associato un peso il cui valore corrisponde ad uno specifico elemento del filtro. In sostanza ogni singolo neurone esegue la convoluzione e la rete è addestrata ad apprendere i pesi (ossia gli elementi del filtro) e il valore del bias per minimizzare la funzione di costo. Abbiamo dunque una batteria o depth slice di 9 neuroni ciascuno collegato ad un campo ricettivo. Ogni neurone ha un bias e 3×3 pesi connessi ad un campo ricettivo: si utilizzano questi stessi pesi e bias per tutti i 9 neuroni appartenente allo stesso depth slice. Questo significa che tutti i neuroni del depth slice applicheranno lo stesso filtro (ossia impareranno a riconoscere la stessa feature), solo collocata diversamente nell’immagine di input. Questo rende le reti convoluzionali adattabili all’invarianza di un’immagine alla traslazione. Bias e pesi condivisi sono chiamati shared weights and biases. Quella che qui ho definito batteria di neuroni o depth slice è anche chiamata mappa di feature (feature map) o mappa di attivazione (activation map).

Ovviamente, per riconoscere in modo efficace un’immagine abbiamo bisogno di più filtri applicati alle stesse regioni (ciascuno dovrà imparare a riconoscere differenti specificità), ciò equivale a dire, nel nostro esempio, utilizzare più depth slice di 9 neuroni in parallelo o, equivalentemente, più mappe di attivazione in parallelo. Il numero di mappe di attivazione è chiamato profondità (depth). In altri termini, la profondità corrisponde anche al numero di neuroni nello strato convoluzionale che sono connessi allo stesso campo ricettivo locale dell’input. L’insieme di neuroni appartenenti a mappe di attivazione diverse, entro lo stesso strato convoluzionale, operanti in parallelo sullo stesso campo ricettivo, è chiamato colonna o depth column. Riassumendo: nel nostro esempio, se ogni mappa di feature includesse 9 neuroni (una per ogni campo di ricezione) e avessimo deciso di usare 4 mappe di attivazione (depth=4) il numero di neuroni coinvolti nello strato convoluzionale sarebbe stato 9×4=36 (ossia 9 neuroni per mappa moltiplicato per 4 che è il numero delle mappe).

Per come abbiamo definito le cose nel nostro esempio, l’output di ogni mappa di attivazione è una matrice convoluzionale di dimensioni 3×3. Spesso può essere utile effettuare un’operazione di padding, con degli zeri (da qui zero padding) spazialmente lungo i bordi della matrice di pixel. Nella figura sottostante è mostrato lo zero-padding e (in modo semplificato) il processo convolutivo su uno specifico campo ricettivo mediante un neurone all’interno di una mappa di attivazione.

paddingwconv

Nell’esempio della figura precedente si è utilizzato uno zero-padding pari a 1. Uno zero-padding a 0 vuol dire che in pratica non si ha nessun padding; con uno zero-padding a 1 significa che tutta la matrice di input avrà, per ciascuna dimensione, un bordo di grandezza 1 di zeri e così via. Lo zero-padding ci consente di ovviare al fatto che le dimensioni del receptive field e dello stride scelti potrebbero non permettere l’intera convoluzione rispetto alle dimensioni della matrice di input.

Una CNN può comporsi di più strati di convoluzione collegati in cascata. L’output di ogni strato di convoluzione è un insieme di matrici di convoluzione (ciascuna generata da una mappa di attivazione). L’insieme di queste matrici definisce un nuovo volume di input utilizzato dalle mappe di attivazione dello strato successivo. Più strati convoluzionali possiede una rete e più feature dettagliate essa riesce ad elaborare.

Solitamente in una rete CNN ogni neurone produce un output tale per cui, superata una certa soglia di attivazione, l’uscita è proporzionale all’ingresso e non limitato superiormente. In termini rigorosi si dice che la funzione di attivazione del neurone è una funzione rampa o ReLU. Una funzione siffatta consente all’uscita di conservare l’intensità dello stimolo di input (input alto=>uscita alta). Inoltre la derivata di una funzione rampa è una funzione gradino ossia una funzione che restituisce uscita costante 1, se il segnale in ingresso è positivo. Ricordando che in fase di retropropagazione (backpropagation) bias e pesi sono iterativamente aggiornati per minimizzare una funzione di costo – e questo aggiornamento dipende dalle derivate parziali della funzione di costo rispetto a ciascuno peso e bias (gradiente) – i contributi dei vari gradienti (data l’azione del gradino) contribuiscono in modo costante alla variazione e non per piccole frazioni. Matematicamente: la derivata parziale della funzione di costo rispetto ad un peso di un generico neurone N è data dal prodotto delle derivate parziali delle funzioni di attivazione rispetto ai pesi dei neuroni a cui nella propagazione in avanti è collegata l’uscita del neurone N. Se la derivata della funzione di attivazione non fosse un gradino e fosse tale da poter assumere piccoli valori reali nell’intorno di 0 (come p.e. nel caso della derivata di tanh o della funzione sigmoidea per x grande in valore assoluto) allora i contributi dei piccoli gradienti man mano che si retropropagano (e quindi sono moltiplicati tra loro) contribuirebbero a produrre variazioni sempre più piccole sia dei pesi sia dei bias nei neuroni (moltiplicazioni a cascata di piccole frazioni). Dunque si assisterebbere ad un apprendimento a due marce: più lento per gli strati prossimi all’ingresso (man mano che i gradienti si propagano all’indietro, le moltiplicazioni di piccole frazioni producono variazioni sempre più piccole), più veloce per gli strati prossimi all’uscita. La conseguenza è un ritardo nell’apprendimento generale della rete. Il fenomeno chiamato decadimento del gradiente (fuga del gradiente o vanishing gradient) in retroproagazone è risolto appunto con l’impiego della funzioni di attivazione ReLU (o variazioni della stessa famiglia per casi più specifici). Nelle reti neurali costituite da molti strati si tende a preferire l’attivazione ReLU per il problema del decadimento del gradiente e con il conseguente vantaggio di un apprendimento più rapido. Ciononstante la funzione tanh può ancora essere usata nelle CNN non particolarmente profonde e a patto di intervenire su altri parametri che regolano l’apprendimento della rete (p.e. momentum).

Ulteriori approfondimenti sul problema del decamento del gradiente possono essere trovati nei seguenti link:

Dunque, una rete CNN si compone di strati convoluzionali alternati da strati di attivazione (generalmente ReLU) come nella figura sottostante5.

Come mostrato nella figura, le CNN usano anche degli strati di pooling posizionati subito dopo gli strati convoluzionali. Uno strato di pooling suddivide le matrici convoluzionali in regioni e seleziona un unico valore rappresentativo (valore massimo max-pooling o valore medio average pooling) al fine di ridurre i calcoli degli strati successivi e aumentare la robustezza delle feature rispetto alla posizione spaziale. In sostanza il pooling sottocampiona spazialmente ogni mappa di feature di input. Nella figura seguente è mostrato un esempio di max-pooling (selezione del massimo in ciascuna regione).

Source: http://cs231n.github.io

Source: http://cs231n.github.io

Source: http://cs231n.github.io

Source: http://cs231n.github.io

Dopo un’alternanza di strati convoluzionali e di pooling, per i compiti di classificazione, all’ultimo strato della gerarchia è usata una rete neurale tradizionale MLP con funzione di attivazione di tipo softmax (esponenziale normalizzato) per lo strato di uscita. Tale rete è anche conosciuta come multinomial logistic regression. La logistic regression è un classificatore probabilistico lineare. Esso è parametrizzato da una matrice di pesi e da un vettore di bias. Per questo tipo di rete si dimostra che le attivazioni dei neuroni output sono probabilità a posteriori di appartenenza di classe e per questo motivo il numero dei neuroni di uscita è pari al numero di classi.

fig1

Il multilayer perceptron (MLP) è una rete neurale artificiale di tipo feedforward che, esattamente come tutte le reti di questo tipo, mappa un insieme di dati di input su uno specifico insieme di valori di output. Un MLP ha la caratteristica di possedere più strati di neuroni ed inoltre ciascuno strato è completamente connesso a quello successivo. Fatta eccezione per i neuroni di input, ogni neurone possiede una funzione di attivazione non lineare. Il MLP rappresenta una funzione di approssimazione universale ed è compatibile con la struttura generale di una rete neurale convoluzionale, la quale viene allenata tramite un algoritmo di backpropagation, cosa che fa anche il MLP.

Da un certo punto di vista, possiamo dire che una rete CNN, per la classificazione, è un sistema che si compone di due componenti fondamentali:

  • Componente di riconoscimento ed estrazione automatica delle feature. Il riconoscimento avviene mediante applicazione di banchi paralleli di filtri convoluzionali i cui elementi sono appresi automaticamente mediante algoritmo del gradiente discendente (essi corrispondono ai pesi delle connessioni dei neuroni). Intuitivamente questa componente ha come obiettivo quello di apprendere dei filtri che si attivano in presenza di un qualche specifico tipo di feature in una determinata regione spaziale dell’input.
  • Componente di classificazione.

L’addestramento di una CNN coinvolge entrambe le componenti in cui i parametri degli elementi costitutivi (p.e. neuroni) sono automaticamente variati al fine di minimizzare una funzione di costo.

Nell’articolo Deep Learning using Linear Support Vector Machine di Yichuan Tanga viene mostrato come, la sostituzione della rete MLP con una support vector machine (SVM) porti a miglioramenti significativi delle performance su diversi dataset popolari come il MNIST (database pubblico di cifre scritte a mano), il CIFAR-10 (10 classi di immagini varie prese da internet) e ICML 2013 (che riguarda il riconoscimento delle espressioni facciali). Nella figura seguente è mostrata un’architettura combinata di CNN con SVM ove la CNN è mantenuta nella sua architettura generale ma le uscite dell’ultimo strato nascosto sono usate come feature anche per addestrare una SVM.

ccn-svm

La capacità di una rete neurale convoluzionale può variare in base al numero di strati che essa possiede. Raramente si può trovare un solo strato convoluzionale, a meno che la rete in questione non sia estremamente semplice. Di solito una CNN possiede una serie di strati convoluzionali: i primi di questi, partendo dallo strato di ingresso ed andando verso lo strato di uscita, servono per ottenere feature di basso livello, come ad esempio linee orizzontali o verticali, angoli, contorni vari, ecc; più si scende nella rete, andando verso lo strato di uscita, e più le feature diventano di alto livello, ovvero esse rappresentano figure anche piuttosto complesse come dei volti, degli oggetti specifici, una scena, ecc. In sostanza dunque più strati convoluzionali possiede una rete e più feature dettagliate essa riesce ad elaborare. La figura seguente mostra le feature estratte ai vari livelli di una rete convoluzionale, per un esame visuale delle modalità operative delle reti profonde suggerisco di installare e provare il Deep Visualtization Toolbox di Jason Yosinski.

hierarchical_features

Esistono diverse tipologie di reti CNN, nella figure seguenti sono mostrate le architetture di tre reti famose: AlexNet6, VGGnet7, GoogLenet8..

La rete AlexNet è una fra le prime reti neurali convoluzionali che ha riscosso un enorme successo. Vincitrice della competizione ILSVRC del 2012, questa rete è stata la prima ad ottenere risultati più che buoni su un dataset molto complicato come ImageNet, utilizzando un’architettura simile a quella descritta in precedenza: una serie di strati convoluzionali seguiti da un’altra serie di strati FC.

VGG è il nome di un team di persone che hanno presentato le proprie reti neurali durante la competizione ILSVRC-2014. Si parla di reti al plurale in quanto sono state create più versioni della stessa rete, ciascuna possedente un numero diverso di strati. In base al numero di strati, ognuna di esse viene solitamente chiamata VGG-n. Tutte queste reti risultano essere più “profonde” rispetto a quella di AlexNet. Per “profonde”, si intende il fatto che sono costituite da un numero di strati con parametri allenabili maggiore di AlexNet (p.e. da 11 a 19 strati allenabili in totale).

cnnarchitecture

GoogLeNet fu costruita nel 2014 da un team di dipendenti di Google ed è chiamata così in onore di una delle prime reti convoluzionali, ovvero LeNet. GoogLeNet è la rete che possiede l’architettura più complessa fra quelle accennate. Come si è visto con le reti VGG, il metodo più diretto per aumentare le performance di una CNN è quello di aumentare le loro dimensioni lungo la profondità. Tuttavia per GoogLeNet l’aumento è inteso sia come incremento di profondità, cioè il numero di livelli che la rete possiede, sia come incremento della larghezza della rete, ovvero il numero di strati presenti su uno stesso livello. Segue lo schema architetturale di GoogLeNet.

GoogLeNet Architecture

GoogLeNet Architecture

Una rete CNN costruita from scratch per il riconoscimento di immagini

A questo punto l’obiettivo è implementare from scratch una CNN dimostrativa in grado di operare su due classi di immagini: cat e airplane. Per la generazione di training, validation e testing set utilizzo il dataset STL-10 che è un sottoinsieme di ImageNet. STL-10 consiste di 13000 immagini a colori di dimensioni 96×96 pixel organizzate in 10 classi più altre 100000 immagini non classificate utilizzabili per apprendimento non supervisionato. Le immagini classificate sono organizzate in 5000 immagini (500 per classe) utilizzabili per il training e 8000 immagini (800 per classe) utilizzabili per validation e testing.

Preparazione di training, validation e testing set

Basandomi sulle funzioni raccolte nel modulo STL-10 Utils ho estratto 2600 immagini relative alle categorie cat e airplane. 1820 (70%) immagini sono usate per il training set, 520 (20%) per il validation set, 260 (10%) per il testing set. Ogni set contiene un numero equivalente di immagini di entrambe le categorie. Ho altresì generato tre file “.lst” per train, validation e testing set ciascuno contenete l’elenco delle immagini e per ciascuna la rispettiva classe di appartenenza (0 per airplane e 1 per cat). Infine, utilizzando il tool im2rec, distribuito con il framework MXNet, ho raggruppato le immagini in due archivi binari (aventi estensione “.rec”). Ho configurato il tool im2rec in modo da eseguire contestualmente alla creazione dell’archivio anche il resize a 28×28 e la conversione in grayscale.

Al termine i dataset sono così organizzati:

  • train.lst: tabella associativa tra classi e immagini del training set
  • validation.lst: tabella associativa tra classi e immagini del validation set
  • test.lst: tabella associativa tra classi e immagini del testing set
  • train_28_gray.rec: archivio delle immagini 28×28 grayscale di training
  • val_28_gray.rec: archivio delle immagini 28×28 grayscale di validation
  • test_28_gray.rec: archivio delle immagini 28×28 grayscale di test

Se sei interessato al codice python per l’estrazione delle immagini e la generazione dei file, puoi richiederlo compilando la form di contatto. Puoi anche visitare MXNet Python Data Loading API per approfondimenti sull’impiego di im2rec e il formato dei file “.lst”.

Creazione e training della rete CNN

Nel blocco seguente, eseguo anzitutto le importazioni delle librerie necessarie tra cui spicca, ovviamente, il wrapper python per MXNet. Successivamente creo due istanze della classe mx.io.ImageRecordIter. Gli oggetti di tipo ImageRecordIter sono essenzialmente degli iteratori su blocchi di immagini. Il primo iteratore è configurato per lavorare sulle immagini del training set, il secondo sulle immagini del validation set.

Nel caso del training set, l’iteratore è costruito affinchè esegua casualmente dei ritagli rand_crop=True e dei ribaltamenti orizzontali rand_mirror=True delle immagini (ciò aiuta la rete ad apprendere in modo più robusto); questi trattamenti delle immagini non sono richiesti sul validation set. Su entrambi i set imposto la dimensione dell’immagine data_shape = (1,28,28) dove 1 rappresenta il numero di canali delle immagini e i successivi valori rappresentano rispettivamente larghezza e altezza. Imposto inoltre batch_size = 10 su entrambi i set. batch_size rappresenta il numero di elementi di input, cioè di immagini in questo caso, che vengono processate nella rete di volta in volta (importante per l’allenamento di una rete). Questo parametro va settato in base alla memoria che si ha a disposizione sul proprio computer (memoria della scheda video se si lavora in modalità GPU, oppure la RAM se si lavora in modalità CPU). La variazione di questo parametro non intacca le prestazioni della rete in termini di accuratezza della classificazione. Certamente un valore più basso di batch size provocherà una fase di training più lenta, perchè si processano meno immagini alla volta, ma ciò è in linea col fatto che si possiede un hardware più limitato. Questo parametro non deve essere necessariamente identico su entrambi training e testing set.

# import library
# neural network performance analysis
import seaborn as sns
import mxnet as mx
import sys,logging,numpy,random,csv
from sklearn.metrics import roc_auc_score, auc, precision_recall_curve, roc_curve, average_precision_score

train = mx.io.ImageRecordIter(
	path_imgrec = './datasets/stl10/images/train_28_gray.rec',
	path_imglist="./datasets/stl10/images/train.lst",
	data_shape = (1,28,28),
	batch_size = 10,
	rand_crop = True,
	rand_mirror = True
)

val = mx.io.ImageRecordIter(
	path_imgrec = './datasets/stl10/images/val_28_gray.rec',
	path_imglist="./datasets/stl10/images/val.lst",
	rand_crop = False,
	rand_mirror = False,
	data_shape = (1,28,28),
	batch_size = 10
)

Nel blocco seguente: configuro il logger per poter giovare di una visualizzazione in debug durante l’addestramento. Per la riproducibilità dei risultati imposto il generatore di numeri casuali specificando il valore del seme (seed) mx.random.seed(100)9. Imposto inoltre devs = mx.cpu() avendo cura di indicare quale processore utilizzare per l’elaborazione (nel mio caso la CPU). Suggerisco di dare una lettura a Run MXNet on Multiple CPU/GPUs with Data Parallel per capire come sfrutturare al meglio CPU e GPU.

# configure logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# set seeds for reproducibility
mx.random.seed(100)

# device used. CPU in my case.
devs = mx.cpu()

Finalmente, nel blocco seguente, specifico l’architettura della rete CNN in modalità “dichiarativa” (si noti l’assenza di qualsiasi riferimento a variabili con dati).

Ho 4 strati con parametri allenabili: una serie di 2 strati convoluzionali seguita da 2 strati FC (completamente connessi). Ogni strato convoluzionale è seguito da uno strato tanh act_type="tanh" e da uno strato di MAX pooling pool_type="max" (l’attivazione tanh su questa tipologia di rete risulta preferibile alla ReLU). Tutti gli strati di pooling hanno una regione di estensione 2×2 kernel=(2,2) ed uno stride pari a 2 stride=(2,2): ciò vuol dire che si utilizza sempre un overlapping pooling. Tale scelta è dovuta al fatto che questo tipo di pooling incrementa leggermente le prestazioni della rete rispetto al normale pooling senza overlapping. Dei 2 strati FC il primo possiede 500 neuroni num_hidden=500, mentro l’ultimo possiede 2 unità num_hidden=2 corrispondenti al numero di classi di nostro interesse (cat e airplane).

Una singola fase di forward propagation implica le seguenti principali operazioni: il primo strato convoluzionale accetta in input una struttura dati 28x28x1 che è la matrice di input (tipicamente il generico input di uno strato convoluzionale è anche chiamato volume) e ad essa applica 20 filtri num_filter=20, ognuno di dimensioni 5×5 kernel=(5,5) con uno stride di 1 (valore di default) e nessun zero-padding (valore di default), ottenendo 20 mappe di attivazione di dimensione 24×24. Per calcolare la dimensione del volume in uscita utilizzo le seguenti formule:

W_{2}=\frac{W_{1}-F+2P}{S}+1
H_{2}=\frac{H_{1}-F+2P}{S}+1

dove W1, H1 sono larghezza ed altezza della regione in ingresso, F è il lato dell’area del campo ricettivo, S è lo stride, P è l’ammontare dello zero-padding, K è il numero di filtri. In questo caso particolare: W2=24, H2=24 con F=5, S=1, P=0.

Il secondo strato convoluzionale prende in input il volume 12x12x20 ottenuto dal volume 24x24x20 a cui sono state applicate le funzioni tanh ed overlapping pooling (quest’ultima produce il volume di dimensioni ridotte). Il secondo strato convoluzionale convolve il volume 12x12x20 con 50 kernel di dimensione 5x5x20, uno stride pari a 1 e nessun zero-padding, ottendendo un volume 8x8x5010. Successivamente dopo le operazioni di tanh, viene applicato un altro strato di pooling identico al precedente e si ottiene un volume 4x4x50.

A questo punto il primo dei 2 strati FC possedente 500 neuroni effettua il suo normale lavoro, cioè i vari prodotti e somme per ottenere le attivazioni dei suoi 500 neuroni ottenendo un vettore in uscita di dimensioni 1×500. Analoga cosa con il secondo ed ultimo strato FC, il quale possedendo 2 unità, il numero di classi di nostro interesse, produce in output un vettore di dimensioni 1×2.

La funzione di attivazione softmax dei neuroni in uscita NN_model = mx.symbol.SoftmaxOutput(data=fc2, name='softmax') crea ogni output in [0,1] con somma di tutti gli output pari a 1, consentendo di interpretare la risposta della rete come stime di probabilità.

Per ulteriori approfondimenti su mxbet.symbol.Convolution e valori dei default dei parametri consultare MXNet Python Symbolic API.

data = mx.symbol.Variable('data')

# 1st convolutional layer
conv1 = mx.symbol.Convolution(data=data, kernel=(5,5), num_filter=20)
tanh1 = mx.symbol.Activation(data=conv1, act_type="tanh")
pool1 = mx.symbol.Pooling(data=tanh1, pool_type="max", kernel=(2,2), stride=(2,2))

# 2nd convolutional layer
conv2 = mx.symbol.Convolution(data=pool1, kernel=(5,5), num_filter=50)
tanh2 = mx.symbol.Activation(data=conv2, act_type="tanh")
pool2 = mx.symbol.Pooling(data=tanh2, pool_type="max",kernel=(2,2), stride=(2,2))

# 1st fully connected layer
flatten = mx.symbol.Flatten(data=pool2)
fc1 = mx.symbol.FullyConnected(data=flatten, num_hidden=500)
tanh3 = mx.symbol.Activation(data=fc1, act_type="tanh")
# 2nd fully connected layer
fc2 = mx.symbol.FullyConnected(data=tanh3, num_hidden=2)

# Output. Softmax output since we'd like to get some probabilities.
NN_model = mx.symbol.SoftmaxOutput(data=fc2, name='softmax')

Dopo la definizione in modalità dichiarativa dell’architettura della rete passo alla creazione del modello mediante l’instanziazione della classe mx.model.FeedForward.

In fase di costruzione del modello specifico il contesto di esecuzione ctx=devs (la CPU nel mio caso, ma in casi più complessi potrebbe essere una lista di GPU), l’architettura della rete symbol=NN_model, il numero di epoche num_epoch=400, il valore di learning rate learning_rate=0.0001. Il learning rate è un fattore moltiplicativo per il gradiente; serve a specificare/regolare il grado di apprendimento, o meglio di aggiornamento, rispetto ai pesi e al bias.

Un altro parametro specificato è il momentum momentum=0.9. Il momentum indica il peso dell’aggiornamento precedente dei parametri durante l’algoritmo di discesa del gradiente (gradient-descent). Solitamente si lascia sempre il valore 0.9 in quanto è stato visto che in questo modo si ottengono buoni risultati.

Infine, l’ultimo parametro specificato è il weight decay weight_decay=0.00001 è un parametro che influenza il termine di regolarizzazione che appare nella formula per il calcolo della funzione di costo:

C(W,b)=\frac{1}{2 \cdot m}\cdot \sum_{i=1}^{m} [y^{(i)}-f_{W,b}(x^{(i)})]^{2}+wd\cdot \left \| W \right \|^{2}

dove fW,b(x) è l’uscita della rete, yx(i) sono le uscite target in relazione all’ingresso x(i), m è il numero di campioni di training. W rappresenta tutti i pesi della rete ed è considerato come un vettore. Il secondo addendo è un fattore di regolarizzazione. Il weight decay (wd) regola l’effetto del fattore di regolarizzazione: un valore elevato rende la rete incapace di trattare le non linearità annullando l’effetto di gran parte dei pesi (in pratica è come se utilizzassimo una rete più semplice), valori bassi rendono la rete più robusta attenuando moderantamente l’effetto dei pesi ma consentendo di trattare le non linearità senza incorrere in overfitting. Il weight decay influenza i gradi di libertà della rete. Anche qui di solito viene lasciato intorno ad un valore fisso (0.0005 o valori poco distanti).

Per rinfrescare i concetti basilari sulle reti neurali suggerisco la lettura di Reti Neurali Artificiali Tutorial.

model = mx.model.FeedForward(
	ctx = devs,
	symbol = NN_model,
	num_epoch = 400,
	learning_rate = 0.0001,
	momentum = 0.9,
	wd = 0.00001
)

Il passaggio successivo consiste nell’avviare l’apprendimento della rete mediante l’invocazione della funzione model.fit(...).

fit è una funzione definita nella classe mx.model.FeedForward e, all’invocazione richiede la specificazione di alcuni parametri. Anzitutto è necessario fornire il training set X=train e il validation set eval_data=val. Al termine nell’esecuzione di ogni batch è possibile collegare l’invocazione di una funzione batch_end_callback=mx.callback.Speedometer(batch_size=10) in grado di visualizzare la velocità di apprendimento (numero di campioni processati al secondo). La funzione Speedometer utilizza l’elenco di funzioni eval_metric=['accuracy',roc_auc_score] per calcolare metriche e visualizzarne i valori al termine di ogni batch sul training set. In eval_metric è possibile specificare metriche built-in (accuracy, top_k_accuracy, f1, mae, mse, rmse, cross-entropy) oppure metriche implementate in funzioni esterne come roc_auc_score che è importata da sklearn.metrics. La metrica built-in accuracy è calcolata come il rapporto tra il numero di campioni correttamente classificati (veri positivi + veri negativi) diviso il numero totale di campioni. La metrica roc_auc_score calcola l’area sotto la curva ROC. Speedometer calcola le suddette metriche sul train set al termine di ogni batch. Quindi se 1000 sono i campioni e batch size è 10, l’addestramento avverrà su 100 blocchi e ogni 50 blocchi frequent=50 saranno visualizzate le statistiche sulle prestazioni della rete in apprendimento. Al termine di ogni epoca saranno anche visualizzate le stesse metriche elencate in eval_metric sul validation set.

Al termine dell’esecuzione di ogni epoca è possibile collegare un’ulteriore funzione in grado di salvare i parametri appresi dalla rete in un checkpoint file su disco epoch_end_callback=mx.callback.do_checkpoint(prefix="rot_tanh_28_5_400",period=1) dove prefix è una stringa arbitraria che è utilizzata per generare automaticamente il nome del file di checkpoint (con indice numerico progressivo) e period indica ogni quante epoche deve essere eseguito il salvataggio. Il salvataggio dei dati appresi su disco consente di distribuire i modelli della rete già pre-addestrati all’epoca che si ritiene più idonea. Per caricare un modello pre-addestrato si può procedere in modo esattamente analogo a quanto già visto precedentemente per i modelli di DMLC.

model.fit(X=train,
	eval_data=val,
	eval_metric=['accuracy',roc_auc_score],
	batch_end_callback=mx.callback.Speedometer(batch_size=10, frequent=50),
	epoch_end_callback=mx.callback.do_checkpoint(prefix="rot_tanh_28_5_400", period=10)
)

La curva ROC (Receiver Operating Characteristics) è una tecnica statistica che che permette di misurare l’accuratezza di una rete lungo tutto il range dei valori possibili. L’area sottostante alla curva ROC (AUC, acronimo dei termini inglesi Area Under the Curve) è una misura di accuratezza. Se un’ipotetica rete addestrata discriminasse perfettamente tutti gli input, l’area della curva ROC avrebbe valore 1, cioè il 100% di accuratezza. Nel caso in cui la rete non discriminasse per niente gli input, la curva ROC avrebbe un’area di 0.5 (o 50%) che coinciderebbe con l’area sottostante la diagonale del grafico. L’area sotto la curva può assumere valori compresi tra 0.5 e 1.0. Tanto maggiore è l’area sotto la curva (cioè tanto più la curva si avvicina al vertice del grafico) tanto maggiore è il potere discriminante della rete. Una rete con AUC compreso tra 0.5 e 0.7 può definirsi poco accurata, una rete con AUC compreso tra 0.7 e 0.9 è moderatamente accurata, una rete con AUC compreso tra 0.9 e 1.0 è altamente accurata.

Nella figura seguente sono mostrati due grafici che ci consentono di analizzare velocemente le prestazioni della rete comparate su training e validation set nello spazio ROC e nello spazio PR con indicazione dell’AUC come metrica di valutazione.

roc-pr

In un problema di decisione binaria, le etichette di un classificatore possono essere indicate come positive o negative. La decisione fatta dal classificatore può essere rappresentata in una struttura nota come matrice di confusione (confusion matrix). La matrice di confusione è suddivisa in quattro blocchi. 1) Veri Positivi (TP): sono campioni correttamente etichettati come positivi. 2) Falsi Positivi (FP): sono campioni negativi erroneamente etichettati come positivi. 3) Veri Negativi (TN): sono campioni negativi correttamente etichettati come negativi. 4) Falsi Negativi (FN): sono campioni positivi etichettati erroneamente come negativi. Una matrice di confusione consente di ottenere tutte le metriche necessarie per creare le rappresentazioni mostrate nei grafici precedenti. Nello spazio ROC sono riportate in ascissa le percentuali di errore Falso Positivo (FPR) e in ordinata le percentuale di errore Vero Positivo (TPR). FPR misura la frazione di campioni negativi che sono stati erroneamente classificati come positivi e TPR misura la frazione di campioni positivi che sono stati correttamente classificati come tali. Il grafico che otteniamo è la curva ROC. Nello spazio PR sull’ascissa sono rappresentati i valori di Richiamo (Recall) e sull’ordinata i valori di Precisione (Precision). Richiamo e TPR coincidono, mentre la precisione misura la frazione di campioni classificati come positivi che sono realmente positivi. Per ulteriori informazioni consultare Precision and Recall su Wikipedia En e The Relationship Between Precision-Recall and ROC Curves.

Segue il codice python per la generazione dei suddetti grafici.

y_train=[]
y_val=[]
with open('./datasets/stl10/images/train.lst','r') as csvfile:
	spamreader = csv.reader(csvfile, delimiter='\t')
	for row in spamreader:
		y_train.append(int(row[1]))
with open('./datasets/stl10/images/val.lst','r') as csvfile:
	spamreader = csv.reader(csvfile, delimiter='\t')
	for row in spamreader:
		y_val.append(int(row[1]))

y_train=np.array(y_train)
y_val=np.array(y_val)

y_eval_pred = model.predict( val )[:,1]
y_train_pred = model.predict( train )[:,1]
t_fpr, t_tpr, _ = roc_curve( y_train, y_train_pred )
t_precision, t_recall, _ = precision_recall_curve( y_train, y_train_pred )
e_fpr, e_tpr, _ = roc_curve( y_val, y_eval_pred )
e_precision, e_recall, _ = precision_recall_curve( y_val, y_eval_pred )

sns.set_style('whitegrid')
plt.figure( figsize=(15, 6) )
train_auc = numpy.around( auc(t_fpr, t_tpr), 4 )
eval_auc = numpy.around( auc(e_fpr, e_tpr), 4 )
plt.subplot(121)
plt.title( "ROC" )
plt.xlabel( "FPR" )
plt.ylabel( "TPR" )
plt.plot( t_fpr, t_tpr, alpha=0.6, c='m', label="Training AUC = {}".format( train_auc ) )
plt.plot( e_fpr, e_tpr, alpha=0.6, c='c', label="Evaluation AUC = {}".format( eval_auc ) )
plt.plot( [0,1], [0, 1], c='k', alpha=0.6 )
plt.legend( loc=2 )
train_auc = numpy.around( average_precision_score( y_train, y_train_pred ), 4 )
eval_auc = numpy.around( average_precision_score( y_val, y_eval_pred ), 4 )
plt.subplot(122)
plt.title( "ROC" )
plt.xlabel( "Recall" )
plt.ylabel( "Precision" )
plt.plot( t_recall, t_precision, alpha=0.6, c='m', label="Training AUC = {}".format( train_auc ) )
plt.plot( e_recall, e_precision, alpha=0.6, c='c', label="Evaluation AUC = {}".format( eval_auc ) )
plt.plot( [0,1], [0.5, 0.5], c='k', alpha=0.6 )
plt.ylim(0.5, 1.0)
plt.legend()
plt.show()

Finalmente è arrivato il momento di provare le capacità predittive della rete. Utilizzo il testing set composto di 260 immagini equamente distribute sulle due categorie. Questo set include immagini che non sono mai state sottoposte alla rete. Le immagini possono essere estratte dall’archivio binario “.rec” e, mediante il file “.lst”, è possibile risalire alla classe di appartenenza di ciascuna. Ciclando su ogni immagine invoco la funzione model.predict per ottenere l’elenco di probabilità per ciascuna delle due classi. In breve, questi i risultati che ho ottenuto:

success: 237, total: 260, cat: 129, plane 108

Dunque, su 260 immagini il 91% circa (237 su 260) è stato correttamente riconosciuto, con il 99% dei successi su cat e l’83% su airplane.

Risultato di notevole interesse e certamente migliorabile lavorando sui parametri della rete. Ma questa è un’altra storia.

Puoi richiedermi dataset, modelli pre-addestrati e moduli software custom compilando la form di contatto.


Per ulteriori approfondimenti, queste sono alcune delle fonti che ho consultato:

  1. Grazie all’organizzazione gerarchica di WordNet, se un sistema di riconoscimento non è sicuro che una data immagine mostri, per esempio, un cane, può verificare il successivo livello (mammiferi) o quello superiore (animali). Questo è un grande vantaggio derivante dall’impiego di WordNet.
  2. Ovviamente la definizione di tensore è matematicamente più rigorosa.
  3. Fonte: http://deeplearning.stanford.edu/wiki/index.php/Feature_extraction_using_convolution
  4. In termini generali, se la matrice di input avesse dimensioni NxMxP con P profondità (p.e. P=3 nel caso di matrice di pixel associata ad un’immagine a colori) un generico campo ricettivo sarebbe composto da 3x3xP elementi che sarebbero gli ingressi del neurone dello strato nascosto ad esso associato. 3×3 è la dimensione del filtro.
  5. Fonte: http://cs231n.github.io
  6. Alex Krizhevsky, Ilya Sutskever, and Geoff Hinton. “Imagenet classification with deep convolutional neural networks”. In Advances in Neural Information Processing Systems 25, pages 1106, 1114, 2012.
  7. Karen Simonyan, Andrew Zisserman. “Very deep convolutional networks for large scale image recognition”. Published as a conference paper at ICLR 2015.
  8. Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., Erhan, D., Vanhoucke, V., and Rabinovich,A. “Going deeper with convolutions”. CoRR, abs/1409.4842, 2014.
  9. Ogni volta che si usa lo stesso seme, si otterrà sempre la stessa identica sequenza di numeri casuali.
  10. Dato un volume WxHxP, l’area di ricezione di ogni neurone di una depth slice è pari WK X HK X P. Dove WK e HK sono larghezza e altezza del kernel. Ogni neurone della slice ha (WK X HK X P) input più il bias.

Pubblicato da lorenzo

Full-time engineer. I like to write about data science and artificial intelligence.

Vuoi commentare?