The Backpropagation Un altro argomento di notevole interesse nel campo delle reti neurali riguarda l'algoritmo di backpropagation, uno dei più efficienti sistemi di apprendimento delle reti multilivello caratterizzate da funzioni di uscita differenziabili. Questo algoritmo viene implementato nel toolbox di Matlab in tutte le sue varianti, che nel tempo sono state aggiunte alla versione originale per ottimizzare il tempo di computazione. Si tenga presente che una rete con bias, livello di uscita lineare e livello intermedio sigmoidale è capace di approssimare, se addestrata con l'algoritmo di backpropagation, qualunque funzione con un numero finito di discontinuità. Ciò che rende di queste reti un punto di partenza per tipologie più complesse è la capacità di determinare delle risposte "ragionevoli" ( conformi quindi alla funzione da approssimare ) a input nuovi, ossia non utilizzati come esempi di addestramento. In questo capitolo vedremo di mettere in pratica le conoscenze basilari che abbiamo assimilato sino a questo punto per mostrare come implementare una rete multilivello che si appoggia sulla backpropagation. Feedforward Network Una rete con architettura feedforward è costituita da un livello neuronale di uscita con funzione di trasferimento lineare preceduto da uno o più livelli nascosti di tipo sigmoidale. La funzione lineare come livello ultimo garantisce alla rete di avere in uscita valori non compresi in un range limitato, come è il caso del sigmoide o del gradino. Un esempio di feedforward network è quello riportato in figura 1. In questo caso la rete ha due ingressi, il primo livello è costituito da 4 neuroni con funzione sigmoidale, il secondo da 3 neuroni lineari che costituiscono l'uscita della rete stessa. Figura 1 - Un esempio di feedforward network Il primo passo da compiere è quello di creare l'oggetto che contiene le informazioni della rete che si vuole realizzare. Il comando matlab adatto allo scopo è newff che accetta 4 parametri di ingresso: 1. 2. 3. 4. Una matrice Rx2 contenente i valori minimo e massimo di ogni input della rete Un vettore riga contenente il numero di neuroni che costituisce ogni livello della rete Un array di celle con i nomi delle funzioni di trasferimento utilizzate in ciascun livello Il nome della funzione di addestramento che si vuole utilizzare per allenare la rete Per esempio, la seguente istruzione: • net = newff( [-1 2 ; 0 5 ] , [ 3 1 ] , { 'tansig' , 'purelin' } , 'traingd' ); crea una rete a due livelli che accetta come input i un vettore di due elementi. Il primo ingresso è limitato tra -1 e 2 mentre il secondo varia tra 0 e 5. Il primo livello della rete è costituito da 3 neuroni sigmoidali mentre il secondo da un solo neurone lineare. Traingd, che analizzeremo in seguito, è la funzione di addestramento della rete. Molti dei concetti che abbiamo analizzato per il caso delle funzioni newlin e newp valgono anche per newff. Di conseguenza è possibile modificare i valori dei pesi e dei bias e riportarli al loro valore di default attraverso il comando init. La modifica dei pesi, cosi' come la loro visualizzazione, avviene sempre attraverso le proprietà IW e b dell'oggetto net creato. In questo caso, visto che ci sono più livelli, dobbiamo stare attenti a quali indici specificare a seconda del livello di riferimento. Per esempio: • net.LW{2,1}.initFcn = 'rands'; va a modificare, in modo casuale, i valori di inizializzazione dei pesi che congiungono i neuroni del livello 1 con quelli del livello 2. Altrimenti è possibile modificare direttamente i pesi come al solito: • netl.LW{2,1} = W; dove W è una matrice 1x3 ( ovviamente ci stiamo riferendo alla rete net creata sopra, che ha 3 neuroni nel livello 1 e uno solo nel livello 2 ). Anche la simulazione segue pari passo quanto abbiamo visto nel caso dei percettroni o delle reti lineari, e di conseguenza si utilizza il comando sim senza alcuna modifica sintattica. Chiaramente ciò su cui conviene soffermarci riguarda la politica di addestramento della rete. L'idea è quella di aggiornare i pesi e i bias in modo incrementale e partendo sempre da un training set di riferimento, di cui conosciamo ingressi e uscite. L'obiettivo è quello di minimizzare una funzione di errore che per default è la consueta sommatoria del quadrato degli errori tra uscita reale e uscita della rete ( sommatoria di tutte gli errori, uno per ogni linea di uscita ) definita qui sotto: Gli algoritmi di adattamento sono numerosi, ma tutti si basano sul concetto di backpropagation, vale a dire una modifica dei pesi nella direzione opposta al gradiente, inteso come derivata parziale dell'errore E sulla matrice dei pesi Wji. Un algoritmo di backpropagation è il seguente: Il Toolbox implementa algortimi a discesa del gradiente simili a quelli riportato sopra cercando di ottenere la miglior ottimizzazione possibile. In generale anche nel caso del backpropagation esiste l'apprendimento incrementale in cui i pesi vengono aggiornati ad ogni esempio e il batch training dove l'aggiornamento dei pesi avviene solo dopo che tutti gli esempi sono stati valutati. La funzione adapt, che abbiamo già analizzato, esegue l'apprendimento incrementale e la sua computazione varia a seconda di alcuni parametri che vengono fissati modificando le proprietà dell'oggetto net. In particolare si può scegliere il numero di epoche di aggiornamento ( net.adaptParam.passes ), il fattore di apprendimento ( net.LW{i,j}.learnParam.lr ) e la funzione di addestramento incrementale ( net.LW{i,j}.learnFcn - Di default è settata a learngd, ma è consigliabile utilizzare learngdm che risulta essere più efficiente ). E' consigliabile come al solito visualizzare l'elenco completo delle proprieta' dell'oggetto rete creato direttamente dalla consolle dei comandi di matlab. La funzione train, viceversa, esegue il training di tipo batch. In questo caso i gradienti calcolati ad ogni passo dell'algoritmo vengono sommati ( per aggiornare i pesi ) solo al termine della valutazione di tutti gli esempi. La funzione di apprendimento eseguita da train è settabile, come quella di adapt. In particolare sono 7 i parametri relativi a train che possono essere modificati.Quelli significativi sono: 1. net.trainParam.epochs = N : setta il numero delle epoche dopo le quali l'algoritmo termina ( se non converge prima ) 2. net.trainParam.lr = L : setta il fattore di apprendimento 3. net.trainParam.goal = E : rappresenta l'errore massimo che si può commettere tra l'uscita vera e quella reale e viene preso come test di uscita dell'algoritmo 4. net.trainParam.show = A, significa che ogni A epoche viene visualizzata nella finestra di output di matlab lo stato dell'algoritmo ( funzione di addestramento usata, numero di epoche trascorse, valore della funzione di errore confrontata con il goal test, valore del gradiente ecc ). La funzione di apprendimento è il famoso 4 parametro di ingresso di newff. Si osservi la tabella rappresentata in figura 2 per avere un idea delle prestazioni di questi algoritmi. Per maggiori informazioni sul loro significato, consultare il capitolo 5 della guida inglese al toolbox. Tempo Epoche MFlops (s) Funzione Tecnica traingdx Variable Learning Rate 57.71 980 2.50 trainrp Rprop 12.95 185 0.56 trainscg Scaled Coniugate Gradient 16.06 106 0.70 traincgf FletcherPowell CG 16.40 81 0.99 traincgp Polak-Ribiere CG 19.16 89 0.75 traincgb Powell-Beale CG 15.03 74 0.59 trainoss One-Step Secant 18.46 101 0.75 trainbfg BFGS quasiNewton 10.86 44 1.02 trainlm LevenbergMarquardt 1.87 6 0.46 Figura 2 - Prestazioni dei diversi algoritmi di backpropagation Il miglioramento della Generalizzazione L'ultimo aspetto che verrà trattato e che consentirà di acquisire nuovi strumenti per migliorare la propria rete neurale è inerente al problema del cosidetto overfitting. Può succedere molto frequentemente che, dopo l'addestramento della rete, l'errore calcolato sugli esempi del training set venga minimizzato ma che, quando nuovi dati vengono portati in ingresso alla rete stessa, l'errore ad essi associato sia molto grande ( uscite della rete discordanti da quelle attese ). In pratica la rete ha memorizzato gli esempi del training set ma non è stata addestrata per generalizzare le nuovi situazioni che ad essa si presentano. La figura 3 mostra il risultato di una rete 1-20-1 ( 2 livelli rispettivamente di 20 e 1 neurone e 1 ingresso) che è stata allenata per approssimare una funzione sinusoidale perturbata dal rumore. Figura 3 - Approssimazione di funzione con una rete soggetta a overfitting La funzione seno pulita dal rumore è graficata in modo tratteggiato, le misurazioni rumorose sono rappresentate dai simboli '+' mentre l'uscita della rete neurale è data dalla linea continua. L'obiettivo di questa rete è quello di avere una buona raffigurazione della funzione seno nonostante la presenza del rumore. Come si può facilmente osservare, le due curve non coincidono affatto e si dice che la rete ha causato un overfit dei dati in ingresso, ossia non ha predetto le corrette uscite come ci si attendeva. Uno dei metodi per ridurre questo problema è quello di progettare una rete con un adeguato numero di livelli e di neuroni. E' vero che, più una rete è grande e più complesse sono le funzion che può simulare, ma si è altrettanto osservato che la complessità della rete aumenta la sua possibilità di creare overfitting sui dati. L'aspetto critico è che attualmente non c'è nessuna teoria che illustri quanto grande deve essere progettata una rete in relazione all'applicazione in cui deve essere utilizzata. Il Neural Network Toolbox implementa due metodi per limitare il problema dell'overfitting. Il primo metodo è chiamato regolarizzazione e consiste nel modificare in modo opportuno la funzione di errore che viene minimizzata durante l'addestramento. Per default questa funzione è mse, definita precedentemente sempre in questa trattazione. E' tuttavia possibile modificare questa funzione aggiungendo un termine relativo alla sommatoria pesata dei quadrati dei pesi: La funzione di errore msereg contiene tutti i pesi della rete e, dato che viene minimizzata, questo comporta sempre la determinazione dei pesi più piccoli durante l'addestramento. Pesi di dimensioni ridotte sono fondamentali affinchè il responso della rete in prossimità a ingressi mai visti non si discosti troppo dal valore desiderato. E' possibile modificare il valore della funzione di errore attraverso il relativo attributo dell'oggetto net creato con il comando newff e specificare il coefficiente di performanza ( y ) che compare nella formula msereg. • • net.performFcn = 'msereg'; net.performParam.ratio = 0.5; Il problema principale della regolarizzazione manuale è che non è facile determinare il giusto valore del coefficiente y. Se è troppo alto, infatti, si rischia l'overfitting mentre se è troppo basso c'è il rischio che la rete non approssimi bene neppure gli esempi del training set. Questa situazione critica viene risolta affidandosi alle routines del toolbox che automaticamente settano il corretto valore di questo parametro. L'esempio di codice riportato qui sotto crea una rete 1-20-1 addestrata con la funzione trainbr che implementa in modo automatico alcune tecniche statistiche per determinare i valori dei parametri di performanza, togliendo notevole carico di lavoro al progettista. L'obiettivo è sempre quello di approssimare una funzione seno con rumore. • • • • • • • • net = newff( [ -1 1 ] , [ 20 , 1 ] , { 'tansig' , 'purelin' } , 'trainbr' ); net.trainParam.show = 10; net.trainParam.epochs = 50; randn( 'seed', 192736547 ); p = [ -1:.05:1 ]; t = sin( 2 * pi * p ) + 0.1*randn( size(p) ); net = init(net); net = train( net, p, t ); Si definisce un set di esempi costituiti da valori casuali di una funzione seno perturbata dal rumore ( rappresentato dall'aggiunta del termine casuale ai valori delle uscite t ). Sebbene il tutto è nascosto agli occhi del progettista, l'algoritmo memorizzato in trainbr provvede a determinare quanti parametri della rete ( pesi e bias ) sono necessari affinchè la rete stessa implementi una certa funzione. Nel nostro esempio. nonostante i parametri totali siano 61 quelli che sono stati settati diversi da zero ( e quindi tutti gli altri saranno inutilizzati ) sono appena 14, riducendo sensibilmente la dimensione della rete ( e ricordando che la grandezza di una rete è indice del suo grado di overfitting dei dati ). In figura 4 viene visualizzata l'uscita della rete in questione. Si può notare come i nuovi dati vengano generalizzati molto bene. Figura 4 - Approssimazione di funzione con una rete regolarizzata Un altro metodo per migliorare la generalizzazione è chiamato early stopping. L'idea è quella di creare un training set in base al quale modificare pesi e bias della rete nel consueto modo, e un validation set che serve per arrestare l'addestramento quando comincia a presentarsi il fenomeno di overfitting. In pratica si cerca di eseguire parallelamente l'addestramento della rete con il training set e la simulazione con il validation set. All'inizio dell'addestramento l'errore del validation set è piccolo, vale a dire che la rete si comporta bene anche con dati diversi da quelli con cui è stata allenata. Tuttavia, quando la rete comincia a overfittare i dati, anche l'errore sugli esempi di validazione aumenta e l'addestramente viene bloccato. Tutte le funzioni elencate in tabella 3 supportano l'early stopping. Non esiste un comando particolare per applicare questo metodo ma si tratta semplicemente di passare opportuni parametri di ingresso al comando train che già abbiamo analizzato. A tale scopo, questo è l'uso del comando che deve essere fatto. • net = train( net, p, t, [], [], v ); dove net è la rete, p e t rappresentano il training set e v è una struttura che contiene i vettori P e T, rispettivamente degli ingressi e delle uscite del validation set. • • v.P = [ -0.975:.05:0.975 ]; v.T = sin( 2 * pi * v.P ) + 0.1*randn( size( v.P ) ); Per concludere, osserviamo il risultato della rete soggetta a early stopping in figura 5. Nonostante la curva non sia regolare come nel caso precedente, la rete non genera overfitting dei dati. Figura 5 - Approssimazione di funzione con una rete addestrata con early stopping