Reti Neurali Artificiali e Deep Learning
Architettura e training delle reti neurali artificiali: percettrone, multilayer perceptron (MLP), funzioni di attivazione (ReLU, sigmoidea, softmax), loss function, gradient descent e back propagation.
Reti neurali
Le reti neurali, o ANN (Artificial Neural Network) è un metodo di machine learning che si ispira alla struttura e funzionamento di reti neurali biologiche presenti nei cervelli degli animali.
Una rete neurale è formata da unità connesse fra loro, chiamati nodi, che simulano il compito di un neurone. Sono connessi fra di loro tramite degli archi che simulano le sinapsi tra neuroni biologici.
Ogni neurone artificiale riceve dei segnali dai neuroni connessi ad esso, processa questi input e invia dei segnali ai neuroni collegati ad esso.
Il "segnale" è rappresentato da un numero reale, e l'output di ogni neurone è calcolato da una funzione non lineare della somma dei suoi input, chiamata la "funzione di attivazione".
La "forza" della connessione del segnale è determinata dai "pesi", ed è la parte che viene aggiustata durante il processo di learning

Percettrone
Il percettrone utilizza vari "neuroni" collegati ad un neurone finale per calcolare il valore dell'output. è visto come un modello a "sigolo strato", dato che contiene solo uno strato di neuroni:

Modelliamo un neurone in maniera semplice:
Un neurone può rievere segnali da altri neuroni, questi segnali sono combinati insieme, e se il risultato di questa combinazione supera una certa soglia, allora il neurone stesso lancerà un segnale, in caso che non superi la soglia, non fa nulla.
Possiamo modellare ciò vedendo come input numeri a questo punto il neurone calcola dove sono i valori dei "pesi".
Se , dove è un valore di "soglia", allora il neurone emette un segnale di "1" ai neuroni a cui è collegato, altrimenti il segnale avrà valore "0"
Il percettrone aggiunge un bias che viene aggiunto alla lista pesata dei neuroni, serve per aggiustare il "confine" del nodo finale.
- Con un bias , il modello classificherà l'input solo in base al risultato dei neuroni
- Con un bias , il modello ha bisogno di un valore maggiore dai neuroni per potersi attivare
- Con un bias , il modello ha bisogno di un valore minore dai neuroni per potersi attivare
A questo punto, la funzione , ritorna 0 se 0 e 1 se .
Per trovare il valore del percettrone, usiamo dove . Possiamo semplificare l'equazione rimuovendo il bias dalla equazione, e creando un nuovo neurone che ha come valore quello del bias
Training del percettrone
Abbiamo un training set di esempi dove ogni ha associato la label output . Fare il training del percettrone su vuol dire derivare i pesi che rendono le predizioni il più vicino possibile a per .
Euristica di Rosenblatt
è una tecnica usata per fare training di un percettrone.
Si parte da inizializzati con valori randomici, a questo punto, per ogni esempio del training set con indice calcoliamo come visto precedentemente, e aggiustiamo i pesi in questo modo:
dove è un iperparametro chiamato il "learning rate".
Assumendo che , allora questo training funziona perchè:
- Se , allora incrementa, dato che . Questo causerà di aumentare il proprio valore, avvicinandosi al valore
- Se , allora decresce. Questo causerà di diminuire il proprio valore, avvicinandosi al valore
In entrambi i casi, il valore del percettrone si avvicina a quello desiderato .
Questo ciclo viene eseguito più volte, dove ogni iterazione è chiamata "epoch" (epoca). L'algoritmo terminerà se arriva ad un numero massimo di iterazioni, o quando l'errore è abbastanza piccolo.
Se abbiamo una funzione di attivazione lineare , il percettrone corrisponde ad un modello lineare che separa linearmente i dati. Questo funziona bene se i dati sono linearmente separabili.

Però, il percettrone, come anche altri modelli, possono usare funzioni di attivazioni non lineari.
Percettrone multi livello
Il percettrone a singolo livello è molto limitato, ma diventa molto più interessante quando mettiamo in sieme (uno dopo l'altro), più strati.
A questo punto vediamo il percettrone in 3 strati principali
- L'input layer, che sono i neuroni iniziali con cui ci si interfaccia quando si da l'input da analizzare
- L'output layer è il neurone finale dove viene comunicato l'output
- L'hidden layer sono tutti gli strati intermedi tra l'input e l'ouput layer

Questo percettrone è chiamato "multi level perceptron" o MLP.
Ha un numero fissato di strati, ed ogni strato ha un numero fisso di neuroni . Ogni strato può avere un numero arbitrario di neuroni, eccetto l'ultimo che ne ha uno solo
Ogni neurone , dello strato , , riceve come input, gli output di tutti i neuroni dello strato precedente .
L'output del neurone viene calcolata come una combinazione lineare dei suoi input con i pesi , seguita dall'applicazione di una funzione di attivazione :

La matematica di un perceptrone multistrato può essere espressa in una forma più compatta e forse più comprensibile adottando una notazione matriciale.
Per semplificare la spiegazione di un layer con indice , usiamo "" per rappresentare l'input di questo strato (lo strato con indice ), ed usiamo "" per rappresentare l'output dello strato corrente (lo strato con indice )
Sia un vettore (colonna) -dimensionale contenente gli output dello strato di input .
Sia una matrice -dimensionale che raccoglie tutti i pesi dallo strato di input (dimensione ) allo strato di output (dimensione ), ovvero:
Le uscite possono essere espresse come:
dove è applicata elemento per elemento a tutte le componenti del vettore .

A questo punto denotiamo come la funzione dello strato i un MLP a strati. Abbiamo l'input dato all'MLP, allora la funzione finale che rappresenta l'intero MLP sarà del tipo:
Nel caso in cui tutte le funzioni di attivazione siano lineari, le funzioni effettuerebbero semplicemente una moltiplicazione, a questo punto potremmo rappresentare il tutto tramite una produttoria dei pesi, ma a questo punto stiamo creando l'equivalente di un percettrone a singolo strato.
Gli MLP sono considerati come "feed forward neural networks", cioè che i dati fluiscono da sinistra verso destra (da input ad output)
Funzione di attivazione
Si può modificare le funzioni di attivazione all'interno di un MLP in maniera da migliorare il learning del modello e gestire task che non sono solo quelli di classificazione binaria.
Di solito la funzione di attivazione è definita come un iperparametro e scelto durante la fase di training tramite lo step di validazione.
ReLU
La funzione di attivazione ReLU è quella più consigliata per la maggior parte dei MLP. é una funzione non lineare ma che rimane molto vicina ad essere lineare, infatti è "lineare in parti", perchè è una funzione che continene due "parti" lineari.
Dato che è quasi lineare, conserva molte proprietà dei modelli lineari, ed è facile da ottimizzare tramite metodi del gradiente, spesso usati per allenare gli MLP

Funzione sigmoidea
è la più comune approssimazione alla funzione "step", è monotona crescente ed ha limite 0 quando tende a e ad 1 quando tende a

Funzione iperbolica
è monotona crescente, ed ha limite -1 quando tende a e ad 1 quando tende a

Arctan
è l'inverso della funzione tangente, è monotona crescente, ed ha limite quando tende a e ad quando tende a

Softmax
è una funzione che converte un vettore -dimensionale in un vettore di valori reali che contiene la distribuzione di probabilità, cioè normalizzarle ad un valore compreso fra 0 e 1, e che sommano tutti ad 1.
Softmax:
è principalmente usato nell'output layer
Loss function
Fare il training di un modello richiede trovare i migliori pesi che minimizzano la "distanza" fra l'esempio in e il proprio label.
Questa funzione che calcola la distanza è chiamata la "loss function", formalmente espressa come che calcola la distanza fra la label calcolata dall'MLP e la vera label . Se la differenza è un valore grande, allora vuol dire che sia numericamente che "concettualmente", i due risultati sono molto differenti.
Questo è applicato su un singolo esempio del dataset, possiamo definire la funzione che calcola l'errore complessivo semplicementte effettuando una somma pesata di ogni esempio nel dataset
e quindi possiamo definire il problema di ML come un problema di ottimizzazione, dove dobbiamo minimizzare:
Gradient descent
Questo problema di ottimizzazione può essere risolto tramite una tecnica di ottimizzazione chiamata "gradient descent", che è l'opposto dell'algoritmo di hill climbing.
è una funzione definita come , la "direzione" dello steepest descent in un punto corrisponde al negativo del gradiente di in , denotato come:
che contiene le derivate parziali di su
Il gradient descent inizia ad un punto casuale , ed applica iterativamente:
- Calcola la direzione del deepest descent in , cioè
- Aggiorna facendo un passo verso la direzione del steepest descent, cioè: dove è un iper parametro chiamato il "learning rate" che dice "quanto grande" è il passo da fare verso lo steepest descent fino a quando non decresce più la funzione obiettivo, e quindi si è arrivati ad un minimo locale

Nel contesto del MLP, il gradient descent è applicato durante la fase di training, applicandola ai pesi per minimizzare la funzione obiettivo
Ora lo step chiave è di calcolare il gradiente della funzione , che può essere complesso, dato che la funzione implementata nell'MLP potrebbe essere molto complessa, dato che può essere composizione di molte funzioni, una per ogni layer.
Un modo per risolvere ciò sarebbe di applicare la "chain rule". Possiamo vedere la funzione da analizzare dell'MLP come:
La chain rule ci dice che la derivata , dove indica la derivata di . A questo punto possiamo applicare ricorsivamente la chain rule per ottenere:
dove
Back propagation
Eseguendo l'applicazione ricorsiva della chain rule ci potrebbe portare ad una duplicazione della stessa derivata, ripetendo inutilmente dei calcoli.
Per evitare ciò possiamo usare un approccio di programmazione dinamica, chiamato "back propagation". è chiamata in questo modo perchè l'algoritmo prima effettua un passo in "avanti" dove le derivate di ogni strato sono calcolate e salvate, per poi effettuare un passo "indietro" dove i gradienti vengono calcolati, muovendosi dall'ultimo al primo strato, combinando tramite programmazione dinamica, le derivate intermedie usate durante il forward pass
Il gradient descent ha delle limitazioni:
- funziona solo se la funzione da minimizzare è differenziabile, cosi da poter calcolare il gradiente
- ci si potrebbe fermare ad un minimo locale
- scegliere un corretto è difficile, se usiamo un troppo grande, potremmo saltare il minimo, se è piccolo, potremmo convergere troppo lentamente
- aumentare il numero degli strati potrebbe causare i gradienti di diventare molto piccoli o molto grandi, causando problemi di rappresentazione numerica
- Durante lo step backward, durante il calcolo dei gradienti dall'output layer all'input layer, i gradienti diventano man mano più piccoli più andiamo avanti, convergendo a 0. Questo causa i pesi ad essere aggiornati in maniera minima, e quindi mai convergere al minimo locale
- Allo stesso modo, i gradienti potrebbero essere molto grandi, causando grandi cambiamenti nei pesi, causando il modello a divergere.
Gradient descent stocastico
Calcolare l'errore complessivo del dataset ad ogni step del gradient descent può essere molto dispendioso. Per risolvere questo problema, usiamo una variante del gradient descent che approssima calcolando la loss function solo su un subset (piccolo) di , che chiamiamo "minibatch", e quindi calcolando la somma della loss function complessiva solo su questo subset.
- Usiamo l'intero training set D che è mischiato "randomicamente" e suddiviso in minibatch.
- Ogni minibatch è usato come singolo step del gradient descent
- Una volta aver processato tutti i minibatch, ci ritroviamo al punto in cui abbiamo analizzato tutti i dati nel nostro dataset, quindi consideriamo questo come una singola epoca di training
- Ripetiamo questo proccesso (randomizzando ogni volta) per un certo numero di epoche.

Loss functions
Elenchiamo alcune loss function più usate. Indicheremo con è la predizione, mentre la label corretta.
distance
è una metrica utile per problemi di regressione, la dimensionalità di e deve essere uguale.
NLL (Negative log-likelihood)
Assumiamo che ogni elemento di rappresenti la probabilità che quell'esempio faccia parte di una certa classe, e che la classe corretta sia il -esimo elemento.
CE (Cross entropy)
La funzione NLL richiede che i valori rappresentino una distribuzione di probabilità. il CE è semplicemente la funzione NLL ma che trasforma, tramite uno strato softmax, qualsiasi vettore numerico in una distribuzione di probabilità.
Softmax:
CE:
BCE (Binary cross entropy)
Corrisponde a NLL però nel caso di classificazione binaria. Assume che sia uno scalare e che sia uno scalare. rappresenta la probabilità che , e rappresenta la probabilità che
Deep learning vs Machine learning vs AI
Il termine deep learning si riferisce a un insieme di tecniche di machine learning in cui i modelli sono rappresentati come circuiti algebrici complessi, con connessioni i cui pesi (o "forze") possono essere modificati durante l'addestramento.
La parola "deep" ("profondo") indica che questi circuiti sono organizzati in molti strati: ciò implica che i percorsi di calcolo dagli input agli output attraversano numerosi passaggi.
Questi circuiti algebrici corrispondono a reti neurali artificiali. Di conseguenza, il deep learning utilizza reti neurali con molti strati di neuroni artificiali connessi tra loro.

Teorema dell'approssimazione universale
è un teorema molto importante sulla rappresentazione e potenza approssimativa delle reti neurali di funzioni arbitrarie.
Il teorema dice che un MLP con solo 1 output layer lineare e un hidden layer con qualsiasi funzione di attivazione non lineare, può approssimare qualsiasi funzione Borel misurabile, cioè qualsiasi funzione continua su un sottoinsieme chiuso e delimitato di , con qualsiasi errore non zero desiderato, a condizione che siano dati abbastanza hidden units.
Quindi ci dice che un MLP con un singolo hidden layer (ma non ci dice quanto grande questo hidden layer sia) riesce ad approssimare qualsiasi funzione Borel misurabile con un arbitrario livello di precisione.
Di solito, in pratica, nelle deep MLP, rappresentazioni più complesse sono costruite sopra rappresentazioni più semplici, arrivando ad una rappresentazione man mano più astratta e significativa.
