Skip to content

Reti neurali

Chris Piemonte edited this page Sep 1, 2018 · 3 revisions

1 - Reti neurali

Introdotte negli anni ‘50, ispirate all’architettura del cervello umano e promosse come approssimatori universali, le reti neurali hanno goduto di popolarità relativamente breve, in quanto sono state presto accantonate in seguito alla pubblicazione di ”An introduction to computational geometry” (Minsky, 1969), dove é stata dimostrata l’impossibilità di risolvere problemi non caratterizzati da separabilità lineare in reti a due strati. Fu solo intorno agli anni ‘80 che furono poste le basi matematiche per l’addestramento di reti neurali multi-strato da (Werbos, 1974) e (Hopfield, 82), attraverso l‘algoritmo di backpropagation. Negli ultimi anni, grazie al notevole miglioramento delle risorse computazionali, sono tornate oggetto di nuovo entusiasmo.

1.1 - Modello matematico

Il neurone è l’elemento basilare del sistema nervoso, tanto che può essere considerato l’unità di calcolo primaria alla base della mente umana. In figura è mostrata la rappresentazione biologica di un neurone ed il modello matematico ad esso ispirato: ogni neurone riceve in input il segnale dai suoi dendriti e, una volta elaborato, produce un segnale di output lungo il suo unico assone, che una volta diramatosi lo collega ai neuroni successivi attraverso le sinapsi.

Questa attività biologica può essere rappresentata da un modello matematico nel quale i segnali che viaggiano attraverso gli assoni interagiscono con i dendriti dei neuroni con i quali sono collegati. La modifica delle sinapsi (i pesi w), rappresenta l’apprendimento. Nel modello base i dendriti portano il segnale al corpo della cellula, dove sono sommati: se il risultato di questa somma supera una certa soglia, il neurone attiva l’impulso attraverso l’assone. Tale somma viene attivata da una funzione di attivazione. In altre parole, ogni neurone esegue un prodotto vettoriale tra i suoi input e il suo set di pesi, somma un termine di distorsione (bias) e infine applica una funzione di attivazione non lineare. La funzione di attivazione deve necessariamente essere non lineare, in caso contrario la rete neurale si ridurrebbe ad un modello lineare generalizzato.

Nella figura è rappresentata una semplice rete neurale con 4 predittori xj, un singolo strato nascosto composto da 5 neuroni calcolato: layer 1 dove sigma è la funzione di attivazione, W è il set di pesi, x 0 è il vettore di input e b è il bias. L'output è data da una singola unità y.

L'idea di base è quella che ogni neurone apprenda una semplice funzione binaria. Lo strato finale è caratterizzato anch'esso dalla presenza di un vettore di pesi e di una funzione di output, che generalmente corrisponde alla funzione identità in problemi di regressione, o alla softmax in problemi di classificazione binaria. Cambiando il numero degli strati e dei neuroni, le reti neurali possono essere considerate come approssimatori universali di funzioni (Hornik, 1989).

Il termine Deep Learning prende nome dalla profondità che caratterizza le reti neurali, dove con profondità ci si riferisce al numero di strati nascosti presenti. Le reti deep vengono infatti chiamate anche MLP, ovvero MultiLayer Perceptron (Goodfellow, 2016).

1.1.1 - Funzione di Attivazione

Lo scopo della funzione di attivazione è quello di aggiungere non-linearità nella rete, permettendo di modellare output che non variano linearmente al variare dei predittori. Questo significa che l'output non può essere rappresentato da una combinazione lineare dell'input. Senza non-linearità, anche aggiungendo diversi strati nascosti in una rete, questi risulterebbero equivalenti ad uno strato solo (single-layer Perceptron).

Sigmoid

Inizialmente la più utilizzata, la funzione sigmoide è stata progressivamente accantonata negli ultimi anni per via di alcune problematiche che comporta a livello pratico.

La prima e più importante è quella relativa alla dissolvenza del gradiente in seguito alla saturazione dei neuroni, ossia quei neuroni che presentano valori di output agli estremi del codominio della funzione di attivazione, in questo caso (0, 1). Tale saturazione diventa problematica durante la fase di Back Propagation, in quanto il gradiente locale assume valori prossimi allo zero, conducendo così all'annullamento del gradiente globale (Glorot - Bengio, 2010). In pratica si ha un flusso utile del gradiente solo per valori di input che rimangono all'interno di una zona di sicurezza, cioè nei dintorni dello zero.

Il secondo problema deriva invece dal fatto che gli output della funzione sigmoide non sono centrati intorno allo zero (LeCun, 98). Si consideri il caso in cui tutti gli input di un nodo sono positivi. L'aggiornamento dei pesi entranti in un determinato neurone avviene in modo proporzionale all'errore in quel nodo (uno scalare) ed il vettore di input. Se tutte le componenti del vettore in input sono positive, tutti gli aggiornamenti dei pesi che arrivano in quel neurone avranno lo stesso segno (il segno dell'errore) e i pesi quindi saranno o tutti incrementati o tutti diminuiti. Se il vettore di pesi in considerazione deve cambiare direzione, questo può avvenire solamente dopo diverse iterazioni di aggiornamento (zigzagando), rallentando così l'apprendimento. Questo si comporta inoltre come una fonte di bias sistematico per i neuroni nello strato successivo della rete. Per aggirare questo problema è stata introdotta come funzione di attivazione la Tangente Iperbolica in quanto funzione dispari. L'apprendimento con Backpropagation con funzioni dispari porta ad una convergenza più veloce rispetto ad un processo con funzioni non simmetriche (Haykin, 2004).

Il terzo e ultimo difetto della funzione sigmoide è che l'operazione esponenziale al denominatore è molto costosa dal punto dal punto di vista computazionale, soprattutto rispetto alle alternative che verranno presentate di seguito.

Tangente Iperbolica

Il problema degli output non centrati sullo zero (Haykin, 2004) della sigmoide può essere risolto ricorrendo all'utilizzo della tangente iperbolica, la quale presenta codominio (−1, 1) centrato sull'origine degli assi. Tuttavia, rimane il problema della saturazione dei neuroni, anzi viene addirittura accentuato, dal momento che la zona di sicurezza risulta ancora più ristretta.

Rectified Linear Unit

La Rectified Linear Unit (ReLU) è diventata popolare negli ultimi anni per via dell'incremento prestazionale che offre nel processo di convergenza: velocizza infatti di circa 6 volte la discesa del gradiente rispetto alle alternative viste finora. Questo risultato è da attribuire in larga parte al fatto che la ReLU risolve il problema della dissolvenza del gradiente (Glorot - Bengio, 2010), non andando a saturare i neuroni. Durante la fase di Back Propagation infatti, se il gradiente calcolato fino a quel punto è positivo questo viene semplicemente lasciato passare, perchè la derivata locale per il quale viene moltiplicato è pari ad uno. Eventuali problemi sorgono invece quando il gradiente accumulato ha segno negativo, in quanto questo viene azzerato (le derivata locale è nulla lungo tutto il semiasse negativo) con la conseguenza che i pesi non vengono aggiornati. Fortunatamente questo problema può essere alleviato attraverso l'utilizzo di un algoritmo SGD (batch size maggiori di 1) (Ioffe, 2015): considerando più dati alla volta c'è infatti la speranza che non tutti gli input del batch provochino l'azzeramento del gradiente, tenendo così in vita il processo di apprendimento del neurone. Al contrario, se per ogni osservazione la ReLU riceve valori negativi, allora il neurone "muore", e non c'è speranza che i pesi vengano aggiornati. Valori elevati del learning rate amplificano questo problema, dal momento che cambiamenti più consistenti dei pesi si traducono in una maggiore probabilità che questi affondino nella "zona morta".

1.1.2 - Apprendimento

Stimare una rete neurale non risulta un compito semplice, trattandosi come abbiamo visto di una complessa funzione gerarchica f(x;W) del vettore di input x e della collezione di pesi W del vettore di input layer 1. Innanzitutto è necessario scegliere opportunamente le funzioni di attivazione in modo che tale funzione risulti differenziabile. Si tratta quindi di risolvere una funzione di perdita L.

Le funzioni di perdita sono generalmente convesse in f, ma non negli elementi di W, con la conseguenza che ci troviamo a dover risolvere un problema di minimizzazione su una superficie che presenta una grande quantità di minimi locali. Una soluzione è quella di effettuare più stime dello stesso modello con differenti inizializzazioni dei parametri, per scegliere infine quello che risulta essere migliore. Una procedura di questo tipo può però richiedere molto tempo, che spesso non è disponibile, pertanto generalmente ci si tende ad accontentare di buoni minimi locali.

I principali metodi utilizzati sono basati sulla tecnica della discesa del gradiente, la cui implementazione in questo contesto prende il nome di Back-Propagation (Werbos, 1974), in virtù del fatto che lo scarto registrato in corrispondenza di un certo dato viene fatto propagare all'indietro nella rete per ottenere le formule di aggiornamento dei coefficienti. Dal momento che layer 1 è definita come una composizione di funzioni a partire dai valori di input della rete, gli elementi di W sono disponibili solo in successione (quella degli strati) e pertanto la differenziazione del gradiente seguirà la regola della catena (chain rule).

Data una generica osservazione (x, y) l'algoritmo di back-propagation prevede di effettuare un primo passo in avanti lungo l'intera rete (feed-forward step) e salvare le attivazioni che si creano ad ogni nodo x lk di ogni strato l, incluso quello di output. La responsabilità di ogni nodo nella previsione del vero valore di y viene quindi misurata attraverso il calcolo di un termine d'errore delta . Nel caso delle attivazioni terminali x lk il calcolo di tali errori è semplice: coincide infatti con i residui o loro trasformazioni, in base a come viene definita la funzione di perdita. Per le attivazioni degli strati intermedidelta viene calcolato invece come somma pesata dei termini d'errore dei nodi che utilizzano x lk come input.

Una volta calcolato il gradiente è possibile procedere con l'aggiornamento del sistema dei pesi. Il calcolo del gradiente ci fornisce la direzione lungo la quale la superficie da minimizzare è più ripida, ma nessuna informazione circa la lunghezza del passo che dovremmo compiere. Tale quantità viene denominata learning-rate e costituisce l'iperparametro più importante da regolare in una rete neurale: valori alti portano ad una convergenza più veloce ma sono rischiosi dal momento che potrebbero saltare il minimo ottimale o fluttuare intorno ad esso, mentre valori bassi sono responsabili di una convergenza lenta e possono comportare il blocco dell'algoritmo in un minimo locale non ottimale.

Stochastic Gradient Descent

Esistono alcune varianti dell'algoritmo di discesa del gradiente che differiscono nella quantità di dati utilizzati nel calcolo del gradiente prima di effettuare l'aggiornamento dei parametri. Quella classica utilizza ad ogni iterazione l'intero dataset. Tuttavia, può spesso risultare più efficiente processare piccole quantità di dati (batch) per volta, generalmente campionate in maniera casuale, da qui il nome Stochastic Gradient Descent (Bottou, 2010). Tale scelta è obbligata quando le dimensioni del dataset sono tali da non poter essere caricato in memoria. Valori estremi del batch come n oppure 1 possono causare problemi rispettivamente di calcoli ridondanti (il gradiente viene ricalcolato sempre su osservazioni simili prima dell'aggiornamento) e di fluttuazioni della funzione da minimizzare, a causa di aggiornamenti troppo frequenti e variabili, essendo basati sulla singola osservazione. Di conseguenza si tendono a scegliere valori intermedi. Indipendentemente dal numero di iterazioni e dalla dimensione del batch prescelta, ogni volta che tutte le osservazioni del dataset vengono utilizzate per il calcolo del gradiente si dice che viene completata un'epoca.

Ottimizzazione discesa del Gradiente

Ci sono alcune modifiche che possono essere apportate all'algoritmo di discesa del gradiente per migliorarne le performance, legate soprattutto al learning rate. Possono sorgere complicazioni invece quando si tratta di minimizzare funzione non convesse, con il rischio di rimanere intrappolati in minimi locali non ottimali. Di seguito verranno presentati alcuni algoritmi di ottimizzazione che mirano alla risoluzione di questo tipo di problematiche.

Confronto tra alcuni metodi di ottimizzazione [source]

  • Momentum: SGD presenta alcune difficoltà in prossimità di aree dove la superficie presenta una curvatura molto più accentuata in una direzione rispetto all'altra (Sutton, 1986). In questo scenario infatti l'algoritmo tende ad oscillare lungo il versante più ripido, rallentando così la convergenza verso il minimo locale ottimale. Momentum (Qian, 1999) è un metodo che aiuta ad accelerare la discesa del gradiente verso la direzione corretta, smorzando l'effetto indotto dalle oscillazioni. Tale risultato si ottiene aggiungendo al termine di aggiornamento corrente una frazione gamma del vettore di aggiornamento precedente.

  • AdaGrad: Momentum permette di adattare i nostri aggiornamenti in relazione alla superficie da minimizzare velocizzando così la convergenza, ma non fa nessuna distinzione circa l'importanza dei parametri, trattandoli tutti allo stesso modo. Adagrad (Duchi, 2011) è un algoritmo di ottimizzazione nato proprio con questo scopo: adattare il learning rate ai parametri, permettendo così di effettuare aggiornamenti più consistenti in corrispondenza dei parametri relativi alle features meno frequenti, e viceversa. In particolare, nella sua regola di aggiornamento, Adagrad utilizza un alpha differente per ogni parametro ad ogni iterazione, modificando quest'ultimo sulla base dei gradienti passati che sono stati calcolati per lo specifico parametro. Uno dei più grandi benefici di Adagrad consiste nell'eliminare la necessità di settare manualmente il learning rate: è necessario impostare solamente il valore iniziale. D'altra parte, il suo punto debole è invece l'accumulo eccessivo del quadrato dei gradienti al denominatore, che comporta un'esagerata e progressiva riduzione del learning rate, il quale tende a diventare infinitamente piccolo, al punto tale che l'algoritmo non è più in grado di acquisire nuova informazione, arrestando così il processo di convergenza.

1.2 - Convolutional Neural Networks

Le Convolutional Neural Networks sono ispirate dall'organizzazione della corteccia visiva animale (Hubel, 1968) (Fukushima, 1980) ed il nome deriva dall'utilizzo di un'operazione matematica lineare chiamata convoluzione. Di conseguenza, una rete neurale classica che implementa operazioni di convoluzione in almeno uno dei suoi strati, viene definita Convolutional. Gli strati composti da operazioni di convoluzione prendono il nome di Convolutional Layers, ma non sono gli unici strati che compongono una CNN: la tipica architettura prevede infatti l'alternarsi di Convolutional Layers, Pooling Layers e Fully Connected Layers (Krizhevsky, 2012).

Come visto nella sezione precedente, le Reti Neurali tradizionali ricevono in input un singolo vettore, e lo trasformano attraverso una serie di strati nascosti, dove ogni neurone è connesso ad ogni singolo neurone sia dello strato precedente che di quello successivo (ovvero "Fully-Connected") e funziona quindi in maniera completamente indipendente, dal momento che non vi è alcuna condivisione delle connessioni con i nodi circostanti. Nel caso l'input sia costituito da immagini di dimensioni ridotte, ad esempio 32×32×3 (32 altezza, 32 larghezza, 3 canali colore), un singolo neurone connesso in questa maniera comporterebbe un numero totale di 32 × 32 × 3 = 3072 pesi, una quantità abbastanza grande ma ancora trattabile. Le cose si complicano però quando le dimensioni si fanno importanti: salire ad appena 256 pixel per lato comporterebbe un carico di 256 × 256 × 3 = 196 608 pesi per singolo neurone, ovvero quasi 2 milioni di parametri per una semplice rete con un singolo strato nascosto da dieci neuroni.

L'architettura Fully-Connected risulta perciò troppo esosa in questo contesto, comportando una quantità enorme di parametri che condurrebbe velocemente a casi di sovradattamento. Inoltre, considerazione ancor più limitante, un'architettura di questo tipo fatica a cogliere la struttura di correlazione tipica dei dati a griglia. Le Convolutional Neural Networks prendono invece vantaggio dall'assunzione che gli input hanno proprio una struttura di questo tipo (LeCun, 1998), e questo permette loro la costruzione di un'architettura su misura attraverso la formalizzazione di tre fondamentali proprietà: l'interazione sparsa (sparse interaction), l'invarianza rispetto a traslazioni (translation invariance), e la condivisione dei parametri (weight sharing) (Zhou, 2015). Il risultato è una rete più efficace e allo stesso tempo parsimoniosa in termini di parametri. L'assunzione di weight sharing è ragionevole dal momento che individuare un determinato pattern è importante in qualsiasi posizione dell'input, e di conseguenza non c'è la necessità di imparare a localizzare la stessa caratteristica in tutte le possibili porzioni dell'input.

1.2.1 - Operazione di Convoluzione

In problemi discreti l'operazione di convoluzione non è altro che la somma degli elementi del prodotto di Hadamard fra un set di parametri (che prende il nome di filtro) e una porzione dell'input di pari dimensioni. L'operazione di convoluzione viene quindi ripetuta spostando il filtro lungo tutta la superficie dell'input, sia in altezza che in larghezza. Questo produce quella che viene chiamata mappa di attivazione, la quale costituisce di fatto il primo strato nascosto della rete. In altre parole, ragionando dalla prospettiva opposta, la mappa di attivazione è formata da neuroni connessi localmente allo strato di input attraverso i parametri del filtro che li ha generati. L'estensione spaziale di questa connettività, che coincide con la dimensione del filtro, costituisce un iperparametro della rete e viene chiamata anche campo recettivo del neurone, o receptive field. Questa rappresenta la prima delle tre proprietà fondamentali delle Convolutional Neural Networks, ossia la sparse interaction.

É importante notare che la singola operazione di convoluzione produce sempre uno scalare, indipendentemente da quali siano le dimensioni del volume di input, sia esso bidimensionale o tridimensionale. Di conseguenza, una volta che il filtro viene fatto convolvere lungo tutta la superficie dell'input, si ottiene sempre una mappa di attivazioni bidimensionale.

Finora abbiamo sempre visto la convoluzione di un singolo filtro ma generalmente un Convolutional Layer è formato da un set di filtri molto più numeroso, diciamo n, tutti con la medesima estensione spaziale. Durante la fase feed-forward ogni filtro viene fatto convolvere lungo la larghezza e l'altezza del volume di input, e pertanto vengono prodotte n mappe di attivazioni bidimensionali, le quali forniscono ognuna la risposta del relativo filtro in ogni posizione spaziale. La loro concatenazione lungo la terza dimensione produce l'output del Convolutional Layer. É facile notare che gli indici dei pesi che definiscono il singolo neurone non dipendono da (i, j), ovvero dalla sua posizione nella mappa di attivazione, ma solamente dal filtro generatore f. In altre parole, questo significa che i neuroni appartenenti alla stessa mappa di attivazione condividono sempre lo stesso set di pesi, ossia quelli del filtro che li ha generati. Questo definisce la seconda proprietà fondamentale delle Convolutional Neural Networks, ossia il weight sharing.

Disposizione finale e numero di neuroni che compongono il volume di ouput del Convolutional layer sono controllati da tre iperparametri: profondità, stride, e padding.

  • Profondità: corrisponde al numero di filtri n che compongono lo strato, ognuno in cerca di caratteristiche differenti nel volume di input.
  • Stride: specifica il numero di pixel di cui si vuole traslare il filtro ad ogni spostamento. Valori più alti muovono il filtro con salti maggiori, e pertanto viene generato un output di dimensioni minori.
  • Padding: a volte può risultare conveniente aggiungere un bordo di zeri al volume di input, in modo così da controllare le dimensioni dell'output ed evitare incongruenze durante le operazioni. Spesso utilizzato per far combaciare la dimensione dell'input con quella dell'output.

1.2.2 - Pooling

Nell'architettura di una CNN è pratica comune inserire fra due o più convolutional layers uno strato di Pooling (Ciresan, 2011), la cui funzione è quella di ridurre progressivamente la dimensione spaziale degli input (larghezza e altezza), in modo da diminuire numero di parametri e carico computazionale, e di conseguenza controllare anche il sovradattamento. Il Pooling Layer opera indipendentemente su ogni mappa di attivazioni applicando un filtro di dimensione F x F che esegue una determinata operazione deterministica (tipicamente il massimo o la media), e pertanto non comporta la presenza di pesi. Anche qui, il calcolo del volume finale dipende, oltre che dalle dimensioni di input, dai due iperparametri richiesti, ossia stride ed estensione spaziale del filtro.