Come fa un Artificial Neural Network ad imparare (spiegato nella maniera più semplice possibile )

Source: Deep Learning on Medium

Come fa un Artificial Neural Network ad imparare (spiegato nella maniera più semplice possibile 😓)

Nell’articolo “Come funziona un Artificial Neural Network (spiegato in maniera semplice)” abbiamo visto il funzionamento di una Rete Neurale artificiale già allenata, ovvero già in grado di prevedere un risultato a partire da determinate condizioni iniziali. Nello specifico abbiamo analizzato il funzionamento di un singolo neurone, e come la combinazione di più neuroni riesca a creare reti con grande potere predittivo. Abbiamo capito, inoltre, il ruolo cruciale svolto dai pesi attribuiti ai segnali in ingresso, in quanto questi dicono ad ogni singolo neurone su quali variabili concentrarsi al fine di attivarsi.

Il processo di determinazione di tali pesi coincide con l’apprendimento vero e proprio della rete neurale. Lo scopo di questo articolo è quello di spiegare come una rete neurale fa ad imparare, ovvero come tali pesi possano essere determinati.

Poiché una rete neurale artificiale mima il funzionamento di una rete neurale biologica, se vogliamo che questa impari qualcosa, dobbiamo fargli fare esperienza: la dobbiamo istruire.

Immaginiamo di voler insegnare all’algoritmo come distinguere tra un cane ed gatto. Esattamente come si farebbe con un bambino, gli mostreremmo più volte cosa sia un gatto o un cane, in modo da facilitarlo nella creazioni di preconcetti utili a distinguere un animale dall’altro.

Esempio — prevedere il valore di un immobile

Analizziamo questo processo passo dopo passo, ritornando all’esempio dell’articolo precedente relativo alla previsione del prezzo di un immobile.

Nella figura seguente viene riportato un esempio di data-set utilizzato per l’allenamento di tale rete neurale, e come questo venga utilizzato dalla rete stessa per imparare a prevedere il valore di un appartamento.

Schema riassuntivo del processo di apprendimento di una rete neurale

La forward-propagation

Lo scopo è allenare la rete neurale in modo che questa riesca a prevedere correttamente il costo di un immobile: ovvero dobbiamo identificare quei valori dei pesi “w” che partendo da Area, Distanza dal Centro, Numero di stanze ed Età dell’immobile permettano di identificarne il Prezzo.

Per dati valori di “w”, quando inseriamo i valori delle variabili indipendenti di una determinata riga della nostra tabella nell’input layer (giallo), il segnale si propaga da sinistra verso destra fino ad arrivare all’output layer (rosso), ovvero il nodo che fornisce la previsione del prezzo dell’immobile. Questo valore, che chiameremo(“y hat”) non è il prezzo effettivo, ma una sua previsione. Il prezzo reale dell’immobile, che chiamiamo y, è invece noto ed è leggibile nella tabella.

Visto che il nostro scopo è quello di avere previsioni sempre più precise, definiremo una funzione costo che tenga conto del discostamento della nostra previsione “ŷ” dalla realtà “y”.

La “cost function”

Anche in questo caso, come avviene per le funzioni di attivazione, in letteratura sono proposte diversi tipi di “cost function”. Noi adotteremo una funzione costo così definita CF = 0.5(ŷ-y)², ma per chi vuole cimentarsi in un po’ di matematica, si rimanda alla lettura del seguente articolo: A list of cost functions used in neural networks, alongside applications.

Il nostro obbiettivo è minimizzare la funzione costo. Pertanto, per ogni riga del nostro data-set di allenamento, calcoliamo il valore della funzione costo per ogni singola previsione CF_prev = 0.5(ŷ-y)².

Una volta percorso tutto il data-set diremo che è trascorsa un’Epoca e saremo in grado di calcolare il costo totale, dato dalla sommatoria di tutti i valori associati ad ogni riga, ovvero CF = Σ 0.5(ŷ-y)².

Quello che dobbiamo fare è solo trovare i pesi “w” che minimizzano la funzione costo. Semplice no? Ahimè no! qui iniziano i problemi…

Trovare i pesi che ottimizzano la Cost Function:

Tentativo #1 — “Forza bruta”. Semplifichiamo per un istante il problema: immaginiamo di avere una rete composta da un solo neurone e con un unico segnale in ingresso.

Assegnado un valore X al nodo giallo, di conseguenza, il nodo rosso assume un valore in base all’activation function e all’unico peso w.

Una strategia per trovare il valore ottimale di w potrebbe essere, banalmente, quella di provare quanti più possibili valori fino a quando non venga identificato quello che minimizza la Cost Function.

Immaginiamo di provare 1000 valori per w. Vediamo cosa accade quando proviamo ad applicare questa stessa strategia a reti neurali più complesse, come quella dell’esempio della previsione del prezzo di un immobile (che è pur sempre semplice).

Nell’esempio i pesi w da definire sono in totale 25, che vuol dire 1000 x 1000 x … x 1000 = 1000²⁵, ovvero 10⁷⁵ combinazioni possibili. Il computer più potente al mondo, nel momento in cui scrivo questo articolo, è il “ The Summit” che può svolgere fino a 2×10²⁷ operazioni al secondo. Per svolgere il calcolo servirebbero 5 x 10⁴⁷ secondi, ovvero 1,59×10⁴⁰ anni: più della vita dell’intero universo!

Tentativo #2 — “Gradiente discendente”. Proviamo a risolvere il problema in modo più intelligente, sfruttando un concetto che in molti abbiamo affrontato fin dalle scuole superiori: ricordate le derivate?

La derivata di una funzione ci dice quale è la pendenza della funzione stessa in un determinato punto. Il gradiente altro non è che lo stesso concetto applicato a funzioni più complesse come quella del nostro caso, che ha 25 variabili dipendenti.

Per ogni combinazione di valori di w, viene calcolato il relativo gradiente della cost function: se il gradiente è negativo, ci si sposta proporzionalmente verso destra; se è invece positivo, ci si sposta verso sinistra. E così via fino ad arrivare a pendenza 0.

Sembra funzionare! Ma cosa succede se la cost function non fosse convessa?

Il rischio è quello di non trovare la combinazione di valori w che minimizzano la cost function, ma di imbattersi invece in un minimo locale.

Tentativo #3 — “Gradiente discendente stocastico”. Dobbiamo trovare un metodo per evitare di imbatterci in un minimo locale, e la soluzione sta nel cambiare il modo in cui ottimizziamo.

Negli esempi precedenti calcolavamo la Funzione Costo Marginale per ogni riga del nostro dataset, poi le sommavamo assieme per trovare la funzione costo totale, e quindi calcolavamo il gradiente, aggiornavamo i pesi e ripetevamo l’operazione.

Il metodo del “Gradiente Discendente Stocastico” suggerisce invece di aggiornare i pesi dopo aver calcolato la Funzione Costo Marginale di ogni riga del dataset, prese in maniera randomica. Anche in questo caso il dataset viene ripercorso più e più volte (o epoche).

Questo approccio garantisce una maggiore fluttuazione della cost function ed evita, pertanto, di rimanere bloccati in un minimo locale. Inoltre, anche se si potrebbe essere portati a pensare il contrario, è anche più veloce: questo perché tale metodo non ha bisogno di memorizzare tutto il datasat prima di calcolare il gradiente, ma questo passaggio viene fatto record per record.

Proprio a causa della sua aleatorietà, questa metodologia può portare a risultati differenti ad ogni training pur partendo dallo stesso set di dati, al contrario del “Gradiente Discendente” semplice che è deterministico, e si ottiene sempre lo stesso risultato.

Backward-propagation

Reti più complesse possono essere composte da svariati Hidden Layer ciascuno composto da molti neuroni.

Una volta determinato il valore della funzione costo per un determinato set di valori dei pesi “w”, e calcolato il relativo gradiente della funzione nel punto, bisogna capire quale sia la responsabilità di ogni singolo peso nella determinazione dell’errore. Per fare ciò, esiste un algoritmo matematico molto complesso che permette di aggiustare i pesi tutti nello stesso momento, e che si basa su una propagazione del segnale a ritroso nel flusso, ovvero da destra verso sinistra.

A partire dall’output layer, si cerca di definire i valori dei pesi dell’ultimo strato in maniera che il risultato previsto sia il più vicino possibile a quello atteso. Poi si procede a ritroso verso gli strati interni fino ad arrivare all’input layer. Per ogni neurone il discorso è sempre lo stesso: trovare i valori dei pesi per i quali il valore restituito dal neurone sia il più vicino possibile a quello atteso dal neurone a valle. Questo processo si chiama Backward propagation.

Possiamo pertanto riassumere i passaggi con cui una rete neurale viene allenata:

  1. Inizializzare i valori dei pesi w con valori casuali vicini ma diversi da 0.
  2. Prendere casualmente una delle righe non ancora estratte dal dataset.
  3. Forward propagation — calcolo del valore atteso a partire dai valori X.
  4. Calcolare il costo come discostamento di da y.
  5. Backward propagation — aggiornare i pesi w per minimizzare il costo.
  6. Ripetere da “2” a “5” per tutti i valori del dataset.
  7. Ripetere per più epoche — ripetere i punti da “2” a “6”.