Apprendimento
Dato questo breve riassunto storico sulle ANNs, vorrei ora introdurre alcuni concetti fondamentali. Il senso di una ANN sta nel fatto che puo' apprendere delle regole ed utilizzarle in modo "creativo" per classificare gli input forniti. Per ottenere l'apprendimento una ANN deve essere addestrata, in una fase chiamata training. Durante il training vengono fornite alla rete diversi esempi di pattern che vogliamo essa classifichi. Dopo l'apprendimento possiamo dare in input alla rete un nuovo valore (un valore che la rete non ha mai visto durante il training) e se tutto e' andato per il verso giusto la rete classifichera' questo input nel modo corretto. L'apprendimento fa uso dell'algoritmo di backpropagation che ho nominato precedentemente. Per farvi afferrare l'idea cerchiamo di capire come funziona un cervello biologico. In modo semplicistico possiamo dire che ci sono dei neuroni, e delle sinapsi che connettono questi neuroni. Possiamo vedere i neuroni come unita' statiche, mentre le connessioni come dinamiche. Quando apprendete un nuovo concetto state stabilizzando le connessioni tra diversi gruppi di neuroni. Quando avete imparato ad andare in bicicletta eravate scoordinati e probabilmente procedevate a stento. La vista ed il senso dell'equilibrio erano l'input della rete, i movimenti l'output. Nella fase di addestramento le connessioni cerebrali nell'area motoria si sono organizzate in modo tale da permettervi di calibrare meglio i movimenti. L'output del vostro sistema motorio e' servito come metro di valutazione ed ha permesso di classificare il risulato ottenuto come buono oppure no. In base all'efficienza del movimento generato le connessioni nella vostra area motoria sono state piu' o meno rafforzate. Dopo una lunga pratica avete cominciato a muovervi in modo piu' fluido ed infine avete imparato a pedalare senza cadere di continuo (almeno spero). Un processo come questo avviene durante l'addestramento di una ANN. Viene fornito un input, viene generato un output e la bonta' di questo output e' stimata confrontandola con l'output ideale. Se l'output si discosta molto da quello ideale, le connessioni vengono modificate in modo sostanziale altrimenti il cambiamento e' lieve. In una ANN le connessioni non sono altro che valori numerici. Cambiare questi valori numerici e' compito dell'algoritmo backpropagation.
Funzionamento
Spiegare il funzionamento di una ANN senza ricorrere a simbologia matematica (che potrebbe spaventare qualcuno) e' compito arduo. Cerchero' di illustrate il tutto nel modo piu' semplice ed intuitivo possibile. Ci sono due fasi principali: forward, backward. Il forward non e' altro che la computazione dell'output della rete. Dal punto di vista tecnico il forward e' composto da due passaggi: moltiplicare l'input per le connessioni (in inglese: connections, parameters, weights) e passare il risultato ad una funzione di attivazione. Il risultato e' chiamato output ed e' generalmente un valore continuo compreso tra zero ed uno. Non spaventatevi! Vi assicuro che questi step tradotti in operazioni matematiche non sono altro che addizioni e moltiplicazioni. Questo modello e' volutamente iper-semplificato (ad esempio non ho utilizzato l'unita' di bias) e serve a farvi afferrare l'idea generale.
La seconda fase principale e' il backward. Il backward procede dall'output verso l'input ed e' lo step utilizzato nella fase di apprendimento. Il backward fa uso dell'algoritmo di backpropagation per aggiustare il valore delle connessioni. Ricordate che il passaggio di backward viene utilizzato solo durante la fase di addestramento. Una volta che la rete e' stata addestrata possiamo far uso soltanto del forward.
Passiamo ad un esempio concreto. Ho realizzato una rappresentazione grafica del Perceptron, che ci aiutera' a capire come funziona.
Il nostro perceptron ha tre unita', due di input (INPUT1 ed INPUT2) ed una di output. Ha due connessioni (weights) W1 e W2. Il nostro input numerico puo' essere un valore compreso tra 0 e 255. Attenzione, il valore di ingresso puo' essere di qualsiasi tipo, nel mio esempio utilizzo il range [0-255] perche' capita spesso di avere un valore simile come lettura di un sensore. Il nostro output e' un valore continuo compreso tra 0 ed 1. Immaginate che i due input siano la lettura di due sensori infrarossi che misurano la distanza da un ostacolo, il primo sensore e' puntato verso sinistra ed il secondo verso destra. L'ouput e' un valore che utilizziamo per muovere un servomotore. Al servomotore abbiamo collegato un pericolosissimo razzo USB come
questo. Abbiamo una torretta robotica di sorveglianza che vogliamo controllare con la nostra rete neurale. Vogliamo muovere il servo nella direzione dell'ostacolo piu' vicino e sparare. Bene, Il valore di output come dicevamo e' compreso tra 0 ed 1, dobbiamo rimappare questo output per muovere il nostro servo. Come fare? Semplice, questa e' la nostra convenzione: 0 significa muovi il servo a fine corsa in senso antiorario (sinistra), 1 significa muovi il servo a fine corsa in senso orario (destra), 0.5 muovi il servo nella posizione centrale. Ovviamente abbiamo tutti i valori intermedi che corrispondono ad altrettanti posizioni del nostro servo. Ora diamo dei valori ai due input. Facciamo una lettura dei due sensori infrarossi e vediamo INPUT1=218 ed INPUT2=35. Il valore di INPUT1 indica che abbiamo un oggetto a 35 cm di distanza. La nostra rete addestrata adeguatamente puntera' il razzo connesso al servo in quella direzione. I valori di W1 e W2 vengono inizialmente generati in modo casuale e devono essere prossimi a zero. Nel nostro caso W1=0.08 e W2=0.12. Ecco come calcolare l'ouput:
OUTPUT = f(INPUT1 * W1 + INPUT2 * W2)
Finito! La quantita' dentro le parentesi non e' altro che un insieme di addizioni e moltiplicazioni. La f e' la funzione di attivazione e non e' altro che un modo per schiacciare tra zero ed uno il risultato ottenuto in (INPUT1 * W1 + INPUT2 * W2). La funzione di attivazione piu' utilizzata e' chiamata
Funzione Sigmoidea. Trovate la sua implementazione in ogni linguaggio di programmazione e crearsela e' estremamente semplice. Dato un valore x l'output della sigmoide non e' altro che:
sigmoid_output = 1 / (1 + math.exp(-x))
Visto che abbiamo i nostri valori calcoliamo l'output:
OUTPUT = f(218 * 0.08 + 35 * 0.12) = 4.0 * 10^(-10)
Il valore 4.0 * 10^(-10) e' espresso in notazione scientifica, e significa che siamo prossimi a zero. Il nostro servo si muovera' a fine corsa in senso antiorario puntanto a sinistra. Ma aspettate, non e' quello che vogliamo noi. Il sensore che ha rilevato qualcosa e' il sensore 2 che punta a destra della nostra torretta. In questo modo spareremo nel vuoto. La rete neurale non e' stata ancora addestrata ed il suo output e' errato. Bene, il forward come avete visto e' estremamente semplice. Una volta che avete addestrato la rete vi serve solo questo passaggio per utilizzarla. Il backward purtroppo e' piu' complicato e per capire come funziona e' necessario conoscere cosa sono le derivate parziali ed il gradiente di una funzione. Cerchero' di spiegarlo in modo intuitivo, promettendo di entrare piu' nei dettagli in un prossimo tutorial.
Siamo nella fase di apprendimento, backward. L'output della nostra rete e' 0 ma noi vogliamo che sia 1 perche' la torretta deve puntare verso destra. Il valore del nostro output ideale dati gli input visti sopra e'quindi 1. Ora state molto attenti perche' questo e' il punto cruciale di tutto il discorso. Quello che fa l'algoritmo di backpropagation e' cambiare i valori delle nostre connessioni W1 e W2 in modo da avere un valore prossimo a 1 quando input simili a quelli del nostro esempio si ripresentato. Come per un cervello biologico i valori di W1 e W2 saranno cambiati in modo graduale al primo ciclo di apprendimento. Dopo il primo ciclo se diamo gli stessi input vedremo che l'output sara' leggermente diverso, ovvero sara' piu' lontano da 0 e piu' vicino ad 1, che e'proprio quello che vogliamo noi. Ripetendo il processo per un certo numero di cicli (chiamate epoche in gergo tecnico) la rete diventera' sempre piu' brava. L'apprendimento finisce quando la prestazione della rete su un campione di valori risulta buono secondo i nostri criteri di valutazione. Ad esempio, nel nostro caso possiamo fermare la fase di apprendimento se vediamo un ouput di 0.999 quando il sensore di destra e' attivo ed un output di 0.00001 quando quello di sinistra e' attivo.
Spero che abbiate afferrato le idee principali dietro le due fasi. Tenete conto che esistono molte librerie di reti neurali che effettuano le due fasi in modo automatico. Implementare un codice completo per la nostra torretta richiederebbe poche righe di codice.